kotlin-control-flow
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseKotlin 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 subject.
whenkotlin
// 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 or an chain.
whenif找到代码中被分类的值。如果每个分支都针对同一个值进行判断,将该值设为的主题。
whenkotlin
// Replace repeated checks against `state` with a subject `when`.
val action = when (state) {
State.SignedOut -> Action.ShowSignIn
is State.SignedIn -> Action.ShowHome(state.user)
}如果没有单一主题,则保留无主题的或链。
whenif2. Pick the branch primitive
2. 选择分支原语
Use this decision table before editing:
| If the code has... | Use... |
|---|---|
| One value being classified | |
| Unrelated boolean conditions | Subjectless |
| A primary match plus an extra branch-local predicate | Guard condition |
| Invalid input before the main path | Early return, |
| A closed enum, Boolean, sealed type, or nullable closed type returning a value | Exhaustive |
| Open external input or a real fallback | Explicit |
在编辑前使用以下决策表:
| 如果代码包含... | 使用... |
|---|---|
| 单个被分类的值 | |
| 无关的布尔条件 | 无主题 |
| 主匹配条件加额外分支本地断言 | Guard condition |
| 主路径前的无效输入 | 提前返回、 |
| 封闭枚举、Boolean、密封类型或可空封闭类型返回值 | 穷尽性 |
| 开放外部输入或明确的 fallback | 显式 |
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 has a subject.
when - The branch has a primary condition (, enum entry, object, value, range, etc.).
is Type - 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 expression over a closed domain, handle every case explicitly. Do not add only to quiet the compiler.
whenelsekotlin
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 when the domain is open: strings from a server, integer status codes, unknown platform values, or a deliberate fallback/logging path.
else对于封闭域上的表达式,显式处理所有情况。不要仅为了让编译器静默而添加。
whenelsekotlin
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
}当域为开放域时使用:来自服务器的字符串、整数状态码、未知平台值,或刻意的回退/日志路径。
else5. 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 , , temporary mutable vars, or duplicated casts, keep the original shape or choose a smaller refactor.
as!!重构后,验证每个分支在使用窄化类型时仍可正常获取。如果重构强制使用、、临时可变变量或重复类型转换,则保留原有结构或选择更小范围的重构。
as!!Rewrite recipes
重构示例
Nested branch inside when
whenwhen
内的嵌套分支
whenWhen 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 null is one branch in a larger classification:
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)
}当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 expressions remain exhaustive without unnecessary
when.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 for open-world external input.
else - 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 - 将业务分支逻辑放在通用代码中,简化平台实现。