1. 15
  1.  

  2. 10

    Oof, the software engineering stack exchange always makes me so sad. Anyway!

    Exceptions are, in essence, sophisticated GOTO statements

    What everybody missed and you picked up on is that both exceptions and GOTO are language dependent constructs. The original “Go to considered harmful” paper was dealing with GOTOs that could jump from anywhere in a program to anywhere else in a program, like the middle of a function to the middle of a different function. The GOTOs we have now are much more limited and so much more useful. Similarly, what you can do with exceptions depends on the language you are in. Eiffel Exceptions, for example, force you to clean up the state and retry if you want to continue the program flow vs, say, halting with error.

    1. 3

      Oof, the software engineering stack exchange always makes me so sad.

      You can’t leave us hanging like that.

      1. 3

        Their treatment of formal methods was so bad I wrote a 6,000 word post on why they are wrong, and one of the “hottest” questions this month was “what do you do if Agile fails in your specific circumstance”, and the top answer is “don’t be in that circumstance.”

        1. 1

          From SO:

          TDD & BDD are approaches built on the principles of Hoare logic, a foundation of formal approaches. They improve efficiency not detract from it.

          I. Cannot.

        2. 2

          I can’t speak for @hwayne but the problems I have include that you aren’t allowed to discuss software engineering on it. Many questions where people have asked how to design computer-based solutions to problems (you know, engineering software) are ignored or downvoted. Questions I’ve asked on things like software licensing (which is definitely about the social applications of technical solutions, or “engineering”) or professional ethics (a hallmark of professional disciplines, including engineering) have been closed as off-topic. And the stack overflow ontology in which questions can be unambiguously and correctly answered by providing the appropriate fact (which can be correctly identified by popularity contest) doesn’t match the problem domain, so in many situations two answers which are mutually incompatible will be given and receive upvotes, with no-one taking the obvious next step of synthesising something useful from them.

      2. 4

        I think that this article would be nicely improved with how this shows up in Python normally, which is not “raw” try/except` branches but things like :

        try:
           do_thing()
        except HttpError:
            handle_connection_error()
        except ValueError:
            handle_parsing_error()
        except Exception as exc:
            log_exception(exc)
            handle_generic_error()
        

        I think that handling specific kinds of errors has a certain principled nature to it, but also is revealing in that you end up needing to assume that whole classes of error come from some similar style of issue.

        1. 4

          The main point of this article is to suggest that exceptions should be used for “ask forgiveness not permission” flow control in the current stack frame to clarify code by replacing if/else statements. The discussion is limited to using exceptions for local flow control. The author does not address in any serious way flow control that passes beyond the current stack frame.

          1. 2

            I think the point is not to clarify if/else statements but that exception capture the intention more. If I want to cast something to an int, I should try to and deal with failure. If I instead try to check first the implementation of the check might not match the implementation of actual action. This is typical for file handling. If you check if a file exists and then read it you have a race condition. You should try to read it and handle failure.

            1. 1

              Yes. Their exception version of the float to int check code handles many more failure cases than the if/else one. This doesn’t mean advice about exceptions as larger scale flow control is incorrect.

              Regarding file handling, yes it is important to know about O_CREATE or the + file flags in Python and handle errors correctly.

            2. 1

              The author does not address in any serious way flow control that passes beyond the current stack frame.

              Such as?

              I’m not exactly sure what you’re alluding to here, i.e. are you referring to using catching throws from a thread/process (in a hypothetical language where this would be very easy) instead of queue/channels for communication?

              1. 4

                One common criticism of exceptions is that they’re a “hidden” control flow, separate from the execution path you can see in the source file, which imposes an additional and persistent cognitive load on readers. Basically, if you’re using exceptions, any function can return at any expression without your knowledge. I think this is what ZicZacBee was alluding to. Your examples sidestep this criticism by always using unqualified ‘except’ clauses — it’s not possible for exceptions to escape your blocks. But this carries it’s own set of liabilities: what if your float conversion failed due to an out-of-memory issue? Shouldn’t that have a different effect than it being NaN or whatever?

                1. 1

                  But this carries it’s own set of liabilities: what if your float conversion failed due to an out-of-memory issue? Shouldn’t that have a different effect than it being NaN or whatever?

                  OOM issues are particularly easy to avoid since exceptions for things like OOM and sigkill are rather specific in most languages. E.g. in python one can do except Exception: and catch basically everything besides those.

                  But more generally, yes, this is an issue and I even mentioned it in the article as one of the tradeoffs.

                2. 3

                  Yes, I’m thinking of throwing as a channel for passing the control to a catch higher in the stack frame for something beyond what could be called ‘error handling’. If writing an Akka like system in Python with exceptions well bets are off. The article covers EAFP in local cases with brief examples. It’s a great way to do it. To use that as a basis to disregard other advice about using exceptions for flow control is poor reasoning. Many practices that are a good fit in a 20 line script do not scale to a larger project.

                  1. 1

                    If writing an Akka like system in Python with exceptions well bets are off

                    I mean, I agree that if you’re writing an actor based system using exceptions as control flow isn’t exactly ideal, but it’s not something that would even cross my mind. The concept of exception shouldn’t even exist in an actor-based paradigm. It’s comparing apples with oranges. I’m talking about imperative programming here.

                    Many practices that are a good fit in a 20 line script do not scale to a larger project.

                    Agree, but I don’t think the particular example you gave is relevant as to why this is the case with exceptions as control flow.

                    1. 1

                      If a larger system uses exceptions for flow control the location of the code that handles the exception is likely to end up based on the call stack. If this buys us something of more value than the cost of the complexity, it could be worth doing.

                      Yes, apples with oranges, we are comparing small imperative code snippets that use exceptions as flow control with a large systems that use exceptions as flow control. One of these it is a good fit and the other it is not.

              2. 3

                Exceptions as control flow are terrible in anything except Java, because they’re invisible. You need to understand everything at once to know where things might change direction on you.

                1. 2

                  may still be missing some edge cases in which a float typed variable can lead to UB

                  There’s no UB in Python. UB is defined in the C and C++ standards as behaviour that compiler authors do not have to write diagnostics for nor give a definite result for. A few other C-like languages also have UB, but UB is standardese. No standard and no definition means no UB.

                  The only reason I’m harping on about this is because floats are not unpredictable, and it pains me when people think of floats as weird, unknowable entities that can do all sorts of wacky unpredictable things. Floats can and should be understood.

                  1. 2

                    I agree with your second half, but Python does have a definition (in the form of the python.org site documentation), and it points out things like “the ordering of X is not defined” (C/++ legalese would rather describe this as “implementation-dependent”). There is also a lot of “currently this returns like this but in the future this might break, so don’t rely on it”. And “This works like this in CPython, but only in CPython!”.

                    Python as a language is more fleshed out than people give it credit for, imo.

                    1. 1

                      I still disagree that Python has a definition. Pypy for example has to implement “whatever cpython does” and can’t rely on a published Python standard to decide what is “correct” Python. It doesn’t matter if there’s documentation for it or not: people want their cpython code to produce exactly the same results on pypy and will consider any divergence a bug.

                      1. 1

                        The Python Language reference is written in an implementation agnostic angle and describes the behaviors that Python implementations should follow. It does have some “CPython implementation notes” but those are mostly things that don’t matter in the grand scheme of things.

                  2. 2

                    A small note that I’d like to add about the last example: you can safely use exceptions there, but with a construct that is used a lot more rarely.

                    try:
                        for x in arr: 
                            assert isinstance(x,float)
                    except:
                        mutable_func_2(arr)
                    else:
                        mutable_func_1(arr)
                    

                    else block only executes if there were no exceptions raised.

                    1. 2

                      It’s a minor nit but Python has other predicates to classify floating point numbers.
                      math.isfinite will return false for NAN or an infinity.
                      The NumPy equivalent is numpy.isfinite.

                      1. 1

                        Actually useful to know, I ended up using something different in that particular scenario but this function might come in handy later, I’m surprised it didn’t turn up when I was looking for something like it.

                      2. 1

                        I’m not a wild fan of exceptions in general mainly because of this, although they are way more of a common idiom in Python than in other languages.

                        That said, I like your post, there’s little I disagree with, but the code on your is_invalid_float example almost made me stop reading. The if version has unnecessary branches and returns, could be just return np.isnan(nr) or np.isinf(nr). I wonder if you would feel as compelled to simplify this version. The exception version then goes on to use a naked except, ensuring that it will gobble up any other error that might arise, including things like SystemExit or KeyboardInterrupt. Since you want a safe float number, perhaps OverflowError would be the right exception to catch, or even ArithmeticError, to check for any kind of, well, arithmetic error. When in doubt, I always consult the documentation

                        <extreme nitpicking mode off>