Estructurar clases (Java)

Clases

Hasta ahora hemos visto como escribir de la mejor manera posible lineas y bloques de código, pero aun no hemos visto como hacerlo con las clases.

Ordenar los métodos

A la hora de organizar las clases es importante que si tenemos un método que hace uso de otro que tenemos en nuestra clase, es mejor si lo ponemos a continuación, de manera que si estamos leyendo el código no tengamos que dar saltos en él y sea lo más parecido a un artículo.

Modificadores

Nuestras variables, métodos o funcionalidades deben tener el modificador más restrictivo posible, si tenemos que acceder a un método en un test y no podemos por ser privado lo pondremos como protected, de manera que será accesible desde el mismo paquete.
Principio de responsabilidad única

Un punto importante a la hora de desarrollar un clase es el tamaño porque, el tamaño si importa, al igual que con lo métodos las clases tienen que ser lo más pequeñas posibles y en lo que a dimensiones se refiere no hay un máximo de líneas. En su lugar lo que hay que tener en cuenta es el número de responsabilidades, una clase no debe tener más de una responsabilidad si se te pasa por la cabeza añadirle otra (consejo, probablemente esto sea una nueva clase). Y esto el principio más importante en la Orientación a Objetos, el principio de responsabilidad única (SRP) y en resumidas cuentas dice que una clase debe tener un solo cometido, de manera que si cambia sea por una única razón. Evitando entremezclar términos de lógica de negocio, con persistencia, con interfaz de usuario.

Cohesión

Es importante que las clases tengan el menor número posible de instancias de variables y además, para lograr mayor cohesión estas variables tienen que ser accedidas por el mayor número de métodos de la clase. Se dice que una clase es cohesiva cuando cada método de esta accede a cada variable instanciada. En general es complicado alcanzar estos niveles de cohesión. Por ejemplo la siguiente clase tiene un nivel de cohesión bastante elevado.

public class Stack {
    private int topOfStack = 0;
    List<Integer> elements = new LinkedList<Integer>();

    public int size() {
        return topOfStack;
    }

    public void push(int element) {
        topOfStack++;
        elements.add(element);
    }

    public int pop() throws PoppedWhenEmpty {
        if (topOfStack == 0)
            throw new PoppedWhenEmpty();
        int element = elements.get(--topOfStack);
        elements.remove(topOfStack);
        return element;
    }
}

Como se ve tienen tan solo dos variables instanciadas y sólo en un método no se accede a ambas (size).

Libro original

 

Test Unitarios

Test Unitarios

La programación a evolucionado mucho en tan solo 20 años, antes por 1997 cuando estaba empezando no había forma de probar las funciones que creábamos. No era raro crear la función añadirle una parte en la que podamos introducir por teclado caracteres o alguna orden y ver como responde este, pero a día de hoy existen los test unitarios. Test que ejecutarán nuestra función y comprobarán si el resultado obtenido es el que nosotros esperábamos. Tan buenos son estos test y tanto ruido han hecho las metodologías ágiles y el TDD que no es raro a día de hoy que en una entrevista de trabajo te pregunten explícitamente por ellos.

Las tres leyes del TDD

  1. No escribirás código de producción hasta que exista un test que falle.
  2. No escribirás más de un test unitario que falle.
  3. No escribirás más código de producción del necesario para pasar el test.

Estas reglas se convierten en un ciclo de trabajo que no dura más de 30 segundos. Escribes un test, compruebas que falle, escribes el código para que este no falle. De esta manera escribimos los test y el código que lo pase al mismo tiempo. Si tomamos este hábito será fácil escribir docenas de test diariamente lo que hará que cada mes tu software tenga cientos de test nuevos que lo prueban ¿quien no quiere eso?

Mantener los test limpios

Puede que pienses que los test son una simple herramienta para llegar a un código de producción sólido y robusto, que realmente el fin es este código de producción. Y por ello no le prestes mucha importancia al como realizar los test, los creas con “prisa” dejas variables o los propios test con malos nombres. El problema vendrá en el futuro cuando creando un nuevo test para una nueva funcionalidad tienes que cambiar algo del código y bum explotan unos cuantos test a parte del nuevo. Comienzas a leer y ves que no tienes idea de que están probando realmente, en ese momento comenzará un viaje el cual (con suerte) si has escrito el test hace poco y tienes buena memoria acaba rápido pero en la mayoría de casos no será así.

Con todo esto quiero decir que el código de los test es tan importante como el de producción.

Un test limpio

¿Como mantenemos un test limpio? Legibilidad, al igual que hemos hablado de como hacer un código limpio los test funcionan de un modo similar: claridad, simplicidad y densidad. Imagina que te enfrentas a este test:

**public void testGetPageHieratchyAsXmlDoesntContainSymbolicLinks()
throws Exception{
    WikiPage pageOne = crawler.addPage(root, PathParser.parse("PageOne"));
    crawler.addPage(root, PathParser.parse("PageOne.ChildOne"));
    crawler.addPage(root, PathParser.parse("PageTwo"));

    PageData data = pageOne.getData();
    WikiPageProperties properties = data.getProperties();
    WikiPageProperty symLinks = properties.set(SymbolicPage.PROPERTY_NAME);
    symLinks.set("SymPage", "PageTwo");
    pageOne.commit(data);

    request.setResource("root");
    request.addInput("type", "pages");
    Responder responder = new SerializedPageResponder();
    SimpleResponse response =
(SimpleResponse) responder.makeResponse(
        new FitNesseContext(root), request);
    String xml = response.getContent();
    assertEquals("text/xml", response.getContentType());
    assertSubString("<name>PageOne</name>", xml);
    assertSubString("<name>PageTwo</name>", xml);
    assertSubString("<name>ChildOne</name>", xml);
    assertNotSubString("SymPage", xml);
}

Puede que logres comprenderlo después de un tiempo de análisis, no digo que sea imposible. Incluso puede que no te lleve mucho tiempo, pero enfrentarse a test así diariamente acaba quemando antes nuestra cabeza. Si en lugar de eso tenemos ese mismo test pero de la siguiente forma:

public void testSymbolicLinksAreNotInXmlPageHierarchy() throws Exception {
    WikiPage page = makePage("PageOne");
    makePages("PageOne.ChildOne", "PageTwo");

    addLinkTo(page, "PageTwo", "SymPage");

    submitRequest("root", "type:pages");

    assertResponseIsXML();
    assertResponseContains(
        "<name>PageOne</name>", "<name>PageTwo</name>", "<name>ChildOne</name>"
    );
    assertResponseDoesNotContain("SymPage");
}

Sigue teniendo las mismas tres partes diferenciadas. Una primera parte donde creas los datos para el test, a continuación donde utilizamos estos datos y finalmente comprobamos si el resultado es el esperado.

Un solo assert por test

Una buena mentalidad es la de un solo assert por test, en la práctica no es tan fácil y veremos que aveces tendremos que poner más de uno. Es una mentalidad que se suele tener en cuenta pero no es posible llevarla a cabo en todos los casos. Por ello mejor el mínimo de asserts por test.

Lo que realmente tiene que ser único por test es el concepto a probar, que es lo que queremos poner a prueba y eliminar todo el ruido que pueda generarse a su alrededor. Con esto me refiero, si tienes una función que puede generar tres casos, por ejemplo. Dado un número este puede ser mayor, menor o igual. No haremos un test que pruebe estos tres casos de una sola vez, tenemos que hacer tres test independientes de manera que cada uno de ellos prueba un concepto, un caso.

F.I.R.S.T.

Las cinco reglas que siguen los test limpios:

  • Fast, rápido: un test tiene que ejecutarse rápido, si tenemos un test que tarda bastante en completarse no nos gustará ejecutarlo con frecuencia y tardaremos más en ver posibles errores.
  • Independent, independiente: los test tienen que ser independientes unos de otros, no podemos tener test que están conectados entre ellos porque en el momento que el primero falle todos los demás irán detrás, y será complicado saber donde empieza el problema.
  • Repeatable, repetible: los test tienen que poder ser ejecutados en cualquier ambiente (enviroment) desde producción hasta tu portátil en casa sin conexión a internet.
  • Self-validating, auto-validados: los test tienen que, por si mismos, dar como resultado un boolean (verdadero o falso) no queremos test que guarden un resultado en un documento de texto que tendremos que mirar a posterior y comprobar si coincide con otro que ya tenemos, Somos humanos y nos equivocamos, podemos no comprobar bien estos resultados y más cuando llevamos ya unas cuantas horas de trabajo encima ese día.
  • Timely, oportuno: los test solo serán escritos antes del código de producción que haga que los pase. Si tratas de escribirlos después acabarás odiando los test, porque habrá partes que son muy complicadas de probar o el código de producción que tienes no está si quiera pensado para ser probado.

Libro original

Referencias

 

Límites

Límites

Cuando consumimos una API de terceros existe un conflicto de ideas entre quien la crea y quien la consume. Los creadores quieren llegar a la mayor cantidad de público posible y, los consumidores de esta, quieren que sea lo más consisa posible y esté lo mejor enfocada con su problema para evitar funcionalidades superfluas. Por ejemplo si usamos un Map es normal ver lo siguiente repetidas veces en nuestro código.

Sensor s = (Sensor) sensor.get(sensorId);

Si, funciona y si, estámos acostumbrados a verlo con bastante frecuencia pero en términos de clean code es mejorable. Todo porque clase Map devuelve un objeto genérico. ¿Pero por qué no “solucionamos” este problema? Podemos crear una clase en medio que en lugar de devolver object devuelva Sensor directamente

public class Sensor {   ...   public Sensor getById(String id) {     return (Sensor) sensors.get(sensorId);   }   ... }

De esta forma evitamos muchos problemas y hacemos que el uso sea más claro, además podremos limitar el uso a las funcionalidades que realmente nos hacen falta evitando el ruido del que hablamos en el comienzo de una API que trata de contentar a un gran público.

Aprende con los test

Si estamos haciendo uso de una nueva API es normal que al comienzo hagamos algunas funcionalidades pequeñas para comprender el funcionamiento de esta y como trabajar con ella correctamente en un proyecto de pruebas donde no rompas nada. ¿Y si en lugar de eso creamos unos test básico? ¿Qué puede tener de malo? Que cuando cambien la API nos demos cuenta porque los test fallan, si un conpañero está en la misma situación pueda ver las funciones básicas en esta “documentación” hechas con los test, si dentro de un tiempo vamos a volver a usarla tener donde refrescar la memoria. Creo que he logrado dejar claro que es una buena idea que ayuda más de lo que molesta y no te consumirá tanto tiempo como para no merecer la pena.

Libro original

Manejo de errores

Manejo de errores

Puede sonar raro hablar de errores en un libro que trata sobre código limpio, pero los errores son algo que ocurre porque somos humanos y como tal erramos. Un usuario puede perfectamente introducir un dato no válido y esto tenemos que controlarlo de alguna manera.

Usa excepciones en lugar de códigos de error

Antes era normal ver en funciones esta función devolverá un número mayor a 0 en caso de que termine correctamente, en caso contrario -1 si no se encontró el fichero, -2 si no se pudo acceder, etc. Esto ocurría porque no existían excepciones, a día de hoy hacer esto solo hace que el código de quien usa tu función se ensucie al tener que controlar si el valor devuelto es válido o no y porque no lo es. En lugar de esto todo queda más limpio si cuando ocurre un error lanzamos una excepción.

Usa excepciones sin marcar (Unchecked Exceptions)

Las checked exceptions son aquellas que tienen que estar en la firma del método reflejadas como que pueden ocurrir en algún momento. En los inicios cuando Java las añadió parecían una buena idea, pero con el paso de los años se han dado cuenta que no son tan necesarias como pensaban. Esto ocurre porque si, tiene sus ventajas, pero también sus inconvenientes.

Uno de ellos es que estas no respetan el principio abierto/cerrado (Open/Closed Principle). Imaginemos, lanzamos un nuevo checked exception en un método y el catch de este está tres niveles por debajo en la implementación. Tendremos que ir nivel por nivel cambiando la firma de todos los métodos para que la aplicación compile nuevamente… Esto no parece una buena idea, y eso que solo son tres niveles si se tratase de una aplicación grande en la que hay muchos más niveles pasaremos una buena tarde cambiando todos los niveles. Esto podemos evitarlo usando unchecked exceptions

Explica el contexto de tu excepción

Si creas una excepción, se lo más claro posible explicando el contexto y la localización del error en el mensaje. ¿Porqué ha ocurrido esta excepción?

Define el flujo normal

No hay porque crear excepciones sin pensar hay casos en los que, con cambiar la lógica de nuestra aplicación solucionamos el problema. Supongamos lo siguiente:

try {
  GastosDeComida gastos = reporteDeGastos.obtenerComida(idEmpleado);
  gastosTotales += gastos.obtenerTotal();
} catch (GastosDeComidaNoEncontrados e){
  gastosTotales += obtenerComidaPorDia();
}

En este ejemplo si no encontramos los gastos en comida del empleado lanzamos una excepción y entonces añadimos la comida por días pero, ¿porqué generamos esta excepción? Si en lugar de lanzar la excepción controlamos en el método obtenerTotal(), si no hay total en este empleado devolvamos la comida por día, de manera que pase lo que pase siempre devolveremos un dato con el que poder trabajar y todo se soluciona evitando todo esto.

No devuelvas nulos

Si has trabajado con Java un tiempo, es normal que en algún momento te encuentres creando algo como esto:

if(usuario != null){
  Tarea[] tareas = usuario.obtenerTareas();
  if(tareas != null){
    ...
  }
}

Puede incluso que te parezca hasta normal ¡pero no lo es! si estás creando una clase y devuelves un nulo recuerda, te estás dejando más trabajo para el futuro. Puedes pensar que no es para tanto, una comprobación de si es nulo o no y ya está pero veamos, que ves más fácil de comprender y ver de primeras entre estos dos códigos:

List empleados = obtenerEmpleados();
if(empleados != null){
  for(Empleado e: empleados){
    pagoTotal += e.obtenerPago();
  }
}
List empleados = obtenerEmpleados();
for(Empleado e: empleados){
  pagoTotal += e.obtenerPago();
}

Creo que ves por donde voy, la segunda opción queda más clara. Pero además dejando la claridad y lo bonito que queda supongamos que se te olvida hacer la comprobación de si es nulo o no, ¿ahora que? a buscar en la pila del error porque es nulo y donde y como tengo que hacer para solucionarlo. Y todo porque cuando implementaste la función obtenerEmpleados() no hiciste una comprobación del tipo si no hay empleados devuelvo una lista vacía.

No pases nulos como parámetros

Si devolver nulos está mal, ¡pasarlos como parámetros es aún peor! Imagina que estás usando una API la cual no espera que le pases un nulo, ¿que va a pasar? pues lo esperado nuestro “querido” NullPointerException y como lo vas a solucionar si la API es tuya puedes añadirle excepciones que, no deberían estar ahí, ensucian y hacen que sea menos claro su funcionamiento. Así que hazte un favor y evita los nulos de todas las formas que puedas.

Libro origina

TODO

  • Escribe tu bloque Try-Catch-Finally primero

Objetos y estructuras de datos

Objetos y estructuras de datos

¿Porque crear variables privadas si acto seguido creamos los getters y setters? Con esto hacemos que el modificador private pierda el sentido.

Los getter y setter solo deben estar cuando sea necesario su existencia porque estamos compartiendo esos datos crudos. Cuando creamos una clase lo hacemos con la intención de mostrar unos datos con un formato. Por ejemplo, si estamos con una clase que sea para control del combustible de un vehículo ¿Cual de las dos clases siguientes esta mejor?

public interface Vehiculo{
  public double capacidadDelTanqueEnLitros;
  public double litrosDeGasolinaActuales;
}
public interface Vehiculo{
  void rellenarTanque(double litros);
  double damePorcentajeDeCombustibleRestante();
}

El segundo caso es el aconsejable, en esta clase no importa como guardamos los datos si en litros o galones o centímetros cúbicos. El usuario solo le interesa cuanto le queda respecto del total y poder indicar que ha llenado el deposito, y eso es lo que le damos en la segunda. No tenemos que dar mas datos de los que son necesarios y con el formato correcto. De forma que si en algún momento queremos cambiar la forma en la que implementamos esta clase, el tipo de datos o la forma de calcular, el usuario no le supondrá problema. Ya que en el segundo caso seguiremos devolviendo el mismo resultado, pero calculado de otra manera o en otras unidades pero haciendo la conversión antes de devolver el resultado. En el primer caso el usuario de nuestra clase tendrá que hacer cambios donde use esos campos.

Datos estructurados u Objetos

Pongamos unos ejemplos para reflejar las diferencias entre estos. En el primero de ellos implementamos una clase por cada figura geométrica y otra en la que calculamos el área.

public class Cuadrado {
  public Punto superiorDerecho;
  public double lado;
}

public class Rectangulo {
  public Punto superiorDerecho;
  public double altura;
  public double anchura;
}

public class Circulo {
  public Punto centro;
  public double radio;
}

public class Geometria {
  public final double PI = 3.141592653589793;

  public double area(Object figura) throws FiguraNoSoportada {
    if (figura instanceof Cuadrado) {
      Cuadrado cuadrado = (Cuadrado) figura;
      return cuadrado.lado * cuadrado.lado;

    }if (figura instanceof Rectacgulo) {
      Rectangulo rectangulo = (Rectangulo) figura;
      return rectangulo.altura * rectangulo.anchura;

    }if (figura instanceof Circulo) {
      Circulo circulo = (Circulo) figura;
      return PI * circulo.radio * circulo.radio;
    }
    throw new FiguraNoSoportada();
  }
}

Los programadores orientados a objetos estarán arrancándose los pelos de la cabeza al ver esto, pero tiene sus aspectos positivos si tienes que añadir una función perímetro en la clase Geometría las clases de figuras ¡no se ven afectadas! Pero por otro lado si incorporamos una nueva figura todos los métodos de la clase Geometría tendrán que ser actualizados con la nueva.

Si por el contrario tenemos el caso siguiente en el que usamos la orientación a objetos en el que el método area() sea polimórfico, la clase Geometría no sera necesaria gracias al uso de las interfaces. Y en caso de tener que añadir una nueva figura ¡no tendremos que hacer nada!

public class Cuadrado implements Figura {
  private Punto superiorDerecho;
  private double lado;

  public double area() {
    return lado * lado;
  }

}

public class Rectangulo implements Figura {
  private Punto superiorDerecho;
  private double altura;
  private double anchura;

  public double area() {
    return altura * anchura;
  }
}

public class Circulo implements Figura {
  private Punto centro;
  private double radio;
  public final double PI = 3.141592653589793;

  public double area() {
    return PI * radio * radio;
  }
}

Solo para dejarlo claro son dos implementaciones completamente opuestas objetos o estructuras de datos, tu decides. Si prevés que vas a incluir nuevas funciones en un futuro las estructuras de datos son mucho mas fáciles pero, por el otro lado, si tu previsión es que incluirás nuevos “tipos” de datos la orientación a objetos soluciona el problema mucho mas sencillamente.

La ley de Demeter

Resumiendo mucho esta ley dice que quien modifica (modulo que lo usa) un objeto no debe conocer sus entresijos (como se implementa internamente). Esta ley no es aplicable a las estructuras de datos, ya que como vimos antes el funcionamiento de estas es justamente lo contrario. Esta ley de Demeter es aplicable para la orientación a objetos.

Híbridos

Si vas a implementar ambos casos a medias terminaras por no implementar ninguno y quedarte con la peor parte de los dos. No hagas un híbrido de objeto y estructura de datos porque acabaras con la peor parte de cada uno de ellos, analiza y decide cual compensa mas implementar, seguramente no uses todas sus ventajas y sufras algunos de sus defectos pero es decisión tuya saber cual compensa una cosa con la otra de mejor forma.

Libro origina

Formato

Formato

¿Dejar una linea en blanco entre métodos o no? ¿El corchete de inicio de la función en la misma linea o en la siguiente? ¿Los campos de la clase todos en la parte superior o al final? Parecen pequeños detalles insignificantes pero a la hora de leer y comprender lo que hemos hecho es mejor seguir siempre un mismo estilo y mas aun si estamos trabajando en equipo. A veces depende del lenguaje que usemos pero en la gran mayoría de casos es un tema del cada persona.

De estos temas no hay hojas de estilo estrictas que sigan todos los programadores, pero existe unas “normas” que todos conocen o van descubriendo con el paso del tiempo.

Formato vertical

¿Cuanto debe ocupar un fichero? Como ya dije no hay nada escrito en Java un fichero es una clase, si estamos haciendo lo correcto. Estas si miramos casos de proyectos ya desarrollados vemos que rara vez superan las 500 lineas y que normalmente se extienden 200 lineas, en esta extensión una clase tiene mas que suficiente para realizar todas las funciones que debería.

Toda nuestra clase debe de estar estructurada como un articulo empezando por un titulo que nos acerque sobre el tema que trata esta, luego una introducción donde tendremos los campos de la clase y el constructor, con lo que sabremos con una mirada rápida quienes son los “personajes” que tenemos en nuestra historia. Un nudo donde estarán todas las funciones de la clase.

Todos estos métodos deben de estar tan cerca unos de otros como de relacionados estén entre si. De manera que si en un primer método usamos un segundo este debería estar justo debajo, como si continuásemos un párrafo. Todo esto es por sentido común, no lo pondremos lejos para estar saltando de arriba a abajo sin parar hasta marearnos.

De igual manera las variables no las declararemos muy lejos de donde las usamos, si estas solo tienen razón de ser dentro de un método lo declararemos en este. Si es una variable para controlar un bucle, esta estará justo encima del bucle.

Formato horizontal

En los inicios de la informática no se escribían mas de 80 caracteres (si no me equivoco) porque los terminales donde se veían no alcanzaban mas. Hoy en día no podemos tener esta guía dada la gran cantidad de monitores en el mercado, panorámicos, etc. de todos los tamaños y colores. Pero como limite se podría decir que unos 120 caracteres es una medida buena.

Los espacios entre los parámetros de una función, personalmente me gusta que después de cada coma ‘,’ dejar un espacio y es recomendable para ayudar a ver que se tratan de variables diferentes. De la misma forma podemos dejar espacios entre las variables de una operación si ayuda a que esta se vea de forma mas clara.

Cuando declaramos muchas variables seguidas, hay quienes las declaran de la siguiente forma:

private   Socket          socket;
private   InputStream     input;
private   OutputStream    output;
private   Request         request;
private   Response        response;
private   FitNesseContext context;
protected long            requestParsingTimeLimit;
private   long            requestProgress;
private   long            requestPars;

Puede parecer a primera vista que de esta forma queda mas claro pero no, no es verdad es mas fácil no seguir la linea que toca y terminar leyendo mas arriba de donde empezamos equivocándonos.

“`Java
private Socket socket;
private InputStream input;
private OutputStream output;
private Request request;
““

Reglas de equipo

Todas esta reglas que seguimos cada uno de nosotros al escribir no dejan de ser por gusto personal. Por ello si vamos a realizar un proyecto en equipo es mejor usar 10 minutos antes de empezar a hacer nada y llegar a un acuerdo entre todos para que todo este sea de la misma forma y no veamos decenas de estilos diferentes. Sera una ayuda en el futuro si tenemos que leer parte del trabajo hecho por un compañero.

Libro origina

Comentarios ¿valen la pena?

Comentarios

Comentarios, un buen comentario puede ser muy útil, puede ser la mejor ayuda que encontraste ese día pero, por otra parte pueden ser tan dañinos y malvados que ni el mismo autor de ellos sabrá porque están ahí. La mala noticia es que la mayoría de las veces los comentarios entran en este segundo grupo.

Malos comentarios

No arreglan mal código

Se supone que cuando dejamos un comentario lo hacemos para suplir alguna carencia que tenemos en el código así que primer consejo, no maquilles mal código con comentarios. Si unas líneas de tu código no hacen lo que dicen, no están nombradas como deberían o no tienen sentido que estén ahí, no les añadas un comentario aclaratorio no es la solución.

No explican que estás haciendo

Si estás ante una condición como esta

// Comprobar si el empleado puede ser seleccionado para jubilarse
if ((empleado.señal & SEÑAL) && (empleado.edad > 65))

¿Porque no intentas darle la vuelta a esto y hacer un código auto explicativo?

if (empleado.estaPreparadoParaLaJubulación())

No son redundantes

Si tienes unas líneas que se explican por si solas no dejes un comentario que explican exactamente lo mismo.

// Si está cerrado devuelve la hora de apertura
if(cerrado){
    return new HorarioApertura();
}

Tampoco hagas esto, creo que sobra explicar porqué.

/**
*
* @param titulo El titulo del CD
* @param autor El autor del CD
* @param año El año del CD
*/
public CD(String titulo, String autor, String año){
...
}

No engañan

No dejes comentarios que engañan al lector, no escribas diciendo que este método devuelve verdadero cuando la tienda está cerrada y luego si miras la implementación realmente lo que hace es mirar si está cerrada y en caso de que no lo esté espera un tiempo para realizar una segunda comprobación. Porque con esto solo logras que al usar esta función no obtengas los resultados esperados.

Deben aportar algo

Si escribes un comentario para explicar algo que no tiene razón de ser explicado, sobra no dejes ese comentario no expliques que esta variable que estás declarando es privada si delante de ella tienes puesto private, no dejes un comentario sobre un constructor por defecto diciendo “este es el constructor por defecto“. ¿Que pretendes lograr con todo esto? Yo te digo lo que logras con todo ello, logras generar ruido en el código. Con ruido me refiero a líneas de código que no tienen razón de estar ahí que entorpecen a los demás a la hora de leer.

No cierran funciones

En los inicios de la programación, estos tenían algo de sentido pero a día de hoy no tienen razón de ser. Si no sabes de lo que te hablo mejor, no los has tenido que ver significa de algún modo todo que estamos por el buen camino. Me refiero a lo siguiente.

while(condición()){
    if(comprobación()){
        for(){
        ...
        } //for
        ...
    } //if
} //while

A día de hoy con los IDEs que tenemos que nos marcan con colores donde cierra cada llave, nos permiten colapsar las líneas que tiene dentro incluso, no hay razón válida para que esto siga existiendo.

No son código no válido

No comentes código que ha dejado de servir, que ya no funciona o incluso nunca ha llegado a funcionar. Si ese código ya no lo usas ¿para que lo “guardas”? En serio crees que en algún momento haciendo construyendo otra clase, dirás “cónchale voy a rescatar este trozo que dejé comentado, que no tiene nada que ver” porque te aviso ya, que esto no ocurre. Es más si ya se te ha ocurrido la solución una vez, se te ocurrirá una segunda y de manera más rápida, en serio no comentes código porque te costó mucho trabajo llegar a él o porque “me será útil luego”.

Buenos comentarios

Existen circunstancias, como ya he comentado, donde un comentario puede ser de gran ayuda. Aunque bajo mi punto de vista siguen siendo un arma de doble filo, explicaré al final porqué.

Comentarios legales

Comentarios que por la razón legal que sea tienen que estar escritos, con el autor, el año, empresa, etc.

Comentarios informativos

Comentarios que información básica de alguna sentencia compleja. Una expresión regular por ejemplo, puede estar bien explicar de manera rápida y básica que comprueba, aunque siempre se puede extraer a una clase que se encarga de estas comprobaciones, de forma que este comentario no haga falta.

Explicar la intención

Si has solucionado un problema no de la mejor forma, puedes indicar que lo has hecho así con un porqué.

Comentarios aclaratorios

En caso de que estés usando una librería que los valores que devuelve no corresponden con resultados legibles, puede estar bien un comentario a final de la línea explicando porque ese 1 que devuelve si es mayor que nuestro valor indica que debemos sumar dos valores.

Javadocs

Si estás creando una API pública es casi obligatorio dejar estos comentarios siguiendo el formato.

TODO

Estos comentarios están estandarizados y representan problemas que como programador no puedes solucionar en este momento o porque no quieres dejar lo que estás haciendo y dejas esta nota para volver en un futuro. Además al estar estandarizados los IDEs potentes en la actualidad son capaces de encontrarlos por nuestro proyecto y mostrarnos una lista con todos ellos.

Para acabar sólo un último aviso, es la razón por la cual dije que son un arma de doble filo. Los comentarios no se refactorizan, normalmente si cambias el nombre, el resultado o la función que realiza ese método no se suelen acordar del comentario que había justo encima tratando de explicar este método. Método, que antes tenía un comentario explicativo que ahora a pasado a ser comentario engañoso.

Libro origina

Funciones

Funciones

Como ya vimos en el capítulo anterior nombrar coherentemente es importante, pero no es lo único que tenemos que hacer. Las funciones si están bien nombradas pero ocupan 3.000 líneas no son precisamente algo manejable, y buscar donde se produce un error en ellas será un dolor de cabeza. El libro recomienda que cada función ocupe tanto como abarque tu pantalla, siempre y cuando esta no sea extremadamente grande, unas 20 (llegando al límite, pero con la mitad sobra para poder cumplir con su objetivo la función)

Haz solo una cosa!

Por ello mantener funciones que sean pequeñas en cuanto a líneas de código y funcionalidad es importante. No debes de liarte y hacer en un mismo bloque 100 cosas distintas, haz una sola cosa por cada función, dale un buen nombre y así sarbás a donde acudir cuando falle.

Identación

Otro punto importante es la identación es algo que si sigues el consejo anterior se soluciona un poco. La cuestión de este punto es que no debes comenzar a encadenar condiciones (por ejemplo, if {} else), aumentándo la identación del código con cada una de ellas, hasta llegar a salir de la pantalla. Esto hace que no sea legible de manera rápida y cómoda y más fácil de liarte a la hora de revisar el trabajo ya hecho.

Switch

Esta sentencia por como está diseñada es difícil que ocupe poco, ya que con tres opciones solamente se extiende 12 líneas. Además si queremos modificar este porque ha cambiado los requisitos de nuestra aplicación un switch nos dará problemas, no siendo la mejor opción. Para evitar que esto ocurra nos recomienda hacer el uso de la abstracción.

Argumentos

Cuando declaramos una función a veces nos olvidamos de que los argumentos luego tendremos que completarlos al hacer uso de esa y comenzamos a solicitar una gran cantidad de ellos. Esto hace que luego cuando la llamemos quede un churro en medio de nuestro código con muchos parámetros que nos chocará cuando estemos leyendola. Además de que siempre será más complicado de modificar en un futuro. En el libro nos recomiendan, que cada función como máximo deba tener 3 parámetros. Si hace uso de más de estos la mayoría de veces significa que lo que necesitas no son los parámetros como tal, si no un objeto con todos estos atributos. Por ejemplo si queremos pintar un punto en pantalla, podremos pedir como parámetros: coordenada X, coordenada Y, radio. Pero si nos fijamos realmente las dos primeras pertenecen a lo mismo, un punto, si en lugar de esto simplemente solicitamos punto y radio queda todo más claro.

Nombrar correctamente

Las funciones deben de ser nombradas como verbos (acciones) de manera que sean ejemplificativas de que están haciendo en su interior sin tener que investigarla. Por esta misma razón es importante que no hagamos dentro de una función más de lo que decimos hacer, si tenemos una función comprobarConección no debemos hacer más que la comprobación, si se nos ocurriera que, una vez comprobado su correcto estado la inicializamos, tendríamos que llamar de mejor manera a esta algo como: comprobarConecciónEIniciar

Argumentos de salida

Estos argumentos existen por razones historicas, desde la llegada de la Orientación a Objetos (OO) carecen de sentido, es más hacen que las funcuiones no sean tan claras como deberían. Si una función tiene que devolver algo, esto lo hará por su parámetro de salida (su return) no por un argumento pasado que es un objeto que modificamos. Si tenemos que modificar un objeto lo haremos de la siguiente forma:

objeto.actualizarParámetro();

Códigos de error

De igual forma los códigos de error no son nada aconsejable, si los usamos tendremos que comprobar si el valor entra dentro de uno de los códigos de error que existen para esa función y luego actuar en consecuencia. Esto hace que el código se haga más complejo añadiendo comprobaciones innecesarias que con excepciones sería más fácil dentro de un bloque try {} catch

Libro origina

Nombres Significativos

Nombres significativos

En este capítulo se trata un tema el cual me importa especialmente, nombrar variables, métodos, clases o cualquier cosa que requiera ser nombrada. Es algo que en los inicios de mi aprendizaje con la programación nadie me dijo y después de dos años me di cuenta y he ido corrigiendo poco a poco.

Evita la desinformación & Usa nombres que revelen infromación

Dar un buen nombre puede llevar tiempo, pero ayuda a ahorrar más en el futuro. Supongamos tenemos lo siguiente

private void calcula(int numero){
// haz algo largo y lioso
...
}

Cuando nos encontremos en nuestro programa esto tendremos que mirar primero si hay algún buen comentario que nos diga que hace, con suerte, o pasar una divertida tarde descifrando que es lo que obtenemos de todo esto y si realmente es lo que queremos porque “calcula” pero que es lo que calcula. Esto es un ejemplo extremista pero queda más claro de esta forma.

Si en lugar de esto tuviésemos esto otro sería mucho más fácil

private void calculaPresiónDelAgua(int metrosCubicos){
// haz algo largo y lioso
...
}

Nombres pronunciable

De igual manera los nombres tienen que ser pronunciables no le demos nombres cortos con siglas que hace que a la hora de nombrarlo sea imposible. Imagina tienes un campo que guarda la fecha con el formato año-mes-dia-hora-minuto-segundo y decides llamar a este private date genamdhms; cuando trates de preguntar a otra persona del equipo por este campo ¿como lo nombrarás? y claro, suerte para que tus compañeros le den el mismo nombre y sean capaces de ponerse de acuerdo. Mejor si lo llamas de la forma private date generateTimestamp


Nombres buscables

Es bastante normal ver trozos de código como este

for (int j=0; j<34; j++){
    s += (t[j]*4/5);
}

Que no está mal cumple su función (cualquiera que sea, supongo) pero si queremos corregir, por ejemplo ese “34” en algún momento prque ha cambiado el límite, para empezar no sabemos que es lo que indica y segundo ¿como lo piensas buscar entre todas las líneas de código? sería mejor si hicieramos esto en su lugar

int realDaysPerIdealDay = 4;
const int WORK_DAYS_PER_WEEK = 5;
int sum = 0;
for (int j=0; j < NUMBER_OF_TASKS; j++) {
    int realTaskDays = taskEstimate[j] * realDaysPerIdealDay;
    int realTaskWeeks = (realdays / WORK_DAYS_PER_WEEK);
    sum += realTaskWeeks;
}

De esta forma en un futuro si queremos cambiar el número de tareas o como se calculan estas por día, solo tendremos que usar el buscador y cambiarlo donde esté la constante.


Notación húngara

Por bagajes culturales de los primeros lenguajes de programación donde indicaban en el nombre de la propia variable el tipo de esta, entiendo que porque era más cómodo al no tener los lenguajes actuales de tipado fuerte y los potentes IDE que tenemos en la actualidad. Pero a día de hoy estoy no es necesario el propio compilador te avisa si no es compatible la variable (en caso de Java, por ejemplo). No hagas String telefonoString


Nombres de clase

Estos deben de ser nombres no verbos usar Customer, Wikipage, Account en lugar de Manager, Processor, Data

Nombres de métodos

De forma contraria los métodos deben de ser nombrados como verbos, identificando una acción postPayment, deletePage, save

No seas lindo

Nombrar tratando de ser gracioso por alguna broma que tienes con cierto compañero, o porque está relacionado con una serie de televisión famosa que “todo el mundo conoce” o “está claro lo que significa” no es una buena idea, por mucho que te vayas a reir cuando lo leas. Recuerda no todas las personas tienen el mismo nivel de cultura en los mismos temas que tú y no tienen porque saber que significan esas siglas.

No añadas contexto gratuitamente

Si estás desarrollando una aplicación que se llama “Gas Station Deluxe” es mala idea nombrar todas las clases con el prefijo “GSD”. Te estás añadiendo ruido cada vez que pulses “G” para usar algún método, variable, etc que realmente si empieza por esta letra el IDE te hará sugerencias de todo lo que no necesitas.

A parte de estos puntos que he resumido de forma muy simple, hay otros puntos que se tratan en el libro que he decidido no añadir.

Puntos no descritos

  • Make Meaningful Distinctions
  • Avoid Encodings
  • Member Prefixes
  • Interfaces and Implementation
  • Avoid Mental Mapping
  • Pick One Word per Concept
  • Don’t Pun
  • Use Solution Domain Names
  • Use Problem Domain Names
  • Add Meaningful Context

Libro original

¿Que es el código limpio?

Código limpio

Todo este capítulo se dedica a hacernos ver la importancia que tiene el código, que este sea lo más limpio posible y, los diferentes puntos de vista sobre que es código limpio. Además muestra como afecta en gran medida en los proyectos actuales y a los equipos que lo desarrollan.

¿Que es el código limpio?

Para algunos de los autores que nombra, el código limpio es un código el cual es placentero de leer, otros nombran que tiene que ser enfocado, sin rodeos, elegante entre otros calificativos con los que estoy bastante de acuerdo. Y si es tan bueno, ¿porque todos no lo hacen de esta forma?. El libro lo deja bastante claro con un símil entre el código y el arte.

Una persona es capaz de diferenciar un buen cuadro en el que el artista ha representado bien un objeto (por ejemplo) pero por mucho que esa persona vea cuadros o lea sobre ellos no va a saber pintar uno.

Con el código limpio ocurre de igual forma, un programador al verlo lo reconoce pero por mucho que vea y lea sobre patrones y técnicas, sin las horas suficientes de práctica no logrará hacerlo. Y para muchos que ya se encuentran en el sector trabajando reaccionan: “Para que voy a aprender chino, si ya funciona como estoy” y no se molestan en aprender y practicar las nuevas técnicas.

Equipos sufren problemas por código lioso/sucio

Muestra como ha visto mermar la productividad de equipos profesionales de desarrollo porque empiezan a implementar código lioso excusándose en: “Si no, no llegaremos al deadline. Después lo arreglo” y este “después” es nunca y termina por ser un software lioso y desastroso que ha llegado a tal punto de descontrol que ni los mismos creadores son capaces de manejar. Y cuando el inversor ve lo que está pasando solicita que rescriban el código completo, contratando a un nuevo equipo que se encargará de ello mientras los otros continuan desarrollando paralelamente. Esta situación puede alargarse durante mucho tiempo, ha llegado a ver casos en los que tardan hasta 10 años, con los costes que esto conlleva.

¿Para quien escribimos el código?

Un detalle a tener en cuenta cuando escribimos código es saber para quien escribimos este, algunas personas piensan que el código que escriben es para los usuarios finales de la aplicación ¿eres uno de ellos? para unos usuarios finales a los que se le da una aplicación empaquetada, compilada, ofuscada en algunos casos, que solo le importa si le soluciona su problema y en un tiempo comprensible, ¿para ellos?

Escribimos código para otros programadores, otros que usaran nuestras librerías, otros que se incorporarán al equipo a mitad, otros que tratarán de ayudarnos a solucionar problemas del que no vemos salida, incluso para nosotros mismos en el futuro. Pasamos más tiempo leyendo código que escribiendo, mejor si lo que escribimos es claro y no requiere de grandes esfuerzos para comprenderlo.

Libro origina