Von Martin Fowler, einem bekannten englischen Softwareentwickler, stammt folgendes Zitat: „Bugs, das sind diese fiesen Dinger, die Vertrauen zerstören, Pläne durcheinanderbringen und den Ruf ruinieren.“ Besser lassen sich die Auswirkungen von Softwarefehlern kaum beschreiben. Das erklärte Ziel agiler Methoden und DevOps ist es, Software schneller und möglichst fehlerfrei zu entwickeln. Eine zentrale Forderung im sogenannten Agile Manifest lautet: „Liefere funktionierende Software binnen weniger Wochen oder Monate, und bevorzuge dabei die kürzere Zeitspanne.“ Ein wichtiger Baustein in diesem Konzept ist die sogenannte Continuous Integration, und darauf aufbauend das Continuous Deploy-ment. Der Grundgedanke von beiden besteht darin, dass je häufiger die Entwickler ihre Arbeit in die gesamte Codebasis integrieren, desto schneller lassen sich Fehler erkennen, lokalisieren und anschließend beheben. Bei Continuous Integration endet dieser Prozess, sobald ein Entwickler seinen Codebeitrag in die sogenannte Mainline – den Hauptzweig der Codebasis – eingecheckt hat und ein dabei ein fehlerloser Build möglich war. Bei Continuous Deployment hingegen reicht der Prozess bis zur Auslieferung der Änderung an den Anwender beziehungsweise Kunden.
In handliche Portionen aufteilen
Beide Verfahren gewinnen immer mehr Anhänger, besonders in der Embedded-Entwicklung. Indem die Verfahren einen Großteil der Routinetätigkeiten in handliche Portionen aufteilen, lassen sich Entwicklungsprozesse deutlich verschlanken und die stets fehlerträchtige Integration in einem großen Rutsch vermeiden. Integration, Tests und Deployment sind jeweils nur für überschaubare Codebeiträge nötig. Der Erfolg dieser Verfahren hängt jedoch entscheidend von der Qualität der Integration ab: Je früher Bugs erkannt und beseitigt werden, desto geringer ist das Risiko fehlgeschlagener Builds – und damit der Gefahr, viel Zeit und Ressourcen mit der Suche nach Fehlern zu vergeuden. Entsprechend sind drei Dinge entscheidend:
Jede Integration muss vor dem Einchecken in die Mainline hinreichend auf Fehler überprüft werden.
Der definierte Integrationsprozess muss zu jeder Zeit eingehalten werden.
Die einzelnen Schritte müssen so weit wie möglich automatisiert sein.
Routinetätigkeiten zerlegen
Dabei nimmt die Automatisierung eine zentrale Rolle ein. Schließlich stellt vor allem beim Testing jeder manuelle Eingriff eine mögliche Fehlerquelle dar. Zudem führt menschliches Zutun fast unweigerlich zu inkonsistenten Prozessen, wodurch das eigentliche Ziel der Agilität in Gefahr gerät.
Code frühzeitig überprüfen
Ein vielversprechendes Tool, mit dessen Hilfe Entwickler bereits vor dem Build zahlreiche Fehler und auch potenzielle Probleme im Code aufspüren können, ist die statische Code-Analyse. Bei dieser Analyseform wird aus dem Code ein Modell erzeugt, in dem alle Daten- und Steuerungsströme durchlaufen werden können. Über definierte Checker lassen sich dadurch Fehler wie Buffer-Overruns oder Null-Pointer-Dereferenzierungen erkennen. Datenströme aus unsicheren, nicht validierten Quellen (Tainted Data) werden ebenfalls aufgezeigt. Außerdem ist es möglich, Abweichungen von Programmierstandards aufzuspüren. Tools wie „CodeSonar“ von Grammatech sind in der Lage, dieses Verfahren auch auf Binärcode anzuwenden.
Vor allem im Embedded-Bereich wird diese Möglichkeit immer wichtiger. Mittlerweile werden schließlich viele Komponenten – zumeist Bibliotheken und Tool-Kits – nicht mehr Inhouse entwickelt, sondern von externen Quellen bezogen. Dementsprechend liegt ein erheblicher Teil des Codes nur in binärer Form vor und entzieht sich damit einer manuellen Überprüfung.
Oft wird die statische Analyse zur Überprüfung einer großen Codebasis herangezogen. Dabei sollte man aber beachten, dass zu Beginn ein nicht unerheblicher Aufwand anfällt: Entwicklungsrichtlinien, Sicherheitsansätze und gegebenenfalls einzuhaltende Normen müssen in die Analysesoftware implementiert werden, sofern sie nicht bereits im Standard enthalten sind. Ihr volles Potential entfaltet die statische Analyse im laufenden Betrieb im Rahmen einer Continous Integration. Innerhalb des Software Development Life-
cycles (SDLC) kann sie an zwei Punkten eingesetzt werden:
Direkt am Arbeitsplatz der Entwickler eingesetzt reduziert die statische Analyse die Gefahr eines fehlgeschlagenen Builds, da viele potenzielle Fehler bereits vor dem lokalen Testbuild entdeckt werden. Neben Warnungen enthält der Entwickler detaillierte Angaben zu den jeweiligen Fehlern, so dass sich diese schnell lokalisieren und beseitigen lassen. Da die Analyse der Inte-
gration vor dem Testbuild und dem Checkin nur wenig Zeit benötigt und die zu untersuchende Codebasis in der Regel überschaubar ist, lässt sich dieser Prozess leicht automatisieren.Wird die statische Analyse vor dem zentralen Build in der Mainline eingesetzt, dann hilft sie beim Beheben von Fehlern, die sich im Rahmen des Testings nicht oder nur sehr schwer aufdecken lassen. Mit ihr können schließlich auch nicht-lauffähige Codeteile sowie Codebereiche, die im Testing unter Umständen nicht durchlaufen werden, untersucht werden.
Build-Time als kritischer Faktor
Innerhalb des SDLC wirkt sich die statische Analyse besonders in der Develop-, Test- und Review-Phase aus. Dabei kann es durchaus sinnvoll sein, die Analyse in den einzelnen Phasen unterschiedlich zu implementieren. Denn sowohl Testing als auch Analyse steigern die Build-Time, und Build-Times von einer Stunde oder mehr unterlaufen die Grundidee von Continuous Integration. In der Regel sollten Builds innerhalb weniger Minuten möglich sein – vorausgesetzt natürlich, alle Commits sind richtig. Genau an dieser Stelle setzt die statische Analyse in der Develop-Phase an: Sie gibt den Entwicklern ein sofortiges Feedback, noch während diese am Code schreiben.
Die Warnungen ähneln der Ausgabe des Compilers. Die Codequalität lässt sich deshalb zeitnah und mit lediglich geringem Aufwand verbessern. In dieser Phase hat die Geschwindigkeit Vorrang gegenüber der Detailtiefe der Analyse. Aus diesem Grund sollten an dieser Stelle nur die wichtigsten Checker benutzt werden, zum Beispiel sicherheitsrelevante Fehler wie etwa Buffer-Overflows oder Abweichungen von Programmierstandards.
Vollständig auf das Design konzentrieren
Die Test-Phase erfolgt schließlich an der vollständigen Codebasis. Da für diese mehr Zeit zur Verfügung steht, kann die Analyse deutlich genauer erfolgen. Diese Phase eignet sich besonders gut dafür, sowohl nach möglichen Tainted-Data-Angriffen als auch nach Parallelitätsproblemen zu suchen. Dennoch sollte man dabei weiterhin die Build-Time im Blick behalten! Unter Umständen kann es zum Beispiel sinnvoll sein, auch auf dem Build-System nur eine Auswahl der benötigten Tests und Analysen durchzuführen, um dann in einem zweiten Schritt tiefergreifende Tests umzusetzen. Auf diese Weise steht der Build mit den aktuellen Commits schnell im Repository zur Verfügung, ohne dass man dadurch auf eine intensive Qualitätssicherung vor dem Deployment verzichten müsste.
Die Review-Phase spielt innerhalb des Continuous-Integration-Prozesses ebenfalls eine wichtige Rolle: Auf jedes Deployment sollte stets ein Review folgen, in dem sich die Entwicklerteams sowohl kritisch mit der Software, als auch mit möglichen neuen Anforderungen oder Zielen auseinandersetzen. In dieser Phase sollte die statische Analyse sehr ausführlich erfolgen. Dadurch lassen sich Fragen nach der Codequalität weitgehend auslagern, und die Entwickler können sich voll und ganz auf Design-
themen konzentrieren.