1. 为什么“编程尚未被解决”是个值得认真对待的判断
第一次看到“编程尚未被解决”这个说法,很多人会本能地反驳:编译器、操作系统、数据库、分布式框架,人类把软件堆到了这个程度,怎么能说没解决?但如果你真正在一线写过几年生产代码,尤其是维护过那种跑了五年以上、经手过十几拨人的老系统,你大概会同意这个判断——我们解决的从来不是“编程”本身,而是编程在某个特定约束下的局部问题。
这个判断在当下变得格外有现实意义,因为大模型(LLM)正在大规模进入编程场景。Claude Code、各类 AI 编程助手、Codex 这类付费工具,把“用自然语言生成代码”变成了日常操作。于是出现了一种很典型的错觉:既然 AI 能写代码了,那编程是不是快被解决了?我的观察恰恰相反——AI 把编程里最容易被自动化的那部分(把明确意图翻译成语法正确的代码)加速了,但同时把真正难的那部分(意图本身是否清晰、系统边界是否想清楚、变更如何安全落地)暴露得更彻底了。
所以这篇内容不是要唱衰 AI 编程,也不是要鼓吹某种工具。我想做的是把“编程尚未被解决”这句话拆开,讲清楚它到底指什么没被解决、为什么 LLM 和 harness 这类工程手段只能缓解而不能根治、以及一个普通从业者在当前工具环境下应该把精力放在哪里。适合正在用 AI 写代码、或者正在搭 AI 编程工作流的人看,也适合那些觉得“AI 都能写代码了我还学什么”的朋友重新校准一下预期。
关键词里出现了 harness、agent、Claude Code、LLM 这些词,我会在后面的章节里逐个落到具体场景,而不是停留在概念层面。先把结论摆出来:编程没被解决的核心,不在于“写不出代码”,而在于“说不清要什么”和“改不动已有的东西”。
2. 编程真正没被解决的三件事,和语法无关
2.1 意图到规格之间的那道鸿沟
写代码这个动作,本质上是一次翻译:把脑子里的意图翻译成机器能执行的指令。问题在于,脑子里的意图往往是模糊的、残缺的、自相矛盾的。客户说“我要一个能统计用户活跃度的功能”,这句话里藏着至少十个未定义项:活跃度怎么算?按登录算还是按操作算?时间窗口是七天还是三十天?去重不去重?时区怎么处理?历史数据要不要回填?
传统编程里,这些模糊点由程序员在写代码的过程中逐步澄清——写着写着发现“这里得问一下”,于是去确认。这个过程很慢,但它是有效的,因为澄清发生在实现之前或之中。LLM 编程把这个顺序打乱了:你给一句模糊的话,它给你一段看起来很完整的代码,模糊点被它“自信地”填上了。它不会告诉你“我这里假设了活跃度按登录算”,它直接按某个假设写完了。等你发现假设错了,代码已经跑了一轮。
这就是为什么很多人用 AI 编程会觉得“快是快,但返工也多”。快在生成,慢在返工,而返工的根源是意图没被显式化。LLM 不会替你解决意图模糊的问题,它只会把模糊的代价推迟到你发现的那一刻。
2.2 已有系统的“不可见约束”
新手写代码面对的是空白文件,老手写代码面对的是几万行别人写的、还在跑的、不能停的系统。这两件事的难度差了一个数量级。空白文件里,你写什么就是什么;老系统里,你写的每一行都要和已有的隐式约定兼容。
这些隐式约定包括:某个字段虽然叫status但实际存的是枚举的字符串而不是数字;某个接口虽然文档说返回列表但空的时候返回 null;某个定时任务依赖另一个任务的完成顺序但没有任何显式依赖声明。这些东西不会写在任何文档里,只存在于“踩过坑的人的记忆里”。LLM 读不到这些记忆,它只能读到你喂给它的代码片段。你喂得不全,它就按通用最佳实践写,然后撞上你系统里的特殊约定。
我见过太多这样的案例:AI 生成的代码单测全过,一上预发就挂,因为预发环境里那个“历史遗留的中间件”对请求体大小有限制,而这个限制在代码里完全看不出来。这不是 AI 的错,这是“已有系统的不可见约束”这个问题的体现,而这个问题在 AI 时代反而被放大了,因为生成代码的速度变快了,撞上约束的频率也变高了。
2.3 变更的安全边界
编程里最危险的动作不是“新增”,而是“修改”。新增一个函数,最坏情况是这个函数没人调用;修改一个已有函数,最坏情况是所有调用方都受影响。而判断“哪些调用方会受影响”这件事,在动态语言、反射、依赖注入、消息队列这些机制面前,静态分析经常给不出完整答案。
LLM 在修改代码时,倾向于做“局部合理”的改动:你让它改一个函数,它把这个函数改得符合你的描述,但它看不到这个函数被谁调用、在什么上下文里调用。它没有“变更影响面”的概念,除非你显式地把调用链喂给它。而真实系统里,调用链往往是跨仓库、跨服务、跨团队的,你根本喂不全。
所以“编程尚未被解决”在这三件事上体现得最明显:意图澄清、隐式约束、变更影响。这三件事都不是语法问题,都不是“写代码”的问题,而是“理解系统”的问题。LLM 擅长的是前者,不擅长的是后者。
3. LLM 在编程里到底解决了什么,又绕过了什么
3.1 它把“翻译”这一步做到了前所未有的效率
必须承认,LLM 在“把明确意图翻译成代码”这件事上,效率提升是实打实的。你如果能把需求描述到“输入是什么、输出是什么、边界条件是什么、异常怎么处理”这个粒度,它生成的代码质量相当可用。尤其是那些模式化程度高的代码:CRUD、数据转换、正则匹配、单元测试骨架、配置文件生成,这些场景下 AI 的产出比手写快得多,而且不容易犯低级语法错误。
Claude Code 这类工具的价值也在这里。它不是“替你思考”,而是“替你打字”。你把思考做完了,它把打字做完了。这个分工是合理的,也是当前技术条件下最稳的用法。我自己的习惯是:凡是能用一句话说清楚输入输出的函数,直接让 AI 写;凡是需要“想一下”的逻辑,先自己想清楚再让 AI 写。
3.2 它绕过了“理解上下文”这一步
但 LLM 有个结构性的局限:它的上下文是有限的,而真实系统的上下文是无限的。你不可能把一个百万行的代码库全部塞进上下文窗口,你只能塞一部分。塞哪一部分,就成了一个需要判断的问题。塞少了,它不知道约束;塞多了,它抓不住重点。
这就引出了 harness 这个概念。harness 在 AI 编程语境里,指的是包裹在模型外面的一层工程结构,负责给模型提供上下文、约束输出格式、校验结果、处理失败重试。它和 agent 的区别在于:agent 更强调“自主决策和行动”,harness 更强调“受控的执行框架”。一个 agent 可能自己决定去读哪个文件、跑哪个命令;一个 harness 则是你预先定义好“模型能看到什么、能做什么、做完怎么验证”。
这个区别很关键。agent 听起来很酷,但在生产环境里,不受控的自主性往往是风险来源。harness 的思路是:把模型的自由度限制在一个你设计好的轨道里,让它在这个轨道内发挥能力。这也是为什么很多团队在试过纯 agent 方案后,最后落地的是 harness 方案——不是模型不够强,而是工程上需要可控性。
3.3 它没有解决“验证”这一步
AI 生成代码之后,谁来验证这段代码是对的?这个问题在当前工具链里依然没有好答案。单元测试可以验证“行为符合预期”,但单元测试本身也可能是 AI 生成的,而 AI 生成的测试往往和 AI 生成的代码共享同一个错误假设——它测的是“代码做了它做的事”,而不是“代码做了正确的事”。
LLM as judge 是一种思路:用另一个模型来评审生成的代码。但评审模型同样受限于上下文,它看不到运行时行为,看不到生产环境的真实数据分布。它能发现“这段代码有 SQL 注入风险”,但发现不了“这段代码在数据量为零时会除零”。验证这件事,最终还是得靠人来定义“什么算对”,然后靠工程手段(测试、灰度、监控、回滚)来兜底。
所以我的判断是:LLM 解决了编程里“表达”的问题,没有解决“理解”和“验证”的问题。而后者才是编程难的地方。
4. 用 harness 思路搭一套可控的 AI 编程工作流
4.1 先定义“模型能看到什么”
搭 harness 的第一步,不是选模型,而是定义上下文边界。你要明确:模型在完成这个任务时,需要看到哪些文件、哪些接口定义、哪些数据样例、哪些约束说明。这个定义过程本身就是一次系统梳理,很多时候你定义完发现,原来自己对这个模块的理解也不够清晰。
具体做法上,我习惯按“任务类型”来组织上下文。比如“新增一个 API 接口”这个任务,模型需要看到:路由注册文件、相邻接口的实现样例、请求和响应的数据结构定义、错误码规范、鉴权中间件用法。这五样东西打包成一个上下文模板,每次做同类任务时复用。这样既保证了信息完整,又避免了每次重新拼上下文。
提示:上下文模板不要追求“大而全”,要追求“刚好够”。塞太多无关文件会稀释模型的注意力,反而降低生成质量。判断标准是:如果这段上下文里有一半内容模型用不上,就该拆细。
4.2 再定义“模型能做什么”
这一步是 harness 和 agent 分道扬镳的地方。agent 模式下,模型可以自己决定跑什么命令、改什么文件;harness 模式下,你预先定义好允许的操作集合。比如:允许读文件、允许写指定目录下的文件、允许跑测试命令、不允许执行任意 shell、不允许访问网络。
这个限制听起来很保守,但实际用起来反而更顺。因为限制明确之后,模型的输出格式可以标准化,你的校验逻辑也可以标准化。模型说“我要改这个文件”,你的 harness 先检查这个文件在不在允许列表里,在就放行,不在就拒绝并提示。这套机制跑顺之后,整个流程的可预测性会大幅提升。
4.3 最后定义“做完怎么验证”
验证环节是 harness 里最容易被忽略、但最重要的一环。我的做法是分三层:第一层是语法和格式校验,模型输出的代码先过 linter 和类型检查,不过就让它重写;第二层是单元测试,跑指定测试集,失败就把失败信息喂回给模型让它修;第三层是人工评审,前两层都过了之后,由人来看一遍逻辑是否符合意图。
这三层里,第一层和第二层可以完全自动化,第三层必须有人。不要试图用模型替代第三层,因为第三层验证的是“意图是否正确”,而意图只有人知道。把模型用在第一二层,把人的精力省下来放在第三层,这是当前性价比最高的分工。
5. 几个真实场景里的踩坑记录
5.1 上下文给多了反而更差
有一次我让模型改一个数据聚合函数,为了“让它理解完整背景”,我把整个数据处理模块的十几个文件都塞进了上下文。结果模型生成的代码里引用了一个根本不存在的工具函数——它把另一个文件里的函数名记混了。上下文太长的时候,模型对细节的把握反而下降。
后来我改成只给三个文件:目标函数所在文件、它调用的工具函数所在文件、一个测试样例。生成质量立刻稳定了。这个教训是:上下文的相关性比完整性重要。宁可少给,不可乱给。
5.2 模型会“自信地”忽略你的约束
我在上下文里明确写了“这个字段可能为 null,必须做空值检查”,模型生成的代码里还是直接用了这个字段。不是它没看到,而是它在生成时被“主流写法”带跑了——大部分代码里这个字段都不为 null,它就按主流写法写了。
解决办法是把约束从“描述”变成“检查”。与其在上下文里写“必须做空值检查”,不如在验证层加一条规则:如果代码里出现对该字段的直接访问而没有前置判空,就拒绝。让约束可执行,而不是可阅读。
5.3 多 AI 协作的协调成本被低估了
多 AI 协作听起来很美:一个模型写代码,一个模型评审,一个模型写测试。实际跑起来,协调成本很高。评审模型提出的修改意见,写代码的模型不一定能正确理解;测试模型写的测试,可能和代码模型的假设不一致。三个模型各说各话,最后还是要人来仲裁。
我的经验是:多 AI 协作适合“任务边界清晰、接口明确”的场景,比如一个模型专门做代码翻译,一个模型专门做文档生成,两者通过结构化数据交互。如果任务本身需要频繁的语义协商,多 AI 反而不如单 AI 加人工。
6. 把精力放在模型替代不了的地方
6.1 需求澄清能力比提示词技巧更重要
现在网上有很多“AI 编程提示词”教程,教你用什么句式能让模型生成更好的代码。这些技巧有用,但边际收益在递减。真正决定产出质量的,是你能不能把需求澄清到模型可以直接执行的粒度。这个能力不是提示词技巧,是需求分析能力。
我自己的做法是:在打开 AI 工具之前,先用纸笔把“输入、输出、边界、异常”四件事写清楚。写不清楚就说明还没想清楚,这时候让 AI 写只会得到一堆需要返工的代码。这个习惯看起来慢,但省下的返工时间远超这点准备时间。
6.2 读代码的能力比写代码的能力更值钱
AI 时代,写代码的成本在下降,读代码的成本在上升。因为代码总量在增加,而理解代码的门槛没有降低。你能快速读懂一个陌生模块在做什么、它的隐式约束是什么、改哪里会影响哪里,这个能力在当前环境下越来越值钱。
我建议把一部分“写代码”的时间挪到“读代码”上。具体做法:每周挑一个自己没参与过的模块,花一小时读一遍,画出它的数据流和依赖关系。坚持几个月,你对系统的整体理解会明显不一样,而这种理解是 AI 给不了你的。
6.3 验证和兜底的设计能力是最后的护城河
不管 AI 多强,最终为代码负责的是人。所以“怎么验证这段代码是对的”“出问题了怎么快速回滚”“怎么在灰度阶段发现异常”这些设计能力,是模型替代不了的。这些能力不性感,不出现在热搜词里,但它们是生产系统的真正底座。
我的建议是:在用 AI 提效的同时,把省下来的时间投入到可观测性建设上——日志、指标、追踪、告警、回滚预案。这些东西平时看不出价值,出事的时候就是救命的。AI 让代码产出变快了,如果验证和兜底跟不上,出事的速度也会变快。
7. 关于“编程尚未被解决”的一点个人体会
我用了大半年 AI 编程工具,从最初的兴奋到中间的怀疑,再到现在的平静。兴奋是因为生成速度确实快,怀疑是因为返工率一度很高,平静是因为想明白了:AI 改变的是编程的“表达层”,没有改变“理解层”和“验证层”。而后者才是编程之所以难的原因。
“编程尚未被解决”这句话,我的理解是:只要软件系统还在增长、还在演化、还在被人维护,编程就不会被“解决”。因为编程的本质不是写代码,是在一个不断变化的系统里做出一系列可维护、可验证、可回滚的决策。代码只是这些决策的副产品。AI 能帮你更快地写出副产品,但决策本身还是得你自己做。
所以与其问“AI 会不会取代程序员”,不如问“在 AI 能写代码之后,程序员的核心价值是什么”。我的答案是:把意图想清楚、把约束找出来、把验证设计好。这三件事,目前还没有任何工具能替你完成。