untime errors can occur as a result of attempts to complete an invalid operation while the program is running. There are two causes of runtime errors: either one of the program’s statements can not be properly resolved or something unexpected happened. The main goal of an error handler is to fix as many errors as possible, or at least prevent the application from crashing when the errors do occur, and provide developers with detailed information for further bug fighting.
Typically an application consists of many forms, classes and standard modules which contain quite a few Sub/Function procedures. Most of those procedures have to be provided with error handlers. It doesn’t take much to notice that we have to handle the same situation with certain or unexpected errors over and over. In this case, code reusability is a big advantage. In order to be able to reuse code, use centralized error handling routines in combination with procedure level error handling code. The procedure level error handler catches the error as it occurs and records information about where in the procedure it occurred. The central error handler manages how the error is displayed to the user and logs the errors for further analysis.
Procedure Level Code
The ON ERROR statement is used to enable the error handler in every procedure. There are three different kinds of statements: ON ERROR GOTO, ON ERROR RESUME NEXT, and ON ERROR GOTO 0. We have to use one of these in order to tell the program what to do in case an error occurs.
Error handling code for a specific Error number either fixes the problem or displays a message to the user explaining what happened and what action can be taken, if any, to continue running the application. After the error has been corrected or the user has been notified, either the RESUME, RESUME NEXT, or RESUME LineLabel statements tells the program how to continue the execution.
In the case where an unexpected error has occurred, the developer needs to locate the error source. It may not be easy to do when the procedure body contains many lines of code that could have generated the error. You can use a variable called EntryPoint to split the code into several blocks where the value of EntryPoint is changed from block to block. Keeping track of EntryPoint’s value helps to narrow the search bounds.
Recording the call stack is another useful tool for determining the cause for an error. The call stack contains all the procedures called up to the point of the error, so that we know how we got to the error. In order to accomplish this, we call a procedure known as the CallStackPush(strProcedure as String) at the beginning of every procedure. This call adds the name of the procedure to the call stack and at the end of procedure we call the Sub CallStackPop removing that name from the call stack. Once we have included the error handling code, the typical procedure looks like this:
ProcedureName (..) 'variables declaration section ON ERROR GOTO ErrHdl StrProcedure = "ModuleName.ProcedureName" EntryPoint = 0 CallStackPush (strProcedure) .. EntryPoint = 1 'EntryPoint is global .. ' procedure body .. EntryPoint = 2 .. CallStackPop Exit SubErrHdl: Select Case Err.Number Case ErrorNum1 ' error handling code for ErrorNum1 Case ErrorNum2 ' error handling code for ErrorNum2 .. Case Else ' error handling routine for unexpected errors End SelectEnd Sub
The last purpose of the error handling routine is to log errors to a database or log file. Note that when using log files, it is a good practice to have a separate folder for the log files. For easy archiving, we include the date in the error log files’ name, and we create a separate log file for each date. To avoid errors in the error handling routine itself, the existence of the folder and log file has to be examined before opening the log file for appending the error information.
The ON ERROR statement enables the routine to show an “unexpected error” message and to stop execution. Since Visual Basic automatically calls the Clear method of the Err object when it executes an ON ERROR statement, it is necessary to store Err.Number and Err.Description by passing the properties’ values to the corresponding variables before the ON ERROR statement is executed. Then, all the error information and the call stack should be dumped to the log file.
Public Sub WriteError(ProcName As String, EPoint As Long)Dim sErrorNumber As StringDim sErrorDescription As StringDim sErrFile As StringDim i As IntegerDim sMsg$, starSeparator As String' save current values of error object propertiessErrorDescription = CStr(Err.Description)sErrorNumber = CStr(Err.Number) On Error GoTo ErrHdl ' seize running procedure name in stack CallStackPush ("ErrLog.WriteError") Set fs = New Scripting.FileSystemObject ' make sure the folder for error loggin exists If Not fs.FolderExists(App.Path & ErrFolder) Then fs.CreateFolder (App.Path & ErrFolder)End If' include the current date into the errorlog file namesErrFile = ErrFolder & ErrFile & Format(Now, "yyyymmdd") & ".txt"' make sure the errorlog file exists and open/create the fileIf fs.FileExists(App.Path & sErrFile) ThenSet ts = fs.OpenTextFile(App.Path & sErrFile, ForAppending)ElseSet ts = fs.CreateTextFile(App.Path & sErrFile)End IfWriteErrorInfo ProcName, sErrorNumber, sErrorDescription' release procedure name from stackCallStackPopMsgBox "Program Has Stopped. Unexpected Error.", vbCriticalEndErrHdl:For i = 0 To CallStackSize - 1sMsg = sMsg & CallStack(i) & vbCrLfNext iMsgBox "Unexpected Error. Source: ErrLog.WriteError" & vbCrLf & sMsg, vbCriticalEnd Sub' write the error info and the call stack to the error log filePrivate Sub WriteErrorInfo (ProcName as String, sErrorNumber as String, sErrorDescription as String)Dim i As IntegerDim starSeparator As StringFor i = 1 To StrLen + 5starSeparator = starSeparator & "*"Next i' write error message into the text filets.WriteLine (starSeparator)' error source procedure namets.WriteLine ("* Source: " & ProcName)' define procedure section containing the errorts.WriteLine ("* EntryPoint: " & EntryPoint)ts.WriteLine ("* Error Number: " & sErrorNumber)ts.WriteLine ("* Description:")' save sErrorDescription string in predefined formatts.Write (ParseErrDescription(sErrorDescription, StrLen))' call cascade descriptionts.WriteLine ("* Error Call History: ")For i = 0 To CallStackSize - 1ts.WriteLine ("* - " & CallStack(i))Next i' put the time stampts.WriteLine ("* Date/Time: " & Now)ts.CloseEnd Sub
There are two ways of creating the call stack maintenance procedures: using a dynamic array or using a collection object. Which one to use is an optimization issue and is not considered in this Solution. In either case, centralized procedures are used to track the call stack, and are therefore declared as a module level global variable.
' add next element into stack using dynamic arrayPublic Sub CallStackPush(sProcedure As String)If CallStackSize = 0 Then ReDim CallStack(0)ReDim Preserve CallStack(CallStackSize)CallStack(CallStackSize) = sProcedureCallStackSize = CallStackSize + 1End Sub' add next element into stack using collection objectPublic Sub CallStackPush(sProcedure As String)If oCallStack Is Nothing Then Set oCallStack = New CollectionEnd IfoCallStack.Add sProcedure, "key" & oCallStack.CountEnd Sub' remove last element out of stack using dynamic arrayPublic Sub CallStackPop()CallStackSize = CallStackSize - 1If CallStackSize = 0 ThenReDim CallStack(0)ElseReDim Preserve CallStack(CallStackSize - 1)End IfEnd Sub
' remove last element out of stack using collection objectPublic Sub CallStackPop() oCallStack.Remove oCallStack.Count - 1 If oCallStack.Count = 0 Then Set oCallStack = Nothing End IfEnd Sub
This 10-Minute Solution has introduced a general approach for error handling with centralized routines. These routines contain many of the elements that developers need to use in order to create error handling code for a typical application. While ideal code does not need the error handling at all?in real life even the best developers let errors slip into their programs. Therefore, it is important that a developer pay special attention to the issue of error handling.