Debugging in Java: Debugging Theory and Strategies

April 12, 2000

One of the most frustrating tasks a developer must face is testing and debugging software applications. Sometimes you'll be looking for general faults, and trying to crash the systems. Or you'll know a fault exists, and you'll be trying to isolate the cause of it. New faults in software are often found by both developers and users alike. Isolating and fixing the faults, alas, always falls to the developer. If you're lucky, you'll have a fairly good idea of where the problem lies. On a large and complex project (or a project you inherited from other developers), a cryptic error message may be all you have to go on. That's where debugging comes in!

Know Thy Enemy

"The process of locating bugs is one of the hardest tasks a developer will face. "

A "bug" is a software defect, the nature of which may or may not have been identified. Bugs can be present in all phases of the software development life cycle. A defect can be introduced from the initial determination of software requirements ("what is to be built"), during software design ("how it should be built"), right through to its final implementation ("how it will be built"). In ad hoc software development, we often know that our software has bugs, but we don't know exactly what they are.

Bugs are present in almost all software products: operating systems, utilities and applications, and even computer games. Many bugs are not critical and are situation specific: they might only be triggered under certain circumstances (such as particular hardware-software combinations) or in response to certain actions. As the number of features in software increases, programs become more complex to construct. This increases the likelihood of introducing bugs and makes it harder to detect them. Many bugs only present themselves after certain combinations of code are executed.

The term "debugging" is used to cover a wide range of tasks but generally applies to the process of detecting, locating, and repairing "bugs." These are three separate tasks; to detect the presence of bugs is not the same thing as locating which section of design or code is responsible for them, nor is it the same as the process of repairing them.

Detecting Bugs

Detecting bugs is a fairly simple task during the early stages of software development. However, the more time you spend looking for bugs, the harder it becomes to find them. The general trend is that as more time is spent looking for defects, fewer new defects will be found (see Figure 1). The most obvious bugs are spotted first. The more unusual bugs don't occur on all hardware/software platforms, are intermittent, and only happen when certain pieces of code are run and when other conditions are met.

This means that the everyday user running the software will not see bugs that affect the power user who relies on advanced features. Detecting bugs should never be performed by just one person people use software in different ways, and a wide range of approaches is required to find bugs in software.

Figure 1: The more time spent, the fewer unique bugs you will find.

One of the more popular approaches to detecting bugs in commercial software is alpha and beta testing. Alpha testing is the application of formalized testing methodology, such as unit testing, component testing, or code reviews. These techniques are more the realm of software engineers, however, and many companies choose to skip them in favor of an ad hoc approach. Beta testing, on the other hand, is far more popular, because it gets the software product out to eager, enthusiastic users running a diverse range of hardware and software platforms. These beta testers use the software features and file bug reports, which help identify the more common bugs overlooked during development. Beta testing often elicits arcane bug reports from users, filled with vague suggestions for replicating the bug, sometimes missing key details.

A better system is to automate the process, by providing capability within your Java applet or application to generate automatic bug reports whenever an error occurs (such as an uncaught exception), or when the user requests it (in cases where the bug is not evident to the software, but is to the user). When designing systems for a beta test, you should always include a snapshot of the current system, including the Java Virtual Machine version, memory availability, and operating system, just in case your users omit these details.

Bugs will often be present in one JVM or browser but not another, so the information is crucial for verifying the bug's existence by replicating it, as well as providing documentation that may be useful later. Most of this information is available by accessing the default system Properties object, which contains useful information to aid later diagnosis of the problem. For example, the following snippet of code determines the JVM/OS versions.

Further system information can also be obtained from the Properties object. Start by consulting the Java API documentation for the System class, which lists a wide range of system properties useful for debugging. Remember, too, that not every JVM will support this information earlier versions of Java and some third-party implementations may offer limited information about the system.

Locating Bugs

"Certain types of Java software present unique challenges. "

The process of locating bugs is one of the hardest tasks a developer will face. It is, however, an essential part of the debugging process the key to repairing a bug is to isolate the problem to a particular system component and to then locate the exact section at fault. In small systems, it may be easy to spot where a problem lies, but in larger systems that are composed of dozens or hundreds of classes, it isn't quite so easy. Tracking which class and which method is responsible can be a frustrating task.

Working with code written by other developers can be especially difficult, because you don't have the intuitive familiarity that you would with code that you've written yourself. The problem can often be very hard to locate, particularly if one component of a system is interacting badly with another, such as modifying public member variables or passing bad data to methods. This often masks the problem, making a developer think that one class is at fault when in reality it is another. Worse still, certain types of Java software present unique challenges. Applets, for example, are notoriously unstable on certain browser configurations, and network restrictions such as a firewall between users and the Internet may make it difficult to replicate bugs within a developer's intranet. Networking code can cause headaches, as it is often hard to tell whether the client, the server, or both are at fault. Servlets have no graphical user interface or user console, so seeing what is going on behind the scenes is very tricky. Multi-threaded code that works fine at some times may exhibit a bug at other times. Software that relies on other components, such as distributed services like RMI servers or CORBA servants, often is not fault-tolerant and reacts badly (and sometimes mysteriously) if the other components fail. Most of these problems can be alleviated by two popular debugging techniques: logging and tracing (discussed further in the second article of this series).

Repairing Bugs

The task of repairing bugs, also known as implementing a "bug-fix," can be a tiresome and frustrating process. Once repaired, however, you need to distribute the changed code to all of your users. One of the big benefits with Java, however, is that applets are automatically downloaded so there's no need to write a "patch" application. You simply copy the new files across to your Web server, and the applet is distributed to every new user. In the case of applications or servlets, however, additional work is required to get the files across. Packaging files in a .JAR or .ZIP/.TAR file is usually the most convenient way to distribute new versions. Remember that even if you use Windows, your users may be running Unix, so always provide a .TAR equivalent for your .ZIP archives.

Summary

Debugging in Java presents unique challenges, but also simplifies other areas of debugging, such as distributing changes. The strategies outlined above are a good way to get started and highlight some of the problems involved with debugging Java software. In the next article ( Debugging in Java: Techniques for Bug Eradication ), I'll show you some useful techniques to help isolate bugs in Java and determine the exact location of bugs.

About the Author

David Reilly is a software engineer and freelance technical writer living in Australia. A Sun Certified Java 1.1 Programmer, his research interests include the Java programming language, networking & distributed systems, and software agents. He can be reached via e-mail at java@davidreilly.com or through his personal site.