par , 08/08/2018 à 10h00 (5703 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 (
):
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.
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... 
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…
[EDIT]
Du coup, suite au commentaire de Qwazerty me signalant qu'on peut en fait se passer du Found, on a le code suivant, plus concis et tout aussi fonctionnel:
1 2 3 4 5 6 7 8 9 10 11
| Function getSheetFromCodeName(CodeName As String, Optional wb As Workbook) As Object
Dim Counter As Long: Counter = 1
If wb Is Nothing Then Set wb = ActiveWorkbook
Do While Counter <= wb.Sheets.Count And getSheetFromCodeName Is Nothing
If StrComp(wb.Sheets(Counter).CodeName, CodeName, vbTextCompare) = 0 Then
Set getSheetFromCodeName = wb.Sheets(Counter)
End If
Counter = Counter + 1
Loop
End Function |
Cette fonction, générique et donc réutilisable, devrait trouver sa place dans votre module générique xlTools, dont je parle dans ce billet... 
Bon code…