第 12 章 阅读 AI 生成的代码
学习目标
- 掌握阅读代码的顺序:先签名,后结构,再验证
- 会用边界测试发现 AI 代码里的隐藏 bug
- 会按审查清单评估并改进 AI 生成的代码
- 树立「理解并验证后再使用」的态度
12.1 为什么要专门练「读 AI 代码」
本书的核心理念之一:学完后,你既要能独立写 Python,也要能读懂 AI 生成的 Python 代码。AI 生成的代码通常能运行,但可能有三类问题:
- 隐藏 bug:常规输入正常,特殊输入出错。
- 命名随意:变量叫
t、s、x,读起来费劲。 - 写法冗长或脆弱:能跑,但容易在边界条件下崩溃。
本章教一套固定的阅读流程,让「读 AI 代码」从凭感觉变成有方法。
12.2 阅读顺序:签名 → 结构 → 验证
第一步,看签名。 函数名、参数、返回值,先猜它的意图:
def find_max(nums):
max_num = 0
for n in nums:
if n > max_num:
max_num = n
return max_numfind_max(nums) —— 名字说明「找出列表里的最大值」,参数是列表,返回一个值。意图明确。
第二步,看结构。 顺着数据流读:nums 里的每个 n 依次与 max_num 比较,大的赋值给 max_num。逻辑上「维护当前最大值」的思路是对的。
第三步,验证。 光看不行,必须运行,尤其要测边界条件:
print(find_max([3, 1, 2])) # 常规:3
print(find_max([-5, -1])) # 全负数:?输出:
3
0bug 出现了:find_max([-5, -1]) 返回 0。原因:最大值初始化为 0,而列表里全是负数,0 比谁都大。常规输入一切正常,边界输入暴露了问题——这正是 AI 代码最常见的 bug 类型。
12.3 边界测试
边界条件指容易出错的特殊输入:空列表、全负数、最大值在开头/结尾、重复元素、超大数等。测试时把常规值和边界值都跑一遍。
修复 find_max:不预设初始值,改用「第一个元素」或直接调用内置函数:
def find_max(nums):
if not nums: # 空列表返回 None
return None
return max(nums)
print(find_max([3, 1, 2]))
print(find_max([-5, -1]))
print(find_max([]))输出:
3
-1
None修复的同时处理了第二个边界:空列表。原代码对空列表会报 ValueError: max() arg is an empty sequence 或 UnboundLocalError,新版本显式返回 None。
12.4 审查清单
阅读和改进 AI 代码时,逐条检查:
- 边界处理:空输入、负数、0、超长输入会不会崩?
- 命名清晰:变量名是否能表达含义(
nums优于s,total优于t)? - 异常处理:可能出错的输入(用户输入、文件读取)有没有
try/except? - 重复代码:同样的逻辑是否出现多次?能否提取成函数?
- 复杂度:有没有不必要的循环或嵌套?能否用内置函数(如
sum、max)简化?
12.5 实例:改进一个能跑但脆弱的代码
假设 AI 生成下面的平均分函数,常规输入没问题:
def average(grades):
total = 0
for g in grades:
total = total + g
return total / len(grades)
print(average([85, 92, 78, 90, 66]))输出:
82.2按清单检查:
- 边界:
average([])会怎样?len(grades)为 0,除数为 0,抛ZeroDivisionError。空列表是真实的输入场景,必须处理。 - 简化:循环累加等价于
sum(grades),可读性更好。
改进:
def average(grades):
if not grades:
return 0
return sum(grades) / len(grades)
print(average([85, 92, 78, 90, 66]))
print(average([]))输出:
82.2
012.6 实例:让代码更简洁
AI 生成的这段代码功能正确,但写法啰嗦:
def count_words(text):
words = text.split()
result = {}
for word in words:
if word not in result:
result[word] = 1
else:
result[word] = result[word] + 1
return result利用字典的 get(key, 默认值),可以把「不存在则从 0 开始」合并成一行:
def count_words(text):
result = {}
for word in text.split():
result[word] = result.get(word, 0) + 1
return result
print(count_words("a b a"))输出:
{'a': 2, 'b': 1}两种写法结果完全一致,后者更短、意图更清楚。读 AI 代码时,「能跑」是底线,「好读、好维护」是目标。
动手实践
任务一:解读并修复。 下面的 AI 代码目标是把列表里所有数字相加。运行常规输入和空列表,按审查清单找出问题并修复:
def total(nums):
t = 0
for i in nums:
t += i
return t任务二:边界测试。 对下面的 AI 代码,写出至少 4 组测试输入(含边界),运行并记录结果,判断它是否有 bug:
def first_half(text):
return text[:len(text) // 2]提示:考虑空字符串、长度是奇数、长度是偶数、长度是 1。
任务三:改写。 用 sum 和 max 重写「求列表最大值和总和」的函数,并处理空列表。
常见错误
| 错误做法 | 说明 |
|---|---|
| 复制粘贴 AI 代码直接上线 | 未经边界测试,隐藏 bug 会在真实数据上爆发 |
| 只测常规输入 | find_max([-5, -1]) 这类边界输入最能暴露问题 |
| 看不懂也照用 | 出问题后无法定位;坚持「先读懂再使用」 |
| 认为能运行=正确 | 正确性要靠边界测试和人工审查,不只是「没报错」 |
| AI 给的 API 直接照抄 | 函数签名、参数可能并不存在,运行时才暴露 NameError |
章末练习
基础
- 简述阅读 AI 代码的三个步骤。
- 写出
find_max([-5, -1])在原版(初始化为 0)代码下的结果,并解释原因。 - 解释
result.get(word, 0) + 1这行代码的含义。
提高
- 下面的 AI 代码有什么问题?修复它:python
def divide(a, b): return a / b - 写出对
first_half的 4 组测试输入及对应正确结果,再运行验证。
挑战
- 下面 AI 代码的目标是「返回列表里第二大的数」。用边界测试找出 bug 并修复:python提示:考虑列表长度不足 2、有重复元素的情况。
def second_largest(nums): nums.sort() return nums[-2] - 选一段自己或 AI 写的 20 行以内的代码,按 12.4 审查清单逐条检查,写一份简短的审查报告(每条清单:通过 / 不通过 + 说明)。
章末自测
每题选择一个最佳答案。本书不附答案:完成后交由老师或 AI 老师批改讲解。
- 阅读代码的第一步通常是?
- A. 逐行读全部代码
- B. 先看函数签名与意图
- C. 直接改代码
- D. 删除注释
- 边界条件不包括?
- A. 空列表
- B. 全负数
- C. 常规正整数
- D. 超长输入
- 原版
find_max(初始值 0)对[-5, -1]返回?- A.
-1 - B.
-5 - C.
0 - D. 报错
- A.
average([])在原版代码下会?- A. 返回 0
- B. 报
ZeroDivisionError - C. 返回 None
- D. 返回空
result.get(word, 0)在键不存在时返回?- A. 报错
- B.
0 - C.
None - D.
word
- 判断 AI 代码是否正确,最可靠的方法是?
- A. 读一遍
- B. 运行并测试边界
- C. 问其他人
- D. 看代码长度
- 审查清单中「重复代码」一项关注的是?
- A. 代码行数多少
- B. 同样的逻辑是否出现多次
- C. 注释多少
- D. 变量类型
- 空列表传给
sum([]) / len([])会?- A. 返回 0
- B. 报
ZeroDivisionError - C. 返回 None
- D. 返回空列表
- 对 AI 代码的正确态度是?
- A. 一律不用
- B. 理解并验证后再使用
- C. 直接使用并信任
- D. 只用于学习
max([])会? A. 返回 0 B. 返回 None C. 报ValueErrorD. 返回空
