4 条数据,每页 2 条,分页函数却返回了 3 页,最后一页还是空的。
代码没有抛异常,接口也可能正常返回成功状态。直到调用方发现“下一页”里什么都没有,问题才暴露出来。
这类 Bug 很适合用来练习 AI 辅助排错:函数短,现象明确,修复是否有效也能马上验证。关键是把输入、预期和实际结果交代清楚,再让 AI 帮忙分析。
下面用一个专门构造的教学案例走通这个过程。示例只使用 Python 标准库,不需要接入模型 API,也不依赖付费会员。
1. 先把问题缩成能独立运行的代码
假设我们正在整理一个数据导出脚本,需要把列表按固定大小拆成多个批次。把原项目里的数据库、接口调用和日志先拿掉,留下这个函数:
defpaginate(items,page_size):pages=len(items)//page_size+1return[items[i*page_size:(i+1)*page_size]foriinrange(pages)]print(paginate([1,2,3,4],2))实际输出:
[[1, 2], [3, 4], []]预期输出:
[[1, 2], [3, 4]]这个例子虽然小,却已经提供了排错最有用的信息:什么输入会触发问题、结果错在哪里、怎样才算修好。
不要只把“分页有问题,帮我看看”交给 AI。它拿不到业务约定,很可能顺手改成另一套接口,或者把空页当作合理行为保留下来。
图 1:先收集证据,再提出修复假设,最后重新运行用例。
2. 把业务约定一起发给 AI
在聊天工具里粘贴函数,或在支持读取文件的编程助手中选中对应文件,然后附上这段提示词:
请分析下面的 Python 分页函数,先定位原因,再给最小修复。 已知事实: - 输入 items=[1, 2, 3, 4],page_size=2。 - 实际输出 [[1, 2], [3, 4], []]。 - 预期输出 [[1, 2], [3, 4]]。 业务约定: - items 是内存中的 Python 列表。 - 返回值是列表组成的列表,不返回空页。 - 空输入返回 [],不是 [[]]。 - page_size 必须是正整数,布尔值不接受。 - 不修改输入列表,不引入第三方依赖。 请依次输出: 1. 用具体输入推导错误发生的位置。 2. 区分已确认原因与仍需验证的猜测。 3. 给出保持函数名和参数不变的修复代码。 4. 列出会让旧实现失败的回归用例。 5. 说明修复的适用范围,不要声称已经运行测试。 待分析代码: (在这里粘贴完整函数)这里最容易漏的是“空输入应该返回什么”。有些页面会保留一个空白展示页,但这个函数负责拆分数据批次,我们约定没有数据就没有批次。业务约定先确定,测试才有可靠的预期值。
真实项目里,还应补充 Python 版本、完整异常堆栈以及相关调用位置。粘贴前去掉令牌、连接密码和真实用户数据;能用合成数据复现,就不必上传生产记录。
3. 看懂修复原因,才能判断 AI 有没有改对
问题出在这一行:
pages=len(items)//page_size+14 条数据,每页 2 条,整除结果已经是 2。再无条件加 1,就变成 3 页。第三次切片取的是items[4:6],因此得到空列表。
如果换成 5 条数据,5 // 2 + 1恰好等于 3,错误就被掩盖了。只测一个“普通输入”,很容易觉得函数没有问题。
图 2:示例统一按每页 2 条计算;能整除和不能整除,都需要验证。
可以修正页数公式,也可以直接按每页的起始下标切片。后一种写法不需要单独维护页数变量。
保存为pagination.py:
defpaginate(items,page_size):iftype(page_size)isnotint:raiseTypeError("page_size must be an integer")ifpage_size<=0:raiseValueError("page_size must be greater than zero")return[items[start:start+page_size]forstartinrange(0,len(items),page_size)]对于 4 条数据,range(0, 4, 2)只产生起始下标 0 和 2,不会产生 4。空列表则没有起始下标,自然返回[]。
这里的类型检查是有意严格的。Python 中bool是int的子类,使用isinstance(True, int)会得到True。示例约定只接受内置int,因此用type(page_size) is int,同时也会拒绝其他整数子类。这是接口约定,不是适用于所有项目的通用写法。相关类型关系见 Python 内置类型文档。
4. 让测试证明修复有效
AI 解释得流畅,不代表改动已经正确。把下面的代码保存为test_pagination.py,与pagination.py放在同一目录:
importunittestfrompaginationimportpaginateclassPaginationTests(unittest.TestCase):deftest_exact_multiple(self):self.assertEqual(paginate([1,2,3,4],2),[[1,2],[3,4]])deftest_empty(self):self.assertEqual(paginate([],2),[])deftest_partial_page(self):self.assertEqual(paginate([1,2,3,4,5],2),[[1,2],[3,4],[5]])deftest_single_item(self):self.assertEqual(paginate([1],2),[[1]])deftest_size_one(self):self.assertEqual(paginate([1,2],1),[[1],[2]])deftest_invalid_size(self):forsizein(0,-1):withself.subTest(size=size):withself.assertRaises(ValueError):paginate([1],size)deftest_invalid_type(self):forsizein(True,1.5,"2",None):withself.subTest(size=repr(size)):withself.assertRaises(TypeError):paginate([1],size)deftest_preserves_input(self):items=[1,2,3]paginate(items,2)self.assertEqual(items,[1,2,3])if__name__=="__main__":unittest.main()进入这两个文件所在目录,运行:
python--versionpython-munittest-vtest_pagination.py环境要求:Python 3,无第三方依赖。如果电脑通过python3调用 Python,就将两条命令中的python都替换为python3。
本稿配套代码已在交付环境实际执行,8 个测试方法全部通过,其中非法参数使用子测试覆盖多个输入。测试输出末尾为:
Ran 8 tests in 0.000s OK运行耗时会随机器变化。这里验证的是内存列表拆分逻辑,没有测试数据库分页、网络接口或前端翻页。
unittest是 Python 标准库中的测试框架,断言、异常检查和命令行运行方式可查阅 Python unittest 官方文档。
5. 防止 AI 顺着错误代码生成错误测试
如果你只说“给这个函数补测试”,AI 可能把当前实现当作正确规格,甚至把多出来的空列表写进预期结果。这样一来,测试越齐全,错误反而固定得越牢。
更有效的追问是:
请根据业务约定复查测试,不要根据现有实现反推预期值。 指出哪些用例能够揭露旧版本的 Bug。 如果把旧版本替换回来,这些用例为什么应当失败? 不要通过修改预期结果来迁就旧实现。这份示例中,“4 条数据恰好分两页”和“空列表返回零页”都能揭露旧实现的问题。交付时也把这两个用例单独作用于旧函数,确认它们确实失败。
测试还应该包含正常路径,比如 5 条数据拆成 3 页,避免修复一个边界时破坏原来正常的行为。
6. 把这个方法迁移到真实项目
遇到更复杂的报错,可以按同样的顺序整理材料:先找到稳定复现的输入,再写下业务预期,缩小到相关函数或调用链,最后让 AI 提出修复并补测试。
如果只有线上偶发的问题,暂时不能稳定复现,就先让 AI 帮你设计需要补充的日志字段、可能的触发条件和验证顺序。不要把一个推测直接写成根因。
示例函数也有明确边界:它一次性构造所有分页结果,适合已经在内存里的有限列表;切片是浅拷贝,页中的可变对象仍与原列表共享引用。处理大文件、生成器或数据库记录时,应重新设计迭代读取或查询逻辑,不能直接把这段代码套上去。
下一次让 AI 帮忙排错,可以先准备这三行:
触发问题的输入: 我期望得到的结果: 程序实际返回的结果:再加上一条能够区分对错的测试,你就有了检查 AI 修复建议的依据。