1. Cognitive load is what matters (minds.md)
认知负荷才是关键
核心观点
- 许多开发中的流行术语和最佳实践之所以失败,是因为它们基于主观想象而非真实约束。认知负荷是开发者完成任务时大脑需要处理的信息量,它是一个根本性的人类限制,而非抽象概念。
- 代码阅读和理解的时间远多于编写时间,因此应尽量减少代码中嵌入的不必要的认知负荷。
认知负荷的类型
- 内在负荷:由任务本身固有难度产生,无法减少,是软件开发的核心。
- 外在负荷:由信息呈现方式(如作者的个人风格)产生,与任务无直接关系,可以大幅减少。本文重点讨论如何减少外在认知负荷。
增加不必要认知负荷的常见问题及改进方法
复杂条件语句
- 问题:复杂的
if条件迫使开发者在工作记忆中同时保持多个条件。 - 方法:引入具有清晰名称的中间变量,分解条件。
- 问题:复杂的
深层嵌套的
if语句- 问题:嵌套层级增加记忆前提条件的负担。
- 方法:使用“提前返回”模式,让主逻辑(happy path)更清晰,无需记住所有前提条件。
继承滥用
- 问题:多层继承链迫使开发者理解多个类层次的逻辑,修改父类可能影响子类。
- 方法:优先使用组合而非继承。
过多的浅层模块(方法/类/模块)
- 问题:过度遵循“方法不超过15行”等规则,导致产生大量接口相对复杂但功能简单的浅层模块。理解项目需要记忆大量模块及其交互关系,违背人类线性思维习惯。
- 方法:推崇深层模块——接口简单,内部功能强大。关键原则是信息隐藏。复杂的实现细节应隐藏在简洁的接口之后(如Unix I/O接口)。
滥用“单一职责”原则
- 问题:对“单一职责”理解模糊(如“只做一件事”),可能导致创建职责不明确的浅层模块(如
MetricsProviderFactoryFactory)。 - 方法:将“职责”重新定义为“对单一用户或利益相关者负责”。真正的原则是:如果引入一个bug,导致两个不同业务方来投诉,那就违反了原则。
- 问题:对“单一职责”理解模糊(如“只做一件事”),可能导致创建职责不明确的浅层模块(如
过多的浅层微服务
- 问题:过度拆分导致大量微服务,形成“分布式单体”。开发、调试复杂,认知负荷极高。
- 方法:初期采用结构良好的单体,内部模块真正隔离。仅在确实需要(如独立部署、扩展开发团队)时,才考虑在网络层增加模块(未来可能成为微服务)。关键是在有足够信息时再做决定。
使用特性繁多的语言
- 问题:过多的语言特性迫使开发者花时间选择如何使用,后续维护者还需理解当初的选择。
- 方法:通过限制选择来减少认知负荷。语言特性应正交,避免过度使用晦涩特性。
后端返回自定义HTTP状态码
- 问题:前端和测试人员需要记住自定义状态码(如401、403、418)的业务含义。
- 方法:在响应体中返回自描述的业务代码(如
“code”: “jwt_has_expired”),而非依赖HTTP状态码传递业务逻辑。
滥用DRY原则
- 问题:过度追求“不重复”,可能导致在不相关的组件间引入紧耦合,或创建基于不确定相似性的过早抽象。
- 方法:接受合理的重复。记住:“一点点的复制好过一点点的依赖”。谨慎引入庞大的第三方库来复用小功能。
过度依赖框架
- 问题:框架的“魔法”迫使新开发者首先学习这些复杂性。当需求与框架架构不匹配时,可能被迫维护框架的自定义分支。
- 方法:编写框架无关的业务逻辑代码。将框架置于核心逻辑之外,像使用库一样使用它。
分层架构(如六边形/洋葱架构)
- 问题:过多的抽象层和胶水代码增加了文件数量和理解成本。当需要调试时,跨越多层进行调用跟踪极其耗费认知。
- 方法:回归更基础的原则,如依赖反转、单一数据源、认知负荷和信息隐藏。仅在需要明确的扩展点时才添加抽象层,而不是为了架构而架构。
对领域驱动设计的误解
- 问题:DDD的核心在于问题空间(如通用语言、限界上下文)以促进沟通,但常被误解为一套解决方案空间的具体模式(如特定的文件夹结构、服务模式),导致不同团队产生不同的、主观的心智模型。
- 方法:借鉴 Team Topologies 等更清晰、更易达成共识的框架来划分团队和认知负荷。
熟悉不等于简单
- 对已熟悉代码的开发者,高认知负荷不易察觉。
- 新开发者是衡量认知负荷的有效标尺:如果他们在加入后超过约40分钟持续感到困惑,说明代码有待改进。
- 独特的心智模型(通常是作者对Clean Architecture、DDD等的主观混合解读)会形成外在认知负荷,阻碍新成员快速贡献价值。
结论
- 应当尽可能减少超出任务本身固有难度的外在认知负荷。
- 许多经得起时间考验的复杂系统(如Unix, Kubernetes)其架构本质上是“无聊”且易于理解的。
- 评估架构的长期影响应关注:问题重现与调试是否容易?修改是否快速?新成员能否快速贡献?