Google C++ Style Guide
7. Comments (주석)
주석은 코드를 가독성 있게 유지시킨다. 다음의 규칙들은 무엇을 작성할지 어디에 작성할지를 기술한다. 주석은 매우 중요하지만, 최선의 코드는 self-documenting이라는 것을 기억해라. 타입과 변수 이름을 센스 있게 지으면 주석으로 설명하는 것보다 훨씬 낫다.
// 또는 /* */ 를 사용하며, 일관성 있게 사용한다.
// 또는 /* */ 를 사용하지만, //이 더 많이 사용된다.
어떻게 사용하고 어떤 스타일로 사용하든지 일관성 있게 사용한다.
각 파일의 시작은 저작권(copyright)을 게시하며, 파일 내용에 대한 설명을 기술한다.
Legal Notice and Author Line
모든 파일은 다음의 아이템을 순서대로 따른다.
- 저작권 문장 (copyright statement) (Copyright 2008 Google Inc)
- 라이선스 어구(license boilerplate) (Apache 2.0, BSD,LGPL, GPL)
- 파일의 원본 저자를 확인하기 위한 저자(author) 라인
누군가 작성한 파일에 중요한 변화를 작성했으면 저자 라인에 자신을 추가한다. 또 다른 기여자가 그 파일에 질문이 있거나, 연락할 필요가 있을 때 유용하다.
File Contents (파일 내용)
저작권과 저자에 대한 주석 다음에, 파일의 내용을 설명하는 주석을 작성한다.
일반적으로 .h파일은 파일에 선언된 클래스에 대한 내용을 기술할 것이다. 무엇을 위한 것이며 어떻게 사용되는지 등등. .cc파일은 구현에 대한 세부적인 정보나 알고리즘 트릭에 관한걸 작성한다. 이런 사항들을 .h파일에 작성하는 것이 유용하다면 .h파일에 작성하되, .cc파일에는 해당 주석이 .h파일에 있다고 언급한다.
.h파일과 .cc파일에 주석을 중복시키지 않는다.
모든 클래스 정의는 무엇을 위한 것이며 어떻게 사용돼야 하는지 주석을 작성한다.
// GargantuanTable의 내용을 탐색하는 Iterator, 사용 예: // GargantuanTableIterator* iter = table->NewIterator(); // for (iter->Seek("foo"); !iter->done(); iter->Next()) { // process(iter->key(), iter->value()); // } // delete iter; class GargantuanTableIterator { ... }; |
이미 파일의 상단에 작성이 되었다면, ‘자세한 설명은 파일의 상단에 있는 주석을 참고하라”처럼 간단히 작성해도 좋다. 그러나 주석에 대한 분류는 해야 한다.
클래스에서 동기화에 대한 가정들을 문서화한다. 만약 클래스 인스턴스가 멀티 스레드에서 사용된다면 멀티스레드 환경에 대해 보다 주의 깊게 문서화 한다.
선언부에 함수의 사용을 설명한다. 정의부에서는 함수의 연산에 대해 설명한다.
Function Declarations (함수 선언부)
모든 함수 선언부는 함수가 어떤 일을 하고, 어떻게 사용하는지를 설명하는 주석을 즉시 작성한다. 이런 주석은 명령형(imperative)(“Open the file”) 보다는 설명적(“Opens the file”)이어야 한다. 주석은 함수를 설명하되, 함수가 무엇을 하는지는 설명하지 않는다. 일반적으로 이런 주석은 함수가 태스크를 어떻게 수행하는지 설명하지 않고, 이런 주석은 정의부에서 작성한다.
함수 선언부에서는 다음과 같은 것을 주석으로 작성한다.
- input과 output이 무엇인지
- 멤버 함수에 대해 파라미터로 넘겨진 객체 참조자는 메소드 콜 후에도 유효한지, 객체가 free되는지.
- 함수를 호출한 곳에서 free 해야 하는 메모리를 할당 하는지.
- 어떤 파라미터가 NULL이 될 수 있는지.
- 함수가 사용되는 방법에 따라 성능에 영향을 줄 수 있는지
- 함수가 재진입(re-entrant) 되는지
예를 들면,
// Returns an iterator for this table. It is the client's // responsibility to delete the iterator when it is done with it, // and it must not use the iterator once the GargantuanTable object // on which the iterator was created has been deleted. // // The iterator is initially positioned at the beginning of the table. // // This method is equivalent to: // Iterator* iter = table->NewIterator(); // iter->Seek(""); // return iter; // If you are going to immediately seek to another place in the // returned iterator, it will be faster to use NewIterator() // and avoid the extra seek. Iterator* GetIterator() const; |
그러나, 불필요하게 장황하거나, 완전히 명백한 경우는 주석을 달지 않는다. 아래의 예는 “그렇지 않으면 false를 리턴한다” 라는 말이 필요 없다.
// Returns true if the table cannot hold any more entries. bool IsTableFull(); |
생성자와 소멸자에 주석을 달 때, 생성자와 소멸자가 무엇을 하는지 누구나 알기 때문에 “객체를 파괴한다”와 같은 주석은 달지 않는다. 생성자가 파라미터가 포인터인 경우에 포인터에 대한 소유권을 갖는지 등에 대해 주석을 작성한다. 중요하지 않은 주석은 작성하지 않는다.
Function Definitions (함수 정의부)
각 함수 정의부는 함수가 무엇을 하며, 어떻게 해당 job을 수행하는지 주석을 작성한다. 예를 들어, 정의부 주석은 코딩 트릭, 함수 구현 단계 개요, 변수를 사용하지 않고 왜 함수로 구현했는지 등을 설명한다. 혹은 왜 함수의 전반부에서 lock을 획득하고 후반부에서 그것이 필요하지 않은지 등을 설명할 수도 있다.
주의할 점은 함수 선언부의 주석과 중복하지 않는다. 함수가 하는 일을 간단히 언급해도 좋지만 주석의 목적은 어떻게 작성하느냐이다.
일반적으로, 변수의 이름은 그 변수가 무엇을 위한 변수인지 충분히 설명할 수 있어야 한다. 어떤 경우에는 주석이 필요할 수도 있다.
Class Data Members (클래스 데이터 멤버)
각 클래스 멤버(인스턴스 변수 혹은 멤버 변수)는 무엇을 위해 사용되는 변수인지 설명하는 주석을 작성한다. 만약 변수가 특별한 의미로 감시 값(NULL이나 -1)을 갖는다면, 주석을 작성한다.
private: // Keeps track of the total number of entries in the table. // Used to ensure we do not go over the limit. -1 means // that we don't yet know how many entries the table has. int num_total_entries_; |
Global Variables (전역 변수)
데이터 멤버처럼 모든 전역 변수는 그 변수가 무엇이고, 무엇을 위해 사용되는지를 설명하는 주석을 갖는다.
// The total number of tests cases that we run through in this regression test. const int kNumTestCases = 6; |
F. Implementation Comments (구현부 주석)
구현부에서는 트릭적이고, 분명하지 않고, 관심을 끌고, 중요한 부분에 주석을 작성한다.
Class Data Members
트릭적이고 복잡한 코드는 주석이 필요하다.
// Divide result by two, taking into account that x // contains the carry from the add. for (int i = 0; i < result->size(); i++) { x = (x << 8) + (*result)[i]; (*result)[i] = x >> 1; x &= 1; } |
Line Comments
분명하지 않은 라인은 라인 끝에 주석을 작성한다. 라인 끝에 주석은 2 스페이스 공간을 준다.
// If we have enough memory, mmap the data portion too. mmap_budget = max<int64>(0, mmap_budget - index_->length()); if (mmap_budget >= data_size_ && !MmapData(mmap_chunk_bytes, mlock)) return; // Error already logged. |
코드가 무엇을 하는지 설명하는 주석과 리턴할 때 에러는 이미 로그가 되었다는 주석 모두 있다는 것에 주목한다.
연속되는 라인에 주석을 작성하려면 아래와 같이 가독성 있게 작성한다.
DoSomething(); // Comment here so the comments line up. DoSomethingElse(); // Comment here so there are two spaces between // the code and the comment. { // One space before comment when opening a new scope is allowed, // thus the comment lines up with the following comments and code. DoSomethingElse(); // Two spaces before line comments normally. } |
NULL, true/false, 1, 2, 3 …
NULL, boolea, 문자 그대로의 정수값을 함수로 넘길 때, 그것이 무엇인지 주석을 작성하거나, 상수를 사용하여 self-document (자기 설명적)이 되도록 한다.
bool success = CalculateSomething(interesting_value, 10, false, NULL); // 이 파라미터는 뭘까?? |
이렇게 작성한다.
bool success = CalculateSomething(interesting_value, 10, // 디폴트 base 값 false, // 이 함수 호출이 처음이 아님 NULL); // No callback |
또는 상수나 self-describe(자기 설명적)인 변수를 사용한다.
const int kDefaultBaseValue = 10; const bool kFirstTimeCalling = false; Callback *null_callback = NULL; bool success = CalculateSomething(interesting_value, kDefaultBaseValue, kFirstTimeCalling, null_callback); |
Don’ts (하지 말아야 할 것들)
절대 코드 자체를 설명하지 않는다.
// Now go through the b array and make sure that if i occurs, // the next element is i+1. ... // Geez. 이 의미 없는 주석은 뭔가. |
G. Punctuation, Spelling and Grammar (구두점, 스펠링, 문법)
구두점(쉼표, 콜론, 점, ~ 등), 스펠링 문법에 주의를 기울인다. 나쁘게 작성하는 것보다 잘 작성하는 것이 읽기 쉽다.
주석은 보통 적당한 대문자와 적당한 길이를 갖는 완벽한 문장으로 작성한다. 라인 끝에 작성하는 주석과 같은 짧은 주석은 때로 덜 비공식적이지만, 스타일에 맞게 일관성 있게 사용하면 된다. 완벽한 문장은 더 읽기 쉽고, 주석을 더 완전하고, 미완된 생각이 아니라는 것을 보장해 준다.
세미콜론을 사용할 때, 코드 리뷰어가 콤마를 사용하라고 지적하면 짜증나겠지만, 소스 코드를 명료하고 가독성 있는 하이 레벨로 유지시키는 것이 중요하다. 적당한 구두점, 스펠링, 문법은 이런 목적에 도움을 준다.
H. TODO Comments (해야 할 일에 대한 주석)
일시적이고, 단기 솔루션, 완벽하지 않지만 충분한 코드의 경우 TODO 주석을 사용한다.
TODO는 TODO 문자열을 포함하며, 이름과 이메일 주소를 쓰거나, 괄호 내에서 다른 지시자를 쓴다. 콜론은 선택사항이다. 주 목적은 주석을 작성하는 사람에 의해서 검색이 가능하도록 일관성있도록 작성한다. TODO는 자신의 코드를 수정하기 위해서 주석을 작성하지는 않는다.
// TODO(kl@gmail.com): Use a "*" here for concatenation operator. // TODO(Zeke) change this to use relations. |
TODO가 “미래 날짜에 할 일”의 형태라면 특정 날짜(“Fix by November 2005”) 또는 특정 이벤트(“모든 클라이언트가XML 응답을 처리할 수 있을 때 제거한다”)를 확실히 해둔다.
I. Deprecation Comments (비추천? 주석)
Deprecated 인터페이스 포인트에 DEPRECATED 주석으로 표시한다.
인터페이스에 단어 DEPRECATED를 포함하는 주석을 작성하여 비추천(deprecated)할 수 있다. 주석은 인터페이스 선언부 앞에 하거나 선언부와 같은 라인에 작성한다.
DEPRECATED 단어 이후에 이름과 주소를 쓰거나, 괄호 내에 다른 지시자를 쓴다.
Deprecation 주석은 호출한 곳(callsite)을 수정하도록 간단하고 명료하게 작성한다. C++에서는 새로운 인터페이스 포인트를 호출하는 인라인 함수로서 deprecated 함수를 구현할 수 있다.
인터페이스 포인트를 DEPRECATED로 표시한다고 해서 모든 callsite 코드가 변경되지는 않는다. 만약 실제로 deprecated 함수를 사용하지 못하게 하려면, callsite를 직접 수정해야 한다.
새로운 코드는 deprecated 인터페이스 포인트를 호출하지 않아야 하며, 대신 새로운 인터페이스 포인트를 사용한다.