mcpField
Z
zig-tiger-style

zig-tiger-style

github.com/zigcc开发5258 次安装2 周前更新
代码仓库

关于 zig-tiger-style

这是一项从 TigerBeetle 的 TIGER_STYLE.md 提炼而来的 Zig 编码规范技能,面向 Zig 开发者,涵盖安全、性能、命名、格式与代码审查实践。

主要功能

  1. 01明确的控制流与断言
  2. 02有界循环与完整错误处理
  3. 03内存和性能设计指导
  4. 04命名、注释与文件布局规则
  5. 05格式规范和提交前检查清单

适用场景

  1. 01检查 Zig 代码的安全实践
  2. 02为 Zig 项目制定团队规范
  3. 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:加 1
  • count → size:乘以单位大小
  • 在变量名中加入单位和限定词(参见“命名”),使这些转换一目了然。

7. 依赖项和工具

  • 零依赖策略:除 Zig 工具链外,不使用外部依赖项。
  • 使用 scripts/*.zig 而非 *.sh 编写脚本——跨平台、类型安全且更可靠。
  • 统一使用 Zig 作为工具语言,以便团队扩大时降低维度。

提交前检查清单

提交前,请确认:

  • 所有行不超过 100 列;已运行 zig fmt
  • 已处理所有错误(没有使用 _ 丢弃错误)
  • 变量名包含单位或限定词,且未使用缩写
  • 复合条件已拆分为嵌套的 if/else
  • 所有循环都有明确的上限
  • 注释说明的是原因,而不只是内容
  • 已使用 comptime assert 验证编译时常量之间的关系

评论

支持 Markdown