回归设计的质量属性,来看“复用是美好的还是邪恶的”
2024-9-21 16:31:15 Author: mp.weixin.qq.com(查看原文) 阅读量:0 收藏

本周阿里技术公众号发表了率鸽老师的文章《架构设计的悖论,复用是美好的还是邪恶的》,文章提到:“错误的抽象、错误的代码复用,所引发的复杂性无限蔓延,对系统的危害比面条代码强大一百倍”。 我赞同这个观点。

不过,究竟什么是“错误的抽象”,什么又是“正确的抽象”呢?什么时候的复用是正确的,什么时候的复用又是不正确的呢?是不是“抽象层次越高,表达能力越低,越难以理解”? 我也来写篇文章,说一下我的理解,希望能对读者有所启发。

1

架构方法与理性权衡

当我们说:“今天出门应该是火车好,还是飞机好”的时候,都是有附加条件的。例如,乘坐时间的长短、花费的多少、早班飞机还是晚班飞机、目的地有台风和暴雨的概率等等。

我们从早期的没几个人关心可复用性,到现在的训练有素的工程师,都能够对代码中的重复保持警惕,开始考虑设计和代码的复用性,是巨大的进步。在这方面,敏捷运动起到了很大的启蒙作用。

敏捷社区向来有“语不惊人死不休”的传统,而且效果显著。可复用性的理论很早就有,但是正是Duplication is Evil (DIE - 是不是很可怕)、Don't Repeat Yourself (DRY - 没有重复的才是“干”的,没水分 )这些词的巧妙组合,让人过目不忘,起到了很好的传播效果。

但是,随着把这些设计原则真的投入开发活动,现实的复杂性就会让人开始怀疑:重复真的那么糟吗? 复用真的那么好吗?为啥我感觉消除了重复之后,代码更难理解了呢?为啥说好的可复用,但是最后完全没用上,而且还把代码搞复杂了呢?

设计的好坏,不是单一维度。它是结合多种因素权衡的结果。

关于从哪些因素权衡,在软件设计领域有一个标准的概念,叫做“质量属性效用树”。我之前也写过一篇和这个话题相关的文章,可以参考。 (如何在爆雷之前发现架构问题?——ATAM方法的应用与适配、  浅谈卓越架构师的思维模式和技能发展

孟子说:“鱼与熊掌不可得兼,舍鱼而取熊掌者也”。在和复用这个相关的主题上,哪些是鱼,哪些是熊掌呢?

2

高质量设计的外部特征

我非常重视“从价值视角来考虑设计原则”这个问题。基于这个出发点,我在《软件设计:从专业到卓越》一书的章节编排上,把“优质代码的外部特征”放在了第一章。

我认为,一个设计好不好,需要从5个关键的方面进行考察。一个有追求的工程师,需要对这5个方面烂熟于心,努力做到。同时,也有一个细节,我的排列顺序是经过了考虑的。也就是说,无论是个人能力的原,还是技术约束的原因,导致这5个方面不容易在某个具体决策中同时满足,前面的具有优先权

  1. 实现了期望的功能:这个不需要解释了。虽然其实很难做到。

  2. 缺陷尽量少:这个也不需要解释了。它有两个方面:一个是通过技术手段减少缺陷,一个是减少缺陷的影响(例如尽早发现缺陷)

  3. 代码必须易于理解。这个很重要。如果你的“可复用”是以不可理解为代价的,那怎么可能“可复用”呢? 当然了,可理解也不总是以牺牲可复用为代价的。 这二者大多数时候是兼容的。

    - 大多数时候,重复的代码是伤害可理解性的。有时候,如果你遇到的代码,是那些表面看相同,实际上还有点差异的代码,那就不是是不是易于理解的问题,而是给人挖坑的问题了。

    - 消除重复不仅仅是消除重复,它强迫我们思考,这些概念是不是同一个概念?

    - 面条代码并不那么美好。我对于可理解性有个原则:能通过阅读 API 声明去理解代码,就不要去阅读 API 是如何实现的;能通过观察代码结构(如类名、包名、方法名)去理解代码,就不需要去阅读代码的内部实现;能通过阅读代码结构直接理解,就不需要再去阅读文档和注释。

    不过,确实,有时候一些重复,反而会增加可理解性。例如,我写自动化测试的时候,对类似的测试场景,我有时候会选择简单重复,而不是去做步骤的抽象,因为那样会让我觉得不够“一目了然”。

    下面这句话比较重要:特别圈一下:

    不纠结原因(无论是技术约束、场景约束还是能力约束),只要是二者发生冲突,易理解性优于可复用性。

4. 代码需要易于演进:代码一定是要被修改的。同样,如果“可复用”牺牲了演进能力,那这个可复用就值得怀疑。

我们用前文作者提到的“业务中台”的例子来做个分析:

由于J和K都包含了一个L的能力,所以我们把L抽象出来。在理想情况下,这确实既提升了可复用性,也提升了可演进能力。很好,没毛病。

问题在于细节。如果我们对L的认知还不够,导致L根本不稳定,天天发生变化呢?如果L的变化只是发生在内部,接口保持不变,那还好,JK无感知,影响不大。如果L的变化导致接口变化,而且经常变,那JK就有苦头吃了。算了算了,我们还是不要复用了,分道扬镳吧。——这是正确的抉择:如果可演进能力和可复用能力是一致的,那就和平共处。如果可演进能力和可复用能力是冲突的,那就优先可演进能力。

3

写在最后

作为一名架构师,我最早识别到质量属性的冲突,当然不是来自孟子,而是ATAM方法学。我最早知道把关键的质量属性排序,也是来自于此。但是,如何把设计原则清晰的表述让人理解(也就是前面5条的排序),灵感和部分内容都是来自Kent Beck。Kent Beck的简单设计四原则是:

  • 提供了所需的功能(等价于第1条)

  • 可沟通 (等价于第3条)

  • 没有重复

  • 最少的类和方法

我所做的改进,是把这个思路应用到了优质代码和设计的总体评价上,并且把它区分为“外部特征”和“内部特征”。 这构成了《软件设计》的第一章和第二章的内容。

下面贴一下第一章最后的思维导图。


文章来源: https://mp.weixin.qq.com/s?__biz=MzU4NDU4OTM4OQ==&mid=2247509921&idx=1&sn=0aa305da22aa05f72b937e87b8a5d092&chksm=fd956e83cae2e7951d24f42b174bd4220b280c71ee4021e35eef3aebf89c67cd1e62ea056e97&scene=58&subscene=0#rd
如有侵权请联系:admin#unsafe.sh