Le code ci-dessous est assez explicite: il suffit de copier et coller le tout dans un module et de lexécuter , il fournit quelques cas dutilisation et de nombreux commentaires explicatifs dans le texte. (Cela fonctionne mais je suis intéressé de savoir ce que les autres en font et pour toutes les suggestions que vous aimeriez faire.)

Les faits les plus importants à réaliser sont:

  1. Lorsque vous utilisez sur erreur goto Label1, la procédure entre dans un état « Je » m gère une erreur « car une exception a été levée. Lorsquil est dans cet état, si une autre instruction label2 « On Error Goto » est exécutée, elle ne passera PAS à label2, mais déclenche une erreur qui est passée au code qui a appelé la procédure.

  2. Vous pouvez arrêter une procédure dans létat « Je » m gère une erreur « en effaçant lexception (en définissant err sur rien pour que la propriété err.number devienne 0) en en utilisant

    Err.clear or On Error Goto -1 " Which I think is less clear! 

(NOTEZ que On Error Goto 0 est différent de ce qui précède)

Il est également important de noter quErr.Clear le remet à zéro mais il est en fait équivalent à:

On Error Goto -1 On Error Goto 0 

ie Err.Clear supprime « On Error Goto » qui est actuellement en place. Il est donc préférable dutiliser:

On Error Goto -1 

en utilisant Err.clear Vous auriez souvent besoin de write

Err.Clear On Error Goto MyErrorHandlerLabel 

Jutilise les techniques ci-dessus avec différentes étiquettes pour simuler les fonctionnalités parfois utiles que donnent les blocs Visual basic TRY CATCH, qui, je pense, ont leur place dans lécriture code lisible.

Certes, cette technique crée quelques lignes de code de plus quune belle instruction VB try catch, mais ce nest pas trop compliqué et assez facile à obtenir r faites le tour.

PS. La procédure ManageErrSource qui fait en sorte que la propriété Err.Source stocke la procédure où lerreur sest produite pourrait également être intéressante.

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 

Commentaires

  • Seulement deux commentaires: 1 Pourquoi diable utiliseriez-vous cela? 2 On Error Goto -1
  • Try / catch peut également être émulé en enveloppant le code correspondant avec On Error Resume Next et On Error GoTo 0 et en vérifiant le Err.Number. Ce qui précède est un peu difficile à suivre, a un peu une structure spaghetti ..
  • Merci Rory, je ‘ je lai changé. Vous lutiliseriez ou pour la même raison que nimporte qui utiliserait une instruction TRY CATCH dans VB ou SQL Server. cest-à-dire quil vous permet de structurer votre code différemment. cest-à-dire que vous pouvez utiliser le même gestionnaire derreurs pour plusieurs lignes de code sans avoir à mettre les lignes dans leur propre procédure.
  • Loannis. oui, jai ‘ fait cela dans le passé, pour des lignes de code uniques qui nécessitent un gestionnaire derreurs. Ie une ligne de code a un gestionnaire derreur. TRY CATCH permet à un bloc de code (avec de nombreuses lignes) dêtre intégré dans une procédure avec son propre gestionnaire derreurs de ‘. Jutilise beaucoup TRY CATCH dans SQL Server et comme il ‘ est également disponible dans VB, il doit servir à des fins généralement utiles. Certes, cette version est un peu brouillonne.
  • @Loannis Que faire si vous voulez sauter plusieurs lignes lorsque vous obtenez une erreur. Voir ma réponse pour un exemple simplifié. Bien sûr, vous pouvez également le faire avec une gestion régulière des erreurs.

Réponse

Le problème est que les erreurs dexécution dans VBA ne sont pas des exceptions , et la gestion des erreurs dans VBA a très peu en commun avec la gestion des exceptions.

RememberErrLine = Erl() 

Le Erl La fonction est un membre masqué du module VBA.Information pour une raison – elle renvoie 0 à moins que lerreur ne se produise sur une ligne numérotée. Et si vous « utilisez des numéros de ligne dans VBA, vous vivez dans une grotte depuis 25 ans et utilisez probablement des instructions GoSub au lieu décrire des procédures. Les numéros de ligne sont pris en charge pour des raisons dhéritage / de compatibilité descendante , car le code écrit dans les années 1980 les a requis .

Jaime la façon dont vous lavez dit vous-même:

" THEREFORE KEEP THE CODE HERE VERY SIMPLE! 

..mais pourquoi cela ne sapplique-t-il pas au reste du code? Aucune infraction, mais cest une logique spaghetti, écrite dans des procédures qui violent clairement et sans vergogne le Principe de responsabilité unique . Aucun code conforme à SRP n’aurait besoin de deux de ces blocs  » try-catch « .

Cela sent:

Case 0: " No Error, do Nothing 

Cela signifie lune des deux choses suivantes: soit vous avez un code de gestion des erreurs qui sexécute dans des contextes sans erreur, ou vous avez du code mort qui devrait être supprimé.

Cela sent puait :

GoTo CatchBlock2_End: CatchBlock2_Start: 

Premièrement, ac olon (:) qui ne spécifie pas de étiquette de ligne , est un séparateur dinstructions .Il savère quune nouvelle ligne est également un  » séparateur dinstructions « , donc les deux points à la fin de GoTo CatchBlock2_End est totalement inutile et déroutant, surtout compte tenu du niveau dindentation de linstruction GoTo .

En parlant de GoTo

Neil Stephenson trouve » mignon de nommer ses étiquettes « dengo »

Je naime pas la façon dont je dois passer dune étiquette à lautre pour suivre le code. OMI, il est désordonné et inutilement spaghetti.


Bien, smartypants . Alors, comment gérer proprement les erreurs dans VBA alors?

1. Rédigez un code propre en premier lieu.

Adhérez aux meilleures pratiques et écrivez de petites procédures qui font une chose, et bien le faire.

2. Écrivez du code orienté objet.

Labstraction et l encapsulation sont deux des 4 piliers de la POO , et ils « sont entièrement pris en charge dans VBA. Le polymorphisme est également une option; seul un héritage approprié est exclu, mais cela n’empêche pas d’abstraire des concepts dans modules de classe et instanciation dobjets spécialisés.

Le code procédural écrit dans les modules standard (.bas) doit être de minuscules petites méthodes publiques (macro  » hooks « ) qui créent les objets requis pour exécuter la fonctionnalité.

Alors, comment cela se rapporte-t-il même à distance à une bonne gestion des erreurs?

3. Adoptez la gestion des erreurs idiomatiques, ne la combattez pas.

Étant donné le code qui respecte les points ci-dessus, il ny a aucune raison de ne pas implémenter la gestion des erreurs la voie VBA idiomatique.

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 

Ce modèle est analogue à un  » try-catch-finally  » de la manière suivante:

  • Le corps est le  » essayez  » partie, qui fait ce que le nom de la méthode dit et rien de plus
  • CleanFail est le  » catch  » partie, qui ne sexécute quen cas derreur
  • CleanExit est la partie  » enfin « , qui sexécute indépendamment du fait quune erreur ait été déclenchée ou non … sauf si vous « re relancez . Mais si vous avez besoin de faire remonter une erreur que le code appelant doit gérer, vous ne devriez pas avoir beaucoup de nettoyage ode à exécuter, et vous devriez avoir une très très bonne raison de le faire.

Si votre sous-programme de gestion d’erreurs peut générer une erreur, alors vous n’adhérez pas à SRP. Par exemple, lécriture dans un fichier journal est une préoccupation en soi, qui doit être résumée dans un objet Logger qui vit pour gérer les problèmes de journalisation et expose les méthodes qui gèrent leurs propres erreurs . Le code du sous-programme de gestion des erreurs doit être simple.

Commentaires

  • Merci @mat ‘ suffisant pour avoir pris le temps dajouter des commentaires qui mont vraiment aidé Je ‘ m pour des critiques violentes mais humoristiques. Jai ‘ révisé mon code et je ‘ est heureux de dire que la grande majorité adhère aux principes que vous décrivez. Votre explication était cependant utile et elle ma fait réfléchir et me rendre compte que je navais ‘ pas compris que les instructions VB et SQL Server TRY CATCH ne sont utilisées quune seule fois dans chaque procédure (je pensais quelles étaient signifie ne pas avoir à faire abstraction du code pour le rendre plus lisible). Si vous souhaitez ajouter dautres commentaires sur la procédure ManageErrSource, je ‘ m toute oreille …
  • @HarveyFrench I ‘ Jen ajouterai plus quand jen aurai loccasion – hadn ‘ t regardé celui-ci 😉 le référencement et lutilisation de lAPI VBIDE nécessitent des paramètres de sécurité spéciaux, ce qui nest pas cool. ‘ jen suis venu à utiliser TypeName(Me) comme source derreurs personnalisées dans les modules de classe, et le seul moyen pour une erreur de savoir quelle procédure il sest produit dans, consiste à coder en dur le nom de la procédure dans une const locale, idéalement pas trop loin de la signature de la méthode ‘. Jaime lidée de la pile dappels, mais un inconvénient est que vous devez systématiquement  » pousser  » et  » pop  » chaque fois que vous entrez / sortez dune procédure, sinon cela devient un mensonge.
  • Le code que jai reçu de fmsinc.com se déplace beaucoup des problèmes que jai ‘ rencontrés.Je ‘ d apprécie votre opinion. Voir ici codereview.stackexchange.com/questions/94498/… Japprécie votre temps car cela me motive noix.

Réponse

Listen to Mat « sMug , mais il na pas abordé la situation dans laquelle vous savez réellement comment récupérer après une erreur. Par souci dexhaustivité, jaimerais couvrir cela.

Voyons dabord comment nous ferions quelque chose comme ça dans VB.Net.

Try foo = SomeMethodLikelyToThrowAnException Catch e As SomeException foo = someDefaultValue End Try " some more code 

La manière idiomatique de faire cela dans VB6 est de ResumeNext. Notez ceci, car cest le seulement temps que je « dirai jamais » s à droite de 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 

Lautre moyen consiste à intégrer cette logique, qui je pense est un peu plus propre et plus proche de lidiome Try...Catch, mais cela peut devenir moche si vous en abusez.

On Error Resume Next foo = SomeMethodLikelyToRaiseAnError If Err.Number = ConstantValueForErrorWeExpected Then foo = someDefaultValue End If On Error Goto 0 

Lun ou lautre est un façon idiomatique de gérer les erreurs attendues, mais quoi que vous fassiez. Ne vous embêtez pas avec Resume Next jusquà ce que vous compreniez complètement ce que cela fait et quand cela est approprié (Plus un avertissement pour les futurs lecteurs que pour vous. Vous semblez bien comprendre la gestion des erreurs dans VB6. Peut-être un peu trop bien pour votre propre bien.)

Commentaires

  • Merci @RubberDuck pour vos commentaires utiles. Pour être honnête, je me retrouve à utiliser  » En cas derreur, reprendre suivant  » avant pas mal dappels de procédure après r pour lequel il existe généralement un SELECT CASE qui répond à toute erreur générée. La grande erreur que je réalise que je fais est que je lève une exception définie par lutilisateur dans la sous-procédure pour signaler les situations qui se présentent (comme lutilisateur demandant dannuler le traitement). Je pense que je devrais utiliser davantage les fonctions. Cest une indication que ma structure générale de code est  » pas idéale  » / pauvre et je pense et je dois résoudre ce problème. Merci.
  • Vous ‘ avez touché un excellent point @HarveyFrench. Les exceptions concernent le comportement exceptionnel , pas le flux de contrôle. Bienvenue au CR.
  • Je ‘ serais très intéressé par vos opinions sur cette question SO: stackoverflow. com / questions / 31007009 / …
  • Le code que jai reçu de fmsinc.com permet de contourner de nombreux problèmes que ‘ jai eu. Je ‘ d apprécie votre opinion. Voir ici codereview.stackexchange.com/questions/94498/…

Réponse

Cette réponse vise à simplifier le modèle Try / Catch pour quil soit facilement compréhensible.

Ce nest pas très différent de gestion derreurs en ligne régulière, sauf quil peut sauter plusieurs lignes à la fois, gérer une erreur et reprendre lexécution régulière. Il sagit dun modèle très bien structuré pour gérer une erreur. Le flux se déplace très proprement de haut en bas; pas de code spaghetti ici.

Traditionnellement, le gestionnaire derreurs est placé en bas. Mais la construction Try / Catch est si élégante. Cest une manière très structurée de gérer les erreurs et est très facile à suivre. Ce modèle tente de reproduire cela dune manière très claire et concise. Le flux est très cohérent et ne saute pas dun endroit à lautre.

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 

Sources:

Correctement géré, cela fonctionne très bien. Cest un modèle fluide très propre qui est reproductible partout où il est nécessaire.

Commentaires

  • @D_Bester, Merci pour les liens et lexemple simple. Je ‘ m toujours en apprentissage et jai trouvé vos commentaires utiles, mais vous devrez ajouter un  » On Error Goto 0  » après le  » sur Erreur goto -1 « . Aussi après réflexion, je pense quil est préférable dutiliser Err.Clear au lieu de  » On Error Goto -1  » car il montre plus clairement ce que est passe. Je ‘ trouve que toute cette gestion des erreurs dans VBA est un peu un art noir.
  • @D_Bester. Après réflexion, votre code est correct si vous voulez simplement donner un message à lutilisateur lorsquune erreur se produit, mais que faire si vous souhaitez relancer lerreur? Ce sera un scénario très courant. Considérer.Si votre code essayait de rechercher les détails dun client ‘ et quil ne pouvait ‘ les obtenir pour une raison INATTENDUE. Vous devrez relancer lerreur et laisser le code qui utilise votre code pour faire la recherche décider quoi faire.
  • @HarveyFrench Si vous voulez relancer lerreur, utilisez simplement ‘ Err.Raise ‘. Aucun problème en supposant que le code est bien structuré et que la gestion des erreurs est activée dans le code appelant.
  • @HarveyFrench Err.Clear et On Error Goto -1 ne sont PAS équivalents. Voir stackoverflow.com/a/30994055/2559297
  • Vous ‘ vous avez raison pas le même désolé. Mais je pense que le code ci-dessus a encore besoin de On Error GoTo -1 remplacé par Err.Clear sinon le  » ‘ plus de code sans gestion des erreurs  » passera à ErrHandler1 si une erreur se produit.

Réponse

Concernant « CleanExit » et le sujet « Enfin ».

La tasse de Mat « a écrit:

CleanExit est le  » enfin  » partie, qui sexécute indépendamment du fait quune erreur ait été déclenchée ou non … sauf si vous « relancez.


Une telle situation pourrait se produire par exemple dans ce code procédural:

Approche procédurale

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 

Problème ici : Si une erreur se produit dans le corps des méthodes qui doit être relancé dans CleanFail, CleanExit ne sera t tout et donc la ressource ne peut pas être fermée correctement.

Bien sûr, vous pouvez fermer la ressource également dans le gestionnaire derreurs lui-même, mais cela pourrait conduire à avoir plusieurs fragments de code où la gestion des ressources sera / a à faire.


Ma suggestion est dutiliser un objet personnalisé pour chaque nécessité de liaison de ressource:

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 

Approche orientée objet

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 

Opportunité : Étant donné que lobjet personnalisé sera hors de portée après le déclenchement de lerreur, sa méthode Terminate sera exécutée automatiquement, ce qui fera que la ressource acquise sera fermée / nettoyée correctement.

Une nécessité de moins pour un bloc « finalement ».


Gestion des erreurs dans la méthode Terminate

À mon avis, cela dépend du contexte comment une erreur sera gérée dans la méthode Terminate de la classe personnalisée. Peut-être devrait-il être enregistré quelque part en plus ou même avalé du tout?

Cest certainement discutable.

Mais cest essentiel pour activer un gestionnaire derreurs dans cette méthode, car, pour autant que je sache, toute erreur non gérée dans cette méthode provoquera linterruption de lexécution par VBA et affichera sa boîte de message derreur dexécution standard.

Réponse

Pour clarifier mon message précédent, la ligne suivante du code de HarveyFrench:

RememberErrLine = Erl() 

ne fonctionnera que si des numéros de ligne ont été ajoutés à chaque ligne de code. Plutôt que de saisir manuellement les numéros de ligne, ce qui est beaucoup trop fastidieux, vous pouvez utiliser un outil pour ajouter automatiquement les numéros de ligne. Là il y a quelques outils qui peuvent le faire, jen utilise un appelé CodeLiner.

Voici le code avec les numéros de ligne, qui permettra à Erl() de fonctionner correctement :

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 

Commentaires

  • Salut! Bienvenue dans Code Review. Veuillez ajouter du contexte à votre réponse: expliquez pourquoi votre suggestion améliorera le code de lOP ‘, ou expliquez peut-être plus en détail ce que vous essayez de dire.

Laisser un commentaire

Votre adresse e-mail ne sera pas publiée. Les champs obligatoires sont indiqués avec *