Jekyll2022-04-21T20:28:59+00:00https://ngarbezza.com.ar/feed.xmlNahuel Garbezza🧑🏻💻 Desarrollador de software saludable. 👨🏻🏫 Docente. 📝 Blogger.Nahuel GarbezzaLos defaults malvados2022-04-19T15:00:00+00:002022-04-19T15:00:00+00:00https://ngarbezza.com.ar/blog/los-defaults-malvados<p>(traducción al castellano de <em>The Evil Defaults</em>, para ver su versión original
<a href="https://blog.10pines.com/2018/09/27/the-evil-defaults/">ir aquí</a>)</p>
<h2 id="intro">Intro</h2>
<p>Trabajar en un proyecto con mucho código heredado siempre es una fuente de grandes aprendizajes. En esta ocasión les
presento a los <em>“defaults malvados”</em>, como a mí me gustan llamarlos. Son partes de código que pueden hacer mucho daño
en una aplicación. Veamos cómo…</p>
<h2 id="qué-sería-un-default-malvado">¿Qué sería un default malvado?</h2>
<p>Un <strong>default malvado</strong> es una pieza de código que actúa, como su nombre lo indica, como una opción <strong>por defecto</strong> en un
flujo de decisión y que bajo ciertas circunstancias <strong>causa problemas</strong>, relacionados al comportamiento esperado
<strong>a nivel negocio</strong>, cuando se evalúa.</p>
<p>Cuando digo pieza de código me refiero a un simple objeto, valor, o a un conjunto de acciones con efectos, o incluso a
la deliberada decisión de no hacer nada. Hacer nada también es hacer algo…</p>
<p>Abstractamente hablando:</p>
<div class="language-ruby highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">if</span> <span class="n">regla_de_negocio_a_aplica</span>
<span class="n">hacer_a</span>
<span class="k">elsif</span> <span class="n">regla_de_negocio_b_aplica</span>
<span class="n">hacer_b</span>
<span class="k">else</span>
<span class="c1"># a veces no sabemos qué hacer aquí, entonces… ¯\_(ツ)_/¯</span>
<span class="n">hacer_algo_que_parecer</span><span class="err">í</span><span class="n">a_estar_bien</span>
<span class="k">end</span>
</code></pre></div></div>
<p><a href="https://blog.10pines.com/2014/10/20/trust-in-objects-they-have-the-right-to-decide/">No nos llevamos muy bien con los condicionales</a>,
pero este simple pseudocódigo es sólo para mostrar el potencial problema :-) Preferimos objetos polimórficos que se
encarguen de resolver estas decisiones. En cualquier caso, sea una rama de un condicional or un objeto polimórfico que
representa ese caso, estamos tomando decisiones. Y debemos elegir cuidadosamente qué hacer en la rama del <code class="language-plaintext highlighter-rouge">else</code>, porque
es también un flujo que puede aplicar en muchos casos, algunos de ellos quizás que no imaginamos al momento de construir
el programa.</p>
<p>El default malvado se vuelve más malvado a medida que continuamos con la ejecución, porque una mala decisión (que no
se condice con lo que a nivel negocio esperamos que suceda) puede poner nuestro sistema en un estado inconsistente o
arrastrar un error a otras partes del programa. A menos que lancemos una excepción, el programa continuará haciendo
cosas en base a lo que ese default haya determinado.</p>
<p>¿Qué problemas tenemos entonces?</p>
<ul>
<li>El sistema parece funcionar, porque “no se rompe”. Pero funciona de una manera incorrecta. Hay una falsa sensación de
éxito.</li>
<li>Cuando descubirmos que había un error, probablemente ya sea demasiado tarde. Depurar y corregir estos errores suelen
llevar mucho tiempo por los efectos que dejan, y porque a veces no tenemos alguna excepción para analizar.</li>
<li>El código no está reflejando lo que necesita ser modelado. Si no sabemos qué hacer en estos casos (¡y puede que el
cliente a nivel negocio tampoco lo sepa!), lo mejor que podemos hacer es
<a href="https://blog.10pines.com/2014/09/29/symbols-the-new-return-codes-part-2/">fallar con una excepción</a>.</li>
</ul>
<h2 id="por-qué-ocurren">¿Por qué ocurren?</h2>
<p>Les programadores hacemos una observación del dominio y luego intentamos reflejarla en un modelo computable. Y sabemos
que en ese proceso seguramente no tengamos una imagen perfecta. Necesitamos de iteraciones, y a medida que avancemos
ganaremos experiencia y conocimiento en el dominio que estamos modelando. Mientras tanto, tenemos que tomar muchas
decisiones!. Para la computadora no hay grises, debemos ser precisos incluso en contextos de incertidumbre cuando aún
no entendimos el problema a modelar en su totalidad…</p>
<p>Es así que la falta de conocimiento en el dominio es la razón principal para tener defaults malvados. Cuando los
introducimos, no nos damos cuenta que algo puede salir mal. Pensamos que funciona. Al menos hoy.</p>
<p>Otra razón para tener defaults malvados es la sobresimplificación del problema que estamos modelando. A veces pensamos
que un simple valor o línea de código puede salvarnos de cualquier posible casi, para luego darnos cuenta más tarde que
nos habíamos equivocado.</p>
<p>Además, tenemos una variable más que es el “miedo a fallar” que a veces experimentamos como desarrolladores. Simplemente
parece mejor “hacer algo” o “devolver algo” en lugar de fallar, porque lo vemos como algo problemático. Pero la realidad
es que si no sabemos qué hacer, es mejor no asumir y fallar, eso reflejará la realidad y nos dirá que debemos hacernos
cargo de esa incertidumbre si ocurre.</p>
<h2 id="un-ejemplo-de-la-vida-real">Un ejemplo de la vida real</h2>
<p>Estábamos junto con mi equipo realizando unas mejoras en un proceso automatizado que importaba compras hechas en Ebay y
Amazon en un ecommerce a través de APIs REST. Entonces, teníamos un endpoint al que consultábamos las compras
realizadas, las traducíamos en el formato que nuestro ecommerce esperaba para guardarlas y utilizarlas más adelante.
Las compras tenían como uno de sus datos el país del comprador que para nuestro sistema era un dato indispensable para
el proceso de fulfillment (hacer que los ítems lleguen a las manos de los compradores).</p>
<p>Para poder guardar correctamente el país necesitábamos hacer un mapeo desde el formato de estos marketplaces externos
al nuestro. Por ejemplo. “ARG” mapea a “Argentina”. Bueno, esta lógica tenía un default malvado: se elegía USA (Estados
Unidos) como la opción por defecto en caso de no encontrar una entrada existente en el mapeo. Teníamos opciones muy
limitadas de envío con lo cual esto no fue un problema por varios meses… hasta que sucedió. Y cuando sucedió, no fue
inmediato darnos cuenta. Nada explotaba. Sólo continuaba con un proceso que iba a terminar mal por haber elegido el país
incorrecto. Los clientes comenzaron a enviar reclamos porque sus compras no llegaban a tiempo. No fue sencillo encontrar
la causa del problema, la solución fue sencilla pero resolver los casos que ya habían ocurrido (sumado a los negativos
comentarios de los clientes afectados) fue difícil.</p>
<h2 id="y-cómo-lo-resolvemos">¿Y cómo lo resolvemos?</h2>
<p>Como mencionamos anteriormente, una manera apropiada de modelar un caso desconocido es con una excepción. Detener el
flujo de ejecución y esperar por intervención humana para ver ese error y decidir qué hacer con ello.</p>
<p>Una cosa clave es tener algún mecanismo para saber cuándo ocurren estas excepciones, y tener toda la información
necesaria para investigar el problema. Necesitamos:</p>
<ul>
<li>Buenos mensajes de error, descriptivos y que contengan información adicional del contexto en el que este error ocurrió
(IDs/números de referencia, valores de configuración, si es una request web, sus parámetros y cuerpo).</li>
<li>Un lugar en donde se reporten estos errores. Recomiendo un servicio de agregación de errores como
<a href="https://rollbar.com/">Rollbar</a> o <a href="https://www.honeybadger.io/">Honeybadger</a>. Y si está conectado a un sistema de
alertas, mejor aún.</li>
<li>Alguien responsable de accionar cuando estos errores ocurren. Por ejemplo, algo que se puede implementar en un equipo
es un rol de <em>triager</em>, en donde una persona del equipo, que va rotando por día o semana, tiene la responsabilidad de
ver los errores que están ocurriendo, analizar su gravedad y reportarlos al lugar correcto o ignorarlos si no
representan un problema real.</li>
</ul>
<h2 id="y-existen-defaults-buenos">¿Y existen defaults buenos?</h2>
<p>¡Por supuesto! Hay una discusión interesante en <a href="https://softwareengineering.stackexchange.com/questions/63908/default-values-are-they-good-or-evil">este hilo de StackExchange</a>
que abarca precisamente este tema. Una de las respuestas presenta un muy buen ejemplo: sabemos que ciertos protocolos
tienen un puerto por defecto asociado, que en el caso de FTP es el puerto 23. Entonces, cuando abrimos una conexión FTP
sin especificar un puerto, es seguro asumir que el 23 va a ser el puerto por defecto. Es algo universalmente conocido y
documentado. En definitva, algo que el dominio nos enseñó.</p>
<h2 id="conclusiones">Conclusiones</h2>
<ul>
<li>Pensemos antes de escribir un <em>default</em>. Preguntémonos lo siguiente: estamos seguros que todos estos casos no
contemplados caigan en esta lógica? Cuáles son los posibles bugs que estamos ocultando? Estamos sobresimplificando el
problema?</li>
<li>Los <em>defaults</em> deben, al igual que cualquier parte del código, tener tests automatizados. De esta menera, tendríamos
una explicación de al menos un caso que nos motivó a introducir este <em>default</em>. Si describimos (a través de un comentario
o en el nombre del test) por qué introducimos esta lógica, puede ayudar a entender este código en el futuro. De esta
manera, podemos continuar haciendo TDD e iterando en caso que necesitemos cambiar esta lógica relacionada al <em>default</em>.</li>
<li>Fallemos rápido. El ciclo de feedback se acorta drásticamente y nos damos cuenta pronto que debemos accionar. No
temamos lanzar errores. Sólo preocupémonos porque el mensaje de error sea lo suficientemente descriptivo para que
podamos accionar.</li>
</ul>Nahuel GarbezzaTrabajar en un proyecto con mucho código heredado siempre es una fuente de grandes aprendizajes. En esta ocasión les presento a los "defaults malvados", como a mí me gustan llamarlos. Son partes de código que pueden hacer mucho daño en una aplicación. Veamos cómo...Lidiando con emergencias: algunas lecciones aprendidas2021-11-12T10:00:00+00:002021-11-12T10:00:00+00:00https://ngarbezza.com.ar/blog/lidiando-con-emergencias-algunas-lecciones-aprendidas<p>Cuando un sistema <em>se cae</em>, todo alrededor se cae… O bueno, quizás no para tanto pero a veces sí. Como en la canción.</p>
<p>Lidiar con una emergencia no es algo que se suele aprender en la universidad, o en <em>bootcamps</em> (pero qué bueno sería,
¿no?) con lo cual las experiencias reales son las que nos van enseñando.</p>
<p>En estos últimos años he formado parte de emergencias relacionadas a sistemas de todos los colores y sabores, algunas
veces en contextos muy desfavorables. ¿Que aprendí? ¡Bastante! Aquí algunos puntos interesantes a tener en cuenta…</p>
<h3 id="mantener-la-calma">Mantener la calma</h3>
<p>Es inevitable que en una situación así domine el nerviosismo, porque dependiendo de qué sistema se trate, las
consecuencias de estar caído pueden ser muy graves. Lo ideal es mantener en lo posible la cabeza fría y pensar con
claridad. Es más fácil decirlo que hacerlo: las primeras veces que me tocó estar en esta situación, además de sufrir
el estrés que genera de por sí, también experimenté parálisis, y en el otro extremo demasiada precipitación; ambos
extremos son perjudiciales. Si tenemos la posibilidad de trabajar en el inconveniente con más de una persona, ambas
pueden darse ánimo o irse relevando. Si estamos en soledad, alejarse un rato del teclado, despejar la vista y volver
con más energías siempre sirve. Un tip que nunca está de más es revisar dos veces antes de ejecutar un comando que
puede ser riesgoso, o verbalizarlo con alguien más que esté trabajando en la solución, con el fin de validar lo que
estamos haciendo.</p>
<h3 id="comprender-la-gravedad-y-el-alcance">Comprender la gravedad y el alcance</h3>
<p>¿Cuántas personas están afectadas? ¿Qué tan afectadas están? ¿Cuánto tiempo podrían permanecer en esa situación? En
resumen, ¿qué tan grave es el problema? Todas preguntas válidas a hacernos al momento de zambullirnos en la resolución.
A veces, no tenemos respuestas a todas estas preguntas; por eso es necesario <strong>trabajar activamente en la
observabilidad</strong> de nuestro sistema: conocer datos de usuaries (por ejemplo dónde se ubican, qué dispositivos usan),
inspeccionar cuestiones de salud general (como tasa de error, o valores de CPU y memoria), y métricas más
particulares/de negocio (tasa de conversión, usuaries teniendo éxito haciendo X tarea, etc).</p>
<h3 id="coordinar-esfuerzos">Coordinar esfuerzos</h3>
<p>Si más de una persona está trabajando para resolver el incidente, es importante mantenerse en sincronía. No pisarse
y a la vez aprovechar lo más posible el tiempo de cada persona. Dividir el incidente en partes para conquistarlo.</p>
<p>En las emergencias que mejor ví manejadas hay algo que siempre sucede: alguien del equipo asume un rol de líder con
la capacidad de ver el problema desde una perspectiva más amplia, mientras que otras personas pueden estar trabajando
de manera enfocada activamente en la resolución. Este liderazgo puede durar toda la emergencia o puede ser más
situacional y otras personas tomarlo (por ejemplo, si trabajamos con personas en otras zonas horarias y en algún
momento hay que hacer un traspaso). Lo que es clave es intentar ser explícito y asertivo en la comunicación.</p>
<p>Otra cosa que caracteriza a un buen manejo de una emergencia es, sobre todo en aquellas que duran varias horas o días,
hacer chequeos frecuentes con las personas involucradas. <em>Parar la pelota</em>, reagruparse y pensar un poco
estratégicamente.</p>
<h3 id="mantener-comunicaciones-y-registro-de-lo-que-va-sucediendo">Mantener comunicaciones y registro de lo que va sucediendo</h3>
<p>Es lógico que a medida que intentemos resolver el problema hagamos cambios (que pueden impactar positivamente o
negativamente). Por ejemplo, cambiar una variable de ambiente o reiniciar algún servicio.</p>
<p>Por ejemplo, si cambio una variable de entorno, recomiendo guardar el valor anterior, y restaurarlo si eso no resolvió
el problema. Si reiniciamos un servicio, y sabemos que va a tardar x minutos, podemos avisar a las personas que podrían
estar afectadas. También si hay más personas desarrollando puede ser necesario avisar que no se hagan nuevos despliegues
o cambios que podrían alterar aún más la situación. Recordemos que el objetivo es fijar la mayor cantidad de variables
posible.</p>
<p>En mi experiencia, esto funciona bastante bien cuando se forma un equipo para resolver la emergencia, y <strong>una persona
asume ese rol</strong>. Entonces el resto está enfocada en la resolución del problema propiamente dicho, y la persona
designada sirve de nexo con el resto de la organización y/o usuaries afectades.</p>
<h3 id="no-sacar-conclusiones-apresuradas">No sacar conclusiones apresuradas</h3>
<p>Uno de los consejos más importantes que me gustaría destacar. A veces, por desear que el problema esté resuelto,
podemos autoconvencernos de que una solución es la definitiva. Seguramente surjan hipótesis (ej: <em>“debe ser la versión
de la base de datos que tenemos en producción”</em>) Es importante validar estas hipótesis antes de intentar una solución.
También, entender el impacto que podría tener cuando hagamos un cambio. Recordemos que debemos intentar, en lo posible,
de no empeorar la situación alterando más cosas. Nuestro rol es como el de un cirujano: estamos operando con un alto
nivel de riesgo, pero debemos valernos de todas las señales que nos da el cuerpo (en nuestro caso el sistema) que
podamos monitorear, y debemos saber que muchas cosas que intentemos van a tener consecuencias.</p>
<h3 id="decisión-fix-parche-workaround-o-rollback">Decisión: fix, parche, workaround o rollback</h3>
<p>A muy alto nivel, una emergencia podría atacarse con alguno de estos 4 enfoques:</p>
<ul>
<li><em>Fix</em>: intentar arreglar el problema de la manera correcta y definitiva. Por ejemplo, si el problema es un bug en el
código, escribir y poner en producción el código que lo arregla, idealmente con sus correspondientes pruebas
automatizadas que nos permitan detectar preventivamente si ocurre de nuevo.</li>
<li><em>Parche</em>: intentar una solución temporal, que potencialmente nos pueda sacar más rápido de la emergencia. Esto nos
deja deuda técnica que deberemos resolver una vez que estemos de nuevo en un estado de normalidad.</li>
<li><em>Workaround</em>: No resolver el problema, sino a recurrir a una solución con la que podamos vivir mientras el problema
se está solucionando. Por ejemplo: si un proceso automatizado está caido, ¿podemos seguir haciéndolo pero de manera
manual?</li>
<li><em>Rollback</em>: restaurar el sistema a su estado anterior, para poder trabajar con más tranquilidad en el problema. Hay
casos en donde se puede hacer esto (por ejemplo, cuando sospechamos que un reciente cambio causó el problema), otros
en donde no (por ejemplo, si estamos ante un ataque de DoS)</li>
</ul>
<p>No es necesario elegir uno, si hay varias personas involucradas se pueden explorar varias soluciones en paralelo;
generalmente vamos a elegir la que más rápido nos saque de la emergencia.</p>
<h3 id="recolectar-aprendizajes">Recolectar aprendizajes</h3>
<p>Una vez resuelta la emergencia, y de vuelta en el ritmo normal de trabajo, inmediatamente es interesante hacer una
reflexión para comprender por qué pasó lo que pasó, cómo podríamos haberlo detectado antes que se vuelva un problema,
y que podríamos hacer mejor en el futuro. Para esto recomiendo las sesiones de <em>post mortem</em>, que es una forma de
estructurar este análisis de manera que, por un lado, deje registro de lo sucedido (la gravedad, quienes estuvieron
involucrades, los tiempos e hitos desde que se detecto el problema hasta que se solucionó por completo) y también
sirva como espacio para <strong>reflexionar sin buscar culpables</strong>, y preguntarse <em>por qué</em> la cantidad de veces necesaria.</p>
<h2 id="resumiendo">Resumiendo</h2>
<ul>
<li>En lo posible, definir un equipo de trabajo con roles claros y explícitos</li>
<li>Mantener la comunicación con el resto de personas involucradas en el problema (¡esto incluye a usuaries también!)</li>
<li>Mantener la calma y no apresurarse con soluciones</li>
<li>Tener un pensamiento “fuera de la caja” y pensar posibles soluciones a corto plazo</li>
<li>Intentar no agravar la situación introduciendo más cambios</li>
<li>Luego de resuelto el problema, analizar en detalle qué sucedió y pensar oportunidades de mejora</li>
</ul>Nahuel GarbezzaQué hacer, y sobre todo, qué no hacer cuando un sistema se cae. Algunas ideas para atravesar lo mejor posible esta experiencia y aprender mucho en el proceso.Contando colaboraciones en Cuis Smalltalk2021-07-29T23:00:00+00:002021-07-29T23:00:00+00:00https://ngarbezza.com.ar/blog/contando-colaboraciones-en-cuis-smalltalk<p>Continuamos la serie de artículos sobre contar colaboraciones como una métrica de calidad de software. Si no viste la
primer parte, te recomiendo leerla ya que allí explico de dónde sale esta métrica y por qué creo que es mejor que
contar líneas de código.</p>
<p>Recordemos brevemente a qué llamábamos colaboración: <strong>envío de un mensaje a un objeto</strong>. Entonces, lo que nos interesa
medir es cuántos envíos de mensaje aparecen en un método, ya que de esa manera podemos entender cuántas
responsabilidades o decisiones ocurren en un mismo lugar. Idealmente, queremos reducir ese número tanto como sea
posible.</p>
<p>Entonces, ¿podemos hacer un programa que cuente las colaboraciones de un método? ¡Sí! Aquí voy a comentar en detalle
la solución hecha en <a href="https://github.com/Cuis-Smalltalk/Cuis-Smalltalk-Dev">Cuis Smalltalk</a>, un ambiente Smalltalk de
código abierto y orientado a la simplicidad, del que tengo el orgullo de haber realizado algunas contribuciones.</p>
<p>¿Por qué esta elección de lenguaje? Al ser los ambientes Smalltalk reflexivos y metacirculares, es muy sencillo
manipular un programa de la misma manera que manipulamos, por ejemplo, una lista, una cuenta bancaria, o cualquier
objeto que se les ocurra. Y además contamos con herramientas que nos permiten visualizar estos objetos ¡y testearlos
como cualquier otro!</p>
<h3 id="empezar-por-el-principio-los-tests">Empezar por el principio: los tests</h3>
<p>Vamos a hacer este contador con TDD, como no podía ser de otra manera. Y claro, al momento de escribir
<a href="https://blog.10pines.com/2020/08/18/el-primer-test/">el primer test</a>, y sobre todo en un metaprograma como éste, nos
puede resultar un poco difícil. Pensemos el caso más sencillo de alguien que quiera contar colaboraciones: ¡cuando no
hay ninguna! Vamos a crearnos una clase de test, que vamos a usarla para dos propósitos: escribir los tests
correspondientes a nuestro contador, y tener métodos de prueba con escenarios que nos sirvan para estos tests. Entonces
el primero de estos escenarios es un método vacío:</p>
<div class="language-smalltalk highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nc">CollaborationsCounterTest</span> <span class="nf">>></span> <span class="ss">#emptyMethod</span>
<span class="c">"...nada por aquí..."</span>
</code></pre></div></div>
<p>Y el primer test quedaría planteado de la siguiente manera:</p>
<div class="language-smalltalk highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nc">CollaborationsCounterTest</span> <span class="nf">>></span> <span class="ss">#test01ItCountsZeroCollaborationsInAnEmptyMethod</span>
<span class="nf">|</span> <span class="nv">counter</span> <span class="nf">|</span>
<span class="nv">counter</span> <span class="o">:=</span> <span class="nc">CollaborationCounter</span> <span class="nf">for:</span> <span class="bp">self</span> <span class="nf">class</span> <span class="nf">>></span> <span class="ss">#emptyMethod</span><span class="p">.</span>
<span class="bp">self</span> <span class="nf">assert:</span> <span class="m">0</span> <span class="nf">equals:</span> <span class="nv">counter</span> <span class="nf">value</span>
</code></pre></div></div>
<p>Pequeña referencia para ubicarnos mejor en el mundo Smalltalk: el mensaje <code class="language-plaintext highlighter-rouge">>></code> nos permite obtener un método compilado
(instancia de <code class="language-plaintext highlighter-rouge">CompiledMethod</code>) de la clase que recibe el mensaje (en este caso, la misma clase de test).</p>
<p>Luego de escribir el primer test… que nos guíen los <a href="https://www.agilealliance.org/resources/sessions/test-driven-development-guided-by-zombies/">ZOMBIES</a>.
Esta técnica, de James Greening, consiste en organizar tu estrategia de tests de manera de plantearlos en el siguiente
orden: <code class="language-plaintext highlighter-rouge">(Z)ero, (O)ne, (M)any, (B)oundaries, (I)nterfaces, (E)xceptional Behavior</code>. Es decir, empezar con el caso de
cero elementos que probablemente sea el más sencillo de hacer pasar, luego seguir con uno, con muchos, con casos borde,
luego con aquellos tests que terminen de definir nuestra interfaz con el mundo exterior, y por último el comportamiento
excepcional. El acrónimo cierra con la <em>“S”</em> de <em>“Simple solutions, simple scenarios”</em> que nos recuerda lo importante
de lo simple que nos conviene que sean los casos de prueba que vayamos escribiendo.</p>
<p>Así, los tests que fuimos construyendo paso a paso fueron los siguientes: (copio sólo los nombres para no redundar en
código de test que es muy similar al de <code class="language-plaintext highlighter-rouge">test01</code> pero con diferentes valores esperados):</p>
<div class="language-smalltalk highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="ss">#test01ItCountsZeroCollaborationsInAnEmptyMethod</span>
<span class="ss">#test02ItCountsOneCollaborationInAMethodWithOnlyOneMessageSend</span>
<span class="ss">#test03ItCountsTwoCollaborationsThatArePlacedInDifferentsStatements</span>
<span class="ss">#test04ItCountsTwoCollaborationsThatArePlacedInTheSameMessageSend</span>
<span class="ss">#test05ItCountsThreeCollaborationsFromACascadeMessage</span>
<span class="ss">#test06ItCountsThreeCollaborationsInsideBlocks</span>
</code></pre></div></div>
<h3 id="la-implementación">La implementación</h3>
<p>La clave es poder tener un objeto que recorra el código de nuestro método bajo análisis e identifique cada vez que se
envíe un mensaje para sumar uno en un contador. Para ello, no vamos a recorrer el código fuente sino que vamos a
recorrer su representación en <a href="https://es.wikipedia.org/wiki/%C3%81rbol_de_sintaxis_abstracta">Árbol de Sintaxis Abstracta</a>
(AST, por sus siglas en inglés) que es muchísimo más fácil de manipular. En Cuis, como en la mayoría de los sistemas
Smalltalk, tenemos un objeto <code class="language-plaintext highlighter-rouge">MethodNode</code> que representa un método en su totalidad y sería la raíz de nuestro árbol de
sintaxis. Luego vienen los diferentes elementos sintácticos como “hojas” de ese árbol (asignaciones, retornos, envíos
de mensajes, variables, bloques…). Y como cada tipo de nodo está asociado a una clase diferente, podemos gracias al
polimorfismo saber con precisión, por ejemplo, cuándo estamos en un <code class="language-plaintext highlighter-rouge">MessageNode</code> (nodo que representa envío de
mensaje).</p>
<p>Dijimos que íbamos a recorrer nuestro código, pero para ser estrictos, tampoco vamos a recorrer, sino que vamos
interactuar con otro objeto que lo haga por nosotros (<em>classic</em> orientación a objetos 🤣). Esta estructura de
<code class="language-plaintext highlighter-rouge">MethodNode</code> y sus nodos hijos necesita ser recorrida por varias tareas con fines diversos. Esto es un buen caso de uso
para el patrón <a href="https://refactoring.guru/es/design-patterns/visitor">Visitor</a>, que justamente propone la idea de objeto
“visitante” que sabe cómo ir recorriendo cada elemento de una estructura usando mensajes polimórficos para todos los
diferentes tipos de elementos con los que se puede encontrar.</p>
<p>Es por eso entonces que en Cuis tenemos al <code class="language-plaintext highlighter-rouge">ParseNodeVisitor</code>. Una clase abstracta que sabe cómo recorrer la estructura
de <em>parse nodes</em>, pero no hace nada. La idea es crear una subclase que haga algo en los pasos en los que nos podemos
encontrar con envíos de mensajes. Es por eso que entonces nuestro <code class="language-plaintext highlighter-rouge">CollaborationCounter</code> redefine dos pasos del
<code class="language-plaintext highlighter-rouge">ParseNodeVisitor</code> en los que aparecen envíos de mensajes:</p>
<div class="language-smalltalk highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nc">CollaborationCounter</span> <span class="nf">>></span> <span class="ss">#visitMessageNode:</span> <span class="nf">aMessageNode</span>
<span class="nf">super</span> <span class="nf">visitMessageNode:</span> <span class="nv">aMessageNode</span><span class="p">.</span>
<span class="bp">self</span> <span class="nf">countOneCollaboration</span>
<span class="nf">CollaborationCounter</span> <span class="nf">>></span> <span class="ss">#visitMessageNodeInCascade:</span> <span class="nf">aMessageNode</span>
<span class="nf">super</span> <span class="nf">visitMessageNodeInCascade:</span> <span class="nv">aMessageNode</span><span class="p">.</span>
<span class="bp">self</span> <span class="nf">countOneCollaboration</span>
</code></pre></div></div>
<p>Nótese el uso de <code class="language-plaintext highlighter-rouge">super</code> para continuar haciendo lo mismo que hace la superclase (<code class="language-plaintext highlighter-rouge">ParseNodeVisitor</code>) que sabe cómo
continuar la visita del árbol de sintaxis. La implementación de <code class="language-plaintext highlighter-rouge">#countOneCollaboration</code> es trivial, suma uno a una
variable de instancia inicializada en 0:</p>
<div class="language-smalltalk highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nc">CollaborationCounter</span> <span class="nf">>></span> <span class="ss">#countOneCollaboration</span>
<span class="nf">numberOfCollaborations</span> <span class="o">:=</span> <span class="nv">numberOfCollaborations</span> <span class="nf">+</span> <span class="m">1</span>
</code></pre></div></div>
<p>Luego, la implementación de <code class="language-plaintext highlighter-rouge">#value</code> necesita iniciar este recorrido de nuestro objeto visitante y luego devolver el
resultado obtenido:</p>
<div class="language-smalltalk highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nc">CollaborationCounter</span> <span class="nf">>></span> <span class="ss">#value</span>
<span class="nf">methodToAnalyze</span> <span class="nf">methodNode</span> <span class="nf">accept:</span> <span class="bp">self</span><span class="p">.</span>
<span class="o">^</span> <span class="nv">numberOfCollaborations</span>
</code></pre></div></div>
<p>Dos cosas a tener en cuenta:</p>
<ul>
<li><code class="language-plaintext highlighter-rouge">methodToAnalyze</code> es el método compilado que estamos analizando. Si le enviamos el mensaje <code class="language-plaintext highlighter-rouge">#methodNode</code> obtenemos
como resultado el <code class="language-plaintext highlighter-rouge">MethodNode</code> que representa nuestro árbol de sintaxis de nuestro método.</li>
<li><code class="language-plaintext highlighter-rouge">MethodNode</code> sabe “aceptar visitas” a través del mensaje <code class="language-plaintext highlighter-rouge">#accept:</code>. Aquí es donde invocamos al <em>visitor</em>.</li>
</ul>
<h3 id="la-demo">La demo!</h3>
<p>Para que la herramienta sea más fácil de utilizar extendí el panel de “annotation” del Browser de Cuis para que cada
vez que estemos visualizando un método, podamos ver cuántas colaboraciones tiene. Así se ve!</p>
<p><img src="/demos/collaborations-counter-cuis-smalltalk.gif" alt="Contador de colaboraciones visualizado en el Browser de Cuis Smalltalk" title="Contador de colaboraciones visualizado en el Browser de Cuis Smalltalk" /></p>
<p>Todo el código y las instrucciones de instalación están acá: <a href="https://github.com/ngarbezza/Cuis-Smalltalk-Utilities#collaborations-counter">https://github.com/ngarbezza/Cuis-Smalltalk-Utilities#collaborations-counter</a>.</p>
<p>En las próximas ediciones, veremos cómo resolver esto mismo en otros lenguajes de programación.</p>Nahuel GarbezzaContinuamos la serie de artículos sobre contar colaboraciones como una métrica de calidad de software. En esta ocasión, vamos a escribir un programa que cuente colaboraciones de un método en Cuis Smalltalk.Contar colaboraciones: una métrica que importa mucho2021-07-18T23:00:00+00:002021-07-18T23:00:00+00:00https://ngarbezza.com.ar/blog/contar-colaboraciones-una-metrica-que-importa-mucho<h2 id="medir-en-el-mundo-del-software">Medir en el mundo del software</h2>
<p>Es bien sabida la frase “no puedes mejorar lo que no puedes medir”. También es cierto que si el instrumento de medición no es preciso, las conclusiones que podamos sacar al respecto tampoco lo van a ser. También es cierto que podemos elegir métricas solo porque nos resulta fácil contarlas o porque nos resultan convenientes.</p>
<p>También hay métricas que no nos ayudan a mejorar, solo quizás a sentirnos en un falso lugar de bienestar. Las llamadas métricas de vanidad. Por ejemplo, cantidad de commits en un intervalo de tiempo. Dice algo de la calidad de lo que hacemos? Una persona es mejor que otra porque hace más commits?</p>
<p>En resumen; hay que tener cuidado con lo que medimos y sobre todo, cómo actuamos en base a eso.</p>
<p>En esta ocasión les invito a reflexionar sobre la métrica de <strong>cantidad de líneas de código</strong> (SLOC, source lines of code). En mi opinión, es una métrica que sirve para algunas cosas y para muchas otras deja que desear y debemos apuntar a buscar mejores métricas.</p>
<h2 id="qué-nos-pasa-con-las-líneas-de-código">Qué nos pasa con las líneas de código</h2>
<p>Un sistema puede tener un millón de líneas de código y ser un software muy saludable, desacoplado, con muchos tests. Mientras que otro pueden ser apenas unas cientas y ser el código más terrible que hayamos visto. Con lo cual, no la podemos usar para comparar un programa con otro.</p>
<p>El formato del código nos altera esta métrica. Por ejemplo podríamos tener un formatter en Java que a aquellos parámetros que tengan más de un largo x los ubique en una línea aparte (digo Java porque las firmas de métodos suelen ser más largas que en otros lenguajes). Ese extra de líneas hace que mi software sea peor? No tiene mucho sentido este razonamiento.</p>
<p>Siguiendo en la línea del formato, existe en algunxs programadorxs una afición por los one-liners, expresiones que resuelven algo largo o difícil con código que cabe en una línea. Esas solo suman 1 en nuestra métrica, pero cuanto suman en complejidad?</p>
<p>Y si lo pensamos muy bien, el hecho que escribimos diferentes líneas de código es una cuestión accidental, y el umbral de cuan ancha es una línea depende del ancho de nuestra pantalla o lo que se nos ocurra configurar en el <em>linter</em>. Incluso hay personas que al día de hoy se preocupan porque su código tenga líneas de no más de 80 caracteres, cuando este límite tenía sentido enla época de las tarjetas perforadas. Podríamos escribir todo nuestro código en una línea, poner un word-wrap o algún otro chiche visual para ver el código distinto y ahí si que jodemos bien jodida a la métrica.</p>
<p>Entonces, ¿debemos dejar de medir líneas de código? No creo que haya que tomar medidas tan drásticas, hay dos casos en los que le encuentro sentido a utilizarlas:</p>
<ul>
<li><strong>Ver evolución en el tiempo:</strong> sabemos que no tiene sentido comparar diferentes módulos o programas en términos de líneas de código, pero un mismo módulo o programa contra otra versión anterior o posterior suya puede ser interesante. Podemos hacer un gráfico y ver la evolución durante semanas, meses o años. Y en base a lo que vemos, podemos percibir qué tanto crece o decrece nuestro software, y de qué manera lo hace. Algo esperado podría ser que crezca linealmente a medida que le agregamos funcionalidad. Si crece exponencialmente, estamos ante un potencial problema de mantenimiento. Si crece, aún sin agregar funcionalidad, puede ser un indicador de que estamos agregando parches a nuestro código. También puede bajar! Lo que puede indicar que estamos generando abstracciones o simplemente quitando funcionalidad con el objetivo de hacerlo más simple.</li>
<li><strong>Encontrar <em>hotspots</em> en el código:</strong> la idea de <a href="https://understandlegacycode.com/blog/focus-refactoring-with-hotspots-analysis/">hotspot</a> (en castellano se podría traducir como “punto de atención”) es interesante cuando analizamos sistemas heredados, que son muy grandes o simplemente que no conocemos. En sistemas así, es muy difícil hacer análisis precisos y detallados porque ni siquiera sabemos por dónde empezar a mirar. Entonces, hacer un pantallazo rápido y ver, por ejemplo, líneas de código por archivo o clase podría ser un indicador que al menos nos ayude a encarar este sistema no por “el” lugar correcto, sino por “un” lugar que necesita nuestra atención. Este análisis, combinado con algunos otros, como por ejemplo el de churn vs. complexity (cuantas veces lo cambiamos vs. qué tan complejo es) nos puede resumir un módulo o una parte de nuestro sistema a un número que podemos trabajar en reducir a lo largo del tiempo.</li>
</ul>
<p>En ambos casos lo que podemos hacer es una aproximación que debemos complementar con más métricas y análisis, no nos sirven para analizar en detalle parámetros de calidad.</p>
<h2 id="una-métrica-superadora">Una métrica superadora</h2>
<p>Si no medimos líneas de código, entonces, ¿qué podemos medir? Mi propuesta es bastante simple: <strong>medir cantidad de colaboraciones</strong>. Es decir, cada vez que se le envía un mensaje a un objeto.</p>
<p>Cuando, dentro de un método, enviamos un mensaje a un objeto, estamos coordinando la realización de una parte de nuestro problema. Si enviamos muchos mensajes, a nuestro propio objeto o a otros, lo que está pasando es que quizás estamos tomando mucha responsabilidad y nos estamos perdiendo la oportunidad de delegar (recordemos la frase “¿no podría hacerlo otro?” de Homero Simpson como slogan de campaña… 🤣).</p>
<p>También lo que ocurre es que nos genera una carga mental alta leer varias colaboraciones. En líneas generales, entender lo que hace un método nos debería llevar unos pocos segundos. Claramente, tener más colaboraciones aumenta ese tiempo de lectura, a veces de manera lineal y a veces hasta de manera exponencial.</p>
<h2 id="cómo-seguimos">Cómo seguimos</h2>
<p>En próximos posts, vamos a ver cómo calcular esta métrica en diferentes lenguajes de programación. Es un lindo (¡y útil!) ejercicio de metaprogramación (o sea, programación cuando te enfocás en el dominio de la programación). ¡Nos leemos pronto!</p>Nahuel GarbezzaContar líneas de código es una métrica cuestionable. Veamos cuáles son sus ventajas y desventajas, y cómo podríamos tener una métrica superadora contando envíos de mensajes.Las 12 propiedades deseables de los tests según Kent Beck2021-04-30T00:00:00+00:002021-04-30T00:00:00+00:00https://ngarbezza.com.ar/blog/las-12-propiedades-deseables-de-los-tests-segun-kent-beck<h2 id="test-desiderata-lo-qué">Test Desiderata: “¿lo qué?”</h2>
<p>En octubre de 2019, Kent Beck publicó un breve y excelente artículo llamado “Test Desiderata” donde resume qué características, cualidades o propiedades deseamos que los tests tengan; lo que (sorpresivamente para Kent) no es algo que esté cubierto por sus libros y posts sobre TDD y que, según sus palabras, “dio por sentado”. El resultado es un post con un excelente nivel de síntesis y de importancia del tema, un post al que vuelvo repetidas veces porque, en mi opinión, es clave para cualquier persona que practique TDD.</p>
<p>Adicionalmente, también hay unas mini-sesiones de videos con ejemplos prácticos que ilustran cada una de estas propiedades. Todo el material está disponible en Youtube:</p>
<p>https://www.youtube.com/watch?v=5LOdKDqdWYU</p>
<h2 id="las-12-propiedades">Las 12 propiedades</h2>
<p>Veamos a qué se refiere Kent con cada una de estas 12 propiedades:</p>
<blockquote>
<p>Isolated — tests should return the same results regardless of the order in which they are run.</p>
</blockquote>
<p>El hecho de que un test esté aislado del contexto en el que corre es fundamental. Citando a Hernán Wilkinson: “el test tiene que estar en control de todo”. Esto implica asegurarse de un inicio limpio, una ejecución libre de intrusiones externas y no asumir cosas que puedan ocurrir por fuera del test.</p>
<blockquote>
<p>Composable — if tests are isolated, then I can run 1 or 10 or 100 or 1,000,000 and get the same results.</p>
</blockquote>
<p>Consecuencia del punto anterior: los tests deberían poder agruparse de cualquier manera y los resultados deberían ser determinísticos.</p>
<blockquote>
<p>Fast — tests should run quickly.</p>
</blockquote>
<p>Uno que damos por sentado, ¡y que es fácil de perder! Para que TDD funcione, tiene que haber feedback inmediato y, justamente, la parte de “inmediato” se refiere principalmente al tiempo entre que escribimos el test y vemos su resultado. Si esperamos dos minutos para ver el resultado del test que escribimos, cabe preguntarse ¿cuál será el efecto después de 50, 100 ciclos de TDD? Aprendemos, pero no aprendemos rápido. Tomamos atajos. Dejamos de hacer TDD. Para poner algo de números en contexto, recordemos la definición de unit test de Michael Feathers: un test que tarda menos de 1/10 de segundo en ejecutarse.</p>
<blockquote>
<p>Inspiring — passing the tests should inspire confidence</p>
</blockquote>
<p>Lo más importante de un test no es que pase o no, sino que tanto al pasar como al fallar nos brinde seguridad. Que no reporte falsos positivos, ni falsos negativos. Que pueda sentir que puedo deployar mi código un viernes por la tarde luego de ver que todos los tests pasan. Siempre es importante ver a cada test fallar. Si estamos desarrollando con TDD eso va a ocurrir al inicio de cada ciclo. En caso de trabajar con tests existentes, podemos usar mutation testing para forzar a que el test falle.</p>
<blockquote>
<p>Writable — tests should be cheap to write relative to the cost of the code being tested.</p>
</blockquote>
<p>Escribir tests automatizados siempre requiere de un esfuerzo. Hay que trabajar activamente para minimizarlo (las consecuencias de no hacerlo afectan otros puntos mencionados aquí también, como la legibilidad; y también el extremo de dejar de escribir tests porque nos resulta costoso). Crear datos de prueba debería ser sencillo, lo mismo con las aserciones, o cualquier otra cosa necesaria de contexto (configurar inputs adicionales, por ejemplo).</p>
<blockquote>
<p>Readable — tests should be comprehensible for reader, invoking the motivation for writing this particular test.</p>
</blockquote>
<p>Recordemos que leemos código, mucho más de lo que escribimos. En varias ocasiones, leemos tests que otras personas escribieron y otras personas leen los tests que escribimos. Es importante que tanto la descripción del test como el código hablen de negocio y no de implementación, que refieran claramente al escenario que están testeando y que den el menor lugar posible a la ambigüedad. Con respecto al código, en este aspecto es muy útil contar con nuestras propias aserciones “de negocio”, que nos pueden abstraer un conjunto de aserciones básicas y ayudar a la persona que lee a comprender más rápidamente qué es lo que se espera. Recordemos también que la aserción es la parte más importante del test! Si nos perdemos en su lectura, probablemente no terminemos de comprender el test.</p>
<blockquote>
<p>Structure-insensitive — tests should not change their result if the structure of the code changes.</p>
</blockquote>
<blockquote>
<p>Behavioral — tests should be sensitive to changes in the behavior of the code under test. If the behavior changes, the test result should change.</p>
</blockquote>
<p>Cambios de implementación, refactorings y rediseños no deberían generar cambios en los tests. Si eso ocurre, es que tenemos tests denominados “de caja blanca”, muy comunes cuando se utilizan test doubles. Por el contrario, los buenos tests tienen aserciones que hablan sobre el comportamiento del sistema. Entonces si éste cambia, las aserciones deben cambiar también.</p>
<blockquote>
<p>Automated — tests should run without human intervention.</p>
</blockquote>
<p>Volviendo a la cuestión de “el test debe estar en control de todo”: esto quiere decir que si un test necesita un dato de entrada, una confirmación o algún paso que eligen/deciden les usuaries, éso debe estar simulado con valores concretos y debe ser una precondición para que el test sea válido. De esta manera, un test se puede correr en un ambiente donde no es posible intervenir manualmente, como un servidor de integración continua.</p>
<blockquote>
<p>Specific — if a test fails, the cause of the failure should be obvious.</p>
</blockquote>
<p>Partamos de la base del valor que tiene el feedback, en su carácter de inmediato, pero también en su carácter de preciso. Si un test dice solamente “failed” y no podemos asociar ese error con la parte del código causante del problema rápidamente, entonces muy probablemente el test necesite escribirse mejor.</p>
<blockquote>
<p>Deterministic — if nothing changes, the test result shouldn’t change.</p>
</blockquote>
<p>Como desarrolladorxs, estamos todo el tiempo viendo diferentes resultados de tests, tanto exitosos como fallidos. Y siempre, muchas veces rápida e inconscientemente, correlacionamos el resultado con el input del test y su comportamiento para sacar conclusiones. Si no existe el determinismo, perdemos la confianza en este análisis, lo que resulta en confusión y desarrollos más lentos, porque, recordemos, una de las claves de TDD es ir avanzando en cada paso con muy alta seguridad y confianza en que nuestro sistema está funcionando como esperamos (en oposición a no escribir tests, o escribirlos luego de escribir el código).</p>
<blockquote>
<p>Predictive — if the tests all pass, then the code under test should be suitable for production.</p>
</blockquote>
<p>Esto habla de la paridad que debe existir entre el ambiente de test y el/los ambiente/s productivo/s. De poco sirve un test que no refleja lo que realmente ocurre en producción. Y esto no sólo se refiere al test coverage, sino también a contemplar todas las variables que puedan causar bugs más allá de código que ningún test ejercita. Un ejemplo común es olvidar sobre qué datos estamos operando. Supongamos que en producción tenemos una inconsistencia de datos y que eso afecta el comportamiento de lo que estamos desarrollando. Deberíamos tener un test que considere ese caso, o arreglar la inconsistencia.</p>
<h2 id="conclusiones">Conclusiones</h2>
<p>Podemos observar que la pérdida de cualquiera de estas 12 propiedades genera un impacto negativo en la experiencia de TDD inmediatamente. ¡Así que es muy bueno tenerlas en mente!</p>
<p>Para reflexionar: los tests que escribimos, ¿cumplen con estas propiedades? ¿en qué grado?</p>Nahuel GarbezzaUn análisis sobre el artículo "Test Desiderata" escrito por Kent Beck, en donde se listan 12 propiedades deseables de los tests.3 cosas que todo mensaje de error debe tener2021-03-07T20:00:00+00:002021-03-07T20:00:00+00:00https://ngarbezza.com.ar/blog/3-cosas-que-todo-mensaje-de-error-debe-tener<h2 id="introducción">Introducción</h2>
<p>Una de las cosas que como desarrolladorxs tendemos a subebstimar o a no prestar mucha atención es la escritura de mensajes
de error.</p>
<p>Voy a mostrar un ejemplo a lo largo del artículo, que recientemente tuve que implementar para <a href="/proyectos#Testy">Testy</a>.
Este proyecto tiene textos internacionalizados, y lo que quería lograr era validar que se estaba configurando un idioma
soportado por la herramienta. Si esto no ocurre, deberíamos mostrar un mensaje explicando qué sucedió.</p>
<h2 id="cuál-es-el-problema">Cuál es el problema</h2>
<p>Para empezar, el error debe poner en tema a quien lee. ¿Cuál es la naturaliza de este problema? ¿Es un valor inválido?
¿Es algo temporal o inesperado?. Lo más claro que pueda ser el mensaje, mejor. Pero, si estamos haciendo visible un mensaje
de error, quizás no querramos decir algo como <code class="language-plaintext highlighter-rouge">MySQL error 1022</code>, que es demasiado técnico; hay que interpretarlo y
convertirlo en un mensaje más amigable.</p>
<p>En nuestro ejemplo, el problema es que el idioma que estamos tratando de usar no está soportado por la herramienta
todavía. Pero, decir <code class="language-plaintext highlighter-rouge">"Idioma no soportado"</code> no es suficiente…</p>
<h2 id="dónde-estuvo-el-problema">Dónde estuvo el problema</h2>
<p>Ahora que sabemos que ocurrió un problema, y quizás ya sepamos en qué parte del software se generó la falla. Pero, ¿cómo
llegamos a esa situación? ¿Hicimos algo malo o la herramienta falló? ¿Dimos algún valor inválido?</p>
<p>Volviendo al ejemplo, quizás querramos indicar cuál fue exactamente el dato que generó el problema, y hacerlo parte del
mensaje de error. Mejorando el texto anterior diríamos algo como <code class="language-plaintext highlighter-rouge">"Idioma 'klingon' no soportado"</code>. ¿Estamos bien? Casi,
podemos hacerlo aún mejor…</p>
<h2 id="cómo-resolvemos-ese-problema">Cómo resolvemos ese problema</h2>
<p>Ahora ya tenemos más información, sabemos exactamente qué dato fue el que generó el problema. Pero entonces, ¿qué dato
debo usar? ¿Hay una lista de valores permitidos? Si era un problema temporal, ¿cuándo se recomienda intentar de nuevo?
Recordemos, un buen software es el que nos enseña cómo usarlo, mientras lo usamos.</p>
<p>Mejoremos nuestro ejemplo agregándole los valores posibles para esta configuración de idioma: <code class="language-plaintext highlighter-rouge">"Idioma 'klingon' no soportado. Por favor, elegir alguno de los siguientes idiomas: 'spanish', 'english'."</code></p>
<h2 id="conclusiones">Conclusiones</h2>
<p>No perdamos de vista estos 3 aspectos, y ante cada situación de error nos podemos hacer estas 3 preguntas y ver si
nuestro mensaje las está respondiendo. SI obtuviste un mensaje de error y tuviste que buscar en internet qué significaba,
es una señal de que es un mensaje de error pobre. No vale la pena “ahorrar tiempo” escribiendo mensajes de error breves.
Si te da curiosidad cómo implementé este ejemplo, aquí está <a href="https://github.com/ngarbezza/testy/blob/develop/lib/i18n.js#L106">el código</a>,
y aquí <a href="https://github.com/ngarbezza/testy/blob/develop/tests/core/i18n_test.js#L69">el test</a> (claro, ¡tampoco olvides testearlo!)</p>Nahuel Garbezza¿Cuál es el problema? ¿dónde está el problema? ¿cómo resolvemos ese problema? 3 preguntas que te ayudarán a construir cualquier mensaje de errorEl arte de las pequeñas mejoras2020-12-28T13:00:00+00:002020-12-28T13:00:00+00:00https://ngarbezza.com.ar/blog/el-arte-de-las-peque%C3%B1as-mejoras<h2 id="intro">Intro</h2>
<p>Refactorizar es <em>lindo</em>. Si fuera por nosotres, pasaríamos el día entero escribiendo y reescribiendo una pieza de software para que sea más entendible, más robusta, mantenible, y <inserte su="" requerimiento="" no="" funcional="" favorito="" aquí="">.</inserte></p>
<p>Pero la realidad nos presenta restricciones (tiempo, alcance, tecnológicas, políticas). Ahí es donde la creatividad toma importancia y debemos lograr un alto impacto en poco tiempo, mientras hacemos crecer nuestro software.</p>
<h2 id="experimentación">Experimentación</h2>
<p>Algo que hacemos cuando trabajamos con TDD es buscar activamente el feedback lo más rápido posible. Con los refactorings debemos hacer lo mismo, aunque por alguna razón tendemos a apuntar a un cierto grado de perfección y feedback loops más largos…</p>
<p>Una cosa muy útil es tener líneas de trabajo experimentales, de vida muy corta y que puedan ser validadas por un servidor de integración continua.</p>
<p>Por ejemplo: sospechamos que un parámetro de configuración está haciendo que los tests sean más lentos; entonces ponemos el cambio en un branch, lo pusheamos y esperamos a ver el resultado de CI. Podemos observar 3 resultados:</p>
<ol>
<li>confirmamos nuestra hipótesis y el tiempo de los tests baja, pudiendo asociar nuestro cambio a la baja del tiempo (dicho en otras palabras, no fue casualidad)</li>
<li>confirmamos que la configuración no tiene relevancia o incluso aporta negativamente a resolver el problema</li>
<li>el resultado es inconcluso, depende de más variables y/o necesitamos más información.</li>
</ol>
<p>En caso de (2) o (3) el experimento se descarta y eventualmente se podrán generar otras variaciones que tengan en cuenta los resultados de este experimento.</p>
<p>A modo de referencia, últimamente de cada 10 intentos de mejoras que pruebo, estaré descartando entre 7 y 8. Pero no es algo para ver como fracaso, sino como aprendizaje para plantear cada vez mejores experimentos.</p>
<p>Y si es difícil medir, hay que ir un pasito atrás y asegurar eso antes de encarar grandes proyectos de mejora. Tener métricas “técnicas” es clave para medir nuestros experimentos.</p>
<p>Todo esto se puede resumir en dos palabras: “permitirnos fallar”.</p>
<h2 id="el-valor-del-equipo-y-la-recurrencia">El valor del equipo y la recurrencia</h2>
<p>Una mejora pequeña pasa a ser insignificante cuando ocurre una vez cada tanto, y pocas personas lo realizan. El libro <a href="https://jamesclear.com/atomic-habits">Atomic Habits</a>, de una lectura muy amena y libre de complicaciones, lo resume muy bien. Lo recomiendo fuertemente a quienes la tengan difícil formando hábitos saludables.</p>
<p>Si una persona puede hacer 3 mejoras por semana, 5 personas harán 15. Si a esto le sumamos ser más cuidadosos introduciendo deuda técnica nueva, ¡estamos mejorando a pasos agigantados!</p>
<p>El siguiente video es un gran resumen de lo que es el papel del liderazgo en iniciativas como estas:</p>
<iframe width="840" height="475" src="https://www.youtube.com/embed/fW8amMCVAJQ" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen=""></iframe>
<h2 id="divide-y-conquistarás">Divide y conquistarás</h2>
<p>A veces suena un poco contradictorio que por un lado aplicamos esta técnica todo el tiempo en el código, pero luego, cuando queremos hacer un refactoring, buscamos el “todo o nada” y terminamos con un <em>pull request</em> de 200 archivos que nadie quiere revisar, que es muy probable que genere conflictos fácilmente, y que si sale mal es más difícil de revertir. Sin contar que a medida que la cantidad de cambios crece, nuestra atención como revisores decae, con lo cual es probable que algún error simple como un error de tipeo no se detecte.</p>
<p>En grandes proyectos, y sobre todo en aplicaciones 24/7, una tarea simple como un <em>rename</em> puede ser difícil de llevar a cabo. Con lo cual hay que pensarlo en partes, por ejemplo:</p>
<ul>
<li>Agregar el nombre nuevo</li>
<li>Comunicar el cambio al equipo y explicar cuál es la solución preferida</li>
<li>Deprecar el nombre viejo</li>
<li>(tantas veces como sea necesario) reemplazar los usos progresivamente</li>
<li>Borrar el nombre viejo</li>
</ul>
<p>Al refactorizar, conviene ser más la tortuga que la liebre…</p>
<h2 id="umbrales-de-tiempo">Umbrales de tiempo</h2>
<p>Refactorizando se suele perder la noción del tiempo. Después de un poco de experimentación, llegué a dos duraciones que me marcan el día a día de trabajo:</p>
<ul>
<li><strong>15 minutos</strong> para explorar qué tan viable es un refactoring. En ese intervalo apunto a comprender con qué me estoy enfrentando, decidir qué atacar y qué no.</li>
<li><strong>1 día</strong> para integrar un conjunto de cambios. Cualquiera sean los cambios que realice, debería tener algo revisable por mi equipo, que pueda integrar en el transcurso de un día.</li>
</ul>
<p>Ambas son duraciones que intentan ser cortas, justamente para evitar perderse en el refactoring. No lo veo como una regla que si no se cumple hay algo mal, sino más bien una heurística que sirve a modo de guía. Sé que al alcanzar estas marcas de tiempo debo tener un entregable/conclusión de lo que estuve haciendo.</p>
<p>Seguramente esto sea diferente para cada persona, tanto en la duración como en lo que se espera lograr en cada intervalo. Ya el ejercicio de pensarlo e iterarlo es muy interesante.</p>
<h2 id="conclusión">Conclusión</h2>
<p>Espero este artículo te haya inspirado y ya estés pensando qué mejora vas a introducir la próxima vez que tengas tu proyecto en frente. Si tenés alguna duda, ¡escribime! Si tenés algún tip que sientas que es relevante, ¡escribilo también! Refactoricemos y recordemos que lo perfecto es enemigo de lo bueno.</p>Nahuel GarbezzaRefactorizar es lindo. Pero la realidad nos presenta restricciones (tiempo, alcance, tecnológicas, políticas). Ahí es donde la creatividad toma importancia y debemos lograr un alto impacto en poco tiempo, mientras hacemos crecer nuestro software.El Primer Test2020-08-22T12:00:00+00:002020-08-22T12:00:00+00:00https://ngarbezza.com.ar/blog/el-primer-test<h2 id="intro">Intro</h2>
<p>Quizás la mayor dificultad al trabajar con TDD es <strong>empezar</strong>. ¿Cómo ir más allá de la hoja en blanco?
¿Cómo resistir a la tentación de <em>armar un sistemita</em> en nuestra cabeza o en una hoja de papel?
¿Cómo tener la certeza de que sabemos bien lo que vamos a hacer y empezar <em>a todo trapo</em>?</p>
<p>Muchas veces las personas queremos aprender, pero no queremos quedar en ridículo mientras lo hacemos.
Aún está instalada esa idea de “hacerlo bien la primera vez” o “que si me equivoco, que no se note”,
incluso en el software, donde es bien sabido que manejamos altos niveles de incertidumbre. TDD viene a
romper con eso, y nos invita a aprender muy rápido, a sabiendas que nos vamos a equivocar mucho.
¡a prepararse, pues!</p>
<p>¿Qué características debería tener ese difícil primer test? Veamos…</p>
<h2 id="lo-más-barato-posible">Lo más barato posible</h2>
<p>En el desarrollo de software el tiempo es crucial. Todo cuesta… tiempo y dinero. Entonces, si nos
vamos a equivocar y darnos cuenta que era por otro camino, qué mejor que eso ocurra dentro del loop
de una sesión de TDD, que no debería durar más que unos minutos. No hay nada peor que invertir horas,
días y semanas para darnos cuenta de que aquello que construimos con mucho empeño no era lo que se esperaba.</p>
<h2 id="lo-más-rápido-e-incorrecto-posible">Lo más rápido e incorrecto posible</h2>
<p>Sin feedback inmediato, no hay TDD. Si nos sentamos media hora enfrente de la computadora sin escribir
un test, es tiempo que perdimos en saber si lo que estamos haciendo va en una dirección correcta o no.
Por más que no tengamos mucha idea de cómo se va a ver la solución (¡es lo normal!) es importante escribir
algo. Un primer <em>assert</em> que nos permita pensar en cuál va a ser el próximo. O al menos algo que nos permita
una contradicción.</p>
<h2 id="ni-siquiera-un-nombre">Ni siquiera un nombre</h2>
<p>Dada la incertidumbre inicial, lo que más nos va a costar es ponerle un nombre <em>bonito</em> al test. ¿Pero qué
importa el nombre cuando estamos en t=0? Poco y nada. El nombre no se ejecuta, y si bien sirve para futuras
lecturas de programadores… para eso está el paso de refactoring. Pongamos <code class="language-plaintext highlighter-rouge">testXXXXX</code>, y sigamos. En algún
momento ese <code class="language-plaintext highlighter-rouge">XXXXX</code> nos va a molestar y vamos a estar en condiciones de ponerle el nombre que se merece.</p>
<h2 id="seguramente-será-borrado">Seguramente será borrado</h2>
<p>Más de una vez me pasó que terminé borrando el primer test que escribí, después de haber escrito varios y
ver <em>de qué va la cosa</em>. Es completamente normal; a medida que vamos aprendiendo, también vamos organizando
el conocimiento y clasificando lo que ocurre en nuestro sistema en casos de una mejor manera. Y podemos ir
hacia atrás y decir: “¡este test no tiene mucho sentido ahora!” <em>Delete</em>. Hemos aprendido y reflejado dicho
aprendizaje, eso es lo que cuenta.</p>
<h2 id="redondeando">Redondeando</h2>
<p>¿Vale equivocarse? <strong>Hay que equivocarse</strong>. Y aprender, y volver a equivocarse; muchas veces, cuantas más,
mejor. Que ese primer test sea el disparador de muchos más; y que puedas junto a tu equipo subirte al <em>tren
hacia la calidad</em>, al que lamentablemente pocos proyectos llegan.</p>Nahuel GarbezzaQuizás la mayor dificultad al trabajar con TDD es empezar. ¿Cómo ir más allá de la hoja en blanco? TDD nos invita a aprender muy rápido, a sabiendas que nos vamos a equivocar mucho. ¡a prepararse, pues! ¿Qué características debería tener ese difícil primer test?6 tips para una exitosa sesión de TDD2019-11-05T12:00:00+00:002019-11-05T12:00:00+00:00https://ngarbezza.com.ar/blog/6-tips-para-una-exitosa-sesion-de-tdd<ol>
<li><strong>No se permite análisis parálisis:</strong> Es inevitable a veces pensar demasiado, incluso lo vemos como algo bueno… pero también consume tiempo, y el tiempo es mejor invertirlo construyendo software, en lugar de pensar cómo debería construirse. Preguntas como “¿cómo puedo llamar a este test?”, “¿es mejor crear un objeto separado para esta lógica o no?” aparecen todo el tiempo. Esa clase de preguntas son para hacérselas una vez que el test esté verde, no antes. Puede pasar que no hayas construido lo suficiente como para tener respuesta a esas preguntas, así que si no las podés responder, TDD te va a guiar tarde o temprano a encontrarlas.</li>
<li><strong>No olvidar el paso 3:</strong> A veces logramos que un test pase e inmediatamente escribimos uno nuevo. Pero el paso 3, el de refactor, es muy importante porque sirve para solucionar deuda técnica que quizás acabas de introducir. Va a ser más difícil de arreglar después. No olvides verificar tener buenos nombres, ¡ni tampoco olvides refactorizar los tests!. No hagas daño a tu yo del futuro. La deuda técnica genera interés siempre que no se pague, y es bueno ser conscientes respecto a medir ese interés.</li>
<li><strong>Plantear las aserciones primero:</strong> un buen test sigue el patrón Act-Arrange-Assert (o Given-When-Then), pero eso no necesariamente indica que hay que escribir el test en ese orden. Personalmente prefiero escribirlos desde el final hasta el principio. Esto a ayuda a enfocarnos en obtener lo que esperamos, y luego escribir todo lo que sea necesario para que eso ocurra. Siguiendo este enfoque, probablemente al final vas a tener el contexto mínimo necesario para que el test tenga sentido. Es importante mantener la simplicidad, nadie quiere mantener tests con cientos de líneas de código de set up.</li>
<li><strong>Frenar la ansiedad:</strong> A veces tenemos una idea sobre cómo será nuestra solución final, y no podemos resistirlo y rompemos con el ciclo de TDD (confieso, ¡me pasa todos los días!). Perdemos el estado de flow. Podríamos estar introduciendo un bug (solemos pensar que nuestras soluciones son “irrompibles”). Es un error pensar que por hacer TDD vamos a ir más lento. Hay que considerar los beneficios a largo plazo. También empezar un nuevo ciclo después de haber interrumpido otro tiene su costo, así que es conveniente ir a una velocidad constante, sin tomar atajos. Después de hacer varios ciclos probablemente notes que estás avanzando más rápido.</li>
<li><strong>Dejar casos borde para el final:</strong> En general, los casos borde son los más difíciles de testear, comparado con los caminos “felices” (happy paths); y en términos de priorización, pueden quedar para el final. ¿Qué es la primer cosa que los futuros usuarios de nuestro sistema van a intentar hacer? Eso debería ser nuestra prioridad. A menos que ya sepas que es lo que no se espera que pase, y no lleva mucho esfuerzo testearlo.</li>
<li><strong>Tomar otro camino si estás estancado:</strong> Supongamos que estamos estancados tratando de que un test pase, o incluso más atrás, escribiendo un test para que falle por primera vez. En general lo que recomiendo es que si luego de 10 o 15 minutos no podemos salir de ese estado, debemos ir por otro camino. Tanto diverger (dejar el test actual sin terminar e intentar otro) or volver hacia atrás borrando todo el trabajo hecho en el ciclo actual, son opciones válidas. En TDD estamos siempre siguiendo un camino, y a veces es sabio cuestionarse si el camino es el correcto, ir un paso hacia atrás y buscar una mejor dirección.</li>
</ol>
<p>En resumen:</p>
<ul>
<li>No dejes que nada te bloquee.</li>
<li>No dejes pasar oportunidades de mejora.</li>
<li>Empezá por algo sencillo y lejos de ser perfecto, y avanzá de a pasos pequeños.</li>
</ul>
<p>¡Y te va a ir bien!</p>Nahuel GarbezzaTDD es ~95% práctica. La teoría seguramente la conozcas o la hayas escuchado, el ciclo de Red-Green-Refactor te sea familiar y sepas qué es lo que se hace en cada paso. La práctica es lo difícil, y siempre es bueno tener una cierta guía a medida que vas practicando. Esta es una simple lista de cosas que funcionaron para mí, y que quizás funcionen para vos.