Tips for Top-Quality LotusScript

 

By Charles Connell

           

LotusScript is a powerful language for programming Domino and Notes applications. LotusScript works on the Notes client, accessing back-end databases and driving the client user interface. It works on the Domino server, running as a scheduled agent, or triggered by document events. And it runs from a web browser when embedded in a Domino agent.

LotusScript code is quite portable between applications, so you can copy pieces of code that you previously wrote and use them to "jump start" another application. The LotusScript development environment (found within Domino Designer) is also quite nice, with design-time syntax checking and single-step debugging. For all of these reasons, LotusScript should become a standard tool in your application development skill set.

Like any computer language, however, there are good and bad ways to write LotusScript. Some methods result in high-quality, maintainable code, while other methods yield many bugs that are hard to find and repair. This article presents easy-to-follow guidelines that will help you write professional LotusScript. While some of the guidelines take a few more seconds to follow initially, they will save you a lot of time in the long run. Your programs will have fewer bugs, and when there is a bug, it will be simpler to locate and fix.

 

Use Option Declare -- This is the single most important line of code you can write in a LotusScript routine. Place it in the Options section of every one of your script modules. Option Declare forces you to declare all variables, saving you from many, many hard-to-find bugs. (The folks at Iris should turn this option on, rather than off, by default.)

Consider the following piece of script.

 

Const DEFAULT = "C"

Dim Diskdrive As String

Diskdrive = Inputbox$("Please enter drive letter.", "Drive?", DEFAULT)

' Use the default if user entered nothing.

If Diskdrive = "" Then Diskdrve = DEFAULT

 

This code compiles and runs cleanly when Option Declare is not present. Unfortunately, the code has a serious error, since the variable name DiskDrive is misspelled in the last line. The code will never do what you want it to. Adding Option Declare will reveal the error as soon as you try to save the script.

 

Declare the actual type of each variable -- LotusScript allows you to perform "lazy declarations" -- meaning that you can declare the existence of a variable without stating its data type. When you declare a variable without a data type, the type defaults to Variant. The problem with this is that Variants can become almost any type at all, allowing type mismatch errors to slip by.

Consider the following example.

 

Dim LastName

LastName = Doc.LName

' many lines of intervening code . . .

If LastName = "Clinton" Then  Msgbox "Found record for Clinton."

 

This piece of script allows the LastName variable to default to the Variant type. As a result, the coding error on line 2 compiles and runs correctly. (The programmer actually wanted to type LName(0) since last names are strings rather than arrays.) The error will not show up until the last line of the program runs and causes a type mismatch. Debugging this program can be difficult because the real error (line 2) is far from the line that generates the error message (the last line).

To improve the example, it should be rewritten in the following manner.
 

Dim LastName As String

LastName = Doc.LName

' many lines of intervening code….

If LastName = "Clinton" Then  Msgbox "Found record for Clinton."

 

The error will still be found at run time, but the error message will be raised directly by the line that actually has the problem. This makes debugging much easier. Line 2 is now a type mismatch because LastName is declared with its true data type (string).

 

Use constants for strings and numbers -- Using constants, instead of hard-coded strings and numbers, is standard practice for most programming languages. Yet many LotusScript programs do not follow this basic tenet. LotusScript programmers should get on the bandwagon. The right way to put any string or number into a program is to create a constant for that value, then use the constant within the body of the code.

Using constants makes it much easier to change strings and numbers as a program evolves. (And things always change, even if the specification says that they won't.)  Consider the following example.

 

EnterAge:

AgeString = Inputbox$("Please enter your age.", "Age?", "")

AgeNumber = Cint(AgeString)  

    

If AgeNumber < 0 Or AgeNumber > 120 Then

     Msgbox "Are you sure you entered the right age?"         

     Goto EnterAge

End If

 

In this piece of script, it could be difficult to find and change the InputBox prompts and the minimum/maximum ages. (Imagine that this code is buried within 1000 other lines and repeated in several places.) Using global search-and-replace is not a good solution for changes, since you may accidentally change other, unrelated, occurrences of the same strings or numbers.

Here is the example rewritten to use constants.

 

Const AGE_PROMPT = "Please enter your age."

Const AGE_TITLE = "Age?"

Const MIN_AGE = 0

Const MAX_AGE = 120

Const AGE_ERROR_MSG = "Are you sure you entered the right age?"

    

EnterAge:

AgeString = Inputbox$(AGE_PROMPT, AGE_TITLE, "")

AgeNumber = Cint(AgeString)  

    

If AgeNumber < MIN_AGE Or AgeNumber > MAX_AGE Then

     Msgbox AGE_ERROR_MSG        

     Goto EnterAge

End If

 

When you write the code in this way, it is maintainable and modifiable. A single change to the MAX_AGE constant (or any of the other constants) will make that change wherever the constant occurs.

String constants are particularly helpful when translating software for use in another country. The translators need only look at the top of each module to see which strings needs to be translated to the local language.

 

Use Soft-Coded Field Names -- Related to the above guideline, one of the worst traps in LotusScript is using hard-coded field names. LotusScript makes it simple to hard code field names, because of its support for extended attributes of the NotesDocument class. The LotusScript documentation even describes this as a feature, though you should avoid it like the plague.

Consider the following example.

         

LastName = Doc.LName(0)

FirstName = Doc.FName(0)

 

If these field names (LName and FName) are repeated dozens of times throughout the program, across many script modules, you will have a hard time changing the field names later if you need to. It is very easy to miss a field. (Option Declare won't catch it, because the fields are object attributes, not data declarations.)

A better way to retrieve fields from a Domino document is with code like this.

 

Const FIRSTNAME_FIELD = "Fname"

Const LASTNAME_FIELD = "Lname"

LastName = Doc.GetItemValue(LASTNAME_FIELD)(0)

FirstName = Doc.GetItemValue(FIRSTNAME_FIELD)(0)

 

It is now simple to change a field name, wherever it occurs in the application, by making a one-time change to its constant name definition.

In a similar way, you should soft code write operations to fields, as shown in this example.

 

Const FIRSTNAME_FIELD = "Fname"

Const LASTNAME_FIELD = "Lname"

Const LASTNAME_PROMPT = "Please enter the last name."

' Constants for FIRSTNAME_PROMPT, LASTNAME_TITLE, FIRSTNAME_TITLE go here.

 

LastName = Inputbox$(LASTNAME_PROMPT, LASTNAME_TITLE, "")

FirstName = Inputbox$(FIRSTNAME_PROMPT, FIRSTNAME_TITLE, "")

Set Item = Doc.ReplaceItemValue(LASTNAME_FIELD, LastName)

Set Item = Doc.ReplaceItemValue(FIRSTNAME_FIELD, FirstName)

         

Again, a one-line change to a constant definition will change the name of a field throughout the application.

 

Use generous and meaningful comments -- Take the time to write comments that will help the next programmer. The next person who works on the code is likely to be a friend of yours or, very possibly, yourself a month later. (It is amazing how much you can forget about your own code if it has no comments.)

Put a block of comments at the start of each module. A module is defined here as any chunk of script that visually stands on its own, including subroutines, functions, shared library routines, form events, click actions, etc. The comment block should contain the following sections: SUMMARY, SPECIAL NOTES, and HISTORY.

Here is an example that you are free to copy for your own use.

 

%REM

SUMMARY

Find responses that have missing data in some of the fields. This is the

result of some field names being changed by mistake, then changed back

again. Get the data into the right field names. Also find the parent

(main form) of each of these responses and make sure their fields are

OK too.

 

SPECIAL NOTES: This code assumes that each response is correctly hooked

up (a child of) its parent main document.

 

HISTORY

12/7/99, Chuck Connell, Created from another agent as template.

12/8/99, Chuck Connell, Make more efficient by saving docs only once,

                        and only if they are actually changed.

12/8/99, Chuck Connell, Added error handler.

12/14/99, Chuck Connell, Changed so that it runs on selected docs

                         (from a view) rather than all docs in a view.

%END REM

 

The SUMMARY section explains why this module was written and what its basic mechanism is. This section helps new team members come up to speed quickly on the structure of the software.

The SPECIAL NOTES section contains information about issues that may trip up the next programmer (or yourself). For example, if the module must be run by someone with Manager access, state that here. Or, if the code has known problems, say so. If there is no such information, create this section and leave it blank for future use.

The HISTORY section tells who wrote the code in the first place and states brief information about each subsequent change. The history of a module is useful when you want to ask questions of the previous engineers and in tracking down mysterious bugs later in the development cycle. (A small change may have caused an unintended problem.) Always add a line about every change you make, even if it is short.

Finally, use meaningful comments throughout the body of the code. Meaningful comments continue the guidelines just described. They tell the reader why a section of code is there, what its purpose is, and how it relates to other code.

 

*

 

As mentioned above, implementing these coding guidelines may take a little longer initially. Very quickly, however, the time spent will pay off dramatically. This is especially true at the end of a project, when schedules are tight and every minute of debugging is a minute too long. The methods presented above will reduce the number of bugs you have, and make it easier to find and fix bugs that do exist.

 

Biography: Charles Connell is president of CHC-3 Consulting and has 10 years of experience with Domino and Notes. He also teaches computer science at Boston University and writes frequently on computer topics. Charles can be reached at www.chc-3.com.

 

(Copyright 2000 by Charles H. Connell Jr.)