Mostrando entradas con la etiqueta uml. Mostrar todas las entradas
Mostrando entradas con la etiqueta uml. Mostrar todas las entradas

Haciendo un juego en Android: lecciones aprendidas (2)

Después del primer capítulo que publiqué hace unas semanas, ya tenemos lo básico para hacer un juego en Android "a pelo". ¡Pero no nos conformamos con eso! Hoy contaré otras cosillas más que fui añadiendo a mi librería personal para el juego según iba avanzando.

Componentes gráficos

Para los que hemos trabajado con interfaces de ventanas, el diseño basado en componentes gráficos resulta muy natural. ¿Por qué no aplicarlo también a un juego?.

La idea es muy sencilla: hacer que el código de cada componente no tenga que preocuparse de coordenadas globales, ni de lo que ocurre en el resto del juego, sino que vive "en su propio mundo" y preocupado de sus cosas. Orientación a objetos pura, vaya. Así, la pantalla se va formando a base de componentes, unos dentro de otros, que acaban conformando todo lo que hay en el juego. Cada uno tiene su propia dinámica, y luego pueden reaccionar ante lo que hagan los otros.

Todas estas librerías tradicionales de componentes de ventanas, como las MVC del Visual C++, o la del Borland C++, o incluso las del más moderno SWT suelen tener un problema gordo en su diseño: abusan mucho de las subclases. Se puso de moda diseñar esto así, y como documentación de la librería aparecía, tachán!, ¡la jerarquía de clases!. O sea, todo el diseño estaba hecho en forma de jerarquía de subclasificación.

El caso es que a mi las subclases en general me dan un poco de urticaria. Los interfaces son mucho más limpios. Las subclases me gustan para reutilizar implementaciones por defecto o sencillas de los interfaces, pero poco más. Así que en lugar de hacer una jerarquía de clases como las de toda la vida (curiosamente Swing, con todo lo que se le ha criticado, está mucho mejor diseñada que todas las demás), me dediqué a darle unas vueltas más (y liar un poquito el tema, ya puestos).

¿Qué necesita cada uno de estos componentes?. Según vimos en la entrada anterior, lo mínimo que necesita cualquier entidad independiente del juego es:
  • Que sea capaz de reaccionar a eventos
  • Que sea capaz de actualizarse sola por cada unidad de tiempo
  • Que sea capaz de dibujarse
Además de esto, nuestros componentes gráficos necesitarán:
  • Una posición relativa a su componente padre
  • Un tamaño
Y por supuesto, necesitaremos entidades contenedoras, a las que podamos meter otras entidades dentro de forma relativa.

Así que hice un interfaz GameEntity para lo básico que tienen que tener todas las entidades de cualquier juego (los tres primeros puntos), y reservé dos interfaces para los otros dos: un Positionable y un Measurable. ¿Por qué usar interfaces para estos dos en lugar de beans?. Porque me puede dar más juego. Por supuesto, creo implementaciones beans que implementen esos interfaces, y un DelegateComponent que cree un componente a partir de una composición de objetos que implementen cada interfaz (y que no tienen por qué ser objetos distintos).

Una vez que te acostumbras a esta forma de trabajar, resulta comodísima, y sobre todo, muy potente. Este es el diseño resultante:




Animaciones, efectos y el estado

Cada objeto normalmente tendrá su estado e irá cambiando según las cosas que le ocurran. Eso ya dependerá de cada entidad. Sin embargo, hay algo que también es muy interesante facilitar, que son las animaciones y efectos. Aunque pueda confundirse, lo cierto es que estos se gestionan mucho mejor si se separan un poco del propio estado del objeto.

Por ejemplo, un personaje puede estar caminando. Su estado es "caminando". Sin que el estado cambie demasiado, se estará moviendo hacia la derecha. Y habrá una animación de caminar que irá cambiando sola cada X frames. Podríamos gestionar todo esto de forma explícita dentro de cada objeto, pero si lo facilitamos para que se declare desde el principio como funciona, nos evitaremos el tener que gestionar cosas incómodas como cuál es el frame actual que se debe mostrar.

Para eso, creé una clase AnimatedComponent, que decora cualquier otro componente. A este componente se le registrarán una serie de estados de transiciones o animaciones posibles, representadas con el interfaz Transition. Al registrar cada transición se indica cómo funciona: qué propiedades modificará, cada cuánto tiempo y qué ocurrirá cuando acabe (¿será cíclica?, ¿se quedará en el estado en el que estaba?, ¿desaparecerá el objeto?). Estas transiciones son las que se ocupan de hacer efectos y transiciones sin que el objeto tenga por qué preocuparse mucho por ello.

Como ejemplos de transiciones están las animaciones de gráficos, movimiento, efectos de "fade", cortinillas... A pesar del tinglado anterior del diseño de componentes por interfaces separados, lo cierto es que en la práctica casi todos los objetos utilizarán un bean para la posición. También bastantes para el tamaño, aunque no tantos, ya que muchas veces puede depender del tamaño de su bitmap o de su texto, por ejemplo. Por supuesto, también las animaciones es lógico que vayan con una propiedad explícita... en definitiva, una de las transiciones más sencillas que podemos hacer es que automáticamente se vaya incrementando o decrementando el valor de una propiedad del componente. De aquí surgió la clase BeanPropertyTransition. Para controlar de forma independiente a qué ritmo se va modificando el valor de la propiedad, se hace que utilice un interfaz IntPropertyChange, con una implementación sencilla que aumente el valor de la propiedad de forma lineal, es decir, de forma constante en el tiempo.

Así quedó el diseño:


Agrupando objetos "globales"

En las primeras versiones del juego, los constructores de los distintos objetos cada vez eran más grandes. Había una serie de objetos, digamos, prácticamente globales, que se podían utilizar casi en cualquier sitio.

Dos de ellos son clases que creé para encapsular, facilitar y/o mejorar el rendimiento de la reproducción de sonido: SoundManager para efectos de sonido, y Jukebox para música. A ellos se les suma el necesario Context de Android, con el que se cargan todos los recursos, y que face falta básicamente en... todos los sitios donde haga falta un recurso. Y otros que iban apareciendo y desapareciendo según se iba refinando el diseño, como por ejemplo una clase que facilita la carga de Bitmaps.

La solución en este caso es muy clara, encapsular todos estos objetos, que además son genéricos y valen para cualquier juego, en una sola clase GameContext, que permita acceder a ellos. Así, sólo será este objeto el que habrá que pasar a todos los demás (junto a otros objetos del propio juego que potencialmente también harán falta). Además, si en algún momento aparece otro objeto del sistema que nos venga bien de forma global, se puede añadir también sin tener que tocar todas las demás clases para que se lo pasen de unas a otras.

Esto parece una tontería, pero lo cierto es que si no se tiene un poco de cuidado, en un juego es muy fácil acabar con miles de objetitos que se van pasando a troche y moche de unos a otros, y eso se convierte en un caos. También hay que tener mucho cuidado de no mezclar churras con merinas, y no encapsular dentro del mismo objeto todo lo global que pueda necesitar el juego. Una cosa son estos objetos que pueden hacer falta en cualquier juego y son, digamos, del sistema y sus recursos, y otra muy distinta meter también por ejemplo los objetos que controlen la dinámica del juego concreto.

En nuestro caso, este es el diseño que quedó:



Acabando, que es gerundio

¡Buah, menudo coñazo de serie que me he marcao! Todavía el primer capítulo tenía su aquel, pero en este me he metido más bien en detalles de diseño e imagino que ha quedado bastante espeso. Y no he puesto chistes ni chorradinas ni ná. Así que nadie estará leyendo esto ya, ¡snif!.

El mensaje fundamental de todo esto es el primero que di: si estáis dudando en poneros o no a hacer un juego con Android, no lo dudeis: hacedlo. Es muy agradecido, con poco que hagáis van saliendo cosas. La librería de Android es genial, da muchas facilidades. Es más, yo hay cosas en las que me he complicado la vida porque he querido, porque lo cierto es que creo que algunas de las cosas que he hecho yo están ya en la propia librería estándar de Android. Y eso por no hablar del resto de librerías que existen para hacer juegos. Simplemente, me apeteció hacerlo así. Y ojo, mientras refinaba un poco la librería me dio tiempo a hacer otro juego, un Reversi (este ya no lo voy a publicar, porque Reversis hay montones y seguro que juegan mejor que el mío, que es un pobre patán...).

El secreto de todo esto de hacer juegos para móviles, lo que lo hace genial, es que es pequeño. No necesitas dedicar meses y meses de tu vida para hacer un juego. En una tarde ya ves avances. Cada tarde que te pongas haces algo, consigues algo nuevo. Es muy gratificante. Estoy convencido de que los juegos pequeños tienen su espacio, que no todo tienen que ser grandes producciones. Dan más pie a la imaginación, a la innovación, a atreverse a hacer cosas que pensabas que no iban a salir... en definitiva, ¿a qué estás esperando?, ¡¡¡ponte ya a hacer tu juego, leche!!!.



Bola extra

Has intentado entender el artículo. Realmente lo has intentado. Te lo has leído de principio a fin, has imprimido los diagramas en grande para seguir las flechitas, has buscado por ahí a ver qué leche es esa mierda de los componentes gráficos... y eso sólo te ha desorientado más aún. Finalmente, has llegado a una conclusión ineludible: ¡no hay dios que entienda este artículo!

Pues bien, aprovechando que hoy es el "día Somos", que propone darle la vuelta al mundo, he apoyado la causa y he hecho una nueva versión del mismo en la que le he dado la vuelta por completo. Jamás la Informática había sido tan sencilla, jamás algo complejo se había visto de una forma tan clara. Jamás había sido tan fácil conseguir que cualquier no iniciado puede comprenderlo con facilidad.

No te pierdas la versión revisada del artículo. De repente verás la luz y lo comprenderás en toda su extensión.

Haciendo un juego en Android: lecciones aprendidas (1)

Uno de los principales motivos por los que hace unos meses me puse con el desarrollo del West Bank es porque tenía ganas de empezar a aprender algo de Android. Android te permite algo que ya se había perdido un poco, y es que puedas hacer una aplicación más o menos pequeña, pero que a la vez esa aplicación sea publicable e incluso sea útil. Y en algunos casos, hasta te haga ganar dinero. Para los que no tenemos casi tiempo libre eso es genial, claro. En este artículo cuento un poco algunas de las cosas que me fui encontrando y lecciones aprendidas..

Hacer juegos con Android es muy fácil. Incluso sin librerías

Esta es seguramente la conclusión más importante que he sacado, algo que por otra parte ya me imaginaba. En mi caso, comencé primero buscando un poco alguna librería / framework / engine de desarrollo de juegos que me facilitara la vida. La idea es que fuera una librería sencillita y enfocada al 2D, nada de polígonos ni historias. La primera que vi fue Rokon, que tenía buena pinta y encajaba en lo que buscaba, pero el autor recomendaba no usarla (imagino que acabó cansado de responder emails de gente haciéndole preguntas). Como el objetivo era complicarse la vida lo menos posible y hacer el juego rápido, la descarté.

Entonces miré libgdx, que es una librería multiplataforma. Eso es una ventaja evidente, pero a la vez para este caso también es una desventaja, porque el objetivo con el que empecé era aprender algo de Android. Cuando me di cuenta de esto, y llevándome eso a descartar sin apenas mirarlas otras librerías con muy buena pinta como AndEngine, pensé: "¿Y si lo hago directamente usando las librerías nativas de Android?".

Buscando por Internet encontré este buen tutorial de introducción de JavaCodeGeeks. Y sólo mirándolo por encima ya vi que Android de serie te da enormes facilidades para manejar gráficos, sonidos y eventos táctiles. Por otra parte, para el juego que nos ocupa no iba a necesitar ningún tipo de aceleración gráfica OpenGL ni otras mandangas de optimizaciones. Así que decidido: no usaría ninguna librería, haría la mía propia basada en la librería estándar de Android.

Gráficos en pantallas con distintas resoluciones

Esto ha sido seguramente lo que más quebraderos de cabeza me ha dado. Y lo cierto es que si lo entiendes bien y no te metes en líos, tampoco es algo que tenga por qué darte problemas. En Android a priori no sabes cuál es el tamaño de la pantalla, ni tampoco sus proporciones (screen ratio), y tienes que intentar que en todas ellas el juego se vea lo mejor posible.

Esto implica redimensionamiento de tres elementos: los gráficos, las posiciones en los que pintas los gráficos y los puntos en los que se detectan los eventos táctiles. De primeras solucioné esto creando una clase (más bien cutre) PointTranslator, con un objeto que le pasaba a todos los demás objetos del juego y al que llamaba cada vez que quería traducir una posición o pintar un bitmap.

Para solucionar el problema de las proporciones de pantalla puse unas franjas negras en la parte que sobrara de la pantalla. Otra solución hubiera sido alargar el bitmap, pero prefería no distorsionar las imágenes. Mi resolución estaba clara, como utilizaba directamente los gráficos del ZX Spectrum, la suya sería la resolución que iba a usar: 256x192 (con proporción 3:4).

Cuando ya tenía algo que pintaba los gráficos y detectaba las pulsaciones en su sitio, me di cuenta de otro problema: según el móvil en el que cargabas los bitmaps, ¡el propio bitmap podía tener un tamaño diferente al real!. Por lo que vi, esto dependía de otro parámetro: la densidad. Esto afecta sobre todo a la carga de sprites. Una de las típicas técnicas en los juegos es meter todos los gráficos de un personaje en un solo fichero. Al dibujar uno de ellos, se dibuja sólo la parte del gráfico que corresponda. Al no tener el bitmap el tamaño que se suponía iba a tener, esto se desmadraba. Ale, otra chapuza a meter al "soluciones todo-en-uno" PointTranslator.

Otro problema que tuve con esto fue el del redondeo. Normalmente los bitmaps se cargaban en su tamaño o en un tamaño múltiplo. Pero, ¡ay!, no siempre es así. Concretamente, el Nexus 7 carga los bitmaps en una escala extraña. Esto hace que en bitmaps con muchos gráficos, el redondeo fuera importante y no se cargaran bien. Concretamente, el problema era muy evidente en las fuentes bitmap que usaba para escribir los textos. Vuelvo a tocar (¡cómo no!) ese maravilloso PointTranslator para que use coma flotante para estas cosas y listo.

Lo cierto es que estos dos últimos problemas: carga de bitmaps y redondeo no tienen por qué existir. Los cuento porque creo que puede ser interesante saberlo, pero la verdad es que desde el principio fue por una metedura de pata mía. Como todo el proyecto lo he ido haciendo sobre la marcha y sin documentarme debidamente, ya que lo más importante era ir avanzando y obtener cosas funcionando lo más rápido posible (doctor, ¿me habré hecho "ágil"?), cuando probé a meter el primer gráfico lo hice en la carpeta "res\drawable-mdpi". En mi ignorancia, pensaba que estas carpetas ldpi, mdpi, hdpi servirían para cargar gráficos más o menos grandes según la resolución de tu dispositivo, y que si sólo encontraba un gráfico en una carpeta cargaría ese y ya está. Craso error. Lo primero es que no es eso lo que significan esas carpetas, no funcionan según la resolución de pantalla (en cuanto a número de pixels), sino en cuanto a la densidad, es decir, los puntos por pulgada del dispositivo. Es decir, el objetivo de usar esto es, por ejemplo, que se vea más o menos igual de grande (en cuanto a tamaño físico) un gráfico cargado en un tablet que cargado en un móvil con resolución alta o en otro con resolución baja. En definitiva, nada que a priori nos interese ni lo más mínimo cuando estamos haciendo un juego. Para más información, tenéis el artículo oficial sobre el tema (un poco ladrillete, pero es que esto es así...). ¿La solución a esto?. Muy fácil, una de estas:

  • Meter los gráficos en una carpeta drawable-nodpi
  • Si queremos cargar diferentes gráficos según la resolución de pantalla, usar carpetas de recursos drawable que dependan del tamaño en píxeles: small, normal, large, xlarge
  • Meter los gráficos como assets en lugar de como resources. Esto, además, nos permite componer de forma dinámica el nombre del bitmap a cargar, o de su carpeta

Esto último es por lo que me he decantado yo, ya que quiero cargar distintos gráficos según la configuración del juego: versión ZX Spectrum, o nueva versión con gráficos HD... ¡pronto en sus kioskos!.

De hecho, esto último me ocasiona un nuevo problema: los gráficos del juego tendrán distintos tamaños, posiciones y hasta resoluciones diferentes según su configuración. Esto me ha llevado a hacer una refactorización, eliminando alguna chapucilla y mejorando el diseño de las clases para facilitar esto. Y sí, tuve que tomar la difícil decisión de eliminar mi clase-bombero... es decir... el PointTranslator ha muerto (¡viva el PointTranslator!).

Por lo pronto, en lugar de redimensionar todos los puntos sobre la marcha dentro del propio juego, ahora lo que hago es pintar todo en un buffer intermedio, que tiene el tamaño exacto de la resolución del juego. Al terminar de dibujar todo, se pinta redimensionado en el buffer de pantalla. Para encapsular todo esto he creado una clase Camera. Cada vez que el controlador del juego quiere pedir a los objetos que se dibujen, llama a la cámara para que devuelva el Canvas (la clase estándar Android donde se pintan cosas) bien configurado. Al terminar todos de dibujar, vuelve a llamarle para que lo plasme. De esta forma, toda la complejidad del proceso se queda en las clases genéricas de mi librería, y las clases específicas del juego se despreocupan totalmente de los problemas de resolución, ellas trabajan siempre con las dimensiones que marque el juego y ya está.

Todavía me he encontrado con un problema, que ha sido a la hora de traducir las posiciones de los eventos táctiles. Para estas cosas, Android tiene una clase, MotionEvent, que la verdad es que me gusta bastante tal cual está y por tanto no tenía intención de crear otra nueva para lo mismo. Pues bien, resulta que la clase MotionEvent no permite reescalar ni modificar sus posiciones. Bueno, a partir de la versión 11 del API sí, pero quiero que el juego funcione en versiones de Android más antiguas. Lo solucioné creando mi propia clase GameMotionEvent, que utiliza el patrón de diseño delegate para encapsular la clase anterior, proporcionando un interfaz igual y llamando en cada método al equivalente a la anterior, haciendo las pertinentes traducciones de puntos cuando hagan falta a través de la clase Camera.

El bucle principal: con cuatro cosillas lo haces, ¡pero cuidado!

Como bien sabrás si has hecho un juego alguna vez, el bucle principal (el game loop) supone la estructura fundamental de cualquier juego. Es un bucle "infinito" de tres pasos: procesar entrada, actualizar estado de los objetos, dibujarlos (aunque en realidad lo de procesar la entrada no tiene por qué ir estrictamente en ese orden y se puede hacer más bien en forma de eventos). Y hacerlo además de forma que cada ciclo se haga en el mismo tiempo, gestionando condiciones como que si tardamos más tiempo de lo que deberíamos en hacer un ciclo, nos podamos saltar pasos de dibujar para que todo vaya lo más suave posible, y si tardamos menos, esperemos un ratito hasta que quede cuadrado. En realidad todo esto está muy bien explicado en el tutorial que enlacé antes, especialmente en un capítulo en el que se da un bucle básico y en otro posterior donde se mejora la implementación. Como digo en el título, hacer un bucle principal es muy sencillo teniendo en cuenta cuatro cosas que son muy conocidas y que este tutorial cuenta muy bien. Pero ojo, ¡es muy importante tenerlas en cuenta!.

Lo que no me gusta tanto del tutorial es el diseño de clases que se da en su implementación, ya que me parece bastante "sucio", haciendo que la clase principal herede directamente de Thread y obligando además al que la use a extender directamente esta misma clase para poder hacer una implementación para un juego concreto. Así que decidí cambiar este diseño y hacer uno más genérico, con una clase limpia GameController que se pueda invocar fácilmente desde el Activity y que use el thread de forma interna. Incorporé las clases Camera y GameMotionEvent que he contado en el punto anterior, para el escalado de los gráficos. Y para permitir la implementación concreta de los objetos creé primero un interface básico GameEntity, con lo mínimo que se necesita para pintar un objeto y que reaccione a eventos. Luego creé otro extendido Scenario, que me pareció interesante para facilitar la creación de "partes" del juego completamente diferenciadas entre sí.

Este es el diseño que quedó:



Continuará...

Y hasta aquí por hoy mis historias programando el juego en Android. Haré un capítulo más, en el que contaré otros refinamientos que he hecho en el diseño, sobre todo para poder tener un sistema sencillo de componentes gráficos y otras cosas como animaciones y efectos. En breve aquí mismito.


Java y el UML maldito

En la entrada de hace unas semanas, En busca del UML perdido, contaba cómo apareció el diseño orientado a objetos, por qué es bueno, y por qué el UML abrió un camino importante para su popularización. Y dejaba en el aire que aunque la programación orientada a objetos sí había triunfado con los años, sin embargo el diseño orientado a objetos no lo había hecho... ¿por qué?.

Voy a centrarme en Java, porque al fin y al cabo en general ha sido el lenguaje dominante durante estos años (lo de que además haya sido el lenguaje que he estado usando yo durante este tiempo es una casualidad nimia y sin importancia, claro).

Los profesores de Ingeniería del Software nos decían (y supongo que nos seguirán diciendo) que el lenguaje final en el que se fuera a programar no importaba, que el diseño se tenía que abstraer de eso y ser genérico, y que luego ya se preocuparía otro de programar en el lenguaje de programación que fuera lo que se había diseñado.

Así, las primeras herramientas UML que aparecieron, entre las que destacaba el Rational Rose (que para eso se había gastado Rational el dinero en fomentar la creación de UML), eran herramientas orientadas fundamentalmente a la documentación. Es decir, el producto que se obtenía al usarlas era un documento, que era el resultado del proceso de diseño, y que se entregaría al programador para que lo siguiera como guía al programar.

¿Qué pasó?. Pues que eso de que el lenguaje final no importe es la primera gran mentira del diseño. Vale, es verdad que eso es posible, pero sencillamente no es práctico.

Para empezar, si tenemos ya un diseño de clases, nos conviene que la herramienta sea capaz de generarnos el código Java a partir del diseño, porque eso nos va a quitar un montón de trabajo absurdo y aburrido. Así surgió la generación de código. Tanto el Rational Rose como otros productos por el estilo que había entonces, como el Paradigm, empezaron a implantar la generación de código en C++... y por supuesto también en Java.


Pero no bastaba con eso. Hay otro problema importante, que se presenta porque como ya comenté el planteamiento del modelo en cascada no suele ser realista. Lo recordamos: el analista Calculín hace el diseño en UML sin pensar en cómo se va a programar, y luego el programador-fontanero Mario (Bros., of course) lo tiene que convertir en Java y preocuparse porque todo eso funcione. Al final Mario Bros. acaba modificando cosas sobre el diseño inicial, porque según se va profundizando en el desarrollo de la aplicación, se van aprendiendo más cosas sobre ella, cosas que pueden afectar mucho al diseño. ¿Qué pasa entonces con ese diseño?. ¿Se tiene que modificar también, según cambiamos cosas en el programa?. ¿Vamos a hacer trabajo doble?. Qué coñazo, ¿no?.

Las herramientas entonces empezaron a implantar otra funcionalidad interesante. Como somos capaces de generar Java a partir de UML, si luego algo cambia en Java... hagamos una función que modifique automáticamente el diseño UML a partir de las modificaciones hechas en Java. A eso le llamaron con el bonito nombre de ingeniería inversa ("reverse engineering").

Todo esto de diseñar en UML - generar Java - modificar Java - volver a UML - volver a Java, etc. al final era, la verdad, un auténtico cacao. Cacao incrementado además porque cada cosa se hacía en una herramienta distinta. A algún comercial de mente despierta se le ocurrió arreglar el problema, ¡cómo no!... poniéndole un nombre chulo al tema (una de las grandes soluciones en todo manual del buen comercial). Así surgio, ¡ta-chán!, la... ¡¡¡round-trip engineering!!! (nuestro traductor a español dimitió el mismo día en el que le dijeron que tenía que traducir algo así y lo único que se le ocurrió fue llamarlo "ingeniería de rosquilla"). Si alguna vez, querido lector, has intentado trabajar en la sincronización bidireccional de dos sistemas que se pueden actualizar por separado, sin duda sabrás que la palabra que mejor puede definir eso es que es un infierno. Se le puede añadir algún calificativo, como el típico "fucking" si hablamos en inglés, o cualquiera de los múltiples equivalentes que existen en el rico castellano, pero vamos, creo que se entiende el tema. Es realmente complicado saber qué lado se modificó antes, qué lado manda, asegurarte de que al generar hacia un lado no se pierde nada que ya teníamos hecho, etc. Es complicado de programar, pero también es complicado de mantener y complicado de entender.

En ese momento, a alguien se le enciende una bombilla en la cabeza... y si nos dejamos de complicar la vida, y más importante aún, dejamos de complicársela al pobre programador... ¿¿¿y si hacemos que haya un sólo modelo???. O sea, ¿y si hacemos que el modelo UML esté representado con el propio código Java?. Creas una clase en Java, la tienes en UML. Modificas algo en un lado, lo tienes en el otro sin tener que hacer nada. Creas una clase en UML, la tienes en Java. No tienes que ejecutar una ingeniería directa ni una ingeniería inversa. Más importante aún, no tienes que ejecutar una "rosquilla" de esas chungas. Sobre todo, eliminamos de un plumazo todas las dificultades que conlleva una sincronización bidireccional. Lo que vemos en UML es lo mismo que vemos en Java. Modificamos cada vez lo que nos sea más cómodo en ese momento. Así surgió una herramienta llamada Together.

Together era una gran herramienta de desarrollo. Podías editar Java a la vez que editabas UML. No se consideraba que el diseño era una cosa distinta a lo que se programaba, se consideraba que el diseño era una vista distinta del mismo modelo. Los ficheros Java contenían el modelo de clases UML completo. Cuando hacías un diagrama, el diagrama se guardaba en un fichero aparte, pero sólo se guardaba el diseño del diagrama, es decir, el modelo final era el modelo Java.


Si sabes un poco de UML y de Java te puedes dar cuenta de cuál es el problema principal. El modelo de clases de UML y el de Java son diferentes. Hay elementos que son idénticos en ambos casos, como puedan ser las clases, pero hay otros cuya correspondencia no es tan sencilla. Por ejemplo, asociaciones, composiciones, relaciones 1-N... Para esos casos, hay que definir un mapeo entre Java y UML. Pero ese mapeo es posible. Aún con eso, sigue habiendo información que nos puede resultar interesante en el diseño UML pero que en Java se pierda. Para eso Java tiene uno de esos elementos geniales que a alguien un día se le ocurrió crear aunque no dejen de ser un parche: las annotations (anotaciones). Vale, estamos hablando de finales de los 90, aún no existían las anotaciones, pero sí que existían en la forma de "comentarios Javadoc". Al fin y al cabo eran lo mismo. Pero en TogetherSoft, los creadores de la herramienta, también tenían sus comerciales molones, y rápidamente le pusieron un nombre a todo esto: le llamaron tecnología "LiveSource".

¿Por qué Together no llegó a triunfar si era una herramienta tan buena?. Lo primero de todo, como IDE no llegaba a ser tan bueno como un IDE de Java de los que existían entonces. En aquella época el IDE que triunfaba era sobre todo el Borland JBuilder, creado en el 95 y que estaba realmente bien. Eso era un peso importante, porque si tienes que elegir entre un "gran" IDE Java y un "buen" IDE Java que-además-tiene-UML, la decisión no es tan fácil. Además, en mi opinión había algunos problemas con el mapeo Java-UML, que al fin y al cabo es el problema fundamental. Algunas decisiones me parecían bastante discutibles.

Sin embargo, no creo que ninguno de ellos fuera el mayor problema. El mayor problema que se encontró Together fue puramente comercial. Por un lado, TogetherSoft no era una marca con peso suficiente como para hacer grandes campañas comerciales, no llegó a hacerse tan popular como llegó a ser el Rational Rose. Por otro, y más importante... Together nació en 1999 (o 1998, no estoy seguro)... y con el paso de los años hemos visto cómo surgió Netbeans primero (en 1999), y Eclipse después (la versión 1.0 es del 2001), y que al final fue el que se llevó el gato al agua y se convirtió en el IDE Java más usado. Ambas triunfaron no por ser mejores que JBuilder, sino porque mientras que esta era de pago, las otras dos eran -y siguen siendo- gratuitas. Ahora mismo, el modelo de sacar un producto gratuito para que domine el mercado y acabar teniendo ganancias por dar soporte a empresas y cursos se sabe que puede funcionar (siempre que el producto sea realmente bueno), pero por aquel entonces era difícil ver a compañías de desarrollo que apostaran por él. El caso es que Together era de pago. Es más, por lo que recuerdo era aparentemente cara, de esas que no dicen su precio en la web (sí, siempre usé una versión pirata...). ¿Por qué iba nadie a pagar por esa herramienta existiendo IDEs que son mejores para el desarrollo Java y que además son gratis?.

Rational Rose había llegado a tener cierta popularidad, es más, muchas empresas compraron licencias del producto, pero en el fondo no dejaba de ser un producto fallido desde el momento en el que se separaba el diseño del código. Era un gran producto académico, no tan bueno para el mundo real. Y costaba una pasta. Las nuevas versiones se centraban más en que hiciera cada vez más cosas, más que en que lo que hiciera lo hiciera bien y fuera práctico. La gente se cansó de él, y sólo las empresas que tenían que justificar la producción de un documento de diseño siguieron utilizándolo... y cada vez menos, pasándose en muchas ocasiones a herramientas con las que dibujar cajitas y diagramas, como Visio, Flowchart o sobre todo los mismísimos Word o Powerpoint. Together era un gran producto, pero nunca llegó a ser popular. Demasiado ambicioso en el momento equivocado. Como curiosidad, puedo añadir que Rational acabó siendo comprada por IBM (autores de Eclipse), y Together acabó siendo comprada por Borland (los del JBuilder, y la mejor empresa para desarrolladores hasta ese momento... a esa la mató el software libre, claro). Ambos productos siguen existiendo, pero... ¿conocéis a alguien que los use?.

Pasaron los años y el UML languideció. La cascada y un puñado de dólares lo sepultaron.

¿Qué hubiera pasado si el comercial de TogetherSoft en lugar de perder el tiempo pensando en nombres molones para su tecnología, hubiera ofrecido el producto gratis?. Estoy convencido de que la historia hubiera sido distinta, y ese tipo estaría ahora tumbado en una hamaca hecha de billetes de dólar (bueno, igual está así de todas formas, a saber). La herramienta seguramente habría seguido creciendo, pero en lugar de buscar motivos comerciales, habría buscado funcionar cada vez mejor. Como le pasó a Eclipse especialmente los primeros años. Y ahora UML estaría posiblemente en un trono. O no... (pero yo pienso que sí!).

Se puede decir que en los 2000, sin haber acabado nunca de despegar, UML se fue difuminando cada vez más. Se necesitaban nuevas herramientas. Comenzaron a aparecer "plugins" de UML para Netbeans y/o Eclipse, la mayor parte de ellos siguiendo el modelo de Rational, es decir: orientado a la documentación, y de pago. También surgió alguna herramienta de UML gratuita, como ArgoUML, pero por un lado en general seguían estando orientadas a la documentación, y por otro el acabado normalmente no era suficiente como para convencer a nadie para que las usara.

En el año 2002 (o quizá 2001), aparece un plugin para Eclipse llamado "EclipseUML" y creado por una empresa llamada Omondo. Las intenciones de Omondo eran muy buenas, el plugin es gratuito y siguen la filosofía "LiveSource" de Together, es decir, el modelo UML se guarda en las propias clases Java. Omondo rebautizó esta técnica como "live round-trip". Las primeras versiones no tenían tanta funcionalidad como tenía el Together, pero al estar integradas con Eclipse, el IDE de Java seguía siendo el mejor. Y lo cierto es que el editor de UML no estaba mal y cumplía muy bien con lo mínimo que se le podía pedir. ¿Esperanza?.

Pero todo se fue al traste en cuanto alguien se dio cuenta de que eso que estaban haciendo ellos, hasta ese momento se estaba cobrando. El dólar volvió a aparecer. Así, el producto empezó a ofrecer una versión gratuita y otra de pago. Una de las "pequeñas" diferencias entre ellas era... que la versión gratuita no se podía usar en proyectos que estuvieran bajo control de versiones, es decir, CVS, Subversion, etc... espera... ¿¿¿pero qué proyecto medianamente serio no está en control de versiones???. Si esto ya de por sí es casi casi volver a caer en los mismos errores del pasado (hay una ventaja importante, y es que seguimos dentro de Eclipse), de repente a los señores de Omondo se les ocurre que por si acaso eso no es suficiente para hundir del todo el producto, mejor darle un empujoncito. Así, en un momento dado por cada versión nueva del plugin que sacaban, cambiaban el formato de los ficheros de diagramas. Es decir, si cambiabas la versión del plugin, tenías que volver a dibujar todos los diagramas. Como además la versión del plugin estaba ligada a la versión de Eclipse, si querías actualizar Eclipse tenías que actualizar el plugin. Ni sé, ni quiero saber, qué habrá sido del plugin de Omondo. Sólo sé que cuando he visto alguna vez su página me ha parecido que volvían a haber cada vez más y más funcionalidades "avanzadas". ¿Repitiendo más errores del pasado?.



También ha habido alguna aproximación diferente, limitada pero interesante. Por ejemplo UMLGraph, que propone algo radicalmente distinto: en lugar de dibujar nosotros los diagramas, esta herramienta los generará automáticamente y los meterá dentro del JavaDoc. El modelo por tanto es también el modelo Java, usando también anotaciones para enriquecer la información UML. Esta aproximación me parece muy interesante como herramienta complementaria, pero no tanto como herramienta única.

Han aparecido también otros plugins gratuitos con la idea del LiveSource, como por ejemplo Green UML, pero el problema de este plugin es... el que dice el nombre, que está verde cual aceituna bañada en rayos gamma (aunque confiamos en que florezca y se convierta en una linda mariposilla). Otros plugins son gratuitos pero siguen con la idea del diseño como documento y la mandanga del roundtrip, como Papyrus ó el propio plugin oficial de Eclipse MDT / UML2.

Es frustrante, porque teniendo en cuenta la arquitectura modular de Eclipse y que tiene módulos para tanto para hacer fácilmente diagramas como para manejar el código fuente de Java a cualquier nivel, lo cierto es que hoy por hoy sería fácil hacer un editor de UML con LiveSource. Ahí tenemos el ejemplo del Green UML, ¡que surgió como un proyecto hecho por estudiantes en la Universidad! (no tengo claro si fue para un proyecto de fin de carrera o para una tesis, pero imagino que fue para algo así).

¿Está todo perdido?. ¿Caemos una y otra vez en los mismos errores?. ¿Qué es lo mínimo que debería tener una herramienta UML para que sea práctica al trabajar en proyectos Java?. ¿No existe ninguna ahora mismo que apunte a eso, todo son fracasos?. ¡Quiero usar una!. ¿Andrés, eres tan abuelo cebolleta que no eres capaz más que de hablar del pasado?. La respuesta a todas estas cuestiones, en la tercera parte de esta serie sobre UML, que como no podía ser de otra forma se llamará "UML y la última cruzada". En pocas semanas en este humilde blog.

En busca del UML perdido

Años 90. Se puede considerar que por aquel entonces, en esto del desarrollo, estábamos en la época de las metodologías. Mentes pensantes se estrujaban las meninges para dar con el método perfecto que pudiera llevar a cualquier mindundi que no supiera nada a hacer programas, qué digo a hacer programas, ¡a hacer programas mantenibles y bien hechos!. Alguno igual hasta suspiraba porque su metodología fuera tan perfecta que los propios ordenadores fueran capaz de hacer sus propios programas, ellos solitos, mientras el programador daba cuenta de una jarra de cerveza. Se pensaba que si la metodología era buena, el programa resultante tenía que ser bueno.

Durante muchos años, la "cascada" era el modelo dominante, dentro de esas metodologías. Primero un señor A pensaba qué tenía que hacer el programa, y lo escribía todo en un documento. Luego, otro señor B pensaba cómo se iba a hacer lo que indicaba el señor A, y lo escribía todo en otro documento. Luego, el señor C, también llamado "el pringadete de turno" tenía que hacer el programa como habían dicho los otros dos. En la práctica, en cuanto el señor C empezaba a meterse en faena, se empezaba a dar cuenta de que había cosas que no cuadraban, otras que se habían pasado por alto, otras que no se entendían, otras que no tenían sentido, etc. Resultado: el proyecto se retrasaba horrores, el señor C hacía horas como un campeón y le tocaba tomar decisiones que no le correspondían, y los maravillosos documentos y esquemas realizados por los señores A y B quedaban anticuados a las dos semanas de trabajo de C. Al final, al cliente se le daban unos tochos de documentación, que le importaban un pepino y ni se molestaba en mirarlos para darse cuenta de que no estaban actualizados, y un programa que (con un poco de suerte) funcionaba pero se acababa convirtiendo en algo imposible de mantener (lo que viene a llamarse efecto bola de nieve). En definitiva, este método normalmente no funcionaba. Es más, esto causaba un divorcio entre el mundo académico y el mundo real, que necesita soluciones prácticas y eficientes para llegar a una solución.

Dentro de esa vorágine, aparecía el paradigma de la programación orientada a objetos. La mayor gracia de los objetos es que facilitan manejar un concepto que ya estaba haciendo mucha falta manejar: la incertidumbre. Vale, supongamos que somos humanos. Supongamos que no conocemos bien lo que se necesita hacer, que el desarrollo conlleva que el programador vaya aprendiendo gradualmente qué es lo que realmente se necesita. Supongamos que además, no conocemos qué se va a necesitar en el futuro, como van a evolucionar las necesidades y cómo van a ir cambiando las cosas. Supongamos, incluso, que esto que hemos hecho podemos acabar utilizándolo como parte de otros programas más grandes, reutilizándolo. En ese caso, necesitamos ser capaces de hacer estructuras y componentes que sean lo más flexibles posible. Eso es precisamente lo que permitían los objetos.

Por supuesto, como buena época de metodología en la que estábamos, rápidamente aparecieron más mentes pensantes buscando el método mágico con el que crear programas orientados a objetos fuera tan fácil que pudiera hacerlo hasta un niño de 3 años dormido y con las manos atadas a la espalda (y peor aún, en Lunes). Es más, aprovechando el vacío existente, cada mente pensante se inventaba sus propios diagramas para hacer el diseño del programa, con cajitas, nubecitas, flechitas o lo que fuera. Un carajal, vaya.

Un buen día tres de esas mentes pensantes que tenían sus propios métodos y notaciones (Booch, Rumbaugh y Jacobson) hicieron equipo, con el simpático nombre de los "three amigos", y en 1997 crearon una notación única para hacer diseño orientado a objetos, con ánimo de convertirse en un estándar (como así fue) y de que encajara en cualquiera de los métodos mágicos existentes. Había nacido el UML. La importancia de esto es grande, ya que con el UML se separaba el lenguaje de modelado de la metodología que se siga para obtenerlo, y se definieron los conceptos que se iban a manejar en el diseño orientado a objetos. Si tú veías un diseño orientado a objetos hecho en UML lo entenderías, independientemente de cómo se hubiera llegado a ese diseño. Y un diseño hecho en UML te da una perspectiva buenísima de cómo se ha pensado un programa, cómo se ha estructurado, cómo se ha diseñado. Esa perspectiva funciona tanto para el que lo está diseñando, que según lo va haciendo lo va entendiendo mejor, como para el que tiene que entenderlo para  modificar el programa más adelante y hacerle cambios sin volverse loco. Ayuda a pensar, ayuda a hacer entender.

No penséis que esta colaboración de los "amigos" vino propiciada por el buen corazón de estos tres tipos. Si fuera así, quizá habrían contado con otros teóricos notables de la época, como Martin Fowler ó Erich Gamma, que además habían hecho contribuciones importantes al empezar a proponer algo distinto a oooootra metodología mágica más: fomentar el uso de patrones. Un patrón no es más que un ejemplo "bien hecho" de cómo resolver de forma adecuada un problema común. O sea, básicamente permite que aprendas a diseñar "copiando" otras soluciones. Por tanto, no llegas a una solución buena por un método mágico, sino que llegas a ella copiándola de otro caso parecido, y al hacerlo, la aprendes y la haces tuya. Es como el que en arquitectura inventó la puerta, o la escalera de caracol, y luego todos copiaron la idea, o como el que inventó una forma innovadora de pintar los paisajes en los cuadros. Eso funciona.

Como decía, el motivo detrás de esta gran amistad tenía que ver con una empresa que había visto el filón que se le presentaba: Rational. Esta empresa contrató a estos tres amigos para impulsar la venta del que lanzaron como su producto estrella: Rational Rose. Sospecho que la jugada les salió bastante bien durante varios años. Sé que aunque mucha gente lo pirateaba, también había muchas empresas que pagaron licencias del producto, y realmente se llegó a "poner de moda". Los "amigos" no se quedaron ahí, y como buenos metodólogos, rápidamente inventaron también una metodología "única": el Proceso Unificado. El Proceso Unificado iba más allá de la cascada, y promovía el desarrollo basado en "iteraciones", en las que cada iteración venía a ser una mini-cascada.

Ahora, volvamos al presente (no se vaya a convertir esto en otra entrada en plan "abuelo cebolleta"). Han pasado ya unos cuantos añitos y se puede decir que los objetos han triunfado, sobre todo gracias a Java. Sin embargo, aunque se programa mucho con objetos, pienso que cada vez se ven menos diseños con objetos. La propia palabra "diseño" se usa hoy en día fundamentalmente para el diseño gráfico, más que para el diseño de software.

¿En qué momento se empezó a perder el diseño, si gracias a UML ya teníamos lo mínimo necesario, que es una notación común, y herramientas capaces de crear esos diseños con facilidad, y hace un rato he hecho un canto a las maravillas del diseño con UML?. ¿Qué pasó?. ¿Realmente hemos perdido algo, era útil, o era más bien prescindible?. ¿Todavía podría convertirse en algo práctico?. ¿Cómo?.

Daré mi opinión sobre todo esto en la segunda parte de esta apasionante saga, con el título provisional de "Herramientas y el UML maldito". Manteneros atentos y tened paciencia. Al fin y al cabo, cuando la serie acabe puede que el UML os haga cambiar vuestra forma de programar...