So löst du einen seltsamen Bug mit bewährten Debugging-Strategien

Avatar of Adrian Bece
Adrian Bece am

DigitalOcean bietet Cloud-Produkte für jede Phase Ihrer Reise. Starten Sie mit 200 $ kostenlosem Guthaben!

Erinnerst du dich an das letzte Mal, als du dich mit einem UI-Bug herumgeschlagen hast, der dich stundenlang den Kopf kratzen ließ? Vielleicht trat das Problem zufällig auf, oder unter bestimmten Umständen (Gerät, Betriebssystem, Browser, Benutzeraktion), oder es war einfach in einer der vielen Frontend-Technologien versteckt, die Teil des Projekts sind?

Ich wurde kürzlich daran erinnert, wie kompliziert UI-Bugs sein können. Ich habe kürzlich einen interessanten Bug behoben, der einige SVGs in Safari-Browsern betraf, ohne ein offensichtliches Muster oder Grund. Ich hatte nach ähnlichen Problemen gesucht, um einen Hinweis auf das Geschehen zu bekommen, aber ich fand keine nützlichen Ergebnisse. Trotz der Hindernisse gelang es mir, ihn zu beheben.

Ich habe das Problem mithilfe einiger nützlicher Debugging-Strategien analysiert, die ich auch in dem Artikel behandeln werde. Nachdem ich den Fix eingereicht hatte, wurde ich an den Rat erinnert, den Chris vor einiger Zeit getwittert hatte.

…und hier sind wir.

Hier ist das Problem

Ich habe den folgenden Bug in einem Projekt gefunden, an dem ich gearbeitet habe. Dies war auf einer Live-Website.

Ich habe dieses Problem mit jedem Paint-Event reproduziert, zum Beispiel beim Ändern der Bildschirmgröße.

Ich habe ein CodePen-Beispiel erstellt, um das Problem zu demonstrieren, damit du es dir selbst ansehen kannst. Wenn wir das Beispiel in Safari öffnen, sehen die Schaltflächen beim Laden möglicherweise wie erwartet aus. Aber wenn wir auf die ersten beiden größeren Schaltflächen klicken, tritt das Problem auf.

Immer wenn ein Browser-Paint-Event auftritt, werden die SVGs in den größeren Schaltflächen falsch gerendert. Sie werden einfach abgeschnitten. Es kann zufällig beim Laden auftreten. Es kann sogar beim Ändern der Bildschirmgröße auftreten. In jedem Fall passiert es!

Ugh, warum wird das SVG-Icon abgeschnitten?!

So bin ich das Problem angegangen.

Zuerst betrachten wir die Umgebung

Es ist immer eine gute Idee, die Details des Projekts zu überprüfen, um die Umgebung und die Bedingungen zu verstehen, unter denen der Bug auftritt.

  • Dieses spezielle Projekt verwendet React (ist aber nicht erforderlich, um diesem Artikel zu folgen).
  • Die SVGs werden als React-Komponenten importiert und von Webpack in HTML eingebettet.
  • Die SVGs wurden aus einem Designtool exportiert und enthalten keine Syntaxfehler.
  • Die SVGs haben einige CSS-Regeln aus einer Stylesheet angewendet bekommen.
  • Die betroffenen SVGs sind in einem HTML-Element <button> positioniert.
  • Das Problem tritt nur in Safari auf (und wurde in Version 13 bemerkt).

Hinunter in den Kaninchenbau

Werfen wir einen Blick auf das Problem und versuchen wir, einige Annahmen darüber anzustellen, was vor sich geht. Bugs wie dieser werden kompliziert, und wir werden nicht sofort wissen, was vor sich geht. Wir müssen nicht zu 100 % richtig liegen mit unserem ersten Versuch, da wir Schritt für Schritt vorgehen und Hypothesen aufstellen, die wir testen können, um die möglichen Ursachen einzugrenzen.

Eine Hypothese aufstellen

Zuerst sieht dies nach einem CSS-Problem aus. Möglicherweise werden einige Stile bei einem Hover-Event angewendet, die das Layout oder die Overflow-Eigenschaft der SVG-Grafik beeinträchtigen. Es sieht auch so aus, als ob das Problem zufällig auftritt, immer wenn Safari die Seite rendert (Paint-Event beim Ändern der Bildschirmgröße, Hover, Klick usw.).

Beginnen wir mit dem einfachen und offensichtlichsten Weg und nehmen wir an, dass CSS die Ursache des Problems ist. Wir können die Möglichkeit in Betracht ziehen, dass es einen Bug im Safari-Browser gibt, der dazu führt, dass SVG falsch gerendert wird, wenn ein bestimmter Stil auf das SVG-Element angewendet wird, wie zum Beispiel ein Flex-Layout.

Damit haben wir eine Hypothese aufgestellt. Unser nächster Schritt ist es, einen Test einzurichten, der die Hypothese entweder bestätigt oder widerlegt. Jedes Testergebnis liefert neue Fakten über den Bug und hilft bei der Formulierung weiterer Hypothesen.

Problemvereinfachung

Wir verwenden eine Debugging-Strategie namens Problemvereinfachung, um das Problem einzugrenzen. Ein CS-Vortrag der Cornell University beschreibt diese Strategie als „einen Ansatz, um nach und nach Teile des Codes zu eliminieren, die für den Bug nicht relevant sind.“

Indem wir davon ausgehen, dass das Problem innerhalb von CSS liegt, können wir das Problem entweder eingrenzen oder CSS aus der Gleichung nehmen, wodurch die Anzahl der möglichen Ursachen und die Komplexität des Problems reduziert werden.

Versuchen wir, unsere Hypothese zu bestätigen. Wenn wir vorübergehend alle Nicht-Browser-Stylesheets ausschließen, sollte das Problem nicht auftreten. Ich habe das in meinem Quellcode getan, indem ich die folgende Codezeile in meinem Projekt auskommentiert habe.

import 'css/app.css';

Ich habe ein praktisches CodePen-Beispiel erstellt, um diese Elemente ohne CSS zu demonstrieren. In React importieren wir SVG-Grafiken als Komponenten, und sie werden mithilfe von Webpack in HTML eingebettet.

Wenn wir dieses Pen in Safari öffnen und auf die Schaltfläche klicken, erhalten wir immer noch das Problem. Es tritt immer noch auf, wenn die Seite geladen wird, aber auf CodePen müssen wir es erzwingen, indem wir auf die Schaltfläche klicken. Wir können schlussfolgern, dass CSS nicht der Schuldige ist, aber wir sehen auch, dass nur zwei von fünf Schaltflächen unter dieser Bedingung fehlschlagen. Behalten wir das im Hinterkopf und fahren wir mit der nächsten Hypothese fort.

Die gleichen SVG-Elemente schlagen immer noch fehl, wenn Stylesheets ausgeschlossen sind (Safari 13)

Das Problem isolieren

Unsere nächste Hypothese besagt, dass Safari einen Bug hat, wenn SVG in einem HTML-Element <button> gerendert wird. Da das Problem bei den ersten beiden Schaltflächen aufgetreten ist, werden wir die erste Schaltfläche isolieren und sehen, was passiert.

Sarah Drasner erklärt die Wichtigkeit der Isolation und ich empfehle dringend, ihren Artikel zu lesen, wenn du mehr über Debugging-Tools und andere Ansätze erfahren möchtest.

Isolation ist vielleicht das stärkste Grundprinzip beim Debuggen. Unsere Codebasen können weitläufig sein, mit verschiedenen Bibliotheken, Frameworks, und sie können viele Mitwirkende enthalten, sogar Personen, die nicht mehr am Projekt arbeiten. Die Isolation des Problems hilft uns, unwesentliche Teile des Problems langsam auszusondern, damit wir uns ausschließlich auf eine Lösung konzentrieren können.

Ein „reduzierter Testfall“ wird es auch oft genannt.

Ich habe diese Schaltfläche auf eine separate und leere Testroute (leere Seite) verschoben. Ich habe das folgende CodePen erstellt, um diesen Zustand zu demonstrieren. Obwohl wir zu dem Schluss gekommen sind, dass CSS nicht die Ursache des Problems ist, sollten wir es ausgeschlossen lassen, bis wir die eigentliche Ursache des Bugs herausgefunden haben, um das Problem so einfach wie möglich zu halten.

Wenn wir dieses Pen in Safari öffnen, können wir sehen, dass wir das Problem nicht mehr reproduzieren können und die SVG-Grafik nach dem Klicken auf die Schaltfläche wie erwartet angezeigt wird. Diese Änderung sollten wir nicht als akzeptable Fehlerbehebung betrachten, aber sie liefert einen guten Ausgangspunkt für die Erstellung eines minimal reproduzierbaren Beispiels.

Ein minimal reproduzierbares Beispiel

Der Hauptunterschied zwischen den beiden vorherigen Beispielen ist die Kombination der Schaltflächen. Nach dem Ausprobieren aller möglichen Kombinationen können wir schlussfolgern, dass dieses Problem nur auftritt, wenn ein Paint-Event auf einer größeren SVG-Grafik auftritt, die sich auf derselben Seite neben einer kleineren SVG-Grafik befindet.

Wir haben ein minimal reproduzierbares Beispiel erstellt, das es uns ermöglicht, den Bug ohne unnötige Elemente zu reproduzieren. Mit einem minimal reproduzierbaren Beispiel können wir das Problem genauer untersuchen und den Teil des Codes, der es verursacht, genau bestimmen.

Ich habe das folgende CodePen erstellt, um das minimal reproduzierbare Beispiel zu demonstrieren.

Wenn wir diese Demo in Safari öffnen und auf eine Schaltfläche klicken, können wir das Problem in Aktion sehen und die Hypothese aufstellen, dass diese beiden SVGs irgendwie miteinander in Konflikt stehen. Wenn wir die zweite SVG-Grafik über die erste legen, können wir sehen, dass die Größe des zugeschnittenen Kreises auf der ersten SVG-Grafik den genauen Abmessungen der kleineren SVG-Grafik entspricht.

Bearbeitetes Bild, das die kleinere SVG-Grafik mit der ersten SVG-Grafik mit dem vorhandenen Bug vergleicht

Teile und Herrsche

Wir haben das Problem auf die Kombination von zwei SVG-Grafiken eingegrenzt. Nun werden wir die Dinge auf den spezifischen SVG-Code eingrenzen, der das Problem verursacht. Wenn wir nur ein grundlegendes Verständnis von SVG-Code haben und das Problem eingrenzen wollen, können wir eine Binärbaum-Suchstrategie mit einem Teile-und-Herrsche-Ansatz verwenden. Ein CS-Vortrag der Cornell University beschreibt diesen Ansatz.

Zum Beispiel, beginnend mit einem großen Codeblock, platziere eine Prüfung auf halbem Weg durch den Code. Wenn der Fehler an dieser Stelle nicht auftritt, bedeutet das, dass der Bug in der zweiten Hälfte auftritt; andernfalls ist er in der ersten Hälfte.

In SVG können wir versuchen, <filter> (und auch <defs>, da es sowieso leer ist) aus der ersten SVG zu löschen. Schauen wir uns zuerst an, was <filter> tut. Dieser Artikel von Sara Soueidan erklärt es am besten.

Genau wie lineare Verläufe, Masken, Muster und andere grafische Effekte in SVG haben Filter ein passend benanntes eigenes Element: das <filter> Element.

Ein <filter> Element wird niemals direkt gerendert; seine einzige Verwendung ist, dass es mithilfe des Attributs filter in SVG oder der Funktion url() in CSS referenziert werden kann.

In unserem SVG wendet <filter> einen leichten Innen-Schatten am unteren Rand der SVG-Grafik an. Nachdem wir ihn aus der ersten SVG-Grafik gelöscht haben, erwarten wir, dass der Innen-Schatten verschwindet. Wenn das Problem weiterhin besteht, können wir schlussfolgern, dass etwas mit dem restlichen SVG-Markup nicht stimmt.

Ich habe das folgende CodePen erstellt, um diesen Test zu demonstrieren.

Wie wir sehen können, besteht das Problem trotzdem weiter. Der Innen-Schatten unten wird angezeigt, obwohl wir den Code entfernt haben. Nicht nur das, jetzt tritt der Bug in allen Browsern auf. Wir können schlussfolgern, dass das Problem im restlichen SVG-Code liegt. Wenn wir das verbleibende id aus <g filter="url(#filter0_ii)"> löschen, wird der Schatten vollständig entfernt. Was ist los?

Betrachten wir noch einmal die zuvor erwähnte Definition der <filter>-Eigenschaft und beachten wir das folgende Detail

Ein <filter> Element wird niemals direkt gerendert; seine einzige Verwendung ist, dass es als etwas referenziert werden kann, das mittels des Attributs filter in SVG verwendet wird.

(Hervorhebung von mir)

Wir können also schlussfolgern, dass die Filterdefinition aus der zweiten SVG-Grafik auf die erste SVG-Grafik angewendet wird und den Fehler verursacht.

Das Problem beheben

Wir wissen jetzt, dass das Problem mit der <filter>-Eigenschaft zusammenhängt. Wir wissen auch, dass beide SVGs die Filter-Eigenschaft haben, da sie sie für den Innen-Schatten der Kreisform verwenden. Vergleichen wir den Code zwischen den beiden SVGs und sehen wir, ob wir das Problem erklären und beheben können.

Ich habe den Code für beide SVG-Grafiken vereinfacht, damit wir klar sehen können, was vor sich geht. Der folgende Schnipsel zeigt den Code für die erste SVG.

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

Und der folgende Schnipsel zeigt den Code für die zweite SVG-Grafik.

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

Wir können feststellen, dass die generierten SVGs die gleiche id-Eigenschaft id=filter0_ii verwenden. Safari hat die zuletzt gelesene Filterdefinition (in unserem Fall das zweite SVG-Markup) angewendet und dazu geführt, dass die erste SVG auf die Größe des zweiten Filters (von 46px auf 28px) zugeschnitten wurde. Die ID-Eigenschaft sollte einen eindeutigen Wert im DOM haben. Wenn zwei oder mehr id-Eigenschaften auf einer Seite vorhanden sind, können Browser nicht verstehen, welche Referenz angewendet werden soll, und die filter-Eigenschaft wird bei jedem Paint-Event neu definiert, abhängig von der Race Condition, die dazu führt, dass das Problem zufällig auftritt.

Versuchen wir, jeder SVG-Grafik eindeutige id-Attributwerte zuzuweisen und sehen wir, ob das das Problem behebt.

Wenn wir das CodePen-Beispiel in Safari öffnen und auf die Schaltfläche klicken, können wir sehen, dass wir das Problem behoben haben, indem wir jedem SVG-Grafikfile eine eindeutige ID für die <filter>-Eigenschaft zugewiesen haben. Wenn wir bedenken, dass wir einen nicht eindeutigen Wert für ein Attribut wie ID haben, bedeutet dies, dass dieses Problem in allen Browsern vorhanden sein sollte. Aus irgendeinem Grund scheinen andere Browser (einschließlich Chrome und Firefox) diesen Edge-Case ohne Fehler zu behandeln, obwohl dies nur ein Zufall sein mag.

Zusammenfassung

Das war ein ziemlicher Ritt! Wir haben angefangen, kaum etwas über ein Problem zu wissen, das scheinbar zufällig auftrat, bis wir es vollständig verstanden und behoben hatten. Das Debuggen von UI und das Verständnis visueller Bugs kann schwierig sein, wenn die Ursache des Problems unklar oder kompliziert ist. Glücklicherweise können uns einige nützliche Debugging-Strategien helfen.

Zuerst haben wir das Problem durch das Aufstellen von Hypothesen vereinfacht, was uns geholfen hat, die Komponenten zu eliminieren, die für das Problem nicht relevant waren (Stil, Markup, dynamische Events usw.). Danach haben wir das Markup isoliert und das minimal reproduzierbare Beispiel gefunden, das es uns ermöglichte, uns auf einen einzelnen Code-Chunk zu konzentrieren. Schließlich haben wir das Problem mit einer Teile-und-Herrsche-Strategie eingegrenzt und behoben.

Vielen Dank, dass du dir die Zeit genommen hast, diesen Artikel zu lesen. Bevor ich gehe, möchte ich dir noch eine letzte Debugging-Strategie mitgeben, die ebenfalls im CS-Vortrag der Cornell University behandelt wird.

Denke daran, eine Pause einzulegen, dich zu entspannen und deinen Kopf freizubekommen zwischen den Debugging-Versuchen.

 Wenn zu viel Zeit für einen Bug aufgewendet wird, wird der Programmierer müde und das Debuggen kann kontraproduktiv werden. Mach eine Pause, befreie deinen Kopf; nach etwas Ruhe versuche, das Problem aus einer anderen Perspektive zu betrachten.

Referenzen