책/CleanCode

CleanCode - 17.냄새와 휴리스틱

728x90
반응형
  • 휴리스틱
    • 불충분한 시간이나 정보로 인해 합리적인 판단을 할 수 없거나, 체계적이면서 합리적인 판단이 굳이 필요하지 않은 상황에서 사람들이 빠르게 사용할 수 있도록 보다 용이하게 구성된 간편 추론의 방법 

    • 문제의 답을 경험 법칙, 경험에 의한 추측, 직관적 판단, 상식, 시행착오 등의 방법을 사용하여 구하는 것 <-> 알고리즘 (논리적 해답)
    • 여기서 나쁜 냄새가 난다 

주석 (Comment)

C1 - 부적절한 정보

  • 다른 시스템에 저장할 정보는 주석으로 작성하지 말자 
  • 주석은 코드와 설계에 기술적인 설명을 부연하는 수단 

C2 - 쓸모 없는 주석 

  • 오래되거나 엉뚱하거나, 잘못된 주석은 과감하게 버리고, 재빨리 삭제하자 

C3 - 중복된 주석 

  • 클린코드 앞서서도 언급한 것처럼 주석이 아닌 코드로 표현하려고 노력하는 것이 좋다. 
  • 구구절절하게 설명하는 주석은 중복된 주석일 뿐, 주석은 코드만으로 다하지 못하는 설명을 할 뿐 
// Bad 1, 코드로 충분한 불필요한 주석

i++; // i 증가

// Bad 2, 함수 서명만 달랑 기술하는 javadoc
/**
 * @param sellRequest
 * @return
 * @throws ManagedComponentException
 * **/
public SellResponse beginSellItem(SellRequest sellRequest) 
    throws ManagedComponentException {
}

C4 - 성의없는 주석

  • 단어를 신중히 고를 것 
  • 문법과 구두점을 올바르게 사용할 것 
  • 나불대거나 주절대지 않을 것 
  • 당연한 말은 반복하지 않을 것 
  • 시간을 들여서 최대한 간결하고 명료하게 작성할 것 

C5 - 주석 처리된 코드 

  • 주석으로 처리된 코드는 즉각 지우기
  • Source Code 관리 시스템이 기억하니까 과감하게 지우기 
  • 누군가가 정말 필요하다면 이전 버전의 코드를 가져오면 된다. 

 

환경(Environment)

E1  - 여러 단계로 빌드하는 것 

  • 빌드는 간단히 한 단계로 끝나야 한다. 
  • 소스 코드 관리 시스템에서 이것저것 따로따로 체크아웃할 필요가 없어야 한다. 
  • 한 명령으로 전체를 체크아웃해서 한 명령으로 빌드할 수 있어야 한다.
  • 온갖 JAR, XML 기타 시스템의 파일을 찾는 수고가 들어가선 안된다.  
    더보기
    svn get mySystem 
    cd mySystem
    ant all 

E2  - 여러 단계로 테스트하는 것

  • 모든 단위테스트는 한 명령으로 돌려야 한다. 
  • IDE에서 버튼 하나로 모든 테스트를 돌린다면 가장 이상적이다. 
  • 모든 테스트를 한 번에 실행하는 능력은 아주 근복적이고 아주 중요하므로 빠르고, 쉽고 명백해야 한다. 

 

함수(Function)

F1 - 너무 많은 인수

  • 함수에서 인수 개수는 작을 수록 좋다.
  • 아예 없으면 가장 좋다. 
  • 넷 이상은 최대한 피하자 (메서드를 나누는 것이 더욱 좋은 방법일 지도) 

F2 - 출력 인수

  • 출력 인수(return)은 직관을 정면으로 위배한다. 
  • 함수에서 뭔가의 상태를 변경해야 한다면 함수가 속한 객체의 상태를 변경한다. 

F3 - 플래그(Flag) 인수 

  • boolean 인수는 함수가 여러 기능을 수행한다는 명백한 증거 
  • 플래그 인수는 혼란을 초래하므로 피하도록 하자 

F4 - 죽은 함수

  • 호출하지 않는 함수는 삭제
  • 죽은 코드는 낭비이고, 혼란만 야기하므로 과감히 삭제 
  • 소스 코드 관리 시스템이 기억하므로 부담 X 

일반(General)

G1 - 한 소스 파일에 여러 언어 사용

  • 어떤 XML, HTML, YAML 등 현재 소스 파일은 다양한 언어를 지원하지만 소스 파일 하나에 언어 하나만 사용한 방식이 가장 이상적 
  • 불가피한 경우라면 소스 파일에서 언어의 수와 범위를 최대한 줄이자 

G2 - 당연한 동작을 구현하지 않음

  • 최소 놀람의 원칙(The Principle of Least Surprise)에 기반해서 함수나 클래스는 프로그래머가 당연하게 여길만한 동작과 기능을 제공해야한다. 

놀람 최소화의 원칙 (POLA)
더 일반적으로 이야기하면 이 원칙은 시스템의 구성 요소가 대부분의 사용자들이 행동할 것으로 예측되는 방식으로 동작하는 것이 좋다는 것을 의미한다.
즉, 해당 동작이 사용자들을 놀래키지 않는 것이 좋다는 것이다.
  • 당연한 동작을 구현하지 않으면 다른 개발자가 더 이상 함수 이름만으로 함수 기능을 직관적으로 예상하기 어려움 
    이는 작성자를 신뢰하지 못하게 되어 코드를 일일이 살펴보는 부작용을 초래함 

G3 - 경계를 올바로 처리하지 않음

  • 부지런함을 대신할 지름길은 없다
    • 모든 경계 조건, 모든 다크사이드, 모든 기벽, 모든 예외는 우아하고 직관적인 알고리즘을 부시게 된다. 
  • 스스로의 직관에 의존하지 말자 -> 모든 경계 조건을 찾아내고, 모든 경계 조건을 테스트하는 TC를 작성하자 

G4 - 안전 절차 무시 

  • serialVersionUID를 직접 제어할 필요가 있을지도 모르지만, 그래도 직접 제어는 언제나 위험하다
  • 실패하는 TC를 일단 제껴두고 나중으로 미루는 태도는 지양하자 

G5 - 중복

  • DRY(Don't Repeat Yourself) 
  • 코드에서 중복을 발견할 때마다 추상화할 기회이다. 
  • 중복된 코드는 하위 루틴 혹은 다른 클래스로 분리 
  • 추상화로 중복을 정리하면 설계 언어의 어휘가 늘어남   추상화 수준 ↑ → 구현 속도 ↑ , 오류 ↓
  • 중복의 유형
    1. 가장 많은 유형 
      • 똑같은 코드가 여러 차례 나오는 중복 
    2. 좀 더 미묘한 유형 
      • 여러 모듈에서 일련의 switch/case 나 if/else 문으로 똑같은 조건을 거듭 확인하는 중복 
      • 해당 중복은 다형성으로 대체할 것 
    3. 알고리즘이 유사하나 코드가 서로 다른 중복 
      • template method 패턴 혹은 strategy 패턴으로 중복을 제거할 것 

 

G6 - 추상화 수준이 올바르지 못함

  • 추상화  - 저차원 상세 개념에서 고차원 일반 개념을 분리 
  • 때로는 추상 클래스(고차원 개념)와 파생 클래스(저차원 개념)을 생성해 추상화를 수행하곤 한다. 
  • 추상 클래스는 구현 정보에 대하여 모르는 것이 바람직하다 
  • 고차원 개념과 저차원 개념을 섞어서는 안된다. 
public interface Stack{

    Object pop() throws EmptyException;
    void push(Object o) throws FullException;
    double percentFull(); // 얘가 여기가 맞을까? -> bounded하게 구현하지 않는 스택이라면?
    class EmptyException extends Exception {}
    class FullException extends Exception {}

}
  • 추상화는 개발자에게 가장 어려운 작업
  • 잘못된 추상화를 임시변통으로 고치기는 불가능하다라는 점을 유의할 것 

G7 - 기초 클래스(부모 클래스)가 파생 클래스에 의존

  • 개념을 기초 클래스와 파생 클래스로 나누는 가장 흔한 이유는
    고차원 기초 클래스 개념을 저차원 파생 클래스 개념으로부터 분리해 독립성을 보장하기 위해서다.
  • 그러므로 기초 클래스가 파생 클래스를 사용한다면 뭔가 문제가 있다는 말이다.
  • 일반적으로 기초 클래스는 파생 클래스를 아예 몰라야 마땅하다.
    • 예외 : FSM 구현엣 파생클래스를 선택하는 코드가 들어간다.
      하지만 FSM은 기초 클래스와 파생 클래스가 굉장히 밀접하며 언제나 같은 JAR 파일로 배포한다.
      (일반적으로는 기초 클래스와 파생 클래스를 다른 JAR에서 배포하는 편이 좋다) 
  • 이렇게 구현하면 변경이 시스템에 미치는 영향이 아주 작아지므로 현장에서 시스템을 유지보수 하기가 한결 수월하게 된다.

G8 - 과도한 정보

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

G9 - 죽은 코드

  • 죽은 코드란 실행되지 않는 코드를 가리킨다.
  • 불가능한 조건을 확인하는 if 문과 throw 문이 없는 try 문에서 catch 블록이 좋은 예다.
  • 아무도 호출하지 않는 유틸리티 함수와 switch/case 문에서 불가능한 case 조건도 또 다른 좋은 예다.
  • 발견시 시스템에서 제거하라.

G10 - 수직 분리

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

G11 - 일관성 부족

  • 어떤 개념을 특정 방식으로 구현했다면 유사한 개념도 같은 방식으로 구현하라.
  • 표기법은 신중하게 선택하며, 일단 선택한 표기법은 신중하게 따른다.
  • 한 함수에서 response 라는 변수에 HttpServletResponse 인스턴스를 저장했다면
    (HttpServletResponse 객체를 사용하는) 다른 함수에서도 일관성 있게 동일한 변수 이름을 사용한다.
  • 착실하게 적용한다면 이처럼 간단한 일관성만으로도 코드를 읽고 수정하기가 대단히 쉬워진다.

G12 - 잡동사니

  • 비어 있는 기본 생성자가 왜 필요한가? 쓸데없이 코드만 복잡하게 만든다.
  • 소스 파일은 언제나 깔끔하게 정리하라! 잡동사니를 없애라!
    • 사용하지 않는 변수
    • 아무도 호출하지 않는 함수
    • 정보를 제공하지 못하는 주석

G13 - 인위적 결합

  • 서로 무관한 개념을 인위적으로 결합하지 않는다.
  • 일반적인 enum은 특정 클래스에 속할 이유가 없다.
  • enum이 클래스에 속한다면 enum을 사용하는 코드가 특정 클래스를 알아야만 한다.
  • 범용 static 함수도 마찬가지로 특정 클래스에 속할 이유가 없다.
  • 함수, 상수, 변수를 선언할 때는 시간을 들여 올바른 위치를 고민한다.
  • 그저 당장 편한곳에 선언하고 내버려두면 안 된다.

G14 - 기능 욕심

  • 마틴 파울러가 말하는 코드 냄새 중 하나다.
  • 클래스 메서드는 다른 클래스의 변수와 함수에 관심을 가져서는 안 된다.
  • 다른 객체의 참초자와 변경자를 사용해 그 객체를 조작하면 그 객체 클래스 범위를 욕심내는 탓이다.
public class HoulryPayCalculator {
	public Money calculateWeeklyPay(HourlyEmployee e) {
    	  int tenthRate = e.getTenthRate().getPennies();
          int thethWorked = e.getTenthsWorked();
          int straightTime = Math.min(400, tenthsWorked);
          int overTime = Math.max(0, tenthsWorked - straightTime);
          int straightPay = straightTime * tenthRate;
          int overTimePay = (int)Math.round(overTime*tenthRate*1.5);
          return new Money(straightPay + overTimePay);
     }
 }
 
 /***
 
 해당 메서드는 HourlyEmployee 객체의 온갖 정보를 참조하는데, 이는 클래스의 범위를 욕심내기 때문에
 해당 메서드는 이러한 기능 욕심에 해당하고 다른 클래스의 내부를 노출한다. 
 ***/
  • 기능 욕심은 한 클래스의 속사정을 다른 클래스에 노출하므로, 별다른 문제가 없다면, 제거하는 편이 좋다.
  • 하지만 때로는 어쩔 수 없는 경우도 생긴다.

G15 - 선택자 인수

  • 선택자 인수는 큰 함수를 작은 함수 여렷으로 쪼개지 않으려는 게으름의 소산이다.
  • boolean 뿐만 아니라 enum, int 등 함수 동작을 제어하려는 인수는 하나 같이 바람직하지 않다.
  • 일반적으로, 인수를 넘겨 동작을 선택하는 대신 새로운 함수를 만드는 편이 좋다.

G16 - 모호한 의도

  • 코드를 짤 때는 의도를 최대한 분명히 밝힌다.
  • 행을 바꾸지 않고 표현한 수식, 헝가리식 표기법, 매직 번호 등은 모두 저자의 의도를 흐린다.
  • 독자에게 의도를 분명히 표현하도록 시간을 투자할 가치가 있다.
public int m_otCalc(){
	return iThsWkd * iThsRte + (int) Math.round(0.5 * iThsRte * Math.max(0,iThsWkd -400));
}

G17 -  잘못 지운 책임

  • 소프트웨어 개발자가 내리는 가장 중요한 결정 중 하나가 코드를 배치하는 위치다.
  • 때로는 개발자가 ‘영리하게’ 기능을 배치한다. 독자에게 직관적인 위치가 아니라 개발자에게 편한 함수에 배치한다.
  • 예를 들어 보고서 모듈에 getTotalHours라는 함수가 있고,
    근무 시간을 입력 받는 모듈에 saveTimeCard라는 함수가 있다 가정한다.
    이름만 보았을 때 어느 쪽이 총계를 계산해야 옳을까? 답은 명백하다.

G18 - 부적절한 static 함수

  • 메서드를 소유하는 객체에서 가져오는 정보가 아니고 인수에서만 정보를 가져와야한다.
  • 일반적으로 static 함수보다 인스턴스 함수가 더 좋다.
  • 조금이라도 의심스럽다면 인스턴스 함수로 정의한다.
  • 반드시 static 함수로 정의해야겠다면 재정의할 가능성은 없는지 꼼꼼히 따져본다.
//특정 객체와 관련이 없으면서 모든 정보를 인수에서 가져오니까 static 함수로 여겨도 적당할까? 

HourlyPayCalculator.calculatePay(employee, overtimeRate); 

// 함수를 재정의할 가능성이 존재하기 때문에 static 함수로 정의해선 안된다. 
// 해당 함수는 Employee 클래스에 속하는 인스턴스 함수여야한다.

G19 - 서술적 변수

  • 프로그램 가독성을 높이는 가장 효과적인 방법
    • 계산을 여러 단계로 나눈다.
    • 중간 값으로 서술적인 변수 이름을 사용.
  • 서술적인 변수 이름은 많이 써도 괜찮다.
  • 일반적으로는 많을수록 더 좋다.

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

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

G21 - 알고리즘을 이해하라

  • 대다수 괴상한 코드는 사람들이 알고리즘을 충분히 이해하지 않은 채 코드를 구현한 탓이다.
  • 구현이 끝났다고 선언하기 전에 함수가 돌아가는 방식을 확실히 이해하는지 확인하라.
  • 테스트 케이스를 모두 통과한다는 사실만으로 부족하다.
  • 작성자가 알고리즘이 올바르다는 사실을 알아야 한다.

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

  • 한 모듈이 다른 모률에 의존한다면 물리적인 의존성도 있어야 한다.
  • 논리적인 의존성만으로는 부족하다.
  • 의존하는 모듈이 상대 모듈에 대해 뭔가를 가정하면(즉, 논리적으로 의존하면) 안 된다.
  • 의존하는 모든 정보를 명시적으로 요청하는 편이좋다.

G23 - if/else 혹은 switch/case 보다는 다형성을 사용하라

  • 선택 유형 하나에는 switch 문을 한 번만 사용한다.
  • 같은 선택을 수행하는 다른 코드에서는 다형성 객체를 생성해 switch 문을 대신한다.

G24 - 표준 표기법을 따르라

  • 팀은 업계 표준에 기반한 구현 표준을 따라야 한다.
  • 표준을 설명하는 문서는 코드 자체로 충분해야 하며 별도 문서를 만들 필요는 없어야 한다.
  • 팀이 정한 표준은 팀원 모두가 따라야 한다.
  • 실제 괄호를 넣는 위치는 중요 하지 않다.
  • 모두가 동의한 위치에 넣는다는 사실이 중요하다.
  • 이 사실을 이해할 정도로 팀원들이 성숙해야 한다.

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

  • 매직숫자는 상수로 변경하라.
  • 어떤 상수는 이해하기 쉬우므로, 코드 자체가 자명하다면, 상수 뒤로 숨길 필요가 없다.
  • 하단 예제에 TWO (int 2)라는 상수가 반드시 필요할까? 어떤 공식은 그냥숫자를 쓰는 편이 훨씬 좋다.
double circumference = radius * Math.PI * 2;
  • 매직 숫자라는 용어는 단지 숫자만 의미하지 않는다. 의미가 분명하지 않은 토큰을 모두가리킨다.

G26 - 정확하라

  • 검색 결과 중 첫 번째 결과만 유일한 결과로 간주하는 행동은 순진하다.
  • 코드에서 뭔가를 결정할 때는 정확히 결정한다.
  • 결정을 내리는 이유, 예외 처리 방법을 분명히 알아야한다.
  • 호출하는 함수가 null을 반환할지도 모른다면 null을 반드시 점검한다.
  • 코드에서 모호성과 부정확은 의견차나 게으름의 결과다. 어느 쪽이든 제거해야 마땅하다.

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

  • 설계 결정을 강제할 때는 규칙보다 관례를 사용한다.
  • 명명 관례도 좋지만 구조 자체로 강제하면 더 좋다.
  • enum 변수가 멋진 switch/case 문보다 추상 메서드가 있는 기초 클래스가 더 좋다.

G28 - 조건을 캡슐화하라

  • 조건의 의도를 분명히 밝히는 함수로 표현하라.
// Bad
if (timer.hasExpired() && !timer.isRecurrent())
	
// Good
if (shouldBeDeleted(timer))

G29 - 부정 조건은 피하라

  • 부정 조건은 긍정 조건보다 이해하기 어렵다.
  • 가능하면 긍정 조건으로 표현한다.

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

  • 함수를 짜다보면 한 함수 안에 여러 단락을 이어, 일련의 작업을 수행하고픈 유혹에 빠진다.
  • 한 가지만 수행하는 좀 더 작은 함수 여렷으로 나눠야 마땅하다.
// bad
public void pay() {
  for (Employee e : employees) {
    if (e.isPayday()) {
      Money pay = e.calculatePay();
      e.deliveryPay(pay);
    }
  }
}

// good
public void pay() {
  for (Employee e : employees) {
    payIfNeessary(e);
  }
}

public void payIfNecessary(Employee e) {
  if (e.isPayday()) {
    cacluateAndDeliverPay(e);
  }
}

private void calculateAndDeliveryPay(Employee e) {
  Money pay = e.calculatePay();
  e.deliverPay(pay);
}

G31 - 숨겨진 시간적인 결합

  • 시간적인 결합이 필요하다, 하지만 시간적인 결합을 숨겨서는 안 된다.
  • 함수를 짤 때는 함수 인수를 적절히 배치해 함수가 호출되는 순서를 명백히 드러낸다.
// Bad, 세 함수가 실행되는 순서가 중요하다.
public class MoogDriver {
  public void dive(String reason) {
    staturateGradient();
    reticulateSplines();
    diveForMoog(reason);
  }
} 

// Good, 일종의 연결소자를 생성해 시간적인 결합을 노출한다.
public class MoogDriver {
  public void dive(String reason) {
    Gradient gradient = staturateGradient();
    List<Spline> splines = reticulateSplines(gradient);
    diveForMoog(splines, reason);
  }
}
  • 위에서 인스턴스 변수를 그대로 두었다는 사실에 주목한다.
  • 해당 클래스의 private 메서드에 필요한 변수일지도 모른다.
  • 그렇다 치더라도 제자리를 찾은 변수들이 시간적인 결합을 좀 더 명백히 드러낼 것이다.

G32 - 일관성을 유지하라

  • 코드 구조를 잡을 때는 이유를 고민해야한다.
  • 그리고 그 이유를 코드 구조로 명백히 표현해야한다.
  • 구조에 일관성이 없어 보인다면 남들이 맘대로 바꿔도 괜찮다고 생각 한다.
  • 시스템 전반에 걸쳐 구조가 일관성이 있다면 남들도 일관성을 따르고 보존한다.

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

  • 경계 조건은 빼먹거나 놓치기 십상이다.
  • 경계 조건은 한 곳에서 별도로 처리한다.
  • 코드 여기저기에서 처리하지 않는다.
  • 다시 말해, 코드 여기저기에 +1이나 -1 을 흩어놓지 않는다.
// Bad
if (level + 1 < tags.length) {
  parts = new Parse(body, tags, level + 1);
  body = null
}

// Good, 경계 조건 변수로 캡슐화
int nextLevel = level + 1;
if (nexLevel < tags.length) {
  parts = new Parse(body, tags, nextLevel);
  body = null
}

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

  • 함수내 모든 문장은 추상화 수준이 동일해야한다.
  • 그리고 그 추상화 수준은 함수 이름이 의미하는 작업보다 한 단계만 낮아야 한다.
  • 개념은 아주 간단하지만 인간은 추상화 수준을 뒤섞는 능력이 너무나도 뛰어나다.
  • 추상화 수준 분리는 리팩터링을 수행하는 가장 중요한 이유 중 하나다.

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

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

G36 - 추이적 탐색을 피하라

  • 일반적으로 한 모듈은 주변 모듈을 모를수록 좋다.
  • 좀 더 구체적으로, A가 B를 사용하고 B가 C를 사용한다 하더라도 A가 C를 알아야 할 필요는 없다는 뜻이다.
  • 이를 디미터의 법칙(Law of Demeter)이라 부른다.
  • 실용주의 프로그래머들은 부끄럼 타는 코드 작성 이라고도 한다.
  • 무엇이라 부르든 요지는 자신이 직접 사용하는 모듈만 알아야 한다는 뜻이다.

자바(Java)

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

  • 패키지에서 클래스를 둘 이상 사용한다면 와일드카드를 사용해 패키지 전체를 가져오라.
  • 와일드카드 import 문은 때로 이름 충돌이나 모호성을 초래한다.
  • 다소 번거롭지만 자주 발생하지 않으므로 여전히 와일드카드 import 문이 명시적인 import 문보다 좋다.
  • 추가) 네이버 컨벤션에서는 static import 에만 와일드 카드 허용한다.

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

  • 이런 상황은 여러 차례 접했는데 매번 인상이 구겨진다.
  • 어떤프로그래머는 상수를 인터페이스에 넣은 다음 그 인터페이스를 상속해 해당 상수를 사용한다.
public class HourlyEmployee extends Employee {
  int str = Math.min(tents, TENTS_PER_WEEK); // 이 상수는 어디에서 왔을까?
}

public abstract class Employee implements Payroll {
}

public interface Payroll {
  public static final int TENTS_PER_WEEK = 400; // 우엑! 이렇게 사용하면 안된다.
}
  • 참으로 끔직한 관행, 상수를 상속 계층 맨 위에 숨겨놨다.
  • 언어의 범위 규칙을 속이는 행위다.
  • 대신 static import 를사용하라.

J3 - 상수 대 Enum

  • 자바 5는 enum을 제공한다.
  • 마음껏 활용하라! public static final int라는 옛날 기교를 더 이상 사용할 필요가 없다.
  • int 보다 훨씬 더 유연하고 서술적인 강력한 도구다.

이름(Name)

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

  • 이름은 성급하게 정하지 않는다. 서술적인 이름을 신중하게 고른다.
  • 단순히 ‘듣기 좋은’ 충고가 아니다. 소프트웨어 가독성의 90%는 이름이 결정 한다.
  • 신중하게 선택한 이름은 추가 설명을 포함한 코드보다 강력하다.

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

  • 구현을 드러내는 이름은 피하라.
  • 작업 대상 클래스나 함수가 위치하는 추상화 수준을 반영하는 이름을 선택하라.
  • 코드를살펴볼 때마다 추상화 수준이 너무 낮은 변수 이름을 발견 하리라 발견할 때마다 기회를 잡아 바꿔놓아야 한다.
  • 안정적인 코드를 만들려면 지속적인 개선과 노력이 필요하다.

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

  • 기존 명명법을 사용하는 이름은 이해하기 더 쉽다.
  • DECORATOR 패턴을 활용 한다면 장식하는 클래스 이름에 Decorator 라는 단어를 사용해야 한다.
  • ToString 과 같이 관례가 존재하는 이름은 관례를 따른다.
  • 프로젝트에 유효한 의미가 담긴 이름을 많이 사용할수록 독자가 코드를 이해하기 쉬워진다.

N4 - 명확한 이름

  • 함수나 변수의 목적을 명확히 밝히는 이름을 선택한다.
  • 길더라도 명확하고 서술적으로 지어야 한다.

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

  • 이름 길이는 범위 길이에 비례해야 한다.
  • 범위가 작으면 아주 짧은 이름을 사용해도 괜찮다.
  • 하지만 범위가 길어지면 긴 이름을 사용한다.
  • 이름 범위가 길수록 이름을 정확하고 길게 짓는다.

N6 - 인코딩을 피하라

  • 이름에 유형 정보나 범위 정보를 넣어서는 안 된다.

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

  • 함수, 변수, 클래스가 하는 일을 모두 기술하는 이름을 사용한다.
  • 이름에 부수 효과를 숨기지 않는다.
  • 실제로 여러 작업을 수행하는 함수에다 동사 하나만 달랑 사용하면 곤란하다.
// Bad
getOos();

// Good, 없으면 새로운 객체를 만드는 메서드
createOrReturnOos();

테스트

T1 - 불충분한 테스트

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

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

  • 커버리지 도구는 테스트가 빠뜨리는 공백을 알려준다.
  • 대다수 IDE는 테스트 커버리지를 시각적으로 표현한다.

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

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

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

  • 때로는 요구사항이 불분명하기에 프로그램이 돌아가는 방식을 확신하기 어렵다.
  • 선택 기준은 모호함이 존재하는 테스트 케이스가 컴파일이 가능한지 불가능한지에 달려있다.

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

  • 경계 조건은 각별히 신경 써서 테스트한다.
  • 알고리즘의 중앙 조건은 올바로 놓고 경계 조건에서 실수하는 경우가 흔하다.

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

  • 버그는 서로 모이는 경향이 있다.
  • 한 함수에서 버그를 발견했다면 그 함수를 철저히 테스트하는 편이 좋다.
  • 십중팔구 다른 버그도 발견하리라.

T7 - 실패 패턴을 살펴라

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

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

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

T9 - 테스트는 빨라야 한다

  • 느린 테스트 케이스는 실행하지 않게 된다.
  • 일정이 촉박하면 느린 테스트 케이스를 제일 먼저 건너뛴다.
  • 그러므로 테스트 케이스가 빨리 돌아가게 최대한 노력한다.

 

결론
이장에서 소개한 휴리스틱과 냄새 목록이 완전하다 말하기는 어렵다.
여기서 소개한 목록은 가치 체계를 피력할 뿐이다.
사실상 가치 체계는 이 책의 주제이자 목표다.
일군의 규칙만 따른다고 깨끗한 코드가 얻어지지 않는다.
휴리스틱 목록을 익힌다고 소프트웨어 장인이 되지는 못한다.
전문가 정신과 장인 정신은 가치에서 나온다.
그 가치에 기반한 규율과 절제가 필요하다.
우리는 지금까지 학습한 내용을 체화하여 휴리스틱이 아닌 지켜야할 가치 쳬계를 코드 및 작업물에 녹여내도록 하자
728x90
반응형

' > CleanCode' 카테고리의 다른 글

CleanCode - Chapter 11. System  (0) 2022.03.14
Clean Code - #6 객체와 자료 구조  (0) 2022.01.31