본문 바로가기
도서/클린코드

4. 주석

by iskull 2022. 1. 10.
728x90

  잘 달린 주석은 그 어떤 정보보다 유용하다. 근거 없는 주석은 코드를 이해하기 어렵게 만든다. 오래되고 조잡한 주석은 거짓과 잘못된 정보를 퍼뜨린다.

  주석은 잘 해봐야 필요악이다. 개발자가 프로그래밍 언어를 제대로 사용할 능력이 있다면, 주석은 전혀 필요하지 않다.

  개발자들은 코드의 의도를 표현하지 못해서 실패를 만회하기 위해 주석을 사용한다. 따라서 주석을 사용해야되는 상황이 올때마다 정말 코드를 통해 의도를 명확히 전달할 방법이 없는지를 우선적으로 생각해야 한다.

  주석이 나쁜 이유는 주석이 코드를 따라가지 않기 때문이다. 코드는 지속적으로 변한다. 코드는 지속적으로 위치가 변하고 여러개로 쪼개지기도 한다. 하지만 주석은 이런 코드의 변화를 바라보기만 한다. 물론 주석 역시 엄격한 관리를 통해 코드의 변화를 따라갈 수 있다. 하지만, 주석에 쏟을 에너지를 보다 명확한 코드에 쏟아 관리할 것을 하나 줄이는 것이 더 좋지 않은가?

  부정확한 코드는 혼란만 야기한다. 진실은 오직 코드에만 존재한다. 오직 코드많이 정확한 정보를 제공하는 유일한 출처다.

 

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

  모듈을 짜고 보니 알아 보기 어렵고 구조가 엉망이라면 주석을 다는 대신 코드를 정리하라. 표현력이 풍부하고, 깔끔하며, 주적이 거의 없는 코드가 복잡하고 어수선하며 주석이 많은 코드 보다 훨씬 좋다.

 

코드의 의도를 표현하라

  분명 코드만으로 의도를 들어내기 어려운 경우가 존재 한다. 많은 개발자가 이를 코드는 훌륭한 수단이 아니라는 의미로 해석한다. 하지만 아래의 예시를 보라. 대다수의 코드는 두 번째 조건문처럼 의도를 표현할 수 있다.

1
2
3
4
5
// 직원 복지 혜택을 받을 자격이 있는지 검사한다.
if ((employee.flags & HOURLY_FLAG)
    && (employee.age > 65))
 
if (employee.isEligibleForFullBenefits())
cs

 

좋은 주석

  분명 주석은 나쁘다. 그럼에도 다음과 같은 주석은 괜찮은 주석이다.

  1. 법적인 주석

    때로는 회사가 정립한 구현 표준에 맞춰 법적인 이유로 특정 주석을 넣을 것을 명시한다. 소스 파일 맨 위에 추가하는 저      작권 정보와 소유권 정보가 그 예시다.

  2. 정보를 제공하는 주석

    때로는 기본 정보를 주석으로 제공하는 것이 편리하다. 다음 예제를 보자.

1
2
// kk:mm:ss EEE, MMM dd, yyyy 형식이다
Pattern timeMatcher = Pattern.compile("//d*://d*://d*: //w*:, //w* //d*, //d*");
cs

    위 코드의 주석은 코드에서 사용한 정규표현식이 시각과 날짜를 뜻한다 설명한다.

    물론 정보를 제공하는 주석을 작성하기 전에 우선적으로 해당 코드를 클래스 또는 메서드로 변환하는 것이 타당한지를 고려하는 것이 좋다.

  3. 의도를 설명하는 주석

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public void testConcurrentAddWidgets() throw Exception {
    WidgetBuilder 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 i = 0; i < 25000; i++) {
        WidgetBuilderThread widgetBuilderThread = 
            new WidgetBuilderThread(widgetBuilder, text, parent, failFlag);
        Thread thread = new Thread(widgetBuilderThread);
        thread.start();
    }
    assertEquals(false, failFlage.get());
}
cs

  4. 의미를 명로하게 밝히는 주석

    때로는 모호한 인수나 반환값은 그 의미를 읽기 좋게 표현하고 이해하기 쉬워진다. 일반적으로는 인수나 반환값 자체를 명확하게 만드는게 좋지만, 인수나 반환값이 표준 라이브러리나 변경하지 못하는 코드에 속한다면 의미를 명료하게 밝히는 주석을 사용하는 것이 유용하다.

1
2
assertTrue(a.compareTo(a) == 0); // a == a
assertTrue(aa.compareTo(ab) == -1); // aa < ab
cs

 5. 결과를 경고하는 주석

  때로는 다른 프로그래머들에게 결과를 경고할 필요가 있다. 

1
2
3
4
5
6
7
public static SimpleDateFormate makeStandardHttpDateFormat() {
    // SimpleDateFormat은 스레드에 안전하지 못하다.
    // 따라서 각 인스턴스를 독립적으로 생성해야 한다.
    SimpleDateFormat df = new SimpleDateFormat("EEE, dd MM yyy HH:mm:ss z");
    df.setTimeZone(TimeZone.getTimeZone("GMT");
    return    df;
}
cs

 6. TODO 주석

  때로는 '앞으로 할 일'을 //TODO 주석으로 남기는 것이 좋다. TODO 주석은 프로그래머가 필요하지만 당장 구현하기 어려운 업무를 기술한다. 더이상 필요 없는 기능, 더 좋은 코드 부탁 등이 그 예시다. 하지만 TODO 주석이 나쁜 코드를 남겨 놓는 핑계가 되서는 안된다.

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

  7. 중요성을 강조하는 주석

    중요하지만 대수롭지 않게 여겨질 것이 있다면 중요성 강조를 위해 주석을 사용하라.

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

 

나쁜 주석

  대다수의 주석이 이 범주에 속한다. 다수의 주석은 나쁜 코드에 대한 변명이다.

  1. 주절거리는 주석

    의미가 명확하지 않고 특별한 이유 없이 의무감으로 하라니까 마지못해 다는 주석이다. 우선 아래의 코드를 보자.

1
2
3
4
5
6
7
8
9
public void loadProperteis() {
    try {
        String propertiesPath = propertiesLocation + "/" + PROPERTIES_FILE;
        FileInputStream propertiesStream = new FileInputStream(propertiesPath);
        loadedProperties.load(propertiesStream);
    } catch (IOException e) {
        // 속성 파일이 없다면 기본값을 모두 메모리로 읽어 들였다는 의미
    }
}
cs

    catch 블록에 있는 주석은 오직 저자에게만 의미를 갖는다. 이 주석을 통해 투가 기본값을 메모리로 읽어 들인 건지, 언제    읽어 들이는지 등의 정보를 알 수 없다. 심지어는 그냥 catch 블록을 비워놓기 뭐해 몇마디 붙인거인지도 알 수 없다. 이 쓸    대없는 주석 하나 때문에 독자들은 의도를 이해하려고 다른 모듈까지 찾아봐야 한다.

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

    주석을 통해 같은 알 수 있는 정보를 메서드를 읽음으로서 똑같이 알 수 있다면 이는 중복이다. 중복된 주석은 독자가 함      수를 대충 읽고 넘어가게 만든다.

1
2
3
4
5
6
7
8
9
10
// 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");
        }
    }
}
cs

  3. 오해할 여지가 있는 주석

    위 코드의 주석을 다시 보자. 위 주석은 실제 동작 과정을 엄밀히 설명하지 못한다. 즉, 오해의 소지가 있다. 실제 코드의      동작 과정을 보면 closed가 false일때 timeoutMillis만큼 기다리고 그 후에도 closed가 false이면 Exception을 던진다.      하지만 주석을 보면 타임아웃이 도달하기 전 closed가 true가 되면 바로 함수가 반환된다고 생각할 수 있다.

  4. 의무적으로 다는 주석

    모든 함수에 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
/**
 * <h1>야구게임을 진행할 두 명의 플레이어 중 컴퓨터 플레이어</h1>
 *
 * @author yunki kim
 * @version 1.0
 * @since 2021-11-25(V1.0)
 */
 
public class ComputerPlayer implements Player{
 
    /** 야구게임 진행을 위해 컴퓨터가 고른 임의의 3자리 난수 */
    private RandomNumber computerSelectedNumber;
 
    /**
     * ComputerPlayer 클래스의 새로은 인스턴스를 만든다
     * 해당 클래스를 사용하는 사용자가 생성자를 사용해 computerSelectedNumber에 대한
     * 의존성을 주입하지 않을 경우 해당 생성자에서 주입해준다
     */
    public ComputerPlayer() {
        this(new RandomNumber());
    }
 
    /**
     * ComputerPlayer 클래스의 새로운 인스턴스를 만든다
     *
     * @param randomNumber 3자리 난수를 스트리으로 만들어 저장하는 인스턴스
     */
    public ComputerPlayer(final RandomNumber randomNumber) {
        this.computerSelectedNumber = randomNumber;
    }
 
    /**
     * {@inheritDoc}
     */
    @Override
    public String getSelectedNumber() {
        return this.computerSelectedNumber.getRandomNumber();
    }
}
cs

   5. 이력을 기록하는 주석

    예전에는 소스 코드 관리 시스템이 없었다. 따라서 코드 변경 이력을 모든 모듈 첫머리에 기록하는 관례가 있었다. 하지만    이제는 혼란만 가중시킨다.

  6. 있으나 마나 한 주석

    너무나 당연한 사실을 언급하고 있어서 전혀 가치가 없는 주석은 달지 마라. 이런 주석은 지나친 참견이다. 이는 결국 개발자가 다른 주석까지 무시하게 하고 정말 필요한 주석을 지나치게 한다.

1
2
3
4
5
6
7
public class AnnualDateRule() {
    
    /*
     * 기본 생성자
     */ 
    public AnnualDateRule() {}
}
cs

  7. 무서운 잡음

    때로는 javadoc도 잡음이다. 다음 예시를 한번 보자. info의 주석은 잘못되어 있으며 없어도 무방한 주석들만 존재한다.

1
2
3
4
5
6
7
8
/** The name. */
private String name;
/** The version. */
private String version;
/** The licenceName. */
private String licenceName;
/** The version. */
private String info;
cs

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

    아래 예시는 동일한 기능을 하는 코드들이다. 두 번째 조건문은 주석이 없음에도 명확한 표현을 하고 있다.

1
2
3
4
5
6
// 전역 목록 <smodule>에 속하는 듈이 우리가 속한 하위 시스템에 의존하는가?
if (smodule.getDependSubsystems().contains(subSysMod.getSubSystem()))
 
ArrayList moduleDependees = smodule.getDependSubsystems();
String ourSubSystem = subSysMod.getSubSystem();
if (moduleDependees.contains(ourSubSystem))
cs

    9. 위치를 표시하는 주석

     때로는 소스 파일의 특정 위치를 표시하기 위해 주석을 사용한다. 물론 이런 주석을 사용하고 그 주석 아래 특정 기능을      모아 뫃으면 유용한 경우도 존재하지만 일반적으로 사용하지 말아야 한다. 이런 주석은 너무 사주 사용되면 눈에 띄고 주.      의를 끈다. 즉, 필요 없는 곳에 눈길이 간다. 아주 드물게 사용하는 것은 괜찮지만 너무 자주 사용하지 말자.

      예시:

1
// Actions /////////////////////
cs

  10. 닫는 괄호 주석

    해당 닫는 괄호가 어느것에 대한 닫는 괄호 인지를 표기하기 위해서 닫는 괄호에 주석을 달기도 한다. 이는 코드가 중첩이 심하고 장황하면 효과가 있다. 하지만 작고 캡슐화된 함수에서는 잡음이다.
1
2
3
4
5
6
7
8
9
10
try {
    ...
// try 
catch (...) {
    ...
// catch
 
while (...) {
    ...
// while
cs

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

    소스 코드 관리 시스템은 코드에 대한 정보를 아주 정확히 기억한다. 따라서 굳이 코드의 저자를 표시할 필요 없다. 이런      주석은 그냥 오랫동한 코드에 방치되 부정확하고 쓸모없는 정보로 변한다.

1
/* 릭이 추가함 */
cs

  12. 주석으로 처리한 코드

    주석으로 처리된 코드는 어떤 의도가 있을지 모르니 다른 사람들이 지우기를 꺼려한다. 1960년대에는 주석으로 처리한 코드가 유용했다. 하지만 현재에는 코드 관리 시스템을 사용하고 있고 이게 우리를 대신해 코드를 관리해준다.

  13. HTML 주석

    HTML 주석은 주석을 읽기 쉬워야 하는 편집기/IDE에서 조차 읽기 어렵다. Javadoc 같은 도구로 주석을 뽑아 웹 페이지    에 올리기 위해 HTML태크를 삽입하는 책임은 개발자가 아닌 도구가 져야 한다.

  14. 전역 정보

    주석을 달아야 한다면 그 주변의 코드만 기술하라. 아래의 예시는 주석에서 기본 포트 정보를 기술한다. 하지만 함수 자체    는 포트 기본값을 전혀 통제하지 않는다. 따라서 이 주석은 바로 아래의 함수가 아닌 시스템 어딘가에 존재하는 다른 함수      를 설명한다.

1
2
3
4
5
6
7
8
/*
 * 적합성 테스트가 동작하는 포트: 기본값은 <b>8082</b>
 *
 * @param fitnessPort
 */
public void setFitnessPort(int fitnessPort) {
    this.fitnessPort = fitnessPort;
}
cs

  15. 너무 많은 정보

    주석에다 코드와 관련 없는 너무 많은 정보를 기술하지 말라. 정확히 필요한 정보만 기술하라.

  16. 모호한 관계

    주석과 주석이 설명하는 코드는 둘 사이 관계가 명확해야 한다. 모호한 정보를 전달하지 말라. 아래의 예제를 보면 주석에    서의 필터 바이트가 코드의 어떤 부분인지 알 수 없다.

1
2
3
4
5
/*
 * 모든 픽셀을 담을 만큼 충분한 배열로 시작한다(여기에 필터 바이트를 더한다).
 * 그리고 헤더 정보를 위해 200바이트를 더한다.
 */
this.pngBytes = new byte[((this.width + 1* this.height * 3+ 200];
cs

  17. 함수 헤더

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

  18. 비공개 코드에서 javadoc을 사용하지 말라

    공개 API에서는 javadoc이 유용하다. 하지만 공개하지 않을 목적이라면 javadoc은 쓸모없다. 그저 javadoc이 요구하는 주석 형식으      로인해 코드만 산만해진다.

 

출처 - 클린코드

'도서 > 클린코드' 카테고리의 다른 글

6. 객체와 자료 구조  (0) 2022.01.17
5. 형식 맞추기  (0) 2022.01.13
3. 함수  (0) 2022.01.03
2. 의미 있는 이름  (0) 2022.01.02
1. 깨끗한 코드  (0) 2022.01.01