About programming style

C++ allows you write very cryptic code, but such code should be avoided. It is better to write clear code that is easy to read and understand. This also makes program maintenance easier.

Above text mentioned some qualities for programs (clarity, readability, understandability, maintainability). Other important things are functionality and efficiency. Such qualities are difficult to define, and there is not a single way to follow a good programming style.

Style guidance followed on this course is described below, and the items are those that also the assistants pay attention to, when they evaluate projects. However, the purpose is to give as general rules as possible to make them useful more widely than just on this course.

Functionality

Functionality of a program can be seen as the most important quality. But if the program works correctly, does it matter at all how the code looks like or how easy it is to understand? However, when the structure of the program is logical and as simple as possible, the program more probably works correctly or you can more easily make it work correctly.

The list below gives some requirements for functionality:

  • The program must work correctly with all valid input values.
  • The program must inform the user about erroneous or invalid input.
  • The program must inform the user about a wrong way to use the program.
  • The program must check the input values to prevent crashing. Instead of crashing it must quit in a controlled manner.
  • The program must not ask such values that it can calculate itself.
  • Using the program must be as easy and clear as possible.

Structuring a program (functions)

Structuring a program means a way to divide the program into parts. Such parts should be small and simple enough and easy to manage.

Structuring concerns any program part, e.g. functions and classes.

Especially functions must not be too long nor too wide (the lines must not be too long). A function should fit on the screen in total.

On the other hand, a function should not be too short, either. Most often a function consisting only of a single line, or a function just calling another function, is too short. An exception is a getter method of a class returning the value of an attribute. Moreover, a 1-line function can be accepted if it improves readability, for example, the function below gives a title for a complicated condition:

bool is_snake_dead()
{
    return new_head.x < 0 or new_head.x >= width_ or
           new_head.y < 0 or new_head.y >= height_);
}

Repetitive code should be avoided. This means writing the same or almost the same code several times. You can avoid this e.g. by functions or by giving additional parameters to functions.

Naming conventions

C++ is a case-sensitive programming language, which means that upper-case and lower-case letters are treated as distinct. Although it would be possible to use identifiers name and Name in the same scope, such a convention does not follow good programming style. More generally, it is better to avoid using names that are too similar, since you can confuse them easily.

The names of variables, constants, functions, types, classes and other identifiers must be descriptive. It does not matter if the names are long.

Typically, the names of variables and functions begin with a lower-case letter, and those of classes and structs with an upper-case letter. Constants are written with upper-case letters in total and different words are separated with an underline.

There are mainly two ways to name a function: printField and print_field. The former is better, but anyway you must follow the chosen way consistently in a program. The names of functions should be commands such as print, instead of nouns such as printing.

Functions returning a truth value (bool) are recommended to start with the string is. In such case, you can conclude from the name, in which situations the function will return true and in which situations false. Therefore isReady is a better name for a truth-value function than checkStatus. However, it is possible that a function returns a truth value to inform whether the implemented operation succeeded or not. In such case, it is not necessary to follow the above naming rule. For example, the main purpose of function setValue is to set a value, but in addition it can return a truth value: true indicating a succeeded operation and false a failed one.

The names of classes shall mainly be in singular form. Almost always there is no reason for using plural, since a class typically describes properties of a single object. However, a class can have (and typically has) several instances.

It is a common practise that the last character of a member variable is the underline. This makes it easier to distinguish them from other variables.

It is better to use named constants instead of magic numbers. It is easier to read program code, if there appears e.g. the constant NUMBER_OF_STUDENTS instead of the number 600. Named constants are also useful, if you want to change its value: It is enough to edit a single point in the code. Like other identifiers, also constants must have names that describe their use, so it is not reasonable to define: const int HUNDRED = 100.

Comments

Comments can improve the understandability of the program. On the other hand, understandability can also be improved by giving descriptive names for variables and other identifiers. The following example can be found from Steve McConnell’s book Code Complete (p. 473):

// if allocation flag is zero
if( AllocFlag == 0 ) ...

The comment above is totally useless, because you can get the same information from the code. The comment can be improved as:

// if allocating new member
if( AllocFlag == 0 ) ...

A better way is to use a named constant instead of zero:

// if allocating new member
if( AllocFlag == NEW_MEMBER ) ...

By the way, the comment is again useless (which is now a good thing, and the comment can be removed).

When commenting code you should stop to think if the comment is useful. However, at this phase when you are only learning programming, it is better to write too much comments than too less of them.

Functions must be commented. Especially the comments concerning a member function are written in the header file (.hh) before the function. The comment tells what the function does, what are the meaning and the valid values of its parameters and return value.

Each file starts with a comment describing the purpose of the whole file. Usually it is good to write a comment before each control structure to clarify its meaning.

Code layout

Although C++ does not require indentations, use them to improve the readability of the code.

The body of control structures must be enclosed with curly brackets, i.e. { }, even if the body consists of a single statement. Curly brackets can be written in the lines of their own, or the starting curly bracket, i.e. {, can be written at the end of the preceding line. You can use either of the above ways, but use the chosen way consistently in the whole program.

Object-oriented programming

In object-oriented programming, the first thing to consider is the rationality and usefulness of your class. An essential mission of a class is to bring data (attributes) and the code handling the data (methods) together.

If a class has no methods (excluding constructor and destructor), the class is not reasonable, since you could simply use a struct. Also if a class has, besides constructor and destructor, only set and get methods, you could again use a struct, whereupon these methods would become needless. On the other hand, if a class has no attributes, most probably its methods do not belong together (since they have no common data to handle), and thus, they should not be written in a class.

Style issues in the current and following sections have been taken from the Finnish book ”Olioiden ohjelmointi C++:lla” written by Matti Rintala and Jyke Jokinen (pp. 341-353). Most of the points have been mentioned in other parts of the material with larger explanation. Therefore they are listed here shortly.

  • Each header file (.hh) contains a single public interface (class). Header files contain only definitions (no implementations).

  • Header files must be prevented from including more than once:

    #ifndef CLASS_A_HH
    #define CLASS_A_HH
    ...
    #endif // CLASS_A_HH
    
  • include directives can be used to include only header files. If you include several files, you must list your own files first and after that the library files.

  • The implementation of an interface is written in the implementation file (.cpp) in the same order as they are in the corresponding header file (.hh).

  • Classes have been defined with a good public interface (as small as possible), and all the operations are conducted via this interface.

    • Especially, an object must not change the values of another object’s attributes, not even if the objects are instances of the same class.
  • Write the public part of a class before the private one.

  • The public part of a class contains no member variables.

  • The private part of a class contains no useless variables.

  • No operations of a class are implemented outside the member functions.

  • The member variables of a class must be initialized in the initialization list of the constructor in the same order as they are defined in the header file.

  • When possible, define a member function as a const function.

Other style issues

Finally, some general style rules are listed shortly below.

  • The scope of a variable must be as small as possible (e.g block, function, class).

  • Each variable must be defined separately, and thus, avoid writing definitions as int a, b;.

  • Variables must be initialized.

  • The characters for pointers and references (* and &) are written immediately after the type without an empty space. For example

    Date* ptr = nullptr;
    void printDate( const Date& dateObject );
    
  • The return value type of main is always int.

  • The names of the parameters of a function must be given in both the declaration and definition of a class, and they must be identical in both places.

  • If you want to pass an object as a parameter for a function, use a reference to the object instead the object itself, whenever possible.

  • The default value parameters of a function must be given in the declaration of the function, and you must not give more of them in the definition of the function.

  • A function must not return a reference or a pointer to its local data.

  • Records (struct) must not have member functions, but constructors, an (empty) destructor, and operator functions are allowed.

  • Do not index vectors with brackets, but use at operation instead. (The same holds also for other containers that have these operations defined.)

  • The elements of a vector must, of course, share the same type, but in addition their meaning should be the same. For example, if you need store a person’s forename, surname, and address (all as strings), it is not a good idea to use a vector, but a struct.

  • Avoid unnecessary complexity. For example, it is useless to evaluate a boolean expression, if you can return its value directly. As a concrete example, the function

    bool is_negative(int number)
    {
        if(number < 0) {
            return true;
        } else {
            return false;
        }
    }
    

    can simply be written as:

    bool is_negative(int number)
    {
        return number < 0;
    }
    

    (which makes one in this case wonder, if the function is needed at all.)

  • Unless you are absolutely sure that you can use C++ exception mechanism, then do not try to use it. Exception handling in C++ will not be considered on this course. Especially, C++ exception mechanism must not be used in the place of control structures (as you may have done in Python programs).

C++ has other style guidelines e.g. those related to dynamic memory management and inheritance of classes. We will return to them in the context of these topics.

Wish

We have tried to follow the above guidelines in the template codes and examples of the course. However, we have not always succeeded in doing so, since there have been many authors writing the codes and most often they have worked in a hurry. We hope that you inform us if you notice any violations of the guidelines.