这一次我们考虑一个简单的实例:一个从键盘读入字符,然后输出到打印机上。假设你是一个在企业的员工,你的老板要你写这样一个程序。经过―番快速思考后,你断定所需的代码不会超过10行。设计和编码所需要的时间会远远小于一个小时。考虑到交叉功能会议、质量教育会议、日常的小组进度会议以及当前3个正在处理的难题,要完成这个程序应该要花费你大约一周的时间一一如果你下班后仍坚持工作的话。不过,你总是把估算值乘以3。然后你告诉老板:3周吧,可以写完。
①小试牛刀
于是,你信心满满地写下了代码:
void Copy(){
int c;
while(c=RdKbd()!=EOF)
WrtPrt(c);
}
在写下这样的代码后,你在公司声名鹊起,作为一流程序员备受赞誉,你也很享受这样的成功。
②需求变更
几个月后,老板又来找你,说有时会希望Copy程序能从纸带读入机中读入信息。你咬牙切齿、翻着白眼。你想知道为何人们总是改变需求。你的程序不是为纸带读入机设计的啊喂!你警告老板像这样的改变会破坏程序的优雅性。不过,老板很固执,他说用户有时确实需要从纸带读入机中读取字符。你叹了口气,开始计划修改方案。你想在Copy 函数中添加个boolean变量。如果变量值为true,那么就从纸带读入机中读取信息;如果变量值为false,就像以前一样从键盘读取信息。糟糕的是,现在已有许多其他程序正在使用Copy程序,你不能改变Copy程序的接口。改变接口会导致长时间的重新编译和重新测试。单单系统测试工程师就会痛恨你,更别提配置控制组的7个家伙了。并且过程控制部门会用专门的一天时间来对所有调用了Copy的模块进行各种各样的代码评审。总之就是麻烦。
看来,不能采用改变接口的方法。那么,如何才能使Copy程序知道它必须要从纸带读入机读取信息呢?你当然会使用一-个全局变量!结果如下所示.
bool ptFlag=false;
// remember to reset this flag
void Copy(){
int c;
while(c=(ptflag?RdPt():RdKbd())!=EOF)
WrtPrt(c);
}
这次任务圆满地完成了,这次比上次甚至更加成功,生活是美好的。
③得寸进尺
你的老板又来了,因为前几次的成功,他很器重你。他告诉你,客户有时希望Copy 程序可以输出到纸带穿孔机上。客户!他们总是毁坏你的没计。如果没有客户,编写软件会变得容易得多。你告诉老板不断地变更会对你的设计的优雅性造成极度的负面影响,如果变更继续以这样可怕的速度进行,那么在年底前软件就会变得难以维护。老板心照不宣地点点头,接着告诉你无论如何都要进行这次改动。
可恶!你一边骂着一边思考,但其实也还好,跟上次差不多,然后你又写下了第三版程序:
bool ptFlag=false;
bool punchFlag=false;
// remember to reset these flag
void Copy(){
int c;
while(c=(ptflag?RdPt():RdKbd())!=EOF)
punchFlag?WrtPunch(c):WrtPrt(c);
}
尤其让你感到骄傲的是,你还记得去修改注释。可是,你对程序的结构开始变得摇摇欲坠感到批心。任何对于输入设备的再次变更肯定会迫使你对while循环的条件判断进行彻底的重新组织。至此,你该考虑重新寻找工作了……
Game Over!
这个例子来源于上一次说的那本书,《敏捷软件开发:原则、模式与实践》。故事的要点是要说明,在变化面前,程序的设计退化的速度是多么的快。Copy程序的原始设计是简单并且优雅的。但是仅经历了两次变更,它就已经表现出了僵化性、脆弱性、牢固性、不必要的复杂性、不必要的重复及晦涩性的症状。在大多数软件项日中最不稳定的东西就是需求。需求处在一个持续变动的状态之中。这是我们作为开发人员必须得接受的事实!我们生存在一个需求不断变化的世界中,我们的工作是要保证我们的软件能够经受得住那些变化。
假设我们采取敏捷软件开发的方案,一开始我们编写的程序和①中是一样的,问题在于当老板要求咱可以从纸带读入机中读取信息时,我们要会做出这样的反应:修改设计并使修改后的设计对于那类需求的变化具有弹性。
class Reader{
public:virtual int read()=0;
};
class KeyboardReader:public Reader{
public:virtual int read(){
return RdKbd();
}
}
KeyboardReader GdefaultReader;
void Copy(Reader& reader=GdefaultReader){
int c;
while((c=reader.read())!=EOF){
WrtPrt(c);
}
}
这样的设计显然是更佳的,最直观的,在要实现新需求时,团队抓住这次机会去改进设计,以便设计对于将来的同类变化具有弹性,而不足设法去给设计打补丁。从现在开始,无论何时老板要求一种新的输入设备,团队将都能以不导敛Copy程序退化的方式做出反应。关键的是如果我们学习了设计原则,那么我们更会发现这个例子是OCP(开闭原则)和DIP(依赖倒置原则)的极佳实践!
有人会认为他们仅仅完成了一半的工作。他们在使自己免于不同的输入设备带来的麻烦时,本可以也使自己免于不同的输出设备带来的麻烦。然而,团队实在不知道输出设备是否会变化。现在就添加额外的保护没有任何现实意义。很明显,如果需要这种保护时,以后可以非常容易地添加。因此,实在没有现在就添加的理由。
因此,简而言之,敏捷开发人员知道要做什么,是因为:
-
他们遵循敏捷实践去发现问题;
-
他们应用设计原则去诊断问题;
-
他们应用适当的设计模式去解决问题。
软件开发这三个方面的相互作用就是设计。而所谓问题,就是这两次讲的一个重要的主题——软件设计的七宗罪,这七宗罪导致了软件的腐化,而软件腐化是敏捷开发人员来说是不可忍受的!很多人学了设计模式存在一个很重要的问题——什么时候去用设计模式,为什么学习设计模式我们需要先了解设计原则。那么我相信在这个部分的讲述后,你会对设计模式的学习有更深的了解。那么下一次,我们就讲讲设计原则吧,然后用设计原则去诊断问题,以作为审丑到审美的过渡。



