Tips for Top Quality LotusScript
Take a few quick bug-proofing steps in the beginning and you'll be a lot happier at run time.
by Charles Connell
LotusScript is a powerful language for programming Domino and Notes applications. It 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 you wrote previously and use them to "jump start" another application. The LotusScript development environment (found within Domino Designer) is also easy and convenient, 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.
But beware: just because it's convenient and powerful doesn't mean it's impossible to use badly. You can produce high-quality, maintainable code in LotusScript. You can also produce a swarm of nasty, elusive bugs. I'm going to give you some easy-to-follow guidelines which 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 (and misery) in the long run. Your programs will have fewer bugs, and when there is a bug, it will be easier to locate and fix.
Tip #1: 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.
Tip #2: Declare the Actual Type of Each Variable
LotusScript lets you perform "lazy declarations" -- you can declare the existence of a variable without stating its datatype. When you declare a variable without a datatype, the type defaults to Variant. The problem with this is 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 like this:
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).
Tip #3: Use Constants for Strings and Numbers
It's standard practice in most programming languages to use constants instead of hard-coded strings and numbers. Yet many LotusScript programs ignore this basic rule. 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 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
Imagine this code buried within a thousand other lines and repeated in several places. Suddenly, it's a bit of a hassle to find and change the InputBox prompts and the minimum/maximum ages. Using global search-and-replace isn't really a solution, 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
Written this way, the code 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 need to be translated to the local language.
Tip #4: Use Soft-Coded Field Names
Related to the previous 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, but 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'll 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.
Tip #5: Use Copious 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's 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. (By "module," I simply mean 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's an example you are free to copy:
%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 to (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 the 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 you). For example, if the module can only 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 briefly details each subsequent change. It's a useful thing to have when you're talking with engineers of previous versions of the module. And it's a very useful thing to have if you're tracking down mysterious bugs late 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.
Implementing these coding guidelines may take you a little longer when you first start using them. You'll quickly see the payoff for the time you spent, however. 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 I've presented will reduce the number of bugs you have, and make it easier to find and fix bugs that do exist.
previous page
|