大數據

複雜性應對之道——矩陣思維(多維度思考)

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的根源。以零售通的商品業務為例。不同的處理場景,其業務邏輯實現是有差異性的。如下圖所示,商品業務的差異性,主要體現在商品類型、銷售方式和倉儲方式的不同。
image.png

這三個維度上的差異組合起來,有2 3 2 = 12之多。這就是為什麼在老代碼中,到處可以看到if(組合品) blabla,if(贈品) blabla,if(實倉) blabla之類的代碼。

那麼,如何消除這些討厭的if-else呢,我們可以考慮以下兩種方式:

  1. 多態擴展:利用面向對象的多態特性,實現代碼的複用和擴展。
  2. 代碼分離:對不同的場景,使用不同的流程代碼實現。這樣很清晰,但是可維護性不好。

多態擴展

多態擴展可以有繼承和組合兩種方式。繼承勿用多言,組合有點像策略模式,也就是把需要擴展的部分封裝、抽象成需要被組合的對象,然後對其進行擴展,比如星環的能力擴展點就是這種方式。

這裡,我們舉一個繼承的例子,商品在上架的時候要檢查商品的狀態是否可售,普通商品(Item)檢查自己就好了,而組合商品(CombineItem)需要檢查每一個子商品。

用過程式編碼的方式,很容易就能寫出如下的代碼:

    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來處理差異,會更加合適。

而贈品上架的情況恰恰相反,它和其他商品的上架流程差異很大。反而不適合和他們合用一套流程代碼,因為這樣反而會增加他人的理解成本。還不如單獨起一個流程來的清晰。

個麼,問題來了,我們什麼時候要用多態來處理差異,什麼時候要用代碼分離來處理差異呢?

接下來,就是今天我要重點為你介紹的矩陣分析法

我們可以弄一個矩陣,縱列代表業務場景,橫列代表業務動作,裡面的內容代表在這個業務場景下的業務動作的詳細業務流程。對於我們的商品業務,我們可以得到如下的矩陣:

創建商品 上架商品 上架審核通過 上架審核拒絕
普通品 + 實倉 1. 檢查cspu狀態。
2. 檢查cspu圖片質量。
3. 檢查上架資質。
4. 檢查商品唯一性。
5. 檢查品牌唯一性。
6. 檢查價格信息。
7. 創建商品。
1. 檢查庫存。
2. 檢查可售狀態。
3. 檢查限購。
4. 檢查運費。
5. 檢查佣金。
6. 檢查活動衝突。
7. 設置銷售範圍。
8. 執行上架。
9. 發送上架消息。
1. 檢查商品狀態。
2. 檢查商家資質量。
3. 檢查控商小二權限。
4. 設置物流佣金。
5. 創建貨品。
6. 審核通過。
1. 拒絕審核
普通品 + 雲倉 同上 同上 同上 1. 拒絕審核
組合品 + 實倉 同上 同上 同上 1. 拒絕審核
組合品 + 雲倉 同上 同上 同上 1. 拒絕審核
贈品 1. 創建商品 1. 贈品上架 1. 審核通過 1. 拒絕審核
出清品 + 實倉 1. 創建商品。
2. 刷新庫存路由。
3. 商品打標。
出清品 + 雲倉

通過上面的矩陣分析,我們不難看出普通品和組合品可以複用同一套流程編排代碼,而贈品和出清品的業務相對簡單,更適合有一套獨立的編排代碼,這樣的代碼結構會更容易理解。

矩陣思維(多維度思考)

上面的案例不是我臆造出來的,而是我在和張文(我同事)討論應該用哪種方式去處理業務差異的真實故事。

我記得在和大學討論完,開車回去的路上,我一直在想這個問題,然後在第二個路口等紅燈的時候,突然有一個靈感冒出來。我抑制不住興奮,一邊開車,一邊發消息給張文說:“我想到了一個很NB的方法論,能解決在‘多態擴展’和‘代碼分離’之間如何做選擇的問題”。

其實,我知道我興奮的不僅僅是解決了這個問題。我興奮的是,我第一次真正領悟到了多維度思考的重要性。從而有機會從一個“單維度”生物,升級成一個“多維度”思考者。媽媽再也不用擔心我被那些思維層級高的人,進行“降維打擊”了。

結構化思維很有用,非常有用,只是它更多關注的是單向維度的事情。比如我要拆解業務流程,我要分解老闆給我的工作安排,我要梳理測試用例,都是單向維度的。

而矩陣分析是兩個維度的,當問題涉及的要素比較多,彼此關聯關係很複雜的時候,兩個維度肯定會比一個維度要來的清晰,這也是為什麼說矩陣思維是比結構化思維更高層次的思維方式

有了這些感悟,我開始系統的整理關於矩陣分析和多維度思考的資料,發現這種思維方式真是無處不在。

比如,用來對產品發展前景進行分析的波士頓矩陣。

image.png

又如,我之前在1688做交易下單業務的時候,有非常多的下單場景,每種場景下,買家享受的權益是不一樣的(如下表所示)。我們當時也是使用了矩陣去表達這個複雜的關係,只是當時還沒有想到要將其提升到方法論的高度。

UMP優惠 分階段付款 階梯團 信用卡 極速到賬 賬期支付 信用憑證 特定人群
普通訂單 Y Y Y Y Y Y Y
夥拼訂單 Y Y Y Y
加工訂單 Y Y Y
採購訂單 Y Y Y Y
自主訂單 Y Y
淘工廠訂單 Y Y Y
一元購訂單 Y
零售通訂單 Y Y

再比如,在數據分析中,維度分析是非常重要的,特別是維度很多的時候,我們可以通過皮爾遜積矩相關係數,做交叉分析,從而彌補獨立維度分析沒法發現的一些問題。

由此可見,這種矩陣分析的方式的確是對複雜業務進行分析的一把利器,業務場景越是多,交叉關聯關係越是複雜,越需要這樣的分析

除此之外,生活中也到處可見多維思考的重要性。

比如,我們說浪費可恥,應該把盤子舔的很乾淨,豈不知加上時間維度之後,你現在的舔盤,後面可能要耗費更多的資源和精力去減肥,反而造成更大的浪費。

我們說代碼寫的醜陋,是因為要“快速”支撐業務,加上時間維度之後,這種臨時的妥協,換來的是意想不到的bug,線上故障,以及無止盡的996。

簡單的思考是“點”狀的,比如舔盤、代碼堆砌就是當下的“點”;好一點的思考是“線”狀,加上時間線之後,不難看出“點”是有問題的;再全面一些的思考是“面”(二維);更體系化的思考是“體”(三維);比如,RFM模型就是一個很不錯的三維模型。

image.png

複雜業務治理總結

在前言部分,我已經說過了,矩陣分析是對之前方法論的升級。加上以前的方法論,完整的方法論應該是“業務理解-->領域建模-->流程分解-->矩陣分析”

再配合COLA架構,我有信心說,在征服複雜度這頭怪獸的路上,我們又向前邁進了一步。

為了方便大家理解,下面我把這些方法論做一個簡單的串聯和解釋。

業務理解

理解業務是所有工作的起點。首先,我們要找到業務的核心要素,理解核心概念,梳理業務流程。

比如,在零售通的商品域,我們要知道什麼是商品(Item),什麼是單品(CSPU),什麼是組合品(CombineItem)。在下單域,我們要知道訂單(order)的構成要素是商品、優惠、支付。在CRM領域,我們要理解客戶、機會、聯繫人、Leads等等。

這裡,我想再次強調下語言的重要性,語言是我們思考的載體,就像維特根斯坦說的:“凡是能夠說的事情,都能夠說清楚

你不應該放過任何一個模糊的業務概念,一定要透徹的理解它,並給與合理的命名(Ubiquitous Language)。唯有如此,我們才能更加清晰的理解業務,才能更好的開展後續的工作。

領域建模

在軟件設計中,模型是指實體,以及實體之間的聯繫,這裡需要我們具備良好的抽象能力。能夠透過龐雜的表象,找到事務的本質核心。

再複雜的業務領域,其核心概念都不應該太複雜,抓住了核心,我們就抓住了主線,業務往往都是圍繞著這些核心實體展開的。

比如,商品域雖然很複雜,但其核心的領域模型,無外乎就如下圖所示:

image.png

流程分解

關於流程分解,在《一文教會你如何寫複雜業務代碼》裡面已經有非常詳細的闡述,這裡就不贅述了。

簡單來說,流程分解就是對業務過程進行詳細的分解,使用結構化的方法論(先演繹、後歸納),最後形成一個金字塔結構

比如,在商品領域,有創建商品、商品上架、上架審核、商品下架、下架審核、修改商品、刪除商品等一些列動作(流程),每個動作的背後都有非常複雜的業務邏輯。我們需要對這些流程進行詳細的梳理,然後按步驟進行分解。最後形成一個如下的金字塔結構:

image.png

矩陣分析

關於矩陣分析,我想我前面應該已經說清楚了。

業務的複雜性主要體現在流程的複雜性和多維度要素相互關聯、依賴關係上,結構化思維可以幫我們梳理流程,而矩陣思維可以幫忙我們梳理、呈現多維度關聯、依賴關係。二者結合,可以更加全面的展現複雜業務的全貌。從而讓我們的治理可以有的放矢、有章可循。

既然是方法論,在這裡,我會嘗試給出一個矩陣分析的框架。試想下,如果我們的業務很簡單,只有一個業務場景,沒有分支流程。我們的系統不會太複雜。之所以複雜,是因為各種業務場景互相疊加、依賴、影響。

因此,我們在做矩陣分析的時候,縱軸可以選擇使用業務場景,橫軸是備選維度,可以是受場景影響的業務流程(如文章中的商品流程矩陣圖),也可以是受場景影響的業務屬性(如文章中的訂單組成要素矩陣圖),或者任何其它不同性質的“東西”。

image.png

通過矩陣圖,可以清晰的展現不同場景下,業務的差異性。基於此,我們可以定製滿足差異性的最佳實現策略,可能是多態擴展,可能是分離的代碼,也可能是其它。

這就是矩陣分析的要義,其本質是一種多維度思考的方法論

篇後寄語

最後,我想說世界是熵增的(即萬物都在緩慢的分崩離析),控制複雜度是我們這些從業者無法推卸的責任和使命。

軟件行業的發展才幾十年,還是一門年輕的學科,軟件工程就像一個剛學會走路的小孩,還很不成熟,有時還很幼稚

但畢竟還是有幾十年的沉澱,還是有一些好的方法和實踐可以參考,我的這些總結沉澱只是在前人的基礎上,多走了一點點而已。但就是這一點點,也實屬來自不易,其中冷暖,只有自己能體會。可以說,這一路走來,是一場對心力、腦力和體力的持續考驗。

image.png

  • 心力是指不將就的匠心,不妥協的好奇心,不放棄的恆心。
  • 腦力是指那些必要的思維能力、學習能力、思考能力、思辨能力。
  • 之所以說“業務理解-->領域建模-->流程分解-->矩陣分析”是體力,是因為實現它們就像是在做填空題,只要你願意花時間,再複雜的業務都可以按部就班的清晰起來

梳理清晰了,再配合COLA(https://start.aliyun.com/bootstrap.html) 的指導,我們就有可能寫出清晰、易讀的代碼,就有可能從一個if-else coder升級為一個complexity conquer。

而這不正是我們工程師孜孜不倦的追求嗎?

Leave a Reply

Your email address will not be published. Required fields are marked *