E01确认了接口、值与取消的局部契约。这次只动同一个任务服务提交入口的字段校验:请求的 Text 不能为空白,配置中的 MaxWords 必须为正。生成校验承担实际 HTTP 路径,反射版本只作对照,业务领取、SQL 与状态转移不复制第二套。完整包、摘要和复跑步骤附在同名目录。

错误在哪一刻出现

字段声明在 taskvalidation/schema.go:Fields:Text string 带 check:"nonblank",MaxWords int 带 check:"positive"。cmd/generate-taskvalidation 用固定 Go 1.27.1 的 go/parser 读取 AST,先核验字段名、类型与标签,再用 go/format 生成 taskvalidation/generated.go:Validate。原 http_tasks.go:create 仍先作 JSON/体积校验,之后调用生成函数;拒绝空白正文仍返回原有 400 和原有错误文本,原 SQL 插入只发生在校验通过之后。

反例把 Text 的标签改成 check:"not-a-rule":生成命令退出 1,stderr 为 unsupported check tag "not-a-rule" on Text,目标文件不存在。普通 Go 类型检查并不解释 struct 标签;若改由 ValidateReflect 在运行时逐字段读 reflect.StructTag.Get,同一坏标签到函数被调用时才发现。生成时发现早于真实请求,并不意味着生成器自动证明所有业务条件;增加新的字段规则仍得写生成逻辑与测试。固定源码 SHA、坏输入、生成命令及正确产物逐字节比较均保留在包内 evidence/E02/。

TestGeneratedAndReflectValidateTaskFields 对有效 Go task、空白字符串和非正词数逐例要求两种实现返回相同结果,并在单独错误结构体上断言反射版直到运行才报未知规则。既有 HTTP 用例仍验证真实 handler 的空白 400、正常接纳 202 与后续结果;只调用校验包的单元测试不能代替这一层。

成本要量的是同一组字段

BenchmarkTaskFieldValidation 用同一个有效任务字段输入、循环内错误检查,对生成版与反射版各跑五轮(200 ms/轮)。原始 bench.stdout 显示,生成版每轮落在 9.362 到 10.19 ns/op,反射版在 240.9 到 289.4 ns/op;两组在该场景均为 0 B/op、0 allocs/op。反射耗时高但此输入没有额外堆分配;不能把「使用反射」写成「一定发生分配」。输入固定、运行时间短、没有在完整 HTTP/SQL 请求上做基准,这些数字不能推导服务吞吐提升倍数。

练习:①把 MaxWords 的字段类型改成 string 但保留 check:"positive",应在哪一步失败?答:生成器在生成前因字段类型不符报错;没有通过真实 HTTP 请求才发现。②只改源码中的标签、不重新生成,会发生什么?答:旧产物仍按旧规则运行,构建未必报错;必须执行生成命令并逐字节比较提交产物,验收脚本的 compare 才能捕获漂移。③反射五轮 0 allocs 是否证明它适用于所有 HTTP 输入?答:不证明;本轮只量有效两字段值,错误路径、吞吐和长尾需另设计负载。

首次独立解包把临时生成的 .go 放在包内证据目录,go test ./... 将它误识别为新包而失败;失败日志保留于 pilot-unpack-generated-file/。改用 .go.txt 保留产物字节但不污染构建后,独立复跑才可以新建 PostgreSQL 与模块/编译缓存,重新运行生成/坏 schema、正反字段用例、五轮 benchmark、两包回归和 race,再检查页面/下载。race 没有报告仅限实际路径;其他平台、全站构建与发布 NOT_RUN。E03继续核查本地代码与不安全指针,而不是把反射性能结果外推到内存边界。

参考资料