本文是一篇翻译文章,原文为作者以日文撰写、后发布英文版的文章。

平包

打开网易新闻 查看精彩图片

观点不一,但 Go 中有一派认为:包要少、要大,也就是平包。反其道而行,仅仅为了得到一条边界就拆分包,是要付出代价的。

  • 导入循环,以及你为了反转依赖、打破循环而写的那些接口
  • 两半共享的每一个名字都必须导出。你本想在两个文件之间划一条边界,结果却发布了一套 API。想把暴露面压下去,就得在每一条边界上加一层 internal/
  • 边界划错的代价更高。把声明从一个文件挪到另一个文件,影响很小;从一个包挪到另一个包,往往会打断所有导入方

所以大多数 Go 代码被说成平着更健康。不过,平包并没有让问题消失。平包这一派是把双刃剑。Go 恰好只有两级可见性。

没有第三级,没有文件作用域。在一个平包里,最小的 helper 也是包级名字,任何一个字段都能从包内任何地方写入。

“这个 helper 属于这个文件,所以别从另一个文件调用它。”

这条约定很可能是合理的。但它唯一存在的地方,是写这个包的人脑子里。你可以在注释里反复重申,编译器依然不知道它。

AI agent 能守住这份纪律吗?

在智能体 AI 的时代,这种没写下来的默契很容易被撕开。一个 agent 发现作用域里有个未导出的 helper,就直接调用了;看到一个未导出的字段,就直接写入了。每一个选择都能编译通过,快速 review 也放行。注释能帮上一点忙,但从来帮不全。AI 写得快,债也就堆得一样快。

“写进 CLAUDE.md 不就行了?”你说?我写了。即便如此,我也没能让它守住。自然语言写成的约定没法被确定性地检查,所以它只能概率性地生效。

那就让机器来检查这条约定。包保持扁平,边界留在包内部,由一个专门的 linter 负责检查。当 agent 越界时……