软件工程基础课-个人项目-代码风格规范


代码风格规范

摘录自Google C++风格指南,并尽量以此为规范,但不完全一样。


一、命名约定

1.1 文件命名

总述
文件名全部小写,可以加下划线_,但不可以使用横线-
说明
可接受的文件名示例:

  • input_ouput.cpp
  • sudoku.cpp
  • stdafx.h

C++文件以.cpp结尾,头文件以.h结尾。

1.2 类型命名

总述
类型名称的每个单词的首字母大写,不包含下划线。
说明
所有类型命名——类、结构体、类型定义(typedef)、枚举、类型模板参数——均使用相同约定,即以大写字母开始,每个单词首字母均大写,不使用下划线。例如:

// 类和结构体
class Sudoku {...};
class Input {..};
struct SonNode {..};

// 类型定义
typedef hash_map<char *, string> NameMap;

// using 别名
using NameMap = hash_map<char *, string>;

// 枚举
enum Today {..};

1.3 变量命名

总述
变量(包括函数参数)和数据成员名一律小写,单词之间用下划线连接。类的成员变量以下划线结尾,但结构体的就不用。如:a_local_variable, a_struct_data_member, a_class_data_member_.
说明

1.3.1 普通变量命名

例如:

int num_table; // 好 - 用下划线
int numtable;  // 中 - 全小写
int numTable;  // 差 - 混合大小写

1.3.2 类数据成员

不管是静态的还是非静态的,类数据成员都可以和普通变量一样,但要在名称末尾接下划线。

class TableInfo 
{
    ..
private:
    string table_name_;  // 好 - 后加下划线.
    string tablename_;   // 中.
    static Pool<TableInfo>* pool_;  // 好.
};

1.3.3 结构体变量

不管是静态的还是非静态的, 结构体数据成员都可以和普通变量一样, 但不用像类那样接下划线。

struct UrlTableProperties 
{
    string name;
    int num_entries;
    static Pool<UrlTableProperties>* pool;
};

1.4 常量命名

总述
声明为constexprconst的变量, 或在程序运行期间其值始终保持不变的, 命名时以 “k” 开头, 大小写混合. 例如:

const int kDaysInAWeek = 7;

说明
所有具有静态存储类型的变量 (例如静态变量或全局变量) 都应当以此方式命名。对于其他存储类型的变量, 如自动变量等, 这条规则是可选的。如果不采用这条规则, 就按照一般的变量命名规则。

1.5 函数命名

总述
常规函数使用大小写混合, 成员函数首字母小写: MyExcitingFunction()MyExcitingMethod()myExcitingMethod()
说明
一般来说,函数名的每个单词首字母大写(即“驼峰命名法”或“帕斯卡命名法”),没有下划线。

1.6 命名空间命名

总述
命名空间以小写字母命名。最高级命名空间的名字取决于项目名称。要注意避免嵌套命名空间的名字之间和常见的顶级命名空间的名字之间发生冲突。
顶级命名空间的名称应当是项目名或者是该命名空间中的代码所属的团队的名字。命名空间中的代码,应当存放于和命名空间的名字匹配的文件夹或其子文件夹中。
注意不使用缩写作为名称的规则同样适用于命名空间。命名空间中的代码极少需要涉及命名空间的名称,因此没有必要在命名空间中使用缩写。
要避免嵌套的命名空间与常见的顶级命名空间发生名称冲突。由于名称查找规则的存在, 命名空间之间的冲突完全有可能导致编译失败。尤其是,不要创建嵌套的std命名空间。建议使用更独特的项目标识符(websearch::indexwebsearch::index_util) 而非常见的极易发生冲突的名称 (比如 websearch::util)。
对于internal命名空间, 要当心加入到同一internal命名空间的代码之间发生冲突(由于内部维护人员通常来自同一团队,因此常有可能导致冲突)。在这种情况下,请使用文件名以使得内部名称独一无二 (例如对于frobber.h,使用websearch::index::frobber_internal)。

1.7 枚举命名

总述
枚举的命名应当和常量一致:kEnumName或是ENUM_NAME

1.8 宏命名

总述
全部大写,使用下划线:MY_MACRO_THAT_SCARES_SMALL_CHILDREN


二、 注释

2.1 文件注释

总述
文件注释描述了该文件的内容。如果一个文件只声明,或实现,或测试了一个对象,并且这个对象已经在它的声明处进行了详细的注释,那么就没必要再加上文件注释。除此之外的其他文件都需要文件注释。

2.2 类注释

总述
每个类的定义都要附带一份注释,描述类的功能和用法,除非它的功能相当明显。

// Iterates over the contents of a GargantuanTable.
// Example:
//    GargantuanTableIterator* iter = table->NewIterator();
//    for (iter->Seek("foo"); !iter->done(); iter->Next()) {
//      process(iter->key(), iter->value());
//    }
//    delete iter;

class GargantuanTableIterator 
{
    //..
};

说明
类注释应当为读者理解如何使用与何时使用类提供足够的信息,同时应当提醒读者在正确使用此类时应当考虑的因素。如果类有任何同步前提,请用文档说明。如果该类的实例可被多线程访问,要特别注意文档说明多线程环境下相关的规则和常量使用。
如果你想用一小段代码演示这个类的基本用法或通常用法,放在类注释里也非常合适。
如果类的声明和定义分开了(例如分别放在了.h.cpp 文件中),此时,描述类用法的注释应当和接口定义放在一起,描述类的操作和实现的注释应当和实现放在一起.

2.3 函数注释

总述
函数声明处的注释描述函数功能;定义处的注释描述函数实现。
说明

2.3.1 函数声明

基本上每个函数声明处前都应当加上注释,描述函数的功能和用途。只有在函数的功能简单而明显时才能省略这些注释(例如,简单的取值和设值函数)。注释使用叙述式(“Opens the file”)而非指令式(“Open the file”);注释只是为了描述函数,而不是命令函数做什么。通常,注释不会描述函数如何工作。那是函数定义部分的事情。

函数声明处注释的内容:

  • 函数的输入输出
  • 对类成员函数而言: 函数调用期间对象是否需要保持引用参数, 是否会释放这些参数
  • 函数是否分配了必须由调用者释放的空间
  • 参数是否可以为空指针
  • 是否存在函数使用上的性能隐患
  • 如果函数是可重入的, 其同步前提是什么?

举例如下:

//* 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();

注释函数重载时,注释的重点应该是函数中被重载的部分,而不是简单的重复被重载的函数的注释。多数情况下,函数重载不需要额外的文档,因此也没有必要加上注释。
注释构造/析构函数时,切记读代码的人知道构造/析构函数的功能,所以“销毁这一对象”这样的注释是没有意义的。你应当注明的是注明构造函数对参数做了什么(例如,是否取得指针所有权)以及析构函数清理了什么。如果都是些无关紧要的内容,直接省掉注释。析构函数前没有注释是很正常的。

2.3.2 函数定义

如果函数的实现过程中用到了很巧妙的方式,那么在函数定义处应当加上解释性的注释。例如,你所使用的编程技巧,实现的大致步骤,或解释如此实现的理由。举个例子,你可以说明为什么函数的前半部分要加锁而后半部分不需要。
不要从.h文件或其他地方的函数声明处直接复制注释。简要重述函数功能是可以的,但注释重点要放在如何实现上。

2.4 变量注释

总述
通常变量名本身足以很好说明变量用途。某些情况下,也需要额外的注释说明。
说明

2.4.1 类数据成员

每个类数据成员(也叫实例变量或成员变量)都应该用注释说明用途。如果有非变量的参数(例如特殊值、数据成员之间的关系、生命周期等)不能够用类型与变量名明确表达,则应当加上注释。然而,如果变量类型与变量名已经足以描述一个变量,那么就不再需要加上注释。
特别地, 如果变量可以接受NULL-1等警戒值,须加以说明。比如:

private:
// Used to bounds-check table accesses. -1 means
// that we don't yet know how many entries the table has.
int num_total_entries_;

2.4.2 全局变量

和数据成员一样,所有全局变量也要注释说明含义及用途,以及作为全局变量的原因。比如:

// The total number of tests cases that we run through in this regression test.
const int kNumTestCases = 6;

2.5 实现注释

总述
对于代码中巧妙的,晦涩的,有趣的,重要的地方加以注释。
说明

2.5.1 代码前注释

巧妙或复杂的代码段前要加注释。比如:

// 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;
}

2.5.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.
DoSomethingElseThatIsLonger();  // 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.
}
std::vector<string> list{
                    // Comments in braced lists describe the next element...
                    "First item",
                    // .. and should be aligned appropriately.
"Second item"};
DoSomething(); /* For trailing block comments, one space is fine. */

2.5.3 函数参数注释

如果函数参数的意义不明显,考虑用下面的方式进行弥补:

  • 如果参数是一个字面常量,并且这一常量在多处函数调用中被使用, 用以推断它们一致,你应当用一个常量名让这一约定变得更明显,并 且保证这一约定不会被打破。
  • 考虑更改函数的签名,让某个bool类型的参数变为enum类型,这样可以让这个参数的值表达其意义。
  • 如果某个函数有多个配置选项,你可以考虑定义一个类或结构体以保存所有的选项,并传入类或结构体的实例。这样的方法有许多优点,例如这样的选项可以在调用处用变量名引用,这样就能清晰地表明其意义。同时也减少了函数参数的数量,使得函数调用更易读也易写。除此之外,以这样的方式,如果你使用其他的选项,就无需对调用点进行更改。
  • 用具名变量代替大段而复杂的嵌套表达式。
  • 万不得已时, 才考虑在调用点用注释阐明参数的意义。

比如下面的事例的对比:

// What are these arguments?
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);

哪一个更清晰一目了然。

2.5.4 不允许的行为

不要描述显而易见的现象,永远不要用自然语言翻译代码作为注释,除非即使对深入理解C++的读者来说代码的行为都是不明显的。要假设读代码的人C++水平比你高,即便他/她可能不知道你的用意。
你所提供的注释应当解释代码为什么要这么做和代码的目的,或者最好是让代码自文档化
比较这样的注释:

// Find the element in the vector.   差: 这太明显了!
auto iter = std::find(v.begin(), v.end(), element);
if (iter != v.end()) {
    Process(element);
}

和这样的注释:

// Process "element" unless it was already processed.
auto iter = std::find(v.begin(), v.end(), element);
if (iter != v.end()) {
    Process(element);
}

自文档化的代码根本就不需要注释。上面例子中的注释对下面的代码来说就是毫无必要的:

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

猜你喜欢

转载自blog.csdn.net/qq_38597315/article/details/79721456
今日推荐