Communitygithub.com

kotlin-patterns

Idiomatische Kotlin-Muster, Best Practices und Konventionen zum Erstellen robuster, effizienter und wartbarer Kotlin-Anwendungen mit Coroutinen, Null-Sicherheit und DSL-Buildern. Verwenden Sie dies beim Schreiben oder Überprüfen von Kotlin-Code, wenn idiomatische Struktur oder Null-Sicherheit in Frage stehen.

Was ist kotlin-patterns?

Dieses Skill beschreibt idiomatische Kotlin-Entwicklungsmuster und Best Practices für den Bau robuster, effizienter und wartbarer Anwendungen. Es behandelt sieben Kernbereiche: Null-Sicherheit durch das Typsystem und sichere Aufrufoperatoren, Unveränderlichkeit mit `val` und `copy()` bei Datenklassen, versiegelte Klassen und Schnittstellen für erschöpfende Typhierarchien, strukturierte Nebenläufigkeit mit Coroutinen und `Flow`, Erweiterungsfunktionen für Verhalten ohne Vererbung, typsichere DSL-Builder mit `@DslMarker` und Lambda-Empfängern sowie Gradle Kotlin DSL für die Build-Konfiguration. Es ist nützlich, wenn Sie neuen Kotlin-Code schreiben, bestehenden Code überprüfen oder umgestalten, Kotlin-Module oder -Bibliotheken entwerfen oder Gradle-Kotlin-DSL-Builds konfigurieren. Das Skill enthält ausführbare Codebeispiele und Anti-Patterns, um gängige Fehler wie das Verschachteln von Bereichsfunktionen, die Verwendung von `!!` oder veränderlichen globalen Zuständen zu vermeiden.

Funktioniert mit~Claude Code~Codex CLI~Cursor
npx skills add https://github.com/affaan-m/ECC/tree/main/skills/kotlin-patterns

In Ihrer bevorzugten KI fragen

Öffnet einen neuen Chat, in dem dieser Agent-Skill bereits geladen ist.

Dokumentation

Kotlin-Entwicklungsmuster

Idiomatische Kotlin-Muster und Best Practices für den Bau robuster, effizienter und wartbarer Anwendungen.

Wann zu verwenden

  • Schreiben von neuem Kotlin-Code
  • Überprüfen von Kotlin-Code
  • Refactoring von vorhandenem Kotlin-Code
  • Entwerfen von Kotlin-Modulen oder -Bibliotheken
  • Konfigurieren von Gradle-Kotlin-DSL-Builds

So funktioniert es

Dieses Skill setzt idiomatische Kotlin-Konventionen in sieben Schlüsselbereichen durch: Null-Sicherheit unter Verwendung des Typsystems und sicherer Aufrufoperatoren, Unveränderlichkeit mittels val und copy() bei Datenklassen, versiegelte Klassen und Schnittstellen für erschöpfende Typhierarchien, strukturierte Nebenläufigkeit mit Coroutinen und Flow, Erweiterungsfunktionen zum Hinzufügen von Verhalten ohne Vererbung, typsichere DSL-Builder unter Verwendung von @DslMarker und Lambda-Empfängern sowie Gradle Kotlin DSL für die Build-Konfiguration.

Beispiele

Null-Sicherheit mit Elvis-Operator:

fun getUserEmail(userId: String): String {
    val user = userRepository.findById(userId)
    return user?.email ?: "[email protected]"
}

Versiegelte Klasse für erschöpfende Ergebnisse:

sealed class Result<out T> {
    data class Success<T>(val data: T) : Result<T>()
    data class Failure(val error: AppError) : Result<Nothing>()
    data object Loading : Result<Nothing>()
}

Strukturierte Nebenläufigkeit mit async/await:

suspend fun fetchUserWithPosts(userId: String): UserProfile =
    coroutineScope {
        val user = async { userService.getUser(userId) }
        val posts = async { postService.getUserPosts(userId) }
        UserProfile(user = user.await(), posts = posts.await())
    }

Kernprinzipien

1. Null-Sicherheit

Das Typsystem von Kotlin unterscheidet zwischen nullable- und non-nullable-Typen. Nutzen Sie es voll aus.

// Gut: Standardmäßig non-nullable-Typen verwenden
fun getUser(id: String): User {
    return userRepository.findById(id)
        ?: throw UserNotFoundException("Benutzer $id nicht gefunden")
}

// Gut: Sichere Aufrufe und Elvis-Operator
fun getUserEmail(userId: String): String {
    val user = userRepository.findById(userId)
    return user?.email ?: "[email protected]"
}

// Schlecht: Forciertes Entpacken von nullable-Typen
fun getUserEmail(userId: String): String {
    val user = userRepository.findById(userId)
    return user!!.email // Wirft NPE wenn null
}

2. Unveränderlichkeit als Standard

Bevorzugen Sie val gegenüber var, unveränderliche Sammlungen gegenüber veränderlichen.

// Gut: Unveränderliche Daten
data class User(
    val id: String,
    val name: String,
    val email: String,
)

// Gut: Transformieren mit copy()
fun updateEmail(user: User, newEmail: String): User =
    user.copy(email = newEmail)

// Gut: Unveränderliche Sammlungen
val users: List<User> = listOf(user1, user2)
val filtered = users.filter { it.email.isNotBlank() }

// Schlecht: Veränderlicher Zustand
var currentUser: User? = null // Veränderlichen globalen Zustand vermeiden
val mutableUsers = mutableListOf<User>() // Nur wenn wirklich nötig verwenden

3. Ausdruckskörper und Einzelausdruck-Funktionen

Verwenden Sie Ausdruckskörper für prägnante, lesbare Funktionen.

// Gut: Ausdruckskörper
fun isAdult(age: Int): Boolean = age >= 18

fun formatFullName(first: String, last: String): String =
    "$first $last".trim()

fun User.displayName(): String =
    name.ifBlank { email.substringBefore('@') }

// Gut: When als Ausdruck
fun statusMessage(code: Int): String = when (code) {
    200 -> "OK"
    404 -> "Nicht gefunden"
    500 -> "Interner Serverfehler"
    else -> "Unbekannter Status: $code"
}

// Schlecht: Unnötiger Blockkörper
fun isAdult(age: Int): Boolean {
    return age >= 18
}

4. Datenklassen für Wertobjekte

Verwenden Sie Datenklassen für Typen, die hauptsächlich Daten halten.

// Gut: Datenklasse mit copy, equals, hashCode, toString
data class CreateUserRequest(
    val name: String,
    val email: String,
    val role: Role = Role.USER,
)

// Gut: Wertklasse für Typsicherheit (keine Laufzeit-Overhead)
@JvmInline
value class UserId(val value: String) {
    init {
        require(value.isNotBlank()) { "UserId darf nicht leer sein" }
    }
}

@JvmInline
value class Email(val value: String) {
    init {
        require('@' in value) { "Ungültige E-Mail: $value" }
    }
}

fun getUser(id: UserId): User = userRepository.findById(id)

Versiegelte Klassen und Schnittstellen

Modellierung eingeschränkter Hierarchien

// Gut: Versiegelte Klasse für erschöpfendes when
sealed class Result<out T> {
    data class Success<T>(val data: T) : Result<T>()
    data class Failure(val error: AppError) : Result<Nothing>()
    data object Loading : Result<Nothing>()
}

fun <T> Result<T>.getOrNull(): T? = when (this) {
    is Result.Success -> data
    is Result.Failure -> null
    is Result.Loading -> null
}

fun <T> Result<T>.getOrThrow(): T = when (this) {
    is Result.Success -> data
    is Result.Failure -> throw error.toException()
    is Result.Loading -> throw IllegalStateException("Wird noch geladen")
}

Versiegelte Schnittstellen für API-Antworten

sealed interface ApiError {
    val message: String

    data class NotFound(override val message: String) : ApiError
    data class Unauthorized(override val message: String) : ApiError
    data class Validation(
        override val message: String,
        val field: String,
    ) : ApiError
    data class Internal(
        override val message: String,
        val cause: Throwable? = null,
    ) : ApiError
}

fun ApiError.toStatusCode(): Int = when (this) {
    is ApiError.NotFound -> 404
    is ApiError.Unauthorized -> 401
    is ApiError.Validation -> 422
    is ApiError.Internal -> 500
}

Bereichsfunktionen

Wann welche verwenden

// let: Nullable oder Bereichsergebnis transformieren
val length: Int? = name?.let { it.trim().length }

// apply: Ein Objekt konfigurieren (gibt das Objekt zurück)
val user = User().apply {
    name = "Alice"
    email = "[email protected]"
}

// also: Seiteneffekte (gibt das Objekt zurück)
val user = createUser(request).also { logger.info("Benutzer erstellt: ${it.id}") }

// run: Einen Block mit Empfänger ausführen (gibt Ergebnis zurück)
val result = connection.run {
    prepareStatement(sql)
    executeQuery()
}

// with: Nicht-Extension-Form von run
val csv = with(StringBuilder()) {
    appendLine("name,email")
    users.forEach { appendLine("${it.name},${it.email}") }
    toString()
}

Anti-Patterns

// Schlecht: Verschachteln von Bereichsfunktionen
user?.let { u ->
    u.address?.let { addr ->
        addr.city?.let { city ->
            println(city) // Schwer zu lesen
        }
    }
}

// Gut: Stattdessen sichere Aufrufe verketten
val city = user?.address?.city
city?.let { println(it) }

Erweiterungsfunktionen

Funktionalität ohne Vererbung hinzufügen

// Gut: Domänenspezifische Erweiterungen
fun String.toSlug(): String =
    lowercase()
        .replace(Regex("[^a-z0-9\\s-]"), "")
        .replace(Regex("\\s+"), "-")
        .trim('-')

fun Instant.toLocalDate(zone: ZoneId = ZoneId.systemDefault()): LocalDate =
    atZone(zone).toLocalDate()

// Gut: Sammlungserweiterungen
fun <T> List<T>.second(): T = this[1]

fun <T> List<T>.secondOrNull(): T? = getOrNull(1)

// Gut: Bereichsbegrenzte Erweiterungen (verschmutzen nicht den globalen Namensraum)
class UserService {
    private fun User.isActive(): Boolean =
        status == Status.ACTIVE && lastLogin.isAfter(Instant.now().minus(30, ChronoUnit.DAYS))

    fun getActiveUsers(): List<User> = userRepository.findAll().filter { it.isActive() }
}

Coroutinen

Strukturierte Nebenläufigkeit

// Gut: Strukturierte Nebenläufigkeit mit coroutineScope
suspend fun fetchUserWithPosts(userId: String): UserProfile =
    coroutineScope {
        val userDeferred = async { userService.getUser(userId) }
        val postsDeferred = async { postService.getUserPosts(userId) }

        UserProfile(
            user = userDeferred.await(),
            posts = postsDeferred.await(),
        )
    }

// Gut: supervisorScope, wenn Kinder unabhängig fehlschlagen können
suspend fun fetchDashboard(userId: String): Dashboard =
    supervisorScope {
        val user = async { userService.getUser(userId) }
        val notifications = async { notificationService.getRecent(userId) }
        val recommendations = async { recommendationService.getFor(userId) }

        Dashboard(
            user = user.await(),
            notifications = try {
                notifications.await()
            } catch (e: CancellationException) {
                throw e
            } catch (e: Exception) {
                emptyList()
            },
            recommendations = try {
                recommendations.await()
            } catch (e: CancellationException) {
                throw e
            } catch (e: Exception) {
                emptyList()
            },
        )
    }

Flow für reaktive Streams

// Gut: Kalter Flow mit korrekter Fehlerbehandlung
fun observeUsers(): Flow<List<User>> = flow {
    while (currentCoroutineContext().isActive) {
        val users = userRepository.findAll()
        emit(users)
        delay(5.seconds)
    }
}.catch { e ->
    logger.error("Fehler beim Beobachten von Benutzern", e)
    emit(emptyList())
}

// Gut: Flow-Operatoren
fun searchUsers(query: Flow<String>): Flow<List<User>> =
    query
        .debounce(300.milliseconds)
        .distinctUntilChanged()
        .filter { it.length >= 2 }
        .mapLatest { q -> userRepository.search(q) }
        .catch { emit(emptyList()) }

Abbruch und Bereinigung

// Gut: Abbruch beachten
suspend fun processItems(items: List<Item>) {
    items.forEach { item ->
        ensureActive() // Vor teurer Arbeit Abbruch prüfen
        processItem(item)
    }
}

// Gut: Bereinigung mit try/finally
suspend fun acquireAndProcess() {
    val resource = acquireResource()
    try {
        resource.process()
    } finally {
        withContext(NonCancellable) {
            resource.release() // Immer freigeben, auch bei Abbruch
        }
    }
}

Delegation

Eigenschaftsdelegation

// Faule Initialisierung
val expensiveData: List<User> by lazy {
    userRepository.findAll()
}

// Beobachtbare Eigenschaft
var name: String by Delegates.observable("initial") { _, old, new ->
    logger.info("Name geändert von '$old' zu '$new'")
}

// Map-gestützte Eigenschaften
class Config(private val map: Map<String, Any?>) {
    val host: String by map
    val port: Int by map
    val debug: Boolean by map
}

val config = Config(mapOf("host" to "localhost", "port" to 8080, "debug" to true))

Schnittstellendelegation

// Gut: Schnittstellenimplementierung delegieren
class LoggingUserRepository(
    private val delegate: UserRepository,
    private val logger: Logger,
) : UserRepository by delegate {
    // Nur das überschreiben, was zum Hinzufügen von Logging benötigt wird
    override suspend fun findById(id: String): User? {
        logger.info("Suche Benutzer nach ID: $id")
        return delegate.findById(id).also {
            logger.info("Benutzer gefunden: ${it?.name ?: "null"}")
        }
    }
}

DSL-Builder

Typsichere Builder

// Gut: DSL mit @DslMarker
@DslMarker
annotation class HtmlDsl

@HtmlDsl
class HTML {
    private val children = mutableListOf<Element>()

    fun head(init: Head.() -> Unit) {
        children += Head().apply(init)
    }

    fun body(init: Body.() -> Unit) {
        children += Body().apply(init)
    }

    override fun toString(): String = children.joinToString("\n")
}

fun html(init: HTML.() -> Unit): HTML = HTML().apply(init)

// Verwendung
val page = html {
    head { title("Meine Seite") }
    body {
        h1("Willkommen")
        p("Hallo, Welt!")
    }
}

Konfigurations-DSL

data class ServerConfig(
    val host: String = "0.0.0.0",
    val port: Int = 8080,
    val ssl: SslConfig? = null,
    val database: DatabaseConfig? = null,
)

data class SslConfig(val certPath: String, val keyPath: String)
data class DatabaseConfig(val url: String, val maxPoolSize: Int = 10)

class ServerConfigBuilder {
    var host: String = "0.0.0.0"
    var port: Int = 8080
    private var ssl: SslConfig? = null
    private var database: DatabaseConfig? = null

    fun ssl(certPath: String, keyPath: String) {
        ssl = SslConfig(certPath, keyPath)
    }

    fun database(url: String, maxPoolSize: Int = 10) {
        database = DatabaseConfig(url, maxPoolSize)
    }

    fun build(): ServerConfig = ServerConfig(host, port, ssl, database)
}

fun serverConfig(init: ServerConfigBuilder.() -> Unit): ServerConfig =
    ServerConfigBuilder().apply(init).build()

// Verwendung
val config = serverConfig {
    host = "0.0.0.0"
    port = 443
    ssl("/certs/cert.pem", "/certs/key.pem")
    database("jdbc:postgresql://localhost:5432/mydb", maxPoolSize = 20)
}

Sequenzen für faule Auswertung

// Gut: Sequenzen für große Sammlungen mit mehreren Operationen verwenden
val result = users.asSequence()
    .filter { it.isActive }
    .map { it.email }
    .filter { it.endsWith("@company.com") }
    .take(10)
    .toList()

// Gut: Unendliche Sequenzen erzeugen
val fibonacci: Sequence<Long> = sequence {
    var a = 0L
    var b = 1L
    while (true) {
        yield(a)
        val next = a + b
        a = b
        b = next
    }
}

val first20 = fibonacci.take(20).toList()

Gradle Kotlin DSL

build.gradle.kts-Konfiguration

// Auf neueste Versionen prüfen: https://kotlinlang.org/docs/releases.html
plugins {
    kotlin("jvm") version "2.3.10"
    kotlin("plugin.serialization") version "2.3.10"
    id("io.ktor.plugin") version "3.4.0"
    id("org.jetbrains.kotlinx.kover") version "0.9.7"
    id("io.gitlab.arturbosch.detekt") version "1.23.8"
}

group = "com.example"
version = "1.0.0"

kotlin {
    jvmToolchain(21)
}

dependencies {
    // Ktor
    implementation("io.ktor:ktor-server-core:3.4.0")
    implementation("io.ktor:ktor-server-netty:3.4.0")
    implementation("io.ktor:ktor-server-content-negotiation:3.4.0")
    implementation("io.ktor:ktor-serialization-kotlinx-json:3.4.0")

    // Exposed
    implementation("org.jetbrains.exposed:exposed-core:1.

Individual skills in this repo

This repo contains 20 individual skills — each has its own dedicated page.

accessibility

Design, implement, and audit inclusive digital products using WCAG 2.2 Level AA. Use when building or auditing UI that must meet WCAG 2.2 Level AA, or when reviewing a change for keyboard, contrast, or screen-reader support.

affaan-m/content-engine

Create platform-native content systems for X, LinkedIn, TikTok, YouTube, newsletters, and repurposed multi-platform campaigns. Use when the user wants social posts, threads, scripts, content calendars, or one source asset adapted cleanly across platforms.

affaan-m/ECC

Multi-platform content distribution across X, LinkedIn, Threads, and Bluesky. Adapts content per platform using content-engine patterns. Never posts identical content cross-platform. Use when the user wants to distribute content across social platforms.

affaan-m/fal-ai-media

Unified media generation via fal.ai MCP — image, video, and audio. Covers text-to-image (Nano Banana), text/image-to-video (Seedance, Kling, Veo 3), text-to-speech (CSM-1B), and video-to-audio (ThinkSound). Use when the user wants to generate images, videos, or audio with AI.

affaan-m/manim-video

日本語翻訳:このファイルは manim-video 用の日本語翻訳が必要です

affaan-m/remotion-video-creation

Remotion のベストプラクティス - React で動画を作成する。3D、アニメーション、音声、字幕、チャート、トランジションなどをカバーするドメイン固有の29のルール。

affaan-m/video-editing

AI-assisted video editing workflows for cutting, structuring, and augmenting real footage. Covers the full pipeline from raw capture through FFmpeg, Remotion, ElevenLabs, fal.ai, and final polish in Descript or CapCut. Use when the user wants to edit video, cut footage, create vlogs, or build video content.

agent-architecture-audit

Full-stack diagnostic for agent and LLM applications. Audits the 12-layer agent stack for wrapper regression, memory pollution, tool discipline failures, hidden repair loops, and rendering corruption. Produces severity-ranked findings with code-first fixes. Essential for developers building agent applications, autonomous loops, or any LLM-powered feature. Use when an agent or LLM feature misbehaves and the failing layer is unknown, or before shipping an agent stack.

agent-eval

Head-to-head comparison of coding agents (Claude Code, Aider, Codex, etc.) on custom tasks with pass rate, cost, time, and consistency metrics. Use when choosing between coding agents, or when a change to an agent setup needs measured pass rate, cost, and time rather than an impression.

agent-harness-construction

Design and optimize AI agent action spaces, tool definitions, and observation formatting for higher completion rates. Use when defining or revising an agent

agentic-engineering

Operate as an agentic engineer using eval-first execution, decomposition, and cost-aware model routing. Use when planning or executing engineering work that agents will carry out end to end.

agentic-os

Build persistent multi-agent operating systems on Claude Code. Covers kernel architecture, specialist agents, slash commands, file-based memory, scheduled automation, and state management without external databases. Use when building a persistent multi-agent system on Claude Code with its own memory, commands, and scheduling.

agent-introspection-debugging

Structured self-debugging workflow for AI agent failures using capture, diagnosis, contained recovery, and introspection reports. Use when an agent run fails and you need a reproducible diagnosis instead of a retry.

agent-payment-x402

Add x402 payment execution to AI agents with per-task budgets, spending controls, and non-custodial wallets. Supports Base through agentwallet-sdk and X Layer through OKX Payments / OKX Agent Payments Protocol. Use when an agent must pay for something itself and needs per-task budgets, spending controls, and a non-custodial wallet.

agent-self-evaluation

Use after completing any non-trivial task. The agent self-rates its output on 5 axes — accuracy, completeness, clarity, actionability, conciseness — with concrete evidence per criterion. Produces a structured 1-5 scorecard with specific improvement suggestions.

agent-sort

Build an evidence-backed ECC install plan for a specific repo by sorting skills, commands, rules, hooks, and extras into DAILY vs LIBRARY buckets using parallel repo-aware review passes. Use when ECC should be trimmed to what a project actually needs instead of loading the full bundle.

ai-first-engineering

Engineering operating model for teams where AI agents generate a large share of implementation output. Use when setting team process, review gates, or ownership rules for a codebase largely written by agents.

ai-regression-testing

Regression testing strategies for AI-assisted development. Sandbox-mode API testing without database dependencies, automated bug-check workflows, and patterns to catch AI blind spots where the same model writes and reviews code. Use when adding regression coverage to AI-assisted code, or when the same model both wrote and reviewed a change.

android-clean-architecture

Clean Architecture patterns for Android and Kotlin Multiplatform projects — module structure, dependency rules, UseCases, Repositories, and data layer patterns. Use when structuring modules, layers, or data flow in an Android or KMP project.

angular-developer

Generates Angular code and provides architectural guidance. Trigger when creating projects, components, or services, or for best practices on reactivity (signals, linkedSignal, resource), forms, dependency injection, routing, SSR, accessibility (ARIA), animations, styling (component styles, Tailwind CSS), testing, or CLI tooling.

Verwandte Skills