引言
这是一篇来自明尼苏达大学的关于软件工程原理的文章,里面总结了一些我们耳熟能详的软件设计和开发原则,解释了其由来和关系,并举例进行了说明。之前没有关注过这几个原则之间的关系,今天和大家一起重温一下。
关注点分离
将关注点分离的一个原因是我们不得不接受人类需要在有限的背景下工作。正如G. A. Miller所说的那样,人类的大脑仅限于一次处理大约7个单位的数据。一个单位是一个人已经学会作为一个整体来处理的东西,比如一个单一的抽象或概念。虽然人类形成抽象的能力似乎是无限的,但是抽象成有用的工具却需要时间,也需要考虑复用,换句话说,就是需要作为一个单位来处理。
例如,在为数据结构组件设计行为时,通常需要处理两方面的事情,一方面是操作数据的基本功能,另一方面是对数据完整性的支持。如果将这两方面的事尽可能多地划分为单独的调用函数集,那么这个数据结构组件通常会更容易使用。如果接口文档分别描述这两个方面,对调用方来说肯定是有益的。另外,对基本算法、数据完整性和异常处理的单独处理,可以让实现文档和算法描述更易理解和维护。
应用关注点分离还有一个重要的原因,那就是避免软件工程师在尝试优化产品质量时必须处理更为复杂的情况。这也是在对算法的复杂性研究中,我们可以得到的一个重要的教训。通常优化单个可测量值容易有有效的算法,但是需要优化可测量组合的问题几乎总是NP完全的。虽然这是一个未经验证的事实,但大多数复杂性理论专家认为,NP完全问题不能通过在多项式时间内运行的算法来解决。
基于以上考虑,对不同的值单独进行处理就变得有意义。这可以通过在不同的时间处理不同的值来完成,也可以通过结构设计,将实现不同值的责任分配给不同的组件来完成。
再比如,运行效率通常与软件的其它方面会有所冲突。处于这个原因,多数软件工程师建议将运行效率作为一个单独的问题来处理。在软件设计满足其它标准之后,就可以检查和分析其运行情况,查看时间花费在哪里。如有必要,就可以修改耗时较多的代码来改进。Ken Auer和Kent Beck在[VCK96,pp 19-42]中的文章”Lazy optimization: patterns for efficient smalltalk programming”中深入描述了这个想法。
模块化
模块化是我们谈到软件设计时,最常说的一个原则,实际上它只是关注点分离原则的一个专门方面。遵循模块化原则,意味着需要根据功能和职责将软件划分为多个组件。Parnas曾经写了一篇讨论模块化考虑因素的论文。WWW最近描述了一种在面向对象的上下文中应用模块化原则的责任驱动方法。
抽象化
和模块化一样,抽象原则也是关注点分离原则的一个具体应用。运用抽象化原则,就是将软件的行为与实现分开。它需要我们学会从两个角度看待软件和软件组件:一个是做什么(行为),另一个是如何做(实现)。
未能将行为与实现分开,是导致不必要耦合的常见原因。例如,在递归算法中,常常要引入额外的参数,让递归可以正常运行。但是,要注意为额外参数提供正确的初始值,以便被非递归shell调用。否则,调用方必须因处理额外参数而增加更复杂的行为,以后将递归算法调整为非递归算法,还需要修改调用方代码。
契约设计是抽象化原则的重要方法,它的基本思想由Fowler和Scott 草拟,最完整的论述由Meyer给出。
对变化的预测
计算机软件实际上是现实问题的自动化解决方案。
问题出现在软件用户所熟悉的某些上下文或域中,域定义了用户需要使用的数据类型及它们之间的关系。软件开发人员熟悉以抽象方式处理数据的技术,这些技术主要是处理结构和算法,而不考虑数据的含义或重要性。软件开发人员可以从图形和图形算法的角度进行思考,而无需为顶点和边附加具体的含义。
因此,为问题制定自动化解决方案,对于软件开发人员及其客户来说,都是一种学习过程。软件开发人员需要学习相关业务领域知识,了解客户价值,要知道哪种形式的数据呈现对客户最有用,什么样的数据至关重要,需要特殊的保护措施。客户需要通过学习看到软件技术可提供的可能的解决方案范围,还需要学习如何评估可能的解决方案,以确定其是否可以满足需求。
如果要解决的问题比较复杂,那么假设短时间内获得最佳解决方案就是不合理的。但是,客户确实希望及时找到解决方案,而且通常他们不愿意等到完美的解决方案出来,他们希望的是尽快找到合理的解决方法,以后再改进。
为了实现及时的解决方案,软件开发人员需要知道需求,需要知道软件应该如何运行。变更行动原则意识到软件开发人员及其客户学习过程的复杂性,要求需要尽早给出初步需求,但随着学习过程的进行,应该提供对需求更改的可能。
耦合性是应对变化的主要障碍。如果两个组件是紧耦合的,只更改其中一个,而不更改另一个,软件将不能正常工作。相反,内聚性则方便进行变更,当需求发生变化时,更为内聚的组件则更容易复用。如果组件将多个任务集于一身,则在变更时可能需要将其拆分。
通用性
通用性原则与预期变化的原则紧密相关。在软件设计时,要重视通用性,不要人为添加限制。两位数的年份数字就是一个很好的反例,这个设计导致了千年问题。尽管两位数的限制在当时看来是合理的,但好的软件经常能存活到其预期的寿命之外。
还可以看一个例子。客户通常试图满足一般性需求,但他们根据当前的做法和理解来提出需求,随着他们越来越熟悉软件的自动化解决方案带来的可能性,会从更高层次看到他们需要什么,而不是他们目前正在做的。这种区别类似于抽象性原则,但其效果在软件开发过程的早期就已经显现了。
增量开发
Fowler 和 Scott 对增量软件开发过程进行了简短但深思熟虑地描述。在这种过程中,将不断以较小的增量来构建软件,比如一次只添加一个用例。
增量式开发过程利于简化验证过程。如果您通过添加少量增量来开发软件,那么,在验证时,只需要处理添加的那部分。如果检测到错误,则引发这些错误的代码是明确的、隔离的,更容易纠正。
经过精心规划的增量式开发,还可以简化对需求变化的处理。规划时,必须找出最有可能被更改的用例,并将它们放在开发过程的末尾。
一致性
采用一致性原则,基于这一的认知,即在熟悉的上下文中做事会更容易。例如,编码风格就是设置代码文本一致性的方式。有了编码风格规范,将使代码阅读更为容易,也为程序员自动化代码输入成为可能,可以释放程序员的思维来处理更为重要的事情。
在更高的层次上,一致性开发原则还体现为处理常见编程问题的习惯用语。Coplien 出色地介绍了在C++中使用习惯用语进行编码。
在设计图形用户界面时,一致性也十分重要,表现在两个方面。首先,一致的外观和感觉使用户更容易学习使用软件。一旦掌握了界面使用的基本操作要点,就不必为不同的软件应用程序重新学习它们。另外,一致的用户界面也可以促进接口组件的复用。图形用户界面系统具有一组框架、窗体和其它视图组件,这些组件支持通用外观。它们还具有用于响应用户输入的控制器集合,以支持常见的感觉。通常,外观和感觉是结合在一起的,例如,在弹出菜单和按钮中。这样的组件可以由任何程序来使用,一致性带来的高度复用性。
Meyer 将一致性原则应用于面向对象的类库。随着可用库变得越来越复杂,必须将它们设计为向调用端提供一致的接口。例如,大多数数据收集结构都支持添加新的数据项,如果add始终作为这类操作的名称,则学习使用集合就容易得多。
结语
7个原则介绍完了,原文也简单介绍了它们之间的关系,下图稍微扩展了一下,其实软件工程研究的就是如何又好又快地研发软件,而简单化应该就是其根本原则。
声明:本站部分文章及图片源自用户投稿,如本站任何资料有侵权请您尽早请联系jinwei@zod.com.cn进行处理,非常感谢!