在Kotlin中处理异常

3,898 阅读3分钟

一.先来聊聊异常

日常开发中难免会遇到对异常的处理,我们先来看一段代码:

    private void handleCustomMessage(TIMMessage timMessage, TIMElem elem) {
        TIMCustomElem timCustomElem = (TIMCustomElem) elem;
        byte[] bytes = timCustomElem.getData();
        String data = new String(bytes);
        Log.e(TAG, "" + data);
        try {
            TXMessage message = new Gson().fromJson(data, TXMessage.class);
            if (message != null) {
                message.setSenderId(timMessage.getSender());
                String groupId = timMessage.getConversation().getPeer();
                message.setGroupId(groupId);
                for (int i = listeners.size() - 1; i > -1; i--) {
                    TXIMStateListener listener = listeners.get(i);
                    listener.onReceiveCustomMessage(message);
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

这是一段非常常见的Java异常捕获处理, 类似的优化你也一定见过。但是笔者还是想说下自己的见解

(一).糟糕的trycatch代码块带来阅读的断档

是的,还是上面的代码,你本来在阅读方法体中1到4行的代码,它们的排版策略和命名风格一致,你的注意力沉浸在逻辑之中,思考着bug是在哪一行产生的,这个时候,突然眼前出现了try关键词,突兀的提示你“这里有异常”。不仅这样还把之后的代码继续缩进,你的注意力会随着眼球的移动停那么一下,虽然你的思维能很快接上,但它依然打断了你。不仅这样,为了处理异常,你可能还会定义命名一些临时变量,额外的思考逻辑相应增加。

(二).不知道该如何处理的异常

在本文中处理数据库、流等资源以及语法中产生的异常,笔者暂时称之为语言异常;我们因为业务关系常常会自定义异常,并附带异常描述,当程序运行出现不符合业务逻辑的时候,我们会抛出这些自定义异常。暂时称之为逻辑异常。

当语言异常产生时,我们可能会像产品或者运营询问,这种场景应该怎么处理,但其实我们的业务是不关注语言异常的产生的。产品和运营也没有办法理解这些异常的产生,所以他们没有对这种异常的处理能力, 而开发者对这种异常的胡乱处理胡乱提示,也可能会给用户造成困扰。

而逻辑异常开发者可能又会不能进行及时处理。

上面的代码在catch住了异常后,并没有对异常作出相应的处理,实际上,我们可能真的不需要处理。真实场景中开发者调用这段代码只是想让程序执行应该的逻辑,达到我们想要的效果,如果这段代码发生异常不能正确执行,那就不执行好了。

所以,在定义异常时需要考虑到调用者需不需要关注异常。

二.Kotlin中的异常

在Kotlin中,我们也可以像Java一样使用trycatch来捕获异常,但是如果仅仅是这样,就没有本篇文章的存在了,利用Kotlin标准库中的扩展函数,我们可以很方便轻松的写出trycatch代码:

kotlin.runCatching {  }

而我们只需要将抛出异常的代码写在里面就可以优雅处理异常,实际内部帮我们做了trycatch处理:

/**
 * Calls the specified function [block] and returns its encapsulated result if invocation was successful,
 * catching any [Throwable] exception that was thrown from the [block] function execution and encapsulating it as a failure.
 */
@InlineOnly
@SinceKotlin("1.3")
public inline fun <R> runCatching(block: () -> R): Result<R> {
    return try {
        Result.success(block())
    } catch (e: Throwable) {
        Result.failure(e)
    }
}

上面的写法是我们完全不需要关注异常,如果我们关注异常的处理结果,但仅仅是true或false,请注意 该方法会返回一个包装类Result,如果代码执行无误,返回success,发生异常返回fail。所以,我们也可以这样写:

    fun testTry(){
        val result = kotlin.runCatching { doSomeThings() }.isFailure
        val result1 = kotlin.runCatching { doSomeThings() }.isSuccess
    }
    
    @Throws
    fun doSomeThings():Exception{
        return NullPointerException()
    }

如果我们关注的结果是null或non-null:

val result3 = kotlin.runCatching { doSomeThings() }.getOrNull()

如果我们关注的结果是exception本身

 val result3 = kotlin.runCatching { doSomeThings() }.getOrThrow()
 val result4 = kotlin.runCatching { doSomeThings() }.getOrElse {  }

如果我们关注的结果是exception本身和出错的逻辑代码以及释放资源,那就需要老老实实写trycatch。