Strategien fuer das Downsizing (Teil 2) Die Zerlegung grosser Programme erfordert eine genaue Vorplanung

04.06.1993

Beim Downsizing bereitet die Portierung der Software die groessten Schwierigkeiten. Aufgrund ihres Umfangs muss sie oft unterteilt werden. In der zweiten Folge seiner dreiteiligen Serie erlaeutert Harry Sneed* Strategien zur Zerlegung grosser Programme und zeigt die Vor- und Nachteile des ablaufbezogenen, funktionalen und objektorientierten Ansatzes.

*Harry Sneed ist Technischer Direktor bei der SES Software- Engineering GmbH in Ottobrunn bei Muenchen.

Fuer die nachtraegliche Modularisierung grosser monolithischer Programme gibt es in der Fachliteratur kaum Anhaltspunkte. Die Re-Engineering-Forschung hat sich bisher vorwiegend mit der Restrukturierung, Bereinigung und Konvertierung einzelner Programme beschaeftigt. Neben diesen wissenschaftlichen Ansaetzen bietet sich ein pragmatisches Konzept an, das sich an die funktionale Gliederung nach Harlan Mills anlehnt, naemlich die Aufteilung nach logischen Funktionen.

Der graphentheoretische Ansatz sieht das Programm als einen gerichteten Graphen mit Kanten und Knoten. Die Knoten sind die Entscheidungen, die Kanten die Verzweigungen. Die Moeglichkeit, ein Programm zu zerlegen, ist durch die Zerschneidung des Gesamtgraphen in Teilgraphen gegeben. Der Schnitt erfolgt an der Stelle, an der die wenigsten Kanten zwei in sich verdichtete Teilgraphen verbinden. Daraus ergibt sich ein Modul pro Teilgraph. Diese Art Zerlegung wird hier als die ablaufbezogene Methodik bezeichnet.

Eine Aufteilung nach logischen Funktionen setzt Kenntnisse der Anwendung voraus. Die Codebloecke, die die einzelnen Funktionen ausfuehren, werden identifiziert und auseinandergerissen. Falls die Funktionen ueberlappend oder verquickt sind, muessen sie dupliziert werden. Daraus ergibt sich ein Modul pro Funktion. Diese Vorgehensweise wird hier als die funktionsbezogene Methodik bezeichnet.

Der dritte Ansatz teilt das Programm nach Objekten auf. Hier kommt es darauf an, die Objekte der Verarbeitung zu identifizieren und die Operationen und ihre Bedingungen den einzelnen Objekten zuzuordnen. Hierdurch entstehen abstrakte Datentypen - ein Modul pro Objekt. Diese Art der Programmzerlegung wird hier als die objektorientierte Methodik bezeichnet.

Allen Methoden gemeinsam ist die Art der intramodularen Kommunikation. In dem Hauptprogramm wird ein Zustandsvektor aufgebaut, der das letzte und das folgende Modul sowie die vorhergehende und die naechste Eingangsmaske registriert. Beim Aufruf eines Unterprogramms erfolgt die Entscheidung, welche Eingangsmaske zu waehlen ist.

Das Hauptprogramm muss daher nach jedem einzelnen Unterprogrammaufruf pruefen, ob die Verarbeitung an einer anderen Stelle im Hauptprogramm fortzusetzen oder ob ein weiteres Unterprogramm aufzurufen ist. Da jedem Aufruf ein weiterer folgen kann, ist dieses Vorgehen gezwungenermassen rekursiv. Wird die Ausfuehrung in einem Unterprogramm entweder abgebrochen oder normal beendet, wird sie in das Hauptprogramm zurueckgefuehrt, um dort den Abschluss einzuleiten.

Natuerlich muessen die Dateien und Datenbanken alle in dem Hauptprogramm eroeffnet und geschlossen werden. Es ist notwendig, dass Online-Programme, die Maskenein- und -ausgabe sowie die Transaktions-Restart- und Recovery-Funktionen im Hauptprogramm stattfinden. Die Unterprogramme koennen die Masken zwar verarbeiten, duerfen sie aber nicht selbst empfangen oder senden. Zu diesem Zweck muss die Steuerung an das Hauptprogramm zurueckgegeben werden.

Es gibt also zahlreiche technische Einschraenkungen, die sich von System zu System unterscheiden. Man ist gut beraten, sie alle sorgfaeltig zu untersuchen, ehe mit dem Zerlegungsprozess begonnen wird.

Nach der Graphentheorie ist der prozedurale Teil eines Programms ein gerichteter Graph. Entscheidungen wie CASE OF, DO WHILE, IF sind die Knoten, die Befehlssequenzen und Verzweigungen die Kanten des Graphen.

PL/1- und Cobol74-Programme auf dem Mainframe sind oft aufgrund der mangelhaften Ausbildung der Entwickler in vielen Anwenderunternehmen uebermaessig komplex. Es wird kreuz und quer von einem Ende der Software zum anderen hin und her gesprungen. PL/1- Programme haben in der Regel eine bessere Struktur, aber der Aufbau der Cobol74-Programme ist nicht weit entfernt von der Assembler-Ebene.

Die Entflechtung des Ablaufnetzes und seine Ueberfuehrung in eine Baumstruktur ist die Aufgabe der Restrukturierung. Das Problem der Modularisierung liegt darin, das ganze Netzwerk darauf zu untersuchen, wo die wenigsten Verbindungen bestehen, um dort den Graphen in Teilgraphen zu zerlegen.

Zwei Loesungsansaetze bieten sich hier an. Einer davon ist der mathematische Ansatz, die Cluster-Analyse. Der Verdichtungsgrad der Verzweigungen wird errechnet. Daraus lassen sich Verdichtungsschemen bilden. Eine Alternative bildet der visuelle Ansatz, bei dem der Graph aufbereitet wird, damit ein Mensch die Teilgraphen erkennen und trennen kann. In beiden Faellen muessen die Verbindungen zwischen den Clustern beziehungsweise Teilgraphen gekappt und als Forderungen (Requests) in einen Steuerungsprozess umgesetzt werden. Auf diese Weise sind die Teilgraphen nur ueber einen uebergeordneten Steuerungsgraphen, den Monitor, miteinander verbunden.

Der Vorteil des ablaufbezogenen Ansatzes liegt darin, dass er sich im Prinzip automatisieren laesst. Es gibt sowohl eine voll- als auch eine semiautomatische Loesung. Der Nachteil ergibt sich aus der Tatsache, dass die daraus entstandenen Module weder den betriebswirtschaftlichen Funktionen noch den informationstechnischen Objekten entsprechen. Sie sind rein prozedurale Einheiten.

Der ablaufbezogene Ansatz eignet sich fuer Bereiche, in denen die Funktionen des Programms stark ueberlappend beziehungsweise optimiert sind, dort, wo ein Codeabschnitt verschiedene Funktionen ausfuehrt oder wo Funktionen auf verschiedene Codeabschnitte verteilt sind.

Der funktionale Ansatz ist der Favorit der Endbenutzer und der DV-Leitung, weil sie die betriebswirtschaftlichen Funktionen kennen und glauben, damit einen Zusammenhang zwischen Programm und Organisation herstellen zu koennen. Die Schwierigkeit liegt darin, dass die wenigsten Programme die funktionale Gliederung erkennen lassen. Dies trifft ganz besonders auf die grossen Anwendungen zu, die ueber Jahre gewachsen sind. Als eine Art Flikkenteppich sind sie kaum eine Eins-zu-Eins-Abbildung der betriebswirtschaftlichen Funktionalitaet.

Falls sie aber doch zu sehen sind, bietet sich dieser Ansatz an. Erkennbar sind die Funktionen in der Sprache PL/1 zum Beispiel als geschlossene BEGIN-Bloecke oder interne Prozeduren, die vom restlichen Code klar abgegrenzt sind. In Cobol koennten Funktionen Sections sein oder zusammenhaengende Absaetze, die mit PERFORM THRU ausgefuehrt werden.

Eine wichtige Rolle spielt hier die Kommentierung. Sehr oft sind Funktionsbloecke am Anfang mit einem Kommentarblock versehen, der erkennen laesst, dass hier eine besondere Funktion ausgefuehrt wird. Das Ende des Codeabschnitts koennte ebenfalls markiert sein. Falls sich das Programm nach Funktionsbloecken aufteilen laesst, wird die Steuerungsfunktion zum Hauptprogramm, die einzelnen Funktionen beziehungsweise Transaktionsarten zu Unterprogrammen. Gemeinsam benutzte Funktionen, die von den Einzelfunktionen aus angesprungen werden, lassen sich in Dienstmodule umwandeln.

Der Vorteil des funktionalen Ansatzes ist die Verstaendlichkeit fuer den Benutzer und die Lesbarkeit des Codes. Je genauer die Codestruktur die betriebswirtschaftliche Funktionalitaet reflektiert, desto leichter ist es fuer den betriebswirtschaftlich orientierten Entwickler, sie verstehen.

Allerdings laesst sich die Vorgehensweise beim funktionalen Ansatz nicht automatisieren. Es gibt keine mathematischen, graphentheoretischen oder sonstigen Algorithmen, die die betriebswirtschaftliche Funktionalitaet indizieren koennen. Einen Zusammenhang zwischen Codeabschnitten und logischen Funktionen bleibt dem Experten vorbehalten, dessen Aufgabe es ist, die Funktionen aus dem Code zu extrahieren. Diese Feststellung ist eine der groessten Enttaeuschungen der Anwender mit der Reverse Engineering-Technologie. Sie kann ihm alles moegliche liefern - Ablaufgraphen, Programmbaeume, Datenfluesse, Transaktionspfade - aber eben keine betriebswirtschaftlichen Funktionen

Beim objektorientierten Ansatz werden aus den Mainframe-PL/1- Programmen Cii-Klassen geschaffen, und aus Mainframe-Cobol74- Programmen gehen OO-Cobol-Klassen hervor. Natuerlich muessen die modularisierten PL/1-Prozeduren zunaechst in C-Prozeduren, und Cobol74-Module in Cobol85-Prozeduren konvertiert werden, aber die zugrundeliegende Klassenstruktur wird durch die Modularisierung der Grossrechner-Programme geschaffen.

Der Ausgangspunkt fuer eine objektorientierte Zerlegung ist die Identifikation der Objekte. Potentielle Objekte sind alle Datenstrukturen, aber nur alle Stufe-1-Deklarationen als Objekte zu deklarieren, waere zu einfach. Objekte sind auf jeden Fall:

- Saetze einer Datei,

- IMS-Segmente,

- Codasyl-Satzarten,

- Adabas-DDMs,

- Berichte sowie

- Bildschirmmasken.

Als Objekte koennen moeglicherweise gelten:

- Tabellen,

- interne Arbeitsbereiche,

- zusammenhaengende Datenmengen sowie

- Datengruppen jeglicher Art, die als Gruppe verarbeitet werden.

Die Identifizierung der Objekte muss durch den DV-Experten erfolgen, danach kann ein Automatismus in Gang treten. Fuer jedes Objekt werden im ersten Schritt die Zugriffe (Input-Output- Operationen) auf das Objekt erkannt und die Pfade zu diesen Zugriffen aus dem prozeduralen Code herausgenommen.

Im zweiten Schritt erfolgt die Indizierung der Operationen beziehungsweise aller Anweisungen, die die Attribute des Objektes veraendern. Zudem werden die Pfade zu diesen Operationen (Pfadausdrueckesamt Bedingungen aus dem prozeduralen Code entnommen. Die Zugriffe auf das Objekt und

die Operationen, auf die Objektattribute sowie die Bedingungen, die zur Ausloesung dieser Zugriffe und Zustandsveraenderungen fuehren, ergeben die Methoden der Klasse.

Nachdem die Klassen gebildet sind, folgt die Regelung der Kommunikation zwischen ihnen, also die Nachrichtenvermittlung. Hier kommt es darauf an, zu erkennen, welche Fremddaten beziehungsweise Attribute eines anderen Objektes eine

Klasse benoetigt, um ihre eigenen Zugriffe und Zustandsveraenderungen auszufuehren. Der Empfang erfolgt ueber eine Import-Schnittstelle. Auf der anderen Seite muessen die Daten vom fremden Objekt ueber eine Export-Schnittstelle gesendet werden. Auf diese Weise entstehen zahlreiche Verbindungen zwischen den Klassen: die sogenannten internen Schnittstellen.

In einem weiteren Schritt koennte die Vererbungstechnik eingefuehrt werden, indem gleichartige Attribute und Operationen identifiziert und in uebergeordnete Musterklassen verlagert werden, von woher sie spaeter geerbt werden. Dies ist jedoch eine Optimierung, die nicht unbedingt zum Modularisierungsprozess gehoert.

Die Nachteile des objektorientierten Ansatzes liegen in dem schwierigen Verfahren und dem unsicheren Ergebnis. Der Vorteil ist klar. Wenn das Ziel eine objektorientierte Sprache ist, dann gibt es keinen anderen Weg, ausser dem, die Programme von Grund auf neu zu entwickeln. Da objektorientierte Sprachen den softwaretechnischen Belangen einer Client-Server-Architektur am besten entgegenkommen, ist dieser Ansatz trotz aller damit verbundenen Schwierigkeiten eine ernstzunehmende Alternative.

Abgesehen davon, welche Modularisierungsstrategie gewaehlt wird, bleiben drei Probleme noch zu loesen:

- die Aufloesung der Subroutinen,

- die Aufteilung der Daten sowie

- die Umsetzung der Datenbankzugriffe.

Egal, ob nach der Ablauf-, der Funktions- oder der Objektstruktur zerlegt wird, muessen die mehrfachen Subroutinen ueberall dort eingebaut werden, wo sie referenziert, das heisst, auf die Module verteilt werden.

In der Sprache PL/1 wird das Konzept der internen Prozeduren haeufig verwendet. Aus einer grossen Hauptprozedur erfolgt der Aufruf zahlreicher kleiner, interner Prozeduren. In der Sprache Cobol74 werden einzelne Paragraphen haeufig per PERFORM aufgerufen. In beiden Faellen werden die Subroutinen von vielen Stellen im Mainline-Code aus aufgerufen. Die Subroutinenaufrufe koennen mehrfach verschachtelt sein, so dass ein hoechst komplexes Netz entsteht.

Ist das Programm in mehrere Module aufgeteilt, muessen die Subroutinen ebenfalls aufgeteilt werden. Jedes Modul wird in bezug auf seine Verzweigungen untersucht. Der Codeabschnitt, den PERFORM aktiviert, wird wiederum analysiert, um festzustellen, welche Subroutinen er enthaelt. Dieser Analysevorgang wird solange wiederholt, bis es kein PERFORM mehr gibt, das heisst, bis alle Subroutinenaufrufe aufgeloest sind. Hierfuer empfiehlt es sich, adaequate Werkzeuge einzusetzen.

Die betreffenden Subroutinen muessen in das entsprechende Modul hineinkopiert werden. Dies hat zur Folge, dass die gemeinsam benutzten Subroutinen mehrfach vorkommen - in jedem Modul, in dem sie aufgerufen sind. Falls es viele solcher Subroutinen gibt, wird der Gesamtcode wegen der Redundanzen um ein Vielfaches groesser. Redundanz ist der Preis fuer die Remodularisierung.