文档

如何贡献

我们如何协作

ZeroMQ 社区在其核心项目:libzmq 和稳定版本(zeromq2-x、zeromq3-x、zeromq4-x),以及像 CZMQ 这样的周边项目中,使用 C4.1 流程(带有一些注意事项)。

请务必花时间阅读 C4.1 RFC,和/或阅读 ZeroMQ 指南第六章中的逐行解析。

同一章节中还有一个包含所有命令的创建补丁的示例。

Git 和 GitHub 入门

Github 提供了一份关于如何贡献开源项目的优秀指南。请首先阅读 https://githubdocs.cn/en/get-started/quickstart/contributing-to-projects 页面。

贡献示例

ZeroMQ 指南中有一个向 libzmq 贡献补丁的详细分步示例

编写好的提交消息

提交消息成为您的更改的公开记录,因此编写清晰的提交消息非常重要。Git 提交消息的基本格式如下:

  • 以“Problem: ”开头的一行摘要。这应该尽量简短——不超过 70 个字符左右,因为它可以用作提交补丁时的电子邮件主题,也可以用于通过 'git format-patch' 生成补丁文件名。如果您的更改只涉及单个文件或子系统,您可以在摘要前加上文件或子系统的名称。
  • 一个空行。
  • 以“Solution: ”开头的详细描述。尽可能使用现在时态编写,例如“Add assertions to zmq_foo()”。如果您的更改并非基于邮件列表上的先前讨论,您可能也希望包含对您更改的简要理由。您的描述应采用纯文本格式,每行不超过 80 个字符。

示例

Problem: Windows build script requires edit of VS version

Solution: Use CMD.EXE environment variable to extract
DevStudio version number and build using it.

libzmq 的编码风格

如果您的贡献目标是 libzmq,请遵循本指南的编码风格。另请注意,clang-format 用于自动验证和强制执行正确的格式。

通用规则

  • 最大行长度限制为 80 个字符。
  • 如果一个语句必须断成两行,第二行(以及后续任何行)应缩进一个层级(控制语句除外;见下文)。
  • 缩进总是使用空格,从不使用制表符。
  • 一个缩进层级长 4 个字符。
  • 每个文件以包含简短许可证文本的头部开始。

注释

允许使用 C 和 C++ 风格的注释。

  • C 风格注释 (/* */) 应用于大块注释,主要在文件开头使用。
  • 代码本身应使用 C++ 行注释 (//) 进行注释。
  • 代码应该分解成小块(每块几行),每块完成一个简单的任务。
  • 每个代码块前面应有一个解释其意图的注释。
  • 每个代码块后面应跟一个空行。
  • C++ 风格行注释以两个斜杠后跟两个空格开始。
  • 注释文本以大写字母开头,并以句点结尾。
//  Compute the factorial.
int factorial = 1;
for (int i = 2; i != 11; i++)
    factorial *= i;

//  Present the result to the user.
cout << "Factorial of 10 is " << factorial << "." << endl;

标识符

标识符使用小写。如果标识符由多个单词组成,单词之间用下划线分隔。

标识符应具有意义,而非随意命名。唯一的例外是短循环的控制变量,可以使用 iit 等短名称。

int sum = 0;
for (int i = 0; i != 100; i++)
     sum += i;

不应在标识符中明确标记变量的类型。即不使用匈牙利命名法。

函数参数应以下划线结尾。这样可以区分函数参数和同名的成员变量。

struct complex_t
{
    complex_t (float real_, float imaginary_) :
        real (real_),
        imaginary (imaginary_)
    {
    }

    float real;
    float imaginary;
};

类型的标识符(结构体、类、枚举、typedef)后缀为“_t”(例如 complex_t)。

代码块

对于函数、结构体、类、枚举和命名空间,开括号和闭括号都放在单独的行上。

void fx ()
{
    //  Code goes here.
}

对于 forwhileifelse 块,开括号与控制语句在同一行。

if (sum > 1000) {
    //  Code goes here.
} else {
    //  More code.
}

即使 forwhileifelse 块只包含单个语句,该语句也应放在单独的行上。

if (end)
    exit (1);

如果 if 块包含 else 部分,始终为两部分都写上花括号。

如果受控块的缩进偶然与断成两行的控制语句发生冲突,控制语句的第二行应缩进 6 个空格而非 4 个空格,以避免混淆。

if (very_long_variable_name == 10000 &&
      another_horrendous_variable_name == 1000000 &&
      ludicrously_long_variable_name == 1000000000)
    counter++;

运算符

一元运算符与表达式之间不应有任何分隔。

counter++;

二元运算符应与相邻表达式之间用空格分隔。

z = x + y;

字符串连接 (PHP) 应使用空格分隔。

$foo = $bar . " " . $bat;

下标 (函数调用、数组成员)

下标和函数调用与表达式之间用空格分隔。

fx (my_array [0]);

相同的间距规则适用于函数定义、直接初始化、初始化列表和函数式运算符。

struct foo_t
{
    foo_t (int a_, int b_) :
        a (a_),
        b (b_)
    {
    }

    void foo (int n) const;
};

foo_t *p = new foo_t (1, 2);
void *q = std::malloc (sizeof (foo_t));

C++ 风格类型转换的类型参数之间也用空格分隔。

std::intptr_t n = reinterpret_cast <std::intptr_t> (&x);
char *p = static_cast <char *> (q);

标准库头文件

不要使用已弃用的头文件(如 <stddef.h>)。请改用 C++ 头文件:<cstddef>

始终限定所有标准库名称:std::size_t, std::memcpy 等。

包含守卫和全局文件结构

文件开头或结尾不要留下多余的空行。文件不应以换行符开头,每个文件应以恰好一个换行符结尾。

包含守卫之间用一个换行符分隔。

/* Initial comments */

#ifndef GUARD
#define GUARD

namespace xyz
{
    // main content
}

#endif

头文件 foo.hpp 的包含守卫应为 __ZMQ_FOO_HPP_INCLUDED__。(实际上,以双下划线开头的名称是保留的,从技术上讲,任何此类名称的定义都会导致程序格式错误。我们需要更改此规则并修复所有头文件。)

杂项间距

构造函数初始化列表的冒号周围有空格。

指针和引用修饰符的间距尚未完全确定,T* pT *p 都可以接受。但在同一个文件中使用一种固定的风格。后一种情况下,类型转换也需要空格:static_cast <T *>

异常

  • 断言表明 0MQ 开发者不期望这种情况发生,即这是一个内部错误,需要修复 0MQ 代码。
  • 错误表明 0MQ 开发者期望这种情况在应用程序中发生,即这是一个外部错误,建议修复应用程序。
  • 核心代码库中不得使用 C++ 异常。

公共接口

组件的公共接口不得由接口内部的代码调用。

例如,这不被允许

int f(int x) { return g (x); }
int fsucc(int x) { return f (x) + 1; }

将公共接口保留给外部调用者对于维护公共不变性是必要的,同时允许封装的实现操作符在临时使用较弱的不变性。即使一个原本正确的调用,其存在也会阻碍简单的代码更改,特别是外部跟踪和性能分析工具提供的透明干预。我们不想混淆对函数的公共调用和内部调用,也不想在性能分析时计算内部调用。

0MQ 系统要求 socket I/O 满足一个不变性:任何两个线程不能同时执行 I/O。保持这一不变性有两种方式:由客户端负责,或者由 0MQ 负责。后一种情况下,用互斥锁包装函数调用体就足够了,但是如果一个执行此操作的公共 API 调用了另一个公共 API,就会立即发生死锁。