虽然我已经很久很久没有做具体的技术工作了,但是我对技术人员一直非常尊敬,我认为这个世界上真正改变世界的,不是各个马,各个布斯,更不是那些把上亿资金玩弄于股掌的投资人,而是那些编写出一行一行代码的程序猿,攻城狮们。他们通过一行一行或优雅,或猥琐,或复杂,或简洁,或牛逼,或拙劣的代码改变了人们的生活方式。
我认为,对于奋战在改变世界一线的工程师们而言,有两个基本节操:面向对象的思想和单元测试的技能。没有这两个基本节操,敏捷的美好世界只能是空中楼阁,没有这两个基本节操,devops的各种自动化工具链,就像是一辆F1赛车开在沙滩上,任你油门到底,也只能开个五六十码,憋屈到死也无可奈何。
我的这个认知是怎么来的呢,请看这样一个图:
我们从敏捷-精益的各种方法和实践的最根本的目的:更早的交付商业价值,更灵活的响应变化 出发,来一步一步的推导为什么这两个节操如此重要。
第一步,既然要更早的交付商业价值,那我们当然得识别出商业价值是什么,这里可以通过影响地图,或者设计思维的一些方法来找到这个商业价值。
第二步,识别出商业价值了之后,如果把围绕这个商业价值所有的功能需求,非功能需求,运营需求全都按照以前瀑布式的方式来交付,那显然就不能满足“更早”这个要求,所以我们需要对需求进行拆分,解耦,这时候我们可以用到用户故事地图的方法来拆分需求和做产品发布规划。
按我自己的理解,微服务构架也是由于互联网世界的多变和复杂而产生的。举例来说,以前一个系统采用SOA构架,登录就是一个接口,阿狗阿猫来登录都用这个接口。但是到了互联网时代,一个登录功能能拆出PC登录,移动设备登录,第三方登录等各种各样的登录形式,还有各种的验证方式,比如短信验证,二维码验证,验证码验证,等等,这些都被解耦成单独的原子服务,这些服务相互之间又存在调用,依赖等各种灵活多变的关系,为了满足如此众多的服务管理和维护要求,微服务构架就应运而生。
第三步,完成了用户故事地图之后,我们有了一堆的用户故事,我们就需要有方法来指导我们按优先级高低顺序快速的交付这些用户故事,这时候SCRUM,或者KANBAN,或者SCRUM和KANBAN混用的方法帮助我们达到这个目的。
第四步,当我们做到了快速交付高优先级的用户故事之后,我们会发现,按照以前人肉测试的方法,过了几个Sprint,或者几次发布之后,我们的效率就完全不能支撑这样的工作节奏。因为从质量的要求来说,每一次发布我们都需要对以前发布的功能做至少一次回归测试,如果全靠人肉来做的话,每次Sprint/发布的测试工作量都是线性上升的,工作量总有一天会爆掉,所以此时我们需要引入自动化的工具和实践,包括了自动化编译,构建,测试,部署,以及配套的分支设计,流水线设计等等。
这里展开一下我对DevOps的理解,在我看来,DevOps并不是一个“用不用”的问题,而是一个“是不是”的问题。DevOps字面上就是Development & Operations 的合并,即开发和运营两种工作共存的形态。对于一个敏捷项目而言,只要第一次发布了,立刻就需要对已经发布的系统进行运营,同时还需要对后续的功能继续进行开发,这自然而然的就是DevOps了,所以一个以更早的交付商业价值为目标的敏捷项目,天生就是具备DevOps属性。我个人认为这些概念本身就是你中有我我中有你,没必要非得搞得泾渭分明。能帮企业赚钱就是王道,其他一切都是扯淡。
上面这四步,少了任何一步都不能真正的做到敏捷。如果一个组织能做到从识别软件系统的商业价值开始,到成功的引入了自动化工具链,已经算是在实践上非常成功的转型了,如果在mindset上还能转变团队的思考方式,领导的管理方式,那基本可以当做业界转型的典范了。做到这一步,生产效率也会得到显著的提升,少则百分之三四十,多则几倍都有可能。但是,这就是敏捷的最终状态了么?我并不这么认为,在我看来,到了这一步尽管成效显赫,但也就是把沙滩变成水泥路,让F1能从四五十码开到一百码上下。
接下来,我们需要看一下自动化测试这个环节。稍有自动化测试经验的同学们都知道分层测试的模型(见图中蓝色三角形)。UI层面测试的BUG FIX成本最高,资源的投资回报率最低,而代码层面单元测试的BUG FIX成本最低,资源的投资回报率最高。
那为什么单元测试的收益会那么高呢?道理很简单,单元测试是可以本地就完成的工作,不存在组件依赖,不存在时间约束,也不存在资源限制,而且单元测试的覆盖面会比接口测试和UI测试高的多,发现问题也会比接口测试和UI测试早的多。
所以在我看来,单元测试才是CI/DevOps的基石,有了高质量的单元测试,可以从代码层面提高质量,引发的连锁效应就是 减少BUG,提高BUG修复效率,还能减轻接口测试和UI测试的压力。有了高质量的单元测试,水泥路就成了柏油路了,F1可以轻轻松松的飚上300码。
上面反复在讲单元测试的收益,要能兑现单元测试的收益,有两个很重要的前提,一个是高质量的单元测试。 这个很容易理解,如果一个单元测试用例都测不出BUG,那显然就是没有达到单元测试的目的,那后续的收益自然无从谈起。要能写出高质量的单元测试,需要工程师经过长时间的实践和积累,一开始肯定是一头雾水四处碰壁,这其实并没有关系,新学一样技能,一开始失败再正常不过了。但是如果就此放弃,那关系就大了,将来只能去闻那些坚持学会单元测试的同学们的尾气了。
第二个前提是,让代码不要频繁的改动。假设我是一个开发工程师,我用JAVA写了一个方法,用了2小时,然后又用Junit框架写了一个针对这个方法的单元测试用例集,用了1小时。我写单元测试多花的这1小时的收益在哪里呢?
1. 我可以通过这些单元测试用例来验证这个方法是没有逻辑错误的;
2. 将来的回归测试中,如果测试用例通过,这个方法就是正常的,如果测试用例不通过,我也可以迅速的定位到问题;
以上两个收益肯定是远远不止1小时的,那么什么情况下,即使有高质量的单元测试,收益还是会降低呢?那就是生产的代码需要改动的时候。我花了2小时写的方法,如果因为需求的变化,或者新加的需求,又需要花1小时去修改,那这个时候,我还得花上点时间把单元测试的代码也给修改了。如果这种情况发生的很频繁,那结果就是改代码的工作始终需要配套投入改单元测试的时间。这样单元测试的收益就会大幅度下降。
那怎么样能让自己的代码不要频繁的改动呢?答案就是面向对象的思想。我刚刚从业那会儿,IT界可是言必称面向对象,这概念相比现在的敏捷,devops,微服务的火热程度是有过之而无不及。那么问题来了,为什么现在很少有人提及了呢?我的理解无非有2个原因,一是得益于此的个人或组织,经过这十几年的积累,面向对象思想已经融入了基因,不需要再多讲了;二是从来就没把这种思想当回事,过了这么十几年,自然也相忘于江湖了。
当年我花了不少时间去学习和掌握面向对象的思想,我的体会是,面向对象的设计思想的3种特性,5种原则,N种设计模式,只有一个目的:尽可能缩小对已发布代码修改的范围。
在我看来,一个优秀的设计,当发生新增需求或者需求变更的时候,最好只需要修改一下配置或者数据就可以实现;如果做不到这点,次选是通过新增代码来实现,而不是去修改现有代码;如果还是做不到这点,再次就是只修改足够小范围的代码,比如只修改某个方法,或者某个类。如果必须要大范围的修改好几个方法,好几个接口,好几个类才能实现新增的需求或者需求变更,那基本上可以断定没有正确使用面向对象的思想在做设计。这种情况下,就算有高质量的单元测试,收益也非常有限。但是我并不相信一个可以写出高质量单元测试的工程师,会做出动辄需要大幅修改已发布代码的拙劣设计。
基于以上的认知和分析,我认为良好的面向对象设计可以最大程度的提升单元测试用例的稳定性,从而最大化单元测试的收益。掌握了面向对象思想,就好比在柏油路下再铺建了扎实的路基,F1以300码的速度不间断狂飚不是梦!
如果这篇文章写到这里就结束了,那我就是在耍流氓了。接下来要聊一聊怎么来培养开发团队的这两种节操。
我相信很多组织都有过强制执行单元测试的经历。比如给开发人员强下规定和指标,必须要写单元测试,覆盖率必须达到多少,等等。我们组织也干过这种事情,但是这么干并没有什么收效。我们好好的反思了一下为什么开发同学们都没有主动意愿去写单元测试,我们的发现是这样的:
1. 强制措施对于单元测试这种知识性工作是不起作用的;
2. 开发同学不愿意用单元测试,是因为他们没有尝到单元测试的甜头;
3. 开发同学没有尝到单元测试的甜头,是因为他们没有真正理解和掌握单测试;
4. 开发同学没有真正理解和掌握单元测试,是因为他们没有足够的时间去学习;
5. 开发同学没有足够的时间去学习,是因为平时工作量很大;
好了,说到底原来是管理团队并没有给开发同学足够的时间去学习这个技能,还我我也不愿意在这样的情况下去写单元测试。所以,我们现在要解决的问题是,如何让开发同学们真正的学会和掌握单元测试。
但是交付的压力还在那里,无论时间和资源,都还是那么有限。所以我们不仅要让开发同学们学会单元测试,还要想办法用最小的代价让让同学们学会。目前我们定下的方案是这样的:
1. 小起步 我们要求每个开发人员在最初的2周,只要完成1个方法的单元测试。
2. 互学习 每个人完成了第一个单元测试之后,我们组织团队花半天时间一起分享单元测试的设计思路和代码review,相互学习,取长补短。
3. 缓加速 在完成前几次的分享,当每个开发同学都初步掌握了单元测试的方法之后,开发同学可以根据自己的掌握程度来缓慢加速,把单元测试更多的渗透到自己的代码里去。
4. 勤反馈 在缓慢加速的过程中,我们会持续的关注开发同学对单元测试的掌握程度,使用体会,获得的收益,并在这个过程中尽可能的收集数据以量化单元测试带来的真正的收益。
对于面向对象思想的学习和掌握,我们采用一模一样的策略,通过以上4个步骤让团队逐步真正的掌握这种设计思想。
我们计划用1年的时间让团队能在日常工作中贯彻面向对象的设计思想和单元测试的技能。这个思路已经被开发同学们认可和接受,但是刚刚开始执行,效果如何还不知道。
其实,严格来说,面向对象和单元测试,对于开发人员来说,更多的是能力,而不是节操。为什么我在这里要称之为节操呢?这是因为我认为这两种技能的背后是一种思考方式:
我们不能仅满足于实现需求,我们还需要在可见的未来让代码尽可能的稳定;
我们不能仅满足于让测试同学来确保质量,我们更需要内建自己的质量能力;
我们不能仅满足于当前掌握的知识,我们需要通过持续的学习来提升自己的能力;
这些,是我认为每一个开发人员都应该具备的基本节操。然而,要让开发同学们具备这样的节操,是管理者的责任,要么一开始就招来有这种节操的同学,要么在工作中把这种节操培养出来,没有第三条路。
本文由@合气大蒜 原创发布于管理圈,未经许可禁止转载。