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++

Vue hybride

Message précédent Message précédent   Message suivant Message suivant
  1. #1
    Membre Expert
    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
    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
    Avatar de koala01
    Homme Profil pro
    aucun
    Inscrit en
    Octobre 2004
    Messages
    11 644
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 53
    Localisation : Belgique

    Informations professionnelles :
    Activité : aucun

    Informations forums :
    Inscription : Octobre 2004
    Messages : 11 644
    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 confirmé
    Homme Profil pro
    Développeur informatique
    Inscrit en
    Février 2005
    Messages
    5 488
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 53
    Localisation : France, Val de Marne (Île de France)

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

    Informations forums :
    Inscription : Février 2005
    Messages : 5 488
    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 Expert
    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
    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?

  5. #5
    Membre Expert
    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
    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.

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

    Informations professionnelles :
    Activité : aucun

    Informations forums :
    Inscription : Octobre 2004
    Messages : 11 644
    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 Expert
    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
    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
    Avatar de koala01
    Homme Profil pro
    aucun
    Inscrit en
    Octobre 2004
    Messages
    11 644
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 53
    Localisation : Belgique

    Informations professionnelles :
    Activité : aucun

    Informations forums :
    Inscription : Octobre 2004
    Messages : 11 644
    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

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