logo

Prototyper une fonction

Pré et post conditions

Voici une fonction en langage Python qui convertit un nombre décimale en binaire sur un nombre de bits donné en paramètre.

def dec_vers_bin(n,bits):
    binaire=""
    for a in range(bits):
        binaire=str(n%2)+binaire
        n=n//2
    return binaire

Pour utiliser cette fonction, il est nécessaire de savoir ce qu"elle fait et sous quelles conditions. C'est pour cela que nous allons rajouter des commentaires et conditions pour le bon usage de cette fonction.

Commentaires

Il est possible de mettre un commentaire, dans le corps de la fonction, pour donner des informations complémentaires sur la fonction.

Voir le code :

def dec_vers_bin(n,bits):
    '''convertit le nombre n en binaire sur un nombre de bits'''
    binaire=""
    for a in range(bits):
        binaire=str(n%2)+binaire
        n=n//2
    return binaire

Tester ce code une console Python.

Pré et post conditions

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.

Dans notre exemple, on peut définir trois préconditions:

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 dec_vers_bin :

def dec_vers_bin(n,bits):
    '''convertit le nombre n en binaire sur un nombre de bits'''
    assert type(n)==int and n>=0, 'n doit être un entier natuel'
    assert type(bits)==int and bits>=0, 'bits doit être un entier natuel'
    assert bits!=0 and n<2**(bits), 'le nombre de bits est insuffisant'
    binaire=""
    for a in range(bits):
        binaire=str(n%2)+binaire
        n=n//2
    return binaire

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 :

Par exemple, on aura :

>>> dec_vers_bin(128,7)
Traceback (most recent call last):
  File "<pyshell#0>", line 1, in <module>
    dec_vers_bin(128,7)
  File "C:\Users\prof\Desktop\python\code.py", line 5, in dec_vers_bin
    assert n<2**(bits), 'le nombre de bits est insuffisant'
AssertionError: le nombre de bits est insuffisant

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.

De même on peut insérer à la fin de la fonction (juste avant le return des post-conditions si nécessaire. Dans notre exemple nous allons écrire deux post-conditions qui pourraient aussi servir de test.

Voici le code en python :
def dec_vers_bin(n,bits):
    '''convertit le nombre n en binaire sur un nombre de bits'''
    #pré-conditions
    assert type(n)==int and n>=0, 'n doit être un entier natuel'
    assert type(bits)==int and bits>=0, 'bits doit être un entier natuel'
    assert bits!=0 and n<2**(bits), 'le nombre de bits est insuffisant'
    binaire=""
    for a in range(bits):
        binaire=str(n%2)+binaire
        n=n//2
    #post-conditions
    assert max(binaire)=='0' or max(binaire)=='1',"pas un nb binaire"
    assert len(binaire)==bits,"le nombre binaire n'a pas le nombre de bits voulu"
    return binaire

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.