A Constant Issue
Defining constants in your application is a good thing. But if you understand constants you can also decide when you don't need to use them, how to name them, when to set up exceptions to your names and -- the best thing -- when to replace them with parameters.
My first job as a programmer was a co-op assignment at the college where I was a student. At one point I got in trouble with my boss for including literals in my COBOL code. Here's the offending code:
CURRENT_MONTH = 1.
PERFORM PROCESS_MONTH UNTIL CURRENT_MONTH = 12.
He objected to the literals 1 and 12, and made me change the code to this:
01 FIRST_MONTH_IN_THE_YEAR_ORDINAL PIC 9 VALUE 1.
01 LAST_MONTH_IN_THE_YEAR_ORDINAL PIC 99 VALUE 12.
CURRENT_MONTH = FIRST_MONTH_IN_THE_YEAR_ORDINAL.
PERFORM PROCESS_MONTH UNTIL CURRENT_MONTH = LAST_MONTH_IN_THE_YEAR_ORDINAL.
I learned a lot of best practices from my boss. I don't, however, think this was one of them. But I can tell you what I have learned about constants and literals since then.
We use constants in our code for two reasons. First, to make our code more obvious by explaining what the values mean. Second, to make it easier to change those values.
I suspect my bosse's change actually made my code more difficult to understand. Any reasonable programmer, when looking at my rewritten code, would (probably) feel obliged to look up the two values FIRST_MONTH and LAST_MONTH in case they weren't set to 1 and 12. Also, that developer would (probably) be disgusted to discover the two constants are set to exactly what you would expect: 1 and 12. In context, the literals needed no explanation. Nor do I think my boss expected the number of months in the year to change or that we'd make 2 the first number in our counting system.
But if my boss and I didn't agree on having the constants, we did agree on what would've been a terrible way of implementing them: calling the constants ONE and TWELVE. The names of the constants should give the developer some information that reading the code doesn't. As I noted in my previous column on naming conventions, names should reflect the business/problem space.
But even that rule has exceptions. For example, the best name for the value 22/7 or 3.142 is "PI" (and, of course, in the Microsoft .NET Framework you don't have to set up PI as a constant because it's already defined for you as Math.PI).
Defining PI as a constant implements the first rule of creating constants: It makes your code more obvious. Someone seeing 3.142 in your code might justifiably wonder if you're using PI or just some random number that, coincidentally, is very close to PI. Using a constant called PI eliminates that concern. Defining PI as a constant also follows the second reason you set up a constant: PI might change, depending on the precision required. Did you, for example, find yourself thinking, "PI isn't 3.142! PI is 3.14159!"?
PI is an example of what I call an "iconic value": a value that's well known among developers (other examples: number of states in the union). The best name for a constant holding an iconic value is the name by which it's known (and, for 3.14159, that's PI). Because iconic values sometimes form a limited set of exceptions to the general naming rules, they should be set up in a class that exposes them (as Microsoft did with Math.PI). Developers should be required to use that class in all applications.
Most applications also have a set of local "iconic values": One example is the name the application uses to store a list of provinces in the ASP.NET Cache. Every application should also have a class exposing its iconic values.
Options: Enums and Parameters
Where a set of constants "hang together," your best choice is to set up an enum. In addition to making your code more obvious, enums allow you to tie variables, parameters and properties to that enum. IntelliSense then prompts you to use your predefined values. The following example sets up the values that identify different kinds of customers:
Public Enum CustomerType
And this property uses that definition:
Public Property CustType As CustomerType
In fact, if I'm not using an enum or defining an iconic constant, I question whether I should be using a constant at all. Constants are actually a terrible way to support changes in an application. If the company allows vendors three "leeway months" when setting pricing, I can certainly define a constant like this:
Const NumberOfContractLeewayMonths as Integer = 3
But if the company changes the number of months from three to two (as is likely), then I have to pull the code out of source control, make the change, recompile the code, test the change, redeploy the application and check the source back into source control. The amount of work required is way out of proportion to the level of the change.
In fact, the right thing to do for most constants is to treat them as parameters. The "constant" values should be read in from a table or configuration file that can be altered without having to recompile and redeploy the application (though I still need a release/testing process to manage the change).
This means the variables holding the values can't be declared as constants. However, these parameters can be set from code in the class constructor, which means the variables should be declared as ReadOnly:
Private ReadOnly NumberOfContractLeewayMonths as Integer
ReadOnly variables can be set from the constructor, but nowhere else. It turns out that with enums and iconic classes, that's usually all the constancy I need.
Peter Vogel is a system architect and principal in PH&V Information Services. PH&V provides full-stack consulting from UX design through object modeling to database design. Peter tweets about his VSM columns with the hashtag #vogelarticles. His blog posts on user experience design can be found at http://blog.learningtree.com/tag/ui/.