02 fév. 2007

Assertions illogiques

Les assertions sont un mécanisme puissant et permettent de contrôler les valeurs des données, de manière à effectuer une vérification dynamique de leur cohérence. Elles sont toutefois difficiles à utiliser - de par leur nature - et de fait, se retrouvent souvent être utilisées d’une mauvaise manière.

Considérez le code suivant, récupéré du projet sur lequel je travaille (et violemment modifié lors de ce processus, pour des raisons évidentes) :

void do_something(std::vector<some_type>& container)
{
  T* first_match = NULL;
  T* last_match = NULL;
  for (iterator i=container.begin() ; i != container.end(); ++i) {
     if (validate_data(*i)) {
        if (!first_match) {
          first_match = last_match = &(*i);
          continue;
        }
        assert(last_match != NULL);
        if (&(*i) < first_match) {
          first_match = &(*i);
        } else if (&(*i) > *last_match) {
          last_match = &(*i);
        }
     }
  }
  assert(first_match < last_match);
  do_something_with(first_match, last_match);
}

Ce code contient deux assertions – et aucune d’elle n’est valide.

La première assertion n’est pas valide pour la simple et bonne raison qu’elle ne peut jamais être fausse. La seule manière pour last_match d’être NULL à ce point est d’avoir first_match lui aussi égal à NULL – mais dans ce cas, on entre dans le test, et last_match est initialisé à une valeur non nulle. Cette assertion est donc complètement illogique, et ne sert à rien.

La seconde considère qu’on a toujours first_match < last_match en sortir de boucle. Si aucune donnée n’a été validée par validate_data(), ou si une seule donnée l’a été, cette assertion est fausse (car first_match == last_match). Remplacer l’assertion par assert(first_match <= last_match) n’apporte rien, car NULL <= NULL.

En fait, cette seconde assertion est invalide pour une raison simple : il ne s’agit pas d’une assertion, mais d’un contrôle des valeurs effectué afin de détecter de possibles erreurs. Si first_match (un pointeur) doit être inférieur à last_match (un autre pointeur) au sortir de la boucle, alors le fait de ne pas avoir ce résultat ne signifie pas qu’on a fait une erreur de programmation, mais que les données sont probablement mauvaises ou corrompues. En tout état de cause, il s’agit d’une erreur qui doit être détectée à tout coup, et pas seulement dans le cas ou les assertions sont actives.

Il y a des moments où il faut se poser des questions lorsqu’on manipule des assertions.

Ajouter un commentaire

Les commentaires peuvent être formatés en utilisant une syntaxe wiki simplifiée.

Fil des commentaires de ce billet