Data-Driven Programming

C, C++ No Comments »

While this is mentioned in an offhand way in the Assert Yourself chapter of Writing Solid Code by Steve Maguire (an excellent, if dated, read), consider using data-driven programming to simplify unwieldly if and switch blocks. For example, have you ever written code like this?

const char* GetString(int indx)
{
    if (indx == 1) {
        return "foo";
    } else if (indx == 2) {
        return "bar";
    } else if (indx == 3) {
        return "baz";
    } ...
}

The problem with this code is that it is repetitive, makes adding or changing entries difficult, and it can quickly get out of hand. For example, imagine trying to manage over 100 entries in this if block. Now imagine trying to change the indx search method, or the type of indx, or adding many other data points also searchable by indx.

To simplify, we can separate the action from the data by using data-driven programming — put the data into a data structure (I chose a static array, but you could use a binary tree, a hash table, or even an external file) and have the code search this data structure to determine the action to perform. For example:

#define ARRAYSIZE(x) ( sizeof(x) / sizeof(x[0]) )

struct GetStringEntry
{
    int indx;
    const char* str;
};

// These values should be set at compile-time, so no run-time
// costs of setting up the array (which may not be true for other
// data structures).
static GetStringEntry[] g_entries =
{
    { 1, "foo" },
    { 2, "bar" },
    { 3, "baz" },
    ...
};

const char* GetString(int indx)
{
    // Search for the entry with the correct index
    for (GetStringEntry* entry = g_entries;
         entry != g_entries + ARRAYSIZE(g_entries);
         ++entry)
    {
        if (entry->indx == indx)
            return entry->str;
    }

    // Handle error
}

Yes, there’s a lot of boilerplate code (much of which becomes unnecessary with other languages), but it allows great flexibility. Adding a record becomes trivial, and seeing the relationship between an individual indx and str is easy. Want to speed up searching g_entries? Make sure g_entries is sorted and use a binary search algorithm (such as STL’s lower_bound). Want to change indx to a string? Change the type of indx in struct GetStringEntry from an int to a const char*, the indx values in g_entries to strings, and the == comparison in GetString() to strcmp() — far less work than it would be for the original. Want to add another data point searchable by indx? Add a member to struct GetStringEntry, the new values to g_entries, and write another accessor function.

You can use this technique in even more complicated scenarios. For example, imagine a message dispatching system in which each struct contains the message identifier and a pointer to the function that will be executed when the message is received. Imagine searching for an entry by testing to see if the provided value has certain bits set (by using bitwise-and) rather than performing an equality comparison. Future maintenance programmers will thank you as they discover how easy it is to manage, add and modify records, and improve upon code which uses this technique.

WP Theme & Icons by N.Design Studio
Entries RSS Comments RSS Log in