Alixx Skevington贴出一篇“完成宣言”以引发讨论,其中谈到团队成员针对各自的工作质量向彼此做出的承诺,并清晰表明了他们对于使用代码交付业务价值的承诺。
他的“完成”条件列表包括:
- 我会确保我的代码可用。我的代码是为了供他人使用、与他人交互的,我所写的一切都要保证这是令人愉快的过程,而且应该降低工作量,而不是增加。
- 我会确保我的代码风格服从团队一致同意的风格。未来会是别人,而不是我,来维护和修补我的代码。所以虽然我可以灵活设计并利用任何技术来构建我的解决方案,我还是会遵循标准,以便于其他人将来维护我的代码。
- 我同意将我的方法保持在合理的大小。大方法难以查看与调试。我会尽量把我的方法保持在合理的大小,以降低复杂度。
- 我会注释所有的代码。不管是创建新代码还是变更现有代码,我都会写下简明扼要的注释,说明我做了什么。这样其他人在阅读代码时,就能理解我做了什么以及代码试图实现的目的。
- 我同意对我的代码做单元测试。我同意让这些测试可以重用、足够健壮。我会确保测试说明它测试的对象及其原因。这样其他人在重构或修复bug时,不仅可以运行我的测试,还能了解我的代码试图达到的效果。
- 我同意维护现有代码的单元测试。当我改变或添加现有代码的新功能时,我会确保所有的测试都可以通过,而且新功能也有对应测试。
- 我同意努力让我的代码的测试覆盖率达到80%。检查完代码覆盖率,我就可以确保所写的一切代码都有价值,不存在会在未来产生问题的“惊奇代码”。我会努力让覆盖率更高。
- 我同意正确检查代码的集成状况。当我写完我的代码后,我会和其他开发人员一起检查我的代码,确保与他人的代码可以一起正确工作,交付客户请求的功能。
对于此,我不由地开始反思自己曾经做过的代码,自己是否真的完成了所有的代码呢?自己的代码是否已经满足了一定的标准,真的可以提交给用户使用了呢?其实,现在回顾这些问题有些亡羊补牢的意味,每个人在提交自己的代码之前都应该先问自己一声,这份代码真的完成了吗?
文中主要是从代码的各种特性来界定代码是否完成的标准的:
- 代码的可用性和易用性
- 可维护性和规范性
- 可测试性和健壮性
- 从总体上对系统的影响
代码的可用性和易用性
首先,对于代码(或者说程序)的可用性和易用性,这应该是最基本的要求。我们的代码开发出来是做什么的呢?不是用来孤芳自赏的,而是要放在业务环境中由业务人员来使用的,也可以说是来检验的。这样的话,可能就有下面的一些要求:
- 完全实现了业务上的要求,准确地完成了相关的任务。
- 在性能上表现良好,应该是节省了用户的时间,而不是浪费他们的时间。提高工作效率,他们的感受会很好。
- 没有太过复杂的操作,即使没有相关的培训,业务人员一眼看上去,大概的功能也就基本了解,并能够很快上手使用。
这些问题看似比较简单,但是实现起来却需要不少细节上的工作,不信你看下面的场景:
场景一:业务人员开始抱怨:你开发的东西根本就不是我要的,我一直是这么做的,为什么要让我改变工作的方式?
这可能会有两种可能,一种是我们在开发的系统中使用了比较先进的管理学理论,改变后的工作方式更有利于业务人员高质高效地完成工作任务,对于此,我们需要和他们耐心的沟通,并劝说他们试验一下,感受一下看看是否能够真正对他们的工作加以改善,一旦他们习惯了,就好了。
而另一种可能就是,没有达到业务上所需要的,业务上用系统根本就无法工作。这种情况也是非常可能出现的,特别是在国内的项目开发过程中,需求分析和概要设计都没有做好,就匆匆开始编码了。或者说,在看到实际的程序之前,业务人员根本就不知道他们想要的是什么,直到你做出来之后,他们才告诉你那不是他们想要的东西。
对于这种情况,或者说对于这种项目,都比较麻烦。但是想要改善的话,我有几点建议,一是要加强与客户的沟通。此时XP编程中的一条原则“现场客户”就非常适用。如果我们能够经常地和他们沟通,听取他们的意见,而且在开发的过程中按照“瞄准-射击-调整”的方式,能够少走不少的弯路,也比较容易得到用户的认可。其次是放下程序员的架子,把自己放在和业务人员同样甚至是低一些的位置上,虚心向他们学习,从客户的角度去理解业务流程,想方设法让他们的工作更简单,效率更高,而不是一味地强迫他们按照我们的思维模式去做,毕竟最终的东西不是我们使用。
在这里一定会涉及到的问题就是变更管理,这也是开发团队和客户之间经常需要“打架”的地方。其实有些时候,如果能够相互理解,不一味地纠缠在钱的问题上,反而更容易解决。试想一下,如果对于每个小的变更都纠缠得非常清楚地话,那么很多时候就会造成之间关系的僵化,从而客户不愿意把相关的业务知识告诉开发团队,那对于我们来说绝对是不可弥补的损失,而且不是能够用Money来衡量的。
场景二:你这东西也太慢了,本来我做这件事儿需要1个小时,用了你的系统之后需要2个小时,我不得不加班!
这个没说的,必须要调整了。很多人都收到学校中的一种思想的蛊惑;先把功能实现了再说,性能那个东西可以用硬件来弥补,以后再调整也没事儿。其实,对性能的考虑不仅仅是从开发程序之前就应该开始,而且是应该在整个系统开始之前就开始考虑,包括数据库服务器、应用服务器的设计,数据库中的数据的存储方式、空间的分配等等,都应该在系统开始之前就做了充分的工作,否则等墙快要倒了的时候,再去补,为时晚矣!
性能应该优化到什么程度呢?我觉得最基本的原则就是,比客户要求的标准稍稍好那么一点儿就可以了,这样他们会觉得你对他们的反馈很重视,并且已经超出了他们的预期,一定会满意的。
场景三:你这程序界面上的东西太多了,我根本不知道怎么做。而且操作也太麻烦了,要很多步才能完成一项工作
在没有完整的信息化系统之前,业务上很多的工作都是使用Excel之类的东西完成的,那是非常方便的工具。但是使用了开发的软件之后,就必须改变原有的工作习惯,比方说回车和Tab的使用,比方说增加新纪录的方式等等;此时就应该尽可能少地把操作的控制暴露给用户,而应该尽可能多地由系统在后台完成工作。如果看到满屏幕都是各种各样奇怪的控件,用户肯定会晕倒的。
所以说,想要开发出来的程序对于用户来说真的可用、易用,要做大量的工作。并且,随着时间的推移,后期肯定还需要大量的调整工作。
可维护性和规范性
对于代码来说,这两个属性其实是紧密相连的。什么样的代码最好维护呢?当然是规范的代码了。再差的规范也要比没有规范强得多。
之前做对日项目的时候,日本人对于“规范”这个东西(他们称之为开发规约)要求的极为严格。一方面会制定严格的规范来供大家遵守,其中不仅仅会包括对命名、代码格式的规定,甚至还包括了每个控件之间的距离,代码的注释的格式,代码中的注释要达到什么样的比例,每种代码结构(循环、选择等等)要怎么写,什么时候应该加空行等等,一般他们的代码规范都至少会有10页左右的内容。另一方面,他们还制定了比较完备的流程来保证规范的执行,代码开发完毕,首先是要进行代码Review,然后是进行单体测试。这两个过程并非是在某些国内项目中,就是走个过场,在对日的项目中甚至还制定了标准,每千行代码中必须Review出多少个问题,必须要测试出多少个Bug,都是有数量限制的,如果达不到标准,除非有充分的理由,否则这个过程是无法通过的。
经过了这么多严格的过程之后,对日项目想要达到的目的就是“所有代码看起来像是一个人写的”。尽管这有些理想化,毕竟每个人处理问题的思路还是会有些不同,但仅就代码来说,的确看起来干干净净,就像是同一个人编写的一样。自然在维护的时候,看起来至少不会有太严重的问题。
相反,在某些国内项目中,对于代码规范的重视程度明显不够,很多代码中连最基本的缩进和命名问题都没有解决好,更别提每个方法的长度,类和接口的设计等问题了。有时,不得不对那样的代码进行修改的时候,我都会先把规范整理一遍,然后再开始修改。但是,就像破窗子理论一样,有时心情不爽的时候,根本就不会做修改,甚至还会加入更多的不符合规范的东西。(那种情况是极少的了,哈哈。而且我不会署名啊,偷偷地闪!)
由此看来,新编写的代码是否遵从了代码规范会给以后的维护和修改工作带来很大的影响。
可维护性体现在什么地方呢?我觉得就在于对现有的程序进行修改的时候,能否快速定位到问题所在,而且在读代码的时候,很容易就能理解代码所要完成的工作,那样才能够更快速有效地对代码进行修改。
在此必然会涉及到注释的问题,关于这个东西的讨论已经有很多了,我的观点是,如果能够用较好的命名和清晰的代码说明的问题,就可以不写注释了。相反,如果仅仅看代码无法理解的东西,尤其是业务上的知识,或者是业务流程上的一些特殊的要求,就非常有必要写上注释,否则以后维护的人就不明白其所以然了。而对于对日项目中规定代码中的注释率要有多少,就有些过分了。
对于自身来说,想要编写规范和可维护的代码,其实也不是很难的事儿,主要在于态度。记得当初我给公司的新人培训的时候,曾经说过:“我们应该编写什么样的代码呢?我觉得有一个原则,那就是别人在以后修改、维护你写的代码的时候,不会一边改,一边骂人,他***,这是谁写的鬼代码,让我有打人的冲动。”呵呵,玩笑而已,但还是能说明一些问题的吧。
可测试性和健壮性
首先向说说可测试性,而这其中先要交代的就是测试的方法。
大家都知道,在一个系统的开发过程中,有很多测试环节,而这些测试环节与设计与开发环节又都是相互对应的,大概是这样:
- 单体测试----->详细设计
- 结合测试----->概要设计
- 业务测试----->需求分析
但是,在不同的开发环境中,所采用的测试方法也都是不一样的。
通常我们都会使用人工的测试方法,尤其是对于界面上的一些元素针对特定操作的反应,只有真正能够得出想要的结果,那样才能够算是做好了这个功能。
但即使是人工的测试,详细的程度也会有所不同:在对日项目中,因为会有人针对详细设计编写详细的单体测试设计书,然后会有测试人员按照这份文档,对界面上的每个功能都做详细的检查,看所提供的功能是否复合设计书上的要求。而对于整合测试,同样会有类似的文档以及更高一级的测试人员来完成相应的工作。并且,日本使用计算机的程度普遍比较高,到了用户那里,同样会测试出一些问题。经过详细的三个步骤,加上厚厚的文档,最终交付使用的产品一般质量会比较高。
而对于国内的项目,很多时间抽不出那么多人来做那么多的流程,所以一般测试都比较简略,而很多开发人员对于应该怎样测试自己的程序也不甚了解,只测试最理想的情况,很多的边界情况和特殊情况都考虑的不够。再加上整合测试做的不够,一般交给用户测试的时候,会有比较多的问题。
在这里,有人可能会说,我们做国内项目的并不是不想测试,也不是不会测试,而是时间紧、任务重,要么要质量、要么要进度,二者权衡取其重,所以我们就用质量来换速度。客户明天就要了,我今天才做好,哪有时间测试啊。
但是,我还是觉得那样做,其实是在饮鸩止渴,项目之所会砸掉,很多都是因为用质量换进度造成的。
上面有些扯远了,其实我在这里想要说的可测试性针对的是另一种测试方法,也就是利用xUnit工具进行的自动化单元测试。
关于此最经典的东西可能就是那本《测试驱动开发》了,每次拜读的时候都会受益匪浅。
然而,在很多情况下,想要达到那样的目标都比较困难,因为那需要编写很多测试代码,而那些代码对于程序本身,或者更清楚一些,针对用户是毫无意义的,只是用来保证我们确实实现了所需要的功能。而且,开发人员编写程序代码的时间都比较紧张,怎么会有时间再去编写一份测试代码呢?
我也是一样,似乎到现在真正编写过的自动化测试代码也不超过20个,只有在觉得时间比较充裕,而且感觉到测试的步骤很多,必须需要自动化测试来帮忙的时候才会那样做。
其实,测试代码与其说是给自己编写的,不如说是给别人编写的,给整个项目组编写的。
我们经常会遇到这种情况,修改一个程序,改好了之后,自己测试了没问题,但是发布了之后,发现其实有一种情况没有考虑到,或者说原有的程序中有一些特殊的处理我们不知道,结果就会导致在某些情况下程序崩溃。
如果有自动化测试代码,特别是自动化的整合测试代码,就可以在某种程度上改善这种情况,每次在修改之后,只修改相关的测试代码,而不变原有的,那样执行一下,闪闪发光的小绿条和小红条就会告诉我们的修改是否合理了。
因此说,如果说自己的代码在可测试性方面完成的话,那就必须有相对应、比较完善的自动化单体测试代码,并且测试的代码覆盖率达到了一定的程序,比方说80%以上。
接下来,我想说的是什么样的程序是健壮的,看我的程序的抗击打性如何,哈哈。
曾经在做对日项目的时候,有这么一种说法(大家一定觉得奇怪我怎么总是提对日开发,希望大家不要对一件事物全盘肯定和否定,其实对日、对国内、对欧美三种开发模式都各有各的优点,也各有各的缺点),那就是叫做“猴子测试”!
猴子怎么会测试呢?当然不会,他们所说的是,像猴子一样测试,因为猴子什么都不知道,它只会在键盘上一顿乱敲,在鼠标上一顿乱按,是吧,哈哈。
而猴子测试方法就是这样,拿过一个程序,打开界面,然后乱弄一顿,看看程序报错了没?如果程序崩溃了,那就意味着你的程序的健壮性不够啊,被猴子给搞定了。
解决这个问题可不是那么容易的,首先必须要对所有的输入进行比较完善的验证,比方说字段的长度、是否是数字、数字的范围、是否子日期类型、日期的范围、成对日期之间的比较等等,一切都需要校验。此外还有对数据库方面的校验,比方说是否有重复的主键,是否有不可为空的输入,是否超出了数据库所定义的范围等等。另外还有对业务的校验,这个就需要针对具体的情况了。如果没有校验的话,就麻烦了,一方面这些非法的输入到程序中处理就会出错,或者存放到数据库中的时候会出问题。而缺少对业务上的校验,可能产生的数据非常不合理,闹了笑话。总之,我们首先要做的就是校验!校验!校验!
除了验证之外,就是对异常情况的处理,那不是错误,而是一种我们预料之中的一种特殊情况,可能不符合一些标准,而这里正是考验一个程序员的经验是否丰富的地方了。知己知彼、百战不殆,如果你能够知道程序在什么情况下可能会出错,并在相应的地方设置好“陷阱”给它,那么你就是战无不胜的大将军了。
而这两种操作,对于Winform形式和Web形式的应用,或者说针对C/S和B/S的程序,总体的思想是一样的,而细节上的处理又会有不少的不同,这些也只能具体情况具体分析了。
从总体上对系统的影响
当编写和修改一个程序的时候,由于一般来说它都是存在于一个更大的系统之中,而不是孤立的单个程序,所以,在编写或者修改完了之后,就必须检查它从总体上对系统的影响。
如果测试代码非常完善的话,这一点当然可以放在可测试性一起,那就是说,需要编写相应的整合测试的代码,并测试通过。然而,在很多情况下,如果没有实行测试驱动开发,并且编写相应的测试代码,这一项还是可以独立出来的。
不由地想到之前曾经修改过的一本程序。当时的需求是这样的:在做了对保单的保全操作之后(主要是新增附加险),在重新打印保单的时候,需要根据具体的情况显示最后缴费日。而此时又分为几种不同的情况,包括保单周年日新增长险、非保单周年日新增长险、保单周年日新增短险和非保单周年日新增短险。当时由于是第一次修改保全方面的程序,所以考虑就欠妥,只顾着达到自己的目的,而没有考虑到对整体的影响。当时我的修改方法是,找到数据库中存储最后缴费日的字段,然后在做保全操作的程序中,在执行相应的操作的时候将最后缴费日置为报表想要显示的日期。在做完之后,报表程序的确是没有问题了。但是在运行了几天之后,财务缴费的程序出现了问题,因为在缴费的时候是根据最后缴费日来判断何时需要产生应收数据的。仔细考虑一下,正是因为没有考虑到一次修改对系统整体上的影响,才导致出现了如此严重的bug。
因此,在进行新的程序编写或者修改原有程序的时候,一定需要做的就是考虑这个程序是否会对系统中的其他模块产生影响,如果有的话,就必须对相应的地方都进行测试,否则就可能产生不必要的麻烦。
另外,还有一个问题在那篇文章中没有提到,就是安全性的问题。如果一个程序没有考虑到必要的安全性问题,那么也不能算是完成了所有的代码。
举例来说,一个用户能够访问不属于自己权限之内的功能,或者程序中有安全漏洞,黑客能够很容易地对其进行SQL注入、OS命令注入、跨站点脚本攻击等等,那样就会给系统带来更多潜在的问题。虽然用户可能会感觉不到,但是信息的泄露和被篡改,对于公司来说风险是非常高的。
陆陆续续写了好多文字,想表达的就是,想要说自己的代码真的完成,需要做的工作真的是非常多。但是,也只有真正完成了的代码,才是高质量的代码,才会节省许多之后维护和修改的工作,也会防范很多潜在的问题。
最后,希望大家在写完一本程序之后,都会自问一声,我的代码真的完成了吗?
本文地址:http://www.nowamagic.net/librarys/veda/detail/707,欢迎访问原出处。