21/CLASS

21/CLASS

面向可扩展性的 C 语言风格

  • 状态:稳定 编辑:Pieter Hintjens ph@imatix.com
  • 面向可扩展性的 C 语言风格 (CLASS) 定义了构建于现代 C 编译器和操作系统之上的可扩展 C 库和应用程序代码的一致风格和组织方式。CLASS 旨在将行业最佳实践收集到一个可重用标准中。

版权所有 (c) 2009-2013 iMatix Corporation

许可证

本规范是自由软件;您可以根据自由软件基金会发布的 GNU 通用公共许可证的条款重新分发和/或修改它;许可证的第 3 版,或(由您选择)任何更高版本。

本规范分发时希望其有用,但**不附带任何担保**;甚至不附带对适销性或特定用途适用性的默示担保。详情请参阅 GNU 通用公共许可证。

您应该已随同本程序收到一份 GNU 通用公共许可证的副本;如果未收到,请参阅 https://gnu.ac.cn/licenses

本规范是一个自由开放的标准(参见“自由开放标准的定义”)并受数字标准组织共识导向规范体系 (COSS) 的管辖(参见“共识导向规范体系”)。

变更流程

本文档中的关键词“必须”、“不得”、“要求”、“应”、“不应”、“建议”、“不建议”、“推荐”、“可”和“可选”应按 RFC 2119 中的描述进行解释(参见“RFC 中用于指示要求级别的关键词”)。

语言

CLASS 旨在成为可扩展 C 项目的完整且可重用的风格和组织指南。CLASS 收集最佳实践来解决 C 项目中一系列特定且众所周知的质量问题。它从 ZeroMQ CZMQ 项目的风格指南演变而来。CLASS 与 C4 流程的“编码风格指南”要求插件兼容(参见“集体代码构建契约”)。

目标

本规范的具体目标是:

为项目维护者和贡献者提供衡量项目补丁质量的工具。

  • 让代码看起来像是由一位完美的作者在同一时刻编写的。
  • 减少 C 项目的代码行数和复杂性。
  • 确保大量项目中的每一行代码都完全一致。
  • 通过以最小化的方式解决常见的 C 风格问题,降低编写 C 代码的风险。
  • 提供一个随时间收集最佳实践的载体。
  • CLASS 的目标是帮助程序员以一致的方式编写高质量的 C 代码。我们将“高质量”主要定义为:

质量期望

即使您对其特定上下文知之甚少或一无所知,代码也易于阅读和理解。

  • 代码易于重用和混用,无论是整体还是片段。
  • 代码易于正确编写,错误会迅速显现。
  • 我们实现此目标的主要工具是识别和应用最佳模式和风格。读者可以一次性学习这些模式,然后会在大量代码中反复看到它们。偏离熟悉模式的行为可被视为低质量代码的标志,这种代码更可能包含错误且不易混用。

通过本规范,我们旨在解决一系列损害许多 C 项目质量、可混用性和经济性的特定问题:

常见问题

如果一个项目没有风格指南,维护者必须手动重写贡献(这会增加额外工作),或者必须放弃在整个项目中强制执行一致“风格”的尝试(这会损害代码质量和可重用性)。因此,维护者需要一份书面风格指南,他们可以将其指向贡献者。

  • 如果没有可重用的风格指南,每个项目创始人都会即兴创作自己的临时风格。这会浪费先前的经验,并给项目创始人带来不必要的工作。这也意味着每个项目最终都会有不同的风格。

  • 如果一系列项目各有不同的风格,它们就无法轻松共享代码,这损害了混用的经济性。使用单一风格的项目越多,代码重用的成本就越低。这在开源中尤其重要,但也适用于组织内部构建的项目。

  • 由于没有收集和分享 C 编程最佳实践的载体,大多数项目将具有平庸的结构和风格,导致不必要的复杂性、过多的代码行、未使用的代码积累以及其他质量问题。

  • 当项目创始人在没有帮助的情况下创建新项目时,他们经常会犯错误,例如忘记为其代码定义许可证。这可能会产生法律问题。

  • 虽然这些问题会影响所有软件项目,但我们专门为 C 项目解决它们。

总体工作单位是一个项目,它构建为一个库,带有一组可执行的测试程序和辅助命令行工具。

通用风格

定义

项目由组成,每个类涵盖一个工作领域,并实现为一个源文件和相应的头文件。

每个类实现一系列方法,每个方法执行一项任务,并实现为一个 C 函数。

所有名称和注释的语言应为英语。

语言

名称建议选择易于阅读且一致的。除非另有说明,以下风格规则适用于所有给定名称:

命名

名称应是简短的词语,简单、清晰且对读者来说是显而易见的。

  • 对于任何给定的语义,名称应一致使用。
  • 名称不建议是生造词或首字母缩略词。
  • 如果广泛使用,名称可是缩写。
  • 名称不应是保留的 C 或 C++ 关键词。
  • 项目应专注于一个可识别的问题空间,该问题空间应在项目中明确说明:

项目风格

项目焦点

README项目应具有以下简称和缩写:.

项目名称

用于标识项目的路径和 URL 中的项目简称。例如,这会用在 GitHub 项目名称中。在本规范中,我们将使用:

  • myproject作为示例。用于项目文件、输出库和方法名称的项目前缀。例如,这会用在为项目生成的库中。前缀可是首字母缩略词。在本规范中,我们将使用:
  • myp这些名称应在项目中注明:用于项目文件、输出库和方法名称的项目前缀。例如,这会用在为项目生成的库中。前缀可是首字母缩略词。在本规范中,我们将使用:

项目应至少包含以下文件和目录:项目应具有以下简称和缩写:.

通用布局

一个

  • 文件,指代本规范并提供有关项目的其他必要信息。项目应具有以下简称和缩写:一个许可证文件(例如,
  • COPYINGLICENSE)指定项目的分发条款。一个
  • include目录,用于存放所有头文件。src
  • 文件,指代本规范并提供有关项目的其他必要信息。目录,用于存放所有库源文件。公共头文件 (
  • include/myproject.h)).
  • 用于在至少一个平台上构建和测试项目的脚本和 makefile。

项目可包含以下文件和目录,如果存在,它们必须具有以下名称:

  • includeAUTHORS文件,列出项目的所有贡献者。
  • 文件,指代本规范并提供有关项目的其他必要信息。doc目录,包含文档。
  • 内部头文件 (src/myp_classes.h).

)

  • 项目建议安装以下文件:
  • 项目头文件以及构成公共 API 的所有类头文件。项目库,以项目前缀命名(POSIX 平台上为libmyp.a,Windows 上为myp.dll
  • )。

依赖项

命令行工具(如果存在)。

项目头文件

项目应至少依赖于 CZMQ (libczmq),后者引入了 ZeroMQ (libzmq),以提供围绕网络、线程、文件系统和其他方面的可移植 API。

  1. 项目应通过头文件提供两种服务:目录,用于存放所有头文件。一组面向类源文件的内部定义,类源文件可以通过一个单一的
  2. #include目录,用于存放所有头文件。一组面向类源文件的内部定义,类源文件可以通过一个单一的

语句来访问。公共 API,调用应用程序可以通过一个单一的#include)语句来访问。src/myp_classes.h这两项服务可合并到一个项目头文件(myproject.h)中,或者可拆分为一个公共头文件(如 myproject.h)和一个内部头文件(如 src/myp_classes.h)。如果需要,项目可进一步细分这些头文件。

公共头文件应按如下方式定义项目的版本号:

//  MYPROJECT version macros for compile-time API detection
#define MYPROJECT_VERSION_MAJOR 1
#define MYPROJECT_VERSION_MINOR 0
#define MYPROJECT_VERSION_PATCH 0

#define MYPROJECT_MAKE_VERSION(major, minor, patch) \
    ((major) * 10000 + (minor) * 100 + (patch))
#define MYPROJECT_VERSION \
    MYPROJECT_MAKE_VERSION(MYPROJECT_VERSION_MAJOR, \
                           MYPROJECT_VERSION_MINOR, \
                           MYPROJECT_VERSION_PATCH)

项目头文件应在包含任何依赖项的相应头文件之后立即断言所需的版本号,如下所示:

#include <czmq.h>
#if CZMQ_VERSION < 10203
1. error "myproject needs CZMQ/1.2.3 or later"
#endif

公共头文件中的定义对调用应用程序以及类源代码可见。公共头文件应 #include 所有构成项目公共 API 的类头文件。

内部头文件中的定义仅对类源代码可见。如果存在,内部头文件应包含公共头文件、所有类头文件以及项目所需的所有系统和库头文件。这里的主要目标是将精细的系统依赖性#include链保留在单个位置,并远离类源代码。

README 文件模板

1. Project Title

<One-paragraph statement of the goals of the project, and the problems it aims to solve>

## References

* Contribution policy is defined by C4 (https://rfc.zeromq.cn/spec:21).
* Project style guide is defined by CLASS (https://rfc.zeromq.cn/spec:14).
 * short name: <title>
 * prefix: <prefix>
* Licensed under <license name>, see COPYING
* Language level: C99

语言级别

项目建议使用 C99 语言以获得最佳清晰度,但可使用 C89 语言以兼容旧平台。语言级别应在项目中注明:项目应具有以下简称和缩写:,并且所有源代码应符合该级别。

注意:Microsoft Visual C/C++ **不**支持 C99,项目必须使用 C++ 语言扩展来访问 C99 语法。因此,项目不建议使用任何不是 C++ 严格子集的 C99 语法。

预处理器的使用

项目源代码不建议包含项目头文件以外的任何头文件。这确保了所有类源代码在完全相同的环境中编译。

项目源代码不应定义“魔术数字”(数字常量);这些数字应在外部或内部头文件中定义,视情况而定。

项目可将预处理器用于以下目的:

  • 为了与旧代码创建向后兼容性。
  • 通过例如将不可移植的系统调用映射为更可移植的,以提高可移植性。
  • 创建具有高可用性的精确、小型宏。

项目不建议将预处理器用于其他工作,除非它能显著降低代码的复杂性。

宏名称在表示常量时应使用大写,在充当函数时应使用小写。

类风格

文件组织

每个类应编写为两个文件:

  • 一个头文件include/myp_myclass.h
  • 一个源文件src/myp_myclass.c

这两个文件应是该类的原始文档。具体而言,类头文件应定义该类的 API,而类源文件应定义每个方法的实现。

类名称应遵循通用命名风格。我们将使用:myclass在示例中。

每个源文件和头文件应以适当的文件头开始,其中至少应包含:

  • 类或文件的名称及其目的
  • 类的版权声明
  • 项目名称和相关 URL(如果适用)
  • 许可证摘要声明

以下是一个 MPLv2 开源项目的文件头模板:

/*  =========================================================================
    <name> - <description>

    Copyright (c) the Contributors as noted in the AUTHORS file.
    This file is part of MYPROJ, see https://github.com/MYORG/MYPROJ.

    This Source Code Form is subject to the terms of the Mozilla Public
    License, v. 2.0. If a copy of the MPL was not distributed with this
    file, You can obtain one at http://mozilla.org/MPL/2.0/.
    =========================================================================
*/

类类型

我们定义两种类型的类:

  • 有状态类,该类提供处理实例的方法,这些实例类似于面向对象语言中的“对象”。
  • 无状态类,该类提供完全依赖于调用者或系统提供的数据的方法。

有状态类应提供以下方法:

  • 构造函数方法myp_myclass_new ()
  • 析构函数方法myp_myclass_destroy ()
  • 自测试方法myp_myclass_test ()

有状态类可提供以下方法,并且在提供此类功能时应使用这些名称:

  • 复制器方法myp_myclass_dup ()
  • 一组列表导航方法myp_myclass_first ()myp_myclass_next ().
  • 打印方法myp_myclass_print ()myp_myclass_fprint ().

无状态类应至少提供此方法:

  • 自测试方法myp_myclass_test ()

方法名称

方法名称应遵循通用命名风格。方法名称建议是动词(如“destroy”、“insert”、“lookup”)或形容词(如“ready”、“empty”、“new”)。方法名称建议暗示方法返回类型,其中动词返回成功/失败指示符(如果返回),而形容词返回一个值或实例。

类头文件

类头文件应具有以下布局:

  • 文件头
  • 一个外部#ifndef,使其可以安全地多次包含头文件
  • C++ 调用约定
  • 类的类型前向声明,用于有状态类
  • 类方法原型

以下是有状态类的一个头文件模板,不显示文件头:

#ifndef __MYMOD_H_INCLUDED__
#define __MYMOD_H_INCLUDED__

#ifdef __cplusplus
extern "C" {
#endif

//  Opaque class structure
typedef struct _myp_myclass_t myp_myclass_t;

//  Create a new <class name> instance
CZMQ_EXPORT myp_myclass_t *
    myp_myclass_new (void);

//  Destroy a <class name> instance
CZMQ_EXPORT void
    myp_myclass_destroy (myp_myclass_t **self_p);

//  Self test of this class
void
    myp_myclass_test (bool verbose);

#ifdef __cplusplus
}
#endif

#endif

以下是无状态类的一个类似头文件模板:

#ifndef __MYMOD_H_INCLUDED__
#define __MYMOD_H_INCLUDED__

#ifdef __cplusplus
extern "C" {
#endif

//  Self test of this class
int
    myp_myclass_test (bool verbose);

#ifdef __cplusplus
}
#endif

#endif

所有公共方法应在类头文件中使用CZMQ_EXPORT声明,以便这些方法在需要它们的操作系统上正确导出。

类源文件

类源文件应定义:

  • 类结构,用于有状态类。此结构应是不透明的,并且仅为类源文件中的代码所知晓。

  • 类方法,顺序与类头文件中定义的相同:构造函数、析构函数、其他方法,最后是自测试。

  • 类方法中使用的任何静态函数。

  • 所需的任何全局或静态变量。

类属性

对于有状态类,类结构具有一个或多个属性,这些属性在类源文件中定义为一个私有 C 结构。

这建议按如下方式定义:

//  Structure of our class

struct _myclass_t {
    <type> <name>;              //  <description>
};

属性名称应遵循通用命名风格。属性名称建议是名词或形容词(通常用于布尔属性)。我们将使用:myprop在示例中。

方法风格

通用规则

参数名称

参数名称应与属性名称保持一致。

返回值

成功/失败应通过返回一个 int 来指示,其值分别为零或 -1。

字符串在传递给调用者时应作为“char *”返回,调用者必须释放它们。

当调用者不能修改或释放字符串时,它们应作为“const char *”返回。

复合返回值,例如指定大小的缓冲区,建议作为合适类的新对象返回。API 不建议通过多种途径返回复合值,例如通过参数返回数据,通过返回值返回大小。

自测试方法

在无状态类中,唯一的标准方法是 myp_myclass_test(),它应对该类进行自测试,成功时静默返回,失败时断言。myp_myclass_test ()自测试方法应采用以下通用形式:

自测试方法应作为该类用户示例代码的主要来源。

//  --------------------------------------------------------------------------
//  Runs selftest of class

void
myp_myclass_test (int verbose)
{
    printf (" * myp_myclass: ");
    //  Conduct tests of every method
    printf ("OK\n");
}
  • 自测试方法建议覆盖类中的所有其他方法。
  • 构造函数方法

有状态类

构造函数应采用以下通用形式:

构造函数应初始化新类实例中的所有属性。属性应获得合适的初始值,或设置为零。非常大的属性出于性能原因可例外地不进行初始化;此类行为必须在构造函数体中明确注明。

//  Create a new myp_myclass instance
myp_myclass_t *
myp_myclass_new (<arguments>)
{
    myp_myclass_t *self = (myp_myclass_t *) zmalloc (sizeof (myp_myclass_t));
    assert (self);
    self->someprop = someprop_new ();
    assert (self->someprop);
    return self;
}
  • 任何动态分配的属性建议在构造函数中分配,但可保留为 null。

  • 构造函数可接受一个或多个参数,这些参数应对应于要初始化的属性。

  • 如果构造失败,构造函数应返回新的实例引用,或者 null。

  • 析构函数方法

析构函数应采用以下通用形式:

析构函数应将提供的实例引用置为 null。

//  Destroy a myp_myclass instance
void
myp_myclass_destroy (myp_myclass_t **self_p)
{
    assert (self_p);
    if (*self_p) {
        myp_myclass_t *self = *self_p;
        someprop_destroy (&self->someprop);
        anotherprop_destroy (&self->anotherprop);
        lastprop_destroy (&self->lastprop);
        free (self);
        *self_p = NULL;
    }
}
  • 析构函数应是幂等的,即可以安全地在同一个实例引用上多次调用。
  • 析构函数应安全地释放非 null 的属性和子类实例。
  • 复制器方法

该类可提供一个复制器方法,用于创建实例的完整副本;如果提供此类语义,则该方法必须称为

myp_myclass_dup ()myp_myclass_dup ()并采用以下通用形式:

//  Create a copy of a myp_myclass instance

myp_myclass_t *
myp_myclass_dup (myp_myclass_t *self)
{
    if (self) {
        assert (self);
        myp_myclass_t *copy = myp_myclass_new (...);
        if (copy) {
            //  Initialize copy
        }
        return copy;
    }
    else
        return NULL;
}
  • 复制器应以与构造函数相同的方式返回新的实例引用,或者在构造失败时返回 null。

  • 复制器应接受 null 实例引用,然后返回 null。

  • 复制的实例应完全独立于原始实例(即所有属性应也被复制)。

列表导航方法

类可用作其他项的列表容器,这些项可能是子类实例、字符串、内存块或其他结构。

此类容器类应在实例中保存列表光标位置,并提供以下列表导航方法:

//  Return first item in the list or null if the list is empty

item_t *
myp_myclass_first (myp_myclass_t *self)
{
    assert (self);
    //  Reset cursor to first item in list
    return item;
}

//  Return next item in the list or null if there are no more items

item_t *
myp_myclass_next (myp_myclass_t *self)
{
    assert (self);
    //  Move cursor to next item in list
    return item;
}
  • 导航方法应返回 null 以指示“没有更多项”。

  • 导航方法应是幂等的,特别是,在列表末尾调用 myp_myclass_next()myp_myclass_next ()时应每次都返回 null。

  • 类可提供myp_myclass_last ()myp_myclass_prev ()

  • 类可提供myp_myclass_size (),返回列表大小。

  • 如果类提供创建列表项的方法,这些方法应称为myp_myclass_append ()(添加到列表末尾)和myp_myclass_insert ()(添加到列表开头)。

  • 如果类提供删除列表项的方法,该方法应称为myp_myclass_delete ();它应接受项引用作为参数,并且如果存在匹配项,它应删除列表中的第一个匹配项。

  • 如果类维护多个列表,它应通过添加列表名称来为每个列表创建独特的方法名称,例如,myp_myclass_myitem_first ().

访问器方法

类可通过其 API 公开实例属性,在这种情况下,这应通过访问器方法完成。

要返回属性的值,类应定义一个访问器方法,如下所示:

//  Return the value of myprop
<type>
myp_myclass_myprop (myp_myclass_t *self)
{
    assert (self);
    return self->myprop;
}

如果允许写入属性的值,类应定义一个访问器方法,如下所示:

//  Set the value of myprop
void
myp_myclass_set_myprop (myp_myclass_t *self, <type> myprop)
{
    assert (self);
    self->myprop = myprop;
}
  • 通过访问器方法公开的属性可能实际上并不以这种方式存在于实例中;它们可能是计算得出的,而不是简单地复制到/从实例结构中。

格式化字符串参数

当方法(例如访问器方法)接受字符串参数作为主要参数时,它建议使用可变参数列表并对该字符串参数执行 vsnprintf 格式化。

通用方法

类可提供任意数量的其他操作实例的方法。这些方法应采用以下通用形式:

  • 方法的第一个参数应是实例引用。
  • 后面可以跟其他参数。

方法可以获取对象实例的所有权,然后在稍后阶段充当该对象实例的析构函数。在这种情况下,该方法应使用与析构函数相同的风格。

返回值

方法建议使用以下模式之一将值返回给调用者:

  • 不返回任何内容(如果不需要返回值)。
  • 在访问器方法上返回属性值。
  • 在构造函数或复制器上返回对象实例。
  • 在列表导航方法上返回子值。
  • 成功时返回零,失败时返回 -1。
  • 返回一个新分配的字符串。

代码风格

线程安全

  • 所有方法应是线程安全的。

  • 类实例通常不建议是线程安全的;类实例将由单个调用线程拥有。

  • 在特殊情况下,通过在方法内部添加互斥锁或锁,类实例可被设置为线程安全。

堆内存使用

CLASS 的目标之一是尽可能地在类内部隐藏堆内存的使用。应用程序建议仅通过构造函数和复制器(包括库strdup ()函数)使用堆。类方法可谨慎地使用堆,但应遵循以下规则:

  • 当类实例被销毁时,它使用的所有堆内存必须被释放。除了异常终止(例如,断言失败)期间,类不应在任何条件下发生内存泄漏。

  • 非原子属性建议在修改它们的访问器函数中根据需要重新分配(即,释放后重新分配)。

  • 实例结构可使用 char 数组代替堆分配的 char 指针。

  • 在析构函数之外释放非原子属性时,如果方法没有立即分配新值,则必须将该属性设置为 null。

静态变量

除了特殊情况(例如全局变量),类不建议使用静态变量。

静态变量不是线程安全的,因此被认为是糟糕的实践。

特别是为了表示类体内的任何临时状态,应使用栈变量代替静态变量。

静态函数

未由类导出的函数被定义为static并命名为s_functionname (),不使用项目前缀或类名称。

静态函数可在首次使用前定义,或者可在首次使用后立即声明原型并定义。

静态函数不建议收集在类源代码的末尾。

代码风格

缩进

缩进应每级 4 个空格。代码中不应使用 Tab 字符。

声明

函数应按如下方式声明原型:

<type>
    <name> (<arguments>);

函数应按如下方式定义:

<type>
<name> (<arguments>)
{
    <body>
}

当项目使用 C99 时,栈变量应内联定义,尽可能靠近首次使用,并初始化。例如:

myp_myclass_t *myclass = myp_myclass_new ();
char *comma = strchr (surname, '.');

当项目使用 C89 时,栈变量应全部在使用它们的函数或方法的开头定义和初始化。

  • 变量和函数应使用小写名称。

  • 如有必要,应使用下划线分隔名称的不同部分。

  • 变量名称,例如itemp不携带信息的,不应使用。

语句

超过 80-100 个字符的代码行建议为提高可读性而折叠。

单语句块不应用括号括起来。

这是一个单语句块的形式:

if (comma == NULL)
    comma = surname;

else语句中,else 关键词else应单独占一行。

多个if/else测试应垂直堆叠,以表明顺序是任意的。

这是一个堆叠的ifif 语句块的形式:

if (command == CMD_HELLO)
    puts ("hello");
else
if (command == CMD_GOODBYE)
    puts ("goodbye");
else
if (command == CMD_ERROR)
    puts ("error");

对于多语句条件块,右花括号应单独占一行,并与起始关键词对齐。

这是一个堆叠的if这是一个带括号的条件语句块的形式:

if (command == CMD_HELLO) {
    puts ("hello");
    myp_peer_reply (peer, CMD_GOODBYE);
}
else
if (command == CMD_GOODBYE) {
    puts ("goodbye");
    myp_peer_reply (peer, CMD_DISCONNECT);
}
else
if (command == CMD_ERROR) {
    puts ("error");
    myp_peer_close (peer);
}

这是一个while语句的形式:

char *comma = strchr (surname, ',');
while (comma) {
    *comma = ' ';
    comma = strchr (surname, ',');
}

注释

代码注释应轻量使用且仅在必要时使用。

在 C99 项目中,注释的语法是:

  • 行内注释应使用 C++//风格。

  • 多行注释可使用 C/* … */风格,或可使用 C++ 风格。

在 C89 项目中,所有注释的语法应是 C 的 /* … */ 风格。/* … */风格。

  • 如果可能,行内注释应从第 33 列开始。

  • 在行内注释中,////LICENSE/*应跟随两个空格。

  • 每个函数应有一个多行注释头,简要说明其目的。

  • 方法注释头应在其前面有一行连字符,该行结束于第 78 列。

  • 函数前适当标记的注释可用作参考文档的源材料。

这是方法注释头的通用模板:

//  --------------------------------------------------------------------------
//  Finds the first item in the list, returns null if the list is empty.

myp_myclass_t *
myp_myclass_first (myp_myclass_t *self)
{
    ...
  • 类结构中的每个属性应有一个 1 行的行内注释,描述其目的。
  • 注释不应用于弥补难以阅读的代码。
  • 普通读者无法合理阅读和理解的代码建议重写,而不是添加注释。
  • 名称中语义不明确的属性和函数建议重命名,而不是添加注释。

空行

应使用空行分隔代码块以提高可读性,在以下情况下:

  • 在函数体的右花括号之后以及函数注释头之前。
  • 分隔超过 6-8 行的代码块。
  • 在类体开头的断言之后。
  • 在一个 ifif语句后跟单语句块时。
  • 在多行case块之后,在switch一组面向类源文件的内部定义,类源文件可以通过一个单一的
  • 语句中。

在多行注释块之后。

  • 在以下情况下不应使用空行:
  • 在条件块的右花括号之后。

垂直对齐

分隔可以更好地组合在一起的单个代码行。

char *comma = strchr (surname, ',');
while (comma) {
    *comma = ' ';
    comma = strchr (surname, ',');
}

标点符号

代码不应使用额外的空格来创建垂直对齐。

标点符号应尽可能遵循英语规则。

char_nbr++;

这是一元运算符的风格,运算符后面有空格,前面没有:

comma = comma + 1;

这是二元运算符的风格,运算符前后都有空格:?:这是

comma = comma? comma + 1: strchr (name, '.');

->

for (char_nbr = 0; *char_nbr; char_nbr++)
    char_nbr++;

运算符的风格:

node = (node_t *) zmalloc (sizeof (node_t));
if (!node)
    return -1;

这是分号的风格,后面有空格,前面没有:

comma = name [char_nbr];

这是圆括号的风格,左括号前有空格,右括号后有空格,多个左括号或右括号紧密相连,中间没有空格:

self->name = strdup (name);

断言

这是方括号的风格:

  • 这是指针解引用的风格,‘->’ 前后没有空格:

  • 类建议使用断言检查参数的有效性。也就是说,对 API 的误用被视为编程错误,而非运行时错误。

  • 断言应用于其文档价值,例如警告读者:“此参数不应为 null”。

  • 对参数的断言应位于类体的开头,并应遵循参数的顺序。

  • 如果代码无法安全处理函数调用的失败情况,可对返回值使用断言。

  • 可对内部状态(例如,实例属性)使用断言,以断言继续执行的必要条件。

  • 断言不应用于捕获外部条件错误,例如,错误的用户输入、无效的协议消息等。

函数退出与 Goto 语句

断言建议用于捕获内部 API 错误,例如从一个线程发送到另一个线程的无效消息。断言不应产生副作用,因为整个语句可能被优化编译器移除。

return语句可用于函数中的任何位置以返回给调用者。如果函数需要进行清理(例如,释放多个属性),代码可使用

goto断言不应产生副作用,因为整个语句可能被优化编译器移除。一组面向类源文件的内部定义,类源文件可以通过一个单一的

  • 和一个位于函数末尾的单一清理块。这样的清理块应跟随最后一个“正常”的 return 语句。一个 void 函数不应以空的 return 结束。开放式循环的推荐模式是 while (true) {},并使用

  • break

for (array_index = 0; array_index < array_size; array_index++) {
    //  Access element [array_index]
}
  • 语句来按需退出循环。
myp_myclass_t *myclass = (myp_myclass_t *) myp_myclass_first (myclass);
while (myclass) {
    //  Do something
    myclass = (myp_myclass_t *) myp_myclass_next (myclass);
}

可移植性

可移植类与原生类

所有项目必须至少依赖 ZeroMQ (libzmq) 和 CZMQ (libczmq),它们提供了围绕网络、线程、文件系统及其他方面的可移植 API。

  • 一个类必须是“可移植的”或“原生的”。

  • 可移植类不得使用预处理器在不同系统上进行不同的编译。

  • 原生类必须导出一个恰当抽象的 API 以隐藏系统差异,并且必须使用预处理器在不同系统上进行不同的编译。

  • 原生类必须使用定义在czmq_prelude.h文件中的预处理器宏,特别是__WINDOWS__, __UNIX____UTYPE_ABC等宏。

  • 原生类不得使用任何特定构建系统提供的预处理器宏。如果 CZMQ 提供的宏不够用,可以对其进行改进和扩展。

  • 项目架构师应该致力于完全分离可移植类和原生类,以便应用程序开发者只看到并编写可移植类。

此示例展示了原生代码的一般风格。

#if (defined (__UNIX__))
    pid = GetCurrentProcessId ();
#elif (defined (__WINDOWS__))
    pid = getpid ();
#else
    pid = 0;
#endif

可移植语言

以下类型和宏由 CZMQ 定义,可在所有代码中安全使用:

  • bool, true, false:布尔数据类型和常量。
  • byte, dbyte, qbyte:无符号 1、2 和 4 字节整数。
  • uint, ulong:无符号整型和长整型。
  • int32_t, int64_t:有符号 32 位和 64 位整数。
  • uint32_t, uint64_t:无符号 32 位和 64 位整数。
  • streq (s1, s2):优先于strcmp (s1, s2) == 0.
  • strneq (s1, s2):优先于:优先于 strcmp (s1, s2) != 0.
  • randof (number):返回范围 0 .. number - 1 内的随机整数。
  • srandom:通常这样使用:srandom ((unsigned) time (NULL));
  • inline, snprintf, vsnprintf:Windows 使用带下划线的非 POSIX 变体。

编译器警告

编译器警告应该始终被视为致命错误。以下是已知在某些(但不是所有)编译器上会引起警告的结构列表:

  • 将 void 指针赋值给有类型指针时未进行类型转换。在将 void * 赋值给有类型指针之前,请始终进行类型转换。

  • 在非 void 函数中未能返回值。非 void 函数末尾必须始终有断言不应产生副作用,因为整个语句可能被优化编译器移除。一组面向类源文件的内部定义,类源文件可以通过一个单一的

代码生成

return 语句。

  • 在有显著收益的情况下,可以使用代码生成来机械地生成类。

  • 代码生成器应该是 GSL,来自 https://github.com/imatix/gsl目录,用于存放所有库源文件。所有代码生成脚本必须位于项目

  • subdirectory 子目录中。目录,用于存放所有库源文件。所有模型数据 (XML 文件)必须位于项目

  • directory 目录中。如果只生成类的部分代码,这些部分必须使用扩展名.inc目录,用于存放所有库源文件。,并且必须生成到项目

  • directory 目录中,并且必须使用 #include 语句包含到类源文件中。

  • 代码生成必须作为手动步骤完成。例如,“make code”。所有生成的代码必须像手写文件一样提交到项目中。

  • 代码生成必须完全幂等,也就是说,生成的代码不得包含任何日期或时间戳。

  • 代码生成必须被视为一种危险的抽象形式,它会给读者带来显著的障碍。一个好的经验法则是,代码生成要有利可图,它应该将手写的代码行数减少 80-90%。

  • 生成的文件开头必须包含以下形式的警告:“GENERATED SOURCE CODE, DO NOT EDIT”(生成的源代码,请勿编辑)。

安全方面

线程安全

生成的文件必须在其他方面符合本规范,使其与手写代码无法区分。

  • 通过引用访问不透明数据结构是线程安全的。但是,代码不得在线程之间共享状态,除非在特殊和有限的情况下。线程必须通过传递 0MQ 消息进行通信。

  • 不得使用静态变量,因为这不可重入,因此不是线程安全的。

  • 类实例不得在线程之间传递,除非是“移交”情况。

  • 代码不应该使用互斥锁、锁或其他机制在线程之间共享状态。

  • 代码绝不能使用非线程安全的系统调用,例如:basename ().

缓冲区溢出

  • 代码必须始终截断过长的数据。

  • 代码绝不能使用不安全的系统调用,例如:gets ().

已知缺陷

  • 对堆内存的过度依赖意味着 CLASS 应用程序容易受到拒绝服务攻击。应用程序可以通过限制创建的类实例数量来减轻此风险。

  • 对堆内存的过度依赖使得 CLASS 不适用于所有内存使用都必须是静态的嵌入式系统。

  • 在大多数 CLASS 应用程序中,除了中止之外,很难以其他方式处理“内存不足”错误。