views:

1952

answers:

28

Sometimes you need to skip execution of part of a method under certain non-critical error conditions. You can use exceptions for that, but exceptions generally are not recommended in normal application logic, only for abnormal situations.

So I do a trick like this:

do
{
   bool isGood = true;

   .... some code

   if(!isGood)
       break;

   .... some more code

   if(!isGood)
       break;

   .... some more code

 } while(false);

 ..... some other code, which has to be executed.

I use a "fake" loop which will run once, and I can abort it by break or continue.

Some of my colleagues did not like that, and they called it "bad practice". I personally find that approach pretty slick. But what do you think?

A: 

I think that there is nothing basically wrong with the technique. I think that I would make sure that the bool is named something more descriptive than isGood. Although, now that I look at it, why would it not work to put all the code that is in the loop into a single if(!isGood) block? The loop only executes once.

EBGreen
Because "isGood" might be set in multiple places in the fake loop.
Paul Tomblin
+3  A: 

I have no problem with that as long as the code is readable.

JesperE
+29  A: 

You're pretty much just disguising a "goto" as a fake loop. Whether you like gotos or not, you'd be just as far ahead using a real undisguised goto.

Personally, I'd just write it as

bool isGood = true;

   .... some code

   if(isGood)
   {
   .... some more code
   }

   if(isGood)
   {
   .... some more code
   }
Paul Tomblin
Nesting the if statements would actually be a better idea, so it doesn't need to check isGood again if it evaluates false the first time around.
SauceMaster
Nesting would increase the indent level, making the code harder to read. The redundant checks of isGood aren't going to hurt, so I think it's fine as is.
Mark Ransom
Nesting ifs leads to slope code, which leads to your team mates jamming pencils into your eyes.
jonnii
That is much better than GOTO. You can clearly see the scope from which you "jump out".<br>My code does not introduce another level of nesting. Usually those things are already inside others conditions. I do not like many levels of indentations.
Ma99uS
@Ma99uS - if you're not going to accept an answer that disagrees with you, why bother asking the question?
Paul Tomblin
@Ma99uS - Paul Tomblin's answer is correct. Your code is what I would call an "abmonination". Voting down the question for not appreciating good advice.
Simucal
@Paul Tomblin, Simucal - Why did you think I will not accept that answer? Read my comment later in the thread. I agreed with criticism. Do not make opinions prematurely next time about people you do not know.
Ma99uS
I was under impression we were having dialog here, where people has to be convinced not forced to accept the best answer. Or do I have some time limit, during which I have to accept an answer?
Ma99uS
You've been hit by a velociraptor: http://xkcd.com/292/. Best thing is to keep quite and hope the goto police don't realise that *any* loop is a "disguised goto", because then we'll really be in trouble.
Steve Jessop
@Ma99uS - I wasn't referring to "accept" in the sense of clicking the checkmark. I was referring to the fact that I gave several reasons why what you wrote is bad, and your response was "That is much better than GOTO" and other reasons why you don't plan to listen to my advice.
Paul Tomblin
@Ma99uS -- You've justified your GOTO-like code with comments that you don't like deep indentations and you don't want to introduce functions. In short, you seem to prefer imperative to structured programming techniques. That may well put you significantly out of sync with your colleagues.
Larry OBrien
I highly agree with this, @Ma99uS. Goto is sometimes necessary, and the loop trick is actually neat... But most of the times, they're just bad coding and there is a better way to write it which will look better.
Cawas
+11  A: 

You have complicated non-linear control flow inside a difficult to recognize idiom. So, yes, I think this technique is bad.

It might be worthwhile to spend sometime trying to figure out if this can be written a little nicer.

Pramod
I have worked in languages where it is a common idiom, for example one with "block ... break ... endblock". It's not complicated once you get used to it: jumps are strictly forwards and all to the end of the block. It's actually simpler than if/else, let alone if, else if, else.
Steve Jessop
I realize that nested if's can get ugly pretty quickly. I'd rather not have any of these idioms. I strongly suspect it might be possible to clean this code up into something that reads a lot easier, but its hard to say how without knowing the specifics.
Pramod
Agreed - generally when I used block/break/endblock in that language it was for those cases where I had a laundry list of conditions to check before proceeding. For cases where you're doing serious work at every step, I'd say this approach is less likely to be the best.
Steve Jessop
+24  A: 

Why use a fake loop? You can do the same thing with a method and it probably won't be considered a "bad practice" as it is more expected.

someMethod()
{
   .... some code

   if(!isGood)
       return;

   .... some more code

   if(!isGood)
       return;

   .... some more code

 }
g .
Having multiple returns sprinkled throughout your functions is typically bad practice as well. If you are using a non-garbage collecting language (e.g., C, C++) you are opening yourself up to memory leaks.
dwj
I'd probably agree in this case given the hint of "some more code" between. Though multiple returns can be a good strategy to prevent deep nesting. Typically be at the top and not "sprinkled" throughout the function.
g .
the code on the bottom of the method has to get executed always. So I can not return from the function early. I and I do not want to create more functions.
Ma99uS
Multiple returns is bad if you are not using RAII/Shared Pointers. With those tools however, early-out programming is definitely useful and preferrable to deeply nested logic, as g said.
Brian Stewart
I think it's incorrect to equate the confusion of multiple returns with the confusion of the original code, which combines complex control-flow logic (whether his 'fake loop' or rewritten as per Tomblin's answer) with domain logic ('... more code here...'). +1 on introducing a function.
Larry OBrien
@Ma99uS why don't you want to create more functions? Do you have some sort of religious or moral objection to them?
Wedge
I have nothing against functions. But in some cases if you "rip" a part of code into separate function, you would have to pass TONS of parameters into them, and back. That would create completely different readability problems.
Ma99uS
@Ma99uS if you have code segments that depend on tons of local variables you should DEFINITELY refactor it anyway.
Wedge
@Wedge +1. @Ma99uS: if you have 'tons of local variables' relating to the 'more code' block _and additionally_ you have variables relating to control flow (isGood and so forth), then surely you are mixing concerns inside a single function.
Larry OBrien
@Larry +1, definately mixing concerns. Your code should be structured in such a way that breaking it up into different functions don't require a ton of local variables.
Simucal
@Ma99uS if your code is so big and messy that ripping out separate functions would be painful, then it's too big and messy. Refactor your way into a happier place. Start with the low-hanging fruit.If you succumb to that kind of thinking ("it's too hard"), your code will grow worse and worse.
Carl Manaster
While I love functions and methods, I dislike multiple returns a lot. As @Larry said, it's just as confusing as multiple breaks in the original, or using gotos. So, doing this isn't any better.
Cawas
+1  A: 

It depends on what the alternatives are. You have to admit that the code you posted is somewhat ugly. I wouldn't say it's clear. It's a kind of a hack. So if using some other coding solution would be worse, then ok. But if you have better alternative, don't let the excuse "it's good enough" comfort you.

phjr
You know, I kinda like that code, I do not think it is UGLY. I even think it sorta "smart". But more accepted if(condition){ do stuff} is easier to understand of course.
Ma99uS
+8  A: 
public bool Method1(){ ... }
public bool Method2(){ ... }

public void DoStuff(){
    bool everythingWorked = Method1() && Method2();
    ... stuff you want executed always ...
}

The reason why this works is due to something called short circuit logic. Method2 won't be called if Method1 returns false.

This also has the additional benefit that you can break your method into smaller chunks, which will be easier to unit test.

jonnii
Works great, as long as the original code didn't include lots of temporary state in local variables.
Mark Ransom
You can always promote this operation so that it lives in its own class, and then you can have all the temporary state you want.
jonnii
Be careful about the order of Method1 and Method2 being called. I'd rather you did worked worked
Matt Cruikshank
Why? It's not like the order of evaluation is going to suddenly change.
jonnii
@Matt - the order of execution is rigidly specified by C/C++, it's not one of those implementation dependent things. No need to jump through hoops.
Mark Ransom
A: 

I think I'd have to agree with your colleagues just because of readability, it's not clear atfirst glance what you are trying to accomplish with this code.

Why not just use

if(isGood)
{
...Execute more code
}

?

Fry
+4  A: 

What you're trying to do is non-local failure recovery. This is what goto is for. Use it. (actually, this is what exception handling is for -- but if you can't use that, 'goto' or 'setjmp/longjmp' are the next best thing).

This pattern, the if(succeeded(..)) pattern, and 'goto cleanup', all 3 are semantically and structurally equivalent. Use whichever one is most common in your code project. There's much value in consistency.

I would caution against if(failed(..)) break; on one point in that you're producing a surprising result should you try to nest loops:

do{
   bool isGood = true;
   .... some code
   if(!isGood)
       break;
   .... some more code
   for(....){
      if(!isGood)
          break; // <-- OOPS, this will exit the 'for' loop, which 
                 //  probably isn't what the author intended
      .... some more code
   }
} while(false);
..... some other code, which has to be executed.

Neither goto cleanup nor if(succeeded(..)) have this surprise, so I'd encourage using one of these two instead.

Aaron
+1  A: 

Its a very strange idiom. It uses a loop for something its not intended and may cause confusion. I'd imagine this is going to span more than one page, and it would be a surprise to most people that this is never run more than once.

How about using more usual language features like functions?

bool success = someSensibleFunctionName();

if(success)
{
   ...
}

someCommonCodeInAnotherFunction();
Dave Hillier
+4  A: 

Basically you just described goto. I use goto in C all the time. I don't consider it bad, unless you use it to emulate a loop (never ever do that!). My typical usage of goto in C is to emulate exceptions (C has no exceptions):

// Code

if (bad_thing_happened) goto catch;

// More code

if (bad_thing_happened) goto catch;

// Even more code

finally:

// This code is executed in any case
// whether we have an exception or not,
// just like finally statement in other
// languages

return whatever;

catch:

// Code to handle bad error condition

// Make sure code tagged for finally
// is executed in any case
goto finally;

Except for the fact that catch and finally have opposite order, I fail to see why this code should be BAD just because it uses goto, if a real try/catch/finally code works exactly like this and just doesn't use goto. That makes no sense. And thus I fail to see why your code should be tagged as BAD.

Mecki
with goto you do not see where the jump is going to be. In my code, the scope is very clear. You have curly braces, and you jump out of them.
Ma99uS
@Ma99uS: with goto, the destination is labeled. With your code, a reader must trace braces. I fail to see the improvement.
Shog9
@Shog9 It's easier to trace braces than pinpoint the goto and its labels. But both are bad anyway. The current accepted solution have a great **conclusion** on this.
Cawas
@Cawas: According to the accepted answer, goto is preferable to anything else (unless you have try/catch, which you don't have in plain C). You have read the accepted answer to the end, haven't you? ;-)
Mecki
Yes, and it does say they're all bad (because it's dodging on saying any is really that good). It's just listing from the least-worst to the worst. :D
Cawas
+1  A: 

I would consider this bad practice. I think it would be more idiomatic, and generally clearer if you made this a function and changed the breaks to returns.

Draemon
+1  A: 

If your code is doing something other than the plain meaning of the constructs in place, it's a good sign you've ventured into "cute" territory.

In this case you have a "loop" that will only run once. Any reader of the code will need to do a double-take to figure out what's going on.

If the case where it isn't "good" is truly exceptional, then throwing exceptions would be the better choice.

JohnMcG
+1  A: 

Split your code into smaller chunks of functional elements - so you could split the above into a function that returns instead of breaking.

I don't know if the above is bad practice but it's readability is a little off and may be confusing to others who might have to maintain the source.

widgisoft
A: 

If splitting code between if(!isGood) break; into separate functions, one can end up with dozens of functions containing of just a couple of lines, so that doers not simplify anything. I could not use return because I am not ready to leave the function, there is still stuf I want to do there. I accept that probably I should just settle for separate if(isGood) {...} condition for every code part which I want to execute, but sometimes that would lead to A LOT of curly braces. But I guess I accept that people does not really like that type of construction, so conditions for everything winds!
Thanks for your answers.

Ma99uS
You misunderstand the suggestion. You create a *new* routine that only contains the code you are putting in your bogus loop. Same effect as your bogus loop, but it removes that convoluted sequenceing logic into its own subroutine. That makes both chunks of code easier to understand.
T.E.D.
having more smaller methods is normally better than having one massive method.
jonnii
That would force me to create separate function not because code logic segments dictate this (like it should be), but just because of separate function WORKS in that case. I do not want to be forced to create functions under that requirement.
Ma99uS
the function with "fake loop" construct is not necessarily big, so no reason to break it up here.
Ma99uS
+1  A: 

You can use exceptions for that, but exceptions generally are not recommended in normal application logic, only for abnormal situations.

Nothing's wrong with using exceptions. Exceptions are part of application logic. The guideline about exceptions is that they should be relatively rare at run time.

David Nehme
+1  A: 

To me what you are doing is bad in so many ways. The loop can be replaced by putting that code in a method.

I personally believe that if you have to put a ! in front of your conditions then you are looking for the wrong thing. For readability make your boolean match what you are checking for. You are really checking if there is an error or some bad condition, so I would prefer:

If (isError)
{
   //Do whatever you need to do for the error and
   return;
}
over
If (!isGood)
{
  //Do something
}

So check for what you really want to check for and keep the exception checks to a minimum. Your goal should be readibility over being tricky. Think of the poor soul that is going to have to come along and maintain your code.

One of the first things I worked on 28 years ago was a Fortran program that always needed to check if there was a graphics device available. Someone made the grand decision to call the boolean for this LNOGRAF, so if there was a graphics device available this would be false. I believe it got set this way because of a false efficiency, the check for the device returned zero if there was a graphics device. Throughout the code all the checks were to see if the graphics device was available. It was full of:

If (.NOT. LNOGRAF)

I don't think there was a single:

If (LNOGRAF)

in the program. This was used in mission planning software for B-52's and cruise missiles. It definitely taught me to name my variables for what I'm really checking for.

bruceatk
Good point! "To check for what I relly need". I gladly accept it. Thanks
Ma99uS
+10  A: 

This is convoluted and confusing, I would scrap it immediately.

Consider this alternative:

private void DoSomething()
{
   // some code
   if (some condition)
   {
      return;
   }
   // some more code
   if (some other condition)
   {
      return;
   }
   // yet more code
}

Also consider breaking up the code above into more than one method.

Wedge
surprised nobody else mentioned this, it just seems so obvious to use a function and just return, instead of the OP's code. And it avoids goto's if you aren't into that sort of thing.
Greg Rogers
This is the way to go!
torial
What you mean, @Greg? .G has answered about the samething 30 minutes before: http://stackoverflow.com/questions/243967/do-you-consider-this-technique-bad/243982#243982
Cawas
+1  A: 

I have used this idiom for years. I find it preferable to the similar nested or serial ifs , the "goto onError", or having multiple returns. The above example with the nested loop is something to watch out for. I always add a comment on the "do" to make it clear to anyone new to the idiom.

Sanjaya R
+1  A: 

What about a functional approach?


        void method1()
        {
         ... some code
         if( condition )
          method2();
        }

        void method2()
        {
         ... some more code
         if( condition )
          method3();
        }

        void method3()
        {
         ... yet more code
         if( condition )
          method4();
        }
Phil Nash
creating separate functions could be a problem in case A LOT of parameters would have to be sent and received from each function.
Ma99uS
Only if there are a lot of parameters to start with.It's clearly not suitable for all cases - but something worth considering. Obviously some languages will suit it better than others.
Phil Nash
+34  A: 

Bad practice, it depends.

What I see in this code is a very creative way to write "goto" with less sulphur-smelling keywords.

There are multiple alternatives to this code, which can or can not be better, depending on the situation.

Your do/while solution

Your solution is interesting if you have a lot of code, but will evaluate the "exit" of this processing at some limited points:

do
{
   bool isError = false ;

   /* some code, perhaps setting isError to true */
   if(isError) break ;
   /* some code, perhaps setting isError to true */
   if(isError) break ;
   /* some code, perhaps setting isError to true */
}
while(false) ;

// some other code

The problem is that you can't easily use your "if(isError) break ;" is a loop, because it will only exit the inner loop, not your do/while block.

And of course, if the failure is inside another function, the function must return some kind of error code, and your code must not forget to interpret the error code correctly.

I won't discuss alternatives using ifs or even nested ifs because, after some thinking, I find them inferior solutions than your own for your problem.

Calling a goto a... goto

Perhaps you should put clearly on the table the fact you're using a goto, and document the reasons you choose this solution over another.

At least, it will show something could be wrong with the code, and prompt reviewers to validate or invalidate your solution.

You must still open a block, and instead of breaking, use a goto.

{
   // etc.
   if(/*some failure condition*/) goto MY_EXIT ;
   // etc.

   while(/* etc.*/)
   {
      // etc.
      for(/* etc.*/)
      {
         // etc.
         if(/*some failure condition*/) goto MY_EXIT ;
         // etc.
      }
      // etc.
      if(/*some failure condition*/) goto MY_EXIT ;
      // etc.
   }

   // etc.
}

MY_EXIT:

// some other code

This way, as you exit the block through the goto, there is no way for you to bypass some object constructor with the goto (which is forbidden by C++).

This problem solves the process exiting from nested loops problem (and using goto to exit nested loops is an example given by B. Stroustrup as a valid use of goto), but it won't solve the fact some functions calls could fail and be ignored (because someone failed to test correctly their return code, if any).

Of course, now, you can exit your process from multiple points, from multiple loop nesting depth, so if it is a problem...

try/catch

If the code is not supposed to fail (so, failure is exceptional), or even if the code structure can fail, but is overly complex to exit, then the following approach could be clearer:

try
{
   // All your code
   // You can throw the moment something fails
   // Note that you can call functions, use reccursion,
   // have multiple loops, etc. it won't change
   // anything: If you want to exit the process,
   // then throw a MyExitProcessException exception.

   if(/* etc. */)
   {
      // etc.
      while(/* etc.*/)
      {
         // etc.
         for(/* etc.*/)
         {
            // etc.
            if(/*some failure condition*/) throw MyExitProcessException() ;
            // etc.
         }
         // etc.

         callSomeFunction() ;
         // the function will throw if the condition is met
         // so no need to test a return code

         // etc.
      }
      // etc.
   }

   // etc.
}
catch(const MyExitProcessException & e)
{
   // To avoid catching other exceptions, you should
   // define a "MyExitProcessException" exception
}

// some other code

If some condition in the code above, or inside some functions called by the code above, is not met, then throw an exception.

This is somewhat weightier than your do/while solution, but has the same advantages, and can even abort the processing from inside loops or from inside called functions.

Discussion

Your need seems to come from the fact you can have a complex process to execute (code, functions calls, loops, etc.), but you want to interrupt it over some condition (probably either failure, or because it succeeded sooner than excepted). If you can rewrite it in a different way, you should do it. But perhaps, there is no other way.

Let's assume that.

If you can code it with a try/catch, do it: To interrupt a complex piece of code, throwing an exception is the right solution (the fact you can add failure/success info inside your exception object should not be underestimated). You will have a clearer code after that.

Now, if you're in a speed bottleneck, resolving your problem with thrown exceptions as an exit is not the fastest way to do it.

No one can deny your solution is a glorified goto. There won't be a goto-spaghetti code, because the do/while won't let you do that, but it is still a semantic goto. This can be the reasons some could find this code "bad": They smell the goto without finding its keyword clearly.

In this case (and in this performance, profiled-verified) case only, your solution seems Ok, and better than the alternative using if), but of lesser quality (IMHO) than the goto solution which at least, doesn't hide itself behind a false loop.

Conclusion

As far as I am concerned, I find your solution creative, but I would stick to the thrown exception solution.

So, in order of preference:

  1. Use try/catch
  2. Use goto
  3. Use your do/while loop
  4. Use ifs/nested ifs
paercebal
Just the effort of such a details answer deserves to be Accepted.I agree on your points on not being able to break out of the inner loops. Failed functions return code still can be stored, if you use integer instead of bool as break condition (like all values > 0 indicate errors)
Ma99uS
I would put non-nested ifs as number 2, but otherwise agree.
Paul Tomblin
In java I would use Exceptions in that case without hesitation, but in C++ I somehow careful with using exceptions everywhere. But in that case I now tend to actually use them. Thanks,
Ma99uS
Well argued, but I dispute that "To interrupt a complex piece of code, throwing an exception is the right solution." You've made your case for "GOTO" but I would argue that exceptions are _semantically_ linked to, well, exceptional cases. Not to the complexities of control flow.
Larry OBrien
An easy and clean way to resolve this is to just put the "fake loop" code into a function that is called. You exit the function (via return) when you are done with the logic.
torial
I dispute "glorified goto". Either *any* non-linear control flow is a glorified goto (including any loop or conditional), or else things should be accused of being goto only if they lead to spaghetti (which this doesn't because the jumps are all forwards and all to the same place).
Steve Jessop
@torial: fine, as long as there aren't a couple of RAII-style things wrapped around the outside, that would have to be passed by referencec as extra parameters and generally lead to a Function Signature Of Doom.
Steve Jessop
+1 for the great conclusion. But I actually think **ifs** is the **first** way to go, and the **nested ifs** alone belong to the 4th place.
Cawas
+2  A: 

For better or worse, I have used the construct in a few places. The start of it is clearly documented, though:

    /* This is a one-cycle loop that simplifies error handling */
    do
    {
        ...modestly complex code, including a nested loop...
    } while (0);

This is in C, rather than C++ - so exceptions aren't an option. If I were using C++, I would consider seriously using exceptions to handle exceptions. The repeated test idiom suggested by Jeremy is also reasonable; I have used that more frequently. RAII would help me greatly, too; sadly, C does not support that easily. And using more functions would help. Handling breaks from inside the nested loop is done by repeated test.

I would not classify it as a great style; I would not automatically categorize it as "BAD".

Jonathan Leffler
A: 

I think people aren't being honest here.

If I, as your team lead, would see code like this you'd be up for a little one on one, and flagged as a potential problem for the team as that piece of code is particularly horrid.

I say you should listen to your colleagues and rewrite it following any of the suggestions posted here.

Sometimes it is better to have an outsider opinion (like Stack Overflow)Authority games can be tricky in some work environments.
Null303
It's not a matter of authority games. It's a matter of having people on your team write maintainable code. If I was interviewing someone and they showed me this kind of stuff, It would be one short interview.
Jeremy
+1  A: 

I would say your solution can be the right solution, but it depends. Paul Tomblin has posted an answer that is better (a series of if tubes) ... if it can be used.

Paul's solution cannot be used when there are expensive object initializations along the way through your loop. If the created objects are used in later steps, the do while (0) solution is better.

That said, variable naming should be improved. Additionally, why reuse the "escape" variable so much? Instead trap each individual error condition explicitly and break the loop so that it is obvious what causes the break out at each point.

Someone else suggested using function calls. Again, this may not be an ideal decomposition if it adds unneeded complexity to the function calls due to the number of args that might be passed at any given step.

Others have suggested this is a difficult to understand idiom. Well, first you could put a comment as suggested at the top of the loop. Second, do-while(0) is a normal and common idiom in macros that all C programmers should recognize immediately, so I just don't buy that.

Colin Jensen
I disagree with some of what you say, but I'm voting it up anyway because it's well said. do-while(0) is common in macros because of the limitations of macros - I disagree that they have any use outside of macros.
Paul Tomblin
A: 

This is what exceptions are for. You can't continue the logic because something went wrong. Even if recovery is trivial, it is still an exception. If you have a really good reason for not using exceptions, such as when you program in a language that does not support them, then use conditional blocks, not loops.

dongilmore
A: 

A meta comment: When you're coding, your goal should be clear, maintainable code first. You should not give up legibility on the altar of efficiency unless you profile and prove that it is necessary and that it actually improves things. Jedi mind tricks should be avoided. Always think that the next guy to maintain your code is a big mean psychopath with your home address. Me, for instance.

Paul Tomblin
+1  A: 

First, if you just want an answer to whether this code structure or idiom is "bad", I would think it is. However, I think this is a symptom of bad decomposition rather than whether the code you have is "good" or "bad".

I would think much better analysis and refactoring will have to be done to be able to further address the source of the problem, rather than looking just at the code. If you can do something like:

if (condition1(...) && condition2(...) && condition3(...) && ... && conditionN(...)) {
    // code that ought to run after all conditions
};
// code that ought to run whether all conditions are met or not

Then I think it would be more "readable" and more idiomatic. This way, you can make functions like:

bool conditionN(...) {
    if (!real_condition) return false;
    // code that ought to run
    return true;
};

You get the benefit of better decomposition and help from the compiler to produce the necessary short-circuitry that &&'s will bring. The compiler might even in-line the code in the functions to produce better code than if you would doing the hand-coded loop.

Dean Michael
+1  A: 

Refactor. The clean solution will in most cases be to split this code out into a smaller helper function, from which you can return, rather than breaking out of your not-actually-a-loop.

Then you can substitute your break's for return's, and now people can immediately make sense of your code when reading it, instead of having to stop and wonder why you made this loop which doesn't actually loop.

Yes, I'd say that simply because it doesn't behave as the reader would expect, it's a bad practice. The principle of least surprise, and so on. When I see a loop, I expect it to loop, and if it doesn't, I have to stop and wonder why.

jalf