閒談軟體設計:Switch 壞味道

更新於 發佈於 閱讀時間約 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
留言分享你的想法!
Spirit-avatar-img
發文者
2024/05/23
書摘《設計重構》提及了這篇文章,趕快過去看看吧!
Spirit-avatar-img
發文者
2023/10/19
閒談軟體設計:架構師難尋?提及了這篇文章,趕快過去看看吧!
avatar-img
Spirit的沙龍
53會員
104內容數
這是從 Medium 開始的一個專題,主要是想用輕鬆閒談的方式,分享這幾年軟體開發的心得,原本比較侷限於軟體架構,但這幾年的文章不僅限於架構,也聊不少流程相關的心得,所以趁換平台,順勢換成閒談軟體設計。
Spirit的沙龍的其他內容
2024/03/23
這篇文章探討了在軟體開發中的技術債可能來自哪些原因,以及如何自動化偵測與修復技術債。作者透過分享不同情境下的技術債選擇,提供了對於技術債的思考與建議,針對開發人員在需要做出無奈的技術決策時,提供了一些建議。此外,還提供了一些在做出技術決策時的方法,如保留抽象層和避免vendor lock-in。
Thumbnail
2024/03/23
這篇文章探討了在軟體開發中的技術債可能來自哪些原因,以及如何自動化偵測與修復技術債。作者透過分享不同情境下的技術債選擇,提供了對於技術債的思考與建議,針對開發人員在需要做出無奈的技術決策時,提供了一些建議。此外,還提供了一些在做出技術決策時的方法,如保留抽象層和避免vendor lock-in。
Thumbnail
2024/03/09
今天來聊個最近很夯的主題 DDD,但不是 DDD 的本尊 Domain Driven Design,而是無所不在的 Database Driven Design,Database Driven Design 不是不好,只是你的模型容易變成貧血模型,邏輯都集中在 service 層等等。
Thumbnail
2024/03/09
今天來聊個最近很夯的主題 DDD,但不是 DDD 的本尊 Domain Driven Design,而是無所不在的 Database Driven Design,Database Driven Design 不是不好,只是你的模型容易變成貧血模型,邏輯都集中在 service 層等等。
Thumbnail
2024/03/02
有趣的是,Model 其實沒什麼嚴格的定義,所以每個人對 Model 的解讀也不盡相同,有人覺得資料怎麼儲存屬於 Model 的一部份 (受 ORM 工具的影響),有人覺得工作流程 (workflow) 是 Model 的一部份,我個人也有自己的想法,而且隨專案的規模和特性,也不是總是一樣的。
Thumbnail
2024/03/02
有趣的是,Model 其實沒什麼嚴格的定義,所以每個人對 Model 的解讀也不盡相同,有人覺得資料怎麼儲存屬於 Model 的一部份 (受 ORM 工具的影響),有人覺得工作流程 (workflow) 是 Model 的一部份,我個人也有自己的想法,而且隨專案的規模和特性,也不是總是一樣的。
Thumbnail
看更多
你可能也想看
Thumbnail
「欸!這是在哪裡買的?求連結 🥺」 誰叫你太有品味,一發就讓大家跟著剁手手? 讓你回購再回購的生活好物,是時候該介紹出場了吧! 「開箱你的美好生活」現正召喚各路好物的開箱使者 🤩
Thumbnail
「欸!這是在哪裡買的?求連結 🥺」 誰叫你太有品味,一發就讓大家跟著剁手手? 讓你回購再回購的生活好物,是時候該介紹出場了吧! 「開箱你的美好生活」現正召喚各路好物的開箱使者 🤩
Thumbnail
歡迎來到 【🔒程式碼質量案例 】 系列, 這邊我們將分享實際開發過程中會遇到的程式碼難以維護的案例及解決方法, 期望透過這些技巧讓我們的產品更加的穩健也更容易於維護。 P.S 本篇會以Python程式語言進行示範, 其他語言的處理方式也雷同, 包括Javascript、Golang…等。
Thumbnail
歡迎來到 【🔒程式碼質量案例 】 系列, 這邊我們將分享實際開發過程中會遇到的程式碼難以維護的案例及解決方法, 期望透過這些技巧讓我們的產品更加的穩健也更容易於維護。 P.S 本篇會以Python程式語言進行示範, 其他語言的處理方式也雷同, 包括Javascript、Golang…等。
Thumbnail
我個人是盡可能不寫 switch statement,但觀察這幾年程式語言的趨勢,會發現許多語言把 switch statement 擴充成為實作 pattern matching 的工具,說不定以後 switch statement 會越來越廣泛使用也說不定。
Thumbnail
我個人是盡可能不寫 switch statement,但觀察這幾年程式語言的趨勢,會發現許多語言把 switch statement 擴充成為實作 pattern matching 的工具,說不定以後 switch statement 會越來越廣泛使用也說不定。
Thumbnail
不管用哪種語言開發軟體,除非是那種一個 function 寫個幾萬行的人 (來人啊,把這種人拖去砍了),不然,一般都會根據某些因素,切割成模組或是特定功能的區塊 (一個 class 或是一個 function),但要完成一個特定功能,這些模組或區塊勢必要一起合作,因此這些模組與區塊就發生了關係。
Thumbnail
不管用哪種語言開發軟體,除非是那種一個 function 寫個幾萬行的人 (來人啊,把這種人拖去砍了),不然,一般都會根據某些因素,切割成模組或是特定功能的區塊 (一個 class 或是一個 function),但要完成一個特定功能,這些模組或區塊勢必要一起合作,因此這些模組與區塊就發生了關係。
Thumbnail
方法鏈接和流暢接口在許多現代編程語言和框架中都有使用。這兩個概念有時互相重疊,因為流暢接口通常使用方法鏈接來實現。使用這些技巧可以提高程式碼的可讀性和維護性,使得編碼更符合人類語言的結構。這對於在專案中協同工作的團隊尤為重要,因為它可以讓每個人更容易理解和使用代碼。
Thumbnail
方法鏈接和流暢接口在許多現代編程語言和框架中都有使用。這兩個概念有時互相重疊,因為流暢接口通常使用方法鏈接來實現。使用這些技巧可以提高程式碼的可讀性和維護性,使得編碼更符合人類語言的結構。這對於在專案中協同工作的團隊尤為重要,因為它可以讓每個人更容易理解和使用代碼。
Thumbnail
這篇文章將會分享 Clean Code 關於函式的重點,內容主要以個人閱讀後有印象的部分著手,有興趣了解更多請自行購買這本書。
Thumbnail
這篇文章將會分享 Clean Code 關於函式的重點,內容主要以個人閱讀後有印象的部分著手,有興趣了解更多請自行購買這本書。
Thumbnail
之前提過 IF 跟 IFS 這兩個可以做條件判斷的函式,但你知道其實條件判斷還有一手!今天要介紹的叫 SWITCH 函式,它跟 IF、IFS 一樣,可以做多條件的判斷,且可以在沒有相符結果時,傳回你指定的預設值。
Thumbnail
之前提過 IF 跟 IFS 這兩個可以做條件判斷的函式,但你知道其實條件判斷還有一手!今天要介紹的叫 SWITCH 函式,它跟 IF、IFS 一樣,可以做多條件的判斷,且可以在沒有相符結果時,傳回你指定的預設值。
Thumbnail
介紹 何謂原則(Principle) A principle is a concept or value that is a guide for behavior or evaluation 所謂【原則】(Principle)就是一種【概念】或【價值】,用來導引你產生適切的行為與價值評量方法 白話文
Thumbnail
介紹 何謂原則(Principle) A principle is a concept or value that is a guide for behavior or evaluation 所謂【原則】(Principle)就是一種【概念】或【價值】,用來導引你產生適切的行為與價值評量方法 白話文
追蹤感興趣的內容從 Google News 追蹤更多 vocus 的最新精選內容追蹤 Google News