命名(Naming)
最重要的一致性规则是那些管理命名的规则。名称的风格可以立即告诉我们被命名实体是什么类型的东西:类型、变量、函数、常量、宏等,而不需要我们去搜索该实体的声明。我们大脑中的模式匹配引擎很大程度上依赖于这些命名规则。
关于命名的风格规则相当随意,但我们认为在这个领域一致性比个人偏好更重要,所以不管你是否觉得它们合理,规则就是规则。
就以下命名规则而言,“单词”是你用英语书写时不包含内部空格的任何内容。 单词要么全部小写,单词之间用下划线分隔(“snake_case ”),要么混合大小写,每个单词的首字母大写(“camelCase ”或”PascalCase ”)。
选择名称
给事物命名时,要让新读者能理解其目的或意图,即使是与所有者不在同一团队的人。不要担心节省水平空间,因为让新读者立即理解你的代码要重要得多。
考虑名称将被使用的上下文。名称应该是描述性的,即使它在远离使其可用的代码的地方使用。然而,名称不应该通过重复直接上下文中已存在的信息来分散读者的注意力。一般来说,这意味着描述性应与名称的可见范围成正比。在头文件中声明的自由函数应该提及头文件的库,而局部变量可能不应该解释它在哪个函数中。
尽量少用项目外的人可能不知道的缩写(尤其是首字母缩略词)。不要通过删除单词内的字母来缩写。当使用缩写时,优先将其作为一个”单词”来大写,例如
StartRpc() 而不是
StartRPC()。根据经验,如果缩写在维基百科中有列出,那么它可能是可以接受的。请注意,某些普遍已知的缩写是可以的,例如用
i 表示循环索引,用 T 表示模板参数。
你最频繁看到的名称与大多数名称不同;少量”词汇”名称被如此广泛地重用,以至于它们总是在上下文中。这些名称往往是短的甚至是缩写的,它们的完整含义来自明确的长篇文档,而不仅仅来自其定义上的注释和名称中的单词。例如,absl::Status
有一个专门的开发指南页面 ,记录其正确使用方式。你可能不会经常定义新的词汇名称,但如果你这样做了,请进行额外的设计审查,以确保所选名称在广泛使用时效果良好。
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;
}
// Function comment doesn't need to explain that this returns non-OK on
// failure as that is implied by the `absl::Status` return type, but it
// might document behavior for some specific codes.
absl::Status DoSomethingImportant() {
std::string fqdn = ...; // Well-known abbreviation for Fully Qualified Domain Name
return absl::OkStatus();
}
private:
const int kMaxAllowedConnections = ...; // Clear meaning within context
};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;
}
// A return type with a generic name is unclear without widespread education.
Result DoSomethingImportant() {
int cstmr_id = ...; // Deletes internal letters
}
private:
const int kNum = ...; // Unclear meaning within broad scope
};文件名
文件名应全部小写,可以包含下划线(_)或连字符(-)。遵循你的项目使用的约定。如果没有一致的本地模式可遵循,优先使用
“_”。
可接受的文件名示例:
my_useful_class.ccmy-useful-class.ccmyusefulclass.ccmyusefulclass_test.cc // _unittest and _regtest are deprecated.
C++ 文件应该有 .cc 文件扩展名,头文件应该有 .h
扩展名。依赖于在特定位置被文本包含的文件应以 .inc
结尾(另请参阅自包含头文件一节)。
不要使用已存在于 /usr/include 中的文件名,例如 db.h。
一般来说,使你的文件名非常具体。例如,使用 http_server_logs.h 而不是
logs.h。一个非常常见的情况是有一对文件,例如 foo_bar.h 和
foo_bar.cc,定义一个名为 FooBar 的类。
类型名
类型名以大写字母开头,每个新单词首字母大写,不使用下划线: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 { ...概念名
概念名遵循与类型名相同的规则。
变量名
变量(包括函数参数)和数据成员的名称使用
snake_case(全部小写,单词之间用下划线分隔)。类(而非结构体)的数据成员额外带有尾部下划线。例如:a_local_variable、a_struct_data_member、a_class_data_member_。
普通变量名
例如:
std::string table_name; // OK - snake_case.std::string tableName; // Bad - mixed case.类数据成员
类的数据成员,无论是静态的还是非静态的,都像普通非成员变量一样命名,但带有尾部下划线。例外是静态常量类成员,它们应遵循常量命名的规则。
class TableInfo {
public:
...
static const int kTableVersion = 3; // OK - constant naming.
...
private:
std::string table_name_; // OK - underscore at end.
static Pool<TableInfo>* pool_; // OK.
};结构体数据成员
结构体的数据成员,无论是静态的还是非静态的,都像普通非成员变量一样命名。它们没有类中数据成员所具有的尾部下划线。
struct UrlTableProperties {
std::string name;
int num_entries;
static Pool<UrlTableProperties>* pool;
};关于何时使用结构体与类的讨论,请参阅结构体 vs. 类。
常量名
声明为 constexpr 或 const
的变量,且其值在程序持续期间是固定的,命名时以 “k”
开头,后跟混合大小写。在大写不能用于分隔的少数情况下,可以使用下划线作为分隔符。例如:
const int kDaysInAWeek = 7;
const int kAndroid8_0_0 = 24; // Android 8.0.0所有具有静态存储期的此类变量(即静态变量和全局变量,详见存储期 )都应该以这种方式命名,包括静态常量类数据成员和模板中不同实例化可能具有不同值的变量。此约定对于其他存储类的变量(例如自动变量)是可选的;否则适用通常的变量命名规则。例如:
void ComputeFoo(absl::string_view suffix) {
// Either of these is acceptable.
const absl::string_view kPrefix = "prefix";
const absl::string_view prefix = "prefix";
...
}void ComputeFoo(absl::string_view suffix) {
// Bad - different invocations of ComputeFoo give kCombined different values.
const std::string kCombined = absl::StrCat(kPrefix, suffix);
...
}函数名
通常,函数遵循 PascalCase :以大写字母开头,每个新单词首字母大写。
AddTableEntry()
DeleteUrl()
OpenFileOrDie()同样的命名规则适用于作为 API 的一部分暴露且旨在看起来像函数的类和命名空间作用域常量,因为它们是对象而不是函数这一事实是不重要的实现细节。
访问器和修改器(getter 和 setter 函数)可以像变量一样命名,使用
snake_case。它们通常对应于实际的成员变量,但这不是必须的。例如,int count()
和 void set_count(int count)。
命名空间名
命名空间名是 snake_case(全部小写,单词之间用下划线分隔)。
在为命名空间选择名称时,请注意在命名空间外部的头文件中使用时名称必须是完全限定的,因为非限定别名通常是被禁止的。
顶层命名空间必须是全局唯一且可识别的,因此每个命名空间应由单个项目或团队拥有,名称基于该项目或团队的名称。通常,命名空间中的所有代码应在与命名空间同名的一个或多个目录下。
嵌套命名空间应避免使用知名顶层命名空间的名称,尤其是 std 和
absl,因为在 C++
中,嵌套命名空间不能防止与其他命名空间中名称的冲突(参见 TotW
#130 )。
枚举值名
枚举值(无论是有作用域的还是无作用域的枚举)应像常量一样命名,而不是像宏。即使用
kEnumName 而不是 ENUM_NAME。
enum class UrlTableError {
kOk = 0,
kOutOfMemory,
kMalformedInput,
};enum class AlternateUrlTableError {
OK = 0,
OUT_OF_MEMORY = 1,
MALFORMED_INPUT = 2,
};在 2009 年 1 月之前,风格是像宏一样命名枚举值。这导致了枚举值和宏之间的名称冲突问题。因此,改为优先使用常量风格命名。新代码应使用常量风格命名。
模板参数名
模板参数应遵循其类别的命名风格:类型模板参数应遵循类型的命名规则,非类型模板参数应遵循变量或常量的命名规则。
宏名
你真的不打算定义宏,对吧?如果你要定义,它们应该像这样:MY_MACRO_THAT_SCARES_SMALL_CHILDREN_AND_ADULTS_ALIKE。
请参阅宏的描述;一般来说,不应该使用宏。然而,如果它们是绝对必要的,那么它们应该用全大写和下划线命名,并带有项目特定的前缀。
#define MYPROJECT_ROUND(x) ...别名(Aliases)
别名的名称遵循与任何其他新名称相同的原则,应用于别名定义的上下文中,而不是原始名称出现的上下文中。
命名规则的例外
如果你命名的东西类似于现有的 C 或 C++ 实体(或通过互操作的 Rust 实体),那么你可以遵循现有的命名约定方案。
bigopen()
函数名,遵循 open() 的形式
uint
typedef
bigpos
struct 或 class,遵循 pos 的形式
sparse_hash_map
类 STL 实体;遵循 STL 命名约定
LONGLONG_MAX
常量,如 INT_MAX