DEV Community

Cover image for Golang Error Handling: Introducing Fold Ninja to Reduce Verbosity
Iñigo Etxaniz
Iñigo Etxaniz

Posted on

Golang Error Handling: Introducing Fold Ninja to Reduce Verbosity

Introduction to Go Error Handling

As a Go developer, one aspect of the language that I deeply appreciate is its approach to error handling. It's designed to be reliable, thorough, and encourages good programming practices. Here are some key points I particularly like about Go's error handling:

  • Immediate Error Handling: In Go, errors are handled as soon as they occur. This immediacy ensures that issues are addressed promptly, enhancing the stability and reliability of applications.

  • Comprehensive Error Consideration: When coding in Go, all potential errors are taken into account. This thoroughness ensures that we're always aware and prepared for possible failure points in our code.

  • Reduced Unexpected Errors: With the correct programming approach, unexpected errors (or panics) are almost a rarity in Go. This is a testament to the language's robustness and our ability to control error flows.

  • Ease in Tracing Errors: Finding the source of a problem in Go is generally straightforward, especially if error descriptions are clear and informative. Often, a simple search in VSCode with the text of the error leads you right to the issue.

  • Tools for Error Analysis: Go also provides powerful tools to understand the sequence of events leading to an error. For instance, here's a snippet showing how you can use Go's runtime package to capture a stack trace just after an error occurs:

  buffer := make([]byte, 96000)
  n := runtime.Stack(buffer, false)
  stackItems := strings.Split(string(buffer[:n]), "\n")
  stackData := make([]string, 0)
  // Further processing of stackData...
Enter fullscreen mode Exit fullscreen mode

This snippet captures the current goroutine's stack trace and splits it into readable segments, allowing for a detailed analysis of the error's context.

In summary, Go's error handling is not just a feature; it's a cornerstone of the language's design philosophy, promoting robust and reliable code. However, despite these advantages, there's one aspect that can be challenging: verbosity.

The Challenge of Verbosity in Go Error Handling

While Go's error handling is effective, it introduces a challenge that I've personally grappled with: verbosity. This becomes particularly evident during the code review process. In my programming practice, I adhere to a principle where the code should largely explain itself. This is achieved through well-thought-out function and variable names, along with breaking down logic into smaller, descriptively named functions. The aim is always to reduce reliance on comments and make the code as self-explanatory as possible.

However, the verbosity inherent in Go's error handling often poses a hurdle to this approach. Let's consider a common scenario: you're reviewing code and trying to grasp the core algorithm. Each function call accompanied by error handling adds layers of additional lines. These lines, while crucial for robustness, can distract from the main logic flow. The result? It becomes more challenging to quickly understand the essence of what the code is doing.

Here's a typical example illustrating this point:

package main

import (
    "bytes"
    "compress/gzip"
    "encoding/base64"
    "fmt"
    "io"
)

// gunzipB64ToString takes a base64 encoded string that represents gzip-compressed data,
// decodes it, unzips it, and returns the resulting string.
func gunzipB64ToString(b64Data string) (string, error) {
    decodedBytes, err := base64.StdEncoding.DecodeString(b64Data)
    if err != nil {
        return "", fmt.Errorf("error decoding base64 data: %w", err)
    }

    buffer := bytes.NewBuffer(decodedBytes)
    gzipReader, err := gzip.NewReader(buffer)
    if err != nil {
        return "", fmt.Errorf("error creating new gzip reader: %w", err)
    }
    defer gzipReader.Close()

    unzippedBytes, err := io.ReadAll(gzipReader)
    if err != nil {
        return "", fmt.Errorf("error reading unzipped data: %w", err)
    }

    return string(unzippedBytes), nil
}

func main() {
    encodedString := "H4sIAAAAAAAA/8tIzcnJBwCGphA2BQAAAA=="
    result, err := gunzipB64ToString(encodedString)
    if err != nil {
        fmt.Println("Error:", err)
        return
    }
    fmt.Println("Decompressed and decoded string:", result)
}
Enter fullscreen mode Exit fullscreen mode

In this snippet, the core operations – decoding a base64 string and unzipping it – are fairly straightforward. Yet, the error handling associated with each step nearly doubles the number of lines in the function. This added verbosity, while crucial for ensuring the robustness and reliability of the operation, can overshadow the main functionality, especially when viewed within the broader context of a larger application.

This isn't just a matter of aesthetics; it has practical implications for code readability and maintenance. When reviewing code, the clarity of the core logic can be clouded by the repetitive nature of error checks. This can make the review process more time-consuming and cumbersome, and sometimes, important aspects of the logic might be overlooked due to the clutter.

As a developer who values clean, self-explanatory code, I found this aspect of Go programming particularly challenging. It was important to me to find a solution that would align with my coding philosophy of minimalistic, clear code, while still respecting Go's rigorous approach to error handling.

Now, let's see how the same function looks when folded using "Fold Ninja":

package main

import (
    "bytes"
    "compress/gzip"
    "encoding/base64"
    "fmt"
    "io"
)

// gunzipB64ToString takes a base64 encoded string that represents gzip-compressed data,
func gunzipB64ToString(b64Data string) (string, error) {
    decodedBytes, err := base64.StdEncoding.DecodeString(b64Data)

    buffer := bytes.NewBuffer(decodedBytes)
    gzipReader, err := gzip.NewReader(buffer)
    defer gzipReader.Close()

    unzippedBytes, err := io.ReadAll(gzipReader)

    return string(unzippedBytes), nil
}

func main() {
    encodedString := "H4sIAAAAAAAA/8tIzcnJBwCGphA2BQAAAA=="
    result, err := gunzipB64ToString(encodedString)
    fmt.Println("Decompressed and decoded string:", result)
}
Enter fullscreen mode Exit fullscreen mode

With "Fold Ninja," the error handling blocks are neatly folded away. This leaves us with a view that emphasizes the core logic of the function. The error handling is still there, just one click away if we need to inspect it, but it no longer dominates the visual landscape of the code. This folding makes it much easier to focus on and understand the primary operations of the function, especially during code reviews or when navigating through a large codebase.

The difference is clear – what was once a multi-line, error-handling-heavy function is now a concise, easily digestible piece of code. This not only enhances the readability but also aligns with the philosophy of writing self-explanatory code that focuses on the core logic.

Development of Fold-Ninja

After recognizing the challenges posed by verbosity in Go error handling, I developed "Fold Ninja," a Visual Studio Code extension tailored to enhance code readability and streamline the review process. The primary goal of Fold Ninja is to reduce the visual noise created by excessive information, particularly verbose error management sections in Go files.

Fold Ninja operates in three modes, each designed to cater to different needs during code analysis and review:

  1. Inactive: In this mode, Fold Ninja is dormant, showing the code in its original, unaltered form.

  2. Compact: This is where the extension truly shines. Compact mode automatically folds away verbose parts of the code, including comments and error management blocks. This mode significantly declutters the code, presenting a focused view that highlights the main logic.

  3. Expanded: In this mode, all previously compacted sections are unfolded, making every part of the code, including comments and error handling routines, visible again.

Switching between these modes is a simple click on the status bar item, making it user-friendly. This flexibility allows developers to quickly shift views based on their current task – whether they're deep diving into a detailed analysis or doing a high-level code review.

When working in compact mode, extension is only activated when user activates a file. So, when coding the extension will not update the view.

Try Fold-Ninja and Contribute

I invite you to try Fold Ninja and experience firsthand how it can enhance your Go coding and review process. You can find the extension on the Visual Studio Marketplace and explore its source code on GitHub. Your feedback and contributions are highly valued. If you have suggestions or encounter any issues, please feel free to open an issue on the GitHub repository.

Conclusion

In summary, Fold Ninja embodies my philosophy of clean, self-explanatory code. It offers a practical solution to the verbosity challenge in Go, allowing developers to focus on what truly matters in their code. As we continue to evolve and improve our tools, I believe that we can make Go development an even more enjoyable and efficient experience. Thank you for reading, and I look forward to your thoughts and contributions to Fold Ninja.

Top comments (0)