diff --git a/README.zh-cn.md b/README.zh-cn.md index e927111..1d975e5 100644 --- a/README.zh-cn.md +++ b/README.zh-cn.md @@ -1,145 +1,142 @@ # 认知负荷才是关键 +[Prompt](README.prompt.md) | [MindsMD](https://minds.md/zakirullin/cognitive) | [English](README.md) | [Korean](README.ko.md) | [Turkish](README.tr.md) | [Japanese](README.ja.md) + +*这是一份持续更新的文档,最后更新:**2025 年 9 月**。欢迎你的贡献!* + +*本译文基于原文 [4a6d92d](https://github.com/zakirullin/cognitive-load/commit/4a6d92d4b0cf326d82b9beaf43d05696b03d22f9) 版本进行翻译。* + ## 简介(Introduction) +这世上充斥着各种流行术语与“最佳实践”,但大多数最终都失灵了。我们需要更基础、更不可能出错的东西。 -这世上有如此多的流行术语和最佳实践,但它们中大多数无法真正落地。因此,让我们把注意力转向更根本的方面,比方说,开发者在阅读代码时所感受到的“困惑程度”。 +有时我们在阅读代码时会感到困惑。困惑消耗时间和金钱。困惑源于过高的“认知负荷”。它不是某种花哨的抽象概念,而是**人类的一种基本约束**。它不是臆想出来的,它的确存在,而且我们能真切感受到。 -困惑是以时间和金钱为代价的。困惑源于过高的*认知负荷*。这不是什么花哨的抽象概念,而是**一种基本的人类限制**。它不是靠想象出来的,而是真实存在,能被感知的。 - -由于我们日常花在阅读和理解代码上的时间远远多于编写代码的时间,所以我们应该经常自省:我们是否在代码里添加了过多会给人带来认知负荷的内容。 +鉴于我们在阅读与理解代码上所花费的时间远多于书写代码所花费的,我们应当持续地自省:我们是否正在把过多的认知负荷添加到代码中。 ## 认知负荷(Cognitive load) +> 认知负荷是指开发者为了完成一项任务需要动多少脑子。 -> “认知负荷”(Cognitive load)指的是开发者为了完成一项任务而需要投入的思考量。 +阅读代码时,你会把变量的取值、控制流逻辑、调用序列等“装”进脑子里。普通人的工作记忆大约能同时容纳[四个这样的信息块](https://github.com/zakirullin/cognitive-load/issues/16)。一旦认知负荷接近这个阈值,理解就会变得困难得多。 -在阅读代码时,人们会将诸如变量值、控制流逻辑和调用序列等内容记在脑海里。通常情况下,一般人在[工作记忆](https://baike.baidu.com/item/%E5%B7%A5%E4%BD%9C%E8%AE%B0%E5%BF%86/5197761)中大约可以保存[四个信息块](https://github.com/zakirullin/cognitive-load/issues/16)。一旦认知负荷达到这个临界值,理解事物就变得更加困难。 - -*假设我们被要求对一个完全不熟悉的项目进行修复工作。我们被告知在此之前,有一位非常聪明的开发人员贡献过这个项目,并在里面应用了许多复杂高级的架构、花哨的库和流行的技术。换句话说,**这位开发人员给我们制造了很高的认知负荷。*** +*假设我们被要求去修补一个完全陌生的项目。有人告诉我们,之前有位非常聪明的开发者贡献过:用了很多酷炫的架构、花哨的库、时髦的技术。也就是说,**作者为我们制造了极高的认知负荷。***
+

+
+
||在requires ((!P || !Q)) 和 requires (!(P || Q)) 中有不同的含义。第一种情况是约束析取(constraint disjunction),第二种情况则是常见的逻辑或(logical OR)运算符,并且它们的行为并不一致。memcpy复制一组字节 —— 这样不会启动对象的生命周期。在 C++20 中,这个问题得到了解决,但语言的认知负荷却不降反增。🤯🤯|| 在 requires ((!P<T> || !Q<T>)) 和 requires (!(P<T> || Q<T>)) 中含义不同。前者是约束析取(constraint disjunction),后者是我们熟悉的逻辑或运算符,而且它们的表现是不同的。memcpy 将字节复制过去——那并不会启动对象的生命周期。这在 C++20 之前是这样的。在C++20的版本里修了,但语言的认知负荷却只增不减。🤯🤯感谢 0xd34df00d 的来稿。

+


Rob Pike
好文章
Andrej Karpathy (ChatGPT, Tesla)
一篇关于软件工程的好文。也许是最真实、却最少被践行的观点。
Elon Musk
确实如此。
Addy Osmani (Chrome,世界上最复杂的软件系统之一)
我见过无数项目中,聪明的开发者用最新的设计模式和微服务构建了令人印象深刻的架构。但当新成员尝试做改动时,他们花了好几周也只是试图弄清楚各部分如何拼在一起。认知负荷高到离谱,生产力直线下降,bug 倍增。
讽刺的是?许多引入复杂性的模式,都是以“整洁代码”的名义被实施的。
+真正重要的是减少不必要的认知负担。有时这意味着用更少、更“深”的模块替代许多“浅”的模块。有时这意味着把相关逻辑放在一起,而不是拆成无数微小函数。
+有时这也意味着选择无聊、直接的方案而非聪明的方案。最好的代码并非最优雅、最复杂的——而是未来的开发者(包括你自己)能很快看懂的代码。
+你的文章让我非常有共鸣,尤其是对于我们在浏览器开发中面临的挑战。你说现代浏览器是最复杂的软件系统之一,这完全正确。管理 Chromium 的复杂性是一项持续的挑战,也与文中关于认知负荷的许多观点高度契合。
+我们在 Chromium 中处理复杂性的做法之一,是谨慎的组件隔离和在子系统(如渲染、网络、JavaScript 执行等)之间定义清晰的接口。这类似你关于 Unix I/O 的“深模块”例子——我们追求在相对简单的接口背后提供强大的功能。比如,我们的渲染管线处理着令人难以置信的复杂性(布局、合成、GPU 加速),但开发者可以通过清晰的抽象层与之交互。
+你关于避免不必要抽象的观点也很扎心。在浏览器开发中,我们持续在“让代码库对新贡献者更友好”与“处理网络标准和兼容性的内在复杂性”之间做平衡。
+有时,在复杂系统里,最简单的方案才是最好的。
+antirez (Redis)
完全同意 :) 我认为《A Philosophy of Software Design》里欠缺的一个概念是“设计牺牲”。也就是,有时你牺牲一些东西,换来简单、性能,或两者兼得。我一直都在践行这个理念,但它常常不被理解。
一个例子是,我一直拒绝让哈希字段(hash items)支持过期。这是一次“设计牺牲”,因为如果某些属性只存在于顶层条目(键本身),设计更简单,值就只是对象。当 Redis 引入哈希字段过期时,功能是不错,但确实需要在许多地方做很多改动,复杂性上升。
+另一个例子是我正在做的 Vector Sets,新的 Redis 数据类型。我决定 Redis 不做向量的“事实来源(source of truth)”,而只是存储近似版本,因此我可以在写入时做归一化、量化,而无需在磁盘上保存大规模的浮点向量等等。很多向量数据库不会牺牲“保留用户放入的全精度向量”这点。
+这只是两个随机例子,但我到处都在应用这种理念。关键是:当然要牺牲对的东西。往往 5% 的特性就占据了非常大比例的复杂性:那正是该砍掉的 :D
+一位来自互联网的开发者
你们可能不会雇我……我靠“发过多少企业项目”这类履历吃饭。
我曾和一个能“说设计模式”的人共事。我从来不会那样说话,尽管我是少数能很好理解他的人之一。管理者很喜欢他,他能主导任何开发讨论。但周围人说,他走到哪儿都留下一地鸡毛。别人说我是第一个能理解他项目的人。可维护性很重要。我最在乎 TCO(总拥有成本)。对很多公司来说,这才重要。
+我很久没上 Github 了,某次登录后不知为何跳到某个看起来随机的人仓库里的文章上。我在想“这是什么”,又一时回不了主页,于是就读了。起初没在意,但它太棒了。每个开发者都该读一读。它基本告诉我们:几乎我们被灌输的所有编程最佳实践,都会导致过量的“认知负荷”,意思是我们的脑子被过度的消耗。我早就有这感觉,尤其在云、安全、DevOps 的要求下。
+我也喜欢它,因为它描述了我几十年来一直在做、但不太好意思承认(因为不流行)的实践……我写的东西非常复杂,需要尽可能多的帮助。
+想想看,如果我是对的,那它会出现在 Github 首页,是因为 Github 的聪明人觉得开发者应该看看它。我赞同。
+