Kategorien
Themen

Was fehlt in COBOL?

COBOL ist eine sehr alte Programmiersprache, nun ja, sehr alt, 1960 ist noch nicht alt, ich bin 2 Jahre jünger und fühle mich auch nicht alt.

Es gab mehrere Standards, 1960, 1968, 1974, 1985, aktuell ist der von 2002. Es gibt Erweiterungen in Richtung Objektorientierung seit Mitte der 1990ziger Jahre.

In Projekten habe ich Mitte der 1980ziger Jahre mit COBOL-74 gearbeitet. Dort brauchte man den „GO TO“ Befehle und hinter jedem Befehl ist einen Punkt „.“ notwendig.
Seit Anfang der 1990ziger Jahre wird COBOL-85 genutzt, mit dieser Version lassen sich Programm sehr gut strukturieren, der „GO TO“ wird zwar unterstützt, wird aber nicht mehr benötigt. Und die Punkte als Abschluss einer Anweisung stören jetzt eher.

Die OOP-Erweiterungen oder gar die Version von 2002 (Frei-Format) habe ich bisher noch nicht in Projekten gesehen, meist geht es bei meinen Kunden ja auch um Erweiterungen von Anwendungssystemen, die vor einigen Jahren konzipiert wurden.

Das Schöne an COBOL ist, dass eine Source, die vor 50 Jahren erstellt wurde, noch heute mit den aktuellen Compiler übersetzbar ist. 

Meines Wissens ist nur der „ALTER“ Befehl nicht mehr im Sprachstandard enthalten, und dieser Befehl war schon bei meinen ersten Berührungen mit COBOL ganz oben auf der Liste der Verbote. Mit ALTER konnte man zur Laufzeit die Reihenfolgen der Abarbeitung ändern, damit war der Programmablauf nicht mehr nachzuvollziehen, gut dass dieser Befehl verschwunden ist.

Aber was fehlt in COBOL heute?

An Befehlen und Sprachstandards eigentlich nichts. Es gibt vier Punkt:

Der Pretty-Printer
Bei dem ersten Punkt geht es wirklich um den Punkt. In Projekten habe ich häufig mit Programmen zu tun, die vor 20 oder mehr Jahren erstellt wurden. Damals waren einige Programmierer noch nicht vollständig mit COBOL-85 vertraut und diese Entwickler nutzten innerhalb von Sections (Blöcken) den Punkt. Ein Punkt terminiert jede Anweisung.
Statt
if x
   if y
      move a to b
   else
      move c to b
   end-if
end-if

kann man auch schreiben
if x
if y
move a to b
else
move c to b.

Es gibt auch einen Befehl, um direkt hinter den nächsten Punkt zu springen (next sentence).
Die Anweisungen-Folge:

move ‚a‘ to b
move ‚c‘ to d
continue.
block-ende.
exit.

ist, sofern nicht innerhalb einer IF-Anweisung, einer Schleife oder ähnliches identisch mit 

move ‚a‘ to b.
move ‚c‘ to d
continue.
block-ende.
exit.

Steht weiter oben im Block „next sentence“, befindet sich bei der zweiten Version das ‚c‘ in der Variable d, und der Entwickler sucht. Haben Sie sofort den Punkt bei „b.“ gesehen? 

Falls ja, stellen Sie sich vor, dass ein wichtiges Programm, vor vielen Jahren von einem Kollegen geschrieben, vor kurzem von Ihnen leicht erweitert, nicht läuft, Sie haben das Problem auf eine Section mit mehreren hundert Anweisungen eingegrenzt, die Source ist schlecht formatiert, einige Zeilen sind auf „Kommentar“ gesetzt, es ist mitten in der Nacht, Sie erhalten regelmäßig Anrufe, wann es weitergeht, … Also viel Stress, und Sie wissen noch nicht, dass es der eine Punkt ist.

Falls die beiden Move Befehle in ein if Statement müssen, wird auch gerne der Punkt übersehen. Sofern der Entwickler das if mit einem end-if abschliesst, findet der Compiler zwar nicht den Fehler, bemerkt aber dass eine end-if zuviel ist.

Leider gab es auch den ein oder anderen Kollegen, der seine Sourcen nicht vernünftig formatierte. Ähnlich wie bei der Programmiersprache c ist COBOL vieles möglich.

Schön wäre ein Pretty-Printer für COBOL Sourcen, der auch die unnützen Punkte entfernt und eine gute Formatierung erstellt. Leider geht dieses nur mit einem Programm, das entsprechend dem Compiler die Source analysiert. Im ersten Beispiel ist ja der Punkt durch zwei end-if zu ersetzen. Manche Punkte können auch nicht ohne weitere Änderung entfernt werden, siehe das next sentence.

Eine Section sollte anschliessend folgen Aufbau. Diesen Aufbau nutze ich auch in meinen Projekten  und bat meine Kollegen, bisher immer mit Erfolg, bitte, ihn einzuhalten. Wobei, die meisten Kollegen machen es ja auch so!

sektion-name SECTION.
    viele Anweisungen, aber nie einen Punkt
    CONTINUE.
sektion-name-ENDE.
    EXIT.

Ein weiterer Fall für den Pretty-Print wäre die Zuweisung auf Aufzählungstype.
01 aufzaehlungs-typ pic x.
88 ist-wahr value ‚1‘.
88 ist-falsch value ‚2‘.
88 weiss-nicht value space.

move ‚1‘ to aufzaehlungs-typ

ist zulässig, aber unübersichtlich, besser wären

set ist-wahr to true

Das Copy-Statement
COBOL lebt, ähnlich wie c, von den Include Dateien, auch wenn sie der COBOL Programmierer in Deutschland meist „Copystrecken“ nennt.
Der Sprachstandard hat den COPY Befehl, und hierbei gibt es den optionalen Parameter „REPLACING“. Ein COBOL Programm hat einen globalen Namensraum, die definierte Variable „meine-variable“ ist also im gesamten Programm sichtbar, eine zweite Variable mit diesem Namen kann es aber geben, der Compiler kann sie aber nicht am Namen unterscheiden.
Gibt es „meine-variable“ in der Struktur eingabe-satz und der Struktur ausgabe-satz, reicht nicht

move meine-variable to meine-variable

sondern es muss qualifiziert werden,

move meine-variable in eingabe-satz to meine-variable in ausgabe-satz

Statt des „in“ kann auch „of“ genutzt werden, ich persönlich nutze „in“ für das Qualifizieren und „of“, wenn ich mit Pointern (ja, das geht auch in COBOL) arbeite.

Es gibt aber auch eine weitere Möglichkeit, die ich lieber nutze.
Beim COPY Statement wird mittels REPLACING der Prefix des Namens angepasst. In den Copystrecken steht dann
10 *-meine-variable pic x(4).

Im Programm
01 eingabe-satz.
copy eingabe-include replacing *- by ein-.

01 ausgabe-satz.
copy ausgabe-include replacing *- by aus-.

move ein-meine-variable to aus-meine-variable

In dem Beispiel ist die Version mit Qualifizierung noch übersichtlich, ich habe aber schon Sourcen gehabt, bei denen die Variablen- und Strukturnamen 20 oder mehr Zeichen lang (zulässig sind 31 Zeichen, und bei generierten Copystrecken sieht man 31 Zeichen häufig) waren und zur Eindeutigkeit über 3 Stufen zu qualifizieren war, also zum Beispiel

move meine-string-variable in eingabe-variante-eins in eingabesatz-kunden to meine-string-variable in ausgabe-variante-zwei in ausgabesatz-bevollmaechtigter

Und das nicht nur für ein Feld, sondern für alle Felder, die bei einem Kunden benötigt werden.

Das COPY Statement mit dem REPLACING ist schön, aber leider wird es erst durch den Compiler ausgewertet. Enthält ein Programm auch SQL Anweisungen, läuft vor dem Compiler der DB2 Preprozessor. Dieser muss die Include-Dateien mit den Host-Variablen einbinden. Host-Variablen sind die Speicherstellen, in die bei Abfrage die Ergebnisse von der Datenbank kopiert oder abgefragt werden. Und diese „exec sql include db2-struktur end-exec“ kennt leider kein Replacing.

Ansätze für Lösungen habe ich schon gesehen. 

Es gibt eine Version des Build Prozesses, bei der alles in einem Lauf passiert, also nicht vor dem Compiler der Preprozessor aufgerufen wird. Bisher habe ich diesen Build Prozesse noch in keinem Projekt gesehen. 

Eine weitere Lösung wäre, den Build um einen eigene Schritt zum Einbinden aller Include Dateien zu erweitern, also auf „exec sql include“ und copy zu verzichten. Dieses Vorgehen hat auch Vorteile bei der Source-Verwaltung. Bei einigen Kunden gab es dieses Vorgehen mit include Anweisungen, aber leider ohne einen Replacing Parameter.

Wenn es „zu wild“ mit dem Qualifizieren sind, siehe das Kopieren von Kunde nach Bevollmächtigter, kann über ein eingebettetes (oder ein externes) Unterprogramm ein eigener Namensraum geschaffen werden.

In dem Unterprogramm steht dann
procedure division using ein, aus.
move meine-string-variable in ein to meine-string-variable in aus

und im Hauptprogramm
call unterprogramm using  eingabe-variante-eins in eingabesatz-kunden, ausgabe-variante-zwei in ausgabesatz-bevollmaechtigter

Ein eindeutige Definition für den Timestamp
In der Datenbank ist ein Timestamp intern meist eine 64-Bit Ganzzahl. In einem COBOL Programm erhält man einen String, meist in einer Länge von 26 Zeichen, entweder als

„2015-03-15-16.04.05.123456“ oder
“2015-03-15 16:04:05.123“

Die drei letzten Stellen ist der Geschwindigkeit der heutigen Rechner, oder war es der Rechner vor 20 Jahren, geschuldet. Ein Timestamp muss eindeutig sein, und wenn der Rechner mehr wie 1000 Inserts pro Sekunde in die Datenbank schafft, reichen eben keine Millisekunden zur Eindeutigkeit.

Viel entscheidender ist der Bindestrich oder das Leerzeichen vor den Stunde und die Doppelpunkte oder die Punkte als Trenner zwischen Stunde, Minute und Sekunde.

Der erste Timestamp wird bei Grossrechner-Programmen, die mit dem DB2 Preprozessor übersetzt wurden, verwendet.
Der zweite Timestamp stammt zum Bespiel von Programmen, die mit dem Microfocus Compiler übersetzt wurden und der ODBC Schnittstelle nutzen. Es ist das Timestamp-Format von ODBC.

Hier sollte ein Schalter vorhanden sein, der das ODBC Format automatisch in der IBM Format umsetzt.

Ach ja, es gibt noch weitere Inkompatibilitäten zwischen verschiedenen COBOL Compilern, die eigentlich nicht seinen sollten, oder zumindest eine Warnung beim Compilieren erzeugen sollten.

Während des Studiums durfte ich ein COBOL Programm, das auf einem IBM Mainframe laufen sollte, schreiben. Ich bereitete natürlich das Programm auf dem PC vor, dort hatte ich ja auch eine COBOL Entwicklungsumgebung. Die Aufgabe lies sich iterativ oder rekursiv lösen, rekursiv war modern, und die Verwaltung der Variablen-Sätze pro Rekursions-Tiefe spannend. Also entwickelte ich die rekursive Version, auf dem PC lief sie super. Die Source war schnell auf dem Grossrechner übertragen und übersetzt. Das Programm lief auch, nur der letzte Rücksprung aus der Rekursion klappte nicht, das Programm war in einer Endlosschleife bzw. brach ab, weil der Datensatz für die Rekursions-Teile -1 nicht definiert war. 
Ich suchte mit Hilfe meines Dozenten mehrere Wochen nach der Lösung, bis er (oder seine Supportanfrage bei IBM) in den Tiefen der Compiler Dokumentation den Grund gefunden hatte. 
Der Compiler auf dem PC hat einen Return-Stack. Bei jedem Perform (Unterprogramm Aufruf) wird die Adresse im Programm auf einen Stapel-Speicher (First-In, First-Out) gespeichert, bei einem Rücksprung Befehl wird diese Adresse geholt und dort mit dem Programmablauf weitergemacht. So funktioniert auch ein rekursiver Aufruf. 
Auf dem Grossrechner ist dieser Return-Stack nicht vorhanden, hier merkt sich das Programm am Anfang der Sektion („Unterprogramm“), von wo es aufgerufen wurde. Beim ersten Aufruf der Rekursion aus dem Hauptteil steht dort also die Adresse des Hauptteils, beim zweiten Aufruf, aus der ersten Stufe der Rekursion, wird die Adresse mit der der Rekursion überschrieben. Geht die Rekursion eine Stufe tiefer, wird die Adresse mit sich selber überschrieben, beim Rücksprung macht das Programm an der richtigen Stelle weiter. Nur der letzte Rücksprung in den Hauptteil klappt nicht, dessen Adresse wurde ja überschrieben.
Das Umstellen von den rekursiven auf ein iteratives Vorgehen war schnell gemacht.

Mehr End-Statements
Es gibt in COBOL viele END Statements, einige benötigt der Compiler, zum Beispiel end-if, end-perform, end-evaluate (ausser man setzt einen Punkt). Andere sind optional, zum Beispiel end-string, end-compute, end-display, …. Diese braucht der Compiler nicht, bei 

compute x = y * 10
move x to z

weiss der Compiler, dass das move nicht mehr zum compute gehört. 
Beim Lesen einer Source ist aber

compute
x = y * 10
end-compute
move x to z

übersichtlicher. Dieses ist auch ein Thema für den Pretty-Printer.

Manchmal fehlt mir das end-move. Der move Befehl ist ja einfach, aber

move spaces to a, b, c, d, e, f

ist zulässig, es werden Leerzeichen auf die Variablen a, b, c, d, e, f zugewiesen.
Jetzt das Ganze mit den qualifizierten Variablen-Name, dann steht da „move“ und einige Bildschirmseiten weiter unten der nächste Befehl, wo hört der move Befehl auf?