La manipulation d'erreur de VB est déficiente (mais elle existe) parce que trop simpliste et sans structure.
Ce tutoriel va vous montrer comment structurer votre manipulation d'erreur jusqu'à rendre votre application vraiment fiable.
La manipulation d'erreur de Java (et de C#, VB.Fred et autres) est la suivante:
try{
code risqué}
catch (typeErreur = spécifique){
récupération de cette erreur}
catch (typeErreur = générale){
récupération de l'erreur plus lourde}
finally{
allez, on essuie tout ou s'il n'y a pas eu d'erreur, on nettoie quand même}
Je vais essayer de structurer une manipulation d'erreur approchant ce modèle.
Un Exemple à ne pas suivre :
Ce code fictif ne montre que la manipulation déficiente parce que suivre ce code est difficile et cette difficulté n'est pas nécessaire. Ce code oblige le programmeur à tricoter dans le code et souvent il doit deviner le pourquoi du Resume Next
On Error GoTo label_1 Open DocAbsent for Binary as #number Open DocAbsent2 for Binary as #number2 '... label_1: MsgBox "Document n'existe pas...", vbOkOnly,"Crétin" Resume next End Function
Autre mauvais exemple :
On Error Resume Next '...'ignorons ici toutes les préventions possible qu'on peut y mettre. 'Ce n'est pas encore le but. CommonDialog1.CancelError = True ShowOpen FaireManip CommonDialog1.Filename
...que se passe-t-il si l'usager cancelle ? Est-ce que "FaireManip" maniera l'erreur ?
LABEL
L'utilisation d'un label de renvoi d'erreur est à prévoir s'il y a un gros bloc de code entre le début de l'opération risquée et sa fin et s'il n'y as pas d'autre opération risquée après le label.
Si c'est le cas, la fonction est trop longue et doit être normalisée (répartie) en plusieurs fonctions distinctes.
Dans tous les cas, si la fonction appelante doit tenir compte des erreurs, faire suivre la valeur.
Public Function Appelée() as Long On Error GoTo label2 Call FonctionQuiPlante '.... Exit function label2: ' Si nécessaire quelque code de nettoyage Call RamasseToi Appelée = Err.Number End Function
Pourquoi faire suivre le numéro d'erreur ? Parce qu'en VB, toute erreur déclarée et maniée localement reste locale tout comme une variable locale. S'il y a erreur, la fonction appelante n'en saura rien à moins qu'on lui dise.
Je sais que la commande Err.Raise Err.Number pourrait être utilisée mais je répugne à l'utiliser, étant super habitué à l'autre méthode.
De nombreux programmeurs décrient cette méthode et avec raison car trop souvent, un paresseux s'en sert pour ignorer l'erreur en espérant que ses effets peuvent être ignorés plus tard.
Mais bien planifiée, elle permet un réel contrôle du cheminement des opérations.
Public Function GetFile(byval strDefaultFile as string) as string On Error Resume Next 'ligne nécessaire à cause de CancelError = True With CommonDialog1 .Flags = cdlOFNFileMustExist ' l'usager est forcé de sélectionner un document existant .Filter ="Text (*.txt)|*.txt" ' l'usager est forcé de sélectionner un doc 'txt' .FilterIndex = 1 ' ...et rien d'autre. noter l'extension est montrée à l'usager .Filename = strDefaultFile ' Facultatif mais bon enfin... .DialogTitle = "Choisi, mec !" .CancelError = True ' le coeur de l'affaire: en cas de cancel, une erreur est générée. .ShowOpen End With If Err.Number = 0 then ' il n'y a qu'une erreur possible ici GetFile CommonDialog1.FileName ' passer le nom. Sinon, GetFile "" End If End Function
'.... On Error Resume Next '... Set ExcelObject = GetObject(paramètres) ' Si Excel est déjà ouvert cette méthode le trouvera ' Sinon, deux facon de déceler l'erreur: If ExcelObject Is Nothing then ' mais cette méthode n'illustre pas l'exemple If Err.Number <>0 then Err.Clear ' sinon la seconde partie sera déjà dans l'erreur Set ExcelObject = CreateObject(paramètres) If Err.Number <>0 then ' capturer cette erreur nouvelle FunctionName = Err.Number ' avertir la fonction appelante Exit Function ' Err est locale ici donc nul besoin de faire un reset End If Err.Clear ' ici on continue
Dans ce dernier exemple, le code de la fonction doit ouvrir/créer un document binaire sans détruire un doc du même nom préexistant. Certaines conditions (qui peuvent sembler factices (mais qui nous sont arrivées avec des clients qui regardaient)) sont établies pour démontrer comment faire une cascade de manipulation.
Les codes d'erreur sont factices. Voir NOTE SUR CONSTANTES D'ERREUR plus bas.
Les erreurs ne sont pas maniées par les fonctions appelées, seulement capturées et renvoyées, comme ceci:
On Error Resume Next If Err.Number <>0 then FunctionCalled = err.Number Exit Function End If If IsMediumPresent(Path) = err_NOMEDIUM then FunctionName = err_NOMEDIUM Exit Function Else If IsMediumUnLocked(Path) = err_MEDIUMLOCKED then FunctionName = err_MEDIUMLOCKED Exit Function Else If IsMediumHasSpace(DocSize) = err_INSUFFICIENTSPACE then FunctionName = err_INSUFFICIENTSPACE Exit Function ' Si ces trois conditions sont satisfaites, le code continue après le block Endif End If Endif
Maintenant continuons avec une autre approche :
Err.Clear On Error Resume Next Select Case CreateDoc(strFilename) Case err_DOCEXIST ' demander un autre nom ou changer le nom du doc et informer l'usager Case err_DOCACCESSERROR ' le doc est ReadOnly: unlock ou informer l'usager Case err_DOCWRITEERROR ' Faut voir la cause exacte de l'erreur Case err_GENERICERROR ' alors la c'était vraiment pas prévu comme type d'erreur mais on reste dans le possible Case er_NOERROR ' mettre une continuation pour la function If WriteAllToDoc(strFilename) <> err_NOERROR Then GoTo label 'ici GoTo vaux la peine! Case Else ' ici c'est le délire...mais écrire dans un log d'erreur pourrait être une bonne idée. ' Caractères inconnus, valeurs invalide, ça peut être vraiment n'importe quoi. End Select label: ' réparation et contrôle des dommages
Par expérience, dès qu'une application dépasse une certaine masse de code, déboguer les erreurs devient très difficile à suivre si les valeurs ne sont pas nommées d'une façon descriptive.
Pour faciliter le travail, regrouper toutes les erreurs possible à mesure que vous écrivez le code, en commençant par la valeur de "pas d'erreur".
Ça donne quelque chose comme ceci.
Public Const err_NOERROR as Long = 0 Public Const err_DOCEXIST as Long = 64 ' rien n'interdit d'utiliser les codes d'erreur de VB Public Const err_DOCACCESSERROR as Long = 98
Rien n'interdit d'avoir des valeurs doublées. L'important est d'avoir un nom pour la valeur.
Pour certaines fonction pouvant retourner des combinaisons de valeurs, utiliser des valeurs Hex comme ceci :
Public Const err_FlAGNONAME as Long= &H1 Public Const err_FlAGNOPASSWORD as Long= &H2 Public Const err_FlAGNOGROUP as Long= &H4 Public Const err_FlAGNOPERMISSION as Long= &H8
...et de suite
et les combiner en utilisant l'opérateur 'Or'
Un Long peut avoir 32 valeurs distinctes.
La capture d'une erreur dans une structure If...ElseIf...Else...End If doit être regardée non seulement de façon à arrêter rapidement les opérations mais aussi à montrer qu'elles sont arrêtées rapidement.
Une condition d'arrêt devrait être au début de la structure si des conditions complexes viennent après.
Ces conditions (il peut y en avoir plusieurs imbriquées) devraient être en tête de la structure.
If IsMediumUnLocked(docName)= False then ' erreur qui bloque tout Else ' on continue ... ' D'autres conditions complexes suivent End If
Cependant si les conditions d'arrêts (même imbriquées) qui arrêtent tout ne sont pas suivies pas quoi que ce soit d'autre, il est plus simple de sauter par dessus.
If DocExist(docName) = true then If DocNotLocked(docName) then ' faire la chose.... End If End If ' rien d'autre à faire
Bonne chance et ne succombez pas à l'erreur. :-)