This is my hot take after I watched a video about "File Lines of Code".
前言
我不確定 "hot take" 比較合適的中文該怎麼說。用這個詞,是因為這裡要說的都是我個人的強烈主觀意見,甚至是偏見。
會寫這些碎碎念的東西,是因為有一天在臉書貼文有一位大大(未徵求對方同意,不敢寫出名號)留言提到《重構的時機與實作:五行程式碼規則》這本書建議(理想的)函式應該盡量不超過 5 行程式碼。
英文書名是 "Five Lines of Code: How and When to Refactor",作者是 Christian Clausen。
我想說:「哇!真的嗎?真的是提倡五行程式碼嗎?」基於好奇,我先去找了作者的一場研討會的實況錄影,底下是該影片的 Youtube 連結:
https://www.youtube.com/watch?v=APdaacGmDew
大致看了一下,我突然懷疑自己是不是跟現代軟體工程脫節得太嚴重了——當然我內心是一定不肯承認的啦。
OK,所以底下是我的 hot take。
一開始就錯了
如下圖,擷取自該研討會的影片。作者以一段程式碼為範例,展示如何重構得更好。
截圖的左邊是重構前,右邊是重構之後。
我知道影片後面還有繼續進一步的重構,但我認為這方向一開始就錯了,然後朝著錯誤的方向努力重構,怎麼樣也不會達到終點。我說的終點是「易讀、易懂、好維護的程式碼。」我相信作者的目標也是這樣。但我不懂的是,把一個簡單易懂的程式碼搞得更複雜、需要花更多時間閱讀更多細碎的程式碼和抽象層,是把事情簡化還是反而複雜化了?
一個可能的原因是,那個例子只是單純示範用的,亦即作者只是拿比較簡單的範例來示範如何進行他所要推廣的重構方法和技巧,讓觀眾學得技巧之後,可以用來對付真正複雜的場景。如果是這樣,那我覺得就比較說得通。但我希望作者能在示範過程中提醒讀者:「如果實際上的需求真的像這個範例那麼簡單,是不需要重構成那麼複雜的。」但似乎沒有。
如果規則改成 10 行程式碼如何?
不是 5 行還是 10 行的問題,是這樣的規則太過死板,難以適用各種情況。
書中說(如上圖),任何方法都能改成五行以內。那麼,這裡有個例子:把 RGB 轉換成 HSL 的函式(開啟頁面之後搜尋 "rgbToHsl")。為了避免連結失效,我把該函式截圖放上來:
這個函式總共 37 行。別說重構成 5 行,連要改成 10 行以內都不切實際。(我讓 ChatGPT 用 C# 寫一個同樣功能的函式,去除註解之後也差不多要 37 行左右。)
我贊成在維持程式碼易讀易懂好維護的前提下盡量讓程式碼簡短,但並不是只要簡短就好。
追求 Clean Code 的極致?
我不免猜想,作者 Christian Clausen 或許是那種追求 Clean Code 極致的理想主義者吧。這不是要批評作者,只是我嘗試去推想和理解。而且,他的著作可能有許多值得參考與學習的觀點,我只是碰巧看到自己覺得怪怪的地方罷了。
後來,我又看到那本書的第 4 章,4.1.1 節,標題寫著:「Rule: NEVER USE IF WITH ELSE」,中文的意思就是「規則:IF 後面絕對不要接 ELSE」。如下圖,右下角的迷因圖代表我當時看到這條規則時的心情。
是沒錯,這條規則的詳細描述還有但書:「unless we are checking against a data type we do not control」。但在我看來,那條規則還是過頭了,不應該成為「規則」。
我的意思是,我自己寫的程式,也經常有避開 else 的情形。例如一個函式裡面有很多返回的 exit points,我用 if 條件判斷時,通常會先判斷能直接返回的條件,符合的話就 return,不符合的話,繼續往下走,也就不需要寫 else 了。可是,這有到需要立一條規則的程度嗎?總是會有一些情況需要使用 else 的嘛。
令人擔心的是,初學者可能都還沒能夠寫出可正常運行的程式,就被這些規則佔據心頭,時時干擾;原本應該先把客戶要的功能實作出來,卻一開始就在煩惱如何雕琢程式碼。此外,如果 code review 時有人拿出這條規則要求讓 else 消失,否則 pull request 無法合併,那恐怕會變成一場災難。
我想說的是
我想說的是,switch 並不邪惡;說超過 3 個或 5 個 if 就一定不好,也未免言重了。一個函式超過 5 行或 10 行,也無需不好意思或內疚。這所有的一切,真正重要的是:
- 你的程式跑起來結果正確嗎?符合客戶需要嗎?
- 以後維護的時候好維護嗎?
只有當程式碼更好維護,加入更多抽象層或特定語法機制(如繼承或多型)才能 justify 其合理性。
而不是:你看我程式碼這樣寫,多漂亮啊!多符合某個教條規則啊。No,那不是最重要的,它們甚至不該成為教條(以免僵化)。
很多時候,僅只是結構化和模組化,就已經能帶來很大的好處(對可維護性而言)。當需要的時候,封裝很好,多型很好,只要加入他們是真的為了解決需要解決的複雜場景。但我不認為任何時候只要有封裝就一定是對的設計,或者用上多型就是好的設計。如果一個簡單的 switch 就能解決問題而且易讀易懂好維護,我看不出有任何理由去 refactor 成更多細碎的型別和細碎的 functions——恕我直言,這真的像在搬磚砸腳。
那麼,一個函式怎樣才算太長?
有很多判斷方式,但很難有個標準。我喜歡這個寬鬆的建議:
Suggestion: 當一個函式超過 VS Code (或你正在使用的 IDE)的編輯視窗範圍而需要上下捲動,就當成一個警訊,提醒自己函式可能過長,不容易一看就懂。
謎之音:把編輯器的字型調得很小就不會超出編輯視窗囉! (哈,這樣眼睛可能很容易疲勞)
此外,也可以透過一些輔助工具來提醒,例如 VS Code extension:SonarLint。以下動畫展示了當它偵測到一個 Go 函式太長、太複雜時出現的提示:"Cognitive Complexity of functions should not be too high (go:S3776)"。
(動畫圖片中的範例程式取自:golongfuncs)
結語
如前面說的,Christian Clausen 的 "Five Lines of Code: How and When to Refactor" 可能有許多值得參考與學習的地方。寫這篇文章並不是要對那本書做出整體評價,而只是對其中一小部份內容說說個人想法而已。也許,我只是碰巧看到一些我不該看到的小地方。
也許,開始學習 Golang,開始用它的角度來看待程式設計,也對我的腦袋產生了一些影響。但就如首屆圖靈獎得主 Alan Peris 說的:
「如果一個語言不會影響你對程式設計的想法,那個語言就不值得去學。」
A language that doesn't affect the way you think about programming, is not worth knowing.
Anyway, just my hot take.
沒有留言: