티스토리 뷰

1. 함수를 작게 만들어라

  • 한 가지만을 하도록 만들기(함수 내 추상화 수준을 동일하게) : SRP
  • 변경에 닫게 만들기 : OCP

예제 1)

public static String renderPageWithSetupsAndTeardowns(
        PageData pageData, boolean isSuite
) throws Exception {
    boolean isTestPage = pageData.hasAttribute("Test"); //-- ①
    if (isTestPage) {
    	// --②
        WikiPage testPage = pageData.getWikiPage();
        StringBuffer newPageContent = new StringBuffer();
        includeSetupPages(testPage, newPageContent, isSuite);
        newPageContent.append(pageData.getContent());
        includeTeardownPages(testPage, newPageContent, isSuite);
        pageData.setContent(newPageContent.toString());
    }
    return pageData.getHtml(); // ③
}

위의 코드에서 함수는 다음 3개의 기능을 수행한다.

① 페이지가 테스트 페이지인지 판단한다.

② 그렇다면 설정 페이지와 해제 페이지를 넣는다.

③ 페이지를 HTML로 렌더링한다.

 

3개의 기능이라는게 애매한 표현이지만 추상화 수준이 서로 다른 함수는 여러가지 일을 한다고 할 수 있고(SRP 위반)

①, ②의 추상화 수준을 높여 다음과 같이

추상화 수준이 동일하게 맞추면 함수가 한가지 일을 한다고 할 수 있다.

함수 내에서 의미있는 이름의 다른 함수를 추출할 수 있다면 그 함수는 여러 작업을 하는 셈이다.

public static String renderPageWithSetupsAndTeardowns(
        PageData pageData, boolean isSuite) throws Exception {
    if (isTestPage(pageData)) //-- ①
        includeSetupAndTeardownPages(pageData, isSuite); //-- ②
    return pageData.getHtml(); //-- ③
}

 

 

예제 2)

public Money calculatePay(Employee e)
        throws InvalidEmployeeType {
    switch (e.type) {
        case COMMISSIONED:
            return calculateCommissionedPay(e);
        case HOURLY:
            return calculateHourlyPay(e);
        case SALARIED:
            return calculateSalariedPay(e);
        default:
            throw new InvalidEmployeeType(e.type);
    }
}

위의 코드에서

switch 문은 본질적으로 N가지를 처리하기 때문에 SRP를 위반하지만 완전히 피할 방법은 없다.

따라서 switch 문을 저차원 클래스에 숨기고 절대로 반복하지 않도록 한다.

그리고 새 직원유형(e.type)을 추가할 때마다 코드를 수정해야 하기 때문에 OCP를 위반한다.

 

public abstract class Employee {
    public abstract boolean isPayday();
    public abstract Money calculatePay();
    public abstract void deliverPay(Money pay);
}
-----------------
public interface EmployeeFactory {
    public Employee makeEmployee(EmployeeRecord r) throws InvalidEmployeeType;
}
-----------------
public class EmployeeFactoryImpl implements EmployeeFactory {
    public Employee makeEmployee(EmployeeRecord r) throws InvalidEmployeeType {
        switch (r.type) {
            case COMMISSIONED:
                return new CommissionedEmployee(r) ;
            case HOURLY:
                return new HourlyEmployee(r);
            case SALARIED:
                return new SalariedEmploye(r);
            default:
                throw new InvalidEmployeeType(r.type);
        }
    }
}

switch 문을 추상 팩토리(EmployeeFactory)에 숨기고 팩토리는 적절한 Employee 파생 클래스의 인스턴스를 생성한다.

생성된 인스턴스는 다형성을 이용해 isPayday, calculatePay, deliverPay 함수를 사용할 수 있다.

비즈니스 코드는 다음과 같이 간결하고 명확해진다. (SRP)

직원유형이 추가되어도 기존의 비즈니스 코드를 변경하지 않고 확장이 가능하다. (OCP)

public Money calculatePay(Employee e) throws InvalidEmployeeType {
	Employee commissionedEmployee = employeeFactoryImpl.makeEmployee(COMMISSIONED);
	return commissionedEmployee.calculatePay();
}

 

 

2. 서술적인 이름을 사용하라.

  • 함수 기능을 잘 표현하는 이름을 사용하기

[클린코드] 2. 의미있는 이름 참고

 

 

3. 인수를 줄이자.

  • 함수에서 이상적인 인수 개수는 0개다.
  • 다음은 1개, 다음은 2개, 3개 이상은 피하기

다음과 같이 인수가 2~3개 필요하다면 일부를 독자적인 클래스 변수로 선언할 가능성을 짚어보자.

Circle makeCircle(double x, double y, double radius);
Circle makeCircle(Point center, double radius);

 

 

4. 안전한 함수 만들기.

  • 부수 효과(Side Effect) 없는 함수 만들기
  • 명령과 조회를 분리하기
  • 오류 코드보다 예외를 사용하고 try ~ catch 블럭은 별도의 함수로 뽑아내자.

예제 1)

public class UserValidator {
    private Cryptographer cryptographer;
    public boolean checkPassword(String userName, String password) {
        User user = UserGateway.findByName(userName);
        if (user != User.NULL) {
            String codedPhrase = user.getPhraseEncodedByPassword();
            String phrase = cryptographer.decrypt(codedPhrase, password);
            if ("Valid Password".equals(phrase)) {
                Session.initialize(); //-- ①
                return true;
            }
        }
        return false;
    }
}

① 패스워드를 체크하는 함수에 세션을 초기화하는 기능이 포함되어 있다.

함수의 이름만 봐서는 세현을 초기화 한다는 사실이 드러나지 않기 때문에

함수 이름만 보고 호출하는 사용자는 인증하면서 기존 세션 정보를 지워버릴 위험이 있다.

 

예제 2)

public boolean set(String attribute, String value); //-- ①
if (set("username", "unclebob"))... //-- ②

if (attributeExists("username")) { //-- ③
	setAttribute("username", "unclebob");
	... 
}

① 이름이 attribute 인 속성을 찾아 값을 value로 설정한 후 성공하면 true, 실패하면 false를 반환

② ①함수를 호출하는 경우 의미가 모호해진다. 명령일까? 조회일까?

③ attributeExists (조회), setAttribute(명령)으로 분리

 

예제 3)

public void delete(Page page) {
    try {
        deletePageAndAllReferences(page); //-- ①
    }
    catch (Exception e) {
        logError(e);
    }
}

------------
private void deletePageAndAllReferences(Page page) throws Exception {
    deletePage(page);
    registry.deleteReference(page.name);
    configKeys.deleteKey(page.name.makeKey());
}
private void logError(Exception e) {
    logger.log(e.getMessage());
}

① try ~ catch 블럭은 별도의 함수로 뽑아내자

 

5. 함수 리팩터링

1) 기능을 구현하는 서투른 함수를 작성한다.

2) 테스트 코드를 작성한다.

3) 리팩터링 한다.

 

기능을 구현하고 테스트 코드를 만들고 리팩터링한 다음

다시 테스트를 통과하는지 확인하자.

 

 

 

출처

Clean Code(클린 코드) 애자일 소프트웨어 장인 정신

로버트 C. 마틴 지음

728x90