凡事有交代 ------ 接到任务时,主动明确任务目标、背景、利益相关方、交付物、时间节点。
件件有着落 ------ 执行任务过程中,在里程碑节点及时同步,做好预期管理。
事事有回音 ------ 任务完成后,有复盘,有汇报,有总结。
这篇文章记录了近期积累的一些零散知识点,临近元旦、春节,这个时期是一年里工作相对轻松的阶段,同时也是复盘总结过去一年的经验、以及做好明年目标规划的关键节点。利用好,别虚度。
你真的需要使用 let 函数吗?

在 Kotlin 中,let、apply、also、run 等等函数,是降低代码行数、提升可读性的利器,然而它们并不是万能的,如果使用不当,会带来相反的效果。
let 的一般用法
kotlin
// 如果元素非空,则对其进行绘制
element?.let { render(it) }
// 非 let 写法
if (element != null) {
render(element)
}
可以看到,let 函数将3行代码精简成为1行,精简了 66% 的代码。它适用于以下场景:
- 单次调用该对象
- lambda 行数很少
- 没有后续依赖
与之相反的条件,就是不适用于 let 的场景。
let 不适用的场景
kotlin
// 灾难现场1:行数多、有后续依赖
element?.let { e ->
config?.let { c ->
adapter?.let { a ->
renderer?.let { r ->
r.render(e, c, a)
}
}
}
}
// 灾难现场2:超过1行使用 let 对象
element?.let {
doSomething(it)
doAnotherThing(it)
doMore(it)
}
以上已经是 Kotlin 的反模式了,这样的代码不断向右偏移(rightward drift),难读并且难改,在进行 Code Review 时一定要识别出来。
替代方案 return fast
在函数的开头,检查变量条件,如果不符合立刻 return,可以在需要的位置增加日志打印,以便追溯问题。
kotlin
fun updateView() {
val e = element ?: return
val c = config ?: return
val a = adapter ?: return
render(e, c, a)
}
为什么这是好代码?
- 没有嵌套
- 控制流清晰
- 可调试(每一行都能打断点)
- 非常符合 "fail fast" 原则
更进一步,如果 e、c、a 这三个对象经常一起判空,可以将它们本质上是一个状态,推荐对它们进行状态聚合,有利于长期使用。
kotlin
// 状态聚合
data class RenderState(
val element: Element,
val config: Config,
val adapter: Adapter
)
// 只有一个变量
val state: RenderState? = ...
// 使用时判断该变量
fun updateView() {
val s = state ?: return
render(s)
}
此外,如果发现,代码里多处进行判空操作,则可以利用 DSL,将判空操作提取出 withReadyElement 函数,
kotlin
// 提取 inline 函数,集中进行可空性处理
private inline fun withReadyElement(block: (Element) -> Unit) {
val e = element ?: return
block(e)
}
// 调用点更加美观干净
fun updateView() {
withReadyElement {
render(it)
}
}
inline fun 用于消除 Lambda 对象额外开销

inline fun 是"为 Lambda 买单的工具",用于消除 Lambda 参数引起的对象创建&间接调用开销,同样地,它也不是"银弹",如果使用不当,反而会引起性能下降。
普通 Lambda 函数与 inline Lambda 函数
普通 Lambda 函数
kotlin
fun doSomething(block: () -> Unit) {
block()
}
系统在编译这段代码时,实际上发生了:
- 创建一个
Function0对象(表示0个参数、即无参函数) - 捕获外部变量,生成闭包
- 通过
invoke()间接调用
例如,我们调用 doSomething { println("hello") } 后,编译器实际上创建了一个类,并调用其 invoke 函数。
java
class DoSomething$1 implements Function0<Unit> {
@Override
public Unit invoke() {
System.out.println("hello")
return Unit.INSTANCE;
}
}
inline Lambda 函数
kotlin
inline fun doSomething(block: () -> Unit) {
block()
}
// 编译后直接展开
println("hello")
标准库的 inline 用法
在 Kotlin 标准库里,这些函数都是 inline 实现
letapplyrunwithusesynchronized
例如:
kotlin
inline fun <T> T.apply(block: T.() -> Unit): T
inline 的负面作用
由于 inline 本质上采用了 复制-粘贴 的模式,因此它会在每个调用点复制一份函数代码,这会造成 APK / DEX 膨胀,对 Android 来说,更会引起 方法数增加,甚至突破 64K 限制。
inline 的应用场景
- 参数是
lambda类型 lambda非常短- 该函数 90% 以上是 lambda 调用
- 应用在循环 / UI / 热路径中
- inline 收益 > 代码膨胀成本
DSL,把握好"提升可读性"与"滥用"之间的界限

DSL(Domain-Specific Language,领域特定语言),是一种 只为某个特定领域服务 的语言,目标不是"什么都能干",而是把这个领域的表达做到最清晰、最省心、最不容易写错。
如何理解"领域"呢?在我看来,"领域"是指特定的场景、语境、上下文,它具有一定的重复性,因此通过提炼总结 DSL,能够降低重复使用的成本。"领域"可以是技术上的,也可以是业务场景里的。
GPL 与 DSL
与 DSL 相对的概念,是 通用语言(GPL),Java / Kotlin / C++ 都属于此类,它们功能强大且全面,但随之而来的,是写法严谨甚至冗长,无法做到针对特定场景(领域)进行简化。
DSL 则专注于解决一个特定领域的问题,为此进行了特定优化,短、平、快、准。
GPL、DSL 都可以解决问题,重点在于选择使用哪一个。
DSL 的两大类型:外部 DSL
外部 DSL 具备 独立的语法和解析器 ,更像一门新的编程语言,因此其表达能力很强。同时,由于依赖于特定解析器,故定制成本高。
常见的外部 DSL 有:
- SQL
- 正则表达式
- Gradle Groovy/Kotlin DSL(build.gradle)
- HTML / CSS
DSL 的两大类型:内部 DSL
内部 DSL 在 Kotlin 语言中是非常常见的,例如前文的 let、apply、also 等均属于此类。可以通过 Kotlin 语法特性,使代码"看起来像新的语言"。Kotlin 的标准库中,有很多 API 本身就是 DSL。举两个常见的例子:
例1,Lambda with Receiver
定义 user DSL,代替 set 函数。
kotlin
// 使用 Lambda,域内对象为 User
fun user(block: User.() -> Unit) {
val u = User()
u.block()
}
// 使用 DSL
user {
name = "Lei"
}
// 不使用 DSL
user.setName("Lei")
例2, infix 函数
可以提升可读性,例如定义二元运算符 eq,比较 String 对象:
kotlin
// 声明 DSL
infix fun String.eq(value: String)
// 使用 DSL
"status" eq "success"
Gradle Kotlin、Jetpack Compose、HTML 都是典型的 DSL,UI结构=代码结构,起到"看代码知UI"的作用。
DSL 不是炫技工具,而是把业务规则提取为代码的手段
| 值得设计 DSL | 不该用DSL |
|---|---|
| ✅是不是反复写相同结构? ✅业务规则是否固定? ✅现在的代码是否"看不出业务语义"? ✅错误是不是经常发生在"用法不对"? | ❌一次性逻辑 ❌需求不稳定 ❌团队 Kotlin 水平参差 ❌DSL 设计者不在一线维护 |