绝对二进制编程
第一代程序员用绝对二进制编写:每条指令 & 每个地址都是原始的二进制数字。一条指令可能看起来像 01100101 00001010 — 指令代码 & 内存地址都是二进制的。
面条式代码问题
当发现错误需要插入新指令时,程序员面临困境。原地插入意味着所有后续指令的地址都向后移动一个位置 — 要求程序员更新整个程序中的每一个地址引用。灾难性的。
解决方案:将插入点前的指令替换为跳转到空闲内存。在空闲位置:写入被覆盖的指令,添加新指令,然后跳回。当修正中出现错误时,使用其他空闲内存重复同样的技巧。
结果:程序执行路径跳转到看似随机的位置。Hamming称这为'一罐意大利面'。控制流路径在纸上绘制时,看起来就像缠绕的意大利面。
逃生路线
两个立即改进:八进制表示法(将二进制数字分组,每组3位)和十六进制(每组4位,用A–F表示9以上的值)。这些减少了写入错误,但没有解决根本的地址问题。
符号汇编(例如IBM的SAP — 符号汇编程序 — 和IBM 650上的SOAP — 符号优化汇编程序)允许程序员写指令名称(ADD, MOVE)和符号地址标签,而不是二进制。汇编器在输入时翻译为二进制,自动管理地址分配。
SOAP执行额外的优化:它在旋转磁鼓上安排指令,使得下一条指令到达读取头时,前一条指令恰好完成 — 最小延迟编码。SOAP甚至可以编译自身:程序A作为数据处理后产生B,B运行在A上以测量自编译改进了多少。
库 & 可重定位代码
Hamming指出,可重用软件(数学库)的想法来得很早 — Babbage就曾设想过。问题:绝对地址库要求每个例程每次使用时都占据相同的内存位置。当库的总规模增长时,程序会争夺相同的地址。
解决方案:可重定位代码。汇编器生成的指令引用内存时使用相对地址 — 从基地址的偏移量 — 而不是绝对地址。链接器在加载时解决最终地址。
Von Neumann的未发表报告(广泛流传)描述了必要的编程技巧。第一部已发表的编程书(Wilkes, Wheeler & Gill, EDSAC, 1951)将这些技术编入其中。
编程语言设计的分叉
FORTRAN(1957, IBM)和ALGOL(1958, 国际委员会)代表两种设计哲学,产生了截然不同的结果。
FORTRAN
John Backus领导了IBM的FORTRAN(FORmula TRANslation)项目。设计目标:让科学家 & 工程师容易使用该语言。FORTRAN接受对其用户而言自然的数学记号:A = B + C * D,而不是ADD B, C; STORE T; MULTIPLY T, D; STORE A。
FORTRAN存活了60多年。它在科学计算、流体动力学、气候建模 & 计算物理中仍在使用。Hamming指出这种耐久性是设计成功的证明。
ALGOL
ALGOL(ALGOrithmic Language)由逻辑学家 & 计算机科学家委员会设计,目标是数学严谨:一种逻辑清洁、形式上可定义的语言。描述语法的Backus-Naur形式(BNF)记号就是为了指定ALGOL而发明的。
ALGOL在实践中失败了。尽管它逻辑上优雅 & 对后续语言设计的影响巨大(Pascal、C & 几乎每一种现代语言都源自ALGOL的语法概念),ALGOL本身从未被广泛部署。Hamming的裁决:逻辑上设计,人类上不可用。
语言的层次结构
Hamming描述了从机器码到汇编、高级语言、最终是'面向问题的语言'的自然层次结构,这种语言接近从业者对其问题领域的理解方式。每一级都以牺牲机器效率为代价增加人的可读性。
Hamming的四个编程语言设计标准
Hamming将FORTRAN vs ALGOL的教训提炼为成功编程语言的四个标准:
1. 易于学习 — 初学者可以快速变得富有成效
2. 易于使用 — 常见任务需要最少的仪式
3. 易于调试 — 错误产生有意义的、可定位的消息
4. 易于使用子程序 — 重用 & 抽象不需要英雄般的努力
他补充了一个结构观察:人类语言约有60%的冗余;书面语言约40%。低冗余语言(如APL)产生专家认为美妙、初学者认为不可解读的单行程序 — 当单个字符改变含义时,其中包含无法检测的错误。
暗示:为逻辑优雅设计的语言是为错误的读者优化的。程序员是人类;人类需要冗余来捕捉错误 & 传达意图。
心理学设计 vs 逻辑设计
Hamming回到FORTRAN/ALGOL对比,作为制度 & 人类动力学,而不仅仅是语言设计的教训。
FORTRAN是心理学上设计的 — 为使用它的人类,特别是用数学记号思考的科学家。ALGOL是逻辑上设计的 — 为形式正确性 & 理论优雅。
Hamming识别的悖论:一种逻辑上正确但人类抗拒的语言失败;一种人类采用的务实设计的语言成功,即使它逻辑上更乱。
他引用APL为极端情况:逻辑上优雅、单行可表达、有自己的特殊字符集。专家喜欢它。普通程序员找不到要点。单个字符改变可以悄悄改变程序的含义。APL有一个小的献身社区 & 几乎零的主流使用。
人类冗余论证:口头语言约60%冗余(重复的内容、澄清的词汇、可预测的结构)。书面语言约40%冗余。这种冗余用于错误检测 — 人类不可靠,所以语言进化以承载足够的重复信息来捕捉 & 修正错误。低冗余语言移除了这个安全网。
编译器层次
Hamming描述了编译器/解释器分层:一个程序可以用高级语言读入 & 将其翻译为低级语言。堆叠这些层 — 每一层向下翻译一级。在顶部:一种特定领域的语言,专家在一个领域(生物学、金融、物理)中自然地写。在底部:机器码。每个转换都是一个编译器或解释器。
预测编程语言的生存
到1993年,Hamming看过许多语言的成功 & 失败。FORTRAN(1957)存活了。ALGOL(1958)失败了。COBOL(1959)在商业计算中存活了数十年。LISP(1958)在AI研究中存活了。PL/I(1964)试图统一一切但失败了。
循环出现的模式
Hamming的软件历史章节包含一个循环结构:
1. 存在痛苦的限制(绝对地址、二进制记号、不可维护的代码)
2. 有人发明了一个抽象层来隐藏该限制
3. 抽象层使新的规模成为可能,这创造了新的痛苦限制
4. 重复
二进制 → 八进制/十六进制 → 符号汇编 → FORTRAN → 结构化编程 → 面向对象语言 → 特定领域语言。每一层解决前身最急性的痛点,同时引入一种新的问题类别。
面条式代码问题(绝对地址)导致符号汇编。大型汇编程序导致FORTRAN。大型FORTRAN程序导致结构化编程,然后是面向对象。Hamming的讲座在这些后来的转变之前结束,但模式继续。
他对工程师的教训:你总是在解决前一个抽象所暴露的痛点。理解你当前所在的层需要知道为什么下面的层存在。