跟著設計原則走,好維護阿自然有(單押)-設計模式software design principle
前言
今天會聊一下設計原則,包括為什麼我們需要設計原則,以及常見的設計原則。
強調是原則而不是模式,是因為我們並不會進到細節去討論類似工廠模式、策略模式等~
什麼是原則?
在聊常見的設計原則前,我們可能會想問知道這樣的程式設計原則能對開發帶來什麼幫助。
根據之前聽過Will保哥分享的,知道原則可以:
- 依循原則寫出較好的程式碼
- 聞到程式碼的壞味道
另外原則通常都是來自於前人的錯誤經驗總結而來,所以盡可能實踐原則也可以讓程式碼相對強健、具有維護性。
接下來介紹一些常見的原則。
SOLID
是一個有關於物件導向設計的五個原則。
原則間的關係
其中包含
Single Responsibility, SRP:
他的定義是每個模組都只應該因為一個原因而被修改。
舉個實際的例子不知道會不會更清楚一點,有沒有看過一個大型事務機,影印、傳真、列印都有的那種,當你是這樣所有功能都整合在一個機器(模組)裡面時,如果設計不好,比如說共用了同一個軟體,結果一個軟體壞了,導致三個功能都無法使用了。
當然你可能會問那拆越細越好嗎?事實上也不一定,因為你拆越細能共用的部分就越少了,比如以上面的例子來看,那是不是個別每個功能都要使用自己的軟體去處理呢?會不會導致成本上升呢?所以最通用的說法是看需求XD
詳細的不會介紹到這麼多,有興趣的話歡迎再看一下參考資料
OCP
You should be able to extend the behavior of a system without having to modify that system.
這個原則要求程式碼的撰寫,在擴充他的行為時不應該修改到系統。
這個概念最常見的實例是什麼?Google Chrome Extension、webpack。
我把固定的介面封裝起來,所有要擴充的新功能完全不會影響到原本已經穩定運作的功能。
LSP
If S is a subtype of T, then objects of type T may be replaced with objects of type S without altering any of the desirable properties of the program (correctness, task performed, etc.)
翻譯: 如果類別S是類別T的subType,程式碼中的類別T的物件,都可以被一個類別S的物件給取代,而且程式碼還運作正常。
就算翻譯了還是複雜XD
意思是說,子類別的程式行為應該遵循父類別的設計、約束。
如果實務上來說,我們希望,如果父類別能通過的限制,子類別也應該通過;父類別會錯誤的地方,子類別也應該錯誤,這樣就不會出現令人措手不及的例外。
詳細可看參考資料
ISP
No client should be forced to depend on methods it does not use.
翻譯: 使用者不應該被強迫依賴他們用不到的介面
有一個我很喜歡的例子是政府表單,假如你去區公所辦事,他們請你填寫表單,所有的服務都使用同一份表單,只是分ABCDE區塊,可能你想辦婚姻相關需要填寫AB,辦福利可能要ACD區塊,是不是很麻煩還很容易填錯?
這樣的全能介面就是ISP希望解決的,為了避免的問題包括
- 關注點分離
- 模組之間因為沒有分割清楚導致誤用
DIP
1.High-level modules should not depend on low-level modules. Both should depend on abstractions.
2.Abstractions should not depend on details. Details should depend on abstractions.
翻譯: 高階模租不應該依賴低階模組,兩者皆須要依賴抽象層;抽象層不應該依賴實作,實作應該應賴抽象層
前面的圖片有提到,其實DIP幫助實現了OCP,這是什麼意思呢?
舉例來說,今天有A牌的冷氣跟遙控器,看起來冷氣提供了功能告訴我們怎麼操控她,比如說cold()、hot(),所以冷氣是遙控器的底層。
如果哪天某間工廠冷氣改了命令他的方式,cold => freeze,那這樣所有遙控器都要被修改發出的指令了,這就是高階依賴低階。
那該怎麼做呢?
我們為了避免這樣改底層壞掉高層的狀況,所以我們訂一個命令對照表,要求所有的冷氣都要按照 cold(), hot(), wind()的方式實作底下的功能,而該牌所有的遙控器都按照這個方式呼叫冷氣,那這樣自然這個廠牌的遙控器都可以通用控制冷氣的這三種功能了,至於這三種功能的細節會不會因為這個有所差異,也可以隨著不同機種的狀況自行處理,我們只是統一介面。
KISS
是Keep It Simple, Stupid的首字母簡寫。
這裡的stupid通常被解釋成易於理解,用程式碼維護的過程來說,指的就是你的程式碼應該能被輕鬆理解。
DRY
是Don’t repeat yourself的首字母簡寫。
字面上很單純就是避免重複的意思,在The Pragmatic Programmer書中將DRY分成四種:
強制重複(Imposed duplication)
所謂的強加,是指開發人員認為必要,但其實並不必要的重複,比如說對於程式碼的註解有沒有可能從命名就可以減少註解數量?
這部分的討論一直以來都有很多派說法,我個人的經驗是: 註解那些商業邏輯,但盡量不註解程式邏輯,因為商業邏輯你會忘掉,有些部分可能會影響程式刻意沒有效率,但程式邏輯應該盡可能以程式本身呈現。
無意的重複(Inadvertent duplication)
開發者並未注意到某些邏輯可以避免重複。
引入一個例子
1 | // from https://shawnlin0201.github.io/Methodology/Methodology-001-DRY-principle/ |
我個人的經驗是會造成這種重複的原因是來自忽略了邏輯的前提,以上面的程式碼為例,忽略自己在寫的立方體的定義前提。
缺乏耐心的重複(Impatient duplication)
是一個對於copy and paste的常用建議。
在開發的時候,對於功能類似的部分,我們可能會選擇從其他類似功能將程式碼貼過來用,但卻懶得將裡面的重複項去做整合。
記得曾經聽過Ruddy老師的演講是說,最大的技術債來自於妳現場就發現卻未處理的部分。
開發者間的重複(Inter-developer duplication)
共同開發時常發生的問題,對於彼此所開發的程式碼並不熟悉,導致相同功能的程式碼被重複撰寫並各自引用。
YAGNI
有那麼多的原則,總算要來個平衡他們的部分了。
這個原則叫做You aren’t gonna need it,我個人的翻譯是要注意時態,所以是: 你現在還不需要它。
為什麼說他是其他原則的平衡呢?
不管是什麼設計原則,都需要注意過度設計(over-designed),特別是像DRY、SRP等維持高聚合的原則,我們需要這個原則澆個冷水,停下來再想想。
不過大部分的時候,我們為什麼會過度設計是有邏輯: 我們覺得現在把這個功能建立起來比未來建立來的成本低。
因此它提供了一些思考方向,包括:
- 構思尚不存在的需求所花費的成本 v.s. 專注開發產生的效益
- 增加尚不存在的新功能所導致的維護複雜性
特別推薦這篇文章
他提到了Over designed導致的成本有哪些
並提到一個結論是:
YAGNI適用在假定需求的功能,但不適用在讓程式更易於維護的努力。
小結
不管是哪一種原則,我認為都應該跟實務經驗結合去思考他如何在開發過程中去使用他。
透過對照自己的經驗、自我反駁、重新理解,我認為是內化這些原則的最好方法。
為了寫出更好的程式,共勉之~~~~
此文章同步發表於部落格,歡迎來逛逛~
參考資料
A Solid Guide to SOLID Principles
使人瘋狂的 SOLID 原則:開放封閉原則 (Open-Closed Principle)
程式設計心法 避免重複原則(DRY principle)