Skip to main content

Constants

We regularly use constants in calls to method or functions or to keep track of an objects state. Since computers are best at dealing with numbers these constants usually are integer values.

But the problems for us humans is that we cannot keep track of what each of these values represents.

The windows messageid is a prime example of this. Everything that windows wants to communicate with an application is done by posting a message.

When we receive the message we inspect the value of the message identifier and react according to its value.

There are literally hundreds of message values that get sent to programs. To keep track of these, Microsoft have assigned each an everyone of these a symbol that is used in most documentation. The first thing that any programmer makes use when working in this environment is the header files that have a symbolic representation of these numeric values.

switch (msg.MsgId)
{
    case WM_MOUSEMOVE:
        ...
      break;

    case WM_LBUTTONDOWN:
        ...
      break;
}

Imagine this without these predefined symbols:

switch (msg.MsgId)
{
    case 0x200:
        ...
      break;

    case 0x201:
        ...
      break;
}

In the first version the symbols tell us what is happening in each case statement. While the second version will work identically, we cannot figure out what is happening without looking up what the values 0x200 and 0x201 are representative of.

If we want to find the place in the program where the mouse move event is handled, we can search on WM_MOUSEMOVE and locate all the instance. Searching on 0x200 will not necessarily only return the code where mouse move is called, but any other part of the program where we need to use the value. Not only that but 0x200 is really just a hexadecimal constant that could be expressed as

so the programmer would have search for all of these, making searching and understanding of the code even more difficult.

Use symbols instead of constants

If we make use of symbolic constants, and chose the names wisely, reading the calling code will tell us instantly what is happening.

private void OrderProduct(int product, int extras)
{
  // product values:  1 = Sundae, 2 = Chicken Wrap, 3 = Burger
  // extras values: 1 = choc sauce, 2 = pickles, 3 = banana flavour
  ...
}

void ImportantCode()
{
    OrderProduct(3, 2);
}

In the code above when we see the line OrderProduct(3,2); but we have no idea what is being ordered. Introducing integer constants can greatly improve the readability:

const int Sundae = 1;
const int ChickenWrap = 2;
const int Burger = 3;

const int ChocolateSauce = 1;
const int ExtraPickles = 2;
const int BananaFlavour = 3;

void ImportantCode()
{
    OrderProduct(Burger, ExtraPickles);
}

Now it is obvious what is being ordered — a burger with extra pickles.

No integer Arguments for constant parameters

When we write a function that takes one or more constants as arguments, we should refrain from using integers or strings for these parameters, because even using symbolic constants has its pitfalls.

Disallowed values can be entered

Because we are basically still dealing with integers, the compiler cannot check whether the values we are pushing into a function is actually a valid value:

void ImportantCode()
{
    OrderProduct(Burger, 4);
}

Here we are calling with an extra of value 4 which is undefined. So the behaviour of the program is undefined.

Incorrectly sequenced arguments

The use of symbolic constants still does not prevent us from accidentally switching constants when the function takes more than one argument.

void ImportantCode()
{
    OrderProduct(ExtraPickles, Burger);
}

While the code is readable — we want to order a burger with extra pickles — what we are actually ordering is a Chicken Wrap with Banana Flavour, because we accidentally reversed the order of the arguments.


Changing to enums or similar eliminates these problems:
enum ProductType  {
  Sundae = 1,
  ChickenWrap = 2,
  Burger = 3
}

enum ProductExtras  {
  ChocolateSauce = 1,
  ExtraPickles = 2,
  BananaFlavour = 3
}

private void OrderProduct(ProductType product, ProductExtras extras) {
  ...
}

void ImportantCode() {
    // generates a compiler error
    OrderProduct(ProductExtras.ExtraPickles, ProductType.Burger);

    // generates a run time exception
    OrderProduct(ProductType.Burger, (ProductExtras) 4);
    
    //  would work AND is readable.
    OrderProduct(ProductType.Burger, ProductExtras.ExtraPickles);
}

For this simple example we have the overhead of defining the extra enums. On a real project this one time setup would be amortised very quickly over many usages of these values.

We also have to type in longer tokens. But this additional effort is more than offset by the time saved when revisiting and trying to understand the code and the elimination of at least some coding errors. Also most modern IDEs will the programmer generate a lot of the code, reducing the overhead.

No boolean Arguments either

One might say that some arguments are open/shut cases, so therefore using boolean arguments to functions should be allowed.

But just as it was with integers, we can make our code a lot easier to read by using enums than it would be by using straight booleans.

Code is more readable

void WriteTableToFile(string filename, Data table, bool csv, bool quotestrings, bool override)
{
  //...
}

  // ....
  WriteTableToFile(pathName, table, true, false, true);

When coming across a call to WriteTableToFile(pathName, table, true, false, true); , we do not really know what the last three arguments mean.

But, if we introduced enums for the various boolean arguments, we would have a much better idea:

enum WriteFileType {
    Csv,
    PlainText
};

enum WriteQuote {
    None,
    Quoted
};

enum WriteMode {
    Append,
    Overwrite
};

void WriteTableToFile(string filename, Data table, WriteFileType fileType, WriteQuote quote, WriteMode writeMode) {
  //...
}

Calling WriteTableToFile(pathName, table, WriteFileType.Csv, WriteQuote.None, WriteMode.Overwrite); is now a lot more comprehensible.

Booleans grow up

I have often implemented a function that was initially had a boolean argument that later on it is life allowed more values. By implementing it as an enum we could simply add a third enum

Take the above example which wrote the table to a file. We originally had the option of switching quotes on with a bool argument. We changed that to either WriteQuote.None or WriteQuote.Quoted , which used single quotes. If someone came to us and said that they wanted to have the ability also have double quotes as an option, we would simply extend the enum:

enum WriteQuote {
    None,
    Quoted = 1,
    Single = Quoted,
    Double = 2
};

So the original WriteQuote.Quoted is still supported and equivalent to WriteQuote.Single , while there is now also a new option WriteQuote.Double which will generate double quoted fields in the file.

Avoiding negative logic

Boolean arguments frequently are used to disable or suppress something. For example, you might have a method that renders a table. Your client is happy, but would like an option to suppress the header. What is logical thing to do? Easy — add a new bool parameter to the method, called suppressHeader.

But if we do this we wind up with code like this:

void RenderTable(Table table, bool suppressHeader) {
    // Now render the header
    if (!suppressHeader)
        RenderHeader(table);

    //...
}

as you can see we now have a negative test in our program. The simple negation operator ! is easily missed. And, in a more complex situation we might have to combine this negative with one or more other negatives, which makes the logic quite fuzzy and hard to understand.

A better approach might have been to make the boolean true to render the header and false to not render the header:

void RenderTable(Table table, bool showHeader) {
    // Now render the header
    if (showHeader)
        RenderHeader(table);

    //...
}

But this runs counter to our mindset of 'suppressing' the header.

A third, and preferable, way would be to again use enums:

enum HeaderMode { Show, Suppress };

void RenderTable(Table table, HeaderMode header) {
    // Now render the header
    if (header == HeaderMode.Show)
        RenderHeader(table);

    //...
}

We could call the method with RenderTable(myTable, HeaderMode.Suppress); which is still consistent with out mindset. And the code inside the method is more comprehensible as well — a win all around.

.