面对复杂业务,if-else coder 如何升级?

简介: 针对业务在不一样场景下的差别,咱们经常会习惯性地使用if-else来实现不一样的业务逻辑,长此以往代码愈来愈难以维护。那么如何消除这些if-else?面对复杂业务应如何思考和分析?本文分享阿里高级技术专家张建飞(Frank)关于复杂业务治理的方法论,介绍一种多维度分析问题的方法:矩阵分析法。设计模式

image.png

You should not be a if-else coder, should be a complexity conquer.
——Frank

这篇文章,是对以前我在《阿里高级技术专家方法论:如何写复杂业务代码?》说的“自上而下的结构化分解 + 自下而上的抽象建模”方法论的升级。由于在以前的方法论中,咱们缺乏一个多维度看问题的视角,这种维度思惟的缺失,可能会致使miss掉一些重要的业务信息,从而使咱们制定软件设计策略的时候,陷入困难。框架

有了维度思惟,咱们即可以更加方面的去看清业务的全貌,更加全面的掌握业务信息,从而帮助咱们更加体系化的去治理复杂性。学习

从if-else提及

我常常说,咱们不要作一个if-else coder。这里的if-else,不是说咱们在coding的时候不能使用if-else,而是说咱们不该该简陋地用if-else去实现业务的分支流程,由于这样随意的代码堆砌很容易堆出一座座“屎山”。测试

业务的差别性是if-else的根源。以零售通的商品业务为例。不一样的处理场景,其业务逻辑实现是有差别性的。以下图所示,商品业务的差别性,主要体如今商品类型、销售方式和仓储方式的不一样。ui

image.png

这三个维度上的差别组合起来,有 2 3 2 = 12 之多。这就是为何在老代码中,处处能够看到 if(组合品) blabla,if(赠品) blabla,if(实仓) blabla 之类的代码。编码

那么,要如何消除这些讨厌的if-else呢?咱们能够考虑如下两种方式:spa

  • 多态扩展:利用面向对象的多态特性,实现代码的复用和扩展。
  • 代码分离:对不一样的场景,使用不一样的流程代码实现。这样很清晰,可是可维护性很差。

多态扩展

多态扩展能够有继承和组合两种方式。继承勿用多言,组合有点像策略模式,也就是把须要扩展的部分封装、抽象成须要被组合的对象,而后对其进行扩展,好比星环的能力扩展点就是这种方式。设计

这里,咱们举一个继承的例子,商品在上架的时候要检查商品的状态是否可售,普通商品(Item)检查本身就行了,而组合商品(CombineItem)须要检查每个子商品。3d

用过程式编码的方式,很容易就能写出以下的代码:code

public void checkSellable(Item item){
    if (item.isNormal()){
        item.isSellable(); 
        //省略异常处理
    }
    else{
        List<Item> childItems = getChildItems();
        childItems.forEach(childItem -> childItem.isSellable()); 
        //省略异常处理
    }
}

然而,这个实现不优雅,不知足OCP,也缺乏业务语义显性化的表达。更好的作法是,咱们能够把CombineItem和Item的关系经过模型显性化的表达出来。

image.png

这样一来,一方面模型正确的反应了实体关系,更清晰了。另外一方面,咱们能够利用多态来处理CombineItem和Item的差别,扩展性更好。重构后,代码会变成:

public void checkSellable(Item item){
    if (!item.isSellable()){
        throw new BizException("商品的状态不可售,不能上架");
    }
}

代码分离

所谓的代码分离是指,对于不一样的业务场景,咱们用不一样的编排代码将他们分开。以商品上架为例,咱们能够这样写:

/**
* 1. 普通商品上架
*/
public void itemOnSale(){
    checkItemStock();//检查库存
    checkItemSellable();//检查可售状态
    checkItemPurchaseLimit();//检查限购
    checkItemFreight();//检查运费
    checkItemCommission();//检查佣金
    checkItemActivityConflict();//检查活动冲突

    generateCspuGroupNo();//生成单品组号
    publishItem();//发布商品
}

/**
* 2. 组合商品上架
*/
public void combineItemOnSale(){
    checkCombineItemStock();//检查库存
    checkCombineItemSellable();//检查可售状态
    checkCombineItemPurchaseLimit();//检查限购
    checkCombineItemFreight();//检查运费
    checkCombineItemCommission();//检查佣金
    checkCombineItemActivityConflict();//检查活动冲突

    generateCspuGroupNo();//生成单品组号
    publishCombineItem();//发布商品
}

/**
* 3. 赠品上架
*/
public void giftItemOnSale(){
    checkGiftItemSellable();//检查可售状态
    publishGiftItem();//发布商品
}

这种方式,固然也能够消除if-else,彼此独立,也还清晰。但复用性是个问题。

多维分析

细心的你可能已经发现了,在上面的案例中,普通商品和组合商品的业务流程基本是同样的。若是采用两套编排代码,有点冗余,这种重复将不利于后期代码的维护,会出现散弹式修改(一个业务逻辑要修改多处)的问题。

一个极端状况是,假如普通商品和组合商品,只有 checkSellable() 不同,其它都同样。那毫无疑问,咱们使用有多态(继承关系)的CombineItem和Item来处理差别,会更加合适。

而赠品上架的状况偏偏相反,它和其余商品的上架流程差别很大。反而不适合和他们合用一套流程代码,由于这样反而会增长他人的理解成本。还不如单独起一个流程来的清晰。

那么,问题来了,咱们何时要用多态来处理差别,何时要用代码分离来处理差别呢?

接下来,是我今天要给你着重介绍的多维度分析问题的方法论之一:矩阵分析法。

咱们能够弄一个矩阵,纵列表明业务场景,横列表明业务动做,里面的内容表明在这个业务场景下的业务动做的详细业务流程。对于咱们的商品业务,咱们能够获得以下的矩阵:

image.png

经过上面的矩阵分析,咱们不难看出普通品和组合品能够复用同一套流程编排代码,而赠品和出清品的业务相对简单,更适合有一套独立的编排代码,这样的代码结构会更容易理解。

维度思惟

多维度的重要性

上面的案例不是我编造出来的,而是我在和张文(我同事)讨论应该用哪一种方式去处理业务差别的真实故事。

我记得在和大学讨论完,开车回去的路上,我一直在想这个问题,而后在第二个路口等红灯的时候,忽然有一个灵感冒出来。我抑制不住兴奋,一边开车,一边发消息给张文说:“我想到了一个很NB的方法论,能解决在‘多态扩展’和‘代码分离’之间如何作选择的问题”。

其实,我知道我兴奋的不只仅是解决了这个问题。我兴奋的是,我第一次真正领悟到了多维度思考的重要性。从而有机会从一个“单维度”生物,升级成一个“多维度”思考者。妈妈不再用担忧我被“降维打击”了 :)

结构化思惟有用、颇有用、很是有用,只是它更多关注的是单向维度的事情。好比我要拆解业务流程,我要分解老板给个人工做安排,我要梳理测试用例,都是单向维度的。

而复杂性,一般不只仅是一个维度上的复杂,而是在多个维度上的交叉复杂性。当问题涉及的要素比较多,彼此关联关系很复杂的时候,两个维度确定会比一个维度要来的清晰,这也是为何说矩阵思惟是比结构化思惟更高层次的思惟方式。

实际上,咱们从汉语的词汇上,也不难看出一我的的思惟层级,是和他的思考维度正相关的。当咱们说这我的很“轴”、“一根筋”的时候,其实是在说他只有一维的线性思惟。因此,观察事物的视角越多,维度越丰富,其思惟层级也会越高。

image.png

无处不在的多维思考

有了这些感悟,我开始系统的整理关于多维度思考分析的资料,发现这种思考方式真是无处不在。发现的越多,我越是感慨,为何如此重要的思惟方式,我到如今才领悟到。

波士顿矩阵

好比,在作产品分析的时候,有对产品发展前景进行分析的波士顿矩阵。
image.png

订单要素分析

当年,我在1688作交易下单业务的时候,有很是多的下单场景,每种场景下,买家享受的权益是不同的(以下表所示)。咱们当时也是使用了矩阵去表达这个复杂的关系,只是当时尚未想到要将其提高到方法论的高度。

image.png

数据交叉分析

在数据分析中,维度分析是很是重要的,特别是维度不少的时候,咱们能够经过皮尔逊积矩相关系数,作交叉分析,从而弥补独立维度分析无法发现的一些问题。

image.png

分析矩阵

最近我碰巧看到Alan Shalloway写的《设计模式解析:Design Patterns Explained》,这是一本很是经典的关于OOP的书,里面的第十六章就是专门讲“分析矩阵”的,做者创造这个方法论的初衷也是由于业务涉及的要素太多,信息量太大,他须要一种组织海量数据的新方式。

image.png

我和Alan的路径不同,可是都得出了一样的结论。因而可知,这种矩阵分析的方式的确是对复杂业务进行分析的一把利器,业务场景越多,交叉关系越是复杂,越须要这样的分析。

组织阵型

生产关系决定生产力,对于一个管理者来讲,如何有效的设置组织结构是决定团队是否能高效协做的关键。因此咱们能够看到公司里面,每一年都有比较大的关于组织结构和人员安排的调整。

对于技术团队来讲,咱们习惯于按领域划分工做范围,这样作的好处是责任到人、职责清晰。然而,领域只是一个维度,咱们工做一般都是以项目的形式的开展,而项目一般是贯穿多个领域的。因此,在作团队组织规划的时候,咱们能够经过业务领域和业务项目两个维度去看。

好比,在我负责的商品团队,我会按照以下的形式去作职责划分。

image.png

时间维度

除了工做,生活中也处处可见多维思考的重要性。

好比,咱们说浪费可耻,应该把盘子舔的很干净,岂不知加上时间维度以后,你当前的舔盘,后面可能要耗费更多的资源和精力去减肥,反而会形成更大的浪费。

咱们说代码写的丑陋,是由于要“快速”支撑业务,加上时间维度以后,这种临时的妥协,换来的是意想不到的bug,线上故障,以及无止尽的996。

RFM模型

简单的思考是“点”状的,好比舔盘、代码堆砌就是当下的“点”;好一点的思考是“线”状,加上时间线以后,不难看出“点”是有问题的;再全面一些的思考是“面”(二维);更体系化的思考是“体”(三维);好比,RFM模型就是一个很不错的三维模型。惋惜的是,在表达上,咱们人类只能在二维的空间里去模拟三维,不然四维可能会更加有用。

image.png

复杂业务治理总结

在前言部分,我已经说过了,多维分析是对以前方法论的升级。加上之前的方法论,完整的方法论应该是“业务理解-->领域建模-->流程分解-->多维分析”。

为了方便你们理解,下面我把这些方法论作一个简单的串联和解释。

业务理解

理解业务是全部工做的起点。首先,咱们要找到业务的核心要素,理解核心概念,梳理业务流程。

好比,在零售通的商品域,咱们要知道什么是商品(Item),什么是单品(CSPU),什么是组合品(CombineItem)。在下单域,咱们要知道订单(order)的构成要素是商品、优惠、支付。在CRM领域,咱们要理解客户、机会、联系人、Leads等等。

这里,我想再次强调下语言的重要性,语言是咱们思考的载体,就像维特根斯坦说的:“凡是可以说的事情,都可以说清楚”。

你不该该放过任何一个模糊的业务概念,必定要透彻的理解它,并给与合理的命名(Ubiquitous Language)。惟有如此,咱们才能更加清晰的理解业务,才能更好的开展后续的工做。

领域建模

在软件设计中,模型是指实体,以及实体之间的联系,这里须要咱们具有良好的抽象能力。可以透过庞杂的表象,找到事务的本质核心。

再复杂的业务领域,其核心概念都不该该太复杂,抓住了核心,咱们就抓住了主线,业务每每都是围绕着这些核心实体展开的。

好比,商品域虽然很复杂,但其核心的领域模型,无外乎就以下图所示:
image.png

流程分解

关于流程分解,在《阿里高级技术专家方法论:如何写复杂业务代码?》里面已经有很是详细的阐述,这里就不赘述了。

简单来讲,流程分解就是对业务过程进行详细的分解,使用结构化的方法论(先演绎、后概括),最后造成一个金字塔结构。

好比,在商品领域,有建立商品、商品上架、上架审核、商品下架、下架审核、修改商品、删除商品等一些列动做(流程),每一个动做的背后都有很是复杂的业务逻辑。咱们须要对这些流程进行详细的梳理,而后按步骤进行分解。最后造成一个以下的金字塔结构:

image.png

多维分析

关于多维分析,我以二维的矩阵分析为例,我想我前面应该已经说清楚了。

业务的复杂性主要体如今流程的复杂性和多维度要素相互关联、依赖关系上,结构化思惟能够帮咱们梳理流程,而矩阵思惟能够帮忙咱们梳理、呈现多维度关联、依赖关系。两者结合,能够更加全面的展示复杂业务的全貌。从而让咱们的治理能够有的放矢、有章可循。

既然是方法论,在这里,我会尝试给出一个矩阵分析的框架。试想下,若是咱们的业务很简单,只有一个业务场景,没有分支流程。咱们的系统不会太复杂。之因此复杂,是由于各类业务场景互相叠加、依赖、影响。

所以,咱们在作矩阵分析的时候,纵轴能够选择使用业务场景,横轴是备选维度,能够是受场景影响的业务流程(如文章中的商品流程矩阵图),也能够是受场景影响的业务属性(如文章中的订单组成要素矩阵图),或者任何其它不一样性质的“东西”。

image.png

经过矩阵图,能够清晰的展示不一样场景下,业务的差别性。基于此,咱们能够定制知足差别性的最佳实现策略,多是多态扩展,多是分离的代码,也多是其它。

这就是矩阵分析的要义,其本质是一种多维度思考的方法论。

篇后寄语

最后,我想说世界是熵增的(即万物都在缓慢的分崩离析),控制复杂度是咱们这些从业者没法推卸的责任和使命。

软件行业的发展才几十年,仍是一门年轻的学科,软件工程就像一个刚学会走路的小孩,还很不成熟,有时还很幼稚。

但毕竟仍是有几十年的沉淀,仍是有一些好的方法和实践能够参考,个人这些总结沉淀只是在前人的基础上,多走了一点点而已。但就是这一点点,也实属来自不易,其中冷暖,只有本身能体会。能够说,这一路走来,是一场对心力、脑力和体力的持续考验。

image.png

  • 心力是指不将就的匠心,不妥协的决心,不知足的好奇心、以及不放弃的恒心。
  • 脑力是指那些必要的思惟能力、学习能力、思考能力、思辨能力。
  • 之因此说“业务理解-->领域建模-->流程分解-->多维分析”是体力,是由于实现它们就像是在作填空题,只要你愿意花时间,再复杂的业务均可以循序渐进的清晰起来。

梳理清晰了,再配合COLA(https://start.aliyun.com/)的指导,咱们就有可能写出清晰、易读的代码,就有可能从一个if-else coder升级为一个complexity conquer。

而这不正是咱们工程师孜孜不倦的追求吗?