Martin Fowler所著的《Refactoring》,介紹如何在不更動軟體系統既有功能的前提下,有系統地調整程式碼,以改善軟體本身的品質及效能,使得程式碼更具可讀性、系統架構更具擴充性,也更容易維護。

「重構(Refactoring)」是一整套系統性的方法,在這套方法中,最有價值的地方之一,在於「重構」方法針對程式碼或系統架構可能會有的缺失,整理出一份完整的列表。這一份列表,就好比病人可能會有的病徵(在《重構》中稱為Bad Smells In Code),程式人便可以按圖索驥。

重構方法不僅整理病徵,更提供了藥方。找到了病徵,便可以服用Martin Fowler寫下的藥方,各式冷熱雜症一帖見效。

過度冗長的Method,拖垮程式品質
儘管重構方法中寫下了十分完備的各式程式碼病徵,但有幾項可以算是患病率極高的,改善這幾項問題,便能有效地改善的品質。就讓我們來看看患病率高居不下的兩種問題吧:

1. 過度冗長的Method(Long Method)

2. 相似或重複的程式碼(Duplicated Code)

首先看「過度冗長的Method」這個問題。在物件導向程式設計中,物件的Method是一種功能單位。Public Method代表著物件對外的介面,物件能提供外部使用的規格;Private Method則代表物件內部實作的功能區塊。

在物件中,「Method」的作用,有如程序導向程式語言中的程序(Procedure)或函式(Function)。在程式語言的發展中,之所以發展出程序及函式的概念,在於程式語言的設計者,查覺程式中時常會有重複作用的程式碼區塊,因而衍生出將重複作用的程式碼區塊,抽離成為單一的程序或函式,以避免重複實作相同的程式碼,也降低程式碼占去的空間。

如何決定那些程式碼區塊構成單一函式,是考驗程式人功力的地方。但一般來說,是從語義的角度衡量一個函式應涵蓋的程式碼內容。正如前文所提到的,做為物件的Public Method,代表這個Method提供給用戶端的,是一個單一的服務。從服務的觀點來看,便很容易規畫一個Public Method應該提供的功能。

有許多程式員,會將實作Public Method所提供的功能,通通寫到Method裡,使得Method本身的程式碼變得十分冗長。就功能的實作來說,這樣的做法一點問題都沒有,但從程式碼的品質觀點來看,卻是大有問題。

首先是程式碼的可讀性問題,當一個Method過於冗長,往往代表其中尚有可以拆解出來的語意區塊,這使得該Method的內容,皆由最低階的程式述句表達。再加上冗長的行數,對程式碼的閱讀者來說,無法迅速且輕易地從高階的角度,理解整個Method的行為及內容。

複製/貼上阻斷重複使用的可能
另一個重大的問題,便是沒有發揮「可重複使用性」。一個冗長的Method,幾乎都包含著多個可以被再拆解成為更小Method的語意單位,而這些可被拆解出的小型Method,在系統中往往可供其他大型Method叫用。

試想以下的情境:為了實作物件的Public Method A所欲提供的強大功能,你將所需的程式碼全都寫到這個Method裡,造就了一個Long Method。當你開始著手撰寫另一個物件需要提供的Public Method B時,發現B的實作裡,也會需要A中的一段完全一樣或者高度相似的程式碼,這個時候該怎麼辦?

你會將這兩個Method所共同需要的部份,抽離成為單一的Private Method C,並在A及B中叫用Method C,以滿足A及B的需求;還是選擇直接將A的程式碼複製到B中?

有很高比例的程式員,會選擇後者:複製/貼上,而這同時衍生出另一個Bad Smell──相似或重複的程式碼(Duplicated Code)。除了Duplicated Code的負面影響,複製/貼上的做法,也阻斷「抽離一段程式碼,以被重複使用」的可能性。

因為這個理應可以成為Method C的程式碼片段,被藏在Method A B中,而接手開發或維護的後人,倘若不知道A和B之間有這段關係,便可能自行撰寫與Method C作用相當的程式碼,或者直接複製/貼上A或B的程式碼。

提煉函式──避免程式開枝散葉,造成維護的困難
冗長的Method和相似或重複的程式碼之間的關係,就好比狼與狽這兩種動物之間的關係,時常是相伴出現的。

如此一來,系統中便處處充滿具有相同血統,或再雜以其他血脈的程式碼。這個問題的根本危害何在?如果位居血統最根源位置的那段程式碼,被發現有問題,由於這段程式碼的子子孫孫們可能已經在系統中開枝散葉,可能難以找出衍生於此的所有程式碼,即便找出,也得大費周章地一一改正。

重構方法中的「提煉函式(Extract Method)」是對付過度冗長Method,及相似或重複程式碼的主要方法之一。所謂「提煉函式」,便是將程式碼中的某一片段,抽離出來成為獨立的Method。

當你發現某個Method過於冗長時,便應該考慮將其中的可獨立的語意單元抽離出來,另行化為一個單獨的(較低階的)Method。這麼一來,較高階的Method,閱讀起來便像是由數個低階的Method所組成,容易從它代表的語義理解高階Method的運作,不致於被Method中較不具語義代表性的冗長程式述句影響。

相同地,當你發現正在撰寫的程式碼,在其他地方已經存在相同或類似的片段,不應該使用複製/貼上功能。相反的,應該將既存的程式碼抽離出來成為獨立的Method(同樣是提煉函式的手法),並在原處及新處,以叫用Method的方式,運用這段程式碼。

多長才算過長?語義的距離是衡量標準
另一個有趣的問題是,過度冗長Method的這個問題,究竟一個Method的行數要有多長才算是過長呢?這問題不好回答,基本上和Method本身的內容,以及實作的程式語言有關(畢竟不同程式語言的抽象表示力也不同)。

每個人習慣的長度也不同,但從問題的源頭推敲起,Martin Fowler所謂的「語義的距離」我想才是衡量的標準。也就是說,從該Method名稱所代表的語義,與程式中所代表的語義,以兩者之間的落差來判斷。

倘若Method名稱本身代表著高階的語義,其中包含的程式述句卻極其低階,這意謂著它所包含的程式述句,有需要被抽離成具備中階語義的Method,以供此高階Method之用。

許多程式碼都廣泛存在本文中所提到的問題,也深受其危害。問題雖然常見,卻是許多程式碼品質問題的根本。若能夠妥善利用重構技巧解決,對品質必有顯著幫助。

《作者簡介》王建興
清華大學資訊工程系的博士研究生,研究興趣包括電腦網路、點對點網路、分散式網路管理、以及行動式代理人,專長則是Internet應用系統的開發。曾參與過的開發專案性質十分廣泛而且不同,從ERP、PC Game到P2P網路電話都在他的涉獵範圍之內。

相關閱讀:
物件導向程式設計常見的錯誤(2)責任分配均衡才是健康的系統
物件導向程式設計常見的錯誤(3)立體的系統架構,可降低修改的影響
物件導向程式設計常見的錯誤(4)Façade畫分子系統,可加速開發
物件導向程式設計常見的錯誤(5)工程化的考量不可走火入魔

熱門新聞

Advertisement