3.1前期准备的重要性
通常我们用建筑过程来隐喻软件构建的过程(这一点很容易看出来,很多常见软件开发术语都是从建筑术语衍生出来的),那么你见过哪一座安全、美丽、舒适的大楼是在没有建筑工程师前期规划下,直接开始建造完成的?一个好的前期的准备工作意味着降低了构建过程中的风险,节约大量的构建成本和构建时间。
如果你理解了上面一段话的含义,你就会明白“软件开发不仅仅是写代码”。当然,首先你得先让你的BOSS明白这句话的含义,你才能顺利地去实践它。
用食物链来类比软件开发过程:架构师吃掉需求,设计师吃掉架构,最后程序员消化设计。在这条食物链中,任何一个环节被污染,那么开发出的软件就不会是健康的。
惠普、IBM、休斯飞机公司、TRW以及其他组织的研究人员发现,在构建活动开始之前清除一个错误,那么返工的成本仅仅是“在开发过程的最后阶段(在系统测试期间或者发布之后)做同样的事情”的十分之一到百分之一。
3.2辨明你所从事的软件的类型(P31)
3.3问题定义的先决条件
问题定义
在开始构建之前,首先要满足的一项先决条件是,对这个系统要解决的问题做出清楚的陈述(产品设想/设想陈述/任务陈述/产品定义),我们这里称为"问题定义"。
问题定义在具体的需求分析工作之前,而需求分析是对所定义的问题的深入调查。
影响
如果没有一个良好的问题定义,你努力解决的可能是一个错误的问题。那么你讲浪费大量的时间。
3.4需求的先决条件
需求
“需求”详细描述软件系统应该做什么,这是达成解决方案的第一步。“需求活动”也称为“需求开发”、“需求分析”、“分析”、“需求定义”、“软件需求”、“规格书”、“功能规格书”、“规格”。
为什么要有正式的需求
- 明确的需求有助于确保是用户(而不是程序员)驾驭系统的功能。
- 明确的需求还有助于避免争论。
- 重视需求有助于减少开始编程开发之后的系统变更情况。
- 在大型项目中,如果在架构阶段检测到需求错误,那么修复它的成本通常是“在需求阶段检测并修复该错误”的成本的3倍。(越晚检测出需求错误,修复成本将越来越高)
- 充分详尽的描述需求,是项目成功的关键,它甚至很可能比有效的构建技术更重要。
稳定需求的神话
稳定的需求是软件开发的圣杯(“圣杯”一词通常代表众人追求的最高目标,而它的另一层含义则暗示渺茫希望)。
典型情况下需求会有多少改动?IBM和其他公司的研究发现,平均水平的项目在开发过程中,需求会有25%的变化。在典型的项目中,需求变更导致的返工占到返工总量的75%到85%。
所以我们能做到的就是采取一些步骤来使需求变更的负面影响最小化。
在构建期间处理需求变更
- 使用需求核对表来评估需求的质量(需求核对表P42)
- 确保每一个人都知道需求变更的代价
- 建立一套变更控制程序
- 使用能适应变更的开发方法
- 放弃这个项目
- 注意项目的商业案例
3.5架构的先决条件(如果您没有耐心看详细内容,请直接看本节末尾的“核对表:架构”)
软件架构师软件设计的高层部分,是用于支撑更细节的设计的框架。“架构”也称为“系统架构”、“高层设计”、“顶层设计”。有些人对“架构”和“高层设计”加以区分——架构指的是适用于整个系统范围的设计约束,而高层设计值得是适用于子系统层次或多个类的层次上的设计约束(但不是整个系统范围的设计)。
好的架构是的构建活动变得更容易。糟糕的架构则是构建活动几乎寸步难行。
架构的典型组成部分
- 程序组织
系统架构首先要以概括的形式对有关系统做出一个综述:
明确每个类对系统的贡献。(为什么要设计这个类?)
确保各个构造块之间不冲突,并且不能共同实现一种高级功能。
明确定义各个构造块的责任,每个构造块只负责某个区域的事情,对其他构造块负责的区域知道的越少越好。
明确定义每个构造块的通信规则。说明每个构造块能直接使用哪些构造块,能间接使用哪些构造块,不能使用哪些构造块。
架构中应该有那些曾经考虑过得最终组织结构的替代方案的记述,找到之所以选用最终的组织结构,而不用其他替代方案的理由。
- 主要的类
架构应该详细定义所用的主要的类:
- 指出每个主要的类的责任。
- 当前类如何与其他类交互。
- 类的继承体系、状态转换、对象持久化等的描述。
- 如果系统足够大,还应该描述如何将这些类组织成一个个子系统。
架构应该记述曾经考虑过得其他类设计方案,并给出选用当前的组织结构的理由。
架构无需详细说明系统中的每一个类,瞄准80/20法则:对那些构成系统80%的行为和20%的类进行详细说明。
- 数据设计
架构应该描述所用到的主要文件和数据表的设计。它应该描述曾经考虑过得其他方案,并说明做出选择的理由。
数据通常只应该由一个子系统或一个类直接访问;例外的情况:透过访问器类或访问器子程序——以受控且抽象的方式——来访问数据。
架构应该详细定义所用数据库的高层组织结构和内容:
- 架构应该解释为什么单个数据库比多个数据库好(反之亦然)。
- 解释为什么不用平坦的文件而要用数据库。
- 指出与其他访问同一数据的程序的可能交互方式。
- 说明会创建哪些数据视图。
- 。。。
- 业务规则
如果架构依赖于特性的业务规则,那么它就应该详细描述这些规则,并描述这些跪着对系统设计的影响。
- 用户界面设计
用户界面一般是在需求阶段进行详细说明,如果没有,就应该在软件架构阶段进行详细说明。
架构应该详细定义Web页面格式、GUI、命令行接口等主要元素。
架构应该模块化,以便在替换为新用户界面时不影响业务规则和程序的输出部分。
- 资源管理
架构应该描述一份管理稀缺资源的计划。稀缺资源包括数据库连接、线程、句柄等。
- 安全性
架构应该描述实现设计层面和代码层面的安全性的方法。如果尚未建立威胁模型,那么久应该在架构阶段建立威胁模型。
在定制编码规范的时候应该吧安全性牢记在心,包括处理缓冲区的方法、处理非受信数据(用户输入的数据、cookies、配置数据(文件)和其他外部接口输入的数据)的规则、加密、错误消息的细致程度、保护内存中的秘密数据,以及其他事项。
- 性能
如果需要关注性能,就应该需求中详细定义性能目标。性能目标可以包含资源的使用,这时,性能目标也应该详细定义资源(速度、内存、成本)中间的优先顺序。
架构应该提供估计的数据:
- 解释为什么架构师相信能达到性能目标。
- 哪些部分存在打不到性能目标的风险。
- 为了满足性能目标,需要在某些部分使用特定的算法或数据类型。
- 也可以包括各个类或各个对象的空间和时间预算。
- 可伸缩性
可伸缩性指系统增长以满足未来需求的能力。
架构应该描述系统如何应对用户数量、服务器数量、网络节点数量、数据库记录数、数据库记录的长度、交易量等的增长。
如果预计系统不会增长,那么架构应该明确列出这一假设。
- 互用性
如果预计这个系统会与其他软件或硬件共享数据或资源,架构应该描述如何完成这一任务。
- 国际化/本地化
- 输入/输出
输入输出是架构中值得注意的一个领域。架构应该详细定义读取策略是先做、后做还是即时做。而且应该描述在哪一层检测输入输出(I/O)错误:在字段、记录、流或者文件的层次。
- 错误处理
错误处理常被视为是“代码约定层次”的事情——如果真有人注意它的话。但是因为错误处理牵连到整个系统,因此最好在架构层次上对待它。
需要考虑的问题:
- 错误处理是进行纠正还是仅仅进行检测?
- 错误检测是主动的还是被动的?
- 程序如何传播错误?
- 错误消息的处理有什么约定?
- 如何处理异常?
- 在程序中,在什么层次上处理错误?
- 每个类在验证其输入数据的有效性方面需要负何种责任?
- 你是希望用运行环境中内建的错误处理机制,还是想建立自己的一套机制?
- 容错性
架构还应该详细定义所期望的容错种类。容错是增强系统可靠性的一组技术,包括检查错误;如果可能的话从错误中恢复;如果不能从错误中恢复,则包容其不利影响。
系统的容错策略:
- 系统在检测到错误的时候退回去,再试一次。如果第一次的结果是错误的,那么系统可以退回到之前一切正常的时刻,然后从该点继续运行。
- 系统拥有一套辅助代码,以备在主代码出错的时候使用。
- 系统使用一种表决算法。可以存在多个处理当前问题的类,每一个都是用不同的方法,然后系统对结果进行比较。根据系统内建的容错机制的种类,系统可以以这些结果的均值、中值、或众数作为最终结果。
- 系统使用某个不会对系统其余部分产生危害的虚假值代替这个错误的值。
- 架构的可行性
架构应该论证系统的技术可行性。如果在任何一个方面不可行都会导致项目无法实施,那么架构应该说明“这些问题是如何经过研究的”——通过验证概念的原型、研究、或其他手段。必须在全面开展构建之前解决掉这些风险。
- 过度工程
健壮性是指“系统在检测到错误后继续运行”的能力。通常架构详细描述的系统会比需求详细描述的系统更健壮。
在软件中,链条的强度不是取决于最薄弱的一环,而是等于所有薄弱环节的乘积。架构应该清楚地指出程序员应该“为了谨慎起见宁可进行过度工程”,还是应该做出最简单的能工作的东西。
详细定义一种过度工程的方法尤其重要,因为许多程序员会处于专业自豪感,对自己编写的类做过度工程,通过在架构中明确地设立期望目标,就能避免出现“某些类异常健壮,而其他类勉强够健壮”的现象。
- 关于"买"还是"造"的决策
最激进的构建软件的解决方案是根本不去构建它——购买软件,或者免费下载开源的软件。
如果架构不采用现货供应的组件,那么就应该说明“自己定制的组件应该在哪些方面胜过现成的程序库和组件”。
- 关于复用的决策
如果开发设计提倡使用已经存在的软件、测试用例、数据格式或其他原料,架构应该说明:如何对复用的软件进行加工,使之符合其他架构目标——如果需要使之符合的话。
- 变更策略
架构应当清楚地描述处理变更的策略。架构应该列出已经考虑过得有可能会有所增强的功能,并说明“最有可能增强的功能同样也是最容易实现的”。
架构应该指出“延迟提交”所用的策略(延迟提交:推迟某些因素的确定时间,做晚绑定,以增强灵活性。)
- 架构的总体质量
优秀的架构规格书的特点在于,讨论了系统中的类、讨论了每个类背后的隐藏信息、讨论了“采纳或排斥所有可能的设计替代方案”的根本理由。
架构的目标应该清楚地描述。以系统的课更改性为首要目标的设计,与以性能方面决不妥协为首要目标的设计肯定是不同的——即便两个系统的功能一样。
架构应该描述所有主要决策的动机。谨防“我们向来这么做”这种自认为有理的说法。
架构应该踏在对系统“欠描述”和“过度描述”之间的那条分界线上。
架构应该明确地指出有风险的区域,并作出解释。
架构应该包含多个视角(视图)。(例如房屋的设计图包括正视图、平面图、结构图、线路布线图及其他视图一样)
核对表:架构
以下是一份问题列表,优秀的架构应该关注这些问题。这张核对表的意图并非用做一份有关如何做架构的完全指南,而是作为一种使用的评估手段,用来评估软件食物链到了程序员这一头还有多少营养成分。这张核对表可用做你自己的核对表的出发点。就像“需求”的核对表一样,如果你从事的是更大型的项目,那么大多数条款都会是很有用的。
针对各架构主题
- 程序的整体组织结构是否清晰?是否包含一个良好的架构全局观(及其理由)?
- 是否明确定义了主要的构造块(包括每个构造块的职责范围及与其他构造块的接口)?
- 是否明显涵盖了“需求”中列出的所有功能(每个功能对应的构造块不太多也不太少)?
- 是否描述并论证了那些最关键的类?
- 是否描述并论证了数据设计?
- 是否详细定义了数据库的组织结构和内容?
- 是否指出了所用关键的业务规则,并描述其对系统的影响?
- 是否描述了用户界面设计的策略?
- 是否将用户界面模块化,使界面的变更不会影响程序其余部分?
- 是否描述并论证了处理I/O的策略?
- 是否估算了稀缺资源(入线程、数据库连接、句柄、网络宽带等)的使用量,是否描述并论证了资源管理的策略?
- 是否描述了架构的安全需求?
- 架构是否为每个类、每个子系统、或每个功能域提出空间与时间预算?
- 架构是否描述了如何达到可伸缩性?
- 架构是否关注互操作性?
- 是否描述了国际化/本地化的策略?
- 是否提供了一套内聚的错误处理策略?
- 是否规定了容错的办法(如果需要)?
- 是否证实了系统各个部分的技术可行性?
- 是否详细描述了过度工程的方法?
- 是否包含了必要的“买vs.造”的决策?
- 架构是否描述了如何加工被复用的代码,使之符合其他架构目标?
- 是否将架构设计得能够适应很可能出现的变更?
架构的总体质量
- 架构是否解决了全部需求?
- 有没有哪个部分是“过度架构”或“欠架构”?是否明确宣布了在这方面的预期指标?
- 整个架构是否在概念上协调一致?
- 顶层设计是否独立于用作实现它的机器和语言?
- 是否说明了所有主要的决策的动机?
- 你,作为一名实现该系统的程序员,是否对这个架构感觉良好?
3.6花费在前期准备上的时间长度
花费在问题定义、需求分析、软件架构上的时间,依据项目的需要而变化。一般来说,一个运作良好的项目会在需求、架构以及其他前期计划方面投入10%~20%的工作量和20%~30%的时间。这些数字不包括详细设计的时间——那是构建活动的一部分。
在为软件架构分配时间的时候,要使用与需求分析类似的方法。如果软件是你以前没有做过的类型,应该为“在新的领域中做设计”的不确定性预留更多的时间。如果有必要,将架构工作也作为独立的项目来对待。