1월, 2014의 게시물 표시

RAII는 무엇인가

RAII 는 C++에서 자주 쓰이는 idiom으로 자원의 안전한 사용을 위해 객체가 쓰이는 스코프를 벗어나면 자원을 해제해주는 기법이다. C++에서 heap에 할당된 자원은 명시적으로 해제하지 않으면 해제되지 않지만, stack에 할당된 자원은 자신의 scope가 끝나면 메모리가 해제되며 destructor가 불린다는 원리를 이용한 것이다. 원래는 exception 등으로 control flow가 예상치 못하게 변경될 때를 대비하기 위해서 쓰이던 기법이다. 아래의 예제를 보자. 첫 번째 코드는 위험하다. thisFunctionCanThrowException() 함수에서 exception을 발생시킨다면 resource를 해제하지 못한다. 두 번째 코드는 일단은 resource 를 해제하고 있다. 하지만 보기에 좋은 코드는 아니다. 그리고 유지하기도 어려워진다. 세 번째 코드는 RAII를 위해 c++ 11의 스마트 포인터인 unique_ptr 을 이용하는 방법이다. unique_ptr 은 소멸할 때 자신이 들고 있는 메모리를 해제시켜주기 때문에 함수 밖으로 나가면 resource 가 해제되는 것이 보장된다. 여기서 말하는 자원은 단순히 heap 메모리만을 말하는 것이 아니다. heap 메모리 이외에도 파일이나 database connection과 같은 것들도 전부 RAII를 이용해 안전하게 사용할 수 있다. 여기에 더 나아가서 특정 scope를 벗어나면 반드시 실행돼야 하는 코드들도 RAII를 이용해 처리할 수 있다. 즉, 다른 언어에서 finally 에 해당하는 구문을 RAII를 이용해서 처리할 수 있다. 실제로 C++의 아버지이자 RAII라는 용어를 처음 만든 Bjarne Stroustrub는 c++에 finally 를 집어넣지 않는 이유를 "RAII가 있는데 굳이 있을 필요가 없다." 라고 말하고 있다.

잘못된 assert는 사용하지 말자

assert라는 함수는 인자로 받는 조건이 true가 아니면 예외를 발생시키는 함수로, 디버깅을 도와주거나, 코드의 가독성을 올리는 용도로 쓰인다. 게다가 릴리즈에서는 아무 일도 않기 때문에(c++의 경우 NDEBUG가 정의되었는가 아닌가로 동작이 달라진다.), 성능 저하 없이 검증 코드를 집어넣을 수 있다. 근데 릴리즈에서는 아무 동작도 하지 않는다는 특성 때문인지, assert가 가지는 implicit 한 의미를 이해 못 했는지 assert를 잘못 사용하는 때도 있다. 다음의 코드를 보고 이상한 점을 찾아보자. Connection이라는 class의 Send method에서 message를 받아서 platformConnection_(내부 구현이 어떻게 되었을지는 신경 쓰지 말자.)을 통해서 Send해주고 결과 값을 돌려준다. 그 전에 platformConnection_이 valid한지를 검사하여 valid하지 않다면 false를 돌려주는 평범한 코드다. 문제는 함수의 맨 앞에서 valid한지 아닌지 assert로 체크를 하고 있다는 것이다. 이 assert문이 가지는 의미는 절대로 valid하지 않을 때는 절대로 이 함수가 호출되지 않는다는 것이다. 그럼에도 그다음 문장에서 valid한지 않은지 검사하고 있다. 이에 대해 assert는 릴리즈 빌드에는 포함되지 않기 때문에 릴리즈 빌드를 위해서 안전한 코드를 작성해야 한다고 주장하는 사람도 있다. 하지만 이런 경우라면 애초에 assert를 쓰지 않고 조건문만을 써야 한다. assert를 썼다면 assert에 걸릴만한 조건이 들어오지 않도록 코드를 작성했어야 한다.

테스트 얼마나 만들어야 할까?

에자일한 소프트웨어를 만드는 방법의 하나로 TDD 에서는 test를 먼저 작성하는 것을 권장? 혹은 강요한다. 스펙에 맞춰 테스트를 하나하나 만들어가고, 그 테스트 케이스들을 초록색으로 만드는 것을 반복해 나가다 보면 에러 없는 코드가 완성된다는 것이다. 이 방법론에 대해 비난할 생각은 없다. 매우 좋은 방법이다. 근데 정말로 언제나 해야 하는 걸까? 부작용은 없을까? 내가 이것들에 대해 고민하게 된 것은 TDD를 처음 배우고 Rails를 사용하고 있던 3년 전 일이다. 그전에 했던 프로젝트가 C++로 서버를 만드는 일이었고 사소한 예외처리 몇 개를 실수해서 몇 일 밤을 새웠던 직후라서 더욱더 테스트에 매달리게 되었다. 다행히도 rails는 유닛테스트에 특화된 framework이었고 (당시에 DB까지 unit test가 가능하도록 지원해주는 framework이 얼마 없었다.), TDD의 원칙에 따라서 많은 테스트와 함께 코드를 작성해 나갔다. 그렇게 가끔 테스트가 없었으면 크게 삽질했을 버그를 잡아가면서, 그 프로젝는 내가 질릴 때까지 별문제 없이 진행되었다. 여기까지만 보면 TDD의 긍정적인 사례에 추가될 수 있을 것 같지만, 실상은 전혀 아니었다. 문제는 두 가지가 있었다. 첫 번째는 테스트 작성에 심취해서 개발에 너무 많은 시간이 들었다는 것이고, 두 번째는 너무 빨리 질렸다는 것이다. 당시에는 아직 TDD에 적응이 안 돼서라고 생각했는데, 정도의 차이가 있었지만 2번째 프로젝트에서도 비슷한 현상이 나타났다. 왜 그렇게 된 것일까? 이것에 대해 고민하다가 RubyOnRails의 제작자인 David Heinemeier Hansson의 글 을 보게 되었다. 논쟁이 많이 되었던 글이지만 David가 말하고자 하는 바는 단순하다. 테스트를 작성하는데 들어가는 비용도 기능 구현이나 디버깅에 들어가는 것과 같은 비용이니 overtest하지 않도록 노력해야 한다는 것이다. Unittest라는 도구를 처음 접해 아무 생각 없이 심취해버렸던 나는, test를

이 블로그의 인기 게시물

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

RAII는 무엇인가

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

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

[Web] SpeechSynthesis - TTS API