11월 세미나 소스 리팩토링 예제

[일러두기]
이글은 11월에 개최할 세미나 예제 중 하나이며, 다소 광고성 성격이 강함을 미리 일러두겠습니다. 11월경의 세미나때문에  최근 저의 강좌들이 다소 세미나 홍보성글인데 대해서 고개 숙여 양해를 구합니다. 남의 코딩 방식을 자주 보면 도움이 되는 경우가 많은데, 이 강좌를 저의 코딩 방식을 엿보는 기회로 삼으시기 바랍니다.
[일러두기 끝]

이글에 첨부한 프라젝트는, 아주 간단한 MDI 방식의 실행 파일로서 다음과 같은 기능을 가지고 있다(참고로 리팩토링이 안된 상태의 프라젝트 소스이다)

1. 프로그램을 종료할 때 Fading효과(조금씩 사라지는 효과)를 보여준다.
2. MDI 메인폼에 배경 그림을 표시한다.
3. 메인메뉴에 세개의 작업폼(MDI Child 폼)을 보이도록 하는 메뉴가 있다.
4. 동일 클래스의 작업폼을 2개 이상 만들지 못하도록 처리되어 있다.

한번 실행해 보면 모든 기능을 알 수 있는 아주 간단한 프로그램이다.

다음은 이 프라젝트의 메인폼 소스 파일 내용이다.

// FmMain.pas.. 리팩토링 이전의 상태

unit fmMain;

interface

uses
 Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
 Dialogs, Menus;

type
 TMainFrm = class(TForm)
 {..중략..}
 private
   procedure OpenForm(AFormClass:TFormClass);    
 public
 end;

var
 MainFrm: TMainFrm;

implementation

{$R *.dfm}

uses fmSub1, fmSub2, fmSub3;

var
 // For Form Painting
 BitMap : TBitMap;

procedure TMainFrm.FormCreate(Sender: TObject);
begin
 // Begin Form Fading Effect Code
 AlphaBlend := True;
 AlphaBlendValue := 255;
 // End Fading Effect Code

 // Begin Form Painting
 Bitmap := TBitmap.Create;
 Bitmap.LoadFromFile('깃털.BMP');
 // End Form Painting
end;

procedure TMainFrm.FormClose(Sender: TObject; var Action: TCloseAction);
begin
 // Begin Form Fading Effect Code
 while AlphaBlendValue > 1 do
 begin
   AlphaBlendValue := AlphaBlendValue - 1;
   Sleep(10);// Change the sleep time to manage fading speed
 end;
 // End Fading Effect Code
end;

procedure TMainFrm.FormPaint(Sender: TObject);
var
 // Form Painting Vars
 X, Y, W, H: LongInt;
begin
 // Begin Form Painting
 with Bitmap do
 begin
   W := Width;
   H := Height;
 end;
 Y := 0;
 while Y < Height do
 begin
   X := 0;
   while X < Width do
   begin
     Canvas.Draw(X, Y, Bitmap);
     Inc(X, W);
   end;
   Inc(Y, H);
 end;
 // End Form Painting
end;

procedure TMainFrm.FormDestroy(Sender: TObject);
begin
 Bitmap.Free;
end;

// 동일 클래스의 폼을 2개 만들지 못하도록 처리
procedure TMainFrm.OpenForm(AFormClass:TFormClass);
var
 i: Integer;
begin
 for i := 0 to MdiChildCount - 1 do begin
   if MdiChildren[i].ClassType = AFormClass then
   begin
     MdiChildren[i].BringToFront;
     Exit;
   end;
 end;

 AFormClass.Create(Self);
end;

procedure TMainFrm.Sub11Click(Sender: TObject);
begin
 OpenForm(TSub1Frm); // 작업폼1 열기
end;

procedure TMainFrm.Sub21Click(Sender: TObject);
begin
 OpenForm(TSub2Frm); // 작업폼2 열기
end;

procedure TMainFrm.Sub31Click(Sender: TObject);
begin
 OpenForm(TSub3Frm); // 작업폼3 열기
end;

end.

아마도, 대부분의 델파이 개발자들은 이런식으로 메인폼 소스 파일을 작성할 것이다. 다시 말해서 전형적인 델파이 방식 메인 폼 소스 파일일 것이다.

이 소스를 주위 델파이 개발자들에게 보여주고, 잘못된 점을 찾아보라고 했다. 대부분 별다른 문제점을 찾아 내지 못했다.사실 너무 간단하기도 하지만, 얼핏 봐서는 잘못된 점이 보이지 않을 수도 있다. 코드 중복도 거의 없으며, 상당히 깔끔하게 작성된 메인 폼 소스처럼 보일 것이다.

그러나, 이 전형적인 메인 폼 소스 파일에는, 후일 유지보수와 디버깅에 치명적인 장애물이 될 수 있는 함정들이 여러 군데에 걸쳐 있다. 이 치명적 장애물이 무엇인지, 어떻게 실제로 리팩토링을 해야 하는지는 이번 세미나에서 굉장히 중요한 주제 중의 하나이므로, 지금 이글에서 여러분들에게 알려주지 못함을 양해해 주시라.

이 강좌에서는 이 메인폼 소스 파일을 리팩토링한, 결과 소스만 보이고자 한다.

// fmMain.pas ...리팩토링한 후의 메인 폼 소스

unit fmMain;

interface

uses
 Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
 Dialogs, Menus, EtcUtil;

type
 TMainFrm = class(TForm)
  {.. 중략...}
 private
   { Private declarations }
 public
   { Public declarations }
 end;

var
 MainFrm: TMainFrm;

implementation

{$R *.dfm}

uses fmSub1, fmSub2, fmSub3;

procedure TMainFrm.FormCreate(Sender: TObject);
begin
 // Fading 효과 설정
 TFadingEffect.Create(Self).Init();
 // 배경 그림 설정
 TBackGround.Create(Self).Init('깃털.bmp');

 // 작업 메뉴 초기화
 mnuJobForm.Add( TMenuItem.Create(Self).Init('Sub1 폼 보이기', TSub1Frm) );
 mnuJobForm.Add( TMenuItem.Create(Self).Init('Sub2 폼 보이기', TSub2Frm) );
 mnuJobForm.Add( TMenuItem.Create(Self).Init('Sub3 폼 보이기', TSub3Frm) );
end;

end.

리팩토링한 소스 파일은 매우 간략해져서, 프로그램 흐름이 한 눈에 들어오는 구조로 변했다. 특히 리팩토링이 안된 버전에 비해서 이벤트 메서드 수가 현저히 작다. 보시다 시피 Form의 OnCreate 이벤트 고작 하나 뿐이다.

리팩토링한 결과 소스를 본 사람 중에는, 왜 Free 호출이 없는가 궁금해 하는 사람도 있을 것 이다. 이런식으로 코딩하면 객체가 파괴되지 않아서 메모리 누수 현상이 발생하지 않는가 의아해할 수도 있다. 그러나, 이것이 바로 자동 파괴 개념이다. 파괴를 안하는 것이 아니라 파괴자 호출을 자동화하는 것이다.

객체지향과 델파이에 매우 능한 개발자라면, 내가 어떻게 리팩토링했는지, 이 소스를 보고 상당 부분 이미 짐작했을 수도 있다.

사실 리팩토링은 끝이 없다. 매우 간략해 보이는 두번째 메인 폼 소스조차도, 리팩토링을 또 할 수 있는 여지는 많기 때문이다. 지나친 리팩토링은 개발기간을 가중시키므로, 바람직하지 않지만, 적절한 리팩토링은 개발 효율을 높일 수 있다는 사실을 이 강좌의 결론으로 두고자 한다.

업무 변경 사항이 잦아서 소스가 꼬이기 시작한다면 리팩토링을 고려할 시기가 된 것이다.

http://cafe.daum.net/delphinegong
신고
Posted by 나비 나비:D

작성자 : 주정섭  (jjsverylong)

원문 : http://delphi.borlandforum.com/impboard/impboard.dll?action=read&db=del_tutorial&no=98


____________________________________________________________________________________________
원래 이 글은 예전에 작성한 것이지만, 볼랜드 포럼의 델파이 강좌란이 너무 심심(?)한 것 같아서 재편집해 올립니다. 개발자 게시판이란 좋은 강좌와 팁이 많아야 하는데, 최근의 여러 델파이 관련 사이트를 보면, 대부분 자유게시판과 질답 게시판에만 글이 올라 오고,  다른 게시판은 거의 죽어 있는 것같아 무지 안타깝습니다.

좋은 강좌와 팁이 델파이 관련 사이트에 넘치기를 바라면서....
___________________________________________________________________________________________

다음 코드는 어떤 버튼의 클릭이벤트이다. 클릭할 때마다 버튼의 Caption 속성을 '하하'와 '호호'로 토글하는 코드다. 이 버튼의 Name 속성을 Button1이라고 하자.

[원본 클릭 이벤트]

procedure TForm1.Button1Click(Sender: TObject);
begin
  if Button1.Caption = '하하' then
    Button1.Caption := '호호'
  else
    Button1.Caption := '하하'
end;

코드는 매우 간단하므로 이해하는데 별로 어려움이 없을 것이다. 이 정도의 간단한 코드에 무슨 리팩토링이 필요할 것인가라고 생각할지 모르나, 사실은 그렇지 않다. 여기서 논하는 리팩토링은 마틴 파울러의 리팩토링 책 내용과는 다소 다른 내용이다. 이 리팩토링은 델파이라는 특수한 개발 환경에 의존적이기 때문이다. 어쨋든 여러분은 이 간단한 코드에도 리팩토링이 가능함을 알 수 있을 것이다. 문제는 이 간단한 리팩토링 마저도 상당수의 델파이 개발자들이 하지 않는다는 것이다.

[리팩토링 1단계]

가급적 상수(constant)들은 코드내에서 들어내서 별도로 두라.

procedure TForm1.Button1Click(Sender: TObject);
const
  s1 = '하하';
  s2 = '호호';
begin
  if Button1.Caption = s1 then
    Button1.Caption := s2
  else
    Button1.Caption := s1;
end;

[리팩토링 2단계]

이벤트 메서드 내에서 가급적이면 콤포넌트 Name을 직접 참조하지 말라. 콤포넌트명을 직접 참조하면 후일 그 콤포넌트 Name 속성을 바꿨을 때, 그 콤퍼넌트를 참조하는 코드도 같이 수정해야만 하기 때문이다.

procedure TForm1.Button1Click(Sender: TObject);
const
  s1 = '하하';
  s2 = '호호';
begin
  if TButton(Sender).Caption = s1 then
    TButton(Sender).Caption := s2
  else
    TButton(Sender).Caption := s1;
end;

[리팩토링 3단계]

방어적인 운전이 있듯이 방어적인 코딩도 있다. 에러에 미리 대처하는 코딩을 말한다. 이 이벤트가 실수로 버튼이 아닌 다른 콤포넌트의 클릭 이벤트에 연결되면 참으로 큰일 날 것이다. 다음은 이에 대한 대처 방법이다.

procedure TForm1.Button1Click(Sender: TObject);
const
  s1 = '하하';
  s2 = '호호';
begin
  Assert(Sender is TButton, '버튼에만 연결할 수 있는 이벤트다. 조졌다 니는 잘못 연결했다.');

  if TButton(Sender).Caption = s1 then
    TButton(Sender).Caption := s2
  else
    TButton(Sender).Caption := s1;
end;

신고
Posted by 나비 나비:D

BLOG main image
by 나비:D

공지사항

카테고리

분류 전체보기 (278)
Programming? (0)
----------------------------- (0)
나비의삽질 (5)
Application (177)
SQL (51)
Web (27)
etc. (14)
Omnia (0)
---------------------------.. (0)

글 보관함

달력

«   2017/10   »
1 2 3 4 5 6 7
8 9 10 11 12 13 14
15 16 17 18 19 20 21
22 23 24 25 26 27 28
29 30 31        
Total : 882,506
Today : 2 Yesterday : 218