iT邦幫忙

2026 iThome 鐵人賽

DAY 2
0
Software Development

諸神也搖頭的 Legacy Code: 30天 .NET 工程師生存之道系列 第 2

Day 02 -「忒修斯之船」客戶要的是什麼?

  • 分享至 

  • xImage
  •  

https://ithelp.ithome.com.tw/upload/images/20260802/20182564qoaN7Zm57i.png

忒修斯從克里特島返回雅典後,雅典人將他的船保存了下來,可是木船放久了,木料難免會腐朽,於是人們不時把壞掉的部分拆下來,換上堅固的新木料。

年復一年,船上被替換的部分越來越多,那麼問題來了:

當所有木板都被替換之後,它還是原本的那艘船嗎?

這個問題從古希臘一路爭論到現在,放進軟體開發裡,我們也可以換個角度來想:

當一套系統裡的邏輯,甚至整個架構都被替換重構之後,它還是原本那套系統嗎?

如果使用者依然可以用相同方式完成工作,資料沒有消失,原有功能也沒有莫名其妙壞掉,那麼對多數使用者來說,它仍然是原本那套系統,至於裡面的實現方式,甚至程式語言是否已經換過,通常都不是他們最在意的事。

問題是,我們要怎麼知道換完木板之後,這艘船仍然能航行?


「知道」不等於「會做」

其實知道「該寫測試」這件事,跟「真的開始寫」還是天差地遠,具體原因可以列出整整一排:

  • 公司沒人在寫,所以就跟著文化走
  • 專案看起來都是 one shot,結案後很少有後續
  • 總覺得寫測試是在造福後人,對現在的自己沒好處

可是真的對自己沒好處嗎?「改 A 壞 B」每天都在職場上發生,但收到 issue 時,大多數人通常沒有餘裕再去追根究柢,本能反應通常都是:

先修掉,客戶都已經在發火了,你還在叫我寫測試?

人在壓力下本來就會選最快、最直覺的路。

所以這 30 天我們真正要探討的,是在壓力很大、時間很少、code 又很難測的情況下,我們到底要怎麼開始建立安全網?在那之前,得先把測試到底在保護什麼搞清楚。


使用者依賴的是行為

沒有測試的 code 就足夠稱為 Legacy Code 了,OK,然後呢?如果測試只是把 method 呼叫過一遍、讓覆蓋率數字變好看,我們仍還是不知道自己到底保護了什麼。

多數使用者不會因為我們的 class 設計的多優雅、多遵守 SOLID,就突然對系統多一分信任,他們真正依賴的是一件事:

這個系統做不做得到我要它做的事?

這就是「行為」,系統在某個情境下,對外產生的可觀察結果,所以不論測試規模大小,都可以驗證行為。

客人把蘋果加入訂單,訂單明細就應該記錄蘋果,如果明細最後只出現香蕉,那麼不管架構多漂亮、設計多優雅,這個系統的行為就是錯的。

所以,測試應該圍繞行為來寫:

[Fact]
public void 下訂蘋果_訂單明細應記錄蘋果()
{
    // Given 客人選擇蘋果

    // When 將商品加入訂單

    // Then 訂單明細包含蘋果
}

而這個測試關心的只有一件事:

把蘋果加入訂單,訂單明細有沒有蘋果?

如果這個行為出了問題,測試就應該亮起紅燈。

更重要的是,測試必須值得信任,它不該在程式沒有改動的情況下,今天失敗、明天又通過,也不該因為測試資料或外部環境不穩定就失敗,否則久而久之,大家只會忽略它。


測試不只是在找 bug

測試當然可以幫我們找出 bug,說它完全不是用來找 bug,也不準確,對維護 Legacy Code 的我們來說,測試所帶來的價值反而是: 讓我們知道哪些行為已經受到保護,並且在每次改動後,都能重新確認這些行為是否仍然成立。

改動之前先跑一次測試,可以確認目前的程式碼符合測試的預期,也能幫助我們分辨哪些問題在修改前就已經存在。

改完之後再跑一次,綠燈代表原本的行為仍然成立,紅燈則表示在我們改動 Legacy Code 的過程中害其他行為錯誤了,不過有時候不見得是程式碼錯誤導致的,有可能是因為加入新的需求才發現與原本的邏輯有衝突,遇到這種狀況,反而要開心,因為至少不用再等到產線後半段的時候才發現有問題。

同理,綠燈也並不代表整套系統完全沒有 bug,它只能證明說 目前這些測試沒有發現問題,那麼要嘛真的沒問題,要嘛就是需求一開始就有問題,甚至有時候客戶根本沒這需求,卻因為我們系統的設計讓他產生應該要可以這麼做的幻覺,不過這又是另一個不一樣的話題了。

到這裡可能就會有人問:

「你講這麼多,所以只要寫了測試,就不會有 bug 了嗎?」

當然不可能,畢竟人非聖賢 XD

沒有測試,不代表程式碼一定寫得很爛,補了幾個測試,也不會讓程式碼立刻變得乾淨又好維護,只要測試值得信任,即使覆蓋率不是 100%,它仍然可以幫助我們提早發現問題、縮小排查範圍,並在每次修改時多給自己一點信心。

有,總比沒有好

Legacy Code 之所以讓人動彈不得,不只是因為程式碼複雜,更是因為我們不知道這次改動究竟會影響什麼,當一個小修改都可能在意想不到的地方出問題,我們自然什麼都不敢動,而測試的價值,就是替這些未知的影響,在我們處理 Legacy Code 的過程中逐漸裝上可以被看見的警報器。


「能跑」和「能改」是兩件不同的事

軟體不可能永遠維持不變,需求會改、bug 會被發現、功能也會持續擴充,軟體的價值不只在於今天能跑,更在於經過一次次改動之後,明天還能不能繼續正常運作。

Legacy Code 的問題往往就在雖然現在正常在運作,卻不容易被安全地修改。

維護是軟體工程中最重要的工作之一,卻也經常讓人避之唯恐不及,從零開發時,許多設計看起來都很合理,而只有進入維護期,被真實需求反覆拉扯之後,才知道當初的設計究竟撐不撐得住。

在大環境下,我們經常被要求快點做完、快點上線、快點交付,而以下這些問題往往被延後考慮:

下一個需求進來時,我們能不能看出它會影響哪些地方?
修改之後,我們有沒有辦法確認既有行為仍然正常?
系統是在穩定演進,還是只能把新需求一次次硬塞進去?

軟體工程實踐的目的,不只是讓程式碼看起來漂亮,更是保護一個系統面對變動時,仍然能夠安全、持續地向前演進,這些問題是一連串從個人到組織的課題,而我們則可以先從自己做起!


總結

在 deadline 很緊的時候,測試永遠像是可以晚點再補的東西,但當我們一次又一次把它往後延,系統就會慢慢變成那個「能跑、但沒人敢改」的東西,到最後真正花掉的時間就不只是寫測試的時間這麼簡單了,就像忒修斯之船,我們無法阻止木板腐朽,也無法要求軟體永遠不要改變,真正重要的,是每換下一塊木板時,我們都有辦法確認這艘船仍然能航行。

那麼,既然「行為」這麼重要,系統又為什麼還要一直被改動?改動的時候到底發生了什麼事?我們又該用什麼思維去決定下一步?

明天我們繼續看:軟體為什麼非改不可?

Reference


上一篇
Day 01 -「阿里阿德涅之線」 Legacy Code 不就是很舊的程式碼嗎?
系列文
諸神也搖頭的 Legacy Code: 30天 .NET 工程師生存之道2
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言