[kotlin] 논리적 오류가 아니라면 null 반환

핑구·2023년 2월 26일
1

kotlin

목록 보기
1/4
post-thumbnail

서론

이번 로또 2단계 미션의 hint에 이런 말이 있었다.

논리적인 오류가 아니면 예외를 던지지 말고 null을 반환한다

아무 생각 없이 이 조건에 맞추려다가
null 을 반환하는 것이 예외를 던지는 것에 비해 어떤 이점이 있는지 고민해보시면 좋겠어요.
라는 리뷰어님의 조언을 시작으로 의문을 가지게 되었다.
먼저 몇 가지의 이론에 대해 찾아보았다.

논리적 오류란?

논리적 오류란 프로그램이 부정확하게 동작하게 하지만 비정상적으로 종료 또는 충돌시키지는 않는 오류이다.
예를 들어 입력된 로또 번호가 1-45가 아닌 0이나 46으로 만들어지는 경우 논리 오류가 발생하게 된다.
이처럼 논리 오류는 종료되거나 충돌되어 예외가 발생하지 않기 때문에 빠르게 실패하도록 해야한다. 보통 우리가 require()로 검사해주는 것이 이에 해당한다.

코틀린의 null safety

코틀린은 null safety를 가진 언어이다.
자바와 같은 다른 프로그래밍 언어에서는 NPE(NullPointerException)이 자주 일어난다. 코틀린은 이를 방지하기 위한 다양한 null처리 장치들을 두었다.
먼저 코틀린은 다른 언어들과 다르게 null을 담을 수 있는 타입(nullable)과 null을 담을 수 없는 타입(non-null)으로 나누어져있다.
nullable타입은 ?연산자를 통해 정의 가능하다.

nullable한 프로퍼티 접근법

  1. if문 사용

    val l = if (b != null) b.length else -1

    위와 같이 null인지 검사한 후에 내용이 수행되도록 한다.

  2. 안전 호출 연산자(safe call) - ?.

    val a = String? = null
    println(a?.length)

    위와 같이 작성하게 된다면 a가 null인 경우에는 null을 반환하게 된다.
    만약 non-null이어야 하는 작업을 nullable한 값이 수행해야할 경우 아래와 같이 let을 사용할 수 있다.

    val number: Int? = null
    val money = number?.let{ Money(it) }
  3. Elvis 연산자 - ?:
    위에서 if문을 사용한 코드를 Elvis연산자를 사용하여 더 간단하게 바꿀 수 있다.

    val l = b?.length ?: -1

    ?:의 왼쪽의 값이 null이 아니라면 그것을 반환하고 null이라면 오른쪽 값을 반환한다.

  4. double-bang 연산자 (non-null 단언 연산자) - !!
    어떤 값이든 non-null 타입으로 바꿔주게 된다. 만약 그 값이 null이라면 예외를 발생시킨다.

    val money = Money(number!!)

    NPE를 발생시킬 수 있기 때문에 주의해서 사용해야한다.(공식 문서에는 NPE-lovers를 위한 것이라고 되어있다🤣)

이번 미션을 하면서 요긴하게 썼던 기능들

  • toIntOrNull() : Int화 되지않는다면 null 반환
  • filterNotNull() : null값을 제외한 리스트를 반환
  • getOrNull() : 참조할 값이 존재하지 않거나 잘못되었을 경우 null을 반환

또 의문

이정도 알아보고 나니 개인적으로 결론을 내릴 수 있었다.

코틀린에는 다양한 NPE 방지 기능이 있으니 이를 사용하여 구조를 만들어 놓으면 알아서 논리적 오류가 아닌 경우(잘못된 입력값)가 걸려져서 null이 반환되게 된다. 그러니 굳이 모든 상황에 예외를 던져주지 않고 논리적 오류같이 중요한 경우에만 예외를 던져주자!

라는 의미에서 논리적 오류가 아닌 경우에 null을 반환하도록 한 것으로 생각이 되었다.
하지만 또 의문점이 생겼다. null을 반환해주더라도 결국 controller에서 이를 잡아주어야하니 굳이 null을 반환해줄 필요없이 옳은 값이 나올 때까지 반복해 주면 되는것 아닐까?
이에 대한 리뷰어님의 답변을 정리하고자 한다.

답변

논리적이지 않은 오류가 발생했을 때, 예외처리를 할 수 있는 방법은 다양하다. hint에 제시된 것처럼 null 을 반환하여 옳지 않은 값이 전달됐다고 ‘약속‘을 할 수 도 있고, 다시 입력값을 받도록 처리할수도 있다.
즉, 예외 상황에 대한 처리에 대해 정답은 없다.

null 을 반환할 경우, controller 혹은 또 다른 레이어에서 null 에 대한 처리를 진행해야한다. 다만, 무조건 예외를 던진다기 보다 상황에 맞게 처리 방식을 선택할 수 있을 것이다.

이에 대해서 “결국 어디서 처리되느냐의 차이만 있을 뿐, 똑같은거 아니냐?’ 라는 생각을 가질 수 있다. 하지만 이 차이는 꽤나 크다.

단순히 null 을 던지느냐 아니냐를 넘어서, 예외상황이 발생했을 때 로직의 제어권을 어디로 넘겨주냐의 논의로도 볼 수 있다.

예외적인 상황이 발생했을 때, 다시 해당 함수를 호출하는 구조에서는 Controller 가 예외상황에 대해 접근할 수 없다.

이 상황에서 예외상황에 대한 추가적인 요구사항이 들어온다면, InputView 에 코드가 추가되어야 하므로 view 에서 비즈니스 로직을 처리하는 구조가 될 것이다. 이는 MVC 등의 컴포넌트로 분리한 이점이 많이 사라지게 된다.

추가로, 코틀린의 경우 예외상황에서 에러가 아닌 null 을 던지는 방식이 표준 라이브러리 레벨에서 정의되어있으므로 좀 더 일반적인 처리에 가깝다고도 말할 수 있을 것이다.

결론

돌아 돌아서 왔지만 결론은 ⭐정답은 없다⭐는 것이다.
리뷰어님이 주신 답변도 정답은 아니다!
지속적인 탐구와 경험을 통해 개인에 맞는 스타일을 찾으면 된다고 생각한다.
다만 코틀린에서 이러한 기능들을 제공하는 이유가 있을 것이니 잘 생각해봐야겠다.


참고
https://kotlinlang.org/docs/null-safety.html
https://elizarov.medium.com/kotlin-and-exceptions-8062f589d07

profile
발전중

2개의 댓글

comment-user-thumbnail
2023년 2월 27일

정리 정말 잘하셨네요😊 잘 읽고 갑니당 ㅎㅎ

1개의 답글