기술 노트/Google C++ Style Guide

Google C++ Style Guide(2024) - 9장 명명 규칙(Naming)

anothel 2024. 11. 12. 19:02

9장 명명 규칙(Naming)

네이밍 규칙에서 가장 중요한 일관성 규칙은 이름을 통해 즉시 그 이름이 어떤 종류의 엔티티인지 알 수 있게 하는 것이다. 네이밍 스타일을 통해 타입인지, 변수인지, 함수인지, 상수인지, 매크로인지 등을 선언을 따로 확인하지 않고도 쉽게 알 수 있어야 한다. 우리 두뇌의 패턴 매칭 엔진은 이러한 네이밍 규칙에 크게 의존한다.

네이밍 규칙은 다소 임의적일 수 있지만, 이 영역에서는 개인의 선호보다는 일관성이 더 중요하다. 규칙이 합리적이라고 느끼지 않더라도, 규칙은 규칙이므로 따라야 한다.

주요 원칙

  • 타입명
    • 일반적으로 PascalCase 사용 (예: MyClass, AnotherType).
  • 변수명 및 함수명
    • 소문자로 시작하고 단어마다 대문자로 표기하는 camelCase 사용 (예: myVariable, doSomething).
  • 상수명
    • 대문자와 언더스코어를 사용하여 구분 (예: MAX_COUNT, DEFAULT_SIZE).
  • 매크로
    • 모든 글자를 대문자로 표기하고, 단어 사이를 언더스코어로 구분 (예: DEBUG_MODE, ENABLE_FEATURE).

이 네이밍 규칙을 준수함으로써, 코드를 읽는 사람들이 코드의 구조와 목적을 빠르게 이해할 수 있으며, 코딩 표준을 일관되게 유지하여 코드베이스의 가독성과 유지보수성을 높일 수 있다.

9.1 일반적인 네이밍 규칙 (General Naming Rules)

가독성을 최적화하고, 다른 팀의 사람에게도 명확하게 이해될 수 있는 이름을 사용해야 한다.

  • 목적 또는 의도를 나타내는 이름을 사용해야 하며, 가로 공간 절약에 대해 걱정할 필요가 없다. 새 독자가 즉시 이해할 수 있도록 만드는 것이 훨씬 더 중요하다. 특히 프로젝트 외부의 사람들이 잘 모를 약어나 두문자어(abbreviations)를 최소화하고, 단어 내부의 글자를 삭제하여 줄여 쓰지 않는다. 일반적으로 약어는 위키백과에 등재된 약어라면 사용해도 무방하다.
  • 범위가 넓은 변수일수록 더 구체적인 이름을 사용하는 것이 좋다. 예를 들어, 5줄 길이의 함수 내에서는 n이 적절할 수 있지만 클래스 범위에서는 너무 모호할 수 있다.

예시)

class MyClass {
 public:
  int CountFooErrors(const std::vector<Foo>& foos) {
    int n = 0;  // 제한된 범위와 문맥상 의미가 명확함
    for (const auto& foo : foos) {
      ...
      ++n;
    }
    return n;
  }
  void DoSomethingImportant() {
    std::string fqdn = ...;  // Fully Qualified Domain Name을 나타내는 잘 알려진 약어
  }
 private:
  const int kMaxAllowedConnections = ...;  // 문맥상 의미가 명확함
};

나쁜 예

class MyClass {
 public:
  int CountFooErrors(const std::vector<Foo>& foos) {
    int total_number_of_foo_errors = 0;  // 제한된 범위와 문맥상 지나치게 장황함
    for (int foo_index = 0; foo_index < foos.size(); ++foo_index) {  // 관례적으로 `i`를 사용
      ...
      ++total_number_of_foo_errors;
    }
    return total_number_of_foo_errors;
  }
  void DoSomethingImportant() {
    int cstmr_id = ...;  // 단어 내부의 글자를 삭제하여 줄인 약어 (권장하지 않음)
  }
 private:
  const int kNum = ...;  // 넓은 범위에서 의미가 불명확함
};

다음과 같이 일반적으로 널리 알려진 약어는 사용해도 좋다:

  • i는 반복 변수로, T는 템플릿 매개변수로 많이 사용됨.

추가 원칙

  • 단어의 정의
    • 여기서 "단어"란 내부 공백 없이 영어로 작성할 수 있는 모든 것을 의미한다. 약어, 두문자어 등도 단어로 간주한다.
  • 혼합형(카멜 케이스, 파스칼 케이스)으로 작성된 이름에서 약어는 하나의 단어로 간주하여 단일 대문자로 표기한다. 예를 들어, StartRPC()보다는 StartRpc()와 같이 표기하는 것을 권장한다.
  • 템플릿 매개변수는 해당하는 카테고리의 네이밍 스타일을 따른다. 타입 템플릿 매개변수는 타입명 규칙을, 비타입 템플릿 매개변수는 변수명 규칙을 따른다.

이렇게 함으로써 코드의 가독성을 높이고, 코드베이스의 일관성을 유지할 수 있다.

9.2 파일 이름 규칙 (File Names)

파일명은 모두 소문자로 작성하며, 언더스코어(_)나 대시(-)를 포함할 수 있다. 프로젝트에서 사용하는 관례를 따르고, 별도의 일관된 패턴이 없으면 언더스코어(_)를 권장한다.

사용 가능한 파일 이름 예시

  • my_useful_class.cc
  • my-useful-class.cc
  • myusefulclass.cc
  • myusefulclass_test.cc // _unittest 및 _regtest는 더 이상 권장하지 않음.

확장자 규칙

  • C++ 파일은 .cc 확장자를 사용하고, 헤더 파일은 .h 확장자를 사용해야 한다.
  • 특정 위치에서 텍스트 포함이 필요한 파일은 .inc 확장자로 끝나도록 한다. (자체 포함 헤더에 관한 부분도 참고)

주의 사항

  • /usr/include 경로에 이미 존재하는 파일명(예: db.h)은 사용하지 않는다.

파일명 지정 권장사항

파일명은 구체적으로 작성하는 것이 좋다. 예를 들어, logs.h보다는 http_server_logs.h처럼 구체적으로 명명하는 것이 좋다. 일반적으로 클래스 파일은 foo_bar.h 및 foo_bar.cc 형식으로 한 쌍을 이루며, **클래스 이름은 FooBar**로 정의하는 것이 일반적이다.

이와 같은 규칙을 통해 파일 이름에서 파일의 목적과 역할을 쉽게 파악할 수 있도록 한다.

9.3 타입 이름 규칙 (Type Names)

타입 이름은 첫 글자를 대문자로 시작하고, 각 단어의 첫 글자를 대문자로 표기하며 언더스코어(_)는 사용하지 않는다. 이는 클래스, 구조체, 타입 별칭, 열거형, 템플릿 파라미터 등 모든 타입에 적용된다.

예시

  • 클래스 및 구조체
class UrlTable { ... }
class UrlTableTester { ... }
struct UrlTableProperties { ... }
  • typedef
typedef hash_map<UrlTableProperties *, std::string> PropertiesMap;
  • using 별칭
using PropertiesMap = hash_map<UrlTableProperties *, std::string>;
  • 열거형
enum class UrlTableError { ... }

이와 같은 일관된 규칙을 통해 코드의 가독성을 높이고, 타입 명명 방식이 직관적으로 파악될 수 있도록 한다.

9.4 개념 이름 규칙 (Concept Names)

개념(Concept) 이름은 타입 이름과 동일한 규칙을 따른다. 첫 글자를 대문자로 시작하며, 각 단어의 첫 글자도 대문자로 표기하고 언더스코어(_)는 사용하지 않는다.

예시

template <typename T>
concept Hashable = requires(T a) {
    { std::hash<T>{}(a) } -> std::convertible_to<size_t>;
};

template <typename T>
concept Printable = requires(T a) {
    { std::cout << a } -> std::same_as<std::ostream&>;
};

이러한 명명 규칙을 통해 코드 내에서 개념의 의도를 명확히 하고, 일관성을 유지하여 가독성을 높인다.

9.5 변수 이름 규칙 (Variable Names)

변수, 함수 매개변수, 그리고 데이터 멤버의 이름은 스네이크 케이스(snake_case)를 사용하여 모두 소문자로 작성하고, 단어 사이에 언더스코어(_)를 사용한다.

  • 클래스의 데이터 멤버는 일반 변수와 동일한 규칙을 따르되, 변수명 끝에 언더스코어(_)를 추가한다.
  • 구조체의 데이터 멤버는 클래스와 달리 끝에 언더스코어(_)를 붙이지 않는다.

일반 변수 이름 예시

std::string table_name;  // OK - 스네이크 케이스 사용.
std::string tableName;   // Bad - 혼합 케이스 사용.

클래스 데이터 멤버 예시

class TableInfo {
  ...
 private:
  std::string table_name_;       // OK - 끝에 언더스코어.
  static Pool<TableInfo>* pool_; // OK - 끝에 언더스코어.
};

구조체 데이터 멤버 예시

struct UrlTableProperties {
  std::string name;               // OK - 스네이크 케이스.
  int num_entries;                // OK - 스네이크 케이스.
  static Pool<UrlTableProperties>* pool;  // OK - 언더스코어 없음.
};

주의 사항

클래스와 구조체의 사용 시기는 Structs vs. Classes 섹션에서 추가 논의한다.

9.6 Constant Names

프로그램 동안 값이 고정된 constexpr 또는 const 변수는 'k'로 시작하며, 각 단어의 첫 글자를 대문자로 하는 혼합 케이스(mixed case)를 사용한다. 드물게 구분이 어려운 경우 언더스코어(_)를 사용할 수 있다.

상수 이름 예시

const int kDaysInAWeek = 7;
const int kAndroid8_0_0 = 24;  // Android 8.0.0

모든 정적 저장 기간(static storage duration)을 가지는 상수(예: 전역 변수 및 static 변수)는 이러한 규칙을 따른다. 템플릿의 경우에도 각 인스턴스화가 다른 값을 가질 수 있는 경우 이 규칙을 준수해야 한다.

기타 저장 클래스 예시

자동 저장 기간(예: 함수 내부 변수)의 상수는 이 규칙이 필수는 아니며, 일반 변수 규칙을 적용할 수 있다.

void ComputeFoo(absl::string_view suffix) {
  // 둘 다 허용됨.
  const absl::string_view kPrefix = "prefix";
  const absl::string_view prefix = "prefix";
  ...
}

잘못된 예시

kCombined는 각 호출 시 다른 값을 가지므로 상수 이름 규칙을 적용하는 것이 부적절하다.

void ComputeFoo(absl::string_view suffix) {
  // Bad - ComputeFoo의 각 호출에서 kCombined가 다른 값을 가짐.
  const std::string kCombined = absl::StrCat(kPrefix, suffix);
  ...
}

9.7 함수 이름 규칙 (Function Names)

일반 함수는 각 단어의 첫 글자를 대문자로 하는 **혼합 케이스(mixed case)**로 작성한다. 접근자와 변경자(accessor와 mutator) 함수는 변수처럼 이름을 지을 수 있다.

일반 함수 예시

일반적으로 함수 이름은 첫 글자가 대문자로 시작하고 각 단어의 첫 글자를 대문자로 쓴다.

AddTableEntry()
DeleteUrl()
OpenFileOrDie()

API의 일부로 제공되는 클래스 또는 네임스페이스 범위 상수도 함수처럼 보이도록 동일한 규칙을 따른다. 상수임이 중요한 구현 세부사항이 아니기 때문이다.

접근자와 변경자 함수 예시

접근자(get)와 변경자(set) 함수는 변수처럼 소문자와 스네이크 케이스(snake_case)를 사용할 수 있다. 실제 멤버 변수와 일치하는 경우가 많지만 필수는 아니다.

int count()             // 접근자 함수
void set_count(int count)  // 변경자 함수

9.8 네임스페이스 이름 규칙 (Namespace Names)

네임스페이스 이름은 모두 소문자로 작성하고, **단어 사이에는 밑줄(underscore)**을 사용한다. 최상위 네임스페이스는 프로젝트 이름을 기준으로 한다. 중첩 네임스페이스가 잘 알려진 최상위 네임스페이스와 충돌하지 않도록 주의한다.

최상위 네임스페이스 이름

최상위 네임스페이스 이름은 보통 해당 네임스페이스에 포함된 코드의 프로젝트 또는 팀 이름이어야 한다. 해당 네임스페이스에 포함된 코드는 네임스페이스 이름과 일치하는 기본 디렉토리(또는 하위 디렉토리)에 있어야 한다.

  • 네임스페이스 이름도 변수 이름처럼 약어를 사용하지 않는다.
  • 네임스페이스 내의 코드에서 네임스페이스 이름을 자주 언급할 필요는 없으므로, 약어를 사용할 필요가 없다.

충돌 방지

  • 잘 알려진 최상위 네임스페이스와 이름이 겹치지 않도록 중첩 네임스페이스는 고유한 프로젝트 식별자를 사용하는 것이 좋다. 예를 들어, websearch::index, websearch::index_util처럼 프로젝트 식별자가 포함된 이름을 사용하는 것이 websearch::util과 같은 충돌 가능성이 높은 이름을 사용하는 것보다 낫다.
  • 깊은 중첩 네임스페이스는 피한다.

내부 네임스페이스

  • 내부 네임스페이스는 관련된 코드가 같은 내부 네임스페이스에 추가되며 충돌 가능성이 높아질 수 있다.
  • 이러한 경우, 파일 이름을 사용하여 고유한 내부 이름을 만드는 것이 도움이 된다. 예를 들어, websearch::index::frobber_internal은 frobber.h에서 사용하도록 고유한 내부 네임스페이스로 지정할 수 있다.

9.9 열거자 이름 (Enumerator Names)

열거자는 상수처럼 이름을 지어야 하며, 매크로처럼 작성하지 말아야 한다. 즉, ENUM_NAME처럼 대문자와 언더스코어 형식을 피하고, 대신 kEnumName 스타일을 사용하는 것이 권장된다.

enum class UrlTableError {
  kOk = 0,
  kOutOfMemory,
  kMalformedInput,
};

과거에는 열거자 이름을 매크로처럼 대문자 스타일로 작성하는 방식이 사용되었지만, 이로 인해 열거자와 매크로 간에 이름 충돌 문제가 발생할 수 있었다. 이러한 문제를 해결하고자, 2009년 이후 상수 스타일의 명명 규칙으로 전환되었다. 이제 새로운 코드에서는 반드시 상수 스타일을 사용해야 한다.

아래는 구식 대문자 형식으로 작성된 예시로, 새 코드 작성에서는 피해야 한다.

enum class AlternateUrlTableError {
  OK = 0,
  OUT_OF_MEMORY = 1,
  MALFORMED_INPUT = 2,
};

요약하자면, 상수 스타일을 사용함으로써 코드 가독성을 높이고 잠재적 충돌을 방지할 수 있다.

9.10 매크로 이름 (Macro Names)

정말 매크로를 정의할 계획이 아니라면 좋겠지만, 필요하다면 다음과 같이 작성해야 한다.

MY_MACRO_THAT_SCARES_SMALL_CHILDREN_AND_ADULTS_ALIKE.

매크로는 일반적으로 사용하지 않는 것이 좋으며, 다른 방식으로 대체 가능한 경우 가급적 피해야 한다. 그러나 부득이하게 매크로를 사용해야 할 때는 반드시 대문자와 언더스코어로 이름을 짓고, 프로젝트에 특화된 접두사를 포함해야 한다.

예시)

#define MYPROJECT_ROUND(x) ...

매크로는 코드 가독성을 해치고 유지보수를 어렵게 만들 수 있으므로, 특별한 이유가 없는 한 사용하지 않는 것이 권장된다.

9.11 네이밍 규칙 예외 (Exceptions to Naming Rules)

기존 C 또는 C++ 엔티티와 유사한 기능을 수행하는 항목을 이름 짓는 경우, 기존의 네이밍 규칙을 따를 수 있다. 이는 일관성과 가독성을 높이고 기존 코드와의 호환성을 유지하는 데 유리하다.

  • 기존 함수와 유사한 이름
    • bigopen()은 open() 함수 형식을 따른다.
  • 기존 타입 정의 형식
    • uint는 typedef 형식을 따른다.
  • 기존 구조체/클래스 형식
    • bigpos는 pos의 형식을 따른다.
  • STL 유사 구조의 네이밍
    • sparse_hash_map은 STL의 명명 규칙을 따른다.
  • 기존 상수 형식
    • LONGLONG_MAX는 INT_MAX와 같은 형식을 따른다.

이와 같은 예외 사항은 특정 라이브러리나 언어 규약과 유사성을 유지하여 코드 가독성과 유지보수를 쉽게 한다.


참조URL

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

728x90