Clean Code es una propuesta hecha por Robert C. Martin, en la que describe las buenas prácticas de programación, las define como algo vital para las compañías y los programadores, porque cada vez que se escribe código desordenado, confuso y sin pruebas unitarias, otros programadores tardan mucho tiempo en la comprensión del código, incluso el mismo programador al cabo de algún tiempo, se volverá incapaz de comprender y entendersu propio código.
A continuación, hablaremos de un conjunto de reglas y principios que denotan buenas prácticas de programación, tomando como referencia el lenguaje de programación Java.
Ejemplo: co.com.qvision.certificación
Para ver el detalle de este estándar vea el siguiente enlace: https://maven.apache.org/guides/mini/guide-naming-conventions.html
Ejemplo:
1 2 3 |
public void imprimirReporte(String informeEjecutivo){ ... } |
Variables: Las variables siempre se escriben en minúsculas y al igual que los métodos si su nombre está compuesto por varias palabras, a partir de la segunda palabra la primera letra de cada una de ellas se escribirá en mayúsculas. Las variables nunca podrán comenzar con el carácter "_" o "$". Los nombres de variables deben ser cortos y sus significados tienen que expresar con suficiente claridad la función que desempeñan en el código. Debe evitarse el uso de nombres de variables con un sólo carácter, excepto para variables temporales.
Constantes:
Todos los
nombre de constantes deben escribirse en mayúsculas, cuando se trate de
palabras compuestas, éstas deben ir separadas por “_”.
Nombres con sentido: Los nombres con sentido se refieren a que se debe tener en cuenta que los nombres de variables, métodos y clases deben tener un sentido y ser descriptivo, es decir, que su nombre explique por sí solo cuál es el uso de la variable, método o clase. Es de anotar que se debe evitar abreviaciones, prefijos y números en variables.
Un ejemplo de ello sería:
1 2 3 4 5 |
private String primerNombre; public String obtenerPrimerNombre() { return primerNombre; } |
Nótese que la variable primerNombre es descriptivo y tiene un sentido, el de guardar el primer nombre de una persona; ahora el método obtenerPrimerNombre(), también es descriptivo y con sentido ya que expresa lo que debe hacer, retornar lo que contenga la variable primerNombre.
Otro elemento importante es evitar variables con letras solas, no usar i, j, k (solo para bucles cuyo contexto sea muy específico). En vez de eso usa nombres que se puedan buscar, nombres que sean dicientes, que expresen lo que se va a guardar.
Una mala práctica se enuncia en el siguiente código:
1 2 3 |
private String a; private String 1a; int i; |
Nótese que las variables a, 1a, i, no expresan lo que se quiere almacenar, no se sabe para qué están creadas, no tienen un sentido y una descripción clara de su objetivo.
Una buena práctica sería:
1 |
char letra; |
Miremos que la variable letra, se entiende que almacenará una letra del alfabeto, es clara y se sabe cuál es el objetivo de su creación.
Ahora utiliza verbos para expresar los métodos
1 2 3 |
public String obtenerPrimerNombre() { return primerNombre; } |
Miremos que se utilizó el verbo obtener, en algunos casos se hace uso del verbo en inglés get, set, etc. Cuando se quiere acceder a los datos es una buena práctica el uso de getter y setter, que consiste en crear los métodos correspondientes con el sufijo get- y set-, para cada variable implementada. Se recomienda usar nombres técnicos cuando la intención sea técnica (Factory, Facade, etc…)
1 2 3 4 5 6 |
public String getPrimerNombre() { return primerNombre; } public void setPrimerNombre(String primerNombre) { this.primerNombre = primerNombre; } |
Las funciones o métodos deben ser reducidas y con nombres descriptivos. Trata de evitar el anidamiento excesivo, es decir, cuando tenemos un conjunto de if anidados, es mejor hacer uso de un switch ya que está hecho para tal caso. Un ejemplo que expresa lo anterior es:
1 2 3 4 5 6 7 8 9 |
public void anidamiento(char letra) { if(letra == 'a') { }else if(letra == 'b') { }else if(letra == 'c') { } } |
En el método anterior, se recibe como parámetro una letra, pero se está haciendo uso de 3 condiciones if, las cuales expresan un condicional de igualación referente a la variable letra, esto es el anidamiento que se hablaba anteriormente, son un conjunto de if uno tras otro, con una condición en común, aunque funciona el código, no es una buena práctica, ya que se hace extenso y difícil de entender.
Una solución para el caso anterior es el uso de un case, éste da claridad de la lógica implementada presentando los diferentes escenarios que se pueden dar. Ahora miremos un ejemplo de ello:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
public void correccionAnidamiento(char letra) { switch (letra) { case 'a': break; case 'b': break; case 'c': break; default: break; } } |
Notemos que ahora se hizo uso de la estructura de control case, donde se suprimió todos los condicionales if, para presentar casos de letras, esto ayuda a la legibilidad del código. Los métodos solo deben hacer una sola cosa, donde se haga lo que dice el nombre y no haya nada oculto.
A continuación se presenta un ejemplo que muestra la implementación de un método suma que realiza dicha operación y retorna su valor, pero miremos que internamente él realiza una resta, es decir, una operación que no es el objetivo por el cual el método fue creado, recordemos que cada método debe tener una única responsabilidad, no se debería estar haciendo una operación diferente a una suma, ni tener un código que no conlleve a una suma, ósea al objetivo del método.
1 2 3 4 5 |
public int suma(int numero1, int numero2) { int resultadoSuma = numero1 + numero2; int resultadoResta = numero1 - numero2; return resultadoSuma; } |
Una buena práctica es devolver excepciones en vez de códigos de error, un código de error se presenta en dos casos:
Caso1: El valor que retorna la función es cero (0), quiere decir que no se ha producido error.
Caso 2: El valor que retorna la función es diferente de cero, quiere decir que se ha producido un error, para cada tipo de error se produce un número distinto. Otra forma de código de error es retornar un valor null en un método, pero tiene un inconveniente, es propenso a una excepción de tipo NullPointerException.
Para gestionar adecuadamente los códigos de error se puede hacer uso de las excepciones del lenguaje de programación, las excepciones son un medio por el cual nos alertan de una situación anómala en el código y nos deja decidir el comportamiento que debería tener en tal caso. Para el manejo de excepciones se utiliza un bloque try/catch, donde en el catch se ingresan las excepciones. Ahora veamos un ejemplo de manejo de excepciones:
1 2 3 4 5 6 7 8 9 |
public boolean argumentoBooleano (boolean bool) { try { bool = false; }catch(Exception e) { e.getMessage(); } return bool; } |
Los comentarios son mensajes que sólo pueden ser vistos a través de código, están denotados con //<mensaje> o /* <mensaje */, dichos mensajes no son justificados cuando el código no expresa lo que queremos decir, es decir, debe haber coherencia entre lo comentado y el código. Miremos un ejemplo
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
public void correccionAnidamiento() { //manejo de conexion a base de datos char letra; switch (letra) { case 'a': break; case 'b': break; case 'c': break; default: break; } } |
El código anterior es una mala práctica porque el comentario no es alusivo, no coherente con lo planteado en el código, es decir, no tiene nada que ver el comentario con el código.
Son comentarios válidos:
1 2 3 4 |
public int suma(int numero1, int numero2) { //retorna la suma de 2 valores return numero1 + numero2; } |
En el ejemplo anterior, se comenta que el método retorna la suma de dos valores, es una mala práctica porque el nombre del método (suma()) expresa el objetivo de lo que realiza, por tanto es redundante y obvio lo que se quiere expresar.
Son comentarios no validos:
El formato es una forma de estructura del código, donde el tamaño de los ficheros no debe exceder 200 líneas en promedio, y con un límite máximo de 500 líneas. Se debe tener en cuenta que la distancia vertical entre los elementos relacionados debe ser mínima. A continuación, veremos un comparativo entre una buena y una mala práctica:
Mala practica
1 2 3 4 |
public int suma(int numero1, int numero2) { //retorna la suma de 2 valores return numero1 + numero2; } |
Nótese el espaciado entre las 2 variables.
Buena practica
1 2 3 4 |
public void espaciado() { int variableInt; String variableString; } |
Nótese que, entre las dos variables relacionadas verticalmente, no hay espacios
Ahora hablemos de la anchura de las líneas de código, debe estar entre 80 y 120 caracteres, ya que no deberíamos hacer scroll horizontal para leer código.
Otra buena práctica es mantener las tabulaciones, es decir, se deben mantener los espacios que permitan la distinción de la siguiente línea de código, aunque la longitud del código sea mínima, lo importante es la claridad. Veamos un ejemplo:
Mala práctica
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
public void correccionAnidamiento() { char letra; switch (letra) { case 'a': break; case 'b': break; case 'c': break; default: break; } } |
Buena práctica
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
public void correccionAnidamiento() { char letra; switch (letra) { case 'a': break; case 'b': break; case 'c': break; default: break; } } |
Objetos y estructuras de datos
La ley de Demeter indica como un método f de la clase C debe comunicarse con otros métodos. De tal forma que dicho método f debe invocar métodos de:
Miremos ahora el código, para que veamos en la implementación cómo sería:
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 |
public class Persona { private String nombre; private int dni; //Ejemplo de cumplimiento de la Ley de Demeter public void comer(Hamburguesa hamburguesa) { //Podemos llamar a métodos de objetos pasados a nuestro método boolean esConCebolla = hamburguesa.llevaCebolla(); if(esConCebolla) { //podemos acceder a propiedades del propio objeto System.out.println("A " + this.nombre + " le gusta la hamburguesa con Cebolla, que rico! "); } //podemos llamar a métodos de objetos creados dentro del método Bebida bebida = new Bebida(); bebida.servir(); //podemos llamar a nuestros propios metodos echarseUnaSiesta(); } private void echarseUnaSiesta() { // por fin a dormir } } |
La Ley de Demeter no permite invocar métodos de objetos devueltos por ninguna de las funciones permitidas, es decir, cuando se tienen muchas llamadas a métodos concatenados o los llamados chain calls, un ejemplo de ello sería como:
Esto es una clara violación a la Ley de Demeter ya que es posible que los valores que estamos obteniendo de esas llamadas, ya existieran previamente al momento en el que accedemos a ellos. No es el hecho de tener la concatenación de los métodos lo que rompe la ley, sino el acceso a ciertos objetos ya existentes antes de la llamada.
Cuando detectamos que estamos incumpliendo la Ley de Demeter, se debe aceptar que hay problema de arquitectura en la base de nuestro proyecto, lo que implicaría cambios grandes en el mismo.
Para ver en detalle estos y muchos otros temas, te recomendamos que leas el libro Clean Code de Robert C. Martin.