Workshop Software Engineering Education for the Next Generation

He sido invitado a ser parte del comité del programa del workshop «Software Engineering Education for the Next Generation» y como tal estoy colaborando en la difusión.


Un detalle importante a mencionar es que se trata de un workshop académico, o sea: no es típico de taller que podemos encontrar en el contexto de una conferencia de industria como podría ser Nerdearla o la RubyConf. Sino que se trata de un workshop en el contexto de una conferencia académica, en este caso particular la conferencia es ICSE (International Conference on Software Engineering). En este tipo de workshops se presentan artículos que son previamente evaluados por pares (los miembros del comité del programa) y luego se trabaja colaborativamente sobre las temáticas planteadas en los artículos.


Esta es la cuarta edición del edición del workshop, yo participé como autor en 2017 y personalmente creo que es un espacio interesante para participar y publicar:

  • Al ser un espacio en formato workshop, además de la exposición de los artículos, hay un espacio interactivo de intercambio, colaboración, debate y hasta co-creación de contenido/materiales
  • La convocatoria (CFP) incluye obviamente trabajos de investigación pero también reportes de experiencia y artículos de posicionamiento (position papers).
  • Es parte de ICSE, lo cual le da una gran exposición, pero al mismo tiempo, al ser un workshop «la vara de entrada» no es tan exigente como en el track de educación de ICSE.
  • Debido a las condiciones de pandemia, esta edición se realizará en formato virtual, con lo cual no hace falta viajar para participar.
  • El deadline para el envió de artículos es el 14 de enero, se que es poco tiempo para escribir algo desde cero, pero tal vez quien algo escrito a medias y esta pueda ser una buena opción para redondear y publicar.

Notas sobre Clean Architecture

Apenas Bob Martin publicó su libro Clean Architecture allá por 2017, leí el índice y me pareció que ya conocía todos los temas tratados. Sin embargo, hace un par de meses, sin haber cambiado de opinión decidí igualmente leerlo. Ayer lo terminé.

Efectivamente considero que el libro no me trajo nada nuevo a mi. O sea, he leído mucho sobre arquitectura de software y tal vez por eso no encontré nada revelador. Sin embargo creo que el libro puede resultar muy valioso para muchos lectores porque de hecho creo que es un muy buen resumen de temas con un enfoque muy concreto y pragmático. Spoiler: la idea central de Clean Architecture es lo que hace unos 20 años Alistair Cockburn bautizó inicialmente como Ports & Adapters y más tarde como Hexagonal Architecture.

Si bien los temas los conocía, algo que encontré muy valioso son algunas explicaciones y argumentos.

Me gustó mucho el capítulo «Clean Embedded Architecture» en el cual trata sobre arquitectura de sistemas embebidos.

También me gustó la «desmitificación» sobre «los micro-servicios».

Una perlita interesante son los capítulos «The Database is a Detail», «The Web is a detail» y «Frameworks are details». Me parece que la posición del autor es demasiado extrema pero igualmente vale la pena leerlos.

Una cuestión que parece importante y que no he visto de forma tan explícita en otros libros es que los principios de diseño «son universales», si bien muchos de ellos han sido popularizados en relación con la programación orientada a objetos, los mismos pueden aplicarse tanto a programas desarrollados en C (que no tiene objetos) como a APIs y Servicios.

En fin, el libro me gusto y lo recomiendo sobre todos para aquellos que no esten muy familiarizados con cuestiones de diseño de software.

Cierre de 2021 en MeMo2 @ FIUBA

Y se nos fue un cuatrimestre más en modalidad virtual. Ya el cuarto. Personalmente estoy muy contento debido principalmente a 3 cuestiones, paso a enumerar en orden aleatorio.

Una de las cuestiones destacadas es que me parece que logramos transmitir de forma efectiva la propuesta de Desarrollo Guiado por Pruebas (TDD ourside-in). Este es un tema que venimos dando desde que comenzamos a dictar la materia pero que creo que este cuatrimestre en particular logramos transmitir esta técnica de forma efectiva. Digo esto basado en ciertas charlas que se dieron con los alumnos, en los trabajos realizados y en el resultado de una actividad de relevamiento que hicimos en clase que se puede apreciar en la figura 1.

Figura 1. Resultado de la actividad de relevamiento de TDD

Otra cuestión destacada son ciertos ajustes internos en la dinámica del TP2. La forma en que presentamos el problema, el mecanismo de seguimiento y tutoría que implementamos para guiar a los alumnos y un conjunto de checklist que implementamos como herramientas de soporte para los docentes. Creo que esto nos permitió mejorar la experiencia de docentes y alumnos en el desarrollo del TP2.

Finalmente, un objetivo que nos habíamos propuesto a partir del feedback del cuatrimestre anterior, fue disminuir la carga de trabajo del TP2. Este objetivo lo logramos con éxito aun cuando esa carga de trabajo es importante, este cuatrimestre fue menor que el cuatrimestre anterior.

Creo que directa o indirectamente todas estas mejoras se reflejan en los resultados de la encuesta final del curso:

  • Evaluación general de la materia: 9.1 / 10
  • Conformidad con la nota: 4.7 / 10
  • Dedicación semanal extra-clase: 9.1 horas
  • Materiales de estudio: 4.1 / 5
  • Claridad de los docentes: 4.5 / 5
  • Conocimientos de los docentes: 4.6 / 5
  • Dinámica de las clases 4.5 / 5
  • Nota promedio de aprobación: 8
  • Conformidad con nota de aprobación: 4.7 / 5
  • Net Promoter Score: 79 (métrica que puede oscilar entre -100 y +100)

De estas 10 métricas, 9 tienen mejores valores que el cuatrimestre anterior. Al mismo tiempo la Evaluación general de la materia tiene un máximo histórico (9.1 vs. el máximo anterior de 8.9). El otro valor record es el NPS que marca 79 vs. el máximo anterior de 42.
Un punto a mencionar es que no todos los alumnos contestaron la encuesta (tenemos 19 respuestas sobre un total de 26 alumnos), este es un tema recurrente, como la encuesta es anónima y como la hacemos una vez terminada la materia no hemos encontrado un mecanismo para asegurar que todos los alumnos la completen. Pero dado que este es tema recurrente de todos los cuatrimestres creemos que la igualmente la encuesta sigue siendo un elemento válido para medir el curso.

Finalmente, para dar un poco más de contexto comparto algunos otros números del curso:

  • Cantidad de inscriptos: 30
  • Abandonos: 4
  • Aprobados: 26
  • Tareas individuales: 42
  • Trabajos grupales: 2
  • Visitas de la industria: 1

Percepciones de mis alumnos sobre TDD y otras prácticas de desarrollo

En MeMo2 estudiamos diversas prácticas de desarrollo que luego pedimos a los alumnos utilizar durante el desarrollo de los trabajos grupales. En la clase de ayer, a punto ya de terminar el cuatrimestre, hicimos una muy breve actividad para relevar cómo les resultó el uso de esas prácticas.

Nos centramos en 3 prácticas concretas: Test-Driven Development (outside-in style), Mob-Programming y Trunk-Based Development. Para cada una de estas prácticas les pedimos que se ubiquen en lo que seria un «cuadro cartesiano» donde un eje horizontal representa cuánto utilizaron la práctica y el eje vertical representa el grado de confort/utilidad que sintieron con esa práctica.

En el caso de Mob-Programming las respuestas estuvieron bastante dispersas y sin extremos.

Pero para Trunk-Based Development y Test-Driven Development las respuestas resultaron mucho más categóricas: la gran mayoría indicó haber utilizado mucho lás prácticas con un grado positivo de confort/utilidad.

Personalmente estoy muy contento con estos resultados, que los alumnos perciban utilidad en lo que enseñamos resulta muy reconfortante. La gran duda (habitual en el ámbito académico) es cuanto finalmente terminan «comprando» estas prácticas los alumnos, o sea: fuera del contexto de la materia ¿seguirán utilizando estas prácticas?…..

Sobre la enseñanza de la Ingeniería de Software

La materia que dicto (tanto en UBA como en UNTReF) es Ingeniería de Software, pero dado que la «Ingeniería de Software» es un rótulo tan amplio, no basta con decir eso para transmitir qué es lo que realmente estudiamos.

Razonablemente alguien podría pensar que toda materia nombrada como «Ingeniería de Software» debería cubrir los temas descriptos en el Software Engineering Body of Knowledge (SWEBoK). Esto es lo que intentamos hacer en las materias Ingeniería de Software 1 y 2 (conocidas informalmente como memo1 y memo2) de la Licenciatura en Análisis de Sistemas de la UBA. Pero esto muchas veces no es así.

En algunas casas de estudio la materia «Ingeniería de Software» solo cubre una parte de esos temas del SWEBoK. Un caso de esto parece ser la materia Ingeniería de Software de la carrera de Licenciatura en Ciencias de la Computación de la UBA, la cual está muy enfocada en cuestiones de diseño y programación orientada a objetos y sin tratar varios temas de SWEBoK.

También está el caso de algunas carreras que no tienen ninguna materia llamada Ingeniería de Software, sino que cuentan con materias particulares para las distintas disciplinas que componen la Ingeniería de Software (análisis, diseño, programación, testing, etc). Este es el caso de la carrera Ingeniería en Informática de la UBA.

También tenemos el caso carreras que cuentan con materias específicas para algunas de las distintas disciplinas de la Ingeniería de Software y al mismo tiempo tienen una materia Ingeniería de Software que cubre los temas restantes de la Ingeniería de Software que no cubre ninguna materia particular como ser muchas veces las cuestiones de proceso, trabajo en equipo y metodologías de desarrollo. Este el caso de la carrera Ingeniería en Computación de UNTreF.

Más allá de todas estas variantes, hay una cuestión que quiero destacar respecto de la forma en que dictamos las materias MeMo1 y MeMo2 en UBA. Ambas materias pretenden cubrir todas las disciplinas de la Ingeniería de Software, en ambas materias se estudia desde requerimientos hasta puesta en marcha pero con mayor profundidad en distintos temas. MeMo1 tiene un mayor foco en las cuestiones iniciales de los proyectos, como ser visión, requisitos, etc, pero aún así trata cuestiones de metodología, arquitectura, etc. Y un punto central: se hace implementación, o sea, los alumnos tienen que programar y poner lo que programan en un ambiente «símil producción». Esto me parece que es fundamental para cerrar el «feedback loop», recién al poner nuestra aplicación en producción sabemos cuán bueno ha sido nuestro trabajo en requisitos y demás tareas de la ciclo de desarrollo. Por otro lado en MeMo2 también cubrimos todo el ciclo pero con un mayor foco en ciertas cuestiones que MeMo1 toca de forma más superficial. En particular en MeMo2 ponemos mucho foco en proceso de desarrollo, el ciclo de BDD/TDD, CI/CD, Configuration Management y dinámica de equipo.

Logs centralizados para aplicaciones Kubernetizadas

Cuando corremos una aplicación en Kubernetes (y en términos más generales en un arquitectura distribuida) donde potencialmente existe más de una instancia de nuestra aplicación resulta conveniente (o incluso imprescindible) poder acceder a los logs de nuestra aplicación en forma centralizada. O sea, en lugar de acceder a cada servidor/nodo donde corre nuestra aplicación, deberíamos poder acceder a una única ubicación y ver ahí mismo los logs de todas las instancias de nuestra aplicación.

Para implementar una solución de este tipo cuando nuestra aplicación corre en Kubernetes existen diferentes estrategias posibles. Voy a referir a continuación 3 estrategias que a mi parecer son bastante habituales, pero antes veamos algunas generalidades.

El tener un log centralizado típicamente implica dos cuestiones: por un lado recolectar los mensajes de log para enviarlos a un lugar central y por otro lado poder acceder a esos mensajes de una forma práctica. Adicionalmente podríamos mencionar el procesamiento de esos mensajes para poder consultarlos de forma más práctica/específica o incluso para accionar ante ciertos mensaje. Esta problemática es tan habitual en la actualidad que existen múltiples productos y servicios para implementarla. Entre las soluciones más populares podemos mencionar New Relic, Datadog, Sumologic y ELK. En general estas soluciones son mucho más amplias que el manejo de logs, son soluciones de monitoreo que incluyen los logs como una funcionalidad particular. Volviendo al foco de este artículo, nos vamos a concentrar puntualmente en el primer paso de este flujo: la recolección de los mensajes de log.

Estrategia Sidecar

Esta estrategia consisten en desplegar dentro cada uno de nuestros pods, a la par del contenedor que corre nuestra aplicación, un contener sidecar que tome los mensajes de log de nuestra aplicación y los envíe a la fuente central. Una form típica de implementar esto es haciendo que nuestra aplicación escriba el log en un archivo de un volumen compartido del cual luego serán leídos por el colector de logs que corre en el contenedor sidecar.

Esta estrategia resulta bastante transparente para la aplicación pero requiere montar el sidecar en cada manifiesto de deployment.

A modo de ejemplo, si utilizamos el servicio de logs centralizados de Sumologic, podemos utilizar esta imagen Docker para montar nuestro contenedor sidecar.

Estrategia Daemonset

En el contexto de kubernetes un daemonset es un pod que corre en cada nodo del cluster de Kubenetes. Entonces podemos desplegar un daemonset que colecte los logs leyendo el stdout de todos contenedores corriendo en el nodo.

Esta estrategia resulta muy económica de implementar, basta hacer que nuestra aplicación escriba los mensajes de log al stdout y desplegar el daemonset. Luego al agregado de nuevas aplicaciones y nodos es completamente transparente.

A modo de ejemplo, si utilizamos el servicio de log de Papertrail, podemos utilizar este daemonset para enviar los logs.

Estrategia Logger con handler/appender remoto

Esta estrategia no tiene nada que ver con Kubernetes y consiste en hacer que sea nuestra aplicación la que directamente envíe los mensajes de log al log central. La forma prolija de implementar esto es usando alguna biblioteca log como Log4J, Log4Net, etc y configurar esta biblioteca con un handler que envíe la información al log central.

Lo interesante de esta solución es que resulta muy simple de implementar. Pero al mismo tiempo tiene un potencial costo de performance que hace que esta estrategia no sea conveniente para algunos escenarios, o sea: si tenemos una aplicación web atendiendo pedidos de nuestros usuarios y en ese contexto el logger van a enviar los mensajes de log via HTTP dentro del mismo proceso podemos tener algún issue de performance. Sin embargo, si nuestra aplicación es un job que corre en background, puede que este costo del log no sea relevante.

A modo de ejemplo, si nuestra aplicación está construída en Ruby podemos utilizar la gema SemanticLogger con un appender http para enviar los Sumologic o Papertrail.

Uso de RubyMine con Docker-compose

Hace un tiempo escribí como sobre usar RubyMine con Docker. En ocasiones, cuando trabajamos con aplicaciones web es habitual utilizar una configuración docker-compose que incluya un contenedor con el runtime Ruby y algunos otros contenedores por ejemplo con MySql, Redis, etc. Para usar RubyMine en estos escenarios debemos hacer lo siguiente.

Armamos la configuración docker-compose y la ponemos a correr asegurándonos de el contendedor que contiene el runtime Ruby quede levantado. Esto puede hacerse agregando las siguientes 3 líneas en la definición del contenedor:

    command: "/bin/bash"
    stdin_open: true # esto es equivalente a hacer docker run -i
    tty: true        # esto es equivalente a hacer docker run -t

Luego, abrimos el proyecto en RubyMine y ajustamos la configuración en «Ruby SDK and Gems». Agregamos una nueva configuración remota.

En la ventana emergente debemos seleccionar la opción «Docker Compose», en «Configuration Files» debemos apuntar al archivo que contiene nuestra configuración de docker-compose y finalmente en «Service» debemos seleccionar el contenedor que contiene el runtime Ruby. Dependiendo de como esté instalado el runtime Ruby en la imagen del contenedor puede que necesitemos ajustar el valor default de «Ruby or version manager path».

Una vez que damos «OK» a ambas ventanas de emergentes el RubyMine puede tardar unos minutos en completar la aplicación de la nueva configuración.

Un detalle más: si queremos utilizar el debugger de RubyMine debemos asegurarnos que nuestro proyecto incluya las gemas «ruby-debug-ide» y «debase» en el Gemfile.

Finalmente les comparto una configuración compose de ejemplo para que se entienda todo el contexto.

version: '3'
services:

  test_db:
    image: postgres
    environment:
      POSTGRES_PASSWORD: example

  dev_db:
    image: postgres
    environment:
      POSTGRES_PASSWORD: example

  webapp:
    build: 
      context: .
      dockerfile: Dockerfile.dev
    command: "/bin/bash"
    stdin_open: true # docker run -i
    tty: true        # docker run -t
    ports:
      - "3000:3000"
    expose:
      - 3000
    volumes:
        - .:/app
    environment:
        RACK_ENV: "development"
        TEST_DB_URL: "postgres://postgres:example@test_db:5432/postgres"
        DEV_DB_URL: "postgres://postgres:example@dev_db:5432/postgres"
    depends_on:
      - test_db
      - dev_db