2017年我在新加坡做开发的时候,至少有两个月的时间我们都工作到凌晨12点,公司给我们租的公寓距离工作的地方只有300米,所以每天除了满足吃饭睡觉等基本需求剩下的基本就是工作了。
当时的项目是给一个大客户做一个医疗的项目,前期通过激烈的竞标从几个大公司手中抢到了这个项目,而付出的代价就是,承诺的太多。承诺的太多让自己获得了机会,也为后期的交付带来了很大的困难,每次交付都好像难产一样。
我们当时名义上用的是敏捷开发,可是执行起来就变成了“客户驱动开发”,因为前期承诺的太多,而合同截止日期也写的明明白白,所以和客户谈具体的交付计划时,只能拼命往每个sprint里塞开发任务,到了该交付的时候,大家拼命熬夜赶工,很多时候都是在最后时刻在完成开发上线,根本没时间做足够的测试,因为第二天上午客户就来现场做这一阶段的验收测试了。我还清晰地记得当时开发到凌晨4点我困得手拄着下巴睡着的情形。
而客户来测试的过程也是心惊肉跳,因为没有经过测试的app是经不住验证的,不知道在哪里就crash了,最佳实践中提到的“现场客户”,如果是和客户讨论需求还好,而如果客户来向你抱怨,你肯定不会非常好受。
当时每天想的最多的事情就是如何在快速开发的前提下交付一个可靠的软件,甚至直到现在我也在一直思考我们当时哪里可以做的更好。有些事情我们是无法改变的,比如合同中承诺的功能和交付日期,有些事情是我们可以改变的,比如如何将行业中的最佳实践引入团队,而这也是我要重点阐述的部分。
其实我们当时也已经引入了一些行业的最佳实践,比如使用敏捷开发,持续集成,可引入不代表就做的好,现在想想如果当时我们做的更好,一定会让工作和生活轻松许多。
DoD
DoD是Definition of Done的缩写,意思是验收标准,只有客户,产品经理和开发对于验收标准有一致的认识,才能保证交付的软件是大家期望的。
我们当时的产品需求是由一个表格来管理的,每次都由产品经理从密密麻麻的数百个产品需求中把写一个sprint要开发的需求摘出来,然后加上简单的描述和设计图就交给程序员开发了。
当时没有DoD,程序员承担了一部分产品经理的角色,缺失的部分是程序员来脑补的,因为当时人手不足,从美国团队过来旅游的一个同事被我们“扣留”在了新加坡,兼产品经理和项目经理的角色,又要和客户沟通,又要管理产品和项目进度,难度可想而知。
如果有DoD的话,程序员开发完成就可以照着验收标准测试一遍,相信crash的几率会大大降低。
Code Review
Code Review是指团队成员间互相审核提交的代码,这个过程既可以找到潜在的问题,又是一个互相学习的过程。
我在开发的过程中就遇到过好几次等我提交我的代码后,过了一段时间我发现这个类似的功能组件已经有人实现过了,如果我们经常做Code Review的话,估计就不会出现这样的问题了。
还有时候我接手别人的代码的时候,发现有些代码的逻辑和写法明显有问题,当时如果做Code Review也不会出现这样低级的错误了。
单元测试
单元测试对于当时的我们来说是一件重要而不紧急的事情,至于说多重要,大家也不清楚,因为没有一个量化的指标来说明单元测试到底会对项目有多大的帮助。甚至我认为我们对单元测试的认识有一个误区,认为它是功能之外的“额外”的工作。如果有这样的认识,那基本大家都会把精力和时间放到开发功能上,毕竟现有的需求还开发不完,那有时间做“额外”的事情呢。
结果这个重要不紧急的事情最终变成了重要并且紧急的事情,客户一测试app就crash,你说这事儿紧不紧急,重不重要?
如果我们当时做了单元测试这个重要而不紧急的事情,虽然可能导致项目延期,可至少可以保证产品的稳定性,后来也不会一天十几个小时为了重要而紧急的事情疲于奔命了。
正确的重构
仓促时间写出来的代码质量不会高到哪里去,后来因为bug太多或者需求要求,不得不对代码进行重构,有的重构涉及到几十上百个文件,几千行代码的修改,有时候我们会欣欣欢喜又搞定了一个恼人的大问题,有时候会因为重构又带来了新的问题而让人苦恼,导致下次犹豫要不要重构那坨烂代码。
重构还是需要做的,代码的重构是要一直进行的,只不过要先做好两个事情,一个是单元测试,另外一个是将任务拆的尽量小。
单元测试在这里的重要性再次体现了出来,在进行大规模重构的时候,是不可能保证不犯错的,如果有单元测试就可以保证有错误及时发现,这样就可以放心的进行重构了。
另外一点,如果将重构任务拆的足够小,就可以避免两个困境,一是重构一旦开始就无法停止,二是重构无法开始——因为重构的任务太大而让人望而却步,宁可有潜在的问题也不想犯大错误。
总结
软件工程是一个复杂的工程,对于这个世界来讲软件开发也是很新的一个东西,经过这么多年的积累已经有了很多的最佳实践,很多是反直觉的,如果依靠自己的聪明可能可以摸索出一些道道来,可最后你会发现你得出的结论往往行业中早就有了成形的解决方案和最佳实践。
我们要追求稳定的软件和产出,放弃那些虚伪的英雄主义吧,抬起头,看看代码以外的世界,最佳实践早已在前方等待久已。