.Net 2.0 新功能:重構(Refactoring)

原貼轉自http://blog.csdn.net/edisundong/archive/2007/09/05/1772829.aspx

1. 前言

  本來不想寫重構,因爲VS2005的加了重構功能但目前和Resharper、Eclipse等還是沒有可比性。但既然已經有了重構,那麼預計VS系列將加強這方面的功能,所以還是先來體驗下吧。

2. 什麼是重構

  重構是在編寫代碼後在不更改代碼的外部行爲的前提下通過更改代碼的內部結構來改進代碼的過程。目的是提高其可理解性,降低其修改成本。

  通俗的說法就是,程序的功能和結果沒有任何的變化。重構只是對程序內部結構進行調整,讓代碼更加容易理解,然後更容易維護。

3. 爲什麼要重構

  至於爲什麼要重構,因本人才疏學淺,故特引用軟件工程專家的一段話:

  在不改變系統功能的情況下,改變系統的實現方式。爲什麼要這麼做?投入精力不用來滿足客戶關心的需求,而是僅僅改變了軟件的實現方式,這是否是在浪費客戶的投資呢?

  重構的重要性要從軟件的生命週期說起。軟件不同與普通的產品,他是一種智力產品,沒有具體的物理形態。一個軟件不可能發生物理損耗,界面上的按鈕永遠不會因爲按動次數太多而發生接觸不良。那麼爲什麼一個軟件製造出來以後,卻不能永遠使用下去呢?

  對軟件的生命造成威脅的因素只有一個:需求的變更。一個軟件總是爲解決某種特定的需求而產生,時代在發展,客戶的業務也在發生變化。有的需求相對穩定一些,有的需求變化的比較劇烈,還有的需求已經消失了,或者轉化成了別的需求。在這種情況下,軟件必須相應的改變。

  考慮到成本和時間等因素,當然不是所有的需求變化都要在軟件系統中實現。但是總的說來,軟件要適應需求的變化,以保持自己的生命力。

  這就產生了一種糟糕的現象:軟件產品最初製造出來,是經過精心的設計,具有良好架構的。但是隨着時間的發展、需求的變化,必須不斷的修改原有的功能、追加新的功能,還免不了有一些缺陷需要修改。爲了實現變更,不可避免的要違反最初的設計構架。經過一段時間以後,軟件的架構就千瘡百孔了。bug越來越多,越 來越難維護,新的需求越來越難實現,軟件的構架對新的需求漸漸的失去支持能力,而是成爲一種制約。最後新需求的開發成本會超過開發一個新的軟件的成本,這就是這個軟件系統的生命走到盡頭的時候。

  重構就能夠最大限度的避免這樣一種現象。系統發展到一定階段後,使用重構的方式,不改變系統的外部功能,只對內部的結構進行重新的整理。通過重構,不斷的調整系統的結構,使系統對於需求的變更始終具有較強的適應能力。

  通過重構可以達到以下的目標:

  ·持續偏糾和改進軟件設計

  重構和設計是相輔相成的,它和設計彼此互補。有了重構,你仍然必須做預先的設計,但是不必是最優的設計,只需要一個合理的解決方案就夠了,如果沒有重構、程序設計會逐漸腐敗變質,愈來愈像斷線的風箏,脫繮的野馬無法控制。重構其實就是整理代碼,讓所有帶着發散傾向的代碼迴歸本位。

  ·使代碼更易爲人所理解

  Martin Flower在《重構》中有一句經典的話:"任何一個傻瓜都能寫出計算機可以理解的程序,只有寫出人類容易理解的程序纔是優秀的程序員。"對此,筆者感觸很深,有些程序員總是能夠快速編寫出可運行的代碼,但代碼中晦澀的命名使人暈眩得需要緊握坐椅扶手,試想一個新兵到來接手這樣的代碼他會不會想當逃兵呢?

  軟件的生命週期往往需要多批程序員來維護,我們往往忽略了這些後來人。爲了使代碼容易被他人理解,需要在實現軟件功能時做許多額外的事件,如清晰的排版佈局,簡明扼要的註釋,其中命名也是一個重要的方面。一個很好的辦法就是採用暗喻命名,即以對象實現的功能的依據,用形象化或擬人化的手法進行命名,一個很好的態度就是將每個代碼元素像新生兒一樣命名,也許筆者有點命名偏執狂的傾向,如能榮此雅號,將深以此爲幸。

  對於那些讓人充滿迷茫感甚至誤導性的命名,需要果決地、大刀闊斧地整容,永遠不要手下留情!

  ·幫助發現隱藏的代碼缺陷

  孔子說過:溫故而知新。重構代碼時逼迫你加深理解原先所寫的代碼。筆者常有寫下程序後,卻發生對自己的程序邏輯不甚理解的情景,曾爲此驚悚過,後來發現這種症狀居然是許多程序員常患的"感冒"。當你也發生這樣的情形時,通過重構代碼可以加深對原設計的 理解,發現其中的問題和隱患,構建出更好的代碼。

  ·從長遠來看,有助於提高編程效率

  當你發現解決一個問題變得異常複雜時,往往不是問題本身造成的,而是你用錯了方法,拙劣的設計往往導致臃腫的編碼。

  改善設計、提高可讀性、減少缺陷都是爲了穩住陣腳。良好的設計是成功的一半,停下來通過重構改進設計,或許會在當前減緩速度,但它帶來的後發優勢卻是不可低估的。

4. 何時使用重構

  新官上任三把火,開始一個全新的項目時,程序員往往也會燃起三把火:緊鑼密鼓、腳不停蹄、加班加點,一支聲勢浩大的千軍萬"碼"夾裹着程序員激情和扣擊鍵盤的鳴金奮力前行,勢如破竹,攻城掠地,直指"黃龍府"。

  開發經理是這支浩浩湯湯代碼隊伍的統帥,他負責這支隊伍的命運,當齊恆公站在山頂上看到管仲訓練的 隊伍整齊劃一地前進時,他感嘆說"我有這樣一支軍隊哪裏還怕沒有勝利呢?"。但很遺憾,你手中的這支隊伍原本只是散兵遊勇,在前進中招兵買馬,不斷壯大, 所以隊伍變形在所難免。當開發經理髮覺隊伍變形時,也許就是剋制住攻克前方山頭的誘惑,停下腳步整頓隊伍的時候了。

  Kent Beck提出了"代碼壞味道"的說法,和我們所提出的"隊伍變形"是同樣的意思,隊伍變形的信號是什麼呢?以下列述的代碼症狀就是"隊伍變形"的強烈信號:

  ·代碼中存在重複的代碼

  中國有118 家整車生產企業,數量幾乎等於美、日、歐所有汽車廠家數之和,但是全國的年產量卻不及一個外國大汽車公司的產量。重複建設只會導致效率的低效和資源的浪費。

  程序代碼更是不能搞重複建設,如果同一個類中有相同的代碼塊,請把它提煉成類的一個獨立方法,如果不同類中具有相同的代碼,請把它提煉成一個新類,永遠不要重複代碼。

  過大的類和過長的方法

  過大的類往往是類抽象不合理的結果,類抽象不合理將降低了代碼的複用率。方法是類王國中的諸侯國, 諸侯國太大勢必動搖中央集權。過長的方法由於包含的邏輯過於複雜,錯誤機率將直線上升,而可讀性則直線下降,類的健壯性很容易被打破。當看到一個過長的方 法時,需要想辦法將其劃分爲多個小方法,以便於分而治之。

  牽一毛而需要動全身的修改

  當你發現修改一個小功能,或增加一個小功能時,就引發一次代碼地震,也許是你的設計抽象度不夠理想,功能代碼太過分散所引起的。

  類之間需要過多的通訊

  A類需要調用B類的過多方法訪問B的內部數據,在關係上這兩個類顯得有點狎暱,可能這兩個類本應該在一起,而不應該分家。

  過度耦合的信息鏈

  "計算機是這樣一門科學,它相信可以通過添加一箇中間層解決任何問題",所以往往中間層會被過多地追加到程序中。如果你在代碼中看到需要獲取一個信息,需要一個類的方法調用另一個類的方法,層層掛接,就象輸油管一樣節節相連。這往往是因爲銜接層太多造成的,需要查看就否有可移除的中間層,或是否可以提供更直接的調用方法。

  各立山頭幹革命

  如果你發現有兩個類或兩個方法雖然命名不同但卻擁有相似或相同的功能,你會發現往往是因爲開發團隊成員協調不夠造成的。筆者曾經寫了一個頗好用的字符串處理類,但因爲沒有及時通告團隊其他人員,後來發現項目中居然有三個字符串處理類。革命資源是珍貴的,我們不應各立山頭幹革命。

  不完美的設計

  在筆者剛完成的一個比對報警項目中,曾安排阿朱開發報警模塊,即通過Socket向指定的短信平臺、語音平臺及客戶端報 警器插件發送報警報文信息,阿朱出色地完成了這項任務。後來用戶又提出了實時比對的需求,即要求第三方系統以報文形式向比對報警系統發送請求,比對報警系 統接收並響應這個請求。這又需要用到Socket報文通訊,由於原來的設計沒有將報文通訊模塊獨立出來,所以無法複用阿朱開發的代碼。後來我及時調整了這個設計,新增了一個報文收發模塊,使系統所有的對外通訊都複用這個模塊,系統的整體設計也顯得更加合理。

  每個系統都或多或少存在不完美的設計,剛開始可能注意不到,到後來纔會慢慢凸顯出來,此時唯有勇於更改纔是最好的出路。

  缺少必要的註釋

  雖然許多軟件工程的 書籍常提醒程序員需要防止過多註釋,但這個擔心好象並沒有什麼必要。往往程序員更感興趣的是功能實現而非代碼註釋,因爲前者更能帶來成就感,所以代碼註釋 往往不是過多而是過少,過於簡單。人的記憶曲線下降的坡度是陡得嚇人的,當過了一段時間後再回頭補註釋時,很容易發生"提筆忘字,愈言且止"的情形。

  曾在網上看到過微軟的代碼註釋,其詳盡程度讓人歎爲觀止,也從中體悟到了微軟成功的一個經驗。

5. 如何使用重構

  VS.NET 2005中包括一個菜單"重構",你可以用它來實現一些常見的重構任務。下圖顯示出這個重構菜單和它的菜單項。重構菜單

VS.NET 2005進行重構的方法有以下幾個:

• 重構類型

<1>. 提取方法

<2>. 重命名

<3>. 封裝字段

<4>. 提取接口

<5>. 將局部變量提升爲參數

<6>. 移除參數

<7>. 重新排列參數

【1】• 提取方法

<1>. 可以通過從現有成員的代碼塊中提取選定的代碼來創建新方法.

<2>. 創建的新方法中包含選定的代碼,而現有成員中的選定代碼被替換爲對新方法的調用.

<3>. 代碼段轉換爲其自己的方法,使您可以快速而準確地重新組織代碼,以獲得更好的重用和可靠性.

• 優點

<1>. 通過強調離散的可重用方法鼓勵最佳的編碼做法。

<2>. 鼓勵通過較好的組織獲得自記錄代碼。當使用描述性名稱時,高級別方法可以像讀取一系列註釋一樣進行讀取。

<3>. 鼓勵創建細化方法,以簡化重載。

<4>. 減少代碼重複.

• 實例

  當你編寫了一個代碼很長的方法,它包含一些非常複雜的算法集合。在完成該方法以後,你可能意識到它變得太大和太複雜了,以至於其它小組成員無法容易地理解它。因此,你決定把它拆分成多個小函數。這不僅會簡化你的代碼而且還能夠改進其易讀和可維護性。"重構"菜單下的 "提取方法"選項正是適合這一工作。
假設方法爲

public void TestMethod()

{

string s = "";

}

(1) 用選中 string s = ""; 然後點擊"提取方法"菜單選項。立即出現"提取方法"對話框。


(2) 按“確定”,結果如下:

public void TestMethod()

{

NewMethod();

}



private static void NewMethod()

{

string s = "";

}

重構爲我們完成了兩件事情:

· 它根據你的每一次選擇創建一個新的方法並且替換其中的所有選擇的代碼。

· 它用一個到這個新創建方法的調用來替換選擇的行。

【2】• 重命名

<1>. 提供了一種重命名代碼符號(如字段、局部變量、方法、命名空間、屬性和類型)標識符的簡單方法.

<2>. “重命名”功能除了可用來更改標識符的聲明和調用以外,還可用來更改註釋中和字符串中的名稱.

• 在何處可以使用重命名操作?

<1>. 代碼編輯器

<2>. 類視圖

<3>. 對象瀏覽器

<4>. Windows 窗體設計器的“屬性網格”

<5>. 解決方案資源管理器



• 重命名執行些什麼操作?

<1>. 字段

• 將字段的聲明和用法更改爲新名稱。

<2>. 局部變量

• 將變量的聲明和用法更改爲新名稱。

<3>. 方法

• 將方法的名稱以及對該方法的所有引用更改爲新名稱。

<4>. 命名空間

• 將聲明、所有正在使用的語句及完全限定名稱中的命名空間名稱更改爲新名稱。

<5>. 屬性

• 將屬性的聲明和用法更改爲新名稱。

<6>. 類型

• 將類型的所有聲明和所有用法都更改爲新名稱,包括構造函數和析構函數。對於部分類型,重命名操作將傳播到其所有部分。

• 實例

假設我們需要“NewMethod”重命名爲“NewMethod2”。

選中 “NewMethod” 然後點擊"重命名"菜單選項。立即出現"重命名"對話框。



結果:

public void TestMethod()

{

NewMethod2();

}



private static void NewMethod2()

{

string s = "";

}

【3】• 封裝字段

<1>. 可以從現有字段快速創建屬性,然後使用對新屬性的引用無縫更新代碼.

<2>. 當某個字段爲public(C# 參考)時,其他對象可以直接訪問該字段並對其進行修改,而不會被擁有該字段的對象檢測到。通過使用屬性(C# 編程指南)封裝該字段,可以禁止對字段的直接訪問。

<3>. 僅當將光標與字段聲明置於同一行時,纔可以執行“封裝字段”操作。

• 實例

大部分開發者都習慣把類級的變量(字段)暴露給外界。由於每一個對象都屬於面向對象編程,所以開發者應該允許通過屬性或方法來存取變量。這種情況可以使用重構菜單下的"封裝字段"選項來進行處理。

爲此,選擇你想包裝在一個屬性中的類級變量並且選擇"封裝字段"選項。這將打開一個如下圖所示的對話框:


你需要輸入該屬性的名字並且決定是否你想從類外或類內部更新到該變量的參考。就象"重命名"對話框一樣,你可以在應用之前先預覽一下所作的改變。

原代碼:

string s;



封裝後代碼:

string s;

public string S

{

get { return s; }

set { s = value; }

}



【4】• 提取接口

<1>. 使用來自現有類、結構或接口的成員創建新接口的簡單方法.

<2>. 當幾個客戶端使用類、結構或接口中成員的同一子集時,或者當多個類、結構或接口具有通用的成員子集時,在接口中嵌入成員子集將很有用.

<3>. 僅當將光標定位於包含要提取成員的類、結構或接口中時,纔可以訪問此功能。當光標處於此位置時,調用“提取接口”重構操作.

• 實例





【5】• 將局部變量提升爲參數

<1>. 提供一種簡單的方法,以在正確更新調用站點的同時將變量從局部使用移動至方法、索引器或構造函數參數.

<2>. 調用“將局部變量提升爲參數”操作時,變量將被添加到成員參數列表的結尾處.

<3>. 對已修改成員的所有調用都將使用新參數(將替代最初賦給該變量的表達式)立即進行更新,並保留代碼,以使其像變量提升之前那樣正常工作.

<4>. 將常數值賦值給提升的變量時,此重構操作效果最好。必須聲明並初始化該變量,而不能僅聲明或僅賦值.

• 實例

原代碼:

private static void NewMethod2()

{

string s = "";

}

選中s,轉換後

private static void NewMethod2(string s)

{



}



【6】• 移除參數

<1>. 從方法、索引器或委託中移除參數的簡單方法.

<2>. 在調用成員的任何位置,都會將參數移除以反映新聲明.

• 實例

原代碼

protected void Page_Load(EventArgs e, object sender)

{

int i = 0;

NewMethod2("1","2");

}



private static void NewMethod2(string s1, string s2)

{

string s = s1 + s2;

}



移除後的代碼

protected void Page_Load(EventArgs e, object sender)

{

int i = 0;

NewMethod2();

}



private static void NewMethod2()

{

string s = s1 + s2;

}



【7】• 重新排列參數

<1>. 對方法、索引器和委託的參數順序進行更改的簡單方法.

<2>. 可以通過方法聲明或方法調用來重新排列參數。要將光標置於方法聲明或委託聲明中,而不是置於正文中。

• 實例

原代碼:

private static void NewMethod2(string s1,string s2)

{

}


重新排列後

private static void NewMethod2(string s2,string s1)

{

發佈了84 篇原創文章 · 獲贊 3 · 訪問量 15萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章