카사 공개세미나1회 W.E.L.C.

Loading...

Flash Player 9 (or above) is needed to view presentations.
We have detected that you do not have it on your computer. To install it, go here.

0 comments

Post a comment

    Post a comment
    Embed Video
    Edit your comment Cancel

    Notes on slide 1

    폭탄 돌리기처럼 , 어느 순간 너무 복잡해져서 리팩토링 하지 않고서는 코드를 추가할 수 없는 그 상황이 되기 전까지는 내 일이 아닌 것이다 .

    2 Favorites

    카사 공개세미나1회 W.E.L.C. - Presentation Transcript

    1. 낡은 코드 (Legacy Code) 로 효과적으로 일하기 박일 (parkpd.egloos.com)
    2. 낡은 코드 (Legacy Code)?
    3. 낡은 코드 (Legacy Code) 가 뭐죠 ?
      • 낡은 코드 ( 낡은 프로젝트 )
        • (1) 자동화 테스트가 되어 있지 않고 , 리팩토링 도 안 되어 있는 프로젝트
        • (2) 인수인계 받은 프로젝트 . 코드도 방대하고 , 실행은 되지만 , 조금만 고쳐도 여기저기에서 에러가 나서 , 수정이 거의 불가능한 프로젝트
        • (3) 이력서 쓰는 데 아무런 도움을 주지 않는 언어나 플랫폼으로 만들어진 프로젝트
      • 지금 당신도 낡은 코드를 작성하고 있을지 모른다 !
    4. ' 소프트웨어 변경 ' 의 어려움
      • 어떻게 보면 , 소프트웨어를 변경하는 건 매우 쉽다
      • 그게 , 우리 프로그래머의 저주의 시작일지도 ?
    5. 배경
      • 이런 지저분한 코드에 새로운 기능을 추가해야 한다면
      if (m_nCriticalError) return; m_nCriticalError=nErrorCode; switch (nEvent) { case FD_READ: case FD_FORCEREAD: if (GetLayerState()==connecting && !nErrorCode)
    6. 소프트웨어 변경의 두 가지 방법
      • 시스템을 변경하는 두 가지 방법 .
        • 기존 코드를 그냥 수정한다
        • 새로운 클래스나 함수를 만든다
      • 많은 프로그래머가 낡은 코드에서는 기존 코드를 그냥 수정한다 . 이유는 ,
        • 가장 빨리 할 수 있고
        • 가장 안전해 보이기 때문
      • 젠가 , 폭탄 돌리기
    7. 낡은 코드의 위험성
      • 원래 있던 코드를 바로 고치는 게 낡은 코드를 수정하는 가장 안전한 방법일까 ?
        • 테스트 할 방법이 없다면 , 기존 코드를 수정하는 도중에 버그를 만들기 쉽다
        • 변경한 부분이 시스템에 어떤 영향을 미칠지 , 뭔가 바뀐 부분은 없는지를 전부 다 파악해야 한다
      • 가장 안전한 방법은 코드를 아예 수정하지 않는 것이다 !
    8. 코드 변경
      • 코드 변경은 버그를 만들어내지만 , 코드를 고치지 않으면 ( 결국에는 ) 더 많은 버그가 생길 수 있다
      • 한 번도 리팩토링하지 않은 프로젝트라면
        • 코드가 이해도 안 되고
        • 기능을 새로 추가하기 어렵고
        • 변경하려면 신경 쓸 게 많아 , 스트레스가 쌓이고
        • 이런 스트레스가 ... 버그를 만들고
        • 이렇게 추가된 버그가 야근 모드 (crunch) 의 우리를 더 괴롭게 만드는 경험이 없었는지 ?
    9. 리팩토링이 필요하다
    10. 변경의 모순 (Paradox of Change)
      • 코드를 변경하다 보면 , 버그가 생긴다
      • 코드를 변경 ( 리팩토링 ) 하지 않으면 , 코드가 점점 복잡해져 , 버그가 생기기 쉬워진다
      • 이런 딜레마를 해결하기 위해서는 우리가 변경한 부분에 대한 확신을 가질 수 있어야 한다
        • 어떻게 제대로 고친 걸 확신할 수 있을까 ?
        • 어떻게 이전에 잘 돌아가는 것을 고장내지 않았다고 확신할 수 있을까 ?
    11. UnitTest 가 필요하다
    12. 경고
      • UnitTest 를 추가하게 되면
        • 여러분은 코드를 더 작성하게 됩니다 .
        • UnitTest 를 위한 framework 를 만들면서 버그도 많이 만드시게 될 겁니다 .
        • DB 나 Rendering, Multithread 같이 UnitTest 를 붙이기 어려운 분야도 많습니다 .
      • 그럼 왜 일을 더 해 가면서 , 버그도 더 만들어 가면서 , 낡은 코드에 UnitTest 를 붙여야 할까 ?
    13. 작업의 모순 (Paradox of Work)
      • 일을 많이 하면 , 코드를 많이 고치게 된다
        • 코드를 많이 고치면 , 버그도 많이 만들게 된다
        • 팀 내의 평판이 안 좋아진다
      • 일을 적게 하면 , 코드를 거의 고치지 않는다
        • 코드를 고치지 않으면 , 버그도 거의 만들지 않는다
        • 팀 내의 평판이 좋아진다 ?!
    14. 억울하지 않은지 ?
      • 홈런 타자는 삼진도 가장 많이 당하지만 , 사람들은 홈런만 기억한다
      • 프로그래머는 기능을 많이 구현하지만 , 사람들은 프로그래머의 버그를 더 기억한다
      • 일도 열심히 하고 , 버그도 적게 만들어 , 안정적인 서비스를 개발하고 , 스트레스도 적게 받자 .
    15. 낡은 코드에 테스트 추가하기
    16. 낡은 코드 변경 알고리즘
      • 변경 지점 찾기
      • 테스트 할 곳 찾기
      • 의존 제거하기
      • 테스트 작성
      • 리팩토링 혹은 변경
    17. 변경 지점 찾기
      • 2007 년 8 월 16 일 ( 목 ) ' 리니지 II 혼돈의왕좌 - 1st Throne : 더 카마엘‘ 업데이트 내용
        • PC 관련 변경 - 레벨에 따른 능력치 상승
          • 70 레벨 이상 PC 의 전투 능력을 대폭 증가 시켰습니다 .
          • 아래 사항은 PC vs 몬스터 관계에서도 동일하게 적용됩니다 .
        • 명중 , 회피 능력
          • 70 레벨 이상 PC 의 명중 , 회피 능력이 증가 하였습니다 .
          • 78 레벨 이상 PC 는 이 보너스가 대폭 상승합니다 .
          • 예를 들어 , 공격자와 방어자의 레벨이 70 이상이라고 하여도 방어자의 레벨이 공격자 보다 더 높을 경우 , 방어자는 기존 보다 더 높은 확률로 회피를 하게 됩니다 .
    18. 변경 지점 찾기
      • 기존 코드 ( 절대로 fake code 입니다 !!!)
      class CMonster : public CActor { public: void Attack(CActor*); void BeAttacked(CActor*); }; class CBoss : public CMonster { public: void Attack(CActor*); void BeAttacked(CActor*); }; void CPlayer::Attack(CBoss* pTarget) { if (CanHit(pTarget)) { pTarget ->BeAttacked(this); } };
    19. 테스트 할 곳 찾기
      • 무엇을 테스트할 수 있을까 ?
        • 70 랩 vs 70 랩의 명중 / 회비 확률 비교
        • 70 랩 vs 78 랩의 명중 / 회비 확률 비교
        • CanHit() 주위를 변경하자
      void CPlayer::Attack(CBoss* pTarget) { if (CanHit(pTarget)) { pTarget ->BeAttacked(this); } };
    20. 의존 제거하기
      • 대부분의 프로그램은 결합되어 (glued) 있다
      • 각 부분별로 독립적으로 테스트하려고 시도해 보기 전에는 , 이런 사실을 실감하지 못한다 .
    21. 결합의 종류
      • singleton (= 전역 변수 )
      • 내부에서 다른 클래스를 초기화해서 사용
      • 구현 클래스와의 의존
        • 클래스 내부에서 다른 구현 클래스 (concrete class) 를 사용한다 .
      • 덕분에 , new CPlayer 나 new CMonster 를 하는 것부터가 쉽지 않을 것이다 .
    22. 의존 제거하기
      • IDE 에서 extract method, extract interface 같은 리팩토링을 잘 지원한다면 최대한 많이 활용하자 .
      • ( 그러나 MSVC++ 에서와 같이 ) 그런 기능이 없다면 , 리팩토링은 직접 해야 한다 . 의존을 제거하기 위해 하는 리팩토링은 최대한 단순하게 하자 .
      • 의존을 제거하는 과정에서 일시적으로 지저분한 코드가 만들어질 수 있다 .
    23. 테스트 작성 TEST_FIXTURE(FixtureActor, 70CantHit70) { pActor1->m_Level = 70; pActor2->m_Level = 70; CHECK(!pActor1->CanHit(pActor2)); } TEST_FIXTURE(FixtureActor, 78LevelCanHit70) { pActor1->m_Level = 78; pActor2->m_Level = 70; CHECK(pActor1->CanHit(pActor2)); }
    24. 리팩토링 혹은 변경
      • 이제 리팩토링을 하는 도중에 , 이전에 잘 돌아가던 기능이 잘못되지 않는다는 것을 보장받기 때문에 마음 편하게 리팩토링 할 수 있다 .
      • ( 사실 ) 그래도 조심스럽게 리팩토링 해야한다 .
    25. 의존 제거하기
    26. 의존 제거하기 0 단계 인터페이스 추출 (Extract Interface) 구현 추출 (Extract Implementor)
    27. 인터페이스 추출
      • 낡은 코드에 가장 필요한 리팩토링
      • 안전하다 .
        • 몇 가지 수칙만 기억하고 있다면 , 전혀 위험하지 않다 .
      • 금방 된다
    28. 인터페이스 추출
      • 인터페이스
        • 순수 가상 함수만 있는 클래스
        • 안전하게 다중 상속해서 , 사용자에게 다양한 모습으로 보여질 수 있다
    29. 인터페이스 추출 순서
      • 해당 클래스의 자식 클래스에서 이 멤버 함수를 일반 함수 (non-virtual) 로 만들어 쓰고 있는 건 없는지 확인한다 .
      • 인터페이스 클래스를 하나 만든다 .
      • 해당 클래스가 이 인터페이스를 상속하게 한다 .
      • 해당 클래스를 참조하는 곳을 전부 새로 만든 인터페이스 이름으로 바꾼다 .
      • 컴파일을 돌려서 , 어떤 함수가 필요한지를 알아본다 .
        • Lean on the Compiler
      • 에러 나는 함수를 모두 인터페이스 쪽으로 복사한다 . 순수 가상 함수 (pure virtual) 로 만드는 걸 잊지 말자 .
    30. 일반 함수 (Non-Virtual) 오버라이딩 문제
      • C++ 에서 인터페이스 추출할 때 , 실수하기 쉬운 부분
      class CMonster : public CActor { public: void Attack(CActor*); void BeAttacked(CActor*); }; class CBoss : public CMonster { public: void Attack(CActor*); void BeAttacked(CActor*); }; void CPlayer::Attack(CBoss* pTarget) { if (CanHit(pTarget)) { pTarget ->BeAttacked(this); } }
    31. 일반 함수 (Non-Virtual) 오버라이딩 문제
      • 가상 함수로 만들면 , 자식 클래스의 똑같은 (same signature) 의 함수도 자동으로 가상 함수가 된다 .
      class CMonster : public CActor { public: void Attack(CActor*); virtual void BeAttacked(CActor*); }; class CBoss : public CMonster { public: void Attack(CActor*); void BeAttacked(CActor*); }; void CPlayer::Attack(CBoss* pTarget) { if (CanHit(pTarget)) { pTarget->BeAttacked(this); } } CBoss* pBoss = new CBoss; pBoss->BeAttack(pPlayer);
    32. 일반 함수 (Non-Virtual) 오버라이딩 문제
      • 해결방법
        • 인터페이스를 추출하기 전에 , 해당 클래스를 상속받는 클래스가 있는지 본다 .
        • 가상 함수로 만들려는 함수를 자식 클래스에서 일반 함수 (non-virtual) 로 선언해 놓은 건 없는지 확인한다 .
        • 그런 게 있다면 , 아예 새로운 가상 함수를 하나 만들어서 , 그걸 호출하게 만든다 .
    33. 구현 추출 (Extract Implementor)
      • 인터페이스 추출이랑 완전 같은데 , 끌어 올리는 거 (pull up) 대신 끌어 내린다 (push down) 는 것만 다르다 .
    34. 의존 제거하기 1 단계 함수에 매개변수 추가 (Parameterize Method) 함수 추출 후 오버라이딩 (Extract and Override Call)
    35. 함수에 매개변수 추가
      • 어떤 함수가 내부에서 다른 클래스를 생성하는 숨은 의존관계 (hidden dependency) 가 있다면 , 함수를 새로 만들어서 그 클래스를 인자로 받을 수 있게 하자 .
      • 다른 곳에서는 그 함수를 호출하게 한다 .
    36. 예 : 몬스터가 공격 당할 때 void CMonster ::Attack(CActor* pTarger) { double angerBonus = 1.0; if (m_VeryAngry) { CAngryData* pAngry = new CAngryData(this); angerBonus = pAngry->GetBonus(pTarget); } void CMonster ::Attack(CActor* pTarger) { CAngryData* pAngryData = NULL; if (m_VeryAngry) { pAngryData = new CAngryData(); } Attack(pTarget, pAngryData); void CMonster ::Attack(CActor* pTarger, CAngryData* pAngryData) { double angerBonus = 1.0; if (pAngryData) { angerBonus = pAngry->GetBonus(pTarget);
    37. 함수에 매개변수 추가 - 단계
      • 새로운 함수를 만들어 , 내부에서 생성하던 클래스를 인자로 만든다 .
      • 원래 함수로부터 코드를 잘라내 붙이고 , 클래스 생성 부분은 제거한다 .
      • 원래 함수의 코드는 제거하고 , 거기에 새 함수를 호출하는 코드를 작성하고 , 인자에 내부에서 클래스를 생성하던 코드를 써 넣는다 .
    38. 함수 추출 후 오버라이딩
      • 함수 내에서 다른 것을 호출하는 좋지 않은 의존관계가 있다면 , 이 부분을 함수로 추출한 후 , 테스트용 자식 클래스에서 이 함수를 오버라이딩한다
    39. 함수 추출 후 오버라이딩 - 단계
      • 다른 함수 호출 부분을 새로운 멤버 함수로 추출한다 .( 형태는 그대로 유지하자 )
      • 새로운 멤버 함수를 가상 함수로 만든다 .
      • 테스트용 자식클래스를 만들고 , 이 함수를 오버라이딩한다 .
    40. 함수 추출 후 오버라이딩 - 예 void CPlayer::Attack(CActor* pTarger) { ... WSASend(m_Handle...); // 공격과 함께 데미지 표시 // 변경 후 void CPlayer::Attack(CActor* pTarger) { ... m_Socket.Send(...); // 공격과 함께 데미지 표시 class CSocket { void Send(...) { WSASend(m_Handle, ...); class CMockSocket : public CSocket { void Send(...) { // do nothing
    41. 더 할 수 있다 class CMockSocket : public CSocket { void Send(int packetType) { m_PacketRecorder.Push(packetType); TEST_FIXTURE(FixtureBattle, PlayerSendBeAttackedPacket) { pMonster->Attack(pPlayer); CHECK(pPlayer->IsSentPacket(PacketType::BeAttacked)); 이런 걸 감지 변수 (sensing) 이라고 한다 .
    42. 의존 제거하기 2 단계
      • Seams 이용하기
        • 1. 가상함수 이용
        • 2. define 을 이용한 코드 변경
        • 3. 링크
    43. Seams?
      • 뜻 : 솔기 , 이은 자리 , 흉터
      • Seam 은 외부에서 행동을 변경할 수 있는 프로그램의 한 지점이다
    44. Seam 예제 void CPlayer::Logout() { g_Database.SavePlayerStatus(this); ... } CDatabase::SavePlayerStatus() 를 하기 위해서는 꼭 DB 에 접근해야 하고 , 시간이 5 초 이상 걸린다고 하면 ?
    45. 가상함수 이용 - 예 CPlayer::CPlayer(CDatabase* pDatabase) : m_pDatabase(pDatabase) {} void CPlayer::Logout() { m_pDatabase->SavePlayerStatus(this); // CDatabase* CPlayer::m_pDatabase; } // 아까의 CMockSocket 과 비슷한 맥락임 . class CMockDatabase : public CDatabase { bool SavePlayerStatus(CPlayer* pPlayer) { return true; } class CMockPlayer : public CPlayer... CMockPlayer* pPlayer1 = new CMockPlayer(new CMockDatabase());
    46. 가상함수 이용
      • 장점
        • 언어가 제공하는 기능으로 원하는 작업을 할 수 있다
        • 가상 함수로 만들면 , 디자인 적으로 다른 곳에도 쓸 수 있다
        • 가상 함수를 쓸 수 있게 하기 위해서라도 , 디자인이 향상되고 , 의존 관계가 줄어든다 .
      • 단점
        • 기존 코드를 꽤 수정해야 한다 .
        • 일반 함수 (Non-Virtual) 오버라이딩 문제
        • 객체가 pass by value 으로 전달될 경우 쓸 수 없다 .
    47. define 을 이용한 코드 변경 - 예 void CPlayer::Logout() { CDatabase::Inst().SavePlayerStatus(this); // singleton 사용 ... } class CDatabase { static CDatabase& Inst() { #ifdef __UnitTest__ if (m_bTesting) { static CMockDatabase s; return s; } #endif static CDatabase s; return s; }
    48. define 을 이용한 코드 변경
      • 장점
        • 일반 프로그래밍 기법으로 할 수 없는 것을 할 수 있다
        • 성능 저하를 막고 , release code 에 UnitTest 코드가 섞이는 것을 막을 수 있다
      • 단점
        • 원래 코드의 의존성은 전혀 줄어들지 않았음
        • release code 에서 빌드가 깨질 수 있음
        • 예상치 못한 버그가 생길 수 있음
        • 지원하지 않는 언어도 있음
    49. 링크 - 예 class CDatabase { void SavePlayerStatus(CActor* pActor) { sql_query(“update ...”); #ifndef __UnitTest__ #pragma comment( lib, "some_sql_library") #else #pragma comment( lib, "fake_sql_library") #endif
    50. 링크
      • 장점
        • 기존 코드를 하나도 안 고쳐도 된다
        • 외부 소프트웨어와의 의존을 제거할 수 있다
          • 바이너리만 받은 third party 라이브러리아면 , 링크 seam 밖에 쓸 수 없다 .
        • 빌드가 빨라질 수도 있다
      • 단점
        • fake 하려는 라이브러리의 인터페이스가 바뀔 때마다 fake 라이브러리의 인터페이스도 같이 바꿔줘야 한다 .
    51. 의존 제거하기 3 단계 정적 함수 노출하기 (Expose Static Method)
    52. 예 : CPlayer 의 공격 코드 class CPlayer : public CActor { bool CanHit(CActor*); void Attack(CActor* pTarget); ... void CPlayer::Attack(CActor* pTarget) { if (CanHit(pTarget)) { pTarget->BeAttacked(this); } }
    53. 어려운 점
      • CPlayer 는 TCP 소켓 연결을 하지 않으면 객체를 생성하기 어렵게 되어 있다 .
      • new CPlayer 를 하지 않고도 , 명중 여부를 확인할 방법이 없을까 ?
    54. CPlayer 의 공격 코드 - 수정판 class CPlayer : public CActor { static bool CanHit (CActor* pAttacker, CActor* pTarget); void Attack(CActor* pTarget); ... void CPlayer::Attack(CActor* pTarget) { if (CanHit(this, pTarget)) { pTarget->BeAttacked(this); } TEST_FIXTURE(FixtureBattle, PlayerCanHitMonster) { CHECK(CPlayer::CanHit(pPlayer, pMonster)); pPlayer->m_Level = 40; pMonster->m_Level = 80; CHECK(!CPlayer::CanHit(pPlayer, pMonster)); class CPlayer : public CActor { bool CanHit(CActor*); void Attack(CActor* pTarget); ... void CPlayer::Attack(CActor* pTarget) { if (CanHit(pTarget)) { pTarget->BeAttacked(this); } }
    55. 코드가 이상하지 않나 ?
      • CanHit 을 굳이 static 으로 만들고 , this 를 넘기는 게 디자인을 망치는 건 아닐지 ?
        • 멤버 함수도 내부적으로 static 함수에 this 를 넘겨주고 있다 .
        • 테스트 외에도 타격 판정을 할 때 쓸모 있게 쓸 수 있다 .
        • 의외로 멤버 변수를 하나도 쓰지 않으면서 , static 이 아닌 멤버함수들이 많다 .
          • 이런 함수는 static 이든 , 아니든 , 부르는 곳에서는 똑같이 쓸 수 있다 .
    56. 의존 제거하기 4 단계 템플릿 재정의 (Template Redefinition)
    57. 예 : test 코드 실행 중 dead lock 이 걸려요 . class CPlayer : public CActor { CMutexLock m_Lock; // recursive lock 아님 void BeAttacked(CActor* pAttacker) { m_Lock.Lock(); pAttacker.Lock(); pAttacker->BeAttacked();
    58. 해결 방법 class CMutexLockImpl : public ILock { void Lock() { WaitForSingleObject(...); class CDummyLockImpl : public ILock { void Lock() { // do nothing class CLockImp<typename T> { T m_Lock; void Lock() { m_Lock.Enter(); } }; typedef CLockImp<CMutexLockImpl> CMutexLock; typedef CLockImp<CDummyLockImpl> CDummyLock;
    59. 더 할 수 있다 ! class CDummyLockImpl : public ILock { void Lock() { if (CDummyLockImpl::Inst().IsDeadLock()) { throw CException(); 테스트를 하면서 , deadlock 를 감지할 수 있다
    60. 그 외 임의성 테스트
    61. 임의성 테스트
      • 타격 크리티컬 같이 random 값이 들어가는 계산은 어떻게 테스트 할 수 있을까 ?
      int GetRand() const { #if defined(_DEBUG) && defined(UnitTestDefined) if (bSettedRandomValue) { return MyTestUnit ::Inst().m_Random; } #endif return ::rand(); } TEST_FIXTURE(FixtureUser2, CheckMagicCritical){ int playerLevel = 60; const double bonus = 50.0; MyTestUnit ::Inst().m_Random = 100.0; // 무조건 성공 . CHECK_EQUAL(true, IsAttackCritical(player, playerLevel, bonus)); MyTestUnit ::Inst().m_Random = 0.0; // 무조건 실패 . CHECK_EQUAL(false, IsAttackCritical(...));
    62. 다른 것들
      • Sprout Method / Class
      • Interception Points
      • Pinch Point Traps
      • Targeted Testing
      • Sensing Variable
      • Construction Test
      • Hack Points
    63. 테스트의 종류
    64. 테스트의 종류 스토리 테스트 비즈니스 의도 ( 제품 설계 ) 사용성 테스팅 탐색적 테스팅 단위 테스트 개발자 의도 ( 코드 설계 ) 특성 테스팅 보안 테스팅 부하 테스팅 조합 테스팅 … 자동 자동 수동 도구
    65. 특성 테스트
      • 일종의 Regression Test
      • 변경하려는 클래스를 위해 가장 먼저 만드는 테스트
      • 이 테스트는 현재 상태의 특성을 나타낸다 .
      • 작업하는 동안 원래 코드를 바이스 (vise) 처럼 잡아준다 .
    66. 특성 테스트
      • 어떤 종류의 테스트가 필요할까 ?
        • 필요한 만큼 많이 만들수록 좋다 .
        • 클래스에 대해 자신감이 생기고
        • 클래스가 어떤 걸 하는지 이해하게 되고
        • 테스트를 추가하기 쉬워진다 .
      • 하지만 ,
        • 깨질만한 부분이 있는지를 찾는 게 중요하다 .
    67. 예 : 공격 코드에 특성 테스트를 붙인다면 ? void CPlayer::BeAttacked(CActor* pAttacker) { int damage = GetDamageFromAttacker(pAttacker); TEST_FIXTURE(FixtureAttack, GetDamageFromAttacker) { pPlayer->m_Level = 40; CHECK_EQUAL(760, pPlayer->GetDamageFromAttacker(pMonster); pPlayer->m_Level = 50; CHECK_EQUAL(400, pPlayer->GetDamageFromAttacker(pMonster); pPlayer->m_Level = 60; CHECK_EQUAL(223, pPlayer->GetDamageFromAttacker(pMonster); pPlayer->m_Level = 70; CHECK_EQUAL(0, pPlayer->GetDamageFromAttacker(pMonster); }
    68. 더 할 수 있다 . TEST_FIXTURE(FixtureAttack, GetDamage) { struct TestData { int m_Level; int m_Expect; }; TestData data[] = { {40, 760}, {50, 400}, {60, 223}, {70, 0}, {80, 0} }; for (int i = 0; i < sizeof(TestData[]) / sizeof(TestData); ++i) { TestData& d = data[i]; pPlayer->m_Level = d.m_Level; CHECK_EQUAL(d.m_Expect, pPlayer->GetDamager(pMonster));
    69. 테스트 코드의 리팩토링
      • 테스트 코드가 넘치면 관리가 안 됨
      • 팀원들이 #define 을 임의로 고치거나 해서 테스트를 하지 않으려 할 수 있음 .
    70. 처음 테스트 붙이는 요령
    71. 처음 테스트 붙이는 요령
      • 정적 함수 노출하기 (Expose Static Method) 을 이용해서 쉬운 테스트부터 붙여본다 .
      • 테스트 코드 안에서 여러 객체를 생성해 보고 , 어떤 의존관계가 있는지 알아본다 .
        • 의존관계가 가장 약한 클래스부터 , mock 객체 같은 seam 을 이용해서 테스트 코드에서 객체를 만들 수 있게 한다 .
        • 점점 의존관계가 복잡한 클래스도 생성할 수 있게 만들어 본다 .
    72. 처음 테스트 붙이는 요령
      • 변경 요청이 들어오는 기능부터 테스트를 살살 붙여본다 .
        • 변경 요청이 들어온 기능은 작업이 끝나면 QA 팀이 한 번 더 검사해 주기 때문에 , 좀 더 안전하다 .
      • 뭘 테스트 해야 할 지 모르겠다면 , 특성 테스트부터 붙여본다 .
        • 아무 함수나 호출해보고 , 이 때 변경되는 멤버변수값이나 , 리턴값을 테스트 결과로 쓴다 .
    73. 처음 테스트 붙이는 요령
      • 낡은 코드에 UnitTest 를 붙이다 보면 문제가 생길 수 있으므로 가능하면 그나마 덜 심각한 부분부터 Test 를 붙여나간다 .
        • 스킬
        • 아이템 , 경험치
    74. 처음 테스트 붙이는 요령
      • private method 를 public method 로 바꾼다 .
      • 너무너무 그러는 게 싫다면 다음과 같이 해 보자 .
    75. class CTest{ protected: int m_Test; void Test() {} }; class CMockTest : public CTest { public: using CTest::m_Test; // 부모 클래스의 멤버를 public 으로 쓰겠다 . using CTest::Test; }; TEST(AccessTest) { CTest a; //a.m_Test = 1; // protected 멤버 변수 접근할 수 없음 . //a.Test(); // protected 멤버 함수 접근할 수 없음 . CMockTest* pMockTest = (CMockTest*)(&a); pMockTest->m_Test = 1; // CMockTest 로 강제 캐스팅 -> 접근 가능 pMockTest->Test(); // a 가 CMockTest 객체가 아니어도 이렇게 쓸 수 있다는 점에 주의 return 0; }
    76. 아니 ? 은닉화 (encapsulation) 은 ?
      • private 과 protected 를 해야 안전한 거 아닌지 ?
        • 테스트 없는 private 보다 , 테스트가 있는 public 이 훨씬 안전한 코드다 .
    77. UnitTest 가 아닐 조건
      • Database 를 access 한다 .
      • 네트워크 전송을 한다 .
      • 파일을 읽고 쓴다 .
      • UnitTest 를 돌리기 위해 설정 파일을 매번 수정해 줘야 한다 .
        • 그럼 아무도 안 할 거다 .
      • UnitTest 하나당 1 초만 걸려도 , 1000 개면 16.5 분이 걸린다 !!!
        • 현재는 600 개에 12 초
    78. 프로그래머를 위한 UnitTest
      • 개발 속도 증가
      • 화성 탐사선 spirit
        • 응답 속도 14 분 !
    79. 프로그래머를 위한 UnitTest
      • 빠른 feedback!
      • 실수를 금방 알기 !
        • 1 분 전에 만든 버그를 고치는 것과
        • 2 주 전에 만든 버그를 고치는 것은 전혀 다르다
    80. Test Driven Debugging?
      • 일반적인 디버깅 방법은 ?
      • 버그 리포트 시스템에 새로운 버그 추가
      • 게임 스크립트 데이타 받아서 컴파일
      • 서버들 빌드 후 loading
        • 여기까지 5~10 분은 걸림 .
      • 클라이언트 1 개 ~3 개 실행
        • 역시나 3 분 이상 소모됨
      • 재현
        • 재현하기 힘든 경우라면 ?
        • 혈맹 전쟁을 테스트하려면 ? 혈맹원 15 명 이상이 접속해야 테스트 가능
      • 코드 수정
      • 3 번으로 돌아가서 확인
    81. Test Driven Debugging!!
      • TDD 를 이용할 때
        • 디버그 관리자에 새로운 버그 추가
        • 게임 스크립트 데이타 받아서 컴파일
        • 서버들 빌드 후 loading
          • 여기까지 5~10 분은 걸림 .
          • 스크립트 없이 테스트 할 수 있는 경우가 많음 .
        • 클라이언트 1 개 ~3 개 실행
          • 역시나 3 분 이상 소모됨
          • 클라이언트 없이 실행 가능 .
        • 재현
          • 재현하기 힘든 경우라면 ?
          • 혈맹 전쟁을 테스트하려면 ? 혈맹원 15 명 이상이 접속해야 테스트 가능
          • 직접 확률을 지정하거나 , 코드에서 loop 돌릴 수 있다 .
        • 코드 수정
    82. Branch & Merge
      • Branch 후 Merge 작업
      • Merge 하면서 다른 팀원이 바꾸어 놓은 코드 때문에 버그 발생
        • 1 차적으로는 지속적인 통합을 권장
        • 2 차적으로는 UnitTest 를 통해서 다른 팀원들에게 지켜야 할 가이드라인을 제시
    83. UnitTest 는 극히 일부분입니다
      • 이는 남을 탓할 것이 아니라 세상 사물에 관심을 가진 이들이 저 좋은 것 보기에 급급한 나머지 사람 보기를 게을리 했기 때문이다 ... 언제나 모든 것의 마지막 조각은 사람이다 .
      • 조선조 강철의적 영웅담 : 공길동전
    84. 다른 팀과의 협조
      • QA 팀과의 협조
        • 원하는 기능 리스트 얻어서 시간 날 때마다 작업해 주기
      • 기획팀과의 협조
        • 기획 개발용 환경 ( 서버 셋팅 등 ) 을 만들어 주기
      • 그래픽팀과의 협조
        • 리소스 변경을 바로 확인할 수 있고 , 변경을 쉽게 할 수 있는 Tool 제공
      • 게임의 ( 그리고 소프트웨어의 ) 품질은 사람에게 달려 있습니다 .
    85. UnitTest 결과를 email 로 축적
      • 다른 팀원이 내가 설치한 테스트를 실패해서 고생하고 있을 때 바로 이메일로 확인할 수 있음
      • 테스트가 늘어남에 따라 , 테스트 전체 시간이 얼마나 늘어나는지 등을 추적할 수 있음 .
      • 테스트 갯수 증가와 테스트 발생 빈도의 상관관계를 분석 중 . 
    86. References
      • Michael Feathers
        • [email_address]
        • http://www.xpnl.org/html/Wiki/WELCXP2005.ppt
        • http://www.xpnl.org/html/Wiki/WELCXP20052.ppt
    87. References
      • TDD, UnitTest for games in KGC 2007
      • http://unittest-cpp.sourceforge.net/
        • UnitTest++ 소스 받는 곳
      • http://www.gamesfromwithin.com
        • Noel Llopis - [email_address]
        • GDC2006 발표자료
      • http://andstudy.com/andwiki/wiki.php/BackwardsIsForward
        • 위 자료를 번역해 놓은 PPT 및 노트
    88. Books
    89. 감사합니다 .

    + Ryan ParkRyan Park, 1 month ago

    custom

    305 views, 2 favs, 4 embeds more stats

    More info about this document

    © All Rights Reserved

    Go to text version

    • Total Views 305
      • 154 on SlideShare
      • 151 from embeds
    • Comments 0
    • Favorites 2
    • Downloads 0
    Most viewed embeds
    • 129 views on http://parkpd.egloos.com
    • 20 views on http://www.hanrss.com
    • 1 views on http://rss.egloos.com
    • 1 views on file://

    more

    All embeds
    • 129 views on http://parkpd.egloos.com
    • 20 views on http://www.hanrss.com
    • 1 views on http://rss.egloos.com
    • 1 views on file://

    less

    Flagged as inappropriate Flag as inappropriate
    Flag as inappropriate

    Select your reason for flagging this presentation as inappropriate. If needed, use the feedback form to let us know more details.

    Cancel
    File a copyright complaint
    Having problems? Go to our helpdesk?

    Categories