What should I throw / expect to be thrown?
I'm completely tired of all the buggy code I've been writing for all this time and so it looks like I really need to understand the correct approach for working with exceptions.
Let's consider the following example:
interface IDataSource
{
string ReadData(int offset);
}
class FileDataSource : IDataSource {...}
class DatabaseDataSource : IDataSource {...}
If I'm using an object of class that implements IDataSource, what are the exceptions I can expect to catch if ReadData() fails for some reason?
FileDataSource can fail because there's no file, or file is less than offset. DatabaseDataSource can fail because it can't connect to the database, or there's no required table in that database.
When I do this:
var data = dataSource.ReadData(10);
What should I catch? From the other side, if I'm implementing a new class FakeDataSource
, what should I throw if something bad happens?
I have a feeling that when I throw FileNotFoundException
from FileDataSource
or SqlEx开发者_如何学Pythonception
from DatabaseDataSource
it means encapsulation violation, since I know implementation details.
Should all the exceptions thrown by any IDataSource
be bound to this interface? What I mean is - when I define any new abstraction, should I also define the related exceptions? Like this:
interface IDataSource { ... }
abstract class DataSourceException : Exception { ... }
So, when someone decides to implement IDataSource
, he should only throw DataSourceException
s. Is that correct? What about wrong values for offset
- should I still throw DataSourceException
s or standard (.NET) ArgumentOutOfRangeException
is OK?
Would also appreciate any links to the articles about error handling you consider worthy.
You should only catch the exceptions that you can handle. If your IDataSource throws FileNotFoundException or SqlException and you can't handle it, don't catch it.
When throwing exceptions, you should throw the type of exception that best describes the problem. There are three things that you want to know from a thrown exception.
- What went wrong?
- Where did it go wrong?
- Why did it go wrong?
When exceptions are used effectively, what is answered by the type of exception thrown, where is answered by the exception stack trace, and why is answered by the exception message. If you find your exceptions aren't answering all three questions, chances are they aren't being used effectively. Three rules will help you make the best use of exceptions when debugging your programs. These rules are: be specific, throw early, and catch late.
In most cases use commonly defined exceptions rather than creating your own. People will be familiar with them and understand what they mean without additional explanation. However, creating your own exception type may be helpful in cases where it is desirable to encapsulate details for a particular module. When taking this approach, wrap the original exception within the exception for your module so that the information doesn't get lost when it is rethrown.
Why should you want to catch anything at that level?
You should catch for one reason only: You know how to handle the exception.
Some say you can catch if you want to log, but you should rethrow the exception in that case. But this means repeating the same log lines all over the place - a better way is to log once at the main loop. The exception contains a stack trace anyway.
Catching and rethrowing a DataSourceException
is a typical Java solution, with one goal only: satisfying the Java compiler. After all, what is more useful when you want to handle the cause: a FileNotFoundException
, or a DataSourceException
?!
So my answer is: Do not catch unless you know how to handle the exception!
Exceptions should only be thrown in exceptional conditions where your library/application cannot do what it's being asked to do and needs help determining if and how it should recover.
Your exceptions should identify exactly what happened. They should be generic enough to indicate what the top level issue is with a message that indicates exactly what the issue was. Break down your exception categories based on the different types of operations.
If you have any trouble making your connection, throw an ConnectionException, and use the message to specify why the exception is being thrown. If you can't connect to the database, then the message should indicate so. Incorrect username? Incorrect password? Let the caller know. And so on.
If there is an issue with your query, as would be the case if the table you're trying to query does not exist, then throw a QueryException.
Limiting every exception in your DataSource implementation to a DataSourceException is almost as bad as throwing every exception as type Exception. You wouldn't do that, would you?
I also wouldn't bother wrapping the exceptions in a DataSourceException. I think this adds far more complexity to your implementation than is necessary. It'll also make readability a little bit more complicated as you'll have a bunch of code that looks like this:
new DataSourceException(new ArgumentException("Range is invalid"), "Range is invalid");
Or this:
try {
...
} catch (Exception e) {
throw new DataSourceException(e, e.Message);
}
Then for all of the classes that consume your implementation, you'll have to catch the exception, then look at the inner exception to check and see if it's something that you want to handle gracefully.
Does that make sense?
I don't think there are any specific rules for throwing exceptions, per say. Its probably more of a choice, very subjective for programmers. That being said, you can have different options. Like you mention, you can either throw built in exceptions or throw your custom exception from the classes that belong to a group, say implementing interface.
In this scenario, you are probably better off throwing your own custom exception as you know exactly where it is coming from and how to handle it. (There is no real way to enforce this through the interface model) Again, it depends on what you are doing with the exception when it bubbles up. There is no hard and fast rule for this but the idea is that you should handle the exception gracefully. I doubt you are writing buggy code. Exceptions happen due to many reasons. Database connection failure is not in your hands. In most cases, the application may not work when the database is down. There is not much you can do other than catch the exception, log, notify the required group, and fail gracefully. A bad application will show server information to the user and not notify anyone so the customer will have to call to fix it.
PS: I do not believe throwing file not found from filedatasource (or corresponding one from sqlds) is a violation of encapsulation. That class is supposed to know the implementation details since it is a specific purpose class.
You should throw whatever describes what happened and why operation failed. You should only expect the exception you really can handle - never catch exceptions just to catch them.
精彩评论