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

Содержание

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

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

Применение стиля

  1. Перейдите в Settings/Preferences | Editor | Code Style | Kotlin.

  2. Щелкните Set from....

  3. Выберите Kotlin style guide.

Убедитесь, что ваш код соответствует выбранному руководству.

  1. Перейдите в Settings/Preferences | Editor | Inspections | General.

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

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

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

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

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

Имена файлов

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

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

Внутренняя организация файла

Мы приветствуем размещение нескольких программных сущностей (классы, верхнеуровневые функции / переменные) в одном файле, если эти сущности тесно связаны друг с другом семантически, а размер файла остаётся разумным и не превышает нескольких сотен строк.

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

Архитектура класса

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

  1. Объявления полей и блоки инициализации.

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

  3. Функции.

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

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

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

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

Схема перегрузки функций

Всегда размещайте перегружаемые функции рядом друг с другом.

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

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

Имена пакетов всегда пишутся строчными буквами без символов подчёркивания (org.example.project). Использовать имена, состоящие из нескольких слов, как правило, не рекомендуется, но если это необходимо, Вы можете соединить их вместе или использовать Camel Case (org.example.myProject).

Имена классов используют Upper Camel Case:

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

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

Названия функций

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

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 / верхнеуровневые поля / поля классов без геттера, содержащие неизменяемые данные), пишутся в Screaming Snake Case:

const val MAX_COUNT = 8
val USER_NAME_FIELD = "UserName"

Верхнеуровневые поля или поля классов, содержащие поведение / изменяемые данные, пишутся в Camel Case:

val mutableCollection: MutableSet<String> = HashSet()

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

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

Для перечислений (enum), допускается использовать или Screaming Snake Case (enum class Color { RED, GREEN }), или Upper Camel Case.

Имена полей-прокладок

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

Форматирование

Отступы

Для отступа используйте 4 пробела. Не используйте табуляцию.

Применяя фигурные скобки, открывающую скобку поместите в конец строки открывающей конструкции, а закрывающую скобку поместите с новой строки; выровняйте её по горизонтали с открывающей конструкцией:

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

  • Ставьте пробел после // : // Это комментарий

  • Не ставьте пробелы вокруг угловых скобок, используемых для указания параметров типа: class Map<K, V> { ... }

  • Не ставьте пробелы вокруг :: : Foo::class, String::length

  • Не ставить пробел перед ? используется для обозначения типа, допускающего значение null: String?

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

Двоеточие

Ставьте пробел перед : в следующих случаях:

  • при использовании для разделения типа и супертипа

  • при делегировании конструктору суперкласса или другому конструктору того же класса

  • после ключевого слова объекта

Не ставьте пробел перед :, когда оно разделяет объявление и его тип.

Всегда ставьте пробел после :.

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() { /*...*/ }

Аннотации @file

Аннотации @file помещаются после комментария к файлу (если есть), перед оператором пакета и отделяются от пакета пустой строкой (чтобы подчеркнуть тот факт, что они нацелены на файл, а не на пакет).

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

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

  • Это делает контроль версий чище, так как все внимание сосредоточено на измененном значении.

  • Это упрощает добавление и изменение порядка элементов — нет необходимости добавлять или удалять запятую, если вы манипулируете элементами.

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

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

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

Перечисления
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, если они не изменяются после инициализации.

// 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 вместо явного объявления параметра. Во вложенных лямбда-выражениях с параметрами всегда объявляйте параметры явно.

return в лямбдах

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

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

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

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

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 значение, допускающее значение null, в условном операторе, используйте проверки if (value == true) или if (value == false).

Циклы

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

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

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

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

for (i in 0..n - 1) { /*...*/ }  // bad
for (i in 0 until n) { /*...*/ }  // good

Строки

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

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

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

fun main() {
    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.

Функции или свойства?

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

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

  • не выбрасывает исключение

  • дёшево вычислить (или кэшировать при первом запуске)

  • возвращает тот же результат при вызовах, если состояние объекта не изменилось

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

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

Scope-функции apply/with/run/also/let

Kotlin предоставляет набор функций для выполнения блока кода в контексте заданного объекта: letrunwithapply, и also. Руководство по выбору правильной scope-функции для вашего случая см. в разделе Scope-функции.

Конвенция для библиотек

При написании библиотек рекомендуется соблюдать дополнительный набор правил для обеспечения стабильности API:

  • Всегда явно указывайте видимость члена (чтобы избежать случайного раскрытия объявлений как общедоступного API)

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

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

Комментарии (5)