Академический Документы
Профессиональный Документы
Культура Документы
Yandex.Money
Введение
Что это?
Целью создания этого списка правил является попытка установить стандарты написания кода на Java,
которые были бы удобными и практичными одновременно. Само собой, мы практикуем то, что создали.
Эти правила являются одним из тех стандартов, которые лежат в основе нашей ежедневной работы в
Yandex.Money. Не все эти правила имеют четкое обоснование. Некоторые из них просто приняты у нас в
качестве стандартов.
Назначение
Соглашения по кодированию служат для следующих целей:
Они создают общепринятый вид кода, таким образом читатель может сфокусироваться на
содержании а не формате.
Они помогают читателям быстрее понимать код делая предположения основанные на предыдущем
опыте.
Упрощают переиспользование, изменение и поддержку кода
Демонстрируют 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), который требует, что бы вы тщательно удаляли дублирование в
коде.
Tip Класс со словом «And» в названии — это явное нарушение данного правила.
Note Если вы создаете класс, который описывает тип, вы можете значительно упростить
его использование, если сделаете его неизменяемым.
Note Этот принцип также известен как Принцип подстановки Барбары Лисков, один из
принципов S.O.L.I.D.
Объект не должен открывать доступ к классам, от которых он зависит, потому что объекты-пользователи
могут неправильно использовать свойства и методы для получения доступа к объектам, находящимся за ним.
Делая так, вы позволяете вызываемому коду объединиться в одно целое с классом, который вы используйте.
Таким образом, вы ограничиваете возможность замены одной реализации на другую в будущем.
if (split.length > 1) {
domain = split[1];
}
}
if (EmailValidator.isValid(emailAddress)) {
email = new Email(emailAddress);
}
}
}
if(isNotValid(email)) {
throw new IllegalArgumentException("Not email");
}
Плохо:
public void wrongParameters(Configuration configuration) {
final ConnectionString connectionString = configuration.getConnectionString();
Хорошо:
public void rightParameters(ConnectionString connectionString) {
//do something only with connectionString
}
Пишите:
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?
}
}
Вместо:
public void mainMethodException() {
try {
doSomethingWithExceptions();
} catch (RetryException e) {
//maybe retry?
} catch (SomeErrorException e) {
logger.error(e.getMessage());
return;
}
//something useful
}
Двойное отрицание более сложно для понимания, чем простое выражение, и люди склонны путаться в нем.
Лучше использовать customer.hasOrders()
Еще пример:
вместо
!StringUtils.isEmpty
использовать
StringUtils.hasText(..)
Строки, предназначенные для логирования или трассировки, являются исключением из этого правила.
Литеральные значения допускается использовать только тогда, когда их смысл ясен из контекста и их не
планируется изменять. Например:
mean = (a + b) / 2; // среднее арифметическое
waitMilliseconds(waitTimeInSeconds * 1000); // тут тоже все понятно,
Если значение одной константы зависит от значения другой, укажите это в своем коде.
public class SomeSpecialContainer {
public static final int MAX_ITEM = 32;
public static final int HIGH_WATER_MARK = 3 * MAX_ITEM / 4; // 75%
}
Использование Optional<T>
Разрешено в:
Результате метода
Геттерах
Запрещено в:
Полях класса
Параметрах метода
Конструкторе
Сеттерах
Коллекциях
DTO
Инициализация массива
может быть «похожей на блок»:
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
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());
//Правильно
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"));
}
//Все прозрачно
long count = items.stream()
.filter(Objects::nonNull)
.map(Item::getId)
.filter(Objects::nonNull)
.filter(id -> id.startsWith("PREFIX"))
.filter(id -> id.contains("PHRASE"))
.count();
//Тяжело читается
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());
Правила форматирования
Правила форматирования нужны для улучшения читаемости и упрощения ревью изменения кода работащего
с 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();
, таким образом при изменении одного условия или добавления нового на ревью будет видно только это
изменение. Изменение всей строчки будет тяжело ревьювить
case "yes":
System.out.println("You answered with Yes");
break;
default:
// Not supposed to end up here.
throw new UnsupportedOperationException("Unexpected answer: " + answer);
}
}
if (val > 0) {
pos = true;
} else {
pos = false;
}
Делайте так:
boolean pos = (val > 0);
if (someInteger < 0) {
result = "Negative";
} else {
result = "Positive";
}
return result;
Пишите:
return (someInteger < 0) ? "Negative" : "Positive”;
Чтобы понять, что делает этот код, вам придется вникать в его детали и предвидеть все варианты его
выполнения. Конечно, вы можете добавить поясняющий комментарий перед этим кодом, но лучше замените
сложное выражение методом, название которого будет говорить само за себя.
if (isSolventAccount()) {
// что-то делаем
}
Если вам потребуется изменить этот метод, вам все равно придется разбираться в том, как он работает. Но
теперь гораздо легче понять код, который его вызывает.
//#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);
}
На первый взгляд все выглядит замечательно, но когда вы будете использовать этот метод, смысл
логической переменной полностью потеряет свою ясность:
Customer customer = createCustomer(true);
Обычно, если метод принимает булевый флаг в качестве параметра, то он делает более, чем одну вещь и
нуждается в рефакторинге для разделения на два или более метода. Альтернативным решением является
замена флага перечислением.
Customer customer = createCustomer(PayerLevel.PLATINUM);
Используйте:
Runnable lambdaRunnable = () -> System.out.println("Hello world two!");
Аннотации
Аннотации применяемые к классу методу или конструктору идут сразу после блока документации и каждая
аннотация должна находится на своей строке (т.е. одна аннотация на строку)
@Override
@Nullable
public String getNameIfPresent() { ... }
Рекомендации по именованию
Константы
Любая константа это 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"};
Не используйте аббревиатуры
Например, используйте onButtonClick вместо onBtnClick. Избегайте использования одиночных символов в
названиях переменных, таких как «i» и «q».
Даже если переменная внутри метода final и immutable, она не должна быть написана в стиле константы.
Хорошие примеры:
StringBuilder
TextField
// Не правильно!
static Employee getEmployee() {}
// Правильно
static Employee get() {...}
// Тоже правильно.
void addNewJob() {...}
void registerForMeeting() {...}
}
может вернуть команда, и что они означают, может ли повтор запроса исправить ошибки.
Документация к классу/интерфейсу
/**
* <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 (true) {}
Отделяйте пробелом любое зарегистрированное слово от закрывающей фигурной скобки ”{"
предшествующей в этой же строке.
Добавляйте пробел перед фигурной скобкой ”{“ за исключением:
@SomeAnnotation({"a","b"}) (не используются пробелы)
String[][] x = {{"foo"}}; (не требуется пробелов между ”{{”)
Добавляйте пробел перед и после бинарных или тернарныхоператоров (например +, -, ==). Это
также применимо к символам выступающим в роли операторов.
Амперсанд в конъюнктивной связи типов: <T extends Foo & Bar>
Оператор ”|" в catch блоке: catch (FooException | BarException e)
Разделитель “:” в for или foreach
Добавляйте пробел после (,:;) или закрывающей скобки «)» в приведении типов (cast)
Добавляйте пробел с обоих сторон от двойного слеша (//)
Добавляйте пробел между типом и переменной в объявлении: List<String> list
Опционально внутри обоих скобок в инициализации массивов:
Например: Вместо:
if ( condition == null )
Пишите:
if (condition == null)
Спец. Символы
Пробельные символы
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 = "\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.
Рекомендации по логированию
Используем slf4j
Следует выбирать уровень логирования согласно критичности информации:
INFO - информация для отслеживания корректного поведения системы
WARN - ошибки не приводящие к деградации системы
ERROR - ошибки требующие действий эксплуатации, т.к. приложение работает не корректно
Формат логов должен быть единым для всех компонентов для возможности автоматизации поиска
информации:
В начале - статика
Двоеточие - разделитель статики и параметров
Параметры в формате - <параметр1>=<значение>, <параметр2>=<значение>