En estos días hemos estado hablando @ulisesantana y yo sobre Toggl. Una herramienta de tracking de tiempo que llevo usando ya unos cuantos años y se ha vuelto en mi día a día casi sin darme cuenta. Con esta aplicación puedo ver como uso mi tiempo. Principalmente, la uso para controlar las tareas que hago en las horas de trabajo. En esta publicación les voy a contar como lo tengo configurado y como hago uso de ella. Lo primero que suelo hacer cuando empiezo en un proyecto nuevo, es crear un “cliente”. Este cliente es para quien estamos haciendo el trabajo y luego, dentro de este cliente, voy añadiendo los proyectos internos en los que estoy metidos entre otras cosas. Como puedes ver, en mi caso tengo dos clientes principales. Uno de ellos “Lean Mind” tengo solamente el proyecto “formación” y en el caso de Clientito, según el proyecto interno tengo varios proyectos, reuniones o simplemente tiempo que no sé muy bien como clasificar (otros). Suelo ser bastante estricto y cada vez que empiezo una tarea. Lo primero que hago es ir a Toggl, darle al play, poner el nombre a la tarea y encajarla dentro de uno de los proyectos. Esto, de alguna forma me ayuda a centrarme en lo que estoy haciendo y pensar un poco antes de empezar. Una vez ya estoy trabajando de vez en cuando consulto a ver cuanto tiempo llevo (sobre todo si estoy atascado) para ver si ha pasado mucho tiempo o no. Me suele pasar que me pongo a trabajar en algo y el tiempo pasa volando sin darme cuenta. De esta manera sé cuanto es ese tiempo. Suelo obligarme a parar en caso de que no esté avanzando para alejarme un poco del problema, sobre todo si ya llevo un tiempo considerable. En estos momentos suele ser cuando reviso el correo, leo slack, contesto a compañeros, reviso PR, etc. Cuando esto ocurre lo que me gusta hacer es volver a darle al play cuando regreso a la tarea en la que estaba sin haber parado el tiempo anterior. De esta manera contabilizo los bloques de tiempo que he dedicado a una tarea en el día. Ejemplo Supongamos estoy creando un nuevo endpoint para obtener gatos. Empiezo a las 10:10 y estoy hasta las 12:20. Paro porque estoy atascado con algo y me paso 10 min revisando slack y demás. Cuando a las 12:31 vuelvo a ponerme con la tarea vuelvo a decirle a Toggl que estoy haciendo de nuevo la misma tarea “duplicando” la entrada. De esta forma me ayuda a ver si me he pasado de tiempo en la última sesión de “pomodoro”. Gestionar el tiempo de esta forma me es muy útil cada mañana cuando al sentarme en el PC voy a reportar las horas de trabajo. Digo que es útil porque me basta con ir a la parte de reportes en Toggl y buscar el tiempo que dediqué el ayer en Clientito y ponerlo en el excel o donde toque. También me es muy útil para mirar las horas de formación que llevo esta semana o este mes y poder dedicarle más o menos tiempo. Por ejemplo esta semana dediqué 2 horas y 19 minutos en revisar grow, preparar un artículo nuevo sobre big data. En mi caso me es útil para no volverme muy loco con el tiempo que le dedico a las cosas. Reviso de vez en cuando y actuar en consecuencia 😸. Además de, en cada reunión diaria o semanal saber decir en que he estado trabajando para no quedarme en blanco 😂
Olores del código: test Existen muchas cosas que no huelen bien en el código y a continuación vas a poder ver las referentes a los test. Esta es una publicación parte de una serie de publicaciones que haré referentes a olores del código. Test Test insuficientes, ¿cuantos test debe tener mi suite? Siento tener que decírtelo pero no hay una métrica para ello. Tienes que tener tantos test como para probar todo lo que puede fallar. Aunque he escuchado decir que normalmente con tener un 80% de la aplicación testada puede valer. Usa una herramientas de cobertura, existen herramientas que te hacen análisis sobre la cobertura de tus test (a veces los propios IDE´s lo tienen integrado). Estas te analizan que partes están cubiertas con un test y cuales no, te hacen tener una visión de como está el proyecto. No te saltes los test triviales, en ocasiones puedes pensar que este test es demasiado obvio como para comprobarlo. Pero la verdad es que este tipo de test tienen más valor como documentación que como comprobación en sí mismo. Hazlos y deja reflejado ese comportamiento. Un test ignorado es una pregunta sobre ambigüedad, a veces no tenemos claro el comportamiento de algún detalle de comportamiento. Esto lo podemos dejar reflejado como un test comentado o ignorado, de esta manera esta cuestión queda reflejada en el código y será más fácil de solucionar. Testea condiciones del entorno, a menudo nos podemos encontrar delante de una solución conrrecta pero ser el entorno quien nos genera errores. Es por ello que hay que tener en cuenta las condiciones del entorno. Testear exhaustivamente cerca de los bugs, cuando encontramos un bug en una función es muy probable que no se encuentre solo. Es por ello que una vez encuentras un bug es recomendable testar esa función a fondo, probablemente encuentres más errores. Los test deben de ser rápidos, un test lento es un test que no se va a ejecutar. Cuando estás ejecutando la suite de test constantemente el test más lento es el test que terminarás por saltar. Este hará que tu ritmo de trabajo baje y te cansarás, por eso es mejor que los test sean lo más rápido posible. Referencias Libro original sobre Clean Code, esta publicación corresponde con el capítulo 17.
Olores del código: funciones y nombres Existen muchas cosas que no huelen bien en el código y a continuación vas a poder ver las referentes a funciones y nombres. Esta es una publicación parte de una serie de publicaciones que haré referentes a olores del código. Funciones 1.Muchos argumentos, las funciones son mejores cuantos menos argumentos tengan. Lo ideal es que no tengan ninguno y como máximo tres, a partir de tres deberías cuestionarte si los argumentos no son realmente un objeto con esas características, por ejemplo. 2.Argumentos de salida, los argumentos como salida no son intuitivos. Al usar una función se entiende que los argumentos son parámetros de entrada y la salida la devolveremos al terminar la ejecución con el return. 3.Argumentos bandera, los argumentos boolean para indicar a la función el tipo de comportamiento que tiene que hacer son confusos. Si una función puede hacer más de una cosa, sepárala en dos funciones independientes. 4.Funciones muertas, funciones que nunca son usadas no tienes porqué tenerlas. No guardes código viejo inútil, bórralo. Tu sistema de control de versiones lo recordará por ti! Nombres 1.Escoge nombres descriptivos, los buenos nombres son el 90% de la legibilidad del código. Darle un buen nombre a una función, a una variable o a una clase no es algo trivial. Esta es de las partes más difíciles, los buenos nombres requieren de pensarlos con calma. No dejes nombres como i, j o aux. Los dos primeros casos si lo usas en un índice de un bucle, se podría pero porque esta variable i no abarca más de las tres líneas del bucle. Si se tratara de un índice global, no lo hagas! 2.Escoge nombres con un nivel de abstracción adecuado, al escoger nombre ten en cuenta en el nivel en el que te encuentras y mantén siempre el mismo. Si estás, por ejemplo, con una interfaz módem con varias funciones como la siguiente. public interface Modem { boolean dial(String phoneNumber); boolean disconect(); boolean send(); String getConnectedPhoneNumber(); } De primeras puede parecer que está correcta pero realmente ¿nos interesa saber que está implementada con un número de teléfono? Dentro de un tiempo si queremos usar otro tipo de localizador ¿como lo hacemos? Es por ello que sería mejor si lo hiciéramos de la siguiente forma. public interface Modem { boolean connect(String connectionLocator); boolean disconect(); boolean send(); String getConnectedLocator(); } 3.Usa la nomenclatura standar, donde sea posible, cuando estás implementando un patrón es probable que este tenga una nomenclatura estandarizada para dar nombres a ciertas partes de la aplicación como pueden ser las funciones, métodos o las clases. Sigue el estándar todo lo posible de esta forma no generas confusiones. Por ejemplo si tienes un método que transforma tu objeto a String, en Java, se usa toString(). Otro ejemplo son las funciones que puedes ver en los condicionales if(), estas suelen empezar por is. Quedando de esta forma if(isFromSpain(phoneNumber)). 4.Nombres no ambiguos, tienes que escoger un nombre que no se quede corto y deje esconder parte de la tarea que realiza una función. Si tienes una función que se llama rename() e internamente, cambia el nombre y añade un prefijo, te estás quedando corto. Un mejor nombre sería renameAndAddPrefix(). 5.Usa nombres largos para ámbitos largos, después tanto hablar de dar nombres descriptivos puedes llegar al extremo de dar nombres largos a variables contadoras en los iteradores. Como te he dicho en el apartado anterior si el ámbito del índice es solamente dentro del iterador for no hace falta que lo llames numberCounter si tienes dos líneas de código dentro y se ve que solamente se usa como iterador. 6.Evita codificar, a día de hoy los IDEs que tenemos son potentes, nos ayudan con los nombres, nos avisan si hay duplicados, si no usamos una variable. No tenemos que comenzar por l_ para indicar que la variable es local, ni nada parecido los IDEs nos lo indican. 7.Los nombres deben ser descriptivos, sin efectos secundarios, los nombres tienen que ser sinceros. No puedes tener una función public getTimer(){ if(localTimer == null){ localTimer = new Timer(): } return localTimer; } Si nos fijamos esta función no devuelve un timer lo que hace realmente es crear uno si no existe ya y luego lo devuelve. Un posible nombre más correcto para la función podría ser createIfNotExistsAndReturnTimer. Referencias Libro original sobre Clean Code, esta publicación corresponde con el capítulo 17.
Olores del código: comentarios y entorno Existen muchas cosas que no huelen bien en el código y a continuación vas a poder ver las referentes a comentarios y al entorno. Esta es una publicación parte de una serie de publicaciones que haré referentes a olores del código. Comentarios Se que lo hemos hablado en varias ocasiones pero una vez más, los comentarios no son una mejora del código. Al contrario, suelen entorpecer más de lo que ayudan por las siguientes razones: 1.Información inapropiada, no hace falta añadir meta-datos a un fichero con comentarios. Cosas como fecha de última modificación, autor, etc no son necesarios y sólo generan ruido. 2.Comentarios obsoletos, tener comentarios explicando una parte de código es algo común (no he dicho que esté bien, es común solo eso). Pero la realidad es que esos comentarios nadie se encarga de mantenerlos y quedan rápidamente desfasados con el código al que está atado. Porque si tu cambias el funcionamiento de un método porque no está correcto, no te acuerdas de modificar el comentario que lo explicaba. 3.Comentarios redundantes, ya que no me vas a hacer caso y vas a poner un comentario, al menos que no diga lo mismo que puedo ver i++; //increment i no lo hagas. 4.Comentarios pobres, si sigues pensando que hace falta poner un comentario, al menos escribe el mejor que puedas. Usa la terminología del contexto en el que estás y no generes más confusión de la que ya hay. 5.Código comentado, por favor esto nunca. No dejes código comentado suelto por tu clase, este código nadie lo borra porque nadie sabe porqué está ahí. Y la realidad es que con cada día que pasa se vuelve más irrelevante, la variables que usa dejan de existir, lo métodos a los que llama cambian la firma, etc. Si está comentado, bórralo no es necesario. Si alguien lo necesita puede volver a la versión anterior de tu sistema de control de versiones y verlo. Entorno 1.Desplegar requiere más de un paso, si tienes una aplicación esta tiene que ser fácil de desplegar un paso, dos como mucho. No debes tener que comprobar decenas de ficheros, ver que dependencias falta y complicarte la vida a ti y a quien quiera usarlo. Tendrías que ser capaz de hacer algo parecido a esto get application, cd application, deploy application, run application. 2.Test requiere más de un paso, al igual que el caso anterior esta tiene que ser fácilmente testable. Tiene que poder ser testada con un simple comando que ejecute todos los test. Referencias Libro original sobre Clean Code, esta publicación corresponde con el capítulo 17. Sistema de control de veriones
JUnit Internos JUnit es de los frameworks open source más famosos de testing en Java. Es simple en su concepto, preciso y elegante de implementar. Además al tener tanta fama y tantos seguidores podemos encontrar documentación de sobra. Ejemplo básico Supongamos que tenemos una calculadora con la siguiente implementación. No es importante la implementación en si, pero si quieres reproducir un test te lo dejo, por aquí ;) public class Calculator { public int evaluate(String expression) { int sum = 0; for (String summand: expression.split("\\+")) sum += Integer.valueOf(summand); return sum; } } Y como es normal queremos hacer un test de nuestra calculadora porque como ya sabemos tener una buena batería de test es importante. Entonces implementamos el siguiente. import static org.junit.Assert.assertEquals; import org.junit.Test; public class CalculatorTest { @Test public void evaluatesExpression() { Calculator calculator = new Calculator(); int sum = calculator.evaluate("1+2+3"); assertEquals(6, sum); } } Como vemos es bastante fácil y limpio, es por lo que es tan famoso. Y como este podemos hacer todos los test que queramos, buscando hacer que falle la calculadora. Porque si conseguimos que falle es buena señal, ya hemos descubierto un hueco que tapar, una funcionalidad rota o que no existe. Y claro, mejor encontrarla nosotros que un usuario frustrado. Pasar los test no significa que no falle Y es aquí donde quería llegar, como he dicho puedes hacer todos los test que se te ocurran. Y ahí está el punto, los que se te ocurran. En este caso sólo tenemos uno y parece claro que esta calculadora no está completa ¿verdad? Claro, eso lo vemos nosotros que conocemos más operaciones aritméticas que no sea la suma. Pero, quien ha implementado esta calculadora no conocía más. Para él parece que está completa y testeada contra todos los problemas y casos existentes. No se si ves lo que trato de decirte. Tener una gran batería de test no significa que tu aplicación esté correcta o perfecta porque pasa todos los test. Eso solo significa que aún no has encontrado/escrito los suficientes test para encontrar un fallo. Y antes o después alguien se le ocurrirá otro caso que haga que la aplicación explote. Es por eso que los test si son importantes, es un gran apoyo contra los cambio que puedas hacer al código. Pero no significa que vaya a funcionar en todos los casos que se den. Haces test para probar todo, sabiendo que todo no puede ser probado. Esto es lo que le pasa al autor en este capítulo. Nos muestra un código de primeras bastante correcto y limpio, con una batería de test bastante completa. Y va haciendo varios cambios en el código, nombres de variables, encapsula algunos métodos, etc y finalmente descubre un fallo. Este fallo no estaba visible por como se redactaban las condiciones. Y tras un poco más de cambios en el código, descubre como dos condiciones más sobran. En un primer momento, miras y ves fSuffix > 0 puede parecer que tiene sentido. ... if (fSuffix > 0) result = result + computeCommonSuffix(); return result; ... Pero después de varias vueltas al código con paciencia, llegamos a esto otro que al verlo choca. ... if (suffixLength >= 0) result = result + computeCommonSuffix(); return result; ... Ya que al tratarse de una aplicación que comprueba si dos strings tienen algún carácter distinto. Si nos ponemos a ver el código, llegamos a la conclusión de como esa comparación no tiene sentido. Puesto que si ha llegado a este punto es porque existe un sufijo, es decir es imposible que sea 0 y nunca entrará al else. Una vez sabemos esto podemos borrar ambas comparaciones dejando más claro aún el código. Y todo esto porque el autor quiso darle una vuelta a un aplicación que parecía, de primeras testeada a prueba de misiles nucleares. Pero a pesar de ello se podía mejorar aún más. Siempre se puede mejorar Referencias Libro original sobre Clean Code. Página oficial de JUnit. Wiki primeros pasos con JUnit.
Refinamiento Sucesivo Si te mostrara ahora un trozo de código y te dijese: “Mira que limpio está” podrías pensar que esto ha salido a la primera de mi cabeza porque soy muy bueno pero no es así. Cuando escribes un código la mayoría de veces te pasará que empiezas a hacer cosas “porque funcionan” sin pensar mucho en las buenas prácticas sobretodo cuando estás empezando con ellas. Pero no por eso debes dejarlo pasar. El código que escribas debes considerarlo como un borrador que siempre tiene mejoras pendientes, cada vez que hagas una funcionalidad antes de considerarla hecha dale una vuelta, revísala como si fuera de tu amigo o compañero y trata de mejorarla (siempre se puede). Es por ello que en este capítulo del libro muestra una clase que implementa y como al verla en su “forma final” gusta pero cuando te enseña las primeras versiones ves que realmente no era tan bonita en sus inicio que se ha llevado varios refinamientos para dejarla en el punto en el que se encuentra ahora. En Incrementalismo La mejor forma de romper un programa que funciona es hacer cambios de nombres “para mejorar” la legibilidad en masa esto no suele llegar a buen puerto nunca… Suele generar problemas, conflictos con otros nombres, y un largo etcétera. Para poder hacer estos cambios tranquilos hay una solución TDD, esto junto con hacer muchos cambios pequeños que vamos comprobando. En este caso las comprobaciones las podremos hacer de manera automáticas con los test que tendremos hechos para nuestro programa. De manera que después de cada cambio ejecutamos los test y si estos fallan volveremos al estado anterior donde todo funcionaba, no haremos cambios que rompan el programa. O en caso de que lo que se rompa sea poca cosa y veamos que se puede solucionar porque no era así como debería funcionar, podemos solucionarlo pero como estábamos hablando de cambios de nombres a variables y métodos normalmente si estos generan un problema no suele ser tan trivial. Conclusión Es por todo ello que el código que hacemos no se considera terminado cuando funciona, porque hacer que funcione algo puede hacerlo cualquiera pero los buenos programadores además lo hacen de manera limpia y lo dejan con pruebas para evitar que los cambios lo rompan. Referencias Libro original TDD, Test Driven Development o Desarrollo Dirigido por Test. Notas Durante todo el capítulo el autor hace cambios en su primera versión del código y va explicando cada uno de ellos la línea que sigue de pensamiento. Subiré estos cambios explicados de manera calmada en el futuro.
Concurrencia Concurrencia… Aaaah mi amiga la concurrencia, como te quiero y te odio a la vez. Tú junto con la recursividad has sido, de momento, los dos conceptos que más me ha costado entender en la informática. Es de esas cosas que o entiendes a la primera o tardas tiempo hasta que un día, hace “click” en tú cabeza y todo cobra sentido, vamos a por ti ¿eres realmente tan necesaria? ¿Que es la concurrencia? La concurrencia, resumiendo mucho, consiste en poder hacer mas de una tarea en el mismo instante de tiempo. Llevado al mundo de la programación dos funciones/métodos/ejecuciones están ejecutándose al mismo tiempo. Problemas que surgen Imaginemos que tenemos un simple programa que incrementa un contador, turno para ser atendidos, cada vez que un usuario pulsa un botón, fácil ¿no? Una clase con un campo contador y un método que devuelve el contador y le suma una unidad. Pues si este trabajo lo hacemos concurrente pongamos que con tres usuarios que llegan en el mismo instante de tiempo y lo solicitan a la vez pueden ocurrir muchas cosas si no controlamos este contador como debemos. Puede ser que el primer usuario tenga el número 15, luego el segundo el 16 y quede un 17 para el siguiente, suerte todo ha salido bien. O puede que al ser la petición al mismo tiempo ambos tengan el 15 y quede el 16 para el siguiente. O aún peor puede que el primero tenga el 15 el segundo el 16 pero quede un 16 para el siguiente que venga. En la concurrencia todo es posible. Los hilos deben ser independientes Cuando trabajamos con concurrencia y creamos nuestros hilos para dar solución al problema estos deben ser lo más independientes posibles, porque no te puedes basar en el estado de otro hilo en ningún momento ya que este va a su ritmo al igual que tú, puede que estés esperando algo que aún no ha ocurrido. Ejemplos Productor-Consumidor Uno de los ejemplos típicos en la concurrencia es el del la pila de productos en la que una persona se encarga de añadir objetos a ella y otro de consumir de la misma. El que produce tiene que esperar a tener espacio para añadir el nuevo objeto y el que consume tiene que esperar a que exista algún objeto en la pila para poder consumir. Para lograr estar coordinados el consumidor y el productor mandan señales de aviso el uno al otro, quien produce cada vez que añade un producto avisa que la pila no está vacía y el consumidor cada vez que saca un objeto avisa de que la pila no está llena. Así ambos saben que pueden actuar. Lectores-Escritores Otro ejemplo conocido es el de lectores y escritores. Si tenemos un documento el cual puede ser leído y escrito por múltiples personas no se puede solucionar de igual forma que el anterior, pues si el escritor no puede actuar sobre el documento hasta que el/los lector/es terminen con él puedes tener múltiples lectores, que se acumulen y que nunca llegue a estar libre el documento. Hay que buscar una forma de hacerlo de manera más equilibrada. Conclusión La concurrencia de código no es trivial, cuesta de comprender y mucho más de manejar con correctitud. Si estás pensando en empezar con ella, tómatelo con calma y no esperes que todo cobre sentido en un día. Referencias Libro original
Sistemas En este capítulo el autor empieza con un símil que me gusta mucho y voy a tratar de traducir lo más fielmente que sé. ¿Cómo construirías una ciudad? ¿Podrías administrar todos los detalles por ti mismo? Probablemente no. Incluso administrar una ciudad es mucho para una sola persona. Sin embargo, las ciudades funcionan (la mayor parte del tiempo) Funcionan porque tienen equipos de personas que administran partes de la ciudad, el sistema de agua, etc […] Las ciudades funcionan porque tienen los niveles apropiados de abstracción y modularidad haciendo posible para las personas y “componentes” se las arreglen para trabajar efectivamente, sin entenderla a gran escala. Aunque los equipos de desarrollo a menudo se organizan así también, los sistemas en los que trabajan a menudo no tienen la misma separación de preocupaciones y niveles de abstracción. […] En este capítulo veremos como mantenerse limpio a niveles altos de abstracción, a nivel de sistema. Y me gusta porque realmente veo así una aplicación o producto software como un ente enorme que tienes que separar en pequeñas “responsabilidades” para poder gestionarlo. Separar la construcción de un sistema de usarlo Una de las formas de separar la construcción del uso es mover todos los aspectos de construcción (creación de objetos) a nuestro main. De esta maneja queda un flujo limpio y fácil de comprender, creamos todos los objetos que nos hacen falta en el main y luego los enviamos a la parte de nuestra aplicación donde nos sea necesario. Es decir a nuestro main no le entra información de la aplicación solo envía hacia afuera. Fábricas Esto no siempre es posible, por ejemplo si tenemos una aplicación de pedidos, esta tendrá que modificar la lista de pedidos añadiendo un nuevo objeto alguna que otra vez. Para ello podemos usar el patrón Abstract Factory Como podemos ver nuestro cliente (main) tiene una interfaz genérica para nuestros productos (productoA) y una fábrica (AbstractFactory), de esta manera el conoce que puede crear productos mediante el uso de la fábrica pero desconoce como esta los crea ya que es una interfaz y carece de detalles de implementación. A él lo único que le interesa e importa es que puede crearlos y usarlos. Inyección de dependencia La inyección de dependencia consiste, como su nombre indica, en añadir, insertar dependencia a un objeto. Esto no es recomendable porque creas una dependencia grande entre objetos pero en ocasiones es la mejor solución que tenemos para asegurarnos de que el objeto B que usamos dentro del objeto A es el que queremos y como lo queremos. Una interfaz puede ser algo como IDisposable, IEnumerable o IPrintable. Una clase es una implementación real de una o más de estas interfaces: List o Map pueden ser implementaciones de IEnumerable. Para entendernos: a menudo tus clases dependen unas de otras. P.ej. podría tener una clase database que acceda a su base de datos, pero también desea que esta clase inicie sesión para acceder a la base de datos. Supongamos que tiene un registrador (logger) entonces la base de datos tiene una dependencia al registrador. Hasta aquí todo bien. Puede modelar esta dependencia dentro de su clase de Base de Datos con la siguiente línea: var logger = new Logger (); y todo está bien Está bien hasta el día en que se da cuenta de que necesita un grupo de registradores: a veces desea iniciar sesión en la consola, a veces en el sistema de archivos, a veces usando TCP / IP y un servidor de registro remoto, y así sucesivamente … Y, por supuesto, NO quieres cambiar todo tu código (mientras tanto, tienes miles de millones) y reemplazar todas las líneas var logger = new Logger (); por: var logger = new TcpLogger (); Primero, esto no es divertido. En segundo lugar, esto es propenso a errores. Tercero, este es un trabajo estúpido y repetitivo para un mono entrenado. Entonces, ¿Qué haces? Obviamente, es una buena idea introducir una interfaz ICanLog (o similar) que implementan todos los registradores. Así que el paso 1 en tu código es lo que haces: ICanLog logger = new Logger (); Ahora que la inferencia de tipo ya no cambia de tipo, siempre tienes una única interfaz contra la cual desarrollar. El siguiente paso es que no desee tener un nuevo Logger() una y otra vez. Así que pones la fiabilidad para crear instancias nuevas en una única clase central de fábrica y obtienes un código como: ICanLog logger = LoggerFactory.Create (); La fábrica misma decide qué tipo de registrador crear. A su código ya no le importa, y si desea cambiar el tipo de registrador que está utilizando, cámbielo una vez: Dentro de la fábrica. De esta manera con la interfaz te aseguras que todas las implementaciones de registrador, sea cual sea actúen de la misma forma ya que implementan la misma interfaz. Esta última último tramo de la publicación es una traducción de una respuesta en StackOverflow la respuesta completa aquí. Referencias Libro original Abstract Factory Esquema abstract factory (imagen) Inyección de dependencia StackOverflow Respuesta StakOverflow
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 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 No escribirás código de producción hasta que exista un test que falle. No escribirás más de un test unitario que falle. 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 Template method Given When Then
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 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<Empleado> empleados = obtenerEmpleados(); if(empleados != null){ for(Empleado e: empleados){ pagoTotal += e.obtenerPago(); } } List<Empleado> 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 original TODO Escribe tu bloque Try-Catch-Finally primero
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 original
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. 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 original
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. Libro original
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 original
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
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 original
Prefacio Se resume en una frase: “Las pequeñas cosas si importan” La filosofía de las 5 S: Sort, ordenar, saber dónde están las cosas. Darles nombres significativos para que se más fácil. Systematize, sistematizar. Como dice el dicho: Un lugar para cada cosa y cada cosa en su lugar. Cada trozo de código debe estar donde se espera que esté y, si no lo está, refactor ya!! Shine, limpieza. Mantener el espacio de trabajo limpio no dejes trozos de código comentados porque “Me harán falta en el futuro” eso no ocurre. Standarization, estandarización. El grupo de trabajo tiene que estar de acuerdo sobre como dejar el espacio de trabajo limpio y donde va cada cosa, así todos lo harán de la misma forma. Self-discipline, auto-disciplina. Para mí la más importante, tienes que tener la capacidad de seguir las practicas y ser capaz de ver su trabajo sin creer que es la mejor creación del universo, reflexionar y estar dispuesto a cambiar. Introdución Para lograr escribir código limpio no basta con leer sobre los principios y patrones. Aprender es más que conocer el conocimiento requiere conocerlo y aplicarlo, trabajar con ello, practicar y aprender aún más. Este es el objetivo del libro hacerte trabajar y ¡trabajar duro! Este libro se ha dividido en tres partes fundamentales, la primera más teórica, preparatoria para los dos siguientes donde introducir los principios y patrones que se usarán en los dos siguientes. Por ello tiene menos partes de código ejemplificativas. La segunda parte consta de ejercicios que van evolucionando en cuanto a dificultad. Ejercicios han de tomarse con calma y comprender. El tercero y último es la recompensa, una lista resumiendo lo que hemos aprendido en los ejemplos (ejercicios) anteriores. AVISO: Si lees solamente el primer y tercer capítulo de este libro añadirás uno más a la lista de “sentirse bien” pero si decides detenerte debidamente en el segundo asentarás estos conocimientos mucho mejor.