TweetFollow Us on Twitter

Using Assert()

Volume Number: 13 (1997)
Issue Number: 12
Column Tag: Beginner's Corner

Using Assert()

by Peter N. Lewis, Perth, Australia

Understanding assertions and how and when to use them

Introduction

Many programmers believe it is impossible to write bug free code. They just assume bugs are a part of life and that beta testers and QA departments (or even end users!) will find and report the bugs which will then (hopefully) be tracked down and resolved. I'm not sure I believe it is possible to write bug free code, but one thing I certainly believe is that it will not happen without a conscious effort on the part of the programmer.

Once you have decided that writing bug free code is a worthwhile goal, the first and most important tool at your disposal is the "Assertion". An assertion is simply a piece of code that validates part of the state of your program, and alerts you if something is wrong. This article describes assertions, why you want to use them, what they are, when and where to use them, and how you implement them. Throughout this article I will use examples in Pascal or C, but the concepts apply to almost any language.

Why You Should Use Assertions

Bugs come in many shapes and sizes, but there is a general rule of thumb that the earlier you detect a bug the less time it takes to fix it:

  • If you detect it as you are typing, it takes basically no time.
  • If you detect it during syntax check, compile, or link, it takes only a few seconds.
  • If you detect it as soon as the program launches, it takes only a minute.
  • If you detect it in your own testing, you probably will not waste much time, especially if an assertion fires to tell you exactly what went wrong.
  • If you send it to your beta testers/QA department, you are wasting days (and other people's time).
  • If you ship it and your end users find the bugs, the results can be arbitrarily bad. (Imagine your company going out of business because of bad reviews of your buggy product!)

Assertions make it easier to detect bugs earlier. By automatically detecting bugs you can find them before the bug has a chance to cascade and destroy the evidence. The more liberally you use assertions, the more quickly you will find bugs. And because assertions can be "compiled out" of your code (I'll show you how to do this in C and Pascal later), assertions only slow down your beta versions so you are free to use lots of them.

What is an Assertion?

At its most basic form, an assertion is simply a procedure that takes a boolean parameter and reports (to the programmer) if the boolean is false, for example:

procedure Assert( must: boolean );
    begin
    if not must then begin
      DebugStr( 'Assertion failed;sc' );
    end;
  end;

You can report the error using any method you like (DebugStr, Alert, writeln/printf, etc), and can include any extra information you want (such as the source file and line or a text message explaining what happened). I generally use DebugStr since it will work even in interrupt level code, but it does require you install MacsBug or some other low level debugger. Also, since the Metrowerks Debugger can catch DebugStr and leave you pointing at exactly the place where the assertion failed (once you step out of the Assert function), there is no need to include an explanatory message.

It is important to note that assertions are not a form of error checking. Assertions exist to detect programatical errors, they are not useful for detecting real life error conditions like disk or network errors -- errors such as these must be detected, handled and reported by error checking code that remains in the shipping version and that reports to the user in a helpful manner. Assertions are to help you as the programmer, your users should never see them if you do your job properly.

When and Where to Use Assertions

The short answer is to use assertions everywhere. Anywhere you are using facts about your programs state that are not obvious from the proceeding lines of code, you should consider using an assertion to confirm that the "facts" really are true. The most important places are:

  • At the start of each procedure (check that the parameters are acceptable).
  • At the start of each loop (check that the loop invariants hold true).
  • At the end of each loop (check that the loop has done its job).
  • At the end of each procedure (check that the procedure has done its job).
  • Before using any pointer (check that the pointer is not nil).
  • Before using any structure (check that the structure is valid).

For example, say we want to write a routine that accepts two string pointers, source and dest, where the source is suppose to be an 8 character string of lowercase letters, and its job is to uppercase the string and store it in dest. With assertions added, we might write it like this:

procedure Uppercase( source: StringPtr; dest: StringPtr );
  const
    required_length = 8;
  var
    i: integer;
begin
  Assert( (source <> nil) & (length(source^) = required_length) );
  Assert( dest <> nil );    // Ideally we would like to test that dest^
                          // is long enough
  Assert( source <> dest );  // Ideally we would like to test that they 
                          // do not overlap
  dest^[0] := chr(required_length);
  for i := 1 to required_length do begin
    Assert( source^[i] in ['a'..'z'] );
    dest^[i] := UpCase( source^[i] );
  end;
  Assert( EqualString( source^, dest^, false, true ) );
end;

So, we start off checking the preconditions (that source is not nil and is the right length and that dest is not nil). We should really also check that source is made up entirely of lowercase letters, but we defer that to the loop where it is easier to check. And then at the end we check that we have done the job - it is a pretty loose test, (checking only that dest is case insensitively equal to source), but it at least checks that we have done something like what we said we would do.

We also assert that source must not be the same as dest -- it would be easy enough to ensure that the code worked properly in this case, but I don't feel like checking the code for that case so instead I save myself some work and simply disallow it - if at some future time a programmer tries to use this routine with that case they will immediately get an assertion, they can then either fix their code to use two strings, or update Uppercase to ensure this code works properly in the case where source and dest are the same.

This is another use for assertions, they are a form of self documenting code. If I simply added a comment to the documentation that source and dest are not allowed to overlap, a programmer might not notice and might accidentally use the procedure in this manner. Worse, the code might work sometimes but not always. It is much better to enforce the restriction in the code so that a any future user of this routine immediately learns of their mistake.

Compiler Generated Assertions and Warnings

It is worth noting that the compiler is capable of generating some assertions of its own, and you should take advantage of these whenever possible. Take the time to go through the compiler settings and ensure all possible warnings and checks are enabled. For example, the compiler may have range checking or nil checking options. It may also be able to detect things like unused variables, variables used before they are initialised and functions that do not return results. These warnings and errors can save you a lot of time so turn them on!

Duplicate your code and check your data structures

In the example above, we checked at the end of the routine that dest was case insensitively equal to source. We can actually go further and check that dest is exactly what it should be by duplicating the routine, something like this:

var
    i: integer;
{$ifc do_debug}
    test_string: Str255;
{$endc}
begin
  ...
{$ifc do_debug}
  test_string := source^;
  UpperString( test_string, false );
  Assert( dest^ = test_string );
{$endc}
end;

Now when this routine executes we don't have to wonder if it is doing the right thing and hope that we spot the problem if it isn't. We know it works correctly every time because if it ever fails it will immediately notify us of the problem.

Note how the debugging variable test_string is compiled out if do_debug is false. This is for two reasons, first it avoids the unused variable warning when you build the non-debugging version, but more importantly it makes it clear that test_string is for debugging purposes only and ensures it is not accidentally used in the "real" code.

Many programs have a single important job that they perform. For example, in a drawing program it might be rendering to the screen, in a spreadsheet it might be the recalculation engine, in a game it might be updating the game state. These all involve taking some input state and mapping it to a new state. You can use assertions to validate your code in two important ways:

  • By checking that the state is valid before and after the update.
  • By duplicating the engine and running both and ensuring they get the same results.

Checking the state is generally pretty easy, you just go through each variable in each structure and ensure that it is within acceptable ranges. For each interaction, you ensure the variables are compatible. For example in a drawing package, you might assert that each object is within range, that each rectangle has four points, that each colour or pattern is valid, that each group is made up of objects that are inside the group, and so forth.

Duplicating the code engine can be a fair amount of work, but it can also be very valuable. Often these engines must be very efficient so they end up being highly optimised. At the start of the project, you might write a very simple engine as a proof of concept - rather than throw this engine away, keep it and execute it in parallel with the new optimised engine you write and then check that the results are identical. For example, in a drawing package, you might do something like this:

procedure UpdateOffscreenWorld( offscreen: GWorldPtr );
  var
    rgn: RgnHandle;
{$ifc do_debug}
    debug_world: GWorldPtr;
{$endc}
begin
  FindChangeRegion( rgn );
  RedrawOnlyChanges( offscreen, rgn );
  DisposeRgn( rgn );
{$ifc do_debug}
  MakeNewOffscreenWorld( debug_world );
  DrawEverything( debug_world );
  Assert( IdenticalBits( offscreen, debug_world ) );
{$endc}
end;

How to Implement Assertions

As described above, the basic assertion is simply a procedure that takes a boolean and reports if the boolean is false. However, since computing the assertion condition may be computationally expensive and since it does not in any way affect the execution of the program, it is desirable to have them automatically removed from your code before you ship the final version. To do this, we use compiler macros (#define in C, {$definec} in Pascal) like this:

#ifndef do_debug
#define do_debug 1
#endif

#if !do_debug
#define Assert(b)
#else
#define Assert(b) AssertCode(b)
#endif

#if do_debug
void AssertCode( Boolean b );
#endif

Or

{$ifc not defined do_debug}
{$setc do_debug := 1}
{$endc}

{$ifc not do_debug}
{$definec Assert(b)}
{$elsec}
{$definec Assert(b) AssertCode(b)}
{$endc}

{$ifc do_debug}
  procedure AssertCode (b: boolean);
{$endc}

First, we default do_debug to true. Then we define the macro, mapping Assert( condition ) to either nothing at all or to a call to AssertCode -- the actual procedure is renamed to AssertCode so that it's definition is not mangled by the Assert macro. For final builds you can use a prefix file to set do_debug to false and then recompile all your source.

There are two things you have to be careful with. First, since the macro mechanism effectively removes the Assert lines from your program, you must never use a function with a side effect in your assertion. For example, you might be tempted to do something like this:

Assert( NewGWorld( ... ) == noErr );

However, when you compile that with do_debug set to false, the line will disappear and the GWorld will not be created. Instead you should write:

err = NewGWorld( ... );
Assert( err == noErr );

For this reason, and just for general safety, it is import that you set do_debug to false for at least your last few beta builds (after you have resolved all bugs that cause assertions to fire of course!) so that you can get some serious testing with a build that is almost identical to the final build.

Extending Assertions

Assertions can be as simple or as complicated as you choose to make them. I have described a very simple implementation, but you can expand on the concept in several ways.

You could use a more interesting reporting mechanism than simple DebugStrs such as Alerts or sending the reports out a serial or TCP connection. You could build on what you want to assert, such as asserting that a pointer or file reference or TCP stream is valid. You could also add information to the assertion such as the file name or line number or a message describing the cause of the assertion.

Always keep in mind that you want to use assertions frequently in all your code so there may be some constraints on what you can do in the Assert routine if you are writing any low level code like interrupt routines or drivers, and you should avoid making the act of including an assertion overly tedious (that is why I generally don't include an explanatory message in my assertions).

You can also look around for other assertion libraries -- CodeWarrior's MSL and PowerPlant both include support for assertions.

Conclusion

The single most important question to ask yourself whenever you find a bug in your code is "How could I have prevented this bug?" or at the very least "How could I have found this bug earlier?" Assertions are one way of finding bugs very early. Steve Maguire's excellent book, Writing Solid Code, describes assertions and many other ways of finding or preventing bugs (including stepping through any new code you write, writing good interfaces, choosing safe/debugable implementations).

These techniques really do work. They will save you time and frustration, and they will dramatically increase the level of confidence you have in your code. Where you would previously have said "this routine probably does more or less what I expect" you can say with confidence that it does exactly what it is suppose to do, and if it ever fails, you'll hear about it immediately. So if you are going to write code (especially if it will end up running on my Mac!) go and read Writing Solid Code, get an attitude adjustment, and start writing bug free code!

References

  • Writing Solid Code by Steve Maguire. This book is the definitive reference in my opinion. I believe all programmers should read this book, it does a great job at explaining this topic and at motivating the reader to strive to write bug-free code.
  • Effective C++ by Scott Meyers. C++ has many ways to introduce bugs in to your code that are very difficult to debug. Effective C++ describes many of these and how to avoid them.

Peter N. Lewis is a successful shareware author. He founded Stairways Software Pty Ltd in 1995 and specializes in Macintosh TCP/IP products but has been known to diversify into other areas.

 

Community Search:
MacTech Search:

Software Updates via MacUpdate

Airmail 3.3.2 - Powerful, minimal email...
Airmail is an mail client with fast performance and intuitive interaction. Support for iCloud, MS Exchange, Gmail, Google Apps, IMAP, POP3, Yahoo!, AOL, Outlook.com, Live.com. Airmail was designed... Read more
Numi 3.15.1 - Menu-bar calculator suppor...
Numi is a calculator that magically combines calculations with text, and allows you to freely share your computations. Numi combines text editor and calculator Support plain English. For example, '5... Read more
Airmail 3.3.2 - Powerful, minimal email...
Airmail is an mail client with fast performance and intuitive interaction. Support for iCloud, MS Exchange, Gmail, Google Apps, IMAP, POP3, Yahoo!, AOL, Outlook.com, Live.com. Airmail was designed... Read more
Numi 3.15.1 - Menu-bar calculator suppor...
Numi is a calculator that magically combines calculations with text, and allows you to freely share your computations. Numi combines text editor and calculator Support plain English. For example, '5... Read more
TextSoap 8.4.1 - Automate tedious text d...
TextSoap can automatically remove unwanted characters, fix up messed up carriage returns, and do pretty much anything else that we can think of to text. Save time and effort. Be more productive. Stop... Read more
TextSoap 8.4.1 - Automate tedious text d...
TextSoap can automatically remove unwanted characters, fix up messed up carriage returns, and do pretty much anything else that we can think of to text. Save time and effort. Be more productive. Stop... Read more
Backblaze 4.3.0.44 - Online backup servi...
Backblaze is an online backup service designed from the ground-up for the Mac. With unlimited storage available for $5 per month, as well as a free 15-day trial, peace of mind is within reach with... Read more
Numi 3.15 - Menu-bar calculator supports...
Numi is a calculator that magically combines calculations with text, and allows you to freely share your computations. Numi combines text editor and calculator Support plain English. For example, '5... Read more
EtreCheck 3.3.3 - For troubleshooting yo...
EtreCheck is an app that displays the important details of your system configuration and allow you to copy that information to the Clipboard. It is meant to be used with Apple Support Communities to... Read more
BusyContacts 1.1.8 - Fast, efficient con...
BusyContacts is a contact manager for OS X that makes creating, finding, and managing contacts faster and more efficient. It brings to contact management the same power, flexibility, and sharing... Read more

Latest Forum Discussions

See All

The best new games we played this week
We were quite busy this week. A bunch of big mobile games launched over the past few days, alongside a few teeny surprises. There're lots of quality games to load your phone with. We've gone and picked out five of our favorites for the week. [... | Read more »
Magikarp Jump beginner's guide
Magikarp Jump is a mystifying little game. Part Tamagotchi, part idle clicker, there's not a whole lot of video game there, per se, but for some reason we can't help coming back to it again and again. Your goal is to train up a little Magikarp to... | Read more »
Goat Simulator PAYDAY (Games)
Goat Simulator PAYDAY 1.0 Device: iOS Universal Category: Games Price: $4.99, Version: 1.0 (iTunes) Description: ** IMPORTANT - SUPPORTED DEVICES **iPhone 4S, iPad 2, iPod Touch 5 or better Goat Simulator: Payday is the most... | Read more »
GRID Autosport delayed until autumn
Sorry mobile racing fans -- GRID Autosport has been delayed a few months. The game is now expected to launch this fall on iOS. Feral Interactive announced that they wanted more time to work on the game's UI and overall performance before launching... | Read more »
Zombie Gunship Survival Beginner's...
The much anticipated Zombie Gunship Survival is here. In this latest entry in the Zombie Gunship franchise, you're tasked with supporting ground troops and protecting your base from the zombie horde. There's a lot of rich base building fun, and... | Read more »
Mordheim: Warband Skirmish (Games)
Mordheim: Warband Skirmish 1.2.2 Device: iOS Universal Category: Games Price: $3.99, Version: 1.2.2 (iTunes) Description: Explore the ruins of the City of Mordheim, clash with other scavenging warbands and collect Wyrdstone -... | Read more »
Mordheim: Warband Skirmish brings tablet...
Legendary Games has just launched Mordheim: Warband Skirmish, a new turn-based action game for iOS and Android. | Read more »
Magikarp Jump splashes onto Android worl...
If you're tired ofPokémon GObut still want something to satisfy your mobilePokémon fix,Magikarp Jumpmay just do the trick. It's out now on Android devices the world over. While it looks like a simple arcade jumper, there's quite a bit more to it... | Read more »
Purrfectly charming open-world RPG Cat Q...
Cat Quest, an expansive open-world RPG from former Koei-Tecmo developers, got a new gameplay trailer today. The video showcases the combat and exploration features of this feline-themed RPG. Cat puns abound as you travel across a large map in a... | Read more »
Jaipur: A Card Game of Duels (Games)
Jaipur: A Card Game of Duels 1.0 Device: iOS Universal Category: Games Price: $1.99, Version: 1.0 (iTunes) Description: ** WARNING: iPad 2, iPad Mini 1 & iPhone 4S are NOT compatible. ** *** Special Launch Price for a limited... | Read more »

Price Scanner via MacPrices.net

Memorial Day savings: 13-inch Touch Bar MacBo...
B&H Photo has the 2016 Apple 13″ Touch Bar MacBook Pros in stock today and on sale for up to $150 off MSRP. Shipping is free, and B&H charges NY & NJ sales tax only: - 13″ 2.9GHz/512GB... Read more
Apple refurbished 13-inch MacBook Airs availa...
Apple has Certified Refurbished 2016 13″ MacBook Airs available starting at $849. An Apple one-year warranty is included with each MacBook, and shipping is free: - 13″ 1.6GHz/8GB/128GB MacBook Air: $... Read more
Apple restocks refurbished 11-inch MacBook Ai...
Apple has Certified Refurbished 11″ MacBook Airs (the latest models recently discontinued by Apple), available for up to $170 off original MSRP. An Apple one-year warranty is included with each... Read more
12-inch 1.2GHz Retina MacBooks on sale for up...
B&H has 12″ 1.2GHz Retina MacBooks on sale for up to $150 off MSRP. Shipping is free, and B&H charges NY & NJ sales tax only: - 12″ 1.2GHz Space Gray Retina MacBook: $1449.99 $150 off... Read more
15-inch 2.7GHz Silver Touch Bar MacBook Pro o...
MacMall has the 15-inch 2.7GHz Silver Touch Bar MacBook Pro (MLW82LL/A) on sale for $2569 as part of their Memorial Day sale. Shipping is free. Their price is $230 off MSRP. Read more
Free Tread Wisely Mobile App Endorsed By Fath...
Just in time for the summer driving season, Cooper Tire & Rubber Company has announced the launch of a new Tread Wisely mobile app. Designed to promote tire and vehicle safety among teens and... Read more
Commercial Notebooks And Detachable Tablets W...
Worldwide shipments of personal computing devices (PCDs), comprised of traditional PCs (a combination of desktop, notebook, and workstations) and tablets (slates and detachables), are forecast to... Read more
Best value this Memorial Day weekend: Touch B...
Apple has Certified Refurbished 2016 15″ and 13″ MacBook Pros available for $230 to $420 off original MSRP. An Apple one-year warranty is included with each model, and shipping is free: - 15″ 2.6GHz... Read more
13-inch MacBook Airs on sale for up to $130 o...
Overstock.com has 13″ MacBook Airs on sale for up to $130 off MSRP including free shipping: - 13″ 1.6GHz/128GB MacBook Air (sku MMGF2LL/A): $869.99 $130 off MSRP - 13″ 1.6GHz/256GB MacBook Air (sku... Read more
2.8GHz Mac mini available for $973 with free...
Adorama has the 2.8GHz Mac mini available for $973, $16 off MSRP, including a free copy of Apple’s 3-Year AppleCare Protection Plan. Shipping is free, and Adorama charges sales tax in NY & NJ... Read more

Jobs Board

*Apple* Retail - Multiple Positions - Apple,...
Job Description: Sales Specialist - Retail Customer Service and Sales Transform Apple Store visitors into loyal Apple customers. When customers enter the store, Read more
*Apple* Media Products - Commerce Engineerin...
Apple Media Products - Commerce Engineering Manager Job Number: 57037480 Santa Clara Valley, California, United States Posted: Apr. 18, 2017 Weekly Hours: 40.00 Job Read more
*Apple* Media Products - Commerce Engineerin...
Apple Media Products - Commerce Engineering Manager Job Number: 57037480 Santa Clara Valley, California, United States Posted: Apr. 18, 2017 Weekly Hours: 40.00 Job Read more
Best Buy *Apple* Computing Master - Best Bu...
**509643BR** **Job Title:** Best Buy Apple Computing Master **Location Number:** 001482- Apple Valley-Store **Job Description:** **What does a Best Buy Apple Read more
*Apple* Media Products - Commerce Engineerin...
Apple Media Products - Commerce Engineering Manager Job Number: 57037480 Santa Clara Valley, California, United States Posted: Apr. 18, 2017 Weekly Hours: 40.00 Job Read more
All contents are Copyright 1984-2011 by Xplain Corporation. All rights reserved. Theme designed by Icreon.