Post

[Book - Clean Code] 17. 냄새와 휴리스틱

위키피디아에 의하면 휴리스틱은 불충분한 시간이나 정보로 인하여 합리적인 판단을 할 수 없거나,
체계적이면서 합리적인 판단이 굳이 필요하지 않은 상황에서 사람들이 빠르게 사용할 수 있게 보다 용이하게 구성된 간편추론의 방법이라고 한다.

주석

  • C1: 부적절한 정보
    • 다른 시스템에 저장할 정보는 주석으로 적절하지 못하다.
    • 예를 들어, 시스템에는 소스 코드 관리 시스템, 버그 추적 시스템, 이슈 추적 시스템, 기타 기록 관리 시스템 등이 있다.
  • C2: 쓸모 없는 주석
    • 주석은 빨리 낡는다.
    • 쓸모 없어질 주석은 아예 달지 않는 편이 좋다.
  • C3:: 중복된 주석
    • 코드만으로 충분한데 구구절절 설명하는 주석이 중복된 주석이다.
  • C4: 성의 없는 주석
    • 주석을 달 것이라면 단어를 신중하게 선택한다.
    • 주절대지 않고, 간결하며 명료하게 작성한다.
  • C5: 주석 처리된 코드
    • 주석으로 처리된 코드는 흉물 그 자체다.
    • 주석으로 처리된 코드를 내버려 두지 마라.

환경

  • E1: 여러 단계로 빌드
    • 한 명령으로 전체를 체크아웃해서 한 명령으로 빌드할 수 있어야 한다.
  • E2: 여러 단계로 테스트
    • 모든 단위 테스트는 한 명령으로 돌려야 한다.

함수

  • F1: 너무 많은 인수
    • 함수에서 인수 개수는 작을수록 좋다.
    • 아예 없으면 가장 좋다.
    • 넷 이상은 그 가치가 아주 의심스러우므로 최대한 피한다.
  • F2: 출력 인수
    • 출력 인수는 직관을 정면으로 위배한다.
    • 함수에서 뭔가의 상태를 변경해야 한다면 함수가 속한 객체의 상태를 변경한다.
  • F3: 플래그 인수
    • boolean 인수는 함수가 여러 기능을 수행한다는 명백한 증거다.
    • 플래그 인수는 혼란을 초래하므로 피해야 마땅하다.
  • F4: 죽은 함수
    • 아무도 호출하지 않는 함수는 삭제한다.
    • 죽은 코드는 낭비다.

일반

  • G1: 한 소스 파일에 여러 언어를 사용
    • 소스 파일 하나에 언어 하나만 사용하는 방식이 가장 이상적이다.
  • G2: 당연한 동작을 구현 X
    • 요일 문자열에서 요일을 나타내는 enum으로 변환하는 함수를 살펴보자.
      • Day day = DayDate.StringToDay(String dayName);
      • Monday를 Day.MONDAY로 변환하리라 기대하고, 일반적으로 쓰는 요일 약어도 올바로 변환하리라 기대한다.
    • 당연한 동작을 구현하지 않으면 코드를 읽거나 사용하는 사람이 더 이상 함수 이름만으로 함수 기능을 직관적으로 예상하기 어렵다.
  • G3: 경계를 올바로 처리하지 않는다.
    • 모든 경계 조건을 찾아내고, 모든 경계 조건을 테스트하는 테스트 케이스를 작성하라.
  • G4: 안전 절차 무시
    • 실패하는 테스트 케이스를 일단 제껴두고 나중으로 미루는 태도는 신용카드가 공짜 돈이라는 생각만큼 위험하다.
  • G5: 중복
    • 코드에서 중복을 발견할 때마다 추상화할 기회로 간주하라.
      • 중복된 코드를 하위 루틴이나 다른 클래스로 분리하라.
      • 추상화로 중복을 정리하면 설계 언어의 어휘가 늘어난다.
      • 추상화 수준을 높이면 구현이 빨라지고 오류가 적어진다.
    • switch/case나 if/else 문으로 똑같은 조건을 거듭 확인하는 중복은 다형성(polyumorphism)으로 대체한다.
    • 알고리즘이 유사하나 코드가 서로 다른 중복은 template method pattern인나 strategy pattern으로 중복을 제거한다.
    • 어디서든 중복을 발견하면 없애라.
  • G6: 추상화 수준이 올바르지 못함
    • 추상화는 저차원 상세 개념에서 고차원 일반 개념을 분리한다.
    • 모든 저차원 개념은 파생 클래스에 넣고, 모든 고차원 개념은 기초 클래스에 넣는다.
  • G7: 기초 클래스가 파생 클래스에 의존
    • 고차원 기초 클래스 개념을 저차원 파생 클래스 개념으로부터 분리해 독립성을 보장
    • 일반적으로 기초 클래스는 파생 클래스를 아예 몰라야 한다.
      • 예외로, 파생 클래스 개수가 확실히 고정되었다면 기초 클래스에 파생 클래스를 선택하는 코드가 들어간다.
    • 일반적으로는 기초 클래스와 파생 클래스를 다른 JAR 파일로 배포하는 편이 좋다.
  • G8: 과도한 정보
    • 잘 정의된 모듈은 인터페이스가 아주 작다.
      • 하지만 작은 인터페이스로도 많은 동작이 가능하다.
    • 잘 정의된 인터페이스는 많은 함수를 제공하지 않는다.
      • 그래서 결합도(coupling)가 낮다.
    • 부실하게 정의된 인터페이스는 반드시 호출해야 하는 온갖 함수를 제공한다.
      • 그래서 결합도(coupling)가 높다.
    • 클래스가 제공하는 메서드 수는 작을수록 좋다.
      • 함수가 아는 변수 수도 작을수록 좋다.
      • 클래스에 들어있는 인스턴스 변수 수도 작을수록 좋다.
    • 자료, 유틸리티 함수, 상수와 임시 변수를 숨겨라.
    • 정보를 제한 해 결합도를 낮춰라.
  • G9: 죽은 코드
    • 죽은 코드란 실행되지 않는 코드를 가리킨다.
    • 죽은 코드를 발견하면 시스템에서 제거한다.
  • G10: 수직 분리
    • 변수와 함수는 사용되는 위치에 가깝게 정의한다.
    • 지역 변수는 처음으로 사용하기 직전에 선언하며 수직으로 가까운 곳에 위치해야 한다.
  • G11: 일관성 부족
    • 어떤 개념을 특정 방식으로 구현했다면 유사한 개념도 같은 방식으로 구현한다.
  • G12: 잡동사니
    • 비어 있는 기본 생성자가 왜 필요한가?
      • 쓸데없이 코드만 복잡하게 만든다.
      • 제거하자.
  • G13: 인위적 결합
    • 서로 무관한 개념을 인위적으로 결합하지 않는다.
    • 함수, 상수, 변수를 선언할 때는 시간을 들여 올바른 위치를 고민한다.
      • 그저 당장 편한 곳에 선언하고 내버려 두면 안 된다.
  • G14: 기능 욕심
    • 클래스 메서드는 자기 클래스의 변수와 함수에 관심을 구져야지 다른 클래스의 변수와 함수에 관심을 가져서는 안된다.
    • 메서드가 다른 객체의 참조자와 변경자를 사용해 그 객체 내용을 조작한다면, 메서드가 그 객체 클래스의 범위를 욕심내는 탓이다.
    • 기능 욕심은 한 클래스의 속사정을 다른 클래스에 노출하므로, 별다른 문제가 없다면, 제거하는 편이 좋다.
  • G15: 선택자 인수
    • 선택자(selector) 인수는 목적을 깅거하기 어려울 뿐 아니라 각 선택자 인수가 여러 함수를 하나로 조합한다.
    • 선택자 인수는 큰 함수를 작은 함수 여럿으로 쪼개지 않으려는 게으름의 소산이다.
    • boolean, enum, int 등 함수 동작을 제어하려는 인수는 하나 같이 바람직하지 않다.
    • 일반적으로, 인수를 넘겨 동작을 선택하는 대신 새로운 함수를 만드는 편이 좋다.
  • G16: 모호한 의도
    • 코드를 짤 때는 의도를 최대한 분명히 밝힌다.
  • G17: 잘못 지운 책임
  • G18: 부적절한 static 함수
    • 일반적으로 static 함수보다 인스턴스 함수가 더 좋다.
    • 조금이라도 의심스럽다면 인스턴스 함수로 정의한다.
    • 반드시 static 함수로 정의해야겠다면 재정의할 가능성은 없는지 꼼꼼히 따져본다.
  • G19: 서술적 변수
    • 서술적 변수 이름은 많이 써도 괜찮다.
    • 일반적으로는 많은수록 더 좋다.
  • G20: 이름과 기능이 일치하는 함수
  • G21: 알고리즘을 이해하라
    • 알고리즘이 올바르다는 사실을 확인하고 이해하려면 기능이 뻔히 보일 정도로 함수를 깔끔하고 명확하게 재구성하는 방법이 최고다.
  • G22: 논리적 의존성은 물리적으로 드러내라
    • 한 모듈이 다른 모듈에 의존한다면 물리적인 의존성도 있어야 한다.
      • 논리적인 의존성만으로는 부족하다.
    • 의존하는 모든 정보를 명시적으로 요청하는 편이 좋다.
  • G23: If/Else 혹은 Switch/Case 문보다 다형성을 사용하라
  • G24: 표준 표기법을 따르라
    • 표준을 설명하는 문서는 코드 자체로 충분해야 하며 별도 문서를 만들 필요는 없어야 한다.
  • G25: 매직 숫자는 명명된 상수로 교체하라
  • G26: 정확하라
    • 명등성
  • G27: 관례보다 구조를 사용하라
  • G28: 조건을 캡슐화하라
  • G29: 부정 조건은 피하라
  • G30: 함수는 한 가지만 해야 한다.
    • 한 가지만 수행하는 좀 더 작은 함수 여럿으로 나눠야 마땅하다.
  • G31: 숨겨진 시간적인 결합
    • 때로는 시간적인 결합이 필요하다.
      • 하지만 시간적인 결합을 숨겨서는 안 된다.
      • 함수를 짤 때는 함수 인수를 적절히 배치해 함수가 호출되는 순서를 명백히 드러낸다.
  • G32: 일관성을 유지하라
  • G33: 경계 조건을 캡슐화하라
  • G34: 함수는 추상화 수준을 한 단계만 내려가게 하라
    • 함수 내 모든 문장은 추상화 수준이 동일해야 한다.
      • 추상화 수준은 함수 이름이 의미하는 작업보다 한 단계만 낮아야 한다.
  • G35: 설정 정보는 최상위 단계에 둬라
  • G36: 추이적 탐색을 피하라
    • 일반적으로 한 모듈은 주변 모듈을 모를수록 좋다.
      • A가 B를 사용하고, B가 C를 사용한다 하더라도 A가 C를 알아야 할 필요는 없다.
      • 이를 디미터의 법칙이라 부른다.

자바

  • J1: 긴 import 목록을 피하고 와일드카드를 사용하라
    • 긴 import 목록은 읽기에 부담스럽다.
      • 사용하는 패키지를 간단히 명시하면 충분하다.
    • 명시적인 import 문은 강한 의존성을 생성하지만 와일드카드는 그렇지 않다.
    • import 문은 패키지를 단순히 검색 경로에 추가하므로 진정한 의존성이 생기지 않는다.
      • 그러므로 모듈 간에 결합성이 낮아진다.
    • 와일드카드 import 문이 명시적인 import 문보다 좋다.
  • J2: 상수는 상속하지 마라
    • static import를 사용하라.
  • J3: 상수 vs Enum
    • enum을 적극 활용하자.
      • public static final int라는 옛날 기교를 더 이상 사용할 필요가 없다.
      • int보다 훨씬 더 유연하고 서술적인 강력한 도구다.

이름

  • N1: 서술적인 이름을 사용하라
    • 시간을 들여 현명한 이름을 선택하고 유효한 상태로 유지한다.
  • N2: 적절한 추상화 수준에서 이름을 선택하라
    • 구현을 드러내는 이름은 피하라
    • 작업 대상 클래스나 함수가 위치하는 추상화 수준을 반영하는 이름을 선택하라
    • 안정적인 코드를 만들려면 지속적인 개선과 노력이 필요하다.
  • N3: 가능하다면 표준 명명법을 사용하라
  • N4: 명확한 이름
    • 함수나 변수의 목적을 명확히 밝히는 이름을 선택한다.
    • 길다는 단점을 서술성이 충분히 메꾼다.
  • N5: 긴 범위는 긴 이름을 사용하라
    • 이름 길이는 범위 길이에 비례해야 한다.
    • 범위가 작으면 아주 짧은 이름을 사용해도 괜찮다.
    • 하지만 범위가 길어지면 긴 이름을 사용한다.
  • N6: 인코딩을 피하라
    • 이름 유형 정보나 범위 정보를 넣어서는 안 된다.
    • 헝가리안 표기법의 오염에서 이름을 보호하라.
  • N7: 이름으로 부수 효과를 설명하라
    • 여러 작업을 수행하는 함수에다 동사 하나만 달랑 사용하면 곤란하다.

테스트

  • T1: 불충분한 테스트
    • 테스트 케이스는 잠재적으로 깨질 만한 부분을 모두 테스트해야 한다.
    • 테스트 케이스가 확인하지 않는 조건이나 검증하지 않는 계산이 있다면 그 테스트는 불완전하다.
  • T2: 커버리지 도구를 사용하라!
    • 테스트가 빠뜨리는 공백을 알려준다.
    • 테스트가 불충분한 모듈, 클래스, 함수를 찾기가 쉬워진다.
  • T3: 사소한 테스트를 건너뛰지 마라
  • T4: 무시한 테스트는 모호함
    • 불분명한 요구사항은 테스트 케이스를 주석으로 처리하거나 테스트 케이스에 @Ignore를 붙여 표현한다.
    • 선택 기준은 모호함이 존재하는 테스트 케이스가 컴파일이 가능한지 불가능한지에 달려있다.
  • T5: 경계 조건을 테스트하라
    • 경계 조건은 각별히 신경 써서 테스트한다.
    • 경계 조건에서 실수하는 경우가 흔하다.
  • T6: 버그 주변은 철저히 테스트하라
    • 버그는 서로 모이는 경향이 있다.
    • 한 함수에서 버그를 발견했다면 그 함수를 철저히 테스트하는 편이 좋다.
  • T7: 실패 패턴을 살펴라
    • 합리적인 순서로 정렬된 꼼꼼한 테스트 케이스는 실패 패턴을 드러낸다.
  • T8: 테스트 커버리지 패턴을 살펴라
    • 통과하는 테스트가 실행하거나 실행하지 않는 코드를 살펴보면 실패하는 테스트 케이스의 실패 원인이 드러난다.
  • T9: 테스트는 빠르게
    • 느린 테스트 케이스는 실행하지 않게 된다.
This post is licensed under CC BY 4.0 by the author.