Team LiB
Previous Section Next Section

Using Asserts Aggressively

Some developers believe in asserting only fatal errors—the kind of errors that would normally cause a program to crash. For instance:

using System.Diagnostics;
using System;

class AssertTest {
    public static object FunctionThatReturnsAnObject() {  ...}
    public static void Main() {
        object o = FunctionThatReturnsAnObject();
        //Program will surely crash if that ever returns null.
        Debug.Assert(o != null);
        if (o == null) {
            throw new Exception("Way bad");
        }
        ...
    }
}

The preceding assert is correct usage. Not only does the assert warn about problems at the point of failure, but there's also error handling to deal with the case in Release mode where asserts don't fire. Nothing at all wrong with this code. But some developers think this is the only type of code where asserts should be used. I disagree. I believe you should also assert on non-fatal errors, too. In fact, you should use asserts on just about everything. Use the power of asserts to track down every bug, not just the ones that crash your program. For example, suppose you're writing code for an online store. Your code fetches the list of products your store sells from a database using ADO.NET:

using System.Data;
using System.Data.SqlClient;
using System.Diagnostics;
class SqlTest {
    public static void Main() {
    string dbConnStr = "Server=(local);Database=myDb;uid=myUid;pwd=myPwd;";
        SqlConnection dbConn = new SqlConnection(dbConnStr);
        dbConn.Open();
        string selectStr = "SELECT productName FROM productsTable";
        SqlDataAdapter da = new SqlDataAdapter(selectStr, dbConn);
        DataSet ds = new DataSet();
        da.Fill(ds, "productsTable");
        //We expect that table will NEVER be empty, so assert:
        Debug.Assert(ds.Tables["productsTable"].Rows.Count > 0);
        foreach (DataRow row in ds.Tables["productsTable"].Rows) {
            ... //Do stuff with each row
        }
        dbConn.Close();
    }
}

Suppose the list of rows in productsTable came back empty. That's not necessarily a bug in this function, since it's perfectly legal for a database table to be empty. And it's certainly not a fatal error—your GUI won't crash; it will simply display zero products. But is it reasonable to think your store will ever sell zero products? Probably not. If that DataSet ever contains zero rows, then something is clearly wrong. Someone accidentally deleted the contents of your database, or maybe your database query was wrong. Either way, it's definitely an error you want to know about. Add an assert that the number of rows will be greater than zero. Add asserts on everything you think will always be true, no matter how minor or non-fatal it is. Even if you think, "Oh, this could never happen," add an assert anyway.

Or what about this one? Suppose your program saves some data to an XML configuration file and later reads it in. Since you wrote that configuration file, you know there ought to be a tag indicating whether the user wants the AdvancedView feature turned on or not. Suppose your program reads in that file and realizes that tag is missing. Is this a fatal error? Absolutely not—well-written code should be able to easily deal with this case by just assigning the default behavior to the AdvancedView feature. But is this an unexplained bug that should worry the programmer? Definitely. If the file is missing this line, then who knows what else is wrong, too? Even though this is a non-fatal error, it's still something that needs an assert.


Team LiB
Previous Section Next Section