编程尚未被解决:从语义鸿沟到知识蒸发的本质困难与应对
1. 为什么“编程尚未被解决”是个值得认真对待的判断第一次听到“编程尚未被解决”这个说法很多人下意识会觉得这是一句博眼球的暴论。毕竟我们身边跑着操作系统、数据库、编译器、浏览器每天有海量代码被提交、被部署、被调用怎么看编程都已经被“解决”得相当彻底了。但如果你在一线写过足够多的项目尤其是维护过那种跑了三五年、经手过七八个人的老系统你大概会有另一种感受我们解决的只是“让机器执行指令”这件事而“让人可靠地表达意图并持续演进”这件事离解决还差得远。这个判断的核心不是说编程语言不够多、框架不够强而是说编程这件事的本质困难一直没被真正消除。本质困难是什么是把一个模糊的、变化的、充满隐含前提的人类需求翻译成一份精确的、可执行的、能被机器和后来者同时理解的指令集合。这个翻译过程里机器那一端我们做得越来越好人这一端几乎原地踏步。标题里的“尚未被解决”指向的正是人这一端的鸿沟。我写这篇文章不是要唱衰编程行业恰恰相反是想把这道鸿沟拆开来看清楚。适合谁来读如果你是被各种“低代码将取代程序员”“AI 让编程消失”之类说法搞得很焦虑的开发者或者是刚入行、想知道自己到底在跟什么困难搏斗的新人再或者你是技术管理者、想搞清楚团队效率瓶颈到底卡在哪这篇内容都值得你花时间。我会从问题本质、现有方案的局限、真实项目里的表现、以及我个人踩过的坑几个角度把“编程尚未被解决”这件事讲透并且给出一些可落地的应对思路。需要先说明一点下面涉及的具体案例、项目、人物都是我在行业里见到的普遍现象的抽象化表达用虚构代称呈现不指向任何真实主体。重点在于现象和规律而不是某个具体故事。2. 拆解“尚未被解决”的三层含义2.1 第一层意图到实现的翻译损耗编程最原始的形态是人把想法写成指令。问题在于人脑里的想法从来不是以指令形式存在的。一个产品经理说“用户下单后如果库存不足就提示等待”这句话里藏着多少没说的东西库存是实时查还是缓存查并发下单时怎么保证不超卖提示是弹窗还是页面跳转等待是排队还是直接失败这些细节在需求描述里全是空白而程序员必须把它们全部补全补错一个线上就出问题。这种翻译损耗我习惯叫它“语义鸿沟”。它不是靠更高级的语法能填平的。你换成函数式、换成声明式、换成任何范式鸿沟依然在因为鸿沟的根源在于自然语言的模糊性和形式语言的精确性之间没有直通车。我见过太多项目需求评审时大家点头如捣蒜开发到一半发现双方理解的根本不是同一件事。这不是沟通能力问题是语言本身的粒度不匹配。2.2 第二层复杂度增长与人的认知上限第二层困难更隐蔽也更致命。软件系统的复杂度不是线性增长的是超线性增长的。你加一个功能它可能和现有五个模块产生交互每个交互又可能引入新的边界情况。一个中型系统跑三年代码量翻三倍但理解成本可能翻十倍。为什么因为理解一个系统不仅要理解每个部分还要理解部分之间的所有连接。人的工作记忆容量是有限的这是生理事实不是靠“多加班多学习”能突破的。当系统复杂度超过某个阈值任何单个开发者都无法在脑子里完整建模整个系统。于是出现一种典型现象改 A 处崩 B 处修 B 处又影响 C 处。这不是程序员水平差是系统复杂度已经超出了单点认知的承载能力。所谓“编程尚未被解决”很大程度上是指我们还没有一套真正有效的手段让复杂度增长和人的认知能力保持同步。2.3 第三层演进中的系统与静态知识的矛盾第三层是最容易被忽视的。代码是静态的文本但系统是动态演进的。今天写下的注释、文档、架构决策半年后可能就过时了但代码还在跑后来者只能对着过时的说明猜意图。更麻烦的是很多关键决策根本没有被记录下来它们只存在于当初那个开发者的脑子里而那个人可能已经离职了。我管这个叫“知识蒸发”。一个系统运行越久蒸发越严重最后变成一座没人敢动的黑箱。你问为什么这里要加个特殊判断没人知道你问能不能删掉这段逻辑没人敢删。这种状态下编程与其说是创造不如说是考古。而考古式的维护效率极低风险极高。这同样是“尚未被解决”的体现我们还没有让系统在演进过程中自动保留可理解性的机制。3. 现有方案为什么没能真正解决它3.1 编程语言与范式的演进改善了表达没消除鸿沟从汇编到 C从 C 到 Java从 Java 到 Python再到各种函数式、声明式语言编程语言的演进确实让表达越来越接近人的思维。但要注意它改善的是“写”的体验不是“翻译”的准确性。你用 Python 写一行filter比用汇编写循环舒服多了但filter背后的语义边界、异常处理、性能特征依然需要人来判断。换句话说语言解决的是“怎么说”没解决“说什么”。而编程的核心困难恰恰在“说什么”这一端。我见过用最时髦语言写出来的最难维护的系统也见过用很朴素语言写出来的极其清晰的系统。语言是工具工具再好也替代不了对问题的理解。把编程的困难归因于语言不够高级是一种常见的误判。3.2 框架与抽象把复杂度藏起来不等于消除框架的思路是“别重复造轮子”把通用问题封装好你只管业务逻辑。这个思路本身没错但它有个副作用复杂度被藏起来了而不是被消除了。当你用框架顺风顺水时一切都很美好一旦遇到框架没覆盖的边界情况你就得掀开盖子往里看而这时候你面对的是一个你并不熟悉的、由别人设计的复杂系统。更麻烦的是抽象泄漏。所有非平凡的抽象在某个层面都会泄漏。你以为调个 API 就完事了结果性能问题、并发问题、内存问题从底层渗上来逼着你去理解那些被抽象掉的细节。这时候你会发现抽象没有降低你需要的知识总量只是改变了知识出现的时机。平时不学出事现学成本更高。3.3 低代码与可视化降低了门槛抬高了天花板低代码平台这几年很热逻辑是让不懂编程的人也能搭应用。它确实能解决一类问题表单、流程、简单 CRUD。但它的边界非常清晰一旦业务逻辑复杂到需要自定义算法、需要精细控制性能、需要处理复杂状态低代码就力不从心了。而且低代码平台往往有自己的锁定效应你搭得越深迁移成本越高。我个人的观察是低代码把编程的门槛降低了但同时把能达到的上限也压低了。它解决的是“让更多人能编程”没解决“让编程本身变得可靠”。对于简单场景它是好工具对于复杂场景它只是把困难推迟了。把它当成万能解药最后一定会失望。3.4 AI 辅助编程加速了写没解决想AI 辅助编程是当下最热的方向自动补全、代码生成、甚至根据注释生成函数确实能显著提升写代码的速度。但这里有个关键区分AI 加速的是“把已经想清楚的东西写出来”它没有解决“想清楚”这件事。而编程的困难大头在想清楚小头在写出来。你给 AI 一个模糊的需求它生成的代码看起来像模像样但边界条件、异常路径、性能特征往往经不起推敲。因为它是在模仿代码的表面模式不是在理解问题的实质约束。AI 能帮你写得更快但不能帮你判断“这个设计对不对”“这个边界要不要处理”“这个取舍合不合理”。这些判断依然是人来做的。所以 AI 让编程的“写”这一环变快了但“想”这一环的困难原封不动。4. 真实项目里“尚未被解决”是怎么表现的4.1 需求反复不是需求方善变是翻译没对齐我参与过一个内部管理系统需求文档写了三十页评审开了四轮大家都觉得清楚了。结果开发到第三周产品经理看到第一个可交互版本说“这不是我要的”。问题出在哪文档里写“列表支持筛选”但没写筛选是前端过滤还是后端查询没写筛选条件能不能组合没写筛选后分页怎么处理。这些空白在文档阶段看不出来一旦变成可点的界面分歧立刻暴露。这类反复表面看是需求变更实质是翻译损耗在开发过程中的延迟暴露。越晚暴露返工成本越高。我后来养成的习惯是任何需求在动手前先用最笨的方式画一遍数据流和状态变化把隐含假设显式写出来让需求方确认。这一步多花两小时能省两天返工。4.2 技术债累积每一笔都是当时的最优解技术债这个词被用烂了但它的本质值得说清楚。技术债不是“程序员偷懒”绝大多数技术债在产生的那一刻都是当时约束下的最优解。 deadline 紧、人手少、需求不确定你选择先跑通再优化这是理性决策。问题在于这些决策的上下文没有被记录下来后来者只看到“这段代码很烂”看不到“为什么当时要这么写”。我维护过一个跑了四年的服务里面有一段极其别扭的兼容逻辑。我花了半天才搞明白是因为早期某个上游系统的字段格式不统一当时为了快速上线做了特殊处理。后来上游系统早就升级了这段逻辑成了纯粹的负担但没人敢删因为不知道删了会不会影响什么。这就是技术债的典型形态不是代码本身有问题是代码背后的决策上下文丢失了。4.3 人员流动知识随人走系统留成谜人员流动是常态但很多团队没有应对知识流失的机制。一个核心开发者离职带走的不只是他的代码能力还有他对系统的大量隐性理解为什么这个模块要这么拆分、为什么这个参数要设成这个值、为什么这个流程要绕一下。这些理解不在文档里不在注释里只在他脑子里。我见过最极端的例子是一个关键服务只有一个人能改因为只有他知道那些看似随意的判断条件背后的业务规则。这个人一休假相关需求全部卡住。这不是个人英雄主义是系统设计没有把知识从个人转移到系统里。所谓“编程尚未被解决”在团队层面就表现为我们还没有一套可靠的方法让系统的可理解性不依赖于特定个人的在场。5. 面对“尚未被解决”一线开发者能做什么5.1 把隐含假设显式化是最低成本的保险既然翻译损耗是核心困难那最直接的应对就是在翻译过程中主动把隐含假设写出来。具体怎么做我常用的方法是“三问法”这个功能的输入有哪些可能输出有哪些可能中间有哪些状态每问一次就逼自己把没说的东西说出来。比如“用户登录”输入可能是手机号、邮箱、第三方凭证输出可能是成功、失败、需要验证中间状态可能是已锁定、已过期、首次登录。把这些列出来和需求方逐条确认能挡掉大量后期返工。这个动作看起来笨但极其有效。它的本质是把自然语言的模糊性在早期就压缩成形式化的清单。你不需要写代码只需要列清单成本极低收益极高。我带的项目里坚持做这一步的需求返工率明显低于不做的。5.2 为决策留痕而不是为代码留痕传统注释是解释“代码在做什么”但代码本身已经说明了它在做什么。真正有价值的是解释“为什么这么做”。所以我现在写注释重点写决策理由为什么选这个方案而不是那个、当时有什么约束、如果未来要改需要注意什么。这种注释才是后来者真正需要的。更进一步我会在关键模块维护一个简短的决策日志记录每次重大改动的背景、选项、取舍。不需要很正式几行字就行。它的作用是让后来者能重建当时的上下文而不是对着一堆代码猜。这个习惯坚持下来团队里“这段代码能不能删”的讨论会少很多因为答案往往就在日志里。5.3 控制复杂度增长主动做减法复杂度是编程困难的放大器所以控制复杂度就是直接对抗困难。控制的手段不是“写得更优雅”而是“主动做减法”。具体来说我会定期审视系统问三个问题这个模块还有用吗这个抽象还有必要吗这个依赖能去掉吗很多复杂度是历史堆积的没人主动清理它就一直在那。做减法最难的是心理关删代码比写代码更需要勇气因为你要承担删错的风险。但如果不做减法系统只会越来越重最后谁都动不了。我的经验是删之前先确认没有调用方再灰度观察一段时间确认无影响再彻底移除。这个过程慢但比让系统烂掉快。5.4 把可理解性当成一等公民很多团队把“能跑”当成唯一标准能跑就行看不懂无所谓。这是短视的。一个系统如果只有原作者能看懂那它的生命周期就绑定在原作者身上这是巨大的风险。所以我会把可理解性当成和功能、性能同等重要的指标。具体表现是命名要达意、结构要清晰、关键路径要有说明、新人能在一周内理解核心流程。怎么检验可理解性最直接的办法是让一个没参与过的人来读看他能不能讲清楚这个模块在干什么、为什么这么干。如果讲不清楚说明可理解性不达标需要补说明或重构。这个检验很残酷但很有效。我见过太多“作者觉得很清楚、别人完全看不懂”的代码问题就出在缺少这个检验环节。6. 常见问题与排查技巧实录6.1 需求理解偏差怎么尽早发现需求偏差最怕的是“开发完了才发现”。要尽早发现核心是缩短反馈环。我的做法是任何需求在动手前先用伪代码或流程图把核心逻辑走一遍拿给需求方看。注意不是拿最终界面是拿逻辑。因为界面容易让人关注样式逻辑才能暴露理解偏差。如果需求方看到逻辑说“不对这里应该是那样”恭喜你你在写代码前就发现了问题省下的返工时间是以天计的。还有一个技巧是“反向复述”让需求方把你要做的事复述一遍。如果他的复述和你的理解一致说明对齐了如果不一致立刻暴露分歧。这个动作只要五分钟但能挡掉大量后期扯皮。6.2 代码越写越乱怎么判断该重构还是该重写这是高频问题。我的判断标准是看“理解成本”和“修改成本”。如果理解成本高但修改成本低说明结构乱但耦合低可以逐步重构如果理解成本和修改成本都高说明耦合严重重构可能比重写还慢这时候要慎重考虑重写。但重写有个大坑你以为重写能解决复杂度其实只是把复杂度从旧系统转移到新系统而且新系统还会引入新问题。我的经验是除非旧系统已经彻底无法维护否则优先重构。重构的关键是“小步走”每次只改一个点改完立刻验证确保系统始终可运行。重写则是一次性替换风险集中一旦出问题很难回退。所以能重构就别重写这是用血换来的教训。6.3 关键人员离职知识怎么交接知识交接不能等到离职那天才开始那时候已经晚了。正确做法是平时就做“知识分散”任何关键模块至少两个人能改。怎么做到靠代码评审和结对。代码评审不只是找 bug更是知识传递的过程。评审的人通过读别人的代码理解系统被评审的人通过解释自己的代码梳理思路。结对则是更直接的知识同步。如果已经面临离职交接我的建议是不要试图交接所有细节优先交接“决策上下文”和“高风险区域”。具体来说让离职者讲清楚哪些地方容易出问题、哪些地方有历史包袱、哪些地方改的时候要特别小心。这些信息比代码本身更重要因为代码可以读上下文读不出来。6.4 常见问题速查表问题现象可能原因排查方向应对建议需求反复变更隐含假设未对齐检查需求文档是否有未定义的状态和边界动手前用伪代码对齐逻辑改一处崩多处模块耦合过重梳理调用关系找隐藏依赖小步重构逐步解耦代码没人敢动决策上下文丢失查注释和文档是否解释“为什么”补决策日志标注风险区域新人上手慢可理解性不足让新人复述核心流程补关键路径说明优化命名技术债越积越多缺少主动清理机制定期审视无用模块和依赖灰度删除确认无影响再移除7. 我对“编程尚未被解决”的真实体会写了这么多最后说点个人感受。我入行这些年最大的认知转变是编程的困难不在技术在表达和理解的传递。技术会更新语言会换代但“把想法准确传递成可执行、可维护的指令”这件事一直没变也一直没被真正解决。AI 来了它加速了写但没解决想低代码来了它降低了门槛但没解决复杂场景新框架来了它封装了通用问题但没消除边界情况。所以我的态度是接受“尚未被解决”这个现实然后在这个前提下把能做的做好。把隐含假设显式化、为决策留痕、控制复杂度、把可理解性当回事这些动作不炫酷但实实在在能降低困难。我试过也见过别人试过有效。如果你也在跟这些困难搏斗我的建议是别追求“彻底解决”追求“持续改善”。每次项目结束问自己一句这次有没有比上次更清楚一点如果有就是在进步。编程这件事可能永远不会有“解决”的那一天但我们可以让它一天比一天不那么难。这大概就是从业者能做的、也值得做的事。