[Previous] [Next]
It's tedious to add error handling to all procedures in a project, but it's a necessity. Every unexpected error must be displayed to the user in the same format, and this can take a considerable amount of code. Adding a central error handler can help.
A central error handler is a procedure that you call when an error occurs. At a minimum, a central error handler displays a consistent error message to the user. However, you can add capabilities to the central error handler as you see fit. For instance, you can have your error handler send an e-mail to a support specialist whenever an unexpected error occurs, or you can actually include code to take a snapshot of the state of the machine and log the loaded applications and loaded DLLs, along with their versions.
The following is a typical central error handler:
FriendSub ShowError(strModule AsString ,strProcedure AsString ,_ lngErrorNumber AsLong ,strErrorDescription AsString ) '*Purpose:Provideacentralerror-handlingmechanism. '*Accepts:strModule-themoduleinwhichtheerrorwas '*encountered(form,class,standard,andsoon.) '*strProcedure-thenameoftheprocedureinwhich '*theerrorwasencountered. '*lngErrorNumber-thenumericidentifierofthe '*error. '*strErrorDescription-thetextdescriptionof '*theerror. OnErrorGoTo PROC_ERR Dim strMessage AsString Dim strCaption AsString '*Buildtheerrormessage. strMessage="Error:"&strErrorDescription&vbCrLf&vbCrLf&_ "Module:"&strModule&vbCrLf&_ "Procedure:"&strProcedure&vbCrLf&vbCrLf&_ "PleasenotifyMySoftware'stechsupport"&_ "at555-1213aboutthisissue."&vbCrLf&_ "Pleaseprovidethesupporttechnicianwith"&_ "informationshownin"&vbCrLf&"thisdialog"&_ "boxaswellasanexplanationofwhatyou"&_ "were"&vbCrLf&"doingwhenthis"&_ "erroroccurred." '*Buildthecaptionforthemessagebox.Thecaptionshows '*theversionnumberoftheprogram. strCaption="UnexpectedError!Version:"&_ Str$(App.major)&"."&Str$(App.minor)&"."&_ Format(App.Revision,"0000") MsgBoxstrMessage,vbCritical,strCaption PROC_EXIT: ExitSub PROC_ERR: ResumeNext EndSub |
To use this central error handler, you simply call the procedure in an error handler like this:
PrivateSub frmMain_Click() '*Purpose:Generateanerrorbysettingthefocusto '*aninvisiblecontrol. OnErrorGoTo PROC_ERR '*Setthefocustoaninvisiblecontrol. txtCity.Visible= False txtCity.SetFocus PROC_EXIT: ExitSub PROC_ERR: Call ShowError(Me.Name,"frmMain_Click",Err.Number,Err.Description) ResumeNext EndSub |
When the error illustrated in the previous example occurs, the central error handler is called and it displays the dialog box shown in Figure 7-4. Imagine trying to display such a comprehensive error message from every error handler in every procedure in every module without using a central error handler!
Notice the use of Me.Name in the ShowError function call, which makes the line of code a bit more portable. You can copy this statement to the Clipboard and paste it into other procedures. In form procedures, all you change is the procedure name. This enables you to write the error handlers in your various procedures more quickly and allows you to change the way errors are handled or displayed by changing code in one location rather than hundreds or thousands of locations.
Figure 7-4. A central error handler makes it easy to display comprehensive error messages
For class modules, Me.Name doesn't work, so you have to use the literal class name. However, in this situation it's best to create a module-level constant and use the constant so that you can easily modify the error handlers in a class module when the module's name is changed. If you use a generic constant name, you can copy the Call statements from one module and paste them into another module without having to make major modifications.
Although the central error handler shown earlier displays the error message to the user in a consistent fashion, you must determine the code that each error handler will have in addition to calling the ShowError procedure. For instance, does the error handler require a Resume or a Resume Next statement? Perhaps it requires a GoTo PROC_EXIT statement instead. You should make your error handlers as generic as possible but make sure that each one is appropriate for the procedure in which it resides.
It's often useful to have a log of any errors that occur. For instance, during the testing phase of your project, you need to know as much as you can about any errors that happen. Often, you can't rely on reports from users. When it's critical that you know about every error in your program, you should use a central error handler to create an error log.
Creating an error log is simple. First create a central error handler as discussed earlier. Then, within the central error handler, devise a mechanism to log the errors to a text file. The following code illustrates one way to log errors to a text file. This code is shown as it would appear as part of the central error handler shown previously. It assumes that the local variables in that procedure are present and that there is a global variable in the project called g_strErrorLogFileName that contains the path and name of the error log file.
Dim intLogFile AsInteger '*Obtainafreefilehandle. intLogFile=FreeFile '*OpentheerrorlogtextfileinAppendmode. '*Ifthefiledoesn'texist,theOpenstatement '*createsit. Open g_strErrorLogFileName ForAppendAs #intLogFile '*Writetheheader. Print #intLogFile,"***ErrorEncountered"&VBA.Now&"***" '*Writethepertinenterrorinformationtothelogfile. Print #intLogFile,"Error:"&lngErrorNumber Print #intLogFile,"Description:"&strErrorDescription Print #intLogFile,"Procedure:"&strProcedure Print #intLogFile,"Module:"&strModule '*Writeablanklinetothelogfile. Print #intLogFile,"" '*Closethelogfile. Close #intLogFile |
This code attempts to open the text file specified in the global variable g_strErrorLogFileName in Append mode. If the file doesn't exist, it's created automatically and then opened. Once the file is opened, a log entry is written. In this example, five lines of text are written for each log entry, with each entry separated from the others by a blank line. Here is a sample of a text file created using the previous code:
***ErrorEncountered8/29/994:19:18PM*** Error:5 Description:Invalidprocedurecallorargument Procedure:ShowCustomer Module:clsCustomer ***ErrorEncountered8/29/994:20:08PM*** Error:11 Description:Divisionbyzero Procedure:CalculateCoefficient Module:mdlScience ***ErrorEncountered8/29/994:22:05PM*** Error:6 Description:Overflow Procedure:CalculateDrag Module:mdlScience ***ErrorEncountered8/29/994:22:34PM*** Error:11 Description:Divisionbyzero Procedure:CalculateCoefficient Module:mdlScience |
You can start to see a trend in this error log. Whoever wrote the mdlScience module needs to spend a little more time with the code. The information shown here is the minimum amount you'd want to include in a text file; you might want to include much more. For instance, you might want to include the user name of the person running the program when the error occurs, or you might want to include the machine name in the log entry. The possibilities are endless. Whatever you choose to put into the text file, make sure it's pertinent information that will help you find and correct the problem.
For clarity, the following is the complete error handler shown previously, with the inclusion of the error log code:
FriendSub ShowError(strModule AsString ,strProcedure AsString ,_ lngErrorNumber AsLong ,strErrorDescription AsString ) '*Purpose:Provideacentralerror-handlingmechanism. '*Accepts:strModule-themoduleinwhichtheerrorwas '*encountered(form,class,standard,andsoon.) '*strProcedure-thenameoftheprocedureinwhich '*theerrorwasencountered. '*lngErrorNumber-thenumericidentifierofthe '*error. '*strErrorDescription-thetextdescriptionof '*theerror. OnErrorGoTo PROC_ERR Dim strMessage AsString Dim strCaption AsString Dim intLogFile AsInteger '*Obtainafreefilehandle. intLogFile=FreeFile '*OpentheerrorlogtextfileinAppendmode. '*Ifthefiledoesn'texist,theOpenstatement '*createsit. Open g_strErrorLogFileName ForAppendAs #intLogFile '*Writetheheader. Print #intLogFile,"***ErrorEncountered"&VBA.Now&"***" '*Writethepertinenterrorinformationtothelogfile. Print #intLogFile,"Error:"&lngErrorNumber Print #intLogFile,"Description:"&strErrorDescription Print #intLogFile,"Procedure:"&strProcedure Print #intLogFile,"Module:"&strModule '*Writeablanklinetothelogfile. Print #intLogFile,"" '*Closetheerrorlogtextfile. Close #intLogFile '*Buildtheerrormessagefordisplaytotheuser. strMessage="Error:"&strErrorDescription&vbCrLf&vbCrLf&_ "Module:"&strModule&vbCrLf&_ "Procedure:"&strProcedure&vbCrLf&vbCrLf&_ "PleasenotifyMySoftware'stechsupport"&_ "at555-1213aboutthisissue."&vbCrLf&_ "Pleaseprovidethesupporttechnicianwith"&_ "informationshownin"&vbCrLf&"thisdialog"&_ "boxaswellasanexplanationofwhatyou"&_ "were"&vbCrLf&"doingwhenthis"&_ "erroroccurred." '*Buildthecaptionforthemessagebox.Thecaptionshows '*theversionnumberoftheprogram. strCaption="UnexpectedError!Version:"&_ Str$(App.major)&"."&Str$(App.minor)&"."&_ Format(App.Revision,"0000") MsgBoxstrMessage,vbCritical,strCaption PROC_EXIT: ExitSub PROC_ERR: ResumeNext EndSub |
Once your application is logging error messages, you must decide what you want to do with those logs. If you're on-site with the program, you can manually retrieve copies of the error logs. Or you can have users e-mail you their logs if they encounter problems. You might even write a program that automatically e- mails the logs to you on a preset schedule. Log files can be very useful for locating specific bugs as well as general program errors, and they are easy to create. You should seriously consider adding this feature to your programs. You might even elect to include the code but turn off the feature by default. Then you can enable or disable log file generation via your program's interface or a registry setting.
Goals of Error Handling
The goals of utilizing error handling are
- Preventing your program from crashing
- Gracefully correcting mistakes whenever possible
- Notifying the user when errors occur so that the problems can be addressed