'90.개발관련문서'에 해당되는 글 73건

  1. 2009.04.21 개발 환경 셋팅및 관련 프로그램 설치등에 관련된 글이 위치하는 곳입니다.
  2. 2009.04.21 개발시 유용한 글이 위치하는 곳입니다.
  3. 2009.03.24 JSP 코드 작성 표준 양식 - 한글판
  4. 2009.03.24 웹 개발자 가이드-실전 웹 표준 가이드 (2005)
  5. 2009.03.23 Gateway timeout메세지 뜨는것
  6. 2009.03.23 HTTP 에러 코드 표 1
  7. 2009.03.20 휴대폰을 이용한 노트북 인터넷 연결하기..
  8. 2009.02.26 내가생각하는 페이징 처리
  9. 2008.12.17 엑셀 sumif 조건별 값 더하기
  10. 2008.11.24 웹싸이트 드래그 방지시...
  11. 2008.11.19 java.lang.OutOfMemoryError
  12. 2008.11.13 모든 디자이너들이 다운로드 받아야 할 30개의 필수 PDF Document
  13. 2008.11.12 SAML을 이용한 SSO Service의 구현
  14. 2008.11.11 엑셀 사업자번호 가운데 0 빠진거 입력
  15. 2008.11.06 컴퓨터도 풀수없는 암호 만들기
  16. 2008.11.04 stripes-crud
  17. 2008.10.23 오라클 EM 사용 백업 복구 문서 1
  18. 2008.10.21 myeclipse 초심자들을 위한 빠른 시작 번역문서 3개
  19. 2008.10.20 색상표
  20. 2008.09.01 인터넷빠르게하는 팁..
  21. 2008.08.19 보안서버구축 의무화...구축방법...
  22. 2008.08.17 새로 보는 프로그래밍 언어
  23. 2008.08.14 프로그래머가 되기 위한 방법
  24. 2008.08.12 각종레퍼런스
  25. 2008.08.12 오른쪽 마우스 메뉴에 도스창 열기 추가하기
  26. 2008.08.12 이더리얼 패킷분석
  27. 2008.08.12 Google Doctype
  28. 2008.08.12 java 초보개발자를 위한 학습 로드맵
  29. 2008.07.25 개발시 유용한 글 모음
  30. 2008.07.24 Mozilla Developer Center contents
90.개발관련문서2009. 4. 21. 14:21
반응형
개발 환경 셋팅및 관련 프로그램 설치등에 관련된 글이 위치하는 곳입니다.

개발환경#

SVN, CVS#

  • Subversion
    1. Eclipse subversion - MS Window에서 http인증으로 subversion(1.1.1버전)설치후 eclipse와 연동하기
    2. the Subversion plugin for Eclipse - eclipse의 subversion용 플러그인인 Subclipse 사용하기(영문링크)
  • CVS
    1. Eclipse CVS - MS Window에서 CVS(2.0.58d)설치후 eclipse와 연동하기

Apache, Tomcat#

기타#

빌드툴#

WAS#

개발툴(IDE)#

Posted by 1010
90.개발관련문서2009. 4. 21. 14:20
반응형
기타 개발시 유용한 글이 위치하는 곳입니다.

XML관련#

  • JConfig - XML로 property를 간단하게 셋팅및 가져올수 있는 기능을 제공한다.
  • XMLBeans - XMLBeans를 2004년 12월 모임에서 비실비실님이 강의하신 내용.
  • Introduce XStream - XStream 소개하기

자바이론#

성능 테스트#

정규표현식#

JavaScript#

Eclipse#

테스팅#

보안#

Cygwin#

바인딩#

  • JiBX - 오픈소스 Java XML바인딩

Ant#

Jakarta Commons Configuration#

Java 예제코드#

Quartz#

PHP#

WEB APPs#

was관련#

스크립트 관련#

Posted by 1010
90.개발관련문서2009. 3. 24. 11:11
90.개발관련문서2009. 3. 24. 10:59
반응형

웹 개발자 가이드

실전 웹 표준 가이드 (2005)

본 가이드는 XHTML, CSS, DOM, ECMAScript 등 모든 웹 표준에 대한 이슈를 새롭게 정리하고, 웹 개발 프로젝트에서 표준을 준수하는 방법을 제공하는 가이드 입니다.

목 차

다운로드 PDF 파일 (223p), PDF 파일(부록 포함, 305pp)
  • 웹 표준이란 무엇인가?
  • 실전 XHTML 가이드
    • XTHML 소개
    • XHTML 일반 문법 준수
    • 구조적 XHTML 사용 방법
  • 실전 CSS 레이아웃
    • CSS 개념 및 소개
    • CSS 레이아웃(LAYOUT) 기초
    • 실전 예제를 통한 CSS 레이아웃
    • 고급 CSS 레이 아웃
  • 실전 DOM/Script 가이드
    • W3C DOM vs. MS DOM
    • 표준 JAVASCRIPT 사용 방법
    • 올바른 플러그인(PLUGIN) 사용
  • 실전 표준 웹 프로그래밍
    • 표준 MIME 타입 설정
    • 표준 문자 인코딩 지정
  • 실전 웹 표준 개발 프로세스
    • 현재 프로세스 소개(Waterfall 방식)
    • 개선된 모델(퍼블리셔 중심)
    • 새로운 개발 프로세스
  • 맺음말
  • 부록 : 웹 표준 사양-브라우저 호환차트
- URL: http://www.mozilla.or.kr/docs/web-developer/web-standard-guide-2005.pdf

웹 표준 기반 홈페이지 구축 가이드 (2004)

본 가이드는 브라우저 호환성 유지 방법은 물론 XHTML/CSS 레이아웃을 통한 홈페이지 개발 방법을 소개한 가이드 입니다.

목 차

  • 1부 브라우저 호환성 유지 방안
    • 현대 웹사이트 제작의 방향
    • W3C 표준안
    • 정확한 문서 형식 사용
    • 올바른 HTML 및 CSS 사용 방법
    • 올바른 객체 모델 및 자바스크립트 사용 방법
    • 웹페이지 디버깅 도구 사용
  • 2부 웹표준 기반 페이지 제작 방법
    • 구조와 표현의 분리
    • XHTML를 사용해야 하는 이유
    • XHTML의 주요 특징
    • CSS 사용 방법
    • CSS 레이아웃 vs.Table 레이아웃
- 인쇄 버전 : PDF 파일

Cross Browsing 가이드(2003)

본 가이드는 각 웹 브라우저의 비 표준 기능과 이를 극복할 수 있는 방법을 위주로 작성된 것입니다.

목 차

- 인쇄 버전 : PDF 파일
Posted by 1010
90.개발관련문서2009. 3. 23. 11:05
반응형
많은 분들이 접해보았을 오류다..말그대로 게이트웨이 까지 가는데 정해진 시간내에 접속시도 했으나 상대측에서 응답이 없을 경우 발생한다. 흔히 504 error이라고 한다.

인터넷에 떠도는 응답과 관련자료를 다 올려볼려고 한다. 카카카...

용어들은 네트워크를 공부할 수 있는 자료를 참고하는것이 좋을 듯 싶다.

다음은 참고할 수 있는 에러 코드들이다. 이 문제를 해결하는데 일조하는 코드이니 참고바람.


[13] HTTP 1.1 status codes

100 : Continue
101 : Switching protocols
200 : OK, 에러없이 전송 성공
201 : Created, POST 명령 실행 및 성공
202 : Accepted, 서버가 클라이언트 명령을 받음
203 : Non-authoritative information, 서버가 클라이언트 요구 중 일부 만 전송
204 : No content, 클라언트 요구을 처리했으나 전송할 데이터가 없음
205 : Reset content
206 : Partial content
300 : Multiple choices, 최근에 옮겨진 데이터를 요청
301 : Moved permanently, 요구한 데이터를 변경된 임시 URL에서 찾았음
302 : Moved temporarily, 요구한 데이터가 변경된 URL에 있음을 명시
303 : See other, 요구한 데이터를 변경하지 않았기 때문에 문제가 있음
304 : Not modified
305 : Use proxy
400 : Bad request, 클라이언트의 잘못된 요청으로 처리할 수 없음
401 : Unauthorized, 클라이언트의 인증 실패
402 : Payment required, 예약됨
403 : Forbidden, 접근이 거부된 문서를 요청함
404 : Not found, 문서를 찾을 수 없음
405 : Method not allowed, 리소스를 허용안함
406 : Not acceptable, 허용할 수 없음
407 : Proxy authentication required, 프록시 인증 필요
408 : Request timeout, 요청시간이 지남
409 : Conflict
410 : Gone, 영구적으로 사용할 수 없음
411 : Length required
412 : Precondition failed, 전체조건 실패
413 : Request entity too large,
414 : Request-URI too long, URL이 너무 김
415 : Unsupported media type
500 : Internal server error, 내부서버 오류(잘못된 스크립트 실행시)
501 : Not implemented, 클라이언트에서 서버가 수행할 수 없는 행동을 요구함
502 : Bad gateway, 서버의 과부하 상태
503 : Service unavailable, 외부 서비스가 죽었거나 현재 멈춤 상태
504 : Gateway timeout
505 : HTTP version not supported


가능성은 2가지 ...상대측 컴퓨터 (서버가 되겠지용? 내가 접속을 시도 하는 클라이언트 개념이니까)에서 포트를 막아놓아서 접속불능이거나..아님 없어져부렀거나..

C프로그램을 만질 줄 알면 이 메시지가 뜰때 다른 정확한 원인을 표시하도록 하는 프로그램을 짤 수 있다. 예전에 어디서 우연히 찾았는데 기억이 나지 않는다. 상대방에게는 이런 메시지로 보이지만 진짜 메세지는 뒤에 숨겨져 있다고 들었는데....


접해보면 알겠지만..특정 사이트에서 이런오류가 발생하면 하루나 며칠동안 나 혼자만 그 사이트 접속이 안될때가 있다.

나같은 경우 네이버 지식검색 결과 창으로의 진입이 안되서 --정확히 말하면 창을 클릭하면 완료라고 메세지가 뜨는데 화면에는 아무것도 안 나온 적이 있었다. 다른 친구들은 다 접속이 되는데 나만 안되는경우 -- 결국 3일 동안 고치려고 발버둥 치다가 포기하고 깔끔하게 밀어버린적이 있다.


이럴경우 네트워크의 문제를 제시하는 분들이 있는데 네트워크의 문제해결을 위해서는 먼저 Ping테스트를 해보길 권한다. 자체 네트워크 카드의 Ping테스트 결과 . 그리고 외부로의 Ping테스트를 통해.....근데 이경우 네트워크의 문제 가능성은 거의 드물다.


일반적으로 상대방 서버측문제라고 본다. 응답이 없거나...서버를 일시적으로 막아 놓았거나.

어디서 찾은지도 모르겠다..넘 많이 뒤지고 돌아다녀서...

아파치(잘 알고 계시져? ^^;) 제공문서(설정파일 포함)에 의하면, 위에서처럼 Timeout에 관한 설명이 나오는데 ..
Timeout 300

-The number of seconds before receives and sends time out.
-The TimeOut directive currently defines the amount of time Apache will wait for three things:
   1.The total amount of time it takes to receive a GET request.
   2.The amount of time between receipt of TCP packets on a POST or PUT request.
   3.The amount of time between ACKs on transmissions of TCP packets in responses.

- time out을 받고 보내기 전의 초(짧은 시간)의수

- Timeout 지시는 현재 아파치가 밑의 3가지를 기다리게 될 상당한 시간으로 정의한다.

  1. (아파치가 클라이언트로 부터) GET 요청(GET 방식의 URL요청)을 받는데 걸리는 시간. - (요청)
  2. (아파치가 클라이언트로 부터) POST나 PUT 방식의 요청에 대한 TCP 패킷을 받는데 걸리는 시간. - (요청)
  3. (아파치가 클라이언트에게) TCP 패킷을 전송할때 ACKs 세그먼트를 보내는데 걸리는 시간 - (응답)

위의 내용을 쉽게 이해할것 같지만,
TCP/IP 네트워크에서 "TCP 3 way handshaking"라는 TCP의 제어기능을 이해해야만
Timeout의 개념을 알 수 있습니다.

질문의 내용은 3번의 응답에 관한 내용인데, 파일을 다운로드할때는 3번의 응답 과정이 모두 끝나고 실제로 Data 패킷을 전송하는 단계이므로 Timeout과 관계가 없습니다.
그렇게 때문에 아주 덩치 큰 파일(100M 이상)을 HTTP 프로토콜을 이용해서 다운로드할때
5분 이상이 걸려도 Time out이 되지 않는 이유가 여기에 있습니다.

질문에 대한 결론은
Timeout 지시자는 실제로 파일을 다운로드하는 Data 패킷 전송과 관계가 없으며,
앞에서 설명한 1,2번의 요청에 걸리는 시간과 3번의 응답에 걸리는 시간과 관계가 있습니다.

1, 2번의 요청에 관한 내용은 따로 설명하지 않아도 이해할 수 있는 부분이므로 생략하고,
좀더 개념적으로 확실히(?) 알기 위해서 "TCP 3 way handshaking"이라는 녀석에 대해서
조금 알아보죠..

예를들어,
TCP/IP 네트워크에서는 A호스트에서 B호스트로 접속하여 Data 패킷을 전송할때
단 한번의 접속으로 Data 패킷을 보내는 것이 아니라 모두 3번에 걸쳐서 이루어집니다.

1단계 : A ----- 접속시도(SYN, SYNchronize sequene number 보냄) -----> B
2단계 : A <---- 확인단계(SYN, ACK(ACKnowledgment 보냄) ----------- B
3단계 : A ----- 전송시작(ACK, Data 전송시작) --------------------> B

이와 같이 TCP 네트워크는 패킷을 순서대로 맞게 전달하기 위해서
이런 제어기능을 하게됩니다.

3번에 걸쳐서 마치 악수하듯이 이루어진다해서 "3 way handshaking"이라는 말이
나온것 같군요.

헷갈리지 않아야할 점은 앞에서 Data 전송이라고해서 요청에 대한 응답에 만 해당되는것이
요청도 이와 같이 3단계를 걸쳐서 이루어 집니다.

좀더 정확하고 많은 정보를 원한다면 TCP/IP 네트워크에 대한 전문서적을 읽어보시길
바랍니다(필자는 이정도 수준이라서...^.9).

질문의 내용과 연결해서,
1, 2번의 요청, 즉 "xxx 받는데 걸리는 시간"은 위의 3단계 모두에 해당되는 시간이고,
3번의 응답, 즉 "xxx ACKs 세그먼트를 보내는데 걸리는 시간"은 2단계에 해당되는
시간을 의미합니다.

따라서
이미 질문에 대한 답이 나와 있듯이 "파일을 다운로드하는 경우"는 이미 2단계가 모두
끝나고 3단계를 의미하므로 아파치의 Timeout 과 관련이 없습니다.

Timeout 300

의 의미는

- URL GET 요청이나 POST, PUT 요청을 할때 네트워크 환경이 지나치게 너무
  느린 환경(아주 멀리 떨어져 있는 아주 느린 환경)에서 접속을 할때 걸리는 시간이
  300초가 넘어가면 Timeout이 됩니다.
- 또한 다운로드가 아닌 ACKs 세그먼트 메시지를 보낼때도 마찬가지로 너무 느린 환경이나
  네트워크 장애로 인해서 시간이 300초를 초과할 경우에 Timeout이 됩니다.

간혹 멀리 떨어진 외국의 싸이트를 접속하다보면 갑작스런 네트워크 장애로
Timeout 이라는 Error 메시지를 본 경험이 있는데 이와 같은 이유로 Timeout이
되기도 합니다.

이와 관련된 HTTP 1.1 status codes(RFC 2068)

"408"   ; Request Time-out
"413"   ; Request Entity Too Large
"414"   ; Request-URI Too Large
"502"   ; Bad Gateway
"504"   ; Gateway Time-out

408 Request Timeout

   The client did not produce a request within the time that the server
   was prepared to wait. The client MAY repeat the request without
   modifications at any later time.

413 Request Entity Too Large

   The server is refusing to process a request because the request
   entity is larger than the server is willing or able to process. The
   server may close the connection to prevent the client from continuing
   the request.

   If the condition is temporary, the server SHOULD include a Retry-
   After header field to indicate that it is temporary and after what
   time the client may try again.

414 Request-URI Too Long

   The server is refusing to service the request because the Request-URI
   is longer than the server is willing to interpret. This rare
   condition is only likely to occur when a client has improperly
   converted a POST request to a GET request with long query
   information, when the client has descended into a URL "black hole" of
   redirection (e.g., a redirected URL prefix that points to a suffix of
   itself), or when the server is under attack by a client attempting to
   exploit security holes present in some servers using fixed-length
   buffers for reading or manipulating the Request-URI.

502 Bad Gateway

   The server, while acting as a gateway or proxy, received an invalid
   response from the upstream server it accessed in attempting to
   fulfill the request.

504 Gateway Timeout

   The server, while acting as a gateway or proxy, did not receive a
   timely response from the upstream server it accessed in attempting to
   complete the request.

많은 도움이 되었기를.....

더 많은 문서나 자료는 RFC문서를 참조하였음 ...좋겠는데......

Posted by 1010
90.개발관련문서2009. 3. 23. 11:01
반응형

 HTTP 에러 코드 표


 HTTP 에러코드          에러 메시지


200                          OK, 에러 없이 전송이 성공함

202                          Accepted, 서버가 클라이언트의 명령을 받음

203                          Non-authoritative information, 서버가 클라이언트 요구 중 일부만 전송함

204                          Non Content, 클라이언트 요구를 처리했으나 전송할 데이터가 없음


300                          Mutiple Choices, 최근에 옮겨진 데이터를 요청함

301                          Moved Permanently, 요구한 데이터를 변경된 임시 URL에서 찾음

302                          Moved Permanently, 요구한 데이터가 변경된 URL에 있음을 명시함.

303                          See other, 요구한 데이터를 변경하지 않았기 때문에 문제가 있음


400                          Bad Request, 요청실패 - 문법상 오류가 있어서 서버가 요청 사항을 이해하지 못함

401.1                       Unauthorized, 권한없음 - 접속실패, 서버에 로그온 하려는 요청 사항이 서버에 들어있는 권한과 비교했을 시 맞지

                                                                  않을 경우 발생.

401.2                       Unauthorized, 권한없음 - 서버 설정으로 인한 접속 실패, 일반저긍로 적절한 www-authenticate head field를 전송

                                                                  하지 않아서 발생함

402.3                       Unauthorized, 권한없음 - 자원에 대한 ACL에 기인한 권한 없음. 이 에러는 클라이언트가 특정 자원에 접근할 수 없을

                                                                   때 발생.

401.4                       Unauthorized, 권한없음 - 필터에 의한 권한 부여 실패. 웹 서버가 서버에 접속하는 사용자들을 확인하기 위해 설치된

                                                                  필터 프로그램이 있음을 의미함.

403.1                       Forbidden, 금지 - 수행 접근 금지. 수행시키지 못하도록 되어 있는 디렉토리 내의 실행 파일을 수행시켜려고 했을 때

                                                        발생

403.2                       Forbidden, 금지 -  읽기 접근 금지. 브라우저가 접근한 디렉터리에 가용한 디폴트 페이지가 없을 경우 발생

403.3                       Forbidden, 금지 - SSL필요. 접근하려는 페이지가 SSL로 보안 유지되고 있는 것일 때 발생

403.6                       Forbidden, 금지 - IP주소 거부됨. 서버가 사이트에 접근이 허용되지 않은 IP주소로 사용자가 접근하려 했을 때 발생

403.7                       Forbidden, 금지 - 클라이언트 확인 필요. 자원을 이용할 수 있는 사용자임을 입증하는데 사용(SSL)

403.8                       Forbidden, 금지 - 사이트 접근 거부. 웹 서버가 요청사항을 수행하고 있지 않거나, 해당 사이트에 접근하는 것을 허락

                                                        하지 않았을 경우 발생

403.9                       Forbidden, 금지 - 연결된 사용자수 과다

403.10                      Forbidden, 금지 - 설정이 확실하지 않음. 웹 서버의 성정 부분에 문제가 있을 경우 발생

403.11                      Forbidden, 금지 - 패스워드 변경. 사용자 인증 단계에서 잘못된 패스워드를 입력했을 경우 발생

404                         Not found, 문서를 찾을 수 없음 - 클라이언트가 요청한 문서를 찾지 못한 경우 발생. URL주소를 확인

405                         Method not allowed, 메소드 허용 안 됨 - 해당 Method의 이용이 허용되지 않았을 경우 발생

406                         Not Acceptable, 받아들일 수 없음

407                         Proxy Authentication Required, Proxy  인증이 필요함

408                         Request timeout, 요청 시간이 지남

414                         Request-URL too long, 요청한 URL가 너무 김


500                         Internal Server Error, 서버 내부 오류 - 웹 서버가 요청사항을 수행할 수 없을 경우에 발생

501                         Not Implemented, 적용 안 됨 - 웹 서버가 요청사항을 수행하는데 필요한 기능을 지우너하지 않는 경우에 발생

502                         Bad Gateway, 게이트웨이 상태 나쁨 - 게이트웨이 상태가 나쁘거나 서버의 과부하 상태일 때 발생

503                         Service Unavailable, 서비스 불가능 - 서비스가 현재 멈춘 상태 또는 현재 일시적인 과부하 또는 관리상황일 때

Posted by 1010
90.개발관련문서2009. 3. 20. 11:03
반응형
Posted by 1010
90.개발관련문서2009. 2. 26. 12:14
반응형

=========정의예를들면=========
  총개수 : 21개    표시할 글의수 : 10개    페이지수 : 10개(한 화면에 표시할 페이지 번호수)
  전체글 : List list에 담는다
  =============================

  ====전체글에서 10개의 페이지를 가지고 오는 쿼리====
  SELECT * FROM (SELECT * from BOARD_JSP07_PIPE0502 order by no desc) WHERE ROWNUM<=10;

  바꿔서 하자면
  SELECT * FROM (SELECT * FROM BOARD_JSP07_PIPE0502 ORDER BY NO DESC) WHERE ROWNUM BETWEEN START AND END
  변경이 가능하다

  ====시작글 구하는 공식====
  시작글 =  (현재페이지-1) * 표시할글의수
  start = (currentpage-1) * numPerPage
  예)
  현재페이지 : 5페이지
  (5-1)*10 = 40
  1 페이지 : 1  10
  2 페이지 : 11 20
  3 페이지 : 21 30
  4 페이지 : 31 40
  5 페이지 : 41 50
  ========================

  ==== 끝글 구하는 공식====
  끝글 = 현재페이지 * 표시할글의수
  end = currentpage * numPerPage
  예)
  현재페이지 : 5페이지
  5*10 = 50(마지막 글일수도 있고 아닐수도 있다)
  ========================

  list.size()%5!=5이면 몫과 나머지를 더한것이 페이지수 totalpage
  list.size()%5==5이면 몫이 페이지수 totalpage

  총 134개
  134 / 10 = 13 ..... 4  ===>   총 14페이지가 결과물
  totalPage = listCount / 10 + listCount % 10
  1블럭당 10페이지를 보여준다
  여기서 1블럭은 화면상에 1|2|3|4|5|6|7|8|9|10을 말한다
  결과물 / 10 = 1 ..... 4 ===>  총 2블럭의 결과물이 나온다
  totalBlock = totalPage / numPerBlock + totalPage % numPerBlock

  리스트의 총 개수 / 10(표시할글의수)

Posted by 1010
90.개발관련문서2008. 12. 17. 18:25
반응형

총입금액은 SUM함수를 각 입금자의 합은 SUMIF함수를 사용하면 되겠습니다.


입금자 A의 합계는 위의 그림과 같이 입력되어 있을 때

=sumif(입금자데이터를절대주소로, 원하는입금자주소, 더할값의주소) 로 구할 수 있습니다.


원할하게 복사해서 입력할 수 있게하기 위하여 절대주소를 잘 활용하세요,.


파일을 첨부하였으니 참고하세요.

Posted by 1010
90.개발관련문서2008. 11. 24. 12:28
반응형

Shift + 클릭  등으로 주소를 알아낸뒤.. 

 

view-source:http://싸이트주소                            또는

http://hackersnews.org/view_source/index.html

 

에서 소스를 추출


마우스드래그 방지 소스 : onDragstaret="return false"
아우스오른쪽 버튼 사용금지 소스 : onContexrtmenu="return false"
영역의 선택 금지 소스 : onSelectstart="return false"

 

주로 바디태그에 있는 걸 보았소. 아뭏든 소스 제거하고 htm 파일로 저장

 

 

더 좋은 방법  알툴바가 있어야함

알툴바메뉴 -> 환경설정 -> 일반메뉴에서 "마우스 오른쪽버튼 제한 해제하기"를 체크

기능이 정상작동하지 않으면 해상 싸이트에서 다시 체크해제 체크를 반복해본다.

Posted by 1010
90.개발관련문서2008. 11. 19. 14:09
반응형

java.lang.OutOfMemoryError

 

                     2005-05-31     학렬    ㈜아이티플러스 기술지원부

 

 

이 문서는 기술지원 또는 개발 시 java.lang.OutOfMemoryError 를 만났을 때 원인 파악 및 해결방안 입니다.

 

 

java.lang.OutOfMemoryError 에는 크게 2가지 패턴이 있다고 볼 수 있습니다.(전적으로 개인적인 생각이지만..^^)

Java heap 메모리가 정말로 Full 되서 나는 종류가 있고 그렇지 않은데도 나는 종류가 있습니다.

그 둘 중에 어는 것 인지부터 가려내는 것이 가장 먼저 선행되어야 합니다.

Java 가상머신에는 메모리 구조가 여러단계로 나뉘어 져 있으므로 어느 영역에 의해 java.lang.OutOfMemoryError 가 나는지 알기 위해 JAVA OPTION으로 싸이트가 안정화 되기 전까진 verbosegc 또는 -XX:+PrintGCDetails  옵션을 추가해 놓는 것이 java.lang.OutOfMemoryError 났을 때를 대비해 좋은 습관이라 할 수 있습니다.

 

-verbosegc 또는  -XX:+PrintGCDetails  옵션으로 로그에 남는 heap 메모리 정보를 본 후 java.lang.OutOfMemoryError 가 날 때 heap이 꽉 차서 나는 것인지 아닌지 부터 판단합니다.

 

1.     Heap Memory Full 차지 않았을 때

1)     Perm 영역이 full 되는 경우

 

Permanent generation is full...

increase MaxPermSize (current capacity is set to: 134217728 bytes)

 

[Full GC[Unloading class sun.reflect.GeneratedSerializationConstructorAccessor31282]

 810207K->802132K(1013632K), 8.3480617 secs]

<GC: 2 4  2465625.831280 10348 0 31 113802808 105534632 286326784 0 0 35782656 715849728 715848840 715849728 134217720 134023296 134217728 8.348677 8.348677 >

Passwd Check =============

<2005. 5. 19. 오전 9 32 23 KST> <Error> <HTTP> <BEA-101017> <[ServletContext(id=2536415,name=/,context-path=)] Root cause of ServletException.

java.lang.OutOfMemoryError

위와 같은 case 인 경우네는 Java 가상머신중에 Perm 영역이 full 차는 경우 입니다. 

Perm 영역에는 class object 및 관련된 meta data가 로드되는 곳인데 싸이트가 매우 많은 수의 class를 사용하는 경우 늘려줘야 하는 case도 있습니다.

얼마가 적정한 사이즈란 정답이 없는 것 같고 max가 계속 늘어나지 않고 일정한 사이즈를 유지 하면 됩니다.

 

             위 에러가 났을때는 -XX:MaxPermSize=256m  옵션으로 사이즈를 적당하게 늘려주는게 방

             법이며 늘려주었는데도 불구하고 Full 차는 시간만 늘어날 뿐 계속 사이즈가 늘어난다면 이 영역은 일반 비즈니스 프로그램으로 핸들링 할 수 없는 영역이므로 WAS 제품의 버그 및 jdk 버그로 보는 것이 일반적입니다.

       

2)     MAXDSIZ 사이즈 관련

  - Exception in thread "CompileThread0" java.lang.OutOfMemoryError: requested 32756 bytes for ChunkPool::allocate

Possible causes:

         - not enough swap space left, or

         - kernel parameter MAXDSIZ is very small.

-          java.lang.OutOfMemoryError: unable to create new native thread

 

위 두 에러가 나는 이유는 Data 사이즈가 Full 차서 더 이상 메모리 할당을 할 수 없다는 java.lang.OutOfMemoryError 입니다.

 

Jdk도 내부적으로 c 라이브러리를 쓰고 jni를 통해서 c프로그램을 호출할 수 도 있는 환경에서 Data 영역이상으로 메모리 사용 시 위 에러를 만날 수 있습니다.

Heap 영역에는 java heap C heap이 있는데 C heap Data 메모리 영역에 영향을 미치는 것으로 보이며 보통 C의 전역 변수들이 잡히는 영역입니다.

 

위 현상을 만났을 때는 hp os MAXDSIZ가 너무 작게 잡혀있지 않은지 확인 후 적당한 크기로 늘려 줘야 합니다.

 

Glance 에서 shift+m 을 누른 후 jvm pid를 누르면 java가 사용하는 Data 사이즈를 모니터링 할 수 있습니다.

모니터링을 통해 적정한 사이즈로 MAXDSIZ를 늘려주어야 하며 만일 늘려 준게 에러 발생의 시간만 지연 시킬 뿐 계속 사용량이 늘어난다면 이는 사용하는 c라이브러리 쪽에 메모리 릭 버그가 있는 것이므로 c라이브러리를 체크 하셔야 합니다.

java.lang.OutOfMemoryError: unable to create new native thread

이 경우는 프로그램에서 Thread pool 프로그램 실수로 필요이상으로 쓰레드가 생성되는 경우도 과도하게 메모리를 사용할 수 있으므로 jvm 쓰레드 덤프를 통해 과도한 쓰레드가 생성되지 않았는지도 확인해 보셔야 합니다.

 

2.     Heap Full 찾을 때

이 경우는 java가 사용하는 heap 영역이 Full 되서 java.lang.OutOfMemoryError 가 나는 경우 인데 두 가지 패턴이 있습니다.

순간적으로 대량의 데이터를 메모리에 올리는 프로그램이 실행되어 문제를 야기 할수 있으며 다른 한 가지는 조금씩 메모리 릭이 발생하여 점차적으로 메모리가 쌓여 가는 경우 입니다.

두 가지 중에  어느 것인지 구별하는 것이 중요하며 이를 위해서도 마찬가지로 -verbosegc 또는  -XX:+PrintGCDetails  로그를 통해 순간적으로 메모리가 차는 것인지 조금씩 메모리가 차는 것인지를 확인하셔야 합니다.

 

1) 특정 프로그램의 특정시점의 과도한 메모리 사용에 의한 경우

                특정 프로그램이 과도하게 heap 메모리 이상 메모리를 사용하면서

java.lang.OutOfMemoryError가 발생하는 경우는 이 에러가 발생하는 시점의 쓰레드 덤프를 통해 어느 프로그램인지 쉽게 찾을 수 있습니다.

쓰레드 덤프에 메모리를 쓸만한 프로그램은 어느 정도의 자바프로그램 경험이 있으면 찾을 수 있습니다.

 

Ex)

"ExecuteThread: '36' for queue: 'default'" daemon prio=10 tid=0x0048e7b0 nid=48 lwp_id=4139729 runnable [0x23f32000..0x23f30500]

          at java.net.SocketInputStream.socketRead(Native Method)

          at java.net.SocketInputStream.read(Unknown Source)

          at oracle.net.ns.Packet.receive(Unknown Source)

          at oracle.net.ns.NetInputStream.getNextPacket(Unknown Source)

          at oracle.net.ns.NetInputStream.read(Unknown Source)

          at oracle.net.ns.NetInputStream.read(Unknown Source)

          at oracle.net.ns.NetInputStream.read(Unknown Source)

          at oracle.jdbc.ttc7.MAREngine.unmarshalUB1(MAREngine.java:718)

          at oracle.jdbc.ttc7.MAREngine.unmarshalSB1(MAREngine.java:690)

          at oracle.jdbc.ttc7.Oall7.receive(Oall7.java:373)

          at oracle.jdbc.ttc7.TTC7Protocol.doOall7(TTC7Protocol.java:1405)

          at oracle.jdbc.ttc7.TTC7Protocol.fetch(TTC7Protocol.java:889)

          - locked <0x35e8e3b0> (a oracle.jdbc.ttc7.TTC7Protocol)

          at oracle.jdbc.driver.OracleResultSetImpl.next(OracleResultSetImpl.java:242)

          - locked <0x36f66c98> (a oracle.jdbc.driver.OracleResultSetImpl)

          at weblogic.jdbc.pool.ResultSet.next(ResultSet.java:180)

          at Dcard.AAA.ejb.monitor.member.wbbb.WACBean.getInfoList(WACBean.java:5789)

 

java.lang.OutOfMemoryError 가 날 상황에 쓰레드 덤프를 두 세번 떠서 같은 쓰레드 번호로 위 같은 로직이 오래 결려있다면 이는 프로그램에서 while(rs.next()) 를 오랜 기간 돌면서 메모리에 DB로부터 읽어서 올리고 있다는 것을 의미 합니다 대량 데이터 조회가 일어나는 경우인 것 입니다.

 

이런 식으로 특정 프로그램의 특정 시점에 과도한 메모리 사용으로 인한 java.lang.OutOfMemoryError 에러는 쓰레드 덤프를 통해 찾을 수 있습니다.

-- 물론 2번에서 설명 할 heap dump를 통해서도 찾을 수 있습니다.

 

2) 메모리 릭에 의해 조금씩 메모리가 쌓인 경우

JVM위 에서 실행중인 프로그램중에 어떤 것이 GC 대상에서 제외되는 영역에 data를 조금씩 쌓고 있다는 얘기입니다.

GC 대상에서 제외되는 영역은 다음과 같은 곳이 있습니다.

- Http Session에 넣는 데이터..(세션 타임아웃 또는 invilidate 씨 까지 제외됨)

- Static 변수

- 서블릿 또는 jsp의 멤버변수 ( WAS 기동 후 최초 호출 시 인스턴스 화 되어 WAS가 내려 갈 때 까지 사라지지 않음 )

- EJB의 멤버변수( pool안에서 객체가 존재하는 한 GC대상에서 제외)

          위 같은 영역에 프로그램에서 data add 하는 구조를 메모리 릭 이라고 할 수 있습니다.

           IBM 또는 SUN JDK 인 경우에는 heapdump를 통해서 분석하여 어느 데이터가 메모리를 많이 잡고 있는지를 알 수 있습니다.

           Heapdump 사용법 및 분석 법은 다음을 참조 하시면 됩니다.

 http://www.alphaworks.ibm.com/aw.nsf/FAQs/heaproots

http://www-1.ibm.com/support/docview.wss?uid=swg21190476

http://www-1.ibm.com/support/docview.wss?rs=180&context=SSEQTP&q1=heapdump+solaris&uid=swg21190608&loc=en_US&cs=utf-8&lang=en

http://www.skywayradio.com/tech/WAS51/IBMHeapDump/

Posted by 1010
90.개발관련문서2008. 11. 13. 10:02
반응형

디자인은 최종 사용자와 가장 맞닿아 있는 프로덕트를 완성시키는 요소입니다.

디자인은 사용자에게 이 웹페이지에서 사용자에게 가장 중요하게 말하고 싶은 것이 무엇인가? 어떻게 하면 사용자가 더욱 편하게 이용할 수 있을까? 를 말하지 않아도 설명해 주고 프로덕트의 가치를 올려 줍니다.

웹디자인은 웹디자이너 뿐만이 아니라 개발자, 기획자, 심지어는 CEO 조차도 알아야 하는 요소 이며, 제공하자고자 하는 서비스/프로덕트에 대한 공통의 합의를 가져 갈 수 있게 하며, 성공의 필수 요소 입니다.

아래의 링크에는 다운로드 받을 수 있는 30개 이상의 필수 PDF 문서에 대한 것입니다.

아래 링크의 원본 포스트 : http://www.positivespaceblog.com/archives/pdf-documents-designer/


RESOURCES


YAHOO! Design Pattern Library

The WordPress Help Sheet

The Advanced WordPress Help Sheet

AIGA | Aquent Salary Survey

AIGA Why Design? Booklet

Type Classification eBook - Jacob Cass

WEB DEVELOPMENT & PROGRAMMING

Getting Real - 37signals (Read Online for Free)

DotMobi Mobile Web Developer’s Guide

Accessibility Checklist - Aaron Cannon

CSS Selectors Cheat Sheet - Cameron Moll

CSS Support in Email Clients

Dive Into Accessibility

PRESENTATIONS

Design is in the Details - Naz Hamid

Grids are Good - Khoi Vinh & Mark Boulton

Destroy the “Web 2.0 Look” - Elliot Jay Stocks

Print is the New Web - Elliot Jay Stocks

Creating Content for the Web - Naz Hamid

Elegant Web Typography - Jeff Croft

Entrepreneurs on Rails - Dan Benjamin

Bulletproof Web Design - Dan Cederholm

Interface Design Juggling - Dan Cederholm

FREELANCING & INCOME

Web Designer’s Success Guide

Passive Income Guide - Freelance Switch

Time Management for Creative People - Mark McGuinness

A Primer in Social Media - SmashLAB

Contingency Design - 37signals

Introduction to Good Usability - Peter Pixel

How to Be Creative - Hugh MacLeod

PDF COLLECTIONS

Creative Latitude Business Resources

Computer Arts Tutorials

AIGA Design & Business Forms

Posted by 1010
90.개발관련문서2008. 11. 12. 17:32
반응형

기고해주신 분

페이게이트 이동산 이사 (자바기반 지불 솔루션 업체)
mountie@paygate.net


1. 개요

SAML은 웹 브라우저에서의 SSO문제를 해결하기 위해서 OASIS의 연구결과로 탄생하였다.
인트라넷 레벨에서의 SSO는 다양한 방식들이 이용되어왔고 구현에 있어서도 크게 문제될요소는 적다.
예를 들어 Cookie 기반의 SSO, LDAP기반의 SSO, 인증서 기반의 SSO등이 있다.

그러나 도메인간의 SSO 구현을 위한 방식은 통제할 수 없는 외부 환경을 포함하므로 통일된 하나의 표준방식이 필요하게 되었고
SAML은 이러한 도메인간의 SSO구현을 가능하게하는 XML 표준이다.

사용되는 용어에 대해서 알아보자.
SAML : Security Asserting Markup Language, http://en.wikipedia.org/wiki/SAML 참조
SSO : Single Sign On 하나의 일관된 인증방식으로 여러 서비스에 로그온할 수 있는 방법
ACS : Assertion Consumer Service, Identity Provider에 의하여 인증된 사용자에 대한 정보가 담긴 SAML response정보를 verify 하고 서비스를 제공할 수 있도록 포워딩 한다

SAML에는 3가지 중요한 구성원이 존재한다.
  • Service Provider : 서비스를 제공하는 주체
  • 유저 : 서비스를 이용하는 사용자
  • Identify Provider : 유저에 대한 인증을 담당하는 주체

SAML의 특징은 Cross domain상황에서 표준화된 방식으로 SSO를 구현할 수 있으면서
Platform에 관계없이 다양한 환경에서 표준적인 방법으로 SSO 구현이 가능하다는 것이다.
실제 다양한 iPhone등 다양한 플랫폼에서 테스트해본 결과 잘 동작한다.


2. SAML Basic Steps

아래 그림은 Wikipedia에서 제시한 흐름도이다.



Google에서도 아래 그림을 제시하고 있다.



각 단계별 과정을 설명하면 아래와 같다.
  • 1단계 : 유저는 서비스 제공자(Service Provider)에게 접근한다. (서비스 이용을 위하여)
  • 2단계 : 서비스 제공자는 SAMLRequest를 생성한다. 생성된 SAMLRequest은 XML format의 텍스트로 구성된다.
  • 3단계 : 유저의 브라우저를 이용하여 인증 제공자(Identity Provider)로 Redirect 한다.
  • 4단계 : 인증 제공자(Identity Provider)는 SAMLRequest를 파싱하고 유저인증을 진행한다.
  • 5단계 : 인증제공자(Identity Provider)는 SAML Response를 생성한다.
  • 6단계 : 인증제공자(Identity Provider)는 유저 브라우저를 이용하여 SAMLResponse data를 ACS로 전달한다.
  • 7단계 : ACS는 Service Provider가 운영하게 되는데 SAMLResponse를 확인하고 실제 서비스 사이트로 유저를 Forwarding한다.
  • 8단계 : 유저는 로그인에 성공하고 서비스를 제공받는다.

위 단계중 SAMLResponse가 중요한 역할을 하는데 Identity Provider로서 SAMLResponse에 대하여 전자서명하고 ACS가 전자서명을 검증하여 유효한 Response인지를 확신할 수 있게 된다.

Identity Provider는 자체적인 다양한 방식으로 유저인증을 진행할 수 있으며 서비스 제공자는 Identity Provider를 신뢰하여 인증의 전권을 Identity Provider에 의존하게 되어 Identity Provider의 신뢰 및 책임부분이 중요한 요소이다.


3. SAML SSO 실제 구현

SAML SSO를 실제 구현해가면서 어떻게 동작하는지 살펴보자.


3.1 사전 준비

구현은 Service Provider로서의 단계와 Identity Provider로서의 단계를 모두 포함하여 Full Cycle을 순환할 수 있도록 하지만
실제로는 대부분 Service Provider나 Identity Provider의 역할중 어느 한 역할을 주로 하게 될것이다.


필요한 핵심 Library는 아래와 같다.


OpenSAML 2.0 Library

기타 apache commons의 유용한 Library들을 필요에 맞게 사용한다.


RSA Keypair를 준비한다.

openssl을 이용한 keypair 생성 command
$ openssl genrsa -out sso_private.pem 1024
$ openssl rsa -in sso_private.pem -pubout -ourform DER -out sso_public.der
$ openssl pkcs8 -topk8 -inform PEM -outform DER -in sso_private.pem -out sso_private.der -nocrypt

생성된 2개의 파일은 sso_private.der, sso_public.der이다.

한가지 주의할점은 sso_public.der은 우리가 통상적으로 알고 있는 인증서(certificate)가 아니다.
certifificate는 인증기관으로부터 public key의 유효성을 인정받은 경우에 인증기관의 전자서명이 첨부되어 발행되는데
여기서는 original rsa public key만으로 SSO를 구현한다.

대신 신뢰성을 확보하기 위하여 identity provider의 public key는 신뢰성 있는 방식으로 service provider에게 전달되어야하며
이 전달과정이나 identity provider의 private key 관리에는 주의를 기울여야한다.

SAML에서 이용하는 xmldsig spec이나 구현 사례를 봐도 인증서를 탑재하여 SAML SSO를 분명히 진행할 수 있다.
이 경우에는 identity provider의 public key를 특별한 방법으로 전달할 필요 없이
service provider는 신뢰할 수 있는 CA(Certificate Authority)가 보장하는 전자인증서를 확보하고 인증서 CRL 또는 OCSP checking등을 추가할 수 있을것이다.


3.2 SAMLRequest 생성 및 redirect

이 단계에서는 유저가 서비스 제공자의 사이트에 접속하고 SAML SSO 로그인을 진행하기 위해 SAMLRequest 생성 및 redirect의 과정을 설명한다.


3.2.1 ServiceProviderForm : 실제 유저가 최초로 접근하는 페이지이다. 여기서부터 시작

<form name="ServiceProviderForm" action="https://api.paygate.net/t/sso/saml/CreateRequestServlet.jsp" method="post">

  <input type="hidden" name="loginForm" value="https://api.paygate.net/t/sso/saml/login_form.jsp" />

  <input type="hideen" name="providerName" value="paygate.net" />

  <input type="hidden" name="RelayState" value="https://api.paygate.net/t/sso/saml/result_view.jsp" />

  <input type="hidden" name="acsURI" value="https://api.paygate.net/t/sso/saml/ACS.jsp" />

  <input type="submit" value="Sign On">

</form>

   * loginForm : Identity Provider에 위치하는 인증을 위한 로그인 폼
   * providerName : 서비스를 제공하는 Service Provider Name
   * RelayState : ACS에서 인증을 마친후 최종적으로 Redirecting하게 되는 서비스 제공 페이지 URL
   * acsURI : Identity Provider가 보낸 SAMLResponse를 검증(Verify)하고 실제 서비스 제공사이트로 Forwarding하게 되는 URL


3.2.2 CreateRequestServlet : SAMLRequst 데이터 생성 단계

  • 먼저 Parameter를 받고
    String ssoURL = request.getParameter("loginForm");
    String providerName = request.getParameter("providerName");
    String RelayState = request.getParameter("RelayState");
    String acsURI = request.getParameter("acsURI");

   public void doPost(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        String ssoURL = request.getParameter("loginForm");
        String providerName = request.getParameter("providerName");
        String RelayState = request.getParameter("RelayState");
        String acsURI = request.getParameter("acsURI");
        .....
    }

  • SAMLRequest xml data를 생성
    String SAMLRequest = createAuthnRequest(acsURI, providerName);
    private String createAuthnRequest(String acsURL, String providerName)
            throws SamlException {
        String filepath = getServletContext().getRealPath("t/sso/saml/templates/" + SAML_REQUEST_TEMPLATE);
        String authnRequest = Util.readFileContents(filepath);
        authnRequest = StringUtils.replace(authnRequest, "##PROVIDER_NAME##", providerName);
        authnRequest = StringUtils.replace(authnRequest, "##ACS_URL##", acsURL);
        authnRequest = StringUtils.replace(authnRequest, "##AUTHN_ID##", Util.createID());
        authnRequest = StringUtils.replace(authnRequest, "##ISSUE_INSTANT##", Util.getDateAndTime());
        return authnRequest;
    }

  • Identity Provider로 redirect할 URL생성
    String redirectURL = computeURL(ssoURL, SAMLRequest, RelayState);
    private String computeURL(String ssoURL, String authnRequest,
            String RelayState) throws SamlException {
        StringBuffer buf = new StringBuffer();
        try {
            buf.append(ssoURL);

            buf.append("?SAMLRequest=");
            buf.append(RequestUtil.encodeMessage(authnRequest));

            buf.append("&RelayState=");
            buf.append(URLEncoder.encode(RelayState));
            return buf.toString();
        } catch (UnsupportedEncodingException e) {
            throw new SamlException(
                    "Error encoding SAML Request into URL: Check encoding scheme - "
                            + e.getMessage());
        } catch (IOException e) {
            throw new SamlException(
                    "Error encoding SAML Request into URL: Check encoding scheme - "
                            + e.getMessage());
        }
    }

  • CreateRequestServlet.java 구현 종합
/*
 * CreateRequestServlet
 */
public class CreateRequestServlet extends HttpServlet {

    private static final String SAML_REQUEST_TEMPLATE = "AuthnRequestTemplate.xml";

    public void doPost(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {

        String returnPage = "service_proc.jsp"; // 실제 forwarding수행할 JSP
        response.setHeader("Content-Type", "text/html; charset=UTF-8");
        response.setContentType("text/html; charset=UTF-8");
       
        // get PARAMETERS
        String ssoURL = request.getParameter("loginForm");
        String providerName = request.getParameter("providerName");
        String RelayState = request.getParameter("RelayState");
        String acsURI = request.getParameter("acsURI");

        String SAMLRequest;
        String redirectURL;

        try {
            // create SAMLRequest
            SAMLRequest = createAuthnRequest(acsURI, providerName);
            request.setAttribute("authnRequest", SAMLRequest);

            // compute URL to forward AuthnRequest to the Identity Provider
            redirectURL = computeURL(ssoURL, SAMLRequest, RelayState);
            request.setAttribute("redirectURL", redirectURL);

        } catch (SamlException e) {
            request.setAttribute("error", e.getMessage());
        }

        request.getRequestDispatcher(returnPage).include(request, response);
    }
}

  • AuthnRequestTemplate.xml 참조
<?xml version="1.0" encoding="UTF-8"?>
<samlp:AuthnRequest
    xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol"
    ID="##AUTHN_ID##"
    Version="2.0"
    IssueInstant="##ISSUE_INSTANT##"
    ProtocolBinding="urn:oasis:names.tc:SAML:2.0:bindings:HTTP-Redirect"
    ProviderName="##PROVIDER_NAME##"
    AssertionConsumerServiceURL="##ACS_URL##"/>


3.2.3 service_proc.jsp : Identity Provider로 SAMLRequest를 forward

<%@page import="net.paygate.saml.util.RequestUtil"%>
<%@page import="java.net.*"%>
<%
      String error = (String) request.getAttribute("error");
      String authnRequest = (String) request.getAttribute("authnRequest");
      String redirectURL = (String) request.getAttribute("redirectURL");
%>
<html><head> <meta http-equiv="content-type" content="text/html; charset=UTF-8"><title>SAML-based Single Sign-On Service </title></head>
<%
    if (error != null) {
%>
        <body><center><font color="red"><b><%= error %></b></font></center><p>
<%
    } else {
        if (authnRequest != null && redirectURL != null) {       
%>
        <body onload="document.location = '<%=redirectURL%>';return true;">
          <h1 style="margin-bottom:6px">Submitting login request to Identity provider</h1>
     <%
       } else {
       %>
       <body>
          <center><font color="red"><b>no SAMLRequest or redirectURL</b></font></center><p>
          <%
       }
     }
     %>
</body></html>


3.3 SAMLRequest를 받고 유저인증 진행 단계

이 단계는 Identity Provider에서 진행하게 된다.
유저인증은 각 Identity Provider별로 다양한 방법을 취할 수 있고 여기서는 LDAP 인증을 사용하고 있다.


3.3.1 login_form.jsp : 기본적인 유저 인증 정보를 입력받는다.

  • Parameter를 받고
    String SAMLRequest = request.getParameter("SAMLRequest");
    String RelayState = request.getParameter("RelayState");

  • SAMLRequest를 Parsing한다.
    String requestXmlString = ProcessResponseServlet.decodeAuthnRequestXML(SAMLRequest);
    String[] samlRequestAttributes = ProcessResponseServlet.getRequestAttributes(requestXmlString);
       
  • 사용자 이름과 비밀번호를 입력받고
    <input type="text" name="username" id="username" size="18">
    <input type="password" name="password" id="password" size="18">

  • IdentityProviderForm을 생성한다.
    <form name="IdentityProviderForm" action="....." method="post">
    ....
    </form>

  • login_form.jsp 구현 종합
<%
    String SAMLRequest = request.getParameter("SAMLRequest");
    String RelayState = request.getParameter("RelayState");
    String ServiceProvider = "";
    if (SAMLRequest == null || SAMLRequest.equals("null")) {
        ServiceProvider = "";
    } else {
        String requestXmlString = ProcessResponseServlet.decodeAuthnRequestXML(SAMLRequest);
        String[] samlRequestAttributes = ProcessResponseServlet.getRequestAttributes(requestXmlString);
        String issueInstant = samlRequestAttributes[0];
        ServiceProvider = samlRequestAttributes[1];
        String acsURL = samlRequestAttributes[2];
    }
%>

<html><head><title>SSO Login Page</title></head>
<body>
<h1><%=ServiceProvider%> Service Login</h1>
<form name="IdentityProviderForm" action="https://api.paygate.net/t/sso/saml/ProcessResponseServlet.jsp" method="post">
      <input type="hidden" name="SAMLRequest" value="<%=SAMLRequest%>"/>
      <input type="hidden" name="RelayState" value="<%=RelayState%>"/>
      <input type="hidden" name="returnPage" value="./login_proc.jsp">
username : <input type="text" name="username" id="username" size="18">
<br>
password :
<input type="password" name="password" id="password" size="18"><br>
<input type="submit" value="로그인">
</form>
</body></html>


3.4 유저 확인후 SAMLResponse 생성


3.4.1 ProcessResponseServlet

  • login_form.jsp에서 parameter를 받는다.
    String SAMLRequest = request.getParameter("SAMLRequest");
    String returnPage = request.getParameter("returnPage");
    String username = request.getParameter("username");
    String password = request.getParameter("password");
    String RelayState = request.getParameter("RelayState");

    public void doPost(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        String SAMLRequest = request.getParameter("SAMLRequest");
        String returnPage = request.getParameter("returnPage");
        String username = request.getParameter("username");
        String password = request.getParameter("password");
        String RelayState = request.getParameter("RelayState");
        ...
    }

  • SAMLRequest를 parsing한다.
    String requestXmlString = decodeAuthnRequestXML(SAMLRequest);

    public static String decodeAuthnRequestXML(String encodedRequestXmlString) throws SamlException {
        try {
            Base64 base64Decoder = new Base64();
            byte[] xmlBytes = encodedRequestXmlString.getBytes("UTF-8");
            byte[] base64DecodedByteArray = base64Decoder.decode(xmlBytes);
            try {

                Inflater inflater = new Inflater(true);
                inflater.setInput(base64DecodedByteArray);
                byte[] xmlMessageBytes = new byte[5000];
                int resultLength = inflater.inflate(xmlMessageBytes);

                if (!inflater.finished()) {
                    throw new RuntimeException("didn't allocate enough space to hold decompressed data");
                }

                inflater.end();
                return new String(xmlMessageBytes, 0, resultLength, "UTF-8");

            } catch (DataFormatException e) {

                ByteArrayInputStream bais = new ByteArrayInputStream(base64DecodedByteArray);
                ByteArrayOutputStream baos = new ByteArrayOutputStream();
                InflaterInputStream iis = new InflaterInputStream(bais);
                byte[] buf = new byte[1024];
                int count = iis.read(buf);
                while (count != -1) {
                    baos.write(buf, 0, count);
                    count = iis.read(buf);
                }
                iis.close();

                return new String(baos.toByteArray());
            }

        } catch (UnsupportedEncodingException e) {
            throw new SamlException("Error decoding AuthnRequest: Check decoding scheme - " + e.getMessage());
        } catch (IOException e) {
            throw new SamlException("Error decoding AuthnRequest: Check decoding scheme - " + e.getMessage());
        }
    }


  • SAMLRequest에서 Attribute을 발췌한다.
    String[] samlRequestAttributes = getRequestAttributes(requestXmlString);
    String issueInstant = samlRequestAttributes[0];
    String providerName = samlRequestAttributes[1];
    String acsURL = samlRequestAttributes[2];

    public static String[] getRequestAttributes(String xmlString) throws SamlException {
        Document doc = Util.createJdomDoc(xmlString);
        if (doc != null) {
            String[] samlRequestAttributes = new String[3];
            samlRequestAttributes[0] = doc.getRootElement().getAttributeValue("IssueInstant");
            samlRequestAttributes[1] = doc.getRootElement().getAttributeValue("ProviderName");
            samlRequestAttributes[2] = doc.getRootElement().getAttributeValue("AssertionConsumerServiceURL");
            return samlRequestAttributes;
        } else {
            throw new SamlException("Error parsing AuthnRequest XML: Null document");
        }
    }

  • User 인증을 진행한다.
    boolean isValiduser = login(username, password);

    private boolean login(String username, String password) {
        LdapLoginHandler ldaplh = new LdapLoginHandler();

        if (password.length() < 1) return false;
        if (ldaplh.isValidOfficeUser(username, password)) {
            return true;
        } else {
            return false;
        }
    }
    * 실제 인증은 Identity Provider의 사정에 맞게 다양한 방식으로 진행한다.

  • RSA Keypair loading
    String publicKeyFilePath = keysDIR + "paygate_public.der";
    String privateKeyFilePath = keysDIR + "paygate_private.der";
    RSAPrivateKey privateKey = (RSAPrivateKey) Util.getPrivateKey(privateKeyFilePath, "RSA");
    RSAPublicKey publicKey = (RSAPublicKey) Util.getPublicKey(publicKeyFilePath, "RSA");

  • SAMLResponse의 유효기간정보 설정, 현시점부터 24시간 유효하게 설정함
    long now = System.currentTimeMillis();
    long nowafter = now + 1000*60*60*24;
    long before = now - 1000*60*60*24;

    SimpleDateFormat dateFormat1 = new SimpleDateFormat("yyyy-MM-dd'T'hh:mm:ss'Z'");
    java.util.Date pTime = new java.util.Date(now);
    String notBefore = dateFormat1.format(pTime);
    java.util.Date aTime = new java.util.Date(nowafter);
    String notOnOrAfter = dateFormat1.format(aTime);

    request.setAttribute("notBefore", notBefore);
    request.setAttribute("notOnOrAfter", notOnOrAfter);

  • 인증을 거친 로그인 유저네임과 유효기간을 포함한 전자서명전의 SAMLResponse XML data생성
    String responseXmlString = createSamlResponse(username, notBefore, notOnOrAfter);

    private String createSamlResponse(
            String authenticatedUser,
            String notBefore,
            String notOnOrAfter) throws SamlException {
        String filepath = getServletContext().getRealPath("t/sso/saml/templates/" + samlResponseTemplateFile);
        String samlResponse = Util.readFileContents(filepath);
        samlResponse = StringUtils.replace(samlResponse, "##USERNAME_STRING##", authenticatedUser);
        samlResponse = StringUtils.replace(samlResponse, "##RESPONSE_ID##", Util.createID());
        samlResponse = StringUtils.replace(samlResponse, "##ISSUE_INSTANT##", Util.getDateAndTime());
        samlResponse = StringUtils.replace(samlResponse, "##AUTHN_INSTANT##", Util.getDateAndTime());
        samlResponse = StringUtils.replace(samlResponse, "##NOT_BEFORE##", notBefore);
        samlResponse = StringUtils.replace(samlResponse, "##NOT_ON_OR_AFTER##", notOnOrAfter);
        samlResponse = StringUtils.replace(samlResponse, "##ASSERTION_ID##", Util.createID());
        return samlResponse;
    }
    * createID()는 UniqueID를 생성하는 함수임.
    * getDateAndTime()은 SAML date format에 맞게 날짜시간정보를 생성하는 함수임.
    * SAML date format : yyyy-MM-ddThh:mm:ssZ (예: 2008-01-30T23:05:23Z)
    * 시간정보는 Localtime이 아닌 UTC에 맞춰줘야함

  • SAMLResponse에 대하여 전자서명
     String signedSamlResponse = SAMLSigner.signXML(responseXmlString, privateKey, publicKey);
    * 이때 publicKey는 SAMLResponse message에 포함되지만 데이터 암호화에는 사용되지 않는다.
    * xmldsig.jar, xmlsec.jar가 필요함

  • 서명된 SAMLResponse를 포함하여 ACS로 forwarding한다.
    request.setAttribute("samlResponse", signedSamlResponse);
    request.getRequestDispatcher(returnPage).include(request, response);

  • ProcessResponseServlet.java 구현 종합
/*
 * ProcessResponseServlet
 */

public class ProcessResponseServlet extends HttpServlet {

    private final String keysDIR = System.getProperty("PGV3_HOME")
            + SystemUtils.FILE_SEPARATOR + "SSO"
            + SystemUtils.FILE_SEPARATOR + "keys" + SystemUtils.FILE_SEPARATOR;

    private final String samlResponseTemplateFile = "SamlResponseTemplate.xml";
    private static final String domainName = "paygate.net";

    public void doPost(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        String SAMLRequest = request.getParameter("SAMLRequest");
        String returnPage = request.getParameter("returnPage");
        String username = request.getParameter("username");
        String password = request.getParameter("password");
        String RelayState = request.getParameter("RelayState");
       
        boolean continueLogin = true;

        if (SAMLRequest == null || SAMLRequest.equals("null")) {
            continueLogin = false;
            request.setAttribute("error","ERROR: Unspecified SAML parameters.");
            request.setAttribute("authstatus","FAIL");
           
        } else if (returnPage != null) {
            try {
               
                String requestXmlString = decodeAuthnRequestXML(SAMLRequest);
                String[] samlRequestAttributes = getRequestAttributes(requestXmlString);
                String issueInstant = samlRequestAttributes[0];
                String providerName = samlRequestAttributes[1];
                String acsURL = samlRequestAttributes[2];

                boolean isValiduser = login(username, password); // 유저인증
               
                if (!isValiduser) {
                    request.setAttribute("error", "Login Failed: Invalid user.");
                    request.setAttribute("authstatus","FAIL");
                } else {
                    request.setAttribute("issueInstant", issueInstant);
                    request.setAttribute("providerName", providerName);
                    request.setAttribute("acsURL", acsURL);
                    request.setAttribute("domainName", domainName);
                    request.setAttribute("username", username);
                    request.setAttribute("RelayState", RelayState);

                    String publicKeyFilePath = keysDIR + "paygate_public.der";
                    String privateKeyFilePath = keysDIR + "paygate_private.der";
                    RSAPrivateKey privateKey = (RSAPrivateKey) Util.getPrivateKey(privateKeyFilePath, "RSA");
                    RSAPublicKey publicKey = (RSAPublicKey) Util.getPublicKey(publicKeyFilePath, "RSA");

                    long now = System.currentTimeMillis();
                    long nowafter = now + 1000*60*60*24;
                    long before = now - 1000*60*60*24;
                   
                    SimpleDateFormat dateFormat1 = new SimpleDateFormat("yyyy-MM-dd'T'hh:mm:ss'Z'");
                    java.util.Date pTime = new java.util.Date(now);
                    String notBefore = dateFormat1.format(pTime);
                   
                    java.util.Date aTime = new java.util.Date(nowafter);
                    String notOnOrAfter = dateFormat1.format(aTime);
                   
                    request.setAttribute("notBefore", notBefore);
                    request.setAttribute("notOnOrAfter", notOnOrAfter);

                    if (!validSamlDateFormat(issueInstant)) {
                        continueLogin = false;
                        request.setAttribute("error", "ERROR: Invalid NotBefore date specified - " + notBefore);
                        request.setAttribute("authstatus","FAIL");
                    } else if (!validSamlDateFormat(notOnOrAfter)) {
                        continueLogin = false;
                        request.setAttribute("error", "ERROR: Invalid NotOnOrAfter date specified - " + notOnOrAfter);
                        request.setAttribute("authstatus","FAIL");
                    }

                    if (continueLogin) {
                        // 서명전의 SAMLResponse Message 생성
                       
String responseXmlString = createSamlResponse(username, notBefore, notOnOrAfter);
                        // SAMLResponse에 대한 전자서명
                        String signedSamlResponse = SAMLSigner.signXML(responseXmlString, privateKey, publicKey);
                        request.setAttribute("samlResponse", signedSamlResponse);
                        request.setAttribute("authstatus","SUCCESS");
                    } else {
                        request.setAttribute("authstatus","FAIL");
                    }
                }
            } catch (SamlException e) {
                request.setAttribute("error", e.getMessage());
                request.setAttribute("authstatus","FAIL");
            }
        }
        // Forward SAML response to ACS
        response.setContentType("text/html; charset=UTF-8");
        request.getRequestDispatcher(returnPage).include(request, response);
    }
}

  • SamlResponseTemplate.xml 참조
<samlp:Response ID="##RESPONSE_ID##" IssueInstant="##ISSUE_INSTANT##" Version="2.0"
    xmlns="urn:oasis:names:tc:SAML:2.0:assertion"
    xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol"
    xmlns:xenc="http://www.w3.org/2001/04/xmlenc#">
    <samlp:Status>
        <samlp:StatusCode Value="urn:oasis:names:tc:SAML:2.0:status:Success"/>
    </samlp:Status>
    <Assertion ID="##ASSERTION_ID##"
        IssueInstant="2003-04-17T00:46:02Z" Version="2.0"
        xmlns="urn:oasis:names:tc:SAML:2.0:assertion">
        <Issuer>https://www.opensaml.org/IDP
        </Issuer>
        <Subject>
            <NameID
                Format="urn:oasis:names:tc:SAML:2.0:nameid-format:emailAddress">
                ##USERNAME_STRING##
            </NameID>
            <SubjectConfirmation Method="urn:oasis:names:tc:SAML:2.0:cm:bearer"/>
        </Subject>
        <Conditions NotBefore="##NOT_BEFORE##"
            NotOnOrAfter="##NOT_ON_OR_AFTER##">
        </Conditions>
        <AuthnStatement AuthnInstant="##AUTHN_INSTANT##">
            <AuthnContext>
                <AuthnContextClassRef>
                    urn:oasis:names:tc:SAML:2.0:ac:classes:Password
                </AuthnContextClassRef>
            </AuthnContext>
        </AuthnStatement>
    </Assertion>
</samlp:Response>


3.5 SAMLResponse Message를 Service Provider의 ACS로 forward


3.5.1 login_proc.jsp : forwarding을 담당


<%@ page contentType="text/html; charset=UTF-8"%>
<%
    String acsURL = (String) request.getAttribute("acsURL");
    String samlResponse = (String) request.getAttribute("samlResponse");
    String RelayState = (String) request.getAttribute("RelayState");
    String authstatus = (String) request.getAttribute("authstatus");
    if (authstatus == null) authstatus = "FAIL";
    if (RelayState == null) RelayState = "";
%>
<html>
<head>
<title>forward to ACS</title>
</head>
<%
    if (samlResponse != null && authstatus.equals("SUCCESS")) {
%>
<body onload="javascript:document.acsForm.submit();">
    <form name="acsForm" action="<%=acsURL%>" method="post">
        <div style="display: none">
          <textarea rows=10 cols=80 name="SAMLResponse"><%=samlResponse%></textarea>
          <textarea rows=10 cols=80 name="RelayState"><%=RelayState%></textarea>
        </div>
    </form>
<%
    } else {
        %><script>alert('Login error'); history.back(-2); </script><%
    }
%>
</body>
</html>




3.6 Service Provider이 ACS에서 SAMLResponse를 verify

3.6.1 PublicACSServlet : Service Provider측의 ACS


  • Parameter를 받고
    String SAMLResponse = request.getParameter("SAMLResponse");
    String RelayState = request.getParameter("RelayState");

  • Identity Provider의 public Key를 load
    RSAPublicKey publicKey;
    publicKey = (RSAPublicKey) Util.getPublicKey(publicKeyFilePath,"RSA");

  • Identity Provider가 보내온 SAMLResponse를 서명검증(verify)
    boolean isVerified = SAMLVerifier.verifyXML(SAMLResponse, publicKey);

  • 검증을 거친이후 실제 서비스 사이트로 forward 요청
    request.getRequestDispatcher("./acs_proc.jsp").include(request, response);

  • PublicACSServlet.java 구현 종합
public class PublicACSServlet extends HttpServlet {
    private final String keysDIR = System.getProperty("PGV3_HOME")
            + SystemUtils.FILE_SEPARATOR + "CryptoServer"
            + SystemUtils.FILE_SEPARATOR + "keys" + SystemUtils.FILE_SEPARATOR;
   
    public void doGet(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        doPost(request, response);
    }
   
    public void doPost(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        String SAMLResponse = request.getParameter("SAMLResponse");
        String RelayState = request.getParameter("RelayState");

        // acs knows public key only.
        String publicKeyFilePath = keysDIR + "paygate_public.der";

        RSAPublicKey publicKey;
        try {
            publicKey = (RSAPublicKey) Util.getPublicKey(publicKeyFilePath,"RSA");

            boolean isVerified = SAMLVerifier.verifyXML(SAMLResponse, publicKey);

            if (isVerified) {

                String loginid = null;
                Document doc = Util.createJdomDoc(SAMLResponse);

                Iterator itr = doc.getDescendants();

                itr = doc.getDescendants(new ElementFilter());
                while (itr.hasNext()) {
                    Content c = (Content) itr.next();
                    if (c instanceof Element) {
                        Element e = (Element) c;
                        System.out.println("Element:" + e.getName());
                       
                        if ("NameID".equals(e.getName())) {
                            loginid = e.getText().trim();
                            break;
                        }
                    }
                }
                request.setAttribute("mid", loginid);
                request.setAttribute("RelayState", RelayState);
               
                response.setContentType("text/html; charset=UTF-8");
                request.getRequestDispatcher("./acs_proc.jsp").include(request, response);
            } else {
                System.out.println("SAMLResponse is modified!!");
                return;
            }

        } catch (SamlException e) {
            e.printStackTrace();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

3.7 완전하게 동작하는 WAR file

  • 화면상의 제약으로 인해 완전하게 동작하는 WAR File을 별도 준비하였다.
  • 파일이 필요한 분은 dev@paygate.net으로 메일요청하십시오.
  • 또한 아래 링크를 통해서 file download 위치를 확인할 수 있다.
    http://docs.google.com/Doc?id=dcxqxct2_4dsh8w4dt

4. SAML SSO 적용 실제 사이트


4.1 PayGate Admin Login

PayGate가 Service Provider 역할을 함.


4.1.1 최초 Service Login Page


  • 이 단계는 SAMLRequest를 생성하기전 서비스 제공자가 제시하는 화면이다.

4.1.2 Identity Provider로서 Login Form 제시


  • 이 화면이 보이기까지 서비스 제공자는 SAMLRequest를 생성하고
  • Identity Provider로 SAMLRequest를 redirect하면
  • 유저인증을 위해서 보여지는 화면이다.
  • 이 화면 이후 로그인 버튼을 누르게 되면 SAMLResponse 메세지를 생성하여 Service Provider의 ACS Site로 forwarding하게 된다.

4.1.3 Service Provider ACS verify를 거친후 마지막 페이지



  • Service Provider에 위치하는 ACS가 SAMLResponse를 verify한 이후 실제 서비스 제공 사이트로 forwarding한 결과이다.

4.2 Google Apps Service

  • google은 service provider역할을 하며
  • paygate는 identity provider역할을 한다.

4.2.1 gmail 서비스 로그인을 위한 인증 화면


  • google에서 SAMLRequest를 생성하여 identity provider인 페이게이트로 redirect하면
  • 페이게이트에서 유저 인증을 위하여 생성한 화면이다.


4.2.2 identity provider의 인증을 거친후 로그인된 화면



  • Identity Provider가 SAMLResponse를 생성하여
  • google acs로 forwarding하면 verify한 이후 실제 서비스 제공 사이트로 연결한다.


5. SAML의 확장


5.1 공인인증기관과 Identity Provider

SAML SSO는 국내에서는 공인인증기관이 Identity Provider역할을 한다면 아주 적당한 business model이 될것 같다.

공인인증기관의 신뢰성을 바탕으로 인증서 발행한 유저에 대한 확인을 직접 수행하고
Service Provider는 공인인증기관의 확인만을 검증하여 그 결과를 신뢰할 수 있다.

더 나아가서는 꼭 공인인증기관이 Identity Provider역할을 하지 않더라도
공인인증서 기반의 Public Identity Provider는 쉽게 예상할 수 있다.


5.2 Payment Gateway와 Identity Provider

결제대행사(Payment Gateway)사가 Identity Provider역할을 하고
쇼핑몰이 Service Provider가 되는 구조를 생각해볼 수 있다.

SAMLRequest는 충분히 확장가능한 구조이므로 Payment 요청에 필요한 필수정보 (상품명, 가격 등)을 포함하여
PG사에 대하여 Identity Provider로서 요청하고
PG사 사이트에서 안전하게 Payment를 처리하고 그 결과를 SAMLResponse format으로 돌려주는 구조이다.

이는 Cross Domain간에 Payment Protocol에 대한 표준이 부재한 현 상황에서
의미있게 시도해봄직한 목표이다.


6. 참고자료

SAML Single Sign On Service for Google Apps


 

OpenSAML


 

SAML at Wikipedia

"특별기고" 카테고리의 다른 글

Posted by 1010
90.개발관련문서2008. 11. 11. 17:49
반응형
=IF(LEN(#REF!)=10,MID(#REF!,4,2),"0"&MID(#REF!,4,1))
Posted by 1010
90.개발관련문서2008. 11. 6. 12:01
반응형
출처:이정환닷컴

'RSA 공개열쇠 방식'의 암호화는 다음의 3단계 과정이 필요하다.

1단계 : 공개 열쇠 만들기.

1. 암호를 받는 쪽에서는 아무거나 두 소수, p와 q를 생각한다.
2. p와 q를 곱해 N을 만든다.

3. 아무 숫자나 다음 식을 만족하는 두 숫자를 생각한다.

1=(e*d)mod[(p-1)(q-1)]

4. mod()는 나머지를 구하는 함수다. 40mod(7)은 40을 7로 나눈 나머지를 구하라는 말이다. 답은 5다.

5. N과 e는 공개 열쇠다. 전화로 불러줘도 되고 사람들에게 알려줘도 된다. 전혀 문제가 없다.

2단계 : 암호 만들기.

1. 암호를 만드는 쪽에서는 공개 열쇠 N과 e를 써서 암호를 만든다. 문제는 다른 사람들도 모두 이 공개 열쇠를 알고 있다는 사실이다. 열쇠는 공개돼 있지만 받는쪽에서만 풀 수 있어야 한다.

2. 먼저, 문서의 모든 글자를 이진수로 바꾸고 다시 십진수로 바꾼다. 이 숫자를 M이라고 한다. 우리는 지금부터 M을 암호로 바꾼다.

3. 다음 식을 써서 암호 C를 만든다.

C=Memod(N)

3단계 : 암호 풀기.

1. 암호 C를 받아서 원래 문서 M으로 풀어보자.
2. 공식은 간단하다. N과 d를 집어넣으면 된다.

M=Cdmod(N)

3. N과 e는 공개돼 있지만 다른 사람들은 p와 q를 알 방법이 없다. 한번 더 계산해서 만든 d는 더 알 수 없다.



백문이 불여일견, 예제를 풀어보자.

1단계 : 공개 열쇠 만들기.

1. 이정환은 두 소수 11과 17을 생각하고 곱해서 공개 열쇠 N을 187로 잡는다.

2. 이정환은 대충 d를 23로 놓고 다음 공식에 넣어서 공개 열쇠 e를 7으로 잡는다.

1=(e*d)mod[(p-1)(q-1)]

1=(7*23)mod(10*16)
1=161mod(160)

3. N과 e, 187과 7을 공개한다. 얘들아, 우리 이걸로 암호 만들거다. 풀 수 있으면 풀어봐라.

2단계 : 암호 만들기.

1. 백우진은 'X'를 암호화하려고 한다. 이진수로 바꾸면 1011000, 다시 십진수로 바꾸면 M은 88이 된다. 백우진은 M을 공개 열쇠 N과 e, 187과 23을 써서 암호로 바꾼다.
2. 공식에 집어넣는다.

C=Memod(N)

C=887mod(187)
C=40867559636992mod(187)
C=11

3. 40867559636992을 187로 나누면 나머지가 11이 나온다는 이야기다. 11이 암호다. 11를 이정환에게 보낸다.

3단계 : 암호 풀기.

1. 이정환은 이제 암호 C, 11을 풀어 원래 숫자 M을 찾아내야 한다.

2. 공식에 집어넣는다. N과 d는 각각 187과 23이다. N은 공개 열쇠지만 d는 이정환만 아는 숫자다. 처음에 생각한 숫자 p와 q를 곱해서 N을 만들고
이걸 조합해서 d와 e를 만들었는데, 공개된 N과 e만 가지고 d를 찾기는 굉장히 어렵다. 거의 불가능하다.

M=Cdmod(N)

M=1123mod(187)
M=895430243255237372246531mod(187)
M=88

3. 88, 암호를 풀었다.

'RSA 공개 열쇠 방식'은 MIT 컴퓨터 사이언스 실험실의 론 리베스트와 아디 샤미르, 레너드 애들먼, 세 사람이 발명했다. RSA는 이들의 머릿글자 모음이다. 이 암호화 방식의 핵심은 소인수 분해에 있다.

두 소수 p와 q를 곱해서 공개 열쇠 N을 만들면 이걸 쥐고 아무리 난리법석을 떨어도 원래 숫자를 찾을 방법이 없다.

35가 5와 7로 나눠져 있다는 건 쉽게 알 수 있다. 소인수 분해는 하나하나 숫자를 맞춰보는 수밖에 딱히 방법이 없다. 숫자가 커지면 슈퍼컴퓨터를 돌려도 몇십년씩 걸린다. 17과 19607843을 곱하면 333333331이 된다는 건 쉽게 알 수 있지만 그걸 찾기가 어렵다는 이야기다. 실제로 수학자들은 수백년동안 이 숫자가 소수라고 생각해왔다.

여기서 문제를 한번 더 뒤집어 공개 열쇠를 두개 만들어 놓고 이걸 꼬아서 혼자만 아는 결정적인 열쇠 d를 만들어 놓으면 당신의 암호는 거의 완벽하다고 할 수 있다.

위의 예제에서 공개 열쇠 N과 d는 각각 187과 23이다. 이걸 풀려면 N이 먼저 뭐와 뭐의 곱인가 알아야 한다. 그리고 그 뭐와 뭐에서 각각 1을 뺀 숫자의 곱을 구하고 거기서 다시 1을 뺀 수가 d의 몇배인가도 알아야 한다. 몇배인가만 알면 암호는 풀리겠지만, 한번 해봐라. 결코 만만치 않다.

187 정도면 어떻게 풀릴 수도 있지만 보통은 자릿수가 308 이상이다. 한번 보겠는가.

4286280348253421170679821480865132823066470938446095505822317253594081284811174502841027019385211055596446229489549303819644288109756659334461284756482337867831652712019091456485669234603486104543266482133936072602491412737245870066063155881748815209209628292540917153643678925903600113305305488204665213841469519415116094330572703657595919530921861173819326117931051185480744699627495237

이게 308자리 숫자다. 이게 뭐와 뭐, 두 소수의 곱으로 돼 있는가 맞춰봐라. 이 정도면 1억대의 PC를 한꺼번에 돌려도 1000년이 넘게 걸린다고 한다. 현재의 과학기술로는 이런 암호를 풀 방법이 없다.

도움 말씀 : 백우진.
참고문헌 : '코드북', '현대수학의 여행자, '수학: 양식의 과학', '영부터 무한대까지', '수학의 황제 가우스'.
Posted by 1010
90.개발관련문서2008. 11. 4. 19:00
반응형
Posted by 1010
90.개발관련문서2008. 10. 23. 16:22
반응형


다운받아 보삼..
Posted by 1010
90.개발관련문서2008. 10. 21. 20:38
반응형

[MyEclipse] J2EE 빠른 시작

[MyEclipse] EJB 개발 빠른 시작

[MyEclipse] Struts 빠른 시작-튜토리얼

 

제 블로그에 있는 번역 문서입니다.

번역이 초역이라 아직 스크랩할 수 있는 수준은 아닙니다.

Posted by 1010
90.개발관련문서2008. 10. 20. 23:46
반응형

■ 000000 black (블랙) 검정
■ 2f4f4f darkslategray (다크슬레이트그레이)  어두운푸른빛회색
■ 708090 slategray (슬레이트그레이) 푸른빛회색
■ 778899 lightslategray (라이트슬레이트그레이) 밝은푸른빛회색
■ 696969 dimgray (딤그레이) 칙칙한회색
■ 808080 gray (그레이) 회색
■ a9a9a9 darkgray (다크그레이) 어두운회색
■ c0c0c0 silver (실버) 은색
■ d3d3d3 lightgrey (라이트그레이) 밝은회색
■ dcdcdc gainsboro (게인스보로)
 

ffffff white (화이트) 백색
■ fff5ee seashell (씨쉘) 바다조가비

■ fffafa snow (스노우) 설백
■ f8f8ff ghostwhite (고스트화이트) 허깨비백색
■ fffaf0 floralwhite (후로랄화이트) 꽃의백색
■ f5f5f5 whitesmoke (화이트스모크) 백색연기
■ f0f8ff aliceblue (앨리스블루) 엷은잿빛바탕에푸른빛
■ f0ffff azure (애쥬어) 하늘색

■ fdf5e6 oldlace (올드레이스) 낡은끈
■ f5fffa mintcream (민트크림) 박하크림
■ ffefd5 papayawhip (파파야윕) 열대아메리카산과수매질
■ ffdab9 peachpuff (피치퍼프) 복숭아빛불기
■ faf0e6 linen (린넨) 리넨
■ eee8aa palegoldenrod (팔레골덴로드) 옅은국화과의다년초
■ ffe4e1 mistyrose (미스티로즈) 짙은장미빛
■ ffe4b5 moccasin (모카신) 사슴가죽의 구두
■ ffdead navajowhite (나바조화이트) 나바호족백색
■ d2b48c tan (탄) 볕에탄빛깔

■ f5deb3 wheat (위트) 밀
■ fafad2 lightgoldenrodyellow (라이트골덴로드옐로우) 밝은국화과다년초노랑
■ ffffe0 lightyellow (라이트옐로우) 밝은노랑
■ fff8dc cornsilk (콘실크) 옥수수명주실
■ faebd7 antiquewhite (안티크화이트) 옛날의백색
■ f5f5dc beige (베이지) 옅은갈색
■ fffacd lemonchiffon (레몬치폰) 레몬빛가볍고얇은직물
■ fffff0 ivory (아이보리) 상앗빛
■ f0e68c khaki (카키) 누른빛에엷은다색이섞인빛깔
■ e6e6fa lavender (라벤더) 연한자주색
■ fff0f5 lavenderblush (라벤더블러시) 연한자주빛붉기
■ ffe4c4 bisque (비스크) 분홍빛이도는검은빛을띤누른빛
■ ffebcd blanchedalmond (블란체달몬드) 희어진엷은황갈색
■ deb887 burlywood (벌리우드) 튼튼한목재
■ cd853f peru (페루)
 남미서부의공화국

■ 00ced1 darkturquoise (다크터콰이즈) 어두운푸른빛녹색
■ 00bfff deepskyblue (딥스카이블루) 짙은하늘파랑
■ 7fffd4 aquamarine (아쿠아마린) 엷은푸른빛녹색
■ 1e90ff dodgerblue (도저블루) 옥수수빵파랑
■ 00ffff cyan (시안) 푸른정도
■ f0fff0 honeydew (허니듀)  식물의잎에서나는단물
■ 87cefa lightskyblue (라이트스카이블루) 밝은하늘파랑
■ afeeee paleturquoise (팔레터콰이즈) 옅은푸른빛녹색
■ e0ffff lightcyan (라이트시안) 밝은푸른정도
■ add8e6 lightblue (라이트블루) 밝은파랑
■ add8e6 lightsteelblue (라이트스틸블루) 밝은철강빛파랑
■ 40e0d0 turquoise (터콰이즈) 푸른빛녹색
■ 48d1cc mediumturquoise (미디엄터콰이즈) 중간의푸른빛녹색
■ 00ffff aqua (아쿠아) 옥색
■ 7b68ee mediumslateblue (미디움슬레이트블루) 중푸른빛회색파랑
■ 191970 midnightblue (미드나이트블루) 깜깜한파랑
■ 6495ed cornflowerblue (콘플라워블루) 옥수수꽃파랑
■ 0000cd mediumblue (미디움블루) 중간의 파랑
■ 6a5acd slateblue (슬레이트블루) 푸른빛회색파랑
■ 4682b4 steelblue (스틸블루) 철강빛파랑
■ 0000ff blue (블루) 파랑
■ 483d8b darkslateblue (다크슬레이트블루) 어두운푸른빛회색파랑
■ 5f9ea0 cadetblue (카뎃블루) 이하의파랑
■ 87ceeb skyblue (스카이블루) 하늘파랑
■ 4169e1 royalblue (로열블루) 황실의파랑
■ b0e0e6 powderblue (파우더블루) 가루파랑
■ 000080 navy (네이비) 짙은검은빛을띤남빛
■ 00008b darkblue (다크블루) 어두운파랑
■ 8a2be2 blueviolet (블루바이올렛) 파란제비꽃색
■ 8b008b darkmagenta (다크마그네타) 어두운짙은분홍색
■ 9932cc darkorchid (다크오치드) 어두운연보라색
■ 9400d3 darkviolet (다크바이올렛) 어두운제비꽃색
■ ff00ff magenta (마그네타) 짙은분홍색
■ ff00ff fuchsia (퍼츠샤) 붉은빛깔이나는자줏빛
■ c71585 mediumvioletred (미디움바이올렛레드) 중제비꽃빨강

■ ba55d3 mediumorchid (미디움오치드) 중간의연보라색
■ 9370db mediumpurple (미디움퍼플) 중간의자줏빛

■ dc143c crimson (크림슨) 짙게붉은색깔
■ ff1493 deeppink (딥핑크) 짙은연분홍색
■ ffb6c1 lightpink (라이트핑크) 밝은연분홍색
■ ff69b4 hotpink (핫핑크) 강렬한연분홍색
■ ffc0cb pink (핑크) 연분홍색
■ dda0dd plum (플럼) 짙은보라색
■ 800080 purple (퍼플) 자줏빛
■ ee82ee violet (바이올렛) 제비꽃색
■ d8bfd8 thistle (디스틀) 엉겅퀴
■ da70d6 orchid (오치드) 연보라색
■ 4b0082 indigo (인디고) 남색
■ a52a2a brown (브라운) 갈색
■ e9967a darksalmon (다크샐몬) 어두운주황색
■ f08080 lightcoral (라이트코랄) 밝은산호빛
■ cd5c5c indianred (인디안레드) 인디언빨강
■ ffa07a lightsalmon (라이트샐몬) 밝은주황색
■ db7093 palevioletred (팔레바이올렛레드) 옅은제비꽃빨강
■ f4a460 sandybrown (샌디브라운) 옅은갈색
■ fa8072 salmon (샐몬) 주황색
■ ff6347 tomato (토마토) 토마토색
■ ff4500 orangered (오렌지레드)
■ ff0000 red (레드) 빨강
■ 800000 maroon (마룬) 붉은색을띤갈색
■ 8b0000 darkred (다크레드) 어두운빨강
■ b22222 firebrick (파이어브릭) 내화
■ d2691e chocolate (초콜렛) 초콜릿색
■ 8b4513 saddlebrown (새들브라운) 안장갈색
■ a0522d sienna (시에나) 붉은빛을띤갈색
■ bc8f8f rosybrown (로지브라운) 장미빛갈색
■ ff7f50 coral (코랄) 산호빛
■ ff8c00 darkorange (다크오렌지) 어두운붉은빛을띤누른색
■ ffa500 orange (오렌지) 붉은빛을띤누른색
■ b8860b darkgoldenrod (다크골덴로드) 어두운국화과의다년초
■ ffd700 gold (골드) 금빛

■ ffff00 yellow (옐로우) 노랑
■ 7fff00 chartreuse (차트리우스) 연두
■ 7cfc00 lawngreen (라운그린) 잔디녹색
■ 00ff00 lime (라임) 열대산의레몬비슷한과일
■ 32cd32 limegreen (라임그린) 라임녹색
■ 00ff7f springgreen (스프링그린) 봄녹색
■ 3cb371 mediumseagreen (미디움씨그린) 중간의바다녹색
■ adff2f greenyellow (그린옐로우) 녹색의 노랑
■ 8fbc8f darkseagreen (다크씨그린) 어두운바다녹색
■ 0ee90 lightgreen (라이트그린) 밝은녹색
■ 8fb98 palegreen (팔레그린) 옅은녹색
■ 9acd32 yellowgreen (옐로우그린) 황록색
■ 2e8b57 seagreen (씨그린) 바다녹색
■ 00fa9a mediumspringgreen (미디움스프링그린) 중봄녹색
■ 20b2aa lightseagreen (라이트씨그린) 밝은바다녹색
■ 66cdaa mediumaquamarine (미디움아쿠아마린) 중엷은청록색
■ 228b22 forestgreen (포레스트그린) 숲녹색
■ 008b8b darkcyan (다크시안) 어두운푸른정도
■ 008080 teal (틸) 암록색을띤청색
■ 006400 darkgreen (다크그린) 어두운녹색
■ 556b2f darkolivegreen (다크올리브그린) 어두운올리브녹색
■ 008000 green (그린) 녹색
■ 808000 olive (올리브) 물푸레나뭇과의상록교목
■ 6b8e23 olivedrab (올리브드래브) 올리브엷은갈색
■ bdb76b darkkhaki (다크카이) 어두운누른빛에엷은다색
■ daa520 goldenrod (골덴로드) 국화과의다년초

 

힘들다 편집..;;

오타 수정하고.. 없는 색이름 넣고..

그나저나 내가 오타낸 건 없겠지..;;

그런데.. 뭐랄까...뭔가 어색한 한글해석..-_-;;

아쿠아와 아쿠아마린 색이 같길래 아쿠아는 사전에 있는 옥색으로

초콜렛과 브라운 색이 갈색으로 같길래 초콜렛을 초콜렛색으로..

나머진..귀찮으니 패~스!



아무튼....결국 무슨색인지 못찾은 gainsboro 게인스보로 ......ㅠㅠ

은색과 회색은 중간정도로 보이는데..

꼭 찾을테야!+_+

그리고 아직 감이 안 잡히면 색상표를 보아야지요~

Posted by 1010
90.개발관련문서2008. 9. 1. 13:33
반응형

대략 5분동안 자신의 컴퓨터를 손 봄으로써 인터넷 속도를 빨라지게 할수 있다.
다음과 같이 해서 좀 더 빠른 내 컴퓨터로 인터넷을 활보해보자.

- 내 네트윅에서 이웃 컴퓨터 빨리 찾기:
윈도우의 좌측 하단 "시작" 버튼을 누른후 "실행"을 찾아 "regedit"을 입력한후 확인을 누른다.
"regedit"창에서 다음 항목으로 들어간다.
HKEY_LOCAL_MACHINE/Software/Microsoft/Windows/CurrentVersion/Explorer/RemoteComputer/NameSpace.
최종 namespace에서 다음 항목을 삭제하면 된다. {D6277990-4C6A-11CF-8D87-00AA0060F5BF}.

- 웹페이지 빨리 열기:

다음 Tweak 설정이 웹페이지를 빨리 열게해주지만 다운로드 속도에는 영향을 미치지 않는다.

윈도우의 좌측 하단 "시작" 버튼을 누른후 "실행"을 찾아 "regedit"을 입력한후 확인을 누른다.
"Regedit"에서 다음 항목으로 이동한다.

윈도우즈 XP & 2000의 경우HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\Tcpip\ServiceProvider

윈도우즈 98, 98SE & ME 경우HKEY_LOCAL_MACHINE\System\CurrentControlSet\Services\VxD\MSTCP\ServiceProvider

각 항목으로 이동하면 오른쪽 창에서 다음 엔트리들의 값을 헥사데시말(HEXIDECIMAL)의 값으로 1을 만든다.

DnsPriority = 1
HostsPriority = 1
LocalPriority = 1
NetbtPriority = 1

값을 바꾸기위해서는 각 엔트리에서 오른쪽 마우스 버튼을 눌러야한다.
값을 변경한후 변경된 것이 적용되게 하기 위해서는 컴퓨터를 다시 시작해야한다.

사용자 삽입 이미지

- 인터넷 익스플로러 제대로 설정하기

도구>인터넷 옵션의 일반 탭의 임시 인터넷 파일의 설정 버튼을 누른다.

사용자 삽입 이미지

임시 인터넷 파일 폴더 섹션의 사용할 디스크 공간의 크기를 100MB이하로 설정한다.
사용자 삽입 이미지

연결탭에서 LAN설정 버튼을 눌러 다음 화면의 모든 사항을 선택 해제한다.
사용자 삽입 이미지

개인정보 탭에서 고급 버튼을 누른후 다음 그림과 같이 설정한다.
사용자 삽입 이미지

임시 인터넷 파일의 용량을 100MB로 설정하고 주기적으로 일주일에 한번씩 임시 인터넷 파일을 삭제하라.


- 모뎀 주기적으로 다시 시작하기
모뎀은 하루 24시간 계속 연결되어 있기 때문에 일주일에 한번 정도는 파워를 15초 정도 끈후 다시 연결한다.


- DNS CACHE 수정

윈도우즈 2000 과 xp는 기본적으로 DNS 캐쉬 서비스에서 제대로 된 DNS 정보 또는 잘못된 DNS 정보를 저장하고 있다. 잘못된 DNS정보를 제거하는 기능을 높임으로서 접속 속도의 저하를 제거한다.
다음 엔트리에 있는 값을 모두 0 으로 만든다.
 

[HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\Dnscache\Parameters]
"NegativeCacheTime"=dword:00000000
"NetFailureCacheTime"=dword:00000000
"NegativeSOACacheTime"=dword:00000000


- 스파이웨어를 주기적으로 체크해서 제거하기.

- 이더넷 드라이버와 펌웨어를 가능한 최신의 것으로 업데이트하라.


- 필요없는 프로트콜 제거하라.

윈도우지 2000이나 xp의 네트윅 연결에서 필요없는 프로토콜을 제거하라. 집에서 컴퓨터 한대만을 쓴다면 필요한 프로토콜은 TCP/IP만 있으면 된다.

- TCP/IP 제대로 설정하기

XP에서 TCP/IP 인터페이스 메트릭을 1로 설정하라.
파일또는 프린터 공유를 하지 않는다면 Netbios over TCP/IP 사용하지 않기로 설정하라.

사용자 삽입 이미지
Posted by 1010
90.개발관련문서2008. 8. 19. 17:30
반응형
Posted by 1010
90.개발관련문서2008. 8. 17. 02:40
반응형
사용자 삽입 이미지
새로 보는 프로그래밍 언어
개발자의 가치를 높이는 프로그래밍 언어 대백과
마이클 스콧 지음 | 민병호 김진혁 옮김 | 1004페이지 (부록CD 260페이지 추가수록)
45,000원 | 2008년 8월 21일 출간 예정

이 책을 한 마디로 표현해볼까요?
괴물!
프로그래밍 언어를 다룬 책 중에 여태까지 이런 책은 한 권도 없었습니다.
여기서 "이런"은 뭘 의미할까요? eRun? 자, 이제부터 차근차근 하나씩 설명해드리죠.

정말 몸값을 높이고 싶으신가요? 단지 코드 몇 줄을 잘 짠다고 몸값이 올라간다고 생각하시나요? 개발자로서 자신의 앎이 부족하다고 느끼신 적은 없으신가요? 학교에서 배우는 프로그래밍 언어론이 수박 겉핥기에 지나지 않는다고 느껴보신 적은 없습니까.

이 책의 원서 목차를 보고 난다긴다 하는 개발자분들이 모두 혀를 내두르셨습니다. 심지어 어떤 프로그래밍 언어를 전공하셨다는 일급 자바 개발자께선 "이런 '발칙한 책'을 봤나"라는 반응을 하실 정도였으니까요. (사실은 좀더 저급한 표현을 쓰셨지만, 여기서는 조금 정제해서 말씀드려야겠죠 -0-)
사용자 삽입 이미지
이 책에서 다루고 있는 프로그래밍 언어의 "간추린" 계보입니다. 이 책에서는 명령형 언어, 함수형 언어, 논리형 언어, 병행/병렬 프로그래밍, 그리고 최근 부쩍 떠오르는 스크립팅 언어까지 거의 "모든" 언어를 다루고 있습니다. 그저 다루는 정도가 아니라 각 언어로 구현한 코드 사례를 보여주며 그 언어의 장단점까지를 파헤치고 있습니다. 개발자가 빠지기 쉬운 함정과 오류를 낱낱이 보여줍니다.
사용자 삽입 이미지
그림이 좀 보이세요? 이 책에서 다루는 간단 목차입니다. 네, 이 책의 당초 집필 목적은 교과서입니다. 학부 3,4학년, 혹은 대학원생을 위한 아주아주 훌륭한 교과서입니다. 잠깐만요! 그렇다고 단지 교과서로 지루하게 나열된 책은 결코 아닙니다. 기존 프로그래밍 언어론 책에서는 다루지 않았던 정석과 기본기를 차근차근 쌓아가게 하는 책입니다.

1부. 프로그래밍 언어의 기초에서는 컴파일러와 컴퓨터 아키텍처, 프로그래밍 언어 설계와 구현에 대한 기본기를 닦아줍니다. 2부. 프로그래밍 언어의 핵심요소에서는 명령형 언어 C와 C++등을 중심으로 프로그래밍 언어의 공통적인 사항과 기본 개념을 다룹니다. 이어지는 3부. 다른 관점에서 바라본 프로그래밍 모델에서는 함수형 언어, 논리형 언어, 병행 프로그래밍 등과 함께 이 책의 (원서) 개정판에서 추가된 내용으로 PHP, 루비, 파이썬 등 스크립팅 언어들까지 다루며 내용을 이어갑니다. 4부에서는 드디어 로우레벨 관점에서 바라본 프로그램 되짚기가 등장합니다. 중간언어 등을 훑어보며, 프로그램을 컴파일 후 기계어로 변환된 코드를 분석해 코드를 개선하는 프로그램 최적화까지 다루게 됩니다.

그야말로 프로그래밍 언어의 개념, 이론, 설계, 구현, 최적화 등 전체적인 프로그래밍 언어의 전반을 꿰뚫고 각 사례를 모든 프로그래밍 언어로 구현해 그 언어들의 강점과 취약점을 분석해줌으로써, 결국 개발자가 프로그래밍 과정에서 가장 적절한 언어를 어떻게 선택해내 얼마나 효율적인 프로그램을 만들 수 있을까를 알려주는 것을 최종 목표로 합니다.

이제 왜 책의 제목에 "새로 보는"이라는 수식어가 붙었는지 이해가 가세요? 저희도 처음엔 아주 쉽게 "완전정복"이라든가 "완벽 가이드" "총서" 등의 아주 식상한 문구를 생각했었습니다. 하지만 이 책은 기존에는 전혀 볼 수 없었던 프로그래밍 언어론에 대한 절대(!)적이고도 상식(!)적인 프로그래밍 언어 대백과라고 할 수 있습니다. 기존 관념을 깨고 혜안을 갖게 해주는, 프로그래밍 언어의 비밀을 한꺼풀 벗겨내는 책이거든요. 자, 이 정도면 왜 자신있게 여러분의 몸값을 높여주는 책이라고 큰 소리를 쳤는지 조금은 동의를 하시나요? 아직도 이해를 못 하시겠다면 8월 21일 서점에 가셔서 직접 확인하시기 바랍니다. :)

게다가 함께 제공하는 부록CD에는 책의 분량(이미 1004페이지, 천사에요. angel ^^)이 비대해지는 것을 막기 위해 중간중간 좀더 심도깊은 심화학습에 해당하는 내용을 옮겨 담았습니다. 260페이지에 달하는 분량인데요. 이것만 해도 웬만한 책 한 권 분량은 족히 됩니다. 부록 CD에는 그밖에도 공개 컴파일러와 해석기 링크 모음, 책에 나온 300개 이상의 코드의 완전한 소스파일 등이 가득가득 들어있습니다. 지금 CD 메뉴 내비게이션까지 한글로 옮겨내는 마무리 작업 중인데요. 독자를 감동시킬 만한 세심한 작업에 최선을 다하고 있습니다. 원서보다 나은 번역서, 에이콘 책이 늘 지향하는 바죠.

이 책을 번역한 역자 민병호님과 김진혁님은 강유님과 함께 『TCP/IP 완벽 가이드』를 공역하셨던 분들인데 드디어 이제 두 분만의 멋진 작품을 만들어내셨습니다. 그때만해도 대학원생 신분이셨는데 이제 한 분은 국방과학연구소에서 한 분은 삼성전자에서 열혈 근무중인 사회인이 되셨어요. 공교롭게도 이 분들의 번역 작품 두 권이 모두 1000페이지가 넘어가는 방대한 작업이었습니다. TCP/IP 완벽 가이드가 1,600쪽이었고, 이 책 『새로 보는 프로그래밍 언어』는 CD에 담긴 내용까지 합치면 1,260쪽을 넘어서니 정말 어마어마하죠. 그간 정말 고생 많으셨어요. TCP/IP 책을 마치고 출판사를 찾아오셔서 "한우 꽃등심 사주세요"하셨었는데, 이번에 책이 나오면 뭔가 미리 준비를 해둬야 할 것 같습니다. ㅎㅎ

제조물 책임법(Product Liability Law) 일명 PL법이라는 게 있습니다. "고양이를 렌지에 넣고 돌려 털을 말리지 마세요"(전자렌지)라든가 "에어콘에서 나오는 물은 마시지 마세요"(에어콘) 등 기상천외한 경고문을 삽입해둠으로써 소비자가 제품 오용으로 인한 피해를 막기 위하고 불가피한 소송을 피하기 위함이라고 하는데요. 정말 기상천외하고 황당한 경고문이 많습니다. 세상은 요지경이잖아요. 별의별 일이 다 일어나는 별천지 세상.

저희도 판권지쪽에 문구하나를 삽입해야 할까요?

"누워서 이 책을 읽으시다가 책을 놓치시면 머리에 심각한 상해를 입을 수 있습니다"
(대처법: 반드시 엎드려서 읽으세요!)
"베고 주무시다가 목 부분에 경련이 일어나 며칠간 고생을 할 수 있습니다"
(대처법: 좀더 얇은 다른 책을 고르세요.)
"책을 읽고 났는데도 가치가 높아지지 않았다구 느끼신다구요? 제대로 꼼꼼히 읽으셨는지 다시 한번 확인해보세요. 그럴 리가 없습니다. :) "


모두 열공하셔서 정말 훌륭한 개발자 되세요! 고고싱!!
Posted by 1010
90.개발관련문서2008. 8. 14. 10:38
반응형
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
Posted by 1010
90.개발관련문서2008. 8. 12. 17:33
반응형

Q : 파일 다운로드시 한글파일의 경우 다운로드 되지 않거나 글자가 깨집니다.
A : 익스플로러 "도구 > 인터넷 옵션 > 고급 "에서 고급 UTF8옵션 해제하시면 됩니다.

최종수정일자 : 2008.01.18 14:49:11



출처 : http://www.ihelpers.co.kr/programming/reference/index.php
Posted by 1010
90.개발관련문서2008. 8. 12. 16:15
반응형

00 오른쪽 마우스 메뉴에 도스창 열기 추가하기

원문 : http://kkamagui.springnote.com/pages/392898

참고 : http://zextor.tistory.com/2669790

 

들어가기 전에...


1.추가 방법

DOS 시절부터 컴퓨터를 이용하였거나 프로그래머 개발자의 경우 아직도 Dos이용하고 있습니다.

하지만 DOS를 실행하고 원하는 폴더에 접근하기 위해서는 원도우키 + R  또는 [시작]→[실행] 후 “ CMD “ 를 입력 후 도스창이 실행되면 CD 명령어를 이용하여 원하는 폴더에 이동할 수 있습니다.

레지스트리를 수정하여 탐색기를 이용하여 먼저 가고자 하는 폴더에 접근 후 DOS를 실행 하여 바로 해당 폴더의 경로로 연결되어 사용하기 편리합니다.


1. [시작]→[실행]에서 “regedit “를 입력하고 레지스트리 편집기를 실행한 후, 다음 키 값을 찾는다. HKEY_CLASSES_ROOT\Directory\shell

2. Shell 키 위에서 마우스 오른쪽 마우스 클릭(또는 shell 키 선택 후 오른쪽 공백에서 오른쪽 마우스 클릭) 후 [새로 만들기(N)] → [키]를 선택합니다.

3. 새로운 키의 이름을 DOS(이름은 원하시는 이름으로 하셔도 됩니다.)로 수정합니다.

4. 만들어진 DOS 키를 클릭 후 오른쪽의 기본값을 더블 클릭하여 오른쪽 마우스에 표시될 이름을 입력하여 주십시오. ( 예를 들어 도스창이라 입력합니다. )

5. 다음 새로 만들어진 DOS에서 Shell 과 마찬가지로 새로운 키를 만들어 Command 이름으로 수정합니다. ( DOS 와는 달리 반드시 command 이름으로 하여야 합니다. )

6. 만들어진 Command 키를 클릭 후 오른쪽의 기본값을 더블 클릭하여 cmd.exe /k cd "%1" 이라는 문자열을 입력하여 주십시오.

7. 컴퓨터를 재 시작하여 탐색기 실행 후 가고자 하는 폴더를 선택 후 오른쪽 마우스 클릭 후 도스창을 클릭하시면 해당 폴더의 경로로 도스창이 열리는 것을 확인 할 수 있습니다.


본 자료는 (주)웰비아닷컴 의 커뮤니티 - 활용팁 에서 스크랩한 것입니다.


이 자료 외에도 많은 정보가 있으니 필요하신 분은 직접 방문해 보시길 바랍니다.

본 페이지에서는 재부팅 후에 사용할 수 있다고 기술하였지만 바로 사용하고 싶으시면 아래와 같이 해주시면 됩니다.


1. '시작' 이 있는 작업표시줄에 마우스 오른쪽 버튼을 눌러 작업관리자를 띄움니다.

2. 프로세스 탭에서 explorer.exe 를 선택하여 프로세스 끝내기를 합니다. 경고가 뜰 경우 그냥 "예" 를 선택하십시오.

3. 그럼 밑에 작업표시줄이 없어질 것 입니다.

4. 그럼 아까 작업관리자의 응용 프로그램 탭으로 이동한 후 새 작업을 클릭합니다.

5. 열기 옆에 있는 입력창에 "explorer" 를 입력합니다.

6. 이제 탐색기의 오른쪽 메뉴에 DOS를 사용할 수 있습니다.

Posted by 1010
90.개발관련문서2008. 8. 12. 14:14
반응형
출판일 :2005년 10월호

 패킷 분석기는 네트워크 구축이나 운영시 발생하는 문제를 해결하기 위한 필수 요소다. 때문에 네트워크 관리자라면 누구라도 패킷 분석기의 세상에 한번쯤은 빠져들어야 한다. 패킷 분석기는 스니퍼와 같은 고가의 상용 제품도 있지만 이더리얼과 깉이 누구라도 쉽게 무료로 사용할 수 있는 공개용 솔루션도 있다. 이번호에는 국내뿐 아니라 해외에서도 네트워크 관리자라면 보편적으로 많이 사용하고 있는 이더리얼의 주요 기능과 활용법에 대해 소개한다.

최성열| 파이오링크 기술지원센터장

 한국의 큰 명절인 지난 추석에 필자는 중요한 일 때문에 가족들과 떨어져 일본에서 보내야만 했다. 신규로 구성하는 네트워크에서 문제가 발생해 일본의 파트너들과 함께 일을 했다. 그곳에서 발생한 문제는 이더넷 패킷이 일부 변경되거나 패킷이 가끔 손실되는 현상이었다. 어떻게 해서든 문제를 찾아야 하는 상황이라 파이어월, 2, 3, 4계층 스위치 등 어느 구간에서 문제가 발생하는지를 찾기 위해 여러 명의 엔지니어들과 함께 일했다.
보통 패킷을 분석할 때는 예상되는 구간에 한 대 정도만 분석기를 설치하고 수집된 데이터를 기반으로 분석을 하는데, 이번에는 어떤 장비도 믿을 수가 없었기에 각 구간마다 분석기를 설치했다. 덕분에 당시 설치했던 패킷 분석기만 13대였다(사실 필자가 문제 해결을 위해 이렇게 많은 장비를 사용하기는 처음이었다). 이때 사용한 프로그램으로는 상용으로 가장 많이 사용되는 스니퍼(Sniffer)와 공개용으로 많이 사용되는 이더리얼(Ethereal)이었다.
대부분의 엔지니어들의 노트북에는 이 두 개의 프로그램이 탑재돼 있을 것이며, 어떤 시스템을 사용하는가는 그 구간을 담당하는 엔지니어가 익숙한 정도에 달려 있다.


패킷 분석기의 역할과 중요성
며칠 동안 필자가 뚫어지라 바라보았던 이더리얼과 스니퍼를 우리는 패킷 분석기(Packet Analyzer)라고 부른다. 이들 솔루션은 연결된 네트워크에 지나가는 패킷들을 하나도 빠짐없이 모두 그 형태에 맞게 나열해주고 일부 필요한 것들은 통계로 알려준다.
이 같은 패킷 분석기의 사용은 문제를 해결할 때 뿐만이 아니라 여러분들이 책으로만 봐왔던 네트워크 프로토콜의 실제 동작을 직접 확인하는 용도로 사용할 수도 있으며, 필요에 따라서는 책으로 소개되지 않은 애플리케이션(P2P, 바이러스, 메신저)에 대해서도 쉽게 파악하는 용도로 사용할 수도 있다. 




패킷 분석기는 (그림 1)처럼 확인하고자 하는 구간에 설치해 패킷들을 캡처하고, 관리자를 이를 기반으로 문제를 분석할 수 있도록 한다. 일반적으로 컴퓨터에서 어떤 통신이 안될 경우에는 해당 컴퓨터에 이더리얼을 설치하거나, 해당 컴퓨터가 설치된 같은 네트워크에 연결해 문제가 있는 컴퓨터와 동일한 동작을 해 볼 수도 있다. (그림 1)에서 무언가를 자세하게 들여보는 듯한 '돋보기’그림이 패킷 분석기라는 툴의 특징을 가장 잘 나타내준다고 할 수 있다.



(화면 1) 이더리얼 실행 화면


이더리얼과 같은 패킷 분석기의 값어치는 여러분들이 어떻게 사용하느냐에 따라서 하늘과 땅 차이가 난다. (화면 1)의 이더리얼 실행 화면에서 볼 수 있듯이 짧은 순간에도 상당히 많은 패킷들을 수집하므로 툴에서 제공하는 여러 기능을 적절히 이용할 줄 아는 지혜를 가져야 한다. 그렇지 않으면 짧은 내용의 패킷 분석을 위해서 너무 많은 시간을 들여야 할지도 모른다.
보통 이더리얼을 처음 접한 사람들은 모두들 큰 그림의 패킷 하나하나를 제일 깊은 단계로 생각을 한다. 하지만 패킷 분석기의 가장 중요한 특징은 바로 패킷 하나를 세부적인 단계로 나눠볼 수 있다는 점이다. (화면 1)을 보고 여러분들이 이미 알아차렸을 수도 있지만 대부분의 툴은 패킷 하나하나의 세부 구조와 정보를 우리가 알기 쉽게 번역해 주고 있다.



(화면 2) 웹에서 GET을 보낼 때의 패킷 세부 내용


그동안 필자의 연재를 지속적으로 보아온 독자라면 (화면 2)를 조금만 살펴봐도 별 어려움 없이 내용을 이해할 수 있을 것이다. (화면 2)는 여러 패킷도 아니고 단 하나의 패킷이다. 앞에서 설명했지만 패킷의 리스트(List)를 주욱 살펴봐서 어떤 패킷이 오는지 혹은 오지 않는지를 따지는 것은 아주 기본적인 사항이며, 나아가서는 이렇게 패킷 하나하나를 살펴서 어떤 정보들을 주고받았는지도 살펴야 한다(아마 시간이 지나면 여러분들도 그렇게 될 수 밖에 없다. 왜냐하면 때로는 패킷은 다 왔는데도 통신이 안 되는 경우가 있기 때문이다).


이더리얼 구하기와 설치하기
이더리얼은 공개용이므로 홈페이지(www.ethereal.com)에 가면 누구라도 손쉽게 구할 수가 있으며 설치 또한 간단하다. 하지만 주의할 점은 이더리얼 설치 전에 컴퓨터의 LAN 카드로 들어오는 패킷들을 이더리얼로 수집해 주는 Winpcap(www.winpcap.org)을 먼저 설치해야 한다. 이 프로그램이 없으면 이더리얼은 무용지물이다.
현재 제공되는 이더리얼의 버전은 0.10.12 버전이다. 맨 앞이 1이 아닌 0이어서 불안해 할 수도 있지만 이미 여러 차례 패치가 됐고 많은 사람들이 사용하고 있으므로 실무에 사용해도 전혀 손색이 없다(사실 상용 버전보다는 디자인 측면에서 많이 부족하지만 요즘 나온 버전에는 버튼에 아이콘들도 추가해서 제법 틀을 갖춰가고 있다).


 

<이더리얼의 탄생 과정>
이더리얼은 리눅스 계열에서 사용한 패킷 덤프(dump) 프로그램인 'tcpdump'라는 툴에서 시작됐다. tcpdump는 리눅스에서 시스템에서 처리하는 패킷을 다양한 옵션으로 볼 수 있는 명령어이고, 지금도 리눅스 애호가로부터 많이 사용되고 있는 명령어다. 이더리얼은 리눅스에서도 비슷한 화면구조로 돼 있는 GUI 프로그램이며, 필자가 설명하고 있는 것은 이것을 윈도우에서 사용할 수 있도록 돼 있는 프로그램이다. 이더리얼 홈페이지를 자세히 살펴보면 깜짝 놀랄 세 가지 일이 있다.
첫번째는 현재 이더리얼은 719개의 다양한 프로토콜을 지원한다는 점이다. 이 세상에 얼마나 많은 프로토콜이 존재하는지는 필자도 잘 모르지만, 실무에서 사용하고 있는 프로토콜 대부분은 아마도 이더리얼이 지원할 것이다(프로토콜이 지원된다는 것은 (화면 2)에서 처럼 해당 프로토콜을 세부적으로 사람이 볼 수 있도록 정리할 수 있다는 의미다).
두번째로는 정말 다양한 플랫폼에서 동작한다는 점이다. 리눅스(레드햇, SuSE, 데비안, PLD, ROCK, SCO, 슬랙웨어), 솔라리스(인텔, 썬), Irix, FreeBSD, OpenBSD, AIX, HP-UX, 맥킨토시 등 다양한 플랫폼에서 동작한다. 아마 어떤 상용 툴도 이렇게 다양한 플랫폼은 지원하지 못할 것이다.
그리고 마지막 특징으로는 NAI의 스니퍼, 썬의 Snoop, 쇼미티의 Surveyor, AIX의 iptrace, 마이크로소프트의 Network Monitor, HP-UX의 nettl, 와일드패킷의 Etherpeak 등 여러 상용 패킷 분석기에서 캡처한 것들도 볼 수가 있다는 것이다. 이 같은 놀라운 특징들 때문에 이더리얼의 사용을 강력하게 추천하는 것이다.

 


필자가 이더리얼을 사용하는 법
스니퍼 사용시 캡처를 할 때 필터(Filter)를 사용하고, 캡처가 끝난 후에 필요에 따라서 캡처된 데이터에서 필터를 사용하는 방법을 사용했었다. 반면 이더리얼의 특징 중 하나가 캡처하는 화면을 실시간으로 보는 것은 기본이고, 필요에 따라서는 캡처하면서도 각종 옵션으로 화면에 나타나는 것들을 조절, 분석하는 데 많은 도움을 줄 수가 있다는 점이다. (화면 3)은 이더리얼에서 캡처를 시작할 때 화면이다.



(화면 3) 캡처를 시작할 때의 다양한 옵션       


이더리얼을 비롯한 대부분의 패킷 분석기는 캡처 시작 전에 몇 가지 기본적인 설정을 하고나서 시작한다. 여러분들이 처음 이더리얼을 구동했다면 제일 먼저 어떤 인터페이스에서 구동할 것인가를 결정해야 한다. 많은 사람들이 이 선택을 잘못하고 캡처를 시작하면서 되려 이더리얼이 몹쓸 툴이라고 흉보는 경우가 많다.
필자는 그동안 많은 시간을 투자해 실시간으로 캡처하면서 보는 습관을 들였다. 이 방법이 분석에 많은 도움이 돼 이제는 'Display Options'에 있는 두가지 옵션(화면 3에서 선택된)을 대부분 기본으로 사용한다. 이 옵션을 사용하면 실시간으로 캡처된 리스트들이 자동으로 업데이트되고, 리스트를 보여주는 스크롤이 자동으로 올라가 최근 패킷만 볼 수가 있다. 때문에 실시간으로 패킷을 보고 싶은 필자에게는 아주 유용하다.
(화면 3) 중 'Name Resolution'이라는 부분은 화면에 보여지는 MAC, IP, TCP/UDP 포트 등을 연관된 이름들로 확인해 보여주는 옵션이다. 예를 들면 MAC은 앞에 있는 3바이트가 제조사 MAC이기 때문에 이를 검색해 보여주고, IP에 해당하는 컴퓨터 이름(DNS 이름), TCP/UDP에 해당하는 프로토콜 이름(TCP 80 → HTTP)으로 보여준다.
하지만 이 옵션은 유용하면서도 실시간으로 많은 패킷들을 받아서 보여줘야 하는 프로그램 입장에서는 많은 부하를 줄 수 있다. 그러니 수십 Mbps 이상을 캡처할 때는 가급적 이들 옵션은 사용하지 않는 것이 낫다(하지만 실제로는 여러분이 직접 경험하고 느껴본 후에 어떤 것이 효율적일지 나만의 옵션을 찾는 게 중요하다).
만약 실시간으로 패킷을 볼 게 아니라 필요에 따라서 캡처했다가 나중에 분석할 계획이라면 파일로 저장하는 옵션을 선택하자. 네트워크에 발생하는 문제들은 이번에 필자가 일본에서 겪었던 것처럼 언제 어느때 발생할지 모르는 경우가 있기 때문에 그것 때문에 실시간으로 패킷을 계속 보고 있는 것이 때로는 바보같은 일이기 때문이다.
이 경우에는 패킷을 일정한 사이즈, 시간마다 자동으로 저장해 나중에 분석이 필요할 때 어느 파일부터 선택해서 분석해야 하는지에 도움이 될 수 있다. 그렇다고 내 컴퓨터의 하드디스크가 크다고 무조건 수 기가바이트로 남기는 것은 나중에 분석을 안하겠다고 하는 것과 마찬가지다. 더구나 윈도우도 그렇게 큰 크기는 읽지 못할뿐더러 수백 메가바이트나 되는 데이터를 프로그램에서 띄운다는 것은 절대 불가하다.


캡처 필터/화면 필터 잘 쓰는 사람이 고수
이더리얼의 고수는 바로 필터를 자유자재로 사용하는 사람이다. 우선 캡처 필터는 리눅스에서 tcpdump를 한번이라도 써 봤던 사람이라면 이해할 만한 문법으로 돼 있다. 처음에는 어려워 보이지만 조금만 이해하면 패킷의 다양한 옵션까지도 선택할 수가 있다.
캡처 필터는 수동으로 입력하는 게 기본인데, 잘 모르겠다면 (화면 3)에 있는 캡처 필터 부분을 클릭해 보면 기본 예제를 볼 수 있다. 물론 'Help'를 누르면 추가 예제 몇 개가 더 나올 것이다. 여기서 중요한 것은 단지 필터 한 개를 잘 사용하는 것이 아니라는 점이다. and, or, not 등의 다양한 연산을 잘 사용하는 것이 제일 중요하다. 다음은 이더리얼에 있는 예제들이다.


(1) 08:00:08:15:ca:fe 라는 MAC을 가진 호스트에서 사용하는 패킷만 캡처
ether host 08:00:08:15:ca:fe


(2) 192.168.0.10라는 IP 주소에서 오는 패킷이나 그쪽으로 향하는 패킷 캡처
host 192.168.0.10


(3) TCP 프로토콜 중 포트 80을 사용하는 패킷 캡처
tcp port 80


(4) IP 주소가 192.168.0.10이면서 TCP 포트 80을 사용하지 않는 패킷 캡처
host 192.168.0.10 and not tcp port 80


위의 예 뿐만이 아니라 arp, 출발지(src), 목적지(dst), 서브넷 기준으로도 구분이 가능하고 패킷사이즈에 따른 캡처도 가능하다. 이를 가장 잘 익힐 수 있는 방법은 메뉴얼을 읽거나 tcpdump 옵션을 찾아보는 것이다.


<not 사용을 습관적으로>
필터를 사용할 때 종종 사용하게 될 옵션 중 하나가 바로 'not'이다. 이 옵션은 말 그대도 어떤 특정 패킷들은 너무도 당연해서 볼 필요가 없을 경우에 이들을 제외한 나머지를 보고자 할 때 사용하는 옵션이다.

 

다음은 icmp를 이용한 웜인 웰치아(Welchia)가 한창 유행일 때 사용했던 옵션이다. 이 패킷의 특징은 icmp를 사용하는 핑(ping)을 이용해 불특정 네트워크를 검색하느라 라우터, 파이어월과 같은 IP 처리 장비들이 제대로 서비스를 못하는 문제를 야기시켰다. 하지만 이 패킷은‘92바이트'라는 패킷 사이즈를 가지는 게 특징이었으므로 단순히 icmp를 네트워크에서 분리할 경우에는 어떤 호스트에 문제가 있는지를 찾기는 쉽지 않았다. 하지만 툴을 제대로 사용할 줄 아는 사람이라면 다음과 같은 방법을 쓰면 어렵지 않게 구분해 낼 수가 있다.



(화면 4) 필터 4. icmp and ip[2:2] = 92


(화면 4)의 옵션인 'icmp and ip[2:2] = 92'를 살펴보자. 무슨 암호문자 같기도 하지만 해석해 보면 그리 어렵지는 않다. 우선 icmp이면서 ip[2:2] = 92라고 했다. ip는 ip인데 뒤에 [2:2]라는 이상한 옵션이 추가됐다. 여기서 앞에 숫자 2는 바이트 단위로, IP 헤더 중에 2바이트인 16비트 뒷부분을 가리킨다. 그리고 뒤에 숫자 2는 거기부터 다시 16비트이다. 바로 그 값이 92인 것을 가리킨다. 물론 한번에 이해하기가 쉽지는 않을 것이다. 사실 필자도 처음 이 옵션을 찾아냈을 때 한참을 해매야만 했다.




(그림 2)는 IP 헤더의 구조다. 이제 앞에서 설명한 2:2의 비밀을 파헤쳐 보자. (그림 2)에서는 32비트 단위로 구분을 했고 그중에 헤더의 16비트, 즉 2바이트 뒤가 바로 패킷의 길이를 나타내는 부분이다. 그리고 길이를 나타내는 필드는 전체 16비트, 즉 2바이트 안에 표기하게 돼 있다. 그 길이가 92인 값을 찾아달라는 것이다. 이제 이해가 좀 될 것이다. 아직도 이해가 안된다면 지난 3월호에 연재했던 IP 헤더에 대해 다시 한번 공부하자.
실시간으로 패킷을 볼 때면 리스트가 너무 빨리 지나가기 때문에 사실 이더리얼에 능숙한 사람이라도 보고 싶은 부분만을 잡아내기란 쉽지 않다. 그래서 이더리얼은 실시간 패킷을 볼 때 바로바로 필터를 적용할 수 있는 방법을 제공한다. 이 필터의 장점은 캡처 필터에서 사용된 옵션대로 필터는 하고 있지만, 화면에 보이는 부분만 다시 필터를 사용할 수가 있다는 것이다. 그렇기 때문에 다시 화면 필터를 제거하면 캡처 필터에서 사용했던 필터대로 나타나게 된다. 가장 중요한 것은 실시간 작업을 할 수가 있다는 점이다. 그런데 이 필터 옵션은 캡처 옵션과는 조금 다른 형태를 갖고 있으므로 여러분들이 조금만 신경 쓰면 어렵지 않게 좋은 결과를 얻을 수가 있다.



(화면 5) ICMP/TCP 등이 혼재돼 있는 캡처


(화면 5)처럼 몇가지 필터를 사용해 캡처를 시작했는데 캡처 중에 icmp만 보고 싶다면 캡처를 정지할 필요가 없다. 그냥 화면 필터 부분에 (화면 6)과 같이 해주면 된다.



(화면 6) 실시간 캡처 중 다시 icmp만 화면 필터 적용


(화면 6)은 icmp만을 다시 보기 위해서 적용한 화면 필터로, 필요한 부분을 봤다면 삭제하거나 'Clear'라고 하면 원래의 필터대로 보이게 하는 옵션이다. 실시간으로 되는 것도 제법인데 이 옵션은 아주 마음에 드는 기능 중 하나다. 이 필터를 제대로 쓰고 싶다면 옆에 있는 Expression 메뉴를 클릭해 보자. 필자가 웰치아(Welchia)를 이더리얼로 캡처할 때 사용한 옵션이 화면 필터에서는 (화면 7)과 같이 Expression 옵션에서 사용할 수가 있다.




(화면 7) 화면 필터의 Expression



(화면 8) IP 전체 길이 92바이트 선택


 

(화면 8)은 Expression의 IP 부분만 선택해서 세부적인 필드 중에 길이(Length) 필드의 값이 92인 것을 선택한 화면이다. 이처럼 여러분들이 프로토콜에 대한 세부지식(헤더구조 등)만 안다면 얼마든지 많은 옵션을 사용해 정말 분석다운 분석을 할 수가 있다.
이번에는 컬러 필터라는 것을 한번 사용해 보자. 컬러 필터는 화면 필터와 같은 옵션이지만 여기에 색(Color)을 겸비해서 사용하는 방법으로 가독성을 한층 올려주는 방법이다. 메뉴의 'Color Rules'를 선택해 필터와 보여지는 색깔을 선택하면 된다. 배경색깔과 각 패킷 리스트의 글씨를 구분할 수 있다.



(화면 9) TCP 패킷들의 색깔만 필터 적용


이 기능은 단순히 화려하게 보이려는 목적보다는 패킷을 조금 더 빨리 분석하기 위한 이더리얼의 배려이니 자신만의 컬러 필터를 만들어 별도로 저장해 보자.


캡처하는 방법에 대한 고민
앞에서 필자가 거짓말을 조금 보태서 이더리얼을 이용하면 많은 것을 할 수 있고 마치 이더리얼을 사용할 줄 알면 패킷 분석의 고수가 될 것 같은 인상을 풍긴 듯 한다. 그런데 사실은 정말 그렇다. 
패킷분석기를 이용해도 별로 소용이 없다라고 말하는 이들 중 대부분은 필자가 아는 한 잘못된 방법이나 위치에서 패킷 분석기를 이용해 캡처를 하는 경우다. 때문에 정작 문제가 발생했을 때 중요한 패킷을 캡처하지 못한 경우가 많다. 사실 이번 출장에서 필자가 문제를 해결할 때도 그런 면이 없지 않았다. 하지만 너무 많은 구간에서의 문제 발생 가능성이 있을 때는 어쩔 수 없기도 하다. 생각해 보라. 패킷 분석을 위해 수십 대의 분석 툴을 설치한다는 것이 쉬운 일인가. 더군다나 캡처는 캡처지만 이후에 이 패킷을 모두 분석해야 하는데, 캡처한 데이터가 수백 기가바이트가 넘어간 시점에서는 정말 암담할 뿐이다. 하지만 조금만 신경쓰면 이 같은 문제들은 조금씩 줄일 수는 있다.


이더리얼을 어디에 연결할까
문제가 생긴 네트워크에 그냥 이더리얼이 설치된 컴퓨터를 연결한다고 해서 모든 패킷이 캡처되는 것은 분명 아니다. 그렇기 때문에 어떻게 연결해야 할까를 이제는 고민해야 한다.
만약 내 컴퓨터만의 문제라면 별 문제없이 내 컴퓨터에서 구동만 하면 되지만 네트워크의 다른 호스트의 문제나 다른 패킷을 분석하려면 모든 패킷이 지나다니는 구간을 선택해야 한다. 가장 좋은 방법이 스위치에서 업링크 포트를 미러링하는 방법이다. 이 방법을 사용할 경우, 스위치에서 간단한 미러링 설정만 하면 된다.




가끔 어떤 작업자는 미러링 설정을 할 때 특정 패킷(In or Out)만 선택하는 경우가 있는데, 이런 부분을 선택할 때도 신중하게 내가 얻고자 하는 데이터와 관련이 있는가를 고민해야 한다. 특별한 문제가 없다면 In/Out을 모두 선택하는 방법을 추천한다. 참고로 일부 스위치들은 미러링을 받는 포트에 연결된 컴퓨터가 다른 포트와의 통신이 안되는 경우가 있다. 이 경우 패킷 분석기가 설치된 컴퓨터를 원격에서 제어하는 방법도 고민해야 한다. 그래서 가끔 LAN 카드를 하나 더 연결하는 경우도 있다.
그리고 필요에 따라서는 외부와 연결되는 한 포트만 미러링을 해도 되지만 N:1처럼 여러 포트를 미러링해야 할 필요가 있을 수도 있다. 가급적이면 유연한 생각을 갖고 운영할 수 있도록 한다.
가끔씩 미러링이 현실적으로 불가능할 경우에는 탭(Tap)이라는 장비를 사용하는 경우가 있다. 이 장비를 연결되는 두 장비 중간에 연결해 필요한 패킷들을 패킷 분석 툴이 설치된 컴퓨터로 보내줄 수가 있다. 이런 장비들은 요즘 한창 사용되고 있으므로 알아둘 필요가 있다.

탭이나 미러링 장비를 연결할 때 가끔 연결에만 신경을 쓰다가 포트 설정(Speed/Duplex)을 무시하는 경우가 있는데, 이 부분도 신경을 쓰도록 하자. 포트 설정이 잘못될 경우 패킷 분석기와의 통신에서 패킷이 손실될 수가 있기 때문이다. 이 같은 사소한 문제로 거사를 놓칠 수는 없지 않겠는가?
스위치에서 미러링을 하는 이유는 스위치는 허브와 달리 입력되는 패킷이 모든 포트로 전달되지 않기 때문이다(사실 이것이 바로 스위치의 장점이다). 그래서 모든 패킷을 받지 못하기 때문에 미러링을 사용하는 것이다. 그래서 가끔 역으로 입력되는 모든 패킷을 모든 포트로 전달하는 허브를 이용하기도 하는데 개인적으로는 이 방법은 권장하지 않는다. 대량의 트래픽 처리시 아무래도 허브의 성능 부족이나 충돌(Collision) 등으로 인해 분석에 혼란을 야기 할 수도 있기 때문이다.


컴퓨터의 시간을 맞추자
이더리얼을 사용할 때 정말로 신경써야 할 게 한가지가 더 남아 있다. 그것은 바로 컴퓨터의 시간을 맞추는 일이다. 지난호의 시스로그 서버도 그렇지만, 이들이 남기는 시간은 모두 컴퓨터에 맞춰진 시간을 기준으로 한다.
문제가 다시 발생했을 때 분석시 정말 사소한 시간 문제로 고민하지 않으려면 컴퓨터의 시간을 주변의 장비들과 동일하게 맞춰야 한다. 가장 좋은 방법은 NTP(Network Time Protocol)를 사용하는 방법인데, 대부분 관리가 가능한 장비들은 NTP를 지원하므로 윈도우용 NTP 클라이언트를 인터넷에서 찾아 시간을 동기화하자.
혹시라도 이런 작업이 귀찮아서 나중에 분석할 때 시간차를 계산해서 하겠다는 생각은 아예 버려라. 잠깐의 귀찮음이 여러분들의 시간과 실력을 한참 퇴보하게 만들것이다. 패킷분석은 때로는 초 단위 보다 더 세분화해서 하는 경우가 있으니 부디 필자의 조언을 듣기 바란다.
이더리얼은 공개용이지만 정말 추천할 만한 툴이다. 사실 필자도 처음에는 불법(?) 스니퍼를 사용하면서 패킷 분석기를 접해봤지만 지금은 대부분의 경우 이더리얼을 사용한다. 물론 업무용도로 말이다. 매달 쓰는 원고 내용마다 중요하지 않은 것은 없지만, 이번에 소개한 패킷 분석기는 정말로 그 어느것보다도 중요하다는 점을 강조하면서

Posted by 1010
90.개발관련문서2008. 8. 12. 11:45
반응형
Welcome  

Welcome to Google Doctype. Written by web developers, for web developers.

What do you want to learn today?

Read HOWTO articles on

Dive into DOM objects, including

Style your pages with CSS

Do you need help with an HTML element? We have an HTML reference from <a> to <xmp>.

Posted by 1010
90.개발관련문서2008. 8. 12. 11:06
반응형
퍼온곳 : http://kwon37xi.egloos.com/ 

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

초보 Java 웹 개발자들을 위한 학습 로드맵
OKJSP에 자주 가서 요즘 자바 개발자들이 어떻게 살아가나를 보는 편인데, 아주 많이 반복적으로 올라오는 질문이 "대체 뭘 공부해야 하나요? 프레임워크는 Spring을 해야 할까요? iBATIS를 해야 할까요?" 하는 식의 질문들이다(이 질문은 사실 말이 안된다. 왜 그런지 읽다보면 나온다).

Java는 웹 관련 프레임워크들이 너무 다양하고, Ruby나 Python 같은 경우에는 RubyOnRailsDjanog 처럼 하나의 프레임워크 안에 기능별 프레임워크들도 모두 다 All in one 형태로 들어 있어서 혼란을 주지 않는 반면, Java는 각 영역별로 프레임워크가 모두 다르고, 또한 각 영역별로 존재하는 프레임워크들의 종류도 많아서 초보 개발자들에게 극심한 혼란을 주고 있다.

그래서 나름대로 Java Web 개발자들을 위한 학습 로드맵을 정리해 보았다.

1. Java 그 자체
많은 웹 개발자들이 마치 JSP 코드를 짤 줄 알면 그걸로 Java 웹 개발을 할 줄아는 것이라 생각하고 Java 그 자체를 소홀히 하는 것을 본다.
말도 안되는 소리이다. Java를 모르고서 Java 웹 개발을 제대로 한다는 것은 어불 성설이다. Java 그 자체를 먼저 공부하라.

특히 Java 5 문법을 숙지하길 권한다. 이제 우리나라도 점차 Java 5가 대세가 되어 가고 있다. 대부분의 프레임워크들과 WAS(JSP와 서블릿을 구동하는 서버)도 모두 Java 5를 기준으로 바뀌었으며, JVM 자체도 버전이 높을 수록 성능이 더 좋다.

2. JSP와 Servlet 그리고 Model 1
모델 1은, JSP 하나에 DB에 접속해서 쿼리를 날리는 등의 모든 업무적인 기능(Business Logic)을 넣고, 그 아래에 HTML 코드를 박아 넣는 식으로 개발하는 것을 의미한다.
아직도 많은 개발자들이 여기에 길들여져 있는데, 일단 JSP 자체에 대한 기본기를 익힌 뒤로는 재빨리 버려야 할 습관이다.

그리고 많은 개발자들이 Servlet을 무시하고 JSP만 하는 것을 보곤 하는데, Servlet에 대한 학습이 제대로 이뤄지지 않으면 더 나은 웹 개발이 곤란하다. Servlet에 대한 기초 개념을 확실히 잡길 권한다.

3. Model 2 - 프레임워크의 등장
JSP로 열심히 개발을 하다보니 프로젝트 규모도 커지기 시작하고, JSP 파일 크기도 수천줄에 달하는등 엄청나게 커진다.
그런데 이 JSP에다 두서없이 모든 기능을 다 때려 넣다보니 JSP마다 똑같은 기능들이 Copy&Paste로 들어가고, JSP 안에 들어 있는 Java 코드들에서 에러가 발생하면 찾아내서 디버깅 하는 일이 지옥같이 느껴지기 시작한다.

여기서 Model 2가 구원자로 등장한다.

Model 2는 말만 멋드러졌지 실제로는 간단한 개념이다.

JSP에서 수행하던 DB 쿼리 등의 작업을 Servlet에게 넘겨주고 JSP에서는 오로지 화면 출력만 담당하는 것이다.

Servlet에서 DB 쿼리등 화면 출력과는 상관없는 비지니스 로직을 일단 먼저 모두 수행하고, 그 결과를 request.setAttribute("key",결과객체);로 담은 다음 JSP 페이지로 포워딩(forward)을 하면 JSP에서는 request.getAttribute("key")로 그 객체를 받아서 화면에 뿌려주기만 한다.
이런 업무 수행단/화면 출력단의 철저한 역할 분리가 Model 2이다.

여기서 이러한 각 역할을 "MVC - Model View Controller" 라고 한다. 그래서 Model 2는 MVC와 동일한 의미로 사용하기 도 한다. MVC의 의미는 공부하면서 찾아보라.

이게 뭐가 좋냐고? 개발 기간이 좀 길어지고 프로젝트 규모가 쬐끔 커지고, 기존 프로젝트를 유지보수를 해보면 얼마나 좋은지 몸소 뼈져리게 느끼게 된다.

Model 2의 기능을 정형화해서 쉽게 구현하게 해주는 것이 MVC Framework들의 역할이다.
가장 유명한 Model 2 웹 프레임워크들은 다음과 같은 것들이 있다.

* 스트럿츠 1 - Struts 1
* 스트럿츠 2 - Struts 2
* 스프링 MVC - Spring MVC
* 기타 덜 유명한 Wicket, Stripes, JSF, Tapestry 등.

Struts 1은 MVC의 효시라고 할 수 있다. 우리에게 MVC라는 축복을 주기는하였으나, 나온지 오래된 만큼 낡은 개념들이 많이 녹아있고 쓸데 없이 복잡하고 배우기도 어려운 편이다.

오히려 Struts 2와 Spring MVC가 더 배우기 쉬울 것이며, 개발도 더 쉽다. 현재 추세는 Struts 2와 Spring MVC이다. 대형 포탈이나 SI 업체들도 Spring/Struts 2를 주로 채택하는 추세로 가고 있는 것으로 알고 있다.

둘 중 하나의 개념만 확실히 이해해도 다른 것을 배우는데 어려움이 별로 없으므로 그냥 둘중에 골라서 배우길 권한다. 나는 Spring을 선호한다.

그리고 MVC 프레임워크를 사용하기 시작하면서 View를 만드는 JSP에 대해서도 재조명이 시작된다. 기존에 Java 코드를 JSP에 직접 넣던 관행을 버리고 JSTL과 태그 라이브러리를 사용하거나 아예 JSP를 버리고 다른 템플릿 엔진으로 만들기도 한다. 이에 관해서는 맨 마지막에.

4. 퍼시스턴스 프레임워크 : JDBC 반복 작업에 짜증이 나기 시작하다.
현대 웹 개발에서 가장 큰 역할을 차지하는 것은 뭐니뭐니해도 단연 Database 작업이다.
지금까지는 아마도 JDBC에서 DB 커넥션을 맺고, 쿼리를 날리고 그 결과 ResultSet을 JSP로 넘겨주어서 출력하는 식으로 했을 것이다.
이미 다들 알고 있겠지만 JDBC를 사용하면 똑같은 코드가 굉장히 많이 반복해서 나온다. 한마디로 "삽질"의 전형이 JDBC 작업이다.
이것을 깨달은 많은 개발자들이 조금 어정짱하게 반복작업을 해결해주는 Util 클래스들을 프로젝트별로 만들어서 사용하곤 한다.
하지만, 물론 이에 대해 정형화하고 깔끔하고 훨씬 더 사용하기 쉬게 만들려는 노력이 이미 수년에 걸쳐 이루어졌다.

이렇게 DB관련된 작업을 정형화한 것들을 Persistence Framework 라고 한다.

* 아이바티스 - iBATIS : SQL Mapper - JDBC보다 더 쉽게 배우고, 더 편하게 사용한다.
* 하이버네이트 - Hibernate : 객체지향을 객체지향답게, 개발 기간을 엄청나게 단축시켜주다.

퍼시스턴스 프레임워크의 양대 산맥은 iBATIS와 Hibernate이다. 이 둘 모두 우리나라에 책이 나와 있다.
iBATIS는 SQL Mapper의 한 종류이고, Hibernate는 ORM의 한 종류이다.

이 둘의 차이는 iBATIS는 개발자가 SQL 쿼리를 직접 작성한 것을 객체에 매핑시켜주는 것이고, ORM은 DB 스키마와 객체간의 관계를 설정파일로 만들면 자동으로 쿼리를 만들어주는 것이다.

자, 이 둘을 보면 미국에서는 Hibernate가 인기가 좋고, 우리나라에서는 iBATIS가 사실상 SI 업계를 평정했다.
그러니까, 일단은 우리나라에서는 iBATIS를 공부하면 된다고 보면 된다.

이렇게 말하니까 마치 이 둘이 경쟁자 같은데, 사실 이 둘은 경쟁 상대라기 보다는 보완해주는 역할을 한다. SI에서 처럼 DB 테이블이 정규화 되어 있지 않은 경우에는 Hibernate같은 ORM을 사용하면 프로젝트를 말아먹을 수 있다.

iBATIS는 테이블 정규화에 무관하게, 개발자가 작성한 SQL을 객체로 매핑하기 때문에 DB 스키마가 마구 꼬여 있는 상황에서도 유연하게 작동하고, 개발자가 직접 SQL 튜닝을 할 수 있다는 장점이다.

그리고 Hibernate는 배우기가 굉장히 어려운 프레임워크이고 튜닝이 매우 어렵다. Hibernate책을 보면 캐싱을 통해 성능을 향상시키라고 하지만 캐싱은 iBATIS도 못지않게 잘 지원한다. 하지만 일단 배우면, 그로인한 코딩 생산성이 iBATIS가 감히 넘볼 수 없을 정도록 급격히 향상된다.

Hibernate는 DB 정규화가 잘되어 있는 웹 포탈 업체나 패키지 소프트웨어 제작시에 강력히 권장할만 하다.

5. IoC와 DI - 객체의 생성주기와 의존성을 관리하고 싶어지다
사실 내가 경험한 SI를 보면 4단계 까지만 가도 막장은 아닌 프로젝트라고 본다. 아직도 신규 프로젝트를 하면서도 Model 1에 JDBC로 코딩하는 것을 많이 보았기 때문이다.

앞서, MVC라는 형태로 웹 애플리케이션의 역할을 철저하게 분할해서 처리하라고 했었다.

이제 여기서 좀 더 역할을 분할하기 시작한다.

Database를 관장하는 코드(DAO)와 Database 처리 결과를 가지고 그외 비지니스 로직을 추가로 수행하는 코드(Service), 그리고 웹으로 들어온 요청을 받아서 비지니스 로직을 호출하고, 그 결과를 다시 웹(HTML 등)으로 내보내는 코드(Controller)로 분할을 하면 유지보수가 더 쉽고, DB가 Oracle에서 DB2 로 변경되는 식의 중대 변화가 있을 때도 DAO만 바꾸면 되는 식으로 변화에 대한 대처가 유연해 진다는 것을 깨닫기 시작한다.

이제는 각 역할별로 클래스를 분할하고 컨트롤러 객체는 서비스 객체에 서비스 객체는 DAO 객체에 의존해서 작동하도록 코드를 바꾸기 시작한다. 그리고 객체의 생성과 파괴 주기도 관리해야만 하게 된다. 객체를 하나만 생성하면 되는데 불필요하게 매번 new를 할 필요는 없으니까.

이렇게 객체의 생성/파괴 주기를 관리하고 객체간의 의존성을 관리해주는 프레임워크를 IoC 컨테이너라고 부른다.

1. Spring Framework
2. EJB 3.0

사실상 대세는 Spring Framework로 굳어졌다. EJB 3.0은 내가 안써봐서 뭐라 말은 못하겠다.

Spring MVC는 이 Spring Framework의 일부분이다.

Spring은 또한 AOP도 지원한다.

AOP 의 개념이 상당히 어려운 편이라서 개념 자체를 확실히 한마디로는 표현하지 못하겠다. 어쨌든 개발자들에게 가장 쉽게 다가오는 표현으로 하자면, AOP는 동일한 패턴으로 반복적으로 해야하는 일을 설정을 통해 자동으로 해주는 것이다.
이에 관한 가장 보편적인 예가 바로 트랜잭션이다.
지금까지는 아마도 비지니스 로직이 시작될 때 트랜잭션이 시작되고, 비지니스 로직이 끝날 때 트랜잭션을 종료하는 코드를 매번 작성해서 넣었을 것이다.
AOP를 사용하면, 비지니스 로직의 역할을 하는 메소드가 무엇인지 설정파일에 넣어주기만 하면 자동으로 메소드가 시작될 때 트랜잭션을 시작시키고, 메소드가 끝날 때 트랜잭션을 종료시켜준다. 물론 예외가 발생하면 트랜잭션을 rollback도 해준다. 따라서 Spring을 사용한 프로젝트에서는 트랜잭션 관련 코드를 볼 수 없을 것이다.

Spring 프레임워크는 기본적으로 IoC 컨테이너 역할을 하는 것이 핵심이다. 따라서 Spring을 사용한다고 해서 꼭 Spring MVC를 사용할 필요는 없다. Struts 2 + Spring + iBATIS 나 SpringMVC + Spring + Hibernate 등... 어떠한 조합이라도 가능하다.

6. 그 외
◈ Template Engine : JSP 보다 더 간결하면서 강력한게 필요해!
   * JSP + JSTL : Sun이 지정한 산업표준이다. JSTL은 당연히 쓰고 있으리라 믿는다.
   * Freemarker : 가장 권장할 만하다.
   * Velocity : 굉장히 배우기 쉽다. JSTL보다 더 빨리 배워서 쓸 수 있다. 가독성도 좋다. 그러나 Freemarker 만큼 편하고 강력하지는 못하다.
많은 사람들이 Java 웹 개발을 그냥 "JSP 개발"이라고도 부르는데, MVC가 도입되고, Freemarker 같은 다른 템플릿 엔진을 사용하게 되면 더이상 JSP는 코빼기도 안보이게 된다. 그러므로.. JSP 개발이라는 말은 쓰지 않았으면 좋겠다.

◈ Layout Engine
   * Sitemesh : 헤더 푸터 처럼 동일 패턴이 반복되는 레이아웃을 관리해준다.

◈ XML 도우미 : W3C DOM은 너무 어렵고 난잡하다. 좀 더 편한 XML관련 개발을 원한다면..
   * JDOM : Java 표준으로 지정됐다고 한다.
   * DOM4J
둘 다 비슷하게 편한거 같다. 예전엔 JDOM을 썼었는데, 나 같은 경우 현재 프로젝트에서는 DOM4J를 사용한다. Hibernate가 DOM4J를 사용하기 때문에, 별도의 라이브러리 더 넣는게 귀찮아서.

◈ 단위 테스트
   * jUnit : 코드를 철저하게 테스트하자.

◈ 소스코드 버전관리
   * CVS
   * Subversion : 현재 대세는 Subversion
내가 최고 막장으로 꼽는 프로젝트는 아직도 FTP로 소스 관리하는 프로젝트이다. 이런 프로젝트에는 절대로 참여하지 않을 것이라고 굳게 맹세하고 또 맹세했다. --;
소스 코드 버전관리는 여러 개발자들이 동시에 개발할 때 소스코드를 저장하고 충돌을 관리해주며, 소스 변경 내역을 계속해서 추적해서 과거 소스로 언제든지 돌아갈 수 있도록 도와준다.
현재 대세는 Subversion이지만 CVS로도 버전관리의 이점을 충분히 만끽할 수 있다. 그리고.. 사실 CVS가 사용법을 익히기는 더 쉽다.

◈ 자동 빌드
   *
Ant : Ant 면 만사 Ok!
   *
Maven
아직도 javac 로 컴파일하고 있고, FTP로 파일 올려서 복사하고 있다면.. 이 모든일을 자동으로 명령 한방에 처리하도록 해야 실수도 적고, 퇴근도 일찍한다.
Ant로 빌드와 배포를 자동화 하자.

결론

내가 권하는 조합은
* SI 업체에서 일하는 경우 : Struts 2 혹은 SpringMVC + iBATIS + JSP/JSTL + 가능하다면 Spring Framework
* 웹 포털등과 같은 업계, 패키지 소프트웨어 제작 업체 : Struts 2 혹은 Spring MVC + Hibernate + Spring Framework + Freemarker + Sitemesh
Posted by 1010
90.개발관련문서2008. 7. 25. 16:32
반응형
  1. XML관련[1]
  2. 자바이론[2]
  3. 성능 테스트[3]
  4. 정규표현식[4]
  5. JavaScript[5]
  6. Eclipse[6]
  7. 테스팅[7]
  8. 보안[8]
  9. Cygwin[9]
  10. 바인딩[10]
  11. Ant[11]
  12. Jakarta Commons Configuration[12]
  13. Java 예제코드[13]
  14. Quartz[14]


출처 : http://openframework.or.kr/Wiki.jsp?page=OpenSources

Posted by 1010
90.개발관련문서2008. 7. 24. 18:10
반응형

Main Page

From MDC

Image:Firefoxlogo2.png

Firefox 3 for developers

Firefox 3 has been released, and is now available for download. See Firefox 3 for developers for developer documentation.

The Truth About JavaScript

Get a sneak peek at upcoming changes to JavaScript from Brendan Eich.

Mozilla Developer Center contents

Topics

Mozilla internals

Application and component framework

Technologies

  • AJAX - Asynchronous JavaScript and XML
  • CSS - Cascading Style Sheets
  • DOM - Document Object Model
  • HTML - Hypertext Markup Language
  • JavaScript
  • NSS - Network Security Services
  • RDF - Resource Description Framework
  • RSS - Really Simple Syndication
  • SVG - Scalable Vector Graphics
  • XBL - Extensible Binding Language
  • XForms - XML Forms
  • XML - Extensible Markup Language
  • XML Web Services - SOAP, XML-RPC, etc.
  • XPath - XML Path Language
  • XSLT - Extensible Stylesheet Language Transformations
  • XUL - XML User Interface Language

Mozilla Developer Center


Mozilla Developer Center blogs

Mozilla Developer News

MDC Webwatch

Posted by 1010