[Kotlin] require(), check(), assert()

케니스·2022년 12월 27일
2

Overview

코틀린에서 더 좋은 코드를 만들기 위해 기대한다면 아래의 함수들을 빠르게 선언하고 적용해야합니다.

  • require : 해당 인자에서 예상을 명시하기 위한 일반적인 방법
  • check : 해당 상태에서 예상을 명시하기 위한 일반적인 방법
  • assert : 테스트모드 어떤 것이 true 일 때 체크하는 방법
  • 엘비스 함수는 return 혹은 throw를 던집니다.

이런 선언적인 검사들을 했을 때 까지는 장점

  • 문서를 읽지 않는 개발자에게 도움이 될 수 있습니다
  • 조건을 만족하지 못했을 때 예외를 던집니다. 예외를 던지지 않았을 때 문제가 생겼을 때 해당 부분을 관리하기 어렵고 그렇기 때문에 어떠한 상태가 변경되기 전에 이러한 예외가 발생하는게 중요합니다. 이런 적극적인 검사가 에러를 놓치지 않고 상태를 안정적이게 도와줍니다.
  • 코드 자체가 셀프체크를 하기 때문에 유닛테스트에 가중되는 코드량을 줄일 수 있습니다.
  • 모든 검사는 스마트 캐스팅을하기 때문에 캐스팅에 대한 수고를 덜 수 있습니다.

Argument - require()

  • 코틀린에서 요구사항을 명시하는 가장 보편적인 방법은 요구사항이 충족되지 않을 때 require 함수를 사용하여 예외를 발생 가능하게 하는것입니다.
  • 이런 요구사항들을 점검하는 로직은 대부분 함수의 처음에 위치하기 때문에 개발자가 함수를 읽을 때 명확합니다(그래도 문서에 명시되어야 하긴 합니다)

예시

  • 숫자를 인자로 받아 계산할 때 그 숫자를 계산할 때 양의 정수 일 때
  • 클러스터에서 좌표값 리스트가 비어있지 않아야 할 때
  • 이메일을 보낼 때 유저가 이메일을 가지고 있거나 올바른 이메일형식인지 검사할 때
var someState: String? = null
fun getStateValue(): String {
    val state = checkNotNull(someState) { "State must be set beforehand" }
    check(state.isNotEmpty()) { "State must be non-empty" }
    // ...
    return state
}

// getStateValue() // will fail with IllegalStateException

someState = ""
// getStateValue() // will fail with IllegalStateException

someState = "non-empty-state"
println(getStateValue()) // non-empty-state

State - check()

  • check()require()와 유사하게 동작하지만 명시된 조건이 충족되지 않으면 illegalStateException을 발생시킵니다.
  • 예외 메시지는 require()과 마찬가지로 lazyMessage 람다함수를 블록을 통해 정의할 수 있습니다.
  • 보통은 사용자가 Contract를 위반하고 호출해서는 안되는 함수를 호출할 수 있다고 의심되는 경우 check() 를 이용해서 검사합니다.

예시

  • 먼저 초기화가 되어있는 객체가 필요한 함수
  • 사용자가 로그인했을 때 처리하고 싶은 함수
  • 객체가 열려있는 상태가 필요한 함수
fun getIndices(count: Int): List<Int> {
    require(count >= 0) { "Count must be non-negative, was $count" }
    // ...
    return List(count) { it + 1 }
}

// getIndices(-1) // will fail with IllegalArgumentException

println(getIndices(3)) // [1, 2, 3]

Assertions - assert()

보통은 프로덕션에서 오류가 발생하지 않고 테스트를 실행할 때만 Kotlin/JVM에서 활성화됩니다. 보통 사용자가 오류를 마주하기 전에 테스트를 선행하여 오류를 확인할 수 있습니다. 만약 심각한 오류를 발생할 가능성이 있다면 check()를 대신하여 사용해야합니다. 다음은 단위테스트 대신에 함수에서 assertion 검사를 하는 경우 장점으로 작용하는 점입니다.

장점

  • Assertion은 자체 검사를 통해 더 효과적인 테스트로 만듭니다
  • 구체적인 유스케이스보다 모든 유스케이스에 대해 확인합니다
  • 정확한 포인트에 무엇을 확인하는게 사용합니다
  • 빠르게 오류코드를 찾아내고 예기치 않은 동작이 어디서 시작될지 예측하고 쉽게 찾을 수 있습니다

Nullability and start casting

require함수 혹은 check 함수는 어떤 조건을 확인후에 true가 나왔다면 이후에도 true일거라고 가정합니다. 만약 null 체크와 같이 requirecheck를 사용하려면 requireNotNullcheckNotNull과 같은 함수를 사용할 수 있습니다.

fun printRequiredParam(params: Map<String, String?>) {
    val required: String = requireNotNull(params["required"]) { "Required value must be non-null" } // returns a non-null value
    println(required)
    // ...
}
var someState: String? = null
fun getStateValue(): String {
    val state = checkNotNull(someState) { "State must be set beforehand" }
    check(state.isNotEmpty()) { "State must be non-empty" }
    // ...
    return state
}

// getStateValue() // will fail with IllegalStateException

someState = ""
// getStateValue() // will fail with IllegalStateException

someState = "non-empty-state"
println(getStateValue()) // non-empty-state

엘비스 연산자는 return 혹은 throw가 nullability를 목적으로하는 변수에 대해서는 사용하는 것을 권장합니다.

참고

profile
노력하는 개발자입니다.

1개의 댓글

comment-user-thumbnail
2024년 9월 23일

글 잘봤습니다.!
예시 코드스니펫이 반대로 되어 있는거 같아서요.
require와 check 예시문 정정해서 올려주시면 좋겠습니다!

답글 달기