How to Use Runtime Asserts to Find Bugs
Runtime asserts, not the asserts in unit tests, are a valuable debugging tool for any game developer. Today’s article shows you what they are, how to use them, how not to use them, and how they work. Read on to learn more!
Today’s article is inspired by a CppCon talk by John Lakos, but there won’t be any C++ today. Instead, this article is all about using asserts in Unity with C#!
So what is an assert? It’s simply a function that you call to say that your program expects something to be a certain way. For example, you might assert that player.Health >= 0
because players can’t have negative health. You basically want to assert that you haven’t made any programming mistakes.
So what do you not want to assert? Well, you don’t want to assert when something is an error that your program is going to handle. You’d never want to assert that the device has a network connection (i.e. with NetworkReachability
) since you’re probably going to handle the user’s connection dropping out during a multiplayer game.
In short, assert to detect your own programming logic mistakes that you probably need to fix in source code rather than at runtime by some error-handling code. Do not assert for error conditions that your program can handle without source code changes. That is the job for exceptions, Either
, or error codes.
In the above CppCon talk, John discusses design by contract which he practices formally by documenting the preconditions and postconditions of each function, among other steps. Even if you don’t have such a formal process, all of your functions still have an implicit contract. Your Divide
function will still crash if the denominator is zero. Your player still can’t buy an item for more gold than is in his inventory. Even if you don’t state the contract, you can still use asserts to make sure your code is abiding by it.
Now that you know when and when not to use asserts, let’s look at what they do. Unity added asserts in version 5.1 and wrote a good introduction article to them. That’s good because System.Diagnostics.Assert
doesn’t seem to do anything in Unity.
Using Unity’s asserts is very simple: just call static functions of the static UnityEngine.Assertions.Assert
class. There are a few functions depending on what you want to assert, but you can often opt for the classic IsTrue
. Here are some quick examples:
using UnityEngine.Assertions; class MathUtils { int Divide(int numerator, int denominator) { // Can't divide by zero Assert.AreNotEqual(0, denominator); return numerator / denominator; } } class Player { private int gold; void BuyItem(int cost) { // Must have enough gold Assert.IsTrue(gold >= cost); gold -= cost; } }
You can check out the API docs for all the kinds of asserts.
By default, all of the asserts won’t throw an exception. Instead, they simply log an error to the debug log and continue on with the code. That’s pretty unusual for an assert function, so it may take you by surprise if you’re used to another assert library. To fix this, set Assert.raiseExceptions
to true somewhere in your code. The start of a MonoBehaviour
in your first scene is usually a good choice.
If at this point you’re thinking that asserts are going to slow your code to a crawl, you’re probably wrong. Unity uses the [Conditional]
attribute to check if UNITY_ASSERTIONS
is defined. By default, it’s defined in development builds and not defined in non-development builds. You can force it to be enabled in non-development builds by setting BuildOptions.ForceEnableAssertions
. Unfortunately, there’s no way to disable it in development builds.
So what happens when you call Assert.IsTrue
? It’s all C# code so it’s easy to decompile UnityEngine.dll
and see! Here’s how it looks as of 5.4.1: (trimmed and commented by me)
namespace UnityEngine.Assertions { public static class Assert { // Version where you don't specify a custom message makes an additional function call // to call the version with a custom message. You can skip this by passing null for // your custom message. [Conditional("UNITY_ASSERTIONS")] public static void IsTrue(bool condition) { Assert.IsTrue(condition, null); } // Custom message version [Conditional("UNITY_ASSERTIONS")] public static void IsTrue(bool condition, string message) { // If the assert passes, it's just this one 'if' check if (!condition) { // All the asserts call the internal Fail function when // their test doesn't pass. They all call a function on // the internal AssertionMessageUtil class to build the // message string explaining why the assert failed. That // creates several strings totaling about 1KB of garbage. Assert.Fail(AssertionMessageUtil.BooleanFailureMessage(true), message); } } private static void Fail(string message, string userMessage) { // Debugger attachment is checked for every assert failure // If attached, you get an exception regardless of your setting. if (Debugger.IsAttached) { throw new AssertionException(message, userMessage); } // Only throws exceptions when enabled or debugger is attached if (Assert.raiseExceptions) { throw new AssertionException(message, userMessage); } // This won't ever happen if (message == null) { message = "Assertion has failed\n"; } // Adds your custom message if you specified one if (userMessage != null) { message = userMessage + '\n' + message; } // The message is only logged if you disable exceptions and aren't // running the debugger. Otherwise, this won't be reached and // therefore won't cause problems with code running outside the // Unity editor. You should be safe to run code with asserts in // an IDE like MonoDevelop or Visual Studio. UnityEngine.Debug.LogAssertion(message); } } }
Most of the code is there to handle failed asserts. If your assert passes, it’s just one if
check and no garbage creation. If you don’t pass a custom message, there’s an additional function call. None of that matters much in the grand scheme and your passing asserts will probably never be a performance concern. Failing asserts, on the other hand, create about 1KB of garbage and involve a dozen or so function calls and either an exception or a log message. The performance of failing asserts is therefore terrible and you have one more reason to avoid a lot of failing asserts. That should truly be a rare case, not something to happen every frame.
That wraps up today’s discussion of asserts. They can be really handy for quickly finding bugs in your code, but make sure to not use them as a replacement for handling errors your app can recover from. Unity’s got built-in support for assertions and they’re fast and flexible as long as they pass. So why not start sprinkling them throughout your codebase to help find some bugs?
Do you use asserts already? Do you think you’ll start using them after reading this article? Share your thoughts in the comments!