Good Practices

Many details make up a “good” program, with the characteristics that we discuss in the course. The following is just a start towards listing them. The items below are considered in the grading of a class project.

Creating a list of these is not easy, as there are always exceptions. However, these exceptions are not common. Think about where this would apply, not what the exception would be.

Indentation and Spacing

Overall Program

Every source-code file needs a header comment, which includes the filename, a description of the contents, and possibly copyright and license. Leave developer names and dates out of header comments. Exception: copyright years

Leave a single blank line after the header comment

Every include file (.hpp) needs an include guard

Every implementation file (.cpp) must include the associated .hpp file first before any other include files

Never include a .cpp file. If you find you have to include a .cpp file to get your program to compile/build, it is not the answer. There are rare circumstances where this is needed, but there are not any of those situations in this course.

Naming

Proper and consistent naming is critical to good programs, and maybe the most important design decision that you make

Use terms from the problem domain as much as possible

Naming should improve as you gain more experience with the problem domain and the code

Use the following naming convention:

Category

Semantics

Convention

Classes

things not actions

PascalCase

Fields/Variables/Parameters

things

camelCase

C++ Methods/Functions

actions

camelCase

C Functions

actions

under_score

Constant scalar values, e.g., int, double

things

UPPERCASE

C-Preprocessor/cpp variables, e.g., INCLUDE_XML_HPP

things

UPPERCASE

Exception: Adding or integrating into an existing library, use the naming convention of the library. E.g., if adding a new Container type that fits into std::vector, etc, follow that naming convention.

Some domain terms are all uppercase, e.g., XML. So a variable relating to that domain can use that form, e.g., XMLBuffer

Single-letter variable names are not to be used, except in certain, well-understood situations:

Use double as the standard C++ floating-point number type. There is hardly any reason to use float

Separate statements into sections (hunks) with a (single) blank line between the sections

Comment each section of code on the line before

Make each section cohesive. All statements in a section should be related to each other

Use const whenever possible

Use auto when initializing a declaration that includes a call, e.g., auto pc = buffer.begin()

Use an explicit type instead of auto when initializing with a literal value to make certain you get the intended type

Favor range-based for instead of direct indexing (or iteration). Exception: If comparing or changing multiple parts of the container in the array

prefer:

over

Prefer pre-increment over post-increment, i.e., prefer ++i; over i++;. Pre-increment is easier to understand, explain, and implement, e.g., post-increment is often implemented using pre-increment, and especially when these operators are overloaded:

This also applies to pre-decrement and post-decrement

Functions

All functions are to be declared in an include file (.hpp) and defined in an implementation file (.cpp), unless specifically told otherwise

All functions require a comment before both the declaration and the definition.

There is much more, so feel free to experiment. However, be consistent.

Do not use the inline specifier. First, the compiler can ignore it. Second, the compiler automatically inlines small functions. Third, it is overused. The inline specifier has valid purposes, but not only in specific cases and not in this course. Note: Do not confuse the general programming term “inline a function” or “inline a method” with the inline specifier.

std::string

The std::string default constructor initializes to an empty string. So instead of:

just declare the string:

A std::string is a (specialized) container. Use container methods over comparison. So instead of:

just check that it is empty, just like other containers:

One exception might be if you are in a set of if statements (such as a nested if) and are comparing the string to other literal strings.

Also, if you need to change to value to an empty string, instead of:

just clear out the data, just like other containers:

Parameter Types

Direction

Primitive

Object

in

T

const T&

out, inout

T&

T&

Examples:

As always, some specific cases:

Note that const of a primitive value, e.g., const int, is not in the list. The reason is that the const is an unnecessary restriction for the function/method definition, and does not change the arguments allowed.

If you are passing an object and make a copy internally, then pass by value (T)

Will discuss rvalue-references, e.g., std::string&&, separately

Return Types

Type

Application

T

Return copy of local variable or field

const T&

Return read-only reference to field

Examples:

In a few cases you may want to return a reference to a field so it can be changed externally to the class. In this case, use T&. Be very careful on this. Safer to just send a copy, and may be just as efficient.

Will discuss rvalue-references, e.g., std::string&&, separately

Classes

Initialize primitive types directly in the field/data member declaration of the class (C++11 feature) if they have a literal initial value:

Initialize as much of the rest that you can in the member initialization list. This is required for const fields, both values and references.

There is no need to write a Get Accessor or Set Mutator for fields that are only used internally in the class, i.e., they should be private. You are free to use fields in any method. Calling get()/set() pairs on internal fields for internal use adds complication without any benefit.
Now, if there is a calculation involved (e.g., it is a Property Accessor), then, of course, use a good internal method.

Comments

The following applies to all comments, except file header comments.

Terms to leave out comments:

Phrases to leave out when referring to program structure:

General phrases to leave out of your comments:

Debugging Output

Write debugging/tracing messages to std::cerr, not std::cout. First, the program already uses std::cout for output. Second, std::cerr is not buffered, while std::cout is. Why does this matter? If your program crashes, any unbuffered output may be lost. So you may not see your debugging messages.