SlideShare a Scribd company logo
T
HERE IS NO SHORTAGE OF TECHNICAL WISDOM
on how to develop clear and robust code, so
why is the paned (sic) expression on a pro-
grammer’s face—sifting through all of the
source windows trying to make sense of the encrypt-
ed code—such a common sight? There are compa-
nies whose development culture does not encourage
best practice: a penny-wise/pound-foolish approach.
However, there are many companies and developers
that want to push themselves to the state of the art,
but seem swamped and bemused by how much
state there really is to that art.
In the last column1
, I looked at the first six of a set
of 12 recommendations that can potentially make a
big difference to a body of C++ code. The short list
does not claim to cover all design practices appropriate
for a C++ system, but it does offer an easily memo-
rable and easily practised set of guidelines that offer
the greatest immediate return on investment—the most
bang for your buck, the most oomph for your euro,
the most kerrang for your quid.
The story so far:
1. Make roots fully abstract:
Classes should be either classifiers of behaviour or imple-
mentations of behaviour. Class hierarchies should not
muddle the roles of abstract and concrete classes. A
fully abstract class—no data and ordinary member
functions are public pure virtual only—offers the low-
est coupling and clearest root to a class hierarchy.
2. Inherit for subtyping only:
There is a strong temptation to use inheritance (in
the sense of public rather than non-public derivation)
as a convenience mechanism to build on parts of exist-
ing code. Inheritance for this purpose is a remarkably
blunt tool when compared to the more precise aggre-
gate-and-forward approach. Inheritance is most
effectively employed as a tool for classifying externally
visible behaviour (subtyping) than for arranging
internal representation (subclassing).
3. Include only what you need:
Redundant #includes and excessive dependencies
slow down both the programmer and the compiler.
Many uses of #include can be replaced by forward dec-
larations. Other heavy conceptual dependencies that
lead to physical dependencies can often be reduced
by delegation and templating. Some conceptual
dependencies are incorrectly conceived: for instance,
centralising error codes or application-related con-
stants in a single header.
4. Consider duplicate code a programming error:
Where the lack of an architectural vision can lead to
a loss of the big picture, duplicated code can cause
loss of the small picture.
5. Use self-contained objects:
Objects that expose their representation management
details, such as publicising data members or allow-
ing users to replace dynamically allocated objects that
they depend on, tend to be difficult to use correct-
ly. Objects should manage their own representa-
tion fully, preventing access or inappropriate
manipulation by users. Consider the difference in ease
of use between using std::string and a char * for
manipulating strings.
6. Make acquisition and release symmetric:
Acquisition of resources, such as dynamically allocated
memory, should be matched by a release at the same
scope, in the same object or through the same inter-
face. Transfer of object ownership should be avoid-
ed, so that object factory interfaces should support
both creation and disposal operations.
The remaining six recommendations continue
the theme of avoiding unnecessary centralisation
and exposure to risk, filtering the necessary complexity
from the unnecessary.
7. Use objects to automate release
Make acquisition and release symmetric simplifies the
style to be used for resource acquisition and release.
For instance, where possible, memory should be
released in the same context in which it is allocated,
where the context could be a block or an object.
{
product *created = new product;
... // use product
delete created;
}
Transferring responsibility for release is tricky,
44 APPLICATION DEVELOPMENT ADVISOR q www.appdevadvisor.co.uk
Kevlin Henney is an
independent software
development consultant
and trainer.He can be
reached at
www.curbralan.com
C++ WORKSHOP
q 7. Use objects to automate release
q 8. Flow don’t jump
q 9. Sharpen fuzzy logic
q 10. Hand redundant code its notice
q 11. Let the code make the decisions
q 12. Prefer code to comments
FACTS AT A GLANCE
Following his Six of the best column last issue, Kevlin Henney concludes his set
of 12 best practice tips for writing elegant, well-trimmed C++ code
The rest of the best
46 APPLICATION DEVELOPMENT ADVISOR q www.appdevadvisor.co.uk
C++ WORKSHOP
and disciplined symmetry reduces the chance that something has
been overlooked. However, the symmetry can be lost if we take
failure into account: what if an exception occurs? Sometimes
the longest way around really is the longest way home:
{
product *created = new product;
try
{
... // use product
}
catch(...)
{
delete created;
throw;
}
delete created;
}
The symmetry has been broken, and in its place we have long-
winded, repetitive and error-prone code—ironic, as it is coping
with the possibility of failure that has inspired this code. As we con-
sider duplicate code a programming error this situation calls for a tidi-
er remedy, and an antidote similar to the advice to use self-contained
objects suggests itself. Although not necessarily fully self-con-
tained or symmetric, the responsibility for release can be encap-
sulated in an object’s destructor:
{
auto_ptr<product> created(new product);
...
}
Not all acquisition and release is concerned with individual objects
on the heap, but the encapsulation of control flow can be gener-
alised2, 3
:
{
scoped<FILE *, closer> file(fopen(name, mode));
...
}
If something is tedious and error-prone, automate it.
8. Flow, don’t jump
It’s tough being a salmon. A life spent at sea is a prelude to a spe-
cial forces survival course for mating, jumping uphill against the
natural flow of a stream. To look at the control flow of some pro-
grams is to stand by a turbulent stream like a hapless and hope-
less bear waiting to knock a salmon out with its paws.
The basic ingredients of continuous control flow are sequence
(where one statement follows on to the next), selection (if else and
switch) and iteration (while, for and do while). These are the basic
ingredients of structured programming, and the lessons learnt from
structured programming should not be forgotten. Sometimes
discontinuous control flow mechanisms seem attractive (return,
break, continue and goto) and offer apparent short cuts. Early return
and break can sometimes be justified, but continue and goto
should be considered surplus to requirements.
Most uses of discontinuous control flow are like a drug: they are
addictive, and it can be difficult to stop. It can all start with some-
thing seemingly simple and innocuous:
while(in_range(value))
{
if(value == delimiter)
break;
... // now carry out intent of loop
}
One fix leads to the next, and before you know it there is a func-
tion that is impossible to understand and difficult to debug
because of all its jump points—and debugging is suddenly impor-
tant because bugs seem to be attracted to long functions with jumpy
control flow. Ironically, jumpy code is often written in the name
of convenience (it’s supposedly “easier this way”). It’s actually eas-
ier this way:
while(in_range(value) && value != delimiter)
{
... // carry out intent of loop
}
When it comes to the infamous goto, I must confess that I am
still surprised to find C++ programmers that feel the need to use
them when the alternatives are invariably simpler. I ran across a piece
of code recently that was chock full of gotos. In spite of having once
been a Fortran programmer, I didn’t have a clue what was going
on...and, when asked, neither did the programmers who wrote and
maintained it. They mumbled something about making it easier
to handle errors and to clean-up resources. We walked through it
and refactored it: without the gotos it was shorter—losing two thirds
of its former length—and clearer—a bug that had been hiding in
the previous control-flow salad revealed itself.
When you consider duplicate code a programming error, use
objects to automate release and sharpen fuzzy logic you find a lot of
the jumpiness disappears from code. Other recurring jumps sug-
gest different selection or looping structures, or encourage the use
of exceptions.
Error return codes tend to encourage a verbose coding style where
most of the code is structured to propagate the error. Error code
propagation increases the number of explicit paths in your code,
which by definition makes the code more complex. Return val-
ues should, on the whole, be used to return useful values rather
than good news/bad news bulletins.
Hang on, don’t exceptions contradict the message about smooth
flow, jumpy code, salmon and streams? Not at all. One of the main
problems with jumpy code is its disrespect for modularity. Refac-
toring one large function into many is often a process of splitting
out a specific sequence or loop, giving it a name and figuring out
what local variables need to be passed in and results returned. Data
flow respects modularity, but what do you do with control that
doesn’t flow? How to make the effect of a break or early return state-
ment non-local is less obvious than just passing and returning copies
of local variables. Extra status values often have to be introduced
and passed around in a game of pass the control flow. Exception
flow, however, is trivial to refactor because it remains unchanged:
an exception leaves a block and a function, or a function called by
a function, in the same way.
9. Sharpen fuzzy logic
Like overly jumpy control flow, a piece of logic can sometimes accu-
mulate mass like a snowball. Before you know it, you haven’t a clue
what is going on and the logic has gone fuzzy. I could have
named this recommendation “review fuzzy logic”, but there
would have been no point: the only thing to do with complex logic
is to simplify it.The most direct way to apply the scalpel is to recall
some of the basic transformations and operations that are possi-
ble with Booleans. For instance, a high incidence of true and false
literals in code can often be an indicator that some simplification
is possible:
48 APPLICATION DEVELOPMENT ADVISOR q www.appdevadvisor.co.uk
C++ WORKSHOP
if(failed)
return false;
else
return true;
Becomes the slightly less pedestrian:
return !failed;
De Morgan’s law allows you to move freely between:
!(a || b)
and:
!a && !b
In other words, if neither a nor b is true, then both a and b are false.
Other operator relationships also help you to simplify things, such
as:
!(i <= j)
to:
i > j
A surprising amount of logic in production code will submit itself
to these and many other simple transformations. If you are con-
fronted with a long or tortuous piece of logic that seems to shrug
off any attempt at reduction, wrap it in a function: give it a
good home and a good name rather than letting it clutter up the
main flow. You may already have arrived at this conclusion if you
find the logic appearing in more than one place and have chosen
to consider duplicate code a programming error. In other cases, flow
don’t jump is reinforced by simplifying complex logic, which in turn
often allows you to hand redundant code its notice and let the code
make the decisions.
10. Hand redundant code its notice
Redundant code is code that has no genuine effect. It varies
from easy-to-spot redundant checks to more elaborate arrangements
that reveal their no-op nature only on closer inspection.
Some redundant pieces of code are trivial and can also be con-
sidered under sharpen fuzzy logic, such as the repetitive:
if(failed == true) ...
Which says the same thing twice:
if(failed) ...
And the similarly wordy:
return value == delimiter ? true : false;
which needs no more detail than:
return value == delimiter;
Redundant code can be more intricate than a few logical excess-
es or a bit of control flow waffle. An example I found recently was
related to thread-safe synchronisation in a multi-threaded envi-
ronment.The code carefully ensured that each public member func-
tion locked a mutex on entry and unlocked it on exit (use objects
to automate release makes this task simpler and safer). However,
not all the functions needed locking: it makes no sense to synchronise
a constructor because during construction the object cannot be
shared meaningfully between threads; functions with empty bod-
ies have nothing to do, and can always do so safely; functions that
do not refer to their data members, such as those that operate only
on their arguments or functions that return constant values, do
not need synchronisation; and functions that refer only to data mem-
bers that are immutable values do not need synchronisation.
Unreachable code is a particular category of redundant code that
can be chopped without further ado. Depending on how obvious
it is, a compiler or checking tool may be able to warn you about
it. For instance, a return statement halfway through a function means
that the last half is unreachable without teleport. Such hiccoughs
are more likely to be present in functions with a long evolution
and a long line count. Sometimes unreachable code can be sub-
tle. The following is from some code I reviewed a while back:
if(container.empty())
{
for(iterator at = container.begin();
at != container.end();
++at)
{
... // many lines of code working through at
}
}
Another category of redundant code is unused code. Featurism,
overgeneralisation and changed requirements can cause code to
be written but never used in practice. Redundant code such as this
fattens source that could be lean; a little source liposuction never
goes amiss. If you are concerned that such code might one day become
useful, the version control system will remember it for you.
11. Let the code make the decisions
How do you get your program to do something? You specifical-
ly write out the code that performs the task. How do you make
your program take alternate actions depending on some context
or condition? The obvious answer is to write all the decisions and
options out explicitly. Decisions and multiple options encoded as
if else and switch statements certainly spell everything out in
detail, but sometimes that can be just a little—if not a lot—more
detail than is strictly necessary.
Sometimes the decision is already being taken for you by the code
you are calling. Checking a pointer against null before deleting it
is perhaps the most common C++-specific redundant coding habit:
if(cache)
delete cache;
if(connection)
delete connection;
if(buffer)
delete buffer;
Nothing more than the obvious is required:
delete cache;
delete connection;
delete buffer;
Here, in an example where you also hand redundant code its notice,
the underlying code is already making the decision for you.
Data structures can be organised to eliminate decision making,
Many companies want to push
themselves to the state of the art,
but seem bemused by how much
state there really is to that art
JUNE 2002 49
C++ WORKSHOP
leading to code that more accurately and explicitly reflects appli-
cation concepts and constraints in the runtime structure of your
program. Consider an application that holds a number of objects
that can be in one of two states: saved or changed. How do you
save all the changed objects? A common solution would be to include
a bool flag and query function in the objects’ class:
for(deque<saveable *>::iterator at = all.begin();
at != all.end();
++at)
{
if(at->changed())
at->save();
}
The Collections for States pattern4
describes an approach that
is more explicit, more efficient and takes decision control flow struc-
tures out of the code. An additional container holds items that have
been changed:
for(deque<saveable *>::iterator at = changed.begin();
at != changed.end();
++at)
{
at->save();
}
changed.clear();
Further refactoring lets the STL carry out all of the loop and grind:
for_each(
changed.begin(), changed.end(),
mem_fun(&saveable::save);
changed.clear();
In this case the other container is the master container and holds
all the objects, regardless of their state, but a variation would be
to have it hold only items that had been saved, so that the two con-
tainers hold complementary states.
A problem with hard-coding some decision structures into
your code is that the openness and adaptability of the code may
be reduced. It is here that the most common form of implicit deci-
sion-making has a role to play. Polymorphism—whether runtime
polymorphism through virtual functions and inheritance or com-
pile-time polymorphism through templates5
—cuts back the need
for many cascading if, else if or switch statements.
Another form of decision elimination is to use lookup tables such
as arrays, standard containers or customised containers6, 7, 8
. Imag-
ine if bus timetables were presented in terms of explicit decisions,
either through switch or if else, based on time: they would be unread-
able. Let the data structures do the work for you, and let the code
make the decisions.
12. Prefer code to comments
Comments are a problem in the majority of systems I have
reviewed. Not their absence, their presence. Sure, a good comment
can be useful, but given the general hit-versus-miss rate across most
commercial code that I’ve seen, such comments seem to be few
and far between. The lingering belief that comments are somehow
always a good thing seems to be adding drag rather than thrust to
a lot of source code.This does not mean that all comments are nec-
essarily bad, just that if you took a production system at random
and removed all of the comments from its source, the result
would probably be an improvement.
You can hand redundant code its notice as far as many comments
are concerned. They add nothing to the code, and often detract
from it. Comments that say nothing or simply parrot the code
are simply a waste of good ASCII. Even worse is the accumula-
tion of comments that are wrong: there is no value—to be pre-
cise, there is negative value—in having the source text lie to the
reader.
Part of the problem is that comments are utterly non-functional
and are bypassed by the compiler. Similarly, because they are non-
functional and often incorrect or uninformative, the other main
audience of the code, the programmer, often skips them as well.
This can be exacerbated by syntax highlighting in editors: having
comments in a separate colour makes it easier to ignore them, thus
letting sleeping dogs lie. I know of many programmers who have
written scripts to filter comments out of files or who switch the
comment colour in their editor to be the same as or similar to the
background colour to avoid being distracted.
In other words, it’s not just a case of preferring good code to bad
comments: you should prefer good code to good comments.
This recommendation is an easy one to put into practice. Read your
comments and if they give you news, not trivia, then keep them.
Otherwise, reach for the delete key.
Safe and sound
The 12 recommendations presented in this column and the last
are not intended to be exhaustive, but they do frame some prin-
ciples that cover a lot of ground. For instance, why isn’t there a spe-
cific recommendation to write short functions? Once you’ve
applied all of the recommendations, that’s by and large what
you’ll be left with. Short functions are a property, not a recom-
mendation.
Clearly, not all C++ practices that may be dear to your heart can
be included. For example, there is no recommendation to be const-
correct, and const-correctness won’t just fall out of the practices
as a by-product. It is certainly something you would want to fix,
but its presence won’t necessarily make the massive difference in
attempting to eliminate bugs and regain control of the software
that eliminating spurious code or twisted logic will. s
References
1. Kevlin Henney, “Six of the best”, Application Development
Advisor, May 2002.
2. Kevlin Henney, “Making an exception”, Application Devel-
opment Advisor, May 2001.
3. Kevlin Henney, “One careful owner”, Application Develop-
ment Advisor, June 2001.
4. Kevlin Henney, “Collections for States”, Java Report,
August 2000, available from www.curbralan.com, as is the
original EuroPLoP ‘99 paper in C++.
5. Kevlin Henney, “Promoting polymorphism”, Application
Development Advisor, October 2001.
6. Kevlin Henney, “Bound and checked”, Application Devel-
opment Advisor, January–February 2002.
7. Kevlin Henney, “Look me up sometime”, Application
Development Advisor, March 2002.
8. Kevlin Henney, “Flag waiving”, Application Development
Advisor, April 2002.
Like overly jumpy control flow,a
piece of logic can sometimes
accumulate mass like a snowball

More Related Content

PDF
Six of the Best
PDF
Exceptional Naming
PDF
What's in a Name?
PDF
Creating Stable Assignments
PPT
How To Navigate And Extend The Flex Infrastructure
PPTX
Refactoring Applications using SOLID Principles
PDF
10 things you're doing wrong in Talend
PDF
Getting Started With Testing
Six of the Best
Exceptional Naming
What's in a Name?
Creating Stable Assignments
How To Navigate And Extend The Flex Infrastructure
Refactoring Applications using SOLID Principles
10 things you're doing wrong in Talend
Getting Started With Testing

What's hot (9)

PPTX
Spl in the wild - zendcon2012
PPTX
Writing High Quality Code in C#
PPTX
How to fix bug or defects in software
PPTX
Clean Code II - Dependency Injection
PDF
Clean Software Design: The Practices to Make The Design Simple
PPTX
Clean Code I - Best Practices
PDF
Prefer Code to Comments
PDF
PPT
Fp201 unit1 1
Spl in the wild - zendcon2012
Writing High Quality Code in C#
How to fix bug or defects in software
Clean Code II - Dependency Injection
Clean Software Design: The Practices to Make The Design Simple
Clean Code I - Best Practices
Prefer Code to Comments
Fp201 unit1 1
Ad

Similar to The Rest of the Best (20)

PDF
60 terrible tips for a C++ developer
PDF
Clean Code
PPTX
Improving Code Quality Through Effective Review Process
PDF
Programming practises and project management for professionnal software devel...
PDF
Programming practises and project management for professionnal software devel...
PDF
Good code
PDF
Patterns, Code Smells, and The Pragmattic Programmer
PDF
Grounded Pointers
PPTX
Reading Notes : the practice of programming
PDF
C++ Restrictions for Game Programming.
PPTX
Clean code quotes - Citações e provocações
PDF
Peddle the Pedal to the Metal
PDF
The Evolution of Good Code
PDF
Minimalism
PDF
How To Win At Software - Advice for New Engineers - by Gabe Johnson
PDF
Clean code and code smells
PDF
Naming Things (with notes)
PDF
Coding Guidelines in CPP
PDF
Coding Guidelines - Crafting Clean Code
60 terrible tips for a C++ developer
Clean Code
Improving Code Quality Through Effective Review Process
Programming practises and project management for professionnal software devel...
Programming practises and project management for professionnal software devel...
Good code
Patterns, Code Smells, and The Pragmattic Programmer
Grounded Pointers
Reading Notes : the practice of programming
C++ Restrictions for Game Programming.
Clean code quotes - Citações e provocações
Peddle the Pedal to the Metal
The Evolution of Good Code
Minimalism
How To Win At Software - Advice for New Engineers - by Gabe Johnson
Clean code and code smells
Naming Things (with notes)
Coding Guidelines in CPP
Coding Guidelines - Crafting Clean Code
Ad

More from Kevlin Henney (20)

PDF
Program with GUTs
PDF
The Case for Technical Excellence
PDF
Empirical Development
PDF
Lambda? You Keep Using that Letter
PDF
Lambda? You Keep Using that Letter
PDF
Solid Deconstruction
PDF
Get Kata
PDF
Procedural Programming: It’s Back? It Never Went Away
PDF
Structure and Interpretation of Test Cases
PDF
Agility ≠ Speed
PDF
Refactoring to Immutability
PDF
Old Is the New New
PDF
Turning Development Outside-In
PDF
Giving Code a Good Name
PDF
Clean Coders Hate What Happens To Your Code When You Use These Enterprise Pro...
PDF
Thinking Outside the Synchronisation Quadrant
PDF
Code as Risk
PDF
Software Is Details
PDF
Game of Sprints
PDF
Good Code
Program with GUTs
The Case for Technical Excellence
Empirical Development
Lambda? You Keep Using that Letter
Lambda? You Keep Using that Letter
Solid Deconstruction
Get Kata
Procedural Programming: It’s Back? It Never Went Away
Structure and Interpretation of Test Cases
Agility ≠ Speed
Refactoring to Immutability
Old Is the New New
Turning Development Outside-In
Giving Code a Good Name
Clean Coders Hate What Happens To Your Code When You Use These Enterprise Pro...
Thinking Outside the Synchronisation Quadrant
Code as Risk
Software Is Details
Game of Sprints
Good Code

Recently uploaded (20)

PDF
Softaken Excel to vCard Converter Software.pdf
PDF
Adobe Premiere Pro 2025 (v24.5.0.057) Crack free
PDF
AI in Product Development-omnex systems
PDF
How Creative Agencies Leverage Project Management Software.pdf
PDF
Navsoft: AI-Powered Business Solutions & Custom Software Development
PDF
Wondershare Filmora 15 Crack With Activation Key [2025
PDF
Digital Strategies for Manufacturing Companies
PPTX
CHAPTER 2 - PM Management and IT Context
PPTX
Transform Your Business with a Software ERP System
PPTX
Lecture 3: Operating Systems Introduction to Computer Hardware Systems
PDF
System and Network Administraation Chapter 3
PDF
Design an Analysis of Algorithms I-SECS-1021-03
PDF
PTS Company Brochure 2025 (1).pdf.......
PDF
Internet Downloader Manager (IDM) Crack 6.42 Build 42 Updates Latest 2025
PDF
Design an Analysis of Algorithms II-SECS-1021-03
PDF
Raksha Bandhan Grocery Pricing Trends in India 2025.pdf
PDF
How to Choose the Right IT Partner for Your Business in Malaysia
PDF
System and Network Administration Chapter 2
PDF
Upgrade and Innovation Strategies for SAP ERP Customers
PDF
Understanding Forklifts - TECH EHS Solution
Softaken Excel to vCard Converter Software.pdf
Adobe Premiere Pro 2025 (v24.5.0.057) Crack free
AI in Product Development-omnex systems
How Creative Agencies Leverage Project Management Software.pdf
Navsoft: AI-Powered Business Solutions & Custom Software Development
Wondershare Filmora 15 Crack With Activation Key [2025
Digital Strategies for Manufacturing Companies
CHAPTER 2 - PM Management and IT Context
Transform Your Business with a Software ERP System
Lecture 3: Operating Systems Introduction to Computer Hardware Systems
System and Network Administraation Chapter 3
Design an Analysis of Algorithms I-SECS-1021-03
PTS Company Brochure 2025 (1).pdf.......
Internet Downloader Manager (IDM) Crack 6.42 Build 42 Updates Latest 2025
Design an Analysis of Algorithms II-SECS-1021-03
Raksha Bandhan Grocery Pricing Trends in India 2025.pdf
How to Choose the Right IT Partner for Your Business in Malaysia
System and Network Administration Chapter 2
Upgrade and Innovation Strategies for SAP ERP Customers
Understanding Forklifts - TECH EHS Solution

The Rest of the Best

  • 1. T HERE IS NO SHORTAGE OF TECHNICAL WISDOM on how to develop clear and robust code, so why is the paned (sic) expression on a pro- grammer’s face—sifting through all of the source windows trying to make sense of the encrypt- ed code—such a common sight? There are compa- nies whose development culture does not encourage best practice: a penny-wise/pound-foolish approach. However, there are many companies and developers that want to push themselves to the state of the art, but seem swamped and bemused by how much state there really is to that art. In the last column1 , I looked at the first six of a set of 12 recommendations that can potentially make a big difference to a body of C++ code. The short list does not claim to cover all design practices appropriate for a C++ system, but it does offer an easily memo- rable and easily practised set of guidelines that offer the greatest immediate return on investment—the most bang for your buck, the most oomph for your euro, the most kerrang for your quid. The story so far: 1. Make roots fully abstract: Classes should be either classifiers of behaviour or imple- mentations of behaviour. Class hierarchies should not muddle the roles of abstract and concrete classes. A fully abstract class—no data and ordinary member functions are public pure virtual only—offers the low- est coupling and clearest root to a class hierarchy. 2. Inherit for subtyping only: There is a strong temptation to use inheritance (in the sense of public rather than non-public derivation) as a convenience mechanism to build on parts of exist- ing code. Inheritance for this purpose is a remarkably blunt tool when compared to the more precise aggre- gate-and-forward approach. Inheritance is most effectively employed as a tool for classifying externally visible behaviour (subtyping) than for arranging internal representation (subclassing). 3. Include only what you need: Redundant #includes and excessive dependencies slow down both the programmer and the compiler. Many uses of #include can be replaced by forward dec- larations. Other heavy conceptual dependencies that lead to physical dependencies can often be reduced by delegation and templating. Some conceptual dependencies are incorrectly conceived: for instance, centralising error codes or application-related con- stants in a single header. 4. Consider duplicate code a programming error: Where the lack of an architectural vision can lead to a loss of the big picture, duplicated code can cause loss of the small picture. 5. Use self-contained objects: Objects that expose their representation management details, such as publicising data members or allow- ing users to replace dynamically allocated objects that they depend on, tend to be difficult to use correct- ly. Objects should manage their own representa- tion fully, preventing access or inappropriate manipulation by users. Consider the difference in ease of use between using std::string and a char * for manipulating strings. 6. Make acquisition and release symmetric: Acquisition of resources, such as dynamically allocated memory, should be matched by a release at the same scope, in the same object or through the same inter- face. Transfer of object ownership should be avoid- ed, so that object factory interfaces should support both creation and disposal operations. The remaining six recommendations continue the theme of avoiding unnecessary centralisation and exposure to risk, filtering the necessary complexity from the unnecessary. 7. Use objects to automate release Make acquisition and release symmetric simplifies the style to be used for resource acquisition and release. For instance, where possible, memory should be released in the same context in which it is allocated, where the context could be a block or an object. { product *created = new product; ... // use product delete created; } Transferring responsibility for release is tricky, 44 APPLICATION DEVELOPMENT ADVISOR q www.appdevadvisor.co.uk Kevlin Henney is an independent software development consultant and trainer.He can be reached at www.curbralan.com C++ WORKSHOP q 7. Use objects to automate release q 8. Flow don’t jump q 9. Sharpen fuzzy logic q 10. Hand redundant code its notice q 11. Let the code make the decisions q 12. Prefer code to comments FACTS AT A GLANCE Following his Six of the best column last issue, Kevlin Henney concludes his set of 12 best practice tips for writing elegant, well-trimmed C++ code The rest of the best
  • 2. 46 APPLICATION DEVELOPMENT ADVISOR q www.appdevadvisor.co.uk C++ WORKSHOP and disciplined symmetry reduces the chance that something has been overlooked. However, the symmetry can be lost if we take failure into account: what if an exception occurs? Sometimes the longest way around really is the longest way home: { product *created = new product; try { ... // use product } catch(...) { delete created; throw; } delete created; } The symmetry has been broken, and in its place we have long- winded, repetitive and error-prone code—ironic, as it is coping with the possibility of failure that has inspired this code. As we con- sider duplicate code a programming error this situation calls for a tidi- er remedy, and an antidote similar to the advice to use self-contained objects suggests itself. Although not necessarily fully self-con- tained or symmetric, the responsibility for release can be encap- sulated in an object’s destructor: { auto_ptr<product> created(new product); ... } Not all acquisition and release is concerned with individual objects on the heap, but the encapsulation of control flow can be gener- alised2, 3 : { scoped<FILE *, closer> file(fopen(name, mode)); ... } If something is tedious and error-prone, automate it. 8. Flow, don’t jump It’s tough being a salmon. A life spent at sea is a prelude to a spe- cial forces survival course for mating, jumping uphill against the natural flow of a stream. To look at the control flow of some pro- grams is to stand by a turbulent stream like a hapless and hope- less bear waiting to knock a salmon out with its paws. The basic ingredients of continuous control flow are sequence (where one statement follows on to the next), selection (if else and switch) and iteration (while, for and do while). These are the basic ingredients of structured programming, and the lessons learnt from structured programming should not be forgotten. Sometimes discontinuous control flow mechanisms seem attractive (return, break, continue and goto) and offer apparent short cuts. Early return and break can sometimes be justified, but continue and goto should be considered surplus to requirements. Most uses of discontinuous control flow are like a drug: they are addictive, and it can be difficult to stop. It can all start with some- thing seemingly simple and innocuous: while(in_range(value)) { if(value == delimiter) break; ... // now carry out intent of loop } One fix leads to the next, and before you know it there is a func- tion that is impossible to understand and difficult to debug because of all its jump points—and debugging is suddenly impor- tant because bugs seem to be attracted to long functions with jumpy control flow. Ironically, jumpy code is often written in the name of convenience (it’s supposedly “easier this way”). It’s actually eas- ier this way: while(in_range(value) && value != delimiter) { ... // carry out intent of loop } When it comes to the infamous goto, I must confess that I am still surprised to find C++ programmers that feel the need to use them when the alternatives are invariably simpler. I ran across a piece of code recently that was chock full of gotos. In spite of having once been a Fortran programmer, I didn’t have a clue what was going on...and, when asked, neither did the programmers who wrote and maintained it. They mumbled something about making it easier to handle errors and to clean-up resources. We walked through it and refactored it: without the gotos it was shorter—losing two thirds of its former length—and clearer—a bug that had been hiding in the previous control-flow salad revealed itself. When you consider duplicate code a programming error, use objects to automate release and sharpen fuzzy logic you find a lot of the jumpiness disappears from code. Other recurring jumps sug- gest different selection or looping structures, or encourage the use of exceptions. Error return codes tend to encourage a verbose coding style where most of the code is structured to propagate the error. Error code propagation increases the number of explicit paths in your code, which by definition makes the code more complex. Return val- ues should, on the whole, be used to return useful values rather than good news/bad news bulletins. Hang on, don’t exceptions contradict the message about smooth flow, jumpy code, salmon and streams? Not at all. One of the main problems with jumpy code is its disrespect for modularity. Refac- toring one large function into many is often a process of splitting out a specific sequence or loop, giving it a name and figuring out what local variables need to be passed in and results returned. Data flow respects modularity, but what do you do with control that doesn’t flow? How to make the effect of a break or early return state- ment non-local is less obvious than just passing and returning copies of local variables. Extra status values often have to be introduced and passed around in a game of pass the control flow. Exception flow, however, is trivial to refactor because it remains unchanged: an exception leaves a block and a function, or a function called by a function, in the same way. 9. Sharpen fuzzy logic Like overly jumpy control flow, a piece of logic can sometimes accu- mulate mass like a snowball. Before you know it, you haven’t a clue what is going on and the logic has gone fuzzy. I could have named this recommendation “review fuzzy logic”, but there would have been no point: the only thing to do with complex logic is to simplify it.The most direct way to apply the scalpel is to recall some of the basic transformations and operations that are possi- ble with Booleans. For instance, a high incidence of true and false literals in code can often be an indicator that some simplification is possible:
  • 3. 48 APPLICATION DEVELOPMENT ADVISOR q www.appdevadvisor.co.uk C++ WORKSHOP if(failed) return false; else return true; Becomes the slightly less pedestrian: return !failed; De Morgan’s law allows you to move freely between: !(a || b) and: !a && !b In other words, if neither a nor b is true, then both a and b are false. Other operator relationships also help you to simplify things, such as: !(i <= j) to: i > j A surprising amount of logic in production code will submit itself to these and many other simple transformations. If you are con- fronted with a long or tortuous piece of logic that seems to shrug off any attempt at reduction, wrap it in a function: give it a good home and a good name rather than letting it clutter up the main flow. You may already have arrived at this conclusion if you find the logic appearing in more than one place and have chosen to consider duplicate code a programming error. In other cases, flow don’t jump is reinforced by simplifying complex logic, which in turn often allows you to hand redundant code its notice and let the code make the decisions. 10. Hand redundant code its notice Redundant code is code that has no genuine effect. It varies from easy-to-spot redundant checks to more elaborate arrangements that reveal their no-op nature only on closer inspection. Some redundant pieces of code are trivial and can also be con- sidered under sharpen fuzzy logic, such as the repetitive: if(failed == true) ... Which says the same thing twice: if(failed) ... And the similarly wordy: return value == delimiter ? true : false; which needs no more detail than: return value == delimiter; Redundant code can be more intricate than a few logical excess- es or a bit of control flow waffle. An example I found recently was related to thread-safe synchronisation in a multi-threaded envi- ronment.The code carefully ensured that each public member func- tion locked a mutex on entry and unlocked it on exit (use objects to automate release makes this task simpler and safer). However, not all the functions needed locking: it makes no sense to synchronise a constructor because during construction the object cannot be shared meaningfully between threads; functions with empty bod- ies have nothing to do, and can always do so safely; functions that do not refer to their data members, such as those that operate only on their arguments or functions that return constant values, do not need synchronisation; and functions that refer only to data mem- bers that are immutable values do not need synchronisation. Unreachable code is a particular category of redundant code that can be chopped without further ado. Depending on how obvious it is, a compiler or checking tool may be able to warn you about it. For instance, a return statement halfway through a function means that the last half is unreachable without teleport. Such hiccoughs are more likely to be present in functions with a long evolution and a long line count. Sometimes unreachable code can be sub- tle. The following is from some code I reviewed a while back: if(container.empty()) { for(iterator at = container.begin(); at != container.end(); ++at) { ... // many lines of code working through at } } Another category of redundant code is unused code. Featurism, overgeneralisation and changed requirements can cause code to be written but never used in practice. Redundant code such as this fattens source that could be lean; a little source liposuction never goes amiss. If you are concerned that such code might one day become useful, the version control system will remember it for you. 11. Let the code make the decisions How do you get your program to do something? You specifical- ly write out the code that performs the task. How do you make your program take alternate actions depending on some context or condition? The obvious answer is to write all the decisions and options out explicitly. Decisions and multiple options encoded as if else and switch statements certainly spell everything out in detail, but sometimes that can be just a little—if not a lot—more detail than is strictly necessary. Sometimes the decision is already being taken for you by the code you are calling. Checking a pointer against null before deleting it is perhaps the most common C++-specific redundant coding habit: if(cache) delete cache; if(connection) delete connection; if(buffer) delete buffer; Nothing more than the obvious is required: delete cache; delete connection; delete buffer; Here, in an example where you also hand redundant code its notice, the underlying code is already making the decision for you. Data structures can be organised to eliminate decision making, Many companies want to push themselves to the state of the art, but seem bemused by how much state there really is to that art
  • 4. JUNE 2002 49 C++ WORKSHOP leading to code that more accurately and explicitly reflects appli- cation concepts and constraints in the runtime structure of your program. Consider an application that holds a number of objects that can be in one of two states: saved or changed. How do you save all the changed objects? A common solution would be to include a bool flag and query function in the objects’ class: for(deque<saveable *>::iterator at = all.begin(); at != all.end(); ++at) { if(at->changed()) at->save(); } The Collections for States pattern4 describes an approach that is more explicit, more efficient and takes decision control flow struc- tures out of the code. An additional container holds items that have been changed: for(deque<saveable *>::iterator at = changed.begin(); at != changed.end(); ++at) { at->save(); } changed.clear(); Further refactoring lets the STL carry out all of the loop and grind: for_each( changed.begin(), changed.end(), mem_fun(&saveable::save); changed.clear(); In this case the other container is the master container and holds all the objects, regardless of their state, but a variation would be to have it hold only items that had been saved, so that the two con- tainers hold complementary states. A problem with hard-coding some decision structures into your code is that the openness and adaptability of the code may be reduced. It is here that the most common form of implicit deci- sion-making has a role to play. Polymorphism—whether runtime polymorphism through virtual functions and inheritance or com- pile-time polymorphism through templates5 —cuts back the need for many cascading if, else if or switch statements. Another form of decision elimination is to use lookup tables such as arrays, standard containers or customised containers6, 7, 8 . Imag- ine if bus timetables were presented in terms of explicit decisions, either through switch or if else, based on time: they would be unread- able. Let the data structures do the work for you, and let the code make the decisions. 12. Prefer code to comments Comments are a problem in the majority of systems I have reviewed. Not their absence, their presence. Sure, a good comment can be useful, but given the general hit-versus-miss rate across most commercial code that I’ve seen, such comments seem to be few and far between. The lingering belief that comments are somehow always a good thing seems to be adding drag rather than thrust to a lot of source code.This does not mean that all comments are nec- essarily bad, just that if you took a production system at random and removed all of the comments from its source, the result would probably be an improvement. You can hand redundant code its notice as far as many comments are concerned. They add nothing to the code, and often detract from it. Comments that say nothing or simply parrot the code are simply a waste of good ASCII. Even worse is the accumula- tion of comments that are wrong: there is no value—to be pre- cise, there is negative value—in having the source text lie to the reader. Part of the problem is that comments are utterly non-functional and are bypassed by the compiler. Similarly, because they are non- functional and often incorrect or uninformative, the other main audience of the code, the programmer, often skips them as well. This can be exacerbated by syntax highlighting in editors: having comments in a separate colour makes it easier to ignore them, thus letting sleeping dogs lie. I know of many programmers who have written scripts to filter comments out of files or who switch the comment colour in their editor to be the same as or similar to the background colour to avoid being distracted. In other words, it’s not just a case of preferring good code to bad comments: you should prefer good code to good comments. This recommendation is an easy one to put into practice. Read your comments and if they give you news, not trivia, then keep them. Otherwise, reach for the delete key. Safe and sound The 12 recommendations presented in this column and the last are not intended to be exhaustive, but they do frame some prin- ciples that cover a lot of ground. For instance, why isn’t there a spe- cific recommendation to write short functions? Once you’ve applied all of the recommendations, that’s by and large what you’ll be left with. Short functions are a property, not a recom- mendation. Clearly, not all C++ practices that may be dear to your heart can be included. For example, there is no recommendation to be const- correct, and const-correctness won’t just fall out of the practices as a by-product. It is certainly something you would want to fix, but its presence won’t necessarily make the massive difference in attempting to eliminate bugs and regain control of the software that eliminating spurious code or twisted logic will. s References 1. Kevlin Henney, “Six of the best”, Application Development Advisor, May 2002. 2. Kevlin Henney, “Making an exception”, Application Devel- opment Advisor, May 2001. 3. Kevlin Henney, “One careful owner”, Application Develop- ment Advisor, June 2001. 4. Kevlin Henney, “Collections for States”, Java Report, August 2000, available from www.curbralan.com, as is the original EuroPLoP ‘99 paper in C++. 5. Kevlin Henney, “Promoting polymorphism”, Application Development Advisor, October 2001. 6. Kevlin Henney, “Bound and checked”, Application Devel- opment Advisor, January–February 2002. 7. Kevlin Henney, “Look me up sometime”, Application Development Advisor, March 2002. 8. Kevlin Henney, “Flag waiving”, Application Development Advisor, April 2002. Like overly jumpy control flow,a piece of logic can sometimes accumulate mass like a snowball