Этот документ представляет собой полное определение стандартов кодирования Google для Android в отношении исходного кода на языке программирования 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. // // ...
Аннотации на уровне файлов
Аннотации с целевым объектом use-site "file" размещаются между любым заголовочным комментарием и объявлением пакета.
Заявление о упаковке
Описание пакета не подпадает под какие-либо ограничения по количеству столбцов и никогда не переносится на следующую строку.
Импорт операторов
Операторы импорта для классов, функций и свойств сгруппированы в один список и отсортированы по 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 - Командные строки в комментарии, которые можно скопировать и вставить в командную оболочку.
Где сделать перерыв
Главная задача переноса строк: предпочтительно разбивать строку на более высоком синтаксическом уровне. Также:
- Когда строка разрывается по имени оператора или инфиксной функции, разрыв происходит после имени оператора или инфиксной функции.
- При разрыве строки в следующих «оператороподобных» символах разрыв происходит перед этим символом:
- Разделитель точек (
.,?.). - Два двоеточия в ссылке на элемент (
::).
- Разделитель точек (
- Имя метода или конструктора остается прикрепленным к открывающей скобке
(), следующей за ним. - Запятая (
,) остается прикрепленной к предшествующему ей токену. - Стрелка лямбда (
->) остается прикрепленной к списку аргументов, предшествующему ей.
Функции
Если сигнатура функции не помещается на одной строке, разбейте каждое объявление параметра на отдельную строку. Параметры, определенные в этом формате, должны иметь один отступ (+4). Закрывающая скобка ( ) ) и тип возвращаемого значения размещаются на отдельной строке без дополнительного отступа.
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: все буквы заглавные, слова разделены подчеркиваниями. Но что же такое константа, собственно?
Constants are val properties with no custom get function, whose contents are deeply immutable, and whose functions have no detectable side-effects. This includes immutable types and immutable collections of immutable types as well as scalars and string if marked as const . If any of an instance's observable state can change, it is not a constant. Merely intending to never mutate the object is not enough.
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-код и удалите все апострофы. Например, «алгоритм Мюллера» может стать «алгоритм Мюллера».
- Divide this result into words, splitting on spaces and any remaining punctuation (typically hyphens). Recommended: if any word already has a conventional camel-case appearance in common usage, split this into its constituent parts (eg, “AdWords” becomes “ad words”). Note that a word such as “iOS” is not really in camel case per se; it defies any convention, so this recommendation does not apply.
- Теперь переведите все слова (включая аббревиатуры) в нижний регистр, а затем выполните одно из следующих действий:
- Чтобы получить регистр Pascal, первую букву каждого слова следует перевести в верхний регистр.
- Чтобы получить верблюжий регистр, первый символ каждого слова следует перевести в верхний регистр.
- Наконец, объедините все слова в один идентификатор.
Обратите внимание, что регистр исходных слов практически полностью игнорируется.
| Прозаическая форма | Правильный | Неверно |
|---|---|---|
| "XML HTTP-запрос" | XmlHttpRequest | XMLHTTPRequest |
| «новый идентификатор клиента» | newCustomerId | newCustomerID |
| «внутренний секундомер» | innerStopwatch | innerStopWatch |
| "Поддерживает 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 , и они никогда не отображаются с пустым описанием. Если блочный тег не помещается на одной строке, строки продолжения отступают на 4 пробела от позиции символа @ .
Краткий обзор
Каждый блок KDoc начинается с краткого резюме. Этот фрагмент очень важен: это единственная часть текста, которая появляется в определенных контекстах, таких как индексы классов и методов.
Это фрагмент — именная или глагольная группа, а не законченное предложение. Он не начинается со слов « A `Foo` is a... » или « This method returns... », и ему не обязательно образовывать законченное повелительное предложение, например, « Save the record. ». Однако фрагмент пишется с заглавной буквы и расставляется знаками препинания так же, как и законченное предложение.
Использование
Как минимум, KDoc присутствует для каждого public типа и каждого public или protected элемента такого типа, за некоторыми исключениями, указанными ниже.
Исключение: Функции, не требующие пояснений.
Функция KDoc является необязательной для «простых, очевидных» функций, таких как getFoo , и свойств, таких как foo , в тех случаях, когда действительно нечего добавить, кроме как «Возвращает foo».
It is not appropriate to cite this exception to justify omitting relevant information that a typical reader might need to know. For example, for a function named getCanonicalName or property named canonicalName , don't omit its documentation (with the rationale that it would say only /** Returns the canonical name. */ ) if a typical reader may have no idea what the term "canonical name" means!
Исключение: переопределения
Документация KDoc не всегда присутствует в методе, который переопределяет метод супертипа.