Привет, Хабр! Предлагаю вашему вниманию авторский перевод страницы документации Kotlin Coding Conventions от JetBrains.
Содержание:
- Применение Style guide в Intellij Idea
- Структура проекта
- Правила наименования
- Форматирование
- Горизонтальные пробелы
- Двоеточие
- Форматирование объявления классов
- Модификаторы
- Форматирование аннотаций
- Файловые аннотации
- Форматирование функций
- Форматирование однострочного выражения
- Форматирование свойствия
- Форматирование управляющий инструкций
- Форматирование вызовов методов
- Форматирование цепочного вызова функций
- Форматирование лямбда-выражений
- Оформление документации
- Избегание ненужных конструкций
- Идиоматическое использование языковых особенностей
- Неизменяемость
- Значение параметров по умолчанию
- Создание псевдонимов [Type alias]
- Именование параметров в лямбда-выражениях
- Возвращение значений из лямбда-выражений
- Вызов методов с именованием аргументов
- Правила для управляющих конструкций с условиями
if
противwhen
- Использование значений Boolean? в условных операторах
- Использование циклов
- Циклы для диапазонов
- Форматирование строк
- Функции против свойств
- Использование функций расширений
- Использование инфиксных функций
- Функции фабрики
- Платформенные типы
Использование функций apply
/with
/run
/also
/let
- Правила при создании библиотек
Применение Style guide в Intellij Idea
Для применения форматирования в Intellij Idea в соответствии с текущим руководством, нужно установить Kotlin plugin версии 1.2.20 или новее, перейти в Settings | Editor | Code Style | Kotlin, нажать на ссылку "Set from..." в верхнем правом углу и выбрать "Predefined style" / Kotlin style guide" из выпадающего меню.
Чтобы проверить что ваш код форматируется согласно рекомендованному стилю, перейдите в настройки проверок (inspection setting) и включите проверку "Kotlin | Style issues | File is not formatted according to project settings". Другие правила проверки, например проверки соглашения наименования, включены по умолчанию.
Структура проекта
Структура папок
В проектах с применением разных языков, файлы с кодом на Kotlin должны лежать в той же папке что и код на других языках и использовать ту же файловую структуру которая принята для основного языка. Например для Java файлы должны лежать в структуре папок согласно наименованию пакета.
В проектах с использование только Kotlin рекомендуемая структура папок: использовать папки для организации пакетов с пропуском корневой директории, т.е. если весь код в проекте находиться в пакете "org.example.kotlin" и его под пакетах, то файлы с исходным кодом принадлежащие пакету "org.example.kotlin" должны лежать в корневой директории проекта, а файлы с исходным кодом пакета "org.example.kotlin.foo.bar" должны лежать в поддиректории "foo/bar" относительно корня проекта.
Наименование файлов с исходным кодом
Если Kotlin файл содержит только один класс (возможно имеющий отношения к верхнеуровневому объявлению [top-level declaration]), то он должен быть назван, так же как и класс с расширением .kt
. Если файл содержит несколько классов или имеет только верхнеуровневые объявления, выбирайте имя, которое описывает что содержит файл и называйте файл соответствующе. Используйте camel hump с заглавной первой буквы для именования файлов (например ProcessDeclarations.kt
).
Имя файлов должно описывать, что делает код находящийся в файле. То есть вы должны избегать бессмысленных слов наподобие "Util" для наименования файлов.
Организация файлов с исходным кодом
Размещение нескольких объявлений (классов, функций или свойств верхнего уровня) в одном и том же исходном файле Kotlin приветствуется, если эти объявления тесно связаны друг с другом семантически и размер файла остается разумным (не более нескольких сотен строк).
В частности, при определении функций расширения для класса, которые относятся ко всем аспектам применения этого класса, поместите их в тот же файл, где определен сам класс. При определении функций расширения, которые имеют смысл только для конкретного контекста использования этого класса, поместите их рядом с кодом использующим эту функцию расширение. Не создавайте файлы только для хранения "всех расширений Foo".
Структура класса
Как правило, содержимое класса сортируется в следующем порядке:
- Объявления свойств и блоки инициализаторов
- Вторичные конструкторы
- Объявления методов
- Объекты компаньоны
Не сортируйте объявления методов по алфавиту или видимости и не отделяйте обычные методы от методов расширения. Вместо этого поместите логически связанный код вместе, чтобы кто-то, кто читает класс сверху вниз, мог следовать логике того, что происходит. Выберите один порядок сортировки (высокоуровневый код вначале [higher-level stuff first], а детали потом или наоборот) и придерживайтесь его.
Поместите вложенные классы рядом с кодом, использующим эти классы. Если классы предназначены для внешнего использования и на них нет ссылок внутри класса, поместите их в конец после объекта компаньона.
Структура реализации интерфейсов
При реализации интерфейса сохраняйте ту же структуру, что и реализуемый интерфейс (при необходимости чередуя его с дополнительными закрытыми методами, используемыми для реализации)
Структура переопределений
Переопределения всегда помещайте вместе, друг за другом.
Правила наименования
Kotlin следует тем же соглашениям об именовании что и Java. В частности:
Имена пакетов строчными буквами и не используйте подчеркивание (org.example.myproject). Использование имен из нескольких слов, как правило, не рекомендуется, но если вам нужно использовать несколько слов, вы можете либо просто объединить их вместе, либо использовать camel hump (org.examle.myProject).
Названия классов и объектов начинаются с заглавной буквы и используют camel hump:
open class DeclarationProcessor { ... }
object EmptyDeclarationProcessor : DeclarationProcessor() { ... }
Наименование функций
Имена функций, свойств и локальных переменных начинаются с буквы нижнего регистра и не содержат символов подчеркивания:
fun processDeclarations() { ... }
var declarationCount = ...
Исключение: функции фабрики, используемые для создания экземпляров классов, могут иметь то же имя, что и создаваемый класс:
abstract class 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"
Имена верхнего уровня или свойств объекта, которые содержат объекты с поведением или изменяемыми данными, должны использовать обычные имена в camel hump:
val mutableCollection: MutableSet<String> = HashSet()
Имена свойств, содержащих ссылки на Singleton объекты, могут использовать тот же стиль именования, что и объявления классов:
val PersonComparator: Comparator<Person> = ...
Для перечислений можно использовать имена, написанные заглавными буквами разделенные нижним подчеркиванием или в стиле camel hump, начинающиеся с заглавной буквы, в зависимости от использования.
enum class Color { RED, GREEN }
enum class Color { RedColor, GreenColor }
Примечание переводчика: Только не смешивайте разные стили. Выберите один стиль и придерживайтесь его в своем проекте.
Именование скрытых свойств
Если класс имеет два свойства, которые концептуально одинаковы, но одно является частью открытого 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
).
Форматирование
В большинстве случаев Kotlin следует соглашениям форматирования принятым в Java.
Используйте 4 пробела для отступа. Не используйте табуляцию.
Для фигурных скобок поместите открывающую фигурную скобку в конец строки, где начинается конструкция, и закрывающую фигурную скобку на отдельной строке, выровненной по горизонтали с открывающей конструкцией.
if (elements != null) {
for (element in elements) {
// ...
}
}
(Примечание: в Kotlin точка с запятой необязательна, поэтому перенос строк важен. Конструкция языка предполагает фигурные скобки в стиле Java, и вы можете встретить неожиданное поведение при исполнении кода, если пытаетесь использовать другой стиль форматирования.)
Горизонтальные пробелы
Поместите пробелы вокруг бинарных операторов (a + b)
. Исключение: не ставьте пробелы вокруг оператора "range to" (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?.бар()
Поставьте пробел после двойного слеша для комментария //
:
// это комментарий
Не ставьте пробелы вокруг угловых скобок, используемых для указания параметров типа:
Class Map<K, V> { ... }
Не ставьте пробелы вокруг двойного двоеточия для указания ссылки на метод класса ::
:
Foo::class
String::length
Не ставьте пробел перед ?
используется для пометки типа, допускающего значение null
:
String?
Как правило, избегайте любого типа выравнивания по горизонтали. Переименование идентификатора в имя другой длины не должно влиять на форматирование кода.
Двоеточие
Ставьте пробел перед двоеточием :
в следующих случаях:
- когда он используется для разделения типа и супер типа;
abstract class Foo<out T : Any>
- при делегировании конструктору суперкласса или другому конструктору того же класса;
constructor(x: String) : super(x) { ... }
constructor(x: String) : this(x) { ... }
- после ключевого слова object.
val x = object : IFoo { ... }
Не ставьте пробел перед :
когда он отделяет объявление и его тип.
abstract fun foo(a: Int): T
Всегда ставьте пробел после :
.
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() { ... }
}
Используйте обычный отступ (4 пробела) для параметров конструктора.
Обоснование: это гарантирует, что свойства, объявленные в основном конструкторе, имеют тот же отступ, что и свойства, объявленные в теле класса.
Модификаторы
Если объявление содержит несколько модификаторов, всегда располагайте их в следующем порядке:
public / protected / private / internal
expect / actual
final / open / abstract / sealed / const
external
override
lateinit
tailrec
vararg
suspend
inner
enum / annotation
companion
inline
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
}
Используйте обычный отступ (4 пробела) для параметров функции.
Обоснование: согласованность с параметрами конструктора
Предпочтительнее использовать выражение без фигурных скобок для функций состоящим из одной строки.
fun foo(): Int { // bad
return 1
}
fun foo() = 1 // good
Форматирование однострочного выражения
Если тело однострочной функции не помещается в той же строке, что и объявление, поставьте знак = в первой строке. Сделайте отступ тела выражения на 4 пробела.
fun f(x: String) =
x.length
Форматирование свойствия
Для простых свойств предназначенных только для чтения предпочтительно использовать однострочное форматирование:
val isEmpty: Boolean get() = size == 0
Для более сложных свойств, всегда используйте get
и set
на отдельных строках:
val foo: String
get() { ... }
Для свойств с инициализацией, если инициализатор слишком длинный, добавьте перенос строки после знака равно и отступ в четыре пробела для строки инициализации:
private val defaultCharset: Charset? =
EncodingRegistry.getInstance().getDefaultCharsetForPropertiesFiles(file)
Форматирование управляющий инструкций
Если условие в управляющей инструкции if
илиwhen
многострочное, всегда используйте фигурные скобки вокруг тела оператора. Отступ каждой последующей строки условия на 4 пробела относительно начала оператора. Поместите закрывающие скобки условия вместе с открывающей фигурной скобкой в отдельную строку:
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
инструкции на одной строке без фигурных скобок.
when (foo) {
true -> bar() // good
false -> { baz() } // bad
}
Форматирование вызовов методов
При использовании длинного списка параметров, поместите перенос строки после круглой скобки. Используйте отступ в 4 пробела и группируйте аргументы связанные логически на одной строке.
drawSquare(
x = 10, y = 10,
width = 100, height = 100,
fill = true
)
Используйте пробелы вокруг знака равно между именем параметра и его значением.
Форматирование цепочного вызова функций
Когда используете цепочные вызовы, помещайте .
или ?.
операторы на новой строке с одним отступов в 4 пробела:
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)
}
Оформление документации
При использовании многострочной документации поместите /**
на отдельной строке, и начинайте каждую последующую строку со звездочки:
/**
* 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) = ...
// Do this instead:
/**
* Returns the absolute value of the given [number].
*/
fun abs(number: Int) = ...
Избегание ненужных конструкций
Многие синтактические конструкции в Kotlin опциональны и подсвечиваются средой разработки как ненужные, не следует использовать их в своем коде только для того, чтобы придать "ясность" вашему коду.
Использвание ключевого слова 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") { ... }
Создание псевдонимов [Type alias]
Если у вас есть функциональный тип или тип с параметрами типа, который используется несколько раз в коде, лучше определить для него псевдоним:
typealias MouseClickHandler = (Any, MouseEvent) -> Unit
typealias PersonIndex = Map<String, Person>
Именование параметров в лямбда-выражениях
В лямбда-выражениях, которые являются короткими и не вложенными, рекомендуется использовать соглашение it
наименования вместо явного объявления параметра. Во вложенных лямбда-выражениях с параметрами параметры всегда должны именоваться явно.
Возвращение значений из лямбда-выражений
Избегайте использования нескольких помеченных точек возврата в лямбде. Рассмотрите возможность реструктуризации лямбда-выражения таким образом, чтобы оно имело единую точку выхода. Если это невозможно или недостаточно ясно, рассмотрите возможность преобразования лямбда-выражения в анонимную функцию.
Не используйте помеченный возврат(@
) для последнего оператора в лямбде.
Вызов методов с именованием аргументов
Используйте синтаксис именованных аргументов, когда метод принимает несколько параметров одного и того же примитивного типа, или для параметров типа boolean
, если значение всех параметров не абсолютно ясно из контекста.
drawSquare(x = 10, y = 10, width = 100, height = 100, fill = true)
Правила для управляющих конструкций с условиями
Предпочитайте использовать возврат конструкции try
, if
и when
, перед явным использованием с ключевым словом return
:
return if (x) foo() else bar() // Такая конструкция предпочтительнее, чем конструкция ниже
if (x)
return foo()
else
return bar()
//
return when(x) {
0 -> "zero"
else -> "nonzero"
} // Такая конструкция предпочтительнее, чем конструкция ниже
when(x) {
0 -> return "zero"
else -> return "nonzero"
}
if
против when
Лучше использовать if
при использовании двух возможных условиях вместо when
when (x) {
null -> ...
else -> ...
}
if (x == null) ... else ... // Такой синтаксис понятнее и короче
Если возможных условий больше чем два, лучше использвать when
конструкцию.
Использование значений Boolean? в условных операторах
Если необходимо использовать Boolean?
значение в условном операторе, используйте проверки сравнения с ключевым словом if (value == true)
или if (value == false)
, вместо проверок типа if (value ?: false)
или if (value != null && value)
.
Использование циклов
Предпочтительнее использовать высокоуровневые функции такие как filtet
, map
и т.д. вместо циклов. Исключение: forEach
(предпочтительнее использовать обычный цикл for
если источник итерации не null или forEach
не часть большой цепочки преобразований)
При выборе между сложным выражением, использующим несколько высокоуровневых функций, и циклом необходимо понимать и учитывать стоимость каждой операций, и делать выбор из соображения производительности и читаемости.
Циклы для диапазонов
Используйте until
для циклов исключающих границы (открытые интервалы):
for (i in 0..n - 1) { ... } // bad
for (i in 0 until n) { ... } // good
Форматирование строк
Предпочтительнее использовать строчные шаблоны для соединения строк.
Вместо встраивания \n
escape-последовательностей в обычные строковые литералы используйте многострочный тип записи.
Чтобы сохранить отступ в многострочных строках, используйте trimIndent
, когда результирующая строка не требует внутреннего отступа, или trimMargin
, когда требуется внутренний отступ:
assertEquals(
"""
Foo
Bar
""".trimIndent(),
value
)
val a = """if(a > 1) {
| return a
|}""".trimMargin()
Функции против свойств
В некоторых случаях функции без аргументов могут быть взаимозаменяемы со свойствами только для чтения. Хотя их семантика похожа, есть некоторые стилистические соглашения о том, когда предпочесть один другому.
Предпочитайте свойство функции при базовом алгоритме:
- не бросает исключений
- легко рассчитывается (или кэшируется при первом запуске)
- возвращает тот же результат при каждом вызове, если состояние объекта не изменилось
Использование функций расширений
Используйте функции расширения легко и свободно. Каждый раз, когда у вас есть функция, которая работает в основным объектом, рассмотрите возможность сделать ее функцией расширения, принимающей этот объект в качестве приемника. Чтобы минимизировать загрязнение 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 и могущего вернуть какnull
значение, так и неnull
public
функция/метод, возвращающие значение платформенного типа, должна явно объявлять тип Kotlin:
fun apiCall(): String = MyJavaApi.getProperty("name")
Любое свойство (package-level или class-level) инициализирующееся вызовом платформенного типа должно быть явно указано типом Kotlin:
class Person {
val name: String = MyJavaApi.getProperty("name")
}
Локальные переменные, которые инициализируются платформенными типами, могут объявляется без явного указания Kotlin типа:
fun main() {
val name = MyJavaApi.getProperty("name")
println(name)
}
Использование функций apply
/with
/run
/also
/let
Kotlin предоставляет множество функций для выполнения блока кода в контексте данного объекта. Чтобы выбрать правильную функцию, учитывайте следующее:
- Вы вызываете методы нескольких объектов в блоке или передаете экземпляр объекта контекста в качестве аргумента? Если это так, используйте одну из функций, которая позволяет получить доступ к объекту контекста как к
it
, а не кthis
(also
илиlet
). Используйтеalso
, если приемник вообще не используется в блоке.
// Context object is 'it'
class Baz {
var currentBar: Bar?
val observable: Observable
val foo = createBar().also {
currentBar = it // Accessing property of Baz
observable.registerCallback(it) // Passing context object as argument
}
}
// Receiver not used in the block
val foo = createBar().also {
LOG.info("Bar created")
}
// Context object is 'this'
class Baz {
val foo: Bar = createBar().apply {
color = RED // Accessing only properties of Bar
text = "Foo"
}
}
- Каким должен быть результат вызова? Если результатом должен быть объект контекста, используйте
apply
илиalso
. Если вам нужно вернуть значение из блока, используйтеwith
,let
илиrun
.
// Return value is context object
class Baz {
val foo: Bar = createBar().apply {
color = RED // Accessing only properties of Bar
text = "Foo"
}
}
// Return value is block result
class Baz {
val foo: Bar = createNetworkConnection().let {
loadBar()
}
}
- Объект контекста допускает значение null или вычисляется в результате цепочки вызовов? Если это так, используйте
apply
,let
илиrun
. В противном случае, используйтеwith
илиalso
.
// Context object is nullable
person.email?.let { sendEmail(it) }
// Context object is non-null and accessible directly
with(person) {
println("First name: $firstName, last name: $lastName")
}
Правила при создании библиотек
При написании библиотек рекомендуется следовать дополнительному набору правил для обеспечения стабильности API:
- Всегда явно указать видимость члена (чтобы избежать случайного раскрытия объявления в качестве публичного API)
- Всегда явно указывать возвращаемые типы из функций и типы свойств (чтобы избежать случайного изменения типа возвращаемых данных при изменении реализации)
- Предоставление документации KDoc для всего public api, за исключением переопределений, которые имеют свою документацию и не требуют её обновления/уточнения