<< All versions
Skill v1.0.0
currentAutomated scan100/100lugassawan/swe-workbench/language-kotlin
──Details
PublishedSeptember 27, 2026 at 08:06 PM
Content Hashsha256:45c99f8d2d2b32e5...
Git SHAec10c6e51f09
──Files
Files (1 file, 5.1 KB)
SKILL.md5.1 KBactive
SKILL.md · 130 lines · 5.1 KB
version: "1.0.0" name: language-kotlin description: Kotlin idioms — null safety, sealed interfaces, scope functions, and Flow. Auto-load when working with .kt files, build.gradle.kts, or when the user mentions Kotlin, coroutines, StateFlow, or Kotlin DSL.
Kotlin
Null safety
?makes nullability explicit in the type —String?vsString.- Safe-call
?.returns null instead of throwing. Elvis?:provides a default. - Never use `!!` in production code — it is a promise you will never break that can't be verified.
- Jackson does not null-check collection elements even with
jackson-module-kotlin— a declared
List<Content> can hold nulls from {"content":[null]}. Use filterNotNull() (not filter { it != null }, which leaves the type as List<Content?>), or @JsonSetter(contentNulls = Nulls.SKIP) at the boundary.
kotlin
val length = name?.trim()?.length ?: 0user?.email?.let { send(it) } // null-guard + scopingval ids = payload.content.filterNotNull().map { it.id }
Data classes and sealed interfaces
data classfor value containers: auto-generatesequals,hashCode,toString,copy, and destructuring.sealed interfacecloses a hierarchy and enables exhaustivewhenwithout anelsebranch.
kotlin
sealed interface Result<out T>data class Success<T>(val value: T) : Result<T>data class Failure(val error: Throwable) : Result<Nothing>fun handle(r: Result<User>) = when (r) {is Success -> show(r.value)is Failure -> log(r.error)}
Coroutines — structured concurrency
suspendfunctions must be called from a coroutine or anothersuspendfunction.- Use
coroutineScope { }for fan-out — child coroutines are cancelled if one fails. withContext(Dispatchers.IO)for blocking IO; never block insideDispatchers.Default.
kotlin
suspend fun fetchDashboard(id: String): Dashboard = coroutineScope {val user = async { fetchUser(id) }val orders = async { fetchOrders(id) }Dashboard(user.await(), orders.await())}
launchis fire-and-forget;asyncreturns aDeferred<T>.- Prefer
coroutineScopeoverGlobalScope— global coroutines outlive their logical parent.
Result and error handling
runCatching { }wraps a block inResult<T>without try/catch noise.- Chain with
map,recover,onSuccess,onFailure. - Exceptions for genuinely exceptional paths;
Resultfor recoverable failures.
kotlin
val result = runCatching { parse(input) }.map { it.validate() }.recover { _ -> ParsedValue.empty() } // recover: failure → success fallback.onFailure { e -> log.warn("parse failed", e) }
Scope functions — pick the right one
| Function | Receiver as | Returns | Use when | |
|---|---|---|---|---|
let | it | lambda result | null-guard, transform, introduce local name | |
apply | this | receiver | builder / configure-and-return | |
run | this | lambda result | scope + transform | |
also | it | receiver | side-effect (logging) without changing the chain | |
with | this | lambda result | operations on a non-nullable object without extension |
Do not nest scope functions more than one level — it destroys readability.
Extension functions
- Additive utilities on existing types. Place in the package that uses them, not in a companion.
- Do not shadow members — extension functions lose to member functions at call sites.
kotlin
fun String.toSlug() = lowercase().replace(Regex("[^a-z0-9]+"), "-").trim('-')
Flow — async sequences
Flow<T>is cold (lazy); it does not run until collected.StateFlowfor observable mutable state;SharedFlowfor events.map,filter,flatMapLatest,debounce— use operators over manual loops.
kotlin
val prices: Flow<BigDecimal> = priceRepo.watch(symbol).filter { it > BigDecimal.ZERO }.distinctUntilChanged()
Doc comments
- KDoc — lead with a single-sentence summary fragment; add
@param/@returnonly when they carry information beyond the type.
kotlin
/** Returns the cached price, refreshing it if older than [maxAge]. */fun priceFor(symbol: String, maxAge: Duration): BigDecimal
Tooling
- Imports/Format:
./gradlew ktlintFormat/ktlint -F(standalone binary) - Lint:
detekt/./gradlew detekt - Test:
./gradlew test(see Testing below)
Testing
- JUnit 5 or Kotest for test structure; MockK for Kotlin-friendly mocking.
runTest { }fromkotlinx-coroutines-testfor coroutine tests — no manual dispatchers.
kotlin
@Testfun `fetch returns cached value`() = runTest {val repo = FakeRepo(listOf(user))assertThat(repo.find(user.id)).isEqualTo(user)}
Avoid
!!— if you know it is non-null, prove it with arequireNotNullor type the field as non-nullable.- Translating Java idioms (
if (x != null)→ use?.and?:). - Nesting scope functions more than one level deep.
lateinit varoutside dependency injection — preferby lazyor constructor injection.GlobalScope— ties coroutines to the process lifetime instead of a logical scope.- Trusting a declared non-null element type on a Jackson-deserialized collection.