BUENAS
PRÁCTICAS EN J2EE. PRIMERA PARTE.
VERSION
PARA IMPRIMIR
(Versión original en español en
../articulos/art6/art6-1.html
Enlace a la versión original en inglés)
Informe de
The Middleware Company
Traducido
y abreviado con permiso por
Dr.
Vicent-Ramon Palasí Lallana.
Gerente General de Aurum Solutions.
http://www.aurumsol.com
Noviembre 2003
J2EE
(“Java 2 Enterprise Edition” o Edición empresarial de Java 2)
ha emergido, en los últimos
años,
como una plataforma estándar para desarrollar aplicaciones
empresariales. Aunque está madurando, la plataforma
J2EE presenta todavía una serie de desafíos
como, por ejemplo, rendimiento, gestión de recursos
y flexibilidad. Los programadores que usan la tecnología
J2EE deben aprender a ir más allá de la sintaxis
de Java y comprender cuál es la mejor forma de diseñar
e implementar nuevas soluciones con J2EE.
Para ello, si no se tiene una amplia experiencia J2EE en
el mundo real, la mejor opción es aprender de la experiencia
de otros. La plataforma J2EE demuestra cada vez más
sus fortalezas y debilidades en entornos de producción.
Usted puede utilizar eficazmente esta experiencia si comprende
las buenas prácticas que se incluyen en este artículo.
¿Qué es exactamente una buena práctica?
Una buena práctica es una técnica comprobada
para conseguir un resultado deseado. Es una técnica
repetible que ha sido exitosa en situaciones de la vida real
y que puede aplicarse ampliamente a muchos problemas.
Estudiaremos las buenas prácticas asociadas a
cinco etapas generales que cualquier metodología de
desarrollo debe incluir: diseño, programación, pruebas, despliegue
y afinación (se omite la fase de análisis por no ser específica
de la plataforma J2EE). También explicaremos las buenas prácticas
aplicables al ciclo y a los entornos de desarrollo.
Por su longitud, dividimos este informe en dos partes, la
primera de las cuales se incluye a continuación.
1. BUENAS PRÁCTICAS SOBRE EL CICLO DE DESARROLLO
1.1. Buena práctica número 1. Enfrente el riesgo
lo más pronto posible
El riesgo asociado con el desarrollo crece exponencialmente
con la complejidad de la aplicación. Aunque este problema
parece no tener solución, hay algunas técnicas
que pueden aplicarse para mitigar los riesgos. En lo que
respecta al proceso de desarrollo, la mitigación de
riesgos más importante consiste en desplazar el riesgo
al principio del cronograma. De esta forma, se reduce el
riesgo por tres motivos:
Se mejora el conocimiento. Los mayores riesgos
se asocian siempre con lo desconocido. Un conocimiento más
temprano permite hacer decisiones más informadas y
definir mejores cronogramas a lo largo del ciclo de desarrollo.
Se permite tiempo de recuperación. Si se
fracasa, es mejor fracasar pronto, cuando hay todavía
tiempo para recuperarse.
Se involucra tempranamente al cliente. La respuesta
del cliente a una interfaz de usuario o a una función
clave puede impactar de forma dramática la dirección
de un proyecto. Además, involucrar tempranamente al
cliente mejora las relaciones con el mismo, al inspirarle
confianza de forma temprana.
Pruebas de concepto en forma de T
Las mayores áreas de riesgo de un proyecto suelen
incluir la interfaz de usuario y los componentes clave de
la capa de negocio. Se suelen usar enfoques diferentes para
tratar cada una de estas áreas. Para la primera de
ellas, se utiliza el prototipado de interfaces de
usuario,
que consiste en programar rápidamente unas cuantas
interfaces aproximadas, para obtener retroalimentación
del usuario de forma temprana. Para la segunda área,
se suele usar una “prueba de concepto” tecnológica
que consiste en implementar un único requerimiento
del sistema, pero de manera completa y profunda, con código
de producción. Como se ve en la figura 1, el prototipo
implementa una capa muy amplia, pero muy poco profunda. En
cambio, la prueba de concepto es muy profunda pero muy estrecha.
Figura
1. El enfoque en forma de T mitiga tempranamente
el riesgo
El método más efectivo para mitigar el riesgo
usa un enfoque híbrido, combinando ambos métodos
en una prueba de concepto en forma de T, como se muestra
en la figura 1. Se construye un prototipo de interfaz de
usuario muy poco profundo y se integra con una prueba de
concepto de un único requerimiento con una implementación
completa de todos sus aspectos. La interfaz del prototipo
será muy poco profunda, teniendo solamente en cuenta
el contenido y el flujo entre pantallas pero no la lógica
de negocio ni la apariencia estética. Por el contrario,
el requerimiento de la prueba de concepto se programará de
manera completa, profunda y robusta, con tanto código
de producción como sea posible y con el mínimo
andamiaje necesario.
Cuando se acabe la T y funcionen el modelo de interfaz de
usuario y el modelo técnico funcionen, el proceso
continúa expandiéndose desde la barra central
de la T para implementar más requerimientos.
Este enfoque “en forma de T” es una herramienta
efectiva para mitigar riesgo, desplazándo dos áreas
de incertidumbre hacia adelante
Otras técnicas para mitigar el riesgo
La prueba de concepto en forma de T no es la única
forma para mitigar riesgo. Las siguientes técnicas
son también efectivas:
Reserve tiempo suficiente para hacer investigación
y aprendizaje de tecnologías críticas en forma
temprana. Así aumentará su conocimiento sobre
los límites de estas tecnologías.
Antes del fin de un ciclo de proyecto, separe
un equipo de programadores para que comprendan los requerimientos
y tecnologías del siguiente ciclo.
Utilice tutores. Si no tiene ninguno en la empresa,
use a sus proveedores de tecnología o encuentre otros
consultores.
2. BUENAS PRÁCTICAS DE DISEÑO.
Las metodologías modernas de desarrollo ponen un
gran énfasis sobre la etapa de diseño pues
contribuye enormemente a la calidad, escalabilidad y fiabilidad
de una aplicación. Si diseñamos los componentes
de una aplicación de forma reusable y escalable, el
código que programemos en el futuro se construirá sobre
unos cimientos sólidos. Se necesita tratar varios
aspectos en esta etapa, como, por ejemplo, la seguridad,
el modelo subyacente de persistencia y los recursos de la
aplicación. Para la mayoría de las aplicaciones,
los problemas comunes tienen soluciones bien conocidas. Trataremos
algunas de ellas en el presente apartado.
2.1. Buena práctica número 2. Diseñe para
el cambio con un modelo de dominio dinámico.
Con la aparición del desarrollo iterativo y de ciclos
de desarrollo más breves, es casi seguro que una aplicación
sufrirá cambios después del diseño inicial.
Para manejar efectivamente estos cambios y conseguir un alto
nivel de calidad, los programadores no deberían volver
a programar código ya probado desde cero. Para evitar
esto, se necesitan modelos de dominio dinámicos, es
decir, que cambien de forma ágil.
Existen varias soluciones posibles para implementar un modelo
de dominio dinámico. Las más comunes son el
uso de “beans” de entidad CMP en EJB o de un
motor de persistencia convencional como Toplink. Estas herramientas
liberan a los programadores de la tarea de mantener el acceso
a datos y les permiten concentrarse en las reglas de negocio.
Como consecuencia, cuando se cambie el esquema de la base
de datos, se deberán cambiar muchas menos líneas
de código que las que sería necesario modificar
si no se utilizaran esas herramientas.
2.2 Buena práctica número 3. Utilice un lenguaje
de modelado estándar.
Los diseñadores y programadores, así como
cualquier persona involucrada en el proceso de desarrollo,
necesitan un lenguaje común para poder comunicarse
claramente. El lenguaje unificado de modelado (“Unified
Modelling Language” o UML) provee un lenguaje y una
variedad de diagramas para que los arquitectos y diseñadores
puedan explicar conceptos técnicos complejos de forma
sencilla y precisa.
Muchos entornos de desarrollo en Java ofrecen herramientas
para generar código a partir de modelos UML y a la
inversa. Esas herramientas permiten un código bien
documentado y una mayor productividad del programador, sin
cargar a este último con el peso de sincronizar el
código con los diagramas UML.
2.3 Buena práctica número 4. Recicle sus recursos.
Fondo Común de Objetos
Las aplicaciones suelen desperdiciar demasiado tiempo recuperando,
creando o destruyendo algunos objetos o recursos complejos.
Para evitar esto, se debería crear un número
limitado de recursos y compartirlos desde un fondo común
(“pool”, en inglés). J2EE gestiona algunos
de estos fondos, como, por ejemplo, los de conexiones.
Accediendo a un recurso existente mediante un fondo común,
el cliente disminuye los costos de creación, destrucción
e inicialización. Cuando el cliente acaba con el
objeto, devuelve el objeto al fondo común para que
sea utilizado por otros clientes.
Caché
Desde hace mucho tiempo, la industria de la computación
ha utilizado cachés de hardware y software para mejorar
el rendimiento desde el nivel más bajo de hardware
a la capa de software más alta. Una caché no
es más que un tipo específico de fondo común.
En vez de guardar en el fondo una conexión u objeto,
se guardan datos remotos, desplazándolos a una ubicación
más cercana al cliente. Muchos vendedores de hardware
y software suministran sus propias cachés de datos
para sus servidores Web y servidores de bases de datos. A
pesar de ello, puede ser útil programar cachés
específicas para tareas especiales, como servicios
Web, solicitudes cruzadas de red u otras.
3. BUENAS PRÁCTICAS DE PROGRAMACIÓN.
Por décadas, la etapa de programación fue
una fase larga y realizada de manera contigua. Sin embargo,
la mayoría de metodologías modernas rompen
esta etapa en iteraciones más pequeñas, para
reducir su riesgo y complejidad. Como consecuencia, los programadores
necesitan escribir código altamente flexible y reusable
y hacerlo muy rápidamente, lo que se logra aplicando
las técnicas que se explican en este apartado.
3.1. Buena práctica número 5. Utilice patrones
de diseño probados
Los patrones de diseño están entre los recursos
más importantes para arquitectos y programadores ya
que pueden usarse como se proponen o aplicarse a nuevos contextos.
Los patrones están probados y son reusables. Proveen
un lenguaje común para expresar la experiencia en
programación. En pocas palabras, los patrones de diseño
ahorran tiempo, dinero y esfuerzo.
Por brevedad, sólo presentamos algunos de los patrones
J2EE más importantes. Para una referencia más
completa, consulte el catálogo de patrones J2EE de
Sun.
“Session Façade” (Fachada de
sesión). Las aplicaciones EJB de grado fino son muy
propensas a presentar una sobrecarga en las comunicaciones.
Las fachadas de sesión proveen una capa de acceso
a servicios que oculta la complejidad de las interacciones
subyacentes y combina muchas comunicaciones lógicas
en una única comunicación física, mejorando
el rendimiento.
MVC. El patrón de diseño Modelo-Vista-Controlador
desacopla las capas de acceso a datos, presentación
de los datos e interacción con el usuario. Los beneficios
que resultan son un mantenimiento más sencillo porque
hay menos dependencias entre componentes, una mayor reusabilidad
pues los diferentes componentes implementan funciones distintas
y una configuración más sencilla, ya que se
pueden implementar nuevas vistas sin cambiar ningún
código subyacente. “Value Objects” (Objetos de valor).
Los objetos de valor son una única representación
de todos los datos que se necesitan en una llamada a un EJB.
Combinando todos los datos requeridos por una serie de llamadas,
se puede ejecutar una única llamada remota en vez
de muchas, aumentando el rendimiento global de la aplicación.
“Data Access Objects” (Objetos de acceso a
datos). Utilice un objeto de acceso a datos para abstraer
y encapsular todo el código de acceso al origen de
datos (normalmente, una base de datos). El objeto de acceso
a datos gestiona la conexión con dicho origen de datos
para recuperar y guardar datos, impidiendo que la lógica
de acceso a datos se mezcle con la lógica del negocio.
- “Service Locator” (Localizador de servicios).
Las aplicaciones empresariales necesitan el uso de componentes
distribuidos. A menudo, es una tarea difícil y costosa
obtener referencias a los componentes, como por ejemplo a
una interfaz “home” EJB. En su lugar, se pueden
gestionar dichas referencias a partir de un localizador de
servicios que centralice las búsquedas de objetos
distribuidos. Algunos beneficios de ello es contar con un único
punto de control y con un punto adecuado para colocar una
caché.
3.2. Buena práctica número 6. Automatice el proceso
de construcción
En un entorno de desarrollo empresarial, el proceso de construcción
de una aplicación (“build process”) puede
resultar muy confuso. Las interrelaciones entre archivos,
los programadores que se centran en un único aspecto
y los componentes de software ya empaquetados son factores
que se añaden a la complejidad de dicho proceso. Si
el proceso se ejecuta de forma manual, se pueden pasar incontables
horas intentando descubrir como construir todos los componentes
en el orden correcto. Por ello, es mucho mejor automatizar
este proceso, para lo cual se deben considerar tres aspectos
importantes:
Se deben escoger herramientas adecuadas para dicha
automatización.
El proceso de desarrollo y el proceso de construcción
deben interoperar bien.
El proceso de construcción debería notificar
el éxito o fracaso y permitir configurar los criterios
que definen este éxito o fracaso.
Si usted trabaja con la línea de comandos, debería
usar una herramienta de construcción. Hoy en día,
Ant es la herramienta escogida por los programadores de Java
en todo el mundo. Ant es una herramienta de construcción
extensible que está escrita en Java. Es un proyecto
de código abierto de Apache Jakarta que se ejecuta
sobre múltiples plataformas y puede contribuir en
gran medida a la flexibilidad del proceso de construcción.
3.3. Buena práctica número 7. Integre con frecuencia
El proceso de construcción es una parte integral
del desarrollo de aplicaciones. Es la primera oportunidad
para tratar los errores de integración. Por su importancia,
los procesos de construcción deberían tener
lugar tan frecuentemente como sea posible, una vez se haya
profundizado lo suficiente en el proceso de desarrollo para
tener código de integración. La integración
continua puede requerir una construcción diaria en
las aplicaciones de mayor tamaño o incluso construcciones
que tengan lugar cada vez que se registra un código
en la herramienta de control de versiones.
El motivo de todo ello es evidente. La integración
empeora con el tiempo. Cuanto más tiempo exista entre
integraciones, mayor será el esfuerzo necesario para
la integración. Mediante una integración constante,
se pueden identificar errores y depurarlos el mismo día
en que se añaden al sistema. Además, esta buena
práctica fomenta entre los programadores una cultura
de respeto mutuo y responsabilidad.
3.4. Buena práctica número 8. Optimice los costos
de comunicación
La comunicación distribuida puede ser bastante eficiente.
Sin embargo, cuando tiene lugar dentro de bucles y sobre
una costosa infraestructura de comunicación, los costes
de comunicación pueden crecer descontroladamente.
Este apartado incluye un número de sugerencias para
mejorar el rendimiento en la comunicación.
Utilice llamadas locales en vez de remotas.
Con la aparición de EJB, la programación distribuida
se ha hecho más sencilla. Debido a ello, se ha extendido
el uso de EJB y, con él, el mal uso del mismo.
Para evitar añadir sobrecarga inútil a la
aplicación, se debería determinar para cada
llamada a método si necesita ser distribuida. Si un
objeto Java local puede encargarse de la llamada y ningún
requerimiento de negocio impone una llamada remota, la llamada
debe ser local. Si el objeto que recibe la llamada no necesita
usar la seguridad del contenedor, el fondo común de
instancias, las transacciones gestionadas por el contenedor
y el objeto no necesita ser distribuido, entonces no utilice
un EJB.
Cuando realmente necesite un EJB, haga uso de las interfaces
locales cuando sea posible. Las interfaces locales permiten
pasar eficientemente parámetros por referencia y reducen
la sobrecarga. Si el cliente que hace la llamada se encuentra
en el mismo contenedor que el que la sirve puede hacer una
llamada local con un rendimiento mucho mayor.
Combine los datos.
Para mejorar el rendimiento, es una buena práctica
combinar los datos de una llamada a un método, tanto
los parámetros como el resultado. Para ello, puede
utilizar objetos. En vez de llamar a un método que
tiene como parámetros una dirección y un número
de teléfono, programe un método que tenga un
objeto “Persona” como parámetro. Esta
opción requiere un único objeto y mejora el
rendimiento de las llamadas locales y distribuidas. En lo
que respecta al resultado, como una llamada a método
sólo puede devolver un único resultado, combinando
los datos en un solo objeto puede devolver una mayor cantidad
de datos en una llamada sencilla. Por lo tanto, se necesitan
menos llamadas a métodos y se mejora el rendimiento.
Agrupe las solicitudes en lotes
Puede llevar todavía más lejos los beneficios
de la combinación de datos, enviando varias solicitudes
de datos similares en una única llamada. Realizando
estas solicitudes de forma conjunta, sólo se necesita
una única conexión al recurso. En el caso de
una base de datos, se requiere una única conexión
de base de datos y el SQL que se ejecuta necesita compilarse
sólo una vez, ahorrando un número considerable
de ciclos de cómputo. JDBC permite fácilmente
agrupar las solicitudes en lotes, aumentando el rendimiento.
Ponga los datos en una caché
Otro factor importante para implementar un esquema eficiente
de invocación de métodos es identificar datos
que son costosos de obtener y que cambian con poca frecuencia.
Usando una caché, un gran número de llamadas
costosas y que consumen tiempo pueden sustituirse por rápidas
lecturas de valores de la caché. Puede leer más
sobre cachés en el apartado “Buenas prácticas
de Diseño” en este mismo documento.
4. BUENAS PRÁCTICAS DE PRUEBAS
La mayoría de los programadores tratan la etapa de
pruebas (“testing”) como una obligación
menor que deben cumplir para complacer a los superiores.
Sin embargo, las pruebas de una aplicación no deberían
tener lugar solamente después de su programación
sino durante todo el ciclo de vida del desarrollo:
Se deben definir los casos de prueba durante el
análisis de requerimientos, permitiendo que los casos
se apliquen a todos los requerimientos.
Los programadores deberían escribir guiones de prueba
antes, o como mínimo al mismo tiempo, que el código
de aplicación. Este proceso asegura que cada línea
de código tiene un caso de prueba y que los programadores
programan la funcionalidad que se desea.
Los casos de prueba deberían ejecutarse durante la
integración, para verificar que no falla nada nuevo
y que no han aparecido nuevos errores en el sistema.
Las pruebas deberían producirse también en
tiempo de despliegue, sobre el proyecto completado, para
demostrar a todos los interesados que la aplicación
cumple todos los fines preestablecidos de rendimiento y escalabilidad.
En este apartado, se discutirán buenas prácticas
que mejoran la eficiencia y calidad de las pruebas
4.1. Buena práctica número 9. Escriba primero los
casos de prueba.
Tradicionalmente, los casos de prueba se han ejecutado separadamente
del código de aplicación. Este ineficiente
proceso está cambiando rápidamente, conforme
metodologías de desarrollo más ágiles
requieren a los programadores escribir casos de prueba antes
que el código de aplicación. Escribiendo primero
los casos de prueba, se pueden obtener varios beneficios
tangibles:
Es más probable que los programadores comprendan
la funcionalidad que se desea y que se centren más
en implementar el código que satisface los requerimientos.
A partir de este punto, las pruebas de integración
pueden tener lugar sin impedimentos ni trabajo adicional,
ya que los casos de prueba que existen aseguran el buen estado
del sistema.
Escribiendo primero los casos de prueba, la cultura de desarrollo
da un mayor valor a las pruebas.
Es menos probable que los programadores implementen código
extraño que no se necesite para satisfacer requerimientos
tangibles.
Una de las razones más comunes por las que se dejan
de ejecutar las pruebas es la falta de casos de prueba predefinidos.
En las etapas tardías del proceso de desarrollo, las
presiones sobre el tiempo frustran los esfuerzos de crear
guiones completos de prueba. Sin embargo, aunque al principio
pueda parecer un atraso, escribir primero los casos de prueba
ahorra tiempo, ya que los programadores se centran en completar
tareas relevantes y pasan menos tiempo en las pruebas de
integración y de sistema que se producen más
adelante en el ciclo de desarrollo.
4.2. Buena práctica número 10. Cree una plataforma
de prueba.
Las pruebas deberían ser un proceso automatizado
que pueda ejecutar cualquier persona en cualquier momento.
Esta regla asegura que las pruebas no se consideran una molestia,
sino una parte integral del proceso de desarrollo. Con una única
instrucción, un usuario o una aplicación debería
poder ejecutar una variedad de juegos de prueba para determinar
el estado global del código, así como el de
cualquier parte del mismo.
Para ello, los casos de prueba se pueden expresar en tres
maneras: con clases a la medida que escriben a la salida
estándar, con una plataforma comercial de pruebas
o con JUnit. La mayoría de organizaciones que programen
en Java utilizan JUnit debido a su destacada flexibilidad
y facilidad de automatización.
JUnit es una plataforma de pruebas de regresión de
código abierto escrita en Java. Ofrece una forma de
crear casos de prueba, agruparlos conjuntamente en juegos
de prueba y hacer afirmaciones sobre los resultados. Esto
permite una plataforma de pruebas completamente automatizada
que puede ser ejecutada por cualquier persona en cualquier
momento.
4.3. Buena práctica número 11. Automatice
las pruebas
Una vez se tiene una plataforma de pruebas, asegúrese
de que no sea molesta. Los programadores no deberían
tener que ejecutar manualmente las pruebas. Dada la gran
importancia de las pruebas, deben ser algo que ocurra como
una parte normal del proceso de desarrollo. Esto llevará a
un número más alto de pruebas ejecutadas y
a un menor número de errores.
Para ayudar a añadir las pruebas al proceso de construcción,
Ant tiene dos formas de ejecutar pruebas en JUnit: con una
tarea que ejecuta clases Java o con una tarea específica
para ejecutar casos de prueba en JUnit. Usando estas opciones,
se pueden integrar eficazmente las pruebas al resto del proceso
de desarrollo.
Compruebe la calidad de nuestro desarrollo offshore.
Aurum Solutions tiene experiencia en desarrollo offshore en
J2EE y .NET de alta calidad y reducidos costos para empresas
europeas y norteamericanas. Conozca nuestro
desarrollo offshore haciendo
clic aquí o
en la página Web ../../offshore.html.
Cursos de Aurum Solutions relacionados con el tema de este
artículo:
“Buenas prácticas en arquitecturas J2EE”
“Buenas prácticas en programación en Java”
“Gestión de proyectos informáticos”
“Análisis y diseño orientado a objetos”
“Programación en n-capas”
“Programación MVC”
“Patrones de diseño”
“Persistencia para Java”
“Enterprise Javabeans”
“Servlets y JSP”
“Herramientas de pruebas para Java”
|