Quale strategia utilizzi per la denominazione dei pacchetti nei progetti Java e perché? [chiuso]


88

Ci ho pensato tempo fa e recentemente è riemerso mentre il mio negozio sta realizzando la sua prima vera app web Java.

Come intro, vedo due principali strategie di denominazione dei pacchetti. (Per essere chiari, non mi riferisco all'intera parte 'domain.company.project' di questo, sto parlando della convenzione del pacchetto sottostante.) Ad ogni modo, le convenzioni di denominazione dei pacchetti che vedo sono le seguenti:

  1. Funzionale: denominazione dei pacchetti in base alla loro funzione architettonicamente piuttosto che alla loro identità in base al dominio aziendale. Un altro termine per questo potrebbe essere la denominazione in base a "livello". Quindi, avresti un pacchetto * .ui e un pacchetto * .domain e un pacchetto * .orm. I tuoi pacchetti sono fette orizzontali anziché verticali.

    Questo è molto più comune della denominazione logica. In effetti, non credo di aver mai visto o sentito parlare di un progetto che fa questo. Questo ovviamente mi rende diffidente (un po 'come pensare di aver trovato una soluzione a un problema NP) poiché non sono molto intelligente e presumo che tutti debbano avere ottime ragioni per farlo nel modo in cui lo fanno. D'altra parte, non sono contrario al fatto che le persone perdano solo l'elefante nella stanza e non ho mai sentito un argomento reale per nominare i pacchetti in questo modo. Sembra solo essere lo standard de facto.

  2. Logico: denominare i pacchetti in base alla loro identità di dominio aziendale e inserire in quel pacchetto ogni classe che ha a che fare con quella fetta verticale di funzionalità.

    Non l'ho mai visto o sentito, come ho detto prima, ma per me ha molto senso.

    1. Tendo ad avvicinarmi ai sistemi verticalmente piuttosto che orizzontalmente. Voglio entrare e sviluppare il sistema di elaborazione degli ordini, non il livello di accesso ai dati. Ovviamente, ci sono buone probabilità che tocchi il livello di accesso ai dati nello sviluppo di quel sistema, ma il punto è che non la penso in questo modo. Ciò significa, ovviamente, che quando ricevo un ordine di modifica o voglio implementare qualche nuova funzionalità, sarebbe bello non dover andare in giro in un mucchio di pacchetti per trovare tutte le classi correlate. Invece, guardo solo nel pacchetto X perché quello che sto facendo ha a che fare con X.

    2. Dal punto di vista dello sviluppo, vedo come una grande vittoria avere i tuoi pacchetti documentare il tuo dominio aziendale piuttosto che la tua architettura. Sento che il dominio è quasi sempre la parte del sistema che è più difficile da capire mentre l'architettura del sistema, specialmente a questo punto, sta diventando quasi banale nella sua implementazione. Il fatto che io possa arrivare a un sistema con questo tipo di convenzione di denominazione e immediatamente dalla denominazione dei pacchetti sapere che si tratta di ordini, clienti, imprese, prodotti, ecc. Sembra davvero utile.

    3. Sembra che questo ti consenta di sfruttare molto meglio i modificatori di accesso di Java. Ciò consente di definire in modo molto più pulito le interfacce nei sottosistemi piuttosto che nei livelli del sistema. Quindi, se hai un sottosistema di ordini che desideri sia persistente in modo trasparente, in teoria potresti non far sapere a nient'altro che è persistente non dovendo creare interfacce pubbliche per le sue classi di persistenza nel livello dao e invece impacchettando la classe dao in solo con le classi di cui si occupa. Ovviamente, se volessi esporre questa funzionalità, potresti fornire un'interfaccia per essa o renderla pubblica. Sembra che tu perda molto di questo avendo una fetta verticale delle funzionalità del tuo sistema suddivisa su più pacchetti.

    4. Suppongo che uno svantaggio che posso vedere è che rende un po 'più difficile strappare gli strati. Invece di eliminare o rinominare un pacchetto e poi rilasciarne uno nuovo con una tecnologia alternativa, devi entrare e cambiare tutte le classi in tutti i pacchetti. Tuttavia, non vedo che questo sia un grosso problema. Potrebbe essere dovuto a una mancanza di esperienza, ma devo immaginare che il numero di volte in cui sostituisci le tecnologie impallidisce rispetto al numero di volte in cui entri e modifichi sezioni di funzionalità verticali all'interno del tuo sistema.

Quindi immagino che la domanda ti verrebbe posta, come si nominano i pacchetti e perché? Per favore, capisci che non penso necessariamente di essere incappato nell'oca d'oro o qualcosa del genere qui. Sono abbastanza nuovo in tutto questo con un'esperienza prevalentemente accademica. Tuttavia, non riesco a individuare i buchi nel mio ragionamento, quindi spero che lo possiate tutti così che io possa andare avanti.


Quello che ho sentito finora sembra indicare che è una specie di giudizio. Tuttavia, la maggior parte delle risposte non si è concentrata su considerazioni pratiche. È più quello che sto cercando. Grazie per l'aiuto finora, però!
Tim Visher

Non penso sia una chiamata di giudizio. Dividere prima per livello, poi per funzionalità è sicuramente la strada da percorrere. Sto aggiornando la mia risposta.
eljenso

@eljenso: dillo ai ragazzi di Eclipse :) In eclipse, la prima divisione è "caratteristiche", che sono unità di implementazioni e anche funzionalità. All'interno di "funzionalità", ci sono "plug-in", che di solito sono divisi per livello, ma i plug-in all'interno della stessa "funzionalità" devono essere sempre distribuiti insieme. Se si dispone di una sola unità di distribuzione, la divisione prima per livello e solo successivamente per funzionalità può avere senso. Con più unità di distribuzione, non si desidera distribuire solo il livello di presentazione, ma è necessaria una funzionalità aggiuntiva.
Peter Štibraný

Ho una domanda specifica riguardante il livello aziendale di un'applicazione, quale dovrebbe essere la convenzione di denominazione?
Bionix1441

Risposte:


32

Per la progettazione del pacchetto, divido prima per livello, quindi per altre funzionalità.

Ci sono alcune regole aggiuntive:

  1. i livelli sono impilati dal più generale (in basso) al più specifico (in alto)
  2. ogni livello ha un'interfaccia pubblica (astrazione)
  3. uno strato può dipendere solo dall'interfaccia pubblica di un altro strato (incapsulamento)
  4. un livello può dipendere solo da livelli più generali (dipendenze dall'alto verso il basso)
  5. uno strato dipende preferibilmente dallo strato direttamente sotto di esso

Quindi, per un'applicazione web, ad esempio, potresti avere i seguenti livelli nel tuo livello di applicazione (dall'alto verso il basso):

  • livello di presentazione: genera l'interfaccia utente che verrà mostrata nel livello client
  • livello applicazione: contiene la logica specifica di un'applicazione, stateful
  • livello di servizio: raggruppa le funzionalità per dominio, apolidi
  • livello di integrazione: fornisce l'accesso al livello di backend (db, jms, email, ...)

Per il layout del pacchetto risultante, queste sono alcune regole aggiuntive:

  • la radice di ogni nome di pacchetto è <prefix.company>.<appname>.<layer>
  • l'interfaccia di un livello è ulteriormente suddivisa per funzionalità: <root>.<logic>
  • l'implementazione privata di un livello ha il prefisso privato: <root>.private

Ecco un esempio di layout.

Il livello di presentazione è diviso per tecnologia di visualizzazione e, facoltativamente, per (gruppi di) applicazioni.

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

Il livello dell'applicazione è suddiviso in casi d'uso.

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

Il livello di servizio è suddiviso in domini aziendali, influenzati dalla logica del dominio in un livello di backend.

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

Il livello di integrazione è suddiviso in "tecnologie" e oggetti di accesso.

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

I vantaggi di separare pacchetti come questo sono che è più facile gestire la complessità e aumenta la testabilità e la riusabilità. Anche se sembra un sacco di spese generali, nella mia esperienza in realtà è molto naturale e tutti coloro che lavorano su questa struttura (o simile) lo prendono in pochi giorni.

Perché penso che l'approccio verticale non sia così buono?

Nel modello a strati, diversi moduli di alto livello possono utilizzare lo stesso modulo di livello inferiore. Ad esempio: è possibile creare più viste per la stessa applicazione, più applicazioni possono utilizzare lo stesso servizio, più servizi possono utilizzare lo stesso gateway. Il trucco qui è che quando ci si sposta attraverso i livelli, il livello di funzionalità cambia. I moduli nei livelli più specifici non mappano 1-1 sui moduli del livello più generale, perché i livelli di funzionalità che esprimono non mappano 1-1.

Quando si utilizza l'approccio verticale per la progettazione del pacchetto, ovvero si divide prima per funzionalità, quindi si forza tutti i blocchi con diversi livelli di funzionalità nella stessa "giacca di funzionalità". Potresti progettare i tuoi moduli generali per quello più specifico. Ma ciò viola l'importante principio secondo cui il livello più generale non dovrebbe conoscere i livelli più specifici. Il livello di servizio, ad esempio, non dovrebbe essere modellato sui concetti del livello dell'applicazione.


"I vantaggi di separare pacchetti come questo sono che è più facile gestire la complessità e aumenta la testabilità e la riusabilità". Davvero non entri nei motivi per cui è così.
mangoDrunk

1
Non approfondisci i motivi per cui non è così. Ma comunque ... le regole per l'organizzazione sono abbastanza chiare e dirette, quindi riduce la complessità. È più testabile e riutilizzabile grazie all'organizzazione stessa. Tutti possono provarlo e poi possiamo essere d'accordo o in disaccordo. Spiego brevemente alcuni degli svantaggi dell'approccio verticale, spiegando implicitamente i motivi per cui ritengo che l'approccio orizzontale sia più facile.
eljenso

8
Questo articolo spiega abbastanza bene perché è meglio usare pacchetto per funzionalità invece di pacchetto per strato .
Tom

@eljenso "Non entri nei motivi per cui non è così", cercando di spostare l'onere della prova? Penso che l'articolo pubblicato da Tom spieghi molto bene perché pacchetto per funzionalità è migliore e sto avendo difficoltà a prendere "Non penso che sia un giudizio. Prima la divisione per livello, poi per funzionalità è sicuramente il modo andare "sul serio.
pka

18

Mi ritrovo ad attenermi ai principi di progettazione della confezione dello zio Bob . In breve, le classi che devono essere riutilizzate insieme e modificate insieme (per lo stesso motivo, ad esempio un cambiamento di dipendenza o un cambiamento di framework) dovrebbero essere messe nello stesso pacchetto. Secondo me, la scomposizione funzionale avrebbe maggiori possibilità di raggiungere questi obiettivi rispetto alla scomposizione verticale / specifica del business nella maggior parte delle applicazioni.

Ad esempio, una porzione orizzontale di oggetti di dominio può essere riutilizzata da diversi tipi di front-end o persino applicazioni ed è probabile che una porzione orizzontale del front-end web cambi insieme quando è necessario modificare il framework web sottostante. D'altra parte, è facile immaginare l'effetto a catena di queste modifiche su molti pacchetti se le classi in diverse aree funzionali sono raggruppate in quei pacchetti.

Ovviamente, non tutti i tipi di software sono uguali e la ripartizione verticale può avere senso (in termini di raggiungimento degli obiettivi di riusabilità e chiudibilità al cambiamento) in alcuni progetti.


Grazie, è molto chiaro. Come ho detto nella domanda, sento che il numero di volte in cui cambieresti le tecnologie è molto inferiore a quello che sposteresti su sezioni verticali di funzionalità. Non è stata questa la tua esperienza?
Tim Visher

Non si tratta solo di tecnologie. Il punto n. 1 del tuo post originale ha senso solo se le sezioni verticali sono applicazioni / servizi indipendenti che comunicano tra loro tramite un'interfaccia (SOA, se vuoi). più sotto ...
Buu Nguyen

Ora, mentre ti muovi nei dettagli di ciascuna di quelle app / servizio a grana fine che ha la propria interfaccia grafica / business / dati, riesco a malapena a immaginare che cambi in una fetta verticale, sia che si tratti di tecnologia, dipendenza, regola / flusso di lavoro, registrazione , sicurezza, stile dell'interfaccia utente, possono essere completamente isolati dalle altre sezioni.
Buu Nguyen

Sarei interessato al tuo feedback sulla mia risposta
i3ensays il

@BuuNguyen Il collegamento ai principi di progettazione del pacchetto è interrotto.
johnnieb

5

Di solito sono presenti entrambi i livelli di divisione. Dall'alto ci sono le unità di schieramento. Questi sono denominati "logicamente" (nei tuoi termini, pensa alle funzionalità di Eclipse). All'interno dell'unità di distribuzione, hai una divisione funzionale dei pacchetti (pensa ai plugin Eclipse).

Ad esempio, è funzione com.feature, ed è composto com.feature.client, com.feature.coree com.feature.uiplugin. All'interno dei plugin, ho pochissima divisione con altri pacchetti, anche se non è insolito.

Aggiornamento: A proposito, Juergen Hoeller parla molto dell'organizzazione del codice su InfoQ: http://www.infoq.com/presentations/code-organization-large-projects . Juergen è uno degli architetti della primavera e sa molto di queste cose.


Non lo seguo abbastanza. Di solito potresti vedere com.apache.wicket.x dove x è funzionale o logico. Di solito non vedo com.x. Immagino tu stia dicendo che andresti con una struttura com.company.project.feature.layer? Hai ragioni?
Tim Visher

Il motivo è che "com.company.project.feature" è l'unità di distribuzione. A questo livello, alcune funzionalità sono opzionali e possono essere ignorate (ovvero non distribuite). Tuttavia, all'interno delle funzionalità, le cose non sono opzionali e di solito le vuoi tutte. Qui ha più senso dividere per strati.
Peter Štibraný

4

La maggior parte dei progetti java su cui ho lavorato per suddividere i pacchetti java prima funzionalmente, poi logicamente.

Di solito le parti sono sufficientemente grandi da essere suddivise in artefatti di build separati, dove potresti mettere le funzionalità di base in un jar, le API in un altro, le cose del frontend web in un warfile, ecc.


Come potrebbe essere? domain.company.project.function.logicalSlice?
Tim Visher

abbastanza! egdcpweb.profiles, dcpweb.registration, dcpapis, dcppersistence ecc. generalmente funziona abbastanza bene, il tuo chilometraggio può variare. dipende anche se stai utilizzando la modellazione del dominio: se hai più domini, potresti voler prima suddividere per dominio.
Ben Hardy

Personalmente preferisco l'opposto, logico poi funzionale. mele.rpc, mele.model e poi banana.model, banana.store
mP.

C'è più valore nell'essere in grado di raggruppare tutte le cose della mela insieme che raggruppare tutte le cose web insieme.
mP.

3

I pacchetti devono essere compilati e distribuiti come un'unità. Quando si considera quali classi appartengono a un pacchetto, uno dei criteri chiave sono le sue dipendenze. Da quali altri pacchetti (incluse le librerie di terze parti) dipende questa classe. Un sistema ben organizzato raggrupperà le classi con dipendenze simili in un pacchetto. Ciò limita l'impatto di una modifica in una libreria, poiché solo pochi pacchetti ben definiti dipenderanno da essa.

Sembra che il tuo sistema logico e verticale possa tendere a "macchiare" le dipendenze nella maggior parte dei pacchetti. Cioè, se ogni funzionalità è confezionata come una sezione verticale, ogni pacchetto dipenderà da ogni libreria di terze parti che usi. È probabile che qualsiasi modifica a una libreria si propaghi all'intero sistema.


Ah. Quindi una cosa che fa le sezioni orizzontali è proteggerti dagli aggiornamenti effettivi nelle librerie che stai utilizzando. Penso che tu abbia ragione sul fatto che le sezioni verticali macchiano ogni dipendenza che il tuo sistema ha in ogni pacchetto. Perché è così importante?
Tim Visher

Per una "funzionalità", non è un grosso problema. Tendono ad essere più volatili e hanno comunque più dipendenze. D'altra parte, questo codice "client" tende ad avere una bassa riutilizzabilità. Per qualcosa che intendi essere una libreria riutilizzabile, isolare i clienti da ogni piccola modifica è un ottimo investimento.
erickson

3

Personalmente preferisco raggruppare le classi in modo logico, quindi all'interno che includa un sottopacchetto per ogni partecipazione funzionale.

Obiettivi del confezionamento

Dopotutto, i pacchetti riguardano il raggruppamento delle cose: l'idea di classi correlate vivono l'una vicino all'altra. Se vivono nello stesso pacchetto possono sfruttare il pacchetto privato per limitare la visibilità. Il problema è raggruppare tutta la tua vista e le tue cose in un unico pacchetto può portare a confondere molte classi in un unico pacchetto. La prossima cosa sensata da fare è quindi creare di conseguenza la visualizzazione, la persistenza, i sotto-pacchetti util e le classi di refactoring. Sfortunatamente, l'ambito protetto e privato del pacchetto non supporta il concetto di pacchetto e sottopacchetto corrente poiché ciò aiuterebbe a far rispettare tali regole di visibilità.

Ora vedo il valore nella separazione tramite funzionalità perché quale valore c'è per raggruppare tutte le cose relative alla vista. Le cose in questa strategia di denominazione si disconnettono con alcune classi nella vista mentre altre sono persistenti e così via.

Un esempio della mia struttura logica del packaging

A scopo illustrativo, nominiamo due moduli: non useremo il nome modulo come concetto che raggruppa le classi sotto un particolare ramo di un albero del pacchetto.

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

Vantaggi

Un client che utilizza Banana.store.BananaStore è esposto solo alle funzionalità che desideriamo rendere disponibili. La versione di ibernazione è un dettaglio di implementazione di cui non hanno bisogno di essere a conoscenza né dovrebbero vedere queste classi mentre aggiungono disordine alle operazioni di archiviazione.

Altri vantaggi logici e funzionali

Più si sale verso la radice, più ampio diventa lo scopo e le cose che appartengono a un pacchetto iniziano a mostrare sempre più dipendenze da cose che appartengono ai suoi moduli. Se si esaminasse ad esempio il modulo "banana", la maggior parte delle dipendenze sarebbe limitata all'interno di quel modulo. In effetti, la maggior parte degli helper sotto "banana" non sarebbe affatto referenziata al di fuori di questo scopo del pacchetto.

Perché funzionalità?

Quale valore si ottiene raggruppando le cose in base alla funzionalità. La maggior parte delle classi in questo caso sono indipendenti l'una dall'altra con poca o nessuna necessità di trarre vantaggio dai metodi o dalle classi private del pacchetto. Il loro refactoring in modo da inserirli nei propri sottopacchetti guadagna poco, ma aiuta a ridurre il disordine.

Modifiche dello sviluppatore al sistema

Quando gli sviluppatori hanno il compito di apportare modifiche un po 'più che banali, sembra sciocco che potenzialmente abbiano modifiche che includono file da tutte le aree dell'albero dei pacchetti. Con l'approccio strutturato logico le loro modifiche sono più locali all'interno della stessa parte dell'albero dei pacchetti che sembra giusto.


"Quando gli sviluppatori [...] file da tutte le aree dell'albero dei pacchetti." Hai ragione quando dici che sembra sciocco. Perché questo è esattamente il punto di una corretta stratificazione: le modifiche non si propagano nell'intera struttura dell'applicazione.
eljenso

Grazie per il consiglio. Non sono sicuro di capirti chiaramente. Credo che tu stia cercando di sostenere i pacchetti logici, non sono esattamente chiaro perché. Potresti provare a rendere la tua risposta un po 'più chiara, magari riformulandola? Grazie!
Tim Visher

3

Entrambi gli approcci funzionali (architettonici) e logici (caratteristici) al packaging hanno un posto. Molte applicazioni di esempio (quelle che si trovano nei libri di testo, ecc.) Seguono l'approccio funzionale di inserire presentazioni, servizi aziendali, mappatura dei dati e altri livelli architettonici in pacchetti separati. Nelle applicazioni di esempio, ogni pacchetto spesso ha solo poche o solo una classe.

Questo approccio iniziale va bene poiché un esempio artificioso spesso serve a: 1) mappare concettualmente l'architettura del framework presentato, 2) è fatto con un unico scopo logico (ad esempio aggiungere / rimuovere / aggiornare / eliminare animali domestici da una clinica) . Il problema è che molti lettori lo considerano uno standard che non ha limiti.

Man mano che un'applicazione "aziendale" si espande per includere sempre più funzionalità, seguire l' approccio funzionale diventa un peso. Sebbene io sappia dove cercare i tipi in base al livello dell'architettura (ad esempio i web controller sotto un pacchetto "web" o "ui", ecc.), Lo sviluppo di una singola caratteristica logica inizia a richiedere il salto avanti e indietro tra molti pacchetti. Questo è ingombrante, per lo meno, ma è peggio di così.

Poiché i tipi logicamente correlati non sono impacchettati insieme, l'API è eccessivamente pubblicizzata; l'interazione tra tipi correlati logicamente è forzata a essere "pubblica" in modo che i tipi possano importare e interagire tra loro (la capacità di ridurre al minimo la visibilità del pacchetto / predefinita è persa).

Se sto costruendo una libreria di framework, i miei pacchetti seguiranno sicuramente un approccio di packaging funzionale / architettonico. I consumatori di API potrebbero persino apprezzare che le loro istruzioni di importazione contengono un pacchetto intuitivo che prende il nome dall'architettura.

Al contrario, quando creo un'applicazione aziendale, creerò un pacchetto per funzionalità. Non ho problemi a posizionare Widget, WidgetService e WidgetController tutti nello stesso pacchetto " com.myorg.widget. " E quindi a sfruttare la visibilità predefinita (e avere meno istruzioni di importazione e dipendenze tra pacchetti).

Esistono, tuttavia, casi di cross-over. Se il mio WidgetService viene utilizzato da molti domini logici (funzionalità), potrei creare un pacchetto " com.myorg.common.service. ". C'è anche una buona possibilità che crei classi con l'intenzione di essere riutilizzabili tra le funzionalità e finire con pacchetti come " com.myorg.common.ui.helpers. " E " com.myorg.common.util. ". Potrei anche finire per spostare tutte queste classi "comuni" successive in un progetto separato e includerle nella mia applicazione aziendale come dipendenza myorg-commons.jar.


2

Dipende dalla granularità dei tuoi processi logici?

Se sono autonomi, spesso hai un nuovo progetto per loro nel controllo del codice sorgente, piuttosto che un nuovo pacchetto.

Il progetto a cui sto lavorando al momento sta errando verso la divisione logica, c'è un pacchetto per l'aspetto jython, un pacchetto per un motore di regole, pacchetti per foo, bar, binglewozzle, ecc. Sto cercando di avere i parser specifici per XML / writer per ogni modulo all'interno di quel pacchetto, piuttosto che avere un pacchetto XML (cosa che ho fatto in precedenza), sebbene ci sarà ancora un pacchetto XML di base in cui va la logica condivisa. Una ragione di ciò, tuttavia, è che può essere estensibile (plug-in) e quindi ogni plug-in dovrà definire anche il proprio codice XML (o database, ecc.), Quindi la centralizzazione potrebbe introdurre problemi in seguito.

Alla fine sembra essere come sembra più sensato per il particolare progetto. Tuttavia, penso che sia facile impacchettare seguendo le linee del tipico diagramma a strati del progetto. Ti ritroverai con un mix di packaging logico e funzionale.

Quello che serve sono gli spazi dei nomi con tag. Un parser XML per alcune funzionalità Jython potrebbe essere etichettato sia Jython che XML, invece di dover scegliere uno o l'altro.

O forse sto ondeggiando.


Non capisco appieno il tuo punto. Penso che quello che stai dicendo è che dovresti solo fare ciò che ha più senso per il tuo progetto e per te è logico con un po 'di funzionalità. Ci sono ragioni pratiche specifiche per questo?
Tim Visher

Come ho detto, avrò plug-in per aumentare le funzionalità integrate. Un plugin dovrà essere in grado di analizzare e scrivere il suo XML (ed eventualmente nel DB) oltre a fornire funzionalità. Quindi ho com.xx.feature.xml o com.xx.xml.feature? Il primo sembra più ordinato.
JeeBee

2

Cerco di progettare le strutture dei pacchetti in modo tale che se dovessi disegnare un grafico delle dipendenze, sarebbe facile da seguire e utilizzare un modello coerente, con il minor numero di riferimenti circolari possibile.

Per me, questo è molto più facile da mantenere e visualizzare in un sistema di denominazione verticale piuttosto che orizzontale. se component1.display ha un riferimento a component2.dataaccess, questo genera più campanelli di avvertimento che se display.component1 ha un riferimento a dataaccess. component2.

Ovviamente, i componenti condivisi da entrambi vanno nel loro pacchetto.


Da quanto ti ho capito, sosterresti la convenzione di denominazione verticale. Penso che questo sia lo scopo di un pacchetto * .utils, quando hai bisogno di una classe su più sezioni.
Tim Visher

2

Seguo e propongo totalmente l'organizzazione logica ("per caratteristica")! Un pacchetto dovrebbe seguire il concetto di "modulo" il più fedelmente possibile. L'organizzazione funzionale può distribuire un modulo su un progetto, con conseguente minore incapsulamento e incline a modifiche nei dettagli di implementazione.

Prendiamo ad esempio un plugin Eclipse: mettere tutte le visualizzazioni o le azioni in un unico pacchetto sarebbe un casino. Invece, ogni componente di una funzionalità dovrebbe andare nel pacchetto della funzionalità, o se ce ne sono molti, in sottopacchetti (featureA.handlers, featureA.preferences ecc.)

Naturalmente, il problema risiede nel sistema di pacchetti gerarchici (che tra gli altri possiede Java), che rende impossibile o almeno molto difficile la gestione delle questioni ortogonali, sebbene si verifichino ovunque!


1

Personalmente sceglierei la denominazione funzionale. Il motivo breve: evita la duplicazione del codice o l'incubo delle dipendenze.

Lasciami elaborare un po '. Cosa succede quando si utilizza un file jar esterno, con il proprio albero dei pacchetti? Stai effettivamente importando il codice (compilato) nel tuo progetto, e con esso un albero dei pacchetti (funzionalmente separato). Avrebbe senso usare le due convenzioni di denominazione contemporaneamente? No, a meno che non ti sia stato nascosto. E lo è, se il tuo progetto è abbastanza piccolo e ha un singolo componente. Ma se hai più unità logiche, probabilmente non vuoi reimplementare, diciamo, il modulo di caricamento dei file di dati. Vuoi condividerlo tra unità logiche, non avere dipendenze artificiali tra unità logicamente non correlate e non devi scegliere in quale unità mettere quel particolare strumento condiviso.

Immagino che questo sia il motivo per cui la denominazione funzionale è la più utilizzata nei progetti che raggiungono, o sono destinati a raggiungere, una certa dimensione e la denominazione logica viene utilizzata nelle convenzioni di denominazione delle classi per tenere traccia del ruolo specifico, se uno qualsiasi di ciascuna classe in un pacchetto.

Cercherò di rispondere in modo più preciso a ciascuno dei tuoi punti sulla denominazione logica.

  1. Se devi andare a pescare nelle vecchie classi per modificare le funzionalità quando hai un cambio di programma, è segno di cattiva astrazione: dovresti costruire classi che forniscano una funzionalità ben definita, definibile in una breve frase. Solo poche classi di alto livello dovrebbero riunire tutto ciò per riflettere la tua business intelligence. In questo modo, sarai in grado di riutilizzare più codice, avere una manutenzione più semplice, una documentazione più chiara e meno problemi di dipendenza.

  2. Dipende principalmente dal modo in cui crei il tuo progetto. Sicuramente, la vista logica e funzionale sono ortogonali. Quindi, se si utilizza una convenzione di denominazione, è necessario applicare l'altra ai nomi di classe per mantenere un certo ordine, o passare da una convenzione di denominazione a un'altra a una certa profondità.

  3. I modificatori di accesso sono un buon modo per consentire ad altre classi che comprendono la tua elaborazione di accedere alle parti interne della tua classe. Relazione logica non significa comprensione dei vincoli algoritmici o di concorrenza. Funzionale può, anche se non lo fa. Sono molto stanco di modificatori di accesso diversi da quelli pubblici e privati, perché spesso nascondono una mancanza di un'architettura adeguata e un'astrazione di classe.

  4. Nei grandi progetti commerciali, il cambiamento delle tecnologie avviene più spesso di quanto si potrebbe credere. Ad esempio, ho dovuto cambiare già 3 volte il parser XML, 2 volte la tecnologia di caching e 2 volte il software di geolocalizzazione. Meno male che avevo nascosto tutti i dettagli grintosi in un pacchetto dedicato ...


1
Perdonami se sbaglio, ma mi sembra che tu stia parlando più dello sviluppo di un framework destinato ad essere utilizzato da molte persone. Penso che le regole cambino tra quel tipo di sviluppo e lo sviluppo di sistemi per l'utente finale. Ho sbagliato?
Tim Visher

Penso che per le classi comuni utilizzate da molte delle sezioni verticali, abbia senso avere qualcosa come un utilspacchetto. Quindi, qualsiasi classe che ne abbia bisogno può semplicemente dipendere da quel pacchetto.
Tim Visher

Ancora una volta, le dimensioni contano: quando un progetto diventa abbastanza grande, deve essere supportato da più persone e si verificherà una sorta di specializzazione. Per facilitare l'interazione e prevenire errori, diventa rapidamente necessario segmentare il progetto in parti. E il pacchetto utils diventerà presto enorme!
Varkhan

1

È un esperimento interessante non usare affatto i pacchetti (eccetto il pacchetto root).

La domanda che sorge quindi è: quando e perché ha senso introdurre i pacchetti. Presumibilmente, la risposta sarà diversa da quella a cui avresti risposto all'inizio del progetto.

Presumo che la tua domanda sorga affatto, perché i pacchetti sono come le categorie ea volte è difficile decidere per l'uno o l'altro. A volte i tag sarebbero più apprezzati per comunicare che una classe è utilizzabile in molti contesti.


0

Da un punto di vista puramente pratico, i costrutti di visibilità di java consentono alle classi nello stesso pacchetto di accedere a metodi e proprietà con visibilità protectede default, oltre a publicquelli. Usare metodi non pubblici da un livello completamente diverso del codice sarebbe sicuramente un grande odore di codice. Quindi tendo a mettere le classi dello stesso livello nello stesso pacchetto.

Non uso spesso questi metodi protetti o predefiniti altrove, tranne forse negli unit test per la classe, ma quando lo faccio, proviene sempre da una classe allo stesso livello


Non è forse qualcosa della natura dei sistemi multistrato essere sempre dipendenti dallo strato successivo? Ad esempio, l'interfaccia utente dipende dal livello dei servizi che dipende dal livello del dominio, ecc. Il confezionamento di sezioni verticali insieme sembra proteggersi da eccessive dipendenze tra pacchetti, no?
Tim Visher

Non uso quasi mai metodi protetti e predefiniti. Il 99% è pubblico o privato. Alcune eccezioni: (1) visibilità predefinita per i metodi utilizzati solo dagli unit test, (2) metodo astratto protetto che viene utilizzato solo dalla classe base astratta.
Esko Luontola

1
Tim Visher, le dipendenze tra i pacchetti non sono un problema, a patto che le dipendenze puntino sempre nella stessa direzione e non ci siano cicli nel grafico delle dipendenze.
Esko Luontola

0

Dipende. Nella mia linea di lavoro, a volte dividiamo i pacchetti per funzioni (accesso ai dati, analisi) o per classe di attività (credito, azioni, tassi di interesse). Seleziona la struttura più conveniente per la tua squadra.


C'è qualche motivo per andare in entrambi i modi?
Tim Visher

La suddivisione per asset class (più in generale: per dominio aziendale) rende più facile per le nuove persone orientarsi nel codice. La divisione in base alla funzione è utile per l'incapsulamento. Per me, l'accesso al "pacchetto" in Java è simile a un "amico" in C ++.
quant_dev

@quant_dev Non sono d'accordo con "..by function is good for encapsulation". Penso che tu voglia dire che è vero l'esatto contrario. Inoltre, non scegliere solo per "comodità". C'è un caso per ciascuno (vedi la mia risposta).
i3ensays

-3

Dalla mia esperienza, la riutilizzabilità crea più problemi che soluzioni. Con i processori e la memoria più recenti ed economici, preferirei la duplicazione del codice piuttosto che una stretta integrazione per il riutilizzo.


5
Il riutilizzo del codice non riguarda il prezzo dell'hardware ma i costi di manutenzione del codice.
user2793390

1
D'accordo, ma aggiustare qualcosa in un semplice modulo non dovrebbe avere effetto sull'intero codice. Di quali costi di manutenzione parli?
Raghu Kasturi

1
Una correzione di bug in un modulo dovrebbe influenzare l'intera applicazione. Perché vorresti correggere lo stesso bug più volte?
user2793390

Hai dovuto mantenere quel tuo codice anni dopo averlo scritto? Maledico coloro che sono venuti prima di me che hanno adottato un approccio così apatico alla programmazione del computer. Passare il mio tempo a correggere il tuo codice più volte a causa della duplicazione è sia inefficiente che fastidioso. Ho dovuto lavorare sul mio codice 18 anni dopo averlo scritto e ogni errore che ho commesso mi ha reso la vita molto più difficile. la ridondanza è uno di quei terribili errori che ho pagato alla fine.
David Rector
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.