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 的危险在于人脑模型与执行状态模型脱节,而不只是关键字难看。
-
可预测的程序状态顺序
类比:流程图箭头大体从上到下,读者可预测;随机跳转打碎「我现在在第几步」。
if/while/for把状态变化显式化,降低理解门槛。 -
以结构代替无约束标签
类比:楼宇按楼层和入口组织,而不是任意楼梯直达任意层。
结构化控制限制穿插跳转,bug 更容易被隔离;受限的向前 cleanup 跳转另当别论。 -
可验证性胜过灵活性幻觉
「可运行」不是终点,推理与验证才是。
不可读控制流让审查、测试和静态分析更贵——这正是 letter 想改的评价坐标。
案例 1:把标签循环改成循环结构
Section titled “案例 1:把标签循环改成循环结构”for (int i = 0; i < n; i++) { if (scores[i] < 0) { continue; } total += scores[i];}逐部分解释:
for和条件分支清楚表达「每轮状态如何前进」。- 相比用
goto skip跳到下一个循环,continue对阅读者的认知模型更友好。 - 代码审查时能快速判断循环边界和错误路径。
案例 2:错误处理改成早返回
Section titled “案例 2:错误处理改成早返回”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再汇合,读者对异常情况的理解更直接。 - 逻辑测试时覆盖路径更完整。
案例 3:用状态枚举替代跳转
Section titled “案例 3:用状态枚举替代跳转”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相比,执行路径与业务语义绑定更紧。 - 对接测试时,可以把每一步写成独立断言。
- 在 C 里教条式禁用一切 goto:没有 RAII/try-finally 时,向前跳到 cleanup 标签往往比复制粘贴释放代码更安全;乱改成五层嵌套 if 反而更难证。
- 把汇编/内核习惯原样搬进 Java/Python:高级语言已有异常、defer、RAII,再手写标签跳转通常是在制造不可测路径。
- 把错误处理藏到函数末尾大标签:调用方看不到失败点,排障时要在 200 行函数里来回搜
goto fail。 - 为了少几行而合并路径:更短不等于更可推断;先保证每个文本位置的前置条件唯一。
适用 vs 不适用场景
Section titled “适用 vs 不适用场景”适用:
- 有清晰阶段的列表处理、状态机、业务主路径(尤其多人维护)
- 需要静态分析/代码评审的关键路径;团队规范强调可读性优先
- 教学上建立「先结构、后跳转」的默认习惯(C/Go/Java/Rust 都适用)
不适用:
- 底层汇编、驱动热路径等确有测量数据的极致优化
- 受限嵌入式遗留代码,短期改动成本高于收益
- 一次性脚本、不要求长期维护的拼接逻辑
- C 内核式资源清理:在约定「只向前跳 cleanup」的团队里,受限 goto 可接受
历史小故事(可跳过)
Section titled “历史小故事(可跳过)”- 1968 年 3 月刊出;原题更平淡,编辑 Wirth 改成 “Considered Harmful” 句式。
- 它成为结构化程序设计运动的核心引用之一,并推动课程共识。
- Knuth 1974 写长文做平衡,承认若干合理 goto 场景,反对的是教条而非 Dijkstra。
- 之后编译器与语言更强调可追踪状态路径;句式本身也被后来者反复戏仿。
- 可控的执行路径比「能随便跳」更重要——关键是状态可唯一推断。
- 隐藏控制流最容易制造长期技术债,尤其在多人协作里成本爆炸。
- 结构化语句不是形式主义,而是工程可验证性的基础设施。
- 语言不同,禁令强度不同:先看有没有 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 —— 维护视角下最小化控制复杂度
- hoare-csp-1978 —— Hoare CSP 1978 — 把并发看成会对话的小程序
- parnas-information-hiding-1972 —— Parnas 信息隐藏 1972 — 模块化设计原则