zoukankan      html  css  js  c++  java
  • 软件设计的复杂度

    http://blog.csdn.net/horkychen/article/details/45381743

    什么是软件设计的复杂度

    软件技术发展的使命之一就是控制复杂度(Complexity)。从高级语言的产生,到结构化编程,再到面向对象编程、组件化编程等等。关于复杂度的定义并不一致,想要详细了解的可以读读The Many Faces of Complexity in Software Design.

    英文中Complex和Complicated有着微妙的不同。但总结起来,软件复杂度偏负面意义,包括两个要点: 
    - 难以理解 (难以维护和扩展。) 
    - 无法预测行为 

    复杂度是随着软件规模不断扩大而必然产生的。它本身又是一个相对的概念,同一个系统对于设计者、开发者,以及维护者而言,复杂度是不同的。不同时期,一个程序员所能掌握的复杂度也是不同的,这也是一个程序员不断提升的目标。

    既然业界已经对抗复杂度几十年了,我们就来整理一下。

    以分解降低复杂度

    以分解的方式进行的设计,主要特点是: 
    - 分离职责(Seperation of Concerns,参考单一职责原则) 
    - 关注接口(定义交互)

    这是最常使用的技术了。将一个大问题,不断的拆解为各个小问题进行分析研究,然后再组合到一起。在西方称为Divide and Conquer Principle (分而治之原则)。

    在结构化编程的时代,提倡模块化(Modularization)。最早提出软件复杂度的工程师提出了基于组件的软件(Component Based Software)。不知道是不是从乐高积木上得到的启发,将系统中拆分为不同的组件,各自实现,然后再组装在一起。

    在架构设计中,无论是C/S风格,分层,还是N-Tier,SOA,和前面组件式一样,都是在进行分解,它们都更加强调组合交互。设计上,分分职责,定义好接口,就可以各自开发了。然后将交互限定于接口层,就能够很好的控制整个系统的复杂度。

    比如应用层使用一个语音库(Speech Library,一个以库的形式的模块化应用), 根本不用关心其内部实现,只要了解如何使用它的API就可以了。

    改善低赖降低复杂度

    改进依赖关系的要点: 
    - 无环形依赖 
    - 稳定依赖原则(SDP)

    分解可以降低系统层级的复杂度,但还有一种复杂度无法解决,即依赖的问题。这在敏捷软件开发:原则、模式与实践中关于依赖性的讨论很详细。当参与者增加时,交互就会随之变得复杂。而当前的软件规模,系统中的各类SDK的API, Framework的API, 各种第三方库越来越多,模块间的依赖就会越来越复杂。 
    cycle_dependencies 
    显然系统中的模块或者组件太多了,需要进一步整理。但真正的问题在于出现了双向和环形的依赖。比如上图中负责计算的Computing模块也依赖到了UI模块,或许是因为UI层持有一个计算所需的关键参数。如果UI层变更,就可能会影响到Computing,出现无法预测的行为,给客户以不稳定的印象。

    所以模块间的依赖关系必须简化,绝对不能出现环形的依赖。以Chromium为例,它对各个模块的依赖就有严格的定义,并且有DEPS在编译期保证程序员不会犯错。下图是Chromium Component依赖关系的定义,其中Component内部目录的依赖关系也有定义: 
    chromium_component

    当底层模块需要依赖上层模块的实现时,就要通过依赖倒置(DIP)来处理。简单而言就是由底层模块定义一个接口,要求上层模块实现并注入到底层模块。 
    DIP

    使用抽象降低复杂度

    人的学习过程最有效的一种方式就是归类,其中运用的就是抽象思维。面对变幻无常的天气,人类通过对云的形状进行抽象,就可以预测天气变化。这里有一个抽象建模的过程。

    抽象并不是面向对象语言专属,其实它和语言无关,本质上是一个思考的方式。它和分离的最大区别在于,抽象强调将细节隐藏,只关注核心的本质。而后者则重视于细节问题的分解和组合。

    以求固定两点的最快捷路线为例。从分离的角度来,可以分解为以下问题:

    1. 步行需要多少时间?
    2. 乘公共交通多少时间?
    3. 乘的士多少时间?
    4. 组合以上答案,再评估哪一个最快捷的方式。

    而从抽象的角度来看,解决的思路会是这样的:

    遍历所有可能的交通工具,取耗时最小的: 
    1. 步行 
    2. 乘公共交通 
    3. 乘的士

    先给出一个抽象的解决思路,至于细节,则是进一步的实现。抽象最大的威力在于它比实现要稳定,也最能用于固化核心设计。在开发过程中,常常围绕着各种细节讨论,似乎抽象过于虚。但是如果没有以抽象来建立系统的设计全景,有些讨论将变得效率低下。

    敏捷软件开发:原则、模式与实践中,Martin大叔简单的用抽象类在总类个数中的占比作为抽象性的度量,再结合稳定性的度量,用来评估设计。详情可以参考组件设计原则之概念篇(三)

    以通俗原则降低复杂度

    设计和实现时引入不必要的抽象或分解,也是一种复杂度.考虑扩展性也是确定会发生的需求才要考虑进来,否则就是引入不必要的复杂性.这也是敏捷设计所倡导的. 
    一些约定俗成的命名,常常隐含着设计.比如Observer, Client, Adapter等等.我们要学习这些模式,也要准确加以命名.否则很容易造成理解上的问题.

    小结

    软件设计是一个平衡的过程,软件的复杂度决定着系统的可维护性、可扩展性和灵活性。我们再来回顾一下前人定义出软件设计的三原则:模块化、抽象和信息隐藏。McCabe也曾有论文专门讨论将圈复杂度应用度量设计的复杂度,不过已经历史久远。现在来看以依赖关系来评估设计的复杂度会更为有效。有兴趣可以了解一下CppDepend。另外Google的工程师则基于LLVM IR也实现了一个工具用于依赖关系分析(Generateing Precise Dependencies for Large Software)。

    转载请注明出处: http://blog.csdn.net/horkychen

  • 相关阅读:
    Linux笔记(九)
    Linux笔记(八)
    Linux笔记(七)
    Linux笔记(五)
    Linux笔记(六)
    Linux笔记(四)
    Linux笔记(三)
    hdu 1009 qsort运用
    dfs模板 二部图的最大匹配
    拉格朗日函数c++
  • 原文地址:https://www.cnblogs.com/feng9exe/p/5592577.html
Copyright © 2011-2022 走看看