## 목차 ##
## 들어가면서 ##
"나쁜 코드에 주석을 달지 마라. 새로 짜라."
브라이언 W. 커니핸, P.J.플라우거
잘 달린 주석은 그 어떤 정보보다 유용하다.
경솔하고 근거없는 주석은 코드를 이해하기 어렵게 만든다.
오래되고 조잡한 주석은 거짓과 잘못된 정보를 퍼뜨려 해악을 미친다.
프로그래밍 언어를 통해 의도를 표현할 능력이 있다면 주석은 필요없다.
- 그러지 못하고 실패했기 때문에 주석을 사용한다.
코드는 수정되고 보완되지만 주석은 현실적으로 코드를 따라가지 못한다.
- 주석은 오래될수록 코드에서 멀어진다.
- 더 이상 지킬필요 없는 규칙을 명시하기도 한다.
- 코드만이 진실을 말한다.
## 주석은 나쁜 코드를 보완하지 못한다 ##
주석을 추가하는 일반적인 이유는 코드 품질이 나쁘기 때문이다.
표현력이 풍부하고 깔끔하며 주석이 없는 코드가 복잡하고 어수선하며 주석이 많이 달린 코드보다 훨씬 좋다.
+ 자신이 저지른 난장판을 주석으로 애쓰는 대신 난장판을 치워라.
## 코드로 의도를 표현하라! ##
- 코드로 의도를 표현하기 어려운 경우가 존재한다.
- 코드가 훌륭한 수단이 아니어서 그런게 아니라 개발자의 표현력이 부족한것이다.
<bad\>
//직원이 복지혜택을 받을 자격이 있는지 검사한다.
if((employee.flags & HOURLY_FLAG) && (employee.age > 65))
<good\>
if(employee.isEligibleForFullBenefits())
- 주석으로 달려는 설명을 함수로 만들어 표현해라.
## 좋은 주석 ##
법적인 내용
- 저작권 정보, 소유권 정보 등
정보를 제공하는 주석
//테스트 중인 Responder 인스턴스를 반환
protected abstract Responder responderInstance();
그러나 가능하면 함수 이름에 정보를 담는 편이 더 좋다. 아래처럼
protected abstract Responder responderBeingTested();
아래 코드의 주석은 정규표현식이 시각, 날짜를 뜻한다는 정보를 제공한다.
// kk:mm:ss EEE, MMM dd, yyyy 형식이다.
Pattern timeMatcher = Pattern.compile("\\d*:\\d*\\d* \\w*, \\w*, \\d*, \\d*");
그러나 이왕이면 날짜 변환 클래스를 따로 만들면 코드가 더 깔끔해지고 좋아진다.
- 의도를 설명하는 주석
- 이러한 결정을 한 이유에 대한 설명
// 스레드를 대량 생성하는 방법으로 어떻게든 경쟁 조건을 만들려 시도한다.
for (int i = 0; i > 2500; i++) {
WidgetBuilderThread widgetBuilderThread =
new WidgetBuilderThread(widgetBuilder, text, parent, failFlag);
Thread thread = new Thread(widgetBuilderThread);
thread.start();
}
- 의미를 명료하게 밝히는 주석
- 모호한 인수나 반환값을 포함하는 코드에 의미를 설명해준다.
- 인수나 반환값 자체를 명료하게 바꾸는 것이 더 좋다.
- 하지만 표준 라이브러리나 변경할 수 없는 코드라면 주석을 사용한다.
public void testCompareTo() throws Exception {
WikiPagePath a = PathParser.parse("pageA");
WikiPagePath b = PathParser.parse("PageB");
assertTrue(a.comparTo(b) != 0); // a!=b
}
- 결과를 경고하는 주석
// 여유 시간이 충분하지 않다면 실행하지 마십시오.
public void _testWithReallyBigFile() {
writeLinesToFile(10000000);
...
}
혹은
public static SimpleDateFormat make StandardHttpDateFormat() {
//SimpleDateFormat은 스레드에 안전하지 못하다.
//따라서 각 인스턴스를 독립적으로 생성해야 한다.
SimpleDateFormat df = new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss z");
df.setTimeZone(TimeZone.getTimeZone("GMT");
return df;
}
- TODO 주석
- 아직 함수를 구현하지 않은 이유
- 필요없는 기능을 삭제해달라는 요청 알림
- 누군가에게 문제를 확인해달라는 요청
- 앞으로의 계획에 따른 수정 필요 알림
- 그럼에도 불구하고 TODO로 떡칠하지 말아라.
- 주기적으로 TODO를 점검해 필요없는 것은 삭제해라.
// TODO-MdM 현재 필요하지 않다.
// 체크아웃 모델을 도입하면 함수가 필요 없다.
protected VersionInfo makeVersion() throws Exception {
return null;
}
- 중요성을 강조하는 주석
String listItemContent = match.group(3).trim();
// 여기서 trim은 정말 중요하다. trim 함수는 문자열에서 시작 공백을 제거한다.
// 문자열에 시작 공백이 있으면 다른 문자열로 인식되기 때문이다.
new ListItemWidget(this, listItemContent, this.level + 1);
return buildList(text.substring(match.end()));
- 공개 API에서 Javadocs
- 사용법에 대한 훌륭한 설명
## 나쁜 주석 ##
- 주절거리는 주석
- 나쁜 코드의 지탱, 변명, 합리화며 개발
- 대다수의 주석이 이 범주에 속한다.
public void loadProperties() {
try {
String propertiesPath = propertiesLocation + "/" + PROPERTIES_FILE;
FileInputStream propertiesStream = new FileInputStream(propertiesPath);
loadedProperties.load(propertiesStream);
} catch (IOException e) {
// 속성 파일이 없다면 기본값을 모두 메모리로 읽어 들였다는 의미다.
}
}
catch 블록의 주석이 무슨 뜻인지 이해하기 힘들다. 독자와 제대로 소통하지 못하는 주석은 낭비일 뿐이다.
- 같은 이야기를 중복하는 주석
- 코드의 내용을 그대로 주석으로 주절거린다.
// this.closed가 true일 때 반환되는 유틸리티 메서드다.
// 타임아웃에 도달하면 예외를 던진다.
public synchronized void waitForClose(final long timeoutMillis) throws Exception {
if (!closed) {
wait(timeoutMillis);
if (!closed) {
throw new Exception("MockResponseSender could not be closed");
}
}
}
아래는 톰캣에서 가져온 코드인데 중복된 Javadocs가 매우 많다.
/**
* 이 컴포넌트의 프로세서 지연값
*/
protedted int backgroundProcessorDelay = -1;
/**
* 이 컴포넌트를 지원하기 위한 생명주기 이벤트
*/
protected LifecycleSupport lifecycle = new LifecycleSuppor(this);
위와 같은 주절거림이 매우 많다.
- 오해할 여지가 있는 주석
// this.closed가 true일 때 반환되는 유틸리티 메서드다.
// 타임아웃에 도달하면 예외를 던진다.
public synchronized void waitForClose(final long timeoutMillis) throws Exception {
if (!closed) {
wait(timeoutMillis);
if (!closed) {
throw new Exception("MockResponseSender could not be closed");
}
}
}
사실 이 코드는 this.closed가 true인 순간 반환하지 않는다. this.closed가 true여야 블록이 실행되지않고 함수 호출이 끝난다는 것이다. 주석의 잘못된 정보로 인해 this.closed가 true인것이 판별되는 순간 함수가 반환될것이라 기대하고 이 함수를 호출한 프로그래머는 지연시간을 감수해야한다.
- 의무적으로 다는 주석
- 별 의미없이 억지로 추가하는 주석
- 무엇인가 정보를 제공해야 한다는 욕심 혹은 압박감에서 탄생한 주석
/**
*
* @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);
}
위 코드는 모든 함수에 Javadocs를 넣으라는 규칙이 만들어낸 괴물이다.
- 이력을 기록하는 주석
- 추가, 삭제, 수정 등의 변경 내용
- 저자 정보
* 변경 이력 (11-Oct-2001부터)
* ------------------------------------------------
* 11-Oct-2001 : 클래스를 다시 정리하고 새로운 패키징
* 05-Nov-2001: getDescription() 메소드 추가
* 이하 생략
소스코드 버전관리 시스템이 없었던 시절에는 변경이록 기록은 관례였다. 하지만 지금은 위와같은 변경이력들이 매번 쌓이면 파일이 매우 지저분해지고 혼란만 가중된다.
- 있으나 마나 한 주석
- 너무나 당연한 사실을 언급
- 새로운 정보를 제공해주지 못하는 주석
/*
* 기본 생성자
*/
protected AnnualDateRule() {
}
/*
* 월 중 일자
*/
private int dayOfMonth;
- 함수나 변수로 표현할 수 있다면 주석을 달지 마라
// 전역 목록 <smodule>에 속하는 모듈이 우리가 속한 하위 시스템에 의존하는가?
if (module.getDependSubsystems().contains(subSysMod.getSubSystem()))
이 코드의 주석을 없애고 표현해보자.
ArrayList moduleDependencies = smodule.getDependSubSystems();
String ourSubSystem = subSysMod.getSubSystem();
if (moduleDependees.contains(ourSubSystem))
- 닫는 괄호에 다는 주석
- 중첩이 심하고 긴 함수라면 의미가 있을지도 모른다. 그러나 차라리 함수를 줄이자.
try{
while(...) {
...
} //while
} //try
catch(...) {
...
} //catch
- 주석으로 처리한 코드
this.bytePos = writeBytes(pngIdBytes, 0);
//hdrPos = bytePos;
writeHeader();
writeResolution();
//dataPos = bytePos;
if (writeImageData()) {
wirteEnd();
this.pngBytes = resizeByteArray(this.pngBytes, this.maxPos);
} else {
this.pngBytes = null;
}
return this.pngBytes;
우수한 버전관리 시스템으로 인해 코드를 잃어버릴 염려는 없으니 과감하게 지워라.
- 전역 정보
- 근처 코드의 정보만 기술해라
/**
* 적합성 테스트가 동작하는 포트: 기본값은 <b>8082</b>.
*
* @param fitnessePort
*/
public void setFitnessePort(int fitnessePort) {
this.fitnewssePort = fitnessePort;
}
일단 중복이다. 포트를 바꾸는 함수인데, 주석에 기본 포트값까지 기술한다. 하지만 이 함수에서 기본 포트값은 변경하지 못한다. 이후에 기본 포트값이 변경되더라도 이 주석은 변하지 않는다.
- 모호한 관계
- 주석을 읽고 무슨 소린지 알아야한다.
/*
* 모든 픽셀을 담을만큼 충분한 배열로 시작한다(여기에 필터 바이트를 더한다.)
* 헤더 정보를 위해 200바이트를 더한다.
*/
this.pngBytes = new byte[((this.width + 1) * this.height * 3) + 200];
주석에서 말하는 필터 바이트란? +1인가? *3인가? 주석 자체가 다시 설명을 요구한다.
'소프트웨어 공학 > 클린코드' 카테고리의 다른 글
(클린코드) Chapter06 - 객체와 자료구조 (0) | 2020.05.16 |
---|---|
(클린코드) Chapter05 - 형식 맞추기 (0) | 2020.05.16 |
(클린코드) Chapter03 - 함수 (0) | 2020.05.16 |
(클린코드) Chapter02 - 의미 있는 이름 (0) | 2020.05.16 |
(클린코드) Chapter01 - 깨끗한 코드 (0) | 2020.05.16 |