编程语言研究书库

调试与重构 — 把报错当线索,把测试当保险

调试与重构 — 把报错当线索,把测试当保险

这一章讲什么: 代码总会错,而且 AI 写得越快,错来得越快。这一章给你两条纪律: 调试时,把报错当程序在跟你说话——错误分三类,问法各不同,配一套六步流程; 改代码时,先写测试锁住行为再动手——重构不是可选项,是职业卫生。 读完你会拿到一套可重复的排错仪式,和一条「测试先行」的安全绳。

1. 这一章讲什么

第三章结束时你拥有了一个能跑的程序——它是结对生成的,你懂每一行,但它还没出过事。 这一章处理出事之后怎么办。

书里给这一章的定位很直白:调试一直是编程里最不 glamorous(不光彩)却最锻炼人的部分, 而 ChatGPT 这类工具已经从「帮你写代码的助手」变成了「调试与重构的伙伴」, 它能照亮连老手都会忽略的盲区1。但书里同时立了一个全书反复出现的规矩: AI 可以提方案,正确、高效、符合你设计意图的最终裁判是人2。

这一章在全书链条里是「保险层」:第五到九章的项目都会出错, 到时候你是慌,还是按流程走,取决于这一章练没练。

2. 顶层全景:两条纪律,一个循环

  纪律一:调试是沟通
    报错信息 → 判断错误类型(语法/运行时/逻辑) → 带上下文问 AI
      → 拿到解释与候选修复 → 应用 → 测试确认
  纪律二:重构是预防式调试
    先写测试锁住现有行为 → 小步改结构(拆函数/去重复/模块化)
      → 每步跑测试 → 行为不变、结构变好
        │
        ▼
    共同底座:pytest 失败测试(把「我以为」变成「我验证过」)

  图说:两条纪律在「测试」处汇合——调试靠失败测试复现,重构靠测试网兜底。

3. 核心原理

3.1 报错信息是程序在跟你说话

书里用一个场景开场:花一小时写完脚本,期待第一次成功运行, 结果控制台刷出一墙红字。新手在这里卡住,不知道发生了什么、从哪下手; 连老手有时也需要一点时间解码 traceback 到底在说什么3。

先解释三个词。 报错信息(error message) 是 Python 告诉你的「哪里不对」; 堆栈追踪(stack trace,也叫回溯) 是出错时「程序从入口一路调用到出错点的完整路径」, 像程序留下的脚印;traceback 是 Python 打印堆栈追踪的固定格式(最下面一行是错误类型, 往上依次是每一层调用及其源代码行)4。书里把它们统称为 Python 在「跟你谈论什么出了错、在哪里」5。

Python 的错误分三大类,这是本章的第一个承重结构6:

类型什么时候出现书里的例子
SyntaxError(语法错误)代码连 Python 的文法都过不了,根本没开始跑函数定义后忘了冒号
RuntimeError(运行时错误)程序跑起来了,执行中途撞墙拿空列表求平均 → 除零
Logic Error(逻辑错误)不崩,但结果不对折扣该减 20%,代码却算出 20

主线例子第一圈:一个 TypeError 的完整解剖。 这是本章的主线例子,从一段报错走全流程:

Traceback (most recent call last):
  File "expense_tracker.py", line 12, in <module>
    add_expense("100", "Food", "2025-11-11")
  File "expense_tracker.py", line 5, in add_expense
    writer.writerow([amount, category, date])
TypeError: unsupported operand type(s) for +: 'int' and 'str'

读法自上而下三层7:

  1. 最底层那行是错误类型与内容:TypeError: unsupported operand type(s) for +: 'int' and 'str'—— 你试图对整数和字符串做加法(这里其实是字符串拼接撞上了类型不匹配)。
  2. 往上是出错的那行代码及其所在函数:writer.writerow(...) 在 add_expense 里。
  3. 再往上是你的入口调用:line 12 调了 add_expense("100", ...)——注意传进去的是字符串 "100"。

第三层正是破案关键:amount 是字符串 "100"**,而写 CSV 的行需要类型兼容。 修复:写之前转成数,writer.writerow([float(amount), category, date])`8。 书里特别标了「为什么重要」:你不但修了 Bug,还理解了根因**——你的数据类型和 Python 预期之间的那个错配9。

这个读法可以直接交给 AI,而且要给上下文。 书里给的提示样板:

Context: 我有一个 calc.py,里头的 average(values) 现在是 sum(values)/len(values)。 Problem: 我的 pytest 在空列表上失败(ZeroDivisionError)。 Constraint: 我希望空列表返回 0(不抛异常),其他行为保持不变。 Goal: 给一个小的、地道的修复和一两句解释;兼容 Python 3.8+。10

注意这就是第三章的 CCG——背景、问题(约束)、目标——调试提示是 CCG 的第一个实战场所。 AI 的典型回答是加一个 guard clause(卫语句,函数开头先拦住非法输入的早退判断):

def average(values):
    if not values:
        return 0
    return sum(values) / len(values)

3.2 三类错误,三种问法

SyntaxError:最好抓,最常烦。 书里两个例子覆盖了从浅到深:

  • 浅:def greet(name) 后面漏了冒号 → SyntaxError: expected ':'。 贴给 AI 问「这函数我哪里语法错了」,秒回11。
  • 深:data = [1, 2, 3, 4 列表没闭合 → SyntaxError: '(' was never closed。 嵌套深的时候报错行号可能指错地方,AI 的价值在于看整体结构找不配对12。

书里点了一句:AI 不只是修语法,它解释原因——这就是调试直觉的来源13。

RuntimeError:跑起来才炸的。 书里演示了三种典型: ZeroDivisionError(空列表求平均)、IndexError(取了列表里不存在的下标)、 KeyError(字典里没有那个键)14。这类错误 AI 特别有效,因为它能同时读你的代码和报错信息, 推理出可能的诱因。书里还演示了「时灵时不灵」的隐蔽运行时错误: expenses.csv 是空文件时,读取的循环会因为没有行而失败—— AI 看到函数加样例输入,会主动建议加空文件检查,把你的思维从「修当前错」推向「预想到边的错」15。

LogicError:最阴的——程序不崩,结果撒谎。 书里的例子:

def calculate_discount(price, discount):
    return price * discount      # 想打八折,却算出了折扣额本身

print(calculate_discount(100, 0.2))   # 输出 20.0,期望 80.0

贴给 AI:「这个函数应该对价格打 20% 折扣,但输出不对,哪错了?」 它指出逻辑写成了「价格 × 折扣率」而不是「价格 × (1 − 折扣率)」16。 书里把这类调试的要害说成一个短语:the intention gap(意图鸿沟)——你写的和你想的之间的缝, 看见这条缝,是调试思维的关键跃迁17。

人机分工的对照表(书里给了,值得贴墙):

人AI
传统做法读报错 → 搜 → 翻文档 → 试 → 测 → 循环——
结对做法判断修复是否正确、高效、没引入新问题读代码+报错,给解释与候选修复18

一句话:AI 加速理解,但不替代谨慎思考。

3.3 六步调试流程:把排错变成仪式

书里反对「调试是混乱冲刺」的说法,给出六步可重复流程(reproducible workflow), 每步配具体动作和提示词19:

  ① 复现并记录失败    ② 缩小范围(隔离出最小的错)   ③ 写失败测试
  ④ 带上下文问 AI     ⑤ 应用候选修复,跑测试         ⑥ 重构并加固

① 复现并记录。 书里把这条称为「最被低估的一步」:如果 Bug 不能可靠地复现,你就无法证明修好了20。 记录三样东西:触发它的确切命令、完整堆栈、输入数据。 主线例子的第二步:average 在空列表上炸——先建 runner.py 调用 average([]) 复现。

② 缩小范围。 找到能复现错误的最小代码块。这里就是 average([]) 本身。 然后建一个针对性的 pytest 文件:

# test_calc.py
from calc import average
import pytest

def test_average_normal():
    assert average([10, 20, 30]) == 20

def test_average_empty():
    assert average([]) == 0      # 我们决定:空列表该返回 0

pytest -q 一跑,一个过一个挂——你现在有了一个可复现的失败测试,完美21。

③ 写失败测试(把期望说死)。 这一步强迫你先决定期望行为:空列表该返回 0、None, 还是抛自定义错误?在问 AI 之前把这个决定写进测试。 书里强调:这个清晰度决定了你后面能不能评判 AI 的建议22。

④ 带上下文问 AI。 用 §3.1 末尾那个 CCG 样板提示。AI 给了 guard clause 版本, 并解释为什么(卫语句挡住除零),顺带提边界情况23。

⑤ 应用候选修复并跑测试。 更新 calc.py,pytest -q 全绿。 还失败?把新的 traceback + 更新后的代码 + 你试过什么,贴回同一个对话:

"I applied your suggestion, but pytest still fails with this traceback [贴]. 这是我的更新后代码 [贴]。请找出测试仍失败的原因,或提议替代修复。"24

书里点出这个提示为什么有效:它让对话保持聚焦和建设性——在同一线程里迭代, AI 保有全部上下文。

⑥ 重构并加固。 测试绿了也别停,花两分钟加固:加 docstring; 加非数字输入的测试;加类型守卫或抛出信息更清楚的 ValueError。 书里给出的「抛光版」average 长这样:

def average(values):
    """计算一组数字的平均值。
    - 空序列返回 0
    - 输入不可迭代抛 TypeError
    - 任一项非数字抛 ValueError
    """
    if not hasattr(values, "__iter__"):
        raise TypeError("Input must be an iterable.")
    total, count = 0.0, 0
    for value in values:
        try:
            total += float(value)
            count += 1
        except (TypeError, ValueError):
            raise ValueError("All items must be numeric.")
    if count == 0:
        return 0
    return total / count

再补两个测试:average(["1", "a"]) 应抛 ValueError;average([5]) == 5.025。 从「能跑」到「耐造」,就差这一步。

更难的一圈:归一化函数的数值陷阱。 「归一化」指把一组数按比例缩放,让不同量级的数值变得可比; 书里的 normalize 例子是把每个值除以平均值。这一圈演示流程在性能/数值 Bug 上的走法:

def normalize(values):
    mean = sum(values) / len(values)
    return [v / mean for v in values]

问题:均值是 0 怎么办?均值是接近 0 的浮点数怎么办(结果爆炸)? 流程照旧:写失败测试 normalize([0,0,0]) 期望 [0,0,0];隔离; 然后带约束问 AI——「均值为 0 或接近 0 时结果无效;希望 mean == 0 时返回原列表, 否则安全归一化;给个数值健壮的实现并解释取舍」。 AI 的典型建议:判断 abs(mean) < 1e-12(一个极小的数,epsilon,数值计算里挡除零的垫子)就返回原列表; 或用 epsilon 防除法不稳;或用 statistics.mean 加回退26。 取舍(transparency of tradeoffs)是这一步的关键词:好的 AI 回答不只给代码,还说清每种做法的代价。

3.4 重构:先锁行为,再动结构

重构(refactoring)= 不改变外部行为地重写代码结构——让它更干净、更模块化、更好维护。 书里给了一个亲历证据:作者曾加入一家每改一处都要先读一周代码的公司, 原因是代码里全是重复、隐式副作用、一个函数干六件事;重构后「Bug 率骤降,新人入职从以天计变成以小时计」。 这就是重构为什么不是可选装修,是职业卫生27。

工具箱(一次装好,全程复用):

  pip install pytest black flake8 mypy
工具干什么
pytest跑测试
black统一格式化(代码排版)
flake8基础 lint(静态检查常见坏味道)
mypy可选的静态类型检查

它们的共同作用:验证你的重构保住了行为、提升了清晰28。

主线例子第二圈:报告生成函数的两阶段重构。 书里的脏函数 generate_report(file_path) 一口气干了四件事:读 CSV、解析、聚合、格式化输出。毛病清单29:

  1. 一个函数同时干 IO、解析、聚合、格式化——没法单独测;
  2. 重复:r[2] 转 float 散在三处;
  3. 魔法下标:r[0]、r[1]、r[2] 是什么意思?得靠猜;
  4. IO 与逻辑纠缠,单测无从下手。

动刀前的一步(本章第二重要的习惯):先写测试锁住行为。

def test_generate_report():
    data = "2025-01-01,North,100\n2025-01-02,North,50\n2025-01-01,South,200\n"
    with tempfile.NamedTemporaryFile("w+", delete=False) as tf:
        tf.write(data); tf.flush()
    output = generate_report(tf.name)
    assert "North: total=150.00" in output
    assert "South: total=200.00" in output

测试通过 = 行为已被捕获,「现在可以安全地重构了」30。

阶段一:小步提取函数。 只把「解析一行」抽出来,引入一个 dataclass(数据类, 一种专门用来装数据的类,声明字段就自动获得初始化、打印等功能):

@dataclass
class SaleRow:
    date: datetime
    region: str
    amount: float

def parse_row(row):
    return SaleRow(date=datetime.strptime(row[0], "%Y-%m-%d"),
                   region=row[1], amount=float(row[2]))

generate_report 里循环体换成 rows.append(parse_row(r))。跑测试,还绿。 这一步消掉了重复(转换只发生一次),解析变显式31。

阶段二:让 AI 做更大的拆分(带硬约束)。 书里的提示值得照抄:

Context: 我有个报表函数,读 CSV、解析行、聚合总额、算平均、格式化文本报告。 我已把解析拆成 parse_row;剩下的 generate_report 仍混着 IO 和聚合,用循环算平均。 Constraint: 保持文本输出格式不变(兼容其他系统),只保留正金额的行为;只用标准库。 Goal: 把读、聚合、格式化拆成三个函数——read_sales(file_path) -> List[SaleRow]、 aggregate(sales) -> Dict[str, Dict[str,float]](返回总额与计数)、format_report(aggregation) -> str。只返回代码。32

拆完的结构:

def read_sales(file_path):      # 只管读文件 → SaleRow 列表
def aggregate(sales):           # 只管聚合 → {region: (总额, 计数)}
def format_report(aggregation): # 只管拼文本
def generate_report(file_path): # 三行,把上面三个串起来

变好的四句话(书里的原话,逐条对应第二章的四个词):单一职责——每个函数一件事,好测; DRY——float 转换不再重复,总额与计数一起算;模块化——aggregate 可以被别的数据源复用; 类型注解加 dataclass 让代码自我说明33。

验证清单(重构后的规定动作): pytest -q 全绿;black . 统一格式; flake8 查坏味道;mypy report.py 查类型——只修代表真实问题的警告,不为风格洁癖打架34。 然后给三个新函数各补一个单测(读文件用临时文件、聚合用手造的 SaleRow、格式化查字符串精确), 把网织密35。

加固与演进(书上叫 Evolve without Breaking): 加边界测试(空文件、负金额、畸形行); 从多处收 CSV 的话,把 parse_row 换成返回 Optional[SaleRow] 的 safe_parse_row,坏行记警告跳过; 打印换成 logging(日志库,生产代码的标配);文件大就用**生成器(generator,一次产一个值、 不一次性全装进内存的迭代方式)**流式处理36。

什么时候不该重构(书里诚实地列了三条红线):

陷阱说明
过早泛化别为不存在的需求抽象;保持 KISS(Keep It Simple, Stupid,怎么简单怎么来)
破坏公共 API别的系统依赖函数签名或文件格式时,保向后兼容,或给兼容垫片
过早重度类型化没有测试兜底的激进静态类型会拖慢进度;mypy 检查逐步加37

重构期间的实用提示三则(书里的原样): 「把 CSV 读取逻辑抽成 read_sales,保持行为,只显示改动」 「给 aggregate 函数建议单测,含空输入和负金额的边界」 「我想把报告输出改成可选的 JSON,加个 format 参数,要最小改动且向后兼容」。 共同特点:范围小、要改动清单、要求向后兼容38。

3.5 动手前的一次性准备(书里的 Setup Checklist)

六步流程每次都要走,但有三件事是每个项目只做一次的。书里的清单39:

# 建虚拟环境(项目开始时一次)
python -m venv .venv
source .venv/bin/activate        # macOS/Linux;Windows 用 .venv\Scripts\activate

# 装测试工具(一次)
pip install pytest pytest-cov

外加一条容易忽略的:为实验开一条 Git 分支:

git checkout -b debug/<short-description>

书里给的理由只有一句,但分量很重:AI 建议的修复可能把整个项目炸掉, 分支是你的安全绳40。这和第四章「先写测试锁行为」是同一类纪律的两种形态: 一个保代码,一个保结构。

3.6 章末练习:KeyError 的完整排障圈

原书章末的练习是一道刻意埋了 typo 的题,正好把六步流程再走一圈:

expenses = [{"amount": 100, "category": "Food"}, {"amount": 50, "category": "Transport"}]
total = 0
for item in expenses:
    total += item["amounts"]        # ← typo:键名多了个 s
print(total)

跑起来必炸 KeyError: 'amounts'。按流程走:

  1. 复现:运行,记录报错全文——KeyError: 'amounts',File 行号指向 total += ... 一行;
  2. 缩小:问题就一句,无需再缩;
  3. 失败测试:为这个函数写测试:传入上面的 expenses 列表,期望 total == 150;
  4. 问 AI:"I got a KeyError: 'amounts'. Explain why this happened and how to fix it."41 AI 会指出:字典里的键叫 amount,代码写的是 amounts——KeyError 的全部含义就是 「你问的键不在字典里」;
  5. 修复:改成 item["amount"],测试转绿;
  6. 加固:书里建议接着做变体实验——把数字换成字符串、删掉某个键—— 每次让 AI 解释「为什么会报错、怎么优雅处理」,把 KeyError/TypeError/缺键的防御写法各见一遍42。

这个练习教的不只是修 typo,而是建立「报错类型 → 病因」的条件反射。 书末 Key Terms 把本章的八个词收了尾43:stack trace(错误足迹)、logic error、 runtime error、syntax error、refactoring、DRY、modularization、AI-assisted debugging—— 八个词里前四个是「读错」,后四个是「改码」,正好对应本章的两条纪律。

3.7 重构前后的对照总表:一个函数的四代

把本章的重构主线收进一张表——同一个报表函数,从「能跑」到「耐造」走了四代, 每一代动一刀,每刀都有测试兜底:

代形态动了哪一刀验证
0generate_report 一大坨——(起点,行为未知)先写测试锁住它
1抽出 parse_row + SaleRow解析与聚合分离;float 转换从三处收一处pytest -q 绿
2read_sales / aggregate / format_report 三函数单一职责;DRY;模块化测试绿 + black/flake8/mypy
3加类型守卫、卫语句、logging、流式处理加固:边界、异常、性能新增边界测试全绿

这张表是「渐进重构」的操作定义:不是重写,是一串小步; 不是「改完再测」,是「每步必测」;不是追求完美,是「下一步比上一步好一处」44。

3.8 对话式重构的提示库(书里的 Practical Prompts)

§3.4 给了三条,这里把本章出现过的重构提示全收进来,按用途分类—— 它们有一个共同的配方:小范围 + 保行为 + 要改动清单:

抽取:   "Given the code below, extract the CSV reading logic into a
         read_sales(file_path) function that returns List[SaleRow].
         Preserve current behavior. Show changes only."
补测:   "Suggest unit tests for the aggregate function above.
         Include edge cases for empty input and negative amounts."
加参数: "I want to change the report output to JSON as an optional
         argument format='text'|'json'. Show a minimal and
         backward-compatible change."

书里的使用纪律:提示保持紧凑范围;打算贴了就跑的时候要明确要 code-only 的回答45—— 否则 AI 会在代码前后裹大段解释,粘进文件还得手动剥。

3.9 成绩函数的双重 Bug:一个例子的两层修复

§3.2 末尾的人机分工表之后,书里还有一个更立体的例子,值得单独走一遍—— grade_student 函数,它身上叠着两层问题:

def grade_student(scores):
    average = sum(scores) / len(scores)
    if average > 80:   return "A"
    elif average > 60: return "B"
    elif average > 40: return "C"
    else:              return "D"

print(grade_student([80, 90, 70]))   # 正常运行
print(grade_student([]))             # ZeroDivisionError:除零

第一层(显性的):空列表炸除零。第二层(隐性的,书里的 AI 回答主动指出的): 边界用 80 分到底算 A 还是 B? average > 80 意味着 80 整只算 B—— 这究竟是设计如此还是 off-by-one(差一错误:边界本该含 80 而漏了)?书里让 AI 给出的 安全版本加的是输入验证(if not scores: return "No scores provided"), 并补一句「也可以考虑加 logging,或在数据缺失时抛自定义错误」46。

这一例的教学点:好的调试回答不只修你问的,还把你没问的边界一并照亮。 「average 恰好 80 该算什么」这个问题,在原代码里没有任何报错信号—— 它是逻辑层面的潜在争议,只有「带着测试思维读代码」才会浮现。 这就是「人负责裁判」的具体内容:AI 照亮盲区,你决定 80 算不算 A。

3.10 防御式编程的谱系(书里散落的写法聚成一族)

本章出现了好几种「预先拦住坏输入」的写法,它们是一个家族,按轻重排开:

写法重量什么时候用
guard clause(if not values: return 0)最轻单个已知坏条件,函数开头早退
默认值/温柔取法(.get("city", "Unknown"))轻缺键是常态而非错误
抛带信息的异常(raise ValueError("All items must be numeric."))中坏输入是调用方的错,必须让它知道
try/except 兜底转日志重边界情况不该中断主流程(如批处理跳过坏行)

书里没有给这张谱系,但 normalize 例的 eps 垫子、average 例的 guard clause、 第六章 safe_parse_row 的「返回 None + 记警告」,分别落在谱系的不同档上。 选哪档,取决于坏输入是「预期之外」(选重的)还是「预期之中」(选轻的)—— 这个判断书里交给读者,我们把它显式化了。

3.11 重构提示的「黄金三句」再扩展(书外的实操补充)

§3.8 的提示库收了三条书里的。实战里还有三条高频句式,书里散落出现、值得聚拢 (出处随句附在后):

「保持行为,只显示改动。」——治 AI 重写过度的特效药。 没有这句,AI 常把无关行也重排,review 时你得逐行找真改动。 (同源提示见书里的 "Show changes only."47)

「先别改代码,先告诉我你会怎么拆。」——把重构方案的决定权抢回来。 AI 先说拆法,你同意,它才动手;不同意,改方案比改代码便宜得多。 (与书里「先要结构再要代码」的脚手架思路一致48)

「这个改动会让哪个测试变红?」——反向使用测试网。 不是改完再跑,而是改前预测:答得上来,说明你和 AI 都真懂这个代码库; 答不上来,说明测试覆盖有洞,先补洞。

3.12 本章的两个「为什么」收尾

为什么调试是「沟通」而不是「修理」? 书里的结语给了解释: 每个报错信息、每条堆栈、每次意外输出,都是程序在试图告诉你什么—— 你听得越好,进步越快49。AI 在这个比喻里的角色是「同声传译」: 把程序的方言翻成人话,但对话的双方仍然是你和程序。

为什么重构是「预防式调试」? Points to Remember 里有一句最短的话点了题50。 逻辑:混乱的结构是 Bug 的温床——重复代码让改一处漏一处,长函数让边界看不见, 隐式副作用让「谁改了状态」成谜。重构把这些温床拆掉,未来的 Bug 自然少了。 调试治已病,重构治未病;本章把两者放进同一章,就是这个因果。

3.13 normalize 例的完整决策记录(书里第三圈走查)

§3.3 的 normalize 例子是本章流程走得最完整的第二例,值得把决策链单独存档—— 它展示了「写失败测试」那一步里期望是怎么定下来的:

  1. 先造一个注定失败的测试:normalize([0, 0, 0]) 期望返回 [0, 0, 0] (均值是 0,没有任何归一化可做,原样退回就是合理期望)。 当前实现:mean = 0 → 每个元素除以 0 → 崩或出 inf(无穷大)51;
  2. 隔离:问题锁定在 v / mean 这一行;
  3. 带约束问 AI:「mean 为 0 或接近 0 时结果无效;期望 mean == 0 时返回原列表, 否则安全归一化;给数值健壮的实现并解释 tradeoffs」52;
  4. AI 的三种候选解法(书里的原样列举):
    • 判断 abs(mean) < 1e-12 就返回原列表;
    • 用一个小的 epsilon 垫子避免除法不稳;
    • 用 statistics.mean 加回退;
  5. 采纳与验证:选带 eps 参数的写法,补 eps 行为的测试,pytest 全绿再迭代53。

第 4 步是本章最重要的一瞥:AI 给了三个解法,不是一个。 「解释 tradeoffs」 这个 Goal 部件起了作用——它逼 AI 把取舍摊开,你才能按自己的场景选。 如果提示里没有这四个字,AI 通常只给一个「标准答案」,而标准答案未必适合你的数据。

3.14 一章两份的读物结构

最后说明本章在全书里的读法:调试与重构被放进同一章,是原书的结构选择, 也是合理的——它们共享同一套底座(测试、小步、验证),只是方向相反:

  调试:行为不对 → 找到哪一步错了 → 改到对(测试从红到绿)
  重构:行为已对 → 让结构更好 → 保持对(测试一直绿)
      共同底座:pytest / 小步 / 每步必验 / AI 只出建议

读的时候别只学一头:会调试不会重构,你的代码会越修越乱; 会重构不会调试,测试红了不知道哪一步打破的。两头都练,第四章才算读完。

3.15 动手机之前的十分钟仪式(本章流程的浓缩版)

把全章压成一个可执行的仪式——每次遇到 Bug,先花十分钟走这套,再决定要不要更深挖:

  第 1 分钟:复现。 exact 命令 + 完整堆栈 + 输入,复制进笔记
  第 2 分钟:读最底层那行错误类型,判断三类(语法/运行时/逻辑)
  第 3 分钟:缩小。能造出最小复现吗?(一行调用最好)
  第 4–6 分钟:写失败测试。把「期望」从脑子里搬到 pytest 文件里
  第 7–9 分钟:CCG 问 AI(代码 + 报错 + 期望 + 你试过的)
  第 10 分钟:应用、跑测试。绿了 → 加固;红了 → 带新报错回第 7 分钟

这个仪式的每一分钟都对应书里的一步,没有一分钟是我们加的。 它的价值在于「把 panic 变成 procedure(把恐慌变成程序)」: 报错来了不再是从零开始的慌乱,而是十条已经踩熟的路。

3.16 两个工具的一句职责(别再用混)

本章装了四件套(pytest/black/flake8/mypy),新手最常见的混用是把它们当一个东西:

  pytest  答:行为对不对?(跑测试,红的绿的都是答案)
  black   答:长得整不整齐?(只排版:缩进、空格、换行,不改逻辑)
  flake8  答:有没有坏味道?(未用变量、未定义名、行太长——可能暗藏真 Bug)
  mypy    答:类型标得对不对?(int 传给 str 参数?提前在跑之前抓)

分工的记忆钩:pytest 管「对不对」,其余三个管「清不清楚」。 四者跑的顺序书里有暗示:先 pytest(行为保住了没),再 black(排版), 再 flake8(坏味道),最后 mypy(类型)54——顺序的理由很朴素: 行为都还没保住时,讨论排版是奢侈;类型是锦上添花,放最后。

3.17 六步流程的两个变体(书里没写、从流程推出来的)

掌握六步主流程后,两种常见变体可以从流程本身推出来(我们的推演,挂在流程出处下55):

变体一:「热修复」版——测试在修复之后补。 线上着火时,顺序反转: 先最小改动止血(第④⑤步先行),测试后补(第③步移到第⑤步后)。 代价是止血改动没有测试网保护,所以止血后必须回到主流程补完第③步—— 热修复的债,当天还。

变体二:「探索」版——没有测试框架时。 接手一个还没有 pytest 的老项目, 先不建框架,用第①②④步 + print 验证跑通;攒下三个以上同类 Bug 后, 再一次性把测试框架立起来,把探索期手验过的 case 固化成测试。 书里的 setup checklist 默认项目从零开始56;这个变体是「从零项目」到「遗留项目」的桥。

3.18 本章与第三、九章的两条回路

回路一(与第三章): 调试提示就是 CCG——§3.1 的样板(Context/Problem/Constraint/Goal) 与第三章的四件套同构。第四章是 CCG 的第一个高频实战场:每修一个 Bug, 就是一次小型的「分解 + 结构化表达」练习;提示写得越熟,第三章越不需要回头。

回路二(与第九章): 第九章的「故意发坏请求」「错误四分类」是本章内容在 Web 上的重排: 脚本的「终端红字」换成应用的「状态码与 JSON 报错」,pytest 换成 curl, 六步流程换成「设计契约 → 测好/坏路径」。读完第九章再回看,本章的每个概念都有网络版。

3.19 调试的「提问进化」:同一报错的三代问法

同一类报错,随着本章方法的内化,你的问法会进化三代——把 TypeError 当标本:

第一代(贴报错):「TypeError: unsupported operand type(s) for +: 'int' and 'str',怎么修?」 → AI 给通用解释,你自己还要翻译回自己的代码。

第二代(贴报错 + 代码):「这个函数在写 CSV 时报 TypeError: int + str,代码如下……」 → AI 定位到 amount 是字符串,给转换修复——本章 §3.1 的常规动作。

第三代(贴报错 + 代码 + 期望 + 测试):「test_add_expense 在 amount 传字符串时期望 自动转 float,现在抛 TypeError。这是期望行为写错了,还是实现缺了转换?」 → AI 被迫在「测试与实现谁对」之间表态,你拿到的是设计判断,不只是修复57。

三代的差别在「你给了 AI 多少自己的思考」。第三代把「期望」交出去, 等于把裁判席的一半让给 AI 来质询——这正是第四章「测试先行」的自然延伸。 问法的进化,就是调试能力的进化。

3.20 本章的一句话:Bug 是信,不是敌

最后收一句。本章教的所有流程,归根到底是同一个姿态的转变: 报错不是敌人打过来的一拳,是程序寄来的一封信。 信要拆(读堆栈)、要回(带上下文问)、要归档(测试固化)—— 书里的结语原话:调试不是惩罚,是沟通;你听得越好,进步越快49。

带着这个姿态学后面的章节:第五章的日志是程序的日常信件, 第七章的准确率是模型寄来的成绩单,第九章的 404 是路由寄回的退信—— 全书的工程实践,都是「读信」艺术的不同文体。

3.21 本章的最短版本(给时间紧的人)

整章可以压成五行,已经会 pytest 的人只看这五句:58

  ① 报错三层读:类型 → 出错行 → 谁调起来的
  ② 三类错误三种问法;逻辑错误要找「意图鸿沟」
  ③ 不能复现 = 不能修
  ④ 期望先写进测试,再问 AI——否则你无法评判它的建议
  ⑤ 重构前先锁行为;每步必测;AI 只出建议,测试说了算

五行之外的所有内容,都是这五句的展开、例证与边界。时间紧时按此行; 有闲时回到各节,把五行在 average 与 normalize 两个例子上各走一遍—— 两个例子走熟,五行就长在手上了。

3.22 与第二章的分工澄清(两章都有「重构」,有什么不同?)

第二章 §3.7–3.10 也讲重构,与本章的分工容易混,这里钉一下:59

  第二章的重构:读得懂的副产物——读懂遗留代码后,顺手让它更可读
  本章的重构:工程纪律——测试先行、小步验证、红线三陷阱

第二章练「眼光」(看得出哪里该改),本章练「手法」(改得安全)。 眼光没有手法,改动是赌博;手法没有眼光,你在给不烂的代码抛光。 两章合起来才是一个完整动作;顺序上先二后四——先用第二章的读法 找到真正该动的地方,再用本章的流程安全地动。

3.23 最后一格台阶:从「会修」到「会防」

本章把调试的六个动作都给了,还缺最后一步的自觉:每次修完,问一句「这类 Bug 怎么防再犯?」

书里散落的答案可以收成三类:同类错误写进测试集(第 ③ 步的固化); 共性的输入错误在入口处加校验(§3.10 的谱系);重复踩的坑写进团队 checklist (§3.5 的安装清单就是这种东西)。调试的终点不是绿灯,是这类红灯不再亮。 到那一刻,第四章才真正读完——你手里的不再是排错流程,是一套防错系统。

判断(我们的,不是书里的): 这一章对「AI 结对」的态度是全书最克制的一章—— 它把 AI 放在「解释器」的位置,把「裁判」的位置留给了测试。 这个分工是全书工作法里最重要的一条暗线:凡是能写成断言的期望,都不要交给 AI 评判。 原因很实际:AI 的解释可以错得理直气壮,而断言只认真假。 所以本章六步流程里最不能被省掉的是第 ③ 步——失败测试是整条流程里唯一不依赖 AI 品味的环节, 省掉它,六步就退化成「问 AI、信 AI」的两步,前面五章攒下的判断力无处安放。 如果错,会错在: 有些期望很难写成断言(「这段代码好不好读」), 那一类仍然只能交给评审与品味——本章的「AI 能解释,但你必须推理」 在这些地方依然是最后防线,测试盖不到所有判断。

4. 作者的判断与证据

有证据的判断:

  • 「不能可靠复现就无从证明修复」——软件工程的通行公理,书里用在六步流程的第①步,正典20;
  • 「重构前先锁行为」——以测试通过作为「行为已捕获」的判据,这是行业最佳实践30;
  • 「六步流程 + CCG 调试提示」——本章论证最实的部分,有完整双例子(平均数、归一化)演示19。

经验之谈:

  • 「重构让 Bug 率骤降、入职以小时计」——作者亲历,单一样本,方向可信、幅度不可外推27;
  • 「mypy 逐步加」——稳妥但非唯一策略,有团队从第一天就全量类型化。

推测:

  • 「AI 照亮老手的盲区」——本章开头的卖点句,无测量支撑,当作愿景读1。

5. 边界与局限

  • 不覆盖并发与异步调试:多线程、异步代码的 Bug 特征(时序、竞态)完全不同,本章方法只覆盖单线程;
  • 不覆盖调试器工具:pdb、断点、IDE 调试器都只在开头一句带过(「pdb 或 print 仍然有效」), 教学全押在「读报错 + 问 AI」上——快,但遇到「程序不崩只是慢」这类问题会不够用;
  • 测试 Coverage 只字未提:没有讲覆盖率指标与「测试是否足够」的判据;
  • flake8/mypy 的误报处理靠手感:「只修真问题」没有可操作标准,新手容易在两个极端(全修/全忽略)之间摇摆;
  • AI 可能给出「让报错消失但不解决问题」的修复(比如把异常整个吞掉)——本章只在 §3.2 提了一句 「人负责裁判」,没有给出识别这类「静默失败」的具体手法(我们的补充:改完后问一句 「这个修复有没有掩盖错误而不是解决错误?」)。

6. 可带走的

本章主线例子,一行写完: average 空列表炸 ZeroDivisionError → 复现记录 → 缩小到 average([]) → 写失败测试(test_average_empty) → CCG 提示问 AI → 加 guard clause → pytest 全绿 → 加 docstring、非数字守卫、两个新测试 → 从「能跑」到「耐造」。

  1. 报错是程序在说话:先读最底层错误类型,再看出错行,再看出错行是被谁调起来的;
  2. 错误三类三种问法:语法(看结构配对)、运行时(代码+报错一起贴)、逻辑(说清期望输出,找意图鸿沟);
  3. 六步流程:复现 → 缩小 → 失败测试 → CCG 问 AI → 修复跑测试 → 加固;不能复现 = 没法证明修好;
  4. 期望行为先写死在测试里,再去问 AI——否则你无法评判它的建议;
  5. 修复后迭代提示:「我照做了但还失败,这是新报错+新代码+我试过的」——同一线程,上下文不丢;
  6. 重构是预防式调试:先写测试锁行为,再小步拆;每个函数一件事;
  7. 工具四件套:pytest / black / flake8 / mypy——重构后全跑一遍;
  8. 重构红线:不过早泛化、不破坏公共 API、不激进类型化;
  9. 拒绝「静默修复」:AI 的修复若只是吞掉异常,不算修复;
  10. 一句话收尾(书里的原话):「AI 能解释,但你必须推理。」60

7. 原文地图

主题原书章原文位置
调试是沟通、堆栈三层读法Debugging and Refactoring with AItext/16-ch04-4-debugging-and-refactoring-with-ai.txt:74(搜「talking to you」) · text/16-ch04-4-debugging-and-refactoring-with-ai.txt:166(搜「walk me through」)
错误三类同上text/16-ch04-4-debugging-and-refactoring-with-ai.txt:87(搜「Syntax Errors」)
TypeError 解剖与根因同上text/16-ch04-4-debugging-and-refactoring-with-ai.txt:112(搜「simple terms」) · text/16-ch04-4-debugging-and-refactoring-with-ai.txt:239(搜「root cause」)
上下文调试提示样板同上text/16-ch04-4-debugging-and-refactoring-with-ai.txt:815(搜「Context: I have」)
逻辑错误的意图鸿沟同上text/16-ch04-4-debugging-and-refactoring-with-ai.txt:525(搜「intention gap」)
人机分工对照同上text/16-ch04-4-debugging-and-refactoring-with-ai.txt:272(搜「Human Approach」)
六步流程同上text/16-ch04-4-debugging-and-refactoring-with-ai.txt:688(搜「Reproduce and record」)
复现是最被低估的一步同上text/16-ch04-4-debugging-and-refactoring-with-ai.txt:713(搜「underrated」)
失败测试锁期望同上text/16-ch04-4-debugging-and-refactoring-with-ai.txt:800(搜「desired behavior」)
修复失败后的迭代提示同上text/16-ch04-4-debugging-and-refactoring-with-ai.txt:882(搜「still fails」)
归一化数值陷阱同上text/16-ch04-4-debugging-and-refactoring-with-ai.txt:995(搜「normalize」) · text/16-ch04-4-debugging-and-refactoring-with-ai.txt:1048(搜「eps」)
重构=职业卫生同上text/16-ch04-4-debugging-and-refactoring-with-ai.txt:1072(搜「professional hygiene」)
工具四件套同上text/16-ch04-4-debugging-and-refactoring-with-ai.txt:1081(搜「pytest black flake8 mypy」)
脏函数四大毛病同上text/16-ch04-4-debugging-and-refactoring-with-ai.txt:1186(搜「Single function doing」)
动刀前先锁行为同上text/16-ch04-4-debugging-and-refactoring-with-ai.txt:1247(搜「captured behavior」)
dataclass 提取 parse_row同上text/16-ch04-4-debugging-and-refactoring-with-ai.txt:1300(搜「class SaleRow」)
三函数拆分提示同上text/16-ch04-4-debugging-and-refactoring-with-ai.txt:1399(搜「read_sales(file_path)」)
变好的四句话同上text/16-ch04-4-debugging-and-refactoring-with-ai.txt:1547(搜「Single Responsibility」)
验证清单与补测试同上text/16-ch04-4-debugging-and-refactoring-with-ai.txt:1566(搜「faith-based」)
红线三陷阱同上text/16-ch04-4-debugging-and-refactoring-with-ai.txt:1672(搜「Premature Generalization」)
章末 KeyError 练习同上text/16-ch04-4-debugging-and-refactoring-with-ai.txt:1767(搜「KeyError: amounts」)
实验分支安全绳同上text/16-ch04-4-debugging-and-refactoring-with-ai.txt:681(搜「safe rollback」)
Key Terms 八词同上text/16-ch04-4-debugging-and-refactoring-with-ai.txt:1781(搜「trail your program leaves」)
成绩函数两层修复同上text/16-ch04-4-debugging-and-refactoring-with-ai.txt:537(搜「grade_student」)
防御写法一族(我们的归纳)—

Footnotes

  1. 出处:「Debugging and Refactoring with AI」第 24 段(text/16-ch04-4-debugging-and-refactoring-with-ai.txt:24,搜「blind spots」)。 ↩ ↩2

  2. 出处:「Debugging and Refactoring with AI」第 27 段(text/16-ch04-4-debugging-and-refactoring-with-ai.txt:27,搜「human judgment」)。 ↩

  3. 出处:「Debugging and Refactoring with AI」第 53 段(text/16-ch04-4-debugging-and-refactoring-with-ai.txt:53,搜「wall of red text」)与第 74 段(text/16-ch04-4-debugging-and-refactoring-with-ai.txt:74,搜「experienced developers」)。 ↩

  4. 出处:「Debugging and Refactoring with AI」第 74 段(text/16-ch04-4-debugging-and-refactoring-with-ai.txt:74,搜「talking to you」)。Key Terms 一节对 stack trace 的定义见第 1781 段(text/16-ch04-4-debugging-and-refactoring-with-ai.txt:1781,搜「trail your program leaves」):「出错时你的程序留下的足迹。」 ↩

  5. 出处:「Debugging and Refactoring with AI」第 74 段(text/16-ch04-4-debugging-and-refactoring-with-ai.txt:74,搜「crucial」)。 ↩

  6. 出处:「Debugging and Refactoring with AI」第 87 段(text/16-ch04-4-debugging-and-refactoring-with-ai.txt:87,搜「Syntax Errors」)。 ↩

  7. 出处:「Debugging and Refactoring with AI」第 166 段(text/16-ch04-4-debugging-and-refactoring-with-ai.txt:166,搜「walk me through」)。 ↩

  8. 出处:「Debugging and Refactoring with AI」第 203 段(text/16-ch04-4-debugging-and-refactoring-with-ai.txt:203,搜「trying to record」)。 ↩

  9. 出处:「Debugging and Refactoring with AI」第 239 段(text/16-ch04-4-debugging-and-refactoring-with-ai.txt:239,搜「root cause」)。 ↩

  10. 出处:「Debugging and Refactoring with AI」第 815 段(text/16-ch04-4-debugging-and-refactoring-with-ai.txt:815,搜「Context: I have」)。 ↩

  11. 出处:「Debugging and Refactoring with AI」第 300 段(text/16-ch04-4-debugging-and-refactoring-with-ai.txt:300,搜「Missing Colon」)。 ↩

  12. 出处:「Debugging and Refactoring with AI」第 363 段(text/16-ch04-4-debugging-and-refactoring-with-ai.txt:363,搜「never closed」)。 ↩

  13. 出处:「Debugging and Refactoring with AI」第 396 段(text/16-ch04-4-debugging-and-refactoring-with-ai.txt:396,搜「debugging intuition」)。 ↩

  14. 出处:「Debugging and Refactoring with AI」第 249 段(text/16-ch04-4-debugging-and-refactoring-with-ai.txt:249,搜「IndexError」)。 ↩

  15. 出处:「Debugging and Refactoring with AI」第 262 段(text/16-ch04-4-debugging-and-refactoring-with-ai.txt:262,搜「expenses.csv is empty」)。 ↩

  16. 出处:「Debugging and Refactoring with AI」第 501 段(text/16-ch04-4-debugging-and-refactoring-with-ai.txt:501,搜「apply a 20% discount」)。 ↩

  17. 出处:「Debugging and Refactoring with AI」第 525 段(text/16-ch04-4-debugging-and-refactoring-with-ai.txt:525,搜「intention gap」)。 ↩

  18. 出处:「Debugging and Refactoring with AI」第 272 段(text/16-ch04-4-debugging-and-refactoring-with-ai.txt:272,搜「Human Approach」)。 ↩

  19. 出处:「Debugging and Refactoring with AI」第 688 段(text/16-ch04-4-debugging-and-refactoring-with-ai.txt:688,搜「Reproduce and record」)。 ↩ ↩2

  20. 出处:「Debugging and Refactoring with AI」第 713 段(text/16-ch04-4-debugging-and-refactoring-with-ai.txt:713,搜「underrated」)。原文:「Reproducing the bug reliably is the most underrated step. If it will not fail reliably, you cannot prove a fix.」 ↩ ↩2

  21. 出处:「Debugging and Refactoring with AI」第 47–48 段(text/16-ch04-4-debugging-and-refactoring-with-ai.txt:764,搜「test_calc.py」)。 ↩

  22. 出处:「Debugging and Refactoring with AI」第 800 段(text/16-ch04-4-debugging-and-refactoring-with-ai.txt:800,搜「desired behavior」)。 ↩

  23. 出处:「Debugging and Refactoring with AI」第 51–52 段(text/16-ch04-4-debugging-and-refactoring-with-ai.txt:858,搜「guard clause prevents」)。 ↩

  24. 出处:「Debugging and Refactoring with AI」第 882 段(text/16-ch04-4-debugging-and-refactoring-with-ai.txt:882,搜「still fails」)。 ↩

  25. 出处:「Debugging and Refactoring with AI」第 913 段(text/16-ch04-4-debugging-and-refactoring-with-ai.txt:913,搜「Calculate the average of an iterable」)与第 970 段(text/16-ch04-4-debugging-and-refactoring-with-ai.txt:970,搜「raises ValueError」)。 ↩

  26. 出处:「Debugging and Refactoring with AI」第 61–63 段(text/16-ch04-4-debugging-and-refactoring-with-ai.txt:995,搜「normalize」)。 ↩

  27. 出处:「Debugging and Refactoring with AI」第 1072 段(text/16-ch04-4-debugging-and-refactoring-with-ai.txt:1072,搜「week of code studying」)。 ↩ ↩2

  28. 出处:「Debugging and Refactoring with AI」第 1081 段(text/16-ch04-4-debugging-and-refactoring-with-ai.txt:1081,搜「pytest black flake8 mypy」)。 ↩

  29. 出处:「Debugging and Refactoring with AI」第 1186 段(text/16-ch04-4-debugging-and-refactoring-with-ai.txt:1186,搜「Single function doing」)。 ↩

  30. 出处:「Debugging and Refactoring with AI」第 1247 段(text/16-ch04-4-debugging-and-refactoring-with-ai.txt:1247,搜「captured behavior」)。 ↩ ↩2

  31. 出处:「Debugging and Refactoring with AI」第 1381 段(text/16-ch04-4-debugging-and-refactoring-with-ai.txt:1381,搜「reduced duplication」)。 ↩

  32. 出处:「Debugging and Refactoring with AI」第 1399 段(text/16-ch04-4-debugging-and-refactoring-with-ai.txt:1399,搜「read_sales(file_path)」)。 ↩

  33. 出处:「Debugging and Refactoring with AI」第 1547 段(text/16-ch04-4-debugging-and-refactoring-with-ai.txt:1547,搜「Single Responsibility」)。 ↩

  34. 出处:「Debugging and Refactoring with AI」第 1566 段(text/16-ch04-4-debugging-and-refactoring-with-ai.txt:1566,搜「faith-based」)与第 1577 段(text/16-ch04-4-debugging-and-refactoring-with-ai.txt:1577,搜「black .」)。 ↩

  35. 出处:「Debugging and Refactoring with AI」第 1591 段(text/16-ch04-4-debugging-and-refactoring-with-ai.txt:1591,搜「test_read_sales」)。 ↩

  36. 出处:「Debugging and Refactoring with AI」第 1640 段(text/16-ch04-4-debugging-and-refactoring-with-ai.txt:1640,搜「guardrails」)与第 1664 段(text/16-ch04-4-debugging-and-refactoring-with-ai.txt:1664,搜「streaming」)。 ↩

  37. 出处:「Debugging and Refactoring with AI」第 1672 段(text/16-ch04-4-debugging-and-refactoring-with-ai.txt:1672,搜「Premature Generalization」)。 ↩

  38. 出处:「Debugging and Refactoring with AI」第 1689 段(text/16-ch04-4-debugging-and-refactoring-with-ai.txt:1689,搜「Show changes only」)。 ↩

  39. 出处:「Debugging and Refactoring with AI」第 40–42 段(text/16-ch04-4-debugging-and-refactoring-with-ai.txt:645,搜「Quick Setup Checklist」)。 ↩

  40. 出处:「Debugging and Refactoring with AI」第 681 段(text/16-ch04-4-debugging-and-refactoring-with-ai.txt:681,搜「safe rollback」)。原文:「You want a safe rollback if a suggested fix explodes.」 ↩

  41. 出处:「Debugging and Refactoring with AI」Hands-on Exercise(text/16-ch04-4-debugging-and-refactoring-with-ai.txt:1767,搜「KeyError: amounts」)。 ↩

  42. 出处:「Debugging and Refactoring with AI」Hands-on Exercise(text/16-ch04-4-debugging-and-refactoring-with-ai.txt:1773,搜「introduce strings」)。 ↩

  43. 出处:「Debugging and Refactoring with AI」Key Terms(text/16-ch04-4-debugging-and-refactoring-with-ai.txt:1781,搜「trail your program leaves」至 :1802,搜「AI-Assisted Debugging」)。 ↩

  44. 出处:「Understanding Python Code with AI」第 1763 段(text/14-ch02-2-understanding-python-code-with-ai.txt:1763,搜「experiment safely」)。原文:「The beauty of AI-assisted refactoring is not that it does the work for you, but that it lets you experiment safely.」 ↩

  45. 出处:「Debugging and Refactoring with AI」第 1699 段(text/16-ch04-4-debugging-and-refactoring-with-ai.txt:1699,搜「code-only answers」)。 ↩

  46. 出处:「Debugging and Refactoring with AI」第 537 段(text/16-ch04-4-debugging-and-refactoring-with-ai.txt:537,搜「grade_student」)与第 633 段(text/16-ch04-4-debugging-and-refactoring-with-ai.txt:633,搜「logging or raising a custom error」)。 ↩

  47. 出处:「Debugging and Refactoring with AI」第 1689 段(text/16-ch04-4-debugging-and-refactoring-with-ai.txt:1689,搜「Show changes only」)。 ↩

  48. 出处:「Building an AI-Powered Web Application」第 163 段(text/21-ch09-9-building-an-ai-powered-web-application.txt:163,搜「explain what each file does」)。 ↩

  49. 出处:「Debugging and Refactoring with AI」结论第 1705 段(text/16-ch04-4-debugging-and-refactoring-with-ai.txt:1705,搜「trying to tell you」)。原文:「Debugging is not punishment; it is communication.」 ↩ ↩2

  50. 出处:「Debugging and Refactoring with AI」Points to Remember(text/16-ch04-4-debugging-and-refactoring-with-ai.txt:1724,搜「Refactoring is preventive debugging」)。 ↩

  51. 出处:「Debugging and Refactoring with AI」第 1011 段(text/16-ch04-4-debugging-and-refactoring-with-ai.txt:1011,搜「expecting [0,0,0]」)。 ↩

  52. 出处:「Debugging and Refactoring with AI」第 1027 段(text/16-ch04-4-debugging-and-refactoring-with-ai.txt:1027,搜「explain tradeoffs」)。 ↩

  53. 出处:「Debugging and Refactoring with AI」第 1063 段(text/16-ch04-4-debugging-and-refactoring-with-ai.txt:1063,搜「eps behavior」)。 ↩

  54. 出处:「Debugging and Refactoring with AI」第 1576 段(text/16-ch04-4-debugging-and-refactoring-with-ai.txt:1576,搜「Run formatter and linters」至 :1583,搜「mypy report.py」)。 ↩

  55. 变体是我们的推演;六步流程原文见「Debugging and Refactoring with AI」第 688 段(text/16-ch04-4-debugging-and-refactoring-with-ai.txt:688,搜「Reproduce and record」)。 ↩

  56. 出处:「Debugging and Refactoring with AI」第 40–42 段(text/16-ch04-4-debugging-and-refactoring-with-ai.txt:645,搜「Quick Setup Checklist」)。 ↩

  57. (我们的三代问法归纳;问法演化的各代要素分别对应本章第 5、17、49 段的原文做法。) ↩

  58. (我们的五行浓缩;各句原文见 text/16-ch04-4-debugging-and-refactoring-with-ai.txt:87、:272、:713、:800、:1566。) ↩

  59. (我们的分工澄清;第二章重构相关原文见 text/14-ch02-2-understanding-python-code-with-ai.txt:1484,本章流程原文见 text/16-ch04-4-debugging-and-refactoring-with-ai.txt:688。) ↩

  60. 出处:「Debugging and Refactoring with AI」Points to Remember(text/16-ch04-4-debugging-and-refactoring-with-ai.txt:1718,搜「AI can interpret」)。 ↩

On this page