7 ms·
I'm not sure you're giving that argument all what it deserves. Control structures and function calls are a very statically-well-behaved control flow: unlike go
by Banana699 4y ago
I'm not sure you're giving that argument all what it deserves.
Control structures and function calls are a very statically-well-behaved control flow: unlike gotos, when you see a jump you know exactly where its destination is (runtime polymorphism for functions\methods is... an exception, and that's why some people don't like it.), unlike a goto where that might be a dynamic address. The Return is the only modern goto with a dynamic destination, but even then the destination is not completely arbitary (stack discipline), and actually the destination is irrelevant if the function is a well-named non-leaky abstraction, the function simply returns to its "caller", wherever it may be.
Furthermore, traditional control flow is Structured, it "owns" its jump labels. Nobody can jump to an else clause of a conditional except that conditional, nobody can jump to a loop start except the code immediately before it or the loop end (breaks and continues are, again, limited exceptions to this, and that's why some people don't like them), only the Returns of a function can jump back to the callsite of that function. As utterly trivial and basic as that sounds, it's a marvel. Gotos are nothing like this, they jump to public labels, anybody can jump to the label, it's a communism of control flow.
Exceptions break all of that. You don't statically know what's the destination of a Throw is, and unlike a Return, the destination is not irrelevant, the program is disrupted, you really need to know where that Throw is going to end up. Throws don't own their catches as well, any Throw can jump to the same Catch.
In a very real sense, Exceptions are more dangerous gotos than ifs and whiles.
- ncmncm 4y agoYou manifestly don't care what the destination of an exception is. Aggressively so. The only choice to make is: here, or not here. If here is at a high enough level to do something intelligent about it, catch it. If not, it is not your problem: there is nothing to be done, so you do that. Generally, a good program will have very few places where exceptions are caught, and the code there is easily exercised and well tested. The destructors that run on the way there are also well exercised because they run with or without exceptions. Beware code that only ever runs in exceptional cases. It grows bugs when what was correct becomes wrong as the code changes around it.
- goto11 4y agoExceptions are like returns, they go back up the call stack. You cant statically determine where a return will end up either, since it depends on where the function was called. > unlike a Return, the destination is not irrelevant, the program is disrupted, you really need to know where that Throw is going to end up. I don't follow your reasoning here. A function should not know where it returns to and a throw should not know where the exceptions will be caught. That is the point of those constructs. If you know exactly at the point of throwing how you want the exception to be handled, then you wouldn't need to throw an exception in the first place.
- Banana699 4y agoYes exactly, exceptions are much much nastier returns. Returns only go up exactly 1 frame, and the call/return pairs are balanced no matter what. Exceptions can unwind the entire stack, and the throw/catch pairs aren't balanced automatically. Returns of a single function own their call sites, nobody jumps to their call site except them, but a single catch accept every single throw from arbitrarily deep into the stack. Exceptions are an entire call/return system hidden beneath the vanilla call/return system, with less thought in the design and more foot guns. >I don't follow your reasoning here Okay forget it, it was a somewhat subjective point, my main point is as above, exceptions are extremely spicy returns, they interact dangerously with the regular call/return system and scoping. That's essentially all of what objections to exceptions boil down to, and it's a legitimate reason to hate and avoid them.
- wizofaus 4y ago"it's a legitimate reason to understand them properly and use them appropriately" FIFY.
- ncmncm 4y agoExceptions interact exactly deterministically with the call/return system. There is no scope for surprises. In particular, destructors run, absolutely reliably. Rely on them. Using modern C++, you rarely need to code your own destructor; usually the language provides one that is guaranteed to be correct.
- shadowofneptune 4y agoFor some of these reasons, I prefer to write this: if (condition){ throw } else { <rest of code> } Rather than: if (condition) { throw } <rest of code> The justification, which I found in this presentation (https://youtu.be/SFv8Wm2HdNM https://youtu.be/SFv8Wm2HdNM) is that if you only looked at the block structures, could you tell that the rest of the code might never run?
- layer8 4y agoThe counter-argument is that you can get arbitrarily deeply nested code that way. The second style uses what is known as guard clauses. The way to think about it is that it establishes a precondition that the rest of the code can then assume is met. It is similar to an assert statement, or to precondition checks in a design-by-contract language. You don’t want to indent the regular-case code just because it is preceded by a guard. If you have multiple such conditions, it’s like going through a checklist of what you have to check before continuing with the actual operation. Moreover, if you have exceptions, any line of code containing a function call can potentially throw, so that the subsequent lines at the same level are not executed. (So you can’t see from the block structure if code is always executed anyway, when the language uses exceptions.) This is as if you’d factored out the guard clause into a separate function that you invoke at the location where you previously had the guard clause. In the if-then-else alternative, you can’t factor out the if-then separately from the else. The guard-clause style thus also affords more flexibility regarding code organization.
- shadowofneptune 4y agoI came across the awkwardness this introduces to guard clauses a while back, and found that it could act as a smell. Instead of drip-feeding the validation, do it all at once if you can. I do see what you mean with your second paragraph, though. It's a good argument against.
- layer8 4y agoDoing it all at once suggests factoring out all checks into a separate function, in which case it also makes sense to throw from that function, in order to give good exception messages depending on the specific error, and possibly including associated data in the exception. Which means that at the call site you won’t have any indented block structure differentiating the validation call from the rest of the function. When programming with exceptions, it is the common case that a function consists of a sequence of steps each of which can fail and thus abort the function via an exception. Simple validation steps are not a special case in that respect.