When to catch the Exception vs When to throw the Exceptions?

I have been coding in Java for a while now. But sometimes, I don't understand when I should throw the exception and when should I catch the exception. I am working on a project in which there are lot of methods. The hierarchy is something like this-

Method A will call Method B and Method B will call some Method C and Method C will call Method D and Method E.

So currently what I am doing is- I am throwing exceptions in all the methods and catching it in Method A and then logging as an error.

But I am not sure whether this will be the right way to do it? Or should I start catching exceptions in all the Methods. So that is why this confusion started in my- When should I catch the Exception vs When should I throw the exceptions. I know it's a silly question but somehow I am struggling to understand this major concept.

Can someone give me a detailed example of When to catch the Exception vs When to throw the Exceptions so that my concepts gets cleared on this? And in my case, should I keep on throwing the exception and then catch it in the main calling Method A?

43935 次浏览

In general, catch at the level where you can do something useful about it. For example, user is trying to connect to some database, and it fails in Method D.

How do you want to handle it? Perhaps by putting up a dialog saying "Sorry, cannot connect to SERVER/DB" or whatever. Is is method A, B, or C that created this SERVER/DB information (say, by reading a settings file or asking for user input) and tried the connection? That is probably the method that should handle the Exception. Or at least 1 away from the method that should handle it.

It really varies depending on your application, so this can only be very general advice. Most of my experience is with Swing / desktop apps, and you can usually get a feel based on which classes are doing program logic (e.g. "Controller" stuff) and who is putting up dialog boxes (e.g. "View" stuff). Usually the "controller" should catch the exception and try to do something.

In a web app this can be different.

Some very skeletal code, most of the classes do not exist, and Im not sure if a URL for the DB even makes sense, but you get the idea. Vaguely Swingish...

/*  gets called by an actionListener when user clicks a menu etc... */
public URL openTheDB() {
URL urlForTheDB = MyCoolDialogUtils.getMeAURL(URL somePreviousOneToFillInTheStart);
try {
verifyDBExists(urlForTheDB);
// this may call a bunch of deep nested calls that all can throw exceptions
// let them trickle up to here


// if it succeeded, return the URL
return urlForTheDB;
}
catch (NoDBExeption ndbe) {
String message = "Sorry, the DB does not exist at " + URL;
boolean tryAgain = MyCoolDialogUtils.error(message);
if (tryAgain)
return openTheDB();
else
return null;  // user said cancel...
}
catch (IOException joe) {
// maybe the network is down, aliens have landed
// create a reasonable message and show a dialog
}


}

You should catch the exception when you are in the method that knows what to do.

For example, forget about how it actually works for the moment, let's say you are writing a library for opening and reading files.

So you have a class, say:

public class FileInputStream extends InputStream {
public FileInputStream(String filename) { }
}

Now, lets say the file doesn't exist. What should you do? If you're struggling to think of the answer, that's because there isn't one... the FileInputStream doesn't know what to do about that problem. So it throws it up the chain, i.e.:

public class FileInputStream extends InputStream {
public FileInputStream(String filename) throws FileNotFoundException { }
}

Now, lets say someone's using your library. They might have code that looks like this:

public class Main {
public static void main(String... args) {
String filename = "foo.txt";
try {
FileInputStream fs = new FileInputStream(filename);


// The rest of the code
} catch (FileNotFoundException e) {
System.err.println("Unable to find input file: " + filename);
System.err.println("Terminating...");
System.exit(3);
}
}
}

Here, the programmer knows what to do, so they catch the exception and handle it.

You generally throws an exception when you want to notify the caller of the method of some failures.

e.g invalid user input, database problems, network outages, absent files

You should handle the exception at the lowest possible level. If method can't handle the exception properly you should throw it.

  • catch If you have method which connects to resource (eg opens file/network)
  • throw if class higher in hierarchy needs information about error

As others have said, as a general rule, you should catch an exception when you can actually handle it, otherwise, just throw it.

For example, if you are writing code that reads information about a connecting player from a save file and one of your I/O methods throws an IOException, then you would want to throw that exception and the code that invoked the load method would want to catch that exception and handle it accordingly (like disconnect the player, or send a response to the client, etc.). The reason why you would not want to handle the exception in the load method is because in the method, you cannot meaningfully handle the exception, so you delegate the exception to the caller in hope that they can handle it.

I'll share a pattern that has saved my bacon in a production environments or two.

Motivation

My aim is to ensure that the poor dude (maybe me) who is in at midnight trying to resolve a sev1 support ticket, gets a nice hierarchy of 'caused by' errors to follow, complete with data such as ID's, all without over cluttering the code.

Method

To achieve this, I catch all checked exceptions and re-throw them as unchecked exceptions. I then use a global catch at the boundary of each of my architectural layers (usually abstracted or injected so it is only ever written once). It is at these points that I can add extra context to the error stack, or decide whether to log and ignore, or raise a custom checked exception with variables to hold any extra context. On an aside, I only log errors at the top layer to stop 'double logging' from occurring (e.g. the cron job, the spring controller for ajax)

throw new RuntimeException(checked,"Could not retrieve contact " + id);

With this approach there is no cluttering of your GUI or business tier's method signatures by having to declare 'throws' for database related exceptions.

An Example of how this works in Real Life:

Lets say my code's job is an automated process to renew many insurance policies. The architecture supports a GUI to manually trigger renewal for one policy. Lets also say that the postcode for the rating area is corrupted in the DB for one of these policies.

An example of the type of error log I would want to achieve would be.

Log message: Flagging policy 1234 for manual intervention due to error:

From Stack Trace: Error Renewing Policy 1234. Rolling back the transaction ... This catch would also cover errors such as save errors, or generation of a letter.

From Stack Trace: Caused by: Error Rating Policy 1234 ... This catch would pickup errors retrieving many other objects, and algorithm errors such as NPE etc...

From Stack Trace: Caused by: Error Retrieving Rating Area 73932 ...

From Stack Trace: Caused by: JPA: unexpected null in field 'postcode'

An exception should be thrown when a function experiences a failure, i.e., an error.

A function is a unit of work, and failures should be viewed as errors or otherwise based on their impact on functions. Within a function f, a failure is an error if and only if it prevents f from meeting any of its callee’s preconditions, achieving any of f’s own postconditions, or reestablishing any invariant that f shares responsibility for maintaining.

There are three different kinds of errors:

  • a condition that prevents the function from meeting a precondition (e.g., a parameter restriction) of another function that must be called;
  • a condition that prevents the function from establishing one of its own postconditions (e.g., producing a valid return value is a postcondition); and
  • a condition that prevents the function from re-establishing an invariant that it is responsible for maintaining. This is a special kind of postcondition that applies particularly to member functions. An essential postcondition of every non-private member function is that it must re-establish its class’s invariants.

Any other condition is not an error and should not be reported as an error.

Report an error wherever a function detects an error that it cannot deal with itself and that prevents it from continuing in any form of normal or intended operation.

Handle the error in the places that have sufficient knowledge to handle the error, to translate it, or to enforce boundaries defined in the error policy, such as on main or thread mainlines.

Source: C++ Coding Standards: 101 Rules, Guidelines, and Best Practices

There are two cases when you should catch an exception.

1. At the lowest possible level

This is the level at which you are integrating with third party code, such as an ORM tool or any library performing IO operations (accessing resources over HTTP, reading a file, saving to the database, you name it). That is, the level at which you leave your application’s native code to interact with other components.

At this level, unexpected problems out of your control such as connection failures and locked files may occur.

You may want to handle a database connection failure by catching a TimeoutException so that you can retry after a few seconds. The same goes for an exception when accessing a file, which may be locked by a process at the moment but be available at the next instant.

The guidelines in this scenario are:

  • Handle only specific exceptions, such as SqlTimeoutException or IOException. Never handle a generic exception (of type Exception)
  • Handle it only if you have something meaningful to do about it, such as retries, triggering compensatory actions, or adding more data to the exception (e.g. context variables), and then re-throw it
  • Do not perform logging here
  • Let all other exceptions bubble up as they will be handled by the second case

2. At the highest possible level

This would be the last place where you can handle the exception before it is thrown directly to the user.

Your goal here is to log the error and forward the details to the programmers so they can identify and correct the error. Add as much information as possible, record it, and then show an apology message to the user, as there’s probably nothing they can do about it, especially if it is a bug in the software.

The guidelines in this second scenario are:

  • Handle the generic Exception class
  • Add more information from current execution context
  • Log the error and notify the programmers
  • Show an apology to the user
  • Work it out as soon as you can

The reasoning behind these guidelines

First, is that exceptions represent irreversible errors. They represent represent a bug in the system, a mistake made by the programmers, or a situation beyond the control of the application.

In these cases, there is usually little or nothing the user can do. Thus, the only thing you can do is log the error, take the necessary compensatory actions, and apologize to the user. If it is a mistake that the programmers made, it is best to let them know and fix it, working towards a more stable version.

Second, try catch blocks can mask application execution flow depending on how they are used. A try catch block has a similar function to that of a label and its goto companion, which causes the application execution flow to jump from one point to another.


When to throw exceptions

Easier to explain in the context of developing a library. You should throw when you reached an error and there's nothing more you can do besides letting the consumer of your APIs know about it, and letting them decide.

Imagine you're the developer of some data access library. When you reach a network error, there's nothing you can do besides throwing an exception. That's an irreversible error from a data access library standpoint.

This is different when you're developing a website. You would likely catch such exception in order to retry, but would want to throw an exception in case you received invalid parameters from outer layers since they should have been validated there.

Which is again different in a Presentation layer, where you expect the user to provide invalid parameters. In that case you just show a friendly message instead of throwing an exception.


As featured at https://roaddd.com/the-only-two-cases-when-you-should-handle-exceptions/