前提: Java11, JUnit5, sbt
モンスターメソッドにうんざりしてるのでこんなものを書いてしまいました…。
プロジェクト初期にしれっと仕込んでしまいましょう。
このようなものを仕込んだ結果、
メソッド分割して1メソッド辺りの行数をとりあえず少なくしたけど、
その代わりに、色んなメソッドで参照できるように、変数のスコープを広げてグローバル変数化されかねない
という問題点もあるにはあるんですが…。
たぶん、それでも今回のこれを仕込んだほうがマシなのかなあ?とは思っています。
build.sbtのlibraryDependenciesには↓を貼り付け
"org.junit.jupiter" % "junit-jupiter-api" % "5.5.0",
"org.junit.jupiter"%"junit-jupiter-engine" % "5.5.0",
"org.javassist" % "javassist" % "3.25.0-GA",
コード
package com.github.momosetkn;
import javassist.ClassPool;
import org.junit.jupiter.api.Test;
import java.io.File;
import java.io.IOException;
import java.net.URISyntaxException;
import java.nio.file.*;
import java.nio.file.attribute.BasicFileAttributes;
import java.util.ArrayList;
import java.util.List;
class MonsterMethodAlert {
@Test
void test() throws Exception {
var cp = ClassPool.getDefault();
var fail = false;
for (var className : getClassNameList()) {
var cc = cp.get(className);
for (var method : cc.getMethods()) {
// java.lang.Object.equalsなど、javaパッケージ配下のメソッドは対象外
if (method.getDeclaringClass().getName().startsWith("java"))
continue;
var methodInfo = method.getMethodInfo();
var start = methodInfo.getLineNumber(Integer.MIN_VALUE);
var end = methodInfo.getLineNumber(Integer.MAX_VALUE);
var line = end - start + 1;
if (line >= 25) {
System.err.println(String.format("%sが%s行のモンスターメソッドとなっております", className + "#" + methodInfo.getName(), line));
fail = true;
}
}
}
if (fail)
throw new Exception("モンスターメソッドが検出されました");
}
private List<String> getClassNameList() throws IOException, URISyntaxException {
var list = new ArrayList<String>();
var classLoader = Thread.currentThread().getContextClassLoader();
var targetUrls = classLoader.getResources("");
var CLASS_EXT = ".class";
while (targetUrls.hasMoreElements()) {
var url = targetUrls.nextElement();
if (!url.getProtocol().equals("file")) {
continue;
}
var targetPath = Paths.get(url.toURI());
Files.walkFileTree(targetPath, new SimpleFileVisitor<>() {
@Override
public FileVisitResult visitFile(Path foundPath, BasicFileAttributes attrs) throws IOException {
if (foundPath.toString().endsWith(CLASS_EXT)){
var relativizeStr = targetPath.relativize(foundPath).toString();
list.add(
relativizeStr
.substring(0, relativizeStr.length() - CLASS_EXT.length())
.replace(File.separatorChar, '.')
);
}
return super.visitFile(foundPath, attrs);
}
});
}
return list;
}
}
Example#mainが36行のモンスターメソッドとなっております
java.lang.Exception: モンスターメソッドが検出されました
at com.github.momosetkn.MonsterMethodAlert.test(MonsterMethodAlert.java:37)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.base/java.lang.reflect.Method.invoke(Method.java:566)
at org.junit.platform.commons.util.ReflectionUtils.invokeMethod(ReflectionUtils.java:436)
at org.junit.jupiter.engine.execution.ExecutableInvoker.invoke(ExecutableInvoker.java:115)
at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.lambda$invokeTestMethod$6(TestMethodTestDescriptor.java:170)
at org.junit.jupiter.engine.execution.ThrowableCollector.execute(ThrowableCollector.java:40)
at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.invokeTestMethod(TestMethodTestDescriptor.java:166)
at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.execute(TestMethodTestDescriptor.java:113)
at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.execute(TestMethodTestDescriptor.java:58)
at org.junit.platform.engine.support.hierarchical.HierarchicalTestExecutor$NodeExecutor.lambda$executeRecursively$3(HierarchicalTestExecutor.java:112)
at org.junit.platform.engine.support.hierarchical.SingleTestExecutor.executeSafely(SingleTestExecutor.java:66)
at org.junit.platform.engine.support.hierarchical.HierarchicalTestExecutor$NodeExecutor.executeRecursively(HierarchicalTestExecutor.java:108)
at org.junit.platform.engine.support.hierarchical.HierarchicalTestExecutor$NodeExecutor.execute(HierarchicalTestExecutor.java:79)
at org.junit.platform.engine.support.hierarchical.HierarchicalTestExecutor$NodeExecutor.lambda$executeRecursively$2(HierarchicalTestExecutor.java:120)
at java.base/java.util.stream.ForEachOps$ForEachOp$OfRef.accept(ForEachOps.java:183)
at java.base/java.util.stream.ReferencePipeline$2$1.accept(ReferencePipeline.java:177)
at java.base/java.util.Iterator.forEachRemaining(Iterator.java:133)
at java.base/java.util.Spliterators$IteratorSpliterator.forEachRemaining(Spliterators.java:1801)
at java.base/java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:484)
at java.base/java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:474)
at java.base/java.util.stream.ForEachOps$ForEachOp.evaluateSequential(ForEachOps.java:150)
at java.base/java.util.stream.ForEachOps$ForEachOp$OfRef.evaluateSequential(ForEachOps.java:173)
at java.base/java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:234)
at java.base/java.util.stream.ReferencePipeline.forEach(ReferencePipeline.java:497)
at org.junit.platform.engine.support.hierarchical.HierarchicalTestExecutor$NodeExecutor.lambda$executeRecursively$3(HierarchicalTestExecutor.java:120)
at org.junit.platform.engine.support.hierarchical.SingleTestExecutor.executeSafely(SingleTestExecutor.java:66)
at org.junit.platform.engine.support.hierarchical.HierarchicalTestExecutor$NodeExecutor.executeRecursively(HierarchicalTestExecutor.java:108)
at org.junit.platform.engine.support.hierarchical.HierarchicalTestExecutor$NodeExecutor.execute(HierarchicalTestExecutor.java:79)
at org.junit.platform.engine.support.hierarchical.HierarchicalTestExecutor$NodeExecutor.lambda$executeRecursively$2(HierarchicalTestExecutor.java:120)
at java.base/java.util.stream.ForEachOps$ForEachOp$OfRef.accept(ForEachOps.java:183)
at java.base/java.util.stream.ReferencePipeline$2$1.accept(ReferencePipeline.java:177)
at java.base/java.util.Iterator.forEachRemaining(Iterator.java:133)
at java.base/java.util.Spliterators$IteratorSpliterator.forEachRemaining(Spliterators.java:1801)
at java.base/java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:484)
at java.base/java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:474)
at java.base/java.util.stream.ForEachOps$ForEachOp.evaluateSequential(ForEachOps.java:150)
at java.base/java.util.stream.ForEachOps$ForEachOp$OfRef.evaluateSequential(ForEachOps.java:173)
at java.base/java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:234)
at java.base/java.util.stream.ReferencePipeline.forEach(ReferencePipeline.java:497)
at org.junit.platform.engine.support.hierarchical.HierarchicalTestExecutor$NodeExecutor.lambda$executeRecursively$3(HierarchicalTestExecutor.java:120)
at org.junit.platform.engine.support.hierarchical.SingleTestExecutor.executeSafely(SingleTestExecutor.java:66)
at org.junit.platform.engine.support.hierarchical.HierarchicalTestExecutor$NodeExecutor.executeRecursively(HierarchicalTestExecutor.java:108)
at org.junit.platform.engine.support.hierarchical.HierarchicalTestExecutor$NodeExecutor.execute(HierarchicalTestExecutor.java:79)
at org.junit.platform.engine.support.hierarchical.HierarchicalTestExecutor.execute(HierarchicalTestExecutor.java:55)
at org.junit.platform.engine.support.hierarchical.HierarchicalTestEngine.execute(HierarchicalTestEngine.java:43)
at org.junit.platform.launcher.core.DefaultLauncher.execute(DefaultLauncher.java:170)
at org.junit.platform.launcher.core.DefaultLauncher.execute(DefaultLauncher.java:154)
at org.junit.platform.launcher.core.DefaultLauncher.execute(DefaultLauncher.java:90)
at com.intellij.junit5.JUnit5IdeaTestRunner.startRunnerWithArgs(JUnit5IdeaTestRunner.java:69)
at com.intellij.rt.execution.junit.IdeaTestRunner$Repeater.startRunnerWithArgs(IdeaTestRunner.java:47)
at com.intellij.rt.execution.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:242)
at com.intellij.rt.execution.junit.JUnitStarter.main(JUnitStarter.java:70)
解説
メソッド行数取得
javassist.bytecode.MethodInfo#getLineNumber
の実装を見ると、
引数に渡した数値を以下メソッドに渡しているようです。
https://github.com/jboss-javassist/javassist/blob/rel_3_25_0_ga/src/main/javassist/bytecode/LineNumberAttribute.java#L77
ループで回していって超えたかどうかでしか判定していないようなので、
Integer.MIN_VALUE
とInteger.MAX_VALUE
を渡しています。
startとendは、
100: public void method(){
101: //startはここの行数
102: //
103: //endはここの行数
104: }
が取れますので、103-102=2となるため、1足して3行のメソッドという扱いにしています。
Javassistについて
古いバージョンのJavassistだと、新し目のJavaのバージョンに追従していないため、できるだけ新しくする。
参考資料
パッケージ配下のクラス一覧を再帰的に探索したい - Qiita
Javassistメモ(Hishidama's Javassist Memo)