kotlin-patterns by affaan-m/everything-claude-code
npx skills add https://github.com/affaan-m/everything-claude-code --skill kotlin-patterns构建健壮、高效、可维护应用程序的惯用 Kotlin 模式与最佳实践。
此技能在七个关键领域强制执行惯用的 Kotlin 约定:使用类型系统和安全调用操作符实现空安全;通过数据类的 val 和 copy() 实现不可变性;使用密封类和接口实现详尽的类型层次结构;使用协程和 Flow 实现结构化并发;使用扩展函数实现无需继承的行为添加;使用 @DslMarker 和 lambda 接收者实现类型安全的 DSL 构建器;以及使用 Gradle Kotlin DSL 进行构建配置。
使用 Elvis 操作符实现空安全:
fun getUserEmail(userId: String): String {
val user = userRepository.findById(userId)
return user?.email ?: "unknown@example.com"
}
用于详尽结果的密封类:
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>()
}
广告位招租
在这里展示您的产品或服务
触达数万 AI 开发者,精准高效
使用 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())
}
Kotlin 的类型系统区分可空和不可空类型。请充分利用它。
// 良好实践:默认使用不可空类型
fun getUser(id: String): User {
return userRepository.findById(id)
?: throw UserNotFoundException("User $id not found")
}
// 良好实践:安全调用和 Elvis 操作符
fun getUserEmail(userId: String): String {
val user = userRepository.findById(userId)
return user?.email ?: "unknown@example.com"
}
// 不良实践:强制解包可空类型
fun getUserEmail(userId: String): String {
val user = userRepository.findById(userId)
return user!!.email // 如果为 null 则抛出 NPE
}
优先使用 val 而非 var,优先使用不可变集合而非可变集合。
// 良好实践:不可变数据
data class User(
val id: String,
val name: String,
val email: String,
)
// 良好实践:使用 copy() 进行转换
fun updateEmail(user: User, newEmail: String): User =
user.copy(email = newEmail)
// 良好实践:不可变集合
val users: List<User> = listOf(user1, user2)
val filtered = users.filter { it.email.isNotBlank() }
// 不良实践:可变状态
var currentUser: User? = null // 避免可变全局状态
val mutableUsers = mutableListOf<User>() // 除非确实需要,否则避免使用
使用表达式体编写简洁、可读的函数。
// 良好实践:表达式体
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('@') }
// 良好实践:将 when 用作表达式
fun statusMessage(code: Int): String = when (code) {
200 -> "OK"
404 -> "Not Found"
500 -> "Internal Server Error"
else -> "Unknown status: $code"
}
// 不良实践:不必要的块体
fun isAdult(age: Int): Boolean {
return age >= 18
}
对于主要持有数据的类型,请使用数据类。
// 良好实践:具有 copy、equals、hashCode、toString 的数据类
data class CreateUserRequest(
val name: String,
val email: String,
val role: Role = Role.USER,
)
// 良好实践:用于类型安全的值类(运行时零开销)
@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)
// 良好实践:用于详尽 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("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:转换可空或作用域结果
val length: Int? = name?.let { it.trim().length }
// apply:配置对象(返回对象本身)
val user = User().apply {
name = "Alice"
email = "alice@example.com"
}
// also:副作用(返回对象本身)
val user = createUser(request).also { logger.info("Created user: ${it.id}") }
// run:使用接收者执行代码块(返回结果)
val result = connection.run {
prepareStatement(sql)
executeQuery()
}
// with:run 的非扩展形式
val csv = with(StringBuilder()) {
appendLine("name,email")
users.forEach { appendLine("${it.name},${it.email}") }
toString()
}
// 不良实践:嵌套作用域函数
user?.let { u ->
u.address?.let { addr ->
addr.city?.let { city ->
println(city) // 难以阅读
}
}
}
// 良好实践:改为链式安全调用
val city = user?.address?.city
city?.let { println(it) }
// 良好实践:领域特定扩展
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()
// 良好实践:集合扩展
fun <T> List<T>.second(): T = this[1]
fun <T> List<T>.secondOrNull(): T? = getOrNull(1)
// 良好实践:作用域扩展(不污染全局命名空间)
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() }
}
// 良好实践:使用 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(),
)
}
// 良好实践:当子任务可能独立失败时使用 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()
},
)
}
// 良好实践:具有适当错误处理的冷流
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())
}
// 良好实践:Flow 操作符
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()) }
// 良好实践:尊重取消
suspend fun processItems(items: List<Item>) {
items.forEach { item ->
ensureActive() // 在耗时工作前检查取消状态
processItem(item)
}
}
// 良好实践:使用 try/finally 进行清理
suspend fun acquireAndProcess() {
val resource = acquireResource()
try {
resource.process()
} finally {
withContext(NonCancellable) {
resource.release() // 即使取消也始终释放资源
}
}
}
// 惰性初始化
val expensiveData: List<User> by lazy {
userRepository.findAll()
}
// 可观察属性
var name: String by Delegates.observable("initial") { _, old, new ->
logger.info("Name changed from '$old' to '$new'")
}
// 基于 Map 的属性
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))
// 良好实践:委托接口实现
class LoggingUserRepository(
private val delegate: UserRepository,
private val logger: Logger,
) : UserRepository by delegate {
// 仅覆盖需要添加日志记录的方法
override suspend fun findById(id: String): User? {
logger.info("Finding user by id: $id")
return delegate.findById(id).also {
logger.info("Found user: ${it?.name ?: "null"}")
}
}
}
// 良好实践:使用 @DslMarker 的 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)
// 用法
val page = html {
head { title("My Page") }
body {
h1("Welcome")
p("Hello, World!")
}
}
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()
// 用法
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)
}
// 良好实践:对具有多个操作的大型集合使用序列
val result = users.asSequence()
.filter { it.isActive }
.map { it.email }
.filter { it.endsWith("@company.com") }
.take(10)
.toList()
// 良好实践:生成无限序列
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()
// 检查最新版本: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")
// Testing
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
}
// 良好实践:使用 Kotlin 的 Result 或自定义密封类
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
}
// 良好实践:链式处理结果
val displayName = createUser(request)
.map { it.name }
.getOrElse { "Unknown" }
// 良好实践:具有清晰消息的前置条件
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)
}
// 良好实践:链式操作
val activeAdminEmails: List<String> = users
.filter { it.role == Role.ADMIN && it.isActive }
.sortedBy { it.name }
.map { it.email }
// 良好实践:分组与聚合
val usersByRole: Map<Role, List<User>> = users.groupBy { it.role }
val oldestByRole: Map<Role, User?> = users.groupBy { it.role }
.mapValues { (_, users) -> users.minByOrNull { it.createdAt } }
// 良好实践:使用 associate 创建 Map
val usersById: Map<UserId, User> = users.associateBy { it.id }
// 良好实践:使用 partition 进行分割
val (active, inactive) = users.partition { it.isActive }
| 惯用法 | 描述 |
|---|---|
val 优于 var | 优先使用不可变变量 |
data class | 用于具有 equals/hashCode/copy 的值对象 |
sealed class/interface | 用于受限的类型层次结构 |
value class | 用于零开销的类型安全包装器 |
表达式 when | 详尽的模式匹配 |
安全调用 ?. | 空安全的成员访问 |
Elvis ?: | 可空类型的默认值 |
let/apply/also/run/with | 用于编写清晰代码的作用域函数 |
| 扩展函数 | 无需继承添加行为 |
copy() | 数据类的不可变更新 |
require/check | 前置条件断言 |
协程 async/await | 结构化并发执行 |
Flow | 冷响应式流 |
sequence | 惰性求值 |
委托 by | 无需继承重用实现 |
// 不良实践:强制解包可空类型
val name = user!!.name
// 不良实践:来自 Java 的平台类型泄漏
fun getLength(s: String) = s.length // 安全
fun getLength(s: String?) = s?.length ?: 0 // 处理来自 Java 的 null
// 不良实践:可变数据类
data class MutableUser(var name: String, var email: String)
// 不良实践:使用异常进行控制流
try {
val user = findUser(id)
} catch (e: NotFoundException) {
// 不要将异常用于预期情况
}
// 良好实践:使用可空返回值或 Result
val user: User? = findUserOrNull(id)
// 不良实践:忽略协程作用域
GlobalScope.launch { /* 避免使用 GlobalScope */ }
// 良好实践:使用结构化并发
coroutineScope {
launch { /* 正确的作用域 */ }
}
// 不良实践:深度嵌套的作用域函数
user?.let { u ->
u.address?.let { a ->
a.city?.let { c -> process(c) }
}
}
// 良好实践:直接的空安全链
user?.address?.city?.let { process(it) }
请记住:Kotlin 代码应简洁但可读。利用类型系统确保安全,优先使用不可变性,并使用协程处理并发。如有疑问,让编译器帮助你。
每周安装次数
397
代码仓库
GitHub 星标数
102.1K
首次出现
11 天前
安全审计
安装于
codex375
cursor326
opencode324
gemini-cli322
github-copilot322
amp322
Idiomatic Kotlin patterns and best practices for building robust, efficient, and maintainable applications.
This skill enforces idiomatic Kotlin conventions across seven key areas: null safety using the type system and safe-call operators, immutability via val and copy() on data classes, sealed classes and interfaces for exhaustive type hierarchies, structured concurrency with coroutines and Flow, extension functions for adding behaviour without inheritance, type-safe DSL builders using @DslMarker and lambda receivers, and Gradle Kotlin DSL for build configuration.
Null safety with Elvis operator:
fun getUserEmail(userId: String): String {
val user = userRepository.findById(userId)
return user?.email ?: "unknown@example.com"
}
Sealed class for exhaustive results:
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>()
}
Structured concurrency with 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())
}
Kotlin's type system distinguishes nullable and non-nullable types. Leverage it fully.
// Good: Use non-nullable types by default
fun getUser(id: String): User {
return userRepository.findById(id)
?: throw UserNotFoundException("User $id not found")
}
// Good: Safe calls and Elvis operator
fun getUserEmail(userId: String): String {
val user = userRepository.findById(userId)
return user?.email ?: "unknown@example.com"
}
// Bad: Force-unwrapping nullable types
fun getUserEmail(userId: String): String {
val user = userRepository.findById(userId)
return user!!.email // Throws NPE if null
}
Prefer val over var, immutable collections over mutable ones.
// Good: Immutable data
data class User(
val id: String,
val name: String,
val email: String,
)
// Good: Transform with copy()
fun updateEmail(user: User, newEmail: String): User =
user.copy(email = newEmail)
// Good: Immutable collections
val users: List<User> = listOf(user1, user2)
val filtered = users.filter { it.email.isNotBlank() }
// Bad: Mutable state
var currentUser: User? = null // Avoid mutable global state
val mutableUsers = mutableListOf<User>() // Avoid unless truly needed
Use expression bodies for concise, readable functions.
// Good: 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('@') }
// Good: When as expression
fun statusMessage(code: Int): String = when (code) {
200 -> "OK"
404 -> "Not Found"
500 -> "Internal Server Error"
else -> "Unknown status: $code"
}
// Bad: Unnecessary block body
fun isAdult(age: Int): Boolean {
return age >= 18
}
Use data classes for types that primarily hold data.
// Good: Data class with copy, equals, hashCode, toString
data class CreateUserRequest(
val name: String,
val email: String,
val role: Role = Role.USER,
)
// Good: Value class for type safety (zero overhead at runtime)
@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)
// Good: Sealed class for 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
}
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: Transform nullable or scoped result
val length: Int? = name?.let { it.trim().length }
// apply: Configure an object (returns the object)
val user = User().apply {
name = "Alice"
email = "alice@example.com"
}
// also: Side effects (returns the object)
val user = createUser(request).also { logger.info("Created user: ${it.id}") }
// run: Execute a block with receiver (returns result)
val result = connection.run {
prepareStatement(sql)
executeQuery()
}
// with: Non-extension form of run
val csv = with(StringBuilder()) {
appendLine("name,email")
users.forEach { appendLine("${it.name},${it.email}") }
toString()
}
// Bad: Nesting scope functions
user?.let { u ->
u.address?.let { addr ->
addr.city?.let { city ->
println(city) // Hard to read
}
}
}
// Good: Chain safe calls instead
val city = user?.address?.city
city?.let { println(it) }
// Good: Domain-specific extensions
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()
// Good: Collection extensions
fun <T> List<T>.second(): T = this[1]
fun <T> List<T>.secondOrNull(): T? = getOrNull(1)
// Good: Scoped extensions (not polluting global namespace)
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() }
}
// Good: Structured concurrency with 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(),
)
}
// Good: supervisorScope when children can fail independently
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()
},
)
}
// Good: Cold flow with proper error handling
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())
}
// Good: Flow operators
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()) }
// Good: Respect cancellation
suspend fun processItems(items: List<Item>) {
items.forEach { item ->
ensureActive() // Check cancellation before expensive work
processItem(item)
}
}
// Good: Cleanup with try/finally
suspend fun acquireAndProcess() {
val resource = acquireResource()
try {
resource.process()
} finally {
withContext(NonCancellable) {
resource.release() // Always release, even on cancellation
}
}
}
// Lazy initialization
val expensiveData: List<User> by lazy {
userRepository.findAll()
}
// Observable property
var name: String by Delegates.observable("initial") { _, old, new ->
logger.info("Name changed from '$old' to '$new'")
}
// Map-backed properties
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))
// Good: Delegate interface implementation
class LoggingUserRepository(
private val delegate: UserRepository,
private val logger: Logger,
) : UserRepository by delegate {
// Only override what you need to add logging to
override suspend fun findById(id: String): User? {
logger.info("Finding user by id: $id")
return delegate.findById(id).also {
logger.info("Found user: ${it?.name ?: "null"}")
}
}
}
// Good: DSL with @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)
// Usage
val page = html {
head { title("My Page") }
body {
h1("Welcome")
p("Hello, World!")
}
}
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()
// Usage
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)
}
// Good: Use sequences for large collections with multiple operations
val result = users.asSequence()
.filter { it.isActive }
.map { it.email }
.filter { it.endsWith("@company.com") }
.take(10)
.toList()
// Good: Generate infinite sequences
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()
// Check for latest versions: 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")
// Testing
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
}
// Good: Use Kotlin's Result or a custom sealed class
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
}
// Good: Chain results
val displayName = createUser(request)
.map { it.name }
.getOrElse { "Unknown" }
// Good: Preconditions with clear messages
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)
}
// Good: Chained operations
val activeAdminEmails: List<String> = users
.filter { it.role == Role.ADMIN && it.isActive }
.sortedBy { it.name }
.map { it.email }
// Good: Grouping and aggregation
val usersByRole: Map<Role, List<User>> = users.groupBy { it.role }
val oldestByRole: Map<Role, User?> = users.groupBy { it.role }
.mapValues { (_, users) -> users.minByOrNull { it.createdAt } }
// Good: Associate for map creation
val usersById: Map<UserId, User> = users.associateBy { it.id }
// Good: Partition for splitting
val (active, inactive) = users.partition { it.isActive }
| Idiom | Description |
|---|---|
val over var | Prefer immutable variables |
data class | For value objects with equals/hashCode/copy |
sealed class/interface | For restricted type hierarchies |
value class | For type-safe wrappers with zero overhead |
Expression when | Exhaustive pattern matching |
Safe call ?. |
// Bad: Force-unwrapping nullable types
val name = user!!.name
// Bad: Platform type leakage from Java
fun getLength(s: String) = s.length // Safe
fun getLength(s: String?) = s?.length ?: 0 // Handle nulls from Java
// Bad: Mutable data classes
data class MutableUser(var name: String, var email: String)
// Bad: Using exceptions for control flow
try {
val user = findUser(id)
} catch (e: NotFoundException) {
// Don't use exceptions for expected cases
}
// Good: Use nullable return or Result
val user: User? = findUserOrNull(id)
// Bad: Ignoring coroutine scope
GlobalScope.launch { /* Avoid GlobalScope */ }
// Good: Use structured concurrency
coroutineScope {
launch { /* Properly scoped */ }
}
// Bad: Deeply nested scope functions
user?.let { u ->
u.address?.let { a ->
a.city?.let { c -> process(c) }
}
}
// Good: Direct null-safe chain
user?.address?.city?.let { process(it) }
Remember : Kotlin code should be concise but readable. Leverage the type system for safety, prefer immutability, and use coroutines for concurrency. When in doubt, let the compiler help you.
Weekly Installs
397
Repository
GitHub Stars
102.1K
First Seen
11 days ago
Security Audits
Gen Agent Trust HubPassSocketPassSnykPass
Installed on
codex375
cursor326
opencode324
gemini-cli322
github-copilot322
amp322
React 组合模式指南:Vercel 组件架构最佳实践,提升代码可维护性
105,000 周安装
LLM提示词缓存优化指南:降低90%成本,实现多级缓存与语义匹配
323 周安装
小红书内容转换器:一键将通用文章转为小红书爆款笔记格式 | AI写作助手
323 周安装
内容摘要AI工具:智能提取YouTube、网页、PDF和推文内容,支持测验学习和深度探索
324 周安装
Notion知识捕获工具 - 将对话笔记自动转化为结构化Notion页面 | 知识管理自动化
324 周安装
现代Angular最佳实践指南:TypeScript严格性、信号响应式、性能优化与测试
324 周安装
iOS VoIP 通话开发:CallKit + PushKit 集成原生通话 UI 指南
324 周安装
| Null-safe member access |
Elvis ?: | Default value for nullables |
let/apply/also/run/with | Scope functions for clean code |
| Extension functions | Add behavior without inheritance |
copy() | Immutable updates on data classes |
require/check | Precondition assertions |
Coroutine async/await | Structured concurrent execution |
Flow | Cold reactive streams |
sequence | Lazy evaluation |
Delegation by | Reuse implementation without inheritance |