练习台球
早年间,我还在一个创业团队里的时候,团队里有一个SAP的老顾问,打台球属于独孤求败的那种,自己没事时会在台球厅开一张桌子,用左手打右手,基本不是左手一杆收八星,就是右手全清。
正好团队里那时大家都对台球很有激情,后来我们每周的体育活动时,大家就去向这位老顾问讨教。老顾问也很爱教,给每个人指导动作,怎么站,怎么握杆,怎么推,讲的很详细。
有一次,我被他好好地指导了一番,感觉自己功力大涨,然后转身就像身边的小伙伴发起了挑战。不过很快我就感觉不对:平时其实和这位小伙伴水平不相上下,但这次很快就败下阵来,甚至还没有被指导之前手感好。
我很是不解,又向老顾问请教,老顾问一听就乐了,摆出一副智叟的样子跟我说:“甚矣,汝之不慧!练球不打球,打球不练球。又打又练,败矣!”
听到这种解释,我当时也是一脸懵逼。你说这打球不就是练习吗,为啥不能跟别人打球的时候顺便把技巧练了,这不是一箭双雕两全其美的事吗?
老顾问给我解释:“你打球的时候是在跟别人比赛,目的是为了赢球;你练球的时候是在训练自己的基本功。训练嘛,一个动作反复练习,为的是在这个动作不停重复的过程中,形成自己的肌肉记忆。这完全是两件关注点不同的事情,怎么能放在一起做呢?”
听到这种说法的时候,说实话,当时我是有点小震惊,因为之前自己常规的脑回路就是如何节省能量嘛,节省时间嘛,一举两得嘛,感觉能在做一件事的时候把自己的能力提升了,心里美美哒。但老顾问这番话,让我从另一个角度来考虑类似的事情。这种考虑的角度经常让我在用直觉思考的时候,停下来想想,问题到底出在哪。或者说,真的是天上有两只雕,我就要想尽办法,找一个好角度拉满弓,嗖地一箭把两只雕全部射中吗?
Solo遗留系统
后来,我有机会接手一个项目,是为一家国内比较大且有历史的IT制造企业维护一套遗留系统,不过有趣的是,这个项目需求范围不是很大,业务也相对没那么复杂,而且最重要的是,这个项目实际的开发人员只有我一个。恰逢当时刚来公司,之前又耳濡目染一些重构手法和设计模式,感觉自己可以大展身手了,想怎么写怎么写,把自己之前学的统统用个遍!
可是真正开始坐下来,拿到那个已经十几岁的代码库的时候,我是比较崩溃的,仅仅是数据库的类型就变化过三次,而且配置文件都在留着。整个技术栈的年代也是停留在它出生的那个时代也没有更新过。总之就是看哪哪不顺眼。
所以当时想的是,要不要给丫重构了?
可是总不能只凭一张嘴去和客户讲,“嗳,你们再加点钱,我给你们把技术栈换成新点的,因为我觉得那样很刺激!” 总是要给个有价值的理由先吧。并且后来发现,这个系统在客户那里并不是核心,甚至只能说是将就着继续用,他们也没什么加钱的意思。所以就只能说服自己将就着写。
写着写着又发现一个问题,这同一个系统里,对数据层的设计一共有两套,并且每一套都不是现在的最佳实践。当时每次操作数据库时,摆在我面前有三个选择:选择第一套,选择第二套,或者再写一套现在的最佳实践。可是转念又一想,我现在如果再加一种写法,等到下一个和我一样的人来维护这个系统时,也会遇到和我一样的困境。或者说,至少,我不应该再增加一种选择了。
其实一样的道理,遗留系统里不仅是数据层,一直到展现层或多或少都有这样的问题。而正确的重构手法是先加测试,在测试保护下再对其已经浮现的模式做手术。如果每一处都要这样正确地做事,很有可能在客户期望的时间内无法完成现有功能。所以当前,正确的事应该是,在完成客户既定的需求上,尽可能地与现有的风格保持一致。这样等有了资源,再去重构的时候也会更容易浮现模式。
关注点分离
不论在面对遗留系统,还是在和团队一起做项目的时候,每个人在读到团队其他人的代码/设计时可能都会面临这样的选择:是保持现有风格不变,还是自己使用更好的写法/框架/模式。在我看来,这两种做法都不可取,前者使得代码丧失了优越性的可能,后者破坏了一致性。优越性的丧失使得代码质量下降,以至于产生一些运行时的问题。没有了一致性,使得代码变的难以维护,降低了团队的开发效率。
那么正确的做法是什么呢?
当我们看到一个风格,被认为是目前的最佳实践,然后我们可能直觉地想到把它应用到目前正在做的项目中,背后的动机往往有两个:一者,这可是社区的推荐写法,肯定好;二者,在工作中应用了最新的最佳实践,可以获得很多经验。而类似前面练习台球的例子,这两种动机完全对应两个关注点:前者是对于代码质量的提升,后者是对于自身能力的提升。而这两个关注点在某些时候是矛盾的:增加了新的行为,使得本来在既有行为下完成任务的成本大大提升。说白了:当我们关注代码质量时,我们是在想顺利完成产品交付,是要赢球;当我们关注自身能力提升时,我们在意的是,使用了最佳实践之后,使用者自身有什么新的变化,而不必或者更少地在意练习后对于团队要交付的产品的影响。可是现实情况是,练习后产生的不一致性,往往提升了后续维护的成本。同时,如果这种实践只能带来充分而非必要的好处,团队也不能获得太大的收益。
成本与收益
那么剥离掉“练习”的动机之后,使用最佳实践变成了什么了呢?
我认为,使用/更新最佳实践可以归类为提升代码优越性,它本质上和修复缺陷是一类事情。修复缺陷时,我们的驱动力是软件运行的实际结果与我们的期望不相符,这种差异给了我们提升代码优越性的一个方向。同样在选择最佳实践时,也一定有一个必要的原因来驱动我们去做优越性的提升,这个必要原因一定是面向具体问题的,甚至说是可证伪的。基于这样的考虑,优越性提升应该对应一个明显的收益。只是在现有的资源下,为了这个优越性提升,会有对应的成本。
所以事情又变成了经济问题:我们会根据成本和收益的结构去选择,分别在什么时候,在哪部分进行优越性的提升。
另一方面,我们来考虑一致性:不论是抱着练习的心态,还是因为成本收益的考虑,局部优越性的提升的副作用就是不一致性的增加,也就导致后续维护成本的增加。直到最终全部旧模式都被改为了最佳实践,才恢复了之前的一致性,即随着项目中新的实践/模式的引入,维护成本是先提高后下降的。也就是说,作为开发团队中的一位协作者,我们要清楚的知道,局部优越性提升的利润其实是最小的。
结论
简而言之:当面对一个新的风格被引入时,我们先考虑必要性。如果没有必须的原因,在考虑一致性的情况下,应该保持现状。其次,如果是有必要原因,我们再考虑两点:一是考虑有没有成本更低的优越性;二是在一的结论下考虑,既然是必要原因,一定要保证旧模式应全部被新的更好的模式替换。最后,如果没有全局优化必须给出合理理由以及后续优化方案。