ЯдроКодаподготовка к экзаменам
Учебная платформа

Загружаем материалы

Подготавливаем материалы и навигацию по разделу.

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: ссылки и различия

Начинайте с первоисточника, чтобы пересказ не подменял правила конкретного проекта:

РешениеSun/Oracle JCCGoogle Java StyleSpring 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, recordUpperCamelCaseShippingCost, PricePolicy, OrderStatus
МетодlowerCamelCase, действие или запросcalculateTotal, hasDiscount
Поле, параметр, локальная переменнаяlowerCamelCasesubtotalInCents, customerName
Настоящая константа классаUPPER_SNAKE_CASEMAX_RETRY_COUNT
Элемент enumОбычно UPPER_SNAKE_CASEREADY_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 = true

EditorConfig помогает редакторам согласовать базовые настройки файлов. Он сам не проверяет имена классов, не добавляет Javadoc и не определяет весь алгоритм переносов Java. Наличие свойства в файле также не гарантирует его поддержку каждым редактором: проверьте интеграцию используемого инструмента.

Форматтер автоматически приводит расположение кода к выбранному профилю. Checkstyle проверяет настроенные правила, включая именование, импорты и оформление. Успешная проверка означает только соблюдение включённых правил, а не правильность бизнес-логики. Набор правил описан в документации Checkstyle.

Если команда выбрала Google Java Style, один из вариантов — google-java-format. Его алгоритм имеет ограниченную настраиваемость: это не универсальный форматтер под любой набор отступов и переносов. Не запускайте его настройки по умолчанию поверх нашего профиля из четырёх пробелов, ожидая сохранения этого профиля. Выберите совместимую конфигурацию инструментов и закрепите их версии в сборке.

Практический порядок работы: применить форматтер локально, посмотреть diff, запустить проверку стиля, скомпилировать и выполнить тесты. В CI обычно используют проверку без исправления файлов, чтобы замечания не зависели от редактора автора. Команды зависят от уже настроенных Maven/Gradle-плагинов; универсальную задачу, которой нет в проекте, придумывать не нужно. Массовое переоформление полезно отделять от изменения логики: так проще проверить обе части.

Практика: проведите небольшое ревью

  1. Сохраните обе версии ShippingCost в разных папках и до запуска предскажите результаты для 0, 1999, 2000 и 3000. Убедитесь, что стиль изменился, а наблюдаемое поведение на этих входах сохранилось.
  2. Измените порог в after с >= на >. Какой контрольный пример заметит ошибку? Верните исходное условие и поясните, почему форматтер не должен исправлять его за вас.
  3. Добавьте -1 во входной массив. Проверьте тип исключения и сообщение. Не считайте отсутствие строки с ответом ошибкой оформления: это выбранный контракт отрицательного входа.
  4. В FinalReferenceDemo попробуйте после инициализации присвоить outputNames новый ArrayList. Сравните ошибку компиляции с успешно выполненным add. Затем восстановите рабочую программу.
  5. Запишите короткое соглашение своей учебной команды: отступ, длина строки, импорты, имена, инструмент проверки. Объясните, как другой участник воспроизведёт тот же результат вне вашей IDE.
  6. Сравните 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 после автоматических исправлений.

Источники