本文借着版权管理系统-付款的改造,总结和抽象了一些老系统改造的方法。希望能对遇到类似问题的同学有所帮助。
作者 | 肖迪(墨诩)
来源 | 阿里开发者公众号
优酷CRP系统-内容采购版权管理系统,是个存在10年的老系统,技术框架上比较老旧;再加上”人来人往“,必然存在很多”不合理但是能跑“和”不敢改,所以ifelse“等等经典代码,一共81w行java代码,17w的jsp代码。我在今年全面接手CRP-财务部分,整体目标就是全面推进CRP财务的业财一体进程。而这些遗留的技术问题都是推进进程的挑战,所以CRP财务本财年的技术主题就是“老系统重构”。
根据以往的工作经验,面对这样的系统,大开大合的重构改版,带来的往往是更加灾难性的”业务不可用“;所以我们的策略,还是要秉着业务优先的原则,跟随业务新需求来逐步重构。但无论产品还是研发心中都要有同一张大图,我们最终要做成什么样子。然后根据大图划清各个业务模块的边界,在保证不会影响其他模块的运行的前提下,进行重构。
81w行java代码中,其实大部分都是废代码,比如:功能和服务还在但是没有人使用;数据都已经迁移到其他系统,下游也不在实际使用,但依赖还在;有很多job还在运行,但并没有实际的数据产出使用方。之前重构的时候跟组里同学开玩笑说“如果随机注释掉一个service中是所有方法实现,系统大概率还是work的”,虽然我们不会这样做,但可能是真的。对于这样的系统,重构的策略如果是重新梳理所有服务的使用情况,无疑是成本特别高的,roi很低。所以应该按需重构和迁移,并保证下游依赖方的不需要做任何改动。
本篇文章会以其中一个模块“付款”来作为示例,原因有二:
一、本财年付款的改版业务述求比较高,这个S的重构进程较其他模块更快一些;
二、想表达的主题更专注在代码重构方向。付款作为整个优酷运营中比较末端的商业行为,在系统上对于付款依赖的下游系统和模块较少。如果是写“合同迁移和改造”,会更偏架构重构和老系统、数据的迁移方案。
付款模块一共涉及大概3w行左右的代码,首先保证下游依赖的接口都不变,还在原有工程服务,并且将老代码迁移到新的工程下。是否迁移工程取决于与迁移的ROI,我们的老工程的前端是用jsp实现的,现在要做前后端分离,所以老代码迁移到新的工程下。
付款
重构的第一原则是以业务为中心,不要为了重构而重构。先来了解一下付款的业务和业务的痛点。
付款主要解决俩个问题:1、0资损;2、流程效率
我通过MECE的从下而上的归纳整理后,审慎判断想法建议的“最小公倍数”的方法,对付款进行梳理,先了解一下付款在做一件什么事,以及如何完成目标?
将这些要解决的业务问题向上抽象总结,付款要想做到
到这里应该可以看出来,付款不是一个复杂业务流程的模块,它的核心述求是“稳定”与“可扩展”。从这个季度的需求也可以验证这点
代码臃肿,扩展性低
付款有个特点,没有很复杂的业务流程,但是涉及到资金,在付款之前需要做很多的金额计算和风险校验。而且另外一个特点,付款作为一个工具性质的模块,会接入很多业务方。不同的业务,在金额计算、风险校验等流程上基本一致,但实际接入实现的时候,会有或多或少的差别(比如,付款金额的依据上,主客和OTT会有不同类型的账单)。可以看出付款这部分对于复用性、扩展性要求是比较高的。现在要接入OTT的付款,我们先来看一下如果继续在老代码上升级,会有哪些问题。
@Override @Transactional(rollbackFor = Exception.class,transactionManager = "transactionManager2") public Payment submitPayment(PaymentDto paymentDto, User user) { **只保留能说明问题的关键代码或者注释,省去前整个方法600行左右** ***payment对象初始化代码*** ...省去60行代码... Integer r = paymentDao.insertPayment(paymentDto); ***payment付款依赖对象初始化代码*** //保存关联节目 playComponent.dealPaymentPlay(paymentDto.getId(), ListUtils.emptyIfNull(paymentDto.getPaymentPlayDtoList()), user); //保存文件 appendixComponent.dealFile(paymentDto.getId(), ListUtils.emptyIfNull(paymentDto.getFileDtoList()), user); //保存账单 paymentAssociatedBillComponent.dealBill(paymentDto.getId(), ListUtils.emptyIfNull(paymentDto.getBillDtoList()).stream().map(AssociatedBillDto::getBillId).collect(Collectors.toList())); //保存责任人和其他操作人 comPermissionComponent.saveComPermission(paymentDto, "ALL"); **第一步做金额和风险校验,为简单只保留注释,省去实现代码** //1.校验重复提交 ...省去5行代码... //2.提交前校验 ...省去20行代码... //3.校验账单金额&&所属公司 ...省去5行代码... //4.校验娱乐宝账号 ...省去1行代码... //5.校验付款条件 checkPayCondition(payment); //6.校验节目金额 if (paymentComponent.needPaymentToPlay(payment.getType())) { checkPaymentSubject(payment); } **校验过程中混入payment对象初始化代码** CrpContract contract = crpContractDao.getContractById(payment.getContractId()); Integer operationFlow = contract.getOperationFlow(); payment.setContractOperationFlow(operationFlow); //7.校验本次申请金额是否超过预期 ....省去40行代码... //8.仅版权采购合同支持预约付款 if (){ throw new RuntimeException("仅版权采购合同支持预约付款!"); } //8.校验预约付款不能选择先款后票 if (){ throw new RuntimeException("预约付款仅支持先收票后付款!"); } //9.版权采购&&收款账户国家为CN&&签约币种为RMB 才可以使用预约付款 ...省去10行代码... **payment对象初始化代码** payment.setApplyDate(new Date()); ...省去40行代码... **多了一次没有必要的数据库update** paymentDao.updatePayment(payment); **payment对象初始化代码** String actualApplyWorkNo = payment.getActualApplyWorkNo(); ...省去10行代码... paymentDao.updatePayment(payment); //异步提交审批流 BpmsDto bpmsDto = new BpmsDto(); ...省去10行代码... return payment; } private xxx(){}
比较典型的“流水账”代码,最直观会导致的问题就是维护困难,比如想查一个字段不正确的bug,最差情况要通读600+代码(还有部分private方法)。在升级的时候,最容易想到的办法就是继续盖楼(比如代码中调用了俩次 paymentDao.updatePayment(payment),应该就是盖楼的时候,代码复制多了),从而使“泥丸”越滚越大。
剩余60%,完整内容请点击下方链接查看:
上线十年,81万行Java代码的老系统如何重构
版权声明:本文内容由阿里云实名注册用户自发贡献,版权归原作者所有,阿里云开发者社区不拥有其著作权,亦不承担相应法律责任。具体规则请查看《阿里云开发者社区用户服务协议》和《阿里云开发者社区知识产权保护指引》。如果您发现本社区中有涉嫌抄袭的内容,填写侵权投诉表单进行举报,一经查实,本社区将立刻删除涉嫌侵权内容。