administration mode
Pssst...Ferdy is the creator of JungleDragon, an awesome wildlife community. Visit JungleDragon

 

Article: Lotusscript Error Tracing »

FERDY CHRISTANT - APR 22, 2005 (10:30:46 PM)

Introduction

Error trapping and handling, much like writing documentation, is not a favorite activity of the average developer. I've seen tons of Lotusscript in my life, spotting proper error handling anywhere being rare. This includes my own work. There are typical reasons to get sloppy with error handling:

  • Plain 'old laziness, often sold as "time pressure"
  • It doesn't look good, code is less readable
  • My code is so solid, it doesn't need error trapping. much like "640k should be enough for everyone"
  • You're not my boss, and by the time anyone finds out about my kiddie script, I'll be long gone
  • Error trapping adds nothing, let Notes throw the error. It's not like users will be surprised

Obviously, none are valid reasons. Proper error handling is a matter of caring. If you're a craftsman, you care. It's as simple as that. Still, why not find a way to make it all easier, so eventually others will care as well? Maybe one day we'lll then live in an error-free world. Let's start with an error-traced world first...

Goal

The goal of this article is to introduce an error-tracing pattern that does two things:

  • Make error-tracing as easy as possible
  • Despite the simple implementation, give you comprehensive information on where your error occured and what led to the error.
I've found out that there are bits and pieces on the net that tell the same tale that I'm about to. I decided to write this article anyway, there's never enough people to inform about this pattern.

Theory: Ground rule

There's (at least) one crucial ground rule that you should tattoo on your forehead when implementing error tracing:

DO NOT *HANDLE* ERRORS FROM YOU CODE, ONLY *THROW* THEM. LET THE CALLING CODE (CLIENT) WORRY ABOUT *HANDLING* THE ERROR.

Why? Your code will never posess the intelligence to understand the impact of the error, no matter how complex you implement it. It is not up to you to decide what should happen in the case of an error, this is up to the client. Additionally, when you do inline error handling, you're making decisions about the output format and/or medium that may be wrong. Maybe your class will be used by both users and agents. It would be incorrect to assume one output format.

Theory: What is tracing

As in most languages, you can implement complex code constructions in Lotusscript. Complexity lies mostly in diverse method calls among different functions, classes or subclasses. Using a basic error trapping mechanism will only tell you that an error occured, leaving you with the time-consuming process of finding where it happened. With a slightly more advanced mechanism you can instantly find where the error occured in your code. Both mechanisms do error trapping.

Error tracing adds one piece of information to the error trapping mechanism. The extra information is a trail of method calls that led to the error. Using this information you can not only find out where the error originated, you can also trace back the method call(s) that "triggered" the error.

Theory: Automated error retrieval

If you're like me, and rather drink pints than do error traciing, you would agree that when an error occurs, you would like to get as much error details automatically, instead of manually coding them. Here's an overview of the most important error information that you can retrieve in such an event:

NameDescriptionType
Err Numeric error code integer
Error Error text string
GetThreadInfo(LSI_THREAD_LINE) Current line number variant
GetThreadInfo(LSI_THREAD_PROC) Current procedure variant
GetThreadInfo(LSI_THREAD_MODULE) Current module variant*
GetThreadInfo(LSI_THREAD_VERSION) Lotusscript version variant
GetThreadInfo(LSI_THREAD_LANGUAGE) Language setting variant
GetThreadInfo(LSI_THREAD_COUNTRY) Country/Region setting variant
GetThreadInfo(LSI_THREAD_TICKS) Current clock ticks variant
GetThreadInfo(LSI_THREAD_TICKS_PER_SEC) Clock ticks per second variant
GetThreadInfo(LSI_THREAD_PROCESS_ID) Current process ID variant
GetThreadInfo(LSI_THREAD_TASK_ID) Current task ID variant
GetThreadInfo(LSI_THREAD_CALLPROC) Calling procedure variant
GetThreadInfo(LSI_THREAD_CALLMODULE) Calling module variant
Lsi_info(1) Current line number string
Lsi_info(2) Current procedure string
Lsi_info(3) Current module string
Lsi_info(6) Lotusscript version string
Lsi_info(9) Language setting string
Lsi_info(12) Calling procedure string
Lsi_info(50) LS memory allocated string
Lsi_info(51) OS memory allocated string
Lsi_info(52) LS blocks used string

* = this call returns a hex code, not the module name

Note:To use the GetThreadInfo constants, you must include LSPRVAL.LSS, which is automatically included through LSCONST.LSS

The functions above you can use to assemble your own custom error report. Note that you are advised to use GetThreadInfo(), since Lsi_info is an undocumented feature.

Putting it into practice

Let me demonstrate all this theory though a simple case. Assume we have a script library called "ABC". In it are 3 classes, A, B, and C. A typical client would call class A (instantiate it). Class A internally calls class B. Class B internally calls class C. An error could occur in any of the classes, but for this example we're forcing one in class C. As the client calling class A, we would like to find out where the error occured and what calls caused it. Here's the full contents of the ABC script library:

Private Const DEBUG_MODE = True 'toggle debugging

Public Class A
  Sub New()
    If ( DEBUG_MODE ) Then On Error Goto ErrorThrower
    'instantiate class B
    Dim objB As New B()
    Exit Sub
ErrorThrower:
    Error Err, Error & Chr(13) + "Module: " &_
    Cstr( Getthreadinfo(1) ) & ", Line: " & Cstr( Erl )
  End Sub
End Class

Public Class B
  Sub New()
    If ( DEBUG_MODE ) Then On Error Goto ErrorThrower
    'instantiate class C
    Dim objC As New C()
    Exit Sub
ErrorThrower:
    Error Err, Error & Chr(13) + "Module: " &_
    Cstr( Getthreadinfo(1) ) & ", Line: " & Cstr( Erl )
  End Sub
End Class

Public Class C
  Sub New()
    If ( DEBUG_MODE ) Then On Error Goto ErrorThrower
    'force a division by zero error
    Dim intI As Integer
    intI = 0
    Msgbox 1/intI
    Exit Sub
ErrorThrower:
    Error Err, Error & Chr(13) + "Module: " &_
    Cstr( Getthreadinfo(1) ) & ", Line: " & Cstr( Erl )
  End Sub
End Class

Now, let's introduce the code that calls class A:

Option Public
Option Declare
Use "ABC"
Sub Initialize
  On Error Goto errorhandler
  Dim objA As New A()
  Exit Sub

ErrorHandler:
  Msgbox Cstr(Err) & ": " & Error
  Exit Sub
End Sub

Note that we are doing the error handling in the calling code. In this case I have decided to output the error in a messagebox, but I could just as easily decide to log or email the message. Let's run the calling code and see what happens:

  1. Calling code instantiates class A
  2. Inside the constructor of class A, class B is instantiated
  3. Inside the constructor of class B, class C is instantaied
  4. Inside the constructor of class C, an error occurs
  5. The error thrower of class C captures the error and throws it at the calling module(class B)
  6. The error thrower handler of class B receives the error throwed from class C, and throws it at the calling module(class A)
  7. The error thrower of class A receives the error throwed from class B, and throws it at the calling module(client code)
  8. The client code outputs the error trace in a messagebox

the result:

error trace output

Not only do we see the actual error, in the form of an error code and text. We also see where the error originated (line 7), and the entire trace to it. The beauty is that this always works. If the error occured earlier, for example in class B, the trace would be one line shorter. It works for both custom-thrown and Motes-thrown errors. Best of all, your error trapping code is always the same, you do not need to think about it anymore.

Conclusion

There are two great advantages in using the above pattern:

  • One simple error trapper, just cut and paste
  • The trace makes it easier to find your error

In short: little effort, great power. So...what excuse have you got left to not do proper error handling?

Tip: Did you notice the DEBUG_MODE constant in the ABC script library? I find it a quick and easy way to fully enable/disable error trapping in my code. I learned this from someone at work.

Share |

Comments: 15
Reviews: 4
Average rating: rating
Highest rating: 5
Lowest rating: 3

COMMENT: RICHARD SCHWARTZ emailhomepagerating

APR 25, 04:50:59

comment » Nice article. In practice, though, I rarely find that it can be quite so simple. One has to distinguish between recoverable errors and non-recoverable errors. I wold therefore modify your rule to say "Do not HANDLE non-recoverable errors in your code, only throw them."

-rich «

COMMENT: FERDY

APR 25, 18:37:05

comment » Fair enough, good point. I'll wait for other comments and update the article when needed. Thanks «

COMMENT: NATHAN T. FREEMAN email

MAY 3, 13:04:24

comment » Ummm, have you not checked out the OpenLog project on OpenNTF.org? A good deal of this is already handled (okay, thrown) there. «

COMMENT: FERDY

MAY 3, 19:04:52

comment » Nathan,

Actually I did check out the OpenLog project. I find it to be an awesome tool, yet unusable at work due to a tightly managed infrastructure

Actually, Openlog implements the pattern described above in its stacktrace methods. And it does a whole lot more. The point of my article was to describe the idea behind the pattern and how a basic implementation could look like. At the beginning of the article, I also mentioned that more people have documented this, yet that practise shows that still hardly any developer actually uses this pattern (or any error handling for that matter). It cannot be said enough dude. «

COMMENT: MATTHEW SMITH email

FEB 21, 06:53:36 AM

comment » I know it's a while since you wrote this, but a useful thing to add to the error handleThenThrow code is TypeName(Me) so that you also have an idea in which class's NEW the error occurred.

Obviously you can still work that out by the line number if you scroll through your code - although when you have multiple large script libraries in play, it's handy to know what class to look for. «

COMMENT: FERDY

FEB 22, 07:40:37 AM

comment » Matthew, that's a great tip! Completely new to me. I always assumed I was stuck with the LSI method of retrieving the class, which only returns a hex code 18 «

COMMENT: JERRY CARTER emailhomepagerating

MAR 17, 04:40:54 PM

comment » Love it. I was just drafting a post to rant on the lack of try catch in LS. Using this pattern is a fair substitute to Java-esque error handling. And to think I used to loath error stack traces. Still... I wish I could just say On Error handleErr()

Thanks! «

COMMENT: BERTRAND email

MAR 2, 17:16:17

comment » Hello everyone. I use your tip very often in my code. Thank you very much.

I have a subsidiary question for you. With LSI_Info and GetThreadInfo, it is possible to get lots of information about the current script but do you know a tip to know if a method exists for a given object in LotusScript ?

Many collegues said to me it was impossible but I want to be sure.

Thanks a lot ! «

COMMENT: MARCELO email

APR 1, 02:35:02 AM

comment » any trick to relate GetThreadInfo(LSI_THREAD_MODULE) hex code to the actual module name in some "name space context"?

e.g. it would be nice to know if the "NEW" comes from code defined in a script library, agent, etc. and which one, the hex code means nothing to me.

Thanks! «

COMMENT: PIERRE emailrating

MAY 22, 12:17:14

comment » great trick !

one question, isn't it dangerous to desactivate the error handling ??

thank you ! «

COMMENT: JJTB SOMHORST emailhomepage

JUL 17, 2008 - 09:59:51

comment » Hello,

thanks for this little but very nice tip. I was used to the exception model from JAVA and well as already said.. I'm happy that I can use this as a working alternative.

btw.. I added this post to my website ( will be up in a few days ) «

COMMENT: MANDRAKE emailrating

MAR 31, 2009 - 10:27:25

comment » Thanks a lot. 01 «

COMMENT: CLAREESH

APR 12, 2010 - 06:30:52 AM

comment » Very new to LS, this is definitely useful!!

Thanks a lot! =D «

COMMENT: BILL HANSON

AUG 6, 2010 - 02:19:12 PM

comment » Has anyone else noticed that Erl does not report the correct line number in 8.5.1? «

COMMENT: BILL HANSON

AUG 6, 2010 - 02:22:58 PM

comment » Found my own answer. Here's the text from IBM technote 1430623:

Problem:

You have created a class in your script library which throws an error. However ERL() misreports the line number.

For example: A script library has the error occur on line 17 of a custom class and line 30 in the all code view. However ERL() reports it happening on line 20.

Resolving the problem:

In previous versions of Domino Designer LotusScript classes were stored in the "Declarations" section of the script library. The ERL() number reported was relative to this section.

In Domino Designer 8.5.1 the classes are still stored in this location, but visually have their own sections in the outline view of the Designer client.

To determine the correct ERL() number you can do the following.

1. Go to the "Options" view of the script library. Determine the number of lines there.

2. Go to the "Full code" view (top of the outline). The correct line the error occurred on is the value of ERL() + the size of the Options section. «

RATE THIS CONTENT (OPTIONAL)
Was this document useful to you?
 
rating Awesome
rating Good
rating Average
rating Poor
rating Useless
CREATE A NEW COMMENT
required field
required field HTML is not allowed. Hyperlinks will automatically be converted.