锁定老帖子 主题:一堂如何提高代码质量的培训课(2)
精华帖 (7) :: 良好帖 (16) :: 新手帖 (0) :: 隐藏帖 (1)
|
|
---|---|
作者 | 正文 |
发表时间:2010-01-07
最后修改:2010-01-08
3.可变更性前面我提到了,软件的变更性是所有软件理论的核心,那么什么是软件的可变更性呢?按照现在的软件理论,客户对软件的需求时时刻刻在发生着变化。当软件设计好以后,为应对客户需求的变更而进行的代码修改,其所需要付出的代价,就是软件设计的可变更性。由于软件合理地设计,修改所付出的代价越小,则软件的可变更性越好,即代码设计的质量越高。一种非常理想的状态是,无论客户需求怎样变化,软件只需进行适当地修改就能够适应。但这之所以称之为理想状态,因为客户需求变化是有大有小的。如果客户需求变化非常大,即使再好的设计也无法应付,甚至重新开发。然而,客户需求的适当变化,一个合理地设计可以使得变更代价最小化,延续我们设计的软件的生命力。 1)通过提高代码复用提高可维护性我曾经遇到过这样一件事,我要维护的一个系统因为应用范围的扩大,它对机关级次的计算方式需要改变一种策略。如果这个项目统一采用一段公用方法来计算机关级次,这样一个修改实在太简单了,就是修改这个公用方法即可。但是,事实却不一样,对机关级次计算的代码遍布整个项目,甚至有些还写入到了那些复杂的SQL语句中。在这样一种情况下,这样一个需求的修改无异于需要遍历这个项目代码。这样一个实例显示了一个项目代码复用的重要,然而不幸的是,代码无法很好复用的情况遍布我们所有的项目。代码复用的道理十分简单,但要具体运作起来非常复杂,它除了需要很好的代码规划,还需要持续地代码重构。 对整个系统的整体分析与合理规划可以根本地保证代码复用。系统分析师通过用例模型、领域模型、分析模型的一步一步分析,最后通过正向工程,生成系统需要设计的各种类及其各自的属性和方法。采用这种方法,功能被合理地划分到这个类中,可以很好地保证代码复用。 采用以上方法虽然好,但技术难度较高,需要有高深的系统分析师,并不是所有项目都能普遍采用的,特别是时间比较紧张的项目。通过开发人员在设计过程中的重构,也许更加实用。当某个开发人员在开发一段代码时,发现该功能与前面已经开发功能相同,或者部分相同。这时,这个开发人员可以对前面已经开发的功能进行重构,将可以通用的代码提取出来,进行相应地改造,使其具有一定的通用性,便于各个地方可以使用。 一些比较成功的项目组会指定一个专门管理通用代码的人,负责收集和整理项目组中各个成员编写的,可以通用的代码。这个负责人同时也应当具有一定的代码编写功力,因为将专用代码提升为通用代码,或者以前使用该通用代码的某个功能,由于业务变更,而对这个通用代码的变更要求,都对这个负责人提出了很高的能力要求。 虽然后一种方式非常实用,但是它有些亡羊补牢的味道,不能从整体上对项目代码进行有效规划。正因为两种方法各有利弊,因此在项目中应当配合使用。 2)利用设计模式提高可变更性对于初学者,软件设计理论常常感觉晦涩难懂。一个快速提高软件质量的捷径就是利用设计模式。这里说的设计模式,不仅仅指经典的32个模式,是一切前人总结的,我们可以利用的、更加广泛的设计模式。
a. if...else...这个我也不知道叫什么名字,最早是哪位大师总结的,它出现在Larman的《UML与模式应用》,也出现在出现在Mardin的《敏捷软件开发》。它是这样描述的:当你发现你必须要设计这样的代码:“if...elseif...elseif...else...”时,你应当想到你的代码应当重构一下了。我们先看看这样的代码有怎样的特点。
if(var.equals("A")){ doA(); }else if(var.equals("B")){ doB(); }else if(var.equals("C")){ doC(); }else{ doD(); }
这样的代码很常见,也非常平常,我们大家都写过。但正是这样平常才隐藏着我们永远没有注意的问题。问题就在于,如果某一天这个选项不再仅仅是A、B、C,而是增加了新的选项,会怎样呢?你也许会说,那没有关系,我把代码改改就行。然而事实上并非如此,在大型软件研发与维护中有一个原则,每次的变更尽量不要去修改原有的代码。如果我们重构一下,能保证不修改原有代码,仅仅增加新的代码就能应付选项的增加,这就增加了这段代码的可维护性和可变更性,提高了代码质量。那么,我们应当如何去做呢?
经过深入分析你会发现,这里存在一个对应关系,即A对应doA(),B对应doB()...如果将doA()、doB()、doC()...与原有代码解耦,问题就解决了。如何解耦呢?设计一个接口X以及它的实现A、B、C...每个类都包含一个方法doX(),并且将doA()的代码放到A.doX()中,将doB()的代码放到B.doX()中...经过以上的重构,代码还是这些代码,效果却完全不一样了。我们只需要这样写:
这样就可以实现以上的功能了。我们看到这里有一个工厂,放着所有的A、B、C...并且与它们的key对应起来,并且写在配置文件中。如果出现新的选项时,通过修改配置文件就可以无限制的增加下去。 这个模式虽然有效提高了代码质量,但是不能滥用,并非只要出现if...else...就需要使用。由于它使用了工厂,一定程度上增加了代码复杂度,因此仅仅在选项较多,并且增加选项的可能性很大的情况下才可以使用。另外,要使用这个模式,继承我在附件中提供的抽象类XmlBuildFactoryFacade就可以快速建立一个工厂。如果你的项目放在spring或其它可配置框架中,也可以快速建立工厂。设计一个Map静态属性并使其V为这些A、B、C...这个工厂就建立起来了。
b.策略模式也许你看过策略模式(strategy model)的相关资料但没有留下太多的印象。一个简单的例子可以让你快速理解它。如果一个员工系统中,员工被分为临时工和正式工并且在不同的地方相应的行为不一样。在设计它们的时候,你肯定设计一个抽象的员工类,并且设计两个继承类:临时工和正式工。这样,通过下塑类型,可以在不同的地方表现出临时工和正式工的各自行为。在另一个系统中,员工被分为了销售人员、技术人员、管理人员并且也在不同的地方相应的行为不一样。同样,我们在设计时也是设计一个抽象的员工类,并且设计数个继承类:销售人员、技术人员、管理人员。现在,我们要把这两个系统合并起来,也就是说,在新的系统中,员工既被分为临时工和正式工,又被分为了销售人员、技术人员、管理人员,这时候如何设计。如果我们还是使用以往的设计,我们将不得不设计很多继承类:销售临时工、销售正式工、技术临时工、技术正式工...如此的设计,在随着划分的类型,以及每种类型的选项的增多,呈笛卡尔增长。通过以上一个系统的设计,我们不得不发现,我们以往学习的关于继承的设计遇到了挑战。 解决继承出现的问题,有一个最好的办法,就是采用策略模式。在这个应用中,员工之所以要分为临时工和正式工,无非是因为它们的一些行为不一样,比如,发工资时的计算方式不同。如果我们在设计时不将员工类分为临时工类和正式工类,而仅仅只有员工类,只是在类中增加“工资发放策略”。当我们创建员工对象时,根据员工的类型,将“工资发放策略”设定为“临时工策略”或“正式工策略”,在计算工资时,只需要调用策略类中的“计算工资”方法,其行为的表现,也设计临时工类和正式工类是一样的。同样的设计可以放到销售人员策略、技术人员策略、管理人员策略中。一个通常的设计是,我们将某一个影响更大的、或者选项更少的属性设计成继承类,而将其它属性设计成策略类,就可以很好的解决以上问题。
使用策略模式,你同样把代码写活了,因为你可以无限制地增加策略。但是,使用策略模式你同样需要设计一个工厂——策略工厂。以上实例中,你需要设计一个发放工资策略工厂,并且在工厂中将“临时工”与“临时工策略”对应起来,将“正式工”与“正式工策略”对应起来。
c.适配器模式我的笔记本是港货,它的插头与我们常用的插座不一样,所有我出差的时候我必须带一个适配器,才能使用不同地方的插座。这是一个对适配器模式最经典的描述。当我们设计的系统要与其它系统交互,或者我们设计的模块要与其它模块交互时,这种交互可能是调用一个接口,或者交换一段数据,接受方常常因发送方对协议的变更而频繁变更。这种变更,可能是接受方来源的变更,比如原来是A系统,现在变成B系统了;也可能是接受方自身的代码变更,如原来的接口现在增加了一个参数。由于发送方的变更常常导致接受方代码的不稳定,即频繁跟着修改,为接受方的维护带来困难。 遇到这样的问题,一个有经验的程序员马上想到的就是采用适配器模式。在设计时,我方的接口按照某个协议编写,并且保持固定不变。然后,在与真正对方接口时,在前段设计一个适配器类,一旦对方协议发生变更,我可以换个适配器,将新协议转换成原协议,问题就解决了。适配器模式应当包含一个接口和它的实现类。接口应当包含一个本系统要调用的方法,而它的实现类分别是与A系统接口的适配器、与B系统接口的适配器...
我曾经在一个项目中需要与另一个系统接口,起初那个系统通过一个数据集的方式为我提供数据,我写了一个接收数据集的适配器;后来改为用一个XML数据流的形式,我又写了一个接收XML的适配器。虽然为我提供数据的方式不同,但是经过适配器转换后,输出的数据是一样的。通过在spring中的配置,我可以灵活地切换到底是使用哪个适配器。
d.模板模式32个经典模式中的模板模式,对开发者的代码规划能力提出了更高的要求,它要求开发者对自己开发的所有代码有一个相互联系和从中抽象的能力,从各个不同的模块和各个不同的功能中,抽象出其过程比较一致的通用流程,最终形成模板。譬如说,读取XML并形成工厂,是许多模块常常要使用的功能。它们虽然有各自的不同,但是总体流程都是一样的:读取XML文件、解析XML数据流、形成工厂。正因为有这样的特征,它们可以使用共同的模板,那么,什么是模板模式呢? 模板模式(Template Model)通常有一个抽象类。在这个抽象类中,通常有一个主函数,按照一定地顺序去调用其它函数。而其它函数往往是某这个连续过程中的各个步骤,如以上实例中的读取XML文件、解析XML数据流、形成工厂等步骤。由于这是一个抽象类,这些步骤函数可以是抽象函数。抽象类仅仅定义了整个过程的执行顺序,以及一些可以通用的步骤(如读取XML文件和解析XML数据流),而另一些比较个性的步骤,则由它的继承类自己去完成(如上例中的“形成工厂”,由于各个工厂各不一样,因此由各自的继承类自己去决定它的工厂是怎样形成的)。
各个继承类可以根据自己的需要,通过重载重新定义各个步骤函数。但是,模板模式要求不能重载主函数,因此正规的模板模式其主函数应当是final(虽然我们常常不这么写)。另外,模板模式还允许你定义的这个步骤中,有些步骤是可选步骤。对与可选步骤,我们通常称为“钩子(hood)”。它在编写时,在抽象类中并不是一个抽象函数,但却是一个什么都不写的空函数。继承类在编写时,如果需要这个步骤则重载这个函数,否则就什么也不写,进而在执行的时候也如同什么都没有执行。 通过以上对模板模式的描述可以发现,模板模式可以大大地提高我们的代码复用程度。
以上一些常用设计模式,都能使我们快速提高代码质量。还是那句话,设计模式不是什么高深的东西,恰恰相反,它是初学者快速提高的捷径。然而,如果说提高代码复用是提高代码质量的初阶,使用设计模式也只能是提高代码质量的中阶。那么,什么是高阶呢?我认为是那些分析设计理论,更具体地说,就是职责驱动设计和领域驱动设计。
声明:ITeye文章版权属于作者,受法律保护。没有作者书面许可不得转载。
推荐链接
|
|
返回顶楼 | |
发表时间:2010-01-08
一堂如何提高代码质量的培训课
这是我的专栏,还在继续。。。 |
|
返回顶楼 | |
发表时间:2010-01-08
不错!,写的很生动!
|
|
返回顶楼 | |
发表时间:2010-01-08
说的好,继续关注~~~
|
|
返回顶楼 | |
发表时间:2010-01-08
不知道是不是楼主经手的项目,有太多的需求变化,所以才总结了这些要点,才觉得只有“高质量”的代码,才能够适应那些突如其来,但又合情合理的需求变化。
我经手的一两个项目,一个企业项目,一个国外项目,需求变化也很频繁。其中一个项目,在总共200天的时间内,经历了大小50多次的变化。这其中固然有管理和沟通上的原因,但仔细分析一下,就能发现,不管什么项目,需求的变化是不可避免的! 我们程序员是根据文档写代码的,但代码中的细节肯定比文档中的多得多,这就导致了代码不可能完全符合文档的意图。另一方面,文档也不可能完整地反映客户脑子里的那个实际构想,这就导致了代码(程序)与客户意图的不一致,这就是需求变化的根源。 不要相信“需求挖掘”,“需求分析”可以把客户的意图完整地找出来。假如客户的思维可以细致到代码级别(详细到每一个函数),那么一定会有一种机器,可以把客户的脑波直接编译成他想要的可执行程序! 楼主提到的各种设计模式,实际上是把需求抽象成代码结构的技术。它确实提高了代码的可伸缩性,但它与“适应需求变化”是两码事。如果客户说“使用XML作为通信格式”,那么你当然不应该把XML作为直接处理的数据,而是应该做一层抽象。这就好比设计一个“温度监控程序”,客户说“当温度低于10度时报警”,你当然不应该把“10度”写死到程序中。 那么怎么样才能“灵活适应需求变化”呢?我的意见是不要在这方面做过多的希望。就算你把客户调研得再透彻,跟着他们吃、住、工作,写了一份详细的文档,客户还是会提出他们认为“锦上添花”,但让你猝不及防的变更(客户的想象力有时候太丰富了)。所以,不要去妄想猜测需求今后的变化,这属于“过度设计(Over Design)”。试想,在某一个节点上,有两种以上的变化方向,你是猜对的几率大,还是猜错的几率大呢? 我认为,我们必须接受“需求会变更”的事实,客户方也不得不承担需求变更所带来的影响。程序员只需依据合约中确认过的文档写代码。在这样的情况下,什么才是“高质量的代码”呢? 其实很简单:有完整的单元测试和集成测试的代码,才是高质量的代码。自己写过测试的人就知道,写测试程序时,自己是以“消费者”的身份来使用自己的代码。你的代码写得混乱,那么你的测试程序就很难写,所以你会被迫地去重构,直到你能完整地写出测试程序。这对管理者来说,也是最好的办法:你不能要求你所有的程序员都是经验丰富的高手,但你可以要求他们必须写测试! 有了单元测试和集成测试,你就不用担心代码结构不好了,也不用担心各种需求变化会搞垮程序了。不管代码有多大的变更,跑一跑测试程序,通过了,就OK了。 其实,写"高质量"的代码是很简单的事吧 |
|
返回顶楼 | |
发表时间:2010-01-08
最后修改:2010-01-08
看来你也是一个有丰富经验的技术人员,但你说的我只能同意一部分。
不错,我们要避免过度设计,这也是我在项目中不断强调的内容。但是,对需求变更的提前预测是你必须考虑的内容,如果不这样,当项目变更来临时你必然会猝不及防。过度设计与预测变更是一对矛盾的两端,关键在于把握一个度。 怎样才能应对来自客户需求的变化,我认为在于设计,更加精妙的设计。糟糕的设计,只能让你在需求变化的汪洋中望洋兴叹。怎样才能精妙设计呢?答案就在DDD,领域驱动设计。我这篇文章从简到难一步一步描述,最终的归宿就是DDD。 在我的项目中,项目到今年已经是第五个年头了,每年都在维护、变更、增加新功能、升级。在这个项目里,变更和升级是一种常态。相信像这样的项目不在少数。 |
|
返回顶楼 | |
发表时间:2010-01-08
我总结出的代码“高质量”就是系统好用,业务人员觉得好用。
不用管他们的天天变来变去的需求,因为那帮人自己都不懂自己要干嘛,替他们想明白他们要干嘛,他们以后准备拿这个做什么,他们会怎么使用,然后再设计,质量都不差。 |
|
返回顶楼 | |
发表时间:2010-01-08
fangang 写道
然而,如果说提高代码复用是提高代码质量的初阶,使用设计模式也只能是提高代码质量的中阶。那么,什么是高阶呢?我认为是那些分析设计理论,更具体地说,就是职责驱动设计和领域驱动设计。
从初阶到中阶,这一般是普通程序员工作之后的事,但往往有了工作了,就不思进取了,还停留在初阶甚至之前。
这一点我想在国内(外)很普遍吧!
都看看楼主的帖,大家都进步一些,工作该多开心啊! |
|
返回顶楼 | |
发表时间:2010-01-08
投精华了~
|
|
返回顶楼 | |
发表时间:2010-01-08
fangang 写道
d.外观模式
32个经典模式中的外观模式,对开发者的代码规划能力提出了更高的要求,它要求开发者对自己开发的所有代码有一个相互联系和从中抽象的能力,从各个不同的模块和各个不同的功能中,抽象出其过程比较一致的通用流程,最终形成外观。譬如说,读取XML并形成工厂,是许多模块常常要使用的功能。它们虽然有各自的不同,但是总体流程都是一样的:读取XML文件、解析XML数据流、形成工厂。正因为有这样的特征,它们可以使用共同的外观,那么,什么是外观模式呢? 外观模式(Façade Model)通常有一个抽象类。在这个抽象类中,通常有一个主函数,按照一定地顺序去调用其它函数。而其它函数往往是某这个连续过程中的各个步骤,如以上实例中的读取XML文件、解析XML数据流、形成工厂等步骤。由于这是一个抽象类,这些步骤函数可以是抽象函数。抽象类仅仅定义了整个过程的执行顺序,以及一些可以通用的步骤(如读取XML文件和解析XML数据流),而另一些比较个性的步骤,则由它的继承类自己去完成(如上例中的“形成工厂”,由于各个工厂各不一样,因此由各自的继承类自己去决定它的工厂是怎样形成的)。
各个继承类可以根据自己的需要,通过重载重新定义各个步骤函数。但是,外观模式要求不能重载主函数,因此正规的外观模式其主函数应当是final(虽然我们常常不这么写)。另外,外观模式还允许你定义的这个步骤中,有些步骤是可选步骤。对与可选步骤,我们通常称为“钩子(hood)”。它在编写时,在抽象类中并不是一个抽象函数,但却是一个什么都不写的空函数。继承类在编写时,如果需要这个步骤则重载这个函数,否则就什么也不写,进而在执行的时候也如同什么都没有执行。 通过以上对外观模式的描述可以发现,外观模式可以大大地提高我们的代码复用程度。
以上一些常用设计模式,都能使我们快速提高代码质量。还是那句话,设计模式不是什么高深的东西,恰恰相反,它是初学者快速提高的捷径。然而,如果说提高代码复用是提高代码质量的初阶,使用设计模式也只能是提高代码质量的中阶。那么,什么是高阶呢?我认为是那些分析设计理论,更具体地说,就是职责驱动设计和领域驱动设计。
楼主很强大,不过这个貌似是模板方法模式吧 |
|
返回顶楼 | |