Structured error handling in LotusScript

This tip shows you how to do a fair simulation of "Structured Error Handling" in LotusScript.

Error handling in LotusScript and other older variants of BASIC is clumsy and often a source of as many errors as it solves. But what if I told you that you can do a fair simulation of "Structured Error Handling" in LotusScript? What if I told you that doing so can help unravel complex code, prevent errors, and save you time? Well if that sounds like a good tool to add to your repertoire, read on.

The benefits of modern, structured error handling can be achieved by carefully formatting code and learning how to properly test for errors, how to raise custom user defined errors, and how to "re-throw" errors. Here are some things, which most people don't seem to know, that can help you write sound error handling code.

1. LotusScript does not support "nested" error handlers, but you can get the same result by breaking your code into subroutines. When your code calls a subroutine, the subroutine inherits the handler set by the caller unless the subroutine sets its own handler. If it sets its own handler, this handler remains in effect until the subroutine returns or until its handler is reset, then the caller's handler is again in effect. The subroutine will not reset the caller's error handler by calling "ON ERROR GOTO 0".

2. LotusScript does support "Bubbling up errors" to the caller, very much like C# or VB.net.

3. You set an error trap with the code "ON ERROR GOTO myhandler_Label". You can also use the command "ON specificError GOTO specificHandler_label" but this will NOT help you write "structured" handlers.

4. Use "ON ERROR GOTO 0" to turn of the error handler in the current scope. If

5. You can call "ON ERROR RESUME NEXT" to suppress all error handling. Errors simply will not be raised.

6. Use "ERROR errNumber,errMessage" to throw (raise) an error. You can specify an error defined by Notes or make up your own. You can partially log an error and then throw it up to the caller by issuing "Error Err,Error$" after you have caught, and partially handled the error.

7. Most Lotus-Defined errors are listed in Err.lss, which you can find somewhere under the Notes directory and can reference in code by issuing %Include "Err.lss".

8. The command "ON ERRO GOSUB" supported by some dialets of BASIC is not supported in LotusScript. Use "ON ERROR GOTO" with "RESUME NEXT" instead.

Here is a sample program that demonstrates how errors can be trapped and "bubbled up" in LotusScript. This is NOT an example to follow in your own code! This is just a sample agent to demonstrate the commands. To skip this example, skip down to "STRUCTURED ERROR":

       Sub Initialize ' this is an agent
 Dim session As New NotesSession
 Dim doc As notesdocument
 
 On Error Resume Next
 Call session.CurrentDatabase.
GetDocumentByUNID("invalidID") 
' Will NOT raise "Invalid UniversalID" Error
 On Error Goto 0
 If doc Is Nothing Then Print 
"Maybe I can handler this right here!" 
' I could also throw a system or custom 
error after writing this message! 
 
 On Error Goto handlerA
 Call StepB                                         
' If subroutine handles an error, 
it will raise one here and jump to handlerA
 On Error Goto 0                               
 ' Now system will handler any errors
 Error 5101,"Step A"                       
  ' Agent will abort and Notes 
will display error for us.
 Exit Sub
       HandlerA:
 On Error Goto 0                                                      
   ' Always turn off error handling 
inside a handler to prevent LOOPING!
 Msgbox Format(Err)+": "+Error$,0,
"My Error " ' Error will be cleared.
 Resume Next                                                            
 ' Jump to line after the one the raised an error.
       End Sub

       Sub stepb
 On Error Goto handlerB ' Set an error handler
 Error 5101,"Sub-0"          
 '   will jump to handler B
 
 On Error Goto handlerC 
' Set a new handler, handlerB no longer exists
 Error 5101,"Sub-1"           
'    will jump to handler C
 On Error Goto 0              
   ' Turn off handler C. 
 
 Error 5101,"Sub-2"        
   ' Will terminate stepB, 
bubble error up to handler in caller
 
 On Error Goto 0               
  ' Would NOT restore handler 
B if called--no NESTING
 Error 5101,"Sub-3"           
'     Would BUBBLE up if called, 
because all subroutine level 
handling turned off above.
 
 Exit Sub
 
       HandlerB:
 On Error Goto 0
 MessageBox Error$,0,
"Subroutine Error"
 Resume Next
       HandlerC:
 On Error Goto 0
 MessageBox Error$,0,
"Subroutine Error C"
 Resume Next
       End Sub

In the vast majority of code that I have inherited, the developer clearly did not understand these concepts. Most code that I have seen sets a single "Catch all" handler that simply displays the error number and message, which Notes would have done anyway.

Often, subroutines each contain the same pointless error handler, as the developer did not realize that the handler of the caller is still in effect. Often, multiple specific error traps (set at the top of a module) catch errors their handlers were not designed to handle, creating more problems than they solve. In some sad cases, error handlers do not turn off error handling and raise a common error, so that after running for years, the first occurrence of an error causes a server agent to go into a loop.

Then there is the deeper problem. Often, errors cannot be handled where they are raised. For example, say a subroutine tries to perform a calculation based on information in a document. If the calculation fails, the subroutine should go on to the next document. The caller needs to know how many documents were successfully processed and display a summary of the errors returned.

Some errors cannot be handled and require user intervention. The subroutine can be called in different contexts which might include being called from a server agent where now user interaction is available. Normally the subroutine passes its results information up to the caller through a parameter. Then the caller has to know all about what the subroutine does and has to pass it a string in which to return a report and the whole program becomes complex and confusing. The subroutine might also have to test to see how it has been run, introducing yet more complexity.

Fortunately, there is a better way. If the subroutine just handles the errors it can and raises a custom error for those things it can't handle, then the caller (or callers) can decide how to handle the error based on the context. The subroutine is simpler, and callers don't have to send parameters to the subroutine or know as much about it, the whole program is simpler and causes less trouble.

Structured error handling in LotusScript

Now, let's use the concepts above (and a little ingenuity) to get structured error handling in LotusScript. Instead of setting traps at the top of the code, writing handlers at the bottom, and trying to figure out what the code in the middle will do when it fails, we'll wrap each functional unit of code in its own, formatted handler, and it we can't do that neatly, we'll push functionality down into subroutines until we can.

Instead of writing a set of traps for specific errors and a set or corresponding handlers, we'll use a "SELECT CASE" to see which error occurs, and then either handle it or bubble it up to the caller. This way, we check for errors next to the code that might raise them instead of guessing what errors might arise out of a large routine and its subroutines. Since we put all the error handling code next to the code it supports, we're MUCH less likely to make mistakes, and since we will only trap errors if we can handle them, we won't have to write convoluted code.

So how do we wrap a function in a "Structured error handler"? First, we'll use a "Dummy" if-then statement to format the handler, since the labels relied on by "ON ERROR GOTO" do not provide any formatting. Next, instead of placing our handler at the bottom of the code and using "Exit Sub" (or worse, GOTO's!) to jump around it during normal processing, we'll place our error handler in the "ELSE" clause of our dummy if-then.

Consider the following example, which you can paste into a test agent and call MyRoutine from the initialize event.

       Sub MyRoutine ' which processes 
a something by calling "someSubRoutine"...
       try:
 If True Then ' dummy if-then 
provides formatting and to allows 
placement of "Catch" block in context
  On Error Goto Catch 
' Mandatory - turn on trapping
  Call someSubroutine("1") 
' routine that may raise an error
  Call someSubroutine("2") 
' will be called even if previous
 line raises error 5101
  Call someSubroutine("X") 
' will cause subroutine to raise a fatal error
  Call someSubroutine("4") 
' will never be called because 
of the previous line.
  On Error Goto 0        
 ' Mandatory -- turn off trapping
 Else
       catch:   ' catch--can never
 be reached except to handle error
  On Error Goto 0 ' turn trapping 
off during handling to be safe
  Select Case Err 
  Case 5101                  
' User Defined Error defined in 
someSubRoutine
   Print Error$ ' custom logic to 
handle this specific error
  'Case someSpecificErrorNumber 
' optional handler to handle specific 
error raised elsewhere in "some code" above.
                                                           
 ' Replace "someSpecificErrorNumber " 
with an error defined in Err.lss.
    ' optionally handle error and 
Resume Next
    ' optionally Gosub Finally and raise 
a custom error with ERROR myErrorNum,
"MyErrorMessage"
  Case Else ' unhandled error should 
bubble up to caller
   Gosub finally 
   Error Err,Error$ ' "bubble up"
 unexpected error to caller--this routine
 is ABORTED
  End Select
                               'If I'm here, I've handled the 
error, so continue on line following the error
  On Error Goto Catch 
' Mandatory - turn trapping back on
  Resume Next               
 End If ' end try catch 
       finally: 
  '...some code that should execute 
even when error occurs
  'this code must not raise an error,
 or must provide it's own handler and 
reset handling to "Catch" when done.
  ' nested error handlers are not possible, 
but nesting can be accomplished by 
breaking code into subroutines.
 If Err<>0 Then Return' mandatory line to 
resolve handler when error has occurred 
and do nothing during normal execution.
               ' Note, if you don't need to execute 
any "FINALLY" logic, remove it along with the 
previous line and never try to call it.
       End Sub

Sub someSubroutine(fieldName As String) 
 Print "Processing "+fieldName
 Select Case fieldname
 Case "1" 
  Error 5101,{Step "}+fieldName+
{" could not be processed. But that's okay. 
Processing will continue.} ' raise a 
user-defined error and abort
  ' I might write this error to a log here
  ' This subroutine is ABORTED
 Case "X"
  Error 4001,"Fatal user defined error from 
subRoutine. Processing will STOP."
  ' This subroutine is ABORTED
 End Select
 
 ' This will not execute if ANY error was 
raised above.
 Print "Processed "+fieldName+" OKAY."
       End Sub

The first thing you should note about the above is that there is no block of "ON ERROR" statement at the top, and no block of error handling logic at the bottom. The error handling takes place immediately after the code, and immediately before the "error invariant" code (or finally block).

The second thing you may notice is that the subroutine raises errors that it does not handle. These errors are passed up to the caller for handling. The caller then decides whether it can handle the error, and if not, throws up the call stack and aborts. A finally block is provided to take care of any cleaning that needs to be done before aborting.

Now, clearly, this is rather kludgey. I have used a dummy if-then statement merely to get the IDE to format my code and to allow me to move the error handler into a more contextually sensible location without resorting to hiding it behind an "Exit sub". But this approach has some merit.

Again, since the error handler cannot be nested, if you need to change the way a given error is handled for one piece of code, you need to push that code into a subroutine and handle it there. If you need a different error handler later on in the main routine, you can create another handler later on, just append a number to the Try, catch, and finally labels. (The try label is for formatting only and is never called.)

In spite of the drawbacks, this approach does the job. For one thing, it allows code to be structured in the same way it would be when using JAVA, C#, or any other language supporting true structured error handling. For another, it moves each error handler next to the code it supports, which improved readability and can unravel what might otherwise require some complex program structure. And finally, it supports the principal of catching an error where it can be handled. By eliminating the need to pass error information up through parameters or capture it in global variables, it decouples each subroutine from those above and below it in the calling chain, and this can vastly reduce the cost of supporting the code.

In the first production situation where I used this technique, I realized that the "old style" error handle had the potential to cause production failures by leading program execution along unpredictable paths in certain circumstances. After implementing this technique, the application involved has stopped exhibiting a number of odd issues that I'd have had great difficulty debugging. So this technique has proven its metal in the field, and will be a valuable addition to your toolkit if you take the time to understand the concepts and adopt it in your projects.

Do you have comments on this tip? Let us know.

This tip was submitted to the SearchDomino.com tip exchange by member Cregg Hardwick. Please let others know how useful it is via the rating scale belowip. Do you have a useful Notes/Domino tip or code to share? Submit it to our monthly tip contest and you could win a prize and a spot in our Hall of Fame.

This was first published in June 2005

Dig deeper on LotusScript

0 comments

Oldest 

Forgot Password?

No problem! Submit your e-mail address below. We'll send you an email containing your password.

Your password has been sent to:

-ADS BY GOOGLE

SearchWindowsServer

Search400

  • iSeries tutorials

    Search400.com's tutorials provide in-depth information on the iSeries. Our iSeries tutorials address areas you need to know about...

  • V6R1 upgrade planning checklist

    When upgrading to V6R1, make sure your software will be supported, your programs will function and the correct PTFs have been ...

  • Connecting multiple iSeries systems through DDM

    Working with databases over multiple iSeries systems can be simple when remotely connecting logical partitions with distributed ...

SearchEnterpriseLinux

SearchDataCenter

SearchExchange

SearchContentManagement

Close