跳到主要内容

写实现时,我会漏掉那些你没明说的边界分支

阶段详细设计角色工程师严重度适用版本Coding Agent 通用证据官方文档

一句话摘要:我写出来的实现,主路径几乎总能跑。但 null、空集合、越界下标、超长字符串、并发、时区、编码这些边界,只要你没明说,我经常就漏过去——代码看着完整,喂个边界输入就崩。这讲的是实现细节层面的遗漏;如果是整个方案的健壮性假设站不住,见《方案看起来对,经不起边界推敲》

现象

我常看到这样的交付:你让我写一个 splitIntoBatches(items, size),我给你一个干净的实现,循环、切片、返回,读起来挑不出毛病。你跑了一遍 [1,2,3,4,5] 分成大小 2,三个批次,对的,合并了。

然后线上来了一个空数组——返回了 [[]] 而不是 [];来了一个 size=0——死循环;来了一个 null——直接抛异常。这些分支我一个都没写,因为你给的例子里一个都没有。

为什么会这样

我写代码,靠的是对「这类函数通常长什么样」的模式补全。你给我的描述和示例,决定了我脑子里激活的是哪条主路径——而边界分支不在主路径上,除非有什么东西把它们勾出来,否则它们不会自己冒出来

具体说,有三股力把我推向「只写主路径」:

  • 你的示例就是我的锚。 你举的例子全是正常输入,我就默认输入都长那样。空集合、负数、size=0 没出现在你的话里,也就没出现在我的代码里。
  • 训练语料里的「典型实现」往往也省略边界。 教程和示例代码为了讲清主干,经常把 null 检查、越界保护略掉。我学到的「标准写法」自带这个盲区。
  • 我没运行过它。 我对边界的覆盖取决于我有没有「想到」,而不是有没有「踩到」——没有真实执行去打我的脸,漏掉的分支在文本层面毫无破绽。

后果

  • 代码通过了你手边那几个例子,给你「写完了」的错觉,于是直接合并。
  • 漏掉的边界往往是最难复现、最贵的 bug:空数据导致的崩溃、off-by-one 的越界、时区错位的对账差、编码问题导致的乱码——都要等到真实数据撞上来才暴露。
  • 你下次会本能地替我把每个边界都列全,等于把我能省的活又还了回去。

最佳实践

别指望我自动想全边界——把「该考虑哪些边界」变成一道我必须回答的题,再用测试把答案钉死。

  • 先让我列举边界,再让我写实现。 「在动手前,先列出这个函数所有可能的边界与异常输入,逐一说明你打算怎么处理。」这一步把我从「补全主路径」切换到「枚举边界」,光是问出来,我的覆盖率就明显上去。
  • 给我一份边界清单当检查项。 常见的就那几类,贴给我让我逐条对照:空 / null、单元素、超长 / 超大、零与负数、重复、越界、并发、时区与 DST、字符编码与多字节。
  • 用测试逼出边界,而不是用眼睛找。 先让我写覆盖这些边界的测试(这与《信任但不验证》是同一招在设计阶段的前置),跑红了再补实现——边界没处理,测试就亮红,我无处藏拙。
  • 明确「非法输入怎么办」。 是抛异常、返回空、还是夹取到合法区间?你不定,我就替你默认一个,而我默认的那个未必是你要的。

示例

改之前:

你:写个 splitIntoBatches(items, size),比如 [1..5] 按 2 分,给我三批
我:(给出实现,跑你的例子正确,你合并了)
线上:splitIntoBatches([], 2) → [[]];splitIntoBatches([1], 0) → 死循环

改之后:

你:写 splitIntoBatches(items, size) 之前,先列出所有边界:
空数组、size<=0、size 大于元素个数、items 为 null。每个说明你的处理。
我:(列出四类边界 + 处理策略:空数组返回 [],size<=0 抛参数错误……)
你:合理。现在为这些边界各写一个测试,再写实现让它们全绿。
我:(测试先行,边界被逐个逼出来,实现一次到位)

什么时候例外

「补全边界」是默认,但有几种情况,省掉某些分支是合理的,硬补反而是噪声:

  • 写完即弃、输入完全受控的一次性脚本:你自己跑、数据你自己给、跑一遍就删——为一个你确知不会出现的空值写防护,是在给马上要扔的代码买保险。
  • 边界在结构上不可能发生:上游类型系统已经保证非空(非可空类型)、调用方是你自己且已校验过、switch 已穷尽枚举——再加一层「以防万一」的检查,是挡一个进不来的输入,只会让读代码的人猜「这分支什么时候会走」。
  • 失败要尽早炸、而非默默兜住:在内部不变量被违反时,与其吞掉边界返回一个「看起来正常」的值,不如让它直接抛——这时「不处理、让它崩」是刻意的设计,不是遗漏。

判据:例外成立,前提是你能说出这个边界为什么到不了这段代码(受控输入 / 类型保证 / 已在上游校验),而不是「我没想到它会来」。只要这段代码会接公开输入、或边界是「可能发生但我没覆盖」,就回到默认:先枚举、再用测试钉死。

版本说明

适用版本

「主路径强、边界弱」是大语言模型写代码的固有倾向,全模型、跨工具通用。模型越强,主路径写得越漂亮,反而越容易让你放松对边界的警惕——所以「先枚举边界、再用测试钉死」这套动作不会因为模型变强而过时。

延伸阅读与出处