Spec-Zone.ru › Kotlin 1.6

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

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

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

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

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

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

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

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

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

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

  2. Откройте Kotlin | Проблемы стиля.

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

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

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

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

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

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

Если файл Kotlin содержит один класс (возможно, с связанными объявлениями верхнего уровня), его имя должно совпадать с именем класса, с добавлением расширения .kt. Если файл содержит несколько классов или только объявления верхнего уровня, выберите имя, описывающее содержимое файла, и назовите файл соответственно. Используйте верхний регистр с заглавной буквы (также известный как Паскаль-кейс), например, ProcessDeclarations.kt.

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

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

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

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

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

Содержание класса должно быть упорядочено следующим образом:

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

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

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

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

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

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

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

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

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

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

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

Правила именования пакетов и классов в 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 функции, содержащие глубоко неизменяемые данные), должны использовать имена с заглавными буквами через знак подчеркивания (стиль "screaming snake case"):

const val MAX_COUNT = 8
val USER_NAME_FIELD = "UserName"

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

val mutableCollection: MutableSet<String> = HashSet()

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

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

Для констант перечислений допустимо использовать имена с заглавными буквами через знак подчеркивания (стиль "screaming snake case") (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 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 , если есть три или более вариантов.

Значения nullable Boolean в условиях

Если вам нужно использовать nullable Boolean в условном операторе, используйте проверки if (value == true) или if (value == false).

Циклы

Предпочтительнее использовать функции высшего порядка (filter, map и т.д.) вместо циклов. Исключение: forEach (предпочтительнее использовать обычный цикл for вместо этого, если получатель forEach является nullable или forEach используется как часть более длинной цепочки вызовов).

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

Циклы по диапазонам

Используйте функцию until для цикла по открытому диапазону:

for (i in 0..n - 1) { /*...*/ }  // bad
for (i in 0 until 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 комментарии для всех открытых членов, за исключением переопределений, для которых не требуется новая документация (для поддержки генерации документации для библиотеки)

Последнее изменение: 07 апреля 2022 г.
Идиомы Основные типы

© 2010–2022 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