Common Mistakes

There are a certain group of mistakes in Java coding that are made over and over again at hundreds of companies throughout the world. Knowing how to avoid these mistakes will make you stand out from the crowd and look like you actually know what you are doing.

System Streams

The Java System streams represent the ability to write to the console or to read from it. When you invoke a method such as printStackTrace( ) with no arguments, its output is written to the default System stream, which is usually the console that started the program. However, these streams can cause problems in your code. Consider the following from a hypothetical GUI:

public void someMethod( ) {
 try {
 // do a whole bunch of stuff
 } (catch Exception ex) {
 ex.printStackTrace( );
 throw new MyappException( );
 }
}


To debug this GUI, you print the stack trace if something goes wrong. The problem is that the code will print the stack trace to the console window, which may be hidden, or even running on another computer. Printing to the console window is iffy, at best, in enterprise Java. In fact, there are times when you cannot use the console at all, such as when you write EJB code. At other times, you may be writing a library for others to use, and not have any idea of what the runtime environment is. Therefore, since one of your prime goals should be to promote reusability, you cannot count on the console always being around. The solution to the problem is to keep throwing those exceptions. In JDK 1.4, there is a new facility that will tell you if one exception caused another. This is called the Chained Exception Facility. In short, if you throw an exception inside of a catch block, the virtual machine will note the exception that you are throwing, along with the exception and stack trace that caused you to enter the catch block in the first place. For more information on this facility, consult the JDK documentation at http://java.oracle.com/j2se/1.4.1/docs/relnotes/features.html#chained-exceptions. The basics of using the chained exception facility are illustrated in this code block:

public void someMethod( ) {
 try {
 // do a whole bunch of stuff
 } (catch IllegalAccessException ex) {
 throw new MyappException( );
 }
}


This version of the method takes advantage of chained exceptions by responding to the initial IllegalAccessException by throwing a new exception (MyappException). The exceptions will keep propagating through the app until some code decides to catch them. When you finally do print the trace (presumably through a GUI, rather than a console window!), you will see that MyappException was caused by IllegalAccessException, and the correct stack trace will be indicated. Feel free to use the System streams in main( ) methods and in other classes in which you control their runtime environment. While you wouldn't want to use it in your app's data model classes, it may be acceptable for a GUI's frame window class. When logging errors and debugging traces are inside a program or library, a much better solution is to use a logging package, which is much more robust. The Log4J package from Jakarta (http://jakarta.apache.org/log4j/docs/index.html) will allow you to print your exception messages in a configurable way, as shown in Example 1-12.

Example 1-12. Log4J as an alternative to the System streams
import org.apache.log4j.Logger class SomeClass {
 /** Logger for this class. */
 private final static Logger LOGGER = Logger.getLogger(SomeClass.class);
 public void someMethod( ) {
 try {
 // do a whole bunch of stuff
 } (catch Exception ex) {
 LOGGER.error("Failure in SomeMethod", ex);
 throw new MyappException( );
 }
 }
}


The benefit to the altered approach shown here is that you can route the output to whichever stream you desire. You can send the output to an XML file, relay it to another app via JMS, create an email to the system administrator, or even dump the information to /dev/null (though there's really no good reason to ever do this!). Using a logging package gives you far more flexibility than Java's System streams could ever provide. Ultimately, using the System streams is okay only if you are writing a console-based app; for error handling, I wouldn't advise them at all. Also, tools and libraries should never write directly to System streams. Instead, they should pass errors and exceptions up to app-specific code that will do the logging for them.

System.exit( )

Every now and then you will encounter a third-party library that has code such as the following:

if (someSeriousErrorCondition) {
 System.exit(-1);
}


This code looks really benign, but watch out! It's a wolf in sheep's clothing. The problem here is that if you are a user of this library, you may not want the entire app to close because of this error. For example, if you are using this library as a part of a plug-in to another product, you should have only the plug-in crash and not the entire tool platform. However, System.exit( ) crashes the entire app in an exceedingly brutal and bloody fashion. Just imagine the look of surprise, consternation, and growing anger on the face of your users when they try to run the plug-in, and their app simply exits without bothering to save two hours' worth of data. Although it sounds funny, it could lead to sore feet from looking for another job. If you use System.exit( ) at all, your best bet is to use it only in the main method. If you ever write a library and embed System.exit( ) into it, you probably deserve any resulting physical violence.

Screenshot

If you think this sort of thing doesn't happen in real life, you are in for a brutal surprise. While developing a scripting support plug-in for Eclipse, I found that the Jython libraries embed System.exit( ) in their code. This resulted in a very long debugging session in which I tried to figure out what the heck I did to get Eclipse to simply die. One more bug report filed; at least I'm an expert at Bugzilla now.


Default Execution

One common mistake I see developers make is shown in the following code:

if (source == yesBtn) {
 processYesBtn( );
} else if (source == noBtn) {
 processNoBtn( );
} else {
 processCancelBtn( );
}


In this code, the developer has three buttons in his panel. The if structure is designed to process the buttons if the user clicks on them. If the user clicks on yesBtn, then the first branch will be executed; if the user clicks on noBtn, then the second branch will be executed. Otherwise, the user must have clicked on cancelBtn. However, there is one small problem. A junior programmer added a new button and registered the panel as an action listener. Unfortunately, he forgot to add handler code for the button. Now, when the user presses Load Favorites, the dialog simply doesn't work. The user subsequently files a bug report that reads something like, "When I try to load favorites, it doesn't work." However, your JUnit test works fine (because you don't depend on the action handling). Now you get to spend 12 hours stepping through all 2,000 lines of code related to loading favorites. Eventually, you will detect the missing handler and feel an irresistible compulsion to toss your computer out the window. Close that window and implement a coding standard instead. Change your if statements to look like the following:

if (source == yesBtn) {
 processYesBtn( );
} else if (source == noBtn) {
 processNoBtn( );
} else if (source == cancelBtn) {
 processCancelBtn( );
} else {
 assert false : source.toString( );
}


Those of you who studied the probably saw this coming. The point I am trying to make here is to never use else clauses or default blocks in switch statements to perform specific tasks without checking your assumptions. If your if-then-else statement or switch statement is dealing with an enumerated set of values, go ahead and enumerate them; in your default or final else clause, throw an error.

      
Comments