본문 바로가기

개발/클린 코드

클린코드 4장 - 주석

나쁜 코드에 주석을 달지 마라. 새로 짜라. 
   - 브라이언 W. 커니핸, P.J. 플라우거

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

  주석은 '순수하게 선하지' 못하다. 기껏해야 필요악이다. 

  우리는 코드로 의도를 표현하지 못해, 실패를 만회하기 위해 주석을 사용한다. 때때로 주석 없이는 자신을 표현할 방법을 찾지 못해 할 수 없이 주석을 사용한다. 

  그러므로 주석이 필요한 상황에 처하면 곰곰이 생각하기 바란다. 상황을 역전해 코드로 의도를 표현할 방법은 없을까?

 

  주석을 무시하는 이유는, 거짓말을 해서다. 항상도 아니고 고의도 아니지만 너무 자주 거짓말을 하니까. 주석은 오래될수록 코드에서 멀어진다. 프로그래머들이 주석을 유지하고 보수하기란 현실적으로 불가능하니까.

 

저자는, 코드를 깔끔하게 정리하고 표현력을 강화하는 방향으로, 그래서 애초에 주석이 필요 없는 방향으로 에너지를 쏟겠다고 말한다. 

 

진실은 한곳에만 존재한다. 바로 코드다. 코드만이 자기가 하는 일을 진실되게 말한다. 코드만이 정확한 정보를 제공하는 유일한 출처다.

 

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

코드에 주석을 추가하는 일반적인 이유는 코드 품질이 나쁘기 때문이다. 자신이 저지른 난장판을 주석으로 설명하려 애쓰는 대신에 그 난장판을 깨끗이 치우는 데 시간을 보내라!

 

코드로 의도를 표현하라!

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


if (employee.isEligibleForFullBenefits())

 

좋은 주석

  • 법적인 주석
    • 소스 파일 첫머리에 주석으로 들어가는 저작권 정보와 소유권 정보
  • 정보를 제공하는 주석
  • 의도를 설명하는 주석
public void testConcurrentAddWidgets() throws 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,failFlag.get());
}

저자의 의도를 분명히 드러낸다.

  • 의미를 명료하게 밝히는 주석
  • 결과를 경고하는 주석
    • 특정 테스트 케이스의 시간이 너무 오래 걸려서  여유 시간이 충분하지 않을 때는 실행하지 말라는 주석 등..
  • TODO 주석
  • 중요성을 강조하는 주석

 

나쁜 주석

  대다수 주석이 이 범주에 속한다. 

  • 주절거리는 주석
  • 같은 이야기를 중복하는 주석
    • 헤더에 달린 주석이 같은 코드 내용을 그대로 중복한다
    • 자칫하면 주석을 읽는 시간이 코드를 보는 것 보다 더 오래걸린다
  • 오해할 여지가 있는 주석
// 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가 아니면 예외를 던진다. 

 

  • 의무적으로 다는 주석
  • 이력을 기록하는 주석
    • 당시에는 소스 코드 관리 시스템이 없었으니까. 하지만, 이제는 혼란만 가중한다.
  • 있으나 마나 한 주석
    • 너무나 당연한 사실을 언급하며 새로운 정보를 제공하지 못하는 주석이다.
  • 함수나 변수로 표현할 수 있다면 주석을 달지마라
// 전역 목록 <smodule>에 속하는 모듈이 우리가 속한 하위 시스템에 의존하는가?
if (smodule.getDependSubsystems().contains(subSysMod.getSubSystem()))

이 코드에서 주석을 없애고 다시 표현하면 다음과 같다.

ArrayList moduleDependees = smodule.getDependSubsystems();
String ourSubSystem = subSysMod.getSubSystem();
if (moduleDependees.contains(ourSubSystem))
  • 닫는 괄호에 다는 주석
    • 중첩이 심하고 장황한 함수라면 의미가 있을지도 모르지만, 우리가 선호하는 작고 캡슐화된 함수에는 잡음일 뿐이다. 
    • 닫는 괄호에 주석을 달아야겠다는 생각이 든다면 대신에 함수를 줄이려 시도하자.
  • 공로를 돌리거나 저자를 표시하는 주석
  • 주석으로 처리한 코드
    • 위의 2건은, 우수한 소스 코드 관리 시스템이 기억해준다.
    • 주석으로 처리한 이전 히스토리 등의 코드는 지우자.
    • 그냥 코드를 삭제하라. 잃어버릴 염려는 없다. 약속한다.
  • HTML 주석
    • 주석에 HTML태그를 넣어서 웹 페이지에 올리는 행위
  • 전역 정보
    • 주석을 달아야 한다면 근처에 있는 코드만 기술하라. 코드 일부에 주석을 달면서 시스템의 전반적인 정보를 기술하지 마라. 
    • 아래 주석은 바로 아래 함수가 아니라 시스템 어딘가에 있는 다른 함수를 설명한다는 말이다.
    • 즉, 포트 기본값을 설정하는 코드가 변해도 아래 주석이 변하리라는 보장은 전혀 없다.
/**
* 적합성 테스트가 동작하는 포트: 기본값은 8082.
*
* @param fitnessePort
*/
public void setFitnessePort(int fitnessesPort) {
   this.fitnessePort = fitnessePort;
}

 

  • 너무 많은 정보
  • 모호한 관계
  • 함수 헤더
    • 짧고 한 가지만 수행하며 이름을 잘 붙인 함수가 주석으로 헤더를 추가한 함수보다 훨씬 좋다.