Quantcast
Viewing all articles
Browse latest Browse all 10

Write Contracts to minimize throwing exceptions

I received a great question that relates to using exceptions to indicate contract failures. I thought it would be of general interest, so I am sharing the answer and discussion here:

I am reading your book More Effective C# and just finished Ch3, Item25, Use Exceptions to Report Method Contract Failures. It all makes good sense but I'm left wondering what to do in the case of a Data Access Object. Supposing I have the following class:
public class OrderDao
{
        public Order GetOrder(int id)
        {
                // Query database
                return order;
        }
        public bool OrderExists(int id)
        {
                // Query database
                return orderExists;
        }
}
What happens if the order is not in the database? Should the GetOrder method return null or throw an exception? We can call the OrderExists method before calling GetOrder but then that would involve up to two database queries, which could be more expensive than just calling GetOrder and handling the exception. What do you think is the best strategy for this?

He correctly realizes that he does not want to make two round trips to the database. Of course, you don’t want to use exceptions as a general flow control mechanism either.

I don’t have enough context to give a definitive answer on this sample. However, that gives me the chance to discuss why you’d make different design decisions.

It may be that this class is correct exactly as it is shown. Suppose the data model is such that any code searching for an Order does so through some other table, and that a missing id would indicate a data integrity error. If that is the scenarios for this application, the correct behavior is in the class above. A query for an order when the order ID does not exist is a data integrity error. When you encounter a data integrity error, you almost certainly want to stop stop processing more requests. Something should trigger a full check and recovery of the database.

Of course, because you asked the question, this likely isn’t your scenario. It’s easy to come up with a scenario that requires a different contract. Suppose your application is a customer service app. Customers call, and give you their order number. A customer service rep keys in the order number, and your code queries that database to find that order. There’s lots of ways that can fail without being too exceptional: a customer may give the wrong ID, a customer may hear it wrong, type it wrong, and so on. There are a lot of ways that searching for an order could fail in this scenario.

The answer is to change the contract so that one method will check the database and correctly return the record, or tell you that it doesn’t exists. Suppose that instead of GetOrder() you followed the LINQ convention and wrote GetOrderOrDefault().  Now your class (in pseudocode) looks like this:

public class OrderDao
{
        public Order GetOrderOrDefault(int id)
        {
                Order order = null;
                // Query database
                // if order is found, set order = returned value.
                // else order remains null
                return order;
        }
        public bool OrderExists(int id)
        {
                // Query database
                return orderExists;
        }
}

You are still doing only one database query, and you can translate that into a single result that returns the answer, if it is found, and completes its contract even when the sought record is not found.

What’s important about this answer is that you, as a class designer, create the contracts your class adheres to. You can write those contracts in such a way that under any reasonable condition, your methods can fulfill their contracts. In this instance, the important concern is that you do not want your users paying for extra database trips. You need to create a contract that enables your users to call your methods in the way they want, and still performs the way you want internally.


Viewing all articles
Browse latest Browse all 10

Trending Articles