Klassische Programmiersprachen gehören noch nicht zum alten Eisen

Automatische Analyse von Fortran- und C-Programmen

08.02.1991

Das Schlimmste ist die Wartung alter Programm. Je öfter nachgebessert wird, desto mehr Fehler entstehen. Um diesem Teufelskreis zu entkommen, versuchen immer mehr Unternehmen auf neue Programmiertechniken auszuweichen. Jürgen Schmitz* zeigt jedoch, daß sich mit Hilfe von Analyse-Tools auch die Wartung von herkömmlichem

C- oder Fortran-Code optimieren läßt.

Für technische und wissenschaftliche Anwendungen sowie für den Bereich der Systemprogrammierung weisen die Sprachen Fortran und C heute den größten Verbreitungsgrad auf Trotzdem hinken diese Sprachen in einigen Punkten hinter moderneren Programmiersystemen her. Im folgenden soll jedoch gezeigt werden, daß der Einsatz eines Programm-Analysewerkzeugs diese Schwächen beheben kann.

Die häufige Verwendung von Fortran und C scheint zunächst erstaunlich, da Fortran eine der ältesten "höheren" Programmiersprachen überhaupt ist und auch die Sprache C bald ihren zwanzigsten Geburtstag feiern darf. In der Zwischenzeit hat es einige Erkenntnisse über strukturierte, modulare und objektorientierte Programmierung gegeben, die zur Entwicklung von Sprachen wie Modula-2 oder Eiffel geführt haben. Gemeinsam ist diesen neuen Sprachen, daß sie weiter vom konkreten Maschinenmodell abstrahieren und Techniken der "sauberen" Programmierung erzwingen, ,wobei sie gleichzeitig den Freiheitsgrad des Programmierers gegenüber Fortran und C einschränken.

Fortran und C sind nicht im eigentlichen Sinne "schlechte" Programmiersprachen, und die Entwickler haben auch keineswegs die anstehenden Probleme der Software-Entwicklung ignoriert. Zur Entwicklungszeit von Fortran waren jedoch Programme mit 100 000 Codezeilen einfach noch undenkbar - zumal sie auf den damaligen Maschinen gar nicht lauffähig gewesen wären.

Ein Anlaß zur Entwicklung neuer Sprachen waren schlechte Erfahrungen mit der aufwendigen und damit teuren Wartung von Cobol-, Fortran- und C-Programmen in großen Softwareprojekten. Verständliche und wartungsfreundliche Software in diesen Sprachen zu schreiben, verlangt den Programmierern ein wesentlich höheres Maß an Selbstdisziplin ab als dies etwa bei einer objektorientierten Sprache der Fall wäre.

Problematisch ist das insbesondere bei Systemen, an denen eine Vielzahl von Programmierern mitgewirkt hat. Je freier die Programmierer arbeiten durften, desto größter war die Gefahr schwerwiegender Fehler - vor allem bei den Schnittstellen-Definitionen.

Die Programmierung von Fortran und C ähnelt einer Motorradfahrt auf kurvenreicher Strecke, wohingegen die Verwendung einer Sprache wie Eiffel eher mit dem Lenken einer großen Limousine auf der Autobahn zu vergleichen wäre. Für das Motorrad spricht der Fahrspaß und das Gefühl der größeren Individualität - manchmal auch die Schnelligkeit. Die Limousine dagegen ist schwerfälliger, bietet aber wesentlich mehr Komfort und Sicherheit.

Ein Kompromiß zwischen Fahrfreude und Sicherheit bietet am ehesten ein Cabrio. Im DV-Bereich entspricht ihm am ehesten die objektorientierte Programmiersprache C + +, die bereits eine beachtliche Verbreitung gefunden hat.

Der Umstieg auf eine neue, komfortable und sichere Programmiersprache ist eben nicht immer möglich. Ein Großteil der Programmierarbeit besteht gerade in der Modifikation, Portierung und Erweiterung bestehender Programmsysteme, bei denen die Sprache schon vorgegeben ist. Solche Systeme sind häufig sehr umfangreich und aufwendig. Es bedarf keiner besonderen prophetischen Gaben, um C und Fortran aus Gründen des Investitionsschutzes noch eine lange Zukunft vorauszusagen. Die Mängel bei der Softwarepflege werden dadurch allerdings nur gravierender.

Besonders brisant sind Wartungsarbeiten, wenn sie nicht mehr vom ursprünglichen Entwicklungsteam durchgeführt werden. Die Arbeit des Programmierers ähnelt in solchen Fällen nicht selten der Situation des oben beschriebenen Motorradfahrers, der mit Slicks bei Regen über eine kurvige Landstraße ohne Leitplanken fährt. Wegwerfen und Neuschreiben sind bisweilen die einzigen Möglichkeiten, wenn das bestehende Programmsystem unzureichend dokumentiert ist und eine Struktur nicht erkennbar wird.

Es bedarf kaum der Erwähnung, daß dieses Verfahren nicht gerade zu den effizienten Lösungen zählt.

Gerade bei Wartung, Erweiterung und Portierung bestehender Fortran- und C-Anwendungen ist ein CASE-Tool erforderlich, das Licht in das bestehende Programmsystem bringt. Zudem muß die Verwendung von Schnittstellen zwischen den Anwendungen und den gewünschten Erweiterungen und Änderungen überwacht werden. Re-Engineering heißt das Zauberwort, das die Wartung nicht nur von Fortran- und C-Programmen vereinfachen soll.

Die Grundlage zu einem Re-Engineering-Tool in diesem Bereich wurde 1978 durch S. C. Johnson an den Bell Laboratories gelegt. Er entwickelte ein "Lint" genanntes Analyse-Tool, das zunächst nur für C-Programme unter Unix lief und vor allem der Portierung des Unix-Kerns auf neue Maschinen dienen sollte. Mittlerweile existieren aber Portierungen und Erweiterungen für C-Programme unter VMS oder OS-9, und es gibt eine Version für Fortran-Programme unter VMS.

Was bietet nun ein solches Analyse-Werkzeug bei der Wartung von bestehenden C- oder Fortran-Programmen? Um im Motorrad-Bild zu bleiben: Programm-Analysewerkzeuge statten den Motorradfahrer mit einem Integralhelm und die Maschine mit neuen Reifen aus, um die gefährlichen Serpentinen der Wartung von Fortran- und C-Programmen sicherer meistern zu können.

Ein wichtiger Aspekt der Tools ist die gemeinsame Analyse aller zum Programm gehörenden Teile. Sie kann vom Fortran- oder C-Compiler nicht vorgenommen werden. Diese sind darauf ausgelegt, möglichst effizienten Code für einzelne, getrennt übersetzbare Programmteile zu erzeugen. Deshalb weisen sie meist nur auf Fehler hin, die eine erfolgreiche Kompilation verhindern.

Exakte Aussagen über Fehler in Programmen sind in vielen Fällen nicht möglich, da über die fraglichen Eigenschaften des Programms nicht oder nur mit sehr hohem Aufwand entschieden werden kann.

Das Leistungsspektrum von Programmanalysen

Die Analyse durch ein Werkzeug erfolgt grundsätzlich statisch, das Programm wird also nicht mit konkreten oder symbolischen Werten ausgeführt, sondern es erfolgt eine Begutachtung des Programmcodes im Hinblick auf spezielle, besonders fehlerträchtige Aspekte der Programmiersprache.

Diese Methode bringt oft nur Hinweise auf eventuelle Fehler ans Licht. Nicht jede Konstruktion, die dem Tool obskur erscheint, muß tatsächlich auf einem Programmierfehler beruhen.

Andererseits können in einer statischen Analyse auch nicht alle Situationen eines bestimmten Fehleraspekts erkannt werden.

Fortran und C weisen im Gegensatz zu neueren Sprachen wie Modula-2 kein strenges Typ-Konzept auf. So schreibt Modula-2 jeder Variablen und jedem formalen Parameter einer Prozedur einen eindeutig definierten, festen Typ vor, der den Wertebereich und die Zugriffsfunktionen festlegt. Hier lautet das Motto: Was in den gleichen Speicherbereich paßt, ist kompatibel. Gerade diese Devise wird aber zur Ursache zahlreicher Fehler, die beispielsweise dann auftreten, wenn irrtümlich ein Zeiger einer Variable zugewiesen wird, die eigentlich einen Integer-Wert enthalten soll, oder wenn Zeiger auf Daten unterschiedlichen Typs derselben Variablen zugewiesen werden.

Besonders unangenehm ist, wenn durch den fehlerhaften Zugriff auf eine Variable eine andere modifiziert wird, deren Inhalt im Anschluß an die erste gespeichert ist. Solche Fehler können vom Compiler insbesondere dann nicht entdeckt werden, wenn Deklaration und Verwendung von Variablen in verschiedenen Modulen erfolgen.

Fortran bietet darüber hinaus inkosistentem Gebrauch eines Common-Blocks in verschiedenen Moduln weitere Fehlermöglichkeiten. Diese könne aber in vielen Fällen durch ein aufgedeckt werden, modulübergreifend auf strenge Typisierung und konsistente Verwendung von Common-Blöcken achtet.

Ein Hauptaspekt der Analysen von Fortran- und C-Programmen ist die konsistente Verwendung von Parameterlisten in der Deklaration und im Aufruf von Prozeduren, weil sie in getrennt übersetzten Programmteilen vorliegen können. Gerade hier erlauben die Fortran- und C-Compiler Programmierfehler, die sehr schwer zu finden sind.

Oft ist eine stundenlange Suche nötig, bis der Entwickler herausgefunden hat, daß eine Bibliotheksprozedur mit zwei und nicht nur mit einem Parameter aufgerufen werden muß oder, daß eine Prozedur einen Parameter vom Typ "int " statt vom Typ "short int" erwartet. Abgesehen von den erheblichen Personalkosten führen solche Langzeitdiagnosen auch zur Überschreitung von Projektterminen.

Dokumentation als wertvolle Hilfe

Ein immer wieder auftauchendes Problem ist die fehlende Dokumentation der Zusammenhänge von Prozeduren in den Modulen eines Programms. Durch die Rekonstruktion der Aufrufhierarchie kann ein Analyse-Werkzeug hier zumindest teilweise Klarheit schaffen. Die dokumentierte Aufrufhierarchie ist eine wertvolle Hilfe, um Zusammenhänge im Programmsystem zu verstehen und insbesondere Auswirkungen von Änderungen abzuschätzen. In einem solchen Dokument sollte natürlich die Beschreibung der Schnittstellen der Prozeduren nicht fehlen.

Nicht nur die Aufrufhierarchie kann im nachhinein dokumentiert werden. Verweislisten (Cross Reference Listings) über die Deklaration und Verwendung von Variablen sind ein unentbehrliches Hilfsmittel, wenn Änderungen oder Korrekturen an einem Programm vorgenommen werden.

Auch diese Listen können von einem Programmanalyse-Werkzeug - quasi als Abfallprodukt der Konsistenzprüfungen - modulübergreifend erzeugt werden, wenn sie nicht bereits zur Dokumentation eines bestehenden Programmsystems gehören.

Auch innerhalb einer Prozedur kann ein Werkzeug nützliche Informationen aus dein Code extrahieren. Ein häufiger Programmierfehler besteht im Zugriff auf eine Variable, der zuvor kein Wert zugewiesen wurde. Solche Konstruktionen führen manchmal erst dann zu Laufzeitfehlern, wenn das Programm portiert wird und auf der neuen Maschine eine andere Vorbelegung des Speichers erfolgt.

Hinweise auf Fehler im Programm kann auch die Information bieten, ob Variablen oder Prozeduren deklariert aber niemals angewandt wurden. Der Grund dafür sind häufig Schreibfehler in Variablen- oder Prozedurnamen, die ohne Analysewerkzeuge oft erst nach langwieriger Suche gefunden werden.

Letztendlich bietet der Einsatz der Programmanalyse Hinweise auf Konstruktionen im Programm, die bei einer Portierung zu Problemen führen können. Sie treten auf, wenn in einem Programmteil von der Kenntnis Gebrauch gemacht wurde, wie das "Alignment" von Variablen, das ist die Abspeicherung auf Wortgrenzen im Speicher, unter einer speziellen Kompilation erfolgt. Bei der Portierung auf eine andere Maschine kann dieses "Alignment" von Variablen natürlich ganz anders aussehen.

Programmanalyse-Werkzeuge weisen bei der Wartung bestehender Fortran- und C-Anwendungen bereits vor der Kompilation auf mögliche Fehlerursachen hin und liefern wichtige Teile der Programmdokumentation. Anwender berichten über Zeiteinsparungen bei der Software-Entwicklung von bis zu 40 Prozent durch den Einsatz der Programmanalyse. Diese Zahl belegt, daß sich die Investition nach kurzer Zeit amortisiert. Darüber hinaus erhöht die Programmanalyse die Qualität der erstellten Software. In Anbetracht der Tatsache, daß ausgelieferte Software heute im Mittel noch acht Fehler pro 1000 Zeilen Sourcecode enthält, ist das auch dringend nötig.