반응형

오늘의 공부는 "어디 가서 코프링 매우 알은 체하기"입니다.

원본 영상은 아래 링크를 참조해주세요~

 

 

떠오르는 언어(?) 코틀린으로 스프링 웹 백엔드를 구현해보고자는 열망이 있었던 찰나에 코틀린과 스프링에 대해서 공부할 수 있는 영상이 있어서 공부한 내용을 적어보고자 한다. 

 

아이템 1. 코틀린 표준 라이브러리를 익히고 사용하라.

 

"표준 라이브러리를 사용하면 그 코드를 작성한 전문가의 지식과 여려분보다 앞서 사용한 다른 프로그래머들의 경험을 활용할 수 있다." 라는 명언을 통해 자바 코드 대신 코틀린 표준 라이브러리를 사용하라고 적극 권장하고 있다.

코틀린에는 읽기 전용 컬렉션과 변경 가능한 컬렉션을 구분해서 제공한다. 플랫폼에서 사용하는 컬렉션들이 읽기/쓰기 전용에 맞게 구분되어 제공되어진다. 코틀린에는 좋은 기능들이 많다. 애용하자.

자바와 관련된 코드들을 줄여나가다 보면 자연스럽게 코틀린스러운 코드를 작성할 수 있게 된다. 

 

아이템 2. 자바로 역컴파일하는 습관을 들여라.

 

코틀린 숙련도를 향상시키는 가장 좋은 방법 중 하나는 작성한 코드가 자바로 어떻게 표현되는지 보는 것이다.

역컴파일을 통해 예기치 않은 코드를 방지할 수 있고 문제가 발생했을 때 빠르게 확인할 수 있다.

IntelliJ IDEA 에서 Tools -> Kotlin -> Show Kotlin Bytecode -> Decompile 기능을 통해 역컴파일할 수 있다.

 

아이템 3. 롬북 대신 데이터 클래스를 사용하라.

코틀린과 자바를 혼용하여 사용하면 코틀린 먼저 class 파일로 변환이 되고 java 파일들을 읽어 애너테이션 프로세싱을 거치게 된다. 즉 애너테이션 프로세서는 코틀린 컴파일 이후에 동작하기 때문에 롬북에서 생성된 자바 코드는 코틀린 코드에서 접근할 수 없다. 그래서 코틀린 코드에서는 롬북 코드를 사용하지 못 한다(?) 라는 이야기가 나오게 되었다.

 

자바와의 호환성을 제공하고자 코틀린 진영에서도 롬북을 사용할 수 있게 1.5.20 부터 롬북 컴파일러 플러그인이 실험적으로 추가되기도 했다. 코틀린 코드보다 자바 코드를 먼저 컴파일하도록 빌드 순서를 조정하면 롬북 문제는 해결될 수도 있다. 하지만 이 역시 자바 코드에서 코틀린 코드를 호출할 수 없게 되는 단점이 생긴다. lombok 을 delombok 으로 순수 자바 코드로 풀어버리는 방법이 있지만, 코틀린의 data class 를 사용하는 것을 추천한다.

 

data class 를 사용하면 컴파일러가 알아서 equals, hashCode, toString, copy 함수등을 자동으로 생성해준다.

주 생성자에 있는 매개변수를 기반으로 생성되므로 하나 이상의 매개변수가 있어야 하며 모든 매개변수는 val 이나 var 로 표시해야 한다. copy 함수를 적절히 사용하면 데이터 클래스를 불변으로 관리할 수도 있다.

 

TIP. all-open 컴파일러 플러그인을 사용하라.

 

 

코틀린의 폐쇄적인 클래스 정책 때문에 SpringBoot main 함수가 실행이 되지 않는다. 자바로 컴파일해보면 Application 클래스에 final 이라는 키워드가 붙어 있다. @SpringBootApplication 은 @Configuration 을 포함하고 있는데 스프링은 기본적으로 CGLIB 을 사용하여 @Configuration 클래스에 대한 프록시를 생성한다.

 

CGLIB 에서 대상 클래스를 상속해서 프록시를 구현하는 문제 때문에 코틀린의 폐쇄적인 클래스 정책과 충돌이 나는 것이다. final 로 지정된 클래스와 메서드들은 상속하거나 오버라이드를 할 수 없기 때문이다.

(스프링 5.2부터 @Configuration 의 proxyBeanMethod 옵션을 사용하여 프록시 생성을 비활성화하는 기능을 사용하면

동작이 된다고 한다.)

 

상속을 허용하고 오버라이드를 허용하려면 open 이라는 변경자를 추가해야 한다. 그런데 spring 에 관련된 모든 클래스와 메서드에 open 이라는 지시자를 붙여야 한다면 코틀린의 매력도가 떨어지게 된다.

 

 

코틀린에서는 이러한 문제점을 해결하고자 all-open 플러그인을 제공한다. all-open 플러그인을 사용하면 지정한 애너테이션이 있는 클래스와 모든 멤버에 open 변경자를 추가한다. 스프링을 사용할 경우 이 all-open 플러그인을 랩핑한 kotlin-spring 컴파일러 플러그인을 사용하면 된다. @Component, @Transactional, @Async 등이 기본적으로 제공된다.

적용되는 애너테이션 항목들을 확인하려면 Intellij IDEA 에서 File -> Project Structure -> Project Settings -> Modules -> Kotlin Compiler Plugins 에서 확인하면 된다.

 

아이템 4. 필드 주입이 필요하면 지연 초기화를 사용하라.

 

생성자를 통해 의존성을 주입하는 것이 당연시 되는 오늘이지만, 때로는 필드를 통해 주입해야 될 때가 있다.

이 때 코틀린에서는 backing field 가 존재하는 프로퍼티라면 인스턴스화될 때 초기화해야 된다. null 로 초기화 할 수는 있지만 null 이 있는 타입은 항상 null 연산자를 통해 체크로직이 필요하다는 불편함이 있다. 코틀린에서는 lateinit 변경자를 붙이면 프로퍼티를 나중에 초기화할 수 있다.

 

TIP. 역직렬화를 위해 잭슨 모듈이 코틀린을 지원하고 있다.

 

역직렬화를 하려면 매개변수가 없는 생성자가 필요하다. 그런데 코틀린에서는 매개변수가 없는 생성자를 만드려면 모든 매개변수에 기본 인자를 넣어야 한다. 잭슨 코튤린 모듈은 매개변수가 없는 생성자가 없더라도 직렬화와 역직렬화를 지원한다. 스프링 부트에서 기본적으로 이 모듈을 포함하고 있다고 한다.

 

아이템 5. 변경 가능성을 제한하라.

 

폐쇄적으로 작성할 수록 안전하기 때문에 모든 멤버를 val 로 선언할 것을 권장하고 있다. (나중에 var로 바꾸면 되므로!) 스프링 부트 2.2 부터 스프링 프로퍼티 클래스에서 생성자 바인딩이 가능하다. (@EnableConfigurationProperties 또는 @ConfigurationPropertiesScan 사용) Configuration 클래스에 적극 활용하자.

클래스에 개념적으로 동일하지만 하나는 공개하고 다른 하나는 구현 세부사항으로 구현할 때 private 프로퍼티 이름의 접두사로 밑줄을 사용한다. 이를 뒷받침하는 프로퍼티(backing property) 라고 한다. 공개되는 쪽에는 읽기 전용의 자료구조를 사용하고 내부에서 사용할 때는 수정이 가능한 자료구조를 사용한다. JVM 에서 기본 getter 와 setter 가 있는 private 프로퍼티에 대해서 함수 호출 오버헤드를 방지하도록 최적화되어 있다.

 

TIP. No-arg 컴파일러 플러그인

 

역직렬화와 마찬가지로 엔티티 클래스를 생성하려면 매개변수가 없는 생성자가 필요하다. no-arg 컴파일러 플러그인은 지정한 애너테이션이 있는 클래스에 매개변수가 없는 생성자를 추가한다. 자바 또는 코틀린에서 직접 호출할 수는 없지만 리플렉션을 사용하여 호출할 수 있다. no-arg 컴파일러 플러그인을 랩핑한 kotlin-jpa 컴파일러 플러그인을 사용할 수 있다. 마찬가지로 스프링 부트에서 자동으로 셋팅되며 @Entity, @Embeddable, @MappedSuperclass 등이 기본적으로 제공된다.

 

 

Q. kotlin("plugin.jpa")구문만 있으면 될 것 같은데 Entity와 MappedSuperclass에 allOpen 애너테이션이 붙은 이유는?

 

프록시를 못 만들기 때문이다. JPA 에서 프록시를 못 만든다는 뜻은 지연로딩을 할 수 없다는 뜻과 같다. 컴파일 에러나 런타임 에러는 발생하지 않는데 성능 문제가 발생하기 시작한다. 지금 당장 로딩이 필요없는 수많은 데이터들이 즉시 로딩되면서 성능 문제가 발생하는 것이다.

 

아이템 6. 엔티티에는 데이터 클래스 사용을 피하라.

 

lombok 의 @data 와 같은 맥락이다. 양방향 연관관계의 경우 toString, hashcode 를 호출할 때 무한 순환 참조가 발생한다.

 

아이템 7. 사용자 지정 getter를 사용하라.

 

JPA 에 의해 인스턴스화 될 때 초기화 블록이 호출되지 않기 때문에 영속화하지 않는 필드는 초기화된 프로퍼티가 아닌 사용자 지정 getter 를 사용해야 한다.

 

 

왼쪽 코드처럼 영속화되지 않는 필드에 초기화 구문을 넣어도 데이터베이스에서 값을 가져올 때 boolean 이나 null 이 될 수 없음에도 호출해보면 null 이 들어가 있다. 오른쪽 코드처럼 getter 를 사용자 정의해서 프로퍼티에 접근할 때마다 호출할 수 있게 구성하여야 한다. 뒷받침하는 필드가 존재하지 않기 때문에 AccessType.FIELD 이더라도 @Transient 를 사용하지 않아도 된다.

 

아이템 8. 널이 될 수 있는 타입은 빠르게 제거하라.

 

null이 될 수 있는 타입을 사용하면 null 검사를 넣거나 !! 연산자를 통해 assert 검사를 해야 한다.

0 또는 빈 문자열로 초기화하면 null이 될 수 있는 타입을 제거할 수 있다. 확장 함수를 사용해 반복되는 null 검사를 제거할 수도 있다. 의문이 드는 건 0 또는 빈 문자열로 초기화해도 검사해야 되지 않나이다. 자연스레 0 이나 빈 문자열을 두어도 상관이 없는 괜찮은 코드라면 무방할 수도 있겠다. 확장함수를 사용해 반복되는 null 검사를 제거할 수도 있다.

반응형

'언어 > Kotlin' 카테고리의 다른 글

[Kotlin] 코틀린 특징  (0) 2022.04.16
[Kotlin] 코틀린의 코루틴 동작 원리  (0) 2022.04.12
반응형

코틀린 언어가 가지고 있는 다른 언어와 다른 특징

 

#1 코틀린은 기본적으로 상속이 불가능하다.

 

 

코틀린 공식 홈페이지에 가보면 코틀린 클래스는 final 클래스이며 상속이 불가능하다고 명시되어 있다.

상속을 허용하려면 open 이라는 지시어를 써야 한다. 이와 관련해서 Github 에서 코틀린 스프링에 관련한 예제를 서치하다가 아래와 같은 내용을 발견했다.

 

출처 : https://github.com/arawn/kotlin-spring-example

 

지금은 공식 홈페이지에 설계 원칙에 대한 내용은 찾아 볼 수 없지만 위 github 작성 당시에는 설계 원칙이 있었던 것 같다. 코틀린은 이펙티브 자바 Item 17 "상속을 허용하지 말라" 를 철저히 지킬 수 있도록 설계된 것 같다.

 

상속 시 주의할 점은 다음과 같다.

- 서브 클래스는 수퍼 클래스에 존재하는 속성과 같은 이름의 속성을 가질 없다. 그래서 변수들을 var 이나 val 선언하지 말고 파라미터 넘겨서 클래스 생성자에 직접 넣어주어야 한다.

- 서브 클래스는 수퍼 클래스의 생성자까지 호출되어야 한다. 기본 생성자이던 보조 생성자이던 반드시 수퍼 클래스의 생성자가 불려야 한다.

 

#2 코틀린은 기본적으로 메서드 오버라이딩, 속성 재정의가 불가능하다.

 

1번과 마찬가지로 변경을 최소화하기 위해 클래스 내에 선언되어 있는 메서드들도 오버라이딩이 불가능하다.

수퍼 클래스에서 open 이라는 키워드로 오버라이딩이 가능하게 만들어 주어야 서브 클래스에서 override 라는 지시자를 통해 오버라이딩이 가능하다. 속성도 마찬가지이다.

 

 

#3 코틀린은 암시적 형변환은 지원하지 않는다.

 

// 컴파일 에러 Type Mismatch

var a: Int = 123
var b: Long = a

암시적 형변환으로 인한 오류를 줄이고자 명시적 형변환만 허용하고 있다. to자료형 메서드를 사용해서 명시적으로 변환해야한다.

 

#4 코틀린은 타입 추론이 가능하다.

 

코틀린은 변수나 함수를 선언할 때나 연산이 이루어질 때 자료형을 코드에 명시하지 않아도 자동으로 자료형을 추론해준다. 기본 자료형들은 값을 선언시 할당만 해주면 알아서 추론이 가능하다. 단일 표현식 함수에서도 반환형의 타입추론이 가능하다.

 

fun add(a: Int, b: Int, c: Int) = a + b + c

 

#5 코틀린은 기본 변수에서 null 절대 허용하지 않는다.

 

Null pointer Exception Error 를 원천적으로 차단하기 위해서 Null 을 할당할 수도 사용할 수도 없다.

보통 다른 언어에서는 변수 선언 시 초기화를 하지 않으면 기본값으로 초기화되거나 값이 할당되지 않았다는 표시로 null 값을 가지게 되는데 코틀린은 문법 에러를 표시하고 컴파일이 불가능하다.

그렇다고 꼭 선언 시에 초기화할 필요는 없다. lateinit 이나 lazy 변수를 활용하여 지연 초기화를 사용해도 된다.

lateinit 변수는 초기화 할당 전까지 변수를 사용할 수 없고 클래스 내부에서만 사용이 가능하다. 초기화를 하였는지 여부는 ::var.isInitialized 키워드를 사용해서 확인한다.

lazy 변수는 변수를 사용하는 시점까지 초기화를 자동으로 늦춰주는 기능이다. val a: Int by lazy {람다함수} 구문을 통해 설정할 수 있다.

 

그런데 정말 null 이 필요한 경우가 생길 수 있으므로 자료형 뒤에 var a:Int? 물음표를 붙이면 null 을 허용하는 nullable 변수로 선언해줄 수도 있다. null 상태로 속성이나 함수를 사용하려고 하면 null pointer exception 이 발생할 우려가 있기 때문에 null 체크 없이는 코드가 컴파일 되지 않는다. 이 때 null 체크를 위해 일일히 if 문으로 조건을 체크하는 대신 아래 연산자를 통해 간단히 체크할 수 있다.

 

 

null safe operator 의 경우에는 참조연산자를 실행하기 전에 먼저 객체가 null 인지 확인부터하고 null 이라면 뒤 따라오는 구문을 실행하지 않는다.

elvis operator 는 객체가 null 이 아니라면 그대로 사용하지만 null 이라면 우측 피연산자 값으로 대체된다.

non-null assertion operator 라면 참조연산자를 사용할 때 null 여부를 컴파일 시 확인하지 않도록 하여 런타임 시 null pointer exception 이 발생하도록 의도적으로 방치하는 연산자이다.

 

보통 위 연산자들과 스코프 함수를 같이 실행한다.

 

var a: String? = null

a?.run{
	println(toUpperCase())
    println(toLowerCase())
}

 

#6 코틀린은 읽기만 가능하고 수정이 불가능한 컬렉션과 수정 및 삭제까지 가능한 Mutable 컬렉션을 제공한다.

반응형

'언어 > Kotlin' 카테고리의 다른 글

[코프링] 코틀린과 스프링 (1)  (0) 2022.04.16
[Kotlin] 코틀린의 코루틴 동작 원리  (0) 2022.04.12
반응형

오늘의 공부는 코틀린의 코루틴 동작원리입니다.

원본 영상은 아래 링크를 참조해주세요.

 

 

코틀린의 코루틴들은 사실 컴파일러에서 Finite State Machine 기반의 재귀 함수로 변환된다.

 

콜백 지옥

 

비동기 코드들이 일반적으로 중첩 for 문의 callback 코드 조각들처럼 구성되어 있는데 코틀린에서는 다르다.

코틀린 컴파일러는 코드 조각들을 순차적으로 작성해도 컴파일 타임 시에 코드 조각들을 찢어 state 재귀 함수로 변환해준다. 상태와 결과 체크를 컴파일러에서 해주기 때문에 callback 지옥에서 벗어나 프로그래머가 순차적으로 작성할 있게 도와준다.

이러한 과정을 "코틀린 컴파일러가 suspend 붙은 함수에 대해 Continuation Passing Style(CPS) 바꾼다" 라고 표현한다.

 

 

위와 같은 예제의 코드를 비동기적으로 처리한다고 가정하자.

 

 

코틀린에서 자바로 변환될 번째 줄부터 Continuation 객체가 주입(Dependency Injection) 것을 있다.

 

 

Continuation 인터페이스는 현재 상태의 context resume 메서드를 가지고 있다.

콜백 지옥에서도 자세히 보면 callback 함수들을 전달하는 것을 있는데 코틀린에서는 callback 함수들을 인터페이스화하여 추상화하고 인자로 주입한 것이다.

 

 

먼저 라인 별로 Label 지정해 자르고,

 

 

앞에서 소개한 Continuation 객체를 인자로 주입하고, 코루틴 구현체를 만들어 Continuation 객체를 체크해서 재귀함수를 호출할지 결정한다.

 

 

case 따라 다음 case 필요한 인자를 셋팅하고 순차적으로 호출하는 것을 있다.

 

코틀린 개발자들은 겸손해서 그런지 Magic 이 아니라고 하는데

나는 다중 for 문(?)의 구조를 상태 DI(Dependency Injection) 와 Finite State Machine 으로 구현한 것이 인상적이었다

반응형

'언어 > Kotlin' 카테고리의 다른 글

[코프링] 코틀린과 스프링 (1)  (0) 2022.04.16
[Kotlin] 코틀린 특징  (0) 2022.04.16

+ Recent posts