Today I../Today I Read

[Clean Code] Chapter 04. 주석, Chapter 05. 포맷팅

HJChung 2022. 2. 5. 14:08
Clean Code 클린 코드 - 로버트 C. 마틴 저
를 읽고, clean code 해설 강의를 통해 제가 이해한 바를 정리한 글입니다. 

Chapter 04. 주석

잘 달린 주석은 그 어떤 정보보다 유용하다.

반면 경솔하고 근거없는 주석, 오래되고 조잡한 주석은 코드를 이해하기 어렵고, 거짓과 잘못된 정보를 전달한다. 

그래서 '(그럴 거면) 주석을 최대한 쓰지 말자' 라는 말이 왜 나왔는지 알 것 같다. 

 

작년 말, 새로운 서비스를 구현하면서 한 번에 30개가 넘는 코드 리뷰를 받곤 했다. 

그 중에 1/3은 다름아닌 '주석'에 관련된 것이었다. 

이전까지는 주석에 대해 크게 생각하지 않고 TODO 정도만 남겼었었는데 이에 대한 코드리뷰를 받은 후 그에 대해서 토론하고, 수정해보니 크게 배운 것이 있다. 

 

  1. 주석은 설명이다. 즉, 해당 코드에 대해서 주석을 달아달라는 요청에 간결하고 명확한 좋은 주석을 달지 못한다면 나는 그 코드를 깊게 이해하고 있지 못한 것이다. (그냥 '이렇게 써 왔으니까..' 하면서 생각 없이 코딩한 경우일 가능성이 크다.)
  2. 1번의 이유로 다시 공부하다보면 주석을 다는게 아니라 코드를 수정하는 것이 필요한 경우가 더러 있다. 
  3. 의미있는 이름과 깨끗한 코드로 짜려는 노력을 다 한 뒤에도 다른 엔지니어에게 전달하고 싶은 의도가 있다면 그건 주석으로 남기는게 미래의 나에게도 큰 도움이 된다. 

이다. 

이렇게 주석을 대하는 태도와 마음가짐을 바꾸게 되었다. 

 

1. 주석을 최대한 쓰지 말자는 이유

그럼 왜 '주석을 최대한 쓰지 말자'라고 하는가?

  1. 주석은 나쁜 코드를 보완하지 못하기 때문이다. 코드에 주석을 추가하는 일반적인 이유는 코드 품질이 나쁘기 때문이다. 주석을 달아서 코드를 이해시키려 하기 보다 코드 자체를 개선하는데 시간을 써야한다. 
  2. 나쁜 주석을 써놓는 것은 안 써져있는 것보다 더 나쁘기 때문이다. 경솔하고 근거없는 주석, 오래되고 조잡한 주석은 코드를 이해하기 어렵고, 거짓과 잘못된 정보를 전달한다. 
  3. 주석은 방치될 수 있다. 코드는 컴파일되고, 실행시키면서 잘못된 부분을 바로바로 알고 고치고 관리할 수 있다. 반면 주석은 의지를 가지고 관리하지 않는 이상 쉽게 방치된다.  코드 업데이트에 따라가지 못하고 방치된 주석은 거짓과 잘못된 정보를 전달한다.

그래서 주석을 적기 전에 아래와 같이 생각해보자.

코드로 의도를 표현할 방법은 없을까?

2. 좋은 주석이란

1) 법적인 주석

: 회사가 정립한 구현 표현에 맞게 법적인 이유로 특정 주석을 넣는 것

2) 정보를 제공하는 주석

: 아래 예시 코드를 보면 이런 정규식 표현은 이것만 봐서는 바로 어떤 의미인지 알기가 어렵다. 그래서 이런 경우 주석으로 이해를 도을 수 있다. 

// kk:mm:ss EEE, MMM dd, yyyy 형식

Pattern timeFormat = Pattern.compile("\\d*:\\d:\\d* \\w*, \\w* \\d* \\d*");​

3) 의미를 명확하게 밝히고, 의도를 설명하는 주석

: 이런 주석이 실무에서 자주 쓰이는 주석이다. 아래는 내가 실제로 실무에서 주석을 달아달라는 코드리뷰를 받고 작성한 예시이다.

# This code is only executed when running as 'python src/app.py'. not used for production.
if __name__ == '__main__':
    # why host=0.0.0.0? Make available to request from other network.
    # why debug=True? Code modifications are reflected in real time and server is automatically restarted.
    app.run(host="0.0.0.0", debug=True)​

4) TODO, FIXME 주석

: TODO는 앞으로 할 일, 지금은 해결하지 않았지만 나중에 해야할 일을 미리 적어 둘 때 사용한다. 우리 회사에서는 Task/Issue Tracking sytem으로 Clickup을 사용하는데, 그래서 TODO 주석을 사용할 때는

# TODO: [앞으로 할 일] (해당 일(aka. Task)가 생성된 clickup Task link)​

이런 식으로 작성하도록 되어있다.

그리고 FIXME는 문제가 있지만 당장 수정할 필요는 없는 것들에 대해 적어두는 용도로 사용된다. 

 

나는 IDE로 VSCode를 사용하는데, VSCode Extension중에 TODO Hightlight를 사용하고 있다. 그러면 이 주석에 대해서 따로 하이라이트가 되어 보여져서 매우 유용하다.

 

5) Docstring

: 실무에서 어떤 class를 만들어서 PR했을 때 아래와 같은 코드리뷰를 받은 적이 있다.

(책과 해설강의는 Java 기준이라 Javadoc를 소개해주고 있지만 나는 Python을 사용하고 있기 때문에 python docstring을 기준으로 적어두고자 한다.)

please put a comment on what this class is for. for each method, please put a comment on what it does and how it can be used.
in addition, if this class is new, please consider to implement a unit test. it would be nice if we can use python docstring. https://pandas.pydata.org/docs/development/contributing_docstring.html

 

그래서 내 경우엔 https://realpython.com/documenting-python-code/#class-docstrings를 참고해서 class docstring을 적었다.

 

PEP8에는 (https://www.python.org/dev/peps/pep-0008/#id31)

Write docstrings for all public modules, functions, classes, and methods. Docstrings are not necessary for non-public methods, but you should have a comment that describes what the method does. This comment should appear after the def line.

 

Google style guide에는 (https://google.github.io/styleguide/pyguide.html#384-classes)

Classes should have a docstring below the class definition describing the class. If your class has public attributes, they should be documented here in an Attributes section and follow the same formatting as a function’s Args section.

 

이라고 되어 있어서 Google style guide와 PEP8 을 합쳐서

  • Class docstring: class 설명, Attribute 설명,
  • public method docstring: method 설명, args, returns, raises 설명
  • private method comment: method설명

이렇게 하는 건 어떨까 제안해보기도 했다. 

 

이렇게 docstring을 적어두고 해당 class나 method를 사용하려고 할 때 마우스 오버만 하면 IDE에서 해당 docstring을 보여주어서 필요한 인자나 리턴 값들을 한 번 더 확인하면서 작성할 수 있다는 장점이 있다. 

 

3. 나쁜 주석

1) 주절거리는 주석

: 주석을 달기로 결정했다면 충분한 시간을 들여 최고의 주석을 달도록 해야하며, 주석을 달라고 해서 마지못해 단 주석은 시간낭비일 뿐이다.

2) 같은 이야기를 중복하는 주석

# build hello-world image
docker build -t hello-world .

이런 코드에 이런 주석을 달았다면 분명 아래와 같은 리뷰를 받을 것이다. (내 경험이었다는게 정말 싫다.......)

please remove the comment. the following command is self-explaining what it does.

3) 있으나마나 한 주석

이런 주석이 정말 나쁜 이유는 개발자가 주석을 무시하는 습관에 빠질 수 있다는 것이다. 

# run
docker-compose up

이런 코드에 이런 주석을 달았다면 분명 아래와 같은 리뷰를 받을 것이다. (내 경험이었다는게 정말 싫다.......)

please remove the comment or please specify details

 

4. 실습 코드

https://github.com/Gracechung-sw/clean-code-practice/tree/chapter4-comment

 

GitHub - Gracechung-sw/clean-code-practice

Contribute to Gracechung-sw/clean-code-practice development by creating an account on GitHub.

github.com

 


 

Chapter 05. 포맷팅

작년 하반기 쯤에 Google TypeScript Style GuidePEP8을 다함께 읽으면서 우리 팀만의 coding convention(코딩 스타일에 관한 약속

)을 정립해나가는 시간을 가졌다. 

책에서 추천하는 

  • 간단한 형식 규칙을 정하고 그 규칙들을 착실히 따르기
  • 필요하다면 규칙을 자동으로 적용하는 도구 활용

을 꽤 오랜 시간을 들여서 시도한 셈이다.

 

그러면 시간을 적지 않게 들이면서까지 형식을 맞추서 코드를 작성하는 것이 왜 중요할까?

 

1. 포맷팅이 중요한 이유 

개발자들의 일차적 의무는 돌아가는 코드가 아니라 의사소통이다. 이 때 코드 포매팅은 가독성을 높여준다는 점에서 의사소통 방식, 일환으로 볼 수 있다. 

간단히 말하면 포맷팅은 가독성에 필수적이다!

 

오늘 구현한 기능이 다음 버전에서 바뀔 확률은 매우 높고, 이 때문에 오랜 시간이 지나 원래 코드가 많이 바뀌어도 맨 처음 잡아놓은 구현 스타일과 가독성은 코드가 바뀌는 중간중간에도 계속 영향을 미치고 이는 유지보수의 용이성과 확장성까지 이어진다. 

 

포맷팅이 중요한 이유를 정리하면 코드의 형식은 의사소통의 일환며 원래 코드는 사라질지라도 개발자의 스타일과 규율은 사라지지 않기 때문이다. 

 

2. 클린 코트 포맷팅

1) 적절한 행 길이를 유지하라. 

코드 길이를 200 줄 정도로 제한하는 것은 반드시 지킬 엄격한 규칙은 아니지만, 일반적으로 큰 파일보다는 작은 파일이 이해하기 쉽다. 

그리고 코드 길이가 200줄을 넘어간다면, 클래스가 여러 개의 일을 하면서 SPR 원칙을 위배하고 있을 가능성이 크다. 

2) 밀접한 개념은 서로 가까이 둔다. 

빈 행은 새로운 개념을 시작한다는 시각적 단서이다. 그렇기 때문에 서로 연관성이 높은 코드이 행을 세로로 가까이 놓아야 한다.

3) 변수 선언

변수는 사용하는 위치에 최대한 가까이 선언한다. 

4) 종속함수

한 함수가 다른 함수를 호출한다면 두 함수는 세로로 가까이 배치한다. 

가능하면 호출하는 함수를 호출되는 함수보다 먼저 배치한다. 

이렇게 하는 이유는 프로그램이 자연스럽게 읽힌다. 이런 규칙이 일관적으로 적용되어 있다면 독자는 방금 호출한 함수가 잠시 후에 정의되리라는 사실을 예측할 수 있다. 

Javascript

  • Bad
class PerformanceReview {
  constructor(employee) {
    this.employee = employee;
  }

  lookupPeers() {
    return db.lookup(this.employee, "peers");
  }

  lookupManager() {
    return db.lookup(this.employee, "manager");
  }

  getPeerReviews() {
    const peers = this.lookupPeers();
    // ...
  }

  perfReview() {
    this.getPeerReviews();
    this.getManagerReview();
    this.getSelfReview();
  }

  getManagerReview() {
    const manager = this.lookupManager();
  }

  getSelfReview() {
    // ...
  }
}

const review = new PerformanceReview(employee);
review.perfReview();
  • Good
class PerformanceReview {
  constructor(employee) {
    this.employee = employee;
  }

  perfReview() {
    this.getPeerReviews();
    this.getManagerReview();
    this.getSelfReview();
  }

  getPeerReviews() {
    const peers = this.lookupPeers();
    // ...
  }

  lookupPeers() {
    return db.lookup(this.employee, "peers");
  }

  getManagerReview() {
    const manager = this.lookupManager();
  }

  lookupManager() {
    return db.lookup(this.employee, "manager");
  }

  getSelfReview() {
    // ...
  }
}

const review = new PerformanceReview(employee);
review.perfReview();

 

 

 

 

Reference

https://github.com/ryanmcdermott/clean-code-javascript