[Note] Refactoring — Improving the Design of Existing Code

Cyan Ho
5 min readFeb 3, 2019
Picture from https://martinfowler.com/books/refactoring.html

Any fool can write code that a computer can understand. Good Programmers write code that humans can understand. — M. Fowler (1999)

Martin Fowler 在第二版書封上開宗明義地寫出他對於好的工程師的定義,而重構(refactoring)是成為好的工程師所必經的途徑。隨著軟體規格需求的演進,原本認為好的程式碼,都有可能漸漸變得不合時宜,不論是 junior 或是 senior 的工程師,重構程式碼都是一項不可或缺、必須被不斷執行的技藝。

怎麼樣才算是重構(refactoring)呢?作者的定義如下:

Refactoring is the process of changing a software system in a way that does not alter the external behavior of the code yet improves its internal structure.

本書主要分成兩個部分,前半部分(1~4章)著重在重構的原則與心法,後半部分(5~12章)則專注在實踐各種重構的動機與技巧。在第二版中,作者選擇了 JavaScript 做為範例的程式語言(第一版是用 Java),因為作者認為 JavaScript 是一個大部分工程師都能讀懂的語言,而語言本身其實並不會影響他所要傳達的內容,我認為作者在本書的兩個版本中,分別使用了不同語言實作,就是一個最好的證明。

我認為本書最精彩的地方在前半講述重構原則與心法的部分,作者點出了許多讓我受用不盡的原則,像是:

  1. First refactor the program to make it easy to add the feature, then add the feature.
  2. Before you start refactoring, make sure you have a solid suite of tests.These tests must be self-checking.
  3. Always leave the code base healthier than when you found it.
  4. Three strikes, then you refactor.
  5. The whole purpose of refactoring is to make us program faster, producing more value with less effort.

在重構的定義中,我們了解到重構前後必須保持程式碼的外在行為,這讓自動化測試在重構的過程中扮演了非常重要的角色,作者在上述第二項原則中,更強調了在開始重構前,都必須要有測試覆蓋即將被更改的程式碼區塊。沒有測試保護,我們會害怕破壞原有功能而不敢輕易著手重構(尤其是 legacy code);或是先改了再說,然後程式就遍地開花,然後就踏上了一個回不了頭、沒有盡頭的 debug 旅程。測試不只提供了功能的驗證,還能除祛改動程式碼的畏懼心理,每一個重構步驟完成後,都該立即執行測試驗證,將錯誤範圍縮到最小,這也是作者在書中不斷傳達的重構節奏。

我曾經有一個 class A ,做了很多不是 A 應該關注的事情,在有測試保護 A 的狀況下,我將許多 A 的程式碼抽離至 class B,讓 A 去依賴 B 的介面,並在搬運程式碼的過程中不斷地執行測試驗證 A 對外揭露的功能,很有信心地在不改變 A 的行為下完成了這一系列的重構動作。這次重構完成不久後,偶然在其他的地方發現也需要使用到 class B 的功能,原來對於 class A 的重構不僅僅使 A 的職責變得單純,抽離出來的 class B 還能被其他地方繼續重用,無形中提升了許多開發的速度,讓我深刻體會到了上述第五項原則所闡述的道理:重構讓我們用更少的力氣來達成更快速的開發

作者還有另外一個非常受用的建議:

When you feel the need to write a comment, first try to refactor the code so that any comment becomes superfluous.

因為這條原則,讓我現在的程式碼裡沒有多餘的註解:

這是一段使用 TypeScript 處理登入並發放 Token 的函式,在函式裡透過子函式的名稱來表達每一行程式的意圖,在不需要註解的情況下就能簡潔明瞭地描述出處理登入要做的事情,當然這個函式最一開始並不是長這個樣子,在測試的保護下,中間其實經過了許多次的重構與提煉。但這並不代表註解就是不好的,更多關於註解的討論可以參考這篇文章:

不管好的或壞的程式都需要被持續地重構,軟體之所以被稱為軟體,是因為它隨時都可能會變動,當下好的決定,都可能在未來變成累贅,唯有以變應變,才能保持穩定的開發速度以及步調。

--

--