該如何接手別人遺留下的代碼?

如果你在軟件行業工作足夠長的時間,遲早你都將面臨一個棘手的問題:修復遺留的代碼庫。本文所提出的並不是唯一可行的方法,且遺憾的是,這種方法並不是衆所周知的。不過以下內容保證風險最小化。假設你遇到了修復遺留應用程序的問題,已經存在風險,並且不需要添加更多應用程序。採用本文方法的風險和成本將會比從頭開始重寫系統更低。

爲什麼不要重寫代碼

在我們開始之前,你應該先了解一些事項。首先,請閱讀這篇 Joel Spolsky 的著名文章,瞭解爲什麼永遠不應該重寫代碼(https://www.joelonsoftware.com/2000/04/06/things-you-should-never-do-part-i/)。在這篇文章中,Spolsky 強調了爲什麼要重構代碼庫而不是重寫代碼庫。所謂重構,即在不改變行爲的情況下對代碼質量進行一系列逐步改進的過程。當你嘗試修復代碼時,同時更改其結構和行爲是自尋麻煩。

話雖如此,但我覺得“永遠”一詞有些言重。假設你的代碼是用 UniBasic 編寫的,而如今你已找不到該語言的開發人員(包括願意學習它的人),那麼重寫是你唯一的選擇。或者,如果你使用的是一個影響較小的小軟件,重寫可能並不那麼危險。

但是,假設你可以輕鬆找到或者培訓開發人員使用當前軟件的編程語言,並且該軟件的意義舉足輕重,而且它的代碼庫非常龐大,那麼重寫就不是那麼有意義了。重構意味着你的代碼一直都在,你不會丟棄業務知識,而你的開發人員不必從頭開始寫代碼,可以快速出成果。換句話說,你可以將風險降至最低。

理雖如此,但是依然有很多公司和開發者選擇重寫。新代碼令人興奮,有望帶來新的機遇。新代碼很有趣,但修復舊代碼通常被視爲苦差事。但是,如果你有一個龐大的遺留代碼庫,並且你編寫的新代碼是一個大型項目,而大型項目的風險很高:

IT 權威機構 Standish Group 在1995年的一項研究表明,只有大約17%的 IT 項目被認爲是“完全成功的”,52%屬於“勉強合格”(沒有達到預算、質量或時間目標),30%是“失敗的”。最近,Standish 對2003年至2012年期間的3555個 IT 項目進行了調研,這些項目的總成本至少爲1000萬美元,而僅有6.4%的 IT 項目成功。

儘管這項調研時間有些久遠,但它仍然適用於當今 IT 世界。項目越大,風險也越大。事實上,在我參與的各種公司的大型項目中,很少有人能夠在預算範圍內按時完成項目。有些項目會被徹底取消,由於沒人願意承擔失敗的責任,因此他們將項目拖延很久,這完全就是一場災難。比如,原本計劃一年完成的項目已經四年都未能完成,目前仍然充滿了漏洞和設計缺陷,並且該軟件向後兼容,因此只能硬着頭皮繼續做。該公司能夠繼續運轉的唯一原因在於,他們收購了另一傢俱有盈利能力的公司。

這個例子暗示了一個行業中沒有公開的小祕密:大規模的重寫通常會將一堆亂糟糟的代碼換成另一堆。這些公司並未真正解決潛在的問題,而是用一系列已知的問題替換了一系列未知的問題。如果你需要修復遺留代碼,那是因爲你需要將風險降至最低;爲什麼你會故意採用無法量化的風險?

如何重構遺留代碼

假設你不想面對大規模重寫帶來的成本和風險,那麼如何重構代碼呢?

首先,你需要對當前項目進行評估,至少包含以下方面:

  • 代碼的功能需求是什麼?
  • 如果有文檔的話,具體有哪些?
  • 通過跟蹤 bug,瞭解哪部分代碼更脆弱。
  • 依賴的外部資源有哪些?
  • 如果已做了測試的話,具體包含哪些測試。

所有這些都需要寫下來,以便任何人都可以一目瞭然地查閱這些信息。假如你要僱用專家來收拾這個爛攤子,這些信息則是重要的必需品。如果上面的列表看起來過於簡單,那是因爲我們正在重構,而不是重寫。

是的,你可能會聘請一位外部專家。如果當前項目的開發人員無法制定修復遺留代碼庫的可靠計劃,同時最大限度地降低風險,那麼你需要聘請一位在該領域有豐富經驗的人,他們不僅會看到你無法看到的問題,還可以讓目前的開發人員變得更好。以下內容無法做到完全直觀,但專家的經驗能夠幫助你擺脫困境。你的專家至少需要滿足以下條件:

  • 你的代碼庫使用的主要編程語言的專家
  • 強大的自動化測試背景
  • 熟練使用代碼覆蓋率工具
  • 熟知數據庫
  • 系統設計和架構專家
  • 能夠自責自省
  • 瞭解業務需求
  • 能夠說服別人

最後幾點似乎很奇怪,但的確非常重要。一流的開發人員很難兼具以上能力,因此很值得花錢去尋找這樣的專家。

開始

首先,你得大致清楚如何規劃你的應用程序。也就是所謂的架構路線圖,但請記住,該路線圖比較靈活,它會隨着時間的推移而發生變化。這也正是需要架構專家介入的原因。應用程序的各個功能應當被拆分爲單獨的部分,以確保應用程序的各個部分都具有其專注的領域。當應用程序的每個部分都有其專注領域時,它就更容易維護、擴展和重用,這也是我們想要修復遺留代碼庫的主要原因。但是,這一階段最好不要制定太過詳細的計劃;相反,只需確保你對大方向有一個粗略的認識即可。

接下來,你將像吃大象一樣一點一點重構你的應用程序。你將選擇一個小的初始目標來熟悉新工具。隨着時間的推移,它會變得更容易,但是當你最開始使用時,切不要操之過急。

重構一個大型應用程序意味着需要編寫測試,但除非你非常清楚知道自己在做什麼,否則你很可能會出錯。通常很少使用 TDD,代碼已經寫好了,你很難爲所有代碼都編寫測試。相反,你應當採用集成測試。

你需要做的第一件事是瞭解應用程序中不會改變的東西。也就是應用程序的輸出,無論是通過 JSON API、網站、SOAP 接口還是其它什麼方式輸出結果都不會改變。由於某些東西必須使用該軟件,因此它可以使一切運轉起來。你需要爲其編寫集成測試。假設我們正在重構 Web 應用程序,你已決定首先編寫測試以驗證是否可以在管理頁面上列出用戶。

在該測試中,你將創建一個瀏覽器對象,以管理員用戶身份登錄,獲取用戶頁面,並編寫測試把預期的用戶顯示在該頁面上。想要實現這些功能通常需要你做大量的工作。例如,如何獲取連接到測試數據庫的代碼?如何確保測試之間的數據隔離(換句話說,運行測試的順序無關緊要)?如何創建瀏覽器對象?當你真正動手時,你需要回答這些問題,以及更多其它的問題。

如果你已經進行了一些測試,那麼可能會更容易實現這一點,如果你沒有進行其它測試那就會非常困難,但這是重構非常重要的第一步。

一旦你針對接口的一個相對較小的不變部分進行了第一次集成測試,就可以在測試中運行代碼覆蓋率工具,以查看這些高級集成測試所涵蓋的代碼。涵蓋的代碼通常是可以安全重構的代碼。

現在,你可以查看應用程序的哪些功能部分嵌入到經過測試的代碼中,並制定計劃將這些部分移到你的架構路線圖中。此時,我們要避免把一切分散開來。相反,我們應該每次只專注一個點。例如,如果你在整個代碼中分散了 SQL,請將其提取到你的架構路線圖中,以便擁有一個乾淨的 API 來處理你需要的數據。又或者你有一個 Web 應用程序,而你一直採用直接打印 HTML 的方式,請嘗試使用模板系統並開始將 HTML 整合到模板中。不要一次修復所有東西,否則你會不堪重負。相反,你應當關注一個領域並充分理解。

不做單元測試

請注意,我們一直在討論集成測試,而不是單元測試。這有一個很好的理由:對於遺留系統的大規模重構,當你剛開始重構時單元模塊會發生很大的變化,但集中在靜態接口上的集成測試則不會。你想花時間重構你的應用程序,而不是你的測試,所以在你穩定代碼內部工作之前,單元測試可能會分散你的注意力。集成測試的優勢在於,你可以一次覆蓋大部分代碼,如果正確完成,可以非常快速地編寫。此外,對於結構不良的應用程序,單元測試可能很難執行。集成測試還有助於發現單元測試無法發現的錯誤:不同組件具有不同期望的錯誤。但是,集成測試也存在一些缺點:

  • 集成測試比單元測試運行得慢
  • 很難追查 bug
  • 如果沒有很好地隔離代碼,那麼集成測試更容易造成破壞

話雖如此,在這個階段集成測試的優勢很明顯:當你有一些基本的測試來防止最壞的錯誤時,重構會容易得多。同樣值得注意的是,如果你在此之前很少甚至沒有做測試,如果你有一些可靠的測試,就不會那麼糟糕。

如果你還沒有實現持續集成(CI)系統,那麼現在是時候開始了。即使你的開發人員忘記運行測試,你的 CI 系統也不應該。如果測試失敗,你需要快速找到答案。

進階

在你開始重構了系統的一小部分功能之後,你可能很快會發現原始計劃中的一些錯誤。沒關係,你已經開始小規模重構,以儘量減少風險。糾正這些錯誤,然後開始使用代碼覆蓋率工具對系統的其它小部分進行集成測試,以及你已經在處理的功能部分(數據庫調用、HTML 或者其它部分)。如果你覺得自己已經擺脫了一些最糟糕的問題,那麼請開始查看系統的另一個功能,即當前測試的代碼共享,看看是否可以解決這個問題。

請注意,這正是專家的架構技能將會發揮作用的地方。他們將理解解耦應用程序採用不同功能部分的重要性。他們將瞭解如何編寫健壯且靈活的界面。他們將學會識別業務邏輯中可以抽象出來的模式。不要把這個責任交給現有的程序員,除非你絕對相信他們擁有完成這項工作所需的技能和經驗。

接下來就是重複這些方法,在最壞的情況下可能需要數年才能完成。這需要很長時間,但它有着顯着的優點:

  • 代碼始終能夠正常工作
  • 你無需爲同時維護兩個系統而付費
  • 業務知識不會丟失
  • 仍然可以添加新功能
  • 可以輕鬆編寫針對現有 bug 的測試(即使你尚未重構該代碼)
  • 一旦發現你的代碼庫“足夠好”了,隨時都可以收工

爲什麼這種方法有效?任何大型項目看起來都令人生畏,但通過將其分解爲更小且更易於管理的部分,你至少可以知道從哪裏開始並瞭解目標,而不必擔心大型項目的失敗。

當我以前使用這種技術時,我經常發現自己能夠更清楚地瞭解代碼是如何發展的,而且當前經驗豐富的團隊並沒有面對看到他們的工作消失的令人沮喪的前景。這種技術的缺點是,雖然代碼質量大大提高,但總有一種感覺,它不夠好。然而,正如我之前提到的,許多重寫系統僅僅會產生新的設計缺陷以取代舊的系統缺陷。這太常見了,它意味着將已知問題換成未知問題。

總結

上述策略並不能得到所有人的認可,對於那些崇尚新事物的人來說很難接受。事實上,在許多方面它可以被視爲無聊(雖然我喜歡重構代碼),但我已經成功地在多個遺留代碼庫中使用了這種方法。但是,如果你仍在嘗試從重寫與重構之間做出決定,請記住,這種方法是一種成本相對較低且風險也較低的方法。如果它被證明是行不通的,你可能會冒很小的風險。如果改寫證明不可行,那麼你可能會花費公司一大筆錢。

因此,你應當明白何時應該考慮修復遺留代碼庫。我建議你未雨綢繆。修復遺留代碼庫雖不如送火箭上天一般高難度,但它確實需要一定程度的專業知識來轉換現有的代碼庫。遺憾的是,大多數開發人員似乎對此技能並不感興趣,他們似乎也並不想處理遺留代碼庫。

原文:https://ovid.github.io/articles/a-simple-way-to-fix-legacy-code.html 作者簡介:Curtis“Ovid”Poe,擁有二十年的軟件開發經驗。主要開發 COBOL 金融應用程序、製藥 ETL 系統以及用 Perl 編寫大型網站等系統。 譯者:安翔,責編:屠敏


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章