M*N个策略造成类爆炸怎样重构?

Posted 编程一生

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了M*N个策略造成类爆炸怎样重构?相关的知识,希望对你有一定的参考价值。

背景

收到朋友针对他们遇到的具体问题写一篇文章的邀请,问题是这样的

朋友:

我们公司对接了M个三方服务  然后还有N种业务  实现用策略的话感觉有点类爆炸  也想不到什么好方法了  这也是早上刚接到一个重构支付的需求想到的   根据业务和供应商搞了太多策略实现了[晕]

我:

初步感觉应该用打造接入平台的方式解决

朋友:

开始的时候没有考虑到  代码一言难尽了 [捂脸]  谢谢谢老师

我从对话中分解出下面几个问题

1、理解重构、重写和重新架构

2、怎样做重构?

3、重构过程与业务支撑之间怎样平衡?

下面我针对这三个问题展开论述。

理解重构、重写和重新架构


重构是对代码和功能进行渐进式改进;重写是要摧毁它,重新构建;重新架构在大框架上做修改和再设计,具体每个实现模块可能是用原来的。


举个例子,搭积木。重构就是我用积木搭建了一个小马,发现小马某些部分不好看,我就把几块积木调整调整,让小马更好看;重写就是我用积木搭建了一个小马,发现自己搭建好了完全不喜欢,把积木推到了重新搭,既然是重新搭,搭好的东西和原来差距有多大就不好说了;重新架构就是我用积木搭建了一个小马,发现自己其实想要个小桥,没有必要全部推到重做,而是可以先画好设计图,然后直接把小马的四条腿拆下来做桥墩,脊背部分做一部分桥面,实在不能用的再拆下来重安装。


重构是小步跑的过程,如果对外提供的功能没有变化,这是稳妥的方法;重写往往伴随着功能上的升级,不然对于“我不知道这段代码是干什么用的但我不敢删”这种事情很难保证对外提供的功能没有变化。


重构和重写与修改量没有直接关系。有个哲学问题叫做“特修斯之船”,是说一艘船持续的在海上航行,船不断有部分老化或者坏掉进行替换,最终整艘船的所有部分都换过一遍,这时候船还是原来那艘船吗?


所以咱们不再纠结概念,统一用重构这个词,聚焦于怎么解决问题。

怎样做重构

首先需要对系统进行一个生命周期预估,包括:

  • 未来需求预判

  • 模块划分拆解

  • 总结历史问题

  • 朋友说:“开始的时候没有考虑到  代码一言难尽了,其实大可不必这样想。架构和代码不是设计出来的,是演进出来的。如果一开始要我做,一段时间之后,也很可能会遇到相同觉得非重构不可的情况。虽然我一定会做未来需求预判,但是现实往往会出现很多例外情况,让程序充满了妥协。

    那针对朋友遇到的问题,怎么设计更合理呢?

    支付重构

    如果只是重构支付部分,我个人觉得相对容易。因为支付需要的要素比较好抽象,主要是下游支付渠道需要什么。自从有了网联,现在对接方也比较固定了。直接接网联或者微信支付、支付宝支付、拉卡拉…… 他们所需要的要素都差不多。因为它们都要接入网联完成国家监管的需求,所以要素主要看网联需要哪些要素。

    当然,它们的签名方式可能会不同;调用的支付渠道地址不同;给支付渠道传递的时候入参组装方式也不同。甚至有的支付渠道还需要有特殊的步骤。这时候怎么抽象呢?

    推荐“工作流”+策略模式。这是一种实现积木化的方式。定义一个统一接口,比如

    class Executor

          void execute(Task task);


    task里放置所有的入参,以及每次执行时产生的,下游需要的中间数据。签名等流程各自实现此接口。这样就实现了自由组装的重用。

    举个例子,下面积木块”各自实现了自己的execute:

    微信支付签名、微信支付入参组装、微信支付结果解析、支付宝支付名、支付宝支付入参组装、支付宝支付结果解析、支付宝特殊处理、下游http调用(这里假设微信和支付宝都是http的post请求来调用的,这样发起调用来说两者只是请求地址和入参不同)

    这时候可以组装微信支付流程:

    List list = new List();

    list.put(微信支付签名);

    list.put(信支付入参组装);

    list.put(下游http调用);

    list.put(信支付结果解析);

    for(Executor e :list) 

        e.execute(task);


    同样可以组装支付宝支付流程:

    List list = new List();

    list.put(付宝支付签名);

    list.put(付宝支付入参组装);

    list.put(下游http调用);

    list.put(付宝支付结果解析);

    list.put(付宝支付特殊处理);

    for(Executor e :list) 

        e.execute(task);



    不同的流程只是list不同,这个list是可以时间配置化的,可以配置在配置文件中、甚至可以从数据库中读取。微信支付流程和支付宝支付流程是两种不同的策略,可以使用策略模式。入参选择哪种策略就用哪种策略执行。这样,各个流程之间没有干扰,积木块变更影响小还可以随意组合灵活性高。

    整体重构

    朋友的业务复杂性还不在于支付,而在于收单。解决方法就是文章开头提到的接入平台的方式。打造平台的关键不是技术,而是规范。自己制定规范。别人接入都要按照规范来

    这里有前提:公司自身的影响力。平台是底层自己需要有决策权,比如微信开放平台,想接入就要理解并使用他们的API。而因为微信基础在,必须按照人家说的来,这就是平台影响力。

    在实际工作中,一般有两种情况。接入方自身也没有什么规范,本身就要求功能可用,这时候咱们自身有规范,对方比较容易接受。另外一种,对方是实力背景的大公司,也有自己的规范。这时候咱们处于弱势,要按照别人的规范来,可以使用上面支付重构的积木化方法进行进行组合。

    重构过程与业务支撑之间怎样平衡?

    在重构过程中一般会遇到两顶思考帽问题:一顶是需求的帽子,一顶是重构的帽子。有限的时间怎么分配呢?建议一开始就定好目标:什么阶段要重构到什么程度,然后分解工作。最后分摊到每天比如需要2小时时间。那么在需求开发时间评估的时候就要把这个时间扣掉。

    重构做完,老逻辑迁移到新逻辑有风险。这时候我的建议是:

    首先,把新功能当成重构结果的验金石。有新功能,先在新功能上验证新逻辑的正确性。因为新功能上线到上量有一个过程,初期出了问题影响也不会很大。

    然后,老逻辑迁移需要灰度。可以一个业务一个业务的灰度迁移,也可以按照流量百分比进行迁移。

    总结

    最后总结一下对重构的几点建议:

    1、小步快跑,稳步迭代

    2、设计与代码的重构结伴而行

    3、如果模块或者函数不能想到一个好名字,很可能是设计出了问题

    4、每步要保证足够简单,想象随时可能会停止

    巴比博弈

    巴比博弈

    参考:博弈论及算法实现

    只有一堆n个物品,两个人从轮流中取出(1~m)个,最后取光者胜。

    考虑到 若n=m+1那么 第一个人不论如何取都不能取胜。

    进一步我们发现 若 n=k(m+1)+r,先取者拿走r个,那么后者再拿(1~m)

    n=(k-1)*(m+1)+r; 先取者再拿走r个 最后总能造成 剩下n=m+1的局面。

    因此,此时先手有必赢策略。

    相对应的,若n=k*(m+1)那么先取者必输。

    因此我们可以写出对应的程序(默认n m都大于0)

    代码:

    bool Bash_Game(int n,int m)//是否先手有必赢策略
    {
        if (n%(m+1)!=0) return true;
        return false;
    }

    以上是关于M*N个策略造成类爆炸怎样重构?的主要内容,如果未能解决你的问题,请参考以下文章

    积木大赛

    洛谷 P1969 积木大赛

    放积木

    搭积木 并查集

    XJOI1657&Codevs1255搭积木树状动规

    洛谷 P1969 积木大赛(NOIp2013提高组D2T1)