본문 바로가기
💻 programming/clean code

[c++] google c++ style guide

by 연구원-A 2020. 12. 15.
반응형

Google C++ Style Guide

본 장에서는 Google C++ 스타일 가이드에 대해 요약하고 있습니다.

Google C++ Style Guide

cpplint는 정적 코드 체커 프로그램입니다. cpplint를 이용하면 작성한 소스코드가 Google C++ 스타일 가이드를 잘 따르는지 체크할 수 있습니다.

cpplint/cpplint

cf. vscode cpplint extension

Contents

Background

C++는 구글 오픈 소스 프로젝트에서 많이 사용되는 주요 프로그래밍 언어들 중 하나입니다. 모든 C++ 프로그래머가 알고 있듯이 C++는 매우 강력한 기능을 제공하고 있습니다. 그러나 강력한 기능을 제공하기 위한 언어의 복잡성 때문에 때로는 버그를 생산하기도 하고, 읽기 어려운 코드가 된다거나 유지 보수가 어려워지는 경우가 많습니다.

이 가이드의 목적은 C++ 코드를 작성하면서 무엇을 해야하고, 또 무엇을 하면 안되는지 설명하는 데에 있습니다. 이러한 규칙들은 코드를 관리 가능하게 하고, 프로그래머들이 C++의 기능을 생산적으로 사용할 수 있게 할 것입니다.


Header file

Self-contained Headers

The #define Guard

다중 참조를 막기 위해 #define을 이용해야 합니다. 포맷은 __H을 이용합니다. 예를 들어 foo/src/bar/baz.h 파일은 다음의 가드를 이용할 수 있습니다.

#ifndef FOO_BAR_BAZ_H_
#define FOO_BAR_BAZ_H_

...

#endif  // FOO_BAR_BAZ_H_

Include What You Use

Forward Declarations

Inline Functions

Names and Order of Includes

소스파일을 작성할 때 C 시스템, C++ 표준 라이브러리와 같이 많은 헤더 파일을 참조하게 됩니다. 헤더 파일을 참조할 때 다음의 순서로 작성해야 합니다.

  1. dir2/foo2.h.
  2. A blank line
  3. C system headers (more precisely: headers in angle brackets with the .h extension), e.g., <unistd.h>, <stdlib.h>.
  4. A blank line
  5. C++ standard library headers (without file extension), e.g., , .
  6. A blank line
  7. Other libraries' .h files.
  8. Your project's .h files.
#include "foo/server/fooserver.h"

#include <sys/types.h>
#include <unistd.h>

#include <string>
#include <vector>

#include "base/basictypes.h"
#include "base/commandlineflags.h"
#include "foo/server/bar.h"

Scoping

Namespaces

Unnamed Namespaces and Static Variables

Nonmember, Static member, and Global Functions

Local Variable

가능하면 지역 변수의 초기화는 선언과 가까워야 합니다. 변수를 선언하고 나서 값을 할당하기 보다는 선언과 동시에 초기화하는 것이 좋습니다.

int i;
i = f();      // Bad -- initialization separate from declaration.
int j = g();  // Good -- declaration has initialization.

Static and Global Varaiable

thread_local Variables


Classes

Doing Work in Constructors

생성자에서 가상 함수를 호출하는 것을 피해야 합니다.

Implicit Conversions

변환 생성자 (Conversion Constructor)를 호출할 때 암시적 변환을 사용하지 말아야 합니다. explicit 키워드를 이용하여 명시적으로 생성자를 호출해야 합니다.

class Foo {
  explicit Foo(int x, double y);
  ...
};

void Func(Foo f);

Func({42, 3.14}); // Error

Copyable and Movable Types

Structs vs. Classes

Structs vs. Pairs and Tuples

Operator Overloading

Access Control

Declaration Order


Functions

Inputs and Outputs

Write Short Functions

Function Overloading

Default Arguments

Trailing Return Type Syntax


Google-Specific Magic

Ownership and Smart Pointers

cpplint


Other C++ Features

Rvalue Reference

Friends

Exceptions

noexcept

Run-Time Type Information (RTTI)

Casting

Streams

Preincrement and Predecrement

Use of const

Use of constexpr

Integer Types

64-bit Portability

Preprocessor Macros

0 and nullptr/NULL

sizeof

Type Deduction (including auto)

Class Template Argument Deduction

Designated Initializers

Lambda Expressions

Template Metaprogramming

Boost

std::hash

Other C++ Features

Nonstandard Extensions

Aliases


Naming

General Naming Rule

설령 다른 팀 사람들이 코드를 읽더라도 이해할 수 있는 명확한 이름을 사용해야 가독성을 높일 수 있습니다.

우선 객체의 목적이나 의도를 설명할 수 있는 이름을 사용해야 합니다. 이름이 길어지는 것을 두려워 할 필요는 없습니다. 새로운 프로그래머가 코드를 직관적으로 이해할 수 있도록 하는 것이 이름의 길이보다 훨씬 더 중요합니다.

  • 약어 사용을 최대한 피해야 합니다 (위키피디아에 나열된 약어는 대부분 사용할 수 있습니다)
  • 단어에 사용되는 문자를 임의대로 줄이지 않아야 합니다
class MyClass {
 public:
  int CountFooErrors(const std::vector<Foo>& foos) {
    int total_number_of_foo_errors = 0;  // Overly verbose given limited scope and context
    for (int foo_index = 0; foo_index < foos.size(); ++foo_index) {  // Use idiomatic `i`
      ...
      ++total_number_of_foo_errors;
    }
    return total_number_of_foo_errors;
  }
  void DoSomethingImportant() {
    int cstmr_id = ...;  // Deletes internal letters
  }
 private:
  const int kNum = ...;  // Unclear meaning within broad scope
};

여기서 일반적으로 널리 알려진 약어는 사용해도 괜찮습니다. 예를 들어 반복문에서 사용되는 i 변수나 템플릿 파라미터에 사용되는 T와 같은 변수는 사용해도 좋습니다.

class MyClass {
 public:
  int CountFooErrors(const std::vector<Foo>& foos) {
    int n = 0;  // Clear meaning given limited scope and context
    for (const auto& foo : foos) {
      ...
      ++n;
    }
    return n;
  }
  void DoSomethingImportant() {
    std::string fqdn = ...;  // Well-known abbreviation for Fully Qualified Domain Name
  }
 private:
  const int kMaxAllowedConnections = ...;  // Clear meaning within context
};

File Names

파일 이름은 반드시 모두 소문자로 사용해야 하고 underscore() 또는 dash (-)를 포함할 수 있습니다. underscore와 dash 중 어떤 것을 사용해도 좋으므로 프로젝트에서 사용하고 있는 규칙을 따르면 됩니다 .만약 프로젝트에서 사용중인 일관된 규칙이 없다면 ""를 사용하는 것이 좋습니다.

맞게 작성된 파일 이름들은 다음과 같습니다.

  • my_useful_class.cc

  • myusefulclass.cc

  • myusefulclass_test.cc // _unittest 와 _regtest 는 삭제되었습니다

    C++ 파일의 확장자는 .cc를 사용해야 하고 헤더 파일은 .h 확장자를 사용해야 합니다.

    이미 /usr/include 에 정의된 파일 이름을 사용해서는 안됩니다.

    파일 이름은 의도와 목적을 설명할 수 있도록 구체적이어야 합니다. 단순히 log.h 보다는 http_server_log.h가 더 낫습니다.

Type Names

타입 이름에 사용된 단어의 시작은 underscore가 없는 대문자여야 합니다. 예를 들어 MyExcitingClass, MyExcitingEnum 등이 될 수 있습니다.

// classes and structs
class UrlTable { ...
class UrlTableTester { ...
struct UrlTableProperties { ...

// typedefs
typedef hash_map<UrlTableProperties *, std::string> PropertiesMap;

// using aliases
using PropertiesMap = hash_map<UrlTableProperties *, std::string>;

// enums
enum class UrlTableError { ...

Variable Names

변수 이름은 undercore가 포함된 소문자로 작성해야 합니다. 구조체가 아닌 클래스의 멤버 변수는 trailing underscore를 붙여서 구분합니다

std::string table_name; // OK

Class Data Memebers

class TableInfo {
...
  private:
      std::string table_name_; // OK - underscore at end.
        static Pool<TableInfo>* pool_; // OK
};

Constant Names

프로그램 동작중에 고정된 값을 가지는 constexpr 또는 const로 선언된 변수는 k로 시작하는 mixed case 이름을 사용해야 합니다.

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

Function Names

대문자로 시작하는 mixed case 이름을 사용합니다.

AddTableEntry()
DeleteUrl()
OpenFileOrDie()

Namespace Names

underscore를 사용한 소문자를 사용합니다. 네임스페이스 간 충돌을 피하기 위해 top-level 네임스페이스는 프로젝트 또는 팀 이름을 사용해야 합니다.

Enumerator Names

Enumerator는 macro가 아닌 constants처럼 정의합니다. 예를 들어 ENUM_NAME 대신 kEnumName과 같이 사용해야 합니다.

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

2009년 1월 이전까지는 enumerator를 macro처럼 사용했으나, 실제 macro와 이름 충돌이 발생하는 경우가 있었습니다. 새로운 코드를 작성할 때는 constant-style 이름을 사용해야 합니다.

Macro Names

Exceptions to Naming Rule


Comments

Comment Style

한 가지 방법만 통일성 있게 사용한다면 // 또는 /* */ 을 사용할 수 있습니다. (// 가 더 일반적)


Formatting

Line Length

코드를 작성할 때 한 라인의 길이는 80 글자를 넘으면 안됩니다. 사실 이 규칙이 다소 논란이 된다는 것을 알고 있지만 우리는 통일된 일관성이 더 중요하다고 생각하고 있습니다.

다음의 경우에는 80 글자를 넘을 수 있습니다.

  • 주석을 80 글자 단위로 나누면 가독성을 심각하게 저해시키는 경우 (e.g. 프로그램 명령어 설명 또는 특정 URL과 같이 복사-붙여넣기가 필요한 경우)
  • 80 글자를 넘는 raw-string 문자열이 있는 경우
  • 헤더 파일 가이드
  • using-declaration

Non-ASCII Chracters

Spaces vs. Tabs

오직 스페이스만 사용해야 합니다. 인덴트는 2 스페이스를 사용합니다.

tab을 사용해서는 안되며, tab 키를 스페이스로 자동변환하도록 에디터를 설정하는 것이 좋습니다.

Function Declarations and Definitions

함수의 반환 타입은 함수와 같은 라인에 있어야 합니다.

ReturnType ClassName::FunctionName(Type par_name1, Type par_name2) {
  DoSomething();
  ...
}

만약 한 줄에 다 넣기 어려운 경우 다음과 같이 쓸 수 있습니다.

ReturnType ClassName::ReallyLongFunctionName(Type par_name1, Type par_name2,
                                             Type par_name3) {
  DoSomething();
  ...
}

함수의 이름이 너무 길어서 파라미터를 다 적을 수 없는 경우는 다음과 같이 씁니다.

ReturnType LongClassName::ReallyReallyReallyLongFunctionName(
    Type par_name1,  // 4 space indent
    Type par_name2,
    Type par_name3) {
  DoSomething();  // 2 space indent
  ...
}

Floating-point Literals

Functions Calls

Braced Initializer List Format

Conditions

if-else와 같은 조건문은 다음과 같이 사용합니다. 잘못된 예시는 다음과 같습니다.

if(condition) {              // Bad - space missing after IF
if ( condition ) {           // Bad - space between the parentheses and the condition
if (condition){              // Bad - space missing before {
if(condition){               // Doubly bad

if (int a = f();a == 10) {   // Bad - space missing after the semicolon

좋은 예시는 다음과 같습니다

if (condition) {                   // no spaces inside parentheses, space before brace
  DoOneThing();                    // two space indent
  DoAnotherThing();
} else if (int a = f(); a != 3) {  // closing brace on new line, else on same line
  DoAThirdThing(a);
} else {
  DoNothing();
}

Loops and Switch Statements

switch (var) {
  case 0: {  // 2 space indent
    ...      // 4 space indent
    break;
  }
  case 1: {
    ...
    break;
  }
  default: {
    assert(false);
  }
}
while (condition) {
  // Repeat test until it returns false.
}
for (int i = 0; i < kSomeNumber; ++i) {}  // Good - one newline is also OK.
while (condition) continue;  // Good - continue indicates no logic.
반응형

댓글