Blog

Kotlin Edelsteine : Funktionen, die ich gerne früher entdeckt hätte

Masood Masaeli

Aktualisiert Oktober 15, 2025
17 Minuten

Haben Sie schon einmal von "Delegation" oder "Value Classes" in Kotlin gehört? Wenn nicht, verpassen Sie einige leistungsstarke Funktionen, die Ihre Entwicklung reibungsloser und effizienter machen können.

Im Laufe meiner Karriere habe ich Tausende von Pull Requests erstellt und geprüft. Obwohl der Code oft sauber und funktional ist, habe ich festgestellt, dass er noch besser sein könnte, wenn die Entwickler mit einigen der fortgeschrittenen Sprachfunktionen und Bibliotheken von Kotlin besser vertraut wären.

Viele Entwickler, die von Java (oder anderen JVM-basierten Sprachen) zu Kotlin wechseln, folgen einer natürlichen Entwicklung - eine, die auch ich erlebt habe. Es gibt jedoch Funktionen, syntaktische Feinheiten und Konventionen, die oft unbemerkt bleiben.

Ich habe eine wachsende Liste dieser übersehenen Aspekte zusammengestellt, und ich habe beschlossen, dass es an der Zeit ist, sie mit Ihnen zu teilen. Zunächst nenne ich die Punkte, die überall in Ihrer Codebasis anwendbar sind, von den am einfachsten anzuwendenden bis zu den teuersten. Dann werde ich einige Punkte ansprechen, die ausschließlich für Testcode gelten.

Variablen und Unveränderlichkeit

Ich finde es reizvoll, die Bedeutung der Unveränderlichkeit zu betonen. Wann immer es möglich ist (fast überall), sollten Sie veränderbare Variablen vermeiden, um die Sicherheit von Threads, die Vorhersagbarkeit und eine klare Absicht zu fördern.

val, var Dilemma

Ich habe oft erlebt, dass Entwickler var gewählt haben, obwohl sie val hätten verwenden können.
Während Variablen eines der ersten Konzepte sind, die wir in der Programmierung lernen, wird die Idee von Konstanten oder Werten (val in Kotlin) oft übersehen.
Vermeiden Sie var so weit wie möglich und verwenden Sie stattdessen val, da es Unveränderlichkeit ohne Kosten bringt.

Daten-Klassen

Seit Java 17 (ursprünglich in Version 14) haben Sie den Luxus, records zu deklarieren. Aber in der Kotlin-Welt wurde dieses Konzept bereits in der Anfangsphase der Entwicklung der Sprache eingeführt. Datenklassen reduzieren den Boilerplate-Code (oder die Injektion von Abhängigkeiten wie Lombok) und machen Ihre Instanzen mit den folgenden Funktionalitäten:

  • equals()/.hashCode() Paar. So können Sie sicher sein, dass der Inhalt verglichen wird, wenn Sie das Gleichheitszeichen oder die Funktion anstelle des Referenzvergleichs verwenden.
  • toString() der Form Person(name=John, age=42).
  • copy()Einer meiner Favoriten, da er die Unveränderlichkeit fördert, da Sie ein bestimmtes Objekt kopieren und nur die benötigten Eigenschaften zuweisen können.
val Jack =  Person(Name  = "Jack", Alter  = 1)
val älterJack =  jack.copy(age  = 2)

jack.toString()  // Person(name=Jack, Alter=1)
älterJack.toString()  // Person(name=Jack, alter=2)

Überlegungen zur Datenklasse

Es ist wichtig zu verstehen, dass die oben genannten Funktionen nur die Eigenschaften berücksichtigen, die im Konstruktor von data class definiert sind.
Das bedeutet, dass sich der folgende Code entgegen den Erwartungen einiger Leute verhalten könnte:

Daten Klasse Person(val Name:  String,  val Alter:  Int) {
    var Stimmung = "normal"
}

val Jack =  Person(Name  = "Jack", Alter  = 1)
val verärgertJack =  jack.copy()
upsetJack.mood  = "verärgert"

jack.toString()  // Serialisiert die Eigenschaft 'Stimmung' NICHT
Jack  ==  verärgertJack  // Berechnet zu TRUE

Unveränderliche Sammlungen

In Kotlin sind Sammlungen wie List standardmäßig unveränderlich. Das bedeutet, dass sie keine Funktionen wie add oder remove bieten. Wenn Sie Elemente hinzufügen oder entfernen möchten, müssen Sie neue Sammlungen erstellen.

Das ist recht praktisch, da es die Unveränderlichkeit fördert, was zu besserer Vorhersehbarkeit und klarer Absicht führt.
Manchmal zwingen Sie Ihr Algorithmus und Ihre Logik jedoch dazu, mit veränderbaren Sammlungen zu arbeiten.
In solchen Fällen können Sie die veränderbare Version der Sammlung verwenden.
Ein Beispiel ist MutableList, das Funktionen zur Veränderung der Liste bietet.

Inline-Wert-Klassen

Wenn eine Eigenschaft eine bestimmte Bedeutung hat, ist es besser, diese Tatsache in Ihrem Code widerzuspiegeln, unabhängig davon, ob Sie den Prinzipien des Domain-Driven Design (DDD) folgen oder nicht. Inline Value Classes in Kotlin können Ihnen dabei helfen, dies zu erreichen.

Warum nicht einfache primitive Typen verwenden?

Primitive Typen funktionieren, aber sie schränken ungültige Werte nicht ein. Es ist besser, Typen zu verwenden, die Geschäftsregeln durchsetzen und eine Bedeutung vermitteln.

Warum nicht "in eine Klasse einpacken"?

Wenn Sie einen Wert in eine Klasse verpacken, entsteht ein Laufzeit-Overhead, z.B. durch zusätzliche Heap-Zuweisungen. Noch kritischer ist, dass Sie Laufzeitoptimierungen verlieren, insbesondere wenn der Wert ein primitiver Typ ist.

Warum nicht "Typ-Alias" verwenden?

Das Hauptproblem bei Typ-Aliasen ist, dass sie mit ihrem zugrunde liegenden Typ zuweisungskompatibel bleiben.
Außerdem können Sie mit Typ-Aliasen keine domänenbezogene Logik implementieren, wie z.B. die Validierung während der Konstruktion oder Konvertierung.

Inline-Wert-Klassen in der Praxis

Nehmen wir an, Sie bauen ein CMS für eine Hochschule auf, bei dem die Studenten-IDs Eigenschaften mehrerer Entitäten sind. Die IDs folgen diesem Format:

[major_code{2}][freshman_year{2}][random_number{3}]

Sie können nun die folgende Werteklasse definieren:

@JvmInline
Wert Klasse StudentId(val id:  Int) {
  init  {
  require(isValid(id)) {
          "Schüler-ID '$id' ist nicht gültig"
  }
  }

  Begleiter Objekt {
      privat Spaß isValid(id:  Int)  =  id  in 1000000L..9999999
  }
}

Daten Klasse Student  (
  val id: StudentId,
  val Name:  String,
)

Überlegungen zur Inline-Wertklasse

Verwendung in Java

Wenn Sie eine Wertklasse in Kotlin definieren und diese in Ihrem Java-Code verwenden möchten, müssen Sie die Annotation @JvmName anwenden, um die Namensvermischung zu deaktivieren.
Die Namensvermischung ist Kotlins Mechanismus, der sicherstellt, dass die JVM zwischen der Wertklasse und dem zugrunde liegenden Typ unterscheiden kann, was die Überladung von Funktionen und andere Funktionen ermöglicht.

Spring, JPA und viele andere Java-Frameworks und -Bibliotheken verarbeiten den kompilierten Code von Kotlin. Standardmäßig sind die Namen der Eigenschaften nicht die exakten Namen, die Sie zuweisen. Sie enthalten ein name-hashcode (z.B. {... "id-9ycq1": 1234567 ...} statt {... "id": 1234567 ...}) aufgrund der Namensumwandlung der Funktion get.
Dies bedeutet, dass Sie bei der Arbeit mit Inline Value Classes auf einige Probleme stoßen können. Nämlich:

Jackson

Jackson ist ein weit verbreiteter Object Mapper in vielen Java-Frameworks wie Spring. Diese Java-Bibliothek funktioniert möglicherweise nicht perfekt mit Inline Value Classes ohne die erforderliche Konfiguration oder Abhängigkeiten.
Das Problem liegt natürlich in der Namensverfälschung, da die Namen der Eigenschaften von Value Class-Typen standardmäßig einen zusätzlichen Hash-Wert haben.
Dieses Problem kann durch Hinzufügen der Abhängigkeit jackson-module-kotlin entschärft werden.
Ihre Anwendung würde nach Hinzufügen dieser Abhängigkeit einwandfrei funktionieren, aber Sie müssen den Jackson Object Mapper aus dem Kotlin-Modul com.fasterxml.jackson.module.kotlin.jacksonObjectMapper anstelle von com.fasterxml.jackson.databind.ObjectMapper verwenden.

Bei der Gson-Bibliothek gibt es diesen Vorbehalt nicht.

JPA und Hibernate

Seien Sie vorsichtig, wenn Sie Wertklassen in JPA-Entitäten verwenden.
Es sollte standardmäßig funktionieren, es sei denn, Sie müssen eine löschbare Eigenschaft vom Typ Wertklasse einführen.
In diesem Fall müssen Sie sicherstellen, dass Sie einen Konverter für den löschbaren Typ einführen.
Betrachten Sie zum Beispiel das folgende Stück Code:

@Entität
Daten Klasse Student  (
  @Id
  val id:  Lang,
  @Kolumne
  val studentId: StudentId?,
  @Kolumne
  val Name:  String,
)

In dem StudentId eine Wertklasse ist. Dabei tritt ein Fehler auf, der wie folgt aussieht:

...
org.hibernate.type.descriptor.java.spi.JdbcTypeRecommendationException: Es konnte kein empfohlener JdbcType für den Java-Typ '....StudentId' ermittelt werden.
...

Die Lösung wäre die Einführung einer @Converter:

@Entität
Daten Klasse Student  (
  @Id
  val id:  Lang,
  @Kolumne
  @Konvertieren(Konverter  =  NullableStudentIdConverter::Klasse)
  val studentId: StudentId?,
  @Kolumne
  val Name:  String,
)

@Konverter
Klasse NullableStudentIdConverter: AttributeConverter<StudentId,  Int?>  {
  Überschreiben Sie Spaß convertToDatabaseColumn(studentId: StudentId?)  =  StudentId?.id
  Überschreiben Sie Spaß convertToEntityAttribute(id:  Int?)  =  id?.let { StudentId(es) }
}
Swagger OpenApi Dokumentation

Da Swagger in Java implementiert ist, kann es vorkommen, dass Sie in Ihrer automatisch generierten Dokumentation auf die verstümmelte Version der Namen der Funktionen stoßen, die eine Value Class als Eingabe haben.
Dies geschieht für die operationId in der Schemaspezifikation, die Sie bei Bedarf durch Überschreiben der operationId aus der @Operation Annotation korrigieren können.

Die Liste ließe sich noch weiter fortsetzen, aber als Faustregel kann man sagen, dass

  1. Stellen Sie sicher, dass Sie die Kotlin-freundlichen Module verwenden, wann immer sie angeboten werden.
  2. Achten Sie auf den Mechanismus der Namensmanipulation und seine Nebenwirkungen.

Bessere Praktiken bei Scope-Funktionen

Die Praxis der Verkettung wurde mit der Einführung von Java Streams populär. In Kotlin können Sie Verkettungs- und Bereichsfunktionen verwenden, um einzelne Codeblöcke zu erstellen, die Lesbarkeit zu verbessern und das Risiko bestimmter menschlicher Fehler zu verringern. Manchmal sind wir jedoch nicht vollständig mit all diesen Funktionen und ihrem Potenzial vertraut.

Es gibt fast keine Möglichkeit, Scope-Funktionen übermäßig zu nutzen. Wenn also eine Aufgabe damit erledigt werden kann, ist es normalerweise eine gute Idee, dies zu tun.

.let und .run

Diese beiden Bereichsfunktionen sind wohlbekannt, werden aber oft nicht ausreichend genutzt.
Ein häufiges Muster, bei dem sie übersehen werden, ist, wenn eine Funktion mehrere Exit-Points hat.
Obwohl mehrere Exit-Points in einer Funktion manchmal unvermeidlich sind, erhöht sich dadurch die kognitive Belastung des Lesers, der den Code durchgehen muss, um sicherzustellen, dass alle Szenarien abgedeckt sind.
Die Verwendung von .let oder .run kann helfen, diese Logik zu rationalisieren. Betrachten Sie das folgende Beispiel:

Spaß berechnenAlter(Student: Student?): Zeitraum? {  
    wenn  (Student  == null)  return null  
    return  LocalDate.now().until(student.dateOfBirth)  
}

Im Gegensatz zum Einsatz von let:

Spaß calculateAgeWithLet(Student: Student?)  =  Student?.lassen {  
  LocalDate.now().until(es.dateOfBirth)  
}

Oder alternativ, wenn run verwendet wird:

Spaß calculateAgeWithLet(Student: Student?)  =  Student?.laufen {  
  LocalDate.now().until(diese.dateOfBirth)  
}

Ich finde die letzteren Versionen viel prägnanter und lesbarer.

.also

Manchmal müssen Sie kurz vor dem Ende Ihres Codeblocks eine zusätzliche Aufgabe ausführen, wie z.B. die Protokollierung oder das Speichern eines Audit-Protokolls. Sie könnten zwar den Rückgabewert speichern, die Aufgabe ausführen und dann den Wert zurückgeben, aber es ist viel einfacher, die Funktion .also zu verwenden. Diese Erweiterungsfunktion ermöglicht es Ihnen, die Aufgabe auszuführen, ohne den Rückgabewert zu verändern, wodurch Ihr Code sauberer und effizienter wird.

Spaß calculateAgeWithLet(Student: Student?): Zeitraum?  =  Student?.lassen {  
  LocalDate.now().until(es.dateOfBirth)  
}?.auch { writeToLog("Student '$Student' ist '$es' alt.") }

.apply

apply ist eine Erweiterungsfunktion, die Variablen eines Objekts neu zuweisen und die geänderte Version zurückgeben kann.
Sie wird häufig für die Objektkonfiguration verwendet.
Zum Beispiel:

val Mapper =  jacksonObjectMapper().apply {
    val Modul =  KotlinModule()
  module.registerSerializer(Student::Klasse, CustomStudentSerializer())
  registerModule(modul)
  }
mapper.writeValueAsString(student)

In dem obigen Beispiel wird zunächst das Mapper-Objekt erstellt und dann die Konfiguration in seinem spezifischen Block darauf angewendet. In diesem Codeblock verweist das Referenzobjekt this auf die Instanz von jacksonObjectMapper.

Umfang Funktion Überlegungen

Verschachtelung

Von der Verschachtelung von Bereichsfunktionen wird generell abgeraten. Wenn Sie diese Funktionen verschachteln, können die Verweise it oder this innerhalb verschiedener verschachtelter Blöcke ihre Bedeutung ändern, was die Lesbarkeit beeinträchtigt und das Risiko von Fehlern aufgrund menschlicher Fehler erhöht.

Konsequent sein

Die Verkettung von run mit let oder ähnlichem kann aufgrund der unterschiedlichen Verwendung von Referenzen (this vs. it) verwirrend sein. Bleiben Sie für eine bessere Lesbarkeit bei einem Stil.

Nicht-Erweiterung run

Es gibt zwei Versionen der Funktion run in Kotlin. Die eine ist eine Nicht-Erweiterungsfunktion, d.h. sie kann am Anfang eines Blocks verwendet werden, ohne dass eine Objektreferenz erforderlich ist:

  laufen {
    // hier wird etwas Arbeit erledigt.
    diese // existieren möglicherweise nicht oder verweisen auf dasselbe Objekt wie außerhalb des Blocks.
  }

Die andere Funktion run ist .run, die eine Erweiterungsfunktion ist, die this als Referenz innerhalb ihres Blocks verwendet.

Anmerkungen zu Null-Sicherheitsoperatoren

Der Elvis Operator

Der Elvis-Operator ?: ist ein leistungsfähiges Werkzeug für den Umgang mit Null-Bedingungen in Ihrem Code.
Er verbessert die Lesbarkeit, indem er Ihren Code prägnanter macht und gleichzeitig sicherstellt, dass Operationen oder Funktionen einen sinnvollen Nicht-Null-Wert zurückgeben.
Betrachten Sie die folgende Zuweisung:

val dateOfBirth: Zeitraum  = wenn(Student  != null) student.dateOfBirth  sonst werfen  IllegalStateException("irgendeine aussagekräftige Fehlermeldung hier")

Dies kann umgeschrieben werden als:

val dateOfBirth =  Student?.dateOfBirth  ?: werfen  IllegalStateException("irgendeine aussagekräftige Fehlermeldung hier")

Das ist besser lesbar, prägnant und auf den Punkt gebracht.

Der !! Operator in Ihrem Hauptcode

Entschuldigung an die NPE
1
1-Liebhaber, aber ich empfehle, die Verwendung dieses Operators in Ihrem Hauptcode zu verbieten (nicht in Tests, da Testcode vollständig deterministisch sein soll).
Verwenden Sie stattdessen den Elvis-Operator oder andere Methoden zur Behandlung der Nullbarkeit.
Der Grund dafür ist, dass Sie die Nullbarkeit Ihres Objekts/Werts zum Zeitpunkt der Deklaration kontrollieren sollten.
Wenn das nicht möglich ist, geben Sie aussagekräftige benutzerdefinierte Meldungen zurück oder werfen sie aus, wenn Sie auf Nullzustände stoßen.
Die !! Operator versucht sicherzustellen, dass der optionale Wert nicht Null ist, aber wenn dies nicht der Fall ist, löst er den berüchtigten NullPointerException.
Zu diesem Zeitpunkt ist es zu spät - Sie hätten sich um die Nullbarkeit kümmern müssen, bevor Sie diese Codezeile erreichen!

Tresor Besetzungen

In Kotlin können Sie das reservierte Wort is verwenden, um zu prüfen, ob ein Objekt von einem Typ ist oder nicht.
Sie können auch das reservierte Wort as verwenden, um ein Objekt auf einen bestimmten Typ zu casten, wenn der Typ ein Supertyp ist.
Ein Beispiel:

val Person: Person?  = wenn  (Student  ist  Person) Student  als  Person  sonst null

Sie können den obigen Code umschreiben in:

val Person: Person?  =  Student  wie?  Person

Ich finde die letztere Version viel lesbarer und vor allem weniger anfällig für menschliche Fehler, da Sie das Objekt und den Typ nicht immer wiederholen.

Delegationen

Eine der mächtigen Funktionen von Kotlin, die ich gerne früher entdeckt hätte, ist die Delegation, die mit dem Schlüsselwort by erreicht wird.

Die Delegation ist in vielen Szenarien nützlich. Wenn Sie zum Beispiel Spring JPA (Java Persistence API) verwenden und eine JpaRepository definieren, deckt die Schnittstelle, die typischerweise von einem JPA-Anbieter wie Hibernate implementiert wird, bereits viele Ihrer Bedürfnisse ab. Nehmen wir jedoch an, Sie möchten diesem Repository eigene Funktionen hinzufügen. In Kotlin können Schnittstellen, anders als in Java, ihre eigenen Funktionen implementieren.

Die Herausforderung besteht darin, dass das JPA-Repository erwartet, dass Funktionsnamen sinnvoll mit der zugrunde liegenden Entität und ihren Eigenschaften übereinstimmen, und es versucht, sie automatisch zu implementieren. Dies kann zu Fehlern bei der Kompilierung führen. Sie haben mindestens zwei Möglichkeiten:

  1. Fügen Sie eine Komponente hinzu: Erstellen Sie eine separate Komponente, die intern die erforderlichen Funktionen aus dem Repository aufruft. Bei diesem Ansatz müssen Sie jedoch jedes Mal eine zusätzliche Codezeile hinzufügen, wenn Sie eine neue Funktion aufrufen müssen.

  2. Verwenden Sie die Delegation: Erstellen Sie eine Komponente, die die Repository-Schnittstelle erbt und die Implementierung der JpaRepository an Hibernate delegiert. Mit der Delegationsfunktion von Kotlin können Sie eine Spring-Komponente erstellen, die die gesamte Funktionalität der Repository-Schnittstelle bietet und gleichzeitig neue Funktionen hinzufügt. Hier ist ein Beispiel:

@Repository  
Schnittstelle StudentRepository  : JpaRepository<Student,  Int>

@Komponente  
Klasse StudentExtendedRepository(jpaRepo: StudentRepository) : StudentRepository  von  jpaRepo {  
  Spaß sayHello()  =  findAll().map {  "Hallo ${es.name}"  }  
}
...
// Das funktioniert:
studentExtendedRepository.sayHello()
// Das hier auch:
studentExtendedRepository.findAll()

Delegation von Eigentum

Delegationen allein und insbesondere Property Delegation verdienen einen eigenen Artikel, da es viele Varianten und Anwendungsfälle gibt.
Ich empfehle Ihnen dringend, die offiziellen Dokumente dazu zu lesen.

An dieser Stelle möchte ich ein paar der Vermögensdelegationen erwähnen, die ich sehr interessant finde.

Erkennbare Eigenschaften

Sie können den observable Standarddelegaten hinzufügen, um Änderungen an einer Eigenschaft zu protokollieren oder notwendige Operationen nach einer Änderung durchzuführen.
Wenn Sie den neuen Wert abfangen und vor der Zuweisung außer Kraft setzen müssen, sollten Sie stattdessen den vetoable Standarddelegaten verwenden.

Klasse Benutzer  {
    var Name:  String von  beobachtbar("<kein Name>") {
  Stütze, alt, neu  ->
  println("$alt  -> $neu")
  }
}

Spaß Haupt() {
    val Benutzer =  Benutzer()
  benutzer.name  = "erste" // druckt <ohne Namen> -> zuerst
  benutzer.name  = "zweite" // druckt erstens -> zweitens
}

Delegieren an eine andere Eigenschaft

An dieser Stelle möchte ich nicht mehr sagen, als dass ich Sie auf das Codebeispiel aus den offiziellen Dokumenten verweise.
Der Code ist so einfach und kommt mit gut benannten Werten daher, dass ich denke, es wäre besser, die Erklärung Kotlin zu überlassen.

var topLevelInt:  Int = 0
Klasse ClassWithDelegate(val anotherClassInt:  Int)

Klasse MyClass(var memberInt:  Int,  val anotherClassInstance: ClassWithDelegate) {
    var delegatedToMember:  Int von diese::memberInt
    var delegatedToTopLevel:  Int von ::topLevelInt

    val delegatedToAnotherClass:  Int von  anotherClassInstance::anotherClassInt
}
var MyClass.extDelegated:  Int von ::topLevelInt

Ich finde die Delegation an eine andere Eigenschaft in derselben Klasse sehr nützlich, wenn Sie eine bestimmte Eigenschaft als veraltet markieren, sie aber noch eine Weile behalten wollen.
Ihre Codebasis muss sich nicht um die veraltete Eigenschaft kümmern, sondern Sie können einfach angeben, dass sie den Wert aus der neuen Eigenschaft liest.

Überlegungen zur Delegation

Achten Sie darauf, wer was umsetzt

Beim Übergang von der traditionellen Erbschaft kann man leicht in eine übliche Falle tappen:

in der Annahme, dass, wenn Ihre abgeleitete Klasse oder Schnittstelle eine val oder fun überschreibt, andere Funktionen in der Basisklasse automatisch die überschriebene Version verwenden werden.

Bei der Delegation verwenden jedoch nicht überschriebene Funktionen in der Basisklasse weiterhin ihre ursprüngliche Implementierung, da es sich um unabhängige Objekte handelt.
Dies kann zu unerwartetem Verhalten führen, daher ist es sehr wichtig zu verstehen, wie die Delegation in diesem Zusammenhang funktioniert. Um dies besser zu verstehen, betrachten Sie das folgende Beispiel:

Schnittstelle Basis  {
  val Nachricht:  Zeichenfolge
  Spaß drucken() { println(Nachricht) }
}

Klasse Abgeleitet(b: Base) : Basis  von  b {
  Überschreiben Sie val Nachricht = "Abgeleitet"
}

Spaß Haupt() {
  val b = Objekt  :  Basis  {
    Überschreiben Sie val Nachricht = "Basis"
  }
  val abgeleitet =  Abgeleitet(b)
  abgeleitet.drucken()  // druckt > Basis
  println(derived.message)  // druckt > Abgeleitet
}

Für immer jetzt!

Schauen Sie sich die Kotest Erweiterungen an, und ich garantiere Ihnen, dass Sie mindestens ein interessantes Tool finden werden, das Sie in Ihrem Projekt verwenden können.

Besonders hervorzuheben ist die Instant-Erweiterung, die die statischen Funktionen von now in java.time ersetzt, um Ihre Tests berechenbarer zu machen.

Hier ist ein Beispiel aus der Dokumentation der Kotest-Erweiterungen:

val foreverNow =  LocalDateTime.now()

withConstantNow(foreverNow) {
  LocalDateTime.now() sollte foreverNow sein
  Verzögerung(10)  // Die Ausführung des Codes dauert ein wenig länger, aber now hat sich geändert!
  LocalDateTime.now() sollte foreverNow sein
}

Forever Now Überlegungen

Rennbedingungen

Diese Erweiterung ist sehr anfällig für Race Conditions, da sie die statische Methode now außer Kraft setzt, die für die gesamte JVM-Instanz global ist. Wenn Sie bei der Verwendung dieser Erweiterung Tests parallel ausführen, können die Ergebnisse inkonsistent sein.

Wieder Rennbedingungen

Beachten Sie, dass sich zeitabhängige Race Conditions in Ihrem Code sehr unterschiedlich verhalten können, wenn die Zeit nicht wie erwartet voranschreitet. Wenn Ihre Logik außerdem verschiedene now Werte während ihres Lebenszyklus benötigt, ist diese Erweiterung möglicherweise nicht die beste Wahl.

Pakete öffnen mit --add-opens

Wenn Sie diese Erweiterung in Kotlin verwenden, kann ein Fehler wie der folgende auftreten:

  ...
  Verarbeitung der Anfrage fehlgeschlagen:
  java.lang.reflect.InaccessibleObjectException: Unable to make private static int java.time.OffsetDateTime.compareInstant(java.time.OffsetDateTime,java.time.OffsetDateTime) accessible: Modul java.base "öffnet nicht java.time" für unbenanntes Modul
  ...

Dieser Fehler zeigt an, dass die Erweiterung die Funktionalität von now nicht ersetzen kann, weil das Paket java.time nicht geöffnet ist. Der einfachste Weg, dieses Problem zu lösen, ist, das Paket mit Java-Argumenten zu öffnen. In Gradle Kotlin können Sie zum Beispiel hinzufügen:

Aufgaben  {  
  Test  {  
  jvmArgs(  
            "--add-opens",  
            "java.base/java.time=ALL-UNNAMED",  
        )  
    }  
}

JSON-Assertionen

Eines der coolsten Matcher-Module im Kotest Framework ist der JSON Matcher. Wenn Sie API- oder Integrationstests schreiben, müssen Sie oft sicherstellen, dass der Inhalt eines JSON-Strings korrekt ist. Dieser Satz von Matchern bietet leistungsstarke Werkzeuge, um zu überprüfen, ob ein ganzer JSON-String oder nur ein Teil davon mit einer erwarteten JSON-Struktur übereinstimmt. Zwei besonders nützliche Matcher sind:

  • shouldEqualJson: Überprüft, ob eine Zeichenkette mit einer gegebenen JSON-Struktur übereinstimmt.
  • shouldEqualSpecifiedJson: Überprüft, ob eine Zeichenkette mit einer gegebenen JSON-Struktur übereinstimmt, erlaubt aber zusätzliche nicht spezifizierte Eigenschaften.

Es gibt noch mehr!

Die Liste ließe sich fortsetzen, aber ich möchte einige der Funktionen kurz aufzählen, da sie recht interessant zu betrachten sind. Ich werde hier nicht versuchen, alle Themen zu behandeln. Sie verdienen ihre eigenen Artikel.

  • Coroutines und reaktive Programmierung in Kotlin.
  • Ktor: Erstellen Sie asynchrone Client- und Serveranwendungen.
  • Kotlin Nativ: Erstellen Sie native ausführbare Dateien für Windows/Linux/macOS, anstatt JVMs zu verwenden.
  • Dokka: Eine API-Dokumentations-Engine für Kotlin.
  • Funktionen einbinden: Eliminieren Sie den Overhead beim Aufruf von Funktionen durch das Inlinen von Lambda-Ausdrücken und mehr!
  • ...

Fazit

Kotlin bietet eine Fülle leistungsstarker Funktionen, die Ihre Codierungserfahrung erheblich verbessern können, von der Bedeutung der Unveränderlichkeit und der Inline-Wertklassen bis hin zu den leistungsstarken Delegationsmechanismen und Scope-Funktionen.

Ich denke, wenn Sie diese Techniken anwenden, können Sie saubereren, effizienteren und besser wartbaren Code schreiben.
Scheuen Sie sich nicht, diese Tools kennenzulernen und in Ihre Projekte zu integrieren.
Sie werden überrascht sein, wie sehr sie sowohl Ihre Codequalität als auch Ihre Produktivität verbessern können.

Entdecken Sie weiter und viel Spaß beim Programmieren!


  1. NullPointerExceptio n

Verfasst von

Masood Masaeli

Masood Masaeli is a software consultant at Xebia Netherlands As a team member and consultant, he likes to promote professionalism together with understanding and kindness in teams and organizations.

Contact

Let’s discuss how we can support your journey.