Чем плохи аннотации Java

Я впервые опубликовал эту заметку на Medium в декабре 2019 года. За прошедшие три года я много работал с Java, но мое отношение к аннотациям осталось прежним: считаю, что они часто усложняют код. Medium же мне больше не подходит, поэтому решил перенести статью сюда.

Аннотации появились в Java в 2004 году и применяются на всех этапах разработки:

Сама по себе аннотация ничего не делает – это просто метка. Чтобы она заработала, нужен специальный обработчик. Из-за этого бывает сложно понять, как работают аннотации: нужно знать, какие библиотеки подключены к проекту и как настроены зависимости в Maven или Gradle для аннотационного процессора.

Усложняет ситуацию и обилие схожих аннотаций, которые обрабатываются по-разному. Классический пример – аннотация @NotNull:

Этот хаос затрудняет работу не только начинающих, но и опытных разработчиков. Иногда приходится использовать несколько видов @NotNull в одном приложении. Отладка ситуаций, когда IDE импортирует не ту аннотацию, становится болезненной, долгой и дорогостоящей.

Особенно сложно отследить, правильно ли обрабатывается аннотация во время выполнения. Например, для @Bean из Spring требуются тесты, учитывающие контекст исполнения – такие, которые запускают Spring Context. В противном случае придется, по старинке, полностью запускать приложение и проверять работоспособность вручную.

Аннотации времени исполнения особенно болезненны для сторонников явной строгой типизации, коих большинство в мире Java. Компилятор не сможет выявить проблемы, поскольку они не связаны с системой типов. Аннотации – инструмент, пришедший из мира неявной слабой типизации или её полного отсутствия. Это противоречит основным принципам типизации Java.

Если взглянуть на ситуацию в более широком масштабе, становится очевидно, что механизм аннотаций в Java – это уникальный пример неудачного языкового дизайна, попытка совместить несовместимые вещи в одном инструменте.

Другие языки программирования четко разделяются на те, что имеют аналоги аннотаций для времени компиляции: C и Rust с их macro функциональностью, C++ с template и constexpr. Или аналоги аннотаций времени исполнения: декораторы Python, теги Go.

Совмещение всего в одном инструменте вынуждает разработчика изучать практически другой язык программирования в рамках Java и одного из многочисленных громоздких фреймворков. Если серьезно, аннотации вообще-то являются тьюринг-полными: достаточно взглянуть на repeating annotations и AliasFor!

К счастью, сегодня у нас есть альтернативы. Простой пример – лямбды. Сравните примеры минимального контроллера на Spring MVC:

package example;

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class HelloController {

    @GetMapping("/")
    public String index() {
        return "Hello World";
    }

}

И Javalin:

package example;

import io.javalin.Javalin;

public class HelloWorld {

    public static void main(String[] args) {
        Javalin app = Javalin.create().start();
        app.get("/", ctx -> ctx.result("Hello World"));
    }

}

В Spring мы видим, как контроллер и путь для GET-запроса описываются декларативно. Проблема в том, что мы не видим, как именно происходит связывание – это скрытая информация. Нам будет сложно изменить поведение этих аннотаций без глубокого изучения того, как они работают. Нужно погружение в документацию.

В Javalin, напротив, все прозрачно и предельно ясно, как создается привязка маршрута для GET-запроса. У нас есть полный контроль над тем, как эта привязка будет работать, и мы можем управлять ей с помощью своего кода. К тому же, можно просто перейти к описанию app.get в IDE и изучить код библиотеки Javalin.

Радует, что некоторые проекты в мире Kotlin также идут по альтернативному пути, используя DSL. Koin представляет собой альтернативу Dagger и Spring, обходясь без аннотаций. В результате, он также прост в понимании и работе, как и подход Javalin.

В заключение: если внимательно разобраться в каждом случае, становится ясно, что аннотации далеко не всегда являются единственным способом достижения цели. Существуют более перспективные альтернативы, которые упростят понимание приложения как для автора кода, так и для его читателя.

И, конечно, есть области, где аннотации вполне оправданы. Это схожий с подходом Go способ тегирования для (де)сериализации данных. Важно отметить, что это точечное применение, которое не создает серьезных препятствий в работе.


В конечном счете, выбор инструментов Java всегда остается за Вами. Но, честно говоря, Java напоминает мне шведский стол, где можно легко затеряться, даже если Вы опытный разработчик. Если у Вас будет возможность, изучите альтернативы аннотациям – результаты могут Вас удивить.

Журнал изменений

16 сентября 2022 г.: перевел и перенес заметку с Medium.

28 марта 2025 г.: стилистически обновил текст.

Если Вы хотите обсудить содержание заметки, задать вопросы или предложить изменения, то со мной можно связаться в Telegram