UKOnline

Assertion

Lorsqu'on définit une nouvelle fonction, et qu'on la spécifie, il faut minimiser le nombre de préconditions si on veut la rendre robuste, c'est-à-dire résistante à des mauvaises utilisations. Par contre, si on écrit une fonction à usage interne, c'est moins critique, surtout si le nombre de personnes qui vont l'utiliser n'est pas trop élevé et qu'elles sont de confiance. Dans ce dernier cas, le code de la fonction sera plus simple.

Instruction assert

On peut vouloir vérifier que des conditions qui sont sensées être satisfaites le sont effectivement, à l'aide du mécanisme d'assertion proposé par Python. Voyons comment l'utiliser pour vérifier les préconditions de la fonction percentage :

Trois instructions assert ont été utilisées pour vérifier les préconditions. Une telle instruction se compose d'une condition (une expression booléenne) éventuellement suivie d'une virgule et d'une phrase en langue naturelle, sous forme d'une chaine de caractères. L'instruction assert teste si sa condition est satisfaite. Si c'est le cas, elle ne fait rien et sinon elle arrête immédiatement l'exécution du programme en affichant éventuellement la phrase qui lui est associée.

Dans le programme d'exemple suivant, le premier appel s'exécutera sans erreur tandis que le second provoquera une erreur d'exécution due à une assertion pas satisfaite :

75.0 %
Traceback (most recent call last):
  File "program.py", line 8, in <module>
    print(percentage(22, 20), '%')
  File "program.py", line 4, in percentage
    assert score <= total, 'score doit être inférieur à total'
AssertionError: score doit être inférieur à total

Le mécanisme d'assertion est là pour empêcher des erreurs qui ne devraient pas se produire, en arrêtant prématurément le programme, avant d'exécuter le code qui aurait produit une erreur. Si une telle erreur survient, c'est que le programme doit être modifié pour qu'elle n'arrive plus. Dans cet exemple, on se rend immédiatement compte qu'un appel ne respectant pas les préconditions a été fait, et qu'il faut donc le changer. C'était peut-être 2/20 au lieu de 22/20...

Enfin, il faut savoir que le mécanisme d'assertion est une aide au développeur, et ne doit en aucun cas faire partie du code fonctionnel d'un programme. En supprimant toutes les instructions assert, le programme doit continuer à fonctionner normalement.

Programmation défensive

Ce mode de programmation, qui exploite les assertions pour vérifier les préconditions, est appelé programmation défensive. Dans ce type de programmation, on suppose que les fonctions sont appelées comme il faut, dans le respect de leurs préconditions. On prévoit néanmoins un garde-fou avec des instructions assert pour vérifier que les préconditions sont effectivement bien satisfaites.

En pratique, on va utiliser ce type de programmation pour du code sur lequel on a le contrôle. Par exemple, lorsqu'on écrit un module, on peut programmer défensivement pour les fonctions qui ne sont destinées qu'à être appelées au sein de ce dernier. Lorsqu'on écrit des fonctions destinées à être utilisées par d'autres personnes, on va plutôt faire une gestion active des erreurs, notamment avec l'instruction if-else et en prévoyant des valeurs de retour spéciales.

Dans les deux cas, on doit spécifier les fonctions que l'on définit. En programmation défensive, on peut se permettre d'avoir des préconditions et faire l'hypothèse qu'elles seront toujours respectées. Par contre, lorsque les fonctions sont destinées à être utilisées par d'autres, on va limiter au maximum le nombre de préconditions.

Reprenons l'exemple du test de la sous-chaine présenté au chapitre 6. On va le réécrire sans utiliser le slicing ni l'égalité de listes, mais en faisant tout avec des opérations de base. Voici le code de la fonction issubsequence, avec sa documentation :

On peut faire plusieurs observations sur la définition de cette fonction qui est, pour rappel, destinée à être appelée par d'autres programmeurs :

  • On a limité drastiquement le nombre de préconditions, les ramenant même au nombre de zéro.
  • On a pratiqué la gestion active des erreurs, en vérifiant que les paramètres reçus par la fonction sont corrects, et en renvoyant False le cas échéant.
  • On a fait appel à une fonction _issubsequenceat au sein de la boucle, qui permet de tester si subseq est une sous-chaine de seq en commençant à son indice i.

Voyons maintenant le code de cette dernière fonction appelée par issubsequence. Comme elle n'est pas destinée à être utilisée en dehors du module, mais uniquement par les autres fonctions du même module, on va pouvoir pratiquer la programmation défensive :

On peut faire plusieurs observations sur la définition de cette fonction qui est, pour rappel, destinée à n'être appelée que depuis les autres fonctions du même module, en l'occurrence par issubsequence :

  • On a ajouté _ au début du nom de la fonction, suivant la convention Python, signalant ainsi qu'elle est privée au module.
  • On n'a pas hésité à définir plusieurs préconditions qui décrivent les paramètres valides.
  • On a pratiqué la programmation défensive, en vérifiant les préconditions à l'aide d'assertions.