Preprocessor ASSERTS in ActionScript

Tags:

Unlike most other computer languages, when ActionScript code fails, it fails silently. If my code sends an undefined argument to a function, there is no crash, no alert message, nothing scary.

Overall, this is a good thing. If my live swf hits a bug, it might work just fine, no one is the wiser and life goes goes on blissfully unaware.

This behavior has a downside, however. The bug that is ignored now might cascade into a serious problem later -- in the next frame or in some code far away from the source of the problem. And I'm left scratching my head trying to figure out the original source of the problem in my code. The appearance of the problem and the source of the problem may be far apart.

Instead, I want code failures to be noisy rather than silent, but only during development. I need the ability to build a release version and a development version of the product. The development version will have extra debugging code, and it doesn't matter if the swf is a little extra bulky and slow. The release version, however, should be lean -- no performance or footprint hit.

So how do you do this and what does it look like?

I'm an old C++ guy, and the thing I miss the most from the C++ development environment is the preprocessor. The preprocessor made it easy to create separate debug and release versions of the product. The Flash dev environment does not come with a preprocessor. Using ANT and a C preprocessor, however, you can make it work. (I use Eclipse/FDT. It should also work with FlashDevelop.)

I'll explain the details of how I set it up in another article. Really, there is more than one way to do it. I use a C preprocessor. There are Java preprocessors also. These might work better. My point is that you can probably figure out a way that works for you. The details are not that interesting.

What's more interesting is what it looks like in the code and how it helps speed development. For me, the key is the preprocessor macro ASSERT.

Note: This is not really a new thing. MTASC seems to support the ASSERT macro natively. I like the Flash IDE and don't use MTASC, so I'm not sure. My goal here is to demonstrate how adding asserts to your code will speed development, not to show you some new thing invented by me.

The quickest way to understand the usefulness of ASSERT is to look at some code:

// function from a class
public function setPoints(pts:Array):void
{
  ASSERT(pts != undefined);	
  ASSERT(pts.length == 3);
		
  ASSERT(Dbg.hasProps(pts[0], ["x", "y", "z"]));	
  ASSERT(Dbg.hasProps(pts[1], ["x", "y", "z"]));	
  ASSERT(Dbg.hasProps(pts[2], ["x", "y", "z"]));	
		
  ASSERT(pts[0].z >= 0);
  ASSERT(pts[1].z >= 0);
  ASSERT(pts[2].z >= 0);
  ASSERT(pts[0].z <= 0xFF);
  ASSERT(pts[1].z <= 0xFF);
  ASSERT(pts[2].z <= 0xFF);
		
  // Make a copy of the array and a copy of each point.
  _corners = new Array();
  for (var i:int = 0 ; i < 3 ; i++){
    var pt:Object = pts[i];
    var newPt:Object = {x: pt.x, y: pt.y, z: pt.z};
    _corners.push(newPt);				
  }
  _corners.sortOn("z", Array.NUMERIC);
}

The asserts are verifying that the argument fits a number of criteria. If any of these conditions are not true, an error message with the as file name and line number will appear in the trace window.

I've created a mine field in my code. Ideally, if the code misbehaves in the slightest way, if it does not act that way I think it should, it will immediately blow up and I will know exactly where the bug is.

Note: This works with AS2 and AS3 code.

The ANT build creates a separate directory tree for the debugging version and populates it with the debugging version of the the as files. The debugging version of the above file looks like this:

// function from a class
public function setPoints(pts:Array):void
{
  pub.connectedpixel.debug.Debug.assert(pts != undefined,"G:\\FlashProjects\\Library\\as3\\Flash-ClassPaths\\ConnectedPixel_Main\\preproctmp\\com.connectedpixel.pixel.morph.Triangle3D.as",51);	
  pub.connectedpixel.debug.Debug.assert(pts.length == 3,"G:\\FlashProjects\\Library\\as3\\Flash-ClassPaths\\ConnectedPixel_Main\\preproctmp\\com.connectedpixel.pixel.morph.Triangle3D.as",52);
  pub.connectedpixel.debug.Debug.assert(Dbg.hasProps(pts[0], ["x", "y", "z"]),"G:\\FlashProjects\\Library\\as3\\Flash-ClassPaths\\ConnectedPixel_Main\\preproctmp\\com.connectedpixel.pixel.morph.Triangle3D.as",54);	
  pub.connectedpixel.debug.Debug.assert(Dbg.hasProps(pts[1], ["x", "y", "z"]),"G:\\FlashProjects\\Library\\as3\\Flash-ClassPaths\\ConnectedPixel_Main\\preproctmp\\com.connectedpixel.pixel.morph.Triangle3D.as",55);	
  pub.connectedpixel.debug.Debug.assert(Dbg.hasProps(pts[2], ["x", "y", "z"]),"G:\\FlashProjects\\Library\\as3\\Flash-ClassPaths\\ConnectedPixel_Main\\preproctmp\\com.connectedpixel.pixel.morph.Triangle3D.as",56);	
  pub.connectedpixel.debug.Debug.assert(pts[0].z >= 0,"G:\\FlashProjects\\Library\\as3\\Flash-ClassPaths\\ConnectedPixel_Main\\preproctmp\\com.connectedpixel.pixel.morph.Triangle3D.as",58);
  pub.connectedpixel.debug.Debug.assert(pts[1].z >= 0,"G:\\FlashProjects\\Library\\as3\\Flash-ClassPaths\\ConnectedPixel_Main\\preproctmp\\com.connectedpixel.pixel.morph.Triangle3D.as",59);
  pub.connectedpixel.debug.Debug.assert(pts[2].z >= 0,"G:\\FlashProjects\\Library\\as3\\Flash-ClassPaths\\ConnectedPixel_Main\\preproctmp\\com.connectedpixel.pixel.morph.Triangle3D.as",60);
  pub.connectedpixel.debug.Debug.assert(pts[0].z <= 0xFF,"G:\\FlashProjects\\Library\\as3\\Flash-ClassPaths\\ConnectedPixel_Main\\preproctmp\\com.connectedpixel.pixel.morph.Triangle3D.as",61);
  pub.connectedpixel.debug.Debug.assert(pts[1].z <= 0xFF,"G:\\FlashProjects\\Library\\as3\\Flash-ClassPaths\\ConnectedPixel_Main\\preproctmp\\com.connectedpixel.pixel.morph.Triangle3D.as",62);
  pub.connectedpixel.debug.Debug.assert(pts[2].z <= 0xFF,"G:\\FlashProjects\\Library\\as3\\Flash-ClassPaths\\ConnectedPixel_Main\\preproctmp\\com.connectedpixel.pixel.morph.Triangle3D.as",63);
		
  // Make a copy of the array and a copy of each point.
  _corners = new Array();
  for (var i:int = 0 ; i < 3 ; i++){
    var pt:Object = pts[i];
    var newPt:Object = {x: pt.x, y: pt.y, z: pt.z};
    _corners.push(newPt);				
  }
  _corners.sortOn("z", Array.NUMERIC);
}

You can see that the file name and line number are embedded in every assert call. The code is ugly, but that's ok; this code is not intended for human viewing. It is automatically generated and the Flash compiler uses it to build the debug version.

The ANT build populates the release directory tree with code that looks like this:

// function from a class
public function setPoints(pts:Array):void
{
  //Assert(pts != undefined);	
  //Assert(pts.length == 3);
		
  //Assert(Dbg.hasProps(pts[0], ["x", "y", "z"]));	
  //Assert(Dbg.hasProps(pts[1], ["x", "y", "z"]));	
  //Assert(Dbg.hasProps(pts[2], ["x", "y", "z"]));	
		
  //Assert(pts[0].z >= 0);
  //Assert(pts[1].z >= 0);
  //Assert(pts[2].z >= 0);
  //Assert(pts[0].z <= 0xFF);
  //Assert(pts[1].z <= 0xFF);
  //Assert(pts[2].z <= 0xFF);
		
  // Make a copy of the array and a copy of each point.
  _corners = new Array();
  for (var i:int = 0 ; i < 3 ; i++){
    var pt:Object = pts[i];
    var newPt:Object = {x: pt.x, y: pt.y, z: pt.z};
    _corners.push(newPt);				
  }
  _corners.sortOn("z", Array.NUMERIC);
}

This code is not bulked up. When building the release version, I switch the classpath to look in this directory tree instead of the debug tree. Also, when I give clients a copy of the code, I give them this code. The assert calls are preserved as comments, so if anyone else makes changes to the code, I can take that version and easily resurrect my assert calls.

Whether the asserts are left in the code peranently is optional. On the downside, they do bulk up the source code and make it possibly harder to read. On the upside, when I reuse the code in the future, I continue to get the protection.

BTW, this is similar to the use of asserts in Unit Testing. Like preprocessor asserts, unit testing asserts also verify the sanity of values and conditions. But they are contained in separate test classes that are included only when running unit tests. Preprocessor asserts run all the time in the development version.

Unit testing is more methodical and probably better at finding problems. I hate to admit it, but I've been too lazy to follow the unit testing approach. One of these days I will, maybe, possibly.

But this is not an either-or thing. Unit testing and preprocessor asserts can both exist in a project.

I'll give the macro code for the assert file and the ant file later. I gotta go get dinner.

Comments

Cool, just what I was looking for. But I have no C++ experience (not enough anyway) and have hardly ever used ANT. Did you ever post the macro code and the ant file? Or could you send it to me? It would be really really useful ;)

all the best!