下面我们将寻找一种新的度量软件复杂度的方法,而对新方法的检验将主要通过对设计模式进行度量来完成。这么做的主要原因是设计模式是这些年来软件领域中沉淀下来的精华,非常有代表性,如果新的度量方法可以适用于设计模式,那就可能适用于大多软件。
由第7章,我们知道影响软件复杂度有如下因素。
1)对概念的分解是否正交。
2)层次的多少。
3)时序。
4)数据流。
那么软件复杂度的度量也必然需要从这4个维度进行考量。
◆与概念分解是否正交相关联的指标有两个:概念个数的多少以及其内在复杂度。
●代码中直接可见的东西都是概念:包/名字空间、类、方法/函数等。概念个数则是上述概念的计数。
●对方法/函数进行度量的指标是圈复杂度。而更大单位概念的复杂度则可以表示为其内含方法/函数的圈复杂度的和。
◆与层次多少相关联的指标是:层次数。
●在最终代码层面,层次数体现为不同时期的栈层次。
◆与时序相关的指标是:动作时序
●在代码层面动作时序即是函数/方法的调用顺序。对此有影响的是需求决定的内在逻辑和概念间的静态结构(继承、包含、聚合、使用等),以及线程/进程。
◆与数据流相关的指标则是:时序流上不同动作间传递信息量的多少,以及数据流的扩散方向。
●在代码层面数据流将表现为方法/函数的参数和方法/函数内部操作引起的数据变迁。
上面的总结,颇为抽象,为使这种度量方法更为可视化,我们需要再次回到软件的本质。软件最终可以看做是概念和逻辑的复合体,而复杂度的度量似乎应该回到对概念和逻辑的个数以及逻辑关系的特质上来。虽然概念间的逻辑关系有层次、继承、包含、聚合、使用等,但我们眼下主要考察动态关系,一旦两个概念发生关联,我们就用箭头把这种关联关系标识出来,这样最终会把各种概念连接成一张图。这种关联可以抽象为从简单到复杂的一个过程,由简单到复杂的各种图如图8-1~图8-6所示。在这里概念开始分解,一分为二,彼此关联。比如说,为了发出命令,那么必须的两个概念是:Invoker和Receiver。接下来,概念个数增加:二变为多。比如说,在分化为Invoker和Receiver之后,Receiver开始增加。
图8-1 概念开始分解
图8-2 概念个数增加
如果一个菜单里面只有一个选项(命令),那是图8-1。如果菜单中有多个选项(命令)那就是图8-2。
再接下来,层数由二变多。比如说,当导入Command设计模式后,Invoker不再直接与Receiver直接发生关联,中间要加入Command这一层。Invoker→Command→Receiver。(www.xing528.com)
图8-3 概念个数增加的同时,层次开始增加
图8-4 数据流向开始复杂化,由树状向网状转化
导入Command模式后,假设存在者4个命令:Copy、Paste、Save和Quit。那么Paste将以Copy动作为基础,而Quit之前则要调用Save。
图8-5 多线程/多进程出现,数据流进一步复杂化
假设说开了一个线程监听指定的端口,并需要把获得的信息定期地显示在UI上,同时UI上也支持Copy和Paste,Save和Quit,那么就必须对信息读写进行同步处理。
图8-6 概念间数据关联加强,传递的信息增加
假设网络上监听的内容越来越多,那么同样的逻辑下传递的信息量会增加,同样的Copy和Paste命令其内部细节也会越来越多。
接下来当某一具体概念自身复杂到一定程度时,其自身需要分裂,返回到图8-1。比如说,如果Copy的内容不只有文本,还有图像,还要支持跨进程的拷贝,那无疑的Copy这一命令自身需要进一步分化。
如果考察上面的图形,我们可以发现:单纯以图形的角度看,直线、以一点为中心的射线是最简单的。联系的脉络比较少,也清晰可见。树状是比较简单的,虽然层次可能会变得很多,但层次相对分明。网状则比较复杂。关联关系多,层次也不好划分,基本上很难限制变化的影响范围。导入了多线程/多进程的状况更为复杂,这时需要考察信息同步的正确与否。一旦概念间的关联强度增强,那么情况会更坏。
这中间并不存在哪种图形一定不好的问题,其中关键的尺度是复杂度的增加是否获得了足够的收益,无谓的复杂度则是一定要避免的。
概念间关联的强度
我们来看几个具体的场景,而后考察一下各个场景下,概念间关联的强度。
第一个场景是,泛型类中具体算法和算法操作的对象。这个时候算法只需要知道待操作对象有特定的接口即可。
第二个场景是,接口类或者说纯虚类的使用。这个时候使用接口类的地方需要知道接口类的名字和接口的名字,但无需关注接口背后的实现。
第三个场景是,具体类的使用。这个时候使用具体类的地方事实上既需要知道具体类的名字,也要知道接口的名字,还要知道具体类的实现—否则没法实例化具体类。
单以上述3个场景而论,其概念间关联的强度应该是依次增强的。
总结一下上述3个场景,我们可以发现,无论如何不能少的是接口。但即使同样是接口,其关联强度仍然不同。比如说,都是在第二个场景下,一个接口只使用基本数据类型,而另一个接口却使用了自定义的类,那么大多时候后一种情形的关联强度更高—因为接口上需要传递的信息量更高。
免责声明:以上内容源自网络,版权归原作者所有,如有侵犯您的原创版权请告知,我们将尽快删除相关内容。