Live data from Hacker News

Eris – A better way to handle, trace, and log errors in Go

github.com

51–60 of 61 posts

Re: Eris – A better way to handle, trace, and log errors in Go

#51
post #48

Earlier quoted context omitted.

That's not exactly true. All struct types in Go can not be nil. However, they also can't be abstracted over in any way, so they are a poor idea for an error type - you want an error interface, and all interface types in Go indeed are nil-able.

You can abstract over a struct as easily as a pointer, and structs work fine for satisfying interfaces. Specifically, structs make it a little harder for someone to inadvertently mutate things (passed by copy) and because they can’t be nil you don’t need to worry about a non-nil interface implemented by a nil concrete pointer (this is mostly only a problem for people who are new to pointers in my experience). The dow…

What I meant about abstraction is that if you define a function which returns an error struct instead of an error interface, you lose any abstraction capability, you can only return that specific struct (and need to find some way of signaling that no error occurred).

Re: Eris – A better way to handle, trace, and log errors in Go

#52
post #47

Earlier quoted context omitted.

It's not just a pain to write. I've accidentally introduced way more bugs through Go style error handling than through Python style error handling. Some examples: Forgetting that a function returns an error: ... foo() // foo returns an error that isn't being handled. ... Forgetting to check the error returned by a function. Note a linter won't pick this up since the err variable is used later. ... err := foo() err =…

Try golangci-lint it catches all your use case.

Thanks! It looks like by default it catches the first two. Is there a way to configure it to catch the other two? I don't think it can catch the case where you accidentally return nil because sometimes you do actually want to return nil when you see an error. I also couldn't find any linter that checks that you are using errors.WithStack when needed.

Re: Eris – A better way to handle, trace, and log errors in Go

#53

This looks pretty good. And I also get the reason behind it’s name. But for a utility package like this, it’s better if it was named errors. It makes the usage and call sites clear. Ex: eris.New() vs errors.New()

  import errors "github.com/morningvera/eris"

Re: Eris – A better way to handle, trace, and log errors in Go

#54

This looks pretty good. And I also get the reason behind it’s name. But for a utility package like this, it’s better if it was named errors. It makes the usage and call sites clear. Ex: eris.New() vs errors.New()

import errors "github.com/morningvera/eris"

Yes it can be import aliased everywhere. But my point is it’s not needed.

Re: Eris – A better way to handle, trace, and log errors in Go

#55

Earlier quoted context omitted.

Of course you can. Sad path first means, after you write your data types and interface signatures, writing the first stub implementations as func (t *Thing) Process(id int) (string, error) { return "", fmt.Errorf("not implemented") } and then filling them in gradually like func (t *Thing) Process(id int) (string, error) { dat, err := t.store.Read(id) if err != nil { return "", fmt.Errorf("error reading ID: %w", err)…

Sure, but in a better language, the second version already does the exact same thing as the first one. Your `return fmt.Errorf` is not exception handling, it is simply manual exception bubbling. It is boilerplate that you can forget to add, and that makes it harder to understand what the code is supposed to do. Maybe for the first error you are adding some context that the Read function didn't have, but for the secon…

> Sure, but in a better language, the second version already does the exact same thing as the first one. Your `return fmt.Errorf` is not exception handling, it is simply manual exception bubbling.

You look at what I'm doing as a more tedious and error-prone version of exception bubbling, but that misses the forest for the trees. The whole point of doing it this way is to lift errors out of the shadows of the exception control flow path, and put them front-and-center in the actual logic of the application. Programming (in many domains) is error handling, the error handling is at least and arguably more important than the business logic.

I don't want exceptions. I do want this (or something like it).

> Even worse, with this style of programming, someone up the stack who would actually want to handle these errors has no mechanism to do, since you're returning the same type from both error cases.

As the author of this module, I get to decide what my callers are able to see. What I've written is (IMO) the most straightforward and best general-purpose example, where callers can still test for the wrapped errors if they need to. If it were important for callers to distinguish between Read and Certificate errors, I would use sentinel errors e.g. var ErrCert (if the details weren't important) or custom error types e.g. type CertificateError struct (if they were).

Adding this stuff isn't bloat. Again, it's just as important as the business code itself.

> Go's error handling strategy is its weakest aspect, and it is annoying to hear advocates pretend that Go is doing it right

In industry, considering languages an organization can conceivably hire for, and considering the general level of industry programmers -- programs written in Go are consistently in the top tier for reliability, i.e. fewest bugs in logic, and fewest crashes. Certainly more reliable than languages with similar productivity like Python, Ruby, Node, etc.

There are plenty of flaws in Go's approach to error handling -- I would love to have Option or Result types, for example -- but I think, judging by outcomes, it's pretty clear that Go is definitely doing something right.

Re: Eris – A better way to handle, trace, and log errors in Go

#56

Earlier quoted context omitted.

Sure, but in a better language, the second version already does the exact same thing as the first one. Your `return fmt.Errorf` is not exception handling, it is simply manual exception bubbling. It is boilerplate that you can forget to add, and that makes it harder to understand what the code is supposed to do. Maybe for the first error you are adding some context that the Read function didn't have, but for the secon…

> Sure, but in a better language, the second version already does the exact same thing as the first one. Your `return fmt.Errorf` is not exception handling, it is simply manual exception bubbling. You look at what I'm doing as a more tedious and error-prone version of exception bubbling, but that misses the forest for the trees. The whole point of doing it this way is to lift errors out of the shadows of the exceptio…

> programs written in Go are consistently in the top tier for reliability.

Citation needed. It may simply be that those code bases are doing relatively trivial work compared to large programs in other languages, where bugs are more likely to happen simply due to code size. Even in this thread another poster wrote:

> I've accidentally introduced way more bugs through Go style error handling than through Python style error handling.

I've seen production golang code where errors were being silently overwritten. Much, much worse than anything in Java or C# where exceptions are explicitly swallowed.

Re: Eris – A better way to handle, trace, and log errors in Go

#57
post #47

Earlier quoted context omitted.

Try golangci-lint it catches all your use case.

Thanks! It looks like by default it catches the first two. Is there a way to configure it to catch the other two? I don't think it can catch the case where you accidentally return nil because sometimes you do actually want to return nil when you see an error. I also couldn't find any linter that checks that you are using errors.WithStack when needed.

Linting for WithStack seems like it might be tough since you want to make sure the error was annotated exactly once (I think that’s the intended use, anyway?). The linter would need to know whether or not a fallible function call has annotated the error or not. Seems like an interesting exercise in any case.

Re: Eris – A better way to handle, trace, and log errors in Go

#58
post #47

Earlier quoted context omitted.

Try golangci-lint it catches all your use case.

Thanks! It looks like by default it catches the first two. Is there a way to configure it to catch the other two? I don't think it can catch the case where you accidentally return nil because sometimes you do actually want to return nil when you see an error. I also couldn't find any linter that checks that you are using errors.WithStack when needed.

[deleted]

Re: Eris – A better way to handle, trace, and log errors in Go

#59
post #48

Earlier quoted context omitted.

You can abstract over a struct as easily as a pointer, and structs work fine for satisfying interfaces. Specifically, structs make it a little harder for someone to inadvertently mutate things (passed by copy) and because they can’t be nil you don’t need to worry about a non-nil interface implemented by a nil concrete pointer (this is mostly only a problem for people who are new to pointers in my experience). The dow…

What I meant about abstraction is that if you define a function which returns an error struct instead of an error interface, you lose any abstraction capability, you can only return that specific struct (and need to find some way of signaling that no error occurred).

Ah, I see. Yes, that’s true. You would need to return a bool alongside it or similar, but the best practice is to use an interface for sure.

Re: Eris – A better way to handle, trace, and log errors in Go

#60

Earlier quoted context omitted.

Sure, but in a better language, the second version already does the exact same thing as the first one. Your `return fmt.Errorf` is not exception handling, it is simply manual exception bubbling. It is boilerplate that you can forget to add, and that makes it harder to understand what the code is supposed to do. Maybe for the first error you are adding some context that the Read function didn't have, but for the secon…

> Sure, but in a better language, the second version already does the exact same thing as the first one. Your `return fmt.Errorf` is not exception handling, it is simply manual exception bubbling. You look at what I'm doing as a more tedious and error-prone version of exception bubbling, but that misses the forest for the trees. The whole point of doing it this way is to lift errors out of the shadows of the exceptio…

I'm not missing the forrest for the trees, I know your argument and reject it.

Again, if you had showed an example where something is actually being done with the errors, I would have agreed with you 100%. But when all that is being done is bubbling the errors, having this be done manually by the programmer (and read every time by the code reviewer) is both inefficient and error-prone. Not to mention that one of the first 'skills' I developed as a Go programmer was to ignore any block starting with 'if err != nil', since it appears so, so much in the code. It's not uncommon to have one function contain 10 different 'if err != nil { return nil, fmt.Errorf("Error doing X %v", err)}' for trivial logic (make 10 calls to some external service, abort if anything fails).

I don't have a problem with encoding errors in the function return type. But, coupled with Go's inability to abstract any kind of control flow, this error 'handling' strategy is almost as bad as C's. Other languages that don't offer exceptions avoid this problem with higher level control mechanisms, such as monads or macros.

Even worse, the Go designers recommend some horrible patterns [0], like Scan() not returning an error, but putting the scanner in an error state that all other Scanner functions respect, and having client code explicitly check for the Error() property of the scanner object at the end - preventing any generic tool from helping check whether you correctly handle errors in your code, and introducing an entirely different pattern.

And I don't know the source of your claim about Go's reliability, but all of the studies I have read comparing programming languages have found no or very little effect of the choice of language on overall number of bugs. One recent study [1] which included Go did have it as one of the more reliable languages (but behind Ruby, Perl or Clojure), but with a very minor overall effect, that may be explained by many factors other than error handling (they did not compare languages by this aspect).

Edit: And one minor point, but I did miss the %w in your example code, which does indeed make it possible for code consuming your errors to differentiate them. In my defense, this is a feature of the very newest version of Go only; and having the difference between a 'testable' error and a not testable one be %w vs %v in a format string seems a design decision particularly hostile to code review.

[0] https://blog.golang.org/errors-are-values

[1] https://www.i-programmer.info/news/98-languages/11184-which-...

Post reply on HN