I'm surprised to see Go's error handling listed as a disadvantage. Go encourages writing good error messages, and that's one of my favorite things about the language.
To get a sense of it, take a look at a failing test in a language such as Python that uses asserts for testing and compare it to the equivalent written in Go. Quite often, the error message in Go will be clear and to the point. On the other hand, I've seen plenty of assertion based tests in other languages that report inane things like "1 != 2" or "true != false" when they fail.
Go encourages putting the effort into this up front. It pays off later when things go wrong and you want to know why.
Probably complaining from someone used to exceptions and/or assertions, where there is perhaps less boilerplate and manual verification that context bubbles to the right place.
You can get there either way, but what you're used to weighs
heavily.
For your example, depending on my experience, knowing that 1!=2 in some code I'm using directly might be more meaningful or actionable than "out of filehandles for accept()" in some deep chain of dependencies I know little about. Just depends on what you're used to.
Or anyone who's used a language where errors aren't as stringly-typed as Go and where the compiler can enforce error handling. Exceptions have benefits and drawbacks, but any language that can return a Result type will have better error handling than Go in basically every way.
Yeah I think the point is, errors shouldn't be overloaded with too much extra structure. They should be errors. If additional structure is needed, it should probably be sent separately via the common multiple returns idiom in Go.
Additional structure like, say, a numeric error code? An HttpError could definitely contain the corresponding status code.
But I think the bigger point is that, for libraries, errors should be an enumerated set of possible error conditions, not strings. You say "additional structure", I say the duplicate filename, the invalid email address or, crucially, the error that caused the error...chaining errors is very useful. The multiple returns idiom in Go may make this possible, but it's hacky and strictly worse than languages that can utilize generics and a more richly-typed set of errors.
The point I'm making here is that everyone knows 1!=2. It's not helpful for a test to tell you that because then you have to go hunting around to figure out what is wrong instead of it just giving you at least a rough idea right away. It does not depend on what you're used to.
The reason I, personally, don't like it is it's basically a slight improvement over C's error handling when everyone else has substancially improved. In C, basically every function could fail and you needed to check the return code. In some cases you'd need to make up an impossible value to return to signal error and in some cases (e.g. functions returning binary data) you would need some other global variable to be tested. So Go fixed all the error signalling issues bit still has the problem of needing to check every function return... which no one is going to do. In C, if printf fails that's usually just going to happen without the program noticing. In any language with more modern error handling (note: not just exceptions) printing can't silently fail.
> I'm surprised to see Go's error handling listed as a disadvantage. Go encourages writing good error messages, and that's one of my favorite things about the language.
The biggest reason for me is the inability to get stacktraces to where an error comes from.
> Quite often, the error message in Go will be clear and to the point. On the other hand, I've seen plenty of assertion based tests in other languages that report inane things like "1 != 2" or "true != false" when they fail.
An error message like '1 != 2' is a lot more meaningful when it's reported as the reason that test_new_foo_has_2_bars failed. Occasionally it's nice to give a more explicit message, which is why in eg Python you can optionally supply one, but it's not generally an issue if every test is really only testing one thing. To me, the Go approach seems to encourage writing cluttered tests that test several things at once.
Something as simple as allowing the use of multi-result calls in if statements would make a huge difference. Being able to write "if err := blah(); err != nil { ... }" is great (although it does kind of hide the original call.)
WTF? You are absolutely right. I could've sworn I had issues with this before. 25% of my irritation with Go just evaporated (the other 75% is how hard it is to run Delve, but I'll figure that out.)
> On the other hand, I've seen plenty of assertion based tests in other languages that report inane things like "1 != 2" or "true != false" when they fail.
In Python, one would need to supply the message argument to avoid that. But Go seems no different, really; you still have to supply the message. My understanding of the testing package is that Python's:
self.assertEqual(expect, actual, f'expected ({expect!r} != actual ({actual!r})')
is approximately:
if expect != actual {
test.Errorf("expected (%s) != actual (%s)", expect, actual)
}
I'm honestly not sure which is "better".
I do wish Python could figure out something better than "1 != 2", s.t. the assert would just do The Right Thing™ in a graceful way, but doing so would require facilities the language hasn't got (e.g., some macro capability). There are some packages that do some black magic stack walking to do slightly better, IIRC.
To me, Go's error handling falls a bit short because it is possible to forget to handle an error (though thankfully that unused variables is an error makes this more difficult), and you don't get an error or a result, you always get an error and result. (The language lacks a sum type, so it can't really do much better here.) It's on the programmer to not use the result, and to me that felt and still feels weird. The constant do-something, check, propagate is thing, too … I had hoped C had beat that out of language design as too tedious, frankly. (C.f. Rust, where even though error propagation is always explicit, it was `try!(foo)` early on, and `foo?` more recently.)
py.test does the magic walking, and it does indeed make for much less verbose tests -- no need to remember which self.assertSomething method to call either:
x = 1
y = 2
items = [4,5,6]
assert (x+y) in items
Outputs this, replacing the variable names with values
test.py:4: in <module>
assert (x+y) in items
E assert (1 + 2) in [4, 5, 6]
I think the if-statement one is clearer, but in terms of results they're both fine. At the same time, I usually don't see people put in the extra work to add these helpful messages to their assertions. Maybe one reason is because they make the code a little harder to read.
I've been searching if there's an equivalent in some Python unit test framework for Google test's predicate-formatter feature. Crafting an intelligible failure interpretation that even directs a developer for what to fix is solid gold.
https://github.com/google/googletest/blob/master/googletest/...
To get a sense of it, take a look at a failing test in a language such as Python that uses asserts for testing and compare it to the equivalent written in Go. Quite often, the error message in Go will be clear and to the point. On the other hand, I've seen plenty of assertion based tests in other languages that report inane things like "1 != 2" or "true != false" when they fail.
Go encourages putting the effort into this up front. It pays off later when things go wrong and you want to know why.