지난번에 올려드렸던 [B급 프로그래머] SQL에서 UNIQUE와 NULL 제약 조건을 많은 분들께서 재미있게 읽어주셨기에, 오늘은 신이 난 김에... 프로그래머 관점에서 NULL을 살펴보기로 하자.
데이터베이스 뿐만 아니라 프로그래밍 과정에서도 NULL은 아주 골치 아픈 존재다. NULL은 실패를 의미하기도 하며, 성공을 의미하기도 하며, 둘 다를 의미하기도 할뿐더러... Map과 같은 자료 구조에서 반환된 NULL은 Map에 키로 조회한 결과 값이 NULL인지 아니면 없는지 구분하기 어렵게 만들기에 주의 깊게 사용하지 않으면 대박 버그를 양산할 가능성을 높인다. 특히 C/C++에 익숙한 개발자라면 습관적으로 NULL을 반환하는 함수를 작성해왔기에 NULL 사용을 특히 조심할 필요가 있다.
자 그렇다면 NULL의 대안이 무엇일까? C++ 프로그래머라면 boost에서 제공하는 Optional을 사용하면 되며, 자바 프로그래머라면 구글이 만든 Guava 라이브러리에서 제공하는 Optional을 사용하면 된다. C++나 자바나 기본 사상은 비슷하므로 실제 자바 코드를 보면서 특성을 파악해보자. 아주 간단한 예부터 살펴보자.
package jrogue
import com.google.common.base.Optional;
public class BasicTest {
@Test
public void optionalTest() throws Exception {
Optional possible = Optional.of(5);
System.out.println("Is possible present? " + possible.isPresent());
System.out.println("possible is " + possible.get());
}
}
Optional은 null이 아닌 값을 포함하거나 아무것도 포함하지 않는다. 위 예에서는 Optional.of() 메소드에 인수로 5를 넘겼기에 isPresent() 메소드가 'true'를 돌려주며, get() 메소드는 값인 5를 돌려줄 것이다. 그렇다면 비어있다는 의미에서 null(응?)은 어떻게 표현할까? 다음 예를 살펴보자.
package jrogue
import com.google.common.base.Optional;
public class BasicTest {
@Test
public void optionalAbsentTest() throws Exception {
Optional absent = Optional.absent();
System.out.println("Is absent present? " + absent.isPresent());
System.out.println("absent is " + absent.get());
}
}
Optional.absent() 메소드는 아무것도 포함하지 않는 상태로 만들어준다. 따라서 isPresent() 메소드가 'false'를 돌려주며, get() 메소드는... "java.lang.IllegalStateException: Optional.get() cannot be called on an absent value"라는 예외를 던진다. 음... try catch로 예외를 잡아도 좋지만 뭔가 다른 처리 방법은 없을까? 다음 예를 살펴보자.
package jrogue
import com.google.common.base.Optional;
public class BasicTest {
@Test
public void optionalAbsentOrTest() throws Exception {
Optional absent = Optional.absent();
System.out.println("absent or " + absent.or(0));
System.out.println("absent orNull " + absent.orNull());
}
}
or 메소드는 만일 null이 아닌 값을 포함할 경우 해당 값을, 아무것도 포함하지 않을 경우 인수로 넘긴 값을 기본값으로 돌려준다(여기서는 Integer 0을 반환하겠지? 기본값을 잘 활용하면 실행 중 null을 참조함으로써 발생하는 끔찍한 NullPointerException을 쉽게 요리할 수 있을 것이다). 그리고 orNull 메소드는 만일 null이 아닌 값을 포함할 경우 해당 값을, 아무것도 포함하지 않을 경우 'null'을 돌려준다. 자 그렇다면 Optional.of()에 인수로 null을 넘기면 어떻게 될까?
package jrogue
import com.google.common.base.Optional;
public class BasicTest {
@Test
public void optionalAbsentAssignTest() throws Exception {
Optional impossible = Optional.of(null);
System.out.println("Is impossible present? " + impossible.isPresent());
System.out.println("impossible is " + impossible.get());
}
}
지금쯤이면 이미 결과를 예상하고 있겠지만 "java.lang.NullPointerException" 예외를 던진다. of() 메소드는 null을 인수로 받아들이지 않으며, 비어 있는 의미에서 null을 지정하려면 absent() 메소드를 사용해야 한다. 그렇다면 null인지 아닌지 모르는 값을 Optional에 대입할 때 매번 번거롭게 orNull로 점검을 해야할까? 다음 예를 살펴보자.
package jrogue
import com.google.common.base.Optional;
public class BasicTest {
@Test
public void optionalAssignTest() throws Exception {
Integer value_five = 5;
Integer value_null = null;
Optional possible = Optional.fromNullable(value_five);
Optional absent = Optional.fromNullable(value_null);
System.out.println("Is possible present? " + possible.isPresent());
System.out.println("Is absent present? " + absent.isPresent());
}
}
결과는 여러분들이 예상한 그대로다. Optional.fromNullable() 메소드에 Nullable Reference를 넘기면 알아서 처리해준다.
지금까지 Guava의 Optional을 신나게 살펴봤다. null을 넘기는 방식에 비해 번거롭다는 생각이 들지도 모르겠는데, 이게 바로 Guava Optional 설계자의 의도다. 원문을 그대로 가져와볼까?
Besides the increase in readability that comes from giving null a name, the biggest advantage of Optional is its idiot-proof-ness. It forces you to actively think about the absent case if you want your program to compile at all, since you have to actively unwrap the Optional and address that case. Null makes it disturbingly easy to simply forget things, and though FindBugs helps, we don't think it addresses the issue nearly as well.
Optional이 있으면 프로그래밍 과정에서 한번 더 생각해야 하므로 null로 인해 흔히 발생하는 오류를 잡을 수 있게 된다. 하지만 Optional이 절대 null을 완전히 없애버릴 만병통치약은 아니므로, 필요한 상황에서 적절히 활용하면 되겠다.
EOB