设计模式导致了认知负担?


在不同的团队中编码多年后,我想我现在终于可以向自己解释,为什么我们需要或不需要用模式和抽象构建的 "聪明的 "代码。

最近我一直在阅读关于团队组织的各种方法(尤其是“团队拓扑”)以及如何组织团队以减少认知负担。这让我更普遍地想到了认知负荷。

回到编码实践,我早就意识到代码和整体设计需要简单:任何领域本身就有足够的内在复杂性,而 "聪明的 "代码,更多时候只会让事情变得更糟。

但重要的是我已经找到了 "为什么"会变遭。
因为它导致了认知负担。

不过,这有点复杂。事实证明,你可以从两个方面使用认知负荷的论点:一个人可以说你需要抽象来隐藏复杂性,以及另一个论点是避免抽象,保持简单。

这是我最新的启发式方法:一个 "认知负荷如何评估?"
评估标准就是:哪个版本的代码为未来的读者创造了最少的认知负荷。

所以,在为一个新项目引入抽象概念之前,我现在试图决定未来的读者是否能够自信地进行修改,而不需要在源代码树上跳来跳去,无论是否有任何复杂的设计,并选择更好地支持这一想法的路径。

你怎么看?

网友评论:

1、这不是抽象的数量问题,而是抽象的质量问题。如果抽象能减少认知负荷,那就是好的。如果抽象会增加认知负担,那就不好。很多时候,好的抽象在问题领域中是显而易见的。

为此,我认为Haskell在追求最纯粹的过程中,可能会导致令人难以置信的高认知负荷的抽象,使得阅读代码变得令人生畏。编写时感觉很好,但阅读时感觉很糟糕。有时,如果你所概括的东西会导致人们在引用它时产生更高的认知负荷,那么应用DRY原则就会变得很糟糕。

另外,我认为抽象的质量取决于抽象所嵌入的语言。C++中的函数式范式工作起来很棘手;即使函数式范式在一般情况下是很好的抽象,但如果语言不支持它的一流性(即使用它们的认知负荷很高),那么它们就不是好的抽象。

你也可以考虑像Clojure这样的语言中的map,那里的map几乎是 "零级 "的;就像这个语言是为map而生的。嵌套map在Clojure中的认知负荷非常低(不仅因为语法支持,而且还因为函数是标准化的)。然而,Common Lisp中的嵌套图没有Clojure中的那么好;你必须导入非标准的库来处理它们(甚至更隐蔽的库来支持类似Clojure的读宏)。

关键问题是:当别人阅读这段代码时,他们需要在多大程度上 "重构 "上下文?他们可以不重构上下文吗?如果他们需要,通过适当的命名、注释和类型,他们可以多快地做到这一点?


2、抽象,就像其他许多东西一样,可以沿着两个XY轴线都是 "困难的":它们可能很难学习,或者很难使用。

前者会在你学习时增加认知负荷,当然,但一旦你学会了,它们也会带来巨大的回报--抽象数学很难,但也能让我们管理复杂性,表达我们无法处理的想法。

另一方面,很多抽象概念并不特别难学(通常是因为它们不是很抽象!),但要么做得不多,要么在你使用它们的时候带来持续的复杂性。

不管你对指针或malloc+free学得有多好,使用它们的非实质性代码都需要很小心,而且很容易出错。

我把它看作是抽象思维和工作记忆之间的一种权衡。你可以在前期花时间来减少你在前进过程中需要记在脑子里的细节,或者你可以继续杂耍更多的细节来减少前期的学习。

当然,问题是大多数人并没有区分这两者的区别。从某种意义上说,这是很公平的:如果一个抽象概念在你学习它的过程中带有一些前期的认知成本,你怎么知道它会变得更好?你现在还有时间去学习新东西吗?但是,归根结底,这是一个巨大的差异,从长远来看,避免那些前期困难的抽象概念是自取灭亡。

Haskell的抽象大多属于前者:很难学习,但一旦学会就很强大。我曾经处理过一些非常糟糕的Haskell代码,在我还是初学者的时候,那些看起来很难的抽象正是让我更容易理解混乱的代码的原因!事实证明,表达式类型和效果管理意味着我不需要仔细了解代码中哪些部分可以间接影响对方,哪些部分不能;这一切都在代码的结构中明确了。我必须学习语言和概念来理解它,但是,一旦我做到了,我就可以直接从程序中读到它,而不需要在我的脑海中模拟代码。

我发现,大多数时候,KISS的设计理念在很大程度上是朝着相反的方向发展的:它都是为了避免读者需要学习的概念和抽象,但代价是让每个人在编写和阅读代码时在脑子里保留更多的细节。小的部分可能更容易理解,但对于同样数量的逻辑来说,还有一大堆的部分需要追踪

一概而论的 "别人会觉得这段代码有多难读吗?"的问题是,它的大部分内容都取决于熟悉程度,而不是代码的基本内容。在任何特定情况下,这都是重要的......但它并没有回答我们应该如何编写(和阅读)代码这一更广泛的问题。毕竟,即使熟悉程度在短期内占主导地位,但为了在中长期内获得更好的体验,仍然值得先期学习。

3、我发现这些争论都是在绕圈子,没有得出任何结论。
所有的团队都是不同的,一个好的经验法则是写出最没有技术或经验的人在没有指导的情况下可以自己理解的代码。因为团队不同,所以这些限制也会改变。在我的团队中,如果尝试使用任何抽象,更不用说代数数据类型,都是很奇怪的,而在函数效果的基础上,有很多抽象和架构模式的团队。这些团队在最好的和最弱的成员的熟练程度上有很大不同,代码库模式也是如此。

这就像在体育运动中,不同的团队需要不同的方法,因为他们的素质和熟练程度不同,没有普遍的真理,只有更适合不同团队的解决方案。


4、我只是一个普通人,请对我说的话要多加留意:
这个问题大致是通过领域驱动设计(DDD)和函数式编程(FP)的结合来解决的。

是的,我知道你已经听腻了FP,你认为FP工程师都是白胡子的单体销售员。

有可能的是,如果你是一个工程师,你不是在做纯粹的抽象工作:你的代码与 "现实世界的东西 "有一些对应关系。FP不受类/对象模型的束缚,你可以用普通的老函数对任何东西进行抽象:不要 "选择 "你的抽象,使用这些和你的领域模型,来找出正确/真实(est)的抽象。这不是关于过多或过少的抽象,而是关于这些抽象有多合适。

我读过的关于 FP 的最好的帖子之一是 John Carmack 意识到 FP 在任何代码库中都占有重要地位

保持大多数函数的纯净和无状态,但对现有状态进行转换,这是一个非常有价值的教训。

围绕这个想法构建你的整个代码库,坦率地说,抽象会照顾好自己。
 http://www.sevangelatos.com/john-carmack-on/

5、作为一个行业,我们花了很长时间才认识到“设计模式”的流行是由于语言问题。最好的解决办法是改进语言。