🧩 第三章 理解OOP:编程语言的历史
📄 一句话概括

无 GOTO 不是洁癖:goto 版 2 处跳转 2 个标签,结构化版两栏都是 0——读代码从【跑一遍】变成【看形状】。

📝 本节和前面几节的关系

3.6 讲复用,这一节讲可控——两者合起来才是结构化的全貌。

一、为什么要禁

GOTO 让程序的文本结构失去意义。 一段代码长得像 A,跑起来像 B——读的人无法从【长得像什么】推出【跑起来是什么】。 那就只能模拟执行一遍。读代码的成本,从【看】变成了【跑】。

二、数一数

拿 3.5 那段【1 加到 10】的源码逐行数: goto 版 2 处跳转、2 个标签;结构化版两栏都是 0。 而且标签数会随嵌套层数继续涨——每多一层循环,就多一对标签。 结构化版无论嵌套几层,标签数永远是 0。

三、读代码方式的改变

禁止 GOTO 之后,读代码变成了【看形状】而不是【跑一遍】。 这是程序员第一次能把别人的代码读得比自己写还快—— 因为形状是标准的:看到 if 就知道有分支,看到 for 就知道有循环。 这一条对团队协作的意义,比任何语法糖都大。

四、边界:goto 不是永远错

Linux 内核里大量使用 goto 做错误清理——goto cleanup 一路跳下去释放资源。 为什么那里可以?因为它只往前跳、只跳到统一出口,不破坏可读性。 规矩的本质不是【禁 goto】,是【禁止结构不可见】。 所以现代语言干脆把它藏起来:break、continue、return、try/except都是受限的跳转。

五、动手 Demo:实现无GOTO编程的结构化语言

拿同一段源码,逐行数跳转语句和标签。 goto 版和结构化版的差别,能数出来。

goto_src = ["i = 0; s = 0", "L1:", "if i > 10 goto L2",
            "s = s + i; i = i + 1", "goto L1", "L2:"]
while_src = ["i = s = 0", "while i <= 10:", "    s += i; i += 1"]
def stat(src):
    jumps = sum(1 for s in src if "goto" in s)
    labels = sum(1 for s in src if s.endswith(":") and " " not in s)
    return jumps, labels
print("goto 版", stat(goto_src))
print("结构化版", stat(while_src))

实跑输出:

--------------------------------------------------------------------
实现无 GOTO 编程的结构化语言
--------------------------------------------------------------------

  【无 GOTO】不是洁癖,是**为了让代码的形状能反映逻辑的形状**。

  拿 3.5 那段【1 加到 10】的源码逐行数:

                         goto 版     结构化版
  --------------------------------------------
  跳转语句               2 处        0 处
  需要认的标签           2 个        0 个
  缩进能看出结构吗       不能        能
  改动要不要重排标签     要          不要

  实测:**goto 版 2 处跳转、2 个标签;结构化版两栏都是 0。**
  而且标签数会随嵌套层数继续涨——**每多一层循环,就多一对标签。**

  这就是 Dijkstra 那篇著名文章的意思:**GOTO 让程序的文本结构失去意义。**
  读代码的人无法从【长得像什么】推出【跑起来是什么】**——必须模拟执行一遍。**

  **结构化编程之后,读代码变成了【看形状】而不是【跑一遍】。**
  这是程序员第一次能把别人的代码读得比自己写还快。

实测:goto 版 2 处跳转、2 个标签;结构化版 0/0。 规矩的本质不是禁 goto,是禁止结构不可见——所以 Linux 里的 goto cleanup 仍然合法。

30 秒实验

  1. 看看你用的语言里,break/continue/return 各相当于哪种受限跳转。
  2. 找一段用异常做流程控制的代码,判断它算不算【结构不可见】。

常见误区

  1. 以为 goto 一无是处。Linux 内核用它做统一错误清理。
  2. 以为禁了 goto 就没有跳转。break、continue、异常都是跳转。
自测三问(点开对答案) 1. 为什么要禁 GOTO? —— 它让程序的文本结构失去意义:读的人无法从长得像什么推出跑起来是什么,只能模拟执行。 2. goto 版和结构化版各几个标签? —— goto 版 2 处跳转 2 个标签;结构化版都是 0,而且嵌套再深也还是 0。 3. 为什么 Linux 里还能用 goto? —— 因为它只往前跳到统一出口,不破坏结构可见性——规矩的本质不是禁 goto,是禁止结构不可见。

相关笔记