Hacker News 中文摘要

RSS订阅

Zig 错误模式 -- Zig Error Patterns

文章摘要

文章探讨了如何改进基于打印的调试方法,特别是在Zig语言中利用errdefer在测试失败时打印相关信息,从而避免输出冗余信息。此外,文章还提到在需要更复杂调试时,使用调试器是更好的选择。

文章总结

标题:Zig 错误处理模式 ~ glfmn.io

引言 尽管我尝试充分利用调试器,但我仍然习惯于基于打印的调试方法,尤其是在单元测试中。我希望探索一些技巧来改进基于打印的调试,并更多地结合调试器的使用。

改进基于打印的调试 使用打印调试的一个主要问题是输出信息过于冗杂。例如,如果我在循环中运行某些代码,而只有一次迭代有值得关注的内容,我仍然需要过滤和整理每次迭代的输出。或者,如果我正在处理某个数据结构,其可打印的表示形式比原始数据更易于解析,但如果不清楚错误来源,我可能不得不在代码中到处插入打印函数,以捕捉必要的上下文。

然而,我意识到由于 Zig 测试使用 error 而不是 panic,因此可以使用 errdefer 仅在测试实际失败时打印信息。例如: zig test { errdefer std.debug.print("{f}", .{ast}); // ... } 这种方法可以避免代码混乱,并在错误发生时提供精确的上下文。

在调试器中运行测试 如果需要处理更复杂的情况,使用调试器是更好的选择。然而,直接从终端运行 seergdbgdb -tui 会变得困难,因为测试二进制文件不在 zig-out 目录中,而是在 zig-cache 目录中。

我从 ziggit 学到了一个技巧,即 build.zig 可以运行命令,并将构建步骤的产物路径作为参数传递给命令: ```zig const debugger = b.addSystemCommand(&.{ "seergdb", "--run", "--" }); debugger.addArtifactArg(exeunittests);

const debugstep = b.step("debug", "Run unit tests under debugger"); debugstep.dependOn(&debugger.step); ``` 这使得运行正确的二进制文件变得非常容易。然而,这还不够:调试器仅在遇到断点或 panic 时才会启动,而测试运行器会优雅地处理错误。

我们可以通过添加 @breakpoint 调用来解决这个问题,但这可能会导致我们回到原点,即必须推测性地添加断点并捕捉失败的确切时刻,或者手动逐步执行直到失败。

结合技巧 一个好的解决方案是结合使用 errdefer@breakpointzig test { errdefer @breakpoint(); } 这将在遇到错误时精确地中断程序。此时,调试器中可能仍有足够的上下文可供查看,并且我们还可以利用 std.testing.expect{.*} 函数提供的调试打印信息。

然而,这种方法也有缺点。例如,考虑以下程序: ```zig test "errdefer @breakpoint()" { errdefer @breakpoint(); return error.FixMe; }

test "normal test" { return error.FixMe; } `` 如果我们运行zig build test`,测试总结只会报告整个测试步骤失败,而不是单独报告每个测试的错误。

条件编译 Zig 构建系统允许我们使用构建选项来传递编译时值。我们可以利用这些选项来传递一个 bool 值,以决定何时在测试中调用 @breakpoint

首先,从一个简单的构建脚本开始,该脚本仅运行测试: ```zig const std = @import("std");

pub fn build(b: *std.Build) void { const target = b.standardTargetOptions(.{}); const optimize = b.standardOptimizeOption(.{});

const lib = b.addModule("zig-test-patterns", .{
    .root_source_file = b.path("src/root.zig"),
    .target = target,
    .optimize = optimize,
});

const mod_tests = b.addTest(.{
    .root_module = lib,
});

const run_mod_tests = b.addRunArtifact(mod_tests);

const test_step = b.step("test", "Run tests");
test_step.dependOn(&run_mod_tests.step);

} 我们可以添加编译时选项: zig const options = b.addOptions(); options.addOption(bool, "debugger", false); 然后,将生成的选项模块添加到我们的库中: zig lib.addImport("config", options.createModule()); 在 `root.zig` 文件中,我们可以引用这些选项: zig const std = @import("std"); const config = @import("config");

test "errdefer @breakpoint()" { errdefer if (config.debugger) @breakpoint(); return error.FixMe; }

test "no breakpoint" { return error.FixMe; } 现在,我们可以运行测试并获得没有断点的行为: zig build test 然而,我们遇到了一个问题:这个值对我们的程序是可观察的,但我们必须重新编译 `build.zig` 才能更改该值。我们需要将该选项添加到构建系统本身: zig var options = b.addOptions(); const usedebugger = b.option( bool, "debugger", "Enables code intended to only run under a debugger", ) orelse false; options.addOption(bool, "debugger", usedebugger); 现在我们可以运行: zig build -Ddebugger test ``` 并观察行为的变化。

作为最后一步,我们可以在 use_debugger 标志设置时,将命令连接到调试器中运行: ```zig const teststep = b.step("test", "Run tests"); if (usedebugger) { const debugger = b.addSystemCommand(&.{ "seergdb", "--run", "--" }); debugger.addArtifactArg(mod_tests);

test_step.dependOn(&debugger.step);

} else { teststep.dependOn(&runmod_tests.step); } `` 现在,如果我们使用-Ddebugger` 标志运行,测试运行器将自动在调试器中运行,并在遇到错误时中断!

Zig 版本 本指南基于 Zig 版本: 0.15.0-dev.1184+c41ac8f19 特别感谢 Alanza 阅读了本文的早期草稿。

评论总结

  1. 网站设计与排版

    • 多位评论者赞赏网站的设计和排版,认为其美观且易于阅读。
    • 引用:
      • "The website design is so pleasing, props!" (ijustlovemath)
      • "I love the formatting and coloring on this blog page, it's delightful to read. Like an old school DOS game." (rkagerer)
  2. Zig语言设计的简洁性与一致性

    • 评论者认为Zig语言的设计简洁且一致,特别是errdefer模式和核心构建块的设计。
    • 引用:
      • "This goes to show how Zig's language design makes everything look nicer and simpler - the errdefer patterns in tests are super nice!" (etyp)
      • "It's amazing how coherent Zig's fundamental building blocks are as a programming language, everything fits together like a puzzle." (vrnvu)
  3. 调试与构建工具的改进

    • 评论者提到Zig的调试和构建工具(如build.zig中的调试器集成)简化了开发流程。
    • 引用:
      • "I especially like the debugger integration in build.zig. I used to grep the cache directory to find the exe. The integration avoids all the extra steps." (ww520)
  4. 错误处理的局限性

    • 有评论者指出Zig的错误处理在某些场景下(如JSON解析)可能不够详细,缺乏有效载荷信息。
    • 引用:
      • "For example, when parsing a JSON string, the error UnexpectedToken is not very helpful." (davidkunz)
  5. 代码风格的不一致性

    • 有评论者对Zig代码中混合使用驼峰式和蛇形命名表示不适,认为这可能导致混淆。
    • 引用:
      • "Cool stuff, but the mixed casings I see here (and have in other Zig code) puts me on edge." (yahoozoo)

总结:评论者普遍对Zig语言的设计和网站排版表示赞赏,特别是其简洁性和一致性。同时,调试工具的改进也受到好评。然而,错误处理的局限性和代码风格的不一致性引发了一些讨论。