2015의 게시물 표시

[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 으로 표현해 보았다. 하지만 이 코드는 컴파일되지 않는다. IOResult 의 Error , Ok 가 ParseResult 의 Error , Ok 와 겹치기 때문이다. 이를 해결하기 위해서는 다음과 같이 enumerator의 이름을 다르게 하거나 아래와 같이 namespace 를 이용해야 했다. 하지만 enum class 는 enumerator의 이름이 enum class 안으로 한정되기 때문에 이런 복잡한 과정이 필요 없이 그저 enum class 를 선언하여 사용하면 된다. 무엇보다 기존 enum 의 가장 큰 문제는 정수형 변수로 암시적으로 변환되는 약 타입(weak type) 변수라는 것이다. 하지만 enum class 는 정수형 변수로 암시적 변환이 되지 않는다. enum class 를 정수형 변수처럼 사용하려고 하면 컴파일 에러를 발생시킨다. 만약 정수형 변수로 사용하고 싶으면 static_cast 를 이용해...

빈 객체 크기는?

위와 같은 클래스를 생각해 보자. 보통 empty 클래스라고 부르는 이 클래스는 아무런 내부 변수를 가지고 있지 않다. 그렇다면 이 empty 클래스의 크기는 얼마일까? 언뜻 생각해보면, 아무런 멤버 변수가 없으니 그 크기가 0일 것 같다. 하지만 Java, C#, C(이 경우는 struct), C++ 어떤 언어에서도 0이 나오지 않는다. 이는 두 다른 객체가 같은 주소를 가르치는 일이 없도록 하기 위한 것 이다. empty 클래스는 보통 32 bit 환경에서는 1 바이트 크기를 가지고, 64 bit 환경에서는 2 바이트 크기를 가진다. 하지만 정확히 어떤 값이 나오는지는 알 수 없다. 스펙에 따르면 크기가 0이 되지 않기만 하면 된다. 정확한 크기는 구현체에 따라서 다르다.

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 대신해서 사용하는...

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 전혀 별개의 것으로 처리한다는 것은 문제다....

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

ES6 Promise 에는 독특한 특징이 있는데, 지난번 글 에서는 설명할 타이밍을 잡지 못해서 그냥 넘어갔었다. 이번에 그 특징에 관해 설명하도록 하겠다. 전에 모나드에 관해서 설명 하면서 모나드의 가장 기본적인 operator 중 하나인 bind operator 는 M[T] 타입의 모나드가 T 타입의 인자를 받아서 M[U] 타입의 값을 리턴하는 함수를 인자로 받아서 M[U] 타입의 모나드로 타입을 진행시킨다 1) 고 하였다. 하지만 ES6 Promise 의 then 함수 에 관해서 설명하면서 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 함수가 두 가지 일을 한다. 동적 타입 언어의 특징을 최대한 활용한 것이다...

[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인 Promise 의 resolver 가 호출되면 이 Promise 는 resolved state가 되고, resolver 의 인자를 값으로 지닌다. 반대로 pending state인 Promise 의 rejecter 이 호출되었다면 이 Promise 는 rejected state가 되고, rejecter 의 인자를 Promis...

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

이미지
프로그래밍할 때 가장 어렵고 복잡한 일 중 하나가 비동기적인 코드를 안전하고, 읽기 쉽게 작성하는 것이다. Promise 는 이에 대해서 간단한 해결책을 제시한다. Promise 는 코드가 성공적으로 실행되었을 때의 값을 가지고 있거나, 코드가 실패했을 때 실패한 이유를 가지고 있다. 그래서 보통 Promise[T, E] 로 표현된다. 이는 기본적으로 Try 와 비슷하다. Try 와 차이는 Promise 는 그 객체가 생성되었을 때, 아직 연산이 끝났는지 알 수 없다. 코드가 비동기적으로 실행되기 때문이다. 코드가 비동기적으로 실행되기 때문에 Promise 에 bind operator를 통해서 타입을 진행시키는 일은 기본적으로 일을 예약하는 것이다. 이 일은 Promise 가 완료된 뒤 언젠가는 실행이 되지만, 언제 실행될지는 모른다. 이미 완료된 Promise 에 bind 한 콜백 함수가 언제 실행되는지도 모른다. 물론 실질적으로는 구현체에 따라서 언제 콜백 함수가 실행되는지 결정되어 있지만, 언제 실행될지 모른다고 생각하고 사용하는 것이 좋다. 아니 옳다. Promise 는 Option , Try 와 함께 가장 널리 쓰이는 모나드이다. 하지만 다른 두 모나드와는 다르게 구현체마다 인터페이스나 사용법이 다르고 그 특성도 다르다. 코드를 비동기적으로 실행시키는 것은 사용하는 언어나 플랫폼에 크게 의존하기 때문이다. 하지만 Promise 가 아직 완료되었는지 알 수 없는 일을 한 번 감싼 타입이라는 것만 잊지 않으면, 어떤 구현체라도 어떻게 사용해야 하는지 쉽게 이해할 수 있다. 어떤 경우에는 Future 라고 불리기도 하는데, 기본적으로 이 둘은 같은 일을 하기 위한 것이니 Promise에 대해서만 이해해도 딱히 문제없다. 굳이 차이를 두자면 Future 는 이미 생성된 모나드를 완료시키지 못하는 read-only Promise 라는 정도의 차이가 있을 뿐이다.

[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 함수를 통해서만 생성할 수 있고, 생성자를 통해서 만들 수 없다. 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) 함수는...

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

JavaScript 함수 선언의 가장 큰 특징은 함수의 선언 위치에 상관없이 언제나 코드의 가장 위에서 함수를 선언한 것처럼 코드가 실행된다는 것이다. 따라서 아래 두 코드는 사실 같은 코드라고 봐도 된다. 이를 function hoisting 이라고 한다. 이 덕분에 함수 선언문보다 앞에서 함수를 사용할 수 있다. 하지만 함수 선언은 언제나 스코프의 가장 윗부분으로 hoisting 된다. 따라서 함수 안에서 선언된 함수는 함수 내에서 언제나 같은 함수를 의미했고, 특정 block 안에서는 다른 함수를 의미하도록 사용할 수 없었다. 하지만 ECMAScript 6에서는 block 단위의 함수 선언을 허용한다. 즉, 위와 같이 if block 안에서만 다른 값을 의미하도록 하는 것이 가능하다. 하지만 아쉽게도 이는 아직 대부분 브라우저나 node.js에서는 구현되지 않았다 . 따라서 블록 단위 함수 선언을 사용하려면 babel.js 를 사용해야 한다.

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

자바스크립트는 두 가지 방식으로 함수를 선언할 수 있다. 평범하게 방법은 함수를 선언하여 사용할 수도 있고, 익명함수를 만들어 사용할 수도 있다. ECMAScript 5 까지는 어떻게 만들어지든 둘 사이에는 차이가 없었다. 만들 수 있는 위치나, 함수의 선언 및 할당이 실제로 이뤄지는 위치가 다르기는 하지만, 어쨌든 만들어지고 난 다음에 둘은 아무런 차이가 없었다. 위와 같은 코드는 사실 아래와 같은 코드에 syntax sugar일 뿐이다. 하지만 ECMAScript 6 이후로 둘은 name property라는 다른 점을 가진다. 첫 번째 방식으로 만든 함수는 named function이라고 불리며 name 이라는 프로퍼티를 가진다. 반면에 두 번째 방식으로 만들어진 함수는 anonymous function이라고 불리며 길이가 0인 문자열( "" )을 name property로 가진다.

[ECMAScript 6] fat arrow function

fat arrow function( => )는 ECMAScript 6 에 추가된 익명 함수를 생성하는 새로운 방법이다. 기존의 함수를 만드는 것보다 짧게 함수를 만들 수 있다. 이는 다른 함수에 콜백으로 함수를 넘겨야 하는 경우 요긴하게 사용된다. 하지만 단순히 길이가 짧다는 것만이 arrow function의 장점이 아니다. 오히려 중요한 특색은 arrow function은 this 를 lexical scope 에서 찾는다는 것이다. JavaScript 함수의 가장 큰 특징 중 하나는 this가 dynamic binding 된다는 것이다. 보통은 큰 문제가 되지 않지만, 메소드를 콜백으로 넘겨주어야 하는 경우나 메소드를 변수로 받을 경우 원하는 대로 돌아가지 않았다. ECMAScript 5 에서는 Function에 bind 메소드 가 추가되어 원하는 오브젝트를 this 로 바인드 한 함수를 만들 수 있지만, 매번 이런 작업을 하는 것은 귀찮은 일이다. 이제 ECMAScript 6에서는 이런 경우에 => 을 이용하여 this 가 lexical binding 된 함수를 만들면 된다.

[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가 동작하지 않는다. 이는 TypeS...

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

이미지
현대의 대부분의 언어는 예외 처리를 위해서 try-catch 시스템을 사용한다. 예외가 발생할 수 있는 코드를 try 블록에 집어넣고, 예외를 throw 하면 catch 블록에서 예외를 잡아서 처리하는 방식으로, 사실상 현대의 언어들이 예외를 처리하는 방식의 de facto라고 할 수 있다. 하지만 try-catch 시스템에는 여러 가지 문제가 있다. 우선 다른 함수를 호출할 때, 어떤 예외가 발생할지 모른다. 그래서 Java 같은 언어는 함수의 시그니쳐에 발생 가능한 예외를 적는 checked exception 이라는 개념을 만들었지만, RuntimeException 은 어떤 예외가 발생할지 모른다거나, 모든 예외를 하나하나 등록하기 귀찮아서 그냥 Exception 이 발생한다고 적거나 하는 이유로 그다지 쓸모없다는 인식이 강하고 C#을 비롯한 다른 언어들에서는 사용되지 않는다. 그저 API 문서에 함수가 어떤 예외를 발생시킬 수 있는지 적을 뿐이다. 게다가 try-catch 시스템은 예외를 던지는 것이기 때문에 컨트롤 플로우가 뛰게 된다. 물론 현대 언어에서는 클로져나 람다 함수가 자주 사용되기 때문에 컨트롤 플로우가 직선적으로 흐르지 않는다. 하지만 try-catch 시스템은 도가 지나치다. 예외를 던지면, 예외를 잡을 때까지 컨트롤 플로우가 거슬러 올라간다. 그래서 try-catch에 의한 예외 시스템을 가독성이라는 측면에서 goto 나 setjmp / longjmp 와 다를 게 없다고 비판하는 사람들도 있다. 이에 비해 Try 모나드를 사용한 예외처리는 좀 더 예측할 수 있고 가독성 있는 코드를 작성할 수 있게 해준다. Try 모나드는 Option 과 마찬가지로 두 모나드의 sum type 이다. 하지만 하나의 타입 파라미터를 받는 Option 과 다르게 타입 파라미터를 두 개 받는다. 이 두 타입은 각각 성공했을 때 결과 타입인 T 와 에러가 발생했을 에러 타입인 E 다. 그래서 Try 모나드는 Try[T, E] 로 표현한다...

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

팬텀 타입 은 받은 타입 파라미터 중에서 구조체의 선언에 기여하지 않는 타입 파라미터가 존재하는 타입을 말한다. 무언가 존재하는 것 같지만, 만져지는 실체가 없는 것이 유령 같다고 하여 팬텀 타입이라고 불린다. 내가 알기로는 Haskell에서 가장 먼저 도입된 것으로 알고 있다. Haskell이 팬텀 타입을 사용하기 시작한 뒤로 파라메트릭 폴리몰피즘 을 중시하며 강타입 타입 시스템 을 가진 Scala나 Rust 같은 다른 언어에서도 사용된다. 보통 팬텀 타입을 사용하는 이유는 런타임 오버헤드 없이 컴파일 타임에 제약 조건을 추가하기 위해서다. 팬텀 타입에 사용된 타입 파라미터는 구조체의 값으로 사용되지 않기 때문에 구조체의 크기를 증가시키거나, 실행 시에 별도의 정보를 더 들고 다니지 않는다. 그저 컴파일 타임에 타입 체크하는 조건을 강화할 뿐이다. 이에 대한 좋은 예제가 rust by example에 있어서 가지고 왔다. 1) 위의 예제는 Length 라는 구조체를 단위를 타입 파라미터로 받는 클래스로 선언하였다. 하지만 단위에 해당하는 Unit 은 Length 구조체의 어떤 값으로도 사용되지 않는다. 따라서 Length 구조체의 크기는 f64의 크기와 같다. 2) 하지만 Length<Mm> 는 Length<Inch> 와 다른 값이기 때문에 Length<Mm> 타입인 값과 Length<Inch> 타입인 두 값을 더할 수 없다. 1) 예제 코드는 Apache 2.0 라이센스로 배포되는 rust by example 에 나오는 예제를 가지고 왔다. 2) rust는 구조체에 기여하지 않는 타입 파라미터를 만들 수 없어 PhantomData 를 이용했다. PhantomData는 size가 0인 구조체이다.

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

>const 는 >let 과 같이 ECMAScript 6 에 도입된 block 단위 상수 선언문이다. 중복으로 선언할 수 없고, 선언 전에 사용할 수 없다는 것은 >let 과 같다. 거기에 >const 는 추가적인 제약이 더 붙는다. 우선 >const 로 선언된 이름에는 값을 재할당할 수 없다. 이는 문법적으로 에러로 처리한다. 따라서 >const 에 값을 할당하는 구문을 실행할 때 발생하는 것이 아니라 >const 에 값을 할당하는 구문이 있는 함수가 선언될 때 에러가 발생한다. 또한, >const 를 이용해서 상수를 선언할 때는 언제나 값을 초기화해주어야 한다. >const 로 선언된 상수에 값을 할당하지 못한다는 것을 생각하면, 당연한 일이다. 이 또한 문법 에러로, 초기화하지 않는 >const 를 선언할 때가 아니라, 선언하는 구문이 있는 함수를 선언할 때 에러가 발생한다. 하지만 >const 도 상수 선언을 위한 완벽한 해결책은 아니다. >const 로 선언한 상수에는 값을 재할당할 수 없지만, 상수임에도 불구하고 값을 변경시킬 수 있기 때문이다. >const 로 선언한 상수는 어디까지나 값의 재할당을 막을 뿐, 그 값을 보호해주지 않는다. >const가 완벽한 해결책인 것은 아니지만 , 한계를 알고 적절하게 사용하면 좀 더 안정적이고 가독성 있는 코드를 작성할 수 있다.

[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 을 지...

[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 은 두 상태를 다른 타입으로 분리함으로써...

[JavaScript] undefined 이해하기

JavaScript에는 3가지 undefined가 존재한다. 타입으로서의 undefined 우선 undefined 는 타입이다. ECMA Script 5까지는 다음과 같은 6가지 1) Built-in type이 있었다. number boolean string object null undefined undefined는 그중 하나다. 값으로서의 undefined undefined 는 undefined 타입의 유일한 값이다. 우선 값이 할당되지 않은 변수 혹은 값이 할당되지 않은 프로퍼티는 undefined 가 된다. 또한, 리턴문이 없는 함수나 리턴하는 값이 없는 리턴문으로 끝나는 함수의 실행 결과도 undefined 가 된다. 혹은 전역 프로퍼티인 undefined 를 통해서 undefined 값을 얻을 수 있다. 전역 프로퍼티 undefined global context에는 undefined 라는 이름의 프로퍼티가 설정되어 있다. 이 undefined 는 undefined 인 값을 가진다. ECMAScript 5 이후로 이 프로퍼티는 non-configurable이고, non-writable로 설정되어 있다. 따라서 변경할 수 없다. 1) ECMAScript 6에는 Symbol type 이 추가되었다.

Monad는 무엇인가

모나드는 하스켈의 성공(?)과 함께 다른 언어에도 유행처럼 퍼져나갔다. 하지만 그 배경이 되는 이론이 너무 복잡하고 수학적이라 많은 사람이 하스켈을 맛만 보다 떠나게 된 이유가 되었고, 다른 언어들에서도 모나드라는 것은 금지어가 되다시피 하였다. 하지만 어렵다고 모른 채로 살기에는 모나드는 너무 유용하다. 아니 유용한 정도가 아니라 이미 많은 곳에 사용되고 있다. 그리고 사실 모나드는 쉽다. 아니 어렵긴 어렵다. 그 배경 지식인 카테고리 이론은 어려운 것 맞다. 하지만 그걸 알 필요 없다. 언제는 프로그래머들이 함수가 무엇인지 수학적으로 이해하고 사용하였나? 카테고리론을 이해하지 못해도 모나드는 얼마든지 사용할 수 있다. 이제 슬슬 모나드가 무엇인지 궁금해졌을 것이다. 이쯤에서 한 문장으로 정리해서 설명했으면 좋겠지만, 아쉽게도 그건 좀 어렵다. 아마 이런 모습도 사람이 모나드를 이해하기 어려워하는 이유일 것이다. 그래도 최대한 풀어서 설명하면 모나드는 다음과 같다. 모나드는 다른 타입을 인자로 받는 타입이다. 모나드는 타입이다. 기본적으로 모나드는 int나 string 같은 타입이다. 다른 점이 있다면, 모나드는 타입을 인자로 받는다. C++에 익숙한 사람이라면, template class 를 다른 generic class 를 지원하는 언어에 익숙한 사람이라면 generic class를 생각하면 된다. 편의에 따라 앞으로 T 라는 타입을 받은 모나드 M 을 M[T] 라고 표현하겠다. 모나드 타입의 값을 생성하는 함수가 있어야 한다. 모나드는 임의 타입의 값을 받아서 그 타입을 인자로 받은 모나드 타입의 값을 반환하는 함수가 있어야 한다. 다시 말하면, T 타입의 값을 받아서 M[T] 타입의 값을 반환하는 함수가 있어야 한다. 이는 하스켈에서 return operator라고 불리는 함수이다. 혹은 unit operator라고 부른다. 다른 모나드 타입으로 진행하는 함수가 있어야 한다. 이는 하스켈에서는 >>= 라고 쓰이...

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

npm publish 라는 명령어를 통해 내가 만든 라이브러리를 npm 을 통해 배포할 수 있다. 보통의 경우라면 문제없다. 하지만 TypeScript 나 CoffeeScript 를 이용하여 컴파일된 라이브러리를 배포하거나, webpack 같은 것을 이용해서 라이브러리들을 패킹해서 배포할 경우 npm publish 를 하는 것은 마음 놓고 할 수 있는 작업이 아니다. 배포 전에 명령어를 수행하기 위해 prepublish에 스크립트 를 저장하거나, npmignore 에 배포하지 않을 파일들을 추가하거나 하는데, 이런 것들이 제대로 되어 있는지 실제 npm에 올리기 전에는 알 수 없기 때문이다. 그럴 때 사용하기 좋은 커맨드가 npm pack 이다. npm pack 을 이용하면, 제대로 된 파일들을 배포할지 확인할 수 있다. npm pack 을 실행하면 prepublish를 실행시키고, npmignore에 들어있는 파일들이 빠진 파일들이 {라이브러리 이름}-{버젼}.tgz 라는 이름의 압축파일이 만들어진다. 그러면 해당 라이브러리를 사용하는 프로젝트를 만들고, npm install {압축파일 경로} 를 실행하면, 실제로 publish된 라이브러리를 설치한 것처럼 라이브러리를 설치하여 테스트할 수 있다.

[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을 사용하지 않는다.

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 전에 컴파일하여 자바스크립트 파일만 배포한다. 원본 소스를 보고 싶으면, 깃헙 리파지토리 를 보길 바란다.

타입스크립트의 단점

지난번 글 에서 너무 타입스크립트를 사용하면서 얻게 되는 장점만 말한 것 같아서 이번 글에서는 타입스크립트를 사용하면서 맞게 되는 단점들을 말해보도록 하겠다. 타입스크립트의 단점은 명확하다. 타입스크립트와 자바스크립트를 섞어서 쓸 수 있다는 점이다. 타입스크립트로 컴파일한 코드는 자바스크립트가 되기 때문에 타입스크립트로 작성한 모듈을 자바스크립트에서 불러올 수 있다. 하지만 이렇게 사용한다면, 지난번 글에서 말한 타입스크립트를 사용하는 장점 중 하나인 타입 체크를 위한 verbose 한 코드를 작성하지 않아도 되는 장점이 사라진다. 자바스크립트에서 사용될 것을 가정하고 코드를 작성할 경우는 여전히 verbose 한 타입 체크 코드를 작성해야 한다. 이것은 내가 자바스크립트 코드를 타입스크립트를 포팅하면서 딱히 좋은 점을 느끼지 못했던 이유 이기도 하다. 하지만 이는 자바스크립트를 사용했다면 언제나 발생했을 문제가 타입스크립트를 사용할 때 다시 발생하는 것뿐이다. 자바스크립트를 사용해야 하는 환경에서 타입스크립트를 사용하지 않을 이유는 되지 않는다. 이번에는 반대로 타입스크립트를 사용하면서 자바스크립트로 작성된 모듈을 불러오는 경우를 보자. 타입스크립트에서는 자바스크립트의 모듈을 그대로 가져다 쓸 수 있다. 덕분에 타입스크립트 생태계는 크게 노력하지 않고 자바스크립트의 생태계를 흡수할 수 있었다. 하지만 이것은 동시에 단점이 되기도 한다. 자바스크립트로 작성된 모듈은 타입 추론을 할 수 없어서 모든 API가 any 타입이 되고, 결국 이 부분이 unsafe 한 부분이 되기 때문이다. 이런 문제를 해결할 수 있도록 타입스크립트는 모듈의 타입만 분리해서 선언하는 선언 파일을 사용할 수 있게 해놓았다. 선언 파일과 함께 사용하면 자바스크립트로 작성된 모듈을 사용할 때도 잘못된 타입을 사용하면 타입 에러를 내준다. 물론 선언 파일을 만드는 것도 비용이다. 하지만 이는 큰 문제가 되지 않는다. 유명한 라이브러리에 대해서는 이미 DefinitelyTyped...

TypeScript와 함께 한 4개월

내가 타입스크립트 를 처음 쓰게 된 것은 올해 3월이었다. 당시에 알바를 하고 있던 회사에서 작성하던 서버 사이드 자바스크립트 코드의 안정성을 향상하기 위해 타입스크립트로 포팅하는 일을 시작하였고, 당시 그 팀의 구성원은 전원 너무 바빴기 때문에 다른 일을 하던 내가 불려가서 포팅하게 되었다. 사실 처음에 포팅을 하기로 했을 때는 흥미로운 일이라고 생각하지만, 딱히 의미 있는 일이라고는 생각하지 않았었다. 당시 코드는 이미 뼈대에 해당하는 부분이 대부분 완성되어 있었고, 그 대부분은 이미 타입의 개념이 없이 짜인 코드였었다. 그래서 단순히 타입스크립트로 옮겨도 별 이득이 없을 것이고, 완벽하게 포팅하는데 들어가는 노력에 대비해서 안정성을 확보할 수 있을지에 대해 확신이 없어서였다. 그래도 돈을 받고 하기로 한 일이었으므로 작업은 시작하였다. 우선 이미 구현되어있는 코드가 너무 많았기 때문에 초반에는 손에 닿는 파일부터 타입스크립트로 변환하며 작업을 하였다. 그 뒤로 한 3개월은 코드를 바꾸는 일만 했다. 하지만 이때까지는 타입스크립트의 장점을 딱히 느낄 수 없었다. 변환하는 과정에서 몇몇 버그를 잡았지만, 이미 타입이 중요한 부분은 underscore.js 를 이용해서 타입체크를 하고 있었기에 타입스크립트가 추가로 해주는 일이 거의 없었다. 변환하는 과정에서 몇몇 버그를 발견하기는 했지만, 충분한 유닛 테스트로 잡을 수 있는 버그들이었기에 딱히 타입스크립트를 사용해야 한다고 느끼지 못했다. 오히려 리팩토링에 너무 많은 시간이 들었기 때문에 비용 대비 효용이라는 측면에서 비효율적이라고 느꼈다. 그러다가 타입스크립트에 대한 인식이 바뀌게 된 것은 다음 프로젝트를 시작하면서부터였다. 다음 프로젝트를 시작하게 된 것은 지난 5월 말이었으니 거의 한 달 정도 전이다. 하게 된 일은 기존의 스칼라 로 작성되었던 beyond 프레임워크 를 node.js 에서 돌도록 포팅한 beyond.ts 를 만드는 것이었는데, 이번 프로젝트는 처음부터 타입스크립트를 이용...

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

내가 병특을 시작했던 회사에서 있었던 일이다. 그 회사는 그냥 흔한 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년 전에 좋은 코드였다고 하더라도 지금 기준에서 좋은 코드라는 보장...

[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을 사용하기도 한다. 부하 분산 서버별로 다른 슬레이브에서 값을 읽게 한다 혹은 쿼리를 분산시키기 위한 목적으로도 사용된다. 대부분의 웹 서비스는 데이터의 변경에 비해서 데이터를 읽는 작업이 많다....

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

이미지
지난번 글 에서는 replication이 무엇인지 알아보았다. 이번에는 MySQL replication이 어떻게 동작하는지 살펴볼 것이다. replication은 다음과 같은 순서로 진행된다. 마스터 데이터베이스가 binary log를 만들어 이벤트를 기록한다. 각 슬레이브는 어떤 이벤트까지 저장되어 있는지를 기억하고 있다. 슬레이브의 IO thread를 통해서 마스터에 이벤트를 요청하고 받는다. 마스터는 이벤트를 요청받으면 binlog dump thread를 통해서 클라이언트에게 이벤트를 전송한다. IO thread는 전송받은 덤프 로그를 이용하여 relay log를 만든다. 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를 읽고 바로 락을 해제한다. 마스터는 슬레이브에 대...

[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을 사용한다.

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

이미지
https://www.reddit.com/r/ProgrammerHumor/comments/1rfstw/there_are_two_types_of_people/ 위의 meem에서 알 수 있듯이 프로그래머는 괄호를 같은 라인에 붙여 쓰는가 띄어 쓰는가 하는 별 중요하지 않은 것으로 끊임없이 논쟁을 벌이고 있다. 여기에 조건문뿐 아니라 함수의 선언에 괄호를 어디에 붙이는가 까지 해서 4가지 조합을 가지고 끊임없이 싸운다. 뭐 나는 개인적으로 함수의 선언이나 조건문에 붙는 괄호를 한 라인에 붙여 쓰는 걸 선호하지만, 그에 대해서 딱히 내 의견을 강요하지 않는다. 그냥 프로젝트에서 기존에 쓰이던 것이나, 다른 팀원들이 원하는 스타일을 따른다. 하지만 괄호에 관해서 절대 양보 못 하는 것이 하나 있다. 한 줄짜리 statement를 위해서 괄호를 사용할 것인가 말 것인가 하는 것이다. 이유를 알 수 없지만, 조건문이나 for 문에 한 줄짜리 statement가 들어갈 일이 있으면, 괄호를 생략하고 쓰는 사람들이 많다. 괄호를 생략하는 사람들은 이것저것 이상한 주장을 한다. 쓸데없이 바이트를 낭비한다거나, 오히려 한 줄짜리 코드라는 것을 명시해주어야 한다거나, 이유 없이 타이핑할 이유가 없다거나, 뭐 이것저것 이유를 대는데 전부 20세기라면 의미 있을지도 모르지만, 지금이라면 전혀 의미 없는 이유다. 21세기에는 괄호를 생략할 이유가 전혀 없다. 오히려 괄호를 생략해서는 안되는 절대적인 이유가 있다. 코딩할 때 언제나 버젼 컨트롤 시스템을 사용하기 때문이다. git을 사용하든 머큐리얼을 사용하든 심지어 subversion을 사용하든 상관없지만 어찌 됐든 코딩할 때는 언제나 버젼 컨트롤 시스템과 함께하며 소스의 변경을 추적한다. 이때, 괄호를 생략했던 한 문장의 코드가 여러 줄로 나누어지면 괄호를 해서 불필요한 변경사항이 두 코드의 diff에 나오게 된다. 이러한 불필요한 변경 이력이 코드에 나오는 것을 막기 위해서 한 줄의 코드에도 반드시 괄호를 써...

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++컴파일러로 컴파일 안 될 수도 있다고 했더니, 거기에 달린 답변은 더 황당했다. 표준이 문제가 아니라 잘 ...

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

Scala에서 여러 개의 값을 묶어서 새로운 타입을 정의하는 방법은 여러 가지가 있다. Type alias 가장 쉬운 방법은 Tuple 의 alias를 만드는 것이다. 위의 코드의 4번째 줄에서 month와 date를 선언하면서 Date 타입의 변수를 쪼개 새로운 값에 할당하는 것을 decomposition이라고 한다. 이렇게 decomposition을 이용하면 구조체의 값들을 쉽게 다른 값에 할당할 수 있고, 혹은 아래와 같이 패턴매칭을 이용해서 사용할 수 있다. 하지만 튜플의 alias를 만드는 방식은 큰 문제가 있다. 이런 방식은 타입 세이프 하지 않다. 예를 들어 위에서 정의한 Address 타입과 함께 아래와 같이 정의된 Date 타입이 같이 사용된다면 둘 다 실제로는 같은 Tuple2[String, Int] 타입이기 때문에 패턴매칭으로는 Address 와 Date 를 구분할 방법이 없다. case class 튜플의 alias가 타입 세이프 하지 않다는 문제를 해결하기 때문에 보통은 data composition에 case class 를 사용한다. case class 를 사용하면, 실제로는 다른 같은 타입들의 묶음과 구분할 수 있을 뿐 아니라, 내부 값에 이름으로 접근해 꺼낼 수 있어서 내부 값을 더 쉽게 읽을 수도 있다.

[C++] Object slicing

Object slicing 이란 상속받은 class의 instance를 부모 class의 instance로 복사함으로써 상속받은 class가 가지고 있던 정보가 손실되는 것을 말한다. 이는 기능이 아니라 stack에 값을 할당하는 value 타입의 특성 때문에 생기는 버그다. 그래서 heap에 값을 할당하는 reference 타입밖에 없는 Java 같은 언어에서는 발생하지 않는다. Object slice 때문에 value type에 대해서는 upcasting을 해서는 안된다. 대부분 upcasting이 필요한 경우는 이미 무언가 잘못된 경우이니 코드를 수정해야 한다. 만약 무슨 일이 있어도 upcasting을 해야 한다면 반드시 heap에 값을 할당해야 한다.

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에서 We...

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...

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 , Rubinius , Lua interpreter 등 많은 인터프리터 구현체들이 GIL을 사용하고 있다. 그 이유는 우선 GIL을 이용한 multi-threads를 구현하는 것이 parallel 한 multi-threads를 구현하는 것보다 훨씬 쉽다는 것이다. 게다가 이런 parallel 한 multi-threads 구현체들의 문제는 싱글 쓰레드에서 오히려...

[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에 들어갈 예정 이었다. 하지만 디자인 이슈를 ...

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

크게는 구조의 변경을 위해서부터, 작게는 오타 수정까지 API를 수정할 일은 많이 있다. 하지만 API를 변경하는 것은 복잡하고, 오래 걸리고, 어려운 일이다. deprecated API를 변경한다고 해서, 전에 사용하던 API를 바로 지우면, 그전 API를 사용하는 사람들이 급격하게 코드를 바꿔야 한다. 반대로 지우지 않고 2개의 API를 둔다 면, 전 API를 사용하던 사람이 코드를 고치지 않을 뿐 아니라, 새로 코드를 작성하는 코드에도 사용하지 않아야 하는 API를 사용하게 돼서 결국 2벌의 코드를 유지 보수해야 한다. 이런 상황에서 사용하는 것이 deprecated annotation 이다. 위의 예제와 같이 deprecated annotation을 붙이면 해당하는 함수를 호출하는 코드를 컴파일할 때, 컴파일은 되지만 경고 메시지를 출력해준다. 게다가 내부적으로 Java의 Deprecated annotation 을 더해주기 때문에, 자바 코드에서 호출해도 경고 메시지가 출력된다. 사실 Scala의 deprecated annotation은 Java의 Deprecated annotation과 에러 메시지와 version을 정해줄 수 있다는 것 외에는 큰 차이가 없다. 그렇기에 Scala는 다른 annotation을 더 지원한다. deprecatedName 함수의 type signature 를 바꾸는 일만큼, 함수의 type signature는 그대로 두고, 함수 인자의 이름만 바꿔야 하는 일도 많이 생긴다. 그 이유는 다양한데 기존에 사용했던 이름이 부적절했기 때문일 수도 있고, 함수의 동작이 바뀌면서 넘겨야 하는 인자가 변했지만, 우연히 타입은 같은 경우였을 수도 있다. Java는 named parameter 를 지원하지 않기 때문에 인자의 이름만 변경하는 경우가 문제 되지 않는다. 하지만 Scala처럼 named parameter를 지원하는 언어에서는 함수의 이름을 바꾸는 것도 문제가 될 수 있다. 이럴 때 사용하기 위한 것...

이 블로그의 인기 게시물

USB 2.0 케이블의 내부 구조

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

Log Aggregator 비교 - Scribe, Flume, Fluentd, logstash

[Web] SpeechSynthesis - TTS API

[Python] cache 데코레이터로 최적화하기