Code Tells You How, Comments Tell You Why


18 Dec 2006


"코드 주석에 대한 철학"이라는 이전 포스트에서 말했듯이, 최고의 코드 주석이란 사용할 필요가 없는 코드 주석(이하 주석)이다.. 다시 한번 강조해서 말하자면, 주석 없이도 이해가 가능한 간단한 코드를 작성하도록 노력해야 한다. 코드로는 쉽게 이해하기 어려운 곳에만 주석을 추가하도록 한다.


이렇게 함으로써 청중을 의식하고 코드를 작성할 수 있다. 1985년에 발간된 고전인 "Structure and Interpretation of Computer Programs"의 서문에서도 이를 올바르게 지적하고 있다.


프로그램은 사람들이 읽을 수 있도록 작성되어야 하며, 단지 부수적으로 기계가 실행할 수 있어야 한다.


Knuth는 그의 1984년 고전적 에세이인 LiterateProgramming(pdf)에서 유사한 의견을 다루고 있다.


프로그램 작성에 대한 우리의 전통적인 태도를 바꾸어 보자 : 우리의 주된 목표가 컴퓨터가 무엇을 할 것인지를 지시하는 것이 아니라, 컴퓨터가 어떻게 동작하기를 원하는지를 인간에게 설명하는 것에 집중해 보자.


"literate programming"를 실천하는 사람은 수필가라고 할 수 있는데, 그들의 주된 관심사는 상세한 설명과 문체의 탁월함이다. 이런 저자는 유의어 사전을 항상 손에 쥐고, 매우 신중하게 변수의 의미를 설명할 수 있는 변수명을 고른다. 저자는 서로를 보강하는 공식적이고 비공식적 방법을 섞어 가면서, 인간이 이해하기 가장 좋은 순서로 개념을 소개함으로써 알기 쉬운 프로그램을 작성하기 위해서 노력한다.


당신 코드의 첫번째 소비자는 다른 프로그래머이고, 컴파일러는 그 다음이라고 생각하고 코드를 작성한다면, 부가적인 코드 주석의 필요성은 크게 줄어들 것이다. "using comments as a crutch"는 매우 훌륭한 설명이다.


이것은 몇년간 양산에 적용된 자금이 풍부한, 폐쇠형 source 시스템에서 가져온 코드의 snippet이다.

float _x = abs(x - deviceInfo->position.x) / scale;
int directionCode;
if (0 < _x & x != deviceInfo->position.x) {
    if (0 > x - deviceInfo->position.x) {
        directionCode = 0x04 /*left*/;
    } else if (0 < x - deviceInfo->position.x) {
        directionCode = 0x02 /*right*/;
    }
}

아래 코드는 위와 동일하지만, 버그를 수정하고 보다 읽기 쉽게 바꾼 것이다.

static const int DIRECTIONCODE_RIGHT = 0x02;
static const int DIRECTIONCODE_LEFT = 0x04;
static const int DIRECTIONCODE_NONE = 0x00;

int oldX = deviceInfo->position.x;
int directionCode = (x > oldX)? DIRECTIONCODE_RIGHT
                  : (x < oldX)? DIRECTIONCODE_LEFT
                  : DIRECTIONCODE_NONE;

주석이 많다고 더 읽기 쉬운 코드는 아니다. 위 예에서는 그렇지 않았다. 위 코드에서의 주석은 - 당신이 이미 알아차렸더라도 - 코드를 보다 복잡하게 할 뿐이다. 때로는 적은 주석이 보다 읽기 쉬운 코드를 만든다. 특히 주석 대신 의미 있는 심볼명을 사용한다면 말이다.


주석을 사용하지 않도록 개정하고 단순화할 수 있는 거의 무한한 기회가 있지만, 주석 없이 코드만으로 설명하는데는 한계가 있다.


코드가 아무리 간단하고, 간결하고, 명확하더라도, 코드가 완전히 스스로 문서를 대체할 수는 없다. 코드만으로 주석을 대체할 수는 없다. Jef Raskin의 말을 들어보자.


코드는 프로그램이 왜 작성되었는지, 이 방법 혹은 저 방법을 선택한 논리적 이유는 무엇인지는 설명할 수 없다. 코드는 여러 가지 대안 중에서 특정 방법이 선택된 이유를 설명할 수 없다. 예를 들면,

/* A binary search turned out to be slower than the Boyer-Moore algorithm for the data sets of interest, thus we have used the more complex, but faster method even though this problem does not at first seem amenable to a string search technique. */

어떤 개발자에게는 완벽하게 명백한 것이라도, 맥락을 모르는 다른 개발자에게는 전혀 이해하기 어려울 수 있다. 다음과 같은 주석에 대한 조언을 생각해 보자.


아마 아래와 같은 코드는 쉽게 잘 알것이다.


$string = join('',reverse(split('',$string)));


문장을 역전시키는 것. 하지만 아래와 같은 문구(주석)를 Perl에 넣는 것이 어려울까?


# Reverse the string

사실 전혀 어렵지 않다. 코드는 어떻게(How) 프로그램 동작하는지를 설명하고, 주석은 왜(Why) 동작하는지를 설명하도록 하면 된다. 이를 헷갈리지 않으면 된다.


Written by Jeff Atwood

Indoor enthusiast. Co-founder of Stack Overflow and Discourse. Disclaimer: I have no idea what I'm talking about. Find me here: http://twitter.com/codinghorror




원본 링크 : https://blog.codinghorror.com/code-tells-you-how-comments-tell-you-why/


* 다소 의역한 부분도 있으므로 정확하지 않을 수 있습니다. 원본 번역에 문제를 알려 주시면 내리도록 하겠습니다.

반응형

'번역' 카테고리의 다른 글

[번역] Python Success Stories  (0) 2018.01.23

Python Success Stories


First published in Linux Journal, May 2000.


Copyright 2000 Specialized Systems Consultants, Inc. All rights reserved.


원본 링크 : https://www.python.org/about/success/esr/


Introduction


1997년초에 우연히 Python을 처음 접했을 때 특별히 좋게 느끼지는 못했다. 최근에 Mark Lutz가 쓴 "Programming Python"이 O'Reilly & Associates에서 출간되었다. O'Reilly의 책들이 가끔 내게 배달되는데, 추측건데 O'Reilly에 있는 익명의 후원자가 신간 중에서 골라서 보내 주는 것 같다.


"Programming Python"은 그런 책중의 하나였다. 컴퓨터 언어에 대한 생각을 정리할 때여서, 이 책이 흥미로운 점이 있다는 것을 알았다. 나는 24개 이상의 컴퓨터 범용 언어를 알고 있고, 재미로 컴파일러와 interpreter들을 작성하며, 특수 목적의 컴퓨터 언어와 markup 형식론을 직접 디자인한다. 최근에는 PNG 이미지를 다루는 SNG라는 특수 목적의 컴퓨터 언어 프로젝트(http://www.tuxedo.org/~esr/sng)를 끝마쳤다. 내가 구현한 특이한 컴퓨터 범용 언어들이 있는데, 나의 Retrocomputing Museum 페이지(http://www.tuxedo.org/retro)에서 확인할 수 있다.


Python에 대해서는 "scripting language"이며, 다른 프로그램들을 호출할 수 있어서 협업이 가능하도록 자체적으로 built-in memory managemen를 가지고 있는 interpretive 언어라는 정도는 들어서 알고 있었다. 그래서 "Programming Python"을 읽으면서 "Perl은 갖지 못한", Python만이 가지고 있는 있는 것이 무엇인지를 알아내려고 했다.


물론 Perl은 scripting 언어로, 800 파운드의 거대한 고릴라다. 시스템 관리자가 shell을 scripting 언어로 대체할 때 대부분의 경우 선택해 왔으며, 일련의 UNIX 라이브러리와 시스템 콜들, 그리고 열성적인 Perl community에 의해 제작된 수 많은 Perl 모듈들이 있다. Perl은 또한 85%이상의 실시간 contents 처리를 위한 CGI에 주로 사용되고 있다. Perl의 창시자인 Larry Wall은 Open Source community의 가장 중요한 리더 중의 한명이며, 종종 Linus Torvalds와 Richard Stallman의 뒤를 이어 해커 반신(半神)의 팬덤에서 3위를 차지하기도 한다.


예전에는 Perl을 이용해서 작은 프로젝트들을 많이 수행했다. 비록 구문과 언어의 다른 측면들이 다소 즉흥적이고, 주의 깊게 사용하지 않으면 문제가 되기도 했지만, 상당히 강력했다. Python은 scripting 언어의 대안이 되기 위해서는 더 시간이 필요해 보였기 때문에, 책을 읽을 때 Perl과 다른 점이 무엇인지를 찾아 보았다.


모든 사람들이 아는 Python의 이상한 특징, 즉 Python 구문에서 여백(들여쓰기)가 매우 중요하다는 사실에서 나는 실수를 했다. C나 Perl이 사용하는 괄호 구문이 없고, Python은 들여쓰기로 statement groups을 구분한다. 이런 사실을 알았을 때 대부분의 해커들 처럼나도 협오스러웠고 별로 쓰고 싶지 않았다.


나는 1970년대에 두서너달 정도 FORTRAN 프로그래밍을 해 봤을 정도로 나이는 들었지만, 대부분의 해커들도 예전에 사용했던 이런 구식의 고정 필드 언어들이 얼마나 까다로웠는지 기억하고 있는 것 같다. 사실 Pascal이나 C에서부터 사용하기 시작한 토큰 위주의 새로운 구문을 "자유 형식"이라고 한다. 최근 수십년 동안 모든 언어는, 혹은 대부분의 언어는 이렇게 디자인되었다. Python의 이러한 요구를 확인하고, 처음에는 마치 예기치 않게 바로 싼 공룡의 똥 더미에 발을 들여놓은 것처럼 반응하는 것에 대해 누구도 탓하기는 어려울 것이다. 


나는 그렇게 느꼈다. 나머지 설명들에 대해서는 관심도 없이 대충 훑어보았는데, 구문이 Perl 보다 깔끔하고, 기본적인 GUI요소를 수행하기 위한 버튼과 메뉴와 같은 기능은 꽤 좋아 보였지만, 특별히 Python을 추천해야 할  이유는  없었다.


책을 다시 책장에 꽂아 두고, 정말로 Python을 이해하기 위해서는 GUI 중심 작은 프로젝트를 한번쯤 코딩해 보리라 생각했다. 하지만 내가 본 것들이 Perl과 효과적으로 경쟁할 수 있을거라고는 생각하지 않았다.


Perl Wears Thin


우선 순위에 밀려서 Python으로 프로젝트를 하지 못했다.  1997년은 나에게 다사 다난했다. 무엇보다도 "성당과 시장"의 원본을 쓰고 발행했다. 마침내 시간을 내서 크고 복잡한 두개의 프로그램이 포함해서 몇가지 Perl 프로그램을 작성할 수 있었다. 그 중 하나인 "keeper"(http://metalab.unc.edu/pub/Linux/!INDEX.html)는 Metalab 소프트웨어 아카이브에서 들어오는  서류를 처리하기 위한 것이었다. 다른 하나는 anthologize로 리눅스 문서 프로젝트 6번째판 HOWTO에 대한 PostScript를 자동으로 생성하는 데 사용되었습니다. 두 프로그램 모두 Metalab에서 사용할 수 있었다.


이런 프로그램들을 작성하면서 Perl에 점점 덜 만족하게 되었다. 보다 큰 프로젝트를 진행하게 되면서 지속적인 문제를 발생시키는 Perl의 성가신 작업은 심각하게 느껴졌다. 100줄 정도의 코드에서는 단지 엉뚱하게만 보이던 구문이, 1000줄 정도로 코드가 늘어나면서 해독 불가능한 가시 덤불처럼 보이기 시작했다. "두 가지 방법 이상으로 처리"할 수 있는 특징은 작은 규모의 작업에서는 풍미와 표현력을 제공했지만, 규모가 큰 코드를 작성하는 경우에는 일관성 있는 스타일을 유지하기 어렵게 만들었다. 그리고 더 큰 프로그램들을 정밀하게 제어하기 위해서 나중에 패치된 많은 기능들(objects, lexical scoping, "use strict"등)은 다루기 힘들고, 조잡하거나 급조된 것처럼 보였다.


이런 문제들이 큰 규모의 Perl 코드들을 며칠만 보지 않다가 다시 봤을때 전체적으로 읽거나 이해하기 어렵게 만들었다. 그리고 작성한 어플리케이션 문제 때문이 아니라 프로그램 언어 자체의 문제를 검토하는라 점점 더 많은 시간을 보내야 했다. 가장 짜증나는 일은 최종 결과물이 보기 흉하다는 것이다. 보기 흉한 코드는 보기 흉한 현수교와 같아서 예쁜 것들보다 쉽게 붕괴할 수 있다. 왜냐하면 사람들(특히 엔지니어들)은 아름다움을 인지하는 방식이 복잡한 것을 처리하고 이해하는 능력과 밀접하게 연관되어 있기 때문이다. 우아하게 코드를 작성하기 힘들면 좋은 코드를 작성하기도 힘들어진다.


여러 프로그래밍 언어를 알고 있었기 때문에, 프로그래밍 언어가 기능적 한계의 끝까지 밀리고 있다는 징후를 알아 차릴 수 있었다.  1997년 중반에는 "보다 나은 방법이 있어야만 해"라고 생각했고, 보다 우아한 스크립팅 언어를 찾기 시작했다.


물론 C 언어를 기본 언어로 사용하려는 생각은 없었다. 최대한의 속도를 내고, 메모리 사용을 매우 엄격히 관리하는등 하드웨어를 최대한 사용해야 하는 kernel 해킹이나 과학 분야의 컴퓨팅, 그리고 3D 그래픽과 같은 몇 가지 특수 분야를 제외하고는 새로운 프로그램에서 메모리 관리 프로그램을 직접 작성하는 것이 상식인 시대는 이미 지났다.


이런 몇가지 특수 분야를 제외한 대부분의 경우, 오늘날의 시스템에서 버퍼 오버런, 포인터 앨리어싱 문제, malloc / free 메모리 누수 및 그 밖의 관련 폐해들을 디버깅하는 것은 미친짓이다. 차라리 스크립팅 언어가 제공하는 "메모리 관리자"를 사용하면서 CPU를 몇 사이클 더 사용하고, 몇 킬로바이트의 메모리를 낭비해서 인간의 귀중한 시간을 아끼는 것이 훨씬 더 낫다. 실제로 Perl은 이 전략으로 1990년대 중반 이후 폭발적인 성장을 이끌어 냈다.

* 에일리어스: 하나의 프로그램에서 동일한 기억 장소를 참조하는 변수에 둘 이상의 이름이 붙은 것


나는 Tcl도 해 보았는데, Perl 보다 더욱 빨리 나빠지는 것을 확인할 수 있었다. 오랜 LISPer인 나조차 현재 사용되는 다양한 Lisp와 구문들을 살펴보았을때, 역사적으로 유용했던 많은 현명한 디자인이, 부족하거나 작성되지 않은 문서들과 POSIX/UNIX에 대한 불완전한 접근, 그리고 작지만 매우 파편화된 사용자 커뮤니티 때문에 쓸모없게 되었다. Perl의 인기는 우연이 아니다. Perl과 비교했을 때 대부분의 경쟁자들은 대형 프로젝트에 사용되더라도 더 나빴고, 이론적으로는 우월할 것으로 기대했던 디자인이 실제로는 유용하지 않았다.


Python Reconsidered


첫번째 것만큼이나 우연히 Python을 두번째로 접했다. 1997년 10월, 사용들은 fetchmail 메일링 리스트에서 내가 작성한 fetchmail 유틸리티로 configuration 파일을 생성하는데 점점 더 많은 문제를 겪고 있었다. 이 파일은 단순하고 고전적인 UNIX free-format 구문을 사용했지만, 사용자가 여러 사이트에 POP3 및 IMAP계정을 가지고 있을 때는 몹시 복잡해질 수 있었다. 아래 Listing 1은 단순하게 표현한 예이다.


set postmaster "esr"
set daemon 300
poll imap.ccil.org with proto IMAP and options no dns
    aka snark.thyrsus.com locke.ccil.org ccil.org
       user esr there is esr here options fetchall dropstatus warnings 3600
poll imap.netaxs.com with proto IMAP
       user "esr" there is esr here options dropstatus warnings 3600
skip imap.21cn.com with proto IMAP
       user esr here is tranxww there options fetchall
skip pop.tems.com with proto POP3:
       user esr here is ed there options fetchall
skip mail.frequentis.com with proto IMAP:
       user esr here is imaptest there with options fetchall

Listing 1


나는 사용자 친화적인 configuration 편집기인 fetchmailconf를 작성하기로 했다. fetchmailconf의 디자인 목적은 단순했다. 선택 버튼과 슬라이드 바, 그리고 채우기 양식이 가득한 멋있고, 인체 공학적으로 올바른 GUI 인터페이스 뒤에 제어를 위한 파일 구문을 완벽히 숨기는 것이다. 


그러나 Perl을 사용해서 구현하고 싶지는 않았다. Perl에 있는 GUI 코드를 본적이 있는데 Perl과 Tcl이 뒤섞여 있었고, 내가 작성한 순수한 Perl 코드보다도 흉해 보였다. 이 시점에 6개월전의 생각을 기억해냈다. 이것은 Python에 대한 실습을 할 수 있는 기회였던 것이다.


물론 이를 계기로 다시 한번 Python에서의 "들여쓰기" 문제를 맞닥뜨렸지만 말이다. 그러나 이번에는 몇가지 샘플 GUI 요소들을 대충 미리 만들어 보았다. 이상하게도, 20분 정도 지나자 Python에서 "들여쓰기"를 해야 한다는 사실이 부자연스럽게 느껴지지 않았다. C 프로그래밍할때 했던 것처럼 코드를 그냥 들여 쓰기만 하면 되었다.


이것이 첫번째 놀라움이었다. 두번째 놀라움을 느낀 것은  몇시간 정도 프로젝트를 진행하면서 (Python이라는 새로운 언어로 프로그래밍을 위한 새로운 기능들을 찾기 위해서 잠깐 쉬는 것을 제외하고는) 코드를 타이핑하는 즉시 동작하는 코드를 생성하고 있다는 것을 알아차렸을 때였다. 나는 너무 놀랐다. 코딩에 있어서 중요한 척도는 문제 해결을 하기 위해서 생각하는 것과 실제로는 일치하지 않는 코드를 작성하거나 실제로 생각하고 하고 있는 것과 방금 타이핑한 것이 서로 틀린 것을 깨닫는 빈도이다. 또한 좋은 언어 디자인의 중요한 척도는 언어를 사용하면서 경험이 축적될수록  얼마나 빨리 이런 종류의 실패 확률이 떨어지는가이다.


거의 타이핑 속도로 빨리 동작하는 코드를 작성한다는 것은 실수의 확률이 거의 0이 된다는 것이며, 일반적으로 언어를 거의 숙달했다는 의미다. 하지만 말이 안 되었다. 왜냐하면 Python을 사용해서 프로그래밍 한 첫째날이었고, 여전히 새로운 언어와 라이브러리의 기능을 찾기 위해서 종종 멈춰야 했기 때문이다.


이것이 Python이 대단히 잘 설계된 것이라고 생각하는 첫번째 이유다. 대부분의 언어는 디자인 요소에 많은 알력과 어색함을 내포하고 있어서 실수의 확률을 거의 없애기 위해서는 오랜 기간 해당 언어의 특성을 배워야 한다. Python은 내가 사용해본 언어 중 이런 과정을 뒤집어 놓은 최초의 범용 언어이다.


기능 세트를 익히는데도 오래 걸리지 않았다. GUI로 동작하는 사용할만한 fetchmailconf를 만드는데 6일이 걸렸는데, 2일은 Python 자체를 배우는데 사용했다. 이것은 언어의 또 다른 유용한 속성을 반영했기 때문인데 - 익혀야 하는 모든 기능 세트(그리고 적어도 라이브러리의 index에 대한 개념)가 매우 간결해서 모두 외울 수 있었다. C 언어는 간결한 언어로 유명하다. Perl도 하나의 개념, 즉 "해결하기 위한 한가지 이상의 방법"은 비용 지불이 필요하지만, 간결함의 가능성을 제공하기 때문에 악명이 높지는 않다.


Delving Deeper


하지만 가장 극적인 발견의 순간이 다가오고 있었다. 내 디자인에는 문제가 있었다. 사용자들의 GUI 동작에 맞춰 configuration 파일은 쉽게 생성할 수 있었지만, 편집하기는 어렵다는 문제였다. 편집 가능한 형식으로 읽어오는 것도 문제였다.


Fetchmail의 configuration 파일 구문을 위한 parser는 상당히 정교하다. 두개의 전통적인 UNIX 도구인 YACC와 Lex로 C 언어에서의 언어 구문 분석 코드를 작성했다. fetchmailconf가 기존의 configuration 파일을 편집하기 위해는 Python이 그 정교한 parser를 복제해야만 한다고 생각했다. 일의 양이 많을 것 같았고, 한편으로는 두개의 서로 다른 프로그래밍 언어를 사용해서 작성된 서로 다른 두개의 parser를 검증할 수 있을지 확신할 수도 없어서 꺼려졌다. 그리고, configuration 언어가 진화함에 따라서 두개의 paser를 유지해야 했다.


이 문제가 나를 괴롭혔다. 그러다가 한가지 영감이 떠올랐다. fetchmailconf가 fetchmail 자체의 parser를 사용하도록 하자는 것이다. .fetchmailrc를 parsing하고, 결과를 Python initialzier 형식에 맞춰 표준 출력으로 dump하도록 하는 configdump 옵션을 fetchmail에 추가했다. 이를 위한 결과는 Listing 2와 비슷했다 (공간을 절약하기 위해서 예제와 관련이 없는 일부 데이터는 생략함)


fetchmailrc = {
    'poll_interval':300,
    "logfile":None,
    "postmaster":"esr",
    'bouncemail':TRUE,
    "properties":None,
    'invisible':FALSE,
    'syslog':FALSE,
    # List of server entries begins here
    'servers': [
    # Entry for site `imap.ccil.org' begins:
    {
        "pollname":"imap.ccil.org",
        'active':TRUE,
        "via":None,
        "protocol":"IMAP",
        'port':0,
        'timeout':300,
        'dns':FALSE,
        "aka":["snark.thyrsus.com", "locke.ccil.org", "ccil.org"],
        'users': [
        {
            "remote":"esr",
            "password":"Malvern",
            'localnames':["esr"],
            'fetchall':TRUE,
            'keep':FALSE,
            'flush':FALSE,
            "mda":None,
            'limit':0,
            'warnings':3600,
        }
        ,        ]
    }
    ,
    # Entry for site `imap.netaxs.com' begins:
    {
        "pollname":"imap.netaxs.com",
        'active':TRUE,
        "via":None,
        "protocol":"IMAP",
        'port':0,
        'timeout':300,
        'dns':TRUE,
        "aka":None,
        'users': [
        {
            "remote":"esr",
            "password":"d0wnthere",
            'localnames':["esr"],
            'fetchall':FALSE,
            'keep':FALSE,
            'flush':FALSE,
            "mda":None,
            'limit':0,
            'warnings':3600,
        }
        ,        ]
    }
    ,
    # Entry for site `imap.21cn.com' begins:
    {
        "pollname":"imap.21cn.com",
        'active':FALSE,
        "via":None,
        "protocol":"IMAP",
        'port':0,
        'timeout':300,
        'dns':TRUE,
        "aka":None,
        'users': [
        {
            "remote":"tranxww",
            "password":None,
            'localnames':["esr"],
            'fetchall':TRUE,
            'keep':FALSE,
            'flush':FALSE,
            "mda":None,
            'limit':0,
            'warnings':3600,
        }
        ,        ]
    }
    ,
    # Entry for site `pop.tems.com' begins:
    {
        "pollname":"pop.tems.com",
        'active':FALSE,
        "via":None,
        "protocol":"POP3",
        'port':0,
        'timeout':300,
        'dns':TRUE,
        'uidl':FALSE,
        "aka":None,
        'users': [
        {
            "remote":"ed",
            "password":None,
            'localnames':["esr"],
            'fetchall':TRUE,
            'keep':FALSE,
            'flush':FALSE,
            "mda":None,
            'limit':0,
            'warnings':3600,
        }
        ,        ]
    }
    ,
    # Entry for site `mail.frequentis.com' begins:
    {
        "pollname":"mail.frequentis.com",
        'active':FALSE,
        "via":None,
        "protocol":"IMAP",
        'port':0,
        'timeout':300,
        'dns':TRUE,
        "aka":None,
        'users': [
        {
            "remote":"imaptest",
            "password":None,
            'localnames':["esr"],
            'fetchall':TRUE,
            'keep':FALSE,
            'flush':FALSE,
            "mda":None,
            'limit':0,
            'warnings':3600,
        }
        ,        ]
    }
    ]
}

Listing 2


Python은 fetchmail --configdump 출력을 평가할 수 있으며, 변수 "fetchmail"의 값으로 사용할 수 있는 구성을 가질 수 있었다.


하지만 이것이 마지막은 아니다. 정말 원했던 것은 단순히 fetchmailconf가 기존의 configuration을 가지는  것이 아니라, 실시간으로 운영되는 3개의 objects와 linked tree로 변경하는 것이었다. Tree는 3 종류의 objects, 즉 Configuration(전체 구성을 나타내는 최상위 개체), Site(폴링 할 사이트 중 하나를 나타냄) 그리고 User(사이트에 연결된 사용자 데이터를 나타냄)다. 예제 파일은 각각 하나의 사용자 개체와 연결된 다섯개의 사이트 개체를 나타낸다.


이미 세개의 객체 클래스를 설계하고 작성했다. (4일이 걸렸는데, 대부분 올바르게 위젯 레이아웃을 하는데 걸렸다.). 각각은 자신의 인스턴스 데이터를 수정하기 위해 GUI 편집 패널을 팝업 하는 Method를 가지고 있었다. 마지막 남은 문제는 Python initializer에 있는 데이터를 개체로 변환하는 것이었다.


이 세가지 클래스에 대해서 명확히 알고, initializer가 일치하는 object를 생성하기 위한 지식을 사용하여  코드를 작성하려 했다. 그러나 시간이 흘러 configuration 언어에 대한 새로운 기능 요구에 따라 새로운 class 멤버가 추가될 경우를 생각해서 이렇게 진행하지 않았다. 만약 쉬운 방법으로 객체 생성 코드를 작성했다면, class 정의나 initializer 구조체가 변경되는 경우 망가지기 쉽고 동기화가 되지 않을 것이다.


Initializer의 모양과 멤버들을 분석하고, class 정의와 그 member들을 쿼리하고, 두 세트간 임피던스 매칭하도록 조정하는 코드를 원했다.


이런 종류의 일은 메타 클래스 해킹이라고 불리며 일반적으로 무시무시하게 비밀스러운 흑마술에 속한다. 대부분의 객체 지향 언어(Perl은 그중의 하나이다)는 이를 전혀 지원하지 않는데, 만약 지원한다면 복잡해지고 망가지기 쉬울 것이다. 지금까지는 쉽게 사용할 수 있다는 점에서 Python에 좋은 인상을 받았는데, 여기 진정한 테스트가 기다리고 있었다. 이렇게 하려면 프로그래밍 언어와 얼마나 씨름을 하느라 힘들까? 나는 경험을 통해서 결국 해내더라도 얼마나 고통스러운지 알고 있었다. 그러나 책에 빠져서 Python의 메타 클래스 기능에 대해서 읽었다. 이를 통해서 구현한 함수들은 Listing3에, 이를 호출하는 코드들은 Listing 4에 있다.


def copy_instance(toclass, fromdict):
# Initialize a class object of given type from a conformant dictionary.
    class_sig = toclass.__dict__.keys(); class_sig.sort()
    dict_keys = fromdict.keys(); dict_keys.sort()
    common = intersect(class_sig, dict_keys)
    if 'typemap' in class_sig:
        class_sig.remove('typemap')
    if tuple(class_sig) != tuple(dict_keys):
        print "Conformability error"
#       print "Class signature: " + `class_sig`
#       print "Dictionary keys: " + `dict_keys`
        print "Not matched in class signature: " + `setdiff(class_sig, common)`
        print "Not matched in dictionary keys: " + `setdiff(dict_keys, common)`
        sys.exit(1)
    else:
        for x in dict_keys:
            setattr(toclass, x, fromdict[x])

Listing 3


# The tricky part -- initializing objects from the configuration global
# `Configuration' is the top level of the object tree we're going
# to mung

Configuration = Controls()
copy_instance(Configuration, configuration)
Configuration.servers = [];
for server in configuration[`servers']:
    Newsite = Server()
    copy_instance(Newsite, server)
    Configuration.servers.append(Newsite)
    Newsite.users = [];
    for user in server['users']:
        Newuser = User()
        copy_instance(Newuser, user)
        Newsite.users.append(Newuser)

Listing 4


극심한 흑마술을 사용한 것처럼은 보이지는 않을 것이다. 주석을 포함해서 32 줄. class 구조체, 코드 호출과 관련한 부분은 읽기도 쉽다. 코드의 크기도 작지만, 이 코드를 작성하는데 단지 90분 밖에 걸리지 않았고, 심지어 작성하고 처음 구동시켰을때 정확히 동작했다.


상당히 자제하면서 말하더라도 놀라왔다. 간단한 기술로 구현한 것이 기대한대로 처음부터 동작한다는 것은 매우 훌륭한 것이다. 더구나 새로운 프로그래밍 언어를 시작한지 6일만에 메타 클래스 해킹 작업을 한 것이다. 내가 상당히 재능있는 해커이지만, 이것은 Python이 명확하고 우아하게 디자인되었다는 놀라운 증거다.


Perl을 아무리 많이 사용했었지만, Perl로는 이렇게 할 수 없었다. 이제 Perl을 더 이상 쓰지 않을 것임을 깨달았다.


Conclusion


이것은 Python과 관련해서 가장 극적인 순간이었다. 그러나 지금까지 말하고, 한것들은 독창적인 해킹에 대한 것이다. 프로그래밍 언어가 오랜 기간 쓸모 있으려면 독창적인 행킹만 지원해서는 안되며, 일상적인 프로그래밍을 잘 지원해야 한다. 일상적인 프로그래밍이란 새로운 프로그램 작성이 아니라 기존의 프로그램들을 읽고 수정하는 것이다.


이 이야기의 핵심은, fetchmailconf 코드를 작성한지 몇주, 몇달이 지났지만 여전히 fetchmailconf 코드를 읽고 충분히 이해하는데 특별한 노력이 필요하지는 않다는 것이다. Perl을 이용해서 작은 프로그램들을 여전히 작성하지만, Perl로 큰 코드를 작성할때는 언젠가는 수정하거나 편집해야 하지 않을까는 두려움이 있었지만 -- Python으로 작성한 fetchmailconf와 관련해서는 꺼리낌이 없다.


Perl은 여전히 쓰임새가 있다. 매우 많은 텍스트 패턴 매칭을 포함하는 작은 프로젝트(100라인이나 그 이하)를 위해서는 Python을 사용하기 보다는 Perl-regexp 기반의 솔루션을 고쳐서 사용하기를 좋아한다. 최근의 그런 예로는 fetchmail 배포판에서 timeseries와 growthplot을 확인해 보라. 실제로 이런것들은 함수를 갖거나 운영체제 API를 직접 건드리기 전의 Perl의 원래 역할일인 일종의  awk/sed/grep/sh의 조합과 같다. 조금만 더 커지만 Python을 사용하게 된다 - 당신도 마찬가지일 것이다.


All listings referred to in this article are available by anonymous download in the file ftp://ftp.linuxjournal.com/pub/lj/listings/issue73/3882.tgz


About the Author

Eric Raymond is a Linux advocate and the author of The Cathedral & The Bazaar. He can be reached via e-mail at esr@thyrsus.com.



반응형

'번역' 카테고리의 다른 글

[번역] Code Tells You How, Comments Tell You Why  (0) 2018.01.24

+ Recent posts