PM123 Programming Style Guidelines

1. File Contents

Use files to group functionality. Each file should contain only one cohesive set of functions. Avoid duplicating functionality in separate files. If different files contain similar functions, consider generalizing the function sufficiently and putting it into its own file so that both function groups can use the one source. For C++ code, put only one class or closely related set of classes in each file.

Avoid strong coupling between functions and classes implemented in separate files. If two objects are so strongly coupled that one can only be used in conjunction with the other then they belong in the same file.

Use header files (.h suffix) to declare public interfaces, use code files (.c or .cpp suffix) to define implementations. Typically each cohesive set of functions you write in a single file will have one accompanying header/interface file pair. Code that uses your implementation will #include the header file.

Be precise with #include statements. Explicitly include the .h files you require, and only where you require them. If, for example, your code calls a function defined externally, include that function's associated .h in your implementation file not in your code’s associated .h file. You should only need to include other files in your .h file if your public function interface or data type definitions require the definitions contained therein.

Avoid using header files to contain a set of #include directives simply for convenience. This "nesting" of #include constructs obscures file dependencies from the reader. It also creates a coupling between modules including the top-level header file. Unless the modules are cohesively coupled functionally, and each requires all the .h files included in the convenience header, it is preferable to instead include all the individual .h files everywhere they are required.

1.1. Header (Interface) File Content

Header files should contain the following items in the given order.

1. Copyright statement comment
2. Module abstract comment
3. Multiple inclusion #ifdef (a.k.a. "include guard")
4. Other preprocessor directives, #include and #define
5. C/C++ #ifdef
6. Data type definitions (classes and structures)
7. typedefs
8. Function declarations
9. C/C++ #endif
10. Multiple inclusion #endif

Example 1. Standard (C) header file layout

    /*
     * Copyright 2004 Dmitry A.Steklenev
     *
     * Redistribution and use in source and binary forms, with or without
     * modification, are permitted provided that the following conditions are met:
     *
     *    1. Redistributions of source code must retain the above copyright notice,
     *       this list of conditions and the following disclaimer.
     *
     *    2. Redistributions in binary form must reproduce the above copyright
     *       notice, this list of conditions and the following disclaimer in the
     *       documentation and/or other materials provided with the distribution.
     *
     *    3. The name of the author may not be used to endorse or promote products
     *       derived from this software without specific prior written permission.
     *
     * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
     * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
     * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
     * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
     * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
     * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
     * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
     * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
     * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
     * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
     */

    #ifndef REL2ABS_H
    #define REL2ABS_H

    #include

    #ifdef __cplusplus
    extern "C" {
    #endif

    /*
     * rel2abs: convert an relative path name into absolute.
     *
     *    path    relative path
     *    base    base directory (must be absolute path)
     *    result  result buffer
     *    size    size of result buffer
     *
     *    return  != NULL: absolute path
     *            == NULL: error
     */

    char* rel2abs( const char* base, const char* path, char* result, size_t size );

    #ifdef __cplusplus
    }
    #endif
    #endif /* REL2ABS_H */

1.2. Code Files

C and C++ code files follow a similar structure to the header files. These files should contain the following information in the given order.

1. Copyright statement comment
2. Module abstract comment
3. Preprocessor directives, #include and #define
4. Revision-string variable
5. Other module-specific variable definitions
6. Local function interface prototypes
7. Class/function definitions

2. File Format

Format your code so that the spatial structure illustrates the logical structure. Use blank lines to help separate different ideas, use indentation to show logical relationships, and use spaces to separate functionality. Each block of code should do exactly one thing.

Start all function definitions and declarations in column zero. Put the return value type, the function interface signature (name and argument list), and the function body open bracket each on a separate line.

Example 2. Formatting function definitions

    void
    debug( const string& message )
    {
    .
    .
    .
    }

Use a single space to separate all operators from their operands. The exceptions to this rule are the "->" and "." operators. Leave no space between these operators and their operands. When breaking operations across lines, put the operator at the end of the broken line rather than at the start of the continuation line.

Use two spaces for each level of indentation. Avoid making lines longer than 80 characters. When breaking lines, use the natural logical breaks to determine where the newline goes. Indent the continuation line to illustrate its logical relationship to the rest of the code in the line. For functions, for example, this means aligning arguments with the opening parenthesis of the argument list.

Example 3. Breaking statements across multiple lines

    new_shape = affine_transform( coords, translation,
                                  rotation );

    if( ( ( new_shape.x > left_border   ) &&
          ( new_shape.x < right_border  ) ) &&
        ( ( new_shape.y > bottom_border ) &&
          ( new_shape.y < top_border    ) ) )
    {
      draw( new_shape );
    }

Use a pure-block, fully bracketed style for blocks of code. This means put brackets around all conditional code blocks, even one-line blocks, and put the opening bracket at the end of the line with the opening statement. The exception to this rule is for conditions that are broken across multiple lines. In this case put the open bracket on a line by itself aligned with the start of the opening statement (as shown above).

Example 4. Fully bracketed, pure block style

    if( value < max ) {
      if( value != 0 ) {
        func( value );
      }
    } else {
      error( "The value is too big." );
    }

Although the brackets may seem tedious for one-line blocks, they greatly reduce the probability of errors being introduced when the block is expanded later in the code’s life.

3. Choosing Meaningful Names

3.1. Variable Names

Use lower case for all variable names. For multi-word names, use an underscore as the separator.

Choose variable names carefully. While studies show that the choice of variable names has a strong influence on the time required to debug code, there are unfortunately no clear and fixed rules for how to choose good names. In the mean time, here are some general guidelines to follow:

- Be consistent! The most important thing is to establish a clear, easily recognizable pattern to your code so that others will be able to understand your implementation and intent as quickly and reliably as possible.
- Use similar names for similar data types, dissimilar names for dissimilar types.
- Avoid names that are homophones: e.g., foo, fu, phoo, etc. Also, don't rely on capitalization to distinguish between variables.
- Use names that say what the variable represents rather than how it is used (i.e. use nouns for variable names); use terminology from the application domain and avoid computer jargon that reflects programming details.
- Avoid generic names such as tmp, buf, reg.
- Avoid intentionally misspelled words such as lo or lite.

3.2. Function Names

Use lower-case letters for public function names. Use an underscore as a word separator.

For functions that return no values (i.e. return type void), use strong verbs that indicate the function's purpose. Typically you will want to include the object of the verb in the name. For example,

    void remove_dc_offset( short* signal, const unsigned long length );
    void set_output_gain( const float gain );

Because functions tend to serve a more complex purpose than variables, longer names are more acceptable.

If a function returns a value it is sometimes better to use a name that indicates the meaning of the value returned. For instance,

    /*
     * Compute the DC offset of the given signal.
     */

    float
    dc_offset( const short* const signal,
               const unsigned long length );
    /*
     * Poll the D/A and return the current gain setting.
     */

    float
    gain( void );

In general, be consistent and be informative. Choose names that make your code easy to read and understand.