重構」的目的是好的,程式設計者持續透過一些系統性的方法,找出程式碼中「腐敗」的部份,接著予以整理、翻新,在不變動程式碼外部行為的情況,改善程式碼的內部品質,使得程式碼一方面更容易為讀者所了解,另一方面也更容易於其上進行修改及擴充。

這樣的結果當然是好的,但是,千萬別只看到了可能會有的好結果,而忽略了重構本身也是需要付出代價,而且在重構的過程中也是具有風險的。倘若在進行重構的時候忽略、或是低估了風險,那麼,在未得到重構所帶來的好處之前,恐怕已付出沉重的代價,反而得不償失。在這一回裡,我們就來探討重構可能的潛在風險,以及因應的對策。

重構的風險在於程式碼改變
重構不會改變軟體運行的外部行為,只會改善內部的程式碼實作。但是,改變程式碼這個動作,本身就是個極具風險的事情。有句話說:「做對的事,何必再改變」。套用到程式碼的重構上,也是一樣。很多人很怕修改程式碼,這個恐懼並不是沒有道理的。一旦你的程式可以通過測試,起碼它在主要的執行路徑上是對的。但倘若對它做修改,原本是對的行為,反而有可能改錯了。

重構必須是在了解原有程式邏輯的前提下進行修改,使得程式邏輯保持不變的情況下,提高程式碼的內部品質。但若修改者對原有邏輯的了解不夠全面、不夠完整、甚至錯誤,那麼透過重構改寫所得的程式碼,就不能夠保持原本對外的行為。

此外,即使修改的人完全理解原先程式的邏輯,但軟體之所以困難,就是難在牽一髮動全身的複雜度。當你修改了程式碼,就有可能引起不可預期的副作用,即使主要的邏輯仍是對的,但是,這些副作用會在其他的地方改變程式的行為,使得整個系統的行為仍然被改變了,甚至造成錯誤。

正因為修改程式的風險其實很高,所以,很多程式設計者向來不喜歡修改既有的程式碼,因為有更動就有風險。我最常舉一個朋友告訴我的例子就是,在很多年前,他們負責維護一個數百萬行的產品,但是,面臨了一些需求使得他們想要進行修改。在整個團隊的人在會議中整整討論了一個下午之後,他們決定因為還是沒有把握,所以最後決定還是暫時不要修改。

不適合重構的時機

也正因為修改的風險如此之高,所以legacy code才會持續疊床架屋,違建愈來愈嚴重。而這不正是重構想要解決的問題嗎?我們必須在因為改動而造成的風險,以及若不改動則程式碼,將會持續敗壞,這二者之間進行權衡。總會有一個抉擇點,是你願意接受並管理這個風險,然後改動程式碼,以求改善程式碼的品質。重構免不了要修改程式碼,那重構又是如何管理這些風險呢?

前一回我們討論到在進行那些工作時,適合伴隨著進行重構,那麼有那些情況下,你不應該重構?

首先,在時程很緊的時候,可能不適合做重構。因為重構伴隨著風險,而風險不可預期。在專案的時程表上,不可預期的項目都有可能花去更多的時間,這些時間會消耗你安排時程的緩衝區。但是,時程很緊的專案最缺乏的,就是時間的緩衝區。因此,當你的時程很緊的時候,不該選擇風險比較高的工作,當然包括了重構。

有時候重構程式碼的人是程式碼的原作者,有時候只是接手他人的程式碼。無論是那一種,都有可能在重構的時候,不能夠完整理解程式碼的邏輯。我們有時候會有這種經驗,即使程式碼是自己寫的,事後也驗證能夠運行。但是事隔多日、多月、多年之後,重新再看到程式碼,卻回想不出當時寫的心境、思路、還有邏輯。當然,這也是重構程式碼想要達到的目的之一——增加可讀性。

但是,重構最重要的就是要讀懂原有的程式碼,才能夠以更簡潔、更易懂的方式,以新的手法重新加以實現。絕大多數時候,我們沒有辦法寫出簡潔易懂的程式碼,是因為我們其實並不是真正的懂了。只有真正懂的時候,才有機會知道最簡單的表達形式是什麼。所以重構時,你一定要讀懂原有程式碼的真正意涵,甚至比前人寫下這段程式碼時,更了解真正的意義時,才能夠在改寫時把它重新表達得更好,也才更有機會規避掉一些副作用。

有些時候,之所以會有副作用是因為並不是真的全懂,改寫後的程式碼忽略掉一些事情,因此才會衍生出副作用。對於想要重構的程式碼,當你不覺得自己全懂時,請等真的了解之後,再來重構吧。

當然,如果你才剛加入團隊、你對程式碼所用到的技術、應用程式框架、甚至是程式語言都不夠熟悉,或者你對系統整體的程式碼運作方式還不夠了解時,那麼也盡量避免重構。因為你所做的修改,很有可能是基於不完整的資訊而做的,而這種修改,很容易引發副作用。

總而言之,重構是在時程可允許、對於程式該有的邏輯完全掌握、同時沒有不熟悉的語言或工具時,比較適合進行的活動。

針對重構所可能導致的風險加以管理

那麼,當上述的這些情況都滿足時,該如何重構,以便管理可能衍生的風險呢?重構的發展和極限編程(eXtreme Programming,XP)息息相關,而重構管理風險的方式,也和XP其他實踐要則有關。

改動程式碼最怕的,就是改錯了、改出了副作用。所以,必須輔以完備的測試,那麼一旦改錯了,也能夠透過強而有力的測試來找到問題。所以在XP裡主張要做自動化的單元測試,而且常常測試。更快速、更嚴密的測試,可以做為重構後盾。

有了更好的測試,程式設計者比較容易鼓起勇氣做重構,相反的,就容易裹足不前。當然,也有人不加強測試,卻勇於做重構的,這麼一來,因為重構而發生問題的機會自然大大的提高。能夠自動化測試的重大意義,在於它可以降低每次測試的時間及成本,有助於更快找出問題,也縮短了「修改->測試->再修改->再測試」的週期。如果你沒有足夠的自動化測試,那麼,就要更注意每次的測試,是否足以找出所做重構的問題。

在重構的方法中,會希望你是一次重構一個問題。這有助於縮小每次要解決的問題規模,除了讓你專心研究、分析該問題中的程式碼邏輯之外,也有助於程式碼改寫完成之後的測試及驗證。

倘若你一次重構多個問題,那麼一來你可能無法專心在特定問題的理解及分析,二來,倘若發生問題時,你也很難將問題隔離出來。每次只做一個問題的重構,每次透過測試驗證一個解決手法是否有產生額外的問題、是否表現出與重構前相同的行為。這也有助於管理重構所帶來的風險。

在XP之類鼓勵運用重構的方法中,也都鼓勵你將開發的迭代週期縮到很短,當然,伴隨著而來的往往就是很短的產品發行週期。這都能促使問題可以更早浮現。總之,在很短的時間內不會埋入太多問題,在很短的時間內問題就可以浮現,能使得風險較好管理。

此外,重構是有系統性的方法,在《重構》)中便整理了諸多的方法,依照每個方法的步驟來進行,較不易犯錯,自然不會因此有副作用。

無疑的,重構可以帶來好處,但是,重構也有它本質上的風險。利用好的方法或工具來做重構,可以降低風險,因為它們降低了犯錯的機會。但是,很多情況下,我們並不是降低風險,而是管理風險。管理風險的關鍵,在於讓問題提前更快速的浮現,這使得我們可以更快速、更即時地採取行動去因應。無論是好的測試、更短的週期、以及一次只處理一個問題,都是有助於我們管理重構所帶來的風險的。

作者簡介


Advertisement

更多 iThome相關內容