有人说,在软件开发中,优良的程序员比蹩脚的程序员的工作产出高 100 倍。这听起来有点夸大,实际上,我可能更乐观一点,就我看来,有时候,后者的工作成绩可能是负向的,也就是说,因为他的工作,我的项目会变得更加艰难,代码变得更加艰涩,难以保护,工期因而推迟,各种莫名其妙改来改去的 bug 一再呈现,而且这种场面还会蔓延扩散,连那些原本还好的代码模块也逐步腐坏变烂,最初我的项目难以为继,以失败告终。
如果仅仅是看过程,蹩脚的程序员和优良的程序员之间,差异并没有那么显著。然而从后果看,如果最初的后果是失败的,那么产出就是负的,和胜利的我的项目比,差异不是 100 倍,而是无穷倍。
程序员的好坏,一方面体现在编程能力上,比方并不是每个程序员都有编写一个编译器程序的能力;另一方面,体现在程序设计方面,即便在没有太多编程技能要求的畛域下,比方开发一个订单治理模块,只有需要明确,具备肯定的编程教训,大家都能开发出这样一个程序,但优良的程序员和蹩脚的程序员之间,仍然有微小的差异。
在软件设计开发这个畛域,好的设计和坏的设计最大的差异就体现在应答需要变更的能力上。而好的程序员和差的程序员的一个重要区别,就是看待需要变更的态度。差的程序员胆怯需要变更,因为每次针对需要变更而开发的代码都会导致无尽的 bug;好的程序员则欢送需要变更,因为他们一开始就针对需要变更进行了软件设计,如果没有需要变更,他们优良的设计就没有了用武之地,产生一拳落空的感觉。这两种不同态度的背地,是设计能力的差别。
一个优良的程序员一旦习惯设计、编写可能灵便应答需要变更的代码,他就再也不会去编写那些僵化的、软弱的、艰涩的代码了,甚至仅仅是看这样的代码,也会产生强烈的不难受的感觉。记得一天下午,一个技术不错的共事忽然跟我销假,说身材不难受,须要回去劳动一下,我看他脸色苍白,有气无力,就问他怎么了。他答复:方才给另一个组的共事 review 代码,代码太恶心了,看到中途去厕所吐了,当初浑身好受,须要劳动。
诧异吗?但实际上,蹩脚的代码就是能产生这么大的威力,这些代码在运行过程中使零碎解体;测试过程中使 bug 无奈收敛,越改越多;开发过程使开发者陷入迷宫,掉到一个又一个坑里;而仅仅是看这些代码,都会使阅读者头晕眼花。
蹩脚的设计
蹩脚的设计和代码有如下一些特点,这些特点独特铸造了蹩脚的软件。
僵化性
软件代码之间耦合重大,难以改变,任何渺小的改变都会引起更大范畴的改变。一个看似渺小的需要变更,却发现须要在很多中央批改代码。
脆弱性
比僵化性更蹩脚的是脆弱性,僵化导致任何一个渺小的改变都能引起更大范畴的改变,而软弱则是渺小的改变容易引起莫名其妙的解体或者 bug,呈现 bug 的中央看似与改变的中央毫无关联,或者软件进行了一个看似简略的改变,重新启动,而后就莫名其妙地解体了。
如果说僵化性容易导致本来只用 3 个小时的工作,变成了须要三天,让程序员加班加点工作,于是开始吐槽工作的话,那么脆弱性导致的忽然解体,则让程序员开始抓狂,狐疑人生。
牢固性
牢固性是指软件无奈进行疾速、无效地拆分。想要复用软件的一部分性能,却无奈容易地将这部分性能从其余局部中分离出来。
目前微服务架构大行其道,然而,一些我的项目在没有解决软件牢固性的前提下,就硬着头皮进行微服务革新,后果可想而知。要晓得,微服务是低耦合模块的服务化,首先须要的,就是低耦合的模块,而后才是微服务的架构。如果单体零碎都做不到模块的低耦合,那么由此革新进去的微服务零碎只会将问题加倍放大,最初就怪微服务了。
粘滞性
需要变更导致软件变更的时候,如果蹩脚的代码变更计划比优良的计划更容易施行,那么软件就会向蹩脚的方向倒退。
很多软件在设计之初有着良好的设计,然而随着一次一次的需要变更,最初变得千疮百孔,趋势腐坏。
艰涩性
代码首先是给人看的,其次是给计算机执行的。如果代码艰涩难懂,必然会导致代码的维护者以设计者不冀望的形式对代码进行批改,导致系统腐坏变质。如果软件设计者冀望本人的设计在软件开发和保护过程中始终都能被良好执行,那么在软件最开始的模块中就应该保障代码清晰易懂,后继者参加开发保护的时候才有章法可循。
一个设计腐坏的例子
软件如果是一次性的,只运行一次就被永远抛弃,那么无所谓设计,能实现性能就能够了。然而事实中的软件,大多数在其漫长的生命周期中都会被一直批改、迭代、演变和倒退。淘宝从最后的小网站,倒退到明天有上万名程序员保护的大零碎;Facebook 从扎克伯格一个人开发的小软件,成为现在服务寰球数十亿人的巨无霸,无不经验过并将持续经验演变倒退的过程。
接下来,咱们就来看一个软件在需要变更过程中,一直腐坏的例子。
假如,你须要开发一个程序,将键盘输入的字符,输入到打印机上。工作看起来很简略,几行代码就能搞定:
void copy()
{
int c;
while ((c=readKeyBoard()) != EOF)
writePrinter(c);
}
你将程序开发进去,测试没有问题,很开心得公布了,其余程序员在他们的我的项目中依赖你的代码。过了几个月,老板突然过去说,这个程序须要反对从纸带机读取数据,于是你不得不批改代码:
bool ptFlag = false;
// 应用前请重置这个 flag
void copy()
{
int c;
while ((c=(ptFlag? readPt() : readKeyBoard())) != EOF)
writePrinter(c);
}
为了反对从纸带机输出数据,你不得不减少了一个布尔变量,为了让其余程序员依赖你的代码的时候能正确应用这个办法,你还增加一句正文。即便如此,还是有人遗记了重设这个布尔值,还有人搞错了这个布尔值的代表的意思,运行时进去 bug。
尽管没有人嗔怪你,然而这些问题还是让你很丧气。这个时候,老板又来找你,说程序须要反对输入到纸带机上,你只好硬着头皮持续批改代码:
bool ptFlag = false;
bool ptFlag2 = false;
// 应用前请重置这些 flag
void copy()
{
int c;
while ((c=(ptFlag? readPt() : readKeyBoard())) != EOF)
ptFlag2? writePt(c) : writePrinter(c);
}
尽管你很贴心地把正文里的”这个 flag“改成了”这些 flag“,但还是有更多的程序员遗记要重设这些奇怪的 flag,或者搞错了布尔值的意思,因为依赖你的代码而导致的 bug 越来越多,你开始犹豫是不是须要跑路了。
解决之道
从这个例子咱们能够看到,一段看起来还比较简单、清晰的代码,只须要通过两次需要变更,就有可能变得僵化、软弱、粘滞、艰涩。
这样的问题场景,在各种各样的软件开发场景中,随处可见。人们为了改善软件开发中的这些问题,使程序更加灵便、强健、易于应用、浏览和保护,总结了很多设计准则和设计模式,遵循这些设计准则,灵便利用各种设计模式,就能够防止程序腐坏,开发出更弱小灵便的软件。
比方针对下面这个例子,更加灵便,对需要更加有弹性的设计、编程形式能够是上面这样的:
public interface Reader {int read();
}
public interface Writer {void write(int c);
}
public class KeyBoardReader implements Reader {public int read() {return readKeyBoard();
}
}
public class Printer implements Writer {public void write(int c) {writePrinter(c);
}
}
Reader reader = new KeyBoardReader();
Writer writer = new Printer():
void copy() {
int c;
while(c=reader.read() != EOF)
writer(c);
}
咱们通过接口将输出和输入形象进去,copy 程序只负责读取输出并进行输入,具体输出和输入实现则由接口提供,这样 copy 程序就不会因为要反对更多的输出和输出设备而不停批改,导致代码简单,应用艰难。
所以你能看到, 应答需要变更最好的方法就是一开始的设计就是针对需要变更的,并在开发过程中依据实在的需要变更一直重构代码,放弃代码对需要变更的灵活性。
小结
咱们在开始设计的时候就须要思考程序如何应答需要变更,并因而领导本人进行软件设计,在开发过程中,须要敏锐地察觉到哪些地方正在变得腐坏,而后用设计准则去判断问题是什么,再用设计模式去重构代码解决问题。
我在面试过程中,考查候选人编程能力和编程技巧的次要形式就是问对于设计准则与设计模式的问题。
我将在”软件的设计原理“这一模块,次要讲如何用设计准则和设计模式去设计强健、灵便、易复用、易保护的程序。心愿这部分内容可能帮你把握如何进行良好的程序设计。
思考题
你在软件开发实际中,是否已经看到过一些蹩脚的代码?这些蹩脚的代码是否合乎僵化、软弱、牢固、粘滞、艰涩这些特点?这些代码给工作带来了怎么的问题呢?
欢送你在评论区写下你的体验,我会和你一起交换。