February 12, 2007

From Literals to Aliases

Abstract
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 helperParameter descriptorsAliases
What it describesWhat the function doesWhat is passed
Aspect of the dataAbstractConcrete
SideServerClient


Summary
Do not assume the reader of your code knows what means 0 or -1 passed as a parameters to the function.

February 9, 2007

From Arguments to Descriptors

Listing 1. Maintenance quiz for typical source code
Look at following statement:
HANDLE hFile = ::CreateFile( instructions, 0, 0, NULL, CREATE_ALWAYS, 0, NULL );

Can you guess what means 6-th parameter (with third 0 passed as argument)?
Do you know the type of 4-th parameter (with first NULL passed as argument)?

Stop maintenance nightmare
Your code includes tens to thousands function calls:
• to system API,
• to C++ standard library,
• to third-party libraries,
• to your own classes and free functions.
Readers of your code (including yourself) do not remember the exact ordering, type and purpose of each parameter/argument in each function call. So help your readers!

From in-place arguments to parameter descriptors
Instead of passing argument directly to the function, use parameter descriptors - appropriately named temporary variables, initialized with arguments you want pass to the function.
Now everybody instantly sees:
• the type of each parameter and argument,
• the role that parameter/argument plays during the function call.
Parameter descriptors are good documentation of the function being called.

Listing 2. Parameter descriptors make the code readable
{
LPCTSTR file_name = instructions;
DWORD access_mode = 0;
DWORD share_mode = 0;
LPSECURITY_ATTRIBUTES security_attributes = NULL;
DWORD actions_to_take_regarding_file_existence = CREATE_ALWAYS;
DWORD attributes_and_flags_or_security_quality_of_service = 0;
HANDLE template_file_supplying_attributes = NULL;

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
);
}


Recommendations
When you create the parameter descriptor, make sure it has exactly the type of the correspondent function parameter. That will help you detect type mismatches between actual argument and formal parameter.
I recommend enclose parameter descriptors and the function call in curly braces in order to limit the visibility and the lifetime of parameter descriptors.

Do not copy brainlessly
When inexperienced developers create parameter descriptors for WinAPI functions, they often copy the names of parameters from API documentation to names of parameter descriptors. That shortcoming reduces the usefulness of parameter descriptors, because API names often are too cryptic. Can you guess the purpose of the parameter dwCreationDisposition?
My advice: do not copy parameter names from the documentation. Instead, describe their meaning. Well, copy - not the name, but the sentence describing the parameter - to your code. Then convert sentences to parameter names.


Summary
Do not assume the reader of your code knows the type and purpose of arguments passed to the function or returned from the function. State them explicitly via parameter descriptors.

February 7, 2007

Reading Is Living

Reading as living
Developers read the software code for living. They write too, of course, but writing of one line is accompanied by reading of 5-10 lines. You literally live in the world of code, and you make the code alive. Reading is living. If nobody reads the code, that code probably is dead.

Room
In order to live comfortably, you need enough space. That means, you need optimal density of information: not too dense, not too sparse. The code with too long lines without visual separators, and the code full of comments and blank spaces, both look equally unattractive.

Navigation
When you move across the virtual space, you need:
• consistent separation of the module to logical parts, such as levels, subsystems, and functional points;
• reasonable size of correspondent parts;
• "landmarks", which simplify the orientation in the code;
• clear visual separation between main stream of control, secondary roads, and dead-ends.

Communication
The reading of the source code may be more informative than the talk with your colleague - if you wrote the code with future maintenance in mind. The main goal of the code is to convey ideas.

The code must speak to the reader.

Most source codes I saw during my life looked unnaturally. The reason is not the artificial nature of programming languages. Programs look so strange because authors do not express their ideas in a human manner. They try to speak like silicon-based aliens. The people-oriented coding practices seem to be better alternative.

Work
Your work on the code magically turns into work of the code inside the computer. You are interested in detection whether given piece of the code does really work, or just pretends to be useful.

Rest?
Is the source code good place for the rest, entertainment, funny jokes? I don't think it is. The personal life is private. The code (especially commercial one) is stored for decades in the source control repository, and is open to any reviewer. Keep your private life and your more or less public code separately.

February 6, 2007

Reading Is Learning

Reading as learning
Developers and maintainers read the API documentation and the source code in order to learn something new, or well-forgotten old. Reading is learning. When you have enough knowledge to complete the task, you stop reading and start writing.

Elements
Learning starts with simplest, easiest, recognizable, and memorable things. When you are learning, begin from the basics, like object's lifetime control (opening/closing, construction/destruction). When you teach the reader of your code, begin with well-known, trivial operators of C++ language, variables of fundamental types, plain function calls, and non-template classes.

Glossary
In order to understand the problem area, the human being needs to know the language of that problem area - specific words that define essential things and actions. Definitions of problem-oriented terms should be used consistently. Well-chosen term describes only one entity. When you read or write the code, watch out aliases - different names for one object or process. Watch out homonyms - cases where one name used for different objects or processes. The worst case is a term with a variable meaning applied to group of unrelated objects - in fact, it does not have any meaning.

Examples
The easy way to clarify the definition is to give an example. The shorter the distance between the definition and example is, the better the understanding is. Good examples do not contain irrelevant details.

Sequence
The order, in which new definitions are appearing and using, does matter. The reader easily understands new terms, if they are based on already well-known terms. Forward definitions ("I'll explain it in details next week, just be patient") require extra motivation of the reader, so avoid using them.

Reminders
Good teacher helps the students to memorize the course and reminds them already explained stuff. Often you should repeat the part of the lesson, or else the auditory will forget everything.

February 2, 2007

Reading Is Searching

Reading as searching
People read in the search of information. Reading is searching. When you found the relevant information, you stop reading.

Cache
The simplest thing to do with search is do not throw found information away. Keep useful results in the cache:
• in the dedicated document under source control,
• on the team wiki page,
• in the web log on corporate web server.

Shortcut
If the information is already stored in safe place, provide the short annotated path to it:
• a bookmark in your web browser,
• a cross-reference from the project document,
• an universal resource locator (URL) in the source file.
Note that in Windows you can create your own information access protocols: in addition to http, ftp, and mailto you may create your own custom protocol that will start particular application with particular parameters. The example is in MSDN article titled "Registering an Application to a URL Protocol".

Keywords
When you want to find the information, it seems reasonable to use the specialized search system, such as:
• http://search.yahoo.com/,
• the Search tab in the Visual Studio,
• the Silly Dog in the Windows XP,
• its daughter from Clippit - Ms. Dewey http://www.msdewey.com/,
• or some less exotic engine.
In any case, you may use the set of keywords as the kind of URL. The more specific keywords you use, the less results the engine will find, and the more relevant the found results are.

Algorithms, or how to search
You may learn much more about Internet searching on the web site http://www.searchlores.org/ of Fravia+. Although the site now lives not so intensively as in previous millennium, and sometimes turns from the inspiring poesy of searching to bore prose of software cracking, it is still a good resource.

Reference lists
As number of cached records, shortcuts, and keywords grows with time, people tend to combine references into arranged lists: favorite links, alphabetic index, information directory, list of deliverables, and so on. Don't miss the good opportunity.