📄 tij317.htm
字号:
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
<html lang="en">
<!--
This document was converted from RTF source:
By r2net 5.8 r2netcmd Windows
See http://www.logictran.com
-->
<head><meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1">
<title>Thinking in Java, 3rd ed. Revision 4.0: 15: Discovering Problems</title>
<link rel="stylesheet" href="stylesheet.css" type="text/css"></head>
<body >
<CENTER> <a href="http://www.MindView.net"> <img src="mindview.gif" alt="MindView Inc." BORDER = "0"></a> <Font FACE="Verdana, Tahoma, Arial, Helvetica, Sans"> <h2>Thinking in Java, 3<sup>rd</sup> ed. Revision 4.0</h2> <FONT size = "-1"><br> [ <a href="README.txt">Viewing Hints</a> ] [ <a href="http://www.mindview.net/Books/TIJ/">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> ] <br><br> </FONT></FONT> </CENTER>
<font face="Georgia"><div align="CENTER"><a href="TIJ316.htm" target="RightFrame"><img src="./prev.gif" alt="Previous " border="0"></a>
<a href="TIJ318.htm" target="RightFrame"><img src="./next.gif" alt="Next " border="0"></a>
<a href="TIJ3_t.htm"><img src="./first.gif" alt="Title Page " border="0"></a>
<a href="TIJ3_i.htm"><img src="./index.gif" alt="Index " border="0"></a>
<a href="TIJ3_c.htm"><img src="./contents.gif" alt="Contents " border="0"></a>
</div>
<hr>
<h1>
<a name="_Toc375545491"></a><a name="_Toc24272654"></a><a name="_Toc24775931"></a><a name="Heading22248"></a>15:
Discovering Problems</h1>
<p class="Intro">Before C was tamed into ANSI C, we had a little joke: “My code compiles, so it should run!” (Ha ha!)<br></p>
<p>This was funny only if you understood C, because at that time the C compiler would accept just about anything; C was truly a “portable assembly language” created to see if it was possible to develop a portable operating system (Unix) that could be moved from one machine architecture to another without rewriting it from scratch in the new machine’s assembly language. So C was actually created as a side-effect of building Unix and not as a general-purpose programming language. <font size="-2"><a href="mailto:TIJ3@MindView.net?Subject=[TIJ3]A0159" title="Send BackTalk Comment">Feedback</a></font><br></p>
<p>Because C was targeted at programmers who wrote operating systems in assembly language, it was implicitly assumed that those programmers knew what they were doing and didn’t need safety nets. For example, assembly-language programmers didn’t need the compiler to check argument types and usage, and if they decided to use a data type in a different way than it was originally intended, they certainly must have good reason to do so, and the compiler didn’t get in the way. Thus, getting your pre-ANSI C program to compile was only the first step in the long process of developing a bug-free program. <font size="-2"><a href="mailto:TIJ3@MindView.net?Subject=[TIJ3]A0160" title="Send BackTalk Comment">Feedback</a></font><br></p>
<p>The development of ANSI C along with stronger rules about what the compiler would accept came after lots of people used C for projects other than writing operating systems, and after the appearance of C++, which greatly improved your chances of having a program run decently once it compiled. Much of this improvement came through <a name="Index2008"></a><a name="Index2009"></a><a name="Index2010"></a><i>strong static type checking</i>: “strong” because the compiler prevented you from abusing the type, “static” because ANSI C and C++ perform type checking at compile time. <font size="-2"><a href="mailto:TIJ3@MindView.net?Subject=[TIJ3]A0161" title="Send BackTalk Comment">Feedback</a></font><br></p>
<p>To many people (myself included), the improvement was so dramatic that it appeared that strong static type checking was the answer to a large portion of our problems. Indeed, one of the motivations for Java was that C++’s type checking wasn’t strong <i>enough</i> (primarily because C++ had to be backward-compatible with C, and so was chained to its limitations). Thus Java has gone even further to take advantage of the benefits of type checking, and since Java has language-checking mechanisms that exist at run time (C++ doesn’t; what’s left at run time is basically assembly language—very fast, but with no self-awareness), it isn’t restricted to only static type checking.<sup><a name="fnB86" href="#fn86">[86]</a></sup> <font size="-2"><a href="mailto:TIJ3@MindView.net?Subject=[TIJ3]A0162" title="Send BackTalk Comment">Feedback</a></font><br></p>
<p>It seems, however, that language-checking mechanisms can take us only so far in our quest to develop a correctly-working program. C++ gave us programs that worked a lot sooner than C programs, but often still had problems such as memory leaks and subtle, buried bugs. Java went a long way toward solving those problems, yet it’s still quite possible to write a Java program containing nasty bugs. In addition (despite the amazing performance claims always touted by the flaks at Sun), all the safety nets in Java added additional overhead, so sometimes we run into the challenge of getting our Java programs to run fast enough for a particular need (although it’s usually more important to have a working program than one that runs at a particular speed). <font size="-2"><a href="mailto:TIJ3@MindView.net?Subject=[TIJ3]A0163" title="Send BackTalk Comment">Feedback</a></font><br></p>
<p>This chapter presents tools to solve the problems that the compiler doesn’t. In a sense, we are admitting that the compiler can take us only so far in the creation of robust programs, so we are moving beyond the compiler and creating a build system and code that know more about what a program is and isn’t supposed to do. <font size="-2"><a href="mailto:TIJ3@MindView.net?Subject=[TIJ3]A0204" title="Send BackTalk Comment">Feedback</a></font><br></p>
<p>One of the biggest steps forward is the incorporation of <i>automated unit testing.</i> This means writing tests and incorporating those tests into a build system that compiles your code and runs the tests every single time, as if the tests were part of the compilation process (you’ll soon start relying upon them as if they are). For this book, a custom testing system was developed to ensure the correctness of the program output (and to display the output directly in the code listing), but the defacto standard <b>JUnit</b> testing system will also be used when appropriate. To make sure that testing is automatic, tests are run as part of the build process using Ant, an open-source tool that has also become a defacto standard in Java development, and CVS, another open-source tool that maintains a repository containing all your source code for a particular project. <font size="-2"><a href="mailto:TIJ3@MindView.net?Subject=[TIJ3]A0164" title="Send BackTalk Comment">Feedback</a></font><br></p>
<p>JDK 1.4 introduced an <i>assertion</i> mechanism to aid in the verification of code at run time. One of the more compelling uses of assertions is <i>Design by Contract</i> (DBC), a formalized way to describe the correctness of a class. In conjunction with automated testing, DBC can be a powerful tool. <font size="-2"><a href="mailto:TIJ3@MindView.net?Subject=[TIJ3]A0203" title="Send BackTalk Comment">Feedback</a></font><br></p>
<p>Sometimes unit testing isn’t enough, and you need to track down problems in a program that runs, but doesn’t run right. In JDK 1.4, the <i>logging API</i> was introduced to allow you to easily report information about your program. This is a significant improvement over adding and removing <b>println( )</b> statements in order to track down a problem, and this section will go into enough detail to give you a thorough grounding in this API. This chapter also provides an introduction to debugging, showing the information a typical debugger can provide to aid you in the discovery of subtle problems. Finally, you’ll learn about profiling and how to discover the bottlenecks that cause your program to run too slowly. <font size="-2"><a href="mailto:TIJ3@MindView.net?Subject=[TIJ3]A0165" title="Send BackTalk Comment">Feedback</a></font><br></p>
<h2>
<a name="_Toc24775932"></a><a name="Heading22260"></a>Unit Testing<br></h2>
<p><a name="Index2011"></a><a name="Index2012"></a><a name="Index2013"></a>A recent realization in programming practice is the dramatic value of unit testing. This is the process of building integrated tests into all the code that you create and running those tests every time you do a build. That way, the build process can check for more than just syntax errors, since you teach it how to check for semantic errors as well. C-style programming languages, and C++ in particular, have typically valued performance over programming safety. The reason that developing programs in Java is so much faster than in C++ (roughly twice as fast, by most accounts) is because of Java’s safety net: features like garbage collection and improved type checking. By integrating unit testing into your build process, you can extend this safety net, resulting in faster development. You can also be bolder in the changes that you make, more easily refactor your code when you discover design or implementation flaws, and in general produce a better product, more quickly. <font size="-2"><a href="mailto:TIJ3@MindView.net?Subject=[TIJ3]A0001" title="Send BackTalk Comment">Feedback</a></font><br></p>
<p>The effect of unit testing on development is so significant that it is used throughout this book, not only to validate the code in the book, but also to display the expected output. My own experience with unit testing began when I realized that, to guarantee the correctness of code in a book, every program in that book must be automatically extracted and organized into a source tree, along with an appropriate build system. The build system used in this book is <i>Ant </i>(described later in this chapter), and after you install it, you can just type <b>ant</b> to build all the code for the book. The effect of the automatic extraction and compilation process on the code quality of the book was so immediate and dramatic that it soon became (in my mind) a requisite for any programming book—how can you trust code that you didn’t compile? I also discovered that if I wanted to make sweeping changes, I could do so using search-and-replace throughout the book or just by bashing the code around. I knew that if I introduced a flaw, the code extractor and the build system would flush it out. <font size="-2"><a href="mailto:TIJ3@MindView.net?Subject=[TIJ3]A0183" title="Send BackTalk Comment">Feedback</a></font><br></p>
<p>As programs became more complex, however, I also found that there was a serious hole in my system. Being able to successfully compile programs is clearly an important first step, and for a published book it seems a fairly revolutionary one; usually because of the pressures of publishing, it’s quite typical to randomly open a programming book and discover a coding flaw. However, I kept getting messages from readers reporting semantic problems in my code. These problems could be discovered only by running the code. Naturally, I understood this and took some early faltering steps toward implementing a system that would perform automatic execution tests, but I had succumbed to publishing schedules, all the while knowing that there was definitely something wrong with my process and that it would come back to bite me in the form of embarrassing bug reports (in the open source world,<sup><a name="fnB87" href="#fn87">[87]</a></sup> embarrassment is one of the prime motivating factors towards increasing the quality of one’s code!). <font size="-2"><a href="mailto:TIJ3@MindView.net?Subject=[TIJ3]A0002" title="Send BackTalk Comment">Feedback</a></font><br></p>
<p>The other problem was that I lacked a structure for the testing system. Eventually, I started hearing about unit testing and JUnit, which provided a basis for a testing structure. I found the initial versions of JUnit to be intolerable because they required the programmer to write too much code to create even the simplest test suite. More recent versions have significantly reduced this required code by using reflection, so they are much more satisfactory. <font size="-2"><a href="mailto:TIJ3@MindView.net?Subject=[TIJ3]A0184" title="Send BackTalk Comment">Feedback</a></font><br></p>
<p>I needed to solve another problem, however, and that was to validate the output of a program and to show the validated output in the book. I had gotten regular complaints that I didn’t show enough program output in the book. My attitude was that the reader should be running the programs while reading the book, and many readers did just that and benefited from it. A hidden reason for that attitude, however, was that I didn’t have a way to test that the output shown in the book was correct. From experience, I knew that over time, something would happen so that the output was no longer correct (or, I wouldn’t get it right in the first place). The simple testing framework shown here not only captures the console output of the program—and most programs in this book produce console output—but it also compares it to the expected output that is printed in the book as part of the source-code listing, so readers can see what the output will be and also know that this output has been verified by the build process, and that they can verify it themselves. <font size="-2"><a href="mailto:TIJ3@MindView.net?Subject=[TIJ3]A0186" title="Send BackTalk Comment">Feedback</a></font><br></p>
<p>I wanted to see if the test system could be even easier and simpler to use, applying the Extreme Programming principle of “do the simplest thing that could possibly work” as a starting point, and then evolving the system as usage demands. (In addition, I wanted to try to reduce the amount of test code in an attempt to fit more functionality in less code for screen presentations.) The result<sup><a name="fnB88" href="#fn88">[88]</a></sup> is the simple testing framework described next. <font size="-2"><a href="mailto:TIJ3@MindView.net?Subject=[TIJ3]A0003" title="Send BackTalk Comment">Feedback</a></font><br></p>
<h3>
<a name="_Toc24775933"></a><a name="Heading22269"></a>A Simple Testing
Framework<br></h3>
<p><a name="Index2014"></a>The primary goal of this framework<sup><a name="fnB89" href="#fn89">[89]</a></sup> is to verify the output of the examples in the book. You have already seen lines such as<br></p>
<BLOCKQUOTE><FONT SIZE = "+1"><PRE><font color=#0000ff>private</font> <font color=#0000ff>static</font> Test monitor = <font color=#0000ff>new</font> Test();</PRE></FONT></BLOCKQUOTE><p><br></p>
<p>at the beginning of most classes that contain a <b>main( )</b> method. The task of the <b>monitor</b> object is to intercept and save a copy of standard output and standard error into a text file. This file is then used to verify the output of an example program by comparing the contents of the file to the expected output. <font size="-2"><a href="mailto:TIJ3@MindView.net?Subject=[TIJ3]A0005" title="Send BackTalk Comment">Feedback</a></font><br></p>
<p>We start by defining the exceptions that will be thrown by this test system. The general-purpose exception for the library is the base class for the others. Note that it extends <a name="Index2015"></a><b>RuntimeException</b> so that checked exceptions are not involved:<br></p>
<BLOCKQUOTE><FONT SIZE = "+1"><PRE><font color=#009900>//: com:bruceeckel:simpletest:SimpleTestException.java</font>
<font color=#0000ff>package</font> com.bruceeckel.simpletest;
<font color=#0000ff>public</font> <font color=#0000ff>class</font> SimpleTestException <font color=#0000ff>extends</font> RuntimeException {
<font color=#0000ff>public</font> SimpleTestException(String msg) {
<font color=#0000ff>super</font>(msg);
}
} <font color=#009900>///:~</font></PRE></FONT></BLOCKQUOTE><p><br></p>
<p>A basic test is to verify that the number of lines sent to the console by the program is the same as the expected number of lines:<br></p>
<BLOCKQUOTE><FONT SIZE = "+1"><PRE><font color=#009900>//: com:bruceeckel:simpletest:NumOfLinesException.java</font>
<font color=#0000ff>package</font> com.bruceeckel.simpletest;
<font color=#0000ff>public</font> <font color=#0000ff>class</font> NumOfLinesException
<font color=#0000ff>extends</font> SimpleTestException {
<font color=#0000ff>public</font> NumOfLinesException(<font color=#0000ff>int</font> exp, <font color=#0000ff>int</font> out) {
<font color=#0000ff>super</font>(<font color=#004488>"Number of lines of output and "</font>
+ <font color=#004488>"expected output did not match.\n"</font> +
<font color=#004488>"expected: <"</font> + exp + <font color=#004488>">\n"</font> +
<font color=#004488>"output: <"</font> + out + <font color=#004488>"> lines)"</font>);
}
} <font color=#009900>///:~</font></PRE></FONT></BLOCKQUOTE><p><br></p>
<p>Or, the number of lines might be correct, but one or more lines might not match:<br></p>
<BLOCKQUOTE><FONT SIZE = "+1"><PRE><font color=#009900>//: com:bruceeckel:simpletest:LineMismatchException.java</font>
<font color=#0000ff>package</font> com.bruceeckel.simpletest;
<font color=#0000ff>import</font> java.io.PrintStream;
<font color=#0000ff>public</font> <font color=#0000ff>class</font> LineMismatchException
<font color=#0000ff>extends</font> SimpleTestException {
<font color=#0000ff>public</font> LineMismatchException(
<font color=#0000ff>int</font> lineNum, String expected, String output) {
<font color=#0000ff>super</font>(<font color=#004488>"line "</font> + lineNum +
<font color=#004488>" of output did not match expected output\n"</font> +
<font color=#004488>"expected: <"</font> + expected + <font color=#004488>">\n"</font> +
<font color=#004488>"output: <"</font> + output + <font color=#004488>">"</font>);
}
} <font color=#009900>///:~</font></PRE></FONT></BLOCKQUOTE><p><br></p>
<p>This test system works by intercepting the console output using the <b>TestStream</b> class to replace the standard console output and console error:<br></p>
<BLOCKQUOTE><FONT SIZE = "+1"><PRE><font color=#009900>//: com:bruceeckel:simpletest:TestStream.java</font>
<font color=#009900>// Simple utility for testing program output. Intercepts</font>
<font color=#009900>// System.out to print both to the console and a buffer.</font>
<font color=#0000ff>package</font> com.bruceeckel.simpletest;
<font color=#0000ff>import</font> java.io.*;
<font color=#0000ff>import</font> java.util.*;
<font color=#0000ff>import</font> java.util.regex.*;
<font color=#0000ff>public</font> <font color=#0000ff>class</font> TestStream <font color=#0000ff>extends</font> PrintStream {
<font color=#0000ff>protected</font> <font color=#0000ff>int</font> numOfLines;
<font color=#0000ff>private</font> PrintStream
console = System.out,
err = System.err,
fout;
<font color=#009900>// To store lines sent to System.out or err</font>
<font color=#0000ff>private</font> InputStream stdin;
<font color=#0000ff>private</font> String className;
<font color=#0000ff>public</font> TestStream(String className) {
<font color=#0000ff>super</font>(System.out, <font color=#0000ff>true</font>); <font color=#009900>// Autoflush</font>
System.setOut(<font color=#0000ff>this</font>);
System.setErr(<font color=#0000ff>this</font>);
stdin = System.in; <font color=#009900>// Save to restore in dispose()</font>
<font color=#009900>// Replace the default version with one that</font>
<font color=#009900>// automatically produces input on demand:</font>
System.setIn(<font color=#0000ff>new</font> BufferedInputStream(<font color=#0000ff>new</font> InputStream(){
<font color=#0000ff>char</font>[] input = (<font color=#004488>"test\n"</font>).toCharArray();
<font color=#0000ff>int</font> index = 0;
<font color=#0000ff>public</font> <font color=#0000ff>int</font> read() {
<font color=#0000ff>return</font>
(<font color=#0000ff>int</font>)input[index = (index + 1) % input.length];
}
}));
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -