111

My specific case here is that the user can pass in a string into the application, the application parses it and assigns it to structured objects. Sometimes the user may type in something invalid. For example, their input may describe a person but they may say their age is "apple". Correct behavior in that case is roll back the transaction and to tell the user an error occurred and they'll have to try again. There may be a requirement to report on every error we can find in the input, not just the first.

In this case, I argued we should throw an exception. He disagreed, saying, "Exceptions should be exceptional: It's expected that the user may input invalid data, so this isn't an exceptional case" I didn't really know how to argue that point, because by definition of the word, he seems to be right.

But, it's my understanding that this is why Exceptions were invented in the first place. It used to be you had to inspect the result to see if an error occurred. If you failed to check, bad things could happen without you noticing.

Without exceptions every level of the stack needs to check the result of the methods they call and if a programmer forgets to check in one of these levels, the code could accidentally proceed and save invalid data (for example). Seems more error prone that way.

Anyway, feel free to correct anything I've said here. My main question is if someone says Exceptions should be exceptional, how do I know if my case is exceptional?

Daniel Kaplan
  • 6,776
  • 5
  • 32
  • 46
  • 4
    possible duplicate? [When to throw an exception](http://stackoverflow.com/questions/77127/when-to-throw-an-exception). Though it was closed there, but I think it fits here. It's still a bit of philosophy, some people and communities tend to see exceptions as a kind of flow control. – thorsten müller Jan 24 '13 at 08:58
  • 8
    When users are dumb, they provide invalid input. When users are smart, they play by providing invalid input. Therefore, invalid user input is not an exception. – mouviciel Jan 24 '13 at 12:10
  • 7
    Also, don't confuse an _exception_, which is a very specific mechanism in Java and .NET, with an _error_ which is a much more generic term. There's more to _error handling_ than throwing exceptions. This discussion touches on the nuances between _exceptions_ and _errors_. – Eric King Jan 24 '13 at 14:39
  • 1
    **["Your peer tells you after reviewing the code."](http://programmers.stackexchange.com/a/141010/31260)** – gnat Jan 24 '13 at 15:58
  • 4
    "Exceptional" != "Rarely Happens" – ConditionRacer Jan 24 '13 at 17:30
  • Here's a related [answer](http://programmers.stackexchange.com/a/161492/44767). – RalphChapin Jan 24 '13 at 22:38
  • @RalphChapin Thanks, I actually read that before I posted my question, I think it's a good read. – Daniel Kaplan Jan 24 '13 at 23:06
  • 3
    I find Eric Lippert's [Vexing exceptions](http://blogs.msdn.com/b/ericlippert/archive/2008/09/10/vexing-exceptions.aspx) to be decent advice. – Brian Nov 19 '13 at 14:02
  • @ConditionRacer: "Invalid user input" != "Rarely happens" :-) So double reason for "no exception". – gnasher729 Feb 16 '16 at 08:57
  • tl;dr Josh Bloch already answered this in EJ, 2nd: to sum up, _don't use exceptions to model valid and expected control flow of the application; use them to model the failure of a method's contract_ - if a method does what it is *expected* to do, *never throw any exception*. Throw only if the contract is broken - either the input is invalid, or you can't produce a proper output when the input is OK due to external circumstances. Eric Lippert seems to agree with this approach BTW. –  May 24 '18 at 16:30

12 Answers12

95

Exceptions were invented to help make error handling easier with less code clutter. You should use them in cases when they make error handling easier with less code clutter. This "exceptions only for exceptional circumstances" business stems from a time when exception handling was deemed an unacceptable performance hit. That's no longer the case in the vast majority of code, but people still spout the rule without remembering the reason behind it.

Especially in Java, which is maybe the most exception-loving language ever conceived, you shouldn't feel bad about using exceptions when it simplifies your code. In fact, Java's own Integer class doesn't have a means to check if a string is a valid integer without potentially throwing a NumberFormatException.

Also, although you can't rely just on UI validation, keep in mind if your UI is designed properly, such as using a spinner for entering short numerical values, then a non-numerical value making it into the back end truly would be an exceptional condition.

Karl Bielefeldt
  • 146,727
  • 38
  • 279
  • 479
  • 11
    Nice swipe, there. Actually, in the real-world app I designed, the performance hit *did* make a difference, and I had to change it to *not* throw exceptions for certain parsing operations. – Robert Harvey Jan 24 '13 at 17:22
  • 20
    I'm not saying there aren't still cases where the performance hit is a valid reason, but those cases are the exception (pun intended) rather than the rule. – Karl Bielefeldt Jan 24 '13 at 17:38
  • 12
    @RobertHarvey The trick in Java is to throw pre-fabricated exception objects, rather than `throw new ...`. Or, throw custom exceptions, where fillInStackTrace() is overwritten. Then you shouldn't notice any performance degradations, not to speak of *hits*. – Ingo Jan 24 '13 at 21:42
  • 4
    +1: Exactly what I was going to answer. Use it when it simplifies the code. Exceptions can give much clearer code where you do not have to bother checking return values on every level in the call-stack. (Like everything else though, if used the wrong way it can make your code a horrible mess.) – Leo Jan 25 '13 at 07:58
  • 1
    Id say that the fact that exception handling is not as high in overhead is not reason to indugle sloppy code, or to neglect to code for expected values and check for nulls – SoylentGray Jan 25 '13 at 15:56
  • 2
    +1 The number spinner is an especially useful example. For the Op's specific case, UI feedback would be the best approach for an invalid or unmatched input. An exception would be used of, for instance, the wrong type (ex an integer instead of a string) is encountered. – Evan Plaice Jan 25 '13 at 19:52
  • 4
    The problem is that exceptions break the normal flow of control and make code harder to read/follow (e.g. trying to get where the catcher might be is far worse than "goto"). They don't simplify code at all, and only reduce the amount of typing needed for "intermediate" code (functions/methods between the catcher and the thrower). – Brendan Nov 19 '13 at 10:08
  • 5
    @Brendan Suppose some exception occurs, and the error-handling code is 4 levels below in the call stack. If you use an error code, all 4 functions above the handler need to have the error code's type as their return value, and you have to do a chain of `if (foo() == ERROR) { return ERROR; } else { // continue }` at every level. If you throw an unchecked exception, there's no noisy and redundant "if error return error". Also, if you're passing functions as arguments, using an error code may change the function signature to an incompatible type, even though the error may not occur. – Doval Mar 25 '14 at 19:52
  • @KarlBielefeldt, You claim that exceptions "simplify the code", but Mike's answer seems to directly contradict that http://programmers.stackexchange.com/a/184714/24257 . What are your thoughts on that? – Pacerier Jun 24 '14 at 15:16
  • 1
    @Doval : When you wrote your explanation, you must have realized afterward that it was a good thing to be forced to write checking code at intermediate levels. This way you are forced to reflect on the state of affairs of your function at this level. If RAII is lacking, you may then execute some steps to leave the function in a clean status, and cascade the error. – v.oddou Oct 23 '14 at 03:35
  • 1
    @v.oddou It's not clear to me that "being forced to reflect on the state of affairs" at the expense of readability is a desirable trade-off in every case. Also consider that in many cases, the error code is the return value and the "true" output of the function is a side effect; there's nothing forcing you to check the return value for errors, so you're not truly forced to consider the state of affairs. It's less of an issue in functional languages where the boilerplate of repeated error checking can be hidden with some clever high-order functions. – Doval Oct 23 '14 at 12:11
  • Exceptions make troubleshooting harder, in development and production, on server and client. As a developer, one should always turn on "Break on unhandled exceptions", in JavaScript and C#, for example. But when the code throws lots of exceptions, you have to turn this off. And then you will then miss important exceptions. The same is true for postmortem dump debugging. When there are lots of exceptions, it becomes difficult to find important ones. In earlier versions of .NET, there was a performance hit, but this is nowadays negligible. – cskwg Oct 29 '21 at 07:49
76

When should an exception be thrown? When it comes to code, I think that following explanation is very helpful:

An exception is when a member fails to complete the task it is supposed to perform as indicated by its name. (Jeffry Richter, CLR via C#)

Why is it helpful? It suggests that it depends on the context when something should be handled as an exception or not. On the level of method calls, the context is given by (a) the name, (b) signature of the method and (b) the client code, which uses or is expected to use the method.

To answer your question you should have a look on the code, where user input is processed. It might look something like this:

public void Save(PersonData personData) { … }

Does the method name suggest that some validation is done? No. In this case an invalid PersonData should throw an exception.

Suppose that the class has another method which looks like this:

public ValidationResult Validate(PersonData personData) { … }

Does the method name suggest that some validation is done? Yes. In this case an invalid PersonData should not throw an exception.

To put the things together, both methods suggest that the client code should look like this:

ValidationResult validationResult = personRegister.Validate(personData);
if (validationResult.IsValid())
{
    personRegister.Save(personData)
}
else
{
    // Throw an exception? To answer this look at the context!
    // That is: (a) Method name, (b) signature and
    // (c) where this method is (expected) to be used.
}

When it is not clear if a method should throw an exception, then maybe it is due a poorly chosen method name or signature. Maybe the design of the class is not clear. Sometimes you need to modify the code design to get a clear answer to the question if an exception should be thrown or not.

Theo Lenndorff
  • 2,534
  • 1
  • 18
  • 15
  • *Just yesterday* I made a `struct` called "ValidationResult" and structured my code the way you describe. – paul Jan 24 '13 at 13:14
  • 4
    It does not help to answer your question, but I just like to point out that you implicitly or purposely followed the Command-query separation principle (http://en.wikipedia.org/wiki/Command-query_separation). ;-) – Theo Lenndorff Jan 24 '13 at 13:21
  • Nice idea! One drawback: In your example, validation is actually performed twice: Once during `Validate` (returning False if invalid) and once during `Save` (throwing a specific, well-documented exception if invalid). Of course, the validation result could be cached inside the object, but that would add additional complexity, since the validation result would need to be invalidated on changes. – Heinzi Oct 21 '13 at 09:27
  • @Heinzi I agree. It could be refactored so that `Validate()` is called inside the `Save()` method, and specific details from the `ValidationResult` could be used to construct an appropriate message for the exception. – Phil Nov 19 '13 at 13:53
  • 1
    Another problem with this approach is that it can only be used if you are sure that there will be no interference from other threads or processes that might affect the validation result. By the time you call Save, the personData may no longer be valid. – flodin Dec 16 '14 at 10:36
  • 3
    This is better than the accepted answer i think. Throw when the call can't do the thing it was supposed to do. – Andy Oct 29 '15 at 01:32
  • Perhaps a more formal expression might be: An exception is when a method is unable to satisfy it's post-condition. – kevin cline Feb 16 '16 at 00:46
  • Perhaps a better example for save might be a problem with the backend, e. g. the server is down - exceptional as hell – Christian Sauer Feb 16 '16 at 15:08
  • This is the correct answer! – JacquesB Feb 25 '16 at 08:55
33

Exceptions should be exceptional: It's expected that the user may input invalid data, so this isn't an exceptional case

On that argument:

  • It's expected that a file may not exist, so that isn't an exceptional case.
  • It's expected that the connection to the server may be lost, so that isn't an exceptional case
  • It's expected that the configuration file may be garbled so that isn't an exceptional case
  • It's expected that your request may sometimes fall out, so that isn't an exceptional case

Any exception that you catch, you must expect because, well, you decided to catch it. And so by this logic, you should never throw an any exceptions you actually plan to catch.

Hence I think "exceptions should be exceptional" is a terrible rule of thumb.

What you should do depends on the language. Different languages have different conventions about when exceptions should be thrown. Python, for example, throws exceptions for everything and when in Python, I follow suit. C++, on the other hand, throws relatively few exceptions, and there I follow suit. You can treat C++ or Java like Python and throw exceptions for everything, but your working at odds with how the language expects itself to be used.

I prefer Python's approach, but I think it a bad idea to shoehorn other languages into it.

Winston Ewert
  • 24,732
  • 12
  • 72
  • 103
  • "Different languages..." - by the way this question is tagged [tag:java] – gnat Jan 24 '13 at 15:01
  • 1
    @gnat, I know. My point was that you should follow the conventions of the language (in this case Java) even if they aren't your favorite. – Winston Ewert Jan 24 '13 at 15:18
  • @gnat It's tagged java because I thought the context could be useful, but I am still interested in answers related to other languages. – Daniel Kaplan Jan 24 '13 at 18:47
  • 7
    +1 `"exceptions should be exceptional" is a terrible rule of thumb.` Well said! That's one of those things people just repeat without thinking about them. – Andres F. Jan 24 '13 at 22:51
  • 2
    "Expected" is defined by not by subjective argument or convention but by the contract of the API/function (this might be explicit, but is often just implied). Different functions / APIs / subsystems can have different expectations, e.g. for some higher level functionality a non-existing file is an expected case for it to handle (it might report this to a user via a GUI), for other lower level functions it is probably not (and hence should throw an exception). This answer seems to miss that important point.... – mikera Oct 21 '13 at 06:05
  • 1
    @mikera, yes a function should (only) throw the exceptions defined in its contract. But that's not the question. The question is how you decide what that contract should be. I maintain that the rule of thumb, "exceptions should be exceptional" isn't helpful in making that decision. – Winston Ewert Nov 21 '13 at 05:58
  • @WinstonEwert: If a function caller will need to handle a situation without breaking its main program flow, throwing an exception for such a situation will require the caller to add a fair amount of `try`/`catch` code. If the caller won't be able to do anything useful in that situation except throw an exception to its caller, not throwing an exception will mean the calling code has to add an `if` statement to check for success. Which type of call will end up being more common might be a matter of some guesswork, but it's an objective measure. – supercat Apr 20 '15 at 19:09
  • 1
    @supercat, I don't think it really matters which ends up being more common. I think the critical question is having a sane default. If I don't explicitly handle the error condition, does my code pretend nothing happened, or do I get a useful error message? – Winston Ewert Apr 21 '15 at 04:34
  • @WinstonEwert: If 99% of callers are going to be expecting to handle the condition, they shouldn't have to catch an exception. Actually, I'd say that if more than about 10-25% of callers will expect to handle a condition it should be possible to do so without having to catch an exception, and if more than about 10-25% will not be prepared to immediately handle it, it should be possible to request that an exception be thrown in case of trouble. In many cases, the right approach will be to have methods to perform actions either way. – supercat Apr 21 '15 at 12:36
  • 1
    @supercat, why? What is so bad about having to catch an exception that you'd risk silent failures instead of having to catch them? But I'll agree that in many cases the ideal is to have an exception-less alternative. – Winston Ewert Apr 22 '15 at 05:13
  • 1
    @WinstonEwert: My point was that in many cases the right alternative is to provide means of issuing *both* "Do X" and "Try to do X" requests, with the former throwing an exception if it couldn't do X, and the latter only throwing an exception *if it couldn't make the expected attempt*. The former should be no less convenient than the latter, but the latter should not be excessively inconvenient. In situations where a caller might wish to retry an action in a loop (possibly with different parameters) it's rather awkward to have a loop around a try/catch block where success exits the loop... – supercat Apr 22 '15 at 14:45
  • ...but the `catch` block allows the loop to run another iteration (hopefully not indefinitely). It's much cleaner to have the requested operation yield a condition which can control loop iteration directly. – supercat Apr 22 '15 at 14:47
  • @supercat, fair enough, incorporating the exception logic in a loop context would be considerably more awkward. – Winston Ewert Apr 23 '15 at 04:09
  • @DanielKaplan: If interested in other languages, the rule in Objective-C is "Exceptions are for catching programming errors". So there is no need to catch exceptions and handle them at all; they are handled by fixing the code. – gnasher729 Feb 16 '16 at 09:00
30

I always think of things like accessing the database server or a web API when thinking of exceptions. You expect the server/web API to work, but in an exceptional case it might not (server is down). A web request might be quick usually, but in exceptional circumstances (high load) it might time out. This is something out of your control.

You users' input data is in your control, since you can check what they send in and do with it what you like. In your case, I'd validate the user input before even trying to save it. And I tend to agree that users providing invalid data should be expected, and your app should account for it by validating the input and providing the user-friendly error message.

That said, I do use exceptions in most of my domain model setters, where there should be absolutely no chance that invalid data goes in. However, this is a last line of defense, and I tend to build my input forms with rich validation rules, so that there's practically no chance of triggering that domain model exception. So when a setter is expecting one thing, and gets another, it is an exceptional situation, which should not have happened in ordinary circumstances.

EDIT (something else to consider):

When sending user provided data to the db, you know beforehand what you should and shouldn't enter into your tables. This means that data can be validated against some expected format. This is something you can control. What you can't control is your server failing in the middle of your query. So you know the query is ok and data is filtered/validated, you try the query and it still fails, this is an exceptional situation.

Similarly with the web requests, you can't know if the request will time out, or fail to connect before you try sending it. So this also warrants a try/catch approach, since you can't ask the server if it will work a few milliseconds later when you send the request.

Ivan Pintar
  • 1,177
  • 1
  • 8
  • 15
  • I wouldn't necessarily say that a database or web server being down is exceptional. It's a distinct possibility and you *could* argue that it's wise to check beforehand rather than making an assumption about its state. +1 for your final 2 paragraphs :) – MattDavey Jan 24 '13 at 09:26
  • 1
    It's more of a grey area than a black/white situation. You could even say that local db server failing is closer to an exceptional situation and a web server failing is closer to an expected situation, but both still are more rare than invalid user input, so they're closer to the exception, unless you have a really crappy db server and no intention of fixing it :). – Ivan Pintar Jan 24 '13 at 09:43
  • @Pinetree totally agree, I tend to see exceptions as something you didn't *anticipate*, or something you did anticipate but were absolutely confident was impossible.. – MattDavey Jan 24 '13 at 11:26
  • 8
    But why? Why are exceptions less useful in handling problems that are more expected? – Winston Ewert Jan 24 '13 at 14:59
  • @WinstonEwert Overusing exceptions can lead to using them as flow control, where there are other means. If reading a file, you can first check if it exists, so try not to use a try/catch block. You can't check beforehand if a web request will time-out, so a try/catch block is warranted (if an exception is in fact thrown on time-out, if not, then another mechanism should be used). Having said that, I strongly believe that one should be pragmatic with the situation at hand, and not just blindly follow conventions and "rules". – Ivan Pintar Jan 24 '13 at 15:18
  • 6
    @Pinetree, checking for file existence before opening a file is bad idea. The file could cease to exist between the check and the open, the file could not have permission that let you open it, and checking for existence and then opening the file will require two expensive system calls. You are better off trying to open the file and then dealing with the failure to do so. – Winston Ewert Jan 24 '13 at 15:20
  • 10
    As far as I can see, pretty much all possible failures are better handled as recovering from the failure rather then trying to check for success before hand. Whether or not you use exceptions or something else indicate failure is a separate issue. I prefer exceptions because I can't accidentally ignore them. – Winston Ewert Jan 24 '13 at 15:23
  • @WinstonEwert The file can disappear, that's true and it was a poorly chosen example. But if you're sending potentially invalid data to the database and you know what the valid data should look like, I think you could do better than catching an exception and doing a bunch of cleanup stuff, when you can find out beforehand that you don't even need to start the query if the data is invalid. But that's just my preference when dealing with stuff like this. – Ivan Pintar Jan 24 '13 at 15:34
  • 1
    @Pinetree, certainly, it probably make sense to validate input in your app before submitting to something like a database. But that really doesn't tell me anything about whether or not I should throw an exception when the validation fails. I'd use a consistent technique between failures reported by the external technique and my own validation so that I only need to handle errors one way. – Winston Ewert Jan 24 '13 at 15:54
  • 11
    I disagree with your premise that because invalid user data is expected, it can not be considered exceptional. If I write a parser, and someone feeds it unparsable data, that is an exception. I can not continue parsing. How the exception is handled is another question entirely. – ConditionRacer Jan 24 '13 at 17:40
  • I was thinking more along the lines of form input going into specific columns in the database. A parser usually deals with bigger amounts of data of undetermined format, for which you probably can't determine if it's parse-able without actually trying to parse it, so I agree with you on that matter. – Ivan Pintar Jan 24 '13 at 21:42
  • 4
    File.ReadAllBytes will throw a `FileNotFoundException` when given the wrong input (for example a non-existant filename). That is the only valid way to threat this error, what else can you do without resulting to returning error codes? – oɔɯǝɹ Jan 24 '13 at 21:49
  • If you have your database set up to use triggers to validate the data on insertion, then also validated it in your service layer would be an unnecessary duplication of code. – Jules May 10 '16 at 10:59
19

Reference

From The Pragmatic Programmer:

We believe that exceptions should rarely be used as part of a program's normal flow; exceptions should be reserved for unexpected events. Assume that an uncaught exception will terminate your program and ask yourself, "Will this code still run if I remove all the exception handlers?" If the answer is "no," then maybe exceptions are being used in nonexceptional circumstances.

They go on to examine the example of opening a file for reading, and the file doesn't exist - should that raise an exception?

If the file should have been there, then an exception is warranted. [...] On the other hand, if you have no idea whether the file should exist or not, then it doesn't seem exceptional if you can't find it, and an error return is appropriate.

Later, they discuss why they chose this approach:

[A]n exception represents an immediate, nonlocal transfer of control - it's a kind of cascading goto. Programs that use exceptions as part of their normal processing suffer from all the readability and maintainability problems of classic spaghetti code. These programs break encapsulation: routines and their callers are more tightly coupled via exception handling.

Regarding your situation

Your question boils down to "Should validation errors raise exceptions?" The answer is that it depends on where the validation is happening.

If the method in question is within a section of the code where it is assumed that input data has already been validated, invalid input data should raise an exception; if the code is designed such that this method will receive the exact input entered by a user, invalid data is to be expected, and an exception should not be raised.

Mike Partridge
  • 6,587
  • 1
  • 25
  • 39
11

There's a lot of philosophical pontification here, but generally speaking, exceptional conditions are simply those conditions which you cannot or don't want to handle (other than cleanup, error reporting, and the like) without user intervention. In other words, they are unrecoverable conditions.

If you hand a program a file path, with the intention of processing that file in some way, and the file specified by that path doesn't exist, that's an exceptional condition. You cannot do anything about that in your code, other than report it to the user and allow them to specify a different file path.

Robert Harvey
  • 198,589
  • 55
  • 464
  • 673
  • 1
    +1, very close to what I was going to say. I would say it is more about scope, and really has nothing to do with the user. A good example of this is the difference between the two .Net functions int.Parse and int.TryParse, the former has no choice but to throw an exception on bad input, the later should never throw an exception – jmoreno Jan 24 '13 at 16:25
  • 1
    @jmoreno: Ergo, you would use TryParse when the code could do something about the unparseable condition, and Parse when it could not. – Robert Harvey Jan 24 '13 at 16:26
8

There are two concerns you should consider:

  1. you discuss a single concern - let's call it Assigner since this concern is to assign inputs to structured objects - and you express the constraint that its inputs be valid

  2. a well-implemented user-interface has an additional concern: validation of user input & constructive feedback on errors (let's call this part Validator)

From the point of view of the Assigner component, throwing an exception is totally reasonable, since you've expressed a constraint that has been violated.

From the point of view of the user experience, the user shouldn't be talking directly to this Assigner in the first place. They should be talking to it via the Validator.

Now, in the Validator, invalid user input is not an exceptional case, it's really the case you're more interested in. So here an exception wouldn't be appropriate, and this is also where you'd want to identify all errors rather than bailing out on the first.

You'll notice I didn't mention how these concerns are implemented. It seems you're talking about the Assigner and your colleague is talking about a combined Validator+Assigner. Once you realise there are two separate (or separable) concerns, at least you can discuss it sensibly.


To address Renan's comment, I'm just assuming that once you've identified your two separate concerns, it's obvious what cases should be considered exceptional in each context.

In fact, if it isn't obvious whether something should be considered exceptional, I'd argue you probably haven't finished identifying the independent concerns in your solution.

I guess that makes the direct answer to

... how do I know if my case is exceptional?

keep simplifying until it is obvious. When you have a pile of simple concepts you understand well, you can reason clearly about composing them back into code, classes, libraries or whatever.

Useless
  • 12,380
  • 2
  • 34
  • 46
  • -1 Yep, there are two concerns, but this does not answer the question "How do I know if my case is exceptional?" – RMalke Jan 24 '13 at 11:45
  • The point is the same case can be exceptional in one context, and not in another. Identifying which context you're actually talking about (rather than conflating them both) answers the question here. – Useless Jan 24 '13 at 11:47
  • ... actually, maybe it doesn't - I've addressed your point in my answer, instead. – Useless Jan 24 '13 at 11:56
4

Others have answered well, but still here is my short answer. Exception is situation where something in the environment has wrong, which you can not control and your code can not go forward at all. In this case you will also have to inform the user what went wrong, why you cannot go further, and what is the resolution.

Manoj R
  • 4,076
  • 22
  • 30
3

I've never been a great fan of the advice that you should only throw exceptions in cases that are exceptional, partly because it doesn't say anything (it's like saying that you should only eat food that is edible), but also because it is very subjective, and it's often not clear what constitutes an exceptional case and what doesn't.

However, there are good reasons for this advice: throwing and catching exceptions is slow, and if you are running your code in the debugger in Visual Studio with it set to notify you whenever an exception is thrown, you can end up being spammed by dozens if not hundreds of messages long before you get to the problem.

So as a general rule, if:

  • your code is bug free, and
  • the services on which it depends are all available, and
  • your user is using your program in the way that it was intended to be used (even if some of the input they provide is invalid)

then your code should never throw an exception, even one that is caught later. To trap invalid data, you can use validators at the UI level or code such as Int32.TryParse() in the presentation layer.

For anything else, you should stick to the principle that an exception means that your method can not do what its name says that it does. In general it's not a good idea to use return codes to indicate failure (unless your method name clearly indicates that it does so, e.g. TryParse()) for two reasons. First, the default response to an error code is to ignore the error condition and carry on regardless; second, you can all too easily end up with some methods using return codes and other methods using exceptions, and forgetting which is which. I have even seen codebases where two different interchangeable implementations of the same interface take different approaches here.

jammycakes
  • 1,150
  • 1
  • 11
  • 10
2

Exceptions should represent conditions that it's likely the immediate calling code will not be prepared to handle, even if the calling method might. Consider, for example, code which is reading some data from a file, may legitimately assume that any valid file will end with a valid record, and is not required to extract any information from a partial record.

If the read-data routine didn't use exceptions but simply reported whether or not the read succeeded, the calling code would have to look like:

temp = dataSource.readInteger();
if (temp == null) return null;
field1 = (int)temp;
temp = dataSource.readInteger();
if (temp == null) return null;
field2 = (int)temp;
temp = dataSource.readString();
if (temp == null) return null;
field3 = temp;

etc. spending three lines of code for each useful piece of work. By contrast, if readInteger will throws an exception upon encountering the end of a file, and if the caller can simply pass on the exception, then the code becomes:

field1 = dataSource.readInteger();
field2 = dataSource.readInteger();
field3 = dataSource.readString();

Much simpler and cleaner looking, with far greater emphasis on the case where things work normally. Note that in cases where the immediate caller would be expecting to handle a condition, a method which returns an error code will often be more helpful than one which throws an exception. For example, to total all the integers in a file:

do
{
  temp = dataSource.tryReadInteger();
  if (temp == null) break;
  total += (int)temp;
} while(true);

versus

try
{
  do
  {
    total += (int)dataSource.readInteger();
  }
  while(true);
}
catch endOfDataSourceException ex
{ // Don't do anything, since this is an expected condition (eventually)
}

The code which asking for the integers is expecting that one of those calls is going to fail. Having the code use an endless loop which will run until that happens is far less elegant than using a method that indicates failures via its return value.

Because classes often won't know what conditions their clients will or will not expect, it's often helpful to offer two versions of methods that could fail in ways that some callers will expect and other callers won't. Doing so will allow such methods to be used cleanly with both types of callers. Note also that even "try" methods should throw exceptions if situations arise the caller probably isn't expecting. For example, tryReadInteger should not throw an exception if it encounters a clean end-of-file condition (if the caller weren't expecting that, the caller would have used readInteger). On the other hand, it probably should throw an exception if the data could not be read because e.g. the memory stick containing it was unplugged. While such events should always be recognized as a possibility, it's unlikely that the immediate calling code would be prepared to do anything useful in response; it should certainly not be reported the same way as would be an end-of-file condition.

supercat
  • 8,335
  • 22
  • 28
2

The most important thing in writing software is making it readable. All other considerations are secondary, including making it efficient and making it correct. If it's readable, the rest can be taken care of in maintenance, and if it's not readable then you're better off just throwing it away. Therefore, you should throw exceptions when it enhances readability.

When you're writing some algorithm, just think about the person in the future who will be reading it. When you come to a place where there could be a potential problem, ask yourself if the reader wants to see how you handle that problem now, or would the reader prefer to just get on with the algorithm?

I like to think of a recipe for chocolate cake. When it tells you to add the eggs, it has a choice: it can either assume you have eggs and get on with the recipe, or it can begin an explanation for how you can get eggs if you don't have eggs. It could fill a whole book with techniques for hunting wild chickens, all to help you bake a cake. That's good, but most people aren't going to want to read that recipe. Most people would prefer to just assume that eggs are available, and get on with the recipe. That's a judgement call that authors need to make when writing recipes.

There can't be any guaranteed rules about what makes a good exception and what problems should be handled immediately, because it requires you to read the mind of your reader. The best you'll ever do is rules of thumb, and "exceptions are only for exceptional circumstances" is a pretty good one. Usually when a reader is reading your method they are looking for what the method will do 99% of the time, and they'd rather not have that cluttered with bizarre corner cases like dealing users entering illegal input and other stuff that almost never happens. They want to see the normal flow of your software laid out directly, one instruction after another as if problems never happen. Understanding your program is going to be hard enough without having to deal with constantly going off on tangents to deal with every little problem that might come up.

Geo
  • 349
  • 3
  • 11
2

There may be a requirement to report on every error we can find in the input, not just the first.

This is why you cannot throw an exception here. An exception immediately interrupts the validation process. So there would be much work-around to get this done.

A bad example:

Validation method for Dog class using exceptions:

void validate(Set<DogValidationException> previousExceptions) {
    if (!DOG_NAME_PATTERN.matcher(this.name).matches()) {
        DogValidationException disallowedName = new DogValidationException(Problem.DISALLOWED_DOG_NAME);
        if (!previousExceptions.contains(disallowedName)){
            throw disallowedName;
        }
    }
    if (this.legs < 4) {
        DogValidationException invalidDog = new DogValidationException(Problem.LITERALLY_INVALID_DOG);
        if (!previousExceptions.contains(invalidDog)){
            throw invalidDog;
        }
    }
    // etc.
}

How to call it:

Set<DogValidationException> exceptions = new HashSet<DogValidationException>();
boolean retry;
do {
    retry = false;
    try {
        dog.validate(exceptions);
    } catch (DogValidationException e) {
        exceptions.add(e);
        retry = true;
    }
} while (retry);

if(exceptions.isEmpty()) {
    dogDAO.beginTransaction();
    dogDAO.save(dog);
    dogDAO.commitAndCloseTransaction();
} else {
    // notify user to fix the problems
}

The problem here is that the validation process, to get all errors, would require to skip already found exceptions. The above could work, but this is a clear misuse of exceptions. The kind of validation you were asked for should take place before the database is touched. So there is no need to roll back anything. And, the results of the validation are likely to be validation errors (hopefully zero, though).

The better approach is:

Method call:

Set<Problem> validationResults = dog.validate();
if(validationResults.isEmpty()) {
    dogDAO.beginTransaction();
    dogDAO.save(dog);
    dogDAO.commitAndCloseTransaction();
} else {
    // notify user to fix the problems
}

Validation method:

Set<Problem> validate() {
    Set<Problem> result = new HashSet<Problem>();
    if(!DOG_NAME_PATTERN.matcher(this.name).matches()) {
        result.add(Problem.DISALLOWED_DOG_NAME);
    }
    if(this.legs < 4) {
        result.add(Problem.LITERALLY_INVALID_DOG);
    }
    // etc.
    return result;
}

Why? There are tons of reasons, and the most reasons have been pointed out in the other responses. To put it simple: It is much simpler to read and understand by others. Second, do you want to show the user stack traces to explain him he set up his dog wrongly?

If, during the commit in the second example, still an error arises, even though your validator validated the dog with zero issues, then throwing an exception is the right thing. Like: No database connection, the database entry has been modified by someone else meanwhile, or that like.

Matthias Ronge
  • 507
  • 3
  • 11
  • Pretty good answer. I would like to add that sometimes things are not this simple. Consider a case where modifying `dog` is only one part of the larger logical change that must be applied to database as one atomic change. In that case one may succeed here with `validate()` and still need to rollback due some other object later failing the validation. A good framework would support doing validation round over all objects taking part in transaction and then doing another round over all objects to commit changes. – Mikko Rantalainen May 03 '20 at 08:50