在绝对二进制中编程
第一个程序员使用绝对二进制进行编程: 每个指令和每个地址都以原始二进制数字表示。一个单独的指令可能看起来像01100101 00001010——二进制指令代码和内存地址。
拉面代码问题
当出现错误时, 程序员面临一个困境。将新指令插入原位置意味着所有后续指令地址都向后移动一个单位——需要程序员更新整个程序中的每个地址引用。灾难性的。
解决方案: 在插入点之前的指令替换为跳转到空内存。然后在该空位置: 写入被覆盖的指令, 添加新指令, 然后跳回。出现错误时, 使用其他空内存再次应用相同的技巧。
结果: 程序的执行路径跳转到似乎随机位置。汉明将这称为'一锅拉面'。在纸上绘制的控制流路径看起来确实像一锅乱七八糟的拉面。
逃生路线
两种立即改进: 八进制表示法(将二进制数字分组为3个)和十六进制(将4个分组, 使用A-F表示大于9的值)。这些减少了写入错误, 但没有解决根本的地址问题。
符号汇编(如IBM的SAP - 符号汇编程序 - 和SOAP - 符号优化汇编程序, 在IBM 650上)允许程序员使用指令名称(ADD, MOVE)和符号地址标签而不是二进制进行编程。汇编器在输入时将指令翻译为二进制, 自动管理地址分配。
SOAP 还执行了另一种优化: 它将指令安排在旋转鼓上, 以便在完成前一个指令时, 下一个指令恰好到达阅读头 - 最小延迟编码。SOAP 甚至将自己编译成程序B, 并在A上运行B, 以测量自编译如何改进它。
库与可移动代码
汉明注意到可重用软件(数学库)的想法非常早——巴贝奇已经构思了它。问题:绝对地址库要求每个子程序在每次使用时都占用相同的内存位置。当库中的总大小过大时,程序将竞争相同的地址。
解决方案:可重定位的代码。汇编器生成的指令引用内存相对位置——从一个基址地址的偏移量,而不是绝对地址。链接器在加载时解析最终地址。
冯·诺伊曼的未发表报告(广泛流传)描述了必要的编程技巧。首本编程书籍(Wilkes, Wheeler & Gill, EDSAC, 1951)阐述了这些技术。
语言设计分叉
FORTRAN(1957,IBM)和ALGOL(1958,国际委员会)代表了两种设计哲学,产生了截然不同的结果。
FORTRAN
约翰·巴克ους领导了IBM的FORTRAN(FORmula TRANslation)项目。设计目标:让语言易于科学家和工程师使用。FORTRAN接受了与用户自然感受的数学符号:A = B + C * D而不是ADD B, C; STORE T; MULTIPLY T, D; STORE A。
FORTRAN在60多年里生存下来。它仍然在科学计算、流体力学、气候建模和计算物理学等领域活跃使用。汉明认为这种耐久性证明了设计的成功。
ALGOL
ALGOL(ALGOrithmic Language)是由一群逻辑学家和计算机科学家设计的,他们追求数学严谨:一个逻辑上清晰、形式定义的语言。为了指定ALGOL,发明了巴克-纳尔形式(BNF)notation来描述语法。
ALGOL在实践中失败了。尽管它的逻辑优雅以及对后续语言设计的巨大影响(Pascal、C以及几乎所有现代语言都源自ALGOL的语法概念),但ALGOL本身从未广泛部署。汉明的裁决:逻辑设计,人工不可用。
语言的层次结构
汉明描述了一种自然的层次结构,从机器代码到汇编、更高级的语言,再到最终的“问题导向语言”,这个语言与实践者在他们的问题领域如何思考非常接近。每个层次增加了人类可读性,但代价是机器效率降低。
汉明的四个语言设计标准
汉明从FORTRAN与ALGOL的教训中总结出四个标准来衡量一个成功的编程语言:
1. 易于学习 —— 新手可以迅速成为高效的生产者
2. 易于使用 —— 常规任务不需要太多的礼节
3. 易于调试 —— 错误会产生有意义的、可定位的消息
4. 易于使用子程序 —— 重新使用和抽象不需要需要英雄般的努力
他还提出了一种结构观察:人类语言包含约60%的冗余;书面语言约40%。低冗余语言(如APL)产生出专家们觉得优美、初学者们觉得晦涩的简洁代码——并且在单个字符改变含义时,包含不可检测的错误。
这个暗示:一个设计为逻辑优雅的语言,优化了错误的读者。程序员是人类;人类需要冗余来捕捉错误和传达意图。
心理学与逻辑语言设计
Hamming将FORTRAN/ALGOL的对比归因于机构与人类动态,而不是仅仅归因于语言设计。
FORTRAN是为了满足使用它的人类,即专注于数学符号的科学家,而设计为心理学设计。ALGOL是为了形式正确性和理论优雅而设计的。
Hamming所识别的悖论: 人类抵制的逻辑正确的语言失败;人类采用的一种实际设计的语言,即使它在逻辑上更复杂,也会成功,这门语言在大众中几乎没有普及。
他举了APL为例:逻辑优雅,一行表达,具有自己的特殊字符集。专家们喜欢它。普通程序员觉得它难以阅读。一个字符的更改可能会静默地转换程序的含义。APL有一个小的忠实社区和几乎没有主流使用。
人类冗余论:口头语约60%冗余(重复的上下文、澄清的单词、可预测的结构)。书面语约40%冗余。这种冗余服务于错误检测——人类不太可靠,所以语言进化了,以携带足够多的重复信息来捕获和纠正错误。低冗余语言移除了这个安全网。
编译器层次结构
Hamming描述了编译器/解释器层次:程序可以读取一个更高级别的语言并将其翻译成一个更低级别的语言。将这些层次堆叠起来——每个层次都将一个级别翻译成另一个级别。最上面:领域特定语言,领域内专家(生物学、金融、物理)可以自然地书写。最下面:机器代码。每个过渡都是一个编译器或解释器。
预测语言存活
到1993年,汉明已经观察了许多语言的成功与失败。FORTRAN(1957年)生存下来。ALGOL(1958年)失败。COBOL(1959年)在商业计算中生存了几十年。LISP(1958年)在人工智能研究中生存下来。PL/I(1964年)试图统一一切却失败了。
重复的模式
汉明的软件历史章节包含一个重复的结构:
1. 存在一个痛苦的限制(绝对地址、二进制表示、难以维护的代码)
2. 某人发明一个抽象层,隐藏这个限制
3. 抽象使得新的规模成为可能,这创建了新的痛苦限制
4. 重复
二进制 → 八进制/十六进制 → 符号汇编 → FORTRAN → 结构化编程 → 面向对象语言 → 专用语言。每个层次都解决了前一个层次的最严重问题,同时引入了新的问题类型。
绝对地址的问题导致了符号汇编。庞大的汇编程序导致了FORTRAN。庞大的FORTRAN程序导致了结构化编程,然后是面向对象。汉明的讲座结束于这些后续过渡之前,但模式仍然继续。
他对工程师的教训:你们总是在解决前一层抽象暴露的问题。理解您目前所在的层次需要知道为什么下一个层次存在。