Вы находитесь на странице: 1из 28

Рекомендации по кодированию на Java

Yandex.Money

Введение

Что это?
Целью создания этого списка правил является попытка установить стандарты написания кода на Java,
которые были бы удобными и практичными одновременно. Само собой, мы практикуем то, что создали.
Эти правила являются одним из тех стандартов, которые лежат в основе нашей ежедневной работы в
Yandex.Money. Не все эти правила имеют четкое обоснование. Некоторые из них просто приняты у нас в
качестве стандартов.

Почему необходимо такое руководство?


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

Назначение
Соглашения по кодированию служат для следующих целей:
Они создают общепринятый вид кода, таким образом читатель может сфокусироваться на
содержании а не формате.
Они помогают читателям быстрее понимать код делая предположения основанные на предыдущем
опыте.
Упрощают переиспользование, изменение и поддержку кода
Демонстрируют Java best practices

Основные принципы
The Principle of Least Surprise (or Astonishment), который означает, что вы не должны использовать
решения, в которых есть вещи, которые люди могут не понять, или которые могут завести в тупик.
Keep It Simple Stupid (a.k.a. KISS), забавный способ сказать, что самое простое решение является
более чем достаточным.
You Ain’t Gonne Need It (a.k.a. YAGNI), говорит нам создавать решения для текущей проблемы, а не
те, которые рассчитаны на возможные проблемы в бедующем (или вы можете предсказывать
будущее?)
Don’t Repeat Yourself (a.k.a. DRY), который требует, что бы вы тщательно удаляли дублирование в
коде.

Эти рекомендации являются стандартами?


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

Рекомендации по проектированию классов

Класс или интерфейс должны иметь единственное предназначение


Класс или интерфейс должен иметь единственное предназначение в рамках системы, в которой он
используется. Как правило, класс служит одной из целей: либо он описывает тип, например, email или ISBN
(международный стандартный книжный номер), либо представляет из себя абстракцию некоторой бизнес-
логики, либо он описывает структуру данных, либо отвечает за взаимодействие между другими классами. Он
никогда не должен в себе комбинировать эти задачи. Это правило известно как Принцип единой
ответственности, один из принципов S.O.L.I.D.

Tip Класс со словом «And» в названии — это явное нарушение данного правила.

Tip Для взаимодействия между классами используйте паттерны проектирования. Если


вам не удается применить ни один из паттернов к классу, возможно, он берет на себя
слишком большую ответственность.

Note Если вы создаете класс, который описывает тип, вы можете значительно упростить
его использование, если сделаете его неизменяемым.

Конструктор должен полностью иницициализировать объект


Не должно быть необходимости устанавливать дополнительные параметры для использования объекта в
целях, для которых объект был разработан.

Интерфейс должен быть небольшим и должен быть сфокусирован на


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

Используйте интерфейс, а не базовый класс, чтобы поддерживать


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

Используйте интерфейс для реализации слабой связанности между


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

Функции, которые используют базовый тип, должны иметь


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

Note Этот принцип также известен как Принцип подстановки Барбары Лисков, один из
принципов S.O.L.I.D.

Не ссылайтесь на производные классы из базового класса


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

Объект должен обладать ограниченным знанием о других объектах,


которые не имеют непосредственного отношения к этому объекту
Если ваш код напоминает код, который приведен ниже, то вы нарушаете Закон Деметры.
someObject.getSomeProperty().getChild().foo();

Объект не должен открывать доступ к классам, от которых он зависит, потому что объекты-пользователи
могут неправильно использовать свойства и методы для получения доступа к объектам, находящимся за ним.
Делая так, вы позволяете вызываемому коду объединиться в одно целое с классом, который вы используйте.
Таким образом, вы ограничиваете возможность замены одной реализации на другую в будущем.

Note Использование класса, реализующего текучий интерфейс (Fluent Interface), может


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

Caution Исключение: При использовании инверсии управления и фреймворков инъекции


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

Избегайте двунаправленной зависимости


Двунаправленная зависимость означает, что два класса знают о публичных методах друг друга или зависят
от внутреннего поведения друг друга. Рефакторинг или замена одного из этих двух классов требуют
изменений в обоих классах и могут повлечь за собой много непредвиденной работы.
Caution Исключение: Доменные модели (Domain Model), применяемые в проектировании
на основе предметной области (Domain Driven Design), могут использовать
двунаправленные зависимости, описывающие ассоциации из реального мира. В
таких случаях я стараюсь удостовериться, что они действительно необходимы, и
по мере возможности пытаюсь их избегать.

Классы должны иметь состояние и поведение


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

Пример плохого использования - когда логика валидации находистя отдельно от данных


public class Email {

private static final String SEPARATOR = "@";

private String local;

private String domain;

public Email(String email) {


final String[] split = email.split(SEPARATOR);
local = split[0];

if (split.length > 1) {
domain = split[1];
}
}

public String getEmail() {


return local + SEPARATOR + domain;
}
}

public class EmailValidator {

private static Pattern VALID_EMAIL_ADDRESS_REGEX =


Pattern.compile("^[A-Z0-9._%+-]+@[A-Z0-9.-]+\\.[A-Z]{2,6}$", Pattern.CASE_INSENSITIVE);

public static boolean isValid(String email) {


return email != null && VALID_EMAIL_ADDRESS_REGEX.matcher(email).matches();
}
}

public class MainClass {

public static void main(String[] args) {


final String emailAddress = args[0];
Email email;

if (EmailValidator.isValid(emailAddress)) {
email = new Email(emailAddress);
}
}
}

Рефактор - валидация перенесена в класс Email


public class Email {

private static final String SEPARATOR = "@";

private static Pattern VALID_EMAIL_ADDRESS_REGEX =


Pattern.compile("^[A-Z0-9._%+-]+" + SEPARATOR + "[A-Z0-9.-]+\\.[A-Z]{2,6}$", Pattern.CASE_INS

private String local;

private String domain;

public Email2(String email) {

if(isNotValid(email)) {
throw new IllegalArgumentException("Not email");
}

final String[] split = email.split(SEPARATOR);


local = split[0];
domain = split[1];
}

public String getEmail() {


return local + SEPARATOR + domain;
}

public static boolean isValid(String email) {


return email != null && VALID_EMAIL_ADDRESS_REGEX.matcher(email).matches();
}

public static boolean isNotValid(String email) {


return !isValid(email);
}
}

public class MainClass {

public static void main(String[] args) {


String[] args = {null, ""};
final String emailAddress = args[0];

Email email = new Email(emailAddress);


}
}

Caution Исключение: Единственным исключением из этого правила являются классы,


используемые для передачи данных между подсистемами приложения, также
называемые Data Transfer Objects, или классы, служащие оберткой для
параметров метода.

Рекомендации по проектированию членов класса

Метод должен иметь единственное предназначение


Так же, как и класс, каждый метод должен иметь одну зону ответственности.

Используйте static final для объектов с заранее определенным


значением
Для примера, рассмотрим структура Color, которая хранит цвета (Red/Green/Blue) и у этого класса есть
числовой конструктор принимающий на вход числовой код цвета (RGB). Этот класс может иметь несколько
предустановленных значений.
public class Color {

public static final Color RED = new Color(0xFF0000);


public static final Color BLACK = new Color(0x000000);
public static final Color WHITE = new Color(0xFFFFFF);

public Color(int redGreenBlue) {


// implementation
}
}

Не выставляйте объекты, описывающие состояние, посредством


статических членов.
Объекты с состоянием (stateful object) – это объекты, которые содержат в себе множество свойств и логики,
которую эти свойства инкапсулируют. Если вы выставите такой объект через статическое свойство или метод
другого объекта, то будут плохо поддаваться рефакторингу и юнит-тестированию классы, которые зависят от
объекта с состоянием.

Возвращемые результаты методов или аргументы, которые


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

Определяйте параметры настолько специфичными, насколько это


возможно
Если элемент класса в качестве параметров требует часть данных другого класса, определяйте типы данных
этих параметров как можно более конкретными и не принимайте в качестве параметра весь объект целиком.
Например, рассмотрим метод, который в качестве параметра требует передать строку подключения,
описанную в некоем центральном интерфейсе Configuration. Вместо того, чтобы в качестве параметра
принимать весь объект, реализующий этот интерфейс, передайте только строку подключения. Это не только
позволит вам уменьшить количество зависимостей в коде, но и улучшит его сопровождаемость в отдаленной
перспективе.
Пример:
public interface Configuration {
ConnectionString getConnectionString();
}

Плохо:
public void wrongParameters(Configuration configuration) {
final ConnectionString connectionString = configuration.getConnectionString();

//do something only with connectionString


}

Хорошо:
public void rightParameters(ConnectionString connectionString) {
//do something only with connectionString
}

Используйте типы, характерные для вашей предметной области,


вместо примитивов
Вместо использования строк, целых и дробных чисел для представления таких специфичных типов, как ISBN
(международный стандартный книжный номер), адрес электронной почты или денежной суммы, создавайте
объекты на каждый тип, которые будут включать в себя как сами данные, так и правила валидации, которые
будут к ним применяться. Делая так, вам удастся избежать множественных реализаций одних и тех же
бизнес-правил. Это улучшит сопровождаемость вашего кода и уменьшит количество багов.

Вызов статического метода должен осуществляться только через


класс, а не объект класса.
Foo aFoo = ...; Foo.aStaticMethod(); // good
aFoo.aStaticMethod(); // bad
somethingThatYieldsAFoo().aStaticMethod(); // very bad

Различные рекомендации по проектированию

Используйте статусное сообщение вместо генерации исключений


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

Пишите:
public void mainMethod() {
final Status status = doSomething();

if (status != Status.SUCCESS) {

if (status == Status.ERROR) {
logger.error(status.getErrorMessage());
return;
}
if (status == Status.RETRY) {
//maybe retry?
}
}

//do something useful


}

Вместо:
public void mainMethodException() {

try {
doSomethingWithExceptions();
} catch (RetryException e) {
//maybe retry?
} catch (SomeErrorException e) {
logger.error(e.getMessage());
return;
}

//something useful
}

Обеспечьте полное и осмысленное сообщение об исключении


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

Генерируйте настолько специфичное исключение, насколько это


возможно
Для примера, если параметр метода не корректного формата, то нужно
выбросить IllegalFormatException вместо его базового типаIllegalArgumentException

Не игнорируйте ошибку путем обработки общих исключений


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

Рекомендации по улучшению сопровождаемости кода

Тело метода не должно быть длиннее 20 выражений


Метод, которому требуется более 20 выражений делает слишком много, или несет несколько обязанностей.
Также длинные методы требуют бОльших умственных затрат для понимания что происходит. Можно разбить
метод на более короткие и сфокусированные, с легко-понятными именами методы.

Делайте все члены классов private по умолчанию


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

Избегайте двойного отрицания


Несмотря на то, что такое свойство, как customer.hasNoOrder() имеет право на существование, избегайте его
использования с отрицанием. Например:
boolean hasOrders = !customer.hasNoOrders();

Двойное отрицание более сложно для понимания, чем простое выражение, и люди склонны путаться в нем.
Лучше использовать customer.hasOrders()
Еще пример:
вместо
!StringUtils.isEmpty

использовать
StringUtils.hasText(..)

Не используйте «магические» числа


Не используйте литеральные значения, числа или строки в вашем коде ни для чего другого, кроме как для
объявления констант.
public class Whatever {
public static final Color PAPAYA_WHIP = new Color(0xFFEFD5);
public static final int MAX_NUMBER_OF_WHEELS = 18;
}

Строки, предназначенные для логирования или трассировки, являются исключением из этого правила.
Литеральные значения допускается использовать только тогда, когда их смысл ясен из контекста и их не
планируется изменять. Например:
mean = (a + b) / 2; // среднее арифметическое
waitMilliseconds(waitTimeInSeconds * 1000); // тут тоже все понятно,

Tip Но правильней для последнего примера:


waitMilliseconds(TimeUnit.SECONDS.toMillis(waitTimeInSeconds));

Если значение одной константы зависит от значения другой, укажите это в своем коде.
public class SomeSpecialContainer {
public static final int MAX_ITEM = 32;
public static final int HIGH_WATER_MARK = 3 * MAX_ITEM / 4; // 75%
}

Note Перечисления часто могут использоваться в качестве хранилища символьных


констант.

Объявляйте и инициализируйте переменные как можно позже


Избегайте стиля языков C и VisualBasic, когда все переменные объявляются в начале блока. Объявляйте и
инициализируйте каждую переменную только тогда, когда она необходима.

Присваивайте значение каждой переменной в отдельном объявлении


Никогда не делайте так:
SomeType result = someField = getSomeMethod();

Использование Optional<T>
Разрешено в:
Результате метода
Геттерах
Запрещено в:
Полях класса
Параметрах метода
Конструкторе
Сеттерах
Коллекциях
DTO

Не производите явного сравнения с true или false


Сравнение логического значения с true или false – это плохой стиль программирования. В качестве примера:
while (condition == false) {} // неправильно, плохой стиль
while (condition != true) {} // тоже неправильно
while (((condition == true) == true) == true) {} // когда ты остановишься?
while (condition) {} // OK

Используйте перечисления вместо строкового списка, если этот список


конечен.
Использование перечислений вместо строковых констант позволяет производить проверку на этапе
компиляции и предотвращает опечатки.
Enum – класс без методов и внутренней документации может быть отформатирован так, как форматируется
инициализация массива:
private enum Suit { CLUBS, HEARTS, SPADES, DIAMONDS }

Инициализация массива
может быть «похожей на блок»:
new int[] {
0, 1, 2, 3
}

new int[] {
0,
1,
2,
3
}

new int[] {
0, 1,
2, 3
}

new int[]
{0, 1, 2, 3}

Не используется стиль C:
Вместо
String args[]
Пишите:
String[] args

Не изменяйте переменную цикла for или foreach внутри тела цикла


Обновление переменной цикла внутри тела цикла ведет к тому, что код становится запутанным. Особенно,
если переменная изменяется более чем в одном месте. Это правило также относится к циклу foreach, хотя
после окончания итерации енумератор обнаружит изменение коллекции и выдаст исключение.
for (int index = 0; index < 10; ++index) {
if (someCondition) {
index = 11; // Wrong! Use ‘break’ or ‘continue’ instead.
}
}

Попытка изменить foreach приведет к исключению


List<String>test = new ArrayList<>();
test.add("Test1");
test.add("Test2");
test.add("Test3");

for (String s : test) {


test.add("Test4");; // java.util.ConcurrentModificationException
}

Java Stream
Старайтесь использовать Stream API для обработки коллекций, в общем случае код будет чище и
компактней.
//Нежелательно
Map<String, Item> map = new HashMap<>();
for (Item item : items) {
if (item != null
&& item.getId() != null
&& item.getId().startsWith("PREFIX")) {
map.put(item.getId(), item);
}
}

//Неправильно
Map<String, Item> map = new HashMap<>();
items.stream()
.filter(Objects::nonNull)
.filter(item -> item.getId() != null)
.filter(item -> item.getId().startsWith("PREFIX"))
.forEach(item -> map.put(item.getId(), item));

//Правильно
Map<String, Item> map = items.stream()
.filter(Objects::nonNull)
.filter(item -> item.getId() != null)
.filter(item -> item.getId().startsWith("PREFIX"))
.collect(Collectors.toMap(Item::getId, item -> item));
Общие правила написания кода работающего со Stream API:
Не используйте Stream API ради Stream API
//Неправильно
Set<Item> itemsWithPrefix = items.stream()
.filter(Objects::nonNull)
.map(Item::getId)
.filter(Objects::nonNull)
.filter(id -> id.startsWith("PREFIX"))
.collect(Collectors.toSet());

Set<Item> itemsWithoutPrefix = items.stream()


.filter(item -> !(item != null
&& item.getId() != null
&& item.getId().startsWith("PREFIX"))
.collect(Collectors.toSet());

//Правильно
for (Item item : items) {
if (item != null
&& item.getId() != null
&& item.getId().startsWith("PREFIX")) {
itemsWithPrefix.add(item);
} else {
itemsWithoutPrefix.add(item);
}
}

Не возвращайте в методах Stream<>. Собрать воедино логику по обработке stream становится очень тяжело.
Исключение - функтор для flatMap()
//Сомнительные приемущества
private Stream<Item> getItemWithPrefix() {
return items.stream()
.filter(Objects::nonNull)
.filter(item -> item.getId() != null)
.filter(item -> item.getId().startsWith("PREFIX"));
}

private Stream<Item> generateItems() {


return Stream.builder()
.add(item1)
.build();
}

long count = Stream.concat(getItemWithPrefix(), generateItems())


.map(Item::getId)
.filter(Objects::nonNull)
.filter(id -> id.contains("PHRASE"))
.count();

//Все прозрачно
long count = items.stream()
.filter(Objects::nonNull)
.map(Item::getId)
.filter(Objects::nonNull)
.filter(id -> id.startsWith("PREFIX"))
.filter(id -> id.contains("PHRASE"))
.count();

if (item1 != null && item1.getId() != null && item1.getId().contains("PHRASE")) {


count++;
}

Выносите большие лямбды в отдельные методы для улучшения читаемости


boolean flag = "true".equals(System.getProperty("FLAG"));

//Тяжело читается
Set<Item> suitableItems = items.stream()
.filter(Objects::nonNull)
.filter(item -> (item.getId() != null
&& (item.getId().startsWith("PREFIX")
|| item.getId().startsWith("OTHER_PREFIX")))
|| (flag && Objects.isNull(item.getId())))
.peek(item -> {
if (flag) {
log.warn("process with null itemId: {}", item);
}
})
.collect(Collectors.toSet());

//Все понятно
Set<Item> suitableItems = items.stream()
.filter(Objects::nonNull)
.filter(this::isItemSuitable)
.peek(this::onFilterItem)
.collect(Collectors.toSet());

private boolean isItemSuitable(Item item) {


return (item.getId() != null
&& (item.getId().startsWith("PREFIX")
|| item.getId().startsWith("OTHER_PREFIX")))
|| (flag && Objects.isNull(item.getId()));
}

private void onFilterItem(Item item) {


if (flag) {
log.warn("process with null itemId: {}", item);
}
}

filter(A && B) лучше разделить на два filter(A).filter(B)


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

Правила форматирования
Правила форматирования нужны для улучшения читаемости и упрощения ревью изменения кода работащего
с Stream API.
Основная идея форматирования обработки stream заключается в выделении в отдельные строки каждой
операции со stream
//Неправильно
items.stream().filter(Objects::nonNull).map(Item::getId).filter(Objects::nonNull)
.filter(id -> id.startsWith("PREFIX")).peek(this::makeSomeMagic).findFirst();

//Правильно
items.stream()
.filter(Objects::nonNull)
.map(Item::getId)
.filter(Objects::nonNull)
.filter(id -> id.startsWith("PREFIX"))
.peek(this::makeSomeMagic)
.findFirst();

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

Не используйте более 3 – х вложенных блоков в методе


Это сильно затрудняет понимание логики метода и код ревью. Выделите вложенный блок в отдельный метод
с понятным названием.
public void someMethod() {
for (int index = 0; index < 10; ++index) {
if (some condition) {
if (some condition) {
if (some condition) { // Уже слишком сложно!
}
}
}
}
}

Всегда используйте конструкции if-else, while, for, foreach и case с


фигурными скобками
if (b1) if (b2) Foo(); else Bar(); // к какому ‘if’ относится ‘else’?

// Правильнее сделать так:


if (b1) {
if (b2) {
Foo();
} else {
Bar();
}
}

Всегда используйте блок default в конце конструкции switch/case


Если блок default будет пуст, добавьте поясняющий комментарий. Кроме того, если этот блок не должен быть
достижимым, сгенерируйте при его вызове UnsupportedOperationException, чтобы обнаружить будущие
изменения, при которых ни один из блоков case не будет достигнут. Следование этой рекомендации позволит
вам писать более чистый код, потому что все сценарии выполнения уже были продуманы
void foo(String answer) {
switch (answer) {
case "no":
System.out.println("You answered with No");
break;

case "yes":
System.out.println("You answered with Yes");
break;

default:
// Not supposed to end up here.
throw new UnsupportedOperationException("Unexpected answer: " + answer);
}
}

Заканчивайте каждый блок if-else-if объявлением else


Объяснение такой необходимости такое же как и в предыдущем правиле:
void foo(String answer) {
if (answer == "no") {
System.out.println("You answered with No");
} else if (answer == "yes") {
System.out.println("You answered with Yes");
} else {
// What should happen when this point is reached? Ignored? If not,
// throw an UnsupportedOperationException.
}
}

Не используйте блок if-else вместо простого (условного) присваивания


Выражайте свои намерения прямо. Например, вместо этого:
boolean pos;

if (val > 0) {
pos = true;
} else {
pos = false;
}

Делайте так:
boolean pos = (val > 0);

Отдавайте предпочтение сокращенной записи if-else


Для примера, вместо:

if (someInteger < 0) {
result = "Negative";
} else {
result = "Positive";
}

return result;
Пишите:
return (someInteger < 0) ? "Negative" : "Positive”;

Инкапсулируйте сложное условное выражение в методе


Рассмотрим следующий пример:
if (!account.isBlocked()
&& (account.isActive()
&& (amount <= account.getBalance())
&& (account.getType() != AccountType.YM_EATING_CARD) {
// что-то делаем
}

Чтобы понять, что делает этот код, вам придется вникать в его детали и предвидеть все варианты его
выполнения. Конечно, вы можете добавить поясняющий комментарий перед этим кодом, но лучше замените
сложное выражение методом, название которого будет говорить само за себя.
if (isSolventAccount()) {
// что-то делаем
}

private boolean isSolventAccount() {


return (!account.isBlocked()
&& (account.isActive()
&& (amount <= account.getBalance())
&& (account.getType() != AccountType.YM_EATING_CARD)
}

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

Разбивайте сложное условное выражение на несколько вызвов


инкапсулирующих части этого выражения
(!account.isBlocked()
&& (account.isActive()
&& (amount <= account.getBalance())
&& (account.getType() != AccountType.YM_EATING_CARD)
}

Вызывайте наиболее близкий перегруженный метод из других


перегруженных методов
Это же правило касается и перегруженных конструкторов.
public class MyClass {

//#1
public String copyValueOf(
char [] phrase) {

//Вызывает перегрузку №2
return copyValueOf(phrase, 0);
}

//#2
public String copyValueOf(
char[] phrase, int startIndex) {

//Вызывает перегрузку №3
return copyValueOf(phrase, startIndex, phrase.length - startIndex);
}

//#3
public String copyValueOf(
char [] phrase, int startIndex, int offset){
return String.copyValueOf(phrase, startIndex, offset);
}

Не допускайте, чтобы метод или конструктор принимал более трех


параметров
Если ваш метод или конструктор принимает более чем три параметра, используйте POJO или Builder для их
инкапсуляции. К тому же юнит-тестирование метода с множеством параметров требует множество сценариев
для тестирования.
При создании объекта с обязательными полями с помощью Builder возможно использовать следующие
подходы: * Если обязательных полей 1-3, а так же ряд необязательных полей, то лучше всего передавать их в
конструктор объекта Builder * Если обязательных полей более 3, а так же ряд необязательных полей, то
лучше всего применить паттерн Step Builder
Это позволит получать compilation error вместо runtime error при изменении набора обязательх полей
объекта.
Удобный плагин для генерации Step Builder в IDEA https://plugins.jetbrains.com/plugin/8276-stepbuilder-
generator
Пример реализации паттерна Step Builder:
public class PojoClass {
private final String nonNullField;

private final String nullableField;


private PojoClass(String nonNullField, String nullableField) {
this.nonNullField = nonNullField;
this.nullableField = nullableField;
}
public static INonNullField builder() {
return new Builder();
}
public interface IBuild {
IBuild withNullableField(String val);
PojoClass build();
}
public interface INonNullField {
IBuild withNonNullField(String val);
}
public static final class Builder implements INonNullField, IBuild {
private String nullableField;
private String nonNullField;
private Builder() {
}
@Override
public IBuild withNonNullField(String val) {
nonNullField = val;
return this;
}
@Override
public IBuild withNullableField(String val) {
nullableField = val;
return this;
}
@Override
public PojoClass build() {
Objects.requireNonNull(nonNullField, "nonNullField");
return new PojoClass(nonNullField, nullableField);
}
}
}

Note Правило не распространяется на классы, которые следуют соглашениям


фреймворков.

Не создавайте методы, которые принимают в качестве параметра


логическое значение
Посмотрите на следующий метод:
public Customer createCustomer(boolean platinumLevel) {…}

На первый взгляд все выглядит замечательно, но когда вы будете использовать этот метод, смысл
логической переменной полностью потеряет свою ясность:
Customer customer = createCustomer(true);

Обычно, если метод принимает булевый флаг в качестве параметра, то он делает более, чем одну вещь и
нуждается в рефакторинге для разделения на два или более метода. Альтернативным решением является
замена флага перечислением.
Customer customer = createCustomer(PayerLevel.PLATINUM);

Не используйте аргументы метода в качестве временных переменных


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

Не оставляйте закомментированные участки кода


Никогда не отправляйте в репозиторий закомментированный код. Вместо этого используйте систему трекинга
задач, чтобы следить за тем, какая работа должна быть сделана. Никто впоследствии не догадается, для
чего предназначен тот или иной блок закомментированного кода. Он был временно закомментирован для
тестирования? Он был скопирован в качестве примера? Должен ли я удалить его?

Не используйте финализаторы. Никогда не переопределяйте


Object.finalize()
Don’t do it. If you absolutely must, first read and understand Effective Java Item 7, "Avoid Finalizers," very carefully,
and then don’t do it.

Не оставляйте public static void main


Вместо ручного тестирования ситуации пользуйтесь unit тестами. Наличие public static void main говорит о
том, что где-то у вас отсутствует unit-тест.

Используйте лямбда-выражения вместо анонимных методов


Вместо:
Runnable anonymousRunnable = new Runnable() {
@Override
public void run() {
System.out.println("Hello world one!");
}
};

Используйте:
Runnable lambdaRunnable = () -> System.out.println("Hello world two!");

Аннотации
Аннотации применяемые к классу методу или конструктору идут сразу после блока документации и каждая
аннотация должна находится на своей строке (т.е. одна аннотация на строку)
@Override
@Nullable
public String getNameIfPresent() { ... }

Порядок следования модификаторов


public
protected
private
abstract
static
final
transient
volatile
synchronized
native
strictfp

Рекомендации по именованию

Используйте американский английский язык


Все идентификаторы должны быть именованы словами из Американского Английского языка.
Выбирайте легкочитаемые имена идентификаторов. horizontalAlignment вместо alignmentHorizontal
Предпочитайте читаемость краткости. canScrollHorizontally вместо scrollableX
Избегайте использования идентификаторов, которые конфликтуют с ключевыми словами, широко
используемых языков программирования

Tip В большинстве проектов, Вы будете использовать именование доменных объектов


специфичное для компании и для корректной работы проверки орфографии Intellij
IDEA, вероятно, вам придется добавить эти слова в исключения Accepted Words

Применяйте the Principle of Least Surprise (or Astonishment)


Например, если таблица в бд называется shop_order, то Dao можно назвать ShopOrderDao.

Для каждого элемента языка используйте соответствующую нотацию

Identifier Casing Example

Имя пакета lowercase com.deepspace

Класс Pascal AppDomain

Интерфейс Pascal BusinessService

Константа All uppercase MIN_WIDTH

Переменная Camel listOfValues

Метод Camel toString

Параметр Camel typeName

Верблюжья нотация (camel case)


1. Сконвертировать строку в ASCII и убрать апострофы. Для примера, "Müller’s algorithm" → "Muellers
algorithm".
2. Разделите результат, разделяя по пробелам или иным знакам препинания (обычно дефис)
3. Запишите все слова в нижнем регистре, затем переведите в верхний регистр первую букву от: a.
Каждого слова – для получения upper camel case b. Всех слов, кроме первого – для получения lower
camel case
4. Совместите все слова в один идентификатор.

Prose form Correct Incorrect

"XML HTTP request" XmlHttpRequest XMLHTTPRequest

"new customer ID" newCustomerId newCustomerID

"inner stopwatch" innerStopwatch innerStopWatch

"supports IPv6 on iOS?" supportsIpv6OnIos supportsIPv6OnIOS

"YouTube importer" YouTubeImporter


YoutubeImporter*

Константы
Любая константа это static поле, но не любое поле static final - константа
Прежде чем написать имя переменной как имя константы задумайтесь, правда ли это константа? Для
примера, если какое-либо состояние этого экземпляра способно изменится, то это практически всегда НЕ
константа. Просто не собираться никогда не изменять объект не достаточно.
// Constants
static final int NUMBER = 5;
static final ImmutableList<String> NAMES = ImmutableList.of("Ed", "Ann");
static final Joiner COMMA_JOINER = Joiner.on(','); // because Joiner is immutable
static final SomeMutableType[] EMPTY_ARRAY = {};
enum SomeEnum { ENUM_CONSTANT }

// Not constants
static String nonFinal = "non-final";
final String nonStatic = "non-static";
static final Set<String> mutableCollection = new HashSet<String>();
static final ImmutableSet<SomeMutableType> mutableElements = ImmutableSet.of(mutable);
static final Logger logger = Logger.getLogger(MyClass.getName());
static final String[] nonEmptyArray = {"these", "can", "change"};

Не включайте числа в наименования переменных, параметров и типов


В большинстве случаев только лень может послужить причиной отсутствия ясного и говорящего самого за
себя имени.

Не используйте префиксы в названиях полей


Например, не используйте g_ или s_ чтобы различить между собой статические и нестатические поля.
Обычно, если в методе трудно отличить локальные переменные от полей класса, то данный метод слишком
громоздок. Вот примеры неправильных наименований: _currentUser, mUserName, m_loginTime.

Не используйте аббревиатуры
Например, используйте onButtonClick вместо onBtnClick. Избегайте использования одиночных символов в
названиях переменных, таких как «i» и «q».

Caution Исключение: Временные переменные и переменные циклов.

Даже если переменная внутри метода final и immutable, она не должна быть написана в стиле константы.

Называйте члены класса, параметры и переменные в соответствии с


их назначением, а не типом
Используйте наименование, которое указывает на функцию, которую выполняет член класса.
Например, название getLength лучше getInt
Не используйте такие термины, как Enum, Class в именах
Добавляйте "s" к названию переменных ссылающимся на коллекции; добавляйте название
коллекции в случаях, когда по множественному числу не понятно list это или Map. orders, parmeters,
requestMap и др.

Именуйте типы, используя словосочетания из существительных или


прилагательных
Плохие примеры:
Common - отсутствует существительное в конце, название не объясняет предназначение
SiteSecurity - название ничего не говорит о предназначении.

Хорошие примеры:
StringBuilder
TextField

При именовании параметров универсальных типов используйте


описательные имена
Всегда добавляйте «Т» к описательным именам параметров типа (RequestT, FooBarT)
Всегда используйте описательные имена, если только имя, состоящее из одной буквы, не является
полностью понятным без пояснений. В этом случае используйте заглавную букву с числом или без в
качестве имени параметра типа (E, T, X, T2)
Рекомендуется в имени параметра типа указывать ограничения, накладываемые на параметр типа.
Например, параметр, предназначенный только для HttpSession, может быть назван HttpSessionT

Не повторяйте имя класса или перечисления в названиях их членов


class Employee {

// Не правильно!
static Employee getEmployee() {}

// Правильно
static Employee get() {...}

// Тоже правильно.
void addNewJob() {...}
void registerForMeeting() {...}
}

Избегайте коротких имен или имен, которые можно спутать с другими


наименованиями
Несмотря на то, что с технической точки зрения следующее выражение может выглядеть корректно, оно
легко может ввести в заблуждение того, кто с ним столкнется
boolean b001 = (lo == l0) ? (I1 == 11) : (lOl != 101);

Именуйте методы, используя связку глагол-объект


Называйте методы с использованием связки глагол-объект. Хороший пример – showDialog. Хорошее имя
должно давать подсказку, что делает этот метод и, если возможно, почему. Также не используйте
слово «And» в названии метода. Это говорит о том, что метод делает более чем одну вещь, что является
нарушением принципа единой ответственности Не используйте названия из одного существительного или
герундия, например, clearing. Примеры с глаголом: doClearing, settle. Для методов, возвращающих примитив
boolean конвенциональное название может начинаться с is или has. Название, содержащее только префикс
get может быть в таком случае двусмысленным.
public interface EmployeeRepository {

Employee[] first() { } // Не правильно. Что значит first()? Сколько это?


Employee[] getFirstFive() {} // Лучше
Employee[] getFiveMostRecent(){} // Отлично: понятное название
void add(Employee employee) {} // Также не использует пару глагол – объект.
// имя типа вносит достаточно ясности
Employee[] gettingFirstFive() {} // Неправильно. Используется герундий вместо глагола.
Employee[] getting() {} // Неправильно. Используется герундий вместо глагола.
boolean getEmpty() {} // Неправильно. get вместо is для примитива.
boolean isEmpty() {} // Правильно.
}

Рекомендации по созданию документации


Документируйте все public, protected типы и члены
Документирование вашего кода позволит IDE выводить подсказки, когда ваш класс будет использоваться где-
нибудь еще. Кроме этого, когда вы хорошо документируете ваши классы, вы можете генерировать
документацию к вашему коду, которая выглядит профессионально.

Caution Исключение: Только private - поля и методы могут быть не прокомментированы

Описывайте документацией или структурами данных возвращаемые в


обертках значения
Например, для CommandResult документируйте возвращаемые значения status , и может ли повтор
запроса исправить статус
SUCCESS
ERROR
PROGRESS
, а также какие коды
ApplicationError
ValidationError

может вернуть команда, и что они означают, может ли повтор запроса исправить ошибки.

При написании документации помните о другом разработчике


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

Избегайте инлайновых комментариев


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

Не используйте /* */ для комментариев


Используйте "//" для каждой строки.

Tip В идее hot key ctrl + /

Пишите комментарии только для того, чтобы объяснить комплексные


решения и алгоритмы
Старайтесь, чтобы ваши комментарии отвечали на вопросы почему и что, а не как. Избегайте разъяснения
блока кода словами, вместо этого помогите понять тому, кто будет читать ваш код, почему вы выбрали это
решение или алгоритм и чего вы пытаетесь этим достичь. В случае выбора альтернативного решения, если
возможно, также объясните, почему простое решение привело к проблемам.

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


должна быть сделана позднее
Добавление к блоку кода комментария ToDo или какого-либо другого для отслеживания работы, которая
должна быть сделана, может показаться хорошим решением. Но на самом деле такие комментарии никому
не нужны. Используйте систему трекинга задач, чтобы отслеживать недоработки.

Документация к классу/интерфейсу
/**
* <p>
* Get information about cards linked to yamoney account
* </p>
*
* @author Platonov Alexey
* @since 16.05.2016
* @see <a href="https://wiki.yamoney.ru/…/Apollo.YaCard">Apollo.YaCard</a>
*/

Tip Для избежания ручной работы шаблон можно применить ко всем файлам. Сделать это
можно в IDEA в Editors → File and Code Templates → Includes → File Header

Рекомендации по оформлению

Общие правила
Держите длину строк в пределах 120 символов (IDEA default).
В качестве отступов используйте 4 пробела и не используйте табуляцию (Для XML документов
используйте 2 пробела)
Всегда используйте конструкции if-else, do, while, for, foreach, с парными фигурными скобками,
даже если без этого можно обойтись
Не разделяйте объявление лямбда-выражения на несколько строк. Используйте формат, как
показано ниже:

methodThatTakesAnAction.do( x -> {
// какие-то действия
};
Так НЕ правильно
methodThatTakesAnAction.do( x
-> {
// какие-то действия
};

Добавляйте скобки вокруг каждого сравнения в условном выражении, когда хотите объеденить
несколько условий, противопоставить другим. Например:

if ((b != null && a > b) || (c != null && a > c))

Разбивайте сокращенный условный оператор на несколько строк, если только он не поместился в


одну, следующим образом:

boolean canBeEvaluated = canBeEvaluated(currentObject)


? evaluator(currentObject);
: failureValue

Разбивайте объявление метода, если длина выходит за пределы


строки
//Добавляйте двойной отступ (4+4) 8 пробелов, для исключения слишком большого отступа
private static synchronized horkingLongMethodName(
int anArg, Object anotherArg, String yetAnotherArg, Object andStillAnother) {

Пробелы
Отделяйте пробелом любое зарегистрированное слово от открывающей скобки ”(" следующей в
этой же строке. Пример: if (true) {}
Отделяйте пробелом любое зарегистрированное слово от закрывающей фигурной скобки ”{"
предшествующей в этой же строке.
Добавляйте пробел перед фигурной скобкой ”{“ за исключением:
@SomeAnnotation({"a","b"}) (не используются пробелы)
String[][] x = {{"foo"}}; (не требуется пробелов между ”{{”)
Добавляйте пробел перед и после бинарных или тернарныхоператоров (например +, -, ==). Это
также применимо к символам выступающим в роли операторов.
Амперсанд в конъюнктивной связи типов: <T extends Foo & Bar>
Оператор ”|" в catch блоке: catch (FooException | BarException e)
Разделитель “:” в for или foreach
Добавляйте пробел после (,:;) или закрывающей скобки «)» в приведении типов (cast)
Добавляйте пробел с обоих сторон от двойного слеша (//)
Добавляйте пробел между типом и переменной в объявлении: List<String> list
Опционально внутри обоих скобок в инициализации массивов:

new int[] {5, 6} и new int[] { 5, 6 } – оба верны

Не используйте пробелы после ’(‘ и перед ’)’.

Например: Вместо:
if ( condition == null )
Пишите:
if (condition == null)

Горизонтальное выравнивание не требуется


Практика допускается, но не требуется.
Не требуется поддерживать данную практику там, где она была использована
private int x; // this is fine
private Color color; // this too

private int x; // permitted, but future edits


private Color color; // may leave it unaligned
Tip Выравнивание реально может улучшить читаемость кода, но это создает проблемы
поддержки в будущем. Изменение в одной строке приведет к изменению формата для
всех остальных. Это приведет к лишней работе по рефакторингу, а также замедлит
ревью кода, и может привести к конфликтам при слиянии веток.

Добавляйте пустую строку между


Статическими инициализаторами (static initializer)
Блоками инициализации переменных (instance initializer)
Полями класса
Конструкторами
Методами
Вложенными классами
Не связанными блоками кода
Перед блоком или одиночным комментариями
Между логически поделенными секциями метода для улучшения читаемости
Опционально: перед первым или после последнего членов класса

Добавляйте 2 пустые строки между


Секциями исходного файла
Между определением класса и описанием вложенного интерфейса

Исходный файл (source file)


Имя файла
По стандартам java – одноименное название файла с именем основного класса плюс .java
Кодировка файла: UTF-8

Спец. Символы
Пробельные символы
ASCII пробельный символ (0x20). Это означает, что:
Все остальные символы пробела в строковых и символьных литералах экранируются
Символы табуляции не используются для отступов
Специальные escape-последовательности
Предпочитайте последовательности с косой чертой перед аски символом, нежели аналоги из octa или
unicode страниц
Используйте: (\b, \t, \n, \f, \r, \", \' and \\)
Не используйте: аналоги из octa (\012) или Unicode (\u000a)

Non-ASCII characters
Выбор других не-ASCII символов склоняется только в сторону упрощения чтения кода.
Например: «∞» и аналог Unicode \u221e

Tip В случае, когда используется символ в Unicode поясняющие комментарии могут быть
крайне полезными

Example Discussion

String unitAbbrev = "μs"; Best: perfectly clear even without a comment.

String unitAbbrev = "\u03bcs"; // "μs" Allowed, but there’s no reason to do this.


Example Discussion

String unitAbbrev = "\u03bcs"; // Greek Allowed, but awkward and prone to mistakes.
letter mu, "s"

String unitAbbrev = "\u03bcs"; Poor: the reader has no idea what this is.

return '\ufeff' + content; // byte order Good: use escapes for non-printable characters, and
mark comment if necessary.

Tip Никогда не ухудшайте читаемость кода только из – за страха, что приложение не


сможет обработать не – ASCII символ. Если такое происходит, значит приложение
сломано и должно быть исправлено

Объявление класса или интерфейса


Объявление пакета
Нет переносов строки. Не действует ограничение на длину строки
Перечисление импортируемых классов
Нет переносов строки . Не действует ограничение на длину строки
Не используйте в импортах Wildcard
Документация класса/интерфейса
Объявление класса или интерфейса
Объявление статических переменных
public, protected, private
Объявление переменных
public, protected, private
Конструктор
Методы класса

Note Если класс имеет несколько перегруженных конструкторов или методов, то


располагайте их последовательно и не вставляйте иные методы между
перегруженными.

Рекомендации по логированию

Используем slf4j
Следует выбирать уровень логирования согласно критичности информации:
INFO - информация для отслеживания корректного поведения системы
WARN - ошибки не приводящие к деградации системы
ERROR - ошибки требующие действий эксплуатации, т.к. приложение работает не корректно
Формат логов должен быть единым для всех компонентов для возможности автоматизации поиска
информации:
В начале - статика
Двоеточие - разделитель статики и параметров
Параметры в формате - <параметр1>=<значение>, <параметр2>=<значение>

private static final Logger log = LoggerFactory.getLogger(CurrentClass.class);


// в коде
log.warn("some error: error={}, details={}, param={}", error, details, param);

Вам также может понравиться