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.

January 31, 2007

Why and How of Reading

Starting the series of articles
In order to turn the source code reading into profit generator, we need to know why different persons - developer, reviewer, and maintainer - read the code at all. We'll analyze typical reading scenarios and elaborate a set of reading-friendly software development techniques. Those techniques will allow you to accomplish reading tasks faster and cheaper than your competitors.

Why the developer reads the source code
The short answer is because the developer makes sure that just written code does what it is supposed to do.

Implementing the function
When you are in the developer position and write the body of the function X (assuming the design work is already done), your tasks are:
1. Find the sequence of elementary steps you need to perform in order to complete the task. For example, in order to create memory-mapped file in Windows, you need first open the file, then create the memory mapping section linked to that file, and then create the range of addresses ("view") over the memory mapping section. The time here is spent when you:
• Read background topics in the Operating System API documentation.
• Search for ready samples in API documentation.
• Search for algorithms in books and in the Internet.
2. Write the inline code, the call to API function, the call to third-party library function, or the call to your own function implementing each step. You spend the time on following topics:
• Search for the function doing the required task in the Operating System API documentation.
• If previous search failed, try to find a function doing the required task in your project or previous projects.
• If previous search failed, try to find a function doing the required task in third-party library (including libraries already bought or to-be-bought).
• If there is no such function, or if the library is too expensive for your task, you need to write the function yourself. Generally, it means dividing the task to subtasks and repeat previous steps for subtasks.

Calling the function
When you write the call to the function Z, your tasks are:
1. Gain basic knowledge about the function: type and purpose of return value and parameters, prerequisites, special conditions, errors. Your occupation is:
• Read the official documentation of Z provided by authors or vendors.
• If the official documentation is missing, getting the description from the Internet.
• If there is no shared description (which by itself is very bad sign), read source code. At least the header file to this function should exist, or else you should not use that function.
2. Transfer the function description from a genuine or surrogate documentation to the call site in the source code. That includes:
• Copying the function signature and adapting it to your coding conventions.
• Obtaining values to be used as function arguments, and passing them to function parameters.
• Analyzing results and processing errors.
3. Integrate supporting libraries into your build system:
• Copy libraries and correspondent C++ headers into your source code tree.
• Include header files into correspondent C++ files.
• Include paths to libraries and header files in your build configuration.
• Add library object files to your linker configuration.
4. Make sure the function does compile:
• Verify that all formal parameters have matching actual arguments.
• Verify that types of correspond to types of formal parameters.

Debugging
When you go through your code step by step in the debugger, your tasks are:
1. Determine what particular function does:
• Analyzing the function name.
• Analyzing the names and types of passed arguments and return value.
• Analyzing how parameters are passing.
• Reading comments, if available.
2. Determine how the function execution affects the system:
• Determine what data should change.
• Determine how the data should change.
• Examine which data are in fact changed.
• Examine how data are in fact changed.

Why the reviewer reads the source code
The short answer is because the reviewer prevents the code with undesirable effects to be stored in the master source repository.

Reviewing
When you are in the reviewer position and look at new code fragment, your tasks are:
1. Understand what the code fragment does. Time passes when you:
• Determine which parts of fragment are known/unknown.
• Determine the origin of unknown parts: written by developer, parts of Operating System API, and parts of third party API.
• If unknown parts are written by developer, search and read the implementation.
• If unknown parts come from some API, read the documentation. That is similar to how the developer gains base knowledge about the function.
2. Determine whether the actual behavior of the fragment is the desired behavior:
• Look for visual symptoms of frequently occurring bugs - resource leaks, buffer overflow, possibility of bad pointers to pass in, disabled compiler warnings, etc.
• Look at compiler warnings for symptoms of frequently occurring bugs: variables used without being initialized, assignment in conditions, type cast with truncation, etc.
• Compare actual behavior of the fragment with the functionality required by project documentation.
• Compare the fragment implementation with known similar fragments in other parts of the source code.
• Ask the developer to explain parts that are difficult to understand.
3. Ensure that new fragment follow the project's coding style. The time is mostly spent by visual searching for formal mismatches in the new text.

Why the maintainer reads the source code
The goal of maintainer is to make desired change in the existing functionality.

Maintaining
When you are in the maintainer position and look at the project's source code, your tasks are:
1. Understand the purpose of the project, module, or file. Time eaters are:
• Read the project documentation: requirements and design documents.
• Read the test documentation: acceptance checklists and step-by-step test cases.
• Read the reference information accompanying the shipping product: manuals, instructions, help system.
• Read the source code. That is similar to understanding of code fragment by reviewer.
• Search the description of intended behavior in transcripts of developer's communication with a customer.
• Read the source code of test client applications.
2. Determine what parts of the module to be changed are risky:
• Look in the version control system for files that change often or experience big changes.
• Reading bug reports.
• Reading product release notes and frequently occurring problems reported by help desk.
• Talk with testers, developers, and managers.
3. Find the place in the code where the change will be made:
• Read the code from the "just do it" perspective.
• Read the code from "minimal change" perspective.
• If the part to be changed is risky, read the code from the "minimal harm" perspective.
4. Apply the change. It is similar to developer's tasks.
5. Document your change. The time consuming operations are:
• Update the project documentation.
• Update the reference information accompanying the shipping product.

Try it at home
Could you analyze these tasks yourself? Why the reading requires so much time? How to make the source code, and optionally other documentation, readable? Invent your own solutions of that problem. Which relationship between tasks will help you? What can be automated? This is your homework today.

January 29, 2007

Readability: the Key to Big Income

Power of global code base
During the history of computers, about of 350 million different computer programs were written around the world. Each year brings to life about of 10 million new programs, including new versions of existing ones.
Most of those programs were compiled from source codes written on different programming languages. And those source codes still exist today. There are about of 200 trillions of source code files stored in the Internet.

Can you get advantage of reuse?
There are hundreds of ready solutions for literally any information technology task available in the Internet in the form of source code. It seems very attractive and profitable to use the knowledge collected by others. Just steal the appropriate source code file, add it to your program, and enjoy the success. Your information processing task solved, the program sells. Easy money!

Oops, not so easy.

Write-only code
A random pick of 60 code files doing particular task - getting the function name by its address - shows that source files are in general:
• unreadable due to ugly coding style,
• hard to understand due to obscure logic and lack of explanation,
• difficult to reuse due to numerous explicit and implicit dependencies between code parts.
Code maintainers call this kind of source code "write-only".

Unreadable code breaks the business
The reuse of the source code stolen from Internet is just an example of the problem related to write-only code. Another problem, more important to you, regards the code written by yourself. If the source code of your own program is write-only, you cannot maintain the program - in other words, your business cannot grow. Did you heard about the operating system OS/2? Its development stopped because nobody at IBM could understand its sources.

When the source code is read
In order to write readable source code, you should understand what "readable" means.
Any source code line of any computer program is read at least four times:
• when the developer creates new fragment of the source code containing this line,
• when the developer walks the code fragment line-by-line in the debugger after writing and compiling the fragment,
• when the lead engineer or code buddy reviews it in the difference utility before storing into source version control system,
• and when the maintainer searches for the code to be changed due to extension or bug fix.
The maintainer in turn writes new fragments, and the history repeats.
Each of those activities requires the person in charge to spend some time. Big time is big money. Preventing the time loss is equal to gaining money. Soon we'll see where and how you may earn a few bucks for bread and butter.