Aus Linux-Magazin 04/2022

Prozedurales Programmieren am Beispiel COBOL

© Oleg Dudko/123rf.com

Das imperativ-prozedurale Paradigma ist so alt wie höhere Programmiersprachen selbst. Dennoch gelten die meisten seiner Prinzipien noch heute. Auch COBOL, eine der ersten prozeduralen Sprachen, kommt weiter zum Einsatz.

Wer das Schlagwort prozedurales Programmieren hört, denkt wohl in der Regel an vergangene Zeiten vor der Einführung von objektorientierten Paradigmen oder der funktionalen Programmierung. Den meisten kommen dann Programmiersprachen wie Pascal oder C in den Sinn, die man vielleicht noch in der Schule oder im Studium erlernen musste. Zumindest der Verfasser dieses Artikels hat auf diese Weise seine ersten Erfahrungen mit diesem Programmierstil gesammelt.

Der vorliegende Artikel stellt das prozedurale Programmieren aus dem Blickwinkel der sogar noch älteren Programmiersprache COBOL vor. Sie lässt erkennen, welche Möglichkeiten Softwareentwickler bereits hatten, als die Entwicklung von höheren Programmiersprachen noch ganz am Anfang stand. Es zeigt sich, dass heute noch aktuelle Features von Anfang an implementiert waren, etwa Strukturierung der programminternen Abläufe, Deklarationen, Lebensdauer und Sichtbarkeit von Variablen, Parameterübergaben innerhalb des Programms und nach außen sowie vieles andere mehr.

Zur Geschichte

Die Programmiersprache COBOL, die Common Business Oriented Language (Abbildung 1), wurde Anfang der 1960er-Jahre dazu entwickelt, Software vor allem für kaufmännische Anwendungen zu erstellen. Dabei sollte schon ein geringes Wissen über Programmierung genügen, ein möglichst einfach lesbar Code war ein weiteres Ziel. COBOL orientiert sich daher insbesondere in seinem imperativen Teil weitgehend an der englischen Sprache und wirkt dadurch für heutige Entwickler eher langatmig und unpräzise. Es gibt je nach COBOL-Dialekt immerhin rund 300 Schlüsselwörter.

Abbildung 1: COBOL gehört zu den ältesten höheren Programmiersprachen und kommt heute hauptsächlich im Mainframe-Umfeld für finanzwirtschaftliche Anwendungen zum Einsatz. Quelle: Scandinavian Stock / 123RF.com

Abbildung 1: COBOL gehört zu den ältesten höheren Programmiersprachen und kommt heute hauptsächlich im Mainframe-Umfeld für finanzwirtschaftliche Anwendungen zum Einsatz. Quelle: Scandinavian Stock / 123RF.com

Trotz der schwierigen Grammatik handelt es sich um eine kompilierte Sprache, aber ohne strenge Typisierung. Der Sprachumfang wurde in mehreren Standards (unter anderem COBOL-74, COBOL-85, COBOL 2002, COBOL 2014) erweitert und teils auch bereinigt. Er geriet dabei aber tendenziell noch unübersichtlicher und wird nicht mehr von allen Implementierungen in vollem Umfang unterstützt. Ungeachtet dieser Nachteile erfreute sich die Sprache bis in die 1980er-Jahre großer Beliebtheit bei der Implementierung von kaufmännischen und finanzwirtschaftlichen Anwendungen, vor allem in Banken und Versicherungen. Schätzungen gehen davon aus, dass sogar heute noch mehr als 200 Milliarden Zeilen COBOL-Code im produktiven Einsatz sind.

Anfang der 2000er-Jahre gab es Bestrebungen, die Sprache mit dem Standard COBOL 2002 für objektorientierte Programmierung zu öffnen. Allerdings verwenden nach Eindruck des Verfassers nur die wenigsten Entwickler die entsprechenden Sprach-Features neuerer COBOL-Versionen. Im Bedarfsfall greift man dann doch lieber zu den “echten” OO-Sprachen.

Zum Einsatz kommt COBOL heute überwiegend auf IBM-Mainframes der zSeries, vorwiegend in der Finanzwirtschaft. Als Betriebssystem für Großrechner mit kaufmännischen Anwendungen dient dabei in aller Regel IBMs z/OS, obwohl auf den zSeries-Mainframes unter anderem auf Linux laufen könnte. IBM hat in den letzten Jahren erhebliche Anstrengungen unternommen, seine Plattform auch für Web- und Internet-Techniken zu öffnen.

Für Linux und Windows ist als Implementierung vor allem GnuCOBOL [1] mit einer sehr weitreichenden Unterstützung des Sprachumfangs interessant. Das im Rahmen dieses Artikels erstellte Beispielprogramm wurde ebenfalls damit und mit der Entwicklungsumgebung OpenCobolIDE [2] entwickelt.

Aufgrund der immensen Anzahl von existierenden Fachanwendungen in COBOL kann man davon ausgehen, dass die Sprache auch in den nächsten Jahren noch eine Rolle spielt, da eine Umstellung der Anwendungen auf andere Programmiersprachen immer problematischer wird. Das liegt zum einen an der abnehmenden Anzahl an COBOL-Entwicklern. Zum anderen verursacht eine reine Portierung von Algorithmen von einer Sprache in eine andere zwar hohe Kosten, bringt tendenziell aber wenig Nutzen.

Zu dieser Einschätzung passt auch, dass IBM bei aller Öffnung zu Open Source und Web Enablement (Stichwort ZOWE) parallel noch die Unterstützung für COBOL verstärkt und einen neuen COBOL-Compiler für Linux x86 ankündigt.

Hello World in COBOL

Listing 1 zeigt zum Einstieg zunächst ein ganz einfaches Hello-World-Programm in COBOL. Bereits an diesem Beispiel lässt sich der grundsätzliche Aufbau eines COBOL-Programms erkennen.

Listing 1

HELLO1.CBL

       IDENTIFICATION DIVISION.
       PROGRAM-ID. HELLO1.
       PROCEDURE DIVISION.
       BEGIN.
          DISPLAY "Hello World"
          STOP RUN.
       END PROGRAM HELLO1.

Neben der Struktur ist hier eine COBOL-typische Eigenheit erkennbar, die aus den Zeiten der Lochkarten stammt: Die Eingabe erfolgt nicht als reiner Fließtext, sondern die ersten Spalten bleiben generell für etwaige Zeilennummern frei. Bestimmte Schlüsselwörter beginnen erst ab Spalte 8, während sich andere erst ab Spalte 12 einsetzen lassen.

In neueren COBOL-Dialekten entfällt diese Einschränkung zwar, doch insbesondere bei älteren Sourcen findet sich üblicherweise diese Struktur. Aus Platzgründen haben wir im Folgenden bei allen Listings die Einrückung auf zwei Spalten reduziert. Die zugehörigen Dateien im Download-Bereich zu diesem Artikel haben dagegen die volle Einrückung.

Kommentarzeilen kennzeichnet in COBOL stets ein Asterisk (“*”) in Spalte 7 einer Zeile. Bei mehrzeiligen Kommentaren muss er vor jeder einzelne Zeile stehen. Seit COBOL 2002 besteht die Möglichkeit, Inline-Kommentare für den Rest der Zeile mit »*>« einzuleiten, was aber nicht alle COBOL-Versionen erlauben.

Aufbau und Struktur

Ein COBOL-Programm gliedert sich in mehrere sogenannte Divisions, die wiederum jeweils mehrere Sections umfassen können. Jedes Programm enthält eine »IDENTIFICATION DIVISION« mit einer fakultativen »PROGRAM-ID«. Bei Bedarf können »AUTHOR« und »DATE-WRITTEN« hinzukommen.

Optional kann eine »ENVIRONMENT DIVISION« folgen, die die Interaktion des Programms mit dem Betriebssystem beschreibt. In der »INPUT-OUTPUT SECTION« definiert sie insbesondere den Aufbau und die Attribute von Dateien, auf die das Programm zugreifen möchte. Dementsprechend hat unser Hello-World-Programm keine »ENVIRONMENT DIVISION«.

Die »DATA DIVISION« beschreibt in mehreren Sections die vom Programm verwendeten Datenbereiche. »WORKING-STORAGE SECTION« und »LOCAL-STORAGE SECTION« definieren die programmseitig genutzten Variablen, mit Ausnahme von Übergabeparametern, die die »LINKAGE SECTION« aufnimmt. Hat die »ENVIRONMENT DIVISION« Dateien definiert, gilt es, die zugehörigen Datenstrukturen in der »FILE SECTION« festzulegen.

Die »PROCEDURE DIVISION« beinhaltet den eigentlichen Programmcode, der sich ebenfalls in Sections unterteilen lässt. In diesem Bereich des COBOL-Programms kann man die Sections beliebig benennen.

Im Rahmen des Artikels hat der Autor ein stark vereinfachtes Beispielprogramm zur Verwaltung einer Leihbibliothek implementiert. Die kompletten Listings finden Sie im Download-Bereich zu diesem Artikel. Eines der verwendeten Unterprogramme (Listing 2) lässt die typischen Strukturen eines komplexeren COBOL-Programms erkennen.

Listing 2

BOOKLIB1.COB (Auszug)

  [...]
  * Buchbestand aus Datei einlesen
   IDENTIFICATION DIVISION.
   PROGRAM-ID. READ-ALL-BOOKS.
   ENVIRONMENT DIVISION.
   INPUT-OUTPUT SECTION.
     FILE-CONTROL.
     SELECT BOOKS-SEQ ASSIGN TO 'BOOKS-SEQ.TXT'
     ORGANIZATION IS LINE SEQUENTIAL.
   DATA DIVISION.
   FILE SECTION.
   FD BOOKS-SEQ.
   01 BOOKS-SEQ-FILE.
   COPY 'BOOK.CPY'.
   WORKING-STORAGE SECTION.
   01 WS-BOOK.
   COPY 'BOOK.CPY'.
   01 WS-BOOKS-SEQ-EOF PIC A(1).
   LINKAGE SECTION.
   01 LS-BOOKS.
     05 LIST OCCURS 20 TIMES.
   COPY 'BOOK.CPY'.
   01 LS-BOOKS-COUNT PIC S9999 USAGE COMP.
   [...]

Prozedurale Programmierung

Grundsätzlich bieten sich in COBOL dieselben Möglichkeiten wie in anderen Sprachen der 3. Generation (etwa C oder PL/I). Es gilt aber dennoch, einige Besonderheiten zu beachten.

Variablen definiert Sie in COBOL entweder im »WORKING-STORAGE« oder im »LOCAL-STORAGE«. Der »WORKING-STORAGE« gibt eine Lebensdauer der Variable über die gesamte Laufzeit des Programms vor. Der entsprechende Speicher wird einmal beim Start des Programms alloziert, gegebenenfalls initialisiert und bleibt dann unverändert bestehen, sofern das Programm ihn nicht explizit modifiziert. In anderen Sprachen bezeichnet man dieses Verhalten meist als statischen Speicher.

Der »LOCAL-STORAGE« hingegen begrenzt die Lebensdauer der Variablen auf die Abarbeitung der jeweiligen »PROCEDURE DIVISION«. Die entsprechenden Variablen werden bei jedem Aufruf neu alloziert und initialisiert, sofern entsprechend angegeben. Sonst bleiben sie uninitialisiert.

Listing 3 und Listing 4 zeigen das Hauptprogramm für unsere Beispielanwendung und eines von zwei Unterprogrammen für den Test der Variablenallokation. In »COBUP1« wird der Aufrufzähler »WS-CALL-COUNT« bei jedem Folgeaufruf hochgezählt, weil er in der »WORKING-STORAGE SECTION« definiert wurde. Dagegen behält er in »COBUP2« immer den Wert 1, da er dort aufgrund der Anlage in »LOCAL-STORAGE« bei jedem Aufruf neu initialisiert wird. Beim Aufruf des Hauptprogramms entsteht das in Listing 5 gezeigte Ergebnis.

Listing 3

COBMAIN1.COB

  IDENTIFICATION DIVISION.
  PROGRAM-ID. COBMAIN1.
  PROCEDURE DIVISION.
  MAIN-PROCEDURE.
    DISPLAY "Hello from COBMAIN1"
    DISPLAY "Erster Aufruf der Unterprogramme"
    CALL 'COBUP1'
    CALL 'COBUP2'
    DISPLAY "Zweiter Aufruf der Unterprogramme"
    CALL 'COBUP1'
    CALL 'COBUP2'
    STOP RUN.
  END PROGRAM COBMAIN1.

Listing 4

COBUP2.CBL

  IDENTIFICATION DIVISION.
  PROGRAM-ID. COBUP2.
  DATA DIVISION.
  LOCAL-STORAGE SECTION.
  01 WS-CALL-COUNT PIC S9999 USAGE COMP VALUE 0.
  PROCEDURE DIVISION.
  MAIN-PROCEDURE.
      ADD 1 TO WS-CALL-COUNT.
      DISPLAY "COBUP2" WS-CALL-COUNT
      EXIT PROGRAM.
  END PROGRAM COBUP2.

Listing 5

Programmausgabe

Hello from COBMAIN1
Erster Aufruf Upros
COBUP1+0001
COBUP2+0001
Zweiter Aufruf Upros
COBUP1+0002
COBUP2+0001

Aufruf von Unterprogrammen

Unterprogramme lassen sich in COBOL sowohl extern als auch intern (nested) verwenden. In beiden Fällen rufen Sie sie dann mit dem Befehl »CALL« auf. Handelt es sich um externe Unterprogramme, also solche mit einer separat zu kompilierenden Quelldatei, lassen sie sich entweder statisch oder dynamisch aufrufen (Listing 6). Bei einem statischen Aufruf (Zeile 11), erkennbar am als Zeichenkette kodierten Namen, löst beim Übersetzen der Linker die Referenz auf das Unterprogramm auf, das dann einen Teil des Lademoduls des Hauptprogramms bildet.

Für einen dynamischen Aufruf (Zeile 12) stellen Sie den Namen des Unterprogramms in einer Variablen bereit, das Programm lädt die entsprechende Routine dann erst zur Laufzeit. Das lässt sich mit den LIB- und DLL-Konzepten in Linux oder Windows vergleichen. Der Compiler GnuCOBOL speichert denn auch externe Unterprogramme zumindest im Windows-Umfeld generell in Form einer DLL – hier entfällt die Unterscheidung zwischen dynamischem und statischem Laden.

Unterprogramme haben auch in der eingebetteten Form (Listing 6 ab Zeile 14) keinen Zugriff auf Variablen ihrer Aufrufer, es sei denn, Sie übergeben sie ihnen explizit über die Anweisung »USING«. Dazu müssen Sie sie vorab entsprechend in der »LINKAGE SECTION« und der »PROCEDURE DIVISION« deklarieren.

Listing 6

COBMAIN2.CBL

  IDENTIFICATION DIVISION.
  PROGRAM-ID. COBMAIN2.
  DATA DIVISION.
  WORKING-STORAGE SECTION.
  01 WS-PARM-1 PIC X(32) VALUE "Aufruf aus COBMAIN2".
  01 WS-TEXT-1 PIC X(16) VALUE "MAIN-TEXT-1".
  01 WS-UPRO PIC X(8) VALUE "COBUP2".
  PROCEDURE DIVISION.
  MAIN-PROCEDURE.
    DISPLAY "Hello from COBMAIN1".
    CALL 'COBUP1' USING WS-PARM-1. *> Statischer Call
    CALL WS-UPRO USING WS-PARM-1.  *> Dynamischer call
    STOP RUN.
 * Nested (eingefügtes) Unterprogramm
  IDENTIFICATION DIVISION.
  PROGRAM-ID. COBUP1.
  DATA DIVISION.
  WORKING-STORAGE SECTION.
  01 WS-TEXT-1 PIC X(16) VALUE "UP-TEXT-1".
  LINKAGE SECTION.
  01 WS-PARM-1 PIC X(32).
  PROCEDURE DIVISION USING WS-PARM-1.
  UP-PROCEDURE.
    DISPLAY "Hello from COBUP1 " WS-PARM-1 " " WS-TEXT-1.
    EXIT PROGRAM.
  END PROGRAM COBUP1.
  END PROGRAM COBMAIN2.

Strukturelle Probleme

Eingebettete (nested) Unterprogramme führte erst COBOL-85 ein, vorher gab es Unterprogramme nur in der externen Form. Insbesondere vor der Verfügbarkeit von IDEs erschwerte das die Bearbeitung des Codes stark. Daher findet sich in vielen alten COBOL-Sourcen eine Strukturierung auf Basis des Aufrufs von Sections. Statt per »CALL« wird der Code einer Section in diesen Fällen über die Schleifen- und Sprunganweisung »PERFORM« ausgeführt.

Die Sections bilden dann einen Bestandteil der »PROCEDURE DIVISION« des jeweiligen Programms. Ihre Verwendung lässt weder eine Trennung von Scopes zu, noch existiert die Möglichkeit, lokale Variablen zu definieren oder Parameter zu übergeben. Stattdessen gibt es nur eine globale Sichtbarkeit aller in der »DATA DIVISION« des Programms definierten Elemente. Das führt insbesondere bei größeren Aufgabenstellungen zu schwer les- und wartbaren Programmen mit einer großen Gefahr von Seiteneffekten. Damit war auch der Autor bei Altprogrammen schon wiederholt konfrontiert.

Bereits ein Blick auf die Hauptschleife der Beispielanwendung (Listing 7) mit ihrer fachlich eher begrenzten Logik, in der es für jeden Use Case ein eigenes Unterprogramm gibt, lässt das Problem erkennen: Die Variablen und Logiken der sieben eigenständigen per »CALL« aufgerufen Unterprogramme müssten in die »DATA DIVISION« beziehungsweise »PROCEDURE DIVISION« des Hauptprogramms übernommen werden. Die Übersichtlichkeit des Codes fiele dann entsprechend schlechter aus.

Aufgrund der geschilderten Nachteile sollten Sie vor allem bei Neuentwicklungen oder größeren Änderungen immer mit externen oder eingebetteten Unterprogrammen arbeiten. Der Autor räumt allerdings ein, dass es in anderen (auch älteren) Programmiersprachen deutlich leichter fällt, eine in sich abgeschlossene Prozedur zu deklarieren, in der sich lokale Variablen und Parameter nutzen lassen.

Listing 7

BOOKLIB1.COB (Auszug)

  [...]
  PROCEDURE DIVISION.
  MAIN-PROCEDURE.
    DISPLAY "BOOKLIB"
    CALL "READ-ALL-BOOKS" USING WS-BOOKS WS-BOOKS-COUNT.
    CALL "GET-ACTION" USING WS-ACTION. *> Aktion?
    PERFORM UNTIL WS-LOOP = 'N' *> Schleife bis Action 99
      EVALUATE WS-ACTION
        WHEN '1' *> Bestand anzeigen
          CALL 'LIST-BOOKS' USING WS-BOOKS WS-BOOKS-COUNT
        WHEN '2' *> Buch ausleihen
          CALL 'GET-BOOK' USING WS-BOOKS WS-BOOKS-COUNT
                                WS-BOOKS-MOD
        WHEN '3' *> Buch zurueckgeben
          CALL 'RETURN-BOOK' USING WS-BOOKS WS-BOOKS-COUNT
                                WS-BOOKS-MOD
        WHEN '99' *> Ende
          DISPLAY 'BOOKLIB wird beendet ' WS-BOOKS-MOD
          IF WS-BOOKS-MOD > 0 THEN *> Sobald Bestand geaendert
                                   *> wurde => schreiben
            CALL 'WRITE-ALL-BOOKS' USING WS-BOOKS WS-BOOKS-COUNT
          END-IF
  [...]

Keine Signaturen

Wie die Listings erkennen lassen, unterstützt COBOL auch die Übergabe von Parametern an Unterprogramme, sowohl in Form von Referenzen (by reference) als auch als Werte (by value). Allerdings gibt es in COBOL keine Möglichkeit, Signaturen beziehungsweise Entry-Deklarationen für externe oder eingebettete Unterprogramme zu definieren.

Der Compiler kann also einen Aufruf nicht auf korrekte Typen hin überprüfen. Fehler bei der Parameterübergabe machen sich daher zumeist erst zur Laufzeit bemerkbar, etwa in Form nicht übereinstimmender Angaben in der »PROCEDURE DIVISION« und der »LINKAGE SECTION« oder wenn nicht alle aufrufenden Programme Änderungen der Parameterliste nachvollziehen.

Fazit

COBOL bleibt selbst mehr als 60 Jahre nach seiner Entstehung vielfältig im Einsatz, und das wird sich auch auf absehbare Zeit nicht ändern. Aufgrund des Alters der Sprache eignen sich nicht alle seinerzeit verwirklichten Ansätze heute noch ideal für das Umsetzen von strukturiert respektive prozedural aufgebauten Anwendungen. Dennoch bietet COBOL dem Programmierer ausreichende Möglichkeiten, selbst wenn er für das Abbilden von Prozeduren auf etwas aufwendig zu deklarierende Unterprogramme zurückgreifen muss. Letztlich liegt es bei jeder Programmiersprache in den Händen des Softwareentwicklers, ansprechenden und vielleicht auch nach weiteren 60 Jahren noch wartbaren Code zu erstellen. (jcb/jlu)

Der Autor

Stefan Garschhammer arbeitet seit 1990 als Software Developer und Consultant, war aber auch schon als Team Lead in den Bereichen IT Security und Geldwäscheprävention tätig. Momentan ist er als Freelancer im Umfeld von IBM-z/OS-Mainframes tätig, unter anderem mit umfangreichen COBOL-Codestrecken. Er beantwortet gern Ihre Fragen.

Infos

  1. GnuCOBOL für Linux und Windows: https://gnucobol.sourceforge.io
  2. OpenCobolIDE: https://pypi.org/project/OpenCobolIDE
DIESEN ARTIKEL ALS PDF KAUFEN
EXPRESS-KAUF ALS PDFUmfang: 5 HeftseitenPreis €0,99
(inkl. 19% MwSt.)
LINUX-MAGAZIN KAUFEN
EINZELNE AUSGABE Print-Ausgaben Digitale Ausgaben
ABONNEMENTS Print-Abos Digitales Abo
TABLET & SMARTPHONE APPS Readly Logo
E-Mail Benachrichtigung
Benachrichtige mich zu:
0 Kommentare
Älteste
Neuste Beste Bewertung
Nach oben