kotlin-patterns
Patrones idiomáticos de Kotlin, buenas prácticas y convenciones para construir aplicaciones Kotlin robustas, eficientes y mantenibles con coroutines, null safety y builders de DSL.
DeepseekModel
Curated skill
Quality Excellent · 90
v1.0.0
Get
https://deepseekmodel.com/api/download.php?id=affaan-m-ecc-docs-es-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 Patrones idiomáticos de Kotlin, buenas prácticas y convenciones para construir aplicaciones Kotlin robustas, eficientes y mantenibles con coroutines, null safety y builders de DSL. origin ECC Patrones de Desarrollo Kotlin Patrones idiomáticos de Kotlin y buenas prácticas para construir aplicaciones robustas, eficientes y mantenibles. Cuándo Usar Escribir nuevo código Kotlin Revisar código Kotlin Refactorizar código Kotlin existente Diseñar módulos o librerías Kotlin Configurar builds con Gradle Kotlin DSL Cómo Funciona Este skill aplica convenciones idiomáticas de Kotlin en siete áreas clave: null safety usando el sistema de tipos y operadores de llamada segura, inmutabilidad mediante val y copy() en data classes, clases selladas e interfaces para jerarquías de tipos exhaustivas, concurrencia estructurada con coroutines y Flow , funciones de extensión para agregar comportamiento sin herencia, builders de DSL type-safe usando @DslMarker y receptores lambda, y Gradle Kotlin DSL para configuración de build. Ejemplos Null safety con el operador Elvis: fun getUserEmail (userId: String ) : String { val user = userRepository.findById(userId) return user?.email ?: "unknown@example.com" } Sealed class para resultados exhaustivos: 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 >() } Concurrencia estructurada con 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()) } Principios Fundamentales 1. Null Safety El sistema de tipos de Kotlin distingue tipos nullable de no-nullable. Aprovecharlo al máximo. // Bien: Usar tipos no-nullable por defecto fun getUser (id: String ) : User { return userRepository.findById(id) ?: throw UserNotFoundException( "User $id not found" ) } // Bien: Llamadas seguras y operador Elvis fun getUserEmail (userId: String ) : String { val user = userRepository.findById(userId) return user?.email ?: "unknown@example.com" } // Mal: Desempaquetar forzadamente tipos nullable fun getUserEmail (userId: String ) : String { val user = userRepository.findById(userId) return user!!.email // Lanza NPE si es null } 2. Inmutabilidad por Defecto Preferir val sobre var , colecciones inmutables sobre mutables. // Bien: Datos inmutables data class User ( val id: String, val name: String, val email: String, ) // Bien: Transformar con copy() fun updateEmail (user: User , newEmail: String ) : User = user.copy(email = newEmail) // Bien: Colecciones inmutables val users: List<User> = listOf(user1, user2) val filtered = users.filter { it.email.isNotBlank() } // Mal: Estado mutable var currentUser: User? = null // Evitar estado global mutable val mutableUsers = mutableListOf<User>() // Evitar a menos que sea realmente necesario 3. Cuerpos de Expresión y Funciones de Una Sola Expresión Usar cuerpos de expresión para funciones concisas y legibles. // Bien: Cuerpo de expresión 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( '@' ) } // Bien: When como expresión fun statusMessage (code: Int ) : String = when (code) { 200 -> "OK" 404 -> "Not Found" 500 -> "Internal Server Error" else -> "Unknown status: $code " } // Mal: Cuerpo de bloque innecesario fun isAdult (age: Int ) : Boolean { return age >= 18 } 4. Data Classes para Objetos de Valor Usar data classes para tipos que principalmente contienen datos. // Bien: Data class con copy, equals, hashCode, toString data class CreateUserRequest ( val name: String, val email: String, val role: Role = Role.USER, ) // Bien: Value class para type safety (cero overhead en tiempo de ejecución) @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) Clases Selladas e Interfaces Modelar Jerarquías Restringidas // Bien: Sealed class para when exhaustivo 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 Interfaces para Respuestas de 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 } Funciones de Scope Cuándo Usar Cada Una // let: Transformar resultado nullable o delimitado val length: Int ? = name?.let { it.trim().length } // apply: Configurar un objeto (retorna el objeto) val user = User().apply { name = "Alice" email = "alice@example.com" } // also: Efectos secundarios (retorna el objeto) val user = createUser(request).also { logger.info( "Created user: ${it.id} " ) } // run: Ejecutar un bloque con receptor (retorna resultado) val result = connection.run { prepareStatement(sql) executeQuery() } // with: Forma no-extensión de run val csv = with(StringBuilder()) { appendLine( "name,email" ) users.forEach { appendLine( " ${it.name} , ${it.email} " ) } toString() } Anti-Patrones // Mal: Anidar funciones de scope user?.let { u -> u.address?.let { addr -> addr.city?.let { city -> println(city) // Difícil de leer } } } // Bien: Encadenar llamadas seguras en su lugar val city = user?.address?.city city?.let { println(it) } Funciones de Extensión Agregar Funcionalidad Sin Herencia // Bien: Extensiones específicas del dominio 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() // Bien: Extensiones de colecciones fun <T> List <T> . second () : T = this [ 1 ] fun <T> List <T> . secondOrNull () : T? = getOrNull( 1 ) // Bien: Extensiones delimitadas (sin contaminar el namespace global) 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() } } Coroutines Concurrencia Estructurada // Bien: Concurrencia estructurada con 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(), ) } // Bien: supervisorScope cuando los hijos pueden fallar independientemente 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 para Streams Reactivos // Bien: Flow frío con manejo de errores adecuado 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()) } // Bien: Operadores de 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()) } Cancelación y Limpieza // Bien: Respetar la cancelación suspend fun processItems (items: List < Item >) { items.forEach { item -> ensureActive() // Verificar cancelación antes del trabajo costoso processItem(item) } } // Bien: Limpieza con try/finally suspend fun acquireAndProcess () { val resource = acquireResource() try { resource.process() } finally { withContext(NonCancellable) { resource.release() // Siempre liberar, incluso al cancelar } } } Delegación Delegación de Propiedades // Inicialización diferida val expensiveData: List<User> by lazy { userRepository.findAll() } // Propiedad observable var name: String by Delegates.observable( "initial" ) { _, old, new -> logger.info( "Name changed from ' $old ' to ' $new '" ) } // Propiedades respaldadas por 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 )) Delegación de Interfaces // Bien: Delegar implementación de interfaz class LoggingUserRepository ( private val delegate: UserRepository, private val logger: Logger, ) : UserRepository by delegate { // Solo sobreescribir lo que necesitas agregar logging 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" } " ) } } } Builders de DSL Builders Type-Safe // Bien: DSL con @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 ) // Uso val page = html { head { title( "My Page" ) } body { h1( "Welcome" ) p( "Hello, World!" ) } } DSL de Configuración 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) }
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.