写点什么

软件设计原则失效了?怎么把代码写得更实在,最新神文让 AI 大神卡帕西和马斯克都点赞

  • 2024-12-30
    北京
  • 本文字数:2723 字

    阅读完需:约 9 分钟

软件设计原则失效了?怎么把代码写得更实在,最新神文让 AI 大神卡帕西和马斯克都点赞

最近,技术圈掀起了一波关于“怎么减少代码认知负荷”的热烈讨论。“导火索”是这样一篇文章:《Cognitive load is what matters》(《认知负荷才是关键》),阐述了为什么写代码最重要的事,不是用什么高级架构,不是追逐酷炫技术,而是别让人看了你的代码后“想骂人”。文章在 Hacker News 上迅速占据热榜,引发了技术社区的广泛共鸣。


作者 Artem Zakirullin 是一位经验丰富的软件架构师,拥有超过 13 年的软件开发和工程经验。他目前担任软件开发公司 Inktech 的 CTO 一职。


OpenAI 创始成员之一、前特斯拉 AI 主管 Andrej Karpathy 在 X 平台上转发了这篇文章,发出感慨评价:“这大概是最真实却最少被实践的观点。”



并且,埃隆·马斯克也在 Karpathy 的帖子下评论道:“确实。”


认知负荷为什么如此重要

文章提出了一个人人都能感受到但少有人正视的问题:写代码时的“认知负荷”(Cognitive Load)。


所谓认知负荷,是指开发人员为了完成一项任务所需要承担的思考量。也可以简单理解为:为了完成一项任务,需要动多少脑子、占用多少注意力。


特别是当我们阅读代码时,需要将变量值、控制流逻辑和调用序列等内容塞进自己的脑袋。普通人能够支配的记忆力大约可以容纳 4 个这样的构建块,而一旦认知负荷达到了这个阈值,对于事物的理解难度就会开始陡然上升。



想象一下,你需要接手一个完全陌生的项目并进行修复。当你打开代码,迎面而来的是前一位“天才开发者”留下的大量酷炫的架构设计、花哨的库以及时下流行的技术方案。这种情况下,你不仅要理解项目本身的逻辑,还得费力去消化这些复杂的设计决策——这就是典型的高认知负荷


Artem Zakirullin 进一步将认知负荷分为两种:


  • 内在负荷:源自任务固有的难度,这种没法避免,也是软件开发工作的核心所在。

  • 外在负荷:由信息的呈现方式导致,与任务本身无关,例如聪明作者们的个人习惯。这方面负荷是可以大大降低的。



因此, Zakirullin 在文章中的核心主张就是,尽量减少项目中的外在认知负荷

“减负秘笈”


那么,如何减少外在认知负荷?结合开发中常见的“认知雷区”例子,Zakirullin 给了一些接地气的建议:

1.复杂条件语句:拜托别玩脑筋急转弯

这类代码你可能见过:


if val > someConstant // 🧠+    && (condition2 || condition3) // 🧠+++,上一条件应当为真,c2或c3之一必须为真    && (condition4 && !condition5) { // 🤯,到这一步我们的脑袋就快爆炸了……    ...}
复制代码


解决办法?引入清晰的中间变量:


isValid = val > someConstantisAllowed = condition2 || condition3isSecure = condition4 && !condition5// 🧠,有了描述性变量,我们就不再需要记住条件if isValid && isAllowed && isSecure {    ...}
复制代码

2. 继承噩梦:少用继承,多用组合

“继承链”是认知负荷的天花板。你改 AdminController 的代码,发现它继承了 UserController,后者又继承了 GuestController,还不止这些——改着改着,突然冒出来个 SuperuserController,让你意识到一处改动可能引发蝴蝶效应。


解决办法?别玩继承了,多试试组合吧。

3. 小模块≠简单:浅模块问题

开发界流传着一种迷思,像“方法应该少于 15 行代码”或者“类不能太大”之类的常规习惯实际上是错的。


事实证明,结果一不小心,你的项目里塞满了多个小模块,那维护起来比修一张蜘蛛网还难。


  • 深模块——接口简单,但功能复杂;

  • 浅模块——相对于所提供的小功能,其接口相对复杂。



Zakirullin 认为更好的方法是设计深模块:隐藏内部复杂性,只暴露一个简单的接口。比如 UNIX 的 I/O 接口就非常简单,只有五个基本调用:

open(path, flags, permissions)read(fd, buffer, count)write(fd, buffer, count)lseek(fd, offset, referencePosition)close(fd)
复制代码


“信息隐藏非常重要,而浅模块中无法隐藏太多的复杂性。”Zakirullin 在文章中说道。

4. 语言功能越多越好吗

当遇到喜欢的编程语言发布新功能时,谁能不感到兴奋并迫不及待想用起来。然而,文章还提醒说,这种一时热情的背后藏着一个巨大的认知负荷陷阱。


Zakirullin 引用了 Rob Pike(Go 语言的主要设计者之一)的一句话:要通过限制选项的数量来降低认知负荷。


举个例子,当你用了一些新特性写了代码,可能当时觉得很“优雅”。但几个月后,当你回头看这些代码时,你不仅需要重新理解代码逻辑,还得想起“我当时为啥要用这个特性来解决这个问题?”


这无疑增加了外在认知负荷。简言之,丰富的语言功能肯定不是坏事,但前提这些功能要彼此关联。

5.别滥用分层架构

有人说,架构设计要“优雅”,于是各种抽象层、接口层、适配器层层叠叠。等到真要查 Bug 时,开发者得顺着 5 层调用链一路追溯问题来源,头发掉得比写论文还快。


分层架构的初衷是通过抽象隐藏复杂性。有些人可能以为这样的分层有助于快速替换数据库或者其他依赖项,但事实并非如此。存储方面的变更会导致很多问题。


Zakirullin 强调,开发过程中最不需要担心的就是数据访问层上的抽象机制。充其量,这些抽象能够节约 10%的迁移时间(这还是在比较乐观的情况下),而真正的麻烦永远来自数据模型不兼容、通信协议、分布式系统挑战以及隐式接口。


所以,既然后续根本没有回报,又何必要为这种分层架构付出高昂的认知负荷成本呢?


总之,分层架构只有在需要明确扩展点时才有意义。否则,这些层次带来的额外认知负荷远高于它能提供的好处。

6.领域驱动设计(DDD):用对了是神器,用错了是灾难

文章还提到另一个开发者热衷但经常误解的概念:领域驱动设计(DDD)。DDD 本质是关于问题空间的思考,而不是解决方案空间的代码设计。


问题是,许多团队在实践中“跑偏”了。本应专注于领域建模和边界划分的 DDD,变成了某种固定的文件夹结构、服务命名规则、或者对“Repository 模式”的机械化崇拜。


每个人对于 DDD 的理解和解释方式都可能独特且主观。如果基于这样的理解来编写代码,则必然会创造出大量非必要的认知负荷——相当于给后续接手的开发人员埋下一颗颗炸雷。

7.熟悉项目 VS 认知负荷

认知负荷高低,决定了一个项目对开发者有多“友好”。熟悉项目的开发者,已经把代码逻辑内化到脑子里,干活轻松省事;但对新人来说,如果项目充满谜语一样的命名、复杂的模块关系和稀缺的文档,他们很快就会懵圈,效率直线下降。



文章给了一个建议:每当有新人加入项目时,请尝试衡量他们的懵圈程度。如果他们花了 40 分钟还是一头雾水,那肯定意味着代码中存在需要改进的地方。


如果能将认知负荷始终保持在较低水平,那么新人在入职后的几个小时内就可以为代码库做出贡献。

写在最后

减少认知负荷并不是一个高深的哲学,而是开发者日常需要实践的基本功。或许,每次写代码时,我们都该问问自己:


“这段代码,是帮同事省脑子,还是让他们掉头发?”


参考链接:

https://minds.md/zakirullin/cognitive

2024-12-30 15:2214971

评论

发布
暂无评论

十五年后苹果再次变心

池建强

apple 苹果 芯片 wwdc

架构师训练营 - 第三周学习总结

清风徐徐

ARTS Week5

丽子

【极客大学】【架构师训练营】【第二周】总结:设计原则

NieXY

极客大学架构师训练营

【极客大学】【架构师训练营】【第二周】依赖倒置原则和接口隔离原则

NieXY

极客大学架构师训练营

好奇心, 优秀软件工程师的内核品质

亚伦碎语

读书感悟 随笔杂谈

设计原则与设计模式

dapaul

极客大学架构师训练营

每日一题-翻转字符串里的单词

程序员老王

LeetCode

基于业务表 Binlog 的事件驱动设计

理帆

MySQL 事件驱动 Binlog

食堂就餐卡系统设计

John

极客大学架构师训练营

wee1作业总结

牛珈羽

极客大学架构师训练营

区块链目前实际的应用场景汇总

CECBC

区块链技术 去中心化 应用场景

设计模式之单例模式和组合模式

dapaul

极客大学架构师训练营

就餐卡系统架构设计文档

牛珈羽

极客大学架构师训练营

MySQL InnoDB存储引擎 - 事务

Axe

线性表(数组、链表、队列、栈)详细总结

淡蓝色

Java 数据结构 算法 链表 线性表

别兜售你自己不会购买的东西

Neco.W

创业 销售管理 销售

LeetCode 655. Print Binary Tree

liu_liu

算法 LeetCode

辟谣:程序员不配谈恋爱?你错的可以!真相来了

码农神说

程序员 漫画 相亲

centos7 操作

InfoQ_1c4a1f813eb1

RabbitMQ跨机房迁移数据零丢失

心平气和

RabbitMQ 消息队列

游戏夜读 | 《老残游记》很有趣

game1night

Redis系列(三):缓存过期该如何剔除?RDB和AOF又是什么?

z小赵

Java redis 高并发 高并发系统设计

SpringBean的生命周期

编号94530

Java spring Spring Boot 生命周期

工作那么久,才知道的 SOLID 设计原则

闻人

架构师 极客大学架构师训练营

循序渐进的中台研发

理帆

中台 业务中台

【在云端 002】云时代,何以安放我的个人数据

Bora.Don

云计算 云存储

查找算法系列文(一)一文入门二叉树

淡蓝色

Java 数据结构 算法 二叉树

第四周 学习总结

冯凯

多个maven项目启动顺序

terrytian

maven

iOS & Android 去马赛克处理

liu_liu

ios android 去马赛克

软件设计原则失效了?怎么把代码写得更实在,最新神文让 AI 大神卡帕西和马斯克都点赞_架构_罗燕珊_InfoQ精选文章