Codigo Limpio y Agilidad

¡Fantástico! Todo es ágil hoy en día, todas las empresas aman la agilidad, todo el mundo hace ágil, pero... ¿realmente lo hacen?

Problemas del enfoque en cascada:

Vamos a hablar de la cascada, sí, el chico malo de la ciudad, el que todo el mundo odia. La idea principal de la metodología en cascada es:

Waterfall cycle

  1. Obtenemos los requisitos, lo que queremos hacer, las necesidades de los interesados.
  2. Se planifica cómo hacerlo, a menudo con un análisis y diseño de la solución.
  3. Se implementa, creando software funcional.
  4. Se entrega el software y se espera el feedback, creando documentación durante el proceso.
  5. Mantenimiento de la solución funcional.

Una vez que entregamos la solución, pedimos feedback y si necesitamos cambiar algo, comenzamos todo el proceso nuevamente.

Esto es genial siempre y cuando los requisitos sean super estables y sepamos que no cambiarán durante la implementación, algo que en el mundo real es prácticamente imposible ya que SIEMPRE cambian.

Si fuéramos una tela, no tendríamos ninguno de estos problemas, ya que sabemos los materiales específicos necesarios para crear algo, los colocamos en la máquina, y el resultado siempre será el mismo.

Pero trabajamos con soluciones de software para satisfacer las necesidades de las personas, y estas siempre cambian y evolucionan. Aquí viene el mayor problema con la metodología en cascada. Tenemos que esperar hasta el final de la implementación para recibir comentarios y luego comenzar todo el proceso nuevamente, ¿y qué pasa si las necesidades del usuario han cambiado en el ínterin? Acabamos de desperdiciar mucho tiempo útil.

Una gran analogía es la del piloto de un avión, le gustaría ser informado lo antes posible si hay algún problema con el avión y no esperar hasta que el motor falle, o peor aún, el avión se estrelle para recibir una notificación.

¿Por qué Agile?

Bueno, aquí está el punto: un proyecto es una sucesión siempre en evolución de eventos, ¡por ejemplo, el análisis nunca termina! Por lo tanto, recibir comentarios lo antes posible es la clave principal de la metodología Agile. Aquí, buscamos la participación de los interesados en todo el proceso, entregando la mínima cantidad de funcionalidades esperando una respuesta, esperando que sea positiva; y si es negativa, no hay problema, podemos abordarla lo antes posible sin tener que esperar al fin del mundo para hacerlo.

Entonces, la forma en que las empresas suelen usar Agile es la siguiente:

Agile cycle

  1. Continuamente obtenemos requisitos, y al trabajar en pequeñas funcionalidades, podemos elegir cuáles son las necesidades más críticas e implementar un plan de acción para entregar pequeñas partes que puedan satisfacer este objetivo.

  2. Continuamos haciendo un análisis de los requisitos para preparar el trabajo futuro. La cantidad corresponderá al marco de tiempo que ya decidimos según las necesidades.

  3. Ahora, con todo preparado, estamos cómodos para comenzar a trabajar en nuestras tareas dentro de un sprint. Representa el marco de tiempo que decidimos en el que nos comprometemos a entregar una cierta cantidad de trabajo y puede ser variable según la necesidad.

  4. Entregamos las funcionalidades y continuamos con el proceso nuevamente. La principal diferencia es que comenzamos el trabajo con comentarios de los interesados de la iteración anterior.

Podemos fallar en la entrega dentro de un marco de tiempo ajustado, y eso no es un problema al principio, ya que medimos el equipo y recopilamos comentarios para ajustarnos al siguiente sprint. Después de algunas iteraciones, podemos estimar la cantidad correcta de trabajo que el equipo puede entregar en un cierto contexto.

Por qué piensas que estás haciendo agile pero en realidad ... no lo estás haciendo

Esto es normal, piensas que estás haciendo agile porque tienes reuniones diarias y eso hace que el equipo sea ágil, pero en realidad esa es solo una ceremonia de muchas. No se puede definir si se es ágil o no por las ceremonias que tienen lugar dentro del proyecto, ya que es más bien una forma de pensar.

Puede que estés trabajando en sprints, utilizando scrum, haciendo retrospectivas y todas esas cosas increíbles, pero también puede que estés trabajando en características enormes, no entregando en cada sprint, no aceptando ningún cambio hasta que termines tu trabajo o incluso siendo dueño de tu conocimiento y no compartiéndolo con el equipo. Si te encuentras en cualquiera de estos últimos puntos ... eh ... no estás haciendo agile.

Programación Extrema

Esta es una práctica impresionante que, según R.C. Martin, cofundador del manifiesto ágil y autor de Clean Agile, es la verdadera esencia del mismo.

Consiste en la organización de prácticas en tres anillos llamados el "Círculo de la Vida". Cada anillo representa un aspecto diferente del desarrollo de proyectos:

Extreme Programing

El "Anillo Exterior" representa el aspecto empresarial y contiene todas las prácticas enfocadas en los negocios que, juntas, crean el ambiente perfecto para el desarrollo de proyectos.

  • Juego de Planificación: agarrar el proyecto y dividirlo en piezas más pequeñas para una mejor comprensión y organización. Características, historias, tareas, etc.

  • Pequeñas Liberaciones: aquí es donde viene lo que estaba diciendo acerca de atacar una funcionalidad completa de una sola vez y cómo eso podría llevar a un enfoque en cascada a algo que debería ser ágil. Siempre debemos tratar de identificar y priorizar las piezas de valor más pequeñas y trabajar en torno a ellas, siendo el trabajo que queremos entregar lo antes posible. Cuanto más pequeña sea la pieza, más rápido recibiremos comentarios y actuaremos en consecuencia.

  • Pruebas de Aceptación: ahora esto es fácil de entender pero difícil de implementar, necesitamos trabajar en lo que consideramos como equipo como "hecho", ¿cuáles son los requisitos para decir realmente que algo se ha completado por completo, o al menos en los límites acordados? Una recomendación es pensar de nuevo en el valor mínimo que queremos y podemos atacar con el equipo proporcionado. Si tomamos algo realmente grande, será difícil de implementar ya que debemos considerar demasiadas cosas, lo que resulta en requisitos omitidos, definiciones vagas y mala comunicación. Considere crear funcionalidades que tengan un inicio y un final claros, el resultado debe ser algo que proporcione valor por sí solo.

  • Todo el Equipo: dentro de un equipo de proyecto adecuado, cada miembro proporciona una cierta funcionalidad, tenemos nuestros front-ends, back-ends, diseñadores, dueños de productos, gerentes de proyectos, etc. El problema principal siempre es el mismo, ¿cómo comunicar el trabajo cuando es tan diferente y, al mismo tiempo, depende de cada uno (volveremos a este punto en un momento)?

El "Anillo Intermedio" representa el aspecto del equipo y contiene todas las prácticas enfocadas en el equipo para mejorar la colaboración e interacción del equipo:

  • Ritmo Sostenible: si preguntas, ¿cuándo quieres que se haga esto? ¡el resultado siempre será ... bueno, lo antes posible! Y, por supuesto, eso es realmente difícil de hacer, no porque el equipo no pueda hacerlo, la mayoría de ellos puede, el problema es hacerlo cada vez manteniendo el mismo ritmo, es imposible. Su equipo se quemará en la tercera o cuarta iteración y luego no se hará ningún trabajo, la velocidad de entrega se reducirá en gran medida. Nuevamente, piense en funcionalidades pequeñas y contenidas que puedan entregarse a una velocidad cómoda.

  • Propiedad Colectiva: ¿Cuántas veces tienes que preguntarle a tus compañeros de equipo de qué está hablando el dueño del producto en una reunión porque no tienes la cantidad correcta de contexto? No estoy hablando de esas veces en las que estás jugando durante la reunión diaria, sino del torbellino que succiona toda la información que debería compartirse con el equipo y parece que nadie sabe lo que está sucediendo porque nunca se les informó. Este es un problema muy conocido en las empresas, la información se comparte en privado, por lo que solo las personas que estaban en la conversación saben lo que está sucediendo, y más tarde intentan comunicar lo mejor posible el resultado con el resto del equipo, pero en el proceso crean un juego telefónico roto. El proyecto necesita tener una estrategia de comunicación para enfrentar este tipo de situación.

  • Integración Continua: Como programadores, deberíamos estar comprometidos a hacer cambios en el código lo más rápido posible, sin dejar pasar ni un solo día sin nuevas modificaciones en el repositorio. Siempre existe la posibilidad de trabajar juntos en una funcionalidad y no comunicar los esfuerzos entre sí. Por ejemplo, alguien puede crear un método que hace exactamente lo mismo que otro que ya fue creado por un compañero, pero no se informó al respecto. Hacer cambios lo más rápido posible entregará comentarios a tus compañeros de equipo y los mantendrá actualizados para que siempre estén trabajando en los "últimos cambios". Si esperamos para entregar nuevo código después de que se complete la funcionalidad, entraremos nuevamente en el territorio de la metodología de cascada.

  • Metáfora: Si vamos a trabajar juntos en un proyecto, todos debemos entender el contexto de la misma manera, teniendo definiciones claras de cada elemento. Tener el mismo nombre exacto para describir un elemento en particular brindará un mayor nivel de comprensión del equipo y evitará la confusión que podría generarse al referirse al mismo elemento de más de una manera. Podría pensar en esto como si cada proyecto fuera un país diferente, algunos de ellos se comunican en los mismos idiomas exactos, pero usan diferentes metáforas. Por ejemplo, Estados Unidos y Londres usan ambos el inglés como su idioma principal, pero para representar estar molesto, el inglés americano usa "disappointed" y el inglés británico usa "gutted".

El "Anillo Interior" representa el aspecto técnico, que contiene todas las prácticas relacionadas con la mejora del trabajo técnico.

  • Programación en Pareja: Sentarse juntos para resolver un problema no solo hará que se llegue a una solución más rápido, sino que al mismo tiempo estás compartiendo tu punto de vista con tus compañeros y también ganando el suyo, con el beneficio adicional de llegar a un terreno medio y crear un conjunto de convenciones que el equipo seguirá después. La comunicación es clave cuando se trabaja en equipo, y tener la posibilidad de trabajar juntos para resolver un problema traerá retroalimentación y contexto en torno a la implementación.

  • Simple Design: Una vez más, trabajemos en pequeñas cosas. Ya hemos hablado de esto antes, pero aquí hay un pequeño consejo: si queremos agregar una cierta funcionalidad que representa un gran desafío para el equipo, siempre debemos buscar una forma de proporcionar la misma cantidad de valor al dar una alternativa mucho más fácil. A veces nos desafiamos a nosotros mismos y entregamos una proposición de valor realmente compleja pero hermosa, pero el problema es que tal vez esa proposición no llegue a ninguna parte porque los requisitos cambian y podemos descubrir que en realidad el usuario no la quiere, por eso trabajar de la manera más simple posible es la mejor opción. Siempre se puede proporcionar una solución simple pero elegante para encontrar el valor adecuado y luego iterar algo mejor.

  • Refactorización: Todos amamos la frase "si funciona, no lo toques", pero ese no es el enfoque correcto ya que entraremos en una espiral de código heredado al reutilizar código que ya no se puede mantener. Necesitamos refactorizar tanto como sea posible. La deuda técnica es prácticamente inevitable, siempre generamos algún código de mala calidad debido a las restricciones de tiempo de los plazos al implementar la solución rápida pero no tan correcta. Una buena manera de lidiar con esto es utilizar una parte del inicio o el final del sprint, según la prioridad, para refactorizar el código, y esto también se puede hacer utilizando la programación en parejas para aprovechar los beneficios descritos anteriormente.

  • Desarrollo guiado por pruebas: Hablaremos más sobre esto más adelante, pero podemos definirlo como un proceso en el que escribimos nuestras pruebas antes de codificar una sola línea. La idea principal es que los requisitos de la tarea definan las pruebas que queremos realizar y, como resultado, guíen lo que codificamos. Puede ser un gran aliado al refactorizar, como veremos más adelante.

TDD

TODO el mundo odia hacer pruebas, por ejemplo, los clientes odian PAGAR a las empresas por el "desperdicio" de tiempo de sus desarrolladores Front-End haciendo pruebas, y al final... dinero. Entonces, ¿por qué nosotros, los "desperdiciadores" de tiempo y dinero, deberíamos querer implementar pruebas, verdad?

Bueno, hay algunas cosas en mi mente que pueden compensar todo ese odio:

  1. Calidad del código
  2. Mantenimiento del código
  3. Velocidad de programación (sí, estás leyendo este punto correctamente)

Comprensible, ¿verdad? Escribimos pruebas para que pasen los casos de uso, para escribirlas debemos estar organizados porque si no... será imposible probar nuestro código. Pero hay cosas que considerar, ¿cómo escribimos pruebas de manera que realmente aumente la calidad de nuestro código de alguna manera significativa?

Primero, veamos qué significa calidad de código, y luego les diré lo que significa para MÍ la calidad del código.

Si buscas una respuesta, esta es la que encontrarás:

"Un código de calidad es uno que es claro, simple, bien probado, libre de errores, refactorizado, documentado y con buen rendimiento".

Ahora bien, la medida de la calidad depende de los requisitos de la empresa y los puntos clave suelen ser la confiabilidad, mantenibilidad, testabilidad, portabilidad y reutilización. Es realmente difícil crear código con un 100% de calidad, incluso Walter White no pudo crear metanfetamina con más del 99,1% de pureza; surgirán problemas de desarrollo, plazos y otras situaciones de contexto y tiempo que pondrán en peligro la calidad de tu código.

No puedes escribir código legible, mantenible, probable, portable y reutilizable si te apresuran a terminar una tarea de 4 puntos de historia en solo una mañana (realmente espero que no sea tu caso, y si lo es... ¡tú puedes!)

Entonces, aquí va lo que significa calidad de código para mí. Hacerlo es una mezcla de hacer lo mejor con las herramientas actuales, buenas prácticas y experiencia, contra los límites de contexto existentes para crear el código más limpio posible. Mi recomendación a todos mis estudiantes es que primero alcancen el objetivo y luego, si tienen tiempo, lo usen para mejorar la calidad tanto como sea posible. Es mejor entregar algo feo que una funcionalidad incompleta, pero hermosa. La calidad de tu código aumentará con tu experiencia en el camino, a medida que ganes más, sabrás los mejores pasos para alcanzar un objetivo en la menor cantidad de tiempo y con las mejores prácticas.

El código de calidad también se relaciona con el nivel de comunicación que puedes proporcionar a tus compañeros de equipo o cualquier persona con una simple mirada. Es fácil ver un código y decir "¡wow, esto es genial!" y también decir "¡wow, qué desorden!". Así que cuando codificas, debes pensar que no eres el único trabajando en ello, incluso si trabajas solo como un ejército de desarrollador único, eso ayudará mucho.

Permíteme darte algunas herramientas para escribir un mejor código, primero abramos un poco tu mente.

Diseño atómico, punto de vista de Front End

Separa tu código en la cantidad mínima de lógica posible, cuanto más pequeño sea el código, más fácil será de probar. Esto también trae más beneficios, como la reutilización del código, un mejor mantenimiento e incluso un mejor rendimiento; a medida que el código se vuelve más pequeño y mejor organizado y dependiendo del lenguaje / marco que usemos, podríamos terminar con menos ciclos de procesamiento.

El mantenimiento mejorará enormemente, ya que estamos codificando pequeñas piezas de trabajo, cada una con el acoplamiento más suelto y la cohesión más alta posible, podemos rastrear y modificar el código con el mínimo número de problemas.

Déjame mostrarte cómo pensar atómicamente y cómo puedes llegar a una aplicación completa a partir de una pequeña entrada.

Primero tenemos nuestra entrada:

Input example

Cosas simples, eso es lo que llamamos un Átomo, la mínima pieza de lógica posible. Si lo codificas de forma atómica, puedes reutilizar esta entrada en cualquier lugar de tu aplicación y, más adelante, si necesitas modificar su comportamiento o apariencia, simplemente modificas un pequeño átomo con el resultado de tener un impacto en toda la aplicación.

Ahora, supongamos que agregas una etiqueta a esa entrada:

Input with label example

¡Felicidades! Ahora tienes lo que se llama una Molécula, la mezcla entre átomos, en este caso una etiqueta y una entrada. Podemos seguir avanzando y reduciendo la granularidad.

Podemos usar la entrada con la etiqueta dentro de un Formulario para crear un Organismo, la mezcla entre moléculas:

Organism example

Si mezclamos Organismos, obtendremos una Plantilla:

Template example

Y una colección de plantillas crea nuestra Página, y luego, usando la misma lógica, nuestra Aplicación.

Usar esta forma de pensar hará que tu código sea realmente mantenible, fácil de navegar para rastrear errores y, más que nada... ¡fácil de probar!

Si escribes algo que no sea un Átomo, sería muy difícil probar cualquier cosa, ya que la lógica tendría un alto acoplamiento y, por lo tanto, sería imposible separarla lo suficiente como para verificar casos específicos.

Un ejemplo sería probar un código altamente acoplado, para validar solo una cosa simple, uno tendría que comenzar a incluir una pieza de código... y luego otra... y otra, y después de que termine, verá que incluyó casi todo el código porque había demasiadas dependencias de un lugar a otro.

Y esa es la clave para incluir un jugador más valioso en todo esto.

Programación Funcional

La programación funcional es un paradigma que especifica formas de programar de manera que dividimos nuestra lógica en métodos declarativos, sin efectos secundarios. De nuevo...piensa de manera atómica.

Cuando comenzamos a aprender a programar, normalmente lo hacemos de manera imperativa, donde la prioridad es el objetivo y no la forma en que lo alcanzamos. Aunque es más rápido que la programación funcional, que lo es, puede traer muchos dolores de cabeza aparte de dejar de lado todos los beneficios de la otra forma.

Escribamos una comparación en JavaScript.

Forma imperativa de buscar un elemento dentro de un array:

var exampleArray = [
	{ name: 'Alan', age: 26 },
	{ name: 'Axel', age: 23 },
];

function searchObject(name) {
	var foundObject = null;

	var index = 0;

	while (!foundObject && exampleArray.length > index) {
		if (exampleArray[index].name === name) {
			foundObject = exampleArray[index];
		}

		index += 1;
	}

	return foundObject;
}

console.log(searchObject('Alan'));

// { name: 'Alan', age: 26 }

Y ahora la manera funcional de alcanzar el mismo objetivo:

const result = exampleArrayMap.find(element => element.name === name);

console.log(result);

No solo es más corto, sino que también es escalable. El método map que estamos aplicando al array es declarativo de ECMAScript, lo que significa que cada vez que ejecutamos el método con los mismos parámetros, siempre obtendremos el mismo resultado. Tampoco modificaremos nada fuera del método, eso es lo que se llama efectos secundarios, el método devuelve un nuevo array con los elementos que cumplen la condición.

Por lo tanto, si creamos métodos que representen las unidades mínimas de lógica posibles, podemos reutilizar el código funcional y probado en toda la aplicación y mantenerlo si es necesario. Una vez más... piensa de manera atómica.

Ahora que sabemos la forma de pensar para crear un código de alta calidad y fácil mantenimiento, pasemos a lo que es una Historia de Usuario.

Historia de Usuario y TDD

¡Vaya título! Todos conocen las historias de usuario, cómo definirlas, qué hacer con ellas, pero nadie sigue la misma forma de escribirlas o incluso su estructura.

Una historia de usuario es la explicación de una funcionalidad desde el punto de vista del usuario. Normalmente se ve así:

¿Qué? Como usuario (quién), quiero tener la posibilidad de... (qué) para... (por qué)

¿Cómo? (casos de uso) 1- paso 1 2- paso 2 3- paso 3 ...

Como puedes ver, definimos quién...el usuario, lo que quiere hacer...la funcionalidad, por qué queremos esta funcionalidad...lo que mientras lo escribimos incluso podemos descubrir que no tiene sentido crearla porque el objetivo no está claro, y cómo la crearemos...los casos de uso.

Los casos de uso representan el número de requisitos que necesitamos cumplir para decir claramente que una historia de usuario está terminada, normalmente cuentan la historia del camino feliz a seguir. También hay lugares donde se describen las entidades relacionadas con la historia de usuario y los casos extremos (camino triste) y creo que es una práctica realmente buena, pero al igual que al escribir código de alta calidad...necesitamos identificar los límites de nuestro contexto para ver cómo podemos escribir el contenido lo más específico posible sin transformar nuestra tarea en un documento difícil de seguir y consumir.

Ahora, TDD, Desarrollo Guiado por Pruebas, es un proceso donde definimos nuestras pruebas antes incluso de escribir una sola línea de código, así que...¿cómo probamos algo que ni siquiera está creado? Bueno, esa es la magia de esto, puedes tomar tus casos de uso y definir lo que necesitas para cumplir cada uno de ellos, crear pruebas alrededor de ellos, hacer que fallen y luego arreglarlos para que pasen...así de simple.

La idea principal detrás del TDD es pensar en:

  1. ¿Qué quieres hacer?
  2. ¿Cuáles son los requisitos principales?
  3. Escribir pruebas que fallarán a su alrededor.
  4. Crear tu código sabiendo lo que quieres hacer.
  5. Hacer que la prueba pase.

Si piensas que las pruebas son consumidoras de tiempo, bueno, lo son, pero porque tal vez lo hayas hecho previamente de la manera clásica y mala de codificar todo primero y luego intentar probar tu código. ¿Recuerdas de lo que hablábamos sobre la calidad del código, las buenas prácticas, etc.? Bueno, esos son los elementos principales que te ayudarán a probar tu código y si no los estás implementando correctamente, terminaremos con una funcionalidad imposible de separar y probar.

Es por eso que codificar sabiendo lo que quieres probar, utilizando la programación funcional y una forma de pensar atómica puede ser tan beneficioso, porque estarás creando lógica, identificando los requisitos y al final...aumentando la velocidad de codificación.

Así que aquí está, la prueba también ayuda a aumentar la velocidad de codificación, a medida que escribes código más manejable, es más fácil modificar un requisito (caso de uso) de tu funcionalidad, ya que lo has identificado mediante una prueba que te dirá si tu refactorización fue correcta. También reduce la posibilidad de errores, por lo que se dedica menos tiempo a solucionar problemas más adelante.

Flujo del TDD:

TDD flow

Aquí hay un flujo TDD sobre cómo mejorar la calidad del código sin romper nada:

TDD code quality improvement