Kotlin Code Review Checkliste: Wissenswertes über die Grundlagen

Wollen Sie und Ihr Team bessere Kotlin-Anwendungen entwickeln? Ein unabhängiges Code-Review verschafft Ihnen einen entscheidenden Vorteil. Code-Reviews helfen Ihnen, Fehler frühzeitig zu erkennen, die Leistung zu steigern und Anwendungen zu entwickeln, die von den Nutzern geliebt und positiv bewertet werden. Diese Checkliste deckt die Grundlagen der Entwicklung von Kotlin-Anwendungen ab und vereinfacht Kotlin-Code-Reviews.

Umfang und Zielsetzung

Die Vorbereitung auf die Überprüfung ist sehr wichtig. Das bedeutet, dass vor Beginn der Code-Analyse überprüft werden muss, ob alles vorhanden ist. Stellen Sie zunächst sicher, dass der Umfang und die Ziele klar definiert sind. Auf diese Weise können Sie sicherstellen, dass Ihre Überprüfung zielgerichtet und präzise ist.

Bewährte Praktiken:

  • Geben Sie die Überprüfungsziele klar an – konzentrieren Sie sich auf Sicherheit, Leistung, Lesbarkeit und mehr
  • Definieren Sie den Umfang genau – ein Modul, eine bestimmte Funktion oder die gesamte Anwendung
  • Legen Sie messbare Erfolgskriterien fest – Anzahl der behobenen Bugs, Leistungsmaßstäbe, Prozentsatz der Codeabdeckung usw.
  • Teilen Sie Ihrem Team den Umfang im Voraus mit

Einrichten der Testumgebung

Der nächste wichtige Schritt in der Vorprüfungsphase ist die Einrichtung Ihrer Testumgebung. Eine ordnungsgemäß konfigurierte Umgebung macht den künftigen Überprüfungsprozess reibungslos und effizient. Sie erspart es Ihnen auch, im Falle eines Fehlers alles neu zu machen.

Wichtigste Best Practices:

  • Prüfen Sie, ob eine IDE angemessen mit Kotlin und allen Abhängigkeiten konfiguriert ist
  • Überprüfen Sie, ob notwendige Plugins wie Detekt oder Gradle installiert sind und funktionieren
  • Richten Sie eine umfassende Testsuite ein, einschließlich Unit-Integrations- und End-to-End-Tests
  • Bestätigen Sie, dass die Testsuite reibungslos läuft und dass die Codebasis zum Testen bereit ist

Code-Dokumentation

Nun ist es an der Zeit für die nächste kritische Phase, nämlich zu prüfen, ob Ihr Kotlin-Code durch die Dokumentation klar für sich selbst spricht. Gut dokumentierter Code ist nicht nur eine Gefälligkeit – er ist eine Superkraft für effiziente Überprüfungen und die langfristige Gesundheit des Projekts.

Wichtige Best Practices:

  • Schreiben Sie aussagekräftige KDoc-Kommentare für Klassen, Funktionen und Eigenschaften
  • Achten Sie auf klare und prägnante Erklärungen komplexer Logik
  • Stellen Sie sicher, dass Beispiele für öffentliche APIs und Funktionen funktionieren
  • Überprüfen Sie, ob die Dokumentation mit dem Code auf dem neuesten Stand ist
  • Fördern Sie Tools zur Dokumentationserstellung wie Dokka
// Good practice of Code Documentation (KDoc)

/**
 * A class representing a user profile.
 *
 * It holds user's personal information and provides methods to manage their profile.
 *
 * @property userId Unique identifier for the user.
 * @property username The user's chosen username.
 */
class UserProfile(val userId: Int, val username: String) {

   /**
* Validates personal information.
*
* @param firstName The user's first name. Can be null.
* @param lastName The user's last name. Can be null.
* @return `true` if both the first name and last name are non-null and non-blank, `false` otherwise.
*/
fun validatePersonalInfo(firstName: String?, lastName: String?) =
!firstName.isNullOrBlank() && !lastName.isNullOrBlank()
}


// Bad practice of Code Documentation (KDoc)

/**
 * UserProfile class.
 */
class UserProfile(val userId: Int, val username: String) {

    /**
     * Function to get full name.
     */
    fun getFullName(firstName: String, lastName: String): String {
        return "$firstName $lastName"
    }
}

Aktuelle Abhängigkeiten

Die Prüfung auf aktuelle Abhängigkeiten zeigt, ob Ihr Projekt auf einem soliden Fundament steht. Die Verwendung der aktuellsten Bibliotheken und Tools ist nicht nur eine Auszeichnung für die neuesten Funktionen, sondern ein wichtiger Schritt für die Sicherheit und Stabilität auf lange Sicht.

Wichtige Best Practices:

  • Überprüfen Sie Aktualisierungen von Abhängigkeiten mit Gradle- oder Maven-Befehlen
  • Überprüfen Sie, ob alle Abhängigkeiten aus vertrauenswürdigen und seriösen Quellen stammen
  • Suchen Sie nach Abhängigkeiten mit bekannten Sicherheitslücken
  • Prüfen Sie, ob die Versionen der Abhängigkeiten im gesamten Projekt konsistent sind
  • Verwenden Sie Tools zur Verwaltung von Abhängigkeiten, um Aktualisierungen und Prüfungen auf Sicherheitslücken zu automatisieren

Werkzeuge zur statischen Analyse

Zögern Sie nicht, die automatisierten Augen der statischen Analysewerkzeuge in Ihre Kotlin-Code-Reviews einzubeziehen. Diese Tools fungieren als Ihre unermüdlichen Assistenten, die potenzielle Probleme frühzeitig und konsequent aufspüren und so den menschlichen Reviewern mehr Zeit für differenziertere Aspekte des Codes geben.

Wichtige Best Practices:

  • Statische Analysetools in die CI/CD-Pipeline Ihres Projekts integrieren
  • Verwenden Sie Linters wie Detekt und Ktlint zur Durchsetzung von Kotlin-Code
  • Konfigurieren Sie die Regeln für die statische Analyse so, dass sie den spezifischen Anforderungen und Codierungsstandards Ihres Projekts entsprechen
  • Überprüfung und Behebung von Entdeckungen aus statischen Analyseberichten
  • Einführen neuer statischer Analyseregeln für andere Entwickler

Testdaten und -szenarien

Gut durchgeführte Tests sind Ihr Sicherheitsnetz, und die Überprüfung dieser Tests ist genauso wichtig wie die Überprüfung des Codes selbst. Solide Testdaten und -szenarien gewährleisten die Zuverlässigkeit Ihres Kotlin-Codes, sowohl jetzt als auch in Zukunft.

  • Prüfen Sie die Testdaten auf Realismus und Randfälle
  • Prüfen Sie auf eine umfassende Abdeckung der Szenarien, einschließlich positiver und negativer Fälle
  • Stellen Sie sicher, dass die Tests unabhängig sind und nicht von externen Zuständen oder Abhängigkeiten abhängen
  • Achten Sie auf klare und beschreibende Testnamen, die das getestete Szenario erklären
  • Fördern Sie datengesteuerte Tests für Szenarien mit mehreren Eingabevarianten

Konsistente Benennung und Kommentare

Benennung, Formatierung und Kommentierung scheinen manchmal nebensächlich, aber sie sind die Grundlage für die Lesbarkeit und Wartbarkeit eines jeden Kotlin-Projekts. Sie ermöglichen reibungslosere Code-Reviews und eine effektivere Ausrichtung für alle Beteiligten.

Wichtige Best Practices:

  • Sicherstellen, dass der Code den Kotlin-Codierkonventionen und Style Guides entspricht
  • Prüfen Sie auf konsistente Namenskonventionen in der gesamten Codebasis
  • Stellen Sie sicher, dass Kommentare nicht offensichtliche Logik oder komplexe Abschnitte erklären
  • Suchen Sie nach Möglichkeiten, die Klarheit des Codes durch bessere Benennung oder Formatierung zu verbessern, anstatt nur Kommentare hinzuzufügen
  • Überprüfen Sie, ob die Kommentare korrekt und auf dem neuesten Stand des Codes sind
// Good practice of Naming, Formatting, and Comments

class OrderProcessor {

    fun calculateTotalPrice(orderItems: List, discountPercentage: Double): Double {
        val subtotal = orderItems.sumOf { it.price * it.quantity }
        return subtotal * (1 - (discountPercentage / 100))
    }
}

data class OrderItem(
    val productId: Int,
    val productName: String,
    val quantity: Int,
    val price: Double
)


// Bad practice of Naming, Formatting, and Comments

class order_processor {

    fun calc_price(items: List, disc: Double): Double {
        // calculate sum
        val s = items.sumOf { it.price * it.quantity }

        // discount
        val discount = s * (disc / 100)
        val finalPrice = s - discount

        return finalPrice
    }
}

data class OrderItem(
    val productID: Int,
    val item_name: String,
    val qty: Int,
    val itemPrice: Double
)

Organisation von Importen

Wenn Sie den Code einfach halten und die Importe organisieren, beschleunigen Sie die Überprüfungen, verringern die Wahrscheinlichkeit, dass sich Fehler einschleichen, und machen die Codebasis für alle verständlich. Code, der leicht zu verstehen und zu navigieren ist, lässt sich leichter überprüfen und pflegen. Dies schafft die Voraussetzungen für ein gesünderes und robusteres Projekt.

Wichtige Best Practices:

  • Komplexe Funktionen aufgliedern
  • Reduzieren Sie Verschachtelungen, um die Navigierbarkeit des Codes zu verbessern
  • Organisieren Sie Importe, um Abhängigkeiten zu verdeutlichen
  • Entfernen Sie ungenutzte Importe, um Unordnung zu vermeiden
  • Vermeiden Sie Wildcard-Importe
// Good practice of Import Organization

import com.example.model.User
import com.example.repository.UserRepository
import com.example.service.UserService
import com.example.view.UserView

class UserProcessor {

    private val userRepository = UserRepository()
    private val userService = UserService(userRepository)

    fun displayUserDetails(userId: Int) {
        val user = userService.getUserById(userId) ?: return // Ранній вихід, якщо користувача не знайдено

        val formattedName = user.username.capitalize() // Використання вбудованої функції Kotlin
        val userView = createUserView(user, formattedName)
        userView.display()
    }

    private fun createUserView(user: User, formattedName: String): UserView {
        return UserView(
            name = formattedName,
            email = user.email.orEmpty() // Безпечна обробка null
        )
    }
}


// Bad practice of Import Organization 

import com.example.utils.*
import com.example.view.UserView
import com.example.service.UserService
import com.example.model.User
import com.example.repository.UserRepository

class UserProcessor {

    private val userRepository = UserRepository()
    private val userService = UserService(userRepository)

    fun displayUserDetails(userId: Int) {
        val user = userService.getUserById(userId)
        if (user != null) {
            val name = user.username
            val formattedName = StringUtils.capitalize(name) // Utility function somewhere
            val email = if (user.email != null) user.email else "" // Null check inline

            val view = UserView(formattedName, email)
            view.display()

        }
    }
}

Logische Gruppierung und Funktionslänge

Gut organisierter Code ist grundsätzlich einfacher zu verstehen, zu testen und weiterzuentwickeln. Indem wir Funktionen logisch gruppieren und sie kurz halten, schaffen wir eine Codebasis, die von Prüfern schnell erfasst werden kann. Änderungen sind weniger fehleranfällig, und das gesamte Projekt ist leichter zu warten.

Wichtige Best Practices:

  • Gruppieren Sie zusammengehörigen Code in logische Einheiten wie Klassen oder Pakete
  • Halten Sie sich an das Prinzip der einzigen Verantwortung (SRP)
  • Begrenzung der Funktionslänge
  • Verwerfen Sie übermäßig lange Funktionen oder schlecht gruppierten Code

Richtige Nutzung von Funktionen

Das Schreiben von gutem Kotlin-Code ist mehr als nur funktionaler Code. Es geht darum, die Werkzeuge von Kotlin zu nutzen, um Anwendungen stabiler, lesbarer und schneller zu machen. Wenn Kotlin-Features richtig eingesetzt werden, laufen Code-Reviews viel reibungsloser ab. Reviewer können schnell das Wesentliche verstehen – wie die Anwendung funktioniert und eingebaut ist – anstatt sich in kompliziertem oder ungewöhnlichem Code zu verlieren.

Wichtige Best Practices:

  • Nutzen Sie die Nullsicherheitsfunktionen von Kotlin (z.B. ?, !!, let, run, also, apply), um NullPointerExceptions zu minimieren und Code-Reviews zu vereinfachen
  • Verwendung von Kotlin-Datenklassen für datenzentrierte Entitäten
  • Verwendung von Kotlin-Erweiterungsfunktionen, Funktionen höherer Ordnung und Lambdas
  • Prüfen Sie die idiomatische Verwendung von Kotlin und vermeiden Sie wortreiche Muster im Java-Stil
// Good practice of Feature Utilization (Idiomatic Kotlin)

import java.util.Locale

data class Address(val street: String?, val city: String, val zipCode: String?)

fun formatAddress(address: Address?) = address?.run {
    """${street?.capitalizeFirst() ?: "Unknown Street"}, ${city.uppercase()}, ${zipCode ?: "N/A"}"""
} ?: "No Address Provided"

fun String.capitalizeFirst(): String =
    replaceFirstChar { if (it.isLowerCase()) it.titlecase(Locale.getDefault()) else it.toString() }

fun main() {
    val validAddress = Address("Main Street", "Kyiv", "01001")
    val partialAddress = Address(null, "Lviv", null)
    val noAddress: Address? = null

    println(formatAddress(validAddress))
    println(formatAddress(partialAddress))
    println(formatAddress(noAddress))
}

// Bad practice of Feature Utilization (More Java-style/Verbose)

class Address {
    var street: String? = null
    var city: String = ""
    var zipCode: String? = null

    constructor(street: String?, city: String, zipCode: String?) {
        this.street = street
        this.city = city
        this.zipCode = zipCode
    }
}

fun getFormattedAddress(address: Address?): String {
    if (address != null) {
        var streetName = address.street
        if (streetName == null) {
            streetName = "Unknown Street"
        } else {
            streetName = streetName.capitalize()
        }

        var zip = address.zipCode
        if (zip == null) {
            zip = "N/A"
        }

        return streetName + ", " + address.city.toUpperCase() + ", " + zip
    } else {
        return "No Address Provided"
    }
}

fun main() {
    val validAddress = Address("Main Street", "Kyiv", "01001")
    val partialAddress = Address(null, "Lviv", null)
    val noAddress: Address? = null

    println(getFormattedAddress(validAddress))
    println(getFormattedAddress(partialAddress))
    println(getFormattedAddress(noAddress))
}

Sicherer Umgang mit Nullen

Null Pointer Exceptions sind der Fluch vieler Entwickler, und Kotlin bietet mächtige Werkzeuge, um sie zu vermeiden. Indem wir genau prüfen, wie Nullen in Kotlin-Code gehandhabt werden, reduzieren wir das Risiko von Abstürzen, verbessern die Stabilität der Anwendung und machen die Codebasis einfacher zu warten.

Wichtige Best Practices:

  • Standardmäßig Nicht-Null-Typen (?) verwenden
  • Verwendung von nullbaren Typen (?), wenn null gültig ist und erwartet wird
  • Fördern Sie den sicheren Aufruf (?.) und den Elvis-Operator (?:) für eine prägnante und sichere Null-Behandlung
  • Verwenden Sie den not-null-Assertion-Operator (!!) nur, wenn es unvermeidbar ist.
  • Testen Sie Null-Behandlungsszenarien, insbesondere für nullbare Typen und externe Dateninteraktionen

Umgang mit Ausnahmen

Kein Code ist perfekt, und es werden unweigerlich Dinge schief gehen. Wichtig ist, dass Ihr Code weiß, wie er auf solche Unfälle reagieren soll, und dass er klar sagen kann, was schief gelaufen ist. Die Überprüfung einer guten Fehlerbehandlung im Rahmen von Code-Reviews stellt sicher, dass die Anwendungen stabil sind und nicht so leicht kaputt gehen. Außerdem sind klare Fehlermeldungen für Entwickler ein hilfreicher Leitfaden, um Probleme schnell zu finden und zu beheben.

Wichtige Best Practices:

  • Verwenden Sie Kotlin try-catch Blöcke, um mögliche Ausnahmen zu behandeln und Abstürze zu verhindern
  • Generieren Sie spezifische und benutzerdefinierte Ausnahmetypen, um einen umfassenderen Fehlerkontext bereitzustellen
  • Fügen Sie informative Fehlermeldungen in Ausnahmen ein
  • Keine generische Exception abfangen, wenn es nicht notwendig ist
  • Praktiken zur Fehlerprotokollierung überprüfen
// Good practice of Exception Management

import java.io.File
import java.io.IOException

class FileProcessor {

    fun readFileContent(filePath: String): String = try {
        File(filePath).readText()
    } catch (e: IOException) {
        throw FileProcessingException("Error reading file at path: $filePath", e)
    } catch (e: SecurityException) {
        throw FileProcessingException("Insufficient permissions to read file: $filePath", e)
    }
}

class FileProcessingException(message: String, cause: Throwable? = null) : Exception(message, cause)

fun main() {
    val processor = FileProcessor()
    val filePath = "data.txt"
    try {
        val content = processor.readFileContent(filePath)
        println("File content:\n$content")
    } catch (e: FileProcessingException) {
        println("Error processing file: ${e.message}")
        e.cause?.message?.let { println("Caused by: $it") } // Simplified cause message printing
    }
}

// Bad practice of Exception Management

import java.io.File
import java.io.IOException

class FileProcessor {

    fun readFileContent(filePath: String): String {
        try {
            return File(filePath).readText()
        } catch (e: Exception) { // Catching generic Exception
            throw Exception("File error") // Generic error message
        }
    }
}


fun main() {
    val processor = FileProcessor()
    val filePath = "data.txt"
    try {
        val content = processor.readFileContent(filePath)
        println("File content:\n$content")
    } catch (e: Exception) {
        println("Error: Something went wrong") // Uninformative error message
    }
}

Effiziente Sammlungen

Die Überprüfung des Codes ist wichtig, um Anwendungen schneller und leichter zu machen. Sie können sicherstellen, dass nicht zu viele Dinge erstellt werden und dass Sie die besten Möglichkeiten zum Speichern von Daten verwenden. Solche Anwendungen sind schneller und reaktionsschneller und verbrauchen weniger Energie. Wenn Sie bei der Codeüberprüfung auf diese Details achten, macht das für die Nutzer einer App einen großen Unterschied.

Wichtige Best Practices:

  • Minimieren Sie die Erstellung von Objekten, insbesondere in Schleifen oder häufig aufgerufenen Funktionen
  • Verwenden Sie effiziente Kotlin-Sammlungen wie List, Set und Map in geeigneter Weise
  • Verwenden Sie unveränderliche Sammlungen wo möglich
  • Verwenden Sie Sammeloperationen wie map, filter, reduce und fold
  • Überprüfen Sie den Code auf potenziellen Boxing/Unboxing-Overhead bei der Verwendung von primitiven Typen und Collections, insbesondere in performance-kritischen Abschnitten
// Good practice of Object Creation and Collection Usage

object StringCache { // Using object declaration for singleton
    private val cache = hashSetOf() // Efficient HashSet for lookups

    fun isCached(text: String) = cache.contains(text)

    fun addToCache(text: String) {
        cache.add(text) // HashSet add() already handles duplicates efficiently
    }
}

fun List.processUniqueToUpperCase() = this
    .filterNot { StringCache.isCached(it) } // Using filterNot for better readability
    .map { it.uppercase() }

fun main() {
    StringCache.addToCache("apple")
    StringCache.addToCache("banana")

    val inputList = listOf("apple", "orange", "banana", "grape")
    val uniqueUpperCaseStrings = inputList.processUniqueToUpperCase() // Using the extension function
    println("Unique Uppercase Strings: $uniqueUpperCaseStrings") // Output: [ORANGE, GRAPE] - Corrected output
}

// Bad practice of Object Creation and Collection Usage

class StringCache { // Class, not object - new instance created every time
    private val cache = ArrayList() // Inefficient ArrayList for lookups

    fun isCached(text: String): Boolean {
        for (item in cache) { // Manual loop for checking existence - slow in ArrayList
            if (item == text) {
                return true
            }
        }
        return false
    }

    fun addToCache(text: String) {
        if (!isCached(text)) {
            cache.add(text)
        }
    }
}

fun processStrings(stringList: List): List {
    val resultList = ArrayList() // Creating a mutable list
    for (str in stringList) { // Manual loop for filtering and transforming
        if (!StringCache().isCached(str)) { // New StringCache instance created unnecessarily!
            resultList.add(str.toUpperCase())
        }
    }
    return resultList
}

fun main() {
    val cache = StringCache() // Instance creation
    cache.addToCache("apple")
    cache.addToCache("banana")

    val inputList = listOf("apple", "orange", "banana", "grape")
    val uniqueUpperCaseStrings = processStrings(inputList)
    println("Unique Uppercase Strings: $uniqueUpperCaseStrings") // Output: [ORANGE, GRAPE]
}

Ihre Kotlin-Entwicklung & Audit-Dienstleistungen

Die Chance unseres modernen mobilen Zeitalters besteht darin, dass ein globales Publikum von 5 Milliarden Smartphone-Nutzern auf Ihre App wartet, um ihre Aufgaben zu lösen! Nutzen Sie diesen Segen, indem Sie die 20-jährige Erfahrung von Redwerk in der Entwicklung mobiler Apps nutzen. Wir setzen Ihre Vision um und erstellen eine erstklassige native Android-App, eine einwandfreie native iOS-App oder sogar eine voll funktionsfähige hybride Cross-Plattform-App.

Vergessen Sie Projektstress und verbrannte Fristen. Die erfahrenen Entwickler, Designer, QA-Ingenieure und Projektmanager von Redwerk freuen sich über Herausforderungen und verwandeln sie in Erfolge. Wir helfen Ihnen dabei, jedes Unternehmensziel auf Ihrem Weg zum Erfolg zu erreichen – werfen Sie einen Blick darauf:

  • Von der Idee zur Lösung. Wir entwickeln kundenspezifische Softwarelösungen von Grund auf. Sehen Sie, wie wir ein leistungsfähiges und intuitives Test-Toolkit für Android (Kotlin) entwickelt haben, das von QA-Ingenieuren, Entwicklern, Produktspezialisten und Kreativen gleichermaßen effektiv genutzt werden kann.
  • Legacy-Code-Modernisierung. Beim Refactoring geht es nicht nur darum, aufzuräumen – es geht darum, Ihrem bestehenden System neues Leben einzuhauchen. Unsere Code-Refactoring-Spezialisten bringen moderne Best Practices in die DNA Ihres Projekts ein. Mit unserer Erfahrung in der Entwicklung von über 20 Android-Apps in 7 Ländern wissen wir, was nötig ist, um die Leistung und Verwaltung Ihres Systems zu verbessern.
  • Wir steigern die Attraktivität Ihres Produkts. Unsere Entwickler fügen nicht einfach nur Funktionen hinzu – sie entwickeln sie im Kontext Ihrer Ziele, damit Ihre Lösung mühelos wachsen und sich anpassen kann. Lesen Sie, wie wir für My Bike Valet ein Fahrradparksystem entwickelt haben, das mit einer Cloud-App, einer nativen Android-App und einer iOS-App verbunden ist. Wir haben die MVP-Architektur entworfen, die eine solide Grundlage für eine unkomplizierte Erweiterung bildete.
  • Nativ oder plattformübergreifend? Entschlüsseln Sie die ideale mobile Strategie. Bei Redwerk beherrschen wir sowohl die native als auch die plattformübergreifende Entwicklung. Native Software ist die beste Wahl, wenn es um Spitzenleistung und innovative Funktionen geht, während die plattformübergreifende Entwicklung günstigere Preise und eine größere Reichweite bietet. Stehen Sie bei dieser Entscheidung nicht alleine da – die Experten von Redwerk helfen Ihnen, den richtigen Weg zu finden.

Ganz gleich, ob Sie vor einer kritischen Code-Überprüfung stehen, eine fachkundige Kotlin-Anleitung benötigen, eine vollständige Entwicklung wünschen oder irgendetwas dazwischen – die Kotlin-Experten von Redwerk sind für Sie da. Wir sind gespannt auf Ihre Vision und darauf, wie wir Ihnen helfen können. Nehmen Sie Kontakt mit unserem Team auf, wann immer Sie bereit sind.