Agent Skillschrisbanes/skills › kotlin-control-flow

kotlin-control-flow

GitHub

用于编写或审查Kotlin分支与控制流代码,通过规范化when表达式、使用守卫条件、保持智能转换及确保封闭域穷尽性,将复杂if/else链重构为清晰、可验证且无冗余的分支逻辑。

skills/kotlin-control-flow/SKILL.md chrisbanes/skills

Trigger Scenarios

编写或审查Kotlin分支代码 处理sealed类型穷尽性检查 优化复杂的if/else链 使用智能转换和空安全分支

Install

npx skills add chrisbanes/skills --skill kotlin-control-flow -g -y
More Options

Use without installing

npx skills use chrisbanes/skills@kotlin-control-flow

指定 Agent (Claude Code)

npx skills add chrisbanes/skills --skill kotlin-control-flow -a claude-code -g -y

安装 repo 全部 skill

npx skills add chrisbanes/skills --all -g -y

预览 repo 内 skill

npx skills add chrisbanes/skills --list

SKILL.md

Frontmatter
{
    "name": "kotlin-control-flow",
    "description": "Use when writing or reviewing Kotlin branching and control flow: when expressions, guard conditions, sealed type exhaustiveness, smart casts, nullable branching, early returns, or replacing complex if\/else chains."
}

Kotlin control flow

Purpose

Use this skill to write or review the shape of Kotlin branching code. Treat it as a refactoring procedure, not as a style preference.

The target state is simple: the classified value is obvious, branch-local predicates stay with their branch, smart casts remain usable, and the compiler proves exhaustiveness for closed domains.

Procedure

Apply these checks in order.

1. Name the subject

Find the value the code is classifying. If every branch asks a question about the same value, make that value the when subject.

// Replace repeated checks against `state` with a subject `when`.
val action = when (state) {
    State.SignedOut -> Action.ShowSignIn
    is State.SignedIn -> Action.ShowHome(state.user)
}

If there is no single subject, keep a subjectless when or an if chain.

2. Pick the branch primitive

Use this decision table before editing:

If the code has... Use...
One value being classified when (subject)
Unrelated boolean conditions Subjectless when or if/else
A primary match plus an extra branch-local predicate Guard condition
Invalid input before the main path Early return, require, or check
A closed enum, Boolean, sealed type, or nullable closed type returning a value Exhaustive when expression
Open external input or a real fallback Explicit else

3. Move branch-local predicates into guard conditions

When a branch first matches a type/value and then checks an extra predicate, use a guard condition:

return when (event) {
    is Event.Message if event.isUnread -> Row.Highlighted(event.message)
    is Event.Message -> Row.Normal(event.message)
    Event.Empty -> Row.Empty
}

Apply guards only when all of these are true:

  • The when has a subject.
  • The branch has a primary condition (is Type, enum entry, object, value, range, etc.).
  • The extra condition belongs only to that branch.
  • A later branch still handles the same primary condition, or the expression remains exhaustive some other way.

Put guarded branches before their unguarded fallback for the same primary condition.

4. Preserve exhaustiveness

For a when expression over a closed domain, handle every case explicitly. Do not add else only to quiet the compiler.

val action = when (state) {
    SessionState.SignedOut -> Action.ShowSignIn
    is SessionState.SignedIn -> Action.ShowHome(state.user)
    is SessionState.Expired if state.canRefresh -> Action.Refresh
    is SessionState.Expired -> Action.ShowSignIn
}

Use else when the domain is open: strings from a server, integer status codes, unknown platform values, or a deliberate fallback/logging path.

5. Split unsupported guarded branches

Guard conditions do not apply to comma-separated branch conditions. If only one case needs an extra predicate, split the branch:

when (status) {
    Status.Pending if canRetry -> retry()
    Status.Pending -> showPending()
    Status.Queued -> showQueued()
}

6. Flatten invalid preconditions

Use early returns when they remove nullable or invalid state from the main path:

fun render(user: User?): UiModel {
    user ?: return UiModel.SignedOut

    return UiModel.SignedIn(
        name = user.name,
        avatar = user.avatar,
    )
}

Do not flatten if nesting is carrying cleanup, transaction, or error-handling structure.

7. Check smart casts

After reshaping, verify that every branch still has the narrowed type available where it is used. If the rewrite forces as, !!, temporary mutable vars, or duplicated casts, keep the original shape or choose a smaller refactor.

Rewrite recipes

Nested branch inside when

When the nested branch only refines one primary case, convert it to guarded branches:

// Before
return when (event) {
    is Event.Message -> {
        if (event.isUnread) Row.Highlighted(event.message) else Row.Normal(event.message)
    }
    Event.Empty -> Row.Empty
}

// After
return when (event) {
    is Event.Message if event.isUnread -> Row.Highlighted(event.message)
    is Event.Message -> Row.Normal(event.message)
    Event.Empty -> Row.Empty
}

Repeated checks against one value

When every condition classifies the same value, make it the subject:

// Before
return when {
    result is Result.Success -> Ui.Success(result.value)
    result is Result.Failure && result.canRetry -> Ui.Retry(result.error)
    result is Result.Failure -> Ui.Error(result.error)
    else -> Ui.Loading
}

// After
return when (result) {
    is Result.Success -> Ui.Success(result.value)
    is Result.Failure if result.canRetry -> Ui.Retry(result.error)
    is Result.Failure -> Ui.Error(result.error)
    Result.Loading -> Ui.Loading
}

Null as one case among several

Use when (value) when null is one branch in a larger classification:

return when (val selected = selection) {
    null -> SelectionUi.None
    is Selection.Single if selected.item.isArchived -> SelectionUi.Archived(selected.item)
    is Selection.Single -> SelectionUi.Active(selected.item)
    is Selection.Multiple -> SelectionUi.Count(selected.items.size)
}

Review checklist

Before finishing a control-flow change, verify:

  • The code has one obvious subject, or intentionally has none.
  • Guarded branches come before the matching unguarded branch.
  • Comma-separated branches do not use guard conditions.
  • Closed-domain when expressions remain exhaustive without unnecessary else.
  • Open-domain fallbacks are still explicit.
  • Smart casts still work without as, !!, or duplicated casts.
  • The new shape is easier to scan than the old shape.

When NOT to apply

  • Do not introduce guard conditions if the project Kotlin version does not support them.
  • Do not turn unrelated boolean checks into an awkward subject when.
  • Do not remove a deliberate else for open-world external input.
  • Do not flatten code if it makes cleanup, transaction boundaries, or error handling less obvious.

Related

Version History

  • 2026.7.21 Current 2026-07-24 12:25

Same Skill Collection

skills/compose-animations/SKILL.md
skills/compose-focus-navigation/SKILL.md
skills/compose-modifier-and-layout-style/SKILL.md
skills/compose-recomposition-performance/SKILL.md
skills/compose-side-effects/SKILL.md
skills/compose-slot-api-pattern/SKILL.md
skills/compose-stability-diagnostics/SKILL.md
skills/compose-state-authoring/SKILL.md
skills/compose-state-deferred-reads/SKILL.md
skills/compose-state-hoisting/SKILL.md
skills/compose-state-holder-ui-split/SKILL.md
skills/compose-ui-testing-patterns/SKILL.md
skills/implement-issue/SKILL.md
skills/kotlin-coroutines-structured-concurrency/SKILL.md
skills/kotlin-flow-state-event-modeling/SKILL.md
skills/kotlin-functions/SKILL.md
skills/kotlin-multiplatform-expect-actual/SKILL.md
skills/kotlin-types-value-class/SKILL.md
skills/shepherd/SKILL.md
skills/using-chrisbanes-skills/SKILL.md

Metadata

Files
0
Version
2026.7.21
Hash
dc5d600f
Indexed
2026-07-24 12:25

Accueil - Wiki
Copyright © 2011-2026 iteam. Current version is 2.155.2. UTC+08:00, 2026-08-07 17:19
浙ICP备14020137号-1 $Carte des visiteurs$