Spec-Zone.ru › Kotlin 1.8

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Правила именования пакетов и классов в 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, если ветвь состоит из более чем одной строки, рассмотрите возможность разделения её от смежных блоков с помощью пустой строки:

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 вместо 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>

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

Параметры лямбда-выражений

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

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

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

Объявляйте функцию инфиксной только тогда, когда она работает с двумя объектами, которые играют схожую роль. Хорошие примеры: and, to, zip. Плохой пример: add.

Не объявляйте метод инфиксным, если он изменяет объект-получатель.

Функции-фабрики

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

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

Последнее изменение: 10 января 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