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 :

Maintenance de code


Sujet :

C++

  1. #1
    Membre du Club
    Profil pro
    Inscrit en
    Janvier 2008
    Messages
    89
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Janvier 2008
    Messages : 89
    Points : 50
    Points
    50
    Par défaut Maintenance de code
    Bonjour,
    J'ai écrit du code et au final, j'ai le sentiment que je peux "mieux faire" pour le rendre maintenable/réutilisable. Deux illustrations:
    - J'ai une classe "droite" (objet géométrique) dans laquelle je fait des calculs (normale de la droite, etc.) mais également de l'affichage (OpenGl) avec une méthode "Draw" de cette classe. Y a-t-il un moyen de dissocier les aspect "géométrie" des aspects affichage - par exemple si je souhaite utiliser la classe pour ne faire que des calculs géométrique, sans avoir à mettre toute l'artillerie de librairies Open GL dont je ne vais pas me servir ?
    - J'ai une autre méthode de calcul de courbe (Bezier) ou un "point" de la courbe est défini à partir d'autre points de contrôle. Mais ces "points" peuvent être soient des valeurs (0.5-1.0-2.3) ou des "points" géométriques (avec des coordonnées). Faut-il utiliser des templates ou un mécanisme d'héritage pour faire une classe générique? Où y a-t-il un autre moyen?
    Merci d'avance pour votre aide.
    Christian

  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,
    Citation Envoyé par coberle Voir le message
    Bonjour,
    J'ai écrit du code et au final, j'ai le sentiment que je peux "mieux faire" pour le rendre maintenable/réutilisable.
    C'est, typiquement, le genre de sentiment auquel il faut etre attentif
    Deux illustrations:
    - J'ai une classe "droite" (objet géométrique) dans laquelle je fait des calculs (normale de la droite, etc.) mais également de l'affichage (OpenGl) avec une méthode "Draw" de cette classe. Y a-t-il un moyen de dissocier les aspect "géométrie" des aspects affichage - par exemple si je souhaite utiliser la classe pour ne faire que des calculs géométrique, sans avoir à mettre toute l'artillerie de librairies Open GL dont je ne vais pas me servir ?
    Oui, il vaut d'ailleurs mieux séparer la responsabilité de la partie "métier" (calcul etc) de la partie "affichage"

    L'idéal est, très certainement, d'avoir une classe "Drawer" (ou tout autre nom qui indiquerait que sa responsabilité est le tracé ) qui prendrait ta droite (ou tout autre objet de type "objet géométrique" ) en paramètre et qui s'occuperait d'effectuer le tracer sur base d'informations récupérées depuis la droite

    Les mots qui te seraient très utiles dans ce cas sont visiteur (design pattern), double dispatch et polymoprhisme
    - J'ai une autre méthode de calcul de courbe (Bezier) ou un "point" de la courbe est défini à partir d'autre points de contrôle. Mais ces "points" peuvent être soient des valeurs (0.5-1.0-2.3) ou des "points" géométriques (avec des coordonnées). Faut-il utiliser des templates
    La manière dont la position d'un point (dans un système) est représentée s'aparente très clairement à ce que l'on appelle "une politique".

    Tu vas, en effet, utiliser des calculs et des procédures différentes pour arriver à positionner un point par rapport aux autres en fonction de la manière dont sa position est représentée, même si, au final, tu peux finir par appeler une fonciton comme "position()"

    L'idéal est donc de partir d'une approche générique, pour ce qui se rapporte à la politique de placement et à ses traits particuliers, en tout cas
    ou un mécanisme d'héritage pour faire une classe générique?
    Il y aura, très certainement, des mécanismes d'héritage, voir des mécanismes d'héritage avancés (une classe non template qui hérite (sans doute en utilisant le CRTP) d'une classe template, une autre classe template qui hérite d'une quatrième classe qui ne l'est pas, ...)

    N'oublie cependant pas que l'héritage reste la relation la plus forte qui puisse unir deux classes, et qu'elle se doit donc de respecter un certain nombre de règles, potentiellement différentes en fonction de ce qui hérite de quoi
    Où y a-t-il un autre moyen?
    Il ne faut en tout cas pas mettre une frontière trop ferme et définitive du genre "je travaille en orienté objet, et j'exclus tout recours au paradigme générique" ou de son contraire "j'utilise le paradigme générique et je m'interdit le recours à l'orienté objet"...

    La meilleure manière consistera sans doute à mélanger très allègrement les deux de sorte à profiter du meilleur des deux mondes

    Je suis désolé d'être tellement imprécis, mais, étant donné que tu n'explique ni tes objectifs ni l'existant de manière précise, tu ne nous offre pas beaucoup de choix
    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
    Membre du Club
    Profil pro
    Inscrit en
    Janvier 2008
    Messages
    89
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Janvier 2008
    Messages : 89
    Points : 50
    Points
    50
    Par défaut
    Merci Koala01 pour ta réponse (très) détaillée. Etant encore débutant, les mots de
    visiteur (design pattern), double dispatch et polymoprhisme
    sont encore une peu "abscon". Si tu as des exemple (ou des liens), je suis évidemment preneur.
    Est-ce que ce que tu suggères est de créer une fonction draw(Objet), où "Objet" et une droite, un point, une courbe, etc. Dans ce cas, il faudrait que droite, point, courbe etc. soient tous fils/fille de Objet? Comment ensuite "récupérer" le type dans draw de façon ce que draw "sache" comment dessiner l'objet ... où est-ce que je m'égare?

    Pour ce qui est du 2e point, je ne suis pas certain - à la lecture de ta réponse - que ma question était très claire. Pour faire simple, imaginons qu'un point P soit défini par P(t)=A*t + B(1-t) (où A et B sont deux autres points et t entre 0 et 1). On voit bien en fait que A et B (et donc P) peuvent être des point au sens géométrique mais aussi des valeurs (par exemple p(t)=3*t+5*(1-t)). Ce que je souhaite faire est une fonction "générique" qui permet de calculer Objet_P(t)=Objet_A*t+Objet_B(1-t) où Objet_A et Objet_B sont soit des valeurs, soit des points et que le type de l'objet P est soit un point, soit une valeur (en fonction du type de A et B).

    Encore merci pour tes conseils.
    Christian

  4. #4
    Membre éclairé
    Avatar de Ekleog
    Homme Profil pro
    Étudiant
    Inscrit en
    Janvier 2012
    Messages
    448
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France

    Informations professionnelles :
    Activité : Étudiant

    Informations forums :
    Inscription : Janvier 2012
    Messages : 448
    Points : 879
    Points
    879
    Par défaut
    Citation Envoyé par coberle Voir le message
    Merci Koala01 pour ta réponse (très) détaillée. Etant encore débutant, les mots de sont encore une peu "abscon". Si tu as des exemple (ou des liens), je suis évidemment preneur.
    Google peut t'aider, pour ça.

    Est-ce que ce que tu suggères est de créer une fonction draw(Objet), où "Objet" et une droite, un point, une courbe, etc. Dans ce cas, il faudrait que droite, point, courbe etc. soient tous fils/fille de Objet? Comment ensuite "récupérer" le type dans draw de façon ce que draw "sache" comment dessiner l'objet ... où est-ce que je m'égare?
    Une surcharge, sans prendre en compte ici l'héritage, me semblerait plus pratique, et surtout plus dans l'esprit c++ qu'un dispatch à l'exécution.

    Pour ce qui est du 2e point, je ne suis pas certain - à la lecture de ta réponse - que ma question était très claire. Pour faire simple, imaginons qu'un point P soit défini par P(t)=A*t + B(1-t) (où A et B sont deux autres points et t entre 0 et 1). On voit bien en fait que A et B (et donc P) peuvent être des point au sens géométrique mais aussi des valeurs (par exemple p(t)=3*t+5*(1-t)). Ce que je souhaite faire est une fonction "générique" qui permet de calculer Objet_P(t)=Objet_A*t+Objet_B(1-t) où Objet_A et Objet_B sont soit des valeurs, soit des points et que le type de l'objet P est soit un point, soit une valeur (en fonction du type de A et B).
    Comment fais-tu pour multiplier un point par une valeur ?
    Après avoir répondu à cette question, tu pourras certainement mieux répondre à ta propre question.

    Et, si tu éloignes de l'origine d'un facteur k ((x, y) * k = (x * k, y * k)), alors simplement coder ta "valeur" par un point sur l'axe des réels peut être envisageable.

    Une autre solution pourrait être de définir P comme :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    template <typename T, typename Objet>
    T P(T const & t, Objet const & objet_a, Objet const & objet_b) {
      return objet_a * t + objet_b * (1 - t);
    }

  5. #5
    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
    Le principe du visiteur est, sommes toutes, relativement simple...

    On peut considérer que tu disposes sans doute d'une hiérarchie d'objets géométriques.

    Cette hiérarchie est à considérer comme une hiérarchie d'objets "visitables" (comprends : qui peut etre visitée).

    A coté de cette hiérarchie, tu aurais un objet "visiteur" dont le role est... de visiter les différentes classes visitables.


    Pour que tous les objets soient visitables, la classe de base se présenterait sans doute sous une forme proche de
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    class ObjetGeometrique
    {
        public:
            void accept(Visiteur /* const*/ & ) /* const  */= 0;
    };
    Toutes les classes dérivées réimplémenteraient cette fonction de manière à appeler une fonction du visiteur qui se charge de faire le traitement propre à la classe en question:
    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
    class Ligne : public ObjetGeometrique
    {
        public:
            void accept(Visiteur /* const */ & v) /* const*/
            {
                v.visit(*this);
            }
    };
    class Carre : public ObjetGeometrique
    {
        public:
            void accept(Visiteur /* const */ & v) /* const*/
            {
                v.visit(*this);
            }
    };
    class Triangle: public ObjetGeometrique
    {
        public:
            void accept(Visiteur /* const */ & v) /* const*/
            {
                v.visit(*this);
            }
    };
    class Bézier: public ObjetGeometrique
    {
        public:
            void accept(Visiteur /* const */ & v) /* const*/
            {
                v.visit(*this);
            }
    };
    et le visiteur dispose d'une surcharge de la fonction visit pour chaque type particulier d'objet géométrique à visiter:
    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
    class Visitor
    {
        public:
            void visit(Ligne /* const */ & l ) /* const
            {
                 /*traitement propre à la ligne */
            } 
            void visit(Carre/* const */ & c) /* const
            {
                 /*traitement propre à la ligne */
            } 
            void visit(Triangle /* const */ & t ) /* const
            {
                 /*traitement propre à la ligne */
            } 
            void visit(Bezier /* const */ & b ) /* const
            {
                 /*traitement propre à la ligne */
            } 
    };
    Dés lors, quand tu vas avoir une collection "d'objet géométriques" dans laquelle tu risques de retrouver aussi bien des lignes, des carrés ou des triangles ou des courbes de béziers, et que tu veux traiter "par lots" (comprends : tu veux traiter l'ensemble des éléments contenus dans la collection en question), tu pourras travailler sous une forme proche de
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    int main()
    {
        std::vector<ObjetGeometrique *> tab;
        /* on remplit tab d'un tas d'objets de type carre, ligne etc... */
        Visiteur v;
        for(std::vector<ObjetGeometrique*>::const_iterator it = tab.begin();
            it!=tab.end(); ++it)
        {
            (*it)->accept(v);
        }
        /* ... */
    }
    En théorie, c'est génial

    En pratique ca l'est aussi... tant que l'on en décide pas de transformer la classe Visiteur en hiérarchie de classes

    En effet, tant que tu n'as qu'un visiteur, il n'y a pas de problème:

    Si tu décide de rajouter une classe "Rectangle" qui hérite de ObjetGeometrique, tu n'as qu'à rajouter la surcharge pour cette classe, et tout va pour le mieux.

    Par contre, si tu te dit "oui, mais, le système qui trace mes objets est un visiteur, mais le système qui (... ) l'est aussi, ainsi que celui qui (...) et que celui qui (...) ", tu risques d'être tenté de dériver trois, quatre, dix fois (ou plus ) ton visiteur pour prendre en compte chaque type de gestion qui pourrait te venir à l'esprit.

    Le problème est alors que tu ne pourras pas envisager de rajouter une classe de type ObjetGeometrique sans passer par tous les visiteurs dérivés pour rajouter la surcharge ad-hoc...

    Y compris pour les visiteurs pour lesquels ce genre de surcharge n'a pas lieu d'être

    Tout cela pour dire que le principe est génial, mais qu'il faut vraiment être attentif à ce que l'on fait, et, surtout, à la granularité des responsabilités que l'on va donner à ses classes de bases
    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

  6. #6
    Rédacteur/Modérateur
    Avatar de JolyLoic
    Homme Profil pro
    Développeur informatique
    Inscrit en
    Août 2004
    Messages
    5 463
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 49
    Localisation : France, Yvelines (Île de France)

    Informations professionnelles :
    Activité : Développeur informatique
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Août 2004
    Messages : 5 463
    Points : 16 213
    Points
    16 213
    Par défaut
    Citation Envoyé par coberle Voir le message
    - J'ai une autre méthode de calcul de courbe (Bezier) ou un "point" de la courbe est défini à partir d'autre points de contrôle. Mais ces "points" peuvent être soient des valeurs (0.5-1.0-2.3) ou des "points" géométriques (avec des coordonnées). Faut-il utiliser des templates ou un mécanisme d'héritage pour faire une classe générique? Où y a-t-il un autre moyen?
    Je n'arrive pas en lisant cette partie à savoir de quel côté de la frontière tu te situe. Quelle frontière ? Celle entre l'interface et l'implémentation.

    Est-ce que tu veux que tes points soient représentés en interne de manière variable, ou est-ce que tu veux que bien qu'utilisant une représentation unique, ils puissent être manipulés par des méthodes différentes.

    Le second cas a a priori ma préférence, pour des raisons de simplicités. Tu choisis une bonne fois pour toute la représentation (par exemple, deux coordonnées cartésiennes), et tu fournis les moyens de créer des points dans cette représentation en fonction de tes besoins :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    Point Bezier::getPointAtCurvilinearAbscissa(double u);
    Point getCenterOfGravity(vector<pair<Point, double>> weightedPoints);
    ...
    Dans le premier cas, tu vas devoir implémenter les différents modes de représentation (et en plus les passages d'un mode à l'autre). Quel serait l'intérêt ? Simplement si certains algorithmes peuvent s'implémenter de manière différente dans un mode ou dans un autre, il peut être intéressant de ne pas perdre de l'information en passant dans une représentation unique.

    Si tel est ton cas, se pose la question de comment quand même gérer de manière unifiée différentes classes de points aux représentation différente (je dis unifiée, pas unique. Par exemple avec des fonctions aux interfaces identiques, mais aux implémentation tirant éventuellement partie des spécificités des représentations). C'est ce que l'on nomme le polymorphisme.

    Et dans ce cas, deux classes de polymorphisme sont potentiellement bien adaptées, qui se représentent en C++ par l'héritage+fonctions virtuelles, ou par les templates.

    Héritage + fonction virtuelles, c'est adapté si tu vas mélanger des points aux représentations différentes, par exemple appeler le même algorithme à tous les points d'un tableau, mais chaque point de ce tableau peut avoir sa propre représentation, qui éventuellement n'est pas connue à la compilation du programme (par exemple, lue dans un fichier). Inconvénients : Ça va t'obliger à passer par des pointeurs (lourdeur d'écriture, risques de fausses manip, coûts d'utilisation) et le choix entre les différents algo se fera lors de l'exécution (coût runtime).

    Templates, c'est adapté si tu veux écrire des algorithmes haut niveau qui puissent être utilisé avec différent modes de représentation, mais qu'au moment de l'appel des ces algorithmes, dans un programme particulier, on sait quel mode de représentation sera utilisé. C'est bien pour faire des bibliothèques réutilisables dans différents contextes. Le choix des algos bas niveaux finalement utilisés aura lieu totalement à la compilation (gratuit au runtime). On ne manipule pas de pointeurs. On gagne en perfs ce qu'on perd en flexibilité.

    Mais n'oublie pas : Il y a des chances que dans ce cas, une seule représentation mais des moyens de création multiple soit la bonne solution... Ne pas faire de l'overdesign.
    Ma session aux Microsoft TechDays 2013 : Développer en natif avec C++11.
    Celle des Microsoft TechDays 2014 : Bonnes pratiques pour apprivoiser le C++11 avec Visual C++
    Et celle des Microsoft TechDays 2015 : Visual C++ 2015 : voyage à la découverte d'un nouveau monde
    Je donne des formations au C++ en entreprise, n'hésitez pas à me contacter.

  7. #7
    Membre du Club
    Profil pro
    Inscrit en
    Janvier 2008
    Messages
    89
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Janvier 2008
    Messages : 89
    Points : 50
    Points
    50
    Par défaut
    Merci à tous pour vos réponses détaillées - je crois que j'ai à "manger" pour plusieurs jours :-).
    Quelques commentaires/précisions:
    - Ekleog
    Comment fais-tu pour multiplier un point par une valeur ?
    -> Je multiplie chaque coordonnée du point
    - LolyLoic
    Je n'arrive pas en lisant cette partie à savoir de quel côté de la frontière tu te situe. Quelle frontière ? Celle entre l'interface et l'implémentation.
    -> "les deux mon général".
    Au final, les avis que vous donnez sont très instructifs et montrent que, pour un problème donné, il n'y a pas de solutions unique (d'une certaine façon, tant mieux, sinon on ne serait pas là :-))
    Encore merci ! Je vais essayer de réfléchir à tout ça et mettre en musique.
    Christian

Discussions similaires

  1. 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
  2. le C++ "moderne" et la maintenance de code
    Par yan dans le forum C++
    Réponses: 21
    Dernier message: 22/08/2009, 17h42
  3. [DW MX2004] Maintenance de Code HTML commun
    Par codenstock dans le forum Dreamweaver
    Réponses: 1
    Dernier message: 14/09/2007, 22h30
  4. l'astuce date et heure de maintenant sans formule ni code
    Par zazaraignée dans le forum Contribuez
    Réponses: 5
    Dernier message: 14/08/2007, 13h54

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