编写良好的代码:如何减少代码的认知负荷

Bug 少,性能好,容易修改。好的代码影响深远,而且它可能是产生 10 倍工作效率的开发者的主要原因。尽管好代码十分重要,但开发新手却不得要领。关于这一主题的技巧多而冗杂,让新手们如何记得住?“Code Complete(《代码大全》)” 是这个主题的经典,但内容多达 960 页!

我认为应该建立起良好的心态,这样,不管你用什么语言或者库,都会自然而然的写出高质量的代码。这里我主要谈到 5 个相关的概念。记住它们,轻松写出写出好代码。

避免特立独行

当你读到一些文章中的新技巧时,如醍醐灌顶,一定会想要写点看起来很聪明的代码,让同行们眼前一亮。

问题是人们只是想修完 BUG,然后继续处理其它事情。那些聪明的技巧常常只会成为一种消遣。我曾经在“将神经科学应用于软件开发”中谈到,当人们被迫花心思来理解你的某段代码时,它们的“精神堆栈”会迅速填满,因而难以理解其中深意。

 [译者注:图片中的注释内容:这在 C 语言中用于避免误写成 variable = null。最近它造成不少人困惑,但似乎并没带来多大好处]

不要在工作中使用太多可能需要额外解释的个性化方式。

不要用“你的方式”来编写代码,只需要按照标准(的代码规范)来编写就好。再次强调,要写让人读得明白,看得下去的代码,让人家能够理解它。

分而治之

模块化可以使复杂的代码结构变得清晰,除此之外还有很多方法可以达到同样的目的,而无需创建更多函数。将长长的条件表达式保存为一到两个变量就是个不错的方法,可以避免调用函数的开销。这些变量可以用在其它地方,甚至可用于组合更复杂的条件。

拆解问题的方法在于尽可能的让每个部分保持集中,只影响局部状态,不要混入不相关的问题,要避免副作用。编程语言和库通常会带来各自相应的问题,避免这些问题可以让你的代码更专注于其表达的业务。单一责任原则就是通过集中代码和局部化代码带来良好设计的例子。

[译者注:图中注释内容:这是不需要额外函数开销的一种模块化方法]

我喜欢利用变量来进行逻辑划分。

TDD(Test Driven Development,测试驱动开发)的成功实施表现出了它所带来的好处,它迫使人们运用一些以前不受欢迎的准则。无状态的代码曾经被嫌弃又慢又没必要(大部分老的 C/C++ 代码中可以看到),然而现在每个人都在谈论纯函数。就算你不采用 TDD,你也应该学习它背后的原则。在新的模式下工作会让你成为适应性极强的开发者。

分离代码并使其可分别处理

你写代码的时候面临着什么样的困难,你的计算机和工具也面临着同样的困难。代码的复杂性,与需要进行的预处理和需要处理的突发情况存在着或多或少的联系。

现在暂时抛开那些额外的构建工具所带来的好处。它们需要你使用特定领域的语言,比如自定义模板,或者复杂的动态数据结构,比如哈稀表。IDE 通常不善于处理这些东西,要找到相关的代码段则更加困难。

尽量避免使用不能很好支持 IDE 的语言扩展和库。它们给你的生产力带来的好处,远大于简易配置和用简洁语法保存击键带来的小便利。

 [译者注:图中注释内容:使用神奇的字体串可能造成 IDE 不能正确识别你的代码]

ServiceLocator 是与 IDE 整合不佳的一个设计样例。

另一个保持 IDE“整合度”的相关方法是避免编写特殊的代码。多数语言都提供了编写动态代码的能力。如果滥用这些特性,比如特殊的字符串、特殊的数组索引和自定义模板语言特性等,会产生难以连接的代码库[译者注:这里的连接应该是指相互关联的关系,连接最直接的影响是在使用 IDE 等工具进行重构的时候可以自动根据连接关系修改相关引用]。一般说来,那些只有你一个人才能看懂的特性会让你摔跟头的,因为如果 IDE 不能理解这些代码,在你想进行结构调整的时候,IDE 就没法帮你进行重构。

让程序可读

致力于可预测的架构。这种架构下你的队友要进行某项查找就会很容易,可以节约不少时间。一旦你为项目确定了一个整体的架构,就一定要把主要元素放在显眼的位置。使用 MVC?把模型、视图和控制器放在他们自己的目录下,不要放在三个深层次的目录中,也不要放在几个不同的地方。

我在前面谈到了模块化。也存在过度的模块化,让定位代码这种事情变得艰难无比。IDE 可能会带来一些帮助,但通常你往往会让 IDE 忽略库目录,因为其中有很多不相关的代码,或者它的索引需要人工处理一些问题,就会造成两败俱伤的局面。尽量使用较少的库,选用那些尽可能多覆盖你需求的库。

库和工具也可能成为新人的障碍。我最近使用 EcmaScript 7 (babel) 构建了一个项目,后来我才意识到我们的初级开发人员一直因为想搞明白它而卡在那里,这对团队的生产力造成了巨大损失。我低估了这对一个新手所带来的压力。不要使用对当前来说太难掌握的工具,等时机成熟再使用。

这是我写的一个 makefile 中的真实代码。新手不需掌握过多的新技术。

让代码易于理解

如果你已经做到了这一点,那我们来解决更重要的问题——选择好名字,这是软件开发中的重要部分。构建工具在这方面不能提供帮助,原因很简单,计算机不会真正知道解决方案背后的逻辑。你得通过文档来解释代码,而与主题相关,且符合上下文,体现变量和功能的名称就能很好做到这一点。语义化的名称甚至可以减少对文档的需求。

在名称中使用前缀对理解它们很有帮助。这在过去是一种流行的做法,我认为对这种作法的误用导致了它的消亡。像匈牙利命名法这样的前缀系统最初只是为了增加意义,但最后用于其中的上下文越来越少,终于少得只剩类型信息。

[译者注:图中的注释内容:使用名称来表达意图,不要利用语言来耍小聪明]

近来,Fluent 接口经常被滥用。

最后要说说老生常谈的回溯复杂度。简单地说就是要尽可能减少条件分支的数目。每多一个分支都会增加缩进,同时降低可读性。不过更重要的是,增加的东西越多,你需要跟踪的东西就越多。

结论 & 相关阅读

本文介绍了五个简单的总体概念,我希望你能从中轻松的学习到组织代码的方法。

实践是最好的老师,用编程来巩固理论。如果你还没有这样做,我诚挚向你推荐代码大全。它带来了大量的示例,几乎剖析了你可能遇到的每一种问题。