digital-garden/dev/java/Границы применимости Tuple и Pair в разработке.md

17 KiB
Raw Blame History

aliases tags date zero-link parents linked permalink
maturity/🌱
content/opinion
2024-09-17
../../meta/zero/00 Java разработка
dev/java/tuple

Тезисы

  • Публичные методы интерфейсов должны быть самодокументируемыми и понятными без дополнительных комментариев.
  • Tuple в публичных методах снижает читаемость и может запутать других разработчиков.
  • Tuple в аргументах метода также может снизить ясность и увеличить вероятность ошибок.
  • В приватных методах Tuple допустим, если используется внутри класса и не нарушает ясность.
  • В реактивных пайпах Tuple могут быть полезны для временного объединения значений.
  • Извлечение данных из Tuple в локальные переменные улучшает читаемость реактивных пайпов.
  • Если вы на проекте один и никто другой код читать не будет, можете использовать Tuple как угодно 😁

Некоторые языки программирования, такие как Go, позволяют возвращать из функции несколько значений сразу. В Java, однако, метод может вернуть только одно значение, и зачастую хочется вернуть сразу два — например, пару связанных данных. Конечно, правильный подход — создать объект с двумя полями, но часто на практике оказывается, что проще и быстрее использовать готовые решения вроде классов Tuple и Pair, которые служат обертками.

Но прежде чем полагаться на эти классы, важно понять их границы применимости. Для упрощения дальнейшего объяснения я буду использовать Tuple как пример, но все сказанное ниже также относится и к Pair.

Возвращаем Tuple в публичном методе

Публичные методы интерфейсов — это своего рода контракты между вами, разработчиком функциональности, и клиентом, то есть другим разработчиком, который будет использовать ваш код для решения своих задач.

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

Представим, что вам нужно вернуть имя пользователя и его возраст. На первый взгляд, использование Tuple может показаться удобным решением: не нужно создавать новый класс, а возвращать сразу несколько значений — легко и быстро.

И ваш контракт (интерфейс) выглядит следующим образом:

public interface UserService {
    Tuple<String, Integer> getUserData(String userId);
}

Посмотрев на контакт getUserData(userId) сможет ли другой разработчик сказать какие результаты он получит? Вряд ли.

Недостатки такого использования:

  • Снижение читабельности кода: Клиенту интерфейса будет неочевидно, что именно содержится в возвращаемом Tuple. В отличие от специально созданного класса с понятными именами полей, Tuple не говорит напрямую, что первое значение — это имя, а второе — возраст. Это усложняет понимание и использование метода.
  • Отсутствие самодокументируемости: Хорошо продуманные классы и методы сами по себе выступают как документация, тогда как Tuple<String, Integer> требует дополнительных пояснений или комментариев, чтобы разработчики понимали, что происходит. ==Вы заставляете других разработчиков тратить больше времени на понимание логики метода.==
  • Повышенная вероятность ошибок: Использование неименованных полей увеличивает риск случайных ошибок, например, при изменении порядка полей. Разработчики могут легко перепутать значения и передать их неправильно. Особенно если тип у значений совпадает.
  • Сложности с расширяемостью: Если в будущем потребуется расширить возвращаемые данные, интерфейс с Tuple станет менее гибким.

Tuple в аргументах метода

Когда методы принимают несколько параметров, иногда может возникнуть желание объединить их в Tuple, чтобы упростить сигнатуру метода или передать параметры в одном объекте. На первый взгляд, это кажется удобным, особенно если типы данных уже известны, и не хочется создавать отдельный класс.

Особенно сильно такое желание возникает, когда вы вызываете публичный метод, который возвращает Tuple, и вам нужны все его результаты. В таких ситуациях кажется логичным продолжать работать с Tuple, чтобы избежать излишней детализации и создавать меньше кода. ../../psychology/Нисходящая спираль закручивается 😊

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

public void processUserData(Tuple<String, Integer> userData) {
    // Логика обработки данных
}

Какие проблемы могут возникнуть при таком подходе. На самом деле все те же:

  • Пониженная читаемость и ясность кода: Как и в случае с возвращаемыми значениями, использование Tuple не даёт ясности о том, что именно передаётся в метод. Из сигнатуры метода непонятно, что строка представляет имя пользователя, а целое число — возраст.
  • Нарушение принципа самодокументируемого кода
  • Увеличение вероятности ошибок
  • Сложности с изменениями и расширением кода

Возвращаем Tuple в приватном методе

В отличие от публичных методов, приватные методы находятся внутри границ одного класса и, не участвуют в контрактах. Это дает больше свободы в выборе структур данных для внутренних операций.

Возвращение Tuple из приватного метода может оказаться разумным решением, когда нужно быстро вернуть несколько значений без создания дополнительных классов. Например:

private Tuple<String, Integer> fetchUserData() {
    // Логика получения данных
    return new Tuple<>("John Doe", 30);
}

Когда использовать Tuple уместно:

  • Если данные возвращаются только внутри приватного метода и используются в узком контексте, где значение каждого элемента Tuple очевидно из контекста вызова.
  • Если метод временный и планируется рефакторинг, или когда требуется быстрое прототипирование.

Преимущества использования Tuple в приватных методах:

  • Удобство и скорость реализации: Использование Tuple может значительно сократить время на реализацию, особенно когда речь идет о простых данных и методах, которые используются в узких контекстах.
  • Снижение избыточности кода: Если данные нужны только внутри класса и их использование ограничено несколькими методами, создание отдельного класса может казаться избыточным. Tuple позволяет избежать ненужной сложности и дополнительных файлов.

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

Когда стоит задуматься о создании отдельного класса:

  • Если структура данных становится сложной или используется в нескольких местах внутри класса.
  • Если потребуется передавать данные в другие классы в будущем.
  • Если работа с данными требует дополнительной логики, такой как валидация или преобразование.

Tuple в реактивном программировании

В ../architecture/Реактивное программирование, особенно с использованием ../../meta/zero/00 Quarkus и Mutiny, часто возникает необходимость передачи нескольких значений между стадиями реактивного потока данных. Tuple может быть удобным решением для объединения значений, особенно когда результат остаётся внутри потока и не становится частью публичного API.

Например, в реактивных пайпах могут использоваться такие структуры, как Tuple2, Tuple3 и далее:

Uni<User> userUni = getUser()
    ...
    .onItem().invoke(tuple -> {
        String name = tuple.getItem1();
        int age = tuple.getItem2();
        // Дальнейшая обработка
    });

Здесь Tuple2 используется для объединения имени и возраста пользователя, ==и значения извлекаются в локальные переменные с понятными именами.== Если нужно объединить больше значений, можно использовать Tuple3, Tuple4 и так далее:

Uni<Order> orderUni = getOrder()
    ...
    .onItem().invoke(tuple -> {
        String orderId = tuple.getItem1();
        double total = tuple.getItem2();
        LocalDate date = tuple.getItem3();
        // Дальнейшая обработка
    });

Когда использование различных Tuple оправдано:

  • Переходные стадии в пайпах: Внутри реактивных потоков, Tuple2, Tuple3 и другие позволяют временно объединять нужное количество значений для обработки, сохраняя компактность и удобство.
  • Локальные переменные для улучшения читаемости: Извлечение значений Tuple в локальные переменные с осмысленными именами значительно улучшает читаемость кода и упрощает понимание логики.

Ограничения:

  • Сложность при увеличении количества элементов: Чем больше значений объединяется в Tuple (например, Tuple4, Tuple5 и далее), тем сложнее становится поддерживать и понимать код. В таких случаях лучше рассмотреть создание именованных классов.
  • Не использовать в публичных методах: Если Tuple выходит за границы класса, в публичные методы или интерфейсы, использование именованных структур данных предпочтительнее для улучшения читаемости и поддержки.

Заключение

Использование Tuple в различных сценариях — будь то публичные методы, приватные методы или реактивные пайпы — всегда требует баланса между удобством и читаемостью кода. Tuple может быть полезным инструментом в определённых ситуациях, но стоит помнить, что ясность и поддерживаемость кода зачастую важнее мгновенного выигрыша в скорости разработки. Создание именованных классов или record помогает сделать код самодокументируемым, снижает вероятность ошибок и упрощает дальнейшую поддержку.

Однако, если вы на проекте один и точно уверены, что других разработчиков там никогда не появится, то можете использовать Tuple как хотите 😁

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


Мета информация

Область:: ../../meta/zero/00 Java разработка Родитель:: Источник:: Создана:: 2024-09-17 Автор::

Дополнительные материалы

Дочерние заметки