## 목차 ##



## 들어가면서 ##

"나쁜 코드에 주석을 달지 마라. 새로 짜라."

브라이언 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인가? 주석 자체가 다시 설명을 요구한다.

+ Recent posts