냄새와 휴리스틱 치트시트

mosad2334·2022년 1월 15일
0

냄새와 휴리스틱

  • 아래 내용은 클린코드(로버트 C. 마틴)의 17장 냄새와 휴리스틱을 내가 필요할 때 보려고 치트시트 형식으로 작성했다.
  • 리팩터링(마틴 파울러)에서 거론된 '코드 냄새'에서 클린코드의 저자가 작성한 내용이 추가되었다.
  • 역시 온라인에서 제공되는 동일한 문서가 있을 것 같았는데 존재한다. 👉Clean Code: Smells and Heuristics

치트시트 TODO

지금 당장 작성 못하는 항목들

  • 이해 못한거 체크하기
  • 예시 붙일 수 있는 항목은 최대한 붙이기
  • 코드 경계라는 단어가 정확하게 내가 이해한게 맞는지 아직 판단이 안선 상태인데, 다시오면 확인하기
  • 오탈자 확인하기

주석 Comments

C1 : 부적절한 정보

  • 소스 코드 관리 시스템, 버그 추적 시스템 등에 저장할 정보는 주석으로 충분치 못하다.
  • 주석은 코드 설계에 기술적인 설명을 부연할 때만 사용한다.

예시

public static final int EARLIEST_DATE_ORDINAL = 2; // 1/1/1900
public static final int LATEST_DATE_ORDINAL = 2958465; // 12/31/9999

C2 : 쓸모 없는 주석

  • 필요 없어진 주석은 재빨리 삭제한다.

C3 : 중복된 주석

  • 코드만으로 설명할 수 있는 내용은 주석을 첨부하지 않는다.
  • 주석은 코드만으로 다하지 못하는 설명을 부언한다.

C4 : 성의 없는 주석

  • 작성할 가치가 있는 주석은 최대한 시간을 들여 멋지게 작성한다.
  • 신중한 단어선택, 문법과 구두점의 올바른 사용법, 주절대지 않기, 당연한 소리 반복하지 않기, 간결하고 명료하게 작성하기.

C5 : 주석 처리된 코드

  • 누군가에게 필요하거나 다른 사람이 사용할 코드라 생각하기 때문에 매일 자리에 남아 낡아간다.
  • 발견하는 즉시 즉각 삭제해도 무관하다.
    • 어차피 소스 코드 관리 시스템이 기억한다.

환경 Environment

E1 : 여러 단계로 빌드해야 한다

  • 빌드는 간단히 한 단계로 끝나야 한다.
  • 한 명령으로 전체를 체크아웃해서 한 명령으로 빌드할 수 있어야 한다.

E2 : 여러 단계로 테스트해야 한다

  • 모든 단위 테스트는 한 명령으로 돌려야 한다.

함수 Functions

F1 : 너무 많은 인수

  • 함수의 인수 개수는 적을수록 좋다.
  • 아예 없으면 가장 좋다.
  • 하나, 둘, 셋이 차례로 좋다.
  • 넷 이상은 그 가치가 아주 의심스러우므로 최대한 피한다.

F2 : 출력 인수

  • 출력 인수는 직관을 정면으로 위배한다. 일반적으로 독자는 인수를 입력으로 간주한다.
  • 함수에서 뭔가의 상태를 변경해야 한다면 함수가 속한 객체의 상태를 변경한다.

F3 : 플래그 인수

  • boolean 인수는 함수가 여러 기능을 수행한다는 명백한 증거다.

F4 : 죽은 함수

  • 아무도 호출하지 않는 함수는 삭제한다.
  • 이 또한 소스 코드 관리 시스템이 기억하므로 걱정할 필요 없다.

일반 General

G1 : 한 소스 파일에 여러 언어를 사용한다

  • 이상적으로는 소스 파일 하나에 언어 하나만 사용하는 방식이 가장 좋다.
  • 현실적으로는 불가피하지만, 각별한 노력을 기울여 언어 수와 범위를 줄이도록 애써야 한다.

G2 : 당연한 동작을 구현하지 않는다.

  • 최소 놀람의 원칙에 의거해 함수나 클래스는 다른 프로그래머가 당연하게 여길 만한 동작과 기능을 제공해야 한다.
  • 함수 기능을 직관적으로 예상하기 어려워 신뢰하지 못한다면 코드를 일일이 살펴야 한다.

G3 : 경계를 올바로 처리하지 않는다.

  • 코드는 올바로 동작해야 한다.
  • 모든 경계 조건을 찾아내고, 모든 경계 조건을 테스트하는 테스트 케이스를 작성하라.

G4 : 안전 절차 무시

  • 컴파일러 경고 일부를 꺼버리면 빌드가 쉬워져도 자칫하면 끝없는 디버깅에 시달릴 것이다.
  • 실패하는 테스트 케이스를 제껴두지 말자.

G5 : 중복

  • 가장 중요한 규칙 중 하나.
  • 어디서든 중복을 발견하면 없애라.
    • 똑같은 코드가 여러 차례 나오는 중복 👉👉 간단한 함수로 교체
    • switch/case, if/else 문으로 똑같은 조건을 거듭 확인하는 중복 👉👉 다형성으로 대체
    • 알고리즘이 유사하나 코드가 서로 다른 중복 👉👉
      1. TEMPLATE METHOD 패턴
      2. STRATEGY 패턴

G6 : 추상화 수준이 올바르지 못하다

  • 모든 저차원 개념은 파생 클래스에 넣고, 모든 고차원 개념은 기초 클래스에 넣는다.
    • 세부구현과 관련된 상수, 변수, 유틸리티 함수는 기초 클래스에 넣으면 안된다.
      • 기초 클래스는 구현 정보에 무지해야 마땅하다.
    • 소스 파일, 컴포넌트, 모듈 또한 마찬가지.
  • 잘못된 추상화 수준은 거짓말이나 꼼수로 해결하지 못한다.

G7 : 기초 클래스가 파생 클래스에 의존한다

  • 일반적으로 기초 클래스는 파생 클래스를 아예 몰라야 마땅하다.
    • 예외: 파생 클래스 개수가 확실히 고정되었다면 기초 클래스에 파생 클래스를 선택하는 코드가 들어간다.
      • FSM(Finite State Machine) 구현에서 자주 있는 사례
      • 하지만 FSM는 기초 클래스와 파생 클래스가 굉장히 밀접하며 항상 같은 JAR로 배포함
      • 일반적인 기초 클래스와 파생 클래스는 다른 JAR 파일로 배포하는 편이 좋음
  • 기초 클래스와 파생 클래스가 독립적이라면 개별 컴포넌트 단위로 시스템을 배치할 수 있다.
  • 즉, 변경이 시스템에 미치는 영향이 아주 작아지므로 시스템을 유지보수 하기가 한결 수월함.

G8 : 과도한 정보

  • 잘 정의된 모듈은 인터페이스가 아주 작지만 많은 동작이 가능하다.
  • 잘 정의된 인터페이스는 많은 함수를 제공하지 않아서 결합도가 낮다.
  • 우수한 소프트웨어 개발자는 클래스나 모듈 인터페이스에 노출할 함수를 제한할 술 알아야 한다.
    • 클래스가 제공하는 메서드 수는 적을수록 좋다.
    • 함수가 아는 변수 수도 적을수록 좋다.
    • 클래스에 들어있는 인스턴스 변수도 적을수록 좋다.
  • 자료를 숨겨라.
    • 유틸리티 함수를 숨겨라.
    • 상수와 임시 변수를 숨겨라.
    • 메서드나 인스턴스 변수가 넘쳐나는 클래스는 피하라.
    • 하위 클래스에서 필요하다는 이유로 protected 변수나 함수를 마구 생성하지 마라.
    • 인터페이스를 매우 작게, 그리고 매우 깐깐하게 만들어라.
    • 정보를 제한해 결합도를 낮춰라.

(제일 어려운 부분으로 느껴진다.)

G9 : 죽은 코드

  • 죽은 코드란 실행되지 않는 코드를 가리킨다.
    • 불가능한 조건을 확인하는 If문
    • throw 문이 없는 try 문에서 catch 블록
    • 아무도 호출하지 않는 유틸리티 함수
    • switch/case 문에서 불가능한 case 조건
  • 죽은 코드는 설계가 변해도 제대로 수정되지 않는다.
  • 발견 즉시 제거해라.

G10 : 수직 분리

  • 변수와 함수는 사용되는 위치에 가깝게 정의한다.
    • 지역 변수는 처음으로 사용하기 직전에 선언하고 수직으로 가까운 곳에 위치해야 한다.
  • 비공개 함수는 처음으로 호출한 직후에 정의한다.
    • 비공개 함수는 처음으로 호출되는 위치를 찾은 후 조금만 아래로 내려가면 쉽게 눈에 띄어야 한다.

G11 : 일관성 부족

  • 어떤 개념을 특정 방식으로 구현했다면 유사한 개념도 같은 방식으로 구사한다.
    • G2에서 언급한 최소 놀람의 원칙에도 부합한다.
    • 표기법은 신중하게 선택하며, 선택한 표기법은 신중하게 따른다.
  • 착실하게 적용한다면 간단한 일관성만으로도 코드를 읽고 수정하기가 대단히 쉬워진다.

(이 부분을 작성하고 ~음, ~함과 ~한다. ~하라. 두 맺음말 문법이 혼용되어 있던 현재의 포스팅을 일괄적으로 ~한다. 맺음말로 통일 시키고 왔다...)

G12 : 잡동사니

  • 잡동사니를 없애라.
    • 비어 있는 기본 생성자
    • 아무도 사용하지 않는 변수
    • 아무도 호출하지 않는 함수
    • 정보를 제공하지 못하는 주석 등

G13 : 인위적 결함

  • 서로 무관한 개념을 인위적으로 결합하지 않는다.
    • 예시: 일반적인 enum은 특정 클래스에 속할 이유가 없다.
      • enum이 클래스에 속한다면 enum을 사용하는 코드가 특정 클래스를 알아야만 한다.
      • 범용 static 함수도 마찬가지로 특정 클래스에 속할 이유가 없다.
  • 일반적으로 인위적인 결합은 직접적인 상호작용이 없는 두 모듈 사이에서 일어난다.
    • 뚜렷한 목적 없이 변수, 상수, 함수를 당장 편하지만 잘못된 위치에 넣어버린 결과다.
    • 함수, 상수, 변수를 선언할 때는 시간을 들여 올바른 위치를 고민한다.

G14 : 기능 욕심

  • 클래스 메서드는 자기 클래스의 변수와 함수에만 관심을 가져야지 다른 클래스의 변수와 함수에 관심을 가져서는 안된다.
    • 메서드가 다른 객체의 참조자와 변경자를 사용해 그 객체 내용을 조작한다면 메서드가 그 객체 클래스의 범위를 욕심내는 탓이다.
    • 자신이 그 클래스에 속해 그 클래스 변수를 직접 조작하고 싶다는 뜻이다.
  • 기능 욕심은 한 클래스의 속사정을 다른 클래스에 노출하므로, 별다른 문제가 없다면 제거하는 편이 좋다.
  • 때로는 어쩔 수 없는 경우도 있다.
    • 제거하거나 옮기면 다른 원칙들을 어기는 경우

G15 : 선택자 인수

  • 선택자 인수는 목적을 기억하기 어려울 뿐 아니라 각 선택자 인수가 여러 함수를 하나로 조합한다.
  • 일반적으로 인수를 넘겨 동작을 선택하는 대신 새로운 함수를 만드는 편이 좋다.

G16 : 모호한 의도

  • 코드를 짤 때는 의도를 최대한 분명히 밝힌다.
  • 저자의 의도를 흐리는 방식들

G17 : 잘못 지운 책임

  • 코드는 독자가 자연스럽게 기대할 위치에 배치한다.
  • 독자에게 직관적인 위치가 아니라 개발자에게 편한 함수에 배치하려 하지 않는다.
    • 예시1 : PI 상수의 위치는 어디일까?
      1. Math 클래스
      2. Trigonometry 클래스
      3. Circle 클래스
      • 정답 : 삼각함수를 선언한 클래스
    • 예시2 : 어느 쪽이 총계를 계산해야 옳은가?
      1. 보고서 모듈의 getTotalHours 라는 함수
      2. 근무 시간을 입력 받는 모듈의 saveTimeCard 함수
      • 정답 : 1번
        • 때로 성능을 높이고자 2번의 모듈에서 총계를 계산하는 편이 좋다고 판단할 수 있음
        • 그러려면 이 사실을 반영하여 함수 이름을 제대로 지어야함.
        • 예: computeRunningTotalOfHours

(수학에 무지해서 그런지 아직 예시1은 이해하지 못함...)

G18 : 부적절한 static 함수

  • 우리는 간혹 재정의 할 수 있는 함수를 static으로 정의할 수 있다.
  • 일반적으로 static 함수보다 인스턴스 함수가 더 좋다.
    • 조금이라도 의심스럽다면 인스턴스 함수로 정의한다.
    • 반드시 static 함수로 정의해야겠다면 재정의할 가능성은 없는지 꼼꼼히 따져본다.

G19 : 서술적 변수

  • 프로그램 가독성을 높이는 가장 효과적인 방법 중 하나가 계싼을 여러 단계로 나누고 중간 값으로 서술적인 변수 이름을 사용하는 방법이다.
  • 서술적인 변수 이름은 많이 써도 괜찮다.
    • 일반적으로는 많을수록 더 좋다.
    • 계싼을 몇 단계로 나누고 중간값에 좋은 변수 이름만 붙여도 해독하기 어렵던 모듈이 읽기 쉬운 모듈로 탈바꿈한다.

G20 : 이름과 기능이 일치하는 함수

  • 이름만으로 분명하지 않기에 구현을 살피거나 문서를 뒤적여야 한다면 더 좋은 이름으로 바꾸거나 아니면 더 좋은 이름을 붙이기 쉽도록 기능을 정리해야 한다.

G21 : 알고리즘을 이해해라

  • 구현이 끝났다고 선언하기 전에 함수가 돌아가는 방식을 확실히 이해하는지 확인하라.
    • 테스트 케이스를 모두 통과하는 사실만으로는 부족하다.
    • 작성자가 알고리즘이 올바르다는 사실을 알아야 한다.
  • 기능이 뻔히 보일 정도로 함수를 깔끔하고 명확하게 재구성하는 방법이 최고다.

G22 : 논리적 의존성은 물리적으로 드러내라

  • 의존하는 모듈이 상대 모듈에 대해 뭔가를 논리적으로만 의존하면 안된다.
  • 의존하는 모든 정보를 명시적으로 요청하는 편이 좋다.

G23 : If/Else 혹은 Switch/Case 문보다 다형성을 사용하라

  • 대다수의 개발자가 swtich 문을 사용하는 이유는 그 상황에서 가장 올바른 선택이기 보다는 당장 손쉬운 선택인 경우가 많다.
    • 그 전에 다형성을 먼저 고려해보자.
  • 유형보다 함수가 더 쉽게 변하는 경우는 극히 드물다. 모든 switch 문을 의심하자.
  • 선택 유형 하나에는 switch 문을 한번만 사용한다.
  • 같은 선택을 수행하는 다른 코드에서는 다형성 객체를 생성해 switch 문을 대신한다.

G24 : 표준 표기법을 따르라

  • 팀은 업계 표준에 기반한 구현 표준을 따라야 한다.
    • 인스턴스 변수 이름을 선언하는 위치
    • 클래스/메서드/변수 이름을 정하는 방법
    • 괄호를 넣는 위치
    • 그외 등
    • 표준을 설명하는 문서는 코드 자체로 충분해야 한다.
  • .팀이 정한 표준은 팀원들 모두가 따라야 한다.
    • 실제 괄호를 넣는 위치보다 모두가 동의한 위치에 넣는다는 사실이 중요하다.

G25 : 매직 숫자는 명명된 상수로 교체하라

  • 일반적으로 코드에서 숫자를 사용하지 말라는 규칙이다. 숫자는 명명된 상수 뒤로 숨긴다.
    • LINES_PER_PAGE = 55
  • 어떤 상수는 이해하기 쉬우므로, 코드 자체가 자명하다면 상수 뒤로 숨길 필요가 없다.
    • double circumference = radius Math.PI 2;
  • 3.1415926.. 같은 상수 역시 독자가 금방 이해할 수 있지만, 그냥 숫자로 두는 경우 오류가 발생할 가능성이 너무 커서 명명된 상수로 교체한다.

G26 : 정확하라

  • 코드에서 뭔가를 결정할 때는 정확히 결정한다.
  • 코드에서 모호성과 부정확은 의견차나 게으름의 결과다. 어느 쪽이든 제거해야 마땅하다.

G27 : 관례보다는 구조를 사용하라

  • 설계 결정을 강제할 때는 규칙보다 관례를 사용한다.
  • 명명 관례도 좋지만 구조 자체로 강제하면 더 좋다.

(이해 못했다. 구조 > 관례 > 규칙 이라는 말인가?)

G28 : 조건을 캡슐화하라

  • 부울 논리는 이해하기 어렵다. 조건의 의도를 분명히 밝히는 함수로 표현하라.
  • 예시: if(timer.hasExpired() && !timer.isRecurrent()) 👉👉 if(shouldBeDeleted(timer))

G29 : 부정 조건은 피하라

  • 부정 조건은 긍정 조건보다 이해하기 어렵다. 가능하면 긍정 조건으로 표현한다.
  • 예시 : if (!buffer.shouldNotCompact()) 👉👉 if (buffer.shouldCompact())

G30 : 함수는 한 가지만 해야 한다

  • 한 가지만 수행하는 수행하는 좀 더 작은 함수 여럿으로 나눠야 마땅하다.

G31 : 숨겨진 시간적인 결합

  • 시간적인 결합이 필요할 때 이를 드러내야 한다.

G32 : 일관성을 유지하라

(G11이랑 뭐가 다른거지)

  • 코드 구조를 잡을 때는 이유를 고민하고 그 이유를 코드 구조로 명백히 표현하라.

G33 : 경계 조건을 캡슐화하라

  • 경계 조건은 빼먹거나 놓치기 십상이라, 한 곳에서 별도로 처리한다.
  • 다시말해, +1이나 -1을 흩어놓지 않는다.

G34 : 함수는 추상화 수준을 한 단계만 내려가야 한다.

  • 함수 내 모든 문장은 추상화 수준이 동일해야 하고, 그 추상화 수준은 함수 이름이 의미하는 작업보다 한 단계만 낮아야 한다.

(아직 코드에서 문제가 되는 추상화 수준을 바로 정확하게 유추하기가 힘들다)

G35 : 설정 정보는 최상위 단계에 둬라

  • 추상화 최상위 단계에 둬야 할 기본값 상수나 설정 관련 상수를 저차원 함수에 숨겨서는 안된다.
  • 대신 고차원 함수에서 저차원 함수를 호출할 때 인수로 넘긴다.

G36 : 추이적 탐색을 피하라

  • 일반적으로 한 모듈은 주변 모듈을 모를수록 좋다.
  • 자신이 직접 사용하는 모듈만 알아야 한다.

자바 Java

일단 아직 자바 관련 공부는 리스트 순위에서 높지 않아서 목록만 적고 패스 후 나중에 필요하면 업데이트.

J1 : 긴 import 목록을 피하고 와일드카드를 사용하라

J2 : 상수는 상속하지 않는다

J3 : 상수 대 Enum

구현을

이름 Names

N1 : 서술적인 이름을 사용하라

  • 이름은 성급하게 정하지 않고, 서술적인 이름을 신중하게 고른다.
  • 소프트웨어가 진화하면 의미도 변하므로 선택한 이름이 적합한지 자주 되돌아본다.
  • 소프트웨어 가독성의 90%는 이름이 결정한다.

N2 : 적절한 추상화 수준에서 이름을 선택하라

  • 구현을 드러내는 이름은 피하라.
  • 작업 대상 클래스나 함수가 위치하는 추상화 수준을 반영하는 이름을 선택하라.

N3 : 가능하다면 표준 명명법을 사용하라

  • 기존 명명법을 사용하는 이름은 이해하기 더 쉽다.
    • DECORATOR 패턴을 활용한다면 장식하는 클래스 이름에 Decorator라는 단어 사용하기
    • 자바에서는 객체를 문자열로 변환하는 함수는 toString이라는 이름을 많이 사용하기
      • 이런 이름은 관례를 따르는 편이 좋다.

N4 : 명확한 이름

  • 함수나 변수의 목적을 명확히 밝히는 이름을 선택한다.
  • 이름이 아주 길어도 서술성이 충분히 메꾼다.

N5 : 긴 범위는 긴 이름을 사용하라

  • 이름 범위가 길수록 이름을 정확하고 길게 짓는다.
  • 이름이 짧은 변수나 함수는 범위가 길어지면 의미를 잃는다.
  • 범위가 작으면 아주 짧은 이름을 사용해도 괜찮다.
    • 범위가 5줄 안팎이라면 i나 j와 같은 변수 이름도 괜찮다.
    • 이름이 짧은 변수나 함수는 범위가 길어지면 의미를 잃는다.

N6 : 인코딩을 피하라

  • 이름에 유형 정보나 범위 정보를 넣어서는 안 된다.
  • m_, f, vis_와 같은 접두어는 필요하지 않다.
    • 중복된 정보이며 독자만 혼란하게 만든다.

N7 : 이름으로 부수 효과를 설명하라

  • 함수, 변수, 클래스가 하는 일을 모두 기술하는 이름을 사용한다.
  • 이름에 부수 효과를 숨기지 않는다.

테스트 Tests

T1 : 불충분한 테스트

  • 테스트 케이스는 잠재적으로 깨질 만한 부분을 모두 테스트해야 한다.
  • 테스트 케이스가 확인하지 않는 조건이나 검증하지 않는 계산이 있다면 그 테스트는 불완전하다.

T2 : 커버리지 도구를 사용하라

  • 커버리지 도구는 테스트가 빠뜨리는 공백을 알려준다.
  • 테스트가 불충분한 모듈, 클래스, 함수를 찾기 쉬워진다.

T3 : 사소한 테스트를 건너뛰지 마라

  • 사소한 테스트는 짜기 쉽다.
  • 사소한 테스트가 제공하는 문서적 가치는 구현에 드는 비용을 넘어선다.

T4 : 무시한 테스트는 모호함을 뜻한다

  • 불분명한 요구사항은 테스트 케이스를 주석으로 처리하거나 테스트 케이스에 @Ignore를 붙여 표현한다.(엥)

T5 : 경계 조건을 테스트하라

  • 경계 조건은 각별히 신경 써서 테스트한다.

T6 : 버그 주변은 철저히 테스트하라

  • 버그는 서로 모이는 경향이 있다.

T7 : 실패 패턴을 살펴라

  • 때로는 테스트 케이스가 실패하는 패턴으로 문제를 진단할 수 있다.
  • 합리적인 순서로 정렬된 꼼꼼한 테스트 케이스는 실패 패턴을 드러낸다.

T8 : 테스트 커버리지 패턴을 살펴라

  • 통과하는 테스트가 실행하거나 실행하지 않는 코드를 살펴보면 실패하는 테스트 케이스의 실패 원인이 드러난다.

T9 : 테스트는 빨라야 한다

  • 느린 테스트 케이스는 실행하지 않게 된다.

결론

  • 사실상 가치 체계는 이 책의 주제이자 목표다.
  • 일군의 규칙만 따른다고 깨끗한 코드가 얻어지지 않는다.
  • 전문가 정신과 장인 정신은 가치에서 나온다.
  • 그 가치에 기반한 규율과 절제가 필요하다

개인적인 여담

  • 다음엔 eBook으로 사야지..
  • 가치 체계
    1. 어떤 대상의 중요성을 판단하고 결정하는 일정한 체계.
profile
Web Developer / Read "KEEP CALM CARRY ON" Poster

0개의 댓글