Throwing Custom Errors
As we have seen in previous examples, the new THROW command can be used to elevate errors the error handler chooses not to handle, so an outer error handler (perhaps another Catch block, or some other type of error handler) can attempt to handle the error. What's not as obvious is that THROW can be used to raise custom errors, allowing us to architect our applications in an entirely different fashion.
Listing 1 shows an example for this technique. In this example, we have a class called CreditCard that simulates a credit card charging object. This object is rather simple. All it has is one method called ChargeCard(), and all that method does is check if the passed credit card number is "12345678". If so, the card is considered valid. This is a simplistic example, but all we are really interested in is the error handling. So let's see what happens when the card number is invalid.
First of all, the ChargeCard() method instantiates a class called CreditCardException and passes some detailed error information to its constructor. This class is defined a little further down and is a simple subclass of the new Visual FoxPro Exception base class. It has a few overridden properties, and one additional one that gets set based on the value passed to the constructor. Once that object is instantiated, the CreditCard class raises an error (exception) using the THROW command and the new exception object as the expression. This will immediately halt the execution of the ChargeCard() method, and invoke whatever error handler is currently in use.
|What's not as obvious is that THROW can be used to raise custom errors, allowing us to architect our applications in an entirely different fashion.|
So now let's work our way back up towards the beginning of this listing to see how this code is invoked. The listing starts out with the instantiation of the credit card object and a call to the ChargeCard()
method. The parameter passed to this method represents an invalid credit card (error handling is easier to demonstrate if things fail). All of this is wrapped into a Try/Catch
Note that the Catch
block traps for error 2071. All user-thrown exceptions end up as error 2071. In this particular example, those are all the errors we are really interested in. Of course, there could be other errors occurring, and those are caught by the second Catch
block. In a larger example, there could also be an outer error handler so we wouldn't have to worry about that possibility. The second Catch
block is not required and I just included it because I'd consider it "good form."
So what exactly happens when a user-thrown error occurs and our Catch
block kicks in? Well, first of all, there could be a number of different user-thrown errors, and we are not interested in any of them other than our custom exception. The user defined information is stored in a property called UserValue,
which is a variant and could be anything. In our case, it is another exception object, since that's what we threw, but it could be a string or any other value if the exception was thrown in the following manner:
THROW "Something is wrong!"
Since we threw an object, we can now check for detailed information on that object, such as the error number or perhaps even the class. If we discover error number 10001 (which is our custom error number), we can handle it. Otherwise, it is a different user-thrown error, and we really do not know what to do at all, so we simply elevate the error to the next level by re-throwing it.
Note that this example is not bullet-proof. The following line of code may, in fact, cause other errors:
IF oEx.UserValue.ErrorNo = 10001
is not an object, or if it is an object but doesn't have a property called ErrorNo
, this would result in yet another exception, which would be thrown to an outer exception handler. Note that the outer exception handler would receive a FoxPro error, and not the user thrown error, which would not be a good thing at all.
At this point, you may wonder how UserValue
could be an object but not have that property. The reason is simple: Just like one can throw a string or a number as the user value, one could throw any type of object as the user value. The thrown object doesn't have to be subclassed from Exception
One of the "gotchas" with this type of architecture is that you should really use Try/Catch
blocks to catch these user thrown errors. Technically, you can use ON ERROR
to catch our CreditCardException, but it is a bit trickier to do so since no error object is available.
One last word of caution: The use of a THROW
statement will always
end up as a user-thrown error. This means that if you intend to elevate an error from within a Catch
block to an outer error handler, you may be re-throwing a system error, but it will end up as a user error in the next-level error handler. The original (system) exception object will end up as the UserValue
. Of course, to handle these situations correctly, the outer exception handler needs to be aware of this.