代码的可读性是编程时的至关重要的一环。实现可读性的一个重要部分在于你如何命名对象、类和变量。它应该是直接明了的,即使这意味着使用额外的字词。别担心,向变量名添加额外的信息不会在你的代码库中造成混乱。
我们都知道,计算机只能理解两件事:1和0,或者说开和关。同样,我们的人类思维也很相似。通过将某物命名为真或假,我们更容易理解它。这有助于我们的大脑更容易地记住或处理那些信息。例如,回想一下我们童年时期在考试中的简单真假题。读一下陈述,给出答案。
让我们探索一下Kotlin的Result类如何应用同样的理论,使代码更容易阅读。
在大多数情况下,当需要检查代码中的操作是否成功时,使用这个函数:
val number = 10
val result = if (number > 5) {
"Number is greater than 5"
} else {
"Number is not greater than 5"
}
这是一个非常基本的操作示例,用于检查条件是否为真或假。然而,在这种情况下,已经知道了else部分,这给了我们为其他情况编写代码。它为用户或开发者提供了信息,说明这将是两种情况的结果。
如果遇到稍微复杂一些的条件,需要使用其他工具来处理情况,比如try-catch块呢?我们不会深入探讨tryCatch块,但会看到一个try-catch块是如何工作的。
val input = "abc"
val number = try {
input.toInt() // Try to parse the string to an integer
} catch (e: NumberFormatException) {
null // If an exception occurs, assign null to 'number'
}
val result = if (number != null) {
"Parsed number: $number"
} else {
"Could not parse input as a number"
}
println(result)
探索一下上面的代码:有一个输入,它是一个字符串,我们试图将它转换成一个整数,这是不可能的。所以,它会抛出一个异常,将会在代码的catch块中捕获这个异常。然后,将根据这个异常显示一个消息。本质上,try-catch块被用来在进行操作时处理异常,这样我们就可以按照期望的方式管理它们。
现在已经理解了何时以及如何使用try-catch块,我认为每次执行一个操作时都写一个try-catch块对于可读性来说并不好。它不符合不要重复自己的原则。
让我们用一个真实的例子来改进上面的代码。考虑一下在我们的代码中常常在哪里使用try-catch块。是的,你猜对了——当执行API调用时。对于每一个API调用,都使用一个try-catch块。
import io.ktor.client.*
import io.ktor.client.features.json.*
import io.ktor.client.features.json.serializer.*
import io.ktor.client.request.*
import io.ktor.http.*
val httpClient = HttpClient {
install(JsonFeature) {
serializer = KotlinxSerializer()
}
}
suspend fun performApiCall(): String? {
return try {
val response=httpClient.get("https://your-api-url.com/endpoint").body<String>()
response // Return response if successful
} catch (e: Exception) {
e.printStackTrace() // Handle or log the exception here
null // Return null in case of an error
}
}
如你所见,在这里使用的是KtorClient,但无论是Retrofit还是其他类似的工具,概念都是一样的。
在上面的代码片段中,创建了一个Ktor客户端,并使用GET操作在try-catch块中进行了API调用。想象一下在你的业务逻辑中写50个API调用;即使它会运行,但每次你写一个API调用时,try-catch块都会被重复。
将使用runCatching来做这个修改。runCatching使用了一个try-catch块,但它还添加了另一样东西:Result类。这里的R是泛型,代表String。
return runCatching {
httpClient.get("https://your-api-url.com/endpoint").body<String>()
}
public inline fun <R> runCatching(block: () -> R): Result<R> {
return try {
Result.success(block())
} catch (e: Throwable) {
Result.failure(e)
}
}
现在,通过在代码中使用runCatching,它已经看起来超级干净了。让我们利用Result类提供的另一个函数,进一步提升其清晰度和可读性。
由于结果可能是Success或Failure,可以使用一个超级干净的方法来轻松处理错误。只需替换几个东西,Result类的魔力就会让你的代码闪闪发光。
suspend fun performApiCall(): Result<String> {
return runCatching {
httpClient.get("https://your-api-url.com/endpoint").body<String>()
}
}
class MyViewModel : ViewModel() {
fun fetchData() {
viewModelScope.launch {
val result = repository.performApiCall()
result.onSuccess {
// Handle success if needed
}.onFailure {
// Handle failure if needed
}
}
}
}
Result类并不仅限于上述例子;它还提供了许多更多的使用场景,配合不同的扩展函数。以下是一些例子:
//For cases solely interested in success response, using getOrNull() function and handling errors manually:
viewModelScope.launch {
if (result.isSuccess) {
val data = result.getOrNull()
// handle data
} else {
// handle error
}
}
//Folding the response, a concise approach for clean and readable code:
viewModelScope.launch {
result.fold(onSuccess = { data ->
// handle data
}, onFailure = { error ->
// handle error
})
}
我们仍然需要每次都写'runCathing',但可以通过使用扩展来进一步改进这一点。以下,可以增强我们的Ktor客户端。可以创建你需要的所有扩展,如GetResult,PutResult,DeleteResult...., 来解决这个问题。可以参考这个KtorBoost的库。库的链接:KtorBoost https://github.com/AndroidPoet/KtorBoost
。
// create this extenstiion function in your code base
suspend inline fun <reified R> HttpClient.getResult(
urlString: String,
builder: HttpRequestBuilder.() -> Unit = {}
): Result<R> = runCatching { get(urlString, builder).body() }
//use this extenstion function like this
suspend fun performApiCall(): Result<String> {
return httpClient.getResult<String>("sample_get_url")
}
class MyViewModel : ViewModel() {
fun fetchData() {
viewModelScope.launch {
val result = repository.performApiCall()
result.onSuccess {
// Handle success if needed
}.onFailure {
// Handle failure if needed
}
}
}
}
默认情况下, runCatching 可能会捕获 CancellationException ,从而影响协程执行。为了防止这种情况,需要编写自己的 runCatching 版本来防止这些问题,KtorBoost 使用自定义的 runCatching 。
public suspend inline fun <R> runSafeSuspendCatching(block: () -> R): Result<R> {
return try {
Result.success(block())
} catch (c: CancellationException) {
throw c
} catch (e: Throwable) {
Result.failure(e)
}
}