Соглашение о стилистике кода
Общеизвестные и простые в использовании соглашения о стилистике кода жизненно важны для любого языка программирования. Здесь мы предоставляем рекомендации по стилю кода и его организации для проектов, использующих Kotlin.
Настройка стиля в IDE
Две самые популярные IDE для Kotlin - IntelliJ IDEA и Android Studio обеспечивают широкую поддержку стиля кода. Вы можете настроить их для автоматического форматирования вашего кода в соответствии с заданным стилем.
Примените руководства по стилю
- Перейдите в раздел Settings/Preferences | Editor | Code Style | Kotlin.
- Нажмите Set from….
- Выберите Kotlin style guide.
Убедитесь, что ваш код соответствует руководству по стилю
- Перейдите в раздел Settings/Preferences | Editor | Inspections | Kotlin.
- Отройте Kotlin | Style issues.
- Включите проверку 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 рекомендуется до тех пор, пока эти объявления тесно связаны друг с другом семантически, а размер файла остается разумным (не более нескольких сотен строк).
В частности, при определении функций-расширения для класса, которые актуальны для всех клиентов этого класса, поместите их в один файл с самим классом. При определении функций-расширения, которые имеют смысл только для конкретного клиента, поместите их рядом с кодом этого клиента. Избегайте создания файлов только для хранения всех расширений какого-либо класса.
Схема классов
Содержимое класса должно располагаться в следующем порядке:
- Объявления свойств и блоки инициализации
- Дополнительные конструкторы
- Объявления методов
- Вспомогательный объект
Не сортируйте объявления методов по алфавиту или по видимости и не отделяйте обычные методы от методов-расширения. Вместо этого соберите связанный код вместе, чтобы тот, кто читает класс сверху вниз, мог следовать логике происходящего. Выберите порядок (либо сначала материал более высокого уровня, либо наоборот) и придерживайтесь его.
Поместите вложенные классы рядом с кодом, который их использует. Если эти классы предназначены для внешнего использования и на них нет ссылок внутри класса, поместите их в конце, после сопутствующего объекта.
Схема реализации интерфейса
При реализации интерфейса располагайте элементы в том же порядке, что и элементы интерфейса (при необходимости чередуйте их с дополнительными частными методами, используемыми для реализации).
Расположение перегрузки
Всегда помещайте перегрузки друг после друга в классе.
Правила наименований
Правила наименования пакетов и классов в 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
, andwhile
) и соответствующей открывающей скобкой.
- Не ставьте пробел перед открывающей скобкой в объявлении основного конструктора, объявлении метода или вызове метода.
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 для всех общедоступных участников, за исключением переопределений, для которых не требуется никакой новой документации (для поддержки создания документации для библиотеки)