8 ms·
I suppose Joel does a better job explaining it than I do, so I'll just quote him verbatim. "In other words, the more information about what code is doing is lo
by couchand 7y ago
I suppose Joel does a better job explaining it than I do, so I'll just quote him verbatim.
"In other words, the more information about what code is doing is located right in front of your eyes, the better a job you’ll do at finding the mistakes. When you have code that says
dosomething();
cleanup();
"… your eyes tell you, what’s wrong with that? We always clean up! But the possibility that dosomething might throw an exception means that cleanupmight not get called. And that’s easily fixable, using finally or whatnot, but that’s not my point: my point is that the only way to know that cleanup is definitely called is to investigate the entire call tree of dosomething to see if there’s anything in there, anywhere, which can throw an exception, and that’s ok, and there are things like checked exceptions to make it less painful, but the real point is that exceptions eliminate collocation. You have to look somewhere else to answer a question of whether code is doing the right thing, so you’re not able to take advantage of your eye’s built-in ability to learn to see wrong code, because there’s nothing to see."
https://www.joelonsoftware.com/2005/05/11/making-wrong-code-look-wrong/ https://www.joelonsoftware.com/2005/05/11/making-wrong-code-...
- layoutIfNeeded 7y agoDon’t write C++ like C and cleanup will be guaranteed by RAII.
- couchand 7y agoYep, RAII is an eminently reasonable way to address the narrow question of cleanup. In fact, that's how idiomatic Rust code would handle it, too. That still leaves the general question of how to tell what errors a function may alert you to. Sanity suggests including it in a type signature, either in the return type or with checked exceptions. From what I've seen, the much more common solution in exception-using code is to ignore or forget the possibility of error.
- quotemstr 7y ago> Sanity suggests including it in a type signature, either in the return type or with checked exceptions Yet basically all of Rust's std::io propagates just io::Result<T>, which is Result<T, io::Error>, where io::Error is a generic result that could be anything vaguely related to the outside world. It's as useful as writing "throws Exception" in Java. C# gets along fine in practice using unchecked exceptions everywhere. Your point would be stronger if, say, File::Create specifically said it could fail only with things like disk-full, file-exists, and so on. But all it actually says is "this can fail". The information richness you're describing does not exist in practice.
- roblabla 7y agoThe problem with File::create is that it must interoperate with the existing OSes. And it turns out, File::create can fail for many many reasons. Your file can be on disk, it can be remotely accessible on the network, it can be virtual, etc... And the OS can return just about any error. Linux lists the errors returned on each operation, but they're really just advisory and not exhaustive. So for io::Error, we're basically constrained by what the OS provides. Not much Rust can do about it, I'm afraid.
- deleted 7y ago[deleted]
- galangalalgol 7y agoThis is my reason for hating exceptions. And impure functions. But I frequently use impure functions for interop or performance reasons. I haven't found a reason to throw exceptions.
- MauranKilom 7y agoBy that same logic we should not write multithreaded code, ever. It is still a good idea to strive for code where wrong things look wrong (which largely requires collocation), but you can't put that above everything else.
- couchand 7y agoI'm not sure I follow. Sure, we shouldn't just have threads blindly working from the same memory, but it seems perfectly reasonable to use tools like Go channels or MPSC queues to write sane code.
- quotemstr 7y ago> dosomething(); > cleanup(); This scenario is contrived. Use RAII and you never have to worry about whether cleanup() is called. You can even write, with some macros, something like FINALLY(cleanup()); dosomething(); And the right thing happens all the time automatically. But you shouldn't even have to use something like this, because cleanup should be implicit in the types you're using, e.g., unique_ptr. > But the possibility that dosomething might throw an exception means that cleanupmight not get called. You shouldn't care whether dosomething() throws: you should be using the language's scoped cleanup facilities to clean up on scope exit anyway, and doing that improves code clarity whether or not you use exceptions. You're doing the wrong thing and then complaining that exceptions make code unclear. It's like complaining that a car doesn't float after you've driven off a dock. Besides: exactly the same bug can occur in Rust, because panics. On the panic path, your cleanup() won't be called. And with "?", it's also easy to forget to call cleanup(), because "?" acts just like rethrowing an exception. Exceptions, in practice, produce clear code. All the confusion scenarios people sometimes claim arise from the use of exceptions are just contrived.
- verdagon 7y agoQuestion about proper destructor/RAII use: C++'s unfortunately falls over when you throw an exception from a destructor, and also doesn't let us return error codes from destructors (not that any other language does). How do you deal with this exceptional destructor problem in practice? Or does it not come up enough to warrant concern?
- quotemstr 7y agoDestructors shouldn't fail: that's why they're implicitly noexcept in C++11. (You can, however, throw inside a destructor so long as you catch before returning.) In practice, it's not a problem. If you really want to do something fallible on every success path, you can use an IIFE or a named function to isolate everything before the thing you want to do on the success path. What cases do you have in mind?
- verdagon 7y ago
- erik_seaberg 7y agoEverything can fail. Anyone who knows Java knows the difference between “a; b” (b depends on a) and “try {a} finally {b}” (b always runs). It’s not even worth the effort to prove that dosomething can fail today, just assume it will after someone changes something.
- couchand 7y agoThis is precisely the power of the Rust model of errors: nothing can fail unless either a) the type signature says it can, or b) it's not sane to try to recover from the failure.
- Groxx 7y agothat just means the language isn't being understood, in a very basic and very dangerous way. you don't need to check the call tree. without `finally`, it is not "always", period. we can debate at length about if that leads to desirable outcomes or not, but the behavior is unambiguous.