我是安徽最忧郁程序员无隅

前言
AI 已经完成代码,功能可以运行,解释也能够听懂。几天之后,需求增加了一个条件,你能判断原来的实现是否仍然成立吗?
一次开发任务结束时,除了可以运行的功能,我们还需要留下能够支持下一次修改的判断依据。订单写入超时后的重试,就是一个适合检查这种能力的具体问题。
一、功能完成之后,哪些知识还没有掌握
自己处理一个陌生需求时,知识缺口通常会直接影响进度。接口调用失败,需要查清参数;数据出现重复,需要检查写入过程;一个修改引发其他错误,需要重新理解模块之间的关系。查阅文档、阅读代码和观察运行结果,都会让原来的认识接受检验。
AI 可以协助完成这些工作。我们还在阅读错误信息,它已经找到了相关文件;我们还在理解设计,它已经提交了能够运行的实现。随后再阅读一份完整解释,很容易产生"这部分已经掌握"的感觉。
这里需要分别检查两件事情:功能是否满足要求,以及自己是否具备修改它所需的知识。两者各有证据。前者可以通过需求验收和运行结果检查;后者需要观察自己能否解释关键机制、预测条件变化后的行为,以及找到验证判断的具体位置。
例如,你知道重试代码会再次发送请求,也能够解释每个参数的用途。但当"创建订单成功,客户端没有收到响应"这个条件出现时,你是否知道接下来应该检查什么?如果只能再次等待 AI 给出解释,当前的理解还没有覆盖这项维护责任。
开发经验会体现在下一次检查中:相似问题出现时,能够主动识别关键条件,知道需要哪些证据。
这也帮助我们确定"理解债务"的范围:在需要负责的功能中,那些已经影响判断、修改或维护,却仍然没有查清的必要知识。这个词可以用来记录具体缺口,例如"尚未确认重试是否复用业务请求标识",并据此安排学习。
阅读一个成熟库时,我们可以通过公开接口和文档使用它。涉及自己负责的写入保证时,则需要知道接口承诺依靠什么成立。学习范围应当随责任确定,否则很容易在无关细节中消耗时间,同时遗漏真正需要检查的机制。
二、围绕当前任务,确定需要理解的范围
面对 AI 生成的一组文件,逐行阅读往往缺少明确目标。可以从当前正在修改的能力出发,分别追问业务需求和支撑机制。
向上理解需求:谁在使用这个能力,它必须提供什么保证?
假设你正在为 Agent 工具增加重试。首先需要查清工具做的是查询还是写入,一次调用表达了什么业务意图,以及调用者如何理解最终结果。创建订单、发送通知、扣减库存都可能产生新的效果,重复调用时的业务后果也各有差异。
对于创建订单,可以把要求写成一个可检查的句子:"同一次下单意图发生多次网络请求时,最多创建一笔订单;调用者能够确认这笔订单的结果。"这句话同时给出了写入要求和结果要求。
向下理解机制:这些保证依靠什么成立,哪些条件变化会影响它们?
接下来需要沿着调用过程找到事实:请求标识在哪里生成,后端读取哪个字段,重复请求怎样处理,相关状态保存在哪里。只要其中一个必要条件还没有确定,就无法解释整个保证。
下面的表格可以作为阅读代码时的问题清单:
| 观察位置 | 需要查清的问题 | 可以寻找的证据 |
|---|---|---|
| 业务入口 | 哪些网络请求属于同一次下单意图? | 调用参数、业务编号、入口逻辑 |
| 重试过程 | 每次发送是否保留同一个去重标识? | 标识的生成位置、各次请求记录 |
| 后端写入 | 重复请求到达后,怎样阻止第二次创建? | 去重记录、写入逻辑、数据库约束 |
| 状态保存 | 并发执行和服务重启后,保证是否仍然成立? | 原子操作、持久化方式、状态有效期 |
| 结果返回 | 后端已经成功时,调用者怎样确认结果? | 接口约定、订单查询、返回内容 |
上下相邻的职责可以帮助确定阅读起点。如果保证依赖更深的事务或并发行为,就继续查阅相关实现和文档,直到能够说明关键条件。
有经验的开发者看到重试,可能会立即检查重复执行和状态保存。新人需要通过当前任务建立这种联系。"负责架构和代码审查"本身不会提供判断依据;这些依据仍然要通过具体问题、证据和反馈逐渐形成。
三、通过预测和实验,检验自己的理解
阅读解释时,推理过程已经由别人组织好了。检验理解则需要自己完成一次推理:写下输入和条件,说明预期结果,再用实际证据检查它。
可以把这个过程安排为四个连续动作:
- 写下预测。 说明当前条件下会发生什么,以及判断依赖的前提。
- 设计验证。 确定怎样触发这些条件,观察哪些请求、日志和数据。
- 解释结果。 检查结果是否符合预测,找到真正决定结果的代码或约束。
- 改变条件。 更换一个有关的条件,重新预测并验证,检查理解能否用于新情况。
下图中,实线表示个人预测与验证的顺序,虚线表示 AI 提供的资料和日志帮助。

改变条件后,流程回到预测环节。这个循环要求再次写出自己的判断,并找到支持结果的证据。
预测需要足够具体。"这个实现应该安全"很难检验;"第一次写入已经提交,第二次请求携带相同去重标识,因此后端应返回已有订单,订单数量保持一笔"就包含了可以核查的条件和结果。
AI 在这个过程中可以协助查找代码、说明文档、设计实验步骤、整理日志。自己则要保留判断的位置:先独立写下预测,再阅读它的分析;先检查真实请求和数据库记录,再讨论解释。
给 AI 的问题也可以包含自己的前提和证据:
我的预测是:第二次请求更换去重标识后,会产生第二笔订单。实际查询仍然只有一笔。请结合写入代码、数据库约束和两次请求参数,指出哪个条件限制了第二次写入,并给出对应位置。
这样得到的分析可以直接针对理解缺口。检查时也有明确对象:证据中的限制是否存在,它是否确实参与了这次执行。
预测正确之后,还要解释正确的原因。 如果去重逻辑存在缺口,但另一项业务唯一约束阻止了重复写入,那么"最终只有一笔订单"这个结果,还不足以证明原先对去重逻辑的理解成立。
测试通过提供了已覆盖场景中的行为证据。学习是否发生,还需要检查自己在测试之前怎样预测、测试之后怎样解释,以及条件改变之后能否继续判断。
四、具体案例:订单写入超时后,重试会发生什么
以下使用一个假设的订单接口分析判断过程。这里给出的是机制推演和验证设计,具体系统的结果需要结合其真实接口、代码和数据库检查。
先查清超时发生时,哪些事情已经完成
假设一次调用经历了这些事件:
- 客户端发送创建订单请求,携带去重标识
K1。 - 后端创建订单
O1,数据库事务完成提交。 - 响应传输出现延迟,客户端达到等待时限。
- 客户端准备再次发送请求。
在这个条件下,客户端没有得到响应,数据库中却已经存在订单。AWS 对安全重试的讨论也指出,未收到响应会使调用者无法确认操作是否已经完成,直接重复创建可能产生额外效果。AWS:Making retries safe with idempotent APIs
因此,需要把"请求有没有返回"和"业务写入有没有完成"分别检查。如果实验只在发送请求之前制造失败,就没有覆盖本例中最关键的条件:第一次业务写入已经提交。
复用标识之后,后端还需要完成哪些工作
幂等性(Idempotency)在这个例子中的含义是:同一次业务操作被重复请求时,额外请求不会再创建第二笔订单。去重标识为后端提供了识别同一次操作的依据。
这里的 K1 指后端实际用来识别重复业务操作的标识。日志中的跟踪编号可能只用于区分每次网络调用,需要阅读实现才能确定它与业务去重的关系。
假设后端按"调用者身份与去重标识"保存处理记录。第一次请求完成后,记录能够关联到 O1;重试仍然携带 K1,后端找到原记录,再按接口约定返回已有订单结果。
下图展示这一假设过程,注意数据库提交完成与客户端等待超时发生的位置。

图中重试查询到了 O1,说明同一次业务操作需要在两次网络请求之间保持可识别的身份,后端也需要保留有效的处理记录。
这个推演依赖多个条件:两次请求表达同一个业务意图,标识保持一致,后端去重记录仍然有效,重复处理不会再次执行订单创建,并且记录与订单写入之间具备一致性保证。对于相同标识携带不同参数的情况,也需要明确接口如何处理。
Stripe 的官方文档提供了一个具体参照:相同幂等键的后续请求会得到已保存的结果,同时检查请求参数是否一致;键被清理后再次使用,会被视为新的请求。这说明检查去重时还需要关注参数约定和记录有效期。Stripe:Idempotent requests
更换标识之后,需要继续检查业务约束
现在改变一个条件:重试代码生成了新的去重标识 K2。
如果后端仅依靠这个标识识别重复操作,那么 K2 无法直接命中 K1 的处理记录。在没有其他限制的前提下,第二次请求可能再次创建订单。
不过,系统也可能具有独立的业务唯一约束。例如,同一次下单意图还携带固定业务编号 B1,订单表要求同一用户的 B1 只能出现一次。即使去重标识发生变化,第二次写入仍可能因业务编号重复而受到限制。
因此,需要确认每个编号的生成时机和用途。每次写入都重新生成的订单主键,可以区分不同数据行;表达同一次下单意图的固定业务编号,才能让对应的唯一约束识别这两次写入之间的业务关系。
PostgreSQL 文档说明,唯一约束可以限制单列或多列组合的重复值。用于此类业务约束时,还要检查字段是否允许空值:默认情况下,唯一约束会把空值视为彼此不同。PostgreSQL:Unique Constraints
检查结果时,也要区分"第二次写入受到限制"和"调用者拿到了正确结果"。数据库报告唯一约束冲突之后,接口仍需要按照已有约定提供明确结果,才能完成整个业务要求。
并发和重启,会改变哪些前提
继续改变条件:两个携带 K1 的请求同时到达。
如果后端执行的是"先查询是否存在,再创建订单",两个执行过程可能都在查询时看见记录不存在,随后分别进入创建流程。这时需要查清系统有没有原子性的占用、对应的唯一约束或其他并发控制,以及重复请求处理中怎样返回结果。
还需要检查去重记录与业务写入之间的一致性。订单已经提交、去重记录尚未保存时发生进程崩溃,重试可能无法识别先前的效果;去重记录已经保存、业务写入没有完成时发生错误,也可能留下无法正确说明的状态。AWS 文档将记录请求标识与相关修改作为需要原子性保证的过程讨论。AWS:Making retries safe with idempotent APIs
如果去重状态只保存在进程内存中,服务重启后,这些状态通常不会保留;不同服务实例也未必共享它们。此时必须检查持久化状态和数据库约束是否仍然能够识别原来的业务操作。
把几个条件放在一起,可以得到一份具体的验证表:
| 改变的条件 | 应当观察的内容 | 需要解释的机制 |
|---|---|---|
| 第一次提交后,客户端响应超时 | 提交记录、重试参数、最终订单数量 | 超时与业务完成的关系 |
重试保留 K1 |
去重记录、关联订单、接口返回结果 | 相同操作怎样得到识别 |
重试改用 K2,业务编号保留 B1 |
两种标识、唯一约束、写入结果 | 哪项机制阻止重复写入 |
两个 K1 请求并发到达 |
实际执行时序、写入次数、返回结果 | 并发控制怎样成立 |
提交后重启,再携带 K1 请求 |
重启前后状态、已有订单、重试结果 | 去重保证依赖的保存位置 |
在测试环境中执行第一项验证时,可以使用真实服务和独立测试数据,让请求经过实际写入过程,再通过可控的网络延迟使客户端在提交后达到等待时限。需要用数据库记录和服务日志确认这个先后关系,随后检查重试发送的标识、最终订单数量与接口结果。其余项目同样应当先确认条件确实触发,再解释观察结果。
这组问题的学习价值在于,它把"重试是否安全"转化成了能够查找和验证的具体条件。下一次遇到类似代码时,你可以主动检查业务意图、去重标识、写入约束和状态保存。
五、把一次任务中的判断带到下一次开发
学习记录可以围绕一个真实的认识变化组织。记录时保留当时的预测、检查到的证据和最终确认的条件,篇幅由问题本身决定。
下面是一份供填写的记录结构:
| 记录项目 | 需要写清的内容 |
|---|---|
| 当前问题 | 哪个功能行为需要由自己判断? |
| 原先预测 | 在明确条件下,预计会发生什么? |
| 观察证据 | 哪些请求、日志、数据或文档支持实际结果? |
| 机制解释 | 哪段代码或哪项约束决定了结果? |
| 成立条件 | 标识、参数、并发、保存位置和有效期有哪些要求? |
| 下次检查 | 再遇到相似问题时,首先检查什么? |
例如,订单重试问题可以形成这样一个下次检查的问题:"同一次下单意图在重试时如何保持可识别的身份?后端依靠哪项约束阻止重复效果,这项保证在并发和重启后是否仍然成立?"
记录完成后,可以用另一处实际调用检验迁移能力。调用对象换成库存扣减时,先说明重复执行可能产生的业务后果,再找到它的请求身份和写入机制。相似之处能够帮助确定检查方向,具体保证仍然需要从当前实现中获得证据。
当多个任务都涉及超时、重复执行和事务边界,就可以把这些案例放在一起学习:网络结果为什么存在不确定性,哪些写入可以安全重试,去重状态怎样与业务状态保持一致。每个概念都有已经遇到的问题作为入口,也有后续任务可以检验理解。
涉及权限、写入和状态一致性的关键知识,需要在交付之前查清。对尚未确定的保证,应当结合文档、运行证据和代码审查完成检查。学习安排也需要包含这些检查所需的时间。
下次修改功能之前,尝试在阅读 AI 的分析之前写下自己的预测;执行之后,用代码、日志和数据说明结果。每完成一次这样的过程,就留下一个具体的判断依据,供后续开发继续使用。
参考资料
- 学习方法参考:所提供的图文文章《AI 写完的代码,如何变成你的经验》。
- AWS:Making retries safe with idempotent APIs,用于核查请求身份、重复执行和原子性要求。
- Stripe:Idempotent requests,用于核查幂等键、参数一致性和记录有效期的具体接口约定。
- PostgreSQL:Unique Constraints,用于核查唯一约束及其空值行为。