重构是一件春天播种,秋天收获的事情,要有耐心;正确的方法很重要,循序渐进可能比推翻重来更科学。
这个总结比较晚了,快相隔一年了,总想挤点什么出来写一下,一方面是避免让自己懒下来,另一方面也是迫使自己复盘,思考这个过程中哪些地方做得还Ok,哪些地方做的不好。
不少公司初期的项目为了快速和低成本开发产品,一开始可能会找外包或者开发能力一般的开发人员来完成,等公司业务上去了,这时候也欠了一屁股的技术债,很幸运,我刚好就当了一回接盘侠。
初接手项目,闻到坏代码的味道,不要急于作出改变,重构是一件需要小心翼翼进行的一件事情,你的每一点改动都会给QA的童鞋带来额外的工作量,尽管你觉得没有问题。所以,第一步要做的就是先把整体的情况先摸个底,先把问题暴露出来,制定好你初步的重构方案。在这个过程中,你可能要先默默的利用空闲时间做好方案,毕竟可能还有很多业务代码要写的,你不得不忍受先在原来的框架上把当前的工作完成。
在我审视整个项目的时候,我发现存在有如下的问题:
- 多个已经不在维护的第三方库,尤其是网络库,没有进行二次封装,耦合度非常高;
- sdk版本也近一年没有进行更新过;
- 一些库使用方法不恰当,可能会带来内存泄漏和组件状态不正确(比如所在的 Activity 已经销毁)导致的崩溃问题;
- 有不少重复性很高的代码散落在各处;
- 变量名和方法名有些随意,驼峰和下划线风格并存;
- 逻辑过于冗长的方法,比如和 H5 页面的协议处理,近 1K 行的
if else
; - 没有考虑一些边界条件,比如请求失败重试,没有数据的情况;
- 存在不少的魔数,往往在一些关键的逻辑里面,涉及到很多状态的变化处理。
- 没有懒加载用户还不需要的资源,页面 overdraw 的情况严重等;
- 还有一些情况暂时回想不起来,总之情况比较恶劣,骂人的冲动都有。
整理好问题和写好初步的重构方案之后,接下来就可以找你的老大去聊这个事情了,一般来说都会得到支持,这样也可以让上面知道你在埋头苦干的时候是在干嘛,当时的想法是想推翻重建的,做法就是一个新的项目工程和一个旧的并行开发,有新的开发任务就先在旧的工程上开发,然后新的工程就逐步赶上和替代,最后一次性把新的 app 交付给 QA 进行一次从头到尾的测试,当时评估这样应该会比在原来的基础上改耗费的时间要更少一些。但很快发现这样做行不通,一方面需求在不断变化,引起的变化两边工程都要改动;另外在开发进度上会和 iOS 端很难同步。所以很快不得改变了思路,整合新旧的代码,然后在同一条工程线上进行重构,这样一来,必然就多了很多整合的工作,重构变成了一个抽丝剥茧的过程,没那么痛快了,但好处就是每一步做的工作,都可以被看见。
既然是想改善代码,那肯定要先阻断烂代码再被添加进来,因此,第一件事要先建立起代码的相关规范,有可能的话,要尽可能加入 Code Review 这一流程来驱动规范的落实。重构的思路是从底层往高层,从变化少到变化频繁,比如底层的网络请求、图片缓存处理,这是变化少的部分,而页面和相关逻辑就是变化频繁的部分,从底层到高层好理解,从变化少到变化多则是对应经常变化的需求,或许在下个版本你就可以顺便把它重新做一遍,原来的代码彻底删除掉了。这里分享一部分具体的做法,可能对你有启发:
- 在改写网络层的时候,这次通过策略模式来分离了网络请求过程和数据解析过程,这样不管以后是用 okhttp 还是 volley ,是 Json 还是 Protocol Buffers 结构的数据,喜欢用 gson 还是 fastjson , 都只需要修改少量的代码,而且对上层调用没有任何影响。另外,由于新和旧的网络库不一样,为了减少 jar 包的数量,决定对旧的接口进行完全的兼容,但底层用的还是新的网络库。改写这一层后,对于新的代码就用新的接口,以前的就可以等待合适的时机再进行替换了。
- 这个 app 很多混合开发的地方,很多 H5 页面的点击需要调用起原生的方法,由于自定义的跳转协议数量非常多,原来的处理方法已经超过近 1K 行的代码,这样必然导致阅读和修改困难的问题,这里我采用了大家熟悉的状态模式把相关职责分散到不同的类里面了。
- 很多页面都有相似的过程,比如从从数据加载到加载失败处理,刷新和加载更多等,这些可以通过模板方法把相关的逻辑封装到基类里面,然后让子类去实现变化的部分,比如不同的视图和数据的绑定,可以大大减少代码量。
还有一些技巧已经回忆不起来了,整个重构过程的彻底完成花了差不多半年的时间,期间经历了好几个版本的迭代。从效果来看,重构后带来的好处是显著的:首先提升了今后的开发效率,拥有了更好的可维护性,其次 bug 的数量和崩溃率也有了大幅度的好转,最后得益于各种库的升级和优化,app 的性能也得到了不少的提升。总结一下,重构是一件春天播种,秋天收获的事情,要有耐心;正确的方法很重要,循序渐进可能比推翻重来更科学。