2015-11-17

[C++] enum class - 안전하고 쓰기 쉬운 enum

 C++ 03까지의 enum은 여러 가지 문제를 가지고 있었다. 그래서 그 문제들을 해결하기 위해 C++ 11은 enum class라는 것을 새로 만들었다. 이제부터 기존의 enum에 어떤 문제가 있었고, 이것을 enum class에서 어떻게 해결하였는지 살펴볼 것이다.

 우선 기존의 enum은 전방 선언할 수 없었다. 그 이유는 enumerator에 어떤 값이 들어있을지 알 수 없으면 그 크기를 정할 수 없기 때문이다. 하지만 enum class는 underlying type을 명시하지 않으면 int 타입과 같은 크기의 변수로 선언되고, int 값 안에 들어가지 못할 값을 지정하면 컴파일 에러를 발생시킨다.
 만약 int를 벗어난 범위의 값을 사용하고 싶다면, underlying type을 명시해주어야 한다.


 기존 enum의 또 다른 문제는 enumerator의 이름의 범위가 한정되지 않는다는 것이다. 예를 들어 아래와 같은 코드를 보자.

 IO 함수의 결과와 Parse 함수의 결과를 enum으로 표현해 보았다. 하지만 이 코드는 컴파일되지 않는다. IOResultError, OkParseResultError, Ok와 겹치기 때문이다. 이를 해결하기 위해서는 다음과 같이 enumerator의 이름을 다르게 하거나

 아래와 같이 namespace를 이용해야 했다.

   하지만 enum class는 enumerator의 이름이 enum class 안으로 한정되기 때문에 이런 복잡한 과정이 필요 없이 그저 enum class를 선언하여 사용하면 된다.


 무엇보다 기존 enum의 가장 큰 문제는 정수형 변수로 암시적으로 변환되는 약 타입(weak type) 변수라는 것이다. 하지만 enum class는 정수형 변수로 암시적 변환이 되지 않는다. enum class를 정수형 변수처럼 사용하려고 하면 컴파일 에러를 발생시킨다. 만약 정수형 변수로 사용하고 싶으면 static_cast를 이용해 명시적으로 캐스팅해서 사용해야 한다.


 위에서 설명한 대로 기존의 enum은 전방 선언할 수 없고, enumerator 이름의 범위가 한정되지 않고, 정수형 변수로 암시적으로 변환되지도 않는다. 사실 enum의 사용법을 생각해보면 enum class가 올바른 방식이다. 하지만 C++ 11은 backward compatibility를 위해서 기존의 enum을 그대로 두고, enum class를 새로 추가하는 것을 선택했다. 이제는 compatibility를 위해서가 아니면 enum의 존재는 잊고 대신에 enum class를 사용하는 것이 좋다.

2015-09-24

빈 객체 크기는?

 위와 같은 클래스를 생각해 보자. 보통 empty 클래스라고 부르는 이 클래스는 아무런 내부 변수를 가지고 있지 않다. 그렇다면 이 empty 클래스의 크기는 얼마일까?

 언뜻 생각해보면, 아무런 멤버 변수가 없으니 그 크기가 0일 것 같다. 하지만 Java, C#, C(이 경우는 struct), C++ 어떤 언어에서도 0이 나오지 않는다. 이는 두 다른 객체가 같은 주소를 가르치는 일이 없도록 하기 위한 것이다.

 empty 클래스는 보통 32 bit 환경에서는 1 바이트 크기를 가지고, 64 bit 환경에서는 2 바이트 크기를 가진다. 하지만 정확히 어떤 값이 나오는지는 알 수 없다. 스펙에 따르면 크기가 0이 되지 않기만 하면 된다. 정확한 크기는 구현체에 따라서 다르다.

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-09-20

confirm password 필드는 더 이상 필요 없는가

 비밀번호는 보안상의 문제로 ""으로 표시되기 때문에 오타를 냈어도 확인할 수 없고 잘못 입력하면 앞으로 로그인할 수도 수정할 수도 없어 계정을 그대로 버리는 문제를 발생시킨다. 따라서 비밀번호의 오타는 다른 정보들과는 다르게 큰 문제가 된다.
 그래서 일반적으로 회원가입을 할 때, 비밀번호를 두 번 입력하도록 한다. 하지만 이런 방식이 UX를 크게 저하한다면서 다른 방식을 사용해야 한다고 주장하는 글이 있었다. 이 글에서는 비밀번호를 두 번 입력하는 대신 입력한 비밀번호를 읽을 수 있게 보여주는 토글 버튼이 있어야 한다고 주장한다.

 언뜻 들으면 그럴싸해 보이지만 결론부터 말하면 절대 좋은 방식이 아니다. 최소한 웹 환경에서는 절대 해서는 안 되는 방식이다. 비밀번호를 보여줄 수 있게 만드는 방법은 다음과 같은 문제가 몇 가지 있다.

 우선 브라우저에서 지원하지 않는다. 현재의 웹 스펙에 password input을 보여주는 방법은 없다. 따라서 text input을 이용해야 한다. 문제는 브라우저, 최소한 제대로 된 브라우저(심지어 I.E조차도)는 text input과 password input을 완전히 별도로 처리한다는 것이다. 이 둘의 차이는 단순히 내용이 눈에 보이는가 아니면 ""으로 보이는가의 차이가 아니다.
 일단 당장 눈앞의 문제로 text input은 password input과 다르게 브라우저가 캐싱하고 자동 완성 한다는 것이나, 브라우저의 비밀번호 저장 기능을 생각해볼 수 있다. 캐시와 관련한 것은 autocomplete를 이용해서 조정할 수 있지만, 비밀번호 저장 기능은 password input만을 저장하기 때문에, text input을 이용한 상태에서는 어떻게 할 방법이 없다.
 사실 비밀번호 저장기능이 중요한 기능이 아니기는 하다. 하지만 이 기능은 포기한다고 해도 여전히 브라우저가 password input과 text input 전혀 별개의 것으로 처리한다는 것은 문제다. 브라우저 개발자들은 비밀번호를 password input을 이용할 것을 기대하지 text input을 사용할 것으로 생각하지 않는다. 최소한 HTML 6가 나와 스펙이 변하거나, 많은 사람이 text input을 이용해서 비밀번호를 저장할 때까지는 그럴 것이다.

 게다가 비밀번호를 눈으로 확인하게 하는 시스템은 기존의 방식보다 시간이 오래 걸린다. 단순히 절차상으로 걸리는 시간이 길어지는 것뿐 아니라, 내 비밀번호와 화면에 출력되는 시간이 길어진다.
 이 시간은 보안에도 큰 문제가 된다. 내가 눈으로 내 비밀번호를 확인할 수 있다는 것은 내 등 뒤의 사람도 그 비밀번호를 볼 수 있다는 것이다. 설령 시스템이 아무리 안전하게 구성되어 있다고 해도 정작 다른 경로로 비밀번호가 노출될 가능성이 있다면, 이 시스템은 안전하지 않은 시스템이다. 즉, 비밀번호를 눈으로 보고 확인하게 하는 방식은 보안적으로 안전하지 않은 방식이다.

 그다음 문제는 강력한 비밀번호일수록 사람의 눈으로 읽고 확인하는 것이 좋은 확인 수단이 아니라는 것이다. 짧고 간단한 비밀번호라면 금방 확인할 수 있다. 하지만 특수기호와 영대소문자를 섞어가면서 20글자의 비밀번호를 만들었다면? 그 비밀번호를 눈으로 보고 자신이 의도한 대로 친 것이라고 확신할 수 있을까?
 기존의 비밀번호를 확인하는 방식으로 잘못된 비밀번호를 확인 못 하는 경우도 있다. Caps lock 키가 눌려서 대소문자가 바뀌어서 입력된 경우는 기존의 방식으로 잘못된 비밀번호를 확인할 수 없다. 하지만 이는 caps lock 키가 눌렸는지를 확인해서 해결해야 할 문제이지 위와 같은 문제를 감수하고 비밀번호를 보여주는 옵션을 추가해서 해결할 문제는 아니다.

 즉, 비밀번호를 보여주는 방식은 기존의 방식에 비해서 제대로 된 비밀번호를 적었을 것을 보장하지도 않고, 안전하지도 않고, 플랫폼에서 지원하지도 않는다. 물론 이것은 웹에서의 이야기이고 위의 문제가 발생하지 않는 환경이라면 딱히 상관없다. 예를 들면, 데스크탑 OS나 서버 프로그램의 설치 화면이라면 그래도 상관없다고 생각한다. 하지만 이것은 오히려 특수한 경우이고 일반적으로 특히나 웹 환경에서 쓸만한 이야기는 아니라고 생각한다.

 하지만 비밀번호를 두 번 타이핑하는 것이 사용자에게 안 좋은 UX를 제공할 수 있다는 의견에는 동의한다. 그렇지만 이를 해결하는 방법이 비밀번호를 보여주는 것은 아니다. 최소한 웹에서 쓸만한 방법은 아니다.
 게다가 사실 비밀번호를 다시 타이핑하지 않고 가입하는 좋은 방법은 이미 나와있다. 이미 github이나 facebook이나 twitter 등에서 사용하고 있다. 가입 시 이메일을 받고 이메일을 통해서 인증받도록 하는 것이다. 이렇게 가입을 받으면 비밀번호를 잘못 입력했더라도 이메일을 통해 바꾸도록 하면 되니 문제 없고, 이메일을 잘못 입력하였더라도 일정 기간 내로 인증을 받지 않은 회원가입은 무효화 하면 되니 잘못 타이핑할 걱정을 하지 않아도 된다.

2015-09-18

[ECMAScript6] 성공적인 Promise는 중첩되지 않는다.

 ES6 Promise에는 독특한 특징이 있는데, 지난번 글에서는 설명할 타이밍을 잡지 못해서 그냥 넘어갔었다. 이번에 그 특징에 관해 설명하도록 하겠다.

 전에 모나드에 관해서 설명하면서 모나드의 가장 기본적인 operator 중 하나인 bind operatorM[T] 타입의 모나드가 T 타입의 인자를 받아서 M[U] 타입의 값을 리턴하는 함수를 인자로 받아서 M[U] 타입의 모나드로 타입을 진행시킨다1)고 하였다. 하지만 ES6 Promisethen 함수에 관해서 설명하면서 then 함수가 받는 콜백이 값을 리턴하면 resolved 된 Promise가 리턴되고, 값을 throw 하면 rejected 된 Promise가 리턴된다고 하였다. 즉, then 함수만으로는 모나드를 리턴하는 함수를 통해서 타입을 전진시키는 bind operator를 구현할 수 없으므로 완전한 모나드를 구현하지 못한다. 그렇다면 ES6의 Promise는 어떻게 Promise를 전진시킬까?

 간단하다. 그냥 then 함수가 인자로 받는 콜백은 Promise를 리턴해도 된다.

 사실 Promise가 모나드라는 것을 생각하면, 이쪽이 올바른 사용 법이다. 하지만 ES6뿐 아니라 다른 모나드 구현체에서도 bind operator뿐 아니라, 모나드가 아닌 값을 리턴하는 함수. 즉, (M[T], T => U) => M[U]에 해당하는 함수도 구현한다. 이는 사실 내부적으로 unit operator와 bind operator를 호출하기 때문에 굳이 필요한 함수는 아니다. 그러함에도 이 함수가 존재하는 이유는 실제로 이 구현을 사용하는 경우가 일반적인 bind operator를 사용하는 경우보다 많아서 사용자의 편의를 위해서 제공되는 것일 뿐이다.
 그래도 보통은 둘을 같은 이름의 함수로 구현하지는 않고, 다른 이름의 함수로 구현한다. 그렇지만 ES6에서는 then 함수가 두 가지 일을 한다. 동적 타입 언어의 특징을 최대한 활용한 것이다.

 하지만 then 함수처럼 콜백 함수가 리턴하는 것이 Promise인지 아닌지에 따라서 동작이 달라지면 실수하기 쉽다. 이것을 ES6는 애초에 Promise가 중첩되지 못하도록 함으로써 동작에 일관성을 취했다. 즉, PromisePromise를 값으로 지니지 못한다.

 Promise가 중첩되지 못하게 하는 역할은 Promise.resolve 함수가 한다. resolve 함수의 스펙을 보면 resolve 함수는 받은 인자가 Promise이고 같은 생성자를 가진다면, 받은 인자 그 자체를 리턴한다고 되어 있다.2) then 함수는 내부적으로 실행된 결괏값을 이용하여 resolve 함수를 호출한 뒤 그 결괏값인 Promise를 돌려준다.


 resolve 함수는 then 함수뿐이 아니라 Promise.all 함수에서도 사용된다. 지난번에 all 함수를 설명할 때는 아직 resolve 함수에 대해서 제대로 설명하지 않아서, Promise의 iterable을 받아 하나의 Promise로 zip 한다고 하였다. 하지만 all 함수도 내부적으로 아이템들을 전부 resolve 함수에 넘기기 때문에, iterable에 Promise가 아닌 다른 값을 넣어서 호출할 수 있다.


1) 말로 풀어쓰려니 복잡한데, 기호로 표현하면 아래와 같다.
(M[T], T => M[U]) => M[U]
2) 스펙 문서 3번 스텝

2015-09-11

[ECMAScript 6] Promise - 비동기 코드 작성하기

 모든 언어가 마찬가지겠지만, 기존의 JavaScript에서는 비동기적 코드를 작성하고 관리하는 것은 크게 어려운 일이었다. node.js에서는 콜백을 이용하는 방식을 사용했지만, 이는 콜백 헬이라는 새로운 문제를 만들어냈다. 이를 해결하기 위해 step이나 async 같은 다양한 라이브러리가 나왔지만 이런 라이브러리로도 콜백 방식이 가지는 복잡도는 해결하지 못했고, 여전히 비동기 코드를 작성하는 것은 어려운 문제였다. 그래서 ECMAScript 6에서는 비동기 코드를 쉽게 작성할 수 있도록 Promise를 표준 라이브러리에 도입하였다. Promise는 그 이름에서도 알 수 있듯이 비동기적인 코드를 작성할 수 있도록 도와주는 promise monad의 일종이다.

 Promise는 기본적으로 생성자를 통해서 만들어진다.

 이렇게 생성된 Promise는 pending state가 된다. pending state는 아무 값도 가지지 않은 상태다. pending인 Promise는 후에 resolved state(혹은 fulfill state)가 되거나 rejected state가 될 수 있지만, 이 상태로는 아무것도 할 수 없다.

 Promise의 상태를 바꾸기 위해서는 콜백 함수를 이용해야 한다. Promise의 생성자는 한 개의 콜백 함수를 받는다. 이 콜백은 executor라고 불리는데, Promise 객체를 생성하는 중에 호출된다. executor가 호출될 때는 2개의 함수가 인자로 넘어간다. 첫 번째는 resolver라고 불리고, 두 번째는 rejecter라고 불린다. pending state인 Promiseresolver가 호출되면 이 Promise는 resolved state가 되고, resolver의 인자를 값으로 지닌다. 반대로 pending state인 Promiserejecter이 호출되었다면 이 Promise는 rejected state가 되고, rejecter의 인자를 Promise가 reject 된 이유로 가지게 된다. 중요한 점은 pending state인 Promise만 resolve 하거나 reject 할 수 있고, 이미 resolved 되었거나 rejected 된 Promise는 다시 resolve 하거나 reject할 수 없다.

 혹은 함수를 실행시키지 않고, 이미 결정된 값을 가지고 콜백 없이 Promise를 만드는 방법도 있다. resolvereject 함수를 이용하는 것이다. resolve 함수를 이용하면 resolved 된 Promise가 만들어지고, reject 함수를 이용하면 reject 된 Promise가 만들어진다.

 이렇게 생성된 Promise는 2개의 멤버 함수를 가진다. 우선 기본적인 멤버 함수는 then()이라는 함수다. then()은 promise monad의 map과 map_error operation을 합쳐놓은 함수이다. then()은 2개의 콜백을 받는다.


 첫 번째 받은 콜백은 Promise가 resolved일 때 호출되는 함수이고, 두 번째 콜백은 Promise가 rejected일 때 호출되는 함수이다.
 정확히는 상태가 변하면 바로 호출되는 것은 아니다. 만약 then() 함수가 호출될 때 Promise가 pending 되어 있었다면 상태가 변할 때 job queue에 추가되고, 상태가 resolved나 rejected였다면 그 즉시 job queue에 추가되었다가, job queue에서 다음 일을 꺼내질 때 실행된다.
 이때 콜백 함수는 값을 하나 인자로 받는데, 첫 번째 콜백의 경우 Promise가 resolve 되었을 때의 값을, 인자로 받고, 두 번째 콜백은 Promise가 reject 된 이유를 받는다.


 then method는 Promise를 리턴하기 때문에 체이닝 할 수도 있다.
 이때 반환되는 Promise가 가지는 값은 실행되는 콜백의 결과에 따라 달라진다. 만약 콜백 함수가 정상적으로 실행되고 값을 반환했다면, 리턴되는 Promise는 함수의 결괏값을 resolve 한 값이 리턴되는 Promise의 값이 된다. 반대로 만약 콜백이 에러를 throw 했으면 throw 한 값을 reason으로 하는 rejected state인 Promise가 리턴된다.


 하지만 then() 함수만 있으면 rejected 된 Promise에 대해서만 값을 진행시키고 싶을 때 약간 불편하다. 그래서 catch 함수가 존재한다. Promise.prototype.catch(onRejected) method는 Promise.prototype.then(undefined, onRejected)와 완전히 같은 동작을 한다. 따라서 아래의 코드의 promise1promise2는 같은 의미의 Promise가 된다.


 ECMAScript 6의 Promise는 여러 개의 Promise를 join 할 수 있는 Promise.all이라는 함수도 제공한다. all() 함수는 인자로 iterable 한 ArraySet 같은 것을 받는다. 그리고 받은 iterable의 모든 값이 resolved 되면, 그 값들이 resolve 된 값의 배열을 리턴한다. 이를 통해서 여러 개의 독립적으로 돌아가는 비동기적인 코드의 실행 결과를 하나로 모을 수 있다.


 혹은 Promise.race() 함수를 이용해서 여러 Promise 중에서 먼저 완료된 Promise를 얻어올 수도 있다.

 사실 기존에도 JavaScript를 위해 Promise를 제공하는 라이브러리는 많이 있었다. node.js가 나오기 전에 유행했던 jQuerydeferred object도 사실상 Promise monad의 일종이었고, node.js나 브라우저 양쪽에서 돌아가도록 pure JavaScript로 작성된 Qbluebird 같은 라이브러리도 존재했다. 하지만 이는 다들 독립적인 동기로 작성된 것으로 서로 간에 API가 호환되지 않아 사용법이 전부 달랐다. 하지만 이제 표준 라이브러리에 Promise가 들어왔으므로 그냥 표준 라이브러리에서 제공하는 Promise를 사용하면 된다.

2015-09-08

[Monad] 사용 예제 - Promise : 비동기 코드 작성하기

 프로그래밍할 때 가장 어렵고 복잡한 일 중 하나가 비동기적인 코드를 안전하고, 읽기 쉽게 작성하는 것이다. Promise는 이에 대해서 간단한 해결책을 제시한다.

 Promise는 코드가 성공적으로 실행되었을 때의 값을 가지고 있거나, 코드가 실패했을 때 실패한 이유를 가지고 있다. 그래서 보통 Promise[T, E]로 표현된다. 이는 기본적으로 Try와 비슷하다. Try와 차이는 Promise는 그 객체가 생성되었을 때, 아직 연산이 끝났는지 알 수 없다. 코드가 비동기적으로 실행되기 때문이다.

 코드가 비동기적으로 실행되기 때문에 Promise에 bind operator를 통해서 타입을 진행시키는 일은 기본적으로 일을 예약하는 것이다. 이 일은 Promise가 완료된 뒤 언젠가는 실행이 되지만, 언제 실행될지는 모른다. 이미 완료된 Promise에 bind 한 콜백 함수가 언제 실행되는지도 모른다. 물론 실질적으로는 구현체에 따라서 언제 콜백 함수가 실행되는지 결정되어 있지만, 언제 실행될지 모른다고 생각하고 사용하는 것이 좋다. 아니 옳다.

 PromiseOption, Try와 함께 가장 널리 쓰이는 모나드이다. 하지만 다른 두 모나드와는 다르게 구현체마다 인터페이스나 사용법이 다르고 그 특성도 다르다. 코드를 비동기적으로 실행시키는 것은 사용하는 언어나 플랫폼에 크게 의존하기 때문이다. 하지만 Promise가 아직 완료되었는지 알 수 없는 일을 한 번 감싼 타입이라는 것만 잊지 않으면, 어떤 구현체라도 어떻게 사용해야 하는지 쉽게 이해할 수 있다.

 어떤 경우에는 Future라고 불리기도 하는데, 기본적으로 이 둘은 같은 일을 하기 위한 것이니 Promise에 대해서만 이해해도 딱히 문제없다. 굳이 차이를 두자면 Future는 이미 생성된 모나드를 완료시키지 못하는 read-only Promise라는 정도의 차이가 있을 뿐이다.

2015-08-26

[ECMAScript 6] Symbol - 7번째 primitive type

  지금까지 자바스크립트에는 number, boolean, string, null, undefined, object의 6가지 타입밖에 없었다. 그래서 C의 enum 같은 타입이 필요하거나, 일종의 태깅 같은 것을 위해 고유한 값이 필요했을 경우 보통 number나 string 타입을 이용했다.

 하지만 ECMAScript 6에서는 이제 number나 string을 이용할 필요가 없다. ECMAScript 6에서는 새로운 타입인 Symbol 타입이 추가되었기 때문이다.

 Symbol 타입의 값은 Symbol 함수를 통해서만 생성할 수 있고, new를 통해서 만들 수 없다.

 Symbol 함수는 인자로 description을 받을 수도 있고, 아무 인자도 받지 않을 수도 있다. 이 인자는 실제로 생성되는 Symbol에 영향을 주지 않는다. 로깅 등을 위해서 toString 함수를 이용해 string으로 변환할 때, 반영되지만 이는 디버깅을 위해서고, 일반적으로 이 description을 이용할 일은 없다. 같은 description을 이용해 생성한 Symbol도 실제로는 다른 값을 가진다.


 이는 Symbol 타입이 unique함을 보장하기 때문이다. Symbol 타입은 immutability와 unique 함이 보장된다. number나 string도 immutability는 보장된다. 하지만 unique 함은 보장되지 않는다. 이것이 Symbol 타입과 number/string 타입과의 차이점이다.

 같은 Symbol을 가지고 오기 위해서는 생성한 Symbol을 전역 변수로 등록시키고 있어야 한다. 이것을 해주는 게 for 함수이다.

 Symbol.for() 함수는 key를 인자로 받는다. 이전에 같은 key로 생성한 Symbol이 있으면 그 Symbol을 돌려주고, 처음 받은 key면 새로운 Symbol을 생성하여, 저장한 뒤 돌려준다.

 주의해야 할 것은 Symbol.for(key) 함수는 이미 등록된 Symbol이 있을 경우만 그것을 가지고 올 수 있다는 것이다. 따라서 Symbol(description)을 이용해서 생성한 Symbol은 Symbol.for(key)를 이용해서 가지고 올 수 없다.

 이런 특징을 이용해서 아래와 같이 object의 private property를 만드는 것도 가능하다.


2015-08-25

[ECMAScript 6] block 안에서 함수 만들기

 JavaScript 함수 선언의 가장 큰 특징은 함수의 선언 위치에 상관없이 언제나 코드의 가장 위에서 함수를 선언한 것처럼 코드가 실행된다는 것이다. 따라서 아래와 같은 코드는

 사실은 아래와 같은 코드라고 보아도 된다.

 이를 function hoisting이라고 한다. 이 덕분에 함수 선언문보다 앞에서 함수를 사용할 수 있다. 하지만 함수 선언은 언제나 스코프의 가장 윗부분으로 hoisting 된다. 따라서 함수 안에서 선언된 함수는 함수 내에서 언제나 같은 함수를 의미했고, 특정 block 안에서는 다른 함수를 의미하도록 사용할 수 없었다.

 하지만 ECMAScript 6에서는 block 단위의 함수 선언을 허용한다.

 즉, 위와 같이 if block 안에서만 다른 값을 의미하도록 하는 것이 가능하다.

 하지만 아쉽게도 이는 아직 대부분 브라우저나 node.js에서는 구현되지 않았다. 따라서 블록 단위 함수 선언을 사용하려면 babel.js를 사용해야 한다.

2015-08-22

[ECMAScript 6] 함수 이름 가져오기

 자바스크립트는 두 가지 방식으로 함수를 선언할 수 있다.
 평범하게 방법은 함수를 선언하여 사용할 수도 있고, 익명함수를 만들어 사용할 수도 있다.

 ECMAScript 5까지는 어떻게 만들어지든 둘 사이에는 차이가 없었다. 만들 수 있는 위치나, 함수의 선언 및 할당이 실제로 이뤄지는 위치가 다르기는 하지만, 어쨌든 만들어지고 난 다음에 둘은 아무런 차이가 없었다.

 위와 같은 코드는 사실 아래와 같은 코드에 syntax sugar일 뿐이다.


 하지만 ECMAScript 6 이후로 둘은 name property라는 다른 점을 가진다. 첫 번째 방식으로 만든 함수는 named function이라고 불리며 name이라는 프로퍼티를 가진다. 반면에 두 번째 방식으로 만들어진 함수는 anonymous function이라고 불리며 길이가 0인 문자열("")을 name property로 가진다.

2015-08-11

[ECMAScript 6] fat arrow function

 fat arrow function(=>)는 ECMAScript 6에 추가된 익명 함수를 생성하는 새로운 방법이다. 기존의 함수를 만드는 것보다 짧게 함수를 만들 수 있다.

이는 다른 함수에 콜백으로 함수를 넘겨야 하는 경우 요긴하게 사용된다.

 하지만 단순히 길이가 짧다는 것만이 arrow function의 장점이 아니다. 오히려 중요한 특색은 arrow function은 thislexical scope에서 찾는다는 것이다.

 JavaScript 함수의 가장 큰 특징 중 하나는 this가 dynamic binding 된다는 것이다. 보통은 큰 문제가 되지 않지만, 메소드를 콜백으로 넘겨주어야 하는 경우나 메소드를 변수로 받을 경우 원하는 대로 돌아가지 않았다.


 ECMAScript 5에서는 Function에 bind 메소드가 추가되어 원하는 오브젝트를 this로 바인드 한 함수를 만들 수 있지만, 매번 이런 작업을 하는 것은 귀찮은 일이다. 이제 ECMAScript 6에서는 이런 경우에 =>을 이용하여 this가 lexical binding 된 함수를 만들면 된다.

2015-08-08

[TypeScript] Type guard - sum type 분리하기

 Type guard는 다른 언어에서 보기 힘든 TypeScript만의 독특한 기능으로, 타입 인트로스펙션을 통해 분기한 블록 안에서 해당 변수의 타입을 한정시켜주는 기능을 말한다.

 TypeScript를 사용하다 보면, 하나의 변수가 2개 이상의 타입일 가능성이 있는 경우가 자주 생긴다. TypeScript의 본질이 JavaScript이고 이는 동적 타입 언어이기 때문일 것이다. 이를 위해서 TypeScritp는 any 타입을 이용하거나, 조금 더 안전한 사용을 위해서 유니언 타입을 이용한다. 하지만 유니언 타입의 값은 그 값이 될 수 있는 모든 타입이 공통으로 가지는 함수와 프로퍼티만 이용할 수 있고, 모든 타입이 들어갈 수 있는 함수에만 사용 사용할 수 있다.
 이런 불편함을 없애기 위해서 나온 기능이 type guard이다.

 위의 코드처럼 여러 개의 타입이 될 수 있는 값을 사용하기 전에 인트로스펙션을 이용해서 타입을 확인하고 값을 사용하는 것은 JavaScript에서 볼 수 있는 흔한 패턴이다. 이렇게 타입을 확인하고 나면, 확인한 블록 안에서 그 값은 해당하는 타입이 되는 것이 type guard이다.

 하지만 아직 모든 인트로스펙션에 대해서 type guard가 적용되는 것은 아니다. 현재 type guard가 적용되는 경우는 인트로스펙션이 조건문에 들어가는 if 블록과 그에 따라오는 else 블록뿐이다.
위의 예제처럼 if 블록이 반드시 return 하여 그다음은 else 블록과 다를 바 없는 경우에는 type guard가 적용되지 않는다.

 게다가 모든 인트로스펙션이 가능한 것도 아니고, instanceof를 사용하는 경우와 typeof의 결과가 'number', 'string', 'boolean'이 되는 경우뿐이다. 그래서 underscore 등을 이용해서 타입 체킹을 하는 경우나 인트로스펙션 부분을 함수로 뺀 경우는 type guard가 동작하지 않는다. 이는 TypeScript 1.6에 계획된 type guard function이 들어오면 해결될 문제지만, 아직은 어쩔 수 없다.

2015-07-28

[Monad] 사용 예제 - Try : 예외 처리하기

 현대의 대부분의 언어는 예외 처리를 위해서 try-catch 시스템을 사용한다. 예외가 발생할 수 있는 코드를 try 블록에 집어넣고, 예외를 throw하면 catch 블록에서 예외를 잡아서 처리하는 방식으로, 사실상 현대의 언어들이 예외를 처리하는 방식의 de facto라고 할 수 있다. 하지만 try-catch 시스템에는 여러 가지 문제가 있다.

 우선 다른 함수를 호출할 때, 어떤 예외가 발생할지 모른다. 그래서 Java 같은 언어는 함수의 시그니쳐에 발생 가능한 예외를 적는 checked exception이라는 개념을 만들었지만, RuntimeException은 어떤 예외가 발생할지 모른다거나, 모든 예외를 하나하나 등록하기 귀찮아서 그냥 Exception이 발생한다고 적거나 하는 이유로 그다지 쓸모없다는 인식이 강하고 C#을 비롯한 다른 언어들에서는 사용되지 않는다. 그저 API 문서에 함수가 어떤 예외를 발생시킬 수 있는지 적을 뿐이다.

 게다가 try-catch 시스템은 예외를 던지는 것이기 때문에 컨트롤 플로우가 뛰게 된다. 물론 현대 언어에서는 클로져나 람다 함수가 자주 사용되기 때문에 컨트롤 플로우가 직선적으로 흐르지 않는다. 하지만 try-catch 시스템은 도가 지나치다. 예외를 던지면, 예외를 잡을 때까지 컨트롤 플로우가 거슬러 올라간다. 그래서 try-catch에 의한 예외 시스템을 가독성이라는 측면에서 gotosetjmp/longjmp와 다를 게 없다고 비판하는 사람들도 있다.
 반면에 Try 모나드를 사용한 예외처리는 좀 더 예측할 수 있고 가독성 있는 코드를 작성할 수 있게 해준다.

 Try 모나드는 Option과 마찬가지로 두 모나드의 sum type이다. 하지만 하나의 타입 파라미터를 받는 Option과 다르게 타입 파라미터를 두 개 받는다. 이 두 타입은 각각 성공했을 때 결과 타입인 T와 에러가 발생했을 에러 타입인 E다. 그래서 Try 모나드는 Try[T, E]로 표현한다. Try 모나드가 가지는 타입은 각각 실행이 정상적으로 되어 T 타입의 값을 가지는 경우인 Ok[T]와 에러가 발생하여 E 타입의 에러가 난 경우인 Error[E]이다.

 Try는 두 개의 타입 파라미터를 가지고 있는 만큼 타입을 진행시키는 bind 함수도 두 개 있다.

 첫 번째 bind 함수는 Try[T, E] 타입을 Try[U, E]로 진행시킨다. 이 함수는 현재의 TryOk일 경우에만 실행된다. 현재의 값을 콜백 함수에 넣어 실행시키며, 콜백 함수의 결과가 이 bind 함수의 결과가 된다. 에러가 발생한 Try인 Error일 경우 콜백 함수를 실행시키지 않으며, 결괏값은 현재와 같은 Error 타입이 된다.

 두 번째 bind 함수 Try[T, E] 타입을 Try[T, F]로 진행시킨다. 이 함수는 위의 함수와 반대로 현재 값이 Ok일 경우 아무 일도 하지 않고, Ok인 값을 그대로 반환한다. 반면에 현재 TryError일 경우, 에러값을 콜백 함수에 넣어 그 결괏값을 반환한다.

 이처럼 Try 모나드는 정상적인 실행 결과를 가지거나 에러값을 가진다. 이를 서로 다른 bind 함수를 이용하여 진행시킨다. 따라서 컨트롤 플로우가 뛰지 않고 언제나 bind 함수에 넘긴 콜백을 실행시키는 것으로 진행된다. 또한, 에러가 타입으로 나오기 때문에 어떤 함수가 어떤 에러를 발생시킬지 쉽게 알 수 있고, 발생한 에러가 어떻게 처리되는지를 컴파일 타임에 알 수 있다.

2015-07-24

Phantom type - 구체화 되지 않는 타입 추가하기

 Phantom type은 받은 타입 파라미터 중에서 구조체의 선언에 기여하지 않는 타입 파라미터가 존재하는 타입을 말한다. 무언가 존재하는 것 같지만, 만져지는 실체가 없는 것이 유령 같다고 하여 phantom type이라고 불린다.

 내가 알기로는 Haskell에서 가장 먼저 도입된 것으로 알고 있다. Haskell이 phantom type을 사용하기 시작한 뒤로 파라메트릭 폴리몰피즘을 중시하며 강타입 타입 시스템을 가진 Scala나 Rust 같은 다른 언어에서도 사용된다.

 보통 phantom type을 사용하는 이유는 런타임 오버헤드 없이 컴파일 타임에 제약 조건을 추가하기 위해서다. phantom type에 사용된 타입 파라미터는 구조체의 값으로 사용되지 않기 때문에 구조체의 크기를 증가시키거나, 실행 시에 별도의 정보를 더 들고 다니지 않는다. 그저 컴파일 타임에 타입 체크하는 조건을 강화할 뿐이다.

이에 대한 좋은 예제가 rust by example에 있어서 가지고 왔다.1)

 위의 예제는 Length라는 구조체를 단위를 타입 파라미터로 받는 클래스로 선언하였다. 하지만 단위에 해당하는 UnitLength 구조체의 어떤 값으로도 사용되지 않는다. 따라서 Length 구조체의 크기는 f64의 크기와 같다.2) 하지만 Length<Mm>Length<Inch>와 다른 값이기 때문에 Length<Mm> 타입인 값과 Length<Inch> 타입인 두 값을 더할 수 없다.


1) 예제 코드는 Apache 2.0 라이센스로 배포되는 rust by example에 나오는 예제를 가지고 왔다.
2) rust는 구조체에 기여하지 않는 타입 파라미터를 만들 수 없어 PhantomData를 이용했다. PhantomData는 size가 0인 구조체이다.

2015-07-19

[ECMAScript 6] const - 상수 선언하기

 constlet과 같이 ECMAScript 6에 도입된 block 단위 상수 선언문이다.
 중복으로 선언할 수 없고, 선언 전에 사용할 수 없다는 것은 let과 같다. 거기에 const는 추가적인 제약이 더 붙는다.

 우선 const로 선언된 이름에는 값을 재할당할 수 없다. 이는 문법적으로 에러로 처리한다. 따라서 const에 값을 할당하는 구문을 실행할 때 발생하는 것이 아니라 const에 값을 할당하는 구문이 있는 함수가 선언될 때 에러가 발생한다.

 또한, const를 이용해서 상수를 선언할 때는 언제나 값을 초기화해주어야 한다. const로 선언된 상수에 값을 할당하지 못한다는 것을 생각하면, 당연한 일이다. 이 또한 문법 에러로, 초기화하지 않는 const를 선언할 때가 아니라, 선언하는 구문이 있는 함수를 선언할 때 에러가 발생한다.

 하지만 const도 상수 선언을 위한 완벽한 해결책은 아니다. const로 선언한 상수에는 값을 재할당할 수 없지만, 상수임에도 불구하고 값을 변경시킬 수 있기 때문이다. const로 선언한 상수는 어디까지나 값의 재할당을 막을 뿐, 그 값을 보호해주지 않는다.

 const가 완벽한 해결책인 것은 아니지만, 한계를 알고 적절하게 사용하면 좀 더 안정적이고 가독성 있는 코드를 작성할 수 있다.

2015-07-18

[ECMAScript 6] let - block 단위 스코프

 ECMAScript 6에서는 기존의 function scope였던 var 이외에 let이라는 block scope 변수 선언을 지원한다. let을 통한 변수 선언은, var를 통한 변수 선언과 다르게 hoisting 하지 않는다. 즉, 변수가 선언된 이후부터 변수가 유효하고 그전에는 해당 변수를 사용할 수 없다.


 hoisting을 없앤 것뿐 아니라 그 외의 실수하기 쉬운 부분을 에러로 처리하여 좀 더 안전한 코드를 작성할 수 있도록 하였다.

 예를 들면, ECMAScript 5에서는 hoisting 된 변수를 실수로 선언 전에 사용할 경우 그 변수는 undefined가 된다. 하지만 let을 사용하면, 변수를 hositing 하지 않을 뿐 아니라, 변수를 선언한 블록 안에서 선언 전에 해당하는 이름을 사용하는 것을 에러로 처리한다.

 또한, 이전에는 같은 스코프, 다시 말해서 같은 함수 안에서 변수의 선언문이 여러 개 있는 것이 정상적인 구문으로 처리되었다. 하지만 let을 사용한다면 같은 스코프에서 중복으로 선언하는 것이 에러로 처리된다.

 아쉬운 점은 위와 같은 에러가 함수의 선언에서 발생하는 것이 아니라, 실제로 그 구문을 실행할 때 발생한다는 것이다. 따라서 여전히 높은 커버리지의 테스트를 작성해야 안전한 코드라고 보장할 수 있다. 하지만 예전처럼 예상하지 못한 undefined가 나와서 문제가 발생한 부분이 아닌 다른 곳부터 추적해가야 할 일은 없어졌다.

 또 다른 문제는 babel에서 다르게 동작한다는 것이다. 파이어폭스나 크롬 등 모던 브라우저나 io.js는 ECMAScript 6를 지원하지만, 오래된 버전의 IE나 node.js 등에서는 아직 let을 지원하지 않는다. 그런 환경에서는 babel을 사용해야 하는데, babel에서는 중복된 선언이나, 선언 전에 사용하는 것을 에러로 처리하지 않는다.
 하지만 조만간 node.js에서도 let을 지원할 것이고, 오래된 버전의 IE도 없어지고 있으니 이런 문제는 곧 해결될 것이다.

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을 써야 한다.

2015-07-14

[JavaScript] undefined 이해하기

 JavaScript에는 3가지 undefined가 존재한다.

타입으로서의 undefined

 우선 undefined는 타입이다.

 ECMA Script 5까지는 다음과 같은 6가지1) Built-in type이 있었다.
  1. number
  2. boolean
  3. string
  4. object
  5. null
  6. undefined
 undefined는 그중 하나다.

값으로서의 undefined

 undefined는 undefined 타입의 유일한 값이다.

 우선 값이 할당되지 않은 변수 혹은 값이 할당되지 않은 프로퍼티는 undefined가 된다.

 또한, 리턴문이 없는 함수나 리턴하는 값이 없는 리턴문으로 끝나는 함수의 실행 결과도 undefined가 된다.

 혹은 전역 프로퍼티인 undefined를 통해서 undefined 값을 얻을 수 있다.

전역 프로퍼티 undefined

 global context에는 undefined라는 이름의 프로퍼티가 설정되어 있다. 이 undefined는 undefined인 값을 가진다.
 ECMA Script 5 이후로 이 프로퍼티는 non-configurable이고, non-writable로 설정되어 있다. 따라서 변경할 수 없다.



1) ECMA Script 6에는 Symbol type이 추가되었다.

2015-07-11

Monad는 무엇인가

 모나드는 하스켈의 성공(?)과 함께 다른 언어에도 유행처럼 퍼져나갔다. 하지만 그 배경이 되는 이론이 너무 복잡하고 수학적이라 많은 사람이 하스켈을 맛만 보다 떠나게 된 이유가 되었고, 다른 언어들에서도 모나드라는 것은 금지어가 되다시피 하였다.

 하지만 어렵다고 모른 채로 살기에는 모나드는 너무 유용하다. 아니 유용한 정도가 아니라 이미 많은 곳에 사용되고 있다.

 그리고 사실 모나드는 쉽다. 아니 어렵긴 어렵다. 그 배경 지식인 카테고리 이론은 어려운 것 맞다. 하지만 그걸 알 필요 없다. 언제는 프로그래머들이 함수가 무엇인지 수학적으로 이해하고 사용하였나? 카테고리론을 이해하지 못해도 모나드는 얼마든지 사용할 수 있다.

 이제 슬슬 모나드가 무엇인지 궁금해졌을 것이다. 이쯤에서 한 문장으로 정리해서 설명했으면 좋겠지만, 아쉽게도 그건 좀 어렵다. 아마 이런 모습도 사람이 모나드를 이해하기 어려워하는 이유일 것이다. 그래도 최대한 풀어서 설명하면 모나드는 다음과 같다.

모나드는 다른 타입을 인자로 받는 타입이다.

 모나드는 타입이다. 기본적으로 모나드는 int나 string 같은 타입이다. 다른 점이 있다면, 모나드는 타입을 인자로 받는다. C++에 익숙한 사람이라면, template class를 다른 generic class를 지원하는 언어에 익숙한 사람이라면 generic class를 생각하면 된다.
 편의에 따라 앞으로 T라는 타입을 받은 모나드 M을 M[T]라고 표현하겠다.

    모나드 타입의 값을 생성하는 함수가 있어야 한다.

     모나드는 임의 타입의 값을 받아서 그 타입을 인자로 받은 모나드 타입의 값을 반환하는 함수가 있어야 한다. 다시 말하면, T 타입의 값을 받아서 M[T] 타입의 값을 반환하는 함수가 있어야 한다. 이는 하스켈에서 return operator라고 불리는 함수이다. 혹은 unit operator라고 부른다.

    다른 모나드 타입으로 진행하는 함수가 있어야 한다.

     이는 하스켈에서는 >>=라고 쓰이는 함수다. 이 함수는 M[T] 타입의 모나드가 있을 때, T 타입의 변수를 받아 M[U] 타입의 모나드를 반환하는 함수를 받아서, M[U] 타입의 값을 반환하는 함수다. 이 함수를 통해서 모나드에서 다른 모나드로 진행할 수 있다.
     보통 bind operator라고 부른다.


     정리하면, 모나드가 되기 위해서는 다음의 3가지 조건을 만족해야 한다.
    1. 타입을 인자로 받는 타입이다.
    2. unit operator가 있어야 한다.
    3. bind operator가 있어야 한다.
     위의 3가지 조건을 만족해야 모나드라고 부른다. 사실 수학적으로 따지려면 몇가지 성질을 더 만족해야 한다. 하지만 그건 어디까지나 수학적으로 그렇다는 것이다. 프로그래머가 사용하기 위해서는 위의 수준으로 이해해도 사용하는데 큰 문제 없다.

     모나드가 무엇인지 설명하였지만, 추상적인 내용이라서 모나드가 무엇이고, 어떻게 사용되며, 어떤 장점이 있는지 잘 감이 안 올 것 같다. 그래서 다음 글에서 모나드가 어떻게 사용되는지를 통하여 좀 더 구체적으로 알아보도록 하겠다.

    사용 예제

    2015-07-07

    [npm] publish 하기 전에 테스트하기


     npm publish라는 명령어를 통해 내가 만든 라이브러리를 npm을 통해 배포할 수 있다.

     보통의 경우라면 문제없다. 하지만 TypeScriptCoffeeScript를 이용하여 컴파일된 라이브러리를 배포하거나, webpack같은 것을 이용해서 라이브러리들을 패킹해서 배포할 경우 npm publish를 하는 것은 마음 놓고 할 수 있는 작업이 아니다.
     배포 전에 명령어를 수행하기 위해 prepublish에 스크립트를 저장하거나, npmignore에 배포하지 않을 파일들을 추가하거나 하는데, 이런 것들이 제대로 되어 있는지 실제 npm에 올리기 전에는 알 수 없기 때문이다.

     그럴 때 사용하기 좋은 커맨드가 npm pack이다. npm pack을 이용하면, 제대로 된 파일들을 배포할지 확인할 수 있다. npm pack을 실행하면 prepublish를 실행시키고, npmignore에 들어있는 파일들이 빠진 파일들이 {라이브러리 이름}-{버젼}.tgz 라는 이름의 압축파일이 만들어진다.

     그러면 해당 라이브러리를 사용하는 프로젝트를 만들고, npm install {압축파일 경로}를 실행하면, 실제로 publish된 라이브러리를 설치한 것처럼 라이브러리를 설치하여 테스트할 수 있다.

    2015-07-06

    [Python] Gil과 Python

     지난번에 언급했듯이 CPython이나 PyPy는 Global interpreter lock(a.k.a. GIL)을 이용해서 동시에 2개 이상의 스레드가 실행되지 못하게 함으로써 스레드 간 동기화를 보장한다.

     하지만 이는 CPython과 PyPy가 thread를 구현하는 방법일 뿐, Python 스펙에는 동시에 2개 이상의 스레드를 실행시키지 말라거나, GIL을 사용하라거나 하는 말은 없다. 그저 CPython과 PyPy가 효율성을 떨어뜨리더라도 GIL을 사용하는 것이 이득이 되는 것이 많다고 생각해서 GIL을 사용하도록 구현한 것뿐이다.

     그래서 Python 구현체 중에서 .net framework 위에서 돌아가는 Iron Python이나 JVM 위에서 올라가는 Jython의 경우 GIL을 사용하지 않는다.

    2015-07-05

    sfuture - JavaScript에서 concurrent한 프로그램 작성하기

     sfuture는 JavaScript에서 사용할 수 있는 컨커런트한 프로그램을 쉽게 작성할 수 있도록 도와주는 라이브러리다. 이름에서 알 수 있듯이, Scala의 Future를 JavaScript로 포팅한 것으로, 내부적으로 ECMA Script 6 promise를 사용하고 있어서, promise가 구현된 환경(node.js 0.12.0 이상, 대부분의 모던 브라우저)에서는 아무런 디펜던시 없이 바로 사용할 수 있다.

     만들게 된 이유는, 전에 rhino engine을 이용해서 Scala로 작성한 beyond라는 게임 서버 엔진이 있었는데, 여러 가지 문제가 있어서 이것을 node.js로 포팅하게 되면서 필요하게 되었다.
     Rhino를 사용할 때는 Java class를 그대로 재사용할 수 있었기에 인터페이스만 수정하는 수준이면 가능했는데, node.js로 포팅해오면서 그럴 수 없게 되었다. 처음에는 Future를 포팅해올지, async등 기존의 라이브러리들을 이용하도록 할지 고민했다.
     하지만 async를 실제로 사용을 해보니, 이것도 결국 콜백 헬을 없앨 수 있는 건 아니고, 오히려 코드가 길어질수록, 가독성만 떨어뜨리는 느낌이라서 결국 새로 구현하였다.

     구현은 타입스크립트로 되어 있지만, publish 전에 컴파일하여 자바스크립트 파일만 배포한다. 원본 소스를 보고 싶으면, 깃헙 리파지토리를 보길 바란다.

    2015-06-26

    타입스크립트의 단점

     지난번 글에서 너무 타입스크립트를 사용하면서 얻게 되는 장점만 말한 것 같아서 이번 글에서는 타입스크립트를 사용하면서 맞게 되는 단점들을 말해보도록 하겠다.

     타입스크립트의 단점은 명확하다. 타입스크립트와 자바스크립트를 섞어서 쓸 수 있다는 점이다.

     타입스크립트로 컴파일한 코드는 자바스크립트가 되기 때문에 타입스크립트로 작성한 모듈을 자바스크립트에서 불러올 수 있다.
     하지만 이렇게 사용한다면, 지난번 글에서 말한 타입스크립트를 사용하는 장점 중 하나인 타입 체크를 위한 verbose 한 코드를 작성하지 않아도 되는 장점이 사라진다. 자바스크립트에서 사용될 것을 가정하고 코드를 작성할 경우는 여전히 verbose 한 타입 체크 코드를 작성해야 한다. 이것은 내가 자바스크립트 코드를 타입스크립트를 포팅하면서 딱히 좋은 점을 느끼지 못했던 이유이기도 하다.
     하지만 이는 자바스크립트를 사용했다면 언제나 발생했을 문제가 타입스크립트를 사용할 때 다시 발생하는 것뿐이다. 자바스크립트를 사용해야 하는 환경에서 타입스크립트를 사용하지 않을 이유는 되지 않는다.

     이번에는 반대로 타입스크립트를 사용하면서 자바스크립트로 작성된 모듈을 불러오는 경우를 보자. 타입스크립트에서는 자바스크립트의 모듈을 그대로 가져다 쓸 수 있다. 덕분에 타입스크립트 생태계는 크게 노력하지 않고 자바스크립트의 생태계를 흡수할 수 있었다. 하지만 이것은 동시에 단점이 되기도 한다. 자바스크립트로 작성된 모듈은 타입 추론을 할 수 없어서 모든 API가 any 타입이 되고, 결국 이 부분이 unsafe 한 부분이 되기 때문이다.

     이런 문제를 해결할 수 있도록 타입스크립트는 모듈의 타입만 분리해서 선언하는 선언 파일을 사용할 수 있게 해놓았다. 선언 파일과 함께 사용하면 자바스크립트로 작성된 모듈을 사용할 때도 잘못된 타입을 사용하면 타입 에러를 내준다.
     물론 선언 파일을 만드는 것도 비용이다. 하지만 이는 큰 문제가 되지 않는다. 유명한 라이브러리에 대해서는 이미 DefinitelyTyped에 많은 선언 파일이 만들어져 있다. DefinitelyTyped에 만들어지지 않은 라이브러리도 있고, 버전이 맞지 않는 경우도 있다. 이에 대해서는 직접 만들거나 수정해서 사용해야 한다. 하지만 이는 큰 문제가 되지 않는다. 어차피 외부 모듈을 사용하려면 어떤 함수가 어떤 인자를 통해서 실행되고 어떤 값을 리턴하는지 파악을 해야 한다. 그렇지 않으면 (특히 자바스크립트같이 동적 타입의 언어들에서는) 무언가 문제가 발생하기 십상이다. 따라서 그저 파악한 API의 시그니쳐를 코드로 옮겨 적으면 된다.


     다시 한 번 강조하지만, 타입스크립트의 단점은 다른 정적 타입 언어들보다 불안한 부분들일 뿐이다. 자바스크립트보다 못하는 점은 없다. 타입어노테이션이나 선언 파일이라는 약간의 비용을 들여서 타입 세이프한 코드를 만든다는 큰 이득이 있어서, 자바스크립트를 사용해야만 하는 환경에서 타입스크립트를 사용하지 않을 이유는 전혀 없다.
     그래서 나는 앞으로도 자바스크립트를 사용할 것이다. 아니 타입스크립트 대신 Google의 AtScript나 Facebook의 flow를 사용할 수는 있겠지만, 정적인 타입 분석이 붙지 않은 상태의 자바스크립트를 이용해 코딩할 생각은 없다.

    2015-06-22

    TypeScript와 함께 한 4개월

     내가 타입스크립트를 처음 쓰게 된 것은 올해 3월이었다. 당시에 알바를 하고 있던 회사에서 작성하던 서버 사이드 자바스크립트 코드의 안정성을 향상하기 위해 타입스크립트로 포팅하는 일을 시작하였고, 당시 그 팀의 구성원은 전원 너무 바빴기 때문에 다른 일을 하던 내가 불려가서 포팅하게 되었다.

     사실 처음에 포팅을 하기로 했을 때는 흥미로운 일이라고 생각하지만, 딱히 의미 있는 일이라고는 생각하지 않았었다. 당시 코드는 이미 뼈대에 해당하는 부분이 대부분 완성되어 있었고, 그 대부분은 이미 타입의 개념이 없이 짜인 코드였었다. 그래서 단순히 타입스크립트로 옮겨도 별 이득이 없을 것이고, 완벽하게 포팅하는데 들어가는 노력에 대비해서 안정성을 확보할 수 있을지에 대해 확신이 없어서였다.

     그래도 돈을 받고 하기로 한 일이었으므로 작업은 시작하였다. 우선 이미 구현되어있는 코드가 너무 많았기 때문에 초반에는 손에 닿는 파일부터 타입스크립트로 변환하며 작업을 하였다. 그 뒤로 한 3개월은 코드를 바꾸는 일만 했다.

     하지만 이때까지는 타입스크립트의 장점을 딱히 느낄 수 없었다. 변환하는 과정에서 몇몇 버그를 잡았지만, 이미 타입이 중요한 부분은 underscore.js를 이용해서 타입체크를 하고 있었기에 타입스크립트가 추가로 해주는 일이 거의 없었다. 변환하는 과정에서 몇몇 버그를 발견하기는 했지만, 충분한 유닛 테스트로 잡을 수 있는 버그들이었기에 딱히 타입스크립트를 사용해야 한다고 느끼지 못했다. 오히려 리팩토링에 너무 많은 시간이 들었기 때문에 비용 대비 효용이라는 측면에서 비효율적이라고 느꼈다. 그러다가 타입스크립트에 대한 인식이 바뀌게 된 것은 다음 프로젝트를 시작하면서부터였다.

     다음 프로젝트를 시작하게 된 것은 지난 5월 말이었으니 거의 한 달 정도 전이다. 하게 된 일은 기존의 스칼라로 작성되었던 beyond 프레임워크node.js에서 돌도록 포팅한 beyond.ts를 만드는 것이었는데, 이번 프로젝트는 처음부터 타입스크립트를 이용해서 작성하였다.

     타입스크립트를 제대로 이용하면서 느껴진 타입스크립트의 장점은 크게 4가지이다.

     우선 자바스크립트를 이용할 때 귀찮은 부분인 타입 체크하는 코드나 테스트를 작성하지 않아도 된다는 것이다. 자바스크립트뿐 아니라 파이썬이나 루비 같은 동적 타입 언어들도 가지는 문제인데 자바스크립트는 타입점검을 하거나 타입을 테스트하기 위해 코드가 verbose 해지기 쉬운데, 타입스크립트를 사용하면 이럴 이유가 전혀 없다.

     두 번째로 안심하고 코딩할 수 있다. 기존에 자바스크립트를 사용할 때는 장황한 테스트를 작성하여 높은 커버리지를 유지하여야 했다. 그렇지 않으면 사소한 실수건 의도적인 수정이었건 간에 코드를 잘못 변경하여 문제가 생기는 경우가 가끔 발생하는데, 타입스크립트는 이런 문제들을 컴파일 타임에 점검해주기 때문에 사전에 막을 수 있었다.

     세 번째로 ES6의 기능들을 사용할 수 있었다. 사실 이건 타입스크립트의 장점이라고 하기는 뭐하지만, 타입스크립트는 대부분의 ES6를 ES5나 ES3로 변환시켜준다. 덕분에 fat arrow, class, const와 let, 다양한 parameter 등 많은 기능을 사용할 수 있다.

     마지막으로 내가 사용하는 라이브러리를 더 쉽게 파악할 수 있다. TypeScript에서는 기존의 JavaScript로 작성된 npm 라이브러리를 그대로 사용할 수 있다. 대신에 타입 추론을 해주기 위해서 선언 파일이 있어야 한다. 하지만 사용하는 모든 모듈에 대해서 선언 파일을 직접 만들 필요는 없고, DefinitelyTyped를 통해서 다른 사람들이 이미 구현해둔 파일을 받을 수 있다. 이것은 사용할 API를 이해하는 데 꽤 많은 도움을 준다.

     물론 타입스크립트에 단점이 없는 것은 아니다. 하지만 타입스크립트를 사용하여 얻을 수 있는 장점이 더 크다고 생각된다. 정확히는 타입스크립트를 사용함으로 인해 들어가는 비용에 비해 타입스크립트를 통해 얻을 수 있는 이득이 훨씬 크다고 생각한다.
     그래서 다음에도 자바스크립트를 사용할 일이 생긴다면, 타입스크립트를 사용할 것이다. 아니 타입스크립트가 아닌 Google의 AtScript나 Facebook의 flow를 사용하게 될 수는 있지만, 최소한 자바스크립트를 동적 타입 언어인 그대로 사용하지는 않을 것이다.

    2015-06-21

    변하지 않아도 되는 코드는 죽은 코드 뿐이다.

     내가 병특을 시작했던 회사에서 있었던 일이다.

     그 회사는 그냥 흔한 SI 회사였는데 덕분에 코드 퀄리티는 크게 보장할 수 없었다. 정말이지 많은 것이 나를 괴롭혔지만, 그중에서 나를 가장 괴롭혔던 건 옛날에 작성되어 관리 안 되는 코드들이었다. 그 회사는 SI 회사답게 유지보수라는 명목으로 몇 년 전에 팔았던 프로젝트의 유지보수라는 이름으로 고정 수익을 벌고 있었는데, 그중에서 가장 심한 건 10년 전에 작성되었던 프로젝트도 있었다.

     그 날도 여전히 그 코드에 괴롭힘당하고 있었다. 내가 괴로워하고 있으니 당시 내 사수였던 개발자 J가 와서 한마디 해줬었다.

    너무 그러지 마. 이거 그래도 네 학교 선배 K가 병특할때 짰던 코드야. 조금씩 변경된 부분이 있지만 대부분 네 선배가 짠 거야.
     위로의 말이었는지, 괴로워하는 거 티 내지 말라는 의미였는지 나는 모른다. 내가 아는 것은 그저 이게 내가 퇴사를 결심하게 된 계기가 되었다는 것이다. 어째서 저 말이 그렇게 내 마음을 흔드는 말이 되었을까?

     당시에 K가 퇴사한 건 거의 10년 가까이 된 일이었다. 즉, 저 코드는 작성된 지 거의 10년이 된 코드라는 것이다. 거기에 유지 보수하면서 추가된 기능 외에 큰 틀은 전혀 건드리지 않았었다는 것이다. 뭐 J는 10년이 지날 만큼 안정적으로 작성된 코드라고 말하고 싶었을지도 모르겠다. 하지만 10년을 변하지 않은 코드가 좋은 코드일 리가 없다.

     지난 10년간 프로그래밍 도구는 많은 발전이 있었다. 단적으로 생각해봐서 2003년에 visual studio 6.0으로 코드를 작성하는 것과 2013년에 visual studio 2013으로 코드를 작성하는 것을 생각해보자. 아무도 2013 대신 6.0을 선택할 사람은 없을 것이다. 10년이라는 시간은 이 정도의 발전을 가지고 왔다. 도구뿐이 아니다. 개발 방법론, 설계법, 분석법 모든 측면에서 지난 10년간 많은 발전이 있었다. 즉, 10년 전에 좋은 코드였다고 하더라도 지금 기준에서 좋은 코드라는 보장은 할 수 없다.

     당시 그 회사는 코드의 이력관리를 전혀 안 했기 때문에1) 당시 K가 작성한 코드가 어떤 코드였는지 알 수 없었다. 하지만 내가 보고 있던 코드는 10년 전 기준으로도 좋은 코드였을 거라고 말할 수 없는 그런 코드였다. 개발에서 가장 중시하는 abstraction이나 flexibility를 전혀 고려하지 않고 전역변수들로 가득 차 있던 그런 코드를 아직 수정하지 않고 있던 회사를 도저히 믿을 수 없었다.

     심지어 그 당시 코드는 10년간 전혀 변경이 없던 코드도 아니었다. 앞에서도 말했듯이 당시에 유지 보수라는 명목으로 계약하고 꾸준히 이것저것 기능들을 추가했었다. 글 불구하고 코드스멜들을 전혀 제거하지 않았다는 것은 더더욱 그 회사를 믿지 못하는 계기가 되었다.


    1) 난 21세기에 이메일로 패치도 아닌 코드 전체를 주고받으며 작업하게 될 거라고는 상상도 못 했다.

    2015-05-24

    [MySQL] Replication (3) - Replication을 사용하는 이유

     지난번 글에서 MySQL replication이 무엇인지 설명하면서, replication은 cluster와 다르게 동기화되는 것을 기다리지 않아도 돼서 빠르므로, 실시간 동기화가 필요하지 않은 경우에 사용된다고 하였다. 그렇다면 실시간 동기화가 필요 없는 경우는 어떤 경우들이 있을까?
     이번 글에서는 MySQL이 추천하는 적절한 replication 사용 방법에 대해서 알아보도록 하겠다.

    백업

     replication의 주목적은 데이터를 백업하는 것이다. MySQL은 데이터의 지속성을 보장해준다. 하지만 아쉽게도 데이터베이스 이외의 다양한 이유(e.g. 하드디스크)로 데이터베이스를 복구할 수 없게 되는 일이 있다. 이런 경우를 대비하여, 다른 컴퓨터에 데이터를 복사하여 마스터 데이터를 복구할 수 없으면 복사된 슬레이브의 데이터를 이용하여 데이터를 복구할 수 있게 한다.

    아카이브

     단순 백업을 위해서 뿐 아니라 아카이브를 만들기 위해서도 replication이 사용된다. mysqldump를 이용하면 데이터를 복사하여 아카이브를 만들 수 있다. 하지만 쿼리를 수행 중인 데이터베이스에 mysqldump를 실행하면, 깨진 데이터가 들어올 수 있다. 이는 MySQL enterprise backup을 이용하면 해결할 수 있지만, replication을 이용해서 해결할 수도 있다.

     지난번 글에서 설명하였듯이, 슬레이브의 SQL thread를 정지시키면, 마스터의 데이터를 읽어와서 relay log를 만들지만, 데이터베이스는 업데이트하지 않는다. 따라서 SQL thread만 정지시켜 놓으면, 안전하게 mysqldump를 실행할 수 있다. 이를 이용하여 서비스 중인 데이터베이스의 데이터를 서비스를 중지시키지 않고 아카이브를 만들기 위해서 replication을 사용하기도 한다.

    부하 분산

     혹은 쿼리를 분산시키기 위한 목적으로도 사용된다.
    서버별로 다른 슬레이브에서 값을 읽게 한다

     대부분의 웹 서비스는 데이터의 변경에 비해서 데이터를 읽는 작업이 많다. 이런 경우 슬레이브를 이용하여 부하를 분산시킬 수 있다. 혹은 로그 분석기처럼 고정된 데이터를 다양하게 하는 서비스의 경우도 replication을 사용하는 것이 좋다. 이런 서비스들은 replication을 이용하여 데이터를 읽는 것을 슬레이브에서 수행하면 부하를 분산시킬 수 있다.

    지역 분산

     혹은 글로벌 서비스를 위하여 지역별로 다른 슬레이브를 구성할 수도 있다.
    지역별로 슬레이브를 만든다.
     간혹 전 세계를 상대로 같은 데이터를 서비스해야 하지만, 레이턴시도 매우 중요한 서비스들이 있다. 보통 이런 서비스의 경우 데이터베이스는 공유하고, 지역별로 서버를 둔 뒤, 서버 메모리 캐시 같은 것을 사용하는 것이 일반적이다. 하지만 replication을 사용하면 위의 복잡한 작업을 간단하게 해결할 수 있다.


     이상으로 replication의 다양한 사용방법을 알아보았다. 하지만 이는 어디까지나 MySQL 매뉴얼에서 추천하는 일반적인 사용법이고, 실제로 활용할 방법은 무궁무진하다. 중요한 것은 cluster와 차이점을 이해하고 적절한 지점에 사용하는 것이다.

    MySQL Replication

    2015-05-23

    [MySQL] Replication (2) - Replication은 어떻게 동작하는가

     지난번 글에서는 replication이 무엇인지 알아보았다. 이번에는 MySQL replication이 어떻게 동작하는지 살펴볼 것이다.


     replication은 다음과 같은 순서로 진행된다.
    1. 마스터 데이터베이스가 binary log를 만들어 이벤트를 기록한다.
    2. 각 슬레이브는 어떤 이벤트까지 저장되어 있는지를 기억하고 있다.
    3. 슬레이브의 IO thread를 통해서 마스터에 이벤트를 요청하고 받는다.
    4. 마스터는 이벤트를 요청받으면 binlog dump thread를 통해서 클라이언트에게 이벤트를 전송한다.
    5. IO thread는 전송받은 덤프 로그를 이용하여 relay log를 만든다.
    6. SQL thread는 relay log를 읽어서 이벤트를 다시 실행하여 슬레이브에 데이터를 복사한다.


     각각을 자세히 설명하면 다음과 같다.

    binary log

     MySQL은 데이터 혹은 스키마를 변경하는 이벤트들을 저장할 수 있다. 이 이벤트들이 저장된 것을 binary log라고 부른다.

     binary log의 주목적은 데이터를 복구하는 것이다. 아카이브된 데이터가 있고, 아카이브 된 다음에 들어온 이벤트를 기록한 binary log가 있으면, 원하는 시점으로 데이터를 복구할 수 있다.
     데이터베이스를 변경하는 모든 이벤트가 저장되어 있으므로 이를 슬레이브에서 다시 실행하는 것만으로도 복사된 데이터베이스가 만들어진다.


    binlog dump thread

     replication을 위해서는 마스터에 저장된 binary log를 슬레이브로 전송해야 한다. 이를 위해 마스터에서는 스레드를 만드는데 이를 binlog dump thread라고 부른다.
     binlog dump thread가 하는 일은 단순하다. 슬레이브가 이벤트를 요청하면 binary log에 락을 걸고, event를 읽어 슬레이브로 이벤트를 전송한다. 이때, binary log를 너무 긴 시간 락하지 않기 위해서 슬레이브에 전송하기 전에 binary log를 읽고 바로 락을 해제한다.


     마스터는 슬레이브에 대한 정보를 전혀 가지고 있지 않다. 슬레이브가 있는지 없는지, 몇 개의 슬레이브가 붙어있는지, 각 슬레이브가 어디까지 데이터를 복사했는지, 보내야 할 이벤트가 있는지 전혀 모른다. 그저 슬레이브가 이벤트를 요청하면 그에 해당하는 이벤트를 보내줄 뿐이다. 덕분에 마스터는 큰 부하 없이 데이터를 복사할 수 있다.

     binlog dump thread는 슬레이브가 마스터에 컨넥트할 때 생성되지만, 여러 개의 슬레이브가 붙어도 단 하나의 스레드만 생성된다.

    I/O thread


     슬레이브는 마스터에 없는 2개의 스레드가 있다. 그중 하나가 I/O thread다. 각 슬레이브는 자신이 어디까지 데이터를 복사했는지 기억하고 있다.
    I/O thread는 슬레이브가 마지막으로 읽었던 이벤트를 기억하고 있다가, 마스터에게 다음 이벤트를 전송해 달라고 요청한다. 마스터의 binlog dump thread가 이벤트를 보내주면 이것을 Relay log에 저장한다.
     따라서 I/O thread가 정지된 상황에서 마스터의 binary log가 지워지면, 슬레이브는 마스터의 데이터를 복제할 수 없다.

    Relay log

     슬레이브는 I/O thread를 통해서 받은 이벤트를 로컬에 있는 file에 저장한다. 이를 relay log라고 부른다.
     보통 relay log는 SQL thread가 이벤트를 읽고 나면 지운다. 따라서 어느 정도 이상의 크기가 되지 않는다. 하지만 SQL thread가 멈추어 있으면 relay log는 계속해서 크기가 커지게 된다. 그렇게 되면 I/O thread는 자동으로 새 relay log 파일을 만들어, 파일이 너무 커지는 것을 막는다.

    SQL thread

     SQL thread는 I/O thread가 만든 relay log를 읽어 실행을 시키고, relay log를 지운다. SQL을 실행시키는 스레드와 마스터로부터 값을 복사해오는 스레드가 분리되어 있다는 것은 매우 중요한 특징이다.

     보통 MySQL의 데이터를 백업하여 아카이브를 만드는 것은 mysqldump를 이용한다. 하지만 mysqldump는 MySQL이 쿼리를 실행하고 있을 때 실행되면 데이터가 깨지는 문제가 발생한다. 하지만 replication을 이용하면, 슬레이브의 SQL thread만 정지시키면 되기 때문에 안전하게 백업을 만들 수 있다.

    MySQL Replication

    2015-05-15

    [MySQL] Replication (1) - Replication은 무엇인가

     MySQL replication은 데이터베이스를 그대로 복사하여 데이터베이스를 한 벌 더 만드는 기능이다. 언뜻 보면 MySQL cluster와 비슷하지만, 말 그대로 분산환경을 만들어서 single point of failure를 없애려는 cluster와는 달리 MySQL replication은 단순히 데이터를 복제한다.

     따라서 모든 데이터가 동기화되는 cluster와는 달리, replication은 동기화가 비동기적으로 발생한다. 따라서 어떤 데이터베이스에는 데이터가 업데이트되어 있지만, 다른 데이터베이스에서는 업데이트되지 않을 수도 있다.

     또한, 마스터와 슬레이브로 나누어지기 때문에 데이터를 변경하는 쿼리는 단 하나의 데이터베이스에만 요청할 수 있다. 다시 말해서 슬레이브의 데이터를 변경하면, 마스터에 그 변경은 반영되지 않고, 동기화하는 도중 에러를 발생시키기도 한다.

     cluster와 비교하면 replication은 동기화도 보장되지 않고 쿼리를 분산할 수도 없어 cluster 대신 사용할 이유가 없어 보인다. replication은 어떤 용도로 사용될까?

     replication이 cluster에 비해서 가지는 가장 큰 장점은 cluster에 비해서 값의 변경이 매우 빠르다는 것이다. cluster는 값을 변경하려고 하면 클러스터 군을 이루는 다른 서버들도 값이 변경되었다는 것을 확인해 주어야 한다. 하지만 replication은 마스터의 값만 변경하면 되기 때문에, 값을 변경하는 쿼리가 매우 빠르게 실행된다.

     그래서 주로 실시간 동기화가 필요 없는 경우 cluster대신 replication을 사용한다.

    MySQL Replication

    2015-04-24

    한줄짜리 코드에도 반드시 괄호를 써야한다.


    http://www.reddit.com/r/ProgrammerHumor/comments/1rfstw/there_are_two_types_of_people/
     위의 meem에서 알 수 있듯이 프로그래머는 괄호를 같은 라인에 붙여 쓰는가 띄어 쓰는가 하는 별 중요하지 않은 것으로 끊임없이 논쟁을 벌이고 있다. 여기에  조건문뿐 아니라 함수의 선언에 괄호를 어디에 붙이는가 까지 해서 4가지 조합을 가지고 끊임없이 싸운다.


     뭐 나는 개인적으로 함수의 선언이나 조건문에 붙는 괄호를 한 라인에 붙여 쓰는 걸 선호하지만, 그에 대해서 딱히 내 의견을 강요하지 않는다. 그냥 프로젝트에서 기존에 쓰이던 것이나, 다른 팀원들이 원하는 스타일을 따른다.

     하지만 괄호에 관해서 절대 양보 못 하는 것이 하나 있다. 한 줄짜리 statement를 위해서 괄호를 사용할 것인가 말 것인가 하는 것이다. 이유를 알 수 없지만, 조건문이나 for 문에 한 줄짜리 statement가 들어갈 일이 있으면, 괄호를 생략하고 쓰는 사람들이 많다.


     괄호를 생략하는 사람들은 이것저것 이상한 주장을 한다. 쓸데없이 바이트를 낭비한다거나, 오히려 한 줄짜리 코드라는 것을 명시해주어야 한다거나, 이유 없이 타이핑할 이유가 없다거나, 뭐 이것저것 이유를 대는데 전부 20세기라면 의미 있을지도 모르지만, 지금이라면 전혀 의미 없는 이유다. 21세기에는 괄호를 생략할 이유가 전혀 없다. 오히려 괄호를 생략해서는 안되는 절대적인 이유가 있다.

     코딩할 때 언제나 버젼 컨트롤 시스템을 사용하기 때문이다. git을 사용하든 머큐리얼을 사용하든 심지어 subversion을 사용하든 상관없지만 어찌 됐든 코딩할 때는 언제나 버젼 컨트롤 시스템과 함께하며 소스의 변경을 추적한다. 이때, 괄호를 생략했던 한 문장의 코드가 여러 줄로 나누어지면 괄호를  해서 불필요한 변경사항이 두 코드의 diff에 나오게 된다. 이러한 불필요한 변경 이력이 코드에 나오는 것을 막기 위해서 한 줄의 코드에도 반드시 괄호를 써야 한다.

    2015-04-02

    C는 C++의 부분집합이 아니다

     오늘 황당한 글을 봤다.

    잘 짜인 C 프로그램은 C++ 프로그램이다. 따라서 잘 짜인 C 프로그램은 C++ 컴파일러로 컴파일할 수 있어야 한다.

     일단 저 말은 C++의 창시자인 비야네 스트롭스투룹이 한 말이다. 하지만 저 말은 틀린 말이다. "네가 뭔데 감히 비야네님을 틀리다고 하느냐"라는 생각이 들겠지만 잠시만 진정하자. 나는 비야네님이 틀렸다고 하지 않았다. 내가 틀리다고 하는 것은 아무런 문맥도 없이 그냥 저 문구만 따와서 말하는 사람을 틀리다고 하는 것이다.

     저 말은 분명히 1999년 이전까지는 맞았던 말이다. 분명히 비야네 스트롭스트룹은 C++을 만들면서 C와의 호환성을 고려하였고, 당시의 표준(ANSI C)을 잘 지킨 C 코드는 C++ 컴파일러로 정상적으로 컴파일 되었다. 하지만 그것은 어디까지나 C99가 나오기 전의 이야기다.

     C99에서는 여러 가지 새로운 기능을 도입하였고, C++은 그것을 이미 다른 방식으로 구현하고 있었거나, 혹은 필요하지 않은 기능이라고 생각하여 가지고 오지 않았다. 게다가 새로운 표준인 C11이 나오고, C++도 새로운 표준인 03, 11을 거쳐 14까지 나오면서 둘 사이의 간극은 이미 어떻게 할 수 없을 정도로 커졌다.

     그런 연유로 비야네 스트롭스트룹은 잘 짜인 C 프로그램이 C++ 프로그램이라고 말할 때 조건을 붙인다. "단, 이건 C89에 한정한다."라고. 하지만 요새 C89를 쓰는 프로그램이 얼마나 있나? 액티브하게 작업이 진행되는 프로젝트 중에서 C89를 쓰는 프로그램 있으면 가지고 와봐라. 찾으려고 노력해본 적은 없지만 찾기 어려울 것이다.

     따라서 요새 저런 말을 하는 사람은 그냥 공부를 안 한 사람이다. 그것도 한 20년 전에 공부했던 사람이니 대선배님일 수도 있겠다. 그 사람에게 C99 이후 C++과 스펙이 변경되어 C99 표준을 지킨 코드는 C++컴파일러로 컴파일 안 될 수도 있다고 했더니, 거기에 달린 답변은 더 황당했다.
    표준이 문제가 아니라 잘 짜인 C 코드라면 C++ 컴파일러로 컴파일할 수 있어야 한다. C++ 컴파일러로 컴파일되지 않는 코드라면 잘 짜인 C 코드가 아니다.
     아.... 정말 충격과 공포다.
     무슨 생각을 하면 잘 짜인 C코드의 기준을 C++에서 찾는지 모르겠다. 둘은 이미 다른 언어로 분화되었다. 한동안 비야네는 C와 C++을 합치려고 노력하던 시절도 있지만, 요새는 포기한 듯이 보인다. 심지어 그는 C/C++은 아무것도 모르는 사람들이나 쓰는 용어로 C와 C++을 합쳐서 불러서는 안된다고 말하기도 한다. C와 C++은 이미 같은 언어가 아니다.

     C++ 컴파일러로 컴파일 가능한 코드만을 잘 짜인 C 코드라고 말한다면, C99 이후에 새로 추가된 기능을 전부 포기하는 것밖에 되지 않는다.

     분명하게 말하건대 표준을 지키면서 잘 짜인 C 코드는 C++ 컴파일러로 컴파일되지 않을 것이다.


    p.s. 아쉽게도(혹은 다행히도) 비공개 커뮤니티에 올라온 글이라 원본 링크는 못 올렸다.

    2015-02-21

    [Scala] 관련있는 데이터를 묶어서 사용하기 - alias와 case class

     Scala에서 여러 개의 값을 묶어서 새로운 타입을 정의하는 방법은 여러 가지가 있다.

    Type alias

     가장 쉬운 방법은 튜플의 alias를 만드는 것이다.

     위의 코드의 4번째 줄에서 month와 date를 선언하면서 Date 타입의 변수를 쪼개 새로운 값에 할당하는 것을 decomposition이라고 한다. 이렇게 decomposition을 이용하면 구조체의 값들을 쉽게 다른 값에 할당할 수 있고, 혹은 아래와 같이 패턴매칭을 이용해서 사용할 수 있다.


     하지만 튜플의 alias를 만드는 방식은 큰 문제가 있다. 이런 방식은 타입 세이프 하지 않다. 예를 들어 위에서 정의한 Address 타입과 함께 아래와 같이 정의된 Date 타입이 같이 사용된다면 둘 다 실제로는 같은 Tuple2[String, Int] 타입이기 때문에 패턴매칭으로는 AddressDate를 구분할 방법이 없다.



    case class

     튜플의 alias가  타입 세이프 하지 않다는 문제를 해결하기 때문에 보통은 data composition에 case class를 사용한다. case class를 사용하면, 실제로는 다른 같은 타입들의 묶음과 구분할 수 있을 뿐 아니라, 내부 값에 이름으로 접근해 꺼낼 수 있어서 내부 값을 더 쉽게 읽을 수도 있다.

    2015-02-18

    [C++] Object slicing

     Object slicing이란 상속받은 class의 instance를 부모 class의 instance로 복사함으로써 상속받은 class가 가지고 있던 정보가 손실되는 것을 말한다. 이는 기능이 아니라 stack에 값을 할당하는 value 타입의 특성 때문에 생기는 버그다. 그래서 heap에 값을 할당하는 reference 타입밖에 없는 Java 같은 언어에서는 발생하지 않는다.

     Object slice 때문에 value type에 대해서는 upcasting을 해서는 안된다. 대부분 upcasting이 필요한 경우는 이미 무언가 잘못된 경우이니 코드를 수정해야 한다. 만약 무슨 일이 있어도 upcasting을 해야 한다면 반드시 heap에 값을 할당해야 한다.

    2015-02-03

    Glowing Bear - 터미널에서 하던 IRC 웹에서 그대로

     나는 freenode를 구경하거나 친구들과 놀기 위한 용도로 IRC를 사용한다. 데스크탑 어플리케이션도 많이 쓰이지만, IRC라는 프로토콜의 특성상 접속하여 있지 않으면 대화를 볼 수 없어서 freenode에 있는 사람들은 IRC를 계속 접속해놓을 방법을 찾는다. 가장 쉬운 방법은 컴퓨터를 끄지 않고 다니는 방식이지만, 보통 개발자 중에 이런 방식을 사용하는 사람은 없다. 보통은 서버에 터미널 기반의 IRC 클라이언인 WeeChat이나 Irssi 띄우거나, IRC Cloud라는 서비스를 사용한다.
     하지만 터미널 클라이언트를 사용하면 언제서나 접속할 수 있는 웹 클라이언트가 아쉬워지고, IRC Cloud를 사용하기에는 한 달에 5$ 하는 비용뿐 아니라 WeeChat의 plug-in기능이 아쉬워진다. 그래서 보통은 WeeChat과 IRC Cloud 양쪽을 사용하는 방식을 택하지만, 그렇게 되면 2개의 접속이 연동되지 않기 때문에 불편한 건 어쩔 수 없다.
     그래서 WeeChat plug-in을 이용해 위와 같은 웹 서비스를 만들어볼 계획이었다. 우선 채팅 로그를 DB에 저장하는 스크립트를 만들던 중 WeeChat에 완전히 같은 목적을 가진 relay protocol이 있다는 것을 알게 되었다. Relay protocol은 WeeChat client가 relay 서버가 되어, Relay 클라이언트와 TCP socket을 이용해 통신을 하게 된다. Relay protocol을 사용하면 WeeChat과 완전히 같은 화면을 볼 수 있는 데다가 WeeChat plug-in을 그대로 사용할 수 있다는 장점이 있다.
     Relay protocol을 사용하는 client는 여러 가지가 있다. Qt를 사용해서 데스크탑 애플리케이션을 만든 QWeeChat, node.js를 이용한 웹 서버 WeeCloud 등도 많이 사용된다. 하지만 내가 사용하는 클라이언트는 Glowing Bear다. Glowing Bear는 완전히 static 한 web page에서 WebSocket을 이용해 WeeChat relay server와 통신하기 때문에 WeeCloud와 달리 웹 서버와의 통신은 필요하지 않아서 순전히 WeeChat 서버하고만 통신한다는 장점과 웹 서버 이외에 별도의 서버가 필요하지 않다는 장점이 있다.
     일단 지금 3~4일째 사용하고 있는데 키보드로 채널을 이동할 수 없다는 것을 제외하고는 딱히 문제를 찾을 수 없어서 앞으로도 계속 사용할 것으로 보인다.

    2015-01-17

    Cyclomatic complexity - 코드의 복잡성을 정량적으로 측정하기

    Cyclomatic Complexity

     Cyclomatic complexity(a.k.a. CC)는 코드의 복잡성을 나타내는 지표 중 하나다. CC를 계산하는 방법은 매우 간단하다. 단순히 코드의 컨트롤 플로우가 분기하는 부분의 개수를 세면 된다.

     CC를 처음 제안했던 Thomas J. McCabe는 함수 하나의 CC가 10을 넘기지 말도록 했지만 이건 76년에 나온 기준이고, 지금의 소프트웨어는 40년 전과 비교가 되지 않게 복잡해진 만큼 15나 20까지는 괜찮다고 주장하는 사람도 있다.
     어찌됐든 간에 중요한 것은 CC가 커지면 커질수록 소프트웨어의 에러가 발생할 확률1)이 증가한다는 것이다. 최댓값을 얼마로 잡을지는 프로젝트의 성격과 팀의 성향에 따라서 다르게 잡지만, 지난 40년간 코드의 복잡도를 정적으로 측정할 수 있는 몇 안 되는 지표로써 널리 쓰이고 있다.

    Extended Cyclomatic Complexity

     하지만 CC가 단순히 분기점만을 세기 때문에 불만을 가지는 사람들이 있었다. 그들이 불만을 가지는 이유는 크게 2가지다.
     우선 CC는 단순히 분기점의 수를 세기 때문에, 실제로 같은 코드를 어떻게 표현하느냐에 따라서 값이 달라진다.
     그래서 단순히 분기점을 세는 것이 아니라 조건문에 들어가는 Boolean operator(&&, ||)의 수를 더하는 지표가 나왔다. 이를 Extend Cyclomatic Complexity(a.k.a ECC)라고 부른다.

    Modified Cyclomatic Complexity

     CC에 다른 이유로 불만을 가지는 사람들도 있다. 원래의 CC는 switch에 사용되는 case 문의 수만큼 증가한다. 하지만 대부분의 경우 switch 문에 들어가는 구문은 매우 간단하다. CC의 원래 목적이 코드의 복잡성을 측정하기 위함이라는 것을 생각하면 실제 코드를 복잡하게 하지 않는 switch 때문에 매우 증가하는 CC는 불공평하다. 그래서 나온 것이 Modified Cyclomatic Complexity(a.k.a. MCC)다. MCC는 case 문의 개수와 상관없이 switch 문이 수를 1만 증가시킨다.


     앞에서도 말했듯이 CC나 그 변종들은 계산이 간단하고 대부분의 경우에 유용하기 때문에 실제로 많이 쓰이는 지표 중 하나다. 하지만 CC는 어디까지나 분기가 많으면 코드가 복잡해진다는 경험에 기반을 두는 지표다. 따라서 일의 성격에 따라서 CC가 높을 때 코드의 가독성이 높아지는 경우도 있다.
     게다가 성능이나 다른 요소들로 인해 코드의 간결성을 포기해야 하는 경우도 있기 때문에 어느 정도로 엄격하게 규칙을 적용해야 하는지는 프로젝트의 성격에 따라 달라진다.

    1) 정확히는 에러가 있는 함수를 수정했을 때 다른 에러가 발생할 확률이 증가한다.

    2015-01-12

    Global Interpreter Lock이란?

    GIL이란?

     GIL이란 Global Interpreter Lock의 약자로 여러개의 쓰레드가 있을떄 쓰레드간의 동기화를 위해 사용되는 기술 중 하나이다. GIL은 전역에 lock을 두고 이 lock을 점유해야만 코드를 실행할 수 있도록 제한한다. 따라서 동시에 하나 이상의 쓰레드가 실행되지 않는다. 예를 들어 아래 그림과 같은 3개의 쓰레드가 분산해서 일을 처리하게 될 때도 실제로 CPU를 점유할 수 있는 thread는 한 개뿐이다. 따라서 실제로 사용하는 코어는 하나뿐이라는 것이다.
    싱글 코어 컴퓨터에서 multi thread program을 실행하는 모습 같다.

    GIL의 efficiency

     직관적으로 멀티코어에서도 코어를 하나밖에 사용 못 한다면 GIL을 사용해서 multi threads를 지원하는 것은 성능에 큰 문제가 있을 거라고 생각된다. 하지만 이는 대부분의 경우에 큰 문제가 되지 않는다. 정확히 말해서 프로그램은 대부분 I/O bound이기 때문에 문제가 되지 않는다. I/O bound의 경우 대부분 시간을 I/O event를 기다리는 데 사용하기 때문에 event를 기다리는 동안 다른 thread가 CPU를 사용하면 된다.
     반대로 말해서 프로그램이 CPU bound인 경우에는 multi-threaded program을 작성해도 성능이 향상되지 않는다. 오히려 lock을 acquire하고 release하는 시간 때문에 성능이 떨어지기도 한다.

    GIL의 장점

     멀티 쓰레드 프로그램에서 성능이 떨어질 수도 있지만, CPython, PyPy, Ruby MRI, RubiniusLua interpreter 등 많은 인터프리터 구현체들이 GIL을 사용하고 있다. 그 이유는 우선 GIL을 이용한 multi-threads를 구현하는 것이 parallel 한 multi-threads를 구현하는 것보다 훨씬 쉽다는 것이다.
     게다가 이런 parallel 한 multi-threads 구현체들의 문제는 싱글 쓰레드에서 오히려 더 느려진다는 것이다. 그래서 CPython이나 Ruby MRI에서는GIL을 없애려는 많은 시도가 있었지만, 결국 싱글 쓰레드에서의 성능 저하를 극복하지 못하고 GIL로 돌아왔다. 결국, 파이썬의 창시자인 귀도 반 로섬은 CPython에서 GIL을 없애는 대신 싱글 쓰레드에서 성능을 떨어뜨릴 구현은 받아들이지 않겠다고 선언하기도 했다. 1)

    1) https://wiki.python.org/moin/GlobalInterpreterLock#line-31

    2015-01-07

    [Scala] 함수의 상속 가능성을 deprecated 시키기

     흠.... 블로거로 옮기고 나서 글을 쓰기 시작한 지 1년이 지나가는데, 이렇게 써도 되는지 고민되는 글은 처음이다. 사실 블로거로 옮기기 전에 폭파한 블로그가 2개인가 3개인가 있는데 당시에는 글을 써도 되는지 고민 없이 막 썼었다. 아는 게 없어 용감했었나 보다. 근데 어느 정도 아는 게 생기고 나니 혹시 나로 인해 잘못된 내용을 배워가는 사람이 있을까봐 최대한 정석에 가까운 내용만을 쓰려고 노력했다. 근데 이번에 쓸 글은 어떻게 봐도 편법에 해당하는 내용이다. 그래서 아래와 같은 경고로 글을 시작하겠다.

    경고: 지금부터 설명할 방법은 권장되는 방법이 아닌 hacky한 방법입니다. 사용함으로 인해 생기는 문제는 책임지지 않습니다.

     지난번 글에서 deprecated annotation을 이용해서 API를 변경하는 방법을 설명했었다. 이번에 설명할 내용은 function prototype은 변경하지 않고 클래스의 상속 가능성을 deprecate 시키는 것에 관한 내용이다.

     Scala에는 sealed modifier나 final modifier를 이용해서 함수의 상속성을 제한하는 기능이 있다. 이 두 modifier에 대해서 간단하게 설명하면 sealed modifier는 다른 파일에서는 해당 클래스를 상속 못하게 막는 modifier이고, final modifier는 클래스를 상속 못하도록 하거나 함수의 override를 막아주는 modifier이다. 즉, API로 제공되던 클래스에 sealed나 final을 붙이거나 함수에 final을 붙이면 상속할 수 있던 클래스나 override할 수 있던 함수를 상속 불가능하게 만들게 된다. 이런 상황에서 쓰기 위해 설계되었던 것이 annotation이 deprecatedInheritance과 deprecatedOverriding이다.

     원래 deprecatedInheritance와 deprecatedOverriding는 Scala 2.10에 들어갈 예정이었다. 하지만 디자인 이슈를 결정하지 못하고 방황하는 사이에 2.10을 출시할 일정이 다가왔고 결국 구현체는 있지만, API로는 공개되지 않은 채 2.10이 출시되게 되었다. 따라서 정상적인 방법으로는 Scala 2.10이나 Scala 2.11에서 이 두 annotation을 사용할 방법은 없다.

     하지만 상속 가능성을 deprecate 시키는 기능은 라이브러리 제작자라면 포기하기 아까운 기능이다. 가장 좋은 것은 이 기능이 Scala 라이브러리에 정식으로 반영되기를 기다리는 것이지만, 만약 어떻게든 지금 당장 사용하길 원한다면 다음과 같은 비정상적인 방법을 써야 한다.

     현재 이 두 annotation은 scala package에 private으로 되어있다. 따라서 scala라는 이름의 package object를 만들고 그 안에서 DeprecatedInheritanceDeprecatedOverriding라는 public 한 새로운 type을 만드는 것으로 scala package 밖에서도 두 annotation을 사용할 수 있게 된다.

     하지만 시작부터 경고하며 시작했듯이 이는 hacky한 방식이다. 언제 이름이 바뀔지 모르고, 심지어 운이 없다면 언제 사라질지도 모르는 기능이다. 거의 편법에 한없이 가까운 방식이므로 사용할지 말지는 알아서 판단해서 선택해야 한다.

    Scala annotation

    2015-01-06

    [Scala] deprecated annotation - 호환성을 보장하며 API 수정하기

     크게는 구조의 변경을 위해서부터, 작게는 오타 수정까지 API를 수정할 일은 많이 있다. 하지만 API를 변경하는 것은 복잡하고, 오래 걸리고, 어려운 일이다.

    deprecated

     API를 변경한다고 해서, 전에 사용하던 API를 바로 지우면, 그전 API를 사용하는 사람들이 급격하게 코드를 바꿔야 한다. 반대로 지우지 않고 2개의 API를 둔다면, 전 API를 사용하던 사람이 코드를 고치지 않을 뿐 아니라, 새로 코드를 작성하는 코드에도 사용하지 않아야 하는 API를 사용하게 돼서 결국 2벌의 코드를 유지 보수해야 한다. 이런 상황에서 사용하는 것이 deprecated annotation이다.

     위의 예제와 같이 deprecated annotation을 붙이면 해당하는 함수를 호출하는 코드를 컴파일할 때, 컴파일은 되지만 경고 메시지를 출력해준다. 게다가 내부적으로 Java의 Deprecated annotation을 더해주기 때문에, 자바 코드에서 deprecated 된 스칼라 함수를 호출해도 경고 메시지가 출력된다.
     사실 Scala의 deprecated annotation은 Java의 Deprecated annotation과 에러 메시지와 version을 정해줄 수 있다는 것 외에는 큰 차이가 없다. 그렇기에 Scala는 다른 annotation을 더 지원한다.

    deprecatedName

     함수의 type signature를 바꾸는 일만큼, 함수의 type signature는 그대로 두고, 함수 인자의 이름만 바꿔야 하는 일도 많이 생긴다. 그 이유는 다양한데 기존에 사용했던 이름이 부적절했기 때문일 수도 있고, 함수의 동작이 바뀌면서 넘겨야 하는 인자가 변했지만, 우연히 타입은 같은 경우였을 수도 있다.
     Java는 named parameter를 지원하지 않기 때문에 인자의 이름만 변경하는 경우가 문제 되지 않는다. 하지만 Scala처럼 named parameter를 지원하는 언어에서는 함수의 이름을 바꾸는 것도 문제가 될 수 있다. 이럴 때 사용하기 위한 것이 deprecatedName이라는 annotation이다.  deprecatedName을 이용하면 아래와 같이 새 이름과 deprecated 된 이름 모두 쓸 수 있다. deprecated 된 이름을 쓰면 컴파일 시 경고 메시지를 출력하지만, 성공적으로 컴파일 된다.


     처음 코드를 작성할 때 수정할 일이 없도록 코드를 작성하는 것이 가장 좋겠지만, 현실적으로 그런 것은 불가능하다. 그래서 Scala를 이용해 라이브러리를 작성할 때는 deprecated annotation을 이용해서 하위 호환성을 보장하며 코드를 수정하고 적당한 시기에 deprecated 된 코드를 지운다.
     deprecated 된 코드를 오래 들고 있을수록 라이브러리를 사용하는 사람에게는 변화를 반영할 여유를 주게 되지만, 라이브러리를 작성하는 입장에서는 쓸데없는 짐을 지고 가는 것이기도 하다. 그래서 일반적인 라이브러리 개발의 경우 major version up 혹은 minor version up에서 deprecated 된 부분을 지우는 것이 일반적이지만, in-house 개발일 경우 deprecated annotation을 사용하지 않고 바로 코드를 지우기도 한다. deprecated 된 코드를 언제 지우는가는 전적으로 개발자가 프로젝트의 성격을 보고, 라이브러리를 사용하는 목적을 보고 적절히 선택해야 한다.

    Scala annotation