Symptoms

 < Day Day Up > 



The most obvious sign of Docuphobia is the lack of documentation. However, even if documentation exists, it does not mean it is good documentation. There are a few main ways to decide whether you feel the documentation is sufficient.

What, How, and Why?

Documentation should answer three main questions for the end user of the code. This user might be another programmer, or it might be you after you have forgotten what you did. No matter who the end user is, the documentation must be clear enough to make the code usable and to avoid any potentially common errors.

The first question that will interest the user is: what does this code do? The user should know what inputs the code accepts and what output can be expected. The user also needs to know what side effects, or global changes, can be expected and what errors might occur. All this information is necessary if the user is to use the functionality properly and handle any potential problems. As a user, you can only spot missing documentation if you attempt to use the function in a way that is improper but not documented as such. You could also analyze the workings of the functionality, but this is unacceptably time consuming.

For instance, you encounter the following function declaration:

   /** Interpolate linearly between two numbers.     *   @param   i_start - first number     *   @param   i_end - second number     *   @param   i_percentage –  *            percentage to interpolate     *   @return   Interpolated number.     */    float linear_interpolate(float i_start,       float i_end, float i_percentage);

You might then try using it:

linear_interpolate(5.0f, 15.0f, 50.0f);

However, this causes the program to report a failed assertion. The cause for this is obvious once the function implementation is revealed:

   float linear_interpolate(float i_start,       float i_end, float i_percentage)    {       assert(!(i_percentage < 0.0f) && !(i_percentage > 1.0f));       return(((i_end - i_start) * i_percentage) + i_start);    }

This error could have been avoided with proper documentation:

   /**   Compute the value along the linear slope  *   between two numbers.     *   @param   i_start - number at 0%(0.0f)  *            on the slope     *   @param   i_end - number at 100%(1.0f)  *            on the slope     *   @param   i_percentage - percentage along  *            slope at which value occurs     *            as a decimal factor from  *            (0.0f,1.0f) inclusive.     *   @return   Interpolated value between  *            (i_start,i_end) inclusive.     */    float linear_interpolate(float i_start,       float i_end, float i_percentage);

The second question is: how does this code accomplish its goal? Documentation for this question does not need to be made public unless it affects the use of the functionality, but it still should be done. Documenting the internals eases maintenance and provides information for refactoring the code if part of it can be used elsewhere. If an error occurs and the documentation is lacking, either the original programmer must dredge up the memories of how it operates or some programmer must examine the code and puzzle out what it does.

This small snippet of Ruby code illustrates the use of comments for explaining how an algorithm works:

# Quadratic equation is # (-b +/- sqrt(b^2 - 4ac)) / 2a # of which the discriminate is the part inside # the square root. The discriminate is used to # determine the number of solutions # the equation has: 0, 1, or 2. discriminate = (b * b) - (4 * a * c) # Positive discriminate means 2 solutions. if discriminate > 0 then       solution = [(-b + Math.sqrt(discriminate)) / (2 * a),          (-b - Math.sqrt(discriminate)) / (2 * a)] # Negative discriminate means no solutions. elsif discriminate < 0 then       solution = nil # Zero discriminate means 1 solution. else       solution = -b / (2 * a) end 

This provides the necessary extra information to remind the programmer what the quadratic equation is and how the discriminate is used. Having this information readily available with the code prevents other programmers from having to put the equation back together and guess at the purpose of the discriminate, as they would have to if the code read like this instead:

discriminate = (b * b) - (4 * a * c) if discriminate > 0 then       solution = [(-b + Math.sqrt(discriminate)) / (2 * a),          (-b - Math.sqrt(discriminate)) / (2 * a)] elsif discriminate < 0 then       solution = nil else       solution = -b / (2 * a) end

The final question is: why does it do it that way? This is the most often overlooked task of documentation. Without the background behind why it was done a certain way, programmers can accidentally modify code in ways that break the functionality without knowing it. The other reason that this question is extremely important to document is that it cannot be extracted from the implementation itself. If necessary, both what and how can be determined by analyzing the code even without documentation. However, the code cannot reveal the motivations of the programmer or programmers behind its inception.

Take this single line of code:

   m_nextUniqueID += 2;

It is easy to see that this line adds two to the next unique identifier, but why use 2? The surrounding code offers little help:

   if(!i_instance) {       return(t_Handle(0, 0));    }    unsigned int l_freeIndex = m_freeIndex;    if(l_freeIndex >= m_handles.size()) {       m_handles.resize(l_freeIndex + 1);    }    if(unsigned int l_nextFreeIndex = m_handles[l_freeIndex].m_GetHandle().m_GetIndex( ) {          m_freeIndex = l_nextFreeIndex;    } else {       ++m_freeIndex;    }    m_handles[l_freeIndex] =       t_HandleInstance<t_Type>(l_freeIndex, m_nextUniqueID, i_instance);    m_nextUniqueID += 2;    return(m_handles[l_freeIndex].m_GetHandle());

However, if the original programmer had added the appropriate comment, the reasoning would have been obvious:

   // Increment the unique identifier by two, since // it is initialized to one it will never be zero // even if it wraps around.  While this does not // guarantee an absolutely unique identifier, the // chances are miniscule that both index and // identifier will be duplicated in any // reasonable amount of time.    m_nextUniqueID += 2;

Documentation is sufficient once it has answered these three questions. For the end user, this can only be discovered through trial and error. Therefore, it is important for the original programmer to make the best effort to properly document. Reviewing the documentation can help in this process, and should be done on a regular basis. Additionally, having other programmers review the documentation and ask questions can point out areas that lack complete information. If you are already doing code reviews, this should become a part of them. If you are not doing code reviews, you might want to consider starting them.

Not That Way

When functionality is used incorrectly, there are two main explanations. The first explanation is that the programmer who attempted to use the functionality did not read the accompanying documentation. If you are such a programmer, try making a new habit of reading the documentation. Otherwise, you can expect to encounter many problems for which you will only have yourself to blame.

The second and equally common explanation is that the documentation was not clear or sufficient to prevent the code from being used incorrectly. If you find someone using your code incorrectly, check your documentation. See if it can be extended or made clearer. Be sure to ask the programmer who used it incorrectly if the new documentation explains the situation better. If you are the end user and you discover a problem, take it to the original programmer and ask that it be updated. If the original programmer is no longer available, update it to the best of your knowledge.

For a good example of function misuse, we can look back on the example from the last section where we talked about how comments should explain what the function does:

   /** Interpolate linearly between two numbers.     *   @param   i_start - first number     *   @param   i_end - second number     *   @param   i_percentage –  *            percentage to interpolate     *   @return   Interpolated number.     */    float linear_interpolate(float i_start,       float i_end, float i_percentage);

If you wrote this function, and the accompanying comments, you might later come across this usage of your function:

linear_interpolate(5.0f, 15.0f, 50.0f);

You realize this will not work, because you assume the percentage is between 0.0f and 1.0f. Chances are that if you ask the programmer who wrote this why he chose the number 50.0f, he will tell you that he wanted 50 percent and assumed that the percentage value should be between 0.0f and 100.0f. The programmer should have instead used 0.5f for the percentage argument. Thus, the comments obviously need more detail.



 < Day Day Up > 



Preventative Programming Techniques. Avoid and Correct Common Mistakes
Preventative Programming Techniques: Avoid and Correct Common Mistakes (Charles River Media Programming)
ISBN: 1584502576
EAN: 2147483647
Year: 2002
Pages: 121
Authors: Brian Hawkins

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net