3

...I wanted to find the source before writing this Q, sadly I couldn't.

My mental model was that exceptions were thought as an alternative to returning error codes from a function, which is the C way to signalize failure. Returning error codes and managing them is error-prone, cumbersome and tedious; which is why a more right-handed mechanism was thought.

Problem is, then I started reading and hearing opinions that abusing exceptions is bad. Well of course - if I want to see if an array contains element X then it would probably not be a good idea to assume it does and then throw and almost immediatelly catch an exception if it doesn't.

But these opinions were more strict than that. An example of an improper use of exceptions was if, for example, we have a web app and a user requests something that is not there / sends invalid data / any other condition arises that requires displaying an error page to the user. In this case, I was told, it would be a bad practice to throw an exception deep down in the server code, catch it before the request stops being processed and then display an error page.

If I understand this mentality correctly (which doesn't have to be the case), the justification was that, since throwing the exception is not intended to crash an app (the situation is recoverable), then the situation is "sensible business-wise"; while exceptions should be reserved for situations "insensible business-wise", which I suppose means "unrecoverable"? But this seems to imply that if the app can continue and is not supposed to crash, I should not throw exceptions; I should only throw if the app cannot continue and must crash.

Is this commonly agreed upon? If so, then why?

I have feeling this is supposed to apply to almost all programming languages (but, notably, not Python.)

I must admit in my anti-pattern ridden game I do otherwise. For example, when the user sends a JSON with a team, I obviously must validate this data. I seem to be doing precisely what is condemned above: I pass the JSON data to the pieces of code that are responsible for validating it and, if it's valid, constructing an instantiation of the Team class. If the data is invalid, an exception is thrown. I thought this would be convenient for me, especially since this seems to mix well with C# facilities I use to construct the team: for example, to instantiate a Monster subclass from a monster name present in the JSON, I use Activator.CreateInstance which naturally throws if the monster name present in the JSON doesn't match any subclass of Monster.

But, well, others are more experienced than me, I marked this with a TODO that I should refactor this to if(isValid(teamJson)) team = new Team(teamJson) even though I don't understand why and even thought I don't like this idea because isValid() would have to share a lot of code with the Team() constructor.

Yesterday, however, I read something surprising: Throwing exceptions on assertion failures is, generally, a bad idea, because assertion failure should crash the program while an exception can be caught*. This seems to go against the mentality I presented above, which would assert that exceptions should be thrown precisely when the program must crash.

Could you clear my confusion? When should exceptions be thrown? In particular, should exceptions be thrown then and only then when the program should crash?


*(Yes, this applies to C++ while remarks about my game were about C#; but again - I don't want to discuss language facilities of this or other language, but when exceptions should be thrown in general, which - again - is AFAIK language-agnostic, although with few exceptions like Python.)

0

5 Answers 5

6

No, exceptions are not always thrown just to "crash" the program. Many exceptions are recoverable. For example, in Java you may get a FileNotFoundException if you try to open a file that doesn't exist. If that file was originally specified by the user, it stands to reason that you will get catch it fairly often and display an appropriate message to the user.

others are more experienced than me, I marked this with a TODO that I should refactor this to if(isValid(teamJson)) team = new Team(teamJson)

I would also suggest refactoring this and having the Team constructor throw an InvalidTeamException.

In my opinion, the decision to use an exception or return an error comes down to expectation. You can probably expect that teamJson is valid most of the time so when it isn't, you throw/raise an exception.

To use a different example, say you were doing some kind of networking app that required authentication. So you have a login() method. Bad/incorrect username and password are common and thus the login() method would return an error. However, what if the network connection dropped in the middle of the process? That's unlikely and thus requires an exception.

In general you want to catch all of your exceptions somewhere. Even it's just to say, "I can't recover from this error" and stop. The reality is, most code we write throws exceptions that are recoverable. It's just a question of who catches and handles the exception.

6

It seems to me that most discussions about exceptions are missing the point.

Most people reiterate "exceptions should be used only for exceptional circumstances" and then argue about their differing opinions of what does or does not qualify as "exceptional circumstances".

That is a meaningless semantic game. Instead, I propose that the use of exceptions should be based on their nature as language features. Exceptions have three core properties:

  1. They can transfer control flow up the call stack without requiring any changes in the code in between.
  2. They cannot be ignored silently. If there is no code handling them, your application will crash with a nice stack trace telling you what happened. And code that catches and ignores them is at least a very visible red flag.
  3. They syntactically represent alternative results of a method, you can have an arbitrary number of different ones, and catch clauses allow you to handle them selectively.

And therefore exceptions should be used when these properties provide a benefit:

  1. When you want to give the caller freedom to decide the granularity of error handling - i.e. if sometimes it would make sense for a caller to immediately do something specific, but in other circumstances it could be handled by a generic error handler (e.g. one that returns a HTTP 500 response).
  2. When there is a case that callers are likely to ignore and you don't want them to.
  3. When your method has a natural return value, but also special cases where the return value doesn't exist, and they might need to be handled in different places.

Especially points 2 and 3 are a good match for the "exceptional circumstances" definition, which is probably where exceptions got their name, but these more specific properties allow for a more concrete reasoning why exceptions should or should not be used in a concrete case.

And to finally answe the question as stated: No, exceptions are useful in many cases where you do not want the program to crash.

2
  • Really clean answer. One feature I would add/clarify is that they (typically) provide pattern matching functionality that allows you to (somewhat) easily filter/pick out specific cases to handle while allowing others to continue to other parts of the application.
    – JimmyJames
    May 20, 2019 at 15:03
  • @JimmyJames good point, I added that to my point 3. May 20, 2019 at 15:31
2

In particular, should exceptions be thrown then and only then when the program should crash?

Absolutely not. Exceptions are there to signal the caller that something went wrong. If the caller can handle the situation and recover from it, then he can handle the exception.

Let's say someone calls a method that writes a string into a file. A situation might occur where the file that he is trying to write to is being locked by another process. So we have to notify the caller of that situation. We can't just return false and leave the caller in the dark about why the operation didn't succeed. Instead we throw a FileLockedByAnotherProcessException (or however you would call it). The caller can now decide if he can handle this exception and whether it makes sense for him to handle it. If for example the caller is on UI layer, he might want to catch the exception and display an error dialog to the user instead of letting it crash the whole program.

There are however situation that are not possible to recover from. These situations are usually caused by a programming error and not by user input. Say stuff like division by 0, NullPointerExceptions, accessing array out of it's range etc. In these cases you would normally either leave the exceptions unhandled and let to program crash or let them propagate all the way down where you would catch them, display an error message and terminate the program yourself.

2

I think it helps to understand the reason exceptions exist as a language feature. If you aren't familiar with this, it's an old classic that can give you some context around the history of programming languages.

Essentially, back in the day, people wrote code that jumped around from here to there based on line numbers or labels. If you've ever read a 'choose your own adventure' book, that's basically how programs were written. Do something and if x goto y. This made programs hard to debug and limited what compilers could do to check for errors optimize the execution.

It was then proven that gotos were not required i.e. any program that could be written with gotos could be rewritten without them. However, no one would want to write code like that. There were certain patterns that were useful and considered OK (by language designers): methods, classes, breaks, returns, for loops, while loops, etc. These were refined and augmented to the point that gotos are not even allowed in some languages (e.g. Java)

One of the most challenging parts of writing real software is dealing with errors and other alternate path cases. Gotos were often used in a well defined pattern to set aside a section of code for error handling in those alternate path scenarios. The basic constructs of ifs and methods and loops were not adequate to replace this pattern. try-catch-finally filled this void. And this has even been refined to try-with type constructs.

The upshot is that in most languages, exceptions are used to handle alternate paths that need special handling (usually errors.) That's it. Nothing more, nothing less. It allows you to keep the 'happy-path' code simple and straight-forward and deal with these alternate paths where it makes the most sense. Without them, you need have mechanisms for passing around not only the data that you need for your main scenario but also the data for all the alternate paths and make sure you are constantly checking for whether you are on the alternate path after every method call. Using them to avoid that kind of mess is not abusing them in any way.

Note that there are performance implications of using exceptions. Often these are overblown but if you are in a very deep call stack, throwing an exception can introduce a significant cost. A large number of bad requests could be used to create a DoS attack, for example, if your exceptions are expensive.

-11

In particular, should exceptions be thrown then and only then when the program should crash?

Yes, they should only be thrown in those circumstances. Exceptions should be exceptional and should indicate that something has gone seriously wrong...

... but in practice there's a lot of code out there already that doesn't follow that principle. As Matthew points out, methods in many frameworks, including Java and .NET, will throw exceptions for the most trivial of reasons, such as a file not existing when trying to open it.

The latter sets a precedent and creates a defacto standard that others follow. So you have a choice: use exceptions properly (but risk facing criticism for not following framework standards) or follow the crowd and use them as returning state for control flow.

Returning error codes and managing them is error-prone, cumbersome and tedious...

Sure. But so are exceptions. That's why code like:

try
{
    DoSomethingThatMayThrowATrivialException();
}
catch {}

is so common.

20
  • 8
    That something has gone seriously wrong does not mean the program should crash. And trying to open a file that does not exist is not a "trivial reason". It is a very good example for where an exception is appropriate. May 20, 2019 at 14:09
  • 4
    Well, then you are wrong on both counts. May 20, 2019 at 14:11
  • 2
    Insisting that "a file not existing when trying to open it" isn't exceptional, in all circumstances, including it having existed microseconds earlier, seems rather naive.
    – Caleth
    May 20, 2019 at 14:14
  • 5
    Yes, but the immediate caller of File.Open(...) probably doesn't have the context to know what to do in the face of that. Exceptions also allow for arbitrary amounts of stack unwinding, without having to lift all the intervening code
    – Caleth
    May 20, 2019 at 14:24
  • 2
    @DavidArno: While I also tend to be a contrarian, the manner in which exceptions are properly used is well-understood and unremarkable. In languages that use them, I suppose you could create your own error code system instead, but that would be swimming against a really strong current. May 20, 2019 at 15:19

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