从“告诉机器怎么做”到“告诉AI做什么”:编程抽象简史
编程的历史,讲到底,是一段人类不断把“如何计算”从自己脑子里搬出去的历史。每一次新的编程语言和编程范式出现,都不只是为了让代码看起来更优雅,或者让程序员拥有新的炫技工具,而是在试图解决同一个问题:如何让人少跟机器较劲,把更多精力放到问题本身。从早期盯着硬件、手动输入指令,到今天用自然语言描述意图,让 AI 生成、修改甚至维护代码,这条路上几乎每隔二三十年,就会发生一次关键的抽象升级。
回头看会发现,编程的发展并不是简单地从一种语言替代另一种语言,而更像是一层层向上搭建抽象。机器依然在那里,指令依然在那里,代码最终仍然要被执行,但程序员需要直接面对的东西越来越少。过去,人必须关心寄存器;后来,只需要关心变量和函数;再后来,开始关心对象、模块、状态和系统边界;而今天,越来越多的工程师开始直接讨论需求、规格、约束和验收标准。人类与计算机之间的距离越来越远,人类与问题本身之间的距离却越来越近。
机器码与汇编:命令式编程的起点
最早的计算机只认二进制。程序员必须精确写出每一条机器指令和内存地址,稍有不慎,整个程序就可能崩溃,而且换一个型号的机器,原来的程序往往几乎需要全部重写。后来,汇编语言用助记符代替了那串难以阅读的 0 和 1,把类似“10110000”这样的机器指令写成“MOV”,总算让人类能够看懂一点自己到底在告诉机器什么。但汇编依然紧贴硬件,程序员脑子里仍然必须装着寄存器、内存地址、指令集和执行顺序这些概念。
这个阶段的编程,本质上是在直接命令机器:先做这个,再做那个,读取这里的数据,然后跳转到那里执行。程序员不仅需要理解问题本身,还必须理解机器究竟如何工作。所谓“写程序”,很大程度上就是把人的意图翻译成机器能够逐条执行的命令。人类负责几乎所有细节,机器只负责忠实执行,没有抽象,也没有体贴,更没有“你大概是这个意思吧”的智能。
高级语言诞生:两条道路开始分岔
1957 年,FORTRAN 出现,事情开始发生变化。它是第一批获得广泛成功的高级编译型语言之一,语法更接近数学公式,写科学计算的人终于不必时时刻刻关注底层硬件,而可以直接表达计算逻辑。程序员开始能够告诉计算机“我要计算这个公式”,而不再需要亲自安排每一个寄存器和指令。这是一次巨大的抽象跃迁:机器仍然在执行机器码,但人已经不需要直接面对机器码了。
几乎与此同时,另一条完全不同的道路也开始出现。1958 年,LISP 诞生,它的思想根植于更早的 λ 演算,把计算理解为函数之间的套用、组合和变换,而不是简单执行一连串命令。LISP 带来了递归、高阶函数、垃圾回收以及“代码即数据”等今天看起来依然十分现代的思想。它关心的不是“下一步机器应该执行什么”,而是“一个表达式如何被计算,一个函数如何作用于数据”。
于是,编程世界开始形成两条极其重要的路线。一条走向过程式和命令式编程,核心是明确描述程序应该按照什么顺序执行;另一条走向函数式编程,更关心数据如何经过一系列规则发生变换。前者强调过程,后者强调关系;前者更接近人类组织行动的方式,后者则更接近数学描述计算的方式。这两条路线此后几十年不断竞争、融合、互相借鉴,最终几乎共同塑造了整个现代编程世界。
结构化编程:先把程序里的“乱跳”管住
到了 1960 年代,程序开始越来越大,问题也随之出现。早期程序大量依赖 goto 语句,控制流可以从一个位置随时跳到另一个位置,条件不满足又跳回来,多个逻辑分支交错之后,程序逐渐变成一团难以理解的网。后来人们给这种代码起了一个非常形象的名字:“意大利面条式代码”,因为程序逻辑像一盘意大利面,线全缠在一起,你很难知道从哪里开始,也很难预测最终会走向哪里。
于是,结构化编程出现了。它的核心主张其实并不复杂:程序应该尽可能建立在清晰、有限的控制结构之上,例如顺序、分支和循环,而不是允许程序员随意跳来跳去。结构化编程并没有让计算机变得更强,却让程序变得更容易理解、验证和维护。它第一次明确地告诉程序员:代码不仅要让机器能够执行,也必须让人类能够读懂。
Pascal 以及后来的 C 等语言,都深受结构化编程思想影响。尤其是 C,它处在一个非常特殊的位置:既足够接近机器,又提供了足够的抽象能力,可以组织相对复杂的软件系统。此后几十年,操作系统、编译器、数据库以及各种基础软件都深受 C 的影响,它也成为现代软件世界最重要的底层语言之一。
与此同时,还有一件当时看起来并不起眼的事情正在发生。1960 年代初,Simula 引入了类和对象等概念,开始探索一种新的程序组织方式:能不能把数据,以及操作这些数据的行为,组织在一起?当时很少有人意识到,这个看似边缘的思想,未来会掀起一场持续几十年的编程革命。
面向对象:用对象组织复杂世界
面向对象真正成为主流,要等到 1980 年代和 1990 年代。原因很简单:软件开始变得太大了。几十万行代码、上百万行代码,以及数百甚至上千名工程师共同维护一个系统,单纯依靠过程式编程开始越来越难以控制复杂度。函数和数据散落在各处,模块之间彼此调用,状态可能被不同地方随意修改,一个人改了某个全局变量,另一个完全无关的模块可能几天之后才莫名其妙地崩溃。
面向对象给出的解决方案是:用对象来组织复杂系统。对象拥有自己的数据,也负责操作这些数据的行为;内部细节尽量隐藏,对外通过明确的接口协作。Smalltalk 是最早进行系统性实践的语言之一,它甚至提出了“一切皆对象”这样极具感染力的理念。后来,C++ 把面向对象带进了 C 的世界,而 Java 则凭借“一次编写,到处运行”以及成熟的生态,把面向对象推成了工业软件开发的重要默认范式。
OOP 真正解决的核心问题,从来不是“怎么写代码更快”,而是“如何让一大群人共同维护一个庞大的系统,而不至于互相踩死”。它本质上是一种复杂性管理工具,通过封装、继承、接口和模块边界,把一个巨大的软件系统切分成相对独立的部分。程序员开始不再只思考“下一步执行什么”,而开始思考“这个系统应该由哪些实体组成,它们之间应该如何协作”。
函数式编程:另一条路线在学术圈慢慢成熟
与此同时,函数式编程并没有消失,它只是长期待在学术界和少数专业领域中,慢慢打磨自己的思想体系。1973 年,ML 将函数式编程与强大的静态类型系统结合;Scheme 在 1970 年代进一步简化和提炼了 λ 演算相关思想,使词法作用域、高阶函数和尾调用等概念得到更加清晰的表达;1990 年,Haskell 出现,把纯函数、不可变数据和惰性求值推向了近乎极致。
函数式编程有一个非常核心的信念:尽量减少可变状态和副作用。理想情况下,同样的输入永远得到同样的输出,一个函数不会偷偷修改外部世界,也不会依赖难以追踪的隐藏状态。这样做的好处是程序更容易推理,因为你不需要时时担心某个地方是否在背后改变了数据。尤其在并发环境下,如果多个任务都尽量避免修改共享状态,那么大量复杂的问题甚至可以直接消失。
函数式编程最有意思的地方在于,它并不总是试图通过增加更多规则来解决复杂性。很多时候,它选择的是另一条路:减少状态,减少副作用,减少可以发生意外的地方。然而在很长一段时间里,函数式编程仍然被认为“太难”“太抽象”“太学院派”,似乎更适合数学家和编程语言研究者,而不适合承担大型工业项目。事实证明,这种判断后来并不完全正确。
多范式融合:函数式思想开始反向渗透
真正的转折发生在 2000 年代之后。硬件世界变了,过去提高计算性能主要依靠不断提升 CPU 主频,但单核性能提升越来越困难,多核处理器逐渐成为主流。与此同时,互联网服务开始面对海量并发请求,分布式系统和云计算逐渐成为基础设施。人们突然发现,函数式编程中那些曾经被认为“过于学院派”的思想,在这个新时代里竟然极其有用。
不可变数据、纯函数、减少共享状态,这些理念天然适合并发和分布式环境。于是,主流语言开始悄悄吸收函数式编程的特性:Java 8 引入 Lambda 和 Stream;JavaScript、Python、C#、Swift 等语言大量支持高阶函数、闭包和函数式风格;Rust 则通过所有权、借用和生命周期模型,试图在系统级编程中更严格地管理状态与内存安全。
到了这个阶段,纯正的 OOP 或纯正的 FP 都已经越来越少见。真正成熟的工业语言大多是多范式混合体:用对象或模块划分系统边界,用函数处理数据变换,用不可变性减少副作用,用类型系统提前发现错误。编程范式开始不再像宗教,而更像一个工具箱。重要的已经不是“你到底属于哪一派”,而是面对具体问题时,知道什么工具最适合解决它。
走到这里,编程已经完成了三次重要的抽象跃迁:第一次,是从机器指令到高级语言,程序员不再直接管理每一个寄存器和二进制指令;第二次,是从单纯的过程组织走向更复杂的模块、对象和系统结构;第三次,则是从可变状态主导,逐渐转向吸收函数式编程中关于不可变性、纯函数和类型安全的思想。每一次跃迁,都把一部分原本必须由人脑承担的细节交给语言、编译器或运行时去处理,让人类能够把注意力移向更高层的问题。
2025:面向 AI 编程成为新的主线
第四次跃迁,不再主要发生在语法层面,而是发生在一个更根本的问题上:到底是谁来写代码?2021 年到 2023 年,GitHub Copilot 和 ChatGPT 已经让“AI 帮你写几行代码”逐渐成为日常,但那个阶段的 AI 更像副驾驶。人仍然是那个真正驾驶汽车的人:你负责写代码,AI 帮你补全;你遇到问题,AI 帮你解释;你负责整个开发过程,AI 只是提高效率。
真正的变化出现在 2025 年前后。2025 年初,Andrej Karpathy 提出了一个迅速传播开来的词:vibe coding。它描述了一种完全不同的编程体验:你不再从函数开始,不再从 API 开始,甚至暂时不从代码开始,而是直接告诉 AI 你想做什么,让模型生成实现,然后运行、观察、调整,甚至在某些场景里暂时不去关心代码本身。这个词迅速火遍全球,并在同一年被 Collins Dictionary 选为年度词汇。
但真正重要的并不是一个新词,而是工具能力开始跨过某个临界点。模型不再只能“帮你写一个函数”,而开始能够读取整个代码仓库、理解多个文件之间的关系、修改多个模块、运行终端命令、安装依赖、执行测试、修复错误并持续迭代。Cursor、Claude Code、Devin、Replit Agent 等工具,把“对话写代码”进一步推进到了“让代理去完成工作”。
这是一种本质上的变化。过去,你必须亲自把想法一步步翻译成代码;现在,你越来越可以把问题描述给一个能够理解代码、调用工具并执行操作的 Agent,然后让它完成相当大比例的实现工作。程序员开始第一次真正面对一个新的分工关系:人不再负责所有细节,AI 也不只是一个自动补全工具,而开始成为能够执行任务的数字劳动力。
从写代码,到管理意图
当然,很快人们也发现,纯粹的 vibe coding 非常适合快速验证想法,却不一定适合直接构建复杂的生产系统。AI 可以非常快地生成代码,也可以非常快地生成一座漂亮的技术债垃圾场。速度从来不是唯一的问题,当代码库越来越复杂、团队越来越庞大、系统需要长期维护时,架构、边界、测试、安全和可维护性依然不会因为 AI 的出现而自动解决。
于是,实践开始逐渐转向一种更有纪律的方式:人负责定义问题、编写规格、设计约束、明确架构、提供上下文、设定验收标准并审查结果;AI 则负责具体实现、样板代码、代码搜索、重构、调试和大量重复性工作。这种模式可以被理解为 AI-native programming,也可以更直白地称为“面向 AI 编程”或“规格驱动的 AI 开发”。
它并不是“以后不用懂代码了”,恰恰相反,它意味着工程师的角色正在发生迁移。过去,你的大量时间花在逐行实现、记忆 API、编写样板代码、查找语法错误和手动重构;未来,你越来越多的时间可能会花在把模糊的想法变成清晰的规格,把意图转化为可执行的约束,设计 AI 所需要的上下文,审查 AI 的计划和改动,并建立可靠的验证机制。
过去,一个优秀工程师的重要能力是“把代码写出来”;未来,一个优秀工程师越来越重要的能力,可能是“让正确的代码被可靠地生产出来”。这两者看似相近,实际上差别很大。前者关注实现本身,后者关注整个生产过程;前者强调个人编码能力,后者更强调问题定义、系统设计、上下文组织和结果验证。
这其实是历史上的第四次抽象升级
如果把整个编程历史连起来,会发现一条非常清晰的主线。汇编时代,人管理寄存器;高级语言时代,人管理算法和程序结构;OOP 与 FP 逐渐成熟之后,人开始管理系统模型、模块边界和状态;而到了 AI 时代,人开始越来越多地管理意图、约束与验证。
机器接管的东西越来越多。过去是编译器替人生成机器指令,后来是运行时、垃圾回收、框架和类型系统替人处理各种基础细节,现在则进一步变成模型加 Agent,开始接管大量“怎么写”的工作。每一次技术升级都不是让人类变得不重要,而是迫使人类把注意力转移到更高层的问题上。
于是,人必须更牢牢地抓住那些真正困难的问题:写对了吗?为什么要写?需求本身是否合理?架构能否长期演化?安全边界在哪里?系统出了问题谁负责?这个功能是否真的值得存在?这些问题,比“这个函数怎么写”困难得多,也更接近工程本身。
为什么 OOP 和 FP 并没有过时
值得注意的是,面向 AI 编程并不意味着 OOP 和 FP 失去意义。恰恰相反,它们可能变得更加重要。AI 生成的代码同样需要活在良好的结构里,仍然需要清晰的模块边界、合理的抽象、类型约束、不可变数据、纯函数、自动化测试以及明确的接口。没有这些结构,AI 并不会自动消灭混乱,只会以更快的速度生产更多混乱。
过去,那些看起来有些繁琐的工程原则,是为了防止人类程序员把代码库写成一锅粥;现在,它们又有了新的使命:防止 AI 以十倍甚至百倍的速度,把代码库写成一锅更大的粥。好的架构不会因为 AI 而消失,反而会成为 AI 能否真正参与大型工程的基础条件。没有边界和约束,Agent 就没有稳定的行动空间;没有测试和验证,AI 的“完成”也很难证明是真的完成。
因此,AI 时代真正淘汰的,可能不是工程原则,而是那些只能依赖人工逐行堆砌代码、缺乏系统化约束和验证机制的开发方式。未来的软件工程,很可能不是更少工程,而是更高层次的工程。
一条贯穿八十年的线
回头看这八十多年,编程一直在做同一件事:把人脑里“如何计算”的负担不断向外转移。编译器搬走了机器指令,结构化编程减少了控制流的混乱,模块和对象组织了复杂系统,函数式思想减少了部分可变状态,运行时和框架搬走了大量基础设施细节,而今天的 AI Agent,则正在进一步搬走样板实现、机械重构、初版探索、重复调试以及大量代码搜索与修改工作。
被搬走的,并不是工程师的价值,而是那些不应该由人类反复支付的认知税。真正留下来的,反而是工程里最困难的部分:问题定义、系统设计、架构判断、质量验证、安全边界、长期演化与责任承担。这些事情,从来都比写代码本身更难。
八十年前,人必须告诉机器每一个比特;后来,人告诉机器应该按照什么算法计算;再后来,人开始告诉机器系统应该由哪些对象和模块组成;今天,人开始告诉 AI:问题是什么,边界在哪里,哪些事情绝对不能做,以及怎样才算真正完成。
语言还在,范式还在,代码也不会消失。但代码在整个工程中的位置,正在发生变化。它越来越像一种由意图、规格、约束和工具共同“编译”出来的中间产物。未来最优秀的工程师,未必是写代码最快的人,更可能是那个最能看清问题、定义问题、拆解问题、描述意图、设计约束、构建上下文并验证结果的人。
因为当机器越来越擅长回答“怎么做”,人类真正稀缺的能力,将越来越集中在另一个问题上:到底应该做什么,以及怎样判断它是否真的做对了。
从命令机器,到描述问题;从编写实现,到治理意图;从“告诉计算机每一步怎么做”,到“定义什么才算正确”。八十多年的编程史,最终似乎绕回了一个非常古老的问题:
当机器越来越会思考,人类究竟应该思考什么?