Voir le flux RSS

Pierre Fauconnier

VBA: Exit Sub, Exit For, Exit Do, ... (Suite et peut-être pas fin)

Noter ce billet
par , 08/08/2018 à 10h00 (229 Affichages)
Dans ce précédent billet, je vous parlais de mon aversion pour les Exit de toute sorte...

J'y développais des considérations essentiellement techniques concernant les problèmes rencontrés par des Exit qui permettent de sortir improprement d'une boucle ou d'une procédure. J'y parlais notamment du problème de Application.EnableEvents = False qui risquait de ne pas être rétabli suite à un Exit Sub.

L'enregistreur de macros (que je déteste même s'il me rend des services) pond d'ailleurs du code impropre puisqu'il place la gestion d'erreur après un Exit Sub, un peu comme dans l'exemple suivant ( ):
Code VB : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
Private Sub Worksheet_Change(ByVal Target As Range)
  On Error GoTo ErrHandler
  ...
  If Target.Address = "$A$1" Then
    Application.EnableEvents = False
    ...
    Range("a2") = Range("a2") + Range("a1")
    ...
    Application.EnableEvents = True
  End If
  Exit Sub
 
EndHandler:
  ... ' <== Gestion de l'erreur
End Sub

Mais tout cela, je l'ai développé dans le billet cité plus haut. Aujourd'hui, je veux vous entretenir de la philosophie des boucles (de leur finalité, si vous préférez ou si le mot philosophie vous fait peur ), car une boucle n'est pas l'autre, et un For i = 1 to 10 et un For Each Item In Items n'ont pas la même finalité qu'un Do While. Et c'est cette différence de finalité qui exclut l'utilisation de Exit dans une boucle.

En français, For i = 1 To 20 veut dire Pour i qui prend les valeurs de 1 à 20. Si je lis cela dans un code, je m'attends donc à ce que la boucle initiée par le For i... se réalise 20 fois. Pas une, pas deux, pas 17 ou 18. ...20 fois. Si je place un Exit For dans la boucle, suite au fait qu'une condition est remplie, ma boucle va-t-elle être effectuée 20 fois? Non, elle va peut-être en sortir après x itérations, le x n'étant pas connu au départ et dépendant d'une condition exprimée dans la boucle et inconnue à l'entrée dans la boucle. Autrement dit, le code me ment . Il me dit: je vais faire le truc 20 fois, mais en fait, il le fait x fois, le x n'étant connu qu'après coup…

En français, For Each Item In Items signifie Pour chaque élément de la collection d'éléments. Lorsque je lis cette ligne de code, je m'attends à ce que la boucle traite chaque élément de la liste, quitte à simplement tester qu'il ne faut rien faire avec, mais je m'attends à ce que chaque élément (for Each) soit à tout le moins examiné par la boucle. Par exemple, dans le code For Each ws in WorskSheets, je m'attends à ce que chaque feuille de travail du classeur actif soit examinée par la boucle. Peut-être une condition fera-t-elle que la boucle ne fera rien avec une des feuilles passées, mais je sais que la boucle passera sur chaque feuille. Si un Exit For provoque une sortie prématurée, je ne peux plus considérer que chaque feuille sera examinée au sein de la boucle. Le code m'a encore une fois menti


En français, Do While signifie Faire tant que et Do Until signifie Faire jusqu'à ce que. On entend bien que ces codes appellent une condition. Faire tant qu'une condition est remplie ou Faire jusqu'à ce qu'une condition soit remplie. La lecture de la simple ligne Do While ou Do Until me renseigne la condition qui détermine que la boucle s'arrête. Je sais que c'est une condition qui pilote la boucle, et qui plus est, je connais cette condition dès la lecture de cette ligne, sans devoir plonger dans le code pour la trouver. Dans le code, je trouverai ce qui permettra à la condition d'exister ou de ne plus exister, mais la condition est exprimée au départ de la boucle. Donc, là non plus, je n'ai pas besoin d'un Exit dans la boucle, qui va venir court-circuiter la condition exprimée sur la ligne Do While/Until (Si vous me parlez de Do While True avec un Exit au sein de la boucle, je m'en vais… ou je vous étripe ).

Le raisonnement similaire sera tenu pour la boucle Do … Loop While/Until, bien entendu, la différence entre Do While/Until et Do… Loop While/Until étant simplement que dans Do… Loop While/Until, la boucle est exécutée au moins une fois.

Exemple (celui qui m'avait fait écrire mon premier billet relatif à Exit).
On cherche une feuille dans un classeur Excel selon son CodeName. VBA et le modèle Object Excel ne nous donne pas de solution toute faite. Il faut donc passer par une fonction perso.

Méthode For Each avec Exit. For Each me laisse croire que je vais boucler sur toutes les feuilles, mais peut-être ne vais-je passer que sur la première. Je dois plonger dans le corps de la boucle pour découvrir qu'une condition va peut-être me faire sortir prématurément.
Code VB : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7
8
9
10
11
Function getSheetFromCodeName1(CodeName As String, Optional wb As Workbook) As Object
  Dim sh As Object
 
  If wb Is Nothing Then Set wb = ActiveWorkbook
  For Each sh In wb.Sheets
    If StrComp(sh.CodeName, CodeName, vbTextCompare) = 0 Then
      Set getSheetFromCodeName = sh
      Exit For
    End If
  Next
End Function


Méthode Do While sans Exit. Dès la lecture de la ligne Do While, je sais qu'une condition va probablement me faire sortir de la boucle sans être passée sur chaque feuille, et je sais qu'une condition est présente à l'intérieur de la boucle pour modifier la valeur de Found. Qui plus est, le nom de la variable Found m'indique que la boucle sert à trouver une feuille particulière . Mais horreur, ça prend 14 lignes au lieu de 11...
Code VB : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7
8
9
10
11
12
13
14
Function getSheetFromCodeName(CodeName As String, Optional wb As Workbook) As Object
  Dim Counter As Long
  Dim Found As Boolean
 
  If wb Is Nothing Then Set wb = ActiveWorkbook
  Counter = 1
  Do While Counter <= wb.Sheets.Count And Not Found
    If StrComp(wb.Sheets(Counter).CodeName, CodeName, vbTextCompare) = 0 Then
      Set getSheetFromCodeName = wb.Sheets(Counter)
      Found = True
    End If
    Counter = Counter + 1
  Loop
End Function

Ok. Cela ne révolutionne pas la manière de coder, mais perso, je pense qu'utiliser les boucles For... ou Do... selon leur finalité clarifie les intentions du programmeur et contribue, comme d'autres choses, à produire du code de qualité, propre, maintenable, évolutif, lisible et compréhensible…

Bon code…

Envoyer le billet « VBA: Exit Sub, Exit For, Exit Do, ... (Suite et peut-être pas fin) » dans le blog Viadeo Envoyer le billet « VBA: Exit Sub, Exit For, Exit Do, ... (Suite et peut-être pas fin) » dans le blog Twitter Envoyer le billet « VBA: Exit Sub, Exit For, Exit Do, ... (Suite et peut-être pas fin) » dans le blog Google Envoyer le billet « VBA: Exit Sub, Exit For, Exit Do, ... (Suite et peut-être pas fin) » dans le blog Facebook Envoyer le billet « VBA: Exit Sub, Exit For, Exit Do, ... (Suite et peut-être pas fin) » dans le blog Digg Envoyer le billet « VBA: Exit Sub, Exit For, Exit Do, ... (Suite et peut-être pas fin) » dans le blog Delicious Envoyer le billet « VBA: Exit Sub, Exit For, Exit Do, ... (Suite et peut-être pas fin) » dans le blog MySpace Envoyer le billet « VBA: Exit Sub, Exit For, Exit Do, ... (Suite et peut-être pas fin) » dans le blog Yahoo

Mis à jour 08/08/2018 à 10h48 par Pierre Fauconnier

Catégories
Programmation , VBA , MS Office , Humeur / Humour

Commentaires

  1. Avatar de Qwazerty
    • |
    • permalink
    Salut

    Pour ce qui est des exit For, j'avoue en être un utilisateur, les boucle do...While/Until me semblent moins naturelles (juste une question d'habitude, rien de tangible) .
    J'ai tendance à croire que la boucle For a été créée pour simplifier les écritures et le exit for pour palier l’absence de conditions de sortie autre que l'arrivée à la borne supérieure. Le For serait donc dans mon esprit un Do...while/until interfacé si je puis dire.
    Philosophiquement, je peux concevoir que la promesse non tenue de la condition de départs puisse être dérangeante, dans les faits moins. Quand serait-il alors des transtypages implicites qui sont monnaie courante en VB. Combien de CDate, CLng, CInt, CBool, CStr,... sont présents dans les codes fournis?
    Et pourtant personne ne s'en offusque, je gratte(ais) un peu sur Delphi et là, pas le choix, un entier c'est un entier et un string c'est un string...
    Je dirais même que les transtypage implicites posent parfois des problèmes, ce qui ne me semble pas être le cas des Exit For

    Dans le dernier cas, on pourrait même se passer de Found en regardant si getSheetFromCodeName est différent de Nothing.

    ++
    Qwaz
    Mis à jour 09/08/2018 à 20h59 par Qwazerty
  2. Avatar de Pierre Fauconnier
    • |
    • permalink
    Salut Qwaz

    Comme je l'ai dit dans mon billet, on fait de la philosophie ici, et donc, chacun aura la sienne.

    Ta vision du For... comme Do While interfacé est intéressante. C'est une façon d'aborder les différences entre les deux méthodes qui a sa raison d'être. Et comme il s'agit ici plus de style de programmation que de technique, je me garderai d'avoir un avis péremptoire.

    Pour moi, toujours sur un plan philosophique, je mets l'Exit au même rang que les transtypages dont tu parles, pour la raison qu'ils polluent à mon avis le code par des facilités qui n'ont pas lieu d'être, mais qui sont l'apanage de VBA (et de quelques autres langages, à ma connaissance). Mais ton observation est juste, le transtypage implicite est souvent proposé, y compris par moi, sans causer des discussions comme celles-ci, alors que, comme tu le soulignes, ils exposent à des dangers là où l'Exit For est somme toute fort inoffensif.

    je suis en fait assez allergique aux sorties multiples de procédures/fonctions qui peuvent, eux, être assez problématiques, et j'ai étendu mon raisonnement aux sorties de boucles

    pour le remplacement de Found par un test sur le nothing