Clean Code 3판을 읽고 정리한 글입니다
Ⅳ. 주석
나쁜 코드에 주석을 달지 말아라. 새로 짜라.
주석
- 잘달린 주석 : 그 어떤 정보보다 유용
- 경솔하고 근거 없는 주석 : 코드 이해 어렵게 만듬
- 오래되고 조잡한 주석 : 거짓과 잘못된 정보를 퍼뜨려 악영향
주석은 필요악
- 주석은 순수하게 선하지 않다
- 프로그래밍 언어 자체가 표현력이 풍부하다면
- 아니면 개발자(우리자신)가 프로그래밍 언어를 사용해 의도를 표현할 능력이 있다면
- 주석은 전혀 필요하지 않을 것이다
- 주석은 실패를 의미하며 줄이려 노력해야 한다
- 주석이 없이는 자신을 표현을 할 수 없기에 사용
- 주석의 내용을 코드로 의도를 표현하는 것이 가장 좋다
- 코드는 변화하고 진화하며 일부가 옮겨지고 조각이 나뉘거나 병합된다
- 주석은 코드를 따라가지 못한다 : 주석을 유지보수 하는 것은 불가능
- 코드만이 정확한 정보를 제공한다
- 부정확한 주석은 없는 주석보다 나쁘다
주석은 나쁜 코드를 보완하지 않는다
주석이 필요한 이유? 나쁜 코드
- 모듈을 작성하니 엉망이고 알어먹기 어려움
- 그래서 주석을 달아야겠다? → No! 코드를 정리해야 한다
- 표현력이 풍부, 깔끔하고 주석이 없는 코드 > 복잡하고 어수선하나 주석이 아주 많이 달려있는 코드
- 어지름을 주석으로 설명하는 노력으로 그 어지름을 치우는 데에 시간과 노력을 투자하라
코드로 의도를 표현하라
1 | // 어떤 쪽이 좋을까 |
- 몇 초만 더 생각하면 코드로 대다수 의도를 표현 가능
- 많은 경우 주석으로 달려는 설명을 함수로 만들어 표현해도 충분
좋은 주석
글자값을 한다고 생각되는 주석
법적인 주석
- 회사가 정립한 구현 표준에 맞춰 법적인 이유로 넣는 주석
- ex)각 소스 파일 첫머리에 주석으로 들어가는 저작권/소유권 정보 : 필요하고도 타당
- 가능하다면, 표준 라이선스나 외부 파일을 참조때로는 주석으로 구현 이해를 도와주는 걸 넘어서 결졍에 깔린 의도를 설명하기도 한다
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32// Copyright (C) 2003, 2004, 2005 by Object Montor, Inc. All right reserved.
// GNU General Public License
```
### 정보를 제공하는 주석
- 예1)기본정보를 주석으로 제공
- 유용함
- 가능하면 함수 이름에 정보를 담는 편이 더 좋음 - 네이밍의 중요성
- 예2)정규표현식의 결과물 형태를 주석으로
- 시각과 날짜를 변환하는 클래스를 만들어 코드를 옮겨주면 주석이 필요가 없음
```java
// 1.
// 테스트 중인 Responder 인스턴스를 반환
protected abstract Responder responderInstance();
// 위의 경우 함수이름에 정보를 담아 다음처럼 작성하면 주석을 없앨 수 있다
protected abstract Responder responderBeingTested();
// 2.
// 좋은 주석
// kk:mm:ss EEE, MMM dd, yyyy 형식이다.
Pattern timeMatcher = Pattern.compile("\\d*:\\d*\\d* \\w*, \\w*, \\d*, \\d*");
```
### 의도를 설명하는 주석
```java
// 스레드를 대량 생성하는 방법으로 어떻게든 경쟁 조건을 만들려 시도한다.
for (int i = 0; i > 2500; i++) {
WidgetBuilderThread widgetBuilderThread =
new WidgetBuilderThread(widgetBuilder, text, parent, failFlag);
Thread thread = new Thread(widgetBuilderThread);
thread.start();
}1
2
3
4
5
6
7
8
9public int compareTo(Object o) {
if (o instanceof WikiPagePath) {
WikiPagePath p = (WikiPagePath) o;
String compressName = StringUtil.join(names, "");
String compressedArgumentName = StringUtil.join(p.names, "");
return compressedName.compareTo(compressedArgumentName);
}
return 1; // 오른쪽 유형이므로 정렬 순위가 더 높다.
}
의미를 명료하게 밝히는 주석
1 | public void testCompareTo() throws Exception { |
- 모호한 인수나 반환값은 그 자체를 명확하게 만드는게 더 좋다 - 이해가 쉬어지므로
- 인수/반환값이 표준라이브러리 이거나 변경하지 못하는 코드? 의미를 명료하게 밝히는 주석이 유용
- 그릇된 주석의 위험, 올바른지 검증이 안되는 위험 - 주석의 위험
- 더 나은 방법이 없는지 고민할 것
결과를 경고하는 주석
- 다른 프로그래머에게 결과를 경고할 목적
1
2
3// 여유 시간이 충분하지 않다면 실행하지 마십시오.
public void _testWithReallyBigFile() {
} - ex)시간이 굉장히 오래걸리는 테스트 케이스
- JUnit에선 어노테이션으로 간단하게
@Ignore("실행이 너무 오래걸림")
TODO 주석
//TODO 주석
1
2
3
4
5// TODO-MdM 현재 필요하지 않다.
// 체크아웃 모델을 도입하면 함수가 필요 없다.
protected VersionInfo makeVersion() throws Exception {
return null;
}- 앞으로 할일, 현재 이유, 미래 모습등
- 필요하지만 당장 구현하기 어려운 업무를 기술
- ex)더 좋은 이름을 떠올려달라, 문제를 봐달라, 더이상 필요없는 기능을 삭제해라, 이벤트에 맞춰 코드를 고쳐라
- 요새 IDE는 TODO주석을 찾아줌 : 주석을 잊을 위험은 없음, 그래도 한번씩 점검해서 없어도 괜찮은 주석은 삭제
- FIXME : 문제가 있지만 당장 수정할 필요는 없을 때(가능하면 빨리 수정하는 것이 좋음)
중요성 강조 주석
공개 API에서의 Javadocs
설명이 잘된 공개 API는 참으로 유용하고 만족스럽다
- ex) 표준 자바 라이브러리에서 사용한 Javadocs가 좋은 예
- 공개 API를 구현한다면 반드시 훌륭한 Javadocs를 작성하라
- 어느 주석과 마찬가지로 Javadocs도 잘못될 가능성이 존재
정말 좋은 주석 - 주석을 달지 않을 방법을 찾아낸 주석!
나쁜 주석 = 대부분의 주석, 대다수의 주석
- 많은 주석 : 허술한 코드 지탱, 엉성한 코드 변명, 미숙한 결정 합리화
주절거리는 주석
- 특별한 이유 없이 다는 주석 → 시간 낭비
- 주석을 막상 단다면 충분히 시간을 들여 최고의 주석을 달도록 노력
- 이해가 안되어 다른 모듈을 뒤져야하는 주석 → 독자와 소통하지 못하는 주석 → 바이트 낭비
- 아래가 바이트 낭비의 예 : 주석의 의미를 알려면 다른 모듈을 뒤져야 함
1
2
3
4
5
6
7
8
9public void loadProperties() {
try {
String propertiesPath = propertiesLocation + "/" + PROPERTIES_FILE;
FileInputStream propertiesStream = new FileInputStream(propertiesPath);
loadedProperties.load(propertiesStream);
} catch (IOException e) {
// 속성 파일이 없다면 기본값을 모두 메모리로 읽어 들였다는 의미다.
}
}
같은 이야기 중복하는 주석
1 | // this.closed가 true일 때 반환되는 유틸리티 메서드다. |
→ 코드 내용이 그대로 설명하는 주석
- 주석이 코드보다 더 많은 정보를 제공하지 못함
- 의도/근거를 설명하는 주석도 아니고 코드보다 읽기가 쉽지도 않음
- 코드보다 부정확 → 독자가 함수를 대충 이해하고 넘어가게 만듬
오해 여지가 있는 주석
1 | // this.closed가 true일 때 반환되는 유틸리티 메서드다. |
this.closed
가 true로 되는 순간에 메서드 반환이 아닌 true여야 반환되는 것이다- 주석만 본 프로그래머는 왜 코드가 느려지는지 알 수가 없음
의무적으로 다는 주석
- 모든 함수에 Javadocs를 달거나 모든 필드에 주석 : 어리석기 그지 없음
- 코드를 복잡하게 만들고 거짓말을 퍼뜨리고 혼동과 무질서를 초래하는 아무 가치가 없는 주석
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15/**
*
* @param title CD 제목
* @param author CD 저자
* @param tracks CD 트랙 숫자
* @param durationInMinutes CD 길이(단위: 분)
*/
public void addCD(String title, String author, int tracks, int durationInMinutes) {
CD cd = new CD();
cd.title = title;
cd.author = author;
cd.tracks = tracks;
cd.duration = durationInMinutes;
cdList.add(cd);
}
이력을 기록하는 주석
- 소스 코드 관리 시스템이 없었던 시절의 바람직한 관례
- 현재는 혼란만 가중하므로 완전히 제거할것
있으나 마나 하는 주석
→ 너무 당연한 사실을 언급하고 새로운 정보를 제공못하는 주석
1 | /* |
- 개발자가 주석을 무시하는 습관에 빠지게 함 → 코드를 읽으며 주석을 건너뜀 → 코드 바뀌면서 주석은 거짓말이 됨
- 감정표현등 → 분풀이 주석으로 할 노력으로 코드 구조를 개선했었어야
무서운 잡음
- 특정 오픈소스의 Javadocs에는 의미 없는 잡음이 존재
함수나 변수로 표현가능하면 주석을 달지 말라
1 | // 전역 목록 <smodule>에 속하는 모듈이 우리가 속한 하위 시스템에 의존하는가? |
1 | ArrayList moduleDependees = smodule.getDependSubSystems(); |
위치를 표시하는 주석
1 | // Actions ///////////////////////////////////////////// |
- 가독성이 떨어짐, 뒷부분의 잡음 제거 필요
- 배너 : 눈에 띄며 주의 환기 → 반드시 필요할 때만 아주 드물게 사용할 것
닫는 괄호에 다는 주석
- 중괄호가 많아서 주석이 필요할 정도? 중첩이 심하고 장환한 함수라는 뜻
- 클린코드가 지향하는 작고 캡슐화된 함수에서 해당 주서은 잡음일 뿐
- 닫는 괄호에 주석을 달야야겠다는 생각이 든다면 함수를 작게 줄이려고 노력해야함
공로를 돌리거나 저자를 표시하는 주석
- 코드 오염시키지 말아라
- 코드에 오랫동안 방치되어 점점 쓸모 없어지고 부정확한 정보로 변함
- 이런 정보는 소스 코드 관리 시스템에 저장할 것
주석으로 처리한 코드
1 | InputStreamResponse response = new InputStreamResponse(); |
1 | this.bytePos = writeBytes(pngIdBytes, 0); |
- 주석 처리한 코드 : 밉살스러운 관행
- 이유가 있다고 남겼다고 사람들이 생각함 → 지우기 주저
- 점점 쓸모없는 코드 주석이 쌓여감
- 1960년대 에는 주석으로 처리한 코드가 유용
- 소스 관리 시스템이 알아서 코드를 기억해줌 → 코드 주석처리가 필요가 없음
- 그냥 코드를 삭제하라
HTML 주석
혐오 그 자체
- Javadocs에 html 태그
나
태그 난리
- IDE에서 조차 읽기 어려움
- Javadocs등의 도구로 주석을 뽑아 웹에 올리려면 해당 태그 삽입은 프로그래머가 아닌 도구가 책임
전역 정보
- (주석을 달아야 한다면) 근처 있는 코드만 기술
- 코드 일부에 주석을 달면서 시스템 정반적 정보 기술 X
- 아래 예시)
- 이미 정보가 중복됨
- 주석은 기본 포트 정보를 기술
- 함수는 포트 기본값 통제 불가 → 이 주석은 밑의 함수가 아닌 다름 함수의 내용 설명
- 즉 포트 기본값 설정 코드가 변해도 이 주석이 변하리라는 보장이 없음
1
2
3
4
5
6
7
8/**
* 적합성 테스트가 동작하는 포트: 기본값은 <b>8082</b>.
*
* @param fitnessePort
*/
public void setFitnessePort(int fitnessePort) {
this.fitnewssePort = fitnessePort;
}
너무 많은 정보
- 주석에 스펙 역사나 관련없는 정보 장황하게 늘어놓지 마라
모호한 관계
1 | /* |
- 위의 주석을 보고 드는 생각
- 필터 바이트는 무엇?
- +1과 관련이 있나? 아니면 *3과 관련이 있나? 아니면 둘다?
- 한 픽셀이 한 바이트인가?
- 200을 추가하는 이유는?
- 주석 다는 목적 : 코드 만으로 설명이 부족해서
- 주석 자체가 다시 설명을 요구하는 안 좋은 주석
함수 헤더
- 짧은 함수 : 긴 설명이 필요 없음
- 짧고 한 가지만 수행하며 이름을 잘 붙인 함수 >>>>> 주석으로 헤더를 추가한 함수
비공개 코드의 Javadocs
- 공개 API에선 Javadocs가 유용
- 공개하지 않을 코드라면 Javadocs가 쓸모가 없음
- 시스템 내부에 속한 클래스와 함수에 Javadocs를 생성할 필요가 없음
예제
예제)
- 주석을 잘 달았다고 착각하게 만들지만 바람직하지 않은 코드
주석을 잘 달았다고 착각하게 만들지만 바람직하지 않은 코드 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61/**
* 이 클래스는 사용자가 지정한 최대 값까지 소수를 생성한다. 사용한 알고리즘은 에라스토테네스의 체다.
* <p>
* 에라스토테네스: 기원전 276년 ...(후략)
* </p>
* 알고리즘은 상당히 단순하다. 2에서 시작하는 정수 배열을 대상으로 2의 배수를 모두 제거한다.
* 다음으로 남은 정수를 찾아 이 정수의 배수를 모두 지운다. 최대 값의 제곱근이 될 때까지 이를 반복한다.
*
* @author Alphonse
* @version 13 Feb 2002 atp
*/
import java.util.*;
public class GeneratePrimes {
/*
* @param maxValue는 소수르 찾아낼 최대 값
*/
public static int[] generatePrimes(int maxValue) {
if (maxValue >= 2) { // 유일하게 유요한 경우
// 선언
int s = maxValue + 1; // 배열 크기
boolean[] f = new booleans[s];
int i;
// 배열을 참으로 초기화
for (i = 0; i < s; i++)
f[i] = true;
// 소수가 아닌 알려진 숫자를 제거
f[0] = f[1] = false;
// 체
int j;
for (i = 2; i < Math.sqrt(s) + 1; i++) {
if (f[i]) { // i가 남아 있는 숫자라면 이 숫자의 배수를 구한다.
for (j = 2 * i; j < s; j += i)
f[j] = false; // 배수는 소수가 아니다.
}
}
// 소수 개수는?
int count = 0;
for (i = 0; i < s; i++) {
if (f[i])
count++; // 카운트 증가
}
int[] primes = new int[count];
// 소수를 결과 배열로 이동한다.
for (i = 0, j = 0; i < s; i++) {
if (f[i]) // 소수일 경우에
primes[j++] = i;
}
return primes; // 소수를 반환한다.
}
else // maxValue < 2
return new int[0]; // 입력이 잘못되면 비어 있는 배열을 반환한다.
}
}
1 | /** |
요약
- 주석을 최대한 쓰지 말자
- 쓰려면 좋은 주석
- 주석보다는 어노테이션
- JavaDoc
Related POST
- [Clean Code] Ⅰ. 깨끗한 코드
- [Clean Code] Ⅱ.의미 있는 이름
- [Clean Code] Ⅲ. 함수
- [Clean Code] Ⅳ. 주석
- [Clean Code] Ⅴ. 형식 맞추기
- [Clean Code] Ⅵ. 객체와 자료구조
- [Clean Code] Ⅶ. 오류 처리
- [Clean Code] Ⅷ. 경계
- [Clean Code] Ⅸ. 단위 테스트
- [Clean Code] Ⅹ. 클래스
- [Clean Code] Ⅺ. 시스템
- [Clean Code] Ⅻ. 창발성(創發性)
- [Clean Code] XIII. 동시성
- [Clean Code] XIV. 점진적 개선(SUCCESSIVE REFINEMENT)
- [Clean Code] XV. JUnit 들여다보기
- [Clean Code] XVI. SerialDate 리팩터링
- [Clean Code] XVII. 냄새와 휴리스틱
- [Clean Code] 다 읽었다~