354.PMD(4)
初回:2024/03/06
前回は、PMDのカスタマイズを行いました。本来であれば、前回までのカスタマイズで十分です。ここから先は少し趣味の範囲に近いカスタマイズを行ってみたいと思います。
P子「趣味の範囲なの?」※1
前回にも書きましたが、PMDチェックは『かるーく』で良いと思っています。前回のカスタマイズでもやり過ぎ感がありましたが、今回のカスタマイズは、やらない方が良い、やるべきではないカスタマイズだと思います。
P子「じゃあ、取り上げなくてもいいんじゃない?」
だから、趣味の範囲なんです。
1.OnlyOneReturn
PMDのルールに、OnlyOneReturn というのがあります。
《参考資料1》
https://pmd.github.io/pmd/pmd_rules_java_codestyle.html#onlyonereturn
OnlyOneReturn
A method should have only one exit point, and that should be the last statement in the method.
メソッドには終了ポイントが 1 つだけ必要であり、それがメソッド内の最後のステートメントである必要があります。
(google 翻訳)
困ったことに私の組むJavaのメソッドでは、最初にメソッド引数やインスタンス変数の正当性チェックを行っています。そして正当性が得られない場合は、そこでreturn でメソッドを終了します。
P子「正当性が確保できないなら、 例外をスローすれば良いんじゃない」
そうなんですが、場合によっては例外ではなく初期値を返したいときとかないですか?例えば、ある引数に応じて結果を返す場合、引数が null の場合は、初期値を返したいとします。その場合、OnlyOneReturn を守るのと守らないのではソースの判りやすさが異なると思います。
OnlyOneReturn を守る public String getExecution( final String key, final String initValue ) { final String returnValue; if( key == null || key.isEmpty() { returnValue = initValue; } else { ・・・・・・ 計算結果を求める処理 returnValue = 計算結果; } return returnValue; }
OnlyOneReturn を守らない public String getExecution( final String key, final String initValue ) { if( key == null || key.isEmpty() { return initValue; } ・・・・・・ 計算結果を求める処理 return 計算結果; }
P子「少し悪意を感じるサンプルソースね」
実際、不要なreturn変数を定義する必要があるのと、if文で階層が深くなる(また、少し複雑になる)のでメインの処理が始まる前に各種条件判定を行っておく方が、プログラム的にすっきりしたり判りやすくなると私は思っています。
もちろんルールから OnlyOneReturn を除外するのが簡単なのですが、ここでは初期設定の return だけ許可するようにカスタマイズしてみましょう。
2.OnlyOneReturn のソース修正
まずは、このソースを見てみましょう。
《参考資料2》
https://github.com/pmd/pmd/blob/master/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/codestyle/OnlyOneReturnRule.java
OnlyOneReturnRule.java
package net.sourceforge.pmd.lang.java.rule.codestyle;
import net.sourceforge.pmd.lang.ast.NodeStream;
import net.sourceforge.pmd.lang.java.ast.ASTMethodDeclaration;
import net.sourceforge.pmd.lang.java.ast.ASTReturnStatement;
import net.sourceforge.pmd.lang.java.rule.AbstractJavaRulechainRule;
public class OnlyOneReturnRule extends AbstractJavaRulechainRule {
public OnlyOneReturnRule() {
super(ASTMethodDeclaration.class);
}
@Override
public Object visit(ASTMethodDeclaration node, Object data) {
if (node.getBody() == null) {
return null;
}
NodeStream<ASTReturnStatement> returnsExceptLast =
node.getBody().descendants(ASTReturnStatement.class).dropLast(1);
for (ASTReturnStatement returnStmt : returnsExceptLast) {
asCtx(data).addViolation(returnStmt);
}
return null;
}
}
P子「OnlyOneReturn のソースが、ルール違反してる・・・」
まあ、その程度のルールという事でしょう。
でも、処理前に条件判定して、確定分を return で返しておくというのは良い考えだという事です。
さて、このソースを修正してみます。
簡単に、先頭の3行以内にある return 文は条件から除外するようにします。先に上げた、OnlyOneReturnRule.java の visit メソッドも、最初の3行以内に 初期判断の return があるので、上記のカスタマイズルールを適用すれば、問題なしになります。
visit メソッドのみ書き換えてみましょう。
public class OnlyOneReturnRule extends AbstractJavaRulechainRule { // メソッドの先頭 3行以内の return 文は対象外にします。 final static int LIMIT = 3; // 追加 ・・・・ @Override public Object visit(ASTMethodDeclaration node, Object data) { if (node.getBody() == null) { return null; }
final int methodLine = node.getReportLocation().getStartLine(); // 1.メソッドの先頭行番号
NodeStream<ASTReturnStatement> returnsExceptLast =
node.getBody().descendants(ASTReturnStatement.class).dropLast(1);
for (ASTReturnStatement returnStmt : returnsExceptLast) {
final int returnLine = returnStmt.getReportLocation().getStartLine(); // 2.return文の行番号
final int maxCnt = returnLine - methodLine; // 3.メソッド先頭からのreturn文の間の行数
if( maxCnt > LIMIT ) // 4.単純比較で、リミットオーバーの場合
// if( limitLine(node,maxCnt) ) { // 5.空行とコメント除外でリミットオーバーの場合
asCtx(data).addViolation(returnStmt);
}
}
return null;
}
}
まず『1.メソッドの先頭行番号』を求めます。そして『2.return文の行番号』を求めて『3.メソッド先頭からのreturn文の間の行数』を計算して『4.単純比較で、リミットオーバーの場合』のみルール違反 に登録します。
これは本当に単純な処理で、メソッドの行番号とreturnの行番号の比較なので、間に改行やコメント行があっても3行あるかな以下でしか判定していません。
そこで『5.空行とコメント除外でリミットオーバーの場合』として、改行とコメントを無視するように変更します。ソースの解説は行いませんので、興味があれば各自で調べてください。
P子「少し冷たくない?」
PMDって、メジャーバージョンアップする都度、結構大きなAPIの変更がありますので、Javaのソースコードのカスタマイズはお勧めしません。なので、趣味でカスタマイズされるのが良いと思います。
private boolean limitLine( final ASTMethodDeclaration methodNode, final int maxCnt ) { boolean isOver = false; if( maxCnt > LIMIT ) { // 単純比較で、リミットオーバーの場合 int cnt = 0; int no = 0; for( final Chars line : methodNode.getText().lines() ) { if( line.startsWith("@") ) { continue; } // メソッドの先頭の アノテーション は行数にカウントしない。 no++; // 行数カウント if( no > maxCnt ) { break; } // returnのある行以上は、計算する必要がない。
final String text = line.trim().toString();
if( text.isEmpty() || text.startsWith("//") ) { continue; } // 空行とコメント行は、無視する。
cnt++; // 実質の行数
if( cnt > LIMIT ) { isOver = true; break; } // リミットオーバー
}
}
return isOver;
}
3.コンパイルと実行
さて、ソースのカスタマイズができましたので、コンパイルしてみましょう。
私が良く使うのは、pmd-bin-7.0.0-rc4 のフォルダの下に、src フォルダを作成して、そこに、OnlyOneReturnRule.java のソースを置きます。そして、jccall.bat というバッチファイルを作成します。バッチの中身は、以下のようにします。
@echo off
‥‥ java へのパスの設定
set CLASSPATH=.;classes;%CLASSPATH%;*;C:/pmd-bin-7.0.0-rc4/lib/*
javac -encoding UTF-8 -d classes -Xlint:all -classpath %CLASSPATH% *.java
pause
簡単に説明すると、src フォルダ内でコンパイルする場合、-d classes で classes フォルダが作成されて、パッケージのフォルダ階層にclassファイルが作成されます。その際、CLASSPATH に、classes と pmd-bin-7.0.0-rc4/lib/* を入れておくことでコンパイルできます。
そして、ルールを実行する方のバッチにも、先の src/classes のCLASSPATH を設定します。
set CLASSPATH=.;src/classes;%CLASSPATH%
カスタマイズしたソースの classes フォルダへのパスを先に通すことで、オリジナルのクラスロードの前にカスタマイズクラスが適用されます。この辺りは、バッチファイルで Java をコンパイルしたり実行している人なら理解できると思います。
4.まとめ
今回のカスタマイズはお勧めしませんが、オープンソースの良い所は、こうやって必要であればカスタマイズができるところでしょう。私の場合は、このカスタマイズを本番適用していますので、OnlyOneReturn はチェック対象にしています。
とはいうものの、一般的なソースチェックなら、このチェックは不要でしょう。
他にも色々と不要なルールが存在するようなので、『かるーく』使うのが良いと思います。
ほな、さいなら
======= <<注釈>>=======
※1 P子「趣味の範囲なの?」
P子とは、私があこがれているツンデレPythonの仮想女性の心の声です。