자동화된 소스 분석, 처리, 검증을 통한
소스의 불필요한 #if - #endif 제거하기
김이선
veblush@[nexon|gmail]
버블파이터
 BNB     카트라이더      프로토타입      에버플래닛      던전엔파이터     GTR
프로그래머   리드 프로그래머   리드 프로그래머   리드 프로그래머   테크니컬 디렉터   프로그래머




                   게임 프로그래밍 11년차
프로젝트 소스 코드의
유지보수성 (Maintainability)
      관리가 중요!
[던전엔 파이터] 에서 했던
    코드 관리 중 하나인
불필요한 #if - #endif 제거 작업
도입                  결과   결론
     분석   처리   검증
오늘 알아볼 코드
[던전엔 파이터]
“퀘스트 시스템”
  추가 작업
Flags.h




 1단계     // 김철수: 퀘스트 시스템 구현
플래그 정의   //#define _QUEST_SYSTEM
*

        #ifdef _QUEST_SYSTEM
          ui.questWnd.setButton(…);
 2단계      ui.questWnd.setEvent(…);
기능 구현   #else
          ui.questWnd.setDisable();
        #endif
Flags.h




   3단계   // 김철수: 퀘스트 시스템 구현
기능 플래그 켬 #define _QUEST_SYSTEM
작업 중인 코드가
  다른 작업자, 라이브 코드에
     영향을 주지 않음
(장기간 작업, 빠른 롤백, 이벤트)
국가별 소스 코드 공유에 도움
  (국가별 플래그, …)
간단한 작업 방식
(교육 비용이 낮음)
플래그 켬/끔이 Rebuild 를 초래
(Flags.h 파일을 모든 파일이 #include 하므로)
코드가 점차 읽기 어렵고 수정하기 어려워짐
(#ifdef - #endif 블록이 코드에 가득 차기 시작함)
코드가 점차 읽기 어렵고 수정하기 어려워짐
(#ifdef - #endif 블록이 코드에 가득 차기 시작함)
죽은 코드가 생겨남
(꺼진 플래그나 #else 에 묶엔 코드들)
그럼 플래그가 얼마나?
5400 개

+1500 /1yr
  2015 년
 10000 개
플래그 개수가 늘어남에 따라
 발생하는 추가적인 문제!
#ifdef _EVENT_2006
          ui.notice.setText(“월드컵 이벤트!”);
 문제 1
          ui.notice.setVisible(1);
죽은 코드
          ui.notice.setEventHandler(…);
        #endif
#ifdef _NEW_UI_COMPONENT
          ui.notice.setText(“…”);
          ui.notice.setVisible(1);
 문제 2
        #else
중복 코드
          set(UI_NOTICE, TEXT, “…”);
          set(UI_NOTICE, VISIBLE, 1);
        #endif
플래그 작업 방식은 유지하되
불필요하게 늘어난 플래그 개수를 줄여보자
불필요한 플래그 제거!
#ifdef _QUEST_SYSTEM
              ui.quest.setVisible(1);
플래그 켜서 제거
              ui.quest.setEventHandler(…);
            #endif
ui.quest.setVisible(1);
플래그 켜서 제거
            ui.quest.setEventHandler(…);
#ifdef _EVENT_2006
            ui.notice.setText(“월드컵 이벤트!”);
플래그 꺼서 제거   ui.notice.setVisible(1);
            ui.notice.setEventHandler(…);
          #endif
플래그 꺼서 제거
#define _NEW_QUEST
         #ifdef _NEW_QUEST
         # define _NEW_QUEST_FIX
         #endif
플래그 병합   #ifdef _NEW_QUEST
  제거       ui.event.setText(…);
         # ifdef _NEW_QUEST_FIX
           ui.event.setPos(…);
         # endif
         #endif
#define _NEW_QUEST




플래그 병합   #ifdef _NEW_QUEST
  제거       ui.event.setText(…);

           ui.event.setPos(…);

         #endif
개운하다!
제거할      소스에서
                  테스트
플래그 선택   플래그 제거
수천개를 수작업으로?




         Image: http://www.flickr.com/photos/15271532@N00/1172675049
리팩토링 딜레마!
  실수하면?
  누가하지?
자동화




      Image: http://www.enggtechsolutions.com/?page_id=5
제거할      소스에서
                  테스트
플래그 선택   플래그 제거


  선택       제거      검증
 자동화      자동화     자동화
도입                  결과   결론
     분석   처리   검증
플래그에 대한 정보 얻기
(삭제할 플래그를 추리기 위해)
소스 파일
#define F | #undef F
#ifdef F | #ifndef F | #if defined(F)
국가별 Flags.h 파일에 정의된 플래그 추리기
#define 뒤에 있는 주석도 가져오기
Subversion Log
모든 커밋의
  Rev#, 날짜, 작성자, 로그
변경한 파일에 포함된 플래그 목록
사용:           날짜:                 주석:
         한국, 중국       2008-06-21      김철수, 퀘스트!



                  _QUEST_SYSTEM
플래그 정보

               SVN:                     파일:
          #29110, ironwater     Interface/QuestWindow.h
                               Interface/QuestWindow.cpp
         “[추가] 퀘스트 시스템                System/Quest.h
              1차 작업”                System/Quest.cpp
판단을 내릴 정보 가공!
(단순한 정보 나열을 구체화)
플래그 정보                쿼리 결과



         DB 테이블   쿼리 수행
DB 를 통해 유연한 분석 가능
국가별 / 연도별 플래그 등장 표
DB: 큰 그림 분석

  시간별 증감 추이
 국가별 사용 현황
작업자별 플래그 추가
 파일별 플래그 추이
   플래그 영향력
DB: 오류 분석


  정의만 되고 사용되지 않음 (버려짐?)
   정의는 없고 사용만 있음 (오타?)
    정의가 여러 곳에 있음 (응?)
정의가 여러 곳에 있으면서 값이 다름 (으악!)
모두 켜거나 끈 플래그 목록 추리기
모든     작성자 및
 국가에서    상급자에게   피드백을
 켜고 끈    삭제 리뷰   받아 진행
플래그 수집     요청
도입                  결과   결론
     분석   처리   검증
제거할 플래그를
 소스에서 제거
#define _QUEST_SYSTEM // 퀘스트

플래그 켜서   #ifdef _QUEST_SYSTEM
  제거       ui.quest.appendMsg(“…”);
           questDlg.show();
         #endif
플래그 켜서
  제거     ui.quest.appendMsg(“…”);
         questDlg.show();
#ifdef _EVENT_2008 // 2008 설 이벤트
           ui.event.setText(…);
           ui.event.show();
플래그 꺼서
         #else
  제거
           ui.event.setVisible(0);
         #endif
플래그 꺼서
  제거
         ui.event.setVisible(0);
#define _NEW_QUEST
         #ifdef _NEW_QUEST
         # define _NEW_QUEST_FIX
         #endif
플래그 병합   #ifdef _NEW_QUEST
  제거       ui.event.setText(…);
         # ifdef _NEW_QUEST_FIX
           ui.event.setPos(…);
         # endif
         #endif
#define _NEW_QUEST




플래그 병합   #ifdef _NEW_QUEST
  제거       ui.event.setText(…);

           ui.event.setPos(…);

         #endif
http://dotat.at/prog/unifdef
Unifdef 가 기본적인 기능은 잘 해줌
다만 부분 평가는 해주지 않음

#if defined(A) && defined(B)
       A=켬|끔 B=그냥둠
             ↓
   #if 1|0 && defined(B)
병합 제거는 따로 구현

#ifdef A        #ifdef A
# ifdef A_fix
…               …
# endif
#endif          #endif
작업 흐름


                 부분 평가
원시소스   unifdef           결과소스
                 병합 제거


          제거 플래그 리스트
도입                  결과   결론
     분석   처리   검증
원시소스    처리   결과소스



원시EXE   =    결과EXE
동일한 소스를 두 번 빌드 후 EXE



             빌드시간, 디버그 정보
              등이 EXE 에 포함
동일한 소스를 두 번 빌드 후 OBJ




              디버그 정보가
              OBJ 에 포함
#       name
      #1    .drectve
      #2   .debug$S
      #3      .text
                         비교할 때
OBJ   #4   .debug$S    debug 섹션은
      #5     .rdata       제외!
      #6      .text
      #7   .debug$S
      #8   .debug$T
원시소스    처리   결과소스



원시OBJ   =    결과OBJ
원시OBJ   ≠   결과OBJ

                DUMPBIN
같음
원시소스   처리   비교         OK

             다름
플래그 켬/끔
                  오류
다른 함수
        원인 파악    툴 버그
 찾기
                소스 문제
오류의 예

#include “show.h”
//#include “Flags.h”

void show() {
#ifdef _QUEST_SYSTEM
  ui.quest.print(…);
  ui.questDlg.show();
#endif
}
__LINE__
    ASSERT 매크로 등에 있음!
검증 단계의 unifdef 는 행을 유지하도록.

         __TIME__
  작업 전에만 지웠다가 다시 살림.
도입                  결과   결론
     분석   처리   검증
제거 플래그   작업자의
                처리 / 검증 / 커밋
후보 선택     리뷰
   자동화                 자동화
작업자 리뷰에 시간이 들기 때문에
리뷰 플래그 개수를 한번에 300~500 개로 유지
6 번의 플래그 제거 작업
총 2,107 개의 플래그 제거
2,309 파일에서
191,979 라인 제거
라인



10%



            코드
            제거



      90%
작성한 툴

분석    Track       소스 & SVN 에서 정보 수집

     Uniform      Unifdef + 추가 소스 처리
처리
     CompareBin   OBJ 파일 동일 검사


검증   BatchRun     플래그 제거 및 검증을 일괄 실행
작성한 툴

분석    Track       소스 & SVN 에서 정보 수집

     Uniform 총 Unifdef + 추가 소스 처리
               LOC:
처리
     CompareBin
                  1500 동일 검사
                   OBJ 파일


검증   BatchRun     플래그 제거 및 검증을 일괄 실행
도입                  결과   결론
     분석   처리   검증
불필요한 #if - #endif 제거는
소스 코드를 깔끔하게 유지하는데 도움!
소스의 유지보수성을 확보하는 작업을
자동화 시키고 신뢰성 있게 만드는 것이 중요!
자동화된 솔루션은 반복해서 사용할 수 있어
   계속해서 도움을 받을 수 있음!
분석은 처리 뿐 아니라 대상을 바라보는
다른 관점을 제시하는 데에도 도움을 줌!
분석, 수행, 검증의 틀을
코드 / 데이터를 개선하는데 사용해보자!
자동화된 Rename 리팩토링 ?

 문제되는    소스에서        빌드
이름 추리기   자동 변경      테스트
여러분들도!
감사합니다!

자동화된 소스 분석, 처리, 검증을 통한 소스의 불필요한 #if - #endif 제거하기 NDC2012