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

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) 링크 추가

2014-12-19

Facade pattern - 간단한 인터페이스 만들기

 퍼사드 패턴이란 복잡한 서브 시스템에 하나의 레이어를 씌워서 복잡한 시스템을 사용하기 쉽게 만드는 방식을 말한다. 퍼사드 패턴은 레이어를 하나 추가하지만, 이 레이어는 추상화가 목적이 아니다. 어디까지나 사용하기 쉽게 만드는 것이 목적이다. 그래서 보통 퍼사드의 인터페이스는 매우 간단한 모습을 가진다.
http://en.wikipedia.org/wiki/File:Example_of_Facade_design_pattern_in_UML.png

 퍼사드 패턴의 대표적인 사용처는 로그 API다. 로그를 그대로 STDOUT에 출력할 수 도 있고 어딘가에 저장할 수도 있다. 저장하는 것도 로컬에 있는 파일에 기록을 남길 수도 있고, 데이터 베이스에 파일을 저장할 수도 있고, 그 이외의 방식으로 로그를 저장할 수도 있다. 즉, 만약 파일에 저장한다면 적절하게 파일 포인터를 관리해야 하고, 데이터 베이스에 저장한다면 그 connection을 적절하게 관리해야한다. 게다가 로그를 저장하는 것도 IO로 인한 병목을 피하기 위해 일정 시간 혹은 일정 갯수의 로그를 모았다가 저장할 수 도 있다. 만약에 모았다가 저장한다면, 저장할 조건을 만족시키지 않았더라도 프로세스가 종료되기전에 저장을 완료해야 한다.
 이런 조건들을 사용자가 일일히 챙겨가며 로그를 저장하는 것은 귀찮은 일이다. 그래서 Logback같은 API에서는 back-end가 어떻게되든 상관 없이 필요한 back-end를 사용하도록 초기화해주면, 그 뒤로는 같은 인터페이스로 기록을 남길 수 있게 해준다.

 전에 소개한 적 있던 Pluggable한 log aggregator인 fluentd의 경우도 퍼사드 패턴을 사용한다. Input plug-in, Buffer plug-in, Output plug-in은 모두 간단한 인터페이스만을 요구한다. 덕분에 fluentd의 engine은 간단한 코드를 유지할 수 있고, 간단하게 원하는 back-end를 선택해서 동작을 바꿀 수도 있다.


본 글은 CC BY-SA 3.0 라이센스를 따릅니다.

2014-06-10

[Design Pattern] Loan pattern - resource를 안전하게 사용하기

 언젠가 썼던 글에서도 설명했듯이 C++에서는 RAII를 이용하여 Resource의 안전한 해제를 보장하는 것을 넘어 control flow를 제어하는 역할까지 해준다.
 하지만 Garbage Collection을 사용하는 C#이나 Java 같은 언어에서는 언제 메모리가 해제될지 모르기 때문에 RAII pattern을 사용할 수 없다. 그래서 코드의 실행을 보장하기 위하여 finally 구문이 생기게 된 것이다.

 try finally를 사용하는 일반적인 방법은 아래와 같다.

 exception이 발생할 수 있으면 try 구문으로 감싸고 반드시 실행시켜야 하는 코드를 finally에 두는 것이다.

 하지만 위의 코드는 딱 보기에도 재사용성이 떨어진다.
 다른 동작을 하기 위해서는 언제나 try / catch를 써야 해서 boilerplate한 코드가 반복되기도 한다.
 이를 해결하는 방법은 없을까?

 Scala에서는 이를 해결하기 위하여 resource를 빌려주는 방식을 자주 이용한다.
 resource의 management를 하는 함수(lender)가 있고, resource를 사용하는 함수(lendee)에게 빌려주어 잠시 사용하게 해주는 것이다.
 이를 이용하여 API의 encapsulation과 reusability를 올릴 수 있다.
 우선은 다음 예제를 보자.

 위의 예시에서는 executeSql이라는 함수가 connection string과 Statement를 인자로 받는 Function을 인자로 받는다(말은 복잡한데 실제로 복잡한건 아닌데....... 말로 설명하려니 복잡해졌다.).
 첫 번째 인자로 받은 connection string을 이용하여 Statement라는 resource를 만들어 관리하게 된다. 즉, executeSql이 lender가 되는 것이다.
 그리고 두 번째 인자인 Statement를 인자로 받는 Function을 landee로 삼아 자신이 만든 Statement를 빌려주어 원하는 작업을 수행하게 한다.

 이런 pattern을 resource를 관리하는 lender와 빌려서 사용하는 lendee로 나뉘기 때문에 lender-lendee pattern으로 부르는 사람도 있지만, 보통은 loan pattern이라고 부른다.

 loan pattern은 resource의 관리/사용을 구분하였기 때문에 resource를 안전하게 사용하는 것을 보장해준다.
 또한, 변하는 부분인 사용에 해당하는 부분만을 재정의하면 되기 때문에 손쉽게 코드를 재사용 가능하게 만들어주고, 함수를 library로 제공하게 되면 user들에게 resource의 할당과 해제를 숨길 수 있어서 encapsulation이라는 측면에서도 매우 좋다.
 실제로 scala library의 많은 부분은 loan pattern을 사용하고 있다.