클린 코드를 읽고 요약 정리

크리링·2023년 11월 21일
0
post-thumbnail

입사하고 첫날 선물 받은 개발자의 바이블 클린코드 8개월 만에 다 봤다.
클린 코드를 보고 유의하고, 기록해야 된다고 생각되는 부분을 정리해봤다. (후반부만)






TDD

실제 코드를 짜기 전에 단위 테스트부터 짠다.

3가지 법칙

  1. 실패하는 단위 테스트를 작성할 때까지 실제 코드를 작성하지 않는다.
  2. 컴파일은 실패하지 않으면서 실행이 실패하는 정도로만 단위 테스트를 작성한다.
  3. 현재 실패하는 테스트를 통과할 정도로만 실제 코드를 작성한다.

깨끗한 테스트 코드

가독성
테스트당 assert문 하나
테스트당 개념 하나

FIRST

  • Fast
    • 테스트는 빨라야 한다.
  • Independent
    • 각 테스트 코드는 서로 의존하지 않는다.
  • Repeatable
    • 어떤 환경에서도 실행 가능
  • Self-Validating
    • 결과 Boolean
  • Timely
    • 실제 코드 구현 직전 구현






클래스

체계

static public -> static private -> private
(공개 변수가 필요한 경우는 거의 없다)

클래스는 작아야한다(클래스는 책임으로 카운트)






창발적 설계

  • 모든 테스트를 실행
  • 중복을 없앤다
  • 프로그래머 의도를 표현한다
  • 클래스와 메서드 수를 최소로 줄인다






동시성

  • SRP (Single Responsibility Principle) : 동시성은 복잡성 이슈 하나만으로도 따로 분리할 이유가 충분
  • 임계영역을 올바르게 보호 (자료를 캡슐화 + 공유 자료를 최대한 줄여라)
  • 스레드 환경에서 안전한 컬렉션 사용
    • 자바에서는 java.util.concrrent, java.util.concurrent.atomic, java.util.concurrent.locks






냄새나는 코드

주석

부적절한 정보

  • 변경 이력 -> 당장 삭제
  • 작성자, 최종 수정일, SPR(Software Problem Reposrt) 번호 등만 설명

쓸모없는 주석

  • 오래된 주석, 엉뚱한 주석, 잘못된 주석 -> 당장 삭제
  • 없어질 주석은 달지도 않는다

중복된 주석

  • 바로 삭제

성의 없는 주석

  • 주석을 이왕 달거면 최대한 멋지게 -> 문법, 단어 신중히 선택

주석 처리된 코드

  • 아주 거슬림, 매일 낡아감, 흉물 그 자체 -> 고민말고 삭제



환경

여러 단계로 빌드

  • 빌드는 간단히 한 단계로 끝나야 한다.
  • 불가해한 명령이나 스크립트를 잇달아 실행해 각 요소를 따로 빌드할 필요 없어야 한다
  • 온갖 JAR 파일, XML 파일, 기타 시스템에 필요한 파일을 찾느라 뒤적일 필요 없어야 한다
  • 한 명령으로 체크아웃해서 빌드
    • ex)
      • svn get mySystem
        cd mySystem
        ant all

여러 단계로 테스트

  • 모든 단위 테스트는 한 명령
  • IDE 버튼 하나가 이상적



함수

너무 많은 인수

  • 함수에서 인수 개수는 작을수록 좋음
  • 없는게 BEST 그 다음 하나, 둘, 셋 순서 넷은 최대한 피한다

출력 인수

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

출력 인수가 직관을 정면으로 위배하는 이유

함수의 매개변수 -> 인수

인수의 두 가지 종류
* 입력 인수
* 출력 인수

ex1)
boolean fileExists("Myfile"){
    // 생략...
}

ex2)
public void appendFooter(StringBuffer report) {
   // 생략...
}


* 함수에서 인수는 두 가지 목적으로 처리
1. 조회 (X(인수) -> f -> Y(boolean; 참 혹은 거짓))
2. 변환 (X(인수) -> f -> Y(다른 객체))
	- 리턴 값이 바뀜

ex2) -> 인수 StringBuffer에 report 추가 후 
		StringBuffer라서 StringBuffer 반환이 필요하지 않음

해결) report.appendFooter();
		-> `this` 사용

출처 : [클린코드] 출력인수를 가급적 피하라 ( this의 존재 이유 )

플래그 인수 (ex> render(bool isTest))

  • boolean 인수는 함수가 여러 기능을 수행한다는 명백한 증거
  • 플래그 인수는 혼란을 야기해서 피해야 마땅

죽은 함수

  • 바로 삭제



일반

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

  • 혼란 + 조잡
  • 언어 하나만 사용 권장

당연한 동작 구현

  • 함수나 클래스는 다른 프로그래머가 다른 프로그래머가 당연하게 여길만한 동작과 기능을 제공
  • ex) Day day = DayDate.StringToDay(String dayName);
  • 당연하지 않아지면 의심이 시작되고 코드 읽는데 불편함 증대

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

  • 모든 경계와 구석진 곳도 증명해야 한다
  • 스스로의 직관에 의존하지 마라
  • 샅샅이 테스트

안전 절차 무시

  • 실패하는 테스트 케이스는 제껴두지 말자

중복

  • 중복은 곧 추상화, 다형성의 기회

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

  • 잘못된 추상화는 거짓말이나 꼼수로 해결이 불가

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

  • 파생 클래스기초 클래스의 기능을 받아써야 한다
  • 기초 클래스가 파생 클래스를 사용한다면 문제가 있는 것
  • 기초 클래스는 파생 클래스를 몰라야한다

과도한 정보

  • 잘 정의된 모듈은 인터페이스가 아주 작다
  • 자료를 숨겨라.

죽은 코드

  • 실행하지 않는 코드
  • 바로 제거

수직 분리

  • 변수와 함수는 사용되는 위치에 가깝게 정의
  • 지역 변수는 처음으로 사용하기 직전에 선언, 수직으로 가까운 곳에 위치

일관성 부족

  • 어떤 개념을 특정 방식으로 구현했다면 유사한 개념도 같은 방식으로 구현

잡동사니

  • 아무도 사용하지 않고, 호출하지 않고, 정보를 제공하지 못하는 주석 등 모두 삭제

인위적 결합

  • 서로 무관한 개념을 인위적으로 결합하지 않는다

기능 욕심

  • 클래스 메소드는 자기 클래스의 변수와 함수에 관심을 가진다.
  • 다른 클래스의 변수와 함수에 관심을 가져서는 안된다

선택자 인수

  • 호출시 function(…, false) 같은 선택자 인수는 내부를 쪼개야 함

모호한 의도

  • 의도는 최대한 분명히

잘못 지운 책임

  • 이름을 제대로 지어 각 모듈의 책임을 명확히

부적절한 static 함수

  • 재정의할 가능성이 있는 함수에 static을 붙이지 말자

서술적 변수

  • 서술적 변수는 중간에 많을수록 읽기 쉽다
    • ...
      Matcher match = headerPattern.matcher(line);
      
      String key = match.group(1);
      String value = match.group(2);
      headers.put(key.toLowerCase(), value);
      ...
      
      **key와 value가 서술적 변수**

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

  • Date newDate = date.add(5);
    (5, 5시간, 5일인지 불명확)
    
    ->
    
    Date newDate = date.IncreaseByDays(5);

알고리즘을 이해해라

  • 대부분 괴상한 코드는 알고리즘을 충분히 이해하지 않은 채 코드를 구현한 탓
  • 테스트 케이스를 확실히 하고 이리저리 굴려본다

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

  • 한 모듈이 다른 모듈에 의존한다면 물리적인 의존성도 있어야 한다 (논리적인 의존성으로는 부족)

  • public class HourlyReporter{
    	private HourlyReportFormatter formatter;
    	private List<LineItem> page;
    	private final int PAGE_SIZE = 55;
    
    	...
    
    }
    • PAGE_SIZE 상수 왜 필요? -> 잘못 지운 책임
    • HourlyReportFormatter가 페이즈 크기를 알고있다는 논리적 의존성
    • HourlyReportFormatter 내에 getMaxPageSize() 메소드 추가로 물리적 의존성 변환

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

  • 선택 유형 하나에는 switch 문을 한번만 사용

표준 표기법을 따르라

  • 팀이 정한 표준은 팀원들 모두 따라야 한다

매직 숫자는 명명된 상수로 교체

정확하라

  • 첫번째 결과만 유일한 결과로 간주하는 것은 범죄
  • 모호함과 부정확은 의견차나 게으름의 결과

관례보다 구조를 사용하라

  • enum 변수가 멋진 switch/case 문보다 더 좋다

조건을 캡슐화

  • ex) if(shouldBeDeleted(timer)

부정 조건은 피하라

  • ex) if(!buffer.shouldNotCompact())
    • -> if (buffer.shouldCompact())

숨겨진 시간적인 결합

  • 실행되는 순서가 중요한 코드는 연결 소자를 생성해 시간적인 결합을 노출한다

  • ...
    public void dive(String reason) {
    	saturateGradient();
    	reticulateSplines();
    	diveForMoog(reason);
    }
    
    ->
    
    public void dive(String reason) {
    	Gradient gradient = saturateGradient();
    	List<Spline> splines = reticulateSplines(gradient);
    	diveForMoog(splines, reason);
    }

일관성을 유지하라

  • 코드 구조를 잡을때 이유를 고민하고 명백히 표현

경계 조건을 캡슐화

  
  if (level + 1 < tags.length) {
  	parts = new Parse(body, tags, level + 1, offset + endTag);
  	body = null;
  }
  
  * level + 1이 두번 반복
  
  ->
  
  int nextLevel = level + 1;
  if (nextLevel < tags.length) {
  	parts = new Parse(body, tags, nextLevel, offset + endTag);
    	body = null;
  }

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

  • 함수 내 모든 문장은 추상화 수준이 동일해야 한다.

  • 추상화 수준은 함수 이름이 의미하는 작업보다 한 단계만 낮아야 한다

  • 가장 어렵고 리팩토링 수행의 가장 큰 이유

  • public String render() throws throws Exception {
    	StringBuffer html = new StringBuffer("<hr");
    	if(size > 0)
    		html.append(" size=\"").append(size + 1).append("\"");
    	html.append(">");
    
    	return html.toString();
    }
    
    * 수평선 크기가 있다 (나도 모름)
    * HR 태그 문법 
    * 추상화 수준 최소 두개?
    
    ->
    
    public String render() throws Excpetion {
    	HtmlTag hr = new HtmlTag("hr");
    	if(size > 0)
    		hr.addAttribute("size", "" + (size+1));
    	return hr.html();
    }
    

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

  • 추상화 최상위 단계에 둬야 할 기본값 상수나 설정 상수는 저차원 함수에 숨겨서는 안된다

추이적 탐색을 피하라

  • 한 모듈은 주변 모듈을 모를수록 좋다



자바

긴 import 목록 피하고 와일드 카드 사용

  • ex) import package.*;

상수는 상속하지 않는다

상수 대 Enum

  • Enum 마음껏 활용



이름

서술적인 이름을 사용하라

적절한 추상화 수준에서 이름 선택

가능하면 표준 명명법 사용

명확한 이름

긴 범위는 긴 이름 사용

인코딩 피하라

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



테스트

불충분한 테스트

커버리지 도구를 사용

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

무시한 테스트는 모호함을 의미

경계 조건 테스트

버그 주변은 철저히 테스트

실패 패턴을 살펴라

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

테스트는 빨라야 한다






회고

솔직히 8개월 동안 밀린 인프런 강의도 많았고, 귀찮다, 바쁘다 등의 핑계로 클린 코드를 미루고 있었다. 근데 얼마 전에 업무 중에 코드 보수하는 과정에서 내 코드에서 허접함이 느껴지고, 전문성이 떨어져 보인다는 생각을 하게되었다.진작 클린 코드를 읽었다면... 이라는 생각이 머릿 속에 스쳐갔다. 물론 읽는게 다는 아니지만 어느정도 좋은 코드, 유지보수 용이한 코드를 만들기 위한 노력을 해야된다고 느끼니 클린 코드를 안 읽을 수 없어 열심히 읽었다.

그리고 이전까지만해도 TDD, 테스트 중심의 코드에 대해 사람들이 너무 유난이다, 그 정돈가?라는 생각이 좀 있었다. 현재 우리 회사는 제품 출시 초기라 테스트보다는 속도와 빠른 적용이 중요한 시기라고 생각하고 테스트에 대한 생각은 미뤘었다. 그런데 1주전 한 코드를 유지보수하다가 다른 부분의 오류가 발생할 수도 있겠다는 생각에 무서웠고, 더 무서운건 내가 짐작할 수 없다는 사실이었다. 그리고 클린 코드를 읽으면서 TDD테스트 코드가 중요하다는 것을 강조하고, 생각 이상으로 중요하다는 걸 느낀다.

그동안에 개발 방식이 잘못됐을 수도 있지만 개선할 점이 보인다는 것은 긍정적이라 믿고 앞으로 깨끗한 코드와 테스트 주도 개발을 할 수 있도록 노력 + 공부해야겠다.

0개의 댓글