본문 바로가기
Kotlin

예외를 활용해 코드에 제한 걸기

by 서퍼리노 2023. 1. 24.
728x90

코드를 작성할 때 확실하게 어떤 조건 일 때만 작동해야 하는 코드일 경우

예외를 걸어 제한해 주는 것이 좋습니다.

 

예를 들어 어떠한 api나 함수를 작성하게 되었을 때

해당 함수가 null일 때만 동작되는 함수를

아래 코드와 같이

그저 분기문으로 처리할 경우

 

해당 함수를 사용하는 사용자의 입장에선 null을 사용하였을 때 

아무런 동작을 하지 않아 직접 코드를 확인해야 합니다.

 

사용자 코드 제한하기

코드에 제한을 거는 방법

코드에 제한을 거는 방법에는 다음과 같은 방법들이 있습니다.

  • require 블록: 아규먼트를 제한할 수 있습니다.
  • check 블록: 상태와 관련된 동작을 제한할 수 있습니다.
  • assert 블록: 어떤 것이 ture 인지 확인할 수 있습니다. (테스트 모드에서만 작동합니다.)
  • return 또는 thorow와 함께 Elvis 연산자를 사용합니다.

이펙티브 코틀린이라는 책에선 예외를 관리해 준다면 아래와 같은 장점들이 발생한다고 소개한다.

장점

  • 제한을 걸면 문서를 읽지 않은 개발자도 문제를 확인할 수 있습니다.
  • 문제가 있을 경우에 함수가 예상하지 못한 동작을 하지 않고 예외를 thorow 합니다.
    예상하지 못하는 동작을 하는 것은 예외를 throw 하는 것보다 굉장히 위험하며,
    상태를 관리하는 것이 굉장히 힘듭니다. 이러한 제한으로 인해서 문제를 놓치지 않을 수 있고,
    코드가 더 안정적으로 작동하게 됩니다.
  • 코드가 어느 정도 자체적으로 검사됩니다. 따라서 이와 관련된 단위 테스트를 줄일 수 있습니다.
  • 스마트 캐스트 기능을 활용할 수 있게 되므로, 캐스트(타입 변환)를 적게 할 수 있습니다.

정리하자면

1. 문서를 굳이 읽지 않더라도 사용할 때 일어나는 문제들을 확인할 수 있다.

2. 예기치 못한 동작을 처리함으로써 코드를 안전하게 만들 수 있습니다.

3. 어느 정도 자체적으로 검사 됨에 따라 단위 테스트를 줄일 수 있습니다.

4. 스마트 캐스트 기능을 적절히 활용하여, 타입을 변환하는 것을 줄일 수 있습니다.

 

require 함수

일반적으로 require 함수는 아규먼트에 제한을 걸 때 사용합니다.

제한에 대한 예를 들어보자면

  • 숫자를 아규먼트로 받아서 팩토리얼을 계산한다면 숫자는 양의 정수여야 합니다.
  • 사용자로부터 이메일 주소를 입력받을 때는 값이 입력되어 있는지, 또한 이메일 형식이 올바른지 확인해야 합니다.

require 함수는 제한을 확인하고, 제한을 만족하지 못할 경우 예외를 throw 합니다.

 

require함수를 이용하여 팩토리얼을 푼다면 위와 같이 구성됩니다.

만약

해당 팩토리얼 함수에서 require에 충족하지 않는 값인 음수가 들어온다면??

다음과 같이 예외가 발생합니다.

 

require 함수는 조건을 만족하지 못할 경우

무조건적으로 IllegalArgumentException을 발생시키므로 제한을 무시할 수 없습니다.

 

일반적으로 require 함수는 함수의 가장 앞부분에 하게 작성되므로, 코드를 읽을 때 쉽게 확인할 수 있습니다.

 

Custom require 함수

require 함수는 람다식을 활용하여 지연 메시지를 정의할 수도 있습니다.

fun main() {
    println(factorial(-10))
}


fun factorial(n: Int): Long {
    require(n >= 0) {
        "Cannot calculate factorial of $n because it is smaller than 0"
    }
    return if (n <= 1) 1 else factorial(n - 1) * n
}

위와 같이 람다식을 이용해 작성한다면 아래와 같이 메시지를 남길 수 있다.

 

check 함수

어떠한 구체적인 조건을 만족할 때만 함수를 사용해야 할 때가 있습니다.

위의 아규먼트와 비슷하다고 생각할 수 있지만,

 

위에서 아규먼트로 받은 값을 확인하는 것이 아닌 

특정 상태에 따라 함수를 사용하는 경우도 있습니다. 

 

예를 들면

  • 어떤 객체가 초기화되어 있어야만 처리를 하게 하고 싶은 함수
  • 사용자가 로그인했을 때만 처리를 하게 하고 싶은 함수
  • 객체를 사용할 수 있는 시점에서 사용하고 싶은 함수

위와 같이 상태와 관련된 제한을 걸 때는 일반적으로 check 함수를 사용합니다.

check 함수는 require과 비슷한듯하지만,

지정된 예측을 만족하지 못할 때, IllegalStateException을 throw 합니다.

 

check 함수는 상태가 올바른지를 확인할 때 사용합니다.

 

require과 마찬가지로 지연 메시지를 사용하여 예외 메시지를 변경할 수 있습니다.

 

분류

require은 어떤 함수 전체에 대한 예측을 하고

check는 나중에 상태에 대해 예측함으로 

일반적으로 require 블록 뒤에 check를 나중에 배치합니다.

 

이러한 확인들은 사용자가 규약을 어기고, 사용하면 안 되는 곳에서 함수를 호출할 가능성이 있다고 

의심될 때 합니다.

 

사용자가 코드를 제대로 사용할 것이라 믿는 것보다

항상 문제가 될만한 상황을 예측하고, 해당 상황을 잘 처리해 주는 것이 좋습니다.

 

위에서 두 내용은 사용자를 확인했지만 구현하는 스스로도 예외를 처리할 수 있습니다.

 

스스로 예외 처리하기

우리는 어떠한 코드를 작성하였을 때 그 코드를 테스트해봐야 한다.

만약 어떤 함수가 2 + 2를 계산한다면, '함덧셈의 결과가 4인가?'라는 코드에서

'참'이라는 결과가 나올 것이다.

 

스스로 구현한 내용에 대해 예외를 처리할 때는 assert라는 계열이 함수를 사용한다.

assert 계열의 함수는 테스트 모드에서만 동작합니다.

 

 

assert 계열의 함수는 실제 프로덕션 환경에서는 오류가 발생하지 않고,

테스트 환경에서만 동작하기 때문에 오류가 발생해도 사용자가 알아차릴 수는 없습니다.

 

만약 이 코드의 오류가 정말 심각한 결과를 초래할 수 있는 오류일 경우는 

check를 사용하는 것이 좋습니다.

 

하지만 assert 함수에는 다음과 같은 장점이 있습니다.

  • 코드를 자체 점검하며, 더 효율적으로 테스트할 수 있게 해 줍니다.
  • 특정 상황이 아닌 모든 상황에 대한 테스트를 할 수 있습니다.
  • 실행 시점에 정확히 어떻게 되는지 확인할 수 있습니다.
  • 실제 코드가 더 빠른 시점에 실패하게 만듭니다.
    따라서 예상하지 못한 동작이 언제 어디서 실행되었는지 쉽게 찾을 수 있습니다.

 

정리

이번에 내용을 정리하자면

  • 사용자가 코드의 제한을 더 쉽게 확인할 수 있다.
  • 애플리케이션을 더 안정적으로 지킬 수 있다.
  • 코드를 잘못 쓰는 상황을 막을 수 있다.

또한 여담으로 저번에 올린 포스트 내용인 스마트 캐스팅도 활용할 수 있습니다.

 

  • require 블록: 아규먼트와 관련된 블록 전체에 대한 예측을 할 때 사용
  • check 블록: 상태와 관련된 예측을 할 때 사용
  • assert 블록: 테스트 모드에서 테스트할 때 사용

 

해당 포스트는 이펙티브 코틀린을 참고하여 정리되었습니다.

728x90