Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Error Handling Approaches

Example 1

Consider a function that writes lines of text to a file. The function is supposed to prevent the overwriting of a file that already exists:

import java.io.File
import kotlin.system.exitProcess

fun writeToFile(lines: List<String>, filePath: File) {
    if (filePath.exists()) {
        println("Error: $filePath already exists!")
        exitProcess(1)
    }

    // rest of function not shown
}

What do you think about this approach to error handling? Is it generally applicable? What drawbacks might there be?

If you’re in class, discuss these questions with the people sitting near you. If you are on your own, think carefully about these questions yourself. When you’ve finished discussing or thinking about them, click on the ‘Discussion’ heading below to reveal our take on these issues.

Discussion

In some situations, this approach might be acceptable, but it isn’t generally applicable to all situations, because it makes specific assumptions about how the error is handled.

The most obvious issue is the assumption that the program should be terminated immediately. In some situations, it may be possible to ask the user to specify a different filename, avoiding the need to terminate the program.

A more subtle issue is the assumption that there is somewhere for the error message to appear! When you run a program from a terminal, you will see output appear in that terminal, but where does the output of println() go when code like this is executed by a server?

Example 2

Now consider this function, which computes the variance for a numerical dataset:

fun variance(dataset: List<Double>): Double {
    if (dataset.size < 2) {
        return -1.0
    }

    val mean = dataset.average()
    val sumSquaredDev = dataset.map { it - mean }.sumOf { it * it }
    return sumSquaredDev / (dataset.size - 1)
}

Notice how the function returns the special value of -1.0 when there aren’t enough data points to compute variance.

Is this better / more flexible than the approach followed in the previous example? What drawbacks might there be?

As before, discuss these questions with the people sitting near you, or think about them yourself. After discussing / thinking about them, click on the ‘Discussion’ heading for our take.

Discussion

This approach improves on the previous example in some respects, because it makes no assumptions about how the error should be handled, but it has problems of its own.

One issue is that it won’t always be possible to find a special value that signals an error. In this case, returning a negative value works, because variance can never be negative—but there will be some functions for which every representable return value is potentially a genuine result of computation.

One option here would be to return a Pair, containing the result of computation plus an error code that indicates whether that result is valid or not, but that feels clumsy. Alternatively, we could return the result of computation or null; we will discuss this idea further when we consider nullable types.

A general problem with this approach is that the caller of a function is free to simply ignore any error code that the function returns.