
Данная статья охватывает не только эстетические вопросы форматирования, но и другие типы соглашений и стандартов, которые необходимо знать 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 // отключен по умолчанию
Заключение
Данная статья получилась довольно большая, надеюсь вам было полезно прочитанное.
В следующей статье: именование, специальные конструкции и документация.
Полезные ссылки:
-
Kotlin style guide (на английском)
-
Книга: Чистый код (Боб Мартин)
Ждите следующей части!
ссылка на оригинал статьи https://habr.com/ru/post/537910/
Добавить комментарий