Practical Standards for Microsoft Visual Basic .NET (Pro-Developer)
[Previous] [Next]
7.1 Use On Error GoTo to trap unexpected errors.
Most error handlers are designed to trap errors that aren't anticipated at design time. The On Error GoTo statement is the most common way of designating an error handler. You should use this as the default in all of your procedures unless you have a specific reason to use a different scheme.
Incorrect:
PrivateSub imgEditor_MouseUp(Button AsInteger ,Shift AsInteger ,_ X AsSingle ,Y AsSingle ) '*Purpose:Iftheeditorisinselect(marquee)mode,display '*theEditshortcutmenuwhentheuserright-clicks '*theCanvas. OnErrorResumeNext '*Displaytheshortcutmenuonlyiftheuserclickedwith '*therightmousebutton. If Button=vbRightButton Then '*SeeiftheactivetooloftheCanvasobjectisthe '*marqueetool. If g_objCanvas.ToolIndex=bdMarquee Then '*DisplaytheEditshortcutmenuusingtheactive '*barcontrol. Me.ActiveBar.Bands("puEdit").TrackPopup-1,-1 EndIf EndIf '*TelltheCanvasobjecttostopitscurrentaction. g_objCanvas.ActionEndButton,Shift,X,Y PROC_EXIT: ExitSub EndSub |
Correct:
PrivateSub imgEditor_MouseUp(Button AsInteger ,Shift AsInteger ,_ X AsSingle ,Y AsSingle ) '*Purpose:Iftheeditorisinselect(marquee)mode,display '*theEditshortcutmenuwhentheuserright-clicks '*theCanvas. OnErrorGoTo PROC_ERR '*Displaytheshortcutmenuonlyiftheuserclickedwith '*therightmousebutton. If Button=vbRightButton Then '*SeeiftheactivetooloftheCanvasobjectisthe '*marqueetool. If g_objCanvas.ToolIndex=bdMarquee Then '*DisplaytheEditshortcutmenuusingtheactive '*barcontrol. Me.ActiveBar.Bands("puEdit").TrackPopup-1,-1 EndIf EndIf '*TelltheCanvasobjecttostopitscurrentaction. g_objCanvas.ActionEndButton,Shift,X,Y PROC_EXIT: ExitSub PROC_ERR: Call ShowError(Me.Name,"imgEditor_MouseUp",Err.Number,_ Err.Description) ResumeNext EndSub |
7.2 Use On Error Resume Next to trap expected errors.
When you expect an error, such as Error 5 when you use SetFocus to move the cursor to a control on a form that is not fully loaded, or an error from a database action, use On Error Resume Next. But be careful ”using On Error Resume Next can be dangerous because it can cause run-time errors to go unnoticed. Do not use it as a panacea; if you do not expect an error, don't use it. Also, just because you expect an error within a procedure doesn't mean that you should use On Error Resume Next for the entire procedure. Use On Error GoTo to trap unexpected errors, but change the enabled error handler immediately before the line that could cause the expected error. After you have handled the expected error, include an On Error GoTo statement to enable the main error handler again.
When you write code to trap an expected error, be sure to document the code thoroughly. State specifically why you are using On Error Resume Next ”such as what error you expect and why.
Incorrect:
PrivateSub cmdCoverLetter_Click() '*Purpose:Allowtheusertobrowseandselectacover '*pageforfaxing. OnErrorResumeNext Const c_CancelChosen=32755 '*Usethecommondialogcontroltoallowtheuserto '*selectafile. With dlgOpenFile .fileName="*.cvp" .DefaultExt="cvp" .DialogTitle="SelectCoverPage" .Filter="AllFiles(*.*)*.*CoverPages(*.cvp)" .FilterIndex=2 .InitDir=App.Path '*TellthecontroltogenerateanerrorifCancelisclicked. .CancelError= True '*ShowtheOpenFiledialogboxandwaitfortheuserto '*selectafileorclickCancel. .ShowOpen '*Seeifanerrorwasgeneratedastheresultofthe '*userclickingCancel. If Err.Number=c_CancelChosen Then '*Cancelwasclicked;getout. GoTo PROC_EXIT EndIf txtCoverpage.Text=.fileName EndWith PROC_EXIT: ExitSub EndSub |
Correct:
PrivateSub cmdCoverLetter_Click() '*Purpose:Allowtheusertobrowseandselectacover '*pageforfaxing. OnErrorGoTo PROC_ERR Const c_CancelChosen=32755 '*Usethecommondialogcontroltoallowtheuserto '*selectafile. With dlgOpenFile .fileName="*.cvp" .DefaultExt="cvp" .DialogTitle="SelectCoverPage" .Filter="AllFiles(*.*)*.*CoverPages(*.cvp)" .FilterIndex=2 .InitDir=App.Path '*TellthecontroltogenerateanerrorifCancelisclicked. .CancelError= True '*IftheuserclicksCancel,arun-timeerroroccurs. '*Trapforthiserror. OnErrorResumeNext '*ShowtheOpenFiledialogboxandwaitfortheuserto '*selectafileorclickCancel. .ShowOpen '*Seeifanerrorwasgeneratedastheresultofthe '*userclickingCancel. If Err.Number=c_CancelChosen Then '*Cancelwasclicked;getout. GoTo PROC_EXIT EndIf '*Enablethemainerrorhandler. OnErrorGoTo PROC_ERR txtCoverpage.Text=.fileName EndWith PROC_EXIT: ExitSub PROC_ERR: Call ShowError(Me.Name,"cmdCoverLetter_Click",Err.Number,_ Err.Description) ResumeNext EndSub |
7.3 Create consistent error handler blocks.
When you use On Error GoTo , it's important to use consistent error blocks. It's definitely best to use a central error handler, but if you don't use one, create error handlers like this typical error block:
PROC_ERR: MsgBox" ModuleName ProcedureName "&vbcrlf&"Error:"&_ Err.Number&vbCrLf&Err.Description,vbCritical '*cleanupcodesuchasarollback ResumeNext '*orResume,orGoToPROC_EXIT |
If you're going to alert the user to the error, which is usually the best approach, the MsgBox statement should be the first statement of the error block. If the MsgBox statement is not the first statement, the Err object might be reset and the MsgBox statement won't return the proper error number and/or description ”for instance, if you branch to another procedure that enables an error handler. The only parts of the MsgBox statement that should be modified for each procedure are the procedure name and possibly the module name.
You can add code to the error block at your discretion, such as invoking a rollback on a database transaction. Of course, the necessary code will vary from procedure to procedure.
If you don't want to terminate a procedure as a result of an error, you must end the error block with Resume , Resume Next , or Resume <line>. To continue executing code starting at the line after the statement that caused the error, use Resume Next. If you believe the offending statement might now properly execute (such as when your error handler has corrected the problem), use Resume to send execution back to the statement that caused the error.
NOTE
If you use Resume at the end of an error block without correcting the offending error, you risk creating an endless loop in your code! The error will cause the error block to execute, which will return to the offending statement, which will cause the error, and so forth.
Creating a central error handler eliminates the necessity to micromanage the error handler in every procedure. However, if you don't use a central error handler, you must handle and display errors in a consistent manner.
Incorrect:
PROC_ERR: MsgBox"Error!:"&Err.Number&vbCrLf&Err.Description GoTo PROC_EXIT PROC_ERR: MsgBoxErr.Number&vbCrLf&Err.Description GoTo PROC_EXIT |
Correct:
PROC_ERR: MsgBoxMe.Name&"MyProcedure"&vbCrLf&Err.Number&vbCrLf&_ Err.Description GoTo PROC_EXIT PROC_ERR: MsgBox"MyModuleMyProcedure"&vbCrLf&Err.Number&vbCrLf&_ Err.Description GoTo PROC_EXIT |
Practical Application
7.3.1 Don't let code fall through to End Sub , End Function , or End Property. Generally, the error block should be the last section of code in a procedure. However, if you want the procedure to terminate at the end of the error block, don't just let the code run through to End Sub , End Function , or End Property. Remember that it's vitally important that each procedure have only one exit point ”see Chapter 3 ”so to terminate a procedure from the error block you should branch to the PROC_EXIT label.
Incorrect:
PrivateSub otToolbox_ToolSelected(Tool As Toolbox.cTool) '*Purpose:Whenanewtoolisselectedfromthetoolbox, '*selectthecorrespondingtooloftheCanvasobject. OnErrorGoTo PROC_ERR '*Iftheselectedtoolisthesameasthecurrenttool,getout. If Tool.Index=objCanvas.ToolIndex ThenGoTo PROC_EXIT '*IftheselectedtooloftheCanvasisthemarqueeandan '*areaisselected,redisplaythemarquee. If objCanvas.ToolIndex=bdMarquee Then '*Iftheuserhasselectedanareawiththemarquee, '*triggerareset(repaint)ofthemarqueerectangle. If objCanvas.AreaSelected Then objCanvas.ResetMarquee EndIf EndIf '*SettheCanvasobjecttothenewtool. objCanvas.ToolIndex=Tool.Index PROC_EXIT: ExitSub PROC_ERR: Call ShowError(Me.Name,"otToolbox_ToolSelected",Err.Number,_ Err.Description) EndSub |
Correct:
PrivateSub otToolbox_ToolSelected(Tool As Toolbox.cTool) '*Purpose:Whenanewtoolisselectedfromthetoolbox, '*selectthecorrespondingtooloftheCanvasobject. OnErrorGoTo PROC_ERR '*Iftheselectedtoolisthesameasthecurrenttool,getout. If Tool.Index=objCanvas.ToolIndex ThenGoTo PROC_EXIT '*IftheselectedtooloftheCanvasisthemarqueeandan '*areaisselected,redisplaythemarquee. If objCanvas.ToolIndex=bdMarquee Then '*Iftheuserhasselectedanareawiththemarquee, '*triggerareset(repaint)ofthemarqueerectangle. If objCanvas.AreaSelected Then objCanvas.ResetMarquee EndIf EndIf '*SettheCanvasobjecttothenewtool. objCanvas.ToolIndex=Tool.Index PROC_EXIT: ExitSub PROC_ERR: Call ShowError(Me.Name,"otToolbox_ToolSelected",Err.Number,_ Err.Description) GoTo PROC_EXIT EndSub |