身为一个维护人员,我每天的工作就是研究产品的代码,修正各种bug,或者添加各种新功能。Martin Fowler在《重构》一书中使用了一个隐喻,“坏味道(bad smell)”。用这个隐喻来形容我目前的处境,那就是我正在粪坑里挣扎。这里充斥着“Copy/Past/Modify”而来的代码。为了实现一个功能而随心所欲添加的成员变量。长达一两千行的函数。几万行的类。到处都是public的成员变量。丰富多彩的编程风格。
这个程序运行起来很漂亮,用户也很满意(据说)。我想作为用户,是不需要关心代码如何如何的。作为老板也是不需要关心代码如何如何的(虽然他声称他很在乎)。那么正真关心代码的人是谁呢,我想来想去就是我自己,如果想改善生存环境只能靠自己。我先强调一下,这套代码不是我写的,我见到它时它已经是这个样子了,并且当我提出要进行整改时,所有人都是“能跑起来就不错啦,用户又没提,工时怎么算”。我只能作罢。我在这里不是发牢骚,牢骚我已经在项目经理那里发过了。我只是想讨论一下这个“粪坑”是如何形成的,有没有办法避免。
先描述一下我见过的比较普遍的新增功能的过程。我们拿到用户的修改需求后会做一个简单的评估,一般是比较有经验的程序员参加,然后给出一个简单的修改方案,就会由一个程序员(不一定是比较有经验的程序员)根据设计修改代码。等程序员完成修改并且测试以后就会提交到项目经理那里。项目经理还会找人,一般是他自己,再测试几遍。都通过了就会发送给用户。当然,用户还会测试。最后是上线。
Bug修改也是差不多的流程。可以看到整个过程都在控制之中,项目经理和用户完全可以通过充分的测试来控制质量。显然,在这个过程中代码的质量并不是问题的关键。代码糟糕,修改难度增加并不会导致用户给更多的钱和时间,相反他们会说:“这本来就是你们的责任”。对于管理层,他们并不会去修改代码,他们只需要驱使手下的程序员加班加点的“努力工作”。所以真正有切身厉害的是程序员自己,而造成这种局面的恰恰也是程序员自己。现在让我们回到上面描述的流程中,好好看看最简单的(项目经理们的原话)环节,代码修改。
这个程序员会先在代码里确定功能的起点也就是启动功能的地方,一般会从菜单入手。然后他会在代码里寻找相似的功能,看看有没有什么可以借鉴的。如果没找到,就只能从头开始编码,但是一旦找到了(大多数情况下是可以找到的,从这一点就可以看出这套代码的可怕),造粪运动就开始了。他会把那个函数整个拷过来,仔细研究,慢慢修改,边改边运行测试效果。这种方法收效甚伟,熟练工会以飞也似的速度进行修改。再加点班,一般都能在用户要求的期限内完成。皆大欢喜。
稍微复杂的功能就需要有点经验的程序员上了。但是操作过程与上面相仿,其实上面的操作就是这么代代相传的。只不过有经验者速度会更快,成功率会更高。但是我不得不说一句,这种方式让人作呕,毕竟这需要极大的耐心和细致,而且在成千上万行代码里上下移动滚轮很容易迷失,一天下来身心都是很疲倦的。有人搞软件搞到猝死也就不足为奇了。
这种代码基本上不可能自动测试,只能手动测试,测试起来非常繁琐,对于程序员来说是很繁重的工作。很容易让人烦躁,尤其是在加班赶进度的时候。这种代码会引起bug的爆炸,试想一处的bug被到处复制,而且还会引入新的Bug,后果之严重可想而之。这种代码破坏了几乎所有OO开发的原则,无法扩展,只能修改。通过他衍生出来的项目变得更加无法维护。
说了这么多,现在说今天的重点,我认为造成这种局面的原因如下:
1、 管理层不重视代码书写,认为是体力劳动;
2、 项目经理疲于应付进度,无心且无力;
3、 程序员水平参差不齐,缺乏正确的指导。
我不是老板,不能要求老板像我一样看问题,其实想想看老板雇佣我就是让我来给他照顾代码的,所以我不能要求老板来帮我。项目经理的职责应该是控制项目进度,协调各方关系,像代码这种小问题也不应该劳烦他。剩下的就是开发人员了,作为每天都在和代码打交道的人实在没有理由不关心代码,实在不应该给本来就一团乱麻的代码添乱。其实只要不满足于只是完成功能(当然对很多人来说这已经很了不起了),完成功能之后多想一下,尝试寻找违反“DRY”原则的地方,尝试把平时学习的OOP知识印证到代码里,代码也许就能有极大提高,自己的水平也会提高。这种对代码进行反复批判调整的过程就叫“重构”,前面我们提到的那本书就是对这些方法的总结和提升。当然,要想正真掌握它并不容易,需要不断学习,实践和总结。但是我以为,最重要的是把重构变成习惯。只有当你养成了重构的习惯你才算是掌握了这个工具。
养成重构的习惯先要从训练对代码的审美开始。经常看到人们提到“代码之美”,我很赞同这种观点,但是对美的欣赏是一种比较高的层次。并不是每个初学者都能做到的。所以不如先学什么是“代码之丑”。辨别代码的丑俊有一个很简单的办法,寻找重复。代码的重复,数据的重复,配置信息的重复,甚至测试发布步骤的重复反复,都是丑恶的,都要清除。其次是简单。简单不是直接,不是把功能代码写到菜单事件里就叫简单。一开始可以这样写,但是要时刻提醒自己这里的代码其他地方可能会用到,要想办法提炼成功能明确的函数(Extract Method)。从习惯养成这个角度上讲,我认为程序员应该有代码洁癖。
在代码品味提高之后,很自然的就会对“不美的代码”产生“整容”的冲动。但是不能乱来,重构是讲求方法学的。Kent Beck说过:“我不会对无法自动测试的代码进行重构”。但是现实并不理想,我们也不是大师,而且生活所迫,所以有时候我们只能对“无法自动测试的代码”进行重构。建议大家都去读一读Martin Fowler的《重构》这本书。他先从方法学的角度对重构进行阐述,然后总结出许多实用的重构技巧。有了思想上的武装,重构就会有的放矢了。
养成一种习惯并不容易,需要外界的压力,需要自身的毅力,最重要的是足够的渴望。只要你是一个想把程序员作为终身职业的人,都应该尝试养成重构这个习惯。