天天看點

《重構:改善既有代碼的設計》—第1章1.1節起點

本節書摘來自異步社群《重構:改善既有代碼的設計》一書中的第1章,第1.節,作者【美】martin fowler,更多章節内容可以通路雲栖社群“異步社群”公衆号檢視。

第1章 重構,第一個案例

重構:改善既有代碼的設計

我該從何說起呢?按照傳統做法,一開始介紹某個東西時,首先應該大緻講講它的曆史、主要原理等等。可是每當有人在會場上介紹這些東西,總是誘發我的瞌睡蟲。我的思緒開始遊蕩,我的眼神開始迷離,直到主講人秀出執行個體,我才能夠提起精神。執行個體之是以可以拯救我于太虛之中,因為它讓我看見事情在真正進行。談原理,很容易流于泛泛,又很難說明如何實際應用。給出一個執行個體,就可以幫助我把事情認識清楚。

是以我決定從一個執行個體說起。在此過程中我将告訴你很多重構的道理,并且讓你對重構過程有一點感覺。然後我才能向你展開通常的原理介紹。

但是,面對這個介紹性執行個體,我遇到了一個大問題。如果我選擇一個大型程式,那麼對程式自身的描述和對整個重構過程的描述就太複雜了,任何讀者都不忍卒讀(我試了一下,哪怕稍微複雜一點的例子都會超過100頁)。如果我選擇一個容易了解的小程式,又恐怕看不出重構的價值。

和任何立志要介紹“應用于真實世界中的有用技術”的人一樣,我陷入了一個十分典型的兩難困境。我隻能帶引你看看如何在一個我所選擇的小程式中進行重構,然而坦白說,那個程式的規模根本不值得我們那麼做。但是如果我給你看的代碼是大系統的一部分,重構技術很快就變得重要起來。是以請你一邊觀賞這個小例子,一邊想象它身處于一個大得多的系統。

1.1 起點

執行個體非常簡單。這是一個影片出租店用的程式,計算每一位顧客的消費金額并列印詳單。操作者告訴程式:顧客租了哪些影片、租期多長,程式便根據租賃時間和影片類型算出費用。影片分為三類:普通片、兒童片和新片。除了計算費用,還要為常客計算積分,積分會根據租片種類是否為新片而有不同。

我用了幾個類來表現這個例子中的元素。圖1-1是一張uml類圖,用以顯示這些類。

《重構:改善既有代碼的設計》—第1章1.1節起點

圖1-1 本例一開始的各個類。此圖隻顯示最重要的特性。圖中所用符号是

uml([fowler,uml])

我會逐一列出這些類的代碼。

movie(影片)

movie隻是一個簡單的純資料類。

rental(租賃)

rental表示某個顧客租了一部影片。

customer(顧客)

customer類用來表示顧客。就像其他類一樣,它也擁有資料和相應的通路函數:

customer還提供了一個用于生成詳單的函數,圖1-2顯示這個函數帶來的互動過程。完整代碼顯示于下一頁。

《重構:改善既有代碼的設計》—第1章1.1節起點

對此起始程式的評價

這個起始程式給你留下什麼印象?我會說它設計得不好,而且很明顯不符合面向對象精神。對于這樣一個小程式,這些缺點其實沒有什麼大不了的。快速而随性地設計一個簡單的程式并沒有錯。但如果這是複雜系統中具有代表性的一段,那麼我就真的要對這個程式信心動搖了。customer裡頭那個長長的statement()做的事情實在太多了,它做了很多原本應該由其他類完成的事情。

即便如此,這個程式還是能正常工作。是以這隻是美學意義上的判斷,隻是對醜陋代碼的厭惡,是嗎?如果不去修改這個系統,那麼的确如此,編譯器才不會在乎代碼好不好看呢。但是當我們打算修改系統的時候,就涉及了人,而人在乎這些。差勁的系統是很難修改的,因為很難找到修改點。如果很難找到修改點,程式員就很有可能犯錯,進而引入bug。

在這個例子裡,我們的使用者希望對系統做一點修改。首先他們希望以html格式輸出詳單,這樣就可以直接在網頁上顯示,這非常符合時下的潮流。現在請你想一想,這個變化會帶來什麼影響。看看代碼你就會發現,根本不可能在列印html報表的函數中複用目前statement()的任何行為。你唯一可以做的就是編寫一個全新的htmlstatement(),大量重複statement()的行為。當然,現在做這個還不太費力,你可以把statement()複制一份然後按需要修改就是了。

但如果計費标準發生變化,又會如何?你必須同時修改statement()和htmlstatement(),并確定兩處修改的一緻性。當你後續還要再修改時,複制粘貼帶來的問題就浮現出來了。如果你編寫的是一個永不需要修改的程式,那麼剪剪貼貼就還好,但如果程式要儲存很長時間,而且可能需要修改,複制粘貼行為就會造成潛在的威脅。

現在,第二個變化來了:使用者希望改變影片分類規則,但是還沒有決定怎麼改。他們設想了幾種方案,這些方案都會影響顧客消費和常客積分點的計算方式。作為一個經驗豐富的開發者,你可以肯定:不論使用者提出什麼方案,你唯一能夠獲得的保證就是他們一定會在六個月之内再次修改它。

為了應付分類規則和計費規則的變化,程式必須對statement()做出修改。但如果我們把statement()内的代碼複制到用以列印html詳單的函數中,就必須確定将來的任何修改在兩個地方保持一緻。随着各種規則變得越來越複雜,适當的修改點越來越難找,不犯錯的機會也越來越少。

你的态度也許傾向于盡量少修改程式:不管怎麼說,它還運作得很好。你心裡牢牢記着那句古老的工程諺語:“如果它沒壞,就不要動它。”這個程式也許還沒壞掉,但它造成了傷害。它讓你的生活比較難過,因為你發現很難完成客戶所需的修改。這時候,重構技術就該粉墨登場了。

未标題-1 

如果你發現自己需要為程式添加一個特性,而代碼結構使你無法很友善地達成目的,那就先重構那個程式,使特性的添加比較容易進行,然後再添加特性。

繼續閱讀