Así es como resolví un error extraño utilizando estrategias de depuración probadas y verdaderas

Índice
  1. Aquí está el problema
  2. En primer lugar, consideremos el medio ambiente.
  3. Por la madriguera del conejo
  4. Formar una hipótesis
  5. Simplificación de problemas
  6. Aislar el problema
  7. Un ejemplo mínimo reproducible
  8. Divide y conquistaras
  • Fixing the issue
    1. Wrapping up
  • ¿Recuerdas la última vez que te enfrentaste a un error relacionado con la interfaz de usuario que te dejó rascándote la cabeza durante horas? ¿Quizás el problema ocurrió al azar, o bajo circunstancias específicas (dispositivo, sistema operativo, navegador, acción del usuario), o simplemente estaba oculto en una de las muchas tecnologías de front-end que forman parte del proyecto?

    Recientemente me acordé de lo complicados que pueden ser los errores de la interfaz de usuario. Recientemente solucioné un error interesante que afectaba a algunos SVG en los navegadores Safari sin ningún patrón o motivo obvio. Busqué problemas similares para tener alguna pista sobre lo que estaba pasando, pero no encontré resultados útiles. A pesar de los obstáculos, logré solucionarlo.

    Analicé el problema utilizando algunas estrategias de depuración útiles que también cubriré en el artículo. Después de enviar la solución, recordé el consejo que Chris tuiteó hace un tiempo.

    Escribe el artículo que desearías encontrar cuando buscaste algo en Google.

    – Chris Coyier (@chriscoyier) 30 de octubre de 2017

    …y aquí estamos.

    Aquí está el problema

    Encontré el siguiente error en un proyecto en el que he estado trabajando. Esto fue en un sitio en vivo.

    Creé un ejemplo de CodePen para demostrar el problema para que puedas comprobarlo por ti mismo. Si abrimos el ejemplo en Safari, los botones podrían verse como se esperaba al cargar. Pero si hacemos clic en los dos primeros botones más grandes, el problema asoma su fea cara.

    Cada vez que ocurre un evento de pintura del navegador , el SVG en los botones más grandes se muestra incorrectamente. Simplemente se corta. Puede suceder aleatoriamente durante la carga. Incluso podría suceder cuando se cambia el tamaño de la pantalla. Cualquiera que sea la situación, ¡sucede!

    Así es como abordé el problema.

    En primer lugar, consideremos el medio ambiente.

    Siempre es una buena idea repasar los detalles del proyecto para comprender el entorno y las condiciones en las que está presente el error.

    • Este proyecto en particular utiliza React (pero no es necesario para seguir este artículo).
    • Los SVG se importan como componentes de React y se insertan en HTML mediante paquete web.
    • Los SVG se exportaron desde una herramienta de diseño y no tienen errores de sintaxis.
    • Los SVG tienen algo de CSS aplicado desde una hoja de estilo.
    • Los SVG afectados se colocan dentro de un elemento HTML button.
    • El problema ocurre sólo en Safari (y se detectó en la versión 13).

    Por la madriguera del conejo

    Echemos un vistazo al tema y veamos si podemos hacer algunas suposiciones sobre lo que está sucediendo. Errores como este se vuelven complicados y no sabremos de inmediato qué está pasando. No tenemos que acertar al 100% en nuestro primer intento porque iremos paso a paso y formularemos hipótesis que podemos probar para reducir las posibles causas.

    Formar una hipótesis

    Al principio, esto parece un problema de CSS. Es posible que se apliquen algunos estilos en un evento de desplazamiento que rompa el diseño o la propiedad de desbordamiento del gráfico SVG. También parece que el problema ocurre de forma aleatoria, cada vez que Safari muestra la página (evento de pintura al cambiar el tamaño de la pantalla, pasar el cursor, hacer clic, etc.).

    Comencemos con la ruta más simple y obvia y supongamos que CSS es la causa del problema. Podemos considerar la posibilidad de que haya un error en el navegador Safari que haga que SVG se represente incorrectamente cuando se aplica algún estilo específico al elemento SVG, como un diseño flexible, por ejemplo.

    Al hacerlo, hemos formado una hipótesis. Nuestro siguiente paso es establecer una prueba que confirme o contradiga la hipótesis. El resultado de cada prueba producirá nuevos datos sobre el error y ayudará a formular más hipótesis.

    Simplificación de problemas

    Usaremos una estrategia de depuración llamada simplificación de problemas para intentar identificar el problema. La conferencia de informática de la Universidad de Cornell describe esta estrategia como “un enfoque para eliminar gradualmente partes del código que no son relevantes para el error”.

    Al asumir que el problema radica en CSS, podemos terminar identificando el problema o eliminando CSS de la ecuación, reduciendo la cantidad de posibles causas y la complejidad del problema.

    Intentemos confirmar nuestra hipótesis. Si excluimos temporalmente todas las hojas de estilo que no son del navegador, el problema no debería ocurrir. Lo hice en mi código fuente comentando la siguiente línea de código en mi proyecto.

    import 'css/app.css';

    He creado un práctico ejemplo de CodePen para demostrar estos elementos sin CSS incluido. En React, importamos gráficos SVG como componentes y están integrados en HTML mediante webpack.

    Si abrimos este lápiz en Safari y hacemos clic en el botón, seguimos teniendo el problema. Todavía sucede cuando se carga la página, pero en CodePen tenemos que forzarlo haciendo clic en el botón. Podemos concluir que el CSS no es el culpable, pero también podemos ver que sólo dos de cada cinco botones se rompen en esta condición. Tengamos esto en cuenta y pasemos a la siguiente hipótesis.

    Aislar el problema

    Nuestra siguiente hipótesis establece que Safari tiene un error al representar SVG dentro de un buttonelemento HTML. Dado que el problema ocurrió en los dos primeros botones, aislaremos el primer botón y veremos qué sucede.

    Sarah Drasner explica la importancia del aislamiento y recomiendo leer su artículo si desea obtener más información sobre las herramientas de depuración y otros enfoques.

    El aislamiento es posiblemente el principio central más sólido de toda la depuración. Nuestras bases de código pueden ser extensas, con diferentes bibliotecas y marcos, y pueden incluir muchos contribuyentes, incluso personas que ya no están trabajando en el proyecto. Aislar el problema nos ayuda a eliminar lentamente las partes no esenciales del problema para que podamos centrarnos singularmente en una solución.

    También se le suele denominar “ caso de prueba reducido” .

    Moví este botón a una ruta de prueba separada y vacía (página en blanco). Creé el siguiente CodePen para demostrar ese estado. Aunque hemos llegado a la conclusión de que el CSS no es la causa del problema, debemos mantenerlo excluido hasta que descubramos la causa real del error, para que el problema sea lo más simple posible.

    Si abrimos este lápiz en Safari, podemos ver que ya no podemos reproducir el problema y el gráfico SVG se muestra como se esperaba después de hacer clic en el botón. No deberíamos considerar este cambio como una corrección de errores aceptable , pero proporciona un buen punto de partida para crear un ejemplo mínimo reproducible.

    Un ejemplo mínimo reproducible

    La principal diferencia entre los dos ejemplos anteriores es la combinación de botones. Después de probar todas las combinaciones posibles, podemos concluir que este problema ocurre solo cuando ocurre un evento de pintura en un gráfico SVG más grande que está junto a un gráfico SVG más pequeño en la misma página.

    Creamos un ejemplo mínimo reproducible que nos permite reproducir el error sin elementos innecesarios. Con un ejemplo mínimo reproducible , podemos estudiar el problema con más detalle e identificar con precisión la parte del código que lo causa.

    Creé el siguiente CodePen para demostrar el ejemplo mínimo reproducible.

    Si abrimos esta demostración en Safari y hacemos clic en un botón, podemos ver el problema en acción y formar una hipótesis de que estos dos SVG de alguna manera entran en conflicto entre sí. Si superponemos el segundo gráfico SVG sobre el primero, podemos ver que el tamaño del círculo recortado en el primer gráfico SVG coincide con las dimensiones exactas del gráfico SVG más pequeño.

    Divide y conquistaras

    Hemos reducido el problema a la combinación de dos gráficos SVG. Ahora vamos a limitar las cosas al código SVG específico que está estropeando las cosas. Si solo tenemos un conocimiento básico del código SVG y queremos identificar el problema, podemos usar una estrategia de búsqueda de árbol binario con un enfoque de divide y vencerás. La conferencia de informática de la Universidad de Cornell describe este enfoque:

    For example, starting from a large piece of code, place a check halfway through the code. If the error doesn’t show up at that point, it means the bug occurs in the second half; otherwise, it is in the first half.

    In SVG, we can try deleting filter (and also defs since it’s empty anyway) from the first SVG. Let’s first check what filter does. This article by Sara Soueidan explains it best.

    Just like linear gradients, masks, patterns, and other graphical effects in SVG, filters have a conveniently-named dedicated element: the filter element.

    A filter element is never rendered directly; its only usage is as something that can be referenced using the filter attribute in SVG, or the url() function in CSS.

    In our SVG, filter applies a slight inset shadow at the bottom of the SVG graphic. After we delete it from the first SVG graphic, we expect the inner shadow to be gone. If the issue persists, we can conclude that something is wrong with the rest of the SVG markup.

    I’ve created the following CodePen to showcase this test.

    As we can see, the issue persists anyway. The inset bottom shadow is displayed even though we’ve removed the code. Not only that, now the bug appears on every browser. We can conclude that the issue lies within the rest of the SVG code. If we delete the remaining id from g filter="url(#filter0_ii)", the shadow is fully removed. What is going on?

    Let’s take another look at the previously mentioned definition of the filter property and notice the following detail:

    A filter element is never rendered directly; its only usage is as something that can be referenced using the filter attribute in SVG.

    (Emphasis mine)

    So we can conclude that the filter definition from the second SVG graphic is being applied to the first SVG graphic and causing the error.

    Fixing the issue

    We now know that issue is related to the filter property. We also know that both SVGs have the filter property since they use it for the inset shadow on the circle shape. Let’s compare the code between the two SVGs and see if we can explain and fix the issue.

    I’ve simplified the code for both SVG graphics so we can clearly see what is going on. The following snippet shows the code for the first SVG.

    svg viewBox="0 0 46 46"  g filter="url(#filter0_ii)"    !-- ... --  /g  !-- ... --  defs    filter x="0" y="0"      !-- ... --    /filter  /defs/svg

    And the following snippet shows the code for the second SVG graphic.

    svg viewBox="0 0 28 28"  g filter="url(#filter0_ii)"    !-- ... --  /g  !-- ... --  defs    filter x="0" y="0"      !-- ... --    /filter  /defs/svg

    We can notice that the generated SVGs use the same id property id=filter0_ii. Safari applied the filter definition it read last (which, in our case, is the second SVG markup) and caused the first SVG to become cropped to the size of the second filter (from 46px to 28px). The id property should have a unique value in DOM. By having two or more id properties on a page, browsers cannot understand which reference to apply, and the filter property redefines on each paint event, dependent on the racing condition that causes the issue to appear randomly.

    Let’s try assigning unique id attribute values to each SVG graphic and see if that fixes the issue.

    If we open the CodePen example in Safari and click the button, we can see that we fixed the issue by assigning a unique ID to filter property in each SVG graphic file. If we think about the fact that we have non-unique value for an attribute like id, it means that this issue should be present on all browsers. For some reason, other browsers (including Chrome and Firefox) seem to handle this edge-case without any bugs, although this might be just a coincidence.

    Wrapping up

    That was quite a ride! We started barely knowing anything about an issue that seemingly occurred at random, to fully understanding and fixing it. Debugging UI and understanding visual bugs can be difficult if the cause of the issue is unclear or convoluted. Luckily, some useful debugging strategies can help us out.

    First, we simplified the problem by forming hypotheses which helped us eliminate the components that were unrelated to the issue (style, markup, dynamic events, etc.). After that, we isolated the markup and found the minimal reproducible example which allowed us to focus on a single chunk of code. Finally, we pinpointed the issue with a divide-and-conquer strategy, and fixed it.

    Thank you for taking the time to read this article. Before I go, I’d like to leave you with one final debugging strategy that is also featured in Cornell University’s CS lecture.

    Remember to take a break, relax and clear your mind between debugging attempts.

    If too much time is spent on a bug, the programmer becomes tired and debugging may become counterproductive. Take a break, clear your mind; after some rest, try to think about the problem from a different perspective.

    References

    • Cornell University CS 312 Lecture 26 – Debugging Techniques
    • Debugging Tips and Tricks
    • Reduced Test Cases
    • SVG Filters 101
    SUSCRÍBETE A NUESTRO BOLETÍN 
    No te pierdas de nuestro contenido ni de ninguna de nuestras guías para que puedas avanzar en los juegos que más te gustan.

    Deja un comentario

    Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *

    Subir

    Este sitio web utiliza cookies para mejorar tu experiencia mientras navegas por él. Este sitio web utiliza cookies para mejorar tu experiencia de usuario. Al continuar navegando, aceptas su uso. Mas informacion