Spec-Zone.ru › Kotlin 1.9

Рекомендации по стилю кодирования

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

Настройка стиля в IDE

Две самые популярные IDE для Kotlin — IntelliJ IDEA и Android Studio — предоставляют мощную поддержку стилей кода. Вы можете настроить их для автоматического форматирования кода в соответствии с данным стилем.

Применение руководства по стилю

  1. Перейдите в Настройки/Предпочтения | Редактор | Стиль кода | Kotlin.

  2. Нажмите Установить из....

  3. Выберите Руководство по стилю Kotlin.

Проверка соответствия кода руководству по стилю

  1. Перейдите в Настройки/Предпочтения | Редактор | Проверки | Общие.

  2. Включите проверку Неправильное форматирование. Дополнительные проверки, которые проверяют другие аспекты, описанные в руководстве по стилю (например, соглашения об именовании), включены по умолчанию.

Организация исходного кода

Структура каталогов

В чистых проектах Kotlin рекомендуемая структура каталогов соответствует структуре пакетов без общего корневого пакета. Например, если весь код проекта находится в пакете org.example.kotlin и его подпакетах, файлы с пакетом org.example.kotlin должны располагаться непосредственно в корне источника, а файлы в org.example.kotlin.network.socket — в подкаталоге network/socket корня источника.

В JVM: в проектах, где Kotlin используется вместе с Java, файлы Kotlin должны находиться в том же корневом каталоге, что и Java-файлы, и следовать той же структуре каталогов: каждый файл должен храниться в каталоге, соответствующем каждой директиве пакета.

Имена файлов исходного кода

Если файл Kotlin содержит единственный класс или интерфейс (возможно, с сопутствующими объявлениями на верхнем уровне), его имя должно совпадать с именем класса с добавлением расширения .kt. Это относится ко всем видам классов и интерфейсов. Если файл содержит несколько классов или только объявления на верхнем уровне, выберите имя, описывающее содержимое файла, и дайте ему соответствующее имя. Используйте верхний регистр с большой буквы (также известный как паскаль-кейс), например, 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. Это приводит к ошибке «Повторные классы JVM».

Простейший способ избежать этого — переименовать один из файлов в соответствии с приведенным выше руководством. Эта схема именования помогает избежать конфликтов, сохраняя читабельность кода.

Есть два случая, когда эти рекомендации могут показаться излишними, но мы все же рекомендуем их соблюдать:

  • Платформы, отличные от JVM, не имеют проблем с дублированием фасадов файлов. Однако эта схема именования может помочь вам сохранить согласованность имен файлов.

  • В JVM, если файлы исходного кода не содержат объявлений на верхнем уровне, фасады файлов не генерируются, и вы не столкнетесь с конфликтами имен.

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

Организация файлов исходного кода

Размещение нескольких объявлений (классов, функций или свойств на верхнем уровне) в одном файле Kotlin поощряется, если эти объявления тесно связаны между собой по смыслу, и размер файла остается разумным (не превышает несколько сотен строк).

В частности, при определении расширяющих функций для класса, которые актуальны для всех клиентов этого класса, поместите их в тот же файл, что и сам класс. При определении расширяющих функций, которые имеют смысл только для конкретного клиента, поместите их рядом с кодом этого клиента. Избегайте создания файлов только для хранения всех расширяющих функций для какого-либо класса.

Структура класса

Содержание класса должно идти в следующем порядке:

  1. Объявления свойств и инициализирующие блоки

  2. Вторичные конструкторы

  3. Объявления методов

  4. Объект-компаньон

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

Поместите вложенные классы рядом с кодом, использующим эти классы. Если классы предназначены для внешнего использования и не ссылаются внутри класса, поместите их в конце, после объекта-компаньона.

Структура реализации интерфейса

При реализации интерфейса сохраняйте реализующие члены в том же порядке, что и члены интерфейса (при необходимости, чередуя их с дополнительными закрытыми методами, используемыми для реализации).

Структура перегрузки

Всегда помещайте перегрузки рядом друг с другом в классе.

END_OF_DOCUMENT_MARKER

Правила именования

Правила именования пакетов и классов в Kotlin довольно просты:

  • Имена пакетов всегда пишутся строчными буквами и без нижних подчеркиваний (org.example.project). Использование имён из нескольких слов обычно не рекомендуется, но если вам необходимо использовать несколько слов, вы можете либо просто объединить их вместе, либо использовать верблюжье написание (org.example.myProject).

  • Имена классов и объектов начинаются с заглавной буквы и используют верблюжье написание:

open class DeclarationProcessor { /*...*/ }

object EmptyDeclarationProcessor : DeclarationProcessor() { /*...*/ }

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

Имена функций, свойств и локальных переменных начинаются со строчной буквы, используют верблюжье написание и не содержат нижних подчеркиваний:

fun processDeclarations() { /*...*/ }
var declarationCount = 1

Исключение: функции-фабрики, используемые для создания экземпляров классов, могут иметь такое же имя, как абстрактный возвращаемый тип:

interface Foo { /*...*/ }

class FooImpl : Foo { /*...*/ }

fun Foo(): Foo { return FooImpl() }

Имена методов тестов

В тестах (и только в тестах) вы можете использовать имена методов с пробелами, заключенными в обратные кавычки. Обратите внимание, что такие имена методов в настоящее время не поддерживаются средой выполнения Android. Нижние подчеркивания в именах методов также разрешены в тестовом коде.

class MyTestCase {
     @Test fun `ensure everything works`() { /*...*/ }
     
     @Test fun ensureEverythingWorks_onAndroid() { /*...*/ }
}

Имена свойств

Имена констант (свойства, помеченные const, или свойства верхнего уровня или объекта val без пользовательской get функции, которые хранят глубоко неизменяемые данные) должны использовать имена с заглавными буквами, разделённые нижними подчеркиваниями (назывной стиль):

const val MAX_COUNT = 8
val USER_NAME_FIELD = "UserName"

Имена свойств верхнего уровня или объекта, которые хранят объекты с поведением или изменяемыми данными, должны использовать имена с верблюжьим написанием:

val mutableCollection: MutableSet<String> = HashSet()

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

val PersonComparator: Comparator<Person> = /*...*/

Для констант перечислений допустимо использовать либо имена с заглавными буквами, разделённые нижними подчеркиваниями (назывной стиль) (enum class Color { RED, GREEN }) или имена с заглавными буквами верблюжьего написания, в зависимости от использования.

Имена вспомогательных свойств

Если у класса есть два свойства, которые концептуально одинаковы, но одно является частью публичного 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) {
        // ...
    }
}

В Kotlin точки с запятой необязательны, поэтому знаки перевода строки важны. Дизайн языка предполагает фигурные скобки в стиле Java, и вы можете столкнуться с неожиданным поведением, если попытаетесь использовать другой стиль форматирования.

Горизонтальные пробелы

  • Помещайте пробелы вокруг бинарных операторов (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

  • Не ставьте пробел перед ?, используемым для обозначения допускаемого значения типа: 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, перейдите в Настройки/Preferences | Editor | Стиль кода | 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 вместо 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 соглашение вместо явного объявления параметров. Во вложенных лямбда-выражениях с параметрами всегда объявляйте параметры явно.

Возврат в лямбда-выражении

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

Не используйте метку возврата для последнего оператора в лямбда-выражении.

Именованные аргументы

Используйте синтаксис именованных аргументов, когда метод принимает несколько параметров одного и того же примитивного типа или для параметров типа 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 если есть три или более вариантов.

Значения типа 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

Строки

Предпочтительнее использовать шаблоны строк вместо конкатенации строк.

Предпочтительнее использовать многострочные строки вместо встраивания \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, ограничивайте видимость расширяющих функций так, как это имеет смысл. По мере необходимости используйте локальные расширяющие функции, расширяющие функции-члены или расширяющие функции верхнего уровня с закрытой видимостью.

Инфиксные функции

Объявляйте функцию как 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 для вашей библиотеки, можно найти в Руководстве для создателей библиотек.

Последнее изменение: 06 июля 2023
Идиомы Основные типы

© 2010–2023 JetBrains s.r.o. and Kotlin Programming Language contributors
Licensed under the Apache License, Version 2.0.
https://kotlinlang.org/docs/coding-conventions.html

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API