Cuando planteamos hacer un post sobre uno de los pilares fundamentales de Redradix, la calidad del código, tuve claro que no podía escribirlo yo. Pese a que es algo por lo que lucho día a día desde mi posición de CTO, hace casi dos años que las labores de dirección me sacaron de la programación en proyectos de cliente. El enfoque que veía para el artículo se alejaba de listados de buenas prácticas y principios de diseño, y en su lugar quería asomarse al día a día del programador, del buen programador.
Por eso pensé en hacer una entrevista a Víctor Míguez, amigo desde hace mucho, compañero en Redradix desde hace más de 5 años y talibán del código mantenible. Una persona a la que, llegado el caso, volvería a confiarle la creación de un SDK para visualizar la telemetría de una flota de 700 satélites, como ya hizo en este proyecto.
Tras un pequeño filtro de postproducción, estas fueron las preguntas y respuestas en la conversación:
¿Qué es un código de calidad en Redradix?
Por orden de prioridad:
- Es un código que funciona y que resuelve el problema.
- Es un código sencillo. Sencillo en el sentido de certero, esencial, preciso, sobrio, escueto, claro y, también, modesto.
Y en este segundo punto es dónde más podemos pensar en técnicas que nos ayuden. Porque para realmente saber si un código es sencillo hay que contrastarlo con otras personas. Podríamos llamarlo “programación por consenso”.
Entonces, ¿las personas con más experiencia son responsables de crear código de forma que pueda entenderlo una persona más junior?
Tenemos que pensar en quién podría coger ese código una vez yo lo suelte. Quién lo va a tener que entender y mantener. El código siempre cambia de manos. Y no sabemos en cuáles puede caer. La mejor programación defensiva es crear código legible.
Para ello considero que hay que tener siempre presente cierto equilibrio entre abstracción y código que funcione sencillo. Todo programador en su día a día está creando abstracciones. Cada vez que aislamos un elemento de su contexto, estamos creando una abstracción. Al crear una función, estamos creando una abstracción. Por eso hay que estar muy atento a las abstracciones prematuras, que no son otra cosa que generalizaciones que hacen que sea más complejo entender una solución. Hay que luchar contra el ego del programador, lidiar contra la necesidad de crear “la solución más inteligente“. Y también hay que desapegarse del código que escribimos, ser capaces de desechar una solución o una abstracción que en su día creamos pero que ya no encaja.
¿Cómo se lucha? ¿Tienes un método para tirar código? ¿Cómo es ponerse a programar una nueva funcionalidad?
Pese a que tiene muchos matices, es un ciclo que se repite:
- Lo primero es que funcione. Suele ser un código bastante guarro. Nombres de variable o de función raros (bla, ble, bli) y código lineal. Que no haya nada que me empuje a crear abstracciones prematuras.
- Una vez que compruebo que funciona, ya sea gracias a los tests o con validación manual, empiezo a darle estructura al código. La propia solución revela sus partes, su organización. Y este orden pide abstracciones, diferentes trozos de código con una responsabilidad concreta. Eso ayuda a crear nombres de variable y funciones coherentes. Es el propio código el que habla.
- Una vez que hemos conseguido un código sencillo, limpio y entendible, pienso en la reutilización. Y es en este paso cuando hay que ir con cuidado, contenerse y pensar si realmente es el momento adecuado.
Entender si merece la pena modificar una función para añadir un parámetro y poder reutilizar ese trozo de código en nuestra nueva funcionalidad es algo más complejo de lo que parece. En muchos casos es mejor convivir con cierta duplicidad que hacer compleja una función para que sea capaz de asumir comportamientos que no acaban de empastar bien. Quizás es en ese momento cuando tienes que levantar la cabeza y preguntar a tus compañeros. Hacer un poco de Pair Programing o al menos comentar la solución. O hacer un PR o Pull Request. Programación por consenso para encontrar la solución mas simple, no solo para ti, sino para todos.
Este es el típico ciclo de (test) red-green- (commit) refactor (commit) que tanto hemos oído en TDD (Test Driven Development). Pero una de las cosas más importantes de este ciclo es hacerlo en pequeños pasitos.
¿Esto es lo de los baby steps que siempre predicas?
Es programar dando pasitos pequeños. Cuanto mas rápido es el ciclo de desarrollo en el que verificar que no has roto nada, mejor. Cambio un nombre de variable - pasan los tests - comiteo. Extraigo un trozo de código a una función más general - pasan los tests - comiteo. A veces no hay test y hay que hacer la comprobación manual, pero se aplica el mismo principio. Cuanto más código cambies desde que empiezas a tocar hasta que lo pruebas, más difícil va a ser encontrar un error que hayas podido cometer durante el proceso. Esto se aplica tanto al paso de refactorización como al de crear funcionalidad, con la salvedad de que cuando tiras código nuevo hay veces que ves claros los pasos desde el principio y otras no. Y es entonces cuando debemos escribir código rápido y dejar que la estructura, la organización, emerja.
Arquitecturas emergentes, ¿no?
Las arquitecturas emergentes son el mismo concepto pero a nivel de arquitectura del código. Dejar que la propia organización de módulos, capas y diferentes necesidades se cree a medida que haga falta y no crear abstracciones prematuras fijando una arquitectura que te ancle y te limite. El simple hecho de dividir tu carpeta "src" en subcarpetas es algo así como una abstracción. A mí me encanta empezar un proyecto con un fichero "index.js" debajo de "src" y nada más. Y a partir de ahí ir dividiendo y creando otros ficheros y carpetas a medida que el código va evolucionando. Aplazar las decisiones hasta tener más conocimiento.
Hablando de términos que siempre comentas, ¿qué es la regla del Scout?
En un proyecto, ya sea legacy o green field, lo primero es entender el código. Y como todo código tendrá partes mejorables. Si debido a la funcionalidad que tengo que añadir, tengo la oportunidad de mejorar algo que ya estaba ahí, aprovecho la circunstancia. Ojo, no hay que sucumbir a la tentación de seguir tirando del hilo y acabar embarcado en una refactorización de un sprint completo. La idea es dejar la zona por la que paso mejor de lo que estaba. Si esto lo hacemos de una forma consistente, es un gran paso de cara a mantener la base de código a lo largo del tiempo y para crear homogeneidad en el código. Sobre todo si a nivel de equipo o de empresa se comparten ciertos patrones y consensos sobre el código.
¿Ayuda a eso el pair programming?
Realmente en Redradix no hacemos tanto pair. Es una técnica que ayuda a resolver un problema poniendo más mentes a funcionar. El objetivo que se persigue es la propiedad compartida del código y una funcionalidad libre de bugs. Lo usamos cuando es necesario. Pero se puede hacer a través de otras técnicas como PRs, la comunicación eficiente entre disciplinas o con un lenguaje común de proyecto, que es lo que llaman lenguaje ubicuo o lenguaje de dominio.
Más allá del lenguaje ubicuo del proyecto, ¿crees que tenemos un lenguaje de dominio de Redradix?
Existe sobre todo a nivel de disciplina, gracias a las PeCs (puestas en común semanales de cada equipo), postmortems y diferentes reuniones Scrum y no Scrum del día a día del proyecto. Pero también existen a nivel global gracias a las ceremonias recurrentes. Los breakfasts semanales y las retros (retrospectivas) aportan bastante en ese sentido. La documentación del proceso, guías y librerías en Notion también ayudan mucho. Y, definitivamente, los canales de Slack de cada Unit (disciplinas e intersecciones entre ellas como devseño, devqueta o disqueta) son una gran fuente de unificación.
¿Qué crees que deberíamos tener siempre presente en el desarrollo de software?
La calidad del código va asociada al dinero. El objetivo de crear un código de calidad no es que el programador se sienta orgulloso. Es que si un día el producto tiene un bug, se encuentre rápido. Que si hay que añadir una nueva funcionalidad, pueda salir rápidamente al mercado. O que si en algún momento un cliente decide cambiar de proveedor, el nuevo equipo pueda hacerse rápidamente con la base de código.
Y es importante recordar esto a todos los niveles. Porque cuando hay prisas lo primero de lo que se prescinde es de la refactorización. Pecamos de aplazarla y la deuda técnica se acumula. Entonces levantamos la mano y pedimos un sprint de mejora del código para la fase dos. Una fase dos que nunca va a llegar… Así que recuerda, ¡sé un Scout!