簡潔的程式碼Clean Architecture
第二次讀了,有些觀念礙於沒有實際應用,仍然是懞懞懂懂
三大程式設計
主要是書本中的介紹直接照搬,因為我也不是很熟悉各類型語言。
結構化程式設計
拆分的模組設計,”goto”類語言,直接控制移轉加上規範。
物件程式設計
三大物件程式特色:封裝、繼承、多型(依賴反向),屬於 「間接」控制移轉加上規範。
函式程式設計
變數不會被改變,如果今天所有的應用程式都不可變,又可以稱之為「函數化」,屬於 「變數賦值」加上規範。
SOLID原則
SRP 單一職責
一個module只對「一個角色」負責,而不是只做一件事而已,因此有時候該拆分的就該好好拆分開來,並非所有「看起來很像」的程式碼都可以共用!
OCP 開放 — 封閉
對於擴充是開放的,而對於修改是封閉的,你要將內部細節隱藏起來,避免受到其他modules污染。
LSP Liskov替換原則
當替換繼承的module時,不會受到替換的影響。
ISP 介面隔離
避免依賴module「自己不使用」的東西。
DIP 反向依賴
我只管「用細節」,不用「管細節」,這點也對應了開放 — 封閉原則,其他module不需要知道隱藏的細節,而且也「不該知道」。
內聚性
總歸一句,以使用者的角度思考,想想會用到該元件的開發者會怎麼使用,把類似的放一起,與使用上不相干的(例如你額外用到的函式庫)隔離開來!
REP 使用發布等價
當你發佈新版本之後,舊有的內容要跟之前一樣,就像我們一般在使用各類函式庫一樣,我們只管更新版本,而不用去管其中實作的細節,除非有些module已經過時了,這時候我們才要修改。
CCP 共同封閉
將類似的方法、module聚在一起,類似OCP的原則,只是此原則應用於「元件」上。
CRP 共同重複
「不要」將類似的方法、module聚在一起,類似ISP的原則,只是此原則應用於「元件」上。
耦合性
ADP 無環依賴
如果UML畫出來後發現,各元件的依賴產生了一個「環」,那麼表示各環節有「互相耦合」的情況,那麼將會難以除錯,甚至連測試都做不了,因為你必須「一層一層地」mock上一層的元件,導致測試成本非常高!
SDP 穩定依賴
往「穩定」的方向做依賴,就像是地基一樣,該「被依賴」的元件越是穩定,因為修改相對少,那麼依賴他的組件比較不會受到影響。
SAP 穩定抽象
元件是部分「穩定」和「抽象」的,越是穩定,則會是「越抽象」。
就像是我只有定義接口的interface,這樣的接口是非常「抽象」的,我們要實作時才會知道該接口如何應用,且一旦定下來,通常不會再修改,頂多擴充而已,這樣大概就是SAP的穩定抽象原則。
架構
設備獨立
例如各種硬體、控制器之間,盡量獨立區分,而且不要與軟體混在一起,尤其小心不要將所有用於硬體的軟體都「寫成韌體」,盡量保持設備與軟體之間的彈性。
獨立於:
- 各層級:
比較常見的有商業邏輯層(Business Rules)、控制層(Controllers)、表現層(Presenters)、UI層(Views)…等,獨立各個層級。 - 使用案例:
各層中,可以用各種使用案例將其中在做切分,例如特定使用於A情境的元件,就可以把他從更廣泛的B情境中獨立出來,那我們如果要修改時,只要修改A情境的元件即可。
規則與邊界業務規則
依賴規則
依賴規則可以想像為一個有如箭靶的圓圈,由外往內做依賴,內部是「越抽象且穩定的」,越往外則是「越具體且動態」。
從內而外,從Entities(商業規則) -> Use Case(使用案例) -> Controllers, Presenters -> 最後才是UI/Views…。
Humble Object(謙虛物件)
例如view就屬於humble object,只負責「顯示」而已,就像react的component(dumb component)一樣,將任何與UI無關的切分開來,讓UI得以方便測試其顯示、互動…等。
邊界
以「變化速率」為軸的地方,就可以將其定為邊界,也就是說相同「變化速率」的元件,就可以將其歸類於一組,在各組不同變化速率的元件外圍,就會產生一條「邊界」。
就如上面所提的,每一個「使用案例」、「層級」之間都有著明顯的邊界。而跨越邊界的箭頭,會從低層(抽象穩定)指向高層(具體動態),如圖所示。
認清楚元件間的邊界,有助於我們寫出容易維護的「一組」元件,就像CCP(共同重複)原則所提的,讓我們知道何時該跨過邊界、邊界在哪邊,免得污染到其他邊界內的元件,讓程式更好維護與使用。
測試
測試本身就是系統的「一部份」,有足夠的測試才能展現該系統的穩固。
細節
細節是最後才考慮的,在開發的早期先別放進來!
保持選項開放
別一開始就決定要用哪種資料庫! 保持各種細節的選項是開放的,如果一開始就嵌入你的程式碼(沒有做好層與邊界分離),那麼之後如果改了另一個資料庫或甚至不用資料庫,那麼修改時會非常痛苦,就只是因為過早決定了細節。
記住,先預留那些「不確定的細節」,保持「開放」的心態與程式碼,先將該部分隔離開來!
各種細節
不只上述提到的資料庫,對於一部份的程式來說,框架、Web…等都算是細節,都是在你決定商業規則之後才考慮的。
考慮實作
最後,考慮真正實作時的細節「複雜度」,再根據當時的複雜度決定整體的設計架構、組織與封裝。
如果過早決定,可能會設計出不適用的「過於龐大」的架構;過晚決定,可能會讓架構「過於僵化」難以擴充或修改。因此,需要在準備著手實作之前,好好規劃整體架構,讓程式能夠事半功倍,免得之後才打掉重做!
總結
將細節抽離、元件切割乾淨
有時候寫程式時,為了圖方便會將各種細節處理「塗抹」在元件各處,像是將api的呼叫寫在view層之中,或是本該拆開的元件寫在同一處…等,當時或許會寫得滿開心的,功能也都正常運作,但是一旦要維護或是測試時,才發現耦合程度相當嚴重,甚至出現了環狀耦合,這時候礙於時間壓力,可能選擇不解決也不進行測試。
但隨著時間的累積、專案規模的擴大,讓「整坨」程式碼進化成一隻怪獸,你光是要修改一個地方,就要擔心是否有其他耦合的程式碼會被變動,每次修改都心驚膽跳,只要上線後有錯誤還要加班修改!
因此, 掌握一個原則,抽離細節、切割元件、分層管理。
像是知名的「MVC」架構就是在處理這種問題,強制你將元件分為至少這三層,減少過度耦合與分清楚各層的邊界,即便我們沒有使用MVC架構,也可以在動手之前,好好想清楚該元件是「做什麼的」。
寫程式時可以問問自己,這元件是否該放在這邊? 一些細節是否該歸在更上層的具體元件?這麼早決定細節好嗎? 這階段是否該決定這些東西? 最底層的商業規則長什麼樣子? 依賴的方向是否有問題,是不是該反向依賴?…等,都有助於我們寫出更乾淨的程式碼,打造更好的程式架構。
應用於人生中
良好的組織文化、適當的組織結構與放心的賦權,跟clean architecture提到的架構是一樣的道理。
除了扁平化組織以外,如果是傳統型的垂直組織,可以借鑑其中的一些概念,像是邊界的劃分,這可以應用在工作與生活的切分,別把工作與生活糊在一起明顯的區隔開來;
組織結構上則是清楚的做好各部門之間的區隔,但是部門之間的溝通必須確實且有效率,就像是元件的層級劃分清楚,並且溝通上要足夠有效。
放心的賦權,就是DIP— 「只管使用」,而不在意「使用細節」。只要知道員工有確實做事,將適當的職權放心交給部署,不要過於緊迫逼人,如此一來,不僅管理階層更輕鬆,員工的工作也能更有效率。
細節部分,則是對於細節別太早著手,保持彈性與開放的心態。尤其是現在變化快速的時代中,在執行事物之前與觀念的理解上,不必一開始就想得太清楚,先了解「概觀」,掌握事物的「全貌」,到了確定要確實執行再去想想當時有哪些資源可以運用,這樣能夠更加彈性。
就像是你還沒構想完整之前,就買好原料打算大量生產,但是在你要做的時候,該原料可能早就過期,或是被檢驗出可能對人體有害,到時就欲哭無淚了。