上次我說了一些 “ 複雜的重構技巧
” ,講的是一些使用 IntelliJ 的簡單功能實作複雜的重構需求的技巧。 看到大家的反響之後我就感覺那個可能不大親民,因為很多人連 inline 這功能都不知道(那豈不是把 IntelliJ 用成了記事本), 于是我決定再寫一篇講講 IntelliJ 已經提供好了的一些複雜的重構功能。
這就不再是需要自己進行奇奇怪怪的操作的教程了,就會親民得多。
https://blog.didispace.com/intellij-idea-refactoring-skills-2/#%E4%BB%8E%E6%96%B9%E6%B3%95%E4%B8%AD%E6%8F%90%E5%8F%96%E6%96%B9%E6%B3%95 從方法中提取方法
這是用來快速複用一段代碼的功能,名叫 “Extract Method” 。
比如,我現在有這麼一段業務代碼(順帶一提,這是在 Java 調用動态語言 API 時能使用的最健壯的處理數值類型的方法):
liceEnv.defineFunction("run-later", ((metaData, nodes) -> {
Number time = (Number) nodes.get(0).eval();
Consumer<Node> nodeConsumer = Node::eval;
if (time != null) runLater(time.longValue(), () -> {
for (int i = 1; i < nodes.size(); i++) {
// 截圖之前寫的時候腦抽了,這個是後來改的
nodeConsumer.accept(nodes.get(i));
}
});
return new ValueNode(null, metaData);
}));
...
為了效率考慮,你決定不使用
subList(1, nodes.size()).forEach
而是使用
for
循環。
然後你突然發現,這個 “周遊一個集合除了第一個元素之外的元素” 操作在你的代碼裡面已經被調用了很多次了。
于是你決定貫徹 “非極端性 DRY 原則” ,把這坨代碼複用起來。
我們仔細觀察一下。
這坨代碼中,直覺上,我們希望可以通過形如
nodes.forEachExceptFirst(someOperation::accept)
的代碼來一行處理這個操作的(不懂方法引用的請退群),但是這個
forEachExceptFirst
是不存在的。
是以我們想自己造一個。
這時候我們就應該使用 IntelliJ IDEA 提供的
Extract method
功能了。
首先選中那一堆代碼,然後按下 Ctrl+Alt+m,看到這麼一個視窗。
然後我們在 “Name” 那一欄輸入
forEachExceptFirst
,也就是我們想提取的函數的函數名;然後回車。
我們可以看到,代碼變成了這樣:
liceEnv.defineFunction("run-later", ((metaData, nodes) -> {
Number time = (Number) nodes.get(0).eval();
Consumer<Node> nodeConsumer = Node::eval;
if (time != null) runLater(time.longValue(), () -> {
forEachExceptFirst(nodes, nodeConsumer);
});
return new ValueNode(null, metaData);
}));
...
我們可以看看它生成的這個
forEachExceptFirst
方法:
private void forEachExceptFirst(
List<? extends Node> nodes,
Consumer<Node> nodeConsumer) {
for (int i = 1; i < nodes.size(); i++) {
nodeConsumer.accept(nodes.get(i));
}
}
然後你就可以在其他地方使用這個方法了。
我們可以給它加上
JetBrains annotations:
private void forEachExceptFirst(
@NotNull List<@NotNull ? extends @NotNull Node> nodes,
@NotNull Consumer<@NotNull Node> nodeConsumer) {
for (int i = 1; i < nodes.size(); i++) {
nodeConsumer.accept(nodes.get(i));
}
}
當然加這麼多意義不大,對
Node
類型的
@NotNull
注解是可以去掉的。
撤回這個操作的話,請使用
上一篇部落格所大量使用的
inline
功能。
https://blog.didispace.com/intellij-idea-refactoring-skills-2/#%E4%BB%8E%E7%B1%BB%E4%B8%AD%E6%8F%90%E5%8F%96%E6%8E%A5%E5%8F%A3 從類中提取接口
比如,我們有這麼一個 Java 類(最近突然覺得,對類型的注解應該比可見性修飾符更靠近類型(比如在一個方法中, 我就可以用這種方法來區分對傳回類型的注解(比如
@NotNull
)和對方法本身的注解(比如
@Override
)), 是以就有了這麼個把注解寫在可見性修飾符後面的奇怪的寫法,希望讀者不要介意這一點)。
public class Marisa {
// blablabla
public Marisa(@NotNull Touhou game) {
// blablabla
}
public @NotNull ImageObject player() {
return player;
}
public @NotNull List<@NotNull ImageObject> bullets() {
return makeLeftRightBullets(player, mainBullet);
}
public void dealWithBullet(@NotNull ImageObject bullet) {
// blablabla
}
}
代碼中省去了一些對文章不重要的細節。
然後我們可以在類名上右鍵,然後找到這個東西:
這樣我們會看到一個視窗,裡面的東西還挺複雜的:
首先我們在 “Interface name” 那裡填我們想抽取的接口的名字,比如剛剛的那個類
Marisa
,就很适合
GensokyoManagement
(畢竟魔理沙是幻想鄉兩位城管之一嘛,又因為城管的翻譯是
Urban management
) 這個名字的接口。
然後我們希望把這三個方法都抽取到接口裡面去,于是就勾選下面的三個方法。請根據實際需求勾選需要抽取的方法。
最後回車。
這時候 IntelliJ IDEA 會詢問你,是否 “盡可能在這個類被使用的地方,把這個類的類型改成接口的類型”。
這是一種很好的作法,比如我們會傾向于把
LinkedList<Marisa> gensokyoManagements = new LinkedList<Marisa>();
寫成
List<GensokyoManagement> gensokyoManagements = new LinkedList<Marisa>();
,對不對吖。
這裡這個提示就是問你要不要這麼換一波的。這個就看需求了,另外建議取消勾選下面的 “Preview usages to be changed”。
最後我們就提取出來了這麼個玩意(這裡隻有三個方法是以生成的代碼很少,看起來不是很高大上, 如果你實作了一種操作比較多的資料結構(比如線段樹啊,各種圖啊樹啊)再這麼來一波,就能生成一大坨):
public interface GensokyoManagement {
@NotNull ImageObject player();
@NotNull List<@NotNull ImageObject> bullets();
void dealWithBullet(@NotNull ImageObject bullet);
}
然後我們就可以再寫其他類,比如:
public class Reimu implements GensokyoManagement {
}
然後讓 IntelliJ IDEA 自動生成之前那些方法,然後我們就可以愉快地寫實作啦。
https://blog.didispace.com/intellij-idea-refactoring-skills-2/#%E6%8E%A5%E5%8F%A3%E4%B8%8E%E5%AE%9E%E7%8E%B0%E9%97%B4%E7%9A%84%E4%BA%92%E7%9B%B8%E5%8F%91%E9%80%81%E4%BB%A3%E7%A0%81 接口與實作間的互相發送代碼
我們還有很多可以做的事情,比如我們現在給
Marisa
類加了新方法作為新功能,然後我們想給
Reimu
也加上, 并把這個方法作為
GensokyoManagement
的一個抽象方法之一(接口的方法是預設抽象的,别因為省了
abstract
修飾符就以為不是了):
public @NotNull List<@NotNull ImageObject> spellCard() {
return masterSpark();
}
我們可以這樣,在新方法上右鍵,然後這麼選:
這樣我們會看到一個視窗,裡面的東西不怎麼複雜:
隻需要勾選我們要送給接口(或者父類)的方法,然後回車就好了。
IntelliJ IDEA 會給你加上
@Override
修飾符,和生成新的抽象方法。
然後我們就可以跳到
Reimu
類,讓 IntelliJ IDEA 生成一個空實作,然後接着寫啦。