Avere classi "Util" è motivo di preoccupazione? [chiuso]


14

A volte creo classi "Util" che servono principalmente a contenere metodi e valori che non sembrano appartenere altrove. Ma ogni volta che creo una di queste lezioni, penso "uh-oh, me ne pentirò più tardi ...", perché ho letto da qualche parte che è male.

D'altra parte, sembrano esserci due casi convincenti (almeno per me) per loro:

  1. segreti di implementazione utilizzati in più classi all'interno di un pacchetto
  2. fornendo funzionalità utili per aumentare una classe, senza ingombrare la sua interfaccia

Sono sulla strada della distruzione? Cosa dici !! Dovrei refactoring?


11
Smetti di usare Utilnei nomi delle tue classi. Problema risolto.
yannis,

3
@MattFenwick Yannis ha un buon punto. Nominare una classe SomethingUtilè un po 'pigro e oscura semplicemente il vero scopo della classe - lo stesso con le classi denominate SomethingManagero SomethingService. Se quella classe ha una sola responsabilità, dovrebbe essere facile dargli un nome significativo. In caso contrario, questo è il vero problema da affrontare ...
MattDavey,

1
@MDavey buon punto, probabilmente è stata una scelta di parolacce da usare Util, anche se ovviamente non mi aspettavo che si sarebbe risolto e il resto della domanda ignorato ...

1
Se crei più classi * Util separate dal tipo di oggetto che manipolano, penso che vada bene. Non vedo alcun problema con avere StringUtil, ListUtil, ecc. A meno che tu non sia in C #, dovresti usare i metodi di estensione.
marco-fiset,

4
Ho spesso insiemi di funzioni che vengono utilizzate per scopi correlati. Non mappano davvero bene a una classe. Non ho bisogno di una classe ImageResizer, ho solo bisogno di una funzione ResizeImage. Quindi inserirò le funzioni correlate in una classe statica come ImageTools. Per le funzioni che non sono ancora in alcun tipo di raggruppamento, ho una classe statica Util per tenerle, ma una volta che ne ho alcune abbastanza correlate tra loro da inserirle in una classe, le sposterò. Non vedo alcun modo OO migliore per gestirlo.
Filippo

Risposte:


26

Il moderno design OO accetta che non tutto è un oggetto. Alcune cose sono comportamenti o formule e alcune di queste non hanno uno stato. È bello modellare queste cose come funzioni pure per ottenere il vantaggio di quel design.

Java e C # (e altri) richiedono di creare una classe util e saltare attraverso quel telaio per farlo. Fastidioso, ma non la fine del mondo; e non è davvero fastidioso dal punto di vista del design.


Sì, è una specie di cosa stavo pensando. Grazie per la risposta!

D'accordo, anche se va detto che .NET offre ora metodi di estensione e classi parziali in alternativa a questo approccio. Se porti questo articolo all'estremo, finisci con i rubini Ruby, un linguaggio che ha poco bisogno di classi di utilità.
neontapir,

2
@neontapir - Tranne l'implementazione dei metodi di estensione, in pratica ti costringe a creare una classe util con i metodi di estensione ...
Telastyn,

Vero. Ci sono due posti in cui le classi Util ostacolano, una è nella classe stessa e l'altra è il sito di chiamata. Facendo sembrare che i metodi esistano sull'oggetto migliorato, i metodi di estensione risolvono il problema del sito. Sono d'accordo, esiste ancora il problema dell'esistenza della classe Util.
neontapir,

16

Mai dire mai"

Non penso che sia necessariamente un male, è un male solo se lo fai male e lo abusi.

Tutti abbiamo bisogno di strumenti e utilità

Per cominciare, tutti usiamo alcune librerie che a volte sono considerate quasi onnipresenti e indispensabili. Ad esempio, nel mondo Java, Google Guava o alcuni degli Apache Commons ( Apache Commons Lang , Apache Commons Collections , ecc ...).

Quindi è chiaramente necessario per questi.

Evita errori di parola, duplicazione e presentazione di bug

Se si pensa a questi sono praticamente solo un grande mucchio di queste Utilclassi che lei descrive, tranne qualcuno ha attraversato tutto per farli (relativamente) a destra, e sono stati tempo - testati e pesantemente eye-balled da altri.

Quindi direi che la prima regola empirica quando si sente il prurito di scrivere una Utilclasse è controllare che la Utilclasse in realtà non esiste già.

L'unico contro-argomento che ho visto è quando vuoi limitare le tue dipendenze perché:

  • vuoi limitare il footprint di memoria delle tue dipendenze,
  • o vuoi controllare attentamente ciò che gli sviluppatori possono usare (succede in grandi gruppi ossessivi, o quando un particolare framework è noto per avere la strana classe super schifosa da evitare assolutamente da qualche parte).

Ma entrambi questi possono essere affrontati reimballando la libreria usando ProGuard o un equivalente, o smontandolo da soli (per gli utenti di Maven , il plug-in maven-shade offre alcuni schemi di filtro per integrarlo come parte della build).

Quindi, se è in una lib e corrisponde al tuo caso d'uso e nessun benchmark ti dice diversamente, usalo. Se varia un po 'da quello che si è, estenderlo (se possibile) o estenderlo, o in ultima istanza riscriverlo.

Convenzioni di denominazione

Tuttavia, finora in questa risposta li ho chiamati Utilcome te. Non chiamarli così.

Dai loro nomi significativi. Prendi Google Guava come (molto, molto) buon esempio di cosa fare e immagina che lo com.google.guavaspazio dei nomi sia in realtà la tua utilradice.

Chiama il tuo pacchetto util, nel peggiore dei casi, ma non le classi. Se si tratta di Stringoggetti e manipolazione di costrutti di stringa, chiamatelo Strings, non StringUtils(scusate, Apache Commons Lang - Mi piacciono ancora e vi uso!). Se fa qualcosa di specifico, scegli un nome di classe specifico (come Splittero Joiner).

Unità-Test

Se devi ricorrere a scrivere queste utility, assicurati di testarle. La cosa positiva delle utility è che di solito sono componenti piuttosto autonomi, che accettano input specifici e restituiscono output specifici. Questo è il concetto. Quindi non ci sono scuse per non testarli.

Inoltre, i test unitari ti permetteranno di definire e documentare il contratto della loro API. Se i test falliscono, o hai cambiato qualcosa nel modo sbagliato, o significa che stai cercando di cambiare il contratto dell'API (o che i tuoi test originali erano una schifezza: impara da esso e non farlo di nuovo) .

Progettazione API

Le decisioni di progettazione che prenderai per queste API ti seguiranno a lungo, possibilmente. Quindi, mentre non trascorri ore a scrivere una Splitterclone, fai attenzione a come affronti il ​​problema.

Ponetevi alcune domande:

  • Il tuo metodo di utilità garantisce una classe da solo o è abbastanza buono un metodo statico, se ha senso far parte di un gruppo di metodi altrettanto utili?
  • Hai bisogno di metodi di fabbrica per creare oggetti e rendere più leggibili le tue API?
  • A proposito di leggibilità, hai bisogno di un'API fluente , di costruttori , ecc ...?

Volete che questi programmi di utilità coprano un'ampia gamma di casi d'uso, che siano robusti, stabili, ben documentati, seguendo il principio della minima sorpresa e che siano autonomi. Idealmente, ogni sotto-pacchetto dei tuoi programmi di utilità, o almeno l'intero pacchetto di programmi di utilità, deve essere esportabile in un pacchetto per un facile riutilizzo.

Come al solito, impara dai giganti qui:

Sì, molti di questi hanno un'enfasi su raccolte e strutture di dati, ma non dirmi che non è dove o cosa di solito è probabile che implementi la maggior parte dei tuoi programmi di utilità, direttamente o indirettamente.


+1 perIf deals with String objects and manipulation of string constructs, call it Strings, not StringUtils
Tulains Córdova,

+1 Per la progettazione API in .Net, leggi il libro degli architetti di .Net. Linee guida per la progettazione di framework: convenzioni, modi di dire e schemi per librerie .NET riutilizzabili
MarkJ

Perché dovresti chiamarlo String invece di StringUtil (s)? Le stringhe per me sembrano un oggetto da collezione.
bdrx,

@bdrx: per me un oggetto di raccolta sarebbe o StringsCollectionTypeHerese vuoi un'implementazione concreta. O un nome più specifico se queste stringhe hanno un significato specifico nel contesto della tua app. Per questo caso particolare, Guava usa Stringsper i suoi aiutanti legati alle stringhe, al contrario di StringUtilsCommons Lang. Lo trovo perfettamente accettabile, significa solo per me che le classi gestiscono l' Stringoggetto o è una classe di uso generale per gestire le stringhe.
Hayylem,

@bdrx: In generale NameUtils, mi infastidiscono senza fine perché se si trova sotto un nome di pacchetto chiaramente etichettato, saprei già che è una classe di utilità (e in caso contrario, lo scoprirò rapidamente guardando l'API). E 'come fastidioso per me come la gente dichiarando roba come SomethingInterface, ISomethingo SomethingImpl. Ero un po 'OK con tali manufatti quando scrivevo in C e non usavo gli IDE. Oggi generalmente non avrei bisogno di cose del genere.
Hayylem,

8

Le classi di utilità non sono coerenti e, in genere, sono progettate male perché una classe deve avere un unico motivo per cambiare (principio di responsabilità singola).

Eppure, ho visto "util" classi nella stessa API Java, come: Math, Collectionse Arrays.

Si tratta, in effetti, di classi di utilità, ma tutti i loro metodi sono correlati a un singolo tema, uno ha operazioni matematiche, uno ha metodi per manipolare le raccolte e l'altro per manipolare le matrici.

Prova a non avere metodi totalmente non correlati in una classe di utilità. In tal caso, è probabile che tu possa anche metterli altrove dove realmente appartengono.

Se deve avere util classi poi cercare loro di essere separati da tema come Java di Math, Collectionse Arrays. Almeno, mostra qualche intenzione di design anche quando sono solo spazi dei nomi.

Io per primo, evito sempre le classi di utilità e non ho mai avuto la necessità di crearne una.


Grazie per la risposta. Quindi, anche se le classi util sono private del pacchetto, hanno ancora un odore di design?

non tutto appartiene a una classe però. se sei bloccato con un linguaggio che insiste nel farlo, allora accadranno le lezioni.
jk.

1
Bene, le classi di utilità non hanno un principio di progettazione per farti sapere quanti metodi sono troppi, dal momento che non c'è coesione per cominciare. Come hai potuto sapere come smettere? Dopo 30 metodi? 40? 100? I principi OOP, d'altra parte, ti danno un'idea di quando un metodo non appartiene a una particolare classe e quando la classe sta facendo troppo. Questo si chiama coesione. Ma come ti ho detto nella mia risposta, le stesse persone che hanno progettato Java hanno usato le classi di utilità. Non puoi farlo anche tu. Non creo classi di utilità e non sono stato in una situazione in cui qualcosa non appartiene a una classe normale.
Tulains Córdova,

1
Le classi di utilità di solito non sono classi ma sono solo spazi dei nomi contenenti un mucchio di funzioni, di solito pure. Le funzioni pure sono il più coese possibile.
jk.

1
@jk intendevo Utils ... A proposito, escono con nomi come Matho Arraysmostrano almeno qualche intenzione di design.
Tulains Córdova,

3

È perfettamente accettabile avere classi util , anche se preferisco il termine ClassNameHelper. Il BCL .NET contiene anche classi di supporto. La cosa più importante da ricordare è documentare accuratamente lo scopo delle classi, nonché ogni singolo metodo di supporto, e renderlo un codice gestibile di alta qualità.

E non lasciarti trasportare dalle lezioni di aiuto.


0

Uso un approccio a due livelli. Una classe "Globals" in un pacchetto "util" (cartella). Perché qualcosa vada nella classe "Globals" o nel pacchetto "util", deve essere:

  1. Semplice,
  2. Immutabile,
  3. Non dipende da altro nella tua applicazione

Esempi che superano questi test:

  • Formati di data e decimali a livello di sistema
  • Dati globali immutabili, come EARLIEST_YEAR (che supporta il sistema) o IS_TEST_MODE o alcuni valori veramente globali e immutabili (mentre il sistema è in esecuzione).
  • Metodi di supporto molto piccoli che aggirano le lacune nella tua lingua, framework o toolkit

Ecco un esempio di un metodo di supporto molto piccolo, completamente indipendente dal resto dell'applicazione:

public static String ordinal(final int i) {
    return (i == 1) ? "1st" :
           (i == 2) ? "2nd" :
           (i == 3) ? "3rd" :
           new StringBuilder().append(i).append("th").toString();
}

Guardando questo posso vedere un bug che 21 dovrebbe essere "21", 22 dovrebbe essere "22", ecc. Ma non è questo il punto.

Se uno di questi metodi di supporto cresce o diventa complicato, dovrebbe essere spostato nella propria classe nel pacchetto util. Se due o più classi helper nel pacchetto util sono correlate tra loro, dovrebbero essere spostate nel loro pacchetto. Se un metodo costante o di supporto risulta correlato a una parte specifica dell'applicazione, dovrebbe essere spostato lì.

Dovresti essere in grado di giustificare il motivo per cui non esiste un posto migliore per tutto ciò che ti attacca in una classe Globals o in un pacchetto util e dovresti pulirlo periodicamente usando i test di cui sopra. Altrimenti, stai solo creando un pasticcio.

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.