[개발 도서 공부 - 📗 Clean Code] 4장: 주석

Hyunjoon Choi·2023년 10월 20일
0
post-thumbnail

서론

  • 프로그래머들이 주석을 유지하고 보수하기란 현실적으로 불가능하다.
  • 부정확한 주석은 아예 없는 주석보다 훨씬 더 나쁘다. 부정확한 주석은 독자를 현혹하고 오도한다. 부정확한 주석은 결코 이뤄지지 않을 기대를 심어준다.

서론과 이전 내용에서 나왔듯, 주석을 사용하지 말 것을 이야기하고 있는데 왜 주석 챕터가 별도로 존재하는 것일까 궁금했다. 그에 대한 내용을 정리해본다.

주석은 나쁜 코드를 보완하지 못한다

  • 표현력이 풍부하고 깔끔하며 주석이 거의 없는 코드가, 복잡하고 어수선하며 주석이 많이 달린 코드보다 훨씬 좋다.
  • 주석이 필요하다는 뜻은 코드 품질이 나쁘다는 뜻이다.

코드로 의도를 표현하라

메서드 등의 이름을 잘 정한다면, 굳이 이 코드가 어떤 의도로 사용되는지 주석으로 설명할 필요가 없다. 그러므로 코드로 의도를 표현하도록 하라.

좋은 주석

대부분의 나쁜 주석 말고, 좋은 주석은 어떨 때 쓰이는 주석인지 알아보자.

법적인 주석

때로는 회사가 정립한 구현 표준에 맞춰 법적인 이유로 특정 주석 (예: Copyright, 표준 라이선스 등)을 넣어야 하는 경우 주석을 작성할 필요가 있다.

정보를 제공하는 주석

아래의 두 가지 경우, 좋은 주석이라고 할 수 있다.

// 테스트 중인 Responder 인스턴스를 반환한다. -> 주석 대신 함수 이름을 responderBeingTested로 바꾸면 주석이 필요없어진다.
protected abstract Responder responderInstance();
// kk:mm:ss EEE, MMM dd, yyyy 형식이다. -> 주석 대신 시각과 날짜를 변환하는 클래스를 만들어 코드를 옮기면 주석이 필요없어진다.
Pattern timeMatcher = Pattern.compile(
    "\\d*:\\d*:\\d* \\w*, \\w* \\d* \\d*");

정보를 제공하는 주석은 좋은 주석이라고 할 수 있으나, 가능한 주석이 필요 없을 수 있도록 함수, 클래스 등에 정보가 표현되도록 하자.

사실 정보를 제공하는 주석이 과연 좋은 주석인지는 의문이 든다. 위의 법적인 주석처럼 약간의 작성해야 할 강제성이 있는 주석도 아니기에, 코드 품질이 좋다면 없애는 게 맞다고 생각된다.

의도를 설명하는 주석

때때로 주석은 구현을 이해하게 도와주는 선을 넘어 결정에 깔린 의도까지 설명한다.

public int compareTo(Object o) {
    if (o instanceof WikiPagePath) {
        WikiPagePath p = (WikiPagePath) o;
        String compressedName = StringUtil.join(names, "");
        String compressedArgumentName = StringUtil.join(p.names, "");
        return compressedName.compareTo(compressedArgumentName);
    }
    return 1; // 오른쪽 유형이므로 정렬 순위가 더 높다.
}

아래의 의미를 명료하게 밝히는 주석처럼, 만약 compareTo와 같은 메서드를 직접 인자로 사용할 경우에는 더 명료한 이름을 가진 함수로 만들어두는 게 좋을 듯 하지만, 함수 안의 내용을 이해하는 데 있어서 도움이 될 수 있는 경우라면 이렇게 주석으로 만들어도 나쁘진 않을 것 같다. (사실 나 또한 1, -1이 어떤 경우인지 가끔씩 헷갈린다.)

의미를 명료하게 밝히는 주석

객체 간 비교를 할 때 compareTo를 사용하여 1, -1, 0을 반환하는 경우를 간혹 접했을 것이다. 이 경우에는 결과에 대해 주석으로 남기면 의미를 명료하게 밝힐 수 있다.

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

인수나 반환값이 표준 라이브러리나 변경하지 못하는 코드에 속한다면 의미를 명료하게 밝히는 주석이 유용하다.
물론 그릇된 주석을 달아놓을 위험은 상당히 높다. 직전 예제를 살펴보면 알겠지만, 주석이 올바른지 검증하기 쉽지 않다. 이것이 의미를 명료히 밝히는 주석이 필요한 이유인 동시에 주석이 위험한 이유이기도 하다.

개인적인 생각으로는, 이러한 경우에는 함수를 만들어 분리해내면 쉽게 유추할 수 있기에 주석을 없앨 수 있지 않을까라고 생각된다. 예시로 아래와 같이 말이다!

private static final int SAME = 0;

public boolean isBothWikipagePathSame(final WikipagePath origin, final WikipagePath test) {
    return origin.compareTo(test) == SAME;
}

결과를 경고하는 주석

때로는 다른 프로그래머에게 결과를 경고할 목적으로 주석을 사용하기도 한다.

// 여유 시간이 충분하지 않다면 실행하지 마십시오.
public void _testWithReallyBigFile() {...} // JUnit4 등장 이전 작성법: 언더바(_) 붙이기

pulbic static SimpleDateFormat makeStandardHttpDateFormat() {

    // SimpleDateFormat은 스레드에 안전하지 못하다.
    // 따라서 각 인스턴스를 독립적으로 생성해야 한다.
    SimpleDateFormat df = new SimpleDateFormat("EEE, dd MMM  yyyy HH:mm:ss z");
    ...
}

이렇게 위험성이 있는 코드에 대해 주석을 남겨두는 것은 불가피한 경우에는 사용해도 괜찮다고 생각이 든다.

TODO 주석

때로는 '앞으로 할 일'을 //TODO 주석으로 남겨두면 편하다.

// TODO-MdM 현재 필요하지 않다.
// 체크아웃 모델을 도입하면 함수가 필요 없다.
protected VersionInfo makeVersion() throws Exception {
    return null;
}

개인적인 생각으로는 이 부분은 필요하지 않을 것 같다고 생각된다. 앞으로 할 일과 같은 것들은 자칫하면 import 하지 않는 것들을 남겨두는 것 처럼, 실제 돌아가는 코드를 읽는 데 있어서 방해되거나 갱신되지 않을 것 같다. 보통 프로젝트를 할 때 코드나 주석으로 남겨두기보다는 노션 등 전문적인 협업 툴에 기록해두는 식으로 했기에, 그런 식으로 사용하면 될 듯 하다.

중요성을 강조하는 주석

예시 코드를 다음과 같이 알려주면서, 자칫 대수롭지 않다고 여겨질 뭔가의 중요성을 강조하기 위해서도 주석을 사용한다고 나와있다.

String listItemCount = match.group(3).trim();
// 여기서 trim은 정말 중요하다. trim 함수는 문자열에서 시작 공백을 제거한다.
// 문자열에 시작 공백이 있으면 다른 문자열로 인식되기 때문이다.
new ListItemWidget(this, listItemContent, this.level + 1);

공개 API에서 Javadocs

표준 자바 라이브러리에 있는 Javadocs는 설명이 잘 되어 있기에 유용하고 만족스럽다. 그러나, 여느 주석과 마찬가지로 Javadocs 역시 독자를 오도하거나, 잘못 위치하거나, 그릇된 정보를 전달할 가능성이 존재한다.

좋은 주석에 대한 개인적 결론

좋은 주석에 대한 예시들을 나열하고 있으나, 대부분 함수화하거나 클래스를 별도로 만드는 등 충분히 주석을 만들지 않는 방법이 존재하는 케이스들임을 확인했다. 법적인 내용을 담은 주석과 같이 강제성이 없다면, 웬만하면 주석을 사용하지 않도록 해 보자.

나쁜 주석

주절거리는 주석

try {
    String propertiesPath = propertiesLocation + "/" + PROPERTIES_FILE;
    FileInputStream propertiesStream = new FileInputStream(propertiesPath);
    loadedProperties.load(propertiesStream);
} catch (IOException e) {
    // 속성 파일이 없다면 기본값을 모두 메모리로 읽어 들였다는 의미다.
}

위 주석은 다음과 같은 문제가 있다.

  • try 안쪽 부분 중 정확히 어떤 부분에서 예외가 발생할 수 있는지 명시하지 않았다.
  • 속성 파일, 기본값이 어떤 의미인지 파악하려면 위의 코드를 전부 확인해야 한다.

따라서 이러한 케이스를 주절거리는 주석이라 하는 것이다. 주석을 달기로 결정했다면 충분한 시간을 들여 최고의 주석을 달도록 노력해야 한다.

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

함수, 변수 등을 통해 의도를 제대로 잘 전달했으나, 주석에서도 똑같이 의도를 작성하는 경우다. 불필요하다!

/*
* 컨테이너의 부모 컨테이너
*/
protected Container parent = null;

/*
* 컨테이너와 관련된 Realm
*/
protected Realm realm = null;

오해할 여지가 있는 주석

다음 주석을 보자.

// 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.closedtrue인 순간에는 반환되지 않는다.
  • this.closedtrue여야 반환된다.
  • this.closedtrue가 아니면 예외가 던져진다.

이러한 문제를 방지하려면 주석도 잘 작성해야겠지만 함수도 잘 작성해야겠음을 느꼈다.

의무적으로 다는 주석

모든 메서드에 Javadocs를 작성하는 것은 매우 좋지 않은 습관이다. 주석이 많아질수록 유지보수도 힘들다.

이력을 기록하는 주석

이력 관리 등에 대한 주석은 깃 등 소스 코드 관리 시스템이 존재하지 않았기 때문에 발생했었던 주석인데, 지금은 전부 깃과 깃허브를 통해 확인할 수 있으므로 작성하지 않도록 한다.

있으나 마나 한 주석

이 경우도 같은 이야기를 중복하는 주석과 비슷한 예시다. 당연한 사실을 굳이 주석까지 달 필요가 없다.

/** 월 중 일자 */
private int dayOfMonth;

무서운 잡음

문서를 제공해야 한다는 잘못된 욕심으로 Javadocs를 사용하지 마라. 잡음만 될 수도 있다.

  • 주석이 없어도 이해에 무리 없다.
  • 오히려 코드가 달라지면 주석도 수정해야 한다. 자칫하면 유지보수도 안된다.
/** The name. */
private String name;

/** The version. */
private String version;

함수나 변수로 표현할 수 있다면 주석을 달지 마라

아래와 같은 경우 함수화를 통해 없앨 수 있는 주석이다. 코드로 의도를 표현하자.

(딱 봐도 디미터 법칙도 위배한 코드인 게 보인다.)

// 전역 목록 <smodule>에 속하는 모듈이 우리가 속한 하위 시스템에 의존하는가?
if (smodule.getDependSubsystems().contains(subSysMod.getSubSystem())

위치를 표시하는 주석

이러한 경우는 처음보는 경우다. 절대 사용하지 말자. 아마 파일에서 현재 진행된 곳이 어디까지인지 쉽게 보기 위해 작성한 것 같다.

// Actions //////////////

닫는 괄호에 다는 주석

이 경우도 본 적이 없고, 해 본 적도 없다. 잡음이 될 뿐이니 작성하지 않도록 하자.

try {
    while(...) {
       ...
    } // while
    ...
} // try

공로를 돌리거나 저자를 표시하는 주석

이 또한 이력을 기록하는 주석과 같은 이유다.

주석으로 처리한 코드

주석으로 처리한 코드는 전체적인 오염을 초래할 뿐이다. 다른 개발자들이 지우기를 주저하기 때문에 관리를 하지 않으면 코드가 매우 더러워지는 데 일조한다.

HTML 주석

소스 코드에서 HTML 주석은 혐오 그 자체다. HTML 주석은 (주석을 읽기 쉬어야 하는) 편집기/IDE에서조차 읽기가 어렵다.

사실 이 경우는 전혀 생각치도 않았던 당연한 경우일 것 같아.. 쉽게 납득되었다.

전역 정보

함수가 통제할 수 없는 전역적인 정보들은 작성하지 않도록 한다. 작성해봤자 함수에서 확인되는 것도 아니다.

/**
* 적합성 테스트가 동작하는 포트: 기본값은 <b>8082</b>.
* 
* @param fitnessePort
*/
public void setFitnessePort(int fitnessePort) {
    this.fitnessePort = fitnessePort;
}

너무 많은 정보

코드를 설명하기 위해 너무 많은 정보를 뿌리지 않는다.

예시로 JWT와 관련된 기능에 대한 함수를 주석으로 설명할 때, JWT의 기본 개념, 역사 등을 전부 나열하지 않도록 한다.

모호한 관계

주석을 이해하는 데 있어 추가적인 설명이 들어가야 한다면 잘못된 주석이다. 주석의 본래 의도는 코드의 부가적인 설명을 덧붙이기 위함이다.

/*
* 모든 픽셀을 담을 만큼 충분한 배열로 시작한다(여기에 필터 바이트를 더한다).
* 그리고 헤더 정보를 위해 200바이트를 더한다.
*/
this.pngBytes = new byte[((this.width + 1) * this.height * 3) + 200];
  • 필터 바이트란 무엇일까?
  • 왜 3을 곱할까?

이렇게 코드에 대한 설명이 아닌 다른 설명을 하고 있다.

함수 헤더

짧은 함수는 긴 설명이 필요 없다. 짧고 한 가지만 수행하며 이름을 잘 붙인 함수가 주석으로 헤더를 추가한 함수보다 훨씬 좋다.

함수 헤더가 무엇을 뜻하는걸까? C언어의 #include<stdio.h>와 같은 것을 뜻하는걸까? 이 부분에 대해서는 다른 사람들과 논의해보고 싶다. 단순히 함수의 윗부분에 작성한 주석을 뜻하는걸까? 만약 그렇다면 이해가 된다. 함수에 이름을 잘 짓고 한 가지만 수행하도록 했다면, 별도의 주석은 필요 없을 것이다. 함수의 이름만으로도 충분히 이해를 할 수 있기 때문이다.

비공개 코드에서 Javadocs

공개하지 않을 코드라면 Javadocs가 필요 없다.

  • 유용하지 않다.
  • Javadocs의 요구 형식으로 인해 코드가 보기 싫어지고 산만해진다.

비공개 코드는 같은 클래스에 있는 공개 코드에서 사용하는 데 쓰인다. 따라서 외부에서 사용하지 않는다. 만약 라이브러리를 만든다고 가정하면, 사용자들은 공개된 코드만 사용할 수 있다. 따라서 사용법과 설명 또한 공개된 코드만 존재하면 되기에 이러한 내용이 있는 것 같다.

결론

주석 부분은 애매한 부분이 많은 것 같다. (쉽게 적용될 수 있는 것이 아니라고 느껴진다. 다른 챕터들도 물론 그렇지만)

일단 최선은 주석이 필요없을 정도로 함수와 변수에 대한 이름을 짓는 것임을 알았다. (코드로 의도를 표현)

좋은 주석의 예시를 지켰는지 판단하는 게 불확실하니, 우선은 나쁜 주석의 예시로 나온 케이스들을 작성하지 않도록 하는 것 부터 시도해야겠다.

그리고 덤으로 그동안 Javadocs 주석들을 간혹 봤어서 매 함수마다 그렇게 작성해야 하나 싶었는데, Javadocs를 쓰는 경우도 매우 신중히 선택해야 하는 것임을 알게 되어 좋았다. (무서운 잡음 참고)

개인적 TMI

최근 학교를 다니며 지하철을 타는데, 정거장에 표시된 시간표와 다르게 실제 운행이 되어 이용에 불편함을 느끼고 있다. 이를 프로그래밍 세상에 비유해보자면, 지하철 (라이브러리 등)을 사용하는 사용자 (실제 라이브러리 코드를 사용하는 개발자)가 불편함을 느끼지 않으려면, 그에 관련된 설명 (주석)이 제대로 기술, 즉 유지보수가 매번 잘 되어 있어야겠다는 생각으로 이끌어 내 보았다. 여튼, 주석의 본질적인 의도를 잊지 말도록 하자!

profile
개발을 좋아하는 워커홀릭

0개의 댓글