3 Debugging
Sobald ein Software-Projekt eine gewisse Größe überschreitet, schleichen sich Fehler in den Quellcode ein. Diese Fehler zu finden und zu beheben, ist das Ziel beim Debugging. Meistens treten diese Fehler im Laufe der Entwicklung auf, oft wird man auch von Anwendern erst später darauf hingewiesen. Wie auch immer, wir müssen das Problem lösen. Und wenn wir es mithilfe der KI schneller lösen können, dann umso besser.
In der Softwareentwicklung haben wir unterschiedliche Werkzeuge, um Problemen auf die Spur zu kommen. Temporäre Print-Anweisungen (oder console.log für Web-Applikationen) im Code sind der einfachste und oft schnellste Weg zum Erfolg. Strukturiertes Logging mit unterschiedlichen Kategorien und weiteren Metadaten sind in Anwendungen hilfreich, die produktiv von Kunden eingesetzt werden. Ein Debugger, der den Code zur Laufzeit analysieren und anhalten kann, ist ein unverzichtbares Werkzeug für komplexe Anwendungen.
Diese Tools helfen Ihnen, Fehler zu erkennen und zu verstehen. Zaubermittel sind sie aber nicht: Es liegt immer noch an Ihnen, das Problem zu durchdenken, Fehlerquellen auszuschließen und nach der Ursache zu forschen. Oftmals sieht man dabei den Wald vor lauter Bäumen nicht mehr: Man ist so in den eigenen Code vertieft, dass man Dummheiten gar nicht mehr wahrnimmt. Wenn man sich erst mal in einer Sackgasse richtig verrannt hat, ist es schwer, alleine den Ausweg zu finden.
In der modernen Softwareentwicklung wird deswegen oft im Team gearbeitet: Beim Pair Programming arbeitet man gemeinsam am Code, denn vier Augen (und zwei Hirne) erkennen Probleme schlicht mehr als doppelt so schnell. Wenn kein Kollege verfügbar ist, hilft oft das Rubber Duck Debugging: Man schnappt sich eine Gummiente und erklärt ihr das Problem. Und da die Ente kein Programmierprofi ist, müssen diese Erklärungen langsam und kleinschrittig sein. Meistens fällt einem die Lösung dann auf, wenn man den Fehler so heruntergebrochen hat.
Aber was wäre, wenn die Gummiente nicht nur eine passive Zuhörerin ist, sondern selbst mit Analysen, Lösungsvorschlägen und Ideen glänzen könnte? Damit hätten Sie einen Partner im Pair Programming, der niemals mürrisch oder gelangweilt ist, immer Zeit hat und zudem ordentliches Grundlagenwissen in fast jeder Sprache und jedem Framework hat.
In diesem Kapitel werden wir daher versuchen, Fehler in unserem eigenen Quellcode mit KI-Unterstützung zu eliminieren. Dank unserer Erfahrung beim Beheben der Fehler können wir abschätzen, wie groß der Zeitunterschied durch die KI-Hilfe ist.
Der Nachteil dabei ist, dass sich unsere Kenntnis des Lösungswegs auf die Fragestellung auswirken kann. Genau das ist nämlich der zentrale Punkt bei der Formulierung des Prompts: Wenn Sie Ihrem KI-Helfer bereits eine Richtung mitgeben und ihn durch Keywords auf eine bestimmte Fährte schicken, wird er sehr wahrscheinlich Ihre Gedankengänge aufgreifen und replizieren. Falls sich das Problem damit nicht lösen lässt, kann es sinnvoll sein, eine neue Session zu starten und die Anfrage offener und allgemeiner zu formulieren. Das widerspricht den Ratschlägen, die wir sonst im Buch geben, nämlich dass die Prompts möglichst konkret und mit viel Kontext formuliert sein sollten, damit die KI weiß, was sie tun muss.
Wenn Sie sich aber selbst nicht sicher sind, wo sich das Problem versteckt und was der richtige Lösungsweg ist, geben Sie der KI besser etwas mehr Leine. Bitten Sie die KI um unterschiedliche Ansätze und Blickwinkel auf das Problem. Aber dann ist es natürlich Ihre Aufgabe, zwischen den verschiedenen Ideen den Weg zu wählen, der das Problem löst. Die KI kann Ihnen sicherlich beim Debugging einfacher Syntaxprobleme helfen, aber wenn es komplexer wird, gilt hier umso mehr das Fazit: Überprüfen Sie stets alle Antworten, und denken Sie immer mit. Nur weil Ihr KI-Helfer stolz sagt, dass er ein Problem gelöst hat, muss noch lange nicht alles so laufen, wie es soll.
Trotzdem können wir schon verraten, dass unsere Erfahrungen beim KI-gestützten Debugging sehr positiv waren. Die Sprachmodelle sind gut darin, Abweichungen von der Norm zu finden und zu korrigieren. Wenn sich der Fehler in Ihrem Programm damit beschreiben lässt, ist die Wahrscheinlichkeit hoch, dass Ihr KI-Helfer ihn finden wird.
3.1 Web-Applikationen
Der Marktanteil von Web-Applikationen wächst ungebrochen. Mit der COVID-19-Pandemie wurde der Ruf nach mehr Online noch lauter, und wer heutzutage eine Applikation online bringt, tut gut daran, den Web-Browser als ein mögliches Frontend nicht zu vergessen.
Der Browser als betriebssystemunabhängige Plattform entwickelt sich rasant weiter und bietet immer ausgefeiltere Werkzeuge zum Debuggen von Web-Applikationen (Stichwort Developer-Tools). Wir wollen sehen, ob und wie uns KI-Unterstützung zu noch schnelleren Lösungen führen kann.
CSS Fixes
Wer sich schon einmal an einem vermeintlich einfachen Layout in HTML versucht hat, wird das Problem kennen: Ein Element will einfach nicht an die Position auf der Seite, an der man es gerne haben möchte. Wir haben hier als Beispiel einen sehr einfachen Seitenabschluss als footer-Element verwendet. Die Inhalte sollen in drei Bereichen links, mittig und rechts aufgeteilt werden. Unser erster Entwurf hat den Nachteil, dass sich die Bereiche mit der CSS-Eigenschaft float nicht an die Höhe des umgebenden Elements halten, sondern auch darüber hinaus fließen können.
Abbildung 3.1 Fehlerhaftes Layout für den Fußbereich einer HTML-Seite
Wir wollen sehen, ob Claude 3.5 Sonnet hier eine bessere Lösung bieten kann:
Prompt: How can I make my footer elements all the same height:
<footer style="margin:auto;text-align:center;width:80%;">
<span style="float:left;">previous page</span>
<span>(c) 2024</span>
<span style="float:right;">next page<br />Intro</span>
</footer>
To make the footer elements all the same height, you can use flexbox. Here’s a modified version of your code that should achieve this:
<footer style="margin:auto; width:80%; display:flex;
justify-content:space-between; align-items:stretch;">
<span style="flex:1;padding:10px;">previous page</span>
<span style="flex:1;padding:10px;text-align:center;">
(c) 2024</span>
<span style="flex:1;padding:10px;text-align:right;">next page
<br />Intro</span>
</footer>
Key changes:
-
Removed float properties and used flexbox instead.
-
Added display:flex to the footer.
-
Used justify-content:space-between to distribute the spans evenly.
-
Used align-items:stretch to make all items stretch to the height of the tallest item. …
Die Lösung verwendet das mächtige Flexbox-Modell, um die Elemente korrekt zu positionieren. Flexbox ist seit knapp 10 Jahren in den weitverbreiteten Browsern implementiert und kann noch viel mehr, als einen Bereich in drei gleich breite Spalten einzuteilen. Dadurch kann Flexbox aber auch sehr komplex werden, weshalb wir die KI fragen wollen, ob wir auch das modernere (und oft einfachere) Grid-Layout verwenden können.
Prompt: Would CSS display:grid be an option?
Yes, CSS Grid is definitely a good option for this layout. Grid can provide even more control and flexibility than Flexbox in many cases. Here’s how you could implement the same layout using CSS Grid:
<footer style="margin:auto; width:80%; display:grid;
grid-template-columns: 1fr 1fr 1fr;">
<span style="padding:10px;">previous page</span>
<span style="padding:10px;text-align:center;">(c) 2024</span>
<span style="padding:10px;text-align:right;">next page<br>
Intro</span>
</footer>
Key points about this Grid implementation: …
Das Ergebnis ist noch einfacher lesbar und in der Anzeige identisch mit der Flexbox-Version.
Abbildung 3.2 Grid-Layout für den Fußbereich einer HTML-Seite mit drei Spalten (mit aktiven Developer-Tools, um die drei Bereiche hervorzuheben)
Unser Fazit für das erste Debugging: Die KI lieferte eine sehr gute Lösung für unser Problem. Auch die von uns verlangte Variante mit einem anderen CSS-Layout-Modell bleibt übersichtlich und funktioniert auf Anhieb.
Ical-Download im Browser
Unser nächstes Problem beschäftigt sich mit der Programmiersprache PHP und den Möglichkeiten, externe Programme vom Browser aus zu starten. In unserer Applikation haben wir einen Export für Termine im standardisierten Ical-Format erstellt. Beim Anklicken des Links wird aber nicht wie von uns gewünscht die Kalender-Applikation gestartet, sondern der Text wird schwer lesbar im Browser angezeigt. Wir fragen Claude 3.5 Sonnet, ob es dafür eine schnelle Lösung gibt:
Prompt: I have this simple PHP class which sends calendar entries over HTTPS. Unfortunatelly it opens in my browser. How can I make it open in my calendar app?
To make the calendar entry open in your calendar app instead of the browser, you need to adjust the HTTP headers you’re sending. Here’s how you can modify your PHP class to achieve this:
… Code …
The key changes are in the send() method:
-
We set the Content-Type header to text/calendar; charset=utf-8. This tells the client that we’re sending a calendar file.
-
We set the Content-Disposition header to attachment; filename=“event.ics”. This tells the browser to treat the file as an attachment rather than trying to display it, and suggests a filename for the file.
Die Lösung ist korrekt, und die beiden Änderungen, die als Key changes in der Antwort noch ausführlich erklärt werden, führen in der Tat zum gewünschten Verhalten. Bei diesem Versuch ist uns allerdings ein Missgeschick passiert: Eigentlich wollten wir die entsprechende Code-Passage zum Prompt hinzufügen. Umso größer war die Überraschung, als sich der von der KI generierte Code perfekt in unsere Code-Basis integrieren ließ. Bei komplexeren Änderungen kann das Mitsenden des eigenen Codes aber sicher weitere Arbeit ersparen.
Backend-Frontend ohne Proxy-Server
Wenn Sie in der Webentwicklung mit getrennten Frontend- und Backend-Anwendungen arbeiten, werden Sie dieses Problem vermutlich kennen: Der Entwicklungsserver für die Backend-Applikation läuft auf Port 8000 und der Bundler mit Live-Reload im Browser (wie vite oder webpack) auf Port 8080. Asynchrone Zugriffe vom Frontend (AJAX) werden vom Browser blockiert.
In solchen Fällen sind die Fehlermeldungen in der Browser-Konsole zwar ausführlich, aber, wenn man sich mit dem Thema noch nicht beschäftigt hat, auch schwer verständlich. Wir haben die Meldung im folgenden Prompt, den wir an Claude geschickt haben, etwas gekürzt:
Prompt: My backend application is a golang json api with gin-gonic running on port 8000. Frontend is running with vite.js on port 8080. When I call backend routes I get the following error:
Access to fetch at 'http://localhost:8000/users/me'
from origin 'http://localhost:8080' has been blocked by CORS
policy: Response to preflight request ...
What can I do?
To resolve this CORS (Cross-Origin Resource Sharing) issue, you need to configure your backend to allow requests from your frontend origin. Here’s what you can do:
-
Install the CORS middleware for Gin:
…
Die KI hat das Problem sofort erkannt und korrekt gelöst. Wir müssen das cors-Modul von der von uns verwendeten Bibliothek gin-gonic einbinden und entsprechend konfigurieren. Wir bekommen zusätzlich noch den Hinweis, dass die detaillierte Konfiguration für Entwicklungszwecke auch umgangen werden kann, wenn man die Option config.AllowAllOrigins = true setzt.
Perfekt, das Problem scheint gelöst. Nur das Login funktioniert immer noch nicht. Die Anfrage mit Benutzernamen und Passwort wird zwar mit dem HTTP-Statuscode 200 als gültig bestätigt, wir werden aber immer wieder zur Login-Seite zurückgeleitet. Vielleicht kann die KI auch hier weiterhelfen?
Prompt: Great! My requests are working now, but my login functionality is broken. Session variables in go are not present. What ist the problem?
I’m glad the CORS issue is resolved! Now, regarding your login functionality and session variables not being present, there could be a few reasons for this. Let’s explore some common issues and solutions:
…
Die KI ist offenbar froh, dass wir das erste Problem lösen konnten. Und froh sind auch wir über die folgende Antwort: Wir werden darauf hingewiesen, dass man beim Übertragen von Zugangsdaten eine weitere Einstellung am Frontend und am Backend machen muss. Das konnte die KI im Vorhinein nicht wissen, wir haben bei unserer ersten Frage ja nichts von einem Login-Problem gesagt. Die ausführliche Antwort enthält auch Anweisungen, um die Cookies, die für die Session verwendet werden, korrekt zu speichern (sodass sie eben auch für andere Ports gelten).
Einzig hier hätten wir erwartet, dass die KI gleich einen Lösungsvorschlag für die von uns verwendete Bibliothek (gin-gonic) und das Session-Management dort liefert. Die Antwort erklärte uns die Einstellung für Cookies allgemein in der Programmiersprache Go. Wir müssen also noch einmal nachfragen:
Prompt: I am using gin-gonic sessions, how do I set relaxed restrictions for samesite here?
Jetzt bekommen wir die korrekte Antwort, und die Applikation funktioniert wie gewünscht. Im Entwicklungsprozess wird für diesen Zweck auch gerne ein Proxy-Server verwendet, der Frontend und Backend vom gleichen Port und vom gleichen Host an den Browser sendet. Wenn Sie die Applikation produktiv auch verteilt (also nicht am gleichen Host) betreiben wollen, müssen Sie sich mit CORS ohnehin beschäftigen.
Informationen zum Thema CORS gibt es im Internet zur Genüge, und sie sind auch leicht zu finden. Eine Web-Suche nach den Begriffen CORS und MDN (für Mozilla developer network) führt uns zu den ausgezeichneten Webseiten, die das Problem im Detail beschreiben. Wir bekommen aber nicht die fertige Lösung für unser Programm in der Programmiersprache Go, wie es die KI geliefert hat. Natürlich kann man hier einwenden, dass es sich um ein sicherheitsrelevantes Thema handelt und man nicht Code ohne tiefere Kenntnis der Materie übernehmen sollte. In diesem Fall hat die KI aber gute Erklärungen geliefert, und wir konnten die Anwendung nach kürzester Zeit entwickeln, ohne einen halben Tag nach Lösungen bei StackOverflow zu suchen (wie wir es ohne KI-Hilfe machten).
JavaScript-fetch mit falschem Content-Type
Unser nächstes Problem stammt von einer Umstellung, bei der wir eine alte JavaScript-Bibliothek zum Ausführen von asynchronen Web-Anfragen im Browser (AJAX) durch die moderne fetch-Schnittstelle ersetzt haben. Die meisten Aufrufe liefen problemlos, aber an ein paar Stellen hakte die Umstellung ein wenig. Ob uns die KI auch für dieses Problem eine schnelle Lösung bieten kann?
Für den Versuch kopieren wir die gesamte Funktion, die das Problem auslöst, und die Fehlermeldung aus der Browser-Konsole in den Prompt:
Prompt: javascript: When I call this function:
const _newComment = function (topic, meetingId, event) {
event.target.disabled = true;
fetch(`api/comments`, {
body: JSON.stringify({
id: topic,
comment: this.newCommentContent.trim(),
meetingId,
}),
})
.then((json) => {
this.comments.push({
id: json.id,
content: this.newCommentContent.trim(),
date: moment().format("YYYY-MM-DD"),
meeting: meetingId,
user: this.$store("user").fullname,
});
this.newCommentContent = "";
})
.finally(() => {
autosize.update(event.target);
event.target.disabled = false;
this.$focus.focus(event.target);
});
};
I get this error message in my browser console window:
comments.js:8 Uncaught (in promise)
TypeError: Failed to execute 'fetch' on 'Window': Request with
GET/HEAD method cannot have body.
at Proxy._newComment (comments.js:8:3)
...
The error message you’re seeing is due to the fact that you’re not specifying the HTTP method in your fetch call, which defaults to GET. However, you’re also trying to send a request body, which is not allowed for GET requests. To fix this issue, you need to specify the HTTP method (likely POST) and include the necessary headers. Here’s how you can modify your fetch call:
… Code …
Key changes:
-
Added method: ‘POST’ to specify that this is a POST request.
-
Added headers to specify the content type as JSON.
-
Changed then(json => { to then(response => response.json()).then(json => { to properly parse the JSON response.
Hier hat uns die KI wieder einmal zum Staunen gebracht: Der Fehler, dass beim Senden der Daten mit fetch die falsche HTTP-Methode verwendet wird (GET statt POST), ergibt sich ja fast schon aus der Meldung in der Browser-Konsole. Aber dass zwei weitere Fehler (der fehlende Content-Type und das fehlende Konvertieren der Antwort von Text in ein JavaScript-Objekt mit response.json()) auch noch gleich aufgelöst wurden, hatten wir nicht erwartet.
JamGPT
Bei unserer Suche nach Werkzeugen mit KI-Unterstützung zum Debugging sind wir auf die Browser-Erweiterung Jam gestoßen. Mithilfe dieser Erweiterung können Sie Fehler, die Ihnen beim Verwenden einer Web-Anwendung auffallen, sehr einfach dokumentieren. Sie können damit Screenshots oder Screencasts (also ein Video der Bildschirmaufzeichnung) erstellen und teilen. Außerdem, und darin liegt die große Stärke der Erweiterung, werden viele Zusatzinformationen (wie die Ausgabe der Browser-Konsole oder die ausgeführten Netzwerkanfragen) aufgezeichnet und ebenfalls gespeichert.
Alle Informationen werden auf die Plattform https://jam.dev geladen, wo jeder Fall, oder Jam, angesehen und kommentiert werden kann. Der Service ist nach einer Registrierung frei verfügbar, natürlich gibt es Bezahlzugänge für Business- und Enterprise-Kunden. Diese bieten unter anderem Anschluss an ein bestehendes Ticket-System und eine Anmeldung über das Firmennetzwerk.
Jetzt können Sie zu Recht fragen, was das mit künstlicher Intelligenz zu tun hat. In der Weboberfläche von Jam finden Sie einen Reiter mit dem Namen JamGPT, und hier kommt die KI ins Spiel.
Abbildung 3.3 Ein Jam mit KI-Unterstützung bei fehlerhaftem JavaScript-Code
Auf Knopfdruck können Sie die Fehlermeldungen an ein Sprachmodell senden und auf dessen Lösungen für das Problem hoffen. Der Komfort bei dieser Lösung ist wirklich hervorragend und führt die Möglichkeiten vor Augen, daher erwarteten wir auch außergewöhnliche Ergebnisse. Dabei wurden wir aber etwas enttäuscht: Bei einfachen Fehlern, wie einer falsch geschriebenen URL für eine Backend-Abfrage, weist JamGPT korrekt auf die möglichen Fehler hin, dazu braucht ein Web-Entwickler aber keine KI-Unterstützung. Der Fehlercode 404 ist hinlänglich bekannt.
Bei einem komplexeren Fehler, den wir für JamGPT produziert haben, konnte die KI aber nicht glänzen. Im Gegenteil leitete die Antwort sogar in die falsche Richtung: Zur Validierung von Formulardaten verwendeten wir eine Bibliothek, die das HTML-Attribut data-rules eines Elements auswertet und anschließend die dort hinterlegten Regeln auf ihre Gültigkeit überprüft. Der Wert von data-rules muss eine Zeichenkette sein, die mit JSON.parse in ein JavaScript-Objekt umgewandelt werden kann. Das sieht zum Beispiel so aus:
<input type="text" data-rules='["required"]' name="email" />
Mit aktuellen Browsern kann man auf diese Art von JavaScript-basierten Validierungen ja zum Glück verzichten, aber der Code ist schon einige Jahre alt. Die oben angeführte Zeichenkette wird von JSON.parse in ein JavaScript-Array mit einem Eintrag umgewandelt, nämlich der Zeichenkette "required". Wenn man die doppelten Anführungszeichen um das Schlüsselwort required vergisst, entsteht ein Fehler im JavaScript-Code der Bibliothek. Die JavaScript-Konsole gibt Auskunft darüber, an welcher Stelle die Umwandlung scheitert:
Uncaught SyntaxError: Unexpected token 'r', "[required]" is not
valid JSON
at JSON.parse (<anonymous>)
at validation.js:17:24
...
Die von JamGPT generierte Antwort legt uns nahe, die Server-Anfrage beziehungsweise die Server-Antwort auf eine gültige JSON-Struktur zu untersuchen:
The bug seems to be related to invalid JSON being processed in your code.
-
Check the JSON structure being returned or sent; it should be properly formatted.
-
Look for any syntax errors in the JSON, such as misplaced commas or quotes.
-
Ensure that the data being sent to the server is correctly serialized as JSON.
So weit waren wir aber noch gar nicht: Der Fehler tritt schon auf, bevor wir eine Anfrage zum Server senden konnten. Zugegeben, die meisten Fehler beim Umwandeln von JSON in Web-Applikationen entstehen bei der Kommunikation mit einem Server, weshalb die KI das auch als mögliches Problem vorschlägt. In unserem Fall war das Problem aber nicht in Zusammenhang mit dem Server, und der Kontext in der Browser-Konsole hätte das auch gezeigt. Wir sind gespannt, ob Jam bei der Integration von Sprachmodellen noch Fortschritte macht, denn das Potenzial wäre zweifellos vorhanden.