본문 바로가기
프로그래밍/kotlin

[kotlin] 코틀린 차곡차곡 - 17. require와 check

by 사바라다 2022. 6. 5.

안녕하세요. 오늘은 오랫만에 코틀린 차곡차곡 시리즈로 찾아왔습니다. 오늘 여러분들께 공유 드릴 내용은 코틀린에서 validation에 대해서 이미 정해져 있는 코드들에 대한 내용입니다.

기존의 validation asis

메서드를 원하는 바로 정확하게 실행시키기 위해서는 실제 본연의 메서드 로직을 실행시키기전에 validation으로 input으로 들어오는 파라미터의 값이나 사용하는 상태 적절한지에 대한 판단이 필요합니다. 이러한 validation은 보통 아래의 2가지로 나눌 수 있습니다.

  • 파라미터 값 자체에 대한 validation
  • 파라미터 값에 의한 기존 Entity를 비롯한 도메인이 가지고 있는 상태에 대한 validation

이러한 valitaion을 구현한다고 하면 기존에는 아래의 코드처럼 일반적으로 구현합니다.

먼저 아래의 코드는 첫번째 상황인 파라미터 값 자체에 대한 validation 입니다. 만약의 controller에 있다면 Validator를 이용해서 작업을 했을 수도 있지만 해당 method는 controller만 호출하는 것이 아닌 것으로 보시면됩니다.

fun saveShopReview(shopId: String, request: ShopReviewCreateRequest) {

    if (shopId.isEmpty()) {
        log.warn { "[Shop] input shopId is empty" }
        throw IllegalArgumentException("input shopId must is not empty")
    }

    [...하략...]
}

아래는 두번째 상황인 상태에 대한 validation 입니다. shopId를 이용하여 shop을 가져왔지만 해당 값이 존재하지 않아 null을 반환한다는 의미를 가지고 있습니다. kotlin에서는 elvis 연산자를 이용해서 아래처럼 활용할 수 있습니다.

fun saveShopReview(shopId: Long) {

    val shop: Shop = shopRepository.findByIdOrNull(shopId)
        ?: throw IllegalStateException("worng input value, value : $shopId")

    [..하략..]
}

이런 부분에 대해서 kotlin에서는 좀 더 활용하기 쉽고 정형화 시키기위한 함수들을 제공합니다. 그것이 바로 require와 check 그리고 추가적으로 assert입니다.

require

required는 boolean 형태의 argument를 받고 만약 false라면 IllegalArgumentException를 throw 하는 kotlin 함수입니다. 이 함수는 parameter의 값을 검증하는데 사용할 수 있는 유용한 함수입니다.

내장 함수의 코드 본문을 보도록 하겠습니다. inline 함수로 들어온 Boolean 타입의 value 값이 false 이면 내부에서 IllegalArgumentException를 throw 하는 것을 알 수 있었습니다.

@kotlin.internal.InlineOnly
public inline fun require(value: Boolean, lazyMessage: () -> Any): Unit {
    contract {
        returns() implies value
    }
    if (!value) {
        val message = lazyMessage()
        throw IllegalArgumentException(message.toString())
    }
}

위의 함수를 사용하면 아래처럼 쓸 수 있습니다. 사용하실 때 기존엔 반환되는 메시지를 커스터마이징하고 싶으시다면 saveShopReview 함수 아래에 있는 코드를 require(shopId.isEmpty()) 대신에 사용하시면 됩니다.

fun saveShopReview(shopId: String, request: ShopReviewCreateRequest) {

    require(shopId.isNotEmpty())

    [...하략...]
}

// 반환 메시지 커스터마이징
require(shopId.isNotEmpty()) {
    "[Shop] input shopId is empty"
}

그리고 조금 더 특별한 함수가 있습니다. requireNotNull 라는 함수입니다. 이 함수는 require 에서 null 체크를 추상화한 메서드입니다. require 와 다른점은 내부에서 null이면 exception을 throw하고 null이 아니라면 해당 값을 반환한다는 사실입니다. 따라서 아래 처럼 코드를 작성하는 것이 가능합니다.

val validatedShopId = requireNotNull(shopId) {
    "[Shop] input shopId is empty"
}

check

check는 boolean 형태의 argument를 받고 만약 false라면 IllegalStateException를 throw하는 kotlin 함수입니다. 이 함수는 이용하고 하는 Entity의 상태, 파라미터 이외를 점검하는데 유용한 함수입니다.

해당 내장 함수 의 본문을 확인하면 require과 크게 차이는 없고 발생되는 Exception 정도의 차이가 있다는 사실을 알 수 있습니다.

@kotlin.internal.InlineOnly
public inline fun check(value: Boolean, lazyMessage: () -> Any): Unit {
    contract {
        returns() implies value
    }
    if (!value) {
        val message = lazyMessage()
        throw IllegalStateException(message.toString())
    }
}

위의 예제 함수에 대해서 check로 변경한다고 하면 아래처럼 바꿀 수 있습니다.

fun saveShopReview(shopId: Long) {

    val shop = shopRepository.findByIdOrNull(shopId)
    check(shop != null) {
            "worng input value, value : $shopId"
    }

    [..하략..]
}

그런데 사실 이렇게 바꾸면 check 함수와 shop을 가져오는 2가지의 코드가 생기게 됩니다. 조금 더 단순화 했으면 좋겠다는 생각이 드니 코드입니다. 이 경우 사용할 수 있는것이 checkNotNull입니다. checkNotNull을 사용하면 이 2줄의 코드를 하나로 만들어낼 수 있습니다.

val shop = checkNotNull(shopRepository.findByIdOrNull(shopId)) {
    "worng input value, value : $shopId"
}

마무리

이 외에도 assert 라고하는 테스트시에만 적용되는 함수가 있습니다.

이는 실제 runtime 환경이 아닌 test 코드시에만 validation을 하는 함수인데요.

관심있으신 분들은 이것도 한번 찾아봐서 확인해보시는 것을 추천드립니다.

감사합니다.

참조

이펙티브 코틀린

https://bignerdranch.com/blog/write-better-code-using-kotlins-require-check-and-assert/

댓글