1. DISPLAY "Hallo Münster 2022!"
Wir schreiben das Jahr 2022 - warum sollten wir jetzt noch COBOL lernen?
1.1. Einige Fakten
-
COBOL läuft bei 90% der Fortune 500 Firmen im täglichen Einsatz
-
71% der kritischen Geschäftslogik dieser Firmen ist in COBOL
-
COBOL läuft bei 80% aller Point-of-sale Transaktionen
Quelle: [mf]
1.2. Und nun?
In regelmäßigen Abständen erscheinen Artikel wie diese:
Cobol ist aus großen Banken, Konzernen und Teilen der US-Regierung nicht wegzudenken. Vor allem für die Finanzbranche hat die Uralt-Programmiersprache eine große Bedeutung.
Manager Magazin: US-Banken holen IT-Kräfte aus Ruhestand zurück
Schlecht für die Firmen, gut für die COBOL-Programmierer! |
2. Was ist COBOL denn nun eigentlich?
Eine vom amerikanischen Verteidigungsministerium eingesetze Arbeitsgruppe unter Leitung von Grace Hopper verabschiedete 1960 CODASYL, welches als COBOL-60 bekannt wurde und stets weiterentwickelt wurde.
2.1. Hallo Welt!
Da es zum guten Ton gehört eine Programmiersprache mit einem "Hallo Welt!"-Gruß einzuführen und COBOL nicht dafür bekannt ist mit Traditionen zu brechen, sagen wir zuerst Hallo Welt!
IDENTIFICATION DIVISION.
PROGRAM-ID. halloWelt. *> (1)
PROCEDURE DIVISION.
DISPLAY "Hallo Welt!".
STOP RUN.
1 | Der Programmname |
2.1.1. COMPILE und EXEC
Das Programm könnt ihr ganz schnell in dieser Web-IDE ausprobieren.
Unter helloworld/HELLWORLD.CBL findet ihr den Code von oben.
Mit folgendem Befehl könnt ihr das Programm compilieren und ausführen:
cobc -x -j helloworld/HELLWORLD.CBL (1)
1 | -x erzeugt ein ausführbares Programm und -j führt dieses aus |
In der Ausgabe sollte folgendes stehen:
Hallo Welt!
Auch die folgenden Beispiele könnt ihr nach dem Schema ausprobieren. Erstellt dazu einfach eine entsprechende .CBL-Datei und führt diese analog oben aus.
Wenn ihr die Programme lokal auf eurem Rechner ausführen wollt, haben wir hier auch eine Anleitung unter Verwendung von Docker geschrieben.
Hierbei ist zu beachten, dass die meisten Legacy-COBOL Anwendungen im fixed format geschrieben sind. Das bedeutet, es ist wichtig, auf welcher Spalte ein Befehl oder Zeichen steht:
2.2. Ein Format sie zu knechten
Eine Zeile klassisches Cobol ist 80 Zeichen lang, denn so viele Zeichen passen auf eine normale Lochkarte:
*>(1)
------*A---B---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+-------
* Eine Kommentarzeile *> (2)
IDENTIFICATION DIVISION. *> (3)
PROGRAM-ID. halloWelt. DOKU
PROCEDURE DIVISION.
DISPLAY "Hallo Welt!". *> (4)
STOP RUN.
1 | Die Spalten 1 bis 6 sowie 73 bis 80 werden vom Compiler ignoriert und können zu Dokumentationszwecken genutzt werden.[1] |
2 | Eine Kommentarzeile wird durch einen * in Spalte 7, der Indikatorspalte, erkannt. |
3 | Eine DIVISION beginnt in Area A, dem Bereich von Spalte 8 bis Spalte 11. Ebenso SECTION Namen und PARAGRAPH Namen. |
4 | Normale Befehle stehen in Area B von Spalte 12 bis 71. |
Der GnuCOBOL-Compiler unterstützt zusätzlich noch so einen modernen Modus, den Free-Format Mode. Dabei sind die Einrückungen dann egal und man kann Hallo Welt! noch prägnanter schreiben:
program-id.hello.procedure division.display "Hallo Welt!".
Wem das noch nicht kurz genug ist, nimmt die definitiv kürzeste Variante:
display"Hallo Welt!".
Das soll ein gültiges COBOL-Programm sein? Schauen wir uns an, was
macht, wenn wir den Schalter cobc
setzen:frelax-syntax
$ cobc -x -frelax-syntax -free hallo.cob
hallo.cob: 1: Warning: PROGRAM-ID header missing - assumed
hallo.cob: 1: Warning: PROCEDURE DIVISION header missing - assumed
Da praktisch jede Legacy-COBOL Anwendung im |
2.3. Klarheit der Sprache
COBOL steht für Common Business Oriented Language und hat in diesem Sinne den Anspruch, möglichst lesbar zu sein.
Die Lesbarkeit und Verständlichkeit ist COBOLs große Stärke. Es gibt wenige Programmiersprachen, in denen die tatsächliche Geschäftslogik derart sprechend ablesbar ist. |
In einer Übungsaufgabe haben wir einen Mehrwertsteuer-Rechner implementiert. Zur Erinnerung hier nochmal der Code:
IDENTIFICATION DIVISION.
PROGRAM-ID. MWST.
ENVIRONMENT DIVISION.
DATA DIVISION.
WORKING-STORAGE SECTION.
01 NETTO-BETRAG PIC 999V99.
01 BRUTTO-BETRAG PIC 999.99.
01 MWST PIC V999 VALUE .19.
PROCEDURE DIVISION.
PERFORM CALCULATE-MWST
GOBACK.
CALCULATE-MWST SECTION.
COMPUTE BRUTTO-BETRAG ROUNDED
= NETTO-BETRAG * (1 + MWST)
END-COMPUTE
EXIT.
END PROGRAM MWST.
Wir schauen uns jetzt eine mögliche Implementierung der gleichen Funktionalität in Java an.
2.3.1. Mehrwertsteuer in Java
Wir sparen uns jetzt die schrittweise Implementierung sondern schauen uns direkt die Testfälle und die zugehörige Implementierung an:
class MehrwertsteuerRechnerTest {
@Test
void testNettoBetragVon0EURist0EUR() {
MehrwertsteuerRechner rechner = new MehrwertsteuerRechner();
BigDecimal bruttoBetrag = rechner.berechneNettoBetrag(BigDecimal.ZERO);
assertThat(bruttoBetrag.compareTo(BigDecimal.ZERO), is(0));
}
@Test
void testNettoBetragVon100EURist119EUR() {
MehrwertsteuerRechner rechner = new MehrwertsteuerRechner();
BigDecimal eur100 = new BigDecimal(100L);
BigDecimal eur119 = new BigDecimal(119L);
BigDecimal bruttoBetrag = rechner.berechneNettoBetrag(eur100);
assertThat(bruttoBetrag.compareTo(eur119), is(0));
}
@Test
void testNettoBetragVon1Eur99Centist2Eur37Cent() {
MehrwertsteuerRechner rechner = new MehrwertsteuerRechner();
BigDecimal eur1cent99 = new BigDecimal(1.99d);
BigDecimal eur2cent37 = new BigDecimal(2.37d).setScale(2, RoundingMode.HALF_UP);
BigDecimal bruttoBetrag = rechner.berechneNettoBetrag(eur1cent99);
assertThat(bruttoBetrag.compareTo(eur2cent37), is(0));
}
}
Die Klasse
sieht so aus:MehrwertsteuerRechner
public class MehrwertsteuerRechner {
private static final int ANZAHL_NACHKOMMASTELLEN = 2;
private static BigDecimal MWST = new BigDecimal(0.19d);
public BigDecimal berechneNettoBetrag(BigDecimal bruttoBetrag) {
return bruttoBetrag.multiply(MWST.add(BigDecimal.ONE))
.setScale(ANZAHL_NACHKOMMASTELLEN, RoundingMode.HALF_UP);
}
}
Die Implementierung des Mehrwersteuer-Rechners ist in Java durchaus kürzer, aber ist sie auch verständlicher?
Man vergleiche die Mehrwertsteuer-Logik in Java:
mit der von COBOL:
|
3. Aufbau eines COBOL-Programms
Nachdem wir jetzt ein einfaches COBOL-Programm gesehen haben, wollen wir uns mal die Details genauer anschauen. Nehmen wir den Programmrumpf aus der Mehrwertsteuer-Übung:
IDENTIFICATION DIVISION. *> (1)
PROGRAM-ID. MWST.
ENVIRONMENT DIVISION. *> (1)
DATA DIVISION. *> (1)
WORKING-STORAGE SECTION.
01 NETTO-BETRAG PIC 999V99.
01 BRUTTO-BETRAG PIC 999.99.
01 MWST PIC V999 VALUE .19.
PROCEDURE DIVISION. *> (1)
PERFORM CALCULATE-MWST
GOBACK.
CALCULATE-MWST SECTION.
* Noch nicht implementiert
.
EXIT.
END PROGRAM MWST.
1 | Ein COBOL-Programm besteht aus genau 4 .[2] |
Schauen wir uns nun jede
mal im Schnelldurchlauf an.DIVISION
3.1. IDENTIFICATION DIVISION
Die
enthält nützliche Informationen über das Programm, z.Bsp.
für den Compiler oder für die Entwickler.IDENTIFICATION DIVISION
Verpflichtend ist der Eintrag
.PROGRAMM-ID. program-name.
Für ein Programm wie unseren
Mehrwertsteuer-Rechner ist der Programm-Name unwichtig, aber für Unterprogramme
gibt der Programm-Name an, wie dieses Programm per -Befehl aufrufbar ist.
|
Weitere Attribute in der
sind der Author-Name und das Datum, zu
dem das Programm geschrieben wurde.IDENTIFICATION DIVISION
IDENTIFICATION DIVISION.
PROGRAM-ID. meinErstesProgramm.
AUTHOR. Bob der Dinosaurier.
DATE-WRITTEN. 20th June 2018.
3.2. ENVIRONMENT DIVISION
Die Idee der
ist, dass es genau eine Stelle im Programm gibt,
an der die Konfiguration an die Laufzeit-Umgebung geschieht.ENVIRONMENT DIVISION
Teil der Laufzeit-Umgebung sind z.Bsp. die Pfade von Dateien, die das Programm einlesen oder ausgeben soll.
Das kann man dann in der
angeben:INPUT-OUTPUT SECTION
ENVIRONMENT DIVISION.
CONFIGURATION SECTION.
INPUT-OUTPUT SECTION.
FILE-CONTROL.
SELECT Students ASSIGN TO "C:\Daten\Students.txt"
ORGANIZATION IS SEQUENTIAL.
3.3. DATA DIVISION
In der
werden alle Datenfelder[3] definiert.DATA DIVISION
3.3.1. Definition von Variablen
Schauen wir uns einige Beispiele an. Soll das Programm den Namen des Benutzers einlesen, braucht man ein Datenfeld um den Namen zu speichern.
In COBOL gibt es keinen Datentyp
für beliebig lange Zeichenketten, sondern man muss die Länge vorher fest definieren:String
01 BENUTZER-NAME PIC A(30) VALUE SPACES.
In diesem Beispiel ist das Datenfeld
30 Zeichen lang und darf nur Buchstaben enthalten, da das BENUTZER-NAME
für Alphabet steht.A
Würden wir im
auch Zahlen erlauben wollen, müssten wir die Datenfelddefinition minimal anpassen:
Wir ersetzen das BENUTZER-NAMEN
durch ein A
, welches für alphanumerischen Inhalt steht:X
01 BENUTZER-NAME PIC X(30) VALUE SPACES.
Würde man das
des Benutzers einlesen wollen, bräuchte man ein Datenfeld, in dem nur Zahlen erlaubt sind.
Das könnte man so erreichen:Alter
01 BENUTZER-ALTER PIC 9(02) VALUE SPACES.
Hier wäre das Datenfeld für das
nun 2-stellig numerisch.BENUTZER-ALTER
Folgende
|
Diese Datendefinition für
wäre äußerst ungünstig. Würde ein 100-jähriger unsere Software benutzen, hätten wir ein Problem.BENUTZER-ALTER
Was wäre die Ausgabe dieses Programms?
IDENTIFICATION DIVISION.
PROGRAM-ID. COMMAND.
ENVIRONMENT DIVISION.
DATA DIVISION.
WORKING-STORAGE SECTION.
01 BENUTZER-ALTER PIC 99.
PROCEDURE DIVISION.
DISPLAY "Hallo. Bitte Alter eingeben:"
ACCEPT BENUTZER-ALTER
DISPLAY "Sie sind " BENUTZER-ALTER
GOBACK.
END PROGRAM COMMAND.
Gibt ein Benutzer hier
ein, so würde das Programm antworten:100
Sie sind 10.
Nicht so gut. Wir werden aber später noch Möglichkeiten kennenlernen, auf diese Situationen angemessen zu reagieren. |
COBOL bietet eine ganze Reihe Möglichkeiten, seine Datenfelder möglichst genau zu spezifizieren.
Im ersten Schritt ist es aber ausreichend, wenn wir uns die eben genannten Beispiele merken und noch ein
bißchen Verständnis der
haben.WORKING-STORAGE SECTION
3.3.2. Gruppenstrukturen
Eine weitere wichtige Möglichkeit, Variablen in COBOL zu definieren, sind Gruppenstrukturen.
Wenn wir zu einem Benutzer seinen Vornamen, Nachnamen und das Alter speichern wollen, können wir uns eine Struktur
wie folgt anlegen:BENUTZER
01 BENUTZER. *> (1)
05 VORNAME PIC X(30).
05 NACHNAME PIC X(30).
05 B-ALTER PIC 999. *> (2)
1 | Hier wird die Struktur definiert. Beachte, dass hier keine -Bedingung steht. |
2 | Da ein Befehl in COBOL ist, ist die Bezeichnung reserviert und wir können keine Variable nennen. |
Angenommen, wir haben noch einen Admin und müssen auch seinen Vornamen, Nachnamen und sein Alter merken. Wir würden uns also eine ähnliche Struktur anlegen:
01 ADMIN.
05 VORNAME PIC X(30).
05 NACHNAME PIC X(30).
05 A-ALTER PIC 999. *> (1)
1 | Das Feld heißt nun nicht , sondern . |
Gut. Nun wollen wir dem Admin den Vornamen BOFH [4] zuweisen. Dazu nutzen wir den MOVE-Befehl:
IDENTIFICATION DIVISION.
PROGRAM-ID. DATADEFS.
ENVIRONMENT DIVISION.
DATA DIVISION.
WORKING-STORAGE SECTION.
01 BENUTZER.
05 VORNAME PIC X(30).
05 NACHNAME PIC X(30).
05 B-ALTER PIC 999.
01 ADMIN.
05 VORNAME PIC X(30).
05 NACHNAME PIC X(30).
05 A-ALTER PIC 999.
PROCEDURE DIVISION.
MOVE "BOFH" *> (1)
TO VORNAME
DISPLAY VORNAME
GOBACK.
END PROGRAM DATADEFS.
1 | Der Variable wird der Wert zugewiesen. |
Funktioniert das so?
cobc -x DATADEFS.cbl DATADEFS.cbl:18: error: 'VORNAME' is ambiguous; needs qualification DATADEFS.cbl:8: error: 'VORNAME IN BENUTZER' defined here DATADEFS.cbl:12: error: 'VORNAME IN ADMIN' defined here
Der Compiler beschwert sich: er weiß nicht, welcher
gemeint ist:
Der VORNAME
in der Struktur VORNAME
oder in der Struktur BENUTZER
?ADMIN
Wir müssen ihm auf die Sprünge helfen und unsere Variable qualifizieren:
MOVE "BOFH"
TO VORNAME IN ADMIN *> (1)
1 | Das sagt dem Compiler eindeutig, welcher gemeint ist. |
Mit Hilfe dieser abgestuften Definitionen lassen sich strukturierte Daten in COBOL wunderbar abbilden. Dafür werden wir in den späteren Kapiteln noch diverse Beispiele sehen.
3.3.3. Arrays
Um mehrdimensionale Strukturen abbilden zu können, also beispielsweise Spielfelder oder - ganz allgemein - Matrizen, gibt es Arrays.
Wollen wir ein
Spielfeld für TicTacToe modellieren, können wir Folgendes schreiben:3x3
01 SPIELFELD.
05 Y-ACHSE OCCURS 3.
07 ZELLE PIC X OCCURS 3.
Auf die Zellen des Spielfelds können wir jetzt zugreifen:
MOVE "X"
TO ZELLE(2,1)
Dieser Befehl setzt ein
in die erste Spalte der zweiten Zeile.X
Interessant wird es, wenn wir das ganze Spielfeld auf einmal mit Werten befüllen wollen. Falls wir die Zellen des Spielfelds der Reihe nach durchnummerieren wollen, reicht dafür dieser Befehl:
MOVE "123456789"
TO SPIELFELD
Warum funktioniert das? Wie im Abschnitt über Gruppenstrukturen ist die Erklärung der Compiler:
Für den Compiler ist das Feld
ein ganz normales Datenfeld, das zwar nochmals in einzelne Zellen
unterteilt ist, aber trotzdem wie ein zusammenhängender Speicherbereich behandelt werden kann.
Der Wert Spielfeld
wird also in den Speicher geschrieben und erst beim Zugriff auf dieses Feld über die
Schablonen 123456789
oder Y-ACHSE
interpretiert.ZELLE
Mit diesem Wissen können wir das Spielfeld auch sehr leicht auf einen komplett leeren Zustand zurücksetzen:
MOVE SPACES
TO SPIELFELD
3.3.4. WORKING-STORAGE SECTION
Die WORKING-STORAGE SECTION ist ein zusammenhängender Speicherbereich, in dem alle Variablen des Programms gespeichert werden. Die Datenfeld-Definitionen haben keinen Einfluss auf den tatsächlichen Aufbau des reservierten Speicherbereichs.
Die Datenfelddefinitionen in der |
Schauen wir uns ein kurzes Beispiel an, um das Schablonen-Prinzip zu verdeutlichen. Angenommen, wir haben ein Programm mit folgender
:WORKING-STORAGE SECTION
WORKING-STORAGE SECTION.
01 BENUTZER-ALTER PIC 99.
01 BENUTZER-NAME PIC X(30).
Der Compiler berechnet nun das
aller Datenfelder, also den relativen Startpunkt des Datenfeldes im Speicher:OFFSET
BENUTZER-ALTER: +0 (1)
BENUTZER-NAME: +2 (2)
1 | Das Feld beginnt direkt am Anfang des Speicherbereichs. |
2 | Der Compiler berechnet die Länge aller vorher definierten Felder und kennt somit den Startpunkt von . Die
Länge von ist 2, also belegt die ersten beiden Bytes und beginnt direkt im Anschluss bei Byte 3. |
Insgesamt wäre die
dieses Programms 32 Bytes groß.WORKING-STORAGE SECTION
Das soll an dieser Stelle für einen groben Überblick reichen. Wir werden später ausführlich die Datendefinitionen besprechen.

3.4. PROCEDURE DIVISION
Die
enthält die Anweisungen, um die in der PROCEDURE DIVISION
definierten Daten zu manipulieren.DATA DIVISION
Sie ist hierarchisch aufgebaut und besteht aus Paragraphen, Sections und Statements. Paragraphen und Sections dienen zur
Strukturierung des Programms und können mit
oder PERFORM
GO TO
aufgerufen werden.
Da das Zitieren von Fachartikeln aus den 1970iger Jahren einen Text seriös wirken lässt[5], soll hier
angemerkt werden, dass Sprungbefehle wie
spätestens seit Edgar Dijkstra’s Artikel Go To Statement Considered Harmful verpöhnt sind.
Sie führen zu undurchschaubarem Spaghetti-Code und sind somit nicht mehr verständlich und fehleranfällig. [ed]GO TO
In der
codiert man die Logik des Programms. Schauen wir uns nun der Reihe nach die Bausteine an, die wir brauchen, um unsere Geschäftslogik zu implementieren.PROCEDURE DIVISION
3.4.1. Datenmanipulationen
Angenommen, wir haben eine Variable
die folgendermaßen definiert ist:TEMP-NAME
01 TEMP-NAME PIC X(30).
Wie können wir dieser Variable einen Wert zuweisen?
Dafür gibt es den
-Befehl:MOVE
3.4.1.1. Elementarer MOVE
MOVE "Bob"
TO TEMP-NAME
Für strukturierte Felder funktioniert das ebenso. Nehmen wir das Beispiel aus dem Abschnitt über strukturierte Daten:
01 BENUTZER.
05 VORNAME PIC X(30).
05 NACHNAME PIC X(30).
05 B-ALTER PIC 999.
01 ADMIN.
05 VORNAME PIC X(30).
05 NACHNAME PIC X(30).
05 A-ALTER PIC 999.
Wenn wir jetzt einen
mit den Werten BENUTZER
haben und möchten
ihn zum Thomas|Müller|29
machen, können wir das mit einem einzigen ADMIN
erreichen:MOVE
MOVE BENUTZER
TO ADMIN
Dieser Gruppen-Move funktioniert nur, wenn
und BENUTZER
auch
tatsächlich die gleichen Felder mit der gleichen Länge enthalten.ADMIN
Wären bei
z.Bsp. zwei Felder vertauscht:ADMIN
01 ADMIN.
05 VORNAME PIC X(30).
05 A-ALTER PIC 999. *>(1)
05 NACHNAME PIC X(30).
1 | Das Alter kommt jetzt vor dem Nachnamen |
Dann kommt nicht das gewünschte Ergebnis raus. Der Compiler berechnet die Länge, die
im Speicher belegt, nimmt den gesamten Inhalt und schiebt ihn in die Gruppe BENUTZER
.
Insbesondere interessieren ihn die Feldnamen überhaupt nicht!ADMIN
Möchte man einen Gruppenmove, wo nur Felder mit dem gleichen Namen aufeinander abgebildet werden,
kann man den Befehl
verwenden.MOVE CORRESPONDING
Der Befehl Was könnte der Grund dafür sein? |
Schauen wir uns im Detail an, wie der
Befehl funktioniert.MOVE
3.4.1.2. MOVE unter der Lupe
Angenommen, wir haben ein numerisches Feld:
01 TEMP-FELD PIC 9.
und wir versuchen, diesem Feld einen Buchstaben zuzuweisen:
MOVE "A"
TO TEMP-FELD
Was passiert? Der
-Compiler beanstandet das nicht, aber was passiert
zur Laufzeit?
Probieren wir es aus:GnuCOBOL
IDENTIFICATION DIVISION.
PROGRAM-ID. DATADEFS.
ENVIRONMENT DIVISION.
DATA DIVISION.
WORKING-STORAGE SECTION.
01 TEMP-FELD PIC 9.
PROCEDURE DIVISION.
MOVE "A"
TO TEMP-FELD
DISPLAY TEMP-FELD
GOBACK.
END PROGRAM DATADEFS.
Wandeln wir das Programm nun um und führen es aus:
cobc -x DATADEFS.cbl ./DATADEFS A
Wir sehen also: nichts passiert! Am Ende steht in
ein ungültiger Wert.TEMP-FELD
Das ist ungünstig, aber
bietet eine Compiler-Option, die einen auf solche
fragwürdigen GnuCOBOL
-Befehle hinweist:MOVE
cobc -x --Wall DATADEFS.cbl DATADEFS.cbl:11: warning: numeric value is expected DATADEFS.cbl:7: warning: 'TEMP-FELD' defined here as PIC 9
Es empfiehlt sich generell mit der Compiler-Option Alles, was der Compiler für uns sicherstellt, müssen wir nicht mehr testen! |
Auch an Stellen, wo der Compiler uns nicht weiterhelfen kann, z.Bsp. bei externen Datenquellen,
sind wir nicht auf verlorenem Posten. Wir können mit
überprüfen,
ob in IS NUMERIC
tatsächlich ein gültiger numerischer Wert steht:TEMP-FELD
IDENTIFICATION DIVISION.
PROGRAM-ID. DATADEFS.
ENVIRONMENT DIVISION.
DATA DIVISION.
WORKING-STORAGE SECTION.
01 TEMP-FELD PIC 9.
PROCEDURE DIVISION.
MOVE "A"
TO TEMP-FELD
IF TEMP-FELD IS NUMERIC THEN
DISPLAY "Numerisch"
ELSE
DISPLAY "Nicht numerisch"
END-IF
GOBACK.
END PROGRAM DATADEFS.
Und dieses Programm liefert - wenig überraschend - die Ausgabe
Nicht numerisch
Schauen wir uns noch 2 weitere Möglichkeiten an, Variablen Werte zuzuweisen:
3.4.1.3. INITIALIZE
Mit dem
-Befehl können Variablen zurückgesetzt werden,
d.h. im Wesentlichen im werden numerischen Felder der Wert INITIALIZE
zugewiesen,
und alphanumerischen Felder der Wert ZERO
.SPACE
Das ist insbesondere für Gruppenstrukturen nützlich. Möchte man einen neuen
anlegen,
so sollte man alle Felder der Struktur zurücksetzen, damit keine Daten des alten BENUTZER
noch
irgendwo stehen bleiben.BENUTZER
Eine Möglichkeit wäre:
MOVE SPACES
TO VORNAME IN BENUTZER
NACHNAME IN BENUTZER
MOVE ZERO
TO B-ALTER IN BENUTZER
Da das insbesondere für größere Strukturen sehr umständlich wäre, kann man das kürzer schreiben:
INITIALIZE BENUTZER
Variablen in der
Das Feld |
Welchen Inhalt hat
nach PLZ-MUENSTER-X4TEL
INITIALIZE PLZ-MUENSTER-X4TEL
3.4.1.4. SET
Es gibt eine weitere Möglichkeit um Variablen Werte zuzuweisen, welche insbesondere im Abschnitt über Bedingungen nützlich ist.
Nehmen wir an, wir möchten zu einem
auch noch den BENUTZER
speichern.
Der STATUS
kann dabei die WerteSTATUS
-
NORMAL
-
PREMIUM
-
GESPERRT
annehmen. Diesen beschränkten Wertebereich können wir nun z.Bsp. so modellieren:
01 BENUTZER.
...
05 BSTATUS PIC X VALUE SPACE. *> (1)
88 NORMAL VALUE SPACE. *> (2)
88 PREMIUM VALUE 'P'.
88 GESPERRT VALUE 'G'.
1 | Da ein reserviertes Wort ist, nennen wir die Gruppe . |
2 | Der Wert für entspricht der Standardbelegung von .
ist also der Standardwert, welcher bei einer frischen -Gruppe aktiviert ist. |
Um nun einem
den Status BENUTZER
zu vergeben, können wir sowohlPREMIUM
MOVE 'P'
TO BSTATUS
als auch
SET PREMIUM
TO TRUE
schreiben. Dabei ist die letztere Variante natürlich deutlich lesbarer, denn nicht
jeder Leser des Codes weiß sofort, dass
für P
steht.PREMIUM
Die |
3.4.2. Bedingungen
Natürlich gibt es auch in COBOL die aus anderen Sprachen bekannten IF-Abfragen.
3.4.2.1. IF-Abfragen
Nehmen wir unseren
aus dem vorherigen Kapitel:BENUTZER
01 BENUTZER.
05 VORNAME PIC X(30).
05 B-ALTER PIC 999.
05 BSTATUS PIC X VALUE SPACE.
88 NORMAL VALUE SPACE.
88 PREMIUM VALUE 'P'.
88 GESPERRT VALUE 'G'.
Dann können wir in unserem Programm z.Bsp. diese Bedingungen formulieren:
IF VORNAME = "Michael" THEN
DISPLAY "Hallo Michael!"
END-IF
Oder auch mit einem
:ELSE-Zweig
IF VORNAME = "Michael" THEN
DISPLAY "Hallo Michael!"
ELSE
DISPLAY "Hallo, jemand anderes als Michael!"
END-IF
In COBOL gibt es kein Zeichen für ungleich, sondern man muss das
Schlüsselwort
benutzen:NOT
IF VORNAME NOT = "Michael" THEN
DISPLAY "Hallo, jemand anderes als Michael!"
ELSE
DISPLAY "Hallo Michael!"
END-IF
3.4.2.2. IF-Abfragen mit AND und OR
Schauen wir uns an, wie nützlich die
Stufen sind.
Wollen wir überprüfen, ob ein 88-iger
den Status BENUTZER
hat,
so schreiben wir:GESPERRT
IF GESPERRT THEN
DISPLAY "Benutzer ist gesperrt"
END-IF
Oder analog:
IF NOT GESPERRT THEN
DISPLAY "Benutzer ist nicht gesperrt"
END-IF
Man kann Bedingungen auch mittels
verknüpfen:OR
IF NORMAL OR PREMIUM THEN
DISPLAY "Hallo aktiver Benutzer!"
END-IF
Oder mittels
:AND
IF PREMIUM AND B-ALTER >= 16 THEN
DISPLAY "Du darfst ein Bier bestellen."
END-IF
Da sich COBOL als Ziel gesetzt hat, möglichst in flüssigem Englisch programmieren zu können, ist auch sowas erlaubt:
IF PREMIUM AND
B-ALTER GREATER THAN OR EQUAL TO 16
THEN
DISPLAY "Du darfst ein Bier bestellen."
END-IF
3.4.2.3. Tests auf Datentypen
Wenn wir z.Bsp. das Alter eines
von der Kommandozeile einlesen wollen,
sollten wir auch überprüfen, ob tatsächlich ein gültiger Wert eingegeben wurde.BENUTZER
Um zu prüfen, ob in einer Variable ein numerischer Wert steht, können wir
den Klassentest
benutzen:NUMERIC
IF TEMP-FELD IS NUMERIC THEN
DISPLAY "Das Feld enthält nur Ziffern."
END-IF
Ebenso können wir prüfen, ob der
eines Name
nur Buchstaben besteht:BENUTZER
IF VORNAME IS ALPHABETIC THEN
DISPLAY "Das Feld enthält nur Buchstaben."
END-IF
Der
|
3.4.2.4. EVALUATE
Nehmen wir an, wir wollen je nach
des Status
eine bestimmte Aktion
ausführen. Dann könnte man das mittels BENUTZER
-Abfragen so lösen:IF
IF PREMIUM THEN
DISPLAY "Nutzer ist Premium-Kunde."
ELSE
IF GESPERRT THEN
DISPLAY "Nutzer ist gesperrt."
ELSE
IF NORMAL THEN
DISPLAY "Nutzer ist normal."
ELSE
DISPLAY "Ungültiger Status."
END-IF
END-IF
END-IF
Dieser Code ist sowohl unverständlich, als auch schlecht erweiterbar, falls es eine
neue
- Ausprägung gibt.Status
Wesentlich schöner geht es mit dem
-Befehl:EVALUATE
EVALUATE TRUE
WHEN NORMAL
DISPLAY "User ist normal."
WHEN PREMIUM
DISPLAY "Nutzer ist Premium-Kunde."
WHEN GESPERRT
DISPLAY "Nutzer ist gesperrt."
WHEN OTHER (1)
DISPLAY "Status ist ungültig."
END-EVALUATE
1 | Der -Fall wird immer dann durchlaufen, wenn kein vorheriges Kriterium
zutraf. |
Noch nützlicher wird das
-Statement, wenn man es für
den Vergleich mit mehreren Variablen benutzt.EVALUATE
Wir wollen herausfinden, ob ein
ein Bier bestellen darf.
Das soll er genau dann dürfen, wenn erBENUTZER
-
Den Status
hat und mindestensPREMIUM
Jahre alt ist.16
-
Den Status
hat und mindestensNORMAL
Jahre alt ist.18
Das modellieren wir mit
z.Bsp. so:EVALUATE
EVALUATE TRUE ALSO B-ALTER (1)
WHEN PREMIUM ALSO GREATER THAN OR EQUAL TO 16 (2)
WHEN NORMAL ALSO GREATER THAN OR EQUAL TO 18
DISPLAY "Du darfst ein Bier bestellen."
WHEN OTHER
DISPLAY "Du darfst leider kein Bier bestellen."
END-EVALUATE
1 | Jeder -Zweig muss nun zwei Bedingungen prüfen, welche mit
getrennt werden. |
2 | Die erste Bedingung, die zu evaluieren muss, ist die Condition .
Die zweite, dass das größer oder gleich ist. |
Es ist Geschmackssache, ob man folgende Variante für lesbarer hält:
EVALUATE TRUE ALSO TRUE (1)
WHEN PREMIUM ALSO B-ALTER >= 16
WHEN NORMAL ALSO B-ALTER >= 18
DISPLAY "Du darfst ein Bier bestellen."
WHEN OTHER
DISPLAY "Du darfst leider kein Bier bestellen."
END-EVALUATE
1 | Die zweite Bedingung ist nun nicht auf einen Vergleich von festgelegt,
sondern ist beliebig. |
Semantisch äquivalent wäre auch noch diese Variante:
EVALUATE TRUE ALSO B-ALTER
WHEN PREMIUM ALSO >= 16
WHEN NORMAL ALSO >= 18
DISPLAY "Du darfst ein Bier bestellen."
WHEN OTHER
DISPLAY "Du darfst leider kein Bier bestellen."
END-EVALUATE
Welche Variante man bevorzugt, kann jeder selber entscheiden.
3.4.3. Schleifen
Bei fast jeder Programmieraufgabe gibt es Dinge, die immer wieder getan werden müssen. Auch in COBOL gibt es für solche Fälle die bekannten Schleifen.
3.4.3.1. PERFORM n TIMES
Die einfachste Schleife mit fest definierter Durchlaufanzahl
ist
:PERFORM n TIMES
PERFORM 3 TIMES
DISPLAY "Hallo"
END-PERFORM
Die Ausgabe dieses Programms ist wenig überraschend:
./NTIMES Hallo Hallo Hallo
Die Anzahl der Schleifendurchläufe muss nicht hart codiert sein, sondern kann durchaus variabel sein:
MOVE 4 TO TEMP-ZAEHLER
PERFORM TEMP-ZAEHLER TIMES
DISPLAY "Hallo"
END-PERFORM
Frage 1
Wie oft wird diese Schleife durchlaufen?
Antwort:
|
3.4.3.2. PERFORM UNTIL
In den meisten Fällen will man einen bestimmten
nicht nur Code-Block
mal durchlaufen,
sondern man möchte auch wissen, das wievielte Mal die Schleife schon durchlaufen wurde und mit diesem n
dann etwas machen, z.Bsp. auf eine Tabelle zugreifen oder für die Ausgabe benutzen.Index
Dafür gibt es in COBOL das
-Konstrukt.PERFORM VARYING … FROM … BY … UNTIL …
IDENTIFICATION DIVISION.
PROGRAM-ID. DATADEFS.
ENVIRONMENT DIVISION.
DATA DIVISION.
WORKING-STORAGE SECTION.
01 TEMP-ZAEHLER PIC 99. *> (1)
PROCEDURE DIVISION.
PERFORM VARYING TEMP-ZAEHLER FROM 1 BY 1 *> (2)
UNTIL TEMP-ZAEHLER > 10 *> (3)
DISPLAY TEMP-ZAEHLER
END-PERFORM
GOBACK.
END PROGRAM DATADEFS.
1 | Hier wird der Schleifenindex definiert. |
2 | Setzen des Startwerts von und Definition der
Schrittweite. |
3 | Die Abbruchbedingung. |
Dieses Programm gibt die Zahlen von 1 bis 10 auf der Konsole aus.
Frage 2
Was passiert, wenn man den Index im Beispiel 1 so definiert:
Antwort
|
-
Die Schrittweise ist standardmäßig schon
, es ist also nicht nötig jedes Mal1
explizit hinzuschreiben.BY 1
-
Mit
kann man vorzeitig aus einer Schleife ausbrechen.EXIT PERFORM CYCLE
Auch die
-Anweisung ist optional, man kann den Schleifenindex auch selber verwalten:VARYING
MOVE 1 TO TEMP-ZAEHLER (1)
PERFORM UNTIL TEMP-ZAEHLER > 10
DISPLAY TEMP-ZAEHLER
ADD 1 TO TEMP-ZAEHLER (2)
END-PERFORM
1 | Der Startwert wird gesetzt. |
2 | Der Zähler muss manuell hochgezählt werden. |
3.4.3.2.1. WITH TEST AFTER
Wann wird die Abbruchbedingung der Schleife geprüft? Passiert das vor dem Durchlauf, oder danach? Probieren wir es aus:
MOVE 10 TO TEMP-ZAEHLER
PERFORM UNTIL TEMP-ZAEHLER = 10
DISPLAY TEMP-ZAEHLER
END-PERFORM
Wenn die Abbruchbedingung vor dem Durchlauf geprüft würde, würden wir keine Ausgabe erwarten:
./DATADEFS
>
Und so ist es auch. Wir können den Compiler aber auch dazu bringen, die Abbruchbedingung erst
am Ende zu prüfen, durch Zusatz von
:WITH TEST AFTER
MOVE 10 TO TEMP-ZAEHLER
PERFORM WITH TEST AFTER UNTIL TEMP-ZAEHLER = 10
DISPLAY TEMP-ZAEHLER
END-PERFORM
Nun bekommen wir:
./DATADEFS 10
Diese Unterscheidung wird beim Lesen von Dateien später noch wichtig werden.
3.4.4. Strukturierung
Um die Programmlogik strukturisieren und modularisieren zu können, gibt es in COBOL mehrere Möglichkeiten. Wir werden uns auf zwei Alternativen beschränken, die aber für die allermeisten Fälle genau passend sind.
3.4.4.1. SECTIONs
Programmlogik innerhalb der
kann mittels PROCEDURE DIVISION
strukturiert werden.SECTIONs
Schauen wir uns das Beispiel aus dem Kapitel über Schleifen nochmal an.
Wir könnten die Schleife in eine eigene
auslagern:SECTION
AUSGABE-ZAHLEN-1-BIS-10 SECTION. *> (1)
PERFORM WITH TEST AFTER VARYING TEMP-ZAEHLER FROM 1 BY 1
UNTIL TEMP-ZAEHLER = 10
DISPLAY TEMP-ZAEHLER
END-PERFORM
EXIT. *> (2)
1 | Wir definieren eine mit dem Namen
|
2 | Am Ende einer sollte ein -Befehl stehen, damit der Programmfluss
an die Stelle zurückkehrt, wo die aufgerufen wurde. |
Und diese dann in der
aufrufen:PROCEDURE DIVISION
PROCEDURE DIVISION.
PERFORM AUSGABE-ZAHLEN-1-BIS-10
GOBACK.
Indem man Logik in eine eigene |
Das hat den großen Vorteil, dass jemand direkt beim Blick in die
erkennen kann,
was das Programm macht. Es ist nicht nötig, erst Sinn und Zweck der Schleife zu entschlüsseln.PROCEDURE DIVISION
Man könnte ja auch Kommentare verwenden, um die Logik zu beschreiben, z.Bsp.:
Was ist der Nachteil an Kommentaren im Code? |
3.4.4.2. Paragraphen
Eine weitere Möglichkeit, COBOL-Code zu strukturieren, sind
.
Diese verhalten sich so ähnlich wie eine Paragraphen
, werden aber ohne ein bestimmtes
Schlüsselwort definiert:SECTION
DAS-IST-EIN-PARAGRAPH. *> (1)
DISPLAY "Herzlich Willkommen."
. *> (2)
1 | Die Definition eines besteht nur aus seinem Namen und einem Punkt. |
2 | Ein Paragraph endet immer beim nächsten Punkt. |
Dieser Paragraph kann jetzt auch mittels
aufgerufen werden:PERFORM
PERFORM DAS-IST-EIN-PARAGRAPH
Der Unterschied zwischen |
Schauen wir uns einige Beispiele an.
Nehmen wir ein kleines Programm und strukturieren es mittels
:Paragraphen
IDENTIFICATION DIVISION.
PROGRAM-ID. DATADEFS.
ENVIRONMENT DIVISION.
DATA DIVISION.
PROCEDURE DIVISION.
PERFORM ICH-BIN-EIN-PARAGRAPH
PERFORM ICH-BIN-AUCH-EIN-PARAGRAPH
GOBACK.
ICH-BIN-EIN-PARAGRAPH.
DISPLAY "Hallo,"
.
ICH-BIN-AUCH-EIN-PARAGRAPH.
DISPLAY "Welt!"
.
END PROGRAM DATADEFS.
Führen wir dieses Programm aus, erhalten wir:
./DATADEFS Hallo, Welt!
Genau das Gleiche gilt, wenn wir aus den
jeweils
eine Paragraphen
machen:SECTION
IDENTIFICATION DIVISION.
PROGRAM-ID. DATADEFS.
ENVIRONMENT DIVISION.
DATA DIVISION.
PROCEDURE DIVISION.
PERFORM ICH-BIN-EINE-SECTION
PERFORM ICH-BIN-AUCH-EINE-SECTION
GOBACK.
ICH-BIN-EINE-SECTION SECTION. *> (1)
DISPLAY "Hallo,"
.
ICH-BIN-AUCH-EINE-SECTION SECTION. *> (1)
DISPLAY "Welt!"
.
END PROGRAM DATADEFS.
1 | Die wurden nun . |
An der Ausgabe des Programms hat sich nichts geändert, das können wir leicht nachprüfen.
3.4.4.3. Bitte nicht mischen!
Es ist keine gute Idee, sein Programm mit einer Mischung aus |
Warum ist das so? Schauen wir uns folgendes Beispiel an:
IDENTIFICATION DIVISION.
PROGRAM-ID. DATADEFS.
ENVIRONMENT DIVISION.
DATA DIVISION.
PROCEDURE DIVISION.
PERFORM ICH-BIN-EINE-SECTION *> (1)
PERFORM ICH-BIN-EIN-PARAGRAPH
PERFORM ICH-BIN-AUCH-EIN-PARAGRAPH
GOBACK.
ICH-BIN-EINE-SECTION SECTION.
DISPLAY "Herzlich Willkommen!"
.
ICH-BIN-EIN-PARAGRAPH.
DISPLAY "Hallo,"
.
ICH-BIN-AUCH-EIN-PARAGRAPH.
DISPLAY "Welt!"
.
END PROGRAM DATADEFS.
1 | Wir haben jetzt eine und zwei
|
Frage 3
Welche Ausgabe erwarten wir bei dem obigen Programm? |
Probieren wir auch dieses Programm einmal aus:
./DATADEFS Herzlich Willkommen! Hallo, Welt! Hallo, Welt!
Überrascht? Warum ist das so? Warum haben wir zweimal als Ausgabe?
Hallo, Welt! Hallo, Welt!
Schauen wir uns die SECTION
mal genauer an:ICH-BIN-EINE-SECTION
ICH-BIN-EINE-SECTION SECTION.
DISPLAY "Herzlich Willkommen!"
.
Wo ist diese
zu Ende? Beim ersten Punkt? Nein, eben gerade nicht!
Ein Punkt beendet einen SECTION
, aber eine PARAGRAPH
kann
mehrere SECTION
enthalten und bei einem Paragraphen
auf die PERFORM
werden dann alle SECTION
aufgerufen.Paragraphen
Gut, also haben wir schonmal verstanden, warum die Ausgabe so ist, wie sie ist.
Aber können wir die Mischung aus
und Paragraphen
irgendwie heilen?SECTIONs
In vielen Legacy-COBOL-Anwendungen findet man am Ende jeder
einen SECTION
-Befehl, also z.Bsp:EXIT
ICH-BIN-EINE-SECTION SECTION.
DISPLAY "Herzlich Willkommen!"
EXIT. *> (1)
1 | Hat das -Keyword den gewünschten Effekt? |
Wir können es ausprobieren… aber, um die Enttäuschung vorweg zu nehmen…
Nein, das
hilft nicht weiter.EXIT
Ein Blick in die Dokumentation erklärt auch, warum |
Wie können wir denn dann unsere SECTION
beenden?ICH-BIN-EINE-SECTION
Die Dokumentation ist eindeutig. Eine |
Daraus folgt direkt, dass eine Mischung aus
und Paragraphen
nur unnötige Probleme macht. Man sollte sich für eine Strukturierung entscheiden!SECTIONs
Wir werden unsere Beispiele mittels |
Mit COBOL 2002 gäbe es aber tatsächlich doch eine Möglichkeit, eine
mit einem Keyword zu beenden: durch das neue SECTION
-Konstrukt, welches eigentlich
dazu gedacht ist, eine EXIT SECTION
vorzeitig zu verlassen. Dieses Keyword ist auch bei IBM-Compilern ab
Version SECTION
5.2
verfügbar.
Frage 4
Ist der Einsatz von |
4. Dateien Lesen und Schreiben
Mit COBOL ist es sehr einfach, Dateien einzulesen und auch wieder zu schreiben. Tatsächlich ist das auch eines der Haupteinsatzgebiete von COBOL.
Wir wollen uns anschauen, wie wir eine einfache Datei, in der Datensätze über Personen stehen, einlesen können.
IDENTIFICATION DIVISION.
PROGRAM-ID. IO.
ENVIRONMENT DIVISION.
INPUT-OUTPUT SECTION.
FILE-CONTROL.
SELECT STUDENT ASSIGN TO 'students.txt' *> (1)
ORGANIZATION IS LINE SEQUENTIAL.
DATA DIVISION.
FILE SECTION.
FD STUDENT.
01 STUDENT-FILE. *> (2)
05 STUDENT-ID PIC 9(5).
05 STUDENT-NAME PIC X(20).
WORKING-STORAGE SECTION.
01 WS-STUDENT.
05 WS-STUDENT-ID PIC 9(05).
05 WS-STUDENT-NAME PIC X(20).
01 WS-EOF PIC X(1).
PROCEDURE DIVISION.
OPEN INPUT STUDENT.
PERFORM UNTIL WS-EOF="Y" *> (3)
READ STUDENT INTO WS-STUDENT *> (4)
AT END MOVE "Y" TO WS-EOF *> (5)
NOT AT END DISPLAY WS-STUDENT *> (6)
END-READ
END-PERFORM
CLOSE STUDENT.
GOBACK.
END PROGRAM IO.
1 | Hier wird der Dateiname fixiert. |
2 | Wir beschreiben den Aufbau der Datei. Die Datei muss an den ersten 5 Stellen Ziffern
haben, die der einer Person entsprechen. Ab dem 6. Zeichen kommt dann in einer Länge von 20 Zeichen
der des Studenten. |
3 | Es wird so lange ein neuer Satz, also die nächste Zeile, gelesen, bis der Schalter auf steht. |
4 | Der Inhalt der Datei wird in die übertragen. Ab hier können wir dann mit den gelesenen Daten arbeiten. |
5 | Wird erkannt, dass die Datei bis zum Ende gelesen wurde, wird der Schalter gesetzt. |
6 | Der Inhalt von wird auf der Konsole ausgegeben. |
Das Programm liest die Datei
ein, schreibt die Sätze in die students.txt
und gibt
diese anschließend auf der Konsole aus.WORKING-STORAGE SECTION
4.1. Schreiben von Dateien
Nehmen wir an, wir sollen die Datei aus dem vorherigen Beispiel einlesen und nur die Namen der Studenten ausgeben - ohne die
.
Wir müssen nun also wie vorhin die Eingabedatei Satz für Satz einlesen und dann jeweils den Namen in eine neue Datei schreiben.ID
IDENTIFICATION DIVISION.
PROGRAM-ID. IO.
ENVIRONMENT DIVISION.
INPUT-OUTPUT SECTION.
FILE-CONTROL.
SELECT STUDENT-IN ASSIGN TO 'students.txt'
ORGANIZATION IS LINE SEQUENTIAL.
SELECT STUDENT-OUT ASSIGN TO 'output.txt' *> (1)
ORGANIZATION IS LINE SEQUENTIAL.
DATA DIVISION.
FILE SECTION.
FD STUDENT-IN.
01 STUDENT-IN-FILE.
05 STUDENT-ID PIC 9(5).
05 STUDENT-NAME PIC X(20).
FD STUDENT-OUT.
01 STUDENT-OUT-FILE.
05 STUDENT-NAME PIC X(20). *> (2)
WORKING-STORAGE SECTION.
01 WS-STUDENT.
05 WS-STUDENT-ID PIC 9(05).
05 WS-STUDENT-NAME PIC X(20).
01 WS-EOF PIC X(1).
PROCEDURE DIVISION.
OPEN OUTPUT STUDENT-OUT *> (3)
OPEN INPUT STUDENT-IN
PERFORM UNTIL WS-EOF="Y"
READ STUDENT-IN INTO WS-STUDENT
AT END MOVE "Y" TO WS-EOF
NOT AT END
MOVE WS-STUDENT-NAME
TO STUDENT-NAME IN STUDENT-OUT-FILE *> (4)
WRITE STUDENT-OUT-FILE FROM STUDENT-OUT-FILE *> (5)
END-READ
END-PERFORM
CLOSE STUDENT-IN
CLOSE STUDENT-OUT *> (6)
GOBACK.
END PROGRAM IO.
1 | Wir definieren eine weitere Datei für die Ausgabe. |
2 | Der Record für die Ausgabedatei besteht jetzt nur aus dem des Studenten. |
3 | Die Ausgabedatei wird geöffnet, diesmal für . Logisch. |
4 | Der Name wird in den Record der Ausgabedatei geschrieben … |
5 | … und anschließend direkt rausgeschrieben. |
6 | Zu guter Letzt wird die Datei geschlossen. |
5. KATAs zum Lernen
Name der Kata | Schwierigkeit | Bedingungen | Schleifen | Arrays | Eingabe | Functions | Rechnen |
---|---|---|---|---|---|---|---|