pixel
Select Page

NETWAYS Blog

JavaScript Data-Watchpoints

Jeder der eine größere Anwendung debuggt, wird sich schnell in einer Situation wiederfinden, in der einfache Breakpoints nicht mehr ausreichen um die Ursache des Fehlers zu finden. Die meisten modernen Debugger beherrschen deswegen das Setzen von Watchpoints.
Watchpoints beobachten eine Variable und halten die Ausführung des Programmes an, sobald sich der Wert der Variable ändert. Auch wenn die Entwicklertools der meisten Browser ziemlich fortgeschritten sind, sucht man Watchpoints hier jedoch vergeblich. Glücklicherweise gibt es ein paar Möglichkeiten um dieses Dilemma herumzuarbeiten, indem man die Watchpoints direkt in JavaScript oder der Konsole setzt:
Gecko-basierte Browser wie Firefox bieten die Möglichkeit die Watchpoints mit der Funktion Object.prototype.watch() direkt in der Debugging-Konsole zu setzen.

var someVar = { myProperty: 'foo' };
Object.watch('myProperty', function () {
   console.log('myProperty changed!');
});

Chrome besitzt das noch mächtigere Object.observe(), mit dem man beliebige Variablen beobachten kann und nicht nur Member von Objekten.

var someVar = ...
Object.observe(someVar, function () {
   console.log('someVar changed!');
});

Für alle anderen Browser gibt es einen Polyfill, der den Objekt-Prototyp mit einer neuen Funktion .watch() erweitert.
Als i-Tüpfelchen kann man nun noch den Debugger halten lassen sobald der gesetzte Watchpoint ausgeführt wird. Hierfür verwendet man im Callback das Statement debugger; welches in nahezu allen Browsern funktioniert.

Object.observe(someVar, function (name, object, type, oldValue) {
    debugger;
});

Git Bisect, oder die verdrehten Tricks der Kernel-Hacker

Wer häufiger in Projekten mit mehreren Entwicklern auf Jagd nach Bugs gehen muss, hat sicher häufig das Problem dass einige Fehler als Nebeneffekte anderer Patches oder Änderungen auftreten können.
Als ich diese Woche dabei war ein einen Bug in der Browser-History in Icingaweb2 zu fixen hatte ich ein vergleichbares Problem: Ich wusste zwar dass ein Fehler früher einmal auftrat der im aktuellen Master nicht mehr auftritt, aber nicht durch welche Änderung der Fehler dann letztendlich beseitigt wurde. Mein bisherigen Ansatz war in diesem Fall einfach zu einem beliebigen verdächtigen Commit in der Mitte auszuchecken und dann einfach zu testen. Wenn der Fehler dort noch auftritt, dann wusste ich zumindest dass der Commit, der den Fehler dann tatsächlich gefixt hat in einem späteren Commit auftritt. Nachteil dieser Technik ist natürlich dass man mühsam die Commit-History durchsuchen muss um manuell einen geeigneten Commit auszuwählen.
Ein Kollege hat mich dabei beobachtet und mich dann freundlicherweise darauf hingewiesen dass es auch wesentlich einfacher geht. Git hat nämlich für diesen Fall tatsächlich eine eingebaute Funktion namens Git-Bisect. Laut Dokumentation führt Git-Bisect eine Binärsuche durch um eine Änderung zu finden, die einen bestimmten Bug einführt, also vergleichbar mit meinem bisherigen Ansatz, aber mit dem kleinen Unterschied dass GIT die Commits automatisch auswählt und auscheckt.

Beispiel

Ich beginne damit an einen Punkt auszuchecken, an dem der Fehler auftritt. Wie man es noch aus der Schulzeit kennt braucht man an dieser Stelle jetzt allerdings ein bisschen Transferleistung um auf die Lösung zu kommen: Git-Bisect sucht nämlich einen Patch der einen Fehler einführt, ich suche aber nach etwas das den Fehler behebt. Deshalb muss ich im Folgenden gute und schlechte Commits verdrehen um das Feature für meinen Zweck nutzen zu können.
Ich starte den Vorgang und markiere den kaputten Commit also demnach mit good.

   git bisect start
   git bisect good

Anschließend markiere ich den aktuellen Master als bad, da ich ja weiß dass der Fehler dort schon gefixt wurde.

   git bisect bad master

Jetzt ist Git bereits zum Commit genau in der Mitte gesprungen und gibt sogar aus wie viele Schritte noch zu tun sind um den Fehler zu finden.
start
An dieser Stelle starte ich jetzt Icinga Web 2 und überprüfe ob der Fehler noch vorhanden ist. Da der Fehler an dieser Stelle nicht mehr auftritt markiere ich den Commit als bad.

    git bisect bad

Git springt wieder an einen neuen Commit und ich wiederhole den selben Schritt. Wenn der Fehler auftreten sollte markiere ich den Commit als gut und fahre ansonsten wie gehabt fort.

    git bisect good

Am Ende des ganzen Prozesses spuckt einem Git den ersten bad Commit aus, also den ersten Commit an dem der Fehler auftritt (oder in unserem Fall eben nicht mehr). Allerdings haben wir jetzt das Problem dass der Commit auf einen größeren Merge-Commit zeigt, was leider noch relativ wenig hilfreich ist. Um dieses Problem zu lösen starte ich den ganzen Prozess noch mal und nehmen diesmal den ersten und letzten Commit des Merge-Commits als Start und Ende.

    git bisect reset; git bisect start
    git bisect bad LETZTER_COMMIT
    git bisect good ERSTER_COMMIT

schluss
Endlich spuckt uns Git den Commit aus der den Fehler dann tatsächlich behoben hat, eine Änderung am Caching unserer Browser-History.

Fazit

Aus Sicht der Linux-Kernel-Hacker, für die Git ja ursprünglich entwickelt wurde, macht so ein Feature definitiv Sinn, denn ein großes verteiltes Projekt wie der Linux-Kernel ist sicher ein Garant für lange Abende der Fehlersuche in großen Commit-Histories. Jedoch kann oft auch jedes andere Projekt von diesen Features profitieren, wenn man denn davon weiß. Wie immer gilt: es lohnt sich seine Tools zu beherrschen und sich mit seinen Kollegen darüber auszutauschen, vielleicht existieren ja Tricks an die man noch nicht einmal gedacht hat!

Visual Studio Code für Node.js unter Linux ausprobiert

Wenn man es genau nimmt hätte dieses Jahr ja eigentlich die Hölle zufrieren müssen, denn Microsoft hat tatsächlich einen Ableger von Visual Studio für Linux veröffentlicht: den Texteditor Visual Studio Code. Bei VSCode handelt es sich allerdings um keinen vollwertigen IDE, wie man beim Namen eigentlich vermuten könnte, sondern um einen Texteditor, der aber immerhin einen Debugger, Autocompletion und GIT-Support mitbringt. Der Editor untersützt C#, HTML, CSS, JS und Node.JS.
Da ich für das Projekt NETRP Node.JS mit dem Express-Webserver verwende und dafür schon länger auf der Suche nach einem guten Editor bin, habe ich die Preview-Version des Microsoft-Editor gleich mal ausprobiert. Meine Erfahrungen dabei möchte ich euch nicht vorenthalten.

Installation

Die Installation ist denkbar einfach und besteht daraus ein Zip zu entpacken und das Binary im Programmordner auszuführen.

Autocompletion

Wie bei anderen Texteditoren, muss man in VSCode keine Projekte anlegen, sondern öffnet einfach den Ordner im Editor. Wenn man eine beliebige JavaScript-Datei öffnet funktioniert grundlegende Autocompletion, Compiler-Warnungen und das Syntax-Highlighting bereits automatisch.
Ohne jegliche Konfiguration kennt die Autocompletion aber momentan nur die Symbole die direkt im Code definiert wurden oder die Built-In-Objects. Beim Hovern über ein unbekanntes Symbol wird einem aber gleich ein Quick Fix angeboten um dieses Problem zu beheben: VSCode fragt im Hintergrund die Datenbank Definitely Typed ab, die Typskriptdefinitionen für sehr viele übliche JavaScript-Libraries bereitstellt. Diese Dateien können dann direkt in eingebunden und vom Autocompleter verwendet werden um das Interface der verfügbaren Funktionen nachzuschlagen.

Debugging

Fürs Debuggen benötigen wir mindestens die Version 3.12 der Mono-Runtime. Da ich aber noch Fedora20 verwende und es hier im Repository relativ düster aussieht, installiere ich die Version direkt vom Mono-Project.
Die Dokumentation von VSCode sagt uns, dass wir zuerst launch.json auf den Einstiegspunkt zeigen lassen müssen, bei NETRP liegt diese Datei in: src/netrp_server/netrp.js. Die launch.json wird automatisch generiert und ins Projekt eingefügt, sobald man die Debugging-Konfiguration des erste mal öffent.
Der Dokumentation zu folge, sollte nach F5 jetzt eigentlich bereits die Anwendung und den Debugger starten, stattdessen überrascht VSCode mit dem wenig aussagekräftigen Fehler “Connection failed“.
connection-failed
Eventuell existiert irgendein Problem mit der Run-Konfiguration, aber aufgrund der fehlenden Fehlerbeschreibung versuche ich lieber einfach direkt einen anderen Ansatz: den Debugger an den vorhandenen Prozess anhängen. Wenn man die Anwendung mit node –debug-brk netrp selbst startet und den Debugger stattdessen über einen TCP-Port and den Prozess anhängt, funktioniert es auch schon einwandfrei:
VSC - Debugger
In der aktuellen Vorabversion unterstützt der Debugger nur die typischen Kernfeatures, also das Setzen von Breakpoints und das manuelle Stepping. Zumindest in der Linuxversion wirkt das Stepping in der Preview aber noch etwas träge und es gibt ein spürbare Verzögerung beim Bedienen des Debuggers.

Git

Das Git-Repository wird automatisch erkannt und der Reiter “Git” zeigt alle Änderungen an. Es ist möglich komplette Dateien zu stagen und Commits zu erstellen, für feinere Aktionen ist weiterhin die CLI notwendig.
git

Fazit

Der Editor lässt sich schnell einrichten und verwenden und besonders das einfache beschaffen von TypeScript-Definitionen für JS-Libraries hat mich beeindruckt. Sobald das Projekt in einer finalen Version ist werde ich es wohl definitiv als Editor für meine Node-Projekte in Betracht ziehen.

I hate reading other peoples code

Machen wir uns nichts vor, als Entwickler verbringt man oft mehr Zeit damit sich vorhandenen Quellcode durchzulesen, als tatsächlich Neuen zu schreiben. Beim lesen so mancher unbekannter Codebasis mit mangelhafter Dokumentation hat man natürlich noch Glück, wenn man sich dabei nicht wie in diesem Webcomic vorkommt. Umso wichtiger ist, dass man gute Mittel kennt um sich schnell mit einer großen Codebasis vertraut machen zu können und um einem diese mühselige Arbeit wenn möglich zu ersparen.

Überblick verschaffen

Ein klassicher Ansatz ein unbekanntes Programm zu verstehen, ist es einfach dieses auszuführen und sich dabei den Quellcode anzusehen. Oft hilft das jedoch nicht wirklich den größeren Zusammenhang und die Struktur des Programmes zu ergründen, da man immer nur eine Funktion / Klasse auf einmal betrachten kann. Zum Glück bieten viele IDEs wie NetBeans oder PHPStorm die Möglichkeit Diagramme für genau diesen Zweck zu generieren.

UML-Klassendiagramme

Wenn man in PHPStorm beispielsweise eine Klasse selektiert und auf “Show Diagram” drückt, wird automatisch ein Klassendiagramm generiert, mit dem man sich einen schnellen Überblick über vorhandene Datentypen und Vererbungshierarchien verschaffen kann.

PHPStorm-UML-Klassendiagramm

Ein UML-Klassendiagramm der Icingaweb2-Charting-Library

Zustands- und Sequenzdiagramme

Besonders bei Webanwendungen kann es aber sinnvoll sein Teile der Funktionen als Sequenz- oder Zustandsdiagramm darzustellen. Solche Diagramme lassen sich leider meistens nicht so einfach aus Quellcode generieren, bieten aber besonders deswegen oft eine Neue/Alternative Sicht auf das was im Programm passiert und können deshalb das Verständnis des Codes erleichtern. Zum modellieren dieser Diagrammtypen gibt es eine Reiher kostenloser Webtools wie Gliffy oder draw.io.

Ausprobieren

Wenn man einfach nur herausfinden will was ein Programm in einer bestimmten Situation macht, geht Probieren oft über Studieren.

strace

Strace listet auf, welche Systemcalls ein Programm aufruft. Mit strace -c <app>  kann man sich eine Zusammenfassung aller Systemaufrufe ausgeben lassen. Wenn man z.B. einfach wissen will welche Dateien ein Programm öffnet, kann man Strace mit dem Parameter -e trace=open starten, und bekommt alle Aufrufe des Linux-Systemcalls open aufgelistet.

Prozesse mit strace untersuchen

Prozesse mit strace untersuchen

Profiling

Ein Profiler verrät einem bekanntermaßen womit ein Programm die meiste Zeit verbringt. Das ist nicht nur hilfreich um Performance-Bottlenecks zu finden, sondern auch um herauszufinden welche Funktion wie oft aufgerufen wird. Für Webanwendung mit PHP lassen sich mit XDebug profilen und können in IDEs wie PHPStorm dargestellt werden.

Sonstige Möglichkeiten

In einem älteren Blogpost, hatte ich die D3 JavaScript-Library “Code-Flower” vorgestellt, die einem eine Übersicht über die Größe (LOCs) einzelner Klassen im Quellcode gibt. Auch wenn das Ergebnis vor allem optisch eindrucksvoll ist, kann die Library in Verbindung mit Versionskontrollen genutzt werden um eine Historie über die zeitliche Entwicklung des Quellcodes zu generieren.
Die verschiedenen Möglichkeiten Programme zu untersuchen sind eigentlich fast nur durch die eigene Kreativität eingeschränkt. Es gibt unzählige Debugging und Analysetools, die sich mit ein wenig Einfallsreichtum dazu verwenden lassen können um ein Programm zu untersuchen auch ohne sich jede Codezeile durchzulesen.

Icinga Web 2 Source-Code-Flower

Um sich schnellen Überblick über eine große Menge Code zu verschaffen, kann es sinnvoll sein dazu grafische Tools zu verwenden. Eine Möglichkeit, die dazu auch noch ziemlich eindrucksvoll aussieht, ist mit der JavaScript-Library CodeFlowers den gesamten Quellcode als interaktive Baumstruktur darstellen zu lassen.
Eine Live-Demo mit einer Code-Flower von Icinga Web 2 befindet sich hier. Neben dem aktuellen Stand könnt ihr dort auch einige ältere Snapshots auswählen, sodass ihr die die verschiedenen Entwicklunggstände über die letzten eineinhalb Jahre miteinander vergleichen könnt.
Auf der Seite befindet sich die detaillierte Anleitung des Code-Flowers-Projekts, falls ihr Interesse bekommen habt eure eigenen Grafiken zu generieren.

Stand: 15. Dezember 2014

Stand: 15. Dezember 2014