17、扩展函数与高阶函数在日志与调试中的应用

日志和调试,说白了就是开发者的眼睛。我做了这么多年Android,最怕的就是线上出问题,日志却一片空白。今天咱们聊聊怎么用Kotlin的扩展函数和高阶函数,把这双眼睛擦得更亮。

一、Log的扩展函数:告别重复代码

你想想看,每次写日志都要敲Log.d(TAG, "xxx"),是不是很烦?我习惯给Log类写个扩展,让调用更简洁。

// 给Log类添加扩展函数
fun Log.d(tag: String, message: () -> String) {
    if (Log.isLoggable(tag, Log.DEBUG)) {
        Log.d(tag, message())
    }
}

// 使用示例
Log.d("MainActivity") { "用户点击了按钮,当前状态:$state" }

这里有个小技巧:用message: () -> String代替直接传字符串。好处是——只有当日志级别开启时,才会执行lambda里的字符串拼接。我在项目中遇到过日志拼接导致性能问题的坑,用这个方式就完美避开了。

核心思路:扩展函数让调用更简洁,高阶函数让计算更懒。

二、调试信息的DSL输出:结构化日志

普通日志是一行行的字符串,看多了眼花。我更喜欢用DSL把调试信息组织成结构化的格式。举个例子:

// 调试信息DSL
class DebugInfo {
    private val sections = mutableListOf<String>()
    
    fun section(title: String, block: SectionBuilder.() -> Unit) {
        val builder = SectionBuilder()
        builder.block()
        sections.add("[$title] ${builder.build()}")
    }
    
    override fun toString() = sections.joinToString("\n")
}

class SectionBuilder {
    private val items = mutableListOf<String>()
    
    infix fun String.`=`(value: Any?) {
        items.add("$this = $value")
    }
    
    fun build() = items.joinToString(", ")
}

// 使用
fun logDebugInfo(block: DebugInfo.() -> Unit) {
    val info = DebugInfo()
    info.block()
    Log.d("Debug", info.toString())
}

// 调用
logDebugInfo {
    section("网络请求") {
        "url" `=` "https://api.example.com"
        "method" `=` "GET"
        "耗时" `=` "230ms"
    }
    section("用户信息") {
        "id" `=` 1001
        "name" `=` "张三"
    }
}

输出效果:

[网络请求] url = https://api.example.com, method = GET, 耗时 = 230ms
[用户信息] id = 1001, name = 张三

嗯,这里要注意:infix关键字让=看起来像赋值操作,但实际上是个函数调用。我第一次用这个写法时也觉得怪,但用顺手后真香。

我的习惯:调试DSL只用在开发阶段,发布版本要关掉。可以加个BuildConfig.DEBUG判断。

三、性能监控的简化:一行代码搞定

性能监控最怕什么?侵入业务代码。我曾经为了监控一个函数的执行时间,在函数前后各加一行System.currentTimeMillis(),结果代码变得又臭又长。

用高阶函数封装一下:

// 性能监控工具
inline fun <T> measureTime(
    tag: String,
    block: () -> T
): T {
    val start = System.nanoTime()
    val result = block()
    val duration = (System.nanoTime() - start) / 1_000_000
    Log.d(tag, "执行耗时:${duration}ms")
    return result
}

// 使用
val data = measureTime("数据库查询") {
    database.query("SELECT * FROM users")
}

你看,业务逻辑完全没被污染。而且inline关键字让lambda在编译时展开,没有额外开销。

避坑指南:我曾经在循环里用measureTime,结果日志刷屏。建议加上采样逻辑,比如每100次才输出一次。

四、知识体系总览

下面这张图总结了本章的核心逻辑:

扩展函数与高阶函数在日志调试中的应用 Log扩展函数 • 简化调用:Log.d(tag){ msg } • 懒加载:lambda延迟求值 • 级别过滤:isLoggable检查 • 避免字符串拼接开销 → 减少重复代码 → 提升运行时性能 调试DSL • 结构化输出 • 可读性强 • infix关键字简化语法 • 分section组织 → 日志一目了然 → 便于排查问题 性能监控 • measureTime封装 • inline消除开销 • 不侵入业务代码 • 支持泛型返回值 → 一行代码监控 → 精准定位瓶颈 核心思想:用扩展函数简化调用,用高阶函数封装逻辑,让调试代码更优雅

五、实战组合:一个完整的调试工具

把上面三个技巧组合起来,就是一个轻量级的调试框架:

// 组合使用
class DebugTool {
    companion object {
        inline fun <T> trace(
            tag: String,
            section: String = "",
            block: DebugInfo.() -> T
        ): T {
            val info = DebugInfo()
            val start = System.nanoTime()
            val result = info.block()
            val duration = (System.nanoTime() - start) / 1_000_000
            
            if (section.isNotEmpty()) {
                info.section("性能") {
                    "耗时" `=` "${duration}ms"
                }
            }
            
            Log.d(tag, info.toString())
            return result
        }
    }
}

// 调用
val user = DebugTool.trace("UserModule", section = "加载用户") {
    section("数据源") {
        "来源" `=` "本地缓存"
        "key" `=` "user_1001"
    }
    loadUserFromCache(1001)
}

这个工具我一直在用。它把日志、调试信息、性能监控三合一,而且对业务代码零侵入。你想想看,如果每个函数都这么写,排查问题是不是快多了?

总结一下:扩展函数让调用更简洁,高阶函数让逻辑更灵活。两者结合,日志调试不再是苦差事。

公众号:蓝海资料掘金营,微信deep3321