게시글
질문&답변
지식공유자님 inline에 대해서의 질문과 추가적인 질문 1가지가 있습니다!
아이고 아닙니다~ 열심히 들어주시는데 제가 더 감사드리죠! 🙏 하나씩 답변 드려 보겠습니다. [1. inline 프로퍼티 + backing field]우선 사용해주신 inline 프로퍼티 안의 this.name 은 사실상 getter입니다.그러니 inline 으로 작성한 코드가 실제 사용부에 옮겨 지더라도 InlinePropertyPerson.getName()과 동일한 효과를 보일테니 문제가 없죠!하지만 backing field는 실제로 내부 필드가 생기고 이 필드에 접근하는 방식으로 구현됩니다! (디컴파일 해보시면 확인 가능하세요! 🙂) 그리고 inline이 컴파일 타임 때 외부로 옮겨지면 당연히 이 필드에 직접 접근할 수는 없으니 inline 안에서는 backing field를 사용할 수 없습니다. [2. inline과 디버깅]맞습니다! 어떤 tool 에서는 inline을 고려하지 않고 compile class를 기준으로 line 수 등을 출력하다 보니 inline이 특정 영역에 붙여 졌다고 가정하고 에러 로그가 나오는 경우가 있더라고요!다만 개인적으로는 몇 년간 코틀린을 사용해 오며 inline 때문에 디버깅이 어려웠던 적은 없는 것 같습니다. inline을 사용하더라도 결국 문제가 될 만한 부분을 명확하게 특정할 수 있었기 때문인데요!혹시나 앞으로 inline으로 불편했던 경험이 생기면 이 글로 찾아 오겠습니다... 😊 (noinline은 결국 inline 함수의 매개변수로 쓰이는 함수를 인라이닝 하지 않는 것이다 보니 inline 함수 자체를 쓸 때는 비슷한 느낌으로 불편함을 겪을 수도 있을 것 같아, 근본적인 해결책은 아닌 것 같습니다!) [3. 라벨에 대한 코드 스타일]제 개인적인 생각으로.. 제일 좋은건!listOf(1L, 2L, 3L).map { it + 1 }이라고 생각합니다!다만 it + 1 처럼 연산이 단순하지 않고 여기서 추가적인 로직이 필요한 경우는listOf(1L, 2L, 3L).map { num -> val a = xxxService.handleA(num) val b = yyyRepository.handleB(a) b }처럼 라벨을 사용하지 않는 편이 깔끔하다고 생각하고, 더 좋은건...private fun handleNum(num: Long): B { val a = xxxService.handleA(num) return yyyRepository.handleB(a) }처럼 아예 listOf(1L, 2L, 3L).map { handleNum(it) }map 쪽 코드를 깔끔하게 만드는 거라 생각합니다!물론 개인적인 선호 이고, 제가 지금 당장 생각하지 못하는 또 다른 상황에서는 다른 의견을 드릴 수도 있습니다!! 😅 도움이 되었으면 좋겠네요~!! 강의 열심히 들어 주셔서 항상 감사드리고~ 추가적인 질문 기다리겠습니다!! 감사합니다. 🙇
- 1
- 1
- 28
질문&답변
안녕하세요 JMH 설정 관련 팁을 드릴까 합니다! (24년 12월 16일 기준)
안녕하세요 보키님! 🙂 아유~~ 꿀팁 감사합니다!! 🙇 다른 분들께도 많은 도움 되겠네요~~
- 0
- 2
- 23
질문&답변
Generic Func 질문이 있습니다
안녕하세요 보키님! 🙂 말씀해주신 2번째 / 4번째 코드에서 에러가 발생하는 이유는T에 대한 제약을 가할 수 있는 fun 에 : Any 와 같은 제약을 가한게 아니라실제 T 타입을 사용해야 하는 List.functionName() 이나 parameter: List 에서 제약을 가하려 하셨기 때문입니다! 🥺 문법 적인 오류로 봐주시면 될 것 같아요! fun List.hasIntersection(other: List): Boolean { return (this.toSet() intersect other.toSet()).isNotEmpty() }라고만 하시더라도 앞에 사용된 List 에는 Any의 하위 타입이 들어와 하고, other: List 에도 Any의 하위 타입이 들어와야 한답니다!"생각해보면 들어갈 수 있는 타입은 nullable인데 반환타입은 not null이거나 그 반대가 되면 헷갈릴 것 같아요. 그것을 막아둔것같기도 하구요!" 라고 생각해주시는 부분도 일리가 있고~ 제약 자체를 fun 과 수신객체 타입 사이에서만 할 수 있다고 이해해 주셔도 좋을 것 같아요!넵! 보내주신 hasIntersection2 처럼 사용한다고 봐주시면 될 것 같습니다! ☺ (Number 타입의 List만 만드는 경우는 드물고 보통은 단순 T 타입으로만 많이 처리하는 것 같아요, 물론 숫자 연산이 필요하면 Number 제약을 줄 수도 있습니다) 아이고~ 잘 들어주셔서 감사합니다. 강의 들으시다가 또 궁금한 점 생기시면 질문 남겨주세요! 🙇 p.s.앗 아쉽네요~!! 🥺 🥺 다음에 뵐 수 있는 기회.. 기다려 보겠습니다.. 🍁
- 1
- 1
- 28
질문&답변
코틀린 val 질문
안녕하세요 terry9611님! 좋은 질문 감사드립니다! ☺ 저는 private 프로퍼티를 만들고 외부 노출이 필요한 경우에 private 구문을 없애 (default가 public 이니까요) 외부에서 getter를 사용하는 편입니다. "후자일 경우 캡슐화 위반" 이라고도 표현해 주셨는데, 아래 캡슐화가 무엇인지에 따라 사람마다 다르게 느껴질 수 있을 것 같습니다! 제 개인적으로는public class Person { private String name; public String getName() { return this.name; } }위의 Java 코드에서 getter가 꼭 필요하다면 캡슐화 위반으로 느껴지지는 않아요!마찬가지로 위의 자바코드와 완전히 동일한class Person(val name: String)역시 캡슐화 위반으로 느껴지지 않는 것 같습니다. 감사합니다. 🙇 또 궁금한 점 있으시면 편하게 질문 남겨주세요~!!
- 0
- 2
- 15
질문&답변
완강 후 Kotlin 이런저런 질문 드려봅니다!
[4. data class]질문을 상세히 나눠서 답변드리겠습니다. 🙇 data class의 강력한 기능 중 하나인 copy 메서드는 백엔드의 프로덕션 개발과 테스트 코드에서 어디 부분에 사용될 수 있을까요?저는 둘 모두 사용할 수 있다고 생각합니다. 대표적으로는 어떠한 객체의 값을 바꿔야 하는데 val 필드를 var 로 변경하기는 싫을 때, copy() 를 사용할 수 있지 않을까 싶어요. 예를 들어 class Person(val name: String, val age: Int) 를 설계 했다고 해보죠. 이제 Person 의 나이가 올라가는 것을 구현해야 하는데 이 Person 클래스를 불변으로 유지하고 싶다면 this.copy(age = age + 1) 로 쉽게 구현할 수 있지 않을까 싶습니다.이 예시는 필드가 2개라서 다른 방법을 사용해도 간단하지만..class Person (..) { // 필드 생략 fun gerOlder(): Person { return Person( // copy 미사용 name = this.name, age = this.age, ) } }필드가 수십개가 된다면 copy가 조금 더 편할거에요!또한 꼭 불변 객체가 아니더라도 비슷한 use case 라면 (= 똑같은 타입의 인스턴스가 새로 필요한데 일부 값만 바꿔야 한다면) 종종 copy를 유용하게 사용할 수 있을 것 같습니다. ☺ Kotlin+Springboot+JPA 를 사용할때 data class/class 중 어떤 클래스로 Entity를 만드시는지 궁금합니다.MySQL or MongoDB or PostgreSQL에서 어떤 클래스와 궁합이 좋은지. data class를 사용한다면 copy를 어느 부분에 사용하면 좋을지도 알고싶습니다!아마 이 질문은 특정 DB에서 ORM을 사용할 때 테이블을 어떤 종류의 클래스로 매핑하는지 질문을 주신것 같아요! 개인적으로는 RDB (= JPA)를 사용할 때는 값 객체라면 (ex. @Embeddable ) data class, 그렇지 않다면 class를 사용하는 편이고요, 혹시나 간혹 계층 구초가 필요하면 sealed class도 활용해서 테이블에 매핑하기도 합니다! 몽고도 동일한 규칙을 선호하는 편입니다. 🙂 [5. Scope Function]아마 개인마다 선호하시는 방식이 다를 것 같아요! 제 개인적으로 scope function을 가장 많이 활용하는 경우는..nullable 한 값을 처리해야 할 때 ?.let { } ?: XXX 처리를 많이 하는 편이고요!apply는 프로덕션 코드에서 잘 사용하지 않는 편입니다. 🥲 apply는 결국 setter를 활용하는 것 같은 느낌이 들고 (혹은 setter를 쉽게 접근할 수 있도록 하고) 코드 상에서 들여쓰기가 깊어 지는 것을 경계하는 편이라서요.그래서 작성해주신 카프카 설정을 예시로 보여드리면,private fun initConsumerProps( keyDeSerClass: Class>, valueDeSerClass: Class> ): Properties { val config = mapOf( BOOTSTRAP_SERVERS_CONFIG to "localhost:9092", KEY_DESERIALIZER_CLASS_CONFIG to keyDeSerClass.name, ... // 생략 ) return Properties(config) }라고 작성하는 편입니다. 물론 정말 setter를 직접적으로 쓰지 않는 이상 큰 차이는 없다고 생각해서, 기존에 적어주신 스타일대로 코드가 적혀 있다면 굳이 변경하지는 않을 것 같습니다.with 같은 경우는 객체 변환에 잘 활용하는 편인 것 같아요! 보내주신 예시는 저라면 다음과 같이 작성했을 것 같습니다.consumerRecords.forEach { record -> logger.info { "key: ${record.key()}, partition: ${record.partition()}, offset: ${record.offset()}, value: ${record.value()}" } }그리고 만약 이런 코드가 2회 이상 발견된다면... 어딘가에fun Record.toLog(): String { return "key: ${key()}, partition: ${partition()}, offset: ${offset()}, value: ${value()}" }와 같은 확장함수를 만들고..consumerRecords.forEach { record -> record.toLog() }만 다양한 곳에서 쓰지 않았을까 싶습니다. (List 단위로 확장 함수를 만들 수도 있고요!)워낙 case가 다양하다보니 기억이 바로 나는 건 이정도인 것 같습니다. 😊 [6. method reference에 대해서]결론부터 말씀드리면, 저는 개인적으로 레퍼런스 방식과 x -> x() 방식 모두 괜찮다고 생각하는데중요한건 들어가 있는 타입을 명확하게 알게 해주는 거라고 생각해요!예를 들어 let { it.xxx() } 보다는 let { num -> num.xxx() } 처럼 사용하는 편이 가독성 면에서 더 좋다라고 생각합니다. 아이고~ 답변이 충분했으면 좋겠네요! 강의를 의미 있게 들어 주시고, 질문도 열심히 해주셔서 정말 감사드립니다! 🙇
- 2
- 2
- 172
질문&답변
완강 후 Kotlin 이런저런 질문 드려봅니다!
안녕하세요 보키님!! ☺ 아유~ 경험이 있으시면 다소 쉬우셨을 수도 있었을텐데 도움이 되었다니 다행이네요! 🙏 질문 주신 내용에 대해 하나씩 답변 드려 보겠습니다![1. TabWidth]시작은 가벼운 질문이군요~~ 저는 개인적으로 tab-width 2를 선호하는데 4로 했을 경우 depth 가 살짝 깊어지거나 변수/함수 등의 네이밍이 길어지면, x-scroll이 생겨서 화면 분할을 하더라도 한 눈에 코드가 들어오지 않는 경우가 간혹 있더라고요! 🥺 그래서 개인적으로는 tab-width 2를 선호하고, 그렇다고 4 depth를 싫어하는 것은 아니라 회사에서는 기존 convention을 따라가는 편입니다. 🙂 [2. 순수함수와 Closure]맞는 말씀이십니다. 👍 다만, 원래 자바에서도 가변 필드를 다음과 같은 꼼수를 통해 lambda에 넣을 수 있었습니다. 때문에 오히려 가변 필드를 lambda에 넣을 때에 불필요한 보일러 플레이트 코드가 필요한 경우도 있었죠.int num = 0 list.stream() .map { x -> x.handle(num) } // 마치 final이 아닌 값은 절대 못넘길 것 같지만..public class MyNumber { private int value = 0; } final MyNumber num = new MyNumber(0); list.stream() .map { x -> x.handle(num) } // 이렇게 하면 num이라는 final Object를 넘겨서 내부 적으로 int value 필드를 수정할 수 있게 됩니다때문에 제 개인적인 생각으로는, 차라리 가변 필드도 쉽게 람다에 넘길 수 있게 해서 언어 편의성을 높이는 선택을 한게 아닐까 싶습니다.추가로, 개인적으로는 람다에 가변 변수를 넘길 일 자체가 드물다고 생각합니다. 경험이 생기면 자연스럽게 명령-질의 분리를 통해 특정 값을 바꿀 수 있는 scope을 제한하기 때문이죠. 이런 코드가 훨씬 직관적이기도 하고요. [3. OOP와 FP에 대해서]rich domain model이 아닌 anemic domain model이 되는게 아닌지 궁금합니다라는 부분이 핵심으로 보입니다. 🙂 우선 잘 알고 계시겠지만, Java 8 이전에는 FP에 대한 지원이 충분하지 않아서 인터페이스 + 익명 클래스 조합을 많이 사용했고, 이 방식의 번거로움으로 (자바에 대한 선호도가 떨어지다가..) 자바 8의 람다가 등장하게 되었는데요. 람다의 등장으로 인해 Java도 말씀해주신 방식대로 프로그래밍을 할 수 있습니다. (실제로 XXTemplate 은 대부분 그러한 패턴이 적용되어 있고요!)하지만 코틀린은 자바 보다 조금 더 편한 방식으로 FP를 지원하기 때문에, 자칫 잘못하면 본래 객체 안에 있어야 할 코드를 밖으로 빠지게 된다거나 지나치게 많은 인자를 함수로 사용해 유지보수 비용을 높이는 선택을 하기 조금 더 쉬워진 것은 맞다고 생각합니다. 그렇지만 저는 결국 도메인 모델을 풍부하게 혹은 빈약하게 만드는 것은 개발자에게 달려 있다고 생각합니다. 누군가는 똑같은 요구사항을 구현할 때 절차지향적으로 작성하기도 하고, 객체지향적으로 작성하기도 하며, 함수를 지나치게 많이 쪼개 대다수의 사람들이 이해하기 어렵게 만들 수도 있죠. 결국 프로그래밍 언어는 하나의 도구일 뿐이고, 그 도구를 어떻게 활용하는지는 개발자의 몫이 아닐까 싶습니다. ☺ (이어서 답변 드릴게요!!)
- 2
- 2
- 172
질문&답변
확장함수 스타일 질문
안녕하세요 보키님! 🙂 좋은 질문 감사드립니다. val 또는 fun으로 확장함수를 만들 수 있는데.. 두가지 방식의 차이점이 있나요?에 대해서 결론부터 말씀드리면 사용하는 쪽에서 '함수'라고 여길 것인지 '프로퍼티' 라고 느낄 것인지에 대한 의미론적인 차이가 있습니다.잘 아시겠지만 val 은 프로퍼티이고 fun 은 함수인데요!프로퍼티는 객체 안에 있을 것 같은 '필드' 느낌을 주고 싶을 때 사용하면 좋고, '함수'는 객체에게 어떠한 행동을 요구하는 느낌을 주고 싶을 때 사용하면 좋습니다. 위에서 예시를 들어 주신 것처럼 Fruit.isSamplePrice 라는 Predicate을 이용해 같은 가격을 갖고 있는 과일을 추출한다고 해보겠습니다.제가 이걸 프로퍼티로 만든다면List.samePriceFruits 이라고 이름 붙일 것 같아요!fruitsInList.flatMap { it.samePriceFilter }이라고 서술할 때 조금 it (List) 안에 들어 있는 하나의 속성 같아 보이게요 만약 함수로 구성한다면..List.filterSamplePrice() 라고 할 것 같습니다. 함수는 어떠한 행위이기에 때문에 같은 가격을 기준으로 필터링 한다~ 라는 느낌을 주고 싶습니다. 결론적으로 (매개변수가 필요하지 않다는 가정 아래) 두 동작 자체는 동일하고 단지 코드에 어떤 의미를 부여할 것인가에 따라 차이가 있다 라고 봐주시면 될 것 같습니다.또 궁금한 점 생기시면 편하게 질문 남겨주세요~ 감사합니다. 🙇
- 0
- 2
- 21
질문&답변
연관관계 관련 질문 있습니다.
안녕하세요 준박님! 🙂 네네 맞습니다!@OneToMany / @ManyToOne 같은 연관관계가 존재하면 연결된 테이블의 데이터를 repository를 사용하지 않고도 바로 불러올 수 있습니다. 굉장히 편하기도 하고 조금 더 객체 지향적인 프로그래밍을 할 수 있도록 해주죠.다만, 연관관계가 항상 좋은 것은 아닙니다.관련해서연관관계를 사용하는 것이 항상 좋을까?를 참고해보셔도 좋을 것 같습니다. 또 궁금한 점 있으시면 편하게 질문 주세요~!! 감사합니다. 🙇
- 0
- 2
- 19
질문&답변
안녕하세요 코드 관련 질문이 있습니다!
안녕하세요 지환님! 🙂 질문 주셔서 감사합니다. 강의 자료 (완성된 코드) 를 보시면 전체 코드를 받으실 수 있습니다. 감사합니다! 🙇 (사진)
- 0
- 2
- 38
질문&답변
mysql 연결 관련 에러 질문있습니다
안녕하세요! wosyh18님! 🙂 질문 올려주셔서 감사합니다. 하나씩 답변 드려 볼게요! [1. no Creators, like default constructor 에러]검색해서 해결하셨다니 정말 훌륭하시네요~ 👍 "인자 있는 생성자가 있었는데 왜 기본 생성자를 만들어야하는건가요?" 에 대해서도 답변 드리면, 네 맞습니다! 다만 모든 경우는 아니고 몇몇 경우에 기본 생성자가 있어야만 동작하는데요, 이런 몇몇 경우는 스프링 버전에 따라 달라질 수 있고, 객체를 어떻게 활용하는지 (GET API로 받는지, @RequestBody 를 사용하는 POST API인지, 필드가 1개인지, 여러 개인지 등등..) 에 따라 다르다 보니저는 개인적으로 API에서 사용하는 DTO에는 기본 생성자를 모두 만들어주는 편입니다! 🙂 [2. Jackson - @JsonCreator와 각 필드에 @JsonProperty를 추가]Jackson에 대해서도 말씀드려 볼게요! 🙂 제가 강의에서 설명드린 것처럼 결국 HTTP 라는 문자열이 서버 (스프링) 으로 들어오면 그 문자열을 우리가 만들어 놓은 객체로 변환하는 작업이 필요합니다!예를 들어 {name: "태현", age: 3} 이라는 문자열은public class UserCreateRequest { private final String name; private final int age; public UserCreateRequest(String name, int age) { this.name = name; this.age = age; } }클래스의 UserCreateRequest("태현", 3) 이라는 객체가 되어야 하죠 이 과정을 역직렬화 (문자열이 객체로 바뀌는 과정) 반대의 과정을 직렬화 (객체를 다시 문자열로 바꾸는 과정) 라고 말합니다.그리고 Jackson이란 친구는 스프링 내부에서 이러한 직렬화/역직렬화 를 담당하는 라이브러리 입니다. 약간 비유해 보면.. 사람이라는 몸에서 소화를 담당하는 기관은 "위"라고 불러 하는 것처럼 Jackson이 그 역할을 담당하고 있는데요,@JsonCreator와 @JsonProperty는 그런 Jackson과 관련된 어노테이션으로, @JsonCreator 를 하게 되면 Jackson이 어떤 함수를 역직렬화 과정에서 생성자 처럼 이용하게 되고, @JsonProperty를 사용하게 되면 특정 필드, 함수를 Jackson이 명시적으로 인식하게 됩니다.다만 [no Creators, like default constructor 에러] 의 일반적인 해결 방법은 위에서 적용해주신 "기본 생성자 추가" 이기 때문에 지금은 아 이런게 있구나~ 하고 넘어가시거나 @JsonProperty 정도를 직접 써보시면서 이런 개념이구나 정도만 알고 계시면 충분할 것 같습니다. [3. SQL 문법 에러]마지막으로 SQL 문법 에러입니다! 결론부터 말씀드리면 INSERT INTO [*]user ... 라고 나와 있는데, user 라는 이름이 H2라는 데이터베이스의 미리 정의된 예약어라 에러가 발생하고 있습니다.실제로 그 아래 에러 코드 중 at.org.h2... 라는 "h2"가 보이죠!즉, 현재 보내주신 화면으로 볼 때 작성해주신 스프링 코드는 MySQL과 실행되고 있지 않고 H2라는 DB와 함께 실행되고 있어요! 🥲 이런 경우는 application.yml 설정이 잘 되어 있는지 한 번 더 확인해 보시고, MySQL와 잘 붙을 수 있도록 해보시는 것이 좋을 것 같습니다. 혹시나 설정도 잘 된 것 같은데 계속 H2와 붙는다면, 저에게 application.yml 설정 그리고 서버가 실행될 때의 로그를 댓글로 보내주시면 저도 한 번 확인해보겠습니다. 꼭 해결되셨으면 좋겠습니다. 감사합니다! 🙇
- 0
- 1
- 52