从17年末到18年初花了差不多三周的时间,将项目中最重要的模块之一--国际化资源管理,进行了彻底的重构。在掉了无数头发加了好多个晚上的班之后,终于改变了先前一个service解决所有逻辑的臃肿情况,代码的可读性,扩展性,模块功能的扩展性以及可用性等性能获得了很大的提升。我在这次重构中有着许许多多的思考和尝试, 对于一个工作经验仅有一年的人来说是一个不小的挑战。最终项目完成并上线之后,自己对于工作结果还挺满意的,从中也收获了很多很多,不写点总结就有点对不起自己过去三周的辛劳了。
先说说背景。在国际化管理的模块,资源被分为多个类型,每个类型有特定的管理方式。每一类型下的单个对象(即页面上展示出的一条记录),底下会包括一个或多个文件名,每一个文件名其实对应着多个文件,即单一文件名下有多个语言文件,这些文件可以理解为文件名在某种语言下的资源,例如文件A.json,底下有中文的A.json,英文的A.json等等。所有的A.json语言不同,但是共享同一套key,即相同的Key对应不同的语言的翻译,组成了不同的语言文件。一个对象的code-文件名-语言,定位出一个唯一的文件。资源管理,以单个文件为维度。
在重构之前,有过一次业务扩展,在json文件的管理方式之外增加了excel管理。excel按资源类型划分文件,又按code划分sheet,再一个一个文件排列成行,列的维度对应各个语言。
原先的代码都是串行执行,一个一个的workbook创建,每个类型一个方法,流执行,最后将所有的workbook一个一个的写到文件里去,再压缩成zip,最后前端下载。数据量大时文件的生成速度偏慢,页面需要等待较长时间,用户体验很差。导入也一样,一个一个文件,一个个sheet,一行行地解析,解析后重新生成全部的json文件并上传到文件服务器,造成了极大的性能问题。除了性能问题之外,所有的代码都在一个service中,阅读,维护起来极为困难。几千行的service包含了导出以及导入时的excel解析等所有逻辑。一个小小的需求改动会引发大量的代码修改,而且难于测试,经常出现改了东边错了西边的情况。
要是这样也还罢了,因为在繁忙的日常版本迭代开发和维护工作中很难抽出时间和精力去重构这一块,勉强能用就先凑合着。当新需求来的时候我彻底傻眼了,新需求要求校对团队和翻译团队导出时,excel的格式加以区别,并且导入时的处理逻辑也不一样。同时将全局中相同的中文去重(因为翻译按字收费,这样能节约不少开支)。原先的代码对于两种导出所生成的excel格式一样所以代码写在了一起,导致新版本中这些代码几乎不能复用。不重构的话再有新需求来,只会加大修改的时间成本和人力成本,长此以往容易造成我的抑郁。这其中也是有一点巧合,最近正好在看设计模式和一本著名的书叫《重构》。以前上学的时候也看过设计模式,当时觉得极为抽象,不知所云,看完就忘。如今带着困惑再去翻阅,有一种茅塞顿开的敞亮。很多设计模式对于代码的抽象正是项目中所需要的。于是下定决心对模块进行完整的重构。
首先做的事是资源入库,每一个文件的每一个key为一条记录,全部转移到数据库表中进行管理,于是不同的业务场景只需要使用不同的sql查找出记录,再对找出的资源对象的集合进行整理,封装,业务导出形式的扩展变得简单。
再聊聊重构中用到的设计模式,或者说我以为我用到了的设计模式以及我的理解。
生成器模式:将复杂对象的构造与表现分离,构建过程可以创建不同的表示。
生成器模式在Mybatis中被大量使用,使得代码表现得极为简洁,多个对象的构造被一个导演对象封装后,调用方只需要一行代码即可完成,阅读起来结构极为清晰。
在项目中,每一个sheet的内容构造是相对一致,可以抽象的。翻译导出和校对导出分别实现了各自的构建器Builder,定义了一个param用于封装所获取的资源对象的集合以及sheet和excel样式等等信息,构建器新建,传入资源集合,资源的重整理,资源写入sheet,在service中只显示为一行代码,new Builder(param).init().createCells()。极大的减小了service的体积,将具体的实现转移到构建器中去
工厂模式:所有的类由同一个工厂来生成,将所有的类解耦,并且封装了实现,分离了对象的构造方和使用方,构造过程扩展不需要去修改使用方的代码。
传入导出的类型,excel构建器统一在工厂中生成,多种构建器Builder均继承自同一基类ExcelBuilder,在service中无需关心所获取的具体对象,使用父类抽象即可完成调用,若builder再扩展出第三中,也无需修改service中的代码,只需要修改工厂中的构建过程即可。
策略模式:定义一系列的算法(策略),这些策略所完成的工作相同,只是提供了不同的实现。
实际上不论是校对导出还是翻译导出,都可以抽象为对于数据集的不同操作形式。基于这样的一点启发,我才将文件资源入库,将资源管理转为对资源对象的管理,在不同的builder中提供不同的实现。