# Рекомендации по использованию вывода типов локальных переменных


## Введение
Вывод типов локальных переменных впервые появился в Java начиная с Java SE 10. При этом явное указание типа локальной переменной может быть заменено на зарезервированное имя типа `var`. В Java SE 11 была добавлена поддержка `var` в формальных параметрах лямбда-выражений. По сути `var` - это "синтаксический сахар" в чистом виде, он никак не проявляется во время выполнения программы и влияет преимущественно на читаемость кода. При уместном применении читаемость кода улучшается за счет удаления бесполезной избыточной информации. В то время как при неуместном применении читаемость кода ухудшается за счет удаления полезной информации. Приведенные далее утверждения и примеры являются скорей рекомендациями и информацией к размышлению, нежели чем строгими правилами. Причиной этому то, что при выборе между явным указанием типа и выводом типа, необходимо принимать во внимание множество различных факторов и учитывать контекст, в котором осуществляется такой выбор.


### Краткое резюме
`var` рекомендуется использовать для устранения дублирования именований типов в левой и правой части присваивания, а также как путь для более компактной записи кода с типами переменных содержащими большое количество параметров. Использование `var` не должно идти в разрез с удобством чтения и модификации кода. `var` не следует использовать если при этом в коде не остается никакой информации о типе переменных там, где эта информация важна для корректности кода и удобства его чтения и модификации. На корректность следует обращать особое внимание в сочетании с diamond-оператором и с generic-методами. Также не следует использовать `var` в сочетании с литералами.


### Базовые принципы
Для  того чтобы эффективно использовать var предлагается руководствоваться следующими принципами:
1. **Удобство чтения кода и внесения в него изменений важнее удобства написания кода.** То что было очевидно автору в момент написания может быть совершенно неочевидно читателю, или самому автору спустя некоторое время. Более компактная запись программы упрощает чтение только до тех пор, пока не сокращаются части кода существенные для понимания сути написанного. И, очевидно, чем проще вносить в код изменения - тем лучше. Тривиальные изменения в коде не должны приводить к нетривиальным регрессиям.
1. **Предпочтительнее, когда контекст необходимый для понимания кода сосредоточен в ближайшей окрестности этого кода.** В частности, если из объявления переменной и ее использования не очевидно сразу, что она собой представляет, то такой код будет сложен для понимания без явной информации о типе переменной. Это особенно актуально при ревью кода, например, когда просматриваются только участки кода содержащие изменения.
1. **Хорошо когда читаемость кода не зависит существенным образом от использования IDE.** Потому что код читается не только в IDE, но и в других инструментах, таких как Arcanum, например. Это, естественно, ни в коем случае не означает, что с кодом может быть неудобно работать в IDE.
1. **При выборе между явным указанием типа и выводом типа следует стремиться к оптимальному соотношению между компактностью записи кода и доступностью сведений о типах при чтении кода.** При этом стоит принимать во внимание, что тип данных может быть достаточно очевиден уже из имени переменной или из того как она инициализируется либо используется. Но не стоит конечно же ни в коем случае это воспринимать как рекомендацию к использованию "венгерской нотации" при именовании переменных.


## Рекомендации
Рассмотрим ряд рекомендаций по эффективному использованию `var`.


### Первая рекомендация
**Старайтесь именовать переменные так, чтобы имя переменной отражало ее сущность.**

{% cut "Пример" %}

Например, можем заменить
```java
try (Stream<Customer> result = dbconn.executeQuery(query, Customer.class)) {
    return result.map(...)
        .filter(...)
        .findAny();
}
```
на
```java
try (var customers = dbconn.executeQuery(query, Customer.class)) {
    return customers.map(...)
        .filter(...)
        .findAny();
}
```
Здесь и так очевидно, что `customers` - это `Stream`, так что нет нужды дополнительно повторять это в имени переменной называя ее `customersStream`. Имя переменной `customers` уже достаточно ясно отражает ее сущность. Кроме того, обратим внимание, что тип элемента в stream-е явно присутствует в правой части. Это важно для того чтобы в IDE можно было легко перейти к объявлению этого типа. Отметим, что если конкретный тип коллекции важен, то предпочтительнее явно объявлять его в левой части, а не включать в наименование переменной, за исключением случаев, когда такое явное объявление сильно загромождает код без существенного выигрыша в читаемости.

{% endcut %}


### Вторая рекомендация
**Cтарайтесь минимизировать область видимости локальных переменных.**
Эта рекомендация актуальна безотносительно того используется вывод типов или нет, но когда вывод типов используется - она еще более оправдана.

{% cut "Пример" %}

Чтобы проиллюстрировать важность минимальной области видимости переменных рассмотрим следующий пример. Пусть в коде
```java
var items = new ArrayList<Item>(...);
items.add(MUST_BE_PROCESSED_LAST);
for (var item : items) ...
```
меняется тип коллекции с `List<Item>` на `Set<Item>`:
```java
var items = new HashSet<Item>(...);
items.add(MUST_BE_PROCESSED_LAST);
for (var item : items) ...
```
тогда элемент `MUST_BE_PROCESSED_LAST` уже не будет обработан последним, а значит это регрессия.
Когда объявление и использование переменной расположены рядом друг с другом, эту регрессию относительно легко заметить, даже когда тип переменной явно не указывается. И, очевидно, значительно сложнее заметить такого рода регрессию, когда код выглядит так:
```java
var items = new HashSet<Item>(...);
// ... 100 строк кода ...
items.add(MUST_BE_PROCESSED_LAST);
for (var item : items) ...
```
Если бы при этом тип переменной был объявлен явно, то при изменении правой части объявления понадобилось бы изменить так же и тип в левой части. Это действие явно ассоциируется с изменением типа переменной и мотивирует проверить места ее использования. Соответственно, вероятность обнаружить регрессию повышается. Когда же тип переменной выводится автоматически, ассоциация с изменением типа при изменении инициализатора будет слабее и вероятность обнаружить регрессию меньше.

{% endcut %}


### Третья рекомендация
**Используйте вывод типа для локальной переменной, когда ее инициализация уже достаточно информативна сама по себе.**

{% cut "Пример" %}

Например, можно заменить
```java
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
```
на
```java
var outputStream = new ByteArrayOutputStream();
```
здесь тип переменной уже явно присутствует справа за счет вызова конструктора и избыточно повторять его еще раз в левой части.
Аналогичное справедливо для фабричных методов с достаточно информативным наименованием. Например, можно заменить
```java
BufferedReader reader = Files.newBufferedReader(...);
List<String> strings = List.of("a", "b", "c");
```
на
```java
var reader = Files.newBufferedReader(...);
var strings = List.of("a", "b", "c");
```
А вот такая замена
```java
List<String> x = timezoneManager.getTzNames();
String availableTimezones = String.join(";", x);
```
на
```java
var timezoneNames = timezoneManager.getTzNames();
String availableTimezones = String.join(";", timezoneNames);
```
не рекомендуется, потому что тип `timezoneNames` будет не очевиден после замены. В частности не ясно, будет ли это коллекция строк или же каких-то иных классов. Тип переменной должен быть очевиден из правой части, либо за счет конструктора, либо за счет фабричного метода из стандартной библиотеки, либо за счет того какой и с какими аргументами вызывается метод в правой части.

{% endcut %}

{% cut "Еще примеры" %}

Можно преобразовать
```java
void removeMatches(Map<? extends String, ? extends Number> map, int max) {
    for (Iterator<? extends Map.Entry<? extends String, ? extends Number>> iterator = map.entrySet().iterator(); iterator.hasNext();) {
        Map.Entry<? extends String, ? extends Number> entry = iterator.next();
        if (max > 0 && matches(entry)) {
            iterator.remove();
            max--;
        }
    }
}
```
к
```java
void removeMatches(Map<? extends String, ? extends Number> map, int max) {
    for (var iterator = map.entrySet().iterator(); iterator.hasNext();) {
        var entry = iterator.next();
        if (max > 0 && matches(entry)) {
            iterator.remove();
            max--;
        }
    }
}
```
Здесь, очевидно, явное выписывание громоздких типов для `iterator` и `entry` только лишь затрудняет чтение кода, а применение var заметно улучшает читаемость без потери важной для понимания кода информации.
Еще один пример, можно преобразовать
```java
try (InputStream is = socket.getInputStream();
        InputStreamReader isr = new InputStreamReader(is, charsetName);
        BufferedReader buf = new BufferedReader(isr)) {
    return buf.readLine();
}
```
к
```java
try (var inputStream = socket.getInputStream();
        var reader = new InputStreamReader(inputStream, charsetName);
        var bufReader = new BufferedReader(reader)) {
    return bufReader.readLine();
}
```
Здесь, очевидно, нет никакой необходимости явно выписывать имена классов при объявлении переменных в `try`.

{% endcut %}


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

{% cut "Пример" %}

Например, пусть есть следующий код
```java
return strings.stream()
    .collect(groupingBy(s -> s, counting()))
    .entrySet()
    .stream()
    .max(Map.Entry.comparingByValue())
    .map(Map.Entry::getKey);
```
Цепочка вызовов достаточно длинная, может возникнуть закономерное желание разделить ее на отдельные логические части. Без `var` это будет выглядеть так:
```java
Map<String, Long> freqMap = strings.stream()
    .collect(groupingBy(s -> s, counting()));
return freqMap.entrySet()
    .stream()
    .max(Map.Entry.comparingByValue())
    .map(Map.Entry::getKey);
```
Явное объявление типа для `freqMap` здесь не улучшает читаемость. С `var` получается соответственно:
```java
var freqMap = strings.stream()
    .collect(groupingBy(s -> s, counting()));
return freqMap.entrySet()
    .stream()
    .max(Map.Entry.comparingByValue())
    .map(Map.Entry::getKey);
```
Здесь сохранили явное деление алгоритма на части, информативно поименовали промежуточный результат, при этом избыточной информации о типах не привнесли.

{% endcut %}


### Пятая рекомендация
**Вывод типов локальных переменных допустимо использовать в обход принципа "программирования на интерфейсах" при соблюдении прочих рекомендаций.**

{% cut "Пример" %}

Например, вполне допустимо вместо
```java
List<Item> itemsList = new ArrayList<>();
```
написать
```java
var itemsList = new ArrayList<Item>();
```
При этом типом `itemsList` станет `ArrayList<Item>` вместо `List<Item>`, что в свою очередь потенциально может привнести зависимость от конкретной реализации списка в код использующий переменную `itemsList`. Но если вспомнить про вторую рекомендацию о минимизации области видимости локальных переменных, то становится ясно, что место потенциальной "утечки" конкретной реализации в код будет хорошо локализовано. Соответственно, не составит большого труда внести необходимые правки при изменении реализации. Более того, для `List` и `ArrayList` в этом примере возможная "утечка" конкретной реализации минимальна.
Стоит, однако, помнить, что эта рекомендация распространяется только на локальные переменные. К полям классов, параметрам и возвращаемым значениям методов она не применима. Так же важно отметить, что в этом примере тип элемента списка после замены явно указан справа, в противном случае такая замена не была бы уместной.

{% endcut %}


### Шестая рекомендация
**Вывод типов локальных переменных в сочетании с diamond-оператором и с generic-методами следует использовать с осторожностью.**
Отсутствие явного объявления типа может в некоторых случаях приводить к тому, что выведенный тип будет отличаться от требуемого.

{% cut "Пример" %}

Например, объявление
```java
PriorityQueue<Item> itemQueue = new PriorityQueue<Item>();
```
можно без изменения смысла записать так:
```java
PriorityQueue<Item> itemQueue = new PriorityQueue<>();
```
или вот так:
```java
var itemQueue = new PriorityQueue<Item>();
```
А вот запись
```java
var itemQueue = new PriorityQueue<>();
```
уже приведет к тому что типом `itemQueue` будет `PriorityQueue<Object>`, что, весьма вероятно, совсем не то что нужно. Это происходит потому, что ни тип переменной слева, ни вызов конструктора не дают никакой дополнительной информации об искомом типе, а значит выбирается наиболее общий тип `Object`. При этом даже если требуется именно `Object` как элемент коллекции, его все равно предпочтительно указывать явно: `var objectsList = new ArrayList<Object>();`.
Аналогично, такое использование generic метода
```java
var list = List.of();
```
приведет к тому что типом `list` будет `List<Object>`, потому что здесь недостаточно информации для вывода более конкретного типа.
Конструкция вида
```java
var list = List.of(foo, bar, baz);
```
также может приводить к тому что будет выведен тип `List<Object>` для `list`, если у переменных в правой части нет более конкретного типа предка. Такая ситуация может быть нежелательной.
Если же информации для вывода конкретного типа достаточно, то var вполне успешно сочетается с diamond-оператором и с generic-методами. Например здесь
```java
Comparator<String> comp = ... ;
var itemQueue = new PriorityQueue<>(comp);
```
типом `itemQueue` будет `PriorityQueue<String>`. А здесь
```java
var list = List.of(BigInteger.ZERO);
```
типом `list` будет `List<BigInteger>`.
Таким образом, при использовании `var` в сочетании с diamond-оператором и с generic-методами, аргументы конструктора или метода должны давать достаточно информации для вывода корректного типа. В противном случае в этом месте нельзя использовать `var`.

{% endcut %}


### Седьмая рекомендация
**Вывод типов локальных переменных в сочетании с литералами не рекомендуется к использованию.**
Использование `var` в объявлении переменной примитивного типа или типа `String` как правило не оправдано, потому что наименования этих типов и так уже достаточно короткие. В то же время в некоторых случаях оно привносит определенные риски.

{% cut "Подробнее о рисках" %}

Если, например, для типов для типов `boolean`, `char`, `long` и `String`
```java
boolean ready = true;
char ch = '\ufffd';
long sum = 0L;
String label = "wombat";
```
можно заменить на
```java
var ready = true;
var ch = '\ufffd';
var sum = 0L;
var label = "wombat";
```
без изменения смысла (хотя отметим, что для `long` пришлось явно использовать соответствующий литерал).
То вот с другими целыми типами ситуация более сложная. Например, если заменить
```java
byte flags = 0;
short mask = 0x7fff;
long base = 17;
```
на
```java
var flags = 0;
var mask = 0x7fff;
var base = 17;
```
то все переменные будут типа `int`, поскольку сужающие и расширяющие преобразования не применятся без явного указания типа.
Для типов с плавающей точкой ситуация несколько проще, чем для целых. Например, можно заменить
```java
float f = 1.0f;
double d = 2.0;
```
на
```java
var f = 1.0f;
var d = 2.0;
```
без изменения смысла (хотя отметим, что для `float` пришлось явно использовать соответствующий литерал).
Но при этом необходимо также учитывать возможность автоматического расширяющего преобразования float-литералов в `double`. Рассмотрим следующий пример:
```java
static final float INITIAL = 3.0f;
...
double temp = INITIAL;
```
здесь есть автоматическое расширяющее преобразование. Если инициализацию `temp` заменить на
```java
var temp = INITIAL;
```
то тип `temp` изменится на `float`. Этот пример к тому же противоречит третьей рекомендации, потому что в контексте объявления переменной недостаточно информации о ее типе.

{% endcut %}


### Восьмая рекомендация
**var в формальных параметрах лямбда-выражений может быть полезен для расстановки аннотаций.**

{% cut "Пример" %}

Например,
```java
(@Nonnull var x, @Nullable var y) -> x.process(y)
```
Это, впрочем, достаточно редкая ситуация.

{% endcut %}


## Прочее
Еще одна потенциальная область применения var - объявление локальных переменных не обозначаемых иначе типов (non-denotable types), таких как анонимные классы и типы-пересечения. Впрочем, на текущий момент это достаточно экзотичная возможность, которую пока едва ли можно рекомендовать для широкого применения.


## Полезные ссылки
Вышеизложенное является вольным пересказом [Local Variable Type Inference: Style Guidelines by Stuart W. Marks](https://openjdk.java.net/projects/amber/LVTIstyle.html), [JEP 323](https://openjdk.java.net/jeps/323), [JEP 286](https://openjdk.java.net/jeps/286) и [Обзор от Oracle](https://developer.oracle.com/java/jdk-10-local-variable-type-inference.html).
