Study/object

[엘레강트 오브젝트] 4-1장 절대 NULL을 반환하지 마세요

에디개발자 2021. 10. 5. 22:23
반응형

나를 닮았다고 한다...

이 글은 엘레강트 오브젝트 새로운 관점에서 바라본 객체지향 도서를 보며 스터디한 글입니다.

책에서 주장하는 내용을 정리하였으며 예제들은 모두 코틀린 코드로 변환하여 작성하였습니다.

 

목차

  1. 무너진 신뢰
  2. 빠르게 실패하기 vs 안전하게 실패하기
  3. NULL 대안
  4. 주관적인 생각

1. 무너진 신뢰

이전 글에서 메서드 인자에 Null을 전달하는 것은 매우 안좋은 방법이라고 소개하였습니다. 마찬가지로 NULL을 반환하는 방법 또한 안좋은 방법입니다.

class Page(
    val title: String?
) {
    fun title(): String? {
        if (title == null) {
            return null  // null return
        }
        return "Elegant Objects"
    }
}

위와 같은 Page 클래스가 있다고 가정합니다. 이 클래스의 title() 메서드는 title이 존재하지 않다면 null을 반환하고 있습니다. 다른 말로 title() 메서드를 사용하면 null을 받을 수 있다는 이야기입니다. 즉, title() 메서드에 대한 신뢰가 무너진다는 사실입니다. 

 

title() 메서드를 사용하기 위해선 아래처럼 null 처리를 반드시 해야합니다.

fun use() {
    val page = Page(null)
    val title = page.title
    if (title == null) {  // null 처리
        println("Can't print. it's not a tile.")
        return
    }
    println("title length=${title.length}")
}

우리는 Page라는 클래스의 title() 메서드를 신뢰할 수 없게 되고 반드시 Null처리 로직을 추가로 작성해야합니다. 메서드에서 NULL을 반환한다면 우리는 이 메서드에서 어떤 경우에 NULL을 반환하는지 분석을 해야만 합니다. 이 것은 유지보수성을 매우 떨어뜨리는 행위입니다.

이처럼 유지보수성을 떨어뜨리는 NULL값 반환보단 아래와 같이 Exception으로 처리하면 코드는 간결하고, 깔끔해질 것 입니다. 

fun title(): String {
    if (title == null) {
        throw IllegalArgumentException("타이틀이 존재하지 않습니다.")
    }
    return "Elegant Objects"
}

이 방법을 빠르게 실패하기라고 합니다.

 

2. 빠르게 실패하기 vs 안전하게 실패하기

안전하게 실패하기

이 방법은 버그, 입출력 문제, 메모리 오버플로우 등 문제가 발생해도 계속 실행할 수 있도록 하는 방법입니다. Null을 반환하는 방법 또한 안전하게 실패하기 중 한 가지 방법입니다. 잘못된 요청이 들어와도 Null을 반환하여 처리할 수 있습니다. 하지만 받는 쪽에서 Null 처리를 하지 않는다면 이 코드는 잠재적 오류를 품은 채 작동할 것 입니다.

 

빠르게 실패하기

안전하게 실패하기와는 다르게 어떤 문제가 발생하면 눈에 보이도록 즉시 실패처리를 합니다. 그렇다면 개발자는 어떤 방법을 써서라도 실패한 케이스에 대해서 처리를 하게 될 것 입니다. 

 

그렇다면 안전하게 실패하는 Null 반환대신 빠르게 실패하려면 어떤 방법을 써야할까요??

3. NULL 대안

fun user(name: String): User? {
    val user = repository.findByName(name)
    if (user == null)
        return null
    return user
}

위 코드는 DB에서 User를 찾고 없으면 NULL을 반환하는 흔한 코드입니다. 이 코드를 개선해보겠습니다.

 

Collection으로 반환

fun getUser(name: String): List<User> {
    val user = repository.findByName(name)
    if (user == null)
        return Collections.emptyList()

    return Collections.singletonList(user)
}

// use
val users = getUsers("")
if (users.isEmpty()) {   // 객체의 메서드를 이용해 처리 가능
    val user = users[0]
    // user 사용
}

 

NULL 객체 반환

class NullUser(
    private val label: String
): User {
    override fun name(): String {
        return this.label
    }

    override fun raise(salary: String) {
        throw IllegalStateException("봉급을 인상할 수 없습니다. 나는 Stub입니다!")
    }
}

 

이번 글을 요약하자면 NULL을 반환하지 말자!!! 

 

4. 주관적인 생각

이번 장을 공부하고 실무에서 NULL 반환을 안하는 방향으로 코드를 작성하였습니다. 결론적으로 코드가 훨씬 깔끔해지고 내가 해야할 일을 남에게 미루는 일이 적어졌습니다. NULL을 반환하면 내가 만드는 코드를 사용하는 그 누군가가 NULL 처리를 하세요~ 라고 무책임하게 던진 경우가 많았습니다. 하지만 NULL을 반환하지 않고 내부에서 처리함으로써 예외 케이스, 버그 발생도 현저히 줄어들었습니다.

반응형