¿Es preocupante tener clases de 'Util'? [cerrado]

14

A veces creo clases de 'Util' que sirven principalmente para contener métodos y valores que realmente no parecen pertenecer a otra parte. Pero cada vez que creo una de estas clases, pienso "uh-oh, me arrepentiré más tarde ...", porque leí en alguna parte que es malo.

Pero, por otro lado, parece haber dos casos convincentes (al menos para mí) para ellos:

  1. secretos de implementación que se usan en varias clases dentro de un paquete
  2. Proporciona funcionalidades útiles para aumentar una clase, sin saturar su interfaz.

¿Estoy en camino a la destrucción? Que dices !! ¿Debo refactorizar?


fuente
11
Deja de usar Utilen los nombres de tus clases. Problema resuelto.
Yannis
3
@MattFenwick Yannis tiene un buen punto. Nombrar una clase SomethingUtiles un poco vago y solo oscurece el verdadero propósito de la clase, lo mismo con las clases nombradas SomethingManagero SomethingService. Si esa clase tiene una sola responsabilidad, debería ser fácil darle un nombre significativo. Si no, ese es el verdadero problema con el que lidiar ...
MattDavey
1
@MDavey punto bueno, que probablemente era una palabra mala elección para su uso Util, aunque, obviamente, no esperaba que eso conseguiría fijado en y el resto de la pregunta ignorado ...
1
Si creas múltiples clases * Util separadas por el tipo de objeto que manipulan, entonces creo que está bien. No veo ningún problema con tener un StringUtil, ListUtil, etc. A menos que esté en C #, entonces debe usar métodos de extensión.
marco-fiset
44
A menudo tengo conjuntos de funciones que se utilizan para fines relacionados. Realmente no se asignan bien a una clase. No necesito una clase ImageResizer, solo necesito una función ResizeImage. Así que pondré funciones relacionadas en una clase estática como ImageTools. Para las funciones que aún no están en ningún tipo de agrupación, tengo una clase estática Util para mantenerlas, pero una vez que tenga algunas que estén lo suficientemente relacionadas entre sí para caber en una clase, las moveré. No veo una mejor manera de manejar esto.
Philip

Respuestas:

26

El diseño moderno de OO acepta que no todo es un objeto. Algunas cosas son comportamientos, o fórmulas, y algunas de ellas no tienen estado. Es bueno modelar estas cosas como funciones puras para obtener el beneficio de ese diseño.

Java y C # (y otros) requieren que hagas una clase util y saltes ese aro para hacerlo. Molesto, pero no el fin del mundo; y no realmente problemático desde una perspectiva de diseño.

Telastyn
fuente
Sí, eso es lo que estaba pensando. ¡Gracias por la respuesta!
De acuerdo, aunque debe mencionarse que .NET ahora ofrece métodos de extensión y clases parciales como alternativa a este enfoque. Si lleva este elemento a su extremo, terminará con los mixins de Ruby, un lenguaje que tiene poca necesidad de clases de utilidad.
neontapir
2
@neontapir - Excepto que la implementación de métodos de extensión básicamente te obliga a hacer una clase util con los métodos de extensión ...
Telastyn
Cierto. Hay dos lugares donde las clases de Util obstaculizan, uno está en la clase misma y el otro es el sitio de llamadas. Al hacer que los métodos parezcan que existen en el objeto mejorado, los métodos de extensión abordan el problema del sitio de llamadas. Estoy de acuerdo, todavía existe el problema de la existencia de la clase Util.
neontapir
16

Nunca digas nunca"

No creo que sea necesariamente malo, solo es malo si lo haces mal y abusas de él.

Todos necesitamos herramientas y utilidades

Para empezar, todos usamos algunas bibliotecas que a veces se consideran casi ubicuas y imprescindibles. Por ejemplo, en el mundo Java, Google Guava o algunos de Apache Commons ( Apache Commons Lang , Apache Commons Collections , etc.).

Entonces claramente hay una necesidad de estos.

Evite las palabras difíciles, la duplicación y la introducción de errores

Si se piensa en ellas se encuentran prácticamente sólo un gran manojo de estas Utilclases que usted describe, excepto que alguien pasó por un gran esfuerzo para conseguir que la derecha (relativamente), y han sido tiempo - probados y fuertemente ojo en ovillo por otros.

Entonces, diría que la primera regla general cuando sientas ganas de escribir una Utilclase es verificar queUtil clase en realidad no exista.

El único contraargumento que he visto para eso es cuando quieres limitar tus dependencias porque:

  • desea limitar la huella de memoria de sus dependencias,
  • o desea controlar estrictamente lo que los desarrolladores pueden usar (sucede en equipos grandes obsesivos, o cuando un marco en particular es conocido por tener una clase súper asquerosa que se debe evitar en algún lugar).

Pero ambos pueden abordarse volviendo a empaquetar la biblioteca con ProGuard o un equivalente, o desarmándola usted mismo (para los usuarios de Maven , el complemento maven-shade-plugin ofrece algunos patrones de filtrado para integrar esto como parte de su compilación).

Entonces, si está en una biblioteca y coincide con su caso de uso, y ningún punto de referencia le dice lo contrario, úselo. Si varía un poco de lo que usted extiende, extiéndalo (si es posible) o extiéndalo, o en el último recurso, vuelva a escribirlo.

Convenciones de nombres

Sin embargo, hasta ahora en esta respuesta los llamé Util s como tú. No les digas eso.

Dales nombres significativos. Tome Google Guava como un (muy, muy) buen ejemplo de qué hacer, e imagine que el com.google.guavaespacio de nombres es realmente suutil raíz.

Llame a su paquete util, en el peor de los casos, pero no a las clases. Si se trata de Stringobjetos y manipulación de construcciones de cadenas, llámelo Strings, no StringUtils(lo siento, Apache Commons Lang , ¡todavía me gusta y lo uso!). Si hace algo específico, elija un nombre de clase específico (como SplitteroJoiner ).

Prueba de unidad

Si tiene que recurrir a escribir estas utilidades, asegúrese de realizar una prueba unitaria. Lo bueno de las utilidades es que generalmente son componentes bastante independientes, que toman entradas específicas y devuelven salidas específicas. Ese es el concepto. Así que no hay excusa para no probarlos en la unidad.

Además, las pruebas unitarias le permitirán definir y documentar el contrato de su API. Si las pruebas se rompen, o cambiaste algo de la manera incorrecta, o significa que estás tratando de cambiar el contrato de tu API (o que tus pruebas originales fueron basura, aprende de ello y no lo vuelvas a hacer) .

Diseño API

Las decisiones de diseño que tomará para estas API lo seguirán durante mucho tiempo, posiblemente. Entonces, si bien no pasa horas escribiendo un Splitterclon, tenga cuidado con la forma en que aborda el problema.

Hágase algunas preguntas:

  • ¿Su método de utilidad garantiza una clase por sí solo, o es un método estático lo suficientemente bueno, si tiene sentido que forme parte de un grupo de métodos igualmente útiles?
  • ¿Necesita métodos de fábrica? para crear objetos y hacer que sus API sean más legibles?
  • Hablando de legibilidad, ¿necesitas una API fluida , constructores , etc.?

Desea que estas utilidades cubran una gran variedad de casos de uso, que sean robustas, estables, bien documentadas, que sigan el principio de menor sorpresa y que sean independientes. Idealmente, cada subpaquete de sus utilidades, o al menos todo su paquete de utilidades, se podrá exportar a un paquete para una fácil reutilización.

Como de costumbre, aprende de los gigantes aquí:

Sí, muchos de estos hacen hincapié en las colecciones y las estructuras de datos, pero no me digas que no es dónde o para qué es probable que implementes la mayoría de tus utilidades, directa o indirectamente.

haylem
fuente
+1 paraIf deals with String objects and manipulation of string constructs, call it Strings, not StringUtils
Tulains Córdova
+1 Para el diseño de API en .Net, lea el libro de los arquitectos de .Net. Pautas de diseño del marco: convenciones, expresiones
idiomáticas
¿Por qué lo llamarías Strings en lugar de StringUtil (s)? Las cuerdas para mí suenan como un objeto de colección.
bdrx
@bdrx: para mí, un objeto de colección sería cualquiera StringsCollectionTypeHeresi desea una implementación concreta. O un nombre más específico si estas cadenas tienen un significado específico en el contexto de su aplicación. Para este caso particular, Guava usa Stringspara sus ayudantes relacionados con cuerdas, a diferencia de StringUtilsCommons Lang. Me parece perfectamente aceptable, solo significa para mí que las clases manejan Stringobjetos o es una clase de propósito general para administrar cadenas.
haylem
@bdrx: En general, las NameUtilscosas me molestan porque si se encuentra bajo un nombre de paquete claramente etiquetado, ya sabría que es una clase de utilidad (y si no, lo resolvería rápidamente al mirar la API). Es tan molesto para mí como la gente declarando cosas como SomethingInterface, ISomethingo SomethingImpl. Estaba bastante de acuerdo con tales artefactos cuando codificaba en C y no usaba IDEs. Hoy, generalmente, no necesito esas cosas.
haylem
8

Las clases de util no son cohesivas y, en general, tienen un diseño incorrecto porque una clase tiene que tener una sola razón para cambiar (Principio de responsabilidad única).

Sin embargo, he visto "util" clases en el API de Java, como: Math, Collectionsy Arrays.

Estas son, de hecho, clases de utilidad, pero todos sus métodos están relacionados con un solo tema, uno tiene operaciones matemáticas, uno tiene métodos para manipular colecciones y el otro para manipular matrices.

Intente no tener métodos totalmente no relacionados en una clase de utilidad. Si ese es el caso, lo más probable es que también puedas ponerlos en otro lugar donde realmente pertenecen.

Si debe tener util clases luego probarlos a estar separados por el tema como Java Math, Collectionsy Arrays. Al menos, muestra cierta intención de diseño incluso cuando son solo espacios de nombres.

Por mi parte, siempre evito las clases de utilidad y nunca he tenido la necesidad de crear una.

Tulains Córdova
fuente
Gracias por la respuesta. Entonces, incluso si las clases util son privadas de paquete, ¿siguen siendo un olor de diseño?
Sin embargo, no todo pertenece a una clase. Si está atrapado con un lenguaje que insiste en que lo hace, entonces se producirán clases de utilidades.
jk.
1
Bueno, las clases de utilidad no tienen un principio de diseño que le permita saber cuántos métodos es demasiado, ya que no hay cohesión para empezar. ¿Cómo puedes saber cómo parar? ¿Después de 30 métodos? 40? 100? Los principios OOP, por otro lado, le dan una idea de cuándo un método no pertenece a una clase en particular y cuándo la clase está haciendo demasiado. Eso se llama cohesión. Pero como te dije en mi respuesta, las mismas personas que diseñaron Java usaban clases de utilidad. Tú tampoco puedes hacerlo. No creo clases de utilidad y no he estado en una situación en la que algo no pertenece a una clase normal.
Tulains Córdova
1
Las clases de utilidades generalmente no son clases, son solo espacios de nombres que contienen un montón de funciones, generalmente puras. Las funciones puras son tan coherentes como puedes conseguir.
jk.
1
@jk me refería a Utils ... Por cierto, aparecer con nombres como Matho Arraysmuestra al menos alguna intención de diseño.
Tulains Córdova
3

Es perfectamente aceptable tener clases de utilidad , aunque prefiero el término ClassNameHelper. El .NET BCL incluso tiene clases auxiliares. Lo más importante para recordar es documentar minuciosamente el propósito de las clases, así como cada método auxiliar individual, y hacer que sea un código mantenible de alta calidad.

Y no te dejes llevar por las clases de ayuda.

David Anderson
fuente
0

Yo uso un enfoque de dos niveles. Una clase "Globals" en un paquete (carpeta) "util". Para que algo entre en una clase "Globals" o paquete "util", debe ser:

  1. Sencillo,
  2. Inmutable
  3. No depende de nada más en su aplicación

Ejemplos que pasan estas pruebas:

  • Fecha de todo el sistema y formatos decimales
  • Datos globales inmutables, como EARLIEST_YEAR (que admite el sistema) o IS_TEST_MODE o algún valor verdaderamente global e inmutable (mientras el sistema se está ejecutando).
  • Métodos auxiliares muy pequeños que evitan lagunas en su idioma, marco o kit de herramientas.

Aquí hay un ejemplo de un método auxiliar muy pequeño, completamente independiente del resto de la aplicación:

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

Mirando esto, puedo ver un error que 21 debería ser "21", 22 debería ser "22", etc. pero eso no viene al caso.

Si uno de esos métodos auxiliares crece o se complica, se debe mover a su propia clase en el paquete util. Si dos o más clases auxiliares en el paquete util están relacionadas entre sí, se deben trasladar a su propio paquete. Si un método constante o auxiliar resulta estar relacionado con una parte específica de su aplicación, debe moverse allí.

Debería poder justificar por qué no hay un mejor lugar para todo lo que pega en una clase de Globals o paquete de utilidades y debe limpiarlo periódicamente utilizando las pruebas anteriores. De lo contrario, solo estás creando un desastre.

GlenPeterson
fuente