Live data from Hacker News

Gopher Wrangling: Effective error handling in Go

stephenn.com

211–220 of 310 posts

Re: Gopher Wrangling: Effective error handling in Go

#211

Earlier quoted context omitted.

The error handling is second nature to anyone who has done C or Unix programming. It just feels dirty not to check for an an error. This is one part I like about Go.

Unfortunately, at scale a feeling of dirtiness doesn't prevent a lot of really bad code from being written. Looking across the Go landscape, inadequate error handling is present in almost all projects I come across.

Is there a language which prevents bad error handling? Many developers praise exceptions but as an Ops person I'm sick of logs full of useless java stack traces which are hard to read and follow (even if you have source code within the reach) and despite verbosity often fail to provide the context necessary to find the failure cause. Best logs I've seen for some reason are all from apps written in C/C++. I know error handling is not only about logs, but in many cases all it does - logging and passing error up the stack.

Re: Gopher Wrangling: Effective error handling in Go

#212
post #116

Go's error handling is a horrible mess: 1. It's easy to ignore returned errors without any compiler warnings. You have to rely on third party tools such as golangci-lint to report missing error handling. 2. Errors don't carry stack traces with them, you have to rely on third party libraries or custom errors to get that functionality and you will only get it for your own code, not in other libraries you are using. 3.…

It sounds like you think about error handling a lot. Is there a language that has error handling "done well " that you like?

Error handling is a difficult topic. Generally, the more you can catch in the compiler, the less you have to write runtime checks and the obligatory unit tests that everyone likes to forget. So if you are on the lookout for a language, I'd look for something that has explicit nullable/non-nullable types, as well as strict and static typing.

However, I wouldn't pick a language purely based on its error handling capabilities. That's treating everything like a nail just because you have a hammer. I'd pick a language that's suitable for the task at hand. Go is suitable for making small(ish) webservices. Over 10k lines of code it becomes really hard to keep things straight. However, that's more due to its very limited scoping abilities.

As far as Go is concerned, you can make the error handling work. In ContainerSSH, we built our own logging overlay, which you can find here: https://github.com/ContainerSSH/libcontainerssh/tree/main/lo... This companion message library has a custom error structure that carries along an error code, which uniquely allows identifying the cause of the error: https://github.com/ContainerSSH/libcontainerssh/blob/main/me... Errors can be wrapped and we added tools to determine, if a certain error has an ancestor with a specific code, allowing for tailored error handling cases. We also added a tool that gathers the comments from the error code constants and adds them to the documentation: https://github.com/ContainerSSH/libcontainerssh/blob/main/cm...

I hope this helps.

Re: Gopher Wrangling: Effective error handling in Go

#213

Earlier quoted context omitted.

Unfortunately, at scale a feeling of dirtiness doesn't prevent a lot of really bad code from being written. Looking across the Go landscape, inadequate error handling is present in almost all projects I come across.

Is there a language which prevents bad error handling? Many developers praise exceptions but as an Ops person I'm sick of logs full of useless java stack traces which are hard to read and follow (even if you have source code within the reach) and despite verbosity often fail to provide the context necessary to find the failure cause. Best logs I've seen for some reason are all from apps written in C/C++. I know error…

Rust does it pretty well. No error handling via exceptions, it's all Result or Option types.

This is great since it's enforced on a type-level that every result or option has to be unwrapped before it can be used. Unwrapping is explicit, the code panics if something goes wrong, and the function signature makes it easy to see what sort of errors to expect (especially when using custom error enums for the Err value).

There is one major caveat here, which is that Rust's type system only forces you to check for errors if you plan to use the return value of a function.

An example: You have a function write(), which writes to a file, and which returns Result. (Here `()` is the zero-sized type, ie. an empty tuple).

If you call this function in your code, it might return an error, but you can just silently drop this error. Your linter is going to complain, though. This issue could be fixed with proper linear types (ie. types which must be used once), but adding them to the language would afaik be really difficult at this point.

But other than that Rust is doing pretty well, honestly, and (imo) Go would be a significantly better language if it had a proper Result type, and just used it for all of its error-handling. Sadly, we can't change history.

Re: Gopher Wrangling: Effective error handling in Go

#214
post #210

Earlier quoted context omitted.

Calling a linter thirdparty in Go is really disingenuous. Like you install go in your favourite IDE and it's batteries included. It's part of the standard set.

Go is a weird mix. It doesn't even let you create an unused variables, but happily lets you ignore errors or return variables. That makes no sense and is on Go, not on the admittedly quite excellent tooling provided by people who are not the Go dev team (third party). An IDE is just as much third party to the language as golangci-lint is.

Ignoring an error is a red herring. You have to go out of your way to actually use a special character to do it.

No, one real issue that can happen if one is not careful (but fortunately linters help) is variable shadowing which may lead to some errors being unchecked.

In general, I find that error handling is not as horrible as some seem to purport.

Re: Gopher Wrangling: Effective error handling in Go

#215

Earlier quoted context omitted.

Unfortunately, at scale a feeling of dirtiness doesn't prevent a lot of really bad code from being written. Looking across the Go landscape, inadequate error handling is present in almost all projects I come across.

Is there a language which prevents bad error handling? Many developers praise exceptions but as an Ops person I'm sick of logs full of useless java stack traces which are hard to read and follow (even if you have source code within the reach) and despite verbosity often fail to provide the context necessary to find the failure cause. Best logs I've seen for some reason are all from apps written in C/C++. I know error…

Unfortunately, as an ops person you are always at the mercy of developers. If a dev writes this code in Go:

    if err != nil {
        return err
    }
If is equally useless as "letting if fly" in Java. However, in Go, developers seem to have more of an awareness for the need for proper error handling, which is not the case with most Java devs. So your problem is cultural, not technical.

I, for one, really like the idea of checked exceptions, forcing you to document and handle your exeptions. However, that idea has turned out to be too tedious for most people, so it didn't catch on.

Re: Gopher Wrangling: Effective error handling in Go

#216
post #203
post #191

Earlier quoted context omitted.

There is no going around abstraction, that’s a necessary part of any non-trivial program as that’s the only method we have to control complexity. Your struct is also an abstraction, you could have defined another one, use it differently, etc. Many of the design patterns are useless bullshit, that is long superseded by a language feature, so that point doesn’t stand imo. Go also has public/protected, it is just case-s…

It is easier to know that lowercase is package-specific, uppercase is exported, than knowing which field is private/public by default. Go reserved keywords: break, default, func, interface, select, case, defer, go, map, struct, chan, else, goto, package, switch, const, fallthrough, if, range, type, continue, for, import, return, var Java reserved keywords: abstract, continue, for, new, switch, assert, default, goto*,…

You have conveniently left out types from go’s list.. with those removed it is hardly longer, and as has been shown (case-sensitive identifiers), not all language complexity lives within keywords.

Re: Gopher Wrangling: Effective error handling in Go

#217
post #127

Earlier quoted context omitted.

if you write a line of code that can fail, then you should deal with the possibility of that line failing there, directly, in-line _how_ you deal with that failure is a separate question but it's critical that every fallible expression explicitly and visibly demonstrates the possibility of failure this is in no way an "error handling mess" -- on the contrary, it is basically the only way to produce robust and reliabl…

In my experience, the vast majority of the time the best (or only) way to correctly deal with the error is to propagate it up the stack. Making this optional (having to explicitly check return values) doesn't make it more likely to be done.

you say "optional", i say "explicit"

it's important that i see the `return` keyword in the source code

Re: Gopher Wrangling: Effective error handling in Go

#218
post #196

Earlier quoted context omitted.

you don't want exception-style "convenience", that's the whole point you want to be able to read code and see a single control flow ? subverts that core requirement

Except that without try-catch blocks you will have n separate control flow instead of a trivial to see pattern.

huh? it's exactly the opposite

ideally, control flow goes 'down' via func calls, and 'up' via return statements

this is the "trivial to see pattern" -- the code as written

exceptions subvert those simple rules, they say any expression can potentially be a return statement, and recursively so!

Re: Gopher Wrangling: Effective error handling in Go

#219
post #214
post #210

Earlier quoted context omitted.

Go is a weird mix. It doesn't even let you create an unused variables, but happily lets you ignore errors or return variables. That makes no sense and is on Go, not on the admittedly quite excellent tooling provided by people who are not the Go dev team (third party). An IDE is just as much third party to the language as golangci-lint is.

Ignoring an error is a red herring. You have to go out of your way to actually use a special character to do it. No, one real issue that can happen if one is not careful (but fortunately linters help) is variable shadowing which may lead to some errors being unchecked. In general, I find that error handling is not as horrible as some seem to purport.

> You have to go out of your way to actually use a special character to do it.

Only if the function returns more than the error. You can happily do this without errors:

   fh = os.Create("/some/file")
   defer fh.Close()
Needless to say, this is a terrible idea if the underlying filesystem can give you an error at close time, e.g. on NFS. The correct way to write the above code would be:

   fh = os.Create("/some/file")
   defer func() {
      if err := fh.Close(); err != nil {
          // Do something with the error
      }
   }()
Yet, I see a lot of the former and very few instances of the latter.

Re: Gopher Wrangling: Effective error handling in Go

#220
post #193

Earlier quoted context omitted.

The error handling is second nature to anyone who has done C or Unix programming. It just feels dirty not to check for an an error. This is one part I like about Go.

An if err with some random one-liner in the err part is not error handling. You can’t reasonably handle an error condition on a local basis, that’s why exceptions (especially checked ones) are superior. They do the correct thing — either bubble up if it doesn’t make sense to handle them in place, or have them in as broad of a scope as it makes sense with try-catches. Oh and they store the stacktrace, so when an excep…

calling a function that can fail means you need to manage that condition

inspecting the err value returned by a function call is in fact error handling

the point of this design is to keep control flow "on the page"

exceptions do not keep control flow "on the page"

Post reply on HN