Chapter 55Style Guide Highlights

附录A. 风格指南要点

概览

当团队的命名、注释与模块布局遵循可预测的节奏时,协作更敏捷。本附录提炼“家规”风格为速查,便于在评审 PR 或搭建新模块时随手翻阅。

Zig 0.15.2 收紧格式化输出、稳定文档注释处理并明确测试体验;采用这些默认值意味着更少的约定协商时间与更多的行为验证时间。v0.15.2

学习目标

  • 通过扫描文档注释、类型、函数和测试的规范排序,快速审查模块。
  • 阐述 Zig 中"紧凑错误词汇"的含义,以及何时应优先选择定制错误集而非 anyerror
  • 将确定性测试与所记录的代码并排放置,同时不牺牲大文件的可读性。

参考文献:见各章节末尾

语气与命名速览

可读代码始于叙述与标识符的契合:文档注释应使用与导出符号实现相同的名词,而辅助函数则保持动词简短有力。fmt.zig 遵循此模式使评审者能专注于语义而非争论措辞选择。

命名、注释与 Writer

该示例将模块级叙述与聚焦的文档注释配合使用,并采用固定缓冲 writer,使测试不触及分配器。Io.zig

Zig
// ! 演示小型诊断辅助函数的命名和文档约定。
const std = @import("std");

// / 表示诊断期间捕获的带标签的温度读数。
pub const TemperatureReading = struct {
    label: []const u8,
    value_celsius: f32,

    // / 使用规范的大小写和单位将读数写入提供的写入器。
    pub fn format(self: TemperatureReading, writer: anytype) !void {
        try writer.print("{s}: {d:.1}°C", .{ self.label, self.value_celsius });
    }
};

// / 使用给定标签和摄氏温度值创建读数。
pub fn createReading(label: []const u8, value_celsius: f32) TemperatureReading {
    return .{
        .label = label,
        .value_celsius = value_celsius,
    };
}

test "temperature readings print with consistent label casing" {
    const reading = createReading("CPU", 72.25);
    var backing: [64]u8 = undefined;
    var stream = std.io.fixedBufferStream(&backing);

    try reading.format(stream.writer());
    const rendered = stream.getWritten();

    try std.testing.expectEqualStrings("CPU: 72.3°C", rendered);
}
运行
Shell
$ zig test 01_naming_and_comments.zig
输出
Shell
All 1 tests passed.

在代码前格式化描述性句子鼓励读者一并浏览类型签名与测试;使术语与文档注释保持一致体现了第 36 章的建议。36

紧凑的错误词汇

精确的错误集在兼顾调用者体验与轻量级控制流间取得平衡;我们不返回 anyerror,而是精确列出解析器可达的状态,并将其提升为公共 API 表面。math.zig

Zig
// ! 保持数值解析器的错误词汇紧凑,以便调用者能精确响应。
const std = @import("std");

// / 枚举解析器可以向其调用者报告的故障模式。
pub const ParseCountError = error{
    EmptyInput,
    InvalidDigit,
    Overflow,
};

// / 解析十进制计数器,同时保留描述性错误信息。
pub fn parseCount(input: []const u8) ParseCountError!u32 {
    if (input.len == 0) return ParseCountError.EmptyInput;

    var acc: u64 = 0;
    for (input) |char| {
        if (char < '0' or char > '9') return ParseCountError.InvalidDigit;
        const digit: u64 = @intCast(char - '0');
        acc = acc * 10 + digit;
        if (acc > std.math.maxInt(u32)) return ParseCountError.Overflow;
    }

    return @intCast(acc);
}

test "parseCount reports invalid digits precisely" {
    try std.testing.expectEqual(@as(u32, 42), try parseCount("42"));
    try std.testing.expectError(ParseCountError.InvalidDigit, parseCount("4a"));
    try std.testing.expectError(ParseCountError.EmptyInput, parseCount(""));
    try std.testing.expectError(ParseCountError.Overflow, parseCount("42949672960"));
}
运行
Shell
$ zig test 02_error_vocabulary.zig
输出
Shell
All 1 tests passed.

测试套件证明各分支均可到达,防止“死字符串”,并教会使用者无需阅读实现即可知道应在何处对名称进行switch36

模块布局清单

当文件导出配置助手时,请将公共接口置于前部、将私有校验器收纳其后,并以表驱动测试收尾,使其可读性如同文档。12

Zig
// ! 突出显示具有集中辅助函数和测试的分层模块布局。
const std = @import("std");

// / 规范化用户提供的重试策略时可能出现的错误。
pub const RetryPolicyError = error{
    ZeroAttempts,
    ExcessiveDelay,
};

// / 封装网络客户端的重试行为,包括合理的默认值。
pub const RetryPolicy = struct {
    max_attempts: u8 = 3,
    delay_ms: u32 = 100,

    /// 指示指数退避是否激活。
    pub fn isBackoffEnabled(self: RetryPolicy) bool {
        return self.delay_ms > 0 and self.max_attempts > 1;
    }
};

//  由配置文件或 CLI 标志提供的部分选项。
pub const PartialRetryOptions = struct {
    max_attempts: ?u8 = null,
    delay_ms: ?u32 = null,
};

//  从可选覆盖构建重试策略,同时保持默认推理集中。
pub fn makeRetryPolicy(options: PartialRetryOptions) RetryPolicy {
    return RetryPolicy{
        .max_attempts = options.max_attempts orelse 3,
        .delay_ms = options.delay_ms orelse 100,
    };
}

fn validate(policy: RetryPolicy) RetryPolicyError!RetryPolicy {
    if (policy.max_attempts == 0) return RetryPolicyError.ZeroAttempts;
    if (policy.delay_ms > 60_000) return RetryPolicyError.ExcessiveDelay;
    return policy;
}

//  生成一个经过验证的策略,强调从原始输入到受限输出的流程。
pub fn finalizeRetryPolicy(options: PartialRetryOptions) RetryPolicyError!RetryPolicy {
    const policy = makeRetryPolicy(options);
    return validate(policy);
}

test "finalize rejects zero attempts" {
    try std.testing.expectError(
        RetryPolicyError.ZeroAttempts,
        finalizeRetryPolicy(.{ .max_attempts = 0 }),
    );
}

test "finalize accepts defaults" {
    const policy = try finalizeRetryPolicy(.{});
    try std.testing.expectEqual(@as(u8, 3), policy.max_attempts);
    try std.testing.expect(policy.isBackoffEnabled());
}
运行
Shell
$ zig test 03_module_layout.zig
输出
Shell
All 2 tests passed.

将错误集置于顶部可保持类型图的清晰,并映射 std.testing 在依赖代码旁物化不变量的方式。testing.zig

随手可用的模式

  • 为模块级叙述保留 //!,为 API 文档保留 ///,使生成的引用在各包间保持一致的语调。36
  • 为每个公开的辅助函数配对专注的测试块;Zig 的测试运行器使并置测试零成本,它们同时也是可执行的使用示例。
  • 当格式化器重排签名时,请接受其裁决——编辑器与 CI 间的一致性是 0.15.x 的重要体验改进之一。

注意与警示

  • 不要抑制来自 zig fmt 的警告;而应调整代码使默认设置成功,并在贡献指南中记录任何不可避免的偏离。36
  • 保持项目本地的 lint 脚本与上游 Zig 版本同步,以便在工具链升级期间将琐事开销降至最低。
  • 若你的 API 发出来自 std 的容器类型,请在文档注释中引用其确切字段名——调用者可直接跳转到 zig/lib/std 确认语义。hash_map.zig

练习

  • 重写你最近的某个模块,按上面显示的顺序将常量、类型、函数和测试分组,然后运行 zig fmt 确认结构保持稳定。36
  • 扩展 parseCount 以接受下划线提高可读性,同时保持严格的错误词汇;为新分支添加针对性测试。
  • 使用 zig build doc 为项目生成 HTML 文档,并审查 //!/// 注释如何呈现——调整叙述直到输出流畅可读。

替代方案与边界情况

  • 一些团队偏好完全分离的测试文件;若你这样做,采用相同的命名和文档注释模式使搜索结果保持可预测性。36
  • 对于公开编译时密集 API 的模块,包含一个 test "comptime" 块,使这些指导原则仍能提供可运行覆盖。15
  • 引入第三方代码时,请用简短 README 注明与本风格的差异,使评审者知悉该偏离是有意为之。Build.zig

Help make this chapter better.

Found a typo, rough edge, or missing explanation? Open an issue or propose a small improvement on GitHub.