最小化副作用(理想情况下没有副作用)
与仅输入某些内容并输出其他内容的函数相比,导致其自身范围之外的3种状态变化的函数要难于推理和维护。您不仅可以知道函数的功能,还必须记住它的功能以及它如何影响所有其他相关功能。
对于OOP来说,将副作用最小化还意味着类具有较少的成员,尤其是可以修改类状态的成员更少,因为成员函数可以修改超出其自身状态的状态并具有副作用(例如,它们可以操纵类的内部)。这也意味着类拥有较少的数据成员,因此这些方法被篡改的状态更少,并且它们可能引起的副作用也更少。
举一个简单的例子,想象一下尝试设计一个幻想的数据结构,该结构可以维持sorted
用来确定执行二进制或线性搜索的状态。在这种情况下,将设计分为两类可能会很有用。sorted
然后,调用未排序的类可能会返回另一个始终保持其内容排序的类的数据结构。现在,您具有更少的副作用(因此更少的容易出错且更易于理解代码),以及更广泛的适用代码(对于不需要排序的小数组,前一种设计在处理和人的智力效率上都是浪费的)。
避免多余的外部依赖
通过使用13个不同的库来完成一个相对简单的任务,您也许能够实现具有最大代码重用性的最简洁的代码。但是,这样一来,您不得不使读者至少了解13个不同库的一部分,从而将知识开销转移给您的读者。任何试图构建和理解第三方库的人都应该立即意识到这种固有的复杂性,而这需要引入并构建许多其他库才能起作用。
这可能是一个很有争议的观点,但是如果最终结果经过了很好的测试(没有比多次重复的未经测试的错误代码更糟糕的话),我宁愿进行一些适度的代码复制,而不是采取极端的做法。如果选择3行重复代码来计算向量叉积,还是选择史诗般的数学库以减少3行代码,那么我建议使用前者,除非您的整个团队都在使用此数学库,这时您仍然可以考虑只编写3行代码而不是1行代码,因为它足够琐碎以换取去耦的好处。
代码重用是一种平衡行为。重复使用过多,就会以一对多的方式转移知识的复杂性,因为上面保存的那三行简单代码的代价是要求读者和维护人员比三行代码了解更多的信息。这也会使您的代码不稳定,因为如果数学库发生更改,您的代码也可能会更改。重复使用太少,您的知识开销也会倍增,并且代码也无法从集中的改进中受益,因此这是一种平衡行为,但是值得一提的是,这是一种平衡行为,因为尝试消除每一种形式的适度复制会导致结果,与相反的极端情况相比,维护起来甚至要困难得多。
测试废话
这是给定的,但是如果您的代码不能处理所有输入情况并且错过了一些边缘情况,那么您如何期望其他人维护您编写的代码,甚至在转移到他们的眼睛和手上之前,您甚至还没有做好准备?很难更改能正常工作的代码,更不用说一开始就不正确的代码了。
最重要的是,通过全面测试的代码通常会发现更改的原因更少。这与稳定性有关,而与可维护性相比,这甚至是更重要的一点,因为不需要更改的稳定代码不会产生维护成本。
接口文件
如果您不能将相同的时间记录在文档上,那么将“事情做什么”优先于“事情如何做”。一个清晰的接口,其意图是在所有可能的输入情况下将要执行的操作(或至少要执行的操作),这将为其自身的实现提供清晰的上下文,这不仅将指导您理解如何使用代码,以及如何工作。
同时,无论其实现细节的文档化程度如何,缺少这些特质的代码甚至使人们甚至都不知道它应该做什么。对于长达20页的关于如何实现源代码的手册,这些人甚至无法弄清楚一开始应该如何使用它,以及在所有可能的情况下该怎么做。
对于实现方面,请优先记录与其他人不同的工作。例如,英特尔为其光线跟踪内核具有一个边界体积层次结构。由于我从事这一领域的工作,因此我一眼就能认出他们的代码在做什么,而无需仔细阅读文档。但是,它们做了一些独特的事情,即遍历BVH并使用ray包并行执行相交的想法。那是我希望他们优先处理其文档的地方,因为代码的这些部分在大多数历史BVH实现中都是奇异的和不寻常的。
可读性
这部分非常主观。我不太关心那种类似于人类思维过程的可读性。如果作者使用奇异而费解的思维过程来解决问题,那么对于我来说,仍然很难遵循最高程度文档化的最高级别描述事物的代码。同时,如果逻辑很简单,使用2或3个字符名称的简洁代码通常可以使我更容易理解。我想您可以同行评审,看看其他人喜欢什么。
我最感兴趣的是可维护性,更重要的是稳定性。没有理由更改的代码就是维护成本为零的代码。