В репозитории приведен набор соглашений по оформлению кода на языке Kotlin.
Этот список правил расширяет предложенные Google и командой разработки Kotlin гайды и пересматривает в них некоторые неоднозначные моменты.
- Длина строки
- Правила именования
- Порядок следования модификаторов
- Форматирование выражений
- Функции
- Классы
- Аннотации
- Структура класса
- Форматирование лямбда-выражений
- Использование условных операторов
- Template header
- Файлы
- Максимальная длина строки: 120 символов.
- Неизменяемые поля в (Companion) Object и compile-time константы именуются в стиле SCREAMING_SNAKE_CASE
- Для полей View из Kotlin Extension используется стиль lower_snake_case
- Любые другие поля именуются в стиле lowerCamelCase
- Функции именуются в стиле lowerCamelCase
- Классы именуются в стиле UpperCamelCase
- Пакеты именуются в стиле lower_snake_case
- override
- public / protected / private / internal
- final / open / abstract
- const / lateinit
- inner
- enum / annotation / sealed / data
- companion
- inline
- infix
- operator
При переносе на новую строку цепочки вызова методов символ .
или оператор ?.
переносятся на следующую строку, property при этом разрешается оставлять на одной строке:
val collectionItem = source.collectionItems
?.dropLast(10)
?.sortedBy { it.progress }
Элвис оператор ?:
при разрыве выражения также переносится на новую строку:
val promoItemDistanceTradeLink: String = promoItem.distanceTradeLinks?.appLink
?: String.EMPTY
При описании переменной с делегатом, не помещающимися на одной строке, оставлять описание с открывающейся фигурной скобкой на одной строке, перенося остальное выражение на следующую строку:
private val promoItem: MarkPromoItem by lazy {
extractNotNull(BUNDLE_FEED_UNIT_KEY) as MarkPromoItem
}
- Позволительно использовать функцию с одним выражением только в том случае, если она помещается в одну строку.
- Использование именованного синтаксиса аргументов остается на усмотрение разработчика. Стоит руководствоваться сложностью вызываемого метода: если вызов метода с переданными в него параметрами понятен и очевиден, нет необходимости использовать именованные параметры. При написании именованных аргументов делать перенос каждого аргумента на новую строку с двойным отступом и переносом закрывающейся круглой скобки на следующую строку:
runOperation(
method = operation::run,
consumer = consumer,
errorHandler = errorHandler,
tag = tag,
cache = cache,
cacheMode = cacheMode
)
- При необходимости разрыва строки осуществляется перенос каждого аргумента функции на новую строку с двойным отступом и переносом закрывающей круглой скобки на следующую строку.
- Всегда использовать полный вариант с написанием
invoke
у переменной вместо использования сокращенного варианта:
fun runAndCall(expression: () -> Unit): Result {
val result = run()
//Bad
expression()
//Good
expression.invoke()
return result
}
- При необходимости разрыва строки осуществляется перенос каждого параметра класса на новую строку с двойным отступом и переносом закрывающейся круглой скобки на следующую строку:
data class CategoryStatistic(
val id: String,
val title: String,
val imageUrl: String,
val percent: Double
) : Serializable
- Если в описании класса родительский класс не помещается на одной строке, также осуществляется перенос каждого из его параметров на новую строку с переносом закрывающей круглой скобки на следующую строку.
- Если описание класса не помещается в одну строку, и класс реализует несколько интерфейсов, то применять стандартные правила переносов, т.е. делать перенос только в случае, когда описание не помещается на одну строку, при этом продолжать перечисление интерфейсов на следующей строке.
- Использование именованного синтаксиса аргументов остается на усмотрение разработчика. Стоит руководствоваться сложностью используемого конструктора класса: если конструктор с переданными в него параметрами понятен и очевиден, нет необходимости использовать именованные параметры.
- Аннотации как правило располагаются над описанием класса/поля/метода, к которому они применяются.
- Если к классу/полю/методу есть несколько аннотаций, размещать каждую аннотацию с новой строки:
@JsonValue
@JvmField
var promoItem: PromoItem? = null
- Если к полю/методу применяется только одна аннотация без параметров, указывать ее над полем/методом.
- Аннотации, относящиеся к файлу, располагаются сразу после комментария к файлу, и перед package, с разделителем в виде пустой строки.
- companion object
- Поля: abstract, override, public, internal, protected, private
- Блок инициализации: init, конструкторы
- Абстрактные методы
- Переопределенные методы родительского класса (желательно в том же порядке, в каком они следуют в родительском классе)
- Реализации методов интерфейсов (желательно в том же порядке, в каком они следуют в описании класса, соблюдая при этом порядок описания этих методов в самом интерфейсе)
- public методы
- internal методы
- protected методы
- private методы
- inner классы
- При возможности оставлять лямбда-выражение на одной строке, используя
it
в качестве аргумента. - При использовании лямбда-функции в качестве аргумента выносить её за скобки если этот параметр единственный.
- Если выражение возможно написать с передачей метода по ссылке, передавать метод по ссылке (Доступно с 1.1):
viewPager.adapter = QuestAdapter(quest, this::onQuestClicked)
- При написании лямбда-выражения более чем в одну строку всегда использовать именованный аргумент, вместо
it
:
viewPager.adapter = QuestAdapter(quest, { quest ->
onQuestClicked(quest)
})
- Неиспользуемые параметры лямбда-выражений всегда заменять символом
_
. - Избегать использования Destructuring Declarations в лямбда-выражениях.
Не обрамлять if
выражения в фигурные скобки только если условный оператор if
помещается в одну строку.
При возможности использовать условные операторы, как выражение:
return if (condition) foo() else bar()
У оператора when
для коротких выражениях ветвей условия размещать их на одной строке с условием без фигурных скобок:
when (someCondition) {
0 -> fooFunction()
1 -> barFunction()
else -> exitFunction()
}
Если хоть в одной из ветвей есть фигурные скобки, обрамлять ими все остальные ветки.
У оператора when
для блоков с выражениями, которые состоят более чем из одной строки использовать для этих блоков фигурные скобки и отделять смежные case-блоки пустой строкой:
when (feed.type) {
FeedType.PERSONAL -> {
with(feed as PersonalFeed) {
datePopupStart = dateBegin
datePopupEnd = dateEnd
}
}
FeedType.SUM -> {
with(feed as SumFeed) {
datePopupStart = dateBegin
datePopupEnd = dateEnd
}
}
FeedType.CARD -> {
with(feed as CardFeed) {
datePopupStart = dateBegin
datePopupEnd = dateEnd
}
}
else -> {
Feed.EMPTY
}
}
- Не использовать Template Header для классов (касается авторства и даты создания файла).
- Возможно описывать несколько классов в одном файле только для
sealed
классов. В остальных случаях для каждого класса необходимо использовать отдельный файл (не относится кinner
классам).