eclipse重構(gòu)詳解
博客分類:
eclipserefactoring
重構(gòu)是對軟件內(nèi)部結(jié)構(gòu)的一種調(diào)整,目的是在不改變軟件行為的前提下,提高其可理解性,降低其修改成本。開發(fā)人員可以使用一系列重構(gòu)準(zhǔn)則,在不改變軟件行為的前提下,調(diào)整軟件的結(jié)構(gòu)。
有很多種原因,開發(fā)人員應(yīng)該重構(gòu)代碼,例如之前的開發(fā)人員代碼寫得很爛、自己以前設(shè)計(jì)時(shí)有缺陷、需求變更需要添加一些新的功能或修改原有功能等等。Martin Fowler在其著名的<<Refactoring—Improving the Design of Existing Code>>一書中談到了為何重構(gòu)的幾點(diǎn)原因:
1. 重構(gòu)可以改進(jìn)軟件設(shè)計(jì)
如果不進(jìn)行重構(gòu),程序的設(shè)計(jì)會變得越來越糟糕。通常程序員只為短期的目的,或者在沒有完全理解整體設(shè)計(jì)的時(shí)候,就開始修改代碼,這樣程序?qū)饾u失去自己的結(jié)構(gòu),程序員也愈來愈難通過閱讀源碼理解原本設(shè)計(jì),相信對此每一個(gè)開發(fā)人員都深有體會。
代碼結(jié)構(gòu)的流失是累積性的,愈難看出代碼所代表的意思,就越難保護(hù)其中的設(shè)計(jì),于是設(shè)計(jì)也將變得越來越糟糕,經(jīng)常性重構(gòu)可以幫助維持設(shè)計(jì)該有的形態(tài)。
2. 重構(gòu)使軟件更易被理解
很多開發(fā)人員認(rèn)為代碼只要能夠運(yùn)行起來就可以了,筆者剛開始做開發(fā)的時(shí)候也是這么認(rèn)為的,也寫過很多的垃圾代碼,也因此吃了不少苦頭。
也許有些人可能會認(rèn)為自己可能不久就會離開所在的職位,不必在意代碼的質(zhì)量,但作為一個(gè)開發(fā)人員來說,寫出漂亮的代碼是最基本的素質(zhì)。
在軟件的不斷修改過程中,代碼的可讀性越來越差也是會慢慢累積的,但這不要緊,只要記得持續(xù)重構(gòu),就能使自己的代碼更容易被理解。
3. 重構(gòu)可以協(xié)助找到Bugs
對代碼的理解,可以更容易找到bug,在重構(gòu)的同時(shí),也能夠更好的理解代碼及其行為,從而通過重構(gòu)能夠幫助開發(fā)人員寫出更強(qiáng)壯的代碼。
4. 重構(gòu)可以提高編程的速度
良好的設(shè)計(jì)是快速軟件開發(fā)的根本,如果沒有良好的設(shè)計(jì),也許開始的一段時(shí)間開發(fā)人員的進(jìn)展迅速,但是惡劣的設(shè)計(jì)很快就會使開發(fā)速度慢下來。也許把時(shí)間花在調(diào)試上的時(shí)間會越來越多,修改的時(shí)間會越來越長,而且這會是一個(gè)惡性的循環(huán)。
良好的設(shè)計(jì)是維持軟件開發(fā)速度的根本,重構(gòu)可以幫助開發(fā)人員更快速地開發(fā)軟件,因?yàn)樗軌蜃柚瓜到y(tǒng)的設(shè)計(jì)變質(zhì),能夠提高代碼的可讀性。
使用Eclipse進(jìn)行代碼重構(gòu)
重構(gòu)是軟件開發(fā)過程中保證代碼質(zhì)量非常重要的手段,而手動(dòng)進(jìn)行重構(gòu)代碼的話,很容易引入一些低級錯(cuò)誤(例如,單詞拼寫錯(cuò)誤),從而導(dǎo)致浪費(fèi)大量不必要的時(shí)間。Eclipse為重構(gòu)提供了很強(qiáng)大的支持,很大程度上用戶不必為重構(gòu)的筆誤而再煩惱。
在Eclipse中,可以使用JDT提供的重構(gòu)功能對Java項(xiàng)目、類和其成員進(jìn)行重構(gòu),所有這些被重構(gòu)的部分都可以看成一個(gè)JDT能識別的Java元素。要執(zhí)行重構(gòu),首先必須選擇相應(yīng)重構(gòu)的Java元素,一些重構(gòu)是適合任何Java元素的,而一部分重構(gòu)只適合特定的Java元素,幾乎所有的重構(gòu)都能夠在重構(gòu)對話框中看到預(yù)覽的效果。
要使用Eclipse的重構(gòu)功能,可以先選擇相應(yīng)的Java元素(Java工程中的資源,包括工程、文件、方法、變量等),通過右鍵菜單選擇Refactor菜單下的重構(gòu)功能,如圖1所示。
圖1 選擇重構(gòu)菜單
在Eclipse中,可以簡單的把重構(gòu)分為結(jié)構(gòu)性重構(gòu)、類級別重構(gòu)和類內(nèi)部重構(gòu),每種類型的重構(gòu)又分別包含了一些具體的實(shí)現(xiàn),接下來將分別介紹Eclipse如何對Java元素進(jìn)行重構(gòu)。
提示:在JDT可識別的范圍內(nèi),可以認(rèn)為工程中資源都是Java元素,包括Java文件名、類、方法、變量等。
結(jié)構(gòu)性重構(gòu)
結(jié)構(gòu)性重構(gòu)涉及到JAVA元素的物理結(jié)構(gòu)的改變,包括“Rename”、“Move”、“Change Method Signature”、“Convert Anonymous Class to Nested”和“Move Member Type to New File”,下面將一一介紹這些重構(gòu)在Eclipse中的實(shí)現(xiàn)。
1. Rename
Rename重構(gòu)的功能就是重命名Java元素。雖然可以通過手動(dòng)修改文件的文件名或其它Java元素的名稱,但這種方式不會更新與此Java元素相關(guān)聯(lián)的引用,用戶必須手動(dòng)查找和此Java元素相關(guān)的位置,然后進(jìn)行手動(dòng)修改。通過手動(dòng)修改名稱的方式,造成筆誤的可能性會太太增加。通過Eclipse提供的Rename的功能,Eclipse會自動(dòng)完成更新相關(guān)引用的操作。
當(dāng)Java元素的命名不清晰或功能發(fā)生改變的時(shí),為了保持代碼的可讀性,可以通過Eclipse的重構(gòu)功能重命名Java元素。選擇相應(yīng)的Java元素,選擇右鍵Refactor菜單下的Rename菜單,可以對當(dāng)前選擇的元素進(jìn)行重命名,在彈出的重命名對話框中修改相應(yīng)的元素名稱即可,例如修改一個(gè)包的重命名,如圖2所示。
圖2 Rename對話框
要修改包名的同時(shí),可以選擇是否更新引用和更新子目錄,甚至是非Java文件也可以選擇性的更新。選擇Preview按鈕可以預(yù)覽重命名重構(gòu)后的效果,如圖3所示。
圖3 預(yù)覽重命名包名
可以查看預(yù)覽的內(nèi)容是否一致,確認(rèn)是否要進(jìn)行重命名的重構(gòu)。可以進(jìn)行重命名的Java元素有Java項(xiàng)目、Java文件、包、方法和屬性字段等。
提示:非Java項(xiàng)目和Java文件等也可以通過重構(gòu)菜單的Rename進(jìn)行重命名。
2. Move
Move的重構(gòu)和Rename的重構(gòu)類似,它可以把一個(gè)Java元素從一個(gè)地方移動(dòng)到另一個(gè)地方,Move的重構(gòu)主要用來移動(dòng)一個(gè)類到不同的包下。首先選中一個(gè)Java文件,選擇Refactor菜單下的Move菜單項(xiàng),彈出Move的重構(gòu)對話框,如圖4所示。
圖4 Move對話框
可以選擇是否更新引用,設(shè)定移動(dòng)文件重構(gòu)的一些參數(shù)。
提示:也可以通過拖動(dòng)的方式把一個(gè)文件從一個(gè)包移動(dòng)到另一個(gè)包,實(shí)現(xiàn)移動(dòng)文件的重構(gòu)。
3. Change Method Signature
“Change Method Signature”重構(gòu)的功能是改變方法的定義,例如改變方法的參數(shù)名稱、類型和個(gè)數(shù)、返回值的類型,方法的可見性以及方法的名稱等。
要改變方法的定義,可以先選擇方法,通過右鍵菜單選擇Refactor菜單的“Change Method Signature”子菜單項(xiàng),彈出“Change Method Signature”對話框,如圖5所示。
圖5 “Change Method Signature”對話框
可以通過“Change Method Signature”對話框改變方法的參數(shù)名稱、類型和個(gè)數(shù)、返回值的類型,方法的可見性以及方法名稱等。
4. Convert Anonymous Class to Nested
“Convert Anonymous Class to Nested”重構(gòu)的功能是把匿名類改成內(nèi)部類,這樣同一個(gè)類的其它部分也可以共享此類了。
例如有例程1所示的類。
例程1 KeyListenerExample.java
public class KeyListenerExample { Display display; Shell shell; KeyListenerExample() { display = new Display(); shell = new Shell(display); shell.setSize(250, 200); shell.setText("A KeyListener Example"); Text text = new Text(shell, SWT.BORDER); text.setBounds(50, 50, 100, 20); text.addKeyListener(new KeyListener() { public void keyPressed(KeyEvent e) { System.out.println("key Pressed -" + e.character); } public void keyReleased(KeyEvent e) { System.out.println("key Released -" + e.character); } }); shell.open(); while (!shell.isDisposed()) { if (!display.readAndDispatch()) display.sleep(); } display.dispose(); } public static void main(String[] args) { new KeyListenerExample(); } }
在KeyListenerExample類有一個(gè)匿名類,實(shí)現(xiàn)了KeyListener接口,可以把這個(gè)匿名類改成內(nèi)部類,首先選擇匿名類,右鍵選擇Refactor的“Convert Anonymous Class to Nested”菜單,輸入內(nèi)部類的名稱,如圖6所示。
圖6 “Convert Anonymous Class to Nested”對話框
重構(gòu)后的結(jié)果是Eclipse為此創(chuàng)建了一個(gè)內(nèi)部類,名稱為TestKeyListener,重構(gòu)后的代碼如例程2所示。
例程2 重構(gòu)后的KeyListenerExample.java
public class KeyListenerExample { private final class TestKeyListener implements KeyListener { public void keyPressed(KeyEvent e) { System.out.println("key Pressed -" + e.character); } public void keyReleased(KeyEvent e) { System.out.println("key Released -" + e.character); } } Display display; Shell shell; KeyListenerExample() { display = new Display(); shell = new Shell(display); shell.setSize(250, 200); shell.setText("A KeyListener Example"); Text text = new Text(shell, SWT.BORDER); text.setBounds(50, 50, 100, 20); text.addKeyListener(new TestKeyListener()); shell.open(); while (!shell.isDisposed()) { if (!display.readAndDispatch()) display.sleep(); } display.dispose(); } public static void main(String[] args) { new KeyListenerExample(); } }
也可以通過“Convert Anonymous Class to Nested”對話框定義新生成的內(nèi)部類的可訪問性。
5. Move Member Type to Top Level
通過“Move Member Type to Top Level”的重構(gòu)方式,可以把內(nèi)部類改成非內(nèi)部類,并且重新創(chuàng)建一個(gè)新的文件,這樣其它的類就可以共享此類。
例程2創(chuàng)建了一個(gè)內(nèi)部類TestKeyListener,現(xiàn)在可以通過“Move Member Type to Top Level”重構(gòu)的方式,把TestKeyListener放入一個(gè)單獨(dú)的類中。首先選擇TestKeyListener類,從右鍵菜單Refactor中選擇“Move Member Type to Top Level”,打開“Move Member Type to Top Level”對話框,如圖7所示。
圖7 “Move Member Type to Top Level”對話框
通過上面“Move Member Type to Top Level”重構(gòu),可以把內(nèi)部類改成非內(nèi)部類。
提示:有些時(shí)候,重構(gòu)并不是一步完成的,可以一步一步重構(gòu),例如,首先把匿名類改成內(nèi)部類,再接著把內(nèi)部類改成非內(nèi)部類。
類級別重構(gòu)
類級別重構(gòu)有如下一些:
1. Push Down
“Push Down”重構(gòu)功能是把父類的方法和屬性移動(dòng)到所有的子類中,父類的方法可以選擇性的保留抽象方法。首先選擇父類,右鍵選擇Refactor菜單的“Push Down”菜單項(xiàng),可以通過“Push Down”對話框選擇重構(gòu),如圖8所示。
圖8 “Push Down”對話框
“Push Down”重構(gòu)在重新設(shè)計(jì)類的時(shí)候是非常有用的,它可以比較有較的改善類的繼承關(guān)系,清楚定義類的行為。
2. Pull Up
“Pull Up”重構(gòu)和“Push Down”重構(gòu)正好相反,它的作用是把方法和屬性移動(dòng)到其父類中去。選擇需要重構(gòu)的子類,從右鍵菜單選擇Refactor菜單的“Pull up”菜單項(xiàng),通過“Pull Up”對話框進(jìn)行重構(gòu),如圖9所示。
圖9 “Pull Up”對話框
提示:“Pull Up”重構(gòu)和“Push Down”重構(gòu)后可能會出錯(cuò),在使用此重構(gòu)的同時(shí),應(yīng)該先弄清楚某些方法中是否有引用到其它方法或?qū)傩浴?
3. Extract Interface
“Extract Interface”重構(gòu)能夠從一個(gè)已存在的類中提取接口,它可以從某個(gè)類中選擇方法,把這些方法提取到一個(gè)單獨(dú)的接口中。選擇提取接口的類,右鍵選擇Refactor菜單的“Extract Interface”菜單項(xiàng),打開“Extract Interface”對話框,如圖10所示。
圖10 “Extract Interface”對話框
單元OK按鈕,將會提取TestInterface的接口,提取接口后,當(dāng)前選擇的類將會實(shí)現(xiàn)此接口。
提示:只有公用方法才可以被提取為接口的方法。
4. Generalize Declared Type
“Generalize Declared Type”重構(gòu)能夠改變變量、參數(shù)、屬性以及函數(shù)的返回值的類型,可以把這些類型改成其父類的類型。在Refactor菜單中選擇“Generalize Declared Type”,如圖11所示。
圖11 “Generalize Declared Type”對話框
單擊OK按鈕,能夠把聲明的類型改成當(dāng)對話框中選擇的類型。
5. User Supertype Where Possible
“User Supertype Where Possible”重構(gòu)能夠用某一個(gè)類的父類的類型替換當(dāng)前類的類型,選擇需要被替換引用的類。在Refactor菜單中選擇“User Supertype Where Possible”打開“User Supertype Where Possible”對話框,如圖12所示。
圖12 “User Supertype Where Possible”對話框
“Generalize Declared Type”重構(gòu)和“User Supertype Where Possible”重構(gòu)在面向接口編程方面是很有用的,可以把引用的對象盡可能用接口進(jìn)行實(shí)現(xiàn)。
提示:“User Supertype Where Possible”重構(gòu)將替換其它類中的引用,要想看到重構(gòu)的效果,應(yīng)該找到其它類引用的位置,此操作不會修改當(dāng)前文件。
類內(nèi)部重構(gòu)
類內(nèi)部重構(gòu)有如下一些:
1. Inline
“Inline”重構(gòu)能用函數(shù)的內(nèi)容替換掉函數(shù)的引用。首先選擇函數(shù)的引用,在Refactor菜單中選擇“Inline”打開“Inline”對話框,如圖13所示。
圖13 “Inline”對話框
單擊確定按鈕,Eclipse將會用方法實(shí)現(xiàn)的部分替換引用的部分,即當(dāng)前不采用方法調(diào)用的方式進(jìn)行操作。也可以選擇“All invocations”和“Delete method declaration”,Eclipse會替換掉所有引用方法的位置,并且刪除方法。
提示:Inline會用方法的實(shí)現(xiàn)部分替換所有調(diào)用方法的地方。
2. Extract Method
“Extract Method”重構(gòu)和“Inline”重構(gòu)相反,它能夠從冗長的方法中提取小的方法,把大的方法分解成多個(gè)小方法來實(shí)現(xiàn),通過此重構(gòu)能夠使代碼看上去更簡單漂亮,也很大程度上提高代碼的復(fù)用性。可以選擇要提取方法的代碼,在Refactor菜單中選擇“Extract Method”打開“Extract Method”對話框,如圖14所示。
圖14 “Extract Method”對話框
“Extract Method”重構(gòu)是非常好的重構(gòu)方式,能夠把大的方法體重構(gòu)成多個(gè)方法的實(shí)現(xiàn),使代碼更清楚易懂。
提示:“Extract Method”重構(gòu)和“Inline”重構(gòu)是對應(yīng)的,有些時(shí)候?yàn)榱私M織一些不合的函數(shù),可以先通過“Inline”的方式生成一個(gè)大的函數(shù),再通過“Extract Method”來重構(gòu)大的函數(shù),使代碼更趨于合理。
3. Extract Local Variable
在開發(fā)過程中,使用變量代替表達(dá)式是非常好的,這樣能使代碼更容易被理解。Eclipse中可以通過“Extract Local Variable”重構(gòu)實(shí)現(xiàn)提取局部的表達(dá)式。首先選擇表達(dá)式,在Refactor菜單中選擇“Extract Local Variable”打開“Extract Local Variable”對話框,如圖15所示。
圖15 “Extract Local Variable”對話框
4. Extract Constant
“Extract Constant”重構(gòu)和“Extract Local Variable”重構(gòu)類似,它可以把表達(dá)式定義為常量,另外“Extract Constant”重構(gòu)能夠設(shè)定常量的可見性。選擇表達(dá)式,在Refactor菜單中選擇“Extract Constant”打開“Extract Constant”對話框,如圖16所示。
圖16 “Extract Constant”對話框
5. Introduce Parameter
“Introduce Parameter”重構(gòu)可以通過函數(shù)中的表達(dá)式、變量或引用為函數(shù)添加新的參數(shù),還能夠自動(dòng)更新引用此函數(shù)的其它位置的默認(rèn)參數(shù)。要想進(jìn)行“Introduce Parameter”重構(gòu),可以選擇表達(dá)式、變量或引用。在Refactor菜單中選擇“Introduce Parameter”打開“Introduce Parameter”對話框,如圖17所示。
圖17 “Introduce Parameter”對話框
6. Introduce Factory
“Introduce Factory”重構(gòu)能夠?yàn)轭悇?chuàng)建工廠方法。首先選擇需要?jiǎng)?chuàng)建工廠方法的類的構(gòu)造函數(shù),在Refactor菜單中選擇“Introduce Factory”打開“Introduce Factory”對話框,如圖18所示。
圖18 “Introduce Factory”對話框
在“Introduce Factory”對話框中,可以輸入工廠方法的名字,以及工廠類,Eclipse將會自動(dòng)根據(jù)構(gòu)造函數(shù)創(chuàng)建工廠方法。
提示:工廠類應(yīng)該已經(jīng)存在,通常可以在一個(gè)工廠類中為多個(gè)關(guān)聯(lián)的類創(chuàng)建工廠方法,所以在使用“Introduce Factory”重構(gòu)前,應(yīng)該先創(chuàng)建好工廠類。
7. Convert Local Variable to Field
“Convert Local Variable to Field”重構(gòu)能夠把局部的變量轉(zhuǎn)換成類中的全局變量。首先選擇要轉(zhuǎn)換的局部變量,在Refactor菜單中選擇“Convert Local Variable to Field”打開“Convert Local Variable to Field”對話框,如圖19所示。
圖19 “Convert Local Variable to Field”對話框
在“Convert Local Variable to Field”對話框中,還能夠修改變量的名稱以及變量的可見性。
8. Encapsulate Field
“Encapsulate Field”重構(gòu)能夠包裝屬性的可訪問性,以及生成訪問的方法。首先選擇要包裝的屬性,在Refactor菜單中選擇“Encapsulate Field”打開“Encapsulate Field”對話框,如圖20所示。
圖20 “Encapsulate Field”對話框
通常通過“Encapsulate Field”可以生成get和set方法。在“Encapsulate Field”對話框中可以輸入屬性的訪問方法的名稱,以及方法生成的位置和方法的可見性。
提示:通過右鍵菜單的Source菜單也能生成相應(yīng)的get和set方法。
Undo and Redo
Eclipse的自動(dòng)重構(gòu)功能能夠很好的支持各種程序元素的重命名,并自動(dòng)更新相關(guān)的引用。Eclipse能夠支持方法、字段在類之間移動(dòng),并自動(dòng)更新引用,較好地支持內(nèi)聯(lián)字段、函數(shù)的更新替換,較好地支持抽取方法、變量等程序元素。
重構(gòu)的過程是一個(gè)不斷嘗試和探索的過程。Eclipse的重構(gòu)支持撤銷和重做,并且能夠預(yù)覽重構(gòu)結(jié)果,這些是很實(shí)用的功能。要想執(zhí)行撤消和重做(Undo and Redo)的功能,可以直接按快捷鍵Ctrl+Z以及Ctrl+Y,也可以選擇Edit菜單的Undo和Redo操作。
提示:雖然Eclipse對重構(gòu)提供了很強(qiáng)大的支持,但是重構(gòu)后代碼的測試是必不可少的,而且不能指望Eclipse能夠解決所有重構(gòu)的問題,有些時(shí)候手動(dòng)重構(gòu)還是必須的。自動(dòng)重構(gòu)的理念應(yīng)該是“工具輔助下的重構(gòu)工作”,但開發(fā)人員仍然承擔(dān)很大一部分重構(gòu)工作。