IntelliJ IDEA 復雜的重構技巧


IntelliJ IDEA 復雜的重構技巧(二)

轉載

上次我說了一些 “復雜的重構技巧” ,講的是一些使用 IntelliJ 的簡單功能實現復雜的重構需求的技巧。 看到大家的反響之后我就感覺那個可能不大親民,因為很多人連 inline 這功能都不知道(那豈不是把 IntelliJ 用成了記事本), 於是我決定再寫一篇講講 IntelliJ 已經提供好了的一些復雜的重構功能。

這就不再是需要自己進行奇奇怪怪的操作的教程了,就會親民得多。

從方法中提取方法

這是用來快速復用一段代碼的功能,名叫 “Extract Method” 。
比如,我現在有這么一段業務代碼(順帶一提,這是在 Java 調用動態語言 API 時能使用的最健壯的處理數值類型的方法):


        
        
       
       
               
  1. liceEnv.defineFunction("run-later", ((metaData, nodes) -> {
  2. Number time = (Number) nodes.get(0).eval();
  3. Consumer <Node> nodeConsumer = Node::eval;
  4. if (time != null) runLater(time.longValue(), () -> {
  5. for (int i = 1; i < nodes.size(); i++) {
  6. // 截圖之前寫的時候腦抽了,這個是后來改的
  7. nodeConsumer.accept(nodes.get(i));
  8. }
  9. });
  10. return new ValueNode(null, metaData);
  11. }));
  12. ...

為了效率考慮,你決定不使用 subList(1, nodes.size()).forEach 而是使用 for 循環。

然后你突然發現,這個 “遍歷一個集合除了第一個元素之外的元素” 操作在你的代碼里面已經被調用了很多次了。
於是你決定貫徹 “非極端性 DRY 原則” ,把這坨代碼復用起來。

我們仔細觀察一下。
這坨代碼中,直覺上,我們希望可以通過形如

nodes.forEachExceptFirst(someOperation::accept)

       
       
      
      
              

的代碼來一行處理這個操作的(不懂方法引用的請退群),但是這個 forEachExceptFirst 是不存在的。

所以我們想自己造一個。
這時候我們就應該使用 IntelliJ IDEA 提供的 Extract method 功能了。

首先選中那一堆代碼,然后按下 Ctrl+Alt+m,看到這么一個窗口。

imgimg

然后我們在 “Name” 那一欄輸入 forEachExceptFirst ,也就是我們想提取的函數的函數名;然后回車。

我們可以看到,代碼變成了這樣:


        
        
       
       
               
  1. liceEnv.defineFunction("run-later", ((metaData, nodes) -> {
  2. Number time = (Number) nodes.get(0).eval();
  3. Consumer <Node> nodeConsumer = Node::eval;
  4. if (time != null) runLater(time.longValue(), () -> {
  5. forEachExceptFirst(nodes, nodeConsumer);
  6. });
  7. return new ValueNode(null, metaData);
  8. }));
  9. ...

我們可以看看它生成的這個 forEachExceptFirst 方法:


        
        
       
       
               
  1. private void forEachExceptFirst(
  2. List <? extends Node> nodes,
  3. Consumer<Node> nodeConsumer) {
  4. for (int i = 1; i < nodes.size(); i++) {
  5. nodeConsumer.accept(nodes.get(i));
  6. }
  7. }

然后你就可以在其他地方使用這個方法了。

我們可以給它加上 JetBrains annotations:


        
        
       
       
               
  1. private void forEachExceptFirst(
  2. @NotNull List <@NotNull ? extends @NotNull Node> nodes,
  3. @NotNull Consumer <@NotNull Node> nodeConsumer) {
  4. for (int i = 1; i < nodes.size(); i++) {
  5. nodeConsumer.accept(nodes.get(i));
  6. }
  7. }

當然加這么多意義不大,對 Node 類型的 @NotNull 注解是可以去掉的。

撤回這個操作的話,請使用上一篇博客所大量使用的 inline 功能。

從類中提取接口

比如,我們有這么一個 Java 類(最近突然覺得,對類型的注解應該比可見性修飾符更靠近類型(比如在一個方法中, 我就可以用這種方法來區分對返回類型的注解(比如 @NotNull)和對方法本身的注解(比如 @Override)), 所以就有了這么個把注解寫在可見性修飾符后面的奇怪的寫法,希望讀者不要介意這一點)。


        
        
       
       
               
  1. public class Marisa {
  2. // blablabla
  3. public Marisa(@NotNull Touhou game) {
  4. // blablabla
  5. }
  6. public @NotNull ImageObject player() {
  7. return player;
  8. }
  9. public @NotNull List <@NotNull ImageObject> bullets() {
  10. return makeLeftRightBullets(player, mainBullet);
  11. }
  12. public void dealWithBullet(@NotNull ImageObject bullet) {
  13. // blablabla
  14. }
  15. }

代碼中省去了一些對文章不重要的細節。

然后我們可以在類名上右鍵,然后找到這個東西:

imgimg

這樣我們會看到一個窗口,里面的東西還挺復雜的:

imgimg

首先我們在 “Interface name” 那里填我們想抽取的接口的名字,比如剛剛的那個類 Marisa ,就很適合GensokyoManagement (畢竟魔理沙是幻想鄉兩位城管之一嘛,又因為城管的翻譯是 Urban management) 這個名字的接口。

然后我們希望把這三個方法都抽取到接口里面去,於是就勾選下面的三個方法。請根據實際需求勾選需要抽取的方法。
最后回車。

這時候 IntelliJ IDEA 會詢問你,是否 “盡可能在這個類被使用的地方,把這個類的類型改成接口的類型”。

imgimg

這是一種很好的作法,比如我們會傾向於把

LinkedList<Marisa> gensokyoManagements = new LinkedList<Marisa>();

       
       
      
      
              

寫成

List<GensokyoManagement> gensokyoManagements = new LinkedList<Marisa>();

       
       
      
      
              

,對不對吖。
這里這個提示就是問你要不要這么換一波的。這個就看需求了,另外建議取消勾選下面的 “Preview usages to be changed”。

最后我們就提取出來了這么個玩意(這里只有三個方法所以生成的代碼很少,看起來不是很高大上, 如果你實現了一種操作比較多的數據結構(比如線段樹啊,各種圖啊樹啊)再這么來一波,就能生成一大坨):


        
        
       
       
               
  1. public interface GensokyoManagement {
  2. @NotNull ImageObject player();
  3. @NotNull List <@NotNull ImageObject> bullets();
  4. void dealWithBullet(@NotNull ImageObject bullet);
  5. }

然后我們就可以再寫其他類,比如:


        
        
       
       
               
  1. public class Reimu implements GensokyoManagement {
  2. }

然后讓 IntelliJ IDEA 自動生成之前那些方法,然后我們就可以愉快地寫實現啦。

接口與實現間的互相發送代碼

我們還有很多可以做的事情,比如我們現在給 Marisa 類加了新方法作為新功能,然后我們想給 Reimu 也加上, 並把這個方法作為 GensokyoManagement 的一個抽象方法之一(接口的方法是默認抽象的,別因為省了 abstract 修飾符就以為不是了):


        
        
       
       
               
  1. public @NotNull List <@NotNull ImageObject> spellCard() {
  2. return masterSpark();
  3. }

我們可以這樣,在新方法上右鍵,然后這么選:

imgimg

這樣我們會看到一個窗口,里面的東西不怎么復雜:

imgimg

只需要勾選我們要送給接口(或者父類)的方法,然后回車就好了。
IntelliJ IDEA 會給你加上 @Override 修飾符,和生成新的抽象方法。

然后我們就可以跳到 Reimu 類,讓 IntelliJ IDEA 生成一個空實現,然后接着寫啦。

本文作者:ice1000
原文鏈接:http://ice1000.org/2017/12/25/IDEARefactoring2/
版權歸作者所有,轉載請注明出處


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM