Pratique de la programmation

Quelques raccourcis :

Ce qui suit couvre divers sujets en lien avec la pratique de la programmation, pour ne pas dire l'art de coder. Pour l'essentiel, ce document liste des liens sur divers sujets associés à l'acte de programmer, mais des réflexions personnelles s'ajouteront quand j'en aurai le temps. Notez que plusieurs des sujets exposés ici entraînent chez les gens des réactions épidermiques, à la limite du religieux. Restons calmes...

Certaines thématiques autrefois discutées ici ont été relocalisées dans des pages à part entière, soit :

Si la pratique de la programmation vous intéresse, je vous invite aussi à examiner la question des schémas de conception.

Idées et opinions sur la pratique de la programmation

La pratique de la programmation est une chose large et complexe; beaucoup plus complexe que ne le croient les gens qui n'y participent pas. En partie scientifique, en partie philosophique, en partie artisanale (le Craft, la Praxis), la programmation est affaire de discipline et de créativité à la fois, pour résoudre chaque fois de nouveaux problèmes et pour le faire chaque fois mieux, mais aussi de manière à ce que les solutions trouvées survivent au passage du temps.

Personnellement, je penche vers certains principes de base, mais je suis d'accord avec la plupart des principes listés – colligés, en fait – par Christopher Diggins dans http://www.artima.com/weblogs/viewpost.jsp?thread=331531 :

J'aime bien aussi les critères préconisés par http://sourceforge.net/apps/wordpress/ogsa-dai/2011/08/22/on-code-quality/ mais je ne suis pas certain qu'il soit raisonnable de les transformer en métrique quantitative. Il y a un volet esthétique à la pratique de la programmation.

Principes de design, tirés de Programming Pearls par Jon Bentley (source) :

Thématiques générales et philosophiques

Des idées et opinions émises par d'autres auteurs sur cette pratique suivent, et pas nécessairement avec lesquelles je suis d'accord; vive la variété des points de vue!

« Code is never "done" imo. Coding is not like making a statue, it's like cultivating a garden.

You try out a vegetable patch and it doesn't work out because the light wasn't right. Next year you do something else in that part of the garden.

You try, learn, adapt and grow. » – Patricia Aas (source)

« I find that programming, like many creative endeavours, involves being at times a blacksmith, at times a gardener.

Sometimes code needs to be forged, shaped, worked into compliance. Sometimes it needs time, nurturing, and the conditions to bring forth the beauty within » – Ben Deane (source)

Certains abordent aussi la programmation sur la base de la poésie :

Notez que je ne partage pas nécessairement les opinions émises dans les textes vers lesquels mènent les liens sur cette page, en particulier en ce qui a trait à des considérations d'esthétique.

La beauté et l'élégance touchent à des questions d'esthétique et de goût. Certains ont même écrit des livres (souvent fort intéressants par ailleurs) sur la beauté dans le code – je pense par exemple à Beautiful Code ou à Beautiful Architecture. À ce sujet :

Portabilité culturelle et technique

Dans cette présentation de 2016 portant sur le Universal CRT, un effort signifiant du point de vue de la portabilité et de la compatibilité, James McNellis présente la compatibilité sous trois angles, soit :

L'acte de programmer

Si vous le souhaitez, le livre Elements of Programming Style, par Brian Kernighan et P. J. Plauger en 1978, est maintenant disponible en ligne.

« The function of good software is to make the complex appear to be simple » – Grady Booch

Gérer la complexité

À propos de la complexité et de sa gestion en programmation :

À propos de « la zone »

Cette section est devenue une page à part entière : la-zone.html

« Most good programmers do programming not because they expect to get paid or get adulation by the public, but because it is fun to program » – Linus Torvalds (source)

« Each routine you read turns out to be pretty much what you expected. You can call it beautiful code when the code also makes it look like the language was made for the problem » puis « Beautiful code reserves its beauty for those familiar with the language and the problem. Careful study of beautiful code will enhance familiarity of both » – Ward Cunningham (source, source)

Plaisir de programmer

La question de l'acte de programmer comme chose plaisante et créative est au coeur de ce qui motive plusieurs programmeuses et plusieurs programmeurs (dont votre illustre serviteur). Plusieurs individus critiquent le passage de l'apprentissage (et de l'application) de la programmation à un modèle qui favorise l'apprentissage et l'utilisation d'outils (souvent de Frameworks ou de bibliothèques de classes). Considérant la difficulté élevée de l'acte de programmer, assurer le maintien du plaisir et l'entretien de la pulsion créative associés à cet acte est, à mes yeux, chose essentielle.

À propos du plaisir de programmer :

L'acte de programmer – façons de faire

Source

Revues de code (Code Reviews)

Cette section est devenue une page à part entière : revue-code.html

Manifestes et écoles de pensée

Le texte de Nikita Popov, sur le code STUPID, est quelque peu provocateur, ce qui n'est pas nécessairement une mauvaise chose, mais met aussi en relief (à mon avis), malgré lui peut-être, le fait que plusieurs pratiques (incluant des schémas de conception usités) peuvent être appliquées de manière abusive. Lisez avec discrimination et souvenez-vous qu'en pratique, bien peu de choses sont « tout noir » ou « tout blanc ».

Travail d'équipe

« Agile is a set of values and principles generalized from the practices of several similar methodologies. Agile does not define any particular practices; but it has no meaning without them. Agile is the abstract class. Practices are the implementation » – Robert C. Martin (source)

Approche Agile

Le processus unifié de développement, ou UP, et l'approche RUP, pour Rational Unified Process, préconisée par IBM :

Le passage du modèle de développement en cascade au modèle itératif de développement, selon Peter Kroll en 2004 : http://www.ibm.com/developerworks/rational/library/4243.html

À propos de l'approche agile :

À propos de la programmation extrême (eXtreme Programming) :

À propos de Scrum :

La programmation par binômes, ou Pair Programming :

Critiques :

Est-ce la fin de l'approche agile?

L'acte de programmer – gestion de carrière

L'acte de programmer – Productivité

Un programmeur est-il productif? Que signifie cette question?

À quel point est-ce pertinent de mesurer la productivité des programmeuses et des programmeurs? Comment y arriver?

Accroître sa productivité :

L'acte de programmer – technique

L'acte de programmer – qualité

Entretien du code

En pratique, la plupart des informaticiens passent une part important de leur temps à entretenir, adapter, raffiner, « refactoriser », corriger du code déjà écrit. Le soin apporté à la rédaction initiale influence la suite des choses, mais l'entretien du code est un art en soi. De saines pratiques de documentation et d'indentation permettent d'alléger ce fardeau.

http://www.monkeyuser.com/2017/todo/

Refactoriser

Refactoriser est une activité importante pour les programmeuses et les programmeurs.

Expérience et expertise

Apprentissage

Mentorat

Questions de documentation

Cette section est devenue une page à part entière : Documentation.html

Questions de nomenclature

Cette section est devenue une page à part entière : Nomenclature.html

La question des commentaires

Cette section est devenue une page à part entière : Commentaires.html

La question de l'indentation

Cette section est devenue une page à part entière : Indentation.html

Quelques standards de programmation au sens large

Cette section est devenue une page à part entière : Standards-programmation.html

Développement d'une API

Cette section est devenue une page à part entière : API.html

Bien programmer

Quelques réflexions sur la question de la bonne programmation (au sens large) :

Gestion des erreurs

Les liens et les réflexions sur ce sujet ont été groupés dans Gestion-erreurs.html

Assertions dynamiques

Les liens et les réflexions sur ce sujet ont été groupés dans Gestion-erreurs.html#assertion_dynamique

Préconditions, postconditions, invariants

Dans la documentation d'une unité de code telle qu'une fonction ou une classe, il est d'usage de porter une attention particulière aux caractéristiques suivantes :

Au sens de son interface, les préconditions d'une fonction en sont les a priori, ce que la fonction tient pout acquis, parfois même sans pouvoir les valider. C'est une forme de contrat destiné au code appelant. Par exemple, une fonction comme strlen() à droite ne peut garantir que s pointe sur une adresse valide au préalable (seul le code client peut offrir cette garantie), mais elle peut toutefois garantir que s soit non nul (ici, par une assertion dynamique).

Faire connaître ses a priori au code client est une responsabilité de chaque fonction. En étant informé, le code client devient responsable de les respecter. Concrètement, un client qui n'est pas en mesure de respecter les préconditions d'une fonction est en erreur s'il utilise celle-ci malgré tout.

Au sens de son implémentation, les préconditions d'une fonction décrivent ce à quoi l'appelé est en droit de s'attendre au moment où l'appel est initié, ce sur quoi l'appelé peut compter a priori.

//
// pre: s non nul
// pre: s pointe sur une séquence contiguëe
//      en mémoire de caractères
//
std::size_t strlen(const char *s) {
   assert(s);
   using std::size_t;
   size_t n = 0;
   for(; *s, ++s)
      ++n;
   return n;
 }

Les postconditions d'une fonction sont les garanties qu'offre cette fonction à qui s'en sert correctement, donc en respectant ses préconditions. Au sens de l'interface, elles décrivent ce à quoi l'appelant peut s'attendre comme effet de l'exécution de la fonction. Au sens de l'implémentation, elles décrivent ce à quoi l'appelé s'engage avant de retourner le contrôle à l'appelant.

Par exemple, une fonction comme sqrt() à droite exige que son paramètre r soit strictement positif (précondition), et garantit en retour que la valeur retournée, si elle est élevée au carré, sera proche de celle de r (postcondition).

//
// pre: r > 0
// post: abs(résultat^2-r) <= ε
//
double sqrt(double r) {
   assert(r>0.0);
   // etc.
 }

Comme l'indique Scott Meyers dans Effective Modern C++ :

« It's worth noting that some library interface designers distinguish functions with wide contracts from those with narrow contracts. A function with a wide contract has no preconditions. Such a function may be called regardless of the state of the program, and it imposes no constraints on the arguments that callers pass it. Functions with wide contracts never exhibit undefined behavior. Functions without wide contracts have narrow contracts. For such functions, if a precondition is violated, results are undefined. »

Ceci donne un aperçu d'une approche pour la documentation de fonction avec (ou sans) préconditions.

Enfin, les invariants d'une classe sont les caractéristiques qui s'avèreront pour toute instance de ce type entre deux appels de méthodes, et ce de la fin de sa construction au début de sa finalisation. Garantir le respect des invariants est la clé de voûte de l'encapsulation.

Les invariants d'un type ne sont pas garantis pendant sa construction, pas plus qu'ils ne le sont pendant sa destruction, du fait que ce sont justement des périodes de fluctuation pour ses états. De même, les invariants d'un type peuvent ne pas être respectés pendant l'exécution d'un appel de méthode, mais il est de la responsabilité dudit type de s'assurer que, suite à un tel appel, et peu importe comment l'appel se sera conclu (fin normale, retour hâtif, levée d'exception, etc.), les invariants de ce type demeureront respectés.

Un exemple typique d'invariant pour une tableau dynamique serait que sa capacité soit supérieure ou égale au nombre d'éléments qu'il contient. Il importe qu'un tel invariant s'avère à la fin de chaque constructeur, incluant le constructeur par défaut, de même que suite à l'exécution de toute méthode susceptible de modifier la structure sous-jacente du tableau.

Un invariant est aussi ce que l'on vérifie dans la condition d'une répétitive. En effet, dans les cas à droite, le corps de la répétitive (le traitement) est exécuté seulement si la condition de poursuite s'avère. Pour cette raison, avec une répétitive correctement construite, la condition de poursuite représente un invariant qui est présum¸é tenir pour la durée de l'itération courante du traitement. Cet argument explique entre autres pourquoi il est prééférable de suivre la forme classique, selon laquelle les éléments testés dans la condition de poursuite ne devraient normalement pas changer pendant le traitement de la répétitive : la condition est présumée s'avérer du début à la fin d'une itération du traitement (ceci explique aussi en partie pourquoi les boucles do...while sont moins utilisées que d'autres formes répétitives en pratique : leur invariant est validé à la fin du traitement!).

Par exemple, parcourant un conteneur, si la condition est de poursuivre tant que le conteneur n'est pas vide, alors il est raisonnable de ne pas changer cet état avant la toute fin de l'itération en cours. Si la condition est i<N, alors mieux vaut ne faire ++i qu'à la fin de l'itération car cette expression pourrait faire en sorte que l'invariant vérifié par cette condition devienne faux.

template <class T, class F>
    void appliquer_et_vider(vector<T> v, F fct) {
       while(!v.empty()) {
          fct(v.back());
          v.pop_back();
       }
    }
template <class T, class Pred>
    typename vector<T>::size_type
       compter_si(const vector<T> &v, Pred pred) {
       using size_type = typename
          vector<T>::size_type;
       size_type n = 0;
       for(size_type i = 0; i < v.size(); ++i)
          if (pred(v[i]))
             ++n;
       return n;
    }
template <class T, class Pred>
    typename vector<T>::size_type
       compter_si(const vector<T> &v, Pred pred) {
       using size_type = typename
          vector<T>::size_type;
       size_type n = 0, i = 0;
       while (i < v.size()) {
          if (pred(v[i]))
             ++n;
          ++i;
       }
       return n;
    }

Les préconditions, postconditions et invariants peuvent être documenté(e)s à travers des commentaires, des assertions (statiques ou dynamiques), ou des validations suivies de potentielles levées d'exceptions. Dans une hiérarchie de classes, l'idiome NVI peut faciliter la validation des préconditions et des postconditions à partir d'une même classe racine. La clause noexcept est une forme normée de postcondition.

Quelques textes pertinents sur le sujet :

Programmation par contrats

La programmation par contrats met de l'avant la pratique selon laquelle il importe de documenter, idéalement à même le code, les préconditions et les postconditions de nos fonctions, de même parfois que les effets secondaires qu'entraîne leur exécution. Certains langages, en particulier Eiffel, offrent un support direct de ce concept (voir http://www.eiffel.com/developers/design_by_contract.html pour des détails).

Mal programmer

Vous apprécierez peut être aussi mon petit Musée des horreurs...

Savoir comment (ne pas) mal programmer est presque aussi utile que savoir comment bien le faire. À ce sujet...

Bourdes et bêtises

Il est parfois sage de réinventer la roue. Cependant, il arrive que l'on s'empêtre alors en perdant de vue l'objectif réel de notre démarche. Un cas vécu, relaté en 2013 : http://www.drmaciver.com/2013/03/what-the-fuck-are-we-doing/

À propos du code spaghetti

Le code spaghetti est ce code entremêlé qui est si difficile à lire et à entretenir. Quelques liens sur le sujet :

Débogage

Cette section est devenue une page à part entière : Debogage.html


Valid XHTML 1.0 Transitional

CSS Valide !