달력

072010  이전 다음

public abstract String findByDong(String dong);

반환값이 String이었는데 여러 개의 객체 배열로 반환받고 싶다. 그리고, static 메소드로 변경하고 싶다. 이럴 때도 이클립스 리팩토링 기능을 이용할 수 있다. 편집기에서 메소드 부분(주석 포함)을 선택하거나 패키지 탐색기에서 메소드를 선택하고 오른쪽 마우스를 누른다.


Alt+Shift+C 조합키를 단축키로 사용할 수 있음을 알 수 있다.


접근 지정자, 반환값 유형, 매개변수, 예외, 메소드 이름 등 모든 것을 바꿀 수 있다. ^^; 아쉽게도 static 은 여기서 부여할 수 없다. 변경을 적용하기 전에 Preview 버튼을 선택하면 미리 보기를 통해 변경되는 부분을 확인할 수 있다.


인터페이스와 이를 구현한 클래스에 적용이 미침을 알 수 있고, 코드 비교를 통해 구체적으로 어떻게 바뀌는지 확인할 수 있다.

원문 작성 일시: 2004/11/19 (금) 13:57
Posted by 영회
리팩토링 카다로그에 보면 Extract Interface가 있다.
Several clients use the same subset of a class's interface, or two classes have part of their interfaces in common.

동일한 클래스 인터페이스(public method)를 여러 클라이언트가 공유하는 경우, 혹은 두 개 이상의 클래스가 동일한 인터페이스를 공통으로 갖고 있는 경우 이를 인터페이스를 뽑아낸다.




이클립스에서 이러한 기능을 지원해준다. 물론, 도와주는 것이지 판단은 사람의 몫이다. ^^;
아래와 같은 코드가 있다고 하자.

public class ZipcodeInDB {
  public String findByDong(String dong){
      
       return "";
      
  }
}

내용은 없지만, findByDong이 다른 클래스에도 있을 수 있어, Zipcode라는 인터페이스를 만들어보겠다. 패키지 탐색기에서 ZipcodeInDB를 선택하고 오른쪽 마우스를 누르면, Refactor > Extract Interface 라는 메뉴를 선택할 수 있다.


Interface name란에 원하는 인터페이스(자바 Inteface) 이름을 넣고, 인터페이스에 포함시킬 메소드를 선택한다. 가장 상당은 옵션은 현재 ZipcodeInDB라는 클래스를 참조하는 코드에서 ZipcodeInDB 대신에 Zipcode 타입으로 참조하게 하고자 할 때 선택한다.


이렇게 하면, 당연히 코드에 변경이 일어나야 한다. 확인해보자.

public class ZipcodeInDB implements Zipcode
ZipcodeInDB에는 "implments Zipcode" 부분이 추가되었고, 인터페이스 코드는 새로 생성되었다.

public interface Zipcode {
  public abstract String findByDong(String dong);
}

인터페이스를 외부에 공개하기 위해서 패키지를 변경하겠다. 이때는 코드를 직접 수정하는 것보다 역시 이클립스에서 제공하는 리팩토링 기능을 확용하는 것이 좋다. Refactor > Move 메뉴를 통해 패키지를 변경하게 되면, 인터페이스를 참조하는 기존 코드의 패키지 명시부분이 모두 자동으로 바뀐다. 옮기고자 하는 패키지가 이미 존재할 경우에는 패키지 탐색기에서 인터페이스를 단순히 드래그 앤 드롭하는 것으로도 동일한 작업이 가능하다.


원문 작성 일시: 2004/11/19 (금) 13:27
Posted by 영회

문자열을 블럭 지정하고 Alt+Shift+L

이클립스 리팩토링 기능을 쓰지 않고
'블럭 지정 > Ctrl+X > Ctrl+V > 타이핑'하는 것이 더 빠를 수도 있겠다.

그런데 왜 이렇게 하냐고 물으면
1) 익숙한 것과의 이별(리팩토링과의 새로운 만남을 위해선 이별이 필요하다.)
2) final을 빠뜨리지 않을 수 있는 기회를 제공[각주:1]
3) 기타 의견: Alt+Shift+L을 누르고 빠르게 타이핑하는 모습이 숙달되면.. 왠지 간지가 난다. ^_____^
  1. '느닷없이 왠 final이냐' 하시는 분이라면 자바의 final 키워드의 용도를 읽어보삼 [본문으로]
Posted by 영회
공통 서비스 추출을 수행하면 문제가 하나 발생한다. 애초에 메소드가 다음과 같았기 때문에 MemberService에서는 바로 이를 활용할 수 없다는 점이다.
public List<Post> findByDate(Date from, Date to);
public List<Post> findByDate(Integer beforeOrAfter, Date timestamp);
public List<Post> findByDate(Integer dateCode, Integer date);

한가지 방법은 타입을 없애고 Object의 List로 받는 방법이다.
public List findByDate(Date from, Date to);
public List findByDate(Integer beforeOrAfter, Date timestamp);
public List findByDate(Integer dateCode, Integer date);

이렇게 되면 엄격한 타입 체크가 불가능한 동시에 클라이언트에게 명시적 casting의 부담을 준다.

자바 5를 사용한다면 Generics를 활용해서 이를 개선할 수 있다.
public interface FindByDateService<T> {
/**
  * 특정 Date 이후를 검색
  */
public static final Integer AFTER = 5;
/**
  * 특정 Date 이전을 검색
  */
public static final Integer BEFORE = 4;

/**
  * 시작 Date와 종료 Date 사이에 작성된 T 객체를 얻는다.
  * @param from 시작 Date
  * @param to 종료 Date
  * @return 조건을 만족하는 T 객체 List
  */
public List<T> findByDate(Date from, Date to);

/**
  * 특정 Date를 기준으로 이전 혹은 이후의 T 객체를 얻는다.
  * <p>BEFORE 이전</p>
  * <p>AFTER 이후</p>
  * @param beforeOrAfter
  * @param timestamp
  * @return 조건을 만족하는 T 객체 List
  */
public List<T> findByDate(Integer beforeOrAfter, Date timestamp);

/**
  * 주어진 연을 기준으로 T 객체 반환
  * @param year 연
  * @return 조건을 만족하는 T 객체 List
  */
public List<T> findByDate(Integer year);
/**
  * 주어진 연, 월을 기준으로 T 객체 반환
  * @param year 연
  * @param month 월
  * @return 조건을 만족하는 T 객체 List
  */
public List<T> findByDate(Integer year, Integer month);
/**
  * 주어진 연, 월, 일을 기준으로 T 객체 반환
  * @param year 연
  * @param month 월
  * @param day 일
  * @return 조건을 만족하는 T 객체 List
  */
public List<T> findByDate(Integer year, Integer month, Integer day);
}

먼저, 인터페이스 정의에서 <T>로 타입을 명기한다. 그리고, 하위 인터페이스 정의를 다음과 같이 변경한다.
public interface MemberService extends FindByDateService<Member>
public interface ForumService extends FindByDateService<Post>

MemberService를 구현한 클래스는 Quick Fix를 수행하면 아래와 같이 메소드 템플릿이 적용될 것이다.
public List<Member> findByDate(Date from, Date to) {
// TODO Auto-generated method stub
return null;
}
public List<Member> findByDate(Integer beforeOrAfter, Date timestamp) {
// TODO Auto-generated method stub
return null;
}
public List<Member> findByDate(Integer year) {
// TODO Auto-generated method stub
return null;
}
public List<Member> findByDate(Integer year, Integer month) {
// TODO Auto-generated method stub
return null;
}
public List<Member> findByDate(Integer year, Integer month, Integer day) {
// TODO Auto-generated method stub
return null;
}
Posted by 영회
테스트 메소드를 작성하다 보니 중복이 발생한다. Post 객체 검색을 위한 테스트 코드의 일부가...
 // 2. 범위 검색: created
 // 2.1 연
 Integer year = 2006;
 posts = forumService.findByDate(ForumService.YEAR, year);
 for(Post post : posts){
  Calendar date = new GregorianCalendar();
  date.setTime(post.getCreated());
  assertEquals(year.intValue(), date.get(Calendar.YEAR));
 }
 // 2.2 월
 Integer month = Calendar.AUGUST;
 posts = forumService.findByDate(ForumService.MONTH, month);
 for(Post post : posts){
  Calendar date = new GregorianCalendar();
  date.setTime(post.getCreated());
  assertEquals(month.intValue(), date.get(Calendar.MONTH));
 }
 // 2.3 일
 Integer day = 15;
 posts = forumService.findByDate(ForumService.DAY, day);

위와 같을 때, 전혀 다른 객체인 Member의 경우도...
 // 2. 범위 검색: joined
 // 2.1 연
 Integer year = 2006;
 members = memberService.findByDate(MemberService.YEAR, year);
 // 2.2 월
 // 2.3 일
  // 2.4 이전
 // 2.5 이후
 // 2.6 특정 범위

겹치는 부분 즉, 중복의 냄새가 초장에 난다.



겹쳐지는 부분을 상위 인터페이스로 정의한다.



이클립스를 활용하는 경우 인터페이스를 새로 만들고 나서 ForumService 인터페이스가 이를 상속하게 하고 나서
public interface ForumService extends FindByDateService

드래그앤드롭으로 상수(contants)와 메소드를 상위 인터페이스로 이동시키면 된다.

이젠 MemberService는 추가적인 상수나 메소드 정의 없이 상속을 이용하면 된다.
public interface MemberService extends FindByDateService


Posted by 영회
재방송(원문 작성 일시:2005/06/07 (화) 10:18)

리팩토링(Refactoring)의 다른 말이 될 수도 있고, 리팩토링 방안 일부가 될 수도 있고
이걸 또 두개의 카테고리로 나눴는데 말장난 같기도 하지만
체크리스트 정도로는 쓰일 수 있을 듯

Code cleaning :
First, I run a quick and fully automated analysis of the code, with just a few rules:
  • remove dead code (classes, methods).
  • remove unused method parameters, variables
  • remove useless variable initialisation
  • remove useless Casting
  • tighten visibility (public => private)
  • move tests code to a separate code tree
  • modernize (Java1.4 => Java5)

Code clarifying :
I examine the code structure, starting with the 2-mile high view of the project, and going down. Along the way, I would perform :
  • rename : (package, class, method, parameter
  • move : package, classes, methods, ...
  • extract/inline method
  • introduce variable
Posted by 영회
JetBrain IDEA 를 쓰는 이들이 refactor 기능에 매료된다고 하는데
이클립스 리팩토링도 많이 개선된 것 같다.
평소 refactor > rename 정도만 쓰다가
Extract Method 를 썼는데 아주 intelligent 하네. :)

메소드 내부에 아래와 같은 코드 블럭이 있었다.

// 테스트를 수행자의 작업 디렉토리 루트를 읽어옴
Properties properties = new Properties();
File file = new File("test/testng.properties");
properties.load(new FileInputStream(file));
String projectRoot = properties.getProperty("project.root");

메소드 내부를 간결하게 하기 위해서 private method 로 분리하고자 할 때
위 부분 전체를 블럭 지정하고 Alt+Shift+M 을 선택


대화상자에서 적절한 이름을 붙여주면 코드가 아래와 같이... :)
// 테스트를 수행자의 작업 디렉토리 루트를 읽어옴
String projectRoot = readTesterWorkspace();
Posted by 영회
Spring을 사용하다 보면 협업 객체의 IoC 기능 활용을 위해 setter가 요구된다.
public class ArticleListController extends AbstractController{
private ArticleLinkDao articleLinkDao;

@Override
protected ModelAndView handleRequestInternal(HttpServletRequest request,
...
}

public void setArticleLinkDao(ArticleLinkDao articleLinkDao) {
this.articleLinkDao = articleLinkDao;
}
}

그런데 이러한 클래스가 또 만들어야 한다고 가정해보자. 중복이 발생한다.
public class ThemePageController extends AbstractController{
private ArticleLinkDao articleLinkDao;

@Override
protected ModelAndView handleRequestInternal(HttpServletRequest request,
...
}

public void setArticleLinkDao(ArticleLinkDao articleLinkDao) {
this.articleLinkDao = articleLinkDao;
}
}

중복을 막기 위해서 BaseController를 만들어 이들을 상속하게 하자.

1. AbstractController -> BaseController로 변경
public class ArticleListController extends BaseController{
private ArticleLinkDao articleLinkDao;

@Override
protected ModelAndView handleRequestInternal(HttpServletRequest request,
...
}

public void setArticleLinkDao(ArticleLinkDao articleLinkDao) {
this.articleLinkDao = articleLinkDao;
}
}

2. 빠른 수정(Quick fix)으로 BaseController 생성


BaseController가 AbstractController를 상속해야 함을 잊지 말자.

3. Refactor > Pull up... 메뉴 선택


상위 클래스로 올릴 속성과 메소드를 선택한 후에 Finish 혹은 Next를 눌러서 미리보기를 하고 Finish.
리팩토링을 하고 나면 기분까지 개운(?)해진다. ^^;

4. BaseController 변경
public abstract class BaseController extends AbstractController {
protected ArticleLinkDao articleLinkDao;
@Override
abstract protected ModelAndView handleRequestInternal(HttpServletRequest request,
  HttpServletResponse response) throws Exception;
public void setArticleLinkDao(ArticleLinkDao articleLinkDao) {
this.articleLinkDao = articleLinkDao;
}
}

BaseController를 abstract로 만들어 반드시 handleRequestInternal을 구현하게 하고, 매개변수 이름을 request, response과 같이 보다 의미있게 바꿔주는 센스를 발휘하면, 마법사 혹은 빠른 수정으로 상속 객체를 생성할 때 이를 그대로 사용하게 된다. ^^
Posted by 영회