2024年4月15日 星期一

易讀程式之美學 : 提升程式碼可讀性的簡單法則

程式書種類非常多,讀書的時間也有限,

因此每一次挑選要看的書之前,

都會思考什麼樣的書對於當前的工作環境最有幫助?

近期接手的專案發現閱讀起來非常痛苦,

應該有許多可以改進的程式碼撰寫方式,

因此手邊這本

“易讀程式之美學 : 提升程式碼可讀性的簡單法則”

應該是最能成為即戰力的書籍了。

看完之後,由於當前負責接手的專案是另一位同事開發的,

詢問了該同仁,是否可以拿他的程式碼當範例,提供一些建議給他,

他也一口答應,讓我多了許多素材,感謝他。

雖然我還沒整理出來,就已經聽到他要離職了。

我想盡可能紀錄該書提到的主題,並從實際案例找到範例,

由於內容很多,因此應該會分為幾篇文章來記錄。


好的程式碼?

什麼叫做好的程式碼?如何定義什麼是好?

執行速度快就是好的程式碼嗎?如果寫的醜才能讓效率最快呢?

因此好的程式碼,應該從你的需求為出發點去定義。

  • 你可能會希望程式碼好閱讀,不要求效率。
  • 你可能會希望程式碼執行起來越快越好,不論後續好不好維護。
  • 你可能會希望程式架構模組化,儘管會讓架構變得相當複雜。

只要符合你的需求,那就會是好的程式碼,

但長期來說,容易閱讀與理解會是大家都期望的通則。


隨著年資的累積(當然我還是菜鳥),看過的程式碼越來越多,就越能體會到好壞的差異。

通常剛開始比較能夠感覺什麼是壞的程式碼

例如一段程式碼非常難以理解,你覺得很爛,

原因很可能是排版混亂、變數命名無法理解、過多的迴圈甚至是過於簡潔,

這些都可能造成閱讀困難,而且也非常常見。

因此反過來會朝向以下目標進行改善:

  •     簡潔好閱讀:排版漂亮
  •     長度適中    :個人喜好把程式碼區段控制在螢幕可視的範圍內
  •     易於理解    :有時候過度簡潔,或者語法太過前衛,都可能造成理解困難

讓讀者理解的所需時間降到最短

此時越讀到這段程式碼就會感覺非常輕鬆,馬上就理解了,

自然認為是一段好的程式碼。


但你可能會想,

"只有我維護,為什麼需要讓別人理解?"

因為在職場上,頻繁的交替維護不同專案是很常見的事情,

而且交替的期間可能幾個月至幾年,回來看的時候已經忘記當時開發的時空背景,

而就是這個時空背景有助於當下的理解

在忘記"當下時空背景"的情況,程式碼就會變得難以理解,

因此把程式碼寫好,讓閱讀起來容易與順暢,就是非常重要的一件事。

而要做好這件事並不難,利用以下幾點就可以立即改善。

  • 命名
  • 排版
  • 註解


邁出改善的第一步:命名

大概所有的軟體書籍都會討論到這個議題,

因為實作起來簡單,也有立竿見影的效果,但卻是大家最常忽略的。

會不會是我們在學習程式語言的時候,上課或書上的範例都是a,b,c來命名變數造成的錯誤觀念XD


我自己也會遵守的幾個的原則:

  • 避免縮寫
  • 盡量詳細,儘管名稱可能變長
  • 避免可解釋成太多意義的名稱

但這只是大原則,

就是避免縮寫可被解釋成不同的意義。

以第三點來說,如果有某些單字是在你的領域是大家都一致認定的意義,

那就沒什麼問題,其實重點就在於維護(或未來可能需要維護)該軟體的團隊成員,

都要能夠輕易理解,因此確認命名好壞最簡單的方式就是,

直接與你的同事討論吧!

這非常有用,花費的時間也短,對後續的維護成本能夠降到最低。

因為一開始就有共識了,即使雙方都忘記了,淺在的意識也不會偏離太遠。


之前閱讀完”Clean Code:無瑕的程式碼“之後,

對我的命名方式產生了很大的影響,

非常建議讀一讀。


來看看實際上遇到的範例:

(案例都是我實際看過,但為了避免敏感資訊,會做一些修改)

涵蓋太多意義的命名

我需要控制一台量測電壓電流的設備,因此有一些設定參數的function。

乍看之下 DeviceSampleRateSetting() 這個function命名沒什麼問題,

但我實際使用時才發現,原來撰寫這個function的同事,

只在裡面包裝了設定Voltage的SampleRate,

因為當時他只用到電壓的量測,所以並沒有設定Current(電流)SampleRate的動作,

我也誤會只要是設定SampleRate,就是使用該Function,

多花了一些時間除錯,但其實是可以省下這時間的。

因此命名常用名稱時必須注意,是否可能會造成誤會,不要有模糊空間

如下所示,直接把設定的名稱寫上:

但這樣還不夠好,雖然function名稱已有SampleRate,

但無法第一時間得知參數就是該SampleRate,

試著讓function名稱和參數有連結性。


設定是一個動作,讓名稱有動詞、主詞與受詞,

因此可將名稱修改如下:

讓function讀起來就像讀一般句子一樣,

也很明確得知第一個參數肯定是SampleRate,

往後要找設定相關的function時,

從名稱開頭就可以清楚知道這是些什麼function。


再來看另一個案例,關於變數的命名。

很常見的在function開頭宣告了很多的變數,

我認為有三點可以進行改善。

  • 避免變數開頭套用型別縮寫

    • 現在的編譯器已經很方便,滑鼠指過去就能得知該變數的型別,不像過去都需要看到宣告才能知道型別,所以之前才會在變數名稱前加上型別,而節省下來的文字能讓程式碼看起來更整潔。
    • 來看第一個改變,我習慣讓所有的變數對齊,更方便閱讀,而非Local的變數則隔一行作區分:

  • 駝峰式命名
    • numberofReadings中的of並沒有以大寫開頭,會造成單字合併與閱讀困難,建議以駝峰式命名,讓單字組合起來時還能順暢閱讀 (雖然我不知道為什麼Reading要加s):

修改為   

  • 命名無法助於理解
    • 以上三個陣列名稱都無法從名稱得知該陣列裝了什麼資料,程式碼一長,很容易發生要回頭尋找資料來源的情況,徒增開發與回憶的時間,以下嘗試修改:
    •  rxBuf → voltageDataFromDevice
      • 藉由閱讀程式碼得知他是從儀器讀回來的voltage原始資料,那麼可以將它命名為: voltageDataFromDevice ,Device可替換成你使用的儀器型號,這樣更為直覺。
    • dataArr → parsedDataForOutput
      • 藉由閱讀程式碼得知他是經過處理後的資料,且要轉換成string型別,以利後續顯示或輸出成文檔,則命名可以修改成 parsedDataForOutput。
    • factorBuf → isVoltageDoAverage
      • 原本是完全沒有意義的命名,查閱之後才發現,此陣列對應每一個voltage資料,告訴儀器是否進行多筆量測且平均後輸出單筆的動作,而型別使用bool會更適合,且is開頭的命名即告訴閱讀者,只有是或否的結果儲存在內。

修改後如下:



藉由以上的改善,儘管程式碼看起來多了一點,但對我自己來說,

應能協助在幾個月後快速理解此段程式碼。


要長還是要短?

以往常聽到程式碼越短越好,但過短反而容易失去許多資訊。

命名也相同,短命名可以讓程式碼看起來簡單有力,甚至是透過縮寫來縮短命名。

但如果沒有補充足夠的註解來說明這些命名,則容易再日後造成理解困難。

因此在這幾本書中,我得到一個有用的建議:

命名的長短,視變數的使用範圍來決定。

我自己的習慣是,

如果在一個小小的範圍內,例如再螢幕的範圍內,

容易查看上下文去理解該名稱的意義,那麼短命名則無所謂。

如果範圍過大,需要上下滾動滑鼠來查閱定義,那麼命名可以越詳盡越好,

減少透過上下文理解意義的時間。


由於書中內容提及不少層面,

但目前我能夠理解後輸出的部分還不多,

寫到這邊已經覺得我的表達能力不足,無法一一舉例去解釋後續的內容,

這應該是受限於我沒有足夠的範例去解釋,又或者我不夠熟悉。

因此先以最基礎也最有幫助的改善命名作為本書的心得記錄。

沒有留言:

張貼留言

社會新鮮人如何投資?

我的觀點是,在 沒有很多 本錢 的情況下, 別寄望每個月幾千元放到股票或者最近很夯的高股息ETF就能讓你致富, 先投資自己,讓自己的本業收入提高吧。