一.先来聊聊异常
日常开发中难免会遇到对异常的处理,我们先来看一段代码:
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。