有時候會聽到人家說「因為最近的工作剛好告一段落了,所以我們打算對程式碼來進行重構,好好整理一番」之類的話。

整理程式碼是好事,不過,到了特定的時間點,將重構排程成為一個專門的活動,似乎不太符合重構的精神。事實上,對於重構,似乎流傳著一些認知,和原始重構的想法不甚相符。在本文中,試著做一些探討。

重新思考重構的意義與目的
先回到何謂「重構(refactoring)」吧。所謂的重構,Martin Fowler的定義是「在不改變軟體外部行為的前提下,改變其內部結構,使其更容易理解且易於修改」。也就是說,在對外的介面上沒有做改變、介面背後的對應行為也沒有改變的情況下,基於可讀性,以及日後更便於修改的目的之下,來改寫內部的程式碼實作。

從這個定義上來看,為軟體增加功能肯定不能算上重構。那麼改善安全性或者是提升效能,算不算是重構呢?顯然也不能。改寫程式碼,使得程式碼在不改變行為的情況執行得更快,這應該算最佳化而不能算是重構。不過,也有一些人把這類型的「整理」,歸類在重構的範圍,這可以說是與Martin Fowler的原意不甚相符了。

並不是所謂「整理」程式碼使其變得更好的動作,都可以說是重構。重構的主要目的就是為了提升可讀性,以及為了日後有了需求的變化時,可以更容易因應修改或是擴充。

如果說,你已經有一個系統,也是不斷疊床架屋,在舊有的程式碼基礎上,不斷衍生、添加程式碼,來得到想要的新功能。當你覺得它因為疊床架屋的關係,使得程式碼內模組的關係交錯複雜,希望可以透過整理程式碼的工作,使得它更容易為程式設計者或程式碼的讀者理解,那麼,這是重構的目的。

如果,現有的架構因為持續疊床架屋,從迷你動物的規模逐漸發展成為可怕的大怪獸,使得在當前的情況下,想要增加新的功能,尤其是變化較大的功能,都不容易在現有的程式碼基礎上進行擴充,而你希望改變這一點,那這也是重構的目的。

但是,如果你覺得因為舊有的架構設計,限制了它的效能、或是構成了處理資料時的瓶頸,那麼希望透過翻修架構的方式,使得它的效能不再被舊有的設計所限制,或是它所能處理的資料量提升到另一個等級,那麼,這應該也不能算是重構。當然,修正臭蟲或錯誤,肯定不能算是重構。

很多人會認為他們試著提升效能的程式碼的整理活動,也算在重構的範圍內,但若以原始定義來說,其實不然。不過,在重構的過程中,會試著使程式碼更容易理解,這使得程式設計者或者是讀者的思路,可以因此更加清晰,接著更能夠洞察程式邏輯是否有盲點,例如有臭蟲,或是帶來效能的問題。

所以,我們不能說提升效能是重構的目的,但是透過重構,使得程式碼更清晰易懂,那麼對於察覺既有的各種問題,都可以提供相當的幫助。反觀,倘若面對的是一團複雜不易懂的程式碼,想要從中找出程式設計的問題,無論是臭蟲或是效能問題,難度都會提高許多。

該執行重構的時機

此外,究竟應當在何時來做重構呢?就像本文一開始所提到的,有人會認為「因為最近的工作剛好告一段落了,所以我們可以對程式碼來進行重構,好好的整理一番了」。但是,事實上,重構並不是一項需要額外撥出時間來進行的工作,它應該是在你的開發過程中持續在發生的事情。 據傳禪宗大師神秀曾說:「時時勤拂拭,莫使惹塵埃」,重構的活動,最理想的情況,也就像是如此。透過持續不斷的整理,掃除那些有礙程式可讀性及可維護性的程式碼,讓程式碼持續盡可能地保持在一定健康的狀態。

但是,這也不意謂著對自身所擁有的程式碼,必須像是有潔癖一般的態度來面對。

有些人眼裡容不下任何一粒異樣的沙子,非得把程式碼整理到自己心目中百分之百完美的情況,才願意開始繼續工作。

很多時候我們所面對的、所必須處理的,就是舊有的程式碼(legacy code)。既然稱為legacy(遺產),往往就是前人或別的團隊「遺留」給我們的。這「遺產」,或許不是你心目中最完美的程式碼,但它通常起碼會動,可以正常執行。若是要等你整個大翻修完成,得到百分百完美的程式碼之後,再繼續動工,那麼恐怕工程浩大、曠日費時。這樣的態度通常不是務實的方式。

重構是一個持續進行的活動,或許你不能在第一個時間點就把程式碼變成完美,但是,你可以讓它持續變好,一次改變一點,持續積累起來,它總會達到合格的水平。

既然重構是一個持續進行的活動,但又不是特意安排、特別撥出時間來做的工作,那麼,在什麼樣明確的時間點,應該觸發重構的進行呢?基本上,重構的活動應該伴隨著我們一般開發過程中的主要活動來進行。這些主要活動包括了:增加新功能、修正錯誤、以及程式碼審查的時候。

當我們在增加新功能的時候,通常不只是憑空寫出一些程式碼,然後和舊有的程式碼毫無關聯,接著把它掛到系統上,就可以完成功能的新增。所增加的程式碼,往往和舊有的程式碼有所牽扯。因此,當我們試著增加新功能時,便有可能發現舊有的程式碼可以進行一些調整,而達成了必須重構所想要達成的目的。

你有可能是因為要增加程式碼,更了解舊有的程式碼,而更了解舊有的程式碼後就有可能體會到更簡潔易懂的寫法或設計方式。也有可能因為新需求的產生,使得舊有的架構面臨難以擴充的局面,若不對舊有的程式碼進行對應的調整,就不容易優雅的以現有的程式碼為基礎,將新功能的程式碼擴充上去。

因此,在前者的情況下,你是因為更了解了,所以有能力做更好的改寫;而在後者的情況,當然也可以選擇疊床架屋——硬上,但這正是一個好的重構時機。如果你在增加功能的時候,發現原設計就足以優雅地讓你將新功能擴充上去,那麼,這意謂著,還不太需要做什麼重構。

當你正在除錯的時候,你不僅會接觸到舊有的程式碼,而且,你通常得搞懂它真正的運作邏輯,否則,你是難以找出其中邏輯的。為此,你會更深一層了解程式碼的真正行為,時常也能洞察出舊有程式碼中複雜難解的部份。當你真的懂了之後,就會知道如何用更清晰、簡潔的方式來改寫這段程式碼。除錯時,很容易找到重構的訊號,因為即使程式碼不是你所寫的,你也必須搞懂它的內部運作,而不再只是以旁觀者的角度,僅從介面來理解它。而且,透過除錯的過程,常常能得到更深一層的體會。因為,只有真正了解的人,才能寫下真正易懂的程式碼。

最後一個時間點,就是在審閱程式碼的時候,在審閱的時候,也很容易看出程式碼的問題。只要看到,就可以順手整理。

正因為增加功能、除錯、程式碼審閱,都是正常開發流程中就會發生的事件,而這些事件都有可能觸發重構、也很適合觸發重構,所以,只要依循著這些時機,自然就能夠做到持續的重構。而且,只有對程式碼時時勤拂拭,才能使它少惹塵埃,愈掃愈乾淨呀。

專欄作者

熱門新聞

Advertisement