AI 时代下,信息传递比写代码更重要
先列两个我再开发中遇到的问题事件:
- 上个月一次需求评审,产品把 PRD 投到屏幕上,二十多页。散会后我直接让 AI 按文档拆任务,产出的东西看着挺像回事,真跑起来全歪了。回头我一条条追问那些含糊的地方,补出十几个「大家都懂」的隐含条件,AI 才跑通。
- 我在执行 TDD ( 先写测试再实现(Red→Green→Refactor)) 虽然准确但并不高效,现在很多公司使用公司本地的部署的本地模型,性能和速度都大打折扣,开发速度慢,pm 催上线,所以这种模式并不适合紧急需求,当面对紧急需求时我需要对llm表达准确,让llm理解我的需求,并给出准确的执行方案,让代码快速落地。
这两件事之后我越来越确信:AI 时代最贵的不是写代码,是把话说到不需要再猜的程度。下面用信息论和 Cynefin 框架,把「说清楚」这件事拆开算一算。
一、先量化:信息传递到底在传什么
香农熵(Shannon Entropy)给出的是一个系统「不确定性」的度量:
H(X)=−i=1∑np(xi)log2p(xi)
- X:一个离散随机变量,比如某条需求的可能状态、某段代码的缺陷分布;
- p(xi):第 i 种状态出现的概率,且 ∑p(xi)=1;
- H(X):单位为 bit,表示「完整描述这个随机变量所需的平均信息量」。
直观理解只有两种情况:
- 结果唯一确定( p=1)时, H(X)=0,一句话都不用传;
- n 种结果等概率( p=1/n)时, H(X)=log2n 取到最大,不确定性最高,要传的信息也最多。
二、熵和 Cynefin:四域就是四种不确定性
这里要先纠一个常见的误解:知识本身不产生熵,熵描述的是接收方对这件事还剩多少不确定 。同一段业务逻辑,老员工眼里 H≈0;同一个新人,或者同一个 AI,眼里可能接近 log2n。
所以文档和流程真正干的事,是把接收方的条件熵 H(X∣D) 往下压------降的是那一侧的不确定性,世界本身没变。隐性知识难编码,就难在这里:它没写成 D,于是 H(X∣D) 一直悬在高位。
它和 Cynefin 四域正好一一对上,按不确定性从高到低排:
| 模式 | 接收方的熵 | 含义 |
|---|---|---|
| 混沌模式(Chaotic) | 最高 | 因果只有事后才看得清,事前任何推断都不比瞎猜强, H 接近 log2n |
| 复杂模式(Complex) | 高 | 必须靠外部知识(RAG、人做决策)补进来,才有条件降熵 |
| 庞杂模式(Complicated) | 中 | 知识是够的,链式推理能一步步把熵压下去 |
| 清晰模式(Simple) | 低 | 归类即可响应, H≈0 |
在 AI 工作流里,LLM 的核心价值之一就是降熵:把混沌、复杂模式下高熵的黑盒,经由感知---分析---RAG 拆成低熵的子场景。而那些说不出口的经验,靠 review、异常 Agent、性能 Agent 的反馈回流,慢慢从隐性变成显性。所谓信息传递重要,本质上就是在持续压低协作和认知中的不确定性。
三、不可言说知识:经验为什么传不过去
你脑子里的经验是什么?是能写成教程的东西,还是只能自己上手摸出来的东西?
后者才是大部分。举个例子:两个 API 都能实现同一个功能,但内部原理不一样,某个场景下其中一个就是更合适;或者同一个 API 在测试环境和生产环境表现完全不同。这类判断没人写进文档,也没法写------它是在一次次踩坑里长出来的。这就是不可言说知识(tacit knowledge),它只存在于你的大脑中,文字和语言只能传个形状,传不了内核。
我是个篮球爱好者,三分球一直投不到。看了很多教程,照着做还是不进。后来一个教练说:教程只是理论,发力和动作得自己去领会,做不到言传身教。那个「言传身教不了」的部分,就是不可言说知识,也是能力的最终体现。
放到 AI 工作里,同一个道理:教程和文档解决不了的问题,只能靠经验和直觉。经验不是读出来的,是试出来、总结出来的(上篇文章实现的小模型》其原理就是"猜")。
那么问题来了------既然传不了,怎么验证它被正确理解?在 AI 时代,答案是 review 和有意识的前置理解。让人把隐性的判断显性化一次,再让 AI 复述、执行,从结果反推它有没有真的接住。
为什么开发测不出自己的 bug,测试可以
两个原因。
一是惯性思维。代码是自己写的,脑子里那条路径已经跑了几十遍,看代码时眼睛会自动跳过不符合预期的部分。
二是知识类型不一样。开发拿的是 PRD,做的是**显性知识(explicit knowledge)**的实现;测试除了验显性功能,还得做异常操作、冒烟、边界值------这些流程是隐形的、不言说的。测什么、怎么测,靠的是经验里那套「哪里容易出问题」。
在 AI 结对编码的模式下,这件事可以工程化:建一个性能 Agent ,专测代码的性能表现;再建一个异常 Agent,专测异常处理能力。这两个 Agent 本质上就是把不可言说知识编码成可执行的检查项------测试结果回流给开发,也就完成了不可言说知识的传递。
四、需求转化:四个域,四套处理链路
Claude Code 这类 Agent 干活时,走的是一条固定的链路:信息感知 → 信息分析 → 信息决策 → 信息执行 → 信息反馈。区别只在于,每一步落在哪个域里,处理方式完全不同。
Cynefin 分成四个域,另外正中间还有一块「失序(Disorder)」,意思是还判断不出它属于哪个域:
清晰模式(Simple):归类就够了
例:归档一份格式已知的文档。
链路:感知(sense)→ 归类(categorize)→ 响应(respond)。
知识充沛、逻辑清楚,剩下的只是执行。这类活儿交给规则或一次 LLM 调用就完事,别多想。
庞杂模式(Complicated):拆开来分析
例:写一个按指定规则排序的函数。
链路:感知(sense)→ 分析(analyze)→ 响应(respond)。
知识是充分的,也能立即取用,但不能一步到位------需要链式推理,一步一步来:先感知要改什么,再分析怎么改,最后落手改。这一步最像 CoT(Chain of Thought,链式思维):链条拉得越长,越容易在中途跑偏,所以通常要配上校验。
复杂模式(Complex):先试探再收敛
例:需求分析、需求拆解、plan 模式。
链路:探索(probe)→ 感知(sense)→ 响应(respond)。
知识不充分,得先调外部知识补进来------这里的外部知识不只是 MCP,还包括人的决策、以及前面说的不可言说经验的试探。补完再分析,生成 TODO List,然后执行、看反馈。
混沌模式(Chaotic):先动手止血
例:偶现的、没人能定位的 bug。
链路:行动(act)→ 感知(sense)→ 响应(respond)。
因果关系是未知的,只能先动一下看反应,从反应里反推原因。找到原因之后,把它拆成子需求,让子需求重新落进清晰、庞杂或复杂模式------这才是混沌域唯一的出口。
五、LLM 怎么参与模式转化
同一个模型放进不同域,表现差得远。清晰模式下它又快又准;复杂模式要多轮检索加追问,时间和成本翻几倍;至于混沌模式,我基本不交给它------它不知道因果,又特别擅长给一个听起来合理的答案,这种猜测比没有答案更危险。
所以真正的工作不是「让 LLM 解决混沌问题」,而是人先把它从混沌里拽出来,拆成清晰或庞杂的子问题,再交给模型。
分解场景:把黑盒拆成白盒
问题卡在混沌或复杂模式,通常都是因为它说不清楚。这时候要做的是切场景,切成一堆尽量独立的子场景。
麻烦在于,子场景往往也切不动------因为它对我们同样是黑盒。这里的做法是借 LLM 来识别黑盒、打开黑盒,再结合自己掌握的上下文把白盒信息串起来,用 CoT 的链式逻辑把复杂问题逐层简化,最终落到清晰模式。
这个过程很少一次做完,多数时候是循环的:切开、看不清、再切。它真正提升的是人「信息感知 → 信息分析」的速度,以及验证一个理解对不对的速度。
六、时间维度:任务是有生命周期的
还有一条特别容易被忽略的拆解线索------时间。
任何需求都有生命周期:什么时候被触发、什么时候装载上下文、什么时候真正执行、什么时候卸载。沿着时间轴切一遍,复杂度会自己摊开,而且 LLM 对时间信号格外敏感。
任务生命周期(UML 状态机)
需求不是一次性输入的,而是有状态、有阶段、有触发时机的实体。把它画成状态机,复杂度就自然沿时间轴展开:每个状态只需要关心「我现在在哪一阶段、下一步被什么事件触发」,不必一次性理解全部细节。
关键时间锚点
| 时间锚点 | 状态跃迁 | 工程含义 | 该交给谁 |
|---|---|---|---|
| 何时触发 | 待触发 → 装载中 |
定时器 / 事件 / API / 人工,四类触发源决定 SLA | 调度层 + LLM 判别触发条件 |
| 何时装载 | 装载中 → 初始化 |
注入哪些上下文、工具、权限,是 Token 成本第一大头 | 上下文工程 / Skill / MCP |
| 何时执行 | 初始化 → 执行中 |
进入 LLM 推理主战场,时延与 Token 双高 | LLM + 工具调用 |
| 何时校验 | 执行中 → 校验中 |
自检 / 测试 / 性能 Agent 介入,构成反馈闭环 | 异常 Agent、性能 Agent |
| 何时重试 | 执行中 ⇄ 异常超时 |
退避重试,避免混沌模式下的盲目重复消耗 | LLM 判断 + 重试策略 |
| 何时卸载 | 完成 / 失败 → 卸载 |
释放资源、清理上下文、留最小快照,任务边界即上下文边界 | 生命周期管理 / 记忆沉淀 |
为什么 LLM 能识别时间维度
- 文本里天然带时间信号 :「每天 8 点」「登录后」「支付成功后」「超时 3 秒」「任务结束前」,这些词本身就是 UML 里的触发事件(trigger),不需要额外标注。
- 可以抽成有向状态转移:LLM 只要能回答「上一个状态结束后必然进入哪个状态」,状态机就抽出来了;描述越确定(越靠近清晰模式),抽取越准。
- 复杂度和时间尺度强相关 :
ms级(初始化、工具调用)确定性高;s级(推理生成)是成本主战场;min~h级(重试、长任务)需要记忆支撑;d级(需求等待、经验沉淀)属于隐性知识。把成本集中投在高熵的时间段,是 Token 治理的第一原则。 - 状态即上下文边界:明确「什么时候卸载」,就等于明确了上下文该在哪里截断------这和 SubAgents「任务结束即销毁」的策略同构,时间维度是 Agent 生命周期管理的理论依据。
和其他两个维度的关系
时间维度解决「何时做 」,另外两个维度一起约束「做什么」:
- 结构维度(下一节要讲的代码架构):任务的组成与依赖关系,决定拆分边界;
- 场景维度(前面的「分解场景」):任务落在 Cynefin 哪个域,决定交给 LLM 还是人。
三者交叉,才能把一个混沌需求切成可独立执行、可独立卸载的子任务。
七、回到日常开发:任务到底怎么拆
前面讲的是从庞杂模式到清晰模式的转化,偏理论。现在换个视角,从日常开发的两个入口来看:产品文档、现有代码结构。
以产品文档为入口
AI 时代的文档,必须比传统文档更清晰、更细致。
PRD 是任务拆解的第一入口,也是 AI 时代落差最大的一环。传统 PRD 是写给「有上下文的人」看的:读者是同一个团队的 PM、前端、后端、测试,共享业务背景、历史包袱和口头共识,于是文档可以大量留白------「按老逻辑处理」「参考 A 页面」「异常情况兜底即可」。这些白被人的经验自动补齐了。
AI 没有这段共享上下文。它只拥有你写给它的信息 。文档里没写的,模型只能用概率去猜;猜出来的结果读起来很合理,跑起来不可用。用前面的说法:传统文档把降熵的成本转移到了读者侧 (读者有共同背景,凭经验补全),AI 时代必须把降熵做在作者侧(写进文档)。
从文档到任务的拆解流水线大致是这样:
对应到手上的动作:
- 先标不确定点,别急着拆任务:通读一遍,把所有「大概 / 类似 / 参考 / 兜底」标出来,这些词就是歧义的定位标记。
- 结构化抽取:把文档拆成四类原子------实体(Entity)、状态(State)、规则(Rule)、动作(Action)。拆不出来的句子,说明还没想清楚。
- 追问黑盒:每个未定义的量都是一次提问机会。这正是复杂模式吃掉人力的地方,也是 LLM 最能补位的地方------它可以把一个模糊问题展开成 10 个待确认的具体问题。
- 与现有代码对齐:文档规则必须能映射到现有数据结构和接口。映射不上,要么文档错,要么代码缺,两种都得在动手前暴露。
- 一规则一任务:拆解粒度以「一条可判定规则 + 一组用例」为单位,而不是以页面为单位。页面是给人看的,规则是给执行者用的。
- 可判定性自检:如果一条任务写不出断言,它就不是任务,只是一句愿望,退回第 3 步。
以前的 PRD 面对的是人,现在面对 AI,得把故事线写完整。我一直觉得 AI Vibe Coding 的终点,是产品自己写代码。
以代码结构为边界
产品文档解决「做什么」,现有代码结构解决「在哪做、改成什么样」。
这两件事是同一个问题的两面:文档给出行为契约,架构给出上下文边界。我们给 LLM 的记忆文件里通常就是项目架构,帮它快速定位代码结构。Agent 改错地方,多数时候不是能力问题,而是它不知道哪块代码属于这个功能。
架构为什么能当上下文用?
因为代码结构不是物理目录,而是已经被人切好的一组功能上下文(Bounded Context)。模块边界、依赖方向、共享类型,本质都是「这里的变更不会影响那里」的承诺。对 LLM 来说,这些边界就是预先切好的上下文切片:
- 不用读全仓库,只需读到相关的子图;
- 任务天然落进某个模块,不用每次重新探索;
- 改动范围有物理边界,越界产生的是编译错误,而不是线上事故。
所以「以代码结构拆任务」的核心动作是收敛:从整个仓库收敛到模块,再到文件,最后到函数。每一跳都必须有证据,否则就是在猜。
记忆文件是地图,不是百科。
CLAUDE.md / AGENTS.md 这类记忆文件最常见的误区,是把它写成项目百科。它实际的职责只有一个------路由:用最少的 Token 把 Agent 引导到正确的文件和正确的约束前。所以里面该放「索引入口」,不该放「知识正文」。
| 该写 | 不该写 |
|---|---|
| 目录树 + 每个模块一句话职责 | 每个类的实现细节 |
| 关键入口文件(启动、路由、数据访问) | 完整 API 文档 |
| 依赖方向与禁止的跨层调用 | 靠读代码就能得到的结论 |
| 常用命令(构建、测试、单模块验证) | 历史背景与演进故事 |
| 命名约定与目录放置规则 | 与代码不同步的设计稿描述 |
一份能用的记忆文件大致长这样:
text
## 架构
src/
├── modules/order/ 订单域:下单、支付回调、状态机
├── modules/coupon/ 优惠域:券模板、发放、核销(纯计算,不依赖 order)
├── modules/user/ 用户域:账号、权限、Profile
├── shared/ 跨域共享:类型、错误码、埋点(只增不改语义)
└── infra/ 基础设施:DB、缓存、MQ 客户端
## 依赖方向
modules/* → shared,modules/* → infra
禁止:modules/a → modules/b 互相直接 import;跨域一律走 shared/contracts
## 命令
pnpm test modules/order 单模块测试
关键一条:依赖方向必须写成禁令。「允许什么」Agent 可能记不住,「禁止什么」它会遵守,因为越界能立刻被验证。
从架构到任务,四个收敛动作:
- 定位(改哪里):把文档里的实体和状态映射到模块。映射不上就是架构缺口------要么新增模块,要么复用现有模块,必须在动手前裁决。
- 锚定(从哪进):找到入口文件------路由注册、事件订阅、定时任务、对外接口。Agent 从入口向内读,比从目录向外扫快一个数量级。
- 算半径(还会动哪):改一个函数,会牵连调用方、被调方与共享类型。半径内的文件要一并列入任务范围,半径外要明确写禁止。
- 划边界(不许动哪) :把
shared/、公共组件库、其他域目录写成冻结区。没有冻结区的任务卡,Agent 很容易「顺手优化」出一堆不相关的 diff。
产出的任务卡应当自带定位信息,而不是只写需求:
text
任务:订单页优惠券叠加计算
目标模块:modules/order(rules/coupon-stacking.ts 新增)
参考契约:modules/coupon(复用 calcDiscount,禁止直接写计算逻辑)
允许改动:modules/order/**、shared/contracts/coupon.ts
禁止改动:modules/coupon/**、shared/errors.ts(错误码复用现有)
验收:pnpm test modules/order -- coupon-stacking
这张卡的信息量,抵得上一份小型架构说明,但它比任何描述都精确------因为路径和禁令都能被工具校验。
人写代码的时候很会偷懒:不写注释、不用设计模式,可读性自然变差。换成 AI 写,这些偷懒的地方就得提前写进约束里。它会越写越快、越写越好,程序员最后大概真的会变成流水线上的操作工------只不过这条流水线的上游,得由我们自己把话说清楚或者把话转述清楚。