针对不同场景下的业务差异,我们常常习惯性的使用if-else来实现不同的业务逻辑,久而久之代码变得越来越难维护。那么如何消除这些if-else呢?面对复杂的业务如何思考和分析?本文分享了阿里巴巴高级技术专家张建飞(Frank)关于复杂业务治理的方法论,并介绍了一种多维度分析问题的方法:矩阵分析。这篇文章是我在《阿里高级技术专家方法论:如何写复杂业务代码?》说的“自上而下的结构化分解+自下而上的抽象建模”方法论的升级。因为在之前的方法论中,我们缺乏多维度的视角,这种维度思维的缺乏可能会导致遗漏一些重要的业务信息,从而使我们在制定软件设计策略时遇到困难。有了维度思维,我们可以更全面地看到业务的全貌,更全面地掌握业务信息,从而帮助我们更系统地管理复杂性。说到if-else,我经常说,我们不要做一个if-else编码器。这里的if-else不是说我们编码的时候不能用if-else,而是说我们不应该简单的用if-else来实现业务的分支流程,因为这样乱码堆砌很容易堆起“狗屎山””。业务上的差异是if-else的根源。以零售快递的商品业务为例。不同的处理场景有不同的业务逻辑实现。如下图所示,商品业务的差异主要体现在商品种类、销售方式和仓储方式的差异。结合起来,这三个维度的差异总计为2*3*2=12。这就是为什么在旧代码中,您可以看到if(combination)blabla、if(gift)blabla、if(realposition)blabla等代码到处。那么,如何消除这些烦人的if-else呢?可以考虑以下两种方式:多态扩展:利用面向对象的多态特性,实现代码的重用和扩展。代码分离:针对不同的场景使用不同的流程代码。这很清楚,但不可维护。多态扩展多态扩展可以通过两种方式实现:继承和组合。继承就不用多说了,组合有点像策略模式,就是把需要扩展的部分封装抽象成需要组合的对象,然后再扩展。比如星环的能力延伸点就是这样。这里,我们举一个继承的例子。当产品上架时,需要查看产品的状态是否为可售。普通项(Item)检查自己就够了,但是对于组合项(CombineItem)需要检查每个子项。使用过程编码,很容易写出如下代码:();childItems.forEach(childItem->childItem.isSellable());//省略异常处理}}但是这种实现并不优雅,不符合OCP,缺乏业务语义的显式表达。更好的做法是,我们可以通过模型明确表达CombineItem和Item之间的关系。这样,一方面,模型正确反映了实体关系,更加清晰。另一方面,我们可以使用多态来处理CombineItem和Item的区别,具有更好的扩展性。重构后代码会变成:publicvoidcheckSellable(Itemitem){if(!item.isSellable()){thrownewBizException("商品状态为非销售,不能上架");}}代码分离所谓代码分离,就是针对不同的业务场景,我们用不同的编排代码进行分离。以上架商品为例,我们可以这样写:/***1.普通上架商品*/publicvoiditemOnSale(){checkItemStock();//查看库存checkItemSellable();//查看销售状态checkItemPurchaseLimit();//查询采购限额checkItemFreight();//查询运费checkItemCommission();//查询佣金checkItemActivityConflict();//查询活动冲突generateCspuGroupNo();//生成单品组号publishItem();//发布商品}/***2.组合商品上架*/publicvoidcombineItemOnSale(){checkCombineItemStock();//查看库存checkCombineItemSellable();//查看销售状态checkCombineItemPurchaseLimit();//查询采购限额checkCombineItemFreight();//查询运费checkCombineItemCommission();//查询佣金checkCombineItemActivityConflict();//查询活动冲突generateCspuGroupNo();//生成单品组号publishCombineItem();//发布商品}/***3.礼物上架*/publicvoidgiftItemOnSale(){checkGiftItemSellable();//查询可售状态publishGiftItem();//发布商品}这样当然可以了也可以去掉if-else,独立于每个ot她,清楚。但是可重用性是一个问题。如果细心多维分析,你可能已经发现,在上述案例中,普通产品和组合产品的业务流程基本相同。如果使用两套布局代码,就有点多余了。这种重复会不利于后期代码的维护,还会出现shotgun修改的问题(一个业务逻辑需要修改多处)。一个极端的情况是,如果普通产品和组合产品,只有checkSellable()不同,其他都一样。毫无疑问,我们使用多态(继承)的CombineItem和Item来处理差异会更合适。另一方面,赠品的情况正好相反,上架的过程与其他产品有很大的不同。相反,不宜与他们共享一套流程代码,因为这会增加其他人的理解成本。它不像一个单独的过程那么清楚。那么,问题来了,什么时候用多态来处理差异,什么时候用代码分离来处理差异呢?接下来就是今天要给大家介绍的多维分析问题的方法论之一:矩阵分析。我们可以做一个矩阵,列代表业务场景,行代表业务动作,里面的内容代表这个业务场景下业务动作的详细业务流程。对于我们的商品业务,可以得到如下矩阵:通过上面的矩阵分析不难看出,普通商品和组合商品可以复用同一套流程编程代码,而赠品和清仓商品的业务则相对简单更适合有一套独立的编排代码,这样代码结构会更容易理解。多维思维的重要性以上案例不是我杜撰的,而是我和张文(我的同事)讨论应该用什么方法来处理业务差异的真实故事。记得当时和大学商量后开车回去,一直在想这个问题,然后在第二个路口等红灯的时候突然灵机一动。我抑制不住兴奋的心情,边开车边给张文发信息说:“我想到了一个很NB的方法论,可以解决‘多态扩展’和‘代码分离’之间如何取舍的问题”.事实上,我知道我对解决这个问题感到兴奋。令我兴奋的是,我第一次真正体会到多维度思考的重要性。于是,就有了从“单维”生物升级为“多维”思考者的机会。妈妈再也不用担心我被“降维打击”了:)结构化思维很有用,非常有用,非常有用,只是更侧重于一维的东西。比如我要拆解业务流程,我要分解老板给我的工作安排,我要梳理测试用例,这些都是一维的。复杂度通常不仅仅是一个维度的复杂度,而是多个维度的交叉复杂度。当一个问题涉及的要素较多,相互关系复杂时,二维肯定会比一个维度清晰,这就是为什么矩阵思维是比结构化思维更高级的思维方式。其实从汉语词汇中不难看出,一个人的思维水平与他的思维维度是正相关的。当我们说这个人很“轴”、“一维”时,其实是在说他只有一维的线性思维。因此,观察事物的角度越多,维度越丰富,思维层次就越高。无处不在的多维思维带着这些感悟,我开始系统地整理多维思维的资料并进行分析,发现这种思维方式真的是无处不在。我发现的越多,我就越觉得我直到现在才意识到为什么这么重要的思维方式。波士顿矩阵比如在做产品分析的时候,有一个分析产品发展前景的波士顿矩阵。下单因素分析当年在1688做交易下单业务的时候,下单场景非常多。在每种情况下,买家享有不同的权益(如下表所示)。我们当时也用矩阵来表达这种复杂的关系,但当时并没有想到把它上升到方法论的层面。数据交叉分析在数据分析中,维度分析是非常重要的,尤其是维度比较多的时候,我们可以使用Pearson积矩相关系数来做交叉分析,以弥补一些独立维度发现不了的问题分析。简单相关系数矩阵分析矩阵最近偶然看到AlanShalloway写的《设计模式解析:Design Patterns Explained》。这是一本关于OOP的非常经典的书。其中第十六章专门介绍“分析矩阵”。作者创建这个方法论的初衷也是因为业务涉及的要素太多,信息量太大。他需要一种新的方式来组织海量数据。艾伦和我走了不同的路,但都得出了相同的结论。可见,这种矩阵分析法确实是分析复杂业务的利器。业务场景越多,交叉关系越复杂,就越需要这样的分析。组织形成生产关系决定生产力。对于管理者而言,如何有效地设置组织架构,是决定团队能否高效协作的关键。所以我们可以看到,在公司内部,每年在组织架构和人事安排上都会有比较大的调整。对于技术团队,我们习惯于按领域来划分工作范围。这样做的好处是责任到人,职责明确。但是,领域只是一个维度,我们的工作通常是以项目的形式开展的,而项目通常贯穿多个领域。因此,我们在做团队组织规划时,可以从业务领域和业务项目两个维度来看。比如我负责的商品组,我会按照下面的形式进行职责分工。时间维度除了工作,生活中处处可见多维思维的重要性。比如我们说浪费是可耻的,应该把盘子舔得很干净。加上时间维度后,你现在的舔舔可能会消耗更多的资源和精力去减肥,造成更大的浪费。我们说代码写得丑,是因为要“快”支撑业务。加上时间维度后,这种暂时的妥协导致了意想不到的bug,上线失败,无休止的996。RFM模型的简单思路就是“点”的形状。比如舔盘、堆码,都是当前的“积分”;更好的思维是“线”的形状。加上时间线后,不难看出“点”是有问题的,更全面的思考是“面”(二维);更系统的思考是“体”(三维);例如,RFM模型是一个非常好的三维模型。遗憾的是,在表达上,我们人类只能在二维空间模拟三维,否则四维可能更有用。复杂业务治理的总结在序言中。我已经说过,多维分析是对以前方法论的升级。除了前面的方法论,完整的方法论应该是“业务理解-->领域建模-->流程分解-->多维分析”。为了方便大家的理解,下面我将这些方法论做一个简单的系列和解释。业务理解理解业务是一切工作的出发点。首先,我们需要找到业务的核心要素,理解核心概念,梳理业务流程。比如在RetailLink的产品域中,我们需要知道什么是单品(Item),什么是单品(CSPU),什么是组合品(CombineItem)。在订单字段中,我们需要知道订单的组成部分是商品、折扣和付款。在CRM领域,我们需要了解客户、机会、联系人、线索等。在这里,我想再次强调一下语言的重要性。语言是我们思维的载体,正如维特根斯坦所说:“凡是能说的,都能说清楚”。任何模糊的商业概念都不能放过,一定要理解透彻,并给它起一个合理的名字(UbiquitousLanguage)。只有这样,才能更清楚地了解业务,更好地开展后续工作。DomainModeling在软件设计中,模型指的是实体以及实体之间的联系,这就需要我们有很好的抽象能力。能够透过复杂的表象,找到事物的本质内核。再复杂的业务领域,核心概念也不应该太复杂。抓住了核心,就抓住了主线。业务通常围绕这些核心实体展开。例如,商品领域虽然很复杂,但其核心领域模型无非如下图所示:流程分解流程分解在《阿里高级技术专家方法论:如何写复杂业务代码?》中已经有详细讲解,这里不再赘述.简单来说,流程分解就是将业务流程进行细化分解,采用结构化的方法论(先演绎,再归纳),最终形成一个金字塔结构。比如在商品领域,有创建商品、上架、审核上架、下架、审核、修改、删除等一系列动作(流程),etc.每个动作背后都有非常复杂的业务逻辑。我们需要详细梳理这些流程,然后一步步分解。最后形成一个金字塔结构如下:多维分析关于多维分析,我以二维矩阵分析为例,我想我早该说清楚了。业务的复杂性主要体现在流程的复杂性和多维要素的相互关系和依赖性上。结构思维可以帮助我们梳理流程,而矩阵思维可以帮助我们梳理和呈现多维度的相关性和依赖性。两者结合可以更全面地展现复杂业务的全貌。让我们的治理有的放矢、有章可循。既然是方法论,那么在这里,我就尽量给出一个矩阵分析的框架。试想一下,如果我们的业务很简单,只有一个业务场景,没有分支流程。我们的系统不会太复杂。之所以复杂,是因为各种业务场景相互重叠、相互依赖、相互影响。所以我们在做矩阵分析的时候,纵轴可以选择使用业务场景,横轴是备选维度,可以是场景影响的业务流程(比如文章中的商品流程矩阵),或者它可能会受到场景业务属性(例如文章中的订单组件矩阵)或任何其他不同性质的“事物”的影响。通过矩阵图,可以清晰的展示出不同场景下的业务差异。基于此,我们可以定制最佳的实现策略来满足差异性,可能是多态扩展、分离代码或其他。这就是矩阵分析的本质,它本质上是一种多维度的思维方法论。在后记的最后,我想说,世界正在增加熵(即一切都在慢慢分崩离析),控制复杂性是我们从业者不可推卸的责任和使命。软件行业才出现了几十年,还是一门年轻的学科。软件工程就像一个蹒跚学步的孩子,还很不成熟,有时还很天真。但毕竟还有几十年的沉淀,还是有一些好的方法和做法可以借鉴的。我的这些总结只是在前人的基础上多了一点点。但就是这一点,也不是那么容易得来的。其中,只有你能体会冷暖。可以说,这段旅程是对脑力、脑力和体力的不断考验。意念力是指不妥协的聪明才智、不妥协的决心、不满足的好奇心、不放弃的毅力。脑力是指必备的思维能力、学习能力、思维能力、思辨能力。之所以说“业务理解-->领域建模-->流程分解-->多维分析”是体力,因为实施起来就像填空一样,只要肯花时间,不管业务有多复杂一步一步搞清楚。整理清楚之后,在COLA(https://start.aliyun.com/)的指导下,我们可以写出清晰易读的代码,并且有可能从if-elsecoder升级到a复杂的征服者。而这不正是我们的工程师孜孜不倦追求的吗?
