So the author's problem really comes down to "I can't re-order things when I declare them with :=". So don't. Do it like this if you think the function order is going to change:
And the final "?" proposal doesn't actually deal with the errors. Part of the point of Go's error handling is to force us to deal with the errors. Endless "if err != nil {return err}" statements is kinda an antipattern in Go. Yes it's common, but I'm sure that's because we've been trained by exception handling to just pass the errors up.
A better (but still not perfect) pattern is to wrap the errors:
if err != nil {
return fmt.Errorf("tried to foo the bar with parameter %s, failed with: %w", baz, err)
}
And obviously, an even better pattern is to actually deal with the error - retry after a delay? return a custom "abandon goroutine" error? Endlessly passing the errors up the stack until some function passes an incomprehensible error message to the user is what we're trying to get away from here.
The problem is that you don't need to mutate every error at every frame in the stack. I've seen codebases which religiously follow this rule, and most of the `fmt.Errorf` calls don't have much more to say than "error in f(): %s".
Languages that implement error handling well do not communicate error states in the form of massive strings like `call to f() failed: g() returned an error with parameter '123': error in h(), with argument x = 'interface{}{}': connection failed`.
The error interface's only member is `func Error() string`. If you want to do fancy things with errors rather than accumulate a massive string then there is absolutely nothing stopping you. As long as you include a `func Error() string` in there too then whatever you implement will be perfectly compatible with the std library and any other Go code that uses errors.
IME, "retry after a delay" is often much more of an anti-pattern than "pass the error up the stack". That's how you get system's that take an age to do anything before eventually failing anyway (or worse, just leaving you with a perma-spinner). As a user, I'd much rather use a system that fails fast, and allows me to easily retry myself.
This is how the bank I used to work at ended up with app loads taking almost a minute. People don't intuitively understand the impact of transitivity and exponential growth. It is a very, very bad idea to make a habit of using retry-with-backoff as a default for error handling. (That's before we even consider the semantics of errors in stateful programs, the virtues of 'let it crash', etc.)
This. You have to pick a layer that is responsible for retries. If every layer does it, you get a failure amplifier that might become a death star laser.
Yup. We once analysed a particularly slow query (as in, a specific call to our server, not a path in the abstract) and saw that across the whole cluster it had resulted in eight million retries. And I think it was a 4xx in the end anyway (not that Go is designed for fancy stuff like telling the difference between two kinds of error!).
Try different strings? Maybe run through some AI code to figure out what the string really should have been? Most errors you just have to throw your hands up and declare something's wrong. I write in Go and Kotlin and Kotlin has been much less buggy because errors naturally flow up where they are logged and stop the normal execution. I've seen a number of examples in Go where the error was not returned, causing silent failures.
There's a ton of static code analysis tools for Go that check whether the error is actually handled/passed/etc - I'd say they're almost mandatory they're so useful :)
edit: The thing with NewFromString is that the string you're trying to convert to a decimal can't be. So yes, you're going to have to try a different string if you want this to work. Or you could deal with it a different way - maybe you could see if it converts to a float and then convert the float to a decimal?
And in the typical exception-using code base I've seen, there's one try block at the bottom of the stack with a catch block that throws whatever error message it has at the user. Anything is better than this.
There's a ton of static code analysis tools for Go that check whether the error is actually handled/passed/etc - I'd say they're almost mandatory they're so useful :)
I find this slightly at odds with the argument (made by other people, not you) that in Go you’re not reliant on the IDE, because all the program behaviour is immediately visible.
I don’t really understand the objection to using an IDE to look up overridden operators and so on. It seems like it’s in the same category as having a type-checking compiler that knows how to look up a type definition and check that you’re using valid operations. There’s no way all the information you need is going to be nearby in the text in any reasonably-sized program.
I work in the banking industry so I can't just try different strings until it works. These strings might represent real money. If it can't be parsed then something is screwed up. The program is reading the wrong file, the data is corrupt but my point is, there is nothing the program can do to fix it. The program simply needs to throw or log and error and someone needs to research what happened. Here's another example:
using `var err error` at the top is a sensible solution, I'm surprised this isn't the norm. As for dealing with the errors, there is value in wrapping an error with a message at the source of the error, but much of what follows is just passing the error up to whichever function is responsible for handling it (which may be, as you say, retrying after a period), analogous to rust's `?` operator. There is a proposal for defining a general error handler (see https://go.googlesource.com/proposal/+/master/design/go2draf...) but iirc that's in limbo at the moment
Which have to be done manually or through code gen. So we're back at exceptions, but with the downsides of verbosity, and ability to forget to propagate them. Basically inferior exceptions in practically every way.
A better (but still not perfect) pattern is to wrap the errors:
And obviously, an even better pattern is to actually deal with the error - retry after a delay? return a custom "abandon goroutine" error? Endlessly passing the errors up the stack until some function passes an incomprehensible error message to the user is what we're trying to get away from here.