Tony Marston's Blog About software development, PHP and OOP

Re: Exceptions and talking back to the user

Posted on 1st May 2019 by Tony Marston
Introduction
Domain objects with an inconsistent state
Domain objects can only throw a single error
There are only two types of error
Checked exceptions are Evil!
Exceptions were not invented for OOP
Keep It Simple, Stupid
Summary
References
Comments

Introduction

Once again I find myself having to disagree with the contents of one of Matthias Noback's blog posts, in this case it is Exceptions and talking back to the user, but as he has seen fit to ban me from posting to his blog I can only make those comments public by creating a blog post of my own. He banned me for the simple reason that he didn't like what I said in Objects should be constructed in one go which, in my humble opinion, makes him nothing more than a snowflake as he cannot stand to have his precious ideas challenged, especially from an OO heretic such as myself. As this is *MY* blog I can say what I damn well please, so buckle up and prepare for a bumpy ride.

Domain objects with an inconsistent state

In his first paragraph Mathias states the following:

We know now that domain objects with setters for every attribute will allow for the objects to be in an inconsistent state.

He is talking about a rule which dictates that a domain object should never be given data which puts it into an invalid state, but as far as I am concerned such a rule does not exist. As we are talking about passing data from the user interface (UI) through a domain object and then into to the database the only genuine rule that exists, and which existed before the OO paradigm was invented, is that all user data must be validated before it is written to database. This will prevent such values a "three" being written to a numeric field, or "today" being written to a date field as these will cause the query to fail. Using the failure code to formulate an error message which can be given back to the user is not a viable option, so such failures are considered to be catastrophic and cause the program to terminate immediately. The correct way to validate user input is within the program code, and it is only after all validation checks have passed that the data should be given to the Data Access Object (DAO) for writing to the database.

While I have seen some OO programmers use separate ->load(), ->validate() and ->store() methods from the Controller to the Model (domain object) I see this as being too clumsy. It also allows for values to be changed after the ->validate() method has been called, which could lead to the object containing unvalidated and possibly erroneous data. In my framework I have a single ->insertRecord() or ->updateRecord() methods which performs all three operations in one go, but it will only perform the ->store() operation if all the validation rules are passed. While the domain object may temporarily contain invalid data, that data is never written to the database but instead is given back to the user with the appropriate error message(s).

Matthias states that using setters for every attribute is not a good idea. In this we agree, but not for the reasons that he gives. In my methodology I have a separate class for each database table, but none of those classes contain separate attributes for the individual table columns. Instead I pass all data around in a single array as a single argument, such as $result = $dbobject->insertRecord($_POST);. Why? Because data coming from both the HTML form and the SQL query is presented in the form of an array, and as the domain object can handle arrays just as well as scalars I see no point in exploding an array into its constituent parts before I examine the individual values. Using a single array for all values instead of a separate variable for individual values avoids the ripple effect of tight coupling as I can easily change the contents of the array without having to change any method signatures.

Domain objects can only throw a single error

In his article Matthias states the following:

Exceptions get thrown ad hoc, whenever something threatens the consistency of the domain object. You can only catch one of these exceptions and turn it into an error message for the user. If the user tries to fix the issue by making a change, sending the form again, and they manage to get past this exception, there may be another exception just around the corner. This will be very frustrating for the user, as it will feel like trial-and-error.

In this we agree, but again my solution to this problem is different from his. His suggestion is as follows:

This idea gets a big thumbs down from me as the UI/presentation layer should never process any business rules as they are the sole responsibility of the domain object in the business layer. To split this processing would violate encapsulation as well as the principles of both the 3-Tier Architecture and the MVC design pattern.

The whole idea of using exceptions to deal with validation errors with user input is totally wrong. Exceptions are for errors which occur under exceptional, rare or unforeseen circumstances, which cannot be corrected by the user, or for bugs in the program. Validating user input is something which is performed over and over again, so errors from such validation cannot be regarded as unexpected and exceptional, they occur quite regularly. I designed my solution for dealing with validations errors long before exceptions were added to the language, and as my design cannot be improved by using exceptions I refuse to use them.

So what is my solution? My framework contains multiple implementations of the Template Method pattern which has separate "hook" methods for performing validation, so each of these methods can test as many business rules as is necessary and can return as many error messages as is necessary. Each domain object has an $errors array which is set to empty before the validation starts. If any test fails then an entry is added to this array using code similar to the following:

if (...condition...) {
    $this->errors['fieldname'] = getLanguageText('eNNNN');
}

Notice here that the $errors array is associative, so each error message can automatically be associated with a particular field on the screen, which then allows that message to appear directly underneath that field in the screen.

Notice also that instead of returning a hard-coded message string I am calling a function which pulls the text from a disk file which contains an array of message numbers with the associated text. As there is a separate file for each language which is supported by the application that takes care of the internationalisation issue. By having the message text in a separate file it also means that I can change the text of any message by changing the file instead of the program code which triggers that message. This is what I did in my COBOL framework in the 1980s, and it still works just as well now as it did then.

Making the Controller aware that the Model encountered errors is as easy as falling off a log:

$result = $dbobject->insertRecord($_POST);
if (!empty($dbobject->errors)) {
    $errors = $dbobject->errors;
}

The $errors array can then be passed to the View component so that its contents, if any, can be incorporated into the HTML output.

There are only two types of error

Matthias tries to say that there are two uses for exceptions when he states the following:

So exceptions thrown to protect domain invariants are not validation messages all by themselves. They are there to prevent bad things from happening to your domain objects. They are not useful if what you want is talk back to the user.

My classification of errors goes back to an earlier day before OOP existed. I was always taught that there are basically two types of error:

  1. Recoverable errors - this is where an invalid condition has been detected, but which may be corrected by going back to the user and asking him to change something before attempting a retry. This requires that a message be displayed on the screen which identifies to the user which value is currently invalid and needs to be changed.
  2. Non-recoverable errors - this is where there is no possibility of the error condition being corrected by the user, so there is no alternative but to abort the application immediately and abandon all further processing. In this case the message which is displayed to the user may be as simple as "A system error has occurred. Please contact your system administrator" but where more comprehensive details are written out to a log file and/or emailed to the administrator.

I have already described my simple mechanism for dealing with validation errors which does not use exceptions, and you'd probably be surprised to hear that my method for dealing with non-recoverable errors also does not use exceptions. For example, in my framework I have a View object which creates all HTML output by first generating an XML document containing all the relevant data, then performs an XSL transformation to create an HTML document with the aid of an XSL stylesheet. For this object to work it requires both the DOM and XSL extensions be available, and if they are not then the application aborts immediately. This is done with the following code:

function __construct ($screen_structure)
{
    // test that DOM extension has been loaded
    if (!class_exists('DomDocument')) {
        // 'DOM functions are not available.'
        trigger_error(getLanguageText('sys0070', 'DOM'), E_USER_ERROR);
    } // if

    if (!class_exists('XsltProcessor')) {
        // 'XSL functions are not available.'
        trigger_error(getLanguageText('sys0070', 'XSL'), E_USER_ERROR);
    } // if

    $this->structure = $screen_structure;

    return;

} // __construct

There are two reasons why I don't throw exceptions here:

  1. Class constructors are not allowed to throw exceptions. They either return a valid object or do not return at all.
  2. There is no point. If I know that the situation is not recoverable then I should call the error handler immediately rather than throwing it back up the chain and have it dealt with somewhere else. By throwing it back up the chain it is possible that vital information could be lost before it can be included in the error log. This was exactly what happened long ago when I was working as a junior programmer on a COBOL project. Despite me telling the team leader that vital information was being lost from the error log because of this approach he chose to ignore me simply because he considered his way to be the "right way". As soon as I became a team leader in charge of my own project I ditched his approach, wrote an error handler which could be called at the place where the error was detected, and then had the luxury of reading error logs which contained ALL the relevant information.

Checked exceptions are Evil!

In his article Matthias proposes the use of a special class of exception called App_Exception which can be thrown but which will be logged with full details but which will only show "An error has occurred" message to the user. It may sound like a good idea to create different classes of exception which need to be handled differently, but by creating different exception classes with different names you are actually creating a different class of problem rather than a better solution. The problem with throwing an exception with a name other than the default of 'Exception' is that the calling program must explicitly name that exception in its catch block otherwise that will become a fatal Uncaught Exception error in its own right.

This in my view is a big mistake for the simple reason that it violates encapsulation, and if you break one of the fundamental pillars of OOP then you shouldn't be using OOP in the first place. Encapsulation is supposed to promote the idea of implementation hiding (which is NOT the same as information hiding) so that you call an object method to do something without having to be concerned with how it is done. You know the method signature but not the code which lives behind that signature. Similarly the calling program should be able to detect an error response from the called object without having to identify all the possible exception names which may be thrown. If the called method is ever updated to include a new exception name and you do not update your calling code to include this name, then this will cause an error the next time you call it. By forcing the calling program to have knowledge of the internal workings of the method being called you are therefore forcing the implementation of that method to be UNhidden, and the simple fact that you are unhiding what is supposed to be hidden is a clear violation of the rules of encapsulation.

The fact that a change in the checked exceptions used by a module requires associated changes in all the places where that module is referenced is a prime example of the ripple effect caused by tight coupling, and as this is supposed to be a bad thing (it should be replaced with loose coupling), it is a clear sign that it should be avoided.

This type of named exception is known as a checked exception, and if you think that I am the only person who doesn't like them then please look at the following:

Exceptions were not invented for OOP

This a common misconception among a large number of OO programmers that exceptions were invented for and are therefore a necessary part of OO programming. This is untrue.

Exceptions were invented to solve the Semipredicate problem where a function can only return a single output value which is either a valid result or an indication of failure (such as boolean FALSE) but without the ability to identify the actual reason for that failure. The solution is to change the function so that when it detects an error it can throw an exception which is separate from the normal result and which can then be caught by the calling code. So instead of:

$result = function($argument);
if ($result === false) {
    ... the function did not work, but I don't know why!
} // if

you replace it with code similar to the following:

try {
    $result = function($argument);
} catch (Exception $e) {
    $errmsg = $e->getMessage();
    $errno  = $e->getCode();
    ... code to handle the error
}	

This requires the function to throw the exception using code similar to the following:

    if (condition) {
        throw new Exception('message text', 1234);
    } // if
    return $result;

In this way you can be confident that unless the called function throws an exception then the result which is returned will be valid.

Keep It Simple, Stupid

After having said that I don't use exceptions in my code base, what I actually mean is that I only use exceptions in those situations for which exceptions were specifically designed, which is the Semipredicate problem. I have an object which is used to convert dates from user (external) format to database (internal) format and back again. The database (internal) format is always 'CCYY-MM-DD' whereas the user (external) format can vary enormously. In my framework each user can specify his own preferred format, so my DateValidation object can use that format in its conversion operations.

I started off with code like this:

$date = $dateobj->getInternalDate($fieldvalue);

This returns the reformatted date, but if there are errors then how can this be detected and dealt with correctly? I tried using an $errors array for a while, but there were a few places in my code where I forgot to examine $dateobj->errors which then allowed invalid dates to pass through unnoticed. In this situation the correct method is to use exceptions, so I amended my DateValidation class to replace this code:

if (...condition...) {
    // Invalid date format: expected 'xxx'
    $this->errors[] = getLanguageText('eNNNN', $date_format);
    return false;
}

with this:

if (...condition...) {
    // Invalid date format: expected 'xxx'
    $msg = getLanguageText('eNNNN', $date_format);
    throw new Exception($msg, nnnn);
}

Notice here that I am passing back a message which has already been translated into the user's language. Notice also that as well as the error message I am also defining an error code, which gives the caller the opportunity to do something different for for different codes.

The calling code now looks like this:

try {
    $date = $dateobj->getInternalDate($fieldvalue);
    $fieldvalue = $date;
} catch (Exception $e) {
    $this->errors[$fieldname] = $e->getMessage();
}

Because I am using exception codes instead of different exception classes I can add as many new codes as I see fit, and have the calling code detect specific errors by their code instead of their name which then avoids the problems caused by checked exceptions.

Summary

Here is a brief summary of the points which I have made above:

Here endeth the lesson. Don't applaud, just throw money.

References

Here are some heretical articles I have written on the topic of OOP:


counter