Giter Club home page Giter Club logo

kotlin-style-guide's Introduction

Kotlin Code Style

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

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

Длина строки

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

Максимальная длина строки: 120 символов.

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

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

При объявлении констант, полей или аргументов функций рекомендуется дополнительно указывать размерность, если контекст или название функции не дает однозначного понимания их назначения:

// Bad
const val TIMEOUT = 1000L
const val PADDING = 24

// Bad 
fun someFunction(timeout: Long)

// Bad
val defaultTimeout get() = 1000L

// Good
const val TIMEOUT_MILLIS = 1000L
const val PADDING_DP = 24

// Good
val TIMEOUT = 1000.milliseconds
val PADDING = 24.dp

// Good
fun preferGoodNames(timeoutMillis: Long)

// Good
val defaultTimeoutMillis get() = 1000L

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

При переносе на новую строку цепочки вызова методов символ . или оператор ?. переносятся на следующую строку, 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, так и сокращенный вариант (), если отсутствуют договоренности внутри проекта. Однако явный invoke имеет ряд преимуществ:

Tip

Одной из основных причин использования явного invoke является концептуальное разделение функции как члена класса и лямбды как входного параметра функции. Используя invoke явно, мы показываем, что используем лямбду, а не функцию.

При этом дополнительным аргументом к использованию invoke является его заметность. Вызывая лямбду без invoke, у нее можно потерять скобки в месте вызова, что приведет к некорректному поведению.

@Composable
fun ProfileScreenContent(
  header: @Composable LazyItemScope.() -> Unit,
  body: @Composable LazyListScope.() -> Unit,
  footer: @Composable LazyItemScope.() -> Unit,
) {
  LazyColumn {
    item(content = header)
    
    // Bad
    body
    // Good
    body()
    body.invoke(this@LazyColumn)
    
    item(content = footer)
  }
}

Форматирование лямбда-выражений

По возможности передавать метод по ссылке:

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
}

kotlin-style-guide's People

Contributors

bigman212 avatar emogurovanton avatar fi5t avatar jeevuz avatar osipxd avatar sonulen avatar vsbauer avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

kotlin-style-guide's Issues

Указывать размерность при объявлении константы

Мотивация: Если не указывать размерность у константы, можно неправильно её использовать в коде или забыть конвертировать в нужный формат.

// Bad
const val TIMEOUT = 1000L
const val PADDING = 24

// Good
const val TIMEOUT_MILLIS = 1000L
const val PADDING_DP = 24

// Good
val TIMEOUT = 1000.milliseconds
val PADDING = 24.dp

Правки по тексту соглашения

В тексте заметил несколько ошибок, предлагаю поправить.

https://github.com/RedMadRobot/kotlin-style-guide#lambda_formating

[При использовании лямба-функции в качестве аругмента выносить её за скобки если этот параметр единственный.]

Избегать использования Destrucion Declaration в лямбда-выражениях.

https://github.com/RedMadRobot/kotlin-style-guide#classes

Если описание класса не помещается в одну строку и реализует несколько интерфейсов (класс реализует или описание?), то применять стандартные правила переносов, т.е. делать перенос только в случае, когда не помещается на одну строку (не помещается что?), и продолжать перечисление интерфейсов на следующей строке.

Предлагаю исправить на: "Если описание класса не помещается в одну строку, и класс реализует несколько интерфейсов, то применять стандартные правила переносов, т.е. делать перенос только в случае, когда описание не помещается на одну строку, при этом продолжать перечисление интерфейсов на следующей строке.

https://github.com/RedMadRobot/kotlin-style-guide#condition_operator

У оператора when для коротких выражениях ветвей условия
when (somenCondition) {

Две пустых строки вместо одной:

when (feed.type) {
        FeedType.PERSONAL -> {
        	with(feed as PersonalFeed) {
        		datePopupStart = dateBegin
        		datePopupEnd = dateEnd
        	}
        }
// 1
// 2
        FeedType.SUM -> {

Добавить правила использования when оператора c sealed

Добавить правила использования when. Например если он используется в таком контексте

sealed class AState {
    class Started() : AState()
    class Finished() : AState()
}

Если в конкретном бизнес процессе нас интересует только состояние Started то можно опустить вторую проверку

when (value) {
    is AState.Started -> {
    //.. 
    }
   //проверка Finished опущена за ненадобностью обработки
}

Зачем это? Мне кажется так проще дополнять возможные обработки в будущем (например если нам в этом бизнес процессе потребуется обрабатывать и Finished). Также такая конструкция оставляет вопрос конечного автомата открытым и расширяемым

PS
Думаю справедливо применить это правило только для использование с sealed классами. Простые же выражения выносить в if блоки

var a = 5
//... bad
when (a) {
    5 -> {
    //.....
    }
}
// okay

if (a==5) //..

Уточнить про перенос Элвис оператора.

Предлагаю заменить формулировку:

Элвис оператор ?: при разрыве выражения также переносится на новую строку

на

Элвис оператор ?: в многострочном выражении также переносится на новую строку

Это подчеркнёт то, что элвис лучше отделять переносом всегда кроме случая однострочника.

Синхронизация style guide

Придумать способ синхронизации нашего внутреннего документа с этим репозиторием.

Упростить блок про Именованные аргументы и избавиться от магического числа 3.

В блоке про Именованные аргументы на мой взгляд слишком много завязано на количество элементов, а не на логику.

Я бы заменил

Если аргументов больше 3х,то следует их именовать.

на

Если значение аргумента не говорит о том, что это за параметр, то следует именовать аргумент.

Потому что даже пример

runOperation(
    method = operation::run,
    consumer = consumer,
    errorHandler = errorHandler,
    tag = tag,
    cache = cache,
    cacheMode = cacheMode
)

читается хуже, чем когда по смыслу

runOperation(
    method = operation::run,
    consumer,
    errorHandler,
    tag,
    cache,
    cacheMode
)

Что-то похожее есть сейчас в той части, где про примитивы:

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

Правило про именование примитивов я бы убрал тоже. Не стоит основываться на том примитив ли это. Лучше всё так же на том, понятно ли по значению или имени функции, что это за параметр.

Использование условных операторов с Compose

При использовании compose, часто возникают ситуации когда мы хотим прятать вьюху по какому то флагу.

Что-то в духе:
if (isVisible) Button()

Сейчас в style guide есть такой пункт:

Не обрамлять if выражения в фигурные скобки только если условный оператор if помещается в одну строку.

Но так как у всех вьюх куча параметров, по style guide пример выше надо написать вот так:

if (isVisible) {
   Button(
    .....
   )
}

Есть идея разрешить в случае с компоузом делать вот так:

if (title != null) Text(
    text = title,
    modifier = Modifier.padding(horizontal = 32.dp),
    style = SbiTextStyles.Title,
    textAlign = TextAlign.Center,
)

Но если есть else - мне кажется, скобки обязательны.

Удалить правило про lower_snake_case для имен пакетов.

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

Это правило очень сильно идет в разрез с тем что написано в Kotlin Coding Conventions

Using multi-word names is generally discouraged, but if you do need to use multiple words, you can either just concatenate them together or use camel case (org.example.myProject).

и в Android Kotlin Style Guide

Package names are all lowercase, with consecutive words simply concatenated together (no underscores).

Предлагаю убрать это правило и всю секцию, пользоваться тем что есть в официальных гайдах.

Возможность перемешивать переопределенные и абстрактный методы с методами класса.

В #13 предложена такая последовательность в структуре класса:

...
3. Абстрактные методы
4. Переопределенные методы родительского класса (желательно в порядке их следования в родительском классе)
5. Реализации методов интерфейсов (желательно в порядке добавления интерфейсов в класс и следования методов в каждом интерфейсе)
6. Методы класса (в логическом порядке. Например, метод располагается после того, в котором впервые упомянут). 
...

Было бы хорошо также разрешить перемешивать abstract и override методы с методами класса, чтобы была единая логика относительно всех методов (например, метод располагается после того, в котором впервые упомянут).

Считаю, что вот так:

override fun onSomeActionInSuperclass() {
  doSomethingHere()
}

private fun doSomethingHere() {
  ...
}

override fun onAnotherActionInSuperclass() {
  ...
}

будет читаться лучше чем

override fun onSomeActionInSuperclass() {
  doSomethingHere()
}

override fun onAnotherActionInSuperclass() {
  ...
}

private fun doSomethingHere() {
  ...
}

Что думаете?

Добавить рекомендацию про Гусь и Элвис переносы.

Сочетание "Гусь и Элвис" (?. ?:) в многострочных выражениях пишем на новых строках:

// OK
child?.let { println(it) } ?: println(parent)

// Хорошо
child
    ?.let {
        println(it)
    }
    ?: println(parent)

// Плохо
child?.let {
    println(it)
} ?: println(parent)

В более сложных случаях следует заменять на if/else:

// Лучше
if (child != null) {
    println(child)
	// ...
} else {
    println(parent)
    //...
}

Предпосылки - сложность чтения ситуации как эта:

// Плохо
child?.let {
    println(it)
} ?: println(parent)

А в реальных случаях кода больше и легко пропустить условие идущее после элвиса.

Удалить или видоизменить из блока Структура класса требование сортировки методов в порядке видимости.

Сейчас в разделе Структура класса есть порядок методов по области видимости:

  1. public методы
  2. internal методы
  3. protected методы
  4. private методы

Это ухудшает читаемость кода и накладывает дополнительные затраты на форматирование кода.
Располагать публичные методы вверху полезно только для библиотек, а во внутренних классах это не несет особой пользы. А увидеть в порядке видимости можно в студии открыв окошко Structure и выбрав Sort by Visibility.

Обычно чтение класса происходит сверху вниз, поэтому удобнее располагать методы по мере использования. Если в одном методе встретился вызов другого, то он идет сразу за ним. Так не нужно листать в конец класса, чтобы увидеть этот метод. Насколько я помню, именно такой вариант советует Дядюшка Боб в книге Чистый код.

А вот что написано про это в официальных гайдах:
Андроид:

What is important is that each file uses some logical order, which its maintainer could explain if asked. For example, new functions are not just habitually added to the end of the file, as that would yield “chronological by date added” ordering, which is not a logical ordering.

Котлин:

Do not sort the method declarations alphabetically or by visibility, and do not separate regular methods from extension methods. Instead, put related stuff together, so that someone reading the class from top to bottom can follow the logic of what's happening. Choose an order (either higher-level stuff first, or vice versa) and stick to it.

Разрешить вызывать лямбды без явного вызова `invoke`

С приходом Compose лямбды теперь передаём в параметрах чаще и необходимость явно прописывать invoke, как мне кажется, только усложняет чтение кода.
Пример:

@Composable
fun ProfileScreenContent(
    header: @Composable LazyItemScope.() -> Unit,
    body: LazyListScope.() -> Unit,
    footer: @Composable LazyItemScope.() -> Unit,
) {
    LazyColumn {
        item(content = header)
        // Что тут происходит? Передаём параметр в лямбду?
        // А, нет. Передаём контекст, на котором нужно вызвать лямбду
        body.invoke(this@LazyColumn)
        // При этом не вижу проблем с вызовом без invoke, исчезает куча лишней информации
        body()
        item(content = footer)
    }
}

Я в очередной раз забыл какие были аргументы за то чтобы явно вызывать invoke, хорошо если кто-нибудь напишет их в комментариях.

Убрать запрет на expression body в общем случае

Чтобы можно было так:

    private fun moo(mo: String) = when (mo) {
        "you",
        "and",
        "me" -> true
        else -> false
    }

    private fun foo() =
        try {
            if ("ohhh" == "you touch my talalam") {
                throw IllegalStateException("It's din din dong")
            }
            "magic"
        } catch (e: Exception) {
            println("ta talala talalalala")

            "the gathering"
        }

Сейчас можно только так:

    private fun moo(mo: String): Any {
        return when (mo) {
            "you",
            "and",
            "me" -> true
            else -> false
        }
    }

    private fun foo(): String {
        return try {
            if ("ohhh" == "you touch my talalam") {
                throw IllegalStateException("It's din din dong")
            }
            "magic"
        } catch (e: Exception) {
            println("ta talala talalalala")

            "the gathering"
        }
    }

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.