This week we're going to put our big applet to rest, so to speak. A few weeks ago, we introduced a (relatively) large applet (with three distinct classes--applet, button, and tag), and before we were diverted into discussing current events at Sun, we said we would point out what was wrong with it. The time has come. Checking Java Applets for Bugs
Inasmuch as we teach programming here at "Java Jolt," and inasmuch as we try to instill good programming practices in general, it behooves us to mention the focus of what we're doing: revision. Being able to edit yourself is essential in programming. Hardly anyone does it right the first time.
In fact, hardly anyone ever does it right. So examine our process of error and recovery, and pay attention. These are common enough mistakes, so the solutions may help you again, but the process is at least as important.
Mistake number one: We've duplicated effort. If you recall, our applet is transferring a Web page over a network by speaking HTTP directly to a Web server via socket. There's a piece of the code here that actually speaks HTTP:
// ------------------------------------------------------------------ // We send the HTTP command to retrieve the document over the socket: outstream.print("GET "+ourfile+" HTTP/1.0\n\n"); // But we don't care about the standard header that the webserver gives // us before the document; we read and discard it: String buffer = instream.readLine(); while (!buffer.equals("")) // (It ends with a blank line) buffer = instream.readLine(); // ------------------------------------------------------------------That's foolish, really. More accurately, it goes against the grain when writing an object-oriented program. Ultimately, one of the main aspirations when writing programs this way is to achieve a kind of specialization, one in which objects refrain from doing too many disparate things at once. How much more advantageous is it when some new versions of HTTP suddenly eclipse the current standard we used earlier to simply have all Web-related transactions handled through a
URLConnectionclass. Just change the class and all the programs are kept up with the standard (in theory).If you write applets like we wrote this one, however, a change like that might require you to rewrite them all.
After switching from a
Socketto aURLConnection, which simplifies a significant fraction of the code in the applet class, we notice that we pass through several parameters to the two "internal" methods of this applet class. We've begun to recognize this as a sign of error in some cases, even if a less severe one.If you're writing something specific as to what the applet does, as we did (readtags and addbuttons really couldn't easily be generalized to do much else), it really doesn't make sense to have the flow of information (from method to method) run through the parameters. Stylistically (and we all know style does matter), it makes more sense to preserve the metaphor that all of these methods are sharing a common pool of variables. Instead of having the
webpage,layoutandtagsvariables defined within theinit()method, it makes more sense to make them a part of the applet itself. So now the definitions for these variables move above the first method declarations within our applet.Yet another problem beckons; this time there's a bug in the program itself. We notice that the first line of buttons displayed contains an extra--ninth--button. Why is this?
If you recall, we determine when to end a line of buttons by dividing the number of buttons we've added by the number 8 and then looking at the remainder. It's a perfectly sound idea; on multiples of eight, we end our lines, as in the following, by changing one of the constraints of the
GridBagLayoutwe're using.:
for (i = 0;(i < MAX_BUTTONS) && (i < tags.size());i++) { // We create a new button: current = new TagButton((Tag) tags.elementAt(i),displaybox,highlightbox); // Every 8 buttons or so, we want to add an extra line, so we do this // fancy math (checking the remainder of a division) to see if we want to // end the current "row" in the layout. if ((i != 0) && (i % 8) == 0) // If we do, we set this in the layout constraints. constraints.gridwidth = GridBagConstraints.REMAINDER; else // If not, we set this: constraints.gridwidth = 1; // Then we add the new button: layout.setConstraints(current,constraints); add(current); }So what's wrong with this picture? Our mistake is actually giving us a clue. Since we say we're only interested in points where the modulo division of our counter is zero, why does the if-statement always check for a second condition--the condition of the counter from the start?
According to Java, the modulo division of zero by 8 is zero, which is perhaps sensible. But why does our for loop start at zero in the first place? In fact, although we want to start pulling out elements from tags starting at index 0, we want to start our count of buttons at 1. Our solution? Do a little math each time:
if (((i + 1) % 8) == 0) // If we do, we set this in the layout constraints. constraints.gridwidth = GridBagConstraints.REMAINDER; else // If not, we set this: constraints.gridwidth = 1;Now we've changed our if-statement to start counting in the right place. The buttons, at last, are evenly spaced. Our revised applet class, for the moment, appears satisfying. We then briefly peruse the other class of significance, TagButton, and notice one last potential problem.
TagButton, as we've mentioned many times, is a subclass of
Button--and as such it's missing one potentially important thing:
// When we're created... public TagButton (Tag ot,TextArea sb,TextField hb) { // We just reccord everything. sourcebox = sb; highlightbox = hb; ourtag = ot; // And we make our own label what the name of our Tag is. setLabel(ourtag.tagname); }Above is our constructor. It's exceedingly simple; it merely stores the arguments it's given into some object variables and sets the button's label with one of them. But the other method we've written here (handleEvent) gives us a clue as to what we've left out here--a call to the superclass's constructor. Although it has thus far not prevented our applet from working, our failure to do so may, at some point, should the API designers structure their work a little differently in the future, cause some bit of code necessary for the creation of the button itself to not be executed.
// And we make our own label what the name of our Tag is. setLabel(ourtag.tagname); // Finally run the original constructor: super(); }And with that, our applet has now taken another step down the boundlessly long road toward integrity and reliability.
Hopefully, this overdocumented exercise in bug hunting and house cleaning has managed to clarify some of the fundamentals of the art. We may at some point revisit this applet to practice it further. Next week, however, more on the new Java.