RedMadRobot Kotlin Style Guide Save

red_mad_robot Kotlin Style Guide

Project README

Kotlin Code Style

В репозитории приведен набор соглашений по оформлению кода на языке Kotlin.

Этот список правил расширяет предложенные Google и командой разработки Kotlin гайды и пересматривает в них некоторые неоднозначные моменты.

Длина строки

Рекомендуемая длина строки: 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() { /*...*/ }
}

Использование именованных аргументов аналогично с функциями

Структура класса

  1. Поля: abstract, override, public, internal, protected, private
  2. Блок инициализации: init, конструкторы
  3. Абстрактные методы
  4. Переопределенные методы родительского класса (желательно в порядке их следования в родительском классе)
  5. Реализации методов интерфейсов (желательно в порядке добавления интерфейсов в класс и следования методов в каждом интерфейсе)
  6. Методы класса (в логическом порядке. Например, метод располагается после того, в котором впервые упомянут). Можно перемешивать с методами из пунктов 3, 4, 5.
  7. inner классы
  8. 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
}
Open Source Agenda is not affiliated with "RedMadRobot Kotlin Style Guide" Project. README Source: RedMadRobot/kotlin-style-guide
Stars
87
Open Issues
6
Last Commit
2 months ago

Open Source Agenda Badge

Open Source Agenda Rating