Kotlin CodeStyle
November 10, 2021 · View on GitHub
Kotlin CodeStyle
- Основные положения
- Правила оформления комментариев
- Именование сущностей
- Структура класса
- Организация файлов и пакетов
- Форматирование
- Идиомы
- Лучшие практики
- Настройка форматирования для проекта
Основные положения
Основано на Coding Conventions и Android Style Guide
Перед Pull Request ОБЯЗАТЕЛЬНО делаем форматирование кода по Ctrl+Alt+L
(⌘ + ⌥ + L)
Правила оформления комментариев
Оформляются по правилам KDoc.
(см. Coding Conventions, Android Style Guide)
Именование сущностей
см. Coding Conventions, Android Code Style
Указаны отличия от Java код-стайла Surf
Имена констант в xml:
-
screen_semantic_postfix, где
-
screen - имя экрана, на котором используется константа, может отсутствовать, если используется на многих экранах (без префикса activity/fragment)
-
semantic - смысл
-
postfix - назначение:
- _text строковая константа используется в TextView,
- _btn строковая константа используется для Button
- _message, _error_message - используется в Toast / Snack
- _hint - подсказка в EditText
- _padding, _margin - паддинг и марджин соответственно
- _width, _height - ширина и высота соответственно
-
Именование свойств
Используется camelCase. Тип свойства указывается явно.
Если используются свойства с функциональном типом, то делаем постфикс Lambda
val someBeautifulLambda: (Int) -> Int = { it * 2 }
Если такое свойство используется как callback, то вместо Lambda -> Callback.
Именование методов
Используется camelCase. Правила оформления смотри Android Code Style.
Использование спец. символов
см. Special Characters from Android Style Guide
Структура класса
- companion object
- приватные поля-ключи(константы)
- приватные val / var свойства
- публичные val/ var свойства
- публичные интерфейсы
- методы
- вложенные классы
Расположение методов в классе
1 вариант:
-
abstract методы
-
override методы
-
публичные методы
-
internal методы
-
protected методы
-
приватные методы
2 вариант:
- по уровням абстракции (см. Android Code Style)
Организация файлов и пакетов
-
Выносить extension-методы в отдельные, логически связанные файлы, если они не используются только в одном месте. Файл именуется с постфиксом Extensions, при этом префикс должен соответсвовать классу, который эти методы расширяют, т.е
EditTextExtensions,ListExtensions,ActivityExtensionsи тд. -
Публичные глобальные константы хранить как
valсвойства в отдельном файле, на глобальном уровне. -
Утилитные функции удобно хранить либо в виде глобальных функций, либо оборачивать в
object.
Форматирование
Пустые/ не пустые блоки кода
Методы
см. Function formatting, Вызов методов
Замечание: форматирование аргументов метода, когда не помещается в одну строку. Делаем так:
override fun onPageScrolled(
position: Int,
positionOffset: Float,
positionOffsetPixels: Int
) {
//some actions
}
Если выражение однострочного метода не помещается в одну строку, то переносить его на следующую.
fun authByEmail(email: String, password: String): Observable<User> =
authApi.auth(AuthRequest(email = email, password = password))
.transform()
Свойства
Заголовок классов
см. Форматирование заголовка класса
class SocialNetworksInteractor @Inject constructor(
private val vkRepository: VkRepository,
private val fbRepository: FbRepository,
private val analyticsService: AnalyticsService,
private val activityProvider: ActivityProvider
) : OAuthCallback, ActivityResultDelegate {
}
Форматирование управляющих конструкций
см. Форматирование управляющих конструкций
Форматирование цепных вызовов
см. Цепные вызовы
Форматирование лямбд
Если метод принимает только лямбду, сразу пишем ее после названия метода, без круглых скобок
doSomething { //do something }
Внутри лямбды удобно использовать it, если не требуется уточнения или
внутри не используется еще одна лямбда, требующая параметр внешней лямбды(см. 2)
1)
someList.map { it.id }.toHashSet()
someList.map {
it.map { it.name }
}
someList.map { objInList ->
// someAnotherList - коллекция вне лямбды
someAnotherList.filter { it.id == objInList.id}
}
Форматирование выражений
Подписки
В методах subscribe писать лямбды с новой строки для лучшей читаемости кода.
subscribe(
activityNavigator.observeResult(CategoryChooserRoute::class.java)
.filter { result -> result.isSuccess },
{ categoryChooserParam ->
screenModel.categoryList = categoryChooserParam.data.categoryList
view.render(screenModel)
}
)
Плюс к этому в случае передачи в subscribe метода принимающего большое количество аргументов лучше переносить вызов метода на следующую строку
subscribeIoHandleError(
placesRepository.getShortPlaces(
filter?.categories?.toList(),
param,
param
),
{
//some actions
})
subscribeIoHandleError(placesRepository.getShortPlaces(
filter?.categories?.toList(),
param,
filter?.isPromo ?: false,
geoPosition = placesLoadRequestData.geodataRequest),
{})
Примечание: Если Observable большой, то выносить в переменную (или даже метод) и там применять нужные операторы, а то метод может стать очень большим и страшным
val someObservable = activityNavigator
.observeResult(CategoryChooserRoute::class.java)
.filter { result -> result.isSuccess }
subscribe(
someObservable,
{ categoryChooserParam ->
screenModel.categoryList = categoryChooserParam.data.categoryList
view.render(screenModel)
},
{ t: Throwable -> handleError(t) }
)
Логические выражения
В логических выражениях предпочтительнее использовать логические
операторы &, | и тд, а не методы в явном виде. Данный тезис не касается nullable типов.
return isEmptyFirstName.not().and(isEmptyLastName.not()).and(isEmptyPhone.not())
return !isEmptyFirstName & !isEmptyLastName & !isEmptyPhone
Здесь нельзя написать !screenModel.currentUserLocation?.isLocationDefault(),
так как currentLocation может вернуть null
fun someBool() : Boolean = screenModel.currentUserLocation?.isLocationDefault()?.not() ?: false
Идиомы
см. Idiomatic use of language features
Kotlin конструкции
.apply{}
Удобно использовать, когда необходимо проинициализировать свойства объекта при передачи, как аргумента, в метод. Но надо быть внимательным и по возможности не собирать все в одном месте.
.let{}
Удобно использовать как проверку на null. При этом, внутри блока let,
переменная будет гарантировано не null.
var nullableInt: Int? = null
nullableInt = 11
// if (nullableInt == 11) <- будет требовать
// выражения nullableInt?.equals(11) ?: false
nullableInt?.let {
if (it == 11) //do something
}
Лучшие практики
-
С помощью lazy удобно инициализировать большие объемные списки, объекты.
-
Использовать синтаксис свойств вместо сеттеров.
-
Для конкатенации элементов списка в строку(например с идентификаторами или именами элементов) удобно использовать метод joinToString().
Настройка форматирования для проекта
Локальное форматирование
Настройка форматирования для AndroidStudio Из-за размещения настроек под vcs изменения распространятся на всю команду.
- Перенести содержимое .idea в корневую директорию .idea проекта
- Добавить в корневой .gitignore
!.idea/codeStyles
!.idea/inspectionProfiles
И заменить в нем
.ideaна.idea/* $git add .idea/
$git commit -a -m "Code formatting”- Убрать из
gradle.properieskotlin.code.style={official}(если имеется) - Запушить изменения
Форматирование для CI
Установка
- Загрузить ktFormatter.gradle в директорию {project}/scripts рабочего проекта.
- Добавить
ktlintPluginVersion = '2.1.0' //https://bit.ly/2YLc3n8
в .gradle файл содержащий версии зависимостей. Как правило это {project}/config.gradle
Имеет смысл проверить и при возможности обновить версию плагина. - Добавить
apply from: "scripts/ktFormatter.gradle"
в корневой build.gradle проекта. - Добавить параметр в gradle.properties (если нет)
surf_maven_libs_url=https\://artifactory.surfstudio.ru/artifactory/libs-release-local - Проверить работоспособность командой
./gradlew ktlintFilesFormat -PlintFiles=relative_path_to_file
где relative_path_to_file это относительный путь к файлу с заранее испорченным форматированием. - Запушить изменение в основную ветку спринта. Соответственно, автоформатирование будет работать только в тех ветках, которые содержат коммит с изменениями выше
- В master ветке проекта в файле
ci/JenkinsfilePullRequestJob.groovyдобавить следующие строки в любое место междуdef pipeline = new PrPipelineAndroid(this)иpipeline.run()
pipeline.getStage(pipeline.CODE_STYLE_FORMATTING).strategy = StageStrategy.UNSTABLE_WHEN_STAGE_ERROR
pipeline.getStage(pipeline.UPDATE_CURRENT_COMMIT_HASH_AFTER_FORMAT).strategy = StageStrategy.UNSTABLE_WHEN_STAGE_ERROR
И запушить изменения
Для проверки можно использовать следующий коммит с полной установкой форматирования.
Настройка для CI
Можно изменять список правил для форматирования добавляя свои или удаляя существующие
Для отключения любого из них достаточно добавить
task ktlintFilesFormat(type: org.jmailen.gradle.kotlinter.tasks.FormatTask) {
...
disabledRules = ["{имя_правила}"]
...
}
{имя_правила} можно получить из
Списка существующих правил
в классе правила в строке декларирования класса : Rule("имя_правила")