본문 바로가기

클린 코드

3장 함수

😃 책에서 기억하고 싶은 내용을 써보세요.

  • 함수를 만드는 첫째 규칙은 '작게'. 둘째 규칙은 '더 작게'
  • if/else while 문에 들어가는 블록은 한 줄이어야 한다.
    • 중첩 구조가 생길만큼 함수가 커져서는 안된다. 들여쓰기 수준이 1단 2단을 넘으면 안된다.
  • 한 가지만 해라! 함수는 한 가지를 해야 한다. 그 한 가지를 잘 해야 한다. 그 한 가지만을 해야 한다.
  • 의미있는 이름으로 다른 함수를 추출할 수 있다면 그 함수는 여러 작업을 하는 셈이다.
  • 함수당 추상화 수준은 하나로! 
    • 함수가 확실히 한 가지 작업만 하려면 함수 내 모든 문장의 추상화 수준이 동일해야 한다. 
      • getHtml()은 추상화 수준이 아주 높고, render()은 추상화 수준이 중간이고, append()는 추상화 수준이 아주 낮다.
      • 한 함수 내에 추상화 수준을 섞으면 코드를 읽는 사람이 헷갈린다.
  • 내려가기 규칙. 코드는 위에서 아래로 이야기처럼 읽혀야 좋다. 한 함수 다음에는 추상화 수준이 한 단계 낮은 함수가 온다. 
    • 각 함수는 다음 함수를 소개한다. 각 함수는 일정한 추상화 수준을 유지한다.
  • Switch 문
    • 함수가 길다.
    • 한 가지 작업만 수행하지 않는다.
    • Single Responsibility Principle 을 위반한다. 코드를 변경할 이유가 여럿이다.
    • Open Closed Principle 을 위반한다. 새 직원 유형을 추가할 때마다 코드를 변경한다.
    • => 따라서 추상 팩토리 Abstract Factory에 꽁꽁 숨긴다. 직원 클래스를 만들어서 파생 클래스와 그 함수들을 이용하라!
  • 서술적인 이름을 사용하라. 이름이 길어도 괜찮다. 길고 서술적인 이름이 길고 서술적인 주석보다 낫다.
    • 대신 일관성 있는 이름으로. 같은 문구, 같은 명사, 동사 등등 
    • isTestable() includeSetupAndTearDownPages()
  • 이상적인 함수의 인수 개수는 0개(무항)이다. 3개는 피하는 것이 좋다. 4개는 사용하면 안 된다.
    • why? 코드를 읽기도 어렵고, 테스트 케이스를 짜는데도 난감하다.
    • 함수에 인수 1개를 넘기는 이유:
      • 인수에 질문을 던지는 경우, 인수를 뭔가로 변환해 결과 반환하는 경우. 이벤트 함수는 조심해서 써라.(이름에 이벤트인 것이 명확해야 함) - 이 경우들이 아니라면 단항 함수는 가급적 피한다.
      • 플래그 인수는 추하다. 함수로 부울 값을 넘기는 관례는 끔찍하다. 함수가 한번에 여러 가지를 한다고 대놓고 말하는 셈.
      • 단항 함수는 함수와 인수가 동사/명사 쌍을 이뤄야 한다. writeField(name), assertExpectedEqualsActual(expected, actual)
    • 2항 함수:
      • 보통 단항 함수보다 이해하기 어렵다. 적절한 경우: 좌표계 표현할 때.
      • 인수의 순서에 유의하기. 가능하면 단항함수로 변환하라.
    • 3항 함수:
      • 이항 함수보다 훨씬 이해가 어렵다. 3항함수를 만드는 것은 아주 신중히 고려하라.
      • 인수가 2-3개 필요하다면 클래스 변수를 선언하는 것을 생각해보라.
      • 때로는 인수개수가 가변적인 함수도 필요하다. e.g. String.format("%s worked %.2f" hours." name, hours);
      • => 사실 상 이것은 2항 함수다. public String format(String format, Object... args)
  • 부수 효과를 일으키지 마라! 
    • 예상치 못하게 클래스 변수 수정. 함수로 넘어온 인수나 시스템 전역 변수 수정 등등.
    • e.g. 함수 checkPassword(str userName, str password){}가 함수 내에서  Sesssion.initialize()를 수행하고 있다.
    • 그러나 함수의 이름만 봐서는 세션 초기화한다는 사실이 드러나지 않는다. 이런 경우에 함수 이름은 checkPasswordAndInitSession()이 더 낫다. 물론 함수가 한 가지만 한다는 규칙을 위반한다.
  • 명령과 조회를 분리하라. 객체 상태를 변경하거나 객체 정보를 반환하거나 둘 중 하나만 해야 한다.
  • 오류 코드보다 예외를 사용해라. 
    • if (deletePage(page)== E_OK)보다는 예외 사용.
      try {
      	deletePage(page);
          ...
      }
      catch (Exception e) {
      	...
      }​
    • 그러나 try/catch 블록도 추하다. 코드 구조에 혼란. 정상 동작과 오류 동작을 뒤섞는다. 따라서 try/catch를 별도 함수로 뽑아내라. 정상 동작과 오류 처리 동작을 분리하면 코드를 이해하고 수정하기 쉽다. 
    • private void deletePangeAndAllReferences(Page page) throws Exception { deletePage(page) ... } private void logError(Exception e){ logger.log(...) }​
    • Error Enum 만들어서 자주 사용하는데 이는 코드 의존성을 높일 뿐. (다른 클래스에서 이넘 import 해야하니까) 따라서 새 오류 코드 추가하는 대신 그냥 기존꺼 사용하는 경향이 높아진다. 따라서 그냥 예외 처리를 해 준다면 재컴파일/재배치 없이도 새 예외 클래스 추가 가능하다.
  • 반복하지 마라!
    • SetuptTeardownIncluder라는 클래스를 정의하고, page는 클래스 변수로. 
    • includeSetupPages(), includeTeardownPages() 라는 각각의 함수를 만들고, 그 함수들을 실행하는 큰 함수를 만든다.

🤔 오늘 읽은 소감은? 떠오르는 생각을 가볍게 적어보세요

  • 내가 회사 코드 짜면서 자주 하는 행동들(함수 하나가 여러개 처리, 불리언 인수 사용 등)이 클린코드의 기본 원칙에 위배되는 것이였다니! 허허 내 코드를 리뷰해준 다른 동료들에게 다시 한 번 감사의 마음이 든다.
  • 함수 한 개가 하나의 펑션을 수행한다는 것이 정말 기본적인 원칙이라는 걸 깨달았다. 간단한데 그동안 지키지 않았던 것들이라 참 아쉽다.
  • 추상화 수준이 동일해야 한다는 것은 그동안 생각치도 못했던 것들이다. 인식은 하고 있었는데 그게 문제라는 것을 자각하지 못하고 있다가 이 부분을 읽을 때 내가 썼던 코드들이 주마등처럼 스쳐 지나갔다. 
  • 추상 클래스를 만드는 방식은 앞으로 좀 활용해봐야겠다.

🔎 궁금한 내용이 있거나, 잘 이해되지 않는 내용이 있다면 적어보세요.

  • 없음
 

'클린 코드' 카테고리의 다른 글

6장 객체와 자료구조  (0) 2022.04.20
5장 형식 맞추기  (0) 2022.02.27
4장 주석  (0) 2022.01.31
2장 의미 있는 이름  (0) 2022.01.25
1장 깨끗한 코드  (0) 2022.01.22