1. 程式人生 > >從分而治之的思想到架構的設計

從分而治之的思想到架構的設計

辛巴當上了國王,他究竟要怎樣才能管理好它的王國?

 

 

 

分治與總量控制

在上一篇文章裡,我們得到兩個資訊:

  • 人類大腦的資訊實時處理能力存在上限

  • 軟體系統的複雜度遠超人類大腦的複雜度處理上限

從而引出了人類解決大規模複雜問題的根本方法

  1. 分而治之

然而分而治之的需要基於一個前提進行

  1. 複雜度總量控制

因為絕大多數人類參與的問題中,分而治之都會引入額外的彙總求解成本。要儘量減少複雜度,有兩個方面的思想層次的指導:

  • 子問題在人類可控範圍內儘量的大

  • 每個子問題要高內聚,問題間要低耦合

子問題在人類可控範圍內儘量大,則有助於減少子問題數量,因而減少合併處理子問題的成本。同時子問題不超過一個人處理能力的上限是因為,一旦超過一個人的能力範圍,要多人合作解決同一個平面上問題的不同部分時,必然會因合作之間溝通等原因降低整體開發效率(1 + 1 < 2)

高內聚、低耦合是一體,但從不同角度描述的理念。高內聚,即所需完成的事情所需的資源在內部通過較少的代價即可取得,低耦合則是指代 輸出給外部、或者從外部輸入的資源儘可能的小。

在存在關聯的子問題中,耦合並不可能消除。耦合這個詞可能大多數程式設計師聽起來有有點不好的意味,但耦合存在另外一箇中性的名字——介面。所以,產生了一個介面就產生了一個耦合,介面引數越多,耦合就越強。

以上是對之前文章的回顧,本文後續將基於個人理解,講述 分治的隔離級別,如何識別要拆分的點,以及解決如何拆的問題。

分治的隔離級別

分治時,拆分軟體系統的形式很多,可以拆分成:

  • 不同的方法

  • 不同的類

  • 不同Package

  • 不同的moudule

  • 不同的jar

  • 不同的程序(微服務)

  • 不同的系統

以上形態拆分形式的的隔離級別越來越大(當然,還存在其他不同的拆分形式和粒度),我們又要如何選擇拆分的形態呢?

這需要看我們當前對業務的理解程度。隔離級別越大,說明打通隔離越困難,修改調整越困難,但同時也意味著設計的架構越容易被遵守。因此當你堅信架構劃分正確,不同部分需要儘可能分割時,可以採用基於程序甚至更高級別的隔離。

但對於業務變化依然很大,邊界不清晰,難以把握全域性時,採用不同程序隔離或者不同jar的隔離可能就不合適,因為架構依然可能還需要調整,如果已經拆分了程序、jar那麼架構調整的難度將大幅增加。若不調整架構並要相容之前架構未作適配的業務的話,那隻能大幅增加模組間的耦合(介面)

因此個人覺得大規模的拆分,最理想的情況下,應該是在同一個jar的粒度下,先做好基於module/pakcage的拆分及重構,然後穩定執行一定程度後,如果仍然覺得有拆分的必要的話,再將其進行jar、程序級別的隔離。

當然上述的做法可能難以奏效,因為我們幹很多事情都是運動式的,上面的做法看起來只是個內部重構,“很簡單”,因此也難以爭取到相關的時間、人力等資源來進行,因而我們的重構也許只能通過一次性“搞個大”的,冒著更高的開發風險與頂著可能不完善的架構來進行。

識別需要進行拆分的點

我們在寫程式碼時,可能經常會遇到以下現象:

  • 一個方法長度過百行,甚至達到一千行 閱讀者無法理解整個方法幹了什麼

  • 一個方法的引數超過7、8個甚至上十個,使用者不知道應該如何使用方法

  • 一個類注入上十個外部資源,進行單元測試時Mock操作時代價極大

  • 一個將來可能會變更的介面定義被外部直接引用呼叫數十遍,需要修改時難以下手,也不敢下手

  • 一個目錄/package內放了數十上百個資源,當需要查詢某個資源時,費眼神,費時間

  • 一個系統顯式依賴於數十個上游系統,協調統籌這些系統工作,上游任意一個系統發生故障導致下游的bug產生時,都需要該系統排查及聯絡解決

  • 一個系統包含的邏輯很多,因此修改經常很多,一部分修改了,導致很大其他一部分要一起跟著測試、停機上線

  • ......

以上現象引起的問題的本質是,一個節點下直接對應的子節點過多,導致問題的規模超過了一個人的認知處理能力,從而需要多人在同一層級協作開發,而每個人都是一個內聚的個體,合作將會由於溝通、知識背景等原因而導致整體開發效率下降。上面舉例的都是程式設計中的例子,實際上你可以推演到我們工作生活的方方面面。

因此,當一個正常的人因為問題的規模原因,而難以理解問題自身時,我們就需要對問題進行分解。

這就是所需拆分的點。

 

 

 

如何進行拆分

要進行拆分的話,我們首先要識別什麼是內聚的。

 

 

 

插入圖上一篇文章的圖,假設這是從業務邏輯上看問題域的形狀。那麼中間那根高內聚低耦合的線是能夠以最小的耦合切分兩個問題域的。

但軟體系統不是平面,是多維立體的,不同維度看到的形狀不一樣,這根線在其他維度的投影,如在組織架構上看,可能就不是最優的。

因此我們可以知道,一個軟體系統的內聚並不是簡單一維的,從不同角度觀察得到的內聚拆分結果 很可能是一致的,也有可能是正交的,但也有可能是非正交得此失彼的。

那看待軟體系統的內聚,可以有哪些視角(維度)呢?

它至少包含以下內容:

  • 業務邏輯視角

    • 業務流程視角

    • 業務穩定程度視角

    • 業務關注點視角

    • 業務物件視角

    • 業務契約視角

  • 效能視角

    • 計算效率視角

    • 儲存完整視角

    • 網路通訊效率視角

  • 人員組織架構視角

    • 溝通協調成本視角

    • 權責利益視角

  • 系統安全視角

  • ......

由於篇幅原因,本文僅討論在業務邏輯視角下的邏輯拆分。

業務流程視角

我們的業務流程通常會分段組成,如

  • 客戶註冊

  • 客戶登入

  • 客戶瀏覽

  • 客戶交易

  • 訂單派送/執行

  • 訂單後評價

而這些流程是業務領域的術語,其天然就是內聚的,若不是內聚的,則會在真實世界裡階段與階段間出現過多的耦合,導致效率變慢。

因此我們很大程度上可以參考實際業務的流程來劃分應用程式的流程,畢竟軟體是對真實世界的模擬與輔助。(至於軟體反向協助業務完善其執行邏輯及抽象,則是另外一個故事了,我們這裡不談)

因此在分段的業務流程中,我們可以將 註冊登入 作為一個元件,而後續的瀏覽交易等內容可以作為一個元件。當然後續業務繼續變得複雜時,我們還可以根據上面討論的繼續拆分,把瀏覽、交易繼續分開。

業務穩定程度視角

大多數業務都存在一些易變的邏輯,也存在一些不穩定的邏輯,按照邏輯穩定程度我們可以對其分層:

  • 渠道邏輯

  • 應用邏輯

  • 共享服務中心邏輯

  • 技術框架邏輯

  • 物理資源管控邏輯

(以上僅為其中一種分層結構,可以有其他形式的分層)

我們借用 阿里巴巴 謝純良 分享的一幅圖

 

 

 

 

上面這幅圖可以體現的是通用穩定邏輯的下沉,達到所謂中臺火力支援的效果。中臺的核心理念其實是特別地樸素的,但任何事情體量大了,實施起來就會特別難。並且中臺也不僅僅是軟體系統的事情,還涉及組織架構等方面內容。

業務關注點視角

在我們購買一件商品進行交易的過程中,實際上有很多邏輯進行了處理:

  • 主交易流程的關注點:放入購物車,下訂單,支付,發貨等等

  • 積分相關係統的關注點:交易成功後要根據交易金額加積分,如果最終交易回滾了我們要回滾積分

  • 營銷系統相關的關注點:這個交易是否從我們的我們的營銷活動進入,我們的營銷活動是否促成了這筆交易

  • 庫存系統的關注點:這筆訂單我應該在什麼時候凍結庫存,這筆訂單我應該在什麼時候釋放庫存,這筆訂單我應該在什麼時候扣減庫存,我應該扣哪個倉儲的庫存

  • ......

如果把諸如此類的邏輯拍平放到一個平面的邏輯中,那麼理解將會特別地困難,

  • 一個原因是程式碼將會變得特別地長

  • 另外一個原因理解這個程式碼將需要掌握更多方面的業務知識

  • 還有就是程式設計師的思維需要在不同的業務上下文中切換,這也會增加理解的難度。

但我們能經常看到程式碼並沒有按照關注點分離,而是將所有的邏輯都堆到一起。

對於此類程式碼,在方法複雜度已經超越了一個人的理解能力範圍後,則需要對其按照業務關注點分離。主流程不應該感知各類分支流程的存在,主流程就應該只關注主流程視角的業務,如 交易主流程 只看 購物車、下訂單、支付、發貨等,而依附於該主流程的 積分、營銷等則不應該在主流程中顯式出現(可以通過訊息,類SPI等方式隱式聯動),因為這會加重主流程的認知負擔。

 

 

業務物件視角

在我們的業務系統裡,經常喜歡使用事務指令碼的模式(有另外一個帶偏見的名字——貧血模型)的模式來進行開發。這種模式是一種簡單高效的模式,直到業務進化到特別複雜的程度。

為什麼事務指令碼不適用於特別複雜的邏輯?其實這就是我們本文一直討論的問題,太複雜從而一個人難以把控。事務指令碼以資料庫記錄為粒度來組織邏輯,並且通常不同型別的資料庫記錄之間缺少封裝隔離,因此眾多型別的記錄以及其相互作用關係在會同一個層級裡直接暴露到一個人眼前。這就相當於一個圖書館裡的書,沒有按分類放到不同區域的不同書架上,而是直接一堆堆到了你面前一樣。

因此我們在應對複雜邏輯的編碼時,需要更高層級的封裝,就像要整理圖書,將圖書劃分到不同區域一樣,這樣我們才能理清圖書館的書的分佈,從而找到對應的書。

那如何歸類封裝我們系統裡的資料記錄以及相關的資料處理邏輯呢?

前人已經給了我們答案,那就是面向物件程式設計(並不是用了面向物件程式語言就是在寫著面向物件的程式碼)。面向物件程式語言提供了我們實現基於面向物件思想系統的必要支援,接下來的就是看我們怎麼使用語言的特性了。我們應該如何高內聚低耦合地劃分物件呢?

領域驅動設計的思想給我們提供了方法論支援,其建議程式碼裡的物件實現要與我們的業務模型設計繫結,而業務模型的設計需要開發與業務一起完成。在一起完成的過程中:

  • 業務告知開發其希望達到效果

  • 開發從業務的陳述過程中,領悟業務的思維模式

  • 開發將業務思維模式裡的概念表達於物件模型中

  • 開發將設計的物件模型與業務一起走查各種業務場景以持續完善模型

  • 在完善模型設計的過程中開發甚至能完善業務腦海中不合適不恰當的思維

如此一來,我們得到的物件設計模型就是與業務領域視角一致的,在開發時我們繫結模型與實現,以物件模型驅動開發,這就是我所理解的領域驅動設計一詞的含義。

 

 

(實際上領域驅動設計一書中帶來的一套面向物件分層方法論在缺失上面的開發、業務互動設計模型的過程也能使用)

業務契約視角

上面的視角都是從如何識別內聚角度出發的,而業務契約視角則是從維護耦合不變的角度出發的。

契約即介面、即耦合。

我們要有了內聚的模組,才能決定耦合的介面,但一旦耦合的介面確定後,我們不應該輕易變動介面,應該儘可能地將邏輯劃分的耦合的一端來處理。

舉個大家都很熟悉的例子,就是組裝臺式電腦。我們組裝電腦的時候,不同廠商的不同 型號部件(CPU、顯示卡、記憶體、硬碟等等)大部分都可以組裝到不同廠商的不同型號主機板裡。而之所以相容性這麼好,主機板和各個零部件都能各自獨立演化,這與固定的介面標準有莫大的關係。

我們軟體也一樣,如果可以的話,儘量保持介面不變,邏輯儘量歸納到介面支援範圍內,而不是去突破介面。

 

 

(左中右三類部件只要契約/介面不變,其就能各自獨立的演化)

架構的設計

什麼是架構呢?

架構是一種設計,一種指導設計的設計,一種為了達到某種目的、效果而對子設計進行限制和規範的設計。

其通常表現為

  1. 在一個整體下,劃分不同的元件,規劃限定不同元件的職責,設定不同元件的協作形式

架構可大可小,有不同的層級。

  • 指導不同類之間協作關係的是架構

  • 指導不同模組之間協作關係的是架構

  • 指導不同系統之間協作關係的是架構

  • 知道人與人之間協作關係的也是架構(組織架構)

所以大家看出來了沒有?我們又回到了分治與高內聚低耦合的思想。只要我們高內聚低耦合的心法熟記於心,我們至少是自己寫的程式碼方法的架構(當然,如果你說我的方法不拆分,一次幾百行擼到底那我無F可說)。

由高內聚低耦合內功心法演化而出來的外功五花八門,我們很容易就陷入到了各種神奇的商業名詞中難以自拔,如 微服務、中臺、雲原生、FAAS等等概念中。但如果我們融會貫通了內功之後,我們可以看到,上面的種種名詞僅僅是在解決特定問題時,用分治的方法加以高內聚低耦合指導得到的其中一種解決方案。

我們收斂一下,上面主要談了在業務邏輯層次如何識別內聚,如何進行拆分,這些實際上是指導我們進行業務邏輯方面架構的方法論,你能很好地運用這些方法時,我們的架構能力就從方法層級至少提到了到模組級。

當然作為一名優秀的架構師,業務邏輯並不是其唯一考慮的方面,我們之前就已經說了,內聚的維度有很多,因此,一個合理設計的軟體系統,還要考慮組織架構內聚、效能內聚等等方面的因素。

總結

  • 人類的認知處理能力有上限,因此對於超過處理上限的問題人類必須對其進行分治處理

  • 而拆分大多數情況下是存在合併消耗的,會使得總體問題變得更復雜

  • 因此我們要控制複雜度的總量,使其總複雜度儘可能少

  • 控制複雜度總量其中一個方面是在人類可控範圍內子問題儘可能的大,以減少合併處理問題的消耗

  • 控制複雜度總量的另外一個方面是合理拆分,使得合併損耗最小

  • 合理拆分的指導思想就是高內聚低耦合

  • 高內聚低耦合從不同的角度去看可能得到的結果可能是不一樣的

  • 從業務邏輯角度實現高內聚低耦合的形式有

    • 按業務流程拆分

    • 按業務穩定程度拆分

    • 按業務關注點拆分

    • 按業務物件視角拆分

    • 按業務契約視角拆分

  • 掌握了合理的拆分技巧,你就是你自己的架構師

     

關於作者

在兩家排名前三的股份制商業銀行及互金創業公司工作過,目前在一家網際網路銀行工作。做過業務,做過中介軟體,做過架構,也帶過小團隊。

歡迎添加個人微信skyesx探討技術相關問題,請備註名字+公司,謝謝。

 

微訊號

 

公眾號

 

 

 

 

(所以辛巴要如何管理它的王國?我想它應該要先學學程式設計吧?