1. 为什么说“编程尚未被解决”
1.1 一个被反复提起的判断
“编程尚未被解决”这句话,最早是在一次小型技术聚会上听到的。当时一位做了二十多年系统软件的老工程师端着杯子说,我们这行看起来热闹,语言一年出几个,框架三个月换一茬,但真正意义上的“编程问题”其实一点都没解决。他说的不是工具不够多,而是我们至今还在用上世纪七十年代就定下来的那套心智模型,去对付今天规模、复杂度、协作密度都完全不同的软件系统。
这句话乍一听有点耸人听闻,毕竟我们每天写代码、跑测试、上线发布,整个行业运转得好好的。但如果你把视角拉高一点,会发现一个尴尬的事实:软件工程的失败率几十年来几乎没有本质改善。大型项目的延期、超支、返工、重构,依然是常态而不是例外。我们造出了越来越快的机器,却没能造出与之匹配的、让人真正省心的编程方式。
所以这篇文章想聊的,不是某个具体框架或语言的好坏,而是把“编程尚未被解决”当成一个真问题来拆解:它到底指什么没被解决,为什么没被解决,现有的各种尝试走到了哪一步,以及作为一个一线开发者,我们能在多大程度上绕开这些坑。适合所有写过一段时间代码、开始对“为什么这么累”产生疑问的人读。
1.2 先厘清:什么叫“被解决”
要讨论一个问题有没有被解决,得先定义“解决”的标准。数学上,一个猜想被证明或证伪,就叫解决了。医学上,一种疾病有了稳定、可普及、副作用可控的疗法,基本算解决了。那编程呢?
我倾向于用三个维度来衡量:
- 表达维度:人想把意图告诉机器,这个过程是否足够自然、低损耗、少歧义。
- 正确性维度:写出来的东西是否符合预期,能否被可靠地验证,而不是靠人肉盯。
- 演化维度:需求变了、规模涨了、人换了,代码能不能低成本地跟着变。
按这三个维度看,编程离“解决”差得远。表达上我们还在和语法、类型、边界条件搏斗;正确性上测试覆盖率是个永远填不满的坑;演化上“祖传代码”几乎成了行业黑话。这不是某个团队不努力,而是整个范式层面的欠账。
1.3 这个判断为什么值得一线开发者认真对待
有人会说,这些是学术界和语言设计者该操心的事,我一个写业务代码的,关心这个干嘛。我的体会恰恰相反:越是理解“没被解决”的根源,越能在日常里做出更聪明的取舍。
举个例子,知道了“表达维度”天然有损耗,你就不会迷信某种“银弹语言”能一劳永逸;知道了“正确性”无法靠单一手段保证,你就会老老实实做分层验证而不是只堆单元测试;知道了“演化”是必然,你在设计接口时就会多留一层抽象,而不是把逻辑焊死。这些判断,都来自对问题本质的理解,而不是对某个工具的盲从。
2. 编程到底卡在哪几个地方
2.1 表达层:人机之间的语义鸿沟
编程的本质,是把人脑子里的意图,翻译成机器能执行的指令。这个翻译过程,损耗大得惊人。你脑子里想的是“用户下单后扣库存并通知仓库”,落到代码里可能变成几十行涉及事务、锁、异常处理、消息队列的逻辑。中间任何一环理解偏差,结果就错了。
更麻烦的是,自然语言本身就有歧义,而编程语言要求绝对精确。这种精确性要求,逼着人用机器的思维方式去思考,而不是用人的方式。我们花了大量精力去学习“怎么像计算机一样想问题”,这本身就是一种妥协。真正理想的编程,应该是人用接近自然的方式描述意图,机器负责处理精确性。这个方向上有过不少尝试,但离普及还很远。
2.2 正确性层:验证永远追不上复杂度
写代码容易,证明代码对,难。形式化验证在学术上很成熟,工业界却极少用,原因很简单:成本太高,收益不确定。于是我们退而求其次,用测试来近似验证。但测试只能证明“存在错误”,不能证明“没有错误”,这是它的先天局限。
随着系统复杂度上升,状态空间爆炸,测试能覆盖的比例越来越小。一个分布式系统,节点间的时序组合多到无法穷举,你测过的路径只是沧海一粟。这就是为什么很多 bug 只在生产环境、只在特定并发下才冒出来。正确性验证的手段,和系统复杂度之间,存在一个越来越大的缺口。
2.3 演化层:代码是写给人看的,顺便给机器执行
有句话流传很广:代码主要是写给人看的,只是顺便让机器执行。这话点出了演化的核心矛盾——代码要服务于两个读者,机器和人,而这两者的需求经常冲突。机器要的是明确、无冗余、可优化;人要的是可读、可改、可理解。
需求一变,代码就得改。改的时候,你得先读懂原来的逻辑,而读懂的成本往往远高于重写。于是“重写”成了很多团队的默认选择,但重写又会引入新的 bug 和新的技术债。这个循环,几乎每个维护过老系统的人都经历过。演化成本高,本质上是表达层和正确性层问题的叠加。
2.4 协作层:规模一上来,一切都不一样
一个人写代码,和一百个人写代码,是两个物种。人一多,沟通成本、接口约定、风格统一、知识传递,全都成了问题。布鲁克斯在《人月神话》里说的“沟通成本随人数平方增长”,到今天依然成立。
协作层的问题,很多时候不是技术问题,而是信息问题。A 写的模块,B 不知道它的隐含假设;C 改了一个函数,D 依赖的某个行为悄悄变了。类型系统、接口契约、文档,都是缓解手段,但没有一个能彻底解决“意图传递失真”。规模越大,失真越严重。
3. 那些试图“解决编程”的尝试,走到了哪一步
3.1 从汇编到高级语言:第一次大跃迁
编程史上第一次真正的范式跃迁,是从机器码、汇编,到高级语言。Fortran、C、Lisp 这些语言的出现,让程序员不用再关心寄存器分配和指令调度,可以用更接近数学和自然语言的方式表达意图。这是一次巨大的表达层解放。
但高级语言解决的是“表达效率”,没有解决“正确性”和“演化”。C 语言给了你指针和内存管理,也给了你段错误和内存泄漏。Lisp 给了你极高的表达力,也给了你难以维护的宏地狱。每一次抽象升级,都伴随着新的复杂度被引入。
3.2 类型系统与形式化:把正确性往前推
类型系统是过去几十年最成功的正确性工具之一。它把一部分错误从运行时提前到编译时,让机器帮你检查。从简单类型到泛型、到依赖类型,类型系统能表达和检查的约束越来越强。
形式化方法走得更远,用数学证明来保证程序正确。但它的门槛太高,需要专门的训练,且证明本身可能比程序还长。工业界只在极少数安全攸关的场景用,比如航天、医疗设备。对绝大多数业务系统来说,形式化验证的投入产出比不划算。所以这条路虽然理论上最彻底,实践上却难以普及。
3.3 低代码与可视化:降低门槛,但没触及本质
低代码平台和可视化编程,是另一条思路:既然写代码难,那就让不写代码的人也能“编程”。拖拖拽拽,配置一下,系统就出来了。对特定场景,这确实有效,比如表单、流程、报表。
但低代码解决的是“入门门槛”,不是“编程本质”。一旦需求超出平台预设的能力边界,你还是得写代码,而且是在一个受限、不透明的环境里写,反而更痛苦。可视化编程同样如此,图一复杂,连线比代码还难读。它们降低了表达层的部分损耗,却没有触及正确性和演化的根本问题。
3.4 AI 辅助编程:改变的是速度,不是范式
最近几年,AI 辅助编程工具火得一塌糊涂。自动补全、代码生成、bug 检测,确实能省不少事。我自己的体验是,写样板代码、查 API 用法、生成测试用例,效率提升明显。
但要清醒地看到,AI 改变的是“写代码的速度”,不是“编程的范式”。它帮你更快地写出你本来就会写的东西,但系统该怎么设计、接口该怎么划分、正确性该怎么保证,这些核心问题它并没有替你解决。而且 AI 生成的代码同样需要人来审查和验证,正确性层的负担并没有消失,只是换了个位置。把它当成一个强大的助手可以,指望它“解决编程”,目前还看不到。
4. 一线开发者能做的现实选择
4.1 接受“没有银弹”,做分层防御
既然编程没被解决,那现实策略就是:不追求单一手段的完美,而是做分层防御。表达层用清晰的语言和命名降低歧义;正确性层用类型、测试、静态分析、代码审查多道关卡;演化层用良好的模块边界和接口设计控制变更影响;协作层用文档、契约、自动化流程减少信息失真。
这套组合拳不优雅,也不彻底,但它是目前最务实的方案。我见过太多团队迷信某一种手段,比如“我们测试覆盖率高就没问题”,结果线上照样出事。分层防御的核心思想是:假设每一层都会漏,用多层来兜底。
4.2 把复杂度当成头号敌人
复杂度是编程所有问题的共同根源。表达复杂、验证复杂、演化复杂、协作复杂,本质都是复杂度失控。所以一线开发者最该培养的能力,是识别和控制复杂度。
具体做法包括:优先选择简单直接的方案,而不是炫技的方案;把大问题拆成小问题,让每个部分都能被单独理解和验证;控制抽象层数,每多一层抽象就多一份理解成本;警惕“为了未来可能的扩展”而过度设计。我个人的经验是,能删的代码比能加的代码更有价值。
4.3 在约束下做取舍,而不是追求最优
现实项目永远在时间、质量、成本之间权衡。理解了编程没被解决,你就不会执着于“完美方案”,而是学会在约束下做取舍。哪些地方可以妥协,哪些地方必须守住,这个判断力比任何技术都重要。
比如,一个内部工具,正确性要求没那么高,快速迭代优先;一个支付核心,正确性压倒一切,宁可慢也要稳。同一个团队,不同模块的策略可以完全不同。这种“因地制宜”的判断,来自对问题本质的理解,而不是对教条的遵守。
5. 常见误区与实操避坑
5.1 误区一:换个语言/框架就能解决问题
这是最常见的幻觉。新语言、新框架往往带来短期的兴奋感,但用不了多久,老问题会以新形式重现。因为问题不在工具,在编程这件事本身的难度。换工具能缓解局部痛点,但解决不了根本矛盾。我的建议是,把学习新工具当成扩展工具箱,而不是寻找救世主。
5.2 误区二:追求 100% 的正确性
有些团队对 bug 零容忍,追求 100% 测试覆盖、零告警。这种态度值得尊敬,但往往不可持续。正确性是有成本的,追求极致正确性的代价可能是交付速度归零。更现实的做法是,根据模块的重要程度设定不同的正确性标准,把资源花在刀刃上。
5.3 误区三:忽视人的因素
编程问题,很多时候不是技术问题,是人的问题。沟通不畅、需求不清、责任不明,这些比任何技术难题都更致命。我见过技术很强但协作很差的团队,项目照样失败。反过来,技术一般但沟通顺畅的团队,往往能做出可用的东西。所以别只盯着代码,多花点心思在人和流程上。
5.4 实操避坑速查表
| 坑点 | 表现 | 应对 |
|---|---|---|
| 过度抽象 | 为了扩展加了一堆用不上的层 | 等真正需要时再抽象 |
| 测试迷信 | 覆盖率很高但线上仍出事 | 补集成测试和监控 |
| 重写冲动 | 一遇到老代码就想推倒重来 | 先评估重写成本和风险 |
| 工具崇拜 | 频繁换技术栈 | 稳定优先,按需引入 |
| 忽视文档 | 关键决策只存在某人脑子里 | 强制记录接口和假设 |
6. 我个人对“编程尚未被解决”的体会
写了这么多年代码,我越来越觉得,编程的难,不在于语法,不在于算法,而在于它要求人同时扮演多个矛盾的角色:既要像机器一样精确,又要像人一样灵活;既要着眼当下交付,又要考虑未来演化;既要独立解决问题,又要和团队高效协作。这些矛盾,没有一劳永逸的解法。
“编程尚未被解决”不是一句丧气话,而是一个清醒的认知。承认它没被解决,反而能让我们放下不切实际的期待,踏踏实实做好手头的事:把命名写清楚,把边界划明白,把测试补到位,把文档留下来。这些小事,才是对抗复杂度的真正武器。
最后分享一个我一直在用的小习惯:每次写完一段逻辑,问自己一句——“三个月后的我,或者一个陌生人,能看懂这段代码为什么这么写吗?”如果答案是否定的,那就再改改。这个习惯不能解决编程的根本问题,但能让问题少一点,让日子好过一点。