Используйте Kotlin для больших команд

Переход на любой новый язык может оказаться непростой задачей. Рецепт успеха — начинать медленно, двигаться порциями и часто тестировать, чтобы сплотить команду для достижения успеха. Kotlin упрощает миграцию, поскольку он компилируется в байт-код JVM и полностью совместим с Java.

Создание команды

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

Формировать учебные группы

Учебные группы – это эффективный способ облегчить обучение и удержать знания. Исследования показывают , что повторение изученного в группе помогает закрепить материал. Приобретите книгу Kotlin или другой учебный материал для каждого члена группы и попросите группу проходить по паре глав каждую неделю. Во время каждой встречи группа должна сравнить то, что они узнали, и обсудить любые вопросы или наблюдения.

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

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

Назначить чемпиона

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

Интегрируйте медленно

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

Модель данных

Ваша модель данных, вероятно, состоит из большого количества информации о состоянии и нескольких методов. Модель данных также может иметь общие методы, такие как toString() , equals() и hashcode() . Эти методы обычно можно легко переносить и модульно тестировать по отдельности.

Например, предположим следующий фрагмент Java:

public class Person {

   private String firstName;
   private String lastName;
   // ...

   public String getFirstName() {
       return firstName;
   }

   public void setFirstName(String firstName) {
       this.firstName = firstName;
   }

   public String getLastName() {
       return lastName;
   }

   public void setLastName(String lastName) {
       this.lastName = lastName;
   }

   @Override
   public boolean equals(Object o) {
       if (this == o) return true;
       if (o == null || getClass() != o.getClass()) return false;
       Person person = (Person) o;
       return Objects.equals(firstName, person.firstName) &&
               Objects.equals(lastName, person.lastName);
   }

   @Override
   public int hashCode() {
       return Objects.hash(firstName, lastName);
   }

   @Override
   public String toString() {
       return "Person{" +
               "firstName='" + firstName + '\'' +
               ", lastName='" + lastName + '\'' +
               '}';
   }
}

Вы можете заменить класс Java одной строкой Kotlin, как показано здесь:

data class Person(var firstName: String?, var lastName : String?)

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

Перенос тестов

Еще один вариант, который следует рассмотреть, — преобразовать существующие тесты и начать писать новые тесты на Kotlin. Это может дать вашей команде время освоиться с языком, прежде чем писать код, который вы планируете поставлять вместе со своим приложением.

Переместите служебные методы в функции расширения.

Любые статические служебные классы ( StringUtils , IntegerUtils , DateUtils , YourCustomTypeUtils и т. д.) могут быть представлены как функции расширения Kotlin и использоваться существующей базой кода Java.

Например, предположим, что у вас есть класс StringUtils с несколькими методами:

package com.java.project;

public class StringUtils {

   public static String foo(String receiver) {
       return receiver...;  // Transform the receiver in some way
   }

   public static String bar(String receiver) {
       return receiver...;  // Transform the receiver in some way
   }

}

Эти методы затем можно использовать где-нибудь в вашем приложении, как показано в следующем примере:

...

String myString = ...
String fooString = StringUtils.foo(myString);

...

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

Для этого вы можете начать с преобразования этого класса Utils в Kotlin, используя автоматическое преобразование, предоставляемое IDE. Пример вывода может выглядеть примерно так:

package com.java.project

object StringUtils {

   fun foo(receiver: String): String {
       return receiver...;  // Transform the receiver in some way
   }

   fun bar(receiver: String): String {
       return receiver...;  // Transform the receiver in some way
   }

}

Затем удалите определение класса или объекта, добавьте к каждому имени функции префикс типа, к которому должна применяться эта функция, и используйте его для ссылки на тип внутри функции, как показано в следующем примере:

package com.java.project

fun String.foo(): String {
    return this...;  // Transform the receiver in some way
}

fun String.bar(): String {
    return this...;  // Transform the receiver in some way
}

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

@file:JvmName("StringUtils")
package com.java.project
...

Окончательная версия должна выглядеть примерно так:

@file:JvmName("StringUtils")
package com.java.project

fun String.foo(): String {
    return this...;  // Transform `this` string in some way
}

fun String.bar(): String {
    return this...;  // Transform `this` string in some way
}

Обратите внимание, что эти функции теперь можно вызывать с использованием Java или Kotlin с соглашениями, соответствующими каждому языку.

Котлин

...
val myString: String = ...
val fooString = myString.foo()
...

Ява

...
String myString = ...
String fooString = StringUtils.foo(myString);
...

Завершите миграцию

Как только ваша команда освоится с Kotlin и вы перенесете небольшие области, вы можете перейти к работе с более крупными компонентами, такими как фрагменты, действия, объекты ViewModel и другие классы, связанные с бизнес-логикой.

Соображения

Подобно тому, как Java имеет особый стиль, Kotlin имеет свой собственный идиоматический стиль, который способствует его краткости. Однако поначалу вы можете обнаружить, что код Kotlin, создаваемый вашей командой, больше похож на код Java, который он заменяет. Со временем это меняется по мере роста опыта вашей команды в Kotlin. Помните, постепенные изменения – ключ к успеху.

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

Общие стандарты кодирования

Обязательно определите стандартный набор соглашений по кодированию на ранних этапах процесса внедрения. Вы можете отклоняться от руководства по стилю Android Kotlin там, где это имеет смысл.

Инструменты статического анализа

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

Непрерывная интеграция

Обязательно соблюдайте общие стандарты кодирования и обеспечьте достаточное тестовое покрытие для вашего кода Kotlin. Включение этой части процесса автоматизированной сборки может помочь обеспечить согласованность и соблюдение этих стандартов.

Совместимость

Kotlin по большей части легко взаимодействует с Java, но обратите внимание на следующее.

Обнуляемость

Kotlin полагается на аннотации об обнуляемости в скомпилированном коде, чтобы сделать вывод об обнуляемости на стороне Kotlin. Если аннотации не предоставлены, Kotlin по умолчанию использует тип платформы, который можно рассматривать как тип, допускающий или не допускающий значения NULL. Однако, если не соблюдать осторожность, это может привести к проблемам NullPointerException во время выполнения.

Принять новые функции

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

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

val nullableFoo: Foo? = ...

// This lambda executes only if nullableFoo is not null
// and `foo` is of the non-nullable Foo type
nullableFoo?.let { foo ->
   foo.baz()
   foo.zap()
}

Целью этого примера является выполнение foo.baz() и foo.zap() если nullableFoo не равно нулю, что позволит избежать NullPointerException . Хотя этот код работает так, как и ожидалось, его читать менее интуитивно понятно, чем простую проверку на значение null и интеллектуальное приведение , как показано в следующем примере:

val nullableFoo: Foo? = null
if (nullableFoo != null) {
    nullableFoo.baz() // Using !! or ?. isn't required; the Kotlin compiler infers non-nullability
    nullableFoo.zap() // from guard condition; smart casts nullableFoo to Foo inside this block
}

Тестирование

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

Такое поведение создает проблему для фреймворков макетирования, таких как Mockito, которые полагаются на реализацию интерфейса или наследование для переопределения поведения во время тестирования. Для модульных тестов вы можете включить использование встроенной функции Mock Maker в Mockito, которая позволяет имитировать конечные классы и методы. Альтернативно вы можете использовать плагин компилятора All-Open, чтобы открыть любой класс Kotlin и его члены, которые вы хотите протестировать в рамках процесса компиляции. Основное преимущество использования этого плагина заключается в том, что он работает как с модульными, так и с инструментальными тестами.

Дополнительная информация

Для получения дополнительной информации об использовании Kotlin перейдите по следующим ссылкам: