note title

독후감

책표지

note title

CleanCode를 읽으면서 가장 인상 깊었던 점은,

코드는 항상 존재한다.

라는 첫 장의 메세지였습니다. 아무리 새로운 프로그래밍 언어나 도구를 만들어도, 결국 코드는 요구 사항을 표현하는 수단이라는 것 이였습니다. 요구 사항을 정확하게 판단하고, 그에 맞는 표현이 필요한 것이죠.

CleanCode는 코드품질의 중요성에 대해서 설명합니다. 급하게 작성된 코드는 수정하기 어려워지고, 팀의 생산성은 0으로 수렴되어 나쁜 결과를 초래합니다. 반면, 깨끗한 높은 품질은 우아하고 효율적일 뿐 아니라 가독성이 높아 생산성은 우상향합니다.

의미 있는 이름에 대해서도 큰 가치를 갖습니다.
실제 개발 현장에서 가장 적용할 수 있었습니다. 특히 검색하기 쉬운 이름을 사용해야 함과 의미있는 상수 이름을 사용하는 것이 코드의 가독성을 확보하고 유지보수하기에 쉬웠습니다. 국립암센터에서 근무할 당시 기존에 생각없이 작성했던 클래스, 메소드, 변수 등을 클린하게 바꾸니 더욱 가독성이 좋고 리뷰하기도 좋았습니다.

의미 있는 이름과 마찬가지로, 함수와 주석의 균형 또한 중요하였습니다. 함수는 한 가지의 기능 만을 포함해야 하며, 인수도 최소화하는 것이 가독성에 도움이 된다는 것입니다. 또한 함수 자체가 의미있고 가독성이 있어야 한다는 것입니다. 주석은 결국 설명이지 함수를 보완하는 효과는 없기 때문입니다. 쉽게 농담을 설명하면 그 농담은 죽은 농담이다. 라는 말처럼 말이죠.

CleanCode와 마찬가지로, 오류처리 또한 중요했습니다. 예외 처리의 중요성을 강조하면서, null을 반환하거나 전달하지 않는 것을 원칙합니다. 이 부분은 지금도 실수를 하는 부분이였습니다. 왜냐면 사소한 null 반환 기능은 앞으로 발생하는 예외처리에서 문제를 찾기 어렵기 때문이였습니다.

Python을 기반으로 코드를 작성할때, 단위 테스트와 클래스 설계를 크게 생각하지 않았습니다. 하지만 TDD 법칙과 FIRST 법칙으로 테스트 코드를 작성하는 것이 중요함을 배웠습니다. 이 부분을 활용하여 기능이 제 기능을 하는지 테스트하는 습관을 들여야겠다고 생각했습니다.

이 책을 통해 깨끗한 코드를 작성하는 것은 단순히 좋은 습관이 아니라 프로젝트의 성패를 결정하는 중요한 요소라고 깨달았습니다. 코드의 가독성과 유지보수성을 높이는 것은 단기적으로 시간이 걸리겠지만, 장기적으로는 더 빠르게 성공을 이끄는 중요한 과정이라는 것을 명심해야겠습니다. 이러한 원칙들을 일상적인 코딩 습관으로 만들어 더 나은 소프트웨어 개발자가 되도록 노력해야겠습니다.


Contents

1장. 깨끗한 코드

코드는 항상 존재한다.

코드는 요구 사항을 표현하는 언어. 즉, 정밀하게 표현할 수록 정확하게 표현된다.

나쁜코드

그렇다면 좋은 정밀한 코드보단 나쁜코드는 무엇일까?

급해서, 구조없이, 무작정 만든 코드는 나쁜코드 이며, 고칠 수 있는 시간은 돌아오지 않는다. 나쁜코드가 많아질 수록 팀 생산성은 떨어져 0에 수렴하고 기존 나쁜 코드를 다시 재설계 후 추가한다면 시간은 매우 매우 길어지게 될 것이다.

그렇다면 좋은 코드란

좋은 코드는 깨끗하고 우아하며 효율적이며 보기에도 즐거운 코드다. 코드가 잘 짜여있을 수록 효율적으로 메모리 관리를 하며 깨끗하게 작성되어있어야 이해하기가 편하기 때문일 것이다. 특히 코드는 글의 조합이기 때문에, 가독성도 매우 좋아야 한다. 즉, 코드를 작성 할 때 매우 명쾌하고 구체적으로 작성해야 하는 것이다.


2장. 의미있는 이름

의도를 분명히 밝혀라.

  1. 요약이 문제가 아니라, 정보가 함축되면 안된다.
import datetime
# ymdstr X
current_date : str = datetime.today().strftime(~)
  1. 다른 이름을 쓰면 안된다. 가령 AcountList, NameList이지만 실제 데이터 종류가 List가 아닐 경우 혼돈을 야기한다. 따라 AccountGroup, NameGroup 등으로 바꿔서 해야 한다.
  2. 확실하게 이름을 분리 해야 한다. 함수나 변수 이름에 같은 뜻의 코드를 여러 이름으로 나눌 경우 혼돈을 야기한다.
def get_user_info()
# def get_client_data() X
def get_user_data()
# def get_customer_record() X
def get_user_record()
 
## 혹은
from typing import Union, Dict
 
class Record:
    pass
 
class User:
    info: str
 
    @property
    def data(self) -> Dict[str, str]:
        return {}
 
    def get_record(self) -> Union[Record, None]:
        return Record()
  1. 검색하기 쉬운 이름 사용. 코드를 검색하기 쉬운 이름으로 나눠서 사용할 수 있도록 한다. 이름 길이는 범위 크기에 비례하여 사용하게 한다.
import time
#time.sleep(86400) X
SECONDS_IN_A_DAY = 60 * 60 * 24
time.sleep(SECONDS_IN_A_DAY)
  1. 인코딩 회피 쉽게 말하면, 암시하는 단어를 쓰지 말고 구체적으로 보이게 끔.
# seq = ("Austin", "New York", "San Francisco") X 
# for item in seq: X
locations = ("Austin", "New York", "San Francisco")
for location in locations:
    print(location)
  1. 접두어를 넣지도 말자. 클래스와 함수는 접두어가 필요없을 정도로 작아야 한다.즉, 명료하게. 클래스는 명사나 명사구로. 메서드는 동사나 동사구로!
class Car:
#	car_make : str  X
	make: str
  1. 기발한 이름도 피하자.
  2. 한 개념에 한 단어만 사용하자.
  3. 이름으로 장난치지 말자.
  4. 해법 영역(Document)에서 가져온 이름을 사용하자.
  5. 적절한 프로그래머 용어가 없을 겨우 문제 영역에서 가져온 이름을 사용한다.
  6. 맥락을 유추하기 힘든 기능이라면, 의미 있는 맥락을 추가하자. 접두어를 조금은 섞는거다.
  7. 그렇다고 불필요한 맥락을 추가하진 말자. 이름은 간결하게, 기능도 간결하게!
import hashlib
 
# def create_micro_brewery(name):
	# name = "Hip" if name is None else name
 
def create_micro_brewery(name : str = "Hip"):
	...
	

3. 함수

작게 만들어라

  1. 함수는 무조건 작게 만드는 것이 좋다.
  2. 블록과 들여쓰기.
  3. if, else, while문 등에 들어가는 블록은 한 줄이어야 한다. 즉, 중첩 구조가 생길 만큼 함수가 커져서는 X

한 가지만 한다.

  1. 함수는 한 가지의 기능을 갖고, 한 가지를 잘해야 한다.
  2. 함수 내 섹션. 세션으로 잘 나눠야한다.
from typing import List
# BAD
class Client:
    active: bool
    
def email(client: Client) -> None:
    pass
    
def email_clients(clients: List[Client]) -> None:
    """Filter active clients and send them an email.
    """
    for client in clients:
        if client.active:
            email(client)
from typing import List
 
class Client:
    active: bool
 
def email(client: Client) -> None:
    pass
 
def get_active_clients(clients: List[Client]) -> List[Client]:
    """Filter active clients.
    """
    return [client for client in clients if client.active]
 
def email_clients(clients: List[Client]) -> None:
    """Send an email to a given list of clients.
    """
    for client in get_active_clients(clients):
        email(client)
class Client:
    active: bool
 
def email(client: Client):
    pass
 
def active_clients(clients: Iterator[Client]) -> Generator[Client, None, None]:
    """Only active clients"""
    return (client for client in clients if client.active)
 
def email_client(clients: Iterator[Client]) -> None:
    """Send an email to a given list of clients.
    """
    for client in active_clients(clients):
        email(client)

위에서 아래로 코드 읽기 : 내려가기 규칙

Top Down 방식으로 코드를 읽어야 한다.

서술적인 이름 사용

  1. 코드를 읽으면서 짐작했던 기능을 각 루틴이 그대로 수행해야 한다.
  2. 이름이 길어도 괜찮다. 짧은것보단 낫다.

인수

1.가장 이상적인건 0개, 그 다음은 1개 … 4개 이상은 특별한 이유없이는 금지
2.TypeDict 등 다양한 라이브러리를 사용하는 것도 방법

부수효과는 없어야 한다.

명령과 조회를 분리해야 한다.

오류 코드 보다 예외를 사용하라.

반복하지마라

구조적으로 작성하라


4. 주석

주석은 나쁜 코드를 보완하지 못한다.

표현력이 풍부하고 깔끔하며 주석이 거의 없는 코드가 복잡하고 어수선하며 주석이 많이 달린 코드보다 훨씬 좋다.

코드로 의도를 표현하라

좋은주석

법적인 주석 : 회사가 정립한 구현 표준에 맞춰 주석을 명시 (license)
정보 제공 주석 : 함수 이름에 정보를 담는다.
의도를 설명하는 주석 : 의도까지 설명 의미를 명료하게 밝히는 주석 결과를 경고하는 주석

TODO 주석

앞으로 할 일을 TODO 주석으로 남겨둔다 중요성을 가하는 주석

나쁜 주석

주절거리는 주석 같은 이야기를 중복하는 주석 오해할 여지가 있는 주석 의무적으로 다는 주석 이력을 기록하는 주석 있으나 마나 한 주석 무서운 잡음 함수나 변수로 표현할 수 있다면 주석을 달지 마라 위치를 표시하는 주석 닫는 괄호에 다는 주석 공로를 돌리거나 저자를 표시하는 주석 주석으로 처리한 코드 HTML 주석 전역 정보 너무 많은 정보 모호한 관계 함수 헤더 비공개 코드에서 Javadocs


5. 형식 맞추기

형식을 맞추는 목적

돌아가는 코드가 전문 개발자의 일차적인 의무라 여길지도 모르겠지만, 개발자의 스타일과 규율은 여전하다.

적절한 행 길이를 유지하라

500줄을 넘지 않고 대부분 200줄 정도인 파일로도 커다란 시스템을 구축할 수 있다. 반드실 지킬 엄격한 규칙은 아니지만 바람직한 규칙으로 삼아야 한다.

3. 신문 기사처럼 작성하라

4. 개념은 빈 행으로 분리하라

생각 사이는 빈 행을 넣어 분리해야 마땅하다. 즉, 빈 행은 새로운 개념을 시작한다.

5. 세로 밀집도

줄바꿈이 개념을 분리한다면 세로 밀집도는 연관성을 의미한다.

6. 수직 거리

서로 밀접한 개념은 세로로 가까이 둬야한다. 변수선언 : 변수는 사용하는 위치에 최대한 가까이 선언한다. 루프를 제어하는 변수 : 루프 문 내부에 선언한다. 인스턴스 변수 : 인스턴스 변수는 클래스 맨 처음에 선언한다. 종속함수 : 한 함수가 다른 함수를 호출시, 두 함수는 세로로 가깝게 배치한다. 또한 가능하다면 호출하는 함수를 호출되는 함수보다 먼저 배치한다. 개념적 유사성 : 친화도가 높을 수록 코드를 가까이 배치한다.

7. 세로순서

개념을 표현할 때는 세세한 사항을 최대한 배제한다.

8. 가로 형식 맞추기

80자 제한은 다소 인위적이다. 100~ 120자도 괜ㅊ낳다 가로 공백과 밀집도 가로 정렬 들여쓰기 들여쓰기 무시하기 : 짧은 if, 짧은 while, 짧은 함수 가짜 범위 : 빈 while 문, for 문을 접한다.

9. 팀 규칙

팀이 정한 규칙을 따라간다.

10. 형식 규칙 참고하기


6. 자료구조

자료 추상화

공개하면 안되는 것들은 이름이나 함수를 추상적으로 표현한다

자료/객체 비대칭

객체 지향 코드 방식과 절차적인 코드 방식을 적합하게 사용해야 한다.

디미터의 법칙

객체는 자료를 숨기고 함수를 공개한다. 즉, 객체의 메서드만 호출하게 해야 한다. 기차 충돌 : (파도 타면 나오는 경우)

잡종구조

공개 변수, 조회, 비공개 변수가 노출되고 함수가 노출되며 절차적 프로그래밍 방식 이건 피해야한다.

구조체 감추기

내부 구조를 드러내지 않으며 모듈에서 함수는 자신이 몰라야 하는 여러 객체를 탐색할 필요를
없게 한다. (경로 > 임시 경로)

자료 전달 객체

자료 전달 객체 (DTO : Data Transfer Object)은 공개 변수만 존재하고 함수가 없는 클래스를 의미한다.

활성 레코드

공개 변수가 있거나 비공개 변수에 조회/.설정 함수가 있지만 대게 save, find를 제공하는 경우. 하지만 이런 내용은 바람직하지 않다. 활성 레코드는 자료구조로 취급하여 규칙을 담으면서 숨기는 객체는 따로 생성하게 한다.


7. 오류처리

프로그램을 개발할때 항상 오류 발생 가지수를 감안해서 작성해야한다. 그리고 오류가 발생한다면 어디서 발생하는지, 어떤 오류인지를 꼭 명시하게끔 처리를 해야한다.

하지만 오류처리를 할 때도 어떻게 처리를 할 것인지, 코드가 무엇을 의미하는지 명시해야 클린 코드가 될 것이다.

오류코드보다 예외를 사용하자

python에는 조건문 (if, else) 그리고 예외처리 (try, except)코드가 있어서 편리하다. 하지만 이것도 남발하면 무슨 말인지도 모르겠고 어려운 코드가 된다.

Try-Except-Finally 문부터 작성하자

즉, try-except 예외처리문을 작성해보는것이 좋다. 내가 원하는 과정을 Try에서 진행 후, 오류가 발생하거나 예외가 처리된다면 Except 문에서 오류/예외를 나타내기 때문이다.

작성시에는 강제로 예외를 일으키는 테스트 케이스를 작성 후 테스트를 통과하게 코드를 작성하면 된다

미확인 예외를 사용하자

예외처리시 비용을 따져보자. OCP(Open Closed Principle), 확인된 예외는 OCP를 위반한다. 위에서 확인된 예제를 연속적으로 처리해야하니 캡슐화도 안되고 클린코드는 커녕 오류많은 코드로 바뀐다.

예외에 의미를 제공한다.

위에서 말한것과 동일하다. 즉, 어디서 무엇이 에러인지 확인해야 한다. 그 에러에 의미도 담고!

호출자를 고려해 예외 클래스를 정의한다.

API 호출자를 고려해 ACMCPort를 감싸는 클래스는 매우 유용하다고 한다. 외부 API를 감싸면 외부 라이브러리와 프로그램 사이에서 의존성이 줄어 테스트하기 편하다고 한다. 안해봐서 잘 모르겠다

정상흐름을 정의해라

흐름자체를 복잡하게 하는 처리를 하지 말고, 클래스를 만들거나 객체를 조작해 특수 사례를 처리하게 한다. 그렇다면 예외적인 상황을 처리할 필요가 없어진다. (캡슐화되니까)

null을 반환하지 마라. null은 Null로

null은 일거리를 늘리고 더럽히며 없어야 할 존재다. 최대한 Null은 null로 보내버리자.

null 전달하지마라

인수에 Null이 있다면 코드가 잘못된거다.


8. 경계

경계 살피고 익히기

외부코드를 가져올때, 코드를 테스트하는 편이 바람직하다 즉, 외부 코드를 호출하는 대신 간단한 테스트 케이스를 작성해서 외부 코드를 익혀보는거다.

Log4j

Apach의 Log4j 패키지다. 뭔지 몰라 결론은, 테스트를 진행해보면서 구글, 문서를 확인하는거다.

학습 테스트는 무료다.

학습 테스트를 이용한 학습이 필요하든 그렇지 않든, 실제 코드와 동일한 방식으로 인터페이스를 사용하는 테스트 케이스가 필요하다.

아직 존재하지 않는 코드

아는 코드와 모르는 코드를 분리해서 사용하자.

깨끗한 경계

경계에 위치하는 코드는 깔끔히 분리한다. 또한 기대치를 정의하는 테스트 케이스도 작성한다. 외부 패키지를 호출하는 코드를 가능한 줄여서 경계를 관리하는게 좋다.


9. 단위테스트

TDD

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

깨끗한 테스트 코드 유지하기

테스트코드가 더러우면 계속해서 늘어나는 부담이 되어버린다. 테스트는 유연성, 유지보수성, 재사용성을 제공한다. 가독성이 중요하다

테스트당 assert가 하나씩 들어간다.

결론이 하나이기 때문에 코드를 이해하기 쉽다.

테스트당 개념 하나

기능적 테스트니, 마찬가지의 의미이다

F.I.R.S.T

F : Fast 테스트는 빠르게 I : Independent 테스트는 서로 의존하면 안된다. R : Repeatable 테스트는 반복 가능해야한다 S : Self-validating 테스트는 bool값을 결과로 낸다 T : Timly 테스트는 적시에 작성해야 한다.


10. 클래스

클래스 체계

가장 먼저 변수 목록이 나온다. 다음은 함수가 나온다. 캡슐화를 잘 이용해야한다.

클래스는 작아야 한다.

클래스 이름은 해당 클래스 책임을 기술한다. (단일 책임 원칙) 큰 클래스 몇 개가 아니라 작은 클래스 여럿으로 이뤄진 시스템이 바람직하다.

클래스는 인스턴스 변수 수가 작아야 한다. (응집도) 응집도가 높다는 말은 클래스가 속한 메서드와 변수가 서로 의존하며 논리적인 단위로 묶인다는 것. 응집도를 유지하면 작은 클래스 들이 나온다.

변경하기 쉬운 클래스

작은 클래스를 깨끗하게 관리하면, 클래스의 가독성이 좋아지고 수정하기가 편하다.




참고자료