Mostrando entradas con la etiqueta Libros. Mostrar todas las entradas
Mostrando entradas con la etiqueta Libros. Mostrar todas las entradas

martes, 27 de agosto de 2013

Domain-Driven Design

DISCLAIMER: ESTE ARTÍCULO TODAVÍA ESTÁ EN CONSTRUCCIÓN

Como otros resúmenes de libros que he hecho, éste es un intento de clarificar las ideas sobre el libro que estoy actualmente leyendo, Domain-Driven Design: Tackling Complexity in the Heart of Software. No aporto nada nuevo, se trata simplemente de tratar de explicar con mis propias palabras lo leído de forma resumida.

Introducción

El Diseño Guiado por el Dominio (DDD) es un enfoque de diseño de software que enlaza el modelado del dominio y el diseño del software, con el objetivo de crear un modelo del dominio rico que evolucione a través de sucesivas iteraciones del diseño. En muchos proyectos la mayor complejidad es debida al dominio del problema, y es ésta complejidad la que trata de atajar DDD, partiendo de las premisas de que el foco principal del proyecto está sobre el dominio y que el diseño de un dominio complejo se basa en un modelo. DDD está orientado a las metodologías ágiles y supone que el desarrollo es iterativo y que existe una estrecha relación entre los desarrolladores y los expertos del dominio.

Parte I : Poniendo el Modelo del Dominio a Trabajar

Se presentan los objetivos del DDD.

  • 1 - Triturando el Conocimiento: La experimentación usando un lenguaje basado en el modelo y el ciclo de realimentación que se obtiene a través de la implementación hace posible encontrar un modelo rico y destilarlo. Esto convierte el conocimiento del equipo en un modelo valioso.
  • 2 - Comunicación y Uso del Lenguaje: Se debe usar el modelo como base del lenguaje que desarrolladores y expertos de negocio usan día a día, tanto para comunicarse entre ellos como en el código. Este LENGUAJE UBICUO evita tener que traducir entre el lenguaje de unos y otros, aunque cada grupo puede extenderlo con términos propios de su ámbito que no afectan al otro grupo. De esta manera los modelos serán explicativos y se podrán usar sin problemas en las discusiones entre ambos grupos.
  • 3 - Enlazando el Modelo y la Implementación: Si el diseño no está alineado con el modelo es difícil determinar si el software es correcto y es imposible llevar a cabo cambios en el diseño. Se debe buscar un modelo que se pueda implementar, para lo que se necesitan herramientas y lenguajes que soporten un paradigma de modelado, como la programación orientada a objetos.

Parte II: Las Piezas de un Diseño Guiado por el Modelo

Se condensa un núcleo de buenas prácticas del modelado orientado a objetos de dominios, que establecerán el conjunto de piezas del DDD, permitiendo dar orden al modelo y entender el trabajo entre desarrolladores mediante el uso de un lenguaje común. Se hace hincapié en la necesidad de mantener el modelo y la implementación alineados.

  • 4 - Aislando el Dominio: El software que resuelve los problemas del dominio constituye sólo una pequeña parte del sistema, aunque su importancia es inmensa. Es necesario desacoplar el modelo de las demás funciones para evitar la confusión con otros conceptos más tecnológicos. Una buena idea para ello es usar una ARQUITECTURA EN CAPAS.
  • 5 - Un Modelo Expresado Mediante Software: En primer lugar, se revisan las asociaciones en el modelo. Se debe definir la dirección en la que deben ser recorridas, tratar de incluir un calificador para reducir la multiplicidad y eliminar las asociaciones que no sean necesarias. Después se revisan los tres patrones de elementos del modelo: ENTIDADES (objetos que poseen una identidad propia que se debe preservar), OBJETOS DE VALOR (objetos que no tienen identidad y que describen alguna característica de algo) y SERVICIOS (operaciones que conceptualmente no pertenecen a ningún objeto). Estos elementos del modelo se pueden agrupar en MÓDULOS, que deben contener conceptos con gran cohesión entre ellos y permiten deben tener un bajo acoplamiento con otros MÓDULOS para facilitar la comprensión del sistema.
  • 6 - El Ciclo de Vida de un Objeto de Dominio: Algunos objetos son creados, usados en alguna computación y destruidos. Otros viven por más tiempo y no siempre en memoria, también pueden ser almacenados en una base de datos, por ejemplo. Los objetos tienen dependencias con otros y cambian de estado debiendo mantener ciertas condiciones. Existen ciertos patrones que ayudan a gestionar el ciclo de vida de un objeto. Un AGREGADO en un grupo de ENTIDADES y OBJETOS DE VALOR sobre el que se deben mantener ciertas condiciones, con una ENTIDAD como raíz que será la única a la que puedan acceder otros objetos externos. Las FACTORÍAS encapsulan la creación de un objeto o un AGREGADO. Los REPOSITORIOS permiten recuperar la ENTIDAD raíz de un AGREGADO proveyendo un punto de entrada para acceder a un objeto durante su ciclo de vida.
  • 7 - Usando el Lenguaje: Un Ejemplo Extendido: Ejemplo del diseño de un sistema de transporte de carga poniendo en práctica los patrones expuestos en los capítulos anteriores.

Parte III: Refactorizando hacia un Conocimiento más Profundo

Se va más allá de las piezas del DDD para afrontar el reto de montar con ellas modelos que supongan una ventaja. Se revisa el proceso de descubrimiento del modelo y su transformación paulatina a medida que se gana nuevo conocimiento sobre el mismo.

  • 8 - Un Avance Significativo: A medida que se asimila el conocimiento emergen gradualmente modelos más profundos a través de pequeñas refactorizaciones. A veces, el modelo se va clarificando hasta dar lugar a modelos mucho más profundos que dan una visión más clara del sistema. Esta oportunidad hay que aprovecharla cuando sea posible y conviene estar preparado para ello, haciendo explícitos los conceptos del sistema, procurando tener un diseño adaptable en todo momento y destilando el modelo.
  • 9 - Haciendo Explícitos los Conceptos Implícitos: Muchas transformaciones de los modelos ocurren cuando se reconoce un concepto que se ha dado a entender en una conversación o que está presente de forma implícita en el diseño, y se representa de forma explícita en el modelo. Para buscar estos conceptos hay que escuchar el lenguaje que se usa, buscar en los puntos de mayor complicación en el modelo y las contradicciones del modelo, leer sobre el negocio del dominio y probar diferentes posibilidades. Algunos conceptos que son menos obvios son las restricciones, los procesos que pueden ser expresados como SERVICIOS, y las reglas de negocio que se deben cumplir que se pueden encapsular mediante ESPECIFICACIONES.
  • 10 - Diseño Adaptable: El software debe servir tanto a los usuarios como a los propios desarrolladores, que deben ser capaces de hacer evolucionar el código. Para ello es necesario un diseño adaptable que facilite el cambio. Para ello el modelo se debe expresar a través de INTERFACES QUE REVELEN SU PROPÓSITO usando términos del LENGUAJE UBICUO. Estos interfaces deben usar FUNCIONES SIN EFECTOS SECUNDARIOS o, en su defecto utilizar ASERCIONES que expongan cuáles son sus efectos secundarios. También se debe simplificar su interpretación a través de la CLAUSURA DE OPERACIONES, haciendo que las operaciones devuelvan objetos del mismo tipo que sus parámetros. Conviene mantener un mismo NIVEL CONCEPTUAL en cuanto a los elementos del sistema y tratar de utilizar CLASES AUTÓNOMAS dentro de lo posible.
  • 11 - Aplicando Patrones de Análisis: Un patrón de análisis puede servir como guía para la investigación del modelo, aunque no suelen ser aplicables tal cual, sino que deben ser adaptados. Sin embargo, permiten ahorrar tiempo en la investigación del modelo.
  • 12 - Relacionando Patrones de Diseño con el Modelo: Algunos patrones de diseño pueden usar como patrones de dominio, ya que sus elementos se corresponden con conceptos generales que emergen en muchos dominios. Es el caso, por ejemplo, de los patrones ESTRATEGIA o POLÍTICA y COMPUESTO.

Parte IV: Diseño Estratégico

Se tratan los problemas que pueden aparecer en sistemas complejos, organizaciones grandes e interacciones con otros sistemas. El diseño estratégico permite llevar a cabo a gran escala los objetivos establecidos en la primera parte del libro.

  • 14: Manteniendo la Integridad del Modelo
  • 15: Destilación
  • 16: Estructuras de Gran Escala
  • 17: Reunificando la Estrategia

Conclusion

Referencias:

jueves, 21 de marzo de 2013

Working Effectively With Legacy Code

Después de estudiar la forma de crear software de la mejor manera posible, usando Patrones de Diseño, evolucionando el diseño mediante TDD, cubriendo el código de pruebas y refactorizándolo cuando se presenta una oportunidad de mejora, me sigo topando con la cruda realidad. Ésta es que la mayor parte del tiempo lo paso trabajando con código ya hecho, por mí o por otras personas, y que no está desarrollado siguiendo las buenas prácticas que ahora intento seguir. Este código es difícil de entender y de mantener y, aunque quiero llevarlo a un estado parecido al código nuevo que desarrollo, esto me parece en ocasiones prácticamente imposible.

Una solución al problema de cómo mejorar este código legado es seguir los consejos que Michael C. Feathers presenta en su libro “Working Effectively With Legacy Code”. En él se presenta su visión sobre qué es el software legado, con que mentalidad enfrentarnos a él y qué técnicas podemos usar para combatir las malas prácticas que hay en él. Este artículo es un resumen de lo que este libro cuenta.

  1. Cambiando el software: El software se puede cambiar por varias razones: añadir funcionalidad, corregir un error, mejorar el diseño u optimizar el uso de los recursos del sistema. En todos los casos se quiere cambiar algún funcionamiento, pero se desea mantener la mayor parte del ya existente.
  2. Trabajando con realimentación: La forma de cambiar el sistema sin introducir errores es trabajar con la realimentación que proporcionan los tests unitarios que cubren el código. Estos tests permiten detectar cualquier comportamiento erróneo que se introduzca en las modificaciones que realicemos. El problema del código legado es que dichos tests no existen, y hay que hacer cambios para introducirlos rompiendo dependencias entre componentes, siendo extremadamente cuidadoso.
  3. Detección y separación: Existen dos motivos para romper las dependencias, detectar cómo afecta un componente a otro, o separar los componentes entre sí y poder ejecutarlos de forma independiente. De todas formas, los componentes deben colaborar entre sí y, para aliviar las dependencias en estas colaboraciones se pueden usar dobles de prueba que sustituyan al colaborador real.
  4. El modelo de costuras: El software no se puede tratar como un listado porque esto dificulta su comprensión y la reutilización de sus componentes. Para poder probarlo es necesario buscar formas de cambiar su comportamiento sin realizar ningún cambio sobre lo que se va a probar, puntos que el autor llama costuras. Estas costuras pueden ser habilitadas de distintas formas y, en función de ello, existen los siguientes tipos: de preprocesado, de enlazado y de objeto.
  5. Herramientas: A la hora de realizar cambios sobre el código legado hay una serie de herramientas que nos van a ayudar. Estas son las herramientas de refactorización automática, los frameworks de objetos “mock” (imitación), y los frameworks de prueba (xUnit, CppUnit, CppUnitLite, Fit, Fitnesse)
  6. No tengo mucho tiempo y tengo que cambiarlo: A veces no se dispone de tiempo para poner bajo tests la clase que tenemos que cambiar. En esos casos se pueden usar una serie de técnicas que, por lo menos, permiten tener bajo tests el código que introduzcamos al hacer los cambios. Estas técnicas consisten en crear una nueva clase o método, o crear una clase o método que envuelva el código existente.
  7. Hacer un cambio se eterniza: Cuando se están haciendo cambios es importante tener realimentación rápida sobre lo que está ocurriendo. Para ello es importante que los proyectos se puedan recompilar rápidamente de forma que no se invierta demasiado tiempo esperando. Para conseguir esta rapidez se deben usar interfaces y paquetes para separar las dependencias entre clases.
  8. Cómo añadir funcionalidad: Una vez que tenemos código bajo test podemos usar las técnicas descritas en este capítulo para añadir nueva funcionalidad de forma controlada. Estas técnicas son TDD y la programación por diferencia. La programación por diferencia, basada en la herencia de implementación, es bastante discutida, pero se puede utilizar de forma inicial a la hora de introducir nuevas funcionalidades y luego, gracias a los tests, ir mejorando el diseño de forma que se elimine su uso.
  9. No puedo incluir esta clase en un test: A veces no es fácil instanciar una clase en un test por distintos motivos. Estos pueden ser la dificultad de crear ciertos objetos debidos a los parámetros de su constructor o las dependencias de éste, que el test no se pueda ejecutar con una determinada clase que depende de una base de datos o parecido, que el constructor tenga efectos colaterales dañinos o que no seamos capaces de comprobar el trabajo que se hace en el constructor. Se presentan una serie de ejemplos donde se solucionan estos problemas a partir de unas útiles técnicas.
  10. No puedo ejecutar este método en un test: Una vez se puede instanciar una clase en un test sólo hemos empezado con lo que tenemos que hacer. Ahora hay que ejecutar alguno de sus métodos y a veces hay que superar problemas como que el método no sea accesible, que se necesiten parámetros difíciles de construir en su llamada, que tenga efectos colaterales perjudiciales o que no se pueda apreciar lo que hace en alguno de los objetos que utiliza. Se presentan una serie de ejemplos donde se solucionan estos problemas a partir de unas útiles técnicas.
  11. Necesito hacer un cambio. ¿Qué métodos debo probar?: Cada cambio en el software tiene una serie de efectos que es necesario considerar. Una técnica útil es hacer un boceto para ver de forma gráfica lo que se ve afectado por cada cambio. Un buen código creará estructuras simples. Es importante saber que los efectos se pueden propagar de tres formas: los valores de retorno de un método, los objetos pasados como parámetros a un método y las variables estáticas o globales. Se pueden simplificar estos bocetos mediante la eliminación de duplicación, y esto lleva a decisiones más sencillas sobre los tests a realizar.
  12. Necesito hacer muchos cambios en un área. ¿Tengo que romper las dependencias para todas las clases implicadas?: A veces es conveniente buscar un lugar donde probar vario cambios a la vez en lugar de poner bajo tests todas las clases implicadas en un cambio. Para ello, a través de los bocetos de efectos se pueden buscar puntos de intercepción, lugares donde se puede probar un determinado cambio. Algunos puntos, donde el boceto de efectos se estrecha, son especialmente indicados para probar varios cambios a la vez.
  13. Necesito hacer un cambio pero no se qué tests escribir: En código legado necesitamos tener tests que permitan preservar el comportamiento del sistema tras las modificaciones. Esos tests se llaman tests de caracterización, y son tests sencillos que simplemente reflejan lo que el sistema hace en este momento. A partir de estos tests se puede refactorizar el código para mejorarlo.
  14. Las dependencias en librerías me están matando: Es importante no confiar demasiado en una librería concreta y llenar el código de llamadas a métodos de esa librería. El uso de forma directa de una librería no favorece la creación de tests.
  15. Mi aplicación está llena de llamadas al API: La mayoría de los sistemas contienen una lógica principal que va más allá de llamadas a la API de una o más librerías. Es importante que el diseño preserve esta lógica para poder hacer buenos tests. A esta separación se puede llegar mediante dos aproximaciones: envolver el API o hacer una extracción basada en responsabilidades.
  16. No entiendo el código suficientemente bien para cambiarlo: Para entender el código existente de cara a hacer un cambio, se pueden usar algunas técnicas de bajo nivel, como tomar notas, hacer bocetos, marcar listados de código para resaltar los aspectos importantes del mismo, hacer una refactorización informal y luego deshacerlo o borrar el código que no se usa.
  17. Mi aplicación no tiene estructura:Las aplicaciones tienden al desorden cuando todo el equipo no es consciente de dicha arquitectura. Para asegurarse que el equipo entiende la arquitectura se puede recurrir a contar la historia del sistema para obtener una visión simple del sistema en su conjunto, usar CRC o “naked CRC” para comunicar el diseño de la aplicación o escrutar las conversaciones sobre el sistema para comprobar que cuando se hace un abstracción al hablar esta tenga una correspondencia en el sistema. Es importante resaltar que el diseño es una tarea que no termina mientras siga habiendo cambios.
  18. Mi código de test está en medio: Tener el código de test mezclado con el código puede no ser bueno por razones de claridad o de recursos. Para separarlo se pueden usar convenciones de nombrado o situar uno y otro en archivos separados.
  19. Mi proyecto no es orientado a objetos. ¿Cómo hago cambios seguros?: Principalmente se habla de lenguajes procedurales. Para estos lenguajes se pueden usar las costuras de procesado o de enlazado, pero si el lenguaje tiene una extensión que sea orientada a objetos, se puede hacer modificaciones que permitan usar las costuras de objeto, que son mucho más potentes.
  20. Esta clase es muy grande y no quiero que crezca más: Las clases grandes generan confusión, dificultad a la hora de planificar las tareas de los desarrolladores y son difíciles de probar. Estas clases tienden a atraer dentro de ellas las nuevas modificaciones y no paran de empeorar. La solución es refactorizar estas clases para hacer que cumplan con el Principio de Única Responsabilidad (SRP). Esta refactorización se debe hacer separando las responsabilidades de cada clase. Para ver estas responsabilidades se pueden agrupar sus métodos, buscar métodos ocultos, buscar las decisiones sobre la misma que puedan cambiar, buscar relaciones internas usando bocetos de propiedades para observar las agrupaciones internas, buscar la responsabilidad principal, hacer refactorizaciones de prueba o centrarse en la tarea que tenemos entre manos. La estrategia a seguir cuando se han identificado las distintas responsabilidades es separarlas a medida que sea necesario en vez de hacer todo a la vez. La mejor táctica para hacerlo sería aplicar el principio de responsabilidad única a nivel de implementación en vez de a nivel de interfaz.
  21. Estoy cambiando el mismo código por todos los sitios: Muchas veces hacemos un cambio y creemos que hemos terminado, pero nos damos cuenta de que debemos hacer este mismo cambio en otras partes del sistema donde el código está duplicado. A través de la refactorización podemos ahorrarnos esta molestia eliminando las duplicidades, consiguiendo además que emerja un mejor diseño y favoreciendo la ortogonalidad del sistema, consiguiendo partes del sistema independientes entre sí.
  22. Necesito cambiar un método monstruoso y no puedo escribir tests para él: Para abordar los cambios en estos métodos monstruosos podemos usar las herramientas de refactorización automáticas, evitando en este paso todo cambio adicional que no soporten. Cuando se refactoriza de forma manual debemos ser mucho más cuidadosos y, para ello, introducir variables que nos permitan sentir los cambios en el sistema, extraer sólo aquello que conocemos, separar el código principal de lo secundario o extraer un objeto método. Se pueden introducir cambios estructurales mientras se refactoriza haciendo que los métodos sean un esqueleto de la lógica que contienen, buscando secuencias de código, extrayendo a la clase actual en un primer paso en vez de llevar el código a otra clase y extrayendo en pequeñas piezas de código.
  23. ¿Cómo sé que no estoy rompiendo nada?: Se puede reducir el riego al editar los cambios si se es plenamente consciente de lo que se está editando, editando con un único objetivo, manteniendo las firmas de los métodos y apoyándose en el compilador para identificar los cambios que se deben hacer.
  24. Nos sentimos abrumados. Esto no va a mejorar: La programación puede ser divertida, aunque trabajar con código legado sea difícil. Rehacer el proyecto desde cero tampoco es una solución porque hay que mantener el mismo avance que el proyecto y esto es muy difícil. Hay que buscar la motivación para trabajar sobre nuestro sistema, solucionando los problemas más inmediatos y haciendo evidente la mejora que podo a poco se produce en el sistema.
  25. Técnicas para romper las dependencias: En este capítulo se definen técnicas para romper las dependencias, pero que no son exactamente refactorizaciones, puesto que están pensadas para hacerse sin tests, con el fin de poder introducir estos tests que nos faltan.

miércoles, 31 de agosto de 2011

Apprenticeship Patterns

La Artesanía del Software es un enfoque del desarrollo de software distinto de la Ingeniería de Software tradicional. Está más orientada hacia las relaciones entre desarrolladores y clientes en busca de valor para los proyectos, y también hacia las relaciones entre los propios desarrolladores, favoreciendo el traspaso del conocimiento.

Este libro trata sobre el enfoque de la Artesanía del Software sobre el aprendizaje que debe realizar un desarrollador. Este aprendizaje no se limita sólo a la etapa inicial de formaciónsino que debe ser continuo a lo largo de toda la carrera profesional.

Los autores (Dave H. Hoover y Adewale Osinheye) han recopilado una serie de patrones que pueden guiar este aprendizaje y que, a continuación, trataré de presentar. Dichos patrones se presentan por capítulos según su intencionalidad:

Vaciando La Taza

Cómo iniciarse en el aprendizaje desde una postura abierta y receptiva, eliminando los prejuicios.
  • Tu Primer Lenguaje: Conocer un primer lenguaje es lo que servirá como base para todo el aprendizaje posterior.
  • El Cinturón Blanco: Hay que ser humildes y receptivos, conscientes de que para aprender algo nuevo hay que desprenderse en parte de lo que sabíamos.
  • Desata Tu Entusiasmo: El entusiasmo da energía a un aprendiz y a su equipo, por lo que es un valor que hay que fomentar.
  • Habilidades concretas: Una persona que está aprendiendo puede tener poco que ofrecer a un equipo de programadores expertos. Sin embargo, es necesario que pueda ofrecer algo para que se le acepte como un miembro más.
  • Expón Tu Ignorancia: Se debe hablar abiertamente de las carencias, y no engañar a nadie atribuyéndonos cualidades que no tenemos.
  • Confronta Tu Ignorancia: Las carencias identificadas se deben combatir. Esto aumenta el conocimiento y permite identificar nuevas carencias.
  • El Profundo Abismo: En determinados momentos es necesario llevar a cabo tareas que están aparentemente más allá de nuestra capacidad para poder crecer.
  • Refúgiate En Tu Competencia: Para ganar confianza en caso de vernos superados se dedicar un rato a tareas que se dominan.

Caminando Por El Largo Camino

El aprendizaje del desarrollo de software es un camino largo abarca toda la carrera profesional.
  • El Largo Camino: A lo largo de la carrera profesional la prioridad debe ser aumentar las habilidades, no en el beneficio económico ni el ascenso en la escala empresarial en sí mismo.
  • Oficio Más Que Arte: Hay que ser consciente de que se están construyendo herramientas que deben aportar valor a los clientes.
  • Motivaciones Sostenibles: Para poder llegar al final del camino, se debe trabajar sin sacrificar de forma permanente ningún otro aspecto importante de la vida personal.
  • Alimenta Tu Pasión: Durante algunos periodos puede que el trabajo del día a día no sea suficientemente gratificante, y habrá que buscar algo que permita mantener la pasión por el aprendizaje, ya sea en el trabajo o fuera de él.
  • Dibuja Tu Propio Mapa: Somos los únicos responsables de nuestra trayectoria profesional. Si nuestra empresa o equipo actuales no son favorables al aprendizaje, tenemos en nuestra mano cambiar estas condiciones.
  • Usa Tu Título: El título profesional es reflejo de la organización en la que nos encontramos. Un título que no refleja nuestra realidad es un síntoma de que debemos actuar para cambiar nuestra situación.
  • Permanece En Las Trincheras: Para aprender a desarrollar del software hay que mantenerse desarrollando, y no dejarse seducir por ascensos profesionales que nos separen de él, llevándonos, por ejemplo hacia la gestión.
  • Un Camino Diferente: Se puede aprender a desarrollar siguiendo muchos caminos, por lo que no se debe menospreciar a quién ha elegido uno distinto.

Auto-Evaluación Precisa

Hay que saber reconocer nuestra situación respecto del entorno en que nos encontramos, para poder actuar si se da el peligro de estancamiento en el aprendizaje.
  • Sé El Peor: Ser el peor en un nuevo equipo permite aprender cosas a costa del esfuerzo que supone ponerse al día.
  • Encuentra Mentores: Conseguir que alguien con más conocimientos nos supervise es la mejor manera de acelerar el aprendizaje.
  • Espíritus Afines: Gente con los mismos intereses nos ayudará a aprender, ofreciendo visiones alternativas, apoyo o nuevas oportunidades.
  • Juntar Los Codos: Aprender junto a alguien incentiva la comprensión y facilita la comunicación de aquello que se da por supuesto pero que supone la diferencia.
  • Barrer El Suelo: Si se es el miembro de menor experiencia en un equipo se deben aceptar las tareas menos llamativas para poder aspirar a tareas más complejas.

Aprendizaje Perpetuo

No se puede dejar de aprender porque la tecnología avanza y ofrece nuevas soluciones que debemos conocer en la medida de lo posible.
  • Expande Tu Ancho De Banda: Hay que abrirse a todas las fuentes de conocimiento disponibles (blogs, Twitter, listas de correo, grupos de usuarios, conferencias, libros, cursos…) para recibir información sobre aspectos distintos a nuestra experiencia.
  • Practica, Practica, Practica: Para poder aprender es útil practicar con ello en un entorno tolerante con los errores. Se puede practicar con ejercicios (katas) sólo o junto con otros desarrolladores (dojo). Una realimentación inmediata sobre los errores que podamos cometer es importante para poder corregirlos y aprender.
  • Juguetes Que se Pueden Romper: Se puede poner a prueba y aplicar lo aprendido construyendo sistemas con exigencias reales pero no críticos, tal vez para uso propio.
  • Usa La Fuente: Para reconocer buen código hay que haberlo visto antes. Leer código de otros permite entender la forma de desarrollar de otras personas y adquirir los mejores hábitos de ellas.
  • Reflexiona Mientras Trabajas: Periódicamente se debe reflexionar sobre las prácticas usadas y analizar si se pueden mejorarlas o cambiars por otras mejores. Como equipo, esto puede traducirse en reuniones retrospectivas sobre la marcha del proyecto.
  • Guarda Lo Que Aprendes: Guardando constancia de cómo se ha resuelto un problema se ahorra tiempo en el futuro cuando se vuelva a presentar.
  • Comparte Lo Que Aprendes: Esto permite la comunicación con otros desarrolladores con los mismos problemas.
  • Crea Ciclos de Realimentación: Sólo alguien distinto de nosotros puede evaluar de forma objetiva lo que estamos haciendo bien o mal.
  • Aprende Cómo Fallas: Identificar nuestros fallos permite afrontarlos o, si el esfuerzo fuese desmedido, aceptarlos y reconocer las tareas para las que no somos aptos.

Construye Tu Currículo

La necesidad de seguir aprendiendo será continua. Estos nuevos conocimiento s irán definiendo nuestro propio currículo.
  • Lista de Lecturas: Existe una enorme cantidad de libros que pueden resultar interesantes. Mantener una lista de estos libros ayuda a priorizar y registrar lo que leemos y hacerla pública la deja abierta a sugerencias por parte de otras personas.
  • Lee Constantemente: Leer de forma periódica permite adquirir un conocimiento aceptable de la tecnología y diferenciarse de la mayoría de quienes nos rodean.
  • Estudia Los Clásicos: Las referencias a clásicos del desarrollo del software son constantes y es importante leer estos libros para entender las referencias de otras personas.
  • Excava Más Profundo: El conocimiento superficial de varias tareas no es suficiente. Habrá casos en que haya que profundizar en una herramienta o lenguaje concreto en busca de la solución a un problema.
  • Herramientas Familiares: Para que los proyectos puedan desarrollarse según lo estimado no se puede innovar siempre. Hay que mantener un conjunto de herramientas constantes en las que basarse.

Personalmente, este libro me parece muy inspirador y me ha dado un montón de ideas sobre cómo afrontar mi carrera como desarrollador de software, al verme reflejado en muchas de las situaciones que en él se describen.

Los patrones que presenta, en ciertas ocasiones parecen sencillas e incluso evidentes. La complejidad está en saber combinarlos y aplicarlos en nuestro día a día.

lunes, 13 de junio de 2011

Apuntes de XP

Recientemente he estado leyendo el libro de Kent Beck "Extreme Programming Explained" y, lo que presento es este post, es el resumen que he hecho sobre el mismo.

XP es una metodología ligera para el desarrollo proyectos de software con requisitos variables o poco definidos. Pone énfasis en el código e introduce prácticas que permiten entregar el software a tiempo, reducir el riesgo, mejorar la respuesta al cambio, aumentar la productividad y hacer más divertido el trabajo en grupo.

El problema del desarrollo de software

El principal problema del desarrollo de software es el riesgo. XP propone soluciones para luchar contra los distintos tipos de riesgo que pueden existir en un proyecto:
  • Las iteraciones cortas permiten planificar mejor y reaccionar a los cambios.
  • Desarrollar primero las tareas de mayor valor acelera el retorno de la inversión.
  • Asegurar la calidad introduciendo pruebas automáticas permite hacer modificaciones de forma segura.
  • Probar constantemente el sistema permite encontrar defectos más rápido.
  • Involucrar al cliente mejora la especificación del sistema y las expectativas sobre él.
  • Otorgar mayor responsabilidad y respeto al programador reduce su frustración. Promueve el espíritu de equipo y crea un mejor ambiente de trabajo.

La Economía del Software

Se debe obtener el mayor valor haciendo la mínima inversión para obtener beneficios de la forma más rápida e incrementar la vida del proyecto.

Para ello, se estudian las distintas opciones de cada funcionalidad del proyecto (implementarla ya, descartarla o posponerla) y se calcula su valor según el beneficio original, el precio de la opción, el beneficio si se ejecuta la opción, el periodo de tiempo durante el que se puede ejecutar la opción y la certeza en el valor final.

Las cuatro variables que permiten gestionar un proyecto son: coste, tiempo, calidad y alcance. El alcance es la variable más potente. XP reduce el riesgo de retrasar el desarrollo de una funcionalidad y juega con el alcance centrándose en realizar aquello que está definido. Se puede modificar el alcance a medida que el proyecto avanza y el conocimiento sobre el producto deseado aumenta.

Se asume que el coste que implica un cambio en el proyecto aumenta exponencialmente según avanza el proyecto. Sin embargo, combinando tecnología y buenas prácticas, se puede conseguir un aumento prácticamente lineal del coste del cambio. Esta es la premisa principal de XP, según la cual las decisiones se deben retrasar hasta obtener la mayor certeza.

Para reducir el coste del cambio se debe usar un diseño simple, pruebas automáticas que puedan ejecutarse en cualquier momento y refactorización para modificar el diseño cuando sea necesario. Se pueden así realizar ajustes durante el desarrollo, de la misma manera que se realizan correcciones al volante cuando se conduce.

Valores, principios, actividades y prácticas de XP

Los 4 valores que dirigen el proceso de XP son:
  • Comunicación: la información debe fluir entre el equipo.
  • Sencillez: hay que hacer las cosas de la forma más sencilla posible.
  • Realimentación: hay que conocer el estado del proyecto en todo momento.
  • Coraje: ser capaz de tomar decisiones difíciles cuando sea necesario.

Los valores anteriores inspiran los principios que guían a XP:
  • Realimentación rápida: acelerando es el aprendizaje.
  • Sencillez: resolver los problemas de forma sencilla.
  • Cambio incremental: los cambios son más sencillos si se realizan poco a poco.
  • Abrazar el cambio: abierto a lo que depare el futuro.
  • Trabajo de calidad: si el trabajo no se hace bien, se pierde la motivación.
  • Enseñar a aprender: demostrar todo aquello sobre lo que no haya certeza.
  • Pequeña inversión inicial: manejando el alcance, no hacen falte grandes presupuestos.
  • Jugar para ganar: la confianza en la propia capacidad ayuda al éxito del proyecto.
  • Experimentos concretos: probar cada decisión para asegurar que es correcta.
  • Comunicación abierta y honesta: hablar del proyecto de forma directa y sincera.
  • Trabajar con los instintos de las personas, no contra ellos: a la gente le gusta hacer un buen trabajo. Hay que ayudar a que esto ocurra.
  • Aceptar la responsabilidad: A nadie le gusta que le digan lo que tiene que hacer. Es preferible que cada persona lo elija, pero como miembro del equipo, también debe asumir la responsabilidad de hacer lo que el equipo estime necesario.
  • Adaptarse de forma local: decidir qué cosas funcionan mejor en cierto entorno.
  • Viajar ligero: mantener pocas cosas, que sean simples y de valor.
  • Medida honesta: usar medidas significativas que reflejen el estado del proyecto.

Las actividades principales del proceso de desarrollo en XP son codificar, probar, escuchar y diseñar:
  • El código sirve para plasmar ideas y aprender sobre ellas a través de la comunicación.
  • El código puede ser probado para ver si cumple con lo esperado. Las pruebas automáticas permiten realizar estas pruebas una y otra vez asegurando el funcionamiento del sistema.
  • Para implementar las pruebas, el desarrollador escucha al usuario, que dice cómo debe funcionar el sistema, y al propio sistema, que da realimentación sobre el sistema y sus problemas.
  • A medida que el conocimiento evoluciona y se amplían las funcionalidades se revisa el diseño del sistema para poder satisfacer las nuevas necesidades sin afectar a las anteriores. El diseño consiste en organizar el sistema de forma que los cambios lo afecten lo menos posible.

Las actividades anteriores se realizan mediante las siguientes prácticas:
  • El Juego de la Planificación: en cada iteración los desarrolladores estiman las tareas y el cliente decide las prioridades, para seleccionar el conjunto de tareas a realizar.
  • Entregas cortas: entregar el mayor valor en producción lo más rápidamente posible.
  • Metáfora: se utiliza una historia sobre el funcionamiento del sistema, que comparte todo el equipo, para describir el funcionamiento del sistema y guiar el desarrollo.
  • Diseño simple: el sistema debe ser lo más simple posible en cada momento.
  • Pruebas: se crean pruebas unitarias y funcionales.
  • Refactorización: el sistema se reestructura constantemente, sin cambiar su comportamiento, para mejorar el diseño o la comunicación.
  • Programación en pareja: el código se escribe en pareja, aumentando la comunicación entre los miembros del equipo y otorgando realimentación inmediata.
  • Propiedad colectiva: cualquier miembro del equipo puede modificar el código.
  • Integración continua: el código se integra cada vez que se completa una tarea para mantenerlo estable.
  • Jornada de trabajo estable: XP supone un esfuerzo y se debe permitir el descanso necesario.
  • Contacto directo con el cliente: el cliente debe estar disponible en todo momento para responder preguntas sobre el sistema.
  • Estándares de codificación: se programa en base a reglas de codificación que mejoran la comunicación.

No son prácticas novedosas, sin embargo, al llevarlas a cabo de manera conjunta, unas se refuerzan a otras aumentando su potencia.

Gestión de proyectos en XP

La gestión del proyecto no puede ser cosa de una sola persona, nadie tiene conocimiento total del sistema. XP permite una estrategia de gestión descentralizada en la que el gestor sólo debe intervenir en situaciones que no se puedan resolver de forma distribuida.

A la hora de gestionar un proyecto debe existir tanto un perfil administrativo como uno técnico. El perfil administrativo se encarga de que se realice el Juego de la Planificación, de recoger métricas sobre el seguimiento del proyecto y mostrárselas a los interesados. El perfil técnico hace de consejero (coach). No toma decisiones técnicas, sino que ayuda a los de demás a tomarlas y colabora con ellos en el desarrollo. También es el encargado de velar por la buena marcha del proceso de desarrollo.

A la hora de realizar la planificación hay que separar las responsabilidades. Negocio decide el ámbito del proyecto y de las iteraciones, y las prioridades de las tareas. Desarrollo informa sobre las estimaciones de las funcionalidades y las alternativas técnicas. Desarrollo decide el proceso que va a seguir para el desarrollo y las prácticas que llevará a cabo.

La planificación se basa en las estimaciones que los desarrolladores hacen sobre las tareas que el cliente ha priorizado. Sólo se debe planificar en detalle la iteración actual, pero se puede planificar a largo plazo con menor detalle el resto de las tareas.

El objetivo de la planificación es el de maximizar el valor entregado al cliente en cada iteración. Para ello, el equipo de desarrollo deberá invertir lo mínimo posible poniendo el mayor valor en producción lo más rápidamente posible.

La planificación se realiza en las siguientes fases:
  • Exploración: trata de descubrir lo que debe hacer el sistema. Se escriben historias que describen lo que debe realizar el sistema, se estiman y, si fuesen demasiado grandes, se dividen en historias más pequeñas.
  • Compromiso: las historias se priorizan. A partir de la velocidad de desarrollo del equipo se elije el alcance del proyecto según el tiempo que se disponga.
  • Dirección: se realizan iteraciones de alcance restringido durante las que se pueden detectar nuevas historias que pueden ser incorporadas a la planificación. Se vuelve a estimar teniendo en cuenta la velocidad obtenida durante las iteraciones anteriores.

Desarrollo en XP

Durante el desarrollo, las prácticas principales son la integración continua, la propiedad colectiva del código y la programación en parejas.

La integración continua consiste en que los desarrolladores integren sus cambios cada pocas horas, manteniendo la corriente principal del desarrollo en sincronía. Gracias a las pruebas automáticas se verifica en todo momento que el sistema sigue funcionando, y gracias a la refactorización se transforma el sistema en elementos cada vez más pequeños e independientes, de forma que los cambios a realizar están mucho más localizados.

La propiedad colectiva del código hace que cualquiera pueda modificar cualquier parte del código cuando lo necesite. Para ello es preciso atenerse a unas reglas de codificación comunes que faciliten la comprensión del código a todo el equipo.

La programación en pareja obliga a programar, analizar, diseñar y probar de forma conjunta. Esto favorece la comunicación y el aprendizaje. Uno de los miembros se centra en programar, mientras que el otro piensa de una manera más estratégica. Hace que la gente se mantenga fiel a las prácticas de XP, puesto que hay cierta supervisión.

La disposición del entorno de trabajo debe favorecer el trabajo en equipo. Es importante tener espacios comunes donde el equipo pueda reunirse a debatir o donde programar en pareja.

Diseño en XP

En XP se procura tener en todo momento un diseño lo más simple posible. Se realiza una inversión inicial pequeña para proporcionar valor rápidamente, haciendo cosas simples que permitan evolucionar el diseño fácilmente.

El diseño comienza por el desarrollo de pruebas que verifiquen lo que tiene que hacer el sistema. Se irán desarrollando nuevas pruebas que verifiquen más requisitos. Si en el proceso se ve la posibilidad de simplificar lo ya hecho, hay que hacerlo. Así, la primera vez que nos encontremos con un problema, lo solucionaremos de la manera más rápida posible. Las siguientes veces, se trata de flexibilizar la solución para acomodarla a nuevas necesidades.

Lo más simple posible es aquello que comunique su función, no contenga código duplicado y tenga el menor número de clases y métodos.

En el diseño se pueden usar gráficos, pero hay que ser conscientes de que entrañan el riesgo de que la funcionalidad del sistema que definen pueden ser comprobada a través de ellos. Se pueden usar gráficos en los primeros pasos de un diseño y luego convertir el diseño a código para eliminar este riesgo lo antes posible.

La arquitectura del sistema se expresa a través de una metáfora que permite hablar sobre el sistema. Para obtener esta arquitectura se recoge un conjunto básico de historias y se cambiará a medida que el conocimiento sobre el sistema aumente.

Pruebas en XP

XP propone realizar las pruebas a la vez que el desarrollo. Antes de codificar una funcionalidad se debe crear una prueba que valide su funcionamiento. Como las pruebas son automáticas se pueden ejecutar en cualquier momento y verificar que el sistema sigue funcionando.

Las pruebas permiten aprender sobre el sistema. Son realizadas por los desarrolladores, pero también por los clientes, que escriben pruebas que verifican si el sistema cumple con el funcionamiento descrito en sus historias de usuario.

Comenzar con XP

A introducir XP en un nuevo entorno se debe hacer paso a paso, comenzando por aquello que suponga el mayor problema. Normalmente se empieza por las pruebas y la planificación. Es importante reorganizar el espacio de trabajo para favorecer la comunicación desde el primer instante.

Hay que cambiar la estrategia en las siguientes áreas:
  • Pruebas: las pruebas otorgan confianza en el código. Hay que ir introduciendo pruebas en el código existente a medida que sea necesario modificarlo por cualquier razón.
  • Diseño: XP provocará un aumento en la refactorización. No se debe realizar al mismo tiempo que se añade una nueva funcionalidad. Los objetivos de la refactorización deben ser identificados cuanto antes y ser abordados antes de avanzar en funcionalidades nuevas.
  • Planificación: se debe educar al cliente en las nuevas reglas que guían la planificación y convencerle para que escriba historias de usuario.
  • Gestión: una de las partes más difíciles es el cambio de mentalidad sobre la gestión del equipo. El gestor debe de dejar de tomar las decisiones que son responsabilidad de los programadores. Los programadores deben asumir la responsabilidad de su trabajo.
  • Desarrollo: se debe aprender a desarrollar en pareja y el entorno debe facilitarlo.

Ciclo de vida del proyecto en XP

El ciclo de vida de XP trata de adelantar cuanto lo máximo posible la puesta en producción de valor para el cliente, aunque sólo sea una parte de la funcionalidad. A partir de ahí, se irá incrementando el valor en las siguientes iteraciones.

En una primera fase de exploración se debe analizar la viabilidad del proyecto y estudiar su utilidad y si tenemos las capacidades necesarias para llevarlo adelante. Esta fase termina en el momento en que existen suficientes historias de usuaria para una puesta en producción.

En esta fase se explora la arquitectura del sistema a través de varias alternativas. Se exploran las tecnologías a usar para conocer sus límites y rendimiento. El cliente también debe aprender en esta etapa a escribir historias de usuario. Para ello debe recibir realimentación por parte de los desarrolladores hasta llegar al nivel adecuado.

En la fase de planificación se recogen las historias más valiosas que puedan servir para una primera puesta en producción y se trata de estimar la fecha. A partir de este punto se realizan varias iteraciones. En la primera iteración se tratará de asentar la arquitectura del proyecto y en las subsecuentes iteraciones se irán incluyendo las historias de usuario seleccionadas.

Posteriormente se pasará producción, para lo cual puede ser necesario hacer pruebas de rendimiento del sistema y refinarlo en base a los resultados.

A partir de este momento el proyecto está en la fase de mantenimiento, que es el estado natural de un proyecto en XP. En esta fase se incorpora nueva funcionalidad manteniendo la existente, manejando las altas y bajas en el equipo.

Cada nueva versión necesitará de una fase de exploración que puede implicar la refactorización de parte del sistema. También hay que responder a los problemas que puedan surgir en el funcionamiento normal del sistema, lo que puede disminuir la velocidad del equipo para aportar nueva funcionalidad.

El día en que el cliente ni tiene nuevas historias es el momento en que se debe redactar el funcionamiento del sistema para la posteridad. Este es el fin del proyecto. También se puede llegar al fin del proyecto porque éste no reporte la rentabilidad esperada.

Roles en XP

En un equipo XP existen los siguientes roles:
  • Programador: programa y comunica sobre lo que hace. Debe buscar la sencillez del sistema. Es miembro de un equipo que le ayuda a mejorar.
  • Cliente: decide lo que debe hacer el sistema en función de la información de que dispone y la que le da el avance del proyecto. También se encarga de escribir las historias que debe cumplir el sistema y crea pruebas para comprobarlo.
  • Probador (tester): ayuda al cliente a escribir y ejecutar las pruebas funcionales. Puede ser un programador.
  • Rastreador (tracker): recoge las estimaciones, verifica su cumplimiento e informa al equipo.
  • Consejero (coach): vela por el cumplimiento del proceso. Su trabajo disminuye a medida que el equipo madura y se hace responsable del proceso.
  • Consultor: aporta conocimiento técnico al equipo en un determinado momento, pero no resuelve el problema por ellos.
  • Jefe: responde a las necesidades del equipo para la realización el proyecto. Debe pedir explicaciones al equipo sobre su forma de trabajar y, si algo no tiene sentido, hacerlo saber para que el equipo lo corrija.

Observaciones generales

En XP es necesario realizar moderadamente bien el conjunto de todas las prácticas para empezar a obtener resultados notables. Esto es porque las prácticas se apoyan unas en otras y es esta conjunción lo que asegura el buen funcionamiento de XP.

Las prácticas de XP son simples, pero es difícil realizarlas de forma conjunta. Cuando hay problemas, se tiende a relajar la aplicación de las prácticas más exigentes y el proceso se desequilibra si no se corrige rápidamente. También se requiere humildad y coraje para reconocer lo que uno no sabe y preguntar por ello. Hay que estar dispuesto a colaborar.

En ciertas condiciones hay que descartar XP, como cuando tratamos con equipos grandes, clientes desconfiados o tecnología que no soporta el cambio. Estos casos dificultan o imposibilitan la aplicación de algunas de las prácticas de XP, desestabilizando el proceso de desarrollo.

Esto último es muy importante, puesto que por más que nos gusten las ideas que XP propone, puede ser contraproducente adoptar XP a ciegas en determinados entornos en los que pueden llevar al caos. Sin embargo, aunque introducir el conjunto de prácticas de XP no sea una buena opción, algunas de ellas son recomendables en sí mismas en el desarrollo de software, como la integración continua (hasta cierto grado), el uso de estándares de codificación y las pruebas auto

Referencias:
  • Extreme Programming Explained (Kent Beck)


viernes, 18 de marzo de 2011

The Mythical Man-Month

Este libro, un clásico de referencia sobre la ingeniería del software, es una colección de ensayos en los que el autor, Frederick P. Brooks, expone sus experiencias como gestor en un gran proyecto de desarrollo.

Aunque se trata de un libro de hace más de 30 años, que refleja experiencias de un proyecto realizado 20 años atrás, este libro sigue resultando valioso. En lugar de centrarse en los ya obsoletos problemas técnicos del proyecto, se centra en los problemas organizacionales de los mismos, y en cómo gestionar a las personas involucradas en ellos.

A continuación, haré un resumen de cada uno de los capítulos que forman parte de este libro:

1. El pozo de alquitrán

Los proyectos de software con como un pozo de alquitrán, en el cual uno se adentra sin darse cuenta y, cuando comienzan los problemas, luchar contra ellos sólo hace que uno se hunda más.

No hay razón aparente para la dificultad de los proyectos, sino un cúmulo factores. Para conocer la naturaleza de este problema es necesario identificar la finalidad de la programación y las satisfacciones y dificultades que entraña.

Existen distintos productos de software en función su usuario final que requiere más o menos diseño, integración o pruebas, e implican diferentes grados de esfuerzo. Un producto que vaya ser usado por otros supone el triple de esfuerzo que un programa de uso privado. Un programa que va a ser usado dentro un sistema supone el triple de esfuerzo que uno que va a ser usado de forma independiente. Estas implicaciones son independientes entre sí, por lo que un producto de software que va a ser usado por sistema cuesta nueve veces más esfuerzo que un programa independiente de uso privado.

La programación proporciona la satisfacción de crear cosas complicadas que sean de utilidad para los demás. Es algo tan maleable que permite hacer casi cualquier cosa. Sin embargo, se sufre la necesidad de perfección y el estar trabajando con objetivos impuestos por otros.

2. El mito del mes-persona

La principal causa de fracaso de los proyectos de software es la desviación respecto a la planificación estimada. Esto se debe al optimismo a la hora de estimar por parte de los programadores, que no tienen en cuenta que las ideas iniciales pueden ser incompletas e incluso erróneas. Además se carece de datos fiables sobre las estimaciones, lo que hace difícil justificar estimaciones que vayan en contra de los intereses del gestor o del propio cliente.

A la hora de estimar, se suele caer en el error de creer que personas y meses son intercambiables. Se olvida que al añadir más personas a una tarea se incrementa el esfuerzo total necesario debido a la necesidad de reorganizar las tareas, de formar a los nuevos miembros y al aumento de la intercomunicación.

La ley de Brooks dice que: “Añadir gente a un proyecto retrasado lo retrasa más”.

Como regla general, Brooks propone repartir el tiempo de un proyecto de la siguiente forma: 1/3 para diseño, 1/6 para la codificación, 1/4 para las pruebas de componentes y 1/4 para las pruebas de sistema.

3. El equipo de cirujía

Los buenos programadores son mucho más productivos que los malos. Además, los equipos, cuanto más pequeños sean menos los problemas de intercomunicación tendrán.

Hay que favorecer el trabajo de los buenos programadores simplificando la comunicación dentro del equipo.

Los equipos pequeños son demasiado lentos para realizar grandes sistemas y, por otra parte, un equipo de mucha gente es costoso, lento e ineficiente, y produce sistemas que no están conceptualmente integrados.

Una solución es tener un equipo organizado en torno a un programador jefe a quien da apoyo, de la misma manera que un equipo de cirugía. De esta forma se logra mantener la integridad de un producto de un número reducido de mentes con la ayuda de unos cuantos colaboradores, reduciendo la comunicación de forma radical.

4. Aristocracia, democracia, y diseño de sistemas

La consideración más importante en el diseño de un sistema es preservar la integridad conceptual. La integridad conceptual otorga mayor facilidad de uso, y permite construir y probar el sistema más rápidamente.
Para lograrla, el diseño debe ser fruto de una sola mente o de un grupo reducido de mentes que están de acuerdo. La separación entre arquitectura e implementación es la forma de conseguir esta integridad conceptual en sistemas grandes.

Alguien debe controlar los conceptos fijando la arquitectura, pero ello mejora la creatividad en la implementación, puesto que permite enfocar el esfuerzo.

De cualquier manera, arquitectura (requisitos), implementación (diseño) y realización (programación) pueden discurrir en paralelo.

5. El efecto del segundo sistema

Una buena comunicación puede dar al arquitecto una lectura adecuada de los costes y al constructor confianza en el diseño, sin difuminar la clara división de responsabilidades entre ellos.

Para que el arquitecto pueda influir con éxito en la implementación debe tener en cuenta que:
  • Es el constructor quien tiene la responsabilidad de la implementación. El arquitecto solo sugiere.
  • El arquitecto debe ser capaz de sugerir una forma de implementar lo que especifica, pero debe estar preparado para aceptar cualquier otra forma igualmente válida.
  • Debe escuchar las sugerencias del constructor para mejorar la propia arquitectura.

El segundo sistema es el más peligroso, porque se tiende a sobrediseñarlo. Asignar a priori valores de memoria y tiempo para cada función ayuda a detectar el sobrediseño durante la implementación.

6. Pasar la Palabra

A la hora de redactar el diseño de un sistema, por grande que sea, es mejor que lo hagan pocas personas (preferiblemente una) para que el resultado sea consistente.

Se necesita tanto una descripción formal que aporte precisión como una descripción en prosa que aporte claridad. Una de ellas debe ser la de referencia y la otra servir como apoyo. Utilizar una implementación como definición es una fuente de problemas.

Es importante registrar y publicar todas las consultas de los desarrolladores a los arquitectos.

7. ¿Por qué cayó la Torre de Babel?

Si no hay comunicación cada uno hace sus propias suposiciones, provocando retrasos, desajuste funcional y errores en el sistema. Los equipos de deben comunicar de todas las formas posibles, tanto informal como formalmente.

Se debe especificar cuanto antes el conjunto de documentos a través de los cuales se producirá la comunicación formal en el proyecto. Esta documentación debe estar al alcance de cualquier miembro del equipo. Se debe actualizar constantemente y resaltar los cambios realizados respecto a la última versión.

Sin embargo, Parnas propone que nadie debe ver las interioridades de ninguna otra parte que no sea aquella en la que se está trabajando. Sólo se deben publicar las interfaces.

La organización debe encargarse de reducir la cantidad de comunicación y coordinación necesaria, mediante la división de tareas y la especialización en las funciones.

En cada proyecto se deben establecer dos roles, el productor y el arquitecto o director técnico, que requieren de distintas habilidades.

8. Apuntando para disparar

No se puede estimar el esfuerzo total de un proyecto estimando el tiempo de codificación y extrapolándolo a las demás tareas.

Tampoco sirve extrapolar la construcción de pequeños sistemas a proyectos de sistemas. El incremento en el esfuerzo es una potencia del tamaño del proyecto.

Hay estudios que dicen que los programadores sólo dedican el 50% de su tiempo a la programación y el resto a otras tareas.

Otros estudios sobre la productividad indican que ésta se mantiene constante en términos de secuencias elementales. La productividad se incrementa por 5 cuando se pasa a usar un lenguaje de alto nivel.

9. 10 libras en una bolsa de 5

Además del tiempo de ejecución, el espacio de memoria que ocupa un programa es su coste principal. Se deben establecer límites de tamaño en cada función para que los costes no se disparen.

Durante la implementación, los arquitectos deben velar por la continuidad de la integridad del sistema.

Para poder hacer un buen balance entre tiempo y espacio, el equipo debe estar entrenado en las peculiaridades del lenguaje de programación y de la máquina que se están usando.

Todo proyecto de programación necesita una librería de componentes estándar.

Los programas sencillos y rápidos son resultado de la innovación estrategia más que de una buena táctica. Esto se puede lograr a través del uso de un nuevo algoritmo o de rehacer la representación de los datos.

10. La hipótesis documental

Sólo unos pocos de todos los documentos del proyecto son el eje sobre el que gira toda la gestión del proyecto: objetivos, manual, calendario, presupuesto, diagrama de organización y situación física de los miembros del equipo.

El gestor debe formalizar este conjunto de documentos. Mantener estos documentos proporciona un mecanismo de vigilancia y alarma.

El gestor debe encargarse de que todo el mundo vaya en la misma dirección a través de la comunicación, y no tomando todas las decisiones. Solo una pequeña parte de las tareas del gestor están relacionadas con información que está fuera de su mente.

11. Planea deshacerte de uno

Cuando se pasa de un entorno protegido a uno de producción es necesario un paso intermedio de prueba. Al probar el primer sistema construido se suele comprobar que éste es demasiado lento, grande o difícil de usar, y que debe ser rediseñado.

Este rediseño se puede hacer de golpe o pieza a pieza, pero habrá que hacerlo de todas formas. Si se entrega el primer sistema, esto será en detrimento del usuario y del desarrollador que lo debe mantener. Por tanto, debe incluirse en la planificación el rediseño de la primera solución.

Las necesidades del cliente y la percepción de las mismas cambiarán a medida que avanza el proyecto. La maleabilidad del software hace que los requerimientos no dejen de cambiar porque los cambios se presuponen fáciles. Algunos de estos cambios son válidos y hay que estar preparados para poder afrontarlos. Las técnicas que nos permiten hacer esto son conocidas pero no son usadas.

Los esfuerzos de los desarrolladores son tentativas de satisfacer los requisitos, sujetos a modificaciones y, por tanto, hace que sean reacios a documentarlos.

Para acomodar una organización al cambio los miembros de los equipos de ben ser intercambiables y reubicables, tanto en la parte de gestión como en la técnica. Una posible solución es la propuesta organizar el equipo en torno a un programador jefe.

El mantenimiento del software consiste principalmente en reparar defectos de diseño, añadir nuevas funcionalidades y adaptarse a los cambios en el entorno o la configuración. El coste de mantenimiento de un programa es aproximadamente el 40% del coste de su desarrollo.

El número de errores encontrados aumenta con el número de usuarios. El número de errores a lo largo de la vida de un proyecto primero aumenta y luego se estabiliza.
Al corregir un error existe una alta probabilidad de introducir otros, por los que se debe disponer de casos de prueba que aseguren que esto no se produce.

Durante la vida de un proyecto el número de módulos se incrementa de forma lineal, pero el número de módulos afectados por las modificaciones se incrementa de forma exponencial. Toda corrección tiende a romper la estructura e incrementa la entropía y el desorden del sistema. Inevitablemente, se llegará a un punto donde la mejor solución es el rediseño.

12. Herramientas precisas

Todo proyecto necesita herramientas personalizadas, por lo que el gestor debe establecer la filosofía para establecer los recursos necesarios para su construcción.

Es necesario instrumentalizar las máquinas sobre las que se va probar para poder extraer resultados de ellas. A la hora de probar en la máquina es mejor repartir periodos separados de tiempo para los distintos miembros.

La documentación voluminosa introduce mayor complejidad al proyecto.

Se debe construir de forma temprana un simulador de rendimiento que analice el programa desde fuera, y hay que prestar atención a lo que dice.

Las mejores herramientas que existen actualmente (1975) son los lenguajes de alto nivel (aumentan el rendimiento y facilitan la depuración) y la programación interactiva (reduce el tiempo de respuesta en la depuración).

13. El todo y las partes

Una buena arquitectura hace que un sistema sea más fácil de usar, más fácil de construir y que tenga menos errores. Antes de empezar la construcción se debe entregar las especificaciones al grupo de test, que debe comprobar si está completa y es clara.

Diseñar de lo general a lo específico refinando la especificación paso a paso reduce los errores y permite volver atrás cuando sea necesario. Hay que usar una notación de tan alto nivel como sea posible en cada paso.

La programación estructurada es el diseño de programas formados por un conjunto reducido de estructuras de control.

Se demuestra que en la primera interacción de depuración se un progreso hace tres veces mayor que en las subsecuentes. La depuración de un sistema siempre lleva más tiempo de lo planeado.

Se debe empezar la depuración sólo cuando las piezas parezcan funcionar, en vez de adelantarlo a cuando se hayan detectado errores pero sigan sin ser corregidos.

Merece la pena construir código de apoyo para la depuración, que puede llegar a ser el 50% del código a depurar.

Durante la depuración se deben añadir los componentes de uno en uno. Es mejor introducir grandes cambio de forma poco frecuente porque esto introduce menos inestabilidad.

14. Vislumbrando una catástrofe

Los proyectos se retrasan día a día. Esto es difícil de reconocer, de prevenir y de arreglar. Para poder controlar un proyecto es necesario tener un calendario con hitos concretos y bien definidos y fechas identificables.

Diversos estudios han demostrado que las estimaciones no suelen variar hasta el arranque del proyecto, posteriormente se van reduciendo las sobreestimaciones y, tres semanas antes del fin estimado del proyecto, lo que cambian son las infraestimaciones.

La planificación de ruta crítica permite identificar que desviaciones son las que realmente importan. Se debe identificar esta ruta crítica lo antes posible.

Todo jefe necesita ser alertado de las excepcionen que precisen de acciones correctoras y que se muestre el estado del proyecto para su conocimiento. Esto es difícil puesto que los subordinados temen la intromisión del jefe en sus responsabilidades. Se debe disponer de técnicas de revisión que ofrezcan el estado del proyecto a todo el mundo.

15. La otra cara

La documentación para el usuario es tan importante como el propio producto. Es importante también para el autor como memoria de lo que se ha hecho.

Por regla general no se entrega una buena documentación porque no se ha instruido a los desarrolladores en cómo hacerlo de forma efectiva y económica.

La documentación crítica para el usuario se debe establecer antes de la construcción ya que contiene decisiones de planificación básica.

A nivel de documentación interna, para mantener la documentación actualizada es necesario incorporarla junto con el código fuente. Se debe explicar por qué las cosas son como son, no solamente cómo están hechas. Los lenguajes de alto nivel permiten técnicas de auto-documentación dentro del propio código.

16. No hay balas de plata – Esencia y accidente

El autor compara el desarrollo de software con los hombres-lobo, gente normal que se convierte de repente en algo fuera de control que solo pueden ser eliminados con una bala de plata. Sin embargo, no existen balas de plata para el software que den una mejora de un orden de magnitud en su desarrollo, aunque existen innovaciones que de forma conjunta sí pueden ofrecer esta mejora.

La construcción de software implica tareas complejas, acomodar sus estructuras conceptuales, y tareas accidentales, representarlas en lenguajes de programación.

Las dificultades esenciales del desarrollo de software son la complejidad, conformidad, variabilidad e invisibilidad a la hora de acomodar las complejas estructuras conceptuales que implica.

El desarrollo de software también implica tareas accidentales, que son la representación en lenguajes de programación.

Las mejoras de los últimos tiempos han estado enfocadas en solucionar las dificultades accidentales a través de:
  • Lenguajes de alto nivel.
  • Tiempo de ejecución compartido.
  • Entornos de programación unificados.

Sin embargo, la mayor parte del tiempo se dedica a acomodar estructuras conceptuales abstractas de gran complejidad y, por tanto, es esta tarea esencial la que se debe atacar. Se han propuesto las siguientes formas:
  • Avances en lenguajes de alto nivel.
  • Programación orientada a objetos.
  • Inteligencia artificial.
  • Sistemas expertos.
  • Programación automática.
  • Programación gráfica.
  • Verificación de programas.
  • Entornos y herramientas de trabajo.
  • Estaciones de trabajo.

Estas alternativas, aunque introducen mejoras, no han alcanzado los niveles esperados. Es necesario atacar la esencia del software elevando el nivel de abstracción que implica su desarrollo. Los avances más prometedores en este sentido son:
  • Comprar en lugar de desarrollar.
  • Refinado de requisitos y prototipado rápido.
  • Desarrollo incremental.
  • Encontrar y educar a los mejores diseñadores.


17. “No hay balas de plata” revisado

En este capítulo el autor rebate las críticas recibidas al artículo presentado en el capítulo anterior “No hay balas de plata”. Sobre todo aquellas que van contra el principal argumento del mismo (que no hay ningún avance en el mundo del software que pueda dar por sí solo una mejora de n orden de magnitud), para presentar la propia bala de plata de cada investigador de turno.

El autor se reafirma en la idea de que la complejidad es la mayor dificultad inherente al software, aunque la mayor parte de esta complejidad se suele deber a problemas de organización de la propia realidad que se intenta modelar. Esta complejidad se puede atacar aumentando el nivel de abstracción de los elementos utilizados y manteniendo en todo momento sistemas que funcionen y que incorporen nuevas funcionalidades de forma incremental.

Se introduce el enfoque a la calidad, que aumentaría de la productividad reduciendo el tiempo dedicado a la corrección de errores.

La productividad también aumenta con mejores equipos y herramientas de trabajo y el uso de la programación orientada a objetos (que promueve la modularidad, la encapsulación, la herencia y el tipado de datos). Aunque se lamenta de la poca introducción de la orientación a objetos y la reutilización.

18. Proposiciones de “El mito del mes-persona”

Este capítulo muestra una recopilación de las afirmaciones expuestas en el libro a modo de resumen.

19. “El mito del mes-persona” 20 años después

Este capítulo es una revisión del libro hecha tras 20 años por el propio autor. En ella expone los argumentos que mantiene tras este tiempo y aquellos en los que ha cambiado de opinión.

El autor mantiene que la integridad conceptual es el factor más importante para la facilidad de uso de un sistema. Esta integridad conceptual suele lograrse cuando el producto ha sido diseñado por una única persona, pero este enfoque no es productivo para grandes sistemas que sufren presiones competitivas.

En estos entornos es necesario tener un arquitecto (o un grupo de ellos organizados de forma jerárquica) que se encargue de la integridad conceptual de todos los aspectos del producto percibibles por el usuario. Además sus tareas estarán separadas de la implementación.

Otro peligro que se recalca en el desarrollo de sistemas es el de cargarlos de funcionalidades de mínimo valor que compliquen el diseño y el uso de los mismos. Se debe ser consciente de quienes son los usuarios y ponderar las nuevas capacidades que se introducen en función del mayor valor ofrecido al mayor número de usuarios.

En lo que sí cambia de opinión es en el consejo de que el primer sistema implementado deba ser desechado. Esto era una consecuencia de la aplicación del desarrollo en cascada, que asume que los pasos del proyecto avanzan uno a continuación de otro, y supone que todos se realizan de forma correcta. El sistema se construye de una sola vez y se asume que todos los errores se introducen en la implementación.

Un modelo de desarrollo incremental permitiría el refinamiento progresivo del sistema. Se puede construir un sistema inicial de funcionalidad limitada y, a partir del cual hacer crecer el sistema introduciendo nuevas funcionalidades. De esta forma se pueden adelantar las pruebas del sistema y se puede entregar un sistema que funciona cuando sea necesario, aunque de funcionalidad reducida.

En cuanto al ocultamiento de información que Parnas proponía y del que el autor desconfiaba, éste se retracta y asume que los programadores son más efectivos si no están expuestos a las interioridades de módulos que no son de su propiedad.

El avance en el desarrollo del software se producirá al aumentar el nivel de abstracción de los elementos que se utilizan. En este sentido se han dado los siguientes pasos:
  • La definición del ocultamiento de información de los módulos hecha por Parnas.
  • La aparición de los tipos de dato abstractos de los que pueden derivar otros objetos.
  • La programación orientada a objetos.

El hecho de disponer de clases diseñadas y probadas para ser reutilizadas es lo que permitiría elevar dicho nivel de abstracción.

La Ley de Brooks (añadir más gente a un proyecto retrasado lo retrasa más) ha sido refinada gracias a varios estudios, aunque se mantiene como una buena aproximación a la realidad. En realidad, añadir más gente a un proyecto retrasado aumenta su coste, pero no siempre lo retrasa.

Otra idea que se ha reforzado durante este tiempo es la importancia de las personas que forman parte de los proyectos, y cómo los factores humanos deben ser tomados en consideración. Es importante dar responsabilidades a la gente y no centralizar todo en una única entidad, puesto que esto fomenta la creatividad.

Se hace eco del avance en el campo de la informática y de la explosión del número de ordenadores. Esto ha cambiado la forma de desarrollar el software y creado una nueva industria, los programas empaquetados.

Respecto al futuro de la ingeniería del software, presume que, a través del conocimiento, será capaz de lidiar en el futuro podrá con la complejidad del comportamiento humano y los aspectos no lineales que introduce.

miércoles, 5 de enero de 2011

Clean Code

Durante los últimos meses he estado leyendo y releyendo el libro “Clean Code: A Handbook of Agile Software Craftmanship” de Robert Martin. En este libro, el autor trata por qué hemos de escribir código limpio y cómo hacerlo.


Código Limpio. Este tema es algo que me interesa particularmente en estos momentos. Cuando eres un desarrollador con responsabilidades puntuales dentro de un proyecto y sin relevancia ninguna en cuanto a las decisiones que toma el arquitecto de turno, lo único importante es quitarte el muerto de encima cuanto antes para evitar las típicas preguntas de: ¿Cómo vamos (¿Vamos? Pero si lo estoy haciendo sólo yo…)? ¿En qué estado está esta tarea? ¿Qué pongo en el Project, 80%, 90%...? ¿Podemos hacer la entrega ya?

Sin embargo, cuando tienes cierto grado de responsabilidad sobre un proyecto y te interesa que su evolución no se complique es importante hacer entender a los miembros del equipo que la responsabilidad de cada uno va más allá de quitarse el muerto de encima, sino que cada uno debe hacer lo mejor que pueda por tener un código lo más comprensible posible. Al código comprensible es a lo que Robert Martin llama código limpio.

Este libro está dividido en tres partes:
  • En una primera parte, se explica qué es el código limpio y cómo llegar a él.
  • La segunda parte son ejemplos más complejos de refactorización de código en busca de código más limpio.
  • La tercera parte es una recopilación de pistas que ayudan a encontrar código que no está todo lo bien que debería estar y que debería ser refactorizado.

En este resumen del libro me voy a centrar en la primera parte de él, puesto que la segunda y la tercera parte son bastante directas.

Capítulo 1 - NOMBRES

La lectura del código es la actividad a la que se dedica más tiempo, incluso para escribir nuevo código hay que leer lo que ya hay escrito. Un mal código puede llegar a arruinar un proyecto, dificultando las modificaciones y alargando los plazos de entrega. El único responsable de ello es el programador que no se toma en serio su trabajo y no es profesional.

Para evitar estas situaciones se debe hacer un código limpio, pero, ¿qué es código limpio?. Según varios autores conocidos, las características del código limpio son:
  • Fácil de leer, expresivo y sencillo.
  • Probado mediante tests automáticos.
  • Hace una única cosa.
  • No existe duplicidad en él.
  • Utiliza el menor número de elementos posibles.
  • Realiza abstracciones de elementos similares.

Para conseguir un código limpio se debe prestar atención a cada uno de los elementos que componen un sistema a nivel de código: variables, funciones, clases, comentarios, tests y el propio sistema. Además existen una serie de aspectos, como son la diferenciación entre objetos y estructuras de datos, el comportamiento en los límites del sistema, el manejo de errores y la concurrencia que afectan directamente al código y a su comprensión. Capítulo a capítulo se va analizando cómo mejorar en cada uno de estos elementos

Tembién habla de la regla del Boy Scout “Hay que dejar el código mejor de cómo lo encontraste”. Esto va en la línea de la mejora continua del código y de la imposibilidad de hacerlo perfecto a la primera.

Capítulo 2 - NOMBRES

Un buen nombre da mucha más información que cualquier otra cosa. Para conseguir buenos nombres hay que usar nombres descriptivos y claros. Deben ser legibles y evitar codificaciones complejas.

Capítulo 3 - FUNCIONES

Una buena función es aquella de la que se puede inferir su comportamiento de un solo vistazo. Para ello deben ser cortas, hacer una única cosa y mantenerse dentro del mismo nivel de abstracción.

Es importante usar buenos nombres y reducir al mínimo el número de argumentos. Pero lo principal es eliminar toda la duplicidad.

Capítulo 4 - COMENTARIOS

Los comentarios no pueden maquillar el mal código. La necesidad de comentarios para aclarar algo es síntoma de que hay código mal escrito que debería ser rediseñado. Es preferible expresarse mediante el propio código.

Hay situaciones en las que los comentarios son apropiados, como cuando tratan de:
  • Aspectos legales del código.
  • Advertir de consecuencias.
  • Comentarios TODO.
  • Remarcar la importancia de algo.
  • Javadocs en APIs públicas.

Sin embargo, en el resto de los casos pueden llevar a confusión y deberían ser evitados.

Capítulo 5 - FORMATEO

El formateo afecta directamente a la legibilidad del código. El código se lee de arriba abajo y de izquierda a derecha. Los espacios verticales y horizontales permiten separar ideas y conceptos distintos.

Se debe mantener un estilo uniforme, y para ello se debe consensuar con todo el equipo las reglas de formateo. Las reglas elegidas no son tan importantes como el hecho de que todo el mundo se atenga a ellas.

Capítulo 6 - OBJETOS Y ESTRUCTURAS DE DATOS.

No todo se puede solucionar usando sólo objetos o sólo estructuras de datos. Hay que aprender a diferenciar en qué situaciones son más convenientes unos u otros. Los objetos esconden sus datos y exponen funciones que permiten manipularlos, mientras que las estructuras de datos exponen datos pero no tiene funciones que operen sobre ellos.

Capítulo 7 - MANEJO DE ERRORES

El código de manejo de errores oculta la verdadera funcionalidad del código. Hay que mantenerlo lo más separado posible de la lógica de negocio para no dificultar la comprensión de ésta última.

Para no complicar el manejo de errores hay que escribir los bloques try-catch-finally en primer lugar permite identificar los puntos del programa en los cuáles se puede producir una excepción. Se deben usar excepciones en lugar de códigos de retorno. Las excepciones deben proporcionar suficiente información sobre el error y el momento en que se ha producido. Además, las excepciones no comprobadas son menos invasivas a nivel de código.

También se debe evitar pasar null como parámetro de una llamada a un método o devolverlo como valor de retorno. Esto introduce complejidad adicional debido a las comprobaciones necesarias.

Capítulo 8 - FRONTERAS

Los sistemas dependen de paquetes de terceros o de componentes desarrollados por otros equipos. Hay que definir de forma clara la frontera entre el código y el exterior para poder acomodar de forma sencilla los futuros cambios, minimizando las partes de nuestro código que dependan de elementos externos.

Los tests ayudan a experimentar con el código externo viendo cómo puede cubrir nuestras necesidades. También permiten comprobar que las nuevas versiones de la librería siguen cumpliendo con nuestras necesidades.

Encapsular el conocimiento adquerido a través de los tests en un interfaz entre nuestro sistema y el código de terceros permite localizar en ún único punto las modificaciones debidas a posibles cambios en código que está fuera de nuestro control.

Capítulo 9 - TESTS UNITARIOS

En TDD los tests de seben escribir en primer lugar. Las 3 reglas de TDD son:
  • No se debe escribir código de producción hast que no se tenga un test unitario que falle.
  • No se debe escribir más de un test unitario que lo necesario para que éste falle.
  • No se debe escribir más código de producción uqe el necesario para que pase un tests unitario que fallaba.

Se debe dar la misma importancia al código de test que al de producción. Los tests permiten que el código de producción se pueda modificar sin temor a introducir nuevos errores, asegurando su mantenibilidad.

El número de assert por cada test debe ser lo más bajo posible. Se debe testear un único concepto en cada test, lo que permite ir aclarando los distintos conceptos progresivamente, mejorando el conocimiento sobre el código.

Las reglas FIRST sobre el código de test son:
  • Fast: Se deben ejecutar rápido y muy a menudo.
  • Independent: Las condiciones de un test no deben depender de un test anterior.
  • Repeteable: Se deben poder ejecutar en cualquier entorno.
  • Self-Validating: El propio test debe decir si se cumple o no, no debe hacer falta realizar comprobaciones posteriores al test.
  • Timely: Los tests se deben escribir en el momento adecuado, que es justo ante de escribir el código de producción, lo que permite escribir código fácilmente testeable.

Capítulo 10 – CLASES

Las clases se deben organizar situando en primer lugar las constantes públicas, después las variables estáticas privadas, variables de instancia privadas y, a continuación, los métodos. Los métodos privados se encuentran junto a los métodos públicos que los usan.

Se debe mantener la encapsulación de las clases. Las clases deben ser pequeñas y con un número reducido de responsabilidades. Se consigue una alta cohesión si todos los métodos de una clase hacen uso de todas sus variables privadas.

Las nuevas funcionalidades se deben de introducir extendiendo el sistema, no modificando el código existente. Las clases concretas contienen detalles de implementación y las clases abstractas expresan conceptos. La dependencia en clases concretas es un riesgo en caso de cambio. Según el principio de inversión de dependencia las clases deben depender de abstracciones.

Capítulo 11 – SISTEMAS

Es importante reconocer y separar las distintas responsabilidades de un sistema.

En primer lugar, se deben separar el proceso de construcción de un sistema de su uso. El proceso de construcción se encarga de crear y conectar entre sí los objetos necesarios para la ejecución de la aplicación. Se debe modularizar y separar de la lógica de ejecución, permitiendo una estrategia independiente para resolver las dependencias de la aplicación.

La inyección de dependencias permite que los objetos sólo se encarguen únicamente de la lógica de negocio. Un elemento contenedor se encarga de inyectar las dependencias de cada objeto de forma externa.

No se puede construir sistemas de forma correcta a la primera hay que ir implementando las historias de que se dispone, y después refactorizar y expandir el sistema para seguir implementando nuevas historias. TDD, refactorización y código limpio permiten esto a nivel de código.

A nivel de sistema, es difícil crecer de sistemas simples a sistemas complejos debido a las dependencias en la arquitectura que se usa. Para evitar estos problemas, se deben separar las distintos responsabilidades de un sistema. Para lograr esta separación se pueden usar proxys, frameworks AOP Java puros o aspectos AspectJ.

Se debe construir la lógica de la aplicación en base a POJOs a través de tests, e ir evolucionando de lo simple a lo complicado, interconectando los distintos aspectos necesarios.

Capítulo 12 – EMERGENCIA

Las siguientes 4 reglas dadas por Kent Beck permiten crear buenos diseños según se trabaja en ellos, conocer nuestro sistema y aplicar buenos principios de diseño:
  • Ejecutar todos los tests: Los tests verifican que el sistema se comporta según lo esperado. Al construir un sistema testeable se intenta que las clases sean simples y tengan un único propósito, y se trata de reducir el acoplamiento.
  • Eliminar la duplicación: La duplicación implica trabajo adicional, más riesgo y mayor complejidad.
  • Expresar la intención del programador: Usar un código lo más expresivo posible facilita el mantenimiento. Se deben escoger buenos nombres, funciones y clases pequeñas y tests bien escritos.
  • Minimizar el número de clases y métodos: Siguiendo las recomendaciones anteriores uno se puede exceder creando demasiadas clases pequeñas. Hay que tener cuidado y mantener un número reducido de clases.

Durante la refactorización de puede aplicar todo nuestro conocimiento para mejorar el diseño: aumentar la cohesión, reducir el acoplamiento, separar responsabilidades, reducir funciones y clase, escoger nombres mejores, etc.

Se dedicar tiempo a estas tareas después hacer que el software funcione. No se debe pasar a la siguiente tarea sin repasar lo hecho. Es imposible conseguir un código limpio a la primera, hay que repasarlo una y otra vez para conseguir mejorarlo progresivamente.

Capítulo 13 – CONCURRENCIA

La concurrencia es otro de los aspectos que pueden estar presentes en el código. Permite desacoplar lo que que ocurre de cuándo ocurre, y mejorar tanto el rendimiento como la estructura de una aplicación.

Desde el punta de vista estructural se puede percibir la aplicación como un grupo de computadoras colaborando entre sí, haciendo el sistema más fácil de entender y permitiendo separar las responsabilidades. La concurrencia permite mejorar los tiempos de respuesta y la eficiencia de una aplicación.

Se deben tener en cuenta las siguientes ideas sobre la concurrencia:
  • Introduce cierta sobrecarga.
  • Es compleja de manejar.
  • Los errores causados por ella son difícilmente reproducibles
  • Normalmente requiere cambios en el diseño.

El problema de la concurrencia es que los distintos hilos de una aplicación pueden entrelazarse siguiendo múltiples flujos de ejecución, lo que puede provocar problemas inesperados en situaciones normales.

Para defendernos de los problemas de concurrencia es importante que cada clase tenga una única responsabilidad,seperando la gestión de hilos se del resto del código. Hay que conocer las librerías que manejas y entender los distintos modelos de ejecución. Las secciones sincronizadas lo más pequeñas posibles.

Ejecutar pruebas de forma frecuente es la mejor manera de encontrar los posibles errores en el código. Sin embargo, es difícil hacer tests cuando hay concurrencia. Se deben tratar los fallos espúreos como posibles problemas de concurrencia e instrumentar el código para forzar la aparición de errores.

CONCLUSIONES FINALES

Todas estas buenas prácticas sobre cómo escribir código limpio se deben ejecitar de forma constante para adquirir buenos hábitos y ser capaces de hacerlo de forma natural. Siguiendo estas prácticas el autor nos promete conseguir un código con el que será mas fácil trabajar y hará mucho menos frustrante nuestro día a día.

Una idea que se repite en varios puntos del libro es que debemos de ser capaces de exprimir las capacidades de nuestro entorno de trabajo. Conocer nuestro entorno de trabajo nos puede simplificar enormemente nuestro trabajo, puesto que puede realizar por nosotros un montón de actividades de forma automática y nos ofrece una serie de ayudas que nos pueden ayudar en el día a día haciendo innecesarias algunas convenciones que sólo sirven para complicar el código.

Otra cosa que me ha gustado de este libro es la bibliografía en que se basa. No es excesivamente grande, pero me ha servido para sacar unas buenas recomendaciones sobre otros libros a leer:
  • Agile Software Development: Principles, Patterns and Practices, Robert Martin.
  • Implementation Patterns, Kent Beck.
  • Test Driven Development, Kent Beck.
  • Extreme Programming Explained: Embrace Change, Kent Beck.
  • Refactoring: Improving the Design of Existing Code, Martin Fowler et al..
  • Design Patterns: Elements or Reusable Object Oriented Software, Gamma et al..
  • The Pragmatic Programmer, Andrew Hunt, Dave Thomas.