30

I don't know whether this question fits better on this site or on Stack Overflow, but because my question is connected rather to practices, that some specified problem.

So, consider an object that does something. And this something can (but should not!) can go wrong. So, this situation can be resolved in two ways:

first, with exceptions:

DoSomethingClass exampleObject = new DoSomethingClass();
try
{
     exampleObject.DoSomething();
}
catch (ThisCanGoWrongException ex)
{
     [...]
}

And second, with if statement:

DoSomethingClass exampleObject = new DoSomethingClass();
if(!exampleObject.DoSomething())
{
     [...]
}

Second case in more sophisticated way:

DoSomethingClass exampleObject = new DoSomethingClass();
ErrorHandler error = exampleObject.DoSomething();
if (error.HasError)
{
     if(error.ErrorType == ErrorType.DivideByPotato)
     {
         [...]
     }
}

Which way is better? On one hand, I heard that exceptions should be used only for real unexpected situations, and if the programmer knows that something may happen, they should use if/else. On the other hand, Robert C. Martin in his book Clean Code wrote: "exceptions are far more object oriented, and more simple to keep clean."

4
  • 11
    It depends. If the error is exceptional then use exceptions, if it could be frequent then trap it with if tests - these are less expensive.
    – ChrisF
    Commented Mar 26, 2012 at 12:29
  • Using exceptions for control flow is like using goto for loops or setjmp/longjmp for nested returns. Sure, it can be done but you feel a little dirty at the end of the day =) IMO Commented Mar 26, 2012 at 18:55
  • 3
    Have you seen this Article-- "Cleaner, more elegant, and harder to recognize" The author is a pretty well recognised authority on programming and has some pretty good points here. Commented Mar 27, 2012 at 6:26
  • If possible could you please paraphrase (especially the last part) this sentence: "... because my question is connected rather to practices, that some specified problem."? Thank you Thaven in advance!
    – Milan
    Commented Oct 7, 2021 at 18:39

9 Answers 9

35

Typically, I tend to use exceptions for exceptional cases - the instance where nothing should go wrong, and if it does go wrong, that's a big problem. An example might be a missing configuration file that ships with the software or a missing hardware driver. If these things happen, that's a huge problem. I will perform checks on things like user input or optional instances where there's a good chance that I'll be given invalid information.

However, it should also be noted that some languages appear to favor exceptions. For example, I've noticed that the use of exceptions for even minor problems is much more common in Python. In the Python community, this is known as EAFP - "Easier to ask forgiveness than permission".

4
  • 3
    docs.python.org/glossary.html#term-eafp
    – DHall
    Commented Mar 26, 2012 at 16:10
  • The usual examples for Python can be generalized into: 1) Duck typing, which needs to be supported by the language to easily use it, and 2) tight race conditions that should be using try/catch regardless of the language (checking if a file exists before reading it, for example. It may get deleted in the interim, or may actually be a directory for some reason, and you didn't check for it!)
    – Izkata
    Commented Mar 26, 2012 at 18:13
  • "An example might be a missing configuration" - what would be an example of non-exceptional error?
    – IceCold
    Commented Dec 5, 2018 at 9:49
  • @Gravity „user input“ was given as example in the answer. Commented Oct 10, 2021 at 11:45
40

The usual way of error handling is this:

  • If you can solve the problem locally, then use the if statement (or if the function throw an exception, handle that exception).
  • If you can not solve the problem locally, then throw an exception (or if the function throw an exception, let it propagate further), and handle it where you know what to do about it.
11

Exceptions handle exceptional cases; that is, cases that do not fall into the "happy path" of normal program execution. Given this very important caveat, it is generally acceptable, and in fact, expected, to use try/catch in cases where (a) something can fail, and (b) you know what to do when it does.

The primary situation in which you would not use try-catch is when knowing the success or failure of a method is at least as important as the actual result of the operation. An example is string parsing: many times, it's at least as useful to know if a string consists of a number or not, rather than what the number actually is. Thus .NET provides "TryParse" flavors of the Parse functions built into the primitive types. They allow you to structure your code with a simple decision at the beginning of an if/else fork, that then determines entry into one or the other code blocks.

For instance, say DoSomething()'s success determines whether to print "Yes" or "No" to the console. Both are "normal" things for the program to do. You could write it like this:

try
{
   DoSomething();
   Console.WriteLine("Yes");
}
catch(Exception)
{
   Console.WriteLine("No");
}

... but there are a couple of problems: first, from a design standpoint, it's not obvious without comments that DoSomething() is a decision being made. Second, DoSomething() may not be the only thing in the try block that could throw an exception. Let's say another developer added some additional code that they need to run when DoSomething succeeds:

try
{
   DoSomething();
   Console.WriteLine("Yes");
   DoSomethingElse();
}
catch(Exception)
{
   Console.WriteLine("No");
}

Now, you have a scenario in which DoSomethingElse() could fail after DoSomething() succeeded, and the result to the end-user from one input would be very confusing:

Yes
No

Instead, consider this:

if(TryDoSomething()) //a "non-throwing" variant that returns success or failure
{
   Console.WriteLine("Yes");
   DoSomethingElse();
}
else
{
   Console.WriteLine("No");
}

The concept that DoSomething is the criteria of a decision between two "normal" execution paths is much clearer in the above code. And, your desired logic flow is enforced no matter what; If DoSomething() succeeds, the user will never see "No" displayed on screen no matter what DoSomethingElse() does.

In most other cases, try-catch is preferred, for one or more of the following reasons:

  • try-catch allows you to use the return value of a method for what it was conceptually designed to do; return the result of the operation, instead of a status code indicating whether a valid result is contained in an output or reference parameter that is the REAL product of the method.

  • try-catch allows for more object-oriented error-handling, as was mentioned, by avoiding the use of "magic numbers"; you don't have to know that return code -2 of this method is an error that an argument was null, you just catch an ArgumentNullException. And, you can catch that same ArgumentNullException thrown from any method that cares whether one of its passed parameters was null, instead of having to know that this method would return -2 and another method returns -3 in the same situation.

  • By that token, try-catch helps to standardize the expected behavior in a particularly exceptional case: If you pass a null parameter to a method that requires that parameter to not be null, you will get an ArgumentNullException, and it is usually bad practice to throw any other exception or to assume some base case that equates null with a discrete value.

  • try-catch isn't limited to calling methods; you can "try" a possible division-by-zero operation inline in your code and catch the exception thrown by the runtime should it occur.

  • try-catch isn't limited to one method at a time; you can, with minimal additional code, handle exceptions from a series of several methods, and abort processing, without having to examine the most recent return code of each method you call.

1
  • Though this reply is too old. If DoSomething and DoSomethingElse both throws exception, then it can be handled with multiple catch blocks. I think exception handling makes your code much more cleaner and readable. Commented Apr 5, 2017 at 6:07
6

Take in account that in case of if/else you're going to perform that check every time, regardless if execution was successful or not. Thus if error case is occurring rarely or practically never under normal circumstances, then exception handling is way more efficient, as on successful execution you're not evaluating any additional conditions.

Another advantage of exceptions over checking in case of multithreading or distributed systems is avoiding race conditions resulting from TOCTTOU (time of check to time of use) problem. You check condition first, then execute code assuming that condition holds. Without assuring atomicity with locks/mutex or so, at time of executing code condition might already been changed by another thread/process.

4
  • 5
    This sounds a lot like "premature optimization". Checking the return code for true/false hardly ever causes a measurable performance penalty. TOCTTOU is not an issue here, either.
    – user281377
    Commented Mar 26, 2012 at 13:00
  • 2
    @ammoQ: TOCTTOU may not be a problem in example used in question, but I've seen TOCTTOU way to often in distributed systems. In code written by experienced programmers, so it isn't just a newbie issue.
    – vartec
    Commented Mar 26, 2012 at 13:12
  • This case can happen even without multi-threading. The common example would be if File.Exists: works 95% of the time, then someone deletes or moves it between your check and your action.
    – Mike
    Commented Sep 21, 2015 at 21:07
  • "Checking the return code for true/false hardly ever causes a measurable performance penalty." - What if your function can return not only true/false, but some constants like BAD_USERNAME, BAD_EMAIL, BAD_PASSWORD, GOOD_CREDENTIALS when you check user credentials, so you would know exact cause of problem? I would think of using exceptions in any situation when I need to know not only result, but also cause of error in parent function (ofc, if there are more than 1 cause possible, otherwise false/true check is enough).
    – dmytroy
    Commented Aug 25, 2017 at 22:53
6

I've used exceptions for their information carrying ability. In C++, a function return code is usually a single value. In contrast, you can throw exceptions of any class type containing any information that you want.

I used an exception to return the specific piece of a query that was not found. It seemed the lesser evil than to add a mostly unused pointer or reference to each function call in a 7-deep call stack.

3

If exampleObject.DoSomething(); SHOULD NEVER cause a problem, use exceptions.

If there are valid, expected business cases where exampleObject.DoSomething() will cause an error, use if/then/else and use comments to describe cases when this might cause a problem.

Example:

Thing thing = findThingInDatabase();

//if a thing was found in the database (we EXPECT thing to not be found sometimes, hence if stmt as opposed to exception)
if (thing != null){
    thing.doStuff();
}
3
  • This argument is a common one but I find it hard to use as a good rule of thumb. IN the really tricky case its hard to decide whether something is "rare" or not.
    – hugomg
    Commented Mar 26, 2012 at 13:18
  • @missingno I think: "If there are valid, expected business cases where exampleObject.DoSomething() will cause an error" is pretty easy to follow, assuming you can find a business expert to ask... Commented Mar 26, 2012 at 18:02
  • I'm not sure if this was ever really the case but generally speaking, these days it's seen as bad practice and "unclean" to return a null. I for one completely agree with the new way of doing things. stackoverflow.com/questions/1274792/…
    – Kalec
    Commented Sep 22, 2015 at 14:57
3

Another thing to consider is that when exceptions occur there is a performance penalty or a cost associated with that exception.

For example, run X/Y in a loop.

One way is to use an if statement. Example:

if (Y != 0) 
{ 
   result = X/Y;
}

Another way is to use a try/catch.

try
{
  result = X/Y;
}
catch (DevideByZeroException zeroEx)
{
  //Log Error
}

If you run this in a loop (million times) you will find the if statement runs a lot faster over the try catch when Y = 0.

So, when coding ask yourself, will I hit this condition alot. In this example, how often will Y be zero? 1% or 50% of the time? If 50% then use an if. If just occasionally use a try. If there are 500,000 excpetions occurring, then that is not really an exception to the rule, it is the rule. You want to avoid that.

That's why I tend to think exceptions are for cases that don't happen that frequently. If you have popular use cases that are handled via exception logic, then they should probably be converted to use logic statements (ifs) versus exceptions. And as a bonus your code will speed up as well.

3
  • 2
    Just to be clear: there is a performance penalty when the exception is thrown, as of course there must be as there is a lot of information in an exception and gathering that information takes time. Depending upon your language, there may be little to no penalty when an exception is not thrown - which goes to show that exceptions should be exceptional.
    – jmoreno
    Commented Mar 26, 2012 at 20:46
  • @JMoreno - Correct. Hopefully my example is clear. There is no penalty for having a try/catch. I've update my answer slightly based on your feedback.
    – Jon Raynor
    Commented Mar 27, 2012 at 14:10
  • Actually following your suggestion I wrote a sample program running the loop int.MaxValue times. The IF/ELSE version completed in 6082 ms and 15407722 ticks whereas the TRY/CATCH version completed in 28174385 ms and 71371326155 ticks: a whopping 4632 times more than the if/else version. Commented Jun 19, 2018 at 0:41
1
X x;
try
{
     x = exampleObject.DoSomething ();
}
catch (ThisCanGoWrongException ex)

If doSomething returns something, it can't meanwhile return an error, except there is enough room in the result Space. A well known instance of this phenomenon is a C-library, which reads a byte and returns an int. While bytes are considered unsigned, the error-case is/was a -1.

But now you have to do an ugly cast to do.

What do you do, if the result space does not give you room for results, meaning "invalid"? Well, you can use a Wrapper, which returns, instead of an T an Either[Error, T]. The right result usually means an result which is alright, and left is left to mean a kind of error.

Instead of the Error as special kind of value, the user of your library is - in statically typed languages - informed beforehand, that there is something which might go wrong.

In contrast, for

  String ips = getIP ("programmers.se.com");
  // returns "err.or.in.DNS" in errorcase - but programmer 
  // forgot to check 
  for (String b : ips.split (".")) {
       Integer.parseInt (b); // crash here

Sometimes, methods get called for their side effect, and the return value isn't inspected at all. Then an error can slip through undetected.

  user.setAge (-42); 

Maybe the class returns a "false" or a message "Invalid age, must be >= 0". But many languages allow to ignore the returned result. An exception would be preferred here.

A problem with exceptions and wrapped result is, that they sometimes clutter the code, and make it harder to follow the healthy case. Maybe we will see improvements there in the future - Java is for example trying to simplify the handling of exceptions recently. IDEs could probably hide or fold exception code on demand.

In some cases, it might be reasonable to return a special Null object. Think about searching a special User in the Database. If something goes wrong, you could return null.

    println (null.getName ()); // crash: NPE

An often seen solution is, to return a List with one Element in the normal case, and zero values in the defect case.

    users = db.get ("Zappa");
    for (User u : users) {
         u.doSomething ();

This will work with one or none Element, and Languages like Haskell or Scala have a special purpose class for this, a Maybe or an Option.

Option is a parametrized class. An Option[T] is either a Some (T) or a None. The user still has to check the result, but can't forget it that easily.

This is similar to the result of an empty List, and might not be appropriate everywhere, but often it is.

0

You Use Exceptions for Exceptional situations where if else block couldn't handle, like connecting to a database, if the database is not available, what will you do? would you connect to another database? or just abort the operation? and other situations like these are the ones where you should use exceptions.

But you should always keep in mind that don't over used Exceptions, like having an index Out of bounds index error, then you'll use exception for that.

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