RSS구독하기:SUBSCRIBE TO RSS FEED
즐겨찾기추가:ADD FAVORITE
글쓰기:POST
관리자:ADMINISTRATOR
IRC채널에 올라온 글을 읽던 도중 후배가 int main()와 void main()의 차이점을 물어보았다.

2학년때 C스터디를 진행할 때 이것땜에 곤란을 겪었던 적이 한번 있었는데, 그때는 설렁 설렁 넘어갔던 기억이 있다.

고급계산이론 과제를 하던 도중에 한번 쉬엄쉬엄 찾아볼까 해서 구글링을 해봤는데 알아낸 사실은 다음과 같다.

1. C99 이후의 표준 main 함수는 다음과 같아야 한다.
int main() 이거나
int main(int argc, char *argv[])

2. void main()에 비해서 int main()이 좋은 점은 종료 후 invoker(호출자)에게 해당 값을 넘겨준다. 이는 exit(); 함수와 같은 역할인듯 하다.

3. 다음과 같은 방법으로 바로 직전에 실행 되었던 프로그램이 반환한 값을 알 수 가 있었고, 실제로 넘겨준다는 것을 알 수 있었다.

다음과 같은 소스 코드를 작성한다.
#include <cstdio>

int main() {
        return 1;
}
컴파일 후 다음과 같이 해본다.
[libe@aslab libe]$ ./a.out
[libe@aslab libe]$ echo $?
1

근데 이것은 linux상에서 확인한 결과이고, 실제로 windows상에서는 어떤 방식으로 확인을 할 수 있을지 궁금하다(아시는 분께서 답변을 해주시면 감사하겠습니다.). 요사이 관심사 중에 하나가 run-time error 체킹을 쉽게 하는 방법에는 무엇이 있을까? 였는데, linux상에서 테스트 해본 결과 assert를 이용한 run-time error의 경우 138, segmentation fault의 경우 139라는 값을 확인 할 수 있었다. 생각 했던 것 보다 쉽다 ~_~

첨가 - windows의 경우
구글링을 해본 결과 Windows에서도 다음과 같은 batch file을 생성하면 확인을 할 수 있다.
@echo off
실행파일명
@if "%ERRORLEVEL%" == "0" goto good

:fail
echo Execution Failed
echo return value = %ERRORLEVEL%
goto end

:good
echo Execution Succeded
echo return value = %ERRORLEVEL%
goto end

:end
하지만 linux와 다른점은 assert()의 경우엔 얌전히 동작하는데, scanf("%d",a); 와 같은 코드에서 오류창을 띄운다음에 아마 메모리 주소로 추정되는 값을 내뱉는다-_-; 조금더 연구해봐야할 필요가 있을듯 싶다.

그런데 exit를 쓰면 void main()도 마찬가지의 기능을 할 수 있을텐데(테스트 결과도 마찬가지 였다.)  int main()의 좋은점이라고 하면 cstdlib헤더를 첨가 하지 않아도 된다는 점이라고 해야하나?

2007/07/10 18:01 2007/07/10 18:01
이 글에는 트랙백을 보낼 수 없습니다

http://jmsn.sf.net

2007/06/22 17:53 2007/06/22 17:53
이 글에는 트랙백을 보낼 수 없습니다

x64 프라이머
 

64비트 윈도우를 프로그래밍 하기 위해서 알아야 할 모든 것들


 

이 글은 아래의 내용을 다루고 있습니다:

    64비트 윈도우의 기초적인 내용

    x64의 간략한 내부 구조

    Visual C++ 2005로 x64용 소프트웨어 개발

    x64용 소프트웨어를 위한 몇 가지 디버깅 기술들

이 글은 아래의 기술들을 사용합니다:
Windows, Win64, Visual Studio 2005

목차


새 로운 64비트 윈도우에서 일했던 경험의 좋았던 점 중의 하나는, 새로운 기술이 어떻게 동작하는지 눈으로 확인할 수 있다는 것이었습니다. 저 자신은 특히 어떤 운영체제 밑바닥에 대해서 조금이라도 알기 전까지는, 그 운영체제에 대해서 그렇게 편안함을 느끼지 못하는 편입니다. 그래서, 64비트 Windows XP와 Windows Server™ 2003이 나타났을 때, 저는 아주 열심히 그 운영체제에 대해서 연구하였습니다.
 

Win64 와 x64 CPU의 좋은 점은, 그 전의 CPU와 조금 다른 구조를 가지고 있지만, 그 차이를 배우는데 그렇게 많은 시간이 요구되지 않는 다는 점입니다. 저희 같은 개발자들에게는, x64로의 이동이 단지 컴파일만 다시 하면 끝나는 그런 작업이었으면 좋겠지만, 그렇게 생각하고 작업을 하신다면, 앞으로 디버거에서 너무 많은 시간을 보내셔야 할 것 같습니다.
 

이 글에서, 저는 Win64와 x64의 내부구조에 대한 저의 지식을 종합해서 유능한 Win32® 프로그래머가 x64로 이동하기 위해서 꼭 필요한 지식들 제공하겠습니다. 저는 여러분이 이미 Win32의 개념과 기본 x86개념, 그리고 왜 여러분의 코드가 Win64에서 동작 해야 하는지에 대해서 이미 알고 계신다고 가정하겠습니다. 그렇게 해야지 제가 좀 더 핵심적인 것들에 집중할 수 있거든요. 여기서의 이 요약이 x86 내부구조와 Win32에 대한 여러분의 지식과 비교해서 상대적으로 중요한 차이점에 대한 고찰이라고 생각하시기 바랍니다.
 

x64 시스템에서 한 가지 좋은 점은, 아이템니윰(Itanium)기반 시스템과는 틀리게 여러 분이 심각한 효율 저하에 대한 고민을 하지 않고, Win32 혹은 Win64를 동일한 기계에서 이용하실 수 있다는 점입니다. 그리고 인텔과 AMD의 x64 구현에 조금 불명확한 몇 가지의 차이가 존재함에도 불구하고, x64용 윈도우는 둘 중 어느 곳에서나 동작합니다. 여러분이 AMD x64와 Intel x64 시스템을 위해서 각각 다른 버젼의 윈도우가 필요하지 않습니다.
 

저는 여기서 크게 세 가지 영역으로 이 글을 나누어서 진행하도록 하겠습니다: 운영체제의 구현의 몇 가지 자세한 내용들, x64 CPU 내부 구조에 대한 개괄적인 설명, 그리고 Visual C++로 x64용 프로그램 개발.


x64 운영체제

 

저 는 어떤 윈도우 내부구조를 설명하더라도, 처음에는 메모리와 주소 공간에서 부터 시작합니다. 비록 64비트 프로세서가 이론적으로는 16 엑사 바이트(exabytes) 의 메모리에 접근할 수 있다고 하더라고, Win64는 현재 16 테라바이트(terabytes), 44비트 만을 지원하고 있습니다. 왜 64비트 전부를 사용해서, 16 엑사바이트 전부를 쓸 수 없었을 까요? 거기에는 몇 가지 이유가 존재합니다.
 

맨 처음 이유로는, 현재 x64 CPU들이 물리적인 메모리 공간에 접근할 때, 오직 40비트(1테라바이트)만을 허용합니다. 그 제한이 없어진다고 하더라고, 지금 현재의 하드웨어가 아닌, 향후에 나올 수 있는 CPU들의 내부구조들이 오직 52비트 (4페타바이트)만큼만 확장될 수 있습니다.그 정도만 해도, 그 많은 메모리를 매핑하기 위한 페이지 테이블의 사이즈는 어마 어마 할 것입니다.
 

Win32 에서와 같이, 접근할 수 있는 주소 공간은, 사용자와 커널의 영역으로 나누어 집니다. 커널 모드의 코드가 8 테라바이트 상위에서 모든 프로세스에 의해 영역되고, 각각의 프로세서는 8 테라바이트 이하에 자신의 고유 영역을 가집니다. 64비트 윈도우의 각 버젼들은 그림 1그림 2 에서 보여지는 것 처럼, 서로 다른 물리적인 메모리 한계를 가지고 있습니다.

Win32 에서와 같이, x64의 페이지 사이즈는 4KB 입니다. 처음의 64KB 공간은 절대로 매핑 되지 않기 때문에, 여러분이 볼 수 있는 맵핑 주소중 가장 낮은 번지는 0x10000입니다. Win32에서는 다르게, 시스템 DLL들은 사용자 모드 주소 공간의 제일 위 부분과 근접해 있는, 기본 로드 어드레스라는 것이 없습니다. 그 대신, 시스템 DLL들은 4GB 위의 공간에 적재 됩니다. 통상적으로, 그 주소는 0x7FF00000000 (8 테라바이트) 근처입니다.
 

새 로운 x64 프로세서의 좋은 기능 중에 하나는, 윈도우가 하드웨어적으로 데이터 실행 보호(Data Execution Protection-DEP)를 할 수 있게 지원해 준다는 것입니다. x86 플랫폼에서는, 많은 버그와 바이러스가 CPU가 데이터를 올바른 코드 바이트로 인식하고 실행했기 때문에 존재할 수 있었습니다. 실수이든 혹은 고의적인 버퍼 오버런(buffer overrun)이 원래는 데이터 저장을 목적으로 했던 메모리 블락을 CPU에서 명령어로 인식하고 실행해 버리는 결과가 발생하곤 했었습니다. 이 DEP의 도움으로, 운영체제는, 의도한 코드 영역의 경계를 명료하게 설정할 수 있고, 이 의도된 경계를 벗어나는 코드 실행에 대해서는 CPU가 일종의 덫을 놓을 수 있게 되었습니다. 이 기능은 윈도우를 악의적인 공격에 덜 취약하게 만드는데 큰 도움을 줄 수 있습니다.
 

에 러들을 잡기 위해서 고안된 장치들 중에 하나가, x64 링커가 실행파일에 대한 기본 적재 주소를 32비트 (4GB) 이상으로 설정한 것입니다. 이것은 코드가 Win64로 포팅된 후, 기존에 있던 코드에서 위에서 이야기한 보안에 문제를 일으키는 부분을 찾기 쉽게 만들어 줍니다. 특히, 만약 포인터가 32비트 값(예를 들어, DWORD)값으로 저장되어 있으면, 그러면, 그 값은 Win64 빌드를 동작시킬 때, 값이 일부분이 짤림 으로서, 포인터를 무효화 시키고, 접근위반(access violation)을 일으킵니다. 이러한 잔기술은 지저분한 포인터 버그를 찾기 쉽게 만들어 줍니다.
 

포 인터와 DWORD에 대한 주제들도 Win64의 타입을 이야기하는데 빠질 수 없습니다. Win64에서 포인터의 크기가 얼마나 될까요? LONG형의 길이는요? 그리고, 핸들과 HWND의 크기는 얼마나 될까요? 다행스럽게도, 마이크로 소프트가 Win16에서 Win32로의 좀 지저분한 변환을 하면서, 새로운 자료형의 모델에 대해서는 64비트 까지의 확장이 쉽게 될 수 있도록 만들었습니다. 일반적으로 이야기 해서, 몇 가지 예외를 제외하고, 새로운 64비트 세계에서도, 포인터와 size_t를 제외한 모든 나머지 자료형 들은 Win32에서와 동일한 길이를 가지고 있습니다. 즉, 64비트 포인터는 8바이트인 반면에, int,long, DWORD는 아직도 4바이트입니다. 후반부에 Win64 개발에 대해서 이야기 하면서, 이 자료형에 대해서 좀 더 이야기 하도록 하겠습니다.[편집자 주 - 5/2/2006: 핸들은 포인터 값으로 선언되었습니다. 그래서, Win64에서는 4바이트가 아닌 8바이트 값입니다.]
 

Win64 의 포맷은 PE32+라고 불립니다. 거의 모든 관점에서 이 포맷은 Win32PE 파일과 거의 구조적으로 동일합니다. ImageBase 같은 몇 몇의 포맷은 더 크기가 커졌고, 한 필드는 없어졌고, 그리고 다른 하나의 필드는 다른 CPU 타입을 반영할 수 있도록 변경되었습니다. 그림 3 은 바뀐 필드들을 보여 줍니다.
 

PE 헤더를 말고는, 그렇게 많이 바뀐 부분이 없습니다. IMAGE_LOAD_CONFIG, IMAGE_THUNK_DATA같은 몇 몇 구조체들은 단순히 필드들을 64비트로의 확장을 했을 뿐입니다. PDATA 섹션의 추가는 Win32와 Win64 구현의 주된 차이점중의 하나를 두드러지게 한다는 점에서 아주 흥미롭습니다: 그 차이는 바로 예외 처리(exception handling) 입니다.
 

x86 세계에서는, 예외 처리는 스택에 기반했었습니다. Win32 함수가 try/catch 혹은 try/finally 코드를 가지고 있을 때, 컴파일러는 스택에 작은 데이타 블락을 만들어 놓는 코드를 생성했었습니다. 추가로, 각각의 데이타 블락은 이전 try 데이타 구조체를 가리켰었습니다. 그래서, 최근에 추가된 구조체가 리스트의 헤드가 되는 링크드 리스트를 생성했었습니다. 함수가 불려지고, 종료될 때, 링크드 리스트의 헤더는 계속 갱신이 되었었습니다. 그러다, 예외가 발생하는 경우, 운영체제가 스택에 있는 링크드 리스트의 블락을 살펴서, 적절한 핸들러를 찾는 방식으로 이루어져 있었습니다. 저의 1997년 MSJ article 에서 좀 더 자세한 내용을 찾아 보실 수 있습니다.
 

Win32 예외처리와는 반대로, Win64 (x64와 아이템니움(Itanium) 버젼 둘 다 해당됩니다.)에서는 테이블 기반의 예외 처리를 사영합니다. 더 이상 스택 위에 있는 try 데이타 블락의 링크드 리스트는 없습니다. 대신, 각각의 Win64 실행파일은 런타임 함수 테이블을 가지고 있습니다. 각각의 함수 테이블은 함수의 시작 주소와 끝 주소를 가지고 있을 뿐만 아니라, 함수의 스택 프레임 레이아웃과 예외 처리 코드에 관련된 데이타들의 위치 역시 가지고 있습니다. 이 구조체들의 핵심을 보기 위해서 WINNT.H안에 들어 있는 x64 SDK안에 WINNT.H 안에 있는 IMAGE_RUNTIME_FUNCTION_ENTRY 구조를 살펴 보시기 바랍니다.
 

예 외가 발생했을 때, 운영체제는 쓰레드 스택을 하나씩 탐색합니다. 스택을 탐색하면서 각각의 프레임을 탐색하고, 저장된 인스트럭션 포인터를 찾아서, 운영체제가 어떤 실행 모듈안에 인스트럭션 포인터가 있는지를 결정합니다. 그리고 나서, 운영체제는 런타임 함수 테이블을 그 모듈에서 찾아서, 적절한 런타임 함수를 찾아서, 데이타로 부터 적절한 예외 처리 결정을 내려 줍니다.
 

만 약, 여러분이 로켓 과학자이고, PE32+ 모듈 없이 직접적으로 메모리에서 코드를 생성했으면 어떻게 될까요?  RtlAddFunctionTable API를 이용하여, 운영체제에게 여러분이 동적으로 생성한 코드에 대해서 알려 줄 수 있습니다.
 

테 이블 기반의 예외 핸들링의 단점은 (x86 스택 기반 모델에 비해 상대적으로) 함수 테이블을 찾아 보는 것이, 링크드 리스트에서 값을 찾는 것 보다 훨씬 시간이 많이 걸린다는 점입니다. 장점은, 함수를 실행시킬 때 마다 매 번 try 데이타 블락을 생성시키는 오버헤드가 없다는 점입니다.
 

꼭 기억하세요! 이 글이 아무리 재미있고, 흥미로워도, 이 글은 x64 예외 핸들링에 대한 자세한 설명이라기 보다, 간단한 소개에 불과합니다. x64의 예외 핸들러에 대한 좀 더 깊은 지식을 알고 싶으시다면, Kevin Frei의 블로그을  꼭 한 번 읽어 보시기 바랍니다.
 

x64 에 호환되는 윈도우에 새로운 API가 그렇게 많지는 않습니다; 거의 모든 새로운 Win64 API들은 아이템니움(Itanium) 프로세서를 위한 윈도우 출시 때 이미 추가되었던 것들 입니다. 간단하게, 그 API들 중 가장 중요한 두 개의 API는 IsWow64Process와 GetNativeSystemInfo 입니다. 이 함수들은 Win32 어플리케이션이 자기 자신들이 Win64에서 돌고 있는지의 여부를 알려 줍니다. 그래서, 만약 64비트 환경에서 동작하고 있다면, 시스템의 진짜 사양(capability)를 올바르게 결정할 수 있게 해 줍니다. 반면에, 32비트 프로세스는 GetSystemInfo 함수를 호출하고, 오직 32비트 시스템인 것 처럼, 시스템의 사양(capability)를 볼 수 있습니다. 예를 들어, GetSystemInfo는 32비트 프로세스 주소 영역만을 보고 합니다. 그림 4 는 x86에서는 사용할 수 없고, 오직 x64에서 쓸 수 있는 API들을 보여 주고 있습니다.
 

전 부 다 64비트로 동작하는 윈도우 시스템이 아주 멋지게 들리겠지만, 현실적으로 여러분은 잠시 동안 Win32 코드를 필요로 하게 될 것 같습니다. 그러한 작업을 위해서, x64 버젼의 윈도우는 Win32와 Win64 프로세스를 동시에 동일한 시스템에서 동작시킬 수 있는 WOW64 서브시스템이 포함되어 있습니다. 그러나, 여러분의 32비트 DLL을 64비트 프로세스로 올리거나 혹은 반대의 일들은 지원되지 않습니다. (저를 믿으세요, 아주 좋은 일입니다.) 그리고 마침내 여러분은 구닥다리 16비트 코드에게 잘 가라고 인사를 할 수 있게 되었습니다!
 

x64 버젼의 윈도우에서는, 프로세서는 오직 Win64 DLL들만 로딩할 수 있는, Explorer.exe 같은 64비트 실행 파일에서 부터 시작될 수 있습니다. 반면에, 32비트 실행 파일에서 시작한 프로세스는 오직 Win32 DLL 들만 로딩할 수 있습니다. Win32 프로세스가 커널 모드의 함수를 호출할 때-예를 들어서 파일을 읽는다든지-WOW64는 그 함수를 조용히 가로채서, 올바른 x64 코드의 주소를 주어서 호출하게 합니다.

물 론, 서로 다른 종족(32비트와 64비트) 프로세서들 끼리 통신할 일도 생길 수 있습니다. 운좋게도, Win32에서 여러분이 사랑하고 좋아했던 모든 프로세스간 통신 방법은 Win64에서도 동작합니다. 쉐어드 메모리(shared memory), 네임드 파이프(named pipe), 그리고, 기타 이름이 있는 동기화 객체들을 포함해서 말입니다.
 

여 러분이 혹시 "그럼 시스템 디렉터리도 Win32와 Win64가 동일한가?"라고 생각하실 지도 모르겠습니다. 동일한 디렉터리가 32 비트와 64 비트 KERNEL32 나 USER32 등과 같은 동일한 이름의 시스템 DLL들을 동시에 가질 수 없습니다, 그렇지요? WOW64는 요술같이 파일 시스템의 리다이렉션(redirection)을 통해서 이 문제를 해결합니다. Win32 프로세스에서의 파일에 대한 쓰기 혹은 읽기 등이 발생하면, SysWow64라는 디렉토리에 있는 커널의 함수를 호출하는 것이 아닌, System32에 있는 커널의 함수를 호출하게 합니다. WOW64가 안보이게 SysWow64 디레토리로 요청한 것을 조용히 바꾸어 주는 것입니다. 그래서, Win64 시스템이 효과적으로 두 개의 시스템 디렉터리, 하나는 x64 용 바이너리들과 또 하나는 Win32용의 바이너리를 가지는 것 입니다.
 

약 간 혼란스러울 수 있지만, 이러한 내부적인 처리는 상당히 부드러운 것 처럼 보입니다. 제가 System32 디렉터리의 Kernel32.dll에서 Dir을 실행했을 때, SysWow64 디렉터리에서 했던 것과 정확히 똑같은 결과를 볼 수 있었습니다. 파일 시스템의 리다이렉션이 이런 방식으로 동작하는 것을 정확히 이해하기 까지, 제 자신은 머리가 좀 많이 아팠었습니다. 여러분이 x64 어플리케이션에서 32비트 Windows\System32 폴더를 알기를 정말로 원하신다면, GetSystemWow64Directory 라는 API가 여러분께 정확한 경로를 전달해 줄 것 입니다. 그래도, 전체 내용을 알기 위해서 MSDN 문서를 꼭 읽어 보시기 바랍니다.
 

파 일 시스템의 리다이렉션이외에도, WOW64가 해주는 또 다른 마법 중의 하나가 레지스트리 리다이렉션입니다. 제가 아까 Win64 프로세스에서는 Win32 DLL들을 불러오지 않는다고 했던 말을 생각해 보시고, 그리고 COM 과 in-process 서버 DLL을 불러올 때, 레지스트리를 이용하는 것을 생각해 보시기 바랍니다. 만약, Win32 DLL에 구현되어 있는 COM 오브젝트를 64비트 어플리케이션이 CoCreateInstance를 이용해서, 생성하려고 하면 어떻게 될까요?  DLL이 올라올 수 없습니다, 맞지요? WOW64는 Win32 어플리케이션으로 부터의 접근을 \Software\Classes 레지스트리 노드로 리다이렉션 해 줍니다. 결과적으로 Win32 어플리케이션에서 보는 레지스트리 구조는 x64 어플리케이션에서 보는 것과 서로 다르게 됩니다. 그리고, 여러분이 기대하시는 대로, 운영체제는 32비트 어플리케이션이 RegOpenKey와 그 계열 함수군을 이용하여, 실제로는 64비트인 레지스트리에 접근하려고 할 때,내부적으로 새로운 플래그 값을 주어서, 그 값들에 접근할 수 있게 합니다.
 

약 간만 더 깊숙이 들어가서, 쓰레드 로컬 데이타 영역도 살펴 보아야 합니다. x86 버젼의 윈도우에서는, FS 레지스터가 각 쓰레드의 메모리 영역과 가장 마지막 에러(GetLastError로 확인할 수 있는 에러 값), 그리고 쓰레드의 지역 저장 영역(TLS:Thread Local Storage, TlsGetValue로 값을 얻을 수 있는) 에 사용되었습니다. x64 버젼의 윈도우에서는, FS 레지스터는, GS 레지스터로 교체되었습니다. 그 외에는 거의 동일한 방식으로 x32와 x64의 운영체제가 동작합니다.
 

비 록, 이 글이 x64의 사용자 입장에 초점을 두고 있기는 하지만, 커널 모드의 내부 구조에서 한 가지 추가된 중요한 점이 있습니다. PatchGuard라고 불리는 새로운 기술이 x64 윈도우에 추가되었습니다. 이 기술은 보안과 견고함을 위한 목적으로 추가되었습니다. 작게는 syscall 테이블이나 인터럽트 디스패치 테이블(interrupt dispatch table-IDT)를 변경하는 사용자 프로그램이나 드라이버들은 보안상의 문제와 잠재적인 안정성의 문제를 일으켜 왔었습니다. x64의 내부에서는, 그러한 방식으로 커널의 메모리를 지원되지 않는 방식으로 바꾸는 방식이 허용되지 않습니다. 이러한 것을 강화시키는 기술이 PatchGuard 입니다. 이 기술은 중요한 커널 메모리의 위치가 바뀌는 것은 커널 모드의 쓰레드에서 항상 감시합니다. 그리고 메모리가 바뀌면, 시스템은 버그체크를 통하여 멈춰 버립니다.
 

모 든 것을 고려해 보아도, 만약 여러분이 Win32의 내부 구조에 어느 정도 알고 있고, 어떻게 코드를 쓸 줄 알고, 동작하는지를 알고 있으면, Win64로의 이동에 있어서 크게 놀라지 않으실 겁니다. 거의 대부분은 좀 더 넓은 환경으로의 이동이라고 간주하셔 됩니다.


x64의 간략한 내부 구조

 

자 이제, CPU의 구조 자체에 대해서 조금 살펴 보기로 하겠습니다. 왜냐하면, 기본적인 CPU의 명령어(instructions)에 대해서 알고 있는 것이, 개발(특히 디버깅!)을 훨씬 쉽게 만들기 때문입니다. 처음에 여러분이 알아 차릴 수 있는 것은, 컴파일러가 생성한 x64 코드가 여러분이 알고 있고, 사랑하는 x86 코드와 거의 흡사하다는 점입니다. IA64 코딩의 경우는 그렇게 유사하지 않았었습니다.
 

그 리고, 두번째로 여러분이 알아차릴 수 있는 것은, 레지스터 이름이 여러분이 사용하던 것들과 조금씩 다르고, 레지스터 자체도 조금 많다는 점 입니다. 일반적인 용도의 x64 레지스턷들의 이름은 R로 시작합니다. 예를 들어서, RAX, RBX, 이런 것들이 있습니다. 이것들은 E이름을 가지고 있는 32비트 x86 레지스터들의 확장입니다. 아주 오래 전에, 16비트 AX 레지스터가 32비트 EAX가 되고, 16비트 BX 레지스터가 32비트 EBX가 되었던 것 처럼 말입니다. 32비트로 전이가 될 때 생겨난 E 레지스터들은 64비트로 이동하면서는 R 레지스터들이 된 것이죠. 그래서, RAX는 EAX의 계승자이고, RBX는 EBX의 계승, RSI는 ESI, 그런 식으로 확장되었습니다.
 

추가로, 8개의 일반적인 용도의 레지스터 (R8-R15)가 추가되었습니다. 64비트에서 주로 쓰이는 일반적인 용도의 레지스터들은 그림 5 와 같습니다.

물론 32비트 EIP 레지스터도 RIP 레지스터가 되었습니다. 그리고 32비트 명령어도 여전히 계속 동작하고 32비트 레지스터는 물론이고16비트 레지스터도 (EAX,AX,AL,AH등과 같은) 여전히 유효합니다.
 

그 래픽 작업을 하거나 혹은 과학적인 연산이 필요한 고수들이 사용할 수 있도록, x64 CPU는 여전히 XMM0에서 XMM15로 명명된 16개의 128비트 SSE2 레지스터를 가지고 있습니다. 그 외 여기서 이야기하지 않는 다른 x64 레지스터들에 대한 모든 정보들은 WINNT.H 안에서 _CONTEXT로 적적하게 #ifdef된 구조체에서 찾아 보실 수 있습니다.
 

아 뭏튼, x64 CPU는 언제라도 구형의 32비트 모드 혹은 64비트 모드 둘 다 에서 동작할 수 있습니다. 32비트 모드에서, CPU는 다른 x86 CPU처럼 명령어를 해석하고, 이에 기반하여 동작합니다. 64비트 모드에서는, CPU는 새로운 레지스터와 명령어를 지원하기 위해서 어떤 특정 명령어 인코딩에 대해서 약간의 사소한 조정을 하였습니다.
 

만 약 여러분이 CPU 오피코드 인코딩 다이아그램(opcode encoding diagram)에 익숙하시다면, 아마도, 새로운 명령어 인코딩을 위한 공간은 빨리 없어진다는 것과 새로운 명령을 위해 여덟 개의 새로운 레지스터를 쥐어짜는 것은 쉬운 일은 아니라는 것을 기억하실 겁니다. 새로운 명령어를 추가하는 방법 중의 하나는, 거의 쓰이지 않는 명령어를 삭제하는 것 입니다. 그래서, x64에서는 기존의 CPU에서 사용되던 몇 개의 명령어가 삭제되었고, 지금까지, 제가 오직 그리워 하는 명령어는 스택의 일반용도 레지스터의 값을 모두 저장했다가, 다시 복원해 주는, 64비트 PUSHAD와 POPAD 입니다. 또 다른 방법 명령어 인코딩 공간을 확보하는 방법은, 64비트에서 더 이상 쓰이지 않는 세그먼트관련 레지스터들을 전부 제거해 버리는 것 입니다. 그래서, CS, DS, ES, SS, FS,그리고 GS 레지스터가 더 이상을 쓰지 않습니다. 그렇게 많은 사람이 이 레지스터들을 그리워할 것 같지는 않군요.
 

64비트 주소가 사용됨에 따라, 여러분이 코드 사이즈에 대해서 궁금해 할지도 모르겠습니다. 예를 들어서, 아래의 경우는 흔한 32비트 명령어 입니다:
 

CALL DWORD PTR [XXXXXXXX]
 

위 에서, X가 된 부분이 바로 32비트 주소입니다. 64비트 모드에서는, 위에서 X가 된 부분이 64비트 주소가 됩니다. 그래서, 5바이트 명령어가 9바이트로 되겠지요? 운 좋게도, 그렇지는 않습니다. 명령어는 여전히 똑같은 사이즈를 유지합니다. 64비트 모드에서는, 명령어의 32비트 오퍼랜드 부분은 현재 명령어에 상대적인 데이터의 오프셋으로 취급됩니다. 예를 하나 들어 보겠습니다. 32비트 모드에서는, 아래의 명령어가 00020000h번지에 있는 32비트 포인터 값을 부릅니다. 
 

00401000: CALL DWORD PTR [00020000h]
 

64 비트에서는, 똑같은 명령어가 0042100h(4010000h + 20000h)에 있는 64비트 포인터 값을 호출합니다. 만약 여러분이 여러분이 스스로 코드를 생성하고 있다면, 조금만 생각해 보아도 이러한 상대 주소 모드가 의미를 가지고 있다는 것을 알 수 있습니다. 여러분은 명령어에 8 바이트 포인트 값을 제공할 수 없습니다. 그 대신에, 여러분은 실제의 64비트 대상 주소가 존재하는 곳의 32비트 상대 경로를 제공해줘야 합니다. 그래서, 64비트 대상 포인터는 반드시 이 포인터를 이용하는 명령어와 2GB 내외로(앞뒤로 각각 2GB씩) 존재해야 한다는 말하지 않아도 알 수 있는 가정이 생깁니다. 거의 모든 사람들에게는 이것이 문제가 될 이유가 별로 없겠지만, 동적으로 코드를 생성한다든지 혹은 기존 코드를 메모리에서 변경하는 경우라면, 문제가 생길 수도 있습니다 .

x64 레지스터의 가장 큰 장점 중의 하나는, 컴파일러가 스택 보다 모든 파라미터들을 레지스터에 전달하는 코드를 마침내 생성할 수 있다는 점 입니다. 스택에 파라미터를 구겨 넣는 것은 메모리 억세스를 필요로 합니다. 그리고, 우리는 CPU 캐쉬에 없는 메모리 억세스는 램에서 그 내용을 가져올 때 까지, 몇 사이클 동안 CPU를 잠시 서있게 한다는 사실도 알고 있습니다.
 

함 수 호출 방식(Calling Convention) 의 경우, x64 내부구조를 이용하여 _stdcall, _cdecl, _fastcall, _thiscall 같은 기존에 존재하는 Win32 함수 호출 방식을 모조리 정리할 수 있는 기회를 가졌습니다. Win64의 경우는, 딱 하나의 함수 호출 방식이 존재합니다. _cdecl 같은 방식은 그냥 컴파일러에서 무시됩니다.  이러한 함수 호출 방식의 단일화는 무엇보다도 디버깅을 원활하게 하는데 큰 혜택입니다.

x64의 함수 호출 방식은 fastcall 방법과 유사합니다. x64 호출 방식에서는, 처음 네 개의 정수 인자가 이 목적을 위해 디자인된 레지스터에 전달됩니다:
 

RCX: 1 번째 정수 인자 RDX: 2 번째 정수 인자 R8: 3번째 정수 인자 R9: 4번째 정수 인자


네 개 이상의 정수 인자는 스택을 통해서 전달됩니다. 그리고 this 포인터는 정수 인자로 간주되어 항상 RCX 레지스터에서 발견될 수 있습니다.
부동 소수점 인자들에 대해서는, 처음 네 개의 인자들은 XMM0에서 부터 XMM3 레지스터를 통해서 전달되고, 나머지 부동 인자들은 쓰레드 스택을 통해서 전달 됩니다.
 

함 수 호출 방식에 대해서 조그만 더 깊숙이 들어가면, 인자들이 레지스터를 통해서 전달될 수 있음에도 불구하고, 컴파일러는 RSP 레지스터를 감소시키면서, 여전히 스택에 공간을 예약해 놓습니다. 최소한, 각각의 함수는 반드시 32바이트 (네 개의 64비트 값)을 예약해 놓아야 합니다. 이 공간은 레지스터들이 함수에 전달되어, 잘 알려진 스택 위치에 쉽게 복사되도록 합니다. 물론, 불리는 함수 측에서 함수의 인자를 채우지는 않습니다. 하지만, 필요한 경우에 이러한 스택 공간의 예약은, 함수 인자들이 레지스터에서 쉽게 스택으로의 복사를 가능하게 합니다. 물론, 네 개 이상의 인자가 전달된다면, 적절한 스택의 추가 공간이 반드시 예약되어야 합니다.
 

예 를 한 번 들어 보겠습니다. 어떤 함수가 자식 함수에게 두 개의 정수 인자를 전달하는 경우가 있다고 가정을 한 번 해보겠습니다. 컴파일러는 두 개의 인자를 각각 RCX와 RDX에 각각 전달할 뿐만 아니라, RSP 스택 포인터 레지스터에서 32바이트를 빼놓습니다. 불리는 함수 입장에서는, 파라미터의 값을 RCX와 RDX 레지스터를 통해서 접근 가능합니다. 만약, 불리는 함수 코드가 레지스터가 다른 이유에서 필요할 경우, 이 값들은 예약된 32바이트 스택 영역에 복사됩니다. 그림 6은 6개의 정수 인자가 전달된 뒤의 레지스터와 스택을 보여 주고 있습니다.

Figure 6 Passing Integers
Figure 6 Passing Integers
 

x64 시스템에서의 파라미터 스택 정리는 약간 재밌는 모습을 보여 주고 있습니다. 기술적으로는, 불리는 함수(callee)가 아닌, 부르는 함수(caller)가 스택의 정리를 책임지고 있습니다. 그러나, 여러분은 프로롤그와 에필로그 코드를 제외하고 다른 부분에서 RSP를 조정하는 모습을 거의 보기가 힘들 것 입니다. PUSH와 POP 명령어로 스택에서 인자를 더하거나 빼주는 x86 컴파일러와 다르게, x64 코드 생성기는 (파라미터의 입장에서 보면) 얼마든지 큰 대상 함수에서도 쓸 수 있을 만큼 충분한 스택을 예약해 놓았습니다. 그래서, 자식 함수를 호출 시에, 파라미터를 설정하기 위해 똑같은 스택 영역을 계속 반복해서 씁니다.


짧게 말해서, RSP 레지스터는 거의 변하지 않습니다. 이 점은 ESP 레지스터 값이 파라미터가 스택에 추가되거나 정리되면서, 계속 변하는 x86 코드와 상당히 틀립니다.

예 를 하나 들어 보겠습니다. 세 개의 다른 함수를 호출하는 x64 함수가 있다고 생각해 보시기 바랍니다. 처음 함수는 네 개의 인자(0x20 바이트=32바이트)를 받습니다. 두번째 인자는 열 두개의 인자(0x60바이트=96바이트)를 받습니다. 세번째 함수는 여덟 개의 인자(0x40=64바이트)를 받습니다. 프롤로그에서는, 생성된 코드는, 스택에 단지 96바이트만 예약해서 대상 함수가 인자들을 찾을 수 있도록, 96 바이트 안의 적절한 위치에 인자들을 복사해 놓습니다.
 

x86 함수 호출 방식에 대한 좀 더 자세한 내부 구조는 Raymond Chen's blog에 서 찾을 수 있습니다. 이 이상은 더 자세히 설명하지는 않겠습니다만, 몇 가지만 중요한 점을 더 말씀 드리자면. 첫 번째, 함수의 인자들 중, 처음 네 개의 인자들 중에서, 64비트 보다 적은 정수 인자들은 부호 확장(sign extended)이 일어나고, 적절한 레지스터를 통하여 전달할 수 있습니다. 두 번째로, 64비트 얼라인을 지키기 위해서, 절대로 8바이트의 정수배가 아닌 함수 인자가 스택에 존재해서는 안됩니다. 구조체를 포함해서, 1, 2, 4, 혹은 8 바이트가 아닌 인자들은 래퍼런스를 통해서 전달됩니다. 그리고 마지막으로, 8,16,32, 64비트의 구조체와 고용체는 동일한 크기의 정수 인 것 처럼, 전달됩니다.

함 수 결과 값은 RAX 레지스터에 저장됩니다. 부동 소수점 형식의 값은 예외적으로 XMM0으로 돌려 받습니다. 함수 호출을 통하여, 제가 말씀드리는 레지스터들은 예약되어 있어야 합니다: RBX, RBP, RDI, RSI, R12, R13, R14, 그리고 R15. 그리고 지금 말씀드리는 레지스터들은 휘발성이고, 값이 없어질 수 있습니다:RAX, RCX, RDX, R8, R9, R10, 그리고 R11.
 

위 에서 제가 예외 처리 메커니즘의 일환으로, 운영체제가 스택 프레임을 검사한다고 말씀 드렸습니다. 여러분이 한 번이라도, 스택을 검사하는 코드를 써보신 적이 있다면, 거의 임시적인 Win32 프레임의 레이아웃이 프로세스를 다루기 힘들게 한다는 것을 아실 겁니다. 이러한 상황이 x64에서는 더 좋아졌습니다. 만약 함수가 스택 공간을 할당하고, 다른 함수를 호출하고, 어떤 레지스터를 예약하거나, 예외 처리를 이용하다면, 그 함수는 반드시 표준화된 프롤로그와 에필로그를 생성하기 위하여 잘 정의된 명령어 집합(well-defined set of instructions)을 써야 합니다.
 

표 준화된 함수의 스택 프레임을 사용하도록 강제하는 것은, 운영체제가 스택을 언제든지 탐색할 수 있는 것을 보장하는 한 방법입니다. 이러한 일관성에, 표준화된 프롤로그를 이용하여 컴파일러와 링커는 관련된 테이블에 데이터를 생성해야 합니다. 궁금하신 분들을 위해서, 좀 더 자세히 설명하면, 테이블의 모든 함수 정보들은 winnt.h에 정의되어 있는 IMAGE_FUNCTION_ENTRY64의 배열 테이블에 저장되어야 합니다. 어떻게 그 테이블을 찾는지 궁금하시다구요? 그 테이블은 PE헤더의 데이터 디렉터리 영역 안에 IMAGE_DIRECTORY_ENTRY_EXCEPTION의 엔트리가 지정하고 있습니다.

상당히 짧은 분량이지만, 내부구조의 많은 부분을 다루었습니다. 그러나, x86에 대한 큰  개념과 32비트 어셈블리 언어에 대한 지식이 있으신 분들은, 상당히 짧은 시간 안에 x64 명령어를 이해하실 수 있으실 겁니다.


Visual C++로 x64용 어플리케이션 개발

 

Visual Studio® 2005 이전에도 마이크로 소프트에서 나온 C++ 컴파일러로 x64용 코드를 생성하는 게 가능했지만, IDE와 완벽히 통합은 되지 않았었습니다. 이 글에서는, 저는 여러분이 Visual Studio® 2005를 가지고 있고, 여러분이 x64용 도구 (기본 설치 옵션이 아닙니다.)을 선택했다고 가정하고 이 글을 진행하겠습니다. 그리고, 여러분이 기존에 Win32 사용자 모드에서 C++를 이용한 프로젝트 경험이 있다고 가정하겠습니다.
 

x64 를 위한 첫 번째 단계는, 64비트 빌드 환경을 구축하는 것 입니다. 이미 다 아시겠지만, 여러분 프로젝트에는 기본으로 두 개의 환경이 있습니다. Debug와 Retail이 그것입니다. 여러분이 여기에서 더 해야 하는 것은, 두 개의 환경을 더 생성하는 것 뿐입니다. x64를 위한 Debug와 Retail 를 추가하는 것 입니다.
 

기 존의 프로젝트 혹은 솔루션을 한 번 열어 보시기 바랍니다. 빌드 메뉴에서, Configuration Manager를 선택해 보십시오. Configuration Manager 다이얼로그 박스에서, Active Solution Plaftfrom 콤보 박스를 눌러서, New를 선택하시기 바랍니다(그림 7). 그러면, 여러분은 New Solution Plaftform이라고 명명된 다른 다이얼로그를 보실 수 있을 것 입니다.
 

Figure 7 Creating a New Build Configuration
Figure 7 Creating a New Build Configuration
 

x64 를 새 플랫폼(그림 8)로 선택하고, 다른 설정은 그냥 놔두시기 바랍니다; 그리고 OK를 클릭하세요. 그게 전부랍니다! 여러분은 이제 네 가지의 가능한 빌드 환경을 구축하셨습니다: Win32 Debug, Win32 Retail, x64 Debug, x64 Retail. Configuration Manager 를 이용해서, 쉽게 다른 환경으로의 변경도 가능합니다.
 

자, 이제 여러분의 코드가 어떻게 64비트에 적용 가능한지 한 번 살펴 보기로 하겠습니다. x64 Debug 설정을 기본으로 놓으시고, 프로젝트를 빌드해 보시기 바랍니다. 프로젝트 자체가 아주 가볍지 않은 이상, Win32 환경에서 볼 수 없었던 컴파일 에러들을 보실 수 있을 겁니다. 여러분이 포팅이 불가능할 정도의 C++코드를 쓰지 않는 이상, 상대적으로 쉬운 에러 몇 개만 발생해서, 여러분은 쉽게 Win32와 x64에 대응하는 코드를 가지게 될 것 입니다. 특별히 조건부 컴파일 같은 것을 이용하지 않아도 말이죠.
 

Figure 8 Selecting the Build Platform
Figure 8 Selecting the Build Platform

Win64 호환되는 코드 만들기

 

아 마도, Win32 코드에서 x64로 컨번팅할 때 가장 문제가 되는 부분은, 타입 정의를 변경하는 일이 될 것 같습니다. 제가 혹시 앞에서 Win64의 자료형에 대해서 이야기 했던 것에 대해서 기억하시나요? C++ 컴파일러의 원래 자료형 (int, long 기타 등등) 을 쓰는 것 보다, 윈도우에서 정의한 typedef로 정의된 자료형을 쓰는 편이 깨끗한 Win32 x64 코드를 생성하는데 쉽습니다. 그리고 Win32의 자료형을 쓰실 때, 일관성있게 쓰셔야 할 필요가 있습니다. 예를 들어서, 윈도우가 HWND을 넘겨줄 때, 단지 편하고 쉽다고 해서, FARPROC 형식의 변수에 이 값을 저장하지 마십시요.
 

많 은 코드를 업그레이드 하면서, 아마도 제가 흔히 그리고 쉽게 보는 에러는, 포인터 값이 32비트 데이타 타입인 int 혹은 long, 그리고 DWORD에 저장되어 있는 것 이었습니다. Win32와 Win64에서의 포인터는 사이즈는 틀리지만, 정수형은 동일한 크기를 유지합니다. 그러나, 컴파일러에게 포인터 값을 정수형에 저장하는 것은 금지하는 것 역시 가능하지 않습니다.
 

이 런 상황을 해결하기 위해서, 윈도우 헤더에는 _PTR 타입이 선언되어 있습니다. 예를 들면, DWORD_PTR, INT_PTR, 그리고 LONG_PTR 같은 타입들이 대상 플랫폼에 따라서, 안전하게 포인터 변수를 이용하게 해줍니다. 예를 들어서, DWORD_PTR타입은 Win32에서 컴파일 되었을 때는, 32비트지만, Win64에서는 64비트입니다. 저의 경우는, 그동안의 연습으로, 어떤 자료형을 선언할 때, "내가 여기서 DWORD를 선언해야 할까? 아니면 DWORD_PTR를 선언해야 할까?"라고 물어보는 것이 버릇이 되어 버렸답니다.
 

기 대하시는 대로, 정수형에서 얼마나 많은 바이트를 원하는지 계산하는데, 약간의 문제가 있을 수 있습니다. DWORD_PTR를 정의하는 헤더파일(basetsd.h)에서 역시 INT32, INT64, INT16, UINT32, DWORD64 같은 여러 형태의 정수를 선언해 놓고 있기 때문입니다.
 

자 료형의 크기에 관련된 또 다른 문제는 printf와 sprintf의 포맷팅(formatting)입니다. 저는 확실히 과거에 %X 혹은 %08X등을 포인터 값을 나타내는데 사용했다는 점에서 약간의 죄책감(?)을 느끼고 있고, 그 코드는 x64 시스템에서 문제를 일으키고 있습니다. 옳은 방식은 대상 플랫폼의 포인터의 크기를 자동으로 계산해 주는 %p를 사용하는 것 입니다. 추가로, printf와 sprintf는 사이즈에 의존하지 않는 타입인 'I' 프리픽스(prefix)를 가지고 있습니다. 예를 들어서, 여러분은 %Iu를 UINT_PTR 변수를 출력하기 위해서 사용할 수 있습니다. 이와 동일한 방식으로, 여러분이 특정 변수가 언제 64비트 부호 있는 값이 될 것이라는 알고 있다면, %I64d를 쓰실 수 있습니다.
 

자 료형의 불일치에서 일어난 에러들을 정리하는 것만으로는 Win64가 준비되었다고 말할 수 없습니다. 여러분은 아직은 아마도 x86에서만 돌아갈 수 있는 소스코드를 가지고 계실겁니다. Win64로의 포팅을 위해서, 특정 코드에서는 여러분이 Win32와 x64를 위한 두 가지 버젼의 함수를 쓰실 수도 있습니다. 이 때야말로, 전처리자(preprocessor)가 아주 유용하게 쓰입니다:
 

_M_IX86 _M_AMD64 _WIN64
 

전절한 전처리자의 사용이야 말로, 여러 플랫폼에서 동작하는 소프트웨어를 만들기 위해서는 필수적입니다. _M_IX86과 _M_AMD64는 특정 프로세서를 위해서 컴파일할 때를 위해서 정의되어 있습니다.

전처리자 매크로를 사용할 때, 뭘 원하는지 한 번 열심히 생각해 보시기 바랍니다. 예를 들어서, 이 코드가 정말로 x64 프로세서만을 위한 것이라면, 아래와 같이 매크로를 쓰시기 바랍니다:
 

#ifdef _M_AMD64


반면에, 동일한 코드가 x64와 아이템니움(Itanium)에서 동작하기를 원한다면, 여러분은 아래와 같이 쓰시는게 좋을 것 같습니다:


#ifdef _WIN64


이러한 매크로를 쓸 때 제가 유용하게 쓰는 버릇이 하나 있습니다. 바로 제가 무엇을 잊어 버렸을 때를 대비해서, 명시적으로 모든 경우에 대해서 #else 케이스문을 써주는 것입니다. 아래에 코드를 한 번 살펴봐 주시기 바랍니다:
 

#ifdef _M_AMD64
    // My x64 code here
#else
    // My x86 code here
#endif
 

또 다른 세번째 CPU가 나타나면? 저의 x86 코드가 저의 의도와는 다르게 컴파일 됩니다. 위의 코드를 더 좋은 방법으로 구성하는 방법은 아래와 같습니다:


#ifdef _M_AMD64
    // My x64 code here
#elif defined (_M_IX86)
    // My x86 code here
#else
    #error !!! Need to write code for this architecture
#endif


그 리고, 마지막으로 저의 경우는 Win32코드 중에서 x64로 쉽게 포팅되지 않는 부분 중의 하나는 인라인 어셈블러 부분이었습니다. Visual C++가 x64 를 위한 인라인 어셈블러를 지원하지 않거든요. 대신에 64비트 MASM이 제공되고, MSDN에 문서화가 되어 있습니다. ML64.exe와 다른 x64툴들은 (CL.EXE와 LINK.EXE를 포함해서) 커멘드라인에서도 쓸 수 있습니다. 단지 VCVARS64.BAT를 실행시키세요. 그러면, 여러분의 패스에 이 파일들의 경로를 포함해 줄 것 입니다.


디버깅

 

여 러분은 마침내, 여러분의 코드를 Win32와 x64 양쪽에서 깨끗하게 컴파일 할 수 있게 되었습니다. 이 대장정의 마지막은 "어떻게 디버깅을 하느냐?" 입니다. 여러분이 x64 에서 x64 버젼을 빌드 했는지의 여부에 상관없이, 여러분은 x64 모드에서 디버깅 하기 위해서는, Visual Studio의 리모트 디버깅 기능이 필요합니다. 운좋게도, 여러분이 64비트 컴퓨터에서 Visual Studio를 동작시키면, IDE가 내부적으로 이 단계를 여러분을 위해서 해줍니다. 어떤 이유로, 리모트 디버깅을 할 수 없을 때, 여러분의 또 다른 옵션은 x64용 WinDbg를 사용하시는 방법도 있습니다. 그러나, 그렇게 되면, Visual Studio 디버그의 많은 훌륭한 기능들을 포기하셔야 합니다.
 

여러분이 리모트 디버깅을 한 번도 해보신 적이 없다고 하더라도, 걱정하실 필요 없습니다. 한 번만 설치 되면, 리모트 디버깅도 로컬처럼 쉼없이 잘 동작합니다.
 

첫 번째 단계는 64비트 MSVSMON을 대상 컴퓨터에 설치하는 것 입니다. 이것은 일반적으로 VisualStudio와 같이 오는 RdbgSetup을 동작시키면, 알아서 해 줍니다. 일단 MSVSMON이 설치되면, Tools 메뉴에서 적절한 보안 설정(혹은 lack)등을 설정할 수 있습니다.  

다음에는, Visual Studio 안에서, 여러분이 x64코드를 위해서 리모트 디버깅을 위한 설정을 하셔야 합니다. 이것은 프로젝트의 프로퍼티(그림 9)에서 할 수 있습니다.
 

Figure 9 Debugging Properties
Figure 9 Debugging Properties
 

여 러분의 64비트 구성을 선택해서, Configuration 프로퍼티 아래에 있는 Debugging을 선택하시기 바랍니다. 위쪽에 Debugger to launch라고 되어 있는 부분이 있습니다. 일반적으로 이 부분이 Local Windows Debugger로 설정되어 있습니다. 이 설정을 Remote Windows Debugger로 변경하시기 바랍니다. 그 아래에서, 여러분이 디버깅을 시작할 때의 리모트 명령(예를 들어서, 프로그램 이름)과 리모트 시스템의 이름과 연결 속성을 선택해 줄 수 있습니다.
 

모 든 것이 다 제대로 설정되었다면, 여러분의 x64 어플리케이션을 여러분이 Win32 프로그램에서 하듯이 디버깅할 수 있습니다. 여러분은 MSVSMON이 매 번 성공적으로 디버그와 연결이 될 때, "connected"라는 메세지를 보내 주기 때문에, 연결이 되었는지의 여부를 판별할 수 있습니다. 여기서 부터는, 여러분이 알고 있고, 사랑하는 Visual Studio 디버거와 거의 모든 것이 동일합니다. 64비트 레지스터를 보기 위해서, 그리고 비슷하지만 조금 다른 x64 어셈블리 코드를 보기 위해서 레지스터 창과 디스어셈블러 창을 띄우는 것을 잊지 마세요.
 

주 의: 64비트 미니덤프는 32비트 덤프처럼 Visual Studio에서 직접 불러 올 수 없습니다. 대신, 여러분은 리모트 디버깅을 이용해야 합니다. 네이티브(native)와 매니지드(managed) 64비트 코드의 상호호환은 현재로서 Visual Studio 2005에서 지원되지 않습니다.


매니지드 코드는 어떻게 하나요?

 

마 이크로 소프트 .NET 프레임 웍에서 코딩 하는 것의 장점 중의 하나는, 깔려있는 운영제제의 많은 부분이 일반적인 목적으로 코드로 추상화 되어 사라져 버렸다는 점입니다. 추가로 IL 명령어 포맷은 CPU에 의존적이지 않습니다. 그래서, 이론적으로는, Win32에서 제작된 .NET에 기반을 둔 바이너리 파일은 x64시스템에서 수정하지 않고 동작해야 합니다. 그러나, 현실은 아주 복잡합니다.
 

x64 버젼의 .NET 프레임웍 2.0을 제 x64시스템에 설치한 뒤에, 저는 제가 Win32에서 실행했던 똑같은.NET 실행파일을 동작시킬 수 있었습니다. 정말 좋지요? 물론, 모든 .NET에 기반을 둔 프로그램이 Win32와 x64에서 컴파일 하지 않고, 동일하게 동작 한다는 보장은 없습니다만, 다만 합리적인 범위의 시간 안에 그냥 동작합니다.
 

만 약 여러분의 코드가 명시적으로 native code를 호출한다면 (예를 들어서, C# 혹은 Visual Basic 에서의 P/Invoke) 64비트 CLR에서 실행 시에 문제에 부딫힐 확률이 커집니다. 그러나, 다행스럽게도 컴파일러 스위치에 여러분의 코드가 어떤 플랫폼에서 동작할지를 명시하는 부분이 있습니다. 예를 들어서, 여러분은 64비트 CLR이 존재함에도 불구하고, 여러분의 코드가 WOW64에서 동작하기를 원한다고 명시할 수도 있습니다.


최종 정리

 

모 든 것을 고려해 보아도, x64 버젼의 윈도우로 이동하는 것은, 비교적 저에게는 어렵지 않은 과정이었습니다. 일단, 여러분이 운영체제 구조와 툴의 상대적으로 미미한 차이점을 한 번 훑어 보기만 하면, 하나의 코드를 바탕으로 해서 두 개의 플랫폼에서 동작하는 것이 어렵지 않습니다. Visual Studio 2005가 이 노력의 과정을 훨씬 쉽게 만들어 주고 있고, x64에 특화된 디바이스 드라이버나 툴, 예를 들면 Sysinternals.com의 Process Explorer은 매일 새롭게 나타나고 있습니다, 그러니 이 쪽으로 뛰어 들지 않을 이유가 없습니다!


2007/05/09 13:50 2007/05/09 13:50
이 글에는 트랙백을 보낼 수 없습니다


저자 Greg Murray

AJAX는 'Asynchronous JavaScript and XML'의 머리글자를 딴 것으로, 웹 애플리케이션이 웹 페이지에 대한 사용자 인터랙션을 효율적으로 처리할 수 있도록 하는 수단을 제공한다(사용자 인터랙션이 이루어질 때마다 페이지를 리프레시(새로 고침)하거나 전체 페이지를 리로드하는 번거로움을 덜어줌). 이는 또한 브라우저를 이용한 리치 비헤이비어(rich behavior)를 가능케 해준다(데스크톱 애플리케이션 또는 플러그인 기반 애플리케이션의 경우와 유사). AJAX 인터랙션은 백그라운드에서 비동기적으로 처리되고, 그 동안 사용자는 페이지에 대한 작업을 계속할 수 있다. AJAX 인터랙션은 웹 페이지 내의 JavaScript에 의해 시작되는데, AJAX 인터랙션이 완료되면 JavaScript는 페이지의 HTML 소스를 업데이트한다. 변경 작업은 페이지 리프레시 없이 즉시 이루어진다. 이 AJAX 인터랙션은 서버측 논리를 이용한 폼 엔트리 검증(사용자가 입력하는 동안), 서버의 상세 데이터 검색, 페이지 상의 데이터에 대한 동적 업데이트, 그리고 페이지에서 폼을 부분적으로 제출하는 등의 작업에 이용될 수 있다.

여기서 특히 흥미를 끄는 부분은 AJAX 애플리케이션이 별도의 플러그인을 요구하지 않으며 플랫폼/브라우저 중립적 특성을 지니고 있다는 점이다. 첨언하자면, 구형 브라우저에서는 AJAX가 충분히 지원되지 않으며, 브라우저간의 차이를 유발하는 클라이언트측 스크립트를 작성할 때는 주의를 기울여야 한다. 따라서 브라우저의 차이를 추상화(abstract)하는 JavaScript 라이브러리를 사용하거나 경우에 따라서는 대체 인터랙션 기법을 이용하여 구형 브라우저를 지원하는 것도 좋은 방법이 될 수 있다. 자세한 내용은 자바 개발자를 위한 AJAX FAQ(영문)를 참조할 것.

자바 기술은 어떤 작업에 적합한가?

자바 기술과 AJAX는 서로 궁합이 잘 맞는다. 자바 기술은 AJAX 인터랙션을 위한 서버측 프로세싱 기능을 제공하는데, 이는 서블릿, JSP(JavaServer Pages) 기술, JSF(JavaServer Faces) 기술, 웹 서비스 등을 통해 제공될 수 있다. AJAX 요청 처리를 위한 프로그래밍 모델은 종래의 웹 애플리케이션에서 사용하던 것과 동일한 API를 사용한다. JSF 기술은 클라이언트측 JavaScript와 그에 대응하는 서버측 AJAX 프로세싱 코드를 작성하는 재사용 가능 컴포넌트를 생성하는 데 사용될 수 있다. 이제 AJAX와 서블릿의 활용 예제를 살펴보기로 하자.

자동 완성(autocomplete) 예제

사용자가 종업원에 관한 정보를 검색할 수 있는 웹 페이지를 상상해보자. 이 페이지에는 사용자가 종업원의 이름을 입력할 수 있는 필드가 포함되어 있다. 이 예제에서 엔트리 필드는 자동 완성(autocomplete) 기능을 가지고 있는데, 다시 말해 사용자가 종업원 이름의 일부를 입력하면 웹 애플리케이션은 입력한 문자로 이름이나 성이 시작되는 모든 종업원을 열거하여 이름을 자동으로 완성하게 된다. 자동 완성 기능은 사용자가 종업원의 정식 이름을 일일이 기억하거나 다른 페이지에서 이름을 찾아보아야 하는 번거로움을 덜어준다.

autocomplete example

검색 필드의 자동 완성 기능은 AJAX를 이용해서 구현될 수 있으며, 이를 위해서는 클라이언트와 서버 상에 코드를 제공해야 한다.

클라이언트 상에서

먼저, 사용자가 브라우저에 의해 로드되는 페이지의 URL을 지정한다. 한편, 이 예제에서는 JSF 컴포넌트, 서블릿, 또는 JSP 페이지에 의해 생성되는 HTML 페이지가 사용되었다고 가정하자. 페이지에는 JavaScript 함수 doCompletion()의 이름으로 된 onkeyup 속성을 가지는 폼 텍스트 필드가 포함되고, 이 함수는 폼 텍스트 필드에서 키를 누를 때마다 호출된다.

    <input type="text"
          size="20"
          autocomplete="off"
          id="complete-field"
                      name="id"
          onkeyup="doCompletion();">

사용자가 폼 텍스트 필드에 문자 "M"을 입력한다고 가정해보자. 그에 대한 응답으로 doCompletion() 함수가 호출되고, doCompletion() 함수는 다시 XMLHttpRequest 오브젝트를 초기화한다.

   function initRequest(url) {
       if (window.XMLHttpRequest) {
           return new XMLHttpRequest();
       } else if (window.ActiveXObject) {
           isIE = true;
           return new ActiveXObject("Microsoft.XMLHTTP");
       }
   }

   function doCompletion() {
       if (completeField.value == "") {
           clearTable();
       } else {
           var url = "autocomplete?action=complete&id=" + 
                   escape(completeField.value);
           var req = initRequest(url);
           req.onreadystatechange = function() {
               if (req.readyState == 4) {
                   if (req.status == 200) {
                       parseMessages(req.responseXML);
                   } else if (req.status == 204){
                       clearTable();
                   }
               }
           };
           req.open("GET", url, true);
           req.send(null);
       }
   }

XMLHttpRequest 오브젝트는 현재 표준 JavaScript에 포함되지는 않지만(표준화를 위한 노력이 진행중임), 사실상의 표준이자 AJAX의 핵심이라 할 수 있다. 이 오브젝트는 HTTP를 통해 서버측 컴포넌트(이 경우에는 서블릿)와 상호 작용하는 부분을 담당한다.

XMLHttpRequest 오브젝트 생성 시 URL, HTTP 메소드(GET 또는 POST), 그리고 상호작용의 비동기 여부 등 세 가지 매개변수가 지정된다. XMLHttpRequest 예제에서 매개변수는 다음과 같다.

  • URL autocomplete 및 전체 필드(complete-field)의 텍스트(M 문자):
         var url = "autocomplete?action=complete&id=" + 
                 escape(completeField.value);
    
  • GET(HTTP 인터랙션이 GET 메소드를 사용함을 의미) 및 true(인터랙션이 비동기적임을 의미):
         req.open("GET", url, true);
    

비동기 호출을 이용할 때는 callback 함수를 설정해야 하는데, XMLHttpRequestreadyState 속성이 변경될 경우 이 callback 함수는 HTTP 인터랙션 과정의 특정 포인트에서 비동기적으로 호출된다. 예제에서 callback 함수는 processRequest()이며, 함수에 대해 XMLHttpRequest.onreadystatechange 속성으로 설정된다. readState가 "4"’일 경우 parseMessages 함수에 대한 호출에 주목할 것. "4"의 XMLHttpRequest.readyState는 HTTP 인터랙션이 성공적으로 완수되었음을 나타낸다.

XMLHttpRequest.send()가 호출되면 HTTP 인터랙션이 시작되고, 인터랙션이 비동기적이면 브라우저는 계속해서 페이지의 이벤트를 처리한다.

서버 상에서

XMLHttpRequest는 URL 자동 완성에 대해 HTTP GET을 요청하고, autocomplete라 불리는 서블릿으로의 매핑이 수행된다. 그리고, AutoComplete 서블릿의 doGet() 메소드가 호출된다. 다음은 doGet() 메소드의 형태이다.

   public void doGet(HttpServletRequest request, 
           HttpServletResponse response) 
        throws IOException, ServletException { 
       ... 
       String targetId = request.getParameter("id"); 
       Iterator it = employees.keySet().iterator(); 
       while (it.hasNext()) { 
           EmployeeBean e = (EmployeeBean)employees.get(
                   (String)it.next()); 
           if ((e.getFirstName().toLowerCase().startsWith(targetId) || 
              e.getLastName().toLowerCase().startsWith(targetId)) 
              && !targetId.equals("")) { 
              sb.append("<employee>"); 
              sb.append("<id>" + e.getId() + "</id>"); 
              sb.append("<firstName>" + e.getFirstName() + 
                      "</firstName>"); 
              sb.append("<lastName>" + e.getLastName() + 
                      "</lastName>"); 
              sb.append("</employee>"); 
              namesAdded = true; 
           } 
       } 
       if (namesAdded) { 
           response.setContentType("text/xml"); 
           response.setHeader("Cache-Control", "no-cache"); 
           response.getWriter().write("<employees>" + 
                   sb.toString() + "</employees>"); 
       } else { 
           response.setStatus(HttpServletResponse.SC_NO_CONTENT); 
       } 
    }

이 서블릿을 보면 알 수 있듯이, AJAX 처리를 위해 서버측 코드 작성 방법을 배우는 데 필요한 새로운 내용은 전혀 나와있지 않다. XML 문서를 교환하고자 할 경우에 대비해서 응답 컨텐트 유형을 text/xml로 설정해야 하는데, AJAX의 경우에는 평문(plain text) 또는 심지어 클라이언트 상의 callback 함수에 의해 평가되거나 실행될 수 있는 JavaScript의 단편도 교환이 가능하다. 일부 브라우저는 결과를 캐시할 수 있으므로 Cache-Control HTTP 헤더를 no-cache로 설정할 필요가 있을 수 있다는 점에도 역시 유의할 것. 이 예제에서 서블릿은 이름이나 성이 문자 M으로 시작되는 모든 종업원을 포함하는 XML 문서를 생성한다. 다음은 호출을 한 XMLHttpRequest 오브젝트로 반환되는 XML 문서의 예제이다.

   <employees>
      <employee>
        <id>3</id>
        <firstName>George</firstName>
        <lastName>Murphy</lastName>
      </employee>
      <employee>
        <id>2</id>
        <firstName>Greg</firstName>
        <lastName>Murphy</lastName>
      </employee>
      <employee>
        <id>11</id><firstName>Cindy</firstName>
        <lastName>Murphy</lastName>
        </employee>
      <employee>
        <id>4</id>
        <firstName>George</firstName>
        <lastName>Murray</lastName>
      </employee>
      <employee>
        <id>1</id>
        <firstName>Greg</firstName>
        <lastName>Murray</lastName>
     </employee>
   </employees>

다시 클라이언트로

처음 호출을 한 XMLHttpRequest 오브젝트가 응답을 받을 경우, parseMessages() 함수가 호출된다(자세한 내용은 이 예제의 앞 부분에 있는 XMLHttpRequest의 초기화를 참조). 다음은 parseMessages() 함수의 모습이다.

   function parseMessages(responseXML) {
       clearTable();
           var employees = responseXML.getElementsByTagName(
                   "employees")[0];
       if (employees.childNodes.length > 0) {
           completeTable.setAttribute("bordercolor", "black");
           completeTable.setAttribute("border", "1");
       } else {
           clearTable();
       }
    
       for (loop = 0; loop < employees.childNodes.length; loop++) {
           var employee = employees.childNodes[loop];
           var firstName = employee.getElementsByTagName(
                   "firstName")[0];
           var lastName = employee.getElementsByTagName(
                   "lastName")[0];
           var employeeId = employee.getElementsByTagName(
                   "id")[0];
           appendEmployee(
                   firstName.childNodes[0].nodeValue,
                   lastName.childNodes[0].nodeValue, 
                   employeeId.childNodes[0].nodeValue);
       }
   }

parseMessages() 함수는 AutoComplete 서블릿이 반환한 XML 문서의 오브젝트 표현을 매개변수로 수신하는데, 이 함수는 XML 문서를 프로그램적으로 traverse한 다음 결과를 이용하여 HTML 페이지의 컨텐츠를 업데이트한다. 이 작업은 XML 문서 내의 이름에 대한 HTML 소스를 ID가 "menu-popup"’인 <div> 엘리먼트로 inject함으로써 수행된다.

   <div style="position: absolute; 
      top:170px;left:140px" id="menu-popup">

사용자가 문자를 더 많이 입력할수록 목록 길이는 줄어들게 되고, 이어서 사용자는 여러 이름 중 하나를 클릭할 수 있다.

이제 여러분은 AJAX가 단순히 페이지의 백그라운드에서 HTTP를 통해 정보를 교환하고 결과를 토대로 해당 페이지를 동적으로 업데이트한다는 것을 알게 되었을 것이다. AJAX와 자바 기술에 관한 자세한 내용은 테크니컬 아티클 Asynchronous JavaScript Technology and XML (AJAX) With Java 2 Platform, Enterprise Edition(영문)을 참조하기 바란다. 아울러 AJAX BluePrints 페이지(영문)와 Greg Murray의 블로그에 실려 있는 AJAX FAQ for the Java Developer(영문)의 내용도 함께 참조할 것.

예제 코드 실행하기

본 팁에는 본문에서 다루어진 기법을 예시하는 예제 패키지가 첨부되어 있는데, Servlet 2.4 이상의 API를 지원하는 웹 컨테이너라면 모두 예제 패키지 설치가 가능하다. 예제를 설치하고 실행하려면 다음 단계를 따르도록 한다.

  1. 먼저 GlassFish Project 페이지(영문)에 서 GlassFish를 다운로드해야 하는데, GlassFish는 Servlet 2.5와 JSTL(JSP Standard Tag Library)을 곧바로 지원한다. 만약 J2EE 1.4 또는 Servlet 2.4 컨테이너를 사용하고 있다면 JSTL JAR 파일을 web/WEB-INF/lib 디렉터리에 포함시켜야 할 수도 있다.
  2. 다음의 환경 변수를 설정한다.
    • GLASSFISH_HOME. This should point to where you installed GlassFish (for example C:\Sun\AppServer) GLASSFISH_HOME. GlassFish 설치 장소를 가리켜야 한다(가령 C:\Sun\AppServer).
    • ANT_HOME. ant 설치 장소를 가리켜야 한다. ant는 다운로드한 GlassFish 번들에 포함되어 있다. (Windows의 경우에는 lib\ant 서브디렉터리 내에 위치함.)
    • JAVA_HOME. 시스템에서의 JDK 5.0 위치를 가리켜야 한다.

    아울러, ant 위치를 PATH 환경 변수에 추가한다.

  3. 예제 파일을 다운로드하여 압축을 해제한다. 이제 새로 추출된 디렉터리가 <install_dir>\ajax-autocomplete로 표시되어야 하는데, 예를 들어 Windows 컴퓨터의 C:\에 압축을 풀었다면 새로 생성된 디렉터리는 C:\ajax-autocomplete가 되어야 한다

  4. ajax-autocomplete 디렉터리로 이동해서 build.properties 파일에 build.properties.sample을 복사한다.

  5. build.properties 파일을 열고 Servlet 2.4 이상의 API를 포함하는 JAR 파일에 servlet.jar 속성을 설정한다. GlassFish의 경우 JAR 파일은 <gf_install_dir>/glassfish/lib/javaee.jar인데, 여기서 <gf_install_dir>은 GlassFish가 설치된 곳이다. javaee.autodeploy를 웹 컨테이너가 애플리케이션을 자동 설치할 디렉터리로 설정한다. GlassFish의 경우 이 디렉터리는 <gf_install_dir>/glassfish/domains/domain1/autodeploy이다.

  6. 6. 다음 명령어를 입력하여 GlassFish를 시작한다.
    <GF_install_dir>\bin\asadmin start-domain domain1
    
    이 때, <GFinstall_dir>은 Glassfish가 설치된 디렉터리이다.

  7. ant 툴을 이용하여 애플리케이션을 구축하고 설치한다. Glassfish는 /glassfish/bin/asant 디렉터리에 ant의 사본을 가지고 있고, 또한 Apache Ant Project 페이지(영문)에서 ant를 다운로드할 수도 있다.

    애플리케이션을 구축하려면 다음 명령어를 입력한다.
          ant
    
    애플리케이션을 설치하려면 다음 명령어를 입력한다.
          ant deploy
    
  8. 브라우저를 다음의 URL로 연다: http://localhost:8080/ajax-autocomplete/.

    NetBeans 4.1 이상을 사용하고 있다면 예제가 포함되어 있을 것이고, Help -> BluePrints Solutions Catalog를 선택하여 툴에서 이를 실행할 수 있다. 여기서 AJAX -> Autocomplete 예제를 선택하면 된다. 이제 NetBeans 내에서 예제를 실행할 수 있으며 원할 경우 수정도 가능하다.

    예제를 실행하면 다음과 같은 모습이 된다.
    example autocomplete

    목록의 이름을 클릭하면 종업원에 관한 정보가 표시된다.
    example autocomplete - Employee Info
2007/02/01 16:41 2007/02/01 16:41
이 글에는 트랙백을 보낼 수 없습니다
How to be a Programmer: A Short, Comprehensive, and Personal Summary

프로그래머가 되는 방법: 짧고 폭넓고 개인적인 요약



Copyright © 2002, 2003 Robert L. Read

Permission is granted to copy, distribute and/or modify this document under the terms of the GNU Free Documentation License, Version 1.2 or any later version published by the Free Software Foundation; with one Invariant Section being 'History (As of May, 2003)', no Front-Cover Texts, and one Back-Cover Text: 'The original version of this document was written by Robert L. Read without renumeration and dedicated to the programmers of Hire.com.' A copy of the license is included in the section entitled 'GNU Free Documentation License'.

목차

Contents

[-]
1 도입
2 초보자
2.1 개인적 기능들
2.1.1 디버그 배우기
2.1.2 문제 공간을 나눠서 디버그 하는 방법
2.1.3 오류를 제거하는 방법
2.1.4 로그를 이용해서 디버그 하는 방법
2.1.5 성능 문제를 이해하는 방법
2.1.6 성능 문제를 해결하는 방법
2.1.7 반복문을 최적화하는 방법
2.1.8 I/O 비용을 다루는 방법
2.1.9 메모리를 관리하는 방법
2.1.10 가끔씩 생기는 버그를 다루는 방법
2.1.11 설계 기능을 익히는 방법
2.1.12 실험을 수행하는 방법
2.2 팀의 기능들
2.2.1 시간 추정이 중요한 이유
2.2.2 프로그래밍 시간을 추정하는 방법
2.2.3 정보를 찾는 방법
2.2.4 사람들을 정보의 원천으로 활용하는 방법
2.2.5 현명하게 문서화하는 방법
2.2.6 형편없는 코드를 가지고 작업하기
2.2.7 소스 코드 제어 시스템을 이용하는 방법
2.2.8 단위별 검사를 하는 방법
2.2.9 막힐 때는 잠깐 쉬어라
2.2.10 집에 갈 시간을 인지하는 방법
2.2.11 까다로운 사람들과 상대하는 방법
3 중급자
3.1 개인적 기능들
3.1.1 의욕을 계속 유지하는 방법
3.1.2 널리 신뢰받는 방법
3.1.3 시간과 공간 사이에서 균형을 잡는 방법
3.1.4 압박 검사를 하는 방법
3.1.5 간결성과 추상성의 균형을 잡는 방법
3.1.6 새로운 기능을 배우는 방법
3.1.7 타자 연습
3.1.8 통합 검사를 하는 방법
3.1.9 의사소통을 위한 용어들
3.2 팀의 기능들
3.2.1 개발 시간을 관리하는 방법
3.2.2 타사 소프트웨어의 위험 부담을 관리하는 방법
3.2.3 컨설턴트를 관리하는 방법
3.2.4 딱 적당하게 회의하는 방법
3.2.5 무리 없이 정직하게 반대 의견을 내는 방법
3.3 판단 능력
3.3.1 개발 시간에 맞춰 품질을 조절하는 방법
3.3.2 소프트웨어 시스템의 의존성을 관리하는 방법
3.3.3 소프트웨어의 완성도를 판단하는 방법
3.3.4 구입과 개발 사이에서 결정하는 방법
3.3.5 전문가로 성장하는 방법
3.3.6 면접 대상자를 평가하는 방법
3.3.7 화려한 전산 과학을 적용할 때를 아는 방법
3.3.8 비기술자들과 이야기하는 방법
4 상급자
4.1 기술적 판단 능력
4.1.1 어려운 것과 불가능한 것을 구분하는 방법
4.1.2 내장 언어를 활용하는 방법
4.1.3 언어의 선택
4.2 현명하게 타협하기
4.2.1 작업 일정의 압박과 싸우는 방법
4.2.2 사용자를 이해하는 방법
4.2.3 진급하는 방법
4.3 팀을 위해 일하기
4.3.1 재능을 개발하는 방법
4.3.2 일할 과제를 선택하는 방법
4.3.3 팀 동료들이 최대한 능력을 발휘하게 하는 방법
4.3.4 문제를 나누는 방법
4.3.5 따분한 과제를 다루는 방법
4.3.6 프로젝트를 위한 지원을 얻는 방법
4.3.7 시스템이 자라게 하는 방법
4.3.8 대화를 잘 하는 방법
4.3.9 사람들에게 듣고 싶어 하지 않는 말을 하는 방법
4.3.10 관리상의 신화들을 다루는 방법
4.3.11 조직의 일시적 혼돈 상태를 다루는 방법
5 참고 문헌
5.1 책
5.2 웹 사이트
6 역사 (2003년 5월 현재) / History (As Of May, 2003)
6.1 피드백 및 확장 요청 / Request for Feedback or Extension
6.2 원본 / Original Version
6.3 원저자의 경력 / Original Author's Bio

1 도입

좋은 프로그래머가 되는 것은 어렵고도 고상한 일이다. 소프트웨어 프로젝트의 공동 비전을 현실화하려고 할 때 가장 어려운 부분은 함께 일하는 개발자들과 고객들을 상대하는 일이다. 컴퓨터 프로그램을 짜는 것은 중요한 일이고 지식과 기능이 많이 드는 일이다. 하지만 그것은 좋은 프로그래머가 고객 및 자기가 크고 작게 책임을 지고 있는 수많은 동료들을 만족시키는 소프트웨어 시스템을 만들기 위해 해야 하는 다른 모든 일들에 비교해 볼 때 정말 어린아이 장난과 같다. 나는 내가 스물 한 살이었을 때 누군가가 나에게 설명해 주길 바랐던 것들을 가능한 한 간결하게 요약하려고 했다.

이것은 매우 주관적이며, 따라서 이 글은 개인적이고 다소 고집스럽게 보일 수밖에 없다. 이 글은 프로그래머가 일하면서 맞부딪치기 아주 쉬운 문제들에 한정되어 있다. 이런 문제들과 이에 대한 해결책은 사람 사는 데서 흔히 볼 수 있기 때문에 이 글이 설교처럼 보일 수도 있다. 그럼에도 불구하고 이 글이 유용하게 쓰이길 바란다.

컴퓨터 프로그래밍은 여러 강좌를 통해 배울 수 있다. The Pragmatic Programmer <Prag99>, Code Complete <CodeC93>, Rapid Development <RDev96>, Extreme Programming Explained <XP99> 등의 훌륭한 책을 통해 컴퓨터 프로그래밍에 대해 배우고, 좋은 프로그래머란 무엇인가에 대한 다양한 논점들을 알게 된다. 폴 그레이엄(Paul Graham) <PGSite>과 에릭 레이먼드(Eric Raymond) <Hacker>의 글은 이 글을 읽기 전이나 읽는 도중에 꼭 읽어 보아야 한다. 이 글은 이상의 훌륭한 글들과 달리 사회 활동의 문제를 강조하고 있으며 내가 보기에 꼭 필요하다고 생각하는 모든 기능들을 폭넓게 요약하고 있다.

이 글에서 "상사"는 나에게 프로젝트를 배정해 주는 사람을 의미한다. 사업, 회사, 부족(tribe)이라고 할 때, 사업이 돈을 버는 것, 회사가 현시대의 일터, 부족이 충성심을 공유하는 사람들이라는 뜻을 내포하는 것 외에는 모두 같은 뜻으로 사용했다.

우리 부족에 온 것을 환영한다.

2 초보자

2.1 개인적 기능들

2.1.1 디버그 배우기

디버깅은 프로그래머의 기본이다. 디버그란 말의 처음 뜻은 오류를 제거하는 것이지만, 더 중요한 것은 프로그램이 실행될 때 그것을 상세히 검사하는 일이다. 효과적으로 디버그 할 줄 모르는 프로그래머는 앞을 못 보는 것과 같다.

이상주의자라면 설계, 분석, 복잡도 이론 등등이 더 기본적인 것이라고 생각할 것이다. 하지만 이들은 현업의 프로그래머가 아니다. 현업의 프로그래머는 이상적인 세계에서 살고 있지 않다. 완벽한 프로그래머라 해도, 그는 대형 소프트웨어 회사, GNU 등의 조직, 자기 동료들이 만든 코드들에 둘러싸여 있고 그것을 가지고 작업해야 한다. 이 코드들은 대부분 불완전하며 불완전하게 문서화되어 있다. 어떤 코드가 실행될 때 그것을 꿰뚫어 볼 수 있는 능력이 없다면 사소한 문제에도 대책 없이 나가떨어질 수밖에 없다. 이러한 투시력은 실험해 보는 것을 통해 얻어지며, 그것이 바로 디버깅이다.

디버깅은 프로그램의 실행에 대한 것이지 프로그램 자체에 대한 것이 아니다. 대형 소프트웨어 회사에서 프로그램을 구입했다면 보통은 프로그램을 들여다볼 수 없다. 하지만 그 코드가 문서대로 동작하지 않거나 (컴퓨터가 멈춰 버리는 것은 아주 흔하고 극적인 예이다) 문서에 원하는 내용이 없는 일은 항상 일어난다. 더 흔한 것은, 오류가 생겨서 자기가 짠 코드를 검사하는데 어떻게 그런 오류가 생길 수 있는지 전혀 실마리를 잡을 수 없는 경우이다. 당연히 이것은 그 프로그래머가 짐작하고 있는 것 중 어떤 것이 잘못됐거나, 예상하지 못했던 상황이 발생하기 때문이다. 가끔은 소스 코드 응시하기 마법으로 문제를 해결할 수 있다. 하지만 그 마법이 통하지 않을 때는 디버그를 해야 한다.

프로그램의 실행에 대한 투시력을 얻기 위해서는 코드를 실행하면서 무엇인가를 관찰해야 한다. 어떤 경우에는 그것이 화면에 나타나서 눈으로 볼 수도 있지만, 다른 많은 경우에는 코드 내의 변수의 상태, 현재 실행되고 있는 코드의 줄 수, 복잡한 자료 구조에서 어떤 검증 조건(assertion)이 계속 유지되는가의 여부 등과 같이 눈에 보이지 않는다. 이런 숨겨진 것들은 드러나야 한다. 실행되고 있는 프로그램의 내부를 들여다보기 위해 널리 쓰이는 방법을 다음과 같이 분류할 수 있다.
  • 디버깅 도구 이용
  • 프린트 줄 넣기(printlining) -- 프로그램을 임시로 고치는 것, 특히 필요한 정보를 프린트하는 줄을 추가하는 것
  • 로그 기록 -- 로그 파일의 형태로 프로그램을 들여다볼 수 있는 영구적인 창을 만드는 것

디버깅 도구는 제대로 동작한다면 훌륭한 것이지만, 그 다음의 두 가지 방법이 더욱 더 중요하다. 디버깅 도구는 종종 언어의 발달을 따라가지 못하기 때문에 어떤 시점이 되면 쓸모 없게 될 수 있다. 그리고 디버깅 도구 자체가 프로그램의 수행 방식을 미묘하게 변화시킬 수도 있기 때문에 모든 경우에 유효한 것이 아니다. 끝으로, 대규모 자료 구조에 대한 검증 조건을 검사하는 것과 같은 디버깅의 경우에는 어떻든 간에 코드를 새로 짜야 한다. 디버깅 도구가 안정적이라면 그것의 사용 방법을 아는 것이 좋은 일이겠지만, 그 다음의 두 가지 방법을 쓸 줄 아는 것은 필수불가결한 일이다.

어떤 초보자는 코드를 고치고 실행하는 일을 반복해야 하는 디버깅에 대한 두려움이 잠재의식 속에 있는 것 같다. 이해할 만한 일이다. 이것은 처음 외과 수술을 하는 것과 비슷해 보인다. 하지만 초보자들은 코드가 쌩쌩 돌아가게 하기 위해 여기저기 찔러 보는 것에 익숙해져야 한다. 그들은 코드를 가지고 실험해 보는 것에 익숙해져야 하고, 자기가 코드를 가지고 무엇을 하더라도 그것이 문제를 악화시키지 않는다는 것을 배워야 한다. 이 소심한 사람들의 교사나 사수라면, 어떻게 해야 하는지 친절하게 알려 주고 손이라도 잡아 이끌면서 그 두려움을 극복할 수 있도록 도와 주라. 그 두려움 때문에 좋은 프로그래머가 될 수 있는 사람들도 아슬아슬하게 시작했다가 포기하는 경우가 많다.

2.1.2 문제 공간을 나눠서 디버그 하는 방법

디버깅은 수수께끼에서 출발하기 때문에 재미있다. 이렇게 디버그 하면 저렇게 될 것이라고 생각하지만 실제로는 다른 결과가 생긴다. 디버깅은 만만한 일이 아니다. 여기에서 무슨 사례를 제시하든 그것은 실제 상황에 비하면 상당히 부자연스운 것이 될 것이다. 디버깅은 창의력과 독창성이 필요하다. 디버깅에 한 가지 열쇠가 있다면 그것은 수수께끼에 대한 분할 정복 기법(divide and conquer technique)을 사용하는 것이다.

예를 들어 열 가지 일을 차례로 하는 프로그램을 만들었다고 하자. 그런데 실행해 보니 멈춰 버렸다. 멈추도록 프로그램하지 않았는데 말이다. 이제 "프로그램이 멈춘다"는 수수께끼가 생긴 것이다. 출력된 결과를 보면 처음 7번까지는 제대로 실행된 것을 알 수 있다. 나머지 세 가지가 출력 결과에서 안 보인다. 이제 우리의 수수께끼는 "프로그램이 8번이나 9번이나 10번에서 멈췄다"로 줄어들었다.

그럼 프로그램이 어디에서 멈췄는지 알아볼 수 있는 실험을 설계할 수 있을까? 물론이다. 디버거를 쓸 수도 있고 8번과 9번 다음에 프린트 줄을 넣을 수도 있다. (물론 사용하는 언어에 적합한 다른 방법을 쓸 수도 있다.) 그리고 다시 실행해 보면 우리의 수수께끼는 "프로그램이 9번에서 멈췄다"와 같이 더 줄어든다. 어느 순간에든 수수께끼가 정확히 무엇인지 기억하는 것은 집중하는 데 도움이 된다. 여러 사람이 급하게 어떤 문제에 매달려 있을 때는 그 일이 무척 혼란스러워질 수 있다.

분할 정복이라는 열쇠는 디버깅 기법일 뿐만 아니라 알고리듬 설계 기법이기도 하다. 수수께끼의 중간을 둘로 나누는 것만으로 일 처리를 잘 할 수 있다면, 더 이상 많이 나눌 필요는 없을 것이고 디버깅도 더 빨리 끝날 것이다. 그런데 수수께끼의 중간이란 어디쯤을 말하는 것인가? 여기가 바로 창의력과 경험이 필요한 지점이다.

아직 초보인 사람에게는, 모든 오류가 존재하는 공간이 소스 코드의 모든 몇 줄뿐인 것처럼 보일 것이다. 그는 아직 프로그램의 다른 차원, 즉, 줄들이 실행되는 공간, 자료 구조, 메모리 관리, 외부 코드와 상호작용, 문제가 생길 만한 코드와 간단한 코드 등을 볼 수 있는 감각이 없다. 이런 다른 차원들은 경험이 쌓인 프로그래머에게 문제를 일으킬 수 있는 모든 것들에 대해 완벽하지는 않지만 매우 유용한 머리 속의 모형(mental model)을 형성하게 해 준다. 이런 모형을 머리 속에 갖고 있으면 수수께끼의 중간이 어디인지 효과적으로 찾는 데 도움이 된다.

문제를 일으킬 수 있는 모든 것들의 공간을 둘로 균등하게 나눴다면, 이제는 그 둘 중 어느 쪽에서 오류가 생겼을지 결정해야 한다. 수수께끼가 "프로그램을 멈추게 하는 그 줄은 이 줄이 실행되기 전에 실행됐을까, 후에 실행됐을까?"와 같이 단순한 경우에는 어느 줄이 실행되는지 관찰하기만 하면 된다. 다른 경우에는 수수께끼가 이런 식으로 분할될 것이다. "저 그래프에 잘못된 노드를 가리키는 포인터가 있거나, 그 그래프에 변수들을 추가하는 알고리듬에 문제가 있다." 이런 경우에는 분할된 수수께끼 중 어느 쪽을 버릴 것인지 결정하기 위해 그 그래프의 포인터들이 모두 정확한지 알아보는 작은 프로그램을 작성해야 할 수도 있다.

2.1.3 오류를 제거하는 방법

나는 의도적으로 프로그램의 실행을 점검하는 행위와 오류를 고치는 행위를 구분하고 있다. 물론 디버깅은 버그를 제거하는 것을 뜻한다. 이상적으로는 코드를 완벽하게 이해하여 오류의 정체와 그것을 고칠 방법을 완벽하게 알게 되면서 "아하!" 하고 외치는 순간에 이를 수도 있다. 하지만 문서화가 잘 되어 있지 않아 그 속을 들여다 볼 수 없는 시스템들을 가지고 프로그램을 만드는 경우도 종종 있으므로 이런 일이 항상 가능한 것은 아니다. 또한 코드가 너무 복잡해서 그것을 완벽하게 이해할 수 없는 경우도 있다.

버그를 고칠 때에는 가능한 한 조금만 수정하여 버그를 고치고 싶을 것이다. 그러면서 성능 개선이 필요한 다른 것들을 보게 될 수도 있다. 하지만 이것들을 동시에 고치지는 말라. 한 번에 단 한 가지만 변경하는 과학 실험 방법을 사용하도록 하라. 이를 위한 최선의 과정은, 그 버그를 쉽게 다시 확인할 수 있게 되면, 고친 내용을 바꿔 넣고 나서, 프로그램에서 버그가 더 이상 없다는 것을 확인하는 것이다. 물론 때때로 한 줄 이상을 고쳐야 하겠지만 그렇다 해도 개념적으로는 더 이상 나눌 수 없는(atomic) 한 부분만 변경해서 버그를 고쳐야 한다.

실제로 버그가 여러 개인데 그것들이 하나인 것처럼 보이는 경우도 있다. 버그를 어떻게 정의하여 그것들을 하나씩 고쳐 갈 것인지 결정하는 것은 결국 프로그래머의 몫이다. 프로그램이 무엇을 해야 하는지, 또는 원래 개발자가 의도했던 것이 무엇인지 불분명할 경우도 있다. 그런 경우에는 경험을 근거로 판단을 내리고 그 코드에 자기 나름대로 의미를 부여해야 할 것이다. 그 프로그램이 무엇을 해야 할지 결정하고, 그것에 대해 주석을 달거나 어떤 식으로든 명료화하고, 그 코드가 그 의미에 부합하도록 만든다. 이것은 중급에서 고급의 기능으로서 처음부터 새로운 함수를 작성하는 것보다 더 어려울 경우도 있지만, 현업에서는 이런 귀찮은 일이 종종 생긴다. 어쩌면 자신에게 수정 권한이 없는 시스템을 고쳐야 하게 될지도 모른다.

2.1.4 로그를 이용해서 디버그 하는 방법

로그 기록(logging)이란 정보를 제공하는 일련의 기록인 로그(log)를 생성하도록 시스템을 작성하는 활동을 말한다. 프린트 줄 넣기(printlining)는 간단한, 보통은 일시적인, 로그를 생성하기만 한다. 완전한 초보자들은 프로그래밍에 대해 아는 것에 한계가 있기 때문에 로그를 이해하고 사용해야 한다. 시스템 설계자들은 시스템의 복잡성 때문에 로그를 이해하고 사용해야 한다. 로그가 제공하는 정보의 양은, 이상적으로는 프로그램이 실행되는 중에도, 설정 가능해야 한다. 일반적으로 로그 기록은 다음의 이점이 있다.
  • 로그는 재현하기 힘든 (예를 들어, 개발 완료된 환경에서는 발생하지만 테스트 환경에서는 재현할 수 없는) 버그에 대한 유용한 정보를 제공할 수 있다.
  • 로그는, 예를 들어, 구문(statement)들 사이에 걸리는 시간과 같이, 성능에 관한 통계와 정보를 제공할 수 있다.
  • 설정이 가능할 때, 로그는 예기치 못한 특정 문제들을 디버그하기 위해, 그 문제들을 처리하도록 코드를 수정하여 다시 적용하지(redeploy) 않아도, 일반적인 정보를 갈무리할 수 있게 한다.

로그에 남기는 정보의 양은 항상 정보성과 간결성 사이의 타협으로 결정된다. 정보를 너무 많이 남긴다면 로그가 낭비적이 되고 스크롤에 가려지게(scroll blindness) 되어, 필요한 정보를 찾기 어려워질 것이다. 너무 조금 남긴다면 필요한 정보가 남지 않을 것이다. 이런 점에서, 무엇이 출력될지 설정할 수 있게 하는 것은 매우 유용하다. 일반적으로 로그에 남는 기록을 통해, 그 기록을 남긴 소스 코드의 위치, (적용 가능하다면) 문제가 되는 작업을 실행한 쓰레드, 정확한 실행 시각, 그리고 일반적으로, 어떤 변수의 값, 여유 메모리의 양, 데이터 객체의 개수 등 그 밖의 유용한 정보를 알 수 있다. 이러한 로그 생성 구문들은 소스 코드 전체에 흩어져 있는데, 특히 주요 기능이 있는 지점과 위험 부담이 있는 코드 근처에 있다. 구문마다 수준이 정해질 수 있으면 시스템의 현재 설정에 따라 그 수준에 해당하는 기록만 남기게 될 것이다. 로그 생성 구문을 설계할 때에는 어디에서 문제가 생길지 예상해서 그것을 기록으로 남길 수 있게 해야 한다. 성능을 측정할 필요성도 예상하고 있어야 한다.

영구적인 로그를 남긴다면, 로그 기록이 프린트 줄 넣기(printlining)를 대신 할 수 있을 것이고, 디버그 구문들 중에도 로그 기록 시스템에 영구적으로 추가할 것들이 있을 것이다.

2.1.5 성능 문제를 이해하는 방법

실행중인 시스템의 성능을 알아내는 방법을 이해하는 일은 디버깅과 마찬가지로 피할 수 없는 일이다. 자기가 작성한 코드의 실행에 드는 비용을 완벽하고 정확하게 이해하고 있다 해도, 그 코드는 통제할 수 없거나 들여다볼 수 없는 다른 소프트웨어 시스템들을 호출할 때도 있다. 하지만 실제로 성능의 문제는 일반적으로 디버깅과는 조금 다르고 또 조금은 쉬운 문제이다.

어떤 시스템이나 하위 시스템이 너무 느린 것 같다고 가정해 보자. 그것을 빠르게 하기 전에 우선 왜 그것이 느린지에 대해 머리 속으로 모형을 만들어야 한다. 그렇게 할 수 있도록 시간과 그 밖의 자원들이 어디에 실제로 쓰이고 있는지 알기 위해 성능 기록 도구(profiling tool)나 좋은 로그 기록을 쓸 수 있다. 유명한 격언 중에 90%의 시간은 10%의 코드에 쓰인다는 말이 있다. 나는 그 격언에 성능 문제에서 입출력 시간(I/O)의 중요성을 추가하고 싶다. 종종 대부분의 시간은 이러저러한 방식으로 I/O에 쓰인다. 낭비가 심한 I/O와 그러한 10%의 코드를 찾아냈다면 한 발짝 잘 내딛은 것이다.

컴퓨터 시스템의 성능에는 여러 차원이 있고 여러 자원들이 사용된다. 측정해야 할 첫 번째 자원은 벽시계 시간(wall-clock time), 즉 계산에 걸리는 총 시간이다. 벽시계 시간을 로그에 기록으로 남기는 것은, 다른 성능 기록 방법이 통하지 않는 예상치 못한 상황에 대한 정보를 줄 수 있으므로 특히 가치가 있다. 하지만 이것으로 모든 것을 알 수는 없다. 때로는 시간이 좀 더 걸리기는 하지만 프로세서의 처리 시간을 많이 잡아먹지 않는 코드가 실제로 작업해야 하는 전산 환경에서는 훨씬 더 좋을 수 있다. 마찬가지로, 메모리, 네트웍 대역폭, 데이터베이스, 그 밖의 서버 접속들이 결국에는 프로세서 처리 시간보다 더 낭비가 클 수 있다.

동시에 사용해야 하는 공유 자원 쟁탈(contention)로 교착 상태(deadlock)나 기아 상태(starvation)가 생길 수도 있다. 교착 상태란 동기화나 자원 요청이 부적절하여 더 이상 진행할 수 없는 상태를 말한다. 기아 상태란 구성요소에 시간을 적절히 배분하는 데에 실패한 것이다. 이런 상황을 예상할 수 있다면 프로젝트를 시작할 때부터 이런 쟁탈을 측정할 방법을 마련하는 것이 최선이다. 이러한 쟁탈이 일어나지 않는다 해도 그것을 확실히 검증할 수 있게 해 놓는 것은 매우 도움이 된다.

2.1.6 성능 문제를 해결하는 방법

대부분의 소프트웨어 프로젝트는 첫 배포판을 냈을 때보다 비교적 적은 노력으로도 10배에서 100배나 더 빠르게 진행될 수 있다. 출시일의 압박 하에서는, 일을 간단하고 신속하게 끝낼 수 있는, 하지만 다른 해결책보다는 효율이 떨어지는, 해결책을 선택하는 것이 어쩌면 현명하고도 효과적인 방법일 수 있다. 하지만 성능은 사용 편이성(usability)의 일부이며, 결국에 가서는 더욱 세심하게 고려해야 할 경우가 많다.

매우 복잡한 시스템의 성능을 향상시키는 열쇠는 병목(bottleneck), 즉 대부분의 자원들이 사용되는 지점을 찾기 위해 충분히 잘 분석하는 것이다. 계산 시간의 1% 밖에 차지하지 않는 함수를 최적화하는 것은 별 의미가 없다. 실제로 시간이 어디에 쓰이는지 알아내기 위해 성능 분석을 먼저 해야 하며, 그로부터 무엇을 향상시킬 것인지 결정할 수 있다. 경험상으로 볼 때, 어떤 작업이 시스템이나 시스템의 중요한 부분을 최소한 두 배 빠르게 할 것이라고 생각되지 않는다면, 그것을 실행에 옮기기 전에 신중하게 생각해야 한다. 보통 이것을 위해 쓰는 방법이 있다. 그 변화에 따라 필요하게 되는 검사와 품질 확인의 수고를 고려하라. 모든 변화에는 검사라는 짐이 따르므로 큰 변화가 적을수록 더 좋은 것이다.

어디에선가 두 배의 향상을 달성한 후에는, 최소한 다시 생각하고 또 다시 분석하여 그 다음으로 낭비가 심한 병목이 어디인지 발견해 내고, 또 다른 두 배의 성능 향상을 이루기 위해 그 지점을 공략해야 할 것이다.

성능 상 병목은 소를 셀 때 머리를 세는 대신 다리를 센 다음 4로 나누는 것에 비유할 수 있을 것이다. 예를 들어, 나는 어떤 관계형 데이터베이스 시스템에서 자주 검색하는 열에 적절한 인덱스를 달지 않아서, 검색이 최소한 스무 배는 느려지는 오류를 일으킨 적이 있다. 그 외에도, 내부 반복문에서 불필요한 I/O를 하는 것, 더 이상 필요 없는 디버그 구문을 남겨 놓는 것, 불필요한 메모리 할당, 성능에 대해 제대로 문서화되어 있지 않은 라이브러리나 그 밖의 하위 시스템들을 전문적인 안목 없이 사용하는 것 등을 예로 들 수 있을 것이다. 이런 식의 성능 향상을, 쉽게 따서 성과를 낼 수 있다는 의미에서, 낮게 달린 과일(low-hanging fruit)이라고 부르기도 한다.

낮게 달린 과일들을 거의 다 따 버렸다면 어떻게 할 것인가? 아마도 더 높이 손을 뻗거나 나무를 베어 내릴 것이다. 즉, 조그만 성능 향상을 계속해 갈 수도 있고, 시스템이나 하위 시스템을 진지하게 재설계할 수도 있을 것이다. (이것은, 새로운 설계라는 측면뿐만 아니라 자기 상사에게 이것이 좋은 생각이라는 것을 설득한다는 측면에서도 좋은 프로그래머로서 자신의 능력을 발휘할 수 있는 훌륭한 기회이다.) 하지만, 재설계를 주장하기 전에는 이것이 하위 시스템을 다섯 배에서 열 배는 더 낫게 할 수 있는지 스스로 질문해 봐야 한다.

2.1.7 반복문을 최적화하는 방법

때때로 제품에서 실행하는 데 시간이 오래 걸리거나 병목(bottleneck)이 되는 반복문이나 재귀함수를 보게 될 것이다. 그 반복문을 조금 빠르게 고치려고 하기 전에, 혹시 그것을 완전히 제거할 방법은 없는지 잠시 생각해 보라. 다른 알고리듬으로 그 일을 할 수 없을까? 다른 계산을 하면서 동시에 그 계산을 할 수는 없을까? 그런 식의 방법을 찾을 수 없다면 반복문 최적화 작업을 해도 된다. 이 일은 단순하다. 잡동사니를 없애 버려라. 결국 이 일은 독창성뿐만 아니라 그런 종류의 구문이나 식에 드는 비용에 대한 이해가 요구된다. 여기 몇 가지를 제안해 보겠다.
  • 실수 연산(floating point operation)을 제거하라
  • 필요 없이 메모리 블록을 새로 할당하지 말라
  • 상수들을 미리 계산하라(fold together)
  • I/O는 버퍼로 옮겨라
  • 나눗셈을 피하라
  • 쓸데없는 형변환(cast)을 피하라
  • 첨자(index)를 반복 계산하는 것보다는 포인터를 옮기는 것이 낫다

이러한 연산에 드는 비용은 사용하는 시스템에 따라 다르다. 어떤 시스템의 컴파일러와 하드웨어는 알아서 이런 일들을 해 준다. 그래도 분명하고 효율적인 코드가 특정 시스템에 대한 이해가 필요한 코드보다 더 낫다.

2.1.8 I/O 비용을 다루는 방법

많은 문제에서 프로세서는 하드웨어 장치들과 통신하는 데 드는 시간에 비해 빠르게 동작한다. 이런 시간 비용을 보통 줄여서 I/O라고 하고, 여기에는 네트웍 시간, 디스크 I/O, 데이터베이스 질의, 파일 I/O, 그리고 프로세서에 가깝지 않은 어떤 하드웨어가 뭔가를 하게 하는 작업들이 포함된다. 따라서 빠른 시스템을 만든다는 것은, 어떤 빽빽한 반복문 속에 있는 코드를 개선하거나 더 나아가 알고리듬을 개선하는 것보다 I/O를 개선하는 일이 될 경우가 많다.

I/O를 개선하는 두 가지 매우 기초적인 방법, 즉 캐쉬와 효율적 데이터 표현(representation)이 있다. 캐쉬는 (일반적으로 어떤 추상적인 값을 읽어 오는) I/O를 피하기 위해 그 값을 가깝게 복사해 놓아서 그 값을 가져오기 위해 I/O를 다시 발생시키지 않는 것을 말한다. 캐쉬의 핵심은 어떤 데이터가 원본(master)이고 어떤 데이터가 복사본인지 분명하게 구분하는 것이다. 원본은 단 하나만 있다! 캐쉬는 복사본이 원본의 변화를 즉시 반영하지 못하는 경우가 있다는 위험 부담이 있다.

효율적 데이터 표현이란 데이터를 더욱 효율적으로 표현하여 I/O의 비용을 줄이는 방식이다. 이 방법은 종종 가독성과 호환성 등의 다른 요구 조건과 대립되기도 한다.

효율적 데이터 표현은 처음의 표현 방식보다 두세 배의 개선 효과가 있기도 하다. 이를 위한 방법들로는 사람이 읽을 수 있는 표현 방식 대신 2진 표현 방식을 사용하는 것, 데이터와 함께 심벌 사전을 전송하여 긴 심벌들을 인코딩할 필요가 없게 하는 것, 그리고 극단적으로는 허프만(Huffman) 인코딩 같은 것 등이 포함된다.

때때로 가능한 세 번째 방법은 계산 부분을 데이터에 밀착시켜 더 가까운 곳에서 참조할 수 있게 하는 것이다. 예를 들어, 데이터베이스에서 어떤 데이터를 읽어 와서 합계와 같은 간단한 계산을 한다고 할 때 데이터베이스 서버가 직접 그 작업을 하게 하는 것이다. 이 방법은 작업하는 시스템의 특성에 매우 많이 의존하기는 하지만, 시험해 볼 필요는 있다.

2.1.9 메모리를 관리하는 방법

메모리는 절대로 다 써 버리면 안 되는 소중한 자원이다. 잠시 동안은 그것을 무시할 수 있겠지만 결국에는 메모리를 어떻게 관리할 것인지 결정해야 할 것이다.

단일 서브루틴이 차지하는 범위 이상으로 유지될 필요가 있는 공간을 종종 할당된 힙(heap)이라고 한다. 아무도 참조하지 않는 메모리 영역은 쓸모없는 쓰레기(가비지, garbage)일 뿐이다. 사용하는 시스템에 따라 메모리가 가비지가 될 것 같으면 명시적으로 메모리 할당을 해제해야 한다. 가비지 수집기를 제공하는 시스템을 사용할 수 있을 경우도 많다. 가비지 수집기는 가비지를 발견하면 프로그래머의 어떤 조작도 필요 없이 그것이 차지하는 공간을 풀어준다. 가비지 수집은 훌륭한 방법이다. 이를 통해 오류를 줄이고, 적은 노력으로도 코드를 더욱 간결하게 할 수 있다. 할 수 있다면 이 방법을 사용하라.

하지만 가비지 수집을 한다 해도 모든 메모리를 가비지로 채우게 될 수 있다. 고전적인 실수 중의 하나는 해쉬 테이블(hash table)을 캐쉬로 사용하고는 해쉬 테이블에 있는 참조 주소들을 제거하는 것을 잊어버리는 것이다. 참조 주소가 남아 있으므로 그 주소에 해당하는 메모리 영역은 가비지 수집이 될 수 없는 상태로 못 쓰게 된다. 이것을 메모리 누수(leak)라고 한다. 메모리 누수는 일찍부터 찾아서 고쳐야 한다. 장시간 실행되는 시스템이 있다면 검사할 때는 메모리가 고갈되는 일이 없다가 실제로 사용될 때가 돼서야 고갈되기도 한다.

새로운 객체의 생성은 어떠한 시스템에서도 어느 정도 시간 비용이 드는 작업이다. 하지만 서브루틴의 지역 변수들(local variables)에 직접 할당된 메모리는 할당 해제 방식이 매우 간단해질 수 있으므로 그렇게 시간 비용이 들지는 않는다. 어떻든 불필요한 객체 생성은 피해야 한다.

한 번에 필요한 객체 수의 상한을 정할 때 생기는 중요한 경우가 있다. 이 객체들이 모두 같은 양의 메모리를 필요로 한다면 그것들을 모두 수용하기 위해 단일 블록의 메모리, 즉 버퍼를 할당할 수 있을 것이다. 그 객체들은 이 버퍼 안에서 정해진 순환 방식에 따라 할당되고 해제될 수 있으므로 이 버퍼를 링 버퍼(ring buffer)라고 부르기도 한다. 이것은 보통 힙 할당보다 더 빠르다.

때로는 할당된 공간이 다시 할당될 수 있도록, 가비지 수집에 의존하는 대신, 그것을 명시적으로 풀어줘야 한다. 그래서 할당된 각 영역을 잘 알아내어 그것을 적절한 때에 해제하는 방법을 설계해야 한다. 그 방법은 생성된 객체의 종류에 따라 달라질 수 있다. 메모리 할당 작업이 수행될 때마다 그것이 결국에는 메모리 할당 해제 작업과 짝을 이뤄야 한다는 사실을 명심해야 한다. 이것은 매우 어렵기 때문에 프로그래머들은 이를 위해 단순하게 참조 회수 세기와 같은 기초적인 형태의 가비지 수집 방법을 구현하는 경우도 있다.

2.1.10 가끔씩 생기는 버그를 다루는 방법

가끔씩 생기는 버그는 "외계에서 온 20미터짜리 투명 전갈"의 사촌 같은 종류의 버그이다. 이 끔찍한 악몽은 관찰하기 어려울 만큼 가끔씩 나타나지만, 또한 무시할 수 없을 만큼 자주 일어난다. 이런 버그는 발견하기도 어렵게 때문에 고치기도 어렵다.

그 버그를 찾기 위해 여덟 시간을 매달린 뒤에 그것이 정말 있는 것인지 의심하기 시작한다 해도, 가끔씩 생기는 버그는 다른 모든 것들이 따르는 동일한 논리 법칙을 따를 수밖에 없다. 이 버그를 상대하기 힘든 것은 알지 못하는 어떤 조건들에서만 생기기 때문이다. 그 버그가 생기는 바로 그 때의 상황들을 기록하여 정말로 어떤 변이가 생긴 것인지 추측할 수 있도록 해 보라. 그 조건은, 예를 들어, '이 버그는 '와이오밍(Wyoming)'이라는 값을 입력했을 때만 생긴다"는 것과 같이, 데이터 값에 관련되어 있을 수도 있다. 만약 이것이 변이의 원인이 아니라면 다음으로는 동시에 수행되어야 하는 작업이 실제로는 그렇게 되지 못했을 경우를 의심해 볼 수 있다.

어떤 통제된 방식으로 그 버그가 다시 나타나도록 계속, 계속, 계속 시험해 보라. 다시 나타나게 할 수 없다면, 버그가 실제로 발생할 때 그것을 분석하기 위해 필요하다고 생각되는 정보들을 기록으로 남길 수 있는 (필요하다면 특별한) 로그 기록 시스템을 만들어 그 버그 앞에 덫을 놓아 보라. 버그가 개발 환경에서는 나타나지 않고 완성 제품에서만 나타난다면 그것은 오래 걸리는 프로세스 때문일 것이라고 믿어 보라. 로그 기록에서 얻는 실마리들은 해결책을 제공해 주지는 못하더라도 로그 기록 방법을 개선하기 위한 정보는 충분히 줄 수 있을 것이다. 개선된 로그 기록 시스템을 완성하는 데에는 오랜 시간이 걸릴 수도 있다. 그리고는 더 많은 정보를 얻기 위해 그 버그가 다시 나타날 때까지 기다려야 한다. 이 일은 어느 시간 동안 계속 반복해야 할 수도 있다.

가끔씩 생기는 버그들 중에서 내가 저지른 가장 어리석었던 것은, 어떤 수업의 프로젝트에서 함수형 프로그래밍 언어(functional programming language)를 다중 쓰레드로 구현하는 것이었다. 나는 그 함수형 프로그램이 모든 (이 수업에서는 여덟 개의) CPU들을 잘 활용하여 수치 계산을 동시에 정확하게 해 내도록 매우 주의를 기울였다. 그런데 가비지 수집기를 동기화하는 것을 깜빡 잊었다. 이 시스템은 오랜 시간 동안 잘 돌아갔고, 뭔가 이상하다는 것을 눈치 채기 전까지는, 어떤 작업을 시작하든 잘 마무리되는 것 같았다. 부끄럽게도 나는 내 실수가 드러나기 전까지는 하드웨어에 문제가 있다고 생각했었다.

최근에 일하면서는, 발견하기까지 몇 주나 걸렸던 가끔씩 생기는 버그를 만난 적이 있다. 우리에게는 아파치(Apache) 웹 서버 뒤에 자바(Java)로 구현된 다중 쓰레드 어플리케이션 서버들이 있다. 페이지 전환을 빠르게 유지하기 위해 우리는 모든 I/O가 페이지 전환 쓰레드들과는 다른 네 개의 독립된 쓰레드에서 일어나게 하고 있다. 그런데 이것들이 (우리 로그 기록에 따르면) 가끔 한 번씩 몇 시간 동안 멈춘 것처럼 되면서 아무 일도 하지 않았다. 쓰레드가 네 개가 있기 때문에 네 개 모두 멈추지 않는 한 그 자체로는 큰 문제는 아니었다. 그렇게 된다면 이 쓰레드들이 비워내는 큐(queue)가 남아 있는 모든 메모리를 순식간에 다 채워서 서버가 멈춰버렸을 것이다. 이것을 알게 되기까지 한 주 정도 걸렸지만 무엇 때문에 이런 일이 생기는지, 언제 생길지, 또는 멈출 때 어느 쓰레드가 어디쯤 작업을 하고 있는지조차 여전히 몰랐다.

이것은 타사 소프트웨어와 연관된 위험성을 보여준다. 우리는 텍스트에서 HTML 태그를 제거하는 코드의 사용권을 받아서 쓰고 있었다. 우리는 그 코드가 나온 나라 이름을 따서 그것을 '프랑스 스트리퍼'라는 애칭으로 불렀다. 우리는 소스 코드를 가지고 있었지만 (감사하나이다!) 우리 서버의 로그 기록을 살펴보다가 이메일 쓰레드들이 프랑스 스트리퍼에서 멈춘다는 것을 알게 되기까지 그 소스를 주의깊게 연구하지 않았다.

이 스트리퍼는 제대로 동작했지만 길이가 길고 특이한 텍스트에 대해서는 그렇지 못했다. 이런 텍스트를 처리하는 데에는 텍스트 길이의 제곱에 비례하거나 더 많은 시간이 걸렸다. 이런 텍스트들이 자주 나타나는 것이었다면 우리는 그 버그를 금방 발견했을 것이다. 그것이 전혀 나타나지 않았다면 우리는 전혀 문제가 없었을 것이다. 하지만 그것은 나타났고, 우리는 문제를 이해하고 해결하는 데 몇 주나 걸렸던 것이다.

2.1.11 설계 기능을 익히는 방법

소프트웨어를 설계하는 방법을 배우기 위해서는 사수(mentor)가 설계를 할 때 그들과 한 자리에 있으면서 그들의 행동을 연구하라. 그리고 잘 작성된 소프트웨어를 연구하라. 그 후에는 최근에 나온 설계 기법에 대한 책을 읽으라.

그리고 그것들을 직접 실천해야 한다. 소규모 프로젝트부터 시작하라. 최종 작업을 마쳤으면 그 설계가 어떻게 실패하거나 성공했는지, 처음 생각했던 개념에서 어떻게 달라졌는지 숙고하라. 이런 식으로 다른 사람들과 더 큰 프로젝트를 수행하게 된다. 설계 기능은 판단 능력의 문제이며, 그것을 얻기까지 몇 년은 걸린다. 현명한 프로그래머는 두 달이면 적당한 수준에서 기본 기능을 익힐 수 있을 것이며 그것을 기반으로 발전해 갈 것이다.

자기 자신의 스타일을 개발하는 것은 자연스러운 일이며 자신에게 도움이 될 것이다. 설계는 예술이지 과학이 아니라는 것을 명심하라. 이런 주제로 책을 쓰는 사람들은 흔히 그것이 과학적으로 보이게 하는 데에 관심을 갖는다. 하지만 특정한 설계 스타일을 고집하지는 않도록 하라.

2.1.12 실험을 수행하는 방법

이제는 고인이 된 위대한 에처 다익스트라(Edsger Dijkstra)는 전산 과학은 실험 과학이 아니며<ExpCS> 전자적인 컴퓨터에 의존하지 않는다고 설득력 있게 설명했다. 그가 1960년대의 전산 과학에 대해 다음과 같이 말했다. <Knife>

해로운 일이 일어났다. 이 주제는 '전산 과학'으로 알려지기 시작했다. - 이것은 꼭 외과 수술을 '칼 과학'이라고 부르는 것과 같다. - 그리고 전산 과학은 기계와 그 주변 장치들에 대한 학문이라고 사람들의 생각 속에 단단히 자리를 잡게 되었다.


프로그래밍이 실험 과학이 되어서는 안 되겠지만, 대부분의 실무 프로그래머들은 다익스트라가 전산 과학에 대해 내린 정의에 동감할 만큼 여유롭지는 못하다. 우리는, 전부는 아니더라도 일부 물리학자들이 그러하듯이, 실험의 영역 속에서 일해야 한다. 지금부터 30년 동안 프로그래밍이 실험 없이 이뤄질 수 있다면, 그것은 전산 과학의 위대한 승리라고 할 수 있을 것이다.

앞으로 해 보게 될 몇 가지 실험들은 다음과 같다.
  • 문서 내용에 부합하는지 검증하거나 (문서가 없다면) 시스템의 반응을 이해하기 위해 몇몇 표본들로 시스템을 검사하기
  • 실제로 버그가 고쳐졌는지 알아보기 위해 코드의 바뀐 부분을 검사하기
  • 시스템의 성능 특성을 완벽하게 알기 위해 두 가지 다른 조건에서 시스템의 성능을 측정하기
  • 데이터의 무결성을 점검하기
  • 어려운, 혹은 반복하기 힘든 버그 해결의 실마리를 얻기 위해 통계 자료를 수집하기

내가 이 글에서 실험 설계를 설명할 수 있으리라고는 생각하지 않는다. 독자 스스로 연구하고 연습해 보아야 할 것이지만, 두 가지 짧은 조언을 해 줄 수는 있겠다.

첫째, 자신의 가설이나 점검하려고 하는 검증 조건(assertion)이 무엇인지 명쾌해야 한다. 혼돈스럽거나 다른 사람들과 함께 작업할 때는 가설을 적어 보는 것이 도움이 되기도 한다.

각 실험이 지난 실험에서 얻은 지식에 기반을 두는 일련의 실험들을 설계해야 하는 상황에 처할 때가 종종 있을 것이다. 그러므로 가능한 한 많은 정보를 얻어낼 수 있도록 실험을 설계해야 한다. 불행히도 이것은 각 실험이 단순명료해야 한다는 조건과 대립된다. 이런 상황에 대한 판단은 경험을 통해 개발해야 할 것이다.

2.2 팀의 기능들

2.2.1 시간 추정이 중요한 이유

소프트웨어 시스템이 최대한 빨리 적극적으로 활용되게 하기 위해서는 개발 계획을 세우는 것뿐만 아니라 문서화, 시스템 배치, 마케팅 등의 계획도 세워야 한다. 상업적인 프로젝트에서는 영업과 재무도 포함한다. 개발 시간을 예측할 수 없다면 이러한 것들을 효과적으로 계획하는 것은 불가능하다.

정확한 시간 추정은 예측 가능성을 높인다. 관리책임자들은 그렇게 되는 것을 매우 좋아하며, 또 당연히 그래야 한다. 관리책임자들은 소프트웨어를 개발하는 데 얼마나 시간이 걸릴지 정확하게 예측하는 것이 이론적으로나 실제적으로 불가능하다는 사실을 전혀 이해하지 못하는 경우가 있다. 우리는 이런 불가능한 일을 하도록 항상 요구받으므로 그 사실을 정직하게 대면해야 한다. 어쨌든 이 과제가 불가능하다는 것을 인정하지 않는 것은 부정직한 일이 될 것이므로 필요하다면 그것을 설명하라. 사람들은,

그 문제를 제대로 이해한 것이라면, (아무도 그 동안 우리를 방해하지 않는다고 할 때) 5주 안에 마칠 가능성이 50% 정도 있다고 추정됩니다.


위의 문장이 실제로는,

그 일을 모두 5주 안에 틀림없이 마치겠습니다.


위의 문장을 뜻한다고 부풀려 생각하는 놀라운 경향이 있으므로, 시간 추정에 대해 의사소통의 문제가 생길 가능성이 많다.

이러한 일상적인 해석의 문제가 있으므로 상사나 고객이 아무 것도 모른다 생각하고 그 시간 추정이 무엇을 의미하는 것인지 분명하게 논의해야 한다. 그리고 예상 시간이 아무리 틀림없어 보여도 다시 한 번 더 그것을 추정해 보아야 한다.

2.2.2 프로그래밍 시간을 추정하는 방법

시간 추정은 연습이 필요하다. 또한 노력해야 한다. 시간 추정은 노력이 많이 드는 일이기 때문에, 특히 대규모 작업 시간을 추정해야 할 경우에는 시간 추정 자체에 드는 시간을 추정할 필요도 있다.

대규모 작업 시간을 추정해야 할 때에는 천천히 하는 것이 가장 정직한 것이다. 대부분의 기술자들은 열심이 있으며 사람들을 기쁘게 하고 싶어 하기 때문에 천천히 하는 것을 분명히 좋아하지 않을 것이다. 하지만 즉석으로 추정한 것은 대개 정확하지도 정직하지도 않다.

천천히 하는 동안 그 과제를 수행하거나 과제의 모형을 만드는 것에 대해 숙고할 수 있을 것이다. 정책의 압력을 피할 수 있다면 이것이 시간 추정을 도출할 수 있는 가장 정확한 방법이며, 실제로도 그대로 작업이 진행될 수 있을 것이다.

조사할 시간을 충분히 갖는 것이 불가능할 때에는, 우선 그 추정이 무엇을 뜻하는지 아주 분명하게 확정해야 한다. 추정 내용 기록의 첫 부분과 마지막 부분에 그 의미를 다시 써 놓으라. 과제를 점진적인 소규모 하위 과제들로 분해하되 각 소규모 과제가 하루 이상이 되지 않을 때까지, 이상적으로는 그 이하의 길이로 추정 내용 기록을 준비하라. 가장 중요한 것은 하나라도 빠뜨리는 일이 없도록 하는 것이다. 예를 들어, 문서화, 검사, 계획 시간, 다른 그룹과 회의 시간, 휴가 시간 등이 모두 매우 중요하다. 매일 바보들을 상대하며 보내는 시간이 있다면 그것도 추정 내용에 한 항목으로 적어 넣으라. 이렇게 하면 최소한 무엇 때문에 시간을 써 버리게 되는지 상사가 알아볼 수 있게는 될 것이며, 자신의 시간을 지킬 수 있게 될 것이다.

추정 시간을 은연중에 불려서 쓰는 기술자들도 있지만, 나는 그렇게 하지 말라고 권하고 싶다. 불려서 쓰는 것이 낳는 결과들 중 하나는 다른 사람의 신뢰를 잃게 된다는 것이다. 예를 들어, 어떤 기술자가 사실은 하루가 걸릴 것이라고 생각되는 어떤 과제를 3일로 추정했다고 하자. 그 기술자가 나머지 이틀은 문서 작업을 하거나 어떤 다른 유용한 프로젝트를 하겠다고 계획했을 수 있다. 하지만 그 과제가 단 하루 만에 끝났다는 것은 (그 사실이 알려졌다면) 추적할 수 있을 것이며, 일을 늦추거나 과대 추정했다는 기색이 보일 것이다. 자신이 실제로 하고 있는 것을 적절히 볼 수 있게 하는 것이 더욱 좋다. 문서 작업하는 데 드는 시간이 코딩 시간의 두 배가 된다는 사실을 시간 추정 내용에서 확인할 수 있다면, 이것을 관리책임자가 볼 수 있게 하는 것이 더욱 큰 이득이 될 것이다.

추정 시간은 숨김없이 불려서 쓰라. 어떤 과제가 하루가 걸릴 것 같은데, 현재 접근 방법이 들어맞지 않을 경우 열흘이 걸릴 수도 있다면, 이 사실을 어떻게든 추정 내용에 적으라. 그렇지 않으면 적어도 추정한 확률을 가중치로 한 평균값을 계산하라. 알 수 있고 추정할 수 있는 위험 부담 요소는 모두 일정에 들어가야 한다. 한 사람이라면 주어진 기간 동안 아프지 않을 수 있다. 하지만 여러 기술자들이 함께 하는 큰 프로젝트에서는 환자가 생기는 시간도 있고 휴가 기간도 있다. 필수로 참석해야 하는 회사의 연수 세미나가 일어날 확률은 얼마나 될까? 추정할 수 있다면 꼭 끼워 넣으라. 물론 알려지지 않은 미지수의 일, 즉 예측불허의 일들도 있다. 예측불허란 개별적으로는 추정할 수 없다는 말이다. 모든 예측불허의 일에 대해 공통적으로 쓰이는 한 항목을 만들거나, 상사와 의사소통하는 그 밖의 방식으로 그것들을 처리할 수 있을 것이다. 하지만 상사가 그것들의 존재를 잊어버리게 하지는 말라. 예측불허의 일을 생각하지 않고 시간을 추정한 그대로 일정이 잡히기는 지독하게도 쉽다.

팀 환경에서는, 그 일을 하는 사람이 그 시간을 추정하도록 해야 하며, 전체 추정 시간에 대해 팀 전체가 합의하도록 해야 한다. 사람들은 능력, 경험, 준비도, 자신감에 큰 차이가 있다. 능력 있는 프로그래머가 자기에게 맞게 추정한 것을 능력이 부족한 프로그래머들도 따라 하다가는 큰 문제가 생긴다. 팀 전체가 추정 내용에 대해 한 줄, 한 줄씩 동의하게 함으로써 팀 전체의 이해를 분명히 하고, 자원을 전술적으로 재분배할 수 있는 (예를 들어, 능력이 부족한 팀원에게서 능력 있는 팀원에게 짐을 넘기는) 기회도 생긴다.

수치로 나타낼 수 없는 큰 위험 부담이 있다면, 프로그래머는 관리책임자가 함부로 거기에 뛰어들었다가 그 위험이 발생했을 때 당황하지 않도록 강력하게 말해 줄 의무가 있다. 바라기에는 그 경우에 그 위험 부담을 줄이기 위해 필요한 모든 일을 하게 될 것이다.

익스트림 프로그래밍(Extreme Programming) 기법을 사용하도록 회사를 설득할 수 있다면 비교적 적은 것들만 추정해도 될 것이며, 더 재미있으면서 더 생산적으로 일할 수 있을 것이다.

2.2.3 정보를 찾는 방법

알아야 하는 정보의 성격에 따라 그것을 찾는 방법이 결정된다.

어떤 소프트웨어의 최근 패치 단계와 같이 객관적이고 검증하기 쉬운 구체적인 것들에 대한 정보가 필요하다면, 그 정보에 대해 인터넷을 검색하거나 토론 그룹에 글을 올려 많은 사람들에게 공손히 물어 보라. 의견이나 주관적인 해석의 낌새가 있는 것은 절대 인터넷에서 검색하지 말라. 허튼 소리가 진실로 취급되는 비율이 매우 놓다.

어떤 것에 대한 생각이 변화해 온 역사와 같이 주관적인 것에 대한 일반적인 지식을 원한다면, (실제 건물이 있는) 도서관으로 가라. 예를 들어, 수학이나 버섯이나 신비주의에 대해 알고 싶다면 도서관으로 가면 된다.

사소하지 않은 어떤 일을 하는 방법을 알고 싶다면 그 주제에 대한 책을 두세 권 사서 읽으라. 소프트웨어 패키지를 설치하는 방법 같은 사소한 일을 하는 방법은 인터넷에서 배울 수 있다. 좋은 프로그래밍 기법 같은 중요한 것들을 인터넷에서 배울 수도 있지만, 그 결과를 검색하고 분류하거나 그 결과가 믿을 만한 것인지 파악하느라, 손으로 잡을 수 있는 책의 적당한 부분을 찾아 읽는 데 드는 시간보다 더 많은 시간을 낭비할 가능성이 크다.

"이 신제품 소프트웨어는 대규모의 데이터를 처리할 수 있는가?"와 같이 아무도 알 것 같지 않은 정보가 필요하다면, 어쨌든 인터넷이나 도서관을 검색해 봐야 할 것이다. 열심히 찾아도 찾을 수 없다면 그것을 확인하기 위한 실험을 설계해야 할 것이다.

어떤 특정한 상황에 대한 의견이나 가치 판단을 원한다면, 전문가에게 이야기하라. 예를 들어, 최신 데이터베이스 관리 시스템(DBMS)을 LISP로 만드는 것이 좋은 생각인지 알고 싶다면 LISP 전문가와 데이터베이스 전문가와 이야기해야 한다.

어떤 응용프로그램을 위한, 아직 발표되지 않은 더 빠른 알고리듬이 있는지 알고 싶다면 그 분야에서 일하는 사람과 이야기해야 할 것이다.

사업을 시작할 것인지 말 것인지에 대한 결정과 같이 자신만이 내릴 수 있는 개인적인 결정을 하려고 한다면, 그 생각에 대한 긍정적인 조건과 부정적인 조건을 나열해서 적어 보라. 그래도 결정을 못하겠다면 점(divination)을 쳐 보라. 어떤 생각에 대해 다각도로 연구해 봤고, 해야 할 일을 다 했고, 모든 결과와 긍정적, 부정적 조건들을 다 따져 봤다 해도, 아직은 결정하지 마라. 이제 머리는 잠시 쉬고 마음의 움직임을 따라 보라. 잘 의식하지 못하는 자신의 욕망을 파악하는 데에는 다양한 점 기법들이 유용하게 쓰일 수 있다. 이 기법들이 보여 주는 모호하고 무작위적인 패턴에 대해 자신의 잠재의식이 의미를 부여하게 될 것이기 때문이다.

2.2.4 사람들을 정보의 원천으로 활용하는 방법

각 사람의 시간을 존중하고 자신의 시간과 균형을 맞추라. 어떤 사람에게 질문하는 것은 답을 얻는다는 것 이상의 것을 이루게 한다. 그 사람은 나의 존재를 인정하고 특정 절문에 귀를 기울이면서 나에 대해 알게 된다. 나도 마찬가지로 그 사람을 알게 되며, 찾던 답도 알 수 있게 된다. 대개의 경우 이것이 질문 자체보다 더욱 중요하다.

하지만 그것을 반복하게 되면 그 가치는 줄어든다. 질문을 한다는 것은 결국 어떤 사람이 갖고 있는 소중한 필수품, 즉 그들의 시간을 사용하는 것이다. 의사소통의 소득은 그 비용과 비교하여 저울질해 보아야 한다. 게다가 그 비용과 소득은 사람마다 다르다. 나는 100명의 부하 직원이 있는 사람은 그 조직의 모든 사람들에게 한 달에 5분씩은 이야기할 시간이 있어야 한다고 확신한다. 이것은 자기 시간의 약 5%를 할애하는 것이 될 것이다. 하지만 10분은 너무 많은 것 같고, 같은 5분이라 해도 직원이 1,000명이라면 그것도 너무 많다. 자신의 조직에 속한 사람들에게 이야기하는 시간의 양은 그 사람들의 (직위보다는) 역할에 따라 다르다. 상사에게 이야기하는 시간이 상사의 상사에게 이야기하는 시간보다는 더 많아야 한다. 불편할 수도 있겠지만, 무엇에 대해서든 자신의 모든 상급자들에게 매달 조금씩 이야기를 할 의무가 있다고 생각한다.

여기에서 기본 법칙은, 우리에게 잠깐 이야기를 하는 사람은 모두 이득을 얻지만, 이야기가 길어질수록 그 이득은 적어진다는 것이다. 우리는 그들에게 이 이득을 나눠주고 그들과 대화하면서 이득을 받아와야 하며, 들인 시간과 이득 사이에 균형을 맞춰야 한다.

자신의 시간을 존중하는 것도 중요하다. 누군가에게 이야기하는 것을 통해, 그들의 시간을 쓰게 한다 해도, 자신의 시간을 많이 아낄 수 있다면, 그 사람의 시간이 나의 시간보다 더 소중하다고 생각하지 않는 한, 그것을 해야 한다. 우리 부족(tribe)에 대해서도 마찬가지이다.

특이한 사례로서 여름방학 인턴의 경우를 들 수 있다. 고급 기술직에 들어온 여름방학 인턴이 무슨 큰 일을 해 낼 것이라고 기대하기는 힘들다. 그들은 단지 거기 있는 모든 사람들을 괴롭게 할 것이라고 기대할 수 있을 것이다. 그렇다면 왜 이걸 참아야 하는가? 괴롭힘을 받는 사람은 그 인턴에게서 중요한 것을 얻게 될 것이기 때문이다. 그들은 살짝 자랑할 수 있는 기회가 생긴다. 새로운 아이디어를 들을 기회가 생길지도 모른다. 다른 시각에서 관찰해 볼 기회가 생긴다. 그 인턴을 채용하려는 의도가 있을 수도 있지만, 그렇지 않다 하더라도 얻는 것이 많다.

솔직히 누군가가 도움이 되는 말을 해 줄 수 있겠다는 생각이 들면, 그들에게 지혜와 판단력을 나눠 달라고 부탁해야 한다. 그러면 그 사람들은 뿌듯해 할 것이며, 나는 그들에게 무엇인가를 배우고 또 무엇인가를 가르쳐 주게 될 것이다. 좋은 프로그래머에게 영업 부사장의 조언은 별로 필요가 없겠지만, 만약 필요하게 된다면 반드시 조언을 구해야 한다. 나는 우리 영업부 직원들이 하는 일을 더 잘 이해하기 위해서 영업 관련 전화 통화 내용 몇 건을 같이 듣게 해 달라고 요청한 적도 있다. 30분도 채 안 걸린 일이었지만 그런 작은 노력이 영업부에 좋은 인상을 주었다고 생각한다.

2.2.5 현명하게 문서화하는 방법

아무도 읽지 않을 쓰레기 같은 글을 쓰기에는 인생은 너무 짧다. 쓰레기 같은 글은 아무도 읽지 않을 것이다. 따라서 문서화를 조금이라도 잘 하는 것이 최선이다. 관리책임자들은 이것을 이해하지 못하는 경우가 많다. 질이 떨어지는 문서를 보면서도 그들이 프로그래머들에게 의존하지 않고 있다는 헛된 안도감을 갖게 하기 때문이다. 누가 나에게 내가 작성한 문서가 정말 쓸모없다고 단호하게 말한다면, 그 말을 인정하고 조용히 다른 직업을 찾아보는 게 좋을 것이다.

문서를 잘 만들어내는 데 걸리는 시간의 양을 정확히 추정하여 문서화에 대한 요구를 완화시키는 데 드는 시간을 추정하는 데 이용하는 것만큼 효과적인 일은 없을 것이다. 진실은 냉혹하다. 문서화는 제품 검사처럼 코드 개발보다 더 긴 시간이 걸릴 수 있다.

문서화를 잘 하기 위해서는 우선 작문 실력이 있어야 한다. 작문에 대한 책을 찾아서 공부하고 실습해 보기를 권한다. 글이 깔끔하지 못하고 문서에 써야 하는 언어를 잘 모른다 해도, 다음의 황금률을 따르면 된다. "무엇이든지 남에게 대접을 받고자 하는 대로 너희도 남을 대접하라." 시간 여유를 가지고서 이 문서를 읽을 사람이 누구이며, 그 사람이 무엇을 얻고 싶어 할지, 그리고 그것을 어떻게 가르쳐 줄 수 있을지 진지하게 생각해 보라. 그렇게만 해도 평균 수준 이상의 문서는 만들어낼 수 있을 것이며 프로그래머로서도 훌륭하게 될 것이다.

코드 자체에 대해 문서화를 하게 될 때에는, 프로그래머가 아닌 사람들이 주로 읽을 문서를 작성할 때와는 반대로, 내가 아는 최고의 프로그래머들이 모두 공감하고 있는 바와 같이, 보면 바로 알 수 있게 코드를 작성하고, 코드만으로 그 의미가 분명하지 않은 곳에만 코드에 대해 문서화하라. 이렇게 하는 것이 좋은 두 가지 이유가 있다. 첫째로, 코드 수준의 문서를 봐야 하는 사람이라면 대부분의 경우에 어떻게든 그 코드를 읽을 수 있으며 그것을 선호하기 때문이다. 물론, 이것은 초보보다는 경력 있는 프로그래머에게 더 적당한 말이다. 하지만 더 중요한 것은, 따로 문서화하지 않을 경우, 코드와 문서가 불일치할 리가 없을 것이라는 사실이다. 소스 코드는 아무리 잘못되어 봐야 틀리거나 혼돈스러울 뿐이다. 하지만 문서는 정확하게 쓰지 않는다면 거짓말을 할 수 있고 이것은 천 배나 더 나쁜 일이다.

책임감 있는 프로그래머는 이 사실을 가볍게 받아들이지 않을 것이다. 보면 바로 알 수 있는 코드를 어떻게 작성할 것인가? 그것이 과연 무슨 뜻인가? 바로 이런 뜻이다.
  • 누군가 읽을 것이라는 사실을 염두에 두고 코드를 작성하기
  • (앞에서 말한) 황금률을 적용하기
  • 복잡하지 않은 해법을 선택하기 (다른 해법이 용케 더 빠르다 해도)
  • 코드를 어지럽게 하는 사소한 최적화 시도는 포기하기
  • 코드를 읽을 사람을 생각하면서 그 사람이 쉽게 이해할 수 있게 하기 위해 시간을 할애하기
  • "foo", "bar", "doIt" 같은 함수명은 절대 쓰지 않기!

2.2.6 형편없는 코드를 가지고 작업하기

다른 사람이 작성한 질이 떨어지는 코드를 가지고 작업해야 하는 경우가 많다. 하지만, 신발을 신고 걸어 보기까지는 그 신발이 아주 형편없다고 생각하지는 말라. 그 사람이 일정의 압박에 맞추기 위해 어떤 일을 빨리 끝내도록 독촉을 받았을 수 있다. 어떻든 간에, 불분명한 코드를 가지고 작업을 하려면 그것을 이해해야 한다. 그 코드를 이해하자면 시간이 걸릴 것이고, 그 시간은 정해진 일정의 어디에선가 빼 와야 할 것이므로, 이 사실을 분명히 해야 한다. 소스 코드를 이해하기 위해서는 그것을 읽어 볼 수밖에 없다. 아마도 그것을 가지고 실험을 해 봐야 할 것이다.

비록 자기 자신만을 위한 것이라 해도, 코드에 대한 문서화 노력을 통해 생각해 보지 못했던 각도에서 그 코드를 생각해 볼 수 있으므로, 지금이 문서화하기 좋은 시간이며, 그 결과로 나온 문서는 유용할 것이다. 이렇게 하는 동안 그 코드의 일부나 전체를 재작성하려면 무엇이 필요할 것인지에 대해 생각해 보라. 그것을 재작성하는 것이 실제로 시간을 아끼는 일이 될 것인가? 코드를 재작성한다면 더 믿을 만하게 될 것인가? 이 때에는 자만심을 주의하도록 하라. 코드를 재작성한다면, 그것을 검사해야 하는 부담은 얼마나 될 것인가? 얻을 수 있는 이득과 비교해 볼 때 정말로 재검사할 필요가 있는 것인가?

자기가 작성하지 않은 코드에 대한 작업 시간을 추정할 때, 그 코드의 품질에 따라 문제나 예측불허의 것이 생길 위험 가능성에 대한 인식이 달라진다.

깔끔하지 못한 코드에는 프로그래머의 최고의 도구 두 가지, 즉 추상화와 캡슐화가 특히 잘 적용된다는 사실을 잘 기억해 두라. 코드의 큰 부분을 재설계할 수는 없겠지만, 어느 정도 추상화를 해 줄 수 있다면, 전체를 재작업하지 않고서도 좋은 설계가 주는 이득을 어느 정도는 얻을 수 있을 것이다. 특히 안 좋은 부분은 다른 부분에서 떼어 놓아 독립적으로 재설계할 수 있도록 해 볼 수도 있다.

2.2.7 소스 코드 제어 시스템을 이용하는 방법

소스 코드 제어 시스템은 프로젝트를 효과적으로 관리할 수 있게 해 준다. 이 시스템은 개인에게도 유용하고 그룹에게는 필수적이다. 이 시스템은 여러 버전의 모든 변경 사항을 추적하므로 어떤 코드도 없어지지 않으며 변경 사항들의 의미를 기록해 놓을 수 있다. 소스 코드 제어 시스템이 있으면 소스 코드의 일부를 버리거나 디버그 코드를 넣는 일을 자신 있게 할 수 있다. 변경한 코드는 팀과 공유하거나 배포할 공식 코드와 잘 분리되어 보존되기 때문이다.

나는 뒤늦게야 소스 코드 제어 시스템의 이득을 알게 되었지만, 이제는 혼자 하는 프로젝트라 해도 그것 없이는 살 수 없을 것 같다. 일반적으로 이 시스템은 동일한 코드를 놓고 팀으로 작업할 때 필요하다. 하지만 이 시스템은 또 다른 큰 장점이 있다. 즉 이것을 통해 소스 코드를 성장하는 유기체로 인식하게 해 준다는 점이다. 변경된 것마다 새 이름이나 번호를 붙여 새로운 개정판으로 표시하기 때문에, 소프트웨어가 눈에 보이게 점진적으로 향상되어 간다고 생각하게 되는 것이다. 이것은 특히 초보자들에게 유용하다고 생각된다.

소스 코드 관리 시스템을 잘 이용하는 방법 중 하나는, 항상 최신의 상태를 유지하면서 며칠 동안 가만히 있는 것이다. 며칠 안에 마무리할 수 없는 코드는 체크인 상태로 있지만, 그것은 활성화되지 않고 호출되지 않을 상태로 있거나, 그 자신에서 갈라져 나온 분기(branch) 위치에 있을 것이므로, 다른 누구에게 어떠한 문제도 일으키지 않을 것이다. 실수가 있는 코드를 올려서 팀 동료들의 작업을 더디게 하는 것은 심각한 오류이다. 이것은 언제나 금기 사항이다.

2.2.8 단위별 검사를 하는 방법

단위별 검사, 즉 코드로 만든 기능의 각 부분을 그것을 작성한 팀에서 검사하는 것은 코딩의 일부이지, 코딩과 다른 무엇이 아니다. 코드를 어떻게 검사할지 설계하는 것도 코드 설계의 한 부분이다. 비록 한 줄이라 해도 검사 계획을 기록해 놓아야 한다. 때때로 그 검사는 다음과 같이 단순할 것이다. "이 버튼이 좋아 보이는가?" 때로는 다음과 같이 복잡할 수도 있다. "이 정합(matching) 알고리듬은 틀림없이 정확한 짝을 찾아낼 것인가?"

할 수 있다면 검증 조건(assertion) 확인 방법이나 검사 자동화 도구(test driver)를 사용하라. 이 방법은 버그를 일찍 잡을 수 있게 해 줄 뿐만 아니라, 나중에도 유용하게 쓰일 수 있으며, 이렇게 하지 않았더라면 한참 고민하게 되었을 애매한 문제들을 줄일 수도 있을 것이다.

익스트림 프로그래밍(Extreme Programming) 기법을 사용하는 개발자들은 단위별 검사를 최대한 효과적으로 활용하여 코드를 작성한다. 이 작성 방법을 추천하는 것만큼 좋은 일도 없을 것이다.

2.2.9 막힐 때는 잠깐 쉬어라

막힐 때는 잠깐 쉬어라. 나는 막힐 때는 15분 정도 명상을 하곤 한다. 그러면 다시 문제로 돌아왔을 때 그것이 마술같이 해결되곤 한다. 규모가 클 경우에는 하룻밤 잘 자는 것이 같은 효과를 내기도 한다. 잠시 다른 활동을 하는 것이 효과적일 수도 있다.

2.2.10 집에 갈 시간을 인지하는 방법

컴퓨터 프로그래밍은 문화라고 할 만한 활동이다. 불행한 것은, 이것이 정신적, 신체적 건강을 그렇게 중요하게 생각하지 않는 문화라는 사실이다. 문화적이고 역사적인 이유 (예를 들어, 컴퓨터가 쉬는 밤중에 작업할 필요) 때문에, 그리고 저항할 수 없는 출시 일정의 압박과 프로그래머의 부족 때문에 컴퓨터 프로그래머는 전통적으로 초과 근무를 해 왔다. 소문으로 듣는 모든 이야기를 믿을 것이라고 생각하지는 않지만, 주당 60시간 근무는 일상적이며, 50시간은 상당히 적은 편에 속한다. 즉, 이보다 더 많은 시간이 요구되는 경우가 있다는 말이다. 이것은 좋은 프로그래머에게는 심각한 문제이다. 그는 자기 자신만 아니라 자기 팀 동료들도 책임지고 있기 때문이다. 자기가 집에 갈 시간, 때로는 다른 사람을 집에 보낼 시간도 인지하고 있어야 한다. 아이를 키우는 불변의 법칙이 없듯이, 이 문제를 해결할 불변의 법칙은 없다. 모든 사람은 서로 다르기 때문이다.

주당 60시간 이상 일하는 것은, 짧은 기간 (한 주 정도) 동안이나 해 볼 수 있을 정도로, 내게는 엄청난 노력이 필요하지만, 때로는 그렇게 해야 할 때가 있다. 한 사람에게 60시간 동안 일을 하게 하는 것이 공정한 것인지는 잘 모르겠다. 사실 40시간이 공정한 것인지도 잘 모르겠다. 하지만 분명한 것은, 초과 근무하는 시간 동안 별로 얻는 것 없이 오래 일하기만 하는 것은 어리석은 일이라는 사실이다. 나에 대해 말하자면, 주당 60시간 이상 일하는 것이 그렇다. 개인적으로는, 프로그래머는 고귀한 의무(noblesse oblige)를 다해야 하고 무거운 짐을 져야 한다고 생각한다. 하지만 봉이 되는 것은 프로그래머의 의무가 아니다. 그런데 슬프게도 프로그래머들은, 경영진의 눈에 들기 위해 애쓰는 관리책임자 같은 이들을 위해 재주를 부리는 곰이 되는 경우가 있다. 프로그래머들은, 다른 사람들을 기쁘게 하고 싶고, 싫다는 말을 잘 못하기 때문에, 종종 이런 요구에 굴복한다. 이를 대처하기 위한 네 가지 방어법이 있다.
  • 회사의 모든 사람들과 최대한 많이 대화하여 아무도 무슨 일이 일어나고 있는지에 대해 경영진을 현혹하지 못하게 하라.
  • 시간을 추정하고 일정을 잡을 때 방어적이고 명백하게 하여, 모든 사람들이 일정이 어떻게 되고 현재 어디쯤 가고 있는지 잘 볼 수 있게 하라.
  • 요구를 거부하는 법을 배우고, 필요하다면 팀이 함께 거부하도록 하라.
  • 어쩔 수 없다면 회사를 그만두라.

많은 프로그래머들이 좋은 프로그래머이고, 좋은 프로그래머는 많은 것을 이루길 원한다. 이를 위해 그들은 시간을 효과적으로 관리해야 한다. 어떤 문제에 대해 생각을 가다듬고 거기에 깊이 몰두하게 될 때까지는 상당한 수고가 필요하다. 많은 프로그래머들은 생각을 가다듬고 몰두할 수 있는, 방해받지 않는 긴 시간이 있을 때 가장 잘 일할 수 있다고 한다. 하지만 사람들은 잠도 자고 다른 의무들도 이행해야 한다. 모든 사람들은 자신의 인간적 리듬과 업무적 리듬을 모두 만족시키는 방법을 찾아야 한다. 모든 프로그래머는, 아주 중요한 회의에만 참석하고 나머지는 일에 집중하는 날들을 확보하는 것과 같이, 효율적인 업무 기간을 획득하기 위해 최선을 다해야 한다.

나는 아이들이 있기 때문에 가끔이라도 아이들과 저녁 시간을 보내기 위해 노력한다. 나에게 가장 잘 맞는 리듬은, 하루 날 잡아 오래 일하고, 사무실이나 사무실 부근에서 잠을 잔 다음 (나는 집에서 직장까지 통근 시간이 길다), 일찍 집에 가서 아이들이 잠자리에 들기 전까지 시간을 보내는 것이다. 이것이 편안하지는 않지만, 여태껏 시험해 본 최선의 타협점이었다. 전염성 있는 병에 걸렸다면 집에 가라. 죽고 싶다는 생각이 든다면 집에 가야 한다. 몇 초 이상 누군가를 죽이고 싶다는 생각이 든다면 집에 가서 쉬어야 한다. 누군가가 가벼운 우울증을 넘어서 심각한 정신 이상이나 정신병의 증세를 보인다면 집에 가게 해야 한다. 피로 때문에 평소와 달리 부정직하거나 남을 속이고 싶다는 유혹이 든다면 쉬어야 한다. 피로와 싸우기 위해 마약이나 각성제를 쓰지 말라. 카페인을 남용하지도 말라.

2.2.11 까다로운 사람들과 상대하는 방법

까다로운 사람들과 상대해야 하는 일이 있을 것이다. 자기 자신이 까다로운 사람일 수도 있다. 나 자신이 같이 일하는 사람들이나 권위 있는 인물들과 수시로 충돌하는 유형의 사람이라면, 여기에서 볼 수 있는 독립심은 소중하게 여겨야 할 것이나, 자신의 지성이나 원칙들을 희생하지 않는 범위 내에서 인간관계의 기능도 길러야 할 것이다.

이런 종류의 일을 겪어 보지 않았거나, 지금까지 살면서 직장 생활에는 별로 쓸모없는 행동 양식만 익혀 온 프로그래머들에게는 이것이 짜증스러운 일이 될 수 있다. 까다로운 사람들은 대개 반대 의견에 단련되어 있고 다른 사람들과 타협해야 한다는 사회적 압력에 별로 영향을 받지 않는다. 이 때 열쇠는 그 사람들을 적당히 존중해 주는 것이다. 이것은 내가 하고 싶은 것 이상으로 해 주는 것이지만, 그 사람들이 원하는 만큼은 안 될 것이다.

프로그래머들은 팀으로 함께 일해야 한다. 의견 불일치가 생기면, 어떻게든 해결해야 한다. 무작정 피할 수는 없는 노릇이다. 까다로운 사람들은 종종 매우 똑똑하고 쓸 만한 이야기를 하기도 한다. 까다로운 사람에게 편견 없이 귀를 기울이고 이해해 주는 것은 매우 중요하다. 대화 단절은 의견 불일치의 기반이 되지만, 강한 인내심으로 극복할 수 있는 경우도 있다. 대화가 산뜻하고 정감 있게 이뤄지도록 노력하고, 의견 충돌을 일으킬 만한 논쟁에 말려들지 말라. 이해하려고 노력할 만큼 한 뒤에는 결단을 내리라.

으스대는 사람의 강요 때문에 동의하지도 않는 일을 하지는 말라. 자신이 팀장이라면 최선이라고 생각하는 일을 하라. 개인적 이유로 결정을 내리지 말고, 자기가 결정한 근거를 설명할 준비를 해 두라. 까다로운 사람이 팀장이라면, 그의 결정이 개인적으로 영향을 미치지 않도록 하라. 일이 자기 방식대로 진행되지 않아도, 그 다른 방식에 따라 마음을 다해 일하라.

까다로운 사람들도 변하며 나아지기도 한다. 내 눈으로 직접 본 적도 있지만, 그렇게 흔하지는 않다. 어쨌든 모든 사람은 수시로 오르락내리락하기 마련이다.

모든 프로그래머, 특히 팀장들이 대면해야 하는 도전들 중 하나는 까다로운 사람을 전적으로 몰두하게 하는 것이다. 그런 사람들은 다른 사람에 비해서 일의 책임을 피하거나 수동적으로 저항하는 경향이 크다.

3 중급자

3.1 개인적 기능들

3.1.1 의욕을 계속 유지하는 방법

프로그래머들이 아름답고 유용하고 멋진 것을 만들고 싶어 하는 의욕이 매우 크다는 사실은 훌륭하고도 놀라운 일이다. 이러한 욕구는 프로그래머에게만 있는 것도 아니고 보편적인 것도 아니지만, 이것은 프로그래머들 사이에 매우 강하고 일반적이어서 다른 일을 하는 사람들과 구분이 된다.

이것은 실제로 중요한 결과를 낳는다. 프로그래머에게 아름답지도 유용하지도 멋지지도 않은 일을 시키면 그들은 사기가 떨어진다. 너저분하고 멍청하고 지루한 일을 해서 돈을 많이 벌기도 한다. 하지만 결국에는 재미있게 하는 일이 회사에 큰 돈을 벌어다 준다.

분명히 의욕을 불러일으키는 기법들을 중심으로 조직된 모든 산업 분야에서 여기에 적용되는 것들이 있다. 프로그래밍에 해당한다고 인정할 만한 것들은 다음과 같다.
  • 그 일을 자랑스럽게 이야기한다.
  • 새로운 기법, 언어, 기술을 적용할 기회를 찾는다.
  • 각 프로젝트에서 아무리 작더라도 무엇인가를 배우거나 가르치려고 노력한다.

끝으로, 할 수 있다면, 개인적으로 의욕을 얼마나 고취시키는가에 따라 자신의 일의 영향력을 가늠해 보라. 예를 들어, 버그를 고칠 때, 내가 버그를 몇 개나 고쳤는지 세는 것으로 의욕이 생기지는 않는다. 지금까지 고친 버그의 개수가 아직 남아 있는 버그의 개수와 무관하며, 그 개수는 회사의 고객들에게 기여하는 전체 가치에서 아주 사소한 부분을 차지할 뿐이기 때문이다. 하지만, 버그 하나마다 고객 한 사람이 기뻐한다고 생각하면 그것은 개인적으로 의욕이 생기게 한다.

3.1.2 널리 신뢰받는 방법

신뢰받기 위해서는 신뢰받을 만해야 한다. 또한 활동이 두드러져야 한다. 자신에 대해 아무도 알지 못한다면, 아무런 신뢰도 받을 수 없을 것이다. 팀 동료처럼 자신과 가까운 사람들과 같이 있을 때는 이것이 큰 문제가 아닐 것이다. 신뢰는 자기 부서나 팀이 아닌 사람들에게 응답하고 지식을 줌으로써 쌓아간다. 때로는 이러한 신뢰를 악용하여 불합리한 부탁을 하는 사람도 있다. 이럴 때에는 걱정하지 말고, 그 부탁을 들어주자면 자신이 무슨 일을 포기해야 하는지 설명하면 된다.

모르는 것을 아는 체하지 말라. 팀 동료가 아닌 사람들에게는 "머리에서 맴돌면서 기억나지 않는 것"과 "전혀 알 수 없는 것"을 명확히 구분해야 할 것이다.

3.1.3 시간과 공간 사이에서 균형을 잡는 방법

대학에 가지 않아도 좋은 프로그래머는 될 수 있지만, 기초적인 계산 복잡도 이론을 모른다면 좋은 중급 프로그래머는 될 수 없다. O("big O") 표기법을 알 필요는 없지만, "상수 시간", "n log n", "n 제곱"의 차이는 이해할 수 있어야 한다. 이런 지식이 없어도 시간과 공간 사이에서 균형을 잡는 방법을 직관으로 알고 있을 수 있지만, 그런 지식이 없다는 것은 동료들과 대화할 때 필요한 튼튼한 기초가 없는 것과 같다.

알고리듬을 설계하거나 이해할 때, 그것을 실행하는 데 걸리는 시간은 입력 값의 크기의 함수인 경우가 있다. 이 때, 알고리듬의 실행 시간이 (변수 n으로 표현되는) 그 크기와 그 크기의 로그값의 곱에 비례하면, 그 최악(또는 기대되는, 또는 최선)의 실행 시간이 "n log n"이라고 말할 수 있다. 이 표기법과 말하는 방식은 자료 구조가 차지하는 공간에도 마찬가지로 적용될 수 있다.

내게는, 계산 복잡도 이론이 물리학만큼이나 아름답고 심오하게 보인다. (조금 딴 길로 샌 것 같다!)

시간(프로세서 속도)과 공간(메모리)은 서로 균형을 맞출 수 있다. 공학이란 타협에 대한 것이며, 이것은 아주 좋은 예가 된다. 이것은 항상 체계적인 것은 아니다. 일반적으로 꽉 조이는 인코딩을 통해 공간을 절약할 수 있지만, 그것을 디코딩해야 할 때는 계산 시간이 더 많이 걸릴 것이다. 캐쉬를 사용함으로써, 즉 가까이에 복사본을 저장할 공간을 사용함으로써 시간을 절약할 수 있지만, 캐쉬의 내용을 일관되게 유지하고 있어야 할 것이다. 자료 구조에 더 많은 정보를 담음으로써 시간을 절약할 수 있는 경우가 있다. 이것은 대개 적은 공간을 차지하지만 알고리듬이 복잡해질 수 있다.

공간이나 시간의 균형 관계를 개선하는 것은 종종 다른 쪽이 극적으로 변하게 할 수 있다. 하지만 이 작업을 하기 전에 지금 개선하려고 하는 것이 정말로 그런 개선이 필요한 것인지 스스로 물어야 한다. 알고리듬을 가지고 작업하는 것은 재미있는 일이지만, 아무 문제가 없는 것을 개선하려는 것은 눈에 띌 만한 차이는 못 내면서 검사할 짐만 늘어나게 할 것이라는 냉엄한 사실에 대해 눈이 가려지지 않도록 조심해야 한다.

최신 컴퓨터의 메모리는, 프로세서 시간과 달리 벽에 부딪히기 전까지는 그것이 어떻게 쓰이는지 볼 수 없기 때문에, 값싼 것처럼 보인다. 하지만 문제가 생기기라도 하면 그것은 재앙이 된다. 메모리를 사용하는 데에는, 메모리에 상주해야 하는 다른 프로그램에 미치는 영향이나, 메모리를 할당하고 해제하는 데 드는 시간 등의 숨어 있는 비용이 있다. 속도를 얻으려고 공간을 써 버리기 전에 이 사실을 주의 깊게 생각하라.

3.1.4 압박 검사를 하는 방법

압박 검사(stress test)는 재미있다. 처음에는 압박 검사의 목적이 시스템에 부하가 걸려도 잘 동작하는지 알아보는 것으로 보인다. 실제로는 시스템에 부하가 걸려도 잘 동작하지만 부하가 아주 클 때는 어떤 방식으로든 동작이 멈추는 경우가 대부분이다. 나는 이것을 벽에 부딪침 또는 탈진이라고 부른다. 예외가 있기도 하지만, 거의 항상 '벽'은 있다. 압박 검사의 목적은 그 벽이 어디에 있는지 알아보는 것이며, 그 벽을 얼마나 더 밀어낼 수 있을지 알아보는 것이다.

압박 검사 계획은, 그것을 통해 프로젝트에서 기대하는 바가 무엇인지 명확해지기 때문에, 프로젝트 초기에 세워야 한다. 웹 페이지 요청에 2초가 걸리는 것은 비참한 실패일까, 대단한 성공일까? 동시 사용자 500명은 충분한가? 이것은 물론 상황에 따라 다르지만, 그 요구에 부응하는 시스템을 설계할 때 그 답을 알고 있어야 한다. 압박 검사는 실제 상황을 충분히 쓸 만하게 본떠서 해야 한다. 동시에 시스템을 사용하면서 오류를 일으키거나 무엇을 할지 예측할 수 없는 500명의 사람들을 쉽게 흉내 내는 일이 실제로는 불가능하지만, 500개의 모의실험을 만들어 사람들이 어떻게 행동할지 본뜨게 해 보는 정도는 할 수 있을 것이다.

압박 검사를 할 때는 가벼운 부하에서 시작해서, 입력 비율이나 입력 크기와 같은 어떤 범위에 따라 벽에 부딪칠 때까지 부하를 늘려간다. 벽이 요구 조건을 만족하기에 너무 가깝다면, 어떤 자원이 병목이 되어 있는지 알아내라. (대개 주된 원인이 있기 마련이다.) 그것이 무엇 때문인가? 메모리? 프로세서? I/O? 네트워크 대역폭? 데이터 쟁탈? 그 후에는 그 벽을 어떻게 움직일 수 있을지 알아내라. 벽을 움직이는 것, 즉 시스템이 견딜 수 있는 최대 부하를 증가시키는 것이 부하가 적은 시스템의 성능에 도움이 되지 않거나 오히려 해가 될 수도 있다는 사실은 기억하라. 보통은 큰 부하가 걸렸을 때의 성능이 적은 부하가 걸렸을 때의 성능보다 중요하다.

머리 속으로 모형을 그릴 수 있도록 여러 가지 다른 차원들도 살필 수 있어야 한다. 한 가지 기법만으로는 충분하지 않다. 예를 들어, 로그 기록은 시스템에서 일어난 두 사건 사이의 벽시계 시간에 대해 잘 알 수 있게 해 주지만, 그것이 잘 구성되어 있지 않은 한, 메모리 사용이나 자료 구조의 크기에 대해서는 살펴볼 수 없다. 같은 원리로, 현대적인 시스템에서는 많은 컴퓨터와 많은 소프트웨어 시스템들이 서로 협력하여 동작하는데, 특히 벽에 부딪쳤을 때 (즉, 성능이 입력 크기에 비례하지 않게 될 때) 이 다른 소프트웨어 시스템들이 병목이 될 수 있다. 이 시스템들을 살펴볼 수 있는 것은, 모든 관련 장비들의 프로세서 부하만 측정할 수 있다 해도, 큰 도움이 될 수 있을 것이다.

벽이 어디에 있는지 아는 것은, 벽을 옮기는 것뿐만 아니라 사업을 효과적으로 관리할 수 있도록 예측 가능성을 마련해 주는 데에도 필수적이다.

3.1.5 간결성과 추상성의 균형을 잡는 방법

추상성은 프로그래밍의 열쇠이다. 얼마나 추상화해야 할지는 주의해서 선택해야 한다. 초보 프로그래머들은 열심이 지나쳐서 실제로 필요 이상으로 추상화하는 경우가 있다. 이것의 징조 중 하나는 아무 코드도 들어 있지 않으면서, 다른 것을 추상화하는 것 외에 아무 일도 하지 않는 클래스를 만드는 것이다. 이런 것의 매력은 이해할 만하지만 코드 간결성의 가치도 추상성의 가치에 비교하여 재 봐야 한다. 때때로 열정적인 이상주의자들이 저지르는 실수를 보게 된다. 즉 프로젝트 초기에 수많은 클래스들을 정의하면서 그것을 통해 추상성을 멋지게 달성하고, 발생할 수 있는 모든 사태를 다룰 수 있을 것으로 기대한다. 그런데 프로젝트가 진행되고 피로가 쌓임에 따라 코드 자체가 너저분해진다. 함수의 몸체는 되어야 할 것 이상으로 길어진다. 비어 있는 클래스들은 문서화하는 데 짐이 되고 압박이 오면 결국 잊혀진다. 추상화에 쏟은 에너지가 프로그램을 간결하고 단순하게 하는 데에 쓰였다면 최종 결과가 더 좋아졌을지도 모른다. 이것은 사색적인(speculative) 프로그래밍의 전형이다. 나는 폴 그레이엄(Paul Graham)의 "간결함이 힘이다(Succinctness is Power)"라는 글을 강력 추천한다. <PGSite>

정보 은닉이나 객체 지향 프로그래밍과 같은 유용한 기법들을 지나치게 사용하면서 독단적 견해가 생기기도 한다. 이 기법들은 코드를 추상화하게 하고 변화를 예측하게 한다. 하지만 개인적인 생각으로는 너무 사색적인 코드를 작성하지 않는 것이 좋다. 예를 들어, 어떤 객체의 정수 변수를 변경자(mutator)와 접근자(accessor) 뒤에 숨겨서 변수 자체는 드러나지 않고 작은 접점(interface)만 드러나게 하는 것은 받아들일 만한 스타일이다. 이를 통해서, 호출하는 코드에 영향을 주지 않으면서 그 변수의 구현 방식을 바꿀 수 있으며, 이것은 매우 안정된 API를 내놓아야 하는 라이브러리 제작자에게 적합할 것이다. 하지만 나는, 호출하는 코드를 우리 팀이 소유하고 있어서 호출되는 쪽만큼 호출하는 쪽도 다시 코딩할 수 있다면, 코드가 장황해지는 것을 감수할 만큼 이것이 이득이 된다고 생각하지는 않는다. 이런 사색적 프로그래밍으로 얻는 이득을 따져 보면 코드 너덧 줄 늘어나는 것도 아깝다.

이식성(portability)에 대해서도 비슷한 문제를 볼 수 있다. 모든 코드는 다른 컴퓨터, 컴파일러, 소프트웨어 시스템, 플랫폼에 바로 혹은 아주 쉽게 이식될 수 있어야 하는가? 나는 이식성은 없지만 간결하면서 쉽게 이식할 수 있는 코드가, 장황하면서 이식성 있는 것보다 낫다고 생각한다. 특정 DBMS에 맞는 데이터베이스 질의를 하는 클래스와 같이 이식성 없는 코드를 정해진 영역에 한정해 놓는 것도 비교적 쉬우면서 분명히 좋은 생각이다.

3.1.6 새로운 기능을 배우는 방법

새로운 기능, 특히 기술과 무관한 것을 배우는 것은 무엇보다 재미있는 일이다. 이것이 프로그래머들의 의욕을 얼마나 많이 불러일으키는지 이해하는 회사들은 사기가 더욱 높아질 수 있을 것이다.

인간은 행함으로 배운다. 책 읽기와 수업 듣기가 유용한 것은 틀림없지만, 아무 프로그램도 작성해 보지 않은 프로그래머를 인정할 수 있겠는가? 어떤 기능이든 배우기 위해서는 그 기능을 스스럼없이 연습해 볼 수 있어야 할 것이다. 새로운 프로그래밍 언어를 배울 때에는, 큰 프로젝트를 해야 하게 되기 전에 그 언어를 사용하는 작은 프로젝트를 해 보라. 소프트웨어 프로젝트 관리를 배울 때에는, 작은 프로젝트를 먼저 관리해 보도록 하라.

좋은 사수(mentor)가 있다고 해서 스스로 하는 것을 소홀히 해서는 안 되겠지만, 사실 책 한 권보다는 훨씬 낫다. 사수의 지식을 전수받는 대신 그들에게 무엇을 해 줄 수 있을까? 최소한 그들의 시간이 낭비가 되지 않도록 열심히 공부해 줄 수는 있을 것이다.

상사에게 공식 연수를 받게 해 달라고 요청해 보라. 하지만 이것이 그 시간 동안 배우고 싶은 새로운 기술을 가지고 놀아 보는 것보다 별로 나을 것이 없는 경우도 있다는 것을 알아 두라. 그렇다 해도, 우리의 불완전한 세상에서는 공식 연수에서 저녁 식사 파티를 기다리며 강의 내내 잠만 자는 경우가 많음에도 불구하고 노는 시간보다는 연수를 요청하기가 쉬울 것이다.

팀장이라면, 팀원들이 어떻게 배우는지 이해하고 그들에게 관심 있는 기능을 연습할 수 있는 적절한 규모의 프로젝트를 할당함으로써 그들을 도와주라. 프로그래머에게 가장 중요한 기능은 기술에 관한 것이 아니라는 사실을 잊지 말라. 팀원들에게 용기와 정직성과 의사소통 능력을 시험하고 실행할 수 있는 기회를 주라.

3.1.7 타자 연습

자판을 보지 않고 타자할 수 있도록 연습하라. 이것은 중급 기능이다. 왜냐하면, 코드 작성은 어려운 일이므로, 아무리 타자를 잘 한다 해도, 거기에는 타자 속도가 별 의미가 없고, 코드 작성에 걸리는 시간에 별 영향을 주지도 않기 때문이다. 하지만, 중급 프로그래머가 되면 동료나 다른 사람들에게 일반 언어로 글을 쓰는 데 많은 시간을 보내게 될 것이다. 이것은 자신의 헌신성에 대한 재미있는 시험이다. 그런 것을 배우는 일이 그렇게 즐겁지는 않지만 어쨌든 시간을 바쳐야 한다. 마이클 티먼(Michael Tiemann)이 MCC에 있을 때 사람들이 그의 방문 밖에 서서, 엄청나게 빠른 타자 속도 때문에 자판이 윙윙거리는 소리를 듣곤 했다는 전설이 있다. 주: 지금 현재 그는 레드햇(RedHat)의 최고 기술 책임자(CTO)이다.

3.1.8 통합 검사를 하는 방법

통합 검사(integration testing)는 단위별 검사를 마친 여러 구성요소들을 통합하는 검사이다. 통합은 비싼 대가를 치르고 얻게 되며, 그것은 이 검사를 통과함으로써 얻게 된다. 시간 추정과 일정 계획에 이 검사를 포함시켜야 한다.

이상적으로는 마지막 단계에서 별도로 통합을 실시하지 않아도 되도록 프로젝트를 조직해야 할 것이다. 프로젝트가 진행되는 과정에서 각 요소들이 완성되어 가면서 점진적으로 그것들을 통합하는 것이 훨씬 낫다. 별도의 통합 과정을 피할 수 없다면 주의해서 시간을 추정하라.

3.1.9 의사소통을 위한 용어들

프로그래밍 언어는 아니지만 의사소통을 위해 공식적으로 정의된 문법 체계에 따른 용어들이 있다. 이 용어들은 특별히 표준화를 통해 의사소통을 돕기 위해 만들어진 것이다. 2003년 현재, 이런 용어들 중 가장 중요한 것으로 UML, XML, SQL이 있다. 이것 모두에 대해서는, 제대로 의사소통하고 언제 그 용어를 사용할지 결정할 수 있기 위해, 어느 정도 친숙해질 필요가 있다.

UML은 설계에 대해 설명하는 도형을 그리기 위해 풍부하게 갖춰진 형식 체계이다. 시각적이고 형식적이라는 점에서 일종의 미학(beauty lines)이라고 할 수 있다. 저자와 독자가 모두 UML을 안다면 많은 양의 정보를 쉽게 전달할 수 있다. 설계 내용에 대해 의사소통할 때 UML을 사용하는 경우가 있으므로 그것을 알 필요가 있다. UML 도형을 전문적으로 그려 주는 유용한 도구들이 있다. 많은 경우에 UML는 너무 형식적이어서, 나는 설계 내용을 도형으로 나타내기 위해 단순한 상자와 화살표 형식을 사용하곤 한다. 하지만 나도 UML이 최소한 라틴어를 공부하는 것만큼 도움이 된다는 사실은 어느 정도 확신한다.

XML은 새로운 표준을 정의하기 위한 표준이다. 간혹 XML이 데이터의 상호교환 문제에 대한 해결책인 것처럼 소개되기도 하지만 사실 그런 것은 아니다. 그것보다는, 데이터의 상호교환에서 가장 따분한 부분, 즉 특정 방식으로 표현된 데이터를 선형의 구조로 나열하고, 그것을 다시 원래의 구조로 파싱하는 작업을 기특하게도 자동화하는 것이다. XML은, 비록 아직 필요한 것들의 일부만 구현되긴 했어도, 훌륭하게 자료형 검사와 정확성 검사를 한다.

SQL은, 완전한 프로그래밍 언어는 아니지만, 매우 강력하고 풍부한 데이터 질의와 처리 언어이다. 이것은 다양한 종류가 있으며, 특히 제품에 크게 의존하지만 표준화된 핵심 부분보다는 덜 중요하다. SQL은 관계형 데이터베이스의 공통 언어이다. 관계형 데이터베이스를 이해하는 것이 득이 되는 분야에서 일할 수도 있고 그렇지 않을 수도 있지만, SQL과 그 문법에 대해 기초적인 것은 알아둘 필요가 있다.

3.2 팀의 기능들

3.2.1 개발 시간을 관리하는 방법

개발 시간을 관리하기 위해서는, 프로젝트 계획서가 간결하고 신선(up-to-date)하도록 하라. 프로젝트 계획서에는 시간 추정, 작업 일정, 진척 상황을 표시하기 위한 진도표(milestones), 시간 추정된 각 과제에 대한 팀이나 자기 자신의 시간 할당 등이 들어 있다. 여기에는 품질보증팀 사람들과 회의, 문서 준비, 장비 주문과 같이 잊지 말고 해야 할 다른 일들도 포함된다. 팀으로 일하는 것이라면, 프로젝트 계획서는 프로젝트 시작부터 진행 과정 내내 팀 전체의 동의에 의한 것이어야 한다.

프로젝트 계획서는 의사결정을 돕기 위해 존재하는 것이지, 자신이 얼마나 조직적인 사람인지 보여 주기 위한 것이 아니다. 프로젝트 계획서가 너무 길고 오래되었다면 의사결정에 아무 소용이 없을 것이다. 실제로 이러한 의사결정은 개개인에 관한 것이다. 계획서와 판단력에 의해 과제를 이 사람에게서 저 사람에게로 넘길 것인지 결정해야 한다. 진도표는 진척 상황을 표시한다. 화려한 프로젝트 기획 도구를 사용한다면 그것으로 프로젝트에서 '개발 초기 대형 설계(Big Design Up Front)'를 하려는 유혹에 빠지지 말고, 간결함과 신선함을 위해 사용하도록 하라.

진도를 못 맞췄다면, 그 진도만큼 프로젝트 일정 완료가 늦어진다고 상사에게 알리는 등의 즉각적인 행동을 취해야 한다. 시간 추정과 작업 일정은 처음부터 완벽할 수는 없을 것이다. 이것은 진도를 못 맞춘 날들을 프로젝트 후반에 만회할 수 있을지도 모른다는 환상을 낳는다. 물론 그럴 수 있을지도 모른다. 하지만 이것은 그 부분을, 과대 추정할 수도 있었겠지만, 과소 추정했기 때문이다. 그러므로 좋듯 싫든 간에 프로젝트의 일정 완료는 이미 늦어진 것이다.

계획서에 다음의 시간들도 포함되도록 명심하라. 팀 내부 회의, 시연, 문서화, 일정에 따라 반복되는 활동들, 통합 검사, 외부인들 상대하기, 질병, 휴가, 기존 제품들의 유지, 개발 환경의 유지 등. 프로젝트 계획서는 외부인이나 상사에게 자신이나 자신의 팀이 무엇을 하고 있는지 보여 줄 수 있는 '중간' 다리가 될 수 있다. 이런 이유 때문에 계획서는 짧고 신선해야 한다.

3.2.2 타사 소프트웨어의 위험 부담을 관리하는 방법

프로젝트 내에서 통제할 수 없는 다른 조직이 만든 소프트웨어에 의존해야 하는 경우가 있다. 타사 소프트웨어와 연관된 큰 위험 부담들은 관련된 모든 사람이 인식하고 있어야 한다.

절대, 절대로 거품(vapor)에 희망을 두지 말라. 거품이란, 나올 것이라고 광고는 하면서 아직 나오지 않은 소프트웨어를 말한다. 이것은 업계를 떠나게 될 가장 확실한 방법이다. 어떤 특징이 있는 어떤 제품이 어느 날에 출시될 것이라는 소프트웨어 회사의 약속을 의심스러워하기만 하는 것은 현명하지 않다. 그것을 완전히 무시하고 그 소식을 들었다는 것조차 잊어버리는 것이 더욱 현명하다. 회사에서 쓰이는 어떤 문서에도 그 소프트웨어에 대해 쓰지 않도록 하라.

타사 소프트웨어가 거품이 아니라면, 위험 부담은 여전히 있지만 그래도 최소한 손을 써 볼 수는 있을 것이다. 타사 소프트웨어의 사용을 고려하고 있다면, 일찍 에너지를 들여서 그것을 평가해야 한다. 사람들은 세 가지 제품이 적합한지 평가하는 데에 2주에서 2개월이 걸린다는 말을 듣고 싶어 하지 않겠지만, 최대한 일찍 그 일을 마쳐야 한다. 적절한 평가 없이는 통합(integration)에 드는 비용을 정확하게 추정할 수 없다.

특정한 목적에 기존의 타사 소프트웨어가 적합한지는 극소수의 사람들만 알고 있다. 그것은 매우 주관적이며 일반적으로 전문가들의 영역이다. 이런 전문가들을 찾을 수 있다면 많은 시간을 아낄 수 있다. 프로젝트가 타사 소프트웨어 시스템에 너무 완전히 의존해서 통합에 실패하면 프로젝트 자체가 실패하는 경우도 있을 수 있다. 그러한 위험 부담들을 작업 일정에 분명히 밝혀 두라. 쓸 수 있는 다른 시스템을 확보하는 등 만약의 사태에 대비한 계획도 세워 두고, 위험 부담을 일찍 제거할 수 없을 때 스스로 그 기능을 구현할 수 있는 능력도 키워 두라. 절대로 일정이 거품에 의존하지 않도록 하라.

3.2.3 컨설턴트를 관리하는 방법

컨설턴트를 활용하되 그들에게 의지하지는 말라. 그들은 훌륭하고 많이 존중받을 만하다. 그들은 수많은 다양한 프로젝트들을 보아 왔기 때문에 특정한 기술들이나 프로그래밍 기법들에 대해 많이 알고 있는 경우가 있다. 그들을 활용하는 최선의 방법은, 사례를 중심으로 강의하는 사내 강사로서 활용하는 것이다.

하지만, 그들의 강점과 약점을 알 만한 충분한 시간이 없다고 해서 그들을 일반 직원들처럼 팀의 일원이 되게 할 수는 없다. 그들이 금전적 손실을 감수하는(financial commitment) 경우는 거의 없다. 그들은 아주 쉽게 떠난다. 회사가 잘 운영된다면 그들이 얻을 것은 별로 없을 것이다. 어떤 사람은 좋고, 어떤 사람은 그저 그렇고, 어떤 사람은 안 좋을 것이다. 컨설턴트를 선정할 때도 직원을 채용할 때처럼 주의를 기울이지 않는다면 좋은 사람을 만나기는 힘들 것이다.

컨설턴트가 코드를 작성해야 한다면, 계속해서 그 코드를 주의를 기울여 검토해야 한다. 검토해 보지 않은 코드가 뭉텅이로 있는 위험 부담을 안고서 프로젝트를 잘 마칠 수는 없을 것이다. 이것은 사실 모든 팀원들에게도 적용되는 사실이지만, 가까이에 있는 팀원들이야 잘 아는 사람들 아닌가.

3.2.4 딱 적당하게 회의하는 방법

회의에 드는 비용을 잘 고려하라. 그 비용은 회의 시간에 참석 인원수를 곱한 값이다. 회의가 필요할 때도 있지만 규모가 작을수록 좋다. 의사소통의 질은 소규모 회의 때 더 좋고, 낭비되는 총 시간도 적다. 누가 회의 때 지루해하는 것 같으면, 이것은 회의 참석 인원을 줄여야 한다는 신호이다.

비공식적인 의사소통을 장려하기 위해서는 할 수 있는 일은 모두 해야 한다. 다른 시간들보다 동료들과 함께 하는 점심시간 동안 쓸만한 일들이 더 많이 이뤄진다. 그런데 이것을 인식하지도 않고 이 사실을 지지하지도 않는 회사가 더 많다는 것은 부끄러운 일이다.

3.2.5 무리 없이 정직하게 반대 의견을 내는 방법

반대 의견은 의사 결정을 잘 하기 위해 꼭 필요한 것이지만, 조심스럽게 다뤄야 한다. 자기 생각을 적절히 표현했고 결정이 내려지기 전에 사람들이 그 생각에 귀를 기울였다고 스스로 느낀다면 좋겠다. 그 경우 더 이상 할 말은 없는 것이고, 이제는 결정된 일에 대해 반대했더라도 그것을 후원할 것인지 결정하면 된다. 반대했더라도 이제 그 결정을 따를 수 있다면 그렇게 하겠다고 말하라. 이것은 내가 생각이 있으며 무조건 찬성하는 사람이 아니지만, 결정된 것은 존중하며 팀의 일원으로 일한다는 자신의 가치를 보여 준다.

어떤 때는 결정 사안에 대해 반대했음에도, 의사결정권자들이 그 의견을 충분히 고려하지도 않고 결정을 내리기도 할 것이다. 이 때에는 문제 제기를 할 것인지 회사나 부족(tribe)의 차원에서 잘 따져 봐야 한다. 그 결정에 사소한 실수가 있는 것 같다면, 재고할 가치는 없을 것이다. 만약 그 실수가 크다면, 당연히 논쟁을 벌여야 한다.

보통은, 이것은 별 문제가 되지 않지만, 스트레스가 많은 상황이나 특별한 성격의 사람들에게는 이것이 개인적인 문제를 일으키기도 한다. 예를 들어, 일 잘 하는 어떤 프로그래머는 결정이 잘못되었다고 믿을 만한 충분한 이유가 있어도 그것에 도전할 자신감이 없다. 더 나쁜 상황이라면, 의사결정권자도 자신감이 없어서 그것을 자기 권위에 대한 개인적인 도전으로 받아들이기도 한다. 이런 경우에 사람들은 비열하게 머리를 써서 반응한다는 것을 꼭 기억할 필요가 있다. 논쟁은 남들이 없을 때 벌여야 하고, 새로운 사실을 알게 됨으로써 전에 내린 결정의 근거가 어떻게 달라지는지 잘 보여주도록 노력해야 한다.

그 결정이 번복되든 그렇지 않든, 그 대안은 충분히 검토되지 않았을 것이므로 나중에라도 '내 그럴 줄 알았어!' 하고 우쭐대지 않도록 하라.

3.3 판단 능력

3.3.1 개발 시간에 맞춰 품질을 조절하는 방법

소프트웨어 개발은 항상 프로젝트의 목적과 프로젝트의 마무리 사이에서 타협하는 일이다. 프로젝트 결과의 배치를 신속하게 하기 위해 공학적 혹은 사업적 감수성을 거스르면서까지 품질을 조절하라는 요구를 받을 수 있다. 예를 들어, 소프트웨어 공학적으로 형편없고 수많은 유지 보수 문제를 일으킬 것이 뻔한 일을 하도록 요구받을 수 있다.

이런 일이 일어난다면 우선 책임 있게 할 일은, 그 사실을 팀에 알리고 품질 저하에 따른 비용을 분명히 설명하는 것이다. 어쨌든, 상사보다는 그 사실에 대해 더 잘 이해하고 있어야 한다. 무엇을 잃을 것이고 무엇을 얻을 것인지, 또 이번에 잃은 것을 다음 단계에 만회하기 위해서는 어떤 비용을 감수해야 하는지 분명히 하라. 이 때, 잘 짜여진 프로젝트 계획서의 선명함이 도움이 될 것이다. 품질을 조절하는 것이 품질 보증의 노력에 영향을 준다면 (상사와 품질보증팀 사람들 모두에게) 그 사실도 지적하라. 품질 조절 때문에 품질 확인 과정을 거치면서 많은 버그가 발견될 것 같다면 그것도 지적하라.

그래도 요구가 계속된다면, 조잡해지는 것이 특정 부분에만 머물게 하여 다음 단계에 재작성이나 개선 계획을 세울 수 있도록 해야 한다. 이 사실을 팀에 알려서 그 계획을 세울 수 있게 하라.

슬래쉬닷(Slashdot)의 닌자프로그래머(NinjaProgrammer)는 이런 보석 같은 글을 보내 왔다.

설계가 좋으면 코드 구현이 나빠도 회복 가능성이 있다는 사실을 기억하라. 코드 전체에 인터페이스와 추상화가 잘 되어 있으면, 언젠가 있을 수 있는 코드 재작성의 고통도 훨씬 덜할 것이다. 코드를 명쾌하게 작성하기도 힘들고 고치기도 힘들다면, 이런 문제를 야기하는 핵심 설계에 무슨 문제가 없는지 생각해 보라.

3.3.2 소프트웨어 시스템의 의존성을 관리하는 방법

최신의 소프트웨어 시스템은 직접 통제할 수 없는 수많은 구성요소(component)들에 의존하는 경향이 있다. 이것은 상승효과(synergy)와 재사용을 통해 생산성을 높인다. 하지만, 각 요소들은 다음의 문제들을 수반한다.
  • 그 구성요소에 있는 버그는 어떻게 고칠 것인가?
  • 그 구성요소 때문에 특정 하드웨어나 소프트웨어 시스템만 사용해야 하는가?
  • 그 구성요소가 기능을 완전히 상실한다면 어떻게 할 것인가?

그 구성요소를 어떻게든 캡슐화 하여 주변과 격리시키고 언제든 다른 것으로 교체할 수 있게 하는 것이 항상 최선이다. 그 요소가 전혀 동작하지 않는 것으로 판명된다면 다른 것으로 대체할 수도 있지만, 직접 작성해야 할 수도 있다. 캡슐화가 이식성(portability)과 같은 말은 아니지만, 쉽게 이식될 수 있게 하기 때문에 거의 그와 같다고 할 수 있다.

구성요소들의 소스 코드를 갖고 있다면 위험 부담이 네 배 정도는 줄어든다. 소스 코드가 있다면 그것을 평가하기도 쉽고, 디버그 하기도 쉽고, 임시방편을 찾기도 쉽고, 수정판을 만들기도 쉽다. 수정판을 만든다면, 그것을 그 구성요소의 소유자에게도 보내 줘서 공식 배포판에 반영하게 해야 한다. 그렇지 않으면 비공식판을 유지하는 불편을 감수해야 할 것이다.

3.3.3 소프트웨어의 완성도를 판단하는 방법

다른 사람들이 작성한 소프트웨어를 사용하는 것은 견고한 시스템을 신속하게 완성하는 가장 효과적인 방법 중 하나이다. 그런 일을 망설일 필요는 없지만, 그에 연관된 위험 요소들을 검사해야 한다. 가장 큰 위험 부담 중 하나는 사용할 제품에 포함되어 사용되는 동안, 완성도를 갖추기 전의 소프트웨어에서 흔히 볼 수 있는 것처럼, 버그가 나타나거나 거의 작동 불능인 상태가 되는 것이다. 어떤 소프트웨어 시스템을 통합할 것인지 고려하기 전에, 그것이 사내에서 만들었든 타사에서 만들었든, 그것이 정말로 사용할 수 있을 만큼 충분한 완성도를 갖췄는지 고려하는 것은 매우 중요하다. 스스로 물어봐야 하는 열 가지 질문이 있다.
  1. 거품(vapor)은 아닌가? (약속만 있는 것은 완성도가 아주 떨어지는 것이다.)
  2. 그 소프트웨어에 대한 평판(lore)이 어떤지 알 수 있는가?
  3. 내가 첫 사용자인가?
  4. 개정판이 계속 나올 만한 충분한 동기(incentive)가 있는가?
  5. 유지 보수 노력이 계속되고 있는가?
  6. 현재 유지 보수 담당자가 없어도 사장되지 않을 것인가?
  7. 절반밖에 안 좋더라도 익숙한 다른 대안(seasoned alternatives)이 있는가?
  8. 부족(tribe)이나 회사에서 그것에 대해 알고 있는가?
  9. 부족이나 회사에서 추천할 만한가?
  10. 문제가 있어도 그것을 해결할 만한 사람을 채용할 수 있는가?

이 항목들을 조금만 생각해 봐도 잘 만들어진 자유 소프트웨어나 공개 소프트웨어가 기업의 위험 부담을 줄이는 데에 얼마나 큰 가치가 있는지 알 수 있을 것이다.

3.3.4 구입과 개발 사이에서 결정하는 방법

사업을 목적으로 한 회사나 프로젝트에서는 소프트웨어로 무엇인가를 달성하기 위해 노력하면서 빈번히 구입과 개발 사이에서 결정을 해야 한다. 이 단계에 왔다는 것은 두 가지 측면에서 불행한 일이다. 즉, 구입하지 않아도 되는 공개 소프트웨어, 자유 소프트웨어를 제쳐놓았기 때문일 것이고, 더 중요한 것은, 통합에 드는 비용도 고려해야 하기 때문에, 구입해서 통합하는 것과 직접 개발해서 통합하는 것 사이에서 결정하는 것을 의미하게 될 것이기 때문이다. 이것은 영업과 경영과 공학적 이해를 전체적으로 결합할 필요가 있다.
  • 자신의 필요와 그 소프트웨어가 설계된 목적이 얼마나 잘 맞는가?
  • 구입한 것 중 얼마만큼이 필요할 것인가?
  • 통합의 가치를 검토하는 데 드는 비용은 얼마나 되는가?
  • 통합하는 데 드는 비용은 얼마나 되는가?
  • 구입하게 되면 장기적 유지 보수 비용은 증가할 것인가, 감소할 것인가?
  • 그것을 구입함으로써 사업상 원치 않는 처지에 있게 되지는 않을 것인가?

다른 사업 전체의 기반이 될 만큼 큰 과제를 직접 개발하는 일은 다시 한 번 생각해 봐야 한다. 그런 생각은 대개 팀에 공헌을 많이 하게 될 똑똑하고 낙관적인 사람들이 제안하기 마련이다. 그들의 생각을 꼭 채택하고 싶다면, 사업 계획을 수정할 각오도 해야 할 것이다. 그러나 뚜렷한 생각 없이 원래 프로젝트보다 더 큰 솔루션(solution) 개발에 투자하는 일은 하지 말라.

이 질문들을 고려한 후에 개발에 대한 것과 구입에 대한 것, 이렇게 두 가지 프로젝트 시안을 준비해야 할 것이다. 통합 비용도 반드시 고려해야 할 것이다. 두 솔루션 모두에 대해 장기적 유지 보수 비용도 고려해야 한다. 통합 비용을 추정하기 위해서는 그 소프트웨어를 구입하기 전에 철저하게 평가해야 할 것이다. 그것을 평가할 수 없다면, 그것을 구입함으로써 생기는 예상 밖의 위험 부담까지 가정하여 그 제품을 구입하는 것에 대해 결정해야 한다. 고려할 구입 결정 대상이 여럿이라면, 각각을 평가하기 위해 상당한 노력을 들여야 할 것이다.

3.3.5 전문가로 성장하는 방법

자신의 권위보다 책임을 더 중하게 생각하라. 바라는 역할에 최선을 다하라. 자신을 개인적으로 도와준 사람들은 물론 조직 전체의 성공에 기여한 사람들에게 감사하라.

팀장이 되고 싶다면, 팀의 합의를 이루기 위해 애쓰라. 관리책임자가 되고 싶다면, 작업 일정에 책임을 지라. 팀장이나 관리책임자 대신 이 일을 맡는다면, 그들이 자유롭게 더 큰 일에 전념할 수 있을 것이므로, 그 일을 편하게 해 낼 수 있을 것이다. 그 일이 시험 삼아 해 보기에 너무 크다면, 한 번에 조금씩 하도록 하라.

자기 자신을 평가하라. 더 나은 프로그래머가 되고 싶다면, 존경하는 사람에게 어떻게 하면 그들과 같이 될 수 있는지 물어보라. 상사에게 물어볼 수도 있다. 그가 아는 것은 적어도 나의 경력에는 큰 영향을 미칠 것이다.

자기 일에 활용할 수 있는 새로운 기능을 배울 방법을 계획하라. 이것은, 새로운 소프트웨어 시스템에 대해 배우는 것과 같이 사소한 기술적 기능일 수도 있고, 글을 잘 쓰는 것과 같이 어려운 사회적 기능일 수도 있다.

3.3.6 면접 대상자를 평가하는 방법

사원이 될 사람들을 평가하는 일은, 그 가치에 비해 큰 노력을 들이지 않고 있다. 잘못된 채용은, 잘못된 결혼과 마찬가지로, 끔찍한 일이다. 모든 사람들은 자기 에너지의 상당 부분을 인재 발굴에 바쳐야 하지만, 실제로 그런 경우는 드물다.

다양한 면접 유형이 있다. 어떤 것은 고문과 같아서, 지원자가 심한 스트레스를 받게 되어 있다. 이것은 스트레스를 받는 상황에서 인격적인 결점과 약점이 드러날 수 있게 하는 매우 중요한 목적이 있다. 지원자들은 자기 자신에 대해 정직한 만큼 면접관에게 정직할 것이다. 그런데 인간의 자기기만(self-deception) 능력은 대단하다.

최소한 두 시간 동안은 지원자에게 기술적 기능에 대해 묻는 구두시험을 실시해야 한다. 여러 번 하다 보면, 그들이 아는 것이 무엇인지 즉시 파악하고, 경계를 명확히 하기 위해 그들이 모르는 것에 대해 즉시 반응할 수 있게 될 것이다. 면접 대상자들은 이것을 순순히 받아들일 것이다. 나는 면접 대상자들에게서 회사를 선택하는 동기 중 하나가 면접의 질적 수준이라는 말을 몇 번 들은 적이 있다. 좋은 사람들은, 전에 일하던 곳이 어디인지, 어느 학교를 나왔는지, 그 밖의 다른 사소한 특성들보다는 자기 실력 때문에 채용되기를 원한다.

면접을 하면서 그들의 학습 능력에 대해서도 평가해야 한다. 이것은 그들이 현재 알고 있는 것이 무엇인가보다 더욱 더 중요하다. 까다로운 사람이라는 낌새도 알아차려야 한다. 면접 후에 기록해 둔 것들을 비교하면서 이런 것을 알아차릴 수 있을 것이다. 하지만 한참 면접이 진행되는 동안에 그것을 알아차리기는 어렵다. 다른 사람과 의사소통하고 같이 일하는 것을 얼마나 잘 하는가는 최신 프로그래밍 언어에 능통한 것보다 중요하다.

한 독자는 면접 대상자들에게 집에서 풀어오는 시험을 실시하여 결과가 좋았다고 한다. 이 방법은 면접 때 말은 잘 하지만 정작 코딩은 못 하는 사람들을 추려낼 수 있다는 장점이 있다. (사실 그런 사람들이 많다.) 개인적으로 이 기법을 써 보지는 않았지만, 괜찮아 보인다.

끝으로, 면접은 판매의 과정이기도 하다. 지원자들에게 회사나 프로젝트를 잘 팔아야 한다. 하지만, 프로그래머에게 이야기하는 것이므로, 진실을 윤색하려고 하지는 말라. 나쁜 점에서 시작하여 좋은 점에 대해 강한 인상을 주면서 마무리 하라.

3.3.7 화려한 전산 과학을 적용할 때를 아는 방법

많은 프로그래머들이 알기는 하지만 거의 사용하지 않는 알고리듬, 자료 구조, 수학, 그 밖의 거창한 내용에 대한 지식들이 있다. 실제로 이런 훌륭한 지식들은 너무 복잡하여 일반적으로는 필요가 없다. 예를 들어, 대부분의 시간을 비효율적인 데이터베이스 질의를 만들고 있으면서 알고리듬을 개선한다는 것은 아무 의미가 없다. 프로그래밍을 하면서 시스템들이 서로 통신하게 한다거나 멋있는 사용자 환경(user interface)을 만들기 위해 매우 단순한 자료 구조를 사용해야 한다면 불행이 시작되는 것이다.

고급 기술을 사용하는 것이 적절한 때는 언제인가? 흔하지 않은 다른 알고리듬을 찾기 위해 책을 펼쳐야 하는 때는 언제인가? 그런 것을 사용하는 것이 유용할 경우가 있지만, 그 전에 주의해서 평가해야 한다.

사용하게 될지 모르는 전산 과학 기법에 대해 고려해야 하는 매우 중요한 세 가지 측면이 있다.
  • 캡슐화가 잘 되어 있어서, 다른 시스템에 미칠 위험 부담이 적고, 복잡도나 유지 보수 비용의 증가가 적은가?
  • 그 이득이 대단한가? (예를 들어, 기존의 것과 유사한 시스템이라면 두 배, 새로운 시스템이라면 열 배 정도의 이득)
  • 그것을 효과적으로 검사하고 평가할 수 있을 것인가?

다소 화려해도 주변과 격리가 잘 되어 있는 알고리듬은 전체 시스템에 대해 두 배 정도로 하드웨어 비용을 줄이거나 그 만큼 성능을 향상시킬 수 있을 것이므로 그것을 고려하지 않는 것은 어리석은 일이 될 것이다. 이런 해결 방법을 주장하기 위한 한 가지 열쇠는, 그 기술에 대한 연구는 이미 잘 되어 있을 것이고 남은 문제는 통합의 위험 부담일 것이므로, 그 위험 부담이 실제로 매우 적다는 것을 보여 주는 것이다. 이렇게 될 때 프로그래머의 경험과 판단 능력이 그 화려한 기술과 어울려 진정한 상승효과를 낼 수 있을 것이며 통합도 쉽게 이뤄질 것이다.

3.3.8 비기술자들과 이야기하는 방법

대중문화에서 기술자, 특히 프로그래머는 일반적으로 보통 사람들과는 다른 사람이라고 인식된다. 이것은 보통 사람들이 우리와 다르다는 뜻이다. 비기술자들과 대화할 때 이 사실을 염두에 두고 있는 것이 중요하다. 항상 듣는 사람을 이해해야 한다.

비기술자들은 똑똑하더라도 우리처럼 기술적인 것들을 만드는 일에 기초가 있지는 않다. 우리는 무엇인가를 만든다. 그들은 무엇인가를 팔거나 다루거나 세거나 관리하지만, 만드는 일에는 전문가가 아니다. 그들은 기술자들처럼 (물론 예외는 있지만) 팀으로 같이 일하는 것에도 익숙하지 않다. 주: 많은 독자들이 이 절의 내용이 오만하거나 자기 경험과는 거리가 멀다고 느낄 수 있다. 나는 비기술자도 매우 존중한다. 겸손한 척하려고 하는 말이 아니다. 나의 이런 신념들로 기분이 상했다면 용서를 구한다. 하지만 솔직하게 말해서 반대 사례를 경험하게 될 때까지는 그 신념을 거둘 수 없을 것 같다. 내가 이례적으로 운이 좋아서 그 동안 좋은 프로그래머들과 같이 일해 왔을 수도 있고, 다른 사람들이 프로그래머는 대화하기 부담스럽다는 고정관념(stereotype)을 일반적인 표준으로 생각하는 것일 수도 있다. 그들의 사회적 기능은 일반적으로, 팀이 아닌 환경에서 일하는 기술자들과 같거나 더 낫지만, 그들이 하는 일은 우리처럼 깊고 정확하게 의사소통을 해야 하거나 세부 과제를 조심스럽게 나눠야 하는 등의 일이 항상 요구되지는 않는다.

비기술자들은 간절히 다른 사람을 만족시키고 싶어 할 수도 있고, 기술자들에게 위협을 느낄 수도 있다. 우리와 똑같이, 그들도 기술자들을 만족시키기 위해서, 혹은 기술자들에게 다소 겁을 먹어서, 별 뜻 없이 '예'라고 말해 놓고는 나중에는 그 말에 책임지지 않을 수 있다.

비프로그래머들이 기술적인 것들을 이해할 수는 있지만 우리에게도 어려운 그것, 즉 기술적 판단 능력은 없다. 그들은 기술이 어떻게 적용되는지는 이해하지만, 왜 어떤 접근 방식은 석 달이나 걸리고, 다른 방식은 사흘이면 되는지 이해하지 못한다. (어쨌든, 프로그래머들이 이런 종류의 추정에 너무 냉정하다는 것도 맞는 말이다.) 이것은 그들과 함께 상승효과를 낼 수 있는 기회가 있다는 뜻도 된다.

팀에서 이야기할 때에는, 별 생각 없이, 일종의 줄임말을 사용할 것이다. 일반 기술이나 특히 함께 작업하는 제품에 대해 많은 경험을 공유하고 있기 때문에 그것이 효과적이다. 그런 경험의 공유가 없는 사람들에게 이야기할 때, 특별히 자기 팀원들도 같이 있다면, 줄임말을 사용하지 않는 데에 노력을 좀 들여야 한다. 이런 어휘는 우리와 그것을 공유하지 않는 사람들 사이에 벽을 만들고, 더 나쁘게는, 그들의 시간을 낭비하게 한다.

팀원들과 있을 때는 기본 가정이나 목표를 수시로 다시 말할 필요는 없으며, 대부분의 대화가 세부적인 것들에 초점이 맞춰진다. 외부인들과 함께 있을 때는 다른 방식으로 해야 한다. 그들은 우리가 당연하게 여기는 것을 이해하지 못할 수 있다. 어떤 것을 당연하게 여기고 다시 설명하지 않기 때문에, 실제로는 커다란 오해가 있는데도 서로를 잘 이해했다고 생각하면서 외부인들과 대화를 마칠 수도 있다. 자기 생각을 잘못 전달할 수 있다는 것을 항상 가정하고 실제로 그런 일이 없는지 잘 살펴봐야 한다. 그들이 잘 이해했는지 알아보기 위해, 요약을 하게 하거나 다른 말로 표현하게 해 보라. 그들을 자주 만날 기회가 있다면, 내가 효과적으로 대화하고 있는지, 어떻게 하면 더 잘 할 수 있을지 잠깐 물어보는 것도 좋다. 의사소통에 문제가 있다면, 그들에게 실망하기 전에 자신의 습관을 고칠 방법을 찾으라.

나는 비기술자들과 같이 일하는 것을 좋아한다. 가르치고 배울 기회가 많기 때문이다. 명확한 용어로 대화하면서, 예를 들어 가며 안내할 수도 있을 것이다. 기술자들은 무질서에서 질서를, 혼동됨에서 명확함을 찾아내도록 훈련받으며, 비기술자들은 우리의 이런 점을 좋아한다. 우리는 기술적 판단 능력이 있고 사업상의 문제들도 대개 이해할 수 있기 때문에, 종종 문제에 대한 간단명료한 해결책을 찾아내기도 한다.

비기술자들은 좋은 뜻으로, 그리고 잘 해 보려는 마음으로 우리가 일을 더 쉽게 해 낼 수 있을 것이라고 생각하는 해결책들을 제안하기도 한다. 사실은 훨씬 더 좋은 종합적 해결책이 존재하는데, 그것은 외부인들의 관점과 우리의 기술적 판단력이 함께 상승효과를 낼 때에만 보인다. 나는 개인적으로 익스트림 프로그래밍(Extreme Programming)을 좋아한다. 그것이 이런 비효율성을 중점적으로 다루고 있으며, 아이디어와 그에 대한 비용 추정을 신속하게 짝지음으로써, 비용과 이득이 최상으로 결합되는 아이디어를 쉽게 찾을 수 있게 해 주기 때문이다.

4 상급자

4.1 기술적 판단 능력

4.1.1 어려운 것과 불가능한 것을 구분하는 방법

어려운 일은 해 내고, 불가능한 일은 골라내는 것이 우리가 할 일이다. 대부분의 현직 프로그래머들의 관점에서 보면, 단순한 시스템에서 나올 수 없거나 비용을 추정할 수 없는 일은 불가능한 것이다. 이 정의에 따르면 연구라고 불리는 것은 모두 불가능한 일이다. 일거리들의 많은 부분이 어렵기는 하지만, 반드시 불가능한 것은 아니다.

이 구분은 전혀 우스운 것이 아니다. 과학적 관점에서든 소프트웨어 공학적 관점에서든, 실제로 불가능한 일을 하라는 요구를 받는 경우가 많을 것이기 때문이다. 어렵기는 해도 사업주가 원하는 것을 최대한 끌어낼 수 있는 합리적인 해결책을 찾도록 돕는 것이 우리가 할 일이다. 자신 있게 일정을 잡을 수 있고 위험 부담을 잘 이해하고 있다면, 그 해결책은 어려운 것일 뿐이다.

예를 들어, '각 사람에게 가장 매력적인 머리 모양과 색깔을 계산할 수 있는 시스템을 개발하라'와 같은 막연한 요구 사항을 만족시키는 것은 불가능한 일이다. 요구 사항이 좀 더 뚜렷해질 수 있다면, 그 일이 어려울 뿐인 일로 바뀌기도 한다. 예를 들어 다음과 같은 식이다. '어떤 사람에게 매력적인 머리 모양과 색깔을 계산할 수 있는 시스템을 개발하되, 그들이 그것을 미리 보고 수정할 수 있게 하여, 처음 제안한 스타일에 대한 고객 만족을 극대화함으로써 많은 수입을 얻을 수 있게 하라.' 성공에 대한 뚜렷한 정의가 없다면 성공할 수 없을 것이다.

4.1.2 내장 언어를 활용하는 방법

시스템에 프로그래밍 언어를 내장하는(embedding) 것은 프로그래머에게는 에로틱하다고 할 만한 황홀함을 느끼게 한다. 이것은 해 볼 수 있는 가장 창의적인 활동들 중 하나이다. 이것은 시스템을 굉장히 강력하게 만들어 준다. 이것을 통해 자신의 창의적이고 프로메테우스적인 능력을 최대한 발휘할 수 있다. 이것은 시스템을 친구로 만들어 준다.

세계적으로 가장 우수한 텍스트 편집기들은 모두 내장 언어를 갖추고 있다. 사용자가 그 언어에 완전히 통달하는 경지에까지 이를 수도 있다. 물론, 그것이 텍스트 편집기 안에 들어 있으므로, 써 보고 싶은 사람들은 쓸 수 있고 그렇지 않은 사람들은 그럴 필요 없도록, 그 언어의 사용을 선택 사항으로 둘 수도 있다.

나를 비롯하여 다른 많은 프로그래머들이 특수한 목적의 내장 언어를 만들고 싶다는 유혹에 빠지곤 한다. 나는 두 번 그런 적이 있다. 내장 언어로 특별히 설계된 언어들이 이미 많이 나와 있다. 새로운 것을 또 만들기 전에 한 번 더 생각해 볼 필요가 있다.

언어를 내장하기 전에 스스로 물어봐야 하는 진짜 질문은 이것이다. 이것이 사용자의 문화와 잘 맞을 것인가, 그렇지 않을 것인가? 사용자가 모두 비프로그래머라면 그것이 무슨 도움이 될 것인가? 사용자가 모두 프로그래머라면 오히려 API를 선호하지 않을 것인가? 무슨 언어로 할 것인가? 프로그래머들은 사용 범위가 좁은 새 언어는 배우고 싶어 하지 않는다. 하지만 그것이 그들의 문화와 잘 맞물린다면 많은 시간을 들이지 않고서도 배울 수 있을 것이다. 새로운 언어를 만든다는 것은 즐거운 일이다. 하지만 그렇다고 해서 사용자들의 필요에 대해 눈이 가려져서는 안 된다. 정말로 근본적인 필요와 아이디어가 있는 것이 아니라면, 사용자들이 이미 친숙한 기존의 언어를 사용해서 부담을 줄여 주는 것이 어떤가?

4.1.3 언어의 선택

자신의 일을 사랑하는 고독한 프로그래머(즉, 해커)는 과제에 가장 잘 맞는 언어를 선택할 수 있다. 대부분의 현직 프로그래머들은 자기가 사용할 언어를 마음대로 고를 수 있는 경우가 드물다. 일반적으로, 이 문제는 잘난 체하는(pointy-haired) 상사들이 마음대로 결정한다. 이들은 기술적으로 결정하기보다는 정략적으로 결정하고, 아직 일반화되지 않은 어떤 도구가 가장 좋다는 것을 (대개 실무 경험에 의해) 알면서도 재래식이 아닌 도구를 사용하자고 나설 만한 용기는 없다. 어떤 경우에는 팀 전체, 더 넓게는 공동체 전체의 통일이 매우 실제적인 이득이 있기 때문에 개인적인 입장에서 선택하는 것을 배제하기도 한다. 관리책임자들은 정해진 언어에 대한 경험이 있는 프로그래머들을 채용해야 하는 필요에 따라 움직이기도 한다. 그들이 프로젝트나 회사에 가장 큰 이익이 된다고 생각하는 것을 위해 일한다는 것은 분명하며, 그것에 대해 존중받을 만하다. 하지만 나는 개인적으로 이것이 흔히 마주치게 되는 가장 낭비적이고 잘못된 일이라고 생각한다.

물론, 모든 일이 1차원적인 경우는 없다. 한 가지 중심 언어가 필수로 정해지고 그것을 내가 어떻게 할 수 없다 해도, 도구나 다른 프로그램을 다른 언어로 작성할 수 있거나 그렇게 해야 하는 경우가 종종 있다. 언어를 내장해야 한다면 (이것은 항상 생각해야 한다!) 언어를 선택할 때 사용자들의 문화를 많이 고려해야 할 것이다. 회사나 프로젝트에 기여하기 위해 그 일에 가장 적합한 언어를 사용하는 것의 장점을 잘 활용해야 하며, 이것을 통해 일이 더욱 흥미로워질 것이다.

프로그래밍 언어는, 그것을 배우는 것이 자연 언어를 배우는 것만큼 어려운 일이 전혀 아니라는 점에서, 표기법들(notations)이라고 부르는 것이 실제에 가깝다. 초보자들이나 외부인들에게는 "새로운 언어 배우기"가 멈칫하게 될 과제로 보인다. 하지만 세 가지 정도 언어를 체험해 보면, 그 일은 주어진 라이브러리들에 익숙해지는 문제일 뿐이다. 구성요소들이 서너 가지 언어로 되어 있는 큰 시스템이 있을 때 그것을 지저분하게 뒤범벅이 되어 있다고 생각할 수 있지만, 나는 그런 시스템이 한 가지 언어만으로 되어 있는 시스템보다 여러 면에서 더 튼튼한 경우가 많다고 하겠다.
  • 서로 다른 표기법으로 작성된 구성요소들은 필연적으로 결합도(coupling)가 낮아진다. (깔끔한 인터페이스는 없겠지만)
  • 각 요소들을 개별적으로 재작성함으로써 새로운 언어/플랫폼으로 발전시키기 쉽다.
  • 실제로는 어떤 모듈들이 최신의 것으로 갱신되었기 때문일 수도 있다.

심리적인 효과일 뿐인 것도 있지만, 심리적인 것도 중요하다. 결국 언어에 대한 독재는 그것이 주는 유익보다 거기에 드는 비용이 더 크다.

4.2 현명하게 타협하기

4.2.1 작업 일정의 압박과 싸우는 방법

출시 시간(time-to-market)의 압박은 좋은 제품을 신속하게 내놓기 위한 압박이다. 이것은 재정적 현실을 반영하는 것이기 때문에 나쁠 것도 없고, 어떤 점에서는 건전한 것이다. 작업 일정의 압박은 내놓을 수 있는 시간보다 더 빨리 내놓기 위한 압박이며, 이것은 낭비적이고 건전하지도 않지만, 너무도 흔하다.

작업 일정의 압박은 몇 가지 이유로 존재한다. 프로그래머들에게 과제를 맡기는 사람들은 우리가 얼마나 강한 직업윤리를 갖고 있으며 프로그래머가 된다는 것이 얼마나 재미있는 일인지 충분히 인식하지 못한다. 아마도 그들은 자신의 행동 방식을 우리에게 그대로 비춰 보기 때문에, 더 빨리 하라고 요구하면 더 열심히 일하게 될 것이라고 믿는다. 이것은 어쩌면 실제로 사실일 수도 있지만, 그 효과는 매우 작으며 손해는 매우 크다. 게다가 그들은 소프트웨어를 만들기 위해 실제로 무엇이 필요한지 볼 수 있는 눈이 없다. 볼 수도 없고 스스로 만들 수도 없기 때문에, 그들이 할 수 있는 단 한 가지는 출시 시간의 압박을 보면서 프로그래머들에게 그것에 대해 떠들어대는 일이다.

작업 일정의 압박과 싸우는 열쇠는 그것을 출시 시간의 압박으로 바꿔 놓는 것이다. 이렇게 하는 방법은 가용 인력과 제품 사이의 관계를 잘 볼 수 있게 하는 것이다. 개입된 모든 인력에 대해 정직하고 상세하고 무엇보다도 이해할 만한 추정치를 내놓는 것이 이것을 위한 가장 좋은 방법이다. 이것은 직무의 조정 가능성에 대한 관리상의 의사결정을 잘 할 수 있게 해 준다는 추가적인 장점이 있다.

이런 추정을 통해 명백해지는 중요한 통찰은, 인력이 비압축성 유체(incompressible fluid)와 같다는 것이다. 그릇의 부피보다 더 많이 물을 눌러넣을 수 없듯이, 일정 시간 안에 더 많은 것을 우겨넣을 수 없다. 어떤 점에서 프로그래머는 '못 합니다.'라고 하기보다는, '원하는 그 일을 위해 무엇을 포기하겠습니까?'라고 해야 할 것이다. 추정을 명확하게 함으로써 프로그래머가 더욱 존중받게 되는 효과가 있을 것이다. 다른 직종의 전문가들은 바로 이렇게 행동한다. 이로써 프로그래머들의 고된 일이 눈에 보이게 될 것이다. 비현실적인 작업 일정을 잡았다는 사실도 고통스럽겠지만 모든 사람에게 분명히 드러날 것이다. 프로그래머들은 함부로 현혹할 수 있는 사람들이 아니다. 그들에게 비현실적인 것을 요구하는 일은 예의 없고 비도덕적인 일이다. 익스트림 프로그래밍(Extreme Programming)은 이것을 상세히 설명하고 있으며 그 과정을 확립해 놓고 있다. 나는 모든 독자들이 이 기법을 활용할 수 있을 만큼 운이 좋기를 바란다.

4.2.2 사용자를 이해하는 방법

우리에게는 사용자를 이해하고, 또한 상사가 그 사용자를 이해할 수 있게 도와 줄 의무가 있다. 사용자는 우리처럼 제품 생산에 깊이 개입되어 있지 않기 때문에 다음과 같이 조금 특이하게 행동한다.
  • 사용자는 일반적으로 짧게 말하고 끝낸다.
  • 사용자는 자기 일이 따로 있다. 그들은 대개 제품이 크게도 아니고 약간 개선되었으면 좋겠다고 생각한다.
  • 사용자는 그 제품 사용자들 전체를 볼 수 있는 눈이 없다.

우리는 그들이 원한다고 말한 것이 아니라, 정말로 그들이 원하는 것을 제공할 의무가 있다. 그래서 일에 착수하기 전에 이쪽에서 먼저 제안을 하고, 그것이 정말로 그들이 원하는 것이라는 동의를 얻는 것이 더 나을 수 있다. 하지만 그들은 이렇게 하는 것이 바람직하다는 것을 모를 수도 있다. 그것을 위한 자신의 아이디어에 대한 자신감의 정도는 경우에 따라 달라져야 한다. 고객이 정말로 무엇을 원하는지 아는 것에 대해, 자만심과 거짓 겸손, 둘 다 경계해야 한다. 프로그래머들은 설계하고 만들어내도록 훈련된다. 시장 연구자들은 사람들이 무엇을 원하는지 알아내도록 훈련된다. 이 두 종류의 사람들, 아니 한 사람 안에 있는 두 가지 사고방식은 함께 조화를 이룰 때 정확하게 통찰할 수 있는 최상의 조건이 된다.

사용자들과 시간을 많이 보낼수록 무엇이 실제로 성공적일지 더 잘 이해하게 될 수 있을 것이다. 가능한 한 많이 자신의 생각을 사용자들의 생각과 비교하여 검사해 봐야 한다. 할 수 있다면 그들과 함께 먹고 마시기도 해 봐야 한다.

가이 카와사키(Guy Kawasaki)는 사용자들의 말을 듣는 것에 더하여 그들이 무엇을 하는지 관찰하는 것의 중요성을 강조한 바 있다. <Rules>

내가 알기로, 의뢰인들이 진정으로 원하는 것이 무엇인지 그들 자신의 마음에 분명해지게 하는 일에 계약직 프로그래머나 컨설턴트들이 엄청난 어려움을 겪는 경우가 종종 있다. 컨설턴트가 될 생각이 있는 사람은 의뢰인을 선택할 때 그들의 수표책뿐만 아니라 그들의 머리 속이 얼마나 명료한지도 확인하라고 권하고 싶다.

4.2.3 진급하는 방법

어떤 역할로 진급하고 싶다면, 그 역할을 먼저 실행하라.

어떤 직위로 진급하고 싶다면, 그 직위에 기대되는 것이 무엇인지 파악하여 그것을 행하라.

임금 인상을 원한다면, 정확한 정보로 무장하고 협상하라.

진급을 할 때가 지났다고 느껴지면, 상사에게 그것에 대해 이야기하라. 진급을 하기 위해 무엇을 해야 하는지 숨기지 말고 그들에게 질문하라. 진부한 이야기로 들리겠지만, 스스로 무엇이 필요하다고 인식하는 것과 상사가 인식하는 것이 상당히 다른 경우가 종종 있다. 또한 이것은 어떤 식으로든 상사에게 그 일을 확실히 못 박아 두는 것도 된다.

대부분의 프로그래머들이 자신의 상대적 능력에 대해 어떤 면에서는 과장되게 생각하는 것 같다. 하지만, 우리가 모두 상위 10%가 될 수는 없는 노릇이다! 그러나, 심각하게 진가를 인정받지 못하는 사람들도 많이 봐 왔다. 모든 사람의 평가가 항상 정확하게 실체와 일치할 것이라고 기대할 수는 없지만, 한 가지 단서가 있다면 사람들은 일반적으로 적당히 공정할 것이라고 생각한다. 자신의 일을 드러내 보여주지 않는다면 제대로 평가받을 수도 없다. 때로는 우연한 실수나 개인적인 버릇 때문에, 충분히 주목받지 못하기도 한다. 집에서 주로 일하거나 팀이나 상사와 지리적으로 떨어져 있는 것 때문에 이것이 특히 어려워지기도 한다.

4.3 팀을 위해 일하기

4.3.1 재능을 개발하는 방법

니체(Nietzsche)는 이렇게 과시하며 말했다. <Stronger>

나를 파괴하지 않는 것은 나를 강하게 하는 것이다.


우리가 가장 많이 책임져야 할 대상은 우리 팀이다. 팀원들을 모두 잘 알아야 한다. 팀에게 도전적으로 요구하더라도, 지나치게 무거운 짐을 지워서는 안 된다. 그들이 긴장을 유지하는 방법에 대해 그들과 이야기해 봐야 할 것이다. 그들이 그것을 기꺼이 받아들인다면(buy in), 더욱 동기가 높아질 것이다. 모든 프로젝트, 또는 하나 건너 하나의 프로젝트마다 그들이 제안한 방법과 그들에게 좋을 것 같다고 생각되는 방법으로 긴장을 유지하게 해 줘야 한다. 그들에게 일을 더 많이 맡기는 것보다는, 새로운 기능을 알려주거나 더 좋게는 팀에서 능력을 발휘할 새로운 역할을 부여하여 긴장을 유지하게 하라.

다른 사람들은 물론 자기 자신도 간혹 일이 안 풀릴 수 있다는 것을 인정해야 하며, 일정대로 일이 진행되지 않을 경우를 대비한 계획을 세워 놓아야 한다. 항상 일이 잘 된다면, 모험은 아무 의미가 없을 것이다. 일이 안 풀리는 경우가 없다는 것은, 위험 부담 없이 편하게만 일을 하고 있다는 뜻이다. 누군가 일이 잘 안 됐다면, 그들이 성공한 것처럼 대우할 필요는 없겠지만, 최대한 부드럽게 대해야 한다.

모든 팀원들이 기꺼이 받아들이고 동기를 높일 수 있도록 노력하라. 팀원 각자에게 그들이 동기가 높지 않을 때 어떻게 해 주면 좋은지 터놓고 물어 보라. 그들을 불만족스러운 채로 내버려둬야 할 경우도 있지만, 각자가 바라는 것이 무엇인지는 알고 있어야 한다.

낮은 의욕이나 불만족 때문에 자기 몫의 짐을 일부러 지지 않는 사람을 무시해 버리거나 되는 대로 내버려 둘 수는 없다. 그들의 동기와 생산성을 높이도록 노력해야 한다. 참을 수 있는 한 계속 노력하라. 인내의 한계를 넘어섰다면 그들을 해고하라. 일부러 자기 능력 이하로 일하는 사람들을 팀에 계속 있게 할 수는 없다. 그렇게 하는 것은 팀에 공정한 일이 아니다.

능력 있는 팀원들에게는 그들이 능력 있다고 생각한다는 사실을 공개적으로 이야기함으로써 그 사실을 확인시켜 주라. 칭찬은 공개적으로, 비판은 사적으로 해야 한다.

능력 있는 팀원들은 자연적으로 능력이 모자라는 팀원들보다 더 어려운 과제를 맡는다. 이것은 아주 자연스러운 일이며, 모두가 다 열심히 일하는 한 아무도 이것 때문에 귀찮아하지 않을 것이다.

좋은 프로그래머 한 사람이 안 좋은 프로그래머 열 사람보다 생산성이 높은데도 그것이 봉급에 반영되지 않는 것은 이상한 일이다. 이것 때문에 미묘한 상황이 생긴다. 능력이 모자란 프로그래머가 길을 비켜 주면 일을 더 빨리 진행할 수 있는 경우가 사실 종종 있다. 그렇게 하면 실제로 단기간에는 더 많은 성과를 낼 수도 있다. 하지만, 부족(tribe) 전체로서는 여러 중요한 이득을 잃게 된다. 능력이 모자라는 팀원의 훈련, 팀 내 지식의 확산, 능력 있는 팀원이 없을 때 대신할 능력 등이 그것이다. 능력 있는 사람들은 이 점에 관해서는 너그러울 필요가 있고 그 문제를 다각도로 고려해야 한다.

능력 있는 팀원에게는 도전할 만하면서 범위가 잘 정의된 과제를 맡겨 보는 것도 좋다.

4.3.2 일할 과제를 선택하는 방법

프로젝트의 어느 부분을 맡아 할 것인지 선택할 때에는 자신의 개인적 필요와 팀 전체의 필요 사이에서 균형을 잡아야 한다. 물론 가장 잘 하는 일을 선택해야겠지만, 일을 더 많이 하는 것보다는 새로운 기능을 발휘할 수 있는 것을 통해 스스로 긴장을 유지할 수 있는 길을 찾아보도록 하라. 지도력과 의사소통 기능은 기술적 기능보다 더 중요하다. 자신의 능력이 뛰어나다면, 더 어렵고 위험부담이 큰 과제를 맡고, 위험부담을 줄이기 위해 프로젝트에서 그 일을 최대한 빨리 처리하라.

4.3.3 팀 동료들이 최대한 능력을 발휘하게 하는 방법

팀 동료들이 최대한 능력을 발휘하게 하기 위해서는, 단체정신을 키우고 팀원 모두가 개별적으로 도전을 느끼고 개별적으로 일에 몰두하도록 격려하라.

단체정신을 키우기 위해서는, 로고가 새겨진 옷이나 파티처럼 다소 진부한 것도 좋지만, 개인적으로 존중하는 것만큼 좋지는 않다. 모두가 모두를 존중하면 아무도 다른 사람을 깎아내리고 싶지 않을 것이다. 단체정신은 사람들이 팀을 위해 희생하고 자신의 이익보다 팀의 이익을 먼저 생각할 때 만들어진다. 팀장으로서 이 점에 대해 솔선해서 한 것 이상으로 다른 사람에게 요구할 수는 없을 것이다.

팀에 대한 지도력의 한 가지 열쇠는 모든 사람이 기꺼이 받아들일 수 있는 합의를 이뤄내는 것이다. 이것은 팀 동료들이 잘못된 일을 하는 것을 인정한다는 말이기도 하다. 즉, 그것이 프로젝트에 너무 큰 피해를 주지만 않는다면, 비록 나는 그렇게 하는 것이 잘못된 것이라는 분명한 확신이 있다 해도, 팀원 중 누가 자기 방식대로 일하는 것을, 합의에 따라 그렇게 하게 두어야 한다. 이런 일이 생길 때는, 찬성하지 말고 공개적으로 반대하되, 합의된 것은 받아들이라. 마음이 상했다거나 어쩔 수 없이 그렇게 한다는 식으로 말하지 말고, 그것에 반대하지만 팀의 합의를 더 중요하게 생각한다고 꾸밈없이 말하라. 이렇게 할 때 그들이 돌이키기도 한다. 그들이 돌이켰다면 그들에게 원래 계획대로 밀고 나가라고 고집하지는 말라.

그 문제에 대해 적절한 모든 관점에서 논의한 후에도 동의하지 않는 사람이 있다면, 이제 결정을 내려야 하며 이것이 나의 결정이라고 꾸밈없이 단언하라. 자신의 결정이 잘못되었는지 판단할 방법이 있거나 나중에 잘못된 것으로 밝혀진다면 할 수 있는 대로 빨리 돌이키고 옳았던 사람을 인정하라.

단체정신을 키우고 팀을 효과적으로 만들기 위해 어떻게 하는 것이 좋을지 팀 전체와 팀원 각자에게 물어보라.

자주 칭찬하되 분별없이 하지는 말라. 특별히 나와 의견이 다른 사람도 칭찬할 만할 때에는 바로 칭찬하라. 공개적으로 칭찬하고 사적으로 비판하라. 한 가지 예외는 있다. 과실을 바로잡아 가치가 증대된 것(growth)을 공개적으로 칭찬할 때에는 원래의 과실이 눈길을 끄는 난처한 상황이 될 수도 있으므로, 가치 증대에 대해서는 사적으로 칭찬하는 것이 좋다.

4.3.4 문제를 나누는 방법

소프트웨어 프로젝트를 맡아서 각 사람이 수행할 과제들로 나누는 것은 재미있는 일이다. 이것은 초반에 해야 한다. 관리책임자들이 그 일을 수행할 각 사람들에 대해 고려하지 않아도 시간을 추정할 수 있다고 생각하는 경우가 있는 것 같다. 이것은 불가능한 일이다. 각자의 생산성은 아주 크게 차이가 나기 때문이다. 어떤 구성요소(component)에 대한 특정 지식이 있는 사람도 계속 변화해 가므로, 수행 능력에서 여러 배의 차이가 날 수도 있다.

지휘자가 연주할 악기의 음색을 고려하고 운동 경기 코치가 각 선수의 능력을 고려하는 것처럼, 경험 많은 팀장이라면 프로젝트를 과제들로 나누는 일을, 그 과제가 할당될 팀원들과 분리해서 생각하지는 않을 것이다. 이것은 수행 능력이 뛰어난 팀이 해체되어서는 안 되는 이유이기도 하다.

여기에도 위험이 다소 따르는데, 사람들이 능력을 쌓아가는 과정에서 따분해져서 약점을 개선하거나 새로운 기능을 배우려고 하지 않는 경우가 그것이다. 하지만, 전문화는 남용되지만 않는다면 생산성 향상에 매우 유용한 도구가 된다.

4.3.5 따분한 과제를 다루는 방법

간혹 회사나 프로젝트의 성공에 결정적이기 때문에 과제가 따분해도 피할 수 없는 경우가 있다. 이런 과제들은 그것을 해야 하는 사람들의 사기를 실제로 떨어뜨릴 수 있다. 이것을 다루는 가장 좋은 기법은 래리 월(Larry Wall)이 말한 '프로그래머의 게으름의 미덕(virtue of Laziness)'을 불러일으키고 북돋아 주는 것이다. 자신이나 동료 대신 컴퓨터가 그 과제를 처리하게 하는 방법이 없는지 찾아보라. 수작업으로 한 주에 할 과제를 해결할 프로그램을 짜는 데 한 주가 걸린다 해도 이것이 더욱 교육적이고 필요하면 반복해서 쓸 수도 있으므로 더 큰 이득이 있는 것이다.

모든 방법이 수포로 돌아간다면, 따분한 일을 해야 하는 사람들에게 양해를 구해야겠지만 어떤 경우에도 그들이 혼자 하게 내버려 두지는 말라. 최소한 두 사람으로 팀을 짜서 그 일을 맡기고 그 일을 완수하기 위해 성실히 협력할 수 있도록 격려하라.

4.3.6 프로젝트를 위한 지원을 얻는 방법

프로젝트를 위한 지원을 얻기 위해서는, 조직 전체가 추구할 실제적인 가치를 잘 드러내는 이상(vision)을 만들고 알려야 한다. 이상을 만드는 일에 다른 사람들도 동참하도록 노력하라. 이것을 통해 그들에게는 우리를 지원할 이유가 생기며 우리에게는 그들의 아이디어를 얻는 혜택이 생긴다. 프로젝트를 위한 핵심적인 지원 인사들을 개별적으로 모집하라. 갈 수 있는 곳이면 어디든 가서, 말만 하지 말고 보여주라. 할 수 있다면, 자신의 아이디어를 시연할 수 있는 시제품(prototype)이나 실물모형(mockup)을 만들라. 시제품은 어느 분야에서든 효력이 있지만 소프트웨어 분야에서는 글로 쓴 어떤 설명보다 훨씬 더 우세하다.

4.3.7 시스템이 자라게 하는 방법

나무의 씨앗에는 어른 나무의 밑그림(idea)이 들어있지만 아직 그 형태나 능력이 완전히 발현되지는 않았다. 싹이 자라고 커진다. 점점 어른 나무를 닮아 가며 점점 더 쓸모 있게 되어 간다. 마침내 열매를 맺고, 그 후에는 죽어서 다른 생물을 위한 거름이 된다.

과장된 표현일 수도 있지만 소프트웨어도 그렇게 취급된다. 다리(bridge)는 그렇지 않다. 미완성 다리는 있어도 '아기 다리'는 없다. 다리는 소프트웨어에 비해 아주 단순하다.

소프트웨어가 자란다고 생각하는 것이 좋다. 그렇게 생각하면 머리 속에 완벽한 그림이 그려지기 전에도 훌륭히 전진해 갈 수 있기 때문이다. 사용자들의 반응을 듣고 소프트웨어의 성장을 바로잡아 줄 수 있다. 약한 가지를 쳐 주는 것도 건강에 좋다.

프로그래머는 전달받아 사용할 수 있는 완성된 시스템을 설계해야 한다. 그런데 고급 프로그래머는 그 이상을 할 수 있어야 한다. 완성된 시스템으로 귀결되는 성장 경로를 설계할 수 있어야 한다. 아이디어의 싹을 가지고 가능한 한 평탄한 경로를 따라 유용한 완성품이 만들어질 수 있게 하는 것이 우리가 할 일이다.

이를 위해서는, 최종 결과를 시각화하고 그것에 대해 기술팀이 흥미를 가질 수 있도록 전달해야 한다. 또한 현재 그들의 위치에서 그들이 원하는 위치까지 가는 경로를 비약 없이 잘 전달해야 한다. 나무는 그 기간 내내 살아 있어야 한다. 어느 순간에 죽었다가 나중에 부활할 수는 없다.

이런 접근 방법은 나선형 개발(spiral development)에 그대로 반영되어 있다. 그 경로에 따라 진도를 표시하기 위해 간격이 너무 멀지 않은 진도표(milestones)를 사용한다. 사업이라는 무한 경쟁의 환경에서는, 비록 잘 설계된 최종 목표와는 거리가 멀다 해도 진도별 배포판(milestone release)을 계속 내면서 최대한 빨리 돈을 버는 것이 상책이다. 프로그래머의 임무 중 하나는, 일정표에 명시되는 성장 경로를 현명하게 선택함으로써 즉각적인 이득과 미래의 이득 사이에 균형을 잡는 일이다.

고급 프로그래머는 소프트웨어와 팀과 개개인의 성장에 대한 3중의 책임이 있다.

독자인 롭 하퍼닉(Rob Hafernik)이 이 절에 대해 다음의 의견을 보내 왔는데, 전문을 인용하는 것이 가장 좋을 것 같다.

여기에서는 그 중요성이 덜 강조된 것 같습니다. 이것은 시스템만의 문제가 아니며, 알고리듬, 사용자 환경(user interface), 데이터 모형(data model) 등의 문제이기도 합니다. 이것은 대형 시스템의 작업을 할 때 중간 목표들을 향해 진도를 맞춰 가기 위해서는 정말로 대단히 중요합니다. 끝까지 다 가서야 전체가 전혀 작동하지 않는다는 사실을 알게 되는 공포 상황만큼 나쁜 것은 없을 것입니다. (보우터 뉴스 서비스(Voter News Service)가 최근 해체된 것을 보십시오. 역자 주: 보우터 뉴스 서비스는 미국 언론사들이 출구 조사를 위해 공동 설립한 기관으로, 2002년 11월 미국 중간 선거를 대비하여 1,000만 달러 이상을 투자하여 시스템을 새롭게 갖추었으나, 선거 당일에 총체적인 문제가 생겨 출구 조사 결과 발표를 포기하고 말았다. 이 기관은 2003년 1월 해체되었다.) 한 걸음 더 나아가 이것은 자연 법칙이라고까지 말하고 싶습니다. 대형의 복잡한 시스템은 무에서 시작하여 구현할 수 없습니다. 의도한 단계를 거쳐 가면서 단순한 시스템에서 복잡한 시스템으로 진화하는 것만 가능합니다.


이 인용글에 대해서는 "빛이 있으라(Fiat lux)!" 하고 응답할 수밖에 없을 것이다.

4.3.8 대화를 잘 하는 방법

대화를 잘 하기 위해서는 우선 그것이 얼마나 어려운 것인지 인식해야 한다. 이것은 기능 자체에 대한 기능이다. 대화할 대상자들이 결점이 있는 사람들이라는 사실 때문에 이 일은 더욱 어려운 일이 된다. 그들은 나를 이해하는 일에 별로 노력을 들이지 않는다. 그들은 말도 잘 못 하고 글도 잘 못 쓴다. 그들은 대개 과로하고 있거나 따분해하고 있으며, 지금 말하고자 하는 큰 문제들보다는 자기 자신의 일에만 초점을 맞추고 있는 것 같다. 개설되어 있는 강좌를 통해 글쓰기, 연설, 듣기 기능을 연습하면, 이것들을 잘 하게 될 때 문제가 어디에 있는지, 그것을 어떻게 고칠 수 있는지 더 쉽게 볼 수 있다는 장점이 있다.

프로그래머는 자기 팀과 대화하는 일에 생존이 달려 있는 사회적 동물이다. 고급 프로그래머는 팀 밖의 사람들과 대화하는 일에 만족이 달려 있는 사회적 동물이다.

프로그래머는 무질서에서 질서를 끌어낸다. 이것을 하는 한 가지 흥미로운 방법은 팀 밖에서 어떤 제안을 시작하게 하는 것이다. 이것은 뼈대(strawman)나 백지 형식으로, 혹은 단지 구두로 시작될 수 있다. 이렇게 이끌어 가는 것은 토론의 조건을 설정한다는 점에서 굉장히 큰 장점이 있다. 이를 통해 나 자신이 비판, 더 나쁘게는 거부와 무시에 내놓인다. 고급 프로그래머는 고유한 권한과 그에 따른 고유한 책임이 있으므로, 이것을 받아들일 각오를 해야 한다. 프로그래머가 아닌 사업가들은 여러 가지 점에서 지도력을 발휘하기 위해 프로그래머가 필요하다. 프로그래머들은 현실에 기초하여 아이디어와 현실을 이어주는 다리의 한 부분이다.

나도 대화를 잘 하는 일에 정통하지는 않지만, 현재 노력하고 있는 것은 네 갈래의 접근 방식이다. 이것은, 아이디어를 정돈하고 충분히 준비를 갖춘 다음, 구두로 이야기를 하고, 사람들에게 백지를 (실제 종이로든, 전자적으로든) 나눠주고, 시연을 하고, 인내심을 가지고 이 과정을 반복하는 것이다. 이런 어려운 대화 과정에서 충분히 인내심을 갖지 않는 때가 많다고 생각한다. 자기 아이디어가 즉각 받아들여지지 않는다고 해서 낙담해서는 안 된다. 그것을 준비하는 데에 노력을 들였다면, 그것 때문에 나를 하찮게 생각하는 사람은 없을 것이다.

4.3.9 사람들에게 듣고 싶어 하지 않는 말을 하는 방법

사람들에게 그들을 불편하게 할 말을 해야 할 때가 있다. 이 일은 어떤 이유가 있기 때문에 하는 것이라는 사실을 기억하라. 그 문제에 대해 아무 것도 할 수 없다 해도, 그들에게 할 수 있는 한 빨리 말해서 그들이 그 사실을 숙지하고 있게 해야 한다.

누군가에게 문제점에 대해 말하는 가장 좋은 방법은 해결책을 동시에 제시하는 것이다. 두 번째로 좋은 방법은 그 문제점에 대해 도움을 요청하는 것이다. 그 사람이 믿지 않을 위험이 있다면, 그 말을 지지해 줄 사람을 모아 봐야 할 것이다.

해야 하는 가장 불쾌하면서 일상적인 말들 중 하나는 ‘예정일을 넘길 것 같군요.’라고 말하는 것이다. 양심적인 프로그래머라면 이런 말을 하기가 싫을 테지만, 그래도 최대한 빨리 해야 한다. 진도 날짜를 지나쳤을 때, 할 수 있는 것이 모든 사람들에게 그 사실을 알리는 일밖에 없다 해도, 대응이 지연되는 것보다 안 좋은 일은 없다. 이런 일을 할 때에는, 물리적으로 같이 하지는 못하더라도, 정신적으로라도 팀으로 같이 하는 것이 더 좋다. 지금의 위치와 그것을 해결하기 위한 일에 대해 팀원들의 의견이 필요할 것이며, 팀원들도 그 결과를 함께 직시해야 할 것이다.

4.3.10 관리상의 신화들을 다루는 방법

신화라는 단어는 허구를 뜻하기도 한다. 하지만 좀 더 깊은 함축이 있다. 이것은 우주에 대해, 그리고 인류와 우주의 관계에 대해 설명하는 종교적으로 중요한 이야기를 뜻하기도 한다. 관리책임자들은 프로그래머로서 배운 것은 잊어버리고, 어떤 신화들을 믿곤 한다. 그 신화들이 거짓이라고 그들에게 설득하려고 하는 것은, 독실한 종교인에게 그들의 믿음에 대한 환상을 깨뜨리려고 하는 것처럼 귀에 거슬리고 성공 가능성이 없는 일이다. 그렇기 때문에 이런 신념들을 신화라고 인식해야 한다.
  • 문서는 많이 만들수록 좋다. (그들은 문서를 원하지만, 그들은 문서를 작성하는 데 시간을 쓰는 것을 원하지 않는다.)
  • 프로그래머들은 다 같다. (프로그래머들도 천차만별로 다르다.)
  • 지연되는 프로젝트에 새로운 자원을 투입해서 진척 속도를 높일 수 있다. (새로 들어온 사람들과 대화하는 데 드는 비용은 거의 항상 도움이 되기보다는 부담이 된다.)
  • 소프트웨어 개발 시간을 확실히 추정하는 것이 가능하다. (이론적으로도 불가능하다.)
  • 프로그래머의 생산성은 코드의 줄 수와 같은 간단한 척도로 측정될 수 있다. (간결한 것을 중시한다면 코드의 줄 수가 늘어나는 것은 좋은 것이 아니라 나쁜 것이다.)

기회가 된다면 이러한 것들을 설명해 볼 수는 있을 것이다. 하지만 실패하더라고 기분나빠 하지 말고, 이런 신화들에 호전적으로 맞서다가 자신의 평판이 나빠지게 하지도 말라. 이러한 신화들 때문에 관리책임자들은 현재 진행되는 프로젝트에 대해서 실제로 통제력을 가지고 있다는 생각을 더욱 굳힌다. 결국 관리책임자가 좋은 사람이라면 도움이 될 것이고, 안 좋은 사람이라면 방해가 될 것이라는 것이 진리이다.

4.3.11 조직의 일시적 혼돈 상태를 다루는 방법

조직이 짧은 시기 동안 큰 혼돈을 겪는 때가 종종 있다. 예를 들어, 근신(layoff), 기업 인수(buyout), 회사 공개(IPO, Initial Public Offering), 해고, 신규 채용 등이 그것이다. 이것은 모든 사람의 마음을 어지럽게 하지만, 자기의 지위가 아닌 능력에 근거하여 개인적 자부심을 가지고 있는 프로그래머는 어지러움이 조금 덜할 것이다. 조직의 혼돈은 프로그래머들이 자신의 마법의 능력을 시험해 볼 수 있는 좋은 기회이다. 나는 이 이야기를 끝까지 아껴 두었다. 이것은 우리 부족(tribe)의 은밀한 비밀이기 때문이다. 프로그래머가 아닌 사람은 이제 그만 읽기를 바란다.

기술자들은 만들고 유지하는 능력이 있다.


전형적인 소프트웨어 회사의 경우, 비기술자들이 주변 사람들에게 지시할 수는 있지만, 기술자 없이는 아무것도 만들거나 유지할 수 없다. 이는 기술자가 일반적으로 제품을 팔거나 사업을 효과적으로 운영할 수 없는 것과 같다. 기술자들의 이 능력은 일시적인 조직의 혼란(mayhem)에 관한 거의 모든 문제들에 대해 버틸 수 있는 힘이 된다. 이런 능력을 가졌다면 이 혼돈을 완전히 무시하고 마치 아무것도 일어나지 않은 것처럼 행동하면 된다. 물론 자신이 해고되는 경우도 있겠지만 이 마법의 능력 때문에 곧 새로운 직장을 찾게 될 것이다. 더욱 흔하게는, 마법의 능력도 없이 스트레스를 받고 있는 사람이 내 자리에 와서 어떤 어리석은 일을 하라고 할 수도 있다. 그 일이 정말로 어리석다고 확신한다면 그 사람 앞에서 그냥 웃으면서 고개를 끄덕이고 회사를 위해서 가장 좋다고 생각되는 다른 일을 하는게 최선이다.

만약 팀장이라면, 팀원들에게도 같은 식으로 하라고 말해 주고, 다른 누가 무슨 말을 해도 무시하라고 말해 주라. 이런 행동 방식이 나 개인에게도 최선이고, 회사와 프로젝트에도 최선이다.

5 참고 문헌

5.1

<Rules00> Guy Kawasaki, Michelle Moreno, and Gary Kawasaki. 2000. HarperBusiness. Rules for Revolutionaries: The Capitalist Manifesto for Creating and Marketing New Products and Services.

<RDev96> Steve McConnell. 1996. Microsoft Press. Redmond, Wash. Rapid Development: Taming Wild Software Schedules.

<CodeC93> Steve McConnell. 1993. Microsoft Press. Redmond, Wash. Code Complete.

<XP99> Kent Beck. 1999. 0201616416. Addison-Wesley. Extreme Programming Explained: Embrace Change.

<PlanXP00> Kent Beck and Martin Fowler. 2000. 0201710919. Addison-Wesley. Planning Extreme Programming.

<Prag99> Andrew Hunt, David Thomas, and Ward Cunningham. 1999. 020161622X. Addison-Wesley. The Pragmatic Programmer: From Journeyman to Master.

<Stronger> Friedrich Nietzsche. 1889. Twilight of the Idols, "Maxims and Arrows", section 8.

5.2 웹 사이트

<PGSite> Paul Graham. 2002. 그의 사이트에 있는 논설(article)들: http://www.paulgraham.com/articles.html 모두 읽을 만하지만, 특히 "Beating the Averages".

<Hacker> Eric S. Raymond. 2003. How to Become a Hacker. http://www.catb.org/~esr/faqs/hacker-howto.html.

<HackDict> Eric S. Raymond. 2003. The New Hacker Dictionary. http://catb.org/esr/jargon/jargon.html

<ExpCS> Edsger W. Dijkstra. 1986. How Experimental is Computing Science? http://www.cs.utexas.edu/users/EWD/ewd09xx/EWD988a.PDF

<Knife> Edsger W. Dijkstra. 1984. On a Cultural Gap. http://www.cs.utexas.edu/users/EWD/ewd09xx/EWD913.PDF

6 역사 (2003년 5월 현재) / History (As Of May, 2003)

6.1 피드백 및 확장 요청 / Request for Feedback or Extension

이 에세이에 대한 의견이 있다면 나에게 보내주십시오. 나는 모든 의견을 반영하려고 노력하고 있으며 많은 수의 의견들이 이 에세이를 발전시켰습니다.

이 에세이는 GNU Free Documentation License를 따릅니다. 이 라이센스는 에세이에만 적용되는 것이 아닙니다. 에세이는 보통 한 사람의 하나의 관점에 바탕을 두고 쓰여져서 특정한 주장에 집착하고 그것을 확신시키려는 경향이 있습니다. 나는 이 에세이가 쉽고 즐겁게 읽힐 수 있었으면 합니다.

또한 나는 이것이 교육적이었으면 합니다. 비록 교과서는 아닐지라도 이것은 많은 단락으로 나뉘어 있어서 쉽게 새로운 단락이 추가될 수 있습니다. 너무 한쪽 시각으로 편중되었다고 생각한다면 올바르다고 생각되는 쪽으로 이 에세이에 추가하십시오. 이것은 이 라이센스의 목적이기도 합니다.

이 문서가 확장될 가치가 있다고 생각하는 것이 너무 잘난 척 하는 것 같기도 하지만, 이것이 영원히 진화됐으면 합니다. 이것이 다음과 같은 방식으로 확장된다면 기쁘겠습니다.
  • 쉽게 읽을 수 있는 목록이 각 단락에 추가됨.
  • 더 많은, 더 좋은 단락이 추가됨.
  • 비록 단락 단위로 번역되는 한이 있더라도 다른 언어로 번역됨.
  • 틀린 점이나 보충할 것이 추가됨.
  • 팜(PDA의 한 종류) 문서나 더 나은 HTML 같이 다른 문서 형식으로 변환될 수 있게 됨.

만약 이러한 일을 나에게 알려 준다면 이 라이센스(GFDL)의 목적에 따라서 다음 버전에 추가하겠습니다. 물론 이 라이센스에 나와 있는 대로, 나에게 알리지 않아도 이 문서에 대한 자기만의 버전을 만들 수 있습니다.

감사합니다.

로버트 L. 리드(Robert L. Read)

6.2 원본 / Original Version

이 문서의 원본은 로버트 L. 리드(Robert L. Read)에 의해 2000년부터 시작되었고 2002년에 사미즈다트(Samizdat) 출판사에서 최초로 전자 문서로 출판되었다.(http://Samizdat.mines.edu) Hire.com의 프로그래머들에게 이 문서를 바친다.

2003년 슬래쉬닷(Slashdot)에서 이 기사가 언급되었고 약 75명의 사람들이 제안과 수정할 것들을 이메일로 나에게 보내왔다. 그것에 감사한다. 비록 많은 중복이 있지만 다음에 열거된 사람들은 커다란 제안을 했거나 문제점을 고치도록 도움을 준 사람들이다. 모건 맥과이어(Morgan McGuire), 데이빗 메이슨(David Mason), 톰 뫼르텔(Tom Moertel), 슬래쉬닷의 닌자 프로그래머(Ninja Programmer) (145252), 벤 비어크(Ben Vierck), 롭 하퍼닉(Rob Hafernik), 마크 하우(Mark Howe), 피터 파라이트(Pieter Pareit), 브라이언 그레이슨(Brian Grayson), 제드 A. 쇼어(Zed A. Shaw), 스티브 벤즈(Steve Benz), 막심 이오프(Maksim Ioffe), 앤드류 우(Andrew Wu), 데이빗 제쉬키(David Jeschke), 톰 코코런(Tom Corcoran).

마지막으로 크리스티나 밸러리(Christina Vallery)에게 감사를 드린다. 그의 수정과 사려깊은 읽기를 통해서 두번째 원고가 크게 발전했다. 그리고 웨인 알렌(Wayne Allen)에게 이 일을 시작하도록 격려해 준 것에 대해서도 감사 드린다.

6.3 원저자의 경력 / Original Author's Bio

로버트 L. 리드(Robert L. Read)는 미국 텍사스주 오스틴에서 부인과 두 아이와 함께 살고 있다. 현재 Hire.com에서 4년 동안 총수석 엔지니어로 일하고 있다. 그전에는 제지 산업 분야에서 스캐너로부터 이미지를 읽어 그것의 품질을 조절해주는 도구를 생산하는 4R Technology를 설립하기도 했다.

그는 1995년 텍사스 주립 대학에서 데이터베이스 이론에 관한 연구로 전산 과학 박사 학위를 받았다. 1987년에는 라이스(Rice) 대학에서 전산 과학 학사 학위를 받았다. 그는 16세 때부터 직업 프로그래머로 일하고 있다.


원저자가 보내온 편지
Subject: RE: Your essay translated into Korean!
Date: Wed, 7 Jan 2004 09:28:48 -0600
From: <Read@hire.com>
To: <*******>

	Thank you!  I am thrilled and honored that this would be done.
I hope it benefits some Korean speakers who do not read English well
enough to enjoy the original.
	Unfortunately, I do not speak Korean---yet.  I do plan to study
it before I reach old age, but there are several other languages that 
I would like to master first.
	It is my understand that the Korean writing sysem (Han Gul?) is
a great acheivement of human invention and the Korean people.


-----Original Message-----
From: *******
Sent: Wednesday, January 07, 2004 7:49 AM
To: Rob Read
Subject: Your essay translated into Korean!


Thank you for your insightful essay, "How to be a Programmer."

Finally it's been translated into Korean on a Wiki site. You can read
it, if you can read Korean ;-), on
http://wiki.kldp.org/wiki.php/HowToBeAProgrammer
2007/01/07 00:01 2007/01/07 00:01
이 글에는 트랙백을 보낼 수 없습니다

작성일 ::

v2, 오후 6:45 2005-09-27

v1, 오전 11:26 2005-08-31

[지원스팩 APP]
# BVRDE - 문번호 미지원, 블럭폴딩 미지원
# 에디트플러스, 2.20, http://www.editplus.com/kr/, 블럭폴딩 미지원
# Relo,1.1, http://www.fifsoft.com/relo/
           http://sourceforge.net/projects/fidel/ 윈도우용
# Wide Studio, http://sourceforge.net/projects/widestudio/
# Visual-Mingw

+ 진행상황 : 개발중단

+ 사이트 : http://visual-mingw.sourceforge.net/
+ 스크린샷  http://visual-mingw.sourceforge.net/shots.htm
# Mingw Studio,

+ 사이트   : http://www.parinya.ca/ (<---폐쇄됨)
+ 스크린샷  http://www.parinya.ca/mds_win_scn_tn.html
            http://www.parinya.ca/mds_linux_scn_tn.html
# Rhide, http://www.rhide.com/(예전 터보씨처럼 생겼음)
# Anjuta C/C++ IDE,  https://sourceforge.net/projects/anjuta/
# The V C++ GUI Framework, http://sourceforge.net/projects/vgui/ 
RSXIDE
Quincy 2005
VIDE

TC비슷

$ DAC, http://www.ristancase.com/dac/v40/index.php
+ 스크린샷  http://www.ristancase.com/dac/v40/images/DACV40big.gif

$ Edituer, http://www.studioware.com/index.htm


[IDE 표준 스팩]

[1.에디터]
-라인넘버(문번호)
-블럭폴딩,Code Folding
+ http://www.ristancase.com/dac/v40/images/DACV40big.gif

-신텍스하이라이트(문법강조,Syntax Highlight )
-프로젝트관리자(워크스페이스메니져, 단위파일관리자)
-찾기
-인쇄미리보기

; 여백편집가능


-헥스에디터
+ http://www.gridinsoft.com/notepad/screenshot08.php
+ http://www.utopia-planitia.de/us/screenshot5.html

-템플릿지원
-다이어그램 표현

; UML같은것 표현가능


-섹션브라우져,Classviewer
+ http://www.utopia-planitia.de/us/screenshot4.html

-컴파일 디버깅
+ http://www.slickedit.com/images/stories/screenshots/vs10_java_debug_latest.jpg

-Output Window
-Build Project 
Resource Editor 
CVS Support
Project Converter 
Code Style

-자동 맞춤법검사

-코드 인사이트(하위구성요소 접근메뉴)


  Templates  Macros
Function List 
Column Editing
FTP/SFTP 
Run DOS Cmd 
Projects/Solutions


HTML Preview
Proj Templates 
Batch Builder
  IntelliTips  

[2.디버거]

- 디버깅 툴


[3.컴파일러]

- 기본환경을 제공

[4.문서화도구]

- 문서화 툴

[5.비교도구]

- 비교도구(Diff Tool) 

[6. 사용자정의 도구]

-사용자 정의 도구(User Tools)

[메인스크린]
http://www.activestate.com/Products/Download/featuretour.plex?id=Komodo&pageid=GraphicalDebugger
http://www.utopia-planitia.de/us/screenshot1.html





[개발미러사이트]
Insight 디버그툴 : http://sources.redhat.com/insight/

레드햇 커뮤니티 프로젝트 : http://sources.redhat.com/mirrors.html

[JAVA사이트]
이클립스 관련 : http://www.jlab.net/

http://www.javanuri.com/board/mainDownload.jsp

http://www.javaforum.co.kr/javaforum/qna/List.faces


[기타 개발도구 및 참고 site]

컴파일러 목록 : http://www.devzoo.com/index.php?tooltype=WindowsEditor

mingw studio (X) : http://www.parinyasoft.com/

OpenWatcom : http://www.openwatcom.org/



pcGRASP 만들고자했던것
jgrasp183 자바 1.3 이상 필요

# VC++ ADD-IN
wntabs         : http://www.wndtabs.com/
Visual Assist : http://www.wholetomato.com

#doxbar 0.38
http://blog.naver.com/thexder.do?Redirect=Log&logNo=8280195
http://doxbar.sourceforge.net/index.html
http://www.graphviz.org/

------------------------------------------------------------------------------------


# C++ FAQ
http://www.parashift.com/c++-faq-lite/virtual-functions.html#faq-20.5


-------------------------------------------

# 문서화 도구

순서도 작성프로그램
codevisual to flowchart


2006/10/22 02:15 2006/10/22 02:15
이 글에는 트랙백을 보낼 수 없습니다
// 제작자 : 정 헌 학
// 연락처 : hunhak94@yahoo.co.kr
// 저작권 : GPL ( Gnu Public License )

// HUP, fork 를 이용한 daemon 만들기.
#include "sysconfig.h"
#include "Server.h"
#include "MailFilterServer.h"
#include "FileCtrl.h"
// for read config file #
include <signal.h>
// for signal 'HUP' char **argv;
// 시그널 핸들링 함수 .
void sig_hangup(int signum) {
cout << "Restarting...." << '\n'; execv(argv[0], argv); cout << "Could not restart" << '\n'; abort();
} void daemon() {
pid_t pid = fork();
switch (pid) {
case 0: close(0);
// 모든 출력을 닫읍시다. close(1); close(2); pid = setsid(); break; case -1: f
printf(stderr, "fail to launch daemon process\n"); exit(1); break; default: exit(0); break; } } int main(int argc, char** strings) { daemon(); // 만약 이게 없으면 부모 ps 는 좀비가 된다. // for HUP signal. struct sigaction act; bzero(&act, sizeof(act)); act.sa_handler = sig_hangup; act.sa_flags = SA_RESTART | SA_NOMASK; // if receive killall -HUP signal if(sigaction(SIGHUP, &act, 0) != 0) perror("Can't capture SIGHUP"); // 이 부분은 데몬에 따라 달라짐 .... try { Server* server = new MailFilterServer (SMTPPORT, 2, mailserver); server->MainLoop(); delete server; return 1; } catch(Exception& exp) { cerr << exp.GetMsg() << endl; return 0; } }
2006/10/22 02:13 2006/10/22 02:13
이 글에는 트랙백을 보낼 수 없습니다

WIN32 API 이용하는 법. (WIN32 UniCode는 UTF-16LE), 단, Win9x/WinMe에서는 안됩니다.


A) ANSI 를 UniCode (UTF-8) 로 변환


1) ANSI -> UniCode (UTF-16LE)

   ::MultiByteToWideChar(CP_ACP, ...);

2) UniCode (UTF-16LE) -> UniCode (UTF-8)

   ::WideCharToMultiByte(CP_UTF8, ...);



B) UniCode (UTF-16LE) 를 ANSI 로 변환


1) ::WideCharToMultiByte(CP_ACP, ...);



C) UniCode (UTF-8) 를 ANSI 로 변환


1) UniCode(UTF-8) -> UniCode (UTF-16LE)

   ::MultiByteToWideChar(CP_UTF8, ...);

2) UniCode (UTF-16LE) -> ANSI

   ::WideCharToMultiByte(CP_ACP, ...);


UTF-8 관련 정보 사이트

http://www.unicode.org

http://www.cl.cam.ac.uk/~mgk25/unicode.html

UniCode 컨버터

http://www.unicode.org/Public/PROGRAMS/CVTUTF/

UTF-8 인코딩 디코딩 관련 소스

http://www.codeproject.com/file/textfiledocument.asp

http://www.codeproject.com/string/UTF8.asp

2006/10/22 02:11 2006/10/22 02:11
이 글에는 트랙백을 보낼 수 없습니다
웅쓰:웅자의 상상플러스
웅자의 상상플러스
전체 (379)
게임 (5)
영화 (2)
기타 (23)
맛집 (5)
영어 (2)
대수학 (3)
형태소 (5)
Hacking (9)
Linux (112)
HTML (48)
Application_developing (48)
Web_developing (102)
Window (11)
«   2024/04   »
  1 2 3 4 5 6
7 8 9 10 11 12 13
14 15 16 17 18 19 20
21 22 23 24 25 26 27
28 29 30        
  1. 2016/01 (1)
  2. 2015/12 (3)
  3. 2015/10 (3)
  4. 2015/03 (2)
  5. 2015/01 (4)