保证架构稳定的优秀实践
在文章的开头,我们就谈到说在项目起始阶段就制定出准确、详细的架构设计是不太现实的。因此,敏捷方法中有很多的实践来令最初的架构设计稳定化。实际上,这些实践并非完全是敏捷方法提出的新概念。敏捷方法只是把这些比较优秀的实践组织起来,为稳定的架构设计提供了保证。以下我们就详细讨论这些实践。
在不稳定的环境中寻求稳定因素。什么是稳定的,什么是不稳定的。RUP推荐使用业务实体(Business Entity)法进行架构设计。这种方法的好处之一是通过识别业务实体从而建立起来的架构是相对稳定的。因为业务实体在不稳定的业务逻辑中属于稳定的元素。大家可以想象,公司、雇员、部门这些概念,几十年来并没有太大的变化。对于特定的业务也是一样的。例如对于会计总帐系统来说,科目、余额、分户账、原始凭证,这些概念从来就没有太大的变化,其对应的行为也相差不大。但是某些业务逻辑就完全相反了。不同的系统业务逻辑不同,不同的时点业务逻辑也有可能发生变化。例如,对于不同的制造业来说,其成本核算的逻辑大部分都是不一样的。即便行业相同,该业务逻辑也没有什么共性。因此,稳定的架构设计应该依赖于稳定的基础,对于不稳定的因素,较好的做法是对其进行抽象,抽象出稳定的东西,并且把不稳定的因素封装在单独的位置,避免其对其它模块的影响。而这种思路的直接成果,就是下一段提到的针对接口编程的做法。例如对于上面提到的成本核算来说,虽然它们是易变的、不稳定的,但是它们仍然存在稳定的东西,就是大部分制造业企业都需要成本核算。这一点就非常的重要,因此着意味着接口方法是相对固定的。
保持架构稳定性的另一种方法是坚持面向接口编程的设计方法。我们在分层模式中就提到了面向接口编程的设计方法,鼓励抽象思维、概念思维。从分层模式中提到的示例中(详见分层模式下篇的面向接口编程一节),我们可以看出,接口的一大作用是能够有效的对类功能进行分组,从而避免客户程序员了解和他不相关的知识。设计模式中非常强调接口和实现分离,其主要的表现形式也正是如此,客户程序员不需要知道具体的实现,对他们来说,只需要清楚接口发布出的方法就可以了。
从另一个方面来看,之所以要把接口和实现相分离,是因为接口是需求中比较稳定的部分,而实现则是和具体的环境相关联的。下图为Java中Collection接口公布出的方法。可以看到,在这个层次上,Collection接口只是根据容器的特性定义了一些稳定的方法。例如增加、删除、比较运算等。所以这个接口是相对比较稳定的,但是对于具体的实现类来说,这些方法的实现细节都有所差别。例如,对于一个List和一个Array,它们对于增加、删除的实现都是不一样的。但是对于客户程序员来说,除非有了解底层实现的需要,否则他们不用了解List的add方法和Array的add方法有什么不同。另一方面,将这些方法实现为固定的、通用的接口,也有利于接口的开发者。他们可以将实现和接口相分离,此外,只要满足这些公布的接口,其它软件开发团队同样能够开发出合用的应用来。在当前这样一个讲求合作、讲求效率的大环境中。这种开发方法是非常重要的。
重构。代码重构是令架构趋于稳定的另一项方法。准确而言,重构应该是程序员的一种个人素质。但是在实际中,我们发现,重构这种行为更加适合作为开发团队的共同行为。为什么这么说呢?最早接触重构概念的时候,我对面向对象的认识并不深入。因此对重构的概念并不感冒。但随着经验的积累,面向对象思维的深入。我渐渐发现,重构是一种非常优秀的代码改进方式,它通过把原子性的操作,逐步的改进代码质量,从而达到改进软件架构的效果。当程序员逐渐熟练运用重构的时候,他已经不再拘泥于这些原子操作,而是自然而然的写出优秀的软件。这是重构方法对各人行为的改进。另一方面,对于一个团队来说,每个人的编程水平和经验都不一而足,因此软件的各个模块质量也都是参差不齐的。这种情况下,软件质量的改进就已经不是个人的问题了,而该问题的难度要比前一个问题大的多。此时重构方法更能够发挥其威力。在团队中提倡使用、甚至半强制性使用重构,有助于分享优秀的软件设计思路,提高软件的整体架构。而此时的重构也不仅仅局限在代码的改进上(指的是Martin Fowler在重构一书中提到的各种方法),还涉及到分析模式、设计模式、优秀实践的应用上。同时,我们还应该看到,重构还需要其它优秀实践的配合。例如代码复审和测试优先。
总结
令架构趋于稳定的因素包括令需求冻结和架构改进两个方面。需求冻结是前提,架构改进是必然的步骤。在面向对象领域,我们可以通过一些实践技巧来保持一个稳定的架构。这些实践技巧体现在从需求分析到编码的过程中。