2010-10-16 10 views
25

Estoy pensando en pedirle a mi equipo, con niveles de habilidades mixtas, que utilicen Google Guava. Antes de Guava, hubiera utilizado las Colecciones Apache (o su versión genérica).¿Es Google Guava "más difícil" de usar que Apache Collections?

Guava, a diferencia de Apache Collections, parece ser más fuerte en algunos aspectos, pero quizás menos fácil de usar para los programadores menos experimentados. Aquí hay un área donde creo que podría ejemplificar eso.

El código que he heredado contiene una gran cantidad de bucle más listas de lo que son mapas en esencia de los valores heterogéneos, sondeando a los valores, haciendo cheques nulos, y luego hacer algo trivial:

boolean foo(final List<MapLike> stuff, final String target) { 
    final String upperCaseTarget = target.toUpperCase(0; 

    for(MapLike m : stuff) { 
    final Maplike n = (MapLike) m.get("hard coded string"); 
    if(n != null) { 
     final String s = n.get("another hard code string"); 
     if(s != null && s.toUpperCase().equals(upperCaseTarget)) { 
      return true ; 
     } 
    } 
    return false ; 
} 

Mi inicial pensamiento era utilizar Apache Colecciones Transformers:

boolean foo(final List<MapLike> stuff, final String target) { 
    Collection< String> sa = (Collection<String>) CollectionUtils.collect(stuff, 
    TransformerUtils.chainedTransformer(new Transformer[] { 
     AppUtils.propertyTransformer("hard coded string"), 
     AppUtils.propertyTransformer("another hard coded string"), 
     AppUtils.upperCaseTransformer() 
     })); 

    return sa.contains(target.toUpperCase()) ;   

} 

El uso de guayaba, que podría ir en dos direcciones:

boolean foo(final List<MapLike> stuff, final String target) { 
    Collection<String> sa = Collections2.transform(stuff, 
     Functions.compose(AppUtils.upperCaseFunction(), 
     Functions.compose(AppUtils.propertyFunction("another hard coded string"), 
          AppUtils.propertyFunction("hard coded string")))); 

    return sa.contains(target.toUpperCase()) ;  
    // or 
    // Iterables.contains(sa, target.toUpperCase()); 
    // which actually doesn't buy me much 

}

En comparación con Apache Colecciones, Functions.compose (g, f) invierte el orden "intuitiva": se aplican funciones de derecha a izquierda, en lugar de la derecha de izquierda a "obvio" de TransformerUtils. encadenadoTransformer.

Un problema más sutil es que, como guayaba devuelve una vista en vivo, llamando contains en la visualización en vivo es probable que se aplique la función (compuesta) varias veces, así que lo que realmente debe hacer es:

return ImmutableSet.copy(sa).contains(target.toUpperCase()) ; 

Pero podría tener nulos en mi conjunto transformado, por lo que no puedo hacer eso. Puedo volcarlo en java.util.Collection, por supuesto.

Pero eso no va a ser obvio para mi equipo (menos experimentado), y es probable que se pierda en el calor de la codificación, incluso después de que lo explique. Esperaba que quizás Iterables.contains() "hiciera lo correcto" y supiera alguna instancia de magia para distinguir un proxy de vista en vivo de una simple colección antigua, pero no es así. Eso hace que la guayaba sea más difícil de usar.

Tal vez escribo algo así como un método estático en mi clase de utilidad para manejar esto?

// List always uses linear search? So no value in copying? 
// or perhaps I should copy it into a set? 
boolean contains(final List list, final Object target) { 
    return list.contains(target) ; 
} 

// Set doesn't use linear search, so copy? 
boolean contains(final Set set, final Object target) { 
    //return ImmutableSet.copy(set).contains(target) ; 
    // whoops, I might have nulls 
    return Sets.newHashSet(set).contains(target) ; 
} 

o tal vez solo copiar conjuntos por encima de un determinado tamaño?

// Set doesn't use linear search, so copy? 
boolean contains(final Set set, final Object target) { 
    final Set search = set.size() > 16 : Sets.newHashSet(set) : set ; 
    return search.contains(target) ; 
} 

supongo que estoy pidiendo, "por qué no hay un 'más fácil' transform en guayaba", y supongo que la respuesta es, "muy bien, apenas siempre volcar lo que devuelve en una nueva colección, o escribe tu propia transformación que hace eso ".

Pero si tengo que hacer eso, ¿no podrían otros clientes de las bibliotecas de Guava? Quizás haya una mejor manera en la Guava, que yo no sepa.

+0

"y probablemente se pierda en el calor de la codificación, incluso después de que lo explique". - ¿no tienes inspecciones de código regulares? –

+4

Bueno, el equipo (s) que heredé no. Ahora lo he instituido. Pero preferiría usar el tiempo de revisión para abordar problemas más grandes. Además, si mi equipo tiene la idea de que "las nuevas formas son muy difíciles", no voy a aceptarlo. Y preferiría un equipo que quiera hacer las cosas de la manera correcta, que un equipo que lo hace de la manera correcta solo por temor a la amonestación. – tpdi

+4

orden de argumento compose(): tenía un nivel adecuado de angustia sobre qué camino tomar, así que investigué y encontré aproximadamente el mismo precedente en ambos sentidos. –

Respuesta

57

Yo diría que la guayaba definitivamente no es más difícil de usar que las colecciones de Apache. Yo diría que es mucho más fácil, en realidad.

Uno de los grandes puntos en la ventaja de Guava es que no expone tantos tipos nuevos de objetos ... le gusta mantener la mayoría de los tipos de implementación reales que utiliza oculta detrás de los métodos de fábrica estáticos que solo exponen el interfaz.Tome los diversos Predicate s, por ejemplo. En Apache Collections, tiene clases de implementación pública de nivel superior como:

NullPredicate 
NotNullPredicate 
NotPredicate 
AllPredicate 
AndPredicate 
AnyPredicate 
OrPredicate 

Más una tonelada más.

En guayaba, éstos se empaquetan cuidadosamente en una sola clase de nivel superior, Predicates:

Predicates.isNull() 
Predicates.notNull() 
Predicates.not(...) 
Predicates.and(...) 
Predicates.or(...) 

Ninguno de ellos exponen su clase de implementación, ya que no es necesario saberlo! Mientras que Apache Collections tiene un equivalente PredicateUtils, el hecho de que expone los tipos de su Predicate s hace que sea más difícil de usar. Tal como lo veo, Apache Collections es simplemente un completo desorden de clases visibles innecesarias y partes no muy útiles que agregan desorden y hacen que sea más difícil acceder y usar las partes útiles. La diferencia es clara cuando observa el número de clases e interfaces que exponen las dos bibliotecas:

  • Apache Collections expone 309 tipos.
  • Guava, incluidos todos sus paquetes (no solo colecciones) expone solo 191 tipos.

Añadir a que la forma de guayaba es mucho más cuidado única para incluir los servicios públicos y las clases verdaderamente útiles, su rigurosa adhesión a los contratos de las interfaces que implementa, etc., y creo que es una calidad muy superior, más fácil usar la biblioteca.

Para hacer frente a algunos de sus puntos específicos:

De hecho, creo que el orden de guayaba eligió para Functions.compose es más intuitiva (aunque creo que eso es un argumento subjetiva para empezar). Tenga en cuenta que en su ejemplo de composición con Guava, el orden en que se aplicarán las funciones se lee desde el final de la declaración hacia el lugar donde se asigna el resultado final. Otro problema con su ejemplo es que, para empezar, no es seguro para tipos, ya que el ejemplo original implica convertir el resultado del método get a otro tipo. Una ventaja de compose de Guava en el conjunto de Transformer en el ejemplo de Apache Commons es que compose puede hacer una composición de funciones de tipo seguro, lo que garantiza (en tiempo de compilación) que la serie de funciones que está aplicando funcionará correctamente. La versión de Apache es completamente insegura en este sentido.

Vistas son superiores a las copias:

En segundo lugar, sobre la vista en vivo "problema" de Collections2.transform. Para ser franco, estás completamente equivocado en ese punto. ¡El uso de una vista en vivo en lugar de copiar todos los elementos del original Collection en un nuevo Collection es en realidad mucho más eficiente! Esto es lo que va a pasar cuando se llama Collections2.transform y luego llamar contains en el Collection vuelve:

  • Una vista Collection envolver el original se crea ... el original y la Function están ambos simplemente asigna a los campos en el mismo.
  • El iterador de Collection se ha recuperado.
  • Para cada elemento en el Iterator, se aplicará el Function, obteniendo el valor transformado de ese elemento.
  • Cuando el primer elemento para el que se ha encontrado el valor transformado equals se encuentra el objeto que está buscando, contains devolverá. ¡Solo itera (y aplica el Function) hasta que se encuentre una coincidencia! ¡El Function se aplica como máximo una vez por elemento!

Aquí es lo que hace la versión de Apache Colecciones:

  • Crea un nuevo ArrayList para almacenar los valores transformados.
  • Obtiene el iterador original Collection.
  • Para cada elemento en el iterador original Collection, aplica la función y agrega el resultado al nuevo Collection. Esto se hace para cada elemento del Collection original, incluso si el resultado de aplicar el Transformer al primer elemento hubiera coincidido con el objeto que estamos buscando.
  • Luego, contains iterarán sobre cada elemento en el nuevo Collection buscando el resultado.

Aquí está el mejor y el peor de los casos para un Collection de tamaño N utilizando ambas bibliotecas. El mejor caso es cuando el valor transformado del primer elemento equals es el objeto que está buscando con contains y el peor caso es cuando el valor que está buscando con contains no existe en la colección transformada.

  • Guava:
    • Mejor caso: itera 1 de elementos, se aplica Function 1 vez, almacena 0 elementos adicionales.
    • Peor caso: itera N elementos, aplica Function N veces, almacena 0 elementos adicionales.
  • Apache:
    • mejor de los casos: itera N + 1 elementos, se aplica Transformer N veces, tiendas de N elementos adicionales (la colección transformado).
    • Peor caso: itera elementos 2N, aplica Transformer N veces, almacena N elementos adicionales (la colección transformada).

espero que sea obvio a partir de lo anterior que, en general, una opinión es una muy buena cosa! Además, es muy fácil copiar una vista en una colección sin vista en cualquier momento que sea útil, y tendrá el mismo rendimiento que la versión de Apache para empezar.Sin embargo, sería decididamente no útil en cualquiera de los ejemplos que ha proporcionado.

Como nota menor final, Iterables.contains existe simplemente para permitirle comprobar si un Iterable que usted sabe que es Collection contiene un valor. Si el Iterable que le da realmente es un Collection, simplemente llama a en ese Collection para permitir un mejor rendimiento posible (si es un Set, por ejemplo).

+0

Para el orden de composición, creo que hay una respuesta más objetiva, como http://en.wikipedia.org/wiki/Function_composition. Creo que debe leerse como "aplicar g() en el resultado de f(). –

+0

@Sylvain: Sí, eso tiene sentido. La documentación describe la composición como" g (f (a)) "también. – ColinD

+0

I Me gustaría saber cómo obtuviste esta información sobre cómo funciona la composición de guayaba (y apache si se transforma). Por cierto, una respuesta increíble. Añadiría más votos positivos si puedo. –

19

Como uno de los desarrolladores de Guava, obviamente soy parcial, pero aquí hay algunos comentarios.

La facilidad de uso fue uno de los principales objetivos detrás del diseño de Guava. Siempre hay margen de mejora, y estamos ansiosos por escuchar cualquier sugerencia o inquietud. Usualmente hay un razonamiento detrás de las decisiones de diseño, aunque todos probablemente puedan encontrar cosas con las que personalmente no estén de acuerdo.

En términos de las vistas en vivo, ColinD describió las ventajas de rendimiento que existen para algunos casos de uso. Además, a veces desea que los cambios en la vista alteren la colección original y viceversa.

Ahora, hay casos en los que copiar la colección proporciona un mejor rendimiento, pero solo se necesita una sola línea de código para hacerlo. Si bien Guava podría incluir métodos transformAndCopy(), omitimos los métodos de una línea, excepto en casos extremadamente comunes como Maps.newHashMap(). Cuantos más métodos estén presentes, más difícil es encontrar el método que necesita.

Cuestiones relacionadas