В этом документе приведены полные определения стандартов кодирования Google для исходного кода на языке программирования Kotlin. Исходный файл Kotlin считается написанным в стиле Google Android, если и только если он соответствует приведенным ниже правилам.
Как и в других руководствах по стилю программирования, в этом документе рассматриваются не только эстетические вопросы форматирования, но и другие типы соглашений и стандартов кодирования. Однако в этом документе основное внимание уделяется строгим правилам, которые мы применяем ко всем сайтам, и не даются советы, которые невозможно проверить (ни вручную, ни с помощью инструментов).
Исходные файлы
Все исходные файлы должны быть в кодировке UTF-8.
Название
Если исходный файл содержит только один класс верхнего уровня, его название должно соответствовать названию класса с учетом регистра и иметь расширение .kt. В противном случае, если исходный файл содержит несколько объявлений верхнего уровня, выберите название, описывающее содержимое файла, примените PascalCase (camelCase допустим, если название файла указано во множественном числе) и добавьте расширение .kt.
// MyClass.kt class MyClass { }
// Bar.kt class Bar { } fun Runnable.toBar(): Bar = Bar()
// Map.kt fun <T, O> Set<T>.map(func: (T) -> O): List<O> = emptyList() fun <T, O> List<T>.map(func: (T) -> O): List<O> = emptyList()
// extensions.kt fun MyClass.process() = { /* ... */ } fun MyResult.print() = { /* ... */ }
Специальные символы
Символы пробелов
Помимо последовательности символов конца строки, горизонтальный пробел ASCII (0x20) – единственный пробельный символ, который может встречаться в исходном файле. Это означает, что:
- Все остальные пробельные символы в строковых и символьных литералах экранируются.
- Символы табуляции не используются для отступов.
Специальные управляющие последовательности
Для любого символа, у которого есть специальная последовательность экранирования (\b, \n, \r, \t, \', \", \\ и \$), используется именно она, а не соответствующая последовательность экранирования Unicode (например, \u000a).
Символы в кодировке, отличной от ASCII
Для остальных символов, не относящихся к ASCII, используется либо сам символ Unicode (например, ∞), либо эквивалентный управляющий символ Unicode (например, \u221e).
Выбор зависит только от того, что делает код более понятным и читаемым.
Использование экранированных последовательностей Unicode для печатаемых символов в любом месте кода не рекомендуется, а за пределами строковых литералов и комментариев – строго не рекомендуется.
| Пример | Обсуждение |
|---|---|
val unitAbbrev = "μs" |
Лучший вариант: все понятно без комментариев. |
val unitAbbrev = "\u03bcs" // μs |
Плохо: нет причин использовать экранирование с печатаемым символом. |
val unitAbbrev = "\u03bcs" |
Низкое качество: читатель не понимает, что это такое. |
return "\ufeff" + content |
Хорошо: используйте экранирование для непечатаемых символов и при необходимости добавляйте комментарии. |
Структура
Файл .kt состоит из следующих частей (в указанном порядке):
- Заголовок с информацией об авторских правах и/или лицензии (необязательно)
- Аннотации на уровне файла
- Заявление о пакете
- Операторы импорта
- Декларации верхнего уровня
Каждый из этих разделов отделяется от другого пустой строкой.
Авторские права / лицензия
Если в файле должен быть заголовок с информацией об авторских правах или лицензии, его следует разместить в самом начале в многострочном комментарии.
/* * Copyright 2017 Google, Inc. * * ... */
Не используйте комментарии в стиле KDoc или однострочные комментарии.
/** * Copyright 2017 Google, Inc. * * ... */
// Copyright 2017 Google, Inc. // // ...
Аннотации на уровне файла
Аннотации с целевым сайтом размещаются между любым комментарием в заголовке и объявлением пакета.
Заявление о пакете
В выписке по пакету нет ограничений на количество столбцов, и текст никогда не переносится на новую строку.
Операторы импорта
Операторы импорта для классов, функций и свойств сгруппированы в один список и отсортированы по ASCII.
Импорт с использованием подстановочных знаков (любого типа) не допускается.
Как и в случае с оператором package, операторы import не ограничены по количеству столбцов и никогда не переносятся на новую строку.
Декларации верхнего уровня
В файле .kt можно объявить один или несколько типов, функций, свойств или псевдонимов типов на верхнем уровне.
Содержимое файла должно быть посвящено одной теме. Например, это может быть один общедоступный тип или набор функций расширения, выполняющих одну и ту же операцию с несколькими типами получателей. Не связанные между собой декларации должны быть разделены на отдельные файлы, а количество общедоступных деклараций в одном файле должно быть минимальным.
Явных ограничений на количество или порядок содержимого файла нет.
Исходные файлы обычно читаются сверху вниз, поэтому порядок объявлений должен соответствовать тому, что объявления выше помогают понять объявления ниже. Разные файлы могут содержать данные в разном порядке. Аналогично, один файл может содержать 100 свойств, другой – 10 функций, а третий – один класс.
Важно, чтобы в каждом файле использовался какой-либо логический порядок, который его автор мог бы объяснить, если бы его спросили. Например, новые функции не просто добавляются в конец файла, поскольку в этом случае они будут упорядочены по дате добавления, а не логически.
Порядок участников курса
Порядок членов класса подчиняется тем же правилам, что и объявления верхнего уровня.
Форматирование
Брекеты
Фигурные скобки не требуются для ветвей when и выражений if, если в них не более одной ветви else и они помещаются на одной строке.
if (string.isEmpty()) return val result = if (string.isEmpty()) DEFAULT_VALUE else string when (value) { 0 -> return // … }
В остальных случаях фигурные скобки обязательны для любых операторов и выражений if, for, when, do и while, даже если тело пустое или содержит только один оператор.
if (string.isEmpty()) return // WRONG! if (string.isEmpty()) { return // Okay } if (string.isEmpty()) return // WRONG else doLotsOfProcessingOn(string, otherParametersHere) if (string.isEmpty()) { return // Okay } else { doLotsOfProcessingOn(string, otherParametersHere) }
Непустые блоки
Фигурные скобки в стиле Кернигана и Ричи ("египетские скобки") используются для непустых блоков и конструкций, похожих на блоки:
- Перед открывающей фигурной скобкой нет переноса строки.
- Перенос строки после открывающей фигурной скобки.
- Перенос строки перед закрывающей фигурной скобкой.
- Перенос строки после закрывающей фигурной скобки только в том случае, если эта скобка завершает оператор или тело функции, конструктора или именованного класса.
Например, нельзя переносить строку после фигурной скобки, если за ней следует
elseили запятая.
return Runnable { while (condition()) { foo() } }
return object : MyClass() { override fun foo() { if (condition()) { try { something() } catch (e: ProblemException) { recover() } } else if (otherCondition()) { somethingElse() } else { lastThing() } } }
Ниже приведены некоторые исключения для классов перечислений.
Пустые блоки
Пустой блок или конструкция, похожая на блок, должны быть оформлены в стиле K&R.
try { doSomething() } catch (e: Exception) {} // WRONG!
try { doSomething() } catch (e: Exception) { } // Okay
Выражения
Условный оператор if/else, используемый в качестве выражения, может не содержать фигурных скобок, если все выражение помещается на одной строке.
val value = if (string.isEmpty()) 0 else 1 // Okay
val value = if (string.isEmpty()) // WRONG! 0 else 1
val value = if (string.isEmpty()) { // Okay 0 } else { 1 }
Отступ
Каждый раз, когда открывается новый блок или конструкция, похожая на блок, отступ увеличивается на четыре пробела. Когда блок заканчивается, отступ возвращается к предыдущему уровню. Уровень отступа применяется ко всему блоку, включая код и комментарии.
По одному утверждению в строке
После каждого утверждения следует перенос строки. Точки с запятой не используются.
Перенос строк
Код не должен превышать 100 символов в столбце. За исключением случаев, описанных ниже, все строки, превышающие это ограничение, должны быть перенесены, как описано ниже.
Исключения
- Строки, в которых невозможно соблюсти ограничение на количество столбцов (например, длинный URL в KDoc).
- Заявления
packageиimport - Командные строки в комментариях, которые можно скопировать и вставить в оболочку.
Где можно сделать перерыв
Основное правило переноса строк: переносите на более высоком синтаксическом уровне. Также:
- Если строка разбивается на операторе или названии инфиксной функции, разрыв происходит после оператора или названия инфиксной функции.
- Если строка разбивается на одном из следующих символов, похожих на операторы, то разрыв происходит перед символом:
- Разделитель в виде точки (
.,?.). - Двоеточия в ссылке на участника (
::).
- Разделитель в виде точки (
- Название метода или конструктора остается прикрепленным к открывающей скобке (
(), которая следует за ним. - Запятая (
,) остается прикрепленной к токену, который ей предшествует. - Стрелка лямбда-функции (
->) остается прикрепленной к списку аргументов, который предшествует ей.
Функции
Если сигнатура функции не помещается в одну строку, каждый параметр должен быть объявлен в отдельной строке. Параметры, определенные в этом формате, должны иметь отступ в четыре пробела. Закрывающая скобка ()) и тип возвращаемого значения размещаются на отдельной строке без дополнительного отступа.
fun <T> Iterable<T>.joinToString( separator: CharSequence = ", ", prefix: CharSequence = "", postfix: CharSequence = "" ): String { // ... }
Функции выражений
Если функция содержит только одно выражение, ее можно представить в виде функции-выражения.
override fun toString(): String { return "Hey" }
override fun toString(): String = "Hey"
Свойства
Если инициализатор свойства не помещается на одной строке, перенесите его после знака равенства (=) и добавьте отступ.
private val defaultCharset: Charset? = EncodingRegistry.getInstance().getDefaultCharsetForPropertiesFiles(file)
Свойства, в которых объявлены функции get и/или set, должны быть размещены на отдельных строках с обычным отступом (+4). Форматируйте их по тем же правилам, что и функции.
var directory: File? = null set(value) { // … }
val defaultExtension: String get() = "kt"
Пробел
Тематика
Появится пустая строка:
- Между последовательными элементами класса: свойствами, конструкторами, функциями, вложенными классами и т. д.
- Исключение. Пустая строка между двумя последовательными свойствами (без другого кода между ними) не обязательна. Пустые строки используются для создания логических групп свойств и связывания свойств с их базовыми свойствами, если они есть.
- Исключение. Пустые строки между константами перечисления рассматриваются ниже.
- Между операторами при необходимости, чтобы организовать код в логические подразделы.
- Необязательно перед первым оператором в функции, перед первым элементом класса или после последнего элемента класса (не рекомендуется и не запрещается).
- Как требуется в других разделах этого документа (например, в разделе Структура).
Допускается использование нескольких пустых строк подряд, но это не рекомендуется и не требуется.
Горизонтальный
Помимо случаев, когда пробел требуется по правилам языка или стиля, а также в строковых литералах, комментариях и KDoc, одиночный пробел ASCII используется только в следующих случаях:
- Разделять любое зарезервированное слово, например
if,forилиcatch, и следующую за ним в той же строке открывающую скобку (().// WRONG! for(i in 0..1) { }
// Okay for (i in 0..1) { }
- Отделение любого зарезервированного слова, например
elseилиcatch, от закрывающей фигурной скобки (}), которая предшествует ему в строке.// WRONG! }else { }
// Okay } else { }
-
Перед любой открывающей фигурной скобкой (
{).// WRONG! if (list.isEmpty()){ }
// Okay if (list.isEmpty()) { }
-
С обеих сторон любого бинарного оператора.
// WRONG! val two = 1+1
Это также относится к следующим символам, похожим на операторы:// Okay val two = 1 + 1
- стрелка в лямбда-выражении (
->);// WRONG! ints.map { value->value.toString() }
// Okay ints.map { value -> value.toString() }
-
два двоеточия (
::) в ссылке на участника.// WRONG! val toString = Any :: toString
// Okay val toString = Any::toString
-
точку-разделитель (
.).// WRONG it . toString()
// Okay it.toString()
-
оператор диапазона (
..).// WRONG for (i in 1 .. 4) { print(i) }
// Okay for (i in 1..4) { print(i) }
- стрелка в лямбда-выражении (
-
Перед двоеточием (
:), только если оно используется в объявлении класса для указания базового класса или интерфейсов, или если оно используется в конструкцииwhereдля ограничений универсальных шаблонов.// WRONG! class Foo: Runnable
// Okay class Foo : Runnable
// WRONG fun <T: Comparable> max(a: T, b: T)
// Okay fun <T : Comparable<T>> max(a: T, b: T)
// WRONG fun <T> max(a: T, b: T) where T: Comparable<T>
// Okay fun <T> max(a: T, b: T) where T : Comparable<T> {}
-
После запятой (
,) или двоеточия (:).// WRONG! val oneAndTwo = listOf(1,2)
// Okay val oneAndTwo = listOf(1, 2)
// WRONG! class Foo :Runnable
// Okay class Foo : Runnable
-
С обеих сторон от двойной косой черты (
//), которая начинает комментарий в конце строки. Здесь можно использовать несколько пробелов, но это необязательно.// WRONG! var debugging = false//disabled by default
// Okay var debugging = false // disabled by default
Это правило никогда не интерпретируется как требующее или запрещающее дополнительное пространство в начале или конце строки; оно относится только к внутреннему пространству.
Определенные конструкции
Классы перечислений
Перечисление без функций и документации по константам можно отформатировать в одну строку.
enum class Answer { YES, NO, MAYBE }
Если константы в перечислении размещены на разных строках, между ними не требуется пустая строка, за исключением случаев, когда они определяют тело.
enum class Answer { YES, NO, MAYBE { override fun toString() = """¯\_(ツ)_/¯""" } }
Поскольку перечисления являются классами, к ним применяются все правила форматирования классов.
Аннотации
Аннотации для членов класса или типов размещаются на отдельных строках непосредственно перед аннотируемой конструкцией.
@Retention(SOURCE) @Target(FUNCTION, PROPERTY_SETTER, FIELD) annotation class Global
Аннотации без аргументов можно размещать в одной строке.
@JvmField @Volatile var disposable: Disposable? = null
Если аннотация без аргументов всего одна, ее можно разместить на той же строке, что и объявление.
@Volatile var disposable: Disposable? = null @Test fun selectAll() { // … }
Синтаксис @[...] можно использовать только с явным целевым сайтом и только для объединения двух или более аннотаций без аргументов в одной строке.
@field:[JvmStatic Volatile] var disposable: Disposable? = null
Неявные типы возвращаемых значений и объектов
Если тело функции-выражения или инициализатор свойства является скалярным значением или тип возвращаемого значения можно определить по телу функции, то тип возвращаемого значения можно опустить.
override fun toString(): String = "Hey" // becomes override fun toString() = "Hey"
private val ICON: Icon = IconLoader.getIcon("/icons/kotlin.png") // becomes private val ICON = IconLoader.getIcon("/icons/kotlin.png")
При написании библиотеки сохраняйте явное объявление типа, если оно является частью общедоступного API.
Название
Идентификаторы содержат только буквы и цифры ASCII, а в некоторых случаях, указанных ниже, – символы подчеркивания. Таким образом, каждое допустимое название идентификатора соответствует регулярному выражению \w+.
Специальные префиксы или суффиксы, как в примерах name_, mName, s_name и kName, не используются, за исключением случаев, когда речь идет о вспомогательных свойствах (см. раздел Вспомогательные свойства).
Названия пакетов
Названия пакетов пишутся строчными буквами, а слова в них просто соединяются без символов подчеркивания.
// Okay package com.example.deepspace // WRONG! package com.example.deepSpace // WRONG! package com.example.deep_space
Названия типов
Названия классов пишутся в стиле PascalCase и обычно представляют собой существительные или словосочетания с существительными. Примеры: Character или ImmutableList. Названия интерфейсов могут быть существительными или словосочетаниями с существительными (например, List), но иногда это прилагательные или словосочетания с прилагательными (например, Readable).
Названия тестовых классов начинаются с названия класса, который они тестируют, и заканчиваются символами Test. Примеры: HashTest или HashIntegrationTest.
Названия функций
Названия функций пишутся в стиле camelCase и обычно представляют собой глаголы или глагольные фразы. Примеры: sendMessage или stop.
В названиях тестовых функций можно использовать символы подчеркивания для разделения логических компонентов.
@Test fun pop_emptyStack() { // … }
Функции, аннотированные с помощью @Composable и возвращающие Unit, записываются в стиле PascalCase и называются существительными, как если бы они были типами.
@Composable fun NameTag(name: String) { // … }
В названиях функций не должно быть пробелов, поскольку это не поддерживается на некоторых платформах (в частности, в Android).
// WRONG! fun `test every possible case`() {} // OK fun testEveryPossibleCase() {}
Названия констант
Названия констант пишутся в формате UPPER_SNAKE_CASE: все буквы заглавные, слова разделены подчеркиваниями. Но что такое константа?
Константы – это свойства val без пользовательской функции get, содержимое которых является глубоко неизменяемым, а функции не имеют заметных побочных эффектов. К ним относятся неизменяемые типы и неизменяемые коллекции неизменяемых типов, а также скаляры и строки, если они отмечены как const. Если наблюдаемое состояние экземпляра может измениться, то он не является константой. Простого намерения не изменять объект недостаточно.
const val NUMBER = 5 val NAMES = listOf("Alice", "Bob") val AGES = mapOf("Alice" to 35, "Bob" to 32) val COMMA_JOINED = NAMES.joinToString(", ") val EMPTY_ARRAY = arrayOf<SomeMutableType>()
Обычно это существительные или именные группы.
Постоянные значения можно определять только внутри элемента object или как объявление верхнего уровня. Значения, которые в противном случае соответствовали бы требованиям к константам, но определены внутри class, должны использовать непостоянное имя.
Константы, которые являются скалярными значениями, должны использовать const
модификатор.
Непостоянные имена
Названия, не являющиеся константами, пишутся в стиле camelCase. Они применяются к свойствам экземпляра, локальным свойствам и названиям параметров.
val variable = "var" val nonConstScalar = "non-const" val mutableCollection: MutableSet<String> = HashSet() val mutableElements = listOf(mutableInstance) val mutableValues = mapOf("Alice" to mutableInstance, "Bob" to mutableInstance2) val logger = Logger.getLogger(MyClass::class.java.name) val nonEmptyArray = arrayOf("these", "can", "change")
Обычно это существительные или именные группы.
Свойства поддержки
Если требуется вспомогательный ресурс, его название должно в точности совпадать с названием реального ресурса, но с подчеркиванием в начале.
private var _table: Map<String, Int>? = null val table: Map<String, Int> get() { if (_table == null) { _table = HashMap() } return _table ?: throw AssertionError() }
Как ввести названия переменных
Переменные типа имеют названия одного из двух стилей:
- Одна заглавная буква, за которой может следовать одна цифра (например,
E,T,X,T2). - Название в форме, используемой для курсов, за которым следует заглавная буква
T(например,RequestT,FooBarT).
Верблюжий регистр
Иногда фразу на английском языке можно преобразовать в верблюжий регистр несколькими способами, например если в ней есть аббревиатуры или необычные конструкции, такие как "IPv6" или "iOS". Чтобы сделать работу с ярлыками более предсказуемой, используйте следующую схему.
Начните с прозаической формы имени:
- Преобразуйте фразу в обычный ASCII-код и удалите все апострофы. Например, "алгоритм Мюллера" может стать "алгоритмом Мюллера".
- Разделите полученный результат на слова, используя в качестве разделителей пробелы и оставшиеся знаки препинания (обычно дефисы). Рекомендация. Если какое-либо слово обычно пишется в верблюжьем регистре, разделите его на составные части (например, AdWords станет ad words). Обратите внимание, что слово iOS не относится к верблюжьему регистру, поскольку не соответствует никаким правилам, поэтому эта рекомендация к нему не применяется.
- Затем переведите все буквы в нижний регистр (включая аббревиатуры) и выполните одно из следующих действий:
- Первый символ каждого слова будет преобразован в заглавный.
- Первый символ каждого слова, кроме первого, будет написан с заглавной буквы.
- Наконец, объедините все слова в один идентификатор.
Обратите внимание, что регистр исходных слов почти не учитывается.
| Проза | Правильно | Неправильно |
|---|---|---|
| "XML Http Request" | XmlHttpRequest |
XMLHTTPRequest |
| "новый идентификатор клиента" | newCustomerId |
newCustomerID |
| "внутренний секундомер" | innerStopwatch |
innerStopWatch |
| "supports IPv6 on iOS" (поддерживает IPv6 в iOS). | supportsIpv6OnIos |
supportsIPv6OnIOS |
| "Импорт видео с YouTube" | YouTubeImporter |
YoutubeImporter* |
(* Допустимо, но не рекомендуется.)
Документация
Форматирование
Ниже приведен пример базового форматирования блоков KDoc:
/** * Multiple lines of KDoc text are written here, * wrapped normally… */ fun method(arg: String) { // … }
…или в этом примере с одной строкой:
/** An especially short bit of KDoc. */
Базовая форма всегда приемлема. Однострочная форма может быть использована, если весь блок KDoc (включая маркеры комментариев) помещается в одну строку. Обратите внимание, что это относится только к случаям, когда нет блокирующих тегов, например @return.
Абзацы
Между абзацами и перед группой тегов блоков (если она есть) должна быть одна пустая строка, то есть строка, содержащая только выровненную по левому краю звездочку (*).
Как заблокировать теги
Стандартные теги блокировки используются в следующем порядке: @constructor, @receiver, @param, @property, @return, @throws, @see. Они никогда не появляются с пустым описанием.
Если тег блока не помещается на одной строке, то строки продолжения сдвигаются на четыре пробела от позиции символа @.
Фрагмент пересказа
Каждый блок KDoc начинается с краткого фрагмента. Этот фрагмент очень важен, поскольку это единственная часть текста, которая появляется в некоторых контекстах, например в индексах классов и методов.
Это фрагмент, а не полное предложение.
Оно не начинается с "A `Foo` is a..." или "This method returns..." и не обязательно должно представлять собой законченное повелительное предложение, как в случае с "Save the record.". Однако фрагмент начинается с заглавной буквы и содержит знаки препинания, как если бы он был полным предложением.
Использование
Как минимум, KDoc присутствует для каждого типа public и каждого элемента public или protected такого типа, за исключением нескольких случаев, описанных ниже.
Исключение: функции, которые не требуют пояснений
KDoc не обязателен для простых и очевидных функций, таких как getFoo, и свойств, таких как foo, если действительно нечего добавить, кроме "Возвращает foo".
Не следует ссылаться на это исключение, чтобы оправдать отсутствие важной информации, которая может понадобиться читателю. Например, если функция называется getCanonicalName, а свойство – canonicalName, не удаляйте их документацию (с обоснованием, что в ней будет только /** Returns the canonical name. */), если обычный читатель может не знать, что такое каноническое имя.
Исключение: переопределения
KDoc не всегда присутствует в методе, который переопределяет метод супертипа.