kotlin-patterns
惯用的Kotlin模式、最佳实践和约定,用于构建健壮、高效且可维护的Kotlin应用程序,包括协程、空安全和DSL构建器。
DeepseekModel
Curated skill
Quality Excellent · 90
v1.0.0
Get
https://deepseekmodel.com/api/download.php?id=affaan-m-ecc-docs-zh-cn-skills-kotlin-patterns-skill-md&format=skill
Download .skill
Standard format with system_prompt and model_config, ready for any agent framework
The actual content of the system_prompt field in the .skill file.
name kotlin-patterns description 惯用的Kotlin模式、最佳实践和约定,用于构建健壮、高效且可维护的Kotlin应用程序,包括协程、空安全和DSL构建器。 origin ECC Kotlin 开发模式 适用于构建健壮、高效、可维护应用程序的惯用 Kotlin 模式与最佳实践。 使用时机 编写新的 Kotlin 代码 审查 Kotlin 代码 重构现有的 Kotlin 代码 设计 Kotlin 模块或库 配置 Gradle Kotlin DSL 构建 工作原理 本技能在七个关键领域强制执行惯用的 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 >() } 使用 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()) } 核心原则 1. 空安全 Kotlin 的类型系统区分可空和不可空类型。充分利用它。 // 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 } 2. 默认不可变性 优先使用 val 而非 var ,优先使用不可变集合而非可变集合。 // 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 3. 表达式体和单表达式函数 使用表达式体编写简洁、可读的函数。 // 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 } 4. 数据类用于值对象 使用数据类表示主要包含数据的类型。 // 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" ) } 用于 API 响应的密封接口 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() }, ) } Flow 用于响应式流 // 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" } " ) } } } DSL 构建器 类型安全构建器 // 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!" ) } } 配置 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 =
Keywords that activate this skill. Click one to copy it.
This skill does not provide trigger words.
The downloaded .skill package contains the following fields.
| Field | Description |
|---|---|
| format | Format tag (skill/v1) |
| skill_id | Unique skill ID |
| name | Skill name |
| version | Version |
| description | Description |
| category | Categories (array) |
| trigger_words | Trigger words |
| tags | Tags |
| source | Source |
| source_url | Source URL (this page) |
| exported_at | Exported at (set per download) |
| system_prompt | System prompt body |
| model_config | Model config: provider / model / temperature / max_tokens / top_p |
| examples | Examples |
| install_guide | Import guide for Coze / Dify / Claude / custom frameworks |
The same skill can be exported in different platform formats.