Руководство по стилю Котлина

Этот документ представляет собой полное определение стандартов кодирования Android для исходного кода Google на языке программирования Kotlin. Исходный файл Kotlin описывается как выполненный в стиле Google Android тогда и только тогда, когда он соответствует изложенным здесь правилам.

Как и в других руководствах по стилю программирования, рассматриваемые вопросы охватывают не только эстетические вопросы форматирования, но и другие типы соглашений или стандартов кодирования. Однако этот документ фокусируется в первую очередь на незыблемых правилах, которым мы следуем повсеместно, и избегает рекомендаций, которые не могут быть четко выполнены (будь то человеком или инструментом).

Последнее обновление: 6 сентября 2023 г.

Исходные файлы

Все исходные файлы должны быть в кодировке UTF-8.

Именование

Если исходный файл содержит только один класс верхнего уровня, имя файла должно отражать имя с учетом регистра плюс расширение .kt . В противном случае, если исходный файл содержит несколько объявлений верхнего уровня, выберите имя, описывающее содержимое файла, примените PascalCase (camelCase допускается, если имя файла имеет множественное число) и добавьте расширение .kt .

// MyClass.kt
class MyClass { }
// Bar.kt
class Bar { }
fun Runnable.toBar(): Bar = // …
// Map.kt
fun <T, O> Set<T>.map(func: (T) -> O): List<O> = // …
fun <T, O> List<T>.map(func: (T) -> O): List<O> = // …
// extensions.kt
fun MyClass.process() = // …
fun MyResult.print() = // …

Специальные символы

Пробельные символы

Помимо последовательности разделителя строки, символ горизонтального пробела ASCII (0x20) является единственным символом пробела, который появляется где-либо в исходном файле. Это подразумевает, что:

  • Все остальные пробельные символы в строковых и символьных литералах экранируются.
  • Символы табуляции не используются для отступов.

Специальные escape-последовательности

Для любого символа, который имеет специальную escape-последовательность ( \b , \n , \r , \t , \' , \" , \\ и \$ ), используется эта последовательность, а не соответствующий Unicode (например, \u000a ) побег.

Символы, отличные от ASCII

Для остальных символов, отличных от ASCII, используется либо фактический символ Юникода (например, ), либо эквивалентный escape-символ Юникода (например, \u221e ). Выбор зависит только от того, что облегчает чтение и понимание кода. Использование escape-символов в Юникоде не рекомендуется для печатаемых символов в любом месте и категорически не рекомендуется за пределами строковых литералов и комментариев.

Пример Обсуждение
val unitAbbrev = "μs" Лучшее: совершенно ясно даже без комментариев.
val unitAbbrev = "\u03bcs" // μs Плохо: нет смысла использовать escape с печатным символом.
val unitAbbrev = "\u03bcs" Плохо: читатель понятия не имеет, что это такое.
return "\ufeff" + content Хорошо: используйте escape-символы для непечатаемых символов и при необходимости комментируйте.

Структура

Файл .kt включает в себя следующее:

  • Заголовок об авторских правах и/или лицензии (необязательно)
  • Аннотации на уровне файла
  • Заявление о пакете
  • Операторы импорта
  • Объявления верхнего уровня

Каждый из этих разделов разделяется ровно одной пустой строкой.

Если заголовок об авторском праве или лицензии присутствует в файле, его следует поместить непосредственно вверху многострочного комментария.

/*
 * Copyright 2017 Google, Inc.
 *
 * ...
 */
 

Не используйте комментарии в стиле KDoc или однострочные комментарии.

/**
 * Copyright 2017 Google, Inc.
 *
 * ...
 */
// Copyright 2017 Google, Inc.
//
// ...

Аннотации на уровне файла

Аннотации с целью использования сайта «файл» размещаются между любым комментарием заголовка и объявлением пакета.

Заявление о пакете

Оператор пакета не подлежит никакому ограничению столбцов и никогда не переносится на строки.

Операторы импорта

Операторы импорта классов, функций и свойств группируются в один список и сортируются по ASCII.

Импорт подстановочных знаков (любого типа) не допускается.

Подобно оператору пакета, операторы импорта не подлежат ограничению столбцов и никогда не переносят строки.

Объявления верхнего уровня

Файл .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 Branch, 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> 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 .

Имена функций

Имена функций пишутся в верблюжьем регистре и обычно представляют собой глаголы или глагольные фразы. Например, sendMessage или stop .

В именах тестовых функций разрешено использовать символы подчеркивания для разделения логических компонентов имени.

@Test fun pop_emptyStack() {
    // …
}

Функции, помеченные @Composable и возвращающие Unit , имеют регистр PascalCased и называются существительными, как если бы они были типами.

@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_JOINER = Joiner.on(',') // Joiner is immutable
val EMPTY_ARRAY = arrayOf()

Эти имена обычно представляют собой существительные или именной группы.

Значения констант могут быть определены только внутри object или в виде объявления верхнего уровня. Значения, в противном случае отвечающие требованию константы, но определенные внутри class должны использовать неконстантное имя.

Константы, являющиеся скалярными значениями, должны использовать модификатор const .

Непостоянные имена

Непостоянные имена пишутся в верблюжьем регистре. Они применяются к свойствам экземпляра, локальным свойствам и именам параметров.

val variable = "var"
val nonConstScalar = "non-const"
val mutableCollection: MutableSet = 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». Для повышения предсказуемости используйте следующую схему.

Начиная с прозаической формы имени:

  1. Преобразуйте фразу в простой ASCII и удалите все апострофы. Например, «алгоритм Мюллера» может стать «алгоритмом Мюллера».
  2. Разделите этот результат на слова, разделив их пробелами и оставшимися знаками препинания (обычно дефисами). Рекомендуется: если какое-либо слово уже используется в обычном использовании в верблюжьем регистре, разделите его на составные части (например, «AdWords» становится «рекламными словами»). Обратите внимание, что такое слово, как «iOS», само по себе не употребляется в верблюжьем регистре; это противоречит любым соглашениям, поэтому эта рекомендация неприменима.
  3. Теперь все строчные буквы (включая аббревиатуры), а затем выполните одно из следующих действий:
    • Первый символ каждого слова регистрируется заглавными буквами для получения регистра Паскаля.
    • Прописными буквами первый символ каждого слова, кроме первого, дающего верблюжий регистр.
  4. Наконец, объедините все слова в один идентификатор.

Обратите внимание, что регистр исходных слов почти полностью не учитывается.

Прозаическая форма Правильный Неправильный
«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».

Неуместно ссылаться на это исключение, чтобы оправдать упущение соответствующей информации, которую может потребоваться знать обычному читателю. Например, для функции с именем getCanonicalName или свойства с именем canonicalName не опускайте документацию (по той причине, что в ней будет указано только /** Returns the canonical name. */ ), если типичный читатель может понятия не иметь, что это за термин. значит "каноническое имя"!

Исключение: переопределения

KDoc не всегда присутствует в методе, который переопределяет метод супертипа.