본문 바로가기

독서찰기(讀書札記)/Clean Code

3장 함수

작게 만들어라!

  • 함수에서 들여쓰기 수준은 1단이나 2단을 넘어서면 안 된다.

한 가지만 해라!

  • 함수는 한 가지를 해야 한다. 그 한 가지를 잘 해야 한다. 그 한 가지만을 해야 한다.
  • 단계가 나눠져 있더라도, 지정된 함수 이름 아래에서 추상화 수준이 하나라면 한 가지이다.
  • 단순히 다른 표현이 아니라 의미 있는 이름으로 다른 함수를 추출할 수 있다면 그 함수는 여러 작업을 하는 중이다.

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

  • 한 함수 내에 추상화 수준을 섞으면 특정 표현이 근본 개념인지 아니면 세부사항인지 구분하기 어려워짐
  • 내려가기 규칙
    • 한 함수 다음에는 추상화 수준이 한 단계 낮은 함수가 오게 한다.
    • 즉, 위에서 아래로 프로그램을 읽으면 함수 추상화 수준이 한 번에 한 단계씩 낮아진다.

Switch 문

  • 본질적으로 switch문은 N가지를 처리한다.
  • 각 switch 문을 저차원 클래스에 숨기고 절대로 반복하지 않는다. e.g. 추상 팩토리

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

  • 길고 서술적인 이름이 짧고 어려운 이름보다 좋다.
  • 길고 서술적인 이름이 길고 서술적인 주석보다 좋다.
  • 이름을 붙일 때는 일관성이 있어야 한다.

함수 인수

  • 함수에서 이상적인 인수 개수는 0개(무항)다.
  • 다음은 1개(단항)고, 다음은 2개(이항)다. 
  • 3개(삼항)는 가능한 피하는 편이 좋고  4개 이상은 쓰지마라.

[괜찮은 경우]

  • 인수에 질문을 던지는 경우 e.g. boolean fileExists(“MyFile”)
  • 인수를 뭔가로 변환해 결과를 반환하는 경우 e.g. InputStream fileOpen(“MyFile”)
  • 이벤트 (입력인수만 있고 출력인수는 없음) e.g. passwordAttemptFailedNtimes(int attempts)

[안 괜찮은 경우]

  • void includeSetupPageInto(StringBuffer pageText)
    • 변환 함수에서 출력 인수를 사용하면 혼란이 생김.
  • 플래그 인수
    • 함수로 bool 값을 넘기는 것은 함수가 여러 가지를 처리한다고 대놓고 공표하는 셈이다.
  • 이항 함수
    • Point p = new Point(0, 0)  처럼 한 값을 표현하는 두 인수, 또는 두 요소에 자연적인 순서가 있는 경우가 아니라면 이항 함수를 쓰지 않는 것이 좋다.
    • 두 인수의 순서를 헷갈리거나 덜 중요한 것을 무시했다가 오류를 낼 수 있기 때문이다.
    • 가능하면 단항 함수로 바꾸도록 애써야 한다.
    • e.g. writeField(outputStream, name)  →  outputStream.writeField(name)
  • 삼항 함수
    • 인수가 3개인 함수는 2개인 함수보다 훨씬 더 이해하기 어렵다.
    • 순서, 주춤, 무시로 야기되는 문제가 두 배 이상 늘어난다.
  • 인수 객체
    • 인수가 2~3개 필요하다면 일부를 독자적인 클래스 변수로 선언할 가능성을 짚어본다.
    • e.g. makeCircle(double x, double y, double  radius); → makeCircle(Point center, double  radius);
    • x와 y를 묶었듯이 변수를 묶어 넘기려면 이름을 붙여야 하므로 결국은 개념을 표현하게 된다.
  • 인수 목록
    • 때로는 인수 개수가 가변적인 함수도 필요하다.
    • e.g. String.format(“%s worked %.2f” hours.”, name, hours);
    • 가변 인수 전부를 동등하게 취급하면 List 형 인수 하나로 취급할 수 있다.
    • String.format은 사실상 이항 함수다. public String format(String format, Object… args)
  • 동사와 키워드
    • 함수의 의도나 인수의 순서를 제대로 표현하려면 좋은 함수 이름이 필수다.
    • 단항 함수는 함수와 인수가 동사/명사 쌍을 이뤄야 한다.
      • e.g. write(name) good
      • e.g. writeField(name) better (이름이 필드라는 사실이 드러남)
    • 함수 이름에 키워드 추가
      • e.g. assertEquals(expected, actual) good
      • e.g. assertExpectedEqualsActual(expected, actual) better (인수 순서가 드러남)

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

  • 함수에서 한 가지를 하겠다고 약속하고선 남몰래 다른 짓도 한다.
  • 많은 경우 시간적인 결합(temporal coupling)이나 순서 종속성(order dependency)을 초래한다.
  • 함수의 이름과 인수만 보고 호출한 사용자는 뜻밖의 부수 효과에 피해를 볼 수도 있다.
  • 인수를 출력으로 사용하는 함수의 어색함
    • appendFooter(s); s를 바닥글로 첨부할까? 아니면 s에 바닥글을 첨부할까?
    • public void appendFooter(StringBuffer report);  함수 선언부를 보니 ‘s에 바닥글을 첨부’
    • 출력 인수로 사용하라고 설계한 변수가 바로 this    report.appendFooter();  better

명령과 조회를 분리하라!

  • 함수는 ‘객체 상태를 변경’하거나 ‘객체 정보를 반환’하거나 둘 중 하나만 해야 한다.

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

  • 명령 함수에서 ‘오류 코드를 반환’하는 방식은 명령/조회 분리 규칙을 미묘하게 위반한다.
  • Try/Catch 블록 뽑아내기
    • try/catch 블록은 코드 구조에 혼란을 일으키며, 정상 동작과 오류 처리 동작을 뒤섞는다.
    • try/catch 블록을 별도 함수로 뽑아내는 편이 좋다.
public void delete(Page page) {
    try {
        deletePageAndAllReferences(page);
    }
    catch (Exception e) {
        logError(e);
    }
}

public void deletePageAndAllReferences(Page page) throws Exception {
    deletePage(page);
    registry.deleteReferences(page.name);
    configKeys.deleteKey(page.name.makeKey());
}

private void logError(Exception e) {
    logger.log(e.getMessage());
}
 
  • 오류 처리도 ‘한 가지’ 작업이다
  • Error.java 의존성 자석
public enum Error {
    OK,
    INVALID,
    NO_SUCH,
    LOCKED,
    OUT_OF_RESOURCES,
    WAITING_FOR_EVENT;
}
 
  • 위와 같은 클래스는 의존성 자석이다. 
  • 다른 클래스에서 Error enum을 import해 사용해야 하므로, Error enum이 변한다면 Error enum을 사용하는 클래스 전부를 다시 컴파일하고 다시 배치해야 한다.
  • 오류 코드 대신 예외를 사용하면 새 예외는 Exception 클래스에서 파생된다.

반복하지 마라!

  • 중복은 소프트웨어에서 모든 악의 근원이다.
  • E. F. Codd는 자료에서 중복을 제거할 목적으로 관계형 데이터베이스에 정규 형식을 만들었다.
  • 구조적 프로그래밍, AOP(Aspect Oriented Programming), COP(Component Oriented Programming) 모두 어떤 면에서 중복 제거 전략이다.

구조적 프로그래밍

  • 에츠허르 데이크스트라 (Edsger Dijkstra)의 구조적 프로그래밍 원칙
    • 함수는 return 문이 하나여야 함 (single entry-exit rule)
    • 루프 안에서 break, continue를 사용해선 안 됨
    • goto는 절대로 안 됨
  • 위 원칙은 함수가 아주 클 때만 상당한 이익을 제공한다.
  • 함수가 작을 때는 간혹 return, break, continue를 여러 차례 사용해도 괜찮다.
  • goto 문은 큰 함수에서만 의미가 있으므로, 작은 함수에서는 피해야만 한다.

함수를 어떻게 짜죠?

  • 초안
    • 길고 복잡하다.
    • 들여쓰기 단계도 많고 중복된 루프도 많다.
    • 인수 목록도 아주 길다.
    • 이름은 즉흥적이고 코드는 중복된다.
  • 단위 테스트를 만든다.
  • 수정
    • 코드를 다듬고, 함수를 만들고, 이름을 바꾸고, 중복을 제거한다.
    • 메서드를 줄이고 순서를 바꾼다.
    • 전체 클래스를 쪼개기도 한다.
    • 이 와중에 항상 단위 테스트를 통과시킨다.

'독서찰기(讀書札記) > Clean Code' 카테고리의 다른 글

4장 주석  (0) 2022.02.07
2장 의미 있는 이름  (0) 2022.02.07
1장 깨끗한 코드  (0) 2022.02.07