业务复杂度治理方法论--十年系统设计经验总结

news2024/11/15 14:08:37

一、复杂度综述

1、什么是复杂度

软件设计的核心在于降低复杂性。

--《软件设计的哲学》

业界对于复杂度并没有统一的定义,斯坦福教授John Ousterhout从认知负担和工作量方面给出了一个复杂度量公式





子模块的复杂度cp乘以该模块对应的开发时间权重值tp,累加后得到系统的整体复杂度C

这里的子模块复杂度cp是一个经验值

需要注意:如果一个子系统特别复杂,但是很少使用及修改,也不会对整体复杂度造成太大影响。例:spring框架内部代码较为复杂,但由于几乎不需要我们去变动,所以对系统的整体复杂度影响并不大

2、复杂度分类







本文主要面向业务复杂度的治理

3、业务复杂度高的影响

(1)研发成本高。需要花费更多的时间去理解、维护代码;同样的需求,可能需要要修改更多的工程和类

(2)稳定性差。过高的业务复杂度,会导致系统难以理解甚至理解出现错漏,改动代码后极易出现“按下葫芦起了瓢”的问题

二、业务系统复杂度高的常见原因

1、业务系统模块多,关系复杂,互相依赖





比如一个电商业务,会包含商品、订单、采购、库存、财务等多个系统,系统之间有各种各样的依赖关系关系,如订单系统依赖库存充足才可以正常下单,采购依赖商品必须创建才可以发起采购;而系统内部又可以划分为多个子系统,如订单系统可以包含接单、营销、会员等各个子系统

2、代码晦涩,从代码中很难找到关键信息

如下边的业务处理代码,不点进具体的方法,根本不知道做了什么:

    /**
     * 处理业务逻辑
     */
    public void handleBiz(){
        step1();
        step2();
        step3();
    }

再比如下边的业务处理代码,做了方法定义外的操作,开发者很容易遗漏重要信息

    /**
     * 转换对象方法
     */
    public Po convert(Dto dto){
        Po po=new Po();
        po.field=dto.filed;
        //更新操作,不应该放到转换方法里
        mapper.update(po);
        //调用rpc服务,不应该放到转换方法里
        XXGateway.update(dto);
        return po;
    }

3、业务规则、流程变化多,变化频繁

大量的变化造成花在系统上的时间增多,提升了开发时间权重;

繁杂的业务规则写在代码中,难以理解、梳理





三、降低业务复杂度的方法

1、抽象分治,分解复杂度

(1)领域拆分

将与核心概念有关的内容抽取/合并,形成独立的领域

a、实体类的系统。映射到物理实体,比如商品中心、用户中心、地址服务等

b、流程类系统。映射到多个角色的串联协调工作,比如供应链上单,审核类系统等

c、计算任务类系统。映射到虚拟计算机类及数据处理,比如搜索排序、推荐计算等

供应链领域化拆分例子:









(2)领域之内拆分

01、变与不变拆分。将不易变化的系统能力拆分出来,上层适配各种业务逻辑,底层提供稳定的能力单元。如营销领域,将优惠卡券这个不易变的内容作为一个子系统来设计



02、场景隔离。如B/C隔离,B端业务复杂度较高,流量较小,更注重数据建模、可配置、可扩展;C端业务复杂度低,但是流量较大,更注重高性能、高可用





例:营销领域进行【变与不变拆分】+【场景BC隔离】例子:

背景:营销域即有面向商家的营销资质、营销活动管理,又有面向C端用户根据活动领红包、优惠券的高频业务

其中,B端业务逻辑较复杂但请求量小,C端业务逻辑较简单但请求量大

治理方案:【变与不变拆分】+【场景BC隔离】

建设营销B端服务。提供面向商家的营销活动管理,重心放在建设复杂的营销活动模型、处理复杂的业务流程;

建设C端用户系统,提供面向C端客户的领营销资产、消费影响资产服务,重心放在高并发、高可用建设;

建设底层营销资产管理服务。如卡券中心提供生成卡券、消费卡券、查询卡券等基础稳定的服务。相对不易变化,不需要经常迭代;

上层的服务根据不同的业务场景来决定把卡券发给谁、什么时候发、发多少,业务场景变化或者有新的场景时,经常需要迭代

拆分架构如下图:







例:库存中心【变与不变拆分】例子

背景:

01、业务上。库存业务面向的业务场景较多,如采购加库存、销售扣库存,退货加库存等等;库存扣减逻辑也较复杂,如一个sku下多个渠道按照优先级扣减、一个sku有多个批次按照过期时间先后扣减等等;

02、技术上。为了提升性能,高频操作场景会先操作redis缓存,再异步同步DB数据

治理方案:抽象业务+变与不变拆分

库存操作业务可以抽象为:根据用户条件、库存划分规则定位到需要操作的库存记录,按照库存记录对库存进行操作

变与不变拆分: 01、DB库存操作、Redis库存操作建设为底层支撑能力,仅提供基础的库存加减能力,尽量保障其不变性,避免大幅度的改动影响所有业务 02、根据业务逻辑去定位库存,这部分相对来说变化比较频繁,由上游业务系统进行处理

库存架构如下图:







(3)子领域/系统 内部拆分

01、代码分层。确定每一层的分工;确定调用关系,不能跨层调用;如果下层能解决的复杂性问题,不要放到上层,如:外部接口调用失败重试,不能放到服务层

常用的贫血模型分层例子:





核心思路:关注点分离、能力复用。各层职责:

•接入层:负责服务接入,包含日志打印、异常处理、参数检查、权限检查等接入层能力。该层不包含业务逻辑

    @Override
    public InsertDeptResponse insertDept(InsertDeptRequest request) {
        //入参记录
        log.error("insertDept request:{}",request);
        InsertDeptResponse response=null;
        String umpKey = XXX;
        Profiler.registerInfo(umpKey, true, true);
        try{
            
            //校验入参,如果校验不通过,checkParam方法会抛出参数校验不通过异常
            checkParam(request);
            //权限校验,如果校验不通过,checkAuth方法会抛出权限校验不通过异常
            checkAuth();
            //业务逻辑处理,内部可能会抛出影响可用性的异常和不影响可用性的异常
            response=XXFacade.insertDept(request);
        }catch(BizRuntimeException ce){
            //不影响可用性的异常
            log.error("XXX");
            //组装返回值
            response=XXX;
        }catch (Exception e) {
            //影响可用性的异常
            log.error("XXX");
            //组装返回值
            response=XXX;
            Profiler.functionError(info);
        }
        log.error("insertDept response:{}",response);
        Profiler.registerInfoEnd(info);
        return response;
    }

•业务逻辑层:整体负责接口中的业务逻辑处理。主要进行各领域间的逻辑串联、数据处理

•领域服务层:以某个核心概念为核心组件领域服务,如权限服务,将该领域相关能力进行收口,提供给上层可复用的能力

•数据层:负责与数据库或者外部服务进行数据交互

02、自上而下的结构化分解。使用金字塔原理,将复杂逻辑进行结构化自上而下分解

例:供应链业务二维码牌安装服务治理

背景:供应链业务中,二维码牌安装服务流程特别复杂,理解与修改都很困难;流程中用到很多数据,在不同方法中被重复获取

治理方案:结构化抽象、分解业务流程







结构化分解后的流程:







拆分后主方法伪代码:

public void setUp(){
    //参数检查阶段
    paramCheck();
    //初始化阶段
    initData();
    //业务校验阶段
    businessCheck();
    //业务执行阶段
    businessExecute();
}

(4)方法逻辑拆分。职责单一、命名准确

方法随意命名,尤其是做了与命名无关的事情,会极大的增加复杂度,影响阅读者对代码逻辑的理解

(5)系统合并

如果多个系统逻辑上耦合严重,改了一个模块,另一些基本都要变动,考虑将这些系统合并

2、添加注释,使代码易懂

(1)代码思路要通过注释/自注释标识出来

例:redis模式的库存操作,在处理逻辑主方法中,较为清晰的标注了每一步核心逻辑





(2)注释应当能提供代码之外额外的信息

重视What和Why,而不只是代码是如何实现的(How)

01、一些不那么直观的代码,可以附上原因







02、设计思路,特别复杂的,可以考虑贴上设计方案的地址





3、配置化

(1)业务对象可配置

业务中用到的同类型对象特别多,使用硬编码方式维护困难时,可以考虑抽象出可配置化的对象配置中心

如:商品中心,将商品抽象为sku,并提供名称、价格、重量等可配置的属性

(2)业务规则可配置

业务中规则部分特别复杂,可以考虑抽象出可配置化的规则配置中心

如:售卖策略配置,某sku在某业务线+某业务场景中,必须搭配某种前置sku,以XX价格进行售卖

业界有多种开源规则引擎,如Aviator、Drools、QLExpress等,不同的规则引擎在功能、性能、学习维护成本上有一定差异,需要根据自己的业务场景来进行选型

业务规则配置例子:

背景:物流能力中心项目中,要计算不同的仓类型、不同的资源类型(人员、场地、物资、车辆等)、不同履约时效的履约能力,每种场景接入的参数都不一样,计算工时也不一样,算法还经常需要调整,如果使用硬编码的方式,要对几十种业务组合编写业务逻辑,工作量大,维护困难

方案:引入规则引擎,实现业务规则可配置,提升开发、维护人效









(3)业务流程可配置

如果不同的业务场景,需要不同的执行流程,可以考虑引入流程配置框架,如:一些业务场景,流程执行顺序为A-->B-->C,另外一些业务场景执行顺序为C-->B-->A,还有一些业务场景执行顺序为B-->A-->C。

注:流程配置框架相对比较复杂,更适合平台/中台建设,其他场景建议谨慎评估后再引入

流程配置框架的核心:流程编排+能力复用(插件化),前提是流程抽象➕流程标准化

业界流程编排框架:阿里TMF、美团BPF、京东物流batrix

TMF2.0 配置流程:





4、使用规范降低复杂度

本质上,是做好约定,简化思考。如:一个类命名为OrderDao,不需要看代码,就可以很清晰的知道这是一个订单数据处理的类

(1)代码规范

如接口、类、方法等的命名

(2)架构层级规范

系统分层级。上层调用底层,避免底层直接调用上层,同层级尽量避免互相调用

例:物流百川三层架构,定义了系统层级,使系统交互有序







四、业务复杂度优化重构原则

1、小步快跑。每个迭代要能独立交付,保障每次迭代充分验证,更快看到重构效果

2、先写后读。通过双写,验证新模型的可行性;通过数据一致性校验后,再逐步迁移读接口

3、先轻后重。先做简单逻辑再做复杂逻辑。先迁移轻业务,有了经验后,再去迁移更复杂的重业务

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2107032.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

快充协议方案的工作原理及场景应用

快充协议芯片是支持各种快充快充协议的芯片,它们能智能识别插入的设备类型,并根据设备的需求调整充电电压和电流,从而实现快速充电。 XSP08Q芯片是内置快充功能的协议芯片,它基于先进的充电技术,通过协商电压和电流&a…

【数据结构】二叉树顺序存储结构堆的应用以及解决TOP-K问题

文章目录 前言1. 堆的应用1.1 堆排序1.2 TOP-K问题 2. 结语 前言 前面我们学习了堆这个数据结构,这种数据结构是一种顺序结构存储的完全二叉树,现在我们来看一看堆的应用。 1. 堆的应用 1.1 堆排序 版本一:基于已有数组建堆、取堆顶元素完…

Linux CentOS 部署Docker

1. yum 配置 (1)更新yum yum update -y 如果不升级更新yum 可能在后续docker部署后再更新容器会出现oci runtime error等 (2)安装yum工具类准备 yum install -y yum-utils device-mapper-persistent-data lvm2 (3&…

不再为存储‘分家’烦恼,teamOS让你的数据全家桶,一键即达

在数字化浪潮下,数据管理已成为企业运营的核心环节。作为企业管理者,我深知数据的重要性,也明白数据管理所面临的种种挑战。 最近开始使用企业网盘,在体验了一段时间后,目前来说,让我比较满意的就是可道云…

C盘红了怎么办?C盘快满了怎么办?如何提高电脑运行速度?

在电脑的日常使用中,C盘红了(也就是C盘满了),那是常有的事,本文将详述一下,C盘红了之后的多种处理方法,只要你看完了,就必有一款适合你。 一、系统自带的磁盘清理 当你的C盘红了&…

vue3 置空a-select数据

置空a-select数据 项目中遇到需求&#xff0c;选择第一个下拉框后&#xff0c;发请求获取数据第二个下拉框数据&#xff08;第二个下拉框已选的情况下需要置空&#xff09;。 解决方法&#xff1a;formData.value.checkUser {value: “” ,label:“”} <a-col :span"…

Win32中的字符串

ANSI字符和Unicode字符 在Visual C中&#xff0c;用CHAR来表示8位ANSI字符&#xff0c;用WCHAR来表示16位Unicode字符&#xff08;宽字符&#xff09; 1 typedef char CHAR 2 typedef wchar_t WCHAR 一般常用的定义如下 TypedefDefinitionCHARcharPSTR or LPSTRchar*PCSTR o…

一个跨平台的换源工具,一键更换操作系统上的软件源或镜像源

大家好&#xff0c;今天给大家分享的是一个全平台通用换源工具chsrc&#xff0c;旨在为各种软件在不同平台上提供从命令行进行换源的功能。 项目介绍 chsrc支持 Linux&#xff08;包括麒麟、openEuler、deepin 等&#xff09;、Windows、macOS、BSD 等多种操作系统&#xff0c…

MySQL数据类型-介绍

MySQL 支持多种数据类型&#xff0c;这些数据类型可以根据它们所存储的数据类型大致分为几类&#xff1a;数值类型、日期和时间类型、字符串&#xff08;字符&#xff09;类型、空间数据类型以及JSON数据类型。 一、数据类型 1.整数类型 TINYINT&#xff1a;非常小的整数。例…

第 2 章:AJAX 的使用

AJAX 的使用 核心对象&#xff1a;XMLHttpRequest&#xff0c;AJAX 的所有操作都是通过该对象进行的。 1. 使用步骤 创建 XMLHttpRequest 对象 var xhr new XMLHttpRequest(); 设置请求信息 xhr.open(method, url);//可以设置请求头&#xff0c;一般不设置 xhr.setReques…

计算机网络八股文之TCP协议

TCP/IP模型 链路层 物理层&#xff1a;主要定义物理设备标准&#xff0c;如网线的接口类型、光纤的接口类型、各种传输介质的传输速率等。它的主要作用是传输比特流&#xff08;就是由1、0转化为电流强弱来进行传输&#xff0c;到达目的地后再转化为1、0&#xff0c;也就是我们…

WPS Office for Linux 12 个人版上线deepin 23商店:UI 视觉重构,新增多项 AI 功能

查看原文 全新WPS Office For Linux个人版&#xff08;12.1.0.17881&#xff09;与deepin 23的AI功能深度融合&#xff0c;正式上架在社区商店啦&#xff01; 这是 Linux 版本的一次里程碑式的重大更新。在产品能力层面上&#xff0c;在 deepin 23 上WPS Office 几乎实现了与 …

30天一次过PMP资料分享!2024备考PMP必看!!!

1、华为项目管理工具模板 2、PMP思维导图 3、PMBOK指南第七版-中文电子版 4、敏捷电子书&#xff08;含敏捷实践指南&#xff09; 5、300道敏捷题 6、PMBOK第六版章节思维导图 PMP备考攻略&#xff1a; 1. 充分了解PMP考试大纲&#xff1a; 详细了解PMP考试大纲&#xff0c;明…

IEEE Latex模板子图标题字体修改

在进行IEEE期刊论文排版时&#xff0c;可能会用到子图&#xff0c;但官方的Latex模板中&#xff0c;子图标题字体与字号和图片标题不同&#xff0c;排版出来不美观&#xff0c;下面介绍一个简单方法解决这一问题。 在tex文件头部找到这一条代码&#xff1a; \usepackage[capti…

Spring学习(四)-AOP

Spring学习&#xff08;四&#xff09;-AOP –2020年06月26日 一、AOP的概念 面向切面编程。 利用AOP可以对业务逻辑的各个部分进行隔离&#xff0c;从而使得业务逻辑各部分之间的耦合度降低&#xff0c;提高程序的可重用性&#xff0c;同时提高了开发的效率。 通俗描述&am…

Mendix 创客访谈录|Mendix赋能汽车零部件行业:重塑架构,加速实践与数字化转型

在当前快速发展的技术时代&#xff0c;汽车行业正经历着前所未有的数字化转型。全球领先的汽车零配件制造商面临着如何利用最新的数字技术优化其制造车间管理的挑战。从设备主数据管理到生产执行工单管理&#xff0c;再到实时监控产量及能耗&#xff0c;需要一个灵活、快速且高…

IF 17.1| 爱竹人士一眼就心动的数据库!

竹子具有重要的经济和生态重要性&#xff0c;并为国际贸易做出贡献。BambooGDB是现有的竹基因组学资源&#xff0c;提供了基于毛竹个体参考基因组草图的信息。然而&#xff0c;我们需要的是一种资源来加强竹科多基因组组装&#xff0c;使研究人员能够通过分支特异性比较基因组研…

Qt基础类02-坐标类QPointF

Qt基础类02-坐标类QPointF 摘要基本信息重要成员函数举例6个程序全貌QPointF::QPointF()static qreal QPointF::dotProduct(const QPointF &p1, const QPointF &p2)bool QPointF::isNull() constconst QPointF operator*(const QPointF &point, qreal factor)const…

程序员都必须要知道的 8个常见数据结构

1. 数组&#xff1a;多功能主力 什么是数组&#xff1f; 数组可能是编程中最基本、使用最广泛的数据结构。将数组视为存储在连续内存位置的项目集合。它就像学校里一排储物柜&#xff0c;每个储物柜&#xff08;元素&#xff09;按顺序编号&#xff0c;可容纳一个物品。 数组…

Qt实现图表绘制

来来来&#xff0c;今天新学习到了一个好东西&#xff0c;就是图表的绘制&#xff0c;这玩意在一般的项目开发中的使用频率还是非常高滴&#xff0c;毕竟相对于数字来说&#xff0c;这个东西更能体现出数据的变化&#xff0c;主要是耐看啊&#xff01;&#xff01;&#xff01;…