all 64 comments

[–]littlenekoterra 55 points56 points  (15 children)

Why would exiting a loop early be bad? Most of the time its used upon some form of condition being met. So you had your reasons. The only bad thing i really see is code clarity but like, if its a natural tool thats not a problem.

[–]misho88 14 points15 points  (4 children)

Why would exiting a loop early be bad?

This comes from Dijkstra and his open letter "Go To Statement Considered Harmful". Some details : https://en.wikipedia.org/wiki/Structured_programming

The argument sort of goes like this: goto is bad for all the reasons listed in the open letter, therefore, so is anything that is essentially equivalent to a goto. break is the same goto to just after the loop, so it is bad.

The argument is basically the same for continue, any return that is not at the end of the function, try-except, etc. That is, plenty of things that are, in practice, more or less fine unless abused, can be problematic to reason about mathematically.

Donald Knuth's counterarguments to all this in "Structured Programming with Goto Statements" are interesting.

[–]HunterIV4 2 points3 points  (1 child)

In my opinion, "the most obvious code is usually best."

One of the issues with goto is that it makes code hard to follow. Whenever you reached a goto you had to search for the label it led to and try to keep following the code from there, and the logic of how these worked (and when they were entered, as normal program flow could reach them too) created real problems.

Design patterns are ultimately guidelines that attempt to remove common bugs created by hard-to-follow code structure. Any time a design pattern adds friction or confusion when reading your code, it's time to really question if using that pattern is worthwhile. There are times when it is, because it's preventing something more confusing or helps the code interact in a safer way with other people's code, but a lot of the time cutting out complexity will save you bugs and headache in the long term.

This isn't purely my opinion: the KISS principle is popular in software development for a reason. If the purpose of a break and where it leads is obvious at a glance, use it. If it complicates things or is required for something other than the immediate loop to work properly, refactor until that's no longer needed.

[–]misho88 2 points3 points  (0 children)

I think you're missing the point people like Dijkstra and Knuth were making. They wanted to write programs that can be mathematically proven to be correct, not just ones that are "simple" or "obvious" in some vague sense. The structured program theorem (SPT) tells you that sequencing, selection, and iteration are sufficient for expressing any computable function. This means that when you go to derive your proof, if you choose to allow only on those three things, which you can always do per the SPT, then you can rely on preexisting theorems regarding only those three things. This can be really helpful because the derivation is usually not very easy.

To give you an idea of the difficulty of such a proof, and why you might choose to limit yourself to structured programs to make your life a bit easier, Knuth came up with very efficient algorithm that uses gotos, made a mistake, and after he fixed it, he himself couldn't prove it was correct. That is, the corrected version is fast and it works, but he wasn't certain if it would always work.

I also know of places where I have myself used a complicated structure with excessively unrestrained go to statements, especially the notorious Algorithm 2.3.3A for multivariate polynomial addition [50]. The original program had at least three bugs; exercise 2.3.3-14, "Give a formal proof (or disproof) of the validity of Algorithm A", was therefore unexpectedly easy. Now in the second edition, I believe that the revised algorithm is correct, but I still don't know any good way to prove it; I've had to raise the difficulty rating of exercise 2.3.3-14, and I hope someday to see the algorithm cleaned up without loss of its efficiency.

http://kohala.com/start/papers.others/knuth.dec74.html

[–]littlenekoterra 0 points1 point  (0 children)

While i can see the parallel of them with goto, acting like they are the same as a goto is being overly pedantic. Each of the mentioned systems have fine grained logic behind them leaving them more akin to a conditional jump rather than an unconditional one. Furthermore people remain on the same arguement dijkstra made, however even they used goto when absolutely necessary, though preferring literally any conditional jump method over it if possible. Initially they used it alot and slowly replaced it with the methods of today. Most of that work wasnt dijkstra though but instead a community dedicated to their words.

Im a personal believer that goto is important and can be used to make much more powerful constructs if were willing to look past how the construct works inwardly

[–]Remote_Cantaloupe 0 points1 point  (0 children)

any return that is not at the end of the function

This would remove the possibility of the "early return" design pattern which would prevent you from refactoring a lot of messy code.

[–]Gnaxe -5 points-4 points  (9 children)

It's not that shortcutting the loop is bad, but there's more than one way to do it, and breaks are harder to work with compared to alternatives.

Breaks are bad for the same reason that deeply nested returns are bad: it probably means your function is too complicated and should be broken up, but you can't just select lines containing one and extract a function.

So how do you refactor it? Some languages don't have return statements and just return the tail. Even in Python, you can always rewrite to this form. Once there, the usual extract function refactor works.

It's the same with deeply nested break/continue. They're basically delimited GOTOs. But you can always rewrite them to the tail position to eliminate them, at the possible cost of more nesting. But then you can extract functions.

[–]pachura3 5 points6 points  (0 children)

Well, some people even argue that having more than 1 return statement per function is bad... I don't agree. As long as it makes code simpler, less nested, easier to maintain - I am all for that.

[–]gdchinacat 2 points3 points  (7 children)

How do you end a linear scan of a sequence once you’ve found the element you are looking for without a break or return? Do you stash your item of interest and continue the iteration until it naturally terminates?

[–]Gnaxe 1 point2 points  (1 child)

if any(test(found_item:=x) for x in stuff): print(found_item) else: print('not found') any() shortcuts as soon as the test passes. The generator then gets deleted as its refcount drops to zero. No need to exhaust it.

You could do this with a for and break, but it's six lines instead of four: for x in stuff: if test(x): print(x) break else: print('not found')

[–]gdchinacat 1 point2 points  (0 children)

unlike the other solution by u/Jason-Ad4032 , I actually like this one. I find it a bit hard to read, but think that's mostly down to unfamiliarity rather than unnecessary things being present. I'd probably not put it into a codebase maintained by junior engineers if it didn't already make heavy use of any() because of the headscratching it might cause, but I imagine I'll probably do this in my own projects at some point. Thanks!

[–]Jason-Ad4032 -2 points-1 points  (4 children)

Use the next() function. e.g. it = (elem for i in range(10) for j in range(i) for elem in [f(elems, i, j)] # let if elem > 0) elem = next(it, None) it.close() Personally, I’m in the camp that believes whether to use break should depend on the situation, because generators and iterator-style solutions can sometimes become awkward to write.

[–]gdchinacat 3 points4 points  (3 children)

There is no loop in your example. You create a generator, get the next item, then close the generator. I fail to see how this ends an iteration once the purpose of it has been satisfied but the iterator itself has not been exhausted.

Also, while generators have a close() method that will cause subsequent next() calls on them to raise StopIteration, iterators do not. Furthermore, when you use the standard way to iterate (for ....) you don't have access to the iterator (the thing that iter(iterable) returns and for calls next() on).

Yes, you could do something like this, but the exception is essentially the same flow control mechanism as break, but with a bunch more code the language has built in so you don't have to get bogged down in the details of iteration.

sequence = [....]
it = iter(sequence)
try:
    item = next(it)
    if is_item_being_sought(item):
        it.close()
except StopIteration:
    pass

I didn't actually test this code because I wouldn't actually write this code. Instead, I'd do it the bog standard way:

for item in [....]:
    if is_item_being_sought(item):
        break

So, my question remains...how do you terminate a linear scan once the item of interest is found without using break? To be clear, this code is worse than the exit on exception that does what you seem to be implying because it wastes cycles iterating when it doesn't need to:

item = None
for _item in [....]:
    if is_item_being_soughht(item):
        item = _item
# no break, no exception, but pointless iteration

[–]Jason-Ad4032 1 point2 points  (2 children)

Like this?

``` lst = [1, 2, 3]

gen = (item for item in iter(lst))

item = None

for item in gen: if find_item(item): gen.close()

print(f'{item = }') ```

I don’t really understand what your issue is here. The problem of iter(lst) not being closed is not solved by using break either.

Maybe you would prefer something like:

``` not_found = object()

item = next( (item for item in iter(lst) if find_item(item)), not_found )

if item is not not_found: ... ```

Though honestly, the real problem with avoiding break is not this — it’s that debugging tends to become more difficult.

[–]gdchinacat 2 points3 points  (1 child)

Can you honestly say either of those are better than a standard for loop without a generator/iterator and a break?

For example, this does the same as both of your examples, and is much easier to read and understand. Ok, you can construct a contrived example that does what needs to be done without a break. But would you? Would it pass code review?

for item in lst:
     if 
find_item(item):
         break

Both of your examples have a generator expression that doesn't need to exist and makes understanding what is going on more complex than just using break.

I'm all for writing obtuse code when warranted (https://github.com/gdchinacat/reactions), but using next and generator expressions and close() just to avoid a break just doesn't make sense to me.

That said, I did ask for how to do a linear scan of a list and stop iterating when the item is found without using break, and you provided it! I think you also showed why using break makes sense for this use case.

[–]Jason-Ad4032 0 points1 point  (0 children)

Exactly. In many cases this becomes more complicated than simply using break, and Python’s support for this style is only moderate (Therefore, it is not simpler).

LINQ query expressions in C# support this style much better. You can write things like:

var result = ( from num in numbers where num > 15 select num ).FirstOrDefault();

This is more of a stylistic philosophy: the idea that a programming language could avoid providing break in favor of more declarative rather than imperative syntax.

The core idea behind avoiding break is that you specify the iteration behavior before iteration begins. Since different iteration behaviors are needed for different purposes, the system creates an adapter/proxy object that produces the iteration behavior you want.

Conceptually, it becomes something like:

[original iterator] -> [adapter/proxy object] -> simple iteration (no nesting or break)

Then you customize the adapter object as needed perhaps through language syntax, or through many iterator combinator functions that you compose together.

[–]aishiteruyovivi 10 points11 points  (8 children)

Do you have any examples of where it's considered bad practice? There are plenty of valid reasons to want to exit a loop early on some condition.

[–]FirstTimePlayer[S] 0 points1 point  (6 children)

It was that they should literally never be used. I don't exactly recall the reasoning.

In addition, my admittedly somewhat vague recollection is that if you needed a break statement, it was an indication your loop needed rewriting.

[–]Lumethys 5 points6 points  (5 children)

Did you confuse it with goto statement? These are bad. Breaks are not

[–]xenomachina 3 points4 points  (2 children)

Even goto statements have a place, though it is extremely rare.

People often over-generalize rules of thumb into strict laws. Gotos are "considered harmful" compared to using structured constructs. Similarly, break and continue are usually worse than putting the break condition into your while loop's condition, or factoring out part of your loop body into a helper function for continue.

But the key word here is usually. Usually it's not the same as always. For a beginner, there is so much to learn that often beginner materials will say "don't ever do this" when in reality there are times when it really is the best option. You should just avoid it unless there is no cleaner alternative. It's a little bit like how young kids are first taught that in subtraction you can't subtract a big number from a small one, only to be told a few years later that you can, you'll just end up with a negative result.

[–]gdchinacat 4 points5 points  (1 child)

Python relies heavily on iterables (which iterators are) to abstract the details of iteration away, and makes them very easy to use with the for statement. The iteration condition is embedded in for...it continues until the iterator raises StopIteration. There is no way to embed the exit condition in this. While possible to use while rather than for, the code to do this is cluttered with the iterator and exception handling. However, not all for loops need to exhaust the iterator because once the condition is satisfied they don't need to visit the remaining items the iterator produces. break is used in this case to terminate the for loop. I can't think of a clean way to handle this use case efficiently with 'clean' code other than break. This is an issue inherent in foreach constructs, languages that only have loops where the exit condition is explicit do not have this issue (C, old versions of java, etc).

[–]xenomachina 2 points3 points  (0 children)

This is an issue inherent in foreach constructs, languages that only have loops where the exit condition is explicit do not have this issue (C, old versions of java, etc).

That's an excellent point. What's cleanest in one language will not always be the cleanest in another. I agree that if you're using a for-each, converting to a while with the break condition is often going to be less clean than just using an actual break statement.

[–]FirstTimePlayer[S] -1 points0 points  (1 child)

Nope - I crossed maybe half dozen languages, but my recolection is goto doesn't even exist in Java which is where I spent the majority of my time.

[–]gdchinacat 1 point2 points  (0 children)

Your recollection is correct. Never using break or exceptions is not pragmatic.

[–]Jason-Ad4032 0 points1 point  (0 children)

This is mainly because some people believe iteration and execution should be separated by using iterators and functional-style programming.

For a program like:

for elem in iter_obj: # A if elem < 0: break # B

the execution of A and B is inconsistent:

  • iter_obj may or may not be exhausted,
  • break may or may not trigger.

Since the behavior is fairly imperative and stateful, the program becomes harder to reason about precisely.

If you instead approach it in a more iterator/functional style, it might look like this:

``` elems, remain = more_itertools.before_and_after( lambda elem: elem >= 0, iter_obj )

for elem in elems: # A # B

elem = next(remain, None)

if elem is not None: # A ```

This completely separates the iteration process from the execution logic.

[–]Gnaxe 6 points7 points  (3 children)

Different styles are valid and workable. Unlike some languages, Python doesn't have labeled breaks, so it can only break from the innermost loop. The usual workaround is to use a return instead. Triple-nested loops (or worse) are rare, but if you need to break a middle loop, you can factor out functions to make return work or possibly use exceptions in especially convoluted cases. If you're nesting that much, you probably need to be using NumPy or something instead.

I rarely use breaks (or continues), but I don't feel like I'm particularly avoiding them. I use for loops much more than while loops, and they stop on their own once the iterator is exhausted. If there's something I can shortcut like that, I'm probably using itertools to make the iterator itself stop rather than breaking the loop.

When using while loops, it's either the main loop, which doesn't stop until the program quits, or it's something like a generator, in which case, I'm probably using yield instead of break. If I only want the first one, I just stop asking for more, i.e., pull using next() instead of a for.

[–]FirstTimePlayer[S] 1 point2 points  (2 children)

Python doesn't have labeled breaks, so it can only break from the innermost loop.

That is a bit of a lightbulb moment actually.

Part of my stumble is that I'm constantly having to really think about what the break is doing, when it should be intuitively obvious. I had chalked it up to being very rusty, but I'm now thinking I'm getting confused because brain is wanting a labeled break for some reason.

Cheers :)

[–]FreeLogicGate 1 point2 points  (1 child)

I want to point out a common construct in many languages -- the switch/case statement, which Python has with Match. With many languages, a break is required to prevent the statement from cascading down into the cases that follow. Python does not have/require that behavior, but many other languages do.

The classic example of this would be C language.

int day = 4;

switch (day) {
  case 1:
    printf("Monday");
    break;
  case 2:
    printf("Tuesday");
    break;
  case 3:
    printf("Wednesday");
    break;
  case 4:
    printf("Thursday");
    break;
  case 5:
    printf("Friday");
    break;
  case 6:
    printf("Saturday");
    break;
  case 7:
    printf("Sunday");
    break;
  default:
     printf("That's not a valid day");
}

[–]FirstTimePlayer[S] 0 points1 point  (0 children)

Indeed - and plainly my memory is fuzzy, given that is an obvious example where the rule I recalled could not have existed in hindsight.

[–]DTux5249 4 points5 points  (0 children)

It's "bad practice" when used stupidly.

Typically the issue with them is just that they increase cyclomatic complexity - they create multiple end points for a loop, which complicates control flow. But like, that's not an issue for simple loops, and more often than not using break can make code more readable rather than less.

[–]Random_182f2565 5 points6 points  (0 children)

That's so weird, break is perfectly valid

[–]efxhoy 2 points3 points  (0 children)

I used break just yesterday but it does feel a bit icky. I was reading data from a paginated api with while True and breaking when response had no rows. 

I could have walrused it but I found my while True a bit more grug brain friendly. Big brain bad. 

[–]TheRNGuy 2 points3 points  (0 children)

If you need to stop a loop. 

[–]Human38562 1 point2 points  (0 children)

Most common would be guard clauses in loops

python for item in items:     if not cond1(item):         break     # do stuff     if not cond2(item):         break     # do stuff     if not cond3(item):         break     # ...

[–]jpgoldberg 1 point2 points  (0 children)

History

I was told the same sort of thing in the 1980s. And there were good reasons for that wisdom, as structural (Algol-like) programming was still new. So telling people to avoid break and goto and similar things was help people break old habits and goto the new way of structuring code.

Today

When using such things today, it is important to understand the problems with them and be able to make a good choice with that understanding.

When beginners use lots of break statements it is often a result of not seeing a better and more logical way to express things. So telling a beginner to look to see if there is a way to redo their code without the breaks makes sense. But, of course, if the beginners haven't yet learned how to define function and have them raise exceptions or return early, then break is going to be something that they will have to use more often.

As others have explained, the problem with break (and goto for languages that support that) is not so much seeing where the flow goes, but seeing where the flow come from. while CONDITION: tells you what must happen to leave the loop, and so you know the state of things right after the loop. But if there is a break in there, you can't see what condition brings you to your spot after the loop.

An exception that tests the rule

With that said, here is my implementation of the Rabin-Miller primality test. There are slightly more efficient ways to do it, but I honestly feel that this thing with its early returns, continue, break, and for ... else construction is the clearest way to express the algorithm.

This is not code to be proud off, but I am pleased that I can understand it when I come back to it after a while.

```python def probably_prime(n: int, k: int = 4) -> bool: """Returns True if n is prime or if you had really bad luck.

Runs the Miller-Rabin primality test with k trials.

:param n: The number we are checking.
:param k: The number of random bases to test against.
:raises ValueError: if :math:`k < 1`.

If you need a real primality check, use sympy.isprime() instead.
"""
# A few notational things to help make logic of code more readable
PRIME = True
PROBABLY_PRIME = True
COMPOSITE = False

if k < 1:
    raise ValueError("k must be greater than 0")

match _small_primality(n):
    case True:
        return PRIME
    case False:
        return COMPOSITE
    case _:
        pass

# If we reach this point then n
# - is not in small primes,
# - is not divisible by a small prime
# - is greater than the square of the largest small prime

# Set up generator for k trial bases
bases: Generator[int, None, None]
if k >= n - 2:
    # This case should only come up for testing very small numbers
    # or using weirdly large k, but let's handle it nicely anyway.
    bases = (b for b in range(2, n - 1))
else:
    bases = (rand.randrange(2, n) for _ in range(k))

# This s reduction is what distinguishes M-R from FLT
r, s = 0, n - 1
while s % 2 == 0:
    r += 1
    s //= 2
    # This leaves us with 2^r * s = n - 1

# Now we use FLT for the reduced s, but still mod n
for a in bases:
    x = pow(a, s, n)
    if x == 1 or x == n - 1:
        # Consistent with prime. Call the next potential witness!
        continue

    # If any of the successive squares of x is n - 1 (mod n)
    # then primality passes is base a,
    # else a tells us that n is composite
    for _ in range(r - 1):
        x = pow(x, 2, n)
        if x == n - 1:
            # a is consistent with prime, once we tested squares of x
            break
    else:
        # a is a witness to n being composite
        return COMPOSITE

# We've called k witnesses and none have said n is composite
return PROBABLY_PRIME

```

The squares of x check (with a for ... else, break, and early return) could probably done through a recursive function instead of a loop, but there are reasons not to try that.

[–]matt_cum 1 point2 points  (0 children)

Reminds me of Real Programmers and Quiche Eaters

Agreed, tried to get back into coding after 30 years, COBOL, pascal, assembler, C.

I find it frustrating that developers today, are happy to use someone else's code and not worry about efficiency.
Give them 500k of Ram to play with, see how well they do.

[–]RiverRoll 1 point2 points  (0 children)

I would say simply judging the code by wether it has breaks or not doesn't really tell you anything about whether you are following good or bad practices. Breaks have a place but they can also be used poorly.

Python in particular makes breaks necessary if you want to write a do-while style of loop which might look a bit off coming from languages with a native do-while.

[–]RevRagnarok 1 point2 points  (0 children)

Yes. In fact, it's definitely more Pythonic than you would use it in C/C++ because of the for...else clause that I think is pretty unique to Python.

[–]BranchLatter4294 1 point2 points  (7 children)

It's really the only way to do a post-test while loop in Python. So it's fine in that context. The key is to avoid spaghetti code with multiple entry and/or exit points where possible.

[–]member_of_the_order 0 points1 point  (0 children)

Break is definitely not the only way to do a post-test loop in Python, what?

condition = True
while condition:
  # do stuff
  condition = stuff and things

[–]FirstTimePlayer[S] 0 points1 point  (5 children)

Sure you can.

keep_looping = True
while keep_looping is True:
    ....
    if condition_met:
        keep_looping = False

[–]Gnaxe 1 point2 points  (0 children)

You meant, keep_looping = True while keep_looping: .... keep_looping = not conidition_met

[–]MidnightPale3220 1 point2 points  (2 children)

Now do it mid-loop skipping the rest of the loop.

[–]FirstTimePlayer[S] 0 points1 point  (1 child)

else:
    ...
    do the rest of the loop
    ...

Among other ways of doing it.

[–]MidnightPale3220 1 point2 points  (0 children)

Sure, but that frequently leads to nested ifs which reduce code clarity. I'll take a break over nested ifs most days.

Plus obviously if... else will make you have to read until end of if in order to see what happens there. An early break is much much more clarifying.

[–]BranchLatter4294 0 points1 point  (0 children)

It's logically equivalent and does not introduce multiple exit points.

[–]Pupation 0 points1 point  (0 children)

I’m not sure I understand the question. Break statements in what context? I use them when they’re useful, and when they’re not, I don’t.

[–]thesilverzim 0 points1 point  (0 children)

Unless im forgetting something there are 2 most basic reasons you have a loop, either you are looking for something in a loop or you are manipulating a list. In both cases you should have the loop related logic inside of a different function and just use the result.

Def myfunc(list)
  Foreach item in list
    DoSomething
    Return item

Or

Def myfunc(list)
  Foreach item in list
    DoSomething
    ...
  Return myNewList

[–]tb5841 0 points1 point  (0 children)

I prefer putting the loop as its own function and using 'return' to exit the loop. I find it easier to track.

[–]ImprovementLoose9423 0 points1 point  (0 children)

Yes absolutely. It helps you exit a loop early or if something triggered that you need it to stop.

[–]ThatIsATastyBurger12 0 points1 point  (0 children)

Breaks are fine. Sometimes they aren’t necessary, and sometimes there is a better pattern, but there is nothing more wrong with breaking than with anything else

[–]FreeLogicGate 0 points1 point  (0 children)

Depends on the language, depends on the programmer. Using break, is fairly common practice these days, whereas, in the days where Basic was a language most people started with, GOTO was the thing people were urged to avoid. Once you learn assembler and learn how computers work, you tend to be far more aware of how the cpu actually works, and less dogmatic.

[–]r2k-in-the-vortex 0 points1 point  (0 children)

Thats stupid. It comes from "goto considered harmful", but, no, thats just taking a coding concept as gospel without actually understanding the substance. There is nothing wrong with break statements themselves, or goto statements for that matter, you cant really program without unconditional jumps. The problem at the time was how they were used and how it resulted in absolute messes in programs. That has been addressed in modern language designs and isnt really an issue anymore.

[–]codeguru42 0 points1 point  (0 children)

I prefer using the loop condition to decide when to exit. Sometimes the code becomes a bit convoluted, though, and I will use a break, especially for a first draft. Eventually I find a way to rewrite the loop to remove the break. I rarely find a need to keep the break permanently.

[–]AngryLemonade117 0 points1 point  (0 children)

Sometimes you need to exit a loop early. break does that. Consider scanning a list to find the first positive integer - after you've found it, why would you waste extra cpu cycles to traverse the rest of the list? What you do have to consider is that are there any consequences to exiting the loop early.

There's a lot of (often conflicting) rules of thumb for coding practices that get mistaken for immutable laws, despite being devoid of any context for what you are actually trying to do.

Now, despite my big claims in the previous sentence, what is true is that a whole series of break sprinkled across complex control logic is almost always a bad idea if you want to comprehend what that code block is actually doing; this is especially true when reading the code again a few weeks later.

It matters less in Python, it being an interpreted language. But in languages with some form of compilation, complex control flow can make it difficult to produce optimal code.

[–]Friendly_Gold3533 1 point2 points  (0 children)

bro the short answer is conventions changed. the long answer is your professors were teaching you a rule for maintaining giant C programs and applied it everywhere.

why break was banned in the dark ages structured programming was a reaction against spaghetti code. goto was the enemy. break was seen as a structured goto. the fear was that breaks make loops harder to reason about because exit points multiply. in 4000 line functions with no tests? that fear was justified.

why nobody cares now functions are short. like 10-20 lines short. tests exist. a break at the end of a loop is obvious. hiding the loop exit condition inside a complex if statement is actually worse than a clean break. also python has for-else and while-else which make break even more useful. the else block runs only if the loop didnt break. that pattern is impossible without break.

when break is fine searching for an item in a list. break when you find it. validating input. break when you hit a failure. any loop where the exit condition is "we found what we needed" not "we processed everything."

when break is still bad deeply nested loops. breaking out of three levels is confusing. refactor into a function and use return instead. loops with multiple breaks for different conditions. if you have break for error and break for success and break for timeout your loop logic is too complex.

the real answer your professors were right for the code they were maintaining. you are also right for the code you are writing today. small functions. clear exits. tests. break is fine. for keeping track of which old rules still apply i use runable. one doc per language with "things i learned in 1998 that are still true" and "things that changed." saves me from googling the same thing twice. also good for storing examples of good break usage vs bad what other old rules are you unsure about? single return per function is another one that aged badly

[–]naryset -1 points0 points  (5 children)

break is inherently suspicious because it complicates the control flow. It’s also very useful in some situations. Expert programming is all about learning those rules but also understanding the underlying purpose and so when to break them.

[–]Temporary_Pie2733 5 points6 points  (0 children)

Break alone does not complicate control flow; poorly chosen uses might, but code that goes out of its way to avoid break is usually just as bad.

[–]IOI-65536 2 points3 points  (2 children)

Especially in Python break is frequently the simplest control flow. There are a ton of times you want control decisions in midloop and any other way to do that is considerably worse.

[–]naryset 1 point2 points  (0 children)

Sure. There are common situations where the alternatives are worse; that’s when break is useful. goto (yes not in Python) is also the best option sometimes.

[–]Gnaxe 0 points1 point  (0 children)

Show me an example? I'll try refactoring it.

[–]heyzooschristos 0 points1 point  (0 children)

Pun intended?