背景
多数のコードを扱っているときに、不具合修正やリファクタリングのためにコード上の特定の構造を検索したいときがありました。具体的には、Javaで以下のようなコードがありました。
public List<Class<? extends Foo>> getArgumentType() {
return new ArrayList<Class<? extends Foo>>() {
{
add(Baa.class);
add(Baz.class);
}
};
}
以上のコードは、匿名の内部クラスを定義していますが、この匿名のクラスが親クラスの参照を持っているため、メモリリークを引き起こす可能性があります。そのため、以下のように修正したいと考えました。
public List<Class<? extends Foo>> getArgumentType() {
List<Class<? extends Foo>> argumentTypes = new ArrayList<>();
argumentTypes.add(Baa.class);
argumentTypes.add(Baz.class);
return argumentTypes;
}
しかし同様の理由でBugとして検出されたものはコード上で94箇所あり、
- リストから手動で検索して修正するのはヒューマンエラーが起きやすく効率的でない
- 類似箇所を探すにも正規表現を用いても改行やコメントが挟まっている場合を考慮すると全て検出するのが難しい
という状況でした。
以上の背景から、 「コード上の特定の構造を検索してあわよくば置換してくれる機能ないかな〜」 と思い、題名の機能に辿り着き、実際に使って問題を解決できたので以下で記事にしようと思います。
本記事に登場するIntelliJの構造検索・置換機能は、現在Java、Kotlin、Groovy の構造検索と置換をサポートしているそうです。
アドベントカレンダーのネタにしては小ネタですが許してください、、!
目次
IntelliJの構造検索・機能
構造検索・置換とは、公式ドキュメントには、
構造検索および置換(SSR)アクションを使用すると、コード構造を考慮して、コード内の特定のコードパターンまたは文法構造を検索できます。
とあります。検索テンプレートを用いて、特定の構造を検索できるということです。
ドキュメントの例では、正規表現と大文字/小文字の区別などを用いて特定の表現を検出する方法が紹介されています。
実際こういった検出をしたい場面があるのか、私のエンジニア歴が短いからなのかわかりませんでした。自分で試した例はもっとシンプルです。
実際に使ってみた
冒頭に紹介したコードを検出してみます。今回検出したい箇所の特徴を、テンプレートに落とし込むと以下のようになります。
@Override
public List<Class<? extends $Type$>> $methodName$() {
return new ArrayList<Class<? extends $Type$>>() {
{
add($Type1$.class);
add($Type2$.class);
}
};
}
以下の例では特定のメソッド内で匿名の内部クラスを定義してreturn
しています。それに対して、置換後のテンプレートは以下のようになります。
public List<Class<? extends Foo>> $methodName$() {
List<Class<? extends $Type$>> argumentTypes = new ArrayList<>();
argumentTypes.add($Type1$.class);
argumentTypes.add($Type2$.class);
return argumentTypes;
}
この検索テンプレート、置換テンプレートを用いて、検索をすると以下のようになります。
おお、検出された、、!
さらに、検出されたものの中から任意の一つを選び、画面下部の「置換のプレビュー」を押すと...??
なんと!置換テンプレートをもとにして、構造を理解して置換してくれるではありませんか!このプレビューウインドウの置換を押しても置換されますし、「置換のプレビュー」の左の「すべてを置換」を押せば、検出されたすべてを一瞬で置換してくれます。
しかもしかも、構造を理解してくれるということは、
@Override
public List<Class<? extends Foo>> getArgumentTypes() {
return new ArrayList<Class<? extends Foo>() {{
add(Baa.class);
add(Baz.class);
}};
}
このように改行位置が違っても、
@Override
public List<Class<? extends Foo>> getArgumentTypes() {
return new ArrayList<Class<? Foo GalapagosType>() {
{
/**
*
* comment
*
*/
add(Baa.class);
add(Baz.class);
}
};
}
こんな感じでコメントがあってもちゃんと検出してくれるんです!
今回のケースはただ数が多い&短期間で治さなければいけないという条件付きでしたが、例えばコードに潜む潜在的なリスクを検出して、一括で治したりなんてこともできるかもしれません。それは、まだエンジニア歴が浅い私よりも、経験のある方が活用方法が思いつくかもしれませんね!
まとめ
今回はIntelliJの構造置換について学んだ内容を共有しました。調べてもあまり記事が出てこなかったので、生成AIと公式ドキュメントを見ながら試行錯誤してみました。今後も既存の方法に縛られず柔軟に課題解決の方法を見つけていきたいなと思います!