关于 zig-tiger-style
这是一项从 TigerBeetle 的 TIGER_STYLE.md 提炼而来的 Zig 编码规范技能,面向 Zig 开发者,涵盖安全、性能、命名、格式与代码审查实践。
主要功能
- 01明确的控制流与断言
- 02有界循环与完整错误处理
- 03内存和性能设计指导
- 04命名、注释与文件布局规则
- 05格式规范和提交前检查清单
适用场景
- 01检查 Zig 代码的安全实践
- 02为 Zig 项目制定团队规范
- 03提交前核对代码
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ zigcc/ skills --skill zig-tiger-style安装后在 Claude Code 中输入/zig-tiger-style确认已加载。
详细介绍
TigerStyle:Zig 编码指南
摘编自 TigerBeetle 的 TIGER_STYLE.md。 设计目标优先级:安全性 > 性能 > 开发者体验。
1. 安全性
控制流
-
仅使用简单、明确的控制流。除非能证明递归有界,否则不要使用递归。
-
将复合条件拆分为嵌套的
if/else分支——确保正向和反向情况都得到处理或断言。 -
以肯定的方式表述不变量:
// preferred if (index < length) { ... } else { ... } // avoid if (index >= length) { ... } -
每个
if分支都应引发这样的思考:是否还需要处理相应的else?
断言
断言用于检测程序员错误,而不是预期中的运行时错误。状态损坏时,唯一正确的响应是崩溃。断言会将灾难性的正确性错误降级为活性错误。
-
函数不得盲目处理未经检查的数据;应在入口处断言参数。
-
成对断言:对于要强制执行的任何属性,都应在至少两条不同的代码路径上添加断言(例如,写入磁盘前,以及读回后立即断言)。
-
拆分复合断言:
// preferred assert(a); assert(b); // avoid assert(a and b); -
使用单行
if断言蕴含关系:if (a) assert(b); -
断言编译期常量之间的关系,以便在程序运行前验证设计完整性:
comptime assert(@sizeOf(Header) == 128); comptime assert(config.pipeline_max <= config.batch_max); -
同时断言正向情况(预期为真的情况)和反向情况(预期为假的情况)——漏洞往往藏在有效与无效的边界处。
内存
-
通过输出指针就地初始化大型结构体,以消除中间副本并保证指针稳定性:
// preferred fn init(target: *LargeStruct) !void { target.* = .{ ... }; } // avoid fn init() !LargeStruct { return LargeStruct{ ... }; }
变量作用域
- 在尽可能小的作用域中声明变量,以降低误用的可能性。
- 在接近使用位置的地方声明变量——不要在需要之前引入变量。这样可以避免 POCPOU 错误(TOCTOU 的远房表亲)。
循环和队列
- 所有循环和队列都必须有固定的上限,以防止无限循环或尾延迟尖峰。遵循快速失败原则。
- 确实无法终止的循环(例如事件循环)必须明确断言其不会终止。
错误处理
- 必须处理所有错误。 大多数灾难性的生产故障都源于对非致命错误处理不当。
- 切勿使用
_丢弃错误返回值。
其他
- 使用明确指定大小的整数类型(
u32、i64等),尽可能避免依赖架构的usize - 启用并遵守编译器最严格的警告设置——对警告零容忍。
- 不要在内联代码中直接响应外部事件;让程序按照自己的节奏运行(这样可以进行批处理,并保持对控制流的掌控)。
- 尽可能缩小函数规模。 拆分时,应选择语义清晰的切分点:
- 将所有
if/switch集中在“父”函数中;将纯逻辑提取到辅助函数中。 - 由父函数管理所有可变状态;辅助函数负责计算要更改的内容,但不应用更改。
- 经验法则:"把
if往上提,把for往下放"。
- 将所有
2. 性能
-
在设计阶段解决性能问题——最大的提升(1000 倍)来自架构,而非事后分析。
-
针对四种资源(网络、磁盘、内存、CPU)及其两种特性(带宽、延迟)进行粗略估算。
-
优先优化最慢的资源:网络 → 磁盘 → 内存 → CPU,并根据访问频率加权。
-
批处理是首要手段:摊销网络、磁盘、内存和 CPU 成本。
-
区分控制平面和数据平面;批处理让两者能够安全、高效地共存。
-
将热路径循环提取为独立函数,并使用原始类型参数(不含
self),以便编译器将字段缓存在寄存器中,也便于人们发现冗余工作:// hot loop extracted, no self fn process_batch(items: []const Item, result: []Output) void { ... } -
明确表达意图。不要依赖编译器做出正确处理。
-
在库调用处始终显式传递选项——绝不依赖默认值:
// preferred @prefetch(a, .{ .cache = .data, .rw = .read, .locality = 3 }); // avoid @prefetch(a, .{});
3. 命名
-
通常,函数采用
camelCase,类型采用PascalCase,变量采用lowercase_with_underscores。 但返回类型的函数是个例外,采用PascalCase:pub fn ArrayList(comptime T: type) type { return ArrayListAligned(T, null); } -
通常,文件名采用
lowercase_with_underscore。但是,直接公开类型的文件应采用PascalCase。 -
不要缩写变量名(排序和矩阵中的原始整数循环索引除外)。
-
首字母缩略词应全部大写:
VSRState,而非VsrState。 -
在名称中附加单位和限定词,并按重要性从高到低排序,让最重要的词排在最前面:
latency_ms_max // not max_latency_ms latency_ms_min // aligns nicely with the above message_size_max -
为相关名称选择相同的字符数,使其在视觉上对齐:
source // same length as target target source_offset target_offset -
辅助函数和回调函数的名称应以调用方名称为前缀:
read_sector()→read_sector_callback() -
回调函数放在参数列表的最后(与调用顺序相对应)。
-
让名称富有含义:
gpa: Allocator和arena: Allocator比allocator: Allocator信息量大得多。 -
接受两个或更多同类型参数的函数必须使用命名的
options: struct参数,以避免混淆参数。
结构体和文件布局
// Struct order: fields → type definitions → methods
time: Time,
process_id: ProcessID,
const ProcessID = struct { cluster: u128, replica: u8 };
const Tracer = @This();
pub fn init(gpa: std.mem.Allocator, time: Time) !Tracer { ... }
- 将
main函数放在文件顶部——让读者首先看到最重要的内容。 - 将复杂的嵌套类型提升为顶层结构体。
4. 注释
- 注释应为完整句子:
//后加空格,以大写字母开头,并以句号结尾(引出内容时则用冒号)。 行尾内联注释可以是没有标点的短语。 - 始终说明原因。 代码展示了是什么以及怎么做;注释解释决策背后的理由。
- 在测试文件顶部添加说明,解释测试目标和方法。
- 有时,可以用显而易见为真的断言代替注释,来记录关键且容易令人意外的不变量——断言能提供更有力的文档说明。
5. 格式
-
始终运行
zig fmt。 -
使用 4 个空格缩进(远距离看比 2 个空格更醒目)。
-
每行严格限制为 100 列,没有例外。添加尾随逗号,并让
zig fmt处理换行。 -
if语句始终添加花括号,除非整条语句能放在一行内:// single-line ok without braces if (ok) return; // multi-line always needs braces if (condition) { do_something(); }
除法——明确指定舍入意图
@divExact(a, b) // asserts no remainder
@divFloor(a, b) // rounds toward negative infinity
div_ceil(a, b) // rounds toward positive infinity
6. 差一错误
index(从 0 开始)、count(从 1 开始)和 size(= count × 单位)是不同的类型,转换规则也很明确:
index→count:加 1count→size:乘以单位大小- 在变量名中加入单位和限定词(参见“命名”),使这些转换一目了然。
7. 依赖项和工具
- 零依赖策略:除 Zig 工具链外,不使用外部依赖项。
- 使用
scripts/*.zig而非*.sh编写脚本——跨平台、类型安全且更可靠。 - 统一使用 Zig 作为工具语言,以便团队扩大时降低维度。
提交前检查清单
提交前,请确认:
- 所有行不超过 100 列;已运行
zig fmt - 已处理所有错误(没有使用
_丢弃错误) - 变量名包含单位或限定词,且未使用缩写
- 复合条件已拆分为嵌套的
if/else - 所有循环都有明确的上限
- 注释说明的是原因,而不只是内容
- 已使用
comptime assert验证编译时常量之间的关系