hyelie
hyelie
Hyeil Jeong
       
글쓰기    관리    수식입력
  • 전체보기 (495)
    • PS (283)
      • Algorithm (28)
      • PS Log (244)
      • Contest (6)
      • Tips (5)
    • Development (52)
      • Java (14)
      • Spring (23)
      • SQL (2)
      • Node.js (2)
      • Socket.io (3)
      • Study (4)
      • Utils (4)
    • DevOps (36)
      • Git (5)
      • Docker (4)
      • Kubernetes (2)
      • GCP (3)
      • Environment Set Up (8)
      • Tutorial (12)
      • Figma (2)
    • CS (74)
      • OOP (7)
      • OS (24)
      • DB (2)
      • Network (24)
      • Architecture (0)
      • Security (2)
      • Software Design (0)
      • Parallel Computing (15)
    • Project (15)
      • Project N2T (5)
      • Project ASG (0)
      • Project Meerkat (1)
      • Model Checking (7)
      • Ideas (2)
    • 내가 하고싶은 것! (34)
      • Plan (16)
      • Software Maestro (10)
      • 취준 (8)
hELLO · Designed By 정상우.
hyelie

hyelie

Development/Study

[개발서적] 다시 쓰는 Clean Code 정리

 개발에 대해 아무것도 모를 때 읽었던 Clean Code와 어느 정도 경험을 쌓고 나서 다시 보는 Clean Code는 느낌이 달랐다.

 직접 프로그램을 유지보수하지 않고 개발만 했던 시절에는 모든 내용이 내 머리속에 있었기 때문에 "대체 왜 interface를 사용하는 거지? 굳이? 그냥 수정하면 되는 거 아닌가?"라고 생각했다. 지나고 보니 우매한 생각이었다. 여러 프로그램을 유지보수하다보니 요구사항을 반영할 때 기존 코드를 최소한으로 만져야 하고, dependency가 꼬여있지 않아 추가하고 싶은 기능만 추가하는 것의 중요성을 깨달았다. 기존 코드가 난잡하면 유지보수 하는데는 훨씬 많은 cost가 든다는 걸 개발병을 하면서 너무 많이 겪었다.

 

 나는 소스 코드는 하나의 글이라 생각한다. 잘 쓴 글은 서두만 봐도 무슨 내용인지 예상되며, 문맥상 어색한 내용을 작성하지 않는다. 또한 한 문단에서 한 주장만을 다루며, (강조를 제외하고는) 같은 말을 반복하지 않는다. 이를 Clean Code식으로 바꾼다면 다음과 같다.

  • 서두만 봐도 무슨 내용인지 예상된다 → abstraction과 naming이 잘 되어 있다.
  • 문맥상 어색한 내용을 작성하지 않는다 → 이것저것 꼬여있지 않고 명확한 hierarchy가 있다.
  • 한 문단에서 한 주장만을 다룬다 → 한 class는 한 역할만을 하고(SRP), 한 function은 한 기능만을 한다.
  • 같은 말을 반복하지 않는다 → 중복이 없다.

 

 이외에도 당연한 이야기를 많이 설명하는데, 이 중에서 내게 필요하다고 생각되는 항목들만 매우 간략히 정리했다.

 

 

2장. 의미 있는 이름

 소스 코드는 나만 읽는 글이 아니다. 나만 알아볼 수 있는 이름이 아니라 누가 봐도 알아볼 수 있는 이름을 작성하자는 게 이 장의 핵심이다.

  • 모든 함수, 변수, 상수는 이름만 보고 무슨 역할을 하는지 알 수 있어야 한다.
  • Class 이름은 명사, 함수 이름은 동사로 사용해야 한다.
  • 같은 개념은 같은 단어로 나타내야 한다.
    • 예를 들어 [어떤 정보를 가져오는 method 이름]을 get, retrieve, fetch 등으로 분산시키지 말고 하나로 통일해야 한다. 가능하면 많은 사람들이 이해하고 있는 이름을 선정해야 한다.

 

 

3장. 함수

 작게 만들어야 한다. 긴 함수는 low level abstraction과 high level abstraction이 혼재되어 있어 이해하기 어렵다. 또한 Single Responsibility Principle와 동일한 이유로 로직의 자그마한 부분이 바뀌어도 해당 method를 고쳐야 하기에 side-effect가 발생할 확률이 높아진다. 이 장에서는 함수를 작게 만드는 방법을 기술한다.

  • 함수는 하나의 역할만 해야 한다. 따라서 함수를 작게 만들어야 한다.
    • Clean Code에서는 추상화 수준이 하나일 때 한 가지 작업만 한다고 판정한다. 또는 더 이상 의미 있는 이름으로 함수를 추출할 수 없을 때 한 가지 작업만 한다고 판정한다.
    • 함수 이름에 맞는 딱 한 가지의 일만 하고 나머지 작업을 몰래 하는 경우는 없어야 한다.
  • abstraction level이 높은 것은 위로, 낮은 것은 아래로 내려야 한다. 
  • parameter는 최대한 줄일수록 좋다. parameter가 많아질수록 test code를 짜기 힘들어지기 때문이다.
  • parameter에 출력 변수를 사용하지 않아야 한다. 직관적이지 않기 때문이다.
  • 오류 코드 리턴보다 try-catch가 낫다.
    • 오류 코드 리턴은 결과로 if-else처리를 해야 한다. try-catch는 별도 함수로 뽑아내 묶을 수 있다.

 

 

4장. 주석

 주석은 관리 대상이 아니기 때문에 방치될 확률이 높고, 따라서 주석은 최대한 줄여야 한다는 것이 이 장의 핵심이다.

  • 주석보다는 Code로 설명해야 한다.
    • 주석으로 설명하는 대신 적절한 naming으로 설명해야 한다.
  • 단, 다음과 같은 몇 가지는 예외이다.
    • 저작권, 소유권 등 법적 주석
    • 정규표현식 등 직관적으로 이해하기 힘든 코드에 대한 주석
    • 의도를 설명하는 주석: 알고리즘 동작 방식 등
    • 경고하는 주석
    • TODO 주석
  • 위 경우를 제외하고는 주석을 최대한 지양한다.

 

 

5장. 형식

 글 내용은 동일하더라도 들여쓰기를 하는지, 문단 분리를 적절히 하는지에 따라 가독성이 크게 달라진다. 소스코드도 동일하다. 이 장에서는 코드가 지키면 좋은 형식을 다룬다.

  • 팀이 정한 규칙이 언제나 1순위이다.
  • 각 개념은 개행으로 구분한다.
    • 개행은 개념의 분리를 나타낸다. package - import - method 등은 개행으로 분리해야 한다.
  • caller function은 callee function보다 먼저 배치되어야 한다. 추상화 수준이 더 높기 때문이다.

 

 

7장. 오류 처리

 앞선 3장에서 "오류 코드 리턴보다 try-catch가 낫다"는 내용이 있었다. 이 장에서는 try-catch문의 처리 방법을 다룬다.

  • 오류 코드 리턴보다 exception을 사용해야 한다.
  • try-catch문을 최대한 뽑아내어 하나로 몰아넣어야 한다.
    • 이를 위해 try-catch문은 최대한 작은 범위에서 사용해야 하며, 정상 동작과 오류 동작을 구분해야 한다.
  • Unchecked exception을 사용해야 한다. exception을 기술한다면 상위 module이 하위 module에 의존하기 때문이다.
  • null을 사용하지 않아야 한다. null 리턴 시 caller function에게 예외처리를 떠넘기며 parameter로 넘기는 경우 exception이 발생하기 때문이다.

 

 

8장. 경계

 이 장에서는 내부 component와 외부 component 사이의 경계를 다루는 방법을 기술한다. 경계를 잘 처리하면 할수록 변경으로 인한 변경을 줄일 수 있다.

  • 경계를 interface로 묶어 관리해야 한다. 코드 변경의 cost가 낮아지기 때문이다.
    • 우수한 설계를 통해 코드 변경의 cost를 줄일 수 있다.
    • class로 경계를 감싸거나, 또는 adapter 패턴으로 원하는 형식의 interface로 변경한다.
  • 경계를 interface로 묶어 미구현된 기능을 구현된 것처럼 포장할 수 있다. 추후 구현된다면 interface의 구현체만 바꿔 끼면 되기 때문에 dependency를 줄일 수 있다.

 

 

9장. 단위 테스트

 이 장은 프로그램의 무결성을 보장해 주는 Unit Test에 대해 다룬다. Unit Test가 존재하기 때문에 소스코드가 변경되어도 프로그램이 잘 동작함을 생각할 수 있다. 즉 Unit Test는 유연성, 유지보수성을 제공한다.

  • TDD는 다음 과정을 거친다.
    • 실패하는 Unit Test를 작성할 때까지 실제 코드를 작성하지 않는다
    • 컴파일은 성공하되 실행이 실패할 정도로만 Unit Test를 적상한다.
    • 실패하는 Unit Test를 통과할 정도로만 실제 코드를 작성한다.
  • 테스트 코드도 Clean Code의 규칙을 따라 작성해야 한다. 작성한 테스트 코드도 소스코드의 변경에 따라 변경되어야 하기 때문이다.
  • 테스트 코드도 함수이다. 함수는 하나의 역할만 해야 한다. 따라서 테스트 코드는 한 개념만 테스트해야 한다.
    • given-when-then template, 그리고 가능한 한 적은 assert를 사용해야 한다.
  • FIRST 규칙을 따라야 한다.
    • Fast - 빨라야 한다.
    • Independent - 테스트 코드는 의존성이 없어야 한다. 의존한다면 테스트 실패 원인을 찾기 어려워지기 때문이다.
    • Repeatable - 반복 가능해야 한다.
    • Self-validating - boolean 값으로 결과를 내어 쉽게 결과를 확인할 수 있어야 한다.
    • Timely - 실 코드를 작성하기 전에 테스트 코드를 작성해야 한다.

 

 

12장. 창발성

 아래 2개의 단계를 반복함으로써 소스코드의 품질을 높일 수 있다.

  • 1. 모든 테스트를 실행한다. - 테스트를 실행하기 위해 작은 class와 method를 만들게 된다.
  • 2. 중복을 없앤다. 프로그래머의 의도를 표현한다. class와 method를 최소로 줄인다.
    • 테스트 코드를 믿고 이 단계를 반복한다.

 

 

13장. 동시성

 이 장에서는 동시성을 처리하는 방법을 기술한다. multi-thread 환경은 예상하지 못했던 결과가 나오기 때문에 주의를 기울여야 한다. 추가적으로 부록 A에서 다루는 동시성 II도 정리했다.

  • 동시성이 있는 코드는 다른 코드와 분리해야 한다. 동시성 처리만으로 이미 하나의 역할을 하기 때문이다.
  • 가능한 thread를 독립적으로 구현해야 한다.
  • critical section과 shared resource를 최소한으로 줄여야 한다. lock과 race condition 때문에 부하가 가중되기 때문이다.
  • java.util.conturrent 패키지 등 thread-safe한 library를 사용해야 한다.
  • thread code를 테스트할 때는 다음과 같은 지침을 지켜야 한다.
    • 됐다 안됐다 하는 코드는 틀린 코드다.
    • single-thread부터 구성한다.
    • 한 번에 많은 thread를 돌려봐야 한다.
    • wait(), sleep(), yield() 등의 instrument를 이용해 thread 실행 순서를 바꿔 본다.
  • 프로그램 성능 테스트 시 프로세스 연산에 많은 시간을 보낸다면 CPU가 부족한 것이므로 성능을 높여야 한다. I/O 연산에 많은 시간을 보낸다면 동시성 처리가 도움이 된다.
  • AtomicBoolean, AtomicInteger, AtomicReference 등 thread-safe한 class를 사용하면 좋다.
  • Deadlock이 일어나지 않게 면밀히 검토해야 한다.

 

 

17장. Heuristics

 Clean Code에서는 많은 code smells와 heuristics를 다룬다. 이 내용들은 위에서 이미 다룬 내용들이기 때문에 중복되지 않고 중요한 몇 가지만 정리하려 한다.

 

일반

  • 중복을 발견한다면 method로 분리하거나 class로 분리해야 한다. switch로 같은 조건을 계속 확인한다면 polymorphism으로 대체해야 한다.
    • Open Closed Principle에서 예시를 다룬다.
  • 필요 없는 주석, 필요 없는 생성자 필요 없는 모든 것을 깔끔하게 정리해야 한다.
  • parameter로 boolean, enum, int code를 넘겨 함수 동작을 제어하는 대신 함수를 쪼개어 해결하는 방법을 강구해야 한다.
  • 변하거나 재정의할 가능성이 있는 method는 static으로 작성해서는 안 된다.
  • 상수도 naming한 후 저장해야 한다.
  • bool 논리를 if문에 사용할 때는 a || b와 같이 풀어 적지 말고 함수로 묶어 작성해야 한다. 가독성이 올라가기 때문이다.

 

테스트

  • 테스트 코드는 모든 경계 조건, 모든 예외를 찾아내고 테스트할 수 있어야 한다.
  • 커버리지 도구를 사용해야 한다.

 

 

 

정리

 지금까지 Clean Code를 간단하게 정리했다. 처음에 소스 코드는 하나의 글이라 생각한다고 말했다. 결국 Clean Code란 그 이름대로 깔끔한 코드, 읽기 쉬운 글을 작성하는 것과 같다. 이를 위해 명확한 naming, 중복 없애기 등의 방법을 채택하는 것이다.

 

 뭔가를 아는 상태에서, 그리고 필요한 상황에서 읽으니 매우 와 닿는다. 정리한 것들을 참고하며 깔끔한 코드를 작성하기 위해 계속해 고민하고, 개선하고, 리팩토링하자.

 

 

 

 

 

저작자표시 (새창열림)

'Development > Study' 카테고리의 다른 글

[개발서적] Clean Architecture 정리  (0) 2023.03.25
[개발서적] The Pragmatic Programmer 정리  (0) 2023.03.22
[개발서적] Clean Code 정리  (0) 2022.06.24
    hyelie
    hyelie

    티스토리툴바