alaegin / kotlin-style-guide

red_mad_robot Kotlin Style Guide

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Kotlin Code Style

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

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

Длина строки

  • Рекомендуемая длина строки: 100 символов.
  • Максимальная длина строки: 120 символов.

Правила именования

  • Пакеты именуются одним словом в стиле lowercase. Если необходимо использовать несколько слов, то просто склеиваем их вместе.

Форматирование выражений

  • При переносе на новую строку цепочки вызова методов символ . или оператор ?. переносятся на следующую строку, property при этом разрешается оставлять на одной строке:
val collectionItems = 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,
    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. Методы класса (в логическом порядке. Например, метод располагается после того, в котором впервые упомянут).
  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
}

About

red_mad_robot Kotlin Style Guide

License:Creative Commons Attribution Share Alike 4.0 International