如何贡献
我们如何协作
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;
标识符
标识符使用小写。如果标识符由多个单词组成,单词之间用下划线分隔。
标识符应具有意义,而非随意命名。唯一的例外是短循环的控制变量,可以使用 i
、it
等短名称。
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.
}
对于 for
、while
、if
和 else
块,开括号与控制语句在同一行。
if (sum > 1000) {
// Code goes here.
} else {
// More code.
}
即使 for
、while
、if
或 else
块只包含单个语句,该语句也应放在单独的行上。
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* p
或 T *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,就会立即发生死锁。