最近,Artem Zakirullin的一篇名为《 Cognitive load is what matters 》的文章引起了广泛关注,文章散获得Karpathy的转载和马斯克认同。我们来一起看看这篇散发着“KISS”味道的文章讲了什么?
什么是认知负荷?
认知负荷说白了就是”开发人员需要动多少脑子才能完成一项任务”。人的工作记忆有限,一般只能同时记住约4个信息块。当你阅读代码时,需要记住变量值、控制流逻辑、调用顺序等信息,一旦超过这个限度,理解代码就会变得困难。
两种认知负荷
文章将认知负荷分为两类:
-
内在认知负荷: 由任务本身的难度造成,无法减少 -
外在认知负荷: 由信息呈现方式造成,可以通过优化大幅减少
几个典型的认知负荷案例
1. 复杂的条件判断
if val > someConstant
&& (condition2 || condition3)
&& (condition4 && !condition5) {
...
}
这样的代码会快速占满你的工作记忆。更好的方式是引入有意义的中间变量:
isValid = val > someConstant
isAllowed = condition2 || condition3
isSecure = condition4 && !condition5
if isValid && isAllowed && isSecure {
...
}
2. 继承地狱
当你需要修改一个类时,发现它继承自多个层级:
AdminController extends UserController extends GuestController extends BaseController
你不得不追踪每一层的实现,最后大脑就会过载。文章建议使用组合而不是继承。
3. 过度微服务化
文章举了一个真实案例:一个 5人的创业团队建了 17 个微服务,结果每个新需求都要改 4个以上的服务,最终导致项目严重延期。这就是典型的认知负荷过高的例子。
如何降低认知负荷?
不要盲目应用一些软件架构和设计原则,而是从直觉出发,文中给出了DDD、分层、DRY,函数的长度不应过大等等我们曾奉为圣经的反模式,文章给出了几个实用建议:
-
避免过度抽象 – 不要为了复用而复用,有时候适当重复比创建复杂抽象更好 -
使用描述性的命名 – 好的命名可以减轻理解负担 -
保持代码线性 – 跳来跳去的代码更难理解 -
控制模块大小 – 不要追求过小的模块,适当的大模块反而更容易理解
深度模块 vs 浅层模块
文章特别强调了”深度模块”的概念:
-
深度模块:接口简单,功能强大 -
浅层模块:接口复杂,功能有限
Unix 的I/O 接口就是典型的深度模块 – 仅有 5个基本调用,但实现了数十万行代码的功能。
总结启示
总的来说,文章告诉我们在软件开发中,不要一味追求所谓的”最佳实践”,而要从认知负荷的角度去思考代码质量。好的代码应该是容易理解的代码,符合直觉的、而不是看起来很酷,或者故弄玄虚的代码。
甚至,最近笔者借助一些AI工具编程的体会来讲,过去那些面向人类程序员而诞生的设计原则,对于AI来讲也并不一定适用,比如,重复代码这个例子,对于人类来讲,为了避免漏改,最好能够抽成一个函数复用,这样修改方便,但对于AI来讲,与其理解代码的依赖关系和影响面,直接按需修改具体功能的代码更为容易。
(文:AI工程化)