重构与代码质量
1.摘要
去年年末和前不久的时候负责拆分了两个比较重型的项目(代码加起来有接近10万行)。那真是一段惨痛的经历,以至于拆分代码一个月之内我看到任何形式的代码都会有一种生理上的不适感。 关于重构及代码质量的话题很久之前就想拿出来说一说,只是因为一直太忙(懒),直到今天才想起要填上这个坑。
2.烂代码
在讨论烂代码之前,我需要先发一下感慨:优雅的程序都是类似的,恶心的代码却各有各的恶心。这些烂代码就好像是喝醉了的人说胡话,你根本无法了解这个人当时在想什么 — 大概他们真的什么都没想。
虽然有些片面,但我还是决定这些形态各异的烂代码做个总结:
-
高耦合
假如在面试的时候问求职者:面向对象设计需要遵循什么原则呀?大概没有人会答错:高内聚低耦合。但工作中能做到这一点的人却少之又少,于是便出现了一些几万行代码的大工程;几十个方法的Service类;几百行的函数,等等。
后来便出现了这种事情:想要依赖某个工程里一个简单的方法,加上依赖之后发现打完包的工程里莫名其妙多了几十个jar包和满屏幕的依赖冲突;改一个简单逻辑却无从下手,只能一遍又一遍的研究那段成百上千行的函数…… -
拷代码
有时候我会觉得,拷代码简直是程序员的天性。尤其是在你跟一个程序员强调说模块间要低耦合之后,一些跟主流程无关的代码便会出现在各个类的角落里。
“看,现在这些代码只有我这个类会用,跟谁都不耦合了。” -
编码
magic number、疯狂的if嵌套、莫名其妙的变量名、梦呓一般的注释,编码大概是烂代码界最能玩出花样的地方。
另一个很有意思的事情,一提到代码质量,大家总是喜欢跟我纠结花括号到底是写在行后还是另起一行,或者缩进究竟是用tab还是2个空格,例如://什么狗屁玩意!
public PersonObject checkPerson(String uid)
{
……
}
//哟,还不错
public PersonObject checkPerson(String uid) {
……
} -
文档
Q:“xx啊,我要用xxx函数,要传什么参数啊?”
A:“我去看看代码。” -
更烂的代码
一旦某个工程里有了烂代码,那么就会像招苍蝇一样招来更烂的代码,假如一个团队里都是这种工程,那么这个团队代码质量下降的速度之快、幅度之大都会令人咋舌。
那么如何解决呢?好,重构。
3.重构
在讨论重构之前,也要先说一个有意思的事情。
还是之前拆分的那个工程,参与过这个工程的人有很多,大致可以分为两类:
一类人认为这个工程的代码“很复杂”,并继续痛苦的维护着这个工程;
另一类人认为这个工程的代码“很烂”,然后他们都离职了。
虽然各种关于重构的书里把重构吹的神乎其神,但是总有三个经典问题:
-
何时重构
有些人喜欢在代码里写注释:“项目工期紧张,blablabla,待重构 — 2011.4” ……嗯?你问我这段注释为什么3年过去了还在这里?
实际情况是,代码实在是撑不住了(依赖冲突解决不了、出现bug没人知道逻辑、新需求要改的地方太多,等等),才会想到重构。 -
谁来重构
有的人说:“谁写代码谁就负责重构”
实际情况是,后来写代码的人离职了,只好找了个当时不太忙的人来做这事。 -
如何重构
有的人说:“保证ut覆盖率blablabla”
但实际情况是,需要重构的项目基本上没有合格的ut(给烂代码写ut的难度远远高于写出优雅的代码),并且项目一旦要重构就得暂停开发,负责重构的人需要一边在粪池里挣扎一边被其他人追杀,基本上没有补ut的空闲。
所以我的观点是:常规重构只应该存在于某一次的开发过程中,把重构当做后续方案的人要么没做过重构,要么只是拿以后重构当做借口。如果说重写的工作量是1,那么重构的工作量在0.7以上。
但是总有一部分倒霉的人被从天而降的烂工程砸到头上,所以还是要写一下重构的几个心得:
-
重构期间一定要周知所有团队成员,暂停新功能的开发。
-
IDE可以完成绝大部分的重构工作,但是要慎用文本替换。
-
不要试图用拷代码的方式解决依赖问题。
-
不要试图一次性解决所有问题,因为重构实在是太!复!杂!了!,模块耦合和代码复制的问题必须一次性完全解决,剩下的可以等下一个倒霉蛋处理。
-
解耦的关键是找出两个模块逻辑上的耦合点在哪里,而不是代码上。
4.代码质量
既然重构是这么一件费时费力的工作,那么解决这个问题的方法自然是尽量减少重构,提高代码质量。
但现实情况总是,项目需求一天三改、产品要求下周上线、新人写不出高质量的代码,blablabla。某天发现项目代码质量凄惨程度令人发指。为了保证代码质量引入code review机制,结果提上来的code review质量仍然凄惨的令人发指,驳回几次,直到需求明天就得上了,最后只好妥协,通过了事。久而久之,便成了一个人写代码,另一个人擦屁股的局面。一旦擦不过来(这简直是一定的),烂代码还是会进来。
在这里我就不纠结于代码质量和工程进度的权衡了,我只从如何在保证代码质量的前提下尽量提高开发效率的角度提几点:
-
前段时间,有人看到我在纠结一个类应该如何设计,便问我:代码质量这玩意不算KPI,这个破工程说不定做完了都没人用,你在那里纠结个啥劲?我回答他,如果平时都在写烂代码,真正需要需要的时候还能快速的写出好代码吗?
-
烂代码和烂设计从来都是形影不离的好基友,从UML开始review会省掉很多写无用代码的时间。
-
好的框架是好代码的一半。
-
对新人来说代码质量远重要于开发效率,不要让新人做“下周二上线”之类的工作。
-
代码质量的关注点更多的应该是“找到更简单的发现/改善代码质量问题的方法”,单纯的强调“代码质量的意义”没有意义。
5.好代码
那么什么是优雅的代码?对我来说评判标准大概是:
-
有单元测试保证逻辑正确
-
某个功能相关的代码是否能很简单的抽取出来作为独立工程(实际上很有可能需要抽取出来)而不影响其它功能。
-
读代码的时候,把代码里的英文直译成中文(不加任何推测和假设)之后句子还通顺。
同时也有一个问题,模块解耦真的就那么美好吗?
前几天因为一个bug久违的又追踪了一次spring源代码,代码逻辑在各种继承关系和实现类之间疯狂的跳转,以至于不用debug单步追踪的话很难了解这个方法到底做了些什么,关于解耦力度的问题我也一直在纠结,就不展开说了。
本作品采用知识共享署名-非商业性使用 4.0 国际许可协议进行许可,转载请注明作者及原网址。
期待什么时候讲讲解耦的力度的问题啊。现在公司的人解耦解疯了。全部模块之间MQ通信。线上一个bug要把所有的模块代码都看一遍。
花括号换行的都是异端!要上火刑架的!RAmen
“项目工期紧张,blablabla,待重构 — 2011.4”
回复@蛋疼的axb:拜谢
回复@点油小良:linode,日本节点
不要让新人做“下周二上线”之类的工作[偷笑][偷笑][偷笑]
你在哪里弄的host请问,这么迅速。我大学弄得网店抽风成狗了,急需换host。
我对里面得“哟,还不错”表示满意
赞“更烂的代码”!