Pflegebedürftige Altprogramme werden neu strukturiert (Teil 2):

Reverse-Engineering schützt Investitionen in SW-Entwicklung

06.10.1989

Harry Sneed ist Geschäftsführer der SES GmbH, Neubiberg bei München.

Die Frage: Neu entwickeln oder weiterpflegen? muß demnächst

neu gestellt werden. Erschien der Aufwand für eine komplette Neuentwicklung zumeist unangemessen hoch, so stellt Harry Sneed mit dem "Reverse-Engineering" eine Methode vor, existierende Programme umzustrukturieren und damit wartungsfreundlicher zu gestalten.

Im Hinblick auf das im ersten Teil geschilderte semantische Modell erscheint Reverse-Engineering als logische Folgerung aus dem computergestützten Software-Engineering. Das Ziel ist die Invertierung der schrittweisen Verfeinerung. Denn durch den Verfeinerungsprozeß entfernt sich die realisierte Lösung immer mehr von der geplanten Lösung. Am Ende kann es signifikante Unterschiede zwischen den laufenden Programmen und den spezifizierten Funktionen geben.

Abgesehen davon (oder vielleicht deshalb) existieren zahlreiche Softwaresysteme ohne konzeptionelle Ebene. Die einzige Dokumentation des Programms ist das Programm selbst. Diese unbefriedigende Situation soll durch Reverse-Engineering verbessert werden, indem ein Konzept aus der Konstruktion gewonnen wird.

Die Betonung hier liegt auf "ein" Konzept, im Gegensatz zu "dem" Konzept. Denn das aus der physischen Konstruktion

abgeleitete Konzept wird sicherlich nicht mit dem ursprünglichen Konzept - falls es je eins gegeben hat - übereinstimmen. Dennoch ist ein Konzept

eine unerläßliche Basis für die Pflege und Fortschreibung der Programme. Alle Änderungen und Ergänzungen zu der Software sollten vom Konzept ausgehen. Das Soll dem Ist anzupassen, ist also ein wichtiger Grund für Reverse-Engineering.

Ein weiterer Grund ist die Wiederverwendung von Software. Es wird immer wieder behauptet, daß die Software nur zum geringen Anteil problemspezifisch sei. Daher komme es darauf an, Alt-Software für neue Aufgaben wiederzuverwenden. Dies setzt jedoch voraus, daß man weiß, was die Alt-Software macht und wo sie zu finden ist. Die Nachspezifikation der Software kann diese Voraussetzung erfüllen.

Eine andere Behauptung, die inzwischen durch einige Feldexperimente untermauert wurde, ist die, daß modulare, strukturierte Programme leichter zu pflegen seien. Es ist deshalb schon immer eine Forderung der Wartungstechnologie gewesen, modulare und strukturierte Programme zu haben, um die Wartungskosten zu reduzieren. Die monolithischen, unstrukturierten Programme müßten also konvertiert werden.

Dieser Konvertierungsprozeß ist allerdings nicht gleich dem Reverse-Engineering, noch bedeutet Reverse-Engineering dasselbe wie Restrukturierung der Programme. Programme lassen sich restrukturieren, sogar modularisieren, ohne sie auf die Spezifikationsebene, zurückzuführen. Andererseits können Programme in Spezifikationen transformiert werden, ohne sie zu restrukturieren. Es ist wichtig, zwischen diesen beiden Zielen zu unterscheiden.

Bei Reverse-Engineering geht es primär um die Ableitung einer übergeordneten abstrakten Sicht oder eines Konzeptes aus dem Ist. Die Begründung dafür ist die These, daß komplexe Software auf der konzeptionellen Ebene leichter überschaubar und verständlicher ist als auf der Implementierungsebene, wo die Sicht des Wartungspersonals durch die Menge der Implementierutigsdetails stark eingeschränkt ist.

Falls die Software von Anfang an korrekt konstruiert wurde, wird es nicht nötig sein, sie zu restrukturieren. Falls die Software nicht nach den Normen des Software-Engineering gebaut wurde, wird es nötig sein, sie auch noch zu remodularisieren und zu restrukturieren, um sie in einen Spezifikationsrahmen zu bringen, der diesen Grundsätzen entspricht.

Wenn zum Beispiel die Spezifikationssprache zur Beschreibung der Ablaufsteuerung nur Sequenz, Auswahl und Wiederholung zuläßt, wird es kaum möglich sein, Schleifen mit mehrfachen Ausgängen oder Verzweigungen von einem Steuerungsblock in einen anderen darzustellen. Hier wird Restructuring zum Bestandteil von Reverse-Engineering.

Die Transformation vom Quellcode in einen Programmentwurf ist ein sehr komplexen Prozeß mit zahlreichen Regeln. Hier soll nur auf den Zusammenhang zwischen Software-Recycling und Reverse-Engineering eingegangen werden. Das Software-Recycling-System als Werkzeug ist auf der einen Seite von der jeweiligen Programmiersprache abhängig - zum Beispiel Cobol, PL/1 oder Fortran - und auf der anderen Seite von der Entwurfssprache oder Entwurfsmethodik, beispielsweise der Jackson-Entwurfstechnik oder der Warnier-Entwurfstechnik.

Im Falle der Sprache Cobol ist das Programm in vier Teile gegliedert, in die Identification Division, die Environment Division, die Data Division und die Procedure Division. In der Identification Division stehen der Programmname, der Autor, das Datum der Erstellung, die Beschreibung und sonstige Kopfinformationen.

In der Environment Division wird das Programm zur Übersetzungszeit mit seiner Umgebung verbunden. Hier sind die Datei und andere Referenzen auf externe Objekte mit symbolischen Namen verbunden.

In der Data Division sind die Daten des Programms, sowohl die externen (in der File Section) als auch die internen (in der Working Section), und die Parameter (in der Linkage Section) beschrieben. Möglicherweise gibt es auch eine Screen Section zu Beschreibung der Masken und eine Report Section zur Beschreibung der Listen.

In der Procedure Division stehen schließlich die ausführbaren Anweisungen, gegliedert in Abschnitte (Sections) und Absätze (Paragraphen). Diese Anweisungen lassen sich in vier Klassen teilen: Strukturanweisungen wie LABEL und EXIT, Steuerungsanweisungen wie IF, PERFORM, GOTO, Datentransformationsanweisungen wie MOVE, COMPUTE, TRANSFORM und Eingabe-/Ausgabe-Anweisungen wie READ, WRITE, REWRITE.

Diese sprachspezifische Programmstruktur steht einer allgemeingültigen Entwurfsstruktur gegenüber. Das Entwurfsschema muß so ausgelegt sein, daß es für verschiedene Zielsprachen und Zielumgebungen gültig ist. Außerdem dient der Entwurf in erster Linie der Dokumentation, Prüfung und Dialogmanipulation statt der Compilierung und Ausführung, insofern hat er zwangsläufig eine andere Struktur als das Programm selbst.

Das Entwurfsschema teilt sich normalerweise in zwei Teile: den Datenentwurf und den Programmentwurf. Der Datenentwurf umfaßt fünf Entwurfs-Entitäten, die in einem hierarchischen Verhältnis zueinander stehen.

An der Spitze der Hierarchie steht die Datenbankstruktur, darunter folgen die Dateibeschreibungen neben den Masken- und Listenbeschreibungen, darunter die Feldbeschreibungen und wieder darunter die Wertbeschreibungen beziehungsweise Schlüsselverzeichnisse. Die Stufen des Datenentwurfs heißen also Datenbankentwurf, Dateibeschreibungen, Datenkommunikationsentwurf,

Datenkapselentwurf und Datenwertentwurf.

Der Programmentwurf, der dem Datenentwurf gegenüber steht, setzt sich ebenfalls aus fünf Entwurfs-Entitäten zusammen, die in einem hierarchischen Verhältnis zueinander stehen und die mit den Datenentwurfs-Entitäten korrespondieren. An der Spitze und gegenüber dem Datenbankentwurf steht der Prozeßentwurf.

Darunter und gegenüber der Dateibeschreibung stehen die Prozeß-, Programm- und Modulbeschreibungen. Daneben sind die Datenflußentwürfe angeordnet, die die Programme mit den gegenüberstehenden Dateien, Masken und Listen verbinden. Die Schnittstellenentwürfe stehen auf der nächsttieferen Ebene den Datenkapseln gegenüber und darunter hängen die Modulentwürfe mit ihren Pseudo-Code-Anweisungen als Pendant zu den Wertzuweisungen auf der Datenseite. Es ergibt sich daraus folgende Entwurfshierarchie:

- Prozeßentwurf,

- Komponentenbeschreibung,

- Datenflußentwurf,

- Schnittstellenentwurf,

- Modulentwurf.

Die Rückwärtstransformation von Cobol-Quellcode in einen Systementwurf basiert auf der Inversion jener Regeln, die benutzt werden, um Cobol-Code zu generieren, ergänzt um zusätzliche Regeln für die Modularisierung und Strukturierung, damit auch andersartige Programme in ein normiertes Entwurfsschema hineingebracht werden können.

Die Identification Division eines Cobol-Programms mit der

Programm-ID und den Programmattributen fließt zusammen mit den SELECT-Anweisungen für die Dateizuweisungen in die

Modulbeschreibung. Sämtliche COPY-Referenzen werden ebenfalls dort zusammengefaßt. Die File Descriptions werden n die allgemeingültigen Dateibeschreibungen übertragen.

Berichsstrukturen und Maskenstrukturen, die mit Hilfe von Benutzerparametern identifiziert werden, bilden den Datenkommunikationsentwurf. Im Falle der Masken müssen auch die CICS- beziehungsweise IMS. Mapmakros herangezogen werden, um die Feldpositionen und Attribute wiederzugewinnen.

Alle Datenstrukturen in der File Section, Working Storage Section und Linkage Section werden mit ihren Feldbeschreibungen 1:1 in

den Datenkapselentwurf übertragen. Dabei werden die Initialwerte beziehungsweise Values abgesondert und in den Datenentwurf überführt.

Die Datenstrukturen in den Report und Screen Sections gehören zum Datenkommunikationsentwurf und fließen dort als Masken oder Listen ein.

Sich wiederholende Substrukturen mit OCCURS-Klausel werden gemäß der ersten Normalform ausgegliedert und bilden getrennte Datenkapseln, die mehrfach vorkommen (REPETITION). Datenstrukturen, die aufeinander redefiniert sind (REDEFINES) werden ebenfalls abgesondert und in eigene Datenkapseln eingetragen, die wahlweise vorkommen (SELECTION). Hierdurch ergeben sich Sequenz, Auswahl und Wiederholung in dem Entwurf der Datenstrukturen.

Aus der Procedure Division werden die Datei-, Masken,- und Berichtsreferenzen gesammelt, um den Datenfluß zu entwerfen. Die

CALL-, ENTRY-, CICS-, IMS- und IO-Operationen, werden ausgewertet,

um die Programm-Schnittstellen zu gewinnen. Die Steuerungsanweisungen IF, GOTO und PERFORM werden in die Steuerungsanweisungen IF, WHILE und PERFORM übersetzt, die Code-Abschnitte und Verbundanweisungen oder in Pseudo-Code-Sections überführt.

Aus den numerischen und literalen Konstanten werden Variablen generiert mit Einträgen in der Datenwerttabelle. Aus Code-Sequenzen, die mehrfach vorkommen, werden COPY-Segmente generiert, die im Entwurf

eigene Module bilden. Eigene Module werden auch für die Dateizugriffe, die Maskeneditierung und die Listenaufbereitung geschaffen.

Am Ende steht die Programmbeschreibung

Am Ende des Transformationsprozesses enthält die

Entwurfsdatenbank eine aggregierte, abstrahierte, logische Beschreibung der Programme in Form von normalisierten Relationstabellen, die zur Dokumentation, Respezifikation und Regenerierung derselben Programme dienen können.

Außerdem ist diese logische Beschreibung von Cobol-Sprache und der jeweiligen Zielumgebung weitgehend unabhängig, da sie auf einer höheren semantischen Ebene liegt. Insofern entspricht diese Beschreibung dem logischen Datenmodell in dem ANSI-SPARC-Dreischichten-Modell, also der mittleren Ebene zwischen der Konzeptionsebene und der physischen Realisierung.

Dieser Prozeß gilt für die Cobol-Sprache. Für andere Sprachen gibt es ähnliche Prozesse, so zum Beispiel für PL/1, aber auch für Sprachen der vierten Generation wie Natural, wo REINPUT- und ESCAPE-Anweisungen immer noch zu strukturellen Anomalien führen.