Error Handling

In Go, there is a built-in error type. The different values of error type, indicate an abnormal state. Usually in Go if the error value is not nil then an error has occurred, and must be dealt with, in order to allow the application to recover from said state without crashing.

A simple example taken from the Go blog follows:

if err != nil {
    // handle the error
}

Not only can the built-in errors be used, we can also specify our own error types. This can be achieved by using the errors.New function. Example:

{...}
if f < 0 {
    return 0, errors.New("math: square root of negative number")
}
//If an error has occurred print it
if err != nil {
    fmt.Println(err)
}
{...}

Just in case we need to format the string containing the invalid argument to see what caused the error, the Errorf function in the fmt package allows us to do this.

{...}
if f < 0 {
    return 0, fmt.Errorf("math: square root of negative number %g", f)
}
{...}

When dealing with error logs, the developers should ensure no sensitive information is disclosed in the error responses, as well as guarantee that no error handlers leak information (e.g. debugging, or stack trace information).

In Go there are additional error handling functions, these functions are panic, recover and defer. When an application state is panic it's normal execution is interrupted, any defer statements are executed, and then the function returns to it's caller. recover is usually used inside defer statements and allow the application to regain control over a panicking routine, and return to normal execution. The following snippet, based on the Go documentation explains the execution flow:

func main () {
    start()
    fmt.Println("Returned normally from start().")
}

func start () {
    defer func () {
        if r := recover(); r != nil {
            fmt.Println("Recovered in start()")
        }
    }()
    fmt.Println("Called start()")
    part2(0)
    fmt.Println("Returned normally from part2().")
}

func part2 (i int) {
    if i > 0 {
        fmt.Println("Panicking in part2()!")
        panic(fmt.Sprintf("%v", i))
    }
    defer fmt.Println("Defer in part2()")
    fmt.Println("Executing part2()")
    part2(i + 1)
}

Output:

Called start()
Executing part2()
Panicking in part2()!
Defer in part2()
Recovered in start()
Returned normally from start().

By examining the output we can see how Go can handle panic situations and recover from them, allowing the application to resume its normal state. These functions allow for a graceful recovery from an otherwise unrecoverable failure.

It's worth noting that defer usages also include Mutex Unlocking, or loading content after the surrounding function has executed (e.g. footer).

In the log package there is also a log.Fatal. Fatal level is effectively logging the message, then calling os.Exit(1). Which means:

  • Defer statements will not be executed.
  • Buffers will not be flushed.
  • Temporary files and directories are not removed.

Considering all the previously mentioned points, we can see how log.Fatal differs from Panic and why it should be used carefully. Some examples of the possible usage of log.Fatal are:

  • Set up logging and check whether we have a sane environment and parameters. If we don't, then there's no need to execute our main().
  • An error that should never occur and that we know that it's unrecoverable.
  • If a non-interactive process encounters an error and cannot complete, there is no way to notify the user about this error. It's best to stop the execution before additional problems can emerge from this failure.

An example of initialization failure to illustrate:

func init(i int) {
    ...
    //This is just to deliberately crash the function.
    if i < 2 {
        fmt.Printf("Var %d - initialized\n", i)
    } else {
        //This was never supposed to happen, so we'll terminate our program.
        log.Fatal("Init failure - Terminating.")
    }
}

func main() {
    i := 1
    for i < 3 {
        init(i)
        i++
    }
    fmt.Println("Initialized all variables successfully")

It's important to assure that in case of an error associated with the security controls it's access is denied by default.

results matching ""

    No results matching ""