☰
编程尚未被解决:LLM、AI Agent与AI编译器的真实能力边界
2026/10/15 9:13:22 网站建设 项目流程

1. 为什么“编程尚未被解决”是个值得认真对待的判断

先把话说直白一点:编程这件事,到今天为止,仍然没有被真正解决。不是说我们不会写代码,而是说我们依然在用一种极其原始的方式,把人类脑子里的意图,翻译成机器能执行的指令。这个翻译过程,充满了歧义、重复劳动、隐式约定和不可靠的抽象。

我做了十多年一线开发,从最早手写汇编、调编译器参数,到后来写业务系统、搞数据管道、再到最近几年深度使用大模型辅助编程,我越来越确信一件事:编程的核心难题从来不是“写代码”,而是“把意图无损地变成可执行系统”。写代码只是这个过程中最显眼、但未必是最关键的一环。

你可能会说,现在不是有 LLM 了吗?不是有 AI 编程助手了吗?不是有 Codex 这类付费 AI 编程软件了吗?确实,这些东西让写代码的门槛降低了不少。但如果你真的在复杂项目里用过它们,就会发现一个尴尬的现实:AI 能帮你写函数,但很难帮你写系统;能帮你补全代码,但很难帮你保证正确性;能帮你生成片段,但很难帮你维护一致性。

这就是“编程尚未被解决”的真正含义。我们解决的是“打字速度”问题,不是“意图表达”问题。我们解决的是“语法记忆”问题,不是“系统设计”问题。我们解决的是“单点代码生成”问题,不是“全局逻辑闭环”问题。

这篇文章,我想从一线从业者的视角,把这件事拆开来讲。我会聊到 LLM 在编程中的真实能力边界、编译器与 AI 编译器的区别、异步编程和并发模型为什么依然难写、AI Agent 在编程场景中的实际表现、以及我们到底该怎么看待“AI 编程”这件事。如果你正在用 AI 写代码,或者正在构建 AI 编程工具,或者只是对“编程为什么这么难”感到困惑,这篇文章应该能给你一些不一样的视角。

2. 编程的本质:把模糊意图翻译成精确指令

2.1 编程难的不是语法,是意图对齐

很多人以为编程难在语法。变量声明、循环、条件判断、函数调用,这些东西确实有学习成本,但它们不是编程的本质难点。真正的难点在于:你脑子里的意图,本身就是模糊的、不完整的、甚至自相矛盾的。

举个例子。产品经理跟你说:“做一个用户登录功能。”这句话听起来很简单,但它背后隐藏了多少决策?用户名密码登录还是手机号验证码登录?要不要支持第三方登录?密码加密用什么算法?登录态怎么维持?Token 过期时间多久?失败几次锁定账户?锁定后怎么解锁?多设备登录怎么处理?异地登录要不要风控?

这些决策,产品经理不会一次性告诉你,甚至他自己也没想清楚。但代码必须精确。你不能写一个“大概能登录”的系统,因为计算机不接受“大概”。编程的本质,就是把人类语言中的模糊意图,逐步收敛成计算机能执行的精确指令。这个收敛过程,才是编程真正难的地方。

LLM 的出现,让“写代码”这个动作变快了,但它没有解决“意图收敛”这个根本问题。你给 LLM 一个模糊需求,它给你一段看起来能跑的代码,但那段代码背后的决策,可能跟你脑子里的完全不一样。你还是要自己去对齐、去修正、去补全。

2.2 编译器:人类意图与机器执行之间的第一道桥梁

说到编程,就绕不开编译器。很多人分不清编译器和编辑器的区别。编辑器是你写代码的工具,比如 VS Code、Vim、Emacs。编译器是把你的代码翻译成机器指令的工具,比如 GCC、MSVC、Clang。

编译器的核心工作,是把高级语言翻译成低级语言。这个过程通常分为几个阶段:词法分析、语法分析、语义分析、中间代码生成、优化、目标代码生成。每个阶段都有严格的数学基础和工程约束。

我举个实际例子。你在 C 语言里写了一个中断函数,比如在 CH32V 这类单片机上用 GCC 编译器定义中断函数。你写的是:

void EXTI0_IRQHandler(void) __attribute__((interrupt)); void EXTI0_IRQHandler(void) { // 中断处理逻辑 }

编译器要做的事情,远不止把你写的函数名放到中断向量表里。它要保证中断发生时,现场保护正确、堆栈对齐正确、中断返回指令正确、编译器优化不会破坏中断上下文。这些东西,你写代码的时候可能根本没想过,但编译器必须帮你处理好。

这就是编译器的价值:它把大量底层细节封装起来,让你能用相对接近人类思维的方式写代码。但编译器也有它的局限。它只能检查语法和部分语义,它不知道你的业务逻辑对不对,不知道你的并发模型有没有死锁,不知道你的分布式系统会不会丢消息。

2.3 为什么“编译器未包含 main 类型”这类错误依然频繁出现

你肯定见过类似“编译器未包含 main 类型”这样的报错。这个错误看起来很简单,但它暴露了一个深层问题:编程工具链的抽象泄漏。

C 语言规定,程序入口是 main 函数。但 main 函数的签名有多种合法形式:int main(void)、int main(int argc, char *argv[])、int main(int argc, char **argv)。如果你写错了,或者链接器找不到 main,就会报错。

这个错误本身不难解决,但它说明了一件事:编程的抽象层次越多,泄漏点就越多。你用的语言、编译器、链接器、构建系统、操作系统,每一层都有自己的规则和约定。这些规则和约定,很多时候并没有被完整地文档化,也没有被工具很好地检查。

LLM 能帮你写代码,但它很难帮你理解这些跨层的隐式约定。你问 LLM“为什么编译器找不到 main”,它可能会给你一堆通用建议,但它不知道你的构建脚本里是不是把 main 所在的源文件排除了,不知道你的链接顺序是不是有问题,不知道你的目标平台是不是有特殊入口约定。

3. LLM 在编程中的真实能力边界

3.1 LLM 擅长什么:模式匹配与代码补全

先说 LLM 在编程中真正擅长的事情。根据我自己的使用经验,LLM 在以下几类任务上表现很好:

  • 代码补全:你写了一个函数签名,它能帮你补全函数体。
  • 样板代码生成:比如生成一个 REST API 的 CRUD 接口,生成一个数据模型的序列化代码。
  • 代码解释:你给它一段看不懂的代码,它能帮你解释这段代码在干什么。
  • 错误信息解读:你给它一个报错信息,它能告诉你大概是什么问题。
  • 代码转换:把 Python 代码转成 JavaScript,把 SQL 转成 Pandas 操作。

这些任务的共同特点是:有大量训练数据,模式相对固定,正确性容易验证。LLM 在这些任务上,确实能显著提升效率。

我举个例子。你要写一个 MobileNetV2 的代码实现,用于移动端图像分类。如果你从零开始写,可能要花几个小时查论文、调参数、写层定义。但如果你用 LLM,它可以帮你生成一个基于 PyTorch 或 TensorFlow 的 MobileNetV2 实现,你只需要微调一下输入输出和超参数。

再比如,你要写一个 XGBoost 的训练代码,LLM 可以帮你生成数据加载、特征工程、模型训练、交叉验证、超参数搜索的完整流程。这些代码有大量公开示例,LLM 学得很好。

3.2 LLM 不擅长什么:系统设计与全局一致性

但 LLM 在以下几类任务上,表现就差很多:

  • 系统架构设计:如何划分模块、如何定义接口、如何处理跨模块依赖。
  • 并发与异步编程:如何避免死锁、如何保证线程安全、如何处理异步错误传播。
  • 分布式系统一致性:如何保证消息不丢、如何实现幂等、如何处理部分失败。
  • 性能优化:如何定位瓶颈、如何权衡时间与空间、如何做缓存策略。
  • 安全与容错:如何防止注入攻击、如何处理敏感数据、如何实现优雅降级。

这些任务的共同特点是:需要全局视角,需要理解系统各部分之间的交互,需要权衡多种约束。LLM 的训练数据里,有大量代码片段,但很少有完整的系统设计过程和权衡决策记录。

我踩过的一个坑:我曾经让 LLM 帮我写一个异步任务队列。它给我生成了一个基于asyncio的实现,看起来很美。但实际跑起来,发现任务堆积时内存暴涨,因为队列没有背压机制。LLM 生成的代码,只考虑了“正常路径”,没有考虑“异常路径”和“边界条件”。

3.3 LLM 编程的“最后一公里”问题

LLM 编程有一个很典型的现象,我称之为“最后一公里”问题。LLM 能帮你完成 80% 的代码,但剩下 20% 的代码,需要你花 80% 的时间去调试、修正、集成。

为什么会这样?因为 LLM 生成的代码,是在“局部”看起来正确的代码。它不知道你的项目结构,不知道你的依赖版本,不知道你的编码规范,不知道你的部署环境。它生成的代码,需要你去适配、去调整、去测试。

我举个例子。你让 LLM 帮你写一个 HDFS 编程实践的示例。它给你生成了一段 Java 代码,使用 Hadoop API 读写 HDFS。但你的 Hadoop 版本是 3.x,它生成的代码可能是基于 2.x 的 API。你需要去查 API 变更,去调整依赖,去处理兼容性问题。

再比如,你让 LLM 帮你写一个 PLC 编程的逻辑。它给你生成了一段梯形图对应的代码,但你的 PLC 品牌是西门子,它生成的代码可能是基于三菱的指令集。你需要去翻译指令,去调整地址映射,去测试实际行为。

这就是“最后一公里”问题:LLM 能帮你跳过从零到一的阶段,但从一到可用的阶段,还是得你自己走。

4. AI Agent 与 AI 编译器:新概念背后的老问题

4.1 AI Agent 在编程场景中的实际表现

最近一年,AI Agent 是个热词。很多人说,AI Agent 能自主规划、自主执行、自主纠错,能帮你完成复杂编程任务。我实际用过一些 AI Agent 工具,也自己搭过一些 Agent 原型,说点真实感受。

AI Agent 在编程场景中,目前能做的事情,主要是任务分解与工具调用。比如你给它一个任务:“帮我写一个 Python 脚本,从数据库读取数据,做清洗,然后导出 CSV。”它可以把这个任务分解成:连接数据库、执行查询、数据清洗、导出文件。然后调用相应的工具或生成相应的代码。

但 AI Agent 的问题也很明显:

  • 规划能力有限:它只能做浅层规划,遇到复杂依赖关系就容易乱。
  • 纠错能力有限:它只能根据报错信息做简单修正,遇到深层逻辑错误就束手无策。
  • 上下文管理有限:它记不住太长的上下文,容易丢失关键信息。
  • 工具使用有限:它能调用的工具,需要你提前定义好,它自己不会创造工具。

我试过一个 AI Agent 帮我调试一个异步编程的问题。它看了报错信息,建议我加await。我加了,还是报错。它又建议我检查事件循环。我检查了,没问题。它又建议我检查协程调度。我检查了,也没问题。最后我自己发现,是某个第三方库在内部创建了新的事件循环,导致协程跑在了错误的循环上。这个问题,AI Agent 根本定位不到,因为它看不到第三方库的内部实现。

4.2 AI 编译器:是革命还是包装

“AI 编译器”这个词,最近也很火。你在 CSDN 上能看到不少相关文章。但说实话,目前大部分所谓的 AI 编译器,本质上还是传统编译器 + AI 辅助优化,或者AI 模型编译器,跟“用 AI 重新发明编译器”是两回事。

传统编译器 + AI 辅助优化,比如用机器学习模型来预测最佳指令调度、最佳寄存器分配、最佳循环展开策略。这些研究确实有进展,但离实用还有距离。

AI 模型编译器,比如 TVM、XLA、TensorRT,它们做的事情,是把深度学习模型编译成能在特定硬件上高效执行的代码。这类编译器解决的是“模型部署”问题,不是“通用编程”问题。

真正的 AI 编译器,应该是能理解人类意图、能自动生成正确代码、能自动优化性能、能自动处理边界条件的编译器。这个东西,目前还不存在。我们离它还有很长的路。

4.3 多 AI 协作:听起来很美,做起来很难

“多 AI 协作”也是个热词。让多个 AI Agent 分工合作,一个写前端,一个写后端,一个写测试,一个做代码审查。听起来很美,但实际做起来,问题很多。

最大的问题是通信成本。多个 AI Agent 之间,需要交换信息、对齐接口、协调进度。这些通信,需要定义协议、需要处理冲突、需要保证一致性。这些工作,目前主要靠人类来协调,AI 自己搞不定。

我试过让两个 AI Agent 协作写一个前后端项目。前端 Agent 生成了一个 API 调用,后端 Agent 生成了一个 API 实现。但两者的接口定义不一致:前端传的是 JSON,后端期望的是 form-data;前端期望返回数组,后端返回的是对象。这种问题,需要人类去发现、去协调、去修正。

5. 编程尚未被解决:我们到底卡在哪里

5.1 意图表达:人类语言与形式语言之间的鸿沟

编程尚未被解决,第一个卡点,是意图表达。人类语言是模糊的、上下文相关的、充满歧义的。形式语言是精确的、上下文无关的、无歧义的。这两者之间的鸿沟,目前只能靠人类来填补。

你可能会说,LLM 不是能理解人类语言吗?是的,LLM 能理解人类语言,但它理解的是“统计意义上的语言”,不是“精确的意图”。你给 LLM 一个需求,它给你一个“统计上最可能正确”的代码,但这个代码不一定符合你的真实意图。

我举个例子。你让 LLM 写一个“用户注册”功能。它给你生成了一个包含用户名、密码、邮箱的注册接口。但你的真实意图可能是:用户名必须是手机号,密码必须包含大小写字母和数字,邮箱必须验证,注册后必须发送欢迎邮件。这些意图,你没有明确表达,LLM 也不会主动追问。

5.2 正确性验证:代码写完只是开始

编程尚未被解决,第二个卡点,是正确性验证。代码写完,只是开始。你怎么知道代码是对的?你怎么知道代码在所有情况下都是对的?你怎么知道代码在并发、分布式、异常情况下都是对的?

目前的主流做法是测试。单元测试、集成测试、端到端测试、压力测试、混沌测试。但测试只能证明代码在测试覆盖的情况下是对的,不能证明代码在所有情况下都是对的。而且,写测试本身,也是一件成本很高的事情。

LLM 能帮你写测试,但它写的测试,往往只能覆盖“正常路径”,很难覆盖“异常路径”和“边界条件”。你让 LLM 写一个异步编程的测试,它可能只测试了成功回调,没有测试超时、取消、异常传播。

5.3 维护与演化:代码是写给人看的

编程尚未被解决,第三个卡点,是维护与演化。代码不是一次性的,代码是要长期维护的。你今天写的代码,明天可能要被别人修改,后天可能要被自己修改。代码的可读性、可维护性、可演化性,比代码能不能跑更重要。

LLM 生成的代码,往往缺乏可读性和可维护性。它生成的代码,可能能跑,但变量命名随意、注释缺失、结构混乱、重复代码多。你让 LLM 生成一个复杂业务逻辑的代码,它可能给你生成一个几百行的函数,没有任何拆分和抽象。

我踩过的一个坑:我曾经让 LLM 帮我重构一段代码。它把代码从 200 行压缩到 50 行,看起来简洁多了。但可读性急剧下降,因为用了大量嵌套的三元表达式和列表推导式。后来我不得不手动改回来,因为团队里其他人看不懂。

6. 从工程实践看:怎么和“未解决”的编程共处

6.1 把 LLM 当助手,不要当替身

既然编程尚未被解决,LLM 也解决不了,那我们应该怎么和这个现实共处?我的建议是:把 LLM 当助手,不要当替身。

LLM 可以帮你写样板代码、帮你查 API、帮你解释报错、帮你生成测试用例。但系统设计、架构决策、关键逻辑、性能优化、安全容错,这些还是得你自己来。

我自己的做法是:用 LLM 做“第一稿”,然后自己做“第二稿”。LLM 生成代码后,我会逐行审查,理解每一行在干什么,然后根据我的项目规范、业务需求、性能要求,进行修改和优化。这个过程,比从零写代码快,但比直接复制粘贴慢。但慢有慢的价值,因为你在审查和修改的过程中,真正理解了代码。

6.2 建立自己的代码审查清单

和 LLM 共处,你需要建立自己的代码审查清单。以下是我常用的清单,你可以参考:

审查项检查内容常见问题
接口一致性输入输出是否符合约定参数类型不匹配、返回值格式不一致
错误处理异常路径是否覆盖缺少 try-catch、错误信息不明确
并发安全共享状态是否保护竞态条件、死锁、内存泄漏
性能边界时间空间复杂度是否可接受嵌套循环、重复计算、内存暴涨
可读性命名、注释、结构是否清晰变量名随意、注释缺失、函数过长
可测试性是否容易写测试依赖硬编码、副作用过多、难以 mock

这个清单,是我踩了无数坑之后总结出来的。每次 LLM 生成代码后,我都会对照这个清单过一遍。虽然不能保证 100% 正确,但能过滤掉大部分低级问题。

6.3 异步编程:LLM 最容易翻车的地方

异步编程是 LLM 最容易翻车的地方之一。异步编程的难点在于:控制流不是线性的,错误传播不是直观的,资源管理不是自动的。

我举个例子。你让 LLM 写一个异步爬虫,它给你生成了这样的代码:

import asyncio import aiohttp async def fetch(url): async with aiohttp.ClientSession() as session: async with session.get(url) as response: return await response.text() async def main(): urls = ["http://example.com" for _ in range(100)] tasks = [fetch(url) for url in urls] results = await asyncio.gather(*tasks) return results asyncio.run(main())

这段代码看起来没问题,但实际上有几个坑:

  • 每个fetch都创建了一个新的ClientSession,这是低效的,应该复用 session。
  • asyncio.gather默认遇到异常就抛出,但其他任务不会被取消,可能导致资源泄漏。
  • 没有限制并发数,100 个请求同时发出,可能被目标服务器限流或封禁。
  • 没有超时控制,某个请求卡住,整个程序就卡住了。

这些问题,LLM 不会主动告诉你。你需要自己知道,然后去修正。

6.4 编译器与工具链:别让抽象泄漏坑了你

编译器与工具链的问题,也是编程尚未被解决的重要方面。你用的语言、编译器、链接器、构建系统、包管理器,每一层都有自己的规则和约定。这些规则和约定,很多时候并没有被完整地文档化,也没有被工具很好地检查。

我踩过的一个坑:我曾经在一个项目里,用 GCC 编译一个 C 程序,链接时报错“undefined reference tomain”。我检查了代码,main 函数明明存在。后来发现,是因为我把 main 函数写在了#ifdef块里,而那个宏没有被定义。编译器把 main 函数排除了,链接器自然找不到。

这种问题,LLM 很难帮你定位,因为它看不到你的编译命令、看不到你的宏定义、看不到你的构建脚本。你需要自己去查、去试、去理解工具链的行为。

7. 一些实操建议:怎么在“未解决”的编程世界里活得更好

7.1 学会读错误信息

错误信息是你最好的朋友。但很多人不读错误信息,直接去搜、去问、去让 LLM 解释。我的建议是:先自己读一遍错误信息,理解它在说什么,再去搜、去问。

错误信息通常包含:错误类型、错误位置、错误原因、可能的解决方案。你读懂了这些,就能自己解决大部分问题。读不懂,再去搜、去问,也能问得更精准。

我举个例子。你看到“由于找不到 msvcp140.dll 无法继续执行代码”这个错误。你读一遍,就知道是缺少 Visual C++ 运行库。你去装一个 Visual C++ Redistributable 就行了。如果你不读,直接去问 LLM,它可能给你一堆复杂的解决方案,其实根本不需要。

7.2 学会写最小可复现示例

遇到问题,学会写最小可复现示例。把问题从你的项目中剥离出来,写一个最小的、能独立运行的、能复现问题的代码。这个过程,本身就能帮你理清思路,发现问题的根源。

我自己的经验是:80% 的问题,在写最小可复现示例的过程中,就自己解决了。因为你在剥离问题的过程中,会排除掉大量无关因素,聚焦到真正的问题上。

7.3 学会用版本控制

版本控制是你的安全网。每次修改代码前,先提交。每次让 LLM 生成代码后,先提交。每次重构前,先提交。这样,如果改坏了,你可以随时回滚。

我见过太多人,不用版本控制,改代码改坏了,只能从头再来。或者用版本控制,但从来不提交,改坏了还是只能从头再来。版本控制不是可选项,是必选项。

7.4 学会写文档

文档是你的第二大脑。你今天想清楚的事情,明天可能就忘了。你今天踩过的坑,明天可能还会踩。写文档,不是为了别人,是为了未来的自己。

我自己的做法是:每个项目,都有一个NOTES.md,记录项目背景、架构决策、踩坑记录、待办事项。每次遇到问题,解决问题后,都会在NOTES.md里记一笔。这个习惯,帮我节省了大量重复排查的时间。

8. 编程的未来:不是被解决,而是被重新定义

8.1 编程不会消失,但会变形

编程尚未被解决,但这不意味着编程会消失。编程会变形。从手写汇编,到写 C,到写 Python,到写 SQL,到写配置,到写提示词。编程的抽象层次在不断提高,编程的表达方式在不断变化。

LLM 的出现,让“写提示词”成为一种新的编程方式。你不再需要写具体的代码,你只需要描述你的意图,LLM 帮你生成代码。但这不意味着编程变简单了,因为描述意图本身,就是编程最难的部分。

8.2 未来的程序员:从写代码到写意图

未来的程序员,可能不再需要写大量代码,但需要更清晰地表达意图、更准确地定义问题、更严谨地验证结果。编程的核心能力,从“语法记忆”转向“问题分解”,从“代码实现”转向“系统设计”,从“调试技巧”转向“验证思维”。

我自己的感受是:用了 LLM 之后,我写代码的时间少了,但思考的时间多了。我不再花时间查 API、写样板代码,而是花更多时间想清楚:这个功能到底要解决什么问题?边界条件是什么?异常情况怎么处理?性能要求是什么?

8.3 给从业者的建议:保持学习,保持怀疑

最后,给从业者一些建议。保持学习,保持怀疑。LLM 在快速进化,工具在快速迭代,今天的最佳实践,明天可能就过时了。你需要保持学习,跟上变化。

但同时,保持怀疑。LLM 生成的代码,不一定是对的。LLM 给出的建议,不一定适合你的场景。LLM 解释的错误,不一定准确。你需要保持怀疑,自己验证,自己判断。

我在实际使用中发现,LLM 最危险的地方,不是它生成的代码有 bug,而是它生成的代码看起来太正确了,让你放松了警惕。你复制粘贴,跑通了,就以为没问题了。但实际上,可能隐藏着性能问题、安全问题、并发问题。这些问题,在测试环境可能不会暴露,在生产环境可能就会爆发。

所以,我的建议是:把 LLM 当助手,但不要当替身。把 LLM 当工具,但不要当权威。把 LLM 当起点,但不要当终点。编程尚未被解决,LLM 也没有解决它。但我们可以和这个现实共处,用 LLM 提升效率,同时保持自己的判断力和掌控力。

这个内容后续还可以这样扩展:如果你对 AI Agent 在编程中的具体实现感兴趣,可以深入研究 Agent 的规划算法、工具调用协议、记忆管理机制。如果你对 AI 编译器感兴趣,可以从 TVM、XLA 这些开源项目入手,理解模型编译的基本原理。如果你对异步编程感兴趣,可以从 asyncio、Tokio、RxJS 这些框架入手,理解异步编程的核心模式。无论哪个方向,记住一点:工具在变,但编程的本质没变——把模糊意图翻译成精确指令,这个难题,还在那里。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询