No one's claiming that requiring braces on conditionals is any kind of silver bullet. The Apple bug is just a concrete example of how a different language design decision (requiring braces) could have avoided this particular bug. The fact that bugs can also be introduced via an erroneously designed switch statement is a red herring.
And I also don't think anyone is claiming that the skill of the developers doesn't matter; of course it does. But the fact that no single change to any one part of the process (tools, developers, methodology, etc.) can prevent all bugs does not in any way negate the value of improving any one of those parts.
> The Apple bug is just a concrete example of how a different language design decision (requiring braces) could have avoided this particular bug.
I don't see how requiring braces would have necessarily avoided the Apple bug. Whoever was responsible for creating the extra "goto fail" might still have left it outside the scope of an if-statement.
if (error_of_first_kind)
{ goto fail; }
if (error_of_second_kind)
{ goto fail; }
if (error_of_third_kind)
{ goto fail; }
if (error_of_fourth_kind)
{ goto fail; }
if (error_of_fifth_kind)
{ goto fail; }
{ goto fail; }
if (error_of_sixth_kind)
{ goto fail; }
The_truly_important_code_handling_non_erroneous_case
It all depends on how the extra line got there in the first place. Presumably there was a mistake made while editing the file. Braces don't necessarily eliminate those kinds of problems.
Assuming this is "C with required conditional braces", this would exhibit the same behavior as the original bug. Introducing a new scope with {} is valid (and regularly used) C.
The problem here is that, visually, this class of error doesn't stand out, because, when you scan the code, it just looks ok (albeit, ever so slightly less right in your example). And that brings us back to language design.
It's hard to have this type of bug and not bring up Python as an example, because Python behaves as your eye processes it.
if error_of_fifth_kind:
goto fail
goto fail
Those two gotos are in the same block, because they are indented the same. People continue to freak out over significant spacing, but there is a lot of value in it.
Meyer argues that a language shouldn't have a separate block statement. Instead block structure should be integrated in the control flow statements. That would result in this code: (I'm keeping your indentation style)
if error_of_fifth_kind
then goto fail end
then goto fail end
Interesting. This works because there's no explicit "begin" to go with the "end". I would normally consider this kind of asymmetry to be a language design flaw, but I suppose it does address this particular problem.
Of course, I think you could still end up with this pretty easily:
if error_of_fifth_kind then
goto fail
end
goto fail
if error of sixth_kind then
goto fail
end
Well, Meyer doesn't let any chance to bash C/C++ go unused :) But a good syntax should minimize the chance that an accidental insertion/transposition/deletion of a character/word/line results in a valid program.
[A dramatic example of bad syntax comes from a FORTRAN, where a loop between the current line and the line with label 10 looks like this:
DO 10 I=1,100
and this code:
DO 10 I=1.100
declares a variable named "DO10I". Hopefully your compiler will warn that label 10 is unused.]
if error_5 {
goto fail;
}
/* line deleted here */
goto fail;
}
if error_7 {
goto fail;
}
Many ways to skin the same cat, but it boils down to following good practices that emphasize errors like this (presumably removing an error condition and leaving the goto).
Congratulations! You've constructed a strawman argument.
Sure, if somebody used a brace formatting rule like the one you've given, which is explicitly done in a way that wouldn't have avoided the issue, it still would've happened. However if somebody was to consistently place the statements on a line of their own as just about any real world brace formatting rule would, the issue would be avoided. Thus:
if (error_of_first_kind) {
goto fail;
}
/* ... */
if (error_of_fifth_kind) {
goto fail;
goto fail;
}
if (error_of_sixth_kind) {
goto fail;
}
The_truly_important_code_handling_non_erroneous_case
Is safe, as would be:
if (error)
{
goto fail;
}
if (error)
{
goto fail;
}
Or:
if (error)
{
goto fail;
}
The idea behind always using braces is to _avoid insertion errors_ when editing. If you need to jiggle the braces around to go from one statement more than one, you're doing it wrong.
Ah, a fair point! However, since we're talking about the hypothetical language what-C-might-have-been, I would suggest a further improvement: require braces for all conditionals /and/ require that the opening and closing braces shall not be on the same line.
That would really start to hurt readability. There are plenty of instances where the body is very similar save for a small change, and having it lined up in a grid makes it much easier to read. As a contrived example, you can look straight down to see the action values here:
if(condition_1) { action(318); }
if(condition_2) { action(219); }
if(condition_3) { action(142); }
In my opinion, the bigger risk is having a single-statement if have its payload on the next line.
if(x.open()) x.close(); //very difficult to add another statement accidentally here
if(x.open())
x.close(); //much easier to do so here
But of course I wouldn't want to start treating whitespace as special in C.
> The fact that bugs can also be introduced via an erroneously designed switch statement is a red herring.
I really didn't think so. Nor did I think it was slippery slope. If our goal is to make a language that's safe against trivial mistakes like this, it's the logical next step. It's just as easy to forget a break; on a case statement as it is to forget to add braces around a multi-statement if block. So why not make it mandatory to indicate whether you want execution to stop or continue at the end of every case statement, in the name of good practice?
I'm also not saying that enforcing {} is really bad in and of itself. Just that the line of thinking is bad. There are so many ways to get burned programming in C, and this is one of the dumbest ways imaginable. If something like goto fail got through with no auditing or testing, it is indicative of much bigger problems than a minor language detail like single-statement if's.
This is the kind of bug that should only hit junior programmers in high school. And even if professionals accidentally mess it up, there should be methods in place for catching and fixing it long before it reaches production.
And I also don't think anyone is claiming that the skill of the developers doesn't matter; of course it does. But the fact that no single change to any one part of the process (tools, developers, methodology, etc.) can prevent all bugs does not in any way negate the value of improving any one of those parts.