블록이 실행되지 말아야 할 때의 Best Practice
Kotlin에서는 if-else 문으로 코드 블록을 실행할지 결정한다. 하지만 if-else 문을 중첩해서 쓰면 블록이 계속 블록이 중첩되기 때문에 가독성이 떨어진다. 특히 오랫동안 유지보수된 레거시 코드들을 보면 이러한 상황이 매우 잘 나타난다.
보통 이러한 코드들은 null이 아님을 확인하기 위해 1번 if 문을 쓰는 것을 기본으로 한다. 예를 들어 다음 코드와 같이 사용된다.
fun blockExecuteExample(apple : Apple?) {
if (apple != null) {
if(apple is Fruit) {
eat()
}
}
}
여기서 끝나면 괜찮겠지만 보통 이런 코드들은 if문을 여러 번 중첩한다. if 문을 여러 번 중첩하면 중첩할 수록 코드의 내용을 파악하기 어려워진다.
if 문을 중첩 없이 사용하기
위와 같은 상황에서 if 문을 중첩하지 않고 쓰는 방법이 있다. 바로 다음과 같이 apple이 null 일 때 블록을 종료해버리는 방법이다. 이렇게 하면 다음 조건문이 이전 조건문에 의해 중첩되지 않아 가독성이 좋아진다.
fun blockExecuteExample(apple : Apple?) {
if(apple == null) return
if(apple is Fruit) {
eat()
}
}
위의 코드에서 만약 apple에 null 값이 왔을 경우 메서드를 멈추는 것이 아닌, Error을 발생시켜야 한다면 다음과 같이 Exception을 throw 시키면 된다.
fun blockExecuteExample(apple : Apple) {
if(apple == null) throw IllegalArgumentException("Null argument is not valid")
if(apple is Fruit) {
eat()
}
}
위의 코드가 if 블록의 가독성은 올려주지만, apple에 null이 들어가면 안된다는 것에 대한 의미는 곧바로 파악하기 어렵다. Kotlin에서는 이러한 경우를 위해 require 메서드를 제공한다.
require 메서드를 활용해 Argument에 제한 걸기
아래의 코드는 require 메서드의 내부 구현 이다. require 메서드는 인자로 받는 argument가 조건을 만족하면 메서드를 계속 수행하고, 만족하지 못하면 메서드를 멈추고 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())
}
}
require 메서드를 사용하면 우리가 앞서 사용한 blockExecuteExample 메서드를 다음과 같이 바꿀 수 있다.
fun blockExecuteExample(apple : Apple) {
require(apple != null){
"apple should not be null"
}
if(apple is Fruit) {
eat()
}
}
훨씬 가독성이 좋아지지 않았는가? 만약 조건을 만족하지 못할 경우 Exception이 Throw 되어야 한다면 if 대신 require을 사용하도록 하자.