<< All versions

Skill v1.0.0

currentAutomated scan100/100
lugassawan/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? vs String.
  • 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 ?: 0
user?.email?.let { send(it) } // null-guard + scoping
val ids = payload.content.filterNotNull().map { it.id }

Data classes and sealed interfaces

  • data class for value containers: auto-generates equals, hashCode, toString, copy, and destructuring.
  • sealed interface closes a hierarchy and enables exhaustive when without an else branch.
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

  • suspend functions must be called from a coroutine or another suspend function.
  • Use coroutineScope { } for fan-out — child coroutines are cancelled if one fails.
  • withContext(Dispatchers.IO) for blocking IO; never block inside Dispatchers.Default.
kotlin
suspend fun fetchDashboard(id: String): Dashboard = coroutineScope {
val user = async { fetchUser(id) }
val orders = async { fetchOrders(id) }
Dashboard(user.await(), orders.await())
}
  • launch is fire-and-forget; async returns a Deferred<T>.
  • Prefer coroutineScope over GlobalScope — global coroutines outlive their logical parent.

Result and error handling

  • runCatching { } wraps a block in Result<T> without try/catch noise.
  • Chain with map, recover, onSuccess, onFailure.
  • Exceptions for genuinely exceptional paths; Result for 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

FunctionReceiver asReturnsUse when
letitlambda resultnull-guard, transform, introduce local name
applythisreceiverbuilder / configure-and-return
runthislambda resultscope + transform
alsoitreceiverside-effect (logging) without changing the chain
withthislambda resultoperations 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.
  • StateFlow for observable mutable state; SharedFlow for 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/@return only 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 { } from kotlinx-coroutines-test for coroutine tests — no manual dispatchers.
kotlin
@Test
fun `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 a requireNotNull or type the field as non-nullable.
  • Translating Java idioms (if (x != null) → use ?. and ?:).
  • Nesting scope functions more than one level deep.
  • lateinit var outside dependency injection — prefer by lazy or 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.
All versions