Eight Tips for Creating Self-Documenting, Maintainable Code

January 15, 2010

The cost of maintaining software is high. According to Robert Glass, maintenance is the dominant phase of the software life cycle, consuming 60% of the total software costs on average. Equally important, only 17% of maintenance is actually spent on fixing errors; a significant amount of time is spent enhancing the software. (Deciding if something is a defect or an enhancement isn’t always as straightforward as it should be, as I wrote about in my post Is it a Defect or an Enhancement?)

This makes it vitally important that the code is designed and written in such a way that it minimizes the effort to maintain it or enhance it. In my post What is Beautiful Code? I noted that “Beautiful code should be completely readable (by humans) to the extent that the code is literally self-documenting and something that can be easily maintained over time by others.”

What does completely readable, self-documenting, maintainable code look like? And does self-documenting code remove the need for any documentation?

Tip #1
A method or function should have a specific purpose and not try to do more than one job.

To improve readability and maintainability, move blocks of code to separate functions or methods:

CountLines(string startDirectory)
    totalLines = 0
    For each directory in startDirectory
        For each file in directory
            lineCount = 0
            Open file
            While file.read
            Close file
            totalLines += lineCount
    Return totalLines

This is a simplistic example, and the code snippet appears to be small enough to understand, but a simple modification to make the code readable and self-documenting would be:

CountTotalSourceLines(string startDirectory)
    totalLines = 0
    For each directory in startDirectory
            For each file in directory
            totalLines += CountSourceLines(file)
    Return totalLines

CountSourceLines(string fullPathAndFileName)
    lineCount = 0
    Open fullPathAndFileName
    While fullPathAndFileName.read
    Close fullPathAndFileName
    Return lineCount

There is now a specific method for opening a file and counting lines in the file, separate from method that drives the totaling, based on where it starts and what files it finds.

Tip #2
Don’t be ambiguous in your programming.

I’m as guilty of this as the next person, but developers constantly complain about vague, high-level requirements, yet many (not all) tolerate vagueness in their own code all too often.

Use clear, descriptive names for variables, constants, enums, and methods or functions. For example, in Tip #1, I have a method called CountSourceLines(), not something vague like Count(). I also made sure that the parameter for the CountSourceLines() method is descriptive; I expect a fully-qualified path and filename to be passed. If I had used a parameter called file, would the caller have known for sure what to pass? Not without looking at the code and possibly experimenting.

Method or function names should describe what the routine does, and the names should be precise and concise. Focus on the primary purpose; the objective is not to “self-document” everything into a name. A name like MethodToReadFilesAndCountSourceCodeLinesAndThrowExceptionOnError() is NOT good naming! If you feel like you are writing a sentence instead of a name, back up and start again.

The same applies to variables, constants, and enums. Name them descriptively and concisely, and by all means don't allow use of magic numbers and other discrete values!

If you have something that looks like this:

for i = 1 to 7

What is i, and what significance does the number 7 have? Sure, you could comment this, but why bother if you can write something like this instead:

for dayInWeek = 1 to NUM_DAYS_IN_WEEK

Tip #3
Declare variables as close as possible to where it is first used.

This works great IF your methods or functions aren’t so big that it is difficult to see the variable declarations. Some people prefer to declare all variables at the beginning of a method or function so that they are in one place, but I personally like to declare variables as close as possible to where I’m using them so that the reader understands why the variable is needed.

Tip #4
Write for clarity and maintainability first, optimize later.

Trying to write optimized code at the outset can create difficult to understand, difficult to maintain code. Write your code first and then profile it to find out where your bottlenecks are. You can determine what to change at that point. Remember the old adage: “An application spends 80% of its execution time in 20% of the code.”

And don’t get stupid! Follow sound design and development practices, learn about and make use of design patterns. Poor design will not only make maintenance difficult, it will also make optimization a challenge.

Here’s a link to a post about “optimizing later,” that is really poor database design, and something that never should have happened:

We'll Optimize Later.

If the reader needs a greater context to understand portions of your code, consider adding specificity and clarity in how the code is structured and written.

Tip #5
Comments should explain what you intended the code to do, and why.

If you’ve followed these tips, chances are that you can avoid spending time explaining what your code does. Self-documenting code with comments that explain why the code is doing what it does provides a fairly complete representation of the business and decisions that went into producing the behavior that exists.

Don’t comment the obvious, but if there is a difficult section of code, by all means, explain it! While you might believe that it is a great exercise to examine a piece of code to understand its “beauty” and function, if you are the person who is trying to fix it in a short time frame without creating any side-effects, an explanation goes a long way!

Tip #6
Follow your company’s style guidelines.

Yes, this might mean that you need to adapt and put the curly brace on a new line instead of at the end of the line, but consistency is one more step towards decreasing the time and effort required by your organization to enhance and adapt the software over time.

Tip #7
Good software documentation provides background information and descriptions of key decisions.

We’ve all seen the monolithic spec that is outdated before the first release, let alone what happens later. No one maintains the comprehensive documentation as much as they maintain the code. (How many of you are REALLY maintaining your documentation?) If there is a question about functionality, the definitive answer comes from someone referring to the code, not the documentation.

A major problem is that while a developer can tell you what the code does, many times he or she cannot tell you why it is doing what it does. If your code is self-documenting with comments that describe the intent of specific methods, functions, or sections of code, then all that is left is documentation that describes the business objectives and higher-level decisions, with no need for a monolithic spec.

Tip #8
Tools can be used to confirm that your code is maintainable.

There are tools available to facilitate code reviews along with checking for vulnerabilities or performing something like a cyclomatic code complexity check, and to provide measurements on things like cohesion and coupling.