아이템 5. 예외를 활용해 코드에 제한을 걸어라

코틀린에선 다음과 같은 방법으로 제한을 걸 수 있다.

  • require : 아규먼트 제한

  • check : 상태와 관련된 동작 제한

  • assert : 어떤 것이 true인지 확인 (테스트 모드에서만 동작)

  • return 또는 throw와 함께 사용하는 Elvis 연산자

다음은 예시이다.

fun pop(num: Int = 1): List<T> {
  require(num <= size) { "현재 사이즈보다 많은 element를 지울 수 없습니다" }
  check(isOpen) { "빈 스택을 pop할 수 없습니다" }
  val ret = collection.take(num)
  collection = collection.drop(num)
  assert(ret.size == num)
  return let
}

제한을 사용할 경우 장점

  • 문서를 읽지 않은 개발자도 문제를 확인할 수 있다.

  • 문제가 있는 경우 예외를 throw 해서 예상치 못한 동작을 방지하고 안정적으로 동작한다.

  • 관련된 단위테스트를 줄일 수 있다.

  • 스마트 캐스트를 활용해 캐스팅을 적게 할 수 있다.

아규먼트

함수를 정의할 때 아규먼트(argument)에 제한을 거는 코드를 많이 사용하는데, 이때 require 함수를 사용한다.

fun factorial(n: Int): Long {
  require(n >= 0) { "n이 0 이상이어야 합니다"}
  return if (n <= 1) 1 else factorial(n - 1) * n
}

아규먼트 제한 코드는 함수 가장 앞 부분에 배치되기 때문에 쉽게 확인할 수 있다.

상태

특정 조건을 만족할 때만 함수를 사용하게 해야할 때가 있다.

  • 객체가 초기화 되어있어야 사용할 수 있는 함수

  • 로그인했을 때만 사용할 수 있는 함수

  • 객체가 사용 가능한 시점에 사용할 수 있는 함수

조건을 만족하지 못할 때 IllegalStateException 예외를 발생시킨다. 일반적으로 require 뒤에 배치한다.

Assert 계열 함수 사용

함수가 올바르게 구현되었는지 확인하기 위해서 단위테스트를 사용한다. 하지만 단위테스트를 작성한 경우를 제외하고 모든 상황에서 제대로 동작하는지 확인할 수 없다. 이때 assert를 이용해 모든 함수 호출마다 제대로 동작하는지 확인할 수 있다.

assert 코드는 프로덕션 환경에 있어도 테스트할때 -ea JVM 옵션을 활성화 해야만 확인할 수 있다. assert를 사용하면 다음과 같은 장점이 있다.

  • 코드 자체를 점검해 더 효율적으로 테스트할 수 있다.

  • 특정 상황이 아닌 모든 상황을 테스트할 수 있다.

  • 실행 시점에 어떻게 되는지 확인할 수 있다.

  • 코드를 빠른 시점에 실패하게 만들어서 디버깅이 쉽다.

하지만 프로덕션 환경에선 assert가 예외를 발생시키지 않기 때문에 단위 테스트는 따로 작성해야 한다.

nullability와 스마트 캐스팅

require, check 블록으로 타입 비교를 하면 스마트 캐스팅이 된다.

fun changeDress(person: Person) {
  require(person.outfit is Dress) // 체크 이후 outfit은 Dress 타입
  val dress: Dress = person.outfit
}

이러한 특징은 null인지 확인할때도 유용하다. 이렇게 변수를 언팩(unpack)하는 용도로 사용할 수도 있다.

class Person(val email: String?)

fun sendEmail(person: Person) {
  val email = requireNotNull(person.email)
}

nullability를 목적으로 throw 또는 return과 Elvis 연산자를 활용할 수도 있다.

fun sendEmail(person: Person) {
  val email = person.email ?: return
}

run 함수와 조합으로 로그를 남길 수도 있다.

fun sendEmail(person: Person) {
  val email = person.email ?: run {
    log("email 주소가 없습니다")
    return
  }
}

return, throw를 활용한 Elvis 연산자는 자주 사용되는 방법으로 적극적으로 활용하는 것이 좋다. 또한 함수 앞부분에 넣어서 잘 보이게 하는 것이 좋다.

정리

  • 제한을 활용하면 코드를 잘못 쓰는 상황을 막을 수 있다.

  • 스마트 캐스팅을 활용할 수 있다.

  • require : 아규먼트 관련 예측을 정의

  • check : 상태 관련 예측을 정의

  • assert : 테스트 모드에서 테스트할 때 사용

  • return, throw와 함께 Elvis 연산자 사용

Last updated