Automatiserte Migration alter COBOL Programme in Java Harry M. Sneed ANECON GmbH, Wien Email: [email protected] Zusammenfassung: Dieser Beitrag befasst sich mit einer Fallstudie zur Software-Migration. Als ersten Teil einer umfassenden Migration von Bull/IDS-COBOL zu Java/Oracle werden 93 Hauptprogramme und 41 Unterprogramme erst saniert und anschließend konvertiert. Die Hauptarbeit besteht drin, ein automatisiertes Transformationswerkzeug zu entwickeln. Die anschließende Umsetzung der Programme läuft voll automatisch ab. Dieser Praxisbericht schildert wie das Transformationswerkzeug funktioniert. Es handelt sich hier um eine Source zu Source Transformation ohne Reverse Engineering. 1 Ausgangssituation Der hier betrachtete COBOL Code ist in einem speziellen Dialekt verfasst. Die Daten sind in einer vernetzten CODASYL Datenbank gespeichert. Die Verbindung zur Benutzerschnittstelle und die Verwaltung der Online-Transaktionen werden von einem Hersteller-spezifischen TP-Monitor aus den 70er Jahren bewerkstelligt. Allerdings erreicht dieser TP-Monitor eine sehr hohe Performanz, die von jedem Nachfolgesystem nur schwer erreichbar ist. Die Architektur des Altsystems entspricht klassischen Mainframe Applikationen. Die Data Division ist in fünf Speicherbereiche aufgeteilt: einen Datenbankbereich, eine Daten-Cache, einen Eingabebereich, bzw. Linkage Section, einen Ausgabebereich, bzw. Communication Section und einen Arbeitsbereich, bzw. Working-Storage. Die Prozedur-Division besteht aus mehreren hundert kleinen Codeblöcken, bzw. Absätzen, bestehend aus 3 bis 30 Anweisungen, die auf die Felder in der Data Division beliebig zugreifen und über GOTO Verzweigungen von einem Baustein zum Anderen hin und her springen. Ein Durchschnittsprogramm hat ca. 5 Kilo Codezeilen davon 2 bis 3 Kilo Datendefinitionen, von denen die Meisten nie benutzt werden. In der Regel allerdings verarbeitet ein Codebaustein die Daten von einem einzigen Datensatz. Dies gab den Anlass die Codebausteine den Datensätzen zuzuordnen die sie am meisten verarbeiten. 2 Alternative Lösungen In einer solchen Situation hat der Anwender theoretisch mehrere Alternativen: a) Er kann die Altsysteme durch Standardsysteme ablösen b) Er kann die alten Programme in COBOL erhalten und in einer Java Umgebung kapseln c) Er kann die COBOL Programme nachdokumentieren und reimplementieren d) Er kann den alten Code in die neue Sprache konvertieren e) Er kann die Altsysteme an einen externen Dienstleister in Pflege übergeben. Was er denn damit macht ist seine Sache. In diesem Falle wurde aus Zeit- und Kostengründen Alternative d) - Konversion gewählt Die COBOL Programme sollen per Werkzeug automatisch in Java konvertiert und anschließend von Java Entwicklern refaktoriert werden. Der konvertierter Java Code sollte deshalb mit JavaDoc und XML gut nachdokumentiert werden. 3 Spezifikation der Transformation Für den Zweck der automatischen Konvertierung von einer prozeduralen Struktur in eine objektorientierter Architektur worden folgende 15 Grundregeln der Konversion zugrunde gelegt: (1) Alle Daten sollten in statischen “singleton” Objekten enthalten sein, die zu Beginn einer Komponentenausführung angelegt werden. (2) Jede COBOL Datenstruktur, die auf der Stufe eins beginnt, bildet ein „singleton“ Objekt. Einzelne Felder im COBOL Programm die mit Stufe 1 oder 77 beginnen, sind durch den Preprozessor in eine globale Struktur zu versetzen. (3) Die COBOL Datensätze, bzw. Strukturen, werden in Java als “character arrays” implementiert mit normierten Gettern und Settern. (4) Nur jene Daten, die in einer Komponente tatsächlich referenziert sind, werden sichtbar gemacht. Alle anderen Daten sind zwar physikalisch vorhanden aber nicht sichtbar. Sie haben keine Setter und Getter. Das Gleiche gilt für Sätze die nicht adressiert sind. Sie bleiben gänzlich weg. Nur Sätze, die verarbeitet werden, werden aufgenommen. (5) Einzelne Datenfelder werden über ihre Anfangsposition und Länge, sowie über ihren Typ identifiziert. Vektoren, werden auch über Indizes identifiziert. (6) Bedingungsdaten, bzw. 88 Felder, bilden eigene Enumerierungsklassen, die auf ihren Inhalt abgefragt werden können (7) Compiler-spezifische Felder wie Indexed by Felder werden am Ende der jeweiligen Klasse als Integer Daten angehängt. (8) Jeder Absatz in der Procedure Division ist in eine Java Methode umzuwandeln. (9) Jede COBOL Anweisung ist in eine semantisch äquivalente Java Anweisung umzusetzen. (10) Generierte Methoden werden Klassen aufgrund der Referenzhäufigkeit zugewiesen, d.h. eine Methode wird jener Klasse zugewiesen deren Attribute am häufigsten angesprochen werden. Die anderen referenzierten Daten werden über get Methoden aus den fremden Klassen geholt. (11) Die GOTO Verzweigungen sind mit einer Labelvariable zu ersetzen, die von der Controllerklasse interpretiert und angesteuert wird. Dies entspricht einem endlichen Automaten. (12) Die PERFORM Aufrufe werden als direkte Methodenaufrufe implementiert. Für PERFORM Sections und PERFORM THRU werden Steuerungsklassen generiert, die eine Methodenaufrufssequenz beinhalten. (13) Die CODAYSL Datenbankaufrufe werden in SQL Anweisungen umgesetzt, die zu den Klassen gehören in denen die Datenbanksätze gekapselt sind. (14) Vor jeder Methode werden in einem JavaDoc Kommentarblock alle Daten die von der Methode verwendet werden, mit der Art der Verwendung und HTML Links zu den Datendefinitionen, aufgelistet. Dies ermöglicht es dem Entwickler den Datenfluss zu verfolgen. (15) Nach jeder Methode werden ebenfalls in einem JavaDoc Kommentarblock alle potentiellen Nachfolgemethoden mit der Art der Verzweigung, ob GOTO, CALL oder PERFORM, sowie mit einem HTML Link zur nächsten Methode, aufgelistet. Dies erlaubt es den Entwickler Ablaufpfade zu verfolgen. 4 Code Konvertierungsprozess Der Code Transformationsprozess vollzieht sich in sechs aufeinander folgenden Schritte: Schritt 1: Nach dem Motto, erst sanieren, dann migrieren, wird der COBOL Code bereinigt und restrukturiert. Der Originalcode war aufgrund der schlechten Editierungsmöglichkeiten schlecht formatiert und unleserlich. Es gab in einer Zeile mehrere Anweisungen. Verschachtelter Code war nicht eingerückt. Die komplexen if Bedingungen sind tief gegliedert und mit einem Punkt abgeschlossen. Es gibt etliche Perform Thru und GOTO Depending on Anweisungen. Die prozeduralen Anweisungen enthalten jede Menge eingebaute Datenwerte – Konstanten und Literale. Der Zweck der Sanierung ist den alten Code auf einen minimalen Qualitätsstand zu bringen. Die Anweisungen werden gespalten – eine pro Zeile, die If Anweisungen mit End-Ifs abgeschlossen, den verschachtelten Code eingerückt, die Perform Thrus und GoTo Depending Ons eliminiert und festverdrahtete Daten entfernt. Schritt 2: Hier wird der Source-Code eines jeden Programmes um die Copy Strecken expandiert und die Daten restrukturiert. Einzelne Datenfelder werden in einer globalen Datenstruktur gesammelt. Sofern sie vorhanden sind, werden die kurzen Datennamen durch lange sprechenden Namen ergänzt. Das Gleiche gilt für die Prozedurnamen. Schritt 3: In diesem Schritt wird die Datenverwendung analysiert und eine Datenquerverweistabelle erstellt. Darin wird für jeden COBOL Codeblock seine Ein- und Ausgabendaten festgehalten. Schritt 4: Für jede verwendete COBOL Datenstruktur wird eine Klasse erzeugt. Das Objekt ist ein Character Array. Jedes Feld ist ein Abschnitt dieser Zeichenfolge, identifiziert über sein Startposition, Typ und Länge. Damit wird das Problem der Redefinitionen, umgangen. Für jedes Datenfeld wird eine set und eine get Methode erzeugt. Für 88 Felder wird eine Enumerierungsklasse generiert. Für jedes Objekt wird eine Konstruktor und eine Initialisierunsmethode angelegt. Das Ergebnis ist ein Klassenrahmen für jedes Objekt. Schritt 5: Für jeden prozeduralen Codeblock wird eine Java Methode erzeugt. In einem Vorkommentar werden die Einund Ausgabedaten als XML Elemente dokumentiert. Nach jedem Codeblock werden in einem Nachkommentar die Nachfolgemethoden ebenfalls als XML Elemente dokumentiert. Dazwischen ist jede COBOL Anweisung in eine entsprechende Java Anweisung umgesetzt. Die GOTO Anweisungen werden durch eine Zuweisung der Methodenname zur Labelvariable mit Return ersetzt. Die wenigen Anweisungen, die nicht konvertiert werden, werden kommentiert. Schritt 6: Die Methoden werden mit den Klassen zusammengeführt. Außerdem werden hier die angesprungenen Methoden durch Klassennamen qualifiziert. Schließlich werden Container Klassen für die Section Aufrufe erzeugt, in denen die Methodenaufrufsequenzen enthalten sind. Schritt 7: Hier werden HTML Links zwischen Daten und Methoden (Datenfluss) sowie zwischen Methoden und Methoden (Ablauffluss) hergestellt. Damit wird die JavaDoc Dokumentation vollendet. 5 Code Konvertierungsergebniss Das Ergebnis der Code-Konvertierung ist eine Menge Java Klassen, eine für jede Stufe 1 COBOL Datenstruktur, die von einer gemeinsamen Superklasse COBOLObjekt erben. Jede Klasse hat eine Konstruktormethode um das singleton Objekt zu Beginn eines Anwendungsfalles zu generieren und eine Initialisierungsmethode um das Objekt mit den Initialwerten zu belegen. Diese stammen aus den Value Klauseln der COBOL Datenvereinbarungen. Das Objekt selbst ist als Character Array definiert. Für jedes benutzte Feld folgen Get und eine Set Methoden. Bei alphanumerischen Zeichenfeldern wird ein Objekt vom Typ String erzeugt bzw, in die Character Array abgelegt. Bei numerischen Feldern wird entweder ein Integer oder ein Double Wert erzeugt, bzw. abgelegt. public static char[] R129LOCO; public String getR129_SA1() { return getString(R129LOCO,14,1000); } public void setR129_SA1(String inStr) { setAsChar(R129LOCO,inStr,14,1000); } Auch in jeder Klasse ist eine Steuerungsmethode die die über die Label-Variabel „xNextMethod“ die betroffene Methode invokiert. Diese wird von der Controllerklasse überreicht. Innerhalb der Verarbeitungsmethoden wird diese Variable anhand der GOTO und PERFORM Anweisungen gesetzt. public ProcessingInfo performOperation(ProcessingInfo processingInfoIn) { String methodId = processingInfoIn.getMethodId(); if (methodID.equals(“methodId01”) { String xNextMethod = this.method.01(); ProcessingInfo processingInfoOut = new ProcessingInfo(xNextMethod); return processingInfoOut; } Ein Zustandsautomat steuert den Ablauffluss über den Wert der Label-Variabel. Es ist als recursive Prozedur implementiert. public void runStateMachine(ProcessingInfo processingInfoIn) { COBOLObject cobolObject = this.getCOBOLObject(processingInfoIn); processingInfoOut = cobolObject.performOperation(processingInfoIn); runStateMachine(processingInfoOut); } Die Speicherklassen bilden eine Aggregation unter der Kontrollerklasse. Sie sind in fünf Speicherbereiche entsprechend den Bereichen im COBOL Programm aufgeteilt: Database,Cache, Work, Input und Output 6 Stand der Migration Mit dem Werkzeug COB2Java wurde bis jetzt das erste Paket des Projektes mit 93 main programs, 41 sub programs, 200711 code lines, 160060 statements und 2908 function points automatisch konvertiert. Die manuelle Überarbeitung, bzw. Reimplementierung, soll jetzt folgen. Die generierten Sourcen – eine pro Klasse – sind in der Summe zweimal so groß wie das ursprüngliche COBOL Source. Das liegt an den vielen Kommentarblöcken sowie an der Set und Get Methoden die es in COBOL nicht gegeben hat. Wahlweise wird der alte COBOL Code als Kommentar in den Java Methoden eingebettet. Mit JavaDoc ist es möglich über HTML den Code zu betrachten und sowohl die Ablauflogik als auch den Datenfluss zu verfolgen. Dies soll dem Java Entwickler helfen den Code zwecks der Reimplementierung zu verstehen. Es geht hier darum die volle Funktionalität der alten Programme in der Java Welt zu bewahren.