kotlin-style-guide
kotlin-style-guide copied to clipboard
red_mad_robot Kotlin Style Guide
Kotlin Code Style
В репозитории приведен набор соглашений по оформлению кода на языке Kotlin.
Этот список правил расширяет предложенные Google и командой разработки Kotlin гайды и пересматривает в них некоторые неоднозначные моменты.
- Длина строки
- Правила именования
- Форматирование выражений
-
Функции
- Функции с одним выражением
- Именованные аргументы
- Вызов переменной функционального типа
- Форматирование лямбда-выражений
- Классы
- Структура класса
- Аннотации
- Использование условных операторов
- Template header
-
Частые ошибки
- Вызов toString() у nullable объектов
- Использование orEmpty() вместо ?:
- Проверка nullable boolean
Длина строки
- Рекомендуемая длина строки: 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
- Не использовать Template Header для классов (касается авторства и даты создания файла).
Частые ошибки
Вызов toString() у nullable объектов
- В первом примере получится строчка
"null"
, это плохо. Необходимо сделать так, чтобы в таком случае возвращалась пустая строка""
binding.authInputPassword.addTextChangeListener { editable: Editable? ->
// Bad
viewModel.onPasswordChanged(editable.toString())
// Good
viewModel.onPasswordChanged(editable?.toString().orEmpty())
}
Использование orEmpty() вместо ?:
- Для коллекций и строк использовать
orEmpty()
.
// Bad
nullableString ?: ""
nullableObject?.toString() ?: ""
someList ?: emptyList()
// Good
nullableString.orEmpty()
nullableObject?.toString().orEmpty()
someList.orEmpty()
Проверка nullable boolean
- При проверке 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
}