Kotlin für große Teams anwenden

Der Wechsel zu einer neuen Sprache kann eine entmutigende Aufgabe sein. Das Erfolgsrezept besteht darin, langsam zu beginnen, in Etappen vorzugehen und häufig zu testen, um Ihr Team auf Erfolgskurs zu bringen. Kotlin erleichtert die Migration, da es in JVM-Bytecode kompiliert wird und vollständig mit Java interoperabel ist.

Team aufbauen

Der erste Schritt vor der Migration besteht darin, ein gemeinsames Grundverständnis für Ihr Team zu schaffen. Hier sind einige Tipps, die Ihnen dabei helfen können, das Lernen Ihres Teams zu beschleunigen.

Lerngruppen bilden

Lerngruppen sind eine effektive Möglichkeit, das Lernen und Behalten von Informationen zu erleichtern. Studien haben gezeigt dass das Wiederholen des Gelernten in einer Gruppe dazu beiträgt, das Material zu festigen. Besorgen Sie für jedes Mitglied der Gruppe ein Kotlin-Buch oder anderes Lernmaterial und bitten Sie die Gruppe, jede Woche ein paar Kapitel durchzugehen. Bei jedem Treffen sollte die Gruppe vergleichen, was sie gelernt hat, und alle Fragen oder Beobachtungen besprechen.

Eine Kultur des Lehrens schaffen

Nicht jeder sieht sich als Lehrer, aber jeder kann lehren. Von einem Technologie- oder Teamleiter bis hin zu einem einzelnen Mitarbeiter kann jeder eine Lernumgebung fördern, die zum Erfolg beitragen kann. Eine Möglichkeit, dies zu erleichtern, sind regelmäßige Präsentationen, bei denen eine Person im Team über etwas spricht, das sie gelernt hat oder teilen möchte. Sie können Ihre Lerngruppe nutzen, indem Sie Freiwillige bitten, jede Woche ein neues Kapitel zu präsentieren, bis sich Ihr Team mit der Sprache vertraut fühlt.

Champion benennen

Benennen Sie schließlich einen Champion, der die Lernbemühungen leitet. Diese Person kann als Fachexperte (Subject Matter Expert, SME) fungieren, wenn Sie mit der Einführung beginnen. Es ist wichtig, diese Person in alle Ihre Übungstreffen zu Kotlin einzubeziehen. Im Idealfall ist diese Person bereits von Kotlin begeistert und verfügt über einige praktische Kenntnisse.

Langsam integrieren

Langsam zu beginnen und strategisch zu überlegen, welche Teile Ihres Ökosystems zuerst migriert werden sollen, ist entscheidend. Oft ist es am besten, dies auf eine einzelne App in Ihrer Organisation zu beschränken, anstatt eine Flaggschiff-App zu verwenden. Was die Migration der ausgewählten App betrifft, ist jede Situation anders. Hier sind jedoch einige gängige Ausgangspunkte.

Datenmodell

Ihr Datenmodell besteht wahrscheinlich aus vielen Statusinformationen und einigen Methoden. Das Datenmodell kann auch gängige Methoden wie toString(), equals() und hashcode() enthalten. Diese Methoden können in der Regel einfach isoliert migriert und getestet werden.

Nehmen wir zum Beispiel das folgende Java-Snippet an:

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 + '\'' +
               '}';
   }
}

Sie können die Java-Klasse durch eine einzelne Zeile Kotlin ersetzen, wie hier gezeigt:

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

Dieser Code kann dann mit Ihrer aktuellen Testsuite getestet werden. Die Idee ist, klein anzufangen, mit jeweils einem Modell, und Klassen zu migrieren, die hauptsächlich Status und nicht Verhalten enthalten. Testen Sie dabei häufig.

Tests migrieren

Ein weiterer möglicher Ausgangspunkt ist, vorhandene Tests zu konvertieren und neue Tests in Kotlin zu schreiben. So hat Ihr Team Zeit, sich mit der Sprache vertraut zu machen, bevor es Code schreibt, den Sie mit Ihrer App ausliefern möchten.

Dienstmethoden in Erweiterungsfunktionen verschieben

Alle statischen Dienstklassen (StringUtils, IntegerUtils, DateUtils, YourCustomTypeUtils, usw.) können als Kotlin-Erweiterungsfunktionen dargestellt und von Ihrer vorhandenen Java-Codebasis verwendet werden.

Nehmen wir zum Beispiel an, Sie haben eine StringUtils-Klasse mit einigen Methoden:

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
   }

}

Diese Methoden können dann an anderer Stelle in Ihrer App verwendet werden, wie im folgenden Beispiel gezeigt:

...

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

...

Mit Kotlin-Erweiterungsfunktionen können Sie Java-Aufrufern dieselbe Utils-Schnittstelle zur Verfügung stellen und gleichzeitig eine prägnantere API für Ihre wachsende Kotlin-Codebasis anbieten.

Dazu können Sie diese Utils-Klasse mit der automatischen Konvertierung der IDE in Kotlin konvertieren. Eine Beispielausgabe könnte so aussehen:

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
   }

}

Entfernen Sie als Nächstes die Klassen- oder Objektdefinition, stellen Sie jedem Funktionsnamen den Typ voran, auf den diese Funktion angewendet werden soll, und verwenden Sie diesen, um innerhalb der Funktion auf den Typ zu verweisen, wie im folgenden Beispiel gezeigt:

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
}

Fügen Sie schließlich oben in der Quelldatei eine JvmName-Annotation hinzu, damit der kompilierte Name mit dem Rest Ihrer App kompatibel ist, wie im folgenden Beispiel gezeigt:

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

Die endgültige Version sollte so aussehen:

@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
}

Beachten Sie, dass diese Funktionen jetzt mit Java oder Kotlin mit Konventionen aufgerufen werden können, die der jeweiligen Sprache entsprechen.

Kotlin

// ...

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

// ...

Java

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

Migration abschließen

Sobald sich Ihr Team mit Kotlin vertraut gemacht hat und Sie kleinere Bereiche migriert haben, können Sie sich größeren Komponenten wie Fragmenten, Aktivitäten, ViewModel-Objekten und anderen Klassen zuwenden, die mit der Geschäftslogik zusammenhängen.

Hinweise

Wie Java hat auch Kotlin einen eigenen idiomatischen Stil, der zu seiner Prägnanz beiträgt. Anfangs sieht der Kotlin-Code, den Ihr Team erstellt, jedoch möglicherweise eher wie der Java-Code aus, den er ersetzt. Das ändert sich mit der Zeit, wenn die Kotlin-Erfahrung Ihres Teams wächst. Denken Sie daran: Allmähliche Veränderungen sind der Schlüssel zum Erfolg.

Hier sind einige Dinge, die Sie tun können, um Konsistenz zu erreichen, wenn Ihre Kotlin-Codebasis wächst:

Gemeinsame Codierungsstandards

Definieren Sie frühzeitig im Einführungsprozess einen Standardsatz von Codierungskonventionen. Sie können vom Android Kotlin-Styleguide abweichen, wenn das sinnvoll ist.

Tools für die statische Analyse

Setzen Sie die für Ihr Team festgelegten Codierungsstandards mit Android Lint und anderen Tools für die statische Analyse durch. klint, ein Kotlin Linter von Drittanbietern, bietet auch zusätzliche Regeln für Kotlin.

Continuous Integration

Achten Sie darauf, dass Sie die gängigen Codierungsstandards einhalten und eine ausreichende Testabdeckung für Ihren Kotlin-Code bieten. Wenn Sie dies in einen automatisierten Build-Prozess einbeziehen, können Sie Konsistenz und die Einhaltung dieser Standards sicherstellen.

Interoperabilität

Kotlin ist größtenteils nahtlos mit Java interoperabel, aber beachten Sie Folgendes.

Null-Zulässigkeit

Kotlin verwendet Null-Zulässigkeits-Annotationen im kompilierten Code, um die Null-Zulässigkeit auf der Kotlin-Seite abzuleiten. Wenn keine Annotationen angegeben werden, verwendet Kotlin standardmäßig einen Plattformtyp, der als Null-zulässiger oder nicht Null-zulässiger Typ behandelt werden kann. Dies kann jedoch zu NullPointerException-Problemen zur Laufzeit führen, wenn nicht sorgfältig damit umgegangen wird.

Neue Funktionen übernehmen

Kotlin bietet viele neue Bibliotheken und syntaktischen Zucker, um Boilerplate-Code zu reduzieren, was die Entwicklungsgeschwindigkeit erhöht. Seien Sie jedoch vorsichtig und methodisch, wenn Sie die Standard bibliotheksfunktionen von Kotlin verwenden, z. B. Sammlungsfunktionen, Coroutinen, und Lambdas.

Hier ist eine sehr häufige Falle, in die neuere Kotlin-Entwickler tappen. Nehmen wir den folgenden Kotlin-Code an:

val nullableFoo: Foo? = 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()
}

In diesem Beispiel sollen foo.baz() und foo.zap() ausgeführt werden, wenn nullableFoo nicht null ist, um eine NullPointerException zu vermeiden. Dieser Code funktioniert zwar wie erwartet, ist aber weniger intuitiv zu lesen als eine einfache Null-Prüfung und ein Smart Cast, wie im folgenden Beispiel gezeigt:

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
}

Test

Klassen und ihre Funktionen sind in Kotlin standardmäßig für die Erweiterung geschlossen. Sie müssen die Klassen und Funktionen, die Sie unterklassen möchten, explizit öffnen. Dieses Verhalten ist eine Entscheidung des Sprachdesigns, die getroffen wurde, um die Komposition gegenüber der Vererbung zu fördern. Kotlin bietet integrierte Unterstützung für die Implementierung von Verhalten durch Delegierung um die Komposition zu vereinfachen.

Dieses Verhalten stellt ein Problem für Mocking-Frameworks wie Mockito dar, die auf der Schnittstellenimplementierung oder der Vererbung beruhen, um Verhaltensweisen während des Tests zu überschreiben. Für Unittests können Sie die Verwendung der Mock Maker Inline Funktion von Mockito aktivieren, mit der Sie finale Klassen und Methoden simulieren können. Alternativ können Sie das Compiler-Plug-in All-Open verwenden, um jede Kotlin-Klasse und ihre Elemente zu öffnen, die Sie im Rahmen des Kompilierungsprozesses testen möchten. Der Hauptvorteil dieses Plug-ins besteht darin, dass es sowohl mit Unittests als auch mit instrumentierten Tests funktioniert.

Weitere Informationen

Weitere Informationen zur Verwendung von Kotlin finden Sie unter den folgenden Links: