Team LiB
Previous Section Next Section

Debugging

Every programmer makes mistakes, and a large part of becoming a more proficient developer is honing your instincts for finding and rooting out errors in your code. Debugging is a skill that is best learned through experience, and although basic debugging practices can be taught, each programmer must develop his/her own approach. In this section we cover tools and techniques that can help you with these tasks.

Turning on Error Messages

The most basic way to track down errors is by turning on error information in your browser. By default, Internet Explorer shows an error icon in the status bar when an error occurs on the page:

Click To expand

Double-clicking this icon takes you to a dialog box showing information about the specific error that occurred.

Because this icon is easy to overlook, Internet Explorer gives you the option to automatically show the Error dialog box whenever an error occurs. To enable this option, select Tools | Internet Options, and click the Advanced tab. Check the Display a Notification About Every Script Error box, as shown in Figure 23-1.

Click To expand
Figure 23-1: Enabling notification of script errors in Internet Explorer

Although Netscape 3 shows an error dialog each time an error occurs, Netscape 4+ and Mozilla browsers send error messages to a special window called the JavaScript Console. To view the Console in Netscape and Mozilla, type javascript: in the browser’s Location bar. In Netscape 7+ and Mozilla you can also pull up the Console using the Tools menu (select Tools | Web Development). Unfortunately, since Netscape 6+ and Mozilla give no visual indication when an error occurs, you must keep the JavaScript Console open and watch for errors as your script executes.

Note 

In Netscape 6, the JavaScript Console is found in the Tasks menu (select Tasks | Tools).

Error Notifications

Error notifications that show up on the JavaScript Console or through Internet Explorer dialog boxes are the result of both syntax and runtime errors. Loading a file with the syntax error from a previous example, var myString = "This string doesn’t terminate results in the error dialog and JavaScript Console messages in Figure 23-2. Loading a file with the runtime error from a previous example, window.allert("Hi there"); results in the error dialog and JavaScript Console shown in Figure 23-3.

Click To expand
Click To expand
Figure 23-2: Syntax errors in Internet Explorer (top) and Mozilla (bottom)
Click To expand
Click To expand
Figure 23-3: Runtime errors in Internet Explorer (top) and Mozilla (bottom)

A very helpful feature of this kind of error reporting is that it includes the line number at which the error occurred. However, you should be aware that occasionally line numbers can become skewed as the result of externally linked files. Most of the time, error messages are fairly easy to decipher, but some messages are less descriptive than others, so it is useful to explicitly mention some common mistakes here.

Common Mistakes

Table 23-2 indicates some common JavaScript mistakes and their symptoms. This list is by no means exhaustive, but it does include the majority of mistakes made by novice programmers. Of this list, errors associated with type mismatches and access to form elements are probably the hardest for beginners to notice, so you should take special care when interacting with forms or other user-entered data.

Table 23-2: Common JavaScript Errors and Their Symptoms

Mistake

Example

Symptom

Infinite loops

while (x<myrray.length)
dosomething(myarray[x]);

A stack overflow error or a totally unresponsive page.

Using assignment instead of comparison (and vice versa)

if (x = 10)
// or
var x == 10;

Clobbered or unexpected values. Some JavaScript implementations automatically fix this type of error. Many programmers put the variable on the right-hand side of a comparison in order to cause an error when this occurs. For example, if (10 = x).

Unterminated string literals

var myString = "Uh oh

An “unterminated string literal” error message or malfunctioning code.

Mismatched parentheses

if (typeof(x) == "number"
alert("Number");

A “syntax error,” “missing ‘)',” or “expected ‘)'” error message.

Mismatched curly braces

function mult(x,y)
{
return (x,y);

Extra code being executed as part of a function or conditional, functions that are not defined, and “expected ‘}',” “missing ‘}',” or “mismatched ‘}'” error message.

Mismatched brackets

x[0 = 10;

“invalid assignment,” “expected ‘]',” or “syntax error” error message.

Misplaced semicolons

if (isNS4 == true);
hideLayers();

Conditional statements always being executed, functions returning early or incorrect values, and very often errors associated with unknown properties.

Omitted “break” statements

switch(browser)
{
case "IE": // IE-specific
case "NS": // NS-specific
}

Statements in the latter part of the switch always being executed and very often errors associated with unknown properties will occur as well.

Type errors

var sum = 2 + "2";

Values with an unexpected type, functions requiring a specific type not working correctly, and computations resulting in NaN.

Accessing undefined variables

var x = variableName;

variableName is not defined” error message.

Accessing non-existent object properties

var x = window.propertyName;

undefined values where you do not expect them, computations resulting in NaN, “propertyName is null or not an object,” or “objectName has no properties” error message.

Invoking non-existent methods

window.methodName()

methodName is not a function,” or “object doesn't support this property or method” error message.

Invoking undefined functions

noSuchFunction();

“object expected” or “noSuchFunction is not defined” error message.

Accessing the document before it has finished loading

<head><script>var myElement=document.all.myElement;</script></head>

undefined values, errors associated with nonexistent properties and methods, transitory errors that go away after page load.

Accessing a form element rather than its value

var x = document.myform.myfield;

Computation resulting in NaN, broken HTML-JS references, and form “validation” that always rejects its input.

Assuming that detecting an object or method assumes the existence of
all other features related to the detected object

if (document.layers)
{
// do Netscape 4 stuff
}
if (document.all)
{
// do all sorts of IE stuff
}

Probably will result in an error message complaining about a nonexistent object or property, because other proprietary objects beyond the detected ones were assumed to be presented and then used.

Using some sort of integrated development environment (IDE) or Web editor that matches parentheses and that colors your code is often helpful in avoiding syntax errors. Such programs automatically show where parentheses and brackets match and provide visual indications of the different parts of the script. For example, comments might appear in red while keywords appear blue and string literals appear in black.

Debugging Techniques

Although turning on error messages and checking for common mistakes can help you find some of the most obvious errors in your code, doing so is rarely helpful for finding semantic errors. There are, however, some widespread practices that many developers employ when trying to find the reason for malfunctioning code.

Manually Outputting Debugging Information

One of the most common techniques is to output verbose status information as the script runs in order to verify the flow of execution. For example, a debugging flag might be set at the beginning of the script that enables or disables debugging output included within each function. The most common way to output information in JavaScript is using the alert() method; for example, you might write something like

var debugging = true;
var whichImage = "widget";
if (debugging) 
   alert("About to call swapImage() with argument: " + whichImage);
var swapStatus = swapImage(whichImage);
if (debugging)
   alert("Returned from swapImage() with swapStatus="+swapStatus);

and include alert()s marking the flow of execution in swapImages(). By examining the content and order of the alert()s as they appear, you are granted a window to the internal state of your script.

Because using many alert()s when debugging large or complicated scripts may be impractical (not to mention annoying), output is often sent to another browser window instead. Using this technique, a new window, say, debugWindow, is opened at the beginning of the script, and debugging information is written into the window using syntax like debugWindow.document.write() method. The only potential gotcha is that you need to wait for the window to actually be opened before attempting to write() to it. See Chapter 12 for more information on inter-window communication.

Stack Traces Whenever one function calls another, the interpreter must keep track of the calling function so that when the called function returns, it knows where to continue execution. Such records are stored in the call stack, and each entry includes the name of the calling function, the line number of invocation, arguments to the function, and other local variable information. For example, consider this simple code:

function a(x) 
{
  document.write(x);
}
function b(x) 
{
  a(x+1);
}
function c(x) 
{
  b(x+1);
}
c(10);

At the document.write in a(), the call stack looks something like this:

a(12), line 3, local variable information…

b(11), line 7, local variable information…

c(10), line 11, local variable information…

When a() returns, b() will continue executing on line 8, and when it returns, c() will continue executing on line 12.

A listing of the call stack is known as a stack trace, and can be useful when debugging. Mozilla provides the stack property of the Error object (discussed in detail in a following section) for just such occasions. We can augment our previous example to output a stack trace in Mozilla:

function a(x) 
{
  document.writeln(x);
  document.writeln("\n----Stack trace below----\n");
  document.writeln((new Error).stack);
}
function b(x) {
  a(x+1);
}
function c(x) {
  b(x+1);
}
c(10);

The output is shown in Figure 23-4. The top of the trace shows that the Error() constructor is called. The next line indicates that the function that called the error constructor is a() and its argument was 10. The other data on the line indicates the filename where this function is defined (after the @) as well as the line number (after the colon) the interpreter is currently executing. Successive lines show the calling functions as we’d expect, and the final line shows that c() was called on line 16 of the currently executing file (the call to c() isn’t within any function, so the record on the stack doesn’t list a function name).

Click To expand
Figure 23-4: Using Error.stack to get a stack trace in Mozilla

Other browsers don’t provide an easy mechanism to get a stack trace, but given the Function properties discussed in Chapter 5, we can construct what it must look like ourselves.

// Helper function to parse out the name from the text of the function
function getFunctionName(f) 
{
  if (/function (\w+)/.test(String(f)))
    return RegExp.$1;
  else
    return "";
}

// Manually piece together a stack trace using the caller property
function constructStackTrace(f) 
{
  if (!f)
    return "";

  var thisRecord = getFunctionName(f) + "(";

  for (var i=0; i<<f.arguments.length; i++) {
    thisRecord += String(f.arguments[i]);
    // add a comma if this isn’t the last argument
    if (i+1 << f.arguments.length)
      thisRecord += ", ";
  }

  return thisRecord + ")\n" + constructStackTrace(f.caller);
}

// Retrieve a stack trace. Works in Mozilla and IE.
function getStackTrace() {
  var err = new Error;
  // if stack property exists, use it; else construct it manually
  if (err.stack)
    return err.stack;
  else
    return constructStackTrace(getStackTrace.caller);
}

We can now write out the example as

function a(x) 
{
  document.writeln(x);
  document.writeln("\n----Stack trace below----\n");
  document.writeln(getStackTrace());
}
function b(x) 
{
  a(x+1);
}
function c(x) 
{
  b(x+1);
}
c(10);

The output in Internet Explorer is shown in Figure 23-5.

Click To expand
Figure 23-5: A manually constructed stack trace

This is a handy function to have in an external script for debugging. However, the capabilities of this function and the techniques we’ve discussed so far leave a lot to be desired. They rely on manual insertion of debugging code into your scripts, and don’t provide any interactivity. Fortunately, specialized tools enable far more in-depth examination of your code at runtime.

Using a Debugger

A debugger is an application that places all aspects of script execution under the control of the programmer. Debuggers provide fine-grained control over the state of the script through an interface that allows you to examine and set values as well as control the flow of execution.

Once a script has been loaded into a debugger, it can be run one line at a time or instructed to halt at certain breakpoints. The idea is that once execution is halted, the programmer can examine the state of the script and its variables in order to determine if something is amiss. You can also watch variables for changes in their values. When a variable is watched, the debugger will suspend execution whenever the value of the variable changes. This is tremendously useful in trying to track down variables that are mysteriously getting clobbered. Most debuggers also allow you to examine stack traces, the call tree representing the flow of execution through various pieces of code that we saw in the previous section. And to top it all off, debuggers are often programmed to alert the programmer when a potentially problematic piece of code is encountered. And because debuggers are specifically designed to track down problems, the error messages and warnings they display tend to be more helpful than those of the browser.

There are several major JavaScript debuggers in current use. By far the most popular free debugger is Venkman, the debugger of the Mozilla project. It integrates with Mozilla and Netscape 6+ and offers all the features most developers might need, including a profiler enabling you to measure the performance of your code. If you’ve installed the “Full” version of a Mozilla-based browser, this debugger is already available to you. If not, use a Mozilla-based browser to access http://www.mozilla.org/projects/venkman/ and follow the installation instructions. This should be as simple as clicking on the .xpi file for the version you want. To start the debugger, select Tools | Web Development | JavaScript Debugger. Figure 23-6 shows a screenshot of Venkman.

Click To expand
Figure 23-6: The Venkman JavaScript debugger in action

A somewhat popular free utility for Internet Explorer 4 and later is the Microsoft Script Debugger. It is available from http://msdn.microsoft.com/scripting and integrates with Internet Explorer if installed. To enable this integration, select Tools | Internet Options. In the Advanced tab, uncheck Disable Script Debugging, as shown in Figure 23-7. Whenever debugging is turned on in IE and you load a page that has errors, the dialog in Figure 23-7 is shown in place of the normal error message, allowing you to load the page into the debugger.

Click To expand
Figure 23-7: Enabling script debugging in Internet Explorer

Of course, you can also load a document directly into the debugger without having an error occur.

The Microsoft Script Debugger has the advantage of close coupling with Microsoft’s JScript and Document Object Model, but no longer appears to be under active development. Microsoft Script Debugger is shown in Figure 23-8.

Click To expand
Figure 23-8: Use Microsoft Script Debugger to help track down errors.

The final major option you have is to use a commercial development environment. A JavaScript debugger is usually just one small part of such development tools, which can offer sophisticated HTML and CSS layout capabilities and can even automate certain aspects of site generation. This option is often the best choice for professional developers, because chances are you will need a commercial development environment anyway, so you might as well choose one with integrated JavaScript support. A typical example of such an environment is Macromedia’s Dreamweaver, available from http://www.macromedia.com/software/dreamweaver/. There are two primary drawbacks to such environments. The first and most obvious is the expense. The second is the fact that such tools tend to emit spaghetti code, so trying to hook your handwritten code into JavaScript or HTML and CSS generated by one of these tools can be tedious.

Now that we have covered some tools for tracking down errors in your code, we turn to techniques you can use to prevent or accommodate problems that might be outside of your direct control.


Team LiB
Previous Section Next Section