코드 리뷰

버그가 발생했을 때 아무리 열심히 코드를 쳐다봐도 도통 뭐가 문제인지 모를 때가 있습니다. 특히 자신이 작성한 코드일수록 코드를 꼼꼼하게 보기가 힘듭니다. 마치 자기 글을 자기가 퇴고하기 어려운 것과 마찬가지 원리입니다. 이럴 때 하염없이 코드만 쳐다보고 있으면 시간을 흘러가고 몸은 지쳐가고 정신은 피폐해지며 정확한 원인을 못찾은 채 코드를 이리저리 수정하다 보면 버그를 없애기는커녕 새로운 버그를 추가하고 있는 슬픈 자화상을 마주하게 됩니다.

저는 이럴 때는 문제가 있을 것으로 추측되는 코드를 하드 카피(종이)로 뽑아서 봅니다. 컴퓨터 모니터의 해상도(resolution)가 높아고 가독성이 높은 글꼴[각주 1]을 사용해도 여전히 종이는 가장 우수한 가독성을 뽐내는 미디엄입니다. 자신이 작성한 코드라 할지라도 종이로 뽑아서 보면 전혀 새로운 맛이 있기 때문에 그냥 간과해 버릴 수 있는 부분을 집어낼 수 있었던 경우가 많이 있었습니다. 특히 퇴근하면서 지하철에 앉아 내가 작성한 코드를 찬찬히 읽어보는 것은 코드 리뷰에 아주 큰 도움이 되었습니다.

물론 문제점이 없지는 않습니다. 종이는 ctags[각주 2]처럼 코드를 추적할 수도 없고 특정 문자열을 쉽게 찾아낼 수도 없습니다. 하지만 본인이 작성한 코드의 오류를 찾는 경우에는 이런 부분은 크게 문제가 되지 않기 때문에 괜찮습니다. vim 사용자라면 머리도 식혈 겸 :hardcopy를 치시고 프린트된 여러분의 코드를 마음 껏 파헤쳐 보시기 바랍니다.

[각주 1] 코딩 작업 시 폰트로 BitStream Vera Sans Mono를 추천합니다.

[각주 2] ctags는 코드 분석을 도와주는 도구입니다. 일례로 vim+ctags 조합을 이용하면 함수나 매크로가 정의된 파일로 쉽게 이동할 수 있습니다.

Extended File Attributes

리눅스 관련 프로젝트를 하다가 Extended file attributes라는 개념을 접해서 정리해 놓고자 합니다.

보통 파일 시스템 내의 파일들은 파일의 내용(content) 외에도 여러 가지 메터 정보를 가지게 됩니다. 예를 들어, 파일의 접근 권한(ACLs), 생성 시간(creation time), 최근 변경 시간(modification time) 등의 정보입니다. 보통 UNIX 환경에서는 stat, fstat, lstat 등의 함수를 이용하면 얻을 수 있습니다.

Extended file attributes는 운영체제가 지원하는 이런 기본적인 메터 데이터 외에 사용자가 임의로 파일에 메터 정보를 줄 수 있게 하기 위해서 사용됩니다. 여기에는 보통 파일의 인코딩(encoding) 정보나, 체크섬(checksum), 추가적인 권한 관리 정보(ACLs) 등을 넣을 수 있습니다.

리눅스에서는 ext2, ext3, ReiserFS, XFS 등의 파일 시스템이 EA(Extneded attribute)를 지원합니다. 각 EA는 (name, value)의 짝으로 구성되는데, 예를 들어 /etc/xxx.conf 파일의 작성자를 알고 싶다면 EA의 user.author를 name으로 요청하는 식입니다.

관련된 라이브러리 함수 및 시스템 콜로는 attr_get, attr_set, getxattr, setxattr 등을 찾아보시면 됩니다. EA도 커널의 지원이 필요하므로, 커널을 컴파일할 때 해당 파일 시스템의 옵션을 켜주어야 합니다. ext3의 경우 default로 EA가 활성화되어 있는데, mount 할 때 user_xattr 옵션을 주어야 EA가 동작합니다.

[참고]
1. Linux EA/ACL manual pages

ARM Thumb 모드

ARM(Advanced RISC machines) 프로세서의 Thumb 모드에 대한 글을 써야겠다고 마음 먹고 있다가 시간을 내어 글을 쓰게 되었습니다.

ARM 프로세서는 일반적인 32비트 인스트럭션 셋을 제공하는 ARM 모드와, 16비트 인스트럭션 셋을 제공하는 Thumb 모드가 있습니다. 모든ARM 프로세서가 Thumb 모드를 지원하진 않고 ARM7TDMI처럼 프로세서 이름에 T가 들어있는 제품군이 Thumb 모드를 지원합니다.

이 이야기는 하나의 프로세서가 두 가지 인스트럭션을 제공한다는 뜻인데, 그 이유가 무엇일까요?

RISC 머신은 복잡한 CISC 머신에 비해서, 칩 설계가 간단해지므로 보통 한 CPU 사이클에 하나의 인스트럭션을 수행할 수 있다는 장점이 있습니다. 부족한 부분은 컴파일러 최적화를 활용해서 성능을 개선하는 것이죠. 반대로 CISC처럼 복잡한 인스트럭션이 없다 보니 같은 기능을 구현하기 위해 드는 인스트럭션의 길이가 길어지는 문제점도 있습니다.

ARM이 주로 조그마한 임베디드 시스템에 사용되는 프로세서임을 생각해 본다면 그런 기계들은 메모리도 무척 귀중한 자원임을 알수 있습니다. 하지만 ARM은 RISC 머신이기 때문에 바이너리 크기가 같은 기능을 하는 CISC 프로세서용 바이너리보다 커지는 것이죠.

이 문제를 해결하기 위해서 도입한 것이 Thumb 모드입니다. ARM 인스트럭션 중에 자주 쓰이는 수치 연산, 브랜치, load/store 등의 일부 명령을 16 비트로 표시할 수 있는모드를 만든 것이죠. ARM은 CSPR(Current Program Status Register)라는 현재 상태를 나타내는 레지스터가 있는데 여기 플래그를 하나 두고 현재 ARM 모드인지 Thumb모드인지 구분하게 됩니다.

하지만 하나의 CPU가 두 개의 인스트럭션 셋을 가진다는 것은 그만큼 프로세서가 복잡해진다는 것을 뜻합니다. 이러면 RISC의 기본 정신에 어긋나게 되겠죠. 실제로 Thumb 모드는 별도의 프로세서가 아닌 보조 하드웨어를 거쳐서 ARM 인스트럭션으로 변환되게 됩니다. 이 변환은 하드웨어에서 이루어지기 때문에 별도의 속도 저하가 일어나지 않는 것이죠.

물론 원래 32비트였던 인스트럭션을 16비트로 줄였기 때문에 몇 가지 제약 사항이 생깁니다. 원래 ARM 사용자모드에서는 16+1개 레지스터가 보이는데, 레지스터 어드레싱하는 비트를 줄이기 위해 이를 8+1개로 바꾸었습니다. 대신에 원래 ARM 모드에는 원래 PUSH, POP 명령은 암묵적으로 R13번을 사용하는 식으로 레지스터 어드레싱을 합니다.

ARM과 Thumb 모드는 하나의 바이너리 내에서도 자유롭게 변환이 가능합니다. 즉 C 프로그램을 컴파일한다고 하면 일부 함수는 ARM 모드로, 일부 함수는Thumb 모드로 컴파일이 가능한 것이죠. 물론 디바이스 드라이버나 예외 처리(exception handling) 같이 모든 레지스터와 인스트럭션에 대한 접근이 필요한 경우는 ARM 모드로 해야 하고요. 이런 변환은 보통 BX(Branch, Exchange)나 BLX(Branch, Link and Exchange) 같은 명령으로 이루어지는데 브랜치 할 때 목적 주소(destination address)의 LSB가 0인지 1인지에 따라 결정을 하게 됩니다.

Thumb 모드로 하면 한 가지 부수적인 장점은 데이터 버스를 16비트로 줄일 수 있다는 겁니다. 이 경우 Thumb 모드 인스트럭션은 16 비트로 버스로 보내고, 32비트 ARM 인스트럭션은 16비트 버스로2번 패치(fetch)해서 처리할 수 있죠. 실제데이터 버스는 16비트이지만 32비트로 RISC 머신을 쓸 수 있다는 장점이 있습니다. 대신에 비용을 낮추는 것이지요.

참고 문서
[1] Introduction to ARM thumb By Joe Lemieux, Courtesy of Embedded Systems Design

GLSL

지금까지 프로그래밍 언어(programming language)에 관심을 가졌으면서도 범용적인 언어만 공부하다 보니 특수 목적(special-purpose) 언어에 대한 관심을 깊이 못 가져본 것 같다. 이번에 한 회사 면접 준비를 하면서 GLSL(OpenGL Shader Language)를 조금 살펴봤는데, 범용 언어가 아니더라도 언어/컴파일러 기술이 활용될 수 있는 곳은 여전히 많이 있는 게 아닐까 하는 생각이 들었다.

흔히들 국내에서 프로그래밍 언어/컴파일러 쪽 전공하면 밥 먹고 살기 힘들다는 이야기를 많이 하는데, 이게 참 웃기다. 마이크로소프트나 썬 마이크로시스템즈의 예만 봐도 알 수 있듯이 소프트웨어에서 정말 기반이 되는 기술은 사실 언어와 런타임을 포함한 소프트웨어 플랫폼이기 때문이다. 즉 이걸 안 하면서 IT 강국을 키우겠다는 건, 모든 제품을 사다가 조립만 하는 조립 공장이 되겠다는 이야기와 크게 다르지 않다.

소프트웨어는 결국 하드웨어에 여러 겹 추상화 계층을 씌우는 작업이다 보니, translation은 거의 모든 소프트웨어의 필수적인 요소다. 3D 그래픽스 프로그래밍에 사용되는 Shading 언어도 결국은 그래픽스 칩 벤더가 제공하는 어셈블리 언어의 불편함을 줄이고, 범용적인 인터페이스를 제공하는 데 있다.

국내에 관련 업체가 별로 없다고는 하지만, 그렇게 밥 못 먹고 살만한 분야는 아닌 것 같은데…

UI 디자인

UI 디자인에 대한 잘못된 믿음 중에 하나는 UI 디자인은 프로그래머가 아닌 디자이너의 일이라는 생각이다. 물론 심미적인 부분도 중요하지만 실제로 UI 디자인에서 가장 중요한 것은 사용성이다. 애써 만든 프로그램이 사용하기 불편하고 어렵다면 그야말로 용두사미에 지나지 않는다.

좋은 UI는 사용자에게 필요한 정보만 줘야 한다. 예를 들어 일반적인 모니터 셋업 UI의 경우 사용자는 버튼을 눌러서 밝기, 명도, 색상 등 값을 1-100으로 조정할 수 있게 해준다. 반면에 어떤 모니터는 밝기 모드로 인터넷, 텍스트, 영화 등으로 몇 가지 직관적인 옵션을 제시하고 이 값에 따라 밝기, 명도, 색상을 적절한 값으로 조절해준다. 어느 쪽이 더 친절한 UI일까?

비슷한 예로 WinAmp 같은 mp3 감상 프로그램의 경우도 마찬가지다. 소리에 관한 세부적인 값을 조절할 수 있는 복잡한 컨트롤이 붙어있지만 이 값을 적절히 조절해 원하는 소리를 즐길 수 있는 전문가는 극소수에 지나지 않는다. 노래의 유형에 따라 재즈, 클래식, 팝, 가요 등으로 몇 가지 옵션을 주고 해당 장르에 적절한 셋팅 값을 주는 쪽이 대부분의 사용자에게는 훨씬 직관적이고 편리하다.

물론 좀 더 정교한 조작을 원하는 고급 사용자도 있기 마련이므로 이들 사용자를 위한 컨트롤은 남겨둘 수 있지만, 일반적으로 불필요한 기능을 과도하게 노출시키는 것은 사용하기 힘든 UI의 원천인 셈이다.

Static Type Checking

2004년 12월에 Bruce Eckel의 Strong Typing vs. Strong Testing을 읽고 다음과 같은 글을 쓴 적이 있다.

컴파일 타임에 최대한 많은 에러 체크를 해주는 Strong Typing이 프로그래머의 생산성 향상에 큰 도움을 준다고 많은프로그래머들이 믿는다. Eckel은 이런 관점에 의문을 던지며 Weak Typing 언어라도 Unit Testing을 강화하면프로그래머의 생산성 향상에 도움을 주며 크고 복잡한 소프트웨어를 충분히 설계할 수 있다고 역설한다.

StrongTyping 언어의 컴파일 타임 에러 체크라는 것도 결국 소프트웨어 테스트의 일부분일 뿐이고 이것만으로 프로그램의 정확성을보장할 수 없다. 또 Weak Typing 언어는 컴파일 타임의 에러 체크를 하지 않는 대신 런타임 에러 체크가 강화 되었다.결국 Strong이든 Weak이든 프로그램의 정확성을 보장하기 위해서는 많은 Unit Test Case를 만들어야하고, WeakTyping 언어가 오히려 빨리 정확한 Unit Test Case를 만들기 유리할 수도 있단 거다.

일례로 파이썬과같이 빠른 속도로 프로토타이핑 할 수 있는 언어의 경우 개발 시간을 단축하고 단축된 시간을 이용해 더 많은 Unit TestCase를 짜면 컴파일 타임 에러 체크가 없다는 단점에도 불구하고 프로그램의 기능을 좀 더 명확히 검증할 수 있다는 것이다.

일단 Eckel도 지적했듯이 제목을 “Static Checking vs Strong Testing”으로 바로 잡아야 한다. 컴파일 타임에 타입 검사를 하느냐 아니냐가 기준이 되기 때문이다.

그 당시에는 Eckel의 말이 그럴듯한 논리라고 생각했으나 2년의 시간이 지나고 나서 내 생각에도 많은 변화가 있었다. 특히 과제 연구로 프로그램 분석(program analysis) 쪽을 공부하면서 Eckel의 이런 주장에 대해 프로그래밍 언어 학계는 냉담하다는 사실을 알았다.

프로그래밍 언어 연구는 거의 대부분 좀 더 정교한 타입 시스템을 도입해서 프로그램을 실행해 보기 전에 여러가지 특성(property)을 보장하는데 목표를 두고 있다. 일례로 Race Free Java의 경우 타입 시스템으로 레이스 컨디션(race condition)이 없음을 컴파일 타임에 보장한다.

이런 학계의 경향과는 정반대로 일부 스크립트 언어의 성공으로 상당수의 개발자는 정적 타입 검사가 없는 언어가 생산성이 좋기 때문에 절약한 시간으로 더 많이 테스트할 수 있고 정적 타입 검사하는 프로그래밍 언어만큼 안정적일 수 있다고 생각하고 있다. 실제로 Eckel이 정적 타입 검사 없이도 성공적으로 크고 복잡한 프로그램을 만들 수 있다는 증거로 든 것이 파이썬의 성공이다.

하지만 이런 주장에는 상당한 무리가 있어 보인다. 정적 타입 검사가 모든 가능성을 점검하는 연역적인 검사인데 비해서, 테스트는 귀납적인 접근 방식이기 때문이다. 모든 가능한 타입에 대해서 테스트 케이스를 작성한다는 것은 비효율적이고, 컴포넌트 통합(integration)에 문제를 일으킬 여지가 많다.

또한 정적 타입 검사가 없으면 런타임에 상당한 오버헤드가 생긴다.예를 들어 파이썬과 동적 타입 체크 언어(사실상 untyped 언어)는 메쏘드를 콜 할 때마다 해당 메쏘드가 해당 오브젝트에 정의되어있는지를 확인하고 아니면 런타임 에러를 발생시켜야한다. C/C++의 경우 바이너리를 링크할 때 이미 알 수 있었을 문제이다. 이런 차이는 수행 속도에 큰 영향을 미친다.

파이썬의 성공은 정적 타입 검사의 부재 때문에 가능했던 것이 아니라 정적 타입 검사의 부재에도 불구하고, 다른 여러가지 요소가 복합되어서 이루어진 것이라고 결론을 내는 것이 맞는 것 같다.

HTTP Content-Type Header

웹서핑을 하다보면 종종 한글이 깨진채로 나오는 경우가 있다. 이때 수동으로 인코딩을 UTF-8이나 한국어(EUC-KR) 등으로 바꾸면 페이지가 올바르게 표시되기도 한다.

이 문제는 HTTP 서버 보내는 HTTP Content-Type Header와 관련이 있다.

HTTP/1.1 200 OK
Content-Type: text/html; charset=UTF-8
Content-Length: 7188
Date: Mon, 18 Feb 2002 22:06:32 GMT
Last-Modified: Fri, 10 Aug 2001 17:32:25 GMT

여기서 Content-Type에 명시적으로 문자셋(charset)을 줄 경우 브라우저는 해당 문자셋으로 페이지를 보여주면 된다. 하지만 다음과 같이 문자셋이 생략되어 오는 경우가 종종 있다.

HTTP/1.1 200 OK
Content-type: text/html

HTTP 규격(specification)에 따르면 문자셋이 생략되었을 경우 디폴트 인코딩은 ISO-8859-1 (Latin1)이 되어야 한다. 하지만 대부분의 웹브라우저는 이 표준을 따르기보다는 각자의 방법으로 해당 페이지가 어떤 인코딩을 사용하고 있는지 추측하는 방식을 사용한다.

한  예로 대표적인 오픈 소스 브라우저인 파이어폭스(Firefox)의 경우 내부적으로 인코딩 디텍션 모듈을 가지고 있는데, [A composite approach to language/encoding detection]란 글에 보면 이 알고리즘을 자세히 설명하고 있다.  소스 코드 mozilla/intl/chardet 디렉토리에서 그 구현을 볼 수 있다(저 글이 작성된 후에 구현이 조금 바뀌었다고 한다).

이 모듈은 원래 독립적으로 있다가 모질라 소스 트리에 통합되었는데, 브라우저 뿐만 아니라 웹 어플리케이션에서도 유용하게 쓰이기 때문에 자바(jchardet)와 파이썬으로도 포팅이 되어있다.