ERREURS SANS DOULEURS...
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.
MODELE
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.
EXAMPLE A 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 EXAMPLE
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
CommonDialog1.ShowOpen
FaireManip CommonDialog1.Filename
...que se passe-t-il si l'usager cancelle ? Est-ce que "FaireManip" maniera l'erreur ?
COMMENT SELECTIONNER UNE METHODE SIMPLE
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 tros 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.
Ex.:
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.
ON ERROR RESUME NEXT
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 controle du cheminement des opérations.
Ex.: CommonDialog sécurisé: méthode simple
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
Ex.: Manipulation plus complexe
....
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'example
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
Ex.: Style Java
Dans ce dernier example, 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 plus 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.
' Charactères inconnus, valeurs invalide, ça peut être vraiment n'importe quoi.
End Select
......
label:
' réparation et controle des dommages
NOTE SUR CONSTANTES D'ERREUR
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 commencant
par la valeur de "pas d'erreur".
Ca 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'mportant 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 untilisant l'opérateur 'Or'
Un Long peut avoir 32 valeurs distinctes.
NOTRES EPARSES
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.
Ex.:
If IsMediumUnLocked(docName)= False then
' erreur qui bloque tout
Else
' on continue
...
' D'autres conditions complexes suivent
End If
Cepandant si les conditions d'arrêts (même imbriquées) qui arrêtent tout ne sont pas suivies pas quoi que ce soitr d'autre, il est plus simple de sauter par dessus.
Ex.:
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. :-)