Previous post was about parameter descriptors. Values that initialize the parameter descriptors may be easy or hard to understand. Today we'll try to make the understanding easier.
Yet another everyday nightmare of code reader
Recall the Listing 2 from previous post.
Readers are happy when the parameter descriptor is initialized by system constant with self-explaining name, such as:
DWORD actions_to_take_regarding_file_existence = CREATE_ALWAYS;
Unfortunately, that cases occurs rarely. More often, the initializer is a brain-teasing value, like that:
DWORD access_mode = 0;
What this 0 mean? No access? Full access? Read-only access? Poor reader again and again has to:
• open documentation (if available), or the source code (if available),
• search the description of the parameter in question (if available),
• and look at meaning of value 0 (if available!).
When developers miss parameter descriptors, the situation is even worse.
Listing 1. Find the mistake
HANDLE hFile = ::CreateFile(
"\\\\ServerName\\FolderName\\Instructions.txt", 0,
0, NULL, 0, CREATE_ALWAYS, NULL
);
In the expression above, the developer mistakenly put CREATE_ALWAYS after last 0. It should be before last 0. The person reviewing developer's work overlooked this bug. Did you?
Literals are sources of problems
Values like
0, NULL, or "\\\\ServerName\\FolderName\\Instructions.txt", being used directly in the source code, are called literals. It is very tempting to use literals when you're in hurry in order to save several precious seconds. However, you or your colleague will spent minutes if not hours in attempts to understand why the code does not work as expected. In other words, passing literals as function arguments trades maintainability for code writing speed.
From literals to aliases
Help your readers! Instead of literals, use aliases - appropriately named constants that add clear interpretation to otherwise meaningless values like NULL, -1, and hardcoded paths to data. This simple action:
• makes the code self-documented,
• greatly reduces the effort to understanding what is expected from the function call,
• helps to find errors easily.
Listing 2. Find another mistake
{
const TCHAR WEEKLY_INSTRUCTIONS[] = {"\\\\ServerName\\FolderName\\Instructions.txt"};
LPCTSTR file_name = WEEKLY_INSTRUCTIONS;
const DWORD FILE_ACCESS_FOR_QUERY_ATTRIBUTES = 0;
DWORD access_mode = FILE_ACCESS_FOR_QUERY_ATTRIBUTES;
const DWORD FILE_DO_NOT_SHARE = 0;
DWORD share_mode = FILE_DO_NOT_SHARE;
const LPSECURITY_ATTRIBUTES DEFAULT_SECURITY_ATTRIBUTES = NULL;
LPSECURITY_ATTRIBUTES security_attributes = DEFAULT_SECURITY_ATTRIBUTES;
DWORD actions_to_take_regarding_file_existence = CREATE_ALWAYS;
const DWORD FILE_ATTRIBUTES_AND_FLAGS_NOT_APPLICABLE = 0;
DWORD attributes_and_flags_or_security_quality_of_service = FILE_ATTRIBUTES_AND_FLAGS_NOT_APPLICABLE;
const HANDLE FILE_DO_NOT_USE_TEMPLATE = NULL;
HANDLE template_file_supplying_attributes = FILE_DO_NOT_USE_TEMPLATE;
HANDLE hFile = ::CreateFile(
file_name, access_mode,
share_mode, security_attributes,
actions_to_take_regarding_file_existence,
attributes_and_flags_or_security_quality_of_service,
template_file_supplying_attributes
);
} How aliases help
When you read Listing 2, you may ask: "From one hand, the file containing weekly instructions opens with access just for query attributes. From other hand, the file has to be created from scratch always. Is this behavior intended? Is the code correct?".
I bet, you never ask that question seeing the Listing 1 in real code because the Listing 1 is not informative - all you can say is that some file is creating always, thanks to the one non-literal constant.
Note how the good alias for literal file path may substantially change the mental interpretation of code. The word "weekly" helps the reader to find and prevent problems of the code: are instructions (whatever they are) updated weekly? Should the reading or writing occur exactly once a week?
I recommend create aliases for every hardcoded path to data: file name, registry key, URL, database connection string, and so on. Try your best to give them informative names.
Alias for 0: to write or not to write?
By creating the named constant for a zero you make the code understandable - in most cases. Most notable exceptions are:
• Loop counter for (int count = 0; count != LIMIT; ++count)
• Head of a vector &slides[0]
Parameter descriptors and aliases go best together
| Function call helper | Parameter descriptors | Aliases |
|---|---|---|
| What it describes | What the function does | What is passed |
| Aspect of the data | Abstract | Concrete |
| Side | Server | Client |
Summary
Do not assume the reader of your code knows what means 0 or -1 passed as a parameters to the function.