레이블이 Option인 게시물을 표시합니다. 모든 게시물 표시
레이블이 Option인 게시물을 표시합니다. 모든 게시물 표시

2015-09-23

Null Object pattern - null 사용하지 않기

 C#, Java 등 현대의 많은 언어는 객체를 레퍼런스로 다룬다. 그리고 모든 레퍼런스는 nullptr이 될 수 있는데, 이 nullptr은 컴파일 타임에 검사할 수 없는 런타임 에러인 NullPointerException을 일으킨다. 그래서 nullptr은 최대한 조심해서 사용해야 하며, nullptr이 될 수 있는 변수는 사용하기 전에 반드시 nullptr인지 검사해야 한다. 하지만 변수를 사용하기 전에 매번 null check를 하는 코드는 예쁘지 못하고 실수하기 쉽다. 이런 문제를 해결하기 위해서 나온 것이 null object pattern이다.

 하지만 null object pattern은 그리 널리 사랑받는 패턴은 아니었다. 사용하기 불편한 여러 가지 부분이 있어 오히려 안티 패턴으로 불리기도 했다. 대체재가 없어서 꼭 필요한 경우에만 어쩔 수 없이 사용하는 패턴이었다.
 하지만 이제는 그마저도 사용할 필요가 없다. null object를 사용할 부분은 전부 Option 모나드로 대체할 수 있다. Option 모나드를 사용하는 것이 훨씬 사용하기 쉽고, 문제도 적다. 그래서 필자는 null object pattern을 사용해야 할 곳은 대신 Option 모나드를 사용하는 것을 추천한다.

 그런데도 null object pattern을 설명하는 이유는 어쨌든 null object pattern이 nullptr이 가지는 문제를 해결하고자 나온 패턴으로 과거 여러 프로젝트에서 쓰였기 때문에, 알고 있는 것이 기존의 코드를 읽는 데 도움이 되기 때문이다. 결코, 사용을 권장하기 위해서는 아니다.

 null object pattern은 아무것도 안 하는 객체(null object)를 제공함으로써 NullPointerException을 피하는 패턴이다. 조금 더 구체적으로는 nullptr를 써야 하는 클래스의 부모 되는 interface를 구현하는 null class를 만들고, 그 클래스의 객체를 nullptr 대신해서 사용하는 것이다. 이렇게 함으로써 verbose 하게 null check 하는 코드를 없애고, 실수로 null check를 하지 않는 경우를 예방할 수 있다.

 null object pattern은 사용하는데 주의해야 할 것이 있다. null object는 아무것도 하지 않는 클래스다. 즉, 아무런 내부 상태도 가지지 않는다. 따라서 이는 empty class가 되고 null object를 공짜로 만들 수 있을 것 같다. 하지만 아쉽게도 C#, C++, Java, C 등 대부분 언어에서 빈 클래스의 객체는 크기가 0이 아니다.1) 따라서 null object를 여러 개 만드는 것은 쓸데없이 메모리를 사용할 뿐이다. 그래서 보통 null object는 전역에 하나의 변수만을 두고 이 변수를 사용하도록 한다. null object를 싱글톤으로 만들어야 한다고 하는 사람도 있지만, 이는 명백히 오버킬이다. null object는 전역 변수로 선언하면 충분하다.

 그리고 null object가 올 수 있는 곳과 null object가 오지 않을 곳을 구분하지 못한다. 만약 Option monad를 썼다면, Option monad는 자신이 가지는 T 타입에 제약이 없다. 값이 없을 수 있는 곳에서만 Option[T] 타입을 사용하면 된다. Option[T]가 오는 곳은 값이 없을 수 있는 곳이고, 무조건 값이 있게 하려면 그냥 T 타입이 오도록 하면 된다. 이는 컴파일 타임에 검증이 된다. 하지만 null object는 어디에서는 null object를 올 수 있고, 어디에서는 null object가 올 수 없다는 제약을 타입에 줄 수 없다. 따라서 컴파일 타임에 에러를 검증하지 못하고, 무조건 assert를 통해서 검출해야 한다.

 그보다 더 찾기 어려운 문제는 null object pattern이 문제를 숨김으로써 버그를 늦게 발견하게 될 수 있다는 것이다. null object는 아무것도 하지 않는다. 그래서 실제로 존재해야 하는 객체가 null object가 되었어도 프로그램은 죽지 않는다. 문제는 지금 당장 죽지 않기 때문에 나중에 다른 어디선가 문제를 발생시킬 수도 있다는 것이다. 이런 단점 때문에 앞에서 말했듯이 null object pattern이 안티 패턴이라고 불리기도 하는 것이다.

2015-09-24 00:03 1) 링크 추가

2015-07-15

[Monad] 사용 예제 - Option : 존재하지 않음을 표현하기

 모나드는 많은 방식으로 사용되지만, 그중에서 Option 타입부터 설명하도록 하겠다. 그 이유는 Option 타입이 가장 기본적인 모나드이고, 가장 많이 사용되는 모나드이기 때문이다.
 Option 타입은 Haskell 및 몇몇 언어에서는 Maybe 모나드로 불리고, 언어 대부분에서는 Option 타입이라고 불린다. 이 글에서는 그냥 많이 사용되는 Option 타입이라는 이름을 쓰도록 하겠다.

 Option 타입이 해결하고자 하는 문제는 값이 존재하지 않음을 런타임 에러가 발생할 가능성 없이 표현하는 것이다. C++, C#, Java 등 기존의 많은 언어는 값이 존재하지 않음을 표현하기 위해서, null point를 사용하였다. 그리고 이 null point 문제는 컴파일 타임에 잡을 수 없는 NullPointerException을 발생시키기 때문에 조심해서 사용해야 했다.
 이런 문제를 해결하기 위해 아무런 동작을 하지 않는 객체를 만드는 Null Object pattern 같은 디자인 패턴을 이용하거나, null check를 한 겹 감싼 클래스를 만들거나 해서 문제를 최소화하고 있으나, 여전히 문제를 완벽하게 해결할 수는 없었다. Option 타입은 이에 대한 완벽한 해결책을 제공한다.

 Option 타입은 하나의 타입 파라미터를 받아, 그 타입의 값을 가지고 있을 수도 있고, 없을 수도 있다. Int 타입을 타입 파라미터로 받았다면, 타입은 Option[Int]가 되며, String 타입을 타입 파라미터로 받았다면, Option[String]이 된다. 즉, T 타입을 타입 파라미터로 받은 Option은 Option[T]가 된다. 이를 간단히 표현하기 위해서 T?같은 방식으로 표현하기도 한다.

 Option[T] 타입의 값은 T 타입의 값을 가지고 있을 수도 있고, 아무런 값이 없을 수도 있다. 이렇게 말하면 단순한 nullable과 다를 게 없어 보인다. 하지만 Option은 두 상태를 다른 타입으로 분리함으로써 nullable보다 안전한 방법을 제공한다.

 Option 타입은 두 타입의 sum type이다. 하나는 값이 존재하지 않음을 나타내는 None이라는 타입이고, 다른 하나는 무언가 값이 있음을 나타내는 Some이라는 타입이다. sum type을 지원하는 F#, rust, Haskell 같은 언어에서는 이를 sum type으로 표현하고, Scala나 전통적인 객체지향 언어에서는 Option이라는 interface의 구현체로 Some과 None이 있는 것으로 표현한다.
sum type을 표현할 방법이 없어 Scala처럼 상속관계로 표현하였다.

 None은 아무런 값도 가지고 있지 않음을 나타내는 타입이다. None 타입의 값에 bind operator를 호출해도 아무 일도 일어나지 않는다. 인자로 넘겨진 함수는 실행되지 않고, bind operator의 결과는 언제나 None이다.

 Some 타입은 무언가 값을 가지고 있음을 나타내는 타입이다. 어떤 타입의 값을 가졌는지 나타내기 위해 타입 파라미터를 받는다. T 타입의 값을 가지고 있는 Some 타입은 Some[T]라고 표현한다. Some[T] 타입은 반드시 T 타입의 값을 들고 있어야 한다. Some 타입이면서 내부적으로 값을 들고 있지 않는 상황은 올 수 없다.1) Some타입은 반드시 값을 가지고 있기 때문에 bind operator는 언제나 NullPointerException 없이 원하는대로 실행된다.

1) Scala는 언어적으로 이것을 금지하지 않는다. 하지만 Some이 null을 가지고 있도록 작성한 코드는 잘못된 코드이다. 이런 경우는 반드시 None을 써야 한다.