zoukankan      html  css  js  c++  java
  • 开发小结-业务管理类

    本篇文章关注于编程实践中的相关流程设计内容,内容来源自己过去的工作总结。

    业务流程设计

    越复杂流程,越容易出错。为了减少出错的情况,需要提取并且封装通用逻辑,用一个易于理解的名字来对外提供服务。在业务不同抽象层级上,干各自职责对应的事情。

    前后台交互的流程越多,需要维护的状态就越多,出现问题的概率就越大,因此在不影响主要功能的前提下,流程能简化就尽量简化,那些被简化的路径,在某些异常场景下,会对业务有一定的影响,具体影响到什么程序,在上线前,谁也不好说。可通过数据埋点得到上线后的真实数据,以供权衡。

    针对复杂业务时,不能一上手就开始写,要先设计好框架和整体流程。对于一些需求上面没有明确的交互细节,一般按照通用交互细节来就可以。

    对于具体子功能的实现上,需要细致考虑,较好的实践方法是逐条列出来,在每种状态或情况下,什么样的输入得到什么样的输出。

    在复杂程序执行过程中,需要维护多个状态,随着业务的改变,在处理于状态关联的事件时,思维负担会越来越重重,很容易顾此失彼。
    为了更好的控制复杂度,简化逻辑,需要对状态进行分级管理,将每个级别的状态和对应处理分层化。对外统一状态操作接口,每个模块中,都通过统一状态接口来管理状态。

    每一个常量数字,在业务上都要有依据来源,要么是经验值,要么是业务规则规定的。

    失败流程

    任何失败重试类操作,都要考虑对后台的影响。因为网关只会转发请求,大量冗余的请求会给后台造成巨大压力,可能会影响正常业务流程。对于失败重试,一定要设置定时间隔以及重试次数,这里的失败要区分类型,如果是业务操作类的失败,业务层直接提示出错即可。如果是其他类型的失败,底层需要做好重试策略,直到重试策略返回失败,才可认为是失败,此过程对上层是透明的。

    针对开发回发的数据,要进行有效性校验才能继续往下走,在有些业务场景中,需要对不同类型的失败分配不同类的错误码,以便外部处理。比如读取配置信息这个功能,可能的出错就有以下几种情况:

    1. 配置文件不存在
    2. 配置文件存在,但文件格式不正确
    3. 配置文件存在,文件格式正确,属性Key缺失
    4. 配置文件存在,文件格式正确,属性Key正常,属性Value缺失
    5. 配置文件存在,文件格式正确,属性Key正常,属性Value是异常值(超大的数,或者是负数)

    如果因为上述种种原因,导致读取配置文件失败,那要做好默认配置的处理,根据产品要求,对不同的异常情况,做出对应的处理,使用默认配置继续运行,还是弹框提示用户等。

    在对后台返回来的格式化数据进行解析时,一定要考虑对应字段不存在的情况下该如何处理。不管后台返回什么类型的错误数据,都不能崩溃。

    流程优化

    仔细分析业务流程,避免不必要的依赖和操作,相关的业务逻辑,要聚合到一起。

    分析问题的思路要不断在实践中打磨,反思,总结。自己第一时间想到的方案,往往还有很大的优化空间,那么,从什么角度去思考优化呢?在开发实践中,逐渐明确两个大的方向,在此记录一下

    1. 从底层往上层思考 : 底层是最基础的数据层,业务流程的数据是不是可以往下移动到底层,然后调用底层明确性语义接口函数来实现上层业务,而不是把一堆判断逻辑都交由业务层去做。

    2. 从上层往底层思考 : 在业务层和底层数据之间,要有一个间接层,对上提供较为高级的功能,对下封装最底层数据,提供更高层次的接口。

    3. 相似的业务处理采用相似的办法,不要一堆条件,范围A-C的用这样的处理方法,业务范围D-F的用那样的处理方法,其他类型业务用其他的处理方法。这样一来,业务和对应的错误处理分散在各处,后期维护很容易出错。

    一个类,如需向其他类获得相关信息,不要依赖于类与类之间的继承组织关系,尝试通过this来进行强制转换来获取,因为这种层级关系在未来可能会发生变动,而这种层级关系的变动,不会通知使用者,因此在实现类似需求时,需要将对外部的依赖关系尽可能的降低,建议采用消息的方式,每层规定好消息的职责,一层一层向上传递请求,直到某一层给与响应为止。

    一致性

    一致性,指的是对于UI元素和代码命名的一致性,同一逻辑元素的命名一致性,它在UI层、中间层、网络层以及后台接口中的核心词汇要保持一致,这样增加代码的可读性。

    一致性体现在资源管理上,同一份资源,谁申请,谁就负责释放。谁增加引用记数,谁就负责减少。

    在一些通用功能的设置上,保持代码一致性很重要。比如设置控件字体,原生系统会提供一套接口,自绘部分也会提供一套接口,那么外部在使用时,就会有疑惑,设置字体是用原生接口还是自绘接口呢?他们会不会相互影响呢?在前期设计时,同一类功能,只提供一套接口较好。

    相似操作能够归集到一起的,一定要归集到一起,让逻辑聚合,而不是到处分散。

    修改影响评估

    比如,你修改了A函数中的A分支,A函数的B分支没有改动。外部O1模块使用了A函数的B分支,O2模块使用了A函数的A分支,那么你的本次修改涉及到的影响范围是哪些呢?O2模块肯定是要测的,O1模块需不需要提测?答案是需要的,从广义上来看,任何调用A函数的模块都要重测一片。即使本次修改没有涉及到分支,也要去测试。

    在尝试修改后台程序时,要注意与之前系统的兼容性。

    在修改一个涉及范围比较大的问题时,如果涉及的地方太多了,为了最终问题的解决和测试方面的平滑过渡,需要将问题分治,按照功能需求模块来统计汇总,然后,将问题列表按模块分配给各自负责人,由他们去进一步往下分散进行修改,这样可以保证大型问题的平滑过渡,为开发和测试提供缓冲时间段。

    比如本次修改点可分为以下几个集合,

    集合1:涉及到哪些方面

    集合2:涉及到哪些方面

    集合3:涉及到哪些方面

    逻辑完备性

    对于层级调用,资源的申请和释放,要特别注意一致。这一点,特别容易遗漏。
    比如说,先压入缓存,再发出请求,那么,清理缓存的时机,应该要覆盖发出请求后的每一条执行路径。
    7.1 发出请求失败
    7.2 发出请求成功,响应成功
    7.3 发出请求成功,响应失败

    一段业务代码,正确的路径只有一条,但错误的路径有很多条,怎么处理在这个过程中有可能出现的错误,就很显的功力所在了。

    如果遇到一些需要特殊处理的地方,优先把大部分情况放在主干逻辑上,在额外的分支中处理额外。

    凡是涉及到网络请求的,如果中途关闭了页面,一定要注意请求资源的清理操作,这样在重新打开页面,不会收到上一次无效的响应。

    优化前的准备

    没有profile的优化都是瞎扯淡,一旦开始重构,就要指定好优化目标,所有的相关修改都聚焦在目标上,不能跑偏了。优化前要分析现状、制定方案和可量化的目标,完成优化后,需要提取相关的数据,总结对比可量化的分析优化成果。后台重构需要配置A/B方案,对于可能会有变化的模块或者业务,或者A、B方案不好取舍的,可采用后台配置采用哪个方案,当重构的版本上线时,先保留旧的模块,如果有问题,可以在后台中配置切回到旧的模块,这种思想同可以同灰度发布一起使用(5W,10W)

    在进行组件的重构时,需要遵循最小影响范围来做,一处重构,尽量只影响到这个业务,不影响到其他业务,保证可控性和可测试性。

    维护经验

    对于接手旧项目,里面用到的第三方库,如果有新版本的接口库或者更好的第三方接口库,该如何抉择?

    • 如果原有的库能够满足需求,则不要贸然替换或者升级
    • 如果原有的库不能满足特定场景下的需求,优先自己封装在特定场景下的接口,以供使用。如果特定场景下的需求需要的新功能不能通过封装已有API来提供,而该功能在原有库的升级版中存在,那么需要和原有库维护者商量讨论后再进行升级。
    • 用新的三方库来替换,该方法改动范围会很大,不到万不得已,不允许使用。

    当老旧代码中有很多相互交缠的逻辑,需要改动多处才能改好词Bug,不好评估改动影响的范围,这个时候,为了最大限度的兼容老旧代码,不能继续在老旧代码的基础上继续填坑、挖坑,如果选取其中一个简单的界面,用清晰简单的逻辑去实现,然后在逐步替换老旧模块,小步慢跑的改进。

    当需要在原有功能上面增加新的功能时,要特别注意一点,就是原有功能所使用的指令和流程不能更改。比如查询A产品的天数信息,原有的指令只支持一种产品的查询,现在其他界面有同时查询多种产品的情况,而原有指令只实现了查询单个产品信息, 那么,下面有两种解决方法:

    		     				  				   优点			            缺点
    方法一:修改原有指令,使其支持多条信息的查询	    增加指令的功能         需修改原有查询单条产品的页面,增加了测试成本
    方法二:不改动原有指令,新增支持多条信息查询指令  不会影响已有功能       类似功能有多处实现,查多条包含查一条的功能
    

    因为查询一条产品信息属于查询多条产品信息的特例,为了可维护性,采用方法一较好。

    一个简答的权限判断功能设计

    有A、B、C三个账号,有ID1,ID2,ID3,ID4四个功能,每个账号允许的功能如下:

    A--> ID1, ID3
    B--> ID2, ID3
    C--> ID4
    

    现在要设计一个权限判断的函数,给定功能ID和账号,返回是否允许执行此操作判断?

    一种是从上往下,从入口ID和各个可操作ID段来判断,优点是直观,简单,缺点是对扩展性不好,未来要增加新的入口时,需要修改多出
    方法一: 从上到下,逻辑简单直接,可扩展性差

    bool IsAllow(Account acnt, int nFunId)
    {
        if (A == acnt)
        {
        	if (nFunId == ID1 || nFunId == ID3)
        	{
        		return TRUE;
        	}
        }
        else if (B == acnt)
        {
        	if (nFunId == ID2 || nFunId == ID3)
        	{
        		return TRUE;
        	}
        }
        else if (C == acnt)
        {
        	if (nFunId == ID4)
        	{
        		return TRUE;
        	}
        }
        
        return FALSE;
    }
    

    另一种方法是从下往上,将每个交易ID和能够操作条件集合在一起,这种方式的扩展性和可读性都非常好,推荐使用。

    struct TIDInfo
    {
        int nId;
        vector<Account> vAllowAcntType;   //  该ID要求的账号属性
        
        bool IsAllow(Account acnt)        // 给定账号是否存在特定属性
        {
    	   return vAllowAcntType.find(acnt) != vAllowAcntType.end();
        }
    }
    
    vector<TIDInfo> allIdInfo;            // 所有功能ID集合
    bool IsAllow(Account acnt, int nFunId)
    {
        for (int i = 0 ; i < allIdInfo.size(); ++i)
        {
        	if (allIdInfo[i].nId == nFunId)
        	{
        		return allIdInfo[i].IsAllow(acnt);
        	}
        }
        
        return FALSE;
    }
  • 相关阅读:
    P5331 [SNOI2019]通信
    P3700 [CQOI2017]小Q的表格
    Linux
    P3268 [JLOI2016]圆的异或并
    P3317 [SDOI2014]重建
    P5492 [PKUWC2018]随机算法
    P3210 [HNOI2010]取石头游戏
    支配树
    P5401 [CTS2019]珍珠
    P4027 [NOI2007]货币兑换
  • 原文地址:https://www.cnblogs.com/cherishui/p/10453605.html
Copyright © 2011-2022 走看看