Erreur sans douleurs...

ERREURS SANS DOULEURS...

Introduction

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.

MODÈLE

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 ?

COMMENT SÉLECTIONNER UNE MÉTHODE 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 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.

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 contrôle du cheminement des opérations.

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 

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'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

Style Java

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

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 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.

NOTES ÉPARSES

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. :-)

Ce document intitulé « Erreur sans douleurs... » issu de CodeS SourceS (codes-sources.commentcamarche.net) est mis à disposition sous les termes de la licence Creative Commons. Vous pouvez copier, modifier des copies de cette page, dans les conditions fixées par la licence, tant que cette note apparaît clairement.
Rejoignez-nous