• Save
Working Effectively With Legacy Code - xp2005
Upcoming SlideShare
Loading in...5
×
 

Like this? Share it with your network

Share

Working Effectively With Legacy Code - xp2005

on

  • 5,150 views

Working Effectively With Legacy Code

Working Effectively With Legacy Code

Statistics

Views

Total Views
5,150
Views on SlideShare
4,615
Embed Views
535

Actions

Likes
5
Downloads
0
Comments
0

11 Embeds 535

http://parkpd.egloos.com 482
http://www.hanrss.com 19
http://wiki.expertvill.net 12
http://www.slideee.com 8
http://app.inticube.com 5
file:// 3
http://www.slideshare.net 2
http://www.parkpd.x-y.net 1
http://ig.gmodules.com 1
http://www.ig.gmodules.com 1
http://www.egloos.com 1
More...

Accessibility

Categories

Upload Details

Uploaded via as Microsoft PowerPoint

Usage Rights

© All Rights Reserved

Report content

Flagged as inappropriate Flag as inappropriate
Flag as inappropriate

Select your reason for flagging this presentation as inappropriate.

Cancel
  • Full Name Full Name Comment goes here.
    Are you sure you want to
    Your message goes here
    Processing…
Post Comment
Edit your comment
  • Module 1: Administration November, 1999 Object-Oriented Programming in C#, @1997-2000 Object Mentor, Inc. Notes 2/5/2004 Michael Feathers

Working Effectively With Legacy Code - xp2005 Presentation Transcript

  • 1. 낡은 코드 (Legacy Code) 로 효과적으로 일하기 (Working Effectively with Legacy Code) Michael Feathers [email_address] 번역 : 박일 (ParkPD.egloos.com)
  • 2. 배경
    • Extreme Programming 을 하고 싶긴 한데
      • 지저분한 코드 베이스에서 해야 한다면 ?
    if (m_nCriticalError) return; m_nCriticalError=nErrorCode; switch (nEvent) { case FD_READ: case FD_FORCEREAD: if (GetLayerState()==connecting && !nErrorCode)
  • 3. 첫 반응 (First Reaction)
    • 기존 코드는 최대한 무시한다 .
    • TDD 를 이용해서 클래스를 새로 만든 후 , 예전 클래스에 덮어씌운 후 , 아무 문제 없기를 빈다 .
    • 좀 더 보수적이라면 , 한 번 작성한 코드는 아예 건드리지 않는다 ..
    • 잘 될까 ?
  • 4. 한 번 해 보자
  • 5. PageGenerator::generate() std::string PageGenerator::generate() { std::vector<Result> results = database.queryResults(beginDate, endDate); std::string pageText; pageText += &quot;<html>&quot;; pageText += &quot;<head>&quot;; pageText += &quot;<title>&quot;; pageText += &quot;Quarterly Report&quot;; pageText += &quot;</title>&quot;; pageText += &quot;</head>&quot;; pageText += &quot;<body>&quot;; pageText += &quot;<h1>&quot;; pageText += &quot;Quarterly Report&quot;; pageText += &quot;</h1><table>&quot;; …
  • 6. 계속 .. … if (results.size() != 0) { pageText += &quot;<table border=&quot;1&quot;>&quot;; for (std::vector<Result>::iterator it = results.begin(); it != results.end(); ++it) { pageText += &quot;<tr border=&quot;1&quot;>&quot;; pageText += &quot;<td>&quot; + it->department + &quot;</td>&quot;; pageText += &quot;<td>&quot; + it->manager + &quot;</td>&quot;; char buffer [128]; sprintf(buffer, &quot;<td>$%d</td>&quot;, it->netProfit / 100); pageText += std::string(buffer); sprintf(buffer, &quot;<td>$%d</td>&quot;, it->operatingExpense / 100); pageText += std::string(buffer); pageText += &quot;</tr>&quot;; } pageText += &quot;</table>&quot;; } else { …
  • 7. 계속 .. … pageText += &quot;<p>No results for this period</p>&quot;; } pageText += &quot;</body>&quot;; pageText += &quot;</html>&quot;; return pageText; } // Argh!!
  • 8. 변경
    • 기존 코드에 변경사항을 바로 추가하면 (inline) 함수가 길어진다 .
    • 변경사항은 테스트 되지 않고 , 코드 품질도 높일 수 없다 .
    • 변경되는 코드를 새 함수에 작성한 다음 , 변경 지점에서 위임 (delegate) 하면
      • 장점 :
        • 기존 함수가 길어지지 않고
        • 더 나아질 수 있다 .
      • 단점 :
        • 기존 코드는 여전히 테스트 되지 않고
        • 이렇게 할 수 없는 경우도 있다 .
  • 9. Chia Pet Pattern ( 물을 부으면 머리카락이 자라는 인형 )
    • 낡은 코드 (Legacy code) 에서는 , 새로운 코드를 그냥 추가하지 말자 . 대신 , 새 클래스와 함수를 만들어 테스트를 붙인 후 , 기존 코드를 위임하자 . 이런 기법을 Sprout Method 와 Sprout Class 라고 한다 .
    역주 : Sprout : 자라기 시작하다 , 싹트다 .
  • 10.
    • 테스트가 없다면 , 코드를 변경하는 도중에 의도하지 않은 에러를 만들기 쉽다 .
    • 개발 과정의 많은 부분은 &quot; 예전과 똑같이 동작하기 (preserving behavior)&quot; 이다 . 이런 과정만 없다면 , 우리는 훨씬 빠르게 개발할 수 있을 것이다 .
    • 역주 : preserving behavior : 없던 버그를 만들지 않기 . regression test
    왜 이렇게 보수적인 거야 ?
  • 11. 리펙토링의 딜레마
    • 리펙토링을 하려면 , 테스트가 있어야 한다 .
    • 테스트를 추가하려면 대부분 리펙토링이 필요하다 .
  • 12. 대부분의 프로그램은 결합되어 (glued) 있다 .
    • 각 부분별로 독립적으로 테스트하려고 시도해 보기 전에는 , 이런 사실을 실감하지 못한다 .
  • 13. 결합의 종류
    • 싱글톤 (= 전역 변수 )
      • 객체가 꼭 하나만 생성 가능하다면 , 테스트에서도 잘 돌아가기를 바라는 수 밖에 없다 .
    • 내부 초기화
      • 클래스가 내부에서 하드 코딩된 클래스를 생성한다면 , 테스트에서도 잘 돌아가기를 바라는 수 밖에 없다 .
    • 구현 클래스와의 의존
      • 클래스가 구현 클래스 (concrete class) 를 쓴다면 , 안에서 무슨 일이 일어나는지를 알 수 있기를 바라는 수 밖에 없다 .
        • 역주 : interface 를 쓰는 거랑 뭐가 다를까 ?
  • 14. 의존 제거하기
    • 의존을 제거하는 방법은 어떤 툴을 쓰느냐에 달렸다 . IDE 에서 extract method, extract interface 같은 걸 완벽하게 지원해 준다면 , 많이 쓰자 .
    • 그런 기능이 없다면 , 리펙토링을 직접 , 조심 조심해야 한다 . 의존을 제거할 때는 최대한 단순하게 해야 한다 .
    • 의존을 제거하는 과정에서 지저분한 코드가 만들어질 수 있다 .
  • 15. ' 놓치기 쉬운 부작용 (Side-Effect)'
    • 형편없는 자바 클래스 ( 내부 코드는 더 끔찍할 거 같군요 !)
  • 16. ( 역시나 ...) public class AccountDetailFrame extends Frame implements ActionListener, WindowListener { private TextField display = new TextField(10); … private AccountDetailFrame(…) { … } public void actionPerformed(ActionEvent event) { String source = (String)event.getActionCommand(); if (source.equals(“project activity”)) { DetailFrame detailDisplay = new DetailFrame(); detailDisplay.setDescription( getDetailText() + “ “ + getProjectText()); detailDisplay.show(); String accountDescription = detailDisplay.getAccountSymbol(); accountDescription += “: “; … display.setText(accountDescription); … } }
  • 17. 의존 (dependency) 분리하기 public class AccountDetailFrame extends Frame implements ActionListener, WindowListener { private TextField display = new TextField(10); … private AccountDetailFrame(…) { … } public void actionPerformed(ActionEvent event) { String source = (String)event.getActionCommand(); if (source.equals(“project activity”)) { DetailFrame detailDisplay = new DetailFrame(); detailDisplay.setDescription( getDetailText() + “ “ + getProjectText()); detailDisplay.show(); String accountDescription = detailDisplay.getAccountSymbol(); accountDescription += “: “; … display.setText(accountDescription); … } }
  • 18. 함수 추출 (method extraction) 한 후 ... public class AccountDetailFrame extends Frame implements ActionListener, WindowListener { … public void actionPerformed(ActionEvent event) { performCommand((String)event.getActionCommand()); } void performCommand(String source); if (source.equals(“project activity”)) { DetailFrame detailDisplay = new DetailFrame(); detailDisplay.setDescription( getDetailText() + “ “ + getProjectText()); detailDisplay.show(); String accountDescription = detailDisplay.getAccountSymbol(); accountDescription += “: “; … display.setText(accountDescription); … } }
  • 19. 좀 더 공격적으로 .. public class AccountDetailFrame extends Frame implements ActionListener, WindowListener { … void performCommand(String source); if (source.equals(“project activity”)) { DetailFrame detailDisplay = new DetailFrame(); detailDisplay.setDescription( getDetailText() + “ “ + getProjectText()); detailDisplay.show(); String accountDescription = detailDisplay.getAccountSymbol(); accountDescription += “: “; … display.setText(accountDescription); … } }
  • 20. 리펙토링을 더 한다면 .. public class AccountDetailFrame extends Frame implements ActionListener, WindowListener { private TextField display = new TextField(10); private DetailFrame detailDisplay; … void performCommand(String source); if (source.equals(“project activity”)) { setDescription(getDetailText() + “” + getProjectText()); … String accountDescripton = getAccountSymbol(); accountDescription += “: “; … setDisplayText(accountDescription); … } } …
  • 21. 그 결과는 ... ( 역주 : 함수를 세분화한 덕분에 ) 클래스를 상속받아 , getAccountSymbol, setDisplayText, setDescription 를 오버라이딩해서 테스트에 사용할 수 있게 되었다 .
  • 22. 테스트 .. public void testPerformCommand() { TestingAccountDetailFrame frame = new TestingAccountDetailFrame(); frame.accountSymbol = “SYM”; frame.performCommand(“project activity”); assertEquals(“Magma!”, frame.getDisplayText()); } 역주 : TestingAccountDetailFrame 는 AccountDetailFrame 를 상속받은 테스트용 클래스 .
  • 23. 클래스는 세포 ?
    • 대부분의 디자인은 어느 정도 핵분열 (mitosis) 을 필요로 한다 .
  • 24. 더 좋게 만들기
    • 책임에 따라 클래스를 분리하면 , 디자인을 향상시킬 수 있다 .
  • 25. 더 나은 (Better) vs. 가장 좋은 (Best)
    • “ 최선은 선의 적이다 .(Best is the enemy of good) – 볼테르 (Voltaire)
  • 26. 자동화된 툴 사용하기
    • 안전하게 리펙토링을 할 수 있는 툴이 있고 , 따로 테스트를 설치하지 않고 , 그 툴을 이용해서 의존관계를 제거하고 싶다면 , 툴을 이용해 자동으로 리펙토링을 하되 , 그 외에 따로 코드를 직접 수정하지 말자 .
    • 툴의 기능이 안전하다면 , 직접 고치지만 않으면 문제가 없다 . 툴을 최대한 활용해서 잘못될 가능성을 최소화하자 .
  • 27. 수동 작업
    • 컴파일러에 의지하기 (Lean on the Compiler)
    • 형태 유지하기 (Signature Preservation)
    • 한 번에 하나만 (Single Goal Editing)
  • 28. 컴파일러에 의지하기
    • 컴파일러가 뱉은 에러를 보고 , 어디를 고쳐야 할지 알 수 있다 .
    • 대부분의 경우 :
      • 선언부 (declaration) 를 변경하는 경우 ,
      • 변경해야 할 레퍼런스를 찾기 위해
  • 29. 형태 유지하기 (Signature Preservation)
    • 테스트를 추가할 때 , 형태를 유지해서 , copy & paste 할 때 최대한 조금만 고쳐도 되게 한다 .
    • 에러가 날 가능성이 훨씬 적다 .
  • 30. 수정할 때는 한 작업만 (Single Goal Editing)
    • 한 번에 하나만 .
      • 역주 : 코드 추가용 모자 , 리펙토링용 모자 쓰기
  • 31. 낡은 코드 변경 알고리즘
    • 변경 지점 찾기
    • 테스트 할 곳 찾기
    • 의존 제거하기
    • 테스트 작성
    • 리펙토링 혹은 변경
  • 32. 개발 속도
    • 화성 탐사선 ‘스피릿’
    • “ 응답 속도가 14 분이 걸린다면 무엇을 해야 할까 ?”
  • 33. 의존 제거하기 0 단계 인터페이스 추출 (Extract Interface) 구현 추출 (Extract Implementor)
  • 34. 인터페이스 추출 (Extract Interface)
    • 낡은 코드에 가장 필요한 리펙토링
    • 안전하다 .
      • 몇 가지만 기억하고 있다면 , 전혀 위험하지 않다 .
    • 금방 된다
  • 35. 인터페이스 추출
    • 인터페이스
      • 순수 가상 함수만 있는 클래스
      • 안전하게 다중 상속해서 , 사용자에게 다양한 모습으로 보여질 수 있다 .
      • 역주 : C++ 에서는 ATL 이 가장 인터페이스를 잘 쓰는 framework 중의 하나이다 .
  • 36. 인터페이스 추출 순서
    • 해당 클래스의 멤버 함수 중 현재 클래스에서 쓰는 것들을 찾는다 .
    • 이 멤버 함수들이 하는 일을 딱 설명할 수 있는 인터페이스 이름을 정한다 .
    • 해당 클래스의 자식 클래스에서 이 멤버 함수를 일반 함수 (non-virtual) 로 만들어 쓰고 있는 건 없는지 확인한다 .
    • 위에서 정한 이름으로 인터페이스 클래스를 하나 만든다 .
    • 해당 클래스가 이 인터페이스를 상속하게 한다 .
    • 해당 클래스를 참조하는 곳을 전부 새 인터페이스 이름으로 바꾼다 .
    • 컴파일을 돌려서 , 어떤 함수가 필요한지를 알아본다 .
    • 에러 나는 함수를 모두 인터페이스 쪽으로 복사한다 . 순수 가상 함수 (pure virtual) 로 만드는 걸 잊지 말자 .
  • 37. 일반 함수 (Non-Virtual) 오버라이딩 문제
    • C++ 에서는 , 인터페이스 추출할 때 , 뒷통수를 맞을 수 있는 사소한 버그 ? 가 있다 .
    class EventProcessor { public: void handle(Event *event); }; class MultiplexProcessor : public EventProcessor { public: void handle(Event *event); };
  • 38. 일반 함수 (Non-Virtual) 오버라이딩 문제
    • 가상 함수를 만들면 , 자식 클래스에 있는 똑같은 형태 (same signature) 의 함수도 자동으로 가상 함수가 된다 .
      • 앞 장 코드에서 , EventProcessor 포인터 형태의 MultiplexProcessor 의 handle 함수를 호출하면 , (MultiplexProcessor 의 handle 이 아닌 ) EventProcessor 의 handle 함수가 실행된다 .
      • 상속받은 객체의 포인터를 부모 클래스 포인터로 받았을 때 이런 일이 일어날 수 있고 , 대부분 원하던 결과가 아니다 .
    • 막아야 한다 .
  • 39. 일반 함수 (Non-Virtual) 오버라이딩 문제
    • 그러니까…
      • 인터페이스를 추출하기 전에 , 해당 클래스를 상속받는 클래스가 있는지 본다 .
      • 가상 함수로 만들려는 함수를 자식 클래스에서 일반 함수 (non-virtual) 로 선언해 놓은 건 없는지 꼭 확인한다 .
      • 그런게 있다면 , 아예 새로운 가상 함수를 하나 만들어서 , 그걸 호출하게 만든다 .
  • 40. 구현 추출 (Extract Implementor)
    • 인터페이스 추출이랑 완전 같은데 , 끌어 올리는 거 (pull up) 대신 끌어 내린다 (push down) 는 것만 다르다 .
  • 41. 의존 제거하기 1 단계 함수에 매개변수 추가하기 (Parameterize Method) 함수 추출 후 오버라이딩하기 (Extract and Override Call)
  • 42. 함수에 매개변수 추가하기 (Parameterize Method)
    • 어떤 함수가 내부에서 다른 클래스를 생성하는 숨은 의존관계 (hidden dependency) 가 있다면 , 함수를 새로 만들어서 그 클래스를 인자로 받을 수 있게 하자 .
    • 다른 곳에서는 그 함수를 호출하게 한다 .
    void TestCase::run() { m_result = new TestResult; runTest(m_result); } void TestCase::run() { run(new TestResult); } void TestCase::run( TestResult *result) { m_result = result; runTest(m_result); }
  • 43. 함수에 매개변수 추가하기 - 단계
    • 새로운 함수를 만들어 , 내부에서 생성하던 클래스를 인자로 만든다 .
    • 원래 함수로부터 코드를 잘라내 붙이고 , 클래스 생성 부분은 제거한다 .
    • 원래 함수의 코드는 제거하고 , 거기에 새 함수를 호출하는 코드를 작성하고 , 인자에 내부에서 클래스를 생성하던 코드를 써 넣는다 .
  • 44. 함수 추출 후 오버라이딩하기 (Extract and Override Call)
    • 함수 내에서 다른 것을 호출하는 안 좋은 의존관계가 있다면 , 이 부분을 함수로 추출한 후 , 테스트용 자식 클래스에서 이 함수를 오버라이딩해 버린다 .
  • 45. 함수 추출 후 오버라이딩하기 - 단계
    • 다른 함수 호출 부분을 새로운 멤버 함수로 추출한다 .( 형태는 그대로 유지하자 )
    • 새로운 멤버 함수를 가상 함수로 만든다 .
    • 테스트용 자식클래스를 만들고 , 이 함수를 오버라이딩한다 .
  • 46. 함수 추출 후 오버라이딩하기 - 예
    • // 역주 : from WELC
    • public class PageLayout {
    • private int id = 0;
    • private List styles;
    • private StyleTemplate template;
    • protected void rebindStyles() {
    • styles = StyleMaster.formStyles(tempate, id);
    • // 변경 후
    • protected void rebindStyles() {
    • style = formStyles(template, id);
    • protected List formStyles(StyleTemplate template, int id) {
    • return StyleMaster.formStyles(template, id);
    • public class TestingPageLayout extends PageLayout {
    • protected List formStyles(StyleTemplate template, int id) {
    • return new ArrayList();
  • 47. 의존 제거하기 2 단계 링크 Polymorphism (Link-Time Polymorphism)
  • 48. 링크 Polymorphism void account_deposit(int amount) { struct Call *call = (struct Call *) calloc(1, sizeof (struct Call)); call->type = ACC_DEPOSIT; call->arg0 = amount; append(g_calls, call); }
  • 49. 링크 Polymorphism - 단계
    • 속이고 싶은 (fake) 함수나 클래스를 정한다 .
    • 그것들을 원하는 형태의 가짜로 만든다 .
    • 빌드할 때 , 이렇게 만들어 놓은 가짜가 릴리즈 버전 (production versions) 대신 링크되도록 한다 .
    • ( 역주 ) 예 :
      • Java 같은 경우 , 환경 변수를 바꿔서 , 테스트에서는 mock class 를 불러오도록 바꾼다 .
      • C++ 에서는 dll 이나 , #pragma comment(lib, “xxx) 를 test 용으로 따로 만들어 , 테스트 코드를 실행시킨다 .
  • 50. 의존 제거하기 3 단계 정적 함수 노출 (Expose Static Method)
  • 51. 정적 함수 (Expose Static) 노출
    • 이 코드를 수정해야 한다 .
    • 클래스를 생성하지 않고 ( 혹은 못한다면 ) 이 코드를 테스트에 추가할 방법이 있을까 ?
    class RSCWorkflow { public void validate(Packet& packet) { if (packet.getOriginator() == “MIA” || !packet.hasValidCheckSum()) { throw new InvalidFlowException; } … } }
  • 52. 정적 함수 노출
    • 다행이 , 이 함수는 객체의 멤버 변수나 멤버 함수를 쓰지 않는다 .
    • 그럼 정적 함수 (static method) 로 만들 수 있다 .
    • 이렇게 하면 , 클래스를 생성하지 않고도 , 이 함수를 테스트에 추가할 수 있다 .
  • 53. 정적 함수 (Expose Static) 노출
    • 함수를 정적 함수로 만들면 무슨 문제가 있을까 ?
    • 전혀 없다 . 이렇게 해도 , 객체 내에서는 이 함수를 호출할 수 있기 때문에 , 이 클래스를 쓰는 클라이언트 입장에서는 전혀 달라지는 게 없다 .
    • 더 해 볼만한 리펙토링이 있을까 ?
      • 물론 있다 . validate() 가 packet 클래스의 멤버로 되어 있는데 , 이것도 정적 함수 노출을 이용하면 안전하게 테스트에 추가할 수 있다 .
      • 이렇게 한 후에 , validate 함수를 다시 Packet 으로 옮겨놓으면 된다 .
  • 54. 정적 함수 (Expose Static) 노출
    • public 정적 함수로 바꾸고 싶은 함수와 관련된 테스트를 하나 작성한다 .
    • 이 함수의 내부 코드를 정적 함수로 복사한다 . 다음과 같이 함수 인자를 포함해서 새 함수의 이름을 정할 수 있다 . ( 예 : validate -> validatePacket)
    • 이 함수의 가시성 (visibility) 를 올려서 , 테스트 코드에서 실행시킬 수 있게 한다 .
    • 컴파일 !
    • 멤버 변수나 멤버 함수를 호출하는 거 때문에 에러가 생긴다면 , 그 부분도 정적 (static) 으로 바꿀 수 있는지를 알아본다 . 가능하다면 , 전부 정적으로 바꿔서 컴파일이 되게 한다 .
  • 55. 정적 함수 (Expose Static) 노출 - 결과 class RSCWorkflow { public void validate(Packet& packet) { validatePacket(packet); } public static void validate(Packet& packet) { if (packet.getOriginator() == “MIA” || !packet.hasValidCheckSum()) { throw new InvalidFlowException; } … } }
  • 56. 의존 제거하기 4 단계 객체 대리자 이용 (Introduce Instance Delegator)
  • 57. 객체 대리자 이용 (Introduce Instance Delegator)
    • 정적 함수는 다형성 (polymorphic) 이 없어 속이기 (fake) 어렵다 .
    • 이런 게 있다면
    static void BankingServices::updateAccountBalance( int userID, Money amount) { … }
  • 58. 객체 대리자 이용
    • 이렇게 해서 ...
    class BankingServices public static void updateAccountBalance( int userID, Money amount) { … } public void updateBalance( int userID, Money amount) { updateAccountBalance(userID, amount); } }
  • 59. 객체 대리자 이용
    • 이렇게 쓴다 .
    기존 클래스 public class SomeClass { public void someMethod() { … BackingServices.updateAccountBalance(id, sum); 를 이렇게 고친다 . public class SomeClass { public void someMethod(BackingServices services) { … services.updateAccountBalance(id, sum); 이제 테스트 코드에서는 someMethod 에 TestBackingServices : public BackingServices 같은 클래스를 넘겨줄 수 있다 .
  • 60. 객체 대리자 이용 - 단계
    • 테스트하기 곤란한 정적 함수를 찾는다 .
    • 해당 클래스에 멤버 함수를 하나 만든다 . 해당 정적 함수와 똑같은 형태를 갖게 한다 .
    • 해당 클래스의 멤버 함수가 정적 함수를 호출하게 한다 .
    • 테스트 코드 안에서 해당 정적 함수를 호출하는 부분을 찾아 , 방금 만든 일반 멤버 함수 (non-static) 를 호출하도록 수정한다 .
    • 함수에 매개변수 추가하기 (Parameterize Method) 나 다른 의존 제거하기 방법을 이용해서 , 정적 함수가 호출되는 곳에 객체를 전달한다 .
      • 역주 : 일반 함수도 결국 정적함수에다가 자동으로 self 를 넘겨주는 것과 다를 바 없다 .
  • 61. 의존 제거하기 5 단계 템플릿 재정의 (Template Redefinition)
  • 62. 템플릿 재정의 (Template Redefinition)
    • C++ 에서는 템플릿을 이용해서 의존관계를 제거할 수 있다 .
    class AsyncReceptionPort { private: CSocket m_socket; // bad dependency Packet m_packet; int m_segmentSize; … public: AsyncReceptionPort(); void Run(); … };
  • 63. 템플릿 재정의 template<typename SOCKET> class AsyncReceptionPortImpl { private: SOCKET m_socket; Packet m_packet; int m_segmentSize; … public: AsyncReceptionPortImpl(); void Run(); }; typedef AsyncReceptionPortImpl<CSocket> AsyncReceptionPort;
  • 64. 템플릿 재정의 - 단계
    • 테스트하려는 클래스에서 교체하고 싶은 기능을 알아본다 .
    • 클래스를 템플릿 클래스로 바꾸고 , 변경하고 싶은 부분을 템플릿 인자로 바꾼 후 , 함수 구현부를 헤더 파일로 옮긴다 .
    • 새로 만든 템플릿 클래스에 다른 이름을 붙인다 . 일반적으로 &quot;Impl&quot; 를 원래 이름 뒤에 붙여준다 .
    • 템플릿 정의 밑에 typedef 를 추가한다 . 템플릿 인자에 , 원래 클래스에서 쓰던 타입을 써 주고 , 이름도 원래 클래스의 이름과 같게 만든다 .
    • 테스트 파일에 템플릿 정의를 include 하고 , 새로운 타입을 템플릿 인자로 넘겨 객체를 생성해서 , 테스트에 맞도록 클래스를 변경한다 .
    • 역주 : CLock<MockLock>, CLock<RecurciveLock> 와도 비슷한 얘기 .
  • 65. 테스트 작성하기
  • 66. 특성 테스트 (Characterization Testing)
    • 변경하려는 클래스를 위해 가장 먼저 만드는 테스트
    • 이 테스트는 현재 상태의 특성을 나타낸다 .
    • 작업하는 동안 원래 코드를 고정쇠 (vise) 처럼 ( 의도하지 않은 부분이 변경되지 않도록 ) 잡아준다 .
  • 67. 특성 테스트
    • 어떤 종류의 테스트가 필요할까 ?
    • 필요한 만큼 많이 만들수록 좋다 .
      • 클래스에 대해 자신감이 생기고
      • 클래스가 어떤 걸 하는지 이해하게 되고
      • 테스트를 추가하기 쉬워진다 .
    • 하지만 ,
      • 깨질만한 부분이 있는지를 찾아내는 게 중요하다 .
  • 68. 특성 테스트 - 예
    • 이 함수의 일부를 BillingPlan 으로 옮기려고 한다면 , 어떤 테스트를 작성해야 할까 ?
    class ResidentialAccount void charge(int gallons, date readingDate){ if (billingPlan.isMonthly()) { if (gallons < RESIDENTIAL_MIN) balance += RESIDENTIAL_BASE; else balance += 1.2 * priceForGallons(gallons); billingPlan.postReading(readingDate, gallons); } }
  • 69. 특성 테스트 - 예
    • 이 함수를 BillingPlan 으로 옮긴다고 할 때 , 이 코드가 변경될 가능성이 있을까 ?
    • 모든 경계 조건 (boundary conditions) 에 대해 테스트를 다 해 봐야 할까 ?
    if (gallons < RESIDENTIAL_MIN) balance += RESIDENTIAL_BASE; else balance += 1.2 * priceForGallons(gallons); billingPlan.postReading(readingDate, gallons);
  • 70. 질문 & 생각해 볼 것
    • 내 코드는 어째서 더 끔찍할까 ? 좋은 수가 없을까 ?
    • 테스트가 도움이 되고 있나 ?
    • 접근 제한 (access protection) 은 어떻게 하지 ? 리플렉션 (reflection) 은 쓸 수 있나 ?
  • 71. WELC Understanding, Isolating, Testing, and Changing ..Untested Code www.objectmentor.com www.michaelfeathers.com
  • 72. WELC
    • 원문 : http://www.xpnl.org/html/Wiki/WELCXP20052.ppt
    • 책에 있는 코드를 약간 추가했습니다 .
    • 이 문서는 제 1 회 Kasa Open Seminar 에서 발표할 내용준비를 위해 만들어 졌습니다 .
      • 실제 발표 내용에는 다른 내용이 추가될 예정입니다 . 
    • http://parkpd.egloos.com