一代人有一代人的COBOL - 编程历史小随笔
一代人有一代人的COBOL#
每一代的计算机从业者,都会遇到属于他们时代的1959年的“COBOL时刻“。
这是一个人类与机器沟通时的抽象层级不断跃升的过程。每一代人士,都会经历那个,“某个职位是否会取代我”的疑惑,“A职位是否会与B职位合并”的害怕,以及“我们的技术未来,到底会向何方”的,仰望天空,但是就如观测宇宙一样,自然却无法立即获得答案的发问。我们当然可以以今时今日的上帝视角告诉1959年的人士,今天的一切,解释他们当时的迷茫,害怕,不满甚至愤怒,正如我们现在被困在今时今日的历史时代中,但是我们自己就在历史所定义的“now and only now”的迷雾中。
从狭义的计算机编程来讲,或者从广义上的人类以各种方式告诉计算机它应该如何干活开始,每一次我们都在以各种方式尝试使得技术变得更加简单,直观,民主化,更加强调人是计算世界的主宰,更加弱化“一个巨大的机器配五个工作人员”的,人围绕机器的想法。从二战结束以后到现在,人类尝试了非常多的方式,所以,这就是我的开题,一代人有一代人的COBOL时刻。
我在雪城大学念书的时候,因为所在学院School of Information Studies与IBM的合作比较密切,所以机缘巧合地接触了Mainframe大机,接触了COBOL,IBM z/OS,ISPF, JCL这一些与个人计算时代完全不同的存储和技术路线。这让我接触到了这一些在时间线上古老的技术,此时此刻,正如彼时彼刻。
COBOL全称 Common Business-Oriented Language,由Grace Hopper在1959年带领团队于1959年公布。(偏题说,1947年,Grace Hopper第一个记录下来了人类有记录的软件运行bug,因为那真的是一个虫子卡在了计算机里造成的故障)。
当时她和她的团队为了解决一个问题:软件工程师的编程非常困难,而审计和商务人士面对如天书一样的代码也不知所以然。我们在这里先没有讨论科学运算中的运用,而是当时在银行业和金融业里早期使用计算机处理业务的场景。
想象一下,你是生活在20世纪60年代初的美国软件工程师,在一家地方大银行公司上班。那个时候,全美国的跨行实时查询余额并现金提款的功能还有十几二十年后才完全支持(无论是柜台与柜员交流还是ATM机),但是你那个时候不知道。你只知道,你的地方银行,已经引入了早期的处理交易的计算机IBM 1401,这个巨大的占用了一整个房间的机器可以把手动工作自动化,你知道它其实是一个非常不好伺候的机器,因为为它编写软件,简直是灾难。你用Autocoder给IBM1401 写代码,但是它的行文可读性太差了,但是你已经知道,比起早期的IBM 650 (更早的型号)上的SOAP (Symbolic Optimal Assembly Program)编写的难度来说,已经好很多了。
下面是一个非常简单的操作,就是一个客户进入到了营业厅,说我要取x美元,这是我的存折。银行柜员取了x美元现金给这位客户,然后记录下来了交易流程,等到每晚上批处理(Batch Processing)。在那个时代,实时在银行机器系统里查询户头余额是不可能的,所以白天只有相信客户存折上记载的是正确的。
接下来就是这个Autocoder程序的一个例子,看看诸位如果在没有行解释的情况下是否能看懂它的一分一毫:
* --------------------------------------------------------------
* IBM 1401 AUTOCODER - CASH WITHDRAWAL
* --------------------------------------------------------------
label op operands remarks
INPUT DA 1,80 ;
ACCT EQU INPUT+10 ;
OLDBAL EQU INPUT+20 ;
AMT EQU INPUT+30 ;
START R ;
SW OLDBAL ;
SW AMT ;
S AMT,OLDBAL ;
BM OVERDFT,OLDBAL ;
;
MCW OLDBAL,050 ;
MCW ACCT,040 ;
P ;
;
B START ;
OVERDFT H START ;
END START
简直就是天书!
你和我生活在今天(2025年),我对于IBM的这个Autocoder编程语言的名字其实挺不满的。你这个语言既不自动(AUTO)也不会自己写代码(CODER),好意思叫自己Autocoder?就如果IBM今天出一个AI Vibe coding的工具(类似于Qoder,Codex,Google Antigravity的工具),你看如果有“IBM Autocoder”这个名字,多直接,如果产品好,上线肯定大卖!但是你猜怎么着?他们在50年代就浪费了这个好名字……真是计算机历史开的天大玩笑呢。
我们继续这个20世纪60年代的故事。然后你听说,远处有一个军方的女性技术大拿,叫做Grace Hopper,先做出了FLOW-MATIC (COOBL的前身,行文已经开始与COBOL相似),接着你听说COBOL至少方便看懂,于是你很兴奋。你从邮局那边想办法邮购了COBOL的使用手册,然后你尝试用同样的逻辑来写。至少你发现,哪怕是审计部门自己不会严谨地写代码,但是他们至少可以懵懵懂懂地读懂这些代码到底在做什么了。
你很高兴,但是同时在想一个问题:曾经我是一个天书翻译员,那么现在呢?如果你的金融专业出身的同事也会编程了怎么办?
于是你似乎又不高兴了。
但是还好,似乎没人在问这个问题,所以你安安稳稳地继续写代码了。IBM System360的确比老旧的650要好多了,你很喜欢它的一些新特性,生活很美好。
000100 IDENTIFICATION DIVISION.
000200 PROGRAM-ID. BANK-WITHDRAWAL.
000300 AUTHOR. TMAX-TANG.
000400 DATE-WRITTEN. NOV 20, 1959.
000500 REMARKS. PROCESS CUSTOMER CASH WITHDRAWAL.
000600 SUBTRACT AMT FROM OLD BALANCE.
000700
000800 ENVIRONMENT DIVISION.
000900 CONFIGURATION SECTION.
001000 SOURCE-COMPUTER. IBM-360.
001100 OBJECT-COMPUTER. IBM-360.
001200
001300 INPUT-OUTPUT SECTION.
001400 FILE-CONTROL.
001500 SELECT IN-FILE ASSIGN TO 'SYS005-UR-2540R-S'.
001600 SELECT OUT-FILE ASSIGN TO 'SYS006-UR-2540P-S'.
001700
001800 DATA DIVISION.
001900 FILE SECTION.
002000 FD IN-FILE
002100 LABEL RECORDS ARE OMITTED
002200 DATA RECORD IS IN-CARD.
002300 01 IN-CARD.
002400 05 IN-ACCT-NUM PICTURE 9(10).
002500 05 IN-OLD-BALANCE PICTURE 9(08)V99.
002600 05 IN-WITHDRAW-AMT PICTURE 9(08)V99.
002700 05 FILLER PICTURE X(44).
002800
002900 FD OUT-FILE
003000 LABEL RECORDS ARE OMITTED
003100 DATA RECORD IS OUT-CARD.
003200 01 OUT-CARD.
003300 05 OUT-ACCT-NUM PICTURE 9(10).
003400 05 OUT-NEW-BALANCE PICTURE 9(08)V99.
003500 05 OUT-MESSAGE PICTURE X(20).
003600 05 FILLER PICTURE X(32).
003700
003800 WORKING-STORAGE SECTION.
003900 77 WS-TEMP-BALANCE PICTURE S9(08)V99.
004000
004100 PROCEDURE DIVISION.
004200 000-START-PROGRAM.
004300 OPEN INPUT IN-FILE
004400 OUTPUT OUT-FILE.
004500
004600 100-READ-LOOP.
004700 READ IN-FILE
004800 AT END GO TO 900-END-JOB.
004900
005000 200-PROCESS-DATA.
005100 MOVE IN-ACCT-NUM TO OUT-ACCT-NUM.
005200
005300 SUBTRACT IN-WITHDRAW-AMT FROM IN-OLD-BALANCE
005400 GIVING WS-TEMP-BALANCE.
005500
005600 IF WS-TEMP-BALANCE IS NEGATIVE THEN
005700 MOVE "INSUFFICIENT FUNDS" TO OUT-MESSAGE
005800 MOVE IN-OLD-BALANCE TO OUT-NEW-BALANCE
005900 ELSE
006000 MOVE "TRANSACTION OK " TO OUT-MESSAGE
006100 MOVE WS-TEMP-BALANCE TO OUT-NEW-BALANCE.
006200
006300 WRITE OUT-CARD.
006400 GO TO 100-READ-LOOP.
006500
006600 900-END-JOB.
006700 CLOSE IN-FILE
006800 OUT-FILE.
006900 STOP RUN.
从COBOL开始,至少行文已经开始有点现代化的意思了,IF ELSE语句已经有了,严格的变量声明定义已经有了。
当然,我们回到2025年,如果用今天的Python来写这个操作,其实可以非常非常简单,如果我们先不讨论浮点精度造成的问题的话……
# 绝对不要在银行系统里这么写!
old_balance = 100.10
withdrawal = 0.04
#咱就是说哪个人真的那么闲,跑到银行里取四分钱……但是这事儿它从业务上是成立的……
new_balance = old_balance - withdrawal
print(new_balance)
# 输出结果竟然是:100.00999999999999 (而不是 100.01)
如果要解决精度问题,那么应该这么写:
from decimal import Decimal, getcontext
# 1. 环境设置 (Environment Division 的现代版)
# 设置全局精度,防止任何溢出或舍入误差
getcontext().prec = 28
def process_transaction(account_id: str, old_balance_str: str, amount_str: str):
"""
处理取款业务
注意:传入金额必须是 String 字符串,而不是 Float,以保证解析精度
"""
# 2. 数据转换 (Data Division 的现代版)
# 类似于 COBOL 的 PICTURE 子句,Decimal 保证了定点数的精确性
current_balance = Decimal(old_balance_str)
withdrawal_amount = Decimal(amount_str)
# 3. 业务逻辑 (Procedure Division 的现代版)
# 逻辑极度简化,没有 GOTO,没有内存搬运
if current_balance >= withdrawal_amount:
current_balance -= withdrawal_amount
status = "TRANSACTION OK"
else:
status = "INSUFFICIENT FUNDS"
# 余额保持不变
# 4. 输出 (Output)
# 返回字典结构,通常会被序列化为 JSON 发送给前端
return {
"account_id": account_id,
"new_balance": str(current_balance), # 转回字符串输出
"status": status
}
# --- 模拟运行 ---
# 假设这是从 API 或 数据库读入的数据
input_data = {
"acc": "1234567890",
"bal": "100.10",
"amt": "0.04"
}
result = process_transaction(input_data["acc"], input_data["bal"], input_data["amt"])
print(f"处理结果: {result}")
你会发现,其实这件事情是变得越来越简单了。COBOL一开始的设计哲学是用英语自然语言来方便程序员向计算机讲述他们到底想让计算机干什么,这是最早的一个伟大尝试。
当然COBOL还有一些其实现在来看早期就实现的很伟大的特性,比如它原生定义了10进制计算,所以它对于金融业来说是非常完美的,因为现代的浮点运算如果不加以限制,会出现类似于1.999999999 + 0.9999999999 = 3.00000000002 类似于这样的情况,但是COBOL和IBM 银行大机却是原生从软件和硬件底层定义了十六进制和十进制运算。
COBOL也是比较早的“写一次,任何地方运行” (“write once runs everywhere”)的代表。在这之前,如果银行硬件升级或者要做到跨平台,那么之前的写的代码全部不能用。COBOL比较早得解决了这个问题,成为了当时比较少的通用解决方案。当然我们现在肯定知道,所谓的跨平台运行,最后都多少要做一些调整,但是总比从头推倒什么都要重写要好得多。
以今天的眼光来看,COBOL的确有非常非常非常非常多的问题,比如你看我给你们的example里面的例如“002700”是啥?这个东西是行号!虽然在80年代以后COBOL已经不再强制需要行号了,我们就能知道这是一个多古老的操作系统了。它没有原生的API,也就是你无法直接从另外一个程序输入一个值给它(通过API),然后请求它通过API来给你一个返回值。不,它没有原生的API定义!!!它没有严格意义上的面向对象编程,没有严格意义上的Function,只有全局变量,没有局部变量(因为它连事实上的function都没有是吧……),所以很多时候还是会在程序里GOTO满天飞,从维护性来说是很刀耕火种的……
但是你如果是1959年的程序员,你有得选择吗?并不,COBOL已经是最好的,最通用的解决方案了,并且它一路主宰了金融业几十年,现在整个西方世界的银行交易,其实仍然事实上基于这个古老的编程语言。
那么我们今天的takeaway是什么呢?COBOL的能力在于,它第一次大范围地打开了,“原来程序还能这么写”的大门。编程语言之外的各种各样新特性,其实在历史上都有相近的概念出现,比如IBM早期的CICS,很类似于今天的微服务,分布式服务,甚至有了一点虚拟化的雏形。每一个时代,都在想办法解决这个时代的问题,不用太先进到百年以后都无需改动,但是至少不要恶心未来的孩子们,相信后人的智慧的同时,也得给后人留好足够的接口和道路。
在这后来,也出现过各种各样的创新。由于计算机的计算和存储能力突飞猛进,所以各种编程语言层出不穷。曾经,是内存地址是要手动从硬件底层控制的,而C和相似语言的出现,使得内存控制变得相对来说比较自动化了。相对来说,大约一直到80年代,人们对于C的诟病是,“它占用了太多资源”,“它太慢了”,但是我们现在都知道,C语言是一个运行速度效率极高的语言(在一些特定事情上,比Python的执行效率高百倍不止)。80年代美国风靡的家庭计算机Commodore 64,由于C语言的编译器在这台机器上极度缓慢(缓慢到得需要通过各种方式才能勉强运行),所以当时如果不想用汇编的话,只能用BASIC语言,这是一个功能并不很全面的语言,当时主要是作为教学和轻量级计算用途。
说“C语言太慢了”,这句话在今天是要被爆锤的。你可以说任何现代编程语言慢,就是不能说C慢。
所以说,Prompt Engineering 是不是就是我们这一代的 COBOL 呢?
如果我们把自己代入回 1959 年,看着那个试图让“审计人员都能读懂代码”的 COBOL 时代,我们心里会怎么想?是欢呼技术的民主化,还是恐惧着:“如果连不懂技术的审计都能写程序了,那我们软件工程师是不是要失业了?”
这种恐惧和迷茫,真的是一代人有一代人的轮回。你我现在都无法给出确切的答案。
历史往往就是这样,它让身处漩涡中心的人,在那个“当下”困惑、打转、探索与重建,然后在混沌中转出一个被后世称为“常识”的东西。
我们无法跳出 2025 年的历史漩涡,正如我们无法预知未来。
但正如当年 Syracuse 漫长冬日里的雪一样,新的技术终将覆盖旧的痕迹,只有地基下的原理,始终在那里,静默无声。