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

C++ Discussion :

Quelles sont les démarches de conception dans les équipes de développement?


Sujet :

C++

  1. #1
    Membre expérimenté
    Homme Profil pro
    Chercheur
    Inscrit en
    Mars 2010
    Messages
    1 218
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Paris (Île de France)

    Informations professionnelles :
    Activité : Chercheur

    Informations forums :
    Inscription : Mars 2010
    Messages : 1 218
    Points : 1 685
    Points
    1 685
    Par défaut Quelles sont les démarches de conception dans les équipes de développement?
    Bonjour,

    je m'interroge sur la manière de concevoir efficacement du code C++.

    Naïvement, j'ai tendance à penser ma structure de classe en essayant de définir toutes les données et fonctions membres.

    Je me demande par exemple s'il n'est pas plus judicieux de définir d'abord tout du point de vue "client" (données et fonctions membres publiques).

    Bref, j'aimerais savoir s'il existe des démarches de conception standards ou éprouvées chez les professionnels.

  2. #2
    Expert éminent sénior
    Avatar de koala01
    Homme Profil pro
    aucun
    Inscrit en
    Octobre 2004
    Messages
    11 614
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 52
    Localisation : Belgique

    Informations professionnelles :
    Activité : aucun

    Informations forums :
    Inscription : Octobre 2004
    Messages : 11 614
    Points : 30 626
    Points
    30 626
    Par défaut
    Salut,

    De manière génrale, l' "âme" de la programmation OO est de ne considérer tes classes que du point de vue des services qu'elles doivent te rendre, et non du point de vue des données qui leur permettront de les rendre.

    En programmation structurée, tu va te dire "j'ai besoin d'une structure qui contiennent les infos suivantes", alors que, en OO, tu vas te dire "j'ai besoin d'une classe qui me fournit tel et tel service".

    Ainsi, à la base, tu peux carrément ne pas t'inquiéter de la manière dont le nom et le prénom d'une personne seront représentés dans ta classe Personne, car cela n'a pas d'importance: Ce qui importe, c'est que ta classe soit en mesure de répondre à deux questions : "Quel est ton nom" et "Quel est ton prénom" (je fais simple, pour te permettre de comprendre, hein )

    De cette manière, la personne qui utilisera cette classe Personne n'a aucun besoin de savoir si le nom est représenté "séparément" du prénom ou non, s'il le sont sous la forme de chaines de caractères "C style" ou sous la forme de std::string, et encore moins le nom de la variable qui permet de les représenter: elle n'a qu'à savoir qu'elle doit appeler name pour avoir le nom et firstname pour avoir le prénom.

    Tant que tu gardera ces fonctions name et firstname qui te donnent respectivement le nom et le prénom, tu pourra modifier la manière de les représenter en interne tant que tu veux, tu ne devra pas changer une seule ligne de code en dehors de ta classe.

    Maintenant, pour savoir les services que tu attend d'une classe, tu dois t'intéresser à sa responsabilité, et veiller à garder une "responsabilité unique":

    Tu dois veiller à ce que ta classe n'ai qu'une seule responsabilité, mais qu'elle l'assume correctement.

    Evidemment, la "granularité" des responsabilités est très variable: Si tu veux recréer une "chaine de caractères maison", sa principale responsabilité sera de... gérer les caractères qu'elle a en charge, mais cela se divise déjà en une série de services attendus, qui se transformeront en autant de fonctions membres.

    Et, tout en haut de l'échelle, tu aura peut être une classe qui doit... gérer le personnel de la société qui doit te permettre d'engager (ou de virer) quelqu'un, de prendre en compte ses déménagements et changements de téléphone, de gérer les horaires, les maladies, les vacances, et de, et de et de...

    Mais, surtout, tu dois veiller à appliquer en permanence deux principes fondamentaux : KISS et YAGNI.
    1. KISS : Keep It Simple, Stupid: garde les choses les plus simples possibles tant que cela te suffit.
    2. YAGNI: You Ain't Gonna Need It : Tu n'a pas encore besoin de cela.
    Tu peux prévoir qu'il te faudra telle ou tellle donnée dans une classe (parce qu'elle semble absolument nécessaire pour permettre de rendre tel ou tel service) ou telle ou telle fonction, mais, tant que tu n'a pas besoin de cette fonction ou de cette donnée, ne perd pas ton temps à la créer... Peut-être n'en auras-tu pas besoin car tu aura choisi une autre voie.

    Dis toi bien que si tout ce qui n'est pas fait risque de manquer, tout ce qui a été fait mais qui est inutile... a fait perdre du temps

    Enfin, il y a quelques règles, principes et lois qu'il faut garder en tête et qui doivent t'inciter quasi en permanence à t'interroger:

    Demeter : Est-ce que l'utilisateur a réellement besoin d'accéder à cette donnée Est-il cohérent d'envisager d'accepter qu'il (l'utilisateur) la modifie

    LSP : Est-ce que je peux décemment dire que tel type que je souhaite faire dériver d'un autre EST-UN objet du type de base

    Open Close Principle : Que se passera-t-il si je donne telle donnée "en pâture" à l'utilisateur et que je souhaite en changer la manière dont c'est représenté Se peut il que je décide un jour de changer de représentation

    ...

    Et, le plus important de tout: Commence par exprimer clairement ce que tu veux, ce que tu attends ou ce que tu souhaite.

    La priorité absolue doit être de savoir exactement quels sont tes besoins et tes attentes, même si elles évoluent au cours du temps
    A méditer: La solution la plus simple est toujours la moins compliquée
    Ce qui se conçoit bien s'énonce clairement, et les mots pour le dire vous viennent aisément. Nicolas Boileau
    Compiler Gcc sous windows avec MinGW
    Coder efficacement en C++ : dans les bacs le 17 février 2014
    mon tout nouveau blog

  3. #3
    Expert éminent sénior
    Homme Profil pro
    Développeur informatique
    Inscrit en
    Février 2005
    Messages
    5 074
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 52
    Localisation : France, Val de Marne (Île de France)

    Informations professionnelles :
    Activité : Développeur informatique
    Secteur : Conseil

    Informations forums :
    Inscription : Février 2005
    Messages : 5 074
    Points : 12 120
    Points
    12 120
    Par défaut
    Aussi :

    Toujours faire des architectures et des conceptions de classe testables.

    Par exemple, quand une classe utilise des données externes venant d'une base de données, ne pas être obligé d'avoir une base de données pour la tester.

    => mocking, architecture en couches etc..

  4. #4
    Membre expérimenté
    Homme Profil pro
    Chercheur
    Inscrit en
    Mars 2010
    Messages
    1 218
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Paris (Île de France)

    Informations professionnelles :
    Activité : Chercheur

    Informations forums :
    Inscription : Mars 2010
    Messages : 1 218
    Points : 1 685
    Points
    1 685
    Par défaut
    Bonsoir,

    Citation Envoyé par koala01 Voir le message
    De manière génrale, l' "âme" de la programmation OO est de ne considérer tes classes que du point de vue des services qu'elles doivent te rendre, et non du point de vue des données qui leur permettront de les rendre.
    Oui, c'est ce que je me suis dit... mais un peu tard!
    Je crois que je vais arrêter le codage un moment et me remettre à travailler sur papier.


    Tu peux prévoir qu'il te faudra telle ou tellle donnée dans une classe (parce qu'elle semble absolument nécessaire pour permettre de rendre tel ou tel service) ou telle ou telle fonction, mais, tant que tu n'a pas besoin de cette fonction ou de cette donnée, ne perd pas ton temps à la créer... Peut-être n'en auras-tu pas besoin car tu aura choisi une autre voie.
    Oui, je continue toujours à polluer localement mes codes mais de moins en moins.

    Demeter : Est-ce que l'utilisateur a réellement besoin d'accéder à cette donnée Est-il cohérent d'envisager d'accepter qu'il (l'utilisateur) la modifie

    LSP : Est-ce que je peux décemment dire que tel type que je souhaite faire dériver d'un autre EST-UN objet du type de base

    Open Close Principle : Que se passera-t-il si je donne telle donnée "en pâture" à l'utilisateur et que je souhaite en changer la manière dont c'est représenté Se peut il que je décide un jour de changer de représentation
    Pour Demeter et Open Close Principle, j'ai bien saisi et j'espère encapsuler proprement mes données en pratique.

    Pour le LSP, je suis moins clair.
    Je reprend l'exemple classique du carré et du rectangle.
    J'ai bien compris pourquoi le carré ne peut hériter du rectangle.
    Par contre, je me demande si le rectangle peut hériter du carré.
    J'aurais tendance à répondre positivement puisque le premier est une extension du second.
    Le problème, c'est que ça paraître étrange à première vue.
    En faisant ce genre de truc, j'ai un peu peur de dérouter les personnes qui voudraient utiliser mes sources.
    Il me semble plus clair pour le plus grand nombre que le carré soit une classe admettant pour donnée membre un rectangle.
    Par contre, cela demande un peu plus de développement.

    D'où ma question : une fois définis les services que doivent te rendre une classe, est-ce que tu réfléchis ensuite aux parties "invisibles" à l'utilisateur mais visibles "aux futurs développeurs"?

    Sinon, merci beaucoup pour ta réponse : elle répond bien à ma question de départ et me confirme que j'ai pris un mauvais départ.

  5. #5
    Membre expérimenté
    Homme Profil pro
    Chercheur
    Inscrit en
    Mars 2010
    Messages
    1 218
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Paris (Île de France)

    Informations professionnelles :
    Activité : Chercheur

    Informations forums :
    Inscription : Mars 2010
    Messages : 1 218
    Points : 1 685
    Points
    1 685
    Par défaut
    Bonsoir,

    Citation Envoyé par bacelar Voir le message
    Aussi :

    Toujours faire des architectures et des conceptions de classe testables.

    Par exemple, quand une classe utilise des données externes venant d'une base de données, ne pas être obligé d'avoir une base de données pour la tester.

    => mocking, architecture en couches etc..
    Je ne suis pas encore pleinement confronté au problème mais je vais l'être sous peu.
    Je développe une interface graphique et ça va vite devenir lourd de valider la partie "calcul" (hors ihm) en l'utilisant.
    Je n'ai aucune expérience dans la validation de code objet sur un gros projet.
    Pour ta validation, tu crées des fonctions membres spécifiques à l'intérieur de la classe que tu veux valider ou des nouveaux objets spécifiques?

    J'ai vu aussi qu'il y a des modules spécifiques de tests unitaires dans les edi.
    C'est une bonne approche?

  6. #6
    Expert éminent sénior
    Avatar de koala01
    Homme Profil pro
    aucun
    Inscrit en
    Octobre 2004
    Messages
    11 614
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 52
    Localisation : Belgique

    Informations professionnelles :
    Activité : aucun

    Informations forums :
    Inscription : Octobre 2004
    Messages : 11 614
    Points : 30 626
    Points
    30 626
    Par défaut
    Citation Envoyé par Aleph69 Voir le message
    Pour le LSP, je suis moins clair.
    Je reprend l'exemple classique du carré et du rectangle.
    J'ai bien compris pourquoi le carré ne peut hériter du rectangle.
    Par contre, je me demande si le rectangle peut hériter du carré.
    J'aurais tendance à répondre positivement puisque le premier est une extension du second.
    En fait, pour pouvoir considérer l'héritage, tu dois te dire que toute propriété publique valide pour la classe de base doit être valide pour la classe dérivée, ce n'est rien de plus que cela.

    Comme il n'y a aucun sens à permettre de manipuler un carré (comprend: une forme qui est carrée à la base et qui doit le rester) sur base de sa longueur et de sa largeur, et, surtout, de faire varier l'un sans faire varier l'autre, tu ne dois avoir aucune relation entre ton carré et ton rectangle.

    Si le carré n'est jamais qu'un "état particulier" du rectangle au moment où tu fais varier l'une des deux dimensions mais que cet état ne fait pas partie des contraintes de ta forme, alors tu ne devrait même pas avoir une classe "carré", mais simplement un fonction membre qui répond à la question "es-tu carré (à cet instant précis)" dans l'interface de ton rectangle.

    Le problème, c'est que ça paraître étrange à première vue.
    En faisant ce genre de truc, j'ai un peu peur de dérouter les personnes qui voudraient utiliser mes sources.
    Il me semble plus clair pour le plus grand nombre que le carré soit une classe admettant pour donnée membre un rectangle.
    Par contre, cela demande un peu plus de développement.
    "Admettant une donnée membre" te permet, éventuellement, de considérer que la "mécanique interne" de ton carré est fournie par le rectangle: cela te place dans un contexte de composition ou d'agrégation, rien de plus
    D'où ma question : une fois définis les services que doivent te rendre une classe, est-ce que tu réfléchis ensuite aux parties "invisibles" à l'utilisateur mais visibles "aux futurs développeurs"?
    Avant de répondre, mettons nous d'accord sur les termes que nous allons utiliser

    Pour moi, dans ce cas particulier, le développeur d'une classe sera celui qui décide de créer ou de modifier cette classe parce qu'elle ne correspond pas "tout à fait" aux besoins qu'elle est sensée couvrir.

    L'utilisateur de cette classe sera toute personne qui décide de profiter de cette classe, simplement pour y faire appel ou comme classe de base pour une autre, mais sans envisager le moins du monde d'aller "chipoter" dans cette classe.

    Alors, un développeur qui reprend une de tes classes, que va-t-il bien pouvoir faire
    • Surement pas décider de retirer un des services dont tu as estimé à la base qu'il était nécessaire, surtout si le service a été implémenté, parce que cela risque de "tout casser"
    • Eventuellement décider de rajouter un service supplémentaire, parce que l'usage en a démontré l'utilité.
    • Eventuellement modifier la "mécanique interne" en décidant de représenter les données de manière différente
    Il y a donc, malgré tout, de très grandes chances pour que tu aies au moins envisagé les modifications qu'il vienne à apporter, même si, pour une raison x ou y, tu les as écartées.

    Maintenant, je pense que le sens de ta question voulait également englober la partie des utilisateurs qui envisageraient volontiers d'hériter de ta classe

    De manière générale, une classe doit connaitre:
    • "tout ce qu'elle peut" de ses classes mères / ancêtre (interface publique + partie protégée)
    • l'interface publique de ses données membres
    • strictement rien de ses classes enfants / descendantes.
    Tu ne peux en effet être responsable que de ce que tu fais, surtout si celui qui fait "quelque chose de mal" se trouve de l'autre coté de l'océan, par rapport à toi (et même si ce n'est que dans le bureau voisin )

    Ta responsabilité est de donner un type de donnée qui fournit un certain nombre de services, et de l'accompagner d'une documentation qui explique exactement ce que l'on peut en faire. C'est déjà pas si mal, et c'est même en fait déjà beaucoup, non

    Si le type décide de faire hériter une de ses classes de la tienne alors qu'il n'aurait pas du, s'il essaye (parce qu'il peut envisager de faire hériter sa classe de la tienne) de redéfinir une fonction qu'il n'aurait pas du, c'est... son problème, pas le tien .

    Bon, évidemment, si c'est un travail collaboratif et que c'est ton collègue, son problème deviendra sans doute à un moment ou un autre le tien, mais, une fois que tu as déterminé les règles de "bon usage" de ta classe, ceux qui voudront l'utiliser devront s'y conformer.

    Pour autant que les règles de bon usage de ta classe sont clairement établies et clairement exprimées (dans la documentation relative, par exemple), l'utilisateur qui décide de ne pas les respecter ne pourra le faire qu'à ses propres risques et périls
    A méditer: La solution la plus simple est toujours la moins compliquée
    Ce qui se conçoit bien s'énonce clairement, et les mots pour le dire vous viennent aisément. Nicolas Boileau
    Compiler Gcc sous windows avec MinGW
    Coder efficacement en C++ : dans les bacs le 17 février 2014
    mon tout nouveau blog

  7. #7
    Membre expérimenté
    Homme Profil pro
    Chercheur
    Inscrit en
    Mars 2010
    Messages
    1 218
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Paris (Île de France)

    Informations professionnelles :
    Activité : Chercheur

    Informations forums :
    Inscription : Mars 2010
    Messages : 1 218
    Points : 1 685
    Points
    1 685
    Par défaut
    Bonsoir,

    Citation Envoyé par koala01 Voir le message
    "Admettant une donnée membre" te permet, éventuellement, de considérer que la "mécanique interne" de ton carré est fournie par le rectangle: cela te place dans un contexte de composition ou d'agrégation, rien de plus
    Oui, je vois. En fait, en y réfléchissant mieux, tu peux quand même violer le LSP dans l'autre sens d'héritage également.

    Citation Envoyé par koala01 Voir le message
    De manière générale, une classe doit connaitre:
    • "tout ce qu'elle peut" de ses classes mères / ancêtre (interface publique + partie protégée)
    • l'interface publique de ses données membres
    • strictement rien de ses classes enfants / descendantes.
    Le dernier point implique que toute classe n'admettant pas de classe enfant :
    • admet principalement des données et fonctions membres publiques ou protégées,
    • n'admet aucune méthode virtuelle si elle n'hérite d'aucune autre classe.

    Tu es d'accord avec ça?

  8. #8
    Expert éminent sénior
    Avatar de koala01
    Homme Profil pro
    aucun
    Inscrit en
    Octobre 2004
    Messages
    11 614
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 52
    Localisation : Belgique

    Informations professionnelles :
    Activité : aucun

    Informations forums :
    Inscription : Octobre 2004
    Messages : 11 614
    Points : 30 626
    Points
    30 626
    Par défaut
    Citation Envoyé par Aleph69 Voir le message
    Oui, je vois. En fait, en y réfléchissant mieux, tu peux quand même violer le LSP dans l'autre sens d'héritage également.
    NYPONIMAÏ, moi pas comprendre

    Une composition ou une agrégation n'a strictement rien à voir avec l'héritage
    Le dernier point implique que toute classe n'admettant pas de classe enfant :
    • admet principalement des données et fonctions membres publiques ou protégées,
    • n'admet aucune méthode virtuelle si elle n'hérite d'aucune autre classe.

    Tu es d'accord avec ça?
    Y a-t-il quoi que ce soit dans ma prose qui te permette de déduire cela

    D'abord, tu ne dois pas oublier toute la "mécanique interne" de ta classe, tout ce qui peut s'avérer utile afin de permettre qu'une fonction puisse rendre le service dont on a besoin:

    La délégation des responsabilités n'est pas seulement une obligation au niveau des classes, car il ne faut pas oublier que la programmation OO s'appuie en très grande partie sur la programmation structurée.

    Tu dois donc aussi veiller à déléguer correctement les responsabilités au niveau de tes fonctions, et surtout au niveau des fonctions publiques.

    Cela implique que, si une fonction se retrouve dans une situation dans laquelle elle a plusieurs responsabilités, c'est sans doute qu'elle en a trop, surtout si une ou plusieurs des responsabilités se retrouvent dans d'autres fonctions.

    Comme chaque responsabilité devra, au final, être implémentée sous la forme d'une fonction, il faut réfléchir à l'intérêt qu'il peut y avoir à exposer cette fonction supplémentaire:

    Soit cette fonction représente un service supplémentaire qui, tant qu'à faire -- parce que déjà utilisé par un des services que l'on avait envisagé à la base -- s'avère intéressant à proposer à l'utilisateur, on en fera une fonction publique.

    Si, par contre, il n'est pas intéressant d'exposer ce service à l'utilisateur -- parce que c'est un mécanisme de "popote interne" -- nous en ferons... une fonction privée.

    Ensuite, il ne faut pas oublier que ta classe dont ton approche semble justifier qu'elle ne sera pas héritée peut parfaitement hériter d'une hiérarchie plus ou moins complexe.

    A ce titre, elle peut parfaitement redéfinir des fonctions issues de n'importe laquelle de ses classes ancêtres.

    Elle peut donc avoir un certain nombre de fonctions virtuelles qui lui viennent de ces classes ancêtres

    Enfin, on peut se dire que, si tu as décidé que ta classe héritait d'une autre, c'est parce que tu t'es retrouvé face à un cas d'utilisation dans lequel tu avais besoin d'une spécialisation de sa classe de base.

    Si tu ne vois pas de raison de créer une classe supplémentaire héritant de celle-ci, cela ne veut en gros dire qu'une seule chose: tu n'a pas encore de cas d'utilisation qui justifie de le faire.

    Peut-être n'y aura-t-il jamais de cas d'utilisation qui justifie de le faire... ou peut être arrivera-t-il avec le prochain coup de téléphone

    Et, au dessus de cette mêlée, il y a le cas des classes ayant sémantique de valeur: ces classes qui n'héritent pas d'une autre (de manière non générique, en tout cas), et qui ne sont pas destinées à servir de base à un héritage (la classe std::string, par exemple).

    Il n'y a strictement aucune raison de placer quoi que ce soit dans l'accessibilité protégée, ni pour créer des fonctions virtuelles, vu qu'il n'y a aucune raison pour qu'elle serve de classe de base à une autre, mais elles peuvent parfaitement avoir des fonctions membres privées qui... facilitent le travail pour les fonctions publiques
    A méditer: La solution la plus simple est toujours la moins compliquée
    Ce qui se conçoit bien s'énonce clairement, et les mots pour le dire vous viennent aisément. Nicolas Boileau
    Compiler Gcc sous windows avec MinGW
    Coder efficacement en C++ : dans les bacs le 17 février 2014
    mon tout nouveau blog

  9. #9
    Membre expérimenté
    Homme Profil pro
    Chercheur
    Inscrit en
    Mars 2010
    Messages
    1 218
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Paris (Île de France)

    Informations professionnelles :
    Activité : Chercheur

    Informations forums :
    Inscription : Mars 2010
    Messages : 1 218
    Points : 1 685
    Points
    1 685
    Par défaut
    Bonsoir,

    Citation Envoyé par koala01 Voir le message
    NYPONIMAÏ, moi pas comprendre

    Une composition ou une agrégation n'a strictement rien à voir avec l'héritage
    Désolé, ma citation porte à confusion.
    Je voulais bien parler d'héritage.
    J'essayais juste de dire qu'en faisant hériter le rectangle du carré, on peut quand même violer le LSP.

    Citation Envoyé par koala01 Voir le message
    Y a-t-il quoi que ce soit dans ma prose qui te permette de déduire cela
    J'ai peut-être conclu un peu vite.
    Après relecture, le Open-Closed Principle tend à me contredire.
    Je ne voyais pas pourquoi interdire à des potentielles classes enfants d'accéder à des données ou fonctions membres non publiques, sauf raison majeure.


    Citation Envoyé par koala01 Voir le message
    Ensuite, il ne faut pas oublier que ta classe dont ton approche semble justifier qu'elle ne sera pas héritée peut parfaitement hériter d'une hiérarchie plus ou moins complexe.

    A ce titre, elle peut parfaitement redéfinir des fonctions issues de n'importe laquelle de ses classes ancêtres.

    Elle peut donc avoir un certain nombre de fonctions virtuelles qui lui viennent de ces classes ancêtres
    C'est pour ça que j'avais précisé "si elle n'hérite d'aucune autre classe".

  10. #10
    En attente de confirmation mail

    Homme Profil pro
    Ingénieur développement logiciels
    Inscrit en
    Août 2004
    Messages
    1 391
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 33
    Localisation : France, Doubs (Franche Comté)

    Informations professionnelles :
    Activité : Ingénieur développement logiciels

    Informations forums :
    Inscription : Août 2004
    Messages : 1 391
    Points : 3 311
    Points
    3 311
    Par défaut
    Avant d'écrire, je présice que je ne suis pas un profesionnel, donc mon avis est peut-etre légèrement biaisé.

    AMA, il y a plusieurs choses à distinguer :

    - D'une part les grands principes directeur que sont YAGNI et KISS, qui ne donne en réalité rien de concret, mais permet d'imposer une ligne directrice lors de la conception. Les suivre évite de se retrouver à faire une usine à gaz pour résoudre quelque chose de simple sous pretexte que c'est plus générique par exemple. Emmanuel Deloget a écrit un billet d'exemple de sur-utilisation de la méta-prog, ca illustre parfaitement YAGNI et KISS je trouve.

    - Ensuite les différents principes de la POO : SRP, OCP, LSP, DIP, ISP en rajoutant LoD. Qui permettent de définir une architecture propre : la responsabilité de chaque classe, les relations entre les classes, les modules, les dépendance. Et de "justifier" ces relations, car le non-respect de ces principes peut très bien ne poser aucun problèmes à cet instant, mais il peut en induire lors d'une maintenance du code, d'un extension, ... L'article sur le DIP (toujours sur le blog d'Emmanuel) montre bien quel peut-être le problème après une décision de changement.

    - Normalement, l'étape précedente permet d'itenfier des problèmes qui était au départ caché et dont la résolution s'applique par un DP connu. Ces DP (ceux du GoF surtout) sont en général bien documenté, ce qui permet de les implémenter facilement et proprement par la suite. L'utilisation de Loki (par exemple) pour certains DP (Singleton, Factory, Visitor), permet d'avoir des implémentations de qualité rapidement, les refaire avec une tel qualité à chaque fois serait assez fastidieux, d'où l'interet de les identifier.

  11. #11
    Expert éminent sénior
    Avatar de koala01
    Homme Profil pro
    aucun
    Inscrit en
    Octobre 2004
    Messages
    11 614
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 52
    Localisation : Belgique

    Informations professionnelles :
    Activité : aucun

    Informations forums :
    Inscription : Octobre 2004
    Messages : 11 614
    Points : 30 626
    Points
    30 626
    Par défaut
    Citation Envoyé par Aleph69 Voir le message
    Désolé, ma citation porte à confusion.
    Je voulais bien parler d'héritage.
    J'essayais juste de dire qu'en faisant hériter le rectangle du carré, on peut quand même violer le LSP.
    Ce n'est pas seulement un risque, et j'ai pourtant bien insisté là dessus: on viole LSP, point: toutes les propriétés valides pour l'un ne sont pas valides pour l'autre, et ce, quel que soit le type que l'on considère pour "l'un" et pour "l'autre

    J'ai peut-être conclu un peu vite.
    Après relecture, le Open-Closed Principle tend à me contredire.
    Je ne voyais pas pourquoi interdire à des potentielles classes enfants d'accéder à des données ou fonctions membres non publiques, sauf raison majeure.
    Simplement parce que cela fait partie de la "popote interne" de la classe parent
    C'est pour ça que j'avais précisé "si elle n'hérite d'aucune autre classe".
    Non, tu as précisé exactement le contraire:
    Le dernier point implique que toute classe n'admettant pas de classe enfant
    signifie "ne pouvant pas être dérivée"

    Or les deux situations sont totalement possibles de manière indépendante, ce qui nous donne trois possibilités au total

    Tu peux avoir une classe dont d'autres héritent, mais qui n'hérite de rien.

    Tu peux avoir une classe qui hérite d'une autre, mais dont aucune classe n'hérite.

    Et, enfin, tu peux avoir une classe qui hérite d'une autre et dont d'autres héritent.

    Le pire de l'histoire étant que, ton analyse ne formalisant jamais que la compréhension d'un problème que tu en as à un instant donné, tu ne peux jamais préjuger de ce que sera ta compréhension "plus tard", car les besoins sont en évolution constante.

    Si une classe hérite déjà d'une autre, le "risque" de te trouver dans une situation dans laquelle tu devra faire hériter une nouvelle classe de ta classe dérivée est tout simplement patent, en fonction de l'évolution du projet
    A méditer: La solution la plus simple est toujours la moins compliquée
    Ce qui se conçoit bien s'énonce clairement, et les mots pour le dire vous viennent aisément. Nicolas Boileau
    Compiler Gcc sous windows avec MinGW
    Coder efficacement en C++ : dans les bacs le 17 février 2014
    mon tout nouveau blog

  12. #12
    Membre expérimenté
    Homme Profil pro
    Chercheur
    Inscrit en
    Mars 2010
    Messages
    1 218
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Paris (Île de France)

    Informations professionnelles :
    Activité : Chercheur

    Informations forums :
    Inscription : Mars 2010
    Messages : 1 218
    Points : 1 685
    Points
    1 685
    Par défaut
    Bonsoir,

    Citation Envoyé par koala01 Voir le message
    Non, tu as précisé exactement le contraire:
    C'est pas très courtois de couper les phrases des gens!

    Le dernier point implique que toute classe n'admettant pas de classe enfant :
    • admet principalement des données et fonctions membres publiques ou protégées,
    • n'admet aucune méthode virtuelle si elle n'hérite d'aucune autre classe.
    Enfin, bref, ce n'est pas bien grave.

    On m'a toujours dit qu'il ne fallait jamais modifier l'encapsulation de l'existant.
    Donc, il me semblait justifié de privilégier les déclarations protégées aux déclarations privées par défaut.
    Je veux bien croire que je me trompe mais il faut que je comprenne pourquoi.
    Je vais lire un peu de documentation sur le Open-Closed Principle pour avoir une meilleure vision de la chose.

  13. #13
    Expert éminent sénior
    Avatar de koala01
    Homme Profil pro
    aucun
    Inscrit en
    Octobre 2004
    Messages
    11 614
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 52
    Localisation : Belgique

    Informations professionnelles :
    Activité : aucun

    Informations forums :
    Inscription : Octobre 2004
    Messages : 11 614
    Points : 30 626
    Points
    30 626
    Par défaut
    Citation Envoyé par Aleph69 Voir le message

    On m'a toujours dit qu'il ne fallait jamais modifier l'encapsulation de l'existant.
    Donc, il me semblait justifié de privilégier les déclarations protégées aux déclarations privées par défaut.
    Je veux bien croire que je me trompe mais il faut que je comprenne pourquoi.
    Je vais lire un peu de documentation sur le Open-Closed Principle pour avoir une meilleure vision de la chose.
    Pour la simple et bonne raison que tu dois veiller à assurer la meilleure encapsulation possible: quand un(e fonction) membre est privée, c'est clair: il n'y a que les fonctions membres de cette classe qui pourront y accéder.

    Quand un(e fonction) membre est protégée, tu "ouvre une voie royale" pour permettre à peu près à n'importe qui d'y accéder.

    Quand je dis "à peu près à n'importe qui" j'entends "n'importe quelle classe qui dérive de manière directe ou indirecte" de ta classe d'origine.

    Comme tu ne sais jamais quantifier, à la base, quels seront les besoins de dérivation qui pourront apparaitre "plus tard", tu ne peux jamais être sur que ta hiérarchie de classes, qui se compose à la base de 3 ou 4 classes, ne finira pas avec 100 ou 200 classes dérivées de manière directe ou indirecte.

    Tu finis donc dans une situation finalement fort proche de celle à laquelle tu serais confronté avec un membre publique, si ce n'est qu'elle sera *un peu* plus limitée:

    Si, pour une raison ou une autre, tu décide de modifier la manière dont le membre protégé est représenté en mémoire, tu devra vérifier l'ensemble des fonctions (toute visibilité confondue) de l'ensemble des classes qui héritent de manière directe ou indirecte de celle dans laquelle le membre est déclaré.

    Si tu décides de placer le membre dans l'accessibilité privée et que, pour permettre malgré tout aux classes dérivées d'en profiter, tu place un accesseur et éventuellement un mutateur sur ce membre dans l'accessibilié protégée, tu restreint les modifications qu'il faudra apporter le jour où tu décide de modifier la manière dont le membre est représentée en mémoire

    [EDIT]Au final, on pourrait schématiser l'accessibilité maximale autorisée sous la forme de:
    • publique: fonctions diverses, types imbriqués et valeurs constantes, statiques ou non, accessibles "partout"
    • protégée : fonctions diverses et valeurs constantes, statiques ou non, accessible depuis les classes dérivées
    • privée: toute donnée constante et surtout non constante + types imbriqués et fonctions à "usage interne" uniquement
    La constance de la donnée étant décisive dés qu'il s'agit d'utiliser une accessibilité plus permissive pour une donnée
    A méditer: La solution la plus simple est toujours la moins compliquée
    Ce qui se conçoit bien s'énonce clairement, et les mots pour le dire vous viennent aisément. Nicolas Boileau
    Compiler Gcc sous windows avec MinGW
    Coder efficacement en C++ : dans les bacs le 17 février 2014
    mon tout nouveau blog

  14. #14
    Membre expérimenté
    Homme Profil pro
    Chercheur
    Inscrit en
    Mars 2010
    Messages
    1 218
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Paris (Île de France)

    Informations professionnelles :
    Activité : Chercheur

    Informations forums :
    Inscription : Mars 2010
    Messages : 1 218
    Points : 1 685
    Points
    1 685
    Par défaut
    Bonjour,

    je viens de lire un peu de documentation sur le Open-Closed Principle.

    En ce qui concerne les données membres, il faut les déclarer en privé dès qu'elles doivent vérifier des conditions particulières pouvant être violées par modification de leur valeur.
    Par exemple, pour faire simple, un nombre devant être compris dans un certain intervalle.
    L'encapsulation permet d'éviter toute violation en déclarant ce nombre comme étant une donnée privée.
    Si cette donnée doit pouvoir être modifiée dans les classes enfants, alors il suffit d'implémenter un mutateur protégé, de préférence en ligne pour conserver de bonnes performances (inlining).
    Ainsi, tu peux lancer une exception si l'une des conditions que doit vérifier le nombre est violée.
    Si elle doit être accédée, on fait un accesseur protégé, toujours en ligne de préférence.
    C'est ce dont tu parles dans ton dernier message :
    Si tu décides de placer le membre dans l'accessibilité privée et que, pour permettre malgré tout aux classes dérivées d'en profiter, tu places un accesseur et éventuellement un mutateur sur ce membre dans l'accessibilié protégée, tu restreint les modifications qu'il faudra apporter le jour où tu décide de modifier la manière dont le membre est représentée en mémoire
    A ceci près que tu y vois en plus un intérêt en cas de modification de la classe concernée (gain de temps de développement).

    Effectivement, ça me paraît être une approche plus sûre que de proposer une donnée protégée.
    Par contre, ça a quand même un coût.
    Tu fais plus de développement pendant la phase de création (mutateurs/accesseurs) et, du coup, tu fais beaucoup de mise en ligne (le code final grossit en taille).
    Ceci dit, je suis tout à fait d'accord avec le fait qu'il faut privilégier la stabilité du code par rapport aux performances ou à la taille.

    Dans le cas où mon nombre n'aurait pas à vérifier de conditions particulières, le fait de déclarer ma donnée protégée n'affecte en rien la stabilité du code.
    En revanche, comme tu le soulignes, je m'expose à un travail de développement plus conséquent dans le cas où je souhaiterais modifier la représentation interne de ma classe.
    Je n'ai pas d'expérience pour en juger, mais il me semblait que la POO permet quand même de limiter ce travail.
    En programmation procédurale (là j'ai plus d'expérience), il est clair que ce genre de modification peut vraiment coûter très cher.

    Bref, je comprends mieux pourquoi tu as sauté au plafond quand j'ai cru comprendre qu'il fallait privilégier les données protégées aux données privées par défaut!
    Et le fait que les données publiques et protégées doivent être constantes me paraît clair maintenant.

  15. #15
    Expert éminent sénior
    Avatar de koala01
    Homme Profil pro
    aucun
    Inscrit en
    Octobre 2004
    Messages
    11 614
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 52
    Localisation : Belgique

    Informations professionnelles :
    Activité : aucun

    Informations forums :
    Inscription : Octobre 2004
    Messages : 11 614
    Points : 30 626
    Points
    30 626
    Par défaut
    N'oublie pas que la programmation orientée objet n'est jamais qu'un "sur ensemble" de la programmation procédurale.

    Elle en subit donc l'ensemble des contraintes et des problèmes.

    La seule différence, c'est qu'elle propose certains mécanismes qui permettent d'en limiter les conséquences, à condition toutefois de faire jouer ces mécanismes.

    Le trio "taille de l'exécutable / peformances / sécurité" et les décisions qui implique de trouver le "meilleur compromis possible" entre ces trois aspects divergents sont donc les mêmes en OO qu'en procédurale

    La programmation procédurale ne donne pas facilement la possibilité de modifier l'existant sans devoir retourner partout où l'on accède à une donnée particulière afin de prendre les modifications en compte, alors que l'OO fournit, grâce à ce mécanisme d'accessibilité, un mécanisme permettant d'éviter ce problème.

    Si tu fais jouer ce mécanisme, tu as une chance de gagner énormément de temps du fait que tu ne coures pas le risque d'oublier de modifier un dernier fichier qui, maque de bol, sera compilé "en fin de processus".

    Mais si tu ne fais pas jouer ce mécanisme, tu subira de plein fouet tous les problèmes liés à cette situation que tu pourrait rencontrer en programmation procédurale

    A ce sujet, j'ai peut être une anecdote récente:

    Je voulais compiler une version de développement de Gcc.

    Pour que tu puisse comprendre le rapprochement, il faut savoir que la compilation de Gcc effectue un "bootstrap", qui est un processus par lequel une bonne partie des sources sont compilées trois fois d'affilée.

    Le bootstrap prend généralement près de deux heures, si on ne demande pas la compilation parallèle.

    Il y avait UN fichier, compilé après le bootstrap, qui présentait un problème.

    Comme ce fichier est, non seulement, particulier à MinGW, mais que, de plus, il est destiné à Ada (et écrit en Ada), tu te rend compte que c'est un fichier qui n'est sans doute pas très souvent retravaillé .

    La première tentative de compilation m'a (merci les messages explicites de Ada ) permis de trouver assez facilement l'endroit où le problème se trouvait et le genre de problème auquel j'étais confronté.

    Mais, si j'arrive à comprendre du code Ada (comme à peu près tout le monde, j'arrive assez facilement à comprendre du code écrit dans à peu près n'importe quel langage dont la base est le procédural), je dois avouer que ma connaissance de ce langage n'est pas suffisante pour permettre de programmer avec.

    J''avais donc une cause connue et plusieurs solutions possibles pour arriver à modifier "plus ou moins correctement" le code, mais j'ai du agir par "tâtonnements successifs"

    Le problème, c'est que, pour savoir si la modification apportée résolvait le problème, j'étais obligé de... relancer l'intégralité du processus de compilation, et donc le bootstrap.

    Au total, c'est presque deux jours que j'ai perdu, en passant la majorité du temps à... attendre que la compilation échoue,

    Je suis d'accord que j'aurais pu renoncé au support de Ada et que j'aurais sans doute eu plus facile si j'avais été un tout petit peu plus habitué avec le langage, mais cette anecdote montre bien que, outre le temps passé à la modification même du code, tu dois parfois compter le temps qui sera nécessaire à t'assurer que la modification de celui-ci est correcte et cohérente.

    Ici, il n'y avait qu'un fichier à modifier, mais il fallait déjà deux heures de compilation pour que le fichier soit compilé et pour savoir si la modification était correcte.

    Si tu as un gros projet, avec une centaine de fichiers à modifier, et que tu en oublies, tu sera confronté exactement au même problème

    Peut être les fichiers à modifier seront ils compilés au début du processus de compilation, et ne perdra tu alors que "quelques secondes" à attendre l'échec de celle-ci.

    Mais tu cours aussi le risque que les fichiers nécessitant une modification ne soient compilés qu'à la fin du processus, avec donc une perte de temps incroyable du seul fait que... faut bien attendre que la compilation échoue
    A méditer: La solution la plus simple est toujours la moins compliquée
    Ce qui se conçoit bien s'énonce clairement, et les mots pour le dire vous viennent aisément. Nicolas Boileau
    Compiler Gcc sous windows avec MinGW
    Coder efficacement en C++ : dans les bacs le 17 février 2014
    mon tout nouveau blog

  16. #16
    Expert éminent sénior
    Homme Profil pro
    Développeur informatique
    Inscrit en
    Février 2005
    Messages
    5 074
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 52
    Localisation : France, Val de Marne (Île de France)

    Informations professionnelles :
    Activité : Développeur informatique
    Secteur : Conseil

    Informations forums :
    Inscription : Février 2005
    Messages : 5 074
    Points : 12 120
    Points
    12 120
    Par défaut
    Je ne suis pas encore pleinement confronté au problème mais je vais l'être sous peu.
    Rendre testable un projet force à avoir une conception qui insiste à un couplage lâche entre les différents composants de la solution. Cela permet de tester une partie de la solution et aussi de dispatcher le travail entre les différents membres de l'équipe.
    La testabilité mais en avant la conception des API qui seront les points d'entré des tests et permet de facilement refactorer les mécanismes et la conception internes d'un composant logiciel.
    Cela oblige donc à commencer par une conception qui divise la solution en composant et à spécifier les API entre ces composants.
    Les tests seront là pour valider que chaque composant fait son travail correctement.
    Je développe une interface graphique et ça va vite devenir lourd de valider la partie "calcul" (hors ihm) en l'utilisant.
    C'est le cas typique d'utilisation d'une architecture en couche. Vous devriez avoir un composant logiciel qui implémente l'IHM et un composant logiciel pour la partie calcule. Vous devriez donc avoir une API entre ces deux composants.
    Si vous êtes en charge de l'IHM, il est très simple de faire un composant logiciel qui implémente le côté calcul de l'API mais qui renvoie des valeurs qu'y sont pertinentes pour le cas de teste que vous êtes entrain d'effectué. C'est soit un composant "bouchon" s'il renvoie systématiquement la même chose, soit un "mock" si celui-ci est plus évolué, comme récupérer les valeurs à retourner depuis un fichier de configuration ou s'il mémorise l'ordre d'appel des fonctions de l'API.

    Je n'ai aucune expérience dans la validation de code objet sur un gros projet.
    Pour ta validation, tu crées des fonctions membres spécifiques à l'intérieur de la classe que tu veux valider ou des nouveaux objets spécifiques?
    Attention, la testabilité n'est pas de la validation de programme. La testabilité doit être maintenue depuis de début de la conception.
    La couverture des tests est un autre problème que bon nombres d'outils d'instrumentations de code tentent de fournir.

    J'ai vu aussi qu'il y a des modules spécifiques de tests unitaires dans les edi.
    C'est une bonne approche?
    Ces modules sont des outils pour accélérer la création et le passage automatique des tests mais il fait commencer par une conception qui permet d'en faire quelque chose d'utile.
    Avant les outils, il faut la démarche.
    - Conception modulaire
    - Séparation des responsabilités
    - etc.

Discussions similaires

  1. Réponses: 20
    Dernier message: 06/07/2009, 13h46
  2. Réponses: 0
    Dernier message: 26/06/2009, 11h18
  3. Réponses: 1
    Dernier message: 23/10/2008, 11h11
  4. Réponses: 5
    Dernier message: 31/05/2007, 13h10
  5. les boutons de deplacements dans les formullaire
    Par adil_math2006 dans le forum Access
    Réponses: 2
    Dernier message: 26/05/2006, 21h44

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