Tutorial 14 - Debugging
Bugs or programming errors occur often when learning a new programming language or when developing a new routine or utility. Ripped or cut/pasted scripts are often filled with latent bugs as well. This tutorial will hopefully give you some successful strategies for squashing any bugs that occur in your JavaScript writings or trying to get ripped scripts to work correctly.
Types of Errors
There are several types of error, each with its own method of detection and repair. The basic types of error are:
- Syntax Errors are errors in grammar and punctuation such as mismatched quotes or missed commas. These errors are caught quickly if you have the browser's built-in error detector in display mode or run the script through jsLint.
- Runtime Errors only show up as the script is executed. Common examples are calling a function that hasn't been declared (typing error or case-sensitivity issue) or division by zero. Although JavaScript is typeless, many built in objects expect and/or return specific types (eg. style.left needs string type).
- Logic Errors are basic errors in the programmer's algorithms or procedural errors. Diagnosis only comes when incorrect results occur and solution requires mapping out the flow for test cases. The wrong scoping of a variable is an example of this kind of error.
- Incorrect Operator Precedence Errors are basic mathematical grouping errors. The best way to avoid them is with brackets to force the order that you want operations to occur explicitly.
- Browser Implementation Errors are quirks that occur in one browser but not others. See Browser Issues for more details. Test your code on all anticipated client browsers!
Common Errors
Errors can take some of the fun out of programming but experience will gradually eliminate (or at least make you more watchful for) certain commonly made ones. Some of the most common errors are:
- Spelling and typo errors. Remember case sensitivity and avoid hard to find lookalikes (1 and l, 0 and O, etc.).
- Not matching brackets or quotes correctly.
- Wrong type of bracket. Each has its own use.
- Not using the escaper backslash when required.
- The type of the variable may be critical in spite of being a typeless language.
- The scope of a variable can affect its current value.
Testing Methods
Brute force or 'poor-man's tests include inserting print statements and/or breakpoints where problems are suspected and running the program. Previous tutorials have shown how to use the alert window to provide feedback to the user. This same technique can be used by a JavaScript programmer to set breakpoints (ie pauses) and watch the value of variables within the code. Don't forget to use the typeof() operator to check the type of the variable. Sometimes it is more convenient to use window.status to display variable contents without pausing the program. You can dump and analyze a list of values by opening a new window and using the write() or writeln() methods.
Static tests are used to reduce known causes of programming problems. You can use lint-like tools such as jsLint to spot issues such as variable use before definition, conditions that are constant, loop range problems, etc. Watch out for utilities that are style-dependent unless you use that specific style.
Dynamic tests are used to check logic and regression (worked ok in last version) errors. They involve writing test drivers and rerunnable scripts to specifically test each object. These drivers and scripts must call each method TWICE! once with valid data, and once with invalid data.
Opera and FireFox browsers have script error displays that are accessible from the Tools menu. The Firefox add-on FireBug is an extremely useful debugging tool for script and CSS problems.
Microsoft Internet Explorer has a script error display but it must be enabled by selecting Tools -- Internet Options -- Advanced. Uncheck the box labeled 'disable script debugging' and check the box that says 'display a notice about every script error'. Note that the line number given in any error message is only approximately correct ;-[
Using the jsLint Validator
jsLint is an on-line validator that is very easy to use. Simply cut and paste your script into the text entry zone and press the JSlint button. Any error will generate a simple text message and a line number reference. This is an excellent way of getting the lint out of the wash ;-]
jsLint enforces a style that is tighter than what most browsers need but which detects many common mistakes that programmers make. Some of the guidelines are as follows:
- jsLint expects that every statement be followed by ; except for for, function, if, switch, try, and while.
- jsLint does not expect to see unnecessary semicolons or the empty statement.
- jsLint does not expect to see an identifier, a string, a number, or a suffix operator such as ) ] ++ -- at the end of a line.
- jsLint expects that if and for statements will be made with blocks {that is, with statements enclosed in braces}.
- jslint expects blocks with function, if, switch, while, for, do, and try statements and nowhere else.
- jsLint expects that a var will be declared only once, and that it will be declared before it is used.
Browser Issues
There are known recommendations for both script [ECMA] and DOM [w3.org] but some recommendations are vague and implemented differently in alternate browsers. Some texts and on-line material continue to use legacy methods which no longer work in modern browsers. The next few subtopics are a guide as to what to beware of in texts, tutorials and ripped scripts. The best way to avoid problems is to stick with the ECMA/w3.org recommendations!
DOM Level 0 Objects
The original document object had properties that could be read and/or written using script. The window.screen object also provided useful readable properties. Refer to client objects for specifics. DOM level 0 has been superceded by DOM level 1. Some examples of DOM level 1 access that should be replaced (often by CSS) are:
textColor=document.fgcolor; document.bgColor='thistle'; colors=screen.colorDepth; // note windows. omitted as redundant! document.myform.outbox.value=xyz; // accessing form controls
Microsoft Internet Explorer
MSIE has properties in addition to those in the DOM model. If possible replace them with w3.org recommendation codes. For example both pixelTop and pixelLeft should be replaced by top and left as in the following:
where=styleObj.pixelLeft; //MSIE specific where=parseInt(styleObj.left,10); // makes integer count in pixels . . . styleObj.pixelLeft=where; // MSIE specific styleObj.left=where+'px' // guarantees measured in pixels
Note: Although MSIE defaults property dimensions to pixels, CSS specs require explicit settings (such as +'px').
MSIE has objects in addition to those in the DOM model. If possible replace them with w3.org recommendation codes. For example document.all should be replaced as in the following:
x=document.all.ident.value; // MSIE specific
x=document.getElementById('ident').value; // DOM way
JavaScript variable names that are the same as html ids can cause error messages within MSIE. This is an undocumented enhancement in MicroSpeak. ;-] ;-]
For some reason MSIE does not like smart forms to be nested in definition lists even though it is a valid html construct. This can result in SELECT box text becoming inaccessible.
MSIE has filters and transitions which look slick but only in MSIE. Avoid the use of any proprietary feature! It is just not worth the maintenance hassle.
Browser Sniffing
The document object model (DOM) is a convenient way of understanding how to access html elements. Unfortunately major browser makers have chosen to implement some DOM objects differently. There are compatibility issues that must be overcome either by programming branches within a script, offering different pages depending on browser, defensive programming, warning users of possible conflicts, or benign neglect (playing the percentages that one browser has over 85 percent of the users). It is very important to realize that there is an issue! Decide how you will deal with these differences. And know your clients, their browsers, and their ability to upgrade to newer, better ones! Browser sniffing is commonly used:
- to program around various vendor interpretations of what the style object should be
- to avoid problems with tags that certain browsers do not handle well
- to provide an alternate stylesheet for a specific browser
Sniffers can be designed to either return a single value denoting the appropriate browser and version or to set global variables which flag capabilities such as DOMable, etc.
The best strategy allows for maintainability and documentation by identifying the object properties that are different or missing and building a JavaScript module of functions to deal with those variations. This isolates the selection of processing techniques to one area of your script. Further scripts allow reuse of these functions and if a newer browser requires sniffer tweaking, it is only needed in one file.
This example identifies a browser as a non-DOM model and then transfers to another page. This method can be used to skip a page (eg a fancy splash screen) entirely or transfer to a new one. My favorite technique is to redirect users of older versions to available upgrade sites (not subtle but many need this type of HINT).
var dom=document.getElementById; //browser uses DOM ??
if (dom==0){window.location.href="http://www.xyz.com/nextpage.htm";}
/* shield page from geezer browsers -- try to get them to upgrade */
if (document.layers){window.location.href="http://www.mozilla.com";}
if (document.all && !document.getElementById){ //oops - MSIE v5
window.location.href="http://www.microsoft.com/windows/ie/";}