Hablar bien no cuesta una puta mierda
Avie, en Snatch

Buenas prácticas – Flujo del programa lineal

Pincha en la imagen para verla ampliada. Oringinal en la tira de xkcd, GOTO.

Podría reestructurar el flujo del programa, o podría usar un pequeño “GOTO”…

Bah, que le den a las buenas prácticas. Qué mal puede hacer? goto main_sub2; *COMPILE*

Yo empecé a programar a los 10 años, así que he sufrido el BASIC, sus líneas de código numeradas… pero sobre todo los GOTOs en programas monolíticos de miles de líneas… así que la modularización, reutilización y control de flujo los tengo muy interiorizados.

Hoy en día espero que ningún lenguaje de alto nivel mantenga algo que únicamente debería existir en lenguajes de bajo nivel (recordáis el ensamblador del Motorola 8Mhz?), así que resulta complicado explicar brevemente a todos los que os habéis iniciado en lenguajes más modernos por qué hay que modularizar, reutilizar y mantener un flujo lineal.

Pero todos los lenguajes de alto nivel modernos tienen lógicamente estructuras de control de flujo, algunas TAN odiosas para mí como break o continue de Java que afortunadamente sólo utilizan una ínfima minoría de “profesionales”, para mí leerlas es como recibir sendas bofetadas y mi opinión de tu profesionalidad me la ahorro.

Cuesta concienciar de por qué no utilizar más de un return por cada método, o por qué evitar salidas inesperadas lanzando excepciones o invocando returns en métodos void… Parece inocuo, al fin y al cabo es algo que puedes ver en los códigos fuentes de casi todos los frameworks, pero qué curioso, los bugs tiene predilección por estos métodos en tu código.

Ójala pudiera invocar al monstruo del GOTO cada vez que tengo que arreglar uno de esos…

MORALEJA

Las buenas prácticas son buenas por algo, porque antes de ti ha habido miles de profesionales que durante décadas se han pelado con tu mismo problema: mantén un flujo lineal, una entrada y una salida en cada método.

No son obligatorias de seguir, pero no seguirlas supone un coste… No te quejes cuando tengas que pagarlo, aunque desgraciadamente suele ser otro el que se encuentra tus muertos bajo la alfombra.

break

La letra con humor entra

Cómo explicar fácilmente conceptos duramente aprendidos?

Pues con una imagen, que por algo vale más que mil palabras, y si encima contiene humor pues mejor. Os parecerá una tontería, y lo es, pero es algo que he utilizado en más de un documento y correos, alguno para enmarcar jejeje.

Así que voy a crear una nueva categoría en esta humilde bitácora donde recopilar tanta sabiduría visual, comenzando con mañana!

Estén atentos y sintonicen esta bitácora!

Diseño top-down, o cómo jugar al Golf

Llevo toda una vida pensando primero de arriba a abajo, de lo general a lo particular, es decir primero visualizo el problema, exploro la solución más óptima para elegir/construir las herramientas y componentes que mejor se adaptan, para finalmente retocar la solución de abajo a arriba ya que en todo este proceso puedes haber descubierto una solución más óptima, o mejores herramientas, mejorar o corregir fallos en el diseño y/o implementación…

Este proceso es como jugar un hoyo de golf.

Cuando te enfrentas a un hoyo, lo primero que haces es estudiarlo para encontrar el plan más óptimo según el hoyo, la meteorología, tu equipo, la variedad de golpes de tu repertorio y tu habilidad para ejecutarlos.

En un circuito profesional embocar de un golpe es improbable cuando no es imposible, así que el objetivo real del primer golpe es alcanzar primero un objetivo más genérico, el green, con una superficie de unos 500 metros cuadrados, en el menor número posible de golpes. Si el green está fuera de tu alcance actual, buscas una posición óptima, estable y segura en la calle que te permita acceder al green en el futuro más inmediato posible.

Si el green estuviera exageradamente fuera de alcance incluso con varios golpes, por ejemplo en un hoyo de 500 metros si sólo eres capaz de alcanzar 50 metros por golpe, no te molestes en trazar un gran plan, sólo en que tu actual golpe te deje en las mejores condiciones posibles para el próximo.

En cada golpe (iteración) te replanteas la situación, las condiciones meteorológicas, los obstáculos presentes y futuros (Riesgos potenciales), seleccionas una herramienta y cómo utilizarla, ejecutas el golpe y compruebas el resultado.

No siempre el resultado del golpe es el esperado: fallos en la estrategia o en la ejecución, obstáculos previsibles (fuera de límites, agua, bunker, árboles) e imprevisibles (ráfagas de viento, botes inesperados, animales…). Riesgos que se han materializado.

La desviación del objetivo suele traer consigo una serie de problemas e incluso penalizaciones: podemos alcanzar el lado no óptimo de la calle o el semi-rough que rodea la calle con la hierba más alta (menos potencia al golpe, menos precisión), que limita con el rough que es hierba sin cuidar o directamente suelo, algún obstáculo (árbol, bunker, agua) o salirnos del campo (fuera de límites) que nos obliga a volver a lanzar desde el mismo lugar y además nos penaliza.

En ocasiones sin embargo la suerte nos sonríe: un árbol, piedra o animal nos evita salirnos de calle o caer en un obstáculo. Es algo que raramente ocurre y es el peor veneno para los necios: cuanto peor es su situación más confían en esta suerte, porque les ocurrió en una ocasión y ahora esperan que les ocurra siempre.

Resulta vital saber cuándo avanzamos más frenando o incluso retrocediendo. Puede ser más seguro utilizar un hierro en lugar de una madera para sacrificar metros por precisión, o dejar un siguiente golpe más largo por el elevado riesgo del actual, o avanzar sólo unos metros pero asegurarnos de que volvemos a calle, incluso si eso significa retroceder. Es de cobardes retroceder siempre, pero de sabios acertar cuándo se debe retroceder.

Según nos acercamos cobra más importancia la precisión, hasta que finalmente alcanzas el objetivo deseado: el green. Pasamos de un objetivo general y difuso a un objetivo muy específico de 108 milímetros de diámetro. Si no te sales del green tarde o temprano embocarás, pero de nada sirve recorrer 500 metros en 2 ó 3 golpes si necesitas 7 para los 5 metros que te quedan: es el momento de la verdad, el que marca si tiramos por la borda todo el trabajo anterior.Leer su relieve es imprescindible para acertar, todo lo que no llega no entra, nunca salirse del green.

Et voilá, has embocado. La práctica lleva a la perfección, a elaborar mejores planes, a comprender mejor los riesgos, a decidir mejor, a ejecutar mejor.

En la vida real sería más loco y divertido jugar en ocasiones como si estuvieras jugando al croquet en el País de las Maravillas, con una Reina de Corazones cambiando las reglas del juego mientras ordena cortar cabezas, un Conejo Blanco que llega tarde a cambiar las banderas de los hoyos entre sí convirtiendo el hoyo que estás jugando en el número 5 en lugar del 2, Jardineros tapando el hoyo cuando estás preparando el putt mientras hacen otro agujero en otra parte del green, y un Naipe pasando el cortacésped cuando ejecutas el golpe.

En el software es casi siempre así, no hay margen para el aburrimiento :) .

PD, efectivamente, estoy planteándome volver al golf en el mejor campo que he conocido, en Zarapicos, Salamanca.

Introducción a UML

UML (Unified Modeling Language, Lenguaje Unificado de Modelado) es un lenguaje visual extensible de modelado de sistemas orientado a objetos, de propósito amplio y genérico estandarizado por el Object Management Group (OMG).

Nace del intento de unificar los lenguajes de modelado visuales más extendidos en 1994 (Booch, OMT…). En 1996 OMG lanzó una RFP (Request for Proposal) aceptado en 1997, marcando el nacimiento de UML. Hoy es un estándar de facto abierto en la industria del software.

UML concibe el mundo como colecciones de objetos que interactúan, por eso resulta idóneo para utilizarse en desarrollo de software con lenguajes orientados a objetos. Se utiliza para especificar o describir sistemas, artefactos, métodos y procesos durante todo el ciclo de vida del sistema sin importar la implementación ni el proceso de desarrollo, incorporando las mejores prácticas de modelado para que puedan ser fácilmente legibles por personas e implementarse por las herramientas software de modelado.

Existen dos aspectos en un modelo UML que no están completos el uno sin el otro.

  • Estructura estática, describe qué tipo de objetos conforman el sistema y cómo se relacionan.
  • Comportamiento dinámico, describe los ciclos de vida de estos objetos y cómo interactúan entre sí para entregar la funcionalidad requerida.

Un modelo UML tiene al menos dos dimensiones:

  • Textual, contiene las especificaciones de los diferentes elementos de modelado.
  • Gráfica, muestra gráficamente el modelo textual utilizando diagramas e iconos, son simplemente vistas o proyecciones visuales de ese plano posterior semántico. Pueden existir elementos elididos (si no se muestran gráficamente), incompletos (pueden faltar en ambas dimensiones) e incoherentes (contradicciones entre los elementos).

UML se compone de 3 Bloques de Construcción.

  • Elementos.
    • Estructurales. Son los nombres de un modelo UML, como una clase, interfaz, colaboración…
    • Comportamiento. Son los verbos de un modelo UML, como interacciones, actividades, máquinas de estado.
    • Agrupación. Paquetes que se utilizan para agrupar los elementos semánticamente en unidades cohesivas.
    • Anotación. Notas que se anexan al modelo para capturar información ad hoc.
  • Relaciones, unen a los elementos entre sí especificando cómo dos o más elementos se relacionan semánticamente.
    • Dependencia. El elemento origen depende del elemento destino y se puede ver afectado por cambios en éste.
    • Asociación. La descripción de un conjunto de vínculos entre objetos.
    • Agregación. El elemento destino es una parte del elemento origen.
    • Composición. Una forma de agregación más fuerte, más restringida.
    • Contención. El elemento origen contiene el elemento destino.
    • Generalización. El elemento origen es una especialización del elemento destino más general y se puede sustituir por éste.
    • Implementación. El elemento origen garantiza llevar a cabo el contrato especificado por el elemento destino.
  • Diagramas, nos muestran qué hará el sistema (diagramas a nivel de análisis) o cómo lo hará (diagramas a nivel de diseño).

UML utiliza 4 Mecanismos comunes para conseguir objetivos específicos:

  • Especificaciones, descripciones textuales de la semántica de un elemento.
  • Adornos, que se añaden a los elementos gráficos para hacer visibles aspectos de la especificación del elemento textual.
  • Divisiones comunes.
    • Clasificador/instancia (abstracción/especificación). Comparten icono pero las instancias tienen el nombre subrayado. UML proporciona 33 clasificadores (actor, clase, componente, interfaz, caso de uso…)
    • Interfaz/implementación. Separando lo que se hace de cómo se hace, los contratos ocultan la complejidad.
  • Mecanismos de extensión. UML no puede ser universal y satisfacer las necesidades presentes y futuras de todos los proyectos, por los que incorpora:
    • Restricciones, cadena de texto entre llaves que especifica cierta condición o regla sobre el elemento. UML define como una extensión estándar OCL (Object Constraint Language o Lenguaje de Restricción de Objetos).
    • Estereotipos, variaciones de un elemento de modelo existente con la misma forma pero con un propósito modificado, representado con el nombre del nuevo elemento entre cursores, <<…>>.
    • Valores etiquetados, son palabras claves que puede tener un valor anexado, al modo del valor de una propiedad.

TDD, Desarrollo Guiado por Pruebas

TDD (Test Driven Development, o Desarrollo Guiado por Pruebas) es un proceso de desarrollo de software (aunque no veo ningún impedimento para extrapolarlo a cualquier otro proceso de desarrollo). Aunque aporta ventajas medibles y no es muy novedoso, en España ha tenido poco impacto y poca penetración.

Proceso de Desarrollo En Cascada

En realidad no supone un gran cambio respecto al proceso tradicional en cascada, pero el énfasis que pone en dos aspectos claves cambia la forma de ver el ciclo de desarrollo, al igual que una tilde o una coma pueden cambiar completamente el significado de un texto.

Una vez que el equipo de desarrollo y el cliente comprueban todo el valor que aportan a un producto, se preguntarán cómo han podido vivir antes. Pero para llegar a este estado se debe contar con el apoyo claro de la dirección del equipo de desarrollo.

Primero las pruebas [1]

TDD - Core

Tradicionalmente primero se desarrolla y luego se prueba. Este proceso propone intercambiar el orden las cajitas de desarrollo y pruebas para que las pruebas guíen el desarrollo (incluso hay quien dice que deben guiar el diseño) :

  • Primero especificar y desarrollar pruebas que debe superar el producto.
  • Después se desarrolla. Este paso no se da por finalizado hasta que se superan todos los test. Esta es la razón por la que se dice que las pruebas guían el desarrollo.
  • Finalmente se refactoriza (cambiar la estructura interna de un sistema sin afectar a sus funcionalidades) tanto las pruebas como el código para mejorar el diseño interno. Al igual que el paso anterior, esta tarea no se da por completada hasta que se superen todos los test.

Se consigue mejorar la comunicación entre equipos, aumentar la comprensión de lo que realmente se debe desarrollar y al automatizar las pruebas se reduce muy significativamente el tiempo dedicado a pruebas manuales (tanto por el equipo de desarrollo como por el cliente al validar los entregables) a la vez que se aumenta la confianza en el producto.

Entregas frecuentes [2]

Waterfall vs Iterative Waterfall

Por otro lado aconseja la repetición de ciclos de desarrollo muy cortos. En cada iteración (o entrega) se parte de un estado estable y confiable (el código supera todas las pruebas automáticas) y finalizamos en otro estado estable y confiable.

También aporta grades beneficios a nivel global del proyecto. Si debemos especificar menos requisitos y partimos de un prototipo podremos ser más específicos y rigurosos, y las entregas continuas facilitan el control del desarrollo (garantizando que la comprensión del producto es la correcta y mejorando el diseño interno) y en la fase de explotación aumentan el valor del producto reduciendo el tiempo de entrega.

Ajustando los parámetros : cobertura

El precio a pagar por la utilización de este proceso es que desarrollar pruebas significa que hay más código que mantener, más tiempo de desarrollo inicial, más código que probar (las pruebas no tienen por qué estar libre de errores, desarrollamos pruebas para las pruebas? Y también pruebas para las pruebas de las pruebas?…). También es posible encontrar pruebas que no cubran efectivamente el código y/o no han evolucionado mientras el código que cubren sí.

Por tanto se puede recuperar con creces la inversión en el tiempo desarrollo de las pruebas o lastrar el proyecto.  Así, la cobertura de los test, es decir el código que se cubre efectivamente con un test, resulta una decisión crucial para optimizar el proceso y maximizar el ROI de aplicar este proceso.

Sobre la cobertura de los test hay opiniones para todos los gustos, incluso propuestas tan disparatadas como perseguir el 100% de cobertura o valorar la calidad de tu código en función del % de código que está cubierto por test. La opción mayoritaria, que es la que yo recomiendo, es cubrir con test hasta que nuestra confianza en el sistema sea aceptable. Cómo?

  • Recomiendo definir un criterio de validación para cada funcionalidad, automatizar estas pruebas de aceptación así como las posibles APIs (puntos de entrada de terceros a nuestro sistema).
  • Recomiendo utilizar los datos que se utilizaron para definir los requisitos como Datos de Entrada para alimentar las pruebas. Para ello se pueden utilizar Hojas de Cálculo y/o Bases de Datos. Las Hojas de Cálculo son fácilmente entendibles y mantenibles por los propios usuarios, se pueden adjuntar como anexos al contrato del proyecto y pueden ser leídos por un programa informático.
  • Prefiero probar el negocio que los servicios o la persistencia ya que el negocio no puede funcionar si no funciona correctamente el resto.

No más, no menos. Más significa más código a mantener y evolucionar (y posibilidades de error) sin que nos aporte valor, simplemente tardaremos un poco más de tiempo en encontrar exactamente el fallo en un test que no se pasa. Menos significa que nuestro sistema no es confiable.

Para profundizar…

De mi cosecha añadiría también un tercer cambio de paradigma o énfasis. El artefacto entregable es un sistema software (como tradicionalmente lo hemos visto) pero también es un producto (como realmente lo ve un usuario): es importante que el desarrollador lo entienda también como un producto para maximizar el valor del sistema para el cliente y poder reutilizar los máximos componentes posibles.

Los seguidores de TDD recomiendan desarrollar siguiendo los principios KISS (“Keep It Simple, Stupid”, lo simple es bello, efectivo y eficaz) y YAGNI (“You Ain’t Gonna Need It”, no lo vas a necesitar). Yo recomiendo utilizar KISS para todo en esta vida, y YAGNI con mucho cuidado, ya que en ocasiones no seguirlo resulta útil para detectar fallos en el diseño: estás seguro de que no lo vas a necesitar?

[1] Imagen vista en nilclass.

[2] Imagen vista en Agile101. Para ampliar información, consultar The Difference Between Waterfall, Iterative Waterfall, Scrum and Lean Software Development (In Pictures!).

Comenta