Проверьте, используется ли параметр метода в теле метода
У меня есть интерфейс, который выглядит следующим образом
interface Evaluator {
boolean requiresP2();
EvalResult evaluate(Param1 p1, Param2 p2, Param3 p3);
// some more methods
}
Этот интерфейс реализован несколькими классами. Параметр p2
метода оценки используется одними и не используется другими. Метод requiresP2
в основном возвращает логическое значение, указывающее, использует ли метод оценки p2
или нет.
Теперь эти вопросы могут показаться немного странными вне контекста, но, поверьте мне, это имеет смысл в нашем случае использования. Кроме того, потребуется много времени на рефакторинг всего кода, чтобы устранить необходимость requiresP2
метод, поэтому я был бы признателен, если бы мы обсуждали решения, отличные от рефакторинга кода сверху вниз.
Проблема в том, что возвращаемое значение метода requiresP2
основан на том, как evaluate
Метод реализован. Поэтому каждый должен убедиться, что они обновляют requiresP2
метод, когда они меняют evaluate
метод.
Я ищу способы, чтобы это могло быть осуществлено компилятором / юнит-тестами / линтерами, а не оставляло это в памяти разработчика.
РЕДАКТИРОВАТЬ: Я все еще изучаю применимость насмешливых рамок для этой проблемы.
Я думал, что смогу отразить в юнит-тестах evaluate
тело в модульном тесте, чтобы проверить, если это относится к p2
или нет, а затем убедитесь, что он совпадает со значением, возвращаемым requiresP2
метод, но кажется, что невозможно проверить тело метода с помощью отражения.
Я ищу предложения о том, как это сделать. Любой вклад приветствуется.
2 ответа
Вы можете использовать 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());
}
(Предполагается, что есть два плохих Evaluator
s, FalsePositive
а также FalseNegative
один из которых документирует, что он использует P2, но не делает, а другой, который не документирует, что он использует P2, хотя и делает, соответственно.)
Примечание: в usesP2
мы проверяем переменную инструкцию (инструкцию, которая обращается к локальной переменной) в слоте 2 фрейма стека. Слоты пронумерованы от 0, а первый this
, P2 находится в слотах 2 только потому, что Evaluator::evaluate
это метод экземпляра. Если бы это был статический метод, мы должны были бы проверить, использовался ли слот 1, чтобы определить, использовался ли параметр P2. Будьте лектором.
Есть еще один вариант, который вы не упомянули: инструмент статического анализа кода.
Вы можете использовать комбинацию SonarQube + SonarLint, чтобы получить желаемое исполнение:
Используйте сервер SonarQube для создания нового правила статического анализа кода, которое будет основано на используемом интерфейсе и вашем уникальном сценарии использования.
Затем установите SonarLint на свои IDE/IDE (Eclipse и IntelliJ оба поддерживаются) и подключите его к серверу SonarQube.
Таким образом, сканирование с помощью статического анализа кода обнаружит неправильное использование вашего интерфейса и укажет на это визуальной маркировкой в IDE на соответствующих строках кода (которые фактически обозначают ваш код).