Whiteship's Note


[리팩토링] 메일을 메시지로 통합하기

모하니?/Coding : 2009.10.20 00:11


이메일 서비스를 알림 서비스쪽으로 통합중입니다. 서비스 클래스를 통합하는 건 간단했습니다.

@Service
public class SendMailService implements NotificationService{
   
    @Autowired JavaMailSender mailSender;

    public void sendMessage(SpringSproutMessage ssm) {
        SpringSproutMail mail = (SpringSproutMail)ssm;
        MimeMessage message = mailSender.createMimeMessage();
        MimeMessageHelper helper = new MimeMessageHelper(message, SpringSprout2System.ENCODING);
        try {
            helper.setTo(mail.getRecievers());
            helper.setFrom(mail.getFrom());
            helper.setSubject(mail.getSubject());
            helper.setText(mail.getContent(), mail.isHTML());
        } catch (MessagingException e) {
            throw new MailPreparationException(e);
        }
        mailSender.send(message);
    }

}

NotificationService를 구현하기만 하면 끝이죠. 그런데 문제는 이제부터 시작이었습니다. SpringSproutMessage.. 이 녀석을 받아서 메시지를 보내도록 해야하는데, SendMailService는 기존에 SpringSproutMail을 상속받아 만든 Mail 클래스들을 받아서 사용하고 있었습니다.

SSMessage와 SSMail은 상당부분 비슷한 속성과 메서드가 있으면서도 메시지 내용 상으로는 다소 차이가 있었습니다. 구글 토크나 트위터에는 간단한 메시지와 링크주소만 보내지만, 이메일로는 좀 더 구체적인 정보들까지 HTML 형태로 보내기 때문이죠.

public class SpringSproutMail extends SpringSproutMessage {
   
    public static final String SUBJECT_PREFIX = "[봄싹]";
    public static final String SENDER_MAIL = "s2cmailer@gmail.com";
   
    private String subject;
    private String content;
    private String[] recievers;
    private String from;
    private boolean isHTML;
   
    public SpringSproutMail() {
        this.from = SENDER_MAIL;
        this.isHTML = true;
    }
   
  ...
   
    protected void setTo(Member member) {
        String[] tos = new String[1];
        tos[0] = member.getEmail();
        setRecievers(tos);
    }
   
    protected void setTo(Collection<Member> members) {
        String[] recievers = new String[members.size()];
        Iterator<Member> memberIterator = members.iterator();
        for(int i = 0 ; i < recievers.length ; i++){
            recievers[i] = memberIterator.next().getEmail();
            tos.add(recievers[i]);
        }
        setRecievers(recievers);
    }
   
}

일단은 Mail 쪽의 최상위 클래스인 SpringSproutMail이 SpringSproutMessage를 상속받도록 수정했습니다. 그랬더니 아무 문제없이 잘 돌아가더군요. 이상태에서 일단 정지입니다. 돌아가게 만든 상태에서 어떻게 리팩토링 해야할지 고민좀 해야겠습니다.
top


[JSP 리팩토링] 태그 파일로 중복 코드 제거하기

모하니?/Coding : 2009.09.26 16:15


<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<%@ taglib prefix="page" tagdir="/WEB-INF/tags/page"%>
<%@ taglib prefix="form" uri="http://www.springframework.org/tags/form"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
<%@ taglib prefix="s" tagdir="/WEB-INF/tags/study"%>

<page:studypage>
<s:defaultpage>
    <h1>스터디 추가</h1>
    <form:form commandName="study" method="post">
    <p>
        <label>스터디명</label>
        <form:input path="studyName" cssClass="text" />
        <form:errors path="studyName" />
    </p>
    <p>
        <label>최대인원수</label>
        <form:input path="maximum" cssClass="text" />
        <form:errors path="maximum" />
    </p>
    <p>
        <label>시작일</label>
        <form:input path="startDay" cssClass="text"/>
        <form:errors path="startDay" />
    </p>
    <p>
        <label>종료일</label>
        <form:input path="endDay" cssClass="text"/>
        <form:errors path="endDay" />
    </p>
    <p>
        <label>설명</label>
        <form:textarea path="descr" rows="4" cols="60" cssClass="text"/>
        <form:errors path="descr" />
    </p>
    <br/><hr/><br/>
    <a href="<c:url value="/study/list.do"/>">취소</a>
    <input type="submit" value="저장" class="s_waitblock" />
    </form:form>
</s:defaultpage>
<script type="text/javascript">
  $(document).ready(function(){
    $("#startDay").datepicker({ dateFormat: 'yy/mm/dd' });
    $("#endDay").datepicker({ dateFormat: 'yy/mm/dd' });
  });
</script>
</page:studypage>

이미 태그 파일로 <html> </html>과 js, css 임포트 하는 부분을 제거 해 두었습니다. 태그 파일을 여러 추상화 계층으로 세분화 해서 로우 레벨 태그파일과 하이 레벨 태그파일로 나눌 수도 있겠습니다. 저 위에 보이는 page 태그는 하이 레벨 태그 파일로 볼 수 있고, s 태그는 로우 레벨로 볼 수 있습니다. 하이 레벨이라고 해서 뭔가 더 여러운 태그라는게 아니라, 로우 레벨 태그를 조합하여 한 단계 더 추상화시킨 태그파일 입니다. 이런 구분이 원래 있는 것이 아니라 제가 생각하는 걸 정리한 것 뿐이오니,,, 괜히 "하이 레벨 태그 파일" 이런식으로 구글링을 하는 사태가 없기를 바랍니다.

사설을 좀 길었네요, 일단락하기로 하고, 위 코드를 태그파일로 리팩토링하면 다음과 같이 됩니다.

<page:studypage>
<s:defaultpage>
    <h1>스터디 추가</h1>
    <form:form commandName="study" method="post">
    <s:ftext title="스터디명" path="studyName" />
    <s:ftext title="최대인원수" path="maximum" />
    <s:fdate title="시작일" path="startDay" />
    <s:fdate title="종료일" path="endDay" />
    <s:ftextarea title="설명" path="descr" rows="4" cols="60" />
    <hr/>
    <s:back-button url="/study/list.do" />
    <input type="submit" value="저장" class="s_waitblock" />
    </form:form>
</s:defaultpage>
<script type="text/javascript">
  $(document).ready(function(){
    $(".fdate").datepicker({ dateFormat: 'yy/mm/dd' });
  });
</script>
</page:studypage>

이렇게 했을 때 좋은 점은 소스 코드에서 중복을 제거 했을 때 얻을 수 있는 장점과 같습니다.

그러나,,, 단점도 있는데 태그 파일에 정의해준 속성만 받아서 사용하기 때문에 그만큼 사용할 수 있는 기능이 제한 될달까.. 그런게 좀 있습니다. 해결책은 있습니다. 태그 파일에 거의 모든 속성을 다 정의해 놓고 정말 필요한 것만 required로 하고 사용해도 될테지만.. 태그 파일을 만드는 비용이 꽤 많이 들겠지요. 결국 선택의 기로에 서게 되는데, 저는 귀찮아서;; 그냥 최소한의 속성만 정의해서 쓰는 편입니다.

top


Introduce Parameter Object



참조 : http://www.refactoring.com/catalog/introduceParameterObject.html

여러개의 파라미터를 가지는 메소드의 경우 그 일련의 파라미터들을 가지는 새로운 객체 타입을 받도록 변경할 수 있습니다.

사용자 삽입 이미지

Eclipse의 refactoring 기능 중에 저런 리팩터링을 지원하는 것이 없는지 찾아봤지만 못찾았습니다. 있으면 좋으련만...
http://dev.eclipse.org/mhonarc/lists/eclipse-pmc/msg00188.html
http://download.eclipse.org/eclipse/downloads/drops/S-3.3M7-200705031400/eclipse-news-M7.html
위 글들을 보니 Eclipse3.3의 refactoring 기능에 추가해 줄 것 같습니다.

하나의 메소드 안에 뭉탱이로 들어가는 인자들이 있다면 그 인자들끼리 관계가 밀접할 수 있으며 해당 객체에서 따로 빼내는 것이 애플리케이션을 더 유연하게 만들 수 있습니다.

메소드에 넘겨줄 인자가 단순해집니다.

'Refactoring > 5~ 12장 Catalogue' 카테고리의 다른 글

Form Template Method  (0) 2007.07.06
Extract Super Class  (0) 2007.07.05
Introduce Parameter Object  (0) 2007.06.06
Extract Class  (0) 2006.10.08
Substitute Algorithm  (0) 2006.10.07
Pull Up Method  (0) 2006.10.07
Extract Method  (0) 2006.10.06
top


Report Validator 개선 사항

모하니?/Coding : 2007.03.29 01:42


검색 API를 여러 개 붙이는 것이 생각보다 쉽지 않더군요. 이 말은 확장이 불편하다는 거고 다시 또 이 말은 중복된 코드로 인해 코드 끼리 의존성이 심해졌다는 것입니다.

소스 코드 문제점

현재까지 크게 두 부분이 문제가 됩니다.
public static int search(Sentence sentence) {
        int totalResult = useNaverAPI(sentence, "blog")
            + useNaverAPI(sentence, "webkr")
            + useNaverAPI(sentence, "kin")
            + useDaumAPI(sentence, "blog");
        sentence.setPoint(totalResult);
        return totalResult;
    }

이런식으로 API를 사용하는 메소드를 호출하여 사용하게 되는데 useNaverAPI와 useDaumAPI는 요청을 보내는 문자열만 다를 뿐 하는 일은 완전히 같기 떄문에 엄청난 양의 중복된 코드를 발생시켰습니다.

이것과 더불어 Sentence 객체를 보면 다음과 같이 만들어져 있습니다.
public class Sentence {
    private String line;

    private int copyLebel;

    private String naverBlogURL;

    private String naverWebURL;

    private String naverKnowURL;

    private String daumBlogURL;

어처구니가 없는거죠.ㅋㅋ Map을 쓰면 되는데 API 붙일 때 마다 속성을 하나씩 추가 해야 된다니.. 왜 naverWebURL 속성을 만들 때 미쳐 중복을 제거할 생각을 못했을까요..ㅠ.ㅠ 아니면 생각은 했지만 Map을 쓰는게 익숙하지 않아서 못본척 하고 넘어려는 자아가 공부하려는 자아를 속였을 수 있습니다. 그리고는 마치 몰랐었던 척..하는...나쁜 기선.

화면의 문제점

사용자 삽입 이미지
첫 화면 입니다. 간단한 메뉴얼이 눈에 걸리적 거립니다. 좀 더 이쁘게 보여줄 수 없을런지.ㅠ.ㅠ 전체적인 색은 제가 좋아하는 파란색과 하얀색 계열로 바꿔야 겠습니다.

사용자 삽입 이미지
흠..텍스트 위주의 화면..저도 이쁜 아이콘과 그림으로 꾸미고 싶습니다. 표는 AJAX로 멋지게 "전체 결과 보기" 같은 탭을 열어 주면 쫙~ 펼처지도록 하고 싶...지...만... 31일까진 무리라는거.. CSS는 역시..색을 좀 조절하고.. 테이블 모양도 좀 더 괜찮은 모양으로 바꿔줄까 합니다.

화면 코드 문제점

jsp 파일 두 개에서도 헤더와 푸터가 중복되어 나타나고 있는데요. 그걸 include 인가를 사용해서 제거 해야겠습니다.

그리고 "있다" "없다"를 판단해서 찍어주는 JSTL 코드 부분이 계속 반복해서 나타납니다. 이건 태그파일을 사용해서 코드를 간결하게 줄일 수 있겠습니다.


'모하니? > Coding' 카테고리의 다른 글

Semina Helper v0.5  (0) 2007.04.06
아 이런 바보..ㅠ.ㅠ  (2) 2007.03.31
Report Validator v1.0  (0) 2007.03.29
JAR, WAR 에피소드 해결 방법  (0) 2007.03.29
JAR, WAR 에피소드  (2) 2007.03.29
Report Validator 개선 사항  (0) 2007.03.29
까다로운 녀석..ㅠ.ㅠ  (0) 2007.03.27
Report Validator v0.9  (2) 2007.03.26
Report Validator v0.5  (0) 2007.03.26
아 답답해;;  (2) 2007.03.26
테스트를 꼭 실행 해야 하는 시점  (0) 2007.03.23
top


Eclipse에서 Rename Method 리팩토링




getNumberOfMembers() 메소드 이름을 count()로 바꾸는 것이 좋겠다는 의견을 받았습니다. 생각해보니 주소록 어플리케이션에서 숫자를 셀 것이 몇명이나 등록되어 있는지 밖에 없는데 굳이 "몇 명이 있는지 세어라" 라고 할 필요 없이 "몇이냐" 라고 물어보는게 타당하다는 생각이 듭니다.

문제는 getNumberOfMembers() 메소드를 사방에서 사용하고 있다는 것입니다. memberDao 인터페이스에 만들어둔 이 메소드를 count()로 변경하게 되면 여러 클래스에서 에러가 발생하게 되고 각 클래스들을 돌아가니며 수정을 해도 되겠지만...Eclipse가 그 모습을 본다면 매우 안타까워 할 것 같습니다.

Alt + Shift + R 을 클릭합니다.
변경하고 싶은 이름을 입력한 뒤 Preview를 눌러 확인 해 볼 수 도 있습니다.
어디서(class) 어떤 부분이 바뀌게 될지 확인 할 수 있습니다.

OK를 눌러주면 리팩토링 끝입니다. :)

참고 :
Rename Method -> http://www.refactoring.com/catalog/renameMethod.html


top


엔터프라이즈 컴퓨팅 중간고사 5)

Design Pattern : 2006.10.23 21:22


 5) [10점] 가상대학에 올라와 있는 프로그램숙제의 예제들을 조사해서 잘못된 점들을 지적하고 수정하여라. 왜 잘못되었는지를 설명하고 수정 후에는 어떻게 좋아졌는지를 설명하라.


답 보기



코드보기


top


3장 코드 속의 나쁜 냄새 - 긴 메소드

Refactoring/1~4장 : 2006.10.08 20:55


긴 메소드(Long Method)

최적의 상태로 가장 오래 살아 남는 객체 프로그램은 메소드가 짧다. 인디렉션(indirection)의 이점은 모두 짧은 메소드에 의해 제공되는 것이다.

인디렉션이란?

위키피디아의 정의 펼쳐 보기


원문에는 더 많은 내용이 나와있지만 간략히 요약하면 인디렉션이란 실제 값 자체가 아닌 그것을 참조하는 다른 이름을 사용하는 기능을 말한다는 것 같다. 이렇게 함으로써 프록시와 dynamic dispatch도 가능한 듯하다.

책의 82쪽에는 이런말이 있다.

컴퓨터 과학은 인디렉션 계층을 한 단계 더 만들면 모든 문제를 풀 수 있다고 믿는 학문이다.
-Dennis DeBruler

프로그래밍 초창기에는 서브루틴을 호출 할 때 오버헤드가 있어서 짧은 메소드 사용을 꺼려 했다고 한다. 그러나 요즘엔 프로세스 내부 호출(in-process call)로 인런 오버헤드가 상당부분 제거됐다고 한다. 하지만 서브루틴이 무엇인지 보려면 일단 그 부분으로 가보야 하기 때문에 사람들에게는 여전히 오버헤드가 존재한다. 그 오버헤드를 줄이기 위해서는 메소드의 이름을 잘 지어야 한다.

부분의 경우 메소드의 길이를 줄이기 위해서는 Extract Method를 사용한다. 메소드에 파라미터와 임시변수가 많으면 메소드를 추출하기가 어렵다.
  • 임시변수를 줄이기 위해서는 Replace Temp with Query를 사용할 수 있고
  • 긴 파라미터 리스트는 Introduce Parameter Object와 Preserve Whole Object로 짧게 할 수 있다.
  • 그럼에도 여전히 임시변수와 파라미터가 많다면 Replace Method with Method Object를 사용한다.
뽑아낼 코드 덩어리 식별 방법으로는 주석을 찾는 것이 좋다. 그리고 조건문과 루프 또한 추출이 필요하다는 신호를 준다. 조건식을 다루지 위해서는 Decompose Conditional을 사용한다. 루프의 경우는 Extract Method에서 예제로 다루고 있다.



top


Extract Class




두 개의 클래스가 해야 할 일을 하나의 클래스가 하고 있는 경우,
새로운 클래스를 만들어서 관련 있는 필드와 메소드를 예전 클래에서 새로운 클래스로 옮겨라.

동기

클래스가 너무 많은 메소드와 데이터를 가지고 있어서 쉽게 이해할 수 없는 경우가 있다. 이때 데이터의 부분집합과 메소드의 부분집합이 몰려 다닌다면 새로운 클래스로 분리할 수 있다는 좋은 신호다.

절차
  1. 클래스의 책임을 어떻게 나눌지를 결정하라.
  2. 분리된 책임을 떠맡을 새로운 클래스를 만든다.
    • 책임을 분리한 후 이전 클래스의 책임이 이름과 더 이상 맞지 않는다면, 이전 클래스의 이름을 변경한다.
  3. 이전 클래스에서 새로 만든 클래스에 대한 링크를 만든다.
    • 양방향 링크가 필요할지도 모른다. 그러나 필요해지기 전에는 새로 만든 클래스에서 이전 클래스로 링크를 만들지 마라.
  4. 옮기고자 하는 각각의 필드에 대해 Move Field를 사용한다.
  5. 각각의 필드를 옮길 때마다 컴파일, 테스트를 한다.
  6. Move Method를 사용해서 이전 클래스에서 새로 만든 클래스로 메소드를 옮긴다. 저수준 메소드(호출하기 보다는 호출되는 메소드)부터 시작해서 짐점 고수준의 메소드에 적용한다.
  7. 각각의 메소드를 옮길 때마다 컴파이르 테스트를 한다.
  8. 각 클래스를 검토하고, 인터페이스를 줄인다.
    • 양방향 링크를 가지고 있다면, 단바양 링크로 만들 수 있는지 알아본다.
  9. 새로운 클래스를 공개할지 결정한다. 새로운 클래슬 공개하기로 결정했다면, 참조 객체로 드러낼지 또는 불변성 값 객체(immutable value object)로 드러낼지를 결정한다.
예제

코드보기



위 예제 코드를 보시면 officeAreaCode와 officeNumber 데이타가 몰려 다니고 거기에 따른 getter와 setter들이 몰려 다니는 것을 볼 수 있습니다. 이것을 TelephoneNumber라는 class로 따로 뽑아내기 위해 먼저 TelephoneNumber 클래스를 만들고 Move Field를 사용합니다.

이클립스에서 클래스를 만들고 이동하고자 하는 필드를 선택하고 Alt + Shift + v 를 클릭하면 다음과 같은 팝업창이 뜨게 되고 이동시킬 클래슬 선택할 수 있다.


하지만 단순히 데이터만 옮겨 줄뿐 거기에 딸린 getter와 setter를 같이 옮겨 주지 않는 것은 조금 아쉬웠다. 이번에는 옮기고 싶은 것을 통째로 드래그를 해보았다. 그러나 메소드를 옮기는 일이 쉽지가 않은 듯하다.

결국 잘라내기 붙여넣기를 사용하여 아래와 같이 두개의 클래로 분리하였다.

Person 클래스 보기


TelephoneNumber 클래스 보기




'Refactoring > 5~ 12장 Catalogue' 카테고리의 다른 글

Form Template Method  (0) 2007.07.06
Extract Super Class  (0) 2007.07.05
Introduce Parameter Object  (0) 2007.06.06
Extract Class  (0) 2006.10.08
Substitute Algorithm  (0) 2006.10.07
Pull Up Method  (0) 2006.10.07
Extract Method  (0) 2006.10.06
top


Substitute Algorithm



알고리즘을 보다 명환한 것으로 바꾸고 싶을 때는, 메소드의 몸체를 새로운 알고리즘으로 바꾼다.



위 코드를 보시면 people이라는 문자열 배열안에 Don, John, Kent라는 이름이 있는지 확인하고 만약에 있다면 해당하는 이름을 반환하는 메소드 입니다. 이것을 좀더 명확하게 표현하자면 아래와 같이 변경할 수 있습니다.


동기

어떤 것을 할 때 여러가지 방법이 있다면 그 중에 쉬운 것이 있다면 그것을 선택할 것입니다. 알고리즘도 마찬가지로 여러가지 방법 중에 좀더 명확한 방법을 선택하여야 합니다. 이 작업을 할 때는 가능한 많이 메소드를 분해해 두어야 합니다. 크고 복잡한 알고리즘을 바꾸는 것은 어렵기 때문이죠.

절차
  1. 대체 알고리즘을 준비한다. 적용하여 컴파일한다.
  2. 알고리즘을 테스트한다. 만약 결과가 같다면 작업은 끝난 것이다.
  3. 만약 결과가 같지 않다면, 테스트에서 비교하기 위해 예전의 알고리즘을 사용하여 디버깅한다.
    • 예전 알고리즘과 새 알고리즘에 대해 각각의 테스트 케이스를 실행시키고 두 결과를 본다. 이것은 어떤 테스트 케이스가 어떻게 문제를 일으키는지 찾는 데에 도움을 줄 것이다.

'Refactoring > 5~ 12장 Catalogue' 카테고리의 다른 글

Form Template Method  (0) 2007.07.06
Extract Super Class  (0) 2007.07.05
Introduce Parameter Object  (0) 2007.06.06
Extract Class  (0) 2006.10.08
Substitute Algorithm  (0) 2006.10.07
Pull Up Method  (0) 2006.10.07
Extract Method  (0) 2006.10.06
top


Pull Up Method



동일한 일을 하는 메소드를 여러 서브클래스에서 가지고 있다면, 이 메소드를 수퍼클래스로 옮겨라.


동기
  • 중복된 동작을 제거해야 하기 때문입니다. 한쪽은 바꿨는데 다른 쪽은 그대로 라면 수정하기도 번거롭고 나중에 버그가 될 가능성이 높을 것입니다.
Pull Up Method가 필요한 경우는 서브클래스에 있는 메소드가 수퍼클래스에 있는 메소드를 오버라이드하는데 여전히 같은 일을 하는 때입니다.
이 때 성사긴 것은 서브클래스의 메소드에서 수퍼클래스에 없는 메소드나 변수를 참조할 때입니다. 이 때는 메소드도 같이 일반화하거나 수퍼클래스에 추상 메소드를 만들 수 있습니다. (이럴 때 메소드의 시그너쳐를 변경하거나 위임 메소드(?)를 만들어야 할 지도 모른다고 합니다.)
비슷하지만 동일한 메소드가 아닐때는 Form Template Method를 사용할 수 있을지도 모릅니다.

절차
  1. 메소드들을 조사해서 그것이 동일한지 확인한다.
    • 만약 같은 일을 하지만 코드가 다른경우 메소드들 중 하나에 Substitute Algorithm을 사용하여 코드가 동일하게 만들어라.
  2. 메소드가 서로 다른 시그너쳐를 가지고 있다면, 수퍼 클래스에서 사용하고 싶은 시그너처로 바꾼다.
  3. 수퍼클래스에 새로운 메소드를 만들고, 메소드들 중 하나의 몸체를 새로운 메소드에 복사해서 적절히 수정한 후 컴파일한다.
    • 그 메소드가 수퍼클래스에 없는 메소드를 호출한다면, 수퍼클래스에 추상 메소드를 만들어라.
    • 수퍼클래스에 없는 필드를 사용한다면, Pull Up Field 또는 Self Encapsulate Field를 사용하고 추상 get메소드를 선언하여 사용하라.
  4. 서브클래스 중 하나를 골라서 메소드를 삭제한다.
  5. 컴파일, 테스트를 한다.
  6. 수퍼 클래스 메소드만 남을 떄까지 서브클래 메소드를 삭제하고 테스트를 계속한다.
  7. 필요한 타입을 수퍼클래스로 변경할 수 있는지 살펴보기 위해서 이 메소드의 호출부를 살펴본다.
예제

두 개의 서브클래스(RegularCustomer, PreferredCustomer)를 가진 Customer 클래스가 있다. 그리고 두 서브클래스에 있는 createBill 메소드는 둘다 다음과 같은 코드로 동일하다.

void createBill(Date date) {
   double chargeAmount = chatgeFor(lastBillDate, date);
   addBill(date, charge);
}


색칠 된 부분의 코드가 모두 중복되고 있지만 둘 중에 녹색으로 칠해진 chargeFor 메소드는 위로 복사해서 옮길 수가 없습니다. 클래스 몸체가 서브클래스마다 다르기 때문입니다. 이때는 수퍼클래스에 추상 메소드로 선언합니다.


addBill 메소드는 수퍼클래스로 옮겼고 chargeFor 메소드는 수퍼클래스에 추상 메소드로 선언했습니다.(UML 표기상 추상 메소드는 이탤릭체로 표기합니다. 그리고 추상 메소드를 가진 클래스는 추상 클래스가 되기 때문에 클래스 이름도 이탤릭체로 표기했습니다.)

'Refactoring > 5~ 12장 Catalogue' 카테고리의 다른 글

Form Template Method  (0) 2007.07.06
Extract Super Class  (0) 2007.07.05
Introduce Parameter Object  (0) 2007.06.06
Extract Class  (0) 2006.10.08
Substitute Algorithm  (0) 2006.10.07
Pull Up Method  (0) 2006.10.07
Extract Method  (0) 2006.10.06
top


Extract Method



그룹으로 함꼐 묶을 수 있는 코드 조각이 있으면 코드의 목적이 잘 드러나도록 메소드의 이름을 지어 별도의 메소드로 뽑아낸다.


동기

지나치게 긴 메소드나 주석이 필요한 코드를 보면 그 부분을 하나의 메소드로 뽑는다.(Martin Fowler 曰).
  1. 메소드가 잘게 쪼개져 있을 때 다른 곳에서 사용하기 좋다.
  2. High-level의 메소드를 보면 주석을 읽는 것 같은 느낌이 들도록 할 수 있다.
  3. 오버라이드 하는 것도 훨씬 쉽다.
작은 메소드들은 작명을 잘했을 때 그 진가를 드러내므로 이름을 잘 짓도록 하자.

절차
  1. 메소드를 새로 만들고, 무엇을 하는지를 나타내도록 이름을 정한다.
  2. 원래 메소드에서 뽑아내고자 했던 부분을 복사하여 새 메소드로 옮긴다.
  3. 원래 메소드에서 사용되고 있는 지역변수가 뽑아낸 코드에 있는지 확인한다. 있으면 새로운 메소드의 임시변수로 선언한다.
  4. 뽑아낸 코드 안에서 지역변수의 값이 변화는지 확인한다. 만약에 하나의 지역변수만 수정 된다면, 뽑아낸 코드를 질의(query)로 보고, 수정된 결과를 관련된 변수에 대입할 수 있는지 본다. 이렇게 하는 것이 이상하거나, 값이 수정되는 지역변수가 두개 이상 있다면 쉽게 메소드로 추출할 수 없는 경우이다. 이럴 때는 Split Temporary Variable을 사용한 다음 다시 시도해야 한다. 임시변수는 Replace Temp with Query로 제거할 수 있다.
  5. 뽑아낸 코드에서 읽기만 하는 코드는 파라미터로 넘긴다.
  6. 지역변수와 관련된 사항을 다룬 후에는 컴파일을 한다.
  7. 원래 메소드에서 새로 만든 메소드를 호출하도록 바꾼다.
  8. 컴파일과 테스트를 한다.
예제 : 지역변수가 없는 경우
위 코드에서 처음에 수정할 부분은 노란색으로 표시된 부분입니다. 매우 단순한 경우이기 때문에 잘라서 새로운 메소드로 붙여넣고 저 노란 부분에서 새로운 메소드를 호출하면 되겠습니다.

이클립스에서 리팩토링 할 부분을 드래그 한 상태에서 Alt + Shift + m 을 클릭합니다. 그리고 새로운 메소드의 이름을 적어 줍니다.
그리고 OK 버튼을 클릭하면...
짠... 멋지게 처리해 주는 군요.. 주석만 빼고. ^^; 주석은 Ctrl + d (행삭제)를 사용하여 지워줍시다.

예제 : 지역변수가 포함되어 있는 경우

지역변수가 옮겨질 코드에 들어있는 경우 중에 가장 쉬운 경우가 바로 그 지역변수를 읽기만 하는 경우입니다.  이런 경우에는 변수를 파라미터로 넘겨주면 됩니다.


이번에는 저 노란 부분을 메소드로 뽑아 낼 것입니다. 보아하니 _name이라는 변수와 outstanding이라는 변수를 사용하고 있는데 outstanding만 지역변수고 _name은 필드네요. 따라서 outstanding만 넘겨주면 되겠습니다. 메소드  이름은 printDetails로 하는군요. 이번에도 뽑아내고 싶은 부분을 드래그 하고 Alt + Shift + m ...와오..


똑똑한 이클립스... 파라미터가 필요한 줄 알고.. 그것도 딱 지역변수만.. 아 정말 똑똑하네요. 역시 OK만 누르면..


대단합니다. 이클립스.

예제 : 지역변수에 다른 값을 여러 번 대입하는 경우 (마지막입니다. :)

이 경우는 복잡한데 이런 경우 중 임시변수에 대해서만 생각을 해봅니다. (파라미터에 다른 값을 대입하는 코드가 있다면 즉시 Remove Assignments to Parameters를 적용해야 한다.)만약에 그 임시변수가 뽑아내는 코드에서만 사용이 된다면 뽑아낸 코드로 그냥 그 임시변수까지 이동하면 되겠습니다. 하지만 그 임시변수가 뽑아내는 코드 밖에서도 사용이 된다면 뽑아낸 코드에서 그 값을 리턴해 주어야 합니다.
노란 부분을 보시면 each라는 임시변수는 뽑아내게 될 코드 안에서만 사용되는 것을 볼 수 있습니다. 그렇기 때문에 그냥 이동 시키면 되겠지요

위에 보이는 노란 부분이 뽑아 낼 부분입니다. 저 중에 each는 노란 부분에서만 쓰이기 때문에 당연히 그냥 빼내는 메소드로 이동하면 되겠습니다. 하지만 문제는 outstanding인데 이 변수가 printDetails 메소드에서 사용되고 있습니다. 따라서 위의 코드에서 계산 부분의 코드를 뽑아내고 outstaning의 값을 리턴하도록 합니다.

이번에는 이클립스의 덕을 잘 못보겠더군요.

임시변수가 너무 많아 코드를 뽑아내기 어려울 때는 Replace Temp with Query를 사용하여 임시변수를 줄이고 어떻게 해봐도 여전히 난처할 때는 Replace Method with Method Object를 사용한다. 이 리팩토링은 임시변수가 얼마나 많든, 그리고 뭘 하든 상관하지 않는다.


'Refactoring > 5~ 12장 Catalogue' 카테고리의 다른 글

Form Template Method  (0) 2007.07.06
Extract Super Class  (0) 2007.07.05
Introduce Parameter Object  (0) 2007.06.06
Extract Class  (0) 2006.10.08
Substitute Algorithm  (0) 2006.10.07
Pull Up Method  (0) 2006.10.07
Extract Method  (0) 2006.10.06
top


3장 코드 속의 나쁜 냄새 - 중복된 코드

Refactoring/1~4장 : 2006.10.06 22:00


1장2장을 통해서 리팩토링이 어떻게 돌아가는지 알게 됐습니다. 하지만 리팩토링을 어떻게 하는지 안다고 해서 할 수 있는 것은 아닙니다. 언제 해야 하는지를 알아야 하는데... 그게 어려운 것 같네요. 3장을 Kent Beck이랑 Martin Fowler가 썼는데도 명확한 시점이라기 어떤 "냄새"가 날 때 라는 모호한 시점을 제기했네요. 경험적으로 인간의 직관보다 나은 기분은 없기 때문이라고 합니다. 그럼 이제 부터 어떠한 "냄새"들이 있으며 그런 "냄새"들은 어떻게 제거할지 살펴봅시다.

중복된 코드(Duplicated Code)

악취 중에 일등이 중복된 코드라고 하네요.
  • 한 클래스의 서로 다른 두 메소드 안에 같은 코드가 있는 경우 => Extract Method로 메쏘드로 뽑아내고 호출하도록 변경.
  • 동일한 슈퍼클래스를 갖는 두 서브 클래스에서 같은 코드가 나타나는 경우 => 양쪽 클래스에서 Extract Method를 한 뒤 Pull UP Method를 사용할 수 있슴.
  • 만약 메소드들이 같은 작업을 하지만 다른 알고리즘을 사용한다면 => 더 명확한 것을 선택하여 Substitute Algorithm을 사용할 수 있습니다.
  • 서로 관계가 없는 두 클래스에서 중복된 코드가 있는 경우에는 한쪽 클래스에서 Extract Class를 사용한 다음 양 쪽에서 이 클래스를 사용하도록 하는 것을 고려할 것.
  • 다른 가능성 : 메소드가 클래스 중 하나에 포함되어 있고, 다른 클래스에서 호출되어야 하거나 또는 세 번째 클래스에 속하는 그 메소그가 원래 두 클래스에서 참조되어야 하는 경우. => 뭔말인지...전혀 감이 안잡히는데요;;;


top


1장 리팩토링, 첫 번째 예제

Refactoring/1~4장 : 2006.10.03 01:01


먼저 리팩토링이란? 외부 동작을 바꾸지 않으면서 내부 구조를 개선하는 방법으로, 소프트웨어 시스템을 변경하는 프로세스이다. 이것은 버그가 끼어 들 가능성을 최소화하면서 코드를 정리하는 정형화된 방법이다.

"코드가 작성된 후에 디자인을 개선한다."

물론 그 코드는 디자인을 거쳐 작성이 되었겠지만 코드가 디자인을 잘 따르지 않았거나 디자인이 잘 못 됐을 수도 있기 때문에 어감이 반대로 된 듯해도 맞는 말이다.
새로운 기능을 추가해야 하는데 프로그램의 코드가 새로운 기능을 추가하기 쉽도록 구조화되어 있지 않은 경우에는 먼저 리팩토링을 해서 프로그램에 기능을 추가하기 쉽게 하고, 그 다음에 기능을 추가한다.

유지보수에는 네 종류의 유지보수가 있는데 기억이 가물가물 하지만 억지로라도 떠올려 보면
에러가 발생하여 수정하는 유지보수(corrective maintanance)
환경의 변화에 따라 적응시키는 유지보수(adaptive maintanance)
미래에 발생할 문제를 미리 예방하는 유지보수
완벽을 기하기 위한 기능을 추가하는 유지보수 가 있다고 배웠다.(시스템 분석 및 설계 시간에...)
여기서는 네번째 유지보수 측면을 고려한 듯하다. 하긴 리팩토링 자체를 유지보수로 본다면 위 네가지 모두 고려대상인 듯하다.

리팩토링을 시작하기 전에 견고한 테스트 세트를 가지고 있는지 확인하라. 이 테스트는 자체 검사여야 한다.

테스트의 중요함을 할 수 있다. 요새 Agile Java 책을 스터디 하면서 TDD(Test Driven Development)를 공부하고 있는데 하나의 습관인지라 역시 쉽지 않다. 습관을 바꾸는게 가장 힘든일인듯 하다.(내 글씨는 악필인데 초등학교 때 서예학원도 다녀보고 맞기도 엄청 맞았지만 아직도 악필이다 --)

리팩토링은 작은 단계로 나누어 프로그램을 변경한다. 실수를 하게 되더라도 쉽게 버그를 찾을 수 있다.

조금씩 고쳐 나갈 때마다 계속해서 test를 해줘야 한다. 그래야 쉽게 버그도 찾을 수 있고 오히려 한번에 왕창 해두고 버그가 발생해서 어디가 문제인지 찾는데 시간이 더 오래 걸린다.

컴퓨터가 이해할 수 있는 코드는 어느 바보나 다 짤 수 있다. 좋은 프로그래머는 사람이 이해할 수 있는 코드를 짠다.

아~ 감명깊은 말이다. 주석의 중요함에 대해 써있는 책을 몇 번 봤었다. 그러나 주석이 잘 달린 프로그램 보다는 주석이 없어도 이해가 되는 프로그램인 듯하다. 물론 주석도 없고 이해도 안되는 코드는...최악이겠지만 말이다. 그러려면 역시 작명에도 신경을 잘 써야 하지만 대부분의 프로그램 언어가 영어인 관계로 작명+작문 이 합쳐지게 된다는 태생적인 문제가 있다.(영어 공부도 열심히?ㅋ)

오늘은 여기까지 보고 자야겠다. 내일 데이트를 해야한다.

Good Night!

1장을 다 보았다.(자랑인가? ㅋ) 보기만 했고 손으로 안따라 해봤기 때문에 아직 제대로 본건 아니다.

추가할 요약사항이 있어서 수정한다.

리팩토링의 리듬!

테스트 -> 조금 수정 -> 테스트 -> 조금 수정

top