Соглашение о стилистике кода

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

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

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

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

  1. Перейдите в раздел Settings/Preferences | Editor | Code Style | Kotlin.
  2. Нажмите Set from....
  3. Выберите Kotlin style guide.

Убедитесь, что ваш код соответствует руководству по стилю

  1. Перейдите в раздел Settings/Preferences | Editor | Inspections | Kotlin.
  2. Отройте Kotlin | Style issues.
  3. Включите проверку File is not formatted according to project settings. Дополнительные проверки, которые проверяют другие проблемы, описанные в руководстве по стилю (например, соглашения об именах), включены по умолчанию.

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

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

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

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

Имена файлов

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

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

Организация файла

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

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

Схема классов

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

  1. Объявления свойств и блоки инициализации
  2. Дополнительные конструкторы
  3. Объявления методов
  4. Вспомогательный объект

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

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

Схема реализации интерфейса

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

Расположение перегрузки

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

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

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

  • Имена пакетов всегда написаны в нижнем регистре и не содержат нижних подчеркиваний. Использование имен, состоящих из нескольких слов, обычно не рекомендуется, но, если вы не можете их не использовать, либо просто объедините их вместе, либо используйте при этом lowerCamelCase (org.example.myProject).
  • Имена классов и объектов начинаются с заглавной буквы и используют UpperCamelCase.
open class DeclarationProcessor { /*...*/ }

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

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

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

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"

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

val mutableCollection: MutableSet<String> = HashSet()

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

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

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

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

Если класс имеет два свойства, которые концептуально одинаковы, но одно из них является частью общедоступного 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, and while) и соответствующей открывающей скобкой.
  • Не ставьте пробел перед открывающей скобкой в объявлении основного конструктора, объявлении метода или вызове метода.
class A(val x: Int)

fun foo(x: Int) { ... }

fun bar() {
    foo(1)
}
  • Никогда не ставьте пробел после (, [ или перед ], ).
  • Никогда не ставьте пробелы вокруг . или ?.: foo.bar().filter { it > 2 }.joinToString(), foo?.bar().
  • Ставьте пробел после //: // Это комментарий.
  • Не ставьте пробелы вокруг угловых скобок, используемых для указания параметров типа: 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 // модификатор в `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 {     // плохо
    return 1 
}

fun foo() = 1        // хорошо

Выражение вместо тела

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

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() // хорошо
    false -> { baz() } // плохо
}

Вызовы методов

В длинных списках аргументов после открывающей скобки переходите на следующую строку. Отступы аргументов в четыре пробела. Сгруппируйте несколько тесно связанных аргументов в одной строке.

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, // завершающая запятая
)

Использование завершающих запятых имеет несколько преимуществ:

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

Завершающие запятые совершенно необязательны – ваш код все равно будет работать без них. Руководство по стилю Kotlin рекомендует использовать конечные запятые при объявлениях и оставляет их использование на ваше усмотрение при вызовах.

Чтобы включить завершающие запятые в IntelliJ IDEA, перейдите в раздел Settings/Preferences | Editor | Code Style | Kotlin, откройте вкладку Other и выберите опцию Use trailing comma.

Перечисления

enum class Direction {
    NORTH,
    SOUTH,
    WEST,
    EAST, // завершающая запятая
}

Аргументы

fun shift(x: Int, y: Int) { /*...*/ }
shift(
    25,
    20, // завершающая запятая
)
val colors = listOf(
    "red",
    "green",
    "blue", // завершающая запятая
)

Свойства и параметры класса

class Customer(
    val name: String,
    val lastName: String, // завершающая запятая
)
class Customer(
    val name: String,
    lastName: String, // завершающая запятая
)

Параметры функции

fun powerOf(
    number: Int, 
    exponent: Int, // завершающая запятая
) { /*...*/ }
constructor(
    x: Comparable<Number>,
    y: Iterable<Number>, // завершающая запятая
) {}
fun print(
    vararg quantity: Int,
    description: String, // завершающая запятая
) {}

Параметры с необязательным типом (включая сеттеры)

val sum: (Int, Int, Int) -> Int = fun(
    x,
    y,
    z, // завершающая запятая
): 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, // завершающая запятая
    ]

Параметры в лямбдах

fun main() {
    val x = {
            x: Comparable<Number>,
            y: Iterable<Number>, // завершающая запятая
        ->
        println("1")
    }
    println(x)
}

when

fun isReferenceApplicable(myReference: KClass<*>) = when (myReference) {
    Comparable::class,
    Iterable::class,
    String::class, // завершающая запятая
        -> true
    else -> false
}

Литералы коллекции

annotation class ApplicableFor(val services: Array<String>)
@ApplicableFor([
    "serializer",
    "balancer",
    "database",
    "inMemoryCache", // завершающая запятая
])
fun run() {}

Type arguments

fun <T1, T2> foo() {}
fun main() {
    foo<
            Comparable<Number>,
            Iterable<Number>, // завершающая запятая
            >()
}

Type parameters

class MyMap<
        MyKey,
        MyValue, // завершающая запятая
        > {}

Destructuring declarations

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()

Документация комментариями

Для длинных документационных комментариев поместите открывающий символ /** в отдельную строку и начинайте каждую последующую строку со звездочки.

/**
 * Это документационный комментарий
 * на нескольких строках.
 */

Короткие комментарии могут быть размещены в одной строке.

/** Это короткий документационный комментарий. */

Следует избегать использования тегов @param и @return. Вместо этого включите описание параметров и возвращаемых значений непосредственно в документационный комментарий и добавьте ссылки на параметры везде, где они упоминаются. Используйте @param и @return только тогда, когда требуется длинное описание, которое не вписывается в основной текст.

// Не делайте так:

/**
 * Возвращает абсолютное значение заданного числа.
 * @param number Число, для которого будет найдено абсолютное значение.
 * @return Абсолютное значение.
 */
fun abs(number: Int): Int { /*...*/ }

// Делайте так:

/**
 * Возвращает абсолютное значение заданного [number].
 */
fun abs(number: Int): Int { /*...*/ }

Избегайте избыточных конструкций

В общем случае, если определенная синтаксическая конструкция в Kotlin является необязательной и выделена IDE как избыточная, вы должны опустить ее в своем коде. Не оставляйте ненужные синтаксические элементы в коде просто "для ясности".

Возвращаемый тип Unit

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

fun foo() { // ": Unit" опущено

}

Точки с запятой

Опускайте точки с запятой везде, где это возможно.

Строковые шаблоны

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

println("У $name уже ${children.size} детей")

Идиоматическое использование функций языка

Неизменность

Предпочтительнее использовать неизменяемые данные. Всегда объявляйте локальные переменные и свойства как val, а не var, если они не будут изменены после инициализации.

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

// Плохо: использование изменяемого типа коллекции для значения, которое не будет изменено
fun validateValue(actualValue: String, allowedValues: HashSet<String>) { ... }

// Хорошо: использование неизменяемого типа коллекции
fun validateValue(actualValue: String, allowedValues: Set<String>) { ... }

// Плохо: arrayListOf() возвращает ArrayList<T>, который является изменяемым типом
val allowedValues = arrayListOf("a", "b", "c")

// Хорошо: listOf() возвращает List<T>
val allowedValues = listOf("a", "b", "c")

Значения параметров по умолчанию

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

// Плохо
fun foo() = foo("a")
fun foo(a: String) { /*...*/ }

// Хорошо
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 vs when

Для двоичных условий лучше использовать if. Например, используйте этот синтаксис с if:

if (x == null) ... else ...

вместо этого с when:

when (x) {
    null -> // ...
    else -> // ...
}

Предпочтительнее использовать when, если есть три и более варианта.

Обнуляемые логические значения в условиях

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

Циклы

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

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

Циклы для диапазонов

Используйте функцию until, чтобы использовать открытый диапазон в цикле.

for (i in 0..n - 1) { /*...*/ }  // плохо
for (i in 0 until n) { /*...*/ }  // хорошо

Строки

Используйте строковые шаблоны вместо конкатенации строк.

Предпочитайте многострочные строки встраиванию escape-последовательностей \n в обычные строковые литералы.

Чтобы сохранить отступ в многострочных строках, используйте trimIndent, когда результирующая строка не требует внутреннего отступа, и trimMargin, когда требуется внутренний отступ.

println("""
    Not
    trimmed
    text
    """
       )

println("""
    Trimmed
    text
    """.trimIndent()
       )

println()

val a = """Trimmed to margin text:
          |if(a > 1) {
          |    return a
          |}""".trimMargin()

println(a)

См. Многострочные строки Java и Kotlin, чтобы узнать разницу между ними в Java и Kotlin.

Функции vs Свойства

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

Предпочтительно использовать свойства вместо функций, если лежащий в основе алгоритм:

  • не выбрасывает исключений
  • имеет O(1) сложность
  • не требует больших затрат на выполнение (или результат вычислений кэшируется при первом вызове)
  • возвращает одинаковый результат

Функции-расширения

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