Skip to main content

kotlin-patterns

Kotlin: coroutines, Flow, sealed/data classes, null safety, Ktor, Compose, KMP. Triggers: Kotlin, coroutine, Flow, suspend, Ktor, Jetpack Compose, KMP, kotlinx.

معلومات المصدر

المستودع
softspark/ai-toolkit
آخر نشاط في المصدر
٦ مايو ٢٠٢٦ في ٠٩:٤٨
لغة SKILL.md المكتشفة
الإنجليزية
النجوم
١٨٠
التفرعات
٢١

خيارات التثبيت

يُحدَّد Prompt الذي يراجع المصدر أولًا بشكل افتراضي. يمكنك التبديل إلى أمر مباشر أو تنزيل نسخة محلية.

مراجعة ملفات المصدر

اقرأ SKILL.md وأي ملفات مرافقة يعرضها SkillsMP قبل أن تقرر التثبيت.

عرض SKILL.md

SKILL.md
تعليمات المصدر · معاينة للقراءة فقط
name
kotlin-patterns
description
Kotlin: coroutines, Flow, sealed/data classes, null safety, Ktor, Compose, KMP. Triggers: Kotlin, coroutine, Flow, suspend, Ktor, Jetpack Compose, KMP, kotlinx.
effort
medium
user-invocable
false
allowed-tools
Read
# Kotlin Patterns Skill ## Project Structure ### Gradle KTS Multi-Module Layout ``` project-root/ ├── build.gradle.kts ├── settings.gradle.kts ├── gradle/libs.versions.toml ├── app/ │ ├── build.gradle.kts │ └── src/{main,test}/kotlin/com/example/app/ ├── domain/ │ └── src/main/kotlin/com/example/domain/ │ ├── model/ │ ├── repository/ │ └── usecase/ └── infrastructure/ └── src/main/kotlin/com/example/infra/ ``` ### settings.gradle.kts ```kotlin rootProject.name = "my-project" dependencyResolutionManagement { versionCatalogs { create("libs") { from(files("gradle/libs.versions.toml")) } } } include(":app", ":domain", ":infrastructure") ``` ### Module build.gradle.kts ```kotlin plugins { alias(libs.plugins.kotlin.jvm) alias(libs.plugins.kotlin.serialization) } dependencies { implementation(project(":domain")) implementation(libs.kotlinx.coroutines.core) testImplementation(libs.bundles.testing) } kotlin { jvmToolchain(21) } ``` --- ## Idioms / Code Style ### Data Classes + Value Classes ```kotlin data class User( val id: UserId, val name: String, val email: String, val role: Role = Role.USER, ) { init { require(name.isNotBlank()) { "Name must not be blank" } require(email.contains("@")) { "Invalid email format" } } } @JvmInline value class UserId(val value: String) // Zero-overhead type-safe ID enum class Role { ADMIN, USER, GUEST } ``` ### Sealed Interfaces for Domain Modeling ```kotlin sealed interface PaymentResult { data class Success(val transactionId: String, val amount: Money) : PaymentResult data class Declined(val reason: String) : PaymentResult data class Error(val exception: Throwable) : PaymentResult } // Exhaustive when -- compiler enforces all branches fun handlePayment(result: PaymentResult): String = when (result) { is PaymentResult.Success -> "Paid: ${result.amount}" is PaymentResult.Declined -> "Declined: ${result.reason}" is PaymentResult.Error -> "Error: ${result.exception.message}" } ``` ### Extension Functions ```kotlin fun String.toSlug(): String = lowercase().replace(Regex("[^a-z0-9\\s-]"), "").replace(Regex("\\s+"), "-").trim('-') // Scoped extensions -- visible only inside containing class class OrderService { private fun Order.totalWithTax(): Money = total * (1 + taxRate) } ``` ### Null Safety ```kotlin fun getDisplayName(user: User?): String = user?.name?.takeIf { it.isNotBlank() } ?: "Anonymous" fun processEmail(email: String?) { email?.let { sendWelcomeEmail(it) } } fun loadConfig(path: String?): Config { val resolved = requireNotNull(path) { "Config path must not be null" } return parseConfig(resolved) } ``` ### Scope Functions | Function | Ref | Returns | Use case | |----------|-----|---------|----------| | `let` | `it` | Lambda result | Null check + transform | | `run` | `this` | Lambda result | Config + compute | | `apply` | `this` | Object itself | Object initialization | | `also` | `it` | Object itself | Side effects | ```kotlin val conn = Connection().apply { host = "localhost"; port = 5432 } fun createUser(req: CreateUserRequest): User = userRepository.save(req.toUser()).also { logger.info("Created: ${it.id}") } ``` ### Type-Safe Builders (DSL) ```kotlin fun html(block: HtmlBuilder.() -> Unit): String = HtmlBuilder().apply(block).build() val page = html { head { title("My Page") } body { p("Hello, world!") } } ``` --- ## Error Handling ### Result<T> and runCatching ```kotlin fun findUser(id: UserId): Result<User> = runCatching { userRepository.findById(id) ?: throw UserNotFoundException(id) } fun getUserDisplayName(id: UserId): String = findUser(id).map { it.name }.recover { "Unknown User" }.getOrThrow() fun handleLookup(id: UserId): Response = findUser(id).fold( onSuccess = { Response.ok(it) }, onFailure = { Response.notFound(it.message) }, ) ``` ### Sealed Class Error Hierarchy ```kotlin sealed class DomainError(override val message: String) : Exception(message) { data class NotFound(val resource: String, val id: String) : DomainError("$resource not found: $id") data class Validation(val field: String, val reason: String) : DomainError("Invalid $field: $reason") data class Conflict(val detail: String) : DomainError("Conflict: $detail") } fun handleError(error: DomainError): Response = when (error) { is DomainError.NotFound -> Response.status(404).body(error.message) is DomainError.Validation -> Response.status(422).body(error.message) is DomainError.Conflict -> Response.status(409).body(error.message) } ``` ### Preconditions ```kotlin fun transferMoney(from: Account, to: Account, amount: Money) { require(amount.value > 0) { "Transfer amount must be positive" } require(from.id != to.id) { "Cannot transfer to same account" } check(from.balance >= amount) { "Insufficient funds: ${from.balance}" } } ``` --- ## Testing Patterns ### JUnit 5 + MockK ```kotlin class UserServiceTest { private val repository = mockk<UserRepository>() private val notifier = mockk<NotificationService>(relaxed = true) private val service = UserService(repository, notifier) @Test fun `creates user and sends welcome notification`() { val expected = User(UserId("1"), "Alice", "alice@test.com") every { repository.save(any()) } returns expected val result = service.createUser(CreateUserRequest("Alice", "alice@test.com")) assertThat(result).isEqualTo(expected) verify(exactly = 1) { notifier.sendWelcome(expected) } } @Test fun `throws on duplicate email`() { every { repository.save(any()) } throws DomainError.Conflict("Email exists") assertThrows<DomainError.Conflict> { service.createUser(CreateUserRequest("Bob", "dup@test.com")) } } } ``` ### Kotest + Property-Based Testing ```kotlin class MoneySpec : FunSpec({ test("addition is commutative") { checkAll(Arb.positiveLong(), Arb.positiveLong()) { a, b -> Money(a) + Money(b) shouldBe Money(b) + Money(a) } } test("cannot create negative money") { shouldThrow<IllegalArgumentException> { Money(-1) } } }) ``` ### assertSoftly + Parameterized Tests ```kotlin @Test fun `user has correct defaults`() { val user = User.create("Alice", "alice@test.com") assertSoftly(user) { name shouldBe "Alice" role shouldBe Role.USER isActive shouldBe true } } @ParameterizedTest @CsvSource("alice@test.com, true", "not-an-email, false", "'', false") fun `validates email format`(input: String, expected: Boolean) { assertThat(isValidEmail(input)).isEqualTo(expected) } ``` --- ## Common Frameworks ### Spring Boot with Kotlin ```kotlin @RestController @RequestMapping("/api/v1/users") class UserController(private val userService: UserService) { @GetMapping("/{id}") fun getUser(@PathVariable id: String): ResponseEntity<UserDto> = userService.findById(UserId(id))?.let { ResponseEntity.ok(it.toDto()) } ?: ResponseEntity.notFound().build() @PostMapping fun createUser(@Valid @RequestBody req: CreateUserRequest): ResponseEntity<UserDto> { val user = userService.create(req) return ResponseEntity.created(URI("/api/v1/users/${user.id.value}")).body(user.toDto()) } } ``` ### Ktor ```kotlin fun Application.configureRouting() { routing { route("/api/v1/users") { get { call.respond(userService.listAll()) } get("/{id}") { val id = call.parameters["id"] ?: return@get call.respond(HttpStatusCode.BadRequest) val user = userService.findById(UserId(id)) ?: return@get call.respond(HttpStatusCode.NotFound) call.respond(user) } post { val req = call.receive<CreateUserRequest>() call.respond(HttpStatusCode.Created, userService.create(req)) } } } } fun Application.configurePlugins() { install(ContentNegotiation) { json() } install(StatusPages) { exception<DomainError.NotFound> { call, e -> call.respond(HttpStatusCode.NotFound, e.message) } } } ``` ### Exposed (SQL DSL) ```kotlin object Users : Table("users") { val id = varchar("id", 36) val name = varchar("name", 255) val email = varchar("email", 255).uniqueIndex() val role = enumerationByName<Role>("role", 20) override val primaryKey = PrimaryKey(id) } suspend fun findByRole(role: Role): List<User> = newSuspendedTransaction(Dispatchers.IO) { Users.selectAll().where { Users.role eq role }.map { it.toUser() } } ``` ### kotlinx.serialization ```kotlin @Serializable data class ApiResponse<T>(val data: T, val meta: Meta? = null) @Serializable data class Meta(val page: Int, val totalPages: Int, val totalItems: Long) ``` ### Koin (DI) ```kotlin val appModule = module { singleOf(::UserRepository) singleOf(::UserService) factoryOf(::CreateUserUseCase) } fun Application.configureKoin() { install(Koin) { modules(appModule) } } ``` --- ## Performance Tips ### Inline Functions ```kotlin inline fun <T> measureTimeAndReturn(block: () -> T): Pair<T, Duration> { val start = System.nanoTime() val result = block() return result to Duration.ofNanos(System.nanoTime() - start) } // crossinline: prevents non-local returns inline fun transaction(crossinline block: () -> Unit) { begin(); try { block() } catch (e: Exception) { rollback(); throw e }; commit() } ``` ### Value Classes ```kotlin @JvmInline value class Email(val value: String) { init { require(value.contains("@")) } } @JvmInline value class Meters(val value: Double) // Compiles to raw String/Double -- zero object allocation ``` ### Sequences vs Lists ```kotlin // Bad: 3 intermediate lists users.filter { it.isActive }.map { it.name }.take(10) // Good: lazy, single pass, stops after 10 users.asSequence().filter { it.isActive }.map { it.name }.take(10).toList() // Rule: sequences for 3+ chained ops on 1000+ elements ``` ### Coroutines ```kotlin suspend fun loadDashboard(userId: String): Dashboard = coroutineScope { val profile = async { userService.getProfile(userId) } val orders = async { orderService.getRecent(userId) } val notifs = async { notificationService.getUnread(userId) } Dashboard(profile.await(), orders.await(), notifs.await()) } fun observeOrders(): Flow<Order> =
عرض على GitHub
ملف SKILL.md هذا كبير جدا، لذلك يعرض SkillsMP القسم الاول فقط هنا. عرض على GitHub