跳转到内容

Go To Statement Considered Harmful — 控制流程的清晰优先级

待复核

《Go To Statement Considered Harmful》是 Dijkstra 1968 年写给 CACM 的一封读者来信(letter,约两页)。核心不是「语法丑」,而是:无约束 goto 让源代码的静态位置与运行时执行历史错位,人脑无法再用「读到第 N 行」唯一推断程序状态

日常类比:导航若每 30 米随机重定向,你就不知道「现在该对应哪一步」。goto 像给状态机开一堆隐藏出入口——能跑,但阅读者很难在脑内复原过程。

Dijkstra 不是要废掉一切跳转,而是警告:在高层语言里,滥用 goto 会让进程/程序状态不透明,维护与验证成本陡增。对理解和证明,它是坏的默认选择。

理解这封 letter,能直接回答几件工程里的事:

  • 为什么长函数读起来像「线索丢失」——同一行可能对应多种到达路径
  • 为什么「先这样写很快,以后没人敢改」——状态不可唯一推断
  • 为什么 if/while/for 比散落标签更易测试——结构化后前置条件更稳定
  • 为什么「能运行」不等于「可维护」——可推理性才是长期成本的关键

goto 的危险在于人脑模型与执行状态模型脱节,而不只是关键字难看。

  1. 可预测的程序状态顺序
    类比:流程图箭头大体从上到下,读者可预测;随机跳转打碎「我现在在第几步」。
    if/while/for 把状态变化显式化,降低理解门槛。

  2. 以结构代替无约束标签
    类比:楼宇按楼层和入口组织,而不是任意楼梯直达任意层。
    结构化控制限制穿插跳转,bug 更容易被隔离;受限的向前 cleanup 跳转另当别论。

  3. 可验证性胜过灵活性幻觉
    「可运行」不是终点,推理与验证才是。
    不可读控制流让审查、测试和静态分析更贵——这正是 letter 想改的评价坐标。

案例 1:把标签循环改成循环结构

Section titled “案例 1:把标签循环改成循环结构”
for (int i = 0; i < n; i++) {
if (scores[i] < 0) {
continue;
}
total += scores[i];
}

逐部分解释:

  • for 和条件分支清楚表达「每轮状态如何前进」。
  • 相比用 goto skip 跳到下一个循环,continue 对阅读者的认知模型更友好。
  • 代码审查时能快速判断循环边界和错误路径。
def safe_divide(a, b):
if b == 0:
return None
if a is None or b is None:
return None
return a / b

逐部分解释:

  • 错误条件先处理,主路径集中,像把「异常路径」先剥离。
  • 比起中间跳到 error_label 再汇合,读者对异常情况的理解更直接。
  • 逻辑测试时覆盖路径更完整。
const state = { READY: 0, RUN: 1, DONE: 2 };
let s = state.READY;
if (s === state.READY) {
s = state.RUN;
}
if (s === state.RUN) {
// do work
s = state.DONE;
}

逐部分解释:

  • 状态机改成显式状态值,让「现在在哪一步」可见。
  • goto labelX 相比,执行路径与业务语义绑定更紧。
  • 对接测试时,可以把每一步写成独立断言。
  1. 在 C 里教条式禁用一切 goto:没有 RAII/try-finally 时,向前跳到 cleanup 标签往往比复制粘贴释放代码更安全;乱改成五层嵌套 if 反而更难证。
  2. 把汇编/内核习惯原样搬进 Java/Python:高级语言已有异常、defer、RAII,再手写标签跳转通常是在制造不可测路径。
  3. 把错误处理藏到函数末尾大标签:调用方看不到失败点,排障时要在 200 行函数里来回搜 goto fail
  4. 为了少几行而合并路径:更短不等于更可推断;先保证每个文本位置的前置条件唯一。

适用

  • 有清晰阶段的列表处理、状态机、业务主路径(尤其多人维护)
  • 需要静态分析/代码评审的关键路径;团队规范强调可读性优先
  • 教学上建立「先结构、后跳转」的默认习惯(C/Go/Java/Rust 都适用)

不适用

  • 底层汇编、驱动热路径等确有测量数据的极致优化
  • 受限嵌入式遗留代码,短期改动成本高于收益
  • 一次性脚本、不要求长期维护的拼接逻辑
  • C 内核式资源清理:在约定「只向前跳 cleanup」的团队里,受限 goto 可接受
  • 1968 年 3 月刊出;原题更平淡,编辑 Wirth 改成 “Considered Harmful” 句式。
  • 它成为结构化程序设计运动的核心引用之一,并推动课程共识。
  • Knuth 1974 写长文做平衡,承认若干合理 goto 场景,反对的是教条而非 Dijkstra。
  • 之后编译器与语言更强调可追踪状态路径;句式本身也被后来者反复戏仿。
  1. 可控的执行路径比「能随便跳」更重要——关键是状态可唯一推断。
  2. 隐藏控制流最容易制造长期技术债,尤其在多人协作里成本爆炸。
  3. 结构化语句不是形式主义,而是工程可验证性的基础设施。
  4. 语言不同,禁令强度不同:先看有没有 RAII/异常,再决定 goto 的位置。
  • structured-programming —— goto 争论后主线方法的整理
  • hoare-logic —— 语义层面的程序推理(Hoare 1969)
  • EWD 手稿归档
  • Wikipedia: Goto
  • Knuth 1974 — Structured Programming with go to Statements
  • structured-programming —— 结构化流程的历史和标准化
  • hoare-logic —— 把「程序对不对」变成可写的证明规则
  • compiler-design —— 控制流设计对编译器优化的影响
  • state-machine —— 用状态显式化代替跳转迷雾
  • software-maintenance —— 维护视角下最小化控制复杂度