Study/object

[엘레강트 오브젝트] 3-3장 인자의 값으로 NULL을 절대 허용하지 마세요.

에디개발자 2021. 9. 17. 00:59
반응형

나를 닮았다고 한다...

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

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

 

목차

  1. 객체를 존중하라
  2. NULL Object를 이용하라
  3. 외부 영향

1. 객체를 존중하라

객체는 목표를 가지고 역할을 수행하고 있습니다. 하지만 객체의 인자값에 NULL을 허용한다면 객체가 역할을 수행하는 것을 무시하는 행위가 됩니다.

// NULL 허용
fun find(mask: String?): Iterable<File> {
    // 경로 탐색
    // mask가 null일 경우 모든 파일 검색
    // mask를 이용한 파일 검색
}

// NULL 허용하지 않는 경우
fun find(mask: String): Iterable<File>
fun findAll(): Iterable<File>

Null을 허용할 경우 하나의 메서드로 작성할 수 있기 때문에 사용자에게 편이성을 주는 것 같지만 객체가 자신의 행동을 온전히 책임진다는 객체 패러다임과는 맞지 않습니다. 

 

Null을 허용할 경우

객체의 역할을 무시하는 코드입니다.

/**
 * 디렉토리 탐색
 *  - mask에 일치하는 모든 파일 찾기
 *  - mask가 null이면 모든 파일 찾기
 * @param mask String?
 * @return Iterable<File>
 */
fun find(mask: String?): Iterable<File> {
    if (mask == null) {
        // 모든 파일 검색   
    } else {
        // mask를 이용한 파일 검색
    } 
}

 

Null을 허용하지 않는 경우

객체의 역할을 존중하는 코드입니다.

// Null을 허용하지 않고 인자값으로 객체를 넘긴다.
fun find(mask: Mask): Iterable<File> {
    if (mask.empty()) {
        // 모든 파일 검색
    } else {
        // mask를 이용한 파일 검색
    }
}

Mask 객체를 인자값으로 넘겨 Mask 객체 메서드의 empty()를 이용하여 빈 값 여부를 Mask에게 스스로 결정할 수 있도록 작성합니다.

 

// 개선된 코드
fun find(mask: Mask): Iterable<File> {
    val files = mutableListOf<File>()
    for (file in /* All File */) {
        if (mask.matches(file))
            files.add(file) 
    }
    return files
}

개선된 방법으로 모든 파일 중 Mask 객체 메서드의 matches()를 이용하여 매칭되는 file를 찾을 수 있도록 작성합니다.

 

2. NULL Object를 이용하라

객체의 인자값으로 NULL을 허용한다면 객체가 맡아야하는 역할과 책임을 빼앗는 것입니다. OOP에서 존재하지 않는 인자에 대한 문제는 NULL Object를 이용해서 해결할 수 있습니다. 

 

이전 코드에서 mask가 null일 경우 모든 file을 찾아야한다는 조건이 있었습니다. 해당 경우를 NULL Object를 이용해서 해결해보겠습니다. 

interface Mask {
    fun matches(file: File): Boolean
}

class AnyFile: Mask {
    override fun matches(file: File): Boolean {
        return true
    }
}

AnyFile이라는 객체륾 만들고 matches 메서드를 오버라이드하여 항상 true를 반환하도록 작성한다면 모든 파일을 찾을 수 있습니다. 

val files = find(AnyFile())  // AnyFile 객체 사용

fun find(mask: Mask): Iterable<File> {
    val files = mutableListOf<File>()
    for (file in /* All File */) {
        if (mask.matches(file))    // 항상 true
            files.add(file) 
    }
    return files
}

 

3. 외부 영향

우리가 작성한 코드는 NULL을 인자값으로 사용하지 않는 컨벤션을 잘 지키고 있다고 가정합니다. 하지만 Client에서 NULL을 호출한다면 문제가 발생할 것입니다. 이 때 2가지로 처리할 수 있습니다. 

Null 체크 후 throw Exception 

fun find(mask: Mask?): Iterable<File> {
    if (mask == null) {
        throw IllegalArgumentException("Mask can't be NULL")
    }
        
    // mask를 사용하여 file 검색
}

인자값을 Null 체크한 후 Exception을 발생시켜 처리합니다. 

 

NullPointException

// use
val files = find(null)    // NullPointException 발생

fun find(mask: Mask): Iterable<File> {
    // mask를 사용하여 file 검색
}

인자값에 Null을 허용하지 않도록하여 NullPointException을 발생시킵니다. Client에게 잘못된 인자값을 넘겼다고 알려주는 방법입니다.

반응형