I wonder why languages keep carrying the C tradition of the "Break" statement in case/switch lists. Other languages do fine without them (VB is an example) by using set-like "match lists" to avoid the need for the "fall through". Some say they use Break, or the lack of it, to "fall through" to the next statement in some cases to avoid duplicate coding. However, this is bad practice in my opinion. One should use IF statements or refactor the "case set" instead of "falling through". The risk of forgetting a "Break" is too great.
Wouldn't your tests show that you had forgotten your break? -- MattStephenson?
Although tests can detect an error resulting from a missing break, significant effort can be expended in isolating the problem. It is simply too easy to overlook the missing break statement when reading code (probably far easier than when writing the code). There are far too few cases where it is beneficial to omit a break, and in those cases, one is forced to structure the code in a unique, inflexible sequence. As a long-time C programmer, I always did find it strange that break was used to delineate cases within a switch statement rather than braces. Then again, C was an advancement over assembly, and hindsight is always 20-20. -- WayneMack
{Not every shop supports UnitTesting, for good or bad}
Some of the complaints about Case statements seem related to the Goto-like problems of the Break statement, when in fact it is a specific syntax that is the problem, not the concept of Case lists.
Oddly enough, CeeSharp has brought back the break, but it's required in Case blocks. You forget a break, the compiler complains. However, it also bring's back the devil Goto, and specifically recommends it as a substitute for fallthrough-like functionality in switch blocks. So you get an odd mix of both worlds - you can do switches, breaks, and fallthroughs, bu you can't do it by accident. Of course, the whole resulting syntax is barbaric, but they have to keep the old C coders happy, I guess. -- MartinZarate
Check SwitchStatement for the AssemblyLanguage roots of the C switch semantics. One can use DuffsDevice as ammunition for either side of this argument.
How about an accompanying "no break" or "fall through" statement, with the compiler enforcing presence of either one? This way the programmer would be forced to clearly express intention without any functional or stylistic limitation. An approximation to this is a habit acquired by some people programming in preprocessed languages like the CeeLanguage: Defining such a statement through the preprocessor and using it instead of a comment. This approach, however, lacks the enforcement. -- SvenTuerpe
<Reacting to the second half of the above statement only. I agree with the intent expressed above, but disagree with the approach of customizing the language through preprocessor statements. These customizations lead to code that others will find difficult to pick up and use. I personally do not like the details of the switch/case statement in C/C++, but when I using it, I just have to take the good with the bad.>
If you define them correctly, you can have enforcement... the example below demonstrates runtime enforcement, not compile-time, but it's similar enough. Your tests will pick out the unexpected fallthrough straight off. Note the syntactic requirement of not having a semi-colon after 'fallthrough' is a pain in the arse, but (since it gives the same symptom of an assert failing on unexpected fallthrough) is quite easy to locate. -- AdamBerger
#include <stdio.h> #include <assert.h> #define default assert(0); default #define case assert(0); case #define fallthrough if(0) int main() { int i; while(-1 != (i = getchar())) { switch(i - '0') { case 0: printf("zero");break;/* zero */ case 1: printf("one");break;/* one */ case 2: printf("two");break;/* two */ case 3: printf("three"); fallthrough /* threefour */ case 4: printf("four");break;/* four */ case 5: printf("five");fallthrough /* fivesixseven */ case 6: printf("six");fallthrough /* sixseven */ case 7: printf("seven"); break;/* seven */ case 8: printf("eight");/* ERROR */ case 9: printf("nine");break;/* nine */ default: printf("\n"); } } return 0; }
Consecutive case statements are fine, but it's way too easy to omit the break from the sequence
case; {statements}; break; case;An IDE that warned you about missing breaks would be nice.
The break statement in a C/C++ case statement seems only to provide for "code reuse" without function calls. The two uses I have seen made use of this are:
Re: stacking a bunch of case labels to call the same code segment
The alternatives usually allow this by allowing multiple match labels. (I am thinking of VB-like right now). Example:
select on x ..case 'foo': ....blah_one() ..case 'bar','star','far':// note multiple match items ....blah_two() ..case 'zeek','Greek': ....blah_three() ..otherwise: ....blah() end select(Dots used due to TabMunging)
I like that approach. It's more intutive and less error-prone than the way C/Java requires a break statement.
If you have something really complex such that this is not sufficient, then if/elseif/else is often the preferred way.
It seems to me that this page could have a better name - something like FallThroughCaseStatementIsBroken?. The original poster's complaint seems to be about the behavior of the case statement in the CeeLanguage and its descendents, rather than the break statement.
In particular, the ability to "break" from inside a loop is valuable and convenient -- along with the ability to "continue" that loop to the next iteration.
Surely, therefore, it is the switch/case statement that is broken (or archaic), rather than "break".
When you been to refactor code down to its essentials, break no longer is necessary to exit loops, return is sufficient. It actually takes quite a bit of language specific knowledge to understand where a break statement branches to. Due to lesser usage, the continue statement is even less well understood. Both break and continue can be considered archaic in C/C++.
break may not be necessary, but it is useful. How does refactoring remove the need?
// print a list of items separated by spaces // eg. (123 456 789) void printList (std::ostream& out, Object list) { out << "("; for (;;) { print (out, car (list)); list = cdr (list); if (isNull (list)) break; out << " "; } out << ")"; }NickKeighley
The context is case/switch statements, not loops.
Not in the preceding paragraph, which I was commenting on
I would suggest removing the section line then.
How about a specific example, and let's see if there is not another way to get a table-like format.
Here's my favorite break-less "case" idiom: parsing command line arguments (I'll do it Java style here):
public static void main(String[] args) { switch(args.length) { case 4: handleFourthArgument(args[3]); case 3: handleThirdArgument(args[2]); case 2: handleSecondArgument(args[1]); case 1: handleFirstArgument(args[0]); break; default: // print warning on unexpected arguments }where the "handleXXXArgument" calls are typically some kind of in-line code.
if (args.length == 4) {handleFourthArgument(args[3]);} if (args.length >= 3) {handleThirdArgument(args[2]);} if (args.length >= 2) {handleSecondArgument(args[1]);} if (args.length >= 1) {handleFirstArgument(args[0]);}However, this is very slow! It means there are 4 comparisons. The case statement of above has only one comparison and one jump to an address calculated from the result of this comparison; It may not matter with 4 comparisons at the beginning of a program, but if you have 200 and the code is executed 800 times every second, then YES, it does matter!
Because that's all this particular program accepts.
Although the shown code segment raises a lot of design-level questions, an alternative to fix it and make the implementation as obvious as possible would be:
Making an implementation "as obvious as possible" is a goal you will never achieve - e.g. my grandma will still not be able to understand this, neither the original version nor the one you propose below. So it boils down to the level of experience you expect from someone to read this. Maybe for some it still gets more obvious if you rename "args" to "argumentsSuppliedAtTheCommandLine" or even "argumentSuppliedatTheCommandLineWhenTheProgramWasStarted" while others consider this cluttering up the source because they perfectly know what "args" is. Same for break in switch.
public static void main(String[] args) { switch(args.length) { case 4: handleFourthArgument(args[3]); handleThirdArgument(args[2]); handleSecondArgument(args[1]); handleFirstArgument(args[0]); break; case 3: handleThirdArgument(args[2]); handleSecondArgument(args[1]); handleFirstArgument(args[0]); break; case 2: handleSecondArgument(args[1]); handleFirstArgument(args[0]); break; case 1: handleFirstArgument(args[0]); break; default: // print warning on unexpected arguments break; }But this is very redundant. I strongly prefer the series of if's over this one.
This supports an arbitrary number of arguments:
ArgumentHandlers[] theArgumentHandlers = loadArgumentHandlers(); if (args.length > theArgumentHandlers.length) throw new TooManyArgumentsException(); for (int index=0; index<args.length; index++) { theArgumentHandlers[index].handleArgument(args[index]); }It is also non-obvious to the vast majority of developers. It perhaps could be argued that it is PrematureAbstraction. Fine. How's this, then?
for(int index = 0; index < args.length; index++) { // switch (index) {//Oh god, I just made a FOR-CASE structure. Forgive me gods of programming, this was the solution of least kludge... case 4: // handles this case only if args.length >= 4 HandleFourthArgument?(args[index]); break; //etc. } }Besides, this is really a bad example; if the handling of the arguments is similar, I'd rather write
void handleNthArgument(int argumentNum, string argumentVal) { ... }
I haven't had to fix a break-bug in years. Pages like this make me wonder what the decay function of specific bug types over years of experience is. I remember being bitten by (C/C++) uninitialized variables, operator precedence and = instead of ==. Now I rarely run into these anymore. There must be studies about this? -- AndrewQueisser
Different things tend to throw different people different ways. I find that it is hard to evangelize my preferences because people think too differently from each other. The bottom line for me is that they trip me from time to time when I forget a break statement. You cannot tell me that they don't trip me because you are not me. And, from an "elegance" standpoint they are ugly in my opinion, a goto-like remnant. I just find the alternatives mentally cleaner and less goto-like. If that is a personal esthetic, then so be it.
I'll side with AndrewQueisser on this. Break is a feature of the language. It is to be used/not used when necessary and code writers should be aware of situations when they should use/not use it.
BUT, reading this page got me thinking about the politics of this situation:
#1:
while (something) { if (something_else) do_something(); else break; // while loop will exit. }#2:
while (something) { // Admittedly, switch wouldn't be used for a true/false comparison. switch (something_else) { case true: do_something(); case false: break; // switch will break, but what about breaking the while loop? } }So, we're to use if() instead of switch() if we wanted to break out of a loop directly from the case block? It seems that as if() and switch() are so similar, they should both have the ability to break out of the enclosing loop.
(If switch was deemed necessary, the following solution could always be put in place:
#3:
while (something) { bool bShouldBreak = false; switch (something_else) { case true: do_something(); case false: bShouldBreak = true; } if (bShouldBreak) break; ... })
Or am I missing something?
David Grant
Does this count as something? #4:
while (something && something_else) { do_something(); }It covers the simple true/false situation. Some languages (Ada, Modula?) have labeled break/continue statements that let you jump out multiple levels. That handles heterogenous do_somethings(). Ofcourse, SwitchStatement is a CodeSmell, so only the true/false case matters.
I don't know about Ada or Modula, but Java and Perl both have labeled loops that let you specify which look you want to break or continue. A Java (1.5) example:
// find a Person in an Organization, given a personSought and a List<Organization> orgLoop: for (Organization org : orgs) { List<Person> people = org.getRoster(); personLoop: for (Person person : people) { if (person.equals(personSought)) { doSomething(person); break orgLoop; } } }Those Java guys sure did choose a funny keyword for "goto" ;) -- dc
No need here for the label on the inner loop, but I prefer all of them in a nested set to have names, if any of them do. -- DavidConrad
This page was originally about case/switch statements, not loops. I don't mind them for loops, but I do for case/switch.
History and Future of CeeLanguage
It should be noted that break originally existed because C was originally (and for the most part still is) essentially a mid-high-level portable assembler. In compiled machine code, the switch compiles to something that can jump to various points in the subsequent code-stream. To jump out of that code-stream past the last case to the next line of source requires another jump instruction in the machine code, and that's represented directly in the C source with the break. Fall-through is the natural flow of machine code, which is why it's the "default" in C if you don't break.
C needs break to accurately represent the machine code, and it can't possibly be removed from the language now. New languages, like Visual Basic, don't need to do that; the more logical and far more common situation is to always break, so that should be the default if nothing else is specified. An explicit "fall through" command would be nice, but always requiring an indication of one or the other is overkill and leads to visual messiness.
I don't recommend that it be removed from the language, only that a better alternative is also offered. In other words, extend the language with a better construct. The old one is simply deprecated, but supported.
Please note that an if, else if sequence also represent a set of jump statements. The "break" statement is not necessary in representing this flow, especially when one considers that both conditionals will often produce the same intermediate assembly code. If the scoping rules on a for loop can be changed, then allowing switch statements to move to a break-free syntax could also be done, with a similar affect on existing code.
But it is better OnceAndOnlyOnce to have the "key" variable be mentioned once at the top of the list instead of repeated for each item (if a==1 {...} elseif a==2 {...} elseif a==3 ...). One has to keep repeating "a==".
The suggestion is only that the "break" is not inherently required in the switch statement. The intent was to compare to the if sequence, not imply that the switch syntax be removed entirely in favor of the if syntax. Instead of using break, an alternative syntax could allow "switch(a) { case 1 {} case 2 {} case 3 {} ... }" Each of the brace pairs could also be replaced with a single line ending in a semicolon, as per standard C syntax rules.
Would this allow set lists?, such as:
switch(a) {// Example Sally-4 case 1,2,3 {...} case 4 {...} case 5,6 {...} ... otherwise {...} }I can dig it. We could keep the old syntax in for backward compatibility. Problem solved! Now we can all go home :-)
Maybe we could use the keyword "when" to distinquish between the old-style. This would even allow mixing for backward compatibility. Use "when" for the non-break-needing multi-set version of a sub-block, and "case" for the old kind. However, "case" fall-thru's (no-break) still would not be valid if the next sub-block is a "when". Fall-thru's would only work on adjacent "case" sub-blocks. This is so that one is not lured into a false sense of security because they are using the new-fangled one. (Generally it would be considered poor style to mix, but possible.)
But, that may all be too confusing for programmers. Perhaps just use different keywords in place of "switch" and "case" for the new style to distinguish from the old style (which would still be supported). Suggestion:
select(a) { when 1,2,3 {...} when 4 {...} when 5,6 {...} ... otherwise {...} }
I like how CsharpLanguage handles this, break is not nessisary in switch statements, but fallthrough is the default if you have a blank case. This covers the main use of fallthrough. --mp
Doesn't this make it incompatible with C++, risking dangerous behavior if old code is imported? The problem is that fallthrough should be explicit and break-out the default, not the other way around. But, I prefer the set-based approach that VB's case statements use instead of fall-through anyhow. Unlike fall-through, sets decouple the case blocks from each other.
A workaround to address the unwanted default fallthrough behavior might be to use the following set of macros
#define SWITCHCASE(thisOne, isThatOne) switch ( thisOne ) { case (isThatOne) : { #define THRUCASE(isThatOne)} case (isThatOne) : { #define THRUDEFAULT} default : { #define CASE(isThatOne)} break; case (isThatOne) : { #define DEFAULT} break; default : { #define SWITCHOFF}}Example 1:
SWITCHCASE ( nBody , .......... 0 ) return nSun CASE ... ( 5 ) THRUCASE ( 6 ) THRUCASE ( 7 ) THRUCASE ( 8 ) return nGiantGaseous DEFAULT ...... return nDwarfRocky SWITCHOFFExample 2:
SWITCHCASE( getchar() , ...... 'y' ) THRUCASE ( 'Y' ) ........... return ID_YES CASE ( 'a' ) THRUCASE ( 'A' ) ........... return ID_ABORT CASE ( 'n' ) THRUCASE ( 'N' ) THRUDEFAULT return ID_NO SWITCHOFFSome pros:
- fallthrough behaviour must be explicitly chosenSome cons:
- ProgrammingDialect? : departing from familiar syntax might make maintenance harder.
If I remember correctly the Given/When (GivenWhen) of PerlLanguage has implicit breaks, and a keyword for fallthrough (continue). This should be what we do. If I wanted fallthrough, I'd ask for it*
How about this:
I don't know. You've described what you have in mind, but not how it's better.
{I think I'd have to seem some semi-realistic code samples using those. Remember that readability is often valued in an org above code-size reduction.}
Is it readability per line, file or project? IMO we want to get the big one, so code-reduction facilitates readability, probably even if per-line suffers for it.
{I don't trust other developers; I'd rather the language enforce line spacing. I value readability and consistency when reading others' code. I don't have FastEyes. A related line-spacing and consistency discussion can be found at ChallengeSixLispVersionDiscussion}
So now you say you want enforced coding style? How does that touch on any previous point? Anyway, you could have that too, no problem, though if you do you should make white-space meaningful, or it is just aggravating. That means dropping block markers... Whatever floats your boat.
{No language will make everybody happy because our WetWare and target project types are all different.}
See also: CaseStatementsConsideredHarmful, ItsTimeToDumpCeeSyntax, ChallengeSixLispVersionDiscussion