淺談程式碼分層:構建模組化程式

無痕幽雨發表於2018-03-16

出處:http://www.cppblog.com/kevinlynx/archive/2011/04/05/143430.html


淺談程式碼分層:構建模組化程式

Author:Kevin Lynx
Date:4.4.2011
Contact:kevinlynx at gmail dot com

模組化的程式是怎樣的程式?我們可以說一個具有明顯物理結構的軟體是模組化的,例如帶 外掛的軟體,一個完整的軟體由若干執行時庫共同構建;也可以說一個高度物件導向的庫是 模組化的,例如圖形引擎OGRE;也可以說一些具有明顯層次結構的程式碼是模組化的。

模組化的軟體具有很多顯而易見的好處。在開發期,一個模組化的設計有利於程式設計師實現, 使其在實現過程中一直保持清晰的思路,減少潛伏的BUG;而在維護期,則有利於其他程式 員的理解。

在我看來,具有良好模組設計的程式碼,至少分為兩種形式:

  • 整體設計沒有層次之分,但也有獨立的子模組,子模組彼此之間耦合甚少,這些子模組 構成了一個軟體層,共同為上層應用提供服務;
  • 整個庫/軟體擁有明顯的層次之分,從最底層,與應用業務毫無相關的一層,到最頂層, 完全對應用進行直接實現的那一層,每一個相對高層的軟體層依賴於更底層的軟體層, 逐層構建。

上述兩種形式並非完全分離,在分層設計中,某一層軟體層也可能由若干個獨立的模組構成 。另一方面,這裡也不會絕對說低層模組就完全不依賴於高層模組。這種雙向依賴絕對不是 好的設計,但事實上我們本來就無法做出完美的設計。

本文將程式碼分層分為兩大類:一是狹義上的分層,這種分層一般伴有檔案形式上的表現;一 是廣義上的分層,完全著眼於我們平時寫的程式碼。

軟體分層

軟體分層一般我們可以在很多大型軟體/庫的結構圖中看到。這些分層每一層本身就包含大 量程式碼。每個模組,每一個軟體層都可能被實現為一個執行時庫,或者其他以檔案形式為 表現的東西。

Example Android

Android是Google推出的智慧手機作業系統,在其官方文件中有Android的系統架構圖:


這幅圖中很好地反映了上文中提到的軟體層次。整個系統從底層到高層分為Linux kernel, Libraries/Runtime,Application Framework,Applications。最底層的Kernel可以說與應 用完全不相關,直到最上層的Applications,才提供手機諸如聯絡人、打電話等應用功能。

每一層中,又可能分為若干相互獨立(Again,沒有絕對)的模組,例如Libraries那一層 中,就包含諸如Surface manager/SGL等模組。它們可能都依賴於Kernel,並且提供介面給 上層,但彼此獨立。

Example Compiler

在編譯器實現中,也有非常明顯的層次之分。這些層次可以完全按照編譯原理理論來劃分。 包括:

  • 詞法分析:將文字程式碼拆分為一個一個合法的單詞
  • 語法分析:基於 詞法分析 得到的單詞流構建語法樹
  • 語義分析:基於 語法分析 得到的語法樹進行語義上的檢查等
  • 生成器:基於 語義分析 結果(可能依然是語法樹)生成中間程式碼
  • 編譯器:基於 生成器 得到的中間程式碼生成目標機器上的機器程式碼
  • 連結器:基於 編譯器 生成的目的碼連結成最終可執行程式

軟體分層的好處之一就是對任務(task)的抽象,封裝某個任務的實現細節,提供給其他 依賴模組更友好的使用介面。隔離帶來的好處之一就是可輕易替換某個實現。 例如很 多UI庫隔離了渲染器的實現,在實際使用過程中,既可以使用Direct X的渲染方式,也可 以使用OpenGL的實現方式。

但正如之前所強調,凡事沒有絕對,凡事也不可過度。很多時候無法保證軟體層之間就是單 向依賴。而另一些時候過度的分層也導致我們的程式過於鬆散,效率在粘合層之間繞來繞去 而消失殆盡。

程式碼分層

如果說軟體分層是從大的方面討論,那麼本節說的程式碼分層,則是從小處入手。而這也更是 貼近我們日常工作的地方。本節討論的程式碼分層,不像軟體分層那樣大。每一層可能就是 百來行程式碼,幾個介面。

Example C中的模組組織

很多C程式碼寫得少的C++程式設計師甚至對一個大型C程式中的模組組織毫無概念。這是對其他技 術接觸少帶來的視野狹窄的可怕結果。

在C語言的世界裡,並不像某些C++教材中指出的那樣,佈滿全域性變數。當然全域性變數的使 用也並不是糟糕設計的標誌(goto不是魔鬼)。一個良好設計的C語言程式懂得如何去抽象、 封裝模組/軟體層。我們以Lua的原始碼為例。

lua.h檔案是暴露給Lua應用(Lua使用者)的直接資訊源。接觸過Lua的人都知道有個結構體 叫lua_State。但是lua.h中並沒有暴露這個結構體的實現。因為一旦暴露了實現,使用者就 可能會隨意使用其結構體成員,而這並不是庫設計者所希望的。 封裝資料的實現,也算 是構建模組化程式的一種方法。

大家都知道暴露在標頭檔案中的資訊,則可能被當作該標頭檔案所描述模組的介面描述。所以, 在C語言中任何置於標頭檔案中的資訊都需要慎重考慮。

相對的,我們可以在很多.c檔案中看到很多static函式。例如lstate.c中的stack_init。 static用於限定其修飾物件的作用域,用它去修飾某個函式,旨在告訴:這個函式僅被當前檔案( 模組)使用,它僅用於本模組實現所依賴,它不是提供給模組外的介面! 封裝內部實現 ,暴露夠用的介面,也是保持模組清晰的方式之一。

良好的語言更懂得對程式設計師做一種良好設計的導向。但相對而言,C語言較缺乏這方面的語 言機制。在C語言中,良好的設計更依賴於程式設計師自己的功底。

Example Java中的模組組織

相較而言,Java語言則提供了模組化設計的語法機制。在Java中,如同大部分語言一樣,一 般一個程式碼檔案對應於一個程式碼模組。而在Java中,每個檔案內只能有一個public class。 public class作為該模組的對外介面。而在模組內部,則可能有很多其他輔助實現的class ,但它們無法被外部模組訪問。這是語言提供的封裝機制,一種對程式設計師的導向。

Example OO語言中類介面設計

無論在C++中,還是在Java中,一個類中的介面,都大致有各種訪問許可權。例如public、 private、protected。訪問許可權的加入旨在更精確地暴露模組介面,隱藏細節。

在C中較為缺乏類似的機制,但依然可以這樣做。例如將結構體定義於.c檔案中,將非 介面函式以static的方式實現於.c檔案中。

OO語言中的這些訪問許可權關鍵字的應用尤為重要。C++新手們往往不知道哪些成員該public ,哪些該private。C++熟手們在不刨根挖底的情況下,甚至會對每個資料成員寫出get/set 介面(那還不如直接public)。在public/private之間,我們需要做的唯一決策就是,哪些 資料/操作並非外部模組所需。如果外部模組不需要,甚至目前不需要,那麼此刻,都不要 將其public。一個public資訊少的class,往往是一個被使用者更喜歡的class。

(至於protected,則是用於繼承體系之間,類之間的資訊隱藏。)

Example Lisp中的模組設計

又得提提Lisp。

基於上文,我們發現了各種劃分模組、劃分程式碼層的方式,無論是語言提供,還是程式設計師自 己的應用。但是如何逐個地構建這些層次呢?

Lisp中倡導了一種更能體現這種將程式碼分層的方式:自底而上地構建程式碼。這個自底而上, 自然是按照軟體層的高低之分而言。這個過程就像上文舉的編譯原理例子一樣。我們先編寫 詞法分析模組,該模組可能僅暴露一個介面:get-token。然後可以立馬對該模組進行功能 測試。然後再編寫語法分析模組,該模組也可能只暴露一個介面:parse。語法分析模組建 立於詞法分析模組之上。因為我們之前已經對詞法分析模組進行過測試,所以對語法分析的 測試也可以立即進行。如此下去,直至構建出整個程式。

每一個程式碼層都會提供若干介面給上層模組。越上層的模組中,就更貼近於最終目標。每一 層都感覺是建立在新的“語言“之上。按照這種思想,最終我們就可以構建出DSL,即Domain Specific Language。

分層的好處

基於以上,我們可以總結很多程式碼分層的好處,它們包括(但不限於):

  • 隱藏細節,提供抽象,隱藏的細節包括資料的表示(如lua_State)、功能的實現
  • 在新的一層建立更高層的“語言”
  • 介面清晰,修改維護方便
  • 方便開發,將軟體分為若干層次,逐層實現

一個問題的解決

有時候,我們的軟體層很難做到單向依賴。這可能是由於前期設計的失誤導致,也可能確實 是情況所迫。在很多庫程式碼中,也有現成的例子。一種解決方法就是通過回撥。回撥的實現 方式可以是回撥函式、多型。多型的表現又可能是Listener等模式。

所有這些,主要是讓底層模組不用知道高層模組。在程式碼層次上,它僅僅儲存的是一個回撥 資訊,而這個資訊具體是什麼,則發生在執行期(話說以前給同事講過這個)。這樣就簡單 避免了底層模組依賴高層模組的問題。

END

精確地定義一個軟體中有哪些模組,哪些軟體層。然後再精確地定義每個模組,每個標頭檔案 ,每個類中哪些資訊是提供給外部模組的,哪些資訊是私有的。這些過程是設計模組化程 序的重要方式。

但需要重新強調的是,過了某個度,那又是另一種形式的糟糕設計。但其中拿捏技巧,則只 能靠實踐獲取。

posted on 2011-04-05 10:12 Kevin Lynx 閱讀(15017) 評論(5)  編輯 收藏 引用 所屬分類: 模組架構

評論

# re: 淺談程式碼分層:構建模組化程式 2011-04-05 11:20 expter

不錯,分析很透徹! 
其實一個模組就是一個功能,所有模組功能的組合就是一個軟體。  回覆  更多評論   

# re: 淺談程式碼分層:構建模組化程式[未登入] 2011-04-05 12:35 by

我的看法: 
分層是一種系統構架,等於說是底層。 
應用層,還是有很多摻雜了各個系統,各個層次的東西。 
應用層有時候是無法分出來哪些是哪些,因為每個需求裡都攙雜著對別的需求的需求。 
一直以來,軟體開發都在考慮如何用軟體工程思想來規範軟體的開發,但是事實證明,大部分的軟體工程思想都是被用在底層、功能模組和通用軟體的開發中,實際上的業務邏輯根本無法運用軟體工程思想來進行開發。 

實際上,真正需要規範化的工程化的東西是需求。讓提需求的人必須接受軟體工程這種類似的培訓,才能來代表使用者對軟體開發提業務需求。這樣會比軟體本身去適應需求更加高效一點。 

在遊戲開發中,策劃擔任的就是這個角色。必須對策劃進行這種培訓,才能規範化他們的需求,實現從需求上的層次清晰。 

  回覆  更多評論   

# re: 淺談程式碼分層:構建模組化程式 2011-04-06 14:58 xielun.szd

最大的感觸是通過分層劃清各個模組的職責,同時做到各個層的pulgin特性,便於進行復雜的單元測試。其實分層和模組化沒有本質區別,模組化是基礎,分層是強調和限制模組之間的關係,使其更加職責單一化,減少出度。  回覆  更多評論   

# re: 淺談程式碼分層:構建模組化程式 2011-04-06 20:49 lin_style

我最喜歡看這種爭論了  回覆  更多評論   

# re: 淺談程式碼分層:構建模組化程式 2016-04-26 20:37 aaa

模組化,還是要看JXADF的架構,那是相當的NB,詳細參見:http://osgia.com  回覆  更多評論   



相關文章