在引入 Claude Opus 4.6 作为团队的主力辅助模型后,我观察到一个有趣的现象:模型的推理能力确实强悍,但这并不意味着我们可以忽视代码质量。
相反,一个残酷的现实逐渐浮出水面:AI 的效能上限,依然被工程代码的质量下限所锁死。
以前我们认为“烂代码”会让 AI 读不懂;现在的情况是,凭借 Opus 强大的上下文窗口和推理能力,它确实能读懂那些逻辑混乱的代码,但代价是消耗惊人的 Token。为了理清一个隐晦的全局状态,它可能需要加载几十个文件的上下文。这不仅导致成本飙升、响应变慢,更重要的是,当上下文过载时,模型的注意力会被稀释,幻觉(Hallucination)的风险依然存在。
因此,我们需要重新审视代码规范,建立一套AI友好的工程标准。
一、 定义“AI 友好”的核心公式
在 AI 辅助编程日益普及的今天,代码的受众已不再仅仅是人类工程师。基于对 IM 模块的实际分析,我认为衡量代码是否“AI 友好”,有一个粗略但核心的公式:
AI 友好度 ≈ (任务相关有效信息) / (完成任务需加载的总 Token)
简单来说,就是信噪比。我们要做的所有架构优化,归根结底都是为了优化这个比值:
- 减小分母(少消耗 Token):小文件、少依赖、集中配置。
- 增大分子(信息密度高):好命名、强类型、声明式。
- 减少推理成本:显式依赖、减少宏定义。
二、 显式依赖与 Token 效率:用架构换取成本
关于单例模式(Singleton)或隐式全局状态(Global State),过去我们常说它是“AI 的噩梦”。严格来说,这个说法在 Claude Opus 4.6 面前已经不完全准确了。如果我们将整个项目的代码都喂给模型,它确实有能力追踪到状态变更。但问题在于:值得吗?
1. 依赖注入 vs. 服务定位器(Service Locator)
在我们的工程中,经常看到类似 findServiceWithProtocol: 的写法。
- 反面案例(服务定位器):
// IMChatListCellContainerView.m
id cameraService = [self findServiceWithProtocol:@protocol(IMChatListCellCameraServiceProtocol)];
return [cameraService cameraDidShow];
人类视角:这很好理解,我要找个相机服务,不用管它从哪来。
AI 视角:这是个黑盒。cameraService 到底是谁?为了确定它的具体实现,AI 无法通过静态分析直接跳转,必须在全局范围内搜索谁注册了这个 Protocol。这不仅增加了 Token 消耗,还容易搜到错误的实现(比如搜到了测试用的 Mock 类)。
- 正面案例(依赖注入):
优先使用构造器注入。当依赖在 init 签名中清晰可见时,AI 仅需读取头文件就能理解该类的能力边界,无需进行昂贵的全局搜索。
2. 依赖关系的可追溯性(Context Locality)
AI 理解一段代码时,如果需要跳转到大量其他文件才能理解语义,则每次跳转都是额外的 Token 开销。
- 反面案例:
IMModuleService.m 拥有 43+ 个 import,依赖了 Compliance、FeedBizUI、TabBar 等十几个模块。AI 要理解这个文件的任何一个方法,可能需要加载数万 Token 的外部接口定义。
- 标准:
单文件 import 数建议控制在 10-15 个以内;跨模块依赖应通过清晰的协议/接口层隔离。
三、 强类型与语义清晰度:提供确定性的决策依据
1. 类型系统表达意图(Type-Driven Design)
强类型系统让 AI 可以通过类型签名推断大量语义,减少阅读实现代码的需要。
- 正面案例:
在Inbox Cell的 Swift DSL 节点系统中:
typealias ContentNode = StackNode
typealias CenterNode = StackNode
AI 看到 CenterNode { ... } 就能理解这是 Cell 中间区域的容器,无需查看 StackNode 的底层实现。
- 反面案例:
ObjC 中大量使用的 id 类型。
- (id)findServiceWithProtocol:(Protocol *)protocol { ... }
返回 id 意味着 AI 无法静态推断实际类型,必须追踪调用链才能确定,这又是对 Token 的极大浪费。
2. 命名自解释与注释一致性
- 命名:
IMChatListCellUnreadUtil.canShowUnreadCountWithModel:这种命名是完美的,AI 不需要看实现就知道它的输入、目的和返回类型。而像 p_2023ReversalStrategy 这种命名,AI 根本无法猜透“2023年的反转策略”到底是指什么业务,必须阅读实现。 - 注释:我们在 IMChatListCellContainerView.m 中发现了中英文混合的注释。这会增加 AI 的解析负担。标准是:同一项目统一使用一种语言,要么全中,要么全英。
四、 结构可预测性:人类与 AI 的认知差异
这里我们需要探讨一个有趣的点:有些代码对人类是友好的,但对 AI 却是极不友好的。
1. 宏(Macros)与代码生成
- 人类视角:像
ADD_NODE_WITH_ID这样的宏,或者GET_PROTOCOL(),对人类来说不会增加理解成本。我们习惯了忽略其内部实现,直接将其视为一个“指令”。 - AI 视角:这是灾难。AI 看到的是源码,宏定义展开后的代码 AI 看不到(除非你把预处理后的文件喂给它)。对于
IMTemplatePBNBase.pbobjc.m这种 2500 多行的自动生成代码,AI 更是难以有效理解其设计意图。
标准:尽量用 Swift 的语言原生特性(如 Result Builders)替代宏;对于生成代码,应引导 AI 去读 Schema 文件而非生成物。
2. 声明式 vs. 命令式
- 正面案例(Swift DSL):
override public class func buildNodeTree(...) -> Node? {
return Node(.root) {
ContentNode {
Node(.avatar)
CenterNode {
...
}
}
}
}
声明式代码直接描述“是什么”。AI 一眼就能理解整棵 UI 树的层级结构,甚至可以直接“画”出布局。
- 反面案例(ObjC 旧架构):
RETURN_NODES({
// top priority
if (chatModel.isStranger || chatModel.isFiltered) {
ADD_CONTAINER_NODE_WITH_2ID(@"messageStatePriorityContainer", IMChatListCellComponentIDPriorityContainer, {
ADD_CONTAINER_NODE_WITH_ID(IMChatListCellComponentIDMessageStateTextContainer, {
ADD_NODE_WITH_2ID(@"message state in container", IMChatListCellComponentIDMessageStateText);
ADD_NODE_WITH_2ID(@"messageStateText_time_dot", IMChatListCellComponentIDTimeLabelDotView);
ADD_NODE_WITH_2ID(@"messageStateText_time_label", IMChatListCellComponentIDTimeLabel);
});
ADD_NODE_WITH_2ID(@"message state", IMChatListCellComponentIDMessageStateText);
});
} else {
...
}
通过一系列 if-else 和宏指令逐步构建 UI。AI 需要模拟执行整个流程,追踪每一步的副作用(Side Effects),才能在脑海中还原出最终的结构。这消耗的推理算力是巨大的。
五、 对抗“上下文失效”与“Token 浪费”
1. 单文件体积适中(Token Density)
这是最容易被忽视的性能杀手。
- 反面案例:
IMConfigManager.m 高达 4,628 行,承载了 IM 模块几乎所有配置逻辑。当 AI 需要修改其中一个配置项时,必须加载整个 4000+ 行的文件,但实际只需要其中 5-10 行的上下文。这意味着 99% 以上的 Token 被浪费了。
- 正面案例:
IMChatListCellUnreadUtil.m 仅 78 行,职责单一。AI 处理它只需消耗极少 Token,且不存在无关信息干扰。
- 标准:
单文件建议控制在 300-500 行以内。超过 1000 行的文件应考虑按职责拆分。
2. 避免散落式的条件分支
在 SocialInboxCellViewModelBuilder.swift 中,同一个 AB 开关在多处出现。这意味着 AI 必须完整读取整个文件,甚至跨文件扫描,才能收集全部分支逻辑。
标准:AB 测试判断应集中化——最好在一个配置层将实验结果“解析”为明确的行为参数,下游代码只消费参数。
3. 架构共存期的文档化
当新旧架构共存时(如Inbox Cell节点树的 ObjC/Swift 双架构),如果没有清晰的指引,AI 很容易“精神分裂”。
本项目中 inbox_cell_node_tree_architecture.md 是一份出色的文档,它明确记录了分叉点和文件索引。这让 AI 能快速判断应该修改新架构还是旧架构,避免了在旧代码堆里打转。
六、 总结
回顾我们的工程演进,从旧的 ObjC 宏架构转向新的 Swift DSL 节点树架构,这本身就是一个**“代码演进方向与 AI 友好度对齐”**的绝佳案例。
AI 友好的代码设计 = 极度解耦(降低 Token 消耗) + 强类型与声明式(提高信息密度) + 显式依赖(降低推理成本) + 语义锚点与文档(防止上下文失效)。
在 AI 时代,写代码不再只是为了让机器跑通,更是为了让你的 AI Copilot 能看懂。把代码库从一个单纯的“指令集”,升级为一个**“AI 可读、可理解、可追踪的知识库”**,这将是我们提升研发效能的关键一战。