两个就是太多
2015年12月21日 by Max Kanat-Alexander
在进行增量开发和设计时,我遵循一条关键原则:“两个就是太多”。这是我实现《软件设计三大缺陷》中"只需做到必要的通用性"规则的方式。
本质上,当我发现想要复制粘贴代码时,我会设计一个仅满足这两个特定需求的通用解决方案,而不是真的复制代码。只要出现需要两个实现的情况,我就会立即这样做。
例如,假设我正在设计音频解码器,最初只支持WAV文件。当我想要添加MP3解析器时,WAV和MP3解析代码肯定有共同部分。这时我不会复制任何代码,而是立即创建一个超类或工具库,仅满足这两个实现的需求。
关键在于立即行动——不允许存在两个竞争实现,而是立即创建一个通用解决方案。另一个重要方面是不过度通用化——该解决方案仅支持WAV和MP3,不以任何方式预期其他格式。
这条规则的另一个部分是:开发人员理想情况下不应该以相似或相同的方式修改代码的不同部分。他们不应该在更新类B时"记得"更新类A。他们不应该知道当常量X改变时,必须更新文件Y。换句话说,不仅两个实现不好,两个位置也不好。虽然不可能总是这样实现系统,但这是值得追求的目标。
如果确实需要在两个位置存放某些内容,请确保当它们"不同步"时系统会明显失败。编译应该失败,总是运行的测试应该失败等。应该不可能让它们失去同步。
当然,这条规则最简单的部分是经典的"不要重复自己"原则——不要有两个表示完全相同事物的常量,不要有两个做完全相同事情的函数等。
这条规则可能还有其他应用方式。总体思路是:当你想为单一概念提供两个实现时,应该以某种方式将其变成单一实现。
在重构时,这条规则有助于发现需要改进的地方并提供指导。当看到系统中的重复逻辑时,应该尝试将这两个位置合并为一个。如果还有另一个位置,就将其合并到新的通用系统中,以此类推。也就是说,如果需要将多个不同实现合并为一个,可以通过每次合并两个实现来进行增量重构,只要合并确实使系统更简单(更容易理解和维护)。有时需要找出最有效的合并顺序,但如果无法确定,不必担心——只需每次合并两个,通常最终会得到解决所有问题的单一优秀方案。
同样重要的是不要在不应该合并的时候合并。有时将两个实现合并为一个会增加整体系统复杂性或违反单一职责原则。例如,如果系统中Car和Person的表示有稍相似的代码,不要通过将它们合并为单个CarPerson类来解决这个"问题"。这不太可能降低复杂性,因为CarPerson实际上是两个不同的事物,应该用两个单独的类表示。
这不是宇宙的硬性法则——更像是我在增量开发时用于设计判断的强力指南。然而,它在重构遗留系统、开发新系统以及提高代码简洁性方面非常有用。
-Max
(文章后续为读者评论部分,此处省略具体评论内容翻译)