Reliable Software Engineering Practices

  1. Introduction
  2. Foundations
  3. Intent & Purpose
  4. Correctness & Completeness
  5. Ease of Maintenance
  6. Optimization and Efficiency

Introduction

“Failure to supply contemporaneous rationale for design decisions is a major defect in many software projects. Lack of accurate rationale causes issues to be revisited endlessly, causes maintenance bugs when a maintainer changes something without realizing it was done a certain way for some purpose, and shortens the useful lifetime of software.”

—Beman Dawes, on the Boost website

“Rationale is fairly easy to provide at the time decisions are made, but very hard to accurately recover even a short time later.”

—The Boost website

  1. Overview
  2. Document Conventions

Overview

Steve McConnell contends in Code Complete, [McConnell1993], that software development should really be termed software engineering. Conceding this to be an accurate statement, we further recognize (based upon the experience of the more traditional engineering disciplines) that all decisions—even the most common and apparently trivial—must thus be considered in light of their ramifications to the final product.

A good engineer does more than provide a solution “that works.” He definitely has to do that, but a good engineer is also concerned about:

These are often competing goals and that is why, when developing software, a good engineer must provide deliverables (such as source code and other documentation) that:

Thus, reliable engineering consists of more than just solutions to the problems an engineer faces; it also consists of documenting decisions, such that, any engineer viewing the original engineer’s deliverables will not have to struggle to understand the problems that faced the original engineer, how that engineer solved those problems and the rationale behind those solutions (thus making it easier to determine whether those decisions completely meet a given set of design goals—whether the original goals or some new set of goals intended to extend the original design. Decisions do not exist in a vacuum.

For software development purposes, all decisions can be classified as either an “engineering practice” or a “stylistic preference.” Within the context of software development, engineering practices are those practices that express, and make plain, critical choices in source code and other deliverables, whether the ultimate object code or executing program is directly effected or not. Although some programmers view a stylistic preference as any practice or choice that, when changed, does not effect the resulting object code, when such a “stylistic preference” is used to make a code critical choice more self-evident to a code reviewer, it should properly be viewed as a reliable engineering practice. Take, for example, providing a comment documenting why a particular algorithm was used (or not used), or providing some indication (stylistically or with a comment) of whether a declared function will modify a passed in object. Neither actually modify the resulting object code, but both should be viewed as necessary engineering practices because they are essential to meeting the general guidelines of quality engineering.

Although stylistic preferences can be quite personal, they should, at a minimum, be chosen in such a way that the meaning of any piece of code is readily apparent to a junior engineer without extensive study. A description of the stylistic preferences we follow when using C, C++ and Objective-C can be found in our Style Guidelines.

This document, however, is a list of various software engineering practices—both generally speaking and as they relate to C, C++ and/or Objective-C—that, when followed, lead to better, more maintainable software. It attempts to bring together rules, guidelines, and practices that are scattered throughout other sources and reflects the programming community’s best tried-and-true experience. Rationale is often provided so that the rules can be understood rather than just memorized.

Although we are mindful of the following quote:

“Undertake not to teach your equal in the art he himself professes; it savors of arrogancy.”

—One of 110 rules of civility copied by a young George Washington

from a sixteenth century French book of etiquette.

…one of the purposes of this document (and our Style Guidelines) is to stimulate discussion among software engineers about what constitutes reliable engineering practices and any stylistic preferences that will help make those practices more clear. We do not claim to have a corner on the market for these ideas—as you will see, many of those mentioned in our documents came from published books—, but we do believe there should be a central repository for such ideas. Not so much to presume to “undertake to teach our equals in the art they themselves profess,” but so more people can apply them in a uniform manner and thereby improve the quality of software any group of engineers chooses to produce.

Document Conventions

Any text in this document that is this color should be considered incomplete and still under construction.

The sections of this document have subsections labeled as follows:

Background

Introduces and provides the necessary background information to understand subsequent maxims.

General Maxim
C++ Maxim
Objective-C Maxim

Maxims answer who, what, where, when and how.

For Example

Provides examples and discussion expounding upon the maxim(s).

Ramifications

Discusses the benefits of (i.e., the why), and the consequences of ignoring (i.e., the why not), the maxim(s).

Exceptions

Lists known exceptions to the maxim(s).

References

Lists references that contain more information about the topic at hand.

Foundations

  1. Memory Area Distinctions
  2. Failure Guarantees
  3. Error Categories

Memory Area Distinctions

C++ Maxim

Understand the five major distinct memory stores, why they are different, and how they behave.

Background

Although the distinction between Free Store and Heap, as here defined, is technically accurate, in everyday use, the term Heap is often used when Free Store is meant.

  1. Const data
  2. Global
  3. Stack
  4. Free store
  5. Heap

Const data

Background

The const data area stores string literals and other data whose values are known at compile-time. No objects of class type can exist in this area. All data in this area are read-only and available during the entire lifetime of the program.

Global

Background

Global or static variables and objects have their storage allocated at program startup, but may not be initialized until after the program has begun executing. For instance, a static variable in a function is initialized only the first time program execution passes through its definition. The order of initialization of global variables across translation units is not defined.

General Maxim

Shared data increases coupling and therefore should be avoided, if possible.

References

See Item 10 of [Sutter2005a].

Stack

Background

The stack stores automatic variables. Objects are constructed immediately at the point of definition and destroyed immediately at the end of the same scope (but the order in which variables are stored on the stack cannot be portably assumed). Stack memory allocation is typically much faster than for dynamic storage (i.e., Free Store or Heap).

Free store

Background

The free store is one of the two dynamic memory areas and is allocated and deallocated using new and delete.

C++ Maxim

Always provide both class specific operator new(params) (or new[]) and class specific operator delete(void*, params) (or delete[]) if you provide either. (See Item 45 of [Sutter2005a].)

C++ Maxim

If you provide any class-specific new, provide all of the standard forms (plain, in-place, and nothrow). (See Item 46 of [Sutter2005a].)

Heap

Background

The heap is the other dynamic memory area and is allocated and deallocated by malloc(), calloc(), realloc() and free(). Note that while the default global operators new and delete might be implemented in terms of malloc() and free(), the heap is not the same as free store, and memory allocated in one area cannot be safely deallocated in the other. According to the C++ standard, memory for the heap must not be implemented in terms of new/delete.

C++ Maxim

Prefer using the Free Store (and employ reliable ownership practices). Avoid using the Heap.

Failure Guarantees

Background

The Basic, Strong, and No-fail (formerly known as No-throw) guarantees were originally defined with respect to exception safety, however they really apply to all failure handling, regardless of the specific method used to report failure. The No-fail guarantee is a strict superset of the Strong guarantee, which is a strict superset of the Basic guarantee. Failure to meet at least the Basic guarantee is always a bug and difficulty in making any piece of code satisfy the Basic guarantee is almost always is a signal of poor design.

References

See Items 8 thru 12 in [Sutter2000], Items 22 thru 23 in [Sutter2002], and Item 71 of [Sutter2005a].

  1. No-fail guarantee
  2. Strong guarantee
  3. Basic guarantee

No-fail guarantee

Background

A function with the No-fail guarantee will not fail under any circumstances. Overall exception safety is not possible unless certain functions (e.g., destructors, deallocators, and the Swap() function) are guaranteed not to fail. Somewhat surprisingly, C++ exception specifications are of limited usefulness when providing this guarantee.

Strong guarantee

Background

A function with the Strong guarantee means that, if the operation fails, program state will remain unchanged. This always implies commit-or-rollback semantics, including that no references or iterators into an object become invalidated if an operation fails.

Basic guarantee

Background

A function with the Basic guarantee means that, if the operation fails, no resources are leaked and objects remain in a destructible and usable—but not necessarily predictable—state. All, some or none of the function’s postconditions may be met but all of a type’s invariants are always reestablished.

General Maxim

Always provide at least the Basic guarantee.

General Maxim

Every function should provide the strongest guarantee it can without needlessly penalizing calling code that does not need the guarantee. Where possible, it should additionally provide enough functionality to allow calling code that needs a still stronger guarantee to achieve that.

General Maxim

The documentation for each function should, at a minimum, declare which of the three standard failure guarantees is being met. (Consider versioning: It is always easier to strengthen a guarantee, but weakening a guarantee breaks calling code that has come to rely on the stronger guarantee without notifying the client.)

Ramifications

Clients should not have to inspect implementation details in order to know what to expect.

References

See Item 71 of [Sutter2005a].

Error Categories

All software defects fall into one of the following categories:

References

This section has been deprecated until some point in the future when we will likely have to incorporate Item 69 (a rational error handling policy) of [Sutter2005a].

Intent & Purpose

  1. Specified Design Goals
  2. Documentation Placement

Specified Design Goals

General Maxim

Before design goals can be met, design goals must be specified. For an entire software project, this often takes the form of a Requirements Specification or something similar, however for an individual class or type, this often takes the form of a few paragraphs at the top of the class’s interface file.

Documentation Placement

General Maxim

Documentation intended for use outside of a development team (e.g., a user’s manual, a ReadMe file, etc.) should be kept outside of the source code used to produce the software (object code). (Such documentation may be “code based” itself—so that it can be “generated” at different stages—, but the salient point is that it has a different target audience and usually a different set of people working on it.)

On the other hand, development team documentation should be stored physically as close to what is being documented as practical.

  1. Development team documentation

Development team documentation

Background

Users of source code should not be required to search for documentation covering the code they wish to use. The documentation should be where clients are going to look anyway, viz., in a class’s interface file.

General Maxim

Documentation intended for clients of a class should be completely within the class’s interface file.

General Maxim

Documentation related to implementation details of a class should be within the class’s implementation file.

Correctness & Completeness

General Maxim

Write for correctness, simplicity and clarity first.

References

See Item 6 of [Sutter2005a].

  1. Deliverables Provide Correct Results
  2. Design By Contract
  3. Minimize Opportunities for Unintentional Defects
  4. Namespaces and Modules
  5. Implicitly Provided Functionality
  6. Overloaded Operators
  7. Error Handling and Exceptions
  8. C++’s Standard Template Library

Deliverables Provide Correct Results

General Maxim

Obviously an engineer’s deliverables must meet the prescribed design goals. That is to say, the ultimate user will only remain a user if the software does what the user needs it to do.

  1. Header file #include guards
  2. Declarations of main()
  3. Virtual functions in constructors/destructors

Header file #include guards

Background

C++ does not allow some information—often part of .h files—to be declared or defined more than once.

References

For specifics, consult section 9.3.3 of [Stroustrup1997].

C++ Maxim

Every .h file should contain #include guards, preventing the contents of the .h file from being #included elsewhere multiple times. (See also our comments about #pragma once.)

For Example

Consult our Style Guidelines for an example.

Ramifications

Clients will not receive errors if more than one interface file they include #includes your interface file.

Declarations of main()

Background

Although your compiler may recognize others, the C++ standard only recognizes two signatures for main().

General Maxim

Always use either of the two standard and portable declarations for main():


int main(void);
int main(int argc, char* argv[]);
Ramifications

Portability.

Virtual functions in constructors/destructors

Background

Virtual functions only mostly behave virtually; inside constructors and destructors, they do not.

C++ Maxim

Avoid calling virtual functions (or other functions that rely on virtual functions) in constructors and destructors.

References

See Item 49 of [Sutter2005a].

Design By Contract

General Maxim

An interface should attempt to delineate “Design by Contract,” i.e., each function declaration should specify the expected preconditions and the delivered postconditions. Quite often this can be effectively accomplished via the careful specification of parameter types. Member functions also implicitly guarantee to maintain a type’s invariants.

General Maxim

Unit testing (including test-driven development) and code coverage tools are particularly useful in validating that functions and methods actually fulfill “Design by Contract.”

Ramifications

Clients can expect contracted behavior and know that, if they do not receive it, the problem is not theirs. In other words, encapsulation can be relied upon.

  1. Prefer providing abstract interfaces
  2. Kinds of classes
  3. Preconditions
  4. Postconditions
  5. Type invariants
  6. Flag incorrect usage

Prefer providing abstract interfaces

Background

Abstract interfaces help you focus on getting an abstraction right without muddling it with implementation or state management details.

General Maxim

Prefer to design hierarchies that implement abstract interfaces that model abstract concepts.

General Maxim

Prefer to define and inherit from abstract interfaces.

General Maxim

Prefer to follow the Dependency Inversion Principle, which states:

References

See Item 36 of [Sutter2005a].

Kinds of classes

Background

There are different kinds of classes. Know which kind you are designing. (This maxim is based primarily upon C++; its general concepts apply in Objective-C, although the rules tend to be less stringent.)

General Maxim
References

Based primarily on Item 32 of [Sutter2005a].

Preconditions

Background

Functions cannot rely upon callers to be well-behaved, therefore…

General Maxim

Every function must validate its preconditions (i.e., starting assumptions). (Parameter verification and ensuring a pointer is non-NULL are classic examples of preconditions.) If a function is internal to a module, it should assert each of its preconditions, otherwise it should report precondition violations by emitting an appropriate error.

Ramifications

Clients are not allowed to use a type incorrectly.

General Maxim

A condition should be a precondition if and only if it is reasonable to expect all callers to verify the condition’s validity before calling the function.

References

See Item 70 of [Sutter2005a].

Postconditions

General Maxim

It is reasonable for a function to validate another called function’s postconditions (i.e., ending assumptions). Because the called function can always be viewed as internal to the calling function’s module, postconditions can be asserted or reported to a caller by emitting an error, as the design dictates.

Ramifications

Errors in, or misuse of, suppliers are more likely to be caught and dealt with.

References

See Item 70 of [Sutter2005a].

Type invariants

General Maxim

Invariants are a special kind of postcondition that applies to member functions and whose violations should be asserted.

General Maxim

Clients should not be able to set an object of a given type to a state that violates that type’s invariant. Therefore, any type containing a member variable that is not allowed to have its full range of values (based on the invariant of that member variable’s type) must be designed so the member variable is private and accessible only through accessors.

Ramifications

Types are always internally consistent.

References

See Item 70 of [Sutter2005a].

Flag incorrect usage

General Maxim

When client programmers use a function incorrectly, the function should detect and flag such incorrect use (at least in a debugging configuration).

Ramifications

Failure to recognize and signal incorrect usage allows client programmers to use types or classes of objects in ways for which they were not designed, and thus allow situations that are likely to behave unpredictably.

  1. Parameter verification
  2. NULL pointers

Parameter verification

General Maxim

Every function that accepts parameters should verify that each passed in argument is within the range of reasonable values for the abstraction the parameter refers to. This is a precondition of the function.

Exceptions

Assuming you are maintaining a type's invariants, the object pointed to by the this (or self) parameter can typically be assumed to be correct.

Ramifications

Clients can assume that, if (a debugging version of) a function does not complain about the arguments being passed in, the function is being used correctly.

NULL pointers

General Maxim

Before dereferencing any pointer, it must be verified to be non-zero.

Exceptions

Because the compiler automatically passes the this (or self) parameter, it can typically be assumed to be non-NULL (except after object initialization in Objective-C).

Ramifications

It should not need to be said that results are unpredictable when accessing an area of memory from a NULL pointer. Furthermore, it should not need to be said that, having a pointer with a non-NULL, yet invalid value, is asking for trouble.

Minimize Opportunities for Unintentional Defects

  1. Enable compiler warnings
  2. Significant changes should force client code to fail
  3. Put entities with linkage in implementation files
  4. Prefer containment
  5. Public inheritance
  6. Minimize multiple inheritance
  7. Base class destructors
  8. Hidden functions
  9. Const-Correctness
  10. Always initialize variables
  11. Commit or rollback if you can
  12. Function argument evaluation order
  13. Default arguments
  14. Isolate resource ownership
  15. Ownership of free store objects
  16. Shun arrays
  17. Shun unions
  18. Prefer typename over class
  19. Use the this qualifier
  20. Do not specialize function templates
  21. Predicates depend only on arguments
  22. Prefer function objects over functions
  23. Function objects should be adaptable

Enable compiler warnings

Background

Most modern compilers provide optional mechanisms to warn engineers when they try to do something potentially hazardous or potentially non-portable. Such warnings are diagnostic messages that report constructs that are not inherently erroneous but are risky or suggest there may have been an error. These warnings typically have some sort of work-around to sidestep the warning.

Some examples of these types of warnings include:

General Maxim

Every project should enable all tenable language warnings that notify engineers when they try to do something potentially hazardous.

Ramifications

Such warnings require engineers to type in a work-around and thus make explicit their consideration of these hazardous choices.

Significant changes should force client code to fail

General Maxim

When a type or class’s interface (i.e., its contract with the outside world) is modified in a significant manner, clients of that interface should receive compile and link time (or, at a minimum, runtime) warnings, errors or signals.

Ramifications

Changes to types or classes that have an effect on clients should cause those clients to fail to compile so that client engineers are forced to take notice of the change and modify their code to work correctly with the newly modified type or class. Failure to flag the change could cause clients to be using the “new” type or class incorrectly, resulting in incorrect behavior.

Put entities with linkage in implementation files

General Maxim

Put all entities with linkage in implementation files.

Exceptions
Ramifications

Eliminates link time errors and memory waste.

References

See Item 61 of [Sutter2005a].

Prefer containment

General Maxim

When modeling “is implemented in terms of,” always prefer membership/containment, not inheritance. Use private inheritance only when inheritance is absolutely necessary—that is, when you need access to protected members or you need to override a virtual function.

Ramifications

Consult the summary of Item 22 in [Sutter2000]. Additionally, because of the Interface Principle, if you do not prefer containment, you may discover surprising effects in your code. See Items 31-34 in [Sutter2000].

References

See also Item 34 of [Sutter2005a].

Public inheritance

C++ Maxim

Avoid inheriting from classes that were not designed to be base classes. Using a standalone class as a base is a serious design error and should be avoided. To add behavior, prefer to add nonmember functions instead of member functions. To add state, prefer composition instead of inheritance.

References

See Item 35 of [Sutter2005a].

General Maxim

Public inheritance means substitutability (i.e., “works-like-a”). Never inherit publicly to reuse code (in the base class); inherit publicly in order to be reused (by code that uses base objects polymorphically).

References

See Item 37 of [Sutter2005a].

General Maxim

When overriding a virtual function, preserve substitutability; in particular, at a minimum, you must observe the function’s pre- and postconditions from the base class.

References

See Item 38 of [Sutter2005a].

Minimize multiple inheritance

Background

Multiple-inheritance is fraught with peril.

C++ Maxim

Avoid multiple-inheritance from more than one non-protocol class.

Base class destructors

C++ Maxim

Always make base class destructors either public and virtual, or protected and nonvirtual.

C++ Maxim

Any class that has only the implicit (nonvirtual) destructor or an explicit nonvirtual destructor cannot be a base class.

References

See Item 50 of [Sutter2005a].

Hidden functions

C++ Maxim

When providing a function with the same name as an inherited function, be sure to bring the inherited function(s) into scope with a using-declaration (if you do not want to hide them).

C++ Maxim

When a derived class deliberately hides a base class’s functionality, it should do so explicitly by writing a private using-declaration.

Const-Correctness

Background

Immutable values are easier to understand, track and reason about.

General Maxim

The const and mutable qualifiers are our friends.

General Maxim

Use const proactively; as much as possible, but no more.

General Maxim

Do not cast away const (except to call a const-incorrect function).

References

See Item 15 of [Sutter2005a].

  1. Prefer mutable over const_cast

Prefer mutable over const_cast

Background

Some uses of const_cast are dangerous (e.g., if the compiler places the object in read-only memory).

C++ Maxim

If possible, prefer using mutable over const_cast.

References

See Item 94 of [Sutter2005a].

Always initialize variables

General Maxim

Automatic (stack based) variables should always be initialized as part of their declaration.

General Maxim

Constructors (and initializers) should always initialize every member variable into some valid state. (In Objective-C, this sometimes means simply leaving a member variable at its default value of zero. In such a case, you should be explicit that zero was intended and assert the value as zero in the initializer method.) In other words, objects always have invariants, and a fully constructed object either reflects that invariant or never existed because an exception was thrown. (See also using initializer lists.)

Objective-C Maxim

If an initializer method cannot fully construct all member variables, it should autorelease itself and return nil.

Objective-C Maxim

Always verify that an alloced/inited object is non-nil before using it.

Ramifications

Clients can rely on the fact that objects are always in a valid state (or unsuccessfully constructed and construction failure indicated—in C++, with an exception thrown, or in Objective-C, because self was set to nil).

Commit or rollback if you can

General Maxim

In each function, take all the code that might fail and do all that work safely off to the side. Only then, when you know that the real work has succeeded, should you modify the program state and clean up using non-throwing operations.

Ramifications

Types whose member functions provide this Strong guarantee are more easily reused.

References

See Items 9 & 10 in [Sutter2000].

Function argument evaluation order

Background

According to the C++ standard, the order in which function arguments are evaluated is undefined and compiler implementation dependent.

General Maxim

Never write code that depends on the order of evaluation of function arguments.

Ramifications

Portability. (Evaluation of multiple function arguments as part of a function call is essentially a compound statement and should therefore be avoided even when portability is not a consideration.)

References

See Item 31 of [Sutter2005a].

Default arguments

C++ Maxim

Never change the default argument(s) of overridden inherited functions. (This is one more reason why “magic numbers” are to be avoided. The default argument values will be needed by derived classes.)

References

See Item 38 of [Sutter2005a].

Isolate resource ownership

Background

See section 14.4 of [Stroustrup1997].

C++ Maxim

Always use “resource acquisition is initialization” (aka, RAII) to isolate resource ownership and management. Here, “resource” can mean many different types of resources such as: memory, thread locks, file writing permission, etc. (The ScopeExitAction family of classes (in our osi namespace) is a general facility designed to simplify implementing precisely this maxim.)

Ramifications

Exceptions will not cause resource leaks.

References

See Item 13 of [Sutter2005a].

Ownership of free store objects

Background

A free store object is one whose memory is allocated via new, and the owner of a free store object is whomever is responsible for deleting it when it is no longer needed.

C++ Maxim

When you need to specify the transfer of ownership of a free store based object, use std::auto_ptr or osi::shared_ptr (or something similar). Because auto_ptr and shared_ptr are designed to take on ownership of free store objects and appropriately delete such objects, even within the context of exceptions, specifying auto_ptr and shared_ptr makes the intended assumption of ownership plain and virtually impossible to ignore.

Ramifications

Doing so intentionally makes it impossible for clients to refuse to relinquish ownership of an object (when you are taking on that responsibility), or, when they need to accept ownership of an object, it makes it harder for them to not recognize that they are assuming responsibility for deleting the object.

The best treatise I have seen on this topic is Item 37 in [Sutter2000].

Background

Memory allocations can throw and the order of execution of multiple allocations within a single statement (e.g., in a function call) can be compiler dependent.

General Maxim

Perform every explicit resource allocation in its own source code statement, which immediately gives the allocated resource to a manager object.

References

The best treatise I have seen on this topic is Item 21 in [Sutter2002], but see also Item 13 of [Sutter2005a].

Shun arrays

Background

You can never treat arrays polymorphically.

C++ Maxim

Prefer using vector (or string) instead of arrays.

References

See Item 77 of [Sutter2005a].

Shun unions

Background

Unions can be abused into obtaining a “cast without a cast.”

General Maxim

Do not use unions to reinterpret representation.

References

See Item 97 of [Sutter2005a].

Prefer typename over class

C++ Maxim

In template declarations, prefer the typename keyword over the class keyword.

Ramifications

A template type T often could be int, which is not a class.

Use the this qualifier

C++ Maxim

Within class template member functions, always use the this-> qualifying construct.

For Example

Do:


this->Swap();

-NOT-


Swap();
Ramifications

See Section 5.2 of [Vandevoorde2003].

Do not specialize function templates

Background

Function templates cannot be partially specialized nor can function template specializations be overloaded.

C++ Maxim

When extending someone else’s function template, avoid trying to write a specialization; instead, write an overload of the function template and place it in the namespace of the type(s) the overload is designed to be used for.

For Example

For a class:


namespace N {
    class SomeClass {/*...*/};
} // namespace N

…prefer:


namespace N {
    void swap(SomeClass& rtOp, SomeClass& ltOp);
} // namespace N

…over:


namespace std {
    void std::swap(SomeClass& rtOp, SomeClass& ltOp);
} // namespace std
C++ Maxim

When writing your own function template, prefer to write a single function template that should never be specialized or overloaded, and implement the function template entirely in terms of a class template.

References

See Item 66 of [Sutter2005a].

Predicates depend only on arguments

Background

A predicate is a function object that returns a boolean answer. Algorithms make an unknowable number of copies of their predicates at unknowable times in an unknowable order, and then go on to pass those copies around while casually assuming that the copies are all equivalent.

C++ Maxim

Do not allow predicates to hold or access state that affect the result of their operator().

C++ Maxim

Prefer to make operator() a const member function of predicates.

Ramifications

The compiler can help prevent modifying the state of the function object.

References

See Item 87 of [Sutter2005a].

Prefer function objects over functions

Background

Comparers for associative containers must be function objects. Function objects are adaptable and typically produce faster code than functions.

C++ Maxim

Prefer function objects over functions as algorithm and comparer arguments.

References

See Item 88 of [Sutter2005a].

Function objects should be adaptable

Background

Function objects, like function pointers, are typically passed by value.

C++ Maxim

Where possible, make function objects cheap to copy and adaptable by inheriting them from unary_- or binary_function.

References

See Item 89 of [Sutter2005a].

Namespaces and Modules

  1. Namespaces
  2. Namespace usage

Namespaces

Background

Using-directives—e.g., using namespace std—cause wanton namespace pollution by bringing in potentially huge numbers of names, many of which are unnecessary.

C++ Maxim

Never write namespace using-directives in header files.

Background

Using-declarations—e.g., using std::strlen—contribute to namespace pollution on a more minor level.

C++ Maxim

Never write namespace using-declarations in header files.

Background

Placing a using-directive or a using-declaration before a #include statement can introduce unintentional changes in semantics for the included file.

C++ Maxim

Within implementation files, never write a using-directive or a using-declaration before any #include directive(s).

References

See Item 59 of [Sutter2005a].

Background

With the introduction of namespaces, the old C-style standard library headers (e.g., stdio.h) have been deprecated.

C++ Maxim

Prefer the new C++ standard library headers (e.g., cstdio).

Namespace usage

C++ Maxim

Use namespaces wisely. If you write a class in some namespace N, be sure to also put all helper functions and operators into namespace N.

References

See the Interface Principle in Items 32 & 33 in [Sutter2000], and Item 57 of [Sutter2005a].

C++ Maxim

Use namespaces wisely. Keep types and functions in separate namespaces unless they are specifically intended to work together.

References

See Item 58 of [Sutter2005a].

Implicitly Provided Functionality

Background

The C/C++ languages provide certain functionality for every type or class, whether the author of the type wants it or not. This turns out to be a mixed blessing. Take for example, a programmer defined class in C++:


class Empty {};

It automatically receives the following implicit (compiler provided) functionality (when, but only if, the compiler requires it):


                Empty(void)             // A default constructor
                Empty(const Empty&)     // A copy constructor
                ~Empty(void)            // A destructor
Empty&          operator=(const Empty&) // A copy assignment operator
Empty*          operator&(void)         // Address-of operators
const Empty*    operator&(void) const
C++ Maxim

The correctness of compiler provided “implied” functions must always be evaluated and documented. (The most troublesome tend to be the copy constructor and operator=.)

General Maxim

Within a given access qualifier section (i.e., public, protected, private), these functions should occur in a consistent order so that clients can easily find them and determine whether they are explicitly defined, implicitly defined, or whether clients are explicitly prevented from invoking such behavior (and the reasons why; whether such functionality is perceived to not be needed or whether it cannot be provided with available resources, or whatever).

Our recommendation (admittedly a stylistic preference) for this order is as shown above, viz.: Constructors should be the first member functions within an access qualifier section. The default constructor, if any, should be listed as the first constructor. The copy constructor should be listed as the last constructor (to keep it closer to operator=, which it is similar to). The destructor should immediately follow the constructor list. operator= should immediately follow the destructor. The two operator& are rare and should follow operator=.

C++ Maxim

If a class provides a Swap() function, it should follow any explicit declarations of (or comments regarding) these potentially implicit functions. If a class provides a Clone() function, it should follow Swap().

References

This section is strongly influenced by Item 45 in [Meyers1998] and Items 52 & 53 of [Sutter2005a]. You are encouraged to read them for more details on this issue.

  1. Implicit default constructor
  2. Implicit copy constructor
  3. Implicit destructor
  4. Implicit operator=
  5. Implicit address-of operators
  6. Swap()
  7. Clone()
  8. Implicit type conversion

Implicit default constructor

Background

The compiler provides the implicit default constructor only when no other constructor is declared.

C++ Maxim

When relying on the implicit default constructor, failure to state, “The implicit default constructor is acceptable” leaves implicit something clients need to know has been explicitly considered.

Ramifications

Such a statement means this type has no constructors other than the implicit default constructor, and the implicit default constructor works correctly. This is hardly ever the case for complex classes. The presence of other constructors obviates the need to make a statement concerning the default constructor.

Implicit copy constructor

Background

Passing an object by value means, by definition, call the copy constructor. The implicit copy constructor provides memberwise copying.

The behavior supplied by the implicit copy constructor is clearly incorrect for some types. For example, relying on the implicit copy constructor for types that contain data members that are pointers to free store objects is a common but subtle source of bugs.

General Maxim

Whenever you modify the number or types of data members of a class, you must always consider (or reconsider) how the class copies itself. One of three things must be done:

  1. Provide a public or protected copy constructor that properly copies the data members of the class.
  2. Declare (but do not define) a copy constructor within a private access qualifier section, thereby preventing the objects of the class from being copied by clients.
  3. Provide a comment that states: “The implicit copy constructor is acceptable.”

Classes that do not provide one of the above three statements, must be considered incomplete, if not incorrect.

Ramifications

Often times the person performing a copy is a client who quite naturally assumes that, “if the compiler lets me do it, it must be okay to do.” As the designer of a class, you are the one who knows whether the data members of that type can safely be copied or not. Make explicit your determination by following this maxim. At the same time, you will prevent subtle bugs caused by clients using your types in ways you failed to prevent.

Background

A POD is a Plain Old Data type; i.e., one with C-compatible layout.

General Maxim

Do not memcpy or memcmp non-PODs.

Ramifications

Doing so violates the C++ type system.

References

See Item 96 of [Sutter2005a].

Implicit destructor

Background

The implicitly provided destructor is always nonvirtual.

C++ Maxim

Failure to provide an explicit destructor means the class cannot be a base class.

C++ Maxim

Failure to state, “The nonvirtual, implicit destructor is acceptable” leaves implicit something clients need to know has been explicitly considered.

Ramifications

Clients see explicit evidence concerning whether the class is designed to be a base class or not.

Implicit operator=

Background

The implicit operator= provides memberwise copy assignment.

The behavior supplied by the implicit operator= is clearly incorrect for some types. For example, relying on the implicit operator= for types that contain data members that are pointers to free store objects is a common but subtle source of bugs.

C++ Maxim

Whenever you modify the number or types of data members of a class, you must always consider (or reconsider) how the class assigns itself. One of three things must be done:

  1. Provide a public or protected operator= that properly assigns the data members of the class (see also next maxim).
  2. Declare (but do not define) an operator= within a private access qualifier section, thereby preventing the objects of the class from being assigned by clients.
  3. Provide a comment that states: “The implicit operator= is acceptable.”

Classes that do not provide one of the above three statements, must be considered incomplete, if not incorrect.

Ramifications

Often times the person performing an assignment is a client who quite naturally assumes that, “if the compiler lets me do it, it must be okay to do.” As the designer of a class, you are the one who knows whether the data members of that type can safely be assigned or not. Make explicit your determination by following this maxim. At the same time, you will prevent subtle bugs caused by clients using your types in ways you failed to prevent.

C++ Maxim

For the sake of exception safety (and ease of maintenance), operator= should usually be implemented in terms of the copy constructor. A reasonable operator= looks like this:


T&
operator=(const T& rtOp)
{
    T temp(rtOp);      // Does the work?
    this->Swap(temp);  // See section on Swap()
    return *this;
}
Ramifications

When provided as above, operator= carries the Strong guarantee as long as the copy constructor does. The above also obviates the need to check for assignment to self. For particularly complex objects, or those where assignment to self is likely, such a check may be performed; but in such a case, it is an efficiency tradeoff not a functional accuracy consideration.

References

See Item 55 of [Sutter2005a].

Implicit address-of operators

Background

These are rare and provide no dangerous side effects, so there is no need to provide any kind of comment when the implicit address-of operators are acceptable.

Swap()

Background

The Swap() function is not implicit, however…

C++ Maxim

In order to implement strongly exception safe copy assignment, the non-throwing Swap() member function is virtually indispensable. It should be declared like this:


void Swap(T& other); // throw()
References

See Item 51 & 56 of [Sutter2005a].

Clone()

Background

The Clone() function is not implicit, however…

C++ Maxim

Avoid slicing; in base classes, consider Clone() instead of copying.

References

See Item 54 of [Sutter2005a].

Implicit type conversion

Background

Constructors that are not declared explicit and that require only one argument to be called—they may be constructors that take multiple parameters with all except the first having default arguments—may be used by a compiler to implicitly convert an object of one type to a new, temporary object of another type.

C++ Maxim

Avoid implicit conversions by declaring all constructors explicit.

References

See Item 40 of [Sutter2005a].

Overloaded Operators

  1. Making an operator a member or nonmember?
  2. Preserve natural semantics for overloaded operators
  3. Implement operator==() and operator<() (and other operators in terms of them)
  4. Implement operator op() in terms of operator op=()
  5. Always implement postincrement in terms of preincrement
  6. Stream operators should return a reference to the stream

Making an operator a member or nonmember?

C++ Maxim

When deciding whether an operator should be a member or a non-member, follow this guideline:

The C++ standard requires that operators =, (), [], and -> must be members,

and class-specific operators new, new[], delete, and delete[] must be static members.

For all other operator functions:

If the function is operator>> or operator<< for formatted stream I/O,

or if it needs type conversions on its leftmost argument,

or if it can be implemented using the class’s public interface alone:

Make it a nonmember (and friend if needed in the first two situations),

If the function needs to behave virtually:

Add a virtual member function to provide virtual behavior, and implement the nonmember in terms of the virtual member function;

otherwise:

Make it a member.

Ramifications

Improved encapsulation.

References

This entry is based on comments contained in Item 20 of [Sutter2000] (and the errata for that item) and Item 44 of [Sutter2005a].

Preserve natural semantics for overloaded operators

General Maxim

For value types “do as the ints do”; that is, follow the semantics of the built-in types.

References

See Item 26 of [Sutter2005a].

Implement operator==() and operator<() (and other operators in terms of them)

C++ Maxim

Implement operator!=() in terms of operator==().

Implement operator>() in terms of operator<().

Implement operator<=() in terms of operator<().

Implement operator>=() in terms of operator<().

Ramifications

This preserves the natural semantics between the operators and they are less likely to diverge during maintenance. They are also less likely to surprise clients.

Implement operator op() in terms of operator op=()

Background

Many operators op (e.g., +, *, |) have an op= equivalent (e.g., +=, *=, |=).

C++ Maxim

For any given class, if you supply a standalone version of operator op (e.g., operator+) always supply an assignment version of the same operator (e.g., operator+=) and implement the former in terms of the latter.

For Example

operator+ should be implemented in terms of operator+= thus:


T&
T::operator+=(const T& rtOp)
{
    // Do whatever it takes to perform +=
    return *this;
}

const T
T::operator+(const T& ltOp, const T& rtOp)
{
    T temp(ltOp);
    temp += rtOp;
    return temp;
}
Ramifications

This preserves the natural semantics between the two operators and they are less likely to diverge during maintenance. They are also less likely to surprise clients.

References

See Item 27 of [Sutter2005a].

Always implement postincrement in terms of preincrement

Background

Clients have expectations of certain operations, such as: postincrement and preincrement are essentially the same thing (with the return value being the only difference).

C++ Maxim

The postincrement operator for a class T should generally be implemented in terms of its preincrement operator. In other words, it should be as follows:


const T
T::operator++(int /*postincrement*/)
{
    T old(*this);
    ++*this;
    return old;
}
Ramifications

This preserves the natural semantics between the two operators and they are less likely to diverge during maintenance. They are also less likely to surprise clients.

Stream operators should return a reference to the stream

C++ Maxim

Always return ostream or istream references (respectively) from operator<< and operator>>.

Ramifications

This permits chaining; e.g., cout << a << b.

Error Handling and Exceptions

  1. Use exceptions to report errors
  2. Throw by value, catch by reference
  3. Exceptions across module boundaries
  4. Never allow an exception to escape a destructor or overloaded operator delete()
  5. Function try blocks

Use exceptions to report errors

Background

Errors are violations of preconditions, postconditions and invariants. Exceptions are superior to error codes for reporting errors because:

C++ Maxim

Prefer using exceptions over error codes to report errors. Use status codes when exceptions cannot be used or to report conditions that are not errors.

Exceptions

The sections on preconditions, postconditions and invariants each discuss when an error should be thrown as an exception and when it should simply be asserted.

References

See Item 72 of [Sutter2005a].

Throw by value, catch by reference

C++ Maxim

Throw exceptions by value and catch them by reference (usually to const).

C++ Maxim

When rethrowing an exception, prefer throw over throw e. (Consider our rethrow_() debugging macro.)

Ramifications

This avoids making extra copies and eliminates the potential for object slicing.

References

See Item 73 of [Sutter2005a].

Exceptions across module boundaries

C++ Maxim

Do not allow exceptions to propagate across module boundaries. At a minimum your application must have catch-all catch(...) seals in these places:

  1. Around the contents of main()
  2. Around module interface boundaries
  3. Around callbacks from code you do not control
  4. Around thread boundaries
  5. Inside destructors
References

See Item 62 of [Sutter2005a].

Never allow an exception to escape a destructor or overloaded operator delete()

Background

Destructors and deallocation functions can be called during stack unwinding as the result of a thrown exception. Throwing a second exception will cause the program to terminate. Therefore, all destructors and deallocation functions should provide the No-fail guarantee.

General Maxim

Implement destructor and deallocation functions as though they had an exception specification of throw(). If a destructor calls a function that has the potential of throwing, always wrap the call in a try/catch block that prevents the exception from escaping.

Ramifications

Clients can throw exceptions and expect that your type will not cause an undesirable termination of the program.

References

See Item 51 of [Sutter2005a].

Function try blocks

Background

A function try block is not the same thing as a normal try block. (See Items 17 & 18 of [Sutter2002].)

C++ Maxim

Constructor function try block handlers are really only good for translating an exception. Destructor function try blocks have no practical use because destructors should never emit an exception. All other function try blocks have little or no practical use.

C++ Maxim

If you make use of unmanaged resource acquisition—which should really be avoided—in a constructor, make sure it is in the body, never within its initializer list. Always clean up such unmanaged resource acquisition in local try block handlers within the constructor or destructor body, never in constructor or destructor function try block handlers.

C++’s Standard Template Library

  1. Prefer a checked STL implementation
  2. Choosing containers
  3. Store only values in containers
  4. Prefer push_back
  5. Prefer range operations
  6. Shrinking capacity and erasing elements
  7. Prefer algorithm calls over loops
  8. Choose the right STL search algorithm
  9. Choose the right STL sort algorithm
  10. Avoid std::rel_op operators

Prefer a checked STL implementation

C++ Maxim

Prefer a checked STL implementation.

Ramifications

Some STL usage mistakes are distressingly common and difficult to detect; however, a checked STL implementation can help detect them.

References

See Item 83 of [Sutter2005a].

Choosing containers

C++ Maxim

Choose an appropriate container when you have a compelling reason; otherwise use vector by default.

C++ Maxim

Use vector (and string::c_str) to exchange data with non-C++ APIs.

References

See Item 76 & 78 of [Sutter2005a].

Store only values in containers

C++ Maxim

Store objects of value in containers. Containers assume they contain value-like types, including value types (held directly), smart pointers, and iterators.

References

See Item 79 of [Sutter2005a].

Prefer push_back

C++ Maxim

Prefer push_back over other ways of expanding a sequence.

References

See Item 80 of [Sutter2005a].

Prefer range operations

C++ Maxim

Prefer range operations over single-element operations.

References

See Item 81 of [Sutter2005a].

Shrinking capacity and erasing elements

C++ Maxim

Use the accepted idioms to really shrink capacity and really erase elements.

References

See Item 82 of [Sutter2005a].

Prefer algorithm calls over loops

C++ Maxim

Prefer using algorithm calls over handwritten loops.

Ramifications

Algorithm calls can be more expressive and maintainable, less error-prone and as efficient as handwritten loops.

C++ Maxim

Consider trying the Boost Lambda library, which automates the task of writing function objects.

References

See Item 84 of [Sutter2005a].

Choose the right STL search algorithm

C++ Maxim

Choose an appropriate search algorithm using the inside cover of [Meyers2001].

References

See Item 85 of [Sutter2005a].

Choose the right STL sort algorithm

C++ Maxim

Choose an appropriate sort algorithm.

References

See Item 86 of [Sutter2005a].

Avoid std::rel_op operators

Background

The std::rel_op operators (in utility) are of limited usefulness. The std::rel_op operators break the iterator operators (and all similar operators). (They were apparently a mistake.) If you insist on using them, you will have trouble.

C++ Maxim

Avoid using the std::rel_op operators.

Ease of Maintenance

  1. Expectations of a Junior Programmer
  2. Orthogonal Modules
  3. Project Glossary
  4. Self-Documenting
  5. Easily Understood by Others
  6. Information Hiding
  7. Identifiers
  8. Passing Information
  9. Commenting Inevitable Complex Areas

Expectations of a Junior Programmer

General Maxim

It is reasonable to assume that a junior programmer understands every feature of the used programming language. If they do not, they can always look it up or ask someone.

Ramifications

Any programmer that does not investigate language features they do not understand should be dismissed. If they are unwilling to take the effort to learn about the effective use of the tools of their trade, they will never become an effective craftsman.

Orthogonal Modules

General Maxim

Design modules so that they are as orthogonal (independent) of each other as possible.

  1. Prefer cohesion
  2. Prefer extensibility
  3. Public functions nonvirtual
  4. Compiler-firewall (Pimpl) idiom
  5. Unit testing

Prefer cohesion

General Maxim

Always endeavor to give each entity—variable, function, class, namespace, module, library—a single, well defined responsibility. Prefer encapsulation. Separate concerns.

Ramifications

Modules, classes, and functions can be more easily reused without dragging along otherwise unnecessary baggage.

References

See Item 5 of [Sutter2005a].

Prefer extensibility

General Maxim

Experienced programmers understand how to strike the right balance between writing special-purpose code that solves only the immediate problem and writing a grandiose general framework to solve what should be a simple problem.

References

See Item 7 of [Sutter2005a].

Public functions nonvirtual

Background

A public virtual function inherently has two different and competing responsibilities, aimed at two different and competing audiences:

General Maxim

In base classes with high cost of change (particularly those in libraries and frameworks): Prefer to make public functions nonvirtual. Prefer to make virtual functions private (or protected if derived classes need to be able to call the base versions). This is the Nonvirtual Interface pattern.

Ramifications

By separating public functions from virtual functions:

Exception

Base class destructors.

References

See Item 39 of [Sutter2005a].

Compiler-firewall (Pimpl) idiom

General Maxim

For widely used classes, prefer to use the compiler-firewall idiom (aka Pimpl Idiom) to hide implementation details. Use an opaque pointer (a pointer to a declared, but as yet undefined, class) declared similar to struct XxxxImpl; XxxxImpl* pimpl; to store private members (including both state variables and member functions).

Ramifications

Changes can be made to implementation details without forcing a recompilation of client code. This also reduces code interdependencies.

References

See Item 43 of [Sutter2005a].

Unit testing

General Maxim

Provide unit test applications for every non-user-interface class and/or module. A Unit test suite should have 100% path coverage.

Exception

Although the goal of unit testing is 100% path coverage, on rare occasions no input to a program can cause a particular path to be executed. In such an instance, an explanatory assertion on the unexecuted line should make this clear (so maintainers later running the unit test can learn this line is not expected to be executed).

Ramifications

Clients can rely on the assumption that your class and/or module has been thoroughly tested.

Project Glossary

General Maxim

For projects used in highly vertical domains, deliverables should contain a project (or class) glossary for domain specific terms.

Ramifications

Client programmers can lookup and properly understand the terms used in the project (or class).

Self-Documenting

General Maxim

Deliverables should be as self-documenting as possible. The careful choosing of descriptive, unambiguous identifiers can greatly enhance self-documentation.

Ramifications

Extraneous documentation is notorious for being out of date with what is actually happening. If source code is self-documented, as the code changes, so does the documentation.

  1. Be explicit
  2. Avoid “magic numbers”
  3. new & delete are static

Be explicit

General Maxim

Instead of leaving things implicit, express them explicitly.

Exceptions

Which files must be #included for a given source file to compile.

Ramifications

Although being explicit may take a few extra keystrokes, it reduces the potential for confusion, which is far more important.

  1. Type checking
  2. C++ style casts
  3. Use initializer lists
  4. -init methods follow a pattern
  5. Assert internal assumptions and invariants
  6. Boolean conditions
  7. Prefer built-in bool type

Type checking

General Maxim

Source code should take advantage of a language’s built in type checking wherever feasible.

Ramifications

If a passed in object is compiler acceptable, both client and implementation know what can be done because the object is of an explicit type.

C++ style casts

C++ Maxim

Prefer the C++ style casts [e.g., static_cast<T>(x) or const_cast<T>(x), et.al.] over C style casts [e.g., (T)x]. However, prefer using mutable over const_cast<>.

Ramifications

C++ style casts make the type of a cast more explicit, and it is easier to search for casts of a particular type. (Often times a compiler warning can be enabled to flag uses of the older, C style casts.)

References

See Item 95 of [Sutter2005a].

Background

reinterpret_cast is not guaranteed to reinterpret the bits of one type as those of another (or guaranteed to do anything else in particular).

C++ Maxim

Avoid using reinterpret_cast.

Exceptions

If you must cast between unrelated pointer types, prefer casting via void* instead of using reinterpret_cast directly.

For Example

Instead of:


T1* p1 = ...
T2* p2 = reinterpret_cast<t2*>(p1);

write


T1* p1 = ...
void* pV = p1;
T2* p2 = static_cast<t2*>(pV);
References

See Item 92 of [Sutter2005a].

Background

Pointers to dynamic objects do not static_cast. Prefer to re-factor, redesign or use dynamic_cast.

C++ Maxim

Avoid using static_cast on pointers.

Q: How does this maxim square with the previous one?

Ramifications

Although dynamic_cast is slightly less efficient, it also detects illegal casting (and this minor efficiency difference can be considered premature optimization).

Exceptions

Q: Can dynamic_cast be called on objects of types that do not have a virtual function table?

References

See Item 93 of [Sutter2005a].

Use initializer lists

Background

C++ does not force a programmer to provide an initializer list for a constructor. Neither does it force us to put an initializer list in the order in which the members will actually be initialized.

C++ Maxim

Every constructor should contain an initializer list that contains initializers for, at a minimum:

  1. Every base class (in the order from which they are derived); then
  2. Every data member that is not a built-in type (in the order that they are declared).

Initializers for built-in types are also recommended, however, unlike the above, there is no duplication of effort if these are put off until the body of the constructor.

Exceptions

Unmanaged resource acquisition in a constructor.

Ramifications

The C++ language mandates that the compiler must construct non-built-in types in the initializer list. A programmer’s failure to explicitly construct an object of a non-built-in type in an initializer list causes the compiler to implicitly construct the object (using the default constructor). Then, when you later assign a value to it in the constructor’s body, the object’s operator= is called. Not only does this leave the phases of object construction implicit, it is also a needless, inefficient duplication of effort.

Regardless of the order initializers are shown in an initializer list, the language always initializes base classes and members in the order they are declared within the class definition. Because initializers can sometimes depend upon each other, failing to display the initializer list in the declared order often leads to subtle bugs. Displaying initializers in the correct order also allows those viewing the list to view the dependencies, thereby reducing the opportunity for unintentional errors.

This is also a requirement for some class template constructors, especially when the templatized parameter is a built-in type. (See Section 5.5 of [Vandevoorde2003] for more on this.)

References

See Item 47 & 48 of [Sutter2005a].

-init methods follow a pattern

Objective-C Maxim

The designated initializer of a class in Objective-C follows a pattern:


- (id)initWithObject:(SomeType*)object
{
    NSParameterAssert(object != nil);
    NSAssert([object isValid], @"Expected validated object");
   
    self = [super init]; // Send message to any appropriate initializer of the base class.
    if (self != nil)
    {
        // Members asserted, assigned, or alloced & inited (in declaration order), thereby documenting that their initial state has been properly considered (and not presumed).
        NSAssert(mNumThings == 0, @"Expected init to zero");
        NSAssert(NSEqualRects(mMemberRect, NSZeroRect), @"Expected init to zero");
        NSAssert(mMemberObj1 == nil, @"Expected init to zero");
        mMemberObj2 = object;
        mMemberObj3 = [[SomeType alloc] init];
        mMemberObj4 = [[SomeOtherType alloc] init];
        
        // If any member allocation failed... (we verify in alloced order)
        if (mMemberObj3 == nil
                || mMemberObj4 == nil)
        {
            [self autorelease]; // -dealloc will release already alloced members
            self = nil;
        }
    }
    // If nothing failed...
    if (self != nil)
    {
        // Here you do those things that cannot fail. For example:
        // - registering for notifications.
    }
    NSParameterAssert(self == nil || self != nil);
    // The above line is not a nonsensical statement as some might imagine. It is an executable comment that states this method may return nil or not, and thereby making plain that it is the responsibility of the caller to ensure what is returned is non-nil before using it.
    return self;
}
Objective-C Maxim

Non-designated initializers of a class call the designated initializer, either directly or indirectly.


- (id)initNonDesignatedInitializer
{
    // Get arguments required by DesignatedInitializer
    SomeType* obj = [SomeType defaultObject];

    // Use designated initializer to init the object
    self = [self initWithObject:obj];
    
    NSAssert(self == nil || self != nil);
    return self;
}
Ramifications

When the above two maixims are followed:

Assert internal assumptions and invariants

Background

“…the problem of proving that a given algorithm is valid evidently consists mostly of inventing the right assertions to put in the flow chart [or code].…The above principle for proving algorithms has another aspect which is perhaps even more important: it mirrors the way we “understand” an algorithm.…It is the contention of the author that we really understand why an algorithm is valid only when we reach the point that our minds have implicitly filled in all the assertions…. This point of view has important psychological consequences for the proper communication of algorithms from one man to another (or from one man to himself, when he looks over his own algorithms several months later)….”

[Knuth1973], p. 16

General Maxim

Liberally use assert()—or an equivalent, like NSAssert()—to document assumptions internal to a module (i.e., where the caller and callee are maintained by the same person or team). Every assertion should be thought of as a comment that happens to be executed (while debugging) to verify that its statement is, in fact, true.

For Example

Documenting preconditions, postconditions and invariants.

General Maxim

Instead of providing a comment saying something like: “At this point X should be true”, provide the comment in the form of an assertion that fires when X is not true.

Ramifications

There are errors that you know might happen. For everything else that should not (and it is a programmer’s fault if it does) there is assert() (or an equivalent). The debugging version of the program will catch those instances when, quite unexpectedly, the assumption is incorrect.

General Maxim

Never write expressions with side effects in assert() statements (or other debugging macros) whose contents will vanish from some configurations.

C/C++ Maxim

Prefer assert(!"informational message") over assert(false) and consider adding && "Informational message" to more complex assertions. (Also consider our proclaim_() and avow_() debugging macros and their printf-like counterparts as well as our CompileTimeAssert_() debugging macro.)

References

See Item 68 of [Sutter2005a].

Boolean conditions

Background

Certain statements in all languages evaluate Boolean conditions (e.g., if(), while(), etc.).

General Maxim

Phrase statements that evaluate Boolean conditions as Boolean expressions.

For Example

Prefer if (pointer != 0) over an implied comparison against zero, such as if (pointer).

Ramifications

By doing so, the condition is explicit and reads more like English. With a moderately proficient compiler there is no performance penalty.

Prefer built-in bool type

Background

C++’s built-in bool type can do some things that homegrown Boolean types (whether based on type char, int or whatever) cannot. That is why it was added to the C++ language.

General Maxim

Prefer bool (or in Objective-C: BOOL) over homegrown Boolean types.

Avoid “magic numbers”

General Maxim

Numeric literals (other than the ubiquitous 1 or 0) should only appear in static constants or enumerator declarations; i.e., avoid “magic numbers.”

Ramifications

This centralizes specification of such numbers and eliminates the possibility of multiple definitions for what should amount to the same number. Names add information. Even if you think your number will be used in only one place, magic numbers by themselves are not self-documenting, nor will others easily understand their raison d’être.

References

See Item 17 of [Sutter2005a].

new & delete are static

C++ Maxim

Always explicitly declare operator new() and operator delete() as static functions.

Ramifications

Regardless of how they are declared, the compiler will never make them non-static member functions.

Easily Understood by Others

General Maxim

Others should easily understand deliverables (i.e., they should be intuitive to a junior programmer).

Ramifications

Understanding leads to correctness.

References

See Item 6 of [Sutter2005a].

  1. Prefer readability
  2. Prefer minimal classes
  3. Avoid compound statements
  4. Uses of typdef
  5. Avoid using macros
  6. Comment preprocessor conditionals

Prefer readability

General Maxim

Prefer readability. Avoid writing terse code (i.e., code that is brief, but difficult to understand and maintain). Eschew superfluous obfuscation.

Prefer minimal classes

General Maxim

Small classes are easier to write, get right, test, and use. They are also more likely to be usable in a variety of situations. Prefer small classes that embody simple concepts instead of kitchen sink classes that try to implement many and/or complex concepts.

References

See Item 33 of [Sutter2005a].

Avoid compound statements

General Maxim

Try to avoid assignments inside Boolean conditions.

For Example

Instead of writing this:


if ((foo = (char*) malloc(sizeof(char*)) == 0)
{
    fatal (“virtual memory exhausted”);
}

write this:


foo = (char*) malloc(sizeof(char*));
if (foo == 0)
{
    fatal (“virtual memory exhausted”);
}
Ramifications

There is no loss of efficiency and it is easier to read. It is also easier to see what is happening when stepping through the code with a debugger.

Objective-C Maxim

Even though the language allows it—one might even say encourages it—, avoid sending more than one message on a line.

For Example

Instead of this:


	NSWindow* window = [[self windowController] window];

Prefer this:


	NSWindowController* windowController = [self windowController];
	
	NSAssert(windowController != nil, @"Expected valid windowController");
	NSWindow* window = [windowController window];
Exceptions

Allocation and initialization, as well as an autorelease, often occur together.

For Example

	NSWindow* window = [[[NSWindow alloc] init] autorelease];
Ramifications

The second form not only validates the returned windowController before using it, more importantly, it is more debugger friendly. When stepping through code, one thing is accomplished per line, and it is therefore easier to step into the appropriate method call. There is no advantage to the more compact form, except its compactness, which is less important than ease of debugging.

Uses of typdef

General Maxim

Use typedefs to improve:

Avoid using macros

General Maxim

Avoid preprocessor macros created with #define.

Ramifications

Macros usually make code more difficult to understand and, therefore, more troublesome to maintain.

Exceptions

Exceptions to this maxim are:

Comment preprocessor conditionals

General Maxim

Every #endif should have a comment—except in the case of short conditionals (just a few lines) that are not nested—and the comment should state the condition of the conditional that is ending, including its sense. Every #else should also have a comment describing both the condition and sense of the code that follows.

For Example

#ifdef qFoo
    //...
#else /* ndef qFoo */
    //...
#endif /* ndef qFoo */

#ifdef qBar
    //...
#endif /* def qBar */

Information Hiding

General Maxim

Do not expose internal information from an entity that provides an abstraction.

Ramifications

This minimizes dependancies, localizes changes and strengthens invariants.

References

See Items 11 & 42 of [Sutter2005a].

General Maxim

Make data members private, except in behaviorless aggregates (C-style structs).

References

See Item 41 of [Sutter2005a].

Identifiers

  1. Descriptive, unambiguous identifiers
  2. Identifiers as abstract as possible
  3. Pointers are unique

Descriptive, unambiguous identifiers

General Maxim

Identifiers should be descriptive, of unambiguous meaning and self-documenting.

Ramifications

An English dictionary is an indispensable tool when it comes to providing descriptive identifiers. (It is also handy if you encounter an identifier whose meaning is even slightly unclear. ;-)

  1. Avoid leading underscores and double underscores in identifiers

Avoid leading underscores and double underscores in identifiers

Background

The C++ standard reserves some leading-underscore and double underscore identifiers for the implementation of the language. The actual rules for this are complex and hard to remember, therefore…

C++ Maxim

In identifiers, avoid leading underscores and double underscores entirely.

Ramifications

Avoids accidental and unintentional name overloading and hiding.

Identifiers as abstract as possible

General Maxim

Have every identifier representative of the highest level of abstraction commensurate with its context.

Ramifications

Understanding leads to correctness.

References

See also Item 63 & 67 of [Sutter2005a].

Pointers are unique

Background

Pointers are a unique form of object identifier. They can either refer to a valid object or they can refer to no object. (Unfortunately, in poorly written programs, they can also refer to a random area of memory.) Other forms of object identifiers (such as those identifying values, or C++ references) must always refer to their object, i.e., they always refer to one particular place in memory (or a register) reserved for their particular object.

  1. Invalid pointers

Invalid pointers

General Maxim

Pointers should always either:

Ramifications

A pointer that points to random memory is always an invalid pointer; i.e., a bug waiting to happen. In the same way that objects of other types should always be initialized to a valid state, a pointer should always be initialized with the address of a valid object or with zero (aka NULL or nil).

Passing Information

Background

Information is passed between different pieces of code using one of five mechanisms:

  1. Global objects
  2. Class static variables
  3. Class member variables
  4. Argument(s) passed to functions
  5. The function return value

Global objects

General Maxim

Avoid global objects. As a client, predicting side effects is virtually impossible.

Ramifications

Predictability leads to correctness; unpredictability leads to errors.

References

See Item 10 of [Sutter2005a].

Class static variables

Treat similar to global objects, although class static variables are to be preferred over global objects because of their more limited scope. In C++, prefer to use anonymous namespaces to create file-scope variables.

Class member variables

General Maxim

Class member variables should really only be used for maintaining an object’s state; they should not really be used to pass information to a function that will be called later. Of course, there are rare situations where this cannot be avoided.

Ramifications

Objects have a minimal footprint.

Arguments passed to functions

Background

Within this section, the terms In, Out & IO have special, mutually exclusive meanings. They are:

In

A parameter delineated In is a parameter whose argument will only be examined by a function; the function will never modify the passed in argument. Think of In as a “read-only” designation for the passed in argument.

Out

A parameter delineated Out is a parameter whose argument may be modified by a function; however, the function will never examine the passed in argument. Think of Out as a “write only” designation for the passed in argument.

IO

A parameter delineated IO is a parameter whose argument may be examined by a function and may also be modified by a function. Think of IO as a “read/write” designation for the passed in argument.

Background

In C++, a function declaration’s parameter list determines how an argument is passed in and it therefore implicitly informs clients of how their passed in argument(s) will be used and/or modified.

C++ Maxim

When an argument is passed according to the method shown in the left column of the following table (an example of which is shown in the second column), clients can rely on the meaning shown in the third column.

Passed Example In, Out or IO Notes
By non-const “this” pointer MemberFn(/*…*/); Can mean In, Out or IO, but should mean Out or IO Should never mean In. Redeclare to ‘by const “this” pointer’ when you mean In
By const “this” pointer MemberFn(/*…*/) const; Always means In
By non-const value Fn(char); Always means In Typically used with built-in types
By const value Fn(const char); Always means In Rarely used in practice
By reference to non-const Fn(char&); Can mean In, Out or IO Should be very rare.
By reference to const Fn(const char&); Always means In
By pointer to non-const Fn(char*); Can mean In, Out or IO Preferred for Out and IO.
By pointer to const Fn(const char*); Always means In Prefer by reference to const unless the pointer is optional
Ramifications

Predictability and understanding leads to correctness.

References

See also Item 25 of [Sutter2005a].

  1. By “this” argument
  2. By value arguments
  3. Elide const pass-by-value in declarations
  4. By reference arguments
  5. By pointer arguments

By “this” argument

Background

Non-static class member functions have an implied “this” parameter. Static member functions do not.

General Maxim

Member functions that do not refer to member variables should be declared static.

For Example

static void Fn(void);
General Maxim

If clients will not need to access such a function—i.e., if it is used by a class’s implementation only—such a function should not be declared within the class definition; instead make it a free standing function in an unnamed namespace in the implementation file. (This keeps implementation details out of a class’s interface.)

C++ Maxim

Member functions that only examine (i.e., read) member variables should be declared const.

For Example

void Fn(void) const;
C++ Maxim

Member functions that modify member variables should be declared non-const.

For Example

void Fn(void);

By value arguments

C++ Maxim

Passing an argument “By value” always means In.

Background

Passing an object by value means, by definition, call the copy constructor.

C++ Maxim

Only built-in types (and very small user defined types) should be passed into functions by value. Prefer passing “By reference to const” for user defined types.

Elide const pass-by-value in declarations

C++ Maxim

Avoid const pass-by-value parameters in function declarations. (However, making the parameter const in the same function’s definition—if it will not be modified—may be useful.)

Ramifications

There is no benefit to declaring a parameter passed by value as const. In fact, to the compiler, the function’s signature is the same whether you include const before the parameter or not, which is why you can define it differently than it is declared.

By reference arguments

C++ Maxim

Passing an argument “By reference to const” always means In.

Background

It is an established C/C++ industry standard (cf. section 5.5 of [Stroustrup1997]) that passing a value by pointer implies that the value may be changed; therefore:

C++ Maxim

Function writers should only very rarely declare parameters as pass “By reference to non-const”.

Ramifications

Why? The calling syntax for a function that takes an argument “By reference” is identical to the calling syntax for a function that takes an argument “By value.” Therefore, receiving an argument “By reference to non-const” allows a function writer to modify the argument without supplying a hint, at the call location, that such a modification might take place.

An additional reason is that a reference to non-const cannot be bound to a temporary object; therefore functions that take their parameters by reference to non-const cannot accept explicit constructor invocations or temporaries resulting from expressions.

Exception

The only known exceptions to this practice are operator overloading such as operator<< and operator>> for formatted stream activities. This does not tend to be a problem because everyone intuitively knows that a stream is being changed by these calls; the implied meaning of “cout << 5” is write to (and therefore change) the output stream.

In those rare instances where arguments are passed “By reference to non-const,” good engineering practice dictates that function writers make plain at the function’s declaration (via comments or identifier naming conventions) exactly how the object being referred to will be used (i.e., Out or IO).

By pointer arguments

C++ Maxim

Because non-const pointers can be used for In, Out or IO, good engineering practice dictates that function writers make plain at a function’s declaration (via comments or identifier naming conventions) how the object being pointed to will be used.

Background

It is an established C/C++ industry standard (cf. section 5.5 of [Stroustrup1997]) that passing a value by pointer implies that the value may be changed; therefore:

C++ Maxim

Passing arguments by pointer to const is discouraged (although when it happens, it always means In).

General Maxim

Because pointers are unique, good engineering practice dictates that function writers make plain at a function’s declaration (via type) ownership issues and (via comments or identifier naming conventions) whether a pointer parameter is required or optional (i.e., whether the function accepts and properly works with a pointer value of zero).

The function return value

Background

In C++, functions can directly return an object. (Of course, objects can also be indirectly returned via a function’s parameter list.)

  1. Return by value
  2. Return by reference
  3. Return by pointer

Return by value

Background

Returning an object by value means, by definition, call the copy constructor.

C++ Maxim

Copy construction for non-built-in types is typically expensive so returning an object by value should be avoided, if possible, unless the object is of one of the built-in types (which are copied quickly).

Background

Some functions have no choice but to return an object by value (e.g., operator[]).

C++ Maxim

If returning an object by value cannot be avoided, attempt to structure the implementation so that the compiler can perform the “return value optimization.”

C++ Maxim

When using return-by-value for non-built-in types, prefer returning a const value.

Ramifications

This assists client code by making sure the compiler emits an error if the caller tries to modify the temporary.

Return by reference

C++ Maxim

Returning an object by reference is the preferred means by which an object can be directly returned from a function.

Background

Local automatic objects have their destructor called when they pass out of scope.

General Maxim

Attempting to refer to local automatic objects (whether by pointer or by reference) after they have passed out of scope is always an error.

Return by pointer

General Maxim

Because pointers are unique, good engineering practice dictates that functions should only return objects by pointer when the function may not have an object to return. In other words, clients must always determine whether a returned pointer is non-nil.

Commenting Inevitable Complex Areas

General Maxim

Deliverables should be well commented in those inevitable complex areas.

Ramifications

Understanding leads to correctness.

Optimization and Efficiency

Write for efficiency only when and where necessary.

  1. Common Information Easily Located
  2. Duplication of Information
  3. Optimizing Compile Times
  4. Optimizing Object Code
  5. Final Size

Common Information Easily Located

Ramifications

The quicker we find things, the more time we can spend on correctness, completeness and runtime efficiency.

  1. Source code files
  2. Within source code files
  3. Non-local declarations

Source code files

General Maxim

Source code files should be arranged so common pieces of information are easily located.

Within source code files

Background

In English we read left to right and top to bottom. Speakers of English tend to look for important information at the top or beginning of a file.

General Maxim

Items of more general interest should be placed towards the top of a file.

Non-local declarations

General Maxim

Variables whose declarations may not be readily visible to a human reader at their point of use should contain a hint as to their declaration’s location.

For Example

Prefixing ‘g’ to global variables, ‘s’ to class static variables or ‘m’ to member variables gives a hint to clients of how to go about finding their declarations.

Duplication of Information

General Maxim

Deliverables should avoid duplication of information.

Ramifications

With identical information in more than one place, it is easy to get the information in these locations out of synchronization. When that happens, clients cannot know which is the correct information.

Optimizing Compile Times

General Maxim

Source code should compile efficiently (both in actual compile time and when considering the effort required to “put the pieces together”).

Ramifications

When even minor corrections do not take an inordinate amount of time to build, corrections are more likely to be made instead of being “put off.”

  1. #pragma once
  2. Prefer forward declarations
  3. Avoid #include <iostream>
  4. #Include general files last

#pragma once

C++ Maxim

Just inside a .h file’s #include guards, at the top, provide these three lines:


#if defined(PRAGMA_ONCE) && PRAGMA_ONCE
    #pragma once
#endif
Ramifications

If a compiler supports it, #pragma once is faster than #include guards because the file does not have to be loaded and parsed each time in order to prevent multiple inclusions.

Prefer forward declarations

Background

Compile times are greatly influenced by how compilers determine those files affected by any given change. Making changes to any .h file forces the compiler to recompile every file that #includes (or #imports) the changed .h file, either directly or indirectly.

General Maxim

Within .h files, do not #include (or #import) a header when a forward declaration will suffice.

Ramifications

Because interface files contain documentation on how their contents are used, we want minor (otherwise insignificant) changes to documentation to only force recompilation of as few files as necessary. This maxim can significantly reduce compile times resulting from a modified .h file.

Avoid #include <iostream>

Background

Because of the C++ standard’s allowance for various implementation defined dependencies and enhancements, client programmers are not allowed to forward declare anything that lives in namespace std. However, the makers of the C++ standard understood the value of preferring forward declarations, so they mandated that library providers supply the file iosfwd.

C++ Maxim

Whenever a forward declaration of one of the stream classes will suffice, #include <iosfwd>. iostream will only need to be included in order to access the actual standard streams (e.g., cout).

#Include general files last

Background

General files tend to have a broader clientele than files covering a more specific, limited topic. The more “common” or general a file is, the more likely it is to have already been included by some other file.

General Maxim

By deferring inclusion of more general files until after the more specific, the more general files may not even need to be explicitly included.

Ramifications

One of the more specific files may have already included an otherwise necessary general file. (This is one exception to the “be explicit” maxim. Explicitly including every file of a #include hierarchy would be a significant maintenance hassle.)

General Maxim

To make a #include list easier to decipher, break it into groups (by library, etc.) In order to reduce the size of #include lists, include less general (i.e., most unique) libraries first and more general libraries later. The order within a given library’s listing should be alphabetical to make it easier to find a given file.

For Example


#include “thisFile.h”  // Within an implementation file only:
                       // An implementation’s .h file should be first and in a group by itself
#include “SomeOtherProjectFile.h”  // Project specific files and files
#include “SomeProjectFile.h”       // not likely to be owned by a client are
                                   // next
#include <iosfwd>  // Readily available library files, such as
                   // Cocoa, the STL, PowerPlant, UniversalInterfaces, etc.
#include <memory>  // For example, the language's library is more general than
                   // the STL; place its files after the STLs'

Optimizing Object Code

Background

The items in this section should not be viewed as so-called “premature optimizations.” They are intended to engender habits that avoid “gratuitous pessimizations.”

  1. Constructors and destructors
  2. Minimize temporary objects
  3. Exception specifications
  4. Just-in-time declaration of objects
  5. Initialize objects as they are declared
  6. Avoid multiple return statements
  7. Precompute values that will not change
  8. Prefer preincrement over postincrement
  9. Prefer op= over op
  10. Prefer T v(a) over T v = a

Constructors and destructors

C++ Maxim

Avoid defining constructors and destructors inline; doing so contributes to code bloat.

Exception

Exceptions should only be made for constructors/destructors of classes that have:

Ramifications

Because of the complexity of constructors and destructors, and because compilers typically do much of their behind the scenes work as part of them, only in the rare exception mentioned above are constructors and destructors sufficiently small to profit through inlining.

Minimize temporary objects

C++ Maxim

Minimize the number of places where temporary objects are used.

Ramifications

Construction of temporary objects can wreak havoc on code’s efficiency.

  1. Avoid pass by value for non-built-in types
  2. Declare constructors explicit

Avoid pass by value for non-built-in types

C++ Maxim

Passing non-built-in types by value causes a temporary to be created, and so should be avoided if possible.

Declare constructors explicit

C++ Maxim

Constructors that can be called with one argument should be declared explicit to prevent unintentional construction of temporary objects due to implicit type conversions.

Exception

An exception should probably be made for the copy constructor. Although requiring explicit copy constructor calls would help prevent unintentional pass-by-value arguments, it also would make other useful constructs impossible.

Exception specifications

C++ Maxim

Avoid actually declaring exception specifications [e.g., void SomeFn(void) throw()]. It is still reasonable to provide such a guarantee to a client but document it as a comment instead of as an actual exception specification [e.g., void SomeFn(void) // throw()]. A non-inline function is the one place a “throws nothing” exception-specification may have some benefit with some compilers.

Ramifications

Exception specifications are notoriously inefficient with today’s compilers. (Consult Boost’s rationale for more on this topic.)

References

See Item 75 of [Sutter2005a].

Just-in-time declaration of objects

General Maxim

Objects should be declared at the point of their need, and not in advance of that.

Ramifications

Because variables are to be initialized when they are declared, to declare an object causes a constructor to be called or it to be alloced and initiallized. If that construction can be put off (or is not needed at all) for some code paths, then the resulting object code will run faster. Of course moving some declarations outside a loop can also be more efficient.

Also, even in languages like C and Objective-C where declaring an object does not cause immediate construction—although objects should still be initialized when they are declared—, delaying declarations allows an optimizing compiler to better overlap register usage.

References

See Item 18 of [Sutter2005a].

Initialize objects as they are declared

General Maxim

Initialize objects at their point of declaration instead of later.

Ramifications

Initialization at the point of declaration [e.g., Type var(initializer)] requires one constructor call; whereas, first declaring an object’s type and then initializing the object (e.g., Type var; var = initializer) results in a (default) construction call and an assignment call.

References

See Item 19 of [Sutter2005a].

Avoid multiple return statements

Background

One of the tenets of Structured Programming is having a Single Entry/Single Exit for every block. This is generally a good policy. It improves readability and should be adhered to when other engineering considerations do not override it.

For Example

With the following example code, some compilers may generate code that calls autoInstance’s destructor in two places, once for each return statement.


int SomeFn1(void)
{
    CSomeClass autoInstance(someInitializer);
    if (SomeTest())
    {
        return SomeOtherFn(someInstance);
    }
    SomeThirdFn();
    return 0;
}

However, with the following example code, the code calling autoInstance’s destructor is only generated once, immediately before the return.


int SomeFn2(void)
{
    int someInt = 0;
    CSomeClass autoInstance(someInitializer);
    if (SomeTest())
    {
        someInt = SomeOtherFn(someInstance);
    }
    SomeThirdFn();
    return someInt;
}
General Maxim

Avoid multiple return statements in functions, especially when they construct/destruct automatic objects. Of course, this must be balanced with the ability to capitalize on the return value optimization.

Precompute values that will not change

General Maxim

With regard to loops, precompute values that will not change as a result of the loop.

For Example

Calls to functions (such as the standard library function end()) take time and, if the function result is not going to change during a loop (because the container is not going to change), it can be called once instead of every pass through the loop.

Ramifications

Unnecessarily executing code negatively impacts the “snappiness” of an application.

Prefer preincrement over postincrement

Background

Postincrement has to do all the same work as preincrement, but in addition it also has to construct and return another object containing the original value.

General Maxim

Only use the postincrement operator (j++) if you are actually going to use the original value. Otherwise, prefer preincrement (++j).

Ramifications

Unnecessarily executing code negatively impacts the “snappiness” of an application. (Using postincrement as an idiomatic part of for statements (e.g. for ( ; ; x++)) is actually a remnant left over from the when C was first introduced. We have learned a few things since then. Break the habit and move on!)

Prefer op= over op

Background

See Implement operator op() in terms of operator op=().

General Maxim

Prefer writing a op= b; instead of a = a op b; (where op stands for any operator). Multiple op= statements are better than a chained compound statement of operators.

Ramifications

It is clearer and more efficient.

Prefer T v(a) over T v = a

C++ Maxim

Prefer using the form “T v(a);” instead of “T v = a;” where possible.

Ramifications

The former usually works wherever the latter works, and has other advantages—e.g., it can take multiple parameters. The latter may also force construction of an otherwise unnecessary temporary.

Ordering if blocks

Background

When both options of a given conditional require a coded response, how does a programmer decide the phrasing of the if statement? Does he use if (isTrue) or if (!isTrue)?

As a general rule, the first block of an if statement—the “is true” part—requires an assembly language statement to jump over the second part—the “is false” part. Conversely, execution of the second part typically “just falls off the end” and continues with the code following the second part.

General Maxim

Phrase the condition of an if statement such that the code responding to the slowest (or most frequent) condition is in the block following the else clause.

Ramifications

The fastest block (or rarely executed block) bears the burden of the extra assembly language instruction. (Granted, the execution time of the extra instruction is trivial, so there is no need to waste a lot of time with this. Don’t “prematurely optimize”, but this provides a reasonable rule for making an otherwise arbitrary decision.)

A classic example of this might be error checking. Only in rare instances will the error condition be encounterd. Therefore, even though the “normal” condition may have far less work to do than when an error occurs, because the “normal” condition is so frequent, it should still follow the else clause.

Coincidentally, following this general maxim also frequently places the smallest—by code size—block before the else making it easier to keep the condition in mind when you get to the second part of the if statement.

Final Size

General Maxim

It should go without saying that resulting footprints (RAM, as well as disk) should be minimal.


This is the end of our Reliable Engineering Practices document. As a reminder, now that you have laid a solid foundation, you may wish to consider reading our Style Guidelines.


© 1996–2010 Steve Sontag; all rights reserved.