41

I just discovered some lovely code in our companies app that uses Try-Catch blocks as logical operators.
Meaning, "do some code, if that throws this error, do this code, but if that throws this error do this 3rd thing instead".
It uses "Finally" as the "else" statement it appears.
I know that this is wrong inherently, but before I go picking a fight I was hoping for some well thought out arguments.
And hey, if you have arguments FOR the use of Try-Catch in this manner, please do tell.

For any who are wondering, the language is C# and the code in question is about 30+ lines and is looking for specific exceptions, it is not handling ALL exceptions.

11
  • 13
    I only have arguments against this practice, and since you already seem convinced it's a bad idea, I'm not going to bother posting them. Sep 12, 2011 at 18:33
  • 19
    @FrustratedWIthFormsDesigner : That's bad reasoning. What about the people who aren't convinced? What about my actual question where I specifically ask for reasons since I can't really tell "why" just that I know it's wrong. Sep 12, 2011 at 18:35
  • 3
    My opinions depends highly on what the code in question actually does. There are things that can't be checked beforehand (or allow race conditions when trying to, such as many file operations - in the delay between the check and the operation, anything can happen to the file) and have to be try'd. Not every exceptional case which warrants an exception in general has to be fatal in this specific case. So, could you do it in a simpler, equally or more robust way without using exceptions?
    – user7043
    Sep 12, 2011 at 18:36
  • 11
    I hope they don't really use the "finally" block as an "else" because the "finally" block is always run after the preceding code regardless of any exceptions being thrown.
    – jimreed
    Sep 12, 2011 at 20:09
  • 3
    What language are they using? It is perfectly fine in, say, OCaml, where the standard library throws exceptions routinely. Exception handling is very cheap there. But it won't be that efficient in CLI or JVM.
    – SK-logic
    Sep 13, 2011 at 6:54

15 Answers 15

61

Exception handling tends to be an expensive way to handle flow control (certainly for C# and Java).

The runtime does quite a lot of work when an exception object is constructed - getting the stack trace together, figuring out where the exception is handled and more.

All this costs in memory and CPU resources that do not need to be expanded if flow control statements are used for flow control.

Additionally, there is a semantic issue. Exceptions are for exceptional situations, not for normal flow control. One should use exception handling for handling unanticipated/exceptional situations, not as normal program flow, because otherwise, an uncaught exception will tell you much less.

Apart from these two, there is the matter of others reading the code. Using exceptions in such a manner is not something most programmers will expect, so readability and how understandable your code is suffer. When one sees "Exception", one thinks - something bad happened, something that is not supposed to happen normally. So, using exceptions in this manner is just plain confusing.

10
  • 4
    If that's the only downside, well thanks - I'll give up performance any day if it buys me better code (except in performance-critical paths, that is; but luckily that's only 20% of all code even in applications that are generally performance-critical).
    – user7043
    Sep 12, 2011 at 18:43
  • 5
    @delnan - No, not the only downside.
    – Oded
    Sep 12, 2011 at 18:51
  • 2
    Other than the word unaticipated I agree with your post. There are times in your code where exceptions happen. Most of them should be predictable like connection failures with the DB or a service call or anytime you are dealing with unmanaged code. You catch problems outside your control and deal with them. But I agree you should never have flow control based on exceptions that you can avoid within your code. Sep 12, 2011 at 20:35
  • 5
    "Exception handling tends to be an expensive way to handle flow control". False for Python.
    – S.Lott
    Sep 12, 2011 at 22:08
  • 3
    +1 for the point on readability. Using try/catch/throw this way is pretty much like swapping "throw" for "goto" and "catch" for "label". Goto spaghetti code is generally awful to read and maintain and is 99 times out of 100 (or more) the wrong way to go.
    – Becuzz
    Sep 13, 2011 at 15:13
18

I just discovered some lovely code in our companies app that uses Try-Catch blocks as logical operators. Meaning, "do some code, if that throws this error, do this code, but if that throws this error do this 3rd thing instead". It uses "Finally" as the "else" statement it appears. I know that this is wrong inherently...

How do you know that? I gave up all that sort of "knowledge" and now just believe that the simplest code is the best. Suppose you want to convert a string to an Optional which is empty if the parse fails. There is nothing wrong with:

try { 
    return Optional.of(Long.valueOf(s)); 
} catch (NumberFormatException) { 
    return Optional.empty(); 
}

I completely disagree with the usual interpretation of "Exceptions are for exceptional conditions". When a function cannot return a usable value or a method cannot meet its post-conditions, throw an exception. It doesn't matter how often these exceptions are thrown until there is a demonstrated performance problem.

Exceptions simplify code, by allowing the separation of error handling from the normal flow. Just write the simplest possible code, and if it is easier to use try-catch or to throw an exception, then do that.

Exceptions simplify testing by reducing the number of paths through the code. A function with no branches will either complete or throw an exception. A function with multiple if statements to check for error codes has many possible paths. It is very easy to get one of the conditions wrong, or to forget one completely, so that some error condition is ignored.

16
  • 4
    I think this is actually a pretty good argument. Your code is clean and concise and makes sense in what it is doing. This is actually similar to what is happening in the code I am seeing, only it is much more complex, is nested and spans 30+ lines of code. Sep 13, 2011 at 11:40
  • 1
    One could argue that Java could really use something more like C#'s TryParse. In C# that code would be int i; if(long.TryParse(s, out i)) return i; else return null;. TryParse returns false if the string could not be parsed. If it could be parsed it sets the output argument to the results of parsing and return true. No exceptions occur, (even internally in TryParse) and especially no exceptions that of a type that mean programmer error, like .NETs FormatException always indicates. Sep 13, 2011 at 17:06
  • 1
    @Kevin Cathcart, but why is that better? the code that catches the NumberFormatException is much more obvious to me then checking the result of TryParse for a null. Sep 13, 2011 at 18:28
  • 1
    @ChrisMiskowiec, I suspect which one you find clearer is pretty much a matter of what you are used to. I find catching the exception much easier to follow then checking for magic values. But that's all subjective. Objectively, I think we should prefer exceptions because they have a sane default (crash the program), rather then a default which hides bug (continue on as if nothing has happened). It is true that .NET performs poorly with exceptions. But thats a result of .NET's design, not the nature of exceptions. If on .NET, yes you've got to do that. But in principle, exceptions are better. Oct 23, 2012 at 22:03
  • 1
    "It doesn't matter how often these exceptions are thrown until there is a demonstrated performance problem." So you just keep on adding code you know is bad until your app actually starts to stop functioning, then you have to go through it and change possibly hundreds (thousands) of lines of code? This is insanity! Mar 11, 2021 at 4:00
14

Debug and maintenance work is very difficult when control flow is performed using exceptions.

Inherently, exceptions are designed to be a mechanism for altering the normal control flow of your program - of performing unusual activities, causing unusual side-effects, as a way to get out of a particularly tight bind that can't be handled with less complicated means. Exceptions are exceptional. This means that, depending on which particular environment you're working in, use of exception for regular control flow can cause:

  • Inefficiency The additional hoops that the environment has to jump through to safely perform the relatively difficult context changes required for exceptions require instrumentation and resources.

  • Debug difficulties Sometimes useful (when trying to debug a program) information gets thrown out the window when exceptions occur. You can lose track of program state or history that's relevant for understanding run-time behavior

  • Maintenance problems Execution flow is hard to follow through exception jumps. Beyond that, an exception may get thrown from inside black-box type code, which may not behave in easy to understand ways when throwing an exception.

  • Poor design decisions Programs built in this manner encourage a frame of mind which, in most cases, doesn't easily map to solving problems elegantly. The complexity of the final program discourages the programmer from understanding fully its execution, and encourages taking decisions which lead to short-term improvements with high long-term costs.

9

I've seen this pattern used several times.

There are 2 major problems:

  • It's extremely expensive (instantiation of the exception object, gathering the call stack etc.). Some compilers might actually be able to optimize it away, but I wouldn't count on that in this case, because exceptions are not intended for such use, therefore you can't expect people to optimize for it.
  • Using exceptions for control flow is in fact a jump, much like a goto. Jumps are considered harmful, for a number of reasons. They should be used, if all the alternatives have considerable disadvantages. In fact, in all my code I recall only 2 cases, where jumps were clearly the best solution.
2
  • 2
    Just putting it out there, if/else is also considered a jump. The difference is that it's a jump that can only occur at that specific spot (and it reads a lot easier) and you don't have to worry about the names of the labels (which you don't have to with try/catch either, but comparing to goto). But just saying "it's a jump, which is bad" also says that if/else is bad, when it isn't.
    – jsternberg
    Sep 13, 2011 at 17:04
  • 1
    @jsternbarg: Yes, if/else are in factcompiled to jumps at machine level, but at language level, the jump target is the next statement, while in the case of loops, it's the current statement. I would argue, that that's more of a step, than a jump ;)
    – back2dos
    Sep 13, 2011 at 17:46
8

Sometimes exceptions are fastest. I've seen cases where null object exceptions were faster even in Java than use of control structures (I cannot cite the study at the moment, so you'll have to trust me). The problem comes in when Java has to actually take the time and populate the stack trace of a custom exception class instead of using native ones (which seem to be at least partially cached). Before saying that something is unilaterally faster or slower, it would be good to benchmark.

In Python it is not only faster, but it is much more correct to do something which might cause an exception and then handle the error. Yes, you can enforce a type system, but that goes against the philosophy of the language -- instead you should simply try to call the method and catch the result! (Testing whether a file is writable is similar -- just try writing to it and catch the error).

I've seen times when it is faster to do something stupid like query tables which weren't there than to figure out whether a table exists in PHP+MySQL (the question where I benchmark this is actually my only accepted answer with negative votes).

All of that said, use of exceptions should be limited for several reasons:

  1. Accidental swallowing of nested exceptions. This is major. If you catch some deeply nested exception which someone else is trying to handle, you have just shot your fellow programmer (maybe even you!) in the foot.
  2. Tests become non-obvious. A block of code which has an exception in it could have one of several things wrong with it. A boolean, on the other hand, while theoretically it could be annoying to debug, it generally isn't. This is especially true as the try...catch control flow adherents generally (in my experience) do not follow the "minimize code in try block" philosophy.
  3. It does not allow for an else if block. Enough said (and if someone counters with a "But they could use different exceptions classes", my response is "Go to your room and don't come out until you've thought about what you've said.")
  4. It is grammatically misleading. Exception, to the rest of the world (as in not adherents to the try...catch control-flow philosophy), means that something has entered an unstable (though perhaps recoverable) state. Unstable states are BAD and it should keep us all up at night if we actually have avoidable exceptions (it actually creeps me out, no lies).
  5. It is not compliant with common coding style. Our job, both with our code and with our UI's is to make the world as obvious as possible. try...catch control-flow goes against what are commonly considered best practices. This means it takes more time for someone new to a project to learn the project, which means an increase in the number of man-hours for absolutely no gain.
  6. This often leads to code duplication. Finally blocks, though this is not strictly necessary, need to resolve all of the dangling pointers which were left open by the interrupted try block. So do try blocks. This means you can have a try{ obj.openSomething(); /*something which causes exception*/ obj.doStuff(); obj.closeSomething();}catch(Exception e){obj.closeSomething();}. In a more traditional, if...else scenario, the closeSomething() is less likely (again, personal experience) to be a copy and paste job. (Admittedly, this particular argument has more to do with people I have met than the actual philosophy itself).
7
  • AFAIK, #6 is a problem only in Java, which, unlike every other modern language, has neither closures nor stack-based resource management. It's certainly not a problem inherent in the use of exceptions. Sep 13, 2011 at 6:23
  • 4 and 5 seem to be the same point to me. 3-6 don't apply if your language is python. 1 and 2 are potential issues, but I think they are handled by minimizing the code in the try block. Sep 13, 2011 at 14:01
  • 1
    @Winston I disagree. 1 and 2 are major issues which, while diminished by better coding standards, still make one wonder whether it might not be better to simply avoid the potential error to begin with. 3 applies if your language is Python. 4-5 can also apply for Python but they do not have to. 4 and 5 are very different points -- misleading is different from stylistic differences. Misleading code might be doing something like naming the trigger to start a vehicle the, "stop button." Stylistically, on the other hand, it could also be analogous to avoiding all block indentation in C. Sep 13, 2011 at 15:27
  • 3) Python has an else block for exceptions. It is massively helpful for avoiding the problems you'd typically get for 1 and 2. Sep 13, 2011 at 16:18
  • 1
    Just to be clear, I'm not advocating using exceptions are your general flow control mechanism. I advocate throwing an exception for any sort of "failure" mode no matter how minor. I think the exception handling version is cleaner and less likely to hide bugs then the return value checking version. And ideally, I'd like languages to make it harder to catch nested errors. Sep 13, 2011 at 16:23
5

My main argument is that using try/catch for logic breaks the logical flow. Following "logic" through non-logic constructs is (to me) counter-intuitive and confusing. I'm used to reading my logic as "if Condition then A else B". Reading the same statement as "Try A catch then execute B" feels weird. It would be even weirder if statement A is a simple assignment, requiring extra code to force an exception if condition is false (and doing that would probably require an if-statement anyway).

2

Well, just a matter of etiquette, before "starting an argument with them", as you state it, I would kindly ask "Why they use exception handling in all these different places?"

I mean, there are several possibilities:

  1. they are incompetent
  2. there is a perfectly valid reason for what they did, which may not appear at first sight.
  3. sometimes it is a question of taste and may simplify some complex program flow.
  4. It's a relic of the past that nobody changed yet and they'd be happy if someone does it

...I think all of these are equally likely. So just ask them nicely.

1

Exception are for when Exceptional thing happen. Is the program functioning according to regular workflow exceptional?

3
  • 1
    But why? The point of the question is why (or why not) exceptions are only good for that. Sep 12, 2011 at 22:19
  • Not always "exceptional". Not finding a key in a hashtable is not that exceptional, for example, and yet OCaml library tend to raise an exception in such cases. And it is ok - exception handling is very cheap there.
    – SK-logic
    Sep 13, 2011 at 6:57
  • 1
    -1: tautological statement Sep 13, 2011 at 14:32
1

Reason to use exceptions:

  1. If you forget to catch an exceptional circumstance, your program will die and the stack trace will tell you exactly why. If you forget to handle the return value for an exception circumstance there is not telling how far away your program will exhibit incorrect behavior.
  2. Using return values only works if there is a sentinel value you can return. If all possible return values are already valid, what are you going to do?
  3. An exception will carry additional information about what happened which may be useful.

Reasons not to use exceptions:

  1. Many languages aren't designed to make exceptions fast.
  2. Exceptions which travel several layers up the stack can leave inconsistent state in their wake

In the end:

The goal is to write code that communicates what is going on. Exceptions can help/hinder that depending on what the code is doing. A try/catch in python for a KeyError on a dictionary reference is perfectly (as long as you know the language) try/catch for the same KeyError five function layers away is dangerous.

0

Try Catch should only be used for exception handling. More so, especific exception handling. Your try catch should catch only expected exceptions, other wise it is not well formed. If you need to use a catch all try catch, then you are probably doing something wrong.

EDIT:

Reason: You can argue that if you use try catch as a conditional operator, How are you going to account for REAL exceptions?

6
  • 2
    The OP is asking for reasons/arguments. You have not provided any.
    – Oded
    Sep 12, 2011 at 18:40
  • "You can argue that if you use try catch as a conditional operator, How are you going to account for REAL exceptions?" - By catching those real exceptions in a catch clause different from the one that catches "non-real" exceptions?
    – user7043
    Sep 12, 2011 at 18:46
  • @delnan - Thank you! you have proven a point I was about to make. If a person answered what you just said to both my reason and Oded's I would just stop talking cause he is not looking for reasons he is just being stubborned and I don't waste my time with stubborness.
    – AJC
    Sep 12, 2011 at 18:53
  • You're calling me studdborn because I point out flaws in some of the arguments listed here? Note how I have nothing to say against other points stated here. I'm not a fan of using exceptions for flow control (although I won't condem anything either without details, hence my query for elaboration by OP), I'm merely playing devil's advocate.
    – user7043
    Sep 12, 2011 at 19:10
  • 1
    Using try-catch as a conditional operator in no way prevents it from working to also catch "real" exceptions. Sep 12, 2011 at 22:36
0

I use try->catch as flow control in certain situations. If your primary logic depends on something that fails, but you don't want to just throw an exception and quit...That's what a try->catch block is for. I write a lot of unmonitored server-side unix scripts, and its far far more important for them not to fail, than it is for them to fail prettily.

So try Plan A, and if Plan A dies, catch and run with Plan B...And if plan B fails, use finally to kick off Plan C, which will either fix one of the failed services in A or B, or page me.

0

It depends on the language and maybe on the implementation being used. The standard in C++ is that exceptions should be saved for truly exceptional events. On the other hand, in Python one of the guidelines is "it is easier to ask for forgiveness than for permission", so the integration of try/except blocks in program logic is encouraged.

1
  • "The standard in C++ is that exceptions should be saved for truly exceptional events." This is nonsense. Even the inventor of the language disagrees with you.
    – arayq2
    Oct 27, 2014 at 14:40
-1

It is a bad habit, but I do it from time to time in python. Like instead of checking to see if a file exists I just try and delete it. If it throws an exception, I catch and just move on knowing that it wasn't there. <==(not necessarily true if another user owns the file, but I do it in a user's home directory so this SHOULDN'T happen).

Still, overused it's a bad practice.

7
  • 3
    In that example, using exceptions instead of "looking before you leap" is actually a good idea: It avoids race conditions. A check for file availability (existence, permissions, not being locked) succeeding doesn't mean a later - even if it's just a few opcodes later - access (opening, deleting, moving, locking, whatever) will suceed, as the file could be changed (deleted, moved, have its permissions changed) in between.
    – user7043
    Sep 12, 2011 at 19:18
  • If python is throwing an exception just because a file doesn't exist, it seems to be encouraging bad habits, as delnan describes. Sep 12, 2011 at 19:28
  • @delnan: Although true, IMHO if running into such race conditions is likely, you should rethink your design. In such a case, there's clearly a lacking separation of concerns in your systems. Unless you have very good reasons to do things just that way, you should either unify access on your end, or use a proper database to handle multiple clients trying to perform transactions on the same persistent data.
    – back2dos
    Sep 12, 2011 at 19:38
  • 1
    This isn't a bad habit. Its the recommended style in Python. Sep 12, 2011 at 22:16
  • @back2dos, can you prevent it when dealing with external resources such as other users of the database/file system? Sep 12, 2011 at 22:33
-1

I like the way Apple defines it: Exceptions are only for programmer errors and fatal runtime errors. Else use error codes. It helps me to decide when to use it, thinking to myself "Was this a programmer error?"

0
-1

This sounds like using exception handling to build goto logic on top of a language that doesn't have a goto command.

For the many reasons why goto logic is bad, you can refer to this question: https://stackoverflow.com/questions/46586/goto-still-considered-harmful

Not the answer you're looking for? Browse other questions tagged or ask your own question.