当架构模型进行迭代的过程中必然伴随着对模型进行修改和改进我们如何防止对模型的修改又如何保证对模型进行正确的改进? Context 架构模型通过精化合并等活动之后将会直接用于指导代码而这个时候往往就会暴露出一些问题出来通常在实际编码中发现架构存在或大或小的问题和错误导致编码活动无法继续这时候我们就需要对架构模型进行修改了而架构设计的过程本身是一个迭代的过程这就意味着在每一次的迭代周期中都需要对架构进行改进 Problem 我们如何避免对架构模型进行修改?又如何保证架构进行正确的改进? Solution 我们从XP中借用了一个词来形容架构模型的修改过程――Refactoring中文可以译作重构这个词原本是形容对代码进行修改的它指的是在不改变代码外部行为(可观察行为)的情况下对代码进行修改我们把这个词用在架构模型上因为经过精化和合并之后的架构模型往往由很多个粗粒度组件构成这些组件之间存在一定的耦合度(虽然我们可以令耦合度尽可能的低但是耦合度一定是存在的)任何一个组件的重构行为都会使变化扩散到系统中的其它组件这取决于被重构的组件和其它组件之间的相对关系如果被重构的组件属于层次较低的工具层上那么这次的修改就可以引起模型很大的变动 在精化和合并模式中我们提到了改变和改进的区别因此我们的对策主要分为两种如何防止改变的发生以及使用重构来改进软件架构 防止改变的发生 在任何时候需求的变更总是对架构及软件有着最大的伤害而需求变更中最大问题是需求蔓延很多人都有这样的感觉项目完成之后发现初期的计划显得那么陌生在项目早期对需求进行控制是重要的但并不是该模式谈论的重点我们更关注在项目中期的需求蔓延问题和晚期的需求控制问题关于这方面的详细讨论请参见稳定化模式在项目中期尤其是编码工作已经开始之后要尽可能避免出现需求蔓延的情况需求蔓延是经常发生的可能是因为用户希望加入额外的功能或是随着用户对软件了解的加深发现原有的需求存在一定的不足完全防止需求蔓延是无法做到的但是需要对其进行必要的控制例如有效的估计变更对开发测试文档管理组织等各个方面带来的影响 避免发生改变的另一个有效的办法是从软件过程着手迭代法或渐进交付法都是可用的方法一个软件的架构设计往往是相对复杂的其中涉及到整体结构具体技术等问题一次性考虑全部的要素就很容易发生考虑不周详的情况人的脑容量并没有我们想象的那么大将架构设计分为多个迭代周期来进展可以减少单次迭代周期中需要建模的架构数量因此可以减少错误的发生另一方面迭代次数的增多的直接结果是时间的延长此外还有一个潜在的问题如果由于设计师的失误在后期的迭代中出现问题必然会导致大量的返工因为之前的模型已经实现了在得与失之间我们如何找到适当的平衡点呢? 迭代次数应该根据不同软件组织的特点来制定对于初期的迭代周期而言它的主要任务应该是制定总原则(使用架构愿景模式)定义层结构和各层的职责(使用分层模式)解决主要的技术问题上在这个过程中可以列出设计中可能会遇到的风险并根据风险发生的可能性和危害性来排定优先级指定专人按次序解决这些问题除此之外在初期参考前一个项目的经验让团队进行设计(参见团队设计模式)这些组织保证也是很重要初期的迭代过程是防止改变的最重要的活动 请注意需求中的非功能需求如果说功能需求定义了架构设计的目标的话非功能需求就对如何到达这个目标做出了限制例如对于实现一个报表有着多种的操作方法但是如果用户希望新系统和旧系统进行有效的融合那么实现的方式就需要好好的规划了请从初期的迭代过程就开始注意非功能需求因为如果忽略它们在后期需要花费很大的精力来调整架构模型试想一下如果在项目晚期的压力测试中发现现有的数据库访问方法无法满足用户基本的速度要求那对项目进行将会造成多么大的影响 注意架构的稳定性在精化和合并模式中我们提到了一些模式能够降低不同组件之间的耦合度并向调用者隐藏具体的实现接口和实现分离是设计模式最大的特点请善用这一点 尽可能的推延正式文档的编写在设计的初期修饰模型和编写文档通常都没有太大的意义因为此时的模型还不稳定需要不断的修改如果这时候开始投入精力开发文档这就意味着后续的迭代周期中将会增加一项维护文档一致性的工作了而这时候的文档却无法发挥出它真正的作用但是延迟文档的编写并不等于什么都不做无论什么时候进行设计都需要随手记录设计的思路这样在需要的时候我们就能够有充分的资料对设计进行文档化的工作 对软件架构进行重构 Martin Fowler的Refactoring一书为我们列举了一系列的对代码进行重构方法架构也是类似的 重构到模式 Joshua Kerievsky在《Refactoring to Patterns》一书中这样描述重构和模式的关系 Patterns are a cornerstone of objectoriented design while testfirst programming and merciless refactoring are cornerstones of evolutionary design (模式是面向对象设计的基石而测试优先编程和无情的重构则是设计演进的基石)作者在文中着重强调了保持适度设计的重要性 在作者看来模式常常扮演着过度设计的角色而在解决这个问题的同时又利用模式的优点的解决方法是避免在一开始使用模式而是在设计演进中重构到模式这种做法非常的有效因为在初始设计中使用模式的话你的注意力将会集中到如何使用模式上而不是集中在如何满足需求上这样就会导致不恰当的设计(过度设计或是设计不充分)因此在初始设计中除非非常有把握(之前有类似的经验)否则我们应当把精力放在如何满足需求上在初始模型完成后(参见精化和合并模式中的例子)我们会对架构进行重构而随着迭代的演进需求的演进架构也需要演进这时候也需要重构行为在这些过程中如果发现某些部分的设计需要额外的灵活性来满足需求那么这时候就需要引入模式了 在软件开发过程中我们更常的是遇见设计不充分的情况例如组件之间耦合度过高业务层向客户端暴露了过多的方法等等很多的时候产生这种现象是由于不切实际的计划而导致的开发人员不得不为了最终期限而赶工所有的时间都花费在新功能上而完成的软件则被仍在一边这样产出的软件是无法保证其质量的对于这种情况我们也需要对设计进行重构当然合理的计划是大前提所在团队的领导者必须向高层的管理者说明现在的这种做法只会导致未来的返工目前的高速开发是以牺牲未来的速度为代价的因为低劣的设计需要的高成本的维护这将抵消前期节省的成本如果软件团队需要可持续的发展那么请避免这种杀鸡取卵的行为 因此使用模式来帮助重构行为以实现恰当的设计 测试行为 重构的前提是测试优先测试优先是XP中很重要的一项实践对于编码来说测试优先的过程是先写测试用例再编写代码来完成通过测试用例(过程细节不只如此请参看XP的相关书籍)但是对于架构设计来说测试行为是发生在设计之后的即在设计模型完成后产出相应的测试用例然后再编码实现这时候测试用例就成为联系架构设计和编码活动的纽带 另一方面在设计进行重构时相应的测试用例也由很大的可能性发生改变此时往往会发生需要改变的测试代码超出普通代码的情况避免这种情况一种做法是令你的设计模型的接口和实现相分离并使测试用例针对接口而不是实现在精化和合并模式中我们提到了一些模式能够有助于稳定设计和测试用例Martin Fowler在他的Application Facade一文中提到使用Facade模式来分离不同的设计部分而测试则应当针对facade来进行其思路也是如此 考虑一个用户转帐的用例银行需要先对用户进行权限的审核在审核通过之后才允许进行转帐(处于简便起见图中忽略了对象的创建过程和调用参数) 需要分别针对三个类编写测试用例设计模型一旦发生变化测试用例也将需要重新编写再考虑下面的一种情况 现在的设计引入了TransferFacade对象这样我们的测试用例就可以针对TransferFacade来编写了而转帐的业务逻辑是相对比较稳定的使用这种测试思路的时候要注意两点首先这并不是说其它的类就不需要测试用例了这种测试思路仅仅是把测试的重点放在外观类上因为任何时候充分的测试都是不可能的但其它类的测试也是必要的对于外观类来说任何一个业务方法的错误都会导致最终的测试失败其次当外观类的测试无法达到稳定测试用例的效果时就没有必要使用外观类了 只针对有需要的设计进行重构 任何时候请确保重构行为仅仅对那些有重构需要的设计重构需要花费时间和精力而无用的重构除了增大设计者的虚荣心之外并不能够为软件增加价值重构的需要来源于两点一是需求的变更目前的设计可能无法满足新的需求因此需要重构二是对设计进行改进以得到优秀简洁的设计除了这两种情况我们不应该对设计模型进行重构 使用文档记录重构的模式 应该承认模式为设计提供了充分的灵活性而这些设计部分往往都是模型的关键之处和难点所在因此需要对模式进行文档化的工作甚至在必要的时候对这部分的设计进行培训和指导确保你的团队能够正确的使用文档来设计理解扩展模式我们在解决方案的前一个部分提到了尽可能延迟文档的创建而在设计重构为模式的时候我们就需要进行文档化的工作了因为模式具有灵活性能够抵抗一定的变更风险 重构并保持模式的一致性 正如上一节所说的那样模式并不是一个很容易理解的东西虽然它保持了设计的灵活性和稳定性对于面向对象的新手而言模式简直就像是飞碟一样由于缺少面向对象的设计经验他们无法理解模式的处理思路在实践中我们不只一次的碰到这种情况我们不得不重头开始教授关于模式的课程因此最后我们在软件设计采用一定数量的模式并确保在处理相同问题的时候使用相同的模式这样应用的模式就成为解决某一类的问题的标准做法从而在一定程度上降低了学习的曲线 保持模式的一致性的另一个方面的含义是将模式作为沟通的桥梁软件开发是一种团队的行为因此沟通在软件开发中扮演着很重要的角色试想一下开发人员在讨论软件设计的时候只需要说使用工厂模式大家就都能够明白而不是费劲口舌的说明几个类之间的关系这将大大提高沟通的效率此外模式的使用和设计的重构对于提高团队的编程水平培养后备的设计人员等方面都是很有意义的 |