馴服爛代碼

 

馴服爛代碼

何爲爛代碼

遺留代碼
  • 難以理解、難以修改的代碼。
  • 沒有編程測試的代碼就是糟糕的代碼。
  • 沒寫測試。
大泥球
  • 結構混亂、肆意蔓延、輕浮草率、貼滿補丁、私搭亂建、一團亂麻的叢林。
  • 無序增長、反覆修補、權宜修復的跡象。
  • 互不相干的部分之間雜亂地被共享。
  • 幾乎所有的重要信息變成全局的或重複的。
  • 系統的總體架構可能從未被被定義過。
爛代碼一般表現在下面幾個方面
  • 命令不清。
  • 多層嵌套。
  • 濫用模式。
  • 代碼冗餘。
  • 難以擴展。
  • 不如重寫。
爛代碼應該具有下面3點特性
  • 爛代碼都是能夠運行的。不能運行的代碼不能稱爲代碼,只能稱爲一堆字符串。
  • 爛代碼都需要修改其中的bug或者在其中增加新功能的。
  • 爛代碼對於其編寫者、測試者和維護者來說都是反饋遲緩的。

簡而言之,爛代碼就是反饋遲緩的代碼。這裏的反饋包括了了解代碼的行爲、驗證代碼、和在這些代碼之上修改bug或新增功能等方面。

分而治之-釜底抽薪

對生成代碼所做的重構無非是用新代碼替換舊代碼。替換的方法可以分爲兩種
  • 第一種,不保留舊代碼,而直接用新代碼替換舊代碼,並最終保證原有的測試在新代碼上也能運行通過,這種方法好比“釜底抽薪”,從根本上解決問題。
  • 第二種是在保留舊代碼的基礎上,在舊代碼後面編寫新代碼,帶新代碼編寫完,將代碼行爲切換到新代碼後仍能保證測試運行通過,此時再刪除舊代碼,這種辦法好比“拋磚引玉”,照着舊代碼這塊“磚”編寫新代碼這塊“玉”。

“釜底抽薪”的方法適用於簡單的重構。“拋磚引玉”的方法適用於複雜一些的重構。

分而治之-拋磚引玉

對於看起來複雜的系統,可以使用“拋磚引玉”的方法來進行
  • 即可以在目前的位置計算邏輯的後面編寫意圖代碼,等新的意圖代碼編寫完畢,將代碼行爲切換到它之上,並運行測試通過後,就可以刪除原來的位置計算邏輯。
  • 使用該重構方法的好處是,能夠最大限度地讓測試頻繁得到運行,讓複雜的重構得到測試最大限度的保護。

使用“拋磚引玉”的重構方法時,若原有代碼出現在if條件中,則其可以與功能等價的意圖代碼做邏輯“或”運算而出現在同一個if條件中,這樣就不會在代碼運行時影響原有的功能。

分而測之-編寫Stub及提取接口

  • 使用提取接口的方法,讓SUT針對對象的接口,而不是針對具體的DOC來編程。先從DOC中提取接口,然後編寫Stub類實現接口,並通過SUT的帶有上述接口類型的構造器,將一個Stub對象注入到SUT對象中,從而實現讓SUT把抑鬱控制的Stub類當成上述接口來看待,對SUT進行測試。
  • 代碼雖然易讀,但由於沒有測試保護,使其對於代碼維護者來說反饋慢,所以這樣的代碼還是屬於爛代碼。
  • 當SUT所依賴的DOC很難再測試中進行控制時,可以根據DOC提取一個接口,然後讓SUT不針對DOC,而是針對這個接口編程,並根據這個接口編寫易於控制的Test Double,來替代那個難以控制的DOC,從而令SUT把Test Double當成那個接口來看待,進而對SUT進行測試,來解決DOC在測試時難以控制的問題。
  • 使用像ISensor這樣命名接口的方式並不理想,比較好的方法是用translate.google.cn來尋找一個類名的英文近義詞爲該類接口命名。
如何解決被測系統(System Under Test,SUT)所依賴的組件(Depended-On Component,DOC)在測試中難以控制的問題
Stub和Mock
  • 兩者都屬於Test Double。
  • Stub的作用是讓測試能夠控制SUT的間接輸入,以便於測試能夠強制SUT進入正常情況下很難進行的運行路徑中。
  • Mock的作用是讓測試能夠驗證SUT的間接輸出。

分而測之-編寫Mock及子類化並複寫方法

  • 一個面向對象編程語言所定義的具體的類,雖然在形式上不是一個接口,但如果該類有一個符合里氏替換原則的子類,那麼在子類眼中,父類就可以被當成接口來使用。
  • Mock除了完成Stub所做的爲SUT在測試中的運行提供間接輸入外,還要額外做驗證SUT在測試中的間接輸出的事情,所以Mock累中一般都有verify()這樣的驗證方法。
  • 使用先編寫意圖代碼,然後通過頻繁運行測試,編寫最少量的代碼(如能讓變異通過的尚未實現得空方法,和能讓測試運行通過的上述空方法的最少量的實現代碼)以修復編譯和測試運行錯誤,來驅動出生產代碼的開發方式,既然能讓我們在編寫意圖代碼時進行適當的設計,並能編寫最少量的能讓測試運行通過的生產代碼,以減少浪費,又能讓我們逐步熟悉這些信息當成指路的嚮導,避免盲目地編寫生產代碼。
  • 與手工編寫的Mock相比,用Mockito框架編寫的Mock又下列好處,不必編寫額外的Mock類,減少工作量;憑藉Mockito框架的諸如mock()、when()、verify()這些方法的精心設計,能夠讓Mock代碼可讀性更高。
  • 與用Mockito框架編寫Mock相比,手工編寫Mock也有下列好處,能夠完全控制Mock的編寫過程;熟悉手工編寫Mock的過程後,有利於理解Mokc框架的使用方法。

真正的單元測試

單元測試運行的快。運行得不快的不是單元測試。一個需要耗時0.1秒才能執行完的單元測試已算是一個慢的單元測試了。

有些測試容易跟單元測試混淆起來。比如
  • 跟數據庫有交互。
  • 進行了網絡間的通信。
  • 調用了文件系統。
  • 需要對環境做特定的準備(如編輯配置文件)才能運行起來。

單元測試要測的僅僅是SUT裏面的軟件行爲,並不是測試SUT於諸如數據庫、網絡和文件系統這些DOC之間是否能夠正常交互。可以先找到DOC的接口,然後讓SUT針對這個接口編程,並且編寫一個運行起來經濟快捷的Test Double來實現這個接口,並注入SUT中,讓SUT把這個Test Double當成那個接口來使用。這樣能夠依賴於DOC的集成測試,轉換爲依賴於實現接口的Test Double的單元測試了。

  • 當不大瞭解要測試的SUT的實際行爲時,使用了特徵測試的方法來進行測試。即可以故意在斷言中填寫一個不正確的期望結果,然後讓測試運行到這裏。一般測試到這裏會運行出錯,並在出錯的信息中給出實際值。此時可以把出錯信息中的實際值填寫到測試斷言中的期望值那裏,來讓測試運行通過。
  • 運行速度慢的不是單元測試。於數據庫、網絡、文件系統和配置文件有交互的測試不是單元測試。
  • 單元測試要測試僅僅是SUT裏面的軟件行爲,而不是測試SUT與諸如數據庫、網絡和文件系統這些DOC之間是否能正常交互。
  • 要把SUT與諸如數據庫、網絡、文件系統這些的DOC之間進行交互的集成測試,轉變成針對SUT的單元測試,可以先找到DOC的接口,讓SUT針對這個接口編程,並且編寫一個運行起來經濟快捷的Test Double來實現這個接口,並且注入SUT中,讓SUT把這個Test Double當成那個接口來使用。這樣就能把依賴於DOC的集成測試,轉變成依賴於實現接口的Test Double的單元測試。

馴服爛代碼的步驟:LePpTr

要了解一段爛代碼,也不僅要聽其言,即要看代碼本身、文檔及註釋來理解代碼的意圖,更要觀其行,即用運行測試代碼的方式來驗證代碼是否言行一致。

  • 如果程序員把內心的“鏡子”擦亮會怎樣?就會“照”出像設計模式、面向對象的SOLID設計原則、重構、測試驅動開發、敏捷軟件開發、精益這些軟件開發的“道”。
  • 如果用昏暗的“鏡子”照出“反模式”,當成軟件開發的“道”。在編寫代碼中最典型的反模式就是圖方便的複製和粘貼“CV大法”。
馴服爛代碼的步驟如下
  1. 聽其言,維護TODO列表、編寫用戶意圖測試和意圖代碼。TODO列表就是意圖列表。程序員可以手機並審查所有當前和今後要做的諸如用戶意圖測試、bug修復和diamante“腐臭”治理的任務、形成一個用意圖來表達的TODO列表。然後根據當時的情況,或者選擇一個條件已經具備且有信心完成的用戶意圖TODO作爲下一步要開發的任務,把它標記爲working-on,根據用戶意圖做出合理的分析和設計,根據設計意圖編寫用戶意圖測試或意圖代碼。可以把每條TODO都以代碼註釋的方式寫到代碼相關的位置,並且用IDE的TODO管理界面加以管理。對於完成的TODO,就可以從列表中刪除。
  2. 觀其行,以修復編譯錯誤和測試運行錯誤爲指引編寫恰好夠用的生產代碼,直至測試運行通過。首先以上一步所編寫的意圖代碼的紅色編譯錯誤爲指引,當信心弱時可以編寫儘量少的生產代碼(即可以寫空類或空方法),當信心強時可以寫自認爲合理的生產代碼,來讓測試代碼和生產代碼編譯通過。然後運行測試,當發現測試運行中的諸如空指針這樣的測試運行錯誤後,以這些錯誤爲指引,同樣當信心弱時編寫少量生產代碼,當信心強時編寫自認爲合理的代碼,讓測試運行。
  3. 守其道,全面重構TODO列表、測試代碼和生產代碼。首先“嗅一嗅”上一步令測試運行通過的生產代碼中,是否有“腐臭”味道。如果有,進行下小步重構生產代碼,消除“腐臭”。然後根據諸如面向對象的SOLID原則、已知的或更新後的產品特性和設計模式這些軟件的“道”的層面的概念,來審查所有的測試代碼和生產代碼,來找出不合理的測試、代碼“腐臭”、被遺漏的User Story或剛剛從測試工程師手上接到的bug報告等,並以TODO的形式記錄下來,補充到“聽其言”步驟中的TODO列表中,來進入下一個迭代。
全面重構的定義
  • 在遵從諸如面向對象設計的SOLID原則、當下更新後的產品特性和消除代碼“腐臭”等這些軟件開發的“道”的層面的概念前提下,對記錄爲完成任何的TODO列表、測試代碼和生產代碼進行修改,以改變代碼內部結構的過程。
馴服爛代碼的IePpTr方法是TDD開發方法的一種實現
  • 聽其言。[維護TODO列表、編寫用戶意圖測試和意圖代碼]
  • 觀其行。[以修復編譯錯誤和測試運行錯誤爲指引編寫恰好夠用的生產代碼,直至測試運行通過]
  • 守其道。[全面重構TODO列表、測試代碼和生產代碼]

Total Refactoring 全面重構,是爲了適應軟件開發過程中軟件外在行爲會主鍵發生變化的情況,而對傳統重構概念所做的擴展。這種擴展強調在遵從軟件開發的“道”的前提下,對記錄未完成任何的TODO列表、測試代碼和生產代碼進行修改、以改進代碼內部的結構的過程。

 

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