01


开始想聊这个话题的时候,我是打算放弃的;因为这个话题涉及范围之广,内容之多,让我犯怵;

近几年,待过两家公司;一家经历过重构,另一家也打算重构......

其实要下定决心,推翻重来,是一个很有勇气的决定;

归根结底,不到万不得已,谁想这么玩,谁愿意花费大精力去做这些脏活、累活;

所以究其原因,也只能说是一种综合因素吧,就像古话说的,天时、地利、人和;

至于为什么这是个很有勇气的决定,因为做重构这事的团队风险极高;上至业务高层,下至底层码农,都有可能一个不小心就被刀;

我曾经待过的一个团队,经历过一次重构,不过那是一次失败的经历;

开发预估了两个多月的时间,业务大佬决定,期间直接对新需求停滞;但是,最终上到生产的系统,老问题还在,然后又多了很多的新问题;

再后来啊,业务大佬和技术主管都被刀了;然后系统回退成老版本,重新承接新需求,并且开发时间被严重压缩,导致技术部门没日没夜的加班;

下面,我大致做了归档和整理,从这八个方面重新聊一聊系统重构;
聊一聊系统重构-LMLPHP


02



【重构原因】
其实上面也提到过,这是一个综合因素,可能是各种各样的因素叠加,造成一个不得不重构的结果;

下面,聊一聊我所碰到过的因素;要是没聊到的地方,也欢迎大家补充;

第一个,可以归结为系统的稳定性或可用性:不管吹得怎么天花乱坠,你的系统总要可用吧,所以说可用和稳定是一个系统的最基本要求。开发一个需求还要全服务停用吗?业务量增长,系统还能抗下所有吗?那么问题来了,怎么保证可用和稳定?

首先,如果你是单服务系统,那么为了保障可用和稳定,需要对服务进行扩展,加入更多的机器来分担系统的性能压力,也就是常说的“服务集群”;

但是,往往我们的系统并不是单服务的,也就是我们常说的分布式微服务;这种情况下,就不得不扯到分布式的三大法宝:限流、熔断、缓存。相信要实现这三个法宝,有很多成熟的框架和工具等,这里就不过多阐述了;

第二个,开发规范问题:这也考验了团队leader对底下成员的约束力,以及是否存在代码review的环节;

假设一个团队,leader确实没有对成员强制约束开发规范,更别提代码的review了;最终导致的问题就是,各写各的,每个人都有自己的一套开发习惯和开发规范;最终就是一个大写的“乱”;

这样日积月累,一代目埋坑,一代目撤,二代目上,二代目撤...... 系统不知道经了多少开发人员的手,最终,新入职的冤种某天需要排查一个产线问题,仿佛误入了代码的迷宫,深陷其中,无法自拔;

第三个,推翻原先核心业务,重立新规:这个要解释起来比较麻烦,也碰到会比较少,然而,我恰恰就遇到了;

某天,业务大佬宣布,目前做的这套业务规则要被废弃了,他们重新拟定了一套全新的规则,意味着先前不知道几手的代码已经失去意义了,几乎90%都需要重新写;

都到这一步了,那就大刀阔斧的改革吧;出事了,emmm,业务的锅;


03



【重构会议】
既然已经确定要进行项目的重构,那么拉成员例会是必不可少的,不然就会一片迷茫,不知道干点啥;
聊一聊系统重构-LMLPHP

第一次会议,由技术老大牵头,叫上业务负责人,技术主管,开发全员,测试,产品,运维;

一般,第一次这种全体会议是没有结果,但是又非常必要的;因为上级领导需要知晓整个重构涉及到的点,影响的方面,开发的时间,以及整体的技术方案,参与的人员,还有需要的资源等等;

第一次会议消化完后,很快就是第二次会议;这个会议也是由技术老大牵头,参会的主要是技术主管和参与开发的人员和运维;主要是对系统重构进行总体的技术选型,讨论用到的框架,组件,实现的方案,部署方案等等;

这个会议持续的时间会比较久,大家搬好小板凳,准备打持久战,可能从上班开始,到下班点了都还不能结束;当然,还是会有中场休息时间的;基本可以各抒己见,提出自己的意见和看法;主要是围绕做什么?怎么做?做多久?这三个疑问点讨论;

接下来,就是由技术主管组织的技术部内部会议了;由于之前已经确定好了统一的技术栈和实现方案,这一次讨论都是在此基础上展开的;

当然这一次会议可能需要更久的时间,因为涉及到是微服务的话,就避免不了一个问题:“服务拆分”;

大家都知道,微服务是需要根据业务去进行拆分的;那么问题来了,业务的划分界限是什么?为什么A业务部分要包含A1,A2,A3;其实这里就需要大家达成一个统一的认知,也是这次会议要达成的最终目的;

这次会议最后,服务已经划分完毕,但是缺少去维护这些服务的人;那就需要对开发人员进行分组,每组有对应的负责人,看人员情况而定(如果就两三个,甚至更少的开发,那就没必要分组什么的了,就是冤种,活都得你干),每组对N个服务的开发和维护;

分组过后,组长会进行简短的组内会议,这次其实就是派活会议,认领各自的工作;也会明确一点,大家要干些什么?谁干哪些?每个人干完都需要多久的时间?

最后,会整理一份排期计划,上面每个人的名字赫然在列,当然还有任务列表,工时,等等;
聊一聊系统重构-LMLPHP

一切准备工作就绪,最后就是开干了,当然后面开发过程中还有一些不明确的点,也会临时组织一些会议,让大家达到统一的认知;


04



【并行需求】
在系统重构的开发过程中,因为占用的周期会很长,投入的资源会比较多,所以难免还会遇到这样一个问题;

一天产品经理急冲冲的跑过来说,现在有一些需求很着急,需要将重构停一停,先把这些需求排期上线;至于为什么会这么着急,咱也不知道,咱也不敢问呀;

这时候就会面临一个恶心的局面,这次开发的需求,肯定需要在原先的服务中进行开发,那么重构的服务中,是不是还需要再写一遍?

王德发!说归说,骂归骂,办法总比困难多!这时候有一种方式,那就是派出个谈判专家,以三寸不烂之舌,动之以情,晓之以理;多说说技术难点、痛点,占用的资源,实现的难度等等,去打动他,说服他,让这次需求排在重构后面;

如果你成功了,那么恭喜你,这次重构任务,大家帮你分担一半

什么?产品经理不同意;那就没有办法,只能在改动老服务的基础上,记录调整的点,最终在写新服务业务代码的时候,将改动的点一并加上去;

最后在提测的时候,通知测试同学,这些需求相关的地方,在重构的新服务上线之前都需要重新走一遍流程;

当然,还有一种情况,就是像我之前说的,需求即伴随着重构;这种需求也肯定是大版本,开发周期较长;这种情况是最理想化的,只需要按部就班的进行开发即可;


05



【技术选型】
这个话题,在上面的会议模块已经提到过了,这里再来详细的聊一聊;

当然,这次也仅限于比较流行的分布式微服务的技术选型;单服务的话,额,暂且不说吧;

其实也没什么好争议的,对于Java系统而言,无非就是spring全家桶无脑往上堆,服务器资源拉满;就像买汽车一样,越豪华的车,堆的配置越多,价格越贵,但是舒适性和操控性也就越好;

但是还需要考虑一些因素:成本、产品开源与否、技术社区活跃度、产品的成熟度、结合实际情况等等;

成本:时间成本、学习成本、金钱成本;

事情是人做的,代码也是人敲的;需要考虑团队成员擅长哪些技术栈,如果一门技术只有少部分人会或者都不会,那就需要结合开发时间,去斟酌需不需要用它,即便是热门的技术;

当然啦,如果老板愿意出钱,直接买各种付费的技术就行,我们只需要搭好基础的框架,然后往里面填充业务代码即可;如果不行,那就老老实实看看免费版,出了啥问题,只能自己排查去解决;

产品开源与否:说到这个,开源是关键,最香的词“开源免费”;

就像上面说的,一旦遇到了什么问题,其实有必要去看这门技术的源码去解决的;比如典型的SpringBoot自定义starter,需要定义“META-INF/spring.factories”文件,将文件中配置的类型信息加载到 Spring 容器;

一旦你闭源,那怎么玩?我怎么知道你内部怎么去实现的?只能看官方文档,看你自己吹什么什么功能;

技术社区活跃度:这个因素其实和产品开源可以归为一类,都讲的是一旦这个框架或产品出了问题,我们就可以去它的官方社区上找类似的解决方案;如果一个产品连社区都没,或者活跃度很低,那也就意味着你只能靠自己去解决了,那花的时间成本可就......

产品的成熟度:我们选用一款合适的产品,首先它得符合我们主要的需求,而后它才有其他额外的功能,这当然是最好的;因为一旦咱有一些特殊的业务场景,恰巧这些额外功能正好符合你这个场景,岂不美哉;这样就不必要接入额外的产品或组件了;

比如Redis,大家都不陌生;相信这个工具大家最多的是用它做缓存,它确实也是一款优秀的缓存工具;

但是,我之前在做OA软件时,碰到这么一个需求;用户申请一个会议,并且指定了参会人员,当在会议开始前15分钟,前5分钟,分别对参会人员发送一个提醒;

典型的发布、订阅的模式,然后正好Redis就有现成的功能,当时就是用了Redis的这个功能;

结合实际情况:说到这个,有句老话说:“只有适合自己的才是最好”;在考虑需要用到哪项技术时,最理想的情况那就是直接付费购买,出啥问题,都是有技术支持滴,我出钱了,你得为我解决,但是,成本有限啊;

当然,有些人会说,技术选型这玩意,那是技术主管或者技术总监他们决定的,我们小码农只管拧好螺丝就行;是的,往往很多时候就是这样;但是,你不去大胆说出你的想法和意见,你怎么知道他们不会听取呢?格局打开,往上一层考虑就对了;


06



【服务划分】
这块内容其实可聊的没多少,但是,也是最难的一块;

首先,划分;怎么分?按什么标准去分?分多少服务合适?分好之后怎么去管理?安排那些人去管理?等等一堆问题;

我也只能浅谈下我之前做过的一次划分经历,因为每个公司遇到业务和场景是不同的,最终得到的结果也是不同的;而且,服务并不是一次划分就能结束,它是一个持续不断的过程;

当时我们是全部参与这次重构的开发一起拉会议的,在此之前,已经确定好了技术栈,就是热门的springcloud全家桶;技术主管先是浅谈一波他自己的想法,然后让大家在他划分服务的基础上,提出自己的看法,并且每个人都要说,因为不可能想法都是一致的;

我记得比较清楚的是,他划分中,有个序列号的服务,就是这个服务专门去生成序列号;后面几个人都点名这个服务也包括我;其实当时想的是,我们的系统业务里没那么大,能省一个服务,也是省一点资源;最终大家统一共识,将这种场景写一个工具类去处理;

最终讨论后,达到的共识,也只是一个初步的共识;随着业务需求的增长,一部分业务服务必将变得臃肿,那时候第二波拆分也就随之到来了;


07



【开发规范】
上面提到,造成系统重构的因素有很多,其中一个因素就是代码混乱,大家各有各的习惯和规范;

其实不难发现问题了,并不是每个人不知道开发规范,而是大家没有统一的一套规范,大家只是没有达成共识;

那就来解决这个问题,制定一套统一的标准,让大家都没有异议的一套规范;

这时候就不得不提到阿里的规范,现在广为流传的《阿里巴巴Java开发手册》,甚至基于这套规范,在IDEA上还有专门的检查插件可以安装;

当然,这样做确实省事,省去了leader对代码的review时间,包括我之前待过的一家公司,也确实就是这么做的;

其实仔细用过几次这款插件就会发现,有些检测其实大可不必,但是你又不得不去遵守它的规则,这时候反而徒增工作量;

遇到这种情况就需要看上级了,可以把这个疑问点反馈给他,或者反馈给身边其他的小伙伴,起码让他们GET到你要表达的点,引起大家的共鸣;相信越来越多的人反馈这些问题,上级不会坐视不管的;那么恭喜你,又去掉一块大家都认为不合适的规范,让你开发不绕弯路;

总之,总结起来就是一句话:没有标准,大家统一认可的就是标准犯规;


08



【数据、接口迁移】
谈到这个,除非开发的是0-1的项目,否则在平时开发中,或多或少都是会遇到的,而非重构会单独遇到的问题;

而我们在入职一家新公司,或者长期在一家公司干活,遇到0-1的项目几乎没有;如果有,那么你的运气很好;

就拿我上面的经历来说,业务推翻了之前的规则,重新制定了新的业务规则,那就不得不引出一个问题,之前的老数据怎么办?

这个时候就需要跟业务确认,这批老数据是否可以按照某种默认的规则,做统一的处理;或者说,直接废弃不管;一般来说,按前者处理的方案居多;

考虑到重构,往往你的表也是重新设计过的,也有可能是老表和新表公用,一般来说后者的情况居多;

其实,说白了就是在老表里面去修改数据,在新表里面去新增数据,而我们要考虑的是这批数据怎么去调整比较合适;

按我们之前的处理方式,如果涉及到的表,字段等较少,存储结构也颇为简单,是可以直接用SQL脚本去解决的;另外,还需要考虑如果表数据较多,SQL执行快慢的问题;

还有一种情况,要是涉及到的场景很多,处理的表也居多,可以考虑写程序去解决;比如,写个任务,去手动执行这个任务,处理掉这批数据;当然,也得考虑数据量的问题,如果数据量大,用多线程跑批,或者多服务跑批等方式;

下面,来谈谈重构中的接口迁移;

要保证一个原则,就是对接口的出入参保持不动;不然,前端直接飞过来跟你聊人生;

但是,老服务的接口很多怎么办?还能怎么办,分工处理;

上面提到过,我们是分组的方式,每个组负责一部分服务,分工比较明确,所以相对来说,也比较快;

大家整理文档,老接口的URL是什么,新接口的URL是什么,出参,入参能不变就不变;如果真的需要调整的,需要给出合理的解释,大家一致通过后,然后自行跟前端去battle;

有些老服务还会运行一段时间,所以并不是所有接口都会涉及到迁移,这种接口,那就相安无事;

在迁移的过程中,肯定也会遇到各种问题;比如举个简单的例子,之前老服务的分页插件使用的是PageHelper;而在新服务中,由于用上了Mybatis Plus框架,那自然也就用它框架自带的分页插件了;这个时候就涉及到修改,你只能自己内部消化了;

最后,当你觉得一切接口都迁移完毕的时候,别忘记对自己迁移的接口,进行一次自测;不要怕麻烦,这个自测对于后面的联调和提测流程,都可以免去很多不必要的问题,也可以大大提高团队的效率;


09



【实现方案】
最后还有一点,业务场景的实现方案;这个听着有点抽象,其实在上面说的会议中,也会涉及;

不要以为这只是技术的决定,其实产品的参与也是很有必要的;你品,你细品;

我拿一个经典场景举例,A账户往B账户打钱,然后系统是微服务,很熟悉吧;

没错,就是分布式事务的一个经典场景,要保证数据的一致性,A被扣钱的同时,B要加上相应的钱;

分布式事务我不多说,相信很多同学随便一查,各种五花八门的方案;

但是这里我只讲一点,达到数据一致的时间节点;你是要什么时候保证这个数据达到一致,也就是说,强一致性还是最终一致性;

要求强一致必然是牺牲掉一部分性能的,要求最终一致性就有可能在一段时间内产生数据不一致的问题;

这个时候,就需要拉上产品经理,跟他阐述其中的缘由,说明技术方案上无法规避的点,让他做取舍;

有的时候,好的设计往往能规避技术上的难点,也可以节省开发的成本和时间;

所以,在讨论这块内容的时候,必须要有产品经理在场,提出设计上不合理之处;我们不能只局限于研究技术问题,其实换个角度,或者你质疑的点,就不存在了;

记得实习的时候,公司的老板讲过一句话“人人都是产品经理”,现在回过头一想,还真是那么回事;

大胆的提出自己的意见,做一个有自己主见的小码农;


10



对于系统重构,我就以一句话结尾吧,这也是引用了一位大佬的话,“对公司现有的资源和战略妥协,在技术栈和方案之间取舍”。
编程文档:
https://gitee.com/cicadasmile/butte-java-note

应用仓库:
https://gitee.com/cicadasmile/butte-flyer-parent
03-20 08:52