안녕하세요 😊
제가 아는 내용을 이해하기 쉽고, 재미있게 설명드려 여러분들이 성장하실 때 행복함을 느끼는 개발자 최태현입니다.
(현) 캐치테이블[와드] 소프트웨어 엔지니어
(전) 스타트업 소프트웨어 엔지니어 리드
(전) 배달의민족[우아한형제들] 소프트웨어 엔지니어
(교육활동) Next Step 리뷰어 다수 참여, 공기관 & 스타트업 경진대회 강사 및 멘토, 스파르타 코딩클럽 멘토
한국과학기술원 (KAIST) 졸업
강의
수강평
- 자바 개발자를 위한 코틀린 입문(Java to Kotlin Starter Guide)
- 자바 개발자를 위한 코틀린 입문(Java to Kotlin Starter Guide)
- 실전! 코틀린과 스프링 부트로 도서관리 애플리케이션 개발하기 (Java 프로젝트 리팩토링)
- 자바 개발자를 위한 코틀린 입문(Java to Kotlin Starter Guide)
- 자바 개발자를 위한 코틀린 입문(Java to Kotlin Starter Guide)
게시글
질문&답변
companion object
안녕하세요! KMC님! 🙂꼭 그렇지는 않습니다! 보통 data clas 를 활용한 DTO 개념이 인스턴스화를 직접 할 일이 많다 보니 정적 팩토리 메소드가 들어갔을 뿐, 실제로는 활용하기에 적절한 곳이라면 정적 팩토리 메소드를 어디나 적용하는 편입니다!답변이 도움이 되었으면 좋겠습니다. 감사합니다. 🙇
- 0
- 2
- 24
질문&답변
Custom 레프직토리 형식
안녕하세요! KMC님~ 🙂 좋은 질문 감사드립니다.하나씩 답변 드려 보면! 저는 사실 Impl 형식을 완전히 사용하지 않습니다. 몇 년 전에 Querydsl을 사용할 때 쓰던 방식이라고만 알고 있고, JPAQueryFactory를 직접 주입 받아 처리 하는 방식이 훨씬 편해서 그 방식을 선호하고 있어요! ☺ 굳이 따지면 Impl 형식을 쓸 경우 하나의 Repository 에서 SimpleJpaRepository 의 기본 기능 (ex. save) 을 한 번에 쓸 수 있다는 잠점이 있겠네요 또한 저는 조회 기능은 join 과 무관하게 무조건 Querydsl로 작성하는 편입니다. 단순한 Spring Data JPA 의 몇 가지 아쉬운 점에서도 소개드렸던 것처럼 조회 기능은 Querydsl 구현이 훨씬 편하다고 생각해서요! 요즘은 AI 도구가 많이 발전해 Spring Data JPA의 선언식 함수 findByXX 의 '간단하다는' 장점도 많이 줄어든 것 같습니다. 제가 말씀드린 내용이 정답은 아니고, 개인적은 의견을 공유드린 정도입니다!답변이 도움이 되었으면 좋겠습니다. 감사합니다. 🙇
- 0
- 2
- 23
질문&답변
강사님
안녕하세요! KMC님~ ☺ 좋은 질문 이시네요~결론부터 말씀드리면 예외 클래스를 적절하게 다루고 있으신 것 같아요! 저 역시 custom exception class가 계속해서 생기는 것 보다는 하나의 예외 클래스와 예외 case를 다루는 enum의 조합을 선호하는 편입니다물론 던져진 예외를 말씀해주신 ControllerAdvice에서 잡게 되고요~ 보다 디테일하게는 예외 경우를 어떻게 나눌지 표준 예외와 custom 예외를 언제 어떻게 사용할지 등도 고민해보시면 좋을 것 같습니다.현업에서는 개발자 분마다 성향이 다르셔서 무조건 정해진 규칙은 없는 것 같아요! 답변이 도움이 되었으면 좋겠습니다. 감사합니다~ 🙏
- 0
- 1
- 28
질문&답변
강의 복습하면서 생긴 질문
안녕하세요!! ☺ 언제나 그렇듯 질문 주셔서 감사합니다.하나씩 답변 드려 보겠습니다!이러면 자동으로 getter/setter를 자동으로 만들어주신다고 하셨는데요. 저는 이것이 자동으로 만들어주는게 아니라 당연하다고 생각합니다. 왜냐하면 프로퍼티 접근 제어자가 public이니까요!자바도 public으로 변경하면 코틀린처럼 필드에 접근해서 사용이 가능하다고 생각이 드는데요. 이런 케이스에 대해서 태현님의 의견을 좀 더 듣고 싶습니다는 아마 이런 의견이신 것 같아요!public class Person { public String name; public int age; }처럼 Java 에서도 퍼블릭 클래스 + 퍼블릭 필드를 갖고 있다면 아래 코드와 다를게 없는데왜 굳이 강의에서class Person( val name: String, val age: Int, )라는 코드를 "자동으로 getter/setter를 자동으로 만들어준다" 라고 표현했는가? 어차피 그게 그거 아닌가? 이에 대한 제 생각은 겉으로 보기에는 그럴 수 있어도 실제 코틀린이 .class 로 변환되는 상황에서 동작하는 구문을 정확히 알려드는게 좋다고 생각했어요. 이런 내용을 잘 알고 있어야 custom getter 와 같은 개념도 더 쉽게 이해할 수 있다고 생각하거든요~ 충분히 표면적으로 다를게 없는 것처럼 느끼실 수 있다고 생각합니다. ☺ 또한, 당연히 코틀린에서는 getter를 호출하겠지만 필드 자체를 private으로 두지 않으면 객체지향 원칙중에 하나로 캡슐화가 좋지 못하다는 판단이 들더라구요! 또한 코틀린에서는 접근제어자를 private으로 두면 당현한 이야기지만 강의에서 설명주신 것처럼 접근이 안되서 강제 getter/setter를 만들어야 하는데 이것에 대해도 듣고 싶습니다.다음으로 이 내용에 대해서도 말씀을 드려보면, "캡슐화" 에 대한 사람들의 생각이 모두 다를 수 있다고 생각해요. 저는 필드 자체가 private 이라면 당연히 좋지만 만약 getter가 반드시 필요한 상황이라면, 이것이 캡슐화를 크게 해치는가? 라고 생각했을 때 그렇지 않다고 봅니다. 물론 완전무결한 private 필드만을 갖고 있는 클래스 보다야 캡슐화가 깨져 있다고 볼 수 있지만, 매우 당연하게도 실무에서 private 필드만 사용해서 기능을 구현하는 것은 불가능합니다. 그렇다면 결국 어느 지점에서는 public한 접근이 어느정도 필요한데요 저는 그 타협점이getter는 public을 자유롭게 써도 되지만, 꼭 필드를 가져가야만 하는지 (흔히 얘기하는 디미터 법칙을 생각할 수 있죠) 한 번 쯤 더 고민해야 한다고 보고setter는 코드 편의성을 위해 public으로 열어두더라도 절대 객체 바깥에서 사용해서는 안된다라고 생각합니다. 사람마다 의견이 다를 수 있다고 생각하지만, 저는 개인적으로 위 타협안을 적용했을 때 가장 좋은 결과물을 만들 수 있었다고 생각해요! 논리적으로 도메인 흐름상 하나의 파라미터만 있는 생성자는 사용되지를 않겠지만 이런 케이스에서 코틀린은 어떻게 처리가 가능할까요? 코틀린에서는 설명해주시기로 부생성자로 만들지만 부생성자는 주생성자를 호출해야 하는 꼴로 가야한다고 답변을 주셔서요!마지막으로 이 질문에 대해서도 답변 드리면~ 코틀린에서는 default parameter를 사용할 수 있습니다.class Person( val name: String, val age: Int = 0, )과 같은 클래스를 작성하시면 밖에서는 Person("A") 로 이름만 초기화 할 수도 있고, Person("A", 10) 처럼 name 과 age 모두를 초기화 할 수도 있습니다.매개변수가 완전히 다른 경우는 secondary consturctor를 사용해야 겠지만, 그렇지 않다면 보통 이 선에서 해결되더라고요~ 답변이 도움이 되었으면 좋겠습니다. ☺ 감사합니다.
- 1
- 1
- 38
질문&답변
Dispatchers 별 차이점 관련 질문
안녕하세요 기우님! ☺ 아주 좋은 질문 감사드립니다.답변을 드리기에 앞서 https://myungpyo.medium.com/%EC%BD%94%EB%A3%A8%ED%8B%B4-%EB%94%94%EC%8A%A4%ED%8C%A8%EC%B3%90-%EC%A1%B0%EA%B8%88-%EB%8D%94-%EC%82%B4%ED%8E%B4%EB%B3%B4%EA%B8%B0-92db58efca24 와 같은 좋은 글도 한 번 보고 오시면 좋을 것 같아요! 이제 하나씩 답변 드려 보겠습니다.각 Dispatcher는 내부적으로 스레드풀을 가지고 있고, 작업이 넘어왔을 때 내부적으로 관리하는 스레드 풀에서 스레드에 코루틴을 할당하는 방식으로 동작하는걸까요?이 내용 먼저 말씀드려볼게요!네 맞습니다. Default와 IO는 내부적으로 스레드 풀을 갖고 있고 (예를 들어 IO의 경우 최대 64개를 갖고 있습니다.) 코루틴이 들어오면 내부적으로 관리하는 pool 내의 thread에서 코루틴이 실행되고 중단되었다가 다시 pool 내의 어떤 thread에 배정해 코루틴을 실행하는 식으로 동작합니다.다만, Default의 스레드 풀을 IO가 포함하고 있는 관계라 (ex. https://sandn.tistory.com/112) IO 에서 실행하더라도 Default 라는 thread name을 확인할 수도 있습니다. Uncofined는 조금 특이합니다. 자체 스레드 풀이 존재하지 않고, suspend 함수가 재개될 때의 그 스레드에서 다음 코드를 이어서 실행시켜 버려요! (Default나 IO는 suspend 함수가 재개 되면 다시 내 pool의 스레드로 코드를 가져와 실행하죠) 이런 특수성 때문에 Unconfined의 경우 실무에서 사용 할 일이 없는 Dispatcher 라고 봐주시면 될 것 같습니다. 내부 코드를 봤을땐 스레드 관리 방식과 스케줄링 방식 등에 차이가 있는 것 같은데, 각각의 Dispatcher가 언제 사용되는지, 가장 주요한 차이점은 뭔지가 궁금해요!!그럼 남은 Dispatcher는 Default와 IO인데요! 간단하게 Default는 CPU-intensive한 작업, I/O는 IO-intensive한 작업에 사용하시면 됩니다. ☺ 마지막으로Default 나 IO Dispatcher의 스레드 개수나 graceful shutdown 설정 등을 커스텀할 수 있는지도 궁금합니다!에 대해서도 답변 드리면 Dispatcher 자체적으로 graceful shutdown hook을 제공하지는 않지만, 마치 Executors 에 대해 저희가 직접 graceful shutdown 기능을 구현할 수 있는 것처럼 직접 구현할 수 있습니다.(ex. https://easywritten.com/post/best-way-to-shutdown-executor-service-in-java/ 에서 Java 표준 docs에 적혀 있는 Executor graceful shutdown 구현을 설명과 함께 보실 수 있습니다)// val scope = CoroutineScope(...) suspend fun shutdownGracefully(timeoutMs: Long = 5000) { // 더 이상 새 작업을 받지 않게 하고 scope.coroutineContext[Job]?.cancel() // 자식들에게 취소 신호 // 일정 시간 내 정상 종료를 대기 withTimeoutOrNull(timeoutMs) { rootJob.join() } // 필요시 미완료 작업 강제 중단 로직 추가 }와 같은 코드를 구현할 수 있겠죠. (구현하기 나름입니다) 역시 비슷하게 custom dispatcher를 만드는 경우도 보통은 Executors.newFixedThreadPool(n).asCoroutineDispatcher 를 사용하니 이 때 만들어지는 Executor 를 이용해 graceful shutdown을 집어 넣을 수 있습니다 ☺ 또한 Executor 를 이용해 custom 스레드 풀을 직접 만들어 스레드 수를 제어하는 방법을 쓰실 수도 있고요!아니면 원래 존재하는 Default pool에서 개수를 격리하거나 아예 원하는 thread 수를 만들어 IO 처럼 사용할 수도 있습니다. 바로 limitedParallelism 함수인데요 공식 문서는 https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-coroutine-dispatcher/limited-parallelism.html 에서 보실 수 있고, https://velog.io/@geronimo124/Kotlin-Coroutines-dispatchers 와 같은 블로그에서는 Default와 IO에 limitedParallelism 함수를 사용했을 때 어떤 메커니즘이 적용되는지 그림으로 쉽게 보실 수 있습니다! ☺ 답변이 도움이 되었으면 좋겠습니다. 감사합니다. 🙇
- 0
- 1
- 43
질문&답변
Kotlin과 Java의 현업에서의 활용
안녕하세요 건영님! ☺ 질문 남겨 주셔서 감사합니다.혹시 Kotlin + SpringBoot의 프로젝트 작업을 진행하실 때 전혀 Java를 사용하지 않고 활용하시는지 아니면 Java와 Kotlin을 같이 사용하는 지 현업에서 실제 어떻게 활용하시는지 궁금합니다.부터 말씀드리면 선택권이 저에게 있다면 Kotlin + Spring 만 사용하는 편이고요! 만약 Java로 된 Legacy 코드가 이미 존재한다면 상황에 따라 유연하게 대응하는 편입니다. (점진적으로 Kotlin으로 된 코드를 늘려 Java와 Kotlin을 혼용하거나 Java로 코드를 작성할 수도 있죠 ☺) 추가적으로 apache 같은 외부 라이브러리를 가져와 사용할 때는 Kotlin으로 따로 제작된 코드를 굳이 사용하지는 않고 Java로 된 외부 라이브러리를 바로 사용하는 편입니다. 물론, 간단한 함수만 필요하다면 굳이 외부 의존성을 가져오지 않고 해당 함수만 Kotlin으로 옮겨오거나 Kotlin 표준 라이브러리에서 이미 제공하고 있는 기능은 아닌지 검색해보기는 합니다. 답변이 도움이 되었으면 좋겠습니다. 감사합니다. 🙇
- 0
- 2
- 54
질문&답변
aws 프리티어 정책
안녕하세요! best11gh님~ 좋은 질문 남겨주셔서 감사합니다. ☺결론부터 말씀드리면, 강의 실습을 얼마나 빠르게 하는지에 따라 달라질 수 있어 보이는데요.. 저희가 사용했던 EC2 저사양 인스턴스는 (t2.micro) 시간당 요금이 0.0144 USD 로 중단 없이 사용했을 때 1년 반 정도는 쓸 수 있는 것으로 보입니다.(물론, 프리티어 제한이 6개월이라 실제로는 6개월 리밋에 걸릴 것 같네요~) 때문에 강의를 듣다가 중도에 멈춰 시간이 아주 지연되는 경우는 크레딧 안에서 커버되기 어렵겠지만 그래도 몇 달 안에 강의를 모두 마무리 하시면 충분히 프리티어 리소스로 실습이 가능하실 것으로 보입니다.구체적인 비용 내용은https://aws.amazon.com/ko/ec2/pricing/on-demand/에서 리전 (region, 지역) 을 서울로 선택하고 micro 시리즈를 확인해보시면 되어요! 또한 62강 내용에서 다룬리소스를 정리하는 방법비용을 계산하는 방법모두 여전히 적용 가능한 내용입니다! ☺ 답변이 도움이 되었으면 좋겠습니다. 감사합니다.
- 1
- 2
- 70
질문&답변
Querydsl 도입
안녕하세요! Mola-Mola님! ☺ 좋은 질문 감사합니다.말씀해주신 레거시 쿼리가 무엇인지에 따라 답변이 조금 달라질 것 같아요~ native raw level 쿼리인지 아니면 Spring Data JPA 레벨의 쿼리인지 또 다른 무언가인지에 따라서요!다만 저는 개인적으로 Querydsl을 좋아하는 이유가 말씀해주신 컴파일 레벨의 오류 감지 외에도동적 쿼리 작성의 편안함query의 중복 제거 원활함 도 있는데요! 동적 쿼리란, https://jojoldu.tistory.com/394 에서도 확인할 수 있는 것처럼 하나의 API 에서 N개의 변수를 사용자 선택에 따라 조합하는 경우를 의미합니다. 만약 raw level SQL을 직접 사용한다면, 상황에 맞는 쿼리를 모두 작성해주거나 꽤 높은 확률로 오류 위험성이 높은 문자열 연산을 사용해야 하죠. 반면 Querydsl은 매우 용이한 동적 쿼리 작성을 도와줍니다. 또한 Query를 code-level에서 building 하는 방식이기에 중복 제거도 매우 원할합니다.예를 들어 A + B + C 테이블이 조인된 상황에서 where c1 = ? 만 쓰는 쿼리가 있고, where 조건 없이 group by 만 덧붙이는 경우가 있다고 할 때 "A + B + C 테이블이 조인된 상황" 자체를 baseQuery로 한 번만 선언한 후 각 쿼리의 변화되는 부분만 JPAQuery에 이어 붙일 수 있죠이를 활용하면 코틀린의 확장 함수를 적용해 레고 블록을 조립하듯 타입 안전하게 쿼리를 작성할 수 있게 됩니다. 아주 간단한 예시로 아래와 같은 확장 함수를 선언해두면 Querydsl 기반 쿼리 어디든 paging(pageable)만 덧붙여 페이징 쿼리를 만들어 낼 수 있죠. fun JPAQuery.paging(pageable: Pageable): JPAQuery { return this.limit(pageable.pageSize.toLong()) .offset(pageable.offset) } fun JPAQuery.paging(page: Int, size: Int): JPAQuery { val pageable = Pageable.ofSize(size).withPage(page) return this.limit(pageable.pageSize.toLong()) .offset(pageable.offset) }아주 작은 예시이고 활용법은 매우 무궁무진 합니다. ☺ 추가적으로 "레거시는 두고 신규 추가되는 부분만 Querydsl를 도입하는 식으로 가면 될까요?"라고 말씀해주셨는데요~ 참 어려운 부분입니다. 상황에 따라신규 추가되는 부분만 새로운 기술을 도입하기신규 피처 외에도 기존 기능을 변경할 때도 새로운 기술을 적용하기최선을 다해 기존 기능을 완전히 마이그레이션 하기등 여러 전락이 있거든요. 이는 제가 답변 드리기 어렵고 프로젝트 상황에 따라, 함께 일하는 분들의 성향에 따라 많이 달라질 수 있습니다.답변이 도움이 되었으면 좋겠습니다. 감사합니다. 🙇
- 0
- 2
- 36
질문&답변
fetch join DISTINCT 중복제거
안녕하세요! 지원님 ☺ 좋은 질문 감사드립니다.양쪽에서 다르게 동작한다는 것은 다음과 같은 의미일 것 같아요~SQL distinct 의 경우 동일한 column 조합으로만 Database가 데이터를 걸러낸다는 뜻이고JPA에서 사용한 JPQL distinct는 join을 통해 나온 엔티티를 부모 엔티티 기준으로 서버가 (즉, 애플리케이션이) 묶어 낸다는 뜻이죠!또한 두 방식 모두 제가 아는 선에서는 "완전한 중복 제거"를 보장합니다. 그래서 GPT가 어떤 맥락에서 완전한 중복 제거가 보장되지 않는다고 한지는 정확히 모르겠네요~ 🤔 관련해서 한 가지 주의하면 좋은 부분은 데이터 양이 꽤 많아지면 (가령 수백만 건의 부모의 1:N 구조를 한 번에 fetch join + distinct 하게 되면...) 수백만개의 컬렉션을 연산해야 하기에 속도가 느려질 수 있다는 문제가 있고, 이를 해결하는 접근은 다양하게 있어 상황에 따라 적당한 방법을 선택해야 합니다. 가장 대표적으로는 조회되는 패턴을 확인해 미리 집계해 두는 방식도 있어요~ 답변이 도움이 되었으면 좋겠습니다. 감사합니다. 🙇
- 1
- 2
- 37
질문&답변
표준 예외와 커스텀 예외 사용 전략 질문
안녕하세요 민우님! 🙂 좋은 질문 감사합니다. AI 인턴이 남겨준 링크도 한 번 읽어보시면 좋을 것 같고요!남겨주신 질문에 대해서도 직접적인 답변을 드려보면...스프링이 제공하는 표준 예외(IllegalArgumentException, IllegalStateException)와 비즈니스 로직을 표현하는 커스텀 예외(NotFoundUserException) 사이에서 표준 예외를 사용하는 경우와 커스텀 예외를 사용하는 경우에 대한 기준을 알 수 있을까요? 우선 IllegalArgumentException, IllegalStateException 와 같은 Exception class는 스프링이 제공하는 것이 아니라 자바 (JDK) 에서 제공하고 있습니다. 둘을 구분해주시면 조금 더 좋을 것 같아요!또한, 저는 다음과 같은 기준을 갖고 커스텀 예외를 사용하는 편입니다. 매개변수에 대한 검증이 실패한 경우이 경우는 매개변수, 즉 Argument가 잘못되었다는 의미이기에 표준 예외 IllegalArgumentException 를 사용합니다.또한 들어온 매개변수로 인해 특정 Entity를 찾지 못하는데 예외를 던져야 하는 경우도 매개변수가 잘못되어 Entity를 찾지 못했을 확률이 높기에 IllegalArgumentException 를 사용하는 편입니다.특정 로직을 수행하다 객체의 pre state가 잘못되었을 경우비즈니스 로직을 구현하다보면 특정 로직에는 precondition 이라 불리는 가정이 들어갑니다. 예를 들어 신호등이 초록색으로 바뀌기 위해서는 빨간색이거나 노란색이어야 한다는 가정이죠. 이런 경우 색을 초록색으로 하려고 했는데 진작 초록색이라면, 로직 실행전의 객체 필드 (= 상태) 가 잘못 되었다는 의미이기에 IllegalStateException 을 사용합니다.클라이언트에서 예외를 분기 처리 해야 하는 경우단순히 HTTP status로 내용을 전달하기에 충분하지 않고, 상황별로 적절한 문구를 사용자에게 노출해야 한다거나 FE 로직에 대한 분기를 태워야하는 경우는 custom JSON을 내려줘야만 하고, (보통 @ControllerAdvice 같은 친구를 사용하죠 ☺) 이런 경우는 커스텀 예외를 사용합니다.다만, 매번 커스텀 예외 클래스를 만드는 것은 매우 번거롭고 클래스 수가 늘어나 관리가 어렵기에 Exception class는 1개를 만들고, 상황에 따른 enum을 구성하는 것을 매우 선호합니다.https://velog.io/@jds7979/Spring-%EC%A0%84%EC%97%AD%EC%98%88%EC%99%B8%EC%B2%98%EB%A6%AC-with-EnumGlobal-Exception-Handler 와 같은 글을 참고해보시면 좋을 것 같아요! (이 글에 100% 동의한다기 보다 예시로 봐주시면 되어요!)감사합니다. 🙇
- 0
- 3
- 33






