«

»

Asserting the Basics – Part IIb

Hello, again! We’re back with another episode of “Asserting the Basics”. Last time we talked about our assertive philosophy on error handling. Today, we go into the dirty little details: the coding of asserts.

Asserts are not something that Actionscript3 offers naturally. Consequently, I added an Assert-method to our Debug class (remember from my first post?). Its job is (of course) to check whether a Boolean expression is true, and, if not, to eventually communicate this fact. Rather than showing all failed asserts directly, our method only stores them. I want to have control over when the asserts are shown, since there are times when you actually want asserts to fail: for instance when you want to try out your error handling in Unit Tests. Thus, there is another method to take care of displaying them, which can be called at an appropriate time. So our assert framework looks like this:

<span style="color: #0000ff;">private</span> <span style="color: #0000ff;">static</span> var m_asserts:Array;
<span style="color: #008000;">/**
 * Asserts that a condition is met
 * @param    _statement condition to check
 * @param    _msg Message to describe the assert
 * @return true if the assert failed
 */</span>
<span style="color: #0000ff;">public</span> <span style="color: #0000ff;">static</span> function Assert(_statement:Boolean, _msg:String):Boolean
{
<span style="color: #0000ff;">    if</span> (_statement != true)
    {
        var msg:String = <span style="color: #800000;">"****************************\n "</span>
            + <span style="color: #800000;">"ASSERT FAILED!!\n "</span>+_msg+<span style="color: #800000;">"\n"</span>+GetStackTrace()+<span style="color: #800000;">"\n"</span>
            + <span style="color: #800000;">"</span><span><span style="color: #800000;">****************************</span></span><span style="color: #800000;">\n "</span>;
<span style="color: #008000;">        // record assert</span>
        <span style="color: #0000ff;">if</span> (m_asserts == null)
            m_asserts = <span style="color: #0000ff;">new</span> Array();
        m_asserts.push(msg);
        Debug.Out(msg);
<span style="color: #0000ff;">        return</span> true;
    }
<span style="color: #0000ff;">    return</span> false;
}
<span style="color: #0000ff;">public</span> <span style="color: #0000ff;">static</span> function ShowAsserts(_stage:Stage) : <span style="color: #0000ff;">void</span>
{
<span style="color: #0000ff;">    if</span> (m_asserts == null)
<span style="color: #0000ff;">        return</span>;
    var assertBox:TextField = <span style="color: #0000ff;">new</span> TextField();
    assertBox.autoSize = TextFieldAutoSize.LEFT;
    assertBox.width = _stage.stageWidth;
    assertBox.textColor = <span style="color: #800000;">0xFF0000</span>;
    assertBox.backgroundColor = <span style="color: #800000;">0x000000</span>;
    assertBox.background = true;
    assertBox.wordWrap = true;
    assertBox.text = <span style="color: #800000;">""</span>;
<span style="color: #0000ff;">    for</span> (var i:<span style="color: #0000ff;">int</span> = <span style="color: #800000;">0</span>; i &lt; m_asserts.length; i++) {
        var assertString:String = m_asserts[i];
        assertBox.appendText(assertString + <span style="color: #800000;">"\n"</span>);
    }
    _stage.addChild(assertBox);
}

You may have noticed that our assert method has a Boolean return value, returning true if the assert fails. This way we can use the method to exit early from a function that would crash otherwise. Since we cannot simply take the asserts back out, we might as well use them in a bigger context.

In our code the use of these asserts look like this:

<span style="color: #0000ff;">public</span> function Load(_path:String) : <span style="color: #0000ff;">void</span> {
<span style="color: #0000ff;">    if</span> (Debug.Assert(m_fileName != null, <span style="color: #800000;">"Filename is null!!"</span>))
<span style="color: #0000ff;">        return</span>;
<span style="color: #0000ff;">    if</span> (Debug.Assert(m_fileName.length != <span style="color: #800000;">0</span>, <span style="color: #800000;">"Filename is empty!!"</span>))
<span style="color: #0000ff;">        return</span>;
<span style="color: #008000;">    // yada yada … more code</span>
}

To give our asserts even more value, I did some research on how to get stack traces. I found out that if you throw an error and catch it in a debug player, you can retrieve a stack trace from the error object, as seen here:
<span style="color: #008000;">/**
 * Returns the stack trace (filtered)
 * @return stack trace
 */</span>
<span style="color: #0000ff;">public</span> <span style="color: #0000ff;">static</span> function GetStackTrace() : String {
<span style="color: #0000ff;">    if</span> (Capabilities.isDebugger == true) {
<span style="color: #0000ff;">        try</span> { <span style="color: #0000ff;">throw</span> <span style="color: #0000ff;">new</span> Error(); }
<span style="color: #0000ff;">        catch</span> (e:Error) { <span style="color: #0000ff;">return</span> FilterStackTrace(e.getStackTrace()); }
<span style="color: #0000ff;">        return</span> <span style="color: #800000;">""</span>;
    }
<span style="color: #0000ff;">    else</span>
        <span style="color: #0000ff;">return</span> <span style="color: #800000;">"Stack trace not available in non-debugger version."</span>;
}

Unfortunately, the stack trace you get this way is really extensive and hard to read. Every line in the trace contains the complete path to the corresponding file on the local machine. So, I added a filtering function to remove the unnecessary path info. This method turns e.g. this:
<span style="color: #ff9900;">tests::TestDynamicTextManager.TestTextEdit()Trace:
AssertionFailedError
  at tests::TestDynamicTextManager/TestTextEdit()
  [D:\projects\programming\bgame\svn\FlashComponents
  \client\src\tests\TestDynamicTextManager.as:15]</span>

into this:
<span style="color: #ff9900;">tests::TestDynamicTextManager.TestTextEdit()Trace:
AssertionFailedError
  at tests::TestDynamicTextManager/TestTextEdit() [line:15]</span>

Here is the filter function:
<span style="color: #0000ff;">public</span> <span style="color: #0000ff;">static</span> function FilterStackTrace(stack:String):String {
    var lines:Array = stack.split(<span style="color: #800000;">"\n"</span>);
<span style="color: #008000;">    // remove the path
</span>    <span style="color: #008000;">// it's too long and we can get the info from the method trace</span>
    var regEx:RegExp = /\w:[\\\/]([\w-]+[\\\/])*\w+.as/ig;
    var newStack:String = <span style="color: #0000ff;">new</span> String(<span style="color: #800000;">"\n"</span>);
<span style="color: #0000ff;">    for</span> (var i:<span style="color: #0000ff;">int</span> = <span style="color: #800000;">0</span>; i &lt; lines.length; i++) {
        var line:String = lines[i];
        line = line.replace(regEx, <span style="color: #800000;">""</span>);
        line = line.replace(<span style="color: #800000;">"[:"</span>, <span style="color: #800000;">" [line:"</span>);
        newStack = newStack + line + <span style="color: #800000;">"\n"</span>;
    }
<span style="color: #0000ff;">    return</span> newStack;
}

Alrighty, now you’re set to do your own asserts. Next time, I’ll talk about unit testing and Actionscript3.
So, see you in a bit for a new episode of “Asserting the Basics”.

Happy coding, Manuel

Popularity: 12% [?]

3 comments

  1. steffenj says:

    if (assert(..)) return;

    Ouch! It really hurts me to see that. So you don’t want your function to crash? That’s ok if it’s a utilitarian function, like “GetDebugName” whose output is completely optional. Maybe even for something like a “Goto” command. So what if the NPC doesn’t move, as long as the game doesn’t crash. Or loading an asset, as in this case, then just use the default asset (checkerboard texture or default cube model or whatever).

    Good. But as a general thing to build your asserts with return values, i don’t know. First of all, there’s a very common advice called “don’t put logic inside your asserts”. I would turn this around and say “don’t build your logic around asserts”. Most often, if the assert fails, the game already is in an undefined state and shouldn’t continue.

    But then again, and maybe that’s the whole issue here, you are trying to build unit tests around your game code, and without some “softness” of error reporting (including delayed or disabled display of assert messages) it’s really hard to get right. Maybe that should have been stressed more.

    As a general rule of thumb i would say that unless you are 100% sure that exiting a function prematurely doesn’t corrupt the game state, don’t use assertion return values in your code flow.

    Your example is just a more convenient way to do the following:

    CClass* pPointer = GetPointer();
    Assert(pPointer);
    if (pPointer != NULL) { … pPointer used here … };

    I would prefer the explicitness in such cases.

  2. Manuel says:

    Hi Steffen,

    Thanks for your insight! You are absolutely right! I agree with you in every respect … if I’d be working in a C or C++ environment like you do. I do know those rules you pointed out. Still, I decided to violate them and there are good reasons to do so:

    (1) Any return-logic inside an assert will never be removed.
    We do not work with a pre-processor. The asserts stay in the production code. Yet, we definately try to make sure, that asserts do not have any other side effects, though!

    (2) Early-outs prevent Flash from throwing any exceptions. While using exceptions is quite handy in a debug-player, they will silently crash any non-debug players without any message whatsoever. Doing it the way I decribed, we get a decent error message even when the game crashes in a web browser.

    (3) There is no way (that I know of) to savely show an assert message and then stop the processing of a Flash application as you can e.g. on PC or consoles. We solved this by simply leave the function early and then show a huge pop-up containing the assertion-message (in the next frame, which is simply the way flash works). The pop-up makes it impossible to continue to work with the game (although it still runs in the background). It doesn’t matter if the game state is corrupted afterwards, you won’t be able to see it.

    (4) And it is a more convenient way to do the code-snipped you mentioned. ;)

    Thinking about it makes me wonder if I should have given the whole thing a different name. This would prevent people from thinking I am an idiot for breaking the most vital rules associated with asserts. ;)

    Cheers,
    Manuel

  3. steffenj says:

    Ok, i admit somewhere i forgot that you’re working with ActionScript. Obviously 3 mentions of AS weren’t enough for me. ;)

    Thanks especially for clarifying (2), that sounds a lot more reasonable to me now.

Leave a Reply

Your email address will not be published.

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong> <pre lang="" line="" escaped="" highlight="">