4 ms·
Once again, you're referring to the conditional as “visual noise” as opposed to the actual thing that reminds you that there is a bifurcation here. The point i
by ainar-g 4y ago
Once again, you're referring to the conditional as “visual noise” as opposed to the actual thing that reminds you that there is a bifurcation here. The point is not to do this:
// Bad, most of the time.
result, err := foo(x)
if err != nil {
return err
}
But to do this:
// Better, most of the time.
result, err := foo(x)
if err != nil {
return fmt.Errorf("performing foo on x = %v: %s", x, err)
}
This isn't noise. This is meaningful logic. That is what exception mechanisms seem to discourage, as they are invisible (unless you wrap every call to everything that throws into a try{}, but then it's not better than an if{} is it?), and this is what the ? operator in some languages discourages as well, because it adds no human- or machine-readable context.
There must be syntax that indicates what to do when an error occurs, and that syntax must support addition of information about the context in which the error has occurred. And why not have the most basic conditional take the role of that syntax?
- 8n4vidtmkvmk 4y agoeven if you wrap in try catch its worse because it moves the error to the bottom, messes with your scopes, and you can't tell which line actually threw
- kaba0 4y agoOr — you can wrap logical parts into a try-catch block, because it doesn’t really makes sense to throw 263 different errors for the same logical unit of work. Also, bubbling up is the sane default, even if you are writing a fast script/trying out some code it won’t clutter anything and you will be notified to handle the problems once you want to write robust code. While in Go you will just put empty if errs and will forget about the whole thing, swallowing errors which are arguably the worst thing.
- hdjrudni 4y agoYes, wrapping individual statements is better. But then it's still overly verbose and has weird scoping issues. e.g. in JS you might write: try { let res = somethingThatMightFail(); catch(err) { // log the error or something return null; } // res is good now... but it's out of scope Whereas in C++ you'd just write ASSIGN_OR_RETURN(auto res, somethingThatMightFail()) And it will bubble your error status just like you wanted, only takes 1 line of code, and keeps all the error information (assuming you're using absl::Status). I haven't really used Go, but it sounds like the worst of both worlds. > even if you are writing a fast script/trying out some code it won’t clutter anything and you will be notified to handle the problems once you want to write robust code How are you going to go back and find every callsite that might potentially fail at a later date? There's no easy way to even tell if somethingThatMightFail might fail if it doesn't return something like a "StatusOr" or "Option" or something. I think Java has some tooling to force exception handling. Not sure about Go.
- kaba0 4y agoWell, Java has checked exceptions which have to be handled at call site, or be declared as being thrown in the method handler (making it ‘viral’). I believe these solve this “find every callsite later” thing quite well. While Java’s exceptions are not without problems (too deep inheritance trees don’t make sense on exception types, lambdas not operate too well with this feature, etc), I think it is a really good direction not utilized to the full by any mainstream language.
- joshuamorton 4y agoThat tells you absolutely no information a stacktrace doesn't. It's actually worse than just passing the err! Like, foo knew you failed performing foo, that's why you have the error, because foo failed. You don't need to reiterate that foo failed while performing foo, because the machine and user already know foo failed. See how annoying that kind of repetition is. I've seen people that do that and it sounds nice until you end up with Failed foo on x: failed bar on x: failed doing baz on x: failed to do quux". You've created a worse stacktrace! I now have to search for a string and hope I can guess where the %s (or v or q) was instead of just grabbing the line number. Or I do none of that, pass errors around directly, and just use the stacktrace. The issue with your claim is that we only care about errors in two places, where we create them (like where the file was unable to be read) and where we handle them (when they are either swallowed bc the behavior was expected or passed to the user). You don't care about them in all the intervening code, and exceptions handle that better. > And why not have the most basic conditional take the role of that syntax? Because it bloats your code and you end up with in practice more lines spent on verbose, cookie cutter error returning than actual logic! Like, you don't need additional human readable context except in very particular cases. Go proves this in practice as almost everyone just returns the unmodified error most of the time, and it works great. Except for the annoying syntax.
- thayne 4y ago> And why not have the most basic conditional take the role of that syntax? Because having approx. 3/4 of your code devoted to error handling and error propagation is both hard to write and hard to read. IMO rust's approach is close to ideal, in the common case where you just propagate up, the syntax is succinct, but still explicit, and when you handle it, the logic is close to the callsite, and is just working with a normal value. Useful stack traces are still a bit of a problem, but there is work being done on that. And that's not to mention how in go you always return both the error and regular values, even though you usually only use one.