閒談軟體設計:Switch 壞味道

更新於 2024/10/12閱讀時間約 7 分鐘

Switch 的問題

這篇的標題來自《重構 — 改善既有程式的設計》第一版的第三章:程式碼的壞味道,條列了 22 個程式碼常見的壞味道,像很多人朗朗上口的 duplicated codelong methodlarge classlong parameter list 等等,其中有一個壞味道倒是比較少人提到,便是這篇的主題:switch statement

想當初念博班當助教時,將這些壞味道列為程式作業扣分標準,學生被扣分恨得牙癢癢,我則是改作業改到快抓狂,但不知道為什麼那堂課學生卻越來越多人修,即使第一堂課我去勸退也沒用,不知道這些當初被扣分的學生畢業後程式寫得如何?

比較少被提到是因為幾乎所有程式語言都有 switch statement 的設計,使用它就像呼吸空氣一樣自然,但書中作者認為 switch statement 問題的本質就是重複,我想這應該很明顯,switch 通常不會單獨出現,以最近看到的一個 Moya 套件為例,當需要實作一個接 API 的 service 時,程式可以寫成如下 :

先說明一下,這裡只是用 Moya 舉例 switch statement 多半會出現重複,沒有說 Moya 不好,可能是在下愚昧看不出來好處在哪裡。

會發現到 switch statement 出現了三次,我看了許久還是無法理解這樣寫的好處是什麼?對我來說,像下面提供一個抽象層的介面 (protocol),然後提供一個實作的 RestfulUserWebService反而自然多了:

在設計這例子的介面時也猶豫過 (1) 用內建的 Result 表達結果;(2) 傳統的結果和錯誤參數分開的 closure;(3) 或是兩個獨立分開的 closures,個人比較偏好第三種,但第三種的缺點是,一個函式帶兩個 closures 會讓程式不好讀,第二種方式無法避免兩個參數都是 nil 的情況,所以使用第一種設計。

但 Result 本身就是一個 enum,所以會有很多的 switch statement,個人沒有很喜歡,偏偏非同步 API 很難用 try catch 來處理錯誤。若改用回傳 Promise [1][2] 的話會更精簡,省去 enum 和 switch statement。

重構

用上 enum,很容易就用上 switch statement,但是可能避免的,書中提到 Replace Type Code with SubclassesReplace Conditional with Polymorphism 或 Replace Type Code with State/Strategy 取代 switch statement。enum 就是一種 type code,因此可以可慮用 Replace Type Code with Subclasses,例如以前在改學生的 OOP 作業時會常看到類似這樣的 switch statement:

透過 Replace Type Code with Subclasses 拆成 RectangleTriangle 和 Circle 三個類別,並將原本寫在 switch statement 中的邏輯分別放到對應類別的函式中。

最後就能用 Replace Conditional with Polymorphism,用一行程式碼把 switch statement 取代:

有人可能會說,一定要用 OO 的方式處理嗎?當然不是,事實上這幾年工作都是寫 JavaScript (重構的第二版用很多 JavaScript 的例子),還是可以寫出不用 class 和 switch statement 的程式。

例如前陣子處理支付的功能,每種不同的支付需要呼叫的第三方服務不一樣,參數也不一樣,若以 OO 的角度想,很快會想到將不同支付的方式宣告共同的 interface,然後為每種支付方式提供一個實作該介面的類別,然後在執行期間根據付款方式生成對應的物件來執行,但用 JavaScript 可以簡化不少程式 (例子中仍有 type code,猜猜看在哪裡?):

例子中沒有 switch statement,而且每個 require 進來的程式其實也就只是一個符合規範的 function:

在 Java 8 導入 Lambda 後,其實很多 design pattern 用 Java 也可以寫得很精簡 (參閱拙譯《用 Lambda 實作設計樣式》),重要的是抽象的思維,而不是 OO 或是非 OO 的語法

上述的例子參數都是 paymentRequest,可以說型別是一樣的,但如果型別不一樣呢?例如回到 Shape 的例子,假設現在要把圖形存成一個 JSON 檔,這時候是可以用多型處理,但如果想把檔案輸出的邏輯抽到別的類別,這時就得一一檢查是哪個類別,根據類別有不同的處理方式,若不想寫 type cast (其實 type cast 也算是一種 switch statement,而且還是比較醜的那種),可能得用上 Visitor,但就是稍嫌麻煩,而且不好讀。

這時候即便是不喜歡動態型別的我,也得說 JavaScript 沒有這煩惱。

Java 也可以用這寫法,但要搭配 cast 使用,不然無法取得 concrete class。

所以說要完全避免使用 switch statement 嗎?我倒覺得也不用這麼極端,應該還是回到問題的本質:是否會造成重複?如果這個 switch statement 就只會出現這麼一次,那也許就算了,例如下面的例子,從資料庫中將不同的圖形讀出來,並生成不同類別的物件,假設整個專案只有這裡會用 switch statement,其他都能依靠多型處理,那這裡是不是一定要用成 factory 來處理,就可以斟酌。

個人偏好

當要宣告 enum 時,先想想宣告 enum 的目的是為了什麼?因為想寫 switch statement,那也許可以想想是否還有其他更好的方式,當然,OO 的語法和非 OO 的語法,實作的方式可能不一樣,但同樣的抽象都是可以用不同的語法來實現。

我個人就想過,Redux 應該是有機會不用寫 switch 才對 XD

我個人是盡可能不寫 switch statement,但觀察這幾年程式語言的趨勢,會發現許多語言把 switch statement 擴充成為實作 pattern matching 的工具,說不定以後 switch statement 會越來越廣泛使用也說不定。

為了馬克杯,把《重構 — 改善既有程式的設計》第二版和另外三本 O’Reilly 的好書一起搬回家,買回來立即翻到第三章,發現 switch statement 被改成 repeated switches,而書中的描述也和上述一樣:

如今,多型有更多含意,它已經不是 15 年前那個簡單的紅旗了,此外,許多語言都支援更精密的 switch 陳述式,不是只使用原始代碼 (primitive code) 作為基礎。所以我們現在把注意力轉到重複的 switch,也就是有同一組條件切換邏輯 (無論是在 switch/case 陳述式裡面,還是在串連起來的 if/else 陳述式裡面) 在許多不同的地方出現。
raw-image

後記

正好在這篇文章寫完後沒幾天看到這一篇文章,如何用 enum 移除程式中的 if/else,但仔細一看,其實是用上 Java 在 enum 上的特殊設計,所以簡單來說,enum 只是用來規範有限的 strategy,算是一種挺有趣的寫法,可以參考看看,缺點可能就是這個 enum 檔案會比較大一點。


avatar-img
53會員
104內容數
這是從 Medium 開始的一個專題,主要是想用輕鬆閒談的方式,分享這幾年軟體開發的心得,原本比較侷限於軟體架構,但這幾年的文章不僅限於架構,也聊不少流程相關的心得,所以趁換平台,順勢換成閒談軟體設計。
留言0
查看全部
avatar-img
發表第一個留言支持創作者!
Spirit的沙龍 的其他內容
長遠的角度來看,內部函式庫還是值得投資的公司資產,只是它需要時間、人力與管理才能做得好。若有不錯的內部函式庫也可以回饋給open-source社群,畢竟,現在開發軟體已經不太可能沒有用到任何open-source的東西。雖然說是將公司資產以 open-source 釋出,但換取的利益卻不見得是零。
整結來說,受到幾種語言的影響,我個人設計 API 時,除了合乎該語言的 convention、上述的穩定性及一致性外,大致還會注意幾點:語意清楚、相近的顆粒度、簡單的文件、讓程式能像文章般閱讀。
第三方套件用了Promise或是Reactive,導致所有business logic都要做調整,這就違反「只能有對內的相依方向」的原則。business logic大多數情況下與效能優化無關,通常需要優化的是I/O的存取,這些既然都在外層,就應該在外層做優化,外層的優化不該影響核心,這才是好架構。
任何語言特性用與不用,其實要看是否提升了生產力?是否提升可讀性?是否提升可維護性?這些都是在三個月甚至半年後回來修改程式時,才能明顯感受到的,而不是寫程式的當下。Java 8 的 CompletableFuture、Stream 和 Optional 都很好,但用的不好反而畫蛇添足又沒提高可讀性。
不管用哪種語言開發軟體,除非是那種一個 function 寫個幾萬行的人 (來人啊,把這種人拖去砍了),不然,一般都會根據某些因素,切割成模組或是特定功能的區塊 (一個 class 或是一個 function),但要完成一個特定功能,這些模組或區塊勢必要一起合作,因此這些模組與區塊就發生了關係。
不重新造輪子,我們使用第三方函式庫,聽起來很合理,但每個被引入的函式庫意味著一種 coupling,看到套件管理工具下載眾多第三方函式庫,意味著不用重寫這些東西,開發效率能提升數倍甚至數百倍,但我們真的都能掌握這些 coupling 嗎?當這其中任何一個環節出錯,我們的系統架構真的很優雅地應付嗎?
長遠的角度來看,內部函式庫還是值得投資的公司資產,只是它需要時間、人力與管理才能做得好。若有不錯的內部函式庫也可以回饋給open-source社群,畢竟,現在開發軟體已經不太可能沒有用到任何open-source的東西。雖然說是將公司資產以 open-source 釋出,但換取的利益卻不見得是零。
整結來說,受到幾種語言的影響,我個人設計 API 時,除了合乎該語言的 convention、上述的穩定性及一致性外,大致還會注意幾點:語意清楚、相近的顆粒度、簡單的文件、讓程式能像文章般閱讀。
第三方套件用了Promise或是Reactive,導致所有business logic都要做調整,這就違反「只能有對內的相依方向」的原則。business logic大多數情況下與效能優化無關,通常需要優化的是I/O的存取,這些既然都在外層,就應該在外層做優化,外層的優化不該影響核心,這才是好架構。
任何語言特性用與不用,其實要看是否提升了生產力?是否提升可讀性?是否提升可維護性?這些都是在三個月甚至半年後回來修改程式時,才能明顯感受到的,而不是寫程式的當下。Java 8 的 CompletableFuture、Stream 和 Optional 都很好,但用的不好反而畫蛇添足又沒提高可讀性。
不管用哪種語言開發軟體,除非是那種一個 function 寫個幾萬行的人 (來人啊,把這種人拖去砍了),不然,一般都會根據某些因素,切割成模組或是特定功能的區塊 (一個 class 或是一個 function),但要完成一個特定功能,這些模組或區塊勢必要一起合作,因此這些模組與區塊就發生了關係。
不重新造輪子,我們使用第三方函式庫,聽起來很合理,但每個被引入的函式庫意味著一種 coupling,看到套件管理工具下載眾多第三方函式庫,意味著不用重寫這些東西,開發效率能提升數倍甚至數百倍,但我們真的都能掌握這些 coupling 嗎?當這其中任何一個環節出錯,我們的系統架構真的很優雅地應付嗎?
你可能也想看
Google News 追蹤
Thumbnail
徵的就是你 🫵 超ㄅㄧㄤˋ 獎品搭配超瞎趴的四大主題,等你踹共啦!還有機會獲得經典的「偉士牌樂高」喔!馬上來參加本次的活動吧!
Thumbnail
隨著理財資訊的普及,越來越多台灣人不再將資產侷限於台股,而是將視野拓展到國際市場。特別是美國市場,其豐富的理財選擇,讓不少人開始思考將資金配置於海外市場的可能性。 然而,要參與美國市場並不只是盲目跟隨標的這麼簡單,而是需要策略和方式,尤其對新手而言,除了選股以外還會遇到語言、開戶流程、Ap
Thumbnail
這篇文章探討了在軟體開發中的技術債可能來自哪些原因,以及如何自動化偵測與修復技術債。作者透過分享不同情境下的技術債選擇,提供了對於技術債的思考與建議,針對開發人員在需要做出無奈的技術決策時,提供了一些建議。此外,還提供了一些在做出技術決策時的方法,如保留抽象層和避免vendor lock-in。
Thumbnail
今天來聊個最近很夯的主題 DDD,但不是 DDD 的本尊 Domain Driven Design,而是無所不在的 Database Driven Design,Database Driven Design 不是不好,只是你的模型容易變成貧血模型,邏輯都集中在 service 層等等。
Thumbnail
有趣的是,Model 其實沒什麼嚴格的定義,所以每個人對 Model 的解讀也不盡相同,有人覺得資料怎麼儲存屬於 Model 的一部份 (受 ORM 工具的影響),有人覺得工作流程 (workflow) 是 Model 的一部份,我個人也有自己的想法,而且隨專案的規模和特性,也不是總是一樣的。
Thumbnail
起源是當時 Facebook 有篇文章討論不少人分不清楚上述二者的差別,當時寫了首部曲《閒談軟體設計:API Naming Style》,接著是《閒談軟體設計:內部函式庫》,但始終沒談到 library 和 framework 的差別,主要是沒有好的例子,這次這例子還蠻不錯的。
Thumbnail
我自己偏好用 Repository 搭配 decorator 來管理 cache,而不是在 controller 層或是到處都有快取的邏輯,如果程式都是透過 Repository 更新資料,Repository 就會是一個不錯的地方更新快取,邏輯也就不會散亂在各處了。
Thumbnail
最近身旁有幾位正在懷孕、或剛生產完的朋友,讓我想起自己在懷孕期間印象最深刻的三件「怪事」,其中又以第三件事最誇張。
Thumbnail
不知道大家在買房之前是不是都會參考親朋好友的意見,或是上網看一些買房注意事項,有時候考慮了這塊就忘了那塊,考慮的那塊又忘了這塊.......
Thumbnail
塔西佗陷阱(Tacitus Trap),一個得名於古羅馬歷史學家塔西佗的政治學理論,意指倘若公權力失去其公信力,無論如何發言或是處事,社會均將給予其負面評價。 當然信著恆信、不信者恆不信,這就是真實的人生。
Thumbnail
關於片名   台灣片名《花漾女子》,原文片名《Promising Young Woman》,台灣譯名將時間定格在悲劇發生前,而原文片名則進一步帶我們看見另一個可能性結果
Thumbnail
前幾年因為身體的關係,當了幾年的律師逃兵,當時開了之前的事務所以後,一時間也沒有特別想要做甚麼事情,所以就邊讀一點書、早晚運動一下,剛好聽到當年同梯朋友進去金融業工作,因此也抱著嘗(ㄊㄠˊ)試(ㄅㄧˋ)的心態,找了份銀行法令遵循的工作
Thumbnail
徵的就是你 🫵 超ㄅㄧㄤˋ 獎品搭配超瞎趴的四大主題,等你踹共啦!還有機會獲得經典的「偉士牌樂高」喔!馬上來參加本次的活動吧!
Thumbnail
隨著理財資訊的普及,越來越多台灣人不再將資產侷限於台股,而是將視野拓展到國際市場。特別是美國市場,其豐富的理財選擇,讓不少人開始思考將資金配置於海外市場的可能性。 然而,要參與美國市場並不只是盲目跟隨標的這麼簡單,而是需要策略和方式,尤其對新手而言,除了選股以外還會遇到語言、開戶流程、Ap
Thumbnail
這篇文章探討了在軟體開發中的技術債可能來自哪些原因,以及如何自動化偵測與修復技術債。作者透過分享不同情境下的技術債選擇,提供了對於技術債的思考與建議,針對開發人員在需要做出無奈的技術決策時,提供了一些建議。此外,還提供了一些在做出技術決策時的方法,如保留抽象層和避免vendor lock-in。
Thumbnail
今天來聊個最近很夯的主題 DDD,但不是 DDD 的本尊 Domain Driven Design,而是無所不在的 Database Driven Design,Database Driven Design 不是不好,只是你的模型容易變成貧血模型,邏輯都集中在 service 層等等。
Thumbnail
有趣的是,Model 其實沒什麼嚴格的定義,所以每個人對 Model 的解讀也不盡相同,有人覺得資料怎麼儲存屬於 Model 的一部份 (受 ORM 工具的影響),有人覺得工作流程 (workflow) 是 Model 的一部份,我個人也有自己的想法,而且隨專案的規模和特性,也不是總是一樣的。
Thumbnail
起源是當時 Facebook 有篇文章討論不少人分不清楚上述二者的差別,當時寫了首部曲《閒談軟體設計:API Naming Style》,接著是《閒談軟體設計:內部函式庫》,但始終沒談到 library 和 framework 的差別,主要是沒有好的例子,這次這例子還蠻不錯的。
Thumbnail
我自己偏好用 Repository 搭配 decorator 來管理 cache,而不是在 controller 層或是到處都有快取的邏輯,如果程式都是透過 Repository 更新資料,Repository 就會是一個不錯的地方更新快取,邏輯也就不會散亂在各處了。
Thumbnail
最近身旁有幾位正在懷孕、或剛生產完的朋友,讓我想起自己在懷孕期間印象最深刻的三件「怪事」,其中又以第三件事最誇張。
Thumbnail
不知道大家在買房之前是不是都會參考親朋好友的意見,或是上網看一些買房注意事項,有時候考慮了這塊就忘了那塊,考慮的那塊又忘了這塊.......
Thumbnail
塔西佗陷阱(Tacitus Trap),一個得名於古羅馬歷史學家塔西佗的政治學理論,意指倘若公權力失去其公信力,無論如何發言或是處事,社會均將給予其負面評價。 當然信著恆信、不信者恆不信,這就是真實的人生。
Thumbnail
關於片名   台灣片名《花漾女子》,原文片名《Promising Young Woman》,台灣譯名將時間定格在悲劇發生前,而原文片名則進一步帶我們看見另一個可能性結果
Thumbnail
前幾年因為身體的關係,當了幾年的律師逃兵,當時開了之前的事務所以後,一時間也沒有特別想要做甚麼事情,所以就邊讀一點書、早晚運動一下,剛好聽到當年同梯朋友進去金融業工作,因此也抱著嘗(ㄊㄠˊ)試(ㄅㄧˋ)的心態,找了份銀行法令遵循的工作