Java: Checked Exceptions, Revisited Part II

Posted on November 5 2012 05:16 AM by John Atten in Java, CodeProject   ||   Comments (0)

Continued From Part I - Java: Checked Exceptions, Revisited (or, a closer examination of a flawed mechanism)

This thing is long. Here are some navigation links to topic headers:

Use Exceptions For Exceptional Circumstances, Right?

Most of what I know (or at least, think I know) was learned in the .net/C# environment. However, I believe it is nearly axiomatic that exceptions are to be used for exceptional circumstances, and (mostly) should not be used as a business logic construct. In the Oracle article, and in my example above, the InvalidAccountException, StopPaymentException and the InsufficientFundsException would appear to flagrantly violate this principle. Within limits, the only valid contingency exception here is AccountNotAvailableException, which is really just an obfuscation of SQLException.

Can we re-write these classes to do away with some of the possibly extraneous exception handling?

A Overly Simple, Hacked Example - Another Look

First off, three of our four contingencies look a whole lot like validation problems. Depending on our project architecture, we could do away with InvalidAccountException, StopPaymentException and InsufficientFundsException and check for these contingencies from our client code before calling the getCheckingAccount method defined on the Bank class, and prior to calling the processCheck method defined on the CheckingAccount Class. First, we can add a couple of boolean methods on CheckingAccount to tell us if there is a stop payment order for a check submitted, and whether there are sufficient funds in the account to process the check:

The re-worked CheckingAccount Class:
public class CheckingAccount 
{
    private String _accountID;
    private double _currentBalance;
    private ArrayList<Integer> _stoppedCheckNumbers;
    
    public String getAccountID()
    {
        return _accountID;
    }
    
    
    public double getCurrentBalance()
    {
        return _currentBalance;
    }
    
    
    public void setAccountID(String accountID)
    {
        _accountID = accountID;
    }
    
    
    public void setCurrentBalance(double currentBalance)
    {
        _currentBalance = currentBalance;
    }
    
    
    public ArrayList<Integer> getStoppedCheckNumbers()
    {
        if(_stoppedCheckNumbers == null)
        {
            _stoppedCheckNumbers = new ArrayList<Integer>();
        }
        
        return _stoppedCheckNumbers;
    }
    
    
    public boolean checkAmountApproved(double amount)
    {
        double testBalance = _currentBalance - amount;
        if(testBalance > 0)
        {
            return true;
        }
        else
        {
            return false;
        }
    }
    
    
    public boolean checkPaymentStopped(int checkNo)
    {
        if(_stoppedCheckNumbers.contains(checkNo))
        {
            return true;
        }
        else
        {
            return false;
        }
    }
    
    
    public double processCheck(Check submitted) 
    throws DatabaseAccessException
    {
        double newBalance = _currentBalance - submitted.getAmount();
        
        try
        {
            // <Code to Update Database to reflect current transaction>
        }
        catch(Exception e)
        {
            // <Code to log SQLException details>
            
            /*
             * After logging and/or otherwise handling the SQL failure, throw
             * an exception more appropriate to the context of the client code, 
             * which does not care about the details of the data access operation, 
             * only that the information could not be retreived. 
             */
            throw new DatabaseAccessException("Database Error");
        }
        
        return newBalance;
    }
}

Note in the above that we have done away with all but our single contingency, DatabaseAccessException, which in reality, is our API response to a fault. Theoretically, the only exception client code is required to handle now is the DatabaseAccessException. Now, what happens to our Bank class?

The re-Worked Bank Class:
public class Bank 
{
    public static CheckingAccount getCheckingAccount(String AccountID) 
    throws DatabaseAccessException
    {
        CheckingAccount account = new CheckingAccount();
        
        try
        {
            /*
             * <Code to retrieve Account data from data store>
             */
                    
            // Use test data to initialize an account instance:
            account.setAccountID("0001 1234 5678");
            account.setCurrentBalance(500.25);
            account.getStoppedCheckNumbers().add(1000);
        
        }
        catch(Exception e)
        {
            // <Code to log SQLException details>
            
            /*
             * After logging and/or otherwise handling the SQL failure, throw
             * an exception more appropriate to the context of the client code, 
             * which does not care about the details of the data access operation, 
             * only that the information could not be retrieved. 
             */
            throw new DatabaseAccessException("Database Error");
        }
        
        return account;
    }
    
    
    public static boolean checkAccountExists(String AccountID)
    {
        boolean exists = false;
        try
        {
            /*
             * <Code to check accountID exists in data store>
             */     
            
            if(//a valid row is returned for AccountID)
            {
                exists = true;
            }
        }
        catch(SQLException e)
        {
            // <Code to log SQLException details>
            
            /*
             * After logging and/or otherwise handling the SQL failure, throw
             * an exception more appropriate to the context of the client code, 
             * which does not care about the details of the data access operation, 
             * only that the information could not be retrieved. 
             */
            throw new DatabaseAccessException("Database Error");
        }
        
        return exists;
    }
}

Here, we have eliminated the InvalidAccountException, and added a boolean method to check whether a valid account exists for a given account number. As before, since the original static method getCheckingAccount is still performing data access, we need to retain our DatabaseAccessException.

Last, what does our client code look like now? We have done away with using Exceptions in a business logic context, what was the impact?

The re-worked Mock Client Code:
public class MockClientCode {
    
    /*
     * Assume this code is supporting UI operations.
     */
    
    static String SYSTEM_ERROR_MSG_UI = ""
        + "The requested account is unavailable due to a system error. "
        + "Please try again later.";
    
    static String INVALID_ACCOUNT_MSG_UI = ""
        + "The account number provided is invalid. Please try again.";
    
    static String INSUFFICIENT_FUNDS_MSG_UI = ""
        + "There are insufficient funds in the account to process this check.";
    
    static String STOP_PAYMENT_MSG_UI = ""
        + "There is a stop payment order on the check submitted.
        + " The transaction cannot be processed";
    
    
    public static void main(String args[])
    {
        // Sample Data:
        String accountID = "0001 1234 5678";
        int checkNo = 1000;
        double checkAmount = 100.00;
        
        // Use test data to initialize a test check instance:
        Check customerCheck = new Check(accountID, checkNo, checkAmount);
        
        CheckingAccount customerAccount = null;
        double newBalance;
        
        if(Bank.checkAccountExists(customerCheck.getAccountID()))
        {
            try 
            {
                customerAccount = Bank.getCheckingAccount(customerCheck.getAccountID());
                
                if(!customerAccount.checkPaymentStopped(customerCheck.getCheckNo()))
                {
                    if(customerAccount.checkAmountApproved(customerCheck.getAmount()))
                    {
                        newBalance = customerAccount.processCheck(customerCheck);
                        
                        // Output transaction result to UI:
                        System.out.printf(""
                        + "The transaction has been processed. New Balance is: " 
                        + DecimalFormat.getCurrencyInstance().format(newBalance));
                    }
                    else // there were insufficient funds
                    {
                        // Output the message to the user interface:
                        System.out.println(INSUFFICIENT_FUNDS_MSG_UI);
                    }
                }
                else // payment was stopped on this check no.
                {
                    // TODO Auto-generated catch block
                    System.out.println(STOP_PAYMENT_MSG_UI);
                }
            } 
            catch (DatabaseAccessException e) 
            {
                // Output the message to the user interface:
                System.out.println(SYSTEM_ERROR_MSG_UI);
            }           
        }
        else // No valid account
        {
            // Output the message to the user interface:
            System.out.println(INVALID_ACCOUNT_MSG_UI);
        }
    }

Wow. Look at that ugly nested conditional. Well, we have absolved our client code of having to handle a bunch of exceptions related to our business logic, and it is possible that improvements to the class structure, and a little refactoring could make significant improvements. In the end, though, it seems like a bit of a trade off.

What I Meant to Like About Java Checked Exceptions

In concept, I liked the idea that a method could declare, as part of its signature, that it throws a specific type of exception. What I do NOT like about it is that, you HAVE to. In many, cases, this mechanism can become quite annoying. A shining example, excerpted from the article link above, as follows:

“To programmers, it seemed like most of the common methods in Java library classes declared checked exceptions for every possible failure. For example, the java.io package relies heavily on the checked exception IOException. At least 63 Java library packages issue this exception, either directly or through one of its dozens of subclasses.”

“An I/O failure is a serious but extremely rare event. On top of that, there is usually nothing your code can do to recover from one. Java programmers found themselves forced to provide for IOException and similar unrecoverable events that could possibly occur in a simple Java library method call. Catching these exceptions added clutter to what should be simple code because there was very little that could be done in a catch block to help the situation. Not catching them was probably worse since the compiler required that you add them to the list of exceptions your method throws. This exposes implementation details that good object-oriented design would naturally want to hide.

Obviously, it seems that it is possible to go a little too far with this scenario.

On the other hand, wouldn’t it be a handy optional language feature to be able to add that throws clause if, in your infinite designer wisdom, it seemed the superior design choice?

Warning: Non-Existent, Hypothetical Feature Ahead

I grew up, so to speak, using C#, which does not have anything like the Java check-or-specify policy. It is up to the developer to properly anticipate, test for, and handle exceptions. Or to throw them programmatically, as the design may require. But in any case, client code accessing an API is blissfully unaware that a method might throw a particular type of exception until either the designed thinks of it, or it occurs in use (er, I mean, “testing”).

I propose that a useful (but at present, non-existent) feature for an existing language would be to put that check-or-specify policy into the hands of the developer, and allow the developer to invoke it by adding the throws clause to a method signature. My hypothetical mechanism would look like this:

  • If a method defined on a class throws one or more exceptions, a compiler warning will evidence itself (at least, in the land of IDE’s such as Eclipse or Visual Studio) as opposed to the compiler error we get in Eclipse with Java. However, if there is not a throws clause included in the method signature, the warning is all you would get. You could still consume the method without handling or propagating the exception from below.
  • If the developer or designer adds a throws clause to the method signature specifying one or more particular exception types, then client code is required to recognize and address those exceptions, similar to the existing Java Check-or-Specify policy.

The above mechanism would place the control in the hands of the designer, and allow selective enforcement of such a policy, while still providing helpful compiler warnings when, by design, such enforcement is relaxed. After all, if one team invests the time and mental energy to think trough the exception possibilities in their code, why not spare the consumer of that code the headache of doing it all over again? This would leave responsibility upon the developer of the client code to address, (or not) such exceptions as he/she sees fit.

What Have We Learned?

  • Exceptions in Java are often misused (as they are in C# and other languages).
  • Checked Exceptions, and the Check-or-Specify Policy, can add complexity to a design, and potentially create the need for a large numbers of additional types within a project.
  • There is a solid theoretical philosophy behind Checked Exceptions in Java that has not been well-implemented in actual use. Understanding the intent behind this language/environment feature can help in design decisions.
  • Thinking of Exceptions in terms of “Faults” and “Contingencies” is a helpful way to guide Exception Handling design in accordance with the previous point.
  • Thinking of Exceptions in terms of “Faults” and “Contingencies” encourages the use of Exceptions to enforce business rules (Not sure how I feel about this).
  • As with most things in programming, effective use of Exceptions is more often than not a case of making appropriate tradeoffs and design decisions. Careful analysis of the problem domain, exception context, and attention to client code context may demand overriding theory and dogma.
  • More study and analysis is needed on my part.

 

Half-Formed Thoughts

In researching this post, and through the discussion on the r/java sub-reddit, I have developed the following thoughts. In examining an API or class structure, and designing an effective Exception mechanism, ask yourself:

  • Who (which component of your code) cares? Where is the specific exception most effectively handled in your design? Can you deal with the specific exception appropriately at the point it is thrown, and propagate (or otherwise notify) a different exception up the call stack which does not contain implementation details of the source class? Can you maintain encapsulation?
  • There is no law which states you can’t log the initial exception, and then throw another which is more appropriate to the context of the calling code.
  • Is the exception a result of user input? If so, this may be a sign that it is more properly handled with a validation mechanism and/or improved implementation of business rules.
  • The previous point is not always true. It could be a tradeoff, where the exception results from user input, but deep within a series of calls. In this case it might be more practically handled with an exception, even though this breaks form, and uses an exception in place of business rules and validation.
  • The Java Checked Exception and Check or Specify policy is a real mixed bag. Philosophically, I like it. However, employing it effectively, and the way I was intended, requires careful design - more, I think, than many put into exception handling.
  • It is easy to use an exception when you don’t know what else to do (meaning, you need to re-examine your design, or go back to the documentation. I am 100% guilty of this at times).
  • Don’t use exceptions to “Pass the buck"” and make what should have been YOUR problem into the nest guy’s problem. As a consumer of your API, he will have even less context do deal with YOUR exception than you do.

This has been a difficult post to write. We tend to think of exceptions and exception handling as something we understand well. I for one tend to think about it when I need it, and not much else. Further, I am learning much of this as I go, meaning I am self-taught, and there is always the danger that I will think I have something figured out, only to learn (often in quite humbling, “what the hell am I doing writing about this” kinds of ways) I had it all wrong, or missed something which should have been obvious.

In trying to formulate a coherent representation of my understanding here, I have developed a greater appreciation for the difficult design choices we are faced with. Trying to wrap my head around the deeper intentions of the Java Exception mechanism, and the semi-polarized disagreement about its usefulness among Java devs, has been an eye-opening experience. The education win for me extends beyond Java, and into my toolbox.

 

Posted on November 5 2012 05:16 AM by John Atten     

Comments (0)

Java: Checked Exceptions, Revisited (or, a closer examination of a flawed mechanism)

Posted on November 5 2012 05:15 AM by John Atten in CodeProject, Java   ||   Comments (4)

This post is a re-examination of some topics I discuss in an older post. This one got long, so I broke into two. I am also going to provide some navigation links:

The Enthusiasm of Discovery

Almost exactly a year ago, I had completed an initial exploration of the Java language, and written a post here in this space about a feature I found attractive in the exception handling mechanism defined by the language. Specifically, I found checked exceptions and the “check or specify” policy associated with them of interest.

That post is here, enthusiastically titled . Ok, perhaps I got a little carried away with the title. Also, in my enthusiasm, I had not yet learned enough to authoritatively comment on the subject. Lastly, I confused the entire Checked Exception paradigm with the small piece of it which I found of interest. More on that in a moment. 

In the interest of getting some feedback on my observations, I posted a link here in  /r/java of Reddit. Apparently, in my naiveté, I had touched on a sore point in the Java community. The very first comment on the post was a good-natured “Well I see a religious war starting here fairly soon.”

“Well I see a religious war starting here fairly soon.”

                                                              -Reddit commentor

 

Soon after this first provocative (but humorous and good-natured) comment, there flowed a wealth of fascinating discussion, as experienced Java devs weighed in, both pro and con on the subject of checked exceptions, and provided a plethora of useful context and information. In considering my responses to some of these, I realized that I had missed my mark in my original article (or, the discussion helped me find it, anyway!).

One very, very helpful tidbit was a link to describing what some of the original thinking was around the checked exception mechanism. This article establishes on paper, at least, an attractive design philosophy, and example cases for the use of checked exceptions.

Cases Made Against the Existing Java Checked Exception Mechanism

Among the discussion points in the Reddit string, the following stand out to me as strong arguments, not necessarily against the Checked Exception architecture itself, but more against the manner it which it has often come to be used in the field:

  1. Many Java libraries and frameworks (especially/including core Java API’s) don’t use checked exceptions in accordance with the original design philosophy.
  2. Checked Exceptions are often used in cases which represent programmer error, rather than predictable events which are at times unavoidable, such as network timeouts, or a storage device is damaged or not available.
  3. Implementation of an existing interface can be problematic if the implementation code requires a checked exception be thrown, and the method definition on the interface does not throw the correct exception.
  4. Using Checked Exceptions is often equivalent to “passing the buck” to the consumer of your Method, class, or API.
  5. The reasoning and actual implementation decisions regarding checked and unchecked exceptions are inconsistent, and no clear rules are available.

Cases Made in Favor of the Checked Exception Mechanism

The following points were made in support of the Checked Exception notion. Philosophically, I agree with them all. It sounds like, though, that points 2 and 3 above may come into play more often than they should in actual practice and negate some of these philosophically sound points:

  1. When methods declare their exceptions, it forces the designer of an API to carefully consider what can go wrong and throw the appropriate exceptions.
  2. Exceptions, declared as part of a method signature, forces the consumer of an API to anticipate and handle what may go wrong.
  3. A method signature represents a contract between the caller and the implementer. It defines the arguments required, the return type, and in the case of Java Checked Exceptions, makes the error cases visible. Requiring a consumer to address anticipatable exceptions theoretically should result in a more robust API (points 2 and three from the previous section notwithstanding).

Clearly, there are some good points on either side of this argument, and like in politics, I find myself stuck on the fence. We will examine my thoughts on this in a bit, after we walk through my current understanding of how things were supposed to be.

On Faults and Contingencies - The Way it was Supposed to Be

According to the article referenced in the link above, a central notion to effective usage of the Java exception architecture is the differentiation between Faults and Contingencies:

Contingency An expected condition demanding an alternative response from a method that can be expressed in terms of the method's intended purpose. The caller of the method expects these kinds of conditions and has a strategy for coping with them.Fault

An unplanned condition that prevents a method from achieving its intended purpose that cannot be described without reference to the method's internal implementation.

The article provides a simple example case in which an API defines a processCheck() method. processCheck will either process a check as requested by the client code, or throw one of two exceptions related to the problem domain: StopPaymentException or InsufficientFundsException. These are presented as examples of Contingencies for which any client calling upon this method should be prepared, and therefore, as exemplary models for Checked Exception usage.

The article additionally discusses a third possibility, in which database access, as part of the transaction processing performed by processCheck, utilizes the JDBC API. JDBC throws a single checked exception, SQLException, to report problems with accessing the data store. Therefore, our processCheck method is required to handle SQLException, or pass any such occurrence up the call stack by including SQLException in its throws clause. In this last case, client code is unlikely to have sufficient context to deal appropriately with whatever caused the SQLException (nor, for that matter, is the processCheck method itself) other than gracefully exiting and informing the calling procedure that something went wrong while accessing the database. This last case is an example of a fault.

To my way of thinking, Contingencies usually exist within the problem domain, and in fact client code calling upon an API is more likely to contain an effective strategy for dealing with them than is the API itself. Faults, on the other hand, represent unexpected conditions which, when our equipment and program is working as designed, should not occur at all. Note that I include “program working as designed” in that sentence. Programmer error and code bugs, for me, fall into this category.

A Overly Simple, Hacked Example

So, thinking I have absorbed the thinking put forth in the article, I construct a hasty example structure which extends the example in the article into a bit of pseudo-code. Note, I am not representing this to be good code, and it represents a hack design at best, greatly over-simplified. My objective is to illustrate the exception usage concepts under discussion. First is the CheckingAccountClass:

A silly mockup of the CheckingAccount Class:
public class CheckingAccount 
{
    private String _accountID;
    private double _currentBalance;
    private ArrayList<Integer> _stoppedCheckNumbers;
 
    public String getAccountID()
    {
        return _accountID;
    }
 
 
    public double getCurrentBalance()
    {
        return _currentBalance;
    }
 
 
    public void setAccountID(String accountID)
    {
        _accountID = accountID;
    }
 
 
    public void setCurrentBalance(double currentBalance)
    {
        _currentBalance = currentBalance;
    }
 
 
    public ArrayList<Integer> getStoppedCheckNumbers()
    {
        if(_stoppedCheckNumbers == null)
        {
            _stoppedCheckNumbers = new ArrayList<Integer>();
        }
 
        return _stoppedCheckNumbers;
    }
 
 
    public double processCheck(Check submitted) 
    throws InsufficientFundsException, StopPaymentException, 
    DatabaseAccessException
    {
        if(_stoppedCheckNumbers.contains(submitted.getCheckNo()))
        {
            throw new StopPaymentException();
        }
 
        double newBalance = _currentBalance - submitted.getAmount();
 
        if(newBalance < 0)
        {
            throw new InsufficientFundsException(_currentBalance, 
                    submitted.getAmount(), 
                    newBalance);
        }
 
        try
        {
            // <Code to Update Database to reflect current transaction>
        }
        catch(SQLException e)
        {
            // <Code to log SQLException details>
 
            /* 
             * After logging and/or otherwise handling the SQL failure, throw
             * an exception more appropriate to the context of the client code, 
             * which does not care about the details of the data access operation, 
             * only that the information could not be retrieved. 
             */
            throw new DatabaseAccessException("Database Error");
        }
 
        return newBalance;
    }
}

 

In order to examine this in context, we will also want to look at a usage scenario with some mock client code. In order to do THAT, we also need a Bank class, which:

  1. Provides a static factory method to access CheckingAccount objects, and;
  2. Is also subject to the nefarious SQLException while doing so.
  3. Introduces yet another contingency - what if the account number submitted on a check does not exist? For this eventuality, we define a fourth Contingency Exception: InvalidAccountException.
The Bank Class (with pseudo-code):
public class Bank 
{
    public static CheckingAccount getCheckingAccount(String AccountID) 
    throws DatabaseAccessException, InvalidAccountException
    {
        CheckingAccount account = new CheckingAccount();
 
        try
        {
            /* 
             * <Code to retrieve Account data from data store>
             */
 
            if(//no row is returned for AccountID)
            {
                throw new InvalidAccountException();
            }
 
            // Use test data to initialize an account instance:
            account.setAccountID("0001 1234 5678");
            account.setCurrentBalance(500.25);
            account.getStoppedCheckNumbers().add(1000);
 
        }
        catch(SQLException e)
        {
            // <Code to log SQLException details>
 
            /* 
             * After logging and/or otherwise handling the SQL failure, throw
             * an exception more appropriate to the context of the client code, 
             * which does not care about the details of the data access operation, 
             * only that the information could not be retrieved. 
             */
            throw new DatabaseAccessException("Database Error");
        }
 
        return account;
    }
}

 

The Bank Class above provides the functionality needed to mock up some client code. I am not going to get all fancy with this, and the overall class structure is NOT what I am here to examine. We will pretend that the void main method used here actually represents some code in the service of a user interface, and see what our Checked Exception-heavy design looks like from the consumption standpoint:

Some Mock Client Code Consuming the Bank and CheckingAccount API’s:
public class MockClientCode 
{
    /* 
     * Assume this code is supporting UI operations.
     */
 
    static String SYSTEM_ERROR_MSG_UI = ""
        + "The requested account is unavailable due to a system error. "
        + "Please try again later.";
 
    static String INVALID_ACCOUNT_MSG_UI = ""
        + "The account number provided is invalid. Please try again.";
 
    static String INSUFFICIENT_FUNDS_MSG_UI = ""
        + "There are insufficient funds in the account to process this check.";
 
    static String STOP_PAYMENT_MSG_UI = ""
        + "There is a stop payment order on the check submitted. 
        + "The transaction cannot be processed";
 
 
    public static void main(String args[])
    {
        // Sample Data:
        String accountID = "0001 1234 5678";
        int checkNo = 1000;
        double checkAmount = 100.00;
 
        // Use test data to initialize a test check instance:
        Check customerCheck = new Check(accountID, checkNo, checkAmount);
 
        CheckingAccount customerAccount = null;
        double newBalance;
 
        try 
        {
            customerAccount = Bank.getCheckingAccount(customerCheck.getAccountID());
            newBalance = customerAccount.processCheck(customerCheck);
 
            // Output transaction result to UI:
            System.out.printf("The transaction has been processed. New Balance is: " 
                    + DecimalFormat.getCurrencyInstance().format(newBalance));
        } 
        catch (DatabaseAccessException e) 
        {
            // Output the message to the user interface:
            System.out.println(SYSTEM_ERROR_MSG_UI);
        } 
        catch (InvalidAccountException e) 
        {
            // Output the message to the user interface:
            System.out.println(INVALID_ACCOUNT_MSG_UI);
        } 
        catch (InsufficientFundsException e) 
        {
            // Output the message to the user interface:
            System.out.println(INSUFFICIENT_FUNDS_MSG_UI);
        } 
        catch (StopPaymentException e) 
        {
            // TODO Auto-generated catch block
            System.out.println(STOP_PAYMENT_MSG_UI);
        } 
    }
}

 

As With Most Things, a Series of Trade-offs

The “design” above (I am using the term very loosely here) works in accordance with the Java Checked Exception mechanism, and specifically addresses most of the concerns discussed in the Oracle article. I included my own embellishment at the point where the SQLException is potentially thrown locally, by way of logging the data-access specifics for examination by the dev team and/or the dba, and throwing a more general contingency-type exception defined for the problem space (DatabaseAccessException) which can then be handled by client code in a proper context (“Sorry, we seem to be experiencing a system outage. Please try again later”).

Upsides:

On the upside, this code makes robust use of the Check-or-Specify policy built in to the Java environment. Code which attempts to call the public processCheck method will be required to handle all three contingency cases:

  1. The account number submitted on the check is not valid within the system
  2. There is a Stop Payment order on the check number being processed
  3. There are not sufficient funds available to cover the withdrawal
  4. There is a problem accessing account data (for whatever reason)

From an API design standpoint, this could be considered a good thing. Developers who may be utilizing this class as part of a library or framework will know immediately what contingencies they will have to address within their own code.

With respect to the SQLException (a fault, as opposed to a domain contingency), there is very little that can be done about this even at the point where it is first thrown, other than log the details and notify the calling method that there was a problem retrieving the requested data. In my mind, the farther this specific exception is allowed to propagate from its source, the there is even less ability to do anything with the information it contains. So in my example, I use the try . . . catch block to deal with it as best we can, and then propagate a more appropriate contingency exception.

Also, while the wealth of business-logic-related exceptions is a bit strange looking from my C# background, the code in the mocked up client class is actually pretty easy to read, and figure out what is going on.

Downsides:

On the other hand, what should be a (relatively) simple class structure actually introduces a total of FIVE new types into our project:

  1. The CheckingAccount class itself
  2. The Bank Class
  3. Four new exception types:
    1. InvalidAccountException
    2. StopPaymentException
    3. InsufficientFundsException
    4. DatabaseAccessException

Client code attempting to use our CheckingAccount class must be aware of all four types, which increases the number of dependencies within a client class. While not serious, as a project grows larger, the inflated number of new types created as contingency exceptions may grow large, and the number of dependencies may grow proportionately. All in all, this could substantially increase project complexity. As one Reddit commenter pointed out, one of the problems with implementations of the Checked Exception mechanism in Java is that it results in what is essentially a “shadow type system.” Hard not to disagree, in light of the example Oracle provides us with.

On top of this, the excessive number of catch clauses is almost as bad as introducing a big switch statement.

Also, within the Oracle article, and within this class definition, what we are essentially doing through the use of “contingency exceptions” is using the exception mechanism to address business logic concerns.

RHMABHT9JGEH

 

Posted on November 5 2012 05:15 AM by John Atten     

Comments (4)

About the author

My name is John Atten, and my username on many of my online accounts is xivSolutions. I am Fascinated by all things technology and software development. I work mostly with C#, Java, SQL Server 2012, learning ASP.NET MVC, html 5/CSS/Javascript. I am always looking for new information, and value your feedback (especially where I got something wrong!). You can email me at:

jatten@typecastexception.com

Web Hosting by