以下のコードは非常に自明です。すべてをコピーしてモジュールに貼り付け、実行するだけです。 、テキスト内のいくつかのユースケースと多くの説明コメントを提供します。 (それは機能しますが、他の人がそれをどのように作っているのか、そしてあなたがしたいと思うかもしれない提案について知りたいです。)
実現する最も重要な事実は次のとおりです。
-
エラー時にLabel1に移動すると、例外が発生したため、プロシージャは「エラーを処理しています」の状態になります。この状態のときに、別の「On Error Goto」label2ステートメントが実行されると、label2には移動しませんが、プロシージャを呼び出したコードに渡されるエラーが発生します。
-
例外をクリアする(err.numberプロパティが0になるようにerrを何も設定しない)ことにより、「エラーを処理しています」状態のプロシージャを停止できます。使用
Err.clear or On Error Goto -1 " Which I think is less clear!
(On Error Goto 0
は上記とは異なることに注意してください)
また、Err.Clearはそれをゼロにリセットしますが、実際には次と同等であることに注意してください。
On Error Goto -1 On Error Goto 0
ieErr.Clearは削除します現在実施されている「OnErrorGoto」。したがって、次を使用するのが最適です。
On Error Goto -1
Err.clearを使用する場合は次のようにする必要があります。書き込み
Err.Clear On Error Goto MyErrorHandlerLabel
上記の手法をさまざまなラベルで使用して、Visual Basic TRY CATCHブロックが提供する便利な機能をシミュレートします。これは、書き込みに適していると思います。読み取り可能なコード。
確かに、この手法では、優れたVB try catchステートメントよりも数行多くのコードが作成されますが、それほど面倒ではなく、簡単に取得できます。 r頭を回します。
PS。 Err.Sourceプロパティにエラーが発生したプロシージャを格納させるプロシージャManageErrSourceも興味深いかもしれません。
Option Compare Database Option Explicit Dim RememberErrNumber As Long Dim RememberErrDescription As String Dim RememberErrSource As String Dim RememberErrLine As Integer Private Sub RememberThenClearTheErrorObject() On Error Resume Next " For demo purposes Debug.Print "ERROR RAISED" Debug.Print Err.Number Debug.Print Err.Description Debug.Print Err.Source Debug.Print " " " This function has to be declared in the same scope as the variables it refers to RememberErrNumber = Err.Number RememberErrDescription = Err.Description RememberErrSource = Err.Source RememberErrLine = Erl() " Note that the next line will reset the error object to 0, the variables above are used to remember the values " so that the same error can be re-raised Err.Clear " Err.Clear is used to clear the raised exception and set the err object to nothing (ie err.number to 0) " If Err.Clear has not be used, then the next "On Error GoTo ALabel" that is used in this or the procedure that called it " will actually NOT pass execution to the ALabel: label BUT the error is paseed to the procedure that called this procedure. " Using Err.Clear (or "On Error GoTo -1 ") gets around this and facilitates the whole TRY CATCH block scenario I am using there. " For demo purposes Debug.Print "ERROR RAISED is now 0 " Debug.Print Err.Number Debug.Print Err.Description Debug.Print Err.Source Debug.Print " " " For demo purposes Debug.Print "REMEMBERED AS" Debug.Print RememberErrNumber Debug.Print RememberErrDescription Debug.Print RememberErrSource Debug.Print " " End Sub Private Sub ClearRememberedErrorObjectValues() " This function has to be declared in the same scope as the variables it refers to RememberErrNumber = 0 RememberErrDescription = "" RememberErrSource = "" RememberErrLine = 0 End Sub Sub ExampleOfTryCatchBlockInVBA() On Error GoTo HandleError " ----------------------------------------------------- " SubProcedure1 has the example of a multiple line TRY block with a block of code executed in the event of an error SubProcedure1 Exit Sub HandleError: Select Case Err.Number Case 0 " This shold never happen as this code is an error handler! " However if it does still allow the Err.raise to execute below. (In this case Err.raise will fail " and itself will raise an error "Invalid procedure call or argument" indicating that 0 cannot be used to raise and error! Case 111111 " You might want to do special error handling for some predicted error numbers " perhaps resulting in a exit sub with no error or " perhaps using the Err.raise below Case Else " Just the Err.raise below is used for all other errors End Select " " I include the procedure ManageErrSource as an exmple of how Err.Source can be used to maintain a call stack of procedure names " and store the name of the procedure that FIRST raised the error. " Err.Raise Err.Number _ , ManageErrSource("MyModuleName", Err.Source, Erl(), "tsub1_RaisesProcedureNotFoundError") _ , Err.Number & "-" & Err.Description " Note the next line never gets excuted, but I like to have resume in the code for when I am debugging. " (When a break is active, by moving the next executable line onto it, and using step over, it moves the exection point to the line that actually raised the error) Resume End Sub Sub SubProcedure1() " ----------------------------------------------------- " Example of a multiple line TRY block with a Case statement used to CATCH the error " " It is sometimes better to NOT use this technique but to put the code in it"s own procedure " (ie I refer to the code below that is surrounded by the tag #OWNSUB) . " However,sometimes using this technique makes code more readable or simpler! " Dim i As Integer " This line puts in place the defualt error handler found at the very foot of the procedure On Error GoTo HandleError " " Perhaps lots of statements and code here " " First an example with comments " ----------------------------------------------------- " TRY BLOCK START " This next line causes execution to "jump" to the "catch" block in the event an error is detected. On Error GoTo CatchBlock1_Start " #OWNSUB tsub_WillNotRaiseError_JustPrintsOk If vbYes = MsgBox("1. Do you want to raise an error in the try block? - (PRESS CTRL+BREAK now then choose YES, try no later.)", vbYesNo) Then i = 100 / 0 End If " " Perhaps lots of statements and code here " " #OWNSUB " TRY BLOCK END " ----------------------------------------------------- " ----------------------------------------------------- " CATCH BLOCK START CatchBlock1_Start: If Err.Number = 0 Then On Error GoTo HandleError " Re-instates the procedure"s generic error handler " This is also done later, but I think putting it here reduces the likelyhood of a coder accidentally removing it. Else " WARNING: BE VERY CAREFUL with any code that is written here as " the "On Error GoTo CatchBlock1_Start" is still in effect and therefore any errors that get raised could goto this label " and cause and infinite loop. " NOTE that a replacement "On Error Goto" cannot be executed until Err.clear is used, otherwise the "On Error Goto" " will itself raise and error. " THEREFORE KEEP THE CODE HERE VERY SIMPLE! " RememberThenClearTheErrorObject should be the only code executed and this called procedure must be tight! " This saves the details of the error in variables so that the "On Error GoTo HandleError" can be used " to determine how the next Err.Raise used below is handled (and also how any unexpected implicitly raised errors are handled) RememberThenClearTheErrorObject On Error GoTo HandleError "#THISLINE# If vbYes = MsgBox("2. Do you want to raise an error in the erro handler? - (PRESS CTRL+BREAK now then try both YES and NO )", vbYesNo) Then i = 100 / 0 End If Select Case RememberErrNumber Case 0: " No Error, do Nothing Case 2517 Debug.Print "The coder has decided to just give a Warning: Procedure not found " & Err.Number & " - " & Err.Description ClearRememberedErrorObjectValues " Not essential, but might save confusion if coding errors are made Case Else " An unexepected error or perhaps an (user) error that needs re-raising occurred and should to be re-raised " NOTE this is giving an example of what woudl happen if the CatchBlock1_ErrorElse is not used below If vbYes = MsgBox("3. Do you want to raise an error in the ELSE error handler? CatchBlock1_ErrorElse *HAS NOT* been used? - (PRESS CTRL+BREAK now then try both YES and NO )", vbYesNo) Then i = 100 / 0 End If On Error GoTo CatchBlock1_ErrorElse " SOME COMPLEX ERROR HANDLING CODE - typically error logging, email, text file, messages etc.. " Because the error objects values have been stored in variables, you can use " code here that might itself raise an error and CHANGE the values of the error object. " You might want to surround the code with the commented out CatchBlock1_ErrorElse lines " to ignore these errors and raise the remembered error. (or if calling a error handling module " just use on error resume next). " Without the CatchBlock1_ErrorElse lines any error raised in this "complex code" will be handled by the " active error handler which was set by the "On Error GoTo HandleError" tagged as "#THISLINE#" above. If vbYes = MsgBox("4. Do you want to raise an error in the ELSE error handler when CatchBlock1_ErrorElse HAS been used? - (PRESS CTRL+BREAK now then try both YES and NO )", vbYesNo) Then i = 100 / 0 End If CatchBlock1_ErrorElse: On Error GoTo HandleError " This line must be preceeded by an new "On error goto" for obvious reasons Err.Raise RememberErrNumber, RememberErrSource, RememberErrDescription End Select On Error GoTo HandleError End If " CATCH BLOCK END " ----------------------------------------------------- On Error GoTo HandleError " Unnecessary but used to delimt the catch block " " lots of code here perhaps " " ----------------------------------------------------- " Example 2 " " In this example goto statements are used instead of the IF statement used in example 1 " and no explanitory comments are given (so you can see how simple it can look) " " ----------------------------------------------------- " TRY BLOCK START On Error GoTo CatchBlock2_Start tsub_WillNotRaiseError_JustPrintsOk If vbYes = MsgBox("Do you want to raise an error? - (PRESS CTRL+BREAK now then choose YES)", vbYesNo) Then i = 100 / 0 End If " " Perhaps lots of statements and code here " " TRY BLOCK END " ----------------------------------------------------- GoTo CatchBlock2_End: CatchBlock2_Start: RememberThenClearTheErrorObject On Error GoTo HandleError Select Case RememberErrNumber Case 0: " No Error, do Nothing Case 2517 Debug.Print "The coder has decided to just give a Warning: Procedure not found " & Err.Number & " - " & Err.Description ClearRememberedErrorObjectValues " Not essential, but might save confusion if coding errors are made Case Else " An unexepected error or perhaps an (user) error that needs re-raising occurred and should to be re-raised " In this case the unexpecetd erro will be handled by teh code that called this procedure " This line must be preceeded by an new "On error goto" for obvious reasons Err.Raise RememberErrNumber, RememberErrSource, RememberErrDescription End Select On Error GoTo HandleError End If CatchBlock2_End: " CATCH BLOCK END " ----------------------------------------------------- On Error GoTo HandleError " Unnecessary but used to delimt the catch block " " Here you could add lots of lines of vba statements that use the generic error handling that is after the HandleError: label " " " " You could of course, alway add more TRY CATCH blocks like the above " " Exit Sub HandleError: Select Case Err.Number Case 0 " This shold never happen as this code isan error handler! " However if it does still allow the Err.raise to execute below. (In this case Err.raise will fail " and itself will raise an error "Invalid procedure call or argument" indicating that 0 cannot be used to raise and error! Case 111111 " You might watch to do special error handling for some predicted error numbers " perhaps exit sub " Perhaps using the Err.raise below End Select " ie Otherwise " " Note that I use the Err.Source to maintain a call stack of procedure names " Err.Raise Err.Number _ , ManageErrSource("MyModuleName", Err.Source, Erl(), "tsub1_RaisesProcedureNotFoundError") _ , Err.Number & "-" & Err.Description " Note the next line never gets excuted, but I like to have resume in the code for when I am debugging. " (By moving the next executable line onto it, and using step over, it moves the exection point to the line that actually raised the error) Resume End Sub Sub tsub_WillNotRaiseError_JustPrintsOk() Static i As Integer i = i + 1 Debug.Print "OK " & i End Sub Public Function ManageErrSource(MyClassName As String, ErrSource As String, ErrLine As Integer, ProcedureName As String) As String " This function would normally be in a global error handling module " On Error GoTo err_ManageErrSource Const cnstblnRecordCallStack As Boolean = True Select Case ErrSource Case Application.VBE.ActiveVBProject.Name " Err.Source is set to this value when a VB statement raises and error. eg In Access by defualt it is set to "Database" ManageErrSource = Application.VBE.ActiveVBProject.Name & " " & MyClassName & "." & ProcedureName & ":" & ErrLine Case "" " When writing code ouside of the error handling code, the coder can raise an error explicitly, often using a user error number. " ie by using err.raise MyUserErrorNumber, "", "My Error descirption". " The error raised by the coder will be handled by an error handler (typically at the foot of a procedure where it was raised), and " it is this handler that calls the ManageErrSource function changing the Err.Source from "" to a meaningful value. ManageErrSource = Application.VBE.ActiveVBProject.Name & " " & MyClassName & "." & ProcedureName & ":" & ErrLine Case Else " This code is executed when ManageErrSource has already been called. The Err.Source will already have been set to hold the " Details of where the error occurred. " This option can be used to show the call stack, ie the names of the procdures that resulted in the prcedure with the error being called. If cnstblnRecordCallStack Then If InStr(1, ErrSource, ";") = 0 Then ManageErrSource = ErrSource & ":: Called By: " End If ManageErrSource = ErrSource & ";" & ProcedureName & ":" & ErrLine Else ManageErrSource = ErrSource End If End Select Exit Function err_ManageErrSource: Err.Raise Err.Number, "MyModuleName.err_ManageErrSource", Err.Description Resume End Function
コメント
回答
問題は、 VBAは例外ではなく、VBAでのエラー処理は、例外処理とほとんど共通点がありません。
RememberErrLine = Erl()
Erl
関数は、理由によりVBA.Information
モジュールの非表示のメンバーです。番号付きの行でエラーが発生しない限り0を返します。また、「VBAで行番号を使用している場合は、洞窟に25年間住んでいて、プロシージャを記述する代わりにGoSub
ステートメントを使用している可能性があります。行番号はサポートされていますレガシー/後方互換性の理由で、1980年代に書かれたコードがそれらを必要したためです。
あなたが自分で言った方法が好きです:
" THEREFORE KEEP THE CODE HERE VERY SIMPLE!
..しかし、それがコードの残りの部分に適用されないのはなぜですか?違反はありませんが、これはスパゲッティロジックであり、明らかに恥知らずに単一責任の原則。SRP準拠のコードでは、このような” try-catch “ブロックを2つ必要としません。
これはにおいがします:
Case 0: " No Error, do Nothing
これは、次の2つのいずれかを意味します。エラー以外のコンテキストで実行されるエラー処理コードがあるか、または、削除する必要のあるデッドコードがあります。
この臭い リーク:
GoTo CatchBlock2_End: CatchBlock2_Start:
まず、ac 行ラベルを指定していないolon(:
)は、命令セパレーターです。改行も”命令セパレータ”であることが判明したため、
はまったく役に立たず、混乱を招きます。特に、GoToステートメントのインデントレベルを考えると。
GoTo
と言えば…
ニール・スティーブンソンは、自分のレーベルに「デンゴ」という名前を付けるのはかわいいと考えています。
コードに従うためにラベル間をジャンプする方法が気に入らない。 IMOは 乱雑で、不必要にスパゲッティ化されています。
すばらしい、スマーティパント。では、VBAのエラーをどのようにきれいに処理するのでしょうか?
1。そもそもクリーンなコードを記述します。
ベストプラクティスを順守し、1つのことを実行する小さな手順を記述します。
2。オブジェクト指向コードを記述します。
抽象化とカプセル化は、OOPの4つの柱のうちの2つです。 、およびそれらは「VBAで完全にサポートされています。ポリモーフィズムもある程度のオプションです。適切な継承のみが除外されますが、それでも概念を抽象化することはできます。クラスモジュールと特殊なオブジェクトのインスタンス化。
標準モジュール(。bas)で記述された手続き型コードは、小さなパブリックメソッド(macro ” hooks “)機能を実行するために必要なオブジェクトを作成します。
では、それはリモートでも適切なエラー処理にどのように関連していますか?
3。慣用的なエラー処理を採用し、それと戦わないでください。
上記の点に準拠したコードがあれば、エラー処理を実装しない理由はありません。慣用的なVBAウェイ。
Public Sub DoSomething() On Error GoTo CleanFail "method body CleanExit: "cleanup code goes here. runs regardless of error state. Exit Sub CleanFail: "handle runtime error(s) here. "Raise Err.Number ""rethrow" / "bubble up" Resume CleanExit Resume "for debugging - break above and jump to the error-raising statement End Sub
このパターンは、” try-catch-finally
は次のようになります:
- 本体は” try “の部分、メソッド名の内容を実行するだけです
-
CleanFail
は” catch “部分。エラーが発生した場合にのみ実行されます -
CleanExit
は”最後に”の部分であり、エラーが発生したかどうかに関係なく実行されます。 em> … “ rethrowing でない限り。ただし、呼び出し元のコードが処理するためにエラーをバブルアップする必要がある場合は、クリーンアップをあまり行わないでください。実行する必要があります。そうする理由は非常にあります。
エラー処理サブルーチンでエラーが発生する可能性がある場合は、SRPに準拠していません。たとえば、ログファイルへの書き込みはそれ自体の問題であり、ログの問題に対処するために存在するLogger
オブジェクトに抽象化する必要があり、独自のエラーを処理するメソッドを公開します。 。エラー処理サブルーチンコードは簡単なはずです。
コメント
- @mat ‘ smugに感謝します私が’暴力的でありながらユーモラスな批判をするのに本当に役立った、コメントを追加する時が来ました。私は’コードを確認しており、’大多数があなたが概説した原則を順守していることを嬉しく思います。あなたの説明は役に立ちましたが、’ VBおよびSQLServerのTRYCATCHステートメントが各プロシージャで1回だけ使用されていることを理解していなかったことを反映し、認識しました(読みやすくするためにコードを抽象化する必要がないことを意味します)。 ManageErrSourceプロシージャに関するコメントをさらに追加したい場合は、’すべての耳に…
- @HarveyFrench I ‘機会があればさらに追加します-‘これを見ていませんでした;-) VBIDE APIを参照して使用するには、特別なセキュリティ設定が必要ですが、これはクールではありません。 ‘クラスモジュールのカスタムエラーのソースとして
TypeName(Me)
を使用するようになり、エラーがどの手順を知る唯一の方法になりました。これは、プロシージャ名をローカルのconst
にハードコーディングすることで発生しました。理想的には、メソッドの署名からそれほど遠くない場所にあります。’コールスタックのアイデアは気に入っていますが、欠点の1つは、一貫して”プッシュ”と” pop “プロシージャを開始/終了するたびに、それ以外の場合は嘘になります。 - fmsinc.comから受け取ったコードはよく使われます。 ‘が抱えている問題の私は’あなたの意見を大切にしています。こちらをご覧ください codereview.stackexchange.com/questions/94498/ … これが私を駆り立てているので、お時間をいただきありがとうございますナッツ。
回答
Mat “sMug しかし、彼はあなたが実際にエラーから回復する方法を知っている状況をカバーしていませんでした。完全を期すために、それについて説明したいと思います。
まず、VB.Netでこのようなことを行う方法を見てみましょう。
Try foo = SomeMethodLikelyToThrowAnException Catch e As SomeException foo = someDefaultValue End Try " some more code
VB6でこれを行う慣用的な方法は、ResumeNext
です。これを書き留めてください。これは、 のみ の時間だからです。 ResumeNext
の権利。
On Error Goto ErrHandler foo = SomeMethodLikelyToRaiseAnError " some more code CleanExit: " clean up resources Exit Sub ErrHandler: If Err.Number = ConstantValueForErrorWeExpected Then foo = someDefaultValue Resume Next End If Resume CleanExit "or re-raise error Exit Sub
別の方法は、このロジックをインライン化することです。これは少しクリーンだと思います。 Try...Catch
イディオムに近いですが、悪用するとすぐに醜くなる可能性があります。
On Error Resume Next foo = SomeMethodLikelyToRaiseAnError If Err.Number = ConstantValueForErrorWeExpected Then foo = someDefaultValue End If On Error Goto 0
どちらも予想されるエラーに対処する慣用的な方法ですが、何をするにしても、何をするのか、いつ適切なのかを完全に理解するまで、Resume Next
を気にしないでください。 。(あなたよりも将来の読者への警告です。あなたはVB6でのエラー処理を完全に理解しているようです。おそらくあなた自身の利益には少し良すぎるでしょう。)
コメント
- 有益なコメントをありがとう@RubberDuck。正直なところ、私は”エラー時に次の”を使用しています。かなりの数のプロシージャがafteを呼び出しますr通常、発生したエラーに応答するSELECTCASEがあります。私が犯している大きな間違いは、サブプロシージャでユーザー定義の例外を発生させて、発生した状況(ユーザーが処理のキャンセルを要求するなど)にフラグを立てることです。私はもっと機能を使っていると思います。これは、私の一般的なコード構造が”理想的ではない” / poorであることを示しており、これに対処する必要があると思います。ありがとう。
- あなたは’素晴らしいポイント@HarveyFrenchをヒットしました。例外は例外的な動作であり、制御フローではありません。 CRへようこそ。
- ‘このSOの質問に対するあなたの意見に非常に興味があります: stackoverflow。 com / questions / 31007009 / …
- fmsinc.comから受け取ったコードは、私が’持っています。私は’あなたの意見を大切にしています。こちらをご覧ください codereview.stackexchange.com/questions/94498/ …
回答
この回答は、Try / Catchパターンを単純化して簡単に理解できるようにすることを目的としています。
これは一度に複数行をスキップし、エラーを処理してから通常の実行を再開できることを除いて、通常のインラインエラー処理。これは、エラーを処理するための非常に明確に構造化されたパターンです。流れは上から下に非常にきれいに移動します。ここにはスパゲッティコードはありません。
従来、エラーハンドラーは下部に配置されていました。しかし、Try / Catchコンストラクトは非常にエレガントです。これは「エラーを処理する非常に構造化された方法であり、非常に簡単に追跡できます。このパターンは、非常にクリーンで簡潔な方法でそれを再現しようとします。フローは非常に一貫しており、場所から場所へとジャンプしません。
Sub InLineErrorHandling() "code without error handling BeginTry1: "activate inline error handler On Error GoTo ErrHandler1 "code block that may result in an error Dim a As String: a = "Abc" Dim c As Integer: c = a "type mismatch ErrHandler1: "handle the error If Err.Number <> 0 Then "the error handler is now active Debug.Print (Err.Description) End If "disable previous error handler (VERY IMPORTANT) On Error GoTo 0 "exit the error handler Resume EndTry1 EndTry1: "more code with or without error handling End Sub
出典:
適切に管理すると、これは非常にうまく機能します。これは非常にクリーンなフローパターンであり、必要な場所で再現できます。
コメント
- @D_Bester、リンクと簡単な例をありがとう。 ‘はまだ学習中であり、フィードバックは役に立ちましたが、”エラーが発生した場合は0 “のエラー後-1 “に移動します。また、振り返ってみると、”エラー時Goto-1 “の代わりにErr.Clearを使用する方が良いと思います。起こっている。 ‘ VBAでのこのエラー処理全体を少しブラックアートだと思っています。
- @D_Bester。振り返ってみると、エラーが発生したときにユーザーにメッセージを表示したい場合はコードで問題ありませんが、エラーを再度発生させたい場合はどうでしょうか。これは非常に一般的なシナリオになります。考えてみてください。コードで顧客の’の詳細を検索しようとしていて、予期しない理由で’取得できなかった場合。エラーを再発生させ、ルックアップを実行するためにコードを使用しているコードに何をするかを決定させる必要があります。
- @HarveyFrenchエラーを再発生させたい場合は、
Err.Raise ‘。コードが適切に構造化されており、呼び出し元のコードでエラー処理が有効になっている場合は問題ありません。
Err.Clear
およびOn Error Goto -1
は同等ではありません。 stackoverflow.com/a/30994055/2559297 回答
「CleanExit」と「Finally」トピックに関して。
Mats Mugは次のように書いています:
CleanExitは
最後に”の部分で、エラーが発生したかどうかに関係なく実行されます…再スローしない限り。
このような状況は、たとえば次の手続き型コードで発生する可能性があります。
手続き型アプローチ
Public Sub DoSomething() On Error GoTo CleanFail " Open any resource " Use the resource CleanExit: " Close/cleanup the resource Exit Sub CleanFail: Raise Err.Number Resume CleanExit End Sub
ここに問題があります:CleanFailで再発生する必要があるメソッド本体でエラーが発生した場合、CleanExitは実行されません。すべて、したがってリソースを適切に閉じることができません。
確かに、エラーハンドラ自体でもリソースを閉じることができますが、リソース処理が行われる/行われる複数のコードフラグメントが発生する可能性があります。
リソースバインディングの必要性ごとにカスタムオブジェクトを使用することをお勧めします:
AnyResourceBindingClass
Private Sub Class_Initialize() "Or even use Mats "Create method" approach here instead. "Open/acquire the resource here End Sub Private Sub Class_Terminate() On Error GoTo CleanFail "Close/clean up the resource here properly CleanExit: Exit Sub CleanFail: MsgBox Err.Source & " : " & Err.Number & " : " & Err.Description Resume CleanExit End Sub Public Sub UseResource() "Do something with the resource End Sub
オブジェクト指向アプローチ
Public Sub DoSomething() On Error GoTo CleanFail " Use custom object which acquires the resource With New AnyResourceBindingClass .UseResource End With CleanExit: Exit Sub CleanFail: Raise Err.Number Resume CleanExit End Sub
機会:エラーが発生した後、カスタムオブジェクトはスコープ外になるため、そのTerminateメソッドが自動的に実行されます。これにより、取得したリソースが適切に閉じられ、クリーンアップされます。
「finally」ブロック。
Terminateメソッドでのエラー処理
私の意見では、コンテキストに依存します how エラーはカスタムクラスのTerminateメソッドで処理されます。おそらく、どこかに追加で記録する必要がありますか、それとも飲み込む必要がありますか?
確かにこれは議論の余地があります。
しかし、はこのメソッドでエラーハンドラを有効にします。これは、私が知る限り、このメソッドで未処理のエラーが発生すると、VBAが実行を中断し、標準のランタイムエラーメッセージボックスが表示されるためです。
回答
以前の投稿を明確にするために、HarveyFrenchのコードから次の行を入力します。
RememberErrLine = Erl()
コードのすべての行に行番号を追加しないと機能しません。手間がかかりすぎる行番号を手動で入力する代わりに、ツールを使用して行番号を自動的に追加できます。これを実行できるツールがいくつかあります。CodeLinerと呼ばれるツールを使用します。
これは、Erl()
が正常に機能するようにする行番号付きのコードです。 :
Option Compare Database Option Explicit Dim RememberErrNumber As Long Dim RememberErrDescription As String Dim RememberErrSource As String Dim RememberErrLine As Integer Private Sub RememberThenClearTheErrorObject() 10 11 On Error Resume Next 12 " For demo purposes 14 Debug.Print "ERROR RAISED" 15 Debug.Print Err.Number 16 Debug.Print Err.Description 17 Debug.Print Err.Source 18 Debug.Print " " 19 20 " This function has to be declared in the same scope as the variables it refers to 22 RememberErrNumber = Err.Number 23 RememberErrDescription = Err.Description 24 RememberErrSource = Err.Source 25 RememberErrLine = Erl() 26 " Note that the next line will reset the error object to 0, the variables above are used to remember the values " so that the same error can be re-raised 29 Err.Clear 30 " Err.Clear is used to clear the raised exception and set the err object to nothing (ie err.number to 0) " If Err.Clear has not be used, then the next "On Error GoTo ALabel" that is used in this or the procedure that called it " will actually NOT pass execution to the ALabel: label BUT the error is paseed to the procedure that called this procedure. " Using Err.Clear (or "On Error GoTo -1 ") gets around this and facilitates the whole TRY CATCH block scenario I am using there. 35 36 " For demo purposes 38 Debug.Print "ERROR RAISED is now 0 " 39 Debug.Print Err.Number 40 Debug.Print Err.Description 41 Debug.Print Err.Source 42 Debug.Print " " 43 " For demo purposes 45 Debug.Print "REMEMBERED AS" 46 Debug.Print RememberErrNumber 47 Debug.Print RememberErrDescription 48 Debug.Print RememberErrSource 49 Debug.Print " " 50 End Sub Private Sub ClearRememberedErrorObjectValues() 54 " This function has to be declared in the same scope as the variables it refers to 56 RememberErrNumber = 0 57 RememberErrDescription = "" 58 RememberErrSource = "" 59 RememberErrLine = 0 60 End Sub Sub ExampleOfTryCatchBlockInVBA() 67 68 On Error GoTo HandleError 69 70 " ----------------------------------------------------- " SubProcedure1 has the example of a multiple line TRY block with a block of code executed in the event of an error 73 74 SubProcedure1 75 76 77 78 Exit Sub 79 HandleError: 80 81 Select Case Err.Number 82 Case 0 " This shold never happen as this code is an error handler! " However if it does still allow the Err.raise to execute below. (In this case Err.raise will fail " and itself will raise an error "Invalid procedure call or argument" indicating that 0 cannot be used to raise and error! 86 87 Case 111111 " You might want to do special error handling for some predicted error numbers " perhaps resulting in a exit sub with no error or " perhaps using the Err.raise below 91 92 Case Else " Just the Err.raise below is used for all other errors 94 95 End Select 96 " " I include the procedure ManageErrSource as an exmple of how Err.Source can be used to maintain a call stack of procedure names " and store the name of the procedure that FIRST raised the error. " 101 Err.Raise Err.Number _ , ManageErrSource("MyModuleName", Err.Source, Erl(), "tsub1_RaisesProcedureNotFoundError") _ , Err.Number & "-" & Err.Description 104 " Note the next line never gets excuted, but I like to have resume in the code for when I am debugging. " (When a break is active, by moving the next executable line onto it, and using step over, it moves the exection point to the line that actually raised the error) 107 Resume 108 End Sub Sub SubProcedure1() 112 " ----------------------------------------------------- " Example of a multiple line TRY block with a Case statement used to CATCH the error 115 " " It is sometimes better to NOT use this technique but to put the code in it"s own procedure " (ie I refer to the code below that is surrounded by the tag #OWNSUB) . " However,sometimes using this technique makes code more readable or simpler! " 121 122 Dim i As Integer 123 " This line puts in place the defualt error handler found at the very foot of the procedure 125 On Error GoTo HandleError 126 127 " " Perhaps lots of statements and code here " 131 132 " First an example with comments 134 135 " ----------------------------------------------------- " TRY BLOCK START 138 " This next line causes execution to "jump" to the "catch" block in the event an error is detected. 140 On Error GoTo CatchBlock1_Start 141 " #OWNSUB 143 144 tsub_WillNotRaiseError_JustPrintsOk 145 146 If vbYes = MsgBox("1. Do you want to raise an error in the try block? - (PRESS CTRL+BREAK now then choose YES, try no later.)", vbYesNo) Then 147 i = 100 / 0 148 End If 149 " " Perhaps lots of statements and code here " 153 " #OWNSUB 155 " TRY BLOCK END " ----------------------------------------------------- 158 159 " ----------------------------------------------------- " CATCH BLOCK START 162 CatchBlock1_Start: 163 164 If Err.Number = 0 Then 165 On Error GoTo HandleError " Re-instates the procedure"s generic error handler " This is also done later, but I think putting it here reduces the likelyhood of a coder accidentally removing it. 168 169 Else 170 " WARNING: BE VERY CAREFUL with any code that is written here as " the "On Error GoTo CatchBlock1_Start" is still in effect and therefore any errors that get raised could goto this label " and cause and infinite loop. " NOTE that a replacement "On Error Goto" cannot be executed until Err.clear is used, otherwise the "On Error Goto" " will itself raise and error. " THEREFORE KEEP THE CODE HERE VERY SIMPLE! " RememberThenClearTheErrorObject should be the only code executed and this called procedure must be tight! 178 " This saves the details of the error in variables so that the "On Error GoTo HandleError" can be used " to determine how the next Err.Raise used below is handled (and also how any unexpected implicitly raised errors are handled) 181 RememberThenClearTheErrorObject 182 183 On Error GoTo HandleError "#THISLINE# 184 185 If vbYes = MsgBox("2. Do you want to raise an error in the erro handler? - (PRESS CTRL+BREAK now then try both YES and NO )", vbYesNo) Then 186 i = 100 / 0 187 End If 188 189 Select Case RememberErrNumber 190 Case 0: " No Error, do Nothing 191 192 Case 2517 193 Debug.Print "The coder has decided to just give a Warning: Procedure not found " & Err.Number & " - " & Err.Description 194 ClearRememberedErrorObjectValues " Not essential, but might save confusion if coding errors are made 195 196 Case Else " An unexepected error or perhaps an (user) error that needs re-raising occurred and should to be re-raised 198 " NOTE this is giving an example of what woudl happen if the CatchBlock1_ErrorElse is not used below 200 If vbYes = MsgBox("3. Do you want to raise an error in the ELSE error handler? CatchBlock1_ErrorElse *HAS NOT* been used? - (PRESS CTRL+BREAK now then try both YES and NO )", vbYesNo) Then 201 i = 100 / 0 202 End If 203 204 On Error GoTo CatchBlock1_ErrorElse 205 206 " SOME COMPLEX ERROR HANDLING CODE - typically error logging, email, text file, messages etc.. " Because the error objects values have been stored in variables, you can use " code here that might itself raise an error and CHANGE the values of the error object. " You might want to surround the code with the commented out CatchBlock1_ErrorElse lines " to ignore these errors and raise the remembered error. (or if calling a error handling module " just use on error resume next). " Without the CatchBlock1_ErrorElse lines any error raised in this "complex code" will be handled by the " active error handler which was set by the "On Error GoTo HandleError" tagged as "#THISLINE#" above. 215 216 If vbYes = MsgBox("4. Do you want to raise an error in the ELSE error handler when CatchBlock1_ErrorElse HAS been used? - (PRESS CTRL+BREAK now then try both YES and NO )", vbYesNo) Then 217 i = 100 / 0 218 End If 219 220 CatchBlock1_ErrorElse: 221 On Error GoTo HandleError " This line must be preceeded by an new "On error goto" for obvious reasons 223 Err.Raise RememberErrNumber, RememberErrSource, RememberErrDescription 224 225 End Select 226 227 On Error GoTo HandleError 228 229 End If " CATCH BLOCK END " ----------------------------------------------------- 232 On Error GoTo HandleError " Unnecessary but used to delimt the catch block 233 234 235 236 " " lots of code here perhaps " 240 241 242 243 " ----------------------------------------------------- " Example 2 " " In this example goto statements are used instead of the IF statement used in example 1 " and no explanitory comments are given (so you can see how simple it can look) " 250 " ----------------------------------------------------- " TRY BLOCK START 253 254 On Error GoTo CatchBlock2_Start 255 256 tsub_WillNotRaiseError_JustPrintsOk 257 258 If vbYes = MsgBox("Do you want to raise an error? - (PRESS CTRL+BREAK now then choose YES)", vbYesNo) Then 259 i = 100 / 0 260 End If 261 " " Perhaps lots of statements and code here " 265 " TRY BLOCK END " ----------------------------------------------------- 268 269 270 GoTo CatchBlock2_End: 271 CatchBlock2_Start: 272 273 RememberThenClearTheErrorObject 274 275 On Error GoTo HandleError 276 277 Select Case RememberErrNumber 278 Case 0: " No Error, do Nothing 279 280 Case 2517 281 Debug.Print "The coder has decided to just give a Warning: Procedure not found " & Err.Number & " - " & Err.Description 282 ClearRememberedErrorObjectValues " Not essential, but might save confusion if coding errors are made 283 284 Case Else " An unexepected error or perhaps an (user) error that needs re-raising occurred and should to be re-raised " In this case the unexpecetd erro will be handled by teh code that called this procedure " This line must be preceeded by an new "On error goto" for obvious reasons 288 Err.Raise RememberErrNumber, RememberErrSource, RememberErrDescription 289 290 End Select 291 292 On Error GoTo HandleError 293 294 End If 295 296 CatchBlock2_End: " CATCH BLOCK END " ----------------------------------------------------- 299 On Error GoTo HandleError " Unnecessary but used to delimt the catch block 300 301 302 303 " " Here you could add lots of lines of vba statements that use the generic error handling that is after the HandleError: label " " 308 " " You could of course, alway add more TRY CATCH blocks like the above " " 313 314 315 316 Exit Sub 317 HandleError: 318 319 Select Case Err.Number 320 Case 0 " This shold never happen as this code isan error handler! " However if it does still allow the Err.raise to execute below. (In this case Err.raise will fail " and itself will raise an error "Invalid procedure call or argument" indicating that 0 cannot be used to raise and error! 324 325 Case 111111 " You might watch to do special error handling for some predicted error numbers " perhaps exit sub " Perhaps using the Err.raise below 329 End Select 330 " ie Otherwise " " Note that I use the Err.Source to maintain a call stack of procedure names " 335 Err.Raise Err.Number _ , ManageErrSource("MyModuleName", Err.Source, Erl(), "tsub1_RaisesProcedureNotFoundError") _ , Err.Number & "-" & Err.Description 338 " Note the next line never gets excuted, but I like to have resume in the code for when I am debugging. " (By moving the next executable line onto it, and using step over, it moves the exection point to the line that actually raised the error) 341 Resume 342 End Sub Sub tsub_WillNotRaiseError_JustPrintsOk() 348 349 Static i As Integer 350 351 i = i + 1 352 353 Debug.Print "OK " & i 354 End Sub Public Function ManageErrSource(MyClassName As String, ErrSource As String, ErrLine As Integer, ProcedureName As String) As String 360 " This function would normally be in a global error handling module 362 " On Error GoTo err_ManageErrSource 364 365 Const cnstblnRecordCallStack As Boolean = True 366 367 Select Case ErrSource 368 369 Case Application.VBE.ActiveVBProject.Name 370 " Err.Source is set to this value when a VB statement raises and error. eg In Access by defualt it is set to "Database" 372 373 ManageErrSource = Application.VBE.ActiveVBProject.Name & " " & MyClassName & "." & ProcedureName & ":" & ErrLine 374 375 Case "" 376 " When writing code ouside of the error handling code, the coder can raise an error explicitly, often using a user error number. " ie by using err.raise MyUserErrorNumber, "", "My Error descirption". " The error raised by the coder will be handled by an error handler (typically at the foot of a procedure where it was raised), and " it is this handler that calls the ManageErrSource function changing the Err.Source from "" to a meaningful value. 381 382 ManageErrSource = Application.VBE.ActiveVBProject.Name & " " & MyClassName & "." & ProcedureName & ":" & ErrLine 383 384 Case Else 385 " This code is executed when ManageErrSource has already been called. The Err.Source will already have been set to hold the " Details of where the error occurred. " This option can be used to show the call stack, ie the names of the procdures that resulted in the prcedure with the error being called. 389 390 If cnstblnRecordCallStack Then 391 392 If InStr(1, ErrSource, ";") = 0 Then 393 ManageErrSource = ErrSource & ":: Called By: " 394 End If 395 ManageErrSource = ErrSource & ";" & ProcedureName & ":" & ErrLine 396 397 Else 398 ManageErrSource = ErrSource 399 400 End If 401 402 End Select 403 404 Exit Function 405 err_ManageErrSource: 406 Err.Raise Err.Number, "MyModuleName.err_ManageErrSource", Err.Description 407 Resume 408 End Function
コメント
- こんにちは!コードレビューへようこそ。回答にコンテキストを追加してください。提案によってOP ‘のコードが改善される理由を説明するか、言いたいことについて詳しく説明してください。
On Error Goto -1
On Error Resume Next
およびOn Error GoTo 0
およびErr.Number
を確認します。上記はややわかりにくく、spaghetti
の構造が少しあります。