Проверьте, используется ли параметр метода в теле метода - PullRequest
0 голосов
/ 18 мая 2018

У меня есть интерфейс, который выглядит следующим образом

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 ]

0 голосов
/ 18 мая 2018

Вы можете использовать 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 является методом экземпляра . Если бы это был статический метод , нам пришлось бы проверить, использовался ли slot 1 , чтобы определить, использовался ли параметр P2. Предостережение лектора .

0 голосов
/ 18 мая 2018

Есть еще один вариант, который вы не упомянули: инструмент статического анализа кода.

Вы можете использовать комбинацию SonarQube + SonarLint , чтобы получить желаемоепринудительное применение:

Используйте сервер SonarQube для создания нового правила статического анализа кода, которое будет основано на используемом интерфейсе и вашем уникальном сценарии использования.

Затем установите SonarLint на свойIDE / IDE (Eclipse и IntelliJ оба поддерживаются) и подключите его к серверу SonarQube.

Таким образом, при сканировании с использованием статического анализа кода будет обнаружено неправильное использование вашего интерфейса, и это будет отображаться с помощью визуальной маркировки в IDE.на соответствующих строках кода (которые фактически обозначают ваш код).

...