코드만이 정확한 정보를 제공하는 유일한 출처이다.
우리는 코드로 의도를 표현하지 못해, 주석을 사용한다.
코드는 변화하고 진화한다. 여기서 저기로 옮겨지기도 하며 조각이 나눠지고 합쳐지기도 한다. 하지만 대부분의 주석은 코드를 따라가지 못한다.
따라서 주석은 오래될수록 코드에서 멀어지며, 오래될수록 완전히 그릇될 가능성도 커진다.
그래서 애초에 주석이 필요 없는 방향으로 코드를 깔끔하게 정리하고 표현력을 강화해야 하며, 가능한 줄이도록 꾸준히 노력해야 한다.
다음은 주석으로 흥미로운 결정을 기록한 예제이다. 두 객체를 비교할 때 저자는 다른 어떤 객체보다 자기 객체에 높은 순위를 주기로 결정했다.
public int compareTo(Object o)
{
if(o instanceof WikiPagePath)
{
WikiPagePath p = (WikiPagePath) o;
String compressedName = StringUtil.join(names, "");
String compressedArgumentName = StringUtil.join(p.names, "");
String compressedName.compareTo(compressedArgumentName);
}
return 1; // 옳은 유형이므로 정렬 순위가 더 높다.
}
저자의 의도가 분명히 드러나는 주석의 예제이다.
public void testConcurrentAddWidgets() throws Exception {
WidgetBuilder =
new WidgetBuilder(new Class[]{BoldWidget.class});
String text = "'''bold text'''";
ParentWidget parent =
new BoldWidget(new MockWidgetRoot(), "'''bold text'''");
AtomicBoolean failFlag = new AtomicBoolean();
failFlag.set(false);
// 스레드를 대량 생성하는 방법으로 어떻게든 경쟁 조건을 만들려 시도한다.
for (int 1 = 0; i < 25000; i++) {
WidgetBuilderThread widgetBuilderThread =
new WidgetBuilderThread(widgetBuilder, text, parent, failFlag);
Thread thread = new Thread(widgetBuilderThread);
thread.start();
}
assertEquals(false, failFlag.get());
}
때로는 인수나 반환값이 표준 라이브러리나 변경하지 못하는 코드에 속한다면 의미를 명료하게 밝히는 주석이 유용하다.
public void testCompareTo() throws Exception
{
WikiPagePath a = PathParser.parse("PageA");
WikiPagePath ab = PathParser.parse("PageA.PageB");
WikiPagePath b = PathParser.parse("PageB");
WikiPagePath aa = PathParser.parse("PageA.PageA");
WikiPagePath bb = PathParser.parse("PageB.PageB");
WikiPagePath ba = PathParser.parse("PageB.PageA");
assertTrue(a.compareTo(a) == 0); // a == a
assertTrue(a.compareTo(b) == 0); // a != b
assertTrue(ab.compareTo(ab) == 0); // ab == ab
assertTrue(a.compareTo(b) == -1); //a < b
assertTrue(aa.compareTo(ab) == -1); // aa < ab
assertTrue(ba.compareTo(bb) == -1); // ba < bb
assertTrue(b.compareTo(a) == 1); // b < a
assertTrue(ab.compareTo(aa) == 1); // ab < aa
assertTrue(bb.compareTo(ba) == 1); // bb < ba
}
다른 프로그래머에게 결과를 경고할 목적으로 주석을 사용한다.
public static SimpleDateFormat makeStandardHttpDateFormat()
{
// SimpleDateFormat은 스레드에 안전하지 못하다.
// 따라서 각 인스턴스를 독립적으로 생성해야 한다.
SimpleDateFormat df = new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss z");
return df;
}
사실 대다수의 주석이 이 범주에 속한다.
허술한 코드를 지탱하거나, 엉성한 코드를 변명하고, 프로그래머의 미숙한 결정을 합리화하는 등.
이해가 안 되어 다른 모듈까지 뒤져야 하는 주석은 독자와 제대로 소통하지 못하는 주석이다. 그런 주석은 바이트만 낭비할 뿐이다.
주석을 달기로 했다면 충분한 시간을 들여 최고의 주석을 달도록 노력한다.
코드의 내용을 그대로 묘사하는 것에 지나지 않는 주석이 있다.
// this.closed가 true일 때 반환되는 유틸리티 메서드이다.
// 타임아웃에 도달하면 예외를 던진다.
public synchronized void waitForClosed(final long timeoutMillis)
throws Exception
{
if(!closed)
{
wait(timeoutMillis);
if(!closed)
throw new Exception("MockResponseSender could not be closed");
}
}
이런 주석은 코드보다 더 많은 정보를 제공하는 것도 아니고, 코드의 의도나 근거를 설명하는 것도 아니다.
오히려 코드보다 더 부정확해 독자가 함수를 대충 이해하고 넘어가게 만드는 역효과가 날 수도 있다.
모든 함수에 Javadocs를 달거나 모든 변수에 주석을 달아야 한다는 규칙은 구시대적이다.
이러한 주석은 아무 가치도 없으며, 코드만 헷갈리게 만들고, 잘못된 정보를 제공할 여지만 만든다.
예전에는 소스 코드 관리 시스템이 없었기 때문에, 모든 모듈 첫머리에 변경 이력을 기록하고 관리하는 관례가 바람직했었다.
이제는 완전히 제거하는 편이 좋다.
// 전역 목록 <smodule>에 속하는 모듈이 우리가 속한 하위 시스템에 의존하는가?
if (smodule.getDependSubSystems().contains(subSysMod.getSubSystem()))
이 코드의 주석을 없애고 개선해보자.
ArrayList moduleDependees = smodule.getDependSubSystems();
String ourSubSystem = subSysMod.getSubSystem();
if (moduleDependees.contains(ourSubSystem));
중첩이 심하고 장황한 함수가 있을 때 닫는 괄호에 구분을 위한 특수한 주석을 달아놓는 경우가 있다.
이러한 주석은 작고 캡슐화된 함수에는 당연히 불필요하며, 중첩이 심하다고 느껴지면 이것을 줄이려는 방향으로 코드를 개선하는 것이 좋다.
다른 사람들이 지우기를 주저하게 만든다. 이유가 있어 남겨놓았으리라고, 중요하니까 지우면 안 된다고 생각하기 때문이다.
소스 코드 관리 시스템은 우리를 대신해 코드를 기억해준다.
이러한 코드는 그냥 삭제하라.
코드 일부에 주석을 달면서 시스템의 전반적인 정보를 기술하지 마라.
공개 API는 Javadocs가 유용하지만 그 반대는 아무런 쓸모가 없다.