Buscar
Social
Ofertas laborales ES
« Los brasileños hablan sobre Prevayler | Main | Arbol de proyectos de Apache actualizado »
lunes
ene032005

Aplicaciones de escritorio eficientes



Untitled Document






















Aplicaciones de escritorio eficientes


Martín Pérez Mariñán

martin@javahispano.or
g


 

A menudo sucede que un desarrollador o un grupo de desarrollo comienza a crear una aplicación de escritorio para un determinado cliente. Estas aplicaciones suelen denominarse clientes ricos. Con el paso del tiempo, y comúnmente con el acercamiento de las fechas de entrega de versiones finales, demasiado a menudo sucede que estos clientes ricos van derivando poco a poco en lo que antes se conocían, quizás injustamente, con el nombre de clientes pesados. Finalmente, también a menudo muchos proyectos serán abandonados, en opinión de los desarrolladores, a causa de los pesados de los clientes.

Sobre clientes ricos, clientes pesados, y pesados clientes


Hace tiempo, coincidiendo con el inicio del auge de las tecnologías web, apareció un nuevo modelo de aplicaciones en las cuales el interfaz de usuario se presentaba dentro de páginas web generadas dentro de algún tipo de servidor web o de aplicaciones. Este tipo de clientes pasó a llamarse clientes ligeros, haciendo referencia este adjetivo a algunas de las ventajas de este tipo de aplicaciones que hacían muy sencilla su ejecución y despliegue, como por ejemplo: no necesitar ningún tipo de instalación en el cliente ya que se ejecutaban en el navegador web del usuario, no consumir enormes cantidades de espacio en disco, el no incluir apenas lógica de procesamiento lo que hacía que aplicaciones que previamente necesitaban ingentes cantidades de memoria y de CPU se pudiesen ejecutar ahora en máquinas más precarias, etc. Evidentemente, si existían clientes ligeros, deberían existir también clientes pesados, y ese fue el adjetivo que le dedicaron los partidarios de este tipo de aplicaciones, a las obsoletas tradicionales aplicaciones de escritorio, cosa que evidentemente no gusto demasiado a la gente que se ganaba la vida desarrollándolas.


Con el paso del tiempo, y por consiguiente con la adopción y el uso de los autoproclamados clientes ligeros, comenzaron a hacerse visibles algunas de sus deficiencias. Estas deficiencias, siempre existieron, como en todas las tecnologías, a pesar de los esfuerzos de los departamentos de marketing por vender servidores de aplicaciones. No disponer de un conjunto tan grande de componentes de interfaz de usuario, no disponer de unas tecnologías de procesamiento realmente potentes, teniendo que limitarse a lenguajes simples de scripting, la problemática de la recepción de datos (pull), o la necesidad de incurrir en continuos accesos al servidor para obtener información, se presentaron como problemas importantes. En ese momento, reaparecen de nuevo los defensores de las aplicaciones de escritorio, que pasan a autoproclamarse clientes ricos ( en la actualidad este término ya se ha hecho mucho más genérico, y tenemos que mucha gente considera clientes Flash o DHTML como clientes ricos ), quedando relegadas las aplicaciones web al rango implícito de clientes pobres.


El objetivo de este artículo, no es discutir que tipo de clientes es mejor, ya que sería una discusión absolutamente sin sentido. Ninguno es mejor que el otro. Cada proyecto tiene una serie de características que hacen que una tecnología sea más adecuada o menos adecuada que otra. Ni las interfaces web son buenas para todo, ni las interfaces de escritorio solucionan todos los problemas.


Este artículo, alejándose pues del típico debate fratricida, busca presentar una serie de guías para que las aplicaciones de escritorio no fracasen, y para, en definitiva, obtener clientes contentos, que no son interfaces de usuario rechonchas y felices, sino clientes que han pagado por el desarrollo de aplicaciones que deben ser ágiles, sencillas, intuitivas, usables y todos esos adjetivos a los que nos hemos desacostumbrado en el mundo de las aplicaciones de escritorio, y que vienen en cualquier panfleto publicitario ( y que no los cumplen ).


Es muy común que después de realizar una presentación de una aplicación a un cliente, el desafortunado desarrollador venga cargado de expresiones como: "Pincho aquí y no se qué pasa, pero la aplicación se queda sin hacer nada medio minuto", o, "aquí tiene que haber un árbol en el que pueda marcar y ver filas y ...", o una de mis preferidas, "¿le tenemos que dar a la manivela para que funcione?". Tanto da las que escoja, hay miles de frases. A menudo, dichas protestas no se deben a que los clientes sean unos pesados (no hablo de las aplicaciones), si no a que las aplicaciones no cumplen una serie de fundamentos básicos a los que se debería prestar mucha más atención, y que redundarían en grandes beneficios, no sólo para la empresa en la que trabajamos, si no que seguramente también para nuestra salud mental al no tener que oír estas cosas.


Muchas veces no se podrán aplicar todas las sugerencias, ya que como todo, su uso depende de la situación y del problema a resolver. Por otra parte, ninguna de estas guías está ligada con una tecnología en concreto. En mis ejemplos aparece el mundo de Java, pero se puede sustituir este lenguaje por cualquier otro que ofrezca la posibilidad de crear aplicaciones de escritorio, por ejemplo .NET. Seguramente me haya olvidado también de muchas sugerencias, quizás de alguna realmente importante que se os ocurra; pero este no es un documento cerrado, y el autor está abierto a sugerencias :-)


1. Utilizar la ayuda de algún framework.


Quiero un arból que presente varias filas, y quiero un calendario, y quiero un navegador web, y quiero una barra como la que sale en el Outlook, y quiero ...


A pesar de la grandísima tentación de responder al cliente con un: "Sí, sí, y yo quiero combo", o con un: "Pues va a ser que no"(anuncios de moda en estas fechas en España), siempre es necesario plantearse la necesidad de utilizar algún framework para el desarrollo de aplicaciones de escritorio. ¿Realmente tenemos el tiempo necesario para reimplementar componentes que ya traen muchos de estos frameworks?


La implementación de componentes de interfaz de usuario no es algo sencillo. Primero, requiere un conocimiento importante de la plataforma con la que estamos trabajando. Por ejemplo, en Swing crear un árbol-tabla medianamente potente es una tarea realmente compleja que requiere conocimiento de todo el sistema interno de Renderers, Headers, Listeners, etc. Segundo, nuestros desarrolladores probablemente no tengan demasiado tiempo para lucirse en las implementaciones, y por lo tanto olviden algunos detalles, por lo que puede que en un futuro "sea necesario introducir un cambio en aquel componente que permitía introducir cifras monetarias". Cambio que por otra parte no sabemos de que forma influirá en el resto de aplicaciones que ya hemos desplegado, derivando esto en o bien el riesgo de múltiples fallos, o en el mantenimiento de múltiples versiones de componentes. Por último, nos aparta de lo que en realidad interesa al cliente, que es el realizar esa aplicación para el acceso central a la información de la empresa, desembocando en tener a desarrolladores actualizando tablas personalizadas, campos de textos, extensiones de visores HTML basados en Swing, y cualquier otra cosa que se os ocurra.


En definitiva, el crear componentes de interfaz de usuario, lejos de ser una tarea trivial, es una tarea que tiene asociada enormes cantidades de tiempo tanto en desarrollo como en mantenimiento. Normalmente lo que menos tienen los desarrolladores es precisamente eso, tiempo. Existen en la actualidad varias suites de componentes de interfaz de usuario listas para utilizar, y que debemos aprovechar siempre que nos sea posible. Algunas de ellos incluso son : jgoodies (http://www.jgoodies.com), l2fprod common-components (http://www.l2fprod.com), kiwi (http://www.dystance.net/software/kiwi), JIDE (http://www.jidesoft.com/products/download.htm) o Javio (http://www.javio.com/).



Pero las suites de componentes, no son la única opción para acelerar la creación de interfaces de usuario. Actualmente se están poniendo de moda las plataformas de creación de clientes ricos: Eclipse Rich Client Platform (http://www.eclipse.org/rcp/), NetBeans Platform (http://www.netbeans.org/products/platform/), Spring Rich Client Platform(http://www.springframework.org/spring-rcp.html) , ... ¿Valen la pena? La respuesta depende de la situación. Normalmente, si nuestra aplicación es algo muy sencillo, no merecerá la pena esforzarse en aprender una nueva tecnología, y en sobrecargar un interfaz de usuario tanto en memoria como en CPU. A fin de cuentas, para que quiero un workspace de Eclipse con todo la carga en la gestión de perspectivas, vistas, eventos, recursos, etc., si lo único que quiero es mostrar una tabla con los artículos de mi cliente y los datos de los mismos.



Captura 2 : Ejemplo de aplicación creada con Eclipse Rich Client Platform.


Ahora bien, si nos estamos planteando un desarrollo largo, de varios meses o años, o de múltiples aplicaciones similares, el coste de aprender una plataforma de desarrollo de clientes ricos casi siempre compensará los beneficios que éstas nos ofrecen. En la imágen anterior se puede ver una captura de pantalla de una aplicación desarrollada con Eclipse Rich Client Platform. Si esa aplicación tuviese que crear todo el código fuente que gestiona las diferentes vistas, perspectivas, el arrastrar, eliminar o insertar nuevos componentes, la gestión de acciones, los asistentes, la ayuda, los diálogos de progreso, la internacionalización o la gestión de fuentes e imágenes, pues ahora mismo en lugar de ver esa pantalla aparecería algo mucho más sencillo o tal vez nada. Es decir, las plataformas de desarollo de clientes ricos, tienen mucho que ofrecernos, pero sólo debemos usarlo cuando realmente necesitemos ese valor añadido.


Para finalizar, quiero dejar claro que no estoy abogando por huir de la creación de componentes propios. Como comprenderéis no quiero que después tenga que leer quejas del estilo "Seguí tu consejo y no creé componentes propios. El usuario quería un campo de texto para introducir un DNI. Como no lo encontré en ningún lado no lo puse en la interfaz. La aplicación fue un desastre y me echaron". No, no es eso. Debemos intentar ahorrar siempre todo el tiempo y trabajo que podamos. Pero si un framework no tiene un componente, y no queda más remedio, entonces tendremos que crear nosotros el componente. No pasará nada, siempre que sea algo excepcional y que no nos encontremos creando decenas o cientos de componentes personalizados. En ese caso estaría fallando algo.


2. Presentar el interfaz de la aplicación tan rápido como sea posible.


¡Y esta aplicación qué! ¿Hay que darle con la manivela para que arranque?


Una de las cosas que puede irritar más a un usuario, y que por consiguiente puede hundir nuestro proyecto, es que la aplicación tarde mucho tiempo en iniciarse. En Java, hace años, era algo habitual, y no existía una solución para este problema. Las máquinas virtuales eran lentas y tardaban mucho tiempo en iniciar las aplicaciones, lo que hacía que el interfaz de usuario de las aplicaciones tardara en aparecer.. Aún así, estabamos trabajando en intervalos de pocos segundos. En la actualidad, las máquinas virtuales ( sobre todo desde la aparición de la familia 1.4 ) son muy veloces, y el retardo por la carga de la JVM es ínfimo.


Pero, a pesar del avance de la tecnología de las JVM, el caso es que muchas aplicaciones siguen tardando un tiempo enorme en iniciarse, minando poco a poco la moral de los sufridos usuarios. Pero entonces, si la máquina virtual ya no tiene la culpa, ¿por qué tardan en iniciarse muchas aplicaciones? Cualquier desarrollador podría responder a esta pregunta con un "hay que cargar muchos datos", o quizás con un "la interfaz tiene demasiadas pantallas", o quizás con un " hay que cargar las conexiones a los recursos", o un sinfín más de razones.


Evidentemente, el inicio de las aplicaciones es algo delicado. Toda aplicación tendrá que cargar una serie de recursos como referencias a entradas JNDI, iniciar colas de mensajería, preparar los diferentes diálogos, paneles o ventanas que forman el interfaz de usuario, inicializar conexiones a base de datos si todavía estamos creando aplicaciones cliente-servidor, arrancar servidores internos a la aplicación o iniciar la comunicación con servidores externos, etc.


Pero todas estas divagaciones técnicas no le importan a nuestro cliente. El ha pagado por una aplicación rápida y es lo que quiere ver, y de ningún modo aceptará una aplicación que tarde treinta segundos en presentar la pantalla de inicio, por mucha tecnología punta que utilice. ¿Cómo podemos satisfacer al cliente al tiempo que realizamos todas las tareas necesarias para el correcto funcionamiento de la aplicación? La solución es sencilla: mostrar el interfaz de usuario lo más rápido que nos sea posible. Si mostramos la pantalla de inicio de nuestra aplicación, la pantalla de entrada al sistema, o la ventana principal de nuestra aplicación de una manera rápida, el usuario estará mucho más satisfecho, y además lo tendremos entretenido mientras vamos cargando en segundo plano todos los datos que necesitamos. Los siguientes puntos muestran algunos trucos que pueden servir de ayuda para conseguir este efecto:



  • Evitar cargar diálogos, paneles o ventanas innecesarias: Imaginemos el siguiente código:



    public class FramePedidos extends JFrame {



    private DialogClientes panelClientes = new DialogClientes(); // panel que muestra todos los clientes de nuestra empresa

    private DialogProveedores panelProveedores = new DialogProveedores(); // panel que muestra todos los proveedores



    // Resto de la ventana principal



    }



    Los dos diálogos que aparecen en el ejemplo, podrían tener muchos datos. El tiempo de carga de estos datos hace que la presentación de la ventana principal se ralentice. ¿Pero y si nuestro usuario no quiere ver ni los clientes ni los proveedores, y tan sólo quiere consultar el último pedido realizado, algo que además puede hacer directamente desde la ventana principal? ¿No resulta frustrante tener que esperar por algo que sabes que no vas a necesitar?



    Hay que evitar siempre que sea posible este tipo de declaraciones, inicializando los diálogos sólo cuando sean necesarios, como por ejemplo al pulsar un botón. El usuario entonces sólo esperará en el momento de pulsar ese botón, y si hacemos uso del patrón Singleton sólo esperará la primera vez que pulse dicho botón, algo que es mucho más razonable que cargar todos los datos al principio de la aplicación.


  • Mostrar pantallas de bienvenida ( Splash Screens ): Una aplicación, si tiene un tiempo de carga considerable, debe mostrar algún indicio de que se está realizando algún tipo de trabajo. Una de las cosas más molestas de algunas aplicaciones, es que se inicien y no muestren ninguna información de lo que está pasando, de modo que te encuentras preguntándote a ti mismo: "¿Pero esto está haciendo algo? ¿Ha arrancado? ¿Se ha colgado?" . Una imagen con el logo de la aplicación, o con el logo de la empresa, o quizás que incluya alguna barra de progreso donde se informe del tiempo de carga, puede hacer mucho más amigable nuestra aplicación, y seguramente el usuario lo agradecerá.


  • Aprovechar los diálogos de entrada a la aplicación: Muchas aplicaciones presentan diálogos de entrada al sistema donde se le pregunta al usuario su nombre y su contraseña. Mientras el usuario introduce estos datos, no hay ninguna razón para desperdiciar esos preciados ciclos de CPU en recibir la entrada del teclado. Debemos intentar aprovecharlos para precargar partes de nuestro interfaz gráfico ( tal vez las que reseñaba en el punto uno ), acceder a recursos que no necesiten de los datos del usuario ( colas de mensajería, o bases de datos a las que nos conectemos con un usuario genérico ), etc. Esta precarga de recursos, hará que después el usuario no tenga que esperar cuando quiera acceder a los mismos.


  • Cargar el acceso a recursos sólo bajo demanda: A no ser que tengamos una buena oportunidad para cargar los recursos a los que vamos a acceder, como pueda ser un diálogo de entrada a la aplicación, no deberíamos perder el tiempo en precargar el acceso a recursos que no sabemos siquiera si se van a utilizar. Imaginémonos por ejemplo un sistema que consta de varios módulos, personal, suministros, contabilidad, proveedores, clientes, etc., y que cada uno de esos módulos tiene su propia conexión a base de datos, o su propio EJB siendo necesario acceder al árbol JNDI, o su propia cola de mensajería. Si el usuario sólo va a acceder a la parte de personal, no deberíamos perder el tiempo en cargar el resto de referencias, ya que todos estos accesos tienen un coste considerable. Por lo tanto, sólo se deben cargar estos recursos cuando sea estríctamente necesario, o cuando tengamos una oportunidad clara de hacerlo porque se estén desperdiciando ciclos de CPU.



Todos estos puntos, evidentemente complican la lógica de inicio de la aplicación, pero nos aportan enormes beneficios, haciendo mucho más efectivas nuestras aplicaciones. Muchas veces se suele decir que la primera imágen es la más importante, y desde luego, si nuestra aplicación se inicia veloz como un rayo, la primera impresión del usuario será muy positiva.

3. Utilizar hilos de trabajo de manera extensiva.


Pulso este botón, y la aplicación se queda en blanco, como parada. Después de un minuto más o menos vuelve a funcionar.


Normalmente, en una aplicación existen una serie de tareas que pueden requerir que el usuario espere a que éstas finalicen para poder seguir trabajando con la aplicación. Sin embargo, existen también un gran número de tareas que no requieren de la interacción del usuario, y que tampoco entran en conflicto con las operaciones que dicho usuario pudiese realizar normalmente. Muchas veces, el usuario se ve obligado a esperar a que estas operaciones, que no le importan, terminen, perdiendo parte de su tiempo y de su paciencia.


El ejemplo más habitual que suele venir en casi todos los libros, es el del usuario que realiza una transacción con su tarjeta de crédito para comprar algún producto. Una vez que el usuario elige el producto que desea e introduce los datos de su tarjeta de crédito, pulsará algún botón o enlace que le permita realizar la transacción. El usuario, ha realizado su pedido, y desde ese momento no le importa que en algún lugar remoto se estén validando los datos de su tarjeta de crédito, comprobando el saldo de su tarjeta, mirando si hay stocks reales de sus productos, comprobando en el caso de que no haya stock si se puede obtener ese producto desde otro almacén, y cualquier otra tarea que se nos ocurra. El usuario lo único que quiere es poder seguir adelante, ya que ya ha comprado ese articulo, y quiere seguir buscando más cosas para comprar. Si lo hacemos esperar varios minutos por el resultado de la transacción, seguramente no le apetezca realizar una nueva compra, y estaremos perdiendo dinero.


Aunque este ejemplo está muy orientado al entorno web, lo cierto es que en las aplicaciones de escritorio sucede exactamente lo mismo. Los procesos que no dependen para nada del usuario, y que no interfieran en sus futuras acciones, debemos intentar ejecutarlos de un modo que no molesten. Una de las formas de realizar esto es ejecutarlos en un hilo en segundo plano. De este modo, el usuario puede seguir trabajando con la aplicación mientras la tarea costosa se realiza de manera transparente.


Existen multitud de ejemplos que nos ayudarían a comprender la utilidad de los hilos en las aplicaciones de escritorio para que éstas sean más ágiles. Normalmente el usuario se pasa la mayor parte del tiempo realizando tareas poco costosas para el PC. Por ejemplo, en un formulario con diez campos, el usuario puede que esté un minuto introduciendo datos, cuando no varios minutos. ¿Por qué no vamos a aprovechar ese tiempo para cargar datos? Imaginémonos que el formulario en algún lugar tiene un botón que al pulsarlo abre una lista con los proveedores. Inicialmente nos veríamos tentados a cargar los datos de dichos proveedores cuando el usuario pulse el botón, o cuando se abre el formulario. Pero, ¿y si los cargamos mientras el usuario está introduciendo los datos? Esto haría que no tuviese que esperar ni al abrir el formulario, ni al pulsar el botón. Este proceso añade algo de complejidad, pero desde luego nuestra aplicación será mucho más ágil.


Existen varios frameworks que nos pueden ayudar a la hora de lanzar aplicaciones en segundo plano. De hecho, en Swing no es demasiado recomendable hacerlo directamente ya que se suelen producir efectos desagradables. Cualquiera que haya trabajado con hilos de interfaz de usuario y Swing recordará con temores esas pantallas en blanco al ejecutar un hilo y tratar de refrescar la aplicación. Un framework Open Source como Foxtrot soluciona eficientemente este problema. Frameworks más generales para la ejecución de tareas en segundo lano son Quartz, un scheduler muy conocido, o incluso la arquitectura interna de trabajos de SWT o la Eclipse Rich Client Platform de lo que hablaré con posterioridad. Nuevamente se hace patente que los frameworks toman un papel decisido para ahorrarnos trabajo.


4. Avisar al usuario de lo que está pasando.


Introduje los datos del cliente y pulsé Aceptar. Pero la aplicación parece que no hace nada, se queda ahí colgada.


¿Nunca os habéis encontrado con una aplicación en la que al pulsar un botón no pasa nada? Sucede muy a menudo. Pulsas un botón y pasan los segundos sin que pase nada, pero sin embargo no puedes trabajar con la aplicación. De repente, tras unos segundos parece que ya puedes trabajar, y terminas preguntándote que es lo que ha pasado.


Este es uno de los comportamientos que debemos evitar a toda costa en nuestras aplicaciones. Siempre que se realice una tarea, debemos mostrar información sobre lo que está pasando. El usuario debe saber en todo momento lo que se está realizando, o al menos que se está realizando algo. Un simple cursor con un reloj puede servir para que por lo menos sepamos que algo sucede. Aunque la verdad es que tampoco sirve de demasiada ayuda, porque en cuanto llevas tres segundos mirando para el icono de reloj empiezas a preguntarte que diablos está haciendo la aplicación.


Habitualmente, como mínimo aceptable para una aplicación se necesita una información textual sobre la tarea que se está realizando en cada momento y una barra de progreso que indique la cantidad de trabajo realizada, y que permita intuir el tiempo restante para finalizar la tarea. Cuando no sabemos cuanto puede durar una tarea, entonces podemos utilizar una barra de progreso indeterminada, de forma que cuando se cumplan n pasos, se vuelva al comienzo de la tarea. Algunos frameworks ya traen este tipo de barras de progreso por lo que seguramente no la tengamos que implementar.


El tratamiento de errores también es algo muy importante. Sí, vale, tenemos nuestra aplicación que hace auditoría por consola o que va guardando en un fichero todo lo que va pasando. ¿Pero va a entender eso el usuario? Siempre que se produzca algún error tendremos que mostrar algún mensaje que se lo indique al usuario, a no ser que queramos torturarlo con diez minutos de castigo enviando como un loco peticiones de la lista de proveedores a un servidor de aplicaciones que se ha caido. Estos errores normalmente se mostrarán en un diálogo que contendrá más o menos información según el tipo de usuarios que estén utilizando la aplicación.


Utilizar hilos en segundo plano para la ejecución de tareas, también requiere un tratamiento especial de los errores que se puedan producir. En una aplicación en la que no se utilizan hilos, la solución es muy sencilla, en el momento en el que se produzca algún fallo se mostrará al usuario un mensaje de advertencia de que se ha producido algún tipo de error. Sin embargo, al ejecutar tareas en segundo plano hay que ser algo más precavido, porque seguramente al usuario le molestará ver aparecer un mensaje de error relacionado con algo que había hecho hace cinco minutos.


Se hace necesario encontrar otra forma de informar al usuario del resultado de las tareas. Aquí, se pueden encontrar múltiples soluciones. Una muy elegante consiste en mostrar un panel donde se pueda ver el progreso de las diferentes tareas y su estado, de este modo el usuario sabe en cada momento que es lo que está realizando el sistema en segundo plano. Esto es por ejemplo lo que hace Eclipse con su vista de progreso de tareas, y algo que la Eclipse Rich Client Platform permite manejar muy eficientemente. La siguiente captura de pantalla muestra un ejemplo de esto.



Figura 3 : Dialogo de progreso con árbol de tareas y vista de tareas en progreso.


Sin embargo, muchas veces estaremos ejecutando tareas en un servidor remoto del que no tenemos control, ni información, por lo tanto poco podemos mostrar, salvo un mensaje en el que se informe si la tarea ha finalizado correctamente o no. Una de las soluciones más utilizadas en estos casos es el envío de mensajes de correo, SMS, o algún sistema de notificación similar. En el ejemplo de la tienda online que trataba en el tercer consejo, si el pedido no se pudiese satisfacer, lo habitual sería que se enviase un mensaje de correo al usuario, o un SMS, adivirtiéndole de que ha sido imposible realizar la transacción de compra y explicándole las razones, además de ofreciéndole soluciones.


Otra casuística que no se nos puede pasar por alto, es ¿qué sucede si el usuario quiere salir de la aplicación y hay tareas ejecutándose?. Evidentemente, algunas de estas tareas no deberían finalizarse bruscamente, pero tampoco podemos dejar al usuario esperando a que la aplicación se cierre al tiempo que van creciendo en él las ganas de acudir al gestor de tareas del sistema operativo. Al crear tareas, podríamos marcar éstas como importantes, y en caso de detectar que el usuario quiere salir de la aplicación, se le mostraría un diálogo donde apareciesen las tareas por las que tiene que esperar y el progreso de las mismas.


Otro dilema interesante es ¿qué sucede si el usuario quiere lanzar una tarea que no debería ejecutarse hasta que termine alguna de las otras tareas que se están ejecutando? Nuevamente la solución podría ser mostrar algún diálogo donde se le explique que es necesario esperar a que terminen las tareas de las que depende lo que quiere ejecutar. Esto es precisamente lo que hace Eclipse cuando por ejemplo quieres ejecutar un proyecto y sin embargo está ocupado compilando el workspace.


Evidentemente, implementar todo esto puede ser bastante engorroso. Por eso es tan importante el primer consejo. Los diferentes frameworks para la creación de aplicaciones de escritorio suelen ofrecer algún sistema para ejecutar trabajos en segundo plano. Eclipse Rich Client Platform dispone de una fenomenal infraestructura para el trabajo con tareas, pudiendo incluso definir multitud de propiedades de las mismas como su prioridad, si es una tarea que bloquee la ejecución de otras tareas, y ofreciendo diferentes vistas de progreso, diálogos, etc. Crear un hilo con la Eclipse Rich Client Platform sería algo tan sencillo como:



Job job = new Job(Messages.getMessage("delete_job_name")) {

protected IStatus run(IProgressMonitor monitor) {

....

}

};

job.setPriority(Job.LONG);

job.setProcessGroup(someProcessGroup);

job.setStheculingRule(someSchedulingRule);

job.setUser(true);

job.schedule();


El código anterior ejecuta una tarea en segundo plano, con baja prioridad ya que es un trabajo definido como muy largo, colocado en una agrupación de trabajos que aparecerá como un árbol en el diálogo de procesos, asignándole una regla para el scheduler de tareas de modo que no le ejecutará hasta que no se produzca la regla y por último estableciendolo como un trabajo de usuario lo que implica que aparecerá un diálogo donde el usuario podrá ver el progreso del trabajo y de todos los trabajos del grupo. Este diálogo es el que se veía en la captura de pantalla anteriormente. Nuevamente los frameworks aparecen como una herramienta fundamental para ahorranos trabajo.


5. No cargar la información que no necesitemos.


Al darle a ver artículos del inventario tarda una eternidad. ¡Así no hay quien trabaje!


Las aplicaciones empresariales muestran información al usuario. Normalmente, esta información se encontrará jerarquizada, y sus entidades relacionadas mediante asociaciones de diferente multiplicidad. El volumen de esta información puede llegar a ser inmenso. Por ejemplo, una aplicación para entornos clínicos en la que se muestre toda la historia clínica de un paciente contendrá pruebas de laboratorio, episodios clínicos, operaciones, informes, etc. Cada una de estas entidades puede estar compuesta de decenas, cientos, o miles de subentidades y valores concretos. Es fácil intuir que en caso de descargarse este volumen de información por completo, se moverían varios megabytes de información a través de la red, lo que acarrearía un enorme coste.


Por otra parte, las aplicaciones de escritorio tienen una filosofía de funcionamiento muy diferente a las aplicaciones web. Al disponer de una cantidad mayor y más completa de componentes de interfaz de usuario, se tiene la oportunidad de poder descargarse gran cantidad de información de una tacada y desplegar dicha información sobre los árboles, tablas, listas, etc., que forman el interfaz de usuario.


Pero antes de verse tentado a descargarse de golpe toda la información que nos sea posible, es necesario pensar en que es lo que sucedería si a los otros mil usuarios concurrentes del sistema se les ocurre la genial idea de realizar lo mismo en el mismo momento. En muchos casos la probabilidad de que esto suceda es pequeña pero, por otra parte, el servidor con el que estemos trabajando quizás esté compartido por otras múltiples aplicaciones que tengan mucha más carga de usuarios. Por lo tanto, es muy importante pensar en qué información es realmente necesaria para la aplicación y de qué información podemos realmente prescindir.


El caso de la historia clínica es bastante esclarecedor. Cuando un médico consulta la historia clínica de un paciente, ¿es más probable que quiera consultar los datos de un análisis que se produjo hace 25 años, o el que quiera ver los datos de los cinco últimos episodios clínicos? En la mayor parte de los casos, la segunda opción será la más probable. Es importante pues, dentro de una aplicación de escritorio, detectar que información no es demasiado probable que se necesite, y evitar la carga de ésta, ya que así no sobrecargaremos nuestro servidor, y lo más importante, el usuario verá en pantalla la información mucho más rápidamente y el aspecto de la aplicación será mucho más ágil.


Este proceso de carga de información sólo cuando se necesite, recibe el nombre de lazy loading o carga perezosa, y es utilizado habitualmente. Un ejemplo muy simple de código fuente que realiza carga perezosa es el siguiente:




public class MiCRM {

private List proveedores = null;

public List getProveedores() {

if (proveedores == null) {

proveedores = cargaProveedoresDelServidor();

}
}
}

En el código anterior, si la aplicación empresarial necesita los proveedores (quizás porque el usuario haya accedido a un diálogo en el cual se muestra una lista con todos los proveedores de nuestra empresa) entonces se carga la lista de proveedores del servidor, pero únicamente en caso de que no hubiese sido cargada en una ocasión anterior. En el caso de que el usuario no necesitase ver la lista de proveedores dentro de su sesión de trabajo con la aplicación, entonces nos habríamos ahorrado un acceso al servidor, varios accesos a base de datos, operaciones de marshalling/unmarshalling si estamos accediendo a servicios web o EJBs y multitud de información transmitiéndose por la red.


Las ventajas de la carga perezosa son evidentes. Pero ojo, lo más complicado de un buen mecanismo de carga perezosa en un aplicación no es el implementarlo, sino que lo más importante es el elegir qué elementos se deben cargar perezosamente y cuáles no se deben cargar. Muchas veces la única forma de saberlo es observar bien como trabajan nuestros usuarios y ver que es lo que hacen diariamente. A veces incluso se podrá cargar perezosamente información en función de los roles de los usuarios conectados a la aplicación. Por ejemplo, si un cardiólogo obtiene la historia clínica de un paciente, seguramente será porque desea ver cosas relacionadas con cardiología, y también es muy probable que no esté interesado en el esguince de tobillo que el paciente tuvo hace seis meses. Igualmente, un psicólogo, estará interesado en los datos de episodios anteriores sobre psicología, neurología, etc., y es poco probable que le interesen los datos cardiológicos.


Si cargar perezosamente la información que no se va a usar es importante, también lo es el no cargar perezosamente la información que con seguridad se va a utilizar. Esa información normalmente debería ser cargada de manera agresiva, como explica sexto consejo. Por ejemplo, imaginémonos una aplicación que maneja pedidos con n artículos, y decidimos cargar perezosamente cada artículo. Si un empleado del almacén accede a un pedido y trata de ver la información de sus artículos asociados uno por uno, tendrá que esperar un rato para obtener la información de cada artículo dependiendo de la carga del sistema. Estamos incurriendo sin darnos cuenta en el problema de las N+1 consultas, 1 consulta para el pedido y N consultas para los artículos ( una cada vez que selecciona un artículo ), cuando seguramente podríamos haber precargado la información. ¿Cuál ha sido el problema? Que hemos elegido mal la información que se carga perezosamente.


La carga perezosa añade complejidad a las aplicaciones, pero contrapartida incrementa la escalabilidad de las mismas. Si se incurre en menos hits de base de datos, y se sobrecarga mucho menos el servidor de aplicaciones, éste podrá soportar muchos más clientes, y como consecuencia nuestra aplicación será mucho más escalable.

6. Precargar toda la información útil que podamos.


Esto es un desastre. Cada vez que selecciono algo en el árbol de artículos tengo que esperar diez segundos para poder hacer algo.


Si la información que no se necesite, no se debe cargar, como se comentaba en el consejo anterior, con la información que con seguridad se vaya a utilizar ocurre exactamente lo contrario. Cuando tengamos la seguridad de que el usuario va a acceder a determinada información, debemos intentar precargar esa información siempre que sea posible, ya que de este modo dicha información permanecerá almacenada en el cliente, y el acceso a la misma será muy rápido.


Yo mismo, durante los últimos años he sido testigo de situaciones donde no sólo era necesario precargar la información, sino que esa precarga adquiere una trascendencia vital. Un ejemplo podría ser una aplicación para algún servicio de hemodinámica, cardiología, o cualquier otro servicio muy crítico. Normalmente, cuando se ingresa a un paciente para realizarle una operación de corazón, los hemodinamistas cargan la información del paciente en los ordenadores, miran algunos datos sobre él, y se ponen manos a la obra. Pero imaginaros que surge alguna complicación y es necesario aplicarle algún tratamiento de choque al paciente. Es probable que el hemodinamista jefe necesite alguna información adicional sobre el paciente: alergias a medicamentos, resistencia a tratamientos, etc. Ahí nos encontramos en una situación límite. Una de los hemodinamistas saldrá de la sala corriendo e intentará buscar esa información. ¿Qué sucede si la información no está disponible? ¿Qué sucede si en ese momento la red no funciona? ¿Qué sucede si el servidor o la base de datos están colapsados? Muy sencillo, que la vida del paciente está en un serio peligro. Esta situación es realmente extrema pero nos da una idea de la relevancia que puede tener el haber precargado o no los datos que va a manejar el usuario.


La precarga de datos se suele conocer por el término de eager loading o carga agresiva. La idea de precargar la información que el usuario va a usar, además de salvar vidas, proporciona un ahorro importante en cuanto a tiempo de acceso a servidor, base de datos, etc., ya que los datos se obtendrán de golpe, con un único acceso de red, y quizás gracias a múltiples join, con pocos accesos a base de datos. Una vez que los datos están precargados, no es necesario volver a acceder a la base de datos o a la red para obtenerlos, lo que agiliza enormemente el funcionamiento de la aplicación.


Podemos clasificar la información que seguramente utilizará el usuario, es decir la susceptible de ser cargada agresivamente, en dos tipos: la invariable y la variable. La información invariable, debemos cargarla sin ninguna duda, ya que de otro modo estaríamos incurriendo en accesos a servidor para recoger información que siempre será exactamente igual. Un ejemplo muy sencillo serían las preferencias de un usuario que se almacenasen en base de datos. Podríamos acceder a cada preferencia en el momento en que fuera necesaria, realizando métodos del estilo getColorDeFondo(), getTipoDeLetra(), etc.Sin embargo, una solución mucho más eficiente sería cargar de golpe toda la información con un único método getPreferencias(). Esa información, no va a ser modificada por ningún otro usuario. No es susceptible de un acceso concurrente y por lo tanto sería una tontería no cargarla toda de golpe.


No obstante, no todo son beneficios, existen algunas pegas a tener en cuenta. Primero, la carga masiva de información puede consumir bastante tiempo. Si realizamos este proceso al iniciar el programa, no debemos olvidarnos de considerar los consejos dos,tres y cuatro, y presentar el interfaz de usuario lo más rápido que nos sea posible, utilizar hilos para precargar todos esos datos y presentar información sobre el progreso de la carga o al menos alguna pantalla de bienvenida que no deje al usuario preguntándose qué es lo que está pasando.


Segundo, si los datos se modifican muy frecuentemente, es decir, estamos ante información que antes definía como invariable, entonces nos encontraremos con que podemos tener copias obsoletas en el cliente de información que ha sido modificada en el servidor por otros usuarios después de haber sido precargada. Debemos considerar si esto es realmente un problema, y en caso afirmativo habrá que implementar algún sistema que actualize la información del servidor, o realizar algún tipo de esquema de bloqueo pesimista, etc., pero esto ya queda fuera del ámbito de este artículo.


Por último, la información precargada ocupa espacio. Antes de decidirnos a precargar la información de detalle de las nóminas de toda nuestra empresa de empleados en una única aplicación cliente, deberíamos depurar y examinar nuestra aplicación con alguna herramienta de análisis de rendimiento(profiler), para comprobar que el consumo de memoria es aceptable, ya que seguramente nuestro jefe no se ponga demasiado contento cuando le digamos que hay que aumenarle la memoria a los cinco mil PCs de nuestra organización. En caso de no ser aceptable, habrá que seleccionar mejor la información que queremos precargar, o quizás el momento en el que la vamos a precargar.


7. Evitar el presentar enormes cantidades de datos.


Le he dado a obtener las propuestas de gasto para el año 2004 y aún no ha terminado. ¡Y ya llevo diez minutos!


¿Qué posibilidades hay de que un gestor económico se lea las veinte mil propuestas de gasto de un ejercicio en los quince minutos que tiene hoy para trabajar con la aplicación? Ninguna. El permitir realizar consultas con filtros demasiado amplios, puede generar problemas para nuestras aplicaciones y desesperación en el usuario. Parece una tontería, pero lo cierto es que si a un usuario le permites hacer una consulta muy genérica, seguramente lo hará, y después no trates de explicarle que se está descargando muchísimos datos del servidor, porque eso a el no le interesa; a el lo que le interesa es que quiere trabajar, y que tiene quince minutos para aprobar las diez propuestas del epígrafe trescientos dieciocho antes de terminar el día, y si la aplicación va lenta no es culpa de él, es culpa de que es una basura.


Pero, las consultas grandes no sólo suponen un problema para las aplicaciones, sino que también suponen un enorme problema para los servidores, tanto de aplicaciones como de bases de datos. ¿Podéis imaginaros lo que sucede en un hospital cuando pretendes acceder a los datos de historia clínica y se te ocurre consultar todos las personas con primer apellido Pérez? Pues cuando hay cientos de aplicaciones accediendo a esos datos, y resulta que la base de datos bloquea todas las filas relacionadas con esas personas, os puedo garantizar que vuestra relación con los responsables de sistemas se verá deteriorada considerablemente, y ya no digamos como se os ocurra hacer eso con varias aplicaciones que se ejecuten concurrentemente.


Hemos de evitar realizar consultas demasiado genéricas para agilizar nuestra aplicación, y el funcionamiento general del sistema. Ahora bien, ¿cómo?. La solución más sencilla es obligar al usuario a realizar un filtro más completo. En el ejemplo anterior de las propuestas de gasto, podríamos obligarle a introducir el epígrafe con el que quiere trabajar, limitar por mes, seleccionar un rango menor de veinte propuestas, etc. Con esto garantizamos que se realizan consultas mucho más específicas, concretas, y sobre todo mucho más rápidas y que impactan mucho menos en el rendimiento de nuestros servidores.


Pero a veces es necesario realizar consultas que irremediablemente devolverán gran cantidad de datos. Ante estos casos, en el mundo de las aplicaciones web, existe una solución muy sencilla. Todos la conocemos y la usamos diariamente, por ejemplo en Google. La idea es devolver los datos de manera páginada. Así, en lugar de realizar toda la consulta completa, pedimos a la base de datos un rango de filas y es ese rango el que devolvemos al usuario. En el mundo de los patrones de diseño empresariales, este patrón suele denominarse Page-by-page iterator.


Pero, ¿funcionan así las aplicaciones de escritorio? La realidad es que no. No es habitual ver una aplicación de escritorio donde tengas dos enlaces Siguiente y Anterior y en el que los registros aparezcan paginados. De hecho el usuario pensaría, "¿pero si sólo hay treinta registros porque este demonio de programador no me los presenta todos en una lista con una barra de scroll?". En las aplicaciones de Internet estamos resignados a que todo vaya lento y veas sólo unos cuantos registros, y si quieres ver más, pulsas en siguiente y esperas otro rato. En las aplicaciones de escritorio esto no funciona así. Los usuarios quieren pinchar en el botón de búsqueda y quieren tener toda la información de inmediato. Si nuestros usuarios se encuentran familiarizados con esta forma de trabajar, con resultados paginados, podemos intentar ofrecerles un interfaz similar. De hecho, si los datos a los que accedemos están clasificados por algún tipo de clave indexada, un serial por ejemplo, podemos mostrarle algún campo de texto que le permita navegar a un índice determinado o cargar elementos entre un rango de índices, haciendo mucho más flexible el interfaz de usuario.


Otra solución que hacen algunas aplicaciones es añadir un listener a la barra de desplazamiento del componente en el que se muestran los resultados ( tabla, lista, etc. ), y cuando esa barra supera un límite, por ejemplo cien registros, se accede a la base de datos y se descargan los cien registros posteriores. De hecho, sería posible aunque laborioso, el crear un sistema que calculase el desplazamiento realizado por la barra de desplazamiento y a partir de ese desplazamiento calcular el rango de filas que es necesario descargarse desde el servidor, previamente habiendo calculado el número total de filas existentes. Lo malo de esta solución es que al final estas haciendo accesos continuos a base de datos y pierdes lo que pretendías ahorrar en un primer momento, además de la evidente complejidad.


Otra solución a este problema, algo más sencilla y elegante, aunque no siempre posible, es el presentar la información de alguna forma estructurada. Si el usuario se empeña en que quiere ver todas las propuestas de gasto de un ejercicio, sería posible que en lugar de presentarle los miles y miles de registros, le presentásemos dichos registros agrupados en epígrafes (comúnmente las propuestas de gasto se agrupan por epígrafes). De este modo, cuando el usuario pulsase en un epígrafe se volvería a realizar la consulta a la base de datos, pero esta vez sería una consulta mucho más concreta. Realmente estamos engañando al usuario, pero en muchas ocasiones se encontrará satisfecho ya que tiene la sensación de poseer toda la información, y para él es mucho más sencillo disponer de esa agrupación estructurada que el tener que realizar decenas de consultas por epígrafe o el navegar a través de decenas de miles de filas en una tabla.


8. Minimizar los accesos a recursos externos.


La aplicación va fatal. Parece una tortuga. Estoy a punto de tirar el proyecto.


El consejo número seis es un caso partícular de éste, más general. El acceso a recursos externos es caro. Ejecutar una consulta de base de datos desde una aplicación de escritorio conlleva el realizar una llamada a través de la red, y el que el motor de base de datos analice la consulta, la compile, trate de optimizarla, la ejecute y devuelva los resultados transmitiéndolos de nuevo a través de la red. El acceder a un EJB implica el realizar un proceso de marshalling en el cliente, transmitir la información a través de la red, realizar el proceso de unmarshalling en el servidor de aplicaciones, acceder al registro JNDI para obtener la referencia al EJB, abrir un contexto transaccional, acceder a otros EJBs creando nuevas transacciones, etc. etc. etc. Lo mismo ocurre con sistemas de mensajería, servicios web, ficheros, etc. Todo esto es muy costoso. Evidentemente, nunca nos será posible eliminar por completo todas las llamadas a recursos externos, pero siempre se pueden buscar medidas para intentar reducir el número de estas llamadas.


Uno de los patrones más conocidos para reducir el número de llamadas remotas a recursos externos es el Transfer Object. Este patrón, (lo podemos encontrar también en la variante de Data Transfer Object pero yo no haré distinciones ), define un objeto de datos que aglutina un conjunto de información relacionada, que de otro modo debería ser transferida de manera individual. Este patrón permite ahorrar muchísimo tiempo en el acceso a recursos externos. Tradicionalmente, uno de los ejemplos donde se ha visto más demostrada su utilidad es con los EJBs de Entidad (aunque no se limita sólo al mundo de los EJB). Simplificando mucho, un EJB de entidad, representa una fila de la base de datos, cuyas columnas podemos modificar utilizando los métodos set que este ofrece. Por lo tanto, en un principio la gente se vería tentada a escribir código como el siguiente:



      EJB... persona = // Obtener ejb de alguna factoría home

persona.setNombre("Martín");

persona.setApellidos("Pérez Mariñán");

persona.setDNI("12345678");

persona.setTelefono("981777777");

persona.setEmail("mpermar@gmail.com");

persona.....


Este código funciona sin ningún problema ( bueno, vale, faltan mucho tratamiento de excepciones ). ¿Pero que está sucediendo en la trastienda? En este caso la cantidad de información transmitida por la red no es demasiada. Lo realmente costoso es el proceso de marshalling/unmarshalling, acceso a base de datos, obtención de EJBs, creación de transacciones, sincronización de transacciones en cluster, etc. Todas estas operaciones para una simple actualización. Es algo realmente excesivo. El patrón Transfer Object alivia esto definiendo un único objeto con todos los datos, que sería el que se transmitiese:



        EJB... persona = // Obtener ejb de alguna factoría home

personaDTO.setNombre("Martín");

personaDTO.setApellidos("Pérez Mariñán");

personaDTO.setDNI("12345678");

personaDTO.setTelefono("981777777");

personaDTO.setEmail("mpermar@gmail.com");

personaDTO.....

persona.actualizaPersona(personaDTO);


Ahora lo que haríamos sería hacer una única llamada a un EJB de sesión que se encargaría de actualizar el EJB de entidad. Para el cliente el código sería el que vemos justo arriba, donde ya sólo se realiza una única llamada a través de la red, un único proceso de marshalling/unmarshalling, una única transacción, etc. Tanto el cliente como el servidor ven aliviado su trabajo. Por lo que a este artículo concierne, el cliente no se ve tan ralentizado al no tener que acceder tanto al servidor, el consumo de recursos disminuye y el usuario verá una aplicación mucho más rápida y eficiente.


Como ya comentaba anteriormente, siempre debemos intentar aprovechar el consejo seis. Pero situémonos en un caso drástico. ¿Qué sucede si la base de datos desaparece? ¿Qué pasa si perdemos la conexión a la red? En muchas aplicaciones, la respuesta es clara, "tómate un descanso y espera a que se restablezca la conexión muchacho". Pero, ¿y si estoy trabajando en algo vital?, o sin irnos a ejemplos tan drásticos como el de la operación de corazón, ¿no podría hacer el trabajo que hacía en local del mismo modo?


Con el paso de los años han ido surgiendo una serie de bases de datos ligeras, capaces de ejecutarse en procesos Java e ideales para incrustar dentro de aplicaciones de escritorio, pequeños servidores, etc. Productos como HypersonicSQL, Daffodill One$DB

o Derby/Cloudscape, se han ido haciendo un hueco dentro del mundo Java, al tiempo que se han ido ganando un respeto dentro de su sector al ser herramientas no destinadas a competir con los gigantes como Oracle o DB2 sino para otro tipo de tareas. Uno de los usos más interesantes de estas bases de datos es el de servir como sistemas de backup cuando se pierde la conexión con una base de datos central. Utilizando una de estas bases de datos incrustadas, si nuesta aplicación perdiese conexión con el servidor de base de datos, inmediatamente se podría lanzar una instancia de HypersonicSQL donde se fuesen almacenando las operaciones realizadas, guardando cambios, almacenando propiedades y nuevos registros, o simplemente realizando consultas sobre un histórico. Periodicamente, un proceso podría ir comprobando si la base de datos remota se restablece. En ese momento se volcaría de nuevo toda la información y se pasaría a utilizar otra vez la base de datos original


El objetivo de todo esto es mantener la aplicación funcionando y que el usuario no pierda el trabajo realizado, algo que seguro que agradecerá. Sin duda, implementar un sistema de este tipo es algo complejo, pero quizás valga la pena. Si tenemos a un vendedor en un proveedor, exponiendo en tiempo real nuestro catálogo de productos, y de pronto se pierde la conexión o se cae la base de datos, ¿no creéis que nuestro vendedor estaría encantado de poder seguir viendo el último volcado de la base de datos de productos, presente en su portátil, y almacenado en HypersonicSQL, en lugar de tener que volver al día siguiente cuando la base de datos se recuperase, o dentro de una semana cuando el cliente le diese cita, o simplemente nunca porque el cliente ya ha comprado a otro proveedor? Y lo más importante, ¿no creéis que nuestro vendedor y nosotros mismos quedaríamos mucho mejor y tendríamos más posibilidades de realizar la venta? A veces una aplicación de escritorio no es más efectiva por ser más rápida, sino por estar más disponible.


De todos modos no quiero finalizar desviándome tanto del asunto original. Es importante que minimicemos el acceso a recursos externos, tanto por la lentitud que provocan como por el punto de fallo que suponen. Casi siempre no nos quedará más remedio que hacerlo, pero en muchos casos encontraremos multitud de optimizaciones que nos ayudarán a mejorar el rendimiento de las aplicaciones.


9. Conocer a tu framework como a ti mismo.


¡¿Qué la aplicación ocupa 120Mb de memoria RAM?! ¡Es toda la RAM de nuestros PCs y no la vamos a cambiar!


Bien. Hemos llegado a un punto donde aplicamos todos los consejos anteriores y tenemos nuestra aplicación lista para el despliegue. La distribuimos a nuestros viejos PCs, y parece que todo va bien. De pronto resulta que comienzan a llover incidencias de que la aplicación se cuelga. Comprobamos el consumo de memoria, y ¡Oh, ocupa 120 Mb de RAM!. A partir de ahí se arma la marimorena.


¿Qué puede haber pasado? Conocemos nuestro código al milímimetro. Hemos optimizado hasta la más recóndita línea de código. Pero, ¿y los frameworks?, ¿conocemos los frameworks que estamos utilizando?, ¿hemos analizado como influyen en la aplicación?


Hace meses, implantamos en nuestras aplicaciones una librería bastante conocida que permitía desplegar diferentes look & feel para las aplicaciones. El objetivo era que el usuario pudiese cambiar el aspecto de las mismas entre varios definidos. La librería funcionaba perfectamente así que no se le dio mayor importancia. Tras varios meses, probamos a utilizar otra librería de look & feel, y la diferencia de consumo de memoria era de alrededor de 20Mb, nada más y nada menos. En nuestro caso, disponíamos de ordenadores con una capacidad moderada, así que no nos causó problemas, pero desde luego en otros casos podría haber sido un problema grave de no haberse detectado.


Nuestro fallo fue no analizar como influía la librería en las aplicaciones. Como funcionaba bien, tiramos para adelante, sin comprobar siquiera el consumo de memoria. Fue un error, y es preciso que todos aprendamos de ello. Siempre que se implante alguna librería, aunque parezca tan inofensiva como la de este ejemplo, deberíamos comprobar diferentes parámetros de funcionamiento de nuestra aplicación, como el rendimiento, el consumo de memoria, el acceso a disco, memoria virtual utilizada, etc., y a poder ser, utilizando algún profiler ( JMemProf, JProbe,JMP, ...) que nos dé datos exactos y no meras intuiciones.


Recordar que aunque nuestro código sea perfecto, puede que el de los demás no lo sea. No debemos fiarnos de donde viene el código. Por mucho que la fuente sea muy fiable, y que allí haya muchos gurús, todo el mundo puede tener un mal día y en cualquier parte puede haber una sección de código que cause problemas con nuestros sistemas.

10. Aprovechar al máximo lo que nos ofrece el Sistema Operativo.


¿Y no podría editar los informes en Word?


No hay duda de que esta es una de las frases más temidas por los desarrolladores. El momento en el que algún usuario influyente insinua la posibilidad de utilizar alguna herramienta integrada con el sistema operativo. Muy a menudo, la respuesta sería algo del estilo "Imposible, Word no se integra bien con nuestros sistemas Java, y sólo causaría problemas. Deberán continuar editando los documentos con nuestro editor.". Con esta respuesta, quizás consigamos evitar el enorme marrón que se nos viene encima, pero ¿por cuánto tiempo?, ¿y si el usuario tiene razón?, ¿y si nuestra aplicación mejoraría considerablemente soportando Word?. Muchas veces es necesario sacrificar la comodidad de los desarrolladores en pos de conseguir un producto más competitivo.


Uno de los ejemplos más claros del sacrificio de la multiplataforma en pos de rendimiento lo podemos encontrar en el mundo de desarrollo de juegos en Java. La mayor parte de los motores de creación de juegos, por otra parte escasos, tienen desarrollado en JNI todo el núcleo encargado del bucle de dibujado, gestión de eventos, etc., es decir, todo aquello que requiere obtener un rendimiento lo mayor que sea posible.


Antes de empezar a aprovechar todo lo que nos ofrece un sistema operativo y empezar a codificar código JNI como locos, debemos sopesar la importancia que tiene el mantener independiente de la plataforma nuestra aplicación. Si nuestra aplicación va a ejecutarse en múltiples sistemas operativos, tendremos que ir con mucho cuidado y preferiblemente utilizar componentes ofrecidos por frameworks que soporten ya varios sistemas operativos, ya que codificar código JNI y mantenerlo para múltiples sistemas es una tarea demasiado costosa. En caso de que nuestras aplicaciones no vayan a ejecutarse en múltiples sistemas operativos, algo que es muy común, entonces no hay razón para no ponernos manos a la obra y no comenzar a realizar llamadas a componentes ActiveX, inteteractuar con GTK, etc. Todo lo que ofrece el sistema operativo está ahí para solucionarnos la vida.


Aún así, implementar código JNI es algo complejo. Existen librerías como JNIWrapper, que permiten realizar llamadas JNI de manera muy sencilla contra determinados sistemas propietarios, y siempre será preferible el utilizarlas a escribir nosotros mismos todo el código. Por otra parte, existen otros frameworks que permiten el acceso a componentes del sistema operativo sin tener siquiera que escribir código JNI. Dos de los más utilizados son el soporte que incluye internamente SWT y el proyecto de Sun Microsystems JDesktop Integration Components (JDIC).



  • SWT ofrece soporte para la incrustación de componentes ActiveX dentro de nuestras aplicaciones. De este modo, podremos incrustar Microsoft PowerPoint, Microsoft Word, Microsoft Excel, Adobe Acrobat, Open Office o cualquier otra herramienta que se presente en forma de control ActiveX. Evidentemente, esto supone una mejora considerable en nuestras aplicaciones, ya que los usuarios están muy acostumbrados a utilizar estas herramientas, y les ahorraríamos muchísimo trabajo. Otro ejemplo de integración con el sistema operativo es la clase Tray que permite añadir un icono de nuestra aplicación a la bandeja de sistema del sistema operativo que nos encontremos utilizando, ya sea Linux, Windows, Mac OS, etc.



    Otro elemento muy interesante dentro de SWT es su clase Explorer que renderiza páginas web utilizando el navegador web característico de cada plataforma, Microsoft Explorer en Windows, Mozilla en Linux, etc. Además, es posible añadir listeners detectando los clicks, los eventos en el explorador, etc.


  • JDIC no ofrece un soporte tan grande para componentes ActiveX pero si que tiene multitud de funciones que nos permiten aprovechar el sistema operativo. Entre estas, destaca un componente Browser que permite incrustar Internet Explorer o Mozilla en nuestras aplicaciones. También un paquete que permite acceder a la bandeja de sistema. Otro paquete permite ejecutar programas externos simplemente con pasarle el nombre del fichero a abrir. En resumen, un conjunto de pequeñas herramientas que harán que nuestras aplicaciones sean más acordes con el trabajo diario de los usuarios.


.


Captura 4 : Open Office incrustado en una aplicación SWT abriendo un documento de Microsoft Word


Seguramente nuestro usuario estaría feliz con el interfaz de la captura de pantalla anterior. Debemos intentar aprovechar pues, todo lo que nos ofrece el sistema operativo, ya que además de estar optimizado para éste, nos ahorrará mucho trabajo y facilitará la vida del usuario.


Para finalizar


Diez consejos para mejorar nuestras aplicaciones de escritorio. Esto es lo que ha presentado este artículo sin pretender establecer ninguna base de ley. Unas veces se podrán aplicar todos. Otras veces se podrán aplicar unos cuantos. Y otras veces no se podrá aplicar ninguno, porque no tenemos más tiempo disponible o hemos heredado aplicaciones intocables de terceros. Lo realmente importante, es darse cuenta de que las aplicaciones de escritorio son un mundo muy diferente que el de las aplicaciones web, y lamentablemente, muy olvidado dentro de los libros y la literatura que podemos encontrar hoy por hoy sobre desarrollo de aplicaciones empresariales.


Intentar seguir estos consejos, nos ayudará a crear aplicaciones mucho más eficientes, robustas y agradables al usuario, lo que sin duda redundará en que tengan muchas más posibilidades de salir adelante exitosamente. La época de "Java es lento", "Swing es una tortura", ha quedado ya en el recuerdo. Pero ello no implica que las aplicaciones tengan exito, y a menudo, a pesar de que Java ya no sea lento, y de que el rendimiento de Swing haya mejorado, lo cierto es que el no tener cuidado creando aplicaciones empresariales de escritorio, hará que los fantasmas vuelvan a aparecer.


Esto es lo que pretende explicar este artículo. La creación de interfaces de usuario no es arrastrar dos botones sobre un panel con nuestro maravilloso entorno de desarrollo. La creación de interfaces de usuario es también planificar la creación de hilos en segundo plano, el decidir como, cuantos y que datos se van a precargar, el meditar como se van a visualizar los informes de nuestras aplicaciones, el pensar en que frameworks nos pueden ahorrar trabajo, o el planear el método de visualización de cargas masivas. Todo ese análisis hará que realment tengamos éxito, y de veras que espero haber aportado mi granito de arena con este artículo.




Acerca del autor

Martín Pérez Mariñán es Ingeniero Técnico en Informática de Sistemas. Además, es Sun Certified Java Programmer, Sun Certified Java Developer y Sun Certified Business Component Developer. Ahora mismo trabaja como Ingeniero de Software para la empresa DINSA Soluciones dentro del Complejo Hospitalario Universitario Juan Canalejo de A Coruña.. Durante los cuatro últimos años ha estado desarrollando aplicaciones de escritorio tanto en Swing como en SWT.


 

 


Reader Comments

There are no comments for this journal entry. To create a new comment, use the form below.
Comentarios deshabilitados
Comentarios deshabilitados en esta noticia.