Artículos en Programación

Cómo hacer una aplicación que soporte precios con decimales sin errores

PostedEn Programación     Comentarios 14 comentarios
Sep
8

Hace algunos días comentaba, en un artículo muy básico, cuáles eran las operaciones matemáticas o fórmulas para sacar y extraer el IVA de los importes. Sin embargo, manipular decimales, cuando se trata de precios, requiere seguir una serie de pautas muy importantes si no queremos encontrarnos con situaciones como la de la imagen.

Y no me refiero a usar simplemente el tipo de datos apropiado, sino a como gestionar los precios y manipularlos durante todo el flujo de la aplicación si queremos que estos sean coherentes durante todo su ciclo de vida: alta de productos, compras, descuentos, impuestos, etc.

La visión del artículo es funcional: hay poco código, pocos tecnicismos y muchas recomendaciones de negocio, principalmente. Si quieres literatura técnica, puedes ir directamente al final, donde hay algunos buenos enlaces y un FAQ propio. Mi idea no es ser un experto en coma flotante, si no cómo hacer una aplicación donde se trabaja con precios de una manera robusta y a prueba de errores de cálculo.

1 Tipo de datos

Olvídate de float y long. Solamente con que hagas un

System.out.println(0.1F+0.2F) // 0.30000000447034836

verás que contiene un minúsculo margen de error en la parte decimal. Es posible que desaparezca si redondeas, pero puede romper realmente tu aplicación. Ojo, esto no es un bug de Java, viene determinado por la naturaleza del tipo de datos decimal cuando es transformado a binario. Así que esto también sucede en tu base de datos si usas float (o double):

CREATE TABLE prueba (chungo float, suelto double);
INSERT INTO prueba VALUES (0.1, 0.1);
INSERT INTO prueba VALUES (0.2, 0.2);
SELECT SUM(chungo), SUM(suelto) FROM prueba;
| 0.30000000447034836 | 0.30000000000000004 |

La explicación de porqué sucede esto la tienes abajo en el FAQ.

Solución:

  • Usa en tus clases de dominio BigDecimal, el cual no tiene este problema. Es tratado como un número entero que contiene simplemente una coma en cierta posición, por lo que cualquier operación decimal realizada es exacta. El único problema es que los cálculos son más lentos (las operaciones las realiza la clase BigDecimal dentro de la JVM) que con primitivos int, long, float y double (las operaciones son realizadas por la CPU). Pero eso es algo con lo que podemos -y debemos- vivir.
  • Usa en tu base de datos el tipo DECIMAL(M, D). Donde M es el tamaño del número (parte entera y decimal) y D es el tamaño de la parte decimal solo. Así un tipo decimal(15, 2) nos permitirá guardar hasta 9999999999999.99

En Java no existe un tipo de datos primitivo para BigDecimal, por lo que todas las operaciones matemáticas realizadas con este tipo se deben hacer mediante métodos, con cosas tan barrocas como esta:

System.out.println(new BigDecimal("0.2").add(new BigDecimal("0.1"))); // -> 0.3

En Groovy no es necesario, ya que todas las operaciones en las que se ve involucrado un número decimal (float, double o BigDecimal) son realizadas con BigDecimal automáticamente.

// Código Groovy, decimales son BigDecimal por defecto
println(0.1 + 0.2) // -> 0.3

2 Como guardar los precios de los productos antes de venderlos

Pese a que en nuestra aplicación siempre vamos a mostrar todos los importes con solo dos decimales (redondeando además), nuestro modelo de datos debe guardar el importe de los productos siempre sin impuestos (sin IVA en España) y con tres decimales. Y el importe con IVA debe ser siempre calculado al extraerlo de base de datos. La pregunta es ¿pero para qué necesito tres decimales, si solo voy a mostrar dos? Veamos los casos posibles:

Que el administrador introduzca el precio del producto sin IVA directamente.
En este caso solo permitimos que teclee dos decimales, aunque guardemos luego tres (es decir, el tercer decimal será siempre 0). Ejemplo: el administrador introduce “11,95€”, por lo que guardamos 11.950. Para mostrar el precio en la web, simplemente leemos el importe sin IVA de base de datos y calculamos el importe con IVA: “11,950€ + IVA 21% = 14,46€” (el cálculo sería 11.95 * 1.21 = 14.4595, que redondeando a dos decimales da 14.46)

Que el administrador introduzca el precio del producto con IVA incluido.
¿Y por qué querría hacer tal cosa? Pues simplemente para asegurarse de que el producto se vende al público con un precio que a él le parece apropiado, por ejemplo “108,99€ IVA incluido”, y desea que la obtención de la base imponible sea calculada por el sistema (o sea nosotros). En este caso, nuestra aplicación recibe el número “108,99″ y después le quita el IVA (para un IVA del 21% lo divide entre 1.21) y lo guarda en base de datos. Aquí es donde puede haber un problema: el sistema debría poder volver a aplicar el IVA al importe guardado en base de datos y obtener exactamente el mismo importe que el administrador había introducido. Y si guardamos el importe sin IVA con solo dos decimales, al volvérselo a añadir, el resultado puede ser distinto. Veamos un ejemplo (haciendo los cálculos con todos los decimales que disponemos, pero redondeando siempre a dos decimales al mostrarlos): el administrador desea que el importe de su producto sea de “108,99€ IVA incluido”.

Si usamos en base de datos dos decimales con tipo DECIMAL(15, 2), el resultado es incorrecto:

  • El administrador introduce “108,99€” IVA incluido.
  • Le quitamos el IVA: 108.99 / 1.21 = 90.07 y lo guardamos en base de datos con dos decimales.
  • Leemos de base de datos y le añadimos el IVA: 90.07 * 1.21 = 108.9847
  • Mostramos ”90,07€ + IVA 21% = 108,98€” -> INCORRECTO! no coincide con los 108.99 del importe inicial.

Pero si usamos en base de datos tres decimales con tipo DECIMAL(16, 3) es correcto:

  • El administrador introduce “108,99€” IVA incluido.
  • Le quitamos el IVA: 108.99 / 1.21 = 90.074 y lo guardamos en base de datos con tres decimales.
  • Leemos de base de datos y le añadimos el IVA: 90.074 * 1.21 = 108.98954
  • Mostramos ”90,07€ + IVA 21% = 108,99€” -> CORRECTO! si coincide con el importe inicial.

Resumen: para conservar un número que tiene N decimales al multiplicarlo y dividirlo por otro, debemos usar N+1 decimales. En nuestro caso, al ser precios en euros, nuestros números tienen dos decimales, por lo que si queremos quitar el IVA y volvérselo añadir, y obtener el mismo número, debemos usar tres decimales: DECIMAL(15, 3)

3 Como guardar los precios de las compras

Al comprar un producto se deben hacer dos cosas:

  1. Primero se realizan todos los cálculos y después se guardan en base de datos. Esto incluye el IVA (y descuentos aplicados), tanto el tipo (por ejemplo 21% para el IVA) como la cantidad. El hecho de guardar todos los importes parciales y tipos usados nos previene ante posibles cambios de IVA y nos evita hacer cálculos cada vez que necesitemos generar una factura o informe. Y también nos ayuda a hacer sumatorios en base de datos de estos parciales. Por ejemplo, si necesitamos saber el importe total de todo el IVA cobrado a todos los usuarios en sus compras.
  2. Todos los importes guardados tendrán solamente DOS decimales y serán los importes que han sido mostrados al usuario (y, por lo tanto, previamente redondeados). Esto debe ser así porque no importa que el cálculo del total a cobrar al usuario haya sido 85.431€, si al usuario le estamos mostrando “85,43€”, se le deben cobrar 85,43€ en la pasarela de pago y se debe almacenar en la base de datos 85.43. Si no se hiciera así y guardásemos 85.431, una suma de todas las compras del usuario podría dar un importe superior al acumular decimales en todas las compras. Si se almacena el número ya redondeado y con dos decimales, esto nunca sucede.

Siguiendo el ejemplo anterior: un usuario compra un libro por “90,07€” (que, recordemos, se guarda en base de datos con tres decimales como 90.074). En nuestra tabla de compras debemos guardar una foto de todos los importes en ese preciso momento con dos decimales ya redondeados:

 base imponible = 90.07    // El importe del producto con 3 decimales redondeado a 2 decimales
 tipo IVA       = 0.21     // Constante configurable
 importe final  = 108.99   // 90.074 * 1.21 = 108.98954 redondeado a 2 decimales
 IVA            = 18.92    // 108.99 - 90.07 no necesita redondear

Ahora podemos obtener las compras de un usuario y hacer sumas totales con dos decimales sin temor a perder exactitud, porque una vez cobrado el importe, ya no hace falta conservar más decimales: los precios se quedan congelados en la tabla para saber que se cobró, que IVA se aplicó (cantidad y tipo), etc. En cualquier momento podemos volver a generar una factura o extraer un informe con esta información, sin necesidad de hacer nuevos cálculos, pues ya han sido precalculados y cobrados, por lo que son inmutables.

Resumen

Siguiendo estas tres reglas protegeremos nuestra aplicación:

  1. Evitando datos incorrectos producidos por la inexactitud de los tipos en float y double gracias al uso de BigDecimal en nuestras clases de dominio y al tipo DECIMAL(M, D) en nuestra base de datos.
  2. Para que al extraer y después añadir el IVA al precio de un producto obtengamos después exactamente el mismo importe debemos guardar en base de datos los importes con tres decimales (regla de conservación de importes: para N decimales, guadar N+1)
  3. Para poder obtener una factura o informe con exactamente los mismos datos que han sido cobrados en su momento al usuario, no tenemos que usar tres decimales: debemos guardar todos los importes parciales y totales ya calculados, pero con dos decimales. Tambien hay que guardar el tipo de IVA utilizado (18, 21, etc).

Errores comunes

  • Utilizar 4 (¡o 5, 6 o 10!) decimales para guardar los importes. No por usar muchos decimales hace que los cálculos sean más exactos. Si necesitamos una precisión de 2 decimales, con 3 es suficiente. Por otro lado, las compras ya realizadas se deben guardar con 2 decimales, o un sumatorio de todas las compras puede dar un importe superior. La regla de oro es: en una compra, lo que se ha cobrado y lo que se ha mostrado al usuario, es lo que se guarda.
  • Utilizar enteros y guardar los importes en céntimos. Esto requiere dividir entre 100 al formatear todos los números antes de imprimirlos. Y que cuando se multiplican o dividen centimos, hay que desplazar la coma. Todas estas operaciones añaden complejidad a la aplicación y son fuentes de errores. Para hacer esto, mejor usar BigDecimal que nos evita este trabajo. Más info sobre trabajar con céntimos: http://floating-point-gui.de/formats/integer/
  • Hacer cosas raras para redondear como multiplicar entre 100, truncar decimales (casting a int) y dividir entre 100 de nuevo. No es necesario, los BigDecimal se redondean el método setScale(decimales). Por ejemplo, para redondear a 2 decimales se hace: new BigDecimal(“0.166″).setScale(2, RoundingMode.HALF_UP). Más info sobre los tipos de redondeo posibles: http://docs.oracle.com/javase/1.5.0/docs/api/java/math/RoundingMode.html
  • Crear números BigDecimal usando float o double. En vez de usar new BigDecimal(0.1), hay que usar new BigDecimal(“0.1″), no sea que suceda esto: http://stackoverflow.com/questions/9795364/java-bigdecimal-precision-problems.
  • Me comenta @jerolba en uno de los comentarios que para comparar dos BigDecimal hay que usar compareTo() en vez de equals(). El método equals() tiene en cuenta el nº de decimales, y por mucho que cueste creerlo, 1 no es igual a 1.00. El siguiente assert no pasa: assertTrue(new BigDecimal(“1″).equals(new BigDecimal(“1.00″))); Cuando queráis saber si dos BigDecimal son iguales es mejor usar el método compareTo y mirar si su valor es 0: assertEquals(0, new BigDecimal(“1″).compareTo(new BigDecimal(“1.00″)));

FAQ

¿Que es la precisión?

  • Es el número de bits que se necesitan para guardar un valor. Así, la precisión de int y float es de 32 bits, mientras que las de long y double es de 64 bits. En el caso de BigDecimal, no hay límite de precisión, y esta viene dada por el número de dígitos (parte entera+parte decimal) que tiene el valor guardado en base 10. Por ejemplo, el número 22.3 tiene precisión 3.

¿Que es la exactitud de una operación?

  • Un cálculo es exacto cuando se corresponde con el resultado real de la operación. Double y float no son exactos porque 0.1F+0.2F = 0.30000000447034836, pero BigDecimal si lo es: 0.1 + 0.2 = 0.3.

¿Cómo funciona un BigDecimal por dentro?

  • A grosso modo, no es mas que un array de ints (BigInteger), lo que permite tener precisión ilimitada (no confundir con infinita). Para las operaciones con decimales, simplemente guarda la posición de la coma y opera como si fueran operaciones enteras, y después desplaza la posición de la coma, si corresponde.

¿Por qué un float y un double no son exactos?

  • En realidad si son exactos, el problema es que los convertimos de/a base 10 pero internamente se almacenan en binario. Y todos los números binarios con decimales tienen correspondencia en base 10, pero no todos los números con decimales en base 10 tienen correspondencia en binario. Veamos por qué: todos conocemos como se almacena la parte entera de un número en base 10 en binario. Por ejemplo, el 13 en binario es 1101, lo que significa 1*2^3 + 1*2^2 + 0*2^1+ 1*2^0 = 8+4+0+1 = 13. En la parte decimal de un número en base 10, cada bit vale 1/2, 1/4, 1/8 (es decir, 2^-1, 2^-2, 2^-3), por lo que 0.11 en binario es 1/2+1/4 = 0.75. El problema viene cuando intentas guardar un número como 0.5 en binario, ya que no hay número exacto para ello: en su lugar, se guarda la aproximación más cercana, que es el número periódico 0.0.00110011… Y un número periódico en un ordenador tiene un límite (el de la precisión de su tipo), por lo que es redondeado al final, alternado el valor. Por eso al sumar 0.1 + 0.2 tiene ese pequeño desvío. Más información aquí: http://en.wikipedia.org/wiki/Binary_numeral_system#Fractions_in_binary

¿Qué significa que un numero esté en coma flotante?

  • A muy muy grosso modo (expertos, tened piedad, esto no es un artículo académico) consiste en almacenar un número con decimal (que se denomina mantisa) y un exponente al que elevamos ese número decimal. Esto nos permite guardar números muy grandes y muy pequeños. Por ejemplo, un número como 0.0000000000667 se guarda como 6.667^-11 (mantisa:6.667 y exponente:-11) y se representa como 6.667e-11 (conocido como notación científica). Más info: http://en.wikipedia.org/wiki/IEEE_754-2008

La clase RoundingMode tiene muchos tipos de redondeo, ¿cuál debería usar?

  • Para importes (y casi cualquier contexto normal) se usa HALF_UP y HALF_EVEN. El redondeo HALF_UP, también denominado como “standard rounding”, es el de “toda la vida” y consiste en redondear hacia el número más cercano. Así que si el decimal a truncar acaba de 0 a 4 se redondea hacia abajo, y si acaba de 5 a 9 se redondea hacia arriba. 2.134 -> 2.13, y  2.135 -> 2.14. Sin embargo, parece ser que estadísticamente, este redondeo no es óptimo. HALF_EVEN, también denominado “bankers’ rounding” o “gaussian rounding”, es el redondeo que aplican los bancos y consiste en redondear hacia el número par más cercano. Funciona igual que HALF_UP solo que para el caso de que el decimal acabe en 5, se redondea hacía el número par más próximo independientemente de si está arriba o abajo. Es decir 2.135 -> 2.14, y 2.125 -> 2.12. Los dos acaban en 5, pero uno se redondea hacia arriba y el otro hacia abajo. En ambos casos, el último decimal es par. Más info: http://www.xbeat.net/vbspeed/i_BankersRounding.htm

Bola extra

Si quieres ampliar información, estos son algunos artículos muy interesantes:

Espero que os haya sido de utilidad, enjoy!

Fórmulas para el IVA

PostedEn Programación     Comentarios 1 comentario
Sep
1

Hoy acaba de subir el IVA, así que para refrescar un poco nuestras matemáticas básicas, vamos a ver que operaciones podemos hacer con él. El lenguaje de programación es Groovy, pero con algunos cambios es compatible en Java.

Obtener el IVA de un producto

public static BigDecimal calcIva(BigDecimal p, BigDecimal IVA)   { return p * (IVA/100) }

assert calcIva(100, 21) == 21

Obtener el precio con IVA de un producto que no tiene IVA

public static BigDecimal sumaIva(BigDecimal p, BigDecimal IVA)   { return p * (1+(IVA/100)) }

assert sumaIva(100, 21) == 121

Dado el precio con IVA de un producto, averiguar su valor sin IVA

public static BigDecimal quitaIva(BigDecimal p, BigDecimal IVA)  { return p / (1+(IVA/100))  }

assert quitaIva(121, 21) == 100

Obtener solo el IVA de un producto con IVA

public static BigDecimal extraeIva(BigDecimal p, BigDecimal IVA) { return p / (1+(100/IVA))  }

assert extraeIva(121, 21) == 21

Supongamos, que es mucho suponer, que sube el IVA del 18% al 21%. Y que el comercio decide “asumir” esta subida, bajando el importe del producto para que, al aplicar el nuevo IVA, tenga el mismo precio final.

public static BigDecimal asumeIva(BigDecimal p, BigDecimal IVA, BigDecimal IVA_NUEVO) {
    return quitaIva(sumaIva(p, IVA), IVA_NUEVO)
}

println asumeIva(100, 18, 21) // devuelve 97.520661157

Es decir, que si antes nuestro producto de 100€ lo vendíamos por 118€ con IVA al 18%, ahora para seguir vendiéndolo a 118€ pero con el IVA al 21% debemos rebajar la base imponible a 97.52€

El uso de BigDecimal en las operaciones no es fortuito. En otro post un poco más avanzado (este ha sido muy básico) explicaremos como guardar y operar correctamente precios con decimales, donde se verá que los Float y Double no son precisamente la mejor opción, y BigDecimal es el único tipo que nos ofrece seguridad a la hora de hacer operaciones matemáticas con importes.

Usando BitSet para guardar ids

PostedEn Programación     Comentarios 1 comentario
Sep
1

Todos los programadores conocemos de sobra la mayoría de estructuras de datos que existen. Bien porque las usamos a diario o porque las hemos estudiado en la facultad, a nadie le resulta raro un array, un mapa (o hash) o una lista enlazada entre otras muchas. Para los programadores Java (u otro lenguaje de la JVM), las interfaces Collection y Map, junto con todas las clases que las implementan son el pan nuestro de cada día.

Sin embargo, hay algunas estructuras de datos que quizá (solo quizá) no conocemos tanto y que son sumamente interesantes. Una de ellas es java.util.BitSet. Quizá sea poco conocida por varias razones: primero porque no implementa ni Collection ni Map ni ninguna otra interfaz del api colecciones. Tampoco hereda de ninguna clase especial: el BitSet es una clase suelta, sin más. Segundo porque su uso típico parece un tanto alejado de lo que usamos cada día. Es interesante tener cientos o miles de bits juntos, y poder hacer operaciones and, or y xor con ellos, pero en realidad pocas veces necesitamos hacer operaciones a nivel de bit. Sin embargo, es posible darle otros usos a BitSet. Veamos uno de ellos.

Otros usos de BitSet

Supongamos que queremos guardar una gran cantidad de números enteros. Por ejemplo, los ids de un tabla en una aplicación con una base da datos. Si, de acuerdo, podríamos decir que para eso basta con crear otra tabla relacionada donde puedo guardar estos ids, filtrarlos, ordenarlos, etc. Pero no, supongamos que por cualquier razón, no queremos/podemos hacer esto. Si no conociéramos la clase BitSet, una primera solución sería guardar los ids en un List (ArrayList/LinkedList, no importa), el cual crecerá linealmente a medida que guardamos los ids.

Es decir, el tamaño que ocupa en memoria el List es directamente proporcional al número de elementos que almacena. Y aquí el problema viene cuando tenemos muchos, muchos elementos que guardar, por ejemplo 10.000.000. Tener 10 millones de integers es un array muy muy grande. Quizá podemos ahorrar algo de espacio con un BitSet, usando algunos trucos.

Veamos un BitSet como un array de booleanos, donde podemos preguntar una posición y que nos diga si es true o false (si el bit está a 1, es true; y si está a 0, es false). Usando el BitSet desde este punto de vista, podemos aprovechar para guardar los elementos posicionalmente. Es decir, en vez de guardar los valores, como se hacía en un List, el BitSet almacena las posiciones donde están los valores a guardar. Así, si queremos guardar el id 14, llamamos a nuestro BitSet y activamos el bit de la posición 14.

Un BitSet guardando el valor 14 simplemente tiene el bit 14 activo (a 1) y el resto desactivos (a 0)

              bit 14
                  v
000000000000000000100000000000000

Repetimos sucesivamente la misma operación con el resto de valores. Si ahora guardamos el 3 y 7, tendríamos un BitSet así:

              bit 14     7   3
                  v      v   v
000000000000000000100000010001000


Crecimiento de un BitSet

El tamaño y crecimiento de un BitSet viene dado por la posición más alta que en él guardamos. Así que no importa cuantos elementos introduzcamos en el BitSet, éste ocupará siempre lo mismo y solo crecerá cuando introduzcamos un elemento con un valor mayor. Es decir, ocupará lo mismo con los bits 3, 7 y 14, que si añadimos todos los bits 0, 1, 2, 3…12, 13 y 14. En ambos casos tiene que reservar sitio para 14 bits.

En resumen, un BitSet crece en función del bit más grande que almacena y un List en función del número de elementos que tiene. Para saber hasta cuanto puede crecer un BitSet tenemos que saber cual es el bit más grande. Después dividimos entre 32 (un Integer son 32 bits, de esta manera podemos hacer una comparación directa con el List de Integers) y ya sabemos cuanto ocupa en memoria. Por otro lado, un ArrayList crece en función del número de elementos que tiene, por lo que para saber cuanto ocupa, tan solo tenemos que ver cuantos elementos hemos insertado.

Todo esto provoca algunas situaciones que debemos valorar. Si vamos a guardar pocos elementos, y estos además tienen un valor muy alto, entonces vamos a desaprovechar mucho espacio. Por ejemplo: tenemos que guardar los valores 14 y 2510. En un List esto supone solo 2 Integers, uno para cada elemento. En un BitSet supone gastar bits hasta la posición 2510 (lo que hacen 79 elementos de 32 bits, o 79 Integers)

Pero si vamos a guardar más elementos, entonces es posible que nos compense un BitSet frente al List. Por ejemplo, queremos guardar solo los números pares del 0 al 2000, lo que suponen 1000 valores. Ahora el BitSet necesita bits hasta la posición 2000, lo que suponen 64 Integers, mientras que el ArrayList necesitaría 1000 Integers, uno por cada valor.

Hacer unos cuantos números antes de decidir qué colección usar nos puede hacer ahorrar bastante espacio. Por lo que la fórmula mágica es, usando la implementación de Java 6 usa en la que se usa un Long por cada 58 bits, si la densidad de tu colección es mayor que 1/58, entonces es preferible usar BitSet. Es decir, si (nº de elementos/valor más alto) > 0,01724 (1,72%). Todo esto suponiendo que nuestro List sea de Integers, si es de Longs, que ocupan el doble, entonces es la mitad.

- Resumen a ojo: si en nuestra tabla el id más alto es un millón, entonces nos compensa usar un BitSet a partir de 1720 ids por colección. -

Trabajando con la clase BitSet

Manipular un BitSet es extremadamente sencillo, si bien difiere un poco a como estamos acostumbrado de las clases List

BitSet bs = new BitSet();

// Número de elementos que tiene "activos" (bits a 1)
assert 0 == bs.cardinality();

// Añadimos 3 elementos: 0, 14, 2400 y el 80000
bs.set(0, true);
bs.set(14, true);
bs.set(2400, true);
bs.set(80000);  // Si no pasamos parametro, por defecto es true

// Número de elementos que tiene "activos" (bits a 1)
assert 4 == bs.cardinality();

// Borrar un elemento es simplemente asignarlo a false
bs.set(14, false);

// Para saber si hay un elemento activo
assert bs.get(0) == true;
assert bs.get(14) == false;
assert bs.get(2400) == true;

Hasta aquí muy fácil. Pero, ¿cómo itero los valores? No tenemos iterator(), así que tendremos que hacerlo así:

int value = -1;
while ((value = bs.nextSetBit(value+1)) != -1) {
    System.out.println(value);
}

Finalmente y de regalo, si queremos persistir a disco nuestro BitSet, podemos usar este código (que en realidad sirve para persistir cualquier objeto):

// Como guardarlo (comprimido, para que ocupe menos)
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(new java.util.zip.GZIPOutputStream(baos));
oos.writeObject(bs);
oos.close();

// Como volverlo a leer
ObjectInputStream ois = new ObjectInputStream(
    new java.util.zip.GZIPInputStream(new ByteArrayInputStream(bytes)));
BitSet bs = (BitSet) ois.readObject();
ois.close();

¡Espero que os haya sido de utilidad!

Bola extra: como siempre, esto es solo la punta del iceberg. Hay otras implementaciones de BitSet optimizadas para que el tamaño que necesitan en memoria no sea lineal con respecto al tamaño del bit más grande que almacenan, como FastBitSet de Javolution, o Javaewah.

Grails tip: mostrar la fecha y último commit de tu build

PostedEn Groovy, Programación     Comentarios 2 comentarios
Mar
29

Un pequeño truco para saber en todo momento que versión de nuestra aplicación Grails hemos desplegado en nuestro servidor. La idea es que, cuando construyamos nuestro war, se introduzcan algunos valores en el application.properties, como la fecha+hora actual y el id del último commit de Subversión con el que se ha hecho la compilación. Después, crearemos un servicio que nos muestre esta información en un controlador, de manera que la podamos consultar o consumir desde algún script por ejemplo.

Modificando el application.properties durante la compilación.

Creamos un fichero llamado _Events.groovy en la carpeta scripts de nuestro proyecto (o si nuestro proyecto se compone de varias aplicaciones Grails y usamos un plugin in-place común para todas, lo podemos incluir en este plugin in-place)

import org.tmatesoft.svn.core.internal.io.dav.*
import org.tmatesoft.svn.core.internal.io.fs.*
import org.tmatesoft.svn.core.internal.io.svn.*
import org.tmatesoft.svn.core.wc.*

eventWarStart = { type ->

    DAVRepositoryFactory.setup()
    SVNRepositoryFactoryImpl.setup()
    FSRepositoryFactory.setup()

    SVNWCClient wcClient = SVNClientManager.newInstance().WCClient
    SVNInfo svninfo = wcClient.doInfo(new File(basedir), SVNRevision.WORKING)
    def svnCommit = "#${svninfo.revision.number} - ${svninfo.committedDate.format("yyyy-MM-dd HH:mm:ss")} (${svninfo.author})"
    println "| Commit: ${svnCommit}"

    metadata.'build.svn.commit' = svnCommit.toString()
    metadata.'build.date' = new Date().format("yyyy-MM-dd HH:mm:ss")
    metadata.persist()

}

Mostrando la información

Después creamos un controlador llamado SystemInfoController.groovy (o como queramos)

class SystemInfoController {
    def grailsApplication
    def index() {
        def versionInfo = "${grailsApplication.metadata.get("app.name")}-${grailsApplication.metadata.get("app.version")}
"+
                          "Build date: ${grailsApplication.metadata.get("build.date")}
"+
                          "SVN ${grailsApplication.metadata.get("build.svn.commit")}
"
        render versionInfo as String
    }
}

Ahora, después de construir con grails war y desplegar nuestra aplicación, solo tenemos que ir a nuestro proyecto: http://localhost:8080/mi-app/systemInfo y veremos algo como esto:

mi-app-1.5.0
Build date: 2012-03-29 16:16:44
SVN #1472 - 2012-03-29 16:16:20 (avilches)

Con esta información podremos saber la fecha exacta de la compilación y evitar esa típica situación de no saber si esta realmente desplegada la última versión o no (que siempre se resuelve mirando el tamaño del war generado y comparándolo con el del war desplegado, borrando todos los temporal, contruyendo y desplegando desde cero otra vez)

Vitaminas para tu proyecto Java

PostedEn Groovy, Programación     Comentarios 1 comentario
Feb
12

El próximo 17 y 18 de Febrero se celebrará la segunda edición de Spring IO en Madrid. Habrá tres tracks con charlas y ponencias muy interesantes; y yo tendré el placer de impartir un taller de horas práctico llamado “Vitaminas para tu proyecto Java” (el jueves 17 a las 11:40)

Pero ¿por qué un taller de groovy para programadores Java? Pues porque Groovy y Grails están tan unidos que a veces se confunden con la misma cosa. Y aunque mucha gente piensa que solo es posible programar con Groovy usando Grails, esto no es así. Sin importar como sea tu proyecto actual, Groovy puede ayudarte de muchas maneras. Así que en este taller de 2 horas trataré varios aspectos de Groovy que podemos explotar desde nuestra aplicación 100% Java. Aprenderemos a hacer testing y mocks de tus componentes Java con Groovy, crearemos un pequeño DSL y aprovecharemos las capacidades dinámicas de Groovy para crear, editar y ejecutar scripts directamente desde la interfaz web de tu aplicación. También aprenderemos a parsear Xml, trabajar con JDBC y muchas cosas más, todo de una manera práctica, con ejercicios y recetas que puedes llevarte a casa y ponerlas en marcha el lunes siguiente en tu trabajo.

El objetivo no es hablar de las bondades de Grails, como llevo haciendo desde hace un par de años, sino demostrar a los que todavía no conocen Groovy, que pueden ayudarles mucho en su día a día. Groovy ¡vitaminas para tu proyecto Java! El nivel de la charla es intermedio y el idioma es el castellano. ¡Nos vemos en el SpringIO 2012! Más información en: http://springio.net/agenda

JSONP, o la inserción dinámica de scripts que podría sustituir a Ajax

PostedEn Programación     Comentarios 15 comentarios
Sep
15

Parece una tecnología nueva, pero no lo es ni mucho menos. Sugerida en diciembre del 2005 por Bob Ippolito (ver este post en su blog) como una solución para cargar código de manera asíncrona desde diferentes dominios, se hizo popular cuando Google la utilizó para implementar Google Instant Search. Sin embargo, tras este nombre peculiar, se esconde algo muy sencillo y extremadamente útil que, para mi gusto, es más potente y sencillo que Ajax. En este post voy a intentar explicar cómo funciona Ajax por dentro junto con una alternativa mucho más sencilla, que es la que usa Jsonp.

Ajax por dentro

Hace ya tiempo que todos conocemos Ajax y lo usamos a diario en nuestras aplicaciones web, pero tenemos que recordar que el objeto XMLHttpRequest fue una invención de Microsoft en el 1999 que no estandarizó hasta el 2006 y que, hasta hoy en día, sigue siendo un hack que funcione en cualquier navegador. De hecho, nadie usa Ajax directamente, sino que siempre se hace uso de alguna librería (como jQuery, Prototype, Mootols o cualquier otra), y ninguna de ellas se libra de tener algo parecido a esto:

var httpRequest;
if (window.XMLHttpRequest) {
    httpRequest = new XMLHttpRequest();
} else if (window.ActiveXObject) {
    try { httpRequest = new ActiveXObject("MSXML2.XMLHTTP");
    } catch (e) {
        try { httpRequest = new ActiveXObject("Microsoft.XMLHTTP");
        } catch (e) {}
    }
}

Lo cual es bastante aberrante, pero funciona.

Cómo solemos usar Ajax

Aunque inicialmente Ajax se ideó para hacer peticiones Xml (la “x” final de Ajax viene precisamente de Xml), el coste que supone parsear este tipo de formato en Javascript hizo que se sustituyera por algo mucho más sencillo de parsear y que, además, viene implícito de manera nativa en el lenguaje Javascript: JSON.

JSON es un formato extremadamente sencillo para guardar colecciones, basicamente listas de elementos y mapas de parejas clave/valor (también conocidos en Javascript como Objetos y en otros lenguajes como tablas hash), que se pueden combinar entre sí para hacer estructuras más complejas:

["Primer elemento","Segundo", 800, true, false, null]

{key:"value", anotherKey:293

{id:201,
 nombre:"Alberto",
 profesion:"Programador",
 lenguajes: ["Java", "Groovy", "Flex", "Javascript"],
 webs: [{id:21, nombre:"Coderfacts", url:"http://coderfacts.com"},
        {id:178, nombre:"Blog", url:"http://albertovilches.com"},
        {id:44, nombre:"Greach", url:"http://greach.es"}
       ],
 contacto: {twitter:"@albertovilches",
            email:"vilches ARROBA gmail.com",
            skype:"alberto_vilches"
           }
}

Y parsearlo es algo tan sencillo como evaluarlo como expresión. Es decir, el formato JSON es exactamente el mismo que usa Javascript para guardar colecciones:

def texto = "['hola', {quien:'Mundo'}]"
def json = eval(texto)
alert(json[0])          // Muestra hola
alert(json[1].quien)    // Muestra mundo

Por esta razón, la mayoría de frameworks Ajax nos permiten decidir que tipo de respuesta esperamos: texto, html, xml o json. Son muchas las veces que usamos Ajax para obtener código html que inyectamos en el Dom de nuestra página web, pero poco a poco hemos ido cambiando el hábito y preferimos que las peticiones devuelvan Json en vez de html. Las ventajas son muchas: en el servidor, nos ahorramos renderizar el resultado para convertirlo en html, y nos limitamos a enviar la información transformada en Json, que ocupa mucho menos. Y en el cliente, podemos utilizar el Json recibido para cambiar el Dom y renderizar el resultado de la petición, pero también podemos hacer transformaciones y cálculos antes de convertirlo en algo visible en nuestra web. Sin duda, Json ha permitido aligerar la carga en los servidores, y delegar que la decisión sobre como renderizar los resultados se haga donde se tiene que mostrar, en la propia página.

Veamos donde estamos: tenemos una aplicación web que necesita ser modificada. Hacemos una petición Ajax al servidor solicitando una información determinada. El servidor recibe la petición, lee los parámetros, busca en la base de datos (o donde sea) y genera un json con la información. El cliente, nuestra página web, recibe el Json, lo parsea con Javascript y renderiza el resultado, modificando la página actual. Podemos continuar.

Javascript dinámico frente a Ajax

Intentemos responder a esta pregunta: si solo usamos Ajax para cargar Json, el cual solo puede ser parseado y utilizado desde Javascript, ¿porqué no cargar el Javascript con el Json y ejecutarlo directamente desde el servidor?

Supongamos este ejemplo básico y casi minimalista:

Nombre <span id="nombre"></span><br/>
Profesión <span id="profesion"></span>

<script type="text/javascript">
function pintaPersona(data) {
    document.getElementById("nombre").innerHTML = data.nombre;
    document.getElementById("profesion").innerHTML = data.profesion;
}
</script>

Solo tiene un par de etiquetas <span> donde mostrar un nombre y profesión, y una función cargaPersona() que se encargará de rellenar estas etiquetas con el objeto json recibido como parámetro. Así que podemos llamar en cualquier momento a la función cargaPersona() para rellenar los campos simplemente con:

pintaPersona({nombre:"Alberto", profesion:"Programador"});

Pero también podemos generar este Javascript dinámicamente en servidor a partir de una url, usando Java, Php, Ruby, etc. Por ejemplo:

<script src="cargaPersona.php?id=25" type="text/javascript"></script>

Esta url podría buscar un usuario (usando el parámetro id con valor 25) y generar directamente la función pintaPersona() con sus datos:

GET /cargaPersona.php?id=25
pintaPersona({nombre:"Alberto", profesion:"Programador"});

No es más que un ejemplo muy sencillo, ya que la función podría hacer algo mucho más completo, pero la idea es la misma: generar dinámicamente Javascript en servidor que llame a una función que ya tenemos previamente definida en nuestra página, con unos datos determinados.

Pero para que este Javascript dinámico pueda competir con Ajax, necesitamos que sea asíncrono. Es decir, poder cargarlo cuantas veces queramos y en cualquier momento, por ejemplo cuando se hace click en un listado de usuarios. Para ello, lo que haremos es crear una etiqueta <script> y añadirla al dom (por ejemplo, al final del body). El navegador cargará dicho Javascript (que puede estar en cualquier dominio) y lo ejecutará en ese mismo momento. Para crear y añadir scripts al Dom no hace falta ningún framework, basta con esto:

var script = document.createElement("script")
script.src = "cargaPersona.php?id=25"
script.type = 'text/javascript';
document.getElementsByTagName("body")[0].appendChild(script)

Aunque es mejorable, ya que la etiqueta script se puede reutilizar una y otra vez, desde mi punto de vista, no hay color con Ajax y la complejidad interna que supone crear y procesar una petición con XMLHttpRequest.

Un ejemplo con todo funcionando

Veamos un ejemplo de los dos sistemas juntos:

Nombre <span id="nombre"> /span><br/>
Profesión <span id="profesion"></span>
<script type="text/javascript">
    function pintaPersona(data) {
        document.getElementById("nombre").innerHTML = data.nombre;
        document.getElementById("profesion").innerHTML = data.profesion;
    }
</script>

<!-- Ajax jQuery flavor -->
<script type="text/javascript" src="http://code.jquery.com/jquery-1.6.4.js"></script>
<script type="text/javascript">
    function cargaPersonaAjax(id) {
        $.ajax({url: "http://localhost/cargaPersonaJson.php", data: {id:id},
                dataType: "json", success: function(json) { pintaPersona(json) }});
    }
</script>

<!-- No framework, script dynamic flavor -->
<script type="text/javascript">
    function cargaPersonaScript(id) {
        var script = document.createElement("script")
        script.src = "http://localhost/cargaPersonaScript.php?id=" + id
        document.getElementsByTagName("body")[0].appendChild(script)
    }
</script>

<ul>
    <li>Usuario 1 <a href="javascript:void(cargaPersonaAjax(1))">Mostrar</a></li>
    <li>Usuario 2 <a href="javascript:void(cargaPersonaScript(2))">Mostrar</a></li>
</ul>

Siendo el resultado de cargaPersonaScript.php un Javascript que llama a la función pintaPersona:

GET /cargaPersonaScript.php?id=25
pintaPersona({"nombre":"Alberto", "profesion":"Programador"});

Y el de cargaPersonaJson.php solamente Json puro, por lo que será responsabilidad del handler “success” de jQuery el llamar a pintaPersona() de nuevo con el Json recibido.

GET /cargaPersonaJson.php?id=25
{"nombre":"Alberto", "profesion":"Programador"}

JSONP

Muy bien, ahora que ya sabemos cargar scripts dinámicamente y que incluso podemos sustituir a Ajax con él (aunque también tiene sus limitaciones), ¿qué es JSONP? Si nos fijamos en el resultado de las dos peticiones anteriores, vemos que la diferencia es que una es Json puro, y la otra es el mismo Json, pero envuelto en una función.

JSON               {"nombre":"Alberto", "profesion":"Programador"}
JSONP pintaPersona({"nombre":"Alberto", "profesion":"Programador"});

Jsonp establece que el nombre de la función que envolverá el Json de respuesta lo debe decidir el usuario, enviándoselo en un parámetro llamado “callback”. Así que JsonP no es más que la carga dinámica de scripts definiendo en uno de los parámetros el nombre de la función que se va a llamar cuando se ejecute. De esta manera, podemos reutilizar la misma llamada para ejecutar diferentes funciones usando los mismos datos.

Siguiendo con nuestro ejemplo anterior, la función cargaPersona, responsable de hacer la llamada Jsonp, deberá enviarle al servidor en uno de los parámetros el nombre de la función de vuelta (callback) a ejecutar, en nuestro caso “pintaPersona”:

function cargaPersonaScript(id) {
    var script = document.createElement("script")
    script.src = "http://localhost/cargaPersonaJsonP.js?id=" + id;
    // Añadimos el nombre de la función callback
    script.src = script.src + "&callback=pintaPersona";
    script.type = 'text/javascript';
    document.getElementsByTagName("body")[0].appendChild(script)
}

En resumen, una llamada JSONP devuelve un JSON envuelto en una llamada a una función Javascript cuyo nombre se especifica en la propia petición como parámetro.

GET /cargaPersonaJsonP.php?id=25&callback=pintaPersona
pintaPersona({nombre:"Alberto", profesion:"Programador"});

GET /cargaPersonaJsonP.php?id=25&callback=otraFuncionDistinta
otraFuncionDistinta({nombre:"Alberto", profesion:"Programador"});

Incluso la versión jQuery del equivalente al ejemplo anterior es más reducida y simple:

function cargaPersonaAjax(id) {
    $.ajax({url: "http://localhost/b.js", data: {id:id},
            dataType: "jsonp", jsonpCallback: "pintaPersona"});
}

Internamente, el hecho de especificar Jsonp como método de envío hace que la petición se ejecute usando la inserción de una etiqueta <script> en vez Ajax, aunque el resultado es el mismo.

Conclusión

¿Debería Jsonp o la carga dinámica de etiquetas <script> sustituir a Ajax? No, ni mucho menos. Las dos formas son equivalentes en resultado y tienen sus ventajas e inconvenientes: con la inserción dinámica de scripts no se pueden hacer POST (aunque ¿quién los necesita realmente?) pero permite cargar contenidos en diferentes dominios (cross domain), cosa que no es posible con Ajax. De hecho, todas las soluciones llamadas Ajax cross-domain usan esta técnica, como ésta o esta otra. Eso sin contar lo obvio: que con Ajax podemos cargar cualquier tipo de contenido (texto,html,script,json o binario), pero tenemos que parsearlo, y que con JsonP nos limitamos a cargar Javascript, pero se ejecuta automáticamente por el navegador, al formar parte de una etiqueta <script>. Aunque esto también tiene sus riesgos: alguien puede interceptar la carga del javascript desde otro servidor y modificar el código que luego se ejecutará en nuestra página, así que solo deberemos usar esta técnica con sitios en los que confiemos, o hayamos creado nosotros. Y, por supuesto, asegurarnos de que no se usa el parámetro callback para inyectar código de vuelta, y para eso tenemos algunos trucos como sanitizar los parámetros.

Personalmente, si voy a trabajar con Json, usaré Jsonp, y dejaré Ajax para trabajar con html y binarios. Si estás dudando qué usar, siempre puedes hacer unos tests de rendimiento en tu propio navegador. Parece ser que JsonP es bastante más rápido y en algunos sitios importantes ya lo están usando, como Delicious, Flickr, Twitpic o Youtube. De todas formas, aprender a usar las dos y saber, además, como funcionan por dentro, son la clave para tomar la decisión correcta en el momento en el que lo necesitemos.

Bola Extra: limitaciones de las peticiones Get

La inserción de <script> solo permite el uso de urls que llegan al servidor como una petición GET. Por un lado, esto hace que sea imposible hacer upload de ficheros, que requieren una petición POST con un enctype “multipart/form-data”. Y por otro lado, la especificación de Htpp no define un límite para el tamaño de las urls, así que cada navegador tiene el suyo propio que debemos tener en cuenta:

  • Internet Explorer: 2083, según nota oficial de Microsoft, pero gente comenta en Stackoverflow que ha llegado a probar longitudes mucho mayores, como 4.000 caracteres en IE8.
  • Firefox: 65.536 de límite en teoría, pero acepta 100.000 en la práctica
  • Safari: 80.000
  • Opera: 190.000

Puedes ver las pruebas prácticas que han hecho algunas personas en este post.

Abierto el registro de Greach

PostedEn Crónicas, Groovy     Comentarios Comments Off
Sep
10

El 1 de septiembre se abrió el registro de Greach, un evento que estoy organizando, dedicado exclusivamente al lenguaje Groovy y a todos los frameworks y herramientas que se han creado a su alrededor. Se celebrará el próximo viernes 4 de Noviembre en la universidad San Pablo CEU de Madrid. De un solo día de duración, será corto pero intenso: dos tracks llenos de charlas y talleres sobre Groovy, Grails y Griffon, con primeras figuras nacionales e internacionales como Guillaume Laforge y Andrés Almiray (líderes del proyecto Groovy y Griffon, respectivamente).

Podéis visitar la web de Greach para conocer el programa y horario (todavía no definitivo) de las charlas, conocer un poco más a los sponsors y ver la localización del evento. Algunos speakers españoles ya han confirmado el tema de sus charlas y tenemos temas tan interesantes como programación funcional con Groovy, un taller de Koans de metaprogramación y MongoDB sobre Grails. Todavía quedan por confirmar nuevos speakers y nuevas ponencias.

Grails, enumerados, combos, i18n y otras historias

PostedEn Groovy, Programación     Comentarios 6 comentarios
Aug
22

Los tipos enumerados aparecieron en Java 5 para aliviar todo el uso (y abuso) de constantes que usábamos en nuestros proyectos. Y en Grails podemos usarlos como atributos en nuestras clases de dominio de una manera muy sencilla.

Antes de ver los enumerados, vamos a ver como manejábamos antes la situación sin ellos. Supongamos que tenemos una tabla y clase de dominio Usuario, y tenemos un campo estado en el que queremos guardar el estado de ese usuario. Podríamos tener algo así:

class Usuario {
    String nombre, password, email
    String estado

    static constraints = {
        estado(inList: estados)
    }

    static String estado_valido = "valido"
    static String estado_eliminado = "eliminado"
    static String estado_bloqueado = "bloqueado"
    static List estados = [estado_valido, estado_eliminado, estado_bloqueado]
}

El valor que le demos a las constantes valido, eliminado y bloqueado no importa demasiado, podrían haber sido los valores 0, 1 y 2 perfectamente. Pero es que ese valor es el que vamos a ver cuando consultemos la base de datos con SQL, así que es interesante que sea descriptivo, por lo que elegimos una cadena de texto.

¿Cuál es el problema? Que los estados no son más que valores, y no tienen información adicional. Además, mezclamos la definición de los estados en la propia clase. Puede que no parezca muy grave si solo tenemos tres estados, pero imagina que tuviéramos 20 estados. O imagina que, además, tuviéramos otros campos como “tipoDeUsuario”, “sexo”, “situaciónLaboral” o lo que sea, que también fueran susceptibles de tener varios valores como el campo estado. El resultado sería una clase llena de constantes, colecciones de constantes y atributos que usan esas constantes.

Con un enumerado todo esto es mucho más sencillo. Un enumerado es igual que una clase, pero define una serie de instancias únicas con un nombre concreto. Para crear el enumerado, podemos hacerlo en un fichero aparte, como si fuera una clase más, o podemos hacerlo dentro de nuestra clase de dominio:

class Usuario {
    String nombre, password, email
    Estado estado
}
enum Estado {
    valido, eliminado, bloqueado
}

Muy bien, y ahora ¿cómo accedemos a estos datos? Simplemente usamos cada enumerado como si fuera un valor estático de la clase Estado:

Usuario u = new Usuario(estado: Estado.valido) // creación
u.estado = Estado.eliminado // asignación
if (u.estado == Estado.bloqueado) println "Oh oh!" // lectura y comparación

Gorm se encargará de leer y persistir este dato en nuestra tabla, usando el propio nombre del enumerado “valido”, “eliminado” y “bloqueado”.

Métodos values() y name()

Los enumerados tienen dos métodos muy interesantes. Uno de ellos es values(), que se usa a nivel de clase y devuelve una colección con todos los enumerados posibles. Y el otro es name(), que se usa a nivel de instancia del enumerado y devuelve un String con el nombre de la instancia.

def u = new Usuario(estado: Estado.valido)
String nombreEstado = u.estado.name()
println nombreEstado // pintará "valido"

Estado.values().each { println it.name() } // pintará valido eliminado bloqueado

Para acceder a un enumerado usando su nombre, podemos hacerlo de varias maneras:

String nombreEstado = "valido"
println Estado[nombreEstado]
println Estado."$nombreEstado"

¿Para qué querríamos hacer una cosa así? Pues por ejemplo cuando el valor viene en un fichero de texto, o a través de un formulario como parámetro de una petición http. En este último caso no sería realmente necesario, pues el binding de Grails transforma automáticamente los campos de texto en su enumerado correspondiente, pero siempre es bueno saber como hacerlo a mano.

def u = new Usuario(params) // Esto funciona si hay un parámetro "estado"
u.estado = Estado."${params.otroEstado}" // Y esto también funciona

Añadiendo información a cada enumerado

Una de las ventajas que tiene el usar enumerados, es que podemos ampliar la información que tiene cada valor. Por ejemplo, en el caso de los estados, podemos añadir un texto con el nombre y un color, que usaremos después para mostrar el estado junto con los datos del usuario.

enum Estado {
    valido("Valido", "blue"),
    eliminado("Eliminado", "gray"),
    bloqueado("Bloqueado", "red")

    String text, color
    Estado(text, color) {
        this.text = text
        this.text = color
    }
}

Ahora, desde nuestros gsp, cuando mostremos la información del usuario, podemos hacer:

Nombre: ${usuario.nombre}, email: ${usuario.email}
Estado: <span style="color: ${usuario.estado.color};">${usuario.estado.text}</span>

Ponemos texto y color, pero bien podríamos haber puesto su código i18n y un nombre de estilo CSS.

Un ejemplo de uso desde un Gsp con un combo:

<g:select name="estado" from="${Estado.values()}" optionValue="text"/>

El atributo optionValue especifica el atributo del enumerado a mostrar en el combo (lo que ve el usuario). También se pueden definir un closure que se ejecutará en cada enumerado. Si no se especifica nada, se muestra el resultado de toString()

Por defecto, el key de cada elemento del combo es el toString() del enumerado, que devuelve lo mismo que name(). Pero si hemos sobreescrito el método toString() para que devuelva otra cosa, entonces tendremos que añadir el atributo optionKey a nuestra etiqueta, especificando un closure que devuelva el name() de cada enumerado:

<g:select name="estado" from="${Estado.values()}" optionValue="text"
          optionKey="${ {estado -> estado.name()} }"/>

Internacionalización en enums y combos

Si queremos internacionalizar nuestros enumerados y combos, solo tenemos que hacer que el atributo text tenga el código de nuestro resource bundle.

enum Estado {
    valido("enum.estado.valido", "blue"),
    eliminado("enum.estado.eliminado", "gray"),
    bloqueado("enum.estado.bloqueado", "red")

    String text, color
    Estado(text, color) {
        this.text = text
        this.text = color
    }
}

En nuestro messages_es.properties:

enum.estado.valido=Valido
enum.estado.eliminado=Eliminado
enum.estado.bloqueado=Bloqueado

Y a la hora de mostrar nuestros datos:

Nombre: ${usuario.nombre}, email: ${usuario.email}
Estado: <span style="color: ${usuario.estado.color}"><g:message code="${usuario.estado.text}"/></span>

Los combos tendrán ahora un closure como optionValue, en vez de un atributo como tenían antes, con el fin de transformar el código del atributo text, en su correspondiente traducción usando el taglib g.message :

<g:select name="estado" from="${Estado.values()}"
          optionValue="${ {estado -> g.message(code:estado.text)} }"/>

Aunque también podemos usar la solución que proponen en este post, aunque personalmente me parece un poco más rebuscada.

Lógica en los enumerados y buenas prácticas

Los enumerados al igual que las clases pueden tener métodos. Una buena práctica es separar la lógica de los enumerados en métodos dentro. ¿Para que querríamos hacer esto? A veces nuestra aplicación se debe comportar de manera diferente en función de si una variable es un enumerado u otro. Una manera de cambiar este comportamiento es añadir una condición que compare el valor de una variable con un enumerado (o varios) en concreto, y obrar en consecuencia. Sin embargo, esto es una mala práctica. Veamos como arreglarlo.

Ejemplo de código que sería una mala práctica:

// EJEMPLO DE MALA PRACTICA
def login(nombre, password) {
    def usuario = Usuario.findByNombreAndPassword(nombre, password)
    if (usuario.estado == Estado.bloqueado || usuario.estado == Estado.eliminado) // NO PASAR
    else …. // OK
}

Estamos usando el valor que hay en usuario.estado para compararlo dos enumerados (bloqueado y eliminado). Si la condición es cierta, no se permite la entrada al usuario a la aplicación. Pero, ¿no sería más legible si, en vez de comparar los valores, le preguntáramos al enumerado si se le permite la entrada a la aplicación?

// EJEMPLO DE BUENA PRACTICA
def login(nombre, password) {
    def usuario = Usuario.findByNombreAndPassword(nombre, password)

    if (usuario.estado.canLogin()) // NO PASAR
    else …. // OK
}

Queda bastante más claro. Ahora tenemos que ser capaces de que cada enumerado tenga un método que devuelva true o false si se le permite entrar en la aplicación o no. La manera más cómoda de hacerlo es añadiendo un atributo boolean a cada enumerado, especificando su valor durante su creación. Y que el método canLogin() devuelva este valor, que será distinto para cada enumerado.

enum Estado {
    valido(true), eliminado(false), bloqueado(false)

    boolean logable
    Estado(logable) {
        this.logable = logable
    }
    boolean canLogin() { logable }
}

Esto tiene la gran ventaja de que si añadimos un nuevo enumerado, no tendremos que buscar por toda nuestra aplicación donde se usa para modificar las condiciones. O si queremos cambiar el comportamiento de la aplicación donde se use el enumerado, solo tendremos que modificar la implementación, no todas las veces donde se use.

En general, podemos decir que tener en la lógica de nuestra aplicación una condición en la que se compara un enumerado con otro es siempre una mala práctica. En su lugar, se debería usar un método dentro del enumerado que devuelva el resultado de esta condición. Y que cada enumerado defina, en su creación como atributos, los valores necesarios que se usarán para evaluar dicha condición.

Resumiendo…

Y nada más. El soporte de enumerados en Grails es muy bueno: se transforman correctamente en el binding de los controladores y se persisten facilmente en nuestras clases de dominio. Y desde el punto de vista del diseño, el código es más claro y estructurado, y permite añadir atributos y lógica a cada enumerado. En definitiva, usar enumerados es una gran solución que solo proporciona ventajas.

Bola extra: alguien se preguntará para que sirve el método name(), si el método toString() de los enumerados ya devuelve el nombre de éste. Sencillo: toString() se puede sobreescribir, y name() no porque es final. Y esto es bueno porque name() nunca debe cambiar, ya que el nombre es una propiedad intrínseca e inmutable de cada enumerado. Y el toString() es simplemente su representación visual, y puede devolver cualquier cadena que decida el que diseñe la aplicación.

PhoneGap vs. Flex vs. Appcelerator vs. Corona: nuevas conclusiones

PostedEn Programación     Comentarios 13 comentarios
Aug
6

Pasada ya casi una semana desde los últimos post en los que evaluaba estas herramientas, he tenido constancia de dos hechos que me han hecho cambiar de opinión. Además, revisando la tabla del último post, creo que no he sido todo lo exacta que me habría gustado, así que en vez de modificar todo el artículo, me he decidido a hacer una nueva tabla y sacar nuevas conclusiones. Si no sabes de qué va todo esto, aquí tienes los posts anteriores relacionados

¿Qué es lo que me ha hecho cambiar de opinión? Para empezar, mi tabla anterior era demasiado “estricta” o “personalizada” y ponía características como “regulares” cuando no eran más que temas subjetivos. Por ejemplo, que a mi me parezca mejor lenguaje ActionScript que JavaScript no significa que si una herramienta usa JavaScript tenga que tener peor nota. Por esta razón, he igualado las características de las herramientas basadas en opiniones subjetivas. ¿Cuando evalúo una característica como mala? cuando supone un impedimento real para el desarrollo. Por ejemplo, el hecho de que para construir una aplicación para IOS sea necesario tener un Mac es una característica regular, pero no es impedimento, por lo que no la he puesto como roja (y antes sí).

¿Que es lo que he puesto entonces como rojo/malo/impedimento? 3 cosas nada más:

  1. El precio: Flash Builder 4 y Corona.
  2. Que la aplicación se distribuya con el código fuente: PhoneGap.
  3. Que requiera un modelo específico de dispositivos: Adobe Air mobile para Android requiere terminales de gama alta con procesador de Arm7 (gracias Erik por avisar!).

Así que mis conclusiones son distintas. Flex baja muchos puestos por el hecho de que no funciona en todos los dispositivos Android, y PhoneGap también, al permitir que cualquier pueda bajarse tu aplicación y verle todas las tripas. Entonces, ¿cuál elijo?

Primero hay que preguntarse el objetivo de la aplicación

  • Publicarla en el AppStore/Android Market con un precio por descarga (aplicación de pago). No puedes usar PhoneGap porque podrá ser “pirateada” con extrema facilidad, al incluir el código fuente. Ni deberías usar AdobeAir porque no funcionará en todos los dispositivos Android. Te queda solo Corona para juegos y Appcelerator para el resto de aplicaciones.
  • Publicarla en el AppStore/Android Market como aplicación gratuita. Puedes usar PhoneGap, siempre que no te importe que el mundo entero vea tu código. No deberías usar Adobe Air por la misma razón que el punto anterior, así que te queda Corona y Appcelerator.
  • Aplicación a medida. Puedes usar PhoneGap, tu cliente quiere que le desarrolles una aplicación a medida, no robarte el código. Puedes usar Adobe Air mobile siempre y cuando te asegures de que tu cliente va a tener terminales con el procesador Arm7, y le dejes bien claro (por escrito) que no funcionará con terminales que no lo tenga, para evitar sorpresas posteriores. Aquí puedes elegir el que quieras y usar la guía para programadores con prisa de mi anterior post.

Nada más, si quieres saber de qué va todo esto, puedes ver mi posts anteriores sobre desarrollo de aplicaciones móviles multiplataforma:

Desarrollo de aplicaciones móviles multiplataforma parte 3/3: Corona y Adobe Air mobile y conclusiones

PostedEn Programación     Comentarios 16 comentarios
Aug
3

Última parte de una serie de posts sobre desarrollo de aplicaciones móviles multiplataforma. Post relacionados:

NOTA: Al final del post hay una guía rápida para programadores con prisa, que incluye una tabla de características de todas las herramientas.

Adobe Air mobile: ActionScript

Adobe Air mobile funciona con Flex 4 y soporta las plataformas IOS, Android y BlackBerry Tablet, además de los sistemas de escritorio Windows, Mac y Linux (a través de un runtime). Las aplicaciones de escritorio no son directamente compatibles con las de móvil, pues los controles visuales cambian (en concreto, el sistema de gestión de ventanas), aunque la lógica de negocio se puede compartir igualmente.

Flex 4 utiliza el lenguaje de programación ActionScript, que es (por hacer una comparación) un Javascript compilado, de tipado fuerte y con clases, interfaces, herencia y paquetes muy parecido a Java con el que poder hacer complejos desarrollos. El IDE oficial, Flash Builder 4.5 es un IDE muy potente, pero de pago. Es posible compilar y empaquetar las aplicaciones con el Flex SDK opensource y gratuito (basado en Java), aunque con Flash Builder es mucho más fácil e inmediato, ya que proporciona una gran cantidad de wizards y editores. Los controles visuales usados durante el desarrollo y ejecución no son los originales de cada plataforma, sino que son específicos de Flex 4. Esto tiene su lado bueno y su lado malo: en su ejecución los elementos no van tan “suaves” como si fueran nativos, pero nos garantiza que todas las aplicaciones tendrán exactamente el mismo aspecto y comportamiento.

EDITADO: Erik Camacho me indica en un comentario que la aplicaciones generadas con Adobe Air necesitan que el terminal cuente con procesadores con arquitectura Arm7 que son los de gama alta hoy en día. Curiosamente para iOS los requerimientos son mucho menores y funciona desde el iPhone 3G para arriba (3GS, iPad 1 y 2). La razón es que en Android las aplicaciones corren sobre Air mobile, en iOS dado que Apple prohibe esto, corren sobre CocoaTouch directamente, obvio usando su propio set de Elementos para Interfaz de Usuario.

Se pueden depurar aplicaciones (poner puntos de ruptura, etc) en remoto. Es decir, puedes instalar y ejecutar una aplicación en tu Ipad/Iphone y depurarla desde tu ordenador. Una de las peculiaridades es que es la única herramienta que no requiere ni el Android SDK ni el Xcode para Mac para ejecutar y crear las aplicaciones.

Si quieres algunas aplicaciones hechas con Adobe Air mobile, aquí tienes un listado.

Ventajas:

  1. Multiplataforma móvil y también de escritorio.
  2. ActionScript es un lenguaje muy potente que permite el uso de patrones y estructuras complejas en los desarrollos.
  3. Desarrollo y definición de las vistas “a golpe de ratón” con el editor visual de MXML con Flash Builder. En general, el IDE y Flex 4 están muy avanzados y son muy potentes, y la documentación es realmente muy buena. Se nota que Adobe ha hecho un gran esfuerzo en todo el sistema.
  4. Flash Builder 4.5 no requiere el uso de Xcode ni Mac.
  5. Depuración remota.

Desventajas:

  1. El precio de Flash Builder 4.5. Aunque hay otras herramientas y se puede usar el SDK gratuito.
  2. No funciona en todos los Android, solo en los de gama alta que tengan arquitectura Arm7. Consulta el listado de dispositivos certificados por Adobe Air mobile.
  3. Rendimiento regular, renderización no muy suave en IOS. Las aplicaciones Air de escritorio consumen mucha CPU, sobre todo en Mac (aunque siguen trabajando en ello).
  4. Aspecto no nativo (aunque homogéneo entre todas las plataformas).

Ansca Corona: Lua

Corona es un framework para el desarrollo de juegos y aplicaciones gráficas para Iphone/Android. Se desarrolla en Lua y no tiene IDE, aunque si viene con un interprete-emulador y varios ejemplos de juegos bastante espectaculares. No estoy seguro de si hace falta el Android SDK o el Xcode para Mac al construir aplicaciones, pues la versión de prueba no me permitía hacer el build. El precio de la licencia es $199/año para solo una plataforma, o $349/año si es para IOS/Android juntas. Aquí puedes ver el showcase de aplicaciones y algunos videos bastante impresionantes.

Ventajas:

  1. Motor gráfico y físico ideal para juegos.
  2. Lua es un lenguaje bastante sencillo.
  3. Buena documentación, ejemplos y plantillas.

Desventajas:

  1. El precio de la licencia anual.
  2. Aunque se puede usar para cualquier cosa, realmente es ideal para aplicaciones gráficas y juegos al incluir motor físico.

Mis conclusiones

DISCLAIMER: A partir de aquí, todo lo escrito es mi opinión personal y, como tal, puede no coincidir con la de nadie. Así que, ¡no te pongas furioso si lees algo con lo que no estás de acuerdo! siempre puedes escribir un comentario explicando tu propia visión. Sobre mí, decir que tengo experiencia con Flex 3, conozco ActionScript y llevo años (muchos) con Java, HTML y JavaScript, así que es posible que para mi sea una buena elección Adobe Air, mientras que para otras personas no lo será nunca. De todas formas, lo justifico y lo explico detalladamente. Bueno, allá vamos:

Lenguajes

Empezamos hablando sobre el lenguaje de cada una de estas herramientas. Tenemos ActionScript, JavaScript y Lua. Sobre Lua no diré nada: no lo conozco, y dado que Corona está especialmente diseñado para hacer juegos, si tuviera que hacer uno, aprendería Lua y usaría Corona sin pensarlo. Por lo que he visto de Lua, es orientado a objetos (herencia basada en prototipos), imperativo pero con rasgos funcionales y de tipado dinámico y débil, así que concluyo que es muy parecido a Javascript, por lo que no debería haber problemas en hacerse con él en poco tiempo. Pero si no tengo que hacer juegos o una aplicación gráfica, no me molestaría en probar Lua.

Si tengo que elegir entre ActionScript y JavaScript como lenguaje de programación, me quedo con ActionScript. Los dos comparten la misma sintaxis, pues provienen de ECMAScript ¿Diferencias? JavaScript es tipado dinámico débil, y ActionScript es tipado estático fuerte. Los dos son orientados a objetos, pero la herencia en JavaScript está basada en prototipos y la de ActionScript está basada en clases. ¿Todo esto en qué concluye? pues que JavaScript es un lenguaje fantástico y me encanta, pero a la hora de desarrollar una gran aplicación con muchos componentes es más difícil (en mi opinión) estructurarla con JavaScript que con ActionScript, debido a su herencia basada en clases principalmente. Con Javascript tengo que usar trucos y patrones para simular namespaces y otras cosas que ActionScript me da directamente. Por esta razón, mi preferido es Flex frente a Appcelerator y PhoneGap, que usan exclusivamente JavaScript.

Maquetación y layaout

Sobre el sistema de maquetación y diseño de vistas. Appcelerator y Corona no tienen, hay que codificar y colocar los componentes a mano, así que cero puntos para ellos. PhoneGap es HTML5/CSS3 y Flex tiene MXML y su herramienta Flash Builder con un editor visual muy potente. Aquí sobre gustos los colores, pero yo lo tengo claro: prefiero MXML pues su sistema de componentes está especialmente pensado para crear aplicaciones RIA, mientras que HTML5 no deja de ser una evolución más de HTML y está orientado a la creación de documentos. Aunque la creación de RIAs en HTML es perfectamente posible, no deja de ser complicada, pues consiste en un repertorio de trucos con enlaces, formularios, Ajax con los que manipular del DOM y requiere bastante pericia. En este sentido, la jerarquía de componentes de Flex 4, Spark, es imbatible, además de que cada MXML se compila y se transforma en código ActionScript, por lo que nunca tendremos un MXML mal formado, cosa que si puede suceder en HTML. De nuevo, me quedo con Flex 4 primero, y PhoneGap después.

Entorno de desarrollo

Lua no tiene. PhoneGap es una plantilla para trabajar con Eclipse para Android; y con Xcode para Iphone, pero nadie te impide crear tu HTML y Javascript con tu editor favorito. Appcelerator tiene Titanium Studio, que es un Eclipse con el que poder probar tus aplicaciones y que, además, detecta errores en el código Javascript, lo cual se agradece muchísimo. Flex tiene Flash Builder, un entorno basado en Eclipse también muy avanzado y potente. Aquí el ganador es Flash Builer, pues Adobe ha invertido mucho tiempo y esfuerzo en crear un IDE realmente adaptado para incrementar la velocidad de desarrollo en Flex, lleno de wizards, editores y con depuración, sin embargo, es de pago y es bastante caro ($699). El resto son gratuitos, así que dejaría como vencedor segundo vencedor a Titanium Studio.

Componentes

Appcelerator y Corona son los únicos que permiten crear controles nativos de cada plataforma. PhoneGap usa los controles de los formularios de HTML y nada más, así que hay que utilizar un framework como jQuery Mobile, Sencha Touch, etc. Y Flex tiene su propio sistema de componentes. Los ganadores son sin duda Appcelerator y Corona por permitir crear controles nativos. Seguido de Flex con su propio sistema que es muy bueno, pero no deja de ser una implementación propia que hace que a la hora de usarlo se note menos fluidez; empatado, para mi gusto, con PhoneGap y sus frameworks externos a gusto del programador.

Rendimiento

Tenemos que tener en cuenta que los móviles son ordenadores en miniatura y, aunque son potentes, hay todavía algunos con procesadores lentos y poca memoria. Por esta razón, Appcelerator, al usar controles nativos, es el que más suavidad proporciona en la interfaz, seguido de PhoneGap, que la simula con HTML5. Por último tenemos a Flex, que aunque es rápido, no tiene la misma suavidad.

Comunidad, documentación, fiabilidad

Appcelerator y PhoneGap son herramientas jóvenes que todavía les falta para estar acabadas: sus apis cambian, están incompletas y a veces fallan y la documentación es regular. Flex lleva muchos años y tiene a Adobe por detrás trabajando duro, y todos sabemos que es un compañía que hace las cosas muy bien. Y la documentación de Adobe es completa. Me quedo con Flex sin duda.

Distribución

Appcelerator y PhoneGap requieren tener un Mac y pelearse con Xcode para crear aplicaciones IOS. Con Android no hay problemas, pues usan el SDK que es Java y es multiplataforma. Flash Builder permite crear directamente aplicaciones para Iphone, Android y BlackBerry Tablet OS sin usar Xcode ni el SDK Android y en solo un par de pasos: elegir el certificado y creas la aplicación. Me quedo con Flash Builder.

Precio

Appcelerator y PhoneGap son gratuitos, solo hay que pagar para el soporte formación. Flash Builder 4.5 Premium cuesta $699, aunque se puede usar la versión de prueba durante 30 días y hay SDK libres. Corona es gratis para desarrollo, pero requiere pagar una licencia anual de $199 o $349 si quieres subir tus aplicaciones al App Store o Market de Android. Si tienes dinero y no te importa pagar (o te lo paga tu empresa), entonces no importa, sino, las únicas opciones son Appcelerator y PhoneGap.

Vale muy bien, ¿pero cuál elijo?

Yo lo tengo claro. Suponiendo que no voy a hacer un juego (solo Corona) y que quiero hacer una aplicación solo para Iphone y Android (si quiero Symbian, WebOS, WP7 me tengo que ir PhoneGap), primero miraría que características del móvil que necesito usar y ver en que medida están soportadas por cada una de las herramientas en la documentación.

Si no necesito ninguna característica especial o en caso de que esté soportada por todas entonces mediría la complejidad potencial de la aplicación por un lado y por otro la velocidad/suavidad de la interfaz que me gustaría tener. Evaluado esto, usaría Flex si veo que merece la pena perder la suavidad de Appcelerator o PhoneGap con tal de tener una aplicación bien diseñada, robusta y que sé con certeza que usa Apis que no van a fallar. No es que Appcelerator y Phonegap sean un mar de bugs, pero no pueden competir con la fiabilidad y acabado que Adobe ha conseguido con Flex. Si veo que me puedo permitir el lujo de meterme con un diseño peor o simplemente es una aplicación más sencilla, o no me importa usar Apis que pudieran estar incompletas, usaría Appcelerator o PhoneGap. Entre estos dos, tenemos que recordar que PhoneGap es HTML5/CSS con Javascript, y Appelerator es Javascript puro con controles nativos. Así que evaluaría si quiero tener un acabado nativo con Appcelerator a costa de no disponer de un diseño visual, y tener que probar la aplicación siempre en el simulador. Pero si prefiero un diseño visual de las vistas, que además puedo probar en el navegador de mi ordenador, usaría PhoneGap con un framework mobile que además me garantiza que se va a ver exactamente igual en cualquier dispositivo.
Y para acabar, me quedo con esta frase de Jano que puso en un comentario en el primer post de la serie:

  • “Al usar estos frameworks multiplataforma hay que tener en cuenta que tu inversión en aprendizaje depende de la suerte de sus respectivos fabricantes, es toda una apuesta.”

Guía rápida para programadores con prisa

Si tienes que elegir una plataforma para YA y no no te quieres leer todos los posts, no te culpo. Aquí pongo una serie de reglas personales que yo aplicaría si tuviera que elegir.

  • Solo usaría Corona exclusivamente para hacer juegos o aplicaciones gráficas.
  • Para alguien que sepa Javascript y no sepa o no quiera aprender Flex ni ActionScript, la opción de Adobe Air/Flex queda descartada.
  • Si tengo prisa o el proyecto es muy simple y elemental, usaría PhoneGap, ya que la interfaz es HTML y me permite maquetar muy rápido usando una plantilla.
  • Si no tengo Mac y quiero desarrollar aplicaciones IOS, usaría Adobe Air ya que es el único que me permite crear aplicaciones IOS sin Mac. Aunque también podría virtualizar un Mac OS, o pediría prestado/compraría un Mac :)
  • Si necesito hacer una aplicación para muchos dispositivos móviles (como Symbian, Palm, WebOS) con la máxima compatibilidad entre ellos, usaría PhoneGap, ya que el uso de HTML5 me garantiza que se verá igual en todos los dispositivos.
  • Si tuviera que hacer una aplicación realmente compleja y grande (de lógica de negocio me refiero), usaría Flex, ya que ActionScript me permite una mejor estructuración de mi código.
  • Si quiero que el rendimiento sea el más rápido, no usaría Flex, sino PhoneGap o Appcelerator. Y si quiero controles nativos, usaría Appcelerator.

Y aquí una tabla-resumen con las características de cada sistema y plataforma (no incluyo todas las de PhoneGap, solo las que he creído más importantes). He coloreado en rojo lo que me parece un punto negativo, en amarillo lo que es neutral y en verde lo positivo. De nuevo, es mi opinión, y puede no coincidir con la de nadie.

Y nada más. Voy a seguir probando todas las herramientas en profundidad y espero acabar haciendo un ejemplo de una aplicación real, más allá del hola mundo, en cada una de ellas. Aunque me consta que ya hay quien lo ha hecho: los chicos de Tidyslice han hecho la misma aplicación para todas estas plataformas y está en un repositorio Github público: litros de a litro. Si quieres que haga un tutorial usando alguna herramienta específica, escríbeme un comentario (¡todo feedback es bienvenido!)

Si todavía te han quedado ganas de leer, aquí pongo algunos enlaces, ¡pero no te acuestes tarde!

Categorías

-moz-border-radius: 4px; -ms-border-radius: 4px; -webkit-border-radius: 4px; border-radius: 4px;