Соглашения о написании кода
Общепринятые и простые в применении соглашения о написании кода важны для любого языка программирования. Здесь приведены рекомендации по стилю кода и организации кода для проектов, использующих Kotlin.
Настройка стиля в IDE
Две самые популярные IDE для Kotlin — IntelliJ IDEA и Android Studio — предоставляют широкие возможности для настройки стиля кода. Их можно настроить так, чтобы код автоматически форматировался в соответствии с заданным стилем.
Применение руководства по стилю
Перейдите в раздел Настройки/Параметры | Редактор | Стиль кода | Kotlin.
Нажмите Выбрать из....
Выберите Руководство по стилю Kotlin.
Проверка соответствия кода руководству по стилю
Перейдите в раздел Настройки/Параметры | Редактор | Проверки | Общие.
Включите проверку Неправильное форматирование. Дополнительные проверки, выявляющие другие проблемы, описанные в руководстве по стилю (например, соглашения об именовании), включены по умолчанию.
Дополнительные сведения см. в руководстве Переход на стиль кода Kotlin в IntelliJ IDEA.
Организация исходного кода
Структура каталогов
В проектах, написанных только на Kotlin, рекомендуется, чтобы структура каталогов соответствовала структуре пакетов, за исключением общего корневого пакета. Например, если весь код проекта находится в пакете org.example.kotlin и его подпакетах, файлы с пакетом org.example.kotlin следует размещать непосредственно в корне исходного кода, а файлы из org.example.kotlin.network.socket — в подкаталоге network/socket корня исходного кода.
Имена исходных файлов
Если файл Kotlin содержит один класс или интерфейс (возможно, с относящимися к нему объявлениями верхнего уровня), его имя должно совпадать с именем класса, к которому добавлено расширение .kt. Это правило относится ко всем типам классов и интерфейсов. Если файл содержит несколько классов или только объявления верхнего уровня, выберите имя, описывающее содержимое файла, и назовите файл соответственно. Используйте верхний регистр CamelCase, при котором первая буква каждого слова заглавная. Например, ProcessDeclarations.kt.
Имя файла должно описывать, что делает содержащийся в нём код. Поэтому не используйте в именах файлов бессмысленные слова, например Util.
Многоплатформенные проекты
В многоплатформенных проектах файлы с объявлениями верхнего уровня в платформозависимых наборах исходного кода должны иметь суффикс, соответствующий имени набора. Например:
jvmMain/kotlin/Platform. jvm .kt
androidMain/kotlin/Platform. android .kt
iosMain/kotlin/Platform. ios .kt
Файлы с объявлениями верхнего уровня в общем наборе исходного кода не должны иметь суффикса. Например, commonMain/kotlin/Platform.kt.
Технические подробности
Мы рекомендуем использовать эту схему именования файлов в многоплатформенных проектах из-за ограничений JVM: она не поддерживает элементы верхнего уровня (функции, свойства).
Чтобы обойти это ограничение, компилятор Kotlin для JVM создаёт классы-обёртки (так называемые «фасады файлов»), содержащие объявления элементов верхнего уровня. Внутреннее имя фасада файла формируется на основе имени файла.
В свою очередь, JVM не допускает существования нескольких классов с одним и тем же полным именем (FQN). Это может привести к ситуациям, когда проект Kotlin невозможно скомпилировать для JVM:
root
|- commonMain/kotlin/myPackage/Platform.kt // contains 'fun count() { }'
|- jvmMain/kotlin/myPackage/Platform.kt // contains 'fun multiply() { }'
Здесь оба файла Platform.kt находятся в одном пакете, поэтому компилятор Kotlin для JVM создаёт два фасада файлов с одинаковым FQN — myPackage.PlatformKt. Это приводит к ошибке «Duplicate JVM classes».
Проще всего избежать этого — переименовать один из файлов согласно приведённым выше рекомендациям. Эта схема именования помогает избежать конфликтов и при этом сохраняет читаемость кода.
Организация исходных файлов
Рекомендуется размещать несколько объявлений (классов, функций или свойств верхнего уровня) в одном исходном файле Kotlin, если они тесно связаны по смыслу, а размер файла остаётся разумным (не превышает нескольких сотен строк).
В частности, если вы определяете функции-расширения класса, полезные всем его клиентам, разместите их в том же файле, что и сам класс. Если функции-расширения имеют смысл только для определённого клиента, разместите их рядом с кодом этого клиента. Не создавайте файлы исключительно для хранения всех расширений какого-либо класса.
Структура класса
Элементы класса следует располагать в следующем порядке:
Объявления свойств и блоки инициализации
Вторичные конструкторы
Объявления методов
Объект-компаньон
Не сортируйте объявления методов по алфавиту или уровню видимости и не отделяйте обычные методы от методов-расширений. Вместо этого располагайте связанные элементы рядом, чтобы читатель мог понять логику происходящего, просматривая класс сверху вниз. Выберите порядок (сначала элементы высокого уровня или наоборот) и придерживайтесь его.
Размещайте вложенные классы рядом с кодом, который их использует. Если классы предназначены для внешнего использования и на них нет ссылок внутри класса, размещайте их в конце, после объекта-компаньона.
Структура реализации интерфейса
При реализации интерфейса сохраняйте тот же порядок реализуемых членов, что и в интерфейсе (при необходимости перемежая их дополнительными закрытыми методами, используемыми при реализации).
Расположение перегрузок
Всегда размещайте перегрузки рядом друг с другом в классе.
Правила именования
Правила именования пакетов и классов в Kotlin довольно просты:
Имена пакетов всегда пишутся строчными буквами и не содержат подчёркиваний (
org.example.project). Использовать имена из нескольких слов обычно не рекомендуется, но если это необходимо, слова можно просто объединить или использовать camelCase (org.example.myProject).Имена классов и объектов записываются в верхнем регистре CamelCase:
open class DeclarationProcessor { /*...*/ }
object EmptyDeclarationProcessor : DeclarationProcessor() { /*...*/ }
Имена функций
Имена функций, свойств и локальных переменных начинаются со строчной буквы и записываются в camelCase без подчёркиваний:
fun processDeclarations() { /*...*/ }
var declarationCount = 1
Имена функций, похожих на классы
Есть два исключения, когда имена функций должны соответствовать соглашению об именовании классов. Такие функции обычно определяются на верхнем уровне.
-
Фабричные функции, создающие экземпляры классов, могут иметь то же имя, что и абстрактный тип возвращаемого значения:
interface Foo { /*...*/ } class FooImpl : Foo { /*...*/ } fun Foo(): Foo { return FooImpl() } -
Функции
@Composable, возвращающиеUnit:@Composable fun TabHeader { /*...*/ }
Имена тестовых методов
В тестах (и только в тестах) можно использовать имена методов с пробелами, заключённые в обратные кавычки. Обратите внимание, что такие имена методов поддерживаются только средой выполнения Android начиная с API уровня 30. В тестовом коде также разрешены подчёркивания в именах методов.
class MyTestCase {
@Test fun `ensure everything works`() { /*...*/ }
@Test fun ensureEverythingWorks_onAndroid() { /*...*/ }
}
Имена свойств
Имена констант (свойств, помеченных как const, или свойств val верхнего уровня либо объектов, не имеющих пользовательской функции get и содержащих глубоко неизменяемые данные) следует записывать прописными буквами с разделением слов подчёркиваниями, согласно соглашению SCREAMING_SNAKE_CASE:
const val MAX_COUNT = 8 val USER_NAME_FIELD = "UserName"
Имена свойств верхнего уровня или объектов, содержащих объекты с поведением или изменяемыми данными, следует записывать в camelCase:
val mutableCollection: MutableSet<String> = HashSet()
Для имён свойств, содержащих ссылки на одиночные объекты, можно использовать тот же стиль именования, что и для объявлений object:
val PersonComparator: Comparator<Person> = /*...*/
Для констант перечислений можно использовать либо имена, полностью записанные прописными буквами с разделением подчёркиваниями (SCREAMING_SNAKE_CASE) (enum class Color { RED, GREEN }), либо имена в верхнем регистре CamelCase — в зависимости от контекста.
Имена свойств-хранилищ
Если в классе есть два концептуально одинаковых свойства, одно из которых является частью общедоступного API, а другое — деталью реализации, добавьте подчёркивание в начало имени закрытого свойства:
class C {
private val _elementList = mutableListOf<Element>()
val elementList: List<Element>
get() = _elementList
}
Выбирайте удачные имена
Имя класса обычно представляет собой существительное или именную группу, объясняющую, чем класс является: List, PersonReader.
Имя метода обычно представляет собой глагол или глагольную группу, описывающую, что метод делает: close, readPersons. Из имени также должно быть понятно, изменяет ли метод объект или возвращает новый. Например, sort сортирует коллекцию на месте, а sorted возвращает отсортированную копию коллекции.
Имена должны ясно отражать назначение сущности, поэтому лучше не использовать в них бессмысленные слова (Manager, Wrapper).
При использовании аббревиатуры в имени объявления придерживайтесь следующих правил:
В двухбуквенных аббревиатурах используйте прописные буквы для обеих букв. Например,
IOStream.В аббревиатурах длиной более двух букв делайте прописной только первую букву. Например,
XmlFormatterилиHttpInputStream.
Форматирование
Отступы
Используйте отступ в четыре пробела. Не используйте табуляцию.
Для фигурных скобок: открывающую скобку ставьте в конце строки, где начинается конструкция, а закрывающую — на отдельной строке, выровняв её по горизонтали с началом конструкции.
if (elements != null) {
for (element in elements) {
// ...
}
}
Пробелы по горизонтали
Ставьте пробелы вокруг бинарных операторов (
a + b). Исключение: не ставьте пробелы вокруг оператора «диапазон до» (0..i).Не ставьте пробелы вокруг унарных операторов (
a++).Ставьте пробел между ключевыми словами управления потоком (
if,when,forиwhile) и соответствующей открывающей круглой скобкой.Не ставьте пробел перед открывающей круглой скобкой в объявлении первичного конструктора, объявлении метода или вызове метода.
class A(val x: Int)
fun foo(x: Int) { ... }
fun bar() {
foo(1)
}
Никогда не ставьте пробел после
(,[или перед],).Никогда не ставьте пробелы вокруг
.или?.:foo.bar().filter { it > 2 }.joinToString(),foo?.bar().Ставьте пробел после
//:// This is a comment.Не ставьте пробелы вокруг угловых скобок, используемых для указания параметров типа:
class Map<K, V> { ... }.Не ставьте пробелы вокруг
:::Foo::class,String::length.Не ставьте пробел перед
?, обозначающим тип, допускающий значение null:String?.
Как правило, избегайте любого горизонтального выравнивания. Переименование идентификатора в имя другой длины не должно влиять на форматирование объявления или любого из мест его использования.
Двоеточие
В следующих случаях ставьте пробел перед ::
Когда оно используется для отделения типа от супертипа.
При делегировании конструктору суперкласса или другому конструктору того же класса.
После ключевого слова
object.
Не ставьте пробел перед :, если оно отделяет объявление от его типа.
Всегда ставьте пробел после :.
abstract class Foo<out T : Any> : IFoo {
abstract fun foo(a: Int): T
}
class FooImpl : Foo() {
constructor(x: String) : this(x) { /*...*/ }
val x = object : IFoo { /*...*/ }
}
Заголовки классов
Классы с небольшим количеством параметров первичного конструктора можно записывать в одну строку:
class Person(id: Int, name: String)
Заголовки более длинных классов следует форматировать так, чтобы каждый параметр первичного конструктора располагался на отдельной строке с отступом. Закрывающую круглую скобку также следует размещать на новой строке. При использовании наследования вызов конструктора суперкласса или список реализуемых интерфейсов следует разместить на той же строке, что и закрывающую скобку:
class Person(
id: Int,
name: String,
surname: String
) : Human(id, name) { /*...*/ }
Если интерфейсов несколько, сначала следует разместить вызов конструктора суперкласса, а затем каждый интерфейс — на отдельной строке:
class Person(
id: Int,
name: String,
surname: String
) : Human(id, name),
KotlinMaker { /*...*/ }
Если список супертипов класса длинный, перенесите строку после двоеточия и выровняйте все имена супертипов по горизонтали:
class MyFavouriteVeryLongClassHolder :
MyLongHolder<MyFavouriteVeryLongClass>(),
SomeOtherInterface,
AndAnotherOne {
fun foo() { /*...*/ }
}
Чтобы чётко отделить заголовок класса от его тела, если заголовок длинный, добавьте пустую строку после заголовка (как в примере выше) или разместите открывающую фигурную скобку на отдельной строке:
class MyFavouriteVeryLongClassHolder :
MyLongHolder<MyFavouriteVeryLongClass>(),
SomeOtherInterface,
AndAnotherOne
{
fun foo() { /*...*/ }
}
Для параметров конструктора используйте обычный отступ (четыре пробела). Это гарантирует, что свойства, объявленные в первичном конструкторе, будут иметь такой же отступ, как и свойства, объявленные в теле класса.
Порядок модификаторов
Если объявление содержит несколько модификаторов, всегда располагайте их в следующем порядке:
public / protected / private / internal expect / actual final / open / abstract / sealed / const external override lateinit tailrec vararg suspend inner enum / annotation / fun // as a modifier in `fun interface` companion inline / value infix operator data
Размещайте все аннотации перед модификаторами:
@Named("Foo")
private val foo: Foo
Если вы не работаете над библиотекой, опускайте избыточные модификаторы (например, public).
Аннотации
Размещайте аннотации на отдельных строках перед объявлением, к которому они относятся, и используйте такой же отступ:
@Target(AnnotationTarget.PROPERTY) annotation class JsonExclude
Аннотации без аргументов можно размещать в одной строке:
@JsonExclude @JvmField var x: String
Одну аннотацию без аргументов можно разместить в одной строке с соответствующим объявлением:
@Test fun foo() { /*...*/ }
Аннотации файлов
Аннотации файлов размещаются после комментария к файлу (если он есть), перед инструкцией package и отделяются пустой строкой от package (чтобы подчеркнуть, что они относятся к файлу, а не к пакету).
/** License, copyright and whatever */
@file:JvmName("FooBar")
package foo.bar
Функции
Если сигнатура функции не помещается в одну строку, используйте следующий синтаксис:
fun longMethodName(
argument: ArgumentType = defaultValue,
argument2: AnotherArgumentType,
): ReturnType {
// body
}
Для параметров функции используйте обычный отступ (четыре пробела). Это помогает обеспечить единообразие с параметрами конструктора.
Для функций, тело которых состоит из одного выражения, отдавайте предпочтение телу-выражению.
fun foo(): Int { // bad
return 1
}
fun foo() = 1 // good
Тела-выражения
Если функция имеет тело-выражение, первая строка которого не помещается в одну строку с объявлением, разместите знак = в первой строке, а тело-выражение сдвиньте на четыре пробела.
fun f(x: String, y: String, z: String) =
veryLongFunctionCallWithManyWords(andLongParametersToo(), x, y, z)
Свойства
Для очень простых свойств только для чтения можно использовать форматирование в одну строку:
val isEmpty: Boolean get() = size == 0
Для более сложных свойств всегда размещайте ключевые слова get и set на отдельных строках:
val foo: String
get() { /*...*/ }
Если у свойства есть инициализатор и он длинный, перенесите строку после знака = и сдвиньте инициализатор на четыре пробела:
private val defaultCharset: Charset? =
EncodingRegistry.getInstance().getDefaultCharsetForPropertiesFiles(file)
Операторы управления потоком
Если условие оператора if или when занимает несколько строк, всегда заключайте тело оператора в фигурные скобки. Каждую следующую строку условия сдвигайте на четыре пробела относительно начала оператора. Закрывающую круглую скобку условия и открывающую фигурную скобку размещайте вместе на отдельной строке:
if (!component.isSyncing &&
!hasAnyKotlinRuntimeInScope(module)
) {
return createKotlinNotConfiguredPanel(module)
}
Это помогает выровнять условие и тело оператора.
Размещайте ключевые слова else, catch, finally, а также ключевое слово while цикла do-while на той же строке, что и предшествующая фигурная скобка:
if (condition) {
// body
} else {
// else part
}
try {
// body
} finally {
// cleanup
}
В операторе when, если ветвь занимает больше одной строки, отделяйте её от соседних блоков case пустой строкой:
private fun parsePropertyValue(propName: String, token: Token) {
when (token) {
is Token.ValueToken ->
callback.visitValue(propName, token.value)
Token.LBRACE -> { // ...
}
}
}
Короткие ветви размещайте на одной строке с условием, без фигурных скобок.
when (foo) {
true -> bar() // good
false -> { baz() } // bad
}
Вызовы методов
В длинных списках аргументов переносите строку после открывающей круглой скобки. Сдвигайте аргументы на четыре пробела. Группируйте несколько тесно связанных аргументов в одной строке.
drawSquare(
x = 10, y = 10,
width = 100, height = 100,
fill = true
)
Ставьте пробелы вокруг знака =, отделяющего имя аргумента от его значения.
Перенос цепочек вызовов
При переносе цепочек вызовов размещайте символ . или оператор ?. на следующей строке с одним отступом:
val anchor = owner
?.firstChild!!
.siblings(forward = true)
.dropWhile { it is PsiComment || it is PsiWhiteSpace }
Перед первым вызовом в цепочке обычно следует переносить строку, но перенос можно опустить, если так код будет понятнее.
Лямбды
В лямбда-выражениях ставьте пробелы вокруг фигурных скобок, а также вокруг стрелки, отделяющей параметры от тела. Если вызов принимает одну лямбду, по возможности передавайте её за пределами круглых скобок.
list.filter { it > 10 }
При назначении метки лямбде не ставьте пробел между меткой и открывающей фигурной скобкой:
fun foo() {
ints.forEach lit@{
// ...
}
}
При объявлении имён параметров в многострочной лямбде разместите имена в первой строке, а затем добавьте стрелку и перевод строки:
appendCommaSeparated(properties) { prop ->
val propertyValue = prop.get(obj) // ...
}
Если список параметров не помещается в одну строку, разместите стрелку на отдельной строке:
foo {
context: Context,
environment: Env
->
context.configureEnv(environment)
}
Завершающие запятые
Завершающая запятая — это запятая после последнего элемента в последовательности:
class Person(
val firstName: String,
val lastName: String,
val age: Int, // trailing comma
)
Использование завершающих запятых даёт несколько преимуществ:
Различия в системе контроля версий становятся нагляднее: всё внимание сосредоточено на изменённом значении.
Добавлять и переставлять элементы проще: при изменении элементов не нужно добавлять или удалять запятую.
Упрощается генерация кода, например инициализаторов объектов. Запятая может стоять и после последнего элемента.
Завершающие запятые полностью необязательны — код будет работать и без них. Руководство по стилю Kotlin рекомендует использовать завершающие запятые в объявлениях, а решение об их использовании при вызовах оставляет на ваше усмотрение.
Чтобы включить завершающие запятые в форматтере IntelliJ IDEA, перейдите в раздел Настройки/Параметры | Редактор | Стиль кода | Kotlin, откройте вкладку Другое и выберите параметр Использовать завершающую запятую.
Перечисления
enum class Direction {
NORTH,
SOUTH,
WEST,
EAST, // trailing comma
}
Аргументы-значения
fun shift(x: Int, y: Int) { /*...*/ }
shift(
25,
20, // trailing comma
)
val colors = listOf(
"red",
"green",
"blue", // trailing comma
)
Свойства и параметры класса
class Customer(
val name: String,
val lastName: String, // trailing comma
)
class Customer(
val name: String,
lastName: String, // trailing comma
)
Параметры-значения функций
fun powerOf(
number: Int,
exponent: Int, // trailing comma
) { /*...*/ }
constructor(
x: Comparable<Number>,
y: Iterable<Number>, // trailing comma
) {}
fun print(
vararg quantity: Int,
description: String, // trailing comma
) {}
Параметры с необязательным типом (включая сеттеры)
val sum: (Int, Int, Int) -> Int = fun(
x,
y,
z, // trailing comma
): Int {
return x + y + x
}
println(sum(8, 8, 8))
Индексирующий суффикс
class Surface {
operator fun get(x: Int, y: Int) = 2 * x + 4 * y - 10
}
fun getZValue(mySurface: Surface, xValue: Int, yValue: Int) =
mySurface[
xValue,
yValue, // trailing comma
]
Параметры в лямбдах
fun main() {
val x = {
x: Comparable<Number>,
y: Iterable<Number>, // trailing comma
->
println("1")
}
println(x)
}
Ветвь when
fun isReferenceApplicable(myReference: KClass<*>) = when (myReference) {
Comparable::class,
Iterable::class,
String::class, // trailing comma
-> true
else -> false
}
Литералы коллекций (в аннотациях)
annotation class ApplicableFor(val services: Array<String>)
@ApplicableFor([
"serializer",
"balancer",
"database",
"inMemoryCache", // trailing comma
])
fun run() {}
Аргументы типа
fun <T1, T2> foo() {}
fun main() {
foo<
Comparable<Number>,
Iterable<Number>, // trailing comma
>()
}
Параметры типа
class MyMap<
MyKey,
MyValue, // trailing comma
> {}
Объявления с деструктуризацией
data class Car(val manufacturer: String, val model: String, val year: Int)
val myCar = Car("Tesla", "Y", 2019)
val (
manufacturer,
model,
year, // trailing comma
) = myCar
val cars = listOf<Car>()
fun printMeanValue() {
var meanValue: Int = 0
for ((
_,
_,
year, // trailing comma
) in cars) {
meanValue += year
}
println(meanValue/cars.size)
}
printMeanValue()
Комментарии документации
Для длинных комментариев документации размещайте открывающий /** на отдельной строке и начинайте каждую следующую строку со звёздочки:
/** * This is a documentation comment * on multiple lines. */
Короткие комментарии можно размещать в одной строке:
/** This is a short documentation comment. */
Как правило, избегайте тегов @param и @return. Вместо этого включайте описание параметров и возвращаемых значений непосредственно в комментарий документации и добавляйте ссылки на параметры везде, где они упоминаются. Используйте @param и @return только тогда, когда требуется длинное описание, которое не вписывается в основной текст.
// Avoid doing this:
/**
* Returns the absolute value of the given number.
* @param number The number to return the absolute value for.
* @return The absolute value.
*/
fun abs(number: Int): Int { /*...*/ }
// Do this instead:
/**
* Returns the absolute value of the given [number].
*/
fun abs(number: Int): Int { /*...*/ }
Избегайте избыточных конструкций
Как правило, если определённая синтаксическая конструкция в Kotlin является необязательной и IDE отмечает её как избыточную, не используйте её в коде. Не оставляйте ненужные синтаксические элементы в коде просто «для ясности».
Тип возвращаемого значения Unit
Если функция возвращает Unit, тип возвращаемого значения следует опустить:
fun foo() { // ": Unit" is omitted here
}
Точки с запятой
По возможности не используйте точки с запятой.
Шаблоны строк
Не используйте фигурные скобки при подстановке простой переменной в шаблон строки. Используйте фигурные скобки только для более длинных выражений:
println("$name has ${children.size} children")
Используйте интерполяцию строк с несколькими знаками доллара, чтобы воспринимать символы доллара $ как строковые литералы:
val KClass<*>.jsonSchema : String
get() = $$"""
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://example.com/product.schema.json",
"$dynamicAnchor": "meta",
"title": "$${simpleName ?: qualifiedName ?: "unknown"}",
"type": "object"
}
"""
Идиоматическое использование возможностей языка
Неизменяемость
Предпочитайте неизменяемые данные изменяемым. Всегда объявляйте локальные переменные и свойства как val, а не var, если после инициализации их значения не изменяются.
Всегда используйте неизменяемые интерфейсы коллекций (Collection, List, Set, Map) для объявления коллекций, которые не изменяются. При использовании фабричных функций для создания экземпляров коллекций всегда по возможности используйте функции, возвращающие неизменяемые типы коллекций:
// Bad: use of a mutable collection type for value which will not be mutated
fun validateValue(actualValue: String, allowedValues: HashSet<String>) { ... }
// Good: immutable collection type used instead
fun validateValue(actualValue: String, allowedValues: Set<String>) { ... }
// Bad: arrayListOf() returns ArrayList<T>, which is a mutable collection type
val allowedValues = arrayListOf("a", "b", "c")
// Good: listOf() returns List<T>
val allowedValues = listOf("a", "b", "c")
Значения параметров по умолчанию
Предпочитайте объявлять функции со значениями параметров по умолчанию, а не перегружать функции.
// Bad
fun foo() = foo("a")
fun foo(a: String) { /*...*/ }
// Good
fun foo(a: String = "a") { /*...*/ }
Псевдонимы типов
Если функциональный тип или тип с параметрами типа используется в кодовой базе несколько раз, предпочтительно определить для него псевдоним типа:
typealias MouseClickHandler = (Any, MouseEvent) -> Unit typealias PersonIndex = Map<String, Person>
Если вы используете приватный или внутренний псевдоним типа, чтобы избежать конфликтов имён, предпочтите import ... as ..., упомянутый в разделе Пакеты и импорты.
Параметры лямбда-выражений
В коротких невложенных лямбда-выражениях рекомендуется использовать соглашение it вместо явного объявления параметра. Во вложенных лямбда-выражениях с параметрами всегда объявляйте параметры явно.
Возвраты в лямбда-выражении
Избегайте использования нескольких помеченных операторов return в лямбда-выражении. Попробуйте перестроить лямбда-выражение так, чтобы у него была одна точка выхода. Если это невозможно или недостаточно понятно, рассмотрите возможность преобразования лямбда-выражения в анонимную функцию.
Не используйте помеченный оператор return в последнем выражении лямбда-выражения.
Именованные аргументы
Используйте синтаксис именованных аргументов, если метод принимает несколько параметров одного и того же примитивного типа или параметры типа Boolean, если только значение всех параметров не предельно ясно из контекста.
drawSquare(x = 10, y = 10, width = 100, height = 100, fill = true)
Условные выражения
Предпочитайте использовать выражения try, if и when.
return if (x) foo() else bar()
return when(x) {
0 -> "zero"
else -> "nonzero"
}
Предпочтительнее следующий вариант:
if (x)
return foo()
else
return bar()
when(x) {
0 -> return "zero"
else -> return "nonzero"
}
if или when
Для бинарных условий предпочитайте if вместо when. Например, используйте следующий синтаксис с if:
if (x == null) ... else ...
Вместо такого синтаксиса с when:
when (x) {
null -> // ...
else -> // ...
}
Если вариантов три или больше, предпочтительно использовать when.
Условия-ограничители в выражении when
Используйте скобки при объединении нескольких логических выражений в выражениях или операторах when с условиями-ограничителями:
when (status) {
is Status.Ok if (status.info.isEmpty() || status.info.id == null) -> "no information"
}
Вместо:
when (status) {
is Status.Ok if status.info.isEmpty() || status.info.id == null -> "no information"
}
Значения Boolean, допускающие null, в условиях
Если в условном операторе нужно использовать значение Boolean, допускающее null, используйте проверки if (value == true) или if (value == false).
Циклы
Предпочитайте циклам функции высшего порядка (filter, map и т. д.). Исключение: forEach (предпочитайте обычный цикл for, если только получатель forEach не допускает null или forEach не используется в составе более длинной цепочки вызовов).
Выбирая между сложным выражением с несколькими функциями высшего порядка и циклом, учитывайте стоимость выполняемых операций в каждом случае и не забывайте о производительности.
Циклы по диапазонам
Используйте оператор ..< для перебора открытого диапазона:
for (i in 0..n - 1) { /*...*/ } // bad
for (i in 0..<n) { /*...*/ } // good
Строки
Предпочитайте шаблоны строк конкатенации строк.
Предпочитайте многострочные строки вместо добавления escape-последовательностей \n в обычные строковые литералы.
Чтобы сохранить отступы в многострочных строках, используйте trimIndent, если в результирующей строке не нужны внутренние отступы, или trimMargin, если внутренние отступы необходимы:
fun main() {
//sampleStart
println("""
Not
trimmed
text
"""
)
println("""
Trimmed
text
""".trimIndent()
)
println()
val a = """Trimmed to margin text:
|if(a > 1) {
| return a
|}""".trimMargin()
println(a)
//sampleEnd
}
Узнайте о различиях между многострочными строками в Java и Kotlin.
Функции и свойства
В некоторых случаях функции без аргументов могут быть взаимозаменяемы со свойствами только для чтения. Несмотря на сходство семантики, существуют стилистические рекомендации о том, что предпочесть в каждом случае.
Предпочитайте свойство функции, если лежащий в основе алгоритм:
Не выбрасывает исключений.
Вычисляется быстро (или кэшируется при первом вызове).
Возвращает один и тот же результат при каждом вызове, если состояние объекта не изменилось.
Функции-расширения
Широко используйте функции-расширения. Если у вас есть функция, которая преимущественно работает с объектом, подумайте о том, чтобы сделать её функцией-расширением, принимающей этот объект в качестве получателя. Чтобы свести к минимуму загрязнение API, по возможности ограничивайте видимость функций-расширений. При необходимости используйте локальные функции-расширения, функции-расширения-члены или функции-расширения верхнего уровня с модификатором private.
Инфиксные функции
Объявляйте функцию как infix только тогда, когда она работает с двумя объектами, играющими схожую роль. Хорошие примеры: and, to, zip. Плохой пример: add.
Не объявляйте метод как infix, если он изменяет объект-получатель.
Фабричные функции
Если вы объявляете фабричную функцию для класса, не давайте ей то же имя, что и самому классу. Предпочитайте отдельное имя, которое ясно указывает, чем особенна фабричная функция. Использовать имя класса можно только в том случае, если функция действительно не имеет особой семантики.
class Point(val x: Double, val y: Double) {
companion object {
fun fromPolar(angle: Double, radius: Double) = Point(...)
}
}
Если у вас есть объект с несколькими перегруженными конструкторами, которые не вызывают разные конструкторы суперкласса и не могут быть сведены к одному конструктору с параметрами, имеющими значения по умолчанию, предпочтительно заменить перегруженные конструкторы фабричными функциями.
Платформенные типы
Публичная функция или метод, возвращающие выражение платформенного типа, должны явно объявлять тип Kotlin:
fun apiCall(): String = MyJavaApi.getProperty("name")
Для любого свойства (уровня пакета или класса), инициализируемого выражением платформенного типа, необходимо явно объявлять тип Kotlin:
class Person {
val name: String = MyJavaApi.getProperty("name")
}
Для локального значения, инициализируемого выражением платформенного типа, объявление типа необязательно:
fun main() {
val name = MyJavaApi.getProperty("name")
println(name)
}
Функции области видимости apply/with/run/also/let
В Kotlin есть набор функций для выполнения блока кода в контексте заданного объекта: let, run, with, apply и also. Рекомендации по выбору подходящей функции области видимости для вашего случая см. в разделе Функции области видимости.
Соглашения о написании кода для библиотек
При написании библиотек рекомендуется соблюдать дополнительные правила, обеспечивающие стабильность API:
Всегда явно указывайте видимость членов, чтобы случайно не сделать объявления частью публичного API.
Всегда явно указывайте типы возвращаемых значений функций и типы свойств, чтобы изменение реализации случайно не изменило тип возвращаемого значения.
Добавляйте комментарии KDoc ко всем публичным членам, за исключением переопределений, для которых не требуется новая документация (это необходимо для создания документации библиотеки).
Подробнее о рекомендациях и идеях, которые следует учитывать при создании API библиотеки, читайте в рекомендациях для авторов библиотек.
© 2010–2026 JetBrains s.r.o. and Kotlin Programming Language contributors
Licensed under the Apache License, Version 2.0.
https://kotlinlang.org/docs/coding-conventions.html