Les classes «Util» sont-elles préoccupantes? [fermé]

14

Je crée parfois des classes «Util» qui servent principalement à contenir des méthodes et des valeurs qui ne semblent pas vraiment appartenir ailleurs. Mais chaque fois que je crée une de ces classes, je pense "euh-oh, je vais le regretter plus tard ...", parce que j'ai lu quelque part que c'était mauvais.

Mais d'un autre côté, il semble y avoir deux cas convaincants (du moins pour moi) pour eux:

  1. secrets d'implémentation utilisés dans plusieurs classes d'un package
  2. fournissant des fonctionnalités utiles pour augmenter une classe, sans encombrer son interface

Suis-je sur le chemin de la destruction? Ce que tu dis !! Dois-je refactoriser?


la source
11
Arrêtez d'utiliser Utildans les noms de vos classes. Problème résolu.
yannis
3
@MattFenwick Yannis a un bon point. Nommer une classe SomethingUtilest un peu paresseux et obscurcit simplement le véritable objectif de la classe - même avec les classes nommées SomethingManagerou SomethingService. Si cette classe a une seule responsabilité, il devrait être facile de lui donner un nom significatif. Sinon, c'est le vrai problème à résoudre ...
MattDavey
1
@MDavey bon point, c'était probablement un mauvais choix de mot à utiliser Util, bien que je ne m'attendais évidemment pas à ce que cela soit résolu et que le reste de la question soit ignoré ...
1
Si vous créez plusieurs classes * Util séparées par le type d'objet qu'elles manipulent, alors je pense que ça va. Je ne vois aucun problème à avoir un StringUtil, ListUtil, etc. À moins que vous ne soyez en C #, vous devriez utiliser des méthodes d'extension.
marco-fiset
4
J'ai souvent des ensembles de fonctions qui sont utilisées à des fins connexes. Ils ne correspondent pas vraiment bien à une classe. Je n'ai pas besoin d'une classe ImageResizer, j'ai juste besoin d'une fonction ResizeImage. Je vais donc mettre les fonctions connexes dans une classe statique comme ImageTools. Pour les fonctions qui ne font encore partie d'aucun groupe, j'ai une classe statique Util pour les contenir, mais une fois que j'en aurai quelques-unes suffisamment liées les unes aux autres pour tenir dans une classe, je les déplacerai. Je ne vois pas de meilleure façon OO de gérer cela.
Philip

Réponses:

26

Le design OO moderne accepte que tout n'est pas un objet. Certaines choses sont des comportements ou des formules, et certaines n'ont pas d'état. Il est bon de modéliser ces choses en tant que fonctions pures pour bénéficier de cette conception.

Java et C # (et autres) vous obligent à créer une classe util et à parcourir ce cerceau pour le faire. Ennuyeux, mais pas la fin du monde; et pas vraiment gênant du point de vue du design.

Telastyn
la source
Ouais, c'est en quelque sorte ce que je pensais. Merci pour la réponse!
D'accord, même s'il convient de mentionner que .NET propose désormais des méthodes d'extension et des classes partielles comme alternative à cette approche. Si vous prenez cet élément à son extrême, vous vous retrouvez avec des mixins Ruby - un langage qui n'a pas besoin de classes utilitaires.
neontapir
2
@neontapir - Sauf que l'implémentation des méthodes d'extension vous oblige à faire une classe util avec les méthodes d'extension ...
Telastyn
Vrai. Il y a deux endroits où les classes Util gênent, l'une est dans la classe elle-même et l'autre est le site d'appel. En donnant l'impression que les méthodes existent sur l'objet amélioré, les méthodes d'extension résolvent le problème du site d'appel. Je suis d'accord, il y a toujours le problème de l'existence de la classe Util.
neontapir
16

Ne jamais dire jamais"

Je ne pense pas que ce soit nécessairement mauvais, c'est seulement mauvais si vous le faites mal et en abusez.

Nous avons tous besoin d'outils et d'utilitaires

Pour commencer, nous utilisons tous des bibliothèques qui sont parfois considérées comme presque omniprésentes et incontournables. Par exemple, dans le monde Java, Google Guava ou certains d' Apache Commons ( Apache Commons Lang , Apache Commons Collections , etc ...).

Il y a donc clairement un besoin pour cela.

Évitez les mots durs, la duplication et l'introduction de bogues

Si vous pensez à ce sont à peu près juste un groupe très grand de ces Utilclasses que vous décrivez, sauf que quelqu'un est passé par beaucoup de mal pour les obtenir (relativement) à droite, et ils ont été le temps - testé et fortement balled les yeux par d' autres.

Donc, je dirais que la première règle générale lorsque vous ressentez la démangeaison d'écrire une Utilclasse est de vérifier que la Utilclasse n'existe pas déjà.

Le seul contre-argument que j'ai vu pour cela est quand vous voulez limiter vos dépendances parce que:

  • vous souhaitez limiter l'empreinte mémoire de vos dépendances,
  • ou vous voulez contrôler étroitement ce que les développeurs sont autorisés à utiliser (cela se produit dans de grandes équipes obsessionnelles, ou lorsqu'un framework particulier est connu pour avoir la classe super-merdique étrange à éviter absolument quelque part).

Mais ces deux éléments peuvent être résolus en reconditionnant la bibliothèque à l' aide de ProGuard ou un équivalent, ou en le démontant vous-même (pour les utilisateurs de Maven , le plug-in maven - shadow offre certains modèles de filtrage pour l'intégrer dans votre build).

Donc, s'il se trouve dans une bibliothèque et correspond à votre cas d'utilisation, et qu'aucun benchmark ne vous dit le contraire, utilisez-le. Si cela diffère un peu de ce que vous, étendez-le (si possible) ou étendez-le, ou en dernier recours réécrivez-le.

Conventions de nommage

Cependant, jusqu'à présent dans cette réponse, je les ai appelés Utilcomme vous. Ne les nommez pas ainsi.

Donnez-leur des noms significatifs. Prenez Google Guava comme (très, très) bon exemple de ce qu'il faut faire, et imaginez simplement que l' com.google.guavaespace de noms est en fait votre utilracine.

Appelez votre forfait util, au pire, mais pas les cours. S'il s'agit d' Stringobjets et de manipulation de constructions de chaînes, appelez Strings-le non StringUtils(désolé, Apache Commons Lang - je vous aime et vous utilise toujours!). S'il fait quelque chose de spécifique, choisissez un nom de classe spécifique (comme Splitterou Joiner).

Test de l'unité

Si vous devez recourir à l'écriture de ces utilitaires, assurez-vous de les tester à l'unité. La bonne chose à propos des utilitaires est qu'ils sont généralement des composants plutôt autonomes, qui acceptent des entrées spécifiques et renvoient des sorties spécifiques. Voilà le concept. Il n'y a donc aucune excuse pour ne pas les tester à l'unité.

De plus, les tests unitaires vous permettront de définir et de documenter le contrat de leur API. Si les tests échouent, soit vous avez changé quelque chose dans le mauvais sens, soit cela signifie que vous essayez de changer le contrat de votre API (ou que vos tests originaux étaient de la merde - apprenez-en et ne recommencez pas) .

Conception d'API

La décision de conception que vous prendrez pour ces API vous suivra peut-être longtemps. Donc, tout en ne passant pas des heures à écrire un Splitter-clone, faites attention à la façon dont vous abordez le problème.

Posez-vous quelques questions:

  • Votre méthode d'utilité justifie-t-elle une classe à elle seule, ou une méthode statique est-elle suffisamment bonne, si elle a un sens pour qu'elle fasse partie d'un groupe de méthodes similaires utiles?
  • Avez-vous besoin de méthodes d'usine pour créer des objets et rendre vos API plus lisibles?
  • En parlant de lisibilité, avez-vous besoin d'une API Fluent , de constructeurs , etc ...?

Vous voulez que ces utilitaires couvrent un large éventail de cas d'utilisation, qu'ils soient robustes, stables, bien documentés, selon le principe de la moindre surprise, et qu'ils soient autonomes. Idéalement, chaque sous-ensemble de vos utilitaires, ou votre ensemble complet d'utilisation au moins, doit être exportable en un ensemble pour une réutilisation facile.

Comme d'habitude, apprenez des géants ici:

Oui, beaucoup d'entre eux mettent l'accent sur les collections et les structures de données, mais ne me dites pas que ce n'est pas où ni pour quoi vous êtes généralement susceptible d'implémenter la plupart de vos utilitaires, directement ou indirectement.

haylem
la source
+1 pourIf deals with String objects and manipulation of string constructs, call it Strings, not StringUtils
Tulains Córdova
+1 Pour la conception d'API dans .Net, lisez le livre des architectes de .Net. Framework Design Guidelines: Conventions, Idioms, and Patterns for Reusable .NET Libraries
MarkJ
Pourquoi l'appeleriez-vous Strings au lieu de StringUtil (s)? Les cordes me ressemblent à un objet de collection.
bdrx
@bdrx: pour moi, un objet de collection serait soit StringsCollectionTypeHeresi vous voulez une implémentation concrète. Ou un nom plus spécifique si ces chaînes ont une signification spécifique dans le contexte de votre application. Pour ce cas particulier, Guava utilise Stringspour ses aides liées aux chaînes, par opposition à StringUtilsCommons Lang. Je le trouve parfaitement acceptable, cela signifie simplement pour moi que la classe gère l' Stringobjet ou est une classe à usage général pour gérer les chaînes.
haylem
@bdrx: En général, les NameUtilschoses me bousculent sans fin parce que si elle se trouve sous un nom de package clairement étiqueté, je saurais déjà que c'est une classe utilitaire (et sinon, je le découvrirais rapidement en regardant l'API). Il est aussi gênant pour moi que les gens déclarant des choses comme SomethingInterface, ISomethingou SomethingImpl. J'étais en quelque sorte OK avec de tels artefacts lors du codage en C et sans utiliser d'IDE. Aujourd'hui, je n'aurais généralement pas besoin de telles choses.
haylem
8

Les classes util ne sont pas cohérentes et sont généralement de mauvaise conception car une classe doit avoir une seule raison de changer (principe de responsabilité unique).

Et pourtant, je l' ai vu « util » classes dans la très Java API, comme: Math, Collectionset Arrays.

Ce sont, en fait, des classes utilitaires mais toutes leurs méthodes sont liées à un seul thème, l'une a des opérations mathématiques, une a des méthodes pour manipuler les collections et l'autre pour manipuler les tableaux.

Essayez de ne pas avoir de méthodes totalement indépendantes dans une classe utilitaire. Si tel est le cas, il y a de fortes chances que vous puissiez aussi les placer ailleurs où ils appartiennent vraiment.

Si doit avoir util classes , puis les essayer d'être séparés par thème comme de Java Math, Collectionset Arrays. Cela, au moins, montre une certaine intention de conception même quand ce ne sont que des espaces de noms.

Pour ma part, évitez toujours les classes utilitaires et n'avez jamais eu besoin d'en créer une.

Tulains Córdova
la source
Merci pour la réponse. Donc, même si les classes util sont privées du package, elles sont toujours une odeur de conception?
Mais tout n'appartient pas à une classe. si vous êtes coincé avec un langage qui insiste sur le fait, alors les classes utils auront lieu.
jk.
1
Eh bien, les classes d'utilité n'ont pas de principe de conception pour vous faire savoir combien de méthodes c'est trop, car il n'y a pas de cohésion pour commencer. Comment pourriez-vous savoir comment arrêter? Après 30 méthodes? 40? 100? Les principes de POO, d'autre part, vous donnent une idée de quand une méthode n'appartient pas à une classe particulière, et quand la classe en fait trop. C'est ce qu'on appelle la cohésion. Mais comme je vous l'ai dit dans ma réponse, les personnes mêmes qui ont conçu Java ont utilisé des classes utilitaires. Tu ne peux pas le faire aussi. Je ne crée pas de classes utilitaires et je n'ai pas été dans une situation où quelque chose n'appartient pas à une classe normale.
Tulains Córdova
1
Les classes Utils ne sont généralement pas des classes, ce sont juste des espaces de noms contenant un tas de fonctions, généralement pures. Les fonctions pures sont aussi cohérentes que possible.
jk.
1
@jk je voulais dire Utils ... Au fait, trouver un nom comme Mathou Arraysmontre au moins une intention de conception.
Tulains Córdova
3

Il est parfaitement acceptable d'avoir util cours, bien que je préfère le terme ClassNameHelper. Le .NET BCL contient même des classes d'assistance. La chose la plus importante à retenir est de bien documenter le but des classes, ainsi que chaque méthode d'aide individuelle, et de lui donner un code maintenable de haute qualité.

Et ne vous laissez pas emporter par les cours d'aide.

David Anderson
la source
0

J'utilise une approche à deux niveaux. Une classe "Globals" dans un package "util" (dossier). Pour que quelque chose entre dans une classe "Globals" ou un package "util", il doit être:

  1. Facile,
  2. Immuable,
  3. Ne dépendant de rien d'autre dans votre application

Exemples qui réussissent ces tests:

  • Formats de date et décimaux à l'échelle du système
  • Données globales immuables, comme EARLIEST_YEAR (que le système prend en charge) ou IS_TEST_MODE ou une telle valeur vraiment globale et immuable (pendant que le système fonctionne).
  • Très petites méthodes d'assistance qui permettent de contourner les lacunes de votre langage, de votre framework ou de votre boîte à outils

Voici un exemple d'une très petite méthode d'assistance, complètement indépendante du reste de l'application:

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

En regardant cela, je peux voir un bug qui 21 devrait être "21e", 22 devrait être "22e", etc., mais ce n'est pas la question.

Si l'une de ces méthodes d'assistance se développe ou se complique, elle doit être déplacée dans sa propre classe dans le package util. Si deux ou plusieurs classes d'assistance dans le package util sont liées entre elles, elles doivent être déplacées dans leur propre package. Si une méthode constante ou auxiliaire s'avère être liée à une partie spécifique de votre application, elle doit y être déplacée.

Vous devriez être en mesure de justifier pourquoi il n'y a pas de meilleur endroit pour tout ce que vous collez dans une classe Globals ou un package d'utilisation et vous devez le nettoyer régulièrement en utilisant les tests ci-dessus. Sinon, vous créez juste un gâchis.

GlenPeterson
la source