지난 글에서는 코틀린의 불변성과 data class를 활용한 설정 객체 관리에 대해 알아보았습니다. 이번 글에서는 서버 개발에서 빼놓을 수 없는 전역 예외 처리(Global Exception Handling) 영역을 다룹니다. 이 글에서는 제가 마이그레이션을 하며 겪은 실수와 함께 코틀린이 제공하는 안전장치를 소개하고자 합니다.
1. 익숙함이 부른 실수와 문법의 차이
자바 코드를 코틀린으로 옮기는 과정에서 가장 흔하게 마주하는 당황스러운 순간은, 잘 작성한 함수가 IDE에서 '사용되지 않음(never used)'으로 표시될 때입니다.
1.1. never used 경고와 클래스 매핑
자바의 예외 처리 로직을 코틀린으로 마이그레이션하면서 겪었던 에피소드가 있습니다. 구조와 타입을 모두 맞추었다고 생각했지만, 코드가 정상적으로 동작하지 않았습니다. 아래는 그 당시 작성했던 잘못된 코드의 예시입니다.
스프링 프레임워크는 언어(Java/Kotlin)를 불문하고 @ExceptionHandler 어노테이션을 통해 특정 예외를 가로챌 메서드를 식별합니다. 제가 코드를 변환하는 데 집중한 나머지 이 필수 어노테이션을 누락했고, 그 결과 스프링 빈 스캐너는 해당 메서드를 단순한 일반 함수로 취급해버렸던 것입니다.
사실 이 과정에서 진짜 주의해야 할 마이그레이션 포인트는 클래스 타입을 넘기는 문법의 차이입니다. 자바의 기존 코드 형태인 @ExceptionHandler(Exception.class)를 코틀린에서는 @ExceptionHandler(Exception::class)로 정확히 매핑하여 명시해야만 스프링이 우리가 의도한 대로 예외를 감지하고 처리할 수 있습니다.
2. 널(Null)을 대하는 안전장치
예외 처리 과정에서 가장 치명적인 잠재적 버그는 예외 메시지(ex.message) 자체가 Null일 수 있다는 점입니다.
2.1. NullPointerException을 유발하는 확언
초기 마이그레이션 시, 자바의 getMessage()를 코틀린의 프로퍼티인 message로 변환하면서 발생하는 타입 불일치를 해결하기 위해 무심코 Not-null assertion(!!)을 사용하는 실수를 범하기 쉽습니다. 특히, Jetbrains IntelliJ의 코틀린 변환 기능을 사용하면, IDE가 온갖 가능성을 열어두고 !!를 남용하도록 유도합니다.
이는 코틀린이 제공하는 널 안전성(Null Safety)을 개발자가 강제로 무력화하는 행위로, 런타임에 더 큰 장애를 유발할 수 있습니다.
2.2. 엘비스 연산자를 활용한 기본값 처리
코틀린은 엘비스 연산자(?:)를 통해 값이 Null일 경우의 대체 동작을 매우 간결하게 정의할 수 있도록 지원합니다.
이러한 코틀린의 이런 문법적 지원 덕분에, 거추장스러운 if (ex.message == null) 분기문 없이도 안전하고 예측 가능한 응답을 클라이언트에게 보장할 수 있습니다.
3. 표현식과 스코프 함수로 구현하는 코드 다이어트
코틀린의 진짜 매력은 프레임워크의 장황한 보일러플레이트(Boilerplate)를 문법적 차원에서 압축할 수 있다는 데 있습니다.
3.1. 단일 표현식(=)과 .also의 결합
기존 자바 스타일에서는 로깅을 수행하고 응답 객체를 반환하기 위해 반드시 중괄호({})와 return 키워드를 사용해야 했습니다. 코틀린은 이를 한 줄의 깔끔한 표현식으로 변환할 수 있습니다. 단일 표현식 함수(=)와 객체 반환 직전에 추가 작업을 수행하는 .also 스코프 함수를 결합하면 코드가 눈에 띄게 줄어듭니다.
3.2. 공통 함수 추출을 통한 아키텍처 개선
여기서 한 걸음 더 나아가, 응답을 생성하는 공통 로직을 별도의 private 함수로 분리하면 전역 예외 처리기가 담당해야 할 '어떤 예외를 어떻게 매핑할 것인가'라는 핵심 비즈니스 로직만 깔끔하게 남게 됩니다.
4. 마무리하며: 보일러플레이트를 걷어낸 자리에 남은 비즈니스 로직
처음 자바 예외 처리 코드를 코틀린으로 옮길 때만 해도, 제 목표는 단순히 '에러 없이 돌아가게 만들기'였습니다. 어노테이션을 빼먹어서 삽질도 하고, IntelliJ의 자동 변환 기능을 무심코 돌렸다가 코드 곳곳에 지뢰처럼 박힌 !! 때문에 당황하기도 했었습니다.
하지만 문법에 익숙해지고 단일 표현식(=)과 엘비스 연산자(?:)를 적극적으로 활용하기 시작하니, 코틀린의 특징이 확 보이기 시작했습니다.
지난 글에서도 이야기했지만, 저는 C++이나 Go, TypeScript처럼 동작이 예측 가능하고 견고한 언어를 좋아합니다. 전역 예외 처리를 코틀린으로 리팩토링하면서 느낀 점도 결국 같았습니다. 언어 차원에서 불필요한 코드를 덜어내 주고 Null을 통제해 주니, 비즈니스 로직이라는 본질만 생각하면 된다는 점이죠.
스프링 부트의 강력함은 그대로 쓰면서 코드의 무게만 덜어내고 싶다면, 코틀린은 꽤 매력적인 선택지입니다.