Use Named Constants to Write Clearer Code
August 18, 2004 Ted Holt
You’d expect experienced programmers to know and use techniques that less experienced programmers might not know. But even programmers with little experience know how to define named constants in one or more languages, although they may not know how to use them effectively. Yet named constants can contribute so much to program clarity, as I wish to show you now. I use RPG IV to illustrate, since that’s the language I use most often, but the principles apply to any language that supports named constants.
THE PROBLEM WITH LITERALS
The purpose of named constants is to avoid using literals, a fact that raises the question, “What’s wrong with literals?” Literals suffer from more than one deficiency.
For one, literals are not self-documenting. What does 20 mean in the following statement?
eval OrdSt = 20
Who knows? When I work on a program with code like this, I have to ask around until I find someone who knows the order status codes. I’d much rather see something like this:
eval OrdSt = Suspended
Literals are also confusing. Look at the following selected lines of code from an RPG member.
D MntSls s 9p 0 dim(12) D RgnSls s 9p 0 dim(12) eval OrdSt = 12 eval Amt = Amt * 12 if Status = 12 for i = 1 to 12
There are six instances of the literal 12 in this example. Do they all mean the same thing? If one of them has to be changed, do any or all of the others have to be changed as well? I don’t know. To be sure I modify the program correctly, I have to take time to examine all of them. I have to determine the purpose of each one, because changing a literal that should not be changed, or failing to change a literal that should be changed, could cause problems.
HOW TO CODE NAMED CONSTANTS
In the RPG IV language, constants are defined in definition specs. They are distinguished from other data definitions by the letter C in position 24 (definition type), and a constant value in positions 44 through 80 (keywords). In the keyword section, you may code the constant value by itself or as a parameter of the CONST keyword, as the following two definitions illustrate.
D NbrOfRegions c const(12) D NbrOfRegions c 12
GOOD USE OF CONSTANTS
Keep these principles in mind when using literals and named constants.
- A constant’s name should reflect function, not value.
- A numeric literal other than 0 or 1 is a good candidate for conversion to a named constant.
- A literal that represents a special value, such as a code, probably should be converted to a named constant.
- A literal intended for humans, such as an error message, probably does not need to be converted to a constant.
Let me discuss these principles in more detail.
A constant’s name should reflect function, not value.
Look at the following RPG calculation.
eval QtyDue = TotQty / 5
You could replace the literal 5 with a named constant FIVE.
D FIVE c const(5) eval QtyDue = TotQty / FIVE
You’ve replaced the literal 5 with a named constant, but the code is no more understandable than it was. Not only that, you’ve introduced another problem.
Let’s assume that the value 5 in this example indicates the number of working days in a week. What are you going to do when your organization decides to produce product on Saturday as well? Well, you could change the value of the named constant.
D FIVE c const(6) eval QtyDue = TotQty / FIVE
Isn’t that a beauty? The program will work correctly, but doesn’t something about it bother you?
Instead of basing the constant’s name on its value, base the name on its function. I suggest the name WorkDaysPerWeek would be more appropriate.
D WorkDaysPerWeek... D c const(5) eval QtyDue = TotQty / WorkDaysPerWeek
A numeric literal other than 0 or 1 is a very good candidate for conversion to a named constant.
The literal 1 is often used for counting.
eval CompletedOrders += 1 eval LineNbr = LineNbr + 1
The literal 0 is often used in numeric comparisons and for resetting the value of a numeric variable.
eval TotalSales = 0 if TotalSales > 0 if AmtDue <= 0
In such cases, use of literal 0 and 1 is fine, although I prefer the predefined constant *ZERO or *ZEROS instead of the character 0.
eval TotalSales = *zero if TotalSales > *zero if AmtDue <= *zero
The same could be said for other literals that are not likely to change. Here weight in pounds is converted to kilograms.
eval (h) MetricWeight = Weight / 2.2046
You could replace the value 2.2046 with a named constant, but it wouldn’t buy you much. The fact that the ratio of kilograms to pounds will never change supports the idea of leaving it alone. The idea of readability suggests that a named constant called PoundsPerKilogram makes for more readable code. If this conversion factor is frequently used, including it among the source lines in a copybook member of named constants would be another good reason to create a named constant. The bottom line is that you can make a good case for changing it or leaving it alone.
But as a rule, numbers other than 0 and 1 are suspect. Here’s some code that contains a number other than 0 or 1, the number 8.
D RgnSls s 9p 0 dim(8) if Regn >= 1 and Regn <= 8 for x = 1 to 8
It is possible that someone might someday redimension the array to 9 elements without changing the test or the for loop limit. Using a named constant ensures that redimensioning the array won’t affect the intention of the program.
D NbrOfRegions c 8 D RgnSls s 9p 0 dim(NbrOfRegions) if Regn >= 1 and Regn <= NbrOfRegions for x = 1 to NbrOfRegions
A literal that represents a special value, such as a code, probably should be converted to a named constant.
Databases are full of code values. I guess they always have been, since there wasn’t a lot of extra room on 80-column punched cards. Codes are wonderful, not only because they conserve disk space but also because they’re less error-prone. It’s much easier to misspell accounts receivable than 1000.
But codes are cryptic, and thus make programs harder to read. As a rule, you should define named constants that describe code values and use the named constants in source programs. You might want to place codes in copybook members so that you can include them at run time with the /COPY or /INCLUDE compiler directives. Here is an example of what I’m talking about.
D Open c 0 D Suspended c 1 D Closed c 2 D Posted c 3 if BatchStatus = Open eval BatchStatus = Posted
Notice that two of the status code values are 0 and 1, which, as I said earlier, do not always need to be converted to named constants. In this case, they serve as code values and therefore do need to be represented by named constants.
Codes are not found just in databases. OS/400 APIs often include special values, many of them in four-byte binary values. The p-values used to set attributes in display files are one-byte hexadecimal values that are much more readable as constants. When you get in the habit of questioning the use of any literal, you will see more and more places to use named constants.
Besides readability, named constants offer another advantage over literals. Suppose you decide to add another status code for deleted batches. You need to define the new status code in one place: the copy member. All programs that use the batch status field gain access to the new status code upon recompilation.
Or suppose you need to reassign the codes. You would have to change the copybook, recompile the programs that use the copybook, modify code that cannot take advantage of named constants (such as an OPNQRYF command in a CL procedure), and update the codes in the database to their new values.
A literal intended for humans, such as an error message, probably does not need to be converted to a constant.
Converting the following literal to a named constant is probably of no advantage.
eval ErrMsg = 'Invalid customer number'
But even these literals are worthy of a second look. Instead of using literals, perhaps you should be using another technique, such as message descriptions.
If you’d like to see a program that makes good use of named constants, take a look at Alex Nubla’s FTP request-validation program.
NAMING CONVENTIONS
Many programmers feel the need to distinguish constant names from variable names on sight. It is common to see constant names written with all capital letters in the case-sensitive C language. In his excellent book Code Complete, Steve McConnell suggests appending _c or _C to constant names. If you like that approach, that’s fine, but I have not found it advantageous. After all, what is today a named constant might tomorrow become a variable retrieved from a data area or an entry parameter to the program.
However, that is not to say that there is no need for a naming convention. It is probably good to develop some convention in order to avoid duplication of names among constants and other identifiers. Customer orders, purchase orders, and employees can all be suspended, but the codes for each entity might be different. It is unlikely that one constant can, or should, serve triple duty. You need three constants, which you might name coSuspended, poSuspended, and empSuspended.
CLARITY IS IMPORTANT
I hope this article has challenged you to think about the importance of writing programs with clarity in mind, no matter what language you use. I frequently work on programs and software systems that were written by people who gave no thought to clear code but threw together something to get the job done. The result is that, what should be a four-day job, takes me two weeks. I feel embarrassed to be so unproductive. Maybe that’s one reason why I try so hard to continually improve the quality of the systems I design and the source code I write. Maybe by searching for and implementing better practices, such as the judicious use of named constants, I can make life easier for myself or for other maintenance programmers in days to come.