Руководство по стилю Kotlin для Android разработчиков (Часть I)

от автора

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

Основной фокус, в первую очередь, на жестких правилах, которым следуют Google разработчики повсеместно!

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

Поэтому я решил разделить её на две части.

Обе части содержат описание стандартов кода на языке прораммирования Kotlin.

Что покрывают обе части:

  • Именование файлов, переменных, классов, свойств и т.д.

  • Структура исходного файла

  • Форматирование — строки, пробелы, скобки, специальные конструкции, переносы и др.

  • Документация

В первой части я затрону исходные файлы и форматирование (неполностью).

Ну что ж пора начинать!

Исходные файлы

Поговорим сначала об исходных файлах, о их структуре и других важных вещах.

Кодировка

Все исходные файлы должны иметь UTF-8 кодировку.

Именование

Все исходные файлы, которые содержат высокоуровневые определения классов, должны именоваться следующим образом: имя класса + расширение файла .kt

Если файл содержит несколько высокоуровневых определений (два класса и один enum к примеру) выбирается имя файла, которое описывает его содержимое:

// PhotoAdapter.kt  class PhotoAdapter(): RecyclerView.Adapter<PhotoViewHolder>() { 	// ... }   // Utils.kt  class Utils {}  fun Utils.generateNumbers(start: Int, end: Int, step: Int) { 	// ... }  // Map.kt  fun <T, O> Set<T>.map(func: (T) -> O): List<O> = // ... fun <T, O> List<T>.map(func: (T) -> O): List<O> = // ...

Структура

Kotlin файл .kt включает в себя:

  • Заголовок, в котором указана лицензия и авторские права (необязательно)

  • Аннотации, которые объявлены на уровне файла

  • package объявление

  • import выражения

  • высокоуровневые объявления (классы, интерфейсы, различные функции)

Заголовок должен быть объявлен выше остальных определений с использованием многострочных комментариев:

/*  * Copyright 2021 MyCompany, Inc.  *  *  */

Не используйте однострочные и KDoc комментарии:

/**   * Copyright 2021 MyCompany, Inc.  *  */  // Copyright 2021 MyCompany, Inc. //

Аннотация @file, которая является use-site target должна быть помещена между заголовком и package объявлением:

/*  * Copyright 2021 MyCompany, Inc.  *  */  @file:JvmName("Foo")  package com.example.android

Оператор package и importникогда не переносятся и всегда размещаются на одной строке:

package com.example.android.fragments  // переносы запрещены  import android.view.LayoutInflater // так же и здесь import android.view.View

Выражения import группируются для классов, функций и свойств в сортированные списки.

Импорты с подстановочным знаком не разрешены:

 import androidx.room.*  // так делать не нужно

Kotlin файл может содержать объявление одного или нескольких классов, функций, свойств или typealias выражений.

Контент файла должен относится к одной теме. Например у нас есть публичный класс и набор extension функций, которые выполняют некоторые операции.

Нет явного ограничения на количество и порядок содержимого файла

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

Важен логический порядок, который может объяснить сам разработчик.

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

Для членов класса применимы те же правила, что и для высокоуровневых определений.

Специальные символы

В исходном коде используется только ASCII горизонтальный пробельный символ (0x20).

Это означает, что:

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

  • Tab символы не используются для отступов

Для любого символа, который имеет экранированную последовательность (\b, \r, \t, \\) используется эта последовательность, а не Unicode (например: \u000a).

Для оставшихся символов, которые не принадлежат ASCII, используется либо Unicode символ (∞), либо Unicode последовательность (\u221e).

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

// Лучшая практика: понятно без комментариев val symbol0 = "∞"	  // Плохо: нет причины не использовать символ вместо Unicode последовательности val symbol1 = "\u221e" // ∞	  // Плохо: читатель не сможет понять, что это за символ  val symbol2 = "\u221e"  // Хорошо: использование Unicode последовательности для непечатаемого символа return "\ufeff" + content	// неразрывный пробел нулевой ширины 

Форматирование

Ближе к коду!

Скобки

Скобки не требуются дляwhen и if которые помещаются на одной строке (оператор if не имеет else ветки):

if (str.isEmpty()) return  when (option) {     0 -> return     // … }

В другом случае скобки обязательно требуются для if, for, when ветвлений и do и while выражений:

if (str.isEmpty())     return  // так делать нельзя!  if (str.isEmpty()) {     return  // OK }

Скобки следуют стилю Кернигана и Ритчи для непустых блоков и блочных конструкций:

  • Нельзя делать разрыв строки перед открывающей скобкой

  • Разрыв строки после открывающей cкобки

  • Разрыв строки перед закрывающей скобкой

  • Разрыв строки после закрывающей скобкой только в том случае, если она заканчивает выражение или тело функции, конструктора, класса.

class MainActivity : AppCompatActivity() {      private lateinit var binding: ActivityMainBinding      override fun onCreate(savedInstanceState: Bundle?) {         super.onCreate(savedInstanceState)         binding = DataBindingUtil.setContentView(this, R.layout.activity_main)         // ...     } }

Пустые блоки тоже должны быть в стиле K&R:

try {     val response = fetchDogs("https://api.dog.com/dogs") } catch (e: Exception) {} // неправильно  try {     val response = fetchDogs("https://api.dog.com/dogs") } catch (e: Exception) { } // OK

if/else выражение может быть без скобок, если помещается на одной строке:

val value = if (str.isEmpty()) 0 else 1  // OK  val value = if (str.isEmpty())	// неправильно 	0 else 	1  val value = if (str.isEmpty()) { 	// OK 	0 } else { 	1 } 

С каждом новым блоком отступ увеличивается на 4 пробела. Когда блок закрывается отступ возвращается на предыдущий уровень (это применимо и для комментариев).

Переносы

Каждое выражение разделяется переносом на новую строку (; не используется)

Строка кода имеет ограничение в 100 символов.

Исключения:

  • Строки, которые невозможно перенести (например: длинный URL)

  • package и import выражения

  • Команды в документации, которые можно вставить в shell

Правила для переноса на новую строку:

  • Перенос после оператора или infix функции.

  • Если строка завершается следующими операторами, то перенос осуществляется вместе с ними:

    • точка (., .?)

    • ссылка на член (::)

  • Имя метода или конструктура находится на одной строке с открывающей скобкой

  • Запятая (,) связана с элементом и не переносится

  • Стрелка (->) для lambda выражений связана с аргументами

Когда сигнатура функции не помещается, объявление параметров располагается на отдельных строчках (параметры должны иметь один отступ в 4 пробела):

fun makeSomething(   val param1: String,   val param2: String,   val param3: Int ) {  }

Когда функция содержит одно выражение можно сделать так:

override fun toString(): String { 	return "Hello, $name" }  override fun toString() = "Hello, $name"

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

fun waitMe() = runBlocking { 	delay(1000) }

Когда инициализация свойства не помещается на одной строке можно сделать перенос после знака присваивания (=):

 val binding: ListItemBinding =   	DataBindingUtil.inflate(inflater, R.layout.list_item, parent, false)

get и set функции должны быть на отдельной строке с обычным отступом (4 пробела):

 val items: LiveData<List<Item>>  	get() = _items

Read-only свойства могут иметь более краткий синтаксис:

val javaExtension: String get() = "java" 

Пробелы

Пустая строка может быть:

  • Между членами классов: свойствами, функциями, конструкторами и другими

    • Пустая строка между двумя свойствами необязательна. Это нужно для создания логических групп (например для backing свойств)

  • Между выражениями для логического разделения

  • Перед первым членом функции или класса (необязательно)

Помимо требуемых правил для языка и литералов (строчных или символьных) одиночный ASCII пробел:

  • Разделяет зарезервированные слова, таких как: if, for или catch от круглой открывающей скобки:

// неправильно for(i in 1..6) { }  // OK for (i in 1..6) { }
  • Разделяет любые зарезервированные слова, таких как else и catch от закрывающей фигурной скобки:

// Неправильно }else { }  // OK } else { }
  • Ставиться перед любой открывающей фигурной скобкой:

// Неправильно if (items.isEmpty()){ }  // OK if (items.isEmpty()) { }
  • Ставиться между операндами:

// Неправильно val four = 2+2  // OK val four = 2 + 2  // Это относится и к оператору лямбда выражения (->)  // Неправильно items.map { item->item % 2 == 0 }  // OK items.map { item -> item % 2 == 0 }
  • Исключение: оператор ссылка на член (::), точка (.) или range (..)

// Неправильно val str = Any :: toString  // OK val str = Any::toString  // Неправильно item . toString()  // OK item.toString()  // Неправильно for (i in 1 .. 6) { 		println(i) }  // OK for (i in 1..6) { 		println(i) }
  • Перед двоеточием (:) для указания расширения базового класса или интерфейса, а также в when выражении для generic типов:

// Неправильно class Worker: Runnable  // OK class Worker : Runnable  // Неправильно fun <T> min(a: T, b: T) where T: Comparable<T>    // OK fun <T> min(a: T, b: T) where T : Comparable<T>
  • После двоеточия (:) или запятой (,)

// Неправильно val items = listOf(1,2)  // OK val items = listOf(1, 2)  // Неправильно class Worker :Runnable  // OK class Worker : Runnable
  • По обеим сторонам двойного слеша:

// Неправильно var debugging = false//отключен по умолчанию  // OK val debugging = false // отключен по умолчанию

Заключение

Данная статья получилась довольно большая, надеюсь вам было полезно прочитанное.

В следующей статье: именование, специальные конструкции и документация.

Полезные ссылки:

Ждите следующей части!

ссылка на оригинал статьи https://habr.com/ru/post/537910/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *