在大型遗留系统基础上运作重构项目(二)

日期: 2009-09-08 作者:CSDN 来源:TechTarget中国 英文

  典型案例

  下面的文章中我们列举了一些eMAN系统(前台部分)中较常见的代码质量问题。我们没有列出一些更常见的“坏味道”(例如大类、长方法等),因为Martin Fowler在《重构》一书中已经把它们描述得足够清楚,而且针对它们的重构也相对容易。本文中列出的是一些相对规模较大、较为复杂的情形。

  我们相信类似的情形也存在于很多其他系统中,但我们并不打算宣称这个列表足以包治百病。大规模重构是一件极其复杂的细致活,很多时候你需要根据当前情况寻找适合自己的解决方案。

  无法在测试环境中创建被测对象

  对象在创建过程中自己尝试获得所需的依赖对象,就可能在单元测试环境下因无法创建依赖对象而导致被测对象的创建失败,从而不能把被测对象放进单元测试。例如AuthorizationService类的创建过程如下:

类的创建过程

  不恰当的对象获取方式

  不必要的Singleton模式。如果一个对象本身没有内部状态,只是根据外界状态进行计算,这样的对象不需要是Singleton的。例如CommonTool类和SessionService类:这两个类只是把请求转发给其他对象处理,它们不需要使用Singleton模式。

  通过其他对象获取。从其它对象中取出自己需要的依赖对象,“由谁提供某个对象”的决策相当随机,有时通过一条长链来获得自己真正需要的依赖对象。例如要得到RightPaneXMLParser对象,就需要通过下列方式: 

RightPaneXMLParser对象

  重构办法:

  如前所述,对象所需的依赖对象全部以构造函数参数的形式获得,将“创建对象”的责任不断上推,直至系统顶端的某个位置聚集了系统中绝大部分的对象创建逻辑。

  在系统顶端分离出一个全局工厂对象,该对象负责创建系统中所有的对象,并组装对象之间的依赖关系。其他地方原则上不作对象创建。

  在少数不直接被这个系统顶端调用的地方(例如对外暴露给第三方的接口),从全局工厂请求自己需要的对象。

  引入轻量级IoC容器(建议在PicoContainer和Spring Core之间选择),替代这个全局工厂对象。

  重构目标:

  系统中主要业务对象的创建都在全局工厂进行。

  业务对象是否Singleton能够以配置的方式管理。

  对象依赖关系复杂

  通过上述重构,尤其是改为通过构造函数参数来获得依赖对象之后,在一段时期会发现各个对象的构造函数堆积了大量的参数。例如经过重构之后的UserLoginDlgHelper的构造函数签名如下:

UserLoginDlgHelper的构造函数签名

  共有5个依赖对象从构造函数传入,显得构造函数相当臃肿。这实际上反映出对象本身的臃肿:这个对象承担了太多责任,因此需要依赖大量其它对象。过长的构造函数参数列表是一个直观的指标,让我们能够清晰地看到对象依赖的情况。经过观察可以发现,这些依赖对象都分别只在几个方法中被用到,这意味着UserLoginDlgHelper对象本身的职责可以分为几个相对独立的方面,可以被拆分为几个独立的对象。

  重构办法:

  以依赖对象的使用情况为线索,把大对象拆分成多个小对象。大对象中原有的方法不删除,而是把调用委派给小对象。

  逐一修改大对象的使用者,让它们使用拆分出来的小对象。

  大对象中的方法无人使用时即可删除,依赖对象无人使用时即可从构造函数参数列表中删除。

  重构目标:

  每个对象的依赖对象数比较合理。

  数据缺乏对象封装

  系统中一些数据以原生类型或者简单容器的形式呈现,“对数据的操作”与数据本身脱离,散落在系统各处。这样做的坏处是不容易看清数据代表的含义和处理这些数据的逻辑。特别是在数据结构发生改变时,必须找出所有操作这些数据的地方,同时修改,一旦有遗漏就很容易引入隐晦的错误。

  例如“登录信息”在系统中用Vector数据类型表示,其中各个字符串分别代表DS服务器IP、DS服务器端口、认证模式、安全模式等信息。“共有多少个字符串”和“各个位置上的字符串表示什么含义”等信息在ServerInfoTable类中以常量的形式保存:

ServerInfoTable类

  系统中共有5个类、十多个地方引用这些常量,还有一些地方直接用魔法数来访问这些信息。如果新增一列数据,需要修改的地方很多,很容易出错。

  重构办法:

  创建类型来封装原生数据,同时提供对象与原生数据之间的双向转换方法。

  找出原生数据的“索引常量”(如前面的ServerInfoTable),以它为线索,逐个方法进行重构,把其中使用原生数据的逻辑改为使用新对象。在方法入口处把原生数据转换为对象,出口处把对象转换回原生数据。

  一条调用链重构完成后,即可修改其中各个方法的签名,由传递原生数据改为传递对象。

  如果某些操作不涉及其他业务对象,只是操作这组数据,把这样的操作移到新建的类中。

  在调用链附近留意检查是否有不通过“索引常量”而用魔法数直接访问数据结构的情况。

  重构目标:

  与特定数据结构相关的描述和操作都封装在对象内部,“索引常量”和魔法数被删除。

  改变数据结构只需要一处修改。

  小结

  大规模遗留系统的重构一直是困扰众多软件组织的难题。所谓“冰冻三尺非一日之寒”,大型系统中的质量问题是经年累月堆积下来的,要解决这些问题也只能从价值最高的地方入手,耐着性子一点点重新恢复代码质量。经过这个咨询项目,eMAN产品开发团队在原有代码的基础上划分了细粒度的重构story,建立了持续集成环境,并按照共同探索出的节奏,以测试为驱动、以代码质量为导向,不断重构以改进代码质量,并且积累了一些常见问题的解决办法,为大规模遗留系统的重构找出了一条切实可行的路子。

我们一直都在努力坚持原创.......请不要一声不吭,就悄悄拿走。

我原创,你原创,我们的内容世界才会更加精彩!

【所有原创内容版权均属TechTarget,欢迎大家转发分享。但未经授权,严禁任何媒体(平面媒体、网络媒体、自媒体等)以及微信公众号复制、转载、摘编或以其他方式进行使用。】

微信公众号

TechTarget微信公众号二维码

TechTarget

官方微博

TechTarget中国官方微博二维码

TechTarget中国

作者

CSDN
CSDN

相关推荐