65 ObjectPAL: Errorhandling
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:
  1. 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.

  2. 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.

To index