UI and Processing Don't Mix

Have you ever attempted to use someone else's library in your code only to find yourself jumping through hoops and wondering if it would have just been easier to write it yourself? There are lots of things that can cause this and one in particular has me fustrated today: Mixing user interface code with processing code. Resusable code should have a clear distinction between code that interacts with the user and code that does processing.

A good rule of thumb is to consider how your program would operate in these three scenarios: graphical interaction with the user, command line interaction with the user, headless batch processing.  Each one may have a different source of input and output.  Consider how an error is typically displayed in each situation: a modal dialog, stderr, and a log file. Let's take the task of calculating widgets and see how to abstract the user interaction out of the processing code.

double computeWidgets ()
    {
    ...
    if (0 == numWidgets)
        {
        showErrorDialog ("No widgets");
        return 0.0;
        }

    for (int w = 0; w < numWidgets; w++)
        {
        addWidgetToResult (widgets[w]);
        updateProgressBar (((float) w) / numWidgets);
        }
    ...
    }
    
    ...
    double result = computeWidgets();

The above code is doing 3 things:  displaying an error if there is a problem, doing a calculation, and updating a progress bar.  Two of the three: displaying the error message and updating the progress bar; are related to user interaction.  Any caller who wants to use this function to calculate widgets gets stuck with the UI behavior of this function as well.  The use of showErrorDialog may be appropriate for a GUI interaction but not for a console or headless application.  In those cases it would be better to print the message to the stderr or write it to a log file.

typedef void (*progressCallback) (float);
double computeWidgets (std::string& message, progressCallback callback)
    {
    ....
    if (0 == numWidgets)
        {
        message = "No widgets";
        return 0.0;
        };

    for (int w = 0; w < numWidgets; w++)
        {
        addWidgetToResult (widgets[w]);
        if (NULL != callback)
            callback (((float) w) / numWidgets);
        }
    ...
    }
    
    ...
    std::string message;
    double result = computeWidgets (message, &updateProgressBar);
    if (0 != message.size())
        showErrorDialog (message.c_str());

With this implementation the caller of computeWidgets can decide to do with the error message and what action to take on progress updates.  Another programmer could have the error sent as an SMS message to their phone and use the update call back to update a web page with the progress of the calculation.