注释风格

代码注释依据其面向的读者不同,可以分为接口注释与实现注释。

  1. 接口注释,重点在于描述业务功能、调用方式、注意事项等,为了使调用者无需了解实现细节的基础上依然能快速、正确的使用接口。接口注释==需求文档+接口文档。
  2. 实现注释,重点在于描述实现算法、设计结构、特殊处理等,为了使用模块维护者可以清晰的了解代码,支撑其需求开发以及问题修复。

通过不断的反问:这段代码是为了什么目的而写?代码的读者会是谁?来区分以上两者。

注:并非头文件中声明的内容都需要接口注释,得按实际用途划分。例如,对于一个代码量较大的Phase,可能将其拆分为多个头文件和源文件,但这些头文件中的内容只服务于当前Pahse,其他模块无需感知,则其应该使用实现注释。若该Phase属于分析Phase,其分析结果AnalysisResult需要传递给其他优化,则其AnalysisResult适用于接口注释。

此处,接口注释使用文档注释风格,实现注释使用代码注释风格。采用两种风格的优点是可以更加明确的区分对外接口和实现接口,并可以生成文档;缺点是新人在使用注释时,需要更多的考量。

文档注释风格

文档注释采用较为通用的doxygen文档注释,注释统一采用C++风格的///注释方式。以下介绍的注释项为当前已经采纳的注释风格,doxygen中有更多的文档注释能力,如group等,暂时不接入,以保持当前代码的简洁。

文件注释

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
/*
 * Copyright (c) [2019] Huawei Technologies Co.,Ltd.All rights reserved.
 *
 * OpenArkCompiler is licensed under the Mulan PSL v1.
 * You can use this software according to the terms and conditions of the Mulan PSL v1.
 * You may obtain a copy of Mulan PSL v1 at:
 *
 *     http://license.coscl.org.cn/MulanPSL
 *
 * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER
 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR
 * FIT FOR A PARTICULAR PURPOSE.
 * See the Mulan PSL v1 for more details.
 *//**
 * \file
 * This is the description of the file.
 */

文件注释是所有文档注释中唯一使用JAVADOC风格而非C++风格的注释方式,主要为了和版权声明的头注释保持一致,如此经过一定考量后的注释方式。借鉴于doxygen官方文档,将版权声明注释尾的*/与文档注释/**合并到一行,以保持文件头注释的美观性。

1
2
 * \file
 * This is the description of the file.

文件注释以\file作为关键字标记,文件注释新起一行。

文件注释并非用于描述具体某个类或接口的功能,而是从整体的设计层面,描述当前文件中类族或接口集合设计的目的和用途,表明其在整个模块中的关系,乃至提供解决问题的代码示例。在仅包含单个类的头文件中,文件注释内容与类注释往往很难界定,所以在无法明确区分时,此处武断的推荐将注释放在类上。

大多数情况下,文件注释均可以省略。

class/struct/enum/union/using的类型注释

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
/// \brief
/// Brief of definition `Number`.
///
/// Detail of definiton `Number`, a blank line is needed upper.
enum Number {};

/// \brief
/// Brief of definition `String`
///
/// Detail of definition `String`
class String {
  /// \brief
  /// Brief of size_type.
  ///
  /// Detail of size_type.
  using size_type = size_t;
};

类型注释以\brief作为概要关键字标记,注释内容另起一行。概要信息后空一行,接详情信息的注释。其中,概要信息将随着元素展示,而详情信息将在所有概要的注释后展开。

缺省\brief关键字时,默认为详情信息的注释。

类型注释描述当前类的设计目的和用途,以及和其他类之间的关系,包括类的安全性、不应被使用的场景、风险等。

函数/成员函数/函数宏的函数注释

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
/// \brief
/// Brief of macro.
///
/// Detail of macro.
#define MACRO_LOG do {} while(0);

class String {
 public:
  /// \brief
  /// Brief of explaination for implict construct.
  ///
  /// \param str The life will be transfered.
  String(char *str)
      : str(str) {}

  /// \brief
  /// Brief of `Get`.
  ///
  /// Detail of `Get`.
  /// \return Never delete.
  /// \attention Never delete.
  char *Get() {
    return str;
  }
};

/// \brief
/// Brief of `Func`.
///
/// Detail of `Func`.
void Func() {
}

函数注释在描述概要信息和详情信息与类型注释相同,但其针对参数和返回值添加了几个新的关键字。

  1. \param {name} {description}阐明参数的用途。

    更推荐由形参名/类型+函数名自注释,且若函数有多个参数时,只针对需要注释的参数进行注释,无需写全参数列表。

    注:[in]|[out]|[in, out]C++中必要性不高,结合规范优先传递引用而非指针,多数场景下,满足:[in]=传值/const引用/底层const指针;[out]/[in, out]=非const引用/非底层const指针

  2. \return {description}阐明返回值的用途。

    更推荐由返回值类型+函数名自注释。

  3. \attention {description}告知风险与注意事项,但亦要避免对语言或领域内的基础性或常识性概念的描述。

枚举成员/全局常量/全局变量的数据注释

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
enum Num {
  kZero,    ///< This is 0
  kOne,     ///< This is 1
  kTwo,     ///< This is 2
  kOther    ///< This is others
};

constexpr int32_t kConstValue0 = 0; ///< \brief This is const 0 brief, but is not suggested.
/// This is const 1 brief.
constexpr int32_t kConstValue1 = 1; ///< This is const 1 detail.
                                    ///< Next of the detail.

/// Brief for global, thread safety is a must.
int32_t globalValue = 0;  ///< Deatil is a must, as golbal values need to be avoided.

可以看到///注释均在内容的上方进行注释,本节新增一种注释格式///<,用于对枚举成员/全局常量/全局变量的右侧(后方)进行注释。

数据注释在上置注释的为概要信息,在右置注释的为详情信息。至于为什么如此设计?个人理解为数据对象无逻辑,其概要信息完全可以且必须由其命名承载,若出现特殊信息,则其也并不是简单的概要可以描述的,当使用详情信息做单独介绍。即不推荐对数据使用上置注释,优选命名自注释,特殊场景使用右置注释进行详情补充。

全局变量并不推荐使用,尤其是在新增代码中当明确禁止,但存量代码中依然存在。

常量宏已被const/consexpr定义的常量所取代,所以此处不支持文档注释。注意:存在部分常量宏用于控制编译。

成员变量必须为私有,私有代码不应包含文档注释。

格式上,右置注释亦可以写多行,规定统一保持列对齐,如kConstValue1

样例注释

针对注释中提供代码片段用作样例,考虑到与文档的一致性,方便拷贝与同步,此处决定使用markdown的代码格式(doxygen支持markdown语法)。

1
2
3
4
5
6
 * ```c++
 * Num num = kZero;
 * Num num1 = kZero;
 * ```
   
 /// For single `num`, `Num`, `kZero`.

完整样例

可用doxygen生产查看效果

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
/*
 * Copyright (c) [2019] Huawei Technologies Co.,Ltd.All rights reserved.
 *
 * OpenArkCompiler is licensed under the Mulan PSL v1.
 * You can use this software according to the terms and conditions of the Mulan PSL v1.
 * You may obtain a copy of Mulan PSL v1 at:
 *
 *     http://license.coscl.org.cn/MulanPSL
 *
 * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER
 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR
 * FIT FOR A PARTICULAR PURPOSE.
 * See the Mulan PSL v1 for more details.
 *//**
 * \file
 * This is the description of the file.
 * ```c++
 * Num num = kZero;
 * Num num1 = kZero;
 * ```
 */
#include <string>

namespace maple {

constexpr int32_t kConstValue0 = 0; ///< \brief This is const 0 brief.
/// This is const 1 brief.
constexpr int32_t kConstValue1 = 1; ///< This is const 1 detail.
                                    ///< Next of the detail.

/// \brief
/// Brief of macro.
///
/// Detail of macro.
#define MACRO_LOG \
do {} while(0);

/// \brief
/// Brief of definition `Num`.
///
/// Detail of definiton `Num`, a blank line is needed upper.
enum Num {
  kZero,    ///< This is 0
  kOne,     ///< This is 1
  kTwo,     ///< This is 2
  kOther    ///< This is others
};

/// \brief
/// Brief of definition `String`
///
/// Detail of definition `String`
class String {
 public:
  /// \brief
  /// Brief of size_type.
  ///
  /// Detail of size_type.
  using size_type = size_t;

  /// \brief
  /// Brief of explaination for implict construct.
  ///
  /// \param str The life will be transfered.
  String(char *str)
      : str(str) {}

  /// \brief
  /// Brief of `Get`.
  ///
  /// Detail of `Get`.
  /// \return Never delete.
  /// \attention Never delete.
  char *Get() {
    return str;
  }

 protected:
  /// \brief
  /// Brief of `Size`.
  ///
  /// Detail of `Size`.
  size_t Size() const {
    return len;
  }

 private:
  void Reset(char *other) {
    str = other;
  }

 private:
  char *str;
  size_t len;
};

/// Brief for global, thread safety is a must.
int32_t globalValue = 0;  ///< Deatil is a must, as golbal values need to be avoided.

/// \brief
/// Brief of `Func`.
///
/// Detail of `Func`.
void Func() {
}

} // maple

代码注释风格

代码注释统一采用C++风格的//注释(文件头注释除外),除非其完全由C实现并被C程序调用。

文件注释

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
/*
 * Copyright (c) [2019] Huawei Technologies Co.,Ltd.All rights reserved.
 *
 * OpenArkCompiler is licensed under the Mulan PSL v1.
 * You can use this software according to the terms and conditions of the Mulan PSL v1.
 * You may obtain a copy of Mulan PSL v1 at:
 *
 *     http://license.coscl.org.cn/MulanPSL
 *
 * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER
 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR
 * FIT FOR A PARTICULAR PURPOSE.
 * See the Mulan PSL v1 for more details.
 *//*
 * This is the description of the file.
 */

相较文档注释,有两点需要注意:

  1. *//**变更为*//*关闭doxygen注释,并移除\file关键字。
  2. 修改Copyright时间戳
1
 * Copyright (c) [2019] Huawei Technologies Co.,Ltd.All rights reserved.

其中[2019]标记的为初始开源年份到最近一次修改的年份。即2019年开源后未修改,[2019];2019年开源,且2020年又进行了修改,[2019-2020]

###class/struct/enum/union/using的类型注释

1
2
3
4
5
6
7
8
// Document of `Number`
enum Number {};

// Document of `String`
class String {
  // Document of size_type.
  using size_type = size_t;
};

勿使用文档注释风格中的\brief关键字。

函数/成员函数(包括private)/函数宏的函数注释

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
// Document of macro.
#define MACRO_LOG do {} while(0);

class String {
 public:
  // Document of construct
  String(char *str)
      : str(str) {}

  // Document of `Get`
  char *Get() {
    return str;
  }
};

// Document of `Func`
void Func() {
}

勿使用文档注释风格中的\param\return\attention关键字。

枚举成员/成员变量/全局常量/全局变量的数据注释

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
enum Num {
  kZero,    // This is 0
  kOne,     // This is 1
  kTwo,     // This is 2
  kOther    // This is others
};

// Document of `kConstValue0`.
constexpr int32_t kConstValue0 = 0;
constexpr int32_t kConstValue1 = 1; // Document of `kConstValue0`.

// Document of `globalValue`.
int32_t globalValue = 0;

class String {
 private:
  // Document of `str`.
  char *str;
  size_t len; // Document of `len`.
};

此处相对文档注释亦有三点需要注意:

  1. 右置注释当简单精炼,不可出现换行,如果右置注释过长,应使用上置注释。
  2. 全局变量只支持上置注释,不支持右置注释。
  3. 枚举成员/成员变量/全局常量优选右置注释,当且仅当右置注释过长时才选用上置注释。

样例注释

同文档注释中的样例注释,采用doxygen风格。

块内注释

此处的代码块指的是函数内部的代码实现,这类代码统一使用上置注释,除非上置注释会让代码可读性下降,且右置注释可以达到更好的效果,此时才使用右置注释。

注释原则

在编译系统中,注释并不会对最终二进制的生成产生任何影响,但程序员大部分时间都是在阅读与理解代码,注释的准确、美观都将会影响到程序的开发。所以此处要求写注释当同写代码一样重视,最期望的结果是代码+注释=美文,注释是对代码无法表达或难以表达的内容补充。

通用原则

  1. 无论是文档注释亦或是代码注释,必须添加注释这类指标性的注释添加方式是误导性的,注释的添加一定要明确阅读的角色是谁,注释的目的是什么,从而明确是否需要注释,以及注释的内容。重点强调代码自注释,但亦不可死鸭子嘴硬,未能自注释时故意不写注释。
  2. 注释应契合其领域,认为其有一定的C++知识储备、编译器知识储备,而非傻瓜式的教程。
  3. 不应重复函数、类等等的声明已经明确表达的内容。
  4. 相同的注释不应多处出现,如注释字面值常量,当提取全局常量;注释同系列的不同字面值常量,当统一提取全局常量;声明和实现处注释相类似的内容应当只保留一处。
  5. 不应注释实现部分已一目了然的内容。

摘自LLVM注释规范:

Aim to describe what the code is trying to do and why, not how it does it at a micro level.

【译】着重描述代码在做什么、为什么这么做,而不是微观层面的代码是怎么实现的。

  1. 注释应为符合英语语法的语句或短语。例如首字母大写,结束使用.等。

  2. 注释符与注释内容间要有1空格;右置注释与前面代码要有1空格,或保持与上下文的右置注释统一列对齐(此处禁用了tab对齐,一来2格缩进与1个空格的长度不明显,未能有明晰的效果,而来重命名会导致tab对齐失效)。

禁止场景1

来自Google编码规范,函数内部对于字面值实参的补充注释禁用。

1
2
3
4
5
ProductOptions options;
options.set_precision_decimals(7);
options.set_use_cache(ProductOptions::kDontUseCache);
const DecimalNumber product =
    CalculateProduct(values, options, /*completion_callback=*/nullptr);
  1. IDE如Clion自动回补充形参名
  2. 通常会使用有意义的常量名、更有意义的函数名、或重构成员函数的设计等方式来让意图变得明晰。
  3. 这类注释往往是不得已而为之,大部分场景下,恰恰证明此函数设计的缺陷,当重构之(重载两个函数、修改业务逻辑等),而非注释之。

禁止场景2

禁止在代码的局部对整段代码进行注释。

1
2
3
for (auto &element : data) { // Document for the loop.
   
}

此处对于for的行为注释,应使用上置注释,但却在{}内部注释循环行为,应禁止。