探索重構

Ken
Apr 15, 2022

--

Photo by Randy Fath on Unsplash

前言

身為一位優秀的 Software Engineer(SWE),就是將自身具備軟體工程知識、商業領域知識應用到軟體開發中,並且產出優良、具有商業價值的軟體。軟體開發的過程中,總是不斷循環著決策、規劃、執行及驗收等週期。

要生產一個好的軟體,從最開始的需求探索、釐清…到實際進入開發環節時的架構規劃、介面設計、細節實作,乃至最後的驗收、驗證等一系列漫長的過程。

SWE 日常主要的工作任務包含:撰寫程式碼(實踐商業需求)、撰寫測試及除錯等;而重構一詞,經常在撰寫程式碼、測試的環節時被聽見。

重構在軟體開發中的意義是什麼?能為軟體帶來什麼更大的價值?今天就要來與大家一起探索何謂重構。

什麼是重構?

簡單來講,在不改變一段程式碼對外部互動、行為的前提下,修改這一段程式碼,便稱為「重構」。

重構不會讓軟體的使用者感受到變化、意外。換句話說,重構不能改變整體功能。

範例:來預測一段程式碼

example code

乍看之下,我們沒辦法立即讀懂 getResult 是什麼,以及為什麼要乘 9.8 這個數字,藉由註解可能才得以勉強預測出該 function 代表的意涵及其意圖。

例如,在我們看了下面這張圖、或是規格書,加上程式碼中的註解,我們才比較明白、肯定這段程式碼!

Specification of example code

然而,經過下面這一段重構後,會讓我們更容易理解這段程式碼的作用--甚至可以不言自明、移除註解。

(註:重構的類型有很多種、包含修改命名、修改底層實作, etc …。但只要符合上述的定義,皆可稱為重構。)

example code after refactoring

如此我們便得以淺嚐 「重構」好處。但是,這邊有個問題:既然這些程式碼的功能已經完備,又為什麼要回來修改呢?

畢竟,重構是個大費周章的行為,看似又沒辦法立即獲得好處。

為什麼要重構?

要探討為何需要重構,其實更應該往問題背後的問題去思索。重構能為我們帶來什麼好處?能為開發者、軟體或是企業帶來什麼更大的價值

畢竟,重構對企業本身就是一個成本,重構將耗費企業的資源(如人力、時間),這些東西都是機會成本。工程師勢必將回答:為何不將人力、時間投入新功能研發或接下更多的新案子等,而是回頭進行重構?

從軟體的壽命長度出發:

舉例而言,若某個案子只是給行銷、企劃用一次性活動網頁,只需存在幾天至幾個月;或是某個軟體僅是為了 Demo, 想快速進行實驗、市場驗證等。在開發初期,這些專案便暫時不需要考慮重構。畢竟,這些專案的壽命可能很短,時間一到就會消失。

相反地,假如很幸運地!經過商業價值驗證後,我們過去嘗試的概念,在市場存活下來;或者我們在開發、維護的產品,本身就是壽命很長的項目。我們或許將開始遭遇困難、痛點。這時候,就可以開始思索重構可能帶來的價值。

現有的痛點:

在當今快速、多變的社會下,既有的軟體將頻繁面臨新功能擴充需求改變。隨程式碼逐漸增加,軟體複雜度也跟著上升。畢竟,如果我們要修改前人的程式碼,或基於前人的程式碼增添新功能,勢必要對程式碼整體有一定程度的理解。當程式碼行數越來越多,開發者要面對、理解的內容就更多,軟體本身的複雜程度也持續上升。

屆時,軟體的身材,就會從原本的窈窕纖細,不斷地被餵養程式碼增胖,肥胖臃腫的程式碼將造成三項痛點:

  1. 難以基於現有基礎(快速)擴充、增添新功能
  2. 難以修改、調整現有的功能
  3. 難以除錯

因此我們會開始想對軟體進行塑身、雕塑美好身形,僅漂亮地保留需要的部分即可。

基於以上 3 點,我們可以將重構的目的(為何重構)定調於

  1. 讓軟體能更容易被理解,因為好理解即代表好調整、修正;我們在上述位能 PE 的例子,已經可以看出這個好處;
  2. 改善軟體的設計、架構及消除不必要、重複的程式碼,精練程式碼,因為好的架構使程式碼更容易擴充、修改;
  3. 使 Bug 更容易被找出、發現(基於上述兩點,也更容易發現、找出錯誤)。

綜合以上三點,重構是為了能讓工程師更具生產力、能夠更快速地開發程式。

重構:為了提升程式的開發速度、軟體可擴充程度

何時要重構?

首先,當我們想到要重構時,腦中一定會出現重構的理由。畢竟,進行重構也是一個時間成本,而重構必須保持相同的 input 能獲得一樣的 output。換句話說,重構僅是更改內部實作而已。在這樣的狀況下,老闆也不會希望工程師的產能無故消耗在那,影響產品其他新需求或 Road Map 的推進。

另外,不同類型的重構,有其規模及對應的成本。我們必須根據專案、軟體產品當下的商業階段,以及重構類型,來決定何時該進行重構。

重構的理由百百種,這邊提 2 種:

1. 投資報酬率

時間即金錢,此時我們更應該想一下:為什麼我們需要重構?再接著決定如何安排重構計畫。

比如說,重構的理由可能是產品需求改變,或要在既存的程式碼內擴充新功能時,我們發現既存寫法難以擴充、改變,難以適應新需求。現階段可能有更適合的做法,或許可以藉此機會,調整成為更具擴充性的結構。

又或者是,當我們在開發新需求時,不斷受到舊的程式碼阻礙,耽誤了開發時間。這時候,也可以趁此機會思考,能不能調整些許既存的程式碼,讓現在的新功能更好開發。

另個可能性,是技術債的累積與償還。比如近期緊急修復的 Bug,或為了給客戶 Demo 快速製作的 prototype。這些短期、應付特定情境所產出的程式碼,可能會採用一些臨時解法 (workaround)寫死 (Hard code)特定區域的做法。不過,這些債務應該在特定的時機下償還,以利後續整體性開發。

想想「為何」此時要重構?「現在」重構的 C/P 值高不高?

重構 C/P 值是個情境與時機的問題。就如同買股票,每天的價格跟波動也不一樣,為什麼我要這個時機點買入?為何現在要買這麼多?明天的價格可能更好!或是我拿去買其他檔股票更划算等等。

投入重構的時間越多,就像是當下 Buy in 這個功能的單價越高,或許你現在花這麼多時間,進行重構設計是不划算的。舉例來說,我們要特別留意 yagni 原則 (you aren’t going to need it);某些我們認定為更彈性的調整,考慮未來更容易擴充的重構,卻有可能是陷入過度工程的浪費。可能在隔天發現市場不需要、或沒有必要調整成這種結構。因此,我們也必須考慮,這個時間拿來做其他事情是否更有價值。

藉由這種詢問自己的過程,我們可以整理思路,根據現況做出適當的決定。

2. 童子軍法則

童子軍法則就是

把你使用過後的營地,變得比原本更乾淨。

隨著持續開發的過程,我們更理解相關領域的商業知識,撰寫程式碼的功力也更進步了。當我們再次回到某個既存程式碼區塊時…

心中要隨時想著:如何讓程式碼更好。就算只是小規模的調整,例如程式碼順序、修改判斷邏輯或更改變數命名等等,都可以讓整段程式碼的品質提升。我們已經在文章前段的討論,也就是計算位能的範例程式碼中,體現了這項好處。

何時不要重構?

1. 路過經過

某些時候,我們會發現某些程式碼片段的設計看似奇特、多此一舉或不合理;然而,我們可能沒想到,那是經過原作者刻意為之或特意避開的。

當你不是接手該區域,或正在開發相對應的功能時,請不要隨意修改別人的程式碼;因為,原作者照理應該最熟悉該片段的商業需求、脈絡、與程式執行情境。

有時我們路過看到的順手之勞,可能反而破壞了原作的精心設計,反而產生 Bug ;

另外,沒有經過溝通的調整,也可能會侵蝕原作的信心。如果真的覺得不妥,或認為需要進行調整時,請先行與原作討論、釐清,以及確認。

2. 沒時間時

我們都會面臨非常時期:需要趕功能上線、緊急修復或 Demo 等等。

假設留給我們的時間非常短暫、沒有餘裕時,便不太適合進行重構;因為,這個階段時間寶貴,重構沒辦法快速救火。

此時,我們可先全力衝刺、完成交付;否則開發者會分心思考既有龐大程式碼的整體性,及其背後的商業意圖,既無法安心釐清既有程式碼、安排重構計劃,也無法達到快速救火的目標。

此時,建議先將需求完成,待後續恰當時機,再回頭進行調整。

3. 不值得重構時

這點相對比較依賴經驗或限定於某些特定時空下,有些程式碼會被判定為不值得重構。

比如:

如同前述,有些專案的生命週期非常短,一陣子後就再也不會用到。

另外,有些程式碼已不再需要調整或增刪,其存在僅為了維持完整性或整體性。除非有必要去了解該區域是如何運行,不然正常使用沒有障礙時,重構該區域完全無法帶來任何好處。

也有一種可能是:全部「重寫」比「重構」還簡單;然而,這也不是個能輕易被做出的判斷。就算開發者相當有經驗,可能也仍須花費相當時間嘗試重構後,才能更清楚評估重構相較於重寫的難易度。

本文同步收錄於 Web 實驗室長島計畫第四期

參考資料

大規模重構 : 奪回源碼庫的控制權
重構 : 改善既有代碼的設計
筆記:重構 — Chapter 1 & 2:第一個範例 & 重構原則

--

--