작게 만들어라!

  • 블록과 들여쓰기
    • if 문 / else 문 / while 문 등에 들어가는 블록은 한 줄이어야 한다.
    • 함수에서 들여쓰기 수준은 1단이나 2단을 넘어서면 안 된다.

한 가지만 해라!

  • 함수는 한가지를 해야 하고 그 한 가지를 잘 해야 하며 그 한 가지만을 해야 한다.
  • 지정된 함수 이름 아래에서 추상화 수준이 하낭니 단계만 수행한다면 그 함수는 한 가지 작업만 한다.
  • 한 가지 작업만 하는 함수는 섹션으로 나누기 어렵다.

함수 당 추상화 수준은 하나로!

  • 함수가 확실히 '한 가지' 작업만 하려면 함수 내 모든 문장의 추상화 수준이 동일해야 한다.
    • getHtml() - 추상화 수준 높음
    • .append("\n") - 추상화 수준 낮음
  • 위에서 아래로 코드 읽기: 내려가기 규칙
    • 코드는 위에서 아래로 이야기처럼 읽혀야 좋다.
    • 한 함수 다음에는 추상화 수준이 한 단계 낮은 함수가 온다.

Switch 문

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 문을 완전히 피할 방법은 없다.
  • 하지만 다형성을 이용해서 각 switch 문을 저차원 클래스에 숨기고 절대로 반복하지 않는 방법은 있다.
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 문을 추상 팩토리에 꽁꽁 숨긴다. 노출 절대 X
  • 팩토리는 switch 문을 사용해 적절한 Employee 파생 클래스의 인스턴스를 생성한다.
  • 다형적 객체를 생성하는 코드 안에서는 switch 문을 사용하자.

서술적인 이름을 사용하라!

  • 코드를 읽으면서 짐작했던 기능을 각 루틴이 그대로 수행한다면 깨끗한 코드라 불러도 된다.

함수 인수

함수에서 이상적인 인수의 개수는 0개이고, 차선은 1개뿐인 경우다.

많이 쓰는 단항 형식

  • 인수에 질문을 던지는 경우
    • boolean fileExists(“MyFile”);
  • 인수를 뭔가로 변환해 결과를 반환하는 경우
    • InputStream fileOpen(“MyFile”);
  • 이벤트 함수일 경우
    • 이벤트라는 사실이 코드에 명확히 드러나야 한다.

플래그 인수

  • 사용하지 말자(함수 내에서 여러 가지를 처리하기 때문)

Bad: render(true)

Good: renderForSuite(), renderForSingleTest()

이항 함수

  • 단항 함수보다 이해하기 어렵다.
  • 하지만 new Point(x, y); 처럼 적절한 경우도 간혹 있다.

삼항 함수

  • 이항 함수보다 더 이해하기 어렵다. 신중히 고려해서 사용하자.

인수 객체

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

인수 목록

  • String.format 처럼 인수 개수가 가변적인 함수도 필요하다.

동사와 키워드

  • 단항 함수는 함수와 인수가 동사/명사 쌍을 이뤄야 한다.
    • writeField(name)
  • 함수 이름에 키워드를 추가하면 인수 순서를 기억할 필요가 없어진다.
    • Bad: assertEquals(expected, actual)
    • Good: assertExpectedEqualsActual(expected, actual)

부수 효과를 일으키지 마라!

  • 하나의 함수에서는 한 가지 일만 해야 한다.
  • 주의해야 할 것
    • 예상치 못하게 클래스 변수를 수정하는 것
    • 함수로 넘어온 인수나 시스템 전역 변수를 수정하는 것

출력 인수

  • 출력 인수는 피해야 한다.
  • 함수에서 상태를 변경해야 한다면 함수가 속한 객체 상태를 변경하는 방식을 택한다.

명령과 조회를 분리하라!

  • 함수는 객체 상태를 변경하거나 객체 정보를 반환하거나 둘 중 하나다.
  • 명령과 조회를 분리해 혼란을 애초에 뿌리뽑자.

Good

if (attributeExists("username")) {
    setAttribute("username", "unclebob");
}

오류 코드보다 예외를 사용하라!

if (deletePage(page) == E_OK) {
    if (registry.deleteReference(page.name) == E_OK) {
        if (configKeys.deleteKey(page.name.makeKey()) == E_OK) {
            logger.log("page deleted");
        } else {
            logger.log("configKey not deleted");
        }
    } else {
        logger.log("deleteReference from registry failed"); 
    } 
} else {
    logger.log("delete failed"); return E_ERROR;
}
  • 오류 코드 대신 예외를 사용해보자.
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 블록은 별도 함수로 뽑아내는 편이 좋다.

오류 처리도 한 가지 작업이다

  • 오류를 처리하는 함수는 오류만 처리해야 마땅하다.
  • try문으로 시작해 catch/finally 문으로 끝나야 한다.

Error.java 의존성 자석

public enum Error { 
    OK,
    INVALID,
    NO_SUCH,
    LOCKED,
    OUT_OF_RESOURCES,     
    WAITING_FOR_EVENT;
}
  • 위와 같은 클래스는 의존성 자석이다.
  • 오류 코드 대신 예외를 사용하자. 그러면 새 예외는 Exception 클래스에서 파생된다.

반복하지 마라!

  • 중복은 소프트웨어에서 몯느 악의 근원이다.

구조적 프로그래밍

  • 함수를 작게 만든다면 return, break, continue를 여러 차례 사용해도 괜찮다.
  • 작은 함수에서는 goto 문을 피해야만 한다.

함수를 어떻게 짜죠?

  • 소프트웨어를 짜는 행위는 여느 글짓기와 비슷하다.
  • 서투른 코드를 빠짐없이 테스트하는 단위 테스트 케이스를 만든다.
  • 그런 다음 코드를 다듬고, 함수를 만들고, 이름을 바꾸고, 중복을 제거한다.
  • 메서드를 줄이고 순서를 바꾼다. 때로는 전체 클래스는 쪼개기도 한다.
  • 이 와중에도 코드는 항상 단위 테스트를 통과해야 한다.

결론

  • 시스템을 구현할 프로그램이 아니라 풀어갈 이야기로 여겨보자.
  • 내가 작성하는 함수가 분명하고 정확한 언어로 깔끔하게 같이 맞아떨어져야 이야기를 풀어가기가 쉬워진다는 사실을 기억해야 한다.

'Clean Code' 카테고리의 다른 글

[Clean Code] 경계  (0) 2020.09.03
[Clean Code] 오류 처리  (0) 2020.09.03
[Clean Code] 객체와 자료구조  (0) 2020.09.03
[Clean Code] 형식 맞추기  (0) 2020.09.03
[Clean Code] 의미 있는 이름  (0) 2020.09.03

+ 따끈한 최근 게시물