AI SaaS 工程诊断实战(二)
备注保存接口回了 success: true,重新读取订单,备注却还是空的。这类问题不难造出来,也很容易被一次"接口正常"的检查放过去。
我用一个 Python 与 SQLite 的最小程序,把失败基线、修复位置和相关回归放在一起。本文是教学构造案例,故意省略了写入语句,不是客户生产故障,也不代表某个 AI Coding 工具实际生成过这段错误代码。
先确认失败发生在哪一步
订单 A 已经确认,订单 B 是另一条不应受影响的数据。我们只想给 A 保存备注。故障函数直接把输入拼回响应:
python
def broken_save(db, order_id, note):
return {'success': True, 'note': note}
看接口返回,备注存在;看数据库,备注没有变化。响应里的 note 来自本次输入,不是保存后的重新读取,因此它不能当作持久状态的证据。
这时我会先固定一组失败基线:确认 A 两次,保存一次备注,再直接查询 orders 和 events。先知道哪些原有行为成立,再决定补丁应该保护什么。否则"顺手重构一下确认逻辑"很容易把一次写入修复扩大成另一项任务。
补丁要回答三个问题
第一,更新哪一行?条件是订单 ID,不能省掉 WHERE。
第二,值怎样传进去?使用参数占位符。示例特意使用带单引号的备注,避免只拿简单英文测试。
第三,找不到订单怎么办?这里的教学规则是抛出 LookupError,由调用方转换成合适的错误响应,不假装保存成功。
python
def save(db, order_id, note):
with db:
changed = db.execute(
'UPDATE orders SET note=? WHERE id=?',
(note, order_id)).rowcount
if changed != 1:
raise LookupError(order_id)
return {'success': True, 'note': note}
成功响应放在事务块之后。订单 ID 是主键,本例以受影响行数检查目标是否存在。这里没有把空字符串视为错误:本例允许清空备注;如果你的业务不允许,先改规则,再改检查。
这次补丁只负责写入 note,不改变状态机,不增加确认事件,不重命名接口字段。真实项目若还存在权限、审计或输入长度要求,应将它们纳入明确范围,不能因为这个示例没写,就默认它们不重要。
回归为什么选这七项
| 检查 | 保护的行为 |
|---|---|
| 首次写入后读取 | 原失败是否消失 |
| 再次保存覆盖原值 | 更新不是仅首次插入有效 |
| 空字符串清空 | 本例允许的边界输入 |
| 未知订单拒绝 | 没有目标时不报成功 |
| 确认状态保留 | 备注修改不倒退业务状态 |
| 确认事件仍为一条 | 没有引入重复副作用 |
| B 订单保持原样 | 更新条件没有误伤其他对象 |
这七项不是通用的固定测试清单,而是从这次改动及相邻行为推出来的。更换业务规则后,这张表也需要变。
实际执行结果是:故障基线的首次写入、覆盖和未知订单拒绝未通过;修复版七项通过。注意基线中的"空字符串清空"可能碰巧通过,因为原值本来就是空的。一个通过结果,必须连同前置状态一起读。
完整演示
保存为 demo.py,用 Python 3.12 执行 python demo.py。仅使用标准库。程序分别运行故障版和修复版,打印逐项结果,并用断言检查关键预期。下方代码与本次运行材料一致。
python
import json
import sqlite3
def new_db():
db = sqlite3.connect(':memory:')
db.executescript('''
CREATE TABLE orders(id TEXT PRIMARY KEY, status TEXT, note TEXT);
CREATE TABLE events(order_id TEXT, action TEXT);
INSERT INTO orders VALUES ('A', 'draft', '');
INSERT INTO orders VALUES ('B', 'draft', 'keep');
''')
return db
def confirm(db, order_id):
with db:
changed = db.execute(
"UPDATE orders SET status='confirmed' WHERE id=? AND status='draft'",
(order_id,)).rowcount
if changed:
db.execute("INSERT INTO events VALUES (?, 'confirm')", (order_id,))
def broken_save(db, order_id, note):
return {'success': True, 'note': note} # Deliberate teaching defect.
def save(db, order_id, note):
with db:
changed = db.execute(
'UPDATE orders SET note=? WHERE id=?', (note, order_id)).rowcount
if changed != 1:
raise LookupError(order_id)
return {'success': True, 'note': note}
def run_checks(writer):
db = new_db()
try:
confirm(db, 'A')
confirm(db, 'A')
response = writer(db, 'A', "O'Reilly 地址")
first = db.execute("SELECT note FROM orders WHERE id='A'").fetchone()[0]
writer(db, 'A', 'updated')
second = db.execute("SELECT note FROM orders WHERE id='A'").fetchone()[0]
writer(db, 'A', '')
empty = db.execute("SELECT note FROM orders WHERE id='A'").fetchone()[0]
try:
writer(db, 'missing', 'x')
unknown_rejected = False
except LookupError:
unknown_rejected = True
checks = {
'first_write': first == "O'Reilly 地址",
'overwrite': second == 'updated',
'clear_allowed': empty == '',
'unknown_rejected': unknown_rejected,
'status_kept': db.execute("SELECT status FROM orders WHERE id='A'").fetchone()[0] == 'confirmed',
'one_confirm_event': db.execute("SELECT COUNT(*) FROM events WHERE order_id='A'").fetchone()[0] == 1,
'other_order_kept': db.execute("SELECT status,note FROM orders WHERE id='B'").fetchone() == ('draft', 'keep'),
}
return {'response': response, 'checks': checks}
finally:
db.close()
if __name__ == '__main__':
baseline, repaired = run_checks(broken_save), run_checks(save)
assert not baseline['checks']['first_write']
assert all(repaired['checks'].values())
print(json.dumps({'baseline': baseline, 'repaired': repaired}, ensure_ascii=False, indent=2))
这次通过还不能说明什么
程序使用同进程内存数据库,没有页面和 HTTP 层,也没有鉴权、并发、崩溃恢复及重启持久化验证。它支持的结论是"在这些输入与检查下,备注写入修复有效且相关样例没有出现回归",还不是"整个订单系统可以上线"。
给 AI Coding 工具交任务时,可以把范围写得很具体:"修复备注未写入;保留确认状态和事件行为;给出修改前失败、修改后通过的存储读取结果;说明未覆盖项。"真正执行完,再核对差异与运行记录。提示词只是任务约定,不会自动保证工具遵守范围。
关于参数绑定和事务上下文的具体行为,可查 Python sqlite3 官方文档。
------ Linksass|AI SaaS 独立开发者。本文由我确定选题与交付范围,使用 AI 辅助整理和制作演示;以上程序已在本地执行,构造案例与验证边界已说明。