기술 노트/Google C++ Style Guide

Google C++ Style Guide(2024) - 10장 주석(Comments)

anothel 2024. 11. 13. 19:02

10장 주석(Comments)

주석은 코드 가독성을 유지하는 데 매우 중요하다. 다음 규칙들은 주석을 어디에 어떻게 작성해야 하는지를 설명한다. 하지만 항상 기억해야 할 점은, 최상의 코드는 주석이 필요 없는 코드, 즉 스스로를 설명할 수 있는 코드다. 변수와 타입에 명확하고 의미 있는 이름을 부여하는 것이, 이해하기 어려운 이름을 사용하고 그 의미를 주석으로 설명하는 것보다 훨씬 좋다.

주석을 작성할 때에는 다음에 코드를 이해해야 할 독자를 염두에 두어야 한다. 후속 작업자가 당신의 코드를 쉽게 이해할 수 있도록 친절하게 작성하라. 그 독자가 나중에 바로 당신이 될지도 모른다.

10.1 주석 스타일 (Comment Style)

주석에는 // 또는 /* */ 구문을 사용할 수 있다. 단, 일관성을 유지해야 한다.

일반적으로 // 주석이 더 많이 사용되며, 코드 전반에서 일관된 주석 스타일을 유지하는 것이 중요하다.

10.2 파일 주석 (File Comments)

모든 파일의 시작 부분에는 라이선스 표준 구문을 포함해야 한다.

만약 .h와 같은 소스 파일이 여러 사용자 대상의 추상화 요소(공통 함수, 관련 클래스 등)를 선언하는 경우, 해당 추상화 요소들의 모음을 설명하는 주석을 포함해야 한다. 하지만 세부적인 문서는 각 추상화 요소에 함께 작성하며 파일 수준에서 상세히 기술할 필요는 없다.

예를 들어, frobber.h에 파일 주석을 작성했다면, frobber.cc나 frobber_test.cc에는 별도의 파일 주석을 포함하지 않아도 된다. 반면, 헤더 파일이 없는 클래스 모음인 registered_objects.cc와 같은 파일에는 파일 주석을 포함해야 한다.

법적 고지 및 작성자 정보

모든 파일에는 라이선스 표준 구문을 포함해야 하며, 프로젝트에서 사용하는 라이선스(예: Apache 2.0, BSD, LGPL, GPL)에 적합한 구문을 선택해야 한다.

작성자 정보가 포함된 파일에 큰 변경 사항을 추가하는 경우, 작성자 정보를 삭제하는 것을 고려해야 한다. 새 파일에는 일반적으로 저작권 고지나 작성자 정보를 포함하지 않는다.

10.3 구조체 및 클래스 주석 (Struct and Class Comments)

모든 명확하지 않은 클래스나 구조체 선언에는 해당 구조체 또는 클래스의 목적과 사용 방법을 설명하는 주석이 있어야 한다.

// GargantuanTable의 내용을 순회하는 반복자 클래스.
// 예시:
//    std::unique_ptr<GargantuanTableIterator> iter = table->NewIterator();
//    for (iter->Seek("foo"); !iter->done(); iter->Next()) {
//      process(iter->key(), iter->value());
//    }
class GargantuanTableIterator {
  ...
};

이와 같은 주석은 클래스나 구조체의 용도와 예상되는 사용 예시를 제공하여, 해당 구조체나 클래스를 이해하고 사용하는 데 도움을 준다.

10.4클래스 주석 (Class Comments)

클래스 주석은 독자가 클래스를 언제, 어떻게 사용해야 하는지 충분히 알 수 있도록 정보를 제공해야 한다. 올바른 사용을 위해 필요한 추가 고려 사항도 함께 문서화해야 한다. 만약 클래스에서 동기화가 필요한 경우(즉, 여러 스레드에서 동시에 접근할 수 있는 경우), 클래스가 가정하는 동기화 조건을 명확히 기술해야 하며, 멀티스레드 환경에서 사용 규칙과 불변 조건을 신중하게 문서화해야 한다.

클래스 주석은 해당 클래스를 간단히 사용하는 예제 코드 조각을 추가하기에 좋은 위치이기도 하다.

만약 인터페이스와 구현이 충분히 분리되어 있다면(예: .h 파일과 .cc 파일), 클래스 사용에 대한 설명은 인터페이스 정의와 함께 위치하고, 클래스의 동작 및 구현에 관한 설명은 메서드 구현과 함께 위치하는 것이 바람직하다.

10.5 함수 주석 (Function Comments)

선언부 주석은 함수의 사용 방식을 설명하며, 정의부 주석은 함수의 동작 방식을 설명한다.

함수 선언 주석

대부분의 함수 선언 앞에는 해당 함수가 무엇을 하고, 어떻게 사용하는지 설명하는 주석이 있어야 한다. 주석은 함수 사용 방식을 설명하며, 함수가 수행하는 작업 방식에 대해서는 주석을 남기지 않는다. 주석은 함수가 단순하고 명확한 경우(예: 클래스의 속성에 대한 단순 접근자) 생략할 수 있다. private 메서드와 .cc 파일에 선언된 함수도 예외가 아니다.

함수 주석은 보통 '이 함수는 ~한다' 라는 문장 구조로 작성하며, 명령문이 아닌 동작을 설명하는 문구로 시작한다. 예를 들어 **'파일을 연다'**보다는 **'파일을 연다'**와 같은 형식으로 작성한다.

주석에 포함해야 하는 정보는 다음과 같다.

  • 입력과 출력 설명. 함수 인수 이름을 백틱으로 감싸면 코드 인덱싱 도구가 더 나은 문서를 제공할 수 있다.
  • 클래스 멤버 함수의 경우, 메서드 호출 후에도 객체가 참조나 포인터 인수를 기억하는지 여부.
  • 포인터 인수의 경우, null을 허용하는지와 null일 때의 동작.
  • 출력 또는 입출력 인수의 상태 처리 방식 (예: 상태가 추가되는지 또는 덮어쓰는지).
  • 함수 사용에 따른 성능 영향이 있다면 그 내용.

예시)

// 이 테이블의 첫 번째 항목에 위치한 이터레이터를 반환한다. 
// 반환된 이터레이터는 `start_word`보다 크거나 같은 첫 번째 항목을 가리키며,
// 해당 항목이 없으면 null 포인터를 반환한다. 
// GargantuanTable이 삭제된 후에는 이터레이터를 사용할 수 없다.
//
// 이 메서드는 다음과 같다:
//    std::unique_ptr<Iterator> iter = table->NewIterator();
//    iter->Seek(start_word);
//    return iter;
std::unique_ptr<Iterator> GetIterator(absl::string_view start_word) const;

불필요하게 길거나 완전히 명백한 내용을 반복하지 않도록 한다.

오버라이드된 함수 주석의 경우, 상위 함수 주석을 반복하지 말고 오버라이드의 구체적인 사항에 집중하라. 오버라이드에 추가 설명이 필요하지 않다면 주석 없이 둬도 된다.

생성자와 소멸자 주석에서, 생성자와 소멸자의 목적에 대한 설명은 생략하라. 인수 처리 방식(예: 포인터 소유권을 가지는지)을 문서화하고, 소멸자의 정리 작업을 설명하라. 내용이 명확한 경우 주석을 생략해도 된다.

함수 정의 주석

함수 구현에 특별한 부분이 있다면, 함수 정의에 설명을 추가하라. 예를 들어 코드 작성 트릭, 단계별 개요, 구현 방식을 선택한 이유 등을 설명할 수 있다.

정의부에서는 선언부에 남긴 주석을 그대로 반복하지 않는다. 함수가 하는 일을 간략하게 되풀이하는 것은 괜찮지만, 주석의 초점은 구현 방식과 동작 방식에 맞춘다.

10.6 변수 주석 (Variable Comments)

일반적으로 변수 이름 자체가 변수의 용도를 충분히 설명할 수 있어야 한다. 그러나 특정 상황에서는 추가 주석이 필요할 수 있다.

클래스 데이터 멤버

클래스 데이터 멤버(인스턴스 변수 또는 멤버 변수라고도 함)의 목적은 명확해야 한다. 타입과 이름만으로 설명이 부족한 불변 조건(특수 값, 멤버 간 관계, 수명 요구 사항 등)이 있는 경우 주석을 통해 설명해야 한다. 타입과 이름만으로 충분히 표현되는 경우(예: int num_events_;)는 주석을 생략해도 된다.

특히 nullptr이나 -1과 같은 센티널 값의 존재와 의미는 명확하지 않다면 주석을 추가해야 한다.

예시)

private:
 // 테이블 접근을 범위 내에서 검사하기 위해 사용됨. -1은 아직
 // 테이블의 항목 수를 알 수 없음을 의미한다.
 int num_total_entries_;

전역 변수

모든 전역 변수는 변수의 용도, 사용 목적을 설명하는 주석이 있어야 하며, 전역 변수로 설정해야 하는 이유가 명확하지 않다면 이를 주석으로 추가한다.

예시)

// 이 회귀 테스트에서 실행할 총 테스트 케이스 수.
const int kNumTestCases = 6;

위와 같은 방식으로 변수의 용도를 명확히 하여 가독성을 높이고, 코드의 유지보수성을 확보해야 한다.

10.7 구현 주석 (Implementation Comments)

구현 코드에서는 복잡하거나 비직관적이고 중요한 부분에 주석을 추가해야 한다.

설명 주석

복잡하거나 어려운 코드 블록에는 해당 블록 앞에 설명 주석을 넣어야 한다. 이 주석은 코드의 동작을 이해하는 데 필요한 주요 사항을 설명해 주어야 한다.

예를 들어, 특정 이유로 비효율적인 방법을 사용하거나 일반적이지 않은 알고리즘을 구현한 경우, 코드의 의도나 선택한 방법의 이유를 설명하는 주석을 추가해 향후 유지보수 시 도움이 되도록 한다.

예시)

// 이 부분에서는 메모리 사용을 줄이기 위해 복잡한 캐싱 알고리즘을 사용했다.
// 성능은 다소 떨어질 수 있지만 메모리 제한을 초과하지 않도록 설계함.
for (int i = 0; i < largeData.size(); ++i) {
  ProcessData(largeData[i]);
}

이처럼 설명 주석은 코드의 의도와 동작 원리를 이해할 수 있도록 하여 코드의 가독성을 높이고, 협업자나 미래의 유지보수자가 코드의 목적을 쉽게 파악할 수 있도록 한다.

10.8 함수 인자 주석 (Function Argument Comments)

함수 인자의 의미가 명확하지 않은 경우, 아래와 같은 방식을 고려할 수 있다.

  • 명명된 상수 사용
    • 인자가 리터럴 상수인 경우, 동일한 상수를 여러 함수 호출에서 사용하여 일관성을 가정한다면, 명명된 상수를 사용하여 명시적 제약을 만들고 일관성을 보장한다.
  • 열거형으로 대체
    • 함수 시그니처에서 bool 인자를 열거형(enum) 인자로 변경하는 것을 고려한다. 이렇게 하면 인자 값이 스스로를 설명하므로 더 읽기 쉽게 된다.
  • 설정 클래스 또는 구조체 사용
    • 구성 옵션이 많은 함수의 경우, 모든 옵션을 포함한 단일 클래스나 구조체를 정의하여 인스턴스를 전달하는 방식을 고려한다. 이렇게 하면 호출 시점에서 옵션의 의미를 명확하게 할 수 있고, 함수 인자 수를 줄여 호출 구문이 더 읽기 쉽고 쓰기 쉬워진다. 추가 옵션을 추가할 때 호출부를 변경할 필요가 없다는 장점도 있다.
  • 복잡한 표현식은 명명된 변수로 대체
    • 큰 표현식이나 복잡한 중첩 표현식을 명명된 변수로 대체하여 가독성을 높인다.
  • 주석을 사용하여 의미 명확화
    • 마지막으로, 호출부에 주석을 추가하여 인자의 의미를 명확히 할 수 있다.

예시)

// 이 인자들은 무엇을 의미하는가?
const DecimalNumber product = CalculateProduct(values, 7, false, nullptr);

위와 같은 코드보다 다음과 같이 옵션 클래스를 활용하여 인자를 명확히 하면 가독성이 향상된다.

ProductOptions options;
options.set_precision_decimals(7);
options.set_use_cache(ProductOptions::kDontUseCache);
const DecimalNumber product =
    CalculateProduct(values, options, /*completion_callback=*/nullptr);

이처럼, 명확하고 읽기 쉬운 코드를 작성하기 위해 함수 인자의 의미를 명확히 하는 방법을 고려해야 한다.

10.9 하지 말아야 할 것들 (Don'ts)

너무 당연한 내용을 주석으로 작성하지 않는다.

  • 특히, 코드가 무엇을 수행하는지 그대로 설명하지 말고, 그 행동이 이해하기 어려운 경우에만 필요한 주석을 추가한다. C++를 잘 이해하는 독자가 볼 때 당연한 코드라면, 높은 수준의 설명을 통해 코드가 그 행동을 수행하는 이유를 설명하거나, 코드가 스스로 설명되도록 작성하는 것이 좋다.

예시 비교

잘못된 예시)

// 벡터에서 요소를 찾는다.  <-- 잘못된 주석: 너무 당연함!
if (std::find(v.begin(), v.end(), element) != v.end()) {
  Process(element);
}

더 나은 주석)

// "element"가 이미 처리되지 않았다면 처리한다.
if (std::find(v.begin(), v.end(), element) != v.end()) {
  Process(element);
}

코드 자체가 설명이 될 때

코드가 스스로 설명될 수 있다면 굳이 주석을 추가할 필요가 없다. 위의 예에서, 아래처럼 함수를 통해 코드를 자체 설명형 코드로 만들 수 있다.

if (!IsAlreadyProcessed(element)) {
  Process(element);
}

이처럼, 이해 가능한 코드를 작성함으로써 불필요한 주석을 줄이고, 필요한 경우에만 높은 수준의 주석을 추가하여 코드를 더욱 읽기 쉽고 명확하게 만든다.

10.10 구두점, 철자 및 문법 (Punctuation, Spelling, and Grammar)

주석에는 구두점, 철자, 문법에 주의해야 한다. 잘 작성된 주석은 읽기 쉽지만, 문법적으로 잘못된 주석은 읽기 어렵다.

  • 주석은 서술형 텍스트처럼 읽히도록 작성해야 하며, 대문자와 구두점을 적절히 사용해야 한다.
  • 문장이 완전할 때 가독성이 높아지므로, 가능한 경우 완전한 문장 형태로 작성한다. 코드 줄 끝에 있는 짧은 주석은 다소 덜 형식적일 수 있지만, 스타일은 일관성을 유지해야 한다.

코드 리뷰어가 콤마 대신 세미콜론을 써야 한다고 지적하는 것이 때로는 짜증날 수 있지만, 코드의 가독성을 위해 구두점, 철자, 문법이 중요하다. 정확한 문법과 구두점 사용이 코드의 명확성과 가독성을 크게 높일 수 있다.

올바른 주석 작성은 코드 유지보수를 용이하게 하고, 독자가 코드의 의미를 쉽게 이해할 수 있도록 돕는다.

10.11 TODO Comments

TODO 주석은 일시적이거나 단기적인 해결책 또는 임시로 충분하지만 완벽하지 않은 코드에 사용해야 한다.

  • TODO 주석에는 반드시 대문자로 TODO를 포함하고, 문제와 가장 관련 있는 사람 또는 문제에 대한 식별자를 표시해야 한다. 이 식별자로는 버그 ID, 이름, 이메일 주소, 문제 URL 등이 될 수 있다.

예시)

// TODO: 버그 12345678 - 2047년 4분기 호환성 기간이 끝나면 이 코드를 제거할 것.
// TODO: example.com/my-design-doc - 이 코드는 다음에 수정될 때 수동으로 고쳐야 한다.
// TODO(bug 12345678): Foo 서비스가 종료된 후 이 목록을 업데이트할 것.
// TODO(John): 연결 연산자로 "\*"를 사용할 것.
  • 미래 특정 시점에 수행할 작업을 나타내는 TODO 주석은, 구체적인 날짜나 명확한 이벤트를 포함해야 한다. 예를 들어 "2005년 11월까지 수정할 것" 또는 "모든 클라이언트가 XML 응답을 처리할 수 있을 때 이 코드를 제거할 것"과 같은 형식으로 작성해야 한다.

이와 같은 TODO 주석 작성 방식은 코드의 임시적 성격을 명확히 전달하고, 나중에 코드 리뷰 및 유지보수 과정에서 필요한 작업을 쉽게 파악할 수 있도록 도와준다.


참조URL

https://google.github.io/styleguide/cppguide.html

728x90