首先回顾一下编码的乐趣,我们坐在家里舒适的沙发上,茶几上放着一杯热气腾腾的绿茶和几块可口的糕点,打开编译器,轻松地编写一段愉快的代码,并使其愉快地运行……生活总是充满乐趣。然而,生活并非如此,我们坐在隔断里揉着发酸的脖颈,喝下一口浓浓的咖啡以便看清屏幕上密密麻麻的代码,永远受着进度的威胁,时间永远不够用,bug越来越多……请吃下红色的药丸,欢迎来到真实的世界——软件工厂。
没错,我们都是软件工厂中的一员,我们可以抱怨软件工厂的制度,埋怨愚蠢并善变的客户,这些足以成为我们生产劣质代码的借口吗?先来看一下需求,下图是前期的需求变更:
图1 需求变更图
这是一份稳定的让人心理发毛的需求,没错,有哪个项目有如此稳定的需求?事实正是如此,用户试用一周后,发来了成堆的需求变更。于是大家怨气冲天:这会改死!没错,糟糕的设计导致过紧的耦合,每碰触一个地方都会导致意想不到的牵连,或许我用词不当,不是糟糕的设计,而是根本没有设计。不是吗?回想一下,碰到问题时是否能忍住强烈的实现愿望?是否动用过可敬的纸笔技术?是否分析过各种方法的优缺点?很可惜,答案都是“没有”,因为我看到系统中到处充斥着复制粘贴,看到了同一个方法的多个不同实现版本……好好计算一下,自己的代码中共有几种判断权限的实现?
如果一开始写的代码就臭气熏天,那么总有一天它会不可救药。让我们看一下散发着坏味道的代码:
- for (i=0; i<addselect.options.length; i++)
- {
- if(addselect.options.item(i).selected==true)
- {
- if(addselect.options.item(i).innerText=="有线电视78元"||addselect.options.item(i).innerText=="有线电视39元"||addselect.options.item(i).innerText=="有线电视13元")
- {
- }
- else{
- if(!checkValue_null("firstNumber", "号码(始)") || !checkValue_null("secondNumber", "号码(止)"))
- return ;
- }
- }
- }
这段JS代码的缺点显而易见:
1. 效率问题
每次循环都要重新计算addselect.options.length,而JS执行的缓慢速度是众所周知的;
2. 语义罗嗦
看看第一个if条件,天晓得它竟出自一个职业程序员之手;
3. 逻辑不清
连在一起的if和else想要执行什么?if的身体为什么是空的?给你30秒时间回答;
4. 重复的复制粘贴
请看第二个if表达式,对,就是那个最长的,系统中不止一处用到了这个判断。
有趣的是第一点,我经常会遇到关于性能的提问,“这样写肯定会影响性能”,奇怪的是我们往往看不到不经意间从身边溜走的性能。不要让性能成为编写臭气代码的借口,我的答案是首先关注可读性,最后考虑性能,良好的代码结构相当于一个有利地形,此时我们可以更多的关注影响性能的“20”部分。对性能优化的经典解决方案是:1、不要优化;2、还是不要优化。
再来一段:
- public InvoiceDisuse getInvoiceDisuseBean(com.founder.ocnbdmp.audit.common.roles.loadroles.RolesBean rolesBean, String userID, String companyID,
- String invoiceRecordTime, String invoiceCategoryID, String subInvoiceCategoryID,
- String invoiceDisuseAmount, String year, String firstNumber, String secondNumber, String mem);
如果你第一次就能把这个接口使用正确,那么恭喜你!如果你连续几次都用错了,请起立,为这个挑战人类极限的程序员敬礼!
正是类似的代码让我们修改效率低下:
图2 缺陷统计图
或许我们应当仔细反思一下问题被重新打开的原因。
当我们做一个项目时,一定会和以往的经验做个比较,我翻出了06年的一份统计数据:
图3 bug数统计图
图4 测试用例统计图
这是一个为期一年半的部级项目,其间由于国家政策问题经历了一次大规模修改,上面两图展示的是其中一个子系统的统计结果。
再来看看东方有线财务发票稽核项目。
有效代码共 32369,测试用例约230个。
图5 bug数统计图
期间共提交测试组37个版本,其中四次没有通过冒烟测试,我们很丢脸(或许测试人员已经在私下里议论了:“这些垃圾制造机!”哎……)。
项目规模虽然不同,但是不妨碍我们就质量进行比较。论技术比经验,我们远在注会系统之上,为何得到了如此的统计结果?在我看来有以下几点:
1. 需求理解不够
这或许是开发人员的通病,且病根难除。重复出真理,多看文档吧,虽然我也不愿意看,哎……
2. 设计不够
我们只看到了敏捷中“无需设计文档”,却没有看到“测试即时设计”,这句带有煽动意味的“无需设计文档”严重误导了我们!在遇到问题时尽量压制住马上打开编译器的冲动,多在纸上做一些推演,思考几点:这个问题已经有了现成的解决方法吗?是否一定要解决?是否还有其它的方式解决?这些方法的优缺点是什么?仔细思考,找出问题的真正所在。
3. 自测试不够
当我们的页面抛出一大堆杂乱无章的alert时,我们是否做过测试?当系统出现了一大堆的”Page Not Found”时我们是否做过测试?当用户手册出现不止一处的标号错误时我们是否做过测试?如果没有足够的自测试,我们永远不会得出一份像样的统计图。重复出真理,反复测试吧,别对自己的代码太好。
4. 查找、解决问题的方法不够
当我修改一段代码的时候,我第一件事就是了解这段代码的用途,理清它的逻辑;这样或许效果太慢,那就还一种:尝试着修改一个地方->运行->崩溃->再尝试一下->运行->崩溃……这辈子也别想改对。在编译器中打7个断点并不能找到问题,弄清问题后只需打一个断点,或许在打断点之前你已经知道答案了。愚公一定要移山吗?为什么不搬家?
5. 对界面重视程度不够
的确,我们的界面永远无法做到让一个瞎眼蛤蟆也能轻松操作,至少应该仔细规范一下布局吧。
6. 不够的东西太多了
为什么客户总有想掐死我的冲动?任重而道远啊……
作为技术人员,在我们拥有了编程技巧并且有了一定的经验后,共有三种方法可以提升我们的技术水平,一是研究高深的算法,二是深入了解计算机和网络结构,这两种方法可以让我们成为高手,所有招式随心而发,拈枝折柳皆可成为利刃,但是这两条路太难,除了严谨的治学态度和持之以恒的专研精神外,第二点,很遗憾,是天份。幸运的是我们还有第三种方法可以选择,那就是提高自己的抽象能力,设计模式就是为我们准备的。
每个人选择的做事方法不同,选择一个适合自己的方法并坚持下去,成功的四叶草或许就在前面的某个地方。