3 ms·
> [...] I find it hard to believe that anyone reasonable can take the stance that its desirable that error handling requires writing substantially more code tha
by klodolph 4y ago
> [...] I find it hard to believe that anyone reasonable can take the stance that its desirable that error handling requires writing substantially more code than The Thing Which Generated The Error In The First Place.
I have personal experience with Java, Go, and C#, to varying degrees. Over the years, my personal style has changed a lot, based on my experience running applications (as someone in ops), developing applications, debugging applications, etc.
First of all, I'll say that doing Go error handling well figuring out the right place to add context to your errors. So in the example above:
return nil, fmt.Errorf("failed to query user: %v", err)
The error is from a method named "GetUser", and in the style guide I use, errors from GetUser would already say something like "get user %d: %w". You'd know this when writing code that calls GetUser, and wouldn't double-wrap it with additional context, if the additional context is not useful.
That's a bit of a tangent.
My experience working in operations is that errors in languages like Java are often stack traces. The stack traces are often large and often have poor signal-noise. Important context is often missing--things like arguments to methods. C++ is sometimes worse, since there's a lot of C++ code out there which won't even give you a stack trace. Worst of all worlds, perhaps.
As I've gotten older, I've moved to a very proactive style of error checking. This style is verbose in all languages, and sometimes this style is much more natural in Go. That's because of the number of times that putting effort into error checking has saved my butt when debugging. By the time I'm debugging a piece of code, it might long forgotten. The error might be hard to reproduce. Getting a good report the first time saves a ton of time when debugging. In other languages, this might be accomplished by aggressive logging, or tools like time travel debugging, or catch & rethrow.
My sense of Go's error handling is that it is a lot of code only if you measure it by line count. The error handling is very boilerplate and you stop seeing it, like when Cypher's looking at code in The Matrix.
https://www.youtube.com/watch?v=MvEXkd3O2ow https://www.youtube.com/watch?v=MvEXkd3O2ow
In isolated cases, it makes sense to panic() / recover() in Go. People new to Go sometimes overuse panic(), and then once they get more experience they overcompensate and don't ever panic()... I think there's a middle ground here, where panic()/recover() is useful in certain situations. I use it most for parsing / marshaling / unmarshaling code. Parsers, for example, can be very dense, and the error handling is often just "return to top, throw everything away, no additional context to report".
What we need is some education like "case studies for when you might want to panic/recover in Go" or "case studies in where you should add context to error messages".
- kaba0 4y agoHard disagree. You lose a lot of context by simply returning an error message, which may not actually contain the relevant reason for the “non-happy path”. While Java stack traces may be verbose, with even an ounce of experience one will realize that you only have to look at the top message and the “caused by” top messages. The other parts are important when you actually want to reproduce the error — good luck reproducing based on a single “failed to query user” message.
- klodolph 4y agoThe point is that "simply returning an error message" is fine when you know that it actually contains the relevant reason. You don't need to blindly annotate the reason for an error every single place an error gets returned. You often end up with redundant errors like this: > could not load document "doc.txt": error reading document "doc.txt": could not open "doc.txt": open "doc.txt": file not found So, sometimes you just return an error upwards, sometimes you conditionally wrap it depending on where the error came from, sometimes you conditionally wrap it depending on what the error type is. And yes, you may be working with poorly-written code that doesn't do this, or you may be working with poorly-written code that just returns some fmt.Errorf() for everything... are you going to parse that? (My approach would be to fix the dependency so it returns better errors, if its needed for good error handling on my end.) Poorly written code is a hazard in all languages--I could just as easily throw RuntimeException in Java if I didn't care how it was handled. > While Java stack traces may be verbose, with even an ounce of experience one will realize that you only have to look at the top message and the “caused by” top messages. I can only conclude from this statement that I don't "even have an ounce of experience" with Java. That's extremely harsh! Maybe I'm just a complete idiot and it's hopeless, I should quit my job, or maybe I just haven't put in enough years working with Java. My first complaint is that the context for an error is not present in the stack trace, because the stack trace only contains the location of the error, and does not tell you which objects the error is associated with (like, which user ID, just to make up an example). That information is either encoded in the exception message (it often isn't) or it is in the log somewhere, although you may not have an easy time finding the relevant line in the logs. My second complaint is that the top and the bottom of the Java stack trace are usually not what you care about. You care about something near the top (but not at the top), some method which called a method that threw the exception, and you care about something farther down near the middle or bottom which shows the operation the program was doing (but not all the way at the bottom, because that will often just be some generic top-level try/catch).