개요
자바 개발을 하다 보면 NullPointerException은 너무 익숙한 단골 손님입니다.
“이건 null이 안 들어오겠지”라는 믿음이 한 줄의 에러 로그로 깨지는 순간,
코틀린의 Null-Safety 개념이 왜 혁신적인지 실감하게 됩니다.
이번 편은 “NPE를 예방하는 언어”로서의 코틀린을 실무 관점에서 직접 체감한 내용을 정리했습니다.
주요 포인트 요약
- String? → nullable, String → non-null
- ?., ?:, !!, let, also, run의 쓰임새 차이
- null-safe 설계는 타입 수준의 약속
- “null은 데이터가 없는 상태”가 아니라 “존재하지 않을 수 있는 값”
- NPE를 컴파일러가 대신 잡아주는 구조
자바에서 흔히 보던 코드
String username = request.getUser().getName(); // NPE 위험 100%
이 코드는 내부적으로 user가 null일 가능성이 있어도 컴파일러는 아무 말도 하지 않습니다.
즉, “널 가능성”을 개발자가 감으로 추측해야 합니다.
코틀린으로 바꿔보면
val username = request.user?.name
- ?. 은 안전 호출 연산자입니다.
- user가 null이면 .name 호출 없이 null을 반환하고 끝납니다.
"null이 흘러들어올 수 있는 지점을 명시적으로 드러내준다"
실무 코드 리뷰 시 “이게 null일 수 있나요?”라는 질문이 사라짐.
실제 느낌:
서비스 코드에서 “기본값” 처리하는 경우가 많아서 ?:는 거의 습관처럼 사용하게 됨.
서비스 코드에서 “기본값” 처리하는 경우가 많아서 ?:는 거의 습관처럼 사용하게 됨.
!!는 위험한 강제 비활성화
val name = request.user!!.name
let으로 안전하게 로직 묶기
val user: User? = getUser()
user?.let {
println("Hello, ${it.name}")
}
let은 null이 아닐 때만 실행됨.
null-safe하면서도 가독성 좋은 코드를 만들 수 있습니다.
응용 예시 (Service 코드에서 자주 쓰는 형태):
val user = userRepository.findByIdOrNull(id)
user?.let {
sendWelcomeEmail(it.email)
} ?: throw NotFoundException("사용자를 찾을 수 없습니다.")
run / apply / also의 null-safe 활용
이건 스코프 함수에서 자세히 다루지만, 간단히 예고하자면 null-safe 패턴과 찰떡궁합입니다.
user?.apply {
lastLoginAt = LocalDateTime.now()
status = ACTIVE
}
“null이 아닐 때만 실행하되, this를 반환”하는 구조.
실습 예제: 자바식 NPE vs 코틀린 Null-Safe 처리
Java
if (user != null && user.getProfile() != null) {
return user.getProfile().getNickname();
}
return "anonymous";
Kotlin
return user?.profile?.nickname ?: "anonymous"
코드 길이 줄이는 게 목적이 아니라,
“null 가능성을 코드에 드러내고 안전하게 다루는 구조”로 바뀌었다는 게 핵심입니다.
실무에서의 설계 포인트
- nullable 타입(String?)은 설계적 신호다. → “이건 null일 수도 있다”
- 가능한 한 non-null로 정의하고, 꼭 필요할 때만 nullable을 쓴다.
- 서비스 레벨에서 null 대신 명시적 상태 객체나 sealed class로 처리하는 것도 좋다.
(예: Success, NotFound, Error)
회고
NPE를 “예외 처리로 막는 게 아니라”
애초에 타입 시스템으로 막는 경험이 새로웠습니다.특히 nullable 타입을 명시하면서,
데이터가 “없음(null)”이라는 개념을 좀 더 구조적으로 설계하게 되었어요.지금은 null-safe 코드가 오히려 “읽기 쉬운 코드”처럼 느껴집니다.