Clean Architecture:程式範式的類型

Chia-wen Hou
5 min readMar 26, 2019

範式規範的出現

  • 結構化程式設計:structured programming
  • 物件導向程式設計:object-orient programming
  • 函數式程式設計:functional programming

這種範式規範,反而是提出一些規範,告訴程式設計師「不要做什麼」。

結構化程式設計:

  • Dijkstra發現goto語句的某些使用方式,會讓整個程式結構無法被細分成更小的模組(divide and conquer); 但是良好的使用goto,會可以對應到簡單的選擇和翼帶控制結構,例如:if/else, do/while。若如此使用,就可以被細分。
  • => 在「直接的控制轉移」上加上規範(移掉某些不良的goto使用方式)
  • => 三種結構:循序(Sequence) . 選擇(selection) . 迭代(iteration)
  • => 可進行功能分解,將程式分解成一組組小的可證偽(可測)功能

物件導向程式設計:

  • 有兩位前輩Dahl & Nygaard發現:發現可將函式呼叫堆疊(stack frame)移到heap中,並發明了OO
  • 三個OO重要概念:封裝/繼承/多型。作者認為,封裝跟繼承,在OO語言出現之前,在原本語言特性中,想做到是可以做到的(雖然會比較麻煩並沒有被嚴格控制)。唯多型被作者認為最有威力及代表性。
  • 作者認為,多型能夠讓一路由上而下的控制流,能夠變成反向依賴!(很重要!)

如下圖:如果像結構式函式,一層一層呼叫更細小的函式任務來執行,其控制流如下:(虛線代表控制流 / 實線代表原始碼依賴性)

這樣會造成:原始碼的依賴性,就完全只能依照控制流來決定了!這樣軟體架構師能做的就不多了…

如下圖左:原本控制流跟原始碼依賴的方向一樣

而厲害的是,多型能夠讓這個依賴逆轉過來!

如下圖右:虛線是控制流(HL1想要ML1.F()幫忙做事情),而實際上,程式碼依賴,變成HL1只是透過介面I在做事情,沒有依賴ML1。反而是ML1反過來依賴上層的I了! (這就是所謂的依賴反向dependency inversion)

圖左:原本控制流跟原始碼依賴的方向一樣 / 圖右:透過多型.介面,讓原始碼依賴反過來!

所以「多型」之所以這麼厲害,是因為,無論在何處,任何的原始碼依賴都可以用這種方式來「反向」,以保護一些重要的模組,不會依賴於其他不該依賴的模組,擁有完全的控制力。

總結來說,這也是OO厲害之處。

函數式程式設計:

函數式編程對我們這些從小(XD)就受if /else/for loop訓練的小朋友來說,應該是比較陌生的。作者舉了這個例子 — 列印前25個整數的平方

圖左:一般結構式寫法 / 圖右:函數式寫法

圖左就是我們一般結構式寫法,而圖右是很不熟悉的函數式程式

他做了以下事情:

…(解釋待補)

到底這兩種寫法,重要的差別是什麼呢?

差別主要在於,左邊的有「可變的變數」,右邊的可以發現都是「不可變的變數」=> 在嚴格的函數式程式中,「變數不會改變」!(驚)

回來說,為什麼變數可不可變這件事,對架構來說,有需要關心&覺得重要?

因為一切關於「race condition / deadlock / 平行更新問題」等,都來自於我們想要去更改到同樣的可變變數。如果變數不可變,就不會遇到死結拉!

但如果不變變數,那麼會需要很多儲存空間跟處理速度!在現實世界比較不可行。這時候就要做一些折衷。

如果可行的話,架構師可以嘗試盡量讓更多處理放到不可變的元件中,與可變的變數分離開來

有個很有趣的思考:在像交易這種行為,帳戶餘額都得是可變的,也因為這樣我們都得用transaction的方式來確保一致性等,也會造成效能瓶頸。

那如果說讓變數變成不可變呢?例如:db只保存所有交易紀錄,如果你要總合,你自己加總!這需要很多空間,但你可以在午夜很少人使用的時候批次儲存&保存狀態(讓運算加總不會無限增長)。=> 也就是嘗試讓變數只有「CR (create/read)」,沒有「UD (update/delete)」。

(雖然以上讓人覺得是個平常開發不太會用的架構,但還是個很有趣的想法…)

回來總結一下:函數式程式設計,是在變數賦值上加上規範。

總結:

  • 結構化程式設計:在「直接的控制轉移」上加上規範
  • 物件導向程式設計:在「間接的控制轉移」上加上規範
  • 函數式程式設計:在「變數賦值」上加上規範

在過去半個世紀學到了哪些不該做!(what not to do),讓我們的架構更容易被維護。

--

--