mirror of
https://github.com/zh-google-styleguide/zh-google-styleguide.git
synced 2026-03-23 23:48:33 +08:00
Merge pull request #161 from LouYu2015/pull-request
修订 Python 风格指南并重新翻译 C++ 风格指南
This commit is contained in:
@@ -1,187 +1,207 @@
|
||||
1. 头文件
|
||||
----------------
|
||||
|
||||
通常每一个 ``.cc`` 文件都有一个对应的 ``.h`` 文件. 也有一些常见例外, 如单元测试代码和只包含 ``main()`` 函数的 ``.cc`` 文件.
|
||||
通常每个 ``.cc`` 文件应该有一个配套的 ``.h`` 文件. 常见的例外情况包括单元测试和仅有 ``main()`` 函数的 ``.cc`` 文件.
|
||||
|
||||
正确使用头文件可令代码在可读性、文件大小和性能上大为改观.
|
||||
正确使用头文件会大大改善代码的可读性和执行文件的大小、性能.
|
||||
|
||||
下面的规则将引导你规避使用头文件时的各种陷阱.
|
||||
下面的规则将带你规避头文件的各种误区.
|
||||
|
||||
.. _self-contained-headers:
|
||||
|
||||
1.1. Self-contained 头文件
|
||||
1.1. 自给自足的头文件
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
.. tip::
|
||||
|
||||
头文件应该能够自给自足(self-contained,也就是可以作为第一个头文件被引入),以 ``.h`` 结尾。至于用来插入文本的文件,说到底它们并不是头文件,所以应以 ``.inc`` 结尾。不允许分离出 ``-inl.h`` 头文件的做法.
|
||||
头文件应该自给自足 (self-contained, 也就是可以独立编译), 并以 ``.h`` 为扩展名. 给需要被导入 (include) 但不属于头文件的文件设置为 ``.inc`` 扩展名, 并尽量避免使用.
|
||||
|
||||
所有头文件要能够自给自足。换言之,用户和重构工具不需要为特别场合而包含额外的头文件。详言之,一个头文件要有 :ref:`define-guard`,统统包含它所需要的其它头文件,也不要求定义任何特别 symbols.
|
||||
所有头文件应该自给自足, 也就是头文件的使用者和重构工具在导入文件时无需任何特殊的前提条件. 具体来说, 头文件要有头文件防护符 (header guards, :ref:`define-guard`),并导入它所需的所有其它头文件.
|
||||
|
||||
不过有一个例外,即一个文件并不是 self-contained 的,而是作为文本插入到代码某处。或者,文件内容实际上是其它头文件的特定平台(platform-specific)扩展部分。这些文件就要用 ``.inc`` 文件扩展名。
|
||||
若头文件声明了内联函数 (inline function) 或模版 (template), 而且头文件的使用者需要实例化 (instantiate) 这些组件时, 头文件必须直接或通过导入的文件间接提供这些组件的实现 (definition). 不要把这些实现放到另一个头文件里 (例如 ``-inl.h`` 文件) 再导入进来; 这是过去的惯例, 但现在被禁止了. 若模版的所有实例化过程都仅出现在一个 ``.cc`` 文件中, 比如采用显式 (explicit) 实例化, 或者只有这个 ``.cc`` 文件会用到模版定义 (definition), 那么可以把模版的定义放在这个文件里.
|
||||
|
||||
如果 ``.h`` 文件声明了一个模板或内联函数,同时也在该文件加以定义。凡是有用到这些的 ``.cc`` 文件,就得统统包含该头文件,否则程序可能会在构建中链接失败。不要把这些定义放到分离的 ``-inl.h`` 文件里(译者注:过去该规范曾提倡把定义放到 -inl.h 里过)。
|
||||
|
||||
有个例外:如果某函数模板为所有相关模板参数显式实例化,或本身就是某类的一个私有成员,那么它就只能定义在实例化该模板的 ``.cc`` 文件里。
|
||||
在少数情况下, 用于导入的文件不能自给自足. 它们通常是要在特殊的地方导入, 例如另一个文件中间的某处. 此类文件不需要使用头文件防护符, 也不需要导入它的依赖 (prerequisite). 此类文件应该使用 ``.inc`` 扩展名. 尽量少用这种文件, 可行时应该采用自给自足的头文件.
|
||||
|
||||
.. _define-guard:
|
||||
|
||||
1.2. #define 保护
|
||||
1.2. #define 防护符
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
.. tip::
|
||||
|
||||
所有头文件都应该有 ``#define`` 保护来防止头文件被多重包含, 命名格式当是: ``<PROJECT>_<PATH>_<FILE>_H_`` .
|
||||
所有头文件都应该用 ``#define`` 防护符来防止重复导入. 防护符的格式是: ``<项目>_<路径>_<文件名>_H_`` .
|
||||
|
||||
为保证唯一性, 头文件的命名应该基于所在项目源代码树的全路径. 例如, 项目 ``foo`` 中的头文件 ``foo/src/bar/baz.h`` 可按如下方式保护:
|
||||
为了保证符号的唯一性, 防护符的名称应该基于该文件在项目目录中的完整文件路径. 例如, ``foo`` 项目中的文件 ``foo/src/bar/baz.h`` 应该有如下防护:
|
||||
|
||||
.. code-block:: c++
|
||||
|
||||
#ifndef FOO_BAR_BAZ_H_
|
||||
#define FOO_BAR_BAZ_H_
|
||||
...
|
||||
#endif // FOO_BAR_BAZ_H_
|
||||
#endif // FOO_BAR_BAZ_H_
|
||||
|
||||
|
||||
.. _include-what-you-use:
|
||||
|
||||
1.3. 导入你的依赖
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
.. tip::
|
||||
|
||||
若代码文件或头文件引用了其他地方定义的符号 (symbol), 该文件应该直接导入 (include) 提供该符号的声明 (declaration) 或者定义 (definition) 的头文件. 不应该为了其他原因而导入头文件.
|
||||
|
||||
不要依赖间接导入. 这样, 人们删除不再需要的 ``#include`` 语句时, 才不会影响使用者. 此规则也适用于配套的文件: 若 ``foo.cc`` 使用了 ``bar.h`` 的符号, 就需要导入 ``bar.h``, 即使 ``foo.h`` 已经导入了 ``bar.h``.
|
||||
|
||||
.. _forward-declarations:
|
||||
|
||||
1.3. 前置声明
|
||||
1.4. 前向声明
|
||||
~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
.. tip::
|
||||
|
||||
尽可能地避免使用前置声明。使用 ``#include`` 包含需要的头文件即可。
|
||||
尽量避免使用前向声明. 应该 :ref:`导入你所需的头文件 <include-what-you-use>`。
|
||||
|
||||
**定义:**
|
||||
|
||||
所谓「前置声明」(forward declaration)是类、函数和模板的纯粹声明,没伴随着其定义.
|
||||
前向声明 (forward declaration) 是没有对应定义 (definition) 的声明.
|
||||
|
||||
.. code-block:: c++
|
||||
|
||||
// 在 C++ 源码文件中:
|
||||
class B;
|
||||
void FuncInB();
|
||||
extern int variable_in_b;
|
||||
ABSL_DECLARE_FLAG(flag_in_b);
|
||||
|
||||
**优点:**
|
||||
|
||||
* 前置声明能够节省编译时间,多余的 ``#include`` 会迫使编译器展开更多的文件,处理更多的输入。
|
||||
* 前置声明能够节省不必要的重新编译的时间。 ``#include`` 使代码因为头文件中无关的改动而被重新编译多次。
|
||||
* 使用前向声明能节约编译时间, 因为 ``#include`` 会迫使编译器打开更多的文件并处理更多的输入.
|
||||
* 使用前向声明能避免不必要的重复编译. 若使用 ``#include``, 头文件中无关的改动也会触发重新编译.
|
||||
|
||||
**缺点:**
|
||||
|
||||
* 前置声明隐藏了依赖关系,头文件改动时,用户的代码会跳过必要的重新编译过程。
|
||||
* 前置声明可能会被库的后续更改所破坏。前置声明函数或模板有时会妨碍头文件开发者变动其 API. 例如扩大形参类型,加个自带默认参数的模板形参等等。
|
||||
* 前置声明来自命名空间 ``std::`` 的 symbol 时,其行为未定义。
|
||||
* 很难判断什么时候该用前置声明,什么时候该用 ``#include`` 。极端情况下,用前置声明代替 ``#include`` 甚至都会暗暗地改变代码的含义:
|
||||
* 前向声明隐藏了依赖关系, 可能会让人忽略头文件变化后必要的重新编译过程.
|
||||
* 相比 ``#include``, 前向声明的存在会让自动化工具难以发现定义该符号的模块.
|
||||
* 修改库 (library) 时可能破坏前向声明. 函数或模板的前向声明会阻碍头文件的负责人修改 API, 例如拓宽 (widening) 参数类型, 为模版参数添加默认值, 迁移到新的命名空间等等, 而这些操作本是无碍的.
|
||||
* 为 ``std::`` 命名空间的符号提供前向声明会产生未定义行为 (undefined behavior).
|
||||
* 很难判断什么时候该用前向声明, 什么时候该用 ``#include`` . 用前向声明代替 ``#include`` 时, 可能会悄然改变代码的含义:
|
||||
|
||||
.. code-block:: c++
|
||||
.. code-block:: c++
|
||||
|
||||
// b.h:
|
||||
struct B {};
|
||||
struct D : B {};
|
||||
// b.h:
|
||||
struct B {};
|
||||
struct D : B {};
|
||||
|
||||
// good_user.cc:
|
||||
#include "b.h"
|
||||
void f(B*);
|
||||
void f(void*);
|
||||
void test(D* x) { f(x); } // calls f(B*)
|
||||
// good_user.cc:
|
||||
#include "b.h"
|
||||
void f(B*);
|
||||
void f(void*);
|
||||
void test(D* x) { f(x); } // 调用 f(B*)
|
||||
|
||||
如果 ``#include`` 被 ``B`` 和 ``D`` 的前置声明替代, ``test()`` 就会调用 ``f(void*)`` .
|
||||
若用 ``B`` 和 ``D`` 的前向声明替代 ``#include``, ``test()`` 会调用 ``f(void*)`` .
|
||||
|
||||
* 前置声明了不少来自头文件的 symbol 时,就会比单单一行的 ``include`` 冗长。
|
||||
* 仅仅为了能前置声明而重构代码(比如用指针成员代替对象成员)会使代码变得更慢更复杂.
|
||||
* 为多个符号添加前向声明比直接 ``#include`` 更冗长.
|
||||
* 为兼容前向声明而设计的代码 (比如用指针成员代替对象成员) 更慢更复杂.
|
||||
|
||||
**结论:**
|
||||
|
||||
* 尽量避免前置声明那些定义在其他项目中的实体.
|
||||
* 函数:总是使用 ``#include``.
|
||||
* 类模板:优先使用 ``#include``.
|
||||
|
||||
至于什么时候包含头文件,参见 :ref:`name-and-order-of-includes` 。
|
||||
尽量避免为其他项目定义的实体提供前向声明.
|
||||
|
||||
.. _inline-functions:
|
||||
|
||||
1.4. 内联函数
|
||||
1.5. 内联函数
|
||||
~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
.. tip::
|
||||
|
||||
只有当函数只有 10 行甚至更少时才将其定义为内联函数.
|
||||
只把 10 行以下的小函数定义为内联 (inline).
|
||||
|
||||
**定义:**
|
||||
|
||||
当函数被声明为内联函数之后, 函数有可能会被内联(到底是否内联,由编译器自行决定)。
|
||||
你可以通过声明让编译器展开内联函数, 而不是使用正常的函数调用机制.
|
||||
|
||||
**优点:**
|
||||
|
||||
只要内联的函数体较小, 内联该函数可以令目标代码更加高效. 对于存取函数以及其它函数体比较短, 性能关键的函数, 鼓励使用内联.
|
||||
只要内联函数体积较小, 内联函数可以令目标代码 (object code) 更加高效. 我们鼓励对存取函数 (accessors)、变异函数 (mutators) 和其它短小且影响性能的函数使用内联展开.
|
||||
|
||||
**缺点:**
|
||||
|
||||
滥用内联将导致程序变得更慢. 内联可能使目标代码量或增或减, 这取决于内联函数的大小. 内联非常短小的存取函数通常会减少代码大小, 但内联一个相当大的函数将戏剧性的增加代码大小. 现代处理器由于更好的利用了指令缓存, 小巧的代码往往执行更快。
|
||||
滥用内联将拖慢程序. 根据函数体积, 内联可能会增加或减少代码体积. 通常, 内联展开非常短小的存取函数会减少代码大小, 但内联一个巨大的函数将显著增加代码大小. 在现代处理器上, 通常代码越小执行越快, 因为指令缓存利用率高.
|
||||
|
||||
**结论:**
|
||||
|
||||
一个较为合理的经验准则是, 不要内联超过 10 行的函数. 谨慎对待析构函数, 析构函数往往比其表面看起来要更长, 因为有隐含的成员和基类析构函数被调用!
|
||||
合理的经验法则是不要内联超过 10 行的函数. 谨慎对待析构函数. 析构函数往往比表面上更长, 因为会暗中调用成员和基类的析构函数!
|
||||
|
||||
另一个实用的经验准则: 内联那些包含循环或 ``switch`` 语句的函数常常是得不偿失 (除非在大多数情况下, 这些循环或 ``switch`` 语句从不被执行).
|
||||
另一个实用的经验准则: 内联那些有循环或 ``switch`` 语句的函数通常得不偿失 (除非这些循环或 ``switch`` 语句通常不执行).
|
||||
|
||||
有些函数即使声明为内联的也不一定会被编译器内联, 这点很重要; 比如虚函数和递归函数就不会被正常内联. 通常, 递归函数不应该声明成内联函数.(YuleFox 注: 递归调用堆栈的展开并不像循环那么简单, 比如递归层数在编译时可能是未知的, 大多数编译器都不支持内联递归函数). 虚函数内联的主要原因则是想把它的函数体放在类定义内, 为了图个方便, 抑或是当作文档描述其行为, 比如精短的存取函数.
|
||||
应该注意, 即使函数被声明为内联函数, 也不一定真的会被内联; 比如, 通常虚函数和递归函数不会被内联. 通常不应该声明递归函数为内联. (YuleFox 注: 递归调用堆栈的展开并不像循环那么简单, 比如递归层数在编译时可能是未知的, 大多数编译器都不支持内联递归函数). 为虚函数声明内联的主要目的是在类 (class) 中定义该函数, 以便于使用该函数或注释其行为. 这常用于存取函数和变异函数.
|
||||
|
||||
.. _name-and-order-of-includes:
|
||||
|
||||
1.5. ``#include`` 的路径及顺序
|
||||
1.6. ``#include`` 的路径及顺序
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
.. tip::
|
||||
使用标准的头文件包含顺序可增强可读性, 避免隐藏依赖: 相关头文件, C 库, C++ 库, 其他库的 `.h`, 本项目内的 `.h`.
|
||||
|
||||
项目内头文件应按照项目源代码目录树结构排列, 避免使用 UNIX 特殊的快捷目录 ``.`` (当前目录) 或 ``..`` (上级目录). 例如, ``google-awesome-project/src/base/logging.h`` 应该按如下方式包含:
|
||||
推荐按照以下顺序导入头文件: 配套的头文件, C 语言系统库头文件, C++ 标准库头文件, 其他库的头文件, 本项目的头文件.
|
||||
|
||||
头文件的路径应相对于项目源码目录, 不能出现 UNIX 目录别名 (alias) ``.`` (当前目录) 或 ``..`` (上级目录). 例如, 应该按如下方式导入 ``google-awesome-project/src/base/logging.h``:
|
||||
|
||||
.. code-block:: c++
|
||||
|
||||
#include "base/logging.h"
|
||||
|
||||
又如, ``dir/foo.cc`` 或 ``dir/foo_test.cc`` 的主要作用是实现或测试 ``dir2/foo2.h`` 的功能, ``foo.cc`` 中包含头文件的次序如下:
|
||||
在 ``dir/foo.cc`` 或 ``dir/foo_test.cc`` 这两个实现或测试 ``dir2/foo2.h`` 内容的文件中, 按如下顺序导入头文件:
|
||||
|
||||
#. ``dir2/foo2.h`` (优先位置, 详情如下)
|
||||
#. C 系统文件
|
||||
#. C++ 系统文件
|
||||
#. 其他库的 ``.h`` 文件
|
||||
#. 本项目内 ``.h`` 文件
|
||||
#. ``dir2/foo2.h``.
|
||||
#. 空行
|
||||
#. C 语言系统文件 (确切地说: 用使用方括号和 ``.h`` 扩展名的头文件), 例如 ``<unistd.h>`` 和 ``<stdlib.h>``.
|
||||
#. 空行
|
||||
#. C++ 标准库头文件 (不含扩展名), 例如 ``<algorithm>`` 和 ``<cstddef>``.
|
||||
#. 空行
|
||||
#. 其他库的 ``.h`` 文件.
|
||||
#. 空行
|
||||
#. 本项目的 ``.h`` 文件.
|
||||
|
||||
这种优先的顺序排序保证当 ``dir2/foo2.h`` 遗漏某些必要的库时, ``dir/foo.cc`` 或 ``dir/foo_test.cc`` 的构建会立刻中止。因此这一条规则保证维护dir这些文件的人们首先看到构建中止的消息,而不是维护其他包的人们。
|
||||
每个非空的分组之间用空行隔开.
|
||||
|
||||
``dir/foo.cc`` 和 ``dir2/foo2.h`` 通常位于同一目录下 (如 ``base/basictypes_unittest.cc`` 和 ``base/basictypes.h``), 但也可以放在不同目录下.
|
||||
这种顺序可以确保在 ``dir2/foo2.h`` 缺少必要的导入时, 构建 (build) ``dir/foo.cc`` 或 ``dir/foo_test.cc`` 会失败. 这样维护这些文件的人会首先发现构建失败, 而不是维护其他库的无辜的人.
|
||||
|
||||
按字母顺序分别对每种类型的头文件进行二次排序是不错的主意。注意较老的代码可不符合这条规则,要在方便的时候改正它们。
|
||||
``dir/foo.cc`` 和 ``dir2/foo2.h`` 通常位于同一目录下 (如 ``base/basictypes_unittest.cc`` 和 ``base/basictypes.h``), 但有时也放在不同目录下.
|
||||
|
||||
您所依赖的符号 (symbols) 被哪些头文件所定义,您就应该包含(include)哪些头文件,`前置声明`__ (forward declarations) 情况除外。比如您要用到 ``bar.h`` 中的某个符号, 哪怕您所包含的 ``foo.h`` 已经包含了 ``bar.h``, 也照样得包含 ``bar.h``, 除非 ``foo.h`` 有明确说明它会自动向您提供 ``bar.h`` 中的 symbol. 不过,凡是 cc 文件所对应的「相关头文件」已经包含的,就不用再重复包含进其 cc 文件里面了,就像 ``foo.cc`` 只包含 ``foo.h`` 就够了,不用再管后者所包含的其它内容。
|
||||
注意 C 语言头文件 (如 ``stddef.h``) 和对应的 C++ 头文件 (``cstddef``) 是等效的. 两种风格都可以接受, 但是最好和现有代码保持一致.
|
||||
|
||||
__ forward-declarations_
|
||||
每个分组内部的导入语句应该按字母序排列. 注意旧代码可能没有遵守这条规则, 应该在方便时进行修正.
|
||||
|
||||
举例来说, ``google-awesome-project/src/foo/internal/fooserver.cc`` 的包含次序如下:
|
||||
举例来说, ``google-awesome-project/src/foo/internal/fooserver.cc`` 的导入语句如下:
|
||||
|
||||
.. code-block:: c++
|
||||
.. code-block:: c++
|
||||
|
||||
#include "foo/public/fooserver.h" // 优先位置
|
||||
#include "foo/server/fooserver.h"
|
||||
|
||||
#include <sys/types.h>
|
||||
#include <unistd.h>
|
||||
#include <sys/types.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include <hash_map>
|
||||
#include <vector>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "base/basictypes.h"
|
||||
#include "base/commandlineflags.h"
|
||||
#include "foo/public/bar.h"
|
||||
#include "base/basictypes.h"
|
||||
#include "foo/server/bar.h"
|
||||
#include "third_party/absl/flags/flag.h"
|
||||
|
||||
**例外:**
|
||||
|
||||
有时,平台特定(system-specific)代码需要条件编译(conditional includes),这些代码可以放到其它 includes 之后。当然,您的平台特定代码也要够简练且独立,比如:
|
||||
有时平台相关的 (system-specific) 代码需要有条件地导入 (conditional include),此时可以在其他导入语句后放置条件导入语句. 当然, 尽量保持平台相关的代码简洁且影响范围小. 例如:
|
||||
|
||||
.. code-block:: c++
|
||||
.. code-block:: c++
|
||||
|
||||
#include "foo/public/fooserver.h"
|
||||
#include "foo/public/fooserver.h"
|
||||
|
||||
#include "base/port.h" // For LANG_CXX11.
|
||||
#include "base/port.h" // 为了 LANG_CXX11.
|
||||
|
||||
#ifdef LANG_CXX11
|
||||
#include <initializer_list>
|
||||
#endif // LANG_CXX11
|
||||
#ifdef LANG_CXX11
|
||||
#include <initializer_list>
|
||||
#endif // LANG_CXX11
|
||||
|
||||
译者 (YuleFox) 笔记
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
0. 扉页
|
||||
扉页
|
||||
============
|
||||
|
||||
:版本: 4.45
|
||||
:版本: 2024/02/18
|
||||
|
||||
:原作者:
|
||||
|
||||
@@ -21,13 +21,14 @@
|
||||
`Yang.Y <https://github.com/yangyubo>`_
|
||||
`acgtyrant <http://acgtyrant.com>`_
|
||||
`lilinsanity <http://github.com/lilinsanity>`_
|
||||
`楼宇 <https://github.com/LouYu2015>`_
|
||||
|
||||
:项目主页:
|
||||
|
||||
- `Google Style Guide <http://google-styleguide.googlecode.com>`_
|
||||
- `Google 开源项目风格指南 - 中文版 <http://github.com/zh-google-styleguide/zh-google-styleguide>`_
|
||||
|
||||
0.1 译者前言
|
||||
译者前言
|
||||
--------------------
|
||||
|
||||
Google 经常会发布一些开源项目, 意味着会接受来自其他代码贡献者的代码. 但是如果代码贡献者的编程风格与 Google 的不一致, 会给代码阅读者和其他代码提交者造成不小的困扰. Google 因此发布了这份自己的编程风格指南, 使所有提交代码的人都能获知 Google 的编程风格.
|
||||
@@ -60,20 +61,69 @@ Google 经常会发布一些开源项目, 意味着会接受来自其他代码
|
||||
|
||||
- 2008-07 1.0 : 出自 `YuleFox 的 Blog <http://www.yulefox.com/?p=207>`_, 很多地方摘录的也是该版本.
|
||||
|
||||
以下是正文.
|
||||
|
||||
0.2 背景
|
||||
背景
|
||||
--------------
|
||||
|
||||
C++ 是 Google 大部分开源项目的主要编程语言. 正如每个 C++ 程序员都知道的, C++ 有很多强大的特性, 但这种强大不可避免的导致它走向复杂,使代码更容易产生 bug, 难以阅读和维护.
|
||||
C++ 是谷歌的开源项目所采用的主要编程语言之一. C++ 程序员都知道, 该语言有很多强大的特性 (feature), 但强大之处也伴随着复杂性, 让代码容易出错, 难以阅读、维护.
|
||||
|
||||
本指南的目的是通过详细阐述 C++ 注意事项来驾驭其复杂性. 这些规则在保证代码易于管理的同时, 也能高效使用 C++ 的语言特性.
|
||||
本指南的目标是详述 C++ 的注意事项来控制复杂性. 这些规则会在保持代码易于管理的同时, 不影响程序员高效地使用 C++ 的语言特性.
|
||||
|
||||
*风格*, 亦被称作可读性, 也就是指导 C++ 编程的约定. 使用术语 "风格" 有些用词不当, 因为这些习惯远不止源代码文件格式化这么简单.
|
||||
风格 (style, 亦称作可读性 (readability)) 是用于管理 C++ 代码的惯例. "风格" 这一术语略有不准确, 因为这些惯例并非仅仅囊括代码格式.
|
||||
|
||||
使代码易于管理的方法之一是加强代码一致性. 让任何程序员都可以快速读懂你的代码这点非常重要. 保持统一编程风格并遵守约定意味着可以很容易根据 "模式匹配" 规则来推断各种标识符的含义. 创建通用, 必需的习惯用语和模式可以使代码更容易理解. 在一些情况下可能有充分的理由改变某些编程风格, 但我们还是应该遵循一致性原则,尽量不这么做.
|
||||
谷歌主导的大部分开源项目遵守本指南的要求.
|
||||
|
||||
本指南的另一个观点是 C++ 特性的臃肿. C++ 是一门包含大量高级特性的庞大语言. 某些情况下, 我们会限制甚至禁止使用某些特性. 这么做是为了保持代码清爽, 避免这些特性可能导致的各种问题. 指南中列举了这类特性, 并解释为什么这些特性被限制使用.
|
||||
注意: 本指南并非 C++ 教程, 我们假定读者已经非常熟悉 C++.
|
||||
|
||||
Google 主导的开源项目均符合本指南的规定.
|
||||
本指南的目标
|
||||
------------------
|
||||
|
||||
注意: 本指南并非 C++ 教程, 我们假定读者已经对 C++ 非常熟悉.
|
||||
为什么编写这份文档?
|
||||
|
||||
我们认为该指南应该实现以下核心目标. 这些目标是每条规则背后的基本 **依据** . 我们希望公开这些想法, 作为讨论的基础, 让广大社区了解每条规则和特定决策背后的来由. 在理解规则所服务的目标以后, 所有人都应该清楚某条规则在哪些情况下可以忽略 (有些规则可以忽略), 以及改变规则时需要提供怎样的论据和替代品.
|
||||
|
||||
我们认为风格指南当前的目标如下:
|
||||
|
||||
风格规则应该有影响力
|
||||
|
||||
一条风格规则应该具备足够大的好处, 以至于值得所有工程师铭记. 所谓好处是相对于当前代码库的状态而言的, 所以即使某一习惯十分恶劣, 如果人们很少使用, 那么禁止这一习惯的好处依然很小. 这样可以解释为什么我们没有写下某些规则. 例如, ``goto`` 语句违背了许多原则, 但是现在已经很少出现, 所以风格指南不会讨论它.
|
||||
|
||||
为读者优化, 而非为作者优化
|
||||
|
||||
我们的代码库 (以及其中的每个组件) 应该会存在很长时间. 因此, 我们读代码的时间比写代码的时间更长. 我们明确地选择优化平均水平的软件工程师阅读、维护和调试代码的体验, 而非编写代码的舒适度. "为读者留下线索" 是这一理念的一个方面. 如果代码中有特殊的情况 (例如指针所有权转移), 在此处给读者留下的文字提示很有价值 (在代码中使用 ``std::unique_ptr`` 就明确地表达了所有权转移).
|
||||
|
||||
和现有代码保持一致
|
||||
|
||||
我们让代码库的风格保持整体一致, 就能聚焦在其他 (更有价值的) 问题上. 一致性也会帮助自动化: 那些格式化代码或者调整 ``#include`` 顺序的工具, 只能在你的代码符合预期时才能正常工作. 很多时候, 那些用于 "保持一致" 的规则本质上就是 "任选其一并停止内耗"; 在这些问题上, 争论的成本超过了提供自由度的价值. 不过, 一致性原则也有局限性. 在没有清晰的技术性论据和长远方向时, 这才是很好的打破平局的方式. 这一原则适合局部使用 (一个文件内, 或者一组关联性强的接口). 不应该为了一致性而采用旧风格, 忽视新风格的好处. 应该考虑到代码库会随时间推移而过渡到新风格.
|
||||
|
||||
恰当时与广大 C++ 社区保持一致
|
||||
|
||||
与其他组织保持一致性是有价值的, 这和我们保持内部一致性的原因一样. 如果 C++ 标准中的特性解决了某个问题, 或者某一范式被广泛采用, 这就是采纳它们的依据. 不过, 有时标准的特性和范式有缺陷, 或者在设计上没有考虑我们代码库的需求. 此时 (正如下文所描述的) 应该限制或者禁止这些标准特性. 有时, 相较于 C++ 标准库, 我们偏向于自研库或某些第三方库. 一般这是因为我们所选择的库具有优越性, 或者迁移到标准库的价值不值得那些工作量.
|
||||
|
||||
避免使用奇特或危险的语法结构
|
||||
|
||||
有些 C++ 的特性比表面上更加奇特或危险. 风格指南中的一些限制就是为了防止掉入这些陷阱. 你需要达到很高的标准才能豁免这些限制, 因为忽略这些规则就很可能直接引起程序错误.
|
||||
|
||||
避免使用那些正常水平的 C++ 程序员认为棘手或难以维护的语法结构
|
||||
|
||||
有些 C++ 特性会给代码带来复杂性, 因此通常不适合使用. 在用途广泛的代码中, 我们可以接受更巧妙的语法结构. 这是因为复杂的实现方式所带来的收益会被众多使用者放大, 而且在编写新代码时, 也不需要重新解读这些复杂的语法. 如有疑问, 可以请求项目主管豁免这些规则. 这对我们的代码库至关重要, 因为代码负责人和团队成员会变化: 即使所有现在修改这段代码的人都理解代码, 几年后人们就不一定还能理解了.
|
||||
|
||||
需要注意我们的规模
|
||||
|
||||
我们有上亿行代码和成千上万的工程师, 因此一位工程师的失误或者投机取巧的行为会成为很多人的负担. 举例来说, 一定要避免污染全局命名空间 (global namespace): 如果所有人都往全局命名空间里塞东西, 就很难避免上亿行代码之间的符号冲突 (name collision), 也难以修复冲突.
|
||||
|
||||
在必要时为优化让路
|
||||
|
||||
即使性能优化的手段会和此文档的其他理念冲突, 有时这些手段也是必要且恰当的.
|
||||
|
||||
此文档的目的是提供最大程度的指导和合理限制. 和往常一样, 你应该追随常理和正常审美. 这里我们特指整个谷歌 C++ 社区所建立的规范, 而不是你个人或者所在团队的偏好. 应该对巧妙或奇特的语法结构保持怀疑和犹豫的态度: 并不是 "法无禁止即可为". 运用你的判断力. 如有疑惑, 请不要犹豫, 随时向项目主管咨询意见.
|
||||
|
||||
C++ 版本
|
||||
------------------
|
||||
|
||||
目前代码的目标版本是 C++20, 所以不应该使用 C++23 的特性. 本指南的 C++ 目标版本会随时间 (激进地) 升级.
|
||||
|
||||
禁止使用非标准扩展.
|
||||
|
||||
在使用 C++17 和 C++20 的特性之前, 需要权衡其他环境的可移植性.
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -168,14 +168,14 @@ Lint
|
||||
def connect_to_next_port(self, minimum: int) -> int:
|
||||
"""连接到下一个可用的端口.
|
||||
|
||||
Args:
|
||||
minimum: 一个大于等于 1024 的端口号.
|
||||
参数:
|
||||
minimum: 一个大于等于 1024 的端口号.
|
||||
|
||||
Returns:
|
||||
新的最小端口.
|
||||
返回:
|
||||
新的最小端口.
|
||||
|
||||
Raises:
|
||||
ConnectionError: 没有可用的端口.
|
||||
抛出:
|
||||
ConnectionError: 没有可用的端口.
|
||||
"""
|
||||
if minimum < 1024:
|
||||
# 注意这里抛出 ValueError 的情况没有在文档里说明,因为 API 的
|
||||
@@ -196,11 +196,11 @@ Lint
|
||||
def connect_to_next_port(self, minimum: int) -> int:
|
||||
"""连接到下一个可用的端口.
|
||||
|
||||
Args:
|
||||
minimum: 一个大于等于 1024 的端口号.
|
||||
参数:
|
||||
minimum: 一个大于等于 1024 的端口号.
|
||||
|
||||
Returns:
|
||||
新的最小端口.
|
||||
返回:
|
||||
新的最小端口.
|
||||
"""
|
||||
assert minimum >= 1024, '最小端口号至少为 1024.'
|
||||
port = self._find_next_open_port(minimum)
|
||||
@@ -225,7 +225,7 @@ Lint
|
||||
避免全局变量.
|
||||
|
||||
定义:
|
||||
在程序运行时可以发生变化的模块级变量和类属性.
|
||||
在程序运行时可以发生变化的模块级变量和类属性 (class attribute).
|
||||
|
||||
优点:
|
||||
偶尔有用.
|
||||
@@ -259,7 +259,7 @@ Lint
|
||||
结论:
|
||||
可以谨慎使用. 尽量避免使用嵌套函数和嵌套类, 除非需要捕获 ``self`` 和 ``cls`` 以外的局部变量. 不要仅仅为了隐藏一个函数而使用嵌套函数. 应将需要隐藏的函数定义在模块级别, 并给名称加上 ``_`` 前缀, 以便在测试代码中调用此函数.
|
||||
|
||||
推导式和生成式
|
||||
推导式 (comprehension expression) 和生成式 (generator expression)
|
||||
--------------------------------
|
||||
|
||||
.. tip::
|
||||
@@ -269,7 +269,7 @@ Lint
|
||||
列表、字典和集合的推导式和生成式可以用于简洁高效地创建容器和迭代器, 而无需借助循环、 ``map()``、 ``filter()``, 或者 ``lambda`` . (译者注: 元组是没有推导式的, ``()`` 内加类似推导式的句式返回的是个生成器)
|
||||
|
||||
优点:
|
||||
相较于其它创建字段、列表和集合的方法, 简单的列表推导式更加清晰和简洁. 生成器表达式十分高效, 因为无需创建整个列表.
|
||||
相较于其它创建字典、列表和集合的方法, 简单的列表推导式更加清晰和简洁. 生成器表达式十分高效, 因为无需创建整个列表.
|
||||
|
||||
缺点:
|
||||
复杂的列表推导式和生成式难以理解.
|
||||
@@ -467,7 +467,7 @@ Lambda函数
|
||||
结论:
|
||||
可以使用, 不过有如下注意事项:
|
||||
|
||||
函数和方法的默认值不能是可变对象.
|
||||
函数和方法的默认值不能是可变 (mutable) 对象.
|
||||
|
||||
正确:
|
||||
|
||||
@@ -494,7 +494,7 @@ Lambda函数
|
||||
...
|
||||
def foo(a, b=_FOO.value): # 此时还没有解析 sys.argv...
|
||||
...
|
||||
def foo(a, b: Mapping = {}): # 可能会赋值给未经过静态检查的代码
|
||||
def foo(a, b: Mapping = {}): # 可能会赋值给未经过静态检查 (unchecked) 的代码
|
||||
...
|
||||
|
||||
|
||||
@@ -520,7 +520,7 @@ Lambda函数
|
||||
#. 子类继承时可能产生困惑.
|
||||
|
||||
结论:
|
||||
允许使用特性. 但是, 和运算符重载一样, 只能在必要时使用, 并且要模仿常规属性的存取特点. 若无法满足要求, 请参考 :ref:`设置器和写入器 <getter_setter>` 的规则.
|
||||
允许使用特性. 但是, 和运算符重载一样, 只能在必要时使用, 并且要模仿常规属性的存取特点. 若无法满足要求, 请参考 :ref:`获取器和写入器 <getter_setter>` 的规则.
|
||||
|
||||
举个例子, 一个特性不能仅仅用于获取和设置一个内部属性: 因为不涉及计算, 没有必要用特性 (应该把该属性设为公有). 而用特性来限制属性的访问或者计算 **简单** 的衍生值则是正确的: 这种逻辑简单明了.
|
||||
|
||||
@@ -548,8 +548,8 @@ True/False的求值
|
||||
|
||||
#. 一定要用 ``if foo is None:`` (或者 ``is not None``) 来检测 ``None`` 值. 例如, 如果你要检查某个默认值为 ``None`` 的参数有没有被调用者覆盖, 覆盖的值在布尔语义下可能也是假值!
|
||||
#. 永远不要用 ``==`` 比较一个布尔值是否等于 ``False``. 应该用 ``if not x:`` 代替. 如果你需要区分 ``False`` 和 ``None``, 你应该用复合表达式, 例如 ``if not x and x is not None:``.
|
||||
#. 多利用空序列(字符串, 列表, 元组)是假值的特性. 因此 ``if not seq:`` 比 ``if len(seq):`` 更好, ``if not seq:`` 比 ``if not len(seq):`` 更好.
|
||||
#. 处理整数时, 使用隐式false可能会得不偿失(例如不小心将 ``None`` 当做0来处理). 你可以显式比较整型值与0的关系 (``len()`` 的返回值例外).
|
||||
#. 多利用空序列(字符串, 列表, 元组)是假值的特点. 因此 ``if not seq:`` 比 ``if len(seq):`` 更好, ``if not seq:`` 比 ``if not len(seq):`` 更好.
|
||||
#. 处理整数时, 使用隐式 False 可能会得不偿失(例如不小心将 ``None`` 当做0来处理). 你可以显式比较整型值与0的关系 (``len()`` 的返回值例外).
|
||||
|
||||
正确:
|
||||
|
||||
@@ -579,7 +579,7 @@ True/False的求值
|
||||
x = x or []
|
||||
|
||||
#. 注意, '0'(字符串, 不是整数)作为布尔值时等于 ``True``.
|
||||
#. 注意, Numpy 数组转换为布尔值时可能抛出异常. 因此建议用 `.size` 属性检查 ``np.array`` 是否为空 (例如 ``if not users.size``).
|
||||
#. 注意, 把 Numpy 数组转换为布尔值时可能抛出异常. 因此建议用 `.size` 属性检查 ``np.array`` 是否为空 (例如 ``if not users.size``).
|
||||
|
||||
词法作用域(Lexical Scoping, 又名静态作用域)
|
||||
---------------------------------------------
|
||||
@@ -680,25 +680,25 @@ True/False的求值
|
||||
|
||||
选择线程间的数据传递方式时, 应优先考虑 ``queue`` 模块的 ``Queue`` 数据类型. 如果不适用, 则使用 ``threading`` 模块及其提供的锁原语(locking primitives). 如果可行, 应该用条件变量和 ``threading.Condition`` 替代低级的锁.
|
||||
|
||||
威力过大的特性
|
||||
威力过大的功能
|
||||
--------------------
|
||||
|
||||
.. tip::
|
||||
避开这些特性.
|
||||
避开这些功能.
|
||||
|
||||
定义:
|
||||
Python是一种异常灵活的语言, 有大量花哨的特性, 诸如自定义元类(metaclasses), 读取字节码(bytecode), 及时编译(on-the-fly compilation), 动态继承, 对象父类重定义(object reparenting), 导入(import)技巧, 反射(例如 ``getattr()``), 系统内部状态的修改, ``__del__`` 实现的自定义清理等等.
|
||||
Python是一种异常灵活的语言, 有大量花哨的功能, 诸如自定义元类(metaclasses), 读取字节码(bytecode), 及时编译(on-the-fly compilation), 动态继承, 对象基类重设(object reparenting), 导入(import)技巧, 反射(例如 ``getattr()``), 系统内部状态的修改, ``__del__`` 实现的自定义清理等等.
|
||||
|
||||
优点:
|
||||
强大的语言特性让代码紧凑.
|
||||
强大的语言功能让代码紧凑.
|
||||
|
||||
缺点:
|
||||
这些很"酷"的特性十分诱人, 但多数情况下没必要使用. 包含奇技淫巧的代码难以阅读、理解和调试. 一开始可能还好(对原作者而言), 但以后回顾代码时, 这种代码通常比那些长而直白的代码更加深奥.
|
||||
这些很"酷"的功能十分诱人, 但多数情况下没必要使用. 包含奇技淫巧的代码难以阅读、理解和调试. 一开始可能还好(对原作者而言), 但以后回顾代码时, 这种代码通常比那些长而直白的代码更加深奥.
|
||||
|
||||
结论:
|
||||
避开这些特性.
|
||||
避开这些功能.
|
||||
|
||||
可以使用那些在内部利用了这些特性的标准模块和类, 比如 ``abc.ABCMeta``, ``dataclasses`` 和 ``enum``.
|
||||
可以使用那些在内部利用了这些功能的标准模块和类, 比如 ``abc.ABCMeta``, ``dataclasses`` 和 ``enum``.
|
||||
|
||||
|
||||
现代python: from __future__ imports
|
||||
@@ -739,7 +739,8 @@ True/False的求值
|
||||
|
||||
.. tip::
|
||||
你可以根据 `PEP-484 <https://www.python.org/dev/peps/pep-0484/>`_ 来对 python3 代码进行注释,并使用诸如 `pytype <https://github.com/google/pytype>`_ 之类的类型检查工具来检查代码.
|
||||
类型注释既可以写在源码里,也可以写在 `pyi <https://www.python.org/dev/peps/pep-0484/#stub-files>`_ 中. 推荐尽量写在源码里. 对于第三方代码和扩展包, 请使用 pyi 文件里
|
||||
|
||||
类型注释既可以写在源码里,也可以写在 `pyi <https://www.python.org/dev/peps/pep-0484/#stub-files>`_ 中. 推荐尽量写在源码里. 对于第三方代码和扩展包, 请使用 pyi 文件.
|
||||
|
||||
定义:
|
||||
用在函数参数和返回值上:
|
||||
@@ -755,10 +756,10 @@ True/False的求值
|
||||
a: SomeType = some_func()
|
||||
|
||||
优点:
|
||||
可以提高代码可读性和可维护性. 类型检查器可以把运行时错误变成编译错误, 并阻止你使用威力过大的特性.
|
||||
可以提高代码可读性和可维护性. 类型检查器可以把运行时错误变成编译错误, 并阻止你使用威力过大的功能.
|
||||
|
||||
缺点:
|
||||
必须时常更新类型声明. 正确的代码也可能有误报. 无法使用威力大的特性.
|
||||
必须时常更新类型声明. 正确的代码也可能有误报. 无法使用威力大的功能.
|
||||
|
||||
结论:
|
||||
强烈推荐你在更新代码时启用 python 类型分析. 在添加或修改公开API时, 请添加类型注释, 并在构建系统(build system)中启用 pytype. 由于python静态分析是新功能, 因此一些意外的副作用(例如类型推导错误)可能会阻碍你的项目采纳这一功能. 在这种情况下, 建议作者在 BUILD 文件或者代码中添加一个 TODO 注释或者链接, 描述那些阻碍采用类型注释的问题.
|
||||
|
||||
@@ -36,30 +36,30 @@ Python风格规范
|
||||
if (width == 0 and height == 0 and
|
||||
color == '红' and emphasis == '加粗'):
|
||||
|
||||
(bridge_questions.clarification_on
|
||||
.average_airspeed_of.unladen_swallow) = '美国的还是欧洲的?'
|
||||
(bridge_questions.clarification_on
|
||||
.average_airspeed_of.unladen_swallow) = '美国的还是欧洲的?'
|
||||
|
||||
with (
|
||||
very_long_first_expression_function() as spam,
|
||||
very_long_second_expression_function() as beans,
|
||||
third_thing() as eggs,
|
||||
):
|
||||
place_order(eggs, beans, spam, beans)
|
||||
with (
|
||||
very_long_first_expression_function() as spam,
|
||||
very_long_second_expression_function() as beans,
|
||||
third_thing() as eggs,
|
||||
):
|
||||
place_order(eggs, beans, spam, beans)
|
||||
|
||||
错误:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
if width == 0 and height == 0 and \
|
||||
color == '红' and emphasis == '加粗':
|
||||
color == '红' and emphasis == '加粗':
|
||||
|
||||
bridge_questions.clarification_on \
|
||||
.average_airspeed_of.unladen_swallow = '美国的还是欧洲的?'
|
||||
|
||||
with very_long_first_expression_function() as spam, \
|
||||
very_long_second_expression_function() as beans, \
|
||||
third_thing() as eggs:
|
||||
place_order(eggs, beans, spam, beans)
|
||||
very_long_second_expression_function() as beans, \
|
||||
third_thing() as eggs:
|
||||
place_order(eggs, beans, spam, beans)
|
||||
|
||||
如果字符串的字面量 (literal) 超过一行, 应该用圆括号实现隐式续行:
|
||||
|
||||
@@ -258,7 +258,7 @@ Shebang行
|
||||
|
||||
**文档字符串**
|
||||
|
||||
Python 的文档字符串用于注释代码. 文档字符串是位于包、模块、类或函数里第一个语句的字符串. 可以用对象的 ``__doc__`` 成员自动提取这些字符串, 并为 ``pydoc`` 所用. (可以试试在你的模块上运行 ``pydoc`` 并观察结果). 文档字符串一定要用三重双引号 ``"""`` 的格式 (依据 `PEP-257 <http://www.python.org/dev/peps/pep-0257/>`_ ). 文档字符串应该是一行概述 (整行不超过 80 个字符), 以句号、问号或感叹号结尾. 如果要写更多注释 (推荐), 那么概述后面必须紧接着一个空行, 然后是剩下的内容, 缩进与文档字符串的第一行的第一个引号对齐. 下面是更多有关文档字符串的格式规范.
|
||||
Python 的文档字符串用于注释代码. 文档字符串是包、模块、类或函数里作为第一个语句的字符串. 可以用对象的 ``__doc__`` 成员自动提取这些字符串, 并为 ``pydoc`` 所用. (可以试试在你的模块上运行 ``pydoc`` 并观察结果). 文档字符串一定要用三重双引号 ``"""`` 的格式 (依据 `PEP-257 <http://www.python.org/dev/peps/pep-0257/>`_ ). 文档字符串应该是一行概述 (整行不超过 80 个字符), 以句号、问号或感叹号结尾. 如果要写更多注释 (推荐), 那么概述后面必须紧接着一个空行, 然后是剩下的内容, 缩进与文档字符串的第一行第一个引号对齐. 下面是更多有关文档字符串的格式规范.
|
||||
|
||||
**模块**
|
||||
|
||||
@@ -287,7 +287,7 @@ Shebang行
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
"""这个blaze测试会使用样板文件.
|
||||
"""这个blaze测试会使用样板文件(golden files).
|
||||
|
||||
若要更新这些文件, 你可以在 `google3` 文件夹中运行
|
||||
`blaze run //foo/bar:foo_test -- --update_golden_files`
|
||||
@@ -872,12 +872,12 @@ TODO (待办) 注释
|
||||
|
||||
**命名规范**
|
||||
|
||||
#. "内部(Internal)"这个词表示仅在模块内可用, 或者在类内是保护/私有的.
|
||||
#. "内部(Internal)"一词表示仅在模块内可用, 或者在类内是受保护/私有的.
|
||||
#. 在一定程度上, 在名称前加单下划线 (``_``) 可以保护模块变量和函数 (格式检查器会对受保护的成员访问操作发出警告).
|
||||
#. 在实例的变量或方法名称前加双下划线 (``__``, 又名为 dunder) 可以有效地把变量或方法变成类的私有成员 (基于名称修饰 name mangling 机制). 我们不鼓励这种用法, 因为这会严重影响可读性和可测试性, 而且没有 **真正** 实现私有. 建议使用单下划线.
|
||||
#. 应该把相关的类和顶级函数放在同一个模块里. 与Java不同, 不必限制一个模块只有一个类.
|
||||
#. 类名应该使用首字母大写的形式 (如 CapWords), 但是模块名应该用小写加下划线的形式 (如 lower_with_under.py). 尽管有些旧的模块使用类似于 CapWords.py 这样的形式, 现在我们不再鼓励这种命名方式, 因为模块名和类名相同时会让人困惑 ("等等, 我刚刚写的是 ``import StringIO`` 还是 ``from StringIO import StringIO``?").
|
||||
#. 新的 **单元测试** 文件应该遵守 PEP 8, 用小写加下划线格式的方法名, 例如 ``test_<被测试的方法名>_<状态>.``. 有些老旧的模块有 ``CapWords`` 这样大写方法名, 为了保持风格一致, 可以在 test 这个词和方法名之后, 用下划线分割名称中不同的逻辑成分. 比如一种可行的格式之一是 ``test<被测试的方法>_<状态>``.
|
||||
#. 新的 **单元测试** 文件应该遵守 PEP 8, 用小写加下划线格式的方法名, 例如 ``test_<被测试的方法名>_<状态>``. 有些老旧的模块有形如 ``CapWords`` 这样大写的方法名, 为了保持风格一致, 可以在 test 这个词和方法名之后, 用下划线分割名称中不同的逻辑成分. 比如一种可行的格式之一是 ``test<被测试的方法>_<状态>``.
|
||||
|
||||
**文件名**
|
||||
|
||||
@@ -1176,7 +1176,7 @@ TODO (待办) 注释
|
||||
|
||||
**NoneType**
|
||||
|
||||
在 Python 的类型系统中, ``NoneType`` 是 "一等" 类型. 在类型注解中, ``None`` 是 ``NoneType`` 的别名. 如果一个变量可能为 ``None``, 则必须声明这种情况! 你可以使用 ``|`` 这样的联合 (union) 类型表达式 (推荐在新的 Python 3.10+ 代码中使用) 或者老的 ``Optional`` 和 ``Union`` 语法.
|
||||
在 Python 的类型系统中, ``NoneType`` 是 "一等" 类型. 在类型注解中, ``None`` 是 ``NoneType`` 的别名. 如果一个变量可能为 ``None``, 则必须声明这种情况! 你可以使用 ``|`` 这样的并集 (union) 类型表达式 (推荐在新的 Python 3.10+ 代码中使用) 或者老的 ``Optional`` 和 ``Union`` 语法.
|
||||
|
||||
应该用显式的 ``X | None`` 替代隐式声明. 早期的 PEP 484 允许将 ``a: str = None`` 解释为 ``a: str | None = None``, 但这不再是推荐的行为.
|
||||
|
||||
@@ -1184,7 +1184,7 @@ TODO (待办) 注释
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
# 现代的联合写法.
|
||||
# 现代的并集写法.
|
||||
def modern_or_union(a: str | int | None, b: str | None = None) -> str:
|
||||
...
|
||||
# 采用 Union / Optional.
|
||||
@@ -1376,7 +1376,7 @@ TODO (待办) 注释
|
||||
|
||||
**有条件的导入**
|
||||
|
||||
仅在一些特殊情况下, 比如必须在运行时避免导入类型检查所需的模块时, 才能有条件地导入. 不推荐这种写法. 替代方案是重构代码, 使类型检查所需的模块可以在顶层导入.
|
||||
仅在一些特殊情况下, 比如在运行时必须避免导入类型检查所需的模块, 才能有条件地导入. 不推荐这种写法. 替代方案是重构代码, 使类型检查所需的模块可以在顶层导入.
|
||||
|
||||
可以把仅用于类型注解的导入放在 ``if TYPE_CHECKING:`` 语句块内.
|
||||
|
||||
|
||||
Reference in New Issue
Block a user