閒談軟體設計:來煮碗拉麵吧

閱讀時間約 9 分鐘

前言

為什麼是煮拉麵呢?主題是來自前同事在問我為什麼有人的程式好像常常會歪掉,或是變得難維護,後續的討論中,他用的例子就是拉麵,所以... 今天就用程式來煮拉麵吧!
說真的,要煮出一碗好吃的拉麵,其實超級複雜的,網路上可以找到很多影片,下面是我找了一個蠻完整的影片,從熬製湯頭開始,滷製叉燒,到製麵,最後煮麵到上桌,步驟相當繁複。所以,這次我們只從影片的 13 分 35 秒開始,也就是真的煮一碗拉麵開始。

從簡單版開始

這次,雖然反而讓我想超久的,但我刻意不加入任何物件的概念,希望都是由 function 來完成這次的例子,另外,我知道可以用 SOLID、DIP 或是很多聽起來很專業的術語,但只要對解釋幫助不大的,我也盡量不用。先從第一個版本開始,我們用 cookRamen 來製作一碗拉麵:
一般來說,剛開始學程式,這樣寫沒什麼問題,甚至在正式工作上,能先用最簡單的方式將「功能」做對,我覺得也可以,醜但是對的程式,比起花俏但錯的程式有用。只是這樣的程式有什麼問題?
這沒有標準答案,全看用什麼角度來看,以我來說,這程式的抽象度不太夠,滿滿的細節,並沒有將 domain term 使用進去 (等等會解釋,如果不是很懂可以先暫時忽略這些術語)。另外,這個 cookRamen 只能煮豚骨拉麵,沒法煮鹽味拉麵、味增拉麵或是魚介拉麵。

加上變化

假設,先不管美醜,因為需求馬上又進來了,現在希望 cookeRamen 也可以煮鹽味拉麵,加上時間又很趕,於是有人可能選擇,讓 cookRamen 帶個參數,由參數決定煮什麼拉麵,如果是鹽味拉麵,那就是加入鹽醬汁以及雞高湯,完美,不用幾分鐘就改完上線了,因為速度很快,老闆也很開心。
那不是很好嗎?但軟體開發都是這樣的,後面會有越來越多的調整,除了少數只提供一種拉麵的店,大多都提供多種口味,麵的硬度、湯的濃淡都可以調整,像是我蠻喜歡的凪 Nagi,就有五種口味,以及很多可以調整的項目:
另外一個我也蠻喜歡的一幻,有三種口味搭四種湯頭,另外有一個特殊口味只使用雞白湯。
也就是說,如果繼續使用 if-else 或是 switch-case,即便將參數細化,最後只會讓程式變成一個超恐怖,難以維護的怪物,如果又因為沒時間沒有寫測試案例支撐,之後連修改都會膽顫心驚。

建立抽象

所以,也許打從一開始,我們提供的抽象就是錯的,事實上,這回到一開始我說的,抽象程度不夠,缺少 domain 的 term,如果去問拉麵師傅,或是問拉麵迷,問他們拉麵是怎麼煮的,他們也不會說的這麼細,相反,他們可能會這樣說 (這只是個範例,我不會煮拉麵,只是看了很多漫畫得到的冷知識,想學美食相關的知識,可以看《美味大挑戰》,第 38 集有很多拉麵相關的知識):
  • 加熱碗 (冷的碗會讓高湯變冷,喝起來會覺得比較鹹)
  • 調製醬汁 (或叫湯底)
  • 調製風味油 (決定湯頭風味的關鍵)
  • 加入高湯 (濃淡也是在這個步驟調整)
  • 煮麵 (時間決定硬度,越硬越地道,最好麵心還有點生)
  • 加入煮好的麵跟配料
會發現,這個說法不像我們第一個版本那樣充滿細節,這就是一種抽象,將原本很複雜很細瑣的東西,用更高層次的方式去描述,使事情更容易理解,而抽象化的過程中,就會看到 domain expert 用哪些詞彙描述一件事,而這些詞彙便是 domain term。
因此,我們可以用這些 domain term 改寫 (refactor) 第一個版本的程式碼,得到一個更容易讀的版本:
然後,會發現另一件事,即便是要同時能煮豚骨拉麵、味增拉麵、鹽味拉麵或是魚介拉麵,雖然不同的拉麵在某些步驟裡的細節可能不同,但主流程都是類似的,這時要設計彈性的組合也會覺得便容易了,例如:
到目前為止,透過將流程抽象化,搭配參數,已經讓 cookeRamen 變成一個還不錯的程式,要增加味增拉麵,在不需要改動主流程的情況下,能夠很輕鬆地就完成。

優化抽象

但現實有時候會更加複雜一點,而且雖然能做出豚骨拉麵、醬油拉麵和味增拉麵,非常有彈性,但也有一些問題:
  • 程式看不出來這家店有提供什麼拉麵,也可以解讀成,我們仍然是用接近程式的語言在描述問題,而不是接近 domain 的語言在描述問題。
  • 如果要限制醬汁與湯頭的組合,例如味增不能搭配雞高湯 (我隨便說說的,這組合應該還是很好吃),似乎在不改動主流程的情況下做不到?
  • 為了滿足不同喜好的客人,高湯要能有不同的準備方式,例如兌水降低濃度,或是用 1.5 倍的湯煮成更濃的湯,這是行為上的不同,無法簡單用參數就可以替換。
上述的問題中,第三個相較是比較容易解決的,既然無法用單純的參數,那乾脆就把製作的方式當作參數:
如此一來,我們就可以煮出一碗濃的豚骨拉麵和一碗正常濃度的鹽味拉麵,這裡稍微用了 JavaScript closure 的小技巧,其實用跟不用都可以,只是這裡使用 closure 的用意是,提供一個對 cookRamen 來說,不論之後因為其他原因要改變實作,仍然一致的 function signature:一個沒有參數的 function 。如此一來,cookRamen 永遠只需呼叫 prepareSoup 即可。有人猜出來這是什麼了嗎?

讓程式說行話

解決了第三個問題,再回頭看第一個問題,以目前第五個版本來說,我覺得已經是相當不錯了,硬要雞蛋裡挑骨頭的話,就是 options 這個字,不太像是廚房裡會用的詞彙,如果真要選一個字,也許 Recipe 食譜會合適一點。
所以只是把 options 換成 recipe?當然不是,一般來說聽到食譜,會覺得食譜是什麼?指定的食材加上有一定順序的執行步驟,聽起來,好像可以搭配一起解決第二個問題,加入食譜的概念:
在這個版本中,除了 recipe 之外,也加入了 order 的概念,order 就是消費者填寫的那張紙,上面可以選擇口味 (flavor)、醬汁(部分口味可選)、麵的硬度、湯的濃度、配料等等。此外,我們也在程式中可以知道這家店目前提供兩種口味:豚骨拉麵與醬油拉麵,以及這兩種口味各別的食譜,若仔細看食譜會發現,醬油拉麵並沒有加豬背脂,而是在最後配料加完後,淋上滾燙的蔥油,且醬汁跟湯頭都是固定的。
如此,我們在不用 if-else 與 switch-case 的情況下 (個人對 switch-case 的想法可參考《閒談軟體架構:Switch 壞味道》),完成了能夠煮出不同口味、麵的硬度、湯的濃度等各式組合的程式,某種程度上,每個 function 應該都非常易懂,在每個層級上都提供了合適的抽象:
  • 將訂單轉成食譜
  • 然後 cookRamen 照著食譜煮
  • 每個食譜都是用合乎餐廳慣用語的步驟
  • 每個步驟則是由可執行的細節組成
要新增一個新口味時,只需要組合既有的步驟即可,不會影響到其他既有的口味。
到這裡為止,我覺得目前的版本算是一個還蠻不錯的寫法。裡面其實用了很多可以拿來說嘴的東西,像是 strategy pattern (大家覺得哪邊是 strategy pattern 呢?),又或者是 dependency injection (這裡使用哪幾種 injection) 等等,但我如果先講 strategy pattern 然後再講怎麼使用在例子中,似乎幫助不大,畢竟光講完 intent、context 等理論,很多人就已經受不了想睡覺了。
反而這次,不斷地用拆解加組合,慢慢一步一步 refactor 的方式,寫出我心目中的樣子,我自己覺得反而更容易解說為什麼要這樣寫,即便沒有剛剛那一段的描述,應該還是可以看得懂怎麼去改善一段程式。

結論

至於所謂資深的工程師是否第一次就要寫出最終版的樣子,我覺得倒未必,視已知的需求而定,但最起碼,要能寫到 v3 的樣子,然後能夠依需求的變化,改寫到 v6 或是更適合新需求的樣子。如果需求就是只煮一種拉麵,花很多時間寫出 v6 其實效益不大,但我個人不會寫出 v1,因為不好讀。切記,只要是有一定複雜度的需求,就沒有一次就寫好的程式,重點是如何隨著需求變化,不斷地改善程式。

番外篇

好了,時間到這邊也差不多該收尾了,這次雖然用 JavaScript 設計例子,但真的要用 Java 或是語言改寫成由 interface/class 組成的程式也很簡單,有興趣的可以自己試試。最後,煮拉麵的程式不只有一種寫法,上面有六種不同的版本,我相信有其他更好的版本,如果看官覺得你的方式更好,也歡迎來煮碗拉麵並分享吧!
大概用了半小時,把 Java 的版本寫出來,注意,主要用 interface、enum 和 record,盡可能簡潔寫,但行數確實比 JavaScript 的版本多一些。
夜深了,來碗雞白湯拉麵當消夜吧!

後記

這是首篇從 Medium 搬過來的文章,整體體驗跟 Medium 還蠻像的,但有幾點比較可惜,貼上 gist 和 YouTube 連結時,似乎無法像 Medium 自動轉換,最可惜的應該是程式碼區塊吧,Medium 最近改版的程式碼區塊很好用,在新文章中我都懶得用 gist,但這邊的程式碼區塊還是得嵌入 gist。
即將進入廣告,捲動後可繼續閱讀
為什麼會看到廣告
53會員
102內容數
這是從 Medium 開始的一個專題,主要是想用輕鬆閒談的方式,分享這幾年軟體開發的心得,原本比較侷限於軟體架構,但這幾年的文章不僅限於架構,也聊不少流程相關的心得,所以趁換平台,順勢換成閒談軟體設計。
留言0
查看全部
發表第一個留言支持創作者!
你可能也想看
Google News 追蹤
Thumbnail
接下來第二部分我們持續討論美國總統大選如何佈局, 以及選前一週到年底的操作策略建議 分析兩位候選人政策利多/ 利空的板塊和股票
Thumbnail
🤔為什麼團長的能力是死亡筆記本? 🤔為什麼像是死亡筆記本呢? 🤨作者巧思-讓妮翁死亡合理的幾個伏筆
Thumbnail
我為什麼喜歡【怪醫豪斯】?很大一部分是因為這個主角應該是史上最迷人的角色之一。以現在角色來說,豪斯醫生就是個爛人,但是他的天才又讓人對他又愛又恨。
Thumbnail
一、從小觀察 從上電腦課學會用 Excel 這個文書處理軟體時開始注意到爸爸有用 Excel 記帳的習慣(簡單記錄,有時候會忘記金額所以不一定確實),每次爸爸發現我在觀察他記帳的時候一定會對我說:「固定支出越少每個月才能存到更多錢。」以及「不要小看雜支,常常花最多花得最莫名其妙的就是雜支。」 高
Thumbnail
通常,看大家聊職場話大多比較嚴肅,所以今天想聊些比較輕鬆的話題,是關於我職場上的LGBTQ族群間會出現的家常幹話
Thumbnail
看到有人因為這次的事情在說這是因為沒有校園沒有髮禁、禁止體罰、延後到校時間導致學生逐漸沒有紀律,覺得自己什麼都能做......我第一個想到的是我爸說他高中時畢業典禮教官只要走得慢一點一定會被憤怒的學生蓋布袋拖去打。發生這樣的事情當然得檢討,但希望不是以一種意氣用事、情緒主導的心態......
Thumbnail
「一群人所決定的,就是對的嗎?」 這是民主的盲點也是缺點
Thumbnail
前言須知-《閒人隨筆》 看官沒錯,歡迎來到閒人的復耕系列《閒人隨筆》,沒錯本系列所有作品是閒人將以往作品重新經查證,彙整的新文章。白話說法 補足故事彙整或翻譯失誤等。 今日配合之前的拔劍篇在轉一篇,
Thumbnail
最近身旁有幾位正在懷孕、或剛生產完的朋友,讓我想起自己在懷孕期間印象最深刻的三件「怪事」,其中又以第三件事最誇張。
Thumbnail
不知道大家在買房之前是不是都會參考親朋好友的意見,或是上網看一些買房注意事項,有時候考慮了這塊就忘了那塊,考慮的那塊又忘了這塊.......
Thumbnail
關於片名   台灣片名《花漾女子》,原文片名《Promising Young Woman》,台灣譯名將時間定格在悲劇發生前,而原文片名則進一步帶我們看見另一個可能性結果
Thumbnail
前幾年因為身體的關係,當了幾年的律師逃兵,當時開了之前的事務所以後,一時間也沒有特別想要做甚麼事情,所以就邊讀一點書、早晚運動一下,剛好聽到當年同梯朋友進去金融業工作,因此也抱著嘗(ㄊㄠˊ)試(ㄅㄧˋ)的心態,找了份銀行法令遵循的工作
Thumbnail
接下來第二部分我們持續討論美國總統大選如何佈局, 以及選前一週到年底的操作策略建議 分析兩位候選人政策利多/ 利空的板塊和股票
Thumbnail
🤔為什麼團長的能力是死亡筆記本? 🤔為什麼像是死亡筆記本呢? 🤨作者巧思-讓妮翁死亡合理的幾個伏筆
Thumbnail
我為什麼喜歡【怪醫豪斯】?很大一部分是因為這個主角應該是史上最迷人的角色之一。以現在角色來說,豪斯醫生就是個爛人,但是他的天才又讓人對他又愛又恨。
Thumbnail
一、從小觀察 從上電腦課學會用 Excel 這個文書處理軟體時開始注意到爸爸有用 Excel 記帳的習慣(簡單記錄,有時候會忘記金額所以不一定確實),每次爸爸發現我在觀察他記帳的時候一定會對我說:「固定支出越少每個月才能存到更多錢。」以及「不要小看雜支,常常花最多花得最莫名其妙的就是雜支。」 高
Thumbnail
通常,看大家聊職場話大多比較嚴肅,所以今天想聊些比較輕鬆的話題,是關於我職場上的LGBTQ族群間會出現的家常幹話
Thumbnail
看到有人因為這次的事情在說這是因為沒有校園沒有髮禁、禁止體罰、延後到校時間導致學生逐漸沒有紀律,覺得自己什麼都能做......我第一個想到的是我爸說他高中時畢業典禮教官只要走得慢一點一定會被憤怒的學生蓋布袋拖去打。發生這樣的事情當然得檢討,但希望不是以一種意氣用事、情緒主導的心態......
Thumbnail
「一群人所決定的,就是對的嗎?」 這是民主的盲點也是缺點
Thumbnail
前言須知-《閒人隨筆》 看官沒錯,歡迎來到閒人的復耕系列《閒人隨筆》,沒錯本系列所有作品是閒人將以往作品重新經查證,彙整的新文章。白話說法 補足故事彙整或翻譯失誤等。 今日配合之前的拔劍篇在轉一篇,
Thumbnail
最近身旁有幾位正在懷孕、或剛生產完的朋友,讓我想起自己在懷孕期間印象最深刻的三件「怪事」,其中又以第三件事最誇張。
Thumbnail
不知道大家在買房之前是不是都會參考親朋好友的意見,或是上網看一些買房注意事項,有時候考慮了這塊就忘了那塊,考慮的那塊又忘了這塊.......
Thumbnail
關於片名   台灣片名《花漾女子》,原文片名《Promising Young Woman》,台灣譯名將時間定格在悲劇發生前,而原文片名則進一步帶我們看見另一個可能性結果
Thumbnail
前幾年因為身體的關係,當了幾年的律師逃兵,當時開了之前的事務所以後,一時間也沒有特別想要做甚麼事情,所以就邊讀一點書、早晚運動一下,剛好聽到當年同梯朋友進去金融業工作,因此也抱著嘗(ㄊㄠˊ)試(ㄅㄧˋ)的心態,找了份銀行法令遵循的工作