1. 程式人生 > >Intellij和eclipse程式碼的抽取對比

Intellij和eclipse程式碼的抽取對比

       

第一種註釋:提示性的行內註釋 

常用的“抽取”功能有三種,抽取常量,區域性變數和方法。下面分別介紹一下它們在eclipse和在intellij中的操作方式。 

抽取常量 

在eclipse中抽取常量方式一 

 

補充說明: 
1. 第一步中,先把游標放在需要抽取的表示式內部任意位置,按“alt+shift+上箭頭”選中上一級語法結構,直到你需要抽取的部分被完全選中。(當然你也可以用任意其他方式選中需要抽取的表示式,這裡只列出我自己覺得比較方便的慣用法供參考,下同)。 

2. 選擇Extract to constant後出現的程式碼模板中,有多個輸入點可供選中,例如可把private改為public,改變常量的型別或名稱等,通過TAB鍵切換。 

在eclipse中抽取常量方式二
 

 

補充說明: 
1. 調出重構選單後,直接按A鍵即可選擇Extract Constant 

2. 在彈出的對話方塊中,如果選中Replace all occurrences of the selected expression with references to the constant (預設為選中),則本檔案內所有相同的表示式字面量均被替換為引用這個常量。 

在Intellij中抽取常量 



補充說明: 
1. 在彈出的氣泡框中,選中Replace all occurrence將替換檔案內所有相同的表示式字面量 

2. 氣泡框中的選擇項設定一次後,下次同樣操作將使用前一次的選擇。 

3. 在氣泡框出現時再按一次Ctrl+Alt+C將出現詳細對話方塊,在這個對話方塊中可選擇同時將抽取出來的常量繼續抽取到另一個類上去,當專案中使用一個專門的常量類集中放置常量時可使用該選項。 

抽取區域性變數
 

在eclipse中抽取區域性變數方式一 

 

補充說明: 
1. Ctrl + 1彈出的Quick Fix選項中關於抽取區域性變數的選項有兩個,其中一個將替換當前方法內所有相同的表示式字面量。(後面關於replace all occurrence就不再重複解釋了) 

在eclipse中抽取區域性變數方式二 

 

補充說明: 
1. 勾選“Declare the local variable as "final"選項後,可自動將建立的變數宣告為final。一般情況下,個人建議勾選此選項,等確認變數需要修改時再去掉。這樣可以提示閱讀程式的人那些變數在後續執行中會發生改動。 

在Intellij中抽取區域性變數
 

 

抽取方法 

在eclipse中抽取方法方式一 

 

補充說明: 

1. 重構工具會自動推演新方法所需的引數和返回值型別 

在eclipse中抽取方法方式二 

 

補充說明: 

1. 在彈出的對話方塊中可以對方法的可見性,引數等進行調整。 

在Intellij中抽取方法 

在Intellij中可以用Ctrl+W (功能與eclipse的Shift+Alt+上箭頭相似)選中需要抽取為方法的表示式或程式片段,然後用Ctrl+Alt+M開啟彈出對話窗,與上面的eclipse方式二類似,在此就不重複了。 

============================================================================= 

可以看到,抽取功能除去命名外,每個動作的按鍵次數在兩次到五次左右,基本上不會對正常的開發和閱讀流程產生影響。.最後我們來看看兩種程式碼的對比 

註釋方式: 

 

重構為自描述程式碼的方式 

 

對比一下,自描述的程式碼雖然略長,但在保持可讀性的前提下,消除了兩個“神祕量” ("save" 和 args[0] ),後續程式碼如果需要可以直接引用抽取出來的常量或變數 (很可能,特別是args[0])。這樣無形中提供了一個統一的修改點,例如說以後需求變化,command引數的位置發生了變動,很容易就能統一改過來。 

至於抽取出來的shouldExecuteSave方法,我們也很容易發現這樣一來,將來很容易就會出現一堆的shouldExecuteXXX方法。在前面的基礎上,保持可讀性不變的前提下,可以很自然地想到將它改為: 

 

這樣,後續的判斷都可以統一使用這個方法,形成了又一個統一修改點。並且這個方法可以單獨進行單元測試。可以看出,把程式重構為自描述方式,在保證可讀性的前提下,不但免除了需要額外維護註釋的麻煩,還提供了額外的可擴充套件性,可測試性,一舉四得。 

如果資深員工主動帶頭進行這種重構,能輕易把這種風格推廣開去。不妨想象一下,將這兩段程式碼分別交給新人維護,拿到第一段程式碼的人必然是把這個片段直接複製,然後修改“save”字串字面量和註釋(如果他還記得的話),導致程式中到處都是直接引用字面量和結構相似的表示式。而拿到第二段程式碼的人,也會自然地跟隨現有的程式碼風格,建立新的字串常量,複用已有的方法。 

你也許會懷疑,為了搞什麼“自描述重構”去背那麼多快捷鍵,改變自己的開發習慣是否值得。好訊息是,關於這一點完全不用糾結,即使你不是有意識地進行“自描述重構”,在日常定義常量變數和方法時就充分利用抽取功能也能顯著提高你的開發效率。從前面的操作示例已經可以看出: 

抽取常量可以幫你省去輸入數個修飾符(private static final String)的工作,並且避免了自行在當前輸入位置和常量程式碼段來回跳轉的麻煩; 

抽取區域性變數可以幫你省去一次輸入變數型別以及final關鍵字的麻煩,對於一些名稱具有明確業務含義的類,特別是單例類,可以免除輸入變數名的麻煩。IDE會自動從類名推演出一些候選變數名稱供選擇; 

抽取方法可以幫你省去輸入返回值,引數列表的麻煩。對於一些臨時起意要建立的短小方法,先輸入實現再抽取為方法可以避免思路中斷。 

第二種註釋:對方法的實現邏輯作框架性註釋 

寫這種註釋的意圖是,在編寫方法時,先不動手直接寫程式碼。而是把設計思路和邏輯框架用註釋的形式先列好,經過檢查和評審確定思路正確後,再往這些已經列好的註釋中間填上實現程式碼。 

這種做法由來已久,在1993年出版的《程式碼大全》(《Code Complete》)中,在第四章《建立子程式的步驟》就提到了一種稱為PDL的程式設計語言(Program Design Language) ,應該算是對這種開發模式較為成熟的總結了。一個使用這種方式來設計的框架性註釋可能是這樣的: 

Java程式碼  收藏程式碼
  1. private boolean createDialogResource() {  
  2.     //檢查已在使用的資源數量  
  3.     //如果有其他資源可用  
  4.         //嘗試為一個對話方塊分配資源  
  5.         //如果資源分配成功  
  6.             //登記該資源已被佔用  
  7.             //對該資源進行初始化  
  8.             //將資源號寫入由呼叫者指定的位置  
  9.         //EndIf  
  10.     //EndIf  
  11.     //若新資源建立成功,返回true;否則返回false。  
  12. }  


理想狀態是,人們會上面的這種設計思路進行討論和評審,確定下來後,再在每行之間填入實現程式碼,這樣原本的設計草稿就直接變成了程式碼註釋。 

按照《程式碼大全》的總結,這種做法所帶來的好處有 

引用
儘管第二段 PDL 是完全用自然語言寫成的,但它卻是非常詳細和精確的,很容易作為用程 
序語言編碼的基礎。如果把這段 PDL 轉為註釋段,那它則可以非常明瞭地解釋程式碼的意圖。 
以下是你使用這種風格的 PDL 可以獲得的益處: 

1 PDL 可以使評審工作變得更容易。不必檢查原始碼就可以評審詳細設計。它可以使詳 
細評審變得很容易,並且減少了評審程式碼本身的工作。 

2 PDL 可以幫助實現逐步細化的思想。從結構設計工作開始,再把結構設計細化為 PDL, 
最後把 PDL 細化為原始碼。這種逐步細化的方法,可以在每次細化之前都檢查設計, 
從而可以在每個層次上都可以發現當前層次的錯誤,從而避免影響下一層次的工作。 

3 PDL 使得變動工作變得很容易。幾行 PDL 改起來要比一整頁程式碼容易得多。你是願意 
在藍圖上改一條線還是在房屋中拆掉一堵牆?在軟體開發中差異可能不是這樣明顯, 
但是,在產品最容易改動的階段進行修改,這條原則是相同的。專案成功的關鍵就是 
在投資最少時找出錯誤,以降低改錯成本。而在 PDL 階段的投資就比進行完編碼、測 
試、除錯的階段要低得多,所以儘早發現錯誤是很明智的。 

4 PDL 極大地減少了註釋工作量。在典型的編碼流程中,先寫好程式碼,然後再加註釋。 
而在 PDL 到程式碼的編碼流程中,PDL 本身就是註釋,而我們知道,從程式碼到註釋的花 
費要比從註釋到程式碼高得多。 

5 PDL 比其它形式的設計檔案容易維護。如果使用其它方式,設計與編碼是分隔的,假 
如其中一個有變化,那麼兩者就毫不相關了。在從 PDL 到程式碼的流程中,PDL 語句則 
是程式碼的註釋,只要直接維護註釋,那麼關於設計的 PDL 檔案就是精確的。 


我曾經是這種開發模式的忠實實踐者——當我還是學生的時候。但在實際參與專案之後,就發現情況並沒有想象中理想。在普通的業務系統專案中,根本就沒有人會給你評審PDL,基本上不可能你為每一個方法寫好PDL,就有幾個人等在那裡給你檢查評審。而你也不可能寫好PDL之後就坐在那裡等著有人評審認可後再開工。所以,事實上PDL在大多數情況下就是你自己寫給自己看的草稿,這樣上述1,3點就根本沒用了。其次,我參與實際專案時,面向物件已經開始流行(93年時我還在用BASICA和PASCAL,大概是98年看到這部書時,頗實踐了一下,同時也開始慢慢接受DELPHI的面向物件方法),UML成為了細化設計的主要工具,所以第2點也過期了。 

至於第5點,就是我們之前一直在說的,如果是為了不惜代價保證文件(註釋)與程式碼同步,那維護註釋當然比維護分離的設計文件要方便。但維護註釋這種事在實際中就很難管理和推行。而一旦產生不同步,過期的註釋比過期的文件破壞力大得多。因為我們都知道設計文件通常情況下都與程式碼不同步,只能反映出大致的設計思路。而對於註釋我們總是假定它與程式碼同步的(上一篇文章已經談過,如果你假定註釋與程式碼不同步,那註釋就根本沒有任何作用)。況且,從閱讀體驗和表現手段角度來看,圖文並茂的文件實在比行內註釋實在強太多。 

所以最後,就只剩下第4點。換句話說,今時今日,我們使用這種結構性註釋的最大作用,就是方便我們寫註釋。 

言歸正傳,在目前的技術條件下,有什麼辦法取代這一種註釋方式呢?答案很簡單,把這每一條註釋都以呼叫方法的方式在程式碼中體現出來。例如前面提到的createDialogResource方法可以這樣寫: 

 

注意這時很多方法甚至某些類都還未建立,引數表也還未確定。但是如果忽略中英文的差別,這段程式碼所表達的意思與使用PDL註釋的方式是幾乎一致的。所不同的是,接下來我們不是要在逐行之間填入程式碼,而是需要分別建立和實現這些方法和類。 

使用IDE的Quick Fix功能,我們可以很方便地從呼叫位置反向創建出對應的方法來。 

 

由於IDE在反向建立方法時會自動推演引數表,因此我們可以在開始建立某個方法之前把預計會用到的引數寫入呼叫位置再使用反向建立功能。 

比較兩種方式,後者在保證了可讀性的同時,消除了維護註釋的煩惱,並且把一個長方法拆分為數個功能相對獨立的短小方法。這些短小的方法具有明確的輸入輸出,避免了一些內部變數的交叉混用。它們可以獨立測試。它們可以被複用從而避免了直接複製程式碼片段。 

第三種註釋:關聯業務需求 

所謂關聯業務需求的註釋,就是在某些需要特殊處理或修改過的地方,用註釋的方式指明這個處理是誰誰誰,某年月日,針對某個bug或需求所做的處理,註釋中往往帶著bug跟蹤系統的問題編號,或者需求文件的章節號。 

一般來說,如果這種處理與上下文的邏輯結合得非常自然,這個需求或修正也非常符合人類思維習慣,那麼是不需要做這種特別說明的。除非團隊裡有個人無聊得喜歡把程式碼和需求文件一行行的對上,但這樣的話維護註釋將是個噩夢,而且過多的噪音將掩蓋真正有用的資訊。寫這種註釋的程式設計師當時的想法往往是: 

引用
1. “這不是一種常規做法,我覺得仍可改進,不過這裡有一個古怪的需求(或bug),在你打算做任何改進之前,需要注意不要違反了這項需求(或重現這個bug) 

2. “我在做這個處理時時間很緊,只進行了簡單的測試,不知道會不會引入其他錯誤。如果後來出現了錯誤,而你定位到這裡的話,請注意不要因為改正那個錯誤而違反了這項需求(或重現這個bug) 

3. “我也覺得這段程式碼與附近的程式碼格格不入,放在這裡很詭異,不過我沒有時間去找到更適合的位置了。如果你看到時覺得很礙眼很醜陋,這個需求(或bug)就是原因,請不要來找我了” 

4. “寫給未來的自己:如果你覺得這段程式碼很醜陋,想跳起來在專案組裡公開罵娘,請先看看這段註釋上的署名” 

5. “我在其他地方看到過這種註釋,我覺得很酷,所以我在維護程式碼時做任何修改都會加上這樣的註釋” 


如果是屬於前4種,我覺得這樣的註釋確實應該出現在程式碼中(符合我在上一篇中提到的非常規處理方式的說明註釋)。但是如果是第5種,我的建議是最好停止這種行為。這樣幹最大的害處是,大量這種無用資訊將掩蓋前4種真正有用的資訊。須知道在閱讀程式碼或排查錯誤的過程中不停跳去查閱某段文件或閱讀某個bug資訊是一樣很中斷思路的事。如果是一些通過閱讀程式碼就能明確的事情,經常由於看到這樣的註釋而去翻查文件或bug資訊,到最後又沒有任何實質性的幫助,長此以往程式設計師就會對這類資訊選擇性失明。這包括濫寫註釋的人自己,甚至極有可能是專門針對自己寫的註釋選擇性失明。 

而對於維護程式碼的人來說,前四種也有兩個很明顯的缺點: 

首先,寫這種註釋純粹是個人行為,最常見的動機是別人問起時能有個交代,聰明人固然會寫,但即使不寫,也無從監督。但缺失這類資訊是很麻煩的,經常導致兩個bug輪流出現,改好A,B就來了;隔一段時間發現B,改好了A又回來了。 

其次,時間緊迫的特殊處理往往來不及做良好的設計,而涉及多個類或檔案互相以某種“隱含約定”的方式互相配合。最常見的情況就是在Java類裡往某個Map或Context裡放入一個值,而在頁面上則直接通過鍵值的字面量(通常是字串)直接取得該值。過一段時間後,如果頁面結果發現出錯,維護的人很難追查到這個值是由哪個Java類put進去的。這種情況,即使你在兩個檔案上分別作了類似的註釋,也於事無補,因為它們之間沒有引用關係,看似毫無聯絡。最好的解決辦法是是需要查出與某個修改位置被同時修改的有哪些檔案。 

那麼從團隊的角度出發,有什麼更好方案呢。 

出於前4種動機的需求關聯註釋還是應該寫,它們能起到一個很好的提醒作用。但是作為維護程式碼的人,不能只看這種註釋,在任何你覺得奇怪,醜陋,低階,但在某種情況下又能正確執行的程式碼,在修改前都要先搞清楚這段程式碼的修改歷史和修改動機。而找到這類資訊的最好地方,就是版本控制系統的提交歷史記錄。 

為了達到這個目的,建議團隊管理者能做到以下幾點: 

1. 要求每個程式設計師認真填寫提交記錄,要寫清楚修改的原始需求編號或問題跟蹤編號。簡單說明本次提交本次提交解決了什麼問題或引入了什麼功能。以及有哪些需要注意的地方。禁止使用一兩句無實質意義的提交記錄(例如:“問題修正”,“提交程式碼”之類) 

2. 提倡“小提交”,每解決一個功能點,修正一個bug,只要能編譯通過,就應該提交。如果同時解決了多個問題,則應該分開提交。最終目標是,每次提交的提交記錄都說明一個單獨的功能點,而所提交的檔案都與該說明相關。

3. 瞭解團隊中所使用的版本控制工具的功能與侷限。比如說,CVS不能容易獲取到同一次提交的有哪些檔案,而SVN或GIT都能很容易辦到。SVN在檔案改名或移動後無法跟蹤其歷史記錄(因此除非必要,不要隨意對一些有長期維護歷史的檔案進行移動、改名、刪除重建),GIT則能辦到,等等。 

以上幾點都是可監督,可跟蹤的具體要求,比起“寫好註釋並且保證與程式碼同步”應該更容易落實。 

如果團隊能堅持這幾點,那麼程式設計師就可以通過版本控制工具的Annotation (又或者叫blame)功能來檢視檔案中每一行所對應的業務功能,以及修改人和修改日期。 

舉例來說,在Intellij中開啟右鍵選單,選擇Git -> Annotate (假設你使用了Git作為版本控制工具) 

 

就能在編輯器左側顯示對應到每行的最後更改記錄: 



補充說明: 
1. 在左側的Annotate區中一共四列,分別為:提交編號、提交日期、提交使用者、提交序號。最後一次更改的行會加粗顯示並在右側加星號。在這個例子中,所顯示的檔案從建立開始一共被提交(更改)了8次(包括建立那一次)。提交序號為8的就是最後被更改的行,序號為1的行則從建立之後就一直未被更改過。 

2. 滑鼠移到某行的Annotate上即可出現該提交的詳細提示,包括提交記錄。 

3. 由於我臨時下載的eclipse上沒有裝外掛,就不截圖了。我記憶中在檔案上開啟右鍵選單,選team -> Show Annotation 就可以開啟Annotation。不過沒有Intellij這麼明顯,而是用一些小色塊來表示提交,滑鼠懸浮時會彈出氣泡提示顯示提交資訊。 

這種細緻到行的提交記錄能提供比行內註釋更詳盡和準確的業務需求資訊。並且,雙擊某一次提交的Annotation,將列出這次提交所涉及的所有檔案,解決了前面所說的無法獲得某個修改所涉及的其他檔案的問題。