Licence CC BY-NC

Avant-propos

Dernière mise à jour :

Nous avons découvert de très nombreuses notions depuis le début de ce tutoriel, et avec elles nous avons touché du doigt certains aspects importants du développement informatique. Il est temps de revenir sur ce que nous avons fait et de l’analyser avec un peu plus de recul. Si nous insistons autant sur ces aspects, c’est parce qu’être un bon développeur nécessite plus que d’écrire du C++.

Ce que nous avons appris

Jusqu’à présent, nous avons abordé plusieurs aspects de la programmation en C++, que ce soit les notions de base comme les conditions, les boucles, ou encore des notions plus avancées comme l’utilisation d’outils fournis par la bibliothèque standard, ainsi que la création de fonctions et de structures de données personnalisées. Vous avez ainsi déjà acquis un arsenal de programmation assez complet pour faire des programmes relativement complexes. Le T.P de la partie précédente est là pour le prouver.

Cependant, nous avons vu dans ces deux parties bien plus que de simples notions de C++. En effet, nous avons touché du doigt certains aspects essentiels du développement. Plutôt que de simplement apprendre à programmer en C++, nous avons découvert une certaine manière de réfléchir qui traduit les qualités d’un bon développeur : le désir d’avoir toujours un code de qualité, à travers un travail de conception poussé et des bonnes pratiques. Citons, parmi celles-ci, les tests unitaires pour vérifier que notre code fait ce qu’on lui demande, les contrats pour définir clairement ce que les différents intervenants attendent comme entrées et sorties et les exceptions pour remonter des problèmes à l’utilisateur.

Ce sont ces qualités qui font qu’un développeur sera non seulement capable d’utiliser son langage, mais surtout de mener ses projets à bien. Pour être un bon marin, il ne suffit pas de savoir utiliser la voile et le gouvernail, encore faut-il savoir naviguer, s’orienter dans la mer. De même, il ne suffit pas pour un développeur de maîtriser ses outils pour mener sa barque à bon port.

Je pense que vous avez commencé à vous rendre compte de ces aspects-là, notamment grâce à la partie II qui insiste fortement sur l’amélioration de la qualité du code. Et c’est l’une des choses les plus importantes à retenir des deux premières parties. Dans cet interlude, nous allons justement découvrir de nouvelles notions qui s’inscrivent dans cette optique, à savoir faire de vous de bons développeurs et pas simplement de bons programmeurs.

Différents paradigmes

Nous avons souvent dit que tout problème de développement a de nombreuses solutions, l’essentiel étant que les choix que l’on fait soient guidés par la volonté de produire un code propre et robuste à travers une bonne conception.

Eh bien cette diversité fait que l’on classe les manières de programmer en différentes catégories, appelées paradigmes. Le C++ étant un langage multi-paradigmes permettant de programmer de manière très libre, il nous laisse choisir parmi plusieurs de ces manières de programmer. Nous en avons déjà croisé quelques-unes.

Le paradigme impératif

Le paradigme impératif est le principal paradigme que l’on a utilisé. C’est aussi celui de beaucoup d’autres langages de programmation, dont notamment les premiers inventés. Dans cette approche, le programme est une suite d’instructions qui s’exécutent linéairement et le font avancer. Ce programme inclut les notions de conditions, boucles, fonctions, etc. Une grande partie des notions que l’on a abordées s’inscrit, au moins partiellement, dans ce paradigme.

Voici un exemple d’un code écrit typiquement avec le paradigme impératif, qui met chaque élément d’un tableau au carré.

#include <iostream>
#include <vector>

int main()
{
    std::vector<int> nombres { -8, 7, 48, 366, 1, 4, 3 };
    for (int i = 0; i < nombres.size(); ++i)
    {
        nombres[i] *= nombres[i];
    }

    for (auto nombre : nombres)
    {
        std::cout << nombre << std::endl;
    }

    return 0;
}
64
49
2304
133956
1
16
9

Le paradigme fonctionnel

Le paradigme fonctionnel est aussi en partie couvert par le langage C++. C’est un paradigme dans lequel, comme son nom l’indique, les fonctions jouent un rôle prépondérant. D’ailleurs, dans les langages dit « fonctionnellement purs », la modification de variable peut ne pas exister et tout est réalisé avec des calculs sur résultats de fonctions ! Nous avons principalement vu ce paradigme à travers les lambdas, qui sont un concept venant tout droit des langages fonctionnels. De manière plus générale, le fait de pouvoir manipuler les fonctions comme des variables vient du paradigme fonctionnel.

Le code suivant est une réécriture de l’exemple précédent, dans un style plus fonctionnel.

#include <algorithm>
#include <iostream>
#include <vector>

int main()
{
    std::vector<int> nombres { -8, 7, 48, 366, 1, 4, 3 };
    std::for_each(std::begin(nombres), std::end(nombres), [](int & nombre) -> void
    {
        nombre *= nombre;
    });

    for (auto nombre : nombres)
    {
        std::cout << nombre << std::endl;
    }

    return 0;
}
64
49
2304
133956
1
16
9

Le paradigme générique

Le paradigme générique consiste principalement en l’idée que parfois, on se fiche du type réel des données que l’on manipule. Ce qui compte, c’est la manière dont elles peuvent être utilisées. Nous l’avons principalement appréhendé sous deux formes.

  • Les algorithmes standards qui peuvent s’appliquer à tout un tas de collections différentes.
  • Les fonctions templates qui permettent d’écrire du code fonctionnant avec plusieurs types, du moment qu’on peut les manipuler de cette manière.

En reprenant les exemples précédents, utilisons le paradigme générique. Voici un résultat qu’on peut obtenir.

#include <algorithm>
#include <iostream>
#include <list>
#include <vector>

template <typename Collection>
void au_carre(Collection & collection)
{
    std::for_each(std::begin(collection), std::end(collection), [](auto & nombre) -> void
    {
        nombre *= nombre;
    });
}

template <typename Collection>
void affichage(Collection const & collection)
{
    for (auto element : collection)
    {
        std::cout << element << std::endl;
    }
}

int main()
{
    std::vector<int> nombres { -8, 7, 48, 366, 1, 4, 3 };
    au_carre(nombres);
    affichage(nombres);

    std::cout << std::endl;

    std::list<int> autres { 10, 30, 60 };
    au_carre(autres);
    affichage(autres);

    return 0;
}
64
49
2304
133956
1
16
9

100
900
3600

La programmation par contrat

Bien que ce ne soit pas aussi poussé que dans d’autres langages comme Eiffel ou le D, en C++, il est possible de faire de la programmation par contrat, comme nous l’avons vu dans le chapitre sur la gestion des erreurs. Comme nous avons pu le constater, les principes de la programmation par contrat se combinent très bien avec les autres paradigmes, donc n’hésitez pas à en user et abuser. :)

C++20 et PpC

La prochaine version de C++ (C++20) devrait inclure de nouveaux outils pour faire de la programmation par contrat en C++. Cela permettra de l’utiliser de manière encore plus poussée.


Maintenant que l’on a plus de recul, on voit que le C++, contrairement à d’autres langages plus restrictifs, met à notre disposition tout un panel d’outils très variés pour écrire du code de qualité. Nous pouvons prendre un peu de chaque paradigme et se servir de ses points forts. Dans la suite du cours, nous introduirons un nouveau paradigme, lui aussi très important en C++ et qu’on appelle la programmation orientée objet.

Remarque au passage

Ce n’est pas une critique envers d’autres langages que de dire qu’ils sont moins permissifs. C’est parfois un avantage d’utiliser un langage qui fait des choix forts. Simplement, l’un des attraits du C++ est cette permissivité.

Mais c’est aussi l’un de ses plus grands dangers. Comme on peut faire beaucoup de choses, on peut très facilement faire n’importe quoi ! C’est pourquoi il faut être particulièrement concentré sur le fait de produire de code propre et robuste. D’où le fait que nous insistions régulièrement dessus.

Brancher le cerveau avant tout

Nous l’avons compris, le C++ nous offre de nombreux outils, ainsi que la liberté d’utiliser et mélanger plusieurs paradigmes. Mais tout cela n’est qu’outils et instruments. Il faut les assembler intelligemment et harmonieusement pour produire de bons programmes. Pour ça, il faut, avant de se ruer sur le clavier et de martyriser énergiquement ses pauvres touches, réfléchir et concevoir.

On prend habituellement un stylo et un papier1, puis on se pose des questions sur le programme sur lequel on travaille. On réfléchit aux fonctionnalités du programme, ce qu’il rendra comme service, ce qu’il ne fera pas, ce qu’il est capable de gérer, etc. On se demande quels sont les cas d’erreurs, les entrées invalides, ce qui peut potentiellement poser problème, etc.

Les contrats

Nous avons déjà commencé à faire ça, notamment avec les contrats et la réflexion sur nos entrées et sorties.

On découpe aussi de grosses fonctionnalités en plus petites afin qu’elles soient plus facilement abordables. Un logiciel de traitement d’images peut ainsi être découpé en sous-tâches comme l’ouverture d’un fichier PNG, la modification d’un unique pixel, la sauvegarde du PNG modifié, etc.

C’est là que le fait de noter ses idées se révèle utile. On peut comparer plusieurs solutions à un même problème, on peut visualiser plus facilement à quoi ressemblera le programme final, on peut écrire du pseudo-code pour s’aider à mieux comprendre un algorithme complexe, etc. Et puis, personne n’ayant une mémoire infaillible, noter ses idées aide à s’en rappeler. ;)

Cependant, concevoir, anticiper, tout cela ne signifie pas que le programme est figé et qu’il n’évoluera pas d’un pouce. Au contraire, réfléchir avant de coder permet de mieux prévoir des aspects qui pourront être amenés à changer. Cela rend le programme plus évolutif.

Planification ou souplesse ?

C’est un exercice délicat que de trouver l’équilibre entre le néant et trop de spécifications, entre une trop grande rigidité et une absence de réflexion et de conception. Ne vous inquiétez pas. Comme beaucoup de choses, tout vient en pratiquant. L’étude de cas vous donnera un exemple concret.


  1. Enfin, un document numérique ça marche aussi. Question de goût.

Savoir se débrouiller

Être un bon développeur, c’est être autonome et se débrouiller. Cela ne veut pas dire que les développeurs ne se parlent jamais entre eux, vivant reclus les uns des autres. Au contraire, l’existence même de ce tutoriel et de la plateforme Zeste de Savoir sont des preuves que l’échange est important.

En fait, ce qu’il faut comprendre, c’est qu'il ne faut pas s’attendre à ce que tout arrive cuit dans le bec. Un bon développeur ne compte pas sur les autres pour faire son travail. Il n’attend pas de réponse toute faite. Quand il est bloqué, il réfléchit d’abord et demande ensuite.

Si vous êtes vraiment bloqués sur un problème, bien entendu que vous pouvez demander un coup de pouce, notamment sur Zeste de Savoir. Mais quand cela arrivera, montrez ce que vous avez tenté, les erreurs qui vous bloquent, si vous avez déjà fait des recherches dessus, etc. Et vous verrez, vous aurez de l’aide et des réponses. ;)

À venir

Dans cette partie, nous verrons notamment une technique appelée le débugage, qui permet de voir pas-à-pas l’exécution du code pour trouver là où une erreur se cache. Avec cette compétence, vous pourrez déjà bien avancer seuls dans la recherche de la solution.

En conclusion, laissez-moi vous lister quelques qualités, en plus de l’autonomie, qui feront de vous de meilleurs développeurs.

  • Patience. Il en faut pour apprendre, pour comprendre, pour faire et corriger des erreurs, pour chercher des solutions, pour ne pas s’énerver quand ça ne marche pas.
  • Persévérance et motivation. Développer, c’est parfois rencontrer des problèmes qui nous dépassent, qui semblent trop complexes à première vue, ou bien des notions trop abstraites. Ces qualités aideront à ne pas abandonner à la première difficulté.
  • Humilité. Un bon développeur accepte de ne pas avoir réponse à tout et donc de demander de l’aide quand il n’y arrive vraiment pas. Il accepte les conseils et les corrections qu’on lui donne. Il accepte de ne pas se reposer sur ses acquis, se remet régulièrement à jour et en question. Cela lui évite les réponses méprisantes, arrogantes et hautaines qu’on voit parfois sur certains forums.

En résumé

  • Nous avons découvert plein de choses sur le langage C++ pendant ce cours, mais on a aussi mis en avant des aspects plus généraux du développement, comme l’écriture de tests unitaires, de contrats ou la réflexion à mener sur les entrées / sorties.
  • Le C++ est un langage très riche qui nous permet de programmer de manières très différentes.
  • Cette liberté est une force mais aussi un danger, c’est pourquoi une bonne conception et du code de qualité sont primordiaux.
  • Être un bon développeur demande aussi plusieurs qualités, dont la persévérance, l’autonomie et la patience.
  • La suite de cette partie nous aidera à tendre vers cet idéal en nous apprenant de nouvelles notions pour mieux développer.