В репозитории приведен набор соглашений по оформлению кода на языке Kotlin.
Этот список правил расширяет предложенные Google и командой разработки Kotlin гайды и пересматривает в них некоторые неоднозначные моменты.
- Длина строки
- Правила именования
- Форматирование выражений
- Функции
- Классы
- Структура класса
- Аннотации
- Использование условных операторов
- Template header
- Частые ошибки
Рекомендуемая длина строки: 100 символов.
Максимальная длина строки: 120 символов.
Пакеты именуются одним словом в стиле lowercase. Если необходимо использовать несколько слов, то просто склеиваем их вместе.
При переносе на новую строку цепочки вызова методов символ .
или оператор ?.
переносятся на следующую строку, property при этом разрешается оставлять на одной строке:
val collectionItems = source.collectionItems
?.dropLast(10)
?.sortedBy { it.progress }
Элвис оператор ?:
в многострочном выражении также переносится на новую строку:
val throwableMessage: String = throwable?.message
?: DEFAULT_ERROR_MESSAGE
throwable.message?.let { showError(it) }
?: showError(DEFAULT_ERROR_MESSAGE)
Если перед элвис оператором ?:
многострочная лямбда, желательно перенести также и лямбду:
// Good
throwable.message
?.let { message ->
...
showError(message)
}
?: showError(DEFAULT_ERROR_MESSAGE)
// Not recommended
throwable.message?.let { message ->
...
showError(message)
}
?: showError(DEFAULT_ERROR_MESSAGE)
При описании переменной с делегатом, не помещающимися на одной строке, оставлять описание с открывающейся фигурной скобкой на одной строке, перенося остальное выражение на следующую строку:
private val promoItem: MarkPromoItem by lazy {
extractNotNull(BUNDLE_FEED_UNIT_KEY) as MarkPromoItem
}
Позволительно использовать функцию с одним выражением только в том случае, если она помещается в одну строку.
Если по контексту не понятно назначение аргумента, то следует сделать его именованным.
runOperation(
method = operation::run,
consumer,
errorHandler,
tag,
cacheSize = 3,
cacheMode
)
calculateSquare(x = 6, y = 19)
getCurrentUser(skipCache = false)
setProgressBarVisible(true)
Если именованные аргументы не помещаются на одной строке, то следует переносить каждый аргумент на новую строку (как в примере выше).
Именуем все лямбды, принимаемые функцией в качестве аргументов (кроме случаев когда лямбда вынесена за круглые скобки), чтобы во время чтения кода было понятно назначение и ответственность каждой лямбды.
editText.addTextChangedListener(
onTextChanged = { text, _, _, _ ->
viewModel.onTextChanged(text?.toString())
},
afterTextChanged = { text ->
viewModel.onAfterTextChanged(text?.toString())
}
)
Полезно именовать аргументы одинаковых типов, чтобы случайно не перепутать их местами.
val startDate: Date = ..
val endDate: Date = ..
compareDates(startDate = startDate, endDate = endDate)
Полезно именовать аргумент при передаче null
.
setAdditionalArguments(arguments = null)
Всегда использовать полный вариант с написанием invoke
у переменной вместо использования сокращенного варианта:
fun runAndCall(expression: () -> Unit): Result {
val result = run()
// Bad
expression()
// Good
expression.invoke()
return result
}
По возможности передавать метод по ссылке:
viewPager.adapter = QuestAdapter(quest, onQuestClickListener = ::onQuestClicked)
При написании лямбда-выражения более чем в одну строку всегда использовать именованный аргумент, вместо it
:
viewPager.adapter = QuestAdapter(
quest,
onQuestClickListener = { quest ->
Log.d(..)
viewModel.onQuestClicked(quest)
}
)
Неиспользуемые параметры лямбда-выражений всегда заменять символом _
.
Если описание класса не помещается в одну строку, и класс реализует несколько интерфейсов, то применять стандартные правила переноса, т.е. делать перенос только в случае, когда описание не помещается на одну строку, при этом продолжать перечисление интерфейсов на следующей строке.
class MyFavouriteVeryLongClassHolder : MyLongHolder<MyFavouriteVeryLongClass>(), SomeOtherInterface, AndAnotherOne,
OneMoreVeryLongInteface, OneMore{
fun foo() { /*...*/ }
}
Использование именованных аргументов аналогично с функциями
- Поля: abstract, override, public, internal, protected, private
- Блок инициализации: init, конструкторы
- Абстрактные методы
- Переопределенные методы родительского класса (желательно в порядке их следования в родительском классе)
- Реализации методов интерфейсов (желательно в порядке добавления интерфейсов в класс и следования методов в каждом интерфейсе)
- Методы класса (в логическом порядке. Например, метод располагается после того, в котором впервые упомянут). Можно перемешивать с методами из пунктов 3, 4, 5.
- inner классы
- companion object
Аннотации располагаются над описанием класса/поля/метода, к которому они применяются.
Если к классу/полю/методу применяется несколько аннотаций, размещать каждую аннотацию с новой строки:
@JsonValue
@JvmField
var promoItem: PromoItem? = null
Аннотации к аргументам в конструкторе класса или объявлении функции можно писать на той же строке, что и соответствующий аргумент.
При этом если аннотаций к одному аргументу несколько, то все аннотации пишутся с новой строки, и соответствующий аргумент отделяется от других сверху и снизу пустыми строками.
data class UserInfo (
@SerializedName("firstName") val firstName: String? = null,
@SerializedName("secondName") val secondName: String? = null
)
@Entity(tableName = "users")
data class UserInfo (
@PrimaryKey val id: Int,
@SerializedName("firstName")
@ColumnInfo(name = "firstName")
val firstName: String? = null,
@SerializedName("secondName")
@ColumnInfo(name = "secondName")
val secondName: String? = null
)
Не обрамлять if
выражения в фигурные скобки только если условный оператор if
помещается в одну строку.
По возможности использовать условные операторы, как выражение:
return if (condition) foo() else bar()
В операторе when
ветки, состоящие более чем из одной строки, обрамлять фигурными скобками и отделять от других case-веток пустыми строками сверху и снизу.
when (feed.type) {
FeedType.PERSONAL -> startPersonalFeedScreen()
FeedType.SUM -> {
showSumLayout()
hideProgressBar()
}
FeedType.CARD -> startCardFeedScreen()
else -> showError()
}
Не использовать Template Header для классов (касается авторства и даты создания файла).
В первом примере получится строчка "null"
, это плохо.
Необходимо сделать так, чтобы в таком случае возвращалась пустая строка ""
binding.authInputPassword.addTextChangeListener { editable: Editable? ->
// Bad
viewModel.onPasswordChanged(editable.toString())
// Good
viewModel.onPasswordChanged(editable?.toString().orEmpty())
}
Для коллекций и строк использовать orEmpty()
.
// Bad
nullableString ?: ""
nullableObject?.toString() ?: ""
someList ?: emptyList()
// Good
nullableString.orEmpty()
nullableObject?.toString().orEmpty()
someList.orEmpty()
При проверке nullable boolean вместо добавления ?: false
в условии явно проверять boolean == true
Это одна из общепринятных идиом Kotlin.
// Bad
val b: Boolean? = ...
if (boolean ?: false) {
...
} else {
// `b` is false or null
}
// Good
val b: Boolean? = ...
if (b == true) {
...
} else {
// `b` is false or null
}