> 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.
It's harder. If you're typing ?, you're aware that you're dealing with a result or option (with the values of the error case clearly documented in the type system), and aware of the fact that you might be returning early, and have forced your own function's signature to also be a result or option.
In contrast, I've had code where finger->Position.X occasionally threw. Because a finger was released, invalidating the old finger ID, before it even gave you the chance to process the finger up event to realize you shouldn't query that finger. Did you know you need to wrap every finger position query in a try/catch? I didn't, so this was just a rare crash bug for awhile. What exception did I catch? I don't remember (but not a null reference nor an access violation exception), and it's not documented, so in new code I guess I'd just try { ... } catch (Platform::Exception^) {} despite the fact that we all know catch-all do-nothing statements are terrible. At least I can minimize the scope!
For bonus points, the exception handling overhead was heavy enough that the framerate of the game we were working on would stutter if you pawed at the touch screen. Better than crashing, but still terrible. Not having source access to the throwing code, and touch release events being delayed, this was the least horrible option available though. Yaaaay.
Was this a nasty edge case and not indicative of all use of exceptions? Yes. Do I eventually encounter such a nasty edge case in most, if not all, large scale projects involving exceptions in practice? Also yes. Often enough that it influences my preferred error handling mechanisms, even.
Do I encounter such a nasty edge case in return based error handling? Not in the wild. The use of error codes makes it much clearer what can fail. On the rare occasion I've encounter something similar, it's been when harshly stress testing cross platform API abstractions in contrived tests, when disambiguating between multiple error codes. Occasionally, the underlying API does something strange and returns an unexpected error code (either a unique error code, or one of the usual error codes in unusual circumstances) and I take a suboptimal error handling path.
Usually, at worst, the end user would've need to retry something, even if I hadn't caught and worked around the underlying API weirdness.
> Exceptions, in practice, produce clear code.
Do you catch NullReferenceException s instead of writing basic if conditionals to check values for null? Probably not. For such unexceptional cases it's less clear (which reference, exactly, was null?), and the performance overhead is often unacceptable.
For more exceptional uses of exceptions, you can sometimes get clearer code. It's often brittle and mishandles rare edge cases, but it's clearer for the happy path at least. But I will happily sacrifice a little of that clarity to make that code handle the edge cases properly instead of crashing - because those exceptional cases aren't really all that exceptional after all.
If you're wrapping lots of things in try/catch, you're doing something very wrong in the first place. People who think you need to do that are using exceptions wrong. It's no wonder that they come to dislike them.
Your example sounds like a badly structured piece of code. If your finger-query code is racing against your event processing code, that's a bug. You violated the function's contract. The exception was telling you about the bug. Don't shoot the messenger.
Maybe you wanted to write the moral equivalent of optional<Coordinate> getFingerPosition(FingerID finger). Nothing stops your using the optional value pattern in exceptional code.
I'm sympathetic. Should contracts be more explicit in code? Sure. In C++, the default should be noexcept, and it should be a compiler error to call a non-noexcept function from a noexcept one. But that's an argument for doing exceptions better, not an argument for abolishing them.
> Do you catch NullReferenceException s instead of writing basic if conditionals to check values for null?
If a pointer is null where it's not supposed to be null, you crash. That's a contract violation, and failing fast in the face of contract violation is the right thing to do. Are you one of those people who writes out null checks at the start of every function? I dislike code like that very much.
> If you're wrapping lots of things in try/catch, you're doing something very wrong in the first place. People who think you need to do that are using exceptions wrong. It's no wonder that they come to dislike them.
I agree something has gone terribly wrong. But in this case, it's the initial API design. Not my fault!
> Your example sounds like a badly structured piece of code. If your finger-query code is racing against your event processing code, that's a bug. You violated the function's contract. The exception was telling you about the bug. Don't shoot the messenger.
That's what it sounds like, and if the original API was sane you'd be correct. The original API was not sane. As I recall, this might've been when handling finger moved events, that the position query threw, because a not-yet-recieved finger up event had already invalidated the finger.
One could say that I violated the contract by checking the position of released fingers. But that contract was designed in such a way as to be impossible to consistently fulfill. It's terrible API design, and arguably a bug, but not my bug - I just wrote the workarounds.
One can share blame with the API authors, but this is a reoccuring pattern with APIs that use exceptions, so I'm willing to share the blame with exceptions too - they seem prone to misuse.
> Maybe you wanted to write the moral equivalent of optional<Coordinate> getFingerPosition(FingerID finger). Nothing stops your using the optional value pattern in exceptional code.
The API I exposed (wrapping the underlying system API) was similar. Well, I didn't use optional, because my API wasn't prone to race conditions despite the underlying system API being prone to race conditions. (Returning a position that's been stale for a few milliseconds seemed acceptable in that case.)
Of course, this had the performance problems I mentioned earlier, but those were fundamentally unfixable.
> I'm sympathetic. Should contracts be more explicit in code? Sure. In C++, the default should be noexcept, and it should be a compiler error to call a non-noexcept function from a noexcept one. But that's an argument for doing exceptions better, not an argument for abolishing them.
Except I've yet to see exceptions done well. Java tried to go a step further with checked exceptions, but that turned out pretty poorly too. I have yet to see exceptions done well, which is an argument for abolishing them until such a time as someone does do them better.
EDIT: Particularly topical in this thread - almost every single exception system I've ever used has caused me grief trying to translate errors across C ABI boundaries at some point or another. Per https://doc.rust-lang.org/nomicon/unwinding.html, unwinding across FFI boundaries is undefined behavior - and I've seen some really nasty bugs from C++ exceptions, C longjmps, C# exceptions, Ruby exceptions, etc. all trying to unwind over ABI boundaries too. And then I get to try and sanitize a whole slew of call sites to not invoke undefined behavior. Yuck.
> If a pointer is null where it's not supposed to be null, you crash. That's a contract violation, and failing fast in the face of contract violation is the right thing to do. Are you one of those people who writes out null checks at the start of every function? I dislike code like that very much.
There are plenty of cases where optional-and-missing values are represented with null in most null supporting programming languages. I'll typically lean towards the null object pattern to get rid of the null checks where sane and possible, but sometimes null checks are the sane and simple solution. This is the case I was asking about.
But sure, let's cover cases where it's not supposed to be null too. I won't just check/bail/ignore, that's terrible for the debugging and bug finding experience. But I probably don't want megs of minidump and a confused end user's error report, just because they installed a mod pack that was missing a sound asset resulting in a null pointer somewhere, either. Just crashing is also unacceptable - I want the error report, which I might not get, and the gamer is probably happier with a missing sound effect instead of a crash.
Instead, I'd rather insert a null check. For the dev side, you can have your check macros insert an automatic breakpoint, log (for the mod makers), report the error via sentry.io (for catching released bugs in your unmodded game), or even explicitly crash for your internal builds (so you and QA can find bugs). Just as easy to debug as a crash (if not easier thanks to smart error messages), much less end user angst.
> Instead, I'd rather insert a null check. For the dev side, you can have your check macros insert an automatic breakpoint, log (for the mod makers), report the error via sentry.io (for catching released bugs in your unmodded game), or even explicitly crash for your internal builds (so you and QA can find bugs). Just as easy to debug as a crash (if not easier thanks to smart error messages), much less end user angst.
I really wish it were feasible to write more code as out-of-process components. The Right Way to handle unreliable mods is to just run them in their own process where they can't hurt anything. I really like that COM tried to make this approach easy. I feel like we've regressed since COM's heyday.
It's harder. If you're typing ?, you're aware that you're dealing with a result or option (with the values of the error case clearly documented in the type system), and aware of the fact that you might be returning early, and have forced your own function's signature to also be a result or option.
In contrast, I've had code where finger->Position.X occasionally threw. Because a finger was released, invalidating the old finger ID, before it even gave you the chance to process the finger up event to realize you shouldn't query that finger. Did you know you need to wrap every finger position query in a try/catch? I didn't, so this was just a rare crash bug for awhile. What exception did I catch? I don't remember (but not a null reference nor an access violation exception), and it's not documented, so in new code I guess I'd just try { ... } catch (Platform::Exception^) {} despite the fact that we all know catch-all do-nothing statements are terrible. At least I can minimize the scope!
For bonus points, the exception handling overhead was heavy enough that the framerate of the game we were working on would stutter if you pawed at the touch screen. Better than crashing, but still terrible. Not having source access to the throwing code, and touch release events being delayed, this was the least horrible option available though. Yaaaay.
Was this a nasty edge case and not indicative of all use of exceptions? Yes. Do I eventually encounter such a nasty edge case in most, if not all, large scale projects involving exceptions in practice? Also yes. Often enough that it influences my preferred error handling mechanisms, even.
Do I encounter such a nasty edge case in return based error handling? Not in the wild. The use of error codes makes it much clearer what can fail. On the rare occasion I've encounter something similar, it's been when harshly stress testing cross platform API abstractions in contrived tests, when disambiguating between multiple error codes. Occasionally, the underlying API does something strange and returns an unexpected error code (either a unique error code, or one of the usual error codes in unusual circumstances) and I take a suboptimal error handling path.
Usually, at worst, the end user would've need to retry something, even if I hadn't caught and worked around the underlying API weirdness.
> Exceptions, in practice, produce clear code.
Do you catch NullReferenceException s instead of writing basic if conditionals to check values for null? Probably not. For such unexceptional cases it's less clear (which reference, exactly, was null?), and the performance overhead is often unacceptable.
For more exceptional uses of exceptions, you can sometimes get clearer code. It's often brittle and mishandles rare edge cases, but it's clearer for the happy path at least. But I will happily sacrifice a little of that clarity to make that code handle the edge cases properly instead of crashing - because those exceptional cases aren't really all that exceptional after all.