Dr. Dobb's is part of the Informa Tech Division of Informa PLC

This site is operated by a business or businesses owned by Informa PLC and all copyright resides with them. Informa PLC's registered office is 5 Howick Place, London SW1P 1WG. Registered in England and Wales. Number 8860726.


Channels ▼
RSS

Tools

Dissecting Error Messages


Alek is a senior application developer at Intel. He can be contacted at alek.davis @intel.com.


Error messages are the most important pieces of information users get when encountering application failures. Good error messages identify the root cause of problems and help users correct them. Bad error messages cause confusion. As an enterprise application developer who frequently participates in software troubleshooting, I see many error messages—and most of them are bad. They are either too generic, ambiguous, misleading, or just plain wrong. Bad error messages cause us to spend long hours investigating problems, which could've been corrected in a matter of minutes had they only been accompanied by meaningful error messages.

In this article, I explain how to build good error messages and present examples showing how to report errors in C/C++ and C#. In the first part of the article, I outline the guidelines covering the most important, but frequently neglected, aspects related to error reporting. The second part describes code samples.

Errors & Error Messages

The right approach to error handling begins with application design. Before writing code, the project team must define a common error-handling methodology, which makes it easier for each developer to process errors. This methodology must include coding standards specifying how errors should be treated (generated, captured, displayed, and stored) and a common library (API) that takes care of the low-level error-handling minutiae. Having a defined standard and available API not only improves developer productivity, but gives us more time to think about the content (meaning) of error messages instead of the implementation details.

Building meaningful error messages is just one aspect of good error handling. Error processing also involves tracing, logging, debugging, and more. Whether you are building Windows GUI applications or web sites, make sure you display error messages in a consistent manner. For Windows GUI apps, use a standard dialog box, which lets users view error details and suggests ways to correct the problem if this information is available.

Displaying error messages in web apps presents more challenges. Unless you have a better approach, show messages at the top of the page or in a JavaScript pop-up dialog. If you include error messages at the top of the web pages, always display them in the same location (relative to page layout) and clearly identify messages as errors. To minimize work, implement the error display area as a common control responsible for rendering error information.

If you choose the JavaScript option, you can follow the example in Listing One. When the server-side code detects an error condition in this example, it calls a shared function, which renders client-side script displaying an alert box containing error information (notice how the code handles quotes and other special JavaScript symbols). You can test this example using the ASPX project (available electronically; see "Resource Center," page 3).

Applications that run without user interfaces (such as server-side applications) normally save error messages in error logs. Examples of error logs can be text files, Windows Event Logs, and databases. Some client-side programs can also use error logs for storing extended error information, such as exception stacks or error traces.

Because a large number of entries in Windows Event Log can make it hard to navigate, server-side programs should use Event Logs only for catastrophic errors and store messages in log files or databases. When storing error information in database tables, make sure that you use space efficiently. For example, when saving an error message returned by a database provider (such as OLE DB) in a 255-character column, store the error description first and details (name of the provider, ANSI code, and so on) last; otherwise, there may not be enough space to hold the description.

When writing error messages, think of the users who read it. Error details that make sense to one group of users can totally confuse another. The users reading your error messages include end users, help desk personnel, and application developers. End users may not know much about the application functionality and technology in general. Help desk representatives (and by "help desk" I mean support organizations that do not have access to the source code) usually know more about software than end users, but not as much as the application developers. Application developers know everything (at least, they think they do).

Error messages displayed to end users must be nontechnical and must not contain information that only makes sense to developers (such as exception call stack, error numbers, names of source files, and line numbers). Nor should details of server-side errors (say, failure of the application to retrieve data from a database) be passed to end users. For one thing, end users will not be able to correct server-side problems. Additionally, server-side error details can reveal information about the back end, thereby imposing a security risk. On the other hand, error messages displayed to end users must contain enough information to help correct the problems. For example, if an error occurs because users do not have the write permission to a local directory, the name of the directory and required permission must be included in the error message.

Most error messages read by support teams (both help desk and developers) typically come as a result of server-side errors. A server-side error message must include all details that help identify the root cause of the problem. Be careful with code-specific error details. Although application developers like to include exception stack, line numbers, and other code-specific information, these details are generally not helpful to most troubleshooters because they only make sense to programmers who have the source code in front of them. Because code-specific details can make it harder for nondevelopers to understand the meaning of an error message, I don't recommend mixing them with error descriptions. Either save these details in a separate file, or separate them from the human-readable error description.

What Are Good Error Messages?

Good error messages must identify the failed operation, describe the execution context, and specify the reason for failure. If the error reflects a common or anticipated problem, the message can also tell users how to fix it.

Consider an application that creates user accounts for a group of customers, where customer data are pulled from an external system. Account creation is a two-step process—creation of a user profile and a mailbox. Imagine that the application fails to create the mailbox for one user.

To identify the failed operation, an application must name the top-level task it has not been able to complete. Assuming that the problem does not cause failure for the whole batch, the top-level operation is user-account creation. The message must say something like: "Cannot create an account for user X," where X uniquely identifies the user. Identifying the user can help the support team to quickly pull customer data.

After describing the failed operation, the error message must specify the execution path (context) that caused the error. The execution context lists all logical operations that lead to the failure (excluding the top-most operation, which has already been mentioned). You can think of the execution path as the exception stack without code-specific details. In this example, the execution context can be: "Failed to create mailbox for Y," where Y specifies the customer's e-mail address. If the mailbox creation contains substeps, the description of all failed substeps on the execution stack must be appended. The description of each step in the execution context must be unique.

Finally, the reason causing the last step in the execution context to fail must be specified. This information is typically returned by the failed API. For example, it can be retrieved via the GetLastError and FormatMessage, or obtained from the exception details. The complete error message must read: "Cannot create an account for user X. Failed to create mailbox for Y. Mailbox already exists." Notice that all parts of the error message are written in complete sentences separated by periods, making it easier for the user to follow the steps that caused the failure.

To build a good error message, you need to provide sufficient details, but still avoid duplicate information. Achieving this goal is not easy because errors normally occur deep in the call stack so the type of information that must be returned by error-handling code may not be obvious. The challenge is to return sufficient information from every error handler on the stack.

For example, look at the pseudocode account creation example in Listing Two. If you are writing a function CreateMailbox, which message should the error handler of the CreateMailbox method pass to the caller? In particular, should it include "Failed to create mailbox for Y" or should this message be generated somewhere else? Keep in mind that a developer responsible for the implementation of the CreateMailbox method may not know who will call it.

If you face a similar dilemma, follow this rule: When returning an error message from a function, do not describe the main operation performed by this function. Instead, identify the step on which this function fails and include the error message returned by this step. If function A calls function B, which calls function C, and function C causes an error in step X, error messages returned by each of these functions should be similar to those in Table 1. Following this rule, the functions in the account creation example would return the error displayed in modified pseudocode in Listing Three.

Although I recommend not including code-specific details, sometimes they may be needed. If a problem is escalated to the development team, a developer may want to know such error details as the exception stack, line number, file name, and so on. To accomplish this, you can make the format of error messages customizable and adjust it at runtime.

Implementing Error Messages In C/C++

Compared to .NET languages, C/C++ does not offer rich error-handling facilities. You can pass error messages as function parameters or return them as exceptions using structured exception handling (SHE) or C++ exception handling. You can use GetLastError in conjunction with FormatMessage to get the description of the last failed system call. When making COM calls, you can obtain error information from the COM Error object or other COM sources, such as the OLE DB Error object.

The hard part about handling errors in C/C++ is memory management. To avoid dealing with memory allocation issues, you can use string classes available in MFC or STL, but this may not always be an optimal approach. In the following example, I show how to build messages in dynamically allocated memory buffers using the basic Win32 APIs (not relying on MFC or STL). I also explain how to process system and COM errors and pass error messages between functions.

The C/C++ sample project (available electronically) builds a static library that implements several methods that can be used to process error messages. To link this library to your project, add it to the project's library settings, making sure that you use the right release target (ANSI or Unicode). In the source code, include a reference to the common.h file, which is located in the Include folder of the project. It defines function prototypes and several helpful macros. Table 2 lists the library's functions related to error processing.

The BuildMessage function lets you create formatted messages of arbitrary sizes. It works similar to sprintf, but BuildMessage writes data to a dynamically allocated memory (on the heap). The function takes three parameters: address of a pointer to a memory buffer, which holds formatted messages; message format; and optional message arguments. There are three versions of BuildMessage that work on TCHAR, ANSI, and Unicode strings.

BuildMessage dynamically allocates memory if needed. To free memory allocated by BuildMessage, you can call the corresponding version of the DeleteMessage (or TryDeleteMessage) function. This is how BuildMessage can be used:

TCHAR* pszErrMsg = NULL;
for (int i=0; i<5; i++)
{
BuildMessage(&pszErrMsg, _T
("#%d: Error in file %s...")
, i, __FILENAME__);
_putts(pszErrMsg);
}
TryDeleteMessage(&pszErrMsg);

When using BuildMessage, follow three simple rules:

  • Never pass the address of a static character array (stack variable) as the first parameter. BuildMessage assumes that the first parameter references a heap variable, so it uses the _msize function to check the amount of memory allocated for it and calls realloc to allocate additional bytes if the buffer is too small. (If the memory buffer has already been allocated and is sufficient to hold the formatted message, BuildMessage reuses it.)
  • Before passing a nonallocated memory buffer, always set the value of the pointer (not the address) to NULL; otherwise, the function causes a memory-access violation.
  • Always free memory allocated by BuildMessage when you no longer need it.

Listing Four is pseudocode illustrating how to use BuildMessage to concatenate error information passed between function calls. I find it helpful to follow the convention of always using the first function parameter to pass error messages. If you prefer to pass error information via exceptions, you can still use BuildMessage to format message strings, just don't forget to free memory.

Processing Error Information In C/C++

Now that you have a method to easily format error messages, you can generate or retrieve error information via the GetWin32ErrorMessage, GetComErrorMessage, and SetComError functions. The first two methods can be used to retrieve error information from a system or COM call. SetComError provides a capability to return COM errors to COM client via the COM Error object.

GetWin32ErrorMessage returns the formatted description of the specified Windows error code. If the description is not found, it generates a generic message that includes the provided error number. The function takes one required and two optional parameters. The first parameter is used to hold the generated error message (similar to BuildMessage). The second parameter is used to pass the error number. If the error number is not provided or set to zero, GetWin32ErrorMessage calls GetLastError to retrieve the system error code. The third parameter indicates whether the error number should be included in the error message.

GetComErrorMessage is similar to GetWin32ErrorMessage, except in addition to retrieving the description of the HRESULT error value passed as a second (optional) parameter, it also attempts to obtain information from the COM Error object (IErrorInfo interface). If the HRESULT value indicates success, it is ignored and the function only gets the information from the COM Error object. You must always free memory buffer allocated by GetWin32ErrorMessage and GetComErrorMessage.

COM methods typically return error information via the COM Error object. SetComError lets you do it easily and also offers some extras. If you are writing a COM object, which encounters an error when making an internal COM call (such as when calling another COM object), you may want to combine your own error message with the error information retrieved from the failed COM call and pass the result to the client via the COM Error object. SetComError does just that. It first calls the GetComErrorMessage to retrieve error information from the specified HRESULT code and the global COM Error object. Then it appends the returned error information—if any—to the message passed via the first parameter and sets this error message as a description field of the ICreateErrorInfo interface generated for the current thread. The last two optional parameters can be used to specify the class and interface IDs (CLSID and IID) of your COM object. When using GetComErrorMessage or SetComError, you must define the _WIN32_DCOM precompiler directive in the project settings.

In addition to the three methods just mentioned, you can use the DebugLog method to print formatted debug messages to a log file. This function can be handy if you cannot step through the program's source code in Visual Studio IDE. Source code comments describe how DebugLog works.

Implementing Error Messages in C#

The .NET Framework offers a much better error-handling methodology that is based on .NET exceptions. Although the concept of exception is not new—you can use exceptions in C/C++—.NET exceptions have a useful feature: They can be nested. This makes it possible to easily pass and retrieve error information. Reflection is another helpful .NET feature, which makes it possible to programmatically retrieve the context of exception at runtime. And of course, using .NET you are free from memory-management hassles.

The C# sample (available electronically) builds a .NET class library that simplifies error reporting. Using this library, you can:

  • Format error messages using error details retrieved from exception objects and inner exceptions.
  • Get extended error information from complex exception classes, such as SqlException, WebException, and others.
  • Customize the format of error messages to include or exclude such exception details as source, type, error code, method, and others.
  • Extend the library to customize message formatting for your own exception classes.

Table 3 lists some of the library's classes, and Figure 1 is the class diagram. The C# project comes with an HTML help document.

ExceptionInfo is the primary class responsible for retrieving error information from exceptions. This is how you would use ExceptionInfo to retrieve error information from a SqlException object including all inner exceptions and error details provided in the collection of SqlError objects:

try
{
...
}
catch (SqlException ex)
{
Console.WriteLine(
ExceptionInfo.GetMessages(
ex, (int)FormatFlags.Detailed));
}

The GetMessages method uses the FormatFlags enumerator, which identifies error details you want to retrieve. FormatFlags currently supports 14 error detail options and a number of flag combinations (see Table 4). If you want to retrieve more than one error detail, you can combine multiple format flags in a bitmask or use a predefined mask. You do not have to specify format flags on every call, but instead set it only once via the SetFormat method.

SetFormat is implemented in the abstract MessageFormatter class, which is a parent of ExceptionInfo. I used the MessageFormatter class to separate message formatting logic from exception processing handled by ExceptionInfo. MessageFormatter includes a number of methods, which make it easier to build error messages from exception details. MessageFormatter implements the IMessageFormatter interface.

The IMessageFormatter interface defines two FormatMessage methods: One uses explicitly specified message format flags, the other uses the default setting. The FormatMessage method that uses the default format flags is already implemented in the FormatMessage class; the other version of FormatMessage is an abstract method, which is implemented in ExceptionInfo and overridden in SqlExceptionInfo, OdbcExceptionInfo, and other exception-specific formatter classes.

The library provides two utility classes:

  • The Helper class is a general-purpose wrapper for frequently used operations.
  • ExceptionFactory can be used to throw any type of exception with a message that can be built using a message format string and optional message parameters (basically, it combines String.Format with throwing an exception).

The ExceptionInfo class serves as a custom formatter for the base Exception class. In addition, it handles the formatting of error messages for exception classes, which do not have custom formatters. By custom formatters, I mean helper classes such as SqlExceptionInfo, which know how to retrieve error details and format error messages for specific types of exceptions, such as SqlException.

ExceptionInfo can also be used to retrieve error information from any exception, including the ones that have custom formatters. It dynamically detects whether the exception class has a custom formatter; if so, it uses it to format the error message. If the exception class does not have a custom formatter, ExceptionInfo checks its parent and grandparents until it finds the one that has a custom formatter.

For example, say that ParentException is derived from ApplicationException, ChildException is derived from ParentException, and ParentExceptionInfo implements a custom formatter for ParentException (Figure 2). If you call ExceptionInfo.GetMessage(new ChildException()), ExceptionInfo uses the FormatMessage method implemented by ParentExceptionInfo to generate the error message.

How does ExceptionInfo find the right custom formatter for a given exception type? It uses a naming convention along with the .NET Framework feature, letting code invoke class instances; which types are determined at runtime. The dynamic invocation is accomplished via the Activator.CreateInstance method call that takes the name of the class as a parameter. ExceptionInfo assumes that the custom formatter is named after the exception class with the postfix "Info," such as SqlExceptionInfo for SqlException. The custom formatter class must belong to the same namespace as the ExceptionInfo class. To see how ExceptionInfo invokes the custom formatter, see the ExceptionInfo.GetFormatter method.

ExceptionInfo provides a number of helpful methods. In addition to GetMessage, which retrieves the error message for the current exception, it implements several overloaded GetMessages and GetMessageArray methods. Both GetMessages and GetMessageArray functions can retrieve error information, not only from the immediate exception, but also from inner exceptions. You can specify from which level of inner exceptions you want to start and how many levels the method must process. GetMessages returns error messages in a single string (messages are formatted as sentences separated by a single whitespace character with all unnecessary whitespaces removed). GetMessageArray returns error messages in a string array. GetMessages and GetMessageArray are handy if you want to display only parts of error information. For example, you can use them to retrieve the information about inner exceptions in the error details field of the error message box.

Finally, ExceptionInfo implements the GetStackTraces and GetStackTraceArray methods, which work like GetMessages and GetMessageArray, only these methods retrieve exception stack information from the current and inner exceptions.

Custom Exception Formatters

The library provides a number of custom formatters for exception classes that contain more information than a basic exception. For example, exceptions returned by data providers (such as SqlException) can include such details as the name of the failed stored procedure, error severity, ANSI code, and others. A custom formatter knows how to retrieve the requested details from the exception object and display these details in the formatted error message. This is accomplished by overriding the FormatMessage method.

To understand how to implement a custom formatter for an exception class not covered by the library, consider one of the custom formatters using SqlExceptionInfo.

SqlExceptionInfo is a custom formatter for SqlException. It is derived from ExceptionInfo and defined under the same namespace. SqlExceptionInfo overrides one function—FormatMessage. This function receives two parameters: the exception object and the message format flags (bitmask).

To make it easier to access exception-specific members, FormatMessage typecasts the exception object from Exception to SqlException. Then it verifies the message format flags, and if the value of the bitmask indicates the default (FormatFlags.Default), it sets the bitmask to the value of the static member of the MessageFormatter class via the MessageFormatter.VerifyFormat method. Finally, it processes error information.

Error information available in SqlException can be retrieved from the class members, such as State, LineNumber, Procedure, and others, as well as from the collection of SqlError objects accessed via the items of the Errors member. According to the documentation, the first item in the SqlError object collection shares some of the details with the members of the SqlException object. For example, the value of SqlException.Class member is the same as SqlException.Errors[0].Class. To avoid duplicate information, SqlExceptionInfo retrieves all information available from the Errors collection. After these errors are processed, it adds the details, which are only accessible via the main class members.

Before adding information about a particular exception detail, SqlExceptionInfo checks whether a corresponding flag in the specified message format is set; if it is not, the detail will not be included. SqlExceptionInfo uses methods inherited from ExceptionInfo (and MessageFormatter) to build formatted messages for exception details. For additional information, see comments in the source code.

If you implement a custom formatter for an exception class not included in the library, extend the project:

  1. Add a class for custom formatter. Name this class by appending "Info" to the name of the corresponding exception class. It must be derived from IMessageFormatter (or any other class derived from IMessageFormatter, such as ExceptionInfo) and created under the same namespace.
  2. Override the FormatMessage method and implement the logic to retrieve exception details and display them in a formatted error message.

If you need to specify additional fields in the message format bitmask, either extend the FormatFlags enumerator or set the unused bits (the format mask is handled as an integer value). I was planning on adding a custom formatter for SoapException, but decided not to because SoapException is very customizable (SOAP exception details can be passed via XML nodes). If your application makes SOAP calls, I suggest implementing your own version of the custom formatter.

Conclusion

Although you cannot totally eliminate errors from software, vigorous error handling and good error messages can help you reduce the impact of these errors on the users. The better job you do reporting errors, the less time your team will spend troubleshooting them.

DDJ



Listing One

//-------------------------------------------------------------------
// $Workfile: BasePage.aspx.cs $
// Description: Implements the BasePage class.
// $Log:  $
//-------------------------------------------------------------------
using System;

namespace ErrorSample
{
/// <summary>
/// Base class implementing common utility functions reused by
/// different pages belonging to this Web application.
/// </summary>
/// <remarks>
/// Page classes on this site must derive from
/// <see cref="ErrorSample.BasePage"/>, not the usual
/// <see cref="System.Web.UI.Page"/>.
/// </remarks>
public class BasePage: System.Web.UI.Page
{
    // We need to keep the counters of the popup script blocks.
    // (Actually, we can distinguish between client-side and
    // start-up scripts, but why bother?)
    private int _errorScriptCount         = 0;
    private string _errorScriptNameFormat = "_errorScript{0}";
    /// <summary>
    /// Formats string and replaces characters, which can break 
    /// JavaScript, with their HTML codes.
    /// </summary>
    /// <param name="message">
    /// Message or message format.
    /// </param>
    /// <param name="args">
    /// Optional message parameters.
    /// </param>
    /// <returns>
    /// Formatted string which is JavaScript safe.
    /// </returns>
    private static string FormatJavaScriptMessage
    (
        string          message,
        params object[] args
    )
    {
        // Make sure we have a valid error message.
        if (message == null)
            return String.Empty;
        // If we have message parameters, build a formatted string.
        if (args != null && args.Length > 0)
            message = String.Format(message, args).Trim();
        else
            message = message.Trim();
        // Make sure we have a valid error message.
        if (message.Length == 0)
            return String.Empty;
        // Back slashes, quotes (both single and double),
        // carriage returns, line feeds, and tabs must be escaped.
        return message.Replace(
            "\\", "\\\\").Replace(
                "'", "\\'").Replace(
                    "\"", "\\\"").Replace(
                        "\r", "\\r").Replace(
                            "\n", "\\n").Replace(
                                    "\t", "\\t");
    }
    /// <summary>
    /// Shows a formatted error message in a client-side (JavaScript)
    /// popup dialog.
    /// </summary>
    /// <param name="message">
    /// Error message or message format.
    /// </param>
    /// <param name="args">
    /// Optional message arguments.
    /// </param>
    /// <remarks>
    /// Error popup will be rendered as the first element of the
    /// page (form).
    /// </remarks>
    public void ShowErrorPopup
    (
        string          message, 
        params object[] args
    )
    {
        ShowErrorPopup(true, message, args);
    }
    /// <summary>
    /// Shows a formatted error message in a client-side (JavaScript)
    /// popup dialog.
    /// </summary>
    /// <param name="showFirst">
    /// Flag indicating whether the error message must be rendered
    /// as the first element of the page (form).
    /// </param>
    /// <param name="message">
    /// Error message or message format.
    /// </param>
    /// <param name="args">
    /// Optional message arguments.
    /// </param>
    public void ShowErrorPopup
    (
        bool            showFirst,
        string          message, 
        params object[] args
    )
    {
        // Build message string which is safe to display in JavaScript code.
        message = FormatJavaScriptMessage(message, args);
        // If we did not get any message, we should not generate any output.
        if (message.Length == 0)
            return;
        // Generate a unique name of the start-up script.
        string scriptBlockName = String.Format(
        // Generate HTML for the script.
        string scriptHtml = String.Format(
                           "{0}" + "<SCRIPT Language=\"JavaScript\">{0}" + 
                           "<!--{0}" + "alert(\"{1}\");{0}" + "-->{0}" + 
                           "</SCRIPT>{0}", Environment.NewLine, message);
        // Generate script opening a popup with error message.
        if (showFirst)
            RegisterStartupScript(scriptBlockName, scriptHtml);
        else
            RegisterClientScriptBlock(scriptBlockName, scriptHtml);
    }
}
}
//-------------------------------------------------------------------
// $Workfile: Default.aspx.cs $
// Description: Implements the DefaultPage class.
// $Log:  $
//-------------------------------------------------------------------
using System;
using System.Collections;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Web;
using System.Web.SessionState;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Web.UI.HtmlControls;

namespace ErrorSample
{
/// <summary>
/// Implements the default Web page.
/// </summary>
public class DefaultPage: BasePage
{
    protected System.Web.UI.WebControls.Button btnTest;
    private void Page_Load(object sender, System.EventArgs e)
    {
    }
    #region Web Form Designer generated code
    override protected void OnInit(EventArgs e)
    {
        // CODEGEN: This call is required by the ASP.NET Web Form Designer.
        InitializeComponent();
        base.OnInit(e);
    }
    /// <summary>
    /// Required method for Designer support - do not modify
    /// the contents of this method with the code editor.
    /// </summary>
    private void InitializeComponent()
    {    
        this.btnTest.Click += new System.EventHandler(this.btnTest_Click);
        this.ID = "Form";
        this.Load += new System.EventHandler(this.Page_Load);
    }
    #endregion
    // Displays error messages when button is clicked.
    private void btnTest_Click(object sender, System.EventArgs e)
    {
        for (int i=0; i<4; i++)
        {
            // Display errors with odd IDs as the first elements of
            // the form and the rest as the last elements of the form.
            bool showFirst = (i & 0x1) == 1;
            ShowErrorPopup( showFirst, "Error '{0}' occurred at:{1}{1}{2}", 
                            i, Environment.NewLine, DateTime.Now);
        }
    }
}
}
Back to article


Listing Two
bool CreateAccounts()
{
  UserListInfo = GetUserListInfo();
  foreach(UserInfo in UserListInfo)
  {
    if (not CreateAccount(UserInfo))
    {
      Report error.
    }
  }
}
bool CreateAccount(UserInfo)
{
  if (not CreateProfile(UserInfo))
  {
    Report error.
    return false;
  }
  if (not CreateMailbox(UserInfo))
  {
    Report error.
    return false;
  }
  return true;
}
bool CreateProfile(UserInfo)
{
  Create profile.
}
bool CreateMailbox(UserInfo)
{
  Create mailbox.
}
Back to article


Listing Three
bool CreateAccounts()
{
  UserListInfo = GetUserListInfo();
  string msg, errMsg;
  foreach(UserInfo in UserListInfo)
  {
    if (not CreateAccount(msg, UserInfo))
    {
      errMsg = "Cannot create account for user "+UserInfo.UserID + ". " + msg;
      ReportError(errMsg);
    }
  }
}
bool CreateAccount(errMsg, UserInfo)
{
  string msg;
  if (not CreateProfile(msg, UserInfo))
  {
    errMsg = "Cannot create profile for " +
                UserInfo.ProfileName +
                ". " + msg;
    return false;
  }
  if (not CreateMailbox(msg, UserInfo))
  {
    errMsg = "Cannot create mailbox for " + UserInfo.MailboxName + ". " + msg;
    return false;
  }
  ...
  return true;
}
bool CreateProfile(errMsg, UserInfo)
{
  if (profile exists)
  {
    errMsg = "Profile already exists.";
    return false;
  }
  Create profile.
  ...
  return true;
}
bool CreateMailbox(errMsg, UserInfo)
{
  if (mailbox exists)
  {
    errMsg = "Mailbox already exists.";
    return false;
  }
  Create malbox.
  ...
  return true;
}
Back to article


Listing Four
bool DoThis(TCHAR** ptszErrMsg, ...)
{
  TCHAR* ptszMsg = NULL;
  if (!DoThat(&ptszMsg, ...))
  {
    BuildMessage(ptszErrMsg, _T("Cannot do that. %s"), ptszMsg);
    TryDeleteMessage(&ptszMsg);
    return false;
  }
  return true;
}
bool DoThat(TCHAR** ptszErrMsg, ...)
{
  TCHAR* ptszMsg = NULL;
  if (!DoTheOther(&ptszMsg, ...))
  {
    BuildMessage(ptszErrMsg, _T("Cannot do the other. %s"), ptszMsg);
    TryDeleteMessage(&ptszMsg);
    return false;
  }
  return true;
}
main()
{
  TCHAR* ptszMsg    = NULL;
  TCHAR* ptszErrMsg = NULL;
  
  if (!DoThis(&ptszMsg, ...))
  {
    BuildMessage(&ptszErrMsg, "Cannot do this. %s", ptszMsg);
    puts(ptszErrMsg);
    TryDeleteMessage(&ptszMsg);
    TryDeleteMessage(&ptszErrMsg);
  }
}
Back to article


Related Reading


More Insights






Currently we allow the following HTML tags in comments:

Single tags

These tags can be used alone and don't need an ending tag.

<br> Defines a single line break

<hr> Defines a horizontal line

Matching tags

These require an ending tag - e.g. <i>italic text</i>

<a> Defines an anchor

<b> Defines bold text

<big> Defines big text

<blockquote> Defines a long quotation

<caption> Defines a table caption

<cite> Defines a citation

<code> Defines computer code text

<em> Defines emphasized text

<fieldset> Defines a border around elements in a form

<h1> This is heading 1

<h2> This is heading 2

<h3> This is heading 3

<h4> This is heading 4

<h5> This is heading 5

<h6> This is heading 6

<i> Defines italic text

<p> Defines a paragraph

<pre> Defines preformatted text

<q> Defines a short quotation

<samp> Defines sample computer code text

<small> Defines small text

<span> Defines a section in a document

<s> Defines strikethrough text

<strike> Defines strikethrough text

<strong> Defines strong text

<sub> Defines subscripted text

<sup> Defines superscripted text

<u> Defines underlined text

Dr. Dobb's encourages readers to engage in spirited, healthy debate, including taking us to task. However, Dr. Dobb's moderates all comments posted to our site, and reserves the right to modify or remove any content that it determines to be derogatory, offensive, inflammatory, vulgar, irrelevant/off-topic, racist or obvious marketing or spam. Dr. Dobb's further reserves the right to disable the profile of any commenter participating in said activities.

 
Disqus Tips To upload an avatar photo, first complete your Disqus profile. | View the list of supported HTML tags you can use to style comments. | Please read our commenting policy.