SlideShare a Scribd company logo
1 of 38
아꿈사
http://cafe.naver.com/architect1
                         박진호
  http://blog.naver.com/jinojjan
   레거시 코드와 클래스 들을 분리해 테스트
    루틴 안에 넣을 때 사용하는 기법

   모든 기법들은 기존 동작이 정상적으로 작
    동하도록 함

   설계 개선 X, 기존 소스에 테스트 루틴이 추
    가되어 유지보수
 매개변수 적응
 매개변수 객체 탈출
 정의 완료
 전역참조 캡슐화
 정적 메소드 드러내기
 호출 추출과 오버라이드
 팩토리 메소드 추출과 오버라이드
 매개변수 적응
 매개변수 객체 탈출
 정의 완료
 전역참조 캡슐화
 정적 메소드 드러내기
 호출 추출과 오버라이드
 팩토리 메소드 추출과 오버라이드
•   메소드 변경 시 매개 변수로 인한 의존 관계
    발생 할 때
•   매개변수에 대해 인터페이스 추출을 사용할
    수 없을 때
•   매개 변수를 Fake로 만들기 어려울 때
public class ARMDispatcher
{
  public void populate(HttpServletRequest request) {
     String [] values
          = request.getParameterValues(pageStateName);
      if (values != null && values.length > 0)
      {
          marketBindings.put(pageStateName +
getDateStamp(),
                   values[0]);
      }
      ...
  }
  ...
}

•문제 : 테스트 작성 시HttpServletRequest 클래스를 구현 하는 클래스 하나를 생성.
-> 구현 해야할 사항 많음
public class ARMDispatcher
{
  public void populate(ParameterSource source) {
    String values =
source.getParameterForName(pageStateName);
    if (value != null) {
        marketBindings.put(pageStateName +
getDateStamp(),
                 value);
    }
    ...
  }
}


•해결 : 매개변수를 Wrap 한 후, API 인터페이스 상에 있는 의존 관계 없앰
•Fake 클래스 : Test 루틴에 사용
class FakeParameterSource implements ParameterSource
{
  public String value;

        public String getParameterForName(String name) {
          return value;
        }
}



•Production클래스 : 기존 루틴에 사용.
    class ServletParameterSource implements ParameterSource
    {
      private HttpServletRequest request;
        public ServletParameterSource(HttpServletRequest request) {
          this.request = request;
        }
        String getParameterValue(String name) {
          String [] values = request.getParameterValues(name);        • J2EE 소스 -> 로직 분리.
          if (values == null || values.length < 1)
             return null;
          return values[0];
        }
    }
1.   메소드의 매개변수에서 사용할 새로운 인터페이스 생성
     (ParameterSource)
2.   새로운 인터페이스를 위한 클래스 생성(ServletParameterSource )
3.   테스트를 위한 Fake 클래스 생성 (FakeParameterSource )
4.   3번클래스를 매개변수에 전달하는 단순한 Test Case 작성
5.   새로운 매개변수를 사용하기 위해 메소드 안에 필요한 변경 을 가함.
6.   Fake 클래스를 이용해서 테스트할 수 있는지 검증하기 위해 테스트
     루틴 실행
 매개변수 적응
 매개변수 객체 탈출
 정의 완료
 전역참조 캡슐화
 정적 메소드 드러내기
 호출 추출과 오버라이드
 팩토리 메소드 추출과 오버라이드
•   사용 시점
    • 메소드크기가 클 때
     ( 그 안에서 인스턴스 데이터와 메소드 사용 시)
•   방법
    • 하나의 긴 메소드 -> 하나의 클래스로 변경
class GDIBrush                                                class Renderer
{                                                             {
public:                                                       public:
  void draw(vector<point>& renderingRoots,                       Renderer(GBIBrush *brush,             •함수 매개변수 그대로
        ColorMatrix& colors,                                         vector<point>&

                                                                                                       가져옴(서명보전)
        vector<point>& selection);                            renderingRoots,
  ...                                                                ColorMatrix &colors,
                                                                     vector<point>& selection);
private:                                                         ...
  void drawPoint(int x, int y, COLOR color);                  };
  ...

};
                                                               class Renderer
void GDIBrush::draw(vector<point>& renderingRoots,             {
                                                               private:
             ColorMatrix& colors,
             vector<point>& selection)                           GDIBrush *brush;                      •인스턴스 변수 추가 및
{                                                                vector<point>& renderingRoots;
  for(vector<points>::iterator it = renderingRoots.begin();
        it != renderingRoots.end();
                                                                 ColorMatrix& colors;
                                                                 vector<point>& selection;             초기화
        ++it) {
    point p = *it;                                             public:
    ...                                                           Renderer(GDIBrush *brush,
                                                                      vector<point>& renderingRoots,
           drawPoint(p.x, p.y, colors[n]);                            ColorMatrix& colors,
     }                                                                vector<point>& selection)
     ...                                                           : brush(brush), renderingRoots(renderingRoots),
}                                                                    colors(colors), selection(selection)
                                                                   {}
                                                               };
class Renderer
{
private:

                                    •draw 함수 추가
  GDIBrush *brush;
  vector<point>& renderingRoots;                     void Renderer::draw()
  ColorMatrix& colors;                               {
  vector<point>& selection;                            for(vector<points>::iterator it =
                                                     renderingRoots.begin();
public:                                                        it != renderingRoots.end();
  Renderer(GDIBrush *brush,                                    ++it) {
       vector<point>& renderingRoots,                      point p = *it;
       ColorMatrix& colors,                                ...
       vector<point>& selection)                           drawPoint(p.x, p.y, colors[n]);
   : brush(brush), renderingRoots(renderingRoots),     }
     colors(colors), selection(selection)              ...
   {}                                                }     •draw 함수의 Body를 멤버
                                                           함수인 draw 로 복사
     void draw();
};




     •컴파일러 의존 사용.(drawPooint에서 에러 발생)
class GDIBrush
class GDIBrush
{
                                                           {                                            •의존 관계가 있는
                                                           public:
public:
  void draw(vector<point>&
                                                             void draw(vector<point>&
                                                           renderingRoots,                              drawPoint 함수를 Public으
renderingRoots,
                                                                   ColorMatrix& colors,
                                                                                                        로 수정
        ColorMatrix& colors,
                                                                   vector<point>& selection);
        vector<point>& selection);
                                                             ...
  ...
                                                           void drawPoint(int x, int y, COLOR color);
private:
                                                                                                         •변수 인 경우 Getter 함수
                                                           private:
  void drawPoint(int x, int y, COLOR color);
  ...
                                                                ...
};
                                                           };                                            생성함


                                        void
                                        GDIBrush::draw(vector<point>&
                                                                                              •큰 draw함수 -> Renderer 클래
                                        renderingRoots,
                                                                                              스가 수행
                                                 ColorMatrix &colors,
                                                 vector<point>& selection)
                                        {
                                          Renderer
                                        renderer(this, renderingRoots,
                                                  colors, selection);
                                          renderer.draw();
                                        }
class PointRenderer                                             class Renderer
{                                                               {
   public:                                                      private:
     virtual void drawPoint(int x, int y, COLOR color) = 0;       PointRender *pointRenderer;
};                                                                vector<point>& renderingRoots;
                                                                  ColorMatrix& colors;
                                                                  vector<point>& selection;
class GDIBrush : public PointRenderer
{                                                               public:
public:                                                          Renderer(PointRenderer *renderer,
   void drawPoint(int x, int y, COLOR color);                          vector<point>& renderingRoots,
   ...                                                                ColorMatrix& colors,
};                                                                     vector<point>& selection)
                                                                   : pointRenderer(pointRenderer),
                                                                     renderingRoots(renderingRoots),
                                                                     colors(colors), selection(selection)
                                                                   {}
  void Renderer::draw()
  {
                                                                     void draw();
    for(vector<points>::iterator it = renderingRoots.begin();
                                                                };
            it != renderingRoots.end();
            ++it) {
        point p = *it;
        ...
        pointRenderer->drawPoint(p.x,p.y,colors[n]);
    }
    ...
  }
1.        메소드 코드 넣을 새로운 클래스 한 개 생성(Renderer)
2.        새로운 클래스의 생성자 생성. (서명 보전 사용하여 매개변수 복사, 원본 메소드에서 인스턴
          스 데이터나 내부 메소드 사용 시 원본 클래스에 대한 참조를 생성자에 대한 첫번째 매개변
          수로 추가)
3.        생성자에 있는 모든 매개변수에 대해 멤버 변수 추가 및 초기화
4.        새로운 클래스 상에 빈 실행 메소드 생성
5.        메소드 안의 소스를 4번의 실행 메소드로 복사 -> 컴파일 -> 컴파일 의존 이용
6.        컴파일 에러 시 컴파일 되도록 함.
     1.     Private 함수 -> Public 함수
     2.     변수일 경우 public Getter 함수 생성

7.        새로운 클래스가 컴파일 성공-> 원본 메소드로 돌아가 서 새로운 클래스의 인스턴스를 하
          나 생성 -> 원본 메소드의 작업을 새로운 클래스에 위임
8.        필요 시 인터페이스 추출을 사용해서 원본 믈래스 상에 있는 의존 관계 청산
 매개변수 적응
 매개변수 객체 탈출
 정의 완료
 전역참조 캡슐화
 정적 메소드 드러내기
 호출 추출과 오버라이드
 팩토리 메소드 추출과 오버라이드
•   사용 시점
    • 테스트 루틴의 정의만 다르게 주고 싶을 때
     (기존의 정의와 의존 관계를 깰때)
•   방법
    • 함수를 선언하고 다른 곳에서 그것을 정의

     (C, C++ 언어 지원.)
class CLateBindingDispatchDriver : public CDispatchDriver
{
public:
                                                            •테스트 파일 내에 직접 정의
        CLateBindingDispatchDriver ();
  virtual ~CLateBindingDispatchDriver ();
                                                            #include "LateBindingDispatchDriver.h"
 ROOTID      GetROOTID (int id) const;
                                                            CLateBindingDispatchDriver::CLateBindingDispatchDriver() {}
 void    BindName (int id,
                                                            CLateBindingDispatchDriver::~CLateBindingDispatchDriver() {}
           OLECHAR FAR *name);
 ...
                                                            ROOTID GetROOTID (int id) const { return ROOTID(-1); }
private:
                                                            void BindName(int id, OLECHAR FAR *name) {}
   CArray<ROOTID, ROOTID& > rootids;
};
                                                            TEST(AddOrder,BOMTreeCtrl)
                                                            {
                                                              CLateBindingDispatchDriver driver;
                                                              CBOMTreeCtrl ctrl(&driver);

                                                                ctrl.AddOrder(COrderFactory::makeDefault());
                                                                LONGS_EQUAL(1, ctrl.OrderCount());
                                                            }
1.   테스트 루틴을 위한 개별적인 실행 파일 생성
     필요 ( 그렇지 않으면 링크 에러)
2.   멤버함수 선언 1개 에 대해 2개의 정의로 인한
     유지보수 부담
3.   환경 설정 미비 시 디버거 혼란 상황 발생

==>최악의 의존 관계 상황이 아니라면 정의 완료
기법 권하지 않음. 어쩔 수 없을경우라도 초기 의
존 관계를 깨는 데만 사용 권장
 매개변수 적응
 매개변수 객체 탈출
 정의 완료
 전역참조 캡슐화
 정적 메소드 드러내기
 호출 추출과 오버라이드
 팩토리 메소드 추출과 오버라이드
•   사용 시점
    • Global 변수 혹은 함수와 의존관계를 가진 문제
     있는 코드를 테스트 하려 할 때
•   방법
    • Global 변수 혹은 함수 -> 하나의 클래스로 캡슐

     화 하여 기존 소스와 분리
// bool AGG230_activeframe[AGG230_SIZE]; // bool AGG230_suspendedframe[AGG230_SIZE];




     bool AGG230_activeframe[AGG230_SIZE];
     bool AGG230_suspendedframe[AGG230_SIZE];
                                                                 class Frame

     void AGGController::suspend_frame()
                                                                 {                                        •전역 변수 -> 클래스 public
                                                                 public:

                                                                                                          멤버
     {                                                             // declare AGG230_SIZE as a constant
       frame_copy(AGG230_suspendedframe,                           enum { AGG230_SIZE = 256 };
            AGG230_activeframe);
                                                                      bool AGG230_activeframe[AGG230_SIZE];
       clear(AGG230_activeframe);                                     bool AGG230_suspendedframe[AGG230_SIZE];
       flush_frame_buffers();                                    };
     }

     void AGGController::flush_frame_buffers()
     {                                                          Frame frameForAGG230;
       for (int n = 0; n < AGG230_SIZE; ++n) {
         AGG230_activeframe[n] = false;                         // bool
                                                                AGG230_activeframe[AGG230_SIZE];
         AGG230_suspendedframe[n] = false;                      // bool
       }                                                        AGG230_suspendedframe[AGG230_SIZ
     }                                                          E];




                                                             •Frame 클래스의 전역 인스턴스 생성
                                                             •전역 변수 주석처리
                                                             •빌드 -> 에러 처리는 다음장에
•에러난 모든 곳에 frameForAGG230. 을 추가
void AGGController::suspend_frame()
{
  frame_copy(frameForAGG230.AGG230_suspendedframe,
        frameForAGG230.AGG230_activeframe);
  clear(frameForAGG230.AGG230_activeframe);
  flush_frame_buffer();
}



                                                        •코드 분리 첫걸음
                                                        •시간이 지날 수록 코드 나아지도록 만듦
class Frame
{                                                        class Frame
public:                                                  {
  // declare AGG230_SIZE as a constant                   public:
  enum { AGG230_SIZE = 256 };                               enum { BUFFER_SIZE = 256 };
                                                            bool activebuffer[BUFFER_SIZE];
     bool AGG230_activeframe[AGG230_SIZE];                  bool suspendedbuffer[BUFFER_SIZE];
     bool AGG230_suspendedframe[AGG230_SIZE];            };
};
                                                         Frame frame;
void AGGController::suspend_frame()
{                                                        void AGGController::suspend_frame()
                                                         {
frame_copy(frameForAGG230.AGG230_suspendedframe            frame_copy(frame.suspendedbuffer,
,                                                                frame.activebuffer);
        frameForAGG230.AGG230_activeframe);                clear(frame.activeframe);
  clear(frameForAGG230.AGG230_activeframe);                flush_frame_buffer();
  flush_frame_buffer();                                  }
}
1.   캡슐화 하고자 하는 전역 변수 혹은 함수를 식별
2.   새로운 클래스 생성
3.   전역물들을 새로운 클래스의 public 으로 복사.
     (그 중 일부가 변수라면 클래스 안에서 초기화)
4.   전역변수 혹은 함수의 원본 선언문을 주석 처리
5.   새로운 클래스의 전역 인스턴스 선언
6.   빌드 (컴파일 의존 사용)
7.   에러 부분을 모두 새 클래스의 전역 인스턴스 이름으로 시작하도록
     변경
8.   Fakes를 사용하고자 하는 부근에서 정적 세터 도입, 생성자 매개변수
     화 또는 메소드 매개변수화, 전역 참조를 게터로 대체를 사용.
 매개변수 적응
 매개변수 객체 탈출
 정의 완료
 전역참조 캡슐화
 정적 메소드 드러내기
 호출 추출과 오버라이드
 팩토리 메소드 추출과 오버라이드
•   사용 시점
    • 인스턴스화 하기 힘든 클래스 내의 함수가 필요
     할때
•   방법
    • 멤버 함수 -> static 함수

     (조건 : 기존 멤버 함수가 멤버함수 혹은 멤버 변
     수 사용하지 않아야 함.)
// bool AGG230_activeframe[AGG230_SIZE]; // bool AGG230_suspendedframe[AGG230_SIZE];




     class RSCWorkflow
     {                                                      public class RSCWorkflow {
       ...                                                    public void validate(Packet packet)
       public void validate(Packet packet)                        throws InvalidFlowException {
                                                                validatePacket(packet);
               throws InvalidFlowException {                  }
           if (packet.getOriginator().equals( "MIA")
                 || packet.getLength() > MAX_LENGTH             public static void validatePacket(Packet packet)
                 || !packet.hasValidCheckSum()) {                       throws InvalidFlowException {
               throw new InvalidFlowException();                    if (packet.getOriginator() == "MIA"
                                                                          || packet.getLength() <= MAX_LENGTH
           }                                                              || packet.hasValidCheckSum()) {
           ...                                                          throw new InvalidFlowException();
       }                                                            }
       ...                                                          ...
     }                                                          }
                                                                ...
                                                            }




                                  •Protected로 만들어서 Test하위 클래스를 통해 접근 하도록 가
                                  능.(namespace도 가능)
1.   클래스의 정적 메소드에 접근하는 테스트 루
     틴 하나 작성
2.   클래스의 메소드 -> 클래스의 정적 메소드 로
     정의 이동
3.   빌드
4.   멤버 변수나 메소드에 접근하는 오류 시 접근
     하는 멤버 변수나 메소드를 정적으로 만들 수
     있는지 확인. 그렇지 않으면 다른 방식을 찾아
     봄.
 매개변수 적응
 매개변수 객체 탈출
 정의 완료
 전역참조 캡슐화
 정적 메소드 드러내기
 호출 추출과 오버라이드
 팩토리 메소드 추출과 오버라이드
•   사용 시점
    • 멤버함수 안에서 다른 클래스의 static 함수 호출 하
     여 의존 관계가 발생할 때
    • 멤버함수 안에서 전역 변수 혹은 함수를 호출하여
     의존 관계가 발생할 때
•   방법
    • 멤버함수내의 static혹은 전역 함수 호출 -> 필요한
     static, 전역 함수를 호출 하는 가상 함수를 생성
// bool AGG230_activeframe[AGG230_SIZE]; // bool AGG230_suspendedframe[AGG230_SIZE];




     public class PageLayout
                                                            public class PageLayout
     {
                                                            {
       private int id = 0;                                    private int id = 0;
       private List styles;                                   private List styles;
       private StyleTemplate template;                        private StyleTemplate template;
       ...                                                    ...
                                                              protected void rebindStyles() {
       protected void rebindStyles() {
                                                                  styles = formStyles(template, id);
           styles = StyleMaster.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();
                                                                  }
                                                                  ...
                                                                }
1.   Static 함수 혹은 전역 변수, 함수와 의존 관계
     가 있는 부분을 식별
2.   호출 되는 부분(static, 전역) 의 선언을 찾아서
     서명 보전 사용
3.   클래스 상에 새로운 메소드 생성(2번의 서명
     보전 사용)
4.   새로운 메소드에 대한 호출을 복사하여 기존
     의 호출을 새로운 메소드에 대한 호출로 대체
 매개변수 적응
 매개변수 객체 탈출
 정의 완료
 전역참조 캡슐화
 정적 메소드 드러내기
 호출 추출과 오버라이드
 팩토리 메소드 추출과 오버라이드
•   사용 시점
    • 객체 생성이 생성자 안에서 이루어 져서 테스트 시
     생성자 접근이 이루어 지지 않을 때
•   방법
    • 팩토리 메소드 사용.
     (C++ 사용 불가 : C++ 에서는 생성자 안의 가상함수
     호출이 파생클래스의 오버라이드된 함수 호출 허용
     되지 않음.)
// bool AGG230_activeframe[AGG230_SIZE]; // bool AGG230_suspendedframe[AGG230_SIZE];




                                                            public class WorkflowEngine
     public class WorkflowEngine                            {
     {                                                        public WorkflowEngine () {
                                                                this.tm = makeTransactionManager();
       public WorkflowEngine () {
                                                                ...
         Reader reader                                        }
           = new ModelReader(
             AppConfig.getDryConfiguration());                  protected TransactionManager makeTransactionManager() {
                                                                  Reader reader
                                                                    = new ModelReader(
        Persister persister
                                                                      AppConfiguration.getDryConfiguration());
          = new XMLStore(
            AppConfiguration.getDryConfiguration());                  Persister persister
                                                                       = new XMLStore(
           this.tm = new                                                  AppConfiguration.getDryConfiguration());
     TransactionManager(reader, persister);
                                                                      return new TransactionManager(reader, persister);
           ...                                                  }
       }                                                        ...
       ...                                                  }
     }

                                              •가상함수 오버라이딩해서 새로운 객체 생성 가능
                                                                public class TestWorkflowEngine extends WorkflowEngine
                                                                {
                                                                  protected TransactionManager makeTransactionManager() {
                                                                    return new FakeTransactionManager();
                                                                  }
                                                                }
1.   생성자 안에서의 객체 생성을 식별해 낸다.

2.   팩토리 메소드로 객체 생성을 모두 분리한
     다.

3.   테스트 하위 클래스를 하나 생성하고 팩토
     리 메소드를 오버라이드 하여 의존 관계 문
     제를 회피한다.
감사합니다

More Related Content

Similar to 25장 레거시코드활용전략 전반부 박진호

[170327 1주차]C언어 A반
[170327 1주차]C언어 A반[170327 1주차]C언어 A반
[170327 1주차]C언어 A반arundine
 
Hoons 닷넷 정기세미나
Hoons 닷넷 정기세미나Hoons 닷넷 정기세미나
Hoons 닷넷 정기세미나병걸 윤
 
Unity Surface Shader for Artist 01
Unity Surface Shader for Artist 01Unity Surface Shader for Artist 01
Unity Surface Shader for Artist 01SangYun Yi
 
파이썬 데이터과학 레벨2 - 데이터 시각화와 실전 데이터분석, 그리고 머신러닝 입문 (2020년 이태영)
파이썬 데이터과학 레벨2 - 데이터 시각화와 실전 데이터분석, 그리고 머신러닝 입문 (2020년 이태영)파이썬 데이터과학 레벨2 - 데이터 시각화와 실전 데이터분석, 그리고 머신러닝 입문 (2020년 이태영)
파이썬 데이터과학 레벨2 - 데이터 시각화와 실전 데이터분석, 그리고 머신러닝 입문 (2020년 이태영)Tae Young Lee
 
C++17 Key Features Summary - Ver 2
C++17 Key Features Summary - Ver 2C++17 Key Features Summary - Ver 2
C++17 Key Features Summary - Ver 2Chris Ohk
 
1_기초필수수학_1_벡터대수.pptx
1_기초필수수학_1_벡터대수.pptx1_기초필수수학_1_벡터대수.pptx
1_기초필수수학_1_벡터대수.pptxssuser3fb17c
 
StarUML NS Guide - Design
StarUML NS Guide - DesignStarUML NS Guide - Design
StarUML NS Guide - Design태욱 양
 
Matplotlib 기초 이해하기_20160730
Matplotlib 기초 이해하기_20160730Matplotlib 기초 이해하기_20160730
Matplotlib 기초 이해하기_20160730Yong Joon Moon
 
이펙트 쉐이더 1강 - Shader 기초 개념
이펙트 쉐이더 1강 - Shader 기초 개념이펙트 쉐이더 1강 - Shader 기초 개념
이펙트 쉐이더 1강 - Shader 기초 개념Jihoo Oh
 
NDC11_슈퍼클래스
NDC11_슈퍼클래스NDC11_슈퍼클래스
NDC11_슈퍼클래스noerror
 
[0312 조진현] good bye dx9
[0312 조진현] good bye dx9[0312 조진현] good bye dx9
[0312 조진현] good bye dx9진현 조
 
Domain Specific Languages With Groovy
Domain Specific Languages With GroovyDomain Specific Languages With Groovy
Domain Specific Languages With GroovyTommy C. Kang
 
Nexon Developers Conference 2017 Functional Programming for better code - Mod...
Nexon Developers Conference 2017 Functional Programming for better code - Mod...Nexon Developers Conference 2017 Functional Programming for better code - Mod...
Nexon Developers Conference 2017 Functional Programming for better code - Mod...Isaac Jeon
 
NDC11_김성익_슈퍼클래스
NDC11_김성익_슈퍼클래스NDC11_김성익_슈퍼클래스
NDC11_김성익_슈퍼클래스Sungik Kim
 
c++ opencv tutorial
c++ opencv tutorialc++ opencv tutorial
c++ opencv tutorialTaeKang Woo
 

Similar to 25장 레거시코드활용전략 전반부 박진호 (20)

6 function
6 function6 function
6 function
 
[170327 1주차]C언어 A반
[170327 1주차]C언어 A반[170327 1주차]C언어 A반
[170327 1주차]C언어 A반
 
Hoons 닷넷 정기세미나
Hoons 닷넷 정기세미나Hoons 닷넷 정기세미나
Hoons 닷넷 정기세미나
 
Unity Surface Shader for Artist 01
Unity Surface Shader for Artist 01Unity Surface Shader for Artist 01
Unity Surface Shader for Artist 01
 
파이썬 데이터과학 레벨2 - 데이터 시각화와 실전 데이터분석, 그리고 머신러닝 입문 (2020년 이태영)
파이썬 데이터과학 레벨2 - 데이터 시각화와 실전 데이터분석, 그리고 머신러닝 입문 (2020년 이태영)파이썬 데이터과학 레벨2 - 데이터 시각화와 실전 데이터분석, 그리고 머신러닝 입문 (2020년 이태영)
파이썬 데이터과학 레벨2 - 데이터 시각화와 실전 데이터분석, 그리고 머신러닝 입문 (2020년 이태영)
 
C++17 Key Features Summary - Ver 2
C++17 Key Features Summary - Ver 2C++17 Key Features Summary - Ver 2
C++17 Key Features Summary - Ver 2
 
1_기초필수수학_1_벡터대수.pptx
1_기초필수수학_1_벡터대수.pptx1_기초필수수학_1_벡터대수.pptx
1_기초필수수학_1_벡터대수.pptx
 
C++에서 Objective-C까지
C++에서 Objective-C까지C++에서 Objective-C까지
C++에서 Objective-C까지
 
StarUML NS Guide - Design
StarUML NS Guide - DesignStarUML NS Guide - Design
StarUML NS Guide - Design
 
Matplotlib 기초 이해하기_20160730
Matplotlib 기초 이해하기_20160730Matplotlib 기초 이해하기_20160730
Matplotlib 기초 이해하기_20160730
 
이펙트 쉐이더 1강 - Shader 기초 개념
이펙트 쉐이더 1강 - Shader 기초 개념이펙트 쉐이더 1강 - Shader 기초 개념
이펙트 쉐이더 1강 - Shader 기초 개념
 
NDC11_슈퍼클래스
NDC11_슈퍼클래스NDC11_슈퍼클래스
NDC11_슈퍼클래스
 
[0312 조진현] good bye dx9
[0312 조진현] good bye dx9[0312 조진현] good bye dx9
[0312 조진현] good bye dx9
 
Domain Specific Languages With Groovy
Domain Specific Languages With GroovyDomain Specific Languages With Groovy
Domain Specific Languages With Groovy
 
[week17] D3.js_Tooltip
[week17] D3.js_Tooltip[week17] D3.js_Tooltip
[week17] D3.js_Tooltip
 
Nexon Developers Conference 2017 Functional Programming for better code - Mod...
Nexon Developers Conference 2017 Functional Programming for better code - Mod...Nexon Developers Conference 2017 Functional Programming for better code - Mod...
Nexon Developers Conference 2017 Functional Programming for better code - Mod...
 
Open Jig Ware
Open Jig WareOpen Jig Ware
Open Jig Ware
 
[Week8]R_ggplot2
[Week8]R_ggplot2[Week8]R_ggplot2
[Week8]R_ggplot2
 
NDC11_김성익_슈퍼클래스
NDC11_김성익_슈퍼클래스NDC11_김성익_슈퍼클래스
NDC11_김성익_슈퍼클래스
 
c++ opencv tutorial
c++ opencv tutorialc++ opencv tutorial
c++ opencv tutorial
 

25장 레거시코드활용전략 전반부 박진호

  • 1. 아꿈사 http://cafe.naver.com/architect1 박진호 http://blog.naver.com/jinojjan
  • 2. 레거시 코드와 클래스 들을 분리해 테스트 루틴 안에 넣을 때 사용하는 기법  모든 기법들은 기존 동작이 정상적으로 작 동하도록 함  설계 개선 X, 기존 소스에 테스트 루틴이 추 가되어 유지보수
  • 3.  매개변수 적응  매개변수 객체 탈출  정의 완료  전역참조 캡슐화  정적 메소드 드러내기  호출 추출과 오버라이드  팩토리 메소드 추출과 오버라이드
  • 4.  매개변수 적응  매개변수 객체 탈출  정의 완료  전역참조 캡슐화  정적 메소드 드러내기  호출 추출과 오버라이드  팩토리 메소드 추출과 오버라이드
  • 5. 메소드 변경 시 매개 변수로 인한 의존 관계 발생 할 때 • 매개변수에 대해 인터페이스 추출을 사용할 수 없을 때 • 매개 변수를 Fake로 만들기 어려울 때
  • 6. public class ARMDispatcher { public void populate(HttpServletRequest request) { String [] values = request.getParameterValues(pageStateName); if (values != null && values.length > 0) { marketBindings.put(pageStateName + getDateStamp(), values[0]); } ... } ... } •문제 : 테스트 작성 시HttpServletRequest 클래스를 구현 하는 클래스 하나를 생성. -> 구현 해야할 사항 많음
  • 7. public class ARMDispatcher { public void populate(ParameterSource source) { String values = source.getParameterForName(pageStateName); if (value != null) { marketBindings.put(pageStateName + getDateStamp(), value); } ... } } •해결 : 매개변수를 Wrap 한 후, API 인터페이스 상에 있는 의존 관계 없앰
  • 8. •Fake 클래스 : Test 루틴에 사용 class FakeParameterSource implements ParameterSource { public String value; public String getParameterForName(String name) { return value; } } •Production클래스 : 기존 루틴에 사용. class ServletParameterSource implements ParameterSource { private HttpServletRequest request; public ServletParameterSource(HttpServletRequest request) { this.request = request; } String getParameterValue(String name) { String [] values = request.getParameterValues(name); • J2EE 소스 -> 로직 분리. if (values == null || values.length < 1) return null; return values[0]; } }
  • 9. 1. 메소드의 매개변수에서 사용할 새로운 인터페이스 생성 (ParameterSource) 2. 새로운 인터페이스를 위한 클래스 생성(ServletParameterSource ) 3. 테스트를 위한 Fake 클래스 생성 (FakeParameterSource ) 4. 3번클래스를 매개변수에 전달하는 단순한 Test Case 작성 5. 새로운 매개변수를 사용하기 위해 메소드 안에 필요한 변경 을 가함. 6. Fake 클래스를 이용해서 테스트할 수 있는지 검증하기 위해 테스트 루틴 실행
  • 10.  매개변수 적응  매개변수 객체 탈출  정의 완료  전역참조 캡슐화  정적 메소드 드러내기  호출 추출과 오버라이드  팩토리 메소드 추출과 오버라이드
  • 11. 사용 시점 • 메소드크기가 클 때 ( 그 안에서 인스턴스 데이터와 메소드 사용 시) • 방법 • 하나의 긴 메소드 -> 하나의 클래스로 변경
  • 12. class GDIBrush class Renderer { { public: public: void draw(vector<point>& renderingRoots, Renderer(GBIBrush *brush, •함수 매개변수 그대로 ColorMatrix& colors, vector<point>& 가져옴(서명보전) vector<point>& selection); renderingRoots, ... ColorMatrix &colors, vector<point>& selection); private: ... void drawPoint(int x, int y, COLOR color); }; ... }; class Renderer void GDIBrush::draw(vector<point>& renderingRoots, { private: ColorMatrix& colors, vector<point>& selection) GDIBrush *brush; •인스턴스 변수 추가 및 { vector<point>& renderingRoots; for(vector<points>::iterator it = renderingRoots.begin(); it != renderingRoots.end(); ColorMatrix& colors; vector<point>& selection; 초기화 ++it) { point p = *it; public: ... Renderer(GDIBrush *brush, vector<point>& renderingRoots, drawPoint(p.x, p.y, colors[n]); ColorMatrix& colors, } vector<point>& selection) ... : brush(brush), renderingRoots(renderingRoots), } colors(colors), selection(selection) {} };
  • 13. class Renderer { private: •draw 함수 추가 GDIBrush *brush; vector<point>& renderingRoots; void Renderer::draw() ColorMatrix& colors; { vector<point>& selection; for(vector<points>::iterator it = renderingRoots.begin(); public: it != renderingRoots.end(); Renderer(GDIBrush *brush, ++it) { vector<point>& renderingRoots, point p = *it; ColorMatrix& colors, ... vector<point>& selection) drawPoint(p.x, p.y, colors[n]); : brush(brush), renderingRoots(renderingRoots), } colors(colors), selection(selection) ... {} } •draw 함수의 Body를 멤버 함수인 draw 로 복사 void draw(); }; •컴파일러 의존 사용.(drawPooint에서 에러 발생)
  • 14. class GDIBrush class GDIBrush { { •의존 관계가 있는 public: public: void draw(vector<point>& void draw(vector<point>& renderingRoots, drawPoint 함수를 Public으 renderingRoots, ColorMatrix& colors, 로 수정 ColorMatrix& colors, vector<point>& selection); vector<point>& selection); ... ... void drawPoint(int x, int y, COLOR color); private: •변수 인 경우 Getter 함수 private: void drawPoint(int x, int y, COLOR color); ... ... }; }; 생성함 void GDIBrush::draw(vector<point>& •큰 draw함수 -> Renderer 클래 renderingRoots, 스가 수행 ColorMatrix &colors, vector<point>& selection) { Renderer renderer(this, renderingRoots, colors, selection); renderer.draw(); }
  • 15. class PointRenderer class Renderer { { public: private: virtual void drawPoint(int x, int y, COLOR color) = 0; PointRender *pointRenderer; }; vector<point>& renderingRoots; ColorMatrix& colors; vector<point>& selection; class GDIBrush : public PointRenderer { public: public: Renderer(PointRenderer *renderer, void drawPoint(int x, int y, COLOR color); vector<point>& renderingRoots, ... ColorMatrix& colors, }; vector<point>& selection) : pointRenderer(pointRenderer), renderingRoots(renderingRoots), colors(colors), selection(selection) {} void Renderer::draw() { void draw(); for(vector<points>::iterator it = renderingRoots.begin(); }; it != renderingRoots.end(); ++it) { point p = *it; ... pointRenderer->drawPoint(p.x,p.y,colors[n]); } ... }
  • 16. 1. 메소드 코드 넣을 새로운 클래스 한 개 생성(Renderer) 2. 새로운 클래스의 생성자 생성. (서명 보전 사용하여 매개변수 복사, 원본 메소드에서 인스턴 스 데이터나 내부 메소드 사용 시 원본 클래스에 대한 참조를 생성자에 대한 첫번째 매개변 수로 추가) 3. 생성자에 있는 모든 매개변수에 대해 멤버 변수 추가 및 초기화 4. 새로운 클래스 상에 빈 실행 메소드 생성 5. 메소드 안의 소스를 4번의 실행 메소드로 복사 -> 컴파일 -> 컴파일 의존 이용 6. 컴파일 에러 시 컴파일 되도록 함. 1. Private 함수 -> Public 함수 2. 변수일 경우 public Getter 함수 생성 7. 새로운 클래스가 컴파일 성공-> 원본 메소드로 돌아가 서 새로운 클래스의 인스턴스를 하 나 생성 -> 원본 메소드의 작업을 새로운 클래스에 위임 8. 필요 시 인터페이스 추출을 사용해서 원본 믈래스 상에 있는 의존 관계 청산
  • 17.  매개변수 적응  매개변수 객체 탈출  정의 완료  전역참조 캡슐화  정적 메소드 드러내기  호출 추출과 오버라이드  팩토리 메소드 추출과 오버라이드
  • 18. 사용 시점 • 테스트 루틴의 정의만 다르게 주고 싶을 때 (기존의 정의와 의존 관계를 깰때) • 방법 • 함수를 선언하고 다른 곳에서 그것을 정의 (C, C++ 언어 지원.)
  • 19. class CLateBindingDispatchDriver : public CDispatchDriver { public: •테스트 파일 내에 직접 정의 CLateBindingDispatchDriver (); virtual ~CLateBindingDispatchDriver (); #include "LateBindingDispatchDriver.h" ROOTID GetROOTID (int id) const; CLateBindingDispatchDriver::CLateBindingDispatchDriver() {} void BindName (int id, CLateBindingDispatchDriver::~CLateBindingDispatchDriver() {} OLECHAR FAR *name); ... ROOTID GetROOTID (int id) const { return ROOTID(-1); } private: void BindName(int id, OLECHAR FAR *name) {} CArray<ROOTID, ROOTID& > rootids; }; TEST(AddOrder,BOMTreeCtrl) { CLateBindingDispatchDriver driver; CBOMTreeCtrl ctrl(&driver); ctrl.AddOrder(COrderFactory::makeDefault()); LONGS_EQUAL(1, ctrl.OrderCount()); }
  • 20. 1. 테스트 루틴을 위한 개별적인 실행 파일 생성 필요 ( 그렇지 않으면 링크 에러) 2. 멤버함수 선언 1개 에 대해 2개의 정의로 인한 유지보수 부담 3. 환경 설정 미비 시 디버거 혼란 상황 발생 ==>최악의 의존 관계 상황이 아니라면 정의 완료 기법 권하지 않음. 어쩔 수 없을경우라도 초기 의 존 관계를 깨는 데만 사용 권장
  • 21.  매개변수 적응  매개변수 객체 탈출  정의 완료  전역참조 캡슐화  정적 메소드 드러내기  호출 추출과 오버라이드  팩토리 메소드 추출과 오버라이드
  • 22. 사용 시점 • Global 변수 혹은 함수와 의존관계를 가진 문제 있는 코드를 테스트 하려 할 때 • 방법 • Global 변수 혹은 함수 -> 하나의 클래스로 캡슐 화 하여 기존 소스와 분리
  • 23. // bool AGG230_activeframe[AGG230_SIZE]; // bool AGG230_suspendedframe[AGG230_SIZE]; bool AGG230_activeframe[AGG230_SIZE]; bool AGG230_suspendedframe[AGG230_SIZE]; class Frame void AGGController::suspend_frame() { •전역 변수 -> 클래스 public public: 멤버 { // declare AGG230_SIZE as a constant frame_copy(AGG230_suspendedframe, enum { AGG230_SIZE = 256 }; AGG230_activeframe); bool AGG230_activeframe[AGG230_SIZE]; clear(AGG230_activeframe); bool AGG230_suspendedframe[AGG230_SIZE]; flush_frame_buffers(); }; } void AGGController::flush_frame_buffers() { Frame frameForAGG230; for (int n = 0; n < AGG230_SIZE; ++n) { AGG230_activeframe[n] = false; // bool AGG230_activeframe[AGG230_SIZE]; AGG230_suspendedframe[n] = false; // bool } AGG230_suspendedframe[AGG230_SIZ } E]; •Frame 클래스의 전역 인스턴스 생성 •전역 변수 주석처리 •빌드 -> 에러 처리는 다음장에
  • 24. •에러난 모든 곳에 frameForAGG230. 을 추가 void AGGController::suspend_frame() { frame_copy(frameForAGG230.AGG230_suspendedframe, frameForAGG230.AGG230_activeframe); clear(frameForAGG230.AGG230_activeframe); flush_frame_buffer(); } •코드 분리 첫걸음 •시간이 지날 수록 코드 나아지도록 만듦 class Frame { class Frame public: { // declare AGG230_SIZE as a constant public: enum { AGG230_SIZE = 256 }; enum { BUFFER_SIZE = 256 }; bool activebuffer[BUFFER_SIZE]; bool AGG230_activeframe[AGG230_SIZE]; bool suspendedbuffer[BUFFER_SIZE]; bool AGG230_suspendedframe[AGG230_SIZE]; }; }; Frame frame; void AGGController::suspend_frame() { void AGGController::suspend_frame() { frame_copy(frameForAGG230.AGG230_suspendedframe frame_copy(frame.suspendedbuffer, , frame.activebuffer); frameForAGG230.AGG230_activeframe); clear(frame.activeframe); clear(frameForAGG230.AGG230_activeframe); flush_frame_buffer(); flush_frame_buffer(); } }
  • 25. 1. 캡슐화 하고자 하는 전역 변수 혹은 함수를 식별 2. 새로운 클래스 생성 3. 전역물들을 새로운 클래스의 public 으로 복사. (그 중 일부가 변수라면 클래스 안에서 초기화) 4. 전역변수 혹은 함수의 원본 선언문을 주석 처리 5. 새로운 클래스의 전역 인스턴스 선언 6. 빌드 (컴파일 의존 사용) 7. 에러 부분을 모두 새 클래스의 전역 인스턴스 이름으로 시작하도록 변경 8. Fakes를 사용하고자 하는 부근에서 정적 세터 도입, 생성자 매개변수 화 또는 메소드 매개변수화, 전역 참조를 게터로 대체를 사용.
  • 26.  매개변수 적응  매개변수 객체 탈출  정의 완료  전역참조 캡슐화  정적 메소드 드러내기  호출 추출과 오버라이드  팩토리 메소드 추출과 오버라이드
  • 27. 사용 시점 • 인스턴스화 하기 힘든 클래스 내의 함수가 필요 할때 • 방법 • 멤버 함수 -> static 함수 (조건 : 기존 멤버 함수가 멤버함수 혹은 멤버 변 수 사용하지 않아야 함.)
  • 28. // bool AGG230_activeframe[AGG230_SIZE]; // bool AGG230_suspendedframe[AGG230_SIZE]; class RSCWorkflow { public class RSCWorkflow { ... public void validate(Packet packet) public void validate(Packet packet) throws InvalidFlowException { validatePacket(packet); throws InvalidFlowException { } if (packet.getOriginator().equals( "MIA") || packet.getLength() > MAX_LENGTH public static void validatePacket(Packet packet) || !packet.hasValidCheckSum()) { throws InvalidFlowException { throw new InvalidFlowException(); if (packet.getOriginator() == "MIA" || packet.getLength() <= MAX_LENGTH } || packet.hasValidCheckSum()) { ... throw new InvalidFlowException(); } } ... ... } } ... } •Protected로 만들어서 Test하위 클래스를 통해 접근 하도록 가 능.(namespace도 가능)
  • 29. 1. 클래스의 정적 메소드에 접근하는 테스트 루 틴 하나 작성 2. 클래스의 메소드 -> 클래스의 정적 메소드 로 정의 이동 3. 빌드 4. 멤버 변수나 메소드에 접근하는 오류 시 접근 하는 멤버 변수나 메소드를 정적으로 만들 수 있는지 확인. 그렇지 않으면 다른 방식을 찾아 봄.
  • 30.  매개변수 적응  매개변수 객체 탈출  정의 완료  전역참조 캡슐화  정적 메소드 드러내기  호출 추출과 오버라이드  팩토리 메소드 추출과 오버라이드
  • 31. 사용 시점 • 멤버함수 안에서 다른 클래스의 static 함수 호출 하 여 의존 관계가 발생할 때 • 멤버함수 안에서 전역 변수 혹은 함수를 호출하여 의존 관계가 발생할 때 • 방법 • 멤버함수내의 static혹은 전역 함수 호출 -> 필요한 static, 전역 함수를 호출 하는 가상 함수를 생성
  • 32. // bool AGG230_activeframe[AGG230_SIZE]; // bool AGG230_suspendedframe[AGG230_SIZE]; public class PageLayout public class PageLayout { { private int id = 0; private int id = 0; private List styles; private List styles; private StyleTemplate template; private StyleTemplate template; ... ... protected void rebindStyles() { protected void rebindStyles() { styles = formStyles(template, id); styles = StyleMaster.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(); } ... }
  • 33. 1. Static 함수 혹은 전역 변수, 함수와 의존 관계 가 있는 부분을 식별 2. 호출 되는 부분(static, 전역) 의 선언을 찾아서 서명 보전 사용 3. 클래스 상에 새로운 메소드 생성(2번의 서명 보전 사용) 4. 새로운 메소드에 대한 호출을 복사하여 기존 의 호출을 새로운 메소드에 대한 호출로 대체
  • 34.  매개변수 적응  매개변수 객체 탈출  정의 완료  전역참조 캡슐화  정적 메소드 드러내기  호출 추출과 오버라이드  팩토리 메소드 추출과 오버라이드
  • 35. 사용 시점 • 객체 생성이 생성자 안에서 이루어 져서 테스트 시 생성자 접근이 이루어 지지 않을 때 • 방법 • 팩토리 메소드 사용. (C++ 사용 불가 : C++ 에서는 생성자 안의 가상함수 호출이 파생클래스의 오버라이드된 함수 호출 허용 되지 않음.)
  • 36. // bool AGG230_activeframe[AGG230_SIZE]; // bool AGG230_suspendedframe[AGG230_SIZE]; public class WorkflowEngine public class WorkflowEngine { { public WorkflowEngine () { this.tm = makeTransactionManager(); public WorkflowEngine () { ... Reader reader } = new ModelReader( AppConfig.getDryConfiguration()); protected TransactionManager makeTransactionManager() { Reader reader = new ModelReader( Persister persister AppConfiguration.getDryConfiguration()); = new XMLStore( AppConfiguration.getDryConfiguration()); Persister persister = new XMLStore( this.tm = new AppConfiguration.getDryConfiguration()); TransactionManager(reader, persister); return new TransactionManager(reader, persister); ... } } ... ... } } •가상함수 오버라이딩해서 새로운 객체 생성 가능 public class TestWorkflowEngine extends WorkflowEngine { protected TransactionManager makeTransactionManager() { return new FakeTransactionManager(); } }
  • 37. 1. 생성자 안에서의 객체 생성을 식별해 낸다. 2. 팩토리 메소드로 객체 생성을 모두 분리한 다. 3. 테스트 하위 클래스를 하나 생성하고 팩토 리 메소드를 오버라이드 하여 의존 관계 문 제를 회피한다.