IntelliJ IDEA
IntelliJ IDEA – the Leading Java and Kotlin IDE, by JetBrains
IntelliJ IDEA で日々のリファクタリングを快適に
バグの修正、可読性の改善、新機能の追加など、さまざまな理由で作業中のコードを変更することがよくあります。 このブログでは、日々の作業でよく利用されるリファクタリングについて説明します。
このブログ記事では、動画と同じ素材を使用しています。 動画を見るよりも記事を読むほうを好む方がコンテンツに素早く目を通せるように提供されており、コードサンプルと追加情報へのリンクも用意されています。
このブログで使用されるコードはこちらから入手できます。
名前の変更
クラスファイルでエディタ上で直接名前を変更して行う名前変更は、安全ではありません。 たとえば、 Bug クラス をエディターで ABug に変更すると、そのクラスを使用するすべてのコードとの間で参照関係の問題が発生し、更新ミスが生じる可能性があります。
ちなみにこのような場合、 IntelliJ IDEA 2020.2 以降では、エディターに “related problems(関連する問題)” エラーメッセージが表示されるようになっています。
ここでリファクタリング機能を使ってみましょう。In-place
rename(インプレース名前変更)リファクタリング(専用ポップアップを開かずに行えるリファクタリング)が可能な場合、(左側)ガターに強調表示されます。 ガターの R アイコンをクリックすると、Bug のすべての使用箇所を ABug に名前変更するプレビューを確認できます。
この場合、IntelliJ IDEA は、類似する名前を持つ変数も見つけ出してくれるため、どれを更新するのかを選択することができます。
または、エディターでクラス名を変更する場合、⌥⏎ または Alt+Enter
ショートカットを使用して、このステップを繰り返すことができます。
また、macOS では ⇧F6、Windows/Linux では Shift+F6
を使用して、名前変更リファクタリングを呼び出すと、IntelliJ IDEA は、クラス、メソッド、および変数の代替名を提案します。 新しい名前を選択した場合でも、IntelliJ IDEA
によって、リファクタリングを安全に完了することができます。
public class Bug { private long id; private String description; public Bug(long id, String description) { this.id = id; this.description = description; } public long getId() { return id; } public String getDescription() { return description; } }
たとえば、フィールド description
を desc
に名前変更する場合、IntelliJ IDEA は、このフィールドが getter と setter
メソッドで使用されているかどうかを検出し、それらを更新するかどうかを尋ねます。 また、メソッドパラメータ名での使用箇所も更新します。
メソッド getItems
を定義する View
というインターフェースを使って作業してみましょう。 macOS では ⌘B
を、Windows/Linux では Ctrl+B を使用して、それを使用している箇所に移動できます。 このインターフェースは、クラス
ViewImpl
で実装されています。
View
というインターフェースで、macOS では ⇧F6 を、Windows/Linux では
Shift+F6 を使用すると、メソッド getItems
が getItemsList
に名前変更されます。
Enter を押すと、この実装は、ViewImpl
クラスで更新されます。
また、プロジェクトウィンドウ(macOSでは⌘1、Windows/LinuxではAlt+1)にアクセスして macOS では ⇧F6 を、Windows/Linux では Shift+F6
を使用すると、クラス、パッケージ、ファイル、またはディレクトリの名前を変更することも可能です。
シグネチャーの変更
クラスまたはメソッドのシグネチャーを安全に変更する方法を見てみましょう。 BugReport
のメソッド binaryStrings
は、String型引数を受け入れます。
public boolean binaryStrings(String b) { Stack<Character> s = new Stack<>(); for (char c : b.toCharArray()) if (s.empty() || !s.peek().equals(c)) s.push(c); else s.pop(); return s.empty(); }
メソッド binaryStrings
にたとえば int 型のカウンタなどのパラメータを追加できます。 これはインプレースリファクタリングであるため、またガター領域に R のアイコンが強調表示されます。
ガターのアイコンをクリックすると、メソッドの読み出し元がデフォルト値を使用するように、パラメータにデフォルト値を追加することでメソッドのシグネチャーを更新することができます。
また、macOS では ⌥⏎ を、Windows/Linux では Alt+Enter
を使えば、メソッドシグネチャを更新し、デフォルトのパラメータ値を追加することができます。
メソッドシグネチャーは、macOS では ⌘F6 を、Windows/Linux では Ctrl+F6 を押して Change Signature(シグネチャーの変更)ダイアログを呼び出して変更することもできます。
ブール型パラメータ state
をデフォルト値 “true” にして追加してみましょう。 このダイアログを使うと、パラメータの順序を変更することもできます。
オーバーロードによる委譲を選択するオプションもあります。 リファクタリングする際、メソッドはオーバーロードされます。
public class MyMap { int count = 0; public int getCount() { return count; } public void setCount(int Count) { this.count = count; } public static void main(String[] args) { MyMap myMap = new MyMap(); System.out.println(myMap.getCount()); } }
この MyMap
クラスを使用して、クラスシグネチャーの変更がそのクラスにどのような影響を与えるかを確認することができます。 macOS では ⌘F6
を、Windows/Linux では Ctrl+F6 を使用して、クラスシグネチャーを変更することができます。 デフォルト値
Integer
の K
とデフォルト値 String
の V
という 2 つの汎用パラメータを追加しましょう。
クラスをリファクタリングすると、MyMap
の使用箇所が変化します。
メソッドの抽出
Extract method(メソッドの抽出)リファクタリングは、メソッドを短くし、読みやすくします。
public boolean binaryStrings(String b) { Stack<Character> s = new Stack<>(); for (char c : b.toCharArray()) if (s.empty() || !s.peek().equals(c)) s.push(c); else s.pop(); return s.empty(); }
まとまった処理にできそうな binaryStrings
メソッド内のコードを選択します。 macOS では ⌥⌘M を、Windows/Linux では
Ctrl+Alt+M のショートカットを使用すると、このコードを別のメソッドに抽出することができます。
このブロックが使用するローカル変数またはメソッドパラメータは、別のメソッドにパラメータとして渡されます。 たとえばメソッド名 manipulateStack
の型と抽出したコードブロックは、メソッドへの呼び出しと置き換えられます。
コードを選択して、macOS では ⌥⏎ を、Windows/Linux では Alt+Enter
を使用し、メソッドの抽出を選択すると、メソッドを抽出することができます。
メソッドのインライン化
Inline method(メソッドのインライン化)リファクタリングは、Extract
Method(メソッドの抽出)リファクタリングの逆のことを行います。 今作成したメソッド manipulateStack
を、macOS では ⌥⌘N、Windows/Linux
では Ctrl+Alt+N を使用してインライン化することができます。
メソッドコンテンツをインライン化してメソッドを削除するか、メソッドを維持することができます。 この例では、メソッドを削除しています。
でも、メソッドをインライン化するのはなぜでしょうか? たとえば、不要なリダイレクションを除去する場合や、うまくリファクタリングされなかったメソッドをインライン化してもう一度抽出し直す場合などに、使用することができます。
定数の導入
製品コードでは一般的にはリテラル値よりも定数を使用する方が推奨されるため、定数を導入するためにコードをリファクタリングする方法を説明しましょう。
public static boolean beautifulBinaryStrings(String s) { while (!s.equals(s = s.replaceAll("AA|BB", ""))) { System.out.println(s); } return s.isEmpty(); }
メソッド beautifulBinaryStrings
は文字列リテラル値を使用します。 macOS では ⌥⌘C を、Windows/Linux では Ctrl+Alt+C
のショートカットを使用すると、この値を定数に抽出することができます。
IntelliJ IDEA は、リテラル値に基づいて定数名を提案します。 ここでは最初の名前を選択することにします。 必要であれば、この定数を別のクラスに移動することも可能です。
フィールドの導入
このリファクタリングを使用して、フィールドを導入または抽出し、初期化することができます。
ViewImpl
クラスのメソッド getItemsList
は、定数 0
と 4
を使用してリストのサブセットを取得します。
public class ViewImpl implements View { @Override public List<Integer> getItemsList() { return List.of(1, 2, 3, 4, 5).subList(0,4); } }
これらを定数として定義するのではなく、インスタンス間で異なる値を持てるように、macOS では ⌥⌘F を、Windows/Linux では
Ctrl+Alt+F を使用して、これらの値を格納するフィールドを導入することができます。
パラメータの抽出
Extract parameter(パラメータの抽出)リファクタリングでは、メソッド内の定数または式を選択し、メソッドにパラメーター(引数)として渡されるように定義することができます。
以下の例を見ていきましょう。最初に(前のセクションで説明したフィールドの導入を行い)toIndex
と呼ばれる 2 つ目のフィールドの導入による変更が getItemsList
メソッドに適用されていますのでご注意ください。
public class ViewImpl implements View { private final int fromIndex; private final int toIndex; public ViewImpl() { fromIndex = 0; toIndex = 4; } public List<Integer> getItemsList() { return List.of(1, 2, 3, 4, 5).subList(fromIndex, toIndex); } }
getItemsList
メソッドに注目します。ここで式 List.of(1,2,3,4,5)
を引数としてこのメソッドに渡してみましょう。 メソッドパラメータの型は、選択した式の型と同じになります。
また、 Delegate via method overloading(メソッドオーバーロードを委譲)チェックボックスをチェックすると、元のメソッドを保持したまま、2つ目の(パラメーター付きの)メソッドの追加することができます。 つまり、いずれのメソッドも、呼び出し側に応じて使用することができます。
Extract parameter(パラメータの抽出)は、式を本来あるべき場所に移動させることができるため、メソッドまたはメソッドの呼び出しをより読みやすくする上で役立ちます。 また、理解しやすいパラメータ名を使用することで、可読性を向上させることができます。
変数の導入
必要に応じて、変数を抽出することもできます。
式を適切な名前が付けられた変数に移動することで、コードの可読性を向上させることができる場合があります。
public ContextActionsWithAltEnter(double cityPopulation) { if (cityPopulation > 0x1.2016eb678a2p43 && cityPopulation < 987677.8) { if (cityPopulation % 5 == 0) { this.cityPopulation /= 2; } } this.cityPopulation = cityPopulation; }
この例ではContextActionsWithAltEnter
コンストラクタの if ステートメントは多少複雑に見えるます。この条件式を選択し、macOS では ⌥⌘V
を、Windows/Linux では Ctrl+Alt+V を使用します。 lowerLimit
などのわかりやすい名前を指定してみましょう。 また、2 つ目の条件も同様に変数に抽出し、upperLimit
を名付けることができます。 抽出された変数は必要であれば final
として定義することができます。Java 10 以降を使用している場合は var
として定義することもできます。
何のための処理を行っているのか、見通しのよいコードになりました。
安全な削除
使用しなくなったファイルやシンボルがプロジェクトにある場合は、安全に削除を使いましょう。
削除するファイルまたはクラスを選択したら、macOS では ⌘⌦ を、Windows/Linux では Alt+Delete
を使用します。 リソースを削除する前に、IntelliJ IDEA によってそれらを削除しても安全であることが確認されます。
まとめ
IntelliJ IDEA には強力なリファクタリング機能が備わっており、日々の作業でコードを安全に改善するすることができます。
ただし、リファクタリング魔法の弾丸ではありません。 リファクタリングによってコードの振る舞いに変化がないことを確認するためのセーフティーネットとしてのテストを行うようにしてください。
以下も合わせてご覧ください。
- IntelliJ IDEA
でサポートされているリファクタリング - リファクタリング: IntelliJ
IDEA でのインラインメソッド(動画を含む) - リファクタリング:
IntelliJ IDEA での変数の抽出および動画