Hacker News new | ask | show | jobs
by tsimionescu 2534 days ago
The fact that you need to add context to errors usually exacerbates the problem and makes it even harder to read the code. You often end up with

  err = doThing()
  if err! = nil {
    return errors.New("Error doing thing", err)
  }
This doesn't add any useful information for whoever is reading the code, it's just boilerplate that you learn to skip while reviewing, while hopefully not missing any important thing that does happen on the error path.

As for 'programming with the error', I would like to see an actual use case for constantly doing this, and why exceptions would prevent that pattern. The only one I can remember is something highlighted as a 'good practice' by Rob himself: write everything you want to a bufio.Writer, without checking the error messages, and then calling Flush and only then checking if maybe something failed. If this is good, safe, sad-path-first, errors-are-values style... then my taste in programming is obviously bad. Obviously, the same could be achieved with exceptions.

2 comments

First, the signature for `errors.New` is `New(text string) error`. It won't take more parameters than that. So I guess you mean `fmt.Errorf`.

If above is true, then how about:

    err := renderTemplate()

    if err! = nil {
        return fmt.Errorf("Error rendering template: %s", err)
    }
The end error could then be something for example:

    Error rendering template: Compiler has failed: Cannot load template: File /tmp/test.tpl was not found
    ------------------------  -------------------  --------------------  --------------------------------
     |                         |                    |                     |
    Returned by that           |                    |                     |
    example                   Returned by the       |                     |
                              fictional compiler   Returned by the        |
                                                   fictional template    Returned by the fictional file 
                                                   Loader                reader
I didn't even twist your example, and yet you can already see more information. And with that information, even a user can understand what's going on clearly. So ... more useful?
Oops, I forgot if errors.New takes the 'cause' as well.

Regarding you example: the code itself still contains redundant information for someone reading it. True, the error ends up being nicer, though I would argue that the user would have been better served with a simple 'failed to load template file: /tmp/test.tpl', no need to show the pseudo call stack (so, only the fictional template loader should have been wrapping the error,for this particular case). And for a developer, the full call stack may be more useful. Exceptions would give you both for free - a nice message that can be shared to the user by whoever caused the most understandable error, and a call stack that can be logged at the upper layer so developers can see it if a bug is logged, and get a much fuller context.

The full call stack is available in Go but it is up to the developer if they want to include it or not which they can do by creating a custom error type and implementing that to be part of their type. And having the choice seems like a benefit to me.
I would argue that if you are using boilerplate annotations, you are doing it wrong. If you really do not need to add context, don't add context. But in my code I find that I want to add context about 90% of the time.

But then I am super zealous about making sure my error messages understandable without the need to track down other information. For example, I want to know that (something like) "the config file needs group read permissions" not "file cannot be opened." But maybe others value ease of programming more than I do and are less concerned about error UX than I am?