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

144 lines
17 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

---
aliases:
tags:
- maturity/🌱
- content/opinion
date: 2024-09-17
zero-link:
- "[[../../meta/zero/00 Java разработка|00 Java разработка]]"
parents:
linked:
permalink: dev/java/tuple
---
## Тезисы
- Публичные методы интерфейсов должны быть самодокументируемыми и понятными без дополнительных комментариев.
- `Tuple` в публичных методах снижает читаемость и может запутать других разработчиков.
- `Tuple` в аргументах метода также может снизить ясность и увеличить вероятность ошибок.
- В приватных методах `Tuple` допустим, если используется внутри класса и не нарушает ясность.
- В реактивных пайпах `Tuple` могут быть полезны для временного объединения значений.
- Извлечение данных из `Tuple` в локальные переменные улучшает читаемость реактивных пайпов.
- Если вы на проекте один и никто другой код читать не будет, можете использовать `Tuple` как угодно 😁
***
Некоторые языки программирования, такие как Go, позволяют возвращать из функции несколько значений сразу. В Java, однако, метод может вернуть только одно значение, и зачастую хочется вернуть сразу два — например, пару связанных данных. Конечно, правильный подход — создать объект с двумя полями, но часто на практике оказывается, что проще и быстрее использовать готовые решения вроде классов `Tuple` и `Pair`, которые служат обертками.
Но прежде чем полагаться на эти классы, важно понять их границы применимости. Для упрощения дальнейшего объяснения я буду использовать `Tuple` как пример, но все сказанное ниже также относится и к `Pair`.
## Возвращаем Tuple в публичном методе
Публичные методы интерфейсов — это своего рода контракты между вами, разработчиком функциональности, и клиентом, то есть другим разработчиком, который будет использовать ваш код для решения своих задач.
==Так как большую часть времени разработчик не пишет, а читает чужой код, важно, чтобы этот контракт был понятен без дополнительных пояснений и комментариев.==
Представим, что вам нужно вернуть имя пользователя и его возраст. На первый взгляд, использование `Tuple` может показаться удобным решением: не нужно создавать новый класс, а возвращать сразу несколько значений — легко и быстро.
И ваш контракт (интерфейс) выглядит следующим образом:
```java
public interface UserService {
Tuple<String, Integer> getUserData(String userId);
}
```
Посмотрев на контакт `getUserData(userId)` сможет ли другой разработчик сказать какие результаты он получит? Вряд ли.
Недостатки такого использования:
- **Снижение читабельности кода:** Клиенту интерфейса будет неочевидно, что именно содержится в возвращаемом `Tuple`. В отличие от специально созданного класса с понятными именами полей, `Tuple` не говорит напрямую, что первое значение — это имя, а второе — возраст. Это усложняет понимание и использование метода.
- **Отсутствие самодокументируемости:** Хорошо продуманные классы и методы сами по себе выступают как документация, тогда как `Tuple<String, Integer>` требует дополнительных пояснений или комментариев, чтобы разработчики понимали, что происходит. ==Вы заставляете других разработчиков тратить больше времени на понимание логики метода.==
- **Повышенная вероятность ошибок:** Использование неименованных полей увеличивает риск случайных ошибок, например, при изменении порядка полей. Разработчики могут легко перепутать значения и передать их неправильно. Особенно если тип у значений совпадает.
- **Сложности с расширяемостью:** Если в будущем потребуется расширить возвращаемые данные, интерфейс с `Tuple` станет менее гибким.
## Tuple в аргументах метода
Когда методы принимают несколько параметров, иногда может возникнуть желание объединить их в `Tuple`, чтобы упростить сигнатуру метода или передать параметры в одном объекте. На первый взгляд, это кажется удобным, особенно если типы данных уже известны, и не хочется создавать отдельный класс.
Особенно сильно такое желание возникает, когда вы вызываете публичный метод, который возвращает `Tuple`, и вам нужны все его результаты. В таких ситуациях кажется логичным продолжать работать с `Tuple`, чтобы избежать излишней детализации и создавать меньше кода. [[../../psychology/Нисходящая спираль|Нисходящая спираль]] закручивается 😊
Представим, что ваш метод выглядит следующим образом. И в этот метод приходит другой разработчик, которому нужно добавить новую фичу или исправить баг и он видит вот это:
```java
public void processUserData(Tuple<String, Integer> userData) {
// Логика обработки данных
}
```
Какие проблемы могут возникнуть при таком подходе. На самом деле все те же:
- **Пониженная читаемость и ясность кода:** Как и в случае с возвращаемыми значениями, использование `Tuple` не даёт ясности о том, что именно передаётся в метод. Из сигнатуры метода непонятно, что строка представляет имя пользователя, а целое число — возраст.
- **Нарушение принципа самодокументируемого кода**
- **Увеличение вероятности ошибок**
- **Сложности с изменениями и расширением кода**
## Возвращаем Tuple в приватном методе
В отличие от публичных методов, приватные методы находятся внутри границ одного класса и, не участвуют в контрактах. Это дает больше свободы в выборе структур данных для внутренних операций.
Возвращение `Tuple` из приватного метода может оказаться разумным решением, когда нужно быстро вернуть несколько значений без создания дополнительных классов. Например:
```java
private Tuple<String, Integer> fetchUserData() {
// Логика получения данных
return new Tuple<>("John Doe", 30);
}
```
Когда использовать `Tuple` уместно:
- Если данные возвращаются только внутри приватного метода и используются в узком контексте, где значение каждого элемента `Tuple` очевидно из контекста вызова.
- Если метод временный и планируется рефакторинг, или когда требуется быстрое прототипирование.
Преимущества использования `Tuple` в приватных методах:
- **Удобство и скорость реализации:** Использование `Tuple` может значительно сократить время на реализацию, особенно когда речь идет о простых данных и методах, которые используются в узких контекстах.
- **Снижение избыточности кода:** Если данные нужны только внутри класса и их использование ограничено несколькими методами, создание отдельного класса может казаться избыточным. `Tuple` позволяет избежать ненужной сложности и дополнительных файлов.
Тем не менее, стоит помнить о проблемах Tuple. Если логика метода со временем усложниться, читаемость кода может понизится.
**Когда стоит задуматься о создании отдельного класса:**
- Если структура данных становится сложной или используется в нескольких местах внутри класса.
- Если потребуется передавать данные в другие классы в будущем.
- Если работа с данными требует дополнительной логики, такой как валидация или преобразование.
## Tuple в реактивном программировании
В [[../architecture/Реактивное программирование|реактивном программировании]], особенно с использованием [[../../meta/zero/00 Quarkus|Quarkus]] и Mutiny, часто возникает необходимость передачи нескольких значений между стадиями реактивного потока данных. `Tuple` может быть удобным решением для объединения значений, особенно когда результат остаётся внутри потока и не становится частью публичного API.
Например, в реактивных пайпах могут использоваться такие структуры, как `Tuple2`, `Tuple3` и далее:
```java
Uni<User> userUni = getUser()
...
.onItem().invoke(tuple -> {
String name = tuple.getItem1();
int age = tuple.getItem2();
// Дальнейшая обработка
});
```
Здесь `Tuple2` используется для объединения имени и возраста пользователя, ==и значения извлекаются в локальные переменные с понятными именами.== Если нужно объединить больше значений, можно использовать `Tuple3`, `Tuple4` и так далее:
```java
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 разработка|00 Java разработка]]
**Родитель**::
**Источник**::
**Создана**:: [[2024-09-17]]
**Автор**::
### Дополнительные материалы
-
### Дочерние заметки
<!-- QueryToSerialize: LIST FROM [[]] WHERE contains(Родитель, this.file.link) or contains(parents, this.file.link) -->