崩溃!如何面对令人脱发的老代码?

>>强大,10k+点赞的 SpringBoot 后台管理系统竟然出了详细教程!

点击关注公众号,实用技术文章及时了解崩溃!如何面对令人脱发的老代码?

办公室里,老T戴上耳机播放一曲浪漫旋律,写出的代码散发出一股诗般的文艺气息。每一次从容不迫的敲下回车,枯燥无味的程序逻辑突然变得有血有肉,运行时的每个字节仿佛都会随着音乐的节奏而舞动。伴随着荷尔蒙的迸发,它们像是被赋予了鲜活的生命,跃然于Monokai风格黑底花字的IDE之上。


崩溃!如何面对令人脱发的老代码?


小白是公司新招的实习生,初入职场的他经验不算丰富,一直对老T的神操作充满了好奇与崇敬。这天一大早小白就跑来老T的办公桌前:
“T哥,听什么呢,能否请教您一些问题?”
老T摘掉耳机,虽然被打断但依旧面带微笑:“哦,什么问题你说。”
小白焦躁的眼神中透着几分期许:‘’我实现的一个模块已经交付了,可是需求不停地变更,太烦人了。这导致我的代码要经常改来改去,我头都大了。而且随着业务的深入,大家的代码量都越来越大,经常看不懂别人的代码,维护起来简直让人抓狂。就像下面的情况一样。所以,如何才能杜绝这些问题的发生?”
崩溃!如何面对令人脱发的老代码?
老T扶了一下眼镜,胸有成竹地说:“首先你要明白,我们并不是为了写代码而写代码。一个好的软件架构,一定是将不变的部分抽象出来固化,把变化的部分隔离开来,使它们统统各司其职地模块化,这样下来你代码维护起来就变得轻而易举,就像下面这样井井有条。”
崩溃!如何面对令人脱发的老代码?
小白好像听了天书一样,一脸茫然地问:“虽然看起来赏心悦目,但这是如何实现的?能不能讲得更具体一些?”
15分钟,我帮你彻底梳理清楚。老T不紧不慢,徐徐道来。
“对于一个优秀设计的软件而言,你可以不用去看别人封装好的代码,更不必试图弄清楚其内部运行机制,你只需要知道它的功能是什么;输入输出是什么,怎么用它就可以了。”
小白眼前一亮:“这么神奇?但大多数情况我都得拆开别人的代码看,这是为什么?”
老T淡定地打开了PPT,边画边讲:“我们就以电脑的发展史来举例,故事从一台老电脑开始”


崩溃!如何面对令人脱发的老代码?


“这台早期的电脑高度集成,配件之间的耦合性极大,因为整机都一体化成了一个类,内部线路如胶似漆难以拆分,就像我们上面的类一样庞杂。如果键盘坏了可能就比较麻烦,我们得把主机拆开来更换,因为它对外根本没有接口。”


“后来有人提出了模块化的概念,设备间通过接口传递数据,于是各种外部设备如雨后春笋般涌现:鼠标,键盘,摄像头,打印机,外接硬盘……”

“但这时又出现一个问题,每种设备都有各自的接口,那么电脑主机上得有多少种接口啊?串口、并口、PS2接口……接口泛滥成灾,制定标准化的接口势在必行,于是便有了现在的USB接口。”

“USB提供了统一的接口标准,只要符合这种标准的设备则可以进行接驳,然后各种各样的设备也都陆陆续续支持了USB接口。”

崩溃!如何面对令人脱发的老代码?

“光说不练不成,我们来写点代码。”老T顺手打开了IDE。“照着上面这个图,我们先写一个USB接口。”

public interface USB {
public String readData();
}

“一目了然,上面是一个接口,名字就叫USB,它确立了一个标准的接口方法readData()。接着外设键盘(Keyboard)和鼠标(Mouse)两个类统统实现(Implements)了USB接口。”

public class Keyboard implements USB {
@Override public String readData() { return "键盘敲击,字符舞动……"; }
}
public class Mouse implements USB {
@Override public String readData() { return "鼠标移动、划出一道彩虹……"; }
}

看了代码,小白连连点头:“的确,这真的太形象了”。

“接口和它的外设都写好了,下来我们得有一台电脑主机,我们就叫它Computer类。”

public class Computer {
private USB usb;
    public void setUSB(USB usb){ this.usb = usb; }
    public void display(){        System.out.println("屏幕上显示:" + usb.readData()); }
}

“看好了,这电脑里面包含了一个USB的接口(第3行),并且对外暴露了一个setUSB(USB usb)的方法(第5行),这就意味着它允许我们把任何USB设备插上去,但是一定得是USB接口的实现,否则就会报错。”

小白逐渐进入了状态:“对,这就好像以前的PS2圆口鼠标,没办法插入USB接口”。

老T趁热打铁:“此外,电脑主机还有一个display()方法(第9行)用以将usb里读取的内容展示于屏幕上。最后,见证奇迹的时刻到了,我们让这台电脑跑起来,再接入设备测试一下。”

public class Client {
public static void main(String[] args){
Computer computer = new Computer(); System.out.println("电脑启动成功。");
System.out.println("1.接入USB设备……"); computer.setUSB(new Keyboard());        computer.display();
System.out.println("2.接入USB设备……"); computer.setUSB(new Mouse()); computer.display();
}
}

键盘劈里啪啦的作响,看着老T在键盘上飞舞的双手,小白不禁解读到:“实例化电脑后,usb接口上插入了键盘(第9行),那么屏幕上应该蹦出来键盘入的字符;然后又换上了鼠标(第13行),这次屏幕上一定会显示出鼠标指针了。”

“说得不错,让我们拭目以待。”老果断运行了Client类的main方法。

崩溃!如何面对令人脱发的老代码?

“果然如你所料,接上哪个设备,电脑就显示谁送来的数据了。”老T喝了一口茶接着说:“对于电脑而言,它根本不知道对接的是谁,它只管向USB接口拿数据即可。而对于实现类已经成为独立于主机(宿主)之外的外挂模块(插件)了。在设计模式里这叫作【策略模式】”。

小白似乎已经完全搞懂了,茅塞顿开:“原来如此,这种设计简直太妙了!一个插件类就是对某种行为的封装,后面如果有新的设备需求,我只需要实现标准的USB接口,然后优雅地调用setUSB(插件对象)把它接入即可,根本不需要拆开主机重新修改里面的逻辑了,这扩展性也太棒了!”

老T接着抛出了其惊世骇俗的哲学观:“变化是世界的常态,唯一不变的就是变本身,天时地利人和,拥有顺势而为、随机应变的能力才能立于不败之地。策略模式的运用能让系统的应变能力得到提升,适应永远得不到满足而随时变化的需求。接口的巧妙运用让一系列的策略都可以脱离系统之外而单独存在了,使系统拥有更灵活、更强大的【可插拔】扩展功能。”说罢顺手抛出一张类图。

崩溃!如何面对令人脱发的老代码?

此刻老T发现小白的脸上又出现了一个大写的“懵”字:“好吧,类图其实就对应着下面这张图,对比着看是不是更好理解了?”

崩溃!如何面对令人脱发的老代码?


小白仔细看了两张图,又回忆了一下代码,瞬间醍醐灌顶般透彻:“秒懂了,秒懂了。”
老T接着发问:“之前你提到的PS2鼠标(类图带红叉的那个类),你看它传递的数据类型是byte[],无法完成与电脑主机的对接,你该怎么办?”
小白挠挠了头:“是不是有另一种模式可以解决?”。
“没错,我们都知道有一种设备叫转换器,它能轻松地将老旧的接口设备调制适配到新的接口达到兼容的目的,这个模式叫作【适配器】”
崩溃!如何面对令人脱发的老代码?
“具体的设计模式还有很多,这个要靠你自己去学习实践了。总之,对于一个庞大的软件系统,我们绝不能想到哪里就写到哪里,这样就是蛮干如此下去,我们会造出很多只哥斯拉一样庞大的怪物类,内部堆积着大量的变量与逻辑代码,而且类与类之间的关系可能也是驴唇不对马嘴。久而久之,系统会变得错综复杂,难以维护。终于有一天,你突然发现,代码已经没法改了,千丝万缕的逻辑牵一发而动全身,稍微一个微小的改动就会造成整个系统的瘫痪。”
小白又疑惑地问:“是什么导致程序一步一步发展成这样的?
老T笑着说:“你想想,当系统中爬满了怪物类以后,你哪有精力一个个拆开去仔细研读?当类间的耦合太强,而你为了添加新功能又不得不修改其中代码的时候,很可能就更倾向于绕开老代码的方式来打补丁,修窟窿,这样造成更多的逻辑堆积与混乱。一旦陷入这种困境后,程序逻辑会变得越发古怪,越是古怪后人更是无法维护,致使软件变得臃肿不堪,运行效率低下,一种恶性循环的死亡怪圈就此形成。”
崩溃!如何面对令人脱发的老代码?
小白被眼前的景象震惊了:“这简直太形象了。”
由于画面过于酸爽,老T顺手合起了电脑:“嗯,等你积累的项目经验足够多了,就会发现重构越早发生越好,一般都是边写边改。甚至是在设计之初我们就有了大体的架构,后面重构就会少发生。所以自始至终我们都得有设计模式的思维与格局,贯穿于整个设计开发周期,而不是等到上面情况发生才亡羊补牢。”
老T最后总结到:“相较于无脑化的代码堆叠,设计模式能让系统以一种更为优雅的方式解决现实问题,并有能力应对不断扩展的需求。恰当的设计模式运用能使软件系统的结构变得更加合理,让软件模块间耦合度大大降低,提升系统的灵活性与扩展性,以便我们在最小改动或者不做改动的前提下,通过增加模块的方式对系统功能进行功能增强。”
小白终于意识到了:“原来设计模式如此重要。”
“设计模式虽然多,但总结下来也就20多种。好了,讲太多你也吸收不了,不要想一口就吞个胖子,学习需要由浅入深一步一个脚印才能将它们逐个攻破,并且理论需要与实践相结合,最终才能融会贯通于实际项目中去。所以慢慢来,今天就到这里吧。”




声明:以上内容由《秒懂设计模式》作者摘取改编自本书章节:
崩溃!如何面对令人脱发的老代码?
老规矩,小程序抽奖,抽出10名幸运粉丝,每人免费赠送一本《秒懂设计模式》。(疫情地区,快递不接的不予发货,见谅)

崩溃!如何面对令人脱发的老代码?

附:《秒懂设计模式》目录
崩溃!如何面对令人脱发的老代码?


原文始发于微信公众号(Java知音):崩溃!如何面对令人脱发的老代码?