真正的程序员用 Fortran 编写。
也许他们现在这样做了,在这个充满精简啤酒、手动计算器和“用户友好”软件的颓废时代,但回到过去的美好时光,当时“软件”这个词听起来很有趣,而真正的计算机是由鼓和真空管制成的,真正的程序员用机器代码编写。不是 Fortran。不是老鼠。甚至不是汇编语言。机器码。原始的、朴素的、难以理解的十六进制数字。直接地。
为了避免新一代的程序员在对这段辉煌过去的无知中长大,我觉得有责任尽我所能地描述一个真正的程序员是如何编写代码的。我会叫他梅尔,因为那是他的名字。
我第一次见到梅尔是在我为打字机公司的现已解散的子公司 Royal McBee Computer Corp. 工作时。该公司制造了 LGP-30,一种小型、便宜(按当时的标准)的鼓式存储器计算机,并且刚刚开始制造 RPC-4000,一个大大改进、更大、更好、更快的鼓-内存计算机。核心成本太高,而且无论如何都不会留在这里。(这就是为什么你还没有听说过这家公司或电脑的原因。)
我受雇为这个新奇迹编写 Fortran 编译器,Mel 是我的向导。梅尔不赞成编译器。
“如果一个程序不能重写自己的代码,”他问道,“那有什么好处呢?”
梅尔用十六进制编写了公司拥有的最流行的计算机程序。它在 LGP-30 上运行,并在电脑展上与潜在客户玩二十一点。它的影响总是戏剧性的。LGP-30 的展台每场都座无虚席,IBM 的销售人员站在一起互相交谈。这是否真正出售计算机是我们从未讨论过的问题。
Mel 的工作是为 RPC-4000 重新编写二十一点程序。(端口?那是什么意思?)新计算机有一个一加一寻址方案,其中每条机器指令,除了操作码和所需操作数的地址外,还有第二个地址,指示位置,在转鼓上,找到了下一条指令。用现代的话来说,每条指令后面都有一个 GO TO!把它放在帕斯卡的烟斗里抽。
Mel 喜欢 RPC-4000,因为他可以优化他的代码:也就是说,在鼓上定位指令,这样当一个完成它的工作时,下一个就会到达“读取头”并可以立即执行。有一个程序可以完成这项工作,一个“优化汇编程序”,但梅尔拒绝使用它。
“你永远不知道它会把东西放在哪里”,他解释说,“所以你必须使用单独的常量”。
过了很久我才明白那句话。由于梅尔知道每一个操作码的数值,并分配了自己的鼓地址,所以他写的每条指令也可以被认为是一个数值常数。如果它具有正确的数值,他可以选择较早的“加法”指令,并乘以它。他的代码不容易被别人修改。
我将 Mel 的手动优化程序与优化汇编程序处理的相同代码进行了比较,Mel 总是运行得更快。那是因为“自上而下”的程序设计方法还没有发明出来,梅尔无论如何也不会使用它。他首先编写了程序循环的最里面部分,因此他们将首先选择鼓上的最佳地址位置。优化汇编器不够聪明,无法这样做。
Mel 也从未编写过延时循环,即使在笨拙的 Flexowriter 需要输出字符之间的延迟才能正常工作时也是如此。他只是在鼓上找到指令,这样每一个连续的指令在需要时都刚好经过
读取头;鼓必须再进行一次完整的旋转才能找到下一条指令。他为这个程序创造了一个令人难忘的术语。尽管“最佳”是一个绝对术语,就像“唯一”一样,但将其设为相对已成为常见的口头习惯:“不是很理想”或“不太理想”或“不是很理想”。梅尔将最大时间延迟位置称为“最悲观”。
在他完成二十一点程序并让它运行之后,(“甚至初始化程序都被优化了”,他自豪地说)他收到了来自销售部门的变更请求。该程序使用了一个优雅的(优化的)随机数生成器来洗牌并从“牌组”中发牌,一些推销员觉得这太公平了,因为有时客户会输。他们希望 Mel 修改程序,以便在控制台上设置感应开关时,他们可以改变赔率并让客户获胜。
梅尔犹豫了。他觉得这显然是不诚实的,事实确实如此,并且它影响了他作为程序员的个人诚信,它确实如此,所以他拒绝这样做。首席推销员与梅尔交谈,大老板也如此,在老板的催促下,几位资深程序员也如此。梅尔终于屈服了,写了代码,但他的测试倒退了,当感应开关打开时,程序就会作弊,每次都赢。梅尔对此很高兴,声称他的潜意识是无法控制的道德,并且坚决拒绝修复它。
在 Mel 离开公司从事更环保的工作后,Big Boss 让我查看代码,看看我是否能找到测试并反转它。有点不情愿,我同意去看。追踪 Mel 的代码是一次真正的冒险。
我经常觉得编程是一种艺术形式,它的真正价值只有另一个精通同样神秘艺术的人才能欣赏;由于过程的本质,有一些可爱的宝石和辉煌的政变隐藏在人类的视野和钦佩之下,有时是永远的。你可以通过阅读他的代码来了解他的很多信息,即使是十六进制的。梅尔是,我认为,一个无名的天才。
当我发现一个没有测试的无辜循环时,也许我最大的震惊来了。没有测试。无。常识说它必须是一个闭环,程序将在其中无限循环。然而,程序控制权直接通过了它,并且安全地从另一边传了出去。我花了两个星期才弄明白。
RPC-4000 计算机有一个非常现代化的设备,称为索引寄存器。它允许程序员编写一个在内部使用索引指令的程序循环;每次通过时,索引寄存器中的数字都会添加到该指令的地址,因此它将引用一系列中的下一个数据。他只需要每次都增加索引寄存器。梅尔从未使用过它。
相反,他会将指令拉入机器寄存器,将其地址加一,然后将其存储回来。然后,他将直接从寄存器执行修改后的指令。编写循环是为了考虑到这个额外的执行时间——就在这条指令完成时,下一条指令就在鼓的读磁头下方,准备好了。但是循环中没有测试。
当我注意到索引寄存器位(位于指令字中地址和操作码之间的位)被打开时,重要的线索出现了——但梅尔从未使用过索引寄存器,一直保持为零。当灯亮起时,它几乎使我失明。
他将他正在处理的数据定位在内存顶部附近——指令可以寻址的最大位置——因此,在处理完最后一个数据之后,增加指令地址会使其溢出。进位会将操作码加一,将其更改为指令集中的下一个:跳转指令。果然,下一条程序指令在地址位置 0 中,程序愉快地运行了。
我没有与 Mel 保持联系,所以我不知道他是否曾经屈服于自那些早已过去的日子以来冲刷编程技术的变革洪流。我喜欢认为他没有。无论如何,我印象深刻,以至于我放弃了寻找有问题的测试,并告诉大老板我找不到它。他似乎并不感到惊讶。
当我离开公司时,如果你打开正确的感觉开关,二十一点程序仍然会作弊,我认为应该是这样。我对破解 Real Programmer 的代码感到不舒服。