conventional commits y algunas herramientas de apoyo

¿ qué es conventional commits ?

CommitLint es una herramienta que ayuda a adoptar una convención para escribir commits de forma estandarizada. esta convención se llama Conventional Commits y esta relacionada con un estándar a la hora de versionar artefactos llamada Semantic Versioning.

La especificación de Conventional Commits es una convención sobre cómo escribir los mensajes de commits (específicamente el título del commit). Además nos provee de un conjunto sencillo de reglas para crear un CHANGELOG de commits explícito; lo que hace más fácil escribir herramientas encima del historial. Esta convención encaja con SemVer, al describir en los mensajes de los commits las funcionalidades, parches y cambios de ruptura hechos.

El mensaje del commit debe ser estructurado de la siguiente manera:

1
2
3
4
5
<tipo>(ámbito opcional): <descripción>

[cuerpo opcional]

[nota(s) al pie opcional(es)]

Por ejemplo:

1
2
3
4
5
fix(api): se corrige validacion de email

Se agregan nuevos dominios a la validación del correo

Issue #2

Los tipos que más se utilizan en Conventional Commits son los siguientes:

  • feat: Se agrega una nueva característica
  • fix: Se corrige algún problema
  • chore: Se modifican archivos que rodean el código fuente (configuraciones, cambios de versión)
  • refactor: Refactorización del código
  • docs: se agrega o modifica documentación
  • test: Se agrega o modifican tests

Los tipos relevantes a la hora de generar valor en el CHANGELOG son los feat y los fix, en base a esos tipos se construye de forma automática la bitácora de cambios relevantes (útil al usar standard-version, leer mas adelante)

Instalar commitlint

1
yarn add -D @commitlint/{cli,config-conventional} 

Si estas usando commonsjs debes cambiar el export default por module.exports. debes revisar el atributo type dentro del archivo package.json.

Ejecuta el siguiente comando para crear el archivo commitlint.config.js

1
$ echo "export default { extends: ['@commitlint/config-conventional'] };" > commitlint.config.js

O agregar este archivo en la raíz del proyecto:

1
2
3
export default { 
extends: ['@commitlint/config-conventional']
};

Demás esta decir que puedes extender la configuración y acomodarla a tus necesidades, sólo considerar que las herramientas que se basan en estas convenciones también deberás adaptarlas y en otros casos es posible que no te sirvan.

husky

Husky es una herramienta que permite gestionar de forma simple git-hooks y que nos permite que cada vez que hagamos un commit, por ejemplo, corra los test unitarios o que nos revise si el commit message cumple con la convención de conventional commits (que es nuestro caso).

1
2
3
4
5
yarn add -D husky 
yarn husky init

npm pkg set scripts.commitlint="commitlint --edit"
echo "yarn commitlint \${1}" > .husky/commit-msg

El ultimo comando (el echo), genera un archivo cuyo contenido es la ejecución de commitlint y será ubicado en el directorio de los hooks de husky.

Nota: Cuando ejecutas el comando yarn husky init este te genera un directorio llamando .husky y dentro quedará un archivo llamado pre-commit con el siguiente contenido:

1
yarn test

Esto quiere decir que antes de que se realice el commit, husky ejecurata el comando yarn test y si este comando sale sin problemas, se efectuará el commit, en caso contrario los archivos no serán agregados al historial.

standard version

Otra herramienta que nos puede ayudar a mejorar la forma en que realizamos los releases, es una utilidad llamada standard-version (es algo vieja pero funciona perfectamente). Según su propia descripción:

Una utilidad para el versionamiento usando semver y generación de CHANGELOG potenciados por conventional commits.

1
yarn add -D standard-version

Una vez agregada la librería se debe agregar un comando en la sección de scripts en el package.json (sólo para mayor comodidad).

1
2
3
4
5
"scripts": {
...
"release": "standard-version",
"pre-release": "standard-version --dry-run"
}

Con eso ya puedes usar los comandos release y pre-release, el primero para concretar el release y el segundo es para ver como quedará de forma verbosa pero sin afectar el repositorio. Por ejemplo:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
$ yarn pre-release

yarn run v1.22.19
$ standard-version --dry-run
✔ bumping version in package.json from 1.0.0 to 1.1.0
✔ created CHANGELOG.md
✔ outputting changes to CHANGELOG.md

---
## 1.1.0 (2024-04-22)


### Features

* se agrega busqueda de app por su id
* se agrega funcion para eliminar aplicacion
* se agrega persistencia (find,delete,create) de app
* se agrega persistencia de eventos por app
* se agrega validacion de esquema para applicacion
* se agregan controladores para aplicaciones y logs


### Bug Fixes

* **ci:** se corrige nombre de la imagen mongodb
* **ci:** se corrige script para test, falta instalar deps
* se corrigen test unitarios
* se incluye close connection
---

✔ committing package.json and CHANGELOG.md
✔ tagging release v1.1.0
ℹ Run `git push --follow-tags origin develop` to publish

Nótese que el comando de pre-release nos indica exactamente lo que hará el comando release, donde hay que resaltar la linea bumping version in package.json from 1.0.0 to 1.1.0 que indica, en función de commits messages, califican para cambio de minor (feat agregados) o patch (fix agregados). Por ejemplo… si entre versiones solo tienes commits messages con fix cuando hagas el release, lo hara modificando el dígito del patch, por otro lado, si agregas un nuevo feature (feat) te modificará el minor en la versión.

Si requieres hacer un versionamiento manual debes ejecutar el siguiente comando:

1
yarn release --releas-as 1.1.1

Con eso fuerzas a que la versión sea la que indicas como argumento.

GitFlow

Si usas git-flow como modelo de ramas, te sugiero configurar standard-version para que no realice el tag una vez que hagas el release, de esta forma le delegas dicha función a git-flow. Les dejo el archivo de configuración que se llama .versionrc y debe estar ubicado en la raíz del proyecto:

1
2
3
4
5
{
"skip": {
"tag": true
}
}

Con esta configuración, puedes lanzar los comandos de release en la rama hotfix o release y luego finalizas las rama con git-flow.

Palabras al cierre

Espero les sirva este pequeño post sobre herramientas de desarrollo que utilizo en el día a día y sus configuraciones.

Les dejo los links de las referencias:

Manejo de excepciones en Spring Boot

Motivación

Se requiere un mecanismo global o centralizado basado en las excepciones, para poder manejar y traducir en errores entendibles hacia las integraciones (frontend y/o otros clientes).

Introducción

La gestión de excepciones en el sentido amplio de la palabra, se refiere a la captura de eventos de tipo errores que se producen en las aplicaciones durante su ejecución. Estos errores pueden ser de muchos tipos, desde fallas de red, errores en accesos a una base de datos, errores en tiempo de ejecución (runtime) o simplemente por validaciones de negocio.

El manejo de excepciones tiene como objetivo hacer más seguras y robustas las aplicaciones así como también tener un mecanismo para dar una respuesta elegante a los usuarios mejorando la confiabilidad y resistencia a fallas en las aplicaciones.

Usos

  • Manejo global de excepciones:

    • ¿ Cuándo usarlo ?
      • Cuando se necesita un formato de respuesta de error consistente para toda la aplicación.
      • Para evitar exponer trazas de la aplicación con información relevante, de esta forma evitar posibles futuros atacantes.
  • Manejo de excepciones en el controlador:

    • Utilizar cuando se necesita una solución particular (distinta a la ofrecida por el manejo de errores globales), como por ejemplo, integraciones donde necesitamos adaptar nuestra respuesta.

Implementación

Para su implementación, Spring Boot nos provee algunas herramientas, las más comunes para este tipo de manejadores son @ControllerAdvice y de @RestControllerAdvice, el primero para cualquier tipo de controlador anotado con @Controller (MCV de Spring) y el segundo esta especializado en controladores de tipo Rest (aplica sólo a los controladores anotados como @RestController)

Para que estos componentes puedan funcionar correctamente y como su nombre lo indica, deben poder atrapar excepciones del tipo específico (jerarquía) que necesitemos manejar y hacer la interpretación de la excepciín, como por ejemplo: se requiere atrapar cualquier Exception del tipo IllegalArgumentException y convertirla en un bad request (error 400 de http).

Hablemos un poco de excepciones

Los manejadores de excepciones siempre actúan cuando una excepción es lanzada y no ha sido atrapada por la capa de controladores, éstas pueden ser lanzadas desde las capas de acceso a datos, directamente desde la capa de negocio o simplemente desde la capa de controladores (validación de entrada).

Aquí la discusión se vuelve un poco densa y depende mucho el cómo quieres trabajar tu jerarquía de excepciones, puede ser tan compleja como quieras (casos en que necesitas mucho detalle en la respuesta del manejador) o tan simple como mantener un par de excepciones que manejar e interpretar.

En un articulo anterior escribí acerca de tipos de excepciones, jerarquía y características el manejo de excepciones. Les dejo aquí el enlace a este artículo Checked y Unchecked Exception.

Una de las alternativas mas simples es utilizar Excepciones de tipo RunTimeException ya que tienen la característica de no ser atrapadas de forma imperativa o declarativa, de esta forma excepciones que son lanzadas en las capas inferiores saltan hasta el manejador de Excepciones, claro está, solo si son manejadas en dicho manejador.

Veamos un ejemplo

En SpringBoot tenemos un par de mecanismos para poder manejar excepciones y que nos permite evitar exponer el stack trace directo al usuario (nos da seguridad en caso de exponer algún dato sensible) y en su reemplazo nos permite modelar una salida elegante dependiendo de la excepción lanzada.

Para efectos prácticos en este artículo utilizaremos sólo excepciones de tipo Runtime ya que este tipo de excepciones suben por el stack sin ser capturadas por nuestro código hasta llegar al lugar donde queremos hacer el manejo de la excepción.

Rest Controller Advice

GlobalExceptionHandleres una clase que se anota con @RestControllerAdvice y es donde podremos hacer el manejode la excepción. Veamos un ejemplo a continuación:

En este primer ejemplo, se evidencia que se atrapan las exepciones especificas y mas altas de la jerarquía, NotFoundException, BadRequestException y UnauthorizedException, ademas para cada una de ellas hay un método disponible donde podremos por ejemplo hacer un log del error para tener el detalle de lo ocurrido y finalmente enviar hacia el consumnidor, el correspondiente codigo Http para cada caso. Nota: en este ejemplo el body de la respuesta es vacío.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
package cl.pcollaog.eh.handler;

import cl.pcollaog.eh.exception.BadRequestException;
import cl.pcollaog.eh.exception.NotFoundException;
import cl.pcollaog.eh.exception.UnauthorizedException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;

@RestControllerAdvice
public class GlobalExceptionHandler {

private static final Logger LOGGER = LoggerFactory.getLogger(GlobalExceptionHandler.class);

@ExceptionHandler(BadRequestException.class)
public ResponseEntity<Void> badRequestHandler(BadRequestException bre) {
LOGGER.error("badRequestHandler - message {}", bre.getMessage());
return ResponseEntity.badRequest().build();
}

@ExceptionHandler(NotFoundException.class)
public ResponseEntity<Void> notFoundExceptionHandler(NotFoundException nfe) {
LOGGER.error("notFoundExceptionHandler - message: {}", nfe.getMessage());
return ResponseEntity.notFound().build();
}

@ExceptionHandler(UnauthorizedException.class)
public ResponseEntity<Void> unauthorizedExceptionHandler(UnauthorizedException ue) {
LOGGER.error("unauthorizedExceptionHandler - message: {}", ue.getMessage());
return ResponseEntity.status(HttpStatus.UNAUTHORIZED).build();
}
}

En este segundo ejemplo, vamos a manipular la respuesta (vamos a suponer que la integración lo pide así) para cada caso.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
package cl.pcollaog.eh.handler;

import cl.pcollaog.eh.exception.BadRequestException;
import cl.pcollaog.eh.exception.NotFoundException;
import cl.pcollaog.eh.exception.UnauthorizedException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;

import java.time.LocalDateTime;

@RestControllerAdvice
public class GlobalExceptionHandler {

private static final Logger LOGGER = LoggerFactory.getLogger(GlobalExceptionHandler.class);

@ExceptionHandler(BadRequestException.class)
public ResponseEntity<CustomResponse> badRequestHandler(BadRequestException bre) {
LOGGER.error("badRequestHandler - message {}", bre.getMessage());
CustomResponse cr = new CustomResponse((HttpStatus.BAD_REQUEST), bre.getMessage());
return new ResponseEntity<>(cr, cr.getStatus());
}

@ExceptionHandler(NotFoundException.class)
public ResponseEntity<CustomResponse> notFoundExceptionHandler(NotFoundException nfe) {
LOGGER.error("notFoundExceptionHandler - message: {}", nfe.getMessage());
CustomResponse cr = new CustomResponse((HttpStatus.NOT_FOUND), nfe.getMessage());
return new ResponseEntity<>(cr, cr.getStatus());
}

@ExceptionHandler(UnauthorizedException.class)
public ResponseEntity<CustomResponse> unauthorizedExceptionHandler(UnauthorizedException ue) {
LOGGER.error("unauthorizedExceptionHandler - message: {}", ue.getMessage());
CustomResponse cr = new CustomResponse((HttpStatus.UNAUTHORIZED), ue.getMessage());
return new ResponseEntity<>(cr, cr.getStatus());
}

public static class CustomResponse {

private final LocalDateTime date;

private final HttpStatus status;

private final String message;

public CustomResponse(HttpStatus status, String message) {
this.date = LocalDateTime.now();
this.status = status;
this.message = message;
}
// ... Omitiré los getter
}
}

Para el caso de NotFoundException la respuesta será la siguiente:

1
2
3
4
5
6
7
8
9
10
11
12
HTTP/1.1 404 
Connection: keep-alive
Content-Type: application/json
Date: Sat, 16 Mar 2024 22:20:02 GMT
Keep-Alive: timeout=60
Transfer-Encoding: chunked

{
"date": "2024-03-16T19:20:02.462356",
"message": "Entity not found",
"status": "NOT_FOUND"
}

Para el caso de BadRequestException la respuesta será la siguiente:

1
2
3
4
5
6
7
8
9
10
11
HTTP/1.1 400 
Connection: close
Content-Type: application/json
Date: Sat, 16 Mar 2024 22:16:41 GMT
Transfer-Encoding: chunked

{
"date": "2024-03-16T19:16:41.886069",
"message": "Invalid argument",
"status": "BAD_REQUEST"
}

Finalmente para el caso de UnauthorizedException la respuesta será la siguiente:

1
2
3
4
5
6
7
8
9
10
11
12
HTTP/1.1 401 
Connection: keep-alive
Content-Type: application/json
Date: Sat, 16 Mar 2024 22:21:38 GMT
Keep-Alive: timeout=60
Transfer-Encoding: chunked

{
"date": "2024-03-16T19:21:38.011316",
"message": "Invalid Token",
"status": "UNAUTHORIZED"
}

Como podrán ver, en el handler (método) podrán manejar la excepción a conveniencia y podrán enviar el mensaje y codigo de error que necesiten hacia el consumidor del servicio.

Para finalizar les dejo un diagrama del funcionamiento del GlobalExceptionHandler con el ejemplo de 404 Not Found.

Si tienen alguna observación no duden en comentar.

AWS Lambda - ESM Node.js

Algunas generalidades previas

En la mayoría de los paradigmas de programación, podemos encontrar sistemas que nos permiten auto organizar el código en pequeñas piezas o para incorporar liberías/bibliotecas de terceros a nuestro código. Al combinar todo lo anterior resulta una pieza de código más grande y compleja.

Desde un inicio Javascript utilizó el sistema de carga de módulos llamado CommonsJS (CJS) y es parte integral de Node.js hasta la version v8.5.0 donde se incorpora un nuevo sistema de carga de módulos, ESM. A partir de la version v13.2.0 de Node.js fue estabilizado e incorporado como un nuevo estándar.

¿ Por qué deberíamos usar ESM en AWS Lambdas ?

James Beswick (Principal Developer Advocate for the AWS Serverless Team) escribió un artículo titulado Using Node.js ES modules and top-level await in AWS Lambda ,donde detalla el por qué y en qué casos deberías usar ESM como cargador de módulos de Javascript en el contexto de un AWS Lambda. Uno de los motivos más importantes de usar ESM, es que la carga en frío de un lambda tarda casi un tercio en comparación con CommonsJS.

Dejo aquí la comparativa entre CJS y ESM donde en el p99 (carga en frío) la partida se reduce a un tercio mejorando el rendimiento en un 43,5%. Para esta prueba, todas las métricas de ESM salieron por debajo (mejores en tiempo) de las de CJS es simplemente marginal (alrededor de 2-5ms).

¿ Cómo usamos ESM en AWS Lambdas ?

Configuración del artefacto - package.json

Primero que todo se deben ajustar un par de atributos en el archivo package.json para indicar que el módulo es del tipo ESM.

package.json
1
2
3
4
5
6
7
8
9
{
"name": "serverless-lambda-esm",
"version": "1.0.0-beta",
"description": "Serverless Lambda ESM",
"main": "src/Handler.js", // <-- Sera cargado como módulo
"license": "UNLICENSED",
"private": true,
"type": "module", // <-- Este atributo debe tener el valor: module
}

Con este ajuste le indicamos al cargador que trate los archivos como ES módulos según su extensión, es decir, con el atributo type y valor en module todos los archivos con extensión .js serán tratados como ESM. Si por alguna razón quieres mezclar ambos mundos debes hacerlo de forma explícita usando como extensión .cjs. Por el contrario, si no utilizas el atributo type o lo dejas con valor commonjs, todos los archivos con extension .js serán tratados como CJS y si quieres utilizar ESM estos deben tener extension .mjs.

Preparamos el Handler

Handler.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import { DateTime } from "luxon";

/**
*
* @param {*} event
* @returns
*/
export const handler = async (event, context) => {
const date = DateTime.now().toFormat("yyyy-MM-dd");
console.log("Date: ", date);
console.log(JSON.stringify(event));

return {
statusCode: 200,
body: JSON.stringify({ message: "ok" })
};
};

El handler debe estar expuesto de esa forma para que sea cargado como ESM. Se puede apreciar que ya no usamos la sentencia require para cargar una dependencia externa (en esta caso luxon) y en su reemplazo utilizamos import.

Algunos detallitos que he ido aprendiendo en el camino y que no está muy explícito en la documentación, es que los imports de nuestros archivos, es decir, el código que está dentro del proyecto, deben ser cargados y nombrados con su extensión, dejo un ejemplo:

Handler.js
1
2
import { DateTime } from "luxon"; // <-- Sin la extension (lib)
import Service from './service/Service.js'; // <-- Con la extension

Export de los módulos

Service.js
1
2
3
4
5
6
7
8
9
10
11

const Service = {
/**
* Funcion
*/
doSomething: async(event)=>{
console.log("Event: ", JSON.stringify(event));
}
};

export default Service; // <-- Así se exponen los módulos al código

Palabras al cierre

Espero les sea útil para sus desarrollos de lambdas con Node.js. Más adelante ire dejando nuevos artículos con ejemplos e ideas para implementar con AWS Lambdas. Pase y deje su comentario.

Enlaces de interés

Escuchando Subterranean Homesick Alien del disco OK Computer

Radiohead

Nuevo Comienzo

De vuelta

Hola, se que no he escrito en mucho tiempo, muchísimo tiempo.

Partiré diciendo que me cambie de sistema de blog desde Jekyll (ruby… puaj) a Hexo (nodejs) solo por salir de una tecnología que simplemente no va conmigo y tampoco le he dado el suficiente tiempo para entenderlo. Hexo es una plataforma para generar contenido estático (blog) a partir de archivos markdown, está desarrollado sobe NodeJS y todos sus componentes (plugins y themes) siguen la misma línea. Debo decir que hay muchos componentes que están bien des-actualizados y muchos de ellos con documentación en algo que parece ser Chino.

Ha pasado muchas cosas desde la última vez que escribí, tuvimos un estallido social en Octubre del 2019 y luego nos remeció una pandemia que nos estuvo encerrados un buen tiempo. De alguna forma ambos eventos forzaron al trabajo remoto y muchas empresas nos vimos obligados a adaptarnos a la nueva forma de trabajar y relacionarnos entre los diferentes equipos.

En Pandemia

En pandemia no había mucho que hacer (fuera de la casa) así que aplicamos creatividad y aprendí a hacer Pan Amasado y pan con Masa Madre (al igual que muchos). Esto debido a que en esos días salir a comprar el pan era de riesgo vital.

En mitad de la pandemia nos cambiamos de casa apenas se dió la oportunidad (los permisos de mudanza estaban cerrados). Ya instalados en el nuevo HQ comenzó la nueva aventura de colegio virtual. No fue fácil y menos para la Vale, no generó un vínculo con sus profesores a través de una pantalla. Por otro lado la Javi se adaptó de una forma increíble a los quehaceres del colegio.

Afortunadamente para nuestro rubro, la pandemia aceleró muchos procesos informáticos y como mencioné mas arriba, presionó para que el trabajo remoto se consolidara como LA nueva forma de trabajo. Muchas compañías necesariamente tuvieron que crear nuevos sistemas y aplicaciones para atender a sus clientes o darles la posibilidad de no ir a hacer un tramite presencial (no se podía), lo que de alguna forma benefició a muchas empresas de desarrollo.

Estando encerrado aprendí cosas muy básicas sobre mantenimiento de guitarras (no quiero decir lutheria porque estoy a años luz de ese conocimiento) para justamente echarles una “manito de gato” a mis guitarras. Fue tanto el entusiasmo que me armé una guitarra que compre en Amazon, un kit DIY Do it yourself estilo PRS.

Por supuesto que no fue fácil y creo que las dos etapas críticas fueron pegar el mástil al cuerpo y la “pintura”, en ese orden de complejidad. Digo “pintura” pero en realidad es una técnica de teñido de la madera con tinturas basadas en anilina y agua. Les dejo parte del proceso:

Ahora esa guitarra es una de mis favoritas por varias razones, tiene un sonido único como hecha para tocar metal, es cómoda y tiene un sustain exagerado. Por lo demás es escala 24 1/2 al igual que todas las otras guitarras que tengo. Cosas que le faltan, aparte de un buen guitarrista XD, mejorar la entonación y cambiar el puente porque lo rompí y no hay repuesto. Afortunadamente la cuerda mas difícil de todas la G quedó perfectamente entonada para el calibre de cuerdas que usa esa guitarra.

Eso es todo por hoy… iré agregando mas contenido a mi blog que dicho sea de paso tiene 19 años (ni yo me lo creo).

Adiós 2017... bienvenido 2018

Un año más, que se va,
un año más, cuántos se han ido.
Un año más, que más da,
cuántos se han ido ya…

Hernán Gallardo Pavéz

Esta canción es casi un himno patrio de fin de año y no hay fiesta donde no se toque esta canción, que dicho sea de paso, ha sido interpretada por varias bandas. Aquí les dejo un link para que lean acerca de la historia de ésta canción.

Symbiose SpA y Tecnología

Este año ha sido particularmente extraño en lo que a negocios y pega se refiere. Tuvimos elecciones presidenciales y que durante lo que dura la “incertidumbre”, los proyectos y las inversiones en tecnología se aletargar paralizan bastante. Una vez pasada la “incertidumbre” (para bien o para mal) se vuelven a activar los proyectos. En Symbiose SpA hemos sentidos esos embates del mundo económico y político aunque no tengamos que ver con ninguno de los dos.

En lo técnico quizás lo mas novedoso ha sido programar en nuevos frameworks y lenguajes. Uno de los nuevos frameworks con los que he tenido que sufrir lidiar ha sido Angular + TypeScript. Solo les puedo comentar que este último lenguaje ha sido vilipendiado de la peor forma en el trabajo y básicamente por lo odioso que es el “compilador” y los mensajes de error que salen en la consola de desarrollo del browser (sólo si es que salen). Hace mucho tiempo que no rabiaba tanto con algo que me gusta hacer.

En comparación, usando AngularJS como framework, es bastante mas legible y decente los errores que salen en la consola y los errores típicos son los dolores de cabeza que te da Javascript, que de alguna u otra forma, muchos developers tenemos interiorizados.

Sin lugar a duras en temas tecnológicos el uso de contenedores ha sido protagonista en el desarrollo de microservicios y la aceptación de esta arquitectura que viene de hace hace un par de años. En este ámbito Docker ha sido una de las plataformas que se mantiene en la pelea de la “containerización” junto a Kubernetes.

El uso que le damos a diario a estas herramientas van de la mano con Continuous Integration y Continuous Delivery evitando así HH preciadas de los desarrolladores peleando contra los servidores de aplicación, bases datos y cuanta aplicación ofrezca pelea. Para resumir este punto, que mejor que utilizar la palabra AUTOMATIZACIÓN.

Para finalizar lo técnico, sigo acumulando horas de vuelo SpringFramework y toda sus componentes orientados a crear microservicios y ambientes cloud. Cada release nueva de estos componentes traen un montón de nuevas funcionalidades que nos hace la vida mas fácil a la hora de pensar en soluciones Cloud. Kudos para los developers de SpringFramework.

Libros varios

Un hobby que había dejado totalmente en el olvido era el de leer. Tenía mi Kindle juntando polvo en algún rincón hasta que me puse la meta de leer al menos 1 libro al mes (empezando como en septiembre) y puedo decir que me fue bastante bien. Les dejo un listado de libros interesantes que pude leer o terminar de leer durante el año pasado:

Cabe destacar todos estos libros son de escritores Chilenos y que por cierto disfruté cada uno en su ámbito. El libro de Patricio Bañados es como estar tras bambalinas de TVN en los tiempos donde nuestro país paso por un periodo triste de su historia. Pero lo mas interesante es cuando superamos el periodo de la dictadura y las historias de codazos, volteretas y bajezas políticas se hacen presente en la vida del Relator.

Los libros de Ciencias de José Maza, Gabriel León y María Teresa Ruíz todos tienen la característica de un lenguaje muy simple y de fácil comprensión. Todos ellos muy entretenidos. Ciencia Pop tiene muchas anécdotas científicas y descubrimientos que se han hecho de casualidad (como la mayoría de los descubrimientos científicos). Somos polvo de estrellas y Hijos de las estrellas nos invitan a reflexionar sobre el origen del universo, sobre el origen de la vida y de qué estamos hechos, literalmente… Polvo de estrellas.

Sobre la Naturaleza del Software, también es un libro de fácil lectura con muchas anécdotas del mundo informático. También invita a reflexionar sobre la evolución de la Ingeniería Informática, los errores recurrentes en esta área, de lo joven que es esta rama de la ingeniería y las comparaciones odiosas con otras ramas. Destaca a grandes personajes que han participado en la evolución del cómo se construye software hasta el día de hoy.

En proceso de lectura aún:

Sobre “Contacto” de Carl Sagan, es un libro lleno de detalles y abundante en información de los personajes… muy muy descriptivo (a veces aburre un poco). Lo que me chocó es que uno espera una cierta similitud con la película Contacto y dicha similitud no es tal. La película la habré visto al menos una docena de veces y siempre emociona el momento en que empieza a escuchar los pulsos que provienen de la estrella Vega o al final cuando les habla apasionadamente a los niños sobre el universo. El libro es totalmente diferente o mejor dicho… la película es “basada” en la novela original de Carl Sagan.

Lo mismo me pasó con “Yo, Robot”, la película es también una de mis favoritas y con el libro no pega ni junta, aunque la trama del libro es la misma que se trata en la película y tiene que ver con el conflicto que hay con las 3 leyes de la robótica, pero en muchas situaciones diferentes a la película. El libro contiene un conjunto historias cortas que hablan de la forma en que los robots interactúan con los humanos en situaciones límite exigiendo al máximo las 3 leyes de la robótica. Interesante libro!

Alguna sugerencia de lectura para este año? déjenlo en los comentarios.

Palabras al cierre

Feliz 2018 para todos! Bienvenido 2018!

y si… voy a escribir mas este año :D

Cambio de casa, adiós Octopress bienvenido Jekyll

Estoy de mudanza de blog, adiós Octopress bienvenido Jekyll.

Una de las razones que me empujó a cambiarme de sistema de blog, es que Octopress esta sacando la versión 3 desde el 15 de enero del 2015, esta algo abandonado (ver los commits).

La pregunta era… ¿ hacia donde migro ?

Octopress se basa en Jekyll y la organización de los artículos es básicamente la misma. Pero no todo es tan fácil. Octopress tiene un conjunto de plugins que facilitan y ayudan a embellecer los artículos, por ejemplo, para destacar código fuente, citas, imágenes y video.

Jekyll de alguna u otra forma también tiene plugins para lo mismo, el problema es que hay que modificar todos los artículos donde fueron usados los plugins de Octopress y modificarlos al gusto de Jekyll.

Siguiente problema, elegir un lindo (?) tema para el blog, después de un largo recorrido buscando, encontré Minimal Mistakes, bien lindo, personalizable, con gran capacidad de extender y todas esa funcionalidades que a los developers nos enamoran (pero que jamás usamos al 100%). De todas formas, la instalación de este tema no fue sencillo, demasiadas configuraciones y cosas que no andan a la primera y la documentación, que es bien buena, no dice las cosas clave.

Algunas características de Jekyll son:

  • Posee muchos plugins
  • Esta escrito en ruby (puaj)
  • Tiene varios mecanismos de importación desde otros blogs.
  • El contenido se genera a partir de documentos escritos en markdown
  • Como el contenido es texto, estos se pueden mantener fácilmente en git

Si ve errores, considere que el blog esta en rodaje y sea amable… reporte el bug.

jEnv una solución para múltiples instalaciones de Java

jEnv es una herramienta de linea de comandos que ayuda a mantener múltiples instalaciones de Java (versiones y/o sabores) y permite cambiar las versiones por linea de comandos para poder mantener diversos entornos de desarrollo.

Una de las funcionalidades interesantes de jEnv es que configura las variables de entorno de java JAVA_HOME al vuelo según una pequeña configuración que se agrega al proyecto. Más adelante veremos algunas gracias de jEnv.

Instalación

La instalación es bien sencilla y la pueden revisar en la página oficial de jEnv, acá dejo un resumen:

1
$ git clone https://github.com/gcuisinier/jenv.git ~/.jenv

Ahora debes ejecutar (dependiendo de tu shell) lo siguiente:

1
2
$ echo 'export PATH="$HOME/.jenv/bin:$PATH"' >> ~/.bash_profile
$ echo 'eval "$(jenv init -)"' >> ~/.bash_profile
1
2
$ echo 'export PATH="$HOME/.jenv/bin:$PATH"' >> ~/.zshrc
$ echo 'eval "$(jenv init -)"' >> ~/.zshrc

Reiniciamos el terminal y/o cargamos las nueva variables de entorno de nuestra shell y primero probamos que todo funcione correctamente con el siguiente comando:

1
$ jenv --version

Si el comando te responde con la versión de jEnv es porque esta todo correctamente instalado.

Configuración

Luego tenemos que agregar las diferentes versiones de java que tengas instaladas en tu sistema de la siguiente forma:

1
2
3
4
5
$ jenv add /path/to/java6-oracle/java_home
$ jenv add /path/to/java6-openjdk/java_home
$ jenv add /path/to/java7-oracle/java_home
...
...

Una ves que hayas concluido la configuración de las diferentes versiones de Java de tu sistema, puedes listarlos con el siguiente comando:

1
$ jenv versions

Y aparecerá algo como esto:

1
2
3
4
5
6
7
  system
* 1.7 (set by /home/pcollaog/.jenv/version)
1.7.0.79
1.8
1.8.0.45
oracle64-1.7.0.79
oracle64-1.8.0.45

Ahora corresponde configurar cual de todas esas instalaciones será la que funcionará de forma global, es decir, la configuración por omisión de Java. En este ejemplo se configura la versión oracle64-1.7.0.79 como global.

1
$ jenv global oracle64-1.7.0.79

Tambien se puede configurar de forma mas genérico, es decir, la última versión de java 7, por ejemplo:

1
$ jenv global 1.7

Con esto siempre tomará la versión mas nueva de java 7 que tengan previamente configurada.

Activar la configuración automática de JAVA_HOME

Para delegar la configuración de las variables de entorno de java a jEnv, debemos activar el plugin export de la siguiente forma:

1
$ jenv enable-plugin export

Nota: para que funcione correctamente el plugin, se debe eliminar la configuración de las variables de entorno que se tengan en los perfiles de bash (.bashrc) , zsh (.zshrc) o de su shell favorita.

Luego deben reiniciar su terminal o volver a cargar los perfiles de su shell y probar:

1
$ echo $JAVA_HOME

Debería aparecer algo como lo que sigue:

1
/home/pcollaog/.jenv/versions/1.7

Y si vemos la versión de java debería aparecer algo como esto:

1
2
3
4
$ java -version
java version "1.7.0_79"
Java(TM) SE Runtime Environment (build 1.7.0_79-b15)
Java HotSpot(TM) 64-Bit Server VM (build 24.79-b02, mixed mode)

Configuración personalizada de Java

Si por alguna razón necesitas tener una versión especifica de Java en un proyecto de código fuente, debes configurar (estando dentro del directorio principal del proyecto) de la siguiente forma:

1
$ jenv local 1.7

Este comando creará un archivo en el directorio en el que te encuentras (directorio base de tu código fuente) llamado .java-version y cuyo contenido será la versión de java especificada. Con esto todos los subdirectorios (a partir de donde se encuentras este archivo) estarán configurados con la versión de java seleccionada. Si tienes correctamente configurado el plugin export, jEnv hará el trabajo sucio de configurar la variable JAVA_HOME.

Sus comentarios son bienvenidos!

Spring Constructor Namespace

En esta oportunidad les escribo sobre un nuevo namespace que apareció en Spring 3.1 (y me dí cuenta recién XD ) y sirve para configurar los beans haciendo uso de su constructor. Como su nombre lo indica este es un namespace que opera sólo sobre los constructores de beans y así permitir la inyección de beans o valores.

Les dejo una imagen de como se activa este nuevo namespace en el STS.

Veamos un ejemplo simple de cómo usar namespace C.

Supongamos la siguiente clase:

1
2
3
4
5
6
7
8
public class SimpleSPImpl extends StoredProcedure
implements SimpleSP {

public SimpleSPImpl(DataSource ds, String spName) {
super(ds, spName);
}

}
1
2
3
4
5
6
7
8
9
10
<bean id="bean_id" class="com.example.SimpleSPImpl">
<constructor-arg name="ds" ref="datasource" />
<constructor-arg name="spName" value="spNameTest" />
</bean>

<!-- Definición del datasource de ejemplo -->
<bean id="datasource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
...
...
</bean>

Ahora usando el namespace c quedaría algo mas simple:

1
2
3
4
5
6
7
8
<bean id="bean_id" class="com.example.SimpleSPImpl 
c:ds-ref="datasource" c:spName="spNameTest" />

<!-- Definición del datasource de ejemplo -->
<bean id="datasource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
...
...
</bean>

Como pueden notar en el ejemplo, el namespace C permite configurar los constructores de los beans y tiene dos formas básicas, por valor y referencia. Para inyectar una instancia preconfigurada se debe usar el sufijo -ref para hacer alusión a que es una referencia. Si se desea inyectar un valor, sólo se usa el nombre del argumento del constructor.

Como siempre sus comentarios son bienvenidos.

Checked y Unchecked Exception

Dado que varios de mis lectores me han solicitado un post sobre tipos de excepciones en Java, les dejaré un par de notas para que consideren al momento de diseñar soluciones y por su puesto el cómo manejar los errores. Aquí vamos!

Primero que todo, un par de definiciones básicas y características antes de partir.

Jerarquía de Excepciones

Esta es la jerarquía de excepciones de mas alto nivel que encontramos en Java.

Unchecked Exception

Generalmente este tipo de excepciones son lanzadas por la aplicación y se generan a partir de errores en tiempo de Runtime. Este tipo de excepciones representan errores en el código y que la aplicación no es capaz de controlar. Algunos de errores causados y que lanzan este tipo de excepciones, por ejemplo, argumentos inválidos pasados a un método (argumentos null pueden causar NullPointerException), otro error común son la excepciones del tipo IndexOutOfBoundsException y que son lanzadas cuando se quieren obtener elementos de una lista y el índice que se entrega está fuera del tamaño del arreglo. Como podrán ver, son errores de programación y que generarán defectos en momento de correr la aplicación (no así al compilar).

Unchecked runtime exceptions represent conditions that, generally speaking, reflect errors in your program’s logic and cannot be reasonably recovered from at runtime.

Gosling, Arnold and Holmes, The Java Programming Language

Las excepciones de tipo Unchecked son subclases que heredan desde RuntimeException. Además este tipo de excepciones no tienen la obligación de ser declaradas con la cláusula throws en la cabecera del método. Otra característica es que tampoco se tiene la obligación de atraparlas con un catch como se muestra en el ejemplo siguiente:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
/**
* Ejemplo de exception tipo {@link RuntimeException}
*
*/
public class RuntimeDemo {

/**
* Método principal
*/
public void mainMethod() {
methodThowsRuntimeException();
methodThowsRuntimeException2();
}

/**
* Este método lanzará una excepción de tipo runtime no declarada en su
* firma
*/
public void methodThowsRuntimeException() {
throw new ExampleRuntimeException();
}

/**
* Este método lanzará una excepción de tipo runtime está declarada en su
* firma (no es obligación) pero deja mas claro al desarrollador las
* excepciones que debería manejar con la API.
*
* @throws ExampleRuntimeException
* en caso de error
*/
public void methodThowsRuntimeException2() throws ExampleRuntimeException {
throw new ExampleRuntimeException();
}

/**
* Clase de error tipo Runtime
*/
public static class ExampleRuntimeException extends RuntimeException {

public ExampleRuntimeException() {
super();
}
}
}

Checked Exception

Este tipo de excepciones representan condiciones inválidas en el contexto de la línea de ejecución y que están fuera del control de dicho contexto, como por ejemplo, problemas con la base de datos, problemas de red, acceso a los archivos. También pueden ser condiciones de ingreso al sistema en donde el sistema no tiene ninguna participación, como por ejemplo, ingresar un nombre de usuario y contraseña incorrectos.

Contexto de ejecución ó scope: Corresponde al las líneas de código que están encerradas en un bloque de código, como por ejemplo, un método, un try/catch, bloque estático, etc.

Este tipo de excepciones deben ser declaradas en la firma del método. Además deben ser atrapadas dentro de los bloques de código donde se invoque un método que contenga la clausula throws.

Todas las excepciones de este tipo son subclases que heredan desde Exception, como por ejemplo:

Otra característica de este tipo de excepciones es que existe una probabilidad de recuperación de la ejecución y el método puede realizar alguna acción correctiva y/o informativa (log) en el bloque catch o simplemente relanzar la excepción y confiar en que el método invocante la atrape y haga algo con ella.

Para enviar el stacktrace al sistema de log (systemout) pueden usar los métodos de Throwable y en específico al método printStackTrace.

Un pequeño ejemplo de checked exception:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
/**
* Clase de ejemplo para checked exceptions
*/
public class CheckedExample {

/**
* Método que atrapa una checked exception
*/
public void catchCheckedException() {
try {
throwCheckedException();
} catch (ExampleCheckedException e) {
// TODO: hacer algo en caso de error (recuperacion)
}
}

/**
* Método que relanza la checked exception a un método superior
*
* @throws ExampleCheckedException
* checked exception error
*/
public void rethrowCheckedException() throws ExampleCheckedException {
throwCheckedException();
}

/**
* Método que lanza una checked exception
*
* @throws ExampleCheckedException
* checked exception error
*/
private void throwCheckedException() throws ExampleCheckedException {
throw new ExampleCheckedException();
}

/**
* Clase que representa la excepcion de ejemplo
*/
private static final class ExampleCheckedException extends Exception {

public ExampleCheckedException() {
super();
}

}
}

Error

Las excepciones de tipo Error son excepciones en las que el sistema no puede hacer nada con ellas, son clasificadas como errores irreversibles y que en su mayoría provienen desde la JVM, como por ejemplo: IOError, NoClassDefFoundError, NoSuchMethodError, OutOfMemoryError y VirtualMachineError por mencionar algunos de los errores.

Un poco de diseño y consejos para el manejo de excepciones

Para iniciar esta última parte, comenzaré con algunos ejemplos de malas prácticas con las que siempre nos encontramos cuando programamos, les dejo unas pocas:

1
2
3
4
5
try {
//ejecución que lanza checked exceptions
} catch (ExampleCheckedException e) {
// No hace nada
}

En el ejemplo de arriba claramente no se hace nada con la excepción dentro del try, lo recomendable es que si de verdad no vas a hacer nada con la excepción, al menos debes enviarla a tu sistema de Logger favorito con algún nivel de debug aceptable para poder revisar el log. Siempre se recomienda hacer algo en el bloque catch ya que ocurrió un error que debe ser controlado.

1
2
3
4
5
try {
//ejecución que lanza checked exceptions
} catch (ExampleCheckedException e) {
throw e;
}

Es similar ejemplo 1 pero lo que se hace es relanzar la excepción atrapada hacia el método invocante. La recomendación es que nunca hagas eso ya que se presta para confusión al leer el código fuente y en la practica se estarían ejecutando dos bloques catch para la misma excepción (el directo y el del método invocante).

1
2
3
4
5
try {
//ejecución que lanza checked exceptions
} catch (Exception e) {
// alguna lógica de negocio
}

Es recomendable que nunca atrapen todas las excepciones en un bloque catch y básicamente porque uno pierde la noción de por qué se produjo la excepción. Además en ese bloque también se atrapan las excepciones de tipo Runtime y ya mencionamos que estas excepciones significan errores en tu programa y que deben ser depurados (no escondidos debajo de la alfombra). Lo mejor es atrapar cada una de las excepciones y darle un tratamiento a cada una, si necesitan agrupar usen la herencia/jerarquía de las excepciones.

1
2
3
4
5
6
7
8
9
10
11
try {
//ejecución que lanza checked exceptions
} catch (Exception e) {
// alguna lógica de negocio

if (e instanceof BlaException ){

} else if (e instanceof FooException) {

} else if .....
}

Ese bloque de if’s compuestos se debe transformar en varios catch para cada una de las excepciones lanzadas. No usen un control de errores manual, es mejor usar las herramientas que te provee el lenguaje.

1
2
3
4
5
6
7
8
9
10
11
12
13
try {
//ejecución que lanza checked exceptions
} catch (ErrorBlaException e) {
...
} catch (ErrorFooException e) {
...
} catch (ErrorOMGException e) {
...
} catch (SDWException e) {
...
} catch (Exception e) {
...
}

No abusar de las checked exceptions ya que hacen nuestro código confuso y poco mantenible. Si bien es cierto, es la herramienta que nos provee el lenguaje, no abusemos de ella y convirtamos los bloques catch en pseudo programas y rutinas anexas a la lógica de negocio (que es la que vale).

1
2
3
private void foo() throws Exception {
// código de negocio
}

Nunca lancen Exception como una excepción de su lógica de negocio y es que básicamente los catch están pensados en atrapar excepciones particulares y al lanzar Exception (de la mas alta jerarquía) jamás entrarás al bloque catch que corresponda y que pueda gestionar el error. Por otro lado el programador pierde la visibilidad de los errores particulares que debe gestionar.

1
2
3
4
5
6
7
private void foo() throws Exception {
try {
// logica de negocio
} catch (ParserConfigurationException e) {
throw new RuntimeException("Error");
}
}

Jamás se debe hacer esto, jamás!. Esto romperá todo tu programa ya que al lanzar la excepción RuntimeException esta llegará sin control a la capa mas alta provocando un error. Recuerden que ese tipo de excepciones son errores sin recuperación y justamente estamos tratando de hacer lo contrario gestionar los errores de lógica de negocio.

Mantener un árbol de excepciones

Esta sección del post quizás sea el más polémico ya que no hay receta perfecta para el manejo de excepciones y daré mis consejos (personales), puede que estén de acuerdo como puede que no.

Les recomiendo siempre mantener un árbol de excepciones que representen los errores (de negocio y de ejecución) de tu aplicación. Para que sea mas simple la mantención del árbol de excepciones, usen polimorfismo, herencia y todas las herramientas que ofrece OOP. En este punto siempre hay detractores de los árboles de excepciones con la excusa de su mantención.

Contraria a mi propuesta de manejo de errores, existen quienes mantienen sólo 1 excepción y a dicha excepción le agregan atributos y cuanta metadata puedan agregar. Qué se consigue finalmente con ese esquema de errores, es llenarte de IF por todos lados mirando los atributos que contiene la instancia de excepción y haciendo todo un control de errores manual.

Palabras al cierre

Quedan muchas cosas por mencionar de las excepciones y ahondar mucho mas en cómo diseñar y construir un árbol de excepciones, creo que sera materia para otro artículo. Demás esta decirles que esta abierta la discusión. Los comentarios bienvenidos sean.

Ud. no lo haga - Parte 1

En este post, pondré algunas capturas de pantallas de cosas que Ud. como desarrollador (java) no debe hacer (en algunos casos debe evitar hacer).

Constantes

En Java las constantes por lo general se declaran en estilo uppercase usando como separador de palabras el underscore ALGO_COMO_ESTO, en este ejemplo, se pueden notar que agregan un underscore al principio (No lo haga!). Generalmente se usa underscore al principio para nombrar los atributos de una clase, de esta forma no usas this para identificar un atributo de clase.

El falso catch

Si va hacer algún tipo de control sobre una exception, pués hágalo. En este ejemplo sólo se captura la exception y se vuelve a lanzar. Si realmente quiere hacer eso, no haga el *catch] de la exception, déjela salir libremente.

La estética del código si importa

Si esta programando y al final de su algoritmo, le queda algo parecido a lo que sale en la imagen, recapacite, tome aire y refactorice su código. Hay algunas alertas que se pueden ver fácilmente con la estética del código, es decir, al cómo queda escrito (forma, silueta). Hay algunos desarrolladores que les encanta tener sus líneas de código hasta el infinito, lo que dificulta su lectura cuando tienes una pantalla distinta a la del desarrollador.

Otro ejemplo más:

Algunos consejos que te ayudarán a darte cuenta de errores en tu código de forma visual, aquí los dejo:

  • Ajusta tu IDE para que te corte las lineas en los 80/120 caracteres. Con esto podrás tener como buena práctica nombrar bien tus variables (cortas y precisas) y es un buen límite cuando empiezas a avanzar en la identación del código producto de los if/else/for/while/try/catch. Si terminas escribiendo código cerca de los 80 caracteres es que algún problema tienes en tu código y necesita refactorización.
  • Si trabajas en equipo, es indispensable que todos tengan los mismos settings para la identación y el encoding, de esta forma, no tendrás problemas al comparar código (diff).

Estos son mis ajustes en el STS/Eclipse:

Estilo Visual Basic

Si esta escribiendo código Java, por favor no cometa este error. Si va a declarar una variable, hágalo en el lugar donde se utilizará, de esta forma los refactoring de código son mas simples. Por otro lado, estéticamente queda feo tu código. Ahora si entramos en el micro manejo de memoria, posiblemente estas reservando memoria que no utilizarás en todo el método. En este ejemplo, se declaran muchas variables con un valor, pero que pasa si salta una exception? o algún control de flujo que no considere todas las variables?, habrás perdido innecesariamente un par de bytes.

WTF!!!

Nunca, pero nunca, asignes a una variable el valor de una constante, no tiene ningún sentido. Además en este ejemplo, podrán notar que hay código que no tiene ningún sentido, nameCombobox nunca jamás en la vida va a ser null por lo tanto ese if esta de sobra. Escriba código que realmente es útil y que funciona.

Hay algunas cosas que no tienen una explicación razonable, como instanciar un objeto para luego no utilizarlo. Esto sólo provoca perdida de preciados bytes y ciclos de procesador.

Creo que califica en la misma descripción de arriba, escriba código que funcione.

Si va a utilizar StringBuffer hágalo de la forma correcta, se merece un mínimo de respeto dicha clase.

Evite el código que está de más, la API de commons-lang StringUtils.isEmpty() evalua que sea null y vacío. Ahora bien en el código podrían utilizar de la misma API StringUtils.isNotEmpty() y con eso le sacan el signo ! y todo queda mas bonito, por su puesto que la primera parte del if vuela también del código.

Finalmente esto se traduce en:

1
2
3
if (StringUtils.isNotEmpty(codeAdditional)){
....
}

Espero les sirvan estos anti-ejemplos de código fuente. A medida que siga revisando código iré agregando algún otro post con mas código para el bronce. La discusión esta abierta por si quieren agregar algún otro tip.

PD: Las variables han sido renombradas para proteger a los verdaderos autores, cualquier coincidencia con la realidad es casualmente cierta y verídica.