What is the purpose of wrapping a lower level exception in Java if I'm just going to throw my wrapper exception?
I understand throwing exceptions specific to the application layer you're in, but what is the purpose of all of this exception handling overhead if you're:
- Just going to let th开发者_如何学JAVAe exceptions bubble up to the top.
- Never going to do anything "unique" with the wrapped exception.
For example, I have a DAO that throws a SQLException, and the next layer up is a manager that calls the DAO. The commonly accepted paradigm is that I create a manager exception, wrap my SQLException, and then throw the manager exception. Why? If another class up the chain is going to deal with it anyhow, then why wrap it?
In your example, it might not be useful. But suppose you're writing an interface with many possible implementations. Suppose for example that two years from now, you switch from a SQL database to a NoSQL one. Your DAO will then be forced to declare NoSQLException, and all the calling code will have to be rewritten. Whereas if the SQLException or NoSQLException is wrapped inside your custom exception, the calling code can stay as it is.
In your specific example, if you have a service layer that calls the DAO, you don't want clients to see SQL Exceptions such as "ORA-00118: unique constraint (BLABLA) violated".
Making such exception messages meaningful to upper level layers is one reason why you might consider wrapping them into more meaningful exceptions.
Catching and re-throwing is not handling anything. Better to let it bubble up in that case.
Catching a checked exception and re-throwing as unchecked can be a useful thing to do, but it's better to add more specific information.
You should only wrap and throw an exception if you're going to add some useful info to it, or to make it specific to your domain. Otherwise you should just be letting an exception bubble up to where it can be handled. I'm guessing that you're looking at some suspect code for your examples.
One example for wrapping exceptions, that might actually be useful, is to "convert" implementation-specific exceptions to something more specific to a framework in an IoC/DI scenario. For example, if you have a framework that does some work, you want to shield the client code from having to "know" a bunch of specific exceptions. So you create a set of common exceptions that your plug-ins throw and that your client code knows and can handle. Like data access exceptions for a bunch of different back ends.
java enforces you to explicitly declare which exceptions can be thrown. wrapping exceptions prevent:
1. expose what is your specific implementation (the higher level might not know that you are using sql as your data base, so you can just throw MyAppException).
2. otherwise, the list of declared exceptions would increase overtime, which will enforce the higher level to take care of all of them, which might be not very neat.
I think it all depends on how specific you want to be and if you want to include more information in your custom exception. Otherwise, you can just allow exceptions thrown by other methods to bubble up and handle them however you see fit.
In your case, SQLException might be too generic, so wrapping it in your own custom exception can help more easily determine where it was thrown in your application and to include more specific information.
So different implementations of the DAO using different datastores can transform the different storage-specific exceptions to the same abstracted storage exception. Then they just include the original for debugging purposes.
I would suggest that in many cases, exceptions could be most usefully handled by reducing them to three types:
- OperationFailedStateOkException
- The operation could not be completed, but the local and global system state is okay except to the extent implied by that failure (e.g. trying to reference a non-existent item in a Dictionary).
- OperationFailedLocalStateCorruptException
- The operation could not be completed, and the local system state is corrupt (e.g. trying to reference an item from a corrupted Dictionary).
- OperationFailedGlobalStateCorruptException
- The operation could not be completed, and the global system state is corrupt (e.g. a shared cache got corrupted).
Note that in many cases the caller of a routine won't be nearly as interested in why it failed as in what the failure implies about the state of the system. Exceptions of the first type may generally be safely caught, and execution resumed, if the programmer knows why they might occur and what to do about them. In some cases, they may need to be rethrown as exceptions of the second type (e.g. if the system was unable to perform some operation which would be necessary to maintain consistency in a data structure). Those of the second type should only be caught and rethrown as the first type at a level where the corrupt state is going to be abandoned (e.g. a "Load document" routine might catch such exceptions and rethrow as a "state ok" exception if any corrupt data structures used in the failed attempt to load a document are going to be jettisoned). Those of the third type should generally trigger a program shut down.
To be sure, it may be useful to have some gradations between the different types (e.g. indicating a global problem sufficient that the program should be shut down gently, saving user data, versus indicating that things are so bad that attempting to save user data would corrupt things worse). Nonetheless, catching exceptions and rethrowing one that indicates something about the system state may be nicer than simply leaving higher levels of the code to wonder what to do with an InvalidArgumentException.
精彩评论