Java Code Conventions: именование, оформление и проверка стиля
Автор: Казачкин Даниил Михайлович · Обновлено
Java Code Conventions — соглашения о том, как называть элементы программы и оформлять исходный код. Они помогают быстрее читать чужие изменения: увидеть границы ветки, отличить…
Java Code Conventions — соглашения о том, как называть элементы программы и оформлять исходный код. Они помогают быстрее читать чужие изменения: увидеть границы ветки, отличить константу от переменной и понять смысл метода без открытия его реализации. Компилятор проверяет допустимость программы, а соглашения делают её удобной для совместной разработки.
После урока вы сможете объяснить правила именования, привести небольшой класс к единому стилю и разделить обязанности форматтера, анализатора и тестов. Для практики достаточно JDK 17 или новее; примеры совместимы с JDK 25, принятой за основу трека, и не используют preview. При необходимости повторите [типы, классы и ссылки](/lessons/java/java-for-developers/java-developer-03).
Спецификация языка и стиль проекта
Ошибку синтаксиса нельзя исправить командным соглашением. Например, обычный публичный класс ShippingCost при сборке javac должен находиться в файле ShippingCost.java с совпадающим регистром. А выбор между двумя и четырьмя пробелами не меняет смысл такой программы. Сначала установите, какое требование проверяете: правило Java, принятое в команде соглашение или собственное предпочтение.
Документ Code Conventions for the Java Programming Language от Sun, опубликованный у Oracle, имеет последнюю редакцию 20 апреля 1999 года. Oracle прямо помечает его как архивный и неподдерживаемый. Это полезная историческая основа, но не исчерпывающее руководство для всех конструкций современной Java. Архив Oracle.
Есть и другие профили. Google Java Style и Spring Framework Code Style задают собственные правила. Команда может принять один профиль или документировать свой. Ссылки на разные руководства без разрешения противоречий не образуют единое соглашение.
Google, Oracle и Spring: ссылки и различия
Начинайте с первоисточника, чтобы пересказ не подменял правила конкретного проекта:
- Google Style Guides — каталог соглашений для разных языков; для Java нужна отдельная страница Google Java Style Guide.
- Java Code Conventions от Sun/Oracle, иногда сокращаемые до JCC, — историческая основа, а не новая редакция стандарта Java.
- Spring Framework Code Style — правила исходников самого Framework. Использование Spring Boot в приложении не обязывает автоматически принимать этот профиль.
| Решение | Sun/Oracle JCC | Google Java Style | Spring Framework Code Style |
|---|---|---|---|
| Отступ блока | Четыре позиции; выбор spaces/tabs не фиксируется | Два пробела | Табуляция |
| Длина строки | Рекомендуется избегать строк длиннее 80 символов | Предел 100 с исключениями | Желательно 90; до 120 допустимо, свыше 120 — нет |
| else, catch, finally | На строке предыдущей закрывающей скобки | На строке предыдущей закрывающей скобки | С новой строки |
| Группы импортов | Package, затем imports; static import документ 1999 года не описывает | Сначала static, затем обычные; сортировка внутри групп | java, затем javax/jakarta, остальные, org.springframework; static в конце |
| Импорты со звёздочкой | Современное правило берут из профиля проекта | Запрещены | Запрещены, включая тесты |
Источники для столбца Oracle: отступы и длина строки, оформление операторов, структура файла. Для Google и Spring используйте руководства выше. В Spring 90 символов — ориентир, а не строгий предел; строки 105–120 нежелательны, для Javadoc ориентир — около 80.
Эти различия не делают вычисления быстрее или правильнее. Их практический эффект виден в diff: два форматтера с разными профилями будут постоянно менять отступы и переносить else туда и обратно. При ревью такой шум мешает заметить реальное изменение условия или аргумента.
Учебный профиль остальной части урока: четыре пробела, открывающая скобка на строке объявления, фигурные скобки у веток и циклов, явные импорты и ориентир 100 символов. Это наш выбор для примеров, а не утверждение, что все три руководства требуют одно и то же.
Одна программа: два варианта оформления
Следующие собственные примеры показывают три различия: отступ, положение else и порядок static import. Они не воспроизводят полный шаблон файлов организаций с лицензиями и документацией. Константа UTF_8 выбрана намеренно: Spring допускает static import констант, хотя ограничивает его использование в production-коде.
Сохраните GoogleImportOrder.java. Здесь два пробела и static import первым:
package com.example.style;
import static java.nio.charset.StandardCharsets.UTF_8;
import java.util.List;
public class GoogleImportOrder {
public static void main(String[] args) {
List<String> names = List.of("Java");
if (names.isEmpty()) {
System.out.println(0);
} else {
System.out.println(names.get(0).getBytes(UTF_8).length);
}
}
}В SpringImportOrder.java отступы состоят из настоящих символов табуляции. Их видимая ширина зависит от редактора; одинаковый вид на экране ещё не означает одинаковые символы в файле.
package com.example.style;
import java.util.List;
import static java.nio.charset.StandardCharsets.UTF_8;
public class SpringImportOrder {
public static void main(String[] args) {
List<String> names = List.of("Java");
if (names.isEmpty()) {
System.out.println(0);
}
else {
System.out.println(names.get(0).getBytes(UTF_8).length);
}
}
}Соберите оба файла и запустите по полному имени класса:
javac -encoding UTF-8 --release 17 -d out GoogleImportOrder.java SpringImportOrder.java
java -cp out com.example.style.GoogleImportOrder
java -cp out com.example.style.SpringImportOrderКаждый запуск выводит 4: оба варианта считают байты одного и того же текста Java в UTF-8. Каталог out нужен для результата компиляции, а часть com.example.style в команде соответствует объявленному пакету. После замены List.of("Java") на List.of() обе программы выведут 0. Различие оформления не меняет ветку, данные или результат.
Как выбрать профиль и не смешать инструменты
В существующем репозитории сначала прочитайте CONTRIBUTING и конфигурацию сборки. При отправке изменений в Spring Framework следуйте правилам Framework; для проекта с Google Style используйте его профиль. В новом приложении на Spring можно выбрать другой стиль и закрепить это решение для всей команды: подключённая библиотека не выбирает отступы за разработчика.
Отдельно различайте Spring Framework Code Style и Spring Java Format. Spring Java Format — инструмент со своими настройками: перенос на 120 символах, табуляция по умолчанию, возможность выбрать пробелы. Он не переставляет импорты; для правил используются отдельные проверки Checkstyle. Поэтому установка форматтера сама по себе не означает соблюдения всех рекомендаций Framework, включая предпочтительную длину 90.
Сначала выберите руководство, затем совместимые инструменты и их версии. Настройте IDE и CI одинаково; проверьте на маленьком файле форматирование, порядок импортов и проверку имён. Если обновление инструмента меняет много строк без изменения логики, вынесите такое обновление в отдельное изменение, чтобы его можно было проверить самостоятельно.
Имена передают роль и смысл
Обычные соглашения Java используют разный регистр для типов, членов и констант. Основу можно сверить с архивным разделом Naming Conventions; примеры в таблице ниже составлены для этого урока.
| Что называем | Принятое оформление | Пример |
|---|---|---|
| Класс, интерфейс, enum, record | UpperCamelCase | ShippingCost, PricePolicy, OrderStatus |
| Метод | lowerCamelCase, действие или запрос | calculateTotal, hasDiscount |
| Поле, параметр, локальная переменная | lowerCamelCase | subtotalInCents, customerName |
| Настоящая константа класса | UPPER_SNAKE_CASE | MAX_RETRY_COUNT |
| Элемент enum | Обычно UPPER_SNAKE_CASE | READY_FOR_PAYMENT |
| Пакет | Строчные компоненты через точку | com.example.orders |
Один регистр не делает имя хорошим. Метод processData остаётся неопределённым даже при правильных заглавных буквах. Если он суммирует оплаченные заказы, calculatePaidTotal объяснит больше. Единицы тоже относятся к смыслу: timeoutMillis точнее, чем timeout, если значение действительно измеряется в миллисекундах. Имя должно соответствовать фактическому поведению, а не желаемому назначению.
Короткое i удобно в небольшом индексном цикле. Но три переменные a, b и c, передаваемые через несколько методов, заставляют читателя держать дополнительную таблицу соответствий в голове. Не кодируйте тип лишними префиксами вроде strCustomerName: тип виден в объявлении и IDE. Для сокращений закрепите один подход, например HttpClient и parseUrl; не меняйте его случайно между соседними классами.
Структура файла и импорты
У обычного исходного файла сначала идёт объявление package, затем импорты, затем объявления типов. Комментарий с лицензией, если он нужен проекту, располагают в начале. Мы храним один основной тип на файл и разделяем группы пустыми строками. Это удобный профиль организации, а не запрет языка на любые дополнительные непубличные типы. JLS, глава 7.
В учебных файлах ниже пакет опущен, чтобы программу можно было собрать одной командой в отдельной папке. В приложении используют осмысленный пакет и согласованную структуру каталогов. Компактные исходники новых версий Java рассматриваются в [уроке о Java 17, 21 и 25](/lessons/java/java-for-developers/java-developer-11); здесь выбран привычный явный класс с main.
Пишите конкретный импорт java.util.List вместо java.util.* и удаляйте неиспользуемые импорты средствами IDE. Правила группировки обычных и static import согласуйте с выбранным профилем. Импорт со звёздочкой допустим в Java, а его запрет — соглашение проекта. Такой импорт не заставляет программу загрузить все классы пакета: причина предпочесть явные имена — читаемость и предсказуемое разрешение имён.
Пример до оформления
Рассчитаем стоимость доставки в условных центах. При сумме заказа от 2000 доставка бесплатна, иначе стоит 199. Отрицательную сумму метод отклоняет. Сохраните этот намеренно плохо оформленный, но рабочий код как ShippingCost.java в папке before:
public class ShippingCost {
private static final int lim=2000;
private static final int c=199;
private static int f(int n){
if(n<0){throw new IllegalArgumentException("subtotal must be non-negative");}
if(n>=lim)return 0;
return c;}
public static void main(String[] args) {
for(int n:new int[]{0,1999,2000,3000})System.out.println(f(n));
}
}Программа компилируется, но при просмотре трудно отличить тело класса от тела метода. Буква c не объясняет, что это цена, а lim скрывает смысл порога. Отсутствие скобок у ветки не нарушает синтаксис, однако делает последующее добавление второй инструкции более рискованным: она может оказаться вне условия.
Тот же расчёт с понятными именами
Сохраните следующий вариант в отдельной папке after под тем же именем ShippingCost.java. Так два определения одного класса не окажутся в одной сборке.
public class ShippingCost {
private static final int FREE_SHIPPING_FROM_CENTS = 2000;
private static final int DELIVERY_FEE_CENTS = 199;
/**
* Calculates the delivery fee in cents.
*
* @param subtotalInCents the non-negative order subtotal
* @return zero when the free-delivery threshold is reached; otherwise the fee
* @throws IllegalArgumentException if the subtotal is negative
*/
private static int deliveryFeeInCents(int subtotalInCents) {
if (subtotalInCents < 0) {
throw new IllegalArgumentException("subtotal must be non-negative");
}
if (subtotalInCents >= FREE_SHIPPING_FROM_CENTS) {
return 0;
}
return DELIVERY_FEE_CENTS;
}
public static void main(String[] args) {
int[] subtotalsInCents = {0, 1999, 2000, 3000};
for (int subtotalInCents : subtotalsInCents) {
System.out.println(deliveryFeeInCents(subtotalInCents));
}
}
}В каждой папке выполните одни и те же команды:
javac -encoding UTF-8 --release 17 ShippingCost.java
java ShippingCostОба варианта выводят четыре строки:
199
199
0
0Мы изменили отступы, пробелы, расположение скобок, имена приватных элементов и описание контракта. Условия расчёта, порог и цена сохранены. Это небольшое переименование вместе с форматированием: один форматтер сам не придумает имя deliveryFeeInCents. Если переименовываете публичный API, потребуется обновить потребителей, а иногда сохранить совместимость; здесь переименованы только приватные члены.
Пробелы, скобки и переносы
Отступ обозначает вложенность: вход в блок добавляет один уровень, выход возвращает прежний. Пробел после запятой отделяет аргументы, а вокруг бинарного оператора помогает увидеть действие. В нашем профиле пишут if (condition), но method(argument); пробел между именем метода и открывающей скобкой не добавляют. Пустая строка отделяет законченные шаги, а не каждую инструкцию от соседней.
Длинное выражение переносите по смысловым частям: аргументам вызова, условиям или шагам цепочки. Не выравнивайте десятки строк ручными пробелами под случайную длину имени: после переименования всё придётся чинить. Удобнее единое правило продолжения строки в форматтере. При этом нельзя бездумно менять содержимое строковых литералов и text blocks: пробелы внутри данных могут быть значимыми.
Фигурные скобки у коротких if и циклов позволяют одинаково читать короткие и длинные ветки. Это не доказательство правильности условия. Ошибку >= вместо > не исправит ни перенос строки, ни красивое выравнивание. Поэтому проверка стиля дополняет тесты и разбор поведения, а не заменяет их.
final не превращает любой объект в константу
final запрещает повторно присвоить переменной другое значение после инициализации. Для ссылки это не означает запрета изменять сам объект. Такое различие задаёт JLS, раздел 4.12.4, и оно важно даже при выборе имени поля.
Сохраните FinalReferenceDemo.java:
import java.util.ArrayList;
import java.util.List;
public class FinalReferenceDemo {
private static final int MAX_RETRY_COUNT = 3;
private static final List<String> outputNames = new ArrayList<>();
public static void main(String[] args) {
outputNames.add("console");
outputNames.add("file");
final int configuredRetries = MAX_RETRY_COUNT;
System.out.println(configuredRetries);
System.out.println(outputNames.size());
}
}После javac -encoding UTF-8 --release 17 FinalReferenceDemo.java и java FinalReferenceDemo получатся строки 3 и 2. Ссылка outputNames остаётся прежней, а список изменяется. В выбранном здесь подходе, совпадающем с трактовкой констант Google Style, такое поле именуют в lowerCamelCase. Локальная final-переменная configuredRetries тоже не становится именем в верхнем регистре. Учебный общий список демонстрирует изменяемость и не является рекомендацией хранить состояние приложения в static-поле.
Комментарии и Javadoc описывают причины и контракт
Комментарий «увеличиваем счётчик» рядом с count++ лишь повторяет инструкцию. Гораздо полезнее объяснить причину необычного ограничения, единицы величины или совместимость с внешней системой. Если комментарий противоречит коду, читатель должен выяснять, чему верить. Обновляйте пояснения вместе с поведением и удаляйте устаревшие следы отладки.
Javadoc предназначен для документации объявлений: что принимает метод, что возвращает и при каких условиях сообщает об ошибке. В примере доставки он фиксирует неотрицательную сумму и единицы. Теги @param, @return и @throws описывают разные части контракта; {@code value} выделяет фрагмент кода внутри описания. Спецификация Javadoc.
Для просмотра документации приватного учебного метода из папки after можно выполнить javadoc -private -encoding UTF-8 -d docs ShippingCost.java и открыть docs/ShippingCost.html. Утилита может предупредить об отсутствии комментариев у класса или main: это отдельные объявления, которые здесь не документированы. Язык документации выбирает команда; английский текст нашего примера не является обязательным требованием Java.
Как закрепить соглашение инструментами
Сначала проверьте настройки репозитория и инструкции для участников. Если они есть, используйте их. Для нового проекта удобно отдельно зафиксировать правила именования, профиль форматирования и список автоматических проверок. Настройки одного разработчика в IDE не должны незаметно переписывать весь файл при каждом сохранении.
Минимальный пример .editorconfig для нашего профиля:
root = true
[*.java]
charset = utf-8
indent_style = space
indent_size = 4
end_of_line = lf
insert_final_newline = trueEditorConfig помогает редакторам согласовать базовые настройки файлов. Он сам не проверяет имена классов, не добавляет Javadoc и не определяет весь алгоритм переносов Java. Наличие свойства в файле также не гарантирует его поддержку каждым редактором: проверьте интеграцию используемого инструмента.
Форматтер автоматически приводит расположение кода к выбранному профилю. Checkstyle проверяет настроенные правила, включая именование, импорты и оформление. Успешная проверка означает только соблюдение включённых правил, а не правильность бизнес-логики. Набор правил описан в документации Checkstyle.
Если команда выбрала Google Java Style, один из вариантов — google-java-format. Его алгоритм имеет ограниченную настраиваемость: это не универсальный форматтер под любой набор отступов и переносов. Не запускайте его настройки по умолчанию поверх нашего профиля из четырёх пробелов, ожидая сохранения этого профиля. Выберите совместимую конфигурацию инструментов и закрепите их версии в сборке.
Практический порядок работы: применить форматтер локально, посмотреть diff, запустить проверку стиля, скомпилировать и выполнить тесты. В CI обычно используют проверку без исправления файлов, чтобы замечания не зависели от редактора автора. Команды зависят от уже настроенных Maven/Gradle-плагинов; универсальную задачу, которой нет в проекте, придумывать не нужно. Массовое переоформление полезно отделять от изменения логики: так проще проверить обе части.
Практика: проведите небольшое ревью
- Сохраните обе версии ShippingCost в разных папках и до запуска предскажите результаты для 0, 1999, 2000 и 3000. Убедитесь, что стиль изменился, а наблюдаемое поведение на этих входах сохранилось.
- Измените порог в after с >= на >. Какой контрольный пример заметит ошибку? Верните исходное условие и поясните, почему форматтер не должен исправлять его за вас.
- Добавьте -1 во входной массив. Проверьте тип исключения и сообщение. Не считайте отсутствие строки с ответом ошибкой оформления: это выбранный контракт отрицательного входа.
- В FinalReferenceDemo попробуйте после инициализации присвоить outputNames новый ArrayList. Сравните ошибку компиляции с успешно выполненным add. Затем восстановите рабочую программу.
- Запишите короткое соглашение своей учебной команды: отступ, длина строки, импорты, имена, инструмент проверки. Объясните, как другой участник воспроизведёт тот же результат вне вашей IDE.
- Сравните GoogleImportOrder и SpringImportOrder: назовите три различия оформления. Затем объясните, обязано ли ваше приложение на Spring Boot использовать табуляцию и почему Spring Java Format не заменяет проверку порядка импортов.
Разбор: на граничной сумме 2000 правильный код возвращает 0, а после ошибочной замены — 199. Для -1 обе исходные версии выбрасывают IllegalArgumentException с одинаковым сообщением. Повторное присваивание final-ссылке запрещено компилятором, но изменение списка разрешено. Настройки команды должны храниться рядом с проектом и проверяться согласованными инструментами. В последнем задании различаются отступы, положение else и группировка импортов. Профиль приложения выбирает его команда, а форматирование и порядок импортов требуют согласованных, но разных проверок.
Проверка понимания: коллега исправил отступы, а CI всё ещё жалуется на имя константы и падающий тест пороговой суммы. Какие действия нужны? Переименовать элемент согласно принятому соглашению, обновить его обращения и отдельно исправить условие по требованиям; повторное форматирование не решит обе задачи.
Частые вопросы
Есть ли единственный обязательный стандарт оформления Java?
Правила языка задаёт спецификация, а профиль оформления выбирает проект. Соглашения Sun/Oracle, Google Java Style и Spring Framework различаются отступами, импортами и переносами. Для существующего репозитория ориентируйтесь на его документированные правила и конфигурацию проверки, не смешивая случайные советы.
Достаточно ли включить автоматическое форматирование в IDE?
Нет: оно решает только часть задачи. Имена должны отражать смысл, комментарии — соответствовать контракту, а условия — проходить тесты. Для совместной работы нужны общие настройки, проверка стиля в сборке и просмотр diff после автоматических исправлений.
Источники
- Oracle: архив Code Conventions for the Java Programming Language
- Oracle Code Conventions: Naming Conventions
- Oracle Code Conventions: Indentation
- Google Style Guides: руководства для разных языков
- Google Java Style Guide
- Spring Framework: Code Style
- Spring Java Format
- Oracle Code Conventions: Statements
- Oracle Code Conventions: File Organization
- Java Language Specification 25: Packages and Modules
- Java Language Specification 25: final Variables
- Javadoc: Documentation Comment Specification, JDK 25
- EditorConfig
- Checkstyle: доступные проверки
- google-java-format