kotlin-control-flow

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Kotlin control flow

Kotlin控制流

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.
本技能适用于编写或评审Kotlin分支代码的结构。将其视为重构流程,而非风格偏好。
目标状态简洁清晰:被分类的值一目了然,分支本地断言与对应分支绑定,smart casts保持可用,编译器可验证封闭域的穷尽性。

Procedure

流程

Apply these checks in order.
按顺序应用以下检查步骤。

1. Name the subject

1. 确定主题

Find the value the code is classifying. If every branch asks a question about the same value, make that value the
when
subject.
kotlin
// 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.
找到代码中被分类的值。如果每个分支都针对同一个值进行判断,将该值设为
when
的主题。
kotlin
// Replace repeated checks against `state` with a subject `when`.
val action = when (state) {
    State.SignedOut -> Action.ShowSignIn
    is State.SignedIn -> Action.ShowHome(state.user)
}
如果没有单一主题,则保留无主题的
when
if
链。

2. Pick the branch primitive

2. 选择分支原语

Use this decision table before editing:
If the code has...Use...
One value being classified
when (subject)
Unrelated boolean conditionsSubjectless
when
or
if
/
else
A primary match plus an extra branch-local predicateGuard condition
Invalid input before the main pathEarly return,
require
, or
check
A closed enum, Boolean, sealed type, or nullable closed type returning a valueExhaustive
when
expression
Open external input or a real fallbackExplicit
else
在编辑前使用以下决策表:
如果代码包含...使用...
单个被分类的值
when (subject)
无关的布尔条件无主题
when
if
/
else
主匹配条件加额外分支本地断言Guard condition
主路径前的无效输入提前返回、
require
check
封闭枚举、Boolean、密封类型或可空封闭类型返回值穷尽性
when
表达式
开放外部输入或明确的 fallback显式
else

3. Move branch-local predicates into guard conditions

3. 将分支本地断言移至guard conditions

When a branch first matches a type/value and then checks an extra predicate, use a guard condition:
kotlin
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.
当分支先匹配类型/值,再检查额外断言时,使用guard condition:
kotlin
return when (event) {
    is Event.Message if event.isUnread -> Row.Highlighted(event.message)
    is Event.Message -> Row.Normal(event.message)
    Event.Empty -> Row.Empty
}
仅当满足以下所有条件时才应用guard:
  • when
    有明确主题。
  • 分支有主条件(
    is Type
    、枚举项、对象、值、范围等)。
  • 额外断言仅属于该分支。
  • 后续分支仍处理相同主条件,或表达式通过其他方式保持穷尽性。
将带guard的分支放在同一主条件对应的无guard分支之前。

4. Preserve exhaustiveness

4. 保留穷尽性

For a
when
expression over a closed domain, handle every case explicitly. Do not add
else
only to quiet the compiler.
kotlin
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.
对于封闭域上的
when
表达式,显式处理所有情况。不要仅为了让编译器静默而添加
else
kotlin
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
}
当域为开放域时使用
else
:来自服务器的字符串、整数状态码、未知平台值,或刻意的回退/日志路径。

5. Split unsupported guarded branches

5. 拆分不支持的带guard分支

Guard conditions do not apply to comma-separated branch conditions. If only one case needs an extra predicate, split the branch:
kotlin
when (status) {
    Status.Pending if canRetry -> retry()
    Status.Pending -> showPending()
    Status.Queued -> showQueued()
}
Guard condition不适用于逗号分隔的分支条件。如果仅单个 case 需要额外断言,拆分分支:
kotlin
when (status) {
    Status.Pending if canRetry -> retry()
    Status.Pending -> showPending()
    Status.Queued -> showQueued()
}

6. Flatten invalid preconditions

6. 扁平化无效前置条件

Use early returns when they remove nullable or invalid state from the main path:
kotlin
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.
当提前返回能从主路径中移除可空或无效状态时,使用提前返回:
kotlin
fun render(user: User?): UiModel {
    user ?: return UiModel.SignedOut

    return UiModel.SignedIn(
        name = user.name,
        avatar = user.avatar,
    )
}
如果嵌套结构承担清理、事务或错误处理逻辑,则不要扁平化。

7. Check smart casts

7. 检查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.
重构后,验证每个分支在使用窄化类型时仍可正常获取。如果重构强制使用
as
!!
、临时可变变量或重复类型转换,则保留原有结构或选择更小范围的重构。

Rewrite recipes

重构示例

Nested branch inside
when

when
内的嵌套分支

When the nested branch only refines one primary case, convert it to guarded branches:
kotlin
// 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
}
当嵌套分支仅细化一个主case时,将其转换为带guard的分支:
kotlin
// 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:
kotlin
// 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
}
当所有条件都针对同一个值进行分类时,将其设为主题:
kotlin
// 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

Null作为多个case之一

Use
when (value)
when null is one branch in a larger classification:
kotlin
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)
}
当null是更大分类中的一个分支时,使用
when (value)
kotlin
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.
完成控制流变更前,验证以下内容:
  • 代码有明确的单一主题,或故意不设置主题。
  • 带guard的分支位于对应的无guard分支之前。
  • 逗号分隔的分支未使用guard condition。
  • 封闭域的
    when
    表达式无需多余
    else
    即可保持穷尽性。
  • 开放域的回退仍为显式声明。
  • Smart casts无需
    as
    !!
    或重复类型转换即可正常工作。
  • 新结构比原有结构更易阅读。

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.
  • 如果项目Kotlin版本不支持guard condition,请勿引入。
  • 请勿将无关的布尔检查强行转换为生硬的主题
    when
  • 请勿移除针对开放外部输入的刻意
    else
    分支。
  • 如果扁平化会使清理、事务边界或错误处理逻辑更模糊,则不要扁平化代码。

Related

相关链接

  • Kotlin concurrency and Flow - flow state and event primitive choices.
  • Kotlin API design - keeping business branching in common code and platform actuals thin.
  • Kotlin concurrency and Flow - 流状态与事件原语选择。
  • Kotlin API design - 将业务分支逻辑放在通用代码中,简化平台实现。