Andrej Karpathy 推荐的关于软件开发文章"Cognitive load is what matters",非常值得一读
“认知负荷才是关键。这或许是最真实却最少被实践的观点。” -Andrej Karpathy
在软件开发的世界里,充斥着各种流行语和最佳实践。然而,有一个问题常常被忽视:认知负担。这并不是一个抽象的理论,而是开发者每天都在面对的现实困境。代码越难理解,开发者的时间和精力就浪费得越多,而这些成本最终都会转化为团队的生产力损失
什么是认知负担?
简单来说,认知负担就是开发者在完成任务时需要思考的量。当你阅读代码时,你的脑子里需要同时装下变量的值、逻辑流程、函数调用顺序等信息。而问题在于,人类的工作记忆容量有限,通常只能同时处理 4 个左右的信息块。一旦超过这个阈值,理解能力就会直线下降。
想象一下,你接手了一个完全陌生的项目,前开发者使用了各种“高端”架构、炫酷的库和流行技术。结果呢?你还没开始写代码,认知负担已经爆表了
认知负担的两种类型
1. 内在认知负担:由任务本身的复杂性引起,无法避免。例如解决算法问题或实现复杂业务逻辑
2. 外在认知负担:由信息的呈现方式引起,与任务无直接关系,比如代码风格怪异、命名不清晰等。这种负担是可以大幅减少的,也是我们应该重点优化的方向
实战案例:如何降低外在认知负担?
1. 简化复杂条件语句
if (val > someConstant && (condition2 || condition3) && (condition4 && !condition5)) {
// 什么鬼?读到这里已经头晕了
}
改进后:
isValid = val > someConstant;
isAllowed = condition2 || condition3;
isSecure = condition4 && !condition5;
if (isValid && isAllowed && isSecure) {
// 条件清晰,变量名一目了然
}
通过引入中间变量,我们不需要再死记硬背每个条件的细节,认知负担瞬间下降
2. 继承噩梦
AdminController -> UserController -> GuestController -> BaseController
修改某个功能需要从 BaseController 一路查到 AdminController,甚至还要考虑 SuperuserController 的影响。认知负担?爆表!
解决方案: 优先使用组合而非继承。组合模式虽然看起来不够“优雅”,但却能极大降低理解代码的成本。
3. 太多小方法,类,模块
过多的小方法、小类或小模块,会让项目变得支离破碎。要理解一个浅模块的作用,你往往需要先搞清楚与之相关的所有模块,简直是噩梦
解决方案:深模块,简单接口,复杂实现
例如 UNIX 的 I/O 接口,虽然底层实现有几十万行代码,但接口只有 5 个简单的调用:
open, read, write, lseek, close
这样的设计隐藏了复杂性,让开发者更容易上手
减少选择,限制语言特性
编程语言的新特性总是令人兴奋,但特性过多反而会增加认知负担。你不仅需要理解复杂的代码,还要猜测作者为什么选择某种特性
解决方案:限制选择,保持特性独立性
正如 Rob Pike 所言,语言特性可以有,但它们必须是正交的,互不干扰。
架构设计:简单才是硬道理
层次化架构(Layered Architecture)本应隐藏复杂性,但实际上却增加了跳转和追踪的负担。每一层的抽象都需要占据开发者的工作记忆,这种设计往往得不偿失。
解决方案:避免过度抽象,遵循实际需求
不要为了优雅的架构而添加无意义的层,抽象层应该为功能扩展服务,而非制造额外负担
结语:认知负担,开发者的隐形成本
代码的复杂性不应该成为团队的负担。无论是通过清晰的命名、简化的逻辑,还是合理的架构设计,我们都应该尽量减少外在认知负担
记住:降低认知负担,不仅是对自己负责,也是对团队负责