3

我有一个如下所示的界面

interface Evaluator {
    boolean requiresP2();
    EvalResult evaluate(Param1 p1, Param2 p2, Param3 p3);
    // some more methods
}

这个接口由几个类实现。评估方法的参数p2被某些人使用,而未被其他人使用。该方法requiresP2基本上返回一个布尔值,告诉评估方法是否使用p2

现在,脱离上下文,这个问题可能看起来有点奇怪,但相信我,这在我们的用例中是有意义的。另外,重构所有代码以消除对该requiresP2方法的需求将需要大量时间,因此如果我们讨论除代码库自上而下重构之外的解决方案,我将不胜感激。

问题是方法的返回值requiresP2取决于evaluate方法的实现方式。因此每个人都必须确保requiresP2在更改方法时更新evaluate方法。

我正在寻找方法,以便编译器/单元测试/linter 可以强制执行此操作,而不是将其留给开发人员的记忆。

编辑:我仍在探索模拟框架对这个问题的适用性。

我认为我可以在单元测试中反思以检查单元测试中evaluate的主体以检查它是否引用p2,然后确保它与requiresP2方法返回的值匹配,但似乎无法检查方法主体使用反射。

我正在寻找有关如何执行此操作的建议。任何输入表示赞赏。

4

2 回答 2

5

还有一个您没有提到的选项:静态代码分析工具。

您可以使用SonarQube + SonarLint组合来获得所需的强制执行:

使用 SonarQube 服务器创建新的静态代码分析规则,该规则将基于您使用的界面和您的独特用例。

然后在您的 IDE/IDE 上安装 SonarLint(Eclipse 和 IntelliJ 都受支持),并将其连接到 SonarQube 服务器。

这样,静态代码分析扫描将检测到您的界面的不当使用,并在 IDE 中的相关代码行(实际上是对您的代码进行 linting)上用视觉标记来指示这一点。

于 2018-05-17T23:28:16.760 回答
4

您可以使用ASM来检查该参数是否被使用。

要使用例如 Apache Ivy 将其添加到您的项目中,您可以将其添加到ivy.xml

<dependency org="org.ow2.asm" name="asm" rev="6.1.1" />

或者对 Maven、Gradle 等执行等效操作。然后您可以通过以下方式检查参数:

import java.io.IOException;
import java.io.InputStream;
import java.io.UncheckedIOException;
import java.util.concurrent.atomic.AtomicBoolean;

import org.objectweb.asm.ClassReader;
import org.objectweb.asm.ClassVisitor;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;

// . . .

public static boolean usesP2(Evaluator evaluator) {
    AtomicBoolean usesP2 = new AtomicBoolean(false);
    String internalName = evaluator.getClass().getName().replace('.', '/');
    String classFileResource = "/" + internalName + ".class";

    ClassVisitor visitor = new ClassVisitor(Opcodes.ASM6) {
        @Override
        public MethodVisitor visitMethod(int access, String name,
                String desc, String signature, String[] exceptions) {
            if ("evaluate".equals(name)) {
                return new MethodVisitor(Opcodes.ASM6) {
                    @Override
                    public void visitVarInsn(final int insn, final int slot) {
                        if (slot == 2) usesP2.set(true);
                    }
                };
            }
            return super.visitMethod(access, name, desc, signature, exceptions);
        }
    };
    try (InputStream is = Evaluator.class.getResourceAsStream(classFileResource)) {
        ClassReader reader = new ClassReader(is);
        reader.accept(visitor, 0);
    } catch (IOException e) {
        throw new UncheckedIOException(e);
    }
    return usesP2.get();
}

public static void assertCorrectlyDocumentsP2(Evaluator evaluator) {
    boolean usesP2 = usesP2(evaluator);
    if (usesP2 && !evaluator.requiresP2()) {
        throw new AssertionError(evaluator.getClass().getName() +
                " uses P2 without documenting it");
    }
    if (!usesP2 && evaluator.requiresP2()) {
        throw new AssertionError(evaluator.getClass().getName() +
                " says it uses P2 but does not");
    }
}

单元测试:

@Test
public void testFalsePositive() {
    assertCorrectlyDocumentsP2(new FalsePositive());
}

@Test
public static void testFalseNegative() {
    assertCorrectlyDocumentsP2(new FalseNegative());
}

(这假设有两个 bad EvaluatorsFalsePositiveFalseNegative,其中一个记录了它使用 P2 但没有,另一个没有记录它使用 P2 即使它使用了,分别。)

注意:usesP2我们在堆栈帧的插槽 2 中检查变量指令(访问局部变量的指令)。插槽从 0 开始编号,第一个是this. P2 位于插槽 2 中只是因为Evaluator::evaluate它是一个实例方法。如果它是静态方法,我们必须检查是否使用了slot 1以检测是否使用了参数 P2。警告讲师

于 2018-05-18T01:48:27.067 回答