Quelle stratégie utilisez-vous pour nommer les packages dans les projets Java et pourquoi? [fermé]


88

J'y ai pensé il y a un moment et cela a récemment refait surface alors que ma boutique fait sa première vraie application Web Java.

En guise d'introduction, je vois deux stratégies principales de dénomination des packages. (Pour être clair, je ne fais pas référence à toute la partie 'domain.company.project', je parle de la convention de package en dessous.) Quoi qu'il en soit, les conventions de dénomination de package que je vois sont les suivantes:

  1. Fonctionnel: Nommer vos packages en fonction de leur fonction architecturale plutôt que de leur identité en fonction du domaine métier. Un autre terme pour cela pourrait être la dénomination selon «couche». Ainsi, vous auriez un package * .ui et un package * .domain et un package * .orm. Vos emballages sont des tranches horizontales plutôt que verticales.

    C'est beaucoup plus courant que la dénomination logique. En fait, je ne crois pas avoir jamais vu ou entendu parler d'un projet qui fait cela. Cela me rend bien sûr méfiant (un peu comme si vous pensiez que vous avez trouvé une solution à un problème de NP) car je ne suis pas très intelligent et je suppose que tout le monde doit avoir de bonnes raisons de le faire comme il le fait. D'un autre côté, je ne suis pas opposé au fait que les gens manquent simplement l'éléphant dans la pièce et je n'ai jamais entendu d'argument réel pour nommer les paquets de cette façon. Cela semble être la norme de facto.

  2. Logique: Nommez vos packages en fonction de leur identité de domaine professionnel et placez chaque classe liée à cette tranche verticale de fonctionnalités dans ce package.

    Je n'ai jamais vu ou entendu parler de cela, comme je l'ai déjà mentionné, mais cela a beaucoup de sens pour moi.

    1. J'ai tendance à aborder les systèmes verticalement plutôt qu'horizontalement. Je veux entrer et développer le système de traitement des commandes, pas la couche d'accès aux données. Évidemment, il y a de bonnes chances que je touche à la couche d'accès aux données dans le développement de ce système, mais le fait est que je ne pense pas de cette façon. Ce que cela signifie, bien sûr, c'est que lorsque je reçois un ordre de modification ou que je veux implémenter une nouvelle fonctionnalité, ce serait bien de ne pas avoir à pêcher dans un tas de packages afin de trouver toutes les classes associées. Au lieu de cela, je regarde juste dans le package X parce que ce que je fais a à voir avec X.

    2. Du point de vue du développement, je vois comme une victoire majeure que vos packages documentent votre domaine d'activité plutôt que votre architecture. J'ai l'impression que le domaine est presque toujours la partie du système qui est la plus difficile à gérer, car l'architecture du système, en particulier à ce stade, devient presque banale dans sa mise en œuvre. Le fait que je puisse arriver à un système avec ce type de convention de dénomination et instantanément à partir de la dénomination des paquets sache qu'il traite des commandes, des clients, des entreprises, des produits, etc. semble sacrément pratique.

    3. Il semble que cela vous permettrait de tirer un bien meilleur parti des modificateurs d'accès de Java. Cela vous permet de définir beaucoup plus clairement les interfaces en sous-systèmes plutôt qu'en couches du système. Donc, si vous avez un sous-système de commandes que vous voulez être persistant de manière transparente, vous pourriez en théorie ne jamais laisser rien d'autre savoir qu'il est persistant en n'ayant pas à créer d'interfaces publiques vers ses classes de persistance dans la couche dao et à la place de conditionner la classe dao dans avec seulement les classes dont il s'occupe. Évidemment, si vous souhaitez exposer cette fonctionnalité, vous pouvez lui fournir une interface ou la rendre publique. Il semble que vous en perdiez beaucoup en ayant une tranche verticale des fonctionnalités de votre système réparties sur plusieurs packages.

    4. Je suppose qu'un inconvénient que je peux voir est que cela rend l'extraction des couches un peu plus difficile. Au lieu de simplement supprimer ou renommer un package, puis d'en déposer un nouveau avec une technologie alternative, vous devez entrer et modifier toutes les classes de tous les packages. Cependant, je ne vois pas que ce soit un gros problème. Cela peut être dû à un manque d'expérience, mais je dois imaginer que le nombre de fois où vous échangez des technologies est pâle par rapport au nombre de fois que vous entrez et modifiez des tranches de fonctionnalités verticales dans votre système.

Donc, je suppose que la question vous serait posée, comment nommez-vous vos colis et pourquoi? Veuillez comprendre que je ne pense pas nécessairement que je suis tombé sur l'oie d'or ou quelque chose ici. Je suis assez nouveau dans tout cela avec une expérience principalement académique. Cependant, je ne peux pas repérer les trous dans mon raisonnement alors j'espère que vous le pourrez tous pour que je puisse passer à autre chose.


Ce que j'ai entendu jusqu'à présent semble indiquer que c'est une sorte de jugement. Cependant, la plupart des réponses ne se sont pas concentrées sur les considérations pratiques. C'est plus ce que je recherche. Merci pour l'aide jusqu'à présent, cependant!
Tim Visher

Je ne pense pas que ce soit un jugement. Diviser d'abord par couche, puis par fonctionnalité est certainement la voie à suivre. Je mets à jour ma réponse.
eljenso

@eljenso: dites ça aux gars d'Eclipse :) Dans Eclipse, la première division est aux "fonctionnalités", qui sont des unités de déploiements et aussi des fonctionnalités. À l'intérieur de «fonctionnalité», il y a des «plugins», qui sont généralement divisés par couche - mais les plugins au sein d'une même «fonctionnalité» doivent toujours être déployés ensemble. Si vous n'avez qu'une seule unité de déploiement, la division par couche d'abord et ensuite seulement par fonctionnalité peut avoir du sens. Avec plusieurs unités de déploiement, vous ne souhaitez pas déployer uniquement la couche de présentation - vous voulez une fonctionnalité supplémentaire.
Peter Štibraný

Réponses:


32

Pour la conception des packages, je divise d'abord par couche, puis par une autre fonctionnalité.

Il existe quelques règles supplémentaires:

  1. les couches sont empilées du plus général (en bas) au plus spécifique (en haut)
  2. chaque couche a une interface publique (abstraction)
  3. une couche ne peut dépendre que de l'interface publique d'une autre couche (encapsulation)
  4. une couche ne peut dépendre que de couches plus générales (dépendances de haut en bas)
  5. une couche dépend de préférence de la couche directement en dessous

Ainsi, pour une application Web par exemple, vous pouvez avoir les couches suivantes dans votre niveau d'application (de haut en bas):

  • couche de présentation: génère l'interface utilisateur qui sera affichée dans le niveau client
  • couche application: contient une logique spécifique à une application, avec état
  • couche de service: regroupe les fonctionnalités par domaine, sans état
  • couche d'intégration: permet d'accéder au niveau backend (db, jms, email, ...)

Pour la mise en page du package résultant, voici quelques règles supplémentaires:

  • la racine de chaque nom de package est <prefix.company>.<appname>.<layer>
  • l'interface d'une couche est en outre divisée par fonctionnalité: <root>.<logic>
  • l'implémentation privée d'une couche est préfixée par private: <root>.private

Voici un exemple de mise en page.

La couche de présentation est divisée par la technologie d'affichage et éventuellement par des (groupes) d'applications.

com.company.appname.presentation.internal
com.company.appname.presentation.springmvc.product
com.company.appname.presentation.servlet
...

La couche d'application est divisée en cas d'utilisation.

com.company.appname.application.lookupproduct
com.company.appname.application.internal.lookupproduct
com.company.appname.application.editclient
com.company.appname.application.internal.editclient
...

La couche de service est divisée en domaines métier, influencés par la logique du domaine dans un niveau backend.

com.company.appname.service.clientservice
com.company.appname.service.internal.jmsclientservice
com.company.appname.service.internal.xmlclientservice
com.company.appname.service.productservice
...

La couche d'intégration est divisée en «technologies» et objets d'accès.

com.company.appname.integration.jmsgateway
com.company.appname.integration.internal.mqjmsgateway
com.company.appname.integration.productdao
com.company.appname.integration.internal.dbproductdao
com.company.appname.integration.internal.mockproductdao
...

L'avantage de séparer des packages comme celui-ci est qu'il est plus facile de gérer la complexité et augmente la testabilité et la réutilisabilité. Bien que cela semble être beaucoup de frais généraux, d'après mon expérience, cela est en fait très naturel et tout le monde travaillant sur cette structure (ou similaire) la récupère en quelques jours.

Pourquoi est-ce que je pense que l'approche verticale n'est pas si bonne?

Dans le modèle en couches, plusieurs modules de haut niveau différents peuvent utiliser le même module de niveau inférieur. Par exemple: vous pouvez créer plusieurs vues pour la même application, plusieurs applications peuvent utiliser le même service, plusieurs services peuvent utiliser la même passerelle. L'astuce ici est que lorsque vous vous déplacez à travers les couches, le niveau de fonctionnalité change. Les modules dans des couches plus spécifiques ne mappent pas 1-1 sur les modules de la couche plus générale, car les niveaux de fonctionnalité qu'ils expriment ne mappent pas 1-1.

Lorsque vous utilisez l'approche verticale pour la conception de packages, c'est-à-dire que vous divisez d'abord par fonctionnalité, vous forcez ensuite tous les blocs de construction avec différents niveaux de fonctionnalité dans la même «veste de fonctionnalité». Vous pouvez concevoir vos modules généraux pour le plus spécifique. Mais cela viole le principe important selon lequel la couche la plus générale ne doit pas connaître les couches plus spécifiques. La couche service, par exemple, ne doit pas être modélisée d'après les concepts de la couche application.


"Les avantages de séparer des packages comme celui-ci sont qu'il est plus facile de gérer la complexité, et cela augmente la testabilité et la réutilisabilité." Vous n'allez vraiment pas dans les raisons pour lesquelles il en est ainsi.
mango ivre le

1
Vous n'allez pas dans les raisons pour lesquelles ce n'est pas le cas. Mais de toute façon ... les règles d'organisation sont assez claires et simples, ce qui réduit la complexité. Il est plus testable et réutilisable en raison de l'organisation elle-même. Tout le monde peut l'essayer, et ensuite nous pouvons être d'accord ou pas. J'explique brièvement certains des inconvénients de l'approche verticale, en donnant implicitement les raisons pour lesquelles je pense que l'approche horizontale est plus facile.
eljenso

8
Cet article explique assez bien pourquoi il est préférable d'utiliser package par fonctionnalité au lieu de package par couche .
Tom

@eljenso "Vous n'allez pas dans les raisons pour lesquelles ce n'est pas le cas", en essayant de déplacer la charge de la preuve? Je pense que l'article posté par Tom explique très bien pourquoi package par fonctionnalité est meilleur et j'ai du mal à prendre "Je ne pense pas que ce soit un jugement. D'abord diviser par couche, puis par fonctionnalité est définitivement la voie aller «au sérieux.
pka du

18

Je me retrouve fidèle aux principes de conception d'emballage d' oncle Bob . En bref, les classes qui doivent être réutilisées ensemble et modifiées ensemble (pour la même raison, par exemple un changement de dépendance ou un changement de cadre) doivent être placées dans le même package. OMI, la ventilation fonctionnelle aurait de meilleures chances d'atteindre ces objectifs que la ventilation verticale / spécifique à l'entreprise dans la plupart des applications.

Par exemple, une tranche horizontale d'objets de domaine peut être réutilisée par différents types de frontaux ou même d'applications et une tranche horizontale du front-end Web est susceptible de changer ensemble lorsque le cadre Web sous-jacent doit être modifié. D'un autre côté, il est facile d'imaginer l'effet d'entraînement de ces changements sur de nombreux packages si des classes de différents domaines fonctionnels sont regroupées dans ces packages.

De toute évidence, tous les types de logiciels ne sont pas identiques et la ventilation verticale peut avoir un sens (en termes de réalisation des objectifs de réutilisabilité et de fermeture au changement) dans certains projets.


Merci, c'est très clair.Comme je l'ai dit dans la question, j'ai le sentiment que le nombre de fois où vous échangeriez des technologies est bien inférieur à celui que vous déplaceriez autour de tranches verticales de fonctionnalités. Cela n'a-t-il pas été votre expérience?
Tim Visher

Ce n'est pas seulement une question de technologies. Le point n ° 1 de votre article d'origine n'a de sens que si les tranches verticales sont des applications / services indépendants communiquant entre eux via une interface (SOA, si vous voulez). plus ci-dessous ...
Buu Nguyen

Maintenant que vous entrez dans le détail de chacune de ces applications / services à granularité fine qui a sa propre interface graphique / entreprise / données, je peux difficilement imaginer que les changements dans une tranche verticale, qu'il s'agisse de technologie, de dépendance, de règle / flux de travail, de journalisation , la sécurité, le style de l'interface utilisateur, peuvent être complètement isolés des autres tranches.
Buu Nguyen le

Je serais intéressé par vos commentaires sur ma réponse
i3ensays

@BuuNguyen Le lien vers les principes de conception des packages est rompu.
johnnieb

5

Les deux niveaux de division sont généralement présents. Du haut, il y a des unités de déploiement. Ceux-ci sont nommés «logiquement» (dans vos termes, pensez aux fonctionnalités d'Eclipse). À l'intérieur de l'unité de déploiement, vous avez une division fonctionnelle des packages (pensez aux plugins Eclipse).

Par exemple, fonctionnalité est com.feature, et se compose de com.feature.client, com.feature.coreet com.feature.uiplugins. À l'intérieur des plugins, j'ai très peu de divisions avec d'autres packages, même si ce n'est pas inhabituel non plus.

Mise à jour: Btw, Juergen Hoeller parle très bien de l'organisation du code sur InfoQ: http://www.infoq.com/presentations/code-organization-large-projects . Juergen est l'un des architectes de Spring et en sait beaucoup sur ce sujet.


Je ne suis pas tout à fait. Habituellement, vous pouvez voir com.apache.wicket.x où x est soit fonctionnel, soit logique. Je ne vois généralement pas com.x. Je suppose que vous dites que vous iriez avec une structure com.company.project.feature.layer? Avez-vous des raisons?
Tim Visher

La raison en est que "com.company.project.feature" est l'unité de déploiement. À ce niveau, certaines fonctionnalités sont facultatives et peuvent être ignorées (c'est-à-dire non déployées). Cependant, à l'intérieur de la fonctionnalité, les choses ne sont pas facultatives et vous les voulez généralement toutes. Ici, il est plus logique de diviser par couches.
Peter Štibraný

4

La plupart des projets java sur lesquels j'ai travaillé découpent d'abord les packages java de manière fonctionnelle, puis logiquement.

Habituellement, les parties sont suffisamment volumineuses pour être divisées en artefacts de construction séparés, où vous pouvez mettre des fonctionnalités de base dans un bocal, des API dans un autre, des éléments de l'interface Web dans un fichier de guerre, etc.


À quoi cela peut-il ressembler? domain.company.project.function.logicalSlice?
Tim Visher

plutôt! egdcpweb.profiles, dcpweb.registration, dcpapis, dcppersistence etc. fonctionne généralement assez bien, votre kilométrage peut varier. dépend également si vous utilisez la modélisation de domaine - si vous avez plusieurs domaines, vous pouvez commencer par diviser par domaine.
Ben Hardy

Personnellement, je préfère le contraire, logique puis fonctionnel. apples.rpc, apples.model puis banana.model, banana.store
mP.

Il est plus intéressant de pouvoir regrouper tous les éléments de la pomme que de regrouper tous les éléments du Web.
mP.

3

Les packages doivent être compilés et distribués sous forme d'unité. Lors de l'examen des classes appartenant à un package, l'un des critères clés est ses dépendances. De quels autres packages (y compris les bibliothèques tierces) dépend cette classe. Un système bien organisé regroupera les classes avec des dépendances similaires dans un package. Cela limite l'impact d'un changement dans une bibliothèque, car seuls quelques paquets bien définis en dépendront.

Il semble que votre système vertical logique pourrait avoir tendance à «étaler» les dépendances dans la plupart des packages. Autrement dit, si chaque fonctionnalité est empaquetée sous forme de tranche verticale, chaque paquet dépendra de chaque bibliothèque tierce que vous utilisez. Toute modification apportée à une bibliothèque est susceptible de se répercuter sur l'ensemble de votre système.


Ah. Donc, une chose que faire des tranches horizontales est de vous protéger des mises à niveau réelles dans les bibliothèques que vous utilisez. Je pense que vous avez raison de dire que les tranches verticales étalent toutes les dépendances de votre système dans chaque package. Pourquoi est-ce une grosse affaire?
Tim Visher

Pour une "fonctionnalité", ce n'est pas si grave. Ils ont tendance à être plus volatils et ont de toute façon plus de dépendances. D'un autre côté, ce code «client» a tendance à avoir une faible réutilisabilité. Pour quelque chose que vous envisagez d'être une bibliothèque réutilisable, isoler les clients de chaque petit changement est un excellent investissement.
erickson

3

Personnellement, je préfère regrouper les classes logiquement puis à l'intérieur qui incluent un sous-paquet pour chaque participation fonctionnelle.

Objectifs de l'emballage

Les packages consistent après tout à regrouper des choses - l'idée étant que les classes liées vivent près les unes des autres. S'ils vivent dans le même package, ils peuvent profiter du package private pour limiter la visibilité. Le problème est que le regroupement de toutes vos vues et de votre persistance dans un seul package peut entraîner le mélange de nombreuses classes dans un seul package. La prochaine chose sensée à faire est donc de créer des vues, de la persistance, des sous-packages util et des classes de refactorisation en conséquence. Malheureusement, la portée privée des packages protégés et des packages ne prend pas en charge le concept du package et du sous-package actuels, car cela faciliterait l'application de ces règles de visibilité.

Je vois maintenant la valeur de la séparation via la fonctionnalité parce que la valeur est là pour regrouper toutes les choses liées à la vue. Les éléments de cette stratégie de dénomination sont déconnectés de certaines classes dans la vue tandis que d'autres sont en persistance et ainsi de suite.

Un exemple de ma structure d'emballage logique

À des fins d'illustration, nommons deux modules - utilisez le module de nom comme un concept qui regroupe des classes sous une branche particulière d'un arbre de pacckage.

apple.model apple.store banana.model banana.store

Avantages

Un client utilisant le Banana.store.BananaStore n'est exposé qu'aux fonctionnalités que nous souhaitons rendre disponibles. La version hibernate est un détail d'implémentation dont ils n'ont pas besoin d'être conscients et qu'ils ne devraient pas voir ces classes car elles ajoutent du fouillis aux opérations de stockage.

Autres avantages logiques v fonctionnels

Plus on monte vers la racine, plus la portée devient large et les éléments appartenant à un même paquet commencent à présenter de plus en plus de dépendances sur des éléments appartenant à leurs modules. Si l'on examinait par exemple le module "banane", la plupart des dépendances seraient limitées à l'intérieur de ce module. En fait, la plupart des assistants sous "banane" ne seraient pas du tout référencés en dehors de ce champ d'application.

Pourquoi la fonctionnalité?

Quelle valeur peut-on atteindre en regroupant les choses en fonction de la fonctionnalité. La plupart des classes dans un tel cas sont indépendantes les unes des autres avec peu ou pas besoin de tirer parti des méthodes ou des classes privées du package. Leur refactorisation dans leurs propres sous-packages gagne peu mais contribue à réduire l'encombrement.

Modifications apportées par le développeur au système

Lorsque les développeurs sont chargés d'apporter des modifications un peu plus que triviales, il semble ridicule qu'ils aient potentiellement des modifications qui incluent des fichiers de toutes les zones de l'arborescence des packages. Avec l'approche structurée logique, leurs changements sont plus locaux dans la même partie de l'arborescence des packages, ce qui semble juste.


"Lorsque les développeurs [...] fichiers de toutes les zones de l'arborescence des packages." Vous avez raison quand vous dites que cela semble idiot. Parce que c'est exactement le but de la superposition appropriée: les changements ne se répercutent pas sur toute la structure de l'application.
eljenso

Merci pour le conseil. Je ne suis pas sûr de vous comprendre clairement. Je crois que vous essayez de défendre les packages logiques, je ne sais pas exactement pourquoi. Pourriez-vous essayer de rendre votre réponse un peu plus claire, éventuellement en reformulant? Merci!
Tim Visher

3

Les approches fonctionnelles (architecturales) et logiques (caractéristiques) de l'emballage ont leur place. De nombreux exemples d'applications (ceux trouvés dans les manuels, etc.) suivent l'approche fonctionnelle consistant à placer la présentation, les services commerciaux, le mappage de données et d'autres couches architecturales dans des packages séparés. Dans les exemples d'applications, chaque package ne contient souvent que quelques classes ou une seule.

Cette approche initiale est bonne car un exemple artificiel sert souvent à: 1) cartographier conceptuellement l'architecture du cadre présenté, 2) le faire avec un seul objectif logique (par exemple, ajouter / supprimer / mettre à jour / supprimer des animaux de compagnie d'une clinique) . Le problème est que de nombreux lecteurs considèrent cela comme une norme sans limites.

Au fur et à mesure qu'une application «métier» se développe pour inclure de plus en plus de fonctionnalités, suivre l' approche fonctionnelle devient un fardeau. Bien que je sache où chercher les types basés sur la couche d'architecture (par exemple, les contrôleurs Web sous un package "web" ou "ui", etc.), le développement d'une seule fonctionnalité logique commence à nécessiter des va-et-vient entre de nombreux packages. C'est à tout le moins encombrant, mais c'est pire que ça.

Puisque les types logiquement liés ne sont pas regroupés, l'API est trop médiatisée; l'interaction entre les types liés logiquement est forcée d'être «publique» afin que les types puissent importer et interagir les uns avec les autres (la capacité de réduire la visibilité par défaut / package est perdue).

Si je construis une bibliothèque de cadres, mes packages suivront certainement une approche de packaging fonctionnelle / architecturale. Mes consommateurs d'API pourraient même apprécier que leurs déclarations d'importation contiennent un package intuitif nommé d'après l'architecture.

Inversement, lors de la création d'une application métier, je vais créer un package par fonctionnalité. Je n'ai aucun problème à placer Widget, WidgetService et WidgetController dans le même package " com.myorg.widget. ", Puis à profiter de la visibilité par défaut (et d'avoir moins de déclarations d'importation ainsi que des dépendances inter-packages).

Il existe cependant des cas croisés. Si mon WidgetService est utilisé par de nombreux domaines logiques (fonctionnalités), je pourrais créer un package " com.myorg.common.service. ". Il y a aussi de bonnes chances que je crée des classes avec l'intention d'être réutilisables dans toutes les fonctionnalités et que je me retrouve avec des packages tels que " com.myorg.common.ui.helpers. " Et " com.myorg.common.util. ". Je peux même finir par déplacer toutes ces classes "courantes" ultérieures dans un projet séparé et les inclure dans mon application métier en tant que dépendance myorg-commons.jar.


2

Cela dépend de la granularité de vos processus logiques?

S'ils sont autonomes, vous avez souvent un nouveau projet pour eux dans le contrôle de code source, plutôt qu'un nouveau package.

Le projet sur lequel je suis en ce moment se trompe vers le fractionnement logique, il y a un package pour l'aspect jython, un package pour un moteur de règles, des packages pour foo, bar, binglewozzle, etc. Je cherche à avoir les analyseurs XML spécifiques / writers pour chaque module dans ce package, plutôt que d'avoir un package XML (ce que j'ai fait précédemment), bien qu'il y ait toujours un package XML de base où la logique partagée va. Cependant, une raison à cela est qu'il peut être extensible (plugins) et que chaque plugin devra donc également définir son code XML (ou base de données, etc.), donc la centralisation pourrait poser des problèmes plus tard.

En fin de compte, cela semble être la façon dont cela semble le plus judicieux pour le projet particulier. Cependant, je pense qu'il est facile de créer un package le long du diagramme en couches typique d'un projet. Vous vous retrouverez avec un mélange d'emballages logiques et fonctionnels.

Ce qu'il faut, ce sont des espaces de noms balisés. Un analyseur XML pour certaines fonctionnalités Jython pourrait être étiqueté à la fois Jython et XML, plutôt que d'avoir à choisir l'un ou l'autre.

Ou peut-être que je vacille.


Je ne comprends pas entièrement votre point. Je pense que ce que vous dites, c'est que vous devriez simplement faire ce qui est le plus logique pour votre projet et pour vous c'est logique avec un peu de fonctionnalité. Y a-t-il des raisons pratiques spécifiques à cela?
Tim Visher

Comme je l'ai dit, je vais avoir des plugins pour augmenter les fonctionnalités intégrées. Un plugin devra être capable d'analyser et d'écrire son XML (et éventuellement dans la base de données) ainsi que de fournir des fonctionnalités. Par conséquent, ai-je com.xx.feature.xml ou com.xx.xml.feature? Le premier semble plus net.
JeeBee

2

J'essaie de concevoir des structures de packages de telle manière que si je devais dessiner un graphe de dépendances, il serait facile de suivre et d'utiliser un modèle cohérent, avec le moins de références circulaires possible.

Pour moi, c'est beaucoup plus facile à maintenir et à visualiser dans un système de dénomination vertical plutôt qu'horizontal. si component1.display a une référence à component2.dataaccess, cela déclenche plus de cloches d'avertissement que si display.component1 a une référence à dataaccess. composant2.

Bien sûr, les composants partagés par les deux vont dans leur propre package.


Si je vous comprends bien, vous préconiseriez la convention de dénomination verticale. Je pense que c'est à cela que sert un package * .utils, lorsque vous avez besoin d'une classe sur plusieurs tranches.
Tim Visher

2

Je suis totalement et propose l'organisation logique ("par fonctionnalité")! Un package doit suivre le concept de "module" aussi étroitement que possible. L'organisation fonctionnelle peut répartir un module sur un projet, ce qui entraîne moins d'encapsulation et est sujette à des changements dans les détails de mise en œuvre.

Prenons un plugin Eclipse par exemple: mettre toutes les vues ou actions dans un seul paquet serait un gâchis. Au lieu de cela, chaque composant d'une fonctionnalité doit aller dans le package de la fonctionnalité, ou s'il y en a beaucoup, dans des sous-packages (featureA.handlers, featureA.preferences, etc.)

Bien sûr, le problème réside dans le système de paquets hiérarchiques (dont Java a entre autres), qui rend la gestion des préoccupations orthogonales impossible ou du moins très difficile - bien qu'elles se produisent partout!


1

J'irais personnellement pour la dénomination fonctionnelle. La raison courte: cela évite la duplication de code ou le cauchemar des dépendances.

Laissez-moi élaborer un peu. Que se passe-t-il lorsque vous utilisez un fichier jar externe, avec sa propre arborescence de packages? Vous importez efficacement le code (compilé) dans votre projet, et avec lui une arborescence de packages (fonctionnellement séparée). Serait-il judicieux d'utiliser les deux conventions de dénomination en même temps? Non, à moins que cela ne vous ait été caché. Et c'est le cas, si votre projet est suffisamment petit et n'a qu'un seul composant. Mais si vous avez plusieurs unités logiques, vous ne voudrez probablement pas réimplémenter, disons, le module de chargement de fichiers de données. Vous voulez le partager entre des unités logiques, ne pas avoir de dépendances artificielles entre des unités logiquement non liées et ne pas avoir à choisir dans quelle unité vous allez placer cet outil partagé particulier.

Je suppose que c'est pourquoi la dénomination fonctionnelle est la plus utilisée dans les projets qui atteignent, ou sont censés atteindre, une certaine taille, et la dénomination logique est utilisée dans les conventions de dénomination de classe pour garder une trace du rôle spécifique, le cas échéant de chaque classe dans un paquet.

Je vais essayer de répondre plus précisément à chacun de vos points sur la dénomination logique.

  1. Si vous devez aller pêcher dans d'anciennes classes pour modifier des fonctionnalités lors d'un changement de plan, c'est le signe d'une mauvaise abstraction: vous devez construire des classes qui fournissent une fonctionnalité bien définie, définissable en une courte phrase. Seules quelques classes de haut niveau devraient rassembler tout cela pour refléter votre intelligence d'affaires. De cette façon, vous serez en mesure de réutiliser plus de code, d'avoir une maintenance plus facile, une documentation plus claire et moins de problèmes de dépendance.

  2. Cela dépend principalement de la façon dont vous réalisez votre projet. Décidément, la vue logique et fonctionnelle est orthogonale. Donc, si vous utilisez une convention de dénomination, vous devez appliquer l'autre aux noms de classe afin de garder un certain ordre, ou passer d'une convention de dénomination à une autre à une certaine profondeur.

  3. Les modificateurs d'accès sont un bon moyen de permettre à d'autres classes qui comprennent votre traitement d'accéder aux entrailles de votre classe. Une relation logique ne signifie pas une compréhension des contraintes algorithmiques ou de concurrence. Fonctionnel peut, bien que ce ne soit pas le cas. Je suis très fatigué des modificateurs d'accès autres que publics et privés, car ils cachent souvent un manque d'architecture appropriée et d'abstraction de classe.

  4. Dans les grands projets commerciaux, l'évolution des technologies se produit plus souvent que vous ne le pensez. Par exemple, j'ai déjà dû changer 3 fois l'analyseur XML, 2 fois la technologie de mise en cache et 2 fois le logiciel de géolocalisation. Heureusement que j'avais caché tous les détails granuleux dans un paquet dédié ...


1
Pardonnez-moi si je me trompe, mais il me semble que vous parlez plutôt de développer un cadre destiné à être utilisé par de nombreuses personnes. Je pense que les règles changent entre ce type de développement et le développement de systèmes d'utilisateurs finaux. Ai-je tort?
Tim Visher

Je pense que pour les classes courantes utilisées par de nombreuses tranches verticales, il est logique d'avoir quelque chose comme un utilspackage. Ensuite, toutes les classes qui en ont besoin peuvent simplement dépendre de ce package.
Tim Visher

Encore une fois, la taille compte: quand un projet devient suffisamment grand, il doit être soutenu par plusieurs personnes et une sorte de spécialisation se produit. Pour faciliter l'interaction et éviter les erreurs, la segmentation du projet en plusieurs parties devient rapidement nécessaire. Et le paquet utils deviendra bientôt gigantesque!
Varkhan

1

C'est une expérience intéressante de ne pas utiliser du tout de paquet (sauf pour le paquet racine).

La question qui se pose alors est de savoir quand et pourquoi il est judicieux d'introduire des packages. Vraisemblablement, la réponse sera différente de ce que vous auriez répondu au début du projet.

Je suppose que votre question se pose du tout, car les packages sont comme des catégories et il est parfois difficile de décider pour l'une ou l'autre. Parfois, les balises seraient plus appréciées pour indiquer qu'une classe est utilisable dans de nombreux contextes.


0

D'un point de vue purement pratique, les constructions de visibilité de java permettent aux classes du même package d'accéder aux méthodes et propriétés avec protectedet defaultvisibilité, ainsi que publiccelles. Utiliser des méthodes non publiques à partir d'une couche complètement différente du code serait certainement une grosse odeur de code. J'ai donc tendance à mettre les classes de la même couche dans le même package.

Je n'utilise pas souvent ces méthodes protégées ou par défaut ailleurs - sauf peut-être dans les tests unitaires de la classe - mais quand je le fais, c'est toujours d'une classe de la même couche


N'est-ce pas quelque chose de la nature des systèmes multicouches de toujours dépendre de la couche suivante? Par exemple, l'interface utilisateur dépend de votre couche de services qui dépend de votre couche de domaine, etc. Empaqueter des tranches verticales ensemble semble protéger contre les dépendances inter-packages excessives, non?
Tim Visher

Je n'utilise presque jamais de méthodes protégées et par défaut. 99% sont publics ou privés. Quelques exceptions: (1) visibilité par défaut pour les méthodes utilisées uniquement par les tests unitaires, (2) méthode abstraite protégée qui n'est utilisée qu'à partir de la classe de base abstraite.
Esko Luontola le

1
Tim Visher, les dépendances entre les packages ne sont pas un problème, tant que les dépendances pointent toujours dans la même direction et qu'il n'y a pas de cycles dans le graphe de dépendances.
Esko Luontola le

0

Ça dépend. Dans mon métier, nous découpons parfois les packages par fonctions (accès aux données, analytique) ou par classe d'actifs (crédit, actions, taux d'intérêt). Sélectionnez simplement la structure qui convient le mieux à votre équipe.


Y a-t-il une raison d'aller dans un sens ou dans l'autre?
Tim Visher

Le fractionnement par classe d'actifs (plus généralement: par domaine d'activité) permet aux nouvelles personnes de trouver plus facilement leur chemin dans le code. La division par fonction est bonne pour l'encapsulation. Pour moi, l'accès aux "packages" en Java s'apparente à un "ami" en C ++.
quant_dev

@quant_dev Je ne suis pas d'accord avec "..by function is good for encapsulation". Je pense que vous voulez dire que le contraire est vrai. Aussi, ne choisissez pas simplement «commodité». Il y a un cas pour chacun (voir ma réponse).
i3ensays

-3

D'après mon expérience, la réutilisation crée plus de problèmes que la résolution. Avec les processeurs et la mémoire les plus récents et bon marché, je préférerais la duplication du code plutôt que de l'intégrer étroitement afin de le réutiliser.


5
La réutilisation du code ne concerne pas le prix du matériel, mais les coûts de maintenance du code.
user2793390

1
D'accord, mais réparer quelque chose dans un module simple ne devrait pas affecter tout le code. De quels coûts de maintenance parlez-vous?
Raghu Kasturi

1
Une correction de bogue dans un module devrait affecter l'ensemble de l'application. Pourquoi voudriez-vous corriger le même bogue plusieurs fois?
user2793390
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.