
Beginning ActionScript 2.0 2006
.pdf
Chapter 12
The Flash compiler is pretty good at giving you feedback on where a syntax or usage error is located, although sometimes it gives you huge lists of errors. Don’t worry too much about this because often one little error has put a whole bunch of things out of whack. Fixing the top two or three errors in a long list usually removes most or all of the remaining problems when you next try to compile.
Logic Bugs
Once your program successfully compiles, there is still a host of potential errors. The most common of these are described in the following sections along with some strategies for dealing with them.
If Statement Logic Errors
Working with if statements is an exercise in precision. It’s easy for these decision statements to get complex and out of control. They need not be difficult to debug, and a few strategies exist for both avoiding and debugging if statements.
Use Brackets
If you are ever unsure about how an if statement is going to be evaluated, make it clear through the use of round brackets. Anything inside a set of brackets is evaluated before anything outside of the brackets. The following example is ambiguous as to whether the and (&&) or the or (||) operator is evaluated first:
if (variable1 == “foo” || variable2 == “bar” && variable3 == “baz”) { }
Adding a pair of brackets around the term that you want to be evaluated first clarifies your intention and forces that order of operations:
if ( (variable1 == “foo” || variable2 == “bar”) && variable3 == “baz”) { }
Here the && operator takes precedence and is evaluated first, so without the extra brackets, the outcome of the expression would be different than intended.
Add Additional Nesting
A key aspect of debugging is your ability to read and understand the code. One way to clarify how an expression is to be evaluated is to separate it into more than one expression through nested if statements. Any spot in the expression where there is an && operator is a candidate for nesting the statements. Take the previous expression:
if ( (variable1 == “foo” || variable2 == “bar”) && variable3 == “baz”) { }
You can separate it into two nested expressions, with the expression for the outside statement taken from the left of the && operator and the expression for the inside statement taken from the right of the && operator:
if (variable1 == “foo” || variable2 == “bar”)
{
if (variable3 == “baz”)
{
// Statements
}
}
298

Debugging Debugged
Those who are concerned about performance may complain that this will be slower than the single expression version. Although this is true, it is so negligible that the only time it is of concern at all is within intensive loops, such as those responsible for scripted animation. In that case, optimizing the code may take precedence over code readability.
Use Trace Statements
One way to debug your logic is by placing trace() statements throughout the statement blocks. Some are just to indicate where the logic flow is going; others are to give an idea of what is happening to the variables used in the decisions. When you look at the entries in the output panel, you should have enough information to evaluate the problem. The following code places a trace() statement in each of three if statement blocks. When the code runs, you can tell which block executed by examining the output panel for the relevant output statement. For instance, if you see “Breakpoint 1” in the output panel and you expected to see “Breakpoint 2”, you can add a trace() statement before the initial if statement and run the code again to determine the values of the variables being tested:
trace(“variable 1: “ + variable1 “ variable2: “ + variable2 + ; “ variable3: “ + variable3);
if (variable1 == “foo” || variable2 == “bar”)
{
trace(“Breakpoint 1”); // Statements ...
}
else if (variable3 == “baz”)
{
trace(“Breakpoint 2”); // Statements ...
}
else
{
trace(“Breakpoint 3”); // Statements ...
}
Make sure that you remove the trace() statements once you have found the problem to avoid confusing them with trace() statements you use later.
Uninitialized Variables
Working with uninitialized data can result in hard-to-trace errors. The issue is that when you declare a variable, it is not given any start value until you assign it one. When you declare a variable without assigning a value, you get the following behavior:
var myBoolean:Boolean; trace(myBoolean);
// Outputs: undefined
If you try to use that variable for something, you do not get the results you want:
var myBoolean:Boolean; if (myBoolean == true)
{
trace(“myBoolean: true”);
299

Chapter 12
}
if (myBoolean == false)
{
trace(“myBoolean: false”);
}
// Outputs: nothing
Another common error involves trying to assign data to an array, like this:
var myArray:Array; myArray.push(“foo”);
trace(“Array length: “ + myArray.length”); // Outputs: Array length: undefined
The solution is to make sure that unless you know that a variable is going to be assigned data right away, give it an initial value when you declare it:
var myArray:Array = new Array(); myArray.push(“foo”);
trace(“Array length: “ + myArray.length”); // Outputs: Array length: 1
The same principle applies to data types other than arrays. The following code shows common initialization values for the core data types:
var myBoolean:Boolean = false; var myNum:Number = 0;
var myString:String = “”;
var myArray:Array = new Array(); var myObject:Object = new Object();
It’s common to dedicate a function to initializing your project. The initialization function is responsible for setting initial values for all your global and timeline variables. This gives you only one place to look for any initialization issues, making it easier to find and fix problems related to uninitialized variables. The following code illustrates an initialization function:
var myBoolean:Boolean; var myNum:Number;
var myString:String; var myArray:Array; var myObject:Object;
function init():Void
{
myBoolean:Boolean = false; myNum:Number = 0; myString:String = “”; myArray:Array = new Array(); myObject:Object = new Object();
// ...} init();
300

Debugging Debugged
Both solutions are fine. The first option is generally more compact, whereas the second one promotes grouping initialization code into a dedicated function, which is good for code organization.
Off-by-One Errors
An off-by-one error is a loop that miscounts how many times it needs to iterate, either one less or one greater than the needed number. One source of this error is forgetting that arrays start at element 0, not element 1, as in this example:
var fruitArray:Array = new Array(“apple”, “orange”, “pear”); for (var i:Number = 1; i < fruitArray.length; i++)
{
trace(“Element “ + i + “: “ + fruitArray[i]);
}
//Outputs:
//Element 1: orange
//Element 2: pear
The output fails to show element 0, “apple”, because the assigned start was 1. The simple solution to this is to make sure that all loops that iterate through an array start at element 0.
Another source of this error involves using the wrong termination condition, such as using a <= operator instead of a < operator, which results in the loop trying to access an element of the array that does not exist:
var fruitArray:Array = new Array(“apple”, “orange”, “pear”); for (var i:Number = 0; i <= fruitArray.length; i++)
{
trace(“Element “ + i + “: “ + fruitArray[i]);
}
//Outputs:
//Element 0: apple
//Element 1: orange
//Element 2: pear
//Element 3: undefined
All arrays start at 0, but array length counts actual elements, so that an array length of 3 contains elements 0, 1, and 2. The last element of any array is at fruitArray.length – 1. A loop should not proceed if the iterator variable i is equal to the array length because there’s no element at that point.
Fence Post Errors
A variation of the off-by-one error is the fence post error. Consider a fence with three sections. How many fence posts does it have? You might say that there are three — one for each section of fence. But there’s a post on each end of the fence, so in reality a three-section fence has four posts.
Where this comes into play with programming is when calculating ranges of numbers. Say you need to work with array elements 12 through 18 with a while loop, and you need to calculate how many times to run the loop. Most people would calculate 18 – 12 = 6, but that would be incorrect. Twelve through 18 is seven numbers: 12, 13, 14, 15, 16, 17, and 18. The correct calculation is 18 – 12 + 1 = 7. In general, when calculating a range of numbers from m to n, the formula to follow is n – m + 1 = total.
301

Chapter 12
Infinite Loops
The extreme case of a looping error is the infinite loop. You’ll usually notice this one right away because your movie will grind to a halt, eventually prompting an error from the Flash player, giving you an option to terminate the movie. Usually this error occurs as a result of forgetting to increment an iterator variable. The following while loop will never terminate because it contains no code to increment i:
var i:Number = 0; while (i < 20)
{
//i++;
//Remaining code ...
}
Make sure that within any while loop, there is always a way for the loop to end.
For the most part, for loops are not prone to infinite loops, although they’re still possible. In the following code, an item is being continually added to the array, so that the length of the array increases by one for each loop — and i can never catch up:
var fruitArray:Array = new Array(“apple”);
for (var i:Number = 0; i < fruitArray.length; i++)
{
fruitArray.push(“pear” + i);
}
It’s better idea to create a variable for holding the length and to use that variable for the array. That works well from a performance standpoint, too. Here’s an example:
var fruitArray:Array = new Array(“apple”); var numFruit:Number = fruitArray.length;
for (var i:Number = 0; i < numFruit; i++)
{
fruitArray.push(“pear” + i);
}
The best tool for debugging infinite loops is the Flash debug panel, to be discussed shortly.
Numerical Precision Errors
It may surprise you to learn that computers actually are fairly limited in their mathematical precision and, under certain conditions, can provide wildly inaccurate results. The issue arises when the computer attempts to store a floating-point number in its native number format. Some floating-point numbers cannot be accurately represented in a binary format, and so must be approximated. Try the following to see this in operation:
trace(1 - 0.9 - 0.1);
The result of the calculation should be 0, but the computer’s answer is a very small fractional amount of 0.0000000000000000277558. The following code illustrates how numerical precision errors can affect a while loop:
302

Debugging Debugged
var currentPosition:Number = 0; var endPosition:Number = 100; while (currentPosition < 100)
{
currentPosition += (endPosition - currentPosition) * 0.1; trace(currentPosition);
}
The condition checking to see if currentVal is less than 100 will never return true. If you try this, wait until the error message comes up warning you about the script running too slowly, and abort it. You should see a row of 99.99999999999 trace statements in the output window. The currentVal variable never reaches exactly 100 because of limitations in computer numeric precision. The most reliable way of dealing with this problem is to just accommodate for the imprecision:
var currentPosition:Number = 0; var endPosition:Number = 100; while (currentPosition < 99.9)
{
currentPosition += (endPosition - currentPosition) * 0.1; trace(currentPosition);
}
while loops like this one are commonly used in animation to make a moving movie clip decelerate. You learn more about how to use code for animation in Chapter 16.
Develop to Debug
Debugging involves more than searching for errors. How you develop your code has a major impact on how easy it is to debug. You make the process so much easier for yourself and for anyone who needs to look at your code if you consistently follow some good basic development practices:
Make your code readable.
Develop in small chunks.
Use small functions.
Make Your Code Readable
The most important aspect of debugging is to be able to understand the code that you wrote. Many people write what takes the least time to type. Here’s an example:
function plot(i:Number, line:Boolean)
{
base[“layer” + cl].lineStyle(1, lc, 100);
base[“layer” + cl].moveTo(x[i], y[i]); px[i] = (Math.cos(pd[i]) * ps[i]); py[i] = (Math.sin(pd[i]) * ps[i]);
303

Chapter 12
if (line == true)
{
base[“layer” + cl].lineTo(px[i], py[i]);
}
}
This code is nice and compact, but can you tell me what the difference is between lc and cl, or between pd and ps? The easiest kind of code to debug is that which is self-documenting. That is, code that you or someone working with your code can understand without having to refer to documentation. You will find it much easier to work with your code if you make variable and function names understandable and if you put thought into making sure their names reflect what they contain. Here’s a much better version of the example:
function plotLine(i:Number, drawLine:Boolean)
{
pBaseTimeline[“layer” + pCurrentLayer].lineStyle(1, pLineColour, 100); pBaseTimeline[“layer” + pCurrentLayer].moveTo(pPointX[i], pPointY[i]); pPointX[i] = (Math.cos(pPointDegree[i]) * pPointScale[i]);
pPointY[i] = (Math.sin(pPointDegree[i]) * pPointScale[i]); if (drawLine == true)
{
pBaseTimeline[“layer” + pCurrentLayer].lineTo(pPointX[i], pPointY[i]);
}
}
This code is much more explicit, and will make more sense when you have to pick it up in a few months or when placing code snippets on an online forum for someone to help you debug a problem.
Develop in Small Chunks
You will find it easier to locate problems if you keep the number of code changes down from the last time you got the code working. Do not write a whole application at once and then try to get all of that code working. Instead, break up your development process into chunks and get one chunk of code completely working before moving to the next chunk. In this way, you can build a project a piece at a time, comfortable in the knowledge that most of the problems you will find will be in the code you just wrote, rather than the code you wrote right at the beginning.
One caveat is that you should avoid the other extreme as well: don’t write the code one line at a time, testing each line as you go. That’s a very slow approach, and removes the focus from what you need to do to implement a particular feature. It works best if you focus on writing code for one self-contained feature and, when you’ve finished, verify that the feature works before going on to the next one.
Use Small Functions
To support the idea of developing code in pieces, use functions to allow for self-contained pieces of functionality that are easy to debug. A function has a defined set of inputs and outputs and should behave in an easy-to-predict way. It is much easier to debug a single function than a screen full of code. Functions should be small and focused, ideally between 10 and 30 lines of code. If a function becomes longer than 50 lines of code, consider how you might pull out some of its code into a second function.
304

Debugging Debugged
The Science of Debugging
In the field of science, every study and experiment uses the scientific process:
1.
2.
3.
4.
Develop a theory to test.
Run an experiment that tests that theory.
Analyze the result to see if the experiment validates the theory.
If the results do not validate the theory, revise the theory.
The process of debugging can be thought of in terms of this scientific process, where every time you compile and run your project, you run an experiment. The scientific process can be tweaked to take debugging in mind:
1.Develop a theory about why the program is behaving the way it is.
2.Run an experiment in which you change the parameters, add/remove functionality, or add debug information, and then run the program to test the theory.
3.Analyze the execution of the program and the contents of the output panel to see if the experiment validates your theory.
4.If the results do not validate the theory, revise the theory.
The following sections examine this process.
Develop a Theory
The most important part of an experiment is to establish a theory to be tested. That theory then forms the basis for the experiment and revolves around trying to determine the reason why some code failed.
Take a look at some code that does not work, and develop a theory to help establish how to fix it.
Try It Out |
Develop a Theory |
In this exercise, you look at some code that is supposed to load a thumbnail image and then position a text field just to the right of the thumbnail. You can see how the code fails for yourself, and from that, you can form a theory as to why it fails.
1.Find any available photo or image in the JPG format; use your favorite image editor to crop the image so that it is 50 pixels wide by 50 pixels high. Save the image somewhere convenient and call it thumbnail.jpg. If you do not have one handy, you can use thumbnail.jpg in <source file directory>/Chapter 12/tryItOut_debug/.
2.Create a new Macromedia Flash document.
3.Click the first frame in the timeline, open the Actions panel (Window Development Panels Actions), and enter in the following ActionScript code:
init();
function init():Void
305

Chapter 12
{
this.createEmptyMovieClip(“imageHolder”, 1); imageHolder.loadMovie(“thumbnail.jpg”); handleLoad();
}
function handleLoad():Void
{
imageHolder._x = 10; imageHolder._y = 10;
this.createTextField(“description1Field”, 1, 70, 25, 100, 20); description1Field.text = “Description 1”;
}
4.Select File Save As, name the file tryItOut_debug.fla, and save it in the same directory as the thumbnail.jpg image.
5.Select Control Test Movie to try it out.
6.From looking at the code and trying it out, what is your theory about what went wrong?
How It Works
If the program worked as designed, an image appears on the screen with a text field located 10 pixels to the right of the image. Instead, there is a text field but no image.
If the problem was that the image being loaded could not be found, Flash would output an error message in the output panel stating this. There is no such error, so the problem must be with something else. One possibility is that the movie clip created right at the beginning was not successfully created, or was created but not where you expected. Proceed with the theory that it was not created in the first place.
Run an Experiment
Now that you have a theory, you need to test it. You have a few ways to do so: using the built-in debugger, using trace() statements, and commenting out code.
Using the Built-In Debugger
The built-in debugger is a great tool for getting to the root of a problem. Figure 12-1 introduces you to the Debugger panel.
306

Debugging Debugged
8 10 12
7 |
9 |
11 |
13 |
14 |
1
16 |
15 |
2 |
3 |
4 |
5 |
6
Figure 12-1
Following are descriptions of the panel’s main parts:
1.Movie clip browser: Shows all the movie clips in the project. Movie clips nested within other movie clips are indented to show the hierarchy. Clicking any of the movie clips enables you to view its properties and variables.
2.Properties tab: Reveals all the properties for the movie clip selected in the movie clip browser.
3.Variables tab: Reveals all the variables assigned to the movie clip selected in the movie clip browser.
4.Locals tab: Reveals all the local variables available within the currently executing function, including any arguments passed to the function.
5.Watch tab: Reveals all the timeline and local variables that you have indicated you want to monitor. Designate a variable as a watched variable by right-clicking the variable and selecting Watch.
6.Call Stack: Shows the hierarchy of function calls currently being executed. (One function can call another function, which can call another function. The first function continues to run until all the functions it calls complete.)
7.Play button: Starts execution of the project and resumes execution after it has stopped at a breakpoint.
8.Cancel button: Terminates the project. It is equivalent to clicking the close button of the running SWF file.
307