Tips for Top-Quality
LotusScript
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.)