IdentifiantMot de passe
Loading...
Mot de passe oublié ?Je m'inscris ! (gratuit)
Navigation

Inscrivez-vous gratuitement
pour pouvoir participer, suivre les réponses en temps réel, voter pour les messages, poser vos propres questions et recevoir la newsletter

Débats sur le développement - Le Best Of Discussion :

Programmation : quand faut-il commenter son code ? Google s’invite dans le débat


Sujet :

Débats sur le développement - Le Best Of

  1. #101
    Expert éminent
    Avatar de Matthieu Vergne
    Homme Profil pro
    Consultant IT, chercheur IA indépendant
    Inscrit en
    Novembre 2011
    Messages
    2 264
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France

    Informations professionnelles :
    Activité : Consultant IT, chercheur IA indépendant
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Novembre 2011
    Messages : 2 264
    Points : 7 760
    Points
    7 760
    Billets dans le blog
    3
    Par défaut
    Ça faisait longtemps que j'avais pas écrit un pavé. {^_^}

    Pour ceux qui n'aiment pas mes speechs, la version courte peut se résumer en 2 points importants :

    1) Je suis du même avis que evanboissonnot (et visiblement le seul). Ce qu'il appelle commentaire public me semble être de la documentation, et le code non améliorable est le code optimisé (i.e. où il devient important d'exploiter des subtilités pour augmenter telle ou telle performance). Seul le dernier concerne donc les commentaires à mon sens, et seul ce cas justifie d'en utiliser. Et cela uniquement pour décrire pourquoi tel choix à tel endroit (i.e. quelle subtilité est exploitée) et non comment (i.e. traduction du code). Indiquer l'algo utilisé, c'est du comment, donc pareil, tout au plus ça va dans la doc (voire la version longue pour l'explication).

    2) Tout comme martopioche ci-dessus, je fais la différence entre les bonnes pratiques et la pratique : le premier est un idéal, le second un constat, l'objectif étant, à hauteur de ses propres capacités, de faire converger le second vers le premier. Il ne s'agit donc pas de dire que le second est mauvais, ça tout le monde le sait et s'en tape le coxis, mais de se mettre d'accord sur ce que contient le premier. De là, ceux qui ont le courage de s'y tenir s'y tiennent, et les autres passent outre. L'important étant que chacun assume les conséquences de ses actes.

    Pour la version longue, déroulez.

    Citation Envoyé par Michael Guilloux Voir le message
    Si vous pensez avoir besoin d'un commentaire pour expliquer ce qu'est un code, ils proposent alors de procéder d'abord à l'une des opérations suivantes :

    1. Introduire une variable explicative
    [...]
    2. Extraire une méthode
    [...]
    3. Utiliser un nom d'identificateur plus descriptif
    [...]
    4. Ajouter un contrôle dans le cas où votre code a des hypothèses
    [...]
    Perso, j'utilise souvent les méthodes 1-3 pour éviter les commentaires. La 4 en revanche pour moi n'a rien à voir : c'est obligatoire pour tout paramètre de fonction publique ne pouvant être garanti valide, de façon à générer des exceptions claires le cas échéant. Pour éviter d'en mettre trop, je minimise les cas invalides : soit en exploitant toutes les valeurs possibles (e.g. null = paramètre facultatif non fournit), soit en faisant des classes dédiées ne pouvant générer que des valeurs valides, type énumérations et leurs compositions.

    Une autre technique est d'insérer non pas un commentaire mais un TODO (oui, c'est un commentaire, mais l'EDI en gère l'affichage et facilite l'accès). Le message mentionne alors les défauts du code et si possible suggère des améliorations. L'idée part du principe que tout commentaire expliquant ce que le code fait indique par définition un code mal fait, ce qui ne devrait arriver que quand on ne prend pas le temps de concevoir correctement ledit code. Ça peut se justifier en cas d'urgence, mais ça veut dire qu'il est nécessaire d'y revenir, d'où le TODO pour indiquer qu'il y a encore quelque chose à faire ici.

    Citation Envoyé par bcag2 Voir le message
    G19 et G20 pages 318 et 319 de «Coder proprement» de Robert C.Martin (ISBN 978-2-7440-2583-9)
    … rien de nouveau
    Merci pour la référence. Ajouté à la bibliothèque. {^_^}

    Citation Envoyé par transgohan Voir le message
    Je serai mitigé pour ma part...
    J'aurai tendance à dire que mettre des commentaires partout est inutile mais à côté de cela je travaille sur du code très gros et très complexe et sans les commentaires inutiles cela devient une galère monstrueuse pour trouver rapidement ce que l'on cherche par simple méconnaissance du code concerné...
    L'architecture est la première à montrer qu'un code est mal fait : si tu ne t'y retrouves pas, c'est que la conception a été bâclée. Je dis pas que les auteurs sont des neuneus, juste que la conception n'a pas été poussée aussi loin qu'il aurait fallu. De là, soit on décide enfin de s'y mettre et on prend le taureau par les cornes, soit on dit amen et on fait avec. Après, faut assumer, faut pas se dire que les codes comme ça, on n'y peut rien.

    Et je maintiens malgré ses réponses [1][2] : soit on prend le temps nécessaire pour concevoir, soit on met ce temps sur autre chose, ça ne veut pas dire que le problème est complexe "par nature" et qu'on n'y peut rien. Ce sont des choix, il faut les assumer en disant "on met des commentaires parce qu'on n'a pas le temps de faire du refactoring" et non "on met des commentaires parce que c'est complexe". Les commentaires ne sont naturellement utiles que là où c'est optimisé, c'est à dire là où on préfère augmenter les performances au détriment de la simplicité du code, et donc de sa maintenabilité. Ce genre de choix se justifie, mais ne devrait intervenir que dans certains blocs de code où le performance se fait désirer. Si la majeure partie du code doit être optimisée, c'est soit que tu as mal choisit ton architecture ou ton algo (cf. l'anecdote de CodeurPlusPlus), soit que tu essayes de faire rentrer un Windows 7 sur une carte à puce, et donc que tu as mal calibré ton matériel pour les fonctionnalités que tu souhaites y mettre. Comme les glaces à 2 boules où tu essayes d'en forcer une 3e : s'il y en a une qui tombe, ça n'étonnera personne, et on ne rejettera pas la faute sur le cornet trop petit, mais sur celui qui aura voulu mettre 3 boules dans une contenance pour 2. Quoi qu'il en soit, ce sont des problèmes de choix, et non de nature. Donc soit on fait et on prend ses responsabilités, soit on fait pas.

    J'adresse tout de suite les arguments "vieux code" : on parle là de pratique, ça ne sert à rien de parler de vieux code. Est-ce qu'on irait dire que Newton n'a pas fait une bonne théorie avec sa mécanique classique parce qu'il parle de la gravité comme d'une force plutôt que comme d'une déformation de l'espace-temps, comme Einstein et sa relativité ? Les recommandations d'aujourd'hui s'appliquent aux pratiques d'aujourd'hui avec les outils d'aujourd'hui. Si on suggère de donner des noms explicites aux fonctions, c'est parce qu'on part du principe qu'on a des fonctions, qu'on peut leur donner un nom, et que celui-ci n'est pas limité à quelques caractères. Dire qu'on ne peut pas faire cela avec du code d'il y a 30 ans ne remet pas en cause la recommandation, car aujourd'hui vous ne feriez pas du code d'il y a 30 ans. Une antiquité, on la prend avec soin pour ne pas la casser, et on fait ce qu'on peut avec jusqu'à ce qu'on ait du neuf. Ça ne remet pas en cause les nouvelles normes pour le neuf.

    C'est d'ailleurs généralisable aux "codes où c'est pas possible". Si je ne m'abuse, la programmation fonctionnelle pure et dure n'a pas de variable au sens usuel car pas d'affectation, donc introduire une variable intermédiaire n'a juste pas de sens. Ça ne remet pas en cause la recommandation 1, celle-ci n'est juste pas applicable dans ce cas et il faudra donc trouver autre chose si la lisibilité n'est pas terrible (e.g. introduire une fonction intermédiaire, si je ne me trompe pas, autrement dit la recommandation 2).

    Citation Envoyé par BugFactory Voir le message
    Un autre conseil : penser à son public. Mon code est souvent lu par des stagiaires. Dans ce cas, le conseil d'éviter les commentaires évident n'est pas valable : pour un débutant, rien n'est évident. Je profite des commentaires pour signaler les designs pattern, etc. Ça évite qu'ils ne saccagent l'architecture pour résoudre un ticket.
    Et si tu as oublié de mettre un commentaire ici ou là ? Si le dév est débutant, soit on le forme, soit on introduit un intermédiaire, soit (vive les forks !) on lui fait faire des pull requests, de façon à s'assurer que toutes ses modifs soient validées avant de les intégrer. Le cas échéant, on lui fournit un feedback pour qu'il corrige. Le jour où il est jugé apte au combat, on lui donne les droits de pousser directement sur le dépôt, pas avant.

    On ne donne pas le volant à une personne qui passe tout juste son code. Un débutant, on le traite comme tel, sinon on est soi-même responsable des bourdes du nouveau pour lui avoir mis une arme chargée entre les mains.

    Citation Envoyé par aepli Voir le message
    Il peut y avoir des commentaires inutiles, mais la plus part du temps ce n'est pas le cas.
    En fait j'utilise les fonctions de certains scripts de création d'aide en ligne à l'aide de commentaires.
    Du coup, je décrit de manière intelligible ce que fait tel ou tel fonction, les paramètres, les entrée, les sortie, etc.

    Par contre à l'intérieur du code, il y a très peu de commentaires, seul les cas particuliers sont expliqués ou les manières d'utiliser tel ou tel API, lorsque j'ai croché pour l'implémentation.
    Si j'ai bien compris, tu parles de documenter la fonction, pas de la commenter. La documentation est faite par l'implémenteur de la fonction pour indiquer comment l'utiliser. Le commentaire est fait par l'utilisateur pour indiquer comment il l'a utilisé. On peut dire aussi que la documentation vient avant l'implémentation (c'est parce que tu veux faire une fonction pour faire telle chose que tu décides de l'implémenter de telle manière), alors que le commentaire vient après (tu ajoutes un commentaire pour parler du code que tu viens d'écrire). Ce n'est pas la même chose.

    Du coup, mention spéciale à gagache :
    Citation Envoyé par gagaches Voir le message
    Et oui, le commentaire est fiable dans ce cas. Et bien sûr que j'ai gagné du temps.
    Le dev a indiqué ce qu'il voulait faire ...
    Si ça vient avant, alors c'est ce que tu devrais voir dans la doc, et non en commentaire. Pour rappel, les bonnes pratiques actuelles recommandent l'inversion de dépendance, tu devrais donc avoir une abstraction/interface qui indique la fonction et la documente. Ajouté à cela, tu dois avoir une implémentation de cette interface avec un choix d'algo particulier. Auquel cas, l'algo que tu utilises sera décrit dans la doc de cette implémentation et non en tant que commentaire dans le code, car l'objectif même de l'implémentation est d'utiliser cet algo. Si tu veux en changer, tu ne modifiera pas le code, tu utiliseras une nouvelle implémentation basée sur un autre algo, mais remplissant toujours la même fonction.

    Comme dit par d'autres, la documentation il la faut tout le temps, que la méthode soit complexe ou non. Elle indique ce qu'un éventuel utilisateur de la fonction doit savoir pour l'utiliser correctement. Celui-là ne doit alors pas avoir besoin de lire le code de la fonction pour pouvoir l'utiliser sereinement. Dans le cas de l'interface, il l'utiliseras quand il aura besoin de la fonctionnalité. Dans le cas de l'implémentation, il l'utiliseras quand il jugera que son contexte justifie de favoriser cette implémentation là plutôt qu'une autre. Les commentaires, eux, se trouvent au coeur d'une implémentation, et ne sont voués à être lus si le code correspondant ne l'est pas non plus. D'où mon opposition à Bryce de Mouriès, qui dit que :
    Citation Envoyé par Bryce de Mouriès Voir le message
    je préfère largement lire les commentaires jusqu'à trouver l'endroit qui m'intéresse plutôt que lire le code
    Dans ce cas, ce que tu cherches c'est la documentation : si le code ne t'intéresse pas, fout ça dans une fonction bien nommée. Pas besoin de surcharger ta lecture avec un commentaire + un code, alors qu'un appel de fonction, typiquement équivalent au commentaire, suffit. C'est d'autant plus vrai avec les fluent interfaces.

    Au passage :
    Citation Envoyé par Bryce de Mouriès Voir le message
    lire le code c'est comme lire une langue étrangère, on aura beau la connaître il y a toujours un travail de traduction dans la tête. Alors que lire un texte se fait naturellement.
    Tant qu'il est dans la même langue. En contexte international, je suis pas convaincu que l'argument tienne longtemps, chacun ayant son "anglais".

    Pour ce qui est des "non, non et non" de jopopmk, je suis du même avis que Cincinnatus : ne pas confondre le boulot du développeur à celui du compilateur. L'optimisation de la syntaxe (e.g. moins de lignes, moins d'appels), c'est le boulot du compilateur. Le dév doit optimiser la sémantique (e.g. identifier les bons concepts et leurs relations, choisir la bonne architecture, le bon algo).

    Quant à l'argument de Pyramidev comme quoi qu'il faut faire des aller-retour pour voir l'implémentation des sous fonctions ce n'est pas un bon argument :
    • soit on s'intéresse à la fonction appelante, et dans ce cas la fonction appelée on se contente de comprendre ce qu'elle apporte à celle-ci par son nom et sa doc, je rejoins donc martopioche sur ce point là, et le contre argument n'y change rien car on change de problème (ce n'est pas que le commentaire est nécessaire, mais que la doc est absente ou incomplète, l'un n'excusant pas l'autre) ;
    • soit on s'intéresse à la fonction appelée et dans ce cas la fonction appelante on n'en a rien à faire, si tant est que la fonction appelée est faite proprement ;
    • soit on s'intéresse aux deux, alors sous-fonction ou pas, il faudra lire les deux.


    Si tu parles de l'effort à fournir pour jongler car il faut se déplacer dans le code, alors ce n'est encore une fois pas un bon argument : utilise un EDI. Sous Eclipse, j'arrive sur un appel de fonction, je fais F3 (ou CTRL+clic pour ceux qui sont plus souris) et j'accède à la fonction appelée, quand à alt+gauche il me fait revenir à la fonction appelante. Ce genre d'argument, c'est le même que se plaindre que le binaire est pas super lisible : on a évolué depuis, il faudrait peut-être se mettre à jour. Pareil pour le code mort d'un autre intervenant : on ne le met pas en commentaire, on le supprime. Et si on veut pouvoir le récupérer, on s'assure d'utiliser un système de gestion de version. À chaque besoin sa solution.

    Citation Envoyé par clementmarcotte Voir le message
    je dirais qu'il y a au moins trois occasions où des commentaires sont pratiquement obligatoires, ne serait-ce que par respect pour lecteurs.

    1) Quand on veut déposer le programme-source sur un site où de parfaits débutants pourraient l'utiliser

    2) Quand on est obligé, pour des raisons d'optimisation, ou d'autres raisons techniques, d'utiliser une sorte de "passe croche" inédite ou inconnue. (Ne serait-ce que parce que la mémoire est une faculté qui oublie)

    3) Quand on sait que le programme peut être utilisé très longtemps. (Comme les antiques programmes en COBOL avec des années à juste deux chiffres qui étaient encore là en 1999.)
    1) Si c'est pour les débutants, c'est un code de tuto, ça n'a pas vocation à être utilisé tel quel dans un projet. Ce n'est pas une question de respect, mais de cible, un code de tuto sera aussi simplifié pour se concentrer sur certains aspects, sinon ce ne sera pas un bon code de tuto. Si tu développes une lib dont tu fournis les sources pour être réutilisée par des devs, c'est le code qui les intéressera avant tout, les commentaires éventuellement après. Justement parce que l'objectif est d'intégrer le code, pas les commentaires.

    2) Optimisation, OK.

    3) Peu pertinent : Si le code ne permet pas de représenter le métier fidèlement, tu mets des commentaires pour pallier les limites du code, c'est bon tout de suite, pas pour plus tard. S'il le permet, mettre des commentaires n'y changera rien, car si le code n'est plus clair dans 30 ans, c'est que le métier aura changé, donc tes commentaires écrits avec la logique métier de l'époque ne seront pas plus clairs. Le seul avantage devient alors les infos supplémentaires que fournissent ton commentaire, de là la question fondamentale : c'est du commentaire ou de la doc ?

    Citation Envoyé par gagaches Voir le message
    Les commentaires ont pour but de rendre le code compréhensible.
    Là, c'est subtil, mais c'est faux : les commentaires ont pour but d'ajouter des informations au code, quelles qu'elles soient. Elles n'ont pas vocation à le rendre plus compréhensible, bien que ce soit un usage possible. En l'occurrence, si on part de ce principe, alors par définition un code non commenté n'est pas compréhensible... alors comment le gars a fait pour créer ce code si c'est incompréhensible ? Il ne faut pas les réduire à un seul usage. Surtout quand cet usage survient justement par un manque de qualité du code : ça devient une excuse pour ne pas coder mieux.

    Citation Envoyé par martopioche Voir le message
    La présence des getters/setters a un grand intérêt : elle permet de déterminer qu'un code "orienté objet" a été écrit par des développeurs qui n'ont aucune notion de POO.
    Objection ! Les getters sont à peu près toujours nécessaires en POO, sous une forme ou sous une autre : un objet dispose généralement de propriétés, un getter ne fait qu'y donner accès, autrement l'objet on n'en fait pas grand chose. Quant aux setters, la POO ne se limite pas à des objets immutables : si tu crées un conteneur, par exemple un singleton de configuration ou un builder, les setters sont nécessaires pour affecter les valeurs.

    Citation Envoyé par deuz59 Voir le message
    Un exemple tout bête : préciser l'unité de mesure dans une variable métrique (temps, distance, etc...).
    Si j'ai une API qui me met demande un paramètre "time" (passons sur le fait que "time" reste hyper générique et pourrait être plus précis quand à la signification de la variable) et que je dois lire la doc pour savoir que c'est en secondes, pourquoi ne pas directement indiquer "timeInSeconds" !
    Parce que "seconds" est encore mieux ? Si la fonction c'est "wait(...)", ça me semble suffisant, pas besoin de faire des noms à rallonge non plus. Utiliser d'abord un terme (seconds), et si on a besoin de nommer plus d'une variable de la même manière, ajouter en plus leurs usages respectifs (secondsToWait + secondsToRun).
    Site perso
    Recommandations pour débattre sainement

    Références récurrentes :
    The Cambridge Handbook of Expertise and Expert Performance
    L’Art d’avoir toujours raison (ou ce qu'il faut éviter pour pas que je vous saute à la gorge {^_^})

  2. #102
    Expert éminent
    Avatar de Pyramidev
    Homme Profil pro
    Développeur
    Inscrit en
    Avril 2016
    Messages
    1 470
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Haute Garonne (Midi Pyrénées)

    Informations professionnelles :
    Activité : Développeur

    Informations forums :
    Inscription : Avril 2016
    Messages : 1 470
    Points : 6 107
    Points
    6 107
    Par défaut
    Citation Envoyé par Pyramidev Voir le message
    Cela dit, si une fonction de taille modeste contient un ou deux bouts de code que je souhaite commenter et que ces bouts de code n'existent pas ailleurs, je ne vais pas les transformer en sous-fonctions seulement pour éviter d'écrire des commentaires !
    L'inconvénient de morceler en sous-fonctions, c'est qu'il faut faire des aller-retours dans le code si on veut voir l'implémentation de ces sous-fonctions.
    Citation Envoyé par Matthieu Vergne Voir le message
    Quant à l'argument de Pyramidev comme quoi qu'il faut faire des aller-retour pour voir l'implémentation des sous fonctions ce n'est pas un bon argument :
    • soit on s'intéresse à la fonction appelante, et dans ce cas la fonction appelée on se contente de comprendre ce qu'elle apporte à celle-ci par son nom et sa doc, je rejoins donc martopioche sur ce point là, et le contre argument n'y change rien car on change de problème (ce n'est pas que le commentaire est nécessaire, mais que la doc est absente ou incomplète, l'un n'excusant pas l'autre) ;
    • soit on s'intéresse à la fonction appelée et dans ce cas la fonction appelante on n'en a rien à faire, si tant est que la fonction appelée est faite proprement ;
    • soit on s'intéresse aux deux, alors sous-fonction ou pas, il faudra lire les deux.


    Si tu parles de l'effort à fournir pour jongler car il faut se déplacer dans le code, alors ce n'est encore une fois pas un bon argument : utilise un EDI. Sous Eclipse, j'arrive sur un appel de fonction, je fais F3 (ou CTRL+clic pour ceux qui sont plus souris) et j'accède à la fonction appelée, quand à alt+gauche il me fait revenir à la fonction appelante.
    Avec une fonction de taille modeste dont certaines portions de code sont résumées par des commentaires, l'ordre de lecture est plus libre que si lesdites portions de code avaient été transformées en sous-fonctions.
    Je vais prendre un exemple tiré d'un fil du forum du langage C.
    Soit l'exercice suivant : « Soit n un nombre entier entre 1 et 10. Afficher, dans l’ordre croissant, toutes les différentes combinaisons de n chiffres différents dans l’ordre croissant. »
    Par exemple, si n == 3, cela donne : « 012 013 014 015 016 017 018 019 023 ... 789 ».
    Admettons que, quelque part dans l'implémentation, on crée une fonction qui prend une chaîne d'un nombre à afficher et qui la transforme en la chaîne du nombre suivant à afficher. Par exemple, avec n == 5, la chaîne à afficher qui suit "13789" est "14567".
    Voici la fonction que j'avais écrite :
    (Je m'excuse d'avance s'il y a des lecteurs du fil présent qui ne connaissent pas l'arithmétique des pointeurs en langage C.)
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    bool remplacerParChaineSuivante(char* chaine, int nbChiffres)
    {
    	assert(nbChiffres >= 1 && nbChiffres <= 10);
    	char* const premier = chaine;
    	char* const dernier = chaine + nbChiffres - 1;
    	// Etape 1 : On cherche le chiffre à incrémenter le plus à droite possible.
    	char* chiffreAIncrementer = dernier;
    	while(true)
    	{
    		const char valeurMaximale = '9' - (dernier - chiffreAIncrementer);
    		const bool incrementationPossible = (*chiffreAIncrementer < valeurMaximale);
    		if(incrementationPossible)
    			break;
    		if(chiffreAIncrementer == premier)
    			return false; // pas de chaîne suivante
    		--chiffreAIncrementer;
    	}
    	// Etape 2 : On incrémente le chiffre trouvé.
    	++(*chiffreAIncrementer);
    	// Etape 3 : On ajuste tous les éventuels chiffres qui sont à droite de celui
    	// qu'on vient d'incrémenter.
    	for(char* ptr = chiffreAIncrementer + 1; ptr <= dernier; ++ptr)
    		*ptr = *(ptr-1) + 1;
    	return true;
    }
    Si j'avais appliqué la maxime "Au lieu de commenter un morceau de code, il faut faire une sous-fonction", j'aurais écrit :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    bool remplacerParChaineSuivante(char* chaine, int nbChiffres)
    {
    	assert(nbChiffres >= 1 && nbChiffres <= 10);
    	char* const premier = chaine;
    	char* const dernier = chaine + nbChiffres - 1;
    	char* chiffreAIncrementerSiExiste =
    		chercherChiffreIncrementableLePlusADroitePossible(premier, dernier);
    	if(chiffreAIncrementerSiExiste == NULL)
    		return false;
    	++(*chiffreAIncrementerSiExiste);
    	ajusterEventuelsChiffresDansIntervalleFerme(chiffreAIncrementerSiExiste+1, dernier);
    	return true;
    }
    
    char* chercherChiffreIncrementableLePlusADroitePossible(char* premierCaracChaine, char* dernierCaracChaine)
    {
    	char* result = dernierCaracChaine;
    	while(true)
    	{
    		const char valeurMaximale = '9' - (dernierCaracChaine - result);
    		const bool incrementationPossible = (*result < valeurMaximale);
    		if(incrementationPossible)
    			return result;
    		if(result == premierCaracChaine)
    			return NULL;
    		--result;
    	}
    }
    
    void ajusterEventuelsChiffresDansIntervalleFerme(char* premierAAjuster, char* dernierAAjuster)
    {
    	for(char* ptr = premierAAjuster; ptr <= dernierAAjuster; ++ptr)
    		*ptr = *(ptr-1) + 1;
    }
    Avec ma version avec une seule fonction et quelques commentaires :
    • Si on veut d'abord une vision globale mais imprécise de l'algorithme puis analyser les détails, dans un premier temps, on lit les commentaires "Étape". Si notre cerveau ne fait attention qu'à ces commentaires, on voit :
      Code : Sélectionner tout - Visualiser dans une fenêtre à part
      1
      2
      3
      4
      	// Etape 1 : On cherche le chiffre à incrémenter le plus à droite possible.
      	// Etape 2 : On incrémente le chiffre trouvé.
      	// Etape 3 : On ajuste tous les éventuels chiffres qui sont à droite de celui
      	// qu'on vient d'incrémenter.
      Dans un deuxième temps, on fait attention au code qui se trouve sous les commentaires.
    • Si on veut analyser l'algorithme de manière séquentielle, on peut lire tout le code d'une traite, du début à la fin.


    Avec ma version avec une fonction appelante et deux sous-fonctions :
    • Si on veut d'abord une vision globale mais imprécise de l'algorithme puis analyser les détails, dans un premier temps, on lit la fonction appelante. Ensuite, pour connaître les détails, on lit le code des sous-fonctions. En fait, dans l'exemple présent, on lit le code dans l'ordre.
    • Si on veut analyser l'algorithme de manière séquentielle, on lit le début du code de la fonction appelante, puis on lit celui de la première fonction appelée, puis on retourne lire la suite de la fonction appelante, puis on va lire le code de la deuxième fonction appelée. On perd du temps en aller-retours. Et même si c'est rapide en faisant du défilement vertical (à condition que les fonctions restent à côté) ou en utilisant des raccourcis clavier via un IDE, ce ne sera jamais aussi rapide que de lire directement le code qu'on a déjà sous les yeux, comme dans l'autre version avec une seule fonction et quelques commentaires.
    • Il ne servirait à rien de documenter ces deux sous-fonctions : une documentation imprécise n'apporterait pas plus d'infos que le nom de la fonction tandis qu'une documentation précise serait plus longue que le code.

  3. #103
    Membre éclairé
    Profil pro
    Inscrit en
    Décembre 2006
    Messages
    614
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Décembre 2006
    Messages : 614
    Points : 713
    Points
    713
    Par défaut
    Oulà, pavé oui, pas sûr d'avoir tout lu/parcouru mais…

    Citation Envoyé par Matthieu Vergne Voir le message
    La documentation est faite par l'implémenteur de la fonction pour indiquer comment l'utiliser. Le commentaire est fait par l'utilisateur pour indiquer comment il l'a utilisé.
    Je crois que pour le sujet actuel, "documentation" et "commentaires" sont écrits par la même personne car on parle du code de la fonction. Documentation et commentaire se distinguent dont de leur cible comme tu l'écrits sinon que le commentaire est destiné à celui qui maintiendra la fonction.

    On peut dire aussi que la documentation vient avant l'implémentation (c'est parce que tu veux faire une fonction pour faire telle chose que tu décides de l'implémenter de telle manière)
    Honnêtement, "bof"…*Le pourquoi tu écris la fonction est illustré par son nom et sa signature. La documentation renseigne l'utilisateur sur on usage, j'aurai donc tendance à dire que la documentation est rédigée avant publication, une fois qu'on a bien défini comment elle se comporte.

    Objection ! Les getters sont à peu près toujours nécessaires en POO, sous une forme ou sous une autre : un objet dispose généralement de propriétés, un getter ne fait qu'y donner accès, autrement l'objet on n'en fait pas grand chose. Quant aux setters, la POO ne se limite pas à des objets immutables : si tu crées un conteneur, par exemple un singleton de configuration ou un builder, les setters sont nécessaires pour affecter les valeurs.
    Le problème est dans la définition de cette forme ou autre…*Lorsqu'on parle de getters et setters, dans 99% des cas, on a des implantations du type
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    setTruc(type truc) {
        this.truc = truc;
    }
    
    getTruc() {
        return this.truc;
    }
    Et c'est tout…
    Idem pour les "setters", ça n'a rien à voir avec une notion d'immuable. Exemple bateau : un compte bancaire a une propriété "solde" et ne devrait pas avoir de "setters". La valeur initiale est fixée dans le contructeur et l'évolution du solde par des méthode de débit et crédit…

    Ceci va de paire avec la citation à laquelle je répondais :
    et bien sûr qu'il faut avoir des getters setters ! Comment tu fais quand t'as un bug et que tu veux mettre un point d'arrêt parce que la valeur que tu as dans ton objet est déconnante ?
    sous entendu la conception est destinée au débugguage et non à la production.

    Je rajouterai pour la notion d'accès que cette notion de "getters/setters" découle de la volonté de mettre les attributs en privé (en tout cas pas en public) et que je défaut de l'objet est que la visibilité est du "tout ou rien". Du coup, on arrive à la structure stupide où on a une méthode pour simplement accéder à un attribut. Ce qui est appréciable avec certains langages modernes, c'est d'avoir enfin une structure qui remédie à ça tout en étant des fonctions en étant exposé comme un attribut (Properties en Python ou les Computed Properties en Swift).

  4. #104
    Membre éclairé
    Profil pro
    Inscrit en
    Décembre 2006
    Messages
    614
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Décembre 2006
    Messages : 614
    Points : 713
    Points
    713
    Par défaut
    Citation Envoyé par Pyramidev Voir le message
    • Si on veut d'abord une vision globale mais imprécise de l'algorithme puis analyser les détails, dans un premier temps, on lit les commentaires "Étape". Si notre cerveau ne fait attention qu'à ces commentaires, on voit :
      Code : Sélectionner tout - Visualiser dans une fenêtre à part
      1
      2
      3
      4
      	// Etape 1 : On cherche le chiffre à incrémenter le plus à droite possible.
      	// Etape 2 : On incrémente le chiffre trouvé.
      	// Etape 3 : On ajuste tous les éventuels chiffres qui sont à droite de celui
      	// qu'on vient d'incrémenter.
      Dans un deuxième temps, on fait attention au code qui se trouve sous les commentaires.
    • Si on veut analyser l'algorithme de manière séquentielle, on peut lire tout le code d'une traite, du début à la fin.
    Et je crois que tout le problème vient bien de "qu'est ce qu'on veut faire". Sérieux, qui veut avoir une vision de l'algo ? En tant qu'utilisateur de la fonction, RAF…*Ou plutôt, si elle a une importance, l'idée générale doit être précisée dans la doc (quelle algo de tri est utilisé par exemple). Mais je n'irai pas vérifier que l'implémentation correspond à l'algo. Si je dois ouvrir le code pour une évol' ou une correction, l'intention, RAF…*Je ne vais pas perdre du temps à lire une intention puis vérifier que l'implantation correspond. Et oui, je vais tout parcourir car tu a beau écrire ce qu'est l'étape 3, "dernier", je n'ai aucune idée de ce que c'est…

    Ton premier listing correspond exactement au problème soulevé par le sujet : tes commentaires sont nécessaire car le code en lui même prête à confusion partout… Par exemple, si la fonction retourne la "combinaison" suivante d'un nombre à n chiffres et que ce nombre est passé en paramètre, à quoi sert le paramètre nbChiffres ??? À quoi sert le commentaire ligne 18 ? Sérieux, "ptr", on est censé comprendre qu'on manipule quoi ? et les noms à rallonge… Dans le second listing, pas besoin de chiffreAIncrementerSiExiste, c'est la doc de chercherChiffreIncrementableLePlusADroitePossible qui renseigne qu'elle peut retourner NULL et la lecture du code montre un test juste après, donc le traitement du NULL donc que l'on peut avoir un résultat "qui n'existe pas". Tiens d'ailleurs, d'un simple point de vue compréhension, qu'apporte le possible de chercherChiffreIncrementableLePlusADroitePossible ? J'oserai même demander à propos de LePlusADroite, à gauche, ça a un sens ?

    Ensuite, j'ai bien lu ton argument
    Si on veut analyser l'algorithme de manière séquentielle, on peut lire tout le code d'une traite, du début à la fin.
    Sauf que là, on suppose que si on ne veut pas être pollué par l'intention, on passe sur les commentaires. Or, les commentaires servent aussi à expliquer pourquoi le code suivant a été écrit ainsi lorsque la lecture n'est pas évidente. Du coup, on doit filtrer les commentaires importants pour la maintenance du bruit…

    Enfin, ton argumentaire tient dans ce cas puisque tu viens de l'écrire et qu'il correspond à ton attente. Sauf que un code, ça vit, ça évolue et dans le monde professionnel, plusieurs développeurs sont appelés à intervenir dessus. Au bout de 5 tickets, ton intention ne correspond plus du tout au code suivant…

    Perso, il y a un cas unique où j'écris ce type de commentaires : pour le code destiné à mes formations. Mais le cas est très particulier : ce code est un support de formation maitrisé et non un code destiné à évoluer.

  5. #105
    Expert éminent
    Avatar de Matthieu Vergne
    Homme Profil pro
    Consultant IT, chercheur IA indépendant
    Inscrit en
    Novembre 2011
    Messages
    2 264
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France

    Informations professionnelles :
    Activité : Consultant IT, chercheur IA indépendant
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Novembre 2011
    Messages : 2 264
    Points : 7 760
    Points
    7 760
    Billets dans le blog
    3
    Par défaut
    Citation Envoyé par Pyramidev Voir le message
    Avec une fonction de taille modeste dont certaines portions de code sont résumées par des commentaires, l'ordre de lecture est plus libre que si lesdites portions de code avaient été transformées en sous-fonctions.
    Ça veut dire quoi, plus libre ? Dans tous les cas, ton code est ordonné, tu ne peux pas déplacer les bouts de code sinon tu casses ta fonction, donc tu ne peux pas bouger les commentaires non plus vu qu'ils décrivent les bouts de code en question. Si par plus libre tu veux dire que tu peux choisir entre lire le commentaire ou le code... ben oui, mais avec cette excuse on ne ferait aucune fonction. La question étant de savoir où se trouve la limite. Soit on part du principe que c'est une question de goût, soit on établit des conventions, soit on définit des mesures objectives qui ne dépendent de personne. Alors quand-est-ce que cette liberté n'a plus lieu d'être et la création d'une sous-fonction se justifie ? Quand le code n'est plus modeste ? C'est quoi un code modeste ? Quell rapport avec le nombre de lignes ? Est-ce que ça dépend de la complexité ? Dans ce cas ça fait combien de boucles ? De quelles longueurs ? De quelle profondeur ? Avec combien de return qui cassent le process ? Etc.

    Pour le coup, y'a rien de concret, c'est chacun sa tambouille. Alors traitons l'exemple :
    Citation Envoyé par Pyramidev Voir le message
    Je vais prendre un exemple tiré d'un fil du forum du langage C.
    Soit l'exercice suivant : « Soit n un nombre entier entre 1 et 10. Afficher, dans l’ordre croissant, toutes les différentes combinaisons de n chiffres différents dans l’ordre croissant. »
    Par exemple, si n == 3, cela donne : « 012 013 014 015 016 017 018 019 023 ... 789 ».
    OK, le cahier des charges me semble clair. Faut pas louper qu'il y a 2 "croissant" : l'ordre des séquences et l'ordre des chiffres dans chaque séquence. Par contre je parle pas de nombre, vu qu'on a des séquences de chiffres à taille fixe, style code-barre. Je parle donc de numéro.

    Citation Envoyé par Pyramidev Voir le message
    Admettons que, quelque part dans l'implémentation, on crée une fonction qui prend une chaîne d'un nombre à afficher et qui la transforme en la chaîne du nombre suivant à afficher.
    Donc j'en comprends qu'on part sur un design itératif : on génère et traite 1 numéro à la fois, qu'on justifiera par des contraintes de place style système embarqué. Pas de génération de liste de numéros (complète ou partielle) à afficher d'un coup.

    Donc déjà, à partir de là, tu as plusieurs responsabilités à partager, à savoir :
    - la génération des numéros, qui se décompose de par le design choisit en : (i) la génération du premier numéro, par construction "012...(n-1)", et (ii) la génération du nombre suivant depuis le nombre courant,
    - l'affichage du nombre, qui peut se faire dans un flux quelconque vu que ce n'est pas précisé (l'exemple d'origine se restreint à la console).

    De là, tu en tire 2 objets :
    - l'itérateur, qui génère les nombre à la suite et s'initialise automatiquement avec le premier numéro en lui passant n,
    - l'afficheur, qui reçoit un numéro et l'affiche sur le flux souhaité.

    Vu que tu ne donnes que la seconde fonction de l'itérateur, je vais aussi me focaliser là dessus et ignorer le reste. On cherche donc, à partir d'une chaîne de caractère contenant un numéro d'une certaine taille, à générer le numéro suivant.

    Maintenant, avant même d'entrer dans le code, il y a de la conception à faire. Or à partir de là, le contexte importe. Tout d'abord, la question originale indique que c'est un exercice (de l'école 42). Ce n'est pas un code de prod, mais un code de présentation : ta fonction n'a pas vocation à être utilisée, mais à être lue et testée selon des critères arbitraires, choisis pour te forcer à travailler tel ou tel aspect pour ta formation. De là, la perspective à prendre est effectivement de commenter, car le but est de juger le code, pas d'en faire une boîte noire qu'un utilisateur pourra utiliser sans creuser le code. On se fiche donc totalement de la doc (d'autant plus vrai si on se focalise sur une seule fonction) et on souhaite savoir comment le code a été conçu. On peut même avoir pour contrainte, totalement arbitraire, de tout faire en une seule fonction, justement pour te forcer à optimiser ton code. Or je rappelle que l'optimisation poussée, nécessitant de favoriser les subtilités à la lisibilité, sont les seuls cas où j'estime justifié de mettre des commentaires (expliquant les choix). Donc si on se focalise sur un exercice, je n'ai rien à dire à ton code : c'est pas moi le prof, c'est pas moi qui établit les critères.

    Je vais donc me focaliser sur un contexte de prod, et là, ça change tout. Car là, ta fonction n'est pas toute seule, elle évolue dans un ensemble, potentiellement très gros. Ta fonction, tu te dois de la rendre facilement utilisable, ce qui implique de rédiger une documentation digne de ce nom. Or, on est en droit de se poser la question de savoir si tes commentaires ne pourraient pas être en faite ta doc. Je vois tout de suite certains me dire "mais ta doc ne doit pas décrire le comment, c'est toi-même qui l'a dit", sauf que la doc doit indiquer l'objectif, et j'ai déjà parlé des cas où l'algo utilisé était justement l'objectif recherché. Mais cela se justifie par le contexte de prod. En effet, un projet entier ne se focalise pas sur quelques bonnes pratiques, mais sur un ensemble de bonnes pratiques. Il est notamment recommandé de dépendre d'abstractions plutôt que d'implémentations, pour la simple raison qu'on peut en avoir plusieurs, chacune étant plus efficace selon le contexte. Par exemple, tu peux imaginer utiliser un algo qui traite la chaîne de caractères en tant que telle (i.e. un séquence de caractères) mais qui doit donc splitter la chaîne, répéter les opérations de conversion char->int->char pour chaque caractère, et concaténer les caractères générés. Tu peux aussi imaginer un algo qui retraduit la chaîne complète en un nombre entier, traite le nombre, et le retraduit d'un coup en chaîne de caractères. Tu peux aussi imaginer utiliser des objets particuliers, ce qui peut être utile si les caractères ne sont pas simplement entre 0 et 9, mais entre 0 et 5, ou hexadécimaux, des symboles bien particuliers du fait du métier, voire des symboles qui changent, et donc on a besoin d'un algo assez générique pour être réutilisé. On ne sait jamais quelle évolution peut arriver.

    De là, tu reprends ta conception itérative, et tu définis les abstractions qui te serviront. En Java on utiliserait une interface étendant Iterator<String> pour du standard, ou autre chose si préfère du dédié (notamment pour utiliser du vocabulaire métier, le standard restant générique). La documentation de cette interface serait alors une documentation fonctionnelle, car ton interface se situe au niveau abstrait, où on connaît les fonctions dont on a besoin, mais dont on se fout de comment c'est implémenté. Par exemple pour ta fonction itérative, tu mettrais une documentation du style (à adapter à ton style conventionnel) :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    Génère un numéro valide supérieur à un numéro de départ. Le numéro généré
    doit être au plus proche du numéro de départ, de façon à pouvoir appeler
    itérativement la fonction pour générer une liste exhaustive de numéros valides.
    
    Paramètre: numéro de départ
    Retour: numéro suivant
    Maintenant parlons code, et notamment design. Ta fonction, si elle est juste (j'ai pas vérifié) a en revanche plusieurs défauts conceptuels invitant à l'erreur :
    • tu réécris la chaîne de caractère fournie en entrée : si la chaîne est utilisée ailleurs, tu la corrompt. Pour un exo, pas de soucis, pour de la prod, c'est un risque en plus. Donc plutôt qu'un remplacerParChaineSuivante(...) je ferai un générerChaineSuivante(...) qui crée sa propre chaîne.
    • tu modifies la chaîne en live, mais retourne tranquillement faux si tu te rends compte qu'il n'y a pas de suivant en plein traitement, ce qui t'amène potentiellement à un état où ta chaîne, initialement valide, ne l'est plus. Dans ce cas, la fonction appelante t'ayant fournie sa propre chaîne, elle dispose d'une chaîne invalide sans le savoir. Là par contre, même pour un exo, ça ne passe pas.
    • ta fonction est en charge de dire s'il n'y a pas de suivant en retournant faux : quant tu appelles une fonction qui dit "remplacer", elle est censée remplacer, mais si ta fonction se termine tranquillement sans remplacer parce qu'il n'y a plus de numéro suivant, alors ta fonction n'est pas une fonction de remplacement à proprement parler. Le soucis est que ta fonction intègre non seulement le remplacement mais aussi le contrôle de la dernière occurrence, 2 responsabilités au lieu d'une. Pour rester clair, il te faut séparer ça, ce qui dans ce cas peut se faire facilement si je ne m'abuse : le dernier numéro est forcément "(10-n)...789", donc il est très facile de savoir si on a atteint le bout avant de faire le moindre traitement (il suffit de vérifier que le premier caractère vaut 10-n, les autres valant forcément le reste si le numéro est valide). On peut donc raisonnablement se proposer de vérifier d'abord si on a atteint le bout, et seulement sinon de demander le suivant. J'aurais donc ajouté une fonction estDernierNumero(numero) qui te renvoie vrai ou faux en conséquence, et si c'est faux alors j'appelle la fonction d'incrémentation, qui peut alors se concentrer sur son boulot. Si tout se passe bien, la fonction d'incrémentation renvoie le numéro suivant, sinon elle génère une exception explicite en fonction du problème. Dans ce contexte, se rendre compte en milieu de traitement qu'il n'y a pas de suivant doit générer une exception, pas juste se terminer silencieusement.


    Si tu ne modifies pas tes entrées directement, tu évites les effets de bord, et si tu t'assure que ça ne retourne qu'une valeur correspondant à la sémantique associée, tu réduit les cas à gérer, rendant ta fonction plus simple car tout ce qui sort de ton traitement revient à lancer une exception plutôt qu'à ajouter du traitement pour rester valide (avec ta fonction, il aurait fallut faire un rollback avant de retourner faux). D'un point de vue maintenabilité, c'est un plus de se limiter à ce qu'on annonce, car appeler remplacerParChaineSuivante(...) pour se rendre compte qu'il n'y a pas de suivant (et que le remplacement n'a pas été fait) n'est pour moi pas un design clair, et donc favorisera des erreurs d'utilisation et des codes peu naturels.

    Une fois que ça c'est dit, qu'aurais-je mis au niveau du code ? Sincèrement, c'est pas le plus important, disons pour l'instant que j'aurais mis le même que le tient (avec les corrections mentionnées). Par contre, la différence significative est que je n'aurais pas mis de commentaire. Pourquoi ? Parce que je rappelle que ce que j'ai défini en premier est une abstraction (interface ou classe abstraite), dans laquelle on défini les fonctions souhaitées auxquelles on met la documentation fonctionnelle. Du coup, ce code là irait dans une implémentation de mon abstraction, qui peut dès lors disposer de sa propre documentation. Or, comme cette implémentation a justement pour objectif d'implémenter mon abstraction avec cet algo là, j'aurais décrit l'algo dans la documentation, cette fois-ci une documentation technique. Contrairement à un commentaire dans le code, qui décrit ce que le code fait déjà et qui doit donc évoluer avec celui-ci, la documentation s'écrit avant et indique donc ce que le code doit faire. Même si le code évolue, la doc elle doit rester respectée. J'ai donc une implémentation dédiée à cet algo là, avec la description de l'algo dans la doc :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    Cette fonction implémente la génération d'un numéro valide
    supérieur à un numéro de départ. L'algorithme utilisé est celui
    proposé par Pyramidev sur developpez.net (...url...) et se
    décompose comme suit:
    1. On cherche le chiffre à incrémenter le plus à droite possible.
    2. On incrémente le chiffre trouvé.
    3. On ajuste tous les éventuels chiffres qui sont à droite de
        celui qu'on vient d'incrémenter.
    
    Paramètre: numéro de départ
    Retour: numéro suivant
    Quelques commentaires sur le code quand même :
    • j'aurais non pas retiré les commentaires mais je les aurait remplacé par des lignes vides, parce qu'un code en différentes étapes, ça se lit mieux quand les étapes sont visuellement séparées.
    • je n'aurais pas utilisé les raccourcis style boucle for sans accolades, qui se contentent de réduire le code sans le rendre plus lisible. Ça augmente les risques de se planter lors d'une modif (e.g. rajouter une ligne en oubliant d'ajouter les accolades). Avoir moins de lignes dans le code ne rend pas l'exe plus léger, donc laisse au compilo le soin de compresser ton exe, il le fait bien plus efficacement que toi. Aère ton code, rend le lisible pour le développeur. Tant que ça compile, ton compilo n'aura aucun mal à le lire, pas besoin de réduire son boulot si ce n'est pas évidemment nécessaire.
    • j'aurais évité un break ou un return en plein milieu de la fonction. Ce genre de chose, c'est précisément ce qui me fait faire une sous-fonction, même petite : une fonction contenant une boucle qui return en plein milieu quand son traitement est terminé. Pas de break t'amenant on ne sait où (pensons aux boucles imbriquées), quand on casse, on casse tout et on sort, comme ça pas d'ambiguïté. La fonction appelante, elle, dispose d'étapes qui se suivent sans rien casser, de manière à ce que toutes les étapes soient clairement exécutées.
    • j'aurais évité un while(true), pour la simple raison que la condition d'arrêt est connue : quand le remplacement est terminé, à savoir après maximum n itérations. Préfère créer un booléen de terminaison nommé en conséquence affecté à true quand c'est terminé. Sinon un for qui va du dernier au premier caractère, avec un boolean disant si il est encore nécessaire d'incrémenter. Un bug dans le while(true) et tu es bon pour avoir une boucle infinie dure à chopper (dans un système de prod, ça freeze et c'est le branle-bas de combat, y'a plus rien qui bouge et même pas une exception générée, obligé de redémarrer comme un bourrin, ça fait pas pro du tout).
    • je n'aurai pas mis de paramètre nbChiffres, car la chaîne de caractères est faite et sa taille suffit à le retrouver, ce qui peut-être fait en utilisant un objet plus riche qu'un char* (de préférence immutable avec tous les contrôles nécessaires à l'initialisation, de façon à garantir la validité de l'état) ou en parcourant la chaîne jusqu'au caractère nul. Pour moi, ce type de paramétrage (origine+taille de la chaîne) est du vieux code, auquel il vaut mieux préférer un objet plus riche. Bien évidemment, c'est à juger en fonction des contraintes, on a déjà parlé qu'on se mettait dans un contexte de système embarqué, où l'espace est petit et donc les bits coûtent chers. Pour autant, ce genre d'optimisation ne se justifie que si le gain est significatif.


    Donc au final, ça fait pas mal de simplifications à apporter sur le code lui-même. Probablement suffisamment pour que les commentaires deviennent superflus. À toi d'essayer et de juger.

    Je répondrais enfin à quelques autres points :

    Citation Envoyé par Pyramidev Voir le message
    Avec ma version avec une fonction appelante et deux sous-fonctions :
    • Si on veut d'abord une vision globale mais imprécise de l'algorithme puis analyser les détails, dans un premier temps, on lit la fonction appelante. Ensuite, pour connaître les détails, on lit le code des sous-fonctions. En fait, dans l'exemple présent, on lit le code dans l'ordre.
    • Si on veut analyser l'algorithme de manière séquentielle, on lit le début du code de la fonction appelante, puis on lit celui de la première fonction appelée, puis on retourne lire la suite de la fonction appelante, puis on va lire le code de la deuxième fonction appelée. On perd du temps en aller-retours. Et même si c'est rapide en faisant du défilement vertical (à condition que les fonctions restent à côté) ou en utilisant des raccourcis clavier via un IDE, ce ne sera jamais aussi rapide que de lire directement le code qu'on a déjà sous les yeux, comme dans l'autre version avec une seule fonction et quelques commentaires.
    • Il ne servirait à rien de documenter ces deux sous-fonctions : une documentation imprécise n'apporterait pas plus d'infos que le nom de la fonction tandis qu'une documentation précise serait plus longue que le code.
    • Si on veut d'abord une vision globale mais imprécise de l'algorithme puis analyser les détails, dans un premier temps, on lit la doc de l'implémentation, qui donne l'algo voire du pseudo code. Ensuite, pour connaître les choix d'implémentation concret, on lit le code.
    • Si on veut analyser l'algorithme de manière séquentielle, on lit le code de la fonction appelante, et seulement si on veut entrer dans les détails d'une sous-fonction (soit parce qu'elle est mal nommée, donc c'est ta faute, soit parce que son nom indique clairement son rôle et nous fait penser que c'est là que se trouve le problème à régler, donc on ira voir celle-là et pas une autre, ni la suite de la fonction appelante) alors on ira voir le code de la sous-fonction. Le temps perdu à faire les aller-retours est négligeable quand on maîtrise son outil, et même si ce ne sera jamais aussi rapide que de lire directement le code à la suite, ça n'a rien d'un argument, car sinon on ne ferait qu'une fonction main() avec tout dedans. C'est là tout l'intérêt d'une fonction bien nommée : y'a pas besoin de savoir comment elle est codée, seulement de savoir à quoi elle sert, et un bon nommage fournit exactement cela (ou si on utilise une fonction plus générique, la variable récupérant le résultat fera le travail de nommage). Il faut être performant, mais pas feignant (1/2s de perdu par aller-retour, y'a pas mort d'homme), et encore moins inutile (on lit le code parce qu'on en a besoin, pas parce qu'il y a du code à lire, un code n'est pas un roman).
    • on ne justifie pas l'absence de doc par le fait que la doc puisse être imprécise (ça me fait une belle jambe) ou plus longue (et alors ? ça arrive de temps en temps et ça n'a rien d'anormal). La doc permet au développeur de ne pas avoir besoin de cherche là où c'est utilisé pour comprendre le rôle de la fonction. Le jour où tu dois corriger la fonction, tu lis sa doc pour en comprendre son cahier des charges et tu la modifie en conséquence. Si la doc est mal faite, tu fait du travail supplémentaire et tu corriges la doc pour que le suivant puisse mieux travailler. Si on devait "ne pas faire" sous prétexte que ça peut être mal fait, on ne ferait rien du tout. Quant à l'argument de la longue doc comparé au source, ça n'a juste aucun intérêt : la doc décrit la fonction comme une boîte noire. Ce que l'utilisateur a besoin de savoir doit être écrit. Cela n'empêche pas d'utiliser à l'intérieur des outils très puissants qui te permettent de faire cela en quelques lignes. Faut pas se sentir coupable de faire une doc digne de ce nom juste parce que le code est court.
    Site perso
    Recommandations pour débattre sainement

    Références récurrentes :
    The Cambridge Handbook of Expertise and Expert Performance
    L’Art d’avoir toujours raison (ou ce qu'il faut éviter pour pas que je vous saute à la gorge {^_^})

  6. #106
    Expert éminent
    Avatar de Matthieu Vergne
    Homme Profil pro
    Consultant IT, chercheur IA indépendant
    Inscrit en
    Novembre 2011
    Messages
    2 264
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France

    Informations professionnelles :
    Activité : Consultant IT, chercheur IA indépendant
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Novembre 2011
    Messages : 2 264
    Points : 7 760
    Points
    7 760
    Billets dans le blog
    3
    Par défaut
    Citation Envoyé par martopioche Voir le message
    Le problème est dans la définition de cette forme ou autre…*Lorsqu'on parle de getters et setters, dans 99% des cas, on a des implantations du type
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    setTruc(type truc) {
        this.truc = truc;
    }
    
    getTruc() {
        return this.truc;
    }
    Et c'est tout…
    Idem pour les "setters", ça n'a rien à voir avec une notion d'immuable. Exemple bateau : un compte bancaire a une propriété "solde" et ne devrait pas avoir de "setters". La valeur initiale est fixée dans le contructeur et l'évolution du solde par des méthode de débit et crédit…
    Ça dépend de ton design. Débit et crédit ne sont pas des setters car ils ne traite pas directement de la propriété solde. Typiquement, quand je fais "setSolde(x)", ça ne me fait aucun contrôle si ce n'est que la valeur de solde n'est pas fantasque (e.g. zéro OK, mais pas nul), alors que les débits et crédits sont des opérations sur le compte ne s'appliquant que sous certaines conditions. Pour moi, tu mélanges donc les responsabilités, notamment le compte et les flux : le compte n'est qu'un conteneur, disposant de getters/setters pour ses propriétés, et le flux est géré par un autre objet qui dispose d'un compte source, d'un compte cible et du montant à transférer. Cet objet utilise les getters pour s'assurer que la transaction est valide (le compte source dispose du solde nécessaire, aucun compte n'est bloqué, etc) et ensuite les setters pour enlever à l'un et ajouter à l'autre.

    Citation Envoyé par martopioche Voir le message
    Je rajouterai pour la notion d'accès que cette notion de "getters/setters" découle de la volonté de mettre les attributs en privé (en tout cas pas en public) et que je défaut de l'objet est que la visibilité est du "tout ou rien". Du coup, on arrive à la structure stupide où on a une méthode pour simplement accéder à un attribut. Ce qui est appréciable avec certains langages modernes, c'est d'avoir enfin une structure qui remédie à ça tout en étant des fonctions en étant exposé comme un attribut (Properties en Python ou les Computed Properties en Swift).
    Il y en a d'autres, des raisons, notamment le fait qu'une interface ne définit pas d'attributs, seulement des fonctions, et donc pas le choix : getters pour indiquer les propriétés abstraites, setters pour celles qui sont modifiables. De plus, si tu veux avoir différents types d'accès (variable locale, DB, reconstruction à partir d'autres valeurs, etc.) partir d'un attribut est une erreur. La fonction, tu y met ce que tu veux dedans, et tu peux la réécrire pour ne plus compter sur l'attribut de la classe parente mais utiliser ton propre accès (non je ne le recommande pas, mais c'est un court-ciruit possible si nécessaire).
    Site perso
    Recommandations pour débattre sainement

    Références récurrentes :
    The Cambridge Handbook of Expertise and Expert Performance
    L’Art d’avoir toujours raison (ou ce qu'il faut éviter pour pas que je vous saute à la gorge {^_^})

  7. #107
    Expert éminent
    Avatar de Pyramidev
    Homme Profil pro
    Développeur
    Inscrit en
    Avril 2016
    Messages
    1 470
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Haute Garonne (Midi Pyrénées)

    Informations professionnelles :
    Activité : Développeur

    Informations forums :
    Inscription : Avril 2016
    Messages : 1 470
    Points : 6 107
    Points
    6 107
    Par défaut
    Merci pour ce retour très détaillé.

    Je viens d'écrire une version améliorée du code sur laquelle je vais m'appuyer de temps en temps dans le message présent. Voici le code complet :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    /*!
     * \file  main.c
     * \brief Correction d'un exercice.
     *
     * Consigne :\n
     * Soit nbChiffres un nombre entier entre 1 et 10 passé en argument de la ligne de commande.\n
     * Afficher dans la sortie standard, dans l’ordre croissant, toutes les différentes combinaisons
     * de nbChiffres chiffres différents dans l’ordre croissant.\n
     * Par exemple, si nbChiffres == 3, l'affichage resemblera à :
     * 012 013 014 015 016 017 018 019 023 024 ... 678 679 689 789.
     */
    
    #include <assert.h>
    #include <ctype.h>
    #include <stdbool.h>
    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    
    typedef struct SuiteDeChiffresDistincsCroissants {
    	char m_chaine[10+1];
    	int  m_cacheNbChiffres; //! But : optimisation : éviter d'appeler strlen.
    } SDCDC;
    
    bool SDCDC_Invariant(const SDCDC* pThis) {
    	assert(pThis != NULL);
    	if(pThis->m_cacheNbChiffres < 1 || pThis->m_cacheNbChiffres > 10)
    		return false;
    	for(int k = 0; k < pThis->m_cacheNbChiffres; ++k)
    		if(!isdigit(pThis->m_chaine[k]))
    			return false;
    	for(int k = 0; k+1 < pThis->m_cacheNbChiffres; ++k)
    		if(pThis->m_chaine[k] >= pThis->m_chaine[k+1])
    			return false;
    	return pThis->m_chaine[pThis->m_cacheNbChiffres] == '\0';
    }
    
    void SDCDC_ConstruireAvecChiffresLesPlusPetits(SDCDC* pThis, int nbChiffres) {
    	assert(pThis != NULL);
    	assert(nbChiffres >= 1 && nbChiffres <= 10);
    	for(int k = 0; k < nbChiffres; ++k)
    		pThis->m_chaine[k] = '0' + k;
    	pThis->m_chaine[nbChiffres] = '\0';
    	pThis->m_cacheNbChiffres = nbChiffres;
    	assert(SDCDC_Invariant(pThis));
    }
    
    void SDCDC_ConvertirEnChaine(const SDCDC* pThis, char* tampon, int tailleTampon) {
    	assert(pThis != NULL);
    	assert(tailleTampon > pThis->m_cacheNbChiffres);
    	strcpy(tampon, pThis->m_chaine);
    }
    
    bool SDCDC_SuiteSuivanteAvecMemeNbChiffresExiste(const SDCDC* pThis) {
    	assert(pThis != NULL);
    	return pThis->m_chaine[0] <= ('9' - pThis->m_cacheNbChiffres);
    }
    
    //! Par exemple, la suite de chiffres qui suit "13789" est "14567".
    void SDCDC_RemplacerParSuiteSuivanteAvecMemeNbChiffres(SDCDC* pThis) {
    	assert(pThis != NULL);
    	assert(SDCDC_SuiteSuivanteAvecMemeNbChiffresExiste(pThis));
    	char* const dernierChiffre = pThis->m_chaine + pThis->m_cacheNbChiffres - 1;
    	// Etape 1 : On cherche le chiffre à incrémenter le plus à droite possible.
    	char* chiffreAIncrementer = dernierChiffre;
    	while(true)	{
    		const char valeurMaximale = '9' - (dernierChiffre - chiffreAIncrementer);
    		const bool incrementationPossible = (*chiffreAIncrementer < valeurMaximale);
    		if(incrementationPossible)
    			break;
    		--chiffreAIncrementer;
    		assert(chiffreAIncrementer >= pThis->m_chaine);
    	}
    	// Etape 2 : On incrémente le chiffre trouvé.
    	++(*chiffreAIncrementer);
    	// Etape 3 : On ajuste tous les éventuels chiffres qui sont à droite de celui
    	// qu'on vient d'incrémenter.
    	for(char* chiffreAAjuster = chiffreAIncrementer + 1; chiffreAAjuster <= dernierChiffre; ++chiffreAAjuster)
    		*chiffreAAjuster = *(chiffreAAjuster-1) + 1;
    	assert(SDCDC_Invariant(pThis));
    }
    
    void afficherDansLaSortieStandardLesCombinaisonsDeChiffresDeLExercice(int nbChiffres) {
    	assert(nbChiffres >= 1 && nbChiffres <= 10);
    	SDCDC chiffresDistincsCroissants;
    	SDCDC_ConstruireAvecChiffresLesPlusPetits(&chiffresDistincsCroissants, nbChiffres);
    	while(true) {
    		char tampon[10+1];
    		SDCDC_ConvertirEnChaine(&chiffresDistincsCroissants, tampon, sizeof(tampon));
    		printf("%s ", tampon);
    		if(!SDCDC_SuiteSuivanteAvecMemeNbChiffresExiste(&chiffresDistincsCroissants))
    			break;
    		SDCDC_RemplacerParSuiteSuivanteAvecMemeNbChiffres(&chiffresDistincsCroissants);
    	}
    }
    
    int main(int argc, char *argv[])
    {
    	if(argc < 2) {
    		printf("Erreur : pas assez d'arguments.");
    		return 1;
    	}
    	const int nbChiffres = atoi(argv[1]);
    	if(nbChiffres < 1 || nbChiffres > 10) {
    		printf("Erreur : le premier argument doit etre un nombre entre 1 et 10.");
    		return 1;
    	}
    	afficherDansLaSortieStandardLesCombinaisonsDeChiffresDeLExercice(nbChiffres);
    	return 0;
    }
    Citation Envoyé par Matthieu Vergne Voir le message
    La question étant de savoir où se trouve la limite. Soit on part du principe que c'est une question de goût, soit on établit des conventions, soit on définit des mesures objectives qui ne dépendent de personne. Alors quand-est-ce que cette liberté n'a plus lieu d'être et la création d'une sous-fonction se justifie ?
    Dans quel contexte parles-tu ? Celui où on utilise des outils d'analyse de code pour cibler à quels endroits il faudra probablement apporter des améliorations ?
    Pour simplifier, admettons que l'on prenne pour critère le nombre de lignes pour estimer si une fonction devrait être découpée en sous-fonctions même quand ces sous-fonctions ne seront pas appelées ailleurs.
    Le choix exact du nombre de lignes sera fatalement une question de goût. Du coup, il pourrait être choisi par le chef de projet. Ou alors, une solution plus démocratique serait de faire un sondage et de choisir la médiane (pas la moyenne, sinon certains donneraient un nombre trop grand ou trop petit pour influer davantage la moyenne).

    Citation Envoyé par Matthieu Vergne Voir le message
    tu réécris la chaîne de caractère fournie en entrée : si la chaîne est utilisée ailleurs, tu la corrompt. Pour un exo, pas de soucis, pour de la prod, c'est un risque en plus. Donc plutôt qu'un remplacerParChaineSuivante(...) je ferai un générerChaineSuivante(...) qui crée sa propre chaîne.
    Dans le cas présent, il est à la fois plus simple et plus performant de modifier la chaîne de départ. Cela me permet d'écrire :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    void afficherDansLaSortieStandardLesCombinaisonsDeChiffresDeLExercice(int nbChiffres) {
    	assert(nbChiffres >= 1 && nbChiffres <= 10);
    	SDCDC chiffresDistincsCroissants;
    	SDCDC_ConstruireAvecChiffresLesPlusPetits(&chiffresDistincsCroissants, nbChiffres);
    	while(true) {
    		char tampon[10+1];
    		SDCDC_ConvertirEnChaine(&chiffresDistincsCroissants, tampon, sizeof(tampon));
    		printf("%s ", tampon);
    		if(!SDCDC_SuiteSuivanteAvecMemeNbChiffresExiste(&chiffresDistincsCroissants))
    			break;
    		SDCDC_RemplacerParSuiteSuivanteAvecMemeNbChiffres(&chiffresDistincsCroissants);
    	}
    }
    Autrement, le code aurait ressemblé à :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    void afficherDansLaSortieStandardLesCombinaisonsDeChiffresDeLExercice(int nbChiffres) {
    	assert(nbChiffres >= 1 && nbChiffres <= 10);
    	SDCDC chiffresDistincsCroissants;
    	SDCDC_ConstruireAvecChiffresLesPlusPetits(&chiffresDistincsCroissants, nbChiffres);
    	while(true) {
    		char tampon[10+1];
    		SDCDC_ConvertirEnChaine(&chiffresDistincsCroissants, tampon, sizeof(tampon));
    		printf("%s ", tampon);
    		if(!SDCDC_SuiteSuivanteAvecMemeNbChiffresExiste(&chiffresDistincsCroissants))
    			break;
    		SDCDC tamponSDCDC;
    		SDCDC_ConstruireAPartirDeSuitePrecedenteAvecMemeNbChiffres(&tamponSDCDC, &chiffresDistincsCroissants);
    		SDCDC_CopierParamDroiteDansParamGauche(&chiffresDistincsCroissants, &tamponSDCDC);
    	}
    }
    Citation Envoyé par Matthieu Vergne Voir le message
    quant tu appelles une fonction qui dit "remplacer", elle est censée remplacer, mais si ta fonction se termine tranquillement sans remplacer parce qu'il n'y a plus de numéro suivant, alors ta fonction n'est pas une fonction de remplacement à proprement parler. Le soucis est que ta fonction intègre non seulement le remplacement mais aussi le contrôle de la dernière occurrence, 2 responsabilités au lieu d'une.
    En effet, c'est une grosse erreur de ma part. Je l'ai corrigée dans la nouvelle version du code.

    Citation Envoyé par Matthieu Vergne Voir le message
    Parce que je rappelle que ce que j'ai défini en premier est une abstraction (interface ou classe abstraite), dans laquelle on défini les fonctions souhaitées auxquelles on met la documentation fonctionnelle. Du coup, ce code là irait dans une implémentation de mon abstraction, qui peut dès lors disposer de sa propre documentation. Or, comme cette implémentation a justement pour objectif d'implémenter mon abstraction avec cet algo là, j'aurais décrit l'algo dans la documentation, cette fois-ci une documentation technique. Contrairement à un commentaire dans le code, qui décrit ce que le code fait déjà et qui doit donc évoluer avec celui-ci, la documentation s'écrit avant et indique donc ce que le code doit faire. Même si le code évolue, la doc elle doit rester respectée.
    Citation Envoyé par Matthieu Vergne Voir le message
    Si on veut d'abord une vision globale mais imprécise de l'algorithme puis analyser les détails, dans un premier temps, on lit la doc de l'implémentation, qui donne l'algo voire du pseudo code. Ensuite, pour connaître les choix d'implémentation concret, on lit le code.
    Citation Envoyé par Matthieu Vergne Voir le message
    on ne justifie pas l'absence de doc par le fait que la doc puisse être imprécise (ça me fait une belle jambe) ou plus longue (et alors ? ça arrive de temps en temps et ça n'a rien d'anormal).
    Un problème fréquent avec la documentation est qu'elle risque de ne pas être à jour par rapport au code. Ce problème existe déjà avec les commentaires mais je crois que c'est encore pire avec la documentation, sauf quand celle-ci a été générée à partir des commentaires. Par exemple, dans la documentation, je pourrais préciser que mon implémentation ne fait pas une seule allocation dynamique (appel à malloc ou fonction équivalente). Mais, le jour où le besoin de se complexifiera et où l'implémentation fera des allocations dynamique, cette précision alors erronée risquera d'être oubliée dans la documentation.

    En règle générale, je pense qu'il faut s'efforcer de rendre le code le plus clair possible pour que l'on puisse lire directement ce code quand on se demande comment l'implémentation a été faite. Mais on n'arrive pas toujours à écrire du code clair. Alors, pour rendre l'implémentation plus claire, on ajoute de la documentation sur l'implémentation, malgré le risque qu'elle ne soit pas à jour plus tard et induise en erreur de futurs lecteurs. Pour cette raison, je pense qu'il vaut mieux limiter cette documentation. Il y a un juste milieu à trouver, qui dépend beaucoup de la clarté du code existant.

    D'ailleurs, même une documentation à jour peut induire en erreur si elle manque de précision et que le lecteur surinterprète. Par exemple, par étourderie, on pourrait écrire quelque chose comme :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    //! \details Dans la suite de caractères de chiffres délimitée par les paramètres,
    //!          remplacer successivement chaque chiffre par le précédent+1.
    void ajusterChiffresDansIntervalleFerme(char* premierAAjuster, char* dernierAAjuster)
    {
    	for(char* chiffre = premierAAjuster - 1; chiffre <= dernierAAjuster; ++chiffre)
    		assert(isdigit(*chiffre));
    	for(char* chiffreAAjuster = premierAAjuster; chiffreAAjuster <= dernierAAjuster; ++chiffreAAjuster)
    		*chiffreAAjuster= *(chiffreAAjuster-1) + 1;
    }
    Le commentaire avant cette fonction sera dans la documentation générée automatiquement.
    Mais, à cause de l'imprécision du commentaire, si le lecteur ne lit pas le code, il risque de croire que, par exemple, si on ajuste les chiffres du 2e au dernier, "26383" devient "23749", alors que, dans la vraie implémentation, "26383" devient "23456".
    On pourrait dire que c'est de la faute du développeur qui rédige mal la documentation et non du principe de documenter en elle-même. Mais il est plus facile d'être imprécis en s'expriment en français et en anglais qu'en s'exprimant directement sous forme de code.

    Citation Envoyé par Matthieu Vergne Voir le message
    je n'aurais pas utilisé les raccourcis style boucle for sans accolades, qui se contentent de réduire le code sans le rendre plus lisible
    C'est une question de goût. Personnellement, je trouve souvent ce genre de raccourcis plus lisible. J'ai plus de code sous les yeux.

    Citation Envoyé par Matthieu Vergne Voir le message
    j'aurais évité un break ou un return en plein milieu de la fonction. Ce genre de chose, c'est précisément ce qui me fait faire une sous-fonction, même petite : une fonction contenant une boucle qui return en plein milieu quand son traitement est terminé. Pas de break t'amenant on ne sait où (pensons aux boucles imbriquées), quand on casse, on casse tout et on sort, comme ça pas d'ambiguïté.
    Quand la boucle est grande, j'évite les break. Quand elle est très petite, je n'hésite pas à écrire un break si besoin. J'utilise souvent un break quand la boucle sert à faire une recherche et que la recherche est terminée.

    Citation Envoyé par Matthieu Vergne Voir le message
    j'aurais évité un while(true), pour la simple raison que la condition d'arrêt est connue : quand le remplacement est terminé, à savoir après maximum n itérations. Préfère créer un booléen de terminaison nommé en conséquence affecté à true quand c'est terminé. Sinon un for qui va du dernier au premier caractère, avec un boolean disant si il est encore nécessaire d'incrémenter. Un bug dans le while(true) et tu es bon pour avoir une boucle infinie dure à chopper (dans un système de prod, ça freeze et c'est le branle-bas de combat, y'a plus rien qui bouge et même pas une exception générée, obligé de redémarrer comme un bourrin, ça fait pas pro du tout).
    Que l'on écrive while(!stop) ou bien while(true) avec if(stop) break;, on a autant de risques de boucle infinie : dans les deux cas, c'est qu'on n'a pas mis la variable stop à true.

    Citation Envoyé par Matthieu Vergne Voir le message
    je n'aurai pas mis de paramètre nbChiffres, car la chaîne de caractères est faite et sa taille suffit à le retrouver
    Je l'utilise pour éviter de recalculer la longueur. J'aurais dû écrire un commentaire dans le code à ce sujet. C'est fait dans la nouvelle version.

  8. #108
    Membre éclairé
    Homme Profil pro
    BTS SN IR
    Inscrit en
    Mai 2017
    Messages
    513
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 24
    Localisation : France, Saône et Loire (Bourgogne)

    Informations professionnelles :
    Activité : BTS SN IR

    Informations forums :
    Inscription : Mai 2017
    Messages : 513
    Points : 700
    Points
    700
    Par défaut
    Personnellement je commente surtout pour identifier mes différents blocks logique de code en #" commentaire :
    par exemple si je fait du Tkinter ou du PyQt j'aime bien faire ça :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    	#" button ouvrir comptabilité mensuelle
    	pushButton_ouvrir_comptabilite = QtWidgets.QPushButton(icon=QtGui.QIcon(os.path.join(ICON_DIR, "tableur.png")), text="comptabilité mensuelle", parent=central_widget)
    	pushButton_ouvrir_comptabilite.setIconSize(QtCore.QSize(48, 48))
    	grid_layout.addWidget(pushButton_ouvrir_comptabilite, 2, 1)
    Pourquoi #" et non # ? ça permet à mon IDE de savoir que c'est un block custom avec une coloration différente de #, que j'utilise dans d'autre cas de figure ... enfin normalement quoi ^^
    C'est tout bête mais si la couleur des commentaires est flashy / se différencie du code ça permet de repérer du premier coup d'œil le block

  9. #109
    Expert éminent
    Avatar de Matthieu Vergne
    Homme Profil pro
    Consultant IT, chercheur IA indépendant
    Inscrit en
    Novembre 2011
    Messages
    2 264
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France

    Informations professionnelles :
    Activité : Consultant IT, chercheur IA indépendant
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Novembre 2011
    Messages : 2 264
    Points : 7 760
    Points
    7 760
    Billets dans le blog
    3
    Par défaut
    Citation Envoyé par Pyramidev Voir le message
    Dans quel contexte parles-tu ? Celui où on utilise des outils d'analyse de code pour cibler à quels endroits il faudra probablement apporter des améliorations ?
    Je ne parle d'aucun contexte en particulier : tu parlais d'avoir une lecture plus "libre" avec une fonction de taille "modeste" utilisant des commentaires plutôt que des sous-fonctions. Je ne faisais que relever que ça n'avait pas beaucoup de sens concret et qu'il serait donc bien de préciser ta pensée. Parce qu'on ne peut pas se permettre de parler de goût perso, ce n'est pas systématisable et donc nulle recommandation ne peut être faite. Si on considère que chacun y va de ses propres goûts, alors inutile de se plaindre que tel code te semble moche ou illisible, car chacun ses goûts. Ça devient de la discussion de café. Pour parler bonnes pratiques, il faut établir une méthode réutilisable et assez précise pour être suivi. Je dis pas qu'elle doit être formelle, mais parler de taille modeste et de liberté de lecture, moi ça m'avance pas à grand chose.

    Citation Envoyé par Pyramidev Voir le message
    Pour simplifier, admettons que l'on prenne pour critère le nombre de lignes pour estimer si une fonction devrait être découpée en sous-fonctions même quand ces sous-fonctions ne seront pas appelées ailleurs.
    Le choix exact du nombre de lignes sera fatalement une question de goût.
    Tu pars de l'hypothèse que le nombre de ligne est un critère viable. Prend une hypothèse fausse, et de là tu en tires les conclusions que tu veux. Typiquement, si on reprend l'idée qu'on a une interface, qui donne la doc fonctionnelle, et une implémentation de cette interface, qui donne la doc technique avec l'algo, alors le code de la fonction doit contenir le pseudo-code de l'algo donné par la doc. De là, tu en tires les sous-fonctions nécessaires à implémenter. Y'a pas de choix perso, tu pars du pseudo code conçu avant de coder, et tu cherches à avoir un code qui garantisse que c'est bien cet algo là, et pas une variante, que tu fournis. Après quoi tu remplis tes sous-fonctions de la même manière : tu écris la doc liant cette sous-fonction à l'algo (contexte) et détails ce que l'algo nécessite que cette fonction fasse (objectif), puis tu code la sous-fonction (moyen). Si pour cela tu utilises un autre algo avec du pseudo-code de haut niveau, et bien rebelote : la sous-fonction se limite au pseudo-code de son algo, et définie des sous-sous-fonction si nécessaire.

    Est-ce qu'il faut suivre à la lettre le pseudo-code ? Oui et non. Une fois que c'est implémenté, les test doivent valider tes fonctions, l'avantage étant que tu peux tester chacun de tes algos séparément vu qu'ils ont chacun leur propre fonction. Il peut y avoir des erreurs dans le pseudo-code, donc à partir de tes tests tu les corriges. Et si tu es vraiment rigoureux, ça devrait s'arrêter là, car ton implémentation a pour objectif d'implémenter cet algo là, et tu ne peux pas être au plus près, tu ne peux donc pas satisfaire davantage cet objectif. Que faut-il faire alors si tu veux améliorer d'autres choses, typiquement pour des raisons de performance ? Et bien dans ce cas tu modifies l'algo, 2 solutions s'offrent alors :
    • ta classe vise à implémenter ton propre algo, et tu te rends compte que c'est ton algo qui est pas génial, donc tu tires tes leçons, tu reconçois ton algo, tu changes d'abord ta doc en conséquence, et tu change ensuite ton code pour respecter la doc.
    • ta classe vise à implémenter l'algo d'un autre, donc tu n'as pas à faire ta propre tambouille. Tu crées une autre classe, qui elle a pour but d'implémenter une variante qui optimise telle dimension, et qui est donc ton propre algo inspiré du premier. Tu conçois cet algo, implémente sa classe, et là tu te trouves désormais dans le premier cas, donc toute nouvelle modif de ton algo viendra modifier la doc puis le code. Si tu t'es contenté d'implémenter un autre algo mais toujours d'une autre personne, alors tu rajoutes juste une classe pour cet algo basée sur son pseudo-code, et tu recréera une autre classe si ça ne te va toujours pas. Le choix de l'implémentation à utiliser se fera en fonction du contexte, et sera décidé par des tests de performance associés. Pas par des goûts persos.


    Citation Envoyé par Pyramidev Voir le message
    Dans le cas présent, il est à la fois plus simple et plus performant de modifier la chaîne de départ.
    La performance, elle ne doit être améliorée que si tu as clairement montré que c'est nécessaire, et non pas possible. Tu implémentes l'algo, tu fais des tests de montée en charge, et si ça passe laisse comme ça. Ne va pas commencer à gratter 3ms ici ou là en utilisant 3 variables au lieu d'une. Ça, c'est l'ego qui parle, qui veut se sentir fier d'avoir fait un truc "optimisé" pour satisfaire un besoin que personne n'a. C'est pas le pro qui satisfait le besoin du client. Ne va pas perdre du temps à faire une optimisation que personne ne sentira.

    Citation Envoyé par Pyramidev Voir le message
    Un problème fréquent avec la documentation est qu'elle risque de ne pas être à jour par rapport au code. Ce problème existe déjà avec les commentaires mais je crois que c'est encore pire avec la documentation, sauf quand celle-ci a été générée à partir des commentaires.
    Sans une hiérarchie, tu laisses libre court à toutes les libertés, et donc à toutes les erreurs.

    La doc vient d'abord, le code vient après la doc pour l'implémenter, les commentaires viennent après le code pour le justifier. C'est la doc qui définit ce que le code doit faire, pas l'inverse. Donc des commentaires pour générer de la doc, encore moins. Oui, en pratique on implémente très souvent avant, parce qu'on code en même temps qu'on conçoit. C'est une habitude de technicien, parce qu'on atteint d'abord ce niveau là avant d'atteindre celui d'ingénieur, parce qu'il faut maîtriser le langage avant de l'utiliser. Ça a l'avantage de pouvoir être exécuté -et donc testé- tout de suite, quand on souhaite produire du code c'est rapide et efficace. Mais comme toute solution bottom-up, tu prends le risque de nécessiter de nombreuses modifications avant d'obtenir ce dont tu as réellement besoin, besoin qui ne vient pas du code mais des décideurs, donc top-down. Bottom-up n'est à faire que si tu est vraiment incapable de faire une vrai conception avant, comme avec un problème nouveau ayant peu d'info accessible ou un problème trop complexe nécessitant d'y aller par itérations. Si tu en as la capacité, tu fais d'abord une conception de haut niveau (indépendante du code pour rester au plus proche du métier, et non des habitudes techniques du code), tu en déduis la doc qui donne le cahier des charges de la fonction, tu en déduis le code et les sous-fonctions, et tu recommences récursivement jusqu'à avoir un code totalement exécutable. Si les circonstances font que tu as du faire du bottom-up, tu ne dois pas oublier de tirer tes leçons du code produit, en tirer les besoins haut niveau que tu as eu besoin de satisfaire, et générer la doc à partir de cela, et non à partir du code directement.

    Citation Envoyé par Pyramidev Voir le message
    Par exemple, dans la documentation, je pourrais préciser que mon implémentation ne fait pas une seule allocation dynamique (appel à malloc ou fonction équivalente). Mais, le jour où le besoin de se complexifiera et où l'implémentation fera des allocations dynamique, cette précision alors erronée risquera d'être oubliée dans la documentation.
    Faux : soit ta fonction a une doc qui définie un besoin plus haut niveau, auquel cas tu peux modifier ta fonction sans modifier la doc, soit ta fonction limite effectivement cela, et dans ce cas il te faut faire une autre fonction avec un autre cahier des charges. Car si tu as ça dans la doc, c'est que c'est une exigences d'au moins un appelant, il ne faut donc pas le casser. Si ce n'est pas une exigence, alors ce n'est qu'un choix d'implémentation qui peut être changé, et n'a donc rien à faire dans la doc.

    On peut voir cela autrement, car après tout cette notion de hiérarchie est un choix de méthode et non un absolu. Le fait est que tu as raison sur ce point : si tu mets des informations relatives à des choix d'implémentation dans la doc, alors la documentation devient dépendante du code, et donc doit évoluer avec lui. De là il s'agit d'une conséquence logique : si tu parts du principe qu'il faut documenter (pas commenter, documenter) son code, et que cette doc dépend du code, alors tu t'imposes un double boulot. Soit tu l'acceptes (on doit bien pouvoir trouver des contextes très particuliers le justifiant), soit tu le refuses (le cas qui me semble le plus général). Si tu le refuses, il te faut retirer l'une des deux contraintes : soit tu ne fais plus de doc, soit tu ne la fait plus dépendre du code. Si on part du principe que la doc sert à l'utilisateur pour qu'il sache comment utiliser la fonction, alors la doc est nécessaire, de là on fait le choix logique de ne pas la faire dépendre du code pour minimiser son travail. De là on en tire que dès lors qu'on souhaite comprendre le code, soit celui-ci est autodescriptif, soit il faut le commenter. De là, on retrouve la problématique de mise à jour mais pour les commentaires, et donc on se repose la même question : soit on ne fait pas de commentaires, soit on ne le fait pas dépendre du code. Comme cette fois-ci on vise justement à décrire le code, on ne peut pas le rendre indépendant, amenant donc à préférer logiquement l'absence de commentaire... et donc le code autodescriptif. Si tu pars d'hypothèses différentes, tu changes de paradigme, mais ça doit se justifier.

    Citation Envoyé par Pyramidev Voir le message
    En règle générale, je pense qu'il faut s'efforcer de rendre le code le plus clair possible pour que l'on puisse lire directement ce code quand on se demande comment l'implémentation a été faite. Mais on n'arrive pas toujours à écrire du code clair. Alors, pour rendre l'implémentation plus claire, on ajoute de la documentation sur l'implémentation, malgré le risque qu'elle ne soit pas à jour plus tard et induise en erreur de futurs lecteurs. Pour cette raison, je pense qu'il vaut mieux limiter cette documentation. Il y a un juste milieu à trouver, qui dépend beaucoup de la clarté du code existant.
    L'idée en général est d'en faire du commentaire, et non de la doc, de façon à ce que ça reste au plus proche du code et donc qu'on ait le maximum de chance de le voir et de le changer avec lui. Maintenant, comme tu le dis, on n'est pas toujours capable de "faire" du code clair, comprendre d'arriver à un moment où le code n'est pas encore très clair mais on doit passer à autre chose. Auquel cas, je suggère de rajouter des TODO (au travers de commentaires, mais l'avantage est que l'EDI peut les retrouver et les afficher pour que le dév y pense, ça devient donc un outil actif et non passif). Ces TODO visent alors à souligner les défauts restants (manque de clarté, partie à optimiser, etc.) et à suggérer des améliorations si possible. Le jour où quelqu'un a le temps d'y revenir, le travail s'en retrouve grandement facilité. Bien entendu, cela présuppose qu'il y a un temps dédié à la révision du code. Soit on s'organise pour, soit on se contentera d'accumuler les TODO. Il faut savoir rester réaliste et avoir un patron comprenant les enjeux sur le long terme d'avoir un code clair. On pourra toujours dire que pour une fonction particulière, qu'on fait à la va vite pour faire une solution temporaire, n'a pas besoin qu'on y revienne, mais il me semble néanmoins nécessaire de lui foutre un TODO pour dire qu'elle est là pour régler le problème X, et donc qu'elle est à retirer le jour où celui-ci sera réglé autrement.

    Citation Envoyé par Pyramidev Voir le message
    D'ailleurs, même une documentation à jour peut induire en erreur si elle manque de précision et que le lecteur surinterprète.
    Ça me fait une belle jambe. Et tu sais quoi ? Le cahier des charges, tu peux le surinterpréter aussi. Alors on fait quoi ? On supprime le cahier des charges ? On ne règle pas un problème en le rendant invisible. Ta documentation sert à quelque chose, il ne s'agit pas de la supprimer si tu n'as rien d'autre pour fournir ce qu'elle est censée fournir. Si elle se contente de décrire le code, je suis tout à fait d'accord, mais depuis le temps que je répète que ce n'est pas son job, ça doit être claire que ça ne suffit pas pour la retirer. Par contre cet argument tient tout à fait pour le commentaire.

    D'ailleurs, la doc comme le cahier des charges doit être plus haut niveau que le code, donc tu as nécessairement une surinterprétation à faire, car tu dois choisir ce que tu utilises dans ton langage pour satisfaire les exigences décrites par ta doc/ton cahier des charges. C'est là que ton expertise de dév est censée s'exprimer (et ce que certains essayent d'automatiser au travers de techniques d'IA). Pour autant, ces choix que tu fais n'ont rien à faire dans la doc, et peuvent évoluer si tu trouves de meilleures solutions, des libs qui font mieux le boulot, ou si le langage évolue avec des nouvelles pratiques plus intéressantes que les anciennes. Pour autant, ce ne sont pas ces évolutions là qui dicteront à la fonction son rôle, c'est les besoins des fonctions appelantes et, a fortiori, du cahier des charges général et des choix d'implémentation intermédiaires qui ont été fait. Si tu fais une étourderie dans ta doc, tu dois réfléchir à une méthode pour éviter ce genre d'étourderie à l'avenir, pas te dire que le code te sauvera, car rien n'empêche de faire une étourderie dans le code non plus. Tu choisis celui qui a la priorité et tu réfléchis aux méthodes à utiliser pour garantir la qualité du travail effectué.

    Citation Envoyé par Pyramidev Voir le message
    On pourrait dire que c'est de la faute du développeur qui rédige mal la documentation et non du principe de documenter en elle-même. Mais il est plus facile d'être imprécis en s'expriment en français et en anglais qu'en s'exprimant directement sous forme de code.
    Il est plus facile d'être précis avec un langage formel que naturel, c'est sûr, pour autant on est tous d'accord qu'il n'y a pas (encore ?) de langage formel que tout le monde parle tous les jours. À un niveau ou à un autre, tu devras donc forcément faire le lien entre les deux. Si on fait une doc pour être lue par un usager humain, alors on décrit les contextes d'application et objectifs en langage naturel. Le code sera pour le codeur et le compilo. Le compilo n'ayant aucune exigence sur la sémantique du code (convention de nommage, tout ça), on se focalisera sur les exigences du codeur, qui lui a besoin de traiter de concepts et relations liés au métier qui le concerne pour bosser correctement.

    Citation Envoyé par Pyramidev Voir le message
    Quand la boucle est grande, j'évite les break. Quand elle est très petite, je n'hésite pas à écrire un break si besoin. J'utilise souvent un break quand la boucle sert à faire une recherche et que la recherche est terminée.
    Place mal ton break, ça compile mais ça fout le bronx, et dur de logger ça. Place ta boucle dans une fonction dédiée et remplace ton break par un return, y'a plus d'ambiguïté ni d'erreur possible sur le niveau auquel il faut retourner : celui de la fonction appelante. Les seuls inconvénients que je vois sont : un appel de plus dans la pile, et la nécessité de ne retourner que une valeur max, et donc de créer un objet dédié le cas échéant. dans les deux cas, ça s'optimise que si y'a besoin (e.g. appels récursifs nombreux remplissant la pile, appels itératifs nombreux remplissant la mémoire). L'optimisation, ça vient après avoir un code qui marche. Donc je ne vais pas te crucifier pour un break, mais pour moi ça doit se justifier.

    Citation Envoyé par Pyramidev Voir le message
    Que l'on écrive while(!stop) ou bien while(true) avec if(stop) break;, on a autant de risques de boucle infinie : dans les deux cas, c'est qu'on n'a pas mis la variable stop à true.
    Je suis d'accord. J'aurai dû suggérer uniquement le for pour ce cas, justement parce qu'on sait qu'il y a n itérations max. Un while ne devrait pas être utilisé dans ce cas, même avec une variable/fonction.

    Citation Envoyé par Pyramidev Voir le message
    Je l'utilise pour éviter de recalculer la longueur. J'aurais dû écrire un commentaire dans le code à ce sujet.
    De la doc, pas du commentaire, car ça fait partie de la signature de la fonction et donc de ce qui est exposé à l'utilisateur. Ce genre de choix dépend soit de ta conception, soit des besoins de la fonction appelante. Dans le premier cas, tu devrais préférer réduire tant que faire se peut les informations à demander, pour minimiser les sources d'erreurs (un petit traitement juste avant qui réduit un peu trop le n et ça peut boguer sans rien dire). La redondance ici sert à l'optimisation des ressources, qui doivent être justifiées par la pratique. Dans le second cas, c'est exigé par la fonction appelante, et donc c'est elle qui décide, ce qui renvoie la question au niveau de la fonction appelante : si elle utilise la même signature, rebelote, sinon c'est qu'elle a eu besoin de représenter ses infos sous cette forme et donc la sous-fonction doit suivre.

    Citation Envoyé par flapili Voir le message
    Personnellement je commente surtout pour identifier mes différents blocks logique de code en #" commentaire :
    par exemple si je fait du Tkinter ou du PyQt j'aime bien faire ça :
    Pourquoi #" et non # ? ça permet à mon IDE de savoir que c'est un block custom avec une coloration différente de #, que j'utilise dans d'autre cas de figure ... enfin normalement quoi ^^
    C'est tout bête mais si la couleur des commentaires est flashy / se différencie du code ça permet de repérer du premier coup d'œil le block
    Tant que tu bosses tout seul ça va, sinon faut s'assurer que ça soit OK pour l'équipe aussi. Y'a des habitudes qui dépendent de l'EDI, ce qui n'est pas recommandé car un EDI est avant tout un choix perso. Une boîte peut suggérer un EDI, mais tant que tu es capable d'avoir les même fonctionnalités tu choisis ce que tu veux. Du coup, ton truc marche pas forcément avec un autre EDI et donc serait difficilement une recommandation générale.
    Site perso
    Recommandations pour débattre sainement

    Références récurrentes :
    The Cambridge Handbook of Expertise and Expert Performance
    L’Art d’avoir toujours raison (ou ce qu'il faut éviter pour pas que je vous saute à la gorge {^_^})

  10. #110
    Membre éclairé
    Profil pro
    Inscrit en
    Décembre 2006
    Messages
    614
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Décembre 2006
    Messages : 614
    Points : 713
    Points
    713
    Par défaut
    Citation Envoyé par Matthieu Vergne Voir le message
    Ça dépend de ton design.
    Ah ! Alors pour l'objet, tout à fait d'accord, l'erreur la plus récurrente de la conception objet est de modéliser, globalement, le métier et non le "problème". Mais là, on peut supposer un système stateful

    Débit et crédit ne sont pas des setters car ils ne traite pas directement de la propriété solde. Typiquement, quand je fais "setSolde(x)", ça ne me fait aucun contrôle si ce n'est que la valeur de solde n'est pas fantasque (e.g. zéro OK, mais pas nul), alors que les débits et crédits sont des opérations sur le compte ne s'appliquant que sous certaines conditions.
    Tout à fait, et je crois que ma formulation prête à confusion car justement, dans mon "exemple bateau", l'illustration est que l'objet n'a pas de setters et ne dois surtout pas en avoir et n'est pas non plus immuable.

    Pour moi, tu mélanges donc les responsabilités, notamment le compte et les flux : le compte n'est qu'un conteneur, disposant de getters/setters pour ses propriétés, et le flux est géré par un autre objet qui dispose d'un compte source, d'un compte cible et du montant à transférer. Cet objet utilise les getters pour s'assurer que la transaction est valide (le compte source dispose du solde nécessaire, aucun compte n'est bloqué, etc) et ensuite les setters pour enlever à l'un et ajouter à l'autre.
    Et bien non, il n'y a aucune confusion et justement une répartition des responsabilités d'une part et l'implémentations des fondamentaux de l'Objet. Bien évidemment, un objet de ce type n'existe pas de manière isolée, bien entendu, il est associé à un autre composant (pas forcément objet, je n'en sais rien) qui gérera la demande de crédit/débit. Mais ce n'est pas à cet autre objet de gérer la responsabilité du type de transaction. Dans ton exemple, cet objet "gestionnaire", dans ton contexte, doit récupérer des donnée des deux comptes, faire le calcul et les réattribuer. Bien. Maintenant, il récupère les données, fait le calcul, un des comtes vient d'être bloqué, il attribue les données… Il y a un problème de cohérence puisqu'à un compte bloqué, tu a modifié le solde… Ok, tu va ajouter un contrôle et une exception au setter, mais va m'expliquer pourquoi la transaction ne s'est pas réalisée alors que le comte n'était pas bloqué, puisque le flag "bloqué" était à false lorsque tu l'a récupéré…
    Allez, on complique : pourquoi tu change le solde du compte débité ? À quel moment ai-je dit que le compte était un compte à débit immédiat ? Alors oui, selon ton implémentation, tu peux ajouter un flag et déléguer la responsabilité à l'objet de gestion… Mais ça voudrait dire exposer la manière dont je gère la notion de "débit différé".*Bref, c'est pour qu'il y a des fondamentaux de l'objet, et ici, l'encapsulation.

    Je persiste, les objets "conteneurs", ça ne sert justement que pour des flux de données pour certains patterns discutables (DTO par exemple). Dans un code "métier", ils sont souvent le signe que les responsabilités sont mal définies.

    Il y en a d'autres, des raisons, notamment le fait qu'une interface ne définit pas d'attributs, seulement des fonctions
    Heu, oui, ça tombe bien, une interface a pour but de définir un contrat de service entre un objet et un autre. Donc oui, des données sont retournées par des fonctions. Mais si tu a une interface, tu a une abstraction, si tu a une abstraction, personne ne peut dire que tes services sont des getters et setters.

    De plus, si tu veux avoir différents types d'accès (variable locale, DB, reconstruction à partir d'autres valeurs, etc.) partir d'un attribut est une erreur. La fonction, tu y met ce que tu veux dedans, et tu peux la réécrire pour ne plus compter sur l'attribut de la classe parente mais utiliser ton propre accès (non je ne le recommande pas, mais c'est un court-ciruit possible si nécessaire).
    Tout à fait, et ça va avec le fait que j'ai rappelé à propos de Python et Swift qui permettent cela pour une donnée exposée sur forme d'attribut ce qui est beaucoup plus cohérent et moderne par rapport à l'époque où les bases de l'objet ont été posées.

  11. #111
    Membre éclairé
    Profil pro
    Inscrit en
    Décembre 2006
    Messages
    614
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Décembre 2006
    Messages : 614
    Points : 713
    Points
    713
    Par défaut
    Pour être honnête, je n'ai pas lu les pavés précédents. Je ne vais pas paraphraser Matthieu pour la plupart des réponses. Cependant, je vais réagir à :


    Citation Envoyé par Matthieu Vergne Voir le message
    La doc vient d'abord, le code vient après la doc pour l'implémenter, les commentaires viennent après le code pour le justifier. C'est la doc qui définit ce que le code doit faire, pas l'inverse. Donc des commentaires pour générer de la doc, encore moins. Oui, en pratique on implémente très souvent avant, parce qu'on code en même temps qu'on conçoit. C'est une habitude de technicien, parce qu'on atteint d'abord ce niveau là avant d'atteindre celui d'ingénieur, parce qu'il faut maîtriser le langage avant de l'utiliser. Ça a l'avantage de pouvoir être exécuté -et donc testé- tout de suite, quand on souhaite produire du code c'est rapide et efficace. Mais comme toute solution bottom-up, tu prends le risque de nécessiter de nombreuses modifications avant d'obtenir ce dont tu as réellement besoin, besoin qui ne vient pas du code mais des décideurs, donc top-down. Bottom-up n'est à faire que si tu est vraiment incapable de faire une vrai conception avant, comme avec un problème nouveau ayant peu d'info accessible ou un problème trop complexe nécessitant d'y aller par itérations. Si tu en as la capacité, tu fais d'abord une conception de haut niveau (indépendante du code pour rester au plus proche du métier, et non des habitudes techniques du code), tu en déduis la doc qui donne le cahier des charges de la fonction, tu en déduis le code et les sous-fonctions, et tu recommences récursivement jusqu'à avoir un code totalement exécutable. Si les circonstances font que tu as du faire du bottom-up, tu ne dois pas oublier de tirer tes leçons du code produit, en tirer les besoins haut niveau que tu as eu besoin de satisfaire, et générer la doc à partir de cela, et non à partir du code directement.
    J'ai l'impression de lire un manuel scolaire. Tu a des exemples de mise en œuvre ? Car coté théorique, on est d'accord, tu vends du rêve. Moi, en 20 ans, je n'ai jamais vu. Pas vraiment parce que tout le monde bosse comme un sagouin ou une différence entre un "technicien" et un "ingénieur" mais parce que :
    - la demande évolue en cours de dev, donc la spec, donc la doc…
    - Il est rare que celui qui modélise soit celui qui réalise, de ce fait, il y a souvent des contraintes qu'on n'avait pas prévu en modélisation
    - cette théorie peut encore s'appliquer pour un nouveau projet. Quand on interagit avec de l'existant, il faut s'attendre à ce que l'attendu ne soit pas conforme…

    Depuis une quinzaine d'années, mes projets ont toujours été plus efficaces avec une approche où on faisait émerger le modèle que l'inverse. De ce fait, la doc s'écrit en deux temps : d'une manière succincte à la déclaration des composants et de manière complète avant publication.

    Ça me fait une belle jambe. Et tu sais quoi ? Le cahier des charges, tu peux le surinterpréter aussi. Alors on fait quoi ? On supprime le cahier des charges ? On ne règle pas un problème en le rendant invisible. Ta documentation sert à quelque chose, il ne s'agit pas de la supprimer si tu n'as rien d'autre pour fournir ce qu'elle est censée fournir.

    […]

    D'ailleurs, la doc comme le cahier des charges doit être plus haut niveau que le code, donc tu as nécessairement une surinterprétation à faire, car tu dois choisir ce que tu utilises dans ton langage pour satisfaire les exigences décrites par ta doc/ton cahier des charges.
    Sur cette partie, je dirai qu'il a un peu raison, et que la réponse, c'est qu'une doc, ça vit autant que le code. Si elle prête à confusion, elle mérite d'être réécrite. On devrait autant pouvoir ouvrir un ticket pour une maintenance de doc que de code.

  12. #112
    Expert éminent
    Avatar de Matthieu Vergne
    Homme Profil pro
    Consultant IT, chercheur IA indépendant
    Inscrit en
    Novembre 2011
    Messages
    2 264
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France

    Informations professionnelles :
    Activité : Consultant IT, chercheur IA indépendant
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Novembre 2011
    Messages : 2 264
    Points : 7 760
    Points
    7 760
    Billets dans le blog
    3
    Par défaut
    Citation Envoyé par martopioche Voir le message
    J'ai l'impression de lire un manuel scolaire. Tu a des exemples de mise en œuvre ? Car coté théorique, on est d'accord, tu vends du rêve. Moi, en 20 ans, je n'ai jamais vu.
    Et ? Tu as tout vu ?

    C'est comme ça que je travaille. Ça te suffit ou faut en plus prouver que c'est ce que ça n'est pas ? Je ne sais plus si c'est dans cette discussion que je l'ai dit, mais encore une fois il ne faut pas mélanger constat et objectif : le premier est comme il est, le second est ce vers quoi on souhaite converger. Que tu me dises que côté théorique on est d'accord, ça suffit à me donner raison. Je dis déjà que dans la pratique on ne fait pas souvent comme ça, donc je ne vois pas où tu me contredis. en l'occurrence :
    Citation Envoyé par martopioche Voir le message
    - la demande évolue en cours de dev, donc la spec, donc la doc…
    ... donc le code et les commentaires qui vont avec. C'est ce que je dis.

    Citation Envoyé par martopioche Voir le message
    - Il est rare que celui qui modélise soit celui qui réalise, de ce fait, il y a souvent des contraintes qu'on n'avait pas prévu en modélisation
    ... et qui seront donc soit revues directement dans le code si on va trop vite, soit revues d'abord dans le modèle pour ne pas se lancer sans en avoir bien analysé l'impact potentiel.

    Citation Envoyé par martopioche Voir le message
    - cette théorie peut encore s'appliquer pour un nouveau projet. Quand on interagit avec de l'existant, il faut s'attendre à ce que l'attendu ne soit pas conforme…
    Encore une fois, l'existant est comme il est, on fait ce qu'on peu avec ce qu'on a. On parle de bonnes pratiques actuelles, pas d'histoire.

    Citation Envoyé par martopioche Voir le message
    Depuis une quinzaine d'années, mes projets ont toujours été plus efficaces avec une approche où on faisait émerger le modèle que l'inverse.
    Les deux sont utiles. Le top-down est nécessaire pour avoir la vision globale qui entretient la cohérence du tout, le bottom-up pour le confronter aux réalités techniques. Ça ne change rien au fait qu'on puisse donner un rôle précis à la doc et aux commentaires et s'organiser pour les gérer de manière systématique.

    Citation Envoyé par martopioche Voir le message
    De ce fait, la doc s'écrit en deux temps : d'une manière succincte à la déclaration des composants et de manière complète avant publication.
    Ce qui, encore une fois, ne remet pas en cause ce que je dis : ta première version vient du top-down et décrit le besoin identifier au départ, ta seconde version vient encore du top-down qui a été corrigé grâce à la confrontation faite lors du bottom-up. Tu as codé, vu que ce que tu avais modélisé n'était pas le mieux, revue ton modèle en conséquence et donc changer la doc en conséquence. Mais tu n'as pas écrit ta doc en fonction du code directement. Si tu modifies ta doc sur la base du code, tu n'en étudie pas l'impact sur les dépendances. Pour des modifs mineures, ça passe, pour des grosses modifs, c'est clairement une prise de risque qu'il vaut mieux s'éviter.

    Citation Envoyé par martopioche Voir le message
    Sur cette partie, je dirai qu'il a un peu raison, et que la réponse, c'est qu'une doc, ça vit autant que le code. Si elle prête à confusion, elle mérite d'être réécrite. On devrait autant pouvoir ouvrir un ticket pour une maintenance de doc que de code.
    Encore une fois, relis ce que tu as cité et tu verras que je parle déjà de revoir la doc, notamment la dernière phrase :
    tu ne dois pas oublier de tirer tes leçons du code produit, en tirer les besoins haut niveau que tu as eu besoin de satisfaire, et générer la doc à partir de cela, et non à partir du code directement.
    On dirait que tu contredis juste les 3 premières phrases sans tenir compte de tout ce qui suit.
    Site perso
    Recommandations pour débattre sainement

    Références récurrentes :
    The Cambridge Handbook of Expertise and Expert Performance
    L’Art d’avoir toujours raison (ou ce qu'il faut éviter pour pas que je vous saute à la gorge {^_^})

  13. #113
    Expert éminent
    Avatar de Matthieu Vergne
    Homme Profil pro
    Consultant IT, chercheur IA indépendant
    Inscrit en
    Novembre 2011
    Messages
    2 264
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France

    Informations professionnelles :
    Activité : Consultant IT, chercheur IA indépendant
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Novembre 2011
    Messages : 2 264
    Points : 7 760
    Points
    7 760
    Billets dans le blog
    3
    Par défaut
    Citation Envoyé par martopioche Voir le message
    il récupère les données, fait le calcul, un des comtes vient d'être bloqué, il attribue les données… Il y a un problème de cohérence puisqu'à un compte bloqué, tu a modifié le solde… Ok, tu va ajouter un contrôle et une exception au setter, mais va m'expliquer pourquoi la transaction ne s'est pas réalisée alors que le comte n'était pas bloqué, puisque le flag "bloqué" était à false lorsque tu l'a récupéré…
    Non, là on touche à du multi-threading, et donc pour assurer la cohérence de ton objet tu peux mettre en place un système de lock, que ce soit un simple synchronized sur les comptes en question ou des mécanismes plus complets si tu dois aussi gérer du multi-processus/multi-clients.

    Citation Envoyé par martopioche Voir le message
    Allez, on complique : pourquoi tu change le solde du compte débité ? À quel moment ai-je dit que le compte était un compte à débit immédiat ? Alors oui, selon ton implémentation, tu peux ajouter un flag et déléguer la responsabilité à l'objet de gestion… Mais ça voudrait dire exposer la manière dont je gère la notion de "débit différé".
    Encore une fois, tu choisis un design pourris en partant du principe que c'est forcément le mien et me retourne la faute. Qu'on m'explique le concept et je ferais un design en conséquence. N'étant pas banquier et n'utilisant pas de carte à débit différé, si je me fie à la description Wikipédia, il n'y a pas de contrôle de solde et le débit est simplement effectué à une date précise. Auquel cas l'opération en question visera non pas à modifier le solde du compte mais à ajouter une opération associée à la carte de débit du compte. C'est lors d'une tâche régulière sur les cartes de débit que les opérations de ce type seraient alors appliquées à leurs comptes. Si un événement fait passer la carte de débit différé à débit immédiat, les opérations restantes sont appliquées dans la foulée (à supposer que ce soit comme ça que ça doivent fonctionner). Encore une fois, on ne change pas la responsabilité du conteneur et le flux est géré par un objet qui prend en charge l'ajout de l'opération. Mais encore une fois, c'est en design à la lumière de ce petit paragraphe Wikipédia.

    Citation Envoyé par martopioche Voir le message
    Heu, oui, ça tombe bien, une interface a pour but de définir un contrat de service entre un objet et un autre. Donc oui, des données sont retournées par des fonctions. Mais si tu a une interface, tu a une abstraction, si tu a une abstraction, personne ne peut dire que tes services sont des getters et setters.
    Tu n'utilises pas une interface sans savoir à quoi elle sert. Si elle définie un setter, tu sais qu'elle a un setter. C'est pas parce qu'on parle d'abstraction qu'on parle d'un truc dont on ne connais rien. On connaît seulement ce qui se place à ce niveau d'abstraction, pas en dessous. Si ton interface fournit un "setX(int x)" va falloir une doc bien écrite pour expliquer pourquoi ce n'est pas un setter.
    Site perso
    Recommandations pour débattre sainement

    Références récurrentes :
    The Cambridge Handbook of Expertise and Expert Performance
    L’Art d’avoir toujours raison (ou ce qu'il faut éviter pour pas que je vous saute à la gorge {^_^})

  14. #114
    Membre éclairé
    Profil pro
    Inscrit en
    Décembre 2006
    Messages
    614
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Décembre 2006
    Messages : 614
    Points : 713
    Points
    713
    Par défaut
    Citation Envoyé par Matthieu Vergne Voir le message
    Et ? Tu as tout vu ?
    Heu…*Non…*Et c'est bien pour ça que je pose la question "Tu a des exemples de mise en œuvre ?"…

    Je ne sais plus si c'est dans cette discussion que je l'ai dit, mais encore une fois il ne faut pas mélanger constat et objectif : le premier est comme il est, le second est ce vers quoi on souhaite converger. Que tu me dises que côté théorique on est d'accord, ça suffit à me donner raison.
    Ce n'était pas à moi car justement j'insistais sur cette différence. Mais ici, de quel objectif parlons nous ? Moi je reste sur le sujet (avoir une doc qui va avec l'implémentation).

    Je dis déjà que dans la pratique on ne fait pas souvent comme ça, donc je ne vois pas où tu me contredis.
    Je ne contredis pas sur la pratique mais sur la qualification de l'approche (bottom-up = approche de technicien inexpérimenté, top-to-bottom = approche d'ingénieur maitrisant le langage). De ce fait, en soi, tu inverse toutes les notions, la maitrise du langage ne permet pas de faire de "meilleurs" modèles mais de meilleurs implémentations.

    et qui seront donc soit revues directement dans le code si on va trop vite, soit revues d'abord dans le modèle pour ne pas se lancer sans en avoir bien analysé l'impact potentiel.
    Si on va trop vite…*Il y a quelques lignes tu disait travailler, comme ça, quel est le TTM pour tes projets ? Parce qu'être dans un contexte où il faut une analyse complète pour chaque incohérence de conception, on est exactement dans le schéma des bon vieux cycles en V qui ne conduisent qu'à des explosions de budgets, livraisons hors délais et à l'adéquation à la demande discutable…

    Encore une fois, l'existant est comme il est, on fait ce qu'on peu avec ce qu'on a. On parle de bonnes pratiques actuelles, pas d'histoire.
    Je ne parle pas d'avoir les pratiques de l'existant mais de prendre compte de l'existant dans son chef-d'œuvre… Je veux dire sa conception. Exactement ce que u paraphrase avec

    Les deux sont utiles. Le top-down est nécessaire pour avoir la vision globale qui entretient la cohérence du tout, le bottom-up pour le confronter aux réalités techniques.
    Ça ne change rien au fait qu'on puisse donner un rôle précis à la doc et aux commentaires et s'organiser pour les gérer de manière systématique.
    Non, ça tombe bien, je n'ai rien dit qui va à l'encontre…

    Ce qui, encore une fois, ne remet pas en cause ce que je dis : ta première version vient du top-down et décrit le besoin identifier au départ, ta seconde version vient encore du top-down qui a été corrigé grâce à la confrontation faite lors du bottom-up. Tu as codé, vu que ce que tu avais modélisé n'était pas le mieux, revue ton modèle en conséquence et donc changer la doc en conséquence.
    Ben non, justement…*Si le modèle a été corrigé du fait de la confrontation avec la réalité de l'implémentation, c'est du bottom-up…

    Mais tu n'as pas écrit ta doc en fonction du code directement. Si tu modifies ta doc sur la base du code, tu n'en étudie pas l'impact sur les dépendances. Pour des modifs mineures, ça passe, pour des grosses modifs, c'est clairement une prise de risque qu'il vaut mieux s'éviter.
    La doc doit correspondre au code, donc elle est finalisée sur les bases de ce code. Quand à l'impact sur les dépendances…*On ne parle pas de basculer en free-style…*Et une "étude" ne changera pas grand chose, si tu veux éviter les régressions, c'est les tests…

    On dirait que tu contredis juste les 3 premières phrases sans tenir compte de tout ce qui suit.
    Alors tu te contredis dans tes différentes parties…

  15. #115
    Membre éclairé
    Profil pro
    Inscrit en
    Décembre 2006
    Messages
    614
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Décembre 2006
    Messages : 614
    Points : 713
    Points
    713
    Par défaut
    Oulà, alors là…

    Citation Envoyé par Matthieu Vergne Voir le message
    Non, là on touche à du multi-threading, et donc pour assurer la cohérence de ton objet tu peux mettre en place un système de lock, que ce soit un simple synchronized sur les comptes en question ou des mécanismes plus complets si tu dois aussi gérer du multi-processus/multi-clients.
    Attends…*Lock de quoi ? De l'objet compte ? Mais le métier ne consiste pas à gérer du multi-threading, juste à faire une opération si elle est possible…

    Encore une fois, tu choisis un design pourris en partant du principe que c'est forcément le mien et me retourne la faute. Qu'on m'explique le concept et je ferais un design en conséquence. N'étant pas banquier et n'utilisant pas de carte à débit différé, si je me fie à la description Wikipédia, il n'y a pas de contrôle de solde et le débit est simplement effectué à une date précise. Auquel cas l'opération en question visera non pas à modifier le solde du compte mais à ajouter une opération associée à la carte de débit du compte. C'est lors d'une tâche régulière sur les cartes de débit que les opérations de ce type seraient alors appliquées à leurs comptes. Si un événement fait passer la carte de débit différé à débit immédiat, les opérations restantes sont appliquées dans la foulée (à supposer que ce soit comme ça que ça doivent fonctionner). Encore une fois, on ne change pas la responsabilité du conteneur et le flux est géré par un objet qui prend en charge l'ajout de l'opération.
    Waw…*Je crois que juste avec cette remarque, tu viens de violer tous les principes de la POO… Je ne t'ai pas demandé d'être banquier, ma demande n'a rien à voir avec une réalité bancaire, je n'ai jamais parlé de cartes. Il s'agit simplement de faire des opérations de crédit et de débit, tu a fait une proposition rigide qui correspondait à ton interprétation. Je te fais juste remarquer que si tu a une approche de conteneurs de données associées à un super-service qui agrège toutes les responsabilités, la moindre évolution met tout le système en péril alors que si on a une approche Objet (abstraction, encapsulation, responsabilité unique) on applique naturellement les autres principes (open/closed ici) avec un impact minime (aucun dans le cas présent) sur l'existant en lui laissant la responsabilité de l'implémentation technique (le multithreading précédent par exemple) au lieu de les déléguer aux dépendances…

    Tu m'étonne que tu a besoin d'aller-retour avec la conception pour le moindre paramètre qui ne correspond pas au design original et je comprends ton inquiétude vis à vis des dépendances…*

    Mais encore une fois, c'est en design à la lumière de ce petit paragraphe Wikipédia.
    Je pense que là, tu illustre tout le problème avec le dev de base aujourd'hui : ignorer la demande pour se conforter dans on œuvre d'Art…*J'aurai préféré un design basé sur la demande mais bon…

    Tu n'utilises pas une interface sans savoir à quoi elle sert. Si elle définie un setter, tu sais qu'elle a un setter. C'est pas parce qu'on parle d'abstraction qu'on parle d'un truc dont on ne connais rien. On connaît seulement ce qui se place à ce niveau d'abstraction, pas en dessous. Si ton interface fournit un "setX(int x)" va falloir une doc bien écrite pour expliquer pourquoi ce n'est pas un setter.
    Mais RAF que ce soit un "setter" ou non…*Lorsque je m'interface avec une cocotte, que l'implémentation de setPression (oui, je fais du franglais) soit this._pression = pression ou this._temperature = (pression * this.volume) / N * R voir même this._temperature = (pression * this.VOLUME) / N * R, m'en f… Ce n'est pas parce que j'ai un "setX" que je dois attendre ou présumer un attribut X, principe de base de l'Objet…

  16. #116
    Expert éminent
    Avatar de Matthieu Vergne
    Homme Profil pro
    Consultant IT, chercheur IA indépendant
    Inscrit en
    Novembre 2011
    Messages
    2 264
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France

    Informations professionnelles :
    Activité : Consultant IT, chercheur IA indépendant
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Novembre 2011
    Messages : 2 264
    Points : 7 760
    Points
    7 760
    Billets dans le blog
    3
    Par défaut
    Citation Envoyé par martopioche Voir le message
    Mais ici, de quel objectif parlons nous ? Moi je reste sur le sujet (avoir une doc qui va avec l'implémentation).
    Le sujet est de savoir quand commenter son code. Il y a un autre sujet sur la doc. Mais passons.

    Pour rester cohérent, moi je parle d'une perspective complète : documentation-code-commentaire, où la documentation décrit ce qui doit être assuré, le code en donne le moyen, et les commentaires en décrivent les subtilités. Que la doc et le code ne se contredisent pas, je crois que tout le monde sera d'accord, mais de ce que j'ai compris du soucis qui nous concerne, c'est que tu estimes que le code te donne les billes pour faire la doc, alors que moi j'estime que c'est au code à dépendre de la doc. Et la question se trouve là : est-ce que la doc doit être écrite en fonction du code fournit, où est-ce qu'on doit se limiter à une vision haut niveau, indépendante du code, pour établir la doc. Encore une fois, je ne remet pas en cause qu'écrire le code peut aider à se faire une meilleure idée, mais ça ne doit pas être le décideur du contenu de la doc.

    Que ce soit clair, dans la pratique les deux sont possibles, mais il s'agit là de parler de bonnes pratiques et donc de juger. Mais avant de parler langage, parlons de manière plus générale de projet.

    Si on fait du bottom-up, en laissant émerger les concepts de plus haut niveau au fur et à mesure, on s'assure effectivement d'avoir un TTM court au début, car on consomme moins de temps à rassembler des infos et à concevoir. Mais c'est une approche très technicienne : on part des outils qu'on a sous la main et on construit dessus, les concepts émergents en fonction de ce que permettent ces outils et de l'usage qu'on en fait. Le technicien maîtrise ses outils et les utilisent comme il peut pour obtenir ce qu'il veut. L'ennui de cette approche, et là je parle de vécu, est le long terme : les contextes couverts démarrent petits et vont croissant en fonction des situations auxquelles on fait face. Typiquement, c'est la logique du "j'ai jamais vu, donc j'ai jamais fait pour", et quand quelqu'un souhaite utiliser ton outils dans son contexte (différent), là tu te rends compte que ça passe mal, notamment parce que tu dois faire du code crade pour cracker l'architecture et en adapter l'usage. Et c'est là que tu sens les limites de cette approche : si tu veux étendre les facultés de ton outil pour couvrir correctement ce nouveau contexte que t'as pas prévu, il te faut faire des modifs significatives, et là ton TTM explose. Ne te reste alors qu'à admettre que t'as mal démarré et mettre les moyens pour une refonte, où se limiter à ce que tu couvres déjà même si tu reconnaît la pertinence du nouveau contexte. Ce genre d'approche est donc très bien pour de la prise en main, du prototypage, de l'expérimental, quelque chose dont tu sais qu'à la fin, tu le jetteras pour du mieux. Mais si tu veux avoir un truc qui tienne la route, tu as besoin d'une vision sur le long terme, qui implique de démarrer des fondamentaux.

    C'est l'objectif du top-down, qui démarre de la conception haut niveau. Et ça, c'est pour moi le pendant de l'ingénieur (c'est ma formation), qui va d'abord concevoir, et seulement après chercher les outils qui lui permettent de répondre à son besoin, quitte à embaucher les techniciens ayant les compétences adaptées aux outils choisis. Tu pars de l'abstraction, des contextes que tu souhaites couvrir. Non pas au travers d'exemples limités mais de propriétés que tu souhaites prendre en compte. De la même manière qu'une théorie bien rodée part d'hypothèses minimales, et non d'exemples spécifiques. Les exemples illustrent les hypothèses, mais ne les limitent pas. Et on ne restreindra ces hypothèses que si, effectivement, on ne souhaite couvrir que les contextes correspondants. Le TTM de ce genre de projet peut être énorme au départ, par contre au fur et à mesure que le projet se forme, les applications commencent à se faire et le TTM à baisser drastiquement. Et parce que le projet a été conçu pour couvrir de larges classes de contextes, qu'on a restreint au fur et à mesure selon les besoins, on n'a pas le soucis de refonte significative. Ce genre de pratique est parfait pour le long terme, mais ça implique d'avoir une vision claire de l'objectif sur le long terme, et donc d'avoir les informations nécessaires. Selon le projet, cela peut être long et difficile à obtenir, voire impossible, mais ce n'est pas nécessairement le cas.

    Mais ce que tu lis est que "le second est fondamentalement mieux que le premier", alors que ce n'est pas ce que je dis. Un exemple de mauvaise interprétation :
    Citation Envoyé par martopioche Voir le message
    Je ne contredis pas sur la pratique mais sur la qualification de l'approche (bottom-up = approche de technicien inexpérimenté, top-to-bottom = approche d'ingénieur maitrisant le langage). De ce fait, en soi, tu inverse toutes les notions, la maitrise du langage ne permet pas de faire de "meilleurs" modèles mais de meilleurs implémentations.
    Tu as lu trop vite, je me cite :
    C'est une habitude de technicien, parce qu'on atteint d'abord ce niveau là avant d'atteindre celui d'ingénieur, parce qu'il faut maîtriser le langage avant de l'utiliser.
    Toi, tu associes technicien à inexperimenté, et ingénieur à expérimenté, moi pas. C'est comme l'idée qu'un bon tech doit finir manager, et donc ne plus faire de tech, ou encore le principe de Peter. Un bon tech fait de la tech de qualité, en utilisant ses outils de manière optimisée. Tu lui donnes une spec et il te l'implémente rapidement et correctement selon les contraintes de l'outil, mais ça reste de la tech. Un ingénieur n'est pas un technicien, c'est quelqu'un qui fait de la conception avant de partir dans le code. C'est celui qui va générer les specs avant de chercher à les implémenter, par lui-même ou au travers d'un autre, potentiellement un technicien. C'est des capacités et une approche différente. On ne demande pas à un ingénieur de devenir un maître sur quelques outils, mais d'être capable de prendre en compte l'ensemble des outils de la chaîne, la maîtrise de chacun d'entre eux étant soit distribuée sur un ensemble d'ingénieurs (pratique pour éviter les pertes de compétence lors de retraite ou de démission), soit pris en charge par des techniciens dédiés.

    Citation Envoyé par martopioche Voir le message
    Si on va trop vite…*Il y a quelques lignes tu disait travailler, comme ça, quel est le TTM pour tes projets ? Parce qu'être dans un contexte où il faut une analyse complète pour chaque incohérence de conception, on est exactement dans le schéma des bon vieux cycles en V qui ne conduisent qu'à des explosions de budgets, livraisons hors délais et à l'adéquation à la demande discutable…
    Va créer un avion en agile et après on en reparlera. Pas sûr que les crashs à répétition soient rentables. Il faut savoir reconnaître les forces et faiblesses de chaque pratique pour les associer correctement. Si tu veux être rapide, fait de l'agile, mais accepte de ne pas être solide sur le long terme. Si tu veux du long terme, prévoit loin et prends le temps de te préparer. Les deux sont importants, mais ce qui te permet d'avoir du solide sur le long terme c'est une bonne conception. Un excès de cycle en V style on prépare tout avant de faire le moindre code te rends inflexible et incapable de te réorienter en cas d'erreur. Un excès d'agile style on laisse émerger la conception du code te rends incapable d'avoir une vision d'ensemble cohérente et donc amène à des refactoring fréquent, sans quoi c'est code spaghetti et hack à gogo. L'important n'est, au final, ni l'agile ni le cycle en V, qui sont les deux extrêmes (un sprint agile n'est qu'un mini cycle en V), mais savoir établir les niveaux de granularités nécessaires au projet :
    • savoir identifier les parties où on dispose d'assez d'information, qui doivent donc partir sur une conception solide pour minimiser les révisions futures et offrir un cadre stable à ce qui doit venir s'y intégrer.
    • savoir identifier les parties complexes ou non maîtrisées, qui doivent partir sur de l'agile le temps d'acquérir les données. Si rien n'empêche d'utiliser en prod si ça marche bien, partir du principe que ça fera le produit fini est une erreur.


    Ce qu'il faut bien comprendre c'est qu'on commence par le second et on fini par le premier. Ne faire que du premier amène à ne jamais rien sortir, que du second à ne jamais vraiment savoir où l'on va et à changer sans arrêt.

    Une fois qu'on a compris que les 2 sont importants, et qu'il ne s'agit pas de savoir lequel est le mieux, on revient sur la question documentation-code-commentaires. L'idée est la même : soit on a établit un cahier des charges de la méthode à implémenter, qui nous amène à écrire une doc qui va bien et ensuite le code qui le permet, soit on fait du code expérimental pour faire une fonctionnalité dont on n'a pas bien dessiné les contours, et on écrira la doc à mesure de ce qu'on comprend du problème. Les deux sont utiles, la question étant alors de savoir quelles sont les bonnes pratiques, car c'est le sujet et ce vers quoi on affirme qu'il faut converger, indépendamment des pratiques actuelles. Et bien mon point de vue est clair : la doc doit venir de la conception et être indépendante du code. S'il n'est pas interdit d'écrire la doc à partir du code, cela doit être pris comme un indice que la conception est incomplète ou mal faite. Passer outre cet indice n'amènera qu'à davantage de conflits ailleurs plutôt qu'à un tout cohérent, facile à maintenir et faire évoluer. Oui il y a de la pratique expérimentale, ou le haut niveau émerge du bas niveau, mais partir du principe que ça suffit est une erreur. À un moment donné, il faut prendre le recul suffisant pour avoir la vision d'ensemble, concevoir la solution à ce niveau, et s'assurer que ce soit appliqué sur les niveaux en dessous. Une doc écrite bottom-up à partir du code, on doit prévoir de la réécrire. Une doc écrite top-down en fonction de la conception, on doit s'appuyer dessus.

    Citation Envoyé par martopioche Voir le message
    Ben non, justement…*Si le modèle a été corrigé du fait de la confrontation avec la réalité de l'implémentation, c'est du bottom-up…
    Ça n'est du bottom-up que si tu acceptes de te limiter à ce que tu as implémenté, par exemple pour des raisons de coûts, des imprévus, etc. Le top-down consiste à en tirer les leçons haut niveau qui vont bien avec le reste de la conception, et de là tu acceptes de rejeter l'implémentation si celle-ci ne répond pas à tes objectifs de plus haut niveau, quitte à l'utiliser en tant que palliatif le temps de le refaire au propre. C'est là qu'est tout mon propos : le bottom-up fait du jetable, le top-down fait du solide. Le jetable sert, mais on doit converger vers le solide, la finalité est donc d'avoir une doc qui correspond à la conception, avec le code qui suit et non l'inverse. On peut se permettre d'écrire la doc après avoir fait évoluer le code, mais ça doit être pris comme une doc qui sera revue dans le futur, et non finale.

    Citation Envoyé par martopioche Voir le message
    La doc doit correspondre au code, donc elle est finalisée sur les bases de ce code. Quand à l'impact sur les dépendances…*On ne parle pas de basculer en free-style…*Et une "étude" ne changera pas grand chose, si tu veux éviter les régressions, c'est les tests…
    Tests qui viennent d'où ? Des contraintes établies par le niveau au dessus. Les tests, surtout si on fait du TDD, c'est l'illustration de base d'un processus top-down. D'abord tu établis tes contraintes, et ensuite tu code. Les tests sont la partie exécutive, la doc la partie descriptive.

    Citation Envoyé par martopioche Voir le message
    Attends…*Lock de quoi ? De l'objet compte ? Mais le métier ne consiste pas à gérer du multi-threading, juste à faire une opération si elle est possible…
    Je te cite :
    Bien. Maintenant, il récupère les données, fait le calcul, un des comtes vient d'être bloqué, il attribue les données… Il y a un problème de cohérence puisqu'à un compte bloqué, tu a modifié le solde…
    Vu que je t'ai déjà dit que la vérification du blocage de compte se faisait avant d'effectuer l'opération, je pars donc du principe que ce blocage intervient en pleine opération, autrement dit il existe une autre tâche en cours qui a la main sur le compte, et qui a changé son état alors que notre opération est en cours dessus. On est donc bien sur du parallélisme, qui implique donc soit du multi-threading si on est dans le même processus, soit de la gestion multi-processus. Sinon l'état du compte ne change pas comme ça.

    Citation Envoyé par martopioche Voir le message
    Waw…*Je crois que juste avec cette remarque, tu viens de violer tous les principes de la POO…
    Je t'invite donc à me dire lesquels, en PM si tu préfères éviter que le sujet se focalise là dessus.

    Citation Envoyé par martopioche Voir le message
    Je te fais juste remarquer que si tu a une approche de conteneurs de données associées à un super-service qui agrège toutes les responsabilités
    Où ais-je dis que toutes les responsabilités se retrouvaient concentrées sur un unique autre objet ? Je parle de séparer la représentation du compte de la gestion d'un flux particulier entre 2 comptes. Encore une fois, tu inventes des trucs que je n'ai pas dit et tape dessus. Faut arrêter, là.

    Citation Envoyé par martopioche Voir le message
    Je pense que là, tu illustre tout le problème avec le dev de base aujourd'hui : ignorer la demande pour se conforter dans on œuvre d'Art…*J'aurai préféré un design basé sur la demande mais bon…
    Tu as parlé d'un contexte en quelques mots. Il me semble que tu n'es pas sans savoir qu'on ne tire pas un cahier des charges de 3 mots. Pour éviter de te demander une description plus détaillée, dire de gagner du temps, je me suis appuyé sur une source comme si c'était la description du besoin, et de là j'en ai tiré des conclusions, avec les réserves d'usage étant donné les limites de la source. Pour répondre au besoin, il faut le comprendre, et pour le comprendre, il faut les infos pertinentes.

    Citation Envoyé par martopioche Voir le message
    Mais RAF que ce soit un "setter" ou non…*Lorsque je m'interface avec une cocotte, que l'implémentation de setPression (oui, je fais du franglais) soit this._pression = pression ou this._temperature = (pression * this.volume) / N * R voir même this._temperature = (pression * this.VOLUME) / N * R, m'en f… Ce n'est pas parce que j'ai un "setX" que je dois attendre ou présumer un attribut X, principe de base de l'Objet…
    Tu es le seul à parler d'attribut. Calme-toi et relis.
    Site perso
    Recommandations pour débattre sainement

    Références récurrentes :
    The Cambridge Handbook of Expertise and Expert Performance
    L’Art d’avoir toujours raison (ou ce qu'il faut éviter pour pas que je vous saute à la gorge {^_^})

  17. #117
    Membre averti
    Homme Profil pro
    Dev
    Inscrit en
    Novembre 2006
    Messages
    112
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 42
    Localisation : France, Alpes Maritimes (Provence Alpes Côte d'Azur)

    Informations professionnelles :
    Activité : Dev

    Informations forums :
    Inscription : Novembre 2006
    Messages : 112
    Points : 350
    Points
    350
    Par défaut
    bonjour

    On dit à juste raison qu'il ne faut pas dupliquer du code mais plutôt faire à la place une fonction qui sera appelé autant de fois que nécessaire.

    mais on accepte de devoir dupliquer des commentaires ou de la documentation.

    je m'interregore sur les point suivant:
    • Si je commente un getter et un setter sur un même attribut , il va avoir une répétition du commentaire. Trouvez cela normal , ou ne devrait-il pas avoir un mécanisme pour crée la doc de manière intelligente.

    • 2° point, où mettriez vous ,le commentaire d'un classe comme ArrayList
      • est-ce vous le mettez en debut de classe pour explique comment elle fonctionne d'une maniéré detaillé, et pas/ou peu de commentaire dans chaqu'une des methodes.

      • ou vous mettez un petit commentaire sur la classe , et un commentaire détaillé ( avec de la redite ) sur l'ensemble des méthodes.

    • 3° point,
      si methode m1() de la classe A< B> fait une délégation sur la methode m1() de l'attribut b de type B
      la documentation/commentaire de A.m1 va faire reference à la documentation de B.m1

      Imaginons que la classe B1 étends la classe B ( et redéfinie la méthode m1 ), et A1 étends la classe A avec la contrainte suivante que l'attribut de b est du type B1 (au lieu de B).

      on pourrait avoir envie de redéfinir le commentaire de A1.m1 sans redéfinir cette méthodes.

    • On peut avoir le même problème avec l’héritage.
      Imaginons que l'on étends une classe HashSet (Java) pour ajouter un contrôle sur le add pour lever un exception si on tente ajouter un valeur non valable.
      On met un commentaire sur la méthode pour dire qu'elle peut lancer un exception.

      Doit-on ( ou est t-il normal ) de redéfinir la méthode addAll pour modifier la documentation et dire qu'elle peut lever une exception.

  18. #118
    Expert éminent
    Avatar de Pyramidev
    Homme Profil pro
    Développeur
    Inscrit en
    Avril 2016
    Messages
    1 470
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Haute Garonne (Midi Pyrénées)

    Informations professionnelles :
    Activité : Développeur

    Informations forums :
    Inscription : Avril 2016
    Messages : 1 470
    Points : 6 107
    Points
    6 107
    Par défaut
    Citation Envoyé par Matthieu Vergne Voir le message
    La doc vient d'abord, le code vient après la doc pour l'implémenter, les commentaires viennent après le code pour le justifier. C'est la doc qui définit ce que le code doit faire, pas l'inverse. Donc des commentaires pour générer de la doc, encore moins. Oui, en pratique on implémente très souvent avant, parce qu'on code en même temps qu'on conçoit. C'est une habitude de technicien, parce qu'on atteint d'abord ce niveau là avant d'atteindre celui d'ingénieur, parce qu'il faut maîtriser le langage avant de l'utiliser. Ça a l'avantage de pouvoir être exécuté -et donc testé- tout de suite, quand on souhaite produire du code c'est rapide et efficace. Mais comme toute solution bottom-up, tu prends le risque de nécessiter de nombreuses modifications avant d'obtenir ce dont tu as réellement besoin, besoin qui ne vient pas du code mais des décideurs, donc top-down. Bottom-up n'est à faire que si tu est vraiment incapable de faire une vrai conception avant, comme avec un problème nouveau ayant peu d'info accessible ou un problème trop complexe nécessitant d'y aller par itérations. Si tu en as la capacité, tu fais d'abord une conception de haut niveau (indépendante du code pour rester au plus proche du métier, et non des habitudes techniques du code), tu en déduis la doc qui donne le cahier des charges de la fonction, tu en déduis le code et les sous-fonctions, et tu recommences récursivement jusqu'à avoir un code totalement exécutable. Si les circonstances font que tu as du faire du bottom-up, tu ne dois pas oublier de tirer tes leçons du code produit, en tirer les besoins haut niveau que tu as eu besoin de satisfaire, et générer la doc à partir de cela, et non à partir du code directement.
    Dans ma courte expérience et dans les témoignages que j'ai lus et entendus, en dehors des systèmes embarqués critiques :
    • Le client ne connaît pas toujours son propre besoin. Il peut y avoir plusieurs raisons :
      • Le client découvre son vrai besoin au moment où il utilise le logiciel. Par exemple, une fois, il y a un client qui a demandé d'implémenter un nouvel indicateur statistique, mais il avait besoin de voir les résultats pour juger si cet indicateur était vraiment pertinent.
      • L'interlocuteur côté client n'est pas l'utilisateur direct du logiciel. Il ne fait que rapporter ce qu'il a compris du besoin.
      • Le besoin lui-même est volatile. En cours de développement, le besoin change.
    • Les délais sont serrés.
    • La hiérarchie met beaucoup de pression sur le court terme. La plupart des développeurs cèdent à la pression et travaillent comme des gros bourrins.


    Comme le besoin change souvent, plus on écrit tôt de la documentation, plus la quantité de choses à y réécrire sera grande.

    Dans certains cas, il arrive qu'on écrive du code pas beaucoup moins clair que de la documentation pour développeur. Dans ces cas-ci, ce que je propose, c'est de ne pas répéter dans la doc pour développeur ce que l'on voit déjà dans le code.
    Pourquoi ? Parce que :
    • Modifier à la fois la documentation et le code est plus lent que de modifier directement le code. Or, dans le monde industriel, il faut aller vite. Donc, si un passage de la documentation n'a pas assez de valeur ajoutée au code pour augmenter la productivité à long terme, ce n'est pas la peine de l'écrire.
    • Même quand on est quelqu'un de consciencieux qui fait attention à la cohérence entre les documentations et le code, il faut anticiper que, quand on ne sera plus là, le code aura plus de chances d'être repris par un gros bourrin développeur normal que par quelqu'un qui tient à l'intégrité des documentations.


    Concernant la documentation générée par des commentaires du code, l'avantage, c'est que le commentaire est près du code et qu'on peut le modifier via le même éditeur que le code. Donc ça facilite la mise à jour des deux en même temps.

    Concernant la démarche de partir du haut niveau pour arriver jusqu'au bas niveau, ben, oui, c'est la bonne manière de raisonner quand on implémente un nouvel algorithme. Mais ça n'a aucun rapport avec le sujet de la documentation. Ce n'est pas parce qu'on a écrit dans un document texte l'algorithme d'une fonction qu'il faut absolument copier-coller cet algorithme dans la doc. On garde ce document de côté, puis on code. Une fois que le code est fini, si le code est suffisamment clair pour qu'on y voit directement l'algorithme, on peut mettre le document dans la corbeille. Si le code n'est pas assez clair, on ajoute des commentaires, dont certains qui serviront à générer de la doc automatiquement. Ils ne seront peut-être pas mis à jour, mais le risque est moins grand que s'ils avaient été écrits dans une documentation non générée par le code.

  19. #119
    Membre du Club
    Profil pro
    Inscrit en
    Mai 2003
    Messages
    50
    Détails du profil
    Informations personnelles :
    Âge : 45
    Localisation : Hong-Kong

    Informations forums :
    Inscription : Mai 2003
    Messages : 50
    Points : 56
    Points
    56
    Par défaut
    J'en reviens pas que cela fasse débat. Les commentaires ne doivent pas commenter le code mais le business. Il faut commenter des (mauvais) choix business. On a tous eu a faire du code dégueulasse parce que le business nous force a faire ainsi pour n'importe quel raison valable ou pas. Les commentaires sont aussi là pour commenter une situation passagère.

  20. #120
    En attente de confirmation mail

    Profil pro
    Inscrit en
    Septembre 2013
    Messages
    639
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Septembre 2013
    Messages : 639
    Points : 2 347
    Points
    2 347
    Par défaut
    Depuis quand les golios du "business" dirigent les choix technologiques / techniques ou d'implémentation ?

Discussions similaires

  1. Réponses: 25
    Dernier message: 06/01/2013, 17h22
  2. Faut-il commenter son code source pour le rendre plus lisible et maintenable ?
    Par mayayu dans le forum Débats sur le développement - Le Best Of
    Réponses: 149
    Dernier message: 09/11/2009, 02h30
  3. Bien commenter son code Java
    Par shaun_the_sheep dans le forum Général Java
    Réponses: 2
    Dernier message: 27/05/2008, 11h13
  4. Comment commenter son code source proprement ...
    Par basnifo dans le forum MFC
    Réponses: 3
    Dernier message: 31/03/2006, 16h22

Partager

Partager
  • Envoyer la discussion sur Viadeo
  • Envoyer la discussion sur Twitter
  • Envoyer la discussion sur Google
  • Envoyer la discussion sur Facebook
  • Envoyer la discussion sur Digg
  • Envoyer la discussion sur Delicious
  • Envoyer la discussion sur MySpace
  • Envoyer la discussion sur Yahoo