In older versions of Basic, line numbers were mandatory and often used as "jump targets." A jump target is a line number used with a GoTo, such as GoTo 2000. The 2000 identifies the start of a block of code to execute next. After GoTo came GoSub (and Return). Now you had a "Basic subroutine," albeit one with a strange name: GoSub 2000. You can think of the (line) number almost as an address (just as in C). These days, of course, Basic is Visual Basic and we use symbolic names for labeling such jump targets (real subroutines, just like those in C and other programming languages). Line numbers have become a peculiarity designed to allow nothing more than some level of backward compatibility with some other version of Basic.
Or then again, maybe not. In Visual Basic, Erl, a Visual Basic (undocumented in Visual Basic 4, 5, and 6 but present in all versions of Visual Basic thus far) "global variable," gives you access to the line number of any erroring line of code. So by using line numbers and by using Erl in your error handlers, you can determine which line of code erred-wow! What happens to Erl if you don't use line numbers? Easy-it will always be 0.
Of course, you won't want to start typing line numbers in by hand. You need some automation. At TMS, we add line numbers to our code using an internal tool we originally developed for working with Visual Basic 2. It now works as an add-in under Visual Basic 6. There are tools on the market that can do the same for your code.
At TMS, we don't work with line numbers in our source code, however. We add them only when we're doing a ship build-that is, when we want to ship a binary to, say, beta testers or to manufacturing for an impending release. We use our internal tool to build a new version of the code, complete with line numbers, and then we make an executable from that. We store the line numbered source code in our source control system and ship the executable. We cross-reference the EXE version number (the Auto Increment option is just great here) to the source code stored in the source control system. Every time we do a new build for shipping, we create a new subproject whose name is the version number of the build and then store the line numbered source code in it along with a copy of the binary image. If an error report comes in, we can easily refer back to the source code to find the erroring line (very easy if you're using Microsoft Visual SourceSafe). Typically, the error report will contain details of the module, routine, and line number of the error.
Listing 1-2 is a typical Click event, line numbers and all.
Listing 1-2 Generic Click event with line numbers
Private Sub Command1_Click() ' ============================================================= ' Module Type : Form ' Module Name : Form1 ' Object : Command1 ' Proc Type : Sub ' Proc Name : Click ' Scope : Private ' Author : ' Date : 01/01/97 00:00 ' ' History : 01/01/97 00:00: Peter J. Morris : Original Code. ' ============================================================= ' Set up general error handler. On Error GoTo Error_In_Command1_Click: 1 Dim sErrorDescription As String 2 Const sProcSig = MODULE_NAME & "Command1_Click" ' ========== Body Code Starts ========== 3 Debug.Print bDriveExists("") ' ========== Body Code Ends ========== 4 Exit Sub ' Error handler Error_In_Command1_Click: 5 With Err 6 sErrorDescription = "Error '" & .Number & " " & _ .Description & "' occurred in " & sProcSig & _ IIf(Erl <> 0, " at line " & CStr(Erl) & ".", ".") 7 End With 8 Select Case MsgBox(sErrorDescription, _ vbAbortRetryIgnore, _ App.Title & " Error") Case vbAbort 9 Resume Exit_Command1_Click: 10 Case vbRetry 11 Resume 12 Case vbIgnore 13 Resume Next 14 Case Else 15 End 16 End Select Exit_Command1_Click: End Sub
Notice in Listing 1-2 that sProcSig is made up of the module name (Form1) and the routine name (Command1_Click). Notice also that the error handler examines Erl to "see" whether line numbers have been used. Figure 1-1 shows what's typically displayed when an error occurs using this kind of scheme.
Figure 1-1 Error and line number information
Of course, the actual code that makes up the error handler is entirely up to you. If you use this scheme, I recommend you have a module-level constant to hold your module name and use a routine-level constant to hold the routine name plus the module name:
Module Declaration Section
Private Const MODULE_NAME As String = "Form1."
Const sProcSig As String = MODULE_NAME & "Command1_Click"