Pull to refresh

Comments 16

Мне известны несколько способов со своими плюсами и минусами:

Есть ещё неплохой способ "Использовать IDE".

А чем этот вариант лучше checkstyle?
Да, тут не нужно "локально запускать руками checkstyle", но зато идёт внедрение в процесс сборки что не может его не замедлять — тогда как запуск checkstyle можно (да и "нужно") привязать к одному из "высоко-уровневых" этапов сборки проекта (например package у maven и build у gradle) и тогда его уже не нужно будет "запускать" руками и к тому же его можно будет игнорировать для "ускорения" этапов более важных при непосредственной разработке (компиляция).

Наверное, если у нас серьезный проект, то у нас будет CI, с checkstyle, или sonarqube. Моя подделка больше для мелких проектов, в которых тоже хочется иметь гарантии компилятора, но нет никакой инфраструктуры.

Подключить checkstyle и на маленьком проекте безо всяких CI совершенное не сложно, а профтов это даст всё равно больше чем подключение этой либы проверяющей только данный кейс.

Приветствую!
Спасибо за статью.
Интересная проблема и решение через обработку аннотаций)
В свое время тоже столкнулся с подобной проблемой поведения AnnotationProcessor и реализовал через такой же хак, чтобы достучатся до переменных внутри методов в одном пет проекте - https://ololx.github.io/cranberry/cranberry-statement/.

Позвольте мне немного дополнить статью и задать вопрос.

В Java 8+ можно зарегистрировать кастомный TaskListener через свою реализацию com.sun.source.util.Plugin; В вашем случае это выглядело бы следующим образом:
1 - Написать свою реализацию Plugin вместо SwitchExhaustiveCheckerProcessor

public class SwitchExhaustiveChecker implements Plugin {

    @Override
    public String getName() {
        return "SwitchExhaustiveCheckerPlugin";
    }

    @Override
    public void init(JavacTask task, String... args) {
	task.addTaskListener(new AnalyzeTaskListener());
    }
}

2 - Зарегистрировать свой плагин в …/src/main/resources/META-INF/services/com.sun.source.util.Plugin ( аналогично тому, как Вы регистрировали свой процессор)

ru.hixon.switchexhaustivenesschecker.SwitchExhaustiveCheckerPlugin

3 - Для реализации такого плагина потребуется еще указать зависимость на JDK tools в Вашем pom.xml

<dependency>
    <groupId>com.sun</groupId>
    <artifactId>tools</artifactId>
    <version>1.8.0</version>
    <scope>system</scope>
    <systemPath>${java.home}/../lib/tools.jar</systemPath>
</dependency>

После чего можно использовать Ваш плагин, подключив его к проекту и указав аргумент в maven-compiler-plugin

<plugin>
          <artifactId>maven-compiler-plugin</artifactId>
          <version>${version}</version>
          <configuration>
            <compilerArgs>
              <arg>-Xplugin:SwitchExhaustiveCheckerPlugin</arg>
            </compilerArgs>
          </configuration>
        </plugin>

Это пример подобного плагина, который я смог найти на github (надеюсь, что авто рне будет против за то, что выложил лине) - https://github.com/moditect/deptective

Вопрос: Рассматривали ли Вы описанный мной способ реализации Вашей задачи? Если - да, то почему остановились на своем варианте - чем он показался Вам выигрышней?

Привет!
Спасьбо за комментарий.

> Вопрос: Рассматривали ли Вы описанный мной способ реализации Вашей задачи? Если — да, то почему остановились на своем варианте — чем он показался Вам выигрышней?

Честно говоря, не рассматривал, так как не знал / не помнил о такой возможности, в то время как кастомные процессоры аннотаций когда-то делал.

А как в случае с com.sun.source.util.Plugin унзнавать, какие элементы нужно обрабатывать, а какие — нет? Как я понимаю, зарегистрировать свой TaskListener мы легко можем, а вот часть про обработку кастомной аннотации, или чего-то другого, что даст мне список нужных классов для анализа, я пока не вижу.

Благодарю за хороший ответ. Точно также, как это реализовано у Вас сейчас, с небольшими изменениями. Вы создаете новый TaskListener в методе init() Вашего процессора; я лишь хотел показать, что инициализировать TaskListener можно иначе, тогда AbstractProcessor не нужен. К сожалению сейчас не обладаю достаточным временем, чтобы написать что-то конкретное по Вашему примеру. Но, в общих словах:
Сейчас логика поиска нужных элементов по аннотирпованным классам и реализация проверки у Вас реуализована в SwitchExhaustiveCheckerProcessor и TestMethodTreePathScanner
Можете просто объединить эту логику в одном Вашем TestMethodTreePathScanner - это конечно очень некрасиво и лучше бы потом отрефакторить и разбить ответственности, но зато позволит быстрее опробовать другой подход. 
Далее можно в самом AnalyzeTaskListener имплементить визитера TestMethodTreePathScanner к каждому элементу и все должно быть ОК, хотя чую, что с первого раза все не заведется, учитывая что пока предлдожение абстрактное.

class AnalyzeTaskListener implements TaskListener {
    
private final SwitchExhaustiveCheckerProcessor processor;

    AnalyzeTaskListener() {}

    @Override
    public void started(TaskEvent taskEvent) {

    }

    @Override
    public void finished(final TaskEvent e) {
        if (e.getKind() != TaskEvent.Kind.ANALYZE) {
            return;
        }

	CompilationUnitTree compilationUnit = event.getCompilationUnit();
        if (compilationUnit == null) {
	    return;
        }

        compilationUnit.accept(scanningVisitor, null);
    }
}

А в самом плагине уже проинициализировать AnalyzeTaskListener как писали ранее. Если нужны какие-нибудь утилитные класссы, наподобии тех , что используются у Вас в процессоре или визитере, то можно в классе плагина их проинициализировать и передать через конструктор AnalyzeTaskListener далее.
Например ``

…
@Override
    public void init(JavacTask task, String... args) {
        final Context context = ((BasicJavacTask)task).getContext();
        Log log = Log.instance(context);
    }
...

Тут есть хорошие примеры кода для такого подхода - https://annimon.com/article/2626

Заметил кучу ошибок в примерах кода - не надо было спешить вчера.
Внесу небольшие правки, чтобы в будущем никог оне путать.

class AnalyzeTaskListener implements TaskListener {
    
    private final TestMethodTreePathScanner testMethodTreePathScanner;

    AnalyzeTaskListener() {}

    @Override
    public void started(TaskEvent taskEvent) {

    }

    @Override
    public void finished(final TaskEvent e) {
        if (e.getKind() != TaskEvent.Kind.ANALYZE) {
            return;
        }

	CompilationUnitTree compilationUnit = e.getCompilationUnit();
        if (compilationUnit == null) {
	    return;
        }

        compilationUnit.accept(testMethodTreePathScanner, null);
    }
}

Спасибо за статью! Как на русский правильно перевести "switch exhaustiveness"?

Спасибо за комментарий.

Извините, но не знаю, какой верный перевод на русский язык.
Хм, мне изначально казалось, что именно этот код вызывается, но после вашего вопроса я стал сомневаться, так как пакет Apache BCEL.

Я попробовал поискать по исходникам, но сходу не нашёл никакого другого места с такой же логикой. Так что, пока не могу сказать точно, ошибя я изначально или нет.

Если честно, меня насторожило даде не наименование пакета, а вот эта фраза

компилятор может генерировать фейковые ветки в байткоде, для перфоманс целей

Не очень понял, как создание избыточного кода улучшает производительность.

Sign up to leave a comment.

Articles