📄 chap02.htm
字号:
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
<HTML>
<!--
This document was converted from RTF source:
By rtftohtml 4.19
See http://www.sunpack.com/RTF
Filename:C:\TEMP\TicV2\html\TicV2.rtf
Application Directory:C:\TOOLS\RTF2HTML\
Subject:
Author:Bruce Eckel
Operator:Bruce Eckel
Document Comments:
Version Comments:
Comments:
Keywords:
Translation Date:09/26/2001
Translation Time:08:32:21
Translation Platform:Win32
Number of Output files:19
This File:C:\TEMP\TicV2\html\Chap02.htm
SplitDepth=1
SkipNavPanel=1
SkipLeadingToc=1
SkipTrailingToc=1
GenContents=1
GenFrames=1
GenIndex=1
-->
<HEAD lang="en"><META http-equiv="Content-Type" content="text/html">
<TITLE>2: Defensive Programming</TITLE>
</HEAD>
<BODY BGCOLOR="#FFFFFF"><DIV ALIGN="CENTER">
<a href="http://www.MindView.net">
<img src="mindview.gif" alt="MindView Inc." BORDER = "0"></a>
<CENTER>
<FONT FACE="Verdana, Tahoma, Arial, Helvetica, Sans" size = "-1">
[ <a href="README.txt">Viewing Hints</a> ]
[ <a href="RevisionHistory.htm">Revision History</a> ]
[ <a href="http://www.mindview.net/Books/TICPP/ThinkingInCPP2e.html">Book Home Page</a> ]
[ <a href="http://www.mindview.net/Etc/MailingList.html">Free Newsletter</a> ] <br>
[ <a href="http://www.mindview.net/Seminars">Seminars</a> ]
[ <a href="http://www.mindview.net/CDs">Seminars on CD ROM</a> ]
[ <a href="http://www.mindview.net/Services">Consulting</a> ]
</FONT>
<H2><FONT FACE="Verdana, Tahoma, Arial, Helvetica, Sans">
Thinking in C++, 2nd edition, Volume 2<br>
<small>Revision 4.0</small></FONT></H2>
<H3><FONT FACE="Verdana, Tahoma, Arial, Helvetica, Sans">
by Bruce Eckel & Chuck Allison<br>©2001 MindView, Inc.</FONT></H3>
<FONT FACE="Verdana, Tahoma, Arial, Helvetica, Sans" size = "-1">
[ <a href="Chap01.htm">Previous Chapter</a> ]
[ <a href="SimpCont.htm">Short TOC</a> ]
[ <a href="Contents.htm">Table of Contents</a> ]
[ <a href="DocIdx.htm">Index</a> ]
[ <a href="Chap03.htm">Next Chapter</a> ]
</FONT>
</CENTER>
</P></DIV><A NAME="_Toc519041902"></A><A NAME="Heading60"></A><FONT FACE = "Verdana, Tahoma, Arial, Helvetica, Sans"><H1 ALIGN="LEFT">
2: Defensive Programming</H1></FONT>
<DIV ALIGN="LEFT"><P><FONT FACE="Verdana" SIZE=4>Intro stuff</FONT><BR></P></DIV>
<DIV ALIGN="LEFT"><P><FONT FACE="Georgia">intro
stuff.</FONT><A NAME="_Toc519041903"></A><BR></P></DIV>
<A NAME="Heading61"></A><FONT FACE = "Verdana, Tahoma, Arial, Helvetica, Sans"><H2 ALIGN="LEFT">
Assertions</H2></FONT>
<DIV ALIGN="LEFT"><P><FONT FACE="Georgia">The Standard C library
<B>assert( )</B> macro is brief, to the point and portable. In addition,
when you’re finished debugging you can remove all the code by defining
<B>NDEBUG</B>, either on the command-line or in code.</FONT><BR></P></DIV>
<DIV ALIGN="LEFT"><P><FONT FACE="Georgia">Also, <B>assert( )</B> can be used
while roughing out the code. Later, the calls to <B>assert( )</B> that are
actually providing information to the end user can be replaced with more
civilized messages.</FONT><A NAME="_Toc519041904"></A><BR></P></DIV>
<A NAME="Heading62"></A><FONT FACE = "Verdana, Tahoma, Arial, Helvetica, Sans"><H2 ALIGN="LEFT">
Design by Contract</H2></FONT>
<DIV ALIGN="LEFT"><P><A NAME="_Toc519041905"></A><BR></P></DIV>
<A NAME="Heading63"></A><FONT FACE = "Verdana, Tahoma, Arial, Helvetica, Sans"><H2 ALIGN="LEFT">
The Simplest Automated Unit Test Framework that could Possibly Work</H2></FONT>
<DIV ALIGN="LEFT"><P><FONT FACE="Georgia">Testing is a necessary evil to many
programmers, but it doesn't have to be all that evil.</FONT><BR></P></DIV>
<DIV ALIGN="LEFT"><P><FONT FACE="Georgia">The title of this section is a variation
on a theme from <I>Extreme
Programming</I></FONT><A NAME="fnB5" HREF="#fn5">[5]</A><FONT FACE="Georgia">,
or XP for short. XP is a code-centric discipline for getting software done
right, on time, within budget, while having fun along the way. </FONT><BR></P></DIV>
<DIV ALIGN="LEFT"><P><FONT FACE="Georgia">The XP approach is to take the best
software practices to the extreme. For example, if code reviews are good, then
code should be reviewed constantly, even as it's written. Hence the XP practice
of <I>pair programming</I>, where all code is written by two developers sharing
a single workstation. One programmer pilots the keyboard while the other watches
to catch mistakes and give strategic guidance. Then they switch roles as needed.
The next day they may pair up with other folks.</FONT><BR></P></DIV>
<DIV ALIGN="LEFT"><P><FONT FACE="Georgia">Likewise, if testing is good, then all
tests should be automated and run many times per day. An ever growing suite of
unit tests should be executed whenever you create or modify any function, to
ensure that the system is still stable. Furthermore, developers should integrate
code into the complete, evolving system and run functional tests often (at least
daily).</FONT><BR></P></DIV>
<DIV ALIGN="LEFT"><P><FONT FACE="Georgia">You've probably seen the old cartoon with
the caption, "You guys start coding while I go find out what they want." I spent
a number of years as a developer wondering why users couldn't figure out what
they wanted before I started coding. I found it very frustrating to attend a
weekly status meeting only to discover that what I completed the week before
wasn't quite going to fit the bill because the analysts changed their mind. It's
hard to reshape concrete while it's drying. Only once in my career have I had
the luxury of a "finished spec" to code from.</FONT><BR></P></DIV>
<DIV ALIGN="LEFT"><P><FONT FACE="Georgia">Over the years, however, I've discovered
that it is unreasonable to expect mere humans to be able to articulate software
requirements in detail without sampling an evolving, working system. It's much
better to specify a little, design a little, code a little, test a little. Then,
after evaluating the outcome, do it all over again. The ability to develop from
soup to nuts in such an iterative fashion is one of the great advances of this
object-oriented era in software history. But it requires nimble programmers who
can craft resilient (i.e., slow-drying) code. Change is hard.</FONT><BR></P></DIV>
<DIV ALIGN="LEFT"><P><FONT FACE="Georgia">Ironically, another kind of change that
good programmers want desperately to perform has always been opposed by
management: improving the physical design of working code. What maintenance
programmer hasn't had occasion to curse the aging, flagship company product as a
convoluted patchwork of spaghetti, wholly resistant to modification? The fear of
tampering with a functioning system, while not totally unfounded, robs code of
the resilience it needs to endure. "If it ain't broke, don't fix it" eventually
gives way to "We can't fix it — rewrite it." Change is
necessary.</FONT><BR></P></DIV>
<DIV ALIGN="LEFT"><P><FONT FACE="Georgia">Fortunately, we are now seeing the rise
of the new discipline of <I>Refactoring</I>, the art of internally restructuring
code to improve its design, without changing the functionality visible to the
user</FONT><A NAME="fnB6" HREF="#fn6">[6]</A><FONT FACE="Georgia">. Such tweaks
include extracting a new function from another, or its inverse, combining
methods; replacing a method with an object; parameterizing a method or class; or
replacing conditionals with polymorphism. Now that this process of improving a
program's internal structure has a name and the support of industry luminaries,
we will likely be seeing more of it in the workplace.</FONT><BR></P></DIV>
<DIV ALIGN="LEFT"><P><FONT FACE="Georgia">But whether the force for change comes
from analysts or programmers, there is still the risk that changes today will
break what worked yesterday. What we're all after is a way to build code that
withstands the winds of change and actually improves over time.</FONT><BR></P></DIV>
<DIV ALIGN="LEFT"><P><FONT FACE="Georgia">Many practices purport to support this
quick-on-your-feet motif, of which XP is only one. In this section I explore
what I think is the key to making incremental development work: a ridiculously
easy-to-use automated unit test framework, which I have implemented not only in
C++, but also in C and Java (although the latter versions are not shown
here).</FONT><BR></P></DIV>
<DIV ALIGN="LEFT"><P><FONT FACE="Georgia">Unit tests are what developers write to
gain the confidence to say the two most important things that any developer can
say:</FONT><BR></P></DIV>
<OL>
<LI><FONT FACE="Verdana"> </FONT><FONT FACE="Georgia">I understand the
requirements.</FONT><LI><FONT FACE="Verdana"> </FONT><FONT FACE="Georgia">My
code meets those
requirements.</FONT></OL><DIV ALIGN="LEFT"><P><FONT FACE="Georgia">I can't think of
a better way ensure that you know what the code you're about to write should do
than to write the unit tests first. This simple exercise helps focus the mind on
the task ahead, and will likely lead to working code faster than just jumping
into coding. Or, to express it in XP terms, Testing + Programming is faster than
just Programming. Writing tests first also puts you on guard up front against
boundary conditions that might cause your code to break, so your code is more
robust right out of the chute.</FONT><BR></P></DIV>
<DIV ALIGN="LEFT"><P><FONT FACE="Georgia">Once you have code that passes all your
tests, you then have the peace of mind that if the system you contribute to
isn't working, it's not your fault. The statement, "All my tests pass" is a
powerful trump card in the workplace that cuts through any amount of politics
and hand waving.</FONT><BR></P></DIV>
<DIV ALIGN="LEFT"><P><FONT FACE="Georgia">Writing good unit tests is so important
that I'm amazed I didn't discover its value earlier in my career. Let me
rephrase that. I'm not really amazed, just disappointed. I still remember what
turned me off to formal testing at my first job right out of school. The testing
manager (yes, we had one in 1978!) asked me to write a unit-test plan, whatever
that was. Being an impatient youth I thought it was silly to waste time writing
a plan — why not just write the test? That encounter soured me on the idea
of formal test plans for years
thereafter.</FONT><A NAME="_Toc519041906"></A><BR></P></DIV>
<A NAME="Heading64"></A><FONT FACE = "Verdana, Tahoma, Arial, Helvetica, Sans"><H3 ALIGN="LEFT">
Automated Testing</H3></FONT>
<DIV ALIGN="LEFT"><P><FONT FACE="Georgia">I think that most developers, like
myself, would rather write code than write about code. But what does a unit test
look like? Quite often developers just verify that some well behaved input
produces the expected output, which they inspect visually. Two dangers exist in
this approach. First, programs don't always receive just well behaved input. We
all know that we should test the boundaries of program input, but it's hard to
think about it when you're trying to just get things working. If you write the
test for a function first before you start coding, you can wear your QA hat and
ask yourself, "What could possibly make this break?" Code up a test that will
prove the function you'll write isn't broken, then put on your developer hat and
make it happen. You'll write better code than if you hadn't written the test
first.</FONT><BR></P></DIV>
<DIV ALIGN="LEFT"><P><FONT FACE="Georgia">The second danger is inspecting output
visually to see if things work. It's fine for toy programs, but production
software is too complex for that kind of activity. It is tedious and error prone
to visually inspect program output to see if a test passed. Most any such thing
a human can do a computer can do, but without error. It's better to formulate
tests as collections of Boolean expressions and have the test program report any
failures.</FONT><BR></P></DIV>
<DIV ALIGN="LEFT"><P><FONT FACE="Georgia">As an example, suppose you need to build
a Date class in C++ that has the following properties:</FONT><BR></P></DIV>
<UL>
<LI><FONT FACE="Symbol"> </FONT><FONT FACE="Georgia">A date can be initialized
with a string (YYYY-MM-DD), three integers (Y, M, D), or nothing (today's date).
</FONT><LI><FONT FACE="Symbol"> </FONT><FONT FACE="Georgia">A date object can
yield its year, month, and day or a string of the form
"YYYY-MM-DD".</FONT><LI><FONT FACE="Symbol"> </FONT><FONT FACE="Georgia">All
relational comparisons are available, as well as computing the duration between
two dates (in years, months, and days), and adding or subtracting a
duration.</FONT><LI><FONT FACE="Symbol"> </FONT><FONT FACE="Georgia">Dates need
to span an arbitrary number of centuries (e.g.,
1600-2200).</FONT></UL><DIV ALIGN="LEFT"><P><FONT FACE="Georgia">Your class could
store three integers representing the year, month, and day. (Just be sure the
year is 16 bits or more to satisfy the last bullet above.) The interface for
your Date class might look like this:</FONT><BR></P></DIV>
<BLOCKQUOTE><FONT SIZE = "+1"><PRE><font color=#009900>//: C02:Date.h</font>
#ifndef DATE_H
#define DATE_H
#include <string>
<font color=#0000ff>struct</font> Duration {
<font color=#0000ff>int</font> years;
<font color=#0000ff>int</font> months;
<font color=#0000ff>int</font> days;
Duration(<font color=#0000ff>int</font> y, <font color=#0000ff>int</font> m, <font color=#0000ff>int</font> d)
: years(y), months(m), days(d) {}
};
<font color=#0000ff>class</font> Date {
<font color=#0000ff>public</font>:
Date();
Date(<font color=#0000ff>int</font> year, <font color=#0000ff>int</font> month, <font color=#0000ff>int</font> day);
Date(<font color=#0000ff>const</font> std::string&);
<font color=#0000ff>int</font> getYear() <font color=#0000ff>const</font>;
<font color=#0000ff>int</font> getMonth() <font color=#0000ff>const</font>;
<font color=#0000ff>int</font> getDay() <font color=#0000ff>const</font>;
std::string toString() <font color=#0000ff>const</font>;
<font color=#0000ff>friend</font> Duration
duration(<font color=#0000ff>const</font> Date&, <font color=#0000ff>const</font> Date&);
<font color=#0000ff>friend</font> <font color=#0000ff>bool</font>
<font color=#0000ff>operator</font><(<font color=#0000ff>const</font> Date&, <font color=#0000ff>const</font> Date&);
<font color=#0000ff>friend</font> <font color=#0000ff>bool</font>
<font color=#0000ff>operator</font><=(<font color=#0000ff>const</font> Date&, <font color=#0000ff>const</font> Date&);
<font color=#0000ff>friend</font> <font color=#0000ff>bool</font>
<font color=#0000ff>operator</font>>(<font color=#0000ff>const</font> Date&, <font color=#0000ff>const</font> Date&);
<font color=#0000ff>friend</font> <font color=#0000ff>bool</font>
<font color=#0000ff>operator</font>>=(<font color=#0000ff>const</font> Date&, <font color=#0000ff>const</font> Date&);
<font color=#0000ff>friend</font> <font color=#0000ff>bool</font>
<font color=#0000ff>operator</font>==(<font color=#0000ff>const</font> Date&, <font color=#0000ff>const</font> Date&);
<font color=#0000ff>friend</font> <font color=#0000ff>bool</font>
<font color=#0000ff>operator</font>!=(<font color=#0000ff>const</font> Date&, <font color=#0000ff>const</font> Date&);
<font color=#0000ff>private</font>:
<font color=#0000ff>int</font> year;
<font color=#0000ff>int</font> month;
<font color=#0000ff>int</font> day;
<font color=#0000ff>int</font> compare(<font color=#0000ff>const</font> Date&) <font color=#0000ff>const</font>;
<font color=#0000ff>static</font> <font color=#0000ff>int</font> daysInPrevMonth(<font color=#0000ff>int</font> year,<font color=#0000ff>int</font> mon);
};
#endif <font color=#009900>///:~</font></PRE></FONT></BLOCKQUOTE>
<DIV ALIGN="LEFT"><P><FONT FACE="Georgia">The implementation for this class looks
like this:</FONT><BR></P></DIV>
<BLOCKQUOTE><FONT SIZE = "+1"><PRE><font color=#009900>//: C02:Date.cpp {O}</font>
#include <font color=#004488>"Date.h"</font>
#include <string>
#include <algorithm> <font color=#009900>// for swap()</font>
#include <ctime>
#include <cassert>
#include <sstream>
#include <iomanip>
<font color=#0000ff>using</font> <font color=#0000ff>namespace</font> std;
<font color=#0000ff>namespace</font> {
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -