안전한 JPA 조회·null 처리: Optional.empty() & !! 사용 기준

박경희·6일 전
0

공부를 해보자

목록 보기
39/40

1. 삭제 후 findByIdOptional.empty()인 이유

동작 방식

  • Spring Data JPA의 findById(id: Long)Optional<T> 를 반환한다.
  • 삭제된 엔티티를 조회하면 Optional.empty() 상태가 되며,
    이는 “Optional 객체는 있지만 내부에 값이 없다”는 의미다.

코드 예시

// 삭제 전: save() → findById() → Optional.of(entity)
val saved = contiRepository.save(conti)
val before: Optional<Conti> = contiRepository.findById(saved.id!!)
assertThat(before).isPresent

// 삭제 후: delete() → findById() → Optional.empty()
contiRepository.delete(saved)
val after: Optional<Conti> = contiRepository.findById(saved.id!!)
assertThat(after).isEmpty

2. 프로덕션 코드에서의 !! 남용 금지

왜 금지해야 하나?

  • Kotlin의 !! 연산자는 런타임에 NPE를 유발한다.

  • 안정성이 중요한 프로덕션 코드에선 명시적인 null 처리가 필수다.

피해야 할 패턴

// ❌ 위험: null이면 곧바로 NPE 발생
fun process(x: String?) {
    println(x!!.length)
}

안전한 대체 패턴

  1. requireNotNull(value) { "메시지" }
val safeX = requireNotNull(x) { "x는 반드시 필요합니다." }
println(safeX.length)
  • 이 패턴은 “null이면 fail with message”라는 의도를 깔끔히 보여준다.

왜 안전한가?

  • 컴파일러 지원: 호출 시점에 null인지 명시적으로 체크하도록 강제한다.

  • 명확한 예외 메시지: requireNotNull에 전달한 람다 메시지가 포함된 IllegalArgumentException이 발생해, 문제 지점을 빠르게 파악할 수 있다.

  • 런타임 안정성: NPE 대신 IllegalArgumentException이 발생하므로, 로그와 스택트레이스를 통해 원인을 정확히 알 수 있다.

  1. Elvis 연산자 ?: throw
val safeX = x ?: throw IllegalArgumentException("x가 없습니다")
println(safeX.length)

왜 안전한가?

  • 간결함: 한 줄로 null 체크와 예외 발생을 모두 표현한다.

  • 일관된 예외 처리: 예외 타입과 메시지를 컨트롤할 수 있어, 서비스 계층 표준 예외 전략에 맞출 수 있다.

  • 명시적 의도: null일 때 절대 다음 로직에 진입하지 않겠다는 의도가 분명진다.

  1. ?.let { … } ?: run { … }
x?.let { 
  println(it.length) 
} ?: run {
  // 예외 처리 로직
}

왜 안전한가?

  • null-안전 블록 분리: null이 아닌 경우와 null인 경우를 완전히 분리된 블록으로 처리해, 로직의 가독성이 높아진다.

  • 부수 효과 최소화: null인 경우에만 실행될 로직을 명확히 선언해 두어, 사이드 이펙트를 관리하기 쉽다.

  • 무분별한 !! 제거: !! 대신 안전 호출(?.)을 기본으로 사용함으로써, NPE 위험을 원천 차단한다.


테스트 코드에서의 !! 사용

테스트 코드에서는 “여기까지 와서 null이면 곧바로 실패하라”는 의도를 명확히 드러내기 위해
!!를 쓰는 것이 오히려 가독성과 의도를 살리는 패턴이다.

// 테스트 내에서는 반드시 값이 있어야 하므로
val saved = contiRepository.save(conti)
val id = saved.id!!              // ID가 null이면 테스트 즉시 실패
val detail = contiService.getContiDetail(id)
  • 이렇게 하면 “실제론 결코 null일 리 없다”라는 전제가 명확히 드러난다.

  • 만약 null이라면 바로 NPE로 깨져서 “여기서 문제가 있구나”를 곧바로 알려준다.

0개의 댓글