Every program should grapple with failure. A happy path is only a part of the story. There are many ways to handle errors. The essential toolkit showcases throwing and catching exceptions, passing results, leveraging optionals, aborting, and asserting.
Exceptions
If there’s an unusual situation that you cannot or don’t want to react to, you can throw an exception. It’s a sort of failure that will stop your current flow of execution and if you don’t handle it eventually, it will terminate your program with an error. This is the most common way to deal with errors.
Handling exceptions
Try-catch:
try {
doSomething(emptyList())
} catch (e: IllegalArgumentException) {
e.stackTraceToString()
}
React differently to different types of exceptions. The topmost catch with a matching type wins, so the most specific exception types should come first.
try {
doSomething(listOf("BANG!"))
} catch (e: IllegalStateException) {
doSomething(listOf("most specific exception first"))
} catch (e: RuntimeException) {
e.stackTraceToString()
} catch (e: Exception) {
throw Error("Rethrowing a different error with cause attached", e)
}
Try-catch can be used as an expression and the exception variable can be ignored:
val output = try {
doSomething(listOf("item"))
} catch (_: Exception) {
"empty"
}
Finally
Always execute something after a block of code, regardless if an exception was thrown:
var resource: Collection<String>? = null
try {
resource = listOf("BANG!")
resource?.let {
doSomething(it)
}
} finally {
resource = null
}
try {
resource = listOf("BANG!")
resource?.let {
doSomething(it)
}
} catch (e: Exception) {
e.stackTraceToString()
} finally {
resource = null
}
finally can also be used in a try-catch expression, without influencing the result.
val finalOutput = try {
resource = listOf("BANG!")
resource?.let {
doSomething(it)
} ?: error("Fallback to default in the catch handler")
} catch (_: Exception) {
"finally block doesn't affect the result of a try-catch expression"
} finally {
resource = null
}