시작하며
나도 코틀린 쓰고 싶다고!
안녕하세요. 카카오페이에서 해외 결제를 개발하고 있는 스노우입니다. 자바의 사용률은 세계적으로 봤을 때는 많이 낮아진 추세이지만, 국내로 한정해서 봤을 때 자바 사용률은 53%(2021, JetBrains)라고 나오고 실제로는 이보다 높을 것이라 생각합니다. 아시는 분들은 아실만한 전자정부 프로젝트 때부터 토비의 스프링(거의 수학의 정석급이죠?)을 거치며 자바+스프링은 웹 개발 또는 웹 API 개발의 표준이 되어왔습니다.
저도 자바로 약 10년 이상 개발을 해왔으며 항상 프로그래밍을 조금 더 행복하게, 재미있게 유지 보수할 수 있는 방법이 무엇일까 고민해왔습니다. 그러다가 2019년쯤 코틀린을 알게 되었고, 스터디를 하며 코틀린을 배워나갔습니다. 이후 본격적으로 코틀린으로 기존 프로젝트도 변환해 보고, 신규 프로젝트에 적용해 보며 코틀린의 장점을 몸소 깨닫게 되었습니다.
제가 경험해 본 코틀린은 자바와 호환성이 매우 뛰어나며, 표현력과 생산성이 더 뛰어난 언어라는 것을 알게 되었습니다. 특히 null 관련 핸들링과 확장 함수, 함수형 프로그래밍 기법에 대한 다양한 지원 등이 매우 매력적이었으며 어느 정도 익숙해진 다음부터는 다시 자바로는 못 돌아갈 것 같다는 것을 느꼈습니다.(개인적 의견입니다😅)
어차피 스프링을 써야만 하는 상황이고, JVM 언어 중에서 선택해야만 한다면 저는 코틀린을 쓰는 게 현시점에서는 가장 좋은 선택이라 생각이 들었습니다. 그래서 한번 코틀린 프로젝트를 수행한 후에는 다른 프로젝트 코드를 건드릴 때마다 코틀린으로 변환을 수행했습니다. 제가 어떻게 코틀린 학습을 하고 적용하고 다른 프로젝트들을 수정했는지 한번 소개해 보겠습니다.
코틀린 학습 및 적용 여정
학습 및 첫 도입
2019년 코틀린을 접하고 koans라는 온라인 학습 사이트를 활용하여 함께 스터디를 진행했습니다. 그 후에 어딘가 써볼 데가 없을까 고민하다가 상대적으로 안전한 어드민(백오피스)에 적용해 봤습니다. 익숙지 않은 문법에 하나하나 익숙해지고 몇 가지 삽질도 했던 것 같습니다.
2020~2021년에는 신규 프로젝트를 진행하며 코틀린으로만 구성해서 개발을 해봤고 그때 함께 하는 개발자들과 열심히 코틀린 사용법에 대해 연구를 함께 해보며 활용해 보고 함수형 프로그래밍도 많이 학습을 하며 활용해 봤습니다.
첫 자바 to 코틀린 변환 고생기
2022년 초 다시 돌아온 프로젝트 A, 자바로 된 코드로 작업을 해보니 이젠 너무 힘들었습니다. ^^;; 그래서 코틀린으로 바꿔야겠다고 마음을 먹고 있었는데, 해당 프로젝트에 신규 멤버 2명이 합류했을 때 마침 주요 업무가 시작되기까지 잠깐의 시간이 있었습니다. 두 분이 빠르게 해당 프로젝트를 익히는 데에 코틀린 전환을 해보는 것도 좋은 방법이라는 생각이 들었습니다.
해당 코드들에는 자바에 롬복(lombok)이 많이 활용되어 있었습니다. gettter/setter/builder/생성자/slf4j 등 다양한 롬복 어노테이션만 붙이면 간편하게 보일러플레이트 코드를 생성해 줄 수 있기 때문에 무조건 적극적으로 활용하던 라이브러리였지요.
하지만 코틀린 변환 시에는 롬복이 큰 걸림돌이 됩니다. 코틀린 코드에서는 다른 자바 클래스에 접근할 때 롬복으로 생성된 코드에 접근하지 못했습니다. 코틀린에서 의존하는 자바 클래스는 디롬복(delombok)을 수행해야만 했고, 의존을 많이 하는 코틀린 클래스의 경우 무수히 많은 자바 클래스를 디롬복해야하고 해당 자바 파일은 롬복의 도움 없이 getter/setter/builder 등 다양한 코드를 직접 수작업으로 수정해야만 했습니다. 그래서 이때는 다른 업무는 최소화하고 일단 코틀린으로 전체 변환을 하자는 목표를 세우고 약 2달 반에 걸친 변환을 수행하게 되었습니다.
변환 순서는 아래와 같이 진행을 했습니다.
- 테스트 강화(unit/integration/e2e)
- gradle.kts로 변환
- entity, repository, util 클래스 중심으로 의존성이 적은 클래스 변환
- controller 클래스 변환
- service(비즈니스) 클래스 변환
안정적인 변환작업과 검증을 위해 테스트를 강화하는 작업을 먼저 수행하였으며 이때 e2e(end-to-end) 테스트도 추가를 했습니다. 그리고 gradle도 더 엄격하게 문법을 검사하고 코틀린과 상성이 좋다고 하는 gradle.kts 파일로 변경을 했습니다.
본격 스프링 프로젝트 변환 시 controller-service-dao(또는 repository)로 구성된 프로젝트였는데 이 3개의 레이어 중 어떤 방향으로 변환을 하는 게 좋을지 고민을 많이 했으며 결론적으로 가장 의존성이 낮은 dao 영역을 먼저 진행하고, 가장 복잡하며 의존성이 많고 서비스의 중요도가 높은 service를 마지막으로 진행하자고 해서 이렇게 진행을 했었습니다.
이렇게 진행을 해보니 사실 클래스 자체는 intellij 변환기로 너무 손쉽게 잘 변환이 되어서 어렵지 않았습니다. 자동 변환을 돌린 후 어노테이션(특히 java bean validation)이나 nullable 하게 생성되는 프로퍼티들을 not null로 바꾸고 프로퍼티 선언 + 생성자 별도 구성으로 된 부분을 생성자 프로퍼티 스타일로 변경해 주는 등의 조금 더 코틀린스럽게 바꾸는 작업이 필요했습니다.
다만 테스트 코드도 MockK 라이브러리를 활용하여 코틀린 스타일로 변경하려 했고 추가로 필요한 테스트 작업을 했기 때문에 이 부분에서 시간이 많이 소요되었습니다. 코틀린 파일 개수로 약 1,100개 정도(클래스 개수로 하면 훨씬 더 많은..)의 변환은 3명이서 약 2.5개월 동안 야근도 하고 토론도 하면서 매우 알차게(?) 전력을 다하여 수행했던 기억이 납니다.(피🩸 땀💦 눈물💧)
이 작업 후에 두 개의 대형 프로젝트가 있었는데.. 이때 코틀린으로 변환된 점, 다들 코드에 빠르게 익숙해진 점, 다양한 테스트들의 강화 덕분인지 해당 프로젝트들을 정해진 기한 내에 매우 안정적으로 진행했던 경험이 너무도 만족스러웠습니다.
더 손쉬워진 자바 to 코틀린 변환 thanks to 코틀린 >= 1.7.20
2022년 연말쯤에 새로운 업무가 있었습니다. 다른 프로젝트에 있는 시스템의 일부를 이관해오는 작업이었는데, 물론 순수 자바로 된 프로젝트였습니다. 해당 프로젝트는 가져오면서부터 변환을 마음먹고 있었으며(기존에 변환 후 프로젝트를 원활히 수행한 경험 때문에) 마침 이때 코틀린 버전이 1.7.20이 되면서 드디어 롬복이 플러그인으로 지원되기 시작했습니다. (그때는 비록 builder까진 지원이 안됐지만, 1.8.0부터는 builder도 지원!)
롬복 하나 지원되는 게 뭐 그리 큰일이냐 생각하실 수도 있지만.. 기존에는 클래스 하나를 코틀린 변환하려면 사용하는 N개의 자바 파일을 모두 디롬복을 해야만 했으며 해당 파일을 롬복 없는 자바 파일 상태에서 수정하는 건 너무 고통스럽기에 해당 파일들까지 모두 코틀린 변환을 울며 겨자 먹기로 하는 경우가 많았습니다.
하지만 이제는 클래스 하나를 변환하려면 해당 파일 하나만 변환하면 됩니다. 다른 의존된 롬복이 활용된 자바 클래스들을 수정할 필요 없이 그냥 사용하면 됩니다. 이렇게 된 후로는 더욱 편리하게 클래스를 수정할 일이 발생할 때마다 해당 파일을 우선 코틀린으로 변환한 후 기존 로직 동작에 이상이 없는지 확인을 한번 하고 수정을 진행하고 있습니다. 굳이 일부러 코틀린 변환을 날 잡고 한다기보다는 업무하면서 바꿔주다 보니… 이제 거의 1년 정도 되어가는 시점에 약 80%~90% 정도는 코틀린으로 바꿔준 것 같습니다.
자바 to 코틀린 실무 적용 방법
자 그렇다면, 여기까지 읽은 분들은 “그럼 어떻게 실제로 변환이 가능하냐?” 하실 것 같습니다.😅 자바 프로젝트도 그냥 스프링을 사용하는 곳과 스프링 부트를 사용하는 곳, 최신 스프링 부트를 사용하는 곳 등 다양한 환경에 처해있을 것이라 딱 한 가지 방법으로 모두 하실 수 있다고 말씀을 드릴 순 없습니다. 크게 보면 아래처럼 변환이 진행되고 세부적으로는 프로젝트 상황에 맞춰 진행이 필요합니다.
- 스프링 부트 버전 업데이트(2.5.2 이상)
- 코틀린 빌드 설정을 gradle/maven에 추가(코틀린 버전 1.8.0 이상)
- 코틀린 변환 진행
1. 스프링 부트 2.5.2 이상으로 업데이트
스프링 부트가 2.5.2가 넘어야 코틀린 1.8.x대 이상을 사용할 수 있으므로 우선 작업이 필요합니다. 이 부분은 인터넷의 스프링 부트 마이그레이션 아티클을 참고하셔서 진행하시면 됩니다.
2. 코틀린 빌드 설정 추가
(여기서부터는 Intellij 기준 설명입니다.) 일단 아무 자바 파일을 우클릭해서 메뉴 중 “Convert Java File to Kotlin File”을 클릭해주세요. IDE가 코틀린 설정을 추가해 줄지 물어보게 됩니다.
코틀린 1.8.22 버전을 선택해 주세요. 혹은 버전 선택이 어려운 경우 gradle에 추가된 ‘org.jetbrains.kotlin.jvm’ 플러그인의 버전을 1.8.22로 변경해 주시면 됩니다.
스프링 부트와 코틀린 빌드 설정을 추가하다 보면 이에 맞춰서 그레이들도 변경이 필요해질 수 있습니다.(Kotlin Docs 참고) 이때는 적절한 버전으로 바꿔주시면 됩니다. (저는 주로 그레이들 파일도 build.gradle.kts로 변환을 함께 합니다.)
이렇게 진행하시면 이제 바로 코틀린을 사용하실 수 있습니다.
3. 본격 코틀린 변환 시작
샘플로 스프링 프로젝트에서 @Service 클래스 하나를 변환하는 과정을 간단하게 보여드리겠습니다.
최초 변환 후 (Intellij 자동 변환 활용)
최초의 변환 결과로 보실 수 있는 결과입니다. bean들(실제론 null이면 안됨)이 nullable하게 변환되었고, mockito로 모킹한 테스트의 경우에는 테스트 코드가 깨질 수 있습니다. 우선 롬복 관련 어노테이션은 제거해 주세요(코틀린엔 적용 안돼요).
주 생성자 변경
클래스를 주 생성자 스타일로 변경(notnull로 모두 변경)하면 테스트가 성공되는 것을 확인할 수 있습니다.
코드 정리
불필요한 not null assertion 제거하고 inellij warning들은 확인하여 제거하면 됩니다. 그리고 프로퍼티, 메서드 파라미터, 메서드 응답 값 중에 not null인 것을 명확히 알 수 있는 경우는 not null로 타입으로 지정하여 코틀린의 장점을 마음껏 누리시기 바랍니다.
git history 남기는 방법
git commit은 intellij로 수행하면서 java > kt renames 커밋을 추가로 해주는 옵션이 켜져 있어야만 git history가 기존 자바파일로부터 연결됩니다. 간혹 이 옵션이 안 떠있는 경우가 있는데 이럴 땐 다시 자바로 롤백하고 다시 수정하시면 됩니다..^^;;
마치며
저는 10년 이상 자바로 개발을 해왔으며 1.6 이전 버전, 1.6, 1.7, 1.8, 11로 올리면서 조금씩 더 좋아지는 것은 느꼈습니다. 하지만, lombok과 이펙티브 자바 등으로 보일러플레이트 코드들을 라이브러리로 생성하는 부분과 조심해야 하는 코딩 스타일들이 너무 많은 점에 항상 아쉬움을 느꼈습니다. 그래서 코틀린을 쓰면서 언어가 위험한 부분들을 많이 제한해 주는 것을 보면서 편안함을 많이 느낄 수 있었습니다.
물론 코틀린만 쓰는 것보다 자바-코틀린을 섞어쓸 때 조심해야 하는 부분들이 있습니다.(코틀린의 플랫폼 타입 등) 따라서 섞어 쓰기에 안전하고 의존성이 적은 Util 클래스나 어드민(백오피스) 같은 프로젝트에 먼저 적용해 보시기 바랍니다.
코틀린 변환은 단순히 조금 더 모던한 언어를 적용하는 것 외에 몇 가지 부가적인 장점을 가져다주기도 했습니다. 일단 코틀린 변환을 하면서 단위 테스트, 통합 테스트, E2E 테스트 등 다양한 테스트를 강화하면서 기본적인 시스템의 안정성과 유지 보수성이 향상되었습니다. 또한 구성원들이 프로젝트와 코드에 대해 이해도가 높아지면서 3개월 정도 예상했던 프로젝트를 1.5개월 정도 만에 거의 마무리를 할 수 있었습니다.
물론 모든 분들에게 저의 방식을 추천을 드릴 순 없고, 맡은 프로젝트에 대한 이해도가 높고 프로젝트의 구성원들이 코틀린 사용에 적극적인 경우에 추천드리고 싶습니다. 다들 행복한 프로그래밍 되세요. 🤗