3º Nivel: Automatización de operaciones
Principio ágil
Las mejores arquitecturas, requisitos y diseños emergen a través de equipos autoorganizados
Como en este nivel y el siguiente, en el que el equipo se tendrá que dividir para superar los niveles.
Descripción
Antes de desplegar a la infraestructura virtual, el código tiene que ser testeado, porque código que no está probado, está roto. El crear los tests y, eventualmente, una forma automática de ejecutarlos es un paso previo a llevar a cabo tareas de integración y despliegue continuo (que se verán a continuación).
Prerrequisitos
Haber superado al menos los tests del nivel anterior; haber organizado el equipo para abordar de forma simultánea este nivel y el siguiente.
Conceptos fundamentales
En este caso se trata más de herramientas que de conceptos, pero aún así hay algunos conceptos que es conveniente entender
- El concepto de lenguaje de serialización, usado para representar en texto estructuras de datos. En estos lenguajes se encuentra el XML, usado de forma extensiva en el ecosistema Java.
- El concepto de DSL, o Domain Specific Language: son lenguajes de programación limitados por su necesidad de servir a un fin específico, como definir tareas en un proyecto.
- Es conveniente aprender nociones básicas del lenguaje del shell, con el cual se pueden escribir programas cortos (denominados scripts). La mayor parte de los gestores de tareas necesitarán lanzar comandos, y a veces incluir pequeños programas de una línea que usen variables, por ejemplo, o secuenciar varias órdenes.
TL;DR
En ingeniería del software hay que aprender a tomar decisiones técnicas sobre las herramientas, servicios y bibliotecas que se van a usar en el proyecto, y a justificarlas documentalmente. Esto es lo que se busca en este objetivo: adoptar una cultura de tomar decisiones objetivas, basadas en el proyecto, y justificarlas.
Explicación
Una guía de cómo abordar la consecución de este objetivo.
Ingeniería == tomar decisiones
La ingeniería del software consiste en resolver problemas, y muchos de esos problemas van a ser qué (otro) software se va a usar para resolverlo. En general, a partir de una historia de usuario se plantean muchos problemas; alcanzar un producto mínimamente viable requiere identificar cuales son esos problemas y crear un marco para tomar una serie de decisiones que sean, a la larga, si no las mejores (nunca hay mejores soluciones en ingeniería de software) sí suficientemente razonables para el proyecto con vistas al siguiente producto mínimamente viable y al mantenimiento, a medio plazo, del proyecto.
El mantenimiento a largo plazo es un problema totalmente diferente.
Ya se ha tomado al menos una decisión, cuál es el lenguaje del proyecto, y esperamos que se haya justificado lo suficiente. Pero a partir de ahora, casi en cada objetivo habrá que decidir qué se va a usar, siguiendo una serie de criterios más o menos generales
- Los estándares que haya en cada lenguaje o aspecto específico del mismo. Si hay un estándar, primero hay que conocerlo; a continuación simplemente hay que seguirlo y adaptar el resto de los criterios al mismo.
- Las mejores prácticas: en cada lenguaje evolucionan continuamente; estas aconsejan cuál es la herramienta más adecuada o al menos qué tipo de herramienta se considera la más adecuada; en el momento de iniciar un proyecto y tomar una decisión es cuando hay que ponerse a buscar de qué se tratan. En muchos casos no te darán una sola herramienta, sino un conjunto de herramientas, pero hay otros criterios para elegir.
- La deuda técnica. La deuda técnica es lo que hay que “pagar” para poder avanzar en un proyecto; es, en general, todo tipo de código difícil de mantener que fuerce al equipo a pagar en su tiempo (y sudores). Son deudas que se incurren cuando se adopta una solución rápida a un problema sin pararse a pensarla mucho, cómo simplemente escoger el marco MVC que sea más popular en cada momento. En muchos proyectos no queda otro remedio que incurrir en esa deuda técnica, y la decisión estará influida por eso. Sin embargo, en un proyecto recién comenzado, hay que intentar no tomar una decisión que aumente la deuda técnica; por ejemplo, si elegimos una herramienta que no se mantiene con asiduidad estaremos aumentando la deuda técnica porque el grupo tendrá que seguirla usando, sin más remedio, y más adelante tendrá que pagar refactorizando para usar otra herramienta. En general, las mejores prácticas asegurarán que no aumenta la deuda técnica, pero en algunos casos, si hay varias elecciones posibles, habrá que tenerla también en cuenta.
- Criterios objetivos adicionales: sobre todo, seguridad, prestaciones o algo que se pueda reducir a la cantidad de tiempo que se va a invertir en mantener lo relacionado con la decisión. La mayoría de las herramientas tendrán benchmarks que las comparen con otras; pero siempre se puede intentar hacer pruebas sobre el propio proyecto. Si miramos a este objetivo en particular, habrá gestores de dependencias que funcionen mejor con muchas dependencias, otras que funcionen mejor con pocas dependencias. ¿Cuantas dependencias tiene nuestro proyecto? En función de eso lo decidimos.
- Debemos tratar de evitar criterios subjetivos: familiaridad, “buena documentación”, “popularidad” (si uno ha oído hablar mucho de él, no tiene por qué ser mejor). Herramientas con las que se está muy familiarizado, como
jQuery
, ya no se consideran el estado del arte (desde hace muchos años, además). Y otras comobootstrap
son bastante discutibles.
En cualquier caso, esta toma de decisiones y su documentación es lo esencial a la hora de alcanzar este objetivo (y la mayoría de los objetivos sucesivos). No es importante que se haya elegido uno y no otro. Es más, si es una mala decisión se puede cambiar más adelante, en función de la evolución del proyecto. Lo importantes es que el estudiante adquiera y ponga en práctica la costumbre de tomar decisiones informadas y justificar las mismas.
En ese sentido, el estudiante debe esforzarse en tratar de hacer su propia elección y no inspirarse en la hecha y ya aceptada para otro estudiante. Dado que lo importante es el aprendizaje del estado del arte y la buena documentación de la toma de decisiones, el hecho de que tome la misma decisión que otra persona (y además lo documente de la misma forma) indica exactamente lo contrario, que no ha tomado esa decisión ni ha aprendido a documentarla y por tanto no ha superado el objetivo.
El objetivo que nos ocupa
En todo proyecto hay una serie de tareas que se llevan a cabo de forma repetitiva; estas tareas van desde las más obvias, que es ejecutar el programa o ejecutar los tests (lo que haremos más adelante) hasta instalar dependencias.
Incluso, en ciertos lenguajes, hay multitud de compiladores o intérpretes que se van a usar en el desarrollo y en la producción; no necesariamente tienen que ser los mismos. Muchos, o la mayoría, de los lenguajes actuales están regidos por estándares, lo que significa que cualquier empresa o comunidad de usuarios puede crear su propia versión del lenguaje, siempre que cumpla los estándares. En el mundo Java, por ejemplo, hay implementaciones libres de la máquina virtual, así como las oficiales de Oracle; en JavaScript ahora mismo hay al menos tres intérpretes de línea de órdenes populares: node.js
, deno
y bun
, aparte de otros más específicos (y los del navegador, claro). A este runtime, intérprete o compilador, habrá que prestarle atención y tomar la primera decisión, porque esta determinará el resto de las decisiones que se tomen, en general.
Una vez elegido esto (o si hay una única versión disponible, tomando lo que haya), hay que tomar una decisión sobre cómo instalar las dependencias. El concepto de dependencias es relativamente moderno y está relacionado con todas las bibliotecas y otros servicios, generalmente libres, que se necesitan para pasar el código a producción. Todo lenguaje tendrá una biblioteca estándar, que incluirá servicios habituales, como trabajar con el sistema de ficheros, pero también, dependiendo del lenguaje, características más avanzadas como trabajar con varios procesadores a la vez o servir páginas web. Pero muchos otros servicios necesitarán bibliotecas externas, que se tendrán que instalar siguiendo, generalmente, un fichero que especificará cómo hacerlo. Pues bien, en muchos lenguajes hay diferentes posibilidades para especificar esas dependencias, y una vez establecidas esas posibilidades, puede haber también diferentes herramientas que sean capaces de analizar esos ficheros e instalar las dependencias que se necesitan, junto todas las demás librerías que son prerrequisitos para las mismas, resolviendo posibles conflictos también (de versiones, por ejemplo) de la mejor forma posible. En la mayoría de los lenguajes, las bibliotecas que efectivamente se han instalado y cómo se han resuelto esos conflictos se registra en un fichero que se denomina lockfile; como por ejemplo, el package-lock.json
que se usa node.js
junto con npm
.
En general, tanto expresar estos requisitos como interpretar tal fichero e instalarlos necesitan un gestor de dependencias y diferentes lenguajes se comportan de forma diferente en la forma de proporcionar uno. En el caso más general, sin embargo, habrá diferentes gestores de dependencias para elegir.
Pero instalar las dependencias es solo una de las tareas que hay que hacer para pasar a producción una aplicación; pero hay muchas otras que también requerirán automatización. Por ejemplo, la ejecución de los tests usando las herramientas de construcción o de gestión de tareas del lenguaje que se esté usando; por ejemplo, incluir un objetivo make test
dentro de un Makefile
. Normalmente, estas tareas u órdenes son estándar para cada lenguaje y generalmente también Travis (o el sistema que elijáis) las ejecutará automáticamente salvo que le digas lo contrario. En algunos casos, como Python, las herramientas de construcción como poetry
solo necesitarán un fichero pyproject.toml
que instale las dependencias, si las hubiera.
Sin embargo, en otros lenguajes el fichero de tareas (task runner o task manager) correspondiente permitirá especificar dependencias, versión del lenguaje, instrucción específica que se va a usar para hacer test y también metadatos adicionales como licencia o autor. En general, hay un continuo que va desde los gestores de dependencias hasta los gestores de tareas pasando por otros que son herramientas de construcción, por ejemplo. En muchos casos un gestor de dependencias completo puede hacer bastantes tareas, por eso puede ser preferible a usar el mismo + un gestor de tareas, ya que en ese caso habría que incluir una dependencia adicional.
En muchos lenguajes modernos, el gestor de dependencias es, primero, una herramienta independiente del compilador o intérprete principal, y, segundo, no especificada por el mismo, habiendo por tanto diferentes opciones a elegir entre todas las que siguen la especificación principal (que suele ser el formato de especificación de tales dependencias). Por ejemplo, Python y la mayoría de los intérpretes de Javascript especifican el formato de las dependencias, el fichero pyproject.toml
y package.json
respectivamente, pero no qué programa tiene que trabajar con él.
Pero sí los estándares a los que se acogen, porque siempre hay que conocer los estándares.
En este caso habrá que establecer los criterios para elegir uno de ellos sobre otros y justificar la elección que eventualmente se tome.
En algunos casos este gestor de dependencias elegido también podrá ejecutar para el usuario una serie de tareas con alguna limitación; justificar que se elija una herramienta específica para ambas cosas también tendrá que hacerse, como es natural.
Finalmente, todo cambio en el repositorio debe, eventualmente, ser parte de un producto mínimamente viable que va a recibir el cliente; por eso hay que incluir este tipo de instrumentación en algún milestone.
Recursos adicionales
El material del curso cero incluye varios temas relacionados con el desarrollo basado en test. Se recomienda encarecidamente que se entiendan los conceptos, antes de proceder a hacer el trabajo relativo a este objetivo en si.
- Sobre los task runners o gestores de tareas.
- Sobre los gestores de dependencias.
- Sobre los criterios de elección.
Lista de comprobación
Preguntas antes de entregar que tienes que hacerte, y copiar en tu PR marcando todo lo que se aplique:
* [ ] ¿Incluye el fichero `is.yaml` las claves de todos los objetivos
anteriores?
* [ ] ¿Se ha desarrollado el fichero para las tareas a partir de un
issue?
* [ ] ¿Ese issue está asignado a un milestone?
* [ ] ¿El milestone incluye, entre sus productos mínimamente viables, la
existencia de herramientas que permitan automatizar tareas?
* [ ] ¿Hemos justificado correctamente en caso de elegir un gestor de
dependencias el que se use como gestor de tareas?
* [ ] ¿El PR, que también es un issue, está asignado al mismo milestone?
* [ ] ¿La tarea `check` efectivamente comprueba la sintaxis de los fuentes
existentes y sólo eso?
* [ ] ¿Se ha comprobado que las instrucciones incluidas en el `README.md` son
completas y correctas?
Entrega de este nivel del proyecto
Se tendrá que haber actualizado el repositorio que se usara en los niveles anteriores y añadir al fichero de este objetivo el enlace al PR para a continuación hacer un pull request.
La descripción del proyecto tiene que seguir progresando, así que el README.md
tiene que incluir una descripción real de la clase que se vaya a comprobar y qué uso va a tener dentro del servicio (o servicios) web que se van a desplegar más adelante. En un proyecto el README.md debe estar escrito para la persona que llegue, y no para que se corrija. Si hay documentación adicional, tendrá que enlazarse desde este README.md
.
Habrá que añadir al fichero is.yaml
la clave automatizar
, que tendrá las siguientes subclaves:
fichero
apuntará al fichero que se va a usar, a partir de ahora, para ejecutar diferentes tareas. En ese fichero, que será específico del lenguaje en algunos casos (en otros será genérico, como un Makefile), se tendrán que ir poniendo todas las tareas que se hagan en el proyecto, empezando pormake install
omake installdeps
y continuando, en este mismo, conmake test
.orden
dirá qué hay que usar en la línea de órdenes para ejecutarlo. Se comprobará, en este objetivo, que en elREADME.md
existe la cadenaorden check
explicando cómo comprobar la sintaxis de la entidad o entidades que se hayan programado hasta ese momento.
Entre las tareas que estén automatizadas, se tendrá que incluir necesariamente la que corresponde al objetivo anterior, es decir, comprobar que la sintaxis de los fuentes que haya hasta ese momento sean correctos. Se puede incluir alguna adicional, siempre dentro del producto mínimamente viable en el que se esté trabajando.
Superación del nivel
Se consigue si se han configurado correctamente las herramientas de construcción y justificado mínimamente su elección en un documento aparte.
A donde ir desde aquí
Las decisiones tomadas, como en todos los niveles desde el 2, tienen que registrarse en los documentos de diseño de arquitectura correspondientes.
Una vez hecho esto, se puede proceder directamente al nivel 5, ya que el nivel 4 estará posiblemente ya aceptado. Antes de ir, una reunión final de este sprint que establezca qué ha funcionado bien y qué no lo ha hecho ayudará a establecer una base sólida de trabajo para los siguientes niveles.