欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

大话重构连载19:大对象的演化过程

程序员文章站 2022-05-05 13:26:20
...
很好,我们终于迈出了重构的第一步,而这第一步我们瞄准了代码问题的重灾区——超级大函数。超级大函数之所以是代码问题的重灾区,就是因为它们往往难于阅读、难于维护。面对大函数我们采取的办法是拆分,以功能为核心将其拆分成一个一个独立的函数。拆分后的程序变得易于阅读了,因为要读懂程序你不再需要读完所有代码,选择性的读取那些*函数,只需了了数行代码,你就可以明白整个程序。

但是,当我们将数千行的大函数分解成数十个小函数时,另一个问题出现了。想象一下,数十个函数被杂乱无章地堆放在一个对象中,看看就让人头疼。实际上,我们是不会这样做的。当我们开始了对大函数的分解时,随之而来的就是对大对象的分解。大对象,就是指的那些包含数十个甚至上百个方法或者函数,功能无所不包的超级对象。在很多遗留系统中,总有那么几个超级对象,系统几乎所有的功能都在它的里面有对应方法。这样的对象,密密麻麻的方法让人困惑,更关键的是,各种各样的功能被耦合在一起,稍有修改就会影响到许多功能,甚至让那些毫不相干的功能产生BUG。因此,我们应当合理地拆分我们的大对象。

与大函数一样,很多时候遗留系统中的大对象,也都是伴随系统业务复杂度的逐渐增长而出现的,我们来看看它的演进过程吧。我们说软件实际上是对现实世界的模拟,通过这种模拟,实现信息化的管理,来提高我们的生产效率。但是,现实世界是复杂的,各种事物之间存在着各种各样纷繁复杂的联系,因此我们不可能完全模拟现实世界的所有,只可能是现实世界的一部分,客户急需要模拟的那一部分。

人的大脑认识事物总是一个由简单到复杂的过程,这是我们的客观规律。因此,我们的软件模拟真实世界也是一个由简单到复杂的过程。最开初我们的想法总是非常简单而单纯的,就是让软件做一件非常简单而明确的事情。由于这时候业务非常简单,我们不需要太多的类和方法就可以实现业务操作。比如开票业务,就是将已开具的发票信息读取出来,保存。这样一个简单操作,设计成一个简单的开票业务类合情合理。

但是,随着软件模拟真实世界的不断发展,业务变得越来越复杂。比如这个开票业务,我们随后的业务开始变更,要检查购方是否存在、开票人是否有权限、库存中是否还有发票,等等。起初只有一种开票方式,但随着非正常开票业务的增加,许多相关的业务也随之变化……随着业务的不断增加,软件代码的规模也在发生着质的变化(如图6.1所示)。


大话重构连载19:大对象的演化过程
            
    
    博客分类: 大话重构 重构软件质量敏捷抽取类 

图6.1 开票业务的演化过程


过去开票业务类只有百来行代码,现在被膨胀到数千行代码。各种条件语句层层嵌套,各种临时变量穿插跑位,程序变得难于理解。由于读不懂代码,修改代码的程序员开始在走钢丝,一不小心改动了某个关键程序就可能引入重大BUG。为了避免重大BUG的出现,测试人员耗费巨大精力进行严格的测试。毫无疑问,软件开始进入一种恶性循环,软件退化开始一步步加深。

面对这种软件规模增大而带来的恶性循环,我们必须做出改变。面对问题我们不能病急乱投医,而是应当对症下药。这个正确的药方就是以职责驱动设计思想为核心,调整我们的程序结构,构建高内聚、低耦合的软件系统。职责驱动设计,就是要求我们设计的所有类和接口,都要有自己的职责定义。而每个类和接口内部的所有方法和属性都是围绕着该职责来进行的,它们都是高度相关的。每个类和接口决不去做跟自己职责无关的事情,所有与自己职责无关的事情,都应当交给其它拥有该职责的类来完成,而自己仅仅是去调用。这就是职责驱动设计的思想,而每个类其内部包含的功能所达到的高度相关的程度,我们称之为“内聚”。

概念似乎有一些抽象,我们来举例说明吧。对于开票业务,我们设计了开票业务类来处理它,因此开票业务类的职责就是完成开票操作,这似乎毫无问题。但是,我们仔细审视开票操作,就会发现它包含了好几个部分:首先,我们读取客户、开票人、发票库存等信息进行相关的校验,然后保存这些发票到数据库中,最后统计当月的票量及金额。通过这样的分析,它们似乎不再那么功能相关了,读取和校验客户、开票人、发票库存等信息是客户、开票人、发票库存实体类的职责,读取和保存发票似乎是发票类的职责,而统计当月票量与金额似乎是财会统计类的职责。分与不分,完全取决于软件代码的复杂程度。如果总共也就几十行代码,我们写成一个类中的几个方法就可以了;但随着功能复杂度的加深,那么我们必须得拆分,分配到不同的类中配合完成我们的功能。

随着软件业务的不断变化,我们的软件在发生着质的变化。发票保存前我们必须要进行一系列的校验工作:检查购方是否存在、开票人是否有权限、是否还有发票库存,等等。不同的校验,读取的是不同的数据,它们的顺序可能变化,校验的个数也可能在调整。随着需求的变化,一些校验被增加进来而另一些则被剔除。我们判断功能是否相关的一个非常重要的原则,就是是否是软件变更的同一个原因。比如,“检查购方是否存在”与“开票人是否有权限”,不是软件变更的同一个原因:“检查购方是否存在”是与客户信息管理直接相关,而“开票人是否有权限”则是与用户权限定义密切相关,因此它们不能放在同一个类中。为什么呢?因为“检查购方是否存在”的业务逻辑变更时,不应当影响到“检查开票人是否有权限”的功能。最好的办法就是,将它们各自封装在各种相关的业务类中(如图6.2所示)。


大话重构连载19:大对象的演化过程
            
    
    博客分类: 大话重构 重构软件质量敏捷抽取类 

图6.2 开票业务的拆分


经过以上分析我们发现,开票操作随着业务逻辑的不断发展,应当在原有的程序结构上,将开票业务类拆分成几个部分:各种校验类、发票业务类与财会统计类。这样的拆分使得开票业务类最终由一个什么都干的多面手,变成了一个管理者。它不再参与那些具体的工作,而是将工作分配给不同的人,成为一个组织协调者。经过这样的调整,我们的程序将变得更加易于阅读、维护、变更。

大话重构连载首页:http://fangang.iteye.com/blog/2081995
特别说明:希望网友们在转载本文时,应当注明作者或出处,以示对作者的尊重,谢谢!
  • 大话重构连载19:大对象的演化过程
            
    
    博客分类: 大话重构 重构软件质量敏捷抽取类 
  • 大小: 23.8 KB
  • 大话重构连载19:大对象的演化过程
            
    
    博客分类: 大话重构 重构软件质量敏捷抽取类 
  • 大小: 25.5 KB