88-Level Variables

Flags and switches use arbitrary values to encode logical information. The classic COBOL example indicates whether we have reached end-of-file:
 01  EOF-SW                        PIC X(01) VALUE 'N'.
     88  EOF                                 VALUE 'Y'.
With additional values we can encode further information about the state of the file:
 01  INPUT-STATUS-FLAG             PIC X(01) VALUE 'A'.  
     88  INPUT-FILE-UNOPENED                 VALUE 'A'.
     88  INPUT-FILE-UNREAD                   VALUE 'B'.
     88  INPUT-FILE-STARTED                  VALUE 'C'.
     88  INPUT-FILE-EOF                      VALUE 'D'.  
     88  INPUT-FILE-CLOSED                   VALUE 'E'.
By convention, a switch has two possible values, and a flag has more than two possible values.

Whenever possible, access flags and switches only through their 88-levels. The name of such a variable shouldn't even appear in the PROCEDURE DIVISION (except perhaps in a MOVE statement which copies one flag or switch to another).

88-Levels Raise the Level of Abstraction

The value of a flag or switch doesn't matter. What matters is its meaning. Use 88-levels to code in terms of meaning rather than the physical details of implementation.

Suppose you're reading somebody else's program and find:

[yuk] [Warning: bad code ahead]

     IF INPUT-STATUS-FLAG = 'C'
        [do something...]
Are you going to know what that means? What if the code had read:
     IF INPUT-FILE-STARTED
        [do something...]
Now isn't that nicer?

The same considerations apply when you assign values to a logical variable. MOVE 'B' TO INPUT-STATUS-FLAG doesn't tell you much, except for what you can guess from the context. SET INPUT-FILE-UNREAD TO TRUE is more informative.

88-Levels Protect You from Mistakes

Since the values of logical variables are arbitrary, different people use different values. A switch might be 'Y' or 'N', or it might be '1' and '0', or it might be 'T' and 'F'. It might even be 'YES' or 'NO '.

Now suppose it's 2:00 AM and you're trying to patch a miscreant program so that the cycle can finish. Oh, there it is -- somebody forgot to reset USOC-FOUND-SW. OK, throw in a quick MOVE 'N' TO USOC-FOUND-SW, compile, link, release, and go back to bed.

Oops. That should have been a '0', not a 'N'. Too bad you didn't code SET USOC-NOT-FOUND TO TRUE.

The Dark Side of 88-Levels

88-levels share a weakness with REDEFINES clauses: they use multiple names to refer to the same variable. These aliases invite confusion, because the reader may not recognize that a variable known by one name has been changed via another name.

Poor names increase the danger. Suppose our INPUT-STATUS-FLAG example had looked like this:

[yuk] [Warning: bad code ahead]

 01  INPUT-STATUS-FLAG             PIC X(01) VALUE 'A'.  
     88  NOT-OPENED-YET                      VALUE 'A'.
     88  JUST-OPENED                         VALUE 'B'.
     88  AT-LEAST-ONE-RECORD-READ            VALUE 'C'.
     88  NO-MORE-RECORDS                     VALUE 'D'.  
     88  INPUT-CLOSED                        VALUE 'E'.
These names are all meaningful, but they bear no obvious relation to each other. If the program sets AT-LEAST-ONE-RECORD-READ to TRUE, will you recognize that the result affects a later test for JUST-OPENED?

Choose your 88-level names carefully to minimize this danger. For flags, add nice long prefixes to the names, so that you can recognize them as members of the same family. For switches the names should be identical except for the addition of the word NOT to one of them.

Beware of THRU clauses

Be careful when using THRU with a VALUES clause. Consider the following example:

[yuk] [Warning: bad code ahead]

  01  ACCT-NR                  PIC X(04).
      88  ACCT-NR-RECEIVABLES            VALUES '1000' THRU '1999'.
      88  ACCT-NR-PAYABLES               VALUES '2000' THRU '2999'.
      88  ACCT-NR-EXPENSE                VALUES '3000' THRU '3999'.
      88  ACCT-NR-CAPITAL                VALUES '4000' THRU '4999'.
      88  ACCT-NR-VALID                  VALUES '1000' THRU '4999'.
For numeric account numbers, the first four 88-levels may work just fine. The last one, though, is unreliable. It will happily treat '32W@' as a valid expense account. At a minimum, you should rename that last 88-level to ACCT-NR-IN-RANGE, and supplement it with a numeric test.

Better yet, create a separate ACCT-TYPE-FLAG, and perhaps a separate ACCT-NR-VALID-SW. To an accountant, account numbers are meaningful values. They are not arbitrary codes which the programmer may assign at will.

Whenever you're tempted to use a THRU clause to define an 88-level, think again. If it is meaningful to specify a range, the variable on which the 88-level is based is probably not a flag with arbitrary values, but real data with meaningful values (see next section).

A THRU clause might be appropriate if you have chosen flag values in such a way that a range is meaningful. For our INPUT-STATUS-FLAG example, we might add:

     88  INPUT-FILE-OPEN                     VALUES 'B' THRU 'D'.

When Not to Use 88-Levels

It's a good idea to use 88-levels, but don't overdo it. They should be reserved for flags and switches -- logical variables whose values are arbitrary.

Consider the following fragment:

[yuk] [Warning: bad code ahead]

 01  BALANCE-DUE                PIC S9(5).99.
     88  ZERO-BALANCE                         VALUE ZERO.
The value of BALANCE-DUE is not arbitrary. It's real money. The fact that one possible value has special significance should not tempt you to use an 88-level for it. It's perfectly clear to code IF BALANCE-DUE = ZERO. Anything else is needlessly obscure.

Likewise, it would be misleading to code SET ZERO-BALANCE TO TRUE. It looks like you're just setting a flag, rather than changing a real value. Code exactly what you mean: MOVE ZERO TO BALANCE-DUE. No one will mistake your meaning.


[home]COBOL Home [style forum]COBOL Style Forum