都說軟體架構要分層、分模組,具體應該怎麼做(二)

sewain發表於2021-03-13

道哥的第 028 篇原創

一、前言

在上一篇文章中,我們主要聊了:在嵌入式系統的應用程式架構設計中,應該從哪些方面來進行需求整理和分析,文章連結:都說軟體架構要分層、分模組,具體應該怎麼做(一)

這篇文章,我們繼續聊一下在概要設計、詳細設計階段,我們應該做什麼工作?用什麼工具或手段來做?輸出結果是什麼?

按照慣例,為了內容描述的方便,我會用一個物聯網閘道器的設計過程,把所有的內容串接在一起。如果小夥伴對於閘道器不太瞭解,請滑到文章底部的推薦閱讀列表,其中有幾篇文章是關於閘道器功能介紹的。

二、需求調研和需求分析

1. 用例圖

上篇文章說到,在進行需求調研和需求分析的時候,用例圖是非常非常好用的一個工具。通過用例圖,我們可以把一個系統中需要完成的所有功能,從粗粒度上一目瞭然的呈現出來。

下面這張圖,是閘道器的用例圖(這裡畫的用例還不完全):

2. 用例描述

用例圖僅僅是描述了系統具有的功能,但是並沒有描述每一個用例的行為,也就是執行過程。

在上一篇文章中說到,我們不需要對每一個用例進行分析,而是需要在這些用例中,找出那些關鍵用例,然後對這些關鍵用例寫出用例描述,因為關鍵用例才是系統架構的決定因素。

那麼又出現一個問題了:如果把所有的用例,按照重要程度進行優先順序排序,那麼從上到下應該選取多少個、或者說百分之多少的關鍵用例呢?這個就要看整個系統的複雜度了,30%不嫌少,50%不嫌多,根據你的時間自由把握。

以上圖閘道器中的用例圖來說,我認為:新增裝置、刪除裝置、控制裝置、規則配置、規則觸發這幾個用例比較關鍵,因此,我就針對這幾個用例寫用例描述。

(1)新增裝置用例描述

其中有 2 點注意的地方:

  1. 在事件流中,我們是把閘道器作為一個黑盒進行描述的,因為我們是在進行需求分析,而不是在進行設計,因此,不需要考慮閘道器內部的執行流程;
  2. 紅色部分都是一個執行主體,這個主體可以是一個人、一個介面、一個裝置、一個系統等等;

事件流可以用文字來描述(就像圖中這樣),也可以畫一個序列圖來展現這個過程,就像下面這樣(這裡沒有詳細描述出更細的執行過程,主要以示意性為主):

(2) 刪除裝置用例描述

(3) 控制裝置用例描述

(4) 規則配置用例描述

(5) 規則觸發用例描述

三、概要設計

可以把概要設計理解成一個粗略、抽象的架構圖,用來體現高層元件,以及它們之間的聯絡。那麼應該怎麼做,才能得到這樣的一張架構圖呢?

我們現在的掌握的材料就是:用例圖和(關鍵用例的)用例描述,而且在用例描述的基本事件流中,把要設計的系統當做一個黑盒子進行描述。

現在我們需要做的事情,就是開啟這個黑盒子,進入其中內部,從執行過程上來分析:需要哪些模組完成什麼動作

注意,這是我們的目的。要達成這個目的,使用魯棒圖這個工具。

也就是說,我們現在需要通過魯棒圖這個工具,去拆解用例描述中的事件流,把系統內部的、為了完成這個用例所需要的參與元素,全部都找出來,並標註它們之間的關係

1. 針對關鍵用例的用例描述,畫出魯棒圖

先說一下容易混淆的概念:魯棒性,也稱作健壯性,是指程式在執行過程中,即使出現了一些錯誤的狀況,也已讓能夠順利的執行下去。它描述的是程式的容錯性

魯棒圖是指:用圖形建模的方式,來描述一個用例描述是否正確、是否完善

主要通過 3 種元素:邊界物件,控制物件和實體物件,來畫出一個用例描述中,待設計的系統內部各功能模組之間的互動關係。

  1. 邊界物件:在系統內部,需要與外界進行互動的元素。它負責接收外部的輸入、向外部輸出內部的處理結果;
  2. 控制物件:描述動態的控制行為,強調從一個執行環節進入另一個執行環節;
  3. 實體物件:對一個資訊內容進行描述,比如:閘道器中的一個裝置描述資訊、一條規則配置資訊等;

關於邊界物件,在 Web 類專案中,可能比較好理解,就是與使用者、外部系統所互動的介面。但是在嵌入式系統中,大部分情況下是沒有介面的,但是我們只要抓住一個根本的東西:接收外部的輸入、向外部輸出資料

我們這裡就簡單畫一下新增裝置、控制裝置和規則觸發,這 3 個用例描述對應的魯棒圖(先忽略這幾張圖中的顏色):

新增裝置:

控制裝置:

規則觸發:

關於新增規則的執行過程中,大部分工作是在手機 APP 上完成的(選擇源裝置--觸發條件--目標裝置),閘道器中只是把配置好的這條規則儲存一下而已,沒有其他過多的操作。

規則中更重要的部分是規則觸發的處理,例如:當紅外裝置(源裝置)檢測到人體時,如果當前處於佈防狀態(觸發條件),就啟動聲光個報警器(目標裝置),因此下面這張圖是描述執行一條規則的執行過程,這個過程的執行鏈條比較長,能把很多的模組串接起來。

2. 對魯棒圖中的模組進行歸類,歸納出子系統

假設我們現在把所有關鍵用例的魯棒圖都畫出來了,下一步的動作就是對這些模組進行分類。上面幾張圖中,有些模組被標記了不同的顏色,相同的顏色表示它們是屬於一類的。

  1. 黃色部分的模組都是與無線通訊相關的,那麼這些模組就可以歸類為無線通訊管理子系統;
  2. 綠色部分的模組都是與裝置相關的,那麼它們就歸類為裝置管理子系統;
  3. 藍色部分的模組都是與規則相關的,那麼它們就歸類為規則管理子系統;
  4. 繼續找出其他的子系統。。。

最終,我們把這些子系統(或者稱之為功能組)畫到一張圖中如下:

這張圖就從上層元件的視角,把整個系統劃分為幾個子系統,每一個子系統都是一個獨立的、可以交付的實體模組。

這張圖的作用還是挺大的,可以用於向領導進行彙報(領導才沒有時間看詳細的設計),也可以用於產品說明書中的技術架構描述部分,還可以用於團隊成員分工,因為每一部分都是一個獨立的單位,與其他子系統之間的耦合性,從靜態和動態兩方面都隔離開來了(待會在後面的開發架構設計中進行說明)。

這些子系統之間是需要通訊的,因此,在畫出這個設計圖之後,我們還需要做出下面的幾個決策

  1. 使用的技術棧:開發語言 C,程式之間的通訊方式:訊息匯流排;
  2. 併發:每個子系統以程式為執行單位執行在系統中,通過 MQTT 訊息匯流排的C語言實現 mosquitto 庫,來接入到匯流排系統上;
  3. 系統不支援二次開發;

四、詳細設計

在上面的概要設計圖中,已經把所有的功能模組劃分到不同的子系統中,也可以稱之為功能組。下一步的工作,就是把每一個功能組中的內部物件、需要完成的功能、互動流程找出來,具體來說,就是要分析出系統的邏輯架構、執行架構和開發架構

1. 邏輯架構

邏輯架構就是把每一個子系統再分為粒度更細的功能塊,如果想粒度更細的話,也可以拆解到類這個級別。此外,還需要定義好各模組之間的互動介面

根據上面的描述,我們已經決定把各子系統設計為一個獨立的程式,各程式之間通過訊息匯流排進行資料互動,而這個訊息匯流排,是基於 topic 主題來進行訊息路由的,因此,下面就要設計好每一個程式需要處理哪些資料互動:

  1. 入口:對其他哪些模組的請求進行響應;
  2. 出口:為了完成自己的工作,需要依賴其他哪些模組提供服務;

一句話總結:就是找出每一個模組,為了完成自己的工作,需要與其他哪些單元模組之間進行互動?互動的介面(函式、方法或者協議)是什麼?

那麼怎麼來找到這些物件和介面呢?用序列圖或者類圖來完成。下面是控制裝置的一個簡單序列圖:

圖中的每一個箭頭,都代表一個介面,對於這個閘道器來說,就代表處理的一個 topic 主題。

如果用類圖來分析,對於物件導向的開發語言來說,可能會更容易理解,比如:可以明確的定義出每一個物件的屬性,私有函式,共有函式,並且能夠清晰的構建出物件之間的關係。

2. 執行架構

執行架構描述的是每一個執行單元的動態狀態、執行時的控制流程,需要考慮的重點是:系統是否安全效能是否滿足質量要求?可擴充套件性如何?

具體到閘道器來說,每一個子系統是以程式為執行單位的,每個程式通過一個第三方的附件(也就是動態庫),掛接到訊息匯流排上,如下圖所示:

系統的併發性,是通過多程式來實現;系統的安全性,主要通過訊息匯流排的安全機制來管理。

比如在開發階段,訊息匯流排允許系統外的其他客戶端接入,這樣就可以在 PC 機上寫一個除錯程式,接入到匯流排中,可以監聽所有的資料,此時資料可以不加密,全部是 human readable 的;但是在專案 release 階段,那麼就關閉這個許可權,PC 機上的客戶端就不能接入匯流排,並且匯流排中所有資料的需要加密、壓縮,進一步提高系統的安全性。

3. 開發架構

作為以擼程式碼為主力的我們來說,開發架構就容易理解了,無非就是定義好專案結構、編譯流程、測試步驟等等。

具體來說,我們可以從下面幾方面來做出規定:

  1. 並行開發:每個子系統是一個獨立的程式,因此可以劃分為一個獨立的專案,提高開發效率;
  2. 第三方庫:作為基礎的公共模組來使用(SSL加密、訊息匯流排接入、通訊協議解析);
  3. 程式碼安全:每位開發人員只能有許可權拿到自己負責的程式碼,只有管理員有許可權獲取所有程式碼;
  4. 程式碼管控:使用 git、svn 等工具進行程式碼版本的管理;
  5. 整合編譯:使用 Jenkins + git module 功能,自動拉取所有的子系統程式碼,自動編譯。如果需要自動部署的話,也可以使用指令碼來實現。

五、架構驗證

終於來到最後一個環節了,其實專案經歷多了,以上設計出來的架構,是否能滿足需求中提出的功能和質量要求,我們在心中已經大概知道答案了。

為了保險起見,我們還是需要對其中的某些關鍵部分進行驗證。這個驗證過程是有價值的,或者說可以把這個驗證過程所得到的成果,作為正式的程式碼進行提交。

驗證的大方向有 2 點:系統的框架是否合理、穩定;一些技術瓶頸是否可以搞定。如果這兩部分都沒問題,那後面就可以大膽的往前走了。

六、總結

經過 2 篇文章的介紹,我基本上把自己在平常工作中,對應用程式架構設計的這個思考過程描述了一遍。

佛經裡說了:渡人就像幫助一個人過河,過了河上了岸,就應該把乘坐的木筏丟掉,心中不要再想著木筏。

這篇文章介紹的設計流程,也是一個套路而已。這個套路在面對一個新領域、新專案時,就像一個腳手架一樣,告訴我們這一步該做什麼,下一步該做什麼,應該使用什麼樣的工具。

在僵化的運用這個套路之後,你可以繼續改造、優化,然後丟掉這個套路,從而形成適合你自己的套路,從此走向思考致富的道路!

祝你好運!

(如果您覺得這是一篇乾貨,對其他的小夥伴有價值,請您轉發、分享!非常感謝!也非常歡迎在留言區一起討論、吹牛!)


好文章,要轉發;越分享,越幸運!



推薦閱讀

【C 語言】

C語言指標-從底層原理到花式技巧,用圖文和程式碼幫你講解透徹
原來gdb的底層除錯原理這麼簡單
一步步分析-如何用C實現物件導向程式設計
提高程式碼逼格的利器:巨集定義-從入門到放棄
利用C語言中的setjmp和longjmp,來實現異常捕獲和協程

【應用程式設計】

物聯網閘道器開發:基於MQTT訊息匯流排的設計過程(上)
物聯網閘道器開發:基於MQTT訊息匯流排的設計過程(下)
我最喜歡的程式之間通訊方式-訊息匯流排

【物聯網】

關於加密、證書的那些事
深入LUA指令碼語言,讓你徹底明白除錯原理

【胡說八道】
以我失敗的職業經歷:給初入職場的技術人員幾個小建議

相關文章