Readit News logoReadit News
h2s · 13 years ago

    > Argument by meaningless blither. What distinguishes
    > “exceptional conditions” from “control flow”? I have
    > reached the end of a million element list. This
    > happens one time in a million! That sounds pretty
    > exceptional to me!
Unless you were expecting the list to have infinite length, then this would be a misuse of exceptions. The true logical fallacy here is the author of this post's argument from personal incredulity. The fact that he hasn't yet learned how to distinguish between exceptional conditions and control flow doesn't mean that it's an impossible or meaningless distinction.

Raphael_Amiard · 13 years ago
The author is consciously using exceptional with another accepted meaning of the word (rare). And you are (consciously or not) at the same time misunderstanding him and being condescending, while adding strictly nothing to the discussion.
KaeseEs · 13 years ago
This intentional use of the definition of a word from one context in another context is equivocation, a form of dishonesty that it makes little sense to tolerate.
dcminter · 13 years ago
Exceptions are often used to catch unanticipated situations but they are also, legitimately, used to simplify common case logic by moving special case logic elsewhere.

"Exceptional" and "unanticipated" are not synonymous.

Edit:

I would add that it is true that there is a semantic burden with exceptions in that they are suggestive of error, rather than special case, conditions. So I would be very wary of using them in that way simply in my own code because it will potentially be misleading to consumers of the source code.

But if the clarity of the remaining code is sufficiently enhanced then it's a fair trade-off.

Chris_Newton · 13 years ago
I would add that it is true that there is a semantic burden with exceptions in that they are suggestive of error, rather than special case, conditions.

That depends very much on the idioms and conventions of whichever language you’re using. In Python, for example, there are some built-in exceptions like StopIteration that are used for routine flow control and do not in general represent an error.

andyjpb · 13 years ago
h2s: Can you explain to us what exactly is the difference between the implementation of flow control during exception throwing and all the other kinds of flow control (including, but not limited to, stack popping).
gleamer · 13 years ago
Exceptions, as implemented in languages such as C++, also influence the flow of execution. Therefore, technically, exceptions are a kind of control flow structure.

But it doesn't mean that abusing exceptions as control structures is a reasonable idea.

astrobe_ · 13 years ago
IIRC no standard flow-control construct can do non-local returns (and that's where the Evil lies).
eps · 13 years ago
Do you mind clarifying who is these "us" you are referring to?
DRMacIver · 13 years ago
Do feel free to enlighten me.
h2s · 13 years ago
Exceptions exist so that you can treat error handling as a separate concern and thus deal with it in a different area of the code. For example, you might have a REST API which catches authorisation exceptions and validation exceptions from the libraries it calls and turns them into appropriate HTTP 30* or 40* error codes, with runtime or null pointer exceptions instead becoming 500 Internal Server Errors.

An example of misusing exceptions for control flow in this context would be throwing a "caching exception" to indicate that a cached version of the requested resource has been found and is to be returned to the client without any further processing.

moron4hire · 13 years ago
I agree with you. There are way too many cases of people making generalized arguments "you shouldn't do XYZ in programming because it's slow/confusing/etc" that really just have no place being uttered unless they can show mathematical proof it is too much to bother with in every situation. Back in the 80s and early 90s it was "no compiled language should ever be used for critical software", and then in the late 90s and early aughts it was "no VM'd language". Now what? It's all there is! You'd be nuts to NOT use a VM'd language. And the only reason we know any better is because people ignored the received wisdom and just did their own thing.

Keep doing your own thing.

yread · 13 years ago
Maybe I'm getting old but VM'd language shouldn't be used for critical software. Critical as in real-time or literally life-or-death situations.

Also how should a a mathematical proof that code that uses exceptions for jumping out of iteration doesn't confuse anybody look like? It's just confusing because it's never done like that. It confuses programmers. At least one. QED?

You CAN devise your own coding style, but when using the conventions reader can safely assume couple of things. Reading code that doesn't follow these conventions is more difficult (for everybody except you).

andyjpb · 13 years ago
I generally agree but I'd replace "unless they can show mathematical proof" with "unless they can show a code profile".

Happy days!

moron4hire · 13 years ago
I get your point, and that's pretty much what I meant. You need to have very specific reasons. I would argue a code profile would only prove a specific case, instead of a general case. But the point remains, "best practices" aren't theorems. They are used by more programmers as CYA than as good software development strategies.
reginaldo · 13 years ago
Writing exception safe code is hard

No it’s not. Be sure to clean up anything that could need cleaning up in a finally block.

This is like saying... be sure to never copy more bytes than the buffer capacity. Easier said than done.

Writing exception safe code is very hard. Do not take my world for it. Read Alessandro Warth's paper (with Alan Kay as a co-author) [1]. Do not skip section 3...

Let me quote section 3.1:

In languages that support exception-handling mechanisms (e.g., the try/catch statement), a piece of code is said to be exception-safe if it guarantees not to leave the program in an inconsistent state when an exception is thrown. Writing exception-safe code is a tall order, as we illustrate with the following example:

  try {
  for (var idx = 0; idx < xs.length; idx++)
      xs[idx].update();
  } catch (e) {
      // ...
  }

Our intent is to update every element of xs, an array. The problem is that if one of the calls to update throws an exception, some (but not all) of xs’ elements will have been updated. So in the catch block, the program should restore xs to its previous consistent state, in which none of its elements was updated. One way to do this might be to make a copy of every element of the array before entering the loop, and in the catch block, restore the successfully-updated elements to their previous state. In general, however, this is not sufficient since update may also have modified global variables and other objects on the heap. Writing truly exceptionsafe code is difficult and error-prone.

Now, I have seen a lot of code, and very very very few times I've seen someone restoring the state of a collection after an exception blows.

[1] http://www.vpri.org/pdf/tr2011001_final_worlds.pdf

simias · 13 years ago
In my opinion, this is not an issue with exception handling. It's an issue with bad programmers.

You can dumb it down as much as you want, people are still going to screw it up.

And you have exactly the same issue with "classical" error handling: you have to undo the partial job. Here's a bit of C code I wrote just this morning which closely ressembles your example:

    for (i = 0; i < pdata->overlay_nr; i++)
        if ((ret = overlay_init(data, i))) {
            while (i--)
                overlay_destroy(data, i);
            goto overlay_err;
        }
No exceptions, but I still have to remember to clean up before I dispatch the error.

I use exception quite a bit in C++, but it does fit my coding style. I use RAII almost exclusively, which alleviates most of the issues. That means that I don't have try{}catches everywhere, mostly only when I want to do actual error handling.

Exceptions are a tool, it's up to you to use it correctly (or not use it).

acdha · 13 years ago
Complex logic isn't going to be easy no matter how you structure it because the problem in question is intrinsically hard. The real question is whether exceptions are harder or easier to write or understand than the alternatives.

Your example illustrates this nicely: say you jumped on the ideological bandwagon and decided that exceptions are evil and must be avoided. How does this change your code? You can try C / golang style where errors must explicitly be checked & routed through normal flow control - something like this has a million variants floating around:

    var recover = 0

    for (var idx = 0; idx < xs.length; idx++)
      err = xs[idx].update()
      if err:
          recover = 1
          break
    }
    if recover > 0:
      // TODO: hard part goes here
In either case, all you're talking about are minor semantics (i.e. whether you have to explicitly code an if statement, use a flag or goto, etc.). The actual hard problem is what meaningfully can be done for recovery and conflating that with the mechanics of how you reach that code isn't particularly interesting.

aneth4 · 13 years ago
"ideological bandwagon" is precisely correct.

It's people blaming their golf clubs for not being good enough, when the real problem is they need to learn to swing properly.

btipling · 13 years ago
How would you write this without exception handling, how would that make it any easier? Perhaps do not put the entire loop in the try catch, but just the single iteration.
reginaldo · 13 years ago
Oh no. I'm not saying it would be easier without exceptions. I'm just saying that the current status of our programming tools makes writing safe code very hard, and exceptions are one more thing you have to think about. I use them a lot...

The authors of the paper propose their "worlds" API as a way to make writing safe code easier. They're still using exceptions, but they would require no cleanup...

The code becomes:

  try {
      in thisWorld.sprout() {
          for (var idx = 0; idx < xs.length; idx++)
            xs[idx].update();
          
          thisWorld.commit();
      }
  } catch (e) {
      // no clean-up required!
  }
So it's commit for data structures for data structures. If an exception is thrown and the commit line is not executed, no changes will be visible.

Deleted Comment

DRMacIver · 13 years ago
> This is like saying... be sure to never copy more bytes than the buffer capacity. Easier said than done.

A solved problem except when people insist on using approaches where it's not a solved problem? I couldn't agree more! :-)

firstly, the update example is a bait and switch. Partially updating an array is not inconsistent. It may be wrong depending on what the contract of the function says but that's but the same as inconsistent.

Indeed the example that prompted this article back when I wrote it many moons ago was a case where you wouldn't want the state restored

> In general, however, this is not sufficient since update may also have modified global variables and other objects on the heap

"Doctor! It hurts when I do this!" "Well don't do that then"

It's hard to write any correct code when you're mutating globals and arguments all over the place. Exceptions can certainly make this worse.

In general if you're not using globals or mutating your arguments this isn't a hard problem. If you are mutating arguments and you want to ensure a "restore all state if an exception is thrown behaviour then yes this is sometimes hard (unless your argument supports a rollback mechanism - e.g. it's a database) but I submit that a large part of why you haven't seen that is that it's a contract people don't care enough about to support

aneth4 · 13 years ago
Yes, that's not exception safe. Writing this error safe without exceptions requires nearly identical discipline. How much non-exception code have you seen checking every return value and restoring state? There are alternative more safe ways of writing this, but none have to do with whether exceptions were used.

This is a case where the developer needs to know that all update calls may not be completed. This may be a high or low probability, and may have fatal or no consequence. How many programs can handle a hard drive crashing or CPU glitch? Writing good error handling requires a lot more than removing exceptions, and often is not worth the cost. No software handles every error condition.

danielbarla · 13 years ago
I think this argument is generally taken out of context; my biggest concern would indeed be that using exceptions for control flow is non-idiomatic currently for most mainstream languages (and yes, Python disagrees).

This means that going against the grain will cost you time (and not you necessarily, but your company / colleagues, etc), and that time better come with some great benefits for it to be worthwhile.

So really, the argument can be looked at from many angles, depending on what value system you are using / what you would like to optimise for. I like to optimise for least surprises / development time.

gleamer · 13 years ago
The main problem with the idea of abusing exceptions as a control flow structure is the fact that they were designed to subvert and obscure the flow of control of any program that uses them. If people start to mindlessly pull clever stunts such as relying on exceptions to implement their algorithms then their code becomes unreadable, unmaintainable and unauditable. Not only their code, but any code which happens to include it. Its only purpose is to create an unmanageable pile of spaghetti code which is needlessly hard to trace. And what good comes out of it?
danielbarla · 13 years ago
Well, I tend to agree; but keeping an open mind, this might just be our subjective rationalisation because we don't understand a newer / better approach.

My point is just that even if the exception-raising approach was better, it'd take a lot of time before it would become truly idiomatic and allow us to reap its benefits, on a software engineering level. I know many would disagree with me on this, however; I like to think I'm a realist.

AndrewDucker · 13 years ago
I've used exceptions for flow control in validation before. Worked well.

Basically, you have nested validation code, and the second you hit something which invalidates your data you throw an InvalidDataException(X), catch it at the top of the validation, and then report back to the user that their input is broken because of X.

The alternative was that every method would need to pass back whether it had found a validation error, and every place that called one would need to check that returned value. Huge numbers of lines of code, for no real gain.

(Obviously, this doesn't work if you want to return _all_ the things that are wrong with the data.)

Deleted Comment

mtrimpe · 13 years ago
I think it all depends on context. I use checked exceptions for handling edge cases in Java, which turns something like this:

    public Session startByInterviewId(Long interviewId, String email, String name) {
        Session session;
        Interview interview = interviewService.getPublicById(interviewId);
        if (interview != null)  {
            Account user = userService.createUser(name, email, interview.getLocale());
            if (user != null) {
                session = startByInterview(interview, user);
            }
        }
        return session;
    }
into this:

    public Session startByInterviewId(Long interviewId, String email, String name)
            throws Interview.NotActive, Interview.NotPublic, Interview.DoesNotExist, Account.EmailExists {
        Interview interview = interviewService.getPublicById(interviewId);
        Account user = userService.createUser(name, email, interview.getLocale());
        return startByInterview(interview, user);
    }
in which case I'm very much in favor of using exceptions as control flow mechanisms.

Instead of just getting nulls to indicate failure I now even know exactly what caused my error.

I would never even think of writing code like this in JavaScript or Clojure though and I'd guess is that in Scala/Haskell a custom Option would be much better.

arethuza · 13 years ago
Interesting, I first looked at the second code segment and really didn't like it. However, I think thats just the whole Java checked exception thing (which I think everyone now regards as a mistake) - if you removed the throws and everything after it on that line it actually looks quite sensible and much better than simply returning null to mean "it didn't work for some reason I'm not going to tell you about".
mtrimpe · 13 years ago
Exactly. It's essentially as close as you can (legibly) get to an Option/Maybe monad in Java.
mullr · 13 years ago
The Option/Maybe monad seems the way to do it. The null object pattern could also apply here, but you have to be pretty disciplined across your project to make it really work. Seems like the kind of thing you need language support for.

Actually: while the second code block is more readable, if I encountered it in the wild I would assume that it's full of bugs. It just doesn't seem very idiomatic. I'd much rather take advantage of the 'goto' nature of the return statement:

    public Session startByInterviewId(Long interviewId, String email, String name) {
        Interview interview = interviewService.getPublicById(interviewId);
        if(interview == null) { return null; }

        Account user = userService.createUser(name, email, interview.getLocale());
        if(user == null) { return null; }

        return startByInterview(interview, user);
    }
There we go. 5 lines, one level of indentation for the main path, and semantics which are obvious to anybody who reads it.

But your point about the exceptions adding more information is well taken.

Deleted Comment

bjourne · 13 years ago
Ouch. Java's checked exception straightjacket would mean that every code that calls startByInterviewId() would either have to handle exceptions or declare "throws Interview.NotActive, Interview.NotPublic, Interview.DoesNotExist, Account.EmailExists". The longer up in the callchain, the longer the throws-list becomes until the programmer either gives up and writes "throws Exception" or is forced to handle the exceptions in a half-assed way. Likely by writing an empty catch-clause and hoping that code piece is never reached. It's especially unnerving for the user of the method if they are sure the input data is valid. For example, if interviewId comes directly from the database, then Interview.DoesNotExist can't happen so having to catch it shouldn't be needed.
dcminter · 13 years ago
I would expect those exceptions to implement a Throwable interface, perhaps ServiceException.

If you don't care about the specifics of the error then you catch that and handle it. If you do care then you're going to need to handle the specific cases anyway.

Personally I like checked exceptions at layer boundaries.

bruceboughton · 13 years ago
Fixed your original code for you:

    public Session startByInterviewId(Long interviewId, String email, String name) {
        Interview interview = interviewService.getPublicById(interviewId);
        if (interview == null) {
            return null;
        }
     
        Account user = userService.createUser(name, email, interview.getLocale());
        if (user == null) {
            return null;
        }
     
        return startByInterview(interview, user);
    }
Besides that, you have not magically reduced LOCs, you have moved them elsewhere so these code blocks are not equivalent.

mtrimpe · 13 years ago
We could always say that you shouldn't use return statements for control flow to complicate things further. ;)

Your code doesn't preserve information about what caused the failure though.

And also, I actually did magically reduce lines of code since I can just let the exceptions I fix bubble up and I've customized my web framework to turn them into appropriate error pages and the the <Class>.NotFound exceptions are even annotated so that they're automatically turned into 404s.

btipling · 13 years ago
Discussions about performance are statements of fact and can be measured. A jsperf shows that code executing in a try catch for chrome on a mac can be up to 3% slower for me[1]. Let's just get the numbers and show them and if they're valid they're valid, if not let's just dispel the rumors. Arguments about code architecture or best practices for intangible reasons are bikesheds. Just do what you like and be consistent and that's good enough. So if you can't demonstrate slowness, can't demonstrate a real problem as in the code wont work, then there is no real argument. In JavaScript avoid exceptions as control flow though, because they really are slower.

http://jsperf.com/try-catch-error-perf/3

sneak · 13 years ago
Oh, wow. On an iPad mini, the exception code is over two orders of magnitude slower in MobileSafari 6.
xenophonf · 13 years ago
I'm reminded of Kent Pitman's essay "Condition Handling in the Lisp Language Family" (http://www.nhplace.com/kent/Papers/Condition-Handling-2001.h...):

To properly understand condition handling, it is critical to understand that it is primarily about protocol, rather than mere computational ability. The establishment of protocols is a sort of before-the-fact hedge against the "prisoner's dilemma"; that is, it creates an obvious way for two people who are not directly communicating to structure independently developed code so that it works in a manner that remains coherent when such code is later combined.