上一篇文章讨论了一个观点:软件开发真正管理的对象,也许从来都不是代码,而是知识。AI 的出现没有让知识变重要——它只是让我们不得不正视这个一直存在的事实
但文章写完之后,一个更基础的问题一直在我脑子里转:
当我们说「知识」的时候,到底在指什么?
代码算知识吗?业务逻辑呢?架构设计呢?那些只存在于老员工脑子里的历史决策呢?这些看起来完全不相关的东西,为什么都叫「知识」?
这篇文章把这个问题拆开来看
一个更完整的视角
在进入正题之前,我想先给出一个整体框架。这其实是我后续一系列思考的基础:
我认为 AI Native 软件开发的核心链路是这样的:
1 | Knowledge → Context → Decision → Specification → Execution |
每一环回答一个不同的问题:
| 环节 | 核心问题 | 产出 |
|---|---|---|
| Knowledge | 系统中存在哪些信息? | 全部知识空间 |
| Context | 当前任务需要哪些信息? | 完备且最小的信息子集 |
| Decision | 基于这些信息,做什么选择? | 工程决策 |
| Specification | 决策如何转化为可执行的描述? | 精确的变更规格 |
| Execution | 如何把规格变成可运行的代码? | 代码、测试、部署 |
这篇文章聚焦第一环:Knowledge。先把「知识是什么」的地基打好,后面的 Context 构建、Decision 质量才有了讨论的基础
那么回到问题:软件开发中,究竟存在哪些类型的知识?
一、代码:最特殊的知识形态
先说代码。很多人会直觉地把代码排除在「知识」之外——「代码是产物,不是知识」。但这不完全对
代码是对系统运行逻辑最精确、最完整的描述。 不管文档写了什么、架构图怎么画、老员工怎么描述,最终系统真正在做什么,只有代码说了算。从保真度的角度,代码是最可靠的知识
但它有一个独特的矛盾属性:代码作为知识,是不自足的
你知道它做了什么。但你看不到:
- 它为什么这样做,而不是那样做;
- 它当初面临什么约束条件;
- 改了它之后会有什么连锁反应。
代码既是知识本身,也是检索其他知识的索引。你打开 RefundService.java,看到一段逻辑,然后发现它调用了一个你不认识的工具方法——于是你跳转过去读。在 PaymentUtil 的几百行代码深处,你看到了那段两年前加上去的幂等校验。你隐隐觉得它似乎不能删,但你不知道为什么
代码把你指向了它自己之外的知识。而那个知识,在代码里并没有写
二、业务逻辑:被打散的知识
一个完整的业务规则,在代码里几乎从来不会完整地出现在同一个地方
举个例子,一个看起来简单的退款业务,完整规则其实是这样的:
退款流程:财务审核通过后,按原支付渠道返回金额;同时需要扣回已发放的积分、作废已使用的优惠券、通过短信或推送通知用户。超过 30 天的订单不支持退款。VIP 用户退款时免扣运费
但在代码里,这段规则被分散在了:
RefundService.refund()——主流程PointsService.deduct()——积分扣回CouponService.invalidate()——优惠券作废NotificationGateway.send()——消息通知- MQ Consumer——异步触发积分扣回,处理可能的消息丢失
- 定时任务——补偿那些 MQ 消息没消费成功的场景
- SQL 视图——运营报表里统计退款数据的口径
这些分散的片段加在一起,才是完整的业务逻辑。但没有任何一个文件能让你一次性看到这个完整的规则。
这就是业务逻辑作为一种知识的特点:它在代码里,但同时被代码结构打散了。 你无法直接读到业务逻辑——你需要从多个代码片段中逆向还原它
如果说业务逻辑是被打散的——散落在多个文件里,但至少还在代码里——那架构设计更进一步:它几乎不在代码里。
三、架构设计:隐形的结构约束
架构知识是一种特殊的存在。它不参与具体逻辑的执行,但它决定了一个修改会波及多远。
比如这些信息:
- 退款服务和支付服务之间通过 MQ 解耦,因为支付回调有超时限制;
- 积分服务不直接访问数据库,必须通过积分服务暴露的接口;
- 系统按 DDD 拆分为订单上下文、支付上下文、用户上下文,跨上下文调用只能走领域事件。
部分架构意图可以从代码结构里看出来——模块的目录划分、接口的定义方式、调用的方向——但更多的东西是隐形的:
- 为什么这个服务选 MQ 而不是 RPC?代码里看不出来。
- 这个拆分方式是刻意的,还是历史偶然形成的?代码里看不出来。
- 哪些模块边界是硬约束不能跨越,哪些只是习惯?代码里看不出来。
架构知识一个很麻烦的地方是:你违反它的时候,代码不会报错。你直接在订单模块里查了用户数据库的表,编译器不会提醒你跨越了上下文边界,测试可能也能过。但三个月后,当用户服务独立拆分出去的时候,这里就会变成线上故障
架构作为知识,它的主要存在形式是约束。而这种约束在代码中,是看不见的
四、历史决策:约束的来源
这可能是六种知识里最被低估的一种。
什么是历史决策?就是那些解释「为什么这里是这个样子」的信息:
- 三年前为什么选择 Redis 做幂等,而不是用分布式锁中间件;
- 支付回调的幂等校验为什么写在
PaymentUtil里,而不是在RefundService里; - 这条 SQL 为什么有
force index提示——因为两年前的慢查询事故; - 这个 Event 为什么不能改字段——因为下游还有三个系统在消费。
这些信息几乎完全不在代码里。它们存在于:
- ADR(架构决策记录)
- 事故复盘文档
- PR 讨论
- 工作聊天记录
- 老员工的记忆
而这恰恰是最危险的知识缺口。因为你看到的几乎每一段代码,都是多个历史决策叠加之后的结果——几乎没有哪一段不带着历史包袱。你看一段代码觉得它写得很奇怪——它可能确实写得很奇怪,也可能是在解决一个你完全不知道的历史问题。你分不清,因为代码不告诉你它为什么长这样
历史决策的价值,不仅仅在于「避免踩坑」。更重要的是:它让你能判断一个约束是否仍然有效
一个三年前基于当时条件做出的合理决策,今天可能已经不再必要。但没有历史记录,你不知道这个约束是「仍然有效的硬约束」还是「可以打破的历史遗留」。你只能选择保守策略——不敢改。这本身就是一种隐性的成本
五、需求:代码的原始输入
需求是代码的起点。但需求有一个麻烦的地方:需求本身不留在系统里
代码是需求的实现,但不是需求的记录。你看一段逻辑:
1 | if (order.getAge() > 30) { |
代码告诉你「超 30 天的订单不能退款」。但它不告诉你这个 30 天规则从哪里来、谁定义的、当时的需求文档在哪、以后会不会改成 60 天
更重要的是——需求是会变的。一个功能经过了三个迭代、五次需求变更之后,最终代码里呈现的逻辑,可能已经和最初的需求文档完全不一样了。但代码不会告诉你它经历了什么变化,它只呈现最终的结果
因此,需求作为一种知识,面临一个独特的问题:它既是代码的源头,也是最早被遗忘的。几个月后你回来看一段逻辑,想搞清楚「为什么有这个判断」,原始需求大概率已经找不到了
六、基础知识:稳定的通用层
最后一种知识和前面五种都不一样——它不在任何项目里,却决定了你能否正确理解任何一个项目:
@Transactional在同类方法自调用时不会生效;- Redis 的
SETNX可以实现分布式锁,但要注意锁续期和脑裂; - 消息队列有 at-least-once 语义,消费端必须幂等。
类似的例子无穷无尽——每个技术选型、每个中间件、每个框架都携带着这样的「注意事项」,写在了文档里,但没有写在任何项目的代码里
这类知识的特点是:相对稳定,不随具体项目变化,但决定了你能否看出代码里的隐含风险
看到一个方法上有 @Transactional,如果你不知道自调用失效的规则,你就不会意识到那段逻辑在某个调用路径上没有事务保护
这层知识,AI 其实是不缺的。大模型在训练过程中已经学习了大量这类基础知识。但这里有一个微妙的点:AI 不缺基础知识,但它不会自动将这些知识与当前项目绑定
它知道 @Transactional 在自调用时会失效,但它不知道这个项目里的哪些方法正依赖着那个会失效的写法。它知道 MQ 有 at-least-once 语义,但它不会主动标注出这个项目里哪个 Consumer 没有做幂等处理
回到第一篇文章里那个例子——AI 删掉了 PaymentUtil 里的幂等校验。它不是不知道幂等的重要性,它是不知道那段代码就是幂等的实现。通用知识和具体代码之间,有一条它跨不过去的鸿沟
这就是基础知识独特的困境:它是唯一不需要「捕获」的知识——它已经以模型参数的形式存在了。但它需要的不是捕获,而是绑定——在具体项目的具体代码位置,激活对应的基础知识。这件事,目前的 AI 还做不好
到这里,六层知识都过了一遍。但停下来想一下:这六层真的都是同一类东西吗?
前五层——代码、业务逻辑、架构设计、历史决策、需求——它们有一个共同特征:是项目特定的。它们随项目不同而不同,随项目演进而变化,其管理方式是从隐式到显式的转化——把脑子里想的、代码深层里藏的、聊天记录里埋的知识,挪到可以被检索和消费的地方
但基础知识不同。它不是项目特定的,也不需要被「捕获」——它已经在书里、在文档里、在 AI 的训练数据里。它需要的不是文档化,而是在正确的位置被激活
两层知识的获取、维护、传递方式完全不同。但它们有一个共同点:缺失任何一层,决策都会出错。区别只在于——前五层的缺失是因为知识没有被外化,基础知识的缺失是因为通用知识和具体代码没有建立连接
各层知识与决策的关系
六层知识拆完之后,回头看一下整个链路。
这些知识之所以重要,不是因为它们需要被「管理」,而是因为:不同类型的工程决策,依赖不同类型的知识
回到上一篇文章里提过的四个核心问题:
| 决策类型 | 核心问题 | 主要依赖的知识 |
|---|---|---|
| 定位决策 | 改哪里? | 架构设计、业务逻辑、需求 |
| 方案决策 | 怎么改? | 历史决策、基础知识、代码模式 |
| 影响决策 | 影响谁? | 架构设计、依赖关系 |
| 验证决策 | 如何验证? | 历史事故、边界条件、需求 |
这不是一个精确的一一对应,但它说明了一个关键点:如果你缺了某一层知识,对应的决策就会出问题
缺历史决策 → 你不知道某些约束不能打破 → 方案决策出错
缺架构设计 → 你不知道修改的波及范围 → 影响决策遗漏
缺基础知识 → 你意识不到代码里的隐藏风险 → 所有决策都可能出错
知识分层的意义不在于分类本身,而在于:它解释了为什么不同的人(或 AI)面对同一个任务,会做出完全不同的决策。因为他们持有的知识集合不同——有的人知道这段代码的历史,有的人不知道;有的人知道 MQ 有 at-least-once 语义,有的人不知道
写在最后
这篇文章试着回答了「软件开发中的知识到底是什么」这个问题。答案是:
软件开发中的知识,是做出正确工程决策所需的全部信息。它不是一个单一的东西,而是分布在不同载体上的六个层次——代码、业务逻辑、架构设计、历史决策、需求、基础知识。
代码是其中最特殊的一层。它既是知识的一部分,又是 Execution 的最终产物,同时还是发现其他知识的入口。回到开头的链路:
1 | Knowledge → Context → Decision → Specification → Execution |
代码既是 Knowledge 的起点(通过代码发现其他知识),也是 Execution 的终点(最终产出代码),链条在这里闭合。
但 Knowledge 本身并不能被直接消费。一个具体的开发任务,不需要(也不可能)吃掉全部六层知识。所以下一个问题自然就是:
如何从浩大的 Knowledge 空间中,为当前任务选出那个完备且最小的子集?
这就是 Context 要解决的问题。下一篇继续。
这是 AI Native 软件开发系列的第二篇。第一篇「软件开发真正管理的是代码,还是知识?」讨论了软件开发管理的对象是知识而不是代码,这一篇定义了「知识是什么」。如果你有不同的理解或者实践经验,欢迎一起讨论。