Kotlin Idiomatic Patterns
Освойте разработку на Kotlin с идиоматическими шаблонами, null-безопасностью, корутинами и DSL-построителями с помощью навыка Kotlin Patterns для Claude Code.
Навык kotlin-patterns предоставляет комплексную основу для разработки надёжных, эффективных и поддерживаемых приложений на Kotlin. Он фокусируется на идиоматических возможностях языка, включая продвинутую null-безопасность с использованием операторов safe-call и Elvis, неизменяемость через data-классы и структурированную конкурентность с помощью корутин и Flow. Этот ресурс на турецком языке идеально подходит для рефакторинга унаследованного кода, проектирования современных бэкенд-сервисов с Ktor или разработки Android-приложений, а также содержит рекомендации по настройке сборок Gradle с помощью Kotlin DSL. Применяя эти паттерны, навык гарантирует, что разработчики используют весь потенциал Kotlin для типобезопасности и производительности.
Ключевые особенности
Варианты использования
| name | kotlin-patterns |
|---|---|
| description | Coroutine'ler, null safety ve DSL builder'lar ile sağlam, verimli ve sürdürülebilir Kotlin uygulamaları oluşturmak için idiomatic Kotlin kalıpları, en iyi uygulamalar ve konvansiyonlar. |
| origin | ECC |
Sağlam, verimli ve sürdürülebilir uygulamalar oluşturmak için idiomatic Kotlin kalıpları ve en iyi uygulamalar.
- Yeni Kotlin kodu yazarken
- Kotlin kodunu incelerken
- Mevcut Kotlin kodunu refactor ederken
- Kotlin modülleri veya kütüphaneleri tasarlarken
- Gradle Kotlin DSL build'lerini yapılandırırken
Bu skill yedi temel alanda idiomatic Kotlin konvansiyonlarını uygular: tip sistemi ve safe-call operatörleri kullanarak null safety, val ve data class'larda copy() ile immutability, exhaustive tip hiyerarşileri için sealed class'lar ve interface'ler, coroutine'ler ve Flow ile yapılandırılmış eşzamanlılık, inheritance olmadan davranış eklemek için extension fonksiyonlar, @DslMarker ve lambda receiver'lar kullanarak tip güvenli DSL builder'lar, ve build yapılandırması için Gradle Kotlin DSL.
Elvis operatörü ile null safety:
fun getUserEmail(userId: String): String {
val user = userRepository.findById(userId)
return user?.email ?: "unknown@example.com"
}Exhaustive sonuçlar için sealed class:
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>()
}async/await ile yapılandırılmış eşzamanlılık:
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())
}Kotlin'in tip sistemi nullable ve non-nullable tipleri ayırır. Tam olarak kullanın.
// İyi: Varsayılan olarak non-nullable tipler kullan
fun getUser(id: String): User {
return userRepository.findById(id)
?: throw UserNotFoundException("User $id not found")
}
// İyi: Safe call'lar ve Elvis operatörü
fun getUserEmail(userId: String): String {
val user = userRepository.findById(userId)
return user?.email ?: "unknown@example.com"
}
// Kötü: Nullable tipleri zorla açma
fun getUserEmail(userId: String): String {
val user = userRepository.findById(userId)
return user!!.email // null ise NPE fırlatır
}var yerine val tercih edin, mutable koleksiyonlar yerine immutable olanları.
// İyi: Immutable veri
data class User(
val id: String,
val name: String,
val email: String,
)
// İyi: copy() ile dönüştürme
fun updateEmail(user: User, newEmail: String): User =
user.copy(email = newEmail)
// İyi: Immutable koleksiyonlar
val users: List<User> = listOf(user1, user2)
val filtered = users.filter { it.email.isNotBlank() }
// Kötü: Mutable state
var currentUser: User? = null // Mutable global state'ten kaçın
val mutableUsers = mutableListOf<User>() // Gerçekten gerekmedikçe kaçınKısa, okunabilir fonksiyonlar için expression body'ler kullanın.
// İyi: Expression body
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('@') }
// İyi: Expression olarak when
fun statusMessage(code: Int): String = when (code) {
200 -> "OK"
404 -> "Not Found"
500 -> "Internal Server Error"
else -> "Unknown status: $code"
}
// Kötü: Gereksiz block body
fun isAdult(age: Int): Boolean {
return age >= 18
}Öncelikle veri tutan tipler için data class'lar kullanın.
// İyi: copy, equals, hashCode, toString ile data class
data class CreateUserRequest(
val name: String,
val email: String,
val role: Role = Role.USER,
)
// İyi: Tip güvenliği için value class (runtime'da sıfır maliyet)
@JvmInline
value class UserId(val value: String) {
init {
require(value.isNotBlank()) { "UserId cannot be blank" }
}
}
@JvmInline
value class Email(val value: String) {
init {
require('@' in value) { "Invalid email: $value" }
}
}
fun getUser(id: UserId): User = userRepository.findById(id)// İyi: Exhaustive when için sealed class
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("Still loading")
}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
}// let: Nullable'ı veya scope edilmiş sonucu dönüştür
val length: Int? = name?.let { it.trim().length }
// apply: Bir nesneyi yapılandır (nesneyi döndürür)
val user = User().apply {
name = "Alice"
email = "alice@example.com"
}
// also: Yan etkiler (nesneyi döndürür)
val user = createUser(request).also { logger.info("Created user: ${it.id}") }
// run: Receiver ile block çalıştır (sonucu döndürür)
val result = connection.run {
prepareStatement(sql)
executeQuery()
}
// with: run'ın extension olmayan formu
val csv = with(StringBuilder()) {
appendLine("name,email")
users.forEach { appendLine("${it.name},${it.email}") }
toString()
}// İyi: Domain'e özgü extension'lar
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()
// İyi: Koleksiyon extension'ları
fun <T> List<T>.second(): T = this[1]
fun <T> List<T>.secondOrNull(): T? = getOrNull(1)
// İyi: Scope edilmiş extension'lar (global namespace'i kirletmez)
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() }
}// İyi: coroutineScope ile yapılandırılmış eşzamanlılık
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(),
)
}
// İyi: child'lar bağımsız başarısız olabildiğinde supervisorScope
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()
},
)
}// İyi: Uygun hata işleme ile cold flow
fun observeUsers(): Flow<List<User>> = flow {
while (currentCoroutineContext().isActive) {
val users = userRepository.findAll()
emit(users)
delay(5.seconds)
}
}.catch { e ->
logger.error("Error observing users", e)
emit(emptyList())
}
// İyi: Flow operatörleri
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()) }// İyi: @DslMarker ile DSL
@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)
// Kullanım
val page = html {
head { title("My Page") }
body {
h1("Welcome")
p("Hello, World!")
}
}// En son versiyonları kontrol et: 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.0.0")
implementation("org.jetbrains.exposed:exposed-dao:1.0.0")
implementation("org.jetbrains.exposed:exposed-jdbc:1.0.0")
implementation("org.jetbrains.exposed:exposed-kotlin-datetime:1.0.0")
// Koin
implementation("io.insert-koin:koin-ktor:4.2.0")
// Coroutines
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.10.2")
// Test
testImplementation("io.kotest:kotest-runner-junit5:6.1.4")
testImplementation("io.kotest:kotest-assertions-core:6.1.4")
testImplementation("io.kotest:kotest-property:6.1.4")
testImplementation("io.mockk:mockk:1.14.9")
testImplementation("io.ktor:ktor-server-test-host:3.4.0")
testImplementation("org.jetbrains.kotlinx:kotlinx-coroutines-test:1.10.2")
}
tasks.withType<Test> {
useJUnitPlatform()
}
detekt {
config.setFrom(files("config/detekt/detekt.yml"))
buildUponDefaultConfig = true
}// İyi: Kotlin'in Result'ını veya özel sealed class kullan
suspend fun createUser(request: CreateUserRequest): Result<User> = runCatching {
require(request.name.isNotBlank()) { "Name cannot be blank" }
require('@' in request.email) { "Invalid email format" }
val user = User(
id = UserId(UUID.randomUUID().toString()),
name = request.name,
email = Email(request.email),
)
userRepository.save(user)
user
}
// İyi: Result'ları zincirle
val displayName = createUser(request)
.map { it.name }
.getOrElse { "Unknown" }// İyi: Net mesajlarla ön koşullar
fun withdraw(account: Account, amount: Money): Account {
require(amount.value > 0) { "Amount must be positive: $amount" }
check(account.balance >= amount) { "Insufficient balance: ${account.balance} < $amount" }
return account.copy(balance = account.balance - amount)
}| İdiyom | Açıklama |
|---|---|
val over var |
Immutable değişkenleri tercih et |
data class |
equals/hashCode/copy ile value objeler için |
sealed class/interface |
Kısıtlı tip hiyerarşileri için |
value class |
Sıfır maliyetli tip güvenli sarmalayıcılar için |
Expression when |
Exhaustive pattern matching |
Safe call ?. |
Null-safe member erişimi |
Elvis ?: |
Nullable'lar için varsayılan değer |
let/apply/also/run/with |
Temiz kod için scope fonksiyonlar |
| Extension fonksiyonlar | Inheritance olmadan davranış ekle |
copy() |
Data class'larda immutable güncellemeler |
require/check |
Ön koşul assertion'ları |
Coroutine async/await |
Yapılandırılmış concurrent execution |
Flow |
Cold reactive stream'ler |
sequence |
Lazy evaluation |
Delegation by |
Inheritance olmadan implementasyonu yeniden kullan |
// Kötü: Nullable tipleri zorla açma
val name = user!!.name
// Kötü: Java'dan platform tipi sızıntısı
fun getLength(s: String) = s.length // Güvenli
fun getLength(s: String?) = s?.length ?: 0 // Java'dan null'ları işle
// Kötü: Mutable data class'lar
data class MutableUser(var name: String, var email: String)
// Kötü: Kontrol akışı için exception kullanma
try {
val user = findUser(id)
} catch (e: NotFoundException) {
// Beklenen durumlar için exception kullanma
}
// İyi: Nullable dönüş veya Result kullan
val user: User? = findUserOrNull(id)
// Kötü: Coroutine scope'u görmezden gelme
GlobalScope.launch { /* GlobalScope'tan kaçın */ }
// İyi: Yapılandırılmış eşzamanlılık kullan
coroutineScope {
launch { /* Uygun şekilde scope edilmiş */ }
}
// Kötü: Derin iç içe scope fonksiyonlar
user?.let { u ->
u.address?.let { a ->
a.city?.let { c -> process(c) }
}
}
// İyi: Doğrudan null-safe zincir
user?.address?.city?.let { process(it) }Hatırla: Kotlin kodu kısa ama okunabilir olmalı. Güvenlik için tip sisteminden yararlanın, immutability tercih edin ve eşzamanlılık için coroutine'ler kullanın. Şüpheye düştüğünüzde, derleyicinin size yardım etmesine izin verin.
📘 Что это
Kotlin Idiomatic Patterns — это справочный набор рекомендаций, шаблонов и конвенций для написания идиоматического, безопасного и поддерживаемого кода на Kotlin. Skill охватывает семь ключевых областей: null safety, immutability, sealed class/interface, scope-функции, extension-функции, корутины с Flow, типобезопасные DSL-построители и Gradle Kotlin DSL. Он подойдёт разработчикам, которые пишут новый Kotlin-код, рефакторят существующий, проектируют библиотеки или настраивают сборку.
⚙️ Как работает
🔹 Null safety и типовая система
Skill учит использовать non-nullable типы по умолчанию, safe-call (?.), оператор Элвиса (?:) и избегать force-unwrap (!!).
// Идиоматично: Элвис и safe-call
fun getUserEmail(userId: String): String {
val user = userRepository.findById(userId)
return user?.email ?: "unknown@example.com"
}
// Анти-паттерн: force-unwrap
fun getUserEmail(userId: String): String {
val user = userRepository.findById(userId)
return user!!.email // NPE при null
}
🔹 Immutability и data class
Рекомендуется val вместо var, immutable коллекции (listOf, mapOf) и data class с автоматической генерацией equals, hashCode, toString и copy().
data class User(val id: String, val name: String, val email: String)
fun updateEmail(user: User, newEmail: String): User = user.copy(email = newEmail)
🔹 Sealed class / interface для ограниченных иерархий
Использование sealed class или sealed interface позволяет делать exhaustive 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
}
🔹 Scope-функции let, apply, also, run, with
Каждая функция имеет чёткое назначение:
let— преобразование nullable или результат с ограничением области видимости.apply— конфигурация объекта (возвращает сам объект).also— побочные эффекты (логирование, валидация).run— выполнение блока с receiver (возвращает результат).with— не-extension вариантrun.
Skill объясняет, какой scope-функцией заменять вложенные цепочки и когда использовать прямые null-safe вызовы.
🔹 Extension-функции и значение классы
Расширения позволяют добавлять поведение без наследования. @JvmInline value class даёт нулевую стоимость обёртки на этапе выполнения.
fun String.toSlug(): String = lowercase()
.replace(Regex("[^a-z0-9\\s-]"), "")
.replace(Regex("\\s+"), "-")
.trim('-')
🔹 Корутины и Flow
Skill описывает структурированную конкурентность с coroutineScope, supervisorScope, async/await и Flow для реактивных потоков.
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())
}
Для Flow показаны операторы debounce, distinctUntilChanged, mapLatest и обработка ошибок через catch.
🔹 DSL-построители и Gradle Kotlin DSL
На примере построения HTML-разметки иллюстрируется использование @DslMarker для ограничения области видимости receiver. В разделе Gradle Kotlin DSL приведён полный файл build.gradle.kts с плагинами (Kotlin, Ktor, Exposed, Koin, Detekt) и зависимостями, а также настройками тестовой платформы JUnit5.
🔹 Обработка ошибок и анти-паттерны
Skill рекомендует runCatching для безопасного выполнения, а также require и check для предусловий. В отдельном блоке перечислены анти-паттерны: force-unwrap nullable, mutable data class, использование исключений для управления потоком, GlobalScope, вложенные scope-функции.
✅ Когда использовать
- При написании нового Kotlin-кода с нуля.
- При код-ревью: чтобы проверять соответствие идиомам.
- При рефакторинге: заменять небезопасные конструкции на идиоматические.
- При проектировании API библиотек или модулей.
- При настройке Gradle Kotlin DSL в проекте.
- При изучении Kotlin после Java: быстрый переход к безопасным подходам.
📌 Важно знать
- Skill не содержит полных примеров из реального проекта, а только сжатые сниппеты для быстрой справки.
- Версии плагинов в
build.gradle.kts(Kotlin 2.3.10, Ktor 3.4.0 и др.) — актуальны на момент создания описания; рекомендуется сверяться с официальными страницами. value classподдерживается начиная с Kotlin 1.5, требует аннотации@JvmInline.- В анти-паттернах особое внимание уделено platform type leakage из Java — параметры функций должны быть явно nullable, если могут прийти null из Java-кода.
Как этот навык помогает с нулевой безопасностью?
Он предоставляет конкретные шаблоны реализации для оператора безопасного вызова, оператора Элвиса и умного приведения, позволяя предотвращать NullPointerException на этапе компиляции, а не во время выполнения.
Что такое идиоматические паттерны Kotlin?
Идиоматические паттерны — это стандартные лучшие практики, использующие специфические возможности Kotlin, такие как делегирование свойств, нулевая безопасность и тела-выражения, для написания более чистого и эффективного кода.
Поддерживает ли он конфигурацию Gradle?
Да, он включает лучшие практики для файлов build.gradle.kts, помогая разработчикам перейти с Groovy на более типобезопасный Gradle Kotlin DSL.
Может ли этот навык помочь с асинхронным программированием?
Да, он специализируется на структурированной конкурентности с использованием Kotlin Coroutines (async/await) и реактивных потоков с Flow, обеспечивая правильное управление областью видимости и обработку ошибок.
На каком языке предоставляется документация?
Этот конкретный навык (docs-tr) предоставляет свои рекомендации, примеры и описания паттернов на турецком языке.
Синхронизируйте навыки с Claude Cowork, Claude Code, Codex и другими.
Установка одной командой.
npx skillfish add affaan-m/everything-claude-code kotlin-patternsИсточник: https://mcpmarket.com/tools/skills/kotlin-idiomatic-patterns-2
Комментарии
Комментариев пока нет. Будьте первым.