[엘레강트 오브젝트] 3-3장 인자의 값으로 NULL을 절대 허용하지 마세요.
이 글은 엘레강트 오브젝트 새로운 관점에서 바라본 객체지향 도서를 보며 스터디한 글입니다.
책에서 주장하는 내용을 정리하였으며 예제들은 모두 코틀린 코드로 변환하여 작성하였습니다.
목차
- 객체를 존중하라
- NULL Object를 이용하라
- 외부 영향
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에게 잘못된 인자값을 넘겼다고 알려주는 방법입니다.