5

閒聊 - 「好程式」跟你想的不一樣! 初讀「重構」有感

 1 year ago
source link: https://blog.darkthread.net/blog/refactoring-and-performance/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

閒聊 - 「好程式」跟你想的不一樣! 初讀「重構」有感

2019-10-17 08:30 PM 16 18,162

從事程式開發超過二十年,我一直不是開發方法論的信徒,對於 TDD、重構、Pair Programming 這些僅止於概略輪廓,不曾深入。不過,寫了超過一萬小時,對於程式該怎麼寫才漂亮,多少也發展出自己的心法,例如:抽取成方法重複利用或共用、將函數結果存成變數重複利用減少呼叫... 等等。不記得是怎麼學會這種做法,也不懂招式名稱,但其實他們都是「重構」(Refactoring)這門學問裡常用的基本手法,叫做 Extract Function、Extract Variable。

前陣子聽說敏捷開發大師 Martin Fowler 的大作「 重構」已出到第二版且有中譯本,雖然不熱衷此道,但要在江湖走跳,增廣見聞確有必要,抱著朝聖的心情入手一本拜讀。

讀了第一章,大驚! 它顛覆我多年來對「好程式」的認知,帶來震憾。歷經一番思考消化,對於「程式好壞」這件事,我有了不一樣新的體認。

Fig1_637069128045828224.jpg

寫了幾十年程式,我心中有一些奉為圭臬的鐵律,其中蠻大比例基於效能考量,例如:

  • 讀取變數比呼叫函式節省資源,故將函式執行結果存成變數重複利用可提升效能
  • 反覆執行的作業,應該要設法合併用最少次數做完

每次看到有人違反這些原則,我的直覺反應是「靠,你會不會寫程式?」

在重構的第一章,Martin Fowler 用了一個劇團發票 JavaScript 程式當範例,展示如何重構,其中有兩招:

原本呼叫 playFor(perf) 將結果存入 play 重複使用三次,重構成每次都直接呼叫 playFor() 函式:

Fig2_637069128047199458.jpg

原本跑一次 for 迴圈同時計算總金額及紅利積點,拆成兩個迴圈,計算總金額跑一次,計算紅利積點跑一次:

Fig3_637069128047704547.jpg

乍讀到這段,心裡浮現滿滿的花惹發! 看完大師示範,覺得自己都不會寫程式了,這是本讓人走火入魔的邪書呀! 心魔既成,後面章節變得難以下嚥,頓失繼續讀下去的動力。

為了打破心魔,向重構專家 91 哥請益,得到一些提點,加上自己反覆琢磨思索,我對於「好程式」有了新認識。

過去我對「好程式」的定義可納歸成一個核心原則 - 極力消滅「重複」,減少浪費。避免相似邏輯的程式碼重複出現(未來修改要改多處),避免反覆呼叫、反覆執行,次數愈少愈有效率。 而 Fowler 有句名言:

任何一個傻瓜都能寫出電腦可以理解的程式,唯有優秀的程式設計師能寫出讓人讀懂的程式。 M. Fowler (1999)

程式執行效率很重要,但好理解容易維護也很重要,很多時候二者無法兼顧,就必須取捨。至於何者為重?以 Fowler 的觀點,當然是後者。

反思多年的開發實際經驗,不得不認同重構精神所主張的 - 程式易讀好維護產生的效益遠大於執行效能。以上面的金額、積點跑兩次迴圈為例,以當今的硬體能力,拆成兩個迴圈增加的時間或許不及 1µs,跑 100 萬次只會多 1 秒。但拆成兩個迴圈有利於後續將邏輯拆解成函式,再進一步實現模組化、物件導向的設計架構。後人接手、修改可節省的時間以小時、以天甚至以月計(更不用提減少程式改壞風險可降低的成本),回頭檢視違反效能最佳化所付的代價,渺小到可以直接忽略。當然,一定有特例我們必須以效能考量優先,放棄方便維護的寫法。對於重構與效能的取捨,Fowler 是這麼建議的:

如果你真的去測量重構前後程式執行時間,會發現差異不大。大部分程式人員(甚至是經驗豐富的老鳥)都無法準確判斷程式寫法的快慢,我們的許多直覺早已被聰明的 Compiler、新一代快取等技術推翻。軟體性能關鍵往往只取決於瓶頸所在的一小段程式,其他部分的修改幾乎都無關痛癢。
但凡事總有例外,有時重構會嚴重影效能,就算如此,我也會選擇繼續重構下去,因為調整重構過的程式容易許多。若重構導致嚴重的效能問題,可在重構後再花時間調整,必要時可以撤銷一些之前做的重構。但在多數情況下,因為重構的關係,可以更有效率地調整效能,最終得到既清楚且快速的程式碼。
對於重構導致的效能問題,整體建議是:在多數情況下,都請無視它。如果重構造成效能變差,請先完成重構,再動手調整提升效能。

看待重構與效能衝突,先重構讓程式好改,之後可再依據效能需求調整,撤銷先前的重構動作也無妨。我想起關聯式資料庫也有「先正規化,再依據效能需求適度反正規化」的設計思維,也是同樣的精神。

想通這點有豁然開朗的感覺,就能繼續看書寫筆記了。老狗也要學會用新角度看世界,加油!


Recommend

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK