1. 软件开发食物链:程序员是软件食物链的最后一环。架构师吃掉需求,设计师吃掉架构,而程序员则消化设计。
2. 需求变更
“一旦客户接受了一份需求文档,就再也不做更改”是一个美好的愿望。然而,对一个典型的项目来说,在编写代码之前,客户无法可靠地描述他们想要的是什么。问题并不在于客户是低级生物。就如同你做这个项目的时间越长,对这个项目的理解也就越深入一样,客户参与项目的时间越长,他们对项目的理解也就越深入。开发过程能够帮助客户更好地理解自己的需求,这是需求变更的主要来源。
3. 程序组织
在架构中,你应该能发现对那些曾经考虑过的最终组织结构的替代方案的记叙,找到之所以选用最终的组织结构,而不用其他替代方案的理由。有一份 对设计实践的综述认为,“维护‘设计缘由’”至少与“维护设计本身”一样重要。
应该明确定义各个构造快的责任。每个构造快应该负责某一个区域的事情,并且对其他构造块负责的区域知道的越少越好。通过使各个构造快对其他构造块的了解达到最小,你能将设计的信息局限于各个构造块之内。应该明确定义每个构造块的通讯规则。对于每个构造块,架构应该描述他能直接使用那些构造块,能间接使用那些构造块,不能使用那些构造块。
4. 主要的类
架构应该详细定义所用的主要的类。它应该指出每个主要的类的责任,以及该类如何与 其他类交互。它应该包含对类的集成体系、状态转换、对象持久化等的描述。
架构应该记叙曾经考虑过的其他类设计方案,并给出选用当前的组织结构的理由。架构无需详细说明系统中的每一个类。瞄准80/20法则:对那些构成系统80%的行为的20%的类进行详细说明。
5. 数据设计
架构应该描述所用到的主要文件和数据表的设计。它 应该描述曾经考虑过的其他方案,并说明做出选择的理由。在构建期间,这些信息能让你洞察架构师的思想。在维护阶段,这种洞察力是无价之宝。离开它,你就就像看一部没有字幕的外语片。
数据通常只应该由一个子系统或一个类直接访问。架构应该详细定义所用数据库的高层组织结构和内容。架构应该解释为什么单个数据库比多个数据库要好,解释为什么不用平坦的文件而要用数据库。
6.用户界面设计
用户界面常常在需求阶段进行详细说明。如果没有,就应该在软件架构中进行详细说明。架构应该详细定义Web页面格式、GUI、命令行接口等主要元素。精心设计的用户界面架构决定了最终做出来的时“人见人爱的程序”还是“没人爱用的程序”。
架构应该模块化,以便在替换为新用户界面时不影响业务规则和程序的输出部分。例如,架构应该使我们很容易地做到:砍掉交互式界面的类,插入一组命令行的类。这种替换能力常常很有用,尤其因为命令行界面便于单元级别和子系统级别的软件测试。
7. 资源管理
架构应该描述一份管理稀缺资源的计划。稀缺资源包括数据库连接、线程、句柄等。在内存受限的应用领域,如驱动程序开发和嵌入式系统中,内存管理是架构应该认真对待的另一个重要领域。架构应该估算在正常情况和极端情况下的资源管理使用量。在简单的情况下,估算数据应该说明:预期的实现环境有能力所提供的资源。在更复杂的情况中,也许会要求应用程序更主动地管理器拥有的资源。如果这样,那么“资源管理器”应该和系统的其他部分一样进行认真的架构设计。
8. 可伸缩性
可伸缩性是指系统增长以满足未来需求的能力。架构应该描述系统如何应对用户数量、服务器数量、网络节点数量、数据库记录数、数据库记录的长度、交易量等的增长。如果预计系统不会增长,而且可伸缩性不是问题,那么架构应该明确地列出这一假设。
9.错误处理
错误处理已被证实为现代计算机科学中最棘手的问题之一,你不能武断地处理它。有人估计程序中高达90%的代码用来处理异常情况、进行错误处理、或作簿记工作。既然这么多代码致力于处理错误,那么在架构中就应该清楚地说明一种“一致的地处理错误”的策略。
错误处理常被视为是“代码约定层次”的事情——如果真有人注意它的话。但是因为错误处理牵连到整个系统,因此最好在架构层次上对待它。下面是一些需要考虑的问题。
错误处理是进行纠正还是仅仅进行检测?如果是纠正,程序可以尝试从错误中恢复过来。如果仅仅是检测,那么程序可以像“没有发生任何事”一样继续运行,也可以退出。无论哪一种情况,都应该通知用户说检测到一个错误。
错误检测是主动还是被动?系统可以主动地预测错误——例如,通过检查用户输入的有效性——也可以在不能避免错误的时候,被动地响应错误——例如,当用户输入的组合产生了一个数值溢出的错误时。前者可以扫清障碍,后者可以清除混乱。同样,无论采用哪种方案,都与用户界面有影响。
程序如何传播错误?程序一旦检测到错误,它可以立即丢弃引发该错误的数据;也可以把这个错误当成一个错误,并进入错误处理状态;或者可等到所有处理完成,再通知用户说在某个地方发现了错误。
错误消息的处理有什么约定?如果架构没有详细定义一个一致的处理策略,那用户界面看起来就像“令人困惑的乱七八糟的抽象拼贴画”,由程序的不同部分的各种界面拼接而成。要避免这种外观体验,架构应该建立一套有关错误消息的约定。
如何处理异常?架构应该规定代码何时能够抛出异常,在什么地方捕获异常,如何记录这些异常,以及如何在文档中描述异常,等等。
在程序中,在什么层次上处理错误?你可以在发现错误的地方处理,可以将错误传递到专门处理错误的类进行处理,或者沿着函数调用链往上传递错误。
每个类在验证其输入数据的有效性方面需要负何种责任?是每个类负责验证自己的数据的有效性,还是有一组类负责验证整个系统的数据的有效性?某个层次上的类是否能假设它接收的数据是干净的。
你是希望用运行环境中内建的错误处理机制,还是想建立自己的一套机制?事实上,运行环境所拥有的某种特定的错误处理方法,并不一定是符合你的需求的最佳方法。
10.容错性
架构还应该详细定义所期望的容错种类。容错是增强系统可靠性的一组技术,包括检测错误;如果可能的话从错误中恢复;如果不能从错误中恢复,则包容其不利影响。
举个例子:为了计算某数的平方根,系统的容错策略有一下几种。
系统在检测到错误的时候退回去,再试一次。如果第一次的结果是错误的,那么系统可以退回到之前一切正常的时刻,然后从该点继续运行。
系统拥有一套辅助代码,以备在主代码吹次的时候使用。在本例中,如果发现第一次的答案似乎有错,系统就切换到另一个计算平方根的子程序,以取而代之。
系统使用一种表决算法。它可以有三个计算平方根的类,每一个都使用不同的计算方法。每个类都计算平方根,然后系统对结果进行比较。根据系统内建的容错机制的种类,系统可以以三个结果的均值、中值或众数作为最终结果。
系统使用某个不会对系统其余部分产生危害的虚假值代替这个错误的值。
其他容错方法包括,在遇到错误的时候,让系统转入某种“部分运转”的状态,或者转入某种“功能退化”的状态。系统可以自动关闭或重启。