파트 3: Refactoring Toward Deeper Insight

리팩터링 수준

다른 책들은 너무 로우 레벨로 얘기하는데 그 보다는 도메인에 대한 통찰을 통한 또는 모델이 말하고자 하는 바를 분명하게 하는 것이 시스템의 실용성에 더 지대한 영향을 주는 리팩터링 이라는 이야기..

The refactorings that have the greatest impact on the viability of the system are those motivated by new insights into the domain or those that clarify the model’s expression through the code.

Deep Model

모델을 좀 더 심도있게 바라보도록.. 피상적인 모습으로만 축출한 모델이 다가 아니라는거…

A deep model provides a lucid expression of the primary concerns of the domain experts and their most relevant knowledge while it sloughs off the superficial aspects of the domain.

Supple Design

변경을 반영할 수 있는 설계가 되어야 한다.

In a process of constant refactoring, the design itself needs to support change.

Deep Model과 Supple Design은 Model-Driven Design을 뒷받침하는 두 개의 축이다.

A MODEL-DRIVEN DESIGN stands on two legs. A deep model makes possible an expressive design. At the same time, a design can actually feed insight into the model discovery process when it has the flexibility to let a developer experiment and the clarity to show a developer what is happening.

The Discovery Process

좋은 모델을 찾아가는 과정은 개인의 창의성에 달려있기도 하지만, 여러 패턴을 따를 수도 있겠다.

You will usually depend on creativity and trial and error to find good ways to model the concepts you discover, but sometimes someone has laid down a pattern you can follow.

가끔은 이론적인 얘기로 머리를 환기시키는 것도 좋네요.

서비스 계층 다이어트 시키기

    private void makeNewMonthlySalesSum(Supp supp, Date date) {
        MonthlySalesSum monthlySalesSum = new MonthlySalesSum();
        monthlySalesSum.setDate(date);
        monthlySalesSum.setSupp(supp);
        this.dao.add(monthlySalesSum);

        MonthlySalesSumDetail detail = null;
        for (Branch branch : branchDao.getBranchBySupp(supp.getId())) {
            DTO dto = this.dao.makeMonthlySalesSumDTO(branch, date);
            detail = makeNewMonthlySalesSumDetail(dto, branch, date);
           monthlySalesSum.addSalesCount(detail.getSalesCount());
            monthlySalesSum.addSellingQty(detail.getSellingQty());
            monthlySalesSum.addProfit(detail.getTotalProfit());
            monthlySalesSum.addSuppUnitPrice(detail.getTotalSuppUnitPrice());
            monthlySalesSum.addSuppUnitPriceWithoutText(detail.getTotalSuppUnitPriceWithoutTex());
            if(detail.getSalesCount() > 0){
                detail.setMonthlySalesSum(monthlySalesSum);
                monthlySalesSumDetailDao.add(detail);
            }
        }
        
    }

    private MonthlySalesSumDetail makeNewMonthlySalesSumDetail(DTO dto, Branch branch, Date date) {
        MonthlySalesSumDetail detail = new MonthlySalesSumDetail();
        detail.setBranch(branch);
        detail.setDate(date);
        if (dto != null) {
            detail.setSalesCount(dto.getSalesCount());
            detail.setSellingQty(dto.getSellingQty());
            detail.setTotalProfit(dto.getTotalProfit());
            detail.setTotalSuppUnitPrice(dto.getTotalSuppUnitPrice());
            detail.setTotalSuppUnitPriceWithoutTex(dto.getTotalSuppUnitPriceWithoutTex());
        }
        return detail;
    }

서비스 클래스에 있는 배치 작업 용 메소드들 입니다. 종합 정보를 생성하는데 상세 정보와 함께 묶어서 작업을 하고 있는데 코드가… 참… 거시기 합니다. 클래스에 들어있는 코드의 절반을 이 두 개의 메소드가 잡아먹고 있었습니다.

자세히 보니까 어떤 객체에서 값들을 꺼내서 다른 객체에 전달하는 일들이 전부 입니다. 흠… 옮길 수 있겠다!! 라는 생각이 젤 먼저 들었습니다. 잠시 뒤.. 오.. 당연히 옮겨야 되겠는데? 사실 저렇게 세팅해주는 일이 원래 저 서비스 클래스가 할 일은 아니니까… 걍 자기가 알아서 값들 세팅하면 되지 왜 서비스 계층이 저렇게 일일히 세팅하게 했을까나;; ㅠ.ㅜ (제가 코딩한 겁니다.ㅋㅋ)

그래서 코드를 고쳤습니다.

    private void makeNewMonthlySalesSum(Supp supp, Date date) {
        MonthlySalesSum monthlySalesSum = new MonthlySalesSum();
        monthlySalesSum.setDate(date);
        monthlySalesSum.setSupp(supp);
        this.dao.add(monthlySalesSum);
       
        MonthlySalesSumDetail detail = null;
        for (Branch branch : branchDao.getBranchBySupp(supp.getId())) {
            DTO dto = this.dao.makeMonthlySalesSumDTO(branch, date);
            detail = makeNewMonthlySalesSumDetail(dto, branch, date);
            monthlySalesSum.applyDetail(detail);
            if(detail.getSalesCount() > 0){
                detail.setMonthlySalesSum(monthlySalesSum);
                monthlySalesSumDetailDao.add(detail);
            }
        }
    }

    private MonthlySalesSumDetail makeNewMonthlySalesSumDetail(DTO dto, Branch branch, Date date) {
        MonthlySalesSumDetail detail = new MonthlySalesSumDetail();
        detail.setBranch(branch);
        detail.setDate(date);
        if (dto != null)
            detail.setByDTO(dto);
        return detail;
    }

코드를 1/3 가량 줄일 수 있었습니다. 야호~

서비스 계층은 가볍게 도메인 계층은 두툼하게…
자기가 할 일은 자기가 하자.

엑셀 시트 복사하기(with POI) + 리팩터링

    private void copySheet(HSSFSheet from, HSSFSheet to) {
        HSSFRow firstRow = from.getRow(0);
        HSSFRow secondRow = from.getRow(1);
        HSSFRow thirdRow = from.getRow(2);
        
        HSSFRow firstRow2 = to.createRow(0);
        HSSFRow secondRow2 = to.createRow(1);
        HSSFRow thirdRow2 = to.createRow(2);
        
        Iterator<HSSFCell> iterator = firstRow.cellIterator();
        short col = 0;
        while(iterator.hasNext())
            addCell(firstRow2, col++, iterator.next().getStringCellValue());
        
        col = 0;
        iterator = secondRow.cellIterator();
        while(iterator.hasNext())
            addCell(secondRow2, col++, iterator.next().getStringCellValue());

        col = 0;
        iterator = thirdRow.cellIterator();
        while(iterator.hasNext())
            addCell(thirdRow2, col++, iterator.next().getStringCellValue());
    }

무려 세 번이나 중복되고 있지만, 일단 기능이 제대로 돌아가는지부터 보려고 허겁 지겁 코딩을 하고 결과를 확인해 보니 괜춘하네요. 여기서 그만 둘까도 생각했지만, 에이~ 뭐 시간도 많은데 저런걸 그냥 두긴 뭐하다 싶어서 리팩터링..

    private void copySheet(HSSFSheet from, HSSFSheet to, int fromRowCnt, int toRowCnt) {
        HSSFRow fromRow = null;
        HSSFRow toRow = null;
        for(int i = fromRowCnt ; i <= toRowCnt ; i++){
            fromRow = from.getRow(i);
            toRow = to.createRow(i);
            Iterator<HSSFCell> iterator = fromRow.cellIterator();
            short col = 0;
            while(iterator.hasNext())
                addCell(toRow, col++, iterator.next().getStringCellValue());
        }
    }

리팩터링 하는 김에 좀 더 Generic하게 만들어서 몇 번째 줄부터 몇 번째 줄까지 복사할지 인자로 넘겨주도록 수정 함. 이제 저 메소드는 ExcelUtils로 옮기면 ExelView쪽 코드는 약간 더 깔끔해지겠죠. 그건 뭐 간단하니 생략합니다.

선언된 순서를 변경하는 것은 리팩터링인가?

원문 : IsDeclarationOrderingRefactoring     refactoring     1 September 2004

RefactoringBoundary.

자바 프로그램 내부에 있는 메소드와 필드가 선언된 순서를 변경하는 것은 리팩터링인가요?

근래의 프로그래밍 언어들은 내부에 선언되어 있는 요소들의 순서가 프로그램에 전혀 영향을 주지 않는다. 만약 텍스트 파일 내부에 있는 두 개의 메소드 위치를 서로 바꾸더라도, 프로그램은 아무런 영향을 받지 않는다. 이것을 리팩터링이라고 할 수 있는 이유는 변경으로 인해서 프로그램이 동작하는 방법에 영향을 주지 않기 때문이다. 이것은 설계를 변경하지 않으며, 만약 설계가 바뀐다면 리팩터링이라고 할 수 없다.

리팩터링 정의에서, “이해하기 쉽고 수정하는 비용을 최소화 하도록” 이라는 구문을 사용했다. 선언된 위치를 변경하는 것이 이에 해당하는가? 몇몇 경우에 그럴 수 있다고 생각한다. 클래스 내부에서 서로 관련되어 있는 속성들을 묶어두는 것이 좋다. 그러한 방법으로 속성들을 정렬하면 클래스가 어떻게 동작하는지 이해하는데 도움이 된다. 특히 테스트 케이스를 작성할 때 유용하다. (비록 특정 xunit 구현체에서는, 순서가 실행 결과에 영향을 줄 수 있지만 말이다.) 결국 코드의 이해도를 높여주기 때문에, 선언된 순서 변경을 리팩터링이라고 볼 수 있다.

이것이 내부 구현을 변경하지 않는다는 사실은 중요하지 않다. 메소드의 이름 변경은 실행하는 내용을 변경하지는 않는다. 하지만 이름 변경은 프로그램의 이해도를 높이는 매우 중요한 리팩터링이다.

리팩터링 정의

원문 : DefinitionOfRefactoring     refactoring     

나(Martin Fowler)의 리팩터링 책에서, 리팩터링에 대한 몇몇 정의를 내렸다.

리팩터링 (명사): 소프트웨어의 내부 구조를 보다 이해하기 쉽고 수정하는 비용을 최소화 하도록 하는 변경. 이 때 “주목할 만한” 행동의 변경이 있으면 안 된다.

리팩터링 (동사): “주목할 만한” 행동의 변경 없이, 몇몇 리팩터링들을 적용하여 소프트웨어의 내부 구조를 변경하다.