====== Buenas Prácticas de programación JEE6 ======
--- //[[pedrody@um.es|PEDRO DELGADO YARZA]] 2014/02/07 12:56//
En esta sección de la Wiki vamos a comentar las buenas prácticas de programación estipuladas en las diferentes convenciones Java/JEE con la finalidad de conseguir un código uniforme para todos los desarrolladores y a la vez en concordancia con los estándares.
===== Convenciones Java =====
====Nombre de clases====
Los nombres de las clases han de comenzar con mayúscula y han de ser descriptivos sobre su tarea. En caso de nombres compuestos, no deben contener ningún carácter separador entre ambas (guiones altos, guiones bajos) en este caso la palabra que se concatena a la anterior debe empezar por Mayúscula en la primera letra y continuar con minúscula.
// Si queremos llamar a una clase "Mi clase compuesta"
public MiClaseCompuesta{
}
El idioma en el que se escriban los nombres de clases es indiferente, siendo recomendable el inglés por lo general, pero es aceptable el español para mejorar la descripción de las clases. Lo importante en este punto es ser coherentes y tener todas las clases, variables y métodos en el mismo idioma (salvo los generados automáticamente get, set, hasCode, equals...)
Según el tipo de objeto que representen las clases deben contener determinadas características en su nombre como indicamos a continuación:
* **Clase normal**: Nomenclartura normal, suficientemente descriptiva.
* **Interfaz**: Nomenclartura normal, suficientemente descriptiva.
* **Implemetación de interfaz**: Nombre igual que el de la interfaz que implementa acabado en **Impl**.
* **Bean de ámbito aplication**: Nomenclartura normal, suficientemente descriptiva con el prefijo **Controlador**.
* **Bean de ámbito session**: Nomenclartura normal, suficientemente descriptiva con el prefijo **Controlador**.
* **Bean de ámbito conversation**: Nomenclartura normal, suficientemente descriptiva con el prefijo **Manejador**.
* **Bean de ámbito page**: Nomenclartura normal, suficientemente descriptiva con el prefijo **Manejador**.
* **Clase de servicio**: Nomenclartura normal, suficientemente descriptiva con el prefijo **Servicio**.
* **Clase de utilidades**: Nomenclartura normal, suficientemente descriptiva con el prefijo **Util**.
Esta nomenclatura, adaptada a la estructura de proyectos Fundeweb se amplía con lo siguiente:
* Bean de respaldo: Controlador/Manejador +Nombre.
* DAS:
* API: Nombre de la entidad que mapea + **DAS**.
* Implementación: Nombre de la entidad que mapea + **DASImpl**.
* PAO:
* API: Nombre del paquete BD + **PAO**.
* Implementación: Nombre del paquete BD + **PAOImpl**.
* Servicios:
* API: **Servicio** + nombre.
* Implementación: **Servicio** + nombre + **Impl**.
* WebServices:
* SOAP:
* API: Nombre + **SOAP**.
* Implementación: Nombre + **SOAPImpl**.
* REST:
* API: Nombre + **REST**.
* Implementación: Nombre + **RESTImpl**.
====Declaración de variables====
Las variables se declararán una por línea, aunque tengamos varias variables del mismo tipo nunca las agruparemos en la misma línea. De esta manera podemos añadir comentarios a cada variable sin que afecte a la otra.
private int variable1; //Correcto
private int variable2;
private int variable1,variable2; //Incorrecto
====Variables y métodos====
Dentro de nuestra clase Java y tras la declaración de la misma, deben ir definidas las constantes y variables que se utilizarán en ella y que la definen. Las variables temporales que se usarán en los métodos **no** deben añadirse a esta parte.
Tras la definición de las variables procedemos a definir los métodos, dentro de los cuales los primeros en declarar deben ser los constructores, si los hubiera, y después el resto de métodos.
Por regla general, los metodos autogenerados get/set se recomienda dejar al final de la clase si no tienen ninguna lógica dentro, salvo la básica de asignación/recuperación de valores.
Todos los métodos deben estar encabezados por comentarios indicando brévemente su función y qué significa cada parámetro que recibe.
/**
* Realiza una búsqueda de provincias en base de datos
* @param query: Consulta a ejecutar
* @return: Listado de provincias
*/
public List autocompleteProvincia(String query) { ... }
Dentro de un método podemos declarar variables adicionales que tienen ámbito temporal a dicho método. Estas declaraciones deben ir siempre (salvo excepciones puntuales) al principio antes de cualquier secuencia de instrucciones.
/**
* Realiza una búsqueda de provincias en base de datos
* @param query: Consulta a ejecutar
* @return: Listado de provincias
*/
public List autocompleteProvincia(String query) {
List provincias = new ArrayList();
Query consulta = null;
this.entityManager.joinTransaction();
consulta = entityManager.createNamedQuery("obtenerProvinciasLike");
...
Las variables deben empezar por minúsculas y en caso de palabras compuestas no deben contener ningún carácter separador entre ambas (guiones altos, guiones bajos) en este caso la palabra que se concatena a la anterior debe empezar por Mayúscula en la primera letra y continuar con minúscula.
private int variableCompuesta; //Correcto
private int variable_compuesta; //Incorrecto
private int variablecompupesta; //Incorrecto
En el caso de representar la variable un bean existente ha de llamarse igual que la clase del bean pero empezando por minúscula. De esta manera el mecanismo de inyección de Seam puede actuar de manera automática.
@In
private BeanImportado beanImportado; //Correcto
@In
private BeanImportado beanimportado; //Incorrecto
@In
private BeanImportado manejadorPantalla; //Incorrecto
En el caso que dichas variables sean **constantes** se deben escribir íntegramente en mayúsculas y encontrarse justo después de la definición de la clase, por encima del resto de variables, permitiéndose en este caso usar el guión bajo "_" como separador de palabras.
private int MAXIMA_ALTURA = 4;
private String FRANJA_HORARIA = "+1";
====Codificación====
Cuando empezamos a codificar debemos tener en cuenta que cada línea equivale a una línea de código por lo que no debemos agrupar en la misma línea varias operaciones por simples que sean.
i++; //Correcto
j--;
i++;j--; //Incorrecto
Los bloques de código están delimitados por **{ ... }** no obstante en sentencias de una sola línea, Java permite obviarlos. Esta práctica va contra la legibilidad del código y contra la convención por lo que **siempre debemos poner los delimitadores de bloque.**
if(x > 1) { //Correcto
j++;
}
if(x > 1) //Incorrecto
j++;
Cuando usamos la sentencia **switch(condición)** debemos asegurar que siempre hay un caso **default**.
switch (condicion) {
case A:
...
break;
case B:
...
break;
default:
...
break;
}
=====Gestión de la aplicación=====
====Uso del Log====
Para mostrar la información de depuración por consola/fichero se ha de utilizar el log, **evitar** poner Systen.out.println en el código. El log podemos importar el que provee la aplicación usando **@In** en los bean manejados por el contendor, o bien crearnos uno nuevo como indica el siguiente código:
public class ClaseLog implements Serializable {
private Logger log = LogManager.getLogger(ClaseLog.class.getName());
...
}
En todos los métodos que se queramos tener log es muy recomendable que tengan una línea al inicio y al final indicando cuando se entra y se sale del método.
Las trazas de log ha de ser de tipo **DEBUG** en caso de que nos encontremos en fase de depuración. Una vez puesta la aplicación en producción el log recomendado es **INFO**. En nuestra aplicación debemos contener las trazas de log INFO necesarias y suficientes como para poder realizar una auditoría de lo que ocurrió en el servidor en un instante dado, así como el flujo de procesos y métodos por el que se ha pasado y quien ha sido.
Las excepciones también se gestionarán con el log, usando el tipo de traza **ERROR**, no se debe utilizar el output por defecto de la excepcion (método printStackTrace())
====Gestión de la memoria====
**Patrón Singleton**
Para mejorar el acceso a los recursos una de las mejoras que tenemos es el uso del patrón **Singleton** el cual garantiza que la instancia que cumple esa acción sólo existe una vez en memoria independiente de las veces que se la llame.
Si para una determinada clase hacemos un **new** cada vez que la necesitemos puede dar lugar a que en un instante determinado esa clase existe más de una vez en memoria puesto que aunque no la usemos ya, hay referencias hacia ella que la mantienen "viva". Si esta acción la realizamos con cierta frecuencia, podemos saturar la memoria con una clase que no necesitábamos tener repetida.
El patrón Singleton se puede especificar de varias maneras, bien con anotaciones, bien definiéndo manualmente:
* **@Singleton**: Con esta anotación sobre la definición de una clase, el mecanismo de inyección nos creará una instancia de esa clase sólo si no existía previamente, en caso contrario nos devuelve la que está viva.
@Stateless
@Name("servicioInput")
@Singleton
public class ServicioInput { ... }
* **Manualmente**: Creando dentro de la clase el mecanismo que implementa este patrón (método getInstance()).
public class CargasDatosDBGenerales {
private static Log log = Logging.getLog(CargasDatosDBGenerales.class);
private static CargasDatosDBGenerales cargador;
private CargasDatosDBGenerales() {
}
public static CargasDatosDBGenerales getInstance() {
if (cargador == null) {
cargador = new CargasDatosDBGenerales();
}
return cargador;
}
...
}
**Gestionar bien los ámbitos**
Gestionar de manera adecuada los ámbitos que maneja nuestra aplicación en cada instante es esencial para optimizar la memoria que estamos utilizando. Es importante elegir el ámbito correcto en un momento determinado y asegurarse que los objetos que no vamos a utilizar se liberen de memoria lo más rápido posible.
Es importante minimizar el uso de ámbitos que permanecen vivos durante mucho tiempo en la aplicación: **Aplication, Session**. Y gestionar de manera adecuada el ámbito **Conversation** para destruirlo cuando no necesitemos más sus objetos, asegurando que no se quedan abiertas conversaciones que ya no utilizamos.
**Importante**: El ámbito **Aplication** sólo debe usarse para almacenar o gestionar la configuración del servidor para nuestra aplicación. Todos los datos guardados en este ámbito permanecen en memoria hasta que el servidor se reinicie. Un redeploy no limpiará estos datos.
Para más información sobre la elección de ámbito puedes consultar la guía [[fdw2.0:fundeweb2.0:gt:elegir_el_ambito_correcto|Elegir el ámbito correcto]]
====Gestión del proyecto====
A la hora de gestionar el proyecto debemos ser cuidadosos ya que la configuración del mismo es tan importante como el código desarrollado. Para ello debemos hacer uso de los plugins que vienen instalados en Fundeweb 2.0 para asegurarnos una correcta configuración y despliegue del proyecto.
**Control del código**
Pese a que sigamos todas las convenciones y buenas prácticas de programación, siempre se nos pueden escapar trozos de código no estándar o bien podemos tener riesgos potenciales. Para evitar futuros problemas podemos hacer uso de los plugins dispuestos en el entorno: **CheckStyle y FindBugs**. Estos plugins buscan en nuestro código los factores descritos anteriormente de la siguiente manera:
* CheckStyle: Comprueba si el estilo del código sigue los estándares y convenciones establecidos.
* FindBugs: Busca fallos potenciales o errores que pueden no estar controlados.
Tras ejecutar estos plugins podremos solventar los problemas que nos indiquen y asegurar un código de mejor calidad.
**Maven**
Disponemos de un repositorio central de librerías en ATICA (archiva) a parte de conexión con el general de Maven. Estos repositorios garantizan que nuestro proyecto tenga las librerías necesarias y suficientes para funcionar correctamente.
Mediante maven, definimos en nuestro proyecto las librerías que queremos utilizar y bajo qué ámbito, de manera que podemos optimizar los recursos que queremos cargar en cada momento.
Es importante destacar, que en caso de que la librería que buscamos no se encuentre en el repositorio, sólo deberemos añadirla al proyecto como última instancia, previamente consultaremos al personal de MNCS si dicha librería puede subirse a los repositorios o si hay alternativas ya subidas. De esta manera tendremos mayor control sobre los proyectos, las actualizaciones y el código de terceros en general.
**Uso de test**
Es imprescindible realizar los casos de test necesarios y suficientes en los proyectos que garanticen la detección de errores en el mismo. Dichos test han de cubrir la mayor parte del código referida a la funcionalidad para asegurar que futuros cambios pueden ser testeados.
Para realizar dichos test disponemos de la herramienta TestNG, una vez implementados los test, el servidor de integración (Jenkins) los chequeará para comprobar los resultados y mostrarlos en su panel de gestión, lo cual es muy útil para el control del estado de los proyectos.
**Jenkins para realizar despliegues**
Los proyectos han de ser desplegados através del servidor de integración continua Jenkins dispuesto para tal fin. Gracias a él podremos realizar de manera casi automática parte de la gestión de calidad y configuración requerida en la gestión de proyectos.
Para saber más vista la wiki del [[https://wiki.um.es/wikis/programador/doku.php?id=fdw2.0:fundeweb2.0#servidor_de_integracion_continua_jenkins|| Servidor de Integración Continua Jenkins]]