Blog 3 min de lecture

Programmation défensive avec les nouvelles normes C++

Share this article
Programmation défensive avec les nouvelles normes C++

La programmation défensive est une forme de conception défensive destinée à garantir le fonctionnement continu d'un logiciel face à des circonstances imprévues. Les pratiques de programmation défensive sont souvent utilisées là où une haute disponibilité, la sûreté ou la sécurité sont nécessaires.

La programmation défensive est une approche visant à améliorer le logiciel et le code source en termes de :

  • Qualité générale – réduire le nombre de bugs et de problèmes logiciels.
  • Compréhensibilité du code source – le code source devrait être lisible et compréhensible afin de passer un audit de code.
  • Comportement prévisible du logiciel malgré des entrées inattendues ou des actions imprévues de l'utilisateur.

La manière habituelle d'adopter une approche de programmation défensive en C++ consiste à utiliser le mécanisme assert :

    void test( int *p ) {
        assert( p != 0 );
        if (p == 0)
            return;
        // use p.
    }

Une assertion est du code utilisé pendant le développement — généralement une routine ou une macro — qui permet à un programme de se vérifier lui-même pendant son exécution. Lorsqu'une assertion est vraie, cela signifie que tout fonctionne comme prévu. Lorsqu'elle est fausse, cela signifie qu'elle a détecté une erreur inattendue dans le code.

Les assertions sont particulièrement utiles dans les grands programmes complexes et dans les programmes à haute fiabilité. Elles permettent aux programmeurs de débusquer plus rapidement les hypothèses d'interface incohérentes, les erreurs qui s'insinuent lorsque le code est modifié, etc.

Une assertion prend généralement deux arguments : une expression booléenne qui décrit l'hypothèse censée être vraie et un message à afficher si elle ne l'est pas.

Quels mécanismes les nouvelles normes C++ offrent-elles pour la programmation défensive ?

C++11 et static_assert

C++11 introduit une nouvelle façon de tester des assertions à la compilation grâce au nouveau mot-clé static_assert. Cette fonctionnalité est très utile pour ajouter des conditions aux paramètres de templates, comme le montre cette classe template issue du code source de Folly :

c3

C++17 et l'attribut [[nodiscard]]

Les fonctions déclarées avec [[nodiscard]] ne devraient pas voir leur valeur de retour ignorée par l'appelant. Cela peut être utile si vous voulez vous assurer que les appelants vérifient une valeur de retour : vous pouvez faire respecter le contrat de code d'une fonction afin que l'appelant ne passe pas à côté de la valeur renvoyée.

Par exemple, si la valeur de retour de la fonction do_something n'est pas utilisée, le compilateur émettra un avertissement.

[[nodiscard]] error do_something (thing&);


do_something(my_thing); // Warning: ignored return value

C++20 et les contrats

C'est une amélioration majeure pour la programmation défensive dans les nouvelles normes : elle fournira de nombreuses facilités et fonctionnalités pour la conception par contrat.

Vous pouvez découvrir la proposition ici.

Examinons d'abord la terminologie utilisée dans la proposition de fonctionnalité des contrats :

1. A précondition is a predicate that should hold upon entry into a function. It expresses a function's expectation on its arguments and/or the state of objects that may be used by the function. Preconditions are expressed by expectsattributes (7.6.10).

2. A postcondition is a predicate that should hold upon exit from a function. It expresses the conditions that a function should ensure for the return value and/or the state of objects that may be used by the function. Postconditions are expressed by ensures attributes (7.6.11).

3. An assertion is a predicate that should hold at its point in a function body. It expresses the conditions, on objects that accessible at its point in a body, that must be satisfied. Assertions are expressed by assert attributes (7.6.12).

4. Preconditions, postoconditions, and assertions are collectively called contrats. A contract shall have no observable effect in a correct program (a program where all contracts would be satisified, if they were evaluated).

5. Contract attributes are followed by a expression-conditionnelle, which is a potentially evaluated expression (3.2). 

Et voici un exemple d'utilisation de cette nouvelle fonctionnalité :


void push(int x, queue & q) [[expects: !q.full()]] [[ensures: !q.empty()]] { //... [[assert: q.is_valid()]]; //... }

La norme C++20 apportera les fonctionnalités qui manquaient aux praticiens de la programmation défensive, éliminant le besoin de recourir à une bibliothèque ou un framework externe pour la conception par contrat.

Share this article