2023년 4월 20일 작성

Clean Code - 좋은 함수 만들기

함수는 한 가지만 해야 합니다.

좋은 함수를 만드는 방법

  • 좋은 함수는 한 가지만 하는, 작고, 잘 이름 붙여진 함수입니다.

작게 만들기

  • 하나의 함수는 하나의 이야기만 표현해야 합니다.

들여쓰기 수준을 낮게 유지하기

  • 함수의 들여쓰기 수준은 1단이나 2단을 넘어서면 안 됩니다.
    • 중첩 구조가 생길만큼 함수가 커져서는 안 됩니다.
  • if-else, while 문 등에 들어가는 block은 한 줄이어야 합니다.
    • block에 한 줄만 들어가면, 바깥을 감싸는 함수(enclosing function)가 작아집니다.
    • block 안에서 호출하는 함수 이름을 적절히 짓는다면, code를 이해하기 쉬워집니다.

한 가지만 하기

함수는 한 가지를 해야 한다. 그 한 가지를 잘 해야 한다. 그 한 가지만을 해야 한다.
  • 함수가 한 가지만 하는지 판단할 수 있습니다.
    • 한 함수 안(같은 추상화 수준)에서 logic을 추상화하여 한 단계만 수행하는 지 확인합니다.
    • 다른 표현이 아니라 의미 있는 이름으로 다른 함수를 추출할 수 있는지 확인합니다.
      • 한 가지 작업만 하는 함수는 section으로 나누기 어렵기 때문입니다.

함수의 추상화 수준을 맞추기

  • code는 위에서 아래로 이야기처럼 읽히는 것이 좋습니다.
  • 따라서 함수 당 추상화 수준은 하나여야 합니다.
  • 함수가 확실히 한 가지 작업만 하려면, 함수 내 모든 문장의 추상화 수준이 동일해야 합니다.
  • 한 함수 내에 여러 추상화 수준을 섞으면 code를 읽는 사람이 헷갈리게 됩니다.
    • 특정 표현이 근본 개념인지 세부사항인지 구분하기 어렵게 되기 때문입니다.

switch 문 숨기기

  • switch 문은 작게 만들기 어렵습니다.
    • switch 문은 본질적으로 여러 가지를 처리하도록 만들어졌기 때문입니다.
    • 분기가 많은 if-else 문도 switch 문과 같은 특징을 가집니다.
  • switch 문은 단일 책임 원칙(SRP, Single Responsibility Principle)을 위반합니다.
    • code를 변경할 이유가 여러 개이기 때문입니다.
  • switch 문은 개방-폐쇄 원칙(OCP, Open Closed Principle)을 위반하게 합니다.
    • 새로운 case를 추가할 때마다 code를 변경해야 하기 때문입니다.
  • switch 문을 완전히 피할 방법은 없기 때문에, 각 switch 문을 저차원 class에 숨기고 반복하지 않는 방식을 사용합니다.
    • 숨긴 후에는 다른 code에 노출시키지 않습니다.

서술적인 이름 사용하기

code를 읽으면서 짐작했던 기능을 각 routine이 그대로 수행한다면, clean code다.
  • 함수가 작고 단순할수록 서술적인 이름을 고르기 쉬워집니다.
  • 이름이 길어도 괜찮습니다.
    • ‘길고 서술적인 이름’이 ‘짧고 어려운 이름’보다 좋습니다.
    • ‘길고 서술적인 이름’이 ‘길고 서술적인 주석’보다 좋습니다.
  • 서술적인 이름을 사용하면 설계가 뚜렷해지므로 code 개선이 쉬워집니다.
  • 일관성 있는 이름을 붙여야 합니다.
    • module 내에서 함수 이름은 같은 문구, 명사, 동사를 사용합니다.
      • 예를 들어, includeSetupAndTeardownPages, includeSetupPages, includeSuiteSetupPage, includeSetupPage, …

인수를 적게 유지하기

함수 인수
무항 0개
단항 1개
이항 2개
삼항 3개
다항 4개 이상
  • 함수의 인수 갯수는 적을수록 좋습니다.
  • 인수가 많으면 개념을 이해하기 어렵습니다.
  • 인수가 많으면 test case를 작성하기 어렵습니다.

  • 인수가 2개 이상 필요하다면, 인수 객체를 생성해 인수를 줄일 수도 있습니다.
    • 인수의 일부를 독자적인 class 변수로 선언하여 사용합니다.
      • class 내에서는 변수를 묶기 위해 이름을 붙여 개념을 표현합니다.

함수 이름에 인수를 넣기

  • 함수(동사)와 인수(명사)가 쌍을 이루면 한번에 알아보기 쉬운 함수 이름이 됩니다.
    • write(name)보다 writeField(name)이 더 낫습니다.
    • 단항 함수에 적용할 수 있습니다.
  • 함수 이름에 keyword(인수 이름)를 추가하면 인수 순서를 기억할 필요가 없습니다.
    • assertEquals(expected, actual)보다 assertExpectedEqualsActual(expected, actual)이 더 낫습니다.

출력 인수 사용하지 않기

입력 인수 출력 인수
함수에 입력되는 인수 함수를 거치고 나오는 입력되었던 인수
  • 입력 인수를 변환해야 한다면, 변환 결과를 반환 값으로 돌려줍니다.
    • 변환 결과를 가져오기 위해 출력 인수를 사용하는 것보다, 반환 값을 사용하는 것이 낫습니다.
  • 객체 지향 언어에서는 출력 인수를 사용할 필요가 거의 없습니다.
    • 출력 인수로 사용하기 위해 설계한 this라는 변수가 있기 때문입니다.
    • 예를 들어, appendFooter(s);s를 바닥글로 첨부 할지, s에 바닥글을 첨부 할지 알 수 없습니다.
      • appendFooter(s)보다 report.appendFooter()가 더 낫습니다.
  • 함수에서 상태를 변경해야 한다면, 함수가 속한 객체 상태를 변경하는 방식을 사용하는 것이 더 좋습니다.

인수의 추상화 수준 맞추기

  • 함수 이름과 인수는 추상화 수준이 같아야 합니다.

Flag 인수(boolean)를 사용하지 않기

  • flag 인수를 받는다는 것은, 함수가 한꺼번에 여러 가지를 처리한다는 뜻입니다.
  • 함수는 한 번에 한 가지만 처리해야 합니다.

부수 효과 일으키지 않기

  • 부수 효과는 그 자체로 거짓말이 됩니다.
    • 함수에서 한 가지를 하는 척 하면서 다른 것도 하기 때문입니다.

명령과 조회를 분리하기

  • 함수는 명령과 조회 중 하나만 해야 합니다.

Error Code보다 예외를 사용하기

  • 오류 처리도 ‘한 가지 작업’이기 때문에, 오류를 처리하는 함수는 오류만 처리해야 합니다.
  • try-catch-finaly 문을 사용합니다.

반복하지 않기

  • 중복이 늘어나면, code 길이가 늘어나고, 수정 시 변경점이 많아집니다.
  • 중복을 없애면 module 가독성이 좋아집니다.
  • 많은 원칙, 기법, 기술이 중복을 제거하고 제어할 목적으로 나왔습니다.

다듬기

  • software를 짜는 행위는 글짓기와 비슷합니다.
    • 생각을 기록한 후 읽기 좋게 다듬습니다.
    • 초안은 서투르고 어수선하므로, 원하는 대로 읽힐 때까지 읽기 좋게 다듬습니다.

Reference

  • Clean Code (도서) - Robert C. Martin

목차