Codeanalyse: Ein Vergleich zwischen Open Source und kommerzieller Software

Matthias Kraemer
© Shutterstock/Manczurov

Es gibt zahlreiche kostenlose Open Source verfügbare statische Analysetools auf dem Markt. Aber was können diese feststellen, das die statische Analyse von kommerziellen Lösungen nicht feststellen kann und umgekehrt? Um dem auf den Grund zu gehen, werden in diesem Artikel die Ergebnisse, also die identifizierten Fehler des Open-Source-Systems FindBugs, mit jenen eines kommerziellen Softwareverfahrens verglichen. Besondere Aufmerksamkeit wird dabei einerseits auf die spezifischen Fehlertypen gerichtet. Andererseits werden die Fehler vorgestellt, die von beiden Tools identifiziert werden konnten. Des Weiteren werden die gängigsten Fehlertypen untersucht, die durch das Projekt Coverity Scan identifiziert werden konnten, mit dessen Hilfe bereits Open-Source-Projekte wie Linux, PHP und PostgreSQL analysiert wurden. Abschließend wird analysiert, inwiefern eine Kombination beider Tools gewinnbringend und risikomindernd in ein bestehendes System eingegliedert werden kann.

Java-Entwickler haben es nicht leicht: Millionen von Nutzern erwarten von ihnen sichere, qualitativ einwandfreie Software – schnell geliefert, stets auf dem neuesten Stand und maximal zugänglich. Als moderne, erwachsene Plattform bietet Java eine Reihe an Vorteilen, mit denen Entwickler diese Forderungen erfüllen können. Das beginnt bereits mit Funktionen, die die Sprache robuster und Entwickler effizienter werden lassen; das umgebende System hat sich ebenfalls weiterentwickelt und umfasst eine große Zahl von Drittsoftware, die ebenfalls auf diese Ziele hinarbeitet.

Die Beliebtheit ihrer Sprache bringt Java-Entwickler jedoch in die Situation, nicht nur ihren Code korrekt implementieren zu müssen, sondern sich dazu um Abwehrmechanismen zu bemühen, um ein schadhaftes Ausnutzen ihrer Applikationen zu unterbinden. Im Webumfeld bleiben Cross-site Scripting oder Injection Attacks gefährlich und geplagte Entwickler verbringen zu viel Zeit damit, entdeckte Fehler wie Null-Dereferenzen, Race Conditions oder Ressource Leaks nachzuverfolgen und zu reparieren. Betrachtet man die Auswirkungen von Qualitäts- und Sicherheitsmängeln, so wird klar: Während des gesamten Prozesses der Softwareentwicklung müssen Risiken gefunden und beseitigt werden. Wer Fehler schnell identifiziert, kann die Releases nach Plan liefern und Produktionsfehler verhindern – und wird nicht zur nächsten Negativschlagzeile.  Defekte frühzeitig zu entdecken hilft den Teams dabei, Lösungen schneller zu implementieren und die Effizienz und Vorhersagbarkeit ihrer Entwicklung zu verbessern. Der Schlüssel hierzu ist das Development Testing und seine Techniken, etwa statische Codeanalyse, die den Code bereits dann auf bestimmte Fehlertypen testen kann, wenn er noch gar nicht lauffähig ist.

Solche Techniken müssen, um den Erwartungen gerecht zu werden, tatsächlich zu einer Qualitätssteigerung führen. Außerdem dürfen sie die Entwickler nicht behindern – keiner würde sonst ein solches Hilfsmittel konsistent einsetzen. Doch selbst wenn ein statisches Analysetool konsistent genutzt wird, kann es nicht jeden Fehler verhindern. Im Java-Bereich gibt es eine breite Auswahl an statischen Analyseangeboten, auf Open-Source-Basis wie auch als kommerzielle Angebote – jede mit ihren spezifischen Stärken. Viele Teams greifen auf eine Mischung verschiedener Tools zurück, um die individuellen Stärken produktiv ausnutzen zu können.

Coding-Standards als Impfung gegen Fehler

Typische Tools, die statische Analyse verwenden, greifen auf einen der beiden Ansätze zurück:

1. Sie helfen dabei, den Coding-Standard durchzusetzen
2. Sie suchen nach tatsächlichen oder mutmaßlichen Fehlern

Coding-Standards wirken wie präventive Medikamente: Sie identifizieren die Fehler nicht direkt, sondern sorgen vielmehr für die Codepflege. Er wird so lesbarer, leichter zu warten, die Fehlerwahrscheinlichkeit nimmt ab, die gefundenen Fehler sind generell leichter zu beseitigen. Standards können effektiv sein, solange sie sorgsam konzipiert und konsistent angewendet werden. Für Java-Code existieren bereits allgemeine Standards, entworfen etwa von Google oder Oracle; zahlreiche kommerzielle und Open-Source-basierte Tools sorgen für eine Identifizierung von Verstößen gegen diese und andere Standards. Das bekannteste Open-Source-Tool ist dabei vermutlich Checkstyle.

Coding-Standards sind primär für einfache Bereiche zuständig, etwa für Formatierung und Stil, die für Disziplin beim Schreiben von Code sorgen. Konsistenz ist in diesem Falle die Voraussetzung für Effektivität. Coding-Standards können pedantisch und nervend werden, jede Abweichung vom Standard könnte immerhin der Integrität des Standard-Regimes schaden. Vielen Teams ist es jedoch nicht möglich, den Standard konsistent über die ganze Codebasis hinweg durchzusetzen – speziell Teams, die mit bestehenden Codebasen arbeiten, stoßen dabei auf zu viele bestehende Verstöße. Da die Durchsetzung von Coding-Standards prinzipiell eine Ja/Nein-Frage ist, wäre ein detaillierter Vergleich der Tools hinsichtlich der Art der Durchsetzung hier nicht interessant. Bei bestehenden Projekten erweist sich die Suche nach Fehlern als relativ unkompliziert, da sie direkt im existierenden Code gefunden werden können. Jedes Tool sucht Fehler auf seine Art; daraus resultieren Unterschiede hinsichtlich gefundener Fehlertypen, Genauigkeit und Performance. Potenzielle Nutzer sollten sich über die Fähigkeiten und Einschränkungen der einzelnen Tools informieren, um das für ihre konkreten Entwicklungsanforderungen geeignete Tool herauszufinden.

Open Source versus kommerzielles Tool: der Vergleich

Um diese Unterschiede deutlich zu machen, nutzen wir einen Vergleich zwischen dem bekanntesten statische Open-Source-Analysetool, FindBugs, und dem kommerziellen Angebot – der Coverity Development Testing Platform (Anmerkung: Der Autor ist Coverty-Mitarbeiter). Die Gegenüberstellung erfolgt durch die Analyse derselben Codebasis, dem Jenkins Continuous Integration Server, mit beiden Tools.

Die Tools fanden eine große Anzahl unterschiedlicher Fehler: Coverity identifizierte 224 Fehler, während FindBugs 655 Fehler finden konnte. Von allen 851 Fehlern wurden lediglich 28 von beiden Tools erkannt (Abb. 1). Die Überschneidung beider Tools ist demnach minimal – aber die reinen Zahlen sind nur ein Teil der Wahrheit.

Abb. 1: Anzahl der gefunden Fehler und gemeinsame Schnittmenge

Statistische Analysetools verwenden eine Vielzahl an Techniken, um ihre spezifischen Ziele zu erreichen: von relativ einfachen Mustervergleichen bis hin zu anspruchsvolleren Aufgaben wie Data-Flow-Analysen oder abstrakten Interpretationen; manche erstrecken sich sogar über die Programmiersprache hinaus auf das Enterprise Framework oder den Change Impact. Diese Techniken beeinflussen entsprechend die Fehlertypen, die von der Analyse entdeckt werden.

Zusätzlich zu den Daten und der Technologie der Analyse gibt es verschiedene Techniken, die zur Verbesserung der Geschwindigkeit, der Genauigkeit und der Skalierbarkeit eingesetzt werden. Techniken wie die Boolean Satisfiability und das False Path Pruning dienen dafür als Beispiel: Sie schaffen es, sowohl Geschwindigkeit als auch Genauigkeit zu verbessern. Indem das Analysetool die Programmlogik nachvollzieht und mögliche Werte der Ausdrücke prüft, kann es auf umfangreiche analytische Berechnungen von Sackgassen verzichten. Die Analysen sind schneller, die Ergebnisse zielgenauer und Entwickler müssen keine Zeit verschwenden auf die Untersuchung potenzieller Fehler in unausführbaren Pfaden. Das soll jedoch nicht heißen, dass diese Pfade von den Tools komplett ignoriert werden sollten – einige basieren auf Logikfehlern und müssen angegangen werden. Letztendlich geht es jedoch um die Ergebnisse der Analyse, nicht um die Techniken. FindBugs fand mehr Fehler als Coverity – was aber bedeutet das? Bei Betrachtung der Fehlertypen werden die Unterschiede deutlicher.

Aufmacherbild: businessman writing analysis von Shutterstock / Urheberrecht: Manczurov

[ header = Seite 2: Die Fehlertypen sind der Schlüssel ]

Die Fehlertypen sind der Schlüssel

Tabelle 1 zeigt, dass die beiden Tools nicht nur eine unterschiedliche Anzahl, sondern auch völlig verschiedenartige Fehler entdecken.

Fehler Coverty FindBugs Beide
Resource Leaks 86 12 13
Concurrency-Fehler 22 10 9
Unhandled Exceptions (inkl. NULL Deref.) 79 7 5
Coding-Standards, andere 9 598 1
Fehler Gesamt 196 627 28

Tabelle 1: Unterschiedliche Fehlertypen im Vergleich

Defekte wie Resource Leaks und Concurrency-Fehler können nur schwer identifiziert werden und führen häufig zu subtilen, aber schwerwiegenden Konsequenzen, wie Instabilität und Datenkorruption. Von den 152 gefundenen Fehlern dieser Typen wurden 14 % sowohl von Coverity als auch von FindBugs entdeckt. 14 % wurden nur von FindBugs gefunden, 71 % ausschließlich von Coverity.

Unhandled Expressions sind eine weitere gängige Fehlerquelle, die starke Konsequenzen, wie beispielsweise Programmabstürze, herbeiführen kann. Ähnlich den Leaks und der Nichtsequenzialität konnte Coverity mehr dieser Fehler aufdecken (Coverity: 87 %; FindBugs: 8 %; beide: 5 %). Die Mehrzahl der verbleibenden Fehler sind Verletzungen des Coding-Standards oder andere Abweichungen von der Norm. Solche Fehler haben mit großer Wahrscheinlichkeit nur kleine Auswirkungen – sollten als Fehler dennoch ernstgenommen werden. FindBugs ist klarer Sieger auf diesem Gebiet und findet unerreichte 98 % der Fehler, verglichen mit nur einem Prozent, das Coverity hier entdeckt.

Dieser Vergleich soll nicht suggerieren, dass ein Tool besser als das andere ist. Vielmehr soll er zeigen, dass verschiedene Tools eben auch unterschiedliche Stärken haben – und dass Qualitätssteigerung ein kompliziertes Unterfangen ist. Natürlich werden die Tools nach ihren Stärken ausgewählt – doch es muss dabei genauso beachtet werden, was die individuellen Anforderungen und die eigene betriebliche Arbeitskultur vorgeben: Ist das vorhanden Team etwa diszipliniert genug, um Coding-Standards wieder und wieder durchzusetzen? Lassen sich die Entwickler ihre Tools vorschreiben oder müssen sie selbst die Auswahl treffen? Gibt es Regularien und Verordnungen, die eingehalten werden müssen?

Das Open-Source-Java-Projekt im Coverity Scan Service

Vor welchen Fehlertypen muss sich nun letztendlich geschützt werden? Diese Frage ist zu komplex und umfassend, um in diesem Artikel adäquat erfasst werden zu können. Des Weiteren hängt die Antwort von vielen verschiedenen Faktoren ab. Jedes Projekt hat unterschiedliche Ziele und Herausforderungen bezüglich der Fehlerdomäne, der Kultur, der Laufzeitumgebung und vielen weiteren Faktoren. In Anlehnung an Informationen des Open-Source-Java-Projekts im Coverity Scan Service soll nun der tatsächliche Bedarf diskutiert werden.

Projekte wie diese repräsentieren ein breites Spektrum von Entwicklern, einschließlich Codebasen in zahlreichen Anwendungsdomänen, deren Umfang von einigen Tausend bis hin zu mehreren Millionen Zeilen Code reicht. Natürlich ist es nicht garantiert, dass die vorgestellten Fehler aus dem Fundus des Scan Service exakt jenen Fehlern entspricht, die der werte Leser aus der Praxis kennt – doch die Fehlertypen, gegen die man sich schützen sollte, sind aller Wahrscheinlichkeit nach identisch. Tatsächlich ähnelt diese Liste den Fehlern, die im Jenkins-Vergleich festgestellt wurden.

Häufigste Fehlerklasse: Null-Dereferenzen

Der Scan zeigt, dass Null-Dereferenzen die häufigste Fehlerklasse in Java-Projekten bilden. Die Dereferenzierung einer Null-Referenz stellt im Java-Code eine NullPointerException dar. Gut geschriebener Code wird keine NullPointerException aufzeigen, was dazu führt, dass die Exception letztlich nicht aufgegriffen und die Anwendung beendet wird. Dies kann zu einem Denial of Service führen. Nicht der Umgang mit dieser Ausnahme muss gelernt werden, es muss vielmehr präventiv gehandelt werden. Der eigene Code muss also, wenn möglich, vor der Dereferenzierung auf Null-Referenzen überprüft werden.

Die zweithäufigste Fehlerklasse bilden die so genannten Race Conditions, die sich dadurch auszeichnen, dass gleichzeitige Threads den Zugang zu geteilten Ressourcen nicht ausreichend schützen. Diese Race Conditions können zu vielen erheblichen Fehlern führen: unautorisierter Zugang zu Ressourcen, Datenkorruption oder Programmabstürze sind nur einige Beispiele. Die Prävention von Race Conditions erweist sich als sehr schwierig, da sie nach einer Synchronisierung verlangt, um den Zugang zu geteilten Ressourcen durch verschiedene Threads in einer geordneten und passenden Reihenfolge zu ermöglichen.

Resource Leaks und Klassenhierarchien

Resource Leaks bilden eine weitere häufig auftretende Fehlerklasse. Sie zeichnen sich dadurch aus, dass vom Code angefragte Ressourcen nicht mehr freigegeben werden können. Dies kann zu Systemüberlastungen, Performancefehlern und letztendlich zu Programmabstürzen führen. Die Annahme, dass Resource Leaks aufgrund des Garbage Collectors keine Java-Programme beeinträchtigen, erweist sich als falsch. Ressourcen wie Database und Socket Handles werden nicht durch den Garbage Collector verwaltet – darüber hinaus gibt es auch keine Garantie, dass der Garbage Collector die von ihm verwalteten Ressourcen freigibt. Entwickler müssen verstehen, wie und wann Ressourcen genutzt werden, um diese adäquat verwalten zu können und sicherzustellen, dass kein Leak auftritt.

Inkonsistenzen der Klassenhierarchie sind die letzte Fehlerquelle, die hier vorgestellt werden soll. Typischerweise treten solche Fehler dann auf, wenn der Class Contract festlegt, dass die abgeleiteten Klassen die Superclass-Implementierung verschiedener Methoden anfragen müssen –die Subclass dies jedoch nicht tut. Ein gängiges Beispiel ist die clone()-Methode: Implementationen von clone() sollten super.clone() aufrufen, um zu prüfen, ob es sich um das korrekte Objekt handelt. Um solche Fehler zu vermeiden, benötigt es Entwickler, die Mitnahme von Pflichten beim Ableiten von Klassen verstehen.

Fazit: Fehlertypen erkennen, passende Tools wählen

Statistische Analyse stellt eine übliche und effektive Methode dar, um die Softwarequalität zu verbessern und so die schnelle, reibungslose und einfache Identifizierung von Fehlern während der Entwicklung zu ermöglichen. Wer die Fähigkeiten der verfügbaren Tools kennt und die Fehlertypen, die dem eigenen Projekt ähneln, identifiziert, kann einen Prozess entwickeln, der Fehler verhindert und somit die Gesamtqualität einer Software, sowie die Effizienz des eigenen Teams verbessert.

Geschrieben von
Matthias Kraemer
Matthias Kraemer verfügt über langjährige Erfahrung als Architekt und Projektleiter von komplexen Projekten, die in ihrer Mehrzahl Open-Source-Komponenten einbinden. Sein Interesse galt dabei der methodischen Prozessoptimierung für hohe Qualitäts- und Sicherheitsstandards während der Entwicklungsphase. Seit 2008 arbeitet er bei Coverity am Standort München als Consultant.
Kommentare

Hinterlasse einen Kommentar

Hinterlasse den ersten Kommentar!

avatar
400
  Subscribe  
Benachrichtige mich zu: