Bonjour,
Je suis en pleine réflexion sur comment gérer un projet composé de
nombreux modules, assemblés à la demande du client. J'ai beaucoup de mal à
trouver de la documentation là dessus.
Pourtant c'est assez courant je pense. Je me demande ainsi : comment gérez
vous les multiples versions simultanées de vos logiciels, je veux dire,
comment structurer son code pour avoir au final plusieurs build:
- version de base
- version complète
- version pro de luxe
- version sur mesure pour client X
- ...
Là où je tatonne c'est au niveau des dépendances / utilisation
inter-modules.
[...]
Qu'en pensez-vous ? Que faites / feriez vous ?
Bonjour,
Je suis en pleine réflexion sur comment gérer un projet composé de
nombreux modules, assemblés à la demande du client. J'ai beaucoup de mal à
trouver de la documentation là dessus.
Pourtant c'est assez courant je pense. Je me demande ainsi : comment gérez
vous les multiples versions simultanées de vos logiciels, je veux dire,
comment structurer son code pour avoir au final plusieurs build:
- version de base
- version complète
- version pro de luxe
- version sur mesure pour client X
- ...
Là où je tatonne c'est au niveau des dépendances / utilisation
inter-modules.
[...]
Qu'en pensez-vous ? Que faites / feriez vous ?
Bonjour,
Je suis en pleine réflexion sur comment gérer un projet composé de
nombreux modules, assemblés à la demande du client. J'ai beaucoup de mal à
trouver de la documentation là dessus.
Pourtant c'est assez courant je pense. Je me demande ainsi : comment gérez
vous les multiples versions simultanées de vos logiciels, je veux dire,
comment structurer son code pour avoir au final plusieurs build:
- version de base
- version complète
- version pro de luxe
- version sur mesure pour client X
- ...
Là où je tatonne c'est au niveau des dépendances / utilisation
inter-modules.
[...]
Qu'en pensez-vous ? Que faites / feriez vous ?
Dans un premier temps, j'exploiterais à fond les namespace.
Dans un premier temps, j'exploiterais à fond les namespace.
Dans un premier temps, j'exploiterais à fond les namespace.
Dans un premier temps, j'exploiterais à fond les namespace.
Si ça peut t'éclairer (?), chaque module est déclaré dans un namespace qui
lui est propre, ce qui en fait une vingtaine au total.
Je vois pas trop le rapport en fait.
Dans un premier temps, j'exploiterais à fond les namespace.
Si ça peut t'éclairer (?), chaque module est déclaré dans un namespace qui
lui est propre, ce qui en fait une vingtaine au total.
Je vois pas trop le rapport en fait.
Dans un premier temps, j'exploiterais à fond les namespace.
Si ça peut t'éclairer (?), chaque module est déclaré dans un namespace qui
lui est propre, ce qui en fait une vingtaine au total.
Je vois pas trop le rapport en fait.
Bonjour,
Je suis en pleine réflexion sur comment gérer un projet composé de
nombreux modules, assemblés à la demande du client. J'ai beaucoup de
mal à trouver de la documentation là dessus.
Pourtant c'est assez courant je pense. Je me demande ainsi : comment
gérez vous les multiples versions simultanées de vos logiciels, je
veux dire, comment structurer son code pour avoir au final plusieurs
build:
- version de base
- version complète
- version pro de luxe
- version sur mesure pour client X
- ...
Là où je tatonne c'est au niveau des dépendances / utilisation
inter-modules.
Bonjour,
Je suis en pleine réflexion sur comment gérer un projet composé de
nombreux modules, assemblés à la demande du client. J'ai beaucoup de
mal à trouver de la documentation là dessus.
Pourtant c'est assez courant je pense. Je me demande ainsi : comment
gérez vous les multiples versions simultanées de vos logiciels, je
veux dire, comment structurer son code pour avoir au final plusieurs
build:
- version de base
- version complète
- version pro de luxe
- version sur mesure pour client X
- ...
Là où je tatonne c'est au niveau des dépendances / utilisation
inter-modules.
Bonjour,
Je suis en pleine réflexion sur comment gérer un projet composé de
nombreux modules, assemblés à la demande du client. J'ai beaucoup de
mal à trouver de la documentation là dessus.
Pourtant c'est assez courant je pense. Je me demande ainsi : comment
gérez vous les multiples versions simultanées de vos logiciels, je
veux dire, comment structurer son code pour avoir au final plusieurs
build:
- version de base
- version complète
- version pro de luxe
- version sur mesure pour client X
- ...
Là où je tatonne c'est au niveau des dépendances / utilisation
inter-modules.
Je suis en pleine réflexion sur comment gérer un projet
composé de nombreux modules, assemblés à la demande du client.
J'ai beaucoup de mal à trouver de la documentation là dessus.
Pourtant c'est assez courant je pense. Je me demande ainsi :
comment gérez vous les multiples versions simultanées de vos
logiciels, je veux dire, comment structurer son code pour
avoir au final plusieurs build:
- version de base
- version complète
- version pro de luxe
- version sur mesure pour client X
- ...
Là où je tatonne c'est au niveau des dépendances / utilisation
inter-modules.
Actuellement, j'ai un objet central Application, qui concentre
tous les modules compilés.
Une fonction globale App() renvoie
l'instance. Par exemple, pour utiliser les services du module
base de données, un module X fait:
class Application
{
public:
//...
#ifdef USE_DATABASE_MODULE
DatabaseMgr * Database();
#endif
//...
};
Application* App();
class X
{
void Toto()
{
App()->Database()->...
}
};
Vous imaginez les problèmes soulevés par la gestion des
dépendances à coup de #ifdef.
Je compte migrer vers quelque chose comme celà:
- pour chaque module, une classe ModuleUser donne le droit d'utiliser le
module. Il faut dériver de cette classe pour pouvoir utiliser la
fonction membre donnant accès à l'instance du module.
class DatabaseUser
{
protected:
DatabaseMgr* Database();
};
class X : public DatabaseUser
{
void Toto()
{
this->Database()->...
}
};
C'est moins pratique à l'utilisation (il faut dériver sa
classe utilisatrice...) mais ça me parrait pas trop mal.
Qu'en pensez-vous ? Que faites / feriez vous ?
Je suis en pleine réflexion sur comment gérer un projet
composé de nombreux modules, assemblés à la demande du client.
J'ai beaucoup de mal à trouver de la documentation là dessus.
Pourtant c'est assez courant je pense. Je me demande ainsi :
comment gérez vous les multiples versions simultanées de vos
logiciels, je veux dire, comment structurer son code pour
avoir au final plusieurs build:
- version de base
- version complète
- version pro de luxe
- version sur mesure pour client X
- ...
Là où je tatonne c'est au niveau des dépendances / utilisation
inter-modules.
Actuellement, j'ai un objet central Application, qui concentre
tous les modules compilés.
Une fonction globale App() renvoie
l'instance. Par exemple, pour utiliser les services du module
base de données, un module X fait:
class Application
{
public:
//...
#ifdef USE_DATABASE_MODULE
DatabaseMgr * Database();
#endif
//...
};
Application* App();
class X
{
void Toto()
{
App()->Database()->...
}
};
Vous imaginez les problèmes soulevés par la gestion des
dépendances à coup de #ifdef.
Je compte migrer vers quelque chose comme celà:
- pour chaque module, une classe ModuleUser donne le droit d'utiliser le
module. Il faut dériver de cette classe pour pouvoir utiliser la
fonction membre donnant accès à l'instance du module.
class DatabaseUser
{
protected:
DatabaseMgr* Database();
};
class X : public DatabaseUser
{
void Toto()
{
this->Database()->...
}
};
C'est moins pratique à l'utilisation (il faut dériver sa
classe utilisatrice...) mais ça me parrait pas trop mal.
Qu'en pensez-vous ? Que faites / feriez vous ?
Je suis en pleine réflexion sur comment gérer un projet
composé de nombreux modules, assemblés à la demande du client.
J'ai beaucoup de mal à trouver de la documentation là dessus.
Pourtant c'est assez courant je pense. Je me demande ainsi :
comment gérez vous les multiples versions simultanées de vos
logiciels, je veux dire, comment structurer son code pour
avoir au final plusieurs build:
- version de base
- version complète
- version pro de luxe
- version sur mesure pour client X
- ...
Là où je tatonne c'est au niveau des dépendances / utilisation
inter-modules.
Actuellement, j'ai un objet central Application, qui concentre
tous les modules compilés.
Une fonction globale App() renvoie
l'instance. Par exemple, pour utiliser les services du module
base de données, un module X fait:
class Application
{
public:
//...
#ifdef USE_DATABASE_MODULE
DatabaseMgr * Database();
#endif
//...
};
Application* App();
class X
{
void Toto()
{
App()->Database()->...
}
};
Vous imaginez les problèmes soulevés par la gestion des
dépendances à coup de #ifdef.
Je compte migrer vers quelque chose comme celà:
- pour chaque module, une classe ModuleUser donne le droit d'utiliser le
module. Il faut dériver de cette classe pour pouvoir utiliser la
fonction membre donnant accès à l'instance du module.
class DatabaseUser
{
protected:
DatabaseMgr* Database();
};
class X : public DatabaseUser
{
void Toto()
{
this->Database()->...
}
};
C'est moins pratique à l'utilisation (il faut dériver sa
classe utilisatrice...) mais ça me parrait pas trop mal.
Qu'en pensez-vous ? Que faites / feriez vous ?
J'ai une certaine réflexion là-dessus, mais qui conduit à des pratiques de
perfectionniste. Il faut aimer...
Il y a plusieurs types de séparations à envisager :
- découpage en modules fonctionnels au sens du produit,
- séparation pour partage des utilitaires de type "librairie",
- découpage fonctionnel selon MVC (Model-View-Controler), proche du premier
type,
- séparation des ressources de localisation (langues, protocoles, etc.) pour
chacun des précédents modules,
- etc.
Quand j'arrive à ça, j'ai très envie de couper tous les liens statiques
entre modules, et de garantir la modularité par un assemblage de type
"contrat fonctionnel", comme dans l'implémentation initiale de COM (avec les
interfaces formelles implémentées par des classes imbriquées, avant que les
ATL ne nous fassent un grand mix).
Il est parfaitement possible de faire ça en C++ 100% system-independent,
sans IDL, avec simplement des interfaces formelles définies comme des
bundles de fonctions virtual=0, et fournies dans des headers individuels de
même nom, pouvant aussi en fournir la typologie, les constantes, etc.
Alors bien sûr ça conduit à faire de la connexion dynamique des modules même
quand les sources sont compilés ensemble, donc de manipuler des fonctions au
bout de pointeurs d'interfaces au lieu de faire du linkage statique
classique.
Mais en contrepartie, les liens entre modules sont 100% basés sur leurs
interfaces formelles. Tout module est substituable à fonctionnalités
équivalentes, et tout module dont les interfaces ne sont pas invoquées peut
être retiré du projet en bloc (donc 0 code mort).
Donc vous pouvez vraiment assembler une version sur mesure comme vous le
disiez.
Votre avis ?
J'ai une certaine réflexion là-dessus, mais qui conduit à des pratiques de
perfectionniste. Il faut aimer...
Il y a plusieurs types de séparations à envisager :
- découpage en modules fonctionnels au sens du produit,
- séparation pour partage des utilitaires de type "librairie",
- découpage fonctionnel selon MVC (Model-View-Controler), proche du premier
type,
- séparation des ressources de localisation (langues, protocoles, etc.) pour
chacun des précédents modules,
- etc.
Quand j'arrive à ça, j'ai très envie de couper tous les liens statiques
entre modules, et de garantir la modularité par un assemblage de type
"contrat fonctionnel", comme dans l'implémentation initiale de COM (avec les
interfaces formelles implémentées par des classes imbriquées, avant que les
ATL ne nous fassent un grand mix).
Il est parfaitement possible de faire ça en C++ 100% system-independent,
sans IDL, avec simplement des interfaces formelles définies comme des
bundles de fonctions virtual=0, et fournies dans des headers individuels de
même nom, pouvant aussi en fournir la typologie, les constantes, etc.
Alors bien sûr ça conduit à faire de la connexion dynamique des modules même
quand les sources sont compilés ensemble, donc de manipuler des fonctions au
bout de pointeurs d'interfaces au lieu de faire du linkage statique
classique.
Mais en contrepartie, les liens entre modules sont 100% basés sur leurs
interfaces formelles. Tout module est substituable à fonctionnalités
équivalentes, et tout module dont les interfaces ne sont pas invoquées peut
être retiré du projet en bloc (donc 0 code mort).
Donc vous pouvez vraiment assembler une version sur mesure comme vous le
disiez.
Votre avis ?
J'ai une certaine réflexion là-dessus, mais qui conduit à des pratiques de
perfectionniste. Il faut aimer...
Il y a plusieurs types de séparations à envisager :
- découpage en modules fonctionnels au sens du produit,
- séparation pour partage des utilitaires de type "librairie",
- découpage fonctionnel selon MVC (Model-View-Controler), proche du premier
type,
- séparation des ressources de localisation (langues, protocoles, etc.) pour
chacun des précédents modules,
- etc.
Quand j'arrive à ça, j'ai très envie de couper tous les liens statiques
entre modules, et de garantir la modularité par un assemblage de type
"contrat fonctionnel", comme dans l'implémentation initiale de COM (avec les
interfaces formelles implémentées par des classes imbriquées, avant que les
ATL ne nous fassent un grand mix).
Il est parfaitement possible de faire ça en C++ 100% system-independent,
sans IDL, avec simplement des interfaces formelles définies comme des
bundles de fonctions virtual=0, et fournies dans des headers individuels de
même nom, pouvant aussi en fournir la typologie, les constantes, etc.
Alors bien sûr ça conduit à faire de la connexion dynamique des modules même
quand les sources sont compilés ensemble, donc de manipuler des fonctions au
bout de pointeurs d'interfaces au lieu de faire du linkage statique
classique.
Mais en contrepartie, les liens entre modules sont 100% basés sur leurs
interfaces formelles. Tout module est substituable à fonctionnalités
équivalentes, et tout module dont les interfaces ne sont pas invoquées peut
être retiré du projet en bloc (donc 0 code mort).
Donc vous pouvez vraiment assembler une version sur mesure comme vous le
disiez.
Votre avis ?
Si tu as une flopée de classes ou de fonctions
qui sont selectionnées par un jeu de #idef, #endif,
il me semble qu'utiliser des using-déclaration présente
plus d'avantages.
Si tu as une flopée de classes ou de fonctions
qui sont selectionnées par un jeu de #idef, #endif,
il me semble qu'utiliser des using-déclaration présente
plus d'avantages.
Si tu as une flopée de classes ou de fonctions
qui sont selectionnées par un jeu de #idef, #endif,
il me semble qu'utiliser des using-déclaration présente
plus d'avantages.
Jusqu'à là, c'est bien. Mais je ne vois pas d'où vient les
#ifdef.
Ils sont définis en fonction des modules à compiler.
Ce que je ferais, c'est un map statique des modules,
chargement dynamiquement par le constructeur d'un objet statique
dans chaque module. Ton code Application::Database() serait
alors quelque chose du genre :
DatabaseMgr*
Application::Database()
{
Map::const_iterator module = ourMap.find( databaseId ) ;
return module == ourMap.end()
? NULL
: static_cast< DatabaseMgr* >( module->second ) ;
}
La « configuration » se ferait lors de l'édition de liens, où tu
incorporeras ou non l'objet static d'initialisation de tel ou
tel module.
Alternativement, tu pourrais faire à peu près pareil avec des
objets dynamiques. Quand la fonction (par exemple
Application::Database) ne trouve pas l'entrée dans le map, il
essaie de charger l'objet dynamique correspondant (qui
s'insérera dans le map) avant de retourner NULL. Dans ce cas-ci,
la « configuration » ne se fait que lors du packaging : tu
inclus plus ou moins d'objets dynamiques dans le package.
Dans ce cas-là, la fonction devient :
DatabaseMgr*
Application::Database()
{
Map::const_iterator module = ourMap.find( databaseId ) ;
if ( module == ourMap.end() ) {
tryToLoadModule( databaseId ) ;
module = ourMap.find( databaseId ) ;
}
return module == ourMap.end()
? NULL
: static_cast< DatabaseMgr* >( module->second ) ;
}
Je compte migrer vers quelque chose comme celà:
- pour chaque module, une classe ModuleUser donne le droit d'utiliser le
module. Il faut dériver de cette classe pour pouvoir utiliser la
fonction membre donnant accès à l'instance du module.class DatabaseUser
{
protected:
DatabaseMgr* Database();
};class X : public DatabaseUser
{
void Toto()
{
this->Database()->...
}
};C'est moins pratique à l'utilisation (il faut dériver sa
classe utilisatrice...) mais ça me parrait pas trop mal.
Ça me paraît un peu lourd, mais ça pourrait marcher aussi.
N'empèche que je chercherais à différer la décision au plus tard
possible, à l'édition de liens ou au packaging, et non dans le
code.
static_cast< DatabaseMgr* >( module->second )
DatabaseMgr*
Application::Database()
Jusqu'à là, c'est bien. Mais je ne vois pas d'où vient les
#ifdef.
Ils sont définis en fonction des modules à compiler.
Ce que je ferais, c'est un map statique des modules,
chargement dynamiquement par le constructeur d'un objet statique
dans chaque module. Ton code Application::Database() serait
alors quelque chose du genre :
DatabaseMgr*
Application::Database()
{
Map::const_iterator module = ourMap.find( databaseId ) ;
return module == ourMap.end()
? NULL
: static_cast< DatabaseMgr* >( module->second ) ;
}
La « configuration » se ferait lors de l'édition de liens, où tu
incorporeras ou non l'objet static d'initialisation de tel ou
tel module.
Alternativement, tu pourrais faire à peu près pareil avec des
objets dynamiques. Quand la fonction (par exemple
Application::Database) ne trouve pas l'entrée dans le map, il
essaie de charger l'objet dynamique correspondant (qui
s'insérera dans le map) avant de retourner NULL. Dans ce cas-ci,
la « configuration » ne se fait que lors du packaging : tu
inclus plus ou moins d'objets dynamiques dans le package.
Dans ce cas-là, la fonction devient :
DatabaseMgr*
Application::Database()
{
Map::const_iterator module = ourMap.find( databaseId ) ;
if ( module == ourMap.end() ) {
tryToLoadModule( databaseId ) ;
module = ourMap.find( databaseId ) ;
}
return module == ourMap.end()
? NULL
: static_cast< DatabaseMgr* >( module->second ) ;
}
Je compte migrer vers quelque chose comme celà:
- pour chaque module, une classe ModuleUser donne le droit d'utiliser le
module. Il faut dériver de cette classe pour pouvoir utiliser la
fonction membre donnant accès à l'instance du module.
class DatabaseUser
{
protected:
DatabaseMgr* Database();
};
class X : public DatabaseUser
{
void Toto()
{
this->Database()->...
}
};
C'est moins pratique à l'utilisation (il faut dériver sa
classe utilisatrice...) mais ça me parrait pas trop mal.
Ça me paraît un peu lourd, mais ça pourrait marcher aussi.
N'empèche que je chercherais à différer la décision au plus tard
possible, à l'édition de liens ou au packaging, et non dans le
code.
static_cast< DatabaseMgr* >( module->second )
DatabaseMgr*
Application::Database()
Jusqu'à là, c'est bien. Mais je ne vois pas d'où vient les
#ifdef.
Ils sont définis en fonction des modules à compiler.
Ce que je ferais, c'est un map statique des modules,
chargement dynamiquement par le constructeur d'un objet statique
dans chaque module. Ton code Application::Database() serait
alors quelque chose du genre :
DatabaseMgr*
Application::Database()
{
Map::const_iterator module = ourMap.find( databaseId ) ;
return module == ourMap.end()
? NULL
: static_cast< DatabaseMgr* >( module->second ) ;
}
La « configuration » se ferait lors de l'édition de liens, où tu
incorporeras ou non l'objet static d'initialisation de tel ou
tel module.
Alternativement, tu pourrais faire à peu près pareil avec des
objets dynamiques. Quand la fonction (par exemple
Application::Database) ne trouve pas l'entrée dans le map, il
essaie de charger l'objet dynamique correspondant (qui
s'insérera dans le map) avant de retourner NULL. Dans ce cas-ci,
la « configuration » ne se fait que lors du packaging : tu
inclus plus ou moins d'objets dynamiques dans le package.
Dans ce cas-là, la fonction devient :
DatabaseMgr*
Application::Database()
{
Map::const_iterator module = ourMap.find( databaseId ) ;
if ( module == ourMap.end() ) {
tryToLoadModule( databaseId ) ;
module = ourMap.find( databaseId ) ;
}
return module == ourMap.end()
? NULL
: static_cast< DatabaseMgr* >( module->second ) ;
}
Je compte migrer vers quelque chose comme celà:
- pour chaque module, une classe ModuleUser donne le droit d'utiliser le
module. Il faut dériver de cette classe pour pouvoir utiliser la
fonction membre donnant accès à l'instance du module.class DatabaseUser
{
protected:
DatabaseMgr* Database();
};class X : public DatabaseUser
{
void Toto()
{
this->Database()->...
}
};C'est moins pratique à l'utilisation (il faut dériver sa
classe utilisatrice...) mais ça me parrait pas trop mal.
Ça me paraît un peu lourd, mais ça pourrait marcher aussi.
N'empèche que je chercherais à différer la décision au plus tard
possible, à l'édition de liens ou au packaging, et non dans le
code.
static_cast< DatabaseMgr* >( module->second )
DatabaseMgr*
Application::Database()
Je ne te présente pas les namespaces comme étant _la_ solution
à ton problème de modularité, mais ce qui m'étonne un peu
c'est que tu dis que chaque module est déclaré dans un namespace
qui lui est propre, et d'un autre côté tu utlises
une structure du type :
class ApplicationCore : public Application
{
virtual DatabaseMgr * Database()
{
#ifdef USE_SQL_SERVER
static SqlServerDataBaseMgr mgr;
#elif defined USE_ORACLE
static OracleDataBaseMgr mgr;
#endif
return &mgr;
}
};
Mais c'est pas grave vu que cela disparaitra dans ta nouvelle mouture...
Par contre, je qui reste flou pour moi, c'est est-ce que tu souhaites
une gestion des modules à l'execution ou à la compilation uniquement.
Je ne te présente pas les namespaces comme étant _la_ solution
à ton problème de modularité, mais ce qui m'étonne un peu
c'est que tu dis que chaque module est déclaré dans un namespace
qui lui est propre, et d'un autre côté tu utlises
une structure du type :
class ApplicationCore : public Application
{
virtual DatabaseMgr * Database()
{
#ifdef USE_SQL_SERVER
static SqlServerDataBaseMgr mgr;
#elif defined USE_ORACLE
static OracleDataBaseMgr mgr;
#endif
return &mgr;
}
};
Mais c'est pas grave vu que cela disparaitra dans ta nouvelle mouture...
Par contre, je qui reste flou pour moi, c'est est-ce que tu souhaites
une gestion des modules à l'execution ou à la compilation uniquement.
Je ne te présente pas les namespaces comme étant _la_ solution
à ton problème de modularité, mais ce qui m'étonne un peu
c'est que tu dis que chaque module est déclaré dans un namespace
qui lui est propre, et d'un autre côté tu utlises
une structure du type :
class ApplicationCore : public Application
{
virtual DatabaseMgr * Database()
{
#ifdef USE_SQL_SERVER
static SqlServerDataBaseMgr mgr;
#elif defined USE_ORACLE
static OracleDataBaseMgr mgr;
#endif
return &mgr;
}
};
Mais c'est pas grave vu que cela disparaitra dans ta nouvelle mouture...
Par contre, je qui reste flou pour moi, c'est est-ce que tu souhaites
une gestion des modules à l'execution ou à la compilation uniquement.