За полтора года работы с языком Kotlin, мы перевели на него все свои проекты и фреймворки. Чтобы разработчики могли быстрее включаться в работу над проектом, а код ревью не превращался в бесконечный спор, мы решили формализовать накопленный опыт и разработали собственный код-стайл.


Поехали!


Каждый разработчик пишет код исходя из собственного опыта и привычек. Если на проекте один разработчик или код пишется для себя, это не проблема. Но в больших командах, работающих над несколькими проектами, неизбежно возникают проблемы:


  • на ревью кода уходит много времени и нервов — без единого стандарта спорить можно до бесконечности;
  • нужно куда больше времени и сил для погружения нового человека в код;
  • сложнее концентрироваться на функции, которую выполняет код;
  • в результате проект выглядит, как отпечаток всех специалистов, которые принимали в нем участие.

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


Наличие единого стиля написания кода позволяет решает все эти проблемы.


Почему сейчас и почему мы


Kotlin довольно популярен в сообществе Android-разработчиков и с каждым днем собирает все больше сторонников. Но отыскать стиль написания кода, которым можно руководствоваться, по-прежнему проблема, а соглашения, опубликованные на официальном сайте языка не дают исчерпывающих ответов, так как то количество правил, которые там присутствуют, едва ли могут покрыть большинство потребностей команды. Например, не содержат правил форматирования функций, их вызов и описания.


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


О процессе разработки правил


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


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


Спорные моменты


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


1. Имена полей View и Kotlin Android Extensions


Правило

Для полей View из Kotlin Android Extensions используется стиль именования lower_snake_case


При разработке стандарта мы старались абстрагироваться от внешних зависимостей или возможностей использования IDE и сосредоточится на языке в чистом виде. Но все же сделали исключение для Kotlin Android Extensions. Поскольку чаще всего мы используем Kotlin в Android-разработке, а приложения не обходятся без использования XML, так как в этом формате в стиле lower_snake_case описывается большинство ресурсов. Таким образом, мы сделали исключение для идентификаторов View (их мы описываем как раз в таком стиле) и обращений к этим полям (хотя если посмотреть как эта магия работает в рантайме, окажется, что это не переменные, а ключи для получения значений из HashMap). Из кода выглядит как обращение к полю, написанному в стиле lower_snake_case, но при этом в коде всегда видно, что происходит обращение к полю View.


2. Порядок private методов


Правило

Структура класса:
1) companion object
2) Поля: abstract, override, public, internal, protected, private
3) Блок инициализации: init, конструкторы
4) Абстрактные методы
5) Переопределенные методы родительского класса (желательно в том же порядке, в каком они следуют в родительском классе)
6) Реализации методов интерфейсов (желательно в том же порядке, в каком они следуют в описании класса, соблюдая при этом порядок описания этих методов в самом интерфейсе)
7) public методы
8) internal методы
9) protected методы
10) private методы
11) inner классы


Еще одним спорным моментом было расположение приватных методов. Есть два распространенных варианта: писать их сразу после метода, где они использовались, или в структуре класса после всех методов. Мы сошлись на втором варианте. Так как мы рассматриваем класс как некоторый интерфейс, в первую очередь интересно узнать, что он делает — благодаря тому, что все приватные методы располагаются в самом конце, можно быстро понять ответственность этого класса по его интерфейсу. В приватных же методах как правило содержатся уже конкретные реализации, а обращаемся мы к ним зачастую лишь в случае, если хотим сделать изменения. И в таком случае структура любого класса всегда останется консистентной.


3. Константы


Правило

Неизменяемые поля в (Companion) Object и compile-time константы именуются в стиле SCREAMING_SNAKE_CASE


Вопрос об именовании констант влечет за собой еще одну важную проблему: а что сейчас является константой в Kotlin? Язык предоставляет ключевое слово для обозначения compile-time констант "const", но compile-time константы могут быть только примитивного типа и String-и. В качестве констант хотелось бы использовать и другие неизменяемые переменные, расположенные в блоке Object — для таких случаев мы расширили понимание констант при именовании, включив в их число и такие неизменяемые переменные. Интересно, что в этом наше мнение совпало с позицией Google-а.


4. Имена пакетов


Правило

Пакеты именуются в стиле lower_snake_case


В процессе работы у нас часто возникала проблема с именованием пакетов: разные фичи могут быть настолько не очевидными, что при создании для них нового пакета нужно использовать два или более слова, чтобы точно отразить смысл. В правилах именования пакетов для Java от Oracle именовать пакеты предлагается слитно без разделителей, что в наших проектах могло привести к появлению пакетов со сложно читаемыми названиями. Поэтому мы добавили возможность разделять пакеты символом underscore. Стоит подчеркнуть, что использование такого стиля должно быть скорее вынужденным исключением, чем правилом, и стоит этого максимально избегать.


5. Аннотации


Другой пункт, по которому наше видение отличается от распространенного, оказалось использование одиночных аннотаций: в некоторых рекомендациях разрешается записывать несколько аннотаций без параметров в одну строку или даже оставлять одну аннотацию без параметров на одной строке с описываемым методом/полем. Такой подход, на наш взгляд, не несет особой пользы (разве что экономия места), а код из-за таких исключений становится менее консистентным, поэтому мы решили оставить единое правило для всех аннотаций: указывать их всегда с новой строки.


6. Функции как выражения


Также мы ограничили возможности по использованию function expression – описывать функцию как выражение разрешено только в случае, если она помещается в одну строку (для этого, кстати, мы увеличили максимальную длину строки до 120 символов). Если выражение не помещается в одну строку, вероятно, что при дальнейших изменениях может возникнуть потребность перевести эту функцию в обычный вариант написания, да и читаться такое выражение будет ничуть не проще.


Что имеем


В конце хотелось бы обозначить почему даже не смотря на то, что Google выпустил свой style guide мы все же публикуем свое представление. Как уже было сказано выше набор соглашений от разработчика самого языка – JetBrains из-за своей лаконичности едва ли покрывает все нужды команды, очень надеюсь, что команда разработки Kotlin-а не останется в стороне и будет в дальнейшем развивать этот список. При более внимательном просмотре замечаешь, что большинство правил Google были скопированы или переформулированы с их style guide по Java, мы же старались учитывать опыт и других смежных языков программирования, но в большей степени отталкивались от применения разных подходов к стилям, и как раз такой подход позволил нам раскрыть некоторые пункты (структуру класса, описание, вызов функций и др. правила).


Что дальше


Даже после принятия единого стандарта написания кода могут возникать проблемы: кто-то может забыть правила, или что еще хуже – начать саботировать их. Хорошо, если этот код будет забракован на этапе code review, но проверяющий может не заметить этих ошибок или даже участвовать в подрывной деятельности. Несмотря на молодость Kotlin, для него уже существуют инструменты по статическому анализу кода, для которых имеется возможность описать все правила и отслеживать любое нарушение автоматически, что уже на подходе в нашем бэклоге. Сервис поможет разработчикам быть более дисциплинированными и освободит Reviewer’а от необходимости проверять соответствие кода принятому стандарту, и тогда эти инструменты станут помощниками в поддержании чистоты на проекте.


Ссылки:


  1. Redmadrobot Kotlin Style Guide
  2. Coding Conventions c официального сайта Kotlin-a
  3. Style Guide в репозитории Дмитрия Жемерова
  4. Style Guide для Kotlin-а от Google

Комментарии (18)


  1. Jeevuz
    29.11.2017 12:24
    +2

    Для полей View из Kotlin Android Extension используется стиль именования lower_snake_case

    Печаль.


    1. Xanderblinov
      29.11.2017 18:38

      Это было тяжелое решение — на самом деле тут trade off между camelCase в xml и snake_case в котлин файлах. Мы выбрали второй вариант, хотя тут команда разбилась на два лагеря.

      На самом деле — это вкусовщина.


      1. Xanderblinov
        29.11.2017 19:31

        Кстати можете форкнуть наш кодстайл на гитхабе и переписать часть под свою команду и привычки ;)

        Если вспомните моменты, которые мы упустили то кидайте пулреквесты / issue


      1. Jeevuz
        29.11.2017 20:22

        Ну и стаилгайды в общем вкусовщина. Имхо, camelCase в xml меньшее зло. И так пишут те же гугловцы. Например, Reto Mayer в одном видео так писал.


      1. AngeuT
        30.11.2017 12:14

        На счет описания ID у вас есть хорошие практики? При Java было удобно именовать ID по описанному в интернете принципу What-Where-Description (textview_authorization_password_hint). В Kotlin Android Extension такое именование выходит чересчур длинное, а дальше кто на что горазд. Как пример: tvPasswordHint, passwordHintTextView.


        1. dsmr Автор
          30.11.2017 12:26

          Мы используем следующее правило: Where-What-Description (fragment_authorization_text_view_password_hint), так оказывается удобнее, т.к. IDE при использовании Kotlin Android Extensions может предлагать все View проекты, которые как-то идентифицированы, а когда мы работаем в контексте AuthorizationFragment и хотим использовать какие-л. View мы заранее знаем с чего начать их писать что бы IDE предлагала более актуальный автокомплит


    1. jericho_code
      30.11.2017 12:15

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


      1. Jeevuz
        30.11.2017 14:15

        Как написал ниже yanex можно включить подсветку для них. А использовать в коде имена не по конвенции не очень хорошо.


        1. jericho_code
          30.11.2017 14:22

          Не знал, что можно настроить подсветку именно для Kotlin Android Extensions, спасибо!) К сожалению, комментария yanex еще не было, когда я писал свой (мой проходил модерацию)


    1. yanex
      30.11.2017 12:15

      Если более точно – названия синтетических свойств совпадают с идентификаторами View (для @+id/text_view будет text_view). Это консистентно с R.id.<id>, и дополнительный манглинг имён редко повышает читаемость кода.
      Кстати, в Preferences > Color Scheme > Kotlin можно выбрать собственную подсветку для свойств Android Extensions.


      1. andreich
        30.11.2017 15:57

        вот кстати поэтому я именую id вьюх в lowCamelCase стиле, в IDE и так видно, что это вьюха среди других переменных. Именование с подчеркиванием напоминает устаревшие префиксы m и s, которые стали ненужны с появлением умных IDE с подсветкой.


    1. yanex
      30.11.2017 21:34

      А какая логика работы для вас была бы более приемлемой? Манглинг имён?


      1. Jeevuz
        30.11.2017 22:37
        +1

        Я считаю абсолютно правильным то как сейчас сделано. То, что имена в файлах такие же как в xml.
        И попробовав, считаю, что camelCase в лейаутах гораздо удобнее.


  1. basnopisets
    29.11.2017 13:24

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

    Гораздо проще оценить интерфейс класса при помощи вкладки Structure (Cmd+7) и тогда не придется отрывать приватные методы от места их использования


    1. Xanderblinov
      29.11.2017 19:29

      Тут дело не только в интерфейсе, но и в структуре класса.

      Мы рассматриваи следующие альтернативы:

      1. порядок по модификаторам
      2. порядок по регионам
      3. порядок не регламентируется

      Вариант порядок по регионам слабо коррелирует с Single Responsibility из солид
      Вариант порядок не регламентируется мы пробовали, но это было неудобно, особенно при переопределении методов ЖЦ Активити код был довольно разнородный и непредсказуемый

      В итоге остановились на варианте с порядоком по модификаторам


    1. OlegEstekhin
      30.11.2017 12:15

      Если надо оценить структуру типа в среде разработки, то да, проще.


      Если смотреть на diff в пул-реквесте вне среды разработки в каком-нибудь веб интерфейсе, когда видны только изменённые строки и не очевидно, что вокруг, и когда идентификаторы, используемые в этих строках, определены где-то в другом месте, то хороший (или для начала хотя бы единообразный) кодстайл очень помогает.


  1. akimserg
    29.11.2017 13:52

    Можете рассказать подробнее какого рода проекты были переписаны?


  1. Dwite
    30.11.2017 11:18

    Интересно по поводу


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

    Были какие-то проблемы?