软件开发的复杂性 :
计算机硬件界的摩尔定律(每隔 18 个月计算机硬件的运算速度提高一倍,价格下降一半)
适用于硬件的发展规律已经超过三十年了。人们想当然的认为计算机软件的发展速度和硬件的发展速度相当,但是不幸的是:每次重大的硬件升级之后,随着更大功能更丰富的软件的出现,硬件的潜能再一次被无情的榨取殆尽。许多开发的软件系统不断的遭受进度延期,人员资金和时间等预算无休止的增加,软件质量的不断反复,开发出来的系统对客户的新需求响应缓慢,更改困难的噩梦。
这样的现实是由软件的固有复杂性造成的,软件不同于硬件的生产过程,是由人的智力劳动完成人的需求到机器程序的翻译转换过程。需求可能不清晰,对需求可能出现个人理解上的差异,选择实现方法的差异,需求的不断变化,具体实现语言平台的差异,软件生产中采用的过程,具体实现人员的变动等等都会对最终的产品产生影响。想象一下,如果一种变化的因素只有两种可能,那么可以使用简单的
0 , 1 表示,只有 10 个变化因素的组合就已经达到了 2--10=1024 种可能性,而实际开发中变化的因素轻易就超过 10
个以上,每个变化的可能是还不止两个,因此软件的复杂性很快就会超出人的理解程度。有一句经典的软件开发名言:世界上唯一不变的是变化本身。不断出现的变化,会使初始的设计和最终的需求之间的距离越来越远。
软件的臭味:
软件开发,使用,维护中出现了以下的“臭味”:
僵化性: rigidity 很难对系统进行改动,因为每个改动都会迫使许多对系统其他部分的其他改动。即使是简单的改动,也会迫使导致右依赖关系的模块的连锁改动。
脆弱性: fragility 对系统的改动会导致系统中和改动的地方在概念上无关的许多地方出现问题。出现新问题的地方和改动的地方没有概念上的关联,难以排错,排错的过程中又会引入更多的“臭虫”。
牢固性 immobility 很难解开系统的纠结,使它成为其他系统中重用的组件。系统中包含了对其他系统中有用的功能,当其他人想复用这个功能到新的系统时,剥离出独立的组件的难度远远大于重新实现的难度,在时间和进度的压力下,大多数人只有选择拷贝涂鸦的方式来实现新系统的功能。
粘滞性: viscosity 做正确的事情比错误的事情要困难。程序完成正常的功能总是倾向于得到不正确的结果。
不必要的复杂性: needless complexity 设计中包含有不具有任何直接好处的基础结构。为了预防后期维护更改需求的对源码的修改,在设计之初放置了那些处理潜在变化的代码来保持软件的灵活性,这样的结果是软件中包含了很多复杂的结构,理解起来更加困难。
不必要的重复: needless repetition 设计中包含有重复的结构,而该重复的结构可以使用单一的抽象进行统一。对鼠标右键(剪切,复制,粘贴)的滥用,使得完成同一或类似的代码片断出现在系统各处。如果原始的代码段完成的功能需要变化,或者存在错误,排错和增加新的功能变得非常困难。
晦涩性: opacity 很难阅读,理解。没有很好的表现出意图。
以上讨论了系统构架的臭味,下面讨论微观层次上代码的臭味:
代码的臭味
重复代码:重复的代码使得更改功能和排错更加困难。同样的模块错误会在拷贝粘贴的程序各处多次出现。
过长的函数:程序越长越难于理解,这已经是软件业开发的常识。越难理解的程序,使用维护的成本就越大。如果一个函数的行数超过一页,很少有人能够在看到下一页的时候还清楚的记得函数开头的变量定义,理解和查错更加困难。
过大类:在一个类中完成几乎所有需要的功能。十项全能的人是不存在的,软件也一样。
过长的参数列:如果一个函数(方法)的调用参数过长,使用这个函数的调用过程也一定是困难的。想象一下,调用一个十个以上参数存储过程会有多么痛苦。这还只是开始,如果任一个参数的定义(名称,类型)发生轻微的变化,函数的调用客户端会有多么大的改动。
其他的臭味还有发散式变化,散弹枪修改,依恋情结,数据泥团,基本型别偏执,复杂的 switch 分支语句,平行的继承体系,冗赘类,夸夸其谈的未来性,令人迷惑的暂时值域,过度耦合的消息链,中间转手人,狎昵关系,异曲同工的类,不完美的程序库类,纯数据类(数据哑元),子类不需要父类的某些特性,过多注释。详细的讨论可以参见《重构》的介绍。
面向对象软件设计的原则 :
总体原则 :
1.针对于接口(抽象)编程,而不要针对于实现(具体)编程。
举例来说:操作系统是对逻辑计算机的抽象,通过操作系统的抽象我们不需要考虑具体使用的硬件配置,可以在较高的层次上进行更高生产力的应用。再如:汇编语言对机器的
0 , 1 代码进行了抽象,大大加快了开发效率,后来使用的高级语言和第四代语言模型驱动抽象的级别更高,生产力也更高。再如: java
和 .net 实现于一个抽象的软件虚拟机,进一步使开发出来的组件可以跨平台和操作系统。通过抽象出数据访问层(持久化层),可以使业务逻辑和具体的数据库访问代码分离,更换数据库提供商对已有的组件没有影响。具体实现可以参照
hibernate 实现和 dao (数据访问对象)模式。
优势:
1 )降低程序各个部分之间的耦合性,使程序模块互换成为可能。调用的客户端无需知道具体使用的对象类型,只要对象有客户希望的接口就可以使用,对象具体是如何实现这些接口的,客户并不需要考虑。
2 )简化了程序各个部分的单元测试,将需要测试的程序模块中通过重构提炼出抽象的接口,然后编制和接口一致的 Mock 类,测试就会变得很容易。如果应用了测试优先的方法,从简化客户端调用的角度,还可以经过抽象改善软件模块的设计。
3 )模块的部署升级由于模块之间的耦合度降低变得更加容易。
相关的设计模式有创建型模式中的工厂模式,结构型模式中的代理模式和组合模式等。
2.对象组合优于类继承。
面向对象为软件开发引入了三大工具:继承,多态和重载。继承使得程序员可以快速的通过扩展子类来增加功能,但是由于继承是在编译时确定的,因此增加的功能较多时,继承不够灵活,还有可能出现“子类爆炸”的局面(为了完成新添功能,不得不在继承体系中添入大量的之间只有细微差别的子类,掌握使用扩展都会变得非常困难)。而通过对象的组合,可以动态透明的添加功能。相关设计模式有装饰模式和代理模式等。
3.分离变化。前面说过需求是在不断变化的,不同的变化可能是仓库安全库存的计算方法,可能是报表和数据的展现形式,通过把这些不同的变化识别并分离出来不同的对象委托,简化了客户端的调用和升级。相关的设计模式有命令模式,观察者模式,策略模式,状态模式,模版方法模式等。
具体原则:
1.单一职责原则 srp ( single responsibility principle ):一个模块的功能应该尽可能的内聚。如果一个类发生了变化,引起变化的原因应该有且只有一个。每一个类承担的职责都是一个变化的轴线,需求变化时,会体现为类的职责的变化。如果一个类承担的职责过多,就等于把这些职责耦合在了一起,一个职责的变化会影响这个类完成其他职责的能力,会出现前面所说的软件的臭味之一脆弱性。相关的设计模式有
2.开放封闭原则 ocp ( open closed principle ):一个模块应该对功能的扩展开放,支持新的行为,对自身的更改封闭。每次对模块的修改都可能会引入新的错误和新的依赖。因此扩展新功能时,已经编好的模块源码和二进制代码都是不应该修改的。相关的设计模式有适配器模式,桥接模式,访问者模式等。
3.Liskov 替换原则 lsp ( liskov subtitle principle )子类型必须可以替换掉他的基类型。一个基类的多个子类型之间要完成动态的替换,各个子类型必须都可以被他们的基类型替换,这样他们之间动态替换后,客户端调用的代码就不需要冗赘的
switch 类型判断代码。如果 子类型无法替换基类型,将会导致在派生类对象作为基类对象进行传值时的错误。这样多态机制处于瘫痪状态了。相关设计模式为组合模式。
4.依赖倒置原则 dip ( dependent inverse principle )高层模块不应该依赖于底层模块,抽象不应该依赖于细节,细节应该依赖于抽象。假定所有的具体类都是回变化的,因此如果一个客户端依赖于(调用或声明)具体的类型,那么当这个具体的类型变化时,依赖的客户端业必须同时进行修改。这些具体的更改可能出现在使用了某个特定的网络协议,特殊的系统
api 调用,特定的数据库储存过程等,这些用法或多或少都会使客户端调用和具体类型成为铁板一块,比较难于重用。 Java 社区中比较热门的
j2ee 轻量级容器框架 spring 就很好的实现了本原则。
5 .接口隔离原则 isp ( interface segregation principle )不应该强迫客户依赖于它们不使用的方法。因为每一个实现接口的对象必须实现所有接口中定义的方法。如果接口的粒度比较小,实现接口的对象可以使用一种即用即付的方式动态实现接口。每个接口的粒度很小,复用起来也非常容易。
这体现了一个趋势:为了更好的实现重用,接口,函数,模块和类等倾向于更容易使用的“小”体积。
敏捷软件开发的宣言和实践:
软件开发项目的失败使得人们开始思考软件开发的过程,人们希望通过引入严格的过程控制产生软件生命周期中各个阶段的文档和制品来保证软件的质量。比较出名的业界实施方法论有
cmmi (能力成熟度模型)和 rup (瑞理统一过程),这些方法论都是重型的。
举例来说,没有经过剪裁的 Rup 实现起来,至少需要在全周期完成四十个以上的制品文档,文档的编写维护和源代码的同步等需要非常多的资源,十人以下的开发团队一般没有精力、时间、人员配置完成这些制品。失控的过程的膨胀迫使人们寻找一种快速工作,相应变化的“敏捷的”方法。敏捷团队提倡通过团队成员之间充分有效的沟通统一大家的目标,结伴的方式完成开发技术的团队内传承,使用“够用就好”的轻量甚至免费的工具管理过程。可以正常工作的代码摆在首要地位,只有必要的时候才生产必要的文档。强调和客户面对面地交流合作,积极地响应客户需求的变化而不是遵循机械的计划。
使用较短的迭代周期,近早和持续提交有价值的软件给客户来验证并修正和用户需求的吻合程度。提倡可以持续的稳定的开发节奏,长期“小步快走”的方式代替突然的“百米冲刺”。保持设计最优,最简单的设计并且持续改进,不断调整。
|