본문 바로가기
나의 서재

[책]Code Craft 짧은 서평

by esstory 2008.03.09

Code Craft 코드 크래프트 - 10점
피트 구들리프 지음, 김윤명 옮김/한빛미디어


책 제목은 참 멋있다.

하지만, 700 페이지나 되는 엄청난 물량공세에 비해 구태의연한 사실의 나열과 반복이 500 페이지는 되어 보인다.

 

책의 내용을 꿰뚫는 멋진 서평은 희한하게도 같은 책 16페이지에 있는 “Code Craft에 보내는 찬사에 적혀 있는 아래 글이다.


정확히 말해서 이 책은 아무 것도 모르는 신입사원에게 줘야 할 그런 종류의 책입니다. 진솔하고, 읽기 쉽고, 신출내기 프로그래머들이 알아야 할 주제들에 대해 광범위한 영역을 다루고 있습니다

신입 프로그래머들에게 어울릴법한 초보적인 내용의 글이 실제로 상당히 많아서 책의 반 정도를 혹시나 하고 읽다가 집어 치울까 여러 번 고민한 책 --;;

 

하지만, 나름 책을 읽다 보면, 책의 전체 내용도 중요하지만, 기억해야 할 몇 군데 좋은 글만 만나도 용서가 된다.

책의 후반부로 갈수록 읽을만한 내용이 조금씩 나오고 특히 부록으로 있는 답과 논의”(611 ~ 732p) 부분은 실무에서 접할 수 있는 여러 가지 질문에 대한 저자의 의견을 엿볼 수 있어 도움이 되었다.

 

개인적으로는 광범위한 실무 경험을 다룬 책자를 원한다면 이 책보다는 Code Complete2 를 권하겠다.  양도 많을 뿐더러 이 책보다 전달하는 내용의 깊이도 상당히 깊어 도움이 많이 되는 책.

 

책에서 건진 내용들 


최적화가 절대적으로 필요하다는 사실이 증명되지 않으면 최적화를 하지 않습니다.
(311p)

가끔 보면 최적화가 굳이 필요하지 않은 부분에 최적화를 위해 상당한 시간과 노력을 투입하는 사람을 만나게 된다.  최적화가 필요할 만큼 성능 이슈가 있는 부분인지, 시간대비 효과가 있는 지 물어 보고 싶다.

설계를 할 때는 언제든지 설계를 하는 부분보다 바로 다음으로 큰 콘텍스트 안에서 생각하고 설계하십시오 의자는 방안에서, 방은 집 안에서, 집은 주변 환경 안에서, 주변 환경은 도시 계획안에서  - 일라일 사리넨
(354p)

 그냥 봐도 멋진 말 ^^; 

객체지향 설계 - … OO 설계는 시스템에 있는 데이터들에 초점을 맞춥니다.
(355p)

당연한 얘기인데, OO 를 하면 할수록 데이터를 잘 다루는 것이 프로그램의 키가 된다는 것을 절감하고 있다. 요즘하고 있는 프로젝트에서도 데이터 클래스를 잘 설계하고 UI 부분과 격리시켜 두면 두고 두고 유지보수가 쉬어지고 데이터에 집중할 수 있게 된다는 것을 뼈저리게 느끼고 있다.


팀장이 결단력이 없고 무능력한 사람이면 그렇게 하려고 한 것이 아닌데도 민주주의를 하게 될 수 있습니다. 그런 어눌한 팀장은 알아차리지 못하는 사이에 서서히 존재가 희미해질 수 있습니다. 팀원들이 좌절해서 그의 역할을 공동으로 대신하게 됩니다
(445p)

팀장의 능력은 참 중요하다. 관리자로서의 역할 못지 않게 기술적 리더를 겸해야 하니 사실상 슈퍼맨이 되어야만 가능한 게 현실의 관리자 같다. 중소기업에서는 거기에다가 영업까지 해야 하는 경우도 많은데 다행히 아직 영업은 시키지 않을 것 같고, 그럴 능력도 없다. 어디나 마찬가지지만, 올라갈수록 월급 많아지는 것 말고 좋아지는 것 하나도 없다. 

실무적인 추정방법

1.     일을 가능한 한 가장 작은 덩어리로 나누십시오. 이것은 시스템 설계의 실질적인 첫 번째 패스에 해당합니다.

2.     이해하기 쉬운 적당한 크기의 구성요소들로 잘게 나누었으면, 각각의 덩어리에 대해 맨-아워 또는 맨-데이 단위로 소요시간을 추정하십시오.

3.     각각의 소요 시간을 모두 추정하고 난 다음에는 그것을 연속해서 늘어놓고 기간을 합산하십시오. 그러면 자 보세요, 바로 소요 시간이 측정되었습니다.

(542p)

개발 일정을 산출해 내는 건 아직도 내가 잘 못하는 분야이다. 그냥 막연하게 2, 1개월, 2개월씩으로는 나오는 데 세세한 계획은 잘 못 잡겠다. 보통은 기간을 미리 정해놓고 일을 거꾸로 맞추고 있는데, 제대로 된 일정 추정을 나도 한번 해 보고 싶다. 

여러 가지 동작의 상대적인 비용에 대해서 대략적으로 생각해 보는 것은 유용한 일입니다. 프로세서가 인스트럭션 하나를 실행하는데 1초가 걸린다면, 전형적인 함수의 호출은 3-4초 걸릴 것이고, 가상함수의 호출을 10~30초 걸릴 것이고……
(673p)

가상함수 호출비용이 저렇게 높다고? 가상함수를 즐겨 쓰는 1으로써 충격.. 내일 당장 테스트 한번 해 봐야겠다. 


효과적인 팀워크를 위해서는 다음과 같은 모든 요소들이 제자리를 잡아야 합니다. :

사람들의 올바른 분포

여러 가지 다양한 경험을 갖춘 팀원들

팀원들의 성격 유형은 서로를 보안해야 합니다.

명확하고 현실적인 목표

동기

가능한 한 빨리 제공되는 적절할 규격서

훌륭한 관리

가능한 만큼 작지만 현실적인 크기이고, 그보다 더 작지는 않은 팀

팀이 따를 명료하고 보편적인 소프트웨어 엔지니어링 프로세스

회사로부터의 후원

(701p)

효율적인 팀 관리를 위해 역시 관리자가 해야 할 몫이 상당히 많다. 부족한 점이 너무 많아 언제나 열공이 필요한데, 요즘은 블로깅에 너무 많은 시간을 뺏기는 듯.

 

 

태그

댓글8

  • BlogIcon 오스카 2008.03.10 14:53

    가상 함수 호출 비용이 분명히 있긴 하죠. ㅋ
    Visual Studio 2005에서 간단하게 가상함수 하나 만들어서 어셈 코드를 보면

    비가상 함수
    00411B1B mov ecx,dword ptr [this]
    00411B1E call TestObject::TestCall (4111D6h)

    가상 함수
    00411B23 mov eax,dword ptr [this]
    00411B26 mov edx,dword ptr [eax]
    00411B28 mov esi,esp
    00411B2A mov ecx,dword ptr [this]
    00411B2D mov eax,dword ptr [edx+4]
    00411B30 call eax
    00411B32 cmp esi,esp
    00411B34 call @ILT+600(__RTC_CheckEsp) (41125Dh)

    인스트럭션 개수 자체가 일반적인 비가상 함수 콜에 비해서 4배 차이니...
    물론 실제 런타임 시에 cpu 사이클을 얼마나 잡아먹을지는 여러가지 요인이 있지만 그건 제쳐 놓고. -0-
    답글

    • BlogIcon esstory 2008.03.10 15:11 신고

      엇 오스카님 벌써 테스트 해 봐 주셨네요..
      이래 저래 불려다니고 하느라 아직 쳐다도 못보고 있는데 --;;

      프로그램 종속성을 최소화하려고 대부분의 호출을 인터페이스를 사용해서 가상함수 호출 방식으로 처리하고 있는데 저정도면 호출 오버헤드가 꽤 크네요.. 아 이런...

  • BlogIcon object 2008.03.11 12:17

    가상함수는 컴퓨터 구조엣 아주 다루기 힘든 녀석 중 하나입니다. 일반적인 함수들은 오스카님이 올려주신 코드처럼 그 주소가 컴파일 시간에 명확하게 구분이 됩니다. 따라서 CPU가 명령을 처리할 때도 어디로 점프해야할지 바로 알 수 있죠. 그러나 가상함수는 어디로 뛸지 그 타겟을 런타임에야 알 수 있습니다. 코드에서 보듯이 vtable을 얻어오는 과정이 있죠. 그런 뒤에 call eax로 레지스터에 계산된 주소 값으로 점프를 합니다. 이걸 이른바 indirect branch라고 하는데 이 녀석들은 branch target을 예측해야하기 때문에 쉽지 않습니다. 가상함수 호출에 들어가는 비용만 줄여도 성능 향상을 많이 얻을 수 있죠. 그래서 VC++ 2005 컴파일러 기능 중, 프로파일을 통해 자주 불려지는 가상함수로 최적화를 할 수도 있습니다. 단순히 인스트럭션의 개수가 4배 많은 것 보다도, call eax를 CPU가 해석할 때, 그 다음 명령어를 어디서 가져와야할지 알 수가 없으므로 성능 저하는 훨씬 클 수 있습니다. 자세하게 설명하려면 엄청나게 길어져서 이쯤에서 그만 -_-;

    여담으로 MFC의 그 엄청난 메세지 맵의 이유가 virtual 함수를 줄이기 위해서였습니다. 그냥 CWnd에 각종 On* 함수들을 죄다 virtual로 해버리면 되는데 이건 비용이 너무크죠. vtable의 물리적인 메모리 크기 뿐만 아니라 마이크로아키텍쳐 단에서의 처리 비용도 일반 direct jump보다 훨씬 큽니다.

    (제 지도 교수가 이런 indirect branch를 보다 효율적으로 처리할 수 있는 것으로 논문 잘 내고 교수하고 있죠 ㅎㅎ)

    언제 시간이 나면 왜 virtual 함수 호출 비용이 막대한지에 대해 좀 써봐야겠네요.
    답글

    • BlogIcon esstory 2008.03.11 13:16 신고

      오스카님에 이어 object 님까지. 고수분들이 모두 출동해 주셔서 저의 모자란 실력을 채워주시네요

      mfc 의 메시지 맵을 보면서 초기 설계의 실패작이 아니었을까 생각했었는데, 그런 사연이 있었군요 (정말 처음 알았습니다. 이런 정보는 어디서 구하나요.)

      그나저나 걱정이네요
      프로그램의 종속성을 줄이기 위해 철저하게 가상함수를 최대한 활용하고 있는데, 중요한 함수들은 가상함수 대신 이전처럼 메시지 호출방식이나, C 형식 함수로 호출하는 걸 고려해 봐야겠군요.. 뭔가 하나를 얻으면 하나를 잃는데, 늘 그게 문제입니다.

      자세한 설명 많이 도움되었습니다. 감사합니다.

    • BlogIcon object 2008.03.11 15:16

      한번 VS 2005의 Profile-guided Optimization을 활용해보세요. Virtual Call Speculation라는 것이 있습니다.

      MSDN에 가보시면 친절하게 예제코드까지 있습니다.
      Virtual Call Speculation. Virtual calls can be expensive due to the jumping through the vtable to invoke method. With PGO, the compiler can speculate at the call site of a virtual call and inline the method of the speculated object into the virtual call site; the data to make this decision is gathered with the instrumented application. In the optimized code, the guard around the inlined function is a check to ensure that the type of the speculated object matches the derived object.
      The following pseudocode shows a base class, two derived classes, and a function invoking a virtual function.

      코드를 보면 클래스 포인터가 대부분의 경우 특정한 타입이라면 그 경우를 특수하게 처리하는 것입니다. 가상함수가 아니라 바로 특정 타입의 함수를 부르는 것이죠. (코드를 보면 분기문이 하나 더 들어가서 최적화가 되는지 의문이 들겠지만, 매우 빈번히 이 분기문이 실행되고 그 결과가 항상 true라면 이건 분기예측기로 매우 정확히 예측할 수 있습니다. 즉, CPU는 바로 특정 타입의 함수를 호출해버리는 것이죠. 그러다가 나중에 예측이 틀리면 다시 복구 해줍니다.)

      물론 성능 향상이 얼마나 있을지는 모릅니다. 미미할 수도 있습니다. 그러나 가상함수호출이 많이 있다면 효과가 크겠지요 (최소 수% 증가는 있을겁니다)

      MFC 메세지 맵 이야기는 MFC Internal에서 본 것 같습니다. MFC 메세지 맵은 좀 복잡하고 ATL의 메세지 맵은 매크로 장난으로 매우 가볍게 구현하고 있습니다.

      http://projects.ece.utexas.edu/hps/pub/kim_isca07.pdf

      이 논문에서 Figure 1을 보면 재밌는 벤치마크가 있습니다. 각종 윈도우 어플에서 direct branch (그냥 일반적인 함수 call, for, while, if 등)과 indirect branch (가상함수, 함수포인터호출)의 비율을 보여주고 있습니다. 보다시피 분기문 중 많은 수가 이런 indirect branch이죠. 그래서 이 수를 줄이는 것은 효과를 가져다 줄 수 있습니다.

      그러나 모든 것에 tradeoff가 있는 법. 가상함수를 써서 그 만큼 하이레벨에서 추상화가 쉽고 그것이 유지보수에도 좋다면 어느 정도 성능 저하는 감내할만 하겠죠.

      그래서 결론을 내리면 가상함수는 꼭 필요할때만 쓰자... 정도가 되겠습니다 :)

    • BlogIcon esstory 2008.03.11 15:24 신고

      안그래도 object 님 블로그에서 Profile-guided Optimization 글을 보고 도움을 받았습니다. 단 저희 프로그램은 한번 배포하고 끝나면 좋은데, 매일 매일 모듈의 일부를 수정해서 새로 내려야 하는 상황이라, 적용에 문제가 있어서 다음번에 대대적으로 수정할 때 써 먹으려고 생각중에 있습니다 ^^;

      유지보수하는데 가상함수 구조만큼 좋은 게 없어서 너무 과신하고 있었는데 반대 급부도 생각할 수 있는 좋은 기회가 된거 같네요 감사합니다.

  • BlogIcon 오스카 2008.03.11 15:13

    메시지 맵의 실제 구현 배경을 알고 mfc의 초기 설계자(이름은 까먹었지만)가 참 대단한 사람이구나 했던 기억이 납니다. ㅎㅎ (물론 멋진 구조라는데 동의하진 않지만) object님 이야기도 그렇고, 가상 함수의 경우 인라인화 할 수도 없어서 메모리 지역성에 있어서의 성능 이득을 볼 수 없다는 것(캐시 미스가 와장창 발생할 수 있는;;;)은 꽤 큰 문제가 될 수 있습니다.

    사실 댓글을 더 달려다가 그냥 위의 코드만 남겼었는데요.. ㅎㅎ

    좀 더 개인적인 생각을 이야기하면, 남용하지만 않으면 가상함수가 훌륭한 설계를 위한 방법 중의 하나임에는 틀림 없습니다. 가상함수의 남용이라는 건 추상화 레벨을 너무 낮게 잡아버리는 거죠. 예를 들어, 60fps의 게임에서 캐릭터나 npc 메시의 개별 폴리곤을 그리는 함수를 가상함수로 잡는다면 이런 가상함수는 평균적으로 메시의 개수가 100개, 해당 메시의 평균 폴리곤이 5000개라고 할 경우 60*100*5000 번이라는 가상함수 콜이 일어나게 되고 이는 가상함수가 비가상함수에 비해 평균적으로 n배라고 할 경우 초당 n*60*100*5000 이라는 시간적 비용이 발생합니다. 엄청나죠;;;

    그러나 반대로 말하면 이런 경우를 피하기만 한다면 이 비용이 크진 않습니다. 왜냐하면 함수라는게 일반적으로 콜하는 비용에 비해 실제 함수 바디에서 실행하는 작업에 걸리는 시간(물론 이 시간도 가상함수이기 때문에 발생하는 추가 비용이 있긴 합니다만)이 훨씬 더 길기 때문에, 원론적인 결론이지만, 가상함수의 비용 발생이 전체 함수의 실행 시간에 대비해 프로젝트의 스펙 상 무시할 수 있는 수준이라면 설계적인 이득을 위하여 가상함수를 사용하는 것은 딱히 문제라고 생각하진 않습니다.
    답글

    • BlogIcon esstory 2008.03.11 15:31 신고

      아..오스카님. npc 메시지, 이런건 잘 모릅니다 ^^;; 넘 많은걸 기대하세요 ~
      주로 클래스의 내부 구현을 바깥으로 드러나지 않도록 하기 위해서 어플리케이션에서 사용하는 대부분의 공용 함수들을 interface 로 묶고 가상 인터페이스를 통해서만 접근하는 방법(거의 com 이랑 같죠) 을 사용하는데요..
      한줄 짜리 공통 함수들도 제법 많거든요. 이런건 배보다 배꼽이 더 클수도 있겠네요 --;;
      C++ 소스로는 일반 함수 호출이랑 별 차이 없어 보이는데.. 어셈이나, 기타 호출되는 실제 구현부는 차이가 너무 많네요.
      역시 이 쪽 세계는 알아야 할게 너무 많습니다.
      두분 다 감사합니다.