[Совершенный код, Разработка мобильных приложений, Разработка под Android, Kotlin] Kotlin Best Practices

Автор Сообщение
news_bot ®

Стаж: 6 лет 9 месяцев
Сообщений: 27286

Создавать темы news_bot ® написал(а)
11-Мар-2021 14:33

Kotlin, созданный всего 5 лет назад, с 2019 года считается приоритетным языком программирования под Android. И все же этот язык достаточно молод и продолжает развиваться, поэтому иногда бывает непонятно, каким образом лучше написать код. У нас в команде часто бывают обсуждения на тему чистого Kotlin-кода, и на их основе мы составили свои best practices. Хотим поделиться этими рекомендациями и ждем ваших вопросов.Ну что ж, приступим! В первую очередь, в Котлине много синтаксического сахара, и если им злоупотреблять, то читать такой код становится затруднительно. Следующие несколько пунктов можно отнести к борьбе между краткостью и читаемостью
Не пишите объявление класса в одну строчкуДаже если сегодня объявление вашего класса уместилось в одну строчку и не выходит за поля – завтра может добавиться еще один аргумент в конструктор или еще один интерфейс в список наследуемых типов. В таком случае этот самый список может уехать вправо и пропасть из поля видимости.
class ChannelViewModel(
       val conversationId: String,
       getChannelUseCase: GetChannelUseCase,
) : ViewModel() {
Размещая каждый параметр конструктора на отдельной строке, мы также получаем бонусы:
  • Если при рефакторинге надо поменять местами параметры, сделать это будет быстрее (в Android Studio сдвигаем строчку кода вверх/вниз с помощью сочетания клавиш Shift+Command+Up/Down)
  • В гите изменения параметров будут отображаться более наглядно.
Вложенный returnРассмотрим такой пример:
data class MyClass(val number: Int, val flag: Boolean)
fun create(numberParam: Int?, flag: Boolean?): MyClass? {
   return MyClass(numberParam ?: return null, flag == true)
}
Обычно если мы доходим до выражения с return, то это такая точка, в которой завершается выполнение функции. В этом же примере в случае, когда numberParam равен null,  мы мысленно проходим сперва через return MyClass(...) и затем через return null. Лучше в данном случае использовать простой if, чтобы поток выполнения программы выглядел понятнее:
fun create(numberParam: Int?, flag: Boolean?): MyClass? {
   if (numberParam == null) {
       return null
   }
   return MyClass(numberParam, flag == true)
}
Анонимный параметр itКак давно замечали на Хабре, цепочка из вложенных методов с анонимным параметром it читается тяжело, однако такие злоупотребления по-прежнему регулярно встречаются в коде:
values?.filterNot { selectedValues?.contains(it) == true }
   ?.let {
       selectedValues?.addAll(it)
       result[key]?.values = selectedValues?.filter { it.isChecked }
   }
Именованный параметр следует добавить как минимум в функцию let:
values?.filterNot { allSelectedValues?.contains(it) == true }
   ?.let { newValues ->
       allSelectedValues?.addAll(newValues)
       result[key]?.values = allSelectedValues?.filter { it.isChecked }
   }
В этом коде it – не единственная проблема. В данном примере обнуляемые типы можно заменить на не-null, в результате код станет опрятнее. Сравните:
val newValues = values.filterNot { selectedValues.contains(it) }
selectedValues.addAll(newValues)
result[key]?.values = selectedValues.filter { it.isChecked }
Сокращайте цепочки безопасных вызовов ?. заменой обнуляемых типов на не-null типыПродолжим затронутую в предыдущем пункте тему с обнуляемыми типами и рассмотрим такой пример:
private var animatedView: FrameLayout? = null
...
animatedView?.animate()?.alpha(1f)?.setDuration(500)?.interpolator = AccelerateInterpolator()
Здесь фактически значение null могло бы появиться только в том случае, когда animatedView равен null. Добавление простой проверки if (animatedView != null) избавит нас от цепочки безопасных вызовов. Но в данном примере вообще нет необходимости в том, чтобы  animatedView принимало значение null. Поэтому лучше его сделать lateinit переменной, и тогда код вообще не будет содержать проверок на null:
private lateinit var animatedView: FrameLayout
...
animatedView.animate().alpha(1f).setDuration(500).interpolator = AccelerateInterpolator()
Некоторые проблемы появляются при автоматическом переводе файлов с Java на Kotlin, особенно если разработчик поторопился и оставил код “как есть”. Обычно такой код пестрит восклицательными и вопросительными знаками и требует пересмотра. При переходе с Java какие-то if можно заменить на when, можно использовать функции области видимости (let, apply, also, with, run), функции для работы с коллекциями, переписать классы Utils на extension функции.

Избавляемся от !!Использование оператора !! считается дурным тоном, так как оно означает игнорирование потенциального NullPointerException, да и сам код с обилием знаков !! выглядит “костыльно”. От !! можно избавиться следующими способами: 
  • заменой обнуляемых типов на не-null типы (как было сделано в примере с lateinit animatedView)
  • функцией let с безопасным вызовом ?.let { … }
  • элвис-оператором ?:
  • и наконец, если мы действительно допускаем, что в приложении должно произойти исключение, когда переменная приняла значение null, то можно воспользоваться функцией checkNotNull или requireNotNull. Они отличаются только типом выбрасываемого исключения: IllegalStateException и IllegalArgumentException соответственно. 
Для того, чтобы предупредить появление !! при переводе кода с Java на Kotlin, можно добавить аннотации @NonNull в Java-код.Отдаем предпочтение when перед ifСравните:
val price = if (priceData.isWeightPrice) {
   priceData.minDiscountPrice.toInt()
} else if (priceData.discountPrice != 0.0) {
   priceData.discountPrice.toInt()
} else {
   priceData.price.toInt()
}
и вариант с when:
val price = when {
   priceData.isWeightPrice -> priceData.minDiscountPrice.toInt()
   priceData.discountPrice != 0.0 -> priceData.discountPrice.toInt()
   else -> priceData.price.toInt()
}
С when мы можем не писать лишние круглые и фигурные скобки, но код все равно выглядит структурированным, так как условия отделены от результатов стрелками. Преимущества when заметны в том случае, когда проверяется более двух условий. Конечно, нет смысла использовать when для проверки значений булевского флага.Заменяем классы Util на функции расширенияЕсли статические вспомогательные функции можно отнести к какому-то определенному классу, лучше оформить их в виде функций extension. Тогда код будет выглядеть более идиоматичным. Еще один плюс функций расширения в том, что автокомплит сам их предлагает. Не надо держать в голове, реализована ли уже на проекте та или иная вспомогательная функция, и в каком файле она лежит.Это не значит, что от объектов Util в Kotlin надо полностью избавляться (обратите внимание: статические методы в Java содержатся в классах, а в Kotlin – в объектах). Если вспомогательная функция не относится к какому-то конкретному типу, не стоит оформлять ее в виде статической функции верхнего (package) уровня. Иначе такие функции будут постоянно появляться в автокомплите (extension функцию автокомплит предложит после того, как мы напечатаем название переменной определенного типа и точку, и это нормально; а функция верхнего уровня появится сразу после того, как мы начнем что-то печатать). Кроме того, код будет лучше организован и структурирован, если вспомогательные функции помещать внутри объекта, а не просто внутри файла.
Котлин периодически обновляется, и иногда разработчик может не уследить за отдельными новшествами (и это не говоря о случаях “прочитал и забыл”). Приведем несколько полезных возможностей языка, которые мы советуем использовать.

Замыкающие запятые (trailing commas) Замыкающие запятые появились в версии языка 1.4. Мы рекомендуем их использовать в сочетании с приведенной выше рекомендацией располагать параметры конструктора (и вообще метода) на отдельных строках по той же причине: с замыкающей запятой аккуратнее выглядит diff в гите при добавлении параметра в конец. Single Abstract Method interface (Fun interface)Тоже появились в Kotlin 1.4.0. Сравните, как выглядит создание анонимного объекта для обычного и fun интерфейса:
this.actionClickListener = object : BubbleView.ClickListener {
   override fun onBubbleViewClick() {
           ...
       }
   }
и
this.actionClickListener = BubbleView.ClickListener {
      ...
  }
Во втором случае к объявлению интерфейса добавилось только  fun:
fun interface ClickListener {
   fun onBubbleViewClick()
}
Таким образом, вместо одного интерфейса в Java, в Kotlin можно использовать обычный интерфейс, SAM-интерфейс или лямбду. Предлагаем в этом выборе руководствоваться следующим правилом:
  • для простых колбеков и мапперов используем функциональный тип (T) -> R;
  • если для наглядности мы хотим иметь название для типа или для вызываемого метода, то SAM-интерфейс;
  • если должно быть несколько методов, то обычный интерфейс. 
Функции для работы с коллекциямиKotlin содержит огромное множество функций для сортировки, фильтрации, поиска, преобразования коллекций. Каждый раз, когда в коде встречается цикл для обхода коллекции, велика вероятность, что его можно переписать с использованием уже существующей функции. Поэтому если вы регулярно пишете циклы, рекомендуем вам изучить/освежить в памяти документацию. Как можно улучшить представленный код?
val itemIdsSet: Set<String> = ...
val currentItemIds: Set<String> = ...
for(itemId in itemIdsSet) {
   if(!currentItemIds.contains(itemId)) {
       repository.exclude(itemId)
   }
}
Как вариант:
val itemIdsSet: Set<String> = ...
val currentItemIds: Set<String> = ...
for (itemId in itemIdsSet subtract currentItemIds) {
   repository.exclude(itemId)
}
Или если отредактировать API репозитория так, чтобы функция exclude могла принимать коллекции, тогда мы сможем написать:
repository.exclude(itemIdsSet subtract currentItemIds)
Согласуйте code styleИногда стиль написания кода у разработчиков в команде может настолько различаться, что проект рискует превратиться в “кашу”. Поэтому лучше согласовывать такие моменты: 
  • принципы наименования переменных (например, запрет на использование префикса _ для теневых полей или написание констант enum только заглавными буквами)
  • порядок следования функций внутри классов
  • положение companion object внутри класса (в начале или в конце) и т.п. 
ЗаключениеВ этой статье мы поделились несколькими правилами, которые показали свою эффективность в нашей команде. Однако, мы не претендуем на их истинность и рекомендуем вам обсуждать и формировать свои наборы best practices.Как пример, больше идей по улучшению Kotlin-кода можно почерпнуть здесь:https://developer.android.com/kotlin/style-guide https://proandroiddev.com/an-opinionated-guide-on-how-to-make-your-kotlin-code-fun-to-read-and-joy-to-work-with-caa3a4036f9e 
https://developer.android.com/kotlin/coroutines/coroutines-best-practices
===========
Источник:
habr.com
===========

Похожие новости: Теги для поиска: #_sovershennyj_kod (Совершенный код), #_razrabotka_mobilnyh_prilozhenij (Разработка мобильных приложений), #_razrabotka_pod_android (Разработка под Android), #_kotlin, #_android, #_kotlin, #_clean_code, #_refactoring, #_blog_kompanii_simbirsoft (
Блог компании SimbirSoft
)
, #_sovershennyj_kod (
Совершенный код
)
, #_razrabotka_mobilnyh_prilozhenij (
Разработка мобильных приложений
)
, #_razrabotka_pod_android (
Разработка под Android
)
, #_kotlin
Профиль  ЛС 
Показать сообщения:     

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

Текущее время: 22-Ноя 23:27
Часовой пояс: UTC + 5