代码风格和语言版本

为了实现外观一致的产品,我们将遵循各语言的外部(如果可能)定义的样式指南。对于包布局或文档布局等其他事项,我们将需要制定自己的指南,借鉴当前流行的样式。

此外,开发人员应尽可能使用集成工具来检查编辑器中是否遵循了这些指南。例如,每个人都应该在其编辑器中内置一个 PEP8 检查器,以减少与样式相关的审查迭代。

此外,尽可能地,软件包应在其单元测试中检查样式,以帮助自动检测样式问题(参见 ament_lint_auto)。

C

标准

我们将以C99为目标。

样式

我们将使用 Python的PEP7 作为我们的C代码风格指南,并进行一些修改和添加:

  • 我们将以C99为目标,因为我们不需要支持C89(如PEP7建议)

    • 理由:除其他外,它允许我们同时使用 ///* */ 样式的注释

    • 解释:C99现在几乎无处不在

  • 允许使用C++风格的``//``注释

  • (可选) 总是将文字直接量放在比较操作符的左边,例如使用``0 == ret``而不是``ret == 0``

    • 解释:ret == 0``容易出错为``ret = 0

    • 可选的,因为当使用``-Wall``(或等效选项)时,现代编译器会在发生此情况时给出警告

以下所有修改仅适用于非编写Python模块的情况:

  • 不要将``Py_``作为所有内容的前缀

    • 而是使用包名的驼峰命名法或其他适当的前缀

  • 关于文档字符串的内容不适用

我们可以使用 pep7 Python 模块进行风格检查。编辑器集成似乎很简单,我们可能需要更详细地研究C语言的自动检查。

C++

标准

Humble 以 C++17 为目标。

样式

我们将使用 Google C++ Style Guide,并进行一些修改:

行长度

  • 我们的最大行长度为 100 个字符。

文件扩展名

  • 头文件应使用 .hpp 扩展名。

    • 理由:允许工具确定文件的内容,无论是 C++ 还是 C。

  • 实现文件应使用 .cpp 扩展名。

    • 理由:允许工具确定文件的内容,无论是 C++ 还是 C。

变量命名

  • 对于全局变量,请使用小写字母和下划线,并在前面加上一个空格``g_``

    • 原因:在整个项目中保持变量命名大小写一致

    • 原因:一目了然地确定变量的作用域

    • 跨语言保持一致性

函数和方法命名

  • 谷歌样式指南要求使用``CamelCase``,但C++标准库的样式``snake_case``也是允许的

    • 理由:ROS 2核心包目前使用``snake_case``

      • 原因:可能是历史遗漏或未经linter检查的个人偏好

      • 不更改的原因:追溯性更改会造成过大的干扰

    • 其他考虑因素:

      • cpplint.py 不检查此情况(除非通过审查进行强制执行,否则很难实施)

      • snake_case 可以实现跨语言更一致的结果

    • 具体指导:

      • 对于现有项目,请优先采用现有的风格

      • 对于新项目,两种风格都可以接受,但建议优先选择与相关现有项目相匹配的风格

      • 最终决策由开发人员自行决定

        • 特殊情况,如函数指针、可调用类型等,可能需要打破规则

      • 注意,类应该仍然默认使用“CamelCase”

访问控制

  • 放宽对所有类成员必须私有的要求,从而需要访问器

    • 解释:这对用户API设计来说过于限制性。

    • 解释:我们应该更倾向于私有成员,只有在需要时才将它们设置为公开。

    • 解释:在选择允许直接成员访问之前,我们应该考虑使用访问器。

    • 解释:除了对我们方便之外,我们应该有一个充分的理由来允许直接成员访问。

异常

  • 允许使用异常

    • 解释:这是一个全新的代码库,所以我们不适用于传统的论点

    • 解释:对于面向用户的 API,使用异常更符合 C++ 的习惯用法

    • 析构函数中应明确避免异常

  • 如果我们打算在C中包装所得到的API,则应考虑避免异常

    • 理由:这将使在C中进行包装更容易

    • 理由:我们打算在C中包装的代码大多数不会使用异常

类似函数的对象

  • 对Lambda、``std::function``或``std::bind``没有限制

Boost

  • 除非绝对必要,应避免使用Boost。

评论和文档注释

  • 使用 ////** */ 注释来进行*文档*编写,使用 // 样式的注释来进行备注和一般性评论

    • 类和函数的注释应该使用 ////** */ 样式的注释

    • 原因:这些注释样式在 C/C++ 中推荐用于 Doxygen 和 Sphinx

    • 理由: 使用``/* *///``混合注释对于注释掉包含注释的代码块很方便。

    • 代码运行方式的描述或类和函数内部的注释应该使用``//``风格的注释。

指针语法对齐

  • 在这种情况下,使用``char * c;``而不是``char* c;``或``char c;``,因为``char c, *d, *e;``。

类隐私关键字

  • 在``public:, ``private:, 或 ``protected:``之前不要加1个空格,所有缩进都应为2的倍数,以保持一致性

    • 理由:大多数编辑器不喜欢缩进不是(软)制表符大小的倍数

    • 在``public:, ``private:, 或 ``protected:``之前使用零个空格或2个空格

    • 如果您在之前使用了2个空格,则将其他类语句缩进2个额外的空格

    • 建议在与类相同的列中使用零个空格,即``public:、``private:``或``protected:

嵌套模板

  • 永远不要在嵌套模板中添加空格

    • 更喜欢使用``set<list<string>>``(C++11特性),而不是``set<list<string> >``或``set< list<string> >``

始终使用花括号

  • 在``if``、elsedo``while``和``for``后始终使用花括号,即使主体只有一行。

    • 理由:减少视觉歧义的机会,避免由于主体中使用宏而导致的复杂情况。

花括号的风格选择

  • 对于 functionclassenumstruct 的定义,使用空格后的开放花括号,但是对于 ifelsewhilefor 等语句使用相邻的花括号...

    • 例外情况:当 if``(或 ``while 等)条件太长需要换行时,使用开放花括号(即不相邻的形式)。

  • 当函数调用不能在一行内容纳时,在开放的括号处换行(不在参数之间),并在下一行以2个空格缩进开始。对于更多的参数,继续使用2个空格缩进的格式进行下一行。注意,这一点在 Google 风格指南 上有内部矛盾。

    • 对于太长无法放在一行的``if``(以及``while``等)条件同样适用。

示例

这是可以的:

int main(int argc, char **argv)
{
  if (condition) {
    return 0;
  } else {
    return 1;
  }
}

if (this && that || both) {
  ...
}

// Long condition; open brace
if (
  this && that || both && this && that || both && this && that || both && this && that)
{
  ...
}

// Short function call
call_func(foo, bar);

// Long function call; wrap at the open parenthesis
call_func(
  foo, bar, foo, bar, foo, bar, foo, bar, foo, bar, foo, bar, foo, bar, foo, bar, foo, bar,
  foo, bar, foo, bar, foo, bar, foo, bar, foo, bar, foo, bar, foo, bar, foo, bar, foo, bar);

// Very long function argument; separate it for readability
call_func(
  bang,
  fooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooo,
  bar, bat);

这是**不**可以的:

int main(int argc, char **argv) {
  return 0;
}

if (this &&
    that ||
    both) {
  ...
}

使用大括号而不是过多的缩进,例如用于区分构造函数中的代码和构造函数初始化列表

这是可以的:

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

MyClass::MyClass(int var)
: some_var_(var),
  some_other_var_(var + 1)
{
  ...
  DoSomething();
  ...
}

这是 不正确的,甚至很奇怪(Google 的方式?):

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

MyClass::MyClass(int var)
    : some_var_(var),             // 4 space indent
      some_other_var_(var + 1) {  // lined up
  ...
  DoSomething();
  ...
}

代码检查工具

我们使用 Google 的 cpplint.pyuncrustify 的组合来检查这些风格。

我们提供带有自定义配置的命令行工具:

一些格式化工具,例如ament_uncrustify和ament_clang_format,支持“--reformat”选项以直接应用更改。

我们还运行其他工具来尽可能检测和消除警告。以下是我们在所有软件包上尝试做的一些额外工作的非详尽列表:

Python

版本

我们将以Python 3为我们的开发目标。

样式

我们将使用 PEP8指南 来进行代码格式化。

我们选择了以下更精确的规则,PEP 8在某些方面有些自由:

在单元测试和/或编辑器集成中应使用类似 (ament_)pycodestyle 的工具来检查 Python 代码风格。

代码检查工具中使用的 pycodestyle 配置文件位于 此处

编辑器集成:

CMake

版本

我们将以CMake 3.8为目标。

样式

由于没有现有的CMake样式指南,我们将定义自己的:

  • 使用小写命令名称(find_package,而不是`FIND_PACKAGE`)。

  • 使用`snake_case`标识符(变量,函数,宏)。

  • 使用空的`else()`和`end...()`命令。

  • 在`(`之前不要有空格。

  • 使用两个空格缩进,不要使用制表符。

  • 不要对多行宏调用的参数使用对齐缩进。仅使用两个空格。

  • 优先使用带有 set(PARENT_SCOPE) 的函数,而不是宏。

  • 在使用宏时,将局部变量前缀命名为 _ 或一个合理的前缀。

Markdown / reStructured Text / docblocks

样式

以下用于格式化文本的规则旨在提高可读性和版本控制。

  • [.md, .rst only] 每个章节标题前应有一个空行,后面也应有一个空行。

    • 原因:这有助于在浏览文档时快速获取结构概览。

  • [.rst only] 在 reStructured Text 中,标题应遵循 Sphinx 风格指南 中描述的层次结构:

    • # 上方加线(仅一次,用于文档标题)

    • * 上方加线

    • =

    • -

    • ^

    • "

    • 解释:保持一致的层次结构有助于在查看文档时快速了解嵌套层级。

  • [仅限.md格式] 在Markdown中,标题应遵循`Markdown语法文档<https://daringfireball.net/projects/markdown/syntax#header>`__中描述的ATX样式

    • ATX样式的标题使用1-6个井号字符(#)在行首表示标题级别1-6

    • 应在井号和标题之间使用一个空格(例如 # 标题1)以便更容易进行视觉分隔

    • ATX样式的偏好来自于`Google Markdown样式指南<https://github.com/google/styleguide/blob/gh-pages/docguide/style.md#atx-style-headings>`__

    • 原理: ATX 样式的标题更容易搜索和维护,并且使得前两个标题级别与其他级别保持一致。

  • [任意] 每个句子必须从新的一行开始。

    • 原理: 对于较长的段落,一开始的单个更改会导致差异难以阅读,因为它会一直延续到整个段落。

  • [任意] 每个句子可以选择性地换行,以保持每行短小。

  • [任意] 行末不应有任何空白。

  • [仅限 .md、.rst] 代码块前后必须有空行。

    • 原因:只有在有围栏代码块的直接前后空格才有意义。遵循这些指示将确保高亮显示正常且一致。

  • [仅限 .md、.rst] 代码块应指定语法(例如 bash)。