El blog de Shackra

Catolicismo Emacs Software libre

“No seas tan abierto de mente o tu cerebro se caerá” ~G.K. Chesterton

Las aventuras de Jorge el desarrollador: Proyecto Betsy

S8n97Zv.jpg

Figura 1: Cuando el proyecto compila... pero la lógica salio como el cangrejo y sólo queda 10 minutos para entregar.

Acabo de terminar un avance en mi proyecto Betsy, y ahora escribo esta entrada en mi blog. Era algo que quería hacer desde hace algún tiempo, así que acá esta.

Betsy es un bot para Telegram Messenger. Betsy ofrece juegos de azar y mujerzuelas a los usuarios de Telegram, los cuales pueden apostar usando Bitcoin (y Dios mediante, otras cripto-monedas) agilizando el tiempo de espera entre el deposito y el uso de los fondos para jugar y también el tiempo de espera de los pagos a los jugadores cuando solicitan los retiros de sus fondos.

El proyecto nació de la necesidad, pues actualmente estoy corto de clientes lol, así que subsistir de trabajos freelance es algo difícil ahora mismo; la situación buscando empleo no es tan distinta a la de buscar clientela. Anterior a Betsy tenia otro proyecto que estaba realizando junto con otra persona, pero esa persona me abandono (literalmente) lo cual mató el proyecto (y a mi, al menos por un tiempo (aunque tengo pensado revivir ese proyecto y ejecutarlo algo diferente al plan original, depende de cómo salgan las cosas con Betsy)). Casinos Bitcoin hay muchos en la web, mi proyecto esta compitiendo con adversarios bastante fuertes y experimentados que pueden lanzar plata a un problema como si nada para resolverlo; lujos que yo no puedo darme. Lo que diferencia mi proyecto de la competencia es el uso de Telegram Messenger como interfaz para el jugador (y próximamente otros clientes de mensajería instantánea como Facebook Messenger).

El proyecto aun no esta listo, pero igual quiero escribir un poco sobre lo que he ido aprendiendo a partir de mis errores.

Organización de tareas

Soy el único ser humano trabajando en este proyecto, y posiblemente siga siendo el único por un tiempo. Cuando se trabaja sólo o en pareja/grupos una cuestión que tiende a ignorar los principiantes es la manera en que se organizan las tareas para completar el proyecto en el que trabajan.

Si uno no lista las cosas que hay que realizar para completar el proyecto, lo más probable es que no se sepa para donde vamos y qué dirección toma el proyecto, como también cuanto falta para terminarlo. En mi caso nunca cometí el error de no listar tareas pendientes porque yo no soy ningun principiante lol, el error que cometí y me costo un mes de trabajo fue olvidarme que tenia una lista de pendientes.

Si uno desarrolla software usando GNU Emacs, org-mode es la herramienta por default para organizar las cosas, y de verdad que es una herramienta buenísima dada su versatilidad. Al principio liste todo en un archivo org, pero cada vez que iniciaba un día y me disponía a desarrollar el proyecto olvidaba consultar primero la lista, este vicio simplemente se fue acentuando hasta que le proyecto llego a un punto donde todo lo listado ya no tenia nada que ver con la realidad ¿Qué podría salir mal?.

Aquí el engaño esta en percibir lo anterior como algo bueno, pues hay un avance tan impresionante que deja la lista de tareas botada ¿no? NO, primero porque uno puede olvidar qué ha sido implementado en el proyecto (en una ocasión me vi escribiendo código que ya existía en otro sitio pero en un archivo nuevo ¡Ups!) y segundo, como ya dije antes, porque no sabemos exactamente cuanto falta para completar el proyecto o la dirección que esta tomando. Tratar de recordar todo lo que falta y lo que ha sido realizado es una cuestión que cansada bastante a la mente (que ya esta trabajando al 100% picando código), lo mejor es hacernos las cosas fáciles, lo más fáciles posible.

Lo que actualmente uso es Trello, el concepto de los tableros me ha llamado la atención desde que conozco la metodología de desarrollo ágil Kanban. Creo que puedo realizar algunas mejoras en Emacs para que muestre el archivo org correspondiente al proyecto según el archivo que estoy visitando, automáticamente, lo cual me quita el problema de recordar revisar el archivo, difícil en especial si comienzo el día con prisa.

Volver a organizarme ha tenido un enorme y positivo impacto en mi flujo de trabajo, listar las tareas simplemente ha agilizado el desarrollo del proyecto, no ralentizarlo, como muchos principiantes creen. Seguro, verter la mente toma su tiempo, pero ese tiempo es bien invertido porque nos ahorra a futuro varias horas perdidas y muchas frustraciones.

Pruebas automatizadas de código

¡TOTAL Y COMPLETA PERDIDA DE TIEMPO! ¿Cierto? ¡¡DE VERDAD NECESITO TERMINAR ESTE PROYECTO/TAREA PARA PRESENTÁRSELO A MI CLIENTE/PROFESOR!! ¡¡¡NO TENGO TIEMPO PARA PERDERLO EN TONTERÍAS!!! ¡¡¡¡VAMOS A MORIR AAAAAH!!!! #panico

Las pruebas automatizadas es una de esas técnicas en el campo del desarrollo de software subestimadas por (¿muchos?) profesionales, futuros y actuales. Quizás sea por la prisa de acabar, por ignorancia o necedad de los gerentes de proyecto o de los mismos desarrolladores/estudiantes, pero en mi interacción con otros desarrolladores he notado que ellos no implementan pruebas automatizadas para sus proyectos. Ellos simplemente se sientan, comienzan a picar código, compilan el proyecto y prueban manualmente lo que acaban de implementar buscando fallos o errores de lógica, corrigen algún fallo que encuentran; enjuague y repita.

Bueno, esto no es óptimo, ni profesional.

Para mejorar la sanidad mental de los desarrolladores, mi recomendación es que adopten alguna aproximación TDD o BDD desde el inicio del ciclo de desarrollo del proyecto... o en cualquier parte del ciclo de desarrollo, no, en serio, háganlo, háganlo ya. El hecho de que sus desarrolladores hagan su trabajo de manera más inteligente no quiere decir que su equipo de QA quede obsoleto.

En el caso de Betsy y desde el inicio, muchos trozos de código contaban con sus respectivas pruebas unitarias, el proyecto sufrió varias refactorizaciones y cometí el error de no refactorizar mis pruebas primero. En un punto durante el desarrollo tuve que literalmente borrar el código que había escrito (por una razón que explicaré más adelante), el código había cambiado y la prisa me consumía entonces pasé de largo en escribir mis pruebas automatizadas, lo que hacia era escribir código, compilar el proyecto y probar manualmente los cambios realizados.

Tengo prisa por acabar, y sin embargo me gusta dispararme en los pies ¿eh? Mira que listo soy.

Gastar 10/15/20 segundos probando manualmente los cambios hechos al proyecto, tomando en cuenta que tenemos prisa por acabar, suena algo bastante estúpido. En mi cabeza, todas esas reglas que ya conocía desde hace un par de años venían a atormentarme todas las noches cuando me iba a dormir con visiones de un futuro donde el proyecto falla en producción y no tengo la menor idea de porqué, haciéndome perder plata, clientes y tiempo.

Para calmar la conciencia, saque un par de horas para escribir pruebas de integración y asegurarme que aquello ya implementado funcionara como debía desde ahora y en el futuro... pero para mi desgracia, un elemento del proyecto invalidó todas esas pruebas, no importaba qué, las pruebas siempre pasan exitosamente.

Sabía que algo debía cambiar, y eso era...

Arquitectura de software

No estoy muy familiarizado con la arquitectura de software, supongo que eso es una de las desventajas de ser auto-didacta. Las decisiones que tomé al principio del ciclo de desarrollo de Betsy fueron tomadas desde mi ignorancia sobre como funciona un bot de Telegram escrito en Go. En el inicio escribí muchos componentes que podría llamar "internos" que tuve que eliminar luego de darme cuenta como era que tenían que funcionar los componentes "externos", por ejemplo, cómo debía el bot recibir y procesar mensajes que enviaba los jugadores, etc.

Yo sabía qué era lo que quería alcanzar con mi proyecto, pero esos pequeños detalles a la larga no son ni pequeños, ni detalles; dictan de alguna forma la manera en la que puedo desarrollar el proyecto.

Mi código necesitaba una tremenda cantidad de trabajo para el proceso de refactorización y corregir mis decisiones en arquitectura de software.

Uno de los errores que me dispuse a corregir fue la pasadera de dependencias a métodos con recibidores de punteros. Era idiota tener que ensuciar la firma de mis métodos con un tipo struct que contenía referencias a la base de datos MongoDB y a Redis, la configuración del programa, etc. ¡Pero era muchísimo más idiota re-crear esa struct cada vez que el bot recibía un mensaje de un jugador!. El cambio consistió en embeber esas dependencias dentro del struct de cada menú que se le estaba presentando al jugador, así cada método podía hacer peticiones de manera interna a la base de datos o a la cache, y las firmas de los métodos permanecían limpias de argumentos repetitivos. Esto definitivamente no complicó las pruebas de integración pues la idea de este tipo de pruebas es simular el entorno de producción donde será desplegado más tarde el proyecto.

El otro error corregido fue la manera en que se enviaban las respuestas a los jugadores. Esto lo hacía enviado cualquier tipo que implementara la interfaz tgbotapi.Chattable a través de un canal. El problema con esto era que me complicaba innecesariamente la implementación de las pruebas de integración. Necesito saber si Betsy esta respondiendo las peticiones de los jugadores de manera correcta, pero atrapar todo lo que venga por un canal para revisión es algo... ¡idiota! difícil. Decidí delegar el envió de respuestas a un struct llamado Replyer que va embebido dentro de todos los struct que representan menús. El tipo Replyer tiene dentro una instancia de tgbotapi.BotAPI, de hecho cada menú tiene una instancia distinta (los menús no comparten un mismo Replyer, aunque no sería mala idea). Varios tgbotapi.BotAPI no significa que el bot tendrá problemas con la API para bots de Telegram, los problemas aparecerían si hiciera que cada instancia de tgbotapi.BotAPI intentaran al mismo tiempo recibir los mensajes que envían los jugadores.

Para fácilmente capturar durante las pruebas unitarias todas las respuestas que envía el bot, implementé el mismo tipo Replyer en un archivo distinto que sólo es compilado por Go cuando la opción -tags es alimentada con el argumento !release en go build o go test.

Integración continua y despliegue de proyecto con Git

Quizás lo único que hice bien desde el principio y que hasta el día de hoy sigue estando bien. Luego de que nuestro proyecto ha sido probado de manera automática y sin problemas, es bueno usar un servicio que nos ayude con el despliegue del proyecto, librándonos de intervenir en el proceso cada vez que una nueva versión de nuestra aplicación esta disponible para los usuarios.

Yo en lo personal uso Wercker. Tiene una edición comunitaria que es gratuita.

En la configuración de Wercker para el proyecto Betsy estoy usando dos segmentaciones. La primera compila y prueba el proyecto. La segunda segmentación compila de nuevo el proyecto y lo copia a mi servidor en DigitalOcean, junto con la configuración de la aplicación que es modificada por Wercker antes de copiarla al servidor y otras dependencias más. Si quieren, pueden ojear el archivo wercker.yml del proyecto.


box: golang
build:
  services:
    - mongo
    - redis
  steps:
    - setup-go-workspace
    - install-packages:
        packages: netcat-openbsd
    - script:
        name: Glide
        code: |
          go get github.com/Masterminds/glide
          glide install
    - script:
        name: Pruebas unitarias
        code: |
          sed -i "1s/^/hdmasterkey: $hdkey\nblockcyphertoken: $apikey\ntelegram_logger_api: $logapi\nchannel_logger_id: $channelid\n/" config.yml
          go test -tags !release -v
    - script:
        name: Esperando que Redis y MongoDB este en linea
        code: |
          while ! nc -q 1 $MONGO_PORT_27017_TCP_ADDR $MONGO_PORT_27017_TCP_PORT </dev/null; do sleep 3; done
          while ! nc -q 1 $REDIS_PORT_6379_TCP_ADDR $REDIS_PORT_6379_TCP_PORT </dev/null; do sleep 3; done
    - script:
        name: Pruebas de integración
        code: |
          echo "MongoDB: $MONGO_PORT_27017_TCP_ADDR:$MONGO_PORT_27017_TCP_PORT - Redis: $REDIS_PORT_6379_TCP_ADDR:$REDIS_PORT_6379_TCP_PORT"
          CONFIGOR_DB_ADDRESS="$MONGO_PORT_27017_TCP_ADDR:$MONGO_PORT_27017_TCP_PORT" \
          CONFIGOR_REDIS_ADDRESS="$REDIS_PORT_6379_TCP_ADDR:$REDIS_PORT_6379_TCP_PORT" \
          go test -race -tags "!release integration" -v
    - script:
        name: Verificación de dependencias
        code: |
          go build -tags release -v
deploy:
  steps:
    - setup-go-workspace
    - script:
        name: Construye el proyecto Betsy
        code: |
          go get github.com/Masterminds/glide
          glide install
          go build -tags release -race
    - add-ssh-key:
        keyname: digitalocean_deploy
    - add-to-known_hosts:
        hostname: miservidor.com
    - script:
        name: Preparación previa de directorios
        code: |
          ssh ejemplo@miservidor.com 'mkdir -p ~/{bin,share/betsy/,var/logs/,.config/systemd/user}'
    - script:
        name: Copiado del binario, el servicio para systemd y la configuración
        code: |
          ssh ejemplo@miservidor.com 'systemctl --user stop betsy || true'
          scp betsy ejemplo@miservidor.com:~/bin/
          scp -r locales ejemplo@miservidor.com:~/share/betsy/
          # modifica config.yml con datos de la API de BlockCypher
          sed -i "1s/^/hdmasterkey: $hdkey\nblockcyphertoken: $apikey\ntelegram_logger_api: $logapi\nchannel_logger_id: $channelid\n/" config.yml
          scp config.yml ejemplo@miservidor.com:~/
          scp betsy.service ejemplo@miservidor.com:~/.config/systemd/user
    - script:
        name: Recargar de unidades de Systemd y reiniciar Betsy
        code: |
          ssh ejemplo@miservidor.com 'systemctl --user daemon-reload && systemctl --user restart betsy && systemctl --user enable betsy'

El tema con esto del desarrollo de software es aprovechar todas las técnicas y tecnologías que facilitan la actividad de crear cosas chivas. Es suicida tratar de empujar cuesta arriba la piedra con el pecho por decir "meh, obviare todo esto porque necesito hacer rápido el proyecto", el ahorro de tiempo que uno hace con todos estos preparativos previos y cuidados es impresionante, ni que decir de la paz mental ¡estos nuevos cambios no parecen romper nada que ya estaba implementado!, poder decir eso con (casi) toda la seguridad del mundo es un verdadero placer.

/