book/Clean Code

[Clean Code] 4장, 주석

항성 2022. 8. 28. 13:28
반응형

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

  • 코드에 주석을 추가하는 일반적인 이유는 코드 품질이 나쁘기 때문.
  • 주석으로 코드의 의미를 표현하기보다는 코드를 리팩터링 하자.

코드로 의도를 표현하라

  • 코드를 깔끔하고 짜임새 있게 작성해서 주석을 달지 않는 것이 좋음.
// 1. 개선 전 코드 + 주석 추가

// 직원에게 복지 혜택을 받을 자격이 있는지 검사한다.
if ((employee.flags & HOURLY_FLAG) && (emplyee.age > 65))

// 2. 개선 후 코드 + 주석 삭제
if (employee.isEligibleForFullBenefits())

좋은 주석

법적인 주석

회사가 정립한 구현 표준에 맞춰 법적인 이유로 추가하는 특정 주석 (ex : 저작권, 소유권)

// Copyright (c) 2003,2004,2005 by Object Mentor, Inc. All rights reserved,
// GNU General Public License 버전 2 이상을 따르는 조건으로 배포한다.

정보를 제공하는 주석

기본적인 정보를 주석으로 제공

// **1. 주석으로 정보를 표현**
// 테스트 중인 Responder 인스턴스를 반환한다.
protected abstract Responder responderInstance();

// **2. 주석을 지우고 코드에 정보를 추가 (더 나은 버전)**
protected abstract Responder responderBeingTested();
// **1. 주석으로 형식 표현**

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

// **2. 시각과 날짜를 반환하는 클래스를 생성하여 코드를 옮겨주면 더 깔끔.**

의도를 설명하는 주석

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

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; // 오른쪽 유형이므로 정렬 순위가 더 높다.
}
// 스레드를 대량 생성하는 방법으로 어떻게든 경쟁 조건을 만들려 시도한다.
for (int i = 0; i < 25000; 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 ab = PathParser.parse("PageA.PageB");
 WikiPagePath b = PathParser.parse("PageB");
 WikiPagePath ba = PathParser.parse("PageB.PageA");
 WikiPagePath aa = PathParser.parse("PageA.PageA");
 WikiPagePath bb = PathParser.parse("PageB.PageB");

 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 void _testWithReallyBigFile() {

 writeLinesToFile(1000000000);

 response.setBody(testFile);
 response.readyToSend(this);
 String responseString = output.toString();
 assertSubString("Content-Length: 1000000000", responseString);
 assertTrue(bytesSent > 1000000000);
}

*최근에는 @Ignore(”실행이 너무 오래 걸린다”) 를 추가하여 테스트 케이스 비활성화

public static SimpleDateFormat makeStandardHttpDateFormat() {
 // SimpleDateFormat은 스레드에 안전하지 못하다

 // 따라서 각 인스턴스를 독립적으로 생성해야 한다.

 SimpleDateFormat df = new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss z");
 df.setTimeZone(TimeZone.getTimeZone("GMT"));
 return df;
}

TODO 주석

앞으로 해야 할 일을 기록하는 주석.

최근 나오는 대다수 IDE는 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

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");
        }
    }
}

오해할 여지가 있는 주석

의도는 좋으나 딱 맞을 정도로 엄밀하게 작성하지 못한 주석.

ex : 같은 이야기를 중복하는 주석의 예시 코드.

‘this.closed가 true일 때’라는 문장은 true로 변하는 순간을 말하는 것인가, 아니면 true인 상태를 말하는 것인가?

(클린 코드 한국어 번역 과정에서 미묘한 차이가 와닿게 표현되지 않은 듯.)

의무적으로 다는 주석

모든 함수에 Javadocs를 달거나 모든 변수에 주석을 달아야 한다는 등의 규칙은 코드를 복잡하고 혼동되게 만든다.

/**
 *
 * @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);
}

이력을 기록하는 주석

지금은 소스 버전 관리 프로그램이 있기 때문에, 모듈의 첫머리에 주석을 추가할 필요 없다.

* 변경 이력 (11-Oct-2001부터)
* ------------------------------------------------
* 11-Oct-2001 : 클래스를 다시 정리하고 새로운 패키징
* 05-Nov-2001: getDescription() 메소드 추가
* ...

있으나 마나 한 주석

너무 당연한 사실을 언급하며 새로운 정보를 제공하지 못하는 주석

/*
 * 기본 생성자
 */
protected AnnualDateRule() {

}

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

주석으로 코드를 설명하기보다는 코드를 개선하라

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

// 2. 개선 후 코드
ArrayList moduleDependencies = smodule.getDependSubSystems();
String ourSubSystem = subSysMod.getSubSystem();
if (moduleDependees.contains(ourSubSystem))

위치를 표현하는 주석

특정 위치를 표현하려 사용하는 주석 (눈에 잘 띄라고 표시) 은 가독성만 낮추므로 제거.

너무 자주 사용하지 않을 때만 눈에 띄며 주위를 환기시키는 기능을 함. 필요할 때 드물게 사용.

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

닫는 괄호에 다는 주석

코드에 중첩이 심하고 장황하다면 필요할 수도 있지만, 작고 캡슐화된 코드에는 필요 없다.

try{
  while((line = in.readLine) != null){
    lineCount++;
    charCount += line.length();
    String words[] = line.split("a");
    wordCount += words.length;
  } //while
  System.out.println("wow");
  System.out.println("wow");
  System.out.println("wow");
} //try

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

소스 코드, 버전 관리 시스템은 누가 언제 무엇을 추가했는지 표시됨.

따라서 주석으로 표시하기보다는 시스템을 활용하여 확인.

주석으로 처리한 코드

주석으로 처리된 코드는 다른 사람들이 지우기를 주저한다.

소스 관리 시스템이 이전 코드를 다 기억해주기 때문에 필요하면 주석으로 남겨두기보다는 지우고 시스템을 활용하여 확인.

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;

HTML 주석

HTML 주석은 편집기/IDE에서조차 읽기 어렵다.

전역 정보

주석은 근처에 있는 코드와 관련된 정보만 기술. 시스템의 전반적인 정보를 기술하지 마라.

/**
 * 적합성 테스트가 동작하는 포트: 기본값은 <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]

함수 헤더

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

비공개 코드에서 Javadocs

공개 API는 Javadocs가 유용하지만 공개하지 않을 코드라면 Javadocs는 쓸모가 없다.

반응형

'book > Clean Code' 카테고리의 다른 글

[Clean Code] 7장, 오류 코드  (0) 2022.08.31
[Clean Code] 6장, 객체와 자료구조  (0) 2022.08.30
[Clean Code] 5장, 형식 맞추기  (0) 2022.08.29
[Clean Code] 3장, 함수  (0) 2022.08.27
[Clean Code] 2장, 의미 있는 이름  (0) 2022.08.27