Partage
  • Partager sur Facebook
  • Partager sur Twitter

Organisation et développement d'un projet.

    3 août 2020 à 12:27:34

    Salut !

    Ca fait quelques jours que j'ai commencé mon premier projet concret. Il s'agit d'un bibliothèque general purpose minimaliste qui puisse fournir aux développeurs l'essentiel de ce dont on a besoin pour ne pas ré-inventer la roue: différents types de conteneurs, handles, algorithmes etc.

    Le développement se déroule plutôt bien, et j'ai les idées *à peu près* claires sur ce que je veux. Le problème se trouve dans l'organisation du projet. Je n'ai jamais suivi quelconque formation sur cet aspect du génie logiciel (comment ranger les fichiers et ce genre de choses). Je ne sais pas non plus comment gérer mon projet sur github, je connais les commandes principales (add, rm, commit, push, pull), mais je ne sais pas comment organiser mes commits, ni quand je suis réellement censé le faire. A ajouter que c'est un petit projet, qui ne contient pas beaucoup de fichiers.

    Voici ma question: Comment organisez-vous vos projet ? Quel dossier dédié pour quelles entités du projet ? Comment gérez-vous le rangements de vos différents fichiers, grosso-modo ? Comment gérez vous les différents commits ? J'ai conscience que votre façon de faire n'est pas forcément universelle mais je me contenterai volontiers d'essayer votre modèle.

    Voilà ! Je suis complètement perdu sur ce point. Je serais ravi de recevoir un lien vers un article, votre avis personnel etc. Merci !

    P.S.: Un lien vers le projet.

    -
    Edité par Daimyo_ 3 août 2020 à 12:30:49

    • Partager sur Facebook
    • Partager sur Twitter
      3 août 2020 à 13:25:01

      Lu',

      La structuration d'un projet est personnelle, voire décidée communément si il s'agit d'un projet où bossent plusieurs personnes.

      Personnellement j'utilise toujours la même structuration :

      - RACINE_PROJET
        |- .gitignore
        |- CMakeLists.txt
        |- CMakeSettings.json -> config CMake de Visual Studio
        |- LICENSE
        |- ReadMe.md
        |- Tools/
        |   |- CMake/ -> ici je range tous les fichiers .cmake
        |   |- Manifests/ -> ici tous les fichiers manifests selon les plateformes
        |   |- Solution/ -> ici y a tous les fichiers Python pour générer facilement le fichier solution de l'IDE
        |- LE_NOM_DU_PROJET
        |   |- CMakeLists.txt
        |   |- Public/ -> Contient tous les fichiers en-têtes qui vont être redistribués
        |   |- Private/ -> Contient les fichiers sources + en-têtes non redistribués
        |   |- Production/ -> Contient toutes les config, les assets et les ressources utilisés par le projet
        -   -

      Utiliser la même organisation de fichiers permet de s'y retrouver facilement :)

      Pour les commits, j'en fait quand j'en ai besoin : grosse modification (beaucoup de lignes), modification importante(correction de bug importants), lorsque je dois arrêter le boulot. C'est les trois raisons principales pour lesquels je commit. Après il faut le faire aussi pour assurer une bonne continuité du projet si tu travailles en équipe.

      • Partager sur Facebook
      • Partager sur Twitter
        3 août 2020 à 14:54:40

        Pour ma part je fais un répertoire par composant (bibliothèque, executable, test, données)

        Par exemple pour mon jeu en cours :

        • Makefile
        • assets
        • assets/icons
        • assets/images
        • assets/music
        • src/
        • src/foo.c
        • src/foo.h
        • tests/
        • tests/test-1.c
        • tests/test-2.c
        • tools/
        • tools/bcc.c
        • Partager sur Facebook
        • Partager sur Twitter

        git is great because Linus did it, mercurial is better because he didn't.

          3 août 2020 à 15:37:22

          Pour une librairie, voici une structure possible assez classique:

          • assets/
          • docs/
          • examples/
          • include/
          • src/
          • tests/
          • tools/
          • .editorconfig
          • .gitattributes
          • .gitignore
          • CMakeLists.txt
          • LICENSE
          • README.md

          Dans include, tu mets les headers publics (l'API de ta librairie en clair). Je te conseille de "namespacer" tes headers avec un répertoire du nom de ta librairie, ça évite les clashs avec les headers publics d'autres libs.

          Dans src (que tu peux renommer du nom de ta librairie aussi, c'est classique) tout le code source privé. On y retrouve bien sûr les .cpp, mais aussi les headers privés.

          Dans tools, tu peux y mettre des utilitaires (des petits outils en ligne de commande) qui ont un rapport avec ta librairie. Bien sûr si cela n'a pas de sens, tu ne mets pas ce répertoire.

          -
          Edité par SpaceIn 3 août 2020 à 15:49:45

          • Partager sur Facebook
          • Partager sur Twitter
            3 août 2020 à 16:08:36

            Salut,

            L'organisation "physique" d'un projet est, en grande partie, un histoire de gouts et de système de build.

            C'est une histoire de goûts dans le sens où il n'y a pas de règle bien spécifique, et que chacun peut décider de l'organisation selon ses propres préférences (quitte à ce que ce soit un "compromis" entre les différents développeurs).

            Et c'est une histoire de système de build parce que la plupart des EDI et des systèmes de configuration de projets vont "naturellement" te proposer une organisation qui leur est relativement propre (mais que tu peux "surpasser", dans une certaine mesure, du moins).

            Cependant, il est généralement de "bon ton" de séparer les torchons et les serviettes, histoire de s'y retrouver "un peu plus facilement" dans le fouillis de fichiers que le projet finira par devenir, et à  ce titre, on peut déjà se dire que l'on aura au minium trois sortes de fichiers:

            • les fichiers destinés à fournir les fonctionnalités propres du projet (bilbliothèques, éventuellement applications)
            • les fichiers de tests (unitaires et / ou d'intégration)
            • les fichiers de documentation

            Et l'on peut décemment estimer que ces trois sortes de fichiers devraient séparées au niveau de ton projet.  On peut donc décemment estimer que l'organisation "de base" devrait prendre une forme proche de

            <dossier racine>
            |-> Applications (s'il y en a)
            |-> Documentation (s'il y en a)
            |-> bibliothèques (le plus souvent, il sera nommé "lib" :D)
            |-> tests (s'il y en a)

            On n'est, bien sur, pas du tout obligé de se limiter à ces quatre dossiers de base, et on peut, si le besoin s'en fait sentir, rajouter un dossier "Examples" qui fournira le code de quelques exemple de l'utilisation "classique" des fonctionnalités, voire un dossier "sandbox" dans lequel nous placerons des applications "bac à  sable" avec lesquelles nous pourrions nous amuser à "faire joujou" pour nous assurer que "tout fonctionne correctement" ;)

            Bien sur, chacun de ces dossiers peut contenir des "sous dossiers":

            • pour les applications, tu peux  parfaitement en avoir une "serveur" et une autre "client"
            • pour les bibliothèques, il est sans doute préférable de séparer clairement ce qui est le "corps de métier" de la représentation graphique ou de la transmission sur le réseau
            • Pour ce qui est des tests et de la documentation, il peut s'avérer utile de garder une organisation à peu près similaire à celle des bibliothèques et des applications

            En outre, si le projet a des dépendances externes, on peut aussi envisager de les "intégrer" au projet sous une forme ou une autre (en les séparant cependant de "tout le reste"), ce qui pourrait nous mener à une organisation ressemblant à  (les noms restent à ta discrétion bien sur :D )

            <dossier racine>
            |-> 3rdParty    # contient les dépendances externes, par exemple
            |    |->catch2  # une bibliothèque légère pour les tests unitaires
            |    |    |-> # l'organisation spécifique de catch2
            |    |->spdlog  # une bibliothèque de logging
            |    |    |-> # l'organisation spécifique de catch2
            |    |-> ...    # toutes les autres dépendances
            |-> Applications   
            |    |->client  # la partie "client"
            |    |-> server # la partie "serveur"
            |-> Documentaiton
            |    |->client  # documentation spécifique à l'application client
            |    |->lib1    # documentation spécifique à lib1
            |    |->libN    # documentation spécifique à libN
            |    |->server  # documentation spécifique à l'application serveur
            |-> Exapmples
            |    |->lib1    # exemple d'utilisation de la bibliothèque lib1
            |    |->libN    # exemple d'utilisation de la bibliothèque libN
            |-> lib
            |    |-> lib1   # le code spécifique à la bibliothèque lib1
            |    |-> libN   # le code spécifique à la bibliothèque libN
            |->tests
            |    |->client  # tests unitaires spécifiques à l'application client
            |    |->lib1    # tests unitaires spécifiques à lib1
            |    |->libN    # tests unitaires spécifiques à libN
            |    |->server  # tests unitaires spécifiques à l'application serveur

            Et, bien sur, chacun de ces sous dossier peut lui-même contenir un ou plusieurs sous dossiers.

            Par exemple, j'apprécie personnellement beaucoup de pouvoir séparer mes fichiers d'implémentation (*.cpp) de mes fichiers d'en-tête (*.hpp), et donc, la plupart de ces sous dossiers vont prendre une forme proche de

            <sous dossier>
            |-> include  # l'ensemble des fichiers d'en-tête
            |-> src      # l'ensemble des fichiers d'implémentation

            Voire, si je veux -- par exemple -- rendre l'utilisation d'une dépendance externe aussi transparente que possible pour l'utilisateur (en créant une interface propre au projet) une organisation qui deviendra proche de

            <sous dossier>
            |-> include  # l'ensemble des fichiers d'en-tête
            |-> src         # l'ensemble des fichiers d'implémentation
            |    |->directX # parce qu'on ne travaille pas avec OpenGL 
            |    |->openGL  # comme on le ferait avec DirectX

            Mais tout cela ne prend pas encore en compte le système de build ou l'EDI qui sera utilisé pour le développement.

            Or, à ce stade, nous nous retrouvons déjà avec pas mal de projets, car, dans l'idéal, il nous faut:

            • une "solution globale" capable de tout générer de A à Z (et, accessoirement, d'exécuter les tests unitaires et / ou de générer un installateur pour l'ensemble)
            • un projet pour la génération de chacune des applications
            • un projet pour la génération de chacune des bibliothèque
            • un projet pour la génération de chacun des examples
            • un projet pour la génération des tests unitaires de chaque partie prise séparément
            • (idéalement) un projet pour la génération de la documentation de chaque partie prise séparément
            • (idéalement) un projet pour la génération de l'ensemble des tests unitaires
            • (idéalement) un projet pour la génération de l'ensemble de la documentation
            • un projet pour la génération des dépendances

            Ouff ... sans l'écrire sous cette forme, on ne se douterait pas qu'il nous faille autant de projets différents, n'est-ce pas?

            Et c'est là que les choses vont commencer à se compliquer, parce que chaque système de build va avoir ses petites manies:

            • Visual Studio va générer un projet fichier *.vcxroj par projet + un fichier *.sln pour la solution "globale"
            • Si on utilise qmake, on a besoin d'un fichier *.pro pour la solution globale + 1 fichier *.pro pour chaque projet (sans compter les fichier *.pri qui pourraient être inclus ici et là :P)
            • Si on utilise CMake, on reste "relativement libre", soit de faire un méga fichier CMakeLists.txt qui reprend l'ensemble (ce que je déconseille), soit de faire un fichier CMakeLists.txt pour la solution globale et un fichier CMakeLists.txt (dans les (sous) dossiers) pour chacun des projets, mais nous voudrons sans doute aussi ajouter quelques modules de gauche et de droite :p
            • CodeBlocks ou Eclipse vont, quant à eu, avoir leur propre système de description de projet :p

            Or, comme je l'ai dit, je préfères de loin éviter -- autant que possible -- de mélanger les fichiers de types différents, ce qui fait que je n'aime pas trop l'idée d'avoir un fichier CMakeLists.txt (par exemple) qui "traine" au milieu de mes fichiers d'implémentations, qui seraient eux-même mélangés avec mes fichiers d'en-tête :p

            Si bien que mes projets vont au final prendre une forme qui ressemble à quelque chose comme

            <dossier racine>
            |-> 3rdParty    # contient les dépendances externes, par exemple
            |    |->catch2  # une bibliothèque légère pour les tests unitaires
            |    |    |-> # l'organisation spécifique de catch2
            |    |->spdlog  # une bibliothèque de logging
            |    |    |-> # l'organisation spécifique de catch2
            |    |-> ...    # toutes les autres dépendances
            |    |->CMakeLists.txt # pour générer les dépendances
            |->cmake        #pour mes scripts perso avec CMake et autres joyeuseté
            |    |-> XXX.cmake
            |    |-> YYY.cmake
            |-> Applications   
            |    |->client  # la partie "client"
            |    |    |-> include # les fichiers d'en-tête (s'il y en a)
            |    |    |-> src     # les fichiers d'implémentation
            |    |    |->CMakeLists # pour générer l'application client
            |    |-> server # la partie "serveur"
            |    |    |-> include # les fichiers d'en-tête (s'il y en a)
            |    |    |-> src     # les fichiers d'implémentation
            |    |    |->CMakeLists # pour générer l'application serveur
            |    |->CMakeLists # pour générer toutes les applications
            |-> Documentaiton
            |    |->client  # documentation spécifique à l'application client
            |    |->lib1    # documentation spécifique à lib1
            |    |->libN    # documentation spécifique à libN
            |    |->server  # documentation spécifique à l'application serveur
            |    |->CMakeLists # pour générer la documentation (générale)
            |-> Exapmples
            |    |->lib1    # exemple d'utilisation de la bibliothèque lib1
            |    |    |->ex1 # un premier exemple
            |    |    |    |-> include # les fichiers d'en-tête (s'il y en a)
            |    |    |    |-> src     # les fichiers d'implémentation
            |    |    |    |->CMakeLists # pour générer l'exemple
            |    |    |->ex2 # un autre exemple
            |    |    |    |-> include # les fichiers d'en-tête (s'il y en a)
            |    |    |    |-> src     # les fichiers d'implémentation
            |    |    |    |->CMakeLists.txt # pour générer l'autre exemple
            |    |    |-> CMakeLists.txt # pour générer l'ensemble des exemples relatif à Lib1
            |    |->libN    # exemple d'utilisation de la bibliothèque libN
            |    |    |->ex1 # un premier exemple
            |    |    |    |-> include # les fichiers d'en-tête (s'il y en a)
            |    |    |    |-> src     # les fichiers d'implémentation
            |    |    |    |->CMakeLists # pour générer l'exemple
            |    |    |->ex2 # un autre exemple
            |    |    |    |-> include # les fichiers d'en-tête (s'il y en a)
            |    |    |    |-> src     # les fichiers d'implémentation
            |    |    |    |->CMakeLists.txt # pour générer l'autre exemple
            |    |    |-> CMakeLists.txt # pour générer l'ensemble des exemples relatif à LibN
            |-> lib
            |    |-> lib1   # le code spécifique à la bibliothèque lib1
            |    |    |    |-> include # les fichiers d'en-tête 
            |    |    |    |-> src     # les fichiers d'implémentation
            |    |    |    |->CMakeLists.txt # pour générer lib1
            |    |-> libN   # le code spécifique à la bibliothèque libN
            |    |    |    |-> include # les fichiers d'en-tête 
            |    |    |    |-> src     # les fichiers d'implémentation
            |    |    |    |-> CMakeLists.txt # pour générer libN
            |    |    |-> CMakeLists.txt # pour générer l'ensemble des bibliothèques
            |->tests
            |    |->client  # tests unitaires spécifiques à l'application client
            |    |    |-> include # les fichiers d'en-tête (s'il y en a)
            |    |    |-> src     # les fichiers d'implémentation
            |    |    |->CMakeLists.txt # pour générer les tests relatifs à l'application client
            |    |->lib1    # tests unitaires spécifiques à lib1
            |    |    |-> include # les fichiers d'en-tête (s'il y en a)
            |    |    |-> src     # les fichiers d'implémentation
            |    |    |->CMakeLists.txt # pour générer les tests relatifs à lib1
            |    |->libN    # tests unitaires spécifiques à libN
            |    |    |-> include # les fichiers d'en-tête (s'il y en a)
            |    |    |-> src     # les fichiers d'implémentation
            |    |    |->CMakeLists.txt # pour générer les tests relatifs à lib2
            |    |->server  # tests unitaires spécifiques à l'application serveur
            |    |    |-> include # les fichiers d'en-tête (s'il y en a)
            |    |    |-> src     # les fichiers d'implémentation
            |    |    |->CMakeLists.txt # pour générer les tests relatifs à l'application serveur
            |    |->CMakeLists.txt # pour générer l'ensemble des tests
            |->CMakeListst.txt    #pour générer l'ensemble de la solution
            |->ChangeLog.txt  # rappel des grandes étapes
            |->LICENSE   # souvent MIT pour ma part
            |->README.md # décrit le projet
            |-> (manquent les fichiers utilisés par git  )

            (Ca fait peur, vu comme cela, non ??? :P)

            Daimyo_ a écrit:

            Comment gérez vous les différents commits ?

            Là encore, il n'y a pas de règles préétablies.  La seule chose est que git est un outil (parmi tant d'autres) et que son principal objectif est de te simplifier la vie ;)

            La seule "règle" à peu pres unanime est que le message des commits doit représenter une action et qu'il doit donc "idéalement" prendre la forme d'un groupe verbal:

            "ajouter <telle ou telle fonctionnalité>"

            "corriger <tel ou tel bug>"

            "améliorer <tel ou tel algorithme>

            Pour le reste, on peut faire la distinction entre l'utilisation "locale" et l'utilisation "serveur".

            En utilisation locale, je te dirais d'effectuer un commit à chaque fois que tu obtiens un "résultat probant".

            Pour ma part, ma description "perso" de "résultat probant" signifie:

            1. quand ma modification (mon ajout) compile correctement et
            2. qu'un test rapide semble indiquer qu'elle fonctionne de la manière attendue

            Cependant, j'essaye "dans la mesure du possible" de rendre mes commits "transactionnels".  Ce que je veux dire par là, c'est si je dois, pour pouvoir ajouter une fonctionnalité ou corriger un problème, de devoir commencer par corriger un "petit problème sous jacent" (comme le fait d'ajouter une option à CMake ou d'améliorer un de ses scripts).

            Quand cela m'arrive, je vais essayer (dans la mesure du possible) de faire plusieurs commits:

            • un pour valider les modifications apportées à cmake
            • un autre pour valider les modifications / ajout au code même du projet

            Bien sur, il s'agit de mon approche personnelle du problème, et d'autres pourraient avoir un avis différent à  ce sujet ;)

            Pour ce qui est du transfert vers le serveur (push): La règle générale devrait être que la branche master doit être livrable en permanence: tu peux, par ton push, casser à  peu près n'importe quelle autre branche, mais surout pas la branche master :p

            J'ai, récemment, découvert le principe de git flow, dont le principe va même plus loin:

            Avec git flow, on a en permanence deux branches principales:

            • la branche "master", qui correspond à la version en exploitation du projet (qui sera généralement "taguée")
            • la branche "develop", qui correspond à  "la prochaine version", en développement, du projet.
            Ces deux branches doivent en permancence pouvoir être validées par les systèmes d'intégration continue, c'est à dire qu'elles doivent à tout le moins:
            • compiler sans aucun problème et
            • passer l'ensemble des tests unitaires qu'elles contiennent avec succès
            (idéalement, on devrait configurer le système d'intégration continue pour effectuer au moins un build journalier de la branche develop ;) )

            A priori, on ne fait aucun commit sur directement sur ces branches, car, en plus de ces deux branches, on va créer, selon les besoins :

            • une branche "release" qui nous permettra de faire les "derniers ajustements" juste avant la mise en exploitation de la prochaine version
            • une (ou plusieurs) branche "feature" qui nous permettra/permettront de développer les "fonctionnalits" qui seront intégrées à la prochaine version (ou à la suivant :P)
            • une ou plusieurs branches "bugfix" qui nous permettra / permettront de corriger les bugs en "pre release"
            • (rarement plus d')une branche "hotfix" (à la fois), qui nous permettra, en cas de besoin, de corriger un bug dans la version en exploitation
            • Eventuellement une (ou plusieurs) branches "support" destinée(s), en cas de besoin, au "support à long terme" des versions antérieures

            L'idée derrière tout cela étant d'appliquer systématiquement nos modifications sur des branches séparées, et de n'envoyer ces modifications (vers la branche develop ou master, selon le cas) qu'une fois que l'on est sur de leur "inoquité" par rapport au reste; l'intégration aux branches master et develop se faisant, généralement, en mergeant / rebasant les branches en question.

            Il est à noter que le système git flow est intégré au moins à la version  "non github" de git que l'on trouve ==>ici<== et que c'est sommes toutes très facile  à configurer ;)

            Par contre, je ne sais pas si l'outil proposé par github fourni cette possibilité, l'ayant "abandonné" depuis quelques temps déjà :p

            • Partager sur Facebook
            • Partager sur Twitter
            Ce qui se conçoit bien s'énonce clairement. Et les mots pour le dire viennent aisément.Mon nouveau livre : Coder efficacement - Bonnes pratiques et erreurs  à éviter (en C++)Avant de faire ce que tu ne pourras défaire, penses à tout ce que tu ne pourras plus faire une fois que tu l'auras fait
              3 août 2020 à 16:55:19

              Je suis d'accord avec tout ce que les autres ont dit mais attention à l'over-engineering. Tu veux faire les choses bien et ça se voit : Tu as mis une license, tu utilises CMake, tes commentaires sont formattés pour la génération de doc avec Doxygen (doc qui est où d'ailleurs ? :-°), etc...

              Je suis un peu perfectionniste et je pense que toi aussi donc je sais ce que tu ressens. Tu as envie de faire tout juste du premier coup et cela peut rapidement donner lieu à un blocage (https://en.wikipedia.org/wiki/Analysis_paralysis#:~:text=Analysis%20paralysis%20(or%20paralysis%20by,of%20action%20is%20decided%20upon).

              Mon conseil : Si c'est un projet perso, l'objectif N°1 c'est de s'amuser un minimum et si le fait de développer ta biblio devient une corvée (à cause de choses que tu t'es imposées), alors tu vas vite t'en lasser. Pour l'instant, personne (à part toi, peut-être) n'utilise ta bibliothèque donc pas de stress, n'essaie pas de résoudre des problèmes que tu n'as pas.

              P.S.: Pour "gérer" ton projet, tu peux utiliser GitHub Projects, c'est très simple à utiliser et c'est parfaitement adapté pour un projet comme le tien.

              • Partager sur Facebook
              • Partager sur Twitter
                3 août 2020 à 18:04:46

                Merci beaucoup pour toutes vos réponses. Le choix  a vite été fait, j'ai décidé de m'orienter vers l'approche de koala01 car elle semble très bien me convenir. En effet, je suis du genre perfectionniste et je déteste voir que mon projet n'est pas bien organisé. En ce qui concerne de l'over-engineering, moi même je le remarque et je me fais souvent la morale. C'est aussi pour cette raison que je vais reconstruire le projet à partir de rien.

                Ton modèle de contrôle de version me plait beaucoup @koala01, je tenterai bien de créer plusieurs branches dédiées, mais pas de là à déployer ma solution, du fait de l'ampleur du projet, et aussi, comme @Raynobrak l'a dit: "Pour l'instant, personne (à part toi, peut-être) n'utilise ta bibliothèque" (même pas moi xD). Je pense donc utiliser la branche master qui correspondra à la dernière version "stable" de la lib, la branche develop qui correspondra à la prochaine version en cours de développement et la branche feature qui correspondra à la branche sur laquelle j'expérimenterai de nouvelles fonctionnalités pour la branche develop.

                Pour les commits, j'utilisais en gros ce modèle: "add, fix, update", donc je ne suis plus trop inquiet à propos de ça.

                • Partager sur Facebook
                • Partager sur Twitter

                Organisation et développement d'un projet.

                × Après avoir cliqué sur "Répondre" vous serez invité à vous connecter pour que votre message soit publié.
                × Attention, ce sujet est très ancien. Le déterrer n'est pas forcément approprié. Nous te conseillons de créer un nouveau sujet pour poser ta question.
                • Editeur
                • Markdown