第三章 软件创建的先决条件
3.4 结构设计先决条件
3.4.1 典型的结构要素
有许多要素是一个好的系统结构所共有的。如果你是一个人在独自开发一个系统,那么你 的结构设计工作,或者说顶层设计工作,将与你的详细设计工作重叠。在这种倩况下,至少你 应该考虑每一个结构要素。如果你正在从事一项由别人进行结构设计的系统工作,你应该不费 什么劲儿就能找到其中的重要部分。下面是一些在两种情况下都需要考虑的要素。
程序的组织形式
一个系统结构首先需要一个总体上的概括性描述。如果没有的话,从成千个细节与几十个 独立模块中勾画出一幅完整的图画将是一件十分困难的事情。如果这个程序仅仅是一个由十二
块积木组成的小房子,那么或许连你那两岁的儿子也会认为这很容易。然而,对于一个由十二 个模块组成的软件系统,事情恐怕就困难得多了。因为你很难把它们组合到一起,而如果不能 把它们组合到一起,你就不会理解自己所开发的这一个模块对系统有什么贡献。
在结构设计中,你应该能找出最终组织形式的几种方案,并且应该知道为什么选中了现在 这种组织形式。如果开发模块在系统中不被重视,会使人产生挫折感。通过描述这些组织形式 的替代方案,我们就可以从结构设计中找出选择目前方案的原因,并已知道每一个模块的功能 都仔细考虑过了。回顾设计实践发现,设计理由对于维护性来说,与设计本身是同样重要的
(Rombach 1990)
在结构设计中,应该在程序中定义主要模块。在这里,“模块”并不是指子程序。在结构 设计中通常不考虑建立模块一级的子程序。一个模块是一个能完成某一高级功能的子程序的组 合,例如,对输出结果进行格式化,解释命令,从文件中读取数据等。在需求定义中列出的每 一项功能,都应该有至少一个模块覆盖这项功能。如果一项功能由两个或更多的模块覆盖,那 么它们之间应该是互补的而不是相互冲突。
每一个模块作什么应该明确定义。一个模块应该只完成一项任务而且圆满完成。对于与它 相作用的其它模块情况,你知道得越少越好。通过尽可能地降低模块之间的了解程度,就可能 把设计信息都集中在一个模块中。
每个模块之间的交界面也应该明确定义。结构设计应该规定可以直接调用哪些模块,哪些 模块它不能调用。同时,结构设计也应该定义模块传送和从其它模块接收的数据。
变动策略
创建一个软件系统,对于程序员和用户来说,都是一个逐渐学习的过程,因此在这个过程 中作出变动是不可避免的。变动产生的原因可能是由于反复无常的数据结构,也可能是由于文 件格式和系统功能改变,新的性能等而引起的。这些变动有时是为了增加新的能力以便强化功 能,也有时是版本增加而引起的。所以结构设计所面临的主要挑战便是增强系统的灵活性,以 便容纳这类变动。
结构设计应该清晰地描述系统应付变动的策略。结构设计应该表明:设计中已经考虑到了 可能的功能增强变动,而且,应该使最可能的变动同时也是最容易实现的变动。比如,假设最 可能的变动是输入或者输出格式、用户界面的方式或者处理需求,那么结构设计就应表明已经 预先考虑到了这些变动,而且,其中每一个单一的变动,只会涉及到数量有限的几个模块。在 结构设计中应付变动的手段可能是非常简单的,比如在数据文件中加入版本号,保留一部分区 域以备将来使用,或是设计一些可以添加内容的文件。
结构设计中应该说明用于延缓变动的策略。比如,结构设计中可能规定应使用表驱动技术 而不是手工编码技术。它还可能规定表所使用的文件应该保存在一个外部文件中,而不是编码 在程序中,这样,可以不必重新编译就可以对程序作出调整。
购买而不是建造的决定
创建一个软件的最彻底的办法并不是创建——而是去购买一个软件,你可以购买数据库管 理系统、屏幕生成程序、报告生成程序和图形环境。在苹果公司 Macintosh 或者微软公司 Windows 环境下编程的一个主要优点是你可以自动获得许多功能;图形程序,对话框管理程序,
键盘输入与处理程序,可以自动与任何打印机或者显示器工作的代码,等等。
如果计划中要求使用已有的程序,那它就该指出如何使这些重新被使用的软件适应新的需 求,而且它应该证明这个软件可以通过改动来满足新的需求。
Barry Boehm 在 1984 年指出:从长远观点来看,重新使用旧软件是提高生产率的首要因素。
购买代码可以降低计划、详细设计、测试和调试的工作量。 Caper Jones 在 1986 年报告如果购 买的代码从 0 上升到 50%,那么生产率可以提高一倍。
主要的数据结构
结构设计应该给出使用的主要文件、表和数据结构。同时,还应给出考虑的替代方案并评 审作出的选择。在《Software Maintenance Guidebook》一书中,Glass 和 Noiseux 认为数据结构 对系统维护有举足轻重的影响,因而,它应该在经过全盘考虑之后,才能选定(1981 年)。如 果某一应用需要维护一个用户识别表,而结构设计又选中了顺序存取表来实现,那它就该解释 为什么顺序存取表要好于随机存取表、堆栈和哈希表。在创建阶段,这些信息可以使你对结构 设计有一个比较深刻的理解。在维护阶段,这些信息也是非常宝贵的。如果没有它们,你就会 有一种看一部不带字幕的外国电影的感觉。
不应该允许一个以上的模块访问数据结构,除非是通过访问子程序,以使得这种访问是抽 象的而且是可控的。这将在 6.2 “信息隐蔽”部分中详细论述。
如果一个程序使用了数据库,那么结构中应该规定这个数据库的组织形式和内容。
最后,应该遵循数据守恒定律:每一个进入的数据都应该出去,或者与其它数据一道出去,
如果它不出去,那它就没有必要进来。
关键算法
如果结构设计依赖于某一特定算法,那它应该描述或指出这一算法。同主要数据结构一样,
结构设计中也应该指出考虑过的算法方案,并指出选中最终方案的原因。比如,如果系统的主 要部分是排序,而结构设计中又指定了排序方式是堆排序,那它就要说明为什么采用堆排序的 方法,以及未采用快速排序或插入排序的理由。如果是在对数据作出某种假定的基础上才选中 堆排序的,那就该给出这个假定。
主要对象
在面向对象的系统中,结构中应指出要实现的主要对象,它应该规定每一个对象的责任并 指出每个对象之间是如何相互作用的。其中应包括对于排序层次、状态转换和对象一致性的描 述。
结构中还应该指出考虑的其它对象,以及选择这种组织形式的原因。
通用功能
除了特定程序的特定功能,绝大多数程序中都需要几种在软件结构中占有一席之地的通用 功能。
用户界面。有时用户界面在需求定义阶段便已经规定了。如果没有的话,那就应该在结构 设计中作出规定。结构中应该定义命令结构,输入格式和菜单。用户界面的精心结构设计,往
往是一个深受欢迎的软件与被人弃之不用的软件间的主要不同之处。
这部分结构应该是模块化的,这样,当用新的界面代替旧的时,就不致影响到处理和输出 部分。比如,这部分结构应该使得用批处理接口替代交互式界面的工作非常容易。这种能力是 很有用的,特别是在单元测试和子系统测试阶段。
用户界面设计本身就值得写一部专著,但本书并未涉及这一内容。
输入/输出。输入/输出是结构中另一个应引起重视的部分。结构中应规定采用向前看、
向后看还是当前规则的查询方式。同时,还应该指出在哪个层次上检查输入/输出错误,是在 区域层次、记录层次还是在文件层次上。
内存管理。内存管理是结构设计中应该处理的另一个重要部分,结构中应该对正常和极端 情况下所需要的内存作出估计。例如,如果你正在写数据表,那么结构就应估计其中每一个单 元所需的内存。它还应估计正常表格和最大表格所需要的内存。在简单情形下,这种估计应表 明内存在某项功能的实现环境中是正常的。在复杂情况下,可能不得不建立自己的内存管理系 统,如果是这样,那么内存管理程序的设计应和系统其它部分一样,需要认真对待。
字符串存储。在交互式系统中,字符串存储也应在结构设计阶段予以重视。在这种系统中,
往往包含了大量的提示、帮助信息和状态显示。应该估计被字符串所占用的内存。如果程序是 商用的,那么,结构中应该考虑到典型的字符串问题,包括字符串的压缩,不必修改代码即可 保持字符串,以及保证在译成外文时对代码的影响将是最小的。结构设计可以决定字符串的使 用方法,是编码在程序中,还是把它保存在数据结构中。是需要时通过存取子程序调用,还是 把它存在一个源文件中,结构设计应该指明采用哪种方法及其原因。
错误处理
错误处理已成为当代计算机科学中最棘手的问题,没有谁能担负起频繁应付它的负担。有
错误处理已成为当代计算机科学中最棘手的问题,没有谁能担负起频繁应付它的负担。有