VERSIONS: All, written for Paradox 5
SUBJECT: Errorhandling
BY: HENRIK BECHMAN and MARK PAUKER
FROM: HENRIK BECHMAN
I have a chapter on the error handling mechanism in "Killer Paradox for Windows" by Que books. The chapter was reproduced in the Paradox
Informant a while ago (December, I believe).
It's actually quite a rational mechanism.
Bottom line:
Check for true/false after each RTL method or proc.
Write all your methods and procedures to return true/false, and check those.
When you get a return of false, propagate a return false all the way back to the bottom of the calling stack, without issuing any more RTL
calls.
FROM: Paul
I like things like a try and fail mechanism, to avoid to have to write loads of exception handling code in the middle of normal code. The try onFail
mechanism only 'works' on errors in the try block. The fail section won't be called when it concerns warnings (like open and attach).
The least you can say that it is badly explained in the OPal guide. I guess the book lacks a description of a warning and error:
- Warnings and errors. Where can I find a (good) definition or a list of what can be returned. The open returns warnings (or also errors?) I don't
know, and I want to know.
- A flag can be set that makes Paradox handle warnings as errors (so fail block gets called). For some reason I don't like this. I have to change
the state of the flag each time I want to do something like testing wether an attach works or so. What's you opinion about this?
FROM: HENRIK BECHMAN
I like things like a try and fail mechanism, to avoid to have to write loads of exception handling code in the middle of normal code.
So do we all, like in InterBase or Delphi. But the fact is that Paradox natively has a *notification* based error handling mechanism, whereby it
is the responsibility of the programmer to check for errors after (essentially) each statement, and respond accordingly.
In practice detection of an error normally means: don't do anything else, ie. just return false all the way back along the calling stack, and then
let Paradox handle the error presentation.
FYI Mark Pauker (who has been frequenting this forum) is working out an implementation of an exception handling approach in PdoxWin,
based on an attempt proc, essentially:
proc attempt(const OK Logical)
if not OK then
fail()
endif
endProc
so that you can do:
attempt(tc.open("xxx")
etc.
But you have to follow up with appropriate placement of try/onFail blocks all the way up the calling chain, and you have to be consistent in
your application of this attempt mechanism, particularly wrt to direct calls to built-in methods.
Be aware that fail() causes flow of control to jump to the nearest explicit or implicit try/onFail block on the calling stack. IOW it causes your
code to be bypassed. Implicit try/onFail blocks are placed on the stack in the prefilter section of all built-in methods. Therefore a fail() will jump
back only to the nearest built-in method call.
A jump to an implicit try/onFail block is one of two things that will cause a direct built-in method call to return false (the other is a setting of
eventInfo.errorCode() <> peOK). So with an exception handling approach you have to do:
attempt(active.action(SomeAction))
to make sure your fail propagates back up the calling chain.
But using fail() a lot in PdoxWin makes me nervous because I believe it was designed to be used in rare circumstances, and is essentially a
sledgehammer of a tool which if not *very* carefully used can leave your app in an undefined state.
My own preference is to go with the flow, and simply check the return values of all RTL methods/procs, and built-in methods, as well as my
own, and simply return false all the way back up the calling stack.
if not active.action(SomeAction) then
errorLog(UserError + UserWarning,"Didn't work")
return false
endif
or
if not active.action(UserAction + UserMySetUp) then
errorLog(UserError + UserFatal,
"Could not set up for month end processing")
; does not disturb error stack, just adds an item
eventInfo.setErrorCode(UserError) ; in scope
endif
It's a little more typing, but it's very easy to implement, requires no widespread contextual structures to be erected (other than to be consistent
about it), and allows for easy development of a central error handler. And it's SAFE, because it conforms to Paradox's approach.
Warnings and errors
I presume you mean warning errors and critical errors.
The difference is simple: warning errors cause a return of false from the current RTL method/proc (as they should from your own
method/procs). What paradox does internally is simply:
errorLog(SomeErrorCode,"Some error message")
return false
Precisely what you can do in your own code.
Critical errors do this:
fail(SomeErrorCode,"Some error message")
thereby causing a jump from the current location in code to the nearest try/onFail block on the calling stack. After that life goes on as normal.
There's really nothing all that complex about it.
Be aware that critical errors (fail) do NOT cause execution of code to stop per se. If the nearest try/onFail block is half way up the calling
stack, then the rest of the code on the calling stack will be executed normally after the fail.
Also be aware that generation of neither warning errors nor critical errors cause the error method to be fired (contrary to the documentation).
The error method is fired if at the bottom of the calling stack there is some error message on the error stack. The corollary is that if the error stack
has been cleared between the time an error is fired and the bottom of the calling stack has been reached (say by firing an RTL method, all of which
clear the error stack before doing their work), then the error method will not be called.
Where can I find a (good) definition or a list of what can be returned. The open returns warnings (or also errors?) I don't know, and I want to
know.
There are *very* few critical errors. Critical errors are fired by Paradox if the statement is illegal or impossible to interpret. For instance an
attempt to assign an unassigned variable to another variable will generate a critical error. In contrast tc.open(..) and virtually all other RTL calls
will generate warning errors and nothing but warning errors. Remember that this is fundamentally a *notification* system, and essentially all you
get is warnings. Critical errors are rare exceptions.
You can get a list of errors in a table with the enumRTLErrorConstants or whatever is is.
A flag can be set that makes Paradox handle warnings as errors (so fail block gets called). For some reason I don't like this. I have to change the
state of the flag each time I want to do something like testing wether an attach works or so. What's you opinion about this?
You mean errorTrapOnWarnings(..). This is like dropping a nuclear bomb into your application. When even the most trivial warning is
generated (eg. at end of table), a fail() will be issued. This should be used only for debugging (even there rarely) and possibly for short blocks of
code:
errorTrapOnWarnings(Yes)
try
tc.field1 = a
tc.field2 = b
...
onFail
errorTrapOnWarnings(No)
return false ; retains the errorStack
endTry
errorTrapOnWarnings(no)
Best advice is not to use it at all.
FROM: Paul
Thanks a lot for your extensive answer on my questions about error handling. It enlightened me a lot. It also confirmed things I guessed to
be so, but was never sure of, because there are always that many side effects...
I share your opinion about the errorTrapOnWarnings () being a atomic bomb. (I'm writing a civil app.)
I still have a funny effect that I can't explain:
When fail () is called during the open method, the form drops in design mode. Is this normal? Does this also happen in other circumstances?
When the form is delivered, it simply doesn't start. This is the effect I want, so I guess this is okay.
FROM: HENRIK BECHMAN
When fail () is called during the open method, the form drops in design mode. Is this normal?
Yes.
Incidentally, setting eventInfo.errorCode() in open as of some recent release will now prevent the form from opening as well. I haven't
explored the ramifications of this, but my guess is that would be the preferred way of preventing an open:
if not tc.open("xxx") then]
eventInfo.setErrorCode(UserError)
return
endif
I'll have to look into this further though.
QUESTION:
Is there any functional difference between:
doSomething()
errorShow()
and
doSomething()
if eventInfo.errorCode() <> 0 then
errorShow()
endIf ?
REPLY BY: MARK PAUKER
Date: 21 August 1996
There is a big difference. Paradox maintains different kinds of errors. First, it has a global error stack which stores information about all
application errors (critical and warning) which have occurred since the last successful RTL (or built-in) method call. This global error stack is what
errorShow() will display to you. (FYI, in v4.5 and earlier, issuing an errorShow would always cause the error dialog to be displayed. If no error
existed, the dialog would simply be empty. This was changed in 5.0 such that it will be ignored if no error occurred.)
eventInfo.errorCode() is related to the global error code, but is conceptually quite different. This refers to a single "error" (not multiple errors
such as can be stored in the error stack) that has occurred in the current event stream. I put the word "error" in quotes, because this isn't
necessarily an error as much as a signal to Paradox to cancel the current event stream.
The event error can only be set by an event's default behavior or by a call to eventInfo.setErrorCode(). To illustrate, the following line causes
an error, but does not set the event error code:
tc.open("NoTable") ;NoTable doesn't exist
msgInfo("Event Error", eventInfo.errorCode()) ;Displays 0
Likewise, this line sets the event error, but not an error code:
eventInfo.setErrorCode(UserError)
errorShow() ;Does nothing
Note that in your second example above, the call to doSomething() cannot effect the event error because you are not passing the event packet
(eventInfo) and doDefault cannot be called within doSomething.
Many people have trouble figuring out the difference between these two because in some situations, they track together. For example, when
an error occurs inside of a method's default behavior (after doDefault is called but before control is returned to the method), Paradox will usually
set both the errorCode and the event error identically. Keep in mind, though, that this is not always the case, and that the errorCode has an error
stack associated with it, while the event error does not.
Another little-known aspect of all of this is the fact that if you issue an eventInfo.setErrorCode() using a Paradox-defined error code, Paradox
will place that error code onto the error stack when returning to the calling method. On the other hand, if you do the exact same thing using a
UserError constant (instead of a Paradox-defined constant), then the errorCode will be 0 upon return.