Go E01:Java 经验迁移时容易误读的三个 Go 契约
主线 39已经核验服务的接纳、查询和退出;系列导航仍把 E01–E08 单独计数。迁移 Java 经验时,几个用词看起来熟悉:null、对象引用、线程中断。但 Go 的接口值、struct 值与 context 取消有自己的规则。本篇只在原累计工程上加三项反例测试,不复制 Java 服务。素材另附SHA-256及复跑步骤。
不能用 Java 的 null 猜接口是否为空
Go 1.27.1 的语言规范在 Variables 一节直接给出 var v *T 为 nil、x = v 后接口 x 动态类型为 *T 的例子。TestElectiveJavaTypedNilIsNotMissingTask 把 nil 的 *electiveLookupError 赋给 error:原指针仍为 nil,接口比较 err != nil 却为 true。这是typed nil,不是「有一个非空错误对象」。测试给错误方法加了 nil 接收者保护,以便观察接口内容,而不是靠 panic 判断。
同一任务服务的真实 TaskStore.Lookup("missing") 返回 (TaskRecord{}, false),不塞一个 typed nil 到 error 里。Java 的 null 返回或 Optional.empty() 可以提醒调用者处理缺失,但不能替代这里的接口动态类型规则,也不能把 false 误当作数据库报错;SQL 路径另有 ErrSQLTaskNotFound。测试日志分别记录动态类型反例与缺失任务的两值结果。
查询的是任务快照,不是存储器的可写引用
TaskStore.Lookup 在锁内从 map[string]TaskRecord 读出记录并按值返回。TestElectiveJavaTaskSnapshotIsCopied 先把 task[0] 的查询结果本地改成 SUCCEEDED/words=99;再次查询存储器,仍为 QUEUED/words=0。只有调用 TaskStore.Start 的受控转移,真正的存储状态才变成 RUNNING。Java 中拿到对象引用后改字段可能影响共享对象;这里的 struct 快照按值复制,绝不依赖「看起来是同一个任务」推断字段共享。
这不代表任意 Go struct 都是深拷贝。若字段持有 slice、map、指针,还得分别追踪底层数据;当前 TaskRecord 的状态与统计数字是值字段,测试只断言这些字段,不能外推到未测试的嵌套引用。SQL 的提交点也不是直接修改这个本地快照:sql_tasks.go:Claim/Finish 分别负责条件更新与终态记录。
取消是请求,退出要另有确认
context.CancelFunc 的本版文档明确说它不等待工作停止。TestElectiveJavaCancelDoesNotJoinWorker 让原 HTTP 服务的分析器停在一个受控闸门上。POST 返回 202 并产生实际任务 ID;等待分析器确实开始后,另发取消请求,响应同样为 202。此时 GET 仍为 RUNNING/cancel_requested=true,闸门没有释放,不能写成「worker 已退出」。只有释放闸门、等待服务 Close,GET 才变成 CANCELED/words=0。对应的正反日志每轮重复十次,进程未用 Sleep 猜完成。
Java 的 Thread.interrupt 也不等于 Thread.join;这个对照只帮助提出问题,不提供 Go 的退出证明。真正承担证明的是服务的取消状态提交、分析器可观察的释放信号和 Close 等待。若计算代码不检查 context,取消无法强制它立刻离开;关闭仍得等它返回。HTTP 应答不能代替 worker 退出,更不能代替 SQL 终态。
练习:①一个 error 接口包装了 nil 的 *MyError,可否用 err == nil 判断底层指针?答:不能,接口仍有动态类型;缺失任务应使用现有 (record, exists) 或明确的错误契约。②改动查询得到的 TaskRecord.Status 为什么没改变队列?答:查到的是值快照,只有存储器的受控转移能提交状态。③取消 202 后立刻返回一条成功结果合适吗?答:不合适;先等工作代码退出,确认最终状态与统计字段,然后再报告结果。
evidence/E01/ 保存冻结工具链一手规范片段、go doc context.CancelFunc、源码 SHA 与符号、十次正反日志以及真实 SQL 全包测试/race 的命令双流退出码;同名包在新目录、新数据库和双缓存独立复跑。单项试验没有测试远端取消、所有类型的值复制、外部副作用或 Java 运行时;这些均不得由类比推出。其他平台、部署、全站构建均为 NOT_RUN;race 无报告只限执行路径。E02再比较任务字段校验的反射与生成。
参考资料
- Go 1.27.1 Variables / Assignment statements:本地固定版
doc/go_spec.html原文与命令见evidence/E01/spec.*。 - Go 1.27.1
context.CancelFunc:本地go doc原文与命令见evidence/E01/doc-context.*。





