一个老项目,大家往上面堆代码,堆到现在有100+个模块,就是上次减肥的那个项目。
最近几个跑来写后端的同事问我,刚clone下来的项目里面一些模块是用来做什么的,为什么叫这些名字。我对项目的小模块不是很喜欢,但是为了让这几位同事对现在要接手的工作有点信心,想了个听起来很合理的理由跟他解释了一下,他们似懂非懂,说出一个“哦”字都能听出勉强,心里很难受。回过头,另一个老员工问我,这个项目里的某个模块为什么叫这个名字,有什么深意。当时的感觉:〃ωo啲吢ぬ痛ぐ
直到最近有同事升级了Gradle版本到4.0.1,也就是昨天,出现了build break,一个同事说是Gradle的Bug,瞅了一眼当时就喷了,之前写Gradle文件的同事到StackOverflow上找了个通用解决办法,为了代码看上去不是复制粘贴,手打了一份不等于抄,搞了一个配置用到现在。Gradle开发团队总是堆feature往前赶不做向前兼容,搞得Gradle plugin社区怨声载道,这次升级我也天天被同事粘着缠着,非要我讲Gradle的故事。面对这次危机,我决定站出来~~成为偶像~~做点升级的事情。
后来看项目100+模块真是昏古七了,想重构,于是今早跑去跟项目负责人撒娇软磨硬泡了一上午,可以动手做,但是要把过程记录下来,于是就写个重构日记吧。
2017-07-19
重构,但是不能停止业务开发流程,开发策略需要小心一点,从master切出refactory分支,向前改动提交在refactory分支上,快速又谨慎合回master。由于牵扯到模块合并,代码大部分我又看不懂,难度有点高,也比较危险,同事建议我女装直播重构过程,我搞了一小会发现不会弄,放弃了。
今天第一天,谨慎一些,先将一些基础依赖升了上去:
升级组件
- Spring Boot Dependency Management Plugin,这个是核心,基本用Gradle绕着这个转就没啥问题,先升级到1.0.3.RELEASE
- Spring Boot、Spring Framework、Spring Data几个组件随着升到Plugin管理的版本
- Tomcat、AspectJ、Jackson、Hateoas、Hikari等一堆外围组件也随着升到Plugin管理的版本
- OkHttp、MyBatis、Guava等一堆工具类也顺手升级到新版了
- EhCache停留在2.10.4版本,Spring CacheManager放弃支持3.X版本,原因是3.X版本的EhCache就是标准
编译,启动,没问题。开始砍组件
砍掉组件
- jgroups、ehcache replication,原本是要做EhCache集群的,后来没用,砍
- spock,不知道谁加进来的,没用,砍
- nosqlunit、olap4j、el、closure compiler,看不懂是干啥的,应该没用,砍
编译,启动,没问题。还能再砍吗?能
计划砍
- EhCache,很大,没啥用,拿GuavaCache或者Caffeine凑算了,快下班的时候同事过来问我GuavaCache能支持多个不同名称的缓存吗,我没吐槽
- SimpleCaptcha,网上很多这个的代码,在某个项目里看到了比较两个字符串用compareTo,抄的,当时看到之后现在已成为一段宝贵的回忆,时时刻刻提醒我以后不要成为这样的人,当然别人说过代码又不是不能用
- 还有好多暂时看不到的代码,到时候再砍
升级上去之后打包编译,肥了1MB,启动速度在Mac Mini孱弱的小机器上慢了很多
Ghost竟然不支持Markdown表格…
今天基本上是扫清障碍,没啥大进步,先写到这里,收摊回家
2017-07-21
这两天在看项目的底层模块,思考从这些项目中如何重构。先拜一下大神Sean
项目的底层有8个模块,分别是base-core、base-data、base-jms、base-jobs、base-mq、base-rest、base-spec、base-test。模块命名很清晰,但里面代码写得很乱,看了一眼赶紧关上了,先跑去整理项目框架的初始依赖Gradle脚本。
Gradle脚本整理
Gradle里没有Maven里面optional、provided的选项,见下图:
脚本里有providedCompile、providedRuntime这样的选项,没有引入war plugin自然无法在脚本中生效,勉强凑了个configuration来追加这个选项,编译没报错,但没生效,于是全删了,引入了更干净的plugin来使用Maven中的provided、optional标签,这样从打出来的包就砍掉了什么Servlet API啊这类玩意,话说这玩意打包到里面用到现在没人管还真是…
另外还有个subprojects的选项,在模块自管理的脚本之外,追入了很多额外的组件,砍了几个什么slf4j、logback、spock的东西,后来试着去砍spring webmvc,编译报错了,真是…你说一个定时任务在那一直跑的玩意用啥spring webmvc…
Base代码抽层
到看代码的时候了,目前只有base,biz层就引这些组件然后开始堆业务代码,依赖看上去是这样的:
base <- biz
后来看代码真是昏古七了,太多奇怪的代码,太多奇怪的轮子,太多奇怪的注解。不是说好是base(基♂)层的代码吗…这么多的业务代码…跑去问了类上注释署名的人为什么要放在这里,说不出原因,说很早之前写的,忘了。
理清这些代码花了我一整个下午的时间,里面有无数陷阱,比如很多看上去很有用的代码却没有调用方,引入依赖但是没有使用,或者本身就没用的但是已经写得遍地都是却没报错的代码。
想了个项目名,叫Mars,第四行星,硬着头皮开始拆。
但是也是有个计划的,构想是这样:
- base里面的各个模块合并,抽出来并入mars-base里面;mars-base统一管理所有的底层依赖,并加入启动Condition
- base里面的公用工具库和公用业务逻辑,抽出来并入mars-common里面,mars-common统一管理所有的公用逻辑
- base里面的Entity、Object还有一些Response(不理解为什么要放base里),抽出来并入mars-dto里面,往后还可以进一步拆小为mars-entity
- 为了不影响目前现有的项目运行,在项目依赖顺序上就要多多思考
这么做的话,项目依赖会变成:
mars-base <- mars-dto <- mars-common <- biz
当时预估是一下午就能全部拆完,很乐观,但是怕从master分支rebase回我当前开发分支时候出现了很多冲突,又怕出现build break,项目依赖先暂时变成这样:
mars-base <- base-core <- mars-common < base-* <- biz
这样base拆迁完毕之后直接砍掉base,其他项目就可以顺畅接上来了,嗯,开始迁移。
少年迁移中…
少年迁移中…
少年迁移中…
少年迁移中…
迁了一天,喷了!还只是迁移,没有重构!动一个文件就要改上百个文件,IDEA又没有即时编译的能力,只能手动遍出问题来再修,编了50多次…另外迁移了mars-dto是暂时拆不出来的了,太多了。
代码运行起来没啥问题,能正常启动,就是不知道运行情况如何,只能等下周一branch cut之后合并到项目中,周末还有两天的时间思考和喘息一下。
当然还要报上一些数值,这鬼玩意Ghost不支持Markdown的表格,要跑Google Doc上面搞个漂亮的图表才行。加了几个新模块,编译时间上去了,比较神的是MGT系统瘦了,启动速度还快了很多:
初次编译90s,再次编译63s,API STARTUP 83s,MGT STARTUP 75s,API SIZE 66M,MGT SIZE 78M
写完,收摊回家
2017-07-28
这一周,仍然是在迁移,砍掉了base-data和base-spec,base-core中的很多东西已经挪出去了,唯独剩下一个Aspect。点开代码的时候,发现就是个雷…这Aspect拦截所有继承BaseEntity的数据库Create和Update操作,当触发这两类操作的时候,拦截器会自动更新BaseEntity中的几个字段,如VERSION、UPDATE_BY_ID、UPDATE_FOR_ID、PROPERTY_BAG。跑去数据库看了一下,除了VERSION有值,为0,其他几个字段都没有值。
继续看代码,发现这些所有继承BaseEntity的Entity中,用了一些乐观锁的注解,代码中有个乐观锁的插件,插在MyBatis的插件集中,插件很老,很长时间没有更新,因为这个原因,MyBatis升级上去就报错。这个乐观锁并没有生效,问了同事,同事说刚上线第一天就没工作,于是就没用。
当时我问他两个问题:没有用为什么不把这些表字段拿下来?为什么这些代码不删掉?
静静趟在base-core里的这个Aspect,每天默默做着这些工作,十分可怕,要砍。我点开继承BaseEntity的类关系,当时觉得世界一下子就黑了…19个Entity,对应19张表,全都是底层基础架构范畴的东西,所有关联的Entity都要砍掉extends BaseEntity,所有关联的表全部都要drop column。日子要继续,代码要改。
刚开始试着改了一个,取消掉BaseEntity,报错报了一堆。单元测试Error,发现MyBatis的Mapper里的SqlProvider又额外继承了一个EntitySqlProvider,全部Error,改了很多之后通过编译,运行时发现Groovy又报了一堆错。
我理了一下这些代码访问数据库的流程:写个Entity类 -> 继承BaseEntity -> 建表,除开业务字段还要加9个Column -> Mapper文件名必须要以Mapper结尾,必须放在特定路径下的mappers文件夹中,多一级不行,少一级不行 -> 必须要使用SqlProvider -> SqlProvider必须继承EntitySqlProvider -> 必须使用onCreate、onUpdate来做Create和Update动作,这样Aspect才能拦截到 -> 用Groovy封一层Repository将这些Mapper的方法暴露出去。
这样写代码累不累啊…这还是Repository层…
看了后来的代码,大家都不用这套,我非常开心,要改的也就是这19个Entity,我到今天,已经改了7个了:)改掉这些Entity之后,我就可以把base-core拆了,其他的base模块拆起来应该也很简单。(立flag)
把一些图表放在了Google Docs上,比Ghost好看了很多,除开访问麻烦一些,基本都还好:)
希望下周能有更大的进展吧
2017-08-11
今天算是第一阶段的最后一天吧,稍微记录一下。
在做重构之前,Sean提醒说,我要留意几个问题:
- 为什么要做重构
- 为什么选择这个时候做重构
- 重构带来的好处是什么
所以在这个阶段快结束的时候,我做了一个有关这次迁移(不能叫重构)的分享,就是提出了上面的三个问题,并给我自己的见解,还在分享的过程中提出了一些问题和同事进行讨论。同事还能接受,觉得很多地方要改,只是没有人去改而已。
base里有两个base模块,一个是base-host,提供UI层接口基础配置,一个是base-job,提供定时任务基础配置,模块比较简单,这两个模块会被上层业务模块依赖,最后会被打包组合成可运行的程序。这样的方式是简单依赖,所有业务逻辑自己管理自己的对外UI层接口和定时任务,业务自己管自己的东西,UI层可能不需要一个集中管理的东西,但是定时任务需要有,停止和启用某个Job,要跑去调整业务模块里的配置项。
简单点来说,就是之前base-job <- biz-module
的依赖关系,现在变成biz-module <- mars-job
这样,依赖倒置过来,管理放在上层的mars-job里,调用会非常干净。说的时候同事有点听不太懂,打开代码和他们讲解了一下这样设计的好处,统一的Aspect,统一的配置调度,他们才说好,还行,就这么搞。
快结束的时候还提到了单元测试里面代码写法有问题,同事说那你再搞个分享讲讲吧,于是又准备了一下,做了个单元测试和Mockito使用的技术分享,直接上手写代码。这个分享讲了4小时,然后同事说再讲讲Spring Boot的一些细节吧…
上周砍掉了base下的所有模块,整合到了mars模块中。有了同事的帮忙,这周砍了EhCache之后,同事参与了其他代码的改动和移除,挪走了正式打包里不需要的flyway-core,还挪走了所有的Groovy代码,包括用来做delegate的Repository层、logback配置文件。之前使用Groovy来做logback的配置,里面有一些logger无法生效,就这么用了好几年,更换为xml配置之后正常了,同事跑过来说好多没用的日志不见了,问我有没有改了什么配置,我说我只换了配置的格式,没有改变配置本身。
砍Groovy这个要提一下,数据库层可以理解使用delegate,不太理解为什么使用Groovy来做logback的配置,分析了一下火焰图,初始化耗了太多时间:
做了这次技术分享之后,同事的参与度变高了,重构不是我一个人自己在做了,同事参与进来之后觉得工作量减轻了很多,比如本来说要砍Groovy Repository会要我一天时间,昨天同事跑来说已经全部砍完了,很开心。单元测试的分享搞完之后,他们也开始重新补充单元测试了。是好事。
最后,还是报一下,由于砍掉了一些模块,现在编译时间都稍微下降了一点,启动时间没变(觉得启动时间跟数据库连接快慢有关),体积都下降了很多,API从64MB直接下降到48MB,MGT从75MB直接下降到60MB。接下来的工作会进入业务重构和合并的阶段,我会开始着手改用户体系、认证和权限方面的功能代码。
不过,运维方面的很多工作已经落下来了,我得找时间回去更新一下运维架构。
不过,今天是第一阶段的最后一天,由于另一个项目现在没有后台开发和运维,所以我会暂时过去做那边的事情,重构工作的步伐会变慢很多,那边Project Owner已经和各种领导打好招呼要我花大量时间在他的项目上。
今天就到这,收摊回家。