Coding with Style - Introduction to Professional C++ - Professional C++ (2014)

Professional C++ (2014)

Part IIntroduction to Professional C++

Chapter 3Coding with Style

WHAT’S IN THIS CHAPTER?

· The importance of documenting your code, and what kind of commenting styles you can use

· What decomposition means and how to use it

· What naming conventions are

· What formatting rules are

If you’re going to spend several hours each day in front of a keyboard writing code, you should take some pride in all that work. Writing code that gets the job done is only part of a programmer’s work. After all, anybody can learn the fundamentals of coding. It takes a true master to code with style.

This chapter explores the question of what makes good code. Along the way, you’ll see several approaches to C++ style. As you will discover, simply changing the style of code can make it appear very different. For example, C++ code written by Windows programmers often has its own style, using Windows conventions. It almost looks like a completely different language than C++ code written by Mac OS programmers. Exposure to several different styles will help you avoid that sinking feeling you get when opening up a C++ source file that barely resembles the C++ you thought you knew.

THE IMPORTANCE OF LOOKING GOOD

Writing code that is stylistically “good” takes time. You probably don’t need much time to whip together a quick-and-dirty program to parse an XML file. Writing the same program with functional decomposition, adequate comments, and a clean structure would take you more time. Is it really worth it?

Thinking Ahead

How confident would you be in your code if a new programmer had to work with it a year from now? A friend of mine, faced with a growing mess of web application code, encouraged his team to think about a hypothetical intern who would be starting in a year. How would this poor intern ever get up to speed on the code base when there was no documentation and scary multiple-page functions? When you’re writing code, imagine that somebody new will have to maintain it in the future. Will you even remember how it works? What if you’re not available to help? Well-written code avoids these problems because it is easy to read and understand.

Elements of Good Style

It is difficult to enumerate the characteristics of code that make it “stylistically good.” Over time, you’ll find styles that you like and notice useful techniques in code that others wrote. Perhaps more important, you’ll encounter horrible code that teaches you what to avoid. However, good code shares several universal tenets that are explored in this chapter.

· Documentation

· Decomposition

· Naming

· Use of the Language

· Formatting

DOCUMENTING YOUR CODE

In the programming context, documentation usually refers to comments contained in the source files. Comments are your opportunity to tell the world what was going through your head when you wrote the accompanying code. They are a place to say anything that isn’t obvious from looking at the code itself.

Reasons to Write Comments

It may seem obvious that writing comments is a good idea, but have you ever stopped to think about why you need to comment your code? Sometimes programmers recognize the importance of commenting without fully understanding why comments are important. There are several reasons, all of which are explored in this chapter.

Commenting to Explain Usage

One reason to use comments is to explain how clients should interact with the code. Each publicly accessible function or method in a header file should have a comment explaining what it does. Some organizations prefer to formalize these comments by explicitly listing the purpose of each method, what its arguments are, what values it returns, and possible exceptions it can throw.

Providing a comment with public methods accomplishes two things. First, you are given the opportunity to state, in English, anything that you can’t state in code. For example, there’s really no way in C++ code to indicate that the saveRecord() method of a database object can only be called after the openDatabase() method is called. A comment, however, can be the perfect place to note this restriction, as follows.

/*

* saveRecord()

*

* Saves the given record to the database.

*

* This method will throw a "DatabaseNotOpenedException"

* if the openDatabase() method was not called first.

*/

The second effect of a comment on a public method can be to state usage information. The C++ language forces you to specify the return type of a method, but it does not provide a way for you to say what the returned value actually represents. For example, the declaration of the saveRecord() method may indicate that it returns an int, but the client reading that declaration wouldn’t know what the int means. Other ancillary data can be included in a comment as well, as shown in the following example.

/*

* saveRecord()

*

* Saves the given record to the database.

*

* Parameters:

* Record& rec: the record to save to the database.

* Returns: int

* An integer representing the ID of the saved record.

* Throws:

* DatabaseNotOpenedException if the openDatabase() method was not

* called first.

*/

Sometimes, the parameters to, and the return type from a function are generic and can be used to pass all kinds of information. In that case you need to clearly document what exact type is being passed. For example, message handlers in Windows accept two parameters, LPARAM and WPARAM, and can return an LRESULT. All three can be used to pass anything you like, but you cannot change the type of them. By using type casting, they can for example be used to pass a simple integer or to pass a pointer to some object. Your documentation could look as follows.

* Parameters:

* WPARAM wParam: (WPARAM)(int): An integer representing...

* LPARAM lParam: (LPARAM)(string*): A string representing...

* Returns: (LRESULT)(Record*)

* nullptr in case of an error, otherwise a pointer to a Record object

* representing...

Most editors allow you to bind keystrokes to perform certain actions. You could bind a keystroke so that the editor automatically inserts a standard commenting block which you subsequently fill in with the right information. For example, the keystroke could automatically insert the following comment template.

/*

* func()

*

* Description of the function.

*

* Parameters:

* int param1: parameter 1.

* Returns: int

* An integer representing...

* Throws:

* Exception1 if...

* Notes:

* Additional notes...

*/

Commenting to Explain Complicated Code

Good comments are also important inside the actual source code. In a simple program that processes input from the user and writes a result to the console, it is probably easy to read through and understand all of the code. In the professional world, however, you will often need to write code that is algorithmically complex or too esoteric to understand simply by inspection.

Consider the code that follows. It is well written, but it may not be immediately apparent what it is doing. You might recognize the algorithm if you have seen it before, but a newcomer probably wouldn’t understand the way the code works.

void sort(int inArray[], int inSize)

{

for (int i = 1; i < inSize; i++) {

int element = inArray[i];

int j = i – 1;

while (j >= 0 && inArray[j] > element) {

inArray[j+1] = inArray[j];

j--;

}

inArray[j+1] = element;

}

}

A better approach would be to include comments that describe the algorithm that is being used, and to document (loop) invariants. Invariants are conditions that have to be true during the execution of a piece of code, for example a loop iteration. In the modified function that follows, a thorough comment at the top explains the algorithm at a high level, and inline comments explain specific lines that may be confusing.

/*

* Implements the "insertion sort" algorithm. The algorithm separates the

* array into two parts--the sorted part and the unsorted part. Each

* element, starting at position 1, is examined. Everything earlier in the

* array is in the sorted part, so the algorithm shifts each element over

* until the correct position is found for the current element. When the

* algorithm finishes with the last element, the entire array is sorted.

*/

void sort(int inArray[], int inSize)

{

// Start at position 1 and examine each element.

for (int i = 1; i < inSize; i++) {

// Invariant: All elements in the range 0 to i-1 (inclusive) are sorted.

int element = inArray[i];

// j marks the position in the sorted part of the array.

int j = i – 1;

// As long as the current slot in the sorted array is higher than

// the element, shift the slot over and move backwards.

while (j >= 0 && inArray[j] > element) {

inArray[j+1] = inArray[j];

j--;

}

// At this point the current position in the sorted array

// is *not* greater than the element, so this is its new position.

inArray[j+1] = element;

}

}

The new code is certainly more verbose, but a reader unfamiliar with sorting algorithms would be much more likely to understand it with the comments included. In some organizations, inline comments are frowned upon. In such cases, writing clean code and having good comments at the top of the function becomes vital.

Commenting to Convey Metainformation

Another reason to use comments is to provide information at a higher level than the code itself. This metainformation provides details about the creation of the code without addressing the specifics of its behavior. For example, your organization may want to keep track of the original author of each method. You can also use metainformation to cite external documents or refer to other code.

The following example shows several instances of metainformation, including the author of the file, the date it was created, and the specific feature it addresses. It also includes inline comments expressing metadata, such as the bug number that corresponds to a line of code and a reminder to revisit a possible problem in the code later.

/*

* Author: marcg

* Date: 110412

* Feature: PRD version 3, Feature 5.10

*/

int saveRecord(Record& rec)

{

if (!mDatabaseOpen) {

throw DatabaseNotOpenedException();

}

int id = getDB()->saveRecord(rec);

if (id == -1) return -1; // Added to address bug #142 – jsmith 110428

rec.setId(id);

// TODO: What if setId() throws an exception? – akshayr 110501

return id;

}

A change-log could also be included at the beginning of each file. The following shows a possible example of such a change-log.

/*

* Date | Change

*----------+--------------------------------------------------

* 110413 | REQ #005: <marcg> Do not normalize values.

* 110417 | REQ #006: <marcg> use nullptr instead of NULL.

*/

However, this might not be necessary when you use a source code control solution, discussed in Chapter 24. They offer an annotated change history with revision dates, authors, and, if properly used, comments accompanying each modification, including references to change requests. You should check-in each change request separately. For example, if you need to implement two change requests in one file, you should check-out the file, implement the first change request and check-in the file with the appropriate change-log comment. Only then you can check-out the file again and work on the second change request. If you want to work on both change requests at the same time, you can branch the source file and start working on the first change request in one branch and work on the second change request in the second branch. When implementation is finished, you can merge both branches back together with appropriate change-log comments for each branch. With this method you don’t need to manually keep a change-log in the beginning of each file.

It’s easy to go overboard with comments. A good approach is to discuss which types of comments are most useful with your group and form a policy. For example, if one member of the group uses a “TODO” comment to indicate code that still needs work, but nobody else knows about this convention, the code in need could be overlooked.

NOTE If your group decides to use metainformation comments, make sure that you all include the same information or your files will be inconsistent.

Commenting Styles

Every organization has a different approach to commenting code. In some environments, a particular style is mandated to give the code a common standard for documentation. Other times, the quantity and style of commenting is left up to the programmer. The following examples depict several approaches to commenting code.

Commenting Every Line

One way to avoid lack of documentation is to force yourself to overdocument by including a comment for every line. Commenting every line of code should ensure that there’s a specific reason for everything you write. In reality, such heavy commenting on a large-scale basis is unscalable, messy, and tedious. For example, consider the following useless comments.

int result; // Declare an integer to hold the result.

result = doodad.getResult(); // Get the doodad's result.

if (result % 2 == 0) { // If the result mod 2 is 0 ...

logError(); // then log an error,

} else { // otherwise ...

logSuccess(); // log success.

} // End if/else

return result; // Return the result

The comments in this code express each line as part of an easily readable English story. This is entirely useless if you assume that the reader has at least basic C++ skills. These comments don’t add any additional information to code. Specifically, look at this line:

if (result % 2 == 0) { // If the result mod 2 is 0 ...

The comment is just an English translation of the code. It doesn’t say why the programmer has used the mod operator on the result with the value 2. A better comment would be:

if (result % 2 == 0) { // If the result is even ...

The modified comment, while still fairly obvious to most programmers, gives additional information about the code. The result is “modded” by 2 because the code needs to check if the result is even.

Despite its tendency to be verbose and superfluous, heavy commenting can be useful in cases where the code would otherwise be difficult to comprehend. The following code also comments every line, but these comments are actually helpful.

// Calculate the doodad. The start, end, and offset values come from the

// table on page 96 of the "Doodad API v1.6".

result = doodad.calculate(kStart, kEnd, kOffset);

// To determine success or failure, we need to bitwise AND the result with

// the processor-specific mask (see "Doodad API v1.6", page 201).

result &= getProcessorMask();

// Set the user field value based on the "Marigold Formula."

// (see "Doodad API v1.6", page 136)

setUserField((result + kMarigoldOffset) / MarigoldConstant + MarigoldConstant);

This code is taken out of context, but the comments give you a good idea of what each line does. Without them, the calculations involving & and the mysterious “Marigold Formula” would be difficult to decipher.

NOTE Commenting every line of code is usually untenable, but if the code is complicated enough to require it, don’t just translate the code to English: explain what’s really going on.

Prefix Comments

Your group may decide to begin all source files with a standard comment. This is an excellent opportunity to document important information about the program and specific file. Examples of information that you might want to document at the top of every file include the following.

· The last-modified date

· The original author

· A change-log as described earlier

· The feature ID addressed by the file

· Copyright information

· A brief description of the file/class

· Incomplete features

· Known bugs

Your development environment may allow you to create a template that automatically starts new files with your prefix comment. Some source control systems such as Subversion (SVN) can even assist by filling in metadata. For example, if your comment contains the string $Id$, SVN can automatically expand the comment to include the author, filename, revision, and date.

An example of a prefix comment is shown here:

/*

* $Id: Watermelon.cpp,123 2004/03/10 12:52:33 marcg $

*

* Implements the basic functionality of a watermelon. All units are expressed

* in terms of seeds per cubic centimeter. Watermelon theory is based on the

* white paper "Algorithms for Watermelon Processing."

*

* The following code is (c) copyright 2011, FruitSoft, Inc. ALL RIGHTS RESERVED

*/

Fixed-Format Comments

Writing comments in a standard format that can be parsed by external document builders is an increasingly popular programming practice. In the Java language, programmers can write comments in a standard format that allows a tool called JavaDoc to create hyperlinked documentation for the project automatically. For C++, a free tool called Doxygen (available at www.doxygen.org) parses comments to automatically build HTML documentation, class diagrams, UNIX man pages, and other useful documents. Doxygen even recognizes and parses JavaDoc-style comments in C++ programs. The code that follows shows JavaDoc-style comments that are recognized by Doxygen.

/**

* Implements the basic functionality of a watermelon

* TODO: Implement updated algorithms!

*/

class Watermelon

{

public:

/**

* @param initialSeeds The starting number of seeds

*/

Watermelon(int initialSeeds);

/**

* Computes the seed ratio, using the Marigold algorithm.

* @param slowCalc Whether or not to use long (slow) calculations

* @return The marigold ratio

*/

double calcSeedRatio(bool slowCalc);

};

Doxygen recognizes the C++ syntax and special comment directives such as @param and @return to generate customizable output. An example of a Doxygen-generated HTML class reference is shown in Figure 3-1.

image

FIGURE 3-1

Automatically generated documentation like the file shown in Figure 3-1 can be helpful during development because it allows developers to browse through a high-level description of classes and their relationships. Your group can easily customize a tool like Doxygen to work with the style of comments that you have adopted. Ideally, your group would set up a machine that builds documentation on a daily basis.

Ad Hoc Comments

Most of the time, you use comments on an as-needed basis. Here are some guidelines for comments that appear within the body of your code.

· Do your best to avoid offensive or derogatory language. You never know who might look at your code some day.

· Liberal use of inside jokes is generally considered okay. Check with your manager.

· Don’t put your initials in the code. Source code control solutions will track that kind of information automatically for you.

· If you are doing something with an API that isn’t immediately obvious, include a reference to the documentation of that API where it is explained.

· Remember to update your comments when you update the code. Nothing is more confusing than code that is fully documented with incorrect information.

· If you use comments to separate a function into sections, consider whether the function might be broken into multiple, smaller functions.

Self-Documenting Code

Well-written code doesn’t always need abundant commenting. The best code is written to be readable. If you find yourself adding a comment for every line, consider whether the code could be rewritten to better match what you are saying in the comments. For example, use descriptive names for your functions, parameters, variables, and so on. Remember that C++ is a language. Its main purpose is to tell the computer what to do, but the semantics of the language can also be used to explain its meaning to a reader.

Another way of writing self-documenting code is to break up, or decompose, your code into smaller pieces. Decomposition is covered in detail in the material that follows.

NOTE Good code is naturally readable and only requires comments to provide useful additional information.

Comments in This Book

The code examples you will see in this book often use comments to explain complicated code or to point things out to you that may not be evident.

DECOMPOSITION

Decomposition is the practice of breaking up code into smaller pieces. There is nothing more daunting in the world of coding than opening up a file of source code to find 300-line functions and massive nested blocks of code. Ideally, each function or method should accomplish a single task. Any subtasks of significant complexity should be decomposed into separate functions or methods. For example, if somebody asks you what a method does and you answer “First it does A, then it does B; then, if C, it does D; otherwise, it does E,” you should probably have separate helper methods for A, B, C, D, and E.

Decomposition is not an exact science. Some programmers will say that no function should be longer than a page of printed code. That may be a good rule of thumb, but you could certainly find a quarter-page of code that is desperately in need of decomposition. Another rule of thumb is if you squint your eyes and look at the format of the code without reading the actual content, it shouldn’t appear too dense in any one area. For example, Figures 3-2 and 3-3 show code that has been purposely blurred so that you don’t focus on the content. It should be obvious that the code in Figure 3-3 has better decomposition than the code in Figure 3-2.

image

FIGURE 3-2

image

FIGURE 3-3

Decomposition through Refactoring

Sometimes when you’ve had a few coffees and you’re really in the programming zone, you start coding so fast that you end up with code that does exactly what it’s supposed to do, but is far from pretty. All programmers do this from time to time. Short periods of vigorous coding are sometimes the most productive times in the course of a project. Dense code also arises over the course of time as code is modified. As new requirements and bug fixes emerge, existing code is amended with small modifications. The computing term cruft refers to the gradual accumulation of small amounts of code that eventually turns a once-elegant piece of code into a mess of patches and special cases.

Refactoring is the act of restructuring your code. The following are a couple of example techniques that you can use to refactor your code. Consult a refactoring book listed in Appendix B to get a more comprehensive list.

· Techniques that allow for more abstraction:

· Encapsulate Field: make a field private and give access to it with getter and setter methods.

· Generalize Type: create more general types to allow for more code sharing.

· Techniques for breaking code apart into more logical pieces:

· Extract Method: turn part of a larger method into a new method to make it easier to understand.

· Extract Class: move part of the code from an existing class into a new class.

· Techniques for improving names and the location of code:

· Move Method or Move Field: move to a more appropriate class or source file.

· Rename Method or Rename Field: change the name to better reveal its purpose.

· Pull Up: in object-oriented programming, move to a base class.

· Push Down: in object-oriented programming, move to a derived class.

Whether your code starts its life as a dense block of unreadable cruft or it just evolves that way, refactoring is necessary to periodically purge the code of accumulated hacks. Through refactoring, you revisit existing code and rewrite it to make it more readable and maintainable. Refactoring is an opportunity to revisit the decomposition of code. If the purpose of the code has changed or if it was never decomposed in the first place, when you refactor the code, squint at it and determine if it needs to be broken down into smaller parts.

Decomposition by Design

If you use modular decomposition and approach every module, method, or function by considering what pieces of it you can put off until later, your programs will generally be less dense and more organized than if you implemented every feature in its entirety as you coded.

Of course, you should still do some design of your program before jumping into the code.

Decomposition in This Book

You will see decomposition in many of the examples in this book. In many cases, methods are referred to for which no implementation is shown because they are not relevant to the example and would take up too much space.

NAMING

The compiler has a couple of naming rules. Names cannot start with a number. You must also not use names that contain a double underscore (for example my__name) or names that begin with an underscore (for example_Name) because these are reserved for use by the compiler and the standard library implementation. Other than that, names exist only to help you and your fellow programmers work with the individual elements of your program. Given this purpose, it is surprising how often programmers use unspecific or inappropriate names in their programs.

Choosing a Good Name

The best name for a variable, method, function, or class accurately describes the purpose of the item. Names can also imply additional information, such as the type or specific usage. Of course, the real test is whether other programmers understand what you are trying to convey with a particular name.

There are no set-in-stone rules for naming other than the rules that work for your organization. However, there are some names that are rarely appropriate. The following table shows some names at the two extreme ends of the naming continuum.

GOOD NAMES

BAD NAMES

sourceName, destinationName
Distinguishes two objects

thing1, thing2
Too general

gSettings
Conveys global status

globalUserSpecificSettingsAndPreferences
Too long

mNameCounter
Conveys data member status

mNC
Too obscure, concise

calculateMarigoldOffset()
Simple, accurate

doAction()
Too general, imprecise

mTypeString
Easy on the eyes

typeSTR256
A name only a computer could love

mWelshRarebit
Acceptable inside joke

mIHateLarry
Unacceptable inside joke

errorMessage
Descriptive name

string
Non-descriptive name

sourceFile, destinationFile
No abbreviations

srcFile, dstFile
Abbreviations

Naming Conventions

Selecting a name doesn’t always require a lot of thought and creativity. In many cases, you’ll want to use standard techniques for naming. Following are some of the types of data for which you can make use of standard names.

Counters

Early in your programming career, you probably saw code that used the variable “i” as a counter. It is customary to use i and j as counters and inner-loop counters, respectively. Be careful with nested loops, however. It’s a common mistake to refer to the “ith” element when you really mean the “jth” element. Some programmers prefer using counters like outerLoopIndex and innerLoopIndex instead, and some even frown upon using i and j as loop counters.

Prefixes

Many programmers begin their variable names with a letter that provides some information about the variable’s type or usage. On the other hand, there are as many programmers who frown upon using any kind of prefix because they could make evolving code less maintainable in the future. For example, if a member variable is changed from static to non-static, this would mean that you have to rename all the uses of that name. This is often time consuming and so most programmers don’t bother to rename the variables. As the code evolves, the declarations of the variables change but the names do not. This results in names giving the illusion of conveying semantics but in fact they convey the wrong semantics.

However, often you don’t have a choice and you need to follow the guidelines for your company. The following table shows some potential prefixes.

PREFIX

EXAMPLE NAME

LITERAL PREFIX MEANING

USAGE

m
m_

mData
m_data

“member”

Data member within a class.

s
ms
ms_

sLookupTable
msLookupTable
ms_lookupTable

“static”

Static variable or data member.

k

kMaximumLength

“konstant” (German for “constant”)

A constant value. Some programmers use all uppercase names to indicate constants.

b
is

bCompleted
isCompleted

“Boolean”

Designates a Boolean value.

n
mNum

nLines
mNumLines

“number”

A data member that is also a counter. Since an “n” looks similar to an “m,” some programmers instead use mNum as a prefix, as in mNumLines.

Getters and Setters

If your class contains a data member, such as mStatus, it is customary to provide access to the member via a getter called getStatus() and a setter called setStatus(). The C++ language has no prescribed naming for these methods, but your organization will probably want to adopt this or a similar naming scheme.

Capitalization

There are many different ways of capitalizing names in your code. As with most elements of coding style, the most important thing is that your group standardizes on an approach and that all members adopt that approach. One way to get messy code is to have some programmers naming classes in all lowercase with underscores representing spaces (priority_queue) and others using capitals with each subsequent word capitalized (PriorityQueue). Variables and data members almost always start with a lowercase letter and either use underscores (my_queue) or capitals (myQueue) to indicate word breaks. Functions and methods are traditionally capitalized in C++, but, as you’ve seen, in this book I have adopted the style of lowercase functions and methods to distinguish them from class names. A similar style of capitalizing letters is used to indicate word boundaries for class and data member names.

Namespaced Constants

Imagine that you are writing a program with a graphical user interface. The program has several menus, including File, Edit, and Help. To represent the ID of each menu, you may decide to use a constant. A perfectly reasonable name for a constant referring to the Help menu ID is kHelp.

The name kHelp will work fine until one day you add a Help button to the main window. You also need a constant to refer to the ID of the button, but kHelp is already taken.

A possible solution for this is to put your constants in different namespaces, which are discussed in Chapter 1. You create two namespaces: Menu and Button. Each namespace has a kHelp constant and you use them as Menu::kHelp and Button::kHelp. Another and more preferred solution is to use enumerators, also introduced in Chapter 1.

Hungarian Notation

Hungarian Notation is a variable and data member naming convention that is popular with Microsoft Windows programmers. The basic idea is that instead of using single-letter prefixes such as m, you should use more verbose prefixes to indicate additional information. The following line of code displays the use of Hungarian Notation:

char* pszName; // psz means "pointer to a null-terminated string"

The term Hungarian Notation arose from the fact that its inventor, Charles Simonyi, is Hungarian. Some also say that it accurately reflects the fact that programs using Hungarian Notation end up looking as if they were written in a foreign language. For this latter reason, some programmers tend to dislike Hungarian Notation. In this book, prefixes are used, but not Hungarian Notation. Adequately named variables don’t need much additional context information besides the prefix. For example, a data member named mNamesays it all.

NOTE Good names convey information about their purpose without making the code unreadable.

USING LANGUAGE FEATURES WITH STYLE

The C++ language lets you do all sorts of terribly unreadable things. Take a look at this wacky code:

i++ + ++i;

This is unreadable but more importantly, its behavior is undefined by the C++ standard. The problem is that i++ uses the value of i but has a side effect of incrementing it. The standard does not say when this incrementing should be done, only that the side effect (increment) should be visible after the sequence point “;”, but the compiler can do it at any point in time during the execution of that line. It’s impossible to know which value of i will be used for the ++i part. Running this code with different compilers and platforms can result in different values. The following is another example of code with undefined behavior which you should avoid.

a[i] = i++;

With all the power that the C++ language offers, it is important to consider how the language features can be used towards stylistic good instead of evil.

Use Constants

Bad code is often littered with “magic numbers.” In some function, the code might be using 2.71828. Why 2.71828? What does that value mean? People with a mathematical background might find it obvious that this represents an approximation of the transcendental value e, but most people don’t know this. The language offers constants to give a symbolic name to a value that doesn’t change, such as 2.71828.

const double kApproximationForE = 2.71828182845904523536;

Use References Instead of Pointers

Traditionally, C++ programmers learn C first. In C, pointers were the only pass-by-reference mechanism, and they certainly worked just fine for many years. Pointers are still required in some cases, but in many situations you can switch to references. If you learned C first, you probably think that references don’t really add any new functionality to the language. You might think that they merely introduce a new syntax for functionality that pointers could already provide.

There are several advantages to using references rather than pointers. First, references are safer than pointers because they don’t deal directly with memory addresses and cannot be nullptr. Second, references are more stylistically pleasing than pointers because they use the same syntax as stack variables, avoiding symbols such as * and &. They’re also easy to use, so you should have no problem adopting references into your style palette. Unfortunately, some programmers think that if they see an & in a function call, they know the called function is going to change the object and if they don’t see the & it must be pass-by-value. With references, they say they don’t know if the function is going to change the object unless they look at the function prototype. This is a wrong way of thinking. Passing in a pointer does not automatically mean that the object will be modified, because the parameter might be const T*. Both passing a pointer and a reference can modify the object or not depending on whether the function prototype uses const T*, T*, const T& or T&.So, you need to look at the prototype anyway to know if the function might change the object.

Another benefit of references is that they clarify ownership of memory. If you are writing a method and another programmer passes you a reference to an object, it is clear that you can read and possibly modify the object, but you have no easy way of freeing its memory. If you are passed a pointer, this is less clear. Do you need to delete the object to clean up memory? Or will the caller do that? The preferred way of handling memory is to use smart pointers introduced in Chapter 1.

Use Custom Exceptions

C++ makes it easy to ignore exceptions. Nothing about the language syntax forces you to deal with exceptions and you could easily write error-tolerant programs with traditional mechanisms such as returning nullptr or setting an error flag.

Exceptions provide a much richer mechanism for error handling, and custom exceptions allow you to tailor this mechanism to your needs. For example, a custom exception type for a web browser could include fields that specify the web page that contained the error, the network state when the error occurred, and additional context information.

Chapter 13 contains a wealth of information about exceptions in C++.

NOTE Language features exist to help the programmer. Understand and make use of features that contribute to good programming style.

FORMATTING

Many programming groups have been torn apart and friendships ruined over code-formatting arguments. In college, a friend of mine got into such a heated debate with a peer over the use of spaces in an if statement that people were stopping by to make sure that everything was okay.

If your organization has standards in place for code formatting, consider yourself lucky. You may not like the standards they have in place, but at least you won’t have to argue about it. If everybody on your team is writing code their own way, try to be as tolerant as you can. As you’ll see, some practices are just a matter of taste, while others actually make it difficult to work in teams.

The Curly Brace Alignment Debate

Perhaps the most frequently argued-about point is where to put the curly braces that demark a block of code. There are several styles of curly brace use. In this book, the curly brace is put on the same line as the leading statement, except in the case of a function, class, or method name. This style is shown in the code that follows (and throughout the book).

void someFunction()

{

if (condition()) {

cout << "condition was true" << endl;

} else {

cout << "condition was false" << endl;

}

}

This style conserves vertical space while still showing blocks of code by their indentation. Some programmers would argue that preservation of vertical space isn’t relevant in real-world coding. A more verbose style is shown below.

void someFunction()

{

if (condition())

{

cout << "condition was true" << endl;

}

else

{

cout << "condition was false" << endl;

}

}

Some programmers are even liberal with use of horizontal space, yielding code like that in the following example.

void someFunction()

{

if (condition())

{

cout << "condition was true" << endl;

}

else

{

cout << "condition was false" << endl;

}

}

Of course, I won’t recommend any particular style because I don’t want hate mail.

NOTE When selecting a style for denoting blocks of code, the important consideration is how well you can see which block falls under which condition simply by looking at the code.

Coming to Blows over Spaces and Parentheses

The formatting of individual lines of code can also be a source of disagreement. Again, I won’t advocate a particular approach, but a few styles are shown that you are likely to encounter.

In this book, I use a space after any keyword, a space before and after any operator, a space after every comma in a parameter list or a call, and parentheses to clarify the order of operations, as follows:

if (i == 2) {

j = i + (k / m);

}

The alternative, shown next, treats if stylistically like a function, with no space between the keyword and the left parenthesis. Also, the parentheses used to clarify the order of operations inside of the if statement are omitted because they have no semantic relevance.

if( i == 2 ) {

j = i + k / m;

}

The difference is subtle, and the determination of which is better is left to the reader, yet I can’t move on from the issue without pointing out that if is not a function.

Spaces and Tabs

The use of spaces and tabs is not merely a stylistic preference. If your group does not agree on a convention for spaces and tabs, there are going to be major problems when programmers work jointly. The most obvious problem occurs when Alice uses four spaces to indent code and Bob uses five space tabs; neither will be able to display code properly when working on the same file. An even worse problem arises when Bob reformats the code to use tabs at the same time that Alice edits the same code; many source code control systems won’t be able to merge in Alice’s changes.

Most, but not all, editors have configurable settings for spaces and tabs. Some environments even adapt to the formatting of the code as it is read in, or always save using spaces even if the tab key is used for authoring. If you have a flexible environment, you have a better chance of being able to work with other people’s code. Just remember that tabs and spaces are different because tabs can be any length and a space is always a space.

STYLISTIC CHALLENGES

Many programmers begin a new project by pledging that, this time, they will do everything right. Any time a variable or parameter shouldn’t be changed, it’ll be marked const. All variables will have clear, concise, readable names. Every developer will put the left curly brace on the subsequent line and will adopt the standard text editor and its conventions for tabs and spaces.

For a number of reasons, it is difficult to sustain this level of stylistic consistency. In the case of const, sometimes programmers just aren’t educated about how to use it. You will eventually come across old code or a library function that isn’t const-savvy. A good programmer will use const_cast to temporarily suspend the const property of a variable, but an inexperienced programmer will start to unwind the const property back from the calling function, once again ending up with a program that never uses const.

Other times, standardization of style comes up against programmers’ own individual tastes and biases. Perhaps the culture of your team makes it impractical to enforce strict style guidelines. In such situations, you may have to decide which elements you really need to standardize (such as variable names and tabs) and which ones are safe to leave up to individuals (perhaps spacing and commenting style). You can even obtain or write scripts that will automatically correct style “bugs” or flag stylistic problems along with code errors. Some development environments, such as Microsoft Visual C++ 2013, support automatic formatting of code according to rules that you specify. This makes it trivial to write code that always follows the guidelines that have been configured.

SUMMARY

The C++ language provides a number of stylistic tools without any formal guidelines on how to use them. Ultimately, any style convention is measured by how widely it is adopted and how much it benefits the readability of the code. When coding as part of a team, you should raise issues of style early in the process as part of the discussion of what language and tools to use.

The most important point about style is to appreciate that it is an important aspect of programming. Teach yourself to check over the style of your code before you make it available to others. Recognize good style in the code you interact with and adopt the conventions that you and your organization find useful.

This chapter concludes the first part of this book. The next part discusses software design on a high level.