> "The measure of a good program is not how cleverly it solves the problem, but how clearly it communicates the solution."
In This Chapter
- 6.1 The Anatomy of a COBOL Condition
- 6.2 Nested IF Statements — Depth Management
- 6.3 The EVALUATE Statement — COBOL's Decision Table
- 6.4 Condition Names (88-Level Items) — Boolean Abstractions
- 6.5 Complex Conditions — AND, OR, NOT
- 6.6 Class Conditions
- 6.7 Sign Conditions
- 6.8 Combining Condition Types: A Worked Example
- 6.9 GlobalBank Case Study: Transaction Routing Engine
- 6.9 MedClaim Case Study: Claim Adjudication Decision Tree
- 6.10 Common Pitfalls and Debugging Strategies
- 6.11 Performance Considerations
- 6.12 Conditional Logic in the Mainframe Ecosystem
- 6.13 Putting It All Together: Style Guide for Conditional Logic
- 6.14 Working Through a Complex Business Rule
- Chapter Summary
- Key Terms
Chapter 6: Advanced Conditional Logic
"The measure of a good program is not how cleverly it solves the problem, but how clearly it communicates the solution." — Gerald Weinberg
Every program makes decisions. A payroll system decides whether an employee is salaried or hourly. A banking application decides whether to approve or decline a transaction. An insurance processor decides which benefit schedule applies to a claim. The quality of your conditional logic — its correctness, its readability, its maintainability — determines the quality of your program.
In your first COBOL course, you learned the basics: the IF statement, simple conditions, and perhaps a taste of EVALUATE. In this chapter, we go far deeper. You will learn to write conditional logic that is not only correct but readable — logic that a maintenance programmer encountering your code five years from now will understand without needing to trace through it line by line.
This is where the theme Readability is a Feature becomes concrete. In the world of enterprise COBOL, where programs live for decades and are maintained by dozens of programmers over their lifetimes, the readability of your conditional logic is not a luxury. It is a survival strategy.
6.1 The Anatomy of a COBOL Condition
Before we dive into advanced patterns, let us establish precise terminology. In COBOL, a condition is an expression that evaluates to either true or false. The COBOL standard defines several categories of conditions:
| Condition Type | Example |
|---|---|
| Relation condition | IF WS-BALANCE > 0 |
| Class condition | IF WS-AMOUNT IS NUMERIC |
| Sign condition | IF WS-BALANCE IS POSITIVE |
| Condition-name condition | IF ACCT-IS-ACTIVE |
| Complex condition | IF WS-AGE > 18 AND WS-STATUS = 'A' |
| Negated condition | IF NOT ACCT-IS-CLOSED |
Every conditional statement in COBOL ultimately resolves to one of these types or a combination of them. Understanding the taxonomy helps you choose the right tool for each situation.
Relation Conditions
A relation condition compares two operands using a relational operator:
IF WS-ACCOUNT-BALANCE > WS-WITHDRAWAL-AMOUNT
PERFORM 2100-PROCESS-WITHDRAWAL
ELSE
PERFORM 2200-INSUFFICIENT-FUNDS
END-IF
COBOL provides the following relational operators:
| Symbol | Word Form | Meaning |
|---|---|---|
> |
IS GREATER THAN |
Greater than |
< |
IS LESS THAN |
Less than |
= |
IS EQUAL TO |
Equal to |
>= |
IS GREATER THAN OR EQUAL TO |
Greater than or equal |
<= |
IS LESS THAN OR EQUAL TO |
Less than or equal |
NOT = |
IS NOT EQUAL TO |
Not equal |
💡 Style Tip: Many shops mandate one form over the other. Some prefer symbols for brevity (>, <, =), while others prefer word forms for readability (IS GREATER THAN). The important thing is consistency within a project. At GlobalBank, Maria Chen's team uses symbols for simple comparisons and word forms when they make compound conditions clearer.
6.2 Nested IF Statements — Depth Management
Nested IF statements are one of the most common — and most dangerous — constructs in COBOL. They are common because business rules are naturally hierarchical: "If the account is active, and if the balance is sufficient, and if the transaction type is valid, then process the transaction." They are dangerous because deep nesting quickly becomes unreadable.
The Nesting Problem
Consider this code from GlobalBank's early transaction processing module, written in 1992:
IF WS-ACCT-STATUS = 'A'
IF WS-TXN-TYPE = 'W'
IF WS-BALANCE >= WS-TXN-AMOUNT
IF WS-DAILY-LIMIT >= WS-TXN-AMOUNT
IF WS-ACCT-HOLD = 'N'
PERFORM 3100-PROCESS-WITHDRAWAL
ELSE
MOVE 'ACCOUNT ON HOLD'
TO WS-ERROR-MSG
PERFORM 9100-LOG-ERROR
END-IF
ELSE
MOVE 'DAILY LIMIT EXCEEDED'
TO WS-ERROR-MSG
PERFORM 9100-LOG-ERROR
END-IF
ELSE
MOVE 'INSUFFICIENT FUNDS'
TO WS-ERROR-MSG
PERFORM 9100-LOG-ERROR
END-IF
ELSE
IF WS-TXN-TYPE = 'D'
PERFORM 3200-PROCESS-DEPOSIT
ELSE
IF WS-TXN-TYPE = 'T'
PERFORM 3300-PROCESS-TRANSFER
ELSE
MOVE 'INVALID TXN TYPE'
TO WS-ERROR-MSG
PERFORM 9100-LOG-ERROR
END-IF
END-IF
END-IF
ELSE
MOVE 'ACCOUNT NOT ACTIVE'
TO WS-ERROR-MSG
PERFORM 9100-LOG-ERROR
END-IF
This is five levels deep in places. When Derek Washington first encountered this code, he spent twenty minutes tracing which ELSE matched which IF. And this is a simple example — production code at GlobalBank sometimes nests eight or nine levels deep.
⚠️ Warning: Most coding standards limit nesting to three or four levels. Beyond that, the human brain struggles to maintain the mental stack of conditions. If you find yourself nesting deeper than three levels, it is time to refactor.
Strategy 1: Early Exit (Guard Clause Pattern)
The most effective way to reduce nesting is to check for error conditions first and exit early:
IF WS-ACCT-STATUS NOT = 'A'
MOVE 'ACCOUNT NOT ACTIVE' TO WS-ERROR-MSG
PERFORM 9100-LOG-ERROR
GO TO 2000-PROCESS-TXN-EXIT
END-IF
IF WS-TXN-TYPE = 'D'
PERFORM 3200-PROCESS-DEPOSIT
GO TO 2000-PROCESS-TXN-EXIT
END-IF
IF WS-TXN-TYPE = 'T'
PERFORM 3300-PROCESS-TRANSFER
GO TO 2000-PROCESS-TXN-EXIT
END-IF
IF WS-TXN-TYPE NOT = 'W'
MOVE 'INVALID TXN TYPE' TO WS-ERROR-MSG
PERFORM 9100-LOG-ERROR
GO TO 2000-PROCESS-TXN-EXIT
END-IF
* At this point we know: account is active, txn is withdrawal
IF WS-BALANCE < WS-TXN-AMOUNT
MOVE 'INSUFFICIENT FUNDS' TO WS-ERROR-MSG
PERFORM 9100-LOG-ERROR
GO TO 2000-PROCESS-TXN-EXIT
END-IF
IF WS-DAILY-LIMIT < WS-TXN-AMOUNT
MOVE 'DAILY LIMIT EXCEEDED' TO WS-ERROR-MSG
PERFORM 9100-LOG-ERROR
GO TO 2000-PROCESS-TXN-EXIT
END-IF
IF WS-ACCT-HOLD = 'Y'
MOVE 'ACCOUNT ON HOLD' TO WS-ERROR-MSG
PERFORM 9100-LOG-ERROR
GO TO 2000-PROCESS-TXN-EXIT
END-IF
PERFORM 3100-PROCESS-WITHDRAWAL
.
2000-PROCESS-TXN-EXIT.
EXIT.
⚖️ Debate: This pattern uses GO TO — which some shops ban entirely. We will explore this controversy in depth in Chapter 8. For now, notice that the guard clause pattern, even with GO TO, produces code that reads like a checklist: each condition is checked independently, each error is handled immediately, and the "happy path" (the withdrawal) only executes if all checks pass. Many experienced COBOL programmers consider this a legitimate, even preferred, use of GO TO.
Strategy 2: Condition Flags with Sequential IFs
If your shop prohibits GO TO, you can achieve a similar effect with a processing flag:
MOVE 'Y' TO WS-OK-TO-PROCESS
IF WS-ACCT-STATUS NOT = 'A'
MOVE 'ACCOUNT NOT ACTIVE' TO WS-ERROR-MSG
PERFORM 9100-LOG-ERROR
MOVE 'N' TO WS-OK-TO-PROCESS
END-IF
IF WS-OK-TO-PROCESS = 'Y'
AND WS-TXN-TYPE = 'W'
IF WS-BALANCE < WS-TXN-AMOUNT
MOVE 'INSUFFICIENT FUNDS' TO WS-ERROR-MSG
PERFORM 9100-LOG-ERROR
MOVE 'N' TO WS-OK-TO-PROCESS
END-IF
END-IF
IF WS-OK-TO-PROCESS = 'Y'
AND WS-TXN-TYPE = 'W'
IF WS-DAILY-LIMIT < WS-TXN-AMOUNT
MOVE 'DAILY LIMIT EXCEEDED' TO WS-ERROR-MSG
PERFORM 9100-LOG-ERROR
MOVE 'N' TO WS-OK-TO-PROCESS
END-IF
END-IF
IF WS-OK-TO-PROCESS = 'Y'
EVALUATE WS-TXN-TYPE
WHEN 'W' PERFORM 3100-PROCESS-WITHDRAWAL
WHEN 'D' PERFORM 3200-PROCESS-DEPOSIT
WHEN 'T' PERFORM 3300-PROCESS-TRANSFER
WHEN OTHER
MOVE 'INVALID TXN TYPE' TO WS-ERROR-MSG
PERFORM 9100-LOG-ERROR
END-EVALUATE
END-IF
This avoids GO TO at the cost of repeatedly checking WS-OK-TO-PROCESS. Some programmers find this cleaner; others find the repeated flag checks verbose. Both patterns are valid — what matters is that your team agrees on a standard.
Strategy 3: Extract to Paragraphs
The most universally accepted strategy is to move nested logic into well-named paragraphs:
2000-PROCESS-TRANSACTION.
PERFORM 2010-VALIDATE-ACCOUNT
IF WS-VALIDATION-OK = 'Y'
PERFORM 2020-ROUTE-TRANSACTION
END-IF
.
2010-VALIDATE-ACCOUNT.
MOVE 'Y' TO WS-VALIDATION-OK
IF WS-ACCT-STATUS NOT = 'A'
MOVE 'ACCOUNT NOT ACTIVE' TO WS-ERROR-MSG
MOVE 'N' TO WS-VALIDATION-OK
END-IF
IF WS-VALIDATION-OK = 'Y'
AND WS-ACCT-HOLD = 'Y'
MOVE 'ACCOUNT ON HOLD' TO WS-ERROR-MSG
MOVE 'N' TO WS-VALIDATION-OK
END-IF
.
2020-ROUTE-TRANSACTION.
EVALUATE WS-TXN-TYPE
WHEN 'W' PERFORM 2100-VALIDATE-AND-WITHDRAW
WHEN 'D' PERFORM 3200-PROCESS-DEPOSIT
WHEN 'T' PERFORM 3300-PROCESS-TRANSFER
WHEN OTHER
MOVE 'INVALID TXN TYPE' TO WS-ERROR-MSG
PERFORM 9100-LOG-ERROR
END-EVALUATE
.
This approach has the best of all worlds: no GO TO, minimal nesting, and each paragraph has a clear, single responsibility. We will explore this design philosophy extensively in Chapter 8.
6.3 The EVALUATE Statement — COBOL's Decision Table
The EVALUATE statement, introduced in COBOL-85, is one of the language's most powerful and underused constructs. If you come from other languages, you might think of EVALUATE as a "switch" or "case" statement. It is that, but it is also much more. EVALUATE is a full decision table — it can evaluate multiple subjects simultaneously and match against complex combinations of values.
Basic EVALUATE
The simplest form evaluates a single identifier against a list of values:
EVALUATE WS-TXN-TYPE
WHEN 'W'
PERFORM 3100-PROCESS-WITHDRAWAL
WHEN 'D'
PERFORM 3200-PROCESS-DEPOSIT
WHEN 'T'
PERFORM 3300-PROCESS-TRANSFER
WHEN 'I'
PERFORM 3400-PROCESS-INQUIRY
WHEN OTHER
MOVE 'INVALID TXN TYPE' TO WS-ERROR-MSG
PERFORM 9100-LOG-ERROR
END-EVALUATE
✅ Best Practice: Always include a WHEN OTHER clause. Even if you believe you have covered all possible values, a WHEN OTHER clause catches unexpected data and prevents your program from silently doing nothing — one of the most dangerous bugs in any system.
EVALUATE TRUE — The Power Pattern
EVALUATE TRUE is arguably the most powerful form. Instead of evaluating an identifier against values, it evaluates which condition is true:
EVALUATE TRUE
WHEN WS-BALANCE > 100000
MOVE 'PLATINUM' TO WS-ACCT-TIER
WHEN WS-BALANCE > 25000
MOVE 'GOLD' TO WS-ACCT-TIER
WHEN WS-BALANCE > 5000
MOVE 'SILVER' TO WS-ACCT-TIER
WHEN WS-BALANCE > 0
MOVE 'STANDARD' TO WS-ACCT-TIER
WHEN OTHER
MOVE 'OVERDRAWN' TO WS-ACCT-TIER
END-EVALUATE
⚠️ Critical: EVALUATE evaluates WHEN clauses in order, top to bottom, and executes only the first match. This matters enormously with EVALUATE TRUE. In the example above, a balance of $150,000 matches both > 100000 and > 25000, but only the PLATINUM assignment executes because it comes first. Always order your WHEN clauses from most specific to most general.
EVALUATE TRUE replaces deeply nested IF-ELSE chains. Consider this common MedClaim pattern for determining claim priority:
* BEFORE: Nested IF (hard to read)
IF CLM-TYPE = 'E'
MOVE 1 TO WS-PRIORITY
ELSE
IF CLM-AMOUNT > 50000
MOVE 2 TO WS-PRIORITY
ELSE
IF CLM-PROVIDER-TYPE = 'H'
MOVE 3 TO WS-PRIORITY
ELSE
MOVE 4 TO WS-PRIORITY
END-IF
END-IF
END-IF
* AFTER: EVALUATE TRUE (reads like a decision table)
EVALUATE TRUE
WHEN CLM-TYPE = 'E'
MOVE 1 TO WS-PRIORITY
WHEN CLM-AMOUNT > 50000
MOVE 2 TO WS-PRIORITY
WHEN CLM-PROVIDER-TYPE = 'H'
MOVE 3 TO WS-PRIORITY
WHEN OTHER
MOVE 4 TO WS-PRIORITY
END-EVALUATE
The EVALUATE version is flatter, each case is visually parallel, and the logic is immediately apparent. This is what we mean by Readability is a Feature.
EVALUATE with Multiple Subjects (ALSO)
The ALSO clause enables EVALUATE to act as a true two-dimensional (or multi-dimensional) decision table. This is where EVALUATE becomes uniquely powerful — no other mainstream language has a built-in construct this expressive.
EVALUATE WS-ACCT-TYPE ALSO WS-TXN-TYPE
WHEN 'C' ALSO 'W'
PERFORM 3110-CHECKING-WITHDRAWAL
WHEN 'C' ALSO 'D'
PERFORM 3120-CHECKING-DEPOSIT
WHEN 'S' ALSO 'W'
PERFORM 3210-SAVINGS-WITHDRAWAL
WHEN 'S' ALSO 'D'
PERFORM 3220-SAVINGS-DEPOSIT
WHEN 'M' ALSO ANY
PERFORM 3300-MONEY-MARKET-TXN
WHEN ANY ALSO 'I'
PERFORM 3400-INQUIRY
WHEN OTHER
MOVE 'INVALID ACCT/TXN COMBINATION'
TO WS-ERROR-MSG
PERFORM 9100-LOG-ERROR
END-EVALUATE
Notice the use of ANY as a wildcard. WHEN 'M' ALSO ANY means "money market account with any transaction type." WHEN ANY ALSO 'I' means "any account type with an inquiry transaction." This is a decision table brought to life in code.
📊 Decision Table Mapping: Here is the conceptual decision table that the EVALUATE above implements:
| W (Withdrawal) | D (Deposit) | I (Inquiry) | Other | |
|---|---|---|---|---|
| C (Checking) | 3110 | 3120 | 3400 | Error |
| S (Savings) | 3210 | 3220 | 3400 | Error |
| M (Money Mkt) | 3300 | 3300 | 3300* | Error |
| Other | Error | Error | Error | Error |
*Note: The Money Market row catches ALL transaction types via ANY, including Inquiry. But because WHEN 'M' ALSO ANY appears before WHEN ANY ALSO 'I' in the code, money market inquiries go to 3300 rather than 3400. Order matters!
EVALUATE TRUE ALSO TRUE
You can combine EVALUATE TRUE with ALSO to create multi-dimensional condition tables:
EVALUATE TRUE ALSO TRUE
WHEN WS-AGE < 18 ALSO WS-GUARDIAN = 'Y'
PERFORM 4100-MINOR-WITH-GUARDIAN
WHEN WS-AGE < 18 ALSO WS-GUARDIAN = 'N'
PERFORM 4200-MINOR-NO-GUARDIAN
WHEN WS-AGE >= 65 ALSO WS-RETIRED = 'Y'
PERFORM 4300-SENIOR-RETIRED
WHEN WS-AGE >= 65 ALSO WS-RETIRED = 'N'
PERFORM 4400-SENIOR-WORKING
WHEN OTHER
PERFORM 4500-STANDARD-ADULT
END-EVALUATE
This is essentially a two-dimensional truth table expressed directly in code. At GlobalBank, Maria Chen uses this pattern extensively for fee calculation logic where both account tier and transaction characteristics determine the fee schedule.
EVALUATE with Ranges and NOT
WHEN clauses can specify ranges and negations:
EVALUATE WS-CREDIT-SCORE
WHEN 750 THRU 850
MOVE 'EXCELLENT' TO WS-CREDIT-TIER
WHEN 700 THRU 749
MOVE 'GOOD' TO WS-CREDIT-TIER
WHEN 650 THRU 699
MOVE 'FAIR' TO WS-CREDIT-TIER
WHEN 300 THRU 649
MOVE 'POOR' TO WS-CREDIT-TIER
WHEN OTHER
MOVE 'INVALID SCORE' TO WS-ERROR-MSG
PERFORM 9100-LOG-ERROR
END-EVALUATE
The THRU keyword makes range checks clean and readable. Compare this with the equivalent nested IF:
* Equivalent nested IF — much harder to read
IF WS-CREDIT-SCORE >= 750 AND WS-CREDIT-SCORE <= 850
MOVE 'EXCELLENT' TO WS-CREDIT-TIER
ELSE IF WS-CREDIT-SCORE >= 700
MOVE 'GOOD' TO WS-CREDIT-TIER
ELSE IF WS-CREDIT-SCORE >= 650
MOVE 'FAIR' TO WS-CREDIT-TIER
ELSE IF WS-CREDIT-SCORE >= 300
MOVE 'POOR' TO WS-CREDIT-TIER
ELSE
MOVE 'INVALID SCORE' TO WS-ERROR-MSG
PERFORM 9100-LOG-ERROR
END-IF
The EVALUATE version is not only more readable — it is also safer, because each range is explicitly bounded. In the nested IF version, a score of 299 would fall through to the ELSE, which might be correct or might be a bug depending on the business rule.
🧪 Try It Yourself: EVALUATE Decision Table
Open your Student Mainframe Lab (or GnuCOBOL environment) and write a program that implements a shipping cost calculator using EVALUATE with ALSO:
- Subject 1:
WS-SHIPPING-ZONE(values: 'D' domestic, 'I' international) - Subject 2:
WS-PACKAGE-SIZE(values: 'S' small, 'M' medium, 'L' large) - Subject 3:
WS-PRIORITY(values: 'S' standard, 'E' express)
Create the EVALUATE statement that sets WS-SHIPPING-COST based on all three dimensions. Use at least one ANY wildcard. Include a WHEN OTHER for invalid combinations.
6.4 Condition Names (88-Level Items) — Boolean Abstractions
If there is one COBOL feature that, when mastered, will improve your code more than any other, it is the 88-level condition name. Condition names transform cryptic value comparisons into self-documenting boolean expressions. They are one of COBOL's most elegant features and one that many other languages still lack.
Basic Condition Names
An 88-level item is defined subordinate to a data item and associates a meaningful name with one or more values of that item:
01 WS-ACCOUNT-STATUS PIC X(01).
88 ACCT-IS-ACTIVE VALUE 'A'.
88 ACCT-IS-CLOSED VALUE 'C'.
88 ACCT-IS-SUSPENDED VALUE 'S'.
88 ACCT-IS-PENDING VALUE 'P'.
Now instead of writing:
IF WS-ACCOUNT-STATUS = 'A'
You write:
IF ACCT-IS-ACTIVE
This might seem like a minor improvement, but consider the impact at scale. A maintenance programmer reading IF ACCT-IS-ACTIVE understands the intent immediately. A programmer reading IF WS-ACCOUNT-STATUS = 'A' has to know (or look up) what 'A' means. When you have programs with hundreds of conditionals, this difference is enormous.
Multiple Values
An 88-level can represent multiple values:
01 WS-TXN-TYPE PIC X(02).
88 TXN-IS-DEBIT VALUE 'WD' 'PY' 'FE'.
88 TXN-IS-CREDIT VALUE 'DP' 'CR' 'RF'.
88 TXN-IS-INQUIRY VALUE 'IQ' 'BQ' 'SQ'.
88 TXN-IS-VALID VALUE 'WD' 'PY' 'FE'
'DP' 'CR' 'RF'
'IQ' 'BQ' 'SQ'.
Now IF TXN-IS-DEBIT is true if the transaction type is 'WD' (withdrawal), 'PY' (payment), or 'FE' (fee). This is vastly more readable than:
IF WS-TXN-TYPE = 'WD' OR 'PY' OR 'FE'
THRU (Range Values)
Condition names can also specify ranges:
01 WS-CREDIT-SCORE PIC 9(03).
88 CREDIT-EXCELLENT VALUE 750 THRU 850.
88 CREDIT-GOOD VALUE 700 THRU 749.
88 CREDIT-FAIR VALUE 650 THRU 699.
88 CREDIT-POOR VALUE 300 THRU 649.
88 CREDIT-VALID VALUE 300 THRU 850.
The EVALUATE from the previous section becomes:
EVALUATE TRUE
WHEN CREDIT-EXCELLENT
MOVE 'EXCELLENT' TO WS-CREDIT-TIER
WHEN CREDIT-GOOD
MOVE 'GOOD' TO WS-CREDIT-TIER
WHEN CREDIT-FAIR
MOVE 'FAIR' TO WS-CREDIT-TIER
WHEN CREDIT-POOR
MOVE 'POOR' TO WS-CREDIT-TIER
WHEN OTHER
MOVE 'INVALID SCORE' TO WS-ERROR-MSG
PERFORM 9100-LOG-ERROR
END-EVALUATE
This is the gold standard of readable conditional logic: EVALUATE TRUE combined with well-named 88-level conditions. The code reads almost like English.
The SET Statement
You can set a condition name to true using the SET statement:
SET ACCT-IS-ACTIVE TO TRUE
This is equivalent to MOVE 'A' TO WS-ACCOUNT-STATUS, but it is self-documenting and, crucially, it insulates your logic from the underlying representation. If the value for "active" ever changes from 'A' to '1', you only change it in the 88-level definition — every SET statement and every IF statement using the condition name continues to work without modification.
💡 Design Insight: This is the principle of abstraction applied to data values. The 88-level condition name creates a layer of indirection between the logical concept ("the account is active") and the physical representation (the character 'A' in a one-byte field). This indirection is not overhead — it is insurance.
FALSE Values (COBOL 2002+)
In COBOL 2002 and later, you can specify a FALSE value for an 88-level:
01 WS-PROCESSING-FLAG PIC X(01).
88 IS-PROCESSING VALUE 'Y'
FALSE IS 'N'.
This allows:
SET IS-PROCESSING TO TRUE *> Sets to 'Y'
SET IS-PROCESSING TO FALSE *> Sets to 'N'
⚠️ Compatibility Note: The FALSE clause is a COBOL 2002 feature. If your shop uses an older compiler standard, you may not have this available. Check your compiler documentation.
Condition Name Hierarchies
You can create hierarchies of condition names for different levels of specificity:
01 WS-CLAIM-STATUS PIC X(02).
88 CLM-NEW VALUE 'NW'.
88 CLM-IN-REVIEW VALUE 'IR'.
88 CLM-PENDING-INFO VALUE 'PI'.
88 CLM-APPROVED VALUE 'AP'.
88 CLM-DENIED VALUE 'DN'.
88 CLM-APPEALED VALUE 'AL'.
88 CLM-PAID VALUE 'PD'.
* Group conditions
88 CLM-IS-OPEN VALUE 'NW' 'IR' 'PI' 'AL'.
88 CLM-IS-CLOSED VALUE 'AP' 'DN' 'PD'.
88 CLM-NEEDS-ACTION VALUE 'NW' 'PI' 'AL'.
88 CLM-IS-TERMINAL VALUE 'DN' 'PD'.
At MedClaim, James Okafor's team uses this pattern extensively. IF CLM-IS-OPEN is far more maintainable than listing out all four status codes every time you need to check whether a claim is still active. And when a new status code is added (say, 'ES' for "escalated"), you update the 88-level definitions in one place — every conditional that uses the group condition automatically includes the new status.
📊 Real-World Impact: Sarah Kim, MedClaim's business analyst, estimates that their migration from raw value comparisons to 88-level condition names reduced claims-processing bugs by roughly 40%. The most common bug type — forgetting to include a new status code in one of many IF statements scattered through the program — was virtually eliminated.
🧪 Try It Yourself: 88-Level Refactoring
Take this poorly written conditional block and refactor it using 88-level condition names:
IF WS-EMP-TYPE = 'F' OR WS-EMP-TYPE = 'P'
IF WS-DEPT-CODE = 'IT' OR WS-DEPT-CODE = 'EN'
OR WS-DEPT-CODE = 'SC'
IF WS-YRS-SERVICE > 5
MOVE 'Y' TO WS-ELIGIBLE
END-IF
END-IF
END-IF
Define 88-levels for employee types (full-time, part-time, contractor), department groups (technical, administrative), and service thresholds. Rewrite the conditional using your 88-level names combined with EVALUATE TRUE.
6.5 Complex Conditions — AND, OR, NOT
Real business rules rarely involve single conditions. COBOL provides the standard boolean operators — AND, OR, and NOT — for combining conditions.
Operator Precedence
COBOL evaluates complex conditions in this order:
- NOT (highest precedence — evaluated first)
- AND
- OR (lowest precedence — evaluated last)
This means:
IF A OR B AND C
is evaluated as:
IF A OR (B AND C)
Not as (A OR B) AND C. This trips up many programmers, especially those coming from languages where the precedence might differ or where parentheses are used more habitually.
⚠️ Warning: Always use parentheses to make your intent explicit. Even if you know the precedence rules perfectly, the next programmer to read your code may not. Write:
IF (A OR B) AND C
or:
IF A OR (B AND C)
Never rely on implicit precedence in production code.
Implied Subjects and Relational Operators
COBOL allows you to abbreviate compound conditions using implied subjects:
* Full form
IF WS-STATE = 'NY' OR WS-STATE = 'NJ' OR WS-STATE = 'CT'
* Abbreviated form (implied subject)
IF WS-STATE = 'NY' OR 'NJ' OR 'CT'
Both forms are equivalent. The implied subject carries the last explicitly stated subject (WS-STATE) and the last relational operator (=) forward. This also works with other operators:
* Full form
IF WS-AMOUNT > 100 AND WS-AMOUNT < 500
* Abbreviated form (implied subject AND operator)
IF WS-AMOUNT > 100 AND < 500
💡 Style Debate: Implied subjects are controversial. Some shops love them for brevity. Others ban them because they can be confusing in complex expressions. Consider:
IF WS-STATUS = 'A' OR 'B' AND WS-TYPE = 'X'
Is this (STATUS = 'A' OR 'B') AND TYPE = 'X'? Or is it STATUS = 'A' OR (STATUS = 'B' AND TYPE = 'X')? The answer is the second interpretation, because AND has higher precedence. But many programmers would read it as the first. Use parentheses!
Negating Complex Conditions
The NOT operator can negate an entire condition:
IF NOT (WS-STATUS = 'A' AND WS-TYPE = 'C')
By De Morgan's law, this is equivalent to:
IF WS-STATUS NOT = 'A' OR WS-TYPE NOT = 'C'
Both are correct, but the first is generally clearer because it expresses the intent as a single negated compound condition rather than two separate conditions linked by OR.
Condition Names in Complex Conditions
This is where 88-levels truly shine in complex conditions:
* Without 88-levels (hard to read)
IF (WS-ACCT-STATUS = 'A' OR WS-ACCT-STATUS = 'P')
AND (WS-TXN-TYPE = 'WD' OR WS-TXN-TYPE = 'PY'
OR WS-TXN-TYPE = 'FE')
AND WS-BALANCE >= WS-TXN-AMOUNT
* With 88-levels (reads like a sentence)
IF (ACCT-IS-ACTIVE OR ACCT-IS-PENDING)
AND TXN-IS-DEBIT
AND WS-BALANCE >= WS-TXN-AMOUNT
The second version is immediately comprehensible. This is the coding style that Maria Chen insists on at GlobalBank — conditions that read like business rules, not like hex dumps.
6.6 Class Conditions
Class conditions test whether a data item's contents belong to a particular category:
IF WS-AMOUNT IS NUMERIC
COMPUTE WS-TAX = WS-AMOUNT * WS-TAX-RATE
ELSE
MOVE 'INVALID AMOUNT' TO WS-ERROR-MSG
PERFORM 9100-LOG-ERROR
END-IF
Built-in Class Conditions
| Condition | True When |
|---|---|
IS NUMERIC |
Contains only digits (and optional sign for signed fields) |
IS ALPHABETIC |
Contains only letters A-Z, a-z, and spaces |
IS ALPHABETIC-LOWER |
Contains only lowercase letters and spaces |
IS ALPHABETIC-UPPER |
Contains only uppercase letters and spaces |
IS NOT NUMERIC |
Fails the NUMERIC test |
⚠️ Critical Gotcha: The NUMERIC test behaves differently depending on the data item's PICTURE. For a PIC X field, IS NUMERIC tests whether all characters are digits 0-9. For a PIC S9 signed numeric field, the test also allows the sign character. For a PIC 9 unsigned numeric field, only digits pass. Always know the PICTURE of the field you are testing.
Defensive Programming with Class Conditions
At GlobalBank, incoming transaction data arrives from multiple sources — ATMs, mobile apps, branch terminals, and batch feeds from partner institutions. Not all sources validate data before sending it. This makes class conditions essential for defensive programming:
2100-VALIDATE-TXN-AMOUNT.
IF WS-TXN-AMOUNT-X IS NOT NUMERIC
MOVE 'NON-NUMERIC AMOUNT' TO WS-ERROR-MSG
MOVE 'E' TO WS-ERROR-SEVERITY
PERFORM 9100-LOG-ERROR
MOVE ZERO TO WS-TXN-AMOUNT
ELSE
MOVE WS-TXN-AMOUNT-X TO WS-TXN-AMOUNT
END-IF
.
Note the pattern: test the alphanumeric version of the field (WS-TXN-AMOUNT-X, PIC X) for NUMERIC before moving it to the numeric field (WS-TXN-AMOUNT, PIC 9). Attempting to use a non-numeric value in arithmetic produces an ABEND (S0C7 on IBM mainframes) — the most common production abend in COBOL systems.
🔴 Production War Story: James Okafor at MedClaim recalls a midnight page in 2019: the nightly claims batch had abended with an S0C7. The culprit? A provider had submitted a claim with the letter 'O' (capital letter O) instead of the digit '0' (zero) in the amount field. A single missing NUMERIC check cost the team four hours of emergency remediation and delayed payment processing for 12,000 claims. The fix was one line: IF CLM-AMOUNT-X IS NUMERIC. The lesson: always validate before you calculate.
User-Defined Class Conditions
COBOL allows you to define custom class conditions in the SPECIAL-NAMES paragraph:
ENVIRONMENT DIVISION.
CONFIGURATION SECTION.
SPECIAL-NAMES.
CLASS VALID-ACCT-CHAR IS
'A' THRU 'Z'
'0' THRU '9'
'-'.
Then in the PROCEDURE DIVISION:
IF WS-ACCOUNT-ID IS VALID-ACCT-CHAR
PERFORM 2100-LOOKUP-ACCOUNT
ELSE
MOVE 'INVALID CHARS IN ACCOUNT ID'
TO WS-ERROR-MSG
PERFORM 9100-LOG-ERROR
END-IF
This is particularly useful for validating input data against specific allowed character sets — account numbers that must be alphanumeric with hyphens, names that must be alphabetic with spaces and hyphens, etc.
6.7 Sign Conditions
Sign conditions test whether a numeric value is positive, negative, or zero:
IF WS-BALANCE IS POSITIVE
PERFORM 3100-NORMAL-PROCESSING
END-IF
IF WS-BALANCE IS NEGATIVE
PERFORM 3200-OVERDRAFT-PROCESSING
END-IF
IF WS-BALANCE IS ZERO
PERFORM 3300-ZERO-BALANCE-PROCESSING
END-IF
These are equivalent to comparing against zero:
IF WS-BALANCE > 0 ... *> Same as IS POSITIVE
IF WS-BALANCE < 0 ... *> Same as IS NEGATIVE
IF WS-BALANCE = 0 ... *> Same as IS ZERO
The sign condition forms are preferred for readability. IF WS-BALANCE IS NEGATIVE communicates intent more clearly than IF WS-BALANCE < 0.
💡 Note: IS POSITIVE means strictly greater than zero. It does not include zero. Similarly, IS NEGATIVE means strictly less than zero. Zero is its own sign condition: IS ZERO. This is worth remembering, because in everyday speech we might say a zero balance is "not negative," but in COBOL, NOT NEGATIVE includes both positive values and zero.
6.8 Combining Condition Types: A Worked Example
Let us work through a comprehensive example that combines every condition type discussed so far. This example processes insurance premium calculations at MedClaim, where the premium depends on the member's age, plan type, smoking status, and geographic region.
Step 1: Define the Data with 88-Levels
01 WS-MEMBER-AGE PIC 9(03).
88 AGE-CHILD VALUE 0 THRU 17.
88 AGE-YOUNG-ADULT VALUE 18 THRU 29.
88 AGE-ADULT VALUE 30 THRU 49.
88 AGE-MATURE VALUE 50 THRU 64.
88 AGE-SENIOR VALUE 65 THRU 120.
88 AGE-VALID VALUE 0 THRU 120.
01 WS-PLAN-TYPE PIC X(03).
88 PLAN-BRONZE VALUE 'BRZ'.
88 PLAN-SILVER VALUE 'SLV'.
88 PLAN-GOLD VALUE 'GLD'.
88 PLAN-PLATINUM VALUE 'PLT'.
88 PLAN-IS-VALID VALUE 'BRZ' 'SLV'
'GLD' 'PLT'.
88 PLAN-IS-PREMIUM VALUE 'GLD' 'PLT'.
01 WS-SMOKING-STATUS PIC X(01).
88 IS-SMOKER VALUE 'Y'.
88 IS-NON-SMOKER VALUE 'N'.
88 SMOKING-STATUS-VALID VALUE 'Y' 'N'.
01 WS-REGION-CODE PIC X(02).
88 REGION-NORTHEAST VALUE 'NE'.
88 REGION-SOUTHEAST VALUE 'SE'.
88 REGION-MIDWEST VALUE 'MW'.
88 REGION-WEST VALUE 'WS'.
88 REGION-IS-VALID VALUE 'NE' 'SE' 'MW' 'WS'.
88 REGION-HIGH-COST VALUE 'NE' 'WS'.
01 WS-BASE-PREMIUM PIC S9(07)V99.
01 WS-FINAL-PREMIUM PIC S9(07)V99.
01 WS-AGE-FACTOR PIC 9V999.
01 WS-SMOKING-FACTOR PIC 9V999.
01 WS-REGION-FACTOR PIC 9V999.
Step 2: Validate All Inputs with Class and Condition Checks
3000-VALIDATE-PREMIUM-INPUTS.
SET INPUTS-ARE-VALID TO TRUE
* Validate age is numeric and in range
IF WS-MEMBER-AGE-X IS NOT NUMERIC
SET INPUTS-ARE-INVALID TO TRUE
MOVE 'AGE IS NOT NUMERIC' TO WS-ERROR-MSG
PERFORM 9100-LOG-ERROR
ELSE
IF NOT AGE-VALID
SET INPUTS-ARE-INVALID TO TRUE
MOVE 'AGE OUT OF RANGE' TO WS-ERROR-MSG
PERFORM 9100-LOG-ERROR
END-IF
END-IF
* Validate plan type
IF INPUTS-ARE-VALID
IF NOT PLAN-IS-VALID
SET INPUTS-ARE-INVALID TO TRUE
MOVE 'INVALID PLAN TYPE' TO WS-ERROR-MSG
PERFORM 9100-LOG-ERROR
END-IF
END-IF
* Validate smoking status
IF INPUTS-ARE-VALID
IF NOT SMOKING-STATUS-VALID
SET INPUTS-ARE-INVALID TO TRUE
MOVE 'INVALID SMOKING STATUS'
TO WS-ERROR-MSG
PERFORM 9100-LOG-ERROR
END-IF
END-IF
* Validate region
IF INPUTS-ARE-VALID
IF NOT REGION-IS-VALID
SET INPUTS-ARE-INVALID TO TRUE
MOVE 'INVALID REGION CODE' TO WS-ERROR-MSG
PERFORM 9100-LOG-ERROR
END-IF
END-IF
.
Step 3: Calculate Premium Using EVALUATE
3100-CALCULATE-PREMIUM.
* Step 3a: Determine base premium by plan type
EVALUATE TRUE
WHEN PLAN-BRONZE
MOVE 250.00 TO WS-BASE-PREMIUM
WHEN PLAN-SILVER
MOVE 450.00 TO WS-BASE-PREMIUM
WHEN PLAN-GOLD
MOVE 650.00 TO WS-BASE-PREMIUM
WHEN PLAN-PLATINUM
MOVE 850.00 TO WS-BASE-PREMIUM
WHEN OTHER
MOVE 0 TO WS-BASE-PREMIUM
PERFORM 9100-LOG-ERROR
END-EVALUATE
* Step 3b: Apply age factor
EVALUATE TRUE
WHEN AGE-CHILD
MOVE 0.750 TO WS-AGE-FACTOR
WHEN AGE-YOUNG-ADULT
MOVE 1.000 TO WS-AGE-FACTOR
WHEN AGE-ADULT
MOVE 1.200 TO WS-AGE-FACTOR
WHEN AGE-MATURE
MOVE 1.500 TO WS-AGE-FACTOR
WHEN AGE-SENIOR
MOVE 1.800 TO WS-AGE-FACTOR
WHEN OTHER
MOVE 1.000 TO WS-AGE-FACTOR
END-EVALUATE
* Step 3c: Apply smoking surcharge
IF IS-SMOKER
MOVE 1.500 TO WS-SMOKING-FACTOR
ELSE
MOVE 1.000 TO WS-SMOKING-FACTOR
END-IF
* Step 3d: Apply regional cost adjustment
IF REGION-HIGH-COST
MOVE 1.150 TO WS-REGION-FACTOR
ELSE
MOVE 1.000 TO WS-REGION-FACTOR
END-IF
* Step 3e: Calculate final premium
COMPUTE WS-FINAL-PREMIUM =
WS-BASE-PREMIUM
* WS-AGE-FACTOR
* WS-SMOKING-FACTOR
* WS-REGION-FACTOR
.
Notice how each step uses the appropriate condition type: 88-level conditions for plan type and age range, simple condition names for boolean flags (IS-SMOKER), and group condition names for cost categories (REGION-HIGH-COST). The code reads like a specification document: "If the plan is Bronze, the base premium is $250. If the member is a senior, the age factor is 1.8. If they are in a high-cost region, apply a 15% regional adjustment."
Step 4: Business Rule Summary Using Combined Conditions
After calculation, we might need to apply special business rules that combine multiple conditions:
3200-APPLY-SPECIAL-RULES.
* Rule 1: Seniors on premium plans get a 5% loyalty discount
IF AGE-SENIOR AND PLAN-IS-PREMIUM
COMPUTE WS-FINAL-PREMIUM =
WS-FINAL-PREMIUM * 0.95
END-IF
* Rule 2: Young adult non-smokers get a wellness discount
IF AGE-YOUNG-ADULT AND IS-NON-SMOKER
COMPUTE WS-FINAL-PREMIUM =
WS-FINAL-PREMIUM * 0.97
END-IF
* Rule 3: Cap premium at $3,000/month regardless
IF WS-FINAL-PREMIUM > 3000.00
MOVE 3000.00 TO WS-FINAL-PREMIUM
END-IF
.
This example shows the full lifecycle of conditional logic: validate inputs (class conditions + 88-levels), calculate using branching logic (EVALUATE TRUE), and apply business rules using combined conditions (AND/OR with 88-levels). Each step is in its own paragraph with a single responsibility. The data definitions with their 88-levels serve as the vocabulary of the business domain.
🧪 Try It Yourself: Premium Calculator
Build the complete premium calculator program in your Student Mainframe Lab. Add these extensions:
- Accept member data from the keyboard (ACCEPT statement)
- Display a formatted premium quote showing each factor
- Add a family plan option that multiplies the premium by the number of dependents
- Add an annual vs. monthly display option
6.9 GlobalBank Case Study: Transaction Routing Engine
Let us put everything together with a substantial real-world example. At GlobalBank, the transaction routing engine determines how to process each incoming transaction based on multiple factors: account type, transaction type, amount thresholds, customer tier, and regulatory flags.
Here is the DATA DIVISION setup:
01 WS-ACCOUNT-TYPE PIC X(01).
88 ACCT-CHECKING VALUE 'C'.
88 ACCT-SAVINGS VALUE 'S'.
88 ACCT-MONEY-MARKET VALUE 'M'.
88 ACCT-CD VALUE 'D'.
88 ACCT-ALLOWS-WITHDRAWAL
VALUE 'C' 'S' 'M'.
88 ACCT-EARNS-INTEREST VALUE 'S' 'M' 'D'.
01 WS-TXN-TYPE PIC X(02).
88 TXN-WITHDRAWAL VALUE 'WD'.
88 TXN-DEPOSIT VALUE 'DP'.
88 TXN-TRANSFER VALUE 'TF'.
88 TXN-PAYMENT VALUE 'PY'.
88 TXN-INQUIRY VALUE 'IQ'.
88 TXN-CHANGES-BALANCE VALUE 'WD' 'DP' 'TF' 'PY'.
88 TXN-IS-VALID VALUE 'WD' 'DP' 'TF'
'PY' 'IQ'.
01 WS-CUSTOMER-TIER PIC X(01).
88 CUST-PLATINUM VALUE 'P'.
88 CUST-GOLD VALUE 'G'.
88 CUST-SILVER VALUE 'S'.
88 CUST-STANDARD VALUE 'R'.
88 CUST-PREMIUM VALUE 'P' 'G'.
01 WS-TXN-AMOUNT PIC S9(11)V99.
01 WS-BALANCE PIC S9(11)V99.
01 WS-DAILY-WD-TOTAL PIC S9(11)V99.
01 WS-DAILY-WD-LIMIT PIC S9(11)V99.
01 WS-REGULATORY-FLAGS.
05 WS-CTR-REQUIRED PIC X(01).
88 CTR-REQUIRED VALUE 'Y'.
88 CTR-NOT-REQUIRED VALUE 'N'.
05 WS-SAR-FLAG PIC X(01).
88 SAR-FLAGGED VALUE 'Y'.
88 SAR-NOT-FLAGGED VALUE 'N'.
05 WS-OFAC-FLAG PIC X(01).
88 OFAC-HIT VALUE 'Y'.
88 OFAC-CLEAR VALUE 'N'.
And here is the routing logic in the PROCEDURE DIVISION:
2000-ROUTE-TRANSACTION.
* -----------------------------------------------
* Step 1: Regulatory pre-checks (always first)
* -----------------------------------------------
PERFORM 2010-CHECK-REGULATORY
IF OFAC-HIT
PERFORM 9200-BLOCK-TRANSACTION
GO TO 2000-ROUTE-TXN-EXIT
END-IF
* -----------------------------------------------
* Step 2: Validate basic transaction data
* -----------------------------------------------
IF NOT TXN-IS-VALID
MOVE 'INVALID TRANSACTION TYPE'
TO WS-ERROR-MSG
PERFORM 9100-LOG-ERROR
GO TO 2000-ROUTE-TXN-EXIT
END-IF
* -----------------------------------------------
* Step 3: Route by transaction type
* -----------------------------------------------
EVALUATE TRUE
WHEN TXN-INQUIRY
PERFORM 3400-PROCESS-INQUIRY
WHEN TXN-DEPOSIT
PERFORM 3200-PROCESS-DEPOSIT
WHEN TXN-WITHDRAWAL
PERFORM 2100-VALIDATE-AND-WITHDRAW
WHEN TXN-TRANSFER
PERFORM 2200-VALIDATE-AND-TRANSFER
WHEN TXN-PAYMENT
PERFORM 2300-VALIDATE-AND-PAY
END-EVALUATE
* -----------------------------------------------
* Step 4: Post-transaction regulatory reporting
* -----------------------------------------------
IF TXN-CHANGES-BALANCE
PERFORM 2400-CHECK-CTR-THRESHOLD
IF SAR-FLAGGED
PERFORM 9300-FILE-SAR-REPORT
END-IF
END-IF
.
2000-ROUTE-TXN-EXIT.
EXIT.
2100-VALIDATE-AND-WITHDRAW.
IF NOT ACCT-ALLOWS-WITHDRAWAL
MOVE 'WITHDRAWAL NOT ALLOWED FOR THIS ACCT'
TO WS-ERROR-MSG
PERFORM 9100-LOG-ERROR
ELSE
EVALUATE TRUE ALSO TRUE
WHEN WS-BALANCE < WS-TXN-AMOUNT
ALSO ANY
MOVE 'INSUFFICIENT FUNDS'
TO WS-ERROR-MSG
PERFORM 9100-LOG-ERROR
WHEN ANY
ALSO WS-DAILY-WD-TOTAL +
WS-TXN-AMOUNT >
WS-DAILY-WD-LIMIT
EVALUATE TRUE
WHEN CUST-PREMIUM
PERFORM 3150-PREMIUM-OVERRIDE
WHEN OTHER
MOVE 'DAILY LIMIT EXCEEDED'
TO WS-ERROR-MSG
PERFORM 9100-LOG-ERROR
END-EVALUATE
WHEN OTHER
PERFORM 3100-PROCESS-WITHDRAWAL
END-EVALUATE
END-IF
.
🔗 Cross-Reference: Notice how the regulatory checks (OFAC, CTR, SAR) are handled with condition names. We will see these regulatory patterns again in Chapter 29 when we cover compliance reporting, and in Chapter 35 when we discuss testing financial applications.
This code demonstrates several patterns from this chapter working together:
- Guard clauses for regulatory blocks (OFAC hit exits immediately)
- 88-level condition names for every status check (no raw value comparisons)
- Group condition names (
TXN-CHANGES-BALANCE,ACCT-ALLOWS-WITHDRAWAL) for logical groupings - EVALUATE TRUE for clean routing based on conditions
- EVALUATE TRUE ALSO TRUE for multi-dimensional validation
- Nested EVALUATE where appropriate (premium customer override within daily limit check)
6.9 MedClaim Case Study: Claim Adjudication Decision Tree
At MedClaim, claim adjudication is the most complex decision logic in the entire system. When a medical claim arrives, the adjudication engine must determine: Is the member eligible? Is the provider in-network? Does the service require prior authorization? What is the member's copay? What is the coinsurance? Is the deductible met? What is the plan maximum?
Sarah Kim spent three months documenting the adjudication decision tree, and it fills seventeen pages. Here is a simplified version that demonstrates the conditional logic patterns:
01 WS-CLAIM-DECISION.
05 WS-ADJ-STATUS PIC X(02).
88 ADJ-APPROVED VALUE 'AP'.
88 ADJ-DENIED VALUE 'DN'.
88 ADJ-PENDED VALUE 'PD'.
88 ADJ-REVIEW VALUE 'RV'.
05 WS-DENY-REASON PIC X(04).
88 DENY-INELIGIBLE VALUE 'D001'.
88 DENY-NOT-COVERED VALUE 'D002'.
88 DENY-NO-AUTH VALUE 'D003'.
88 DENY-MAX-REACHED VALUE 'D004'.
88 DENY-DUPLICATE VALUE 'D005'.
05 WS-PEND-REASON PIC X(04).
88 PEND-NEED-AUTH VALUE 'P001'.
88 PEND-NEED-RECORDS VALUE 'P002'.
88 PEND-HIGH-DOLLAR VALUE 'P003'.
01 WS-MEMBER-INFO.
05 WS-MEMBER-STATUS PIC X(01).
88 MEMBER-ACTIVE VALUE 'A'.
88 MEMBER-COBRA VALUE 'C'.
88 MEMBER-TERMED VALUE 'T'.
88 MEMBER-ELIGIBLE VALUE 'A' 'C'.
05 WS-PLAN-TYPE PIC X(03).
88 PLAN-HMO VALUE 'HMO'.
88 PLAN-PPO VALUE 'PPO'.
88 PLAN-POS VALUE 'POS'.
88 PLAN-REQUIRES-REFERRAL
VALUE 'HMO' 'POS'.
01 WS-SERVICE-INFO.
05 WS-SERVICE-CODE PIC X(05).
05 WS-AUTH-REQUIRED PIC X(01).
88 AUTH-REQUIRED VALUE 'Y'.
88 AUTH-NOT-REQUIRED VALUE 'N'.
05 WS-AUTH-ON-FILE PIC X(01).
88 AUTH-ON-FILE VALUE 'Y'.
88 AUTH-NOT-ON-FILE VALUE 'N'.
05 WS-PROVIDER-STATUS PIC X(01).
88 PROVIDER-IN-NET VALUE 'I'.
88 PROVIDER-OUT-NET VALUE 'O'.
88 PROVIDER-TERMED VALUE 'T'.
88 PROVIDER-VALID VALUE 'I' 'O'.
The adjudication procedure:
3000-ADJUDICATE-CLAIM.
* -----------------------------------------------
* Phase 1: Eligibility check
* -----------------------------------------------
IF NOT MEMBER-ELIGIBLE
SET ADJ-DENIED TO TRUE
SET DENY-INELIGIBLE TO TRUE
PERFORM 9500-LOG-DENIAL
GO TO 3000-ADJUDICATE-EXIT
END-IF
* -----------------------------------------------
* Phase 2: Duplicate check
* -----------------------------------------------
PERFORM 3010-CHECK-DUPLICATE
IF WS-IS-DUPLICATE = 'Y'
SET ADJ-DENIED TO TRUE
SET DENY-DUPLICATE TO TRUE
PERFORM 9500-LOG-DENIAL
GO TO 3000-ADJUDICATE-EXIT
END-IF
* -----------------------------------------------
* Phase 3: Authorization check
* -----------------------------------------------
EVALUATE TRUE
WHEN AUTH-REQUIRED AND AUTH-ON-FILE
CONTINUE
WHEN AUTH-REQUIRED AND AUTH-NOT-ON-FILE
EVALUATE TRUE
WHEN PLAN-HMO
SET ADJ-DENIED TO TRUE
SET DENY-NO-AUTH TO TRUE
PERFORM 9500-LOG-DENIAL
GO TO 3000-ADJUDICATE-EXIT
WHEN PLAN-PPO OR PLAN-POS
SET ADJ-PENDED TO TRUE
SET PEND-NEED-AUTH TO TRUE
GO TO 3000-ADJUDICATE-EXIT
END-EVALUATE
WHEN AUTH-NOT-REQUIRED
CONTINUE
END-EVALUATE
* -----------------------------------------------
* Phase 4: Network status and pricing
* -----------------------------------------------
EVALUATE TRUE ALSO TRUE
WHEN PROVIDER-IN-NET ALSO PLAN-HMO
PERFORM 3100-PRICE-HMO-IN-NET
WHEN PROVIDER-IN-NET ALSO PLAN-PPO
PERFORM 3200-PRICE-PPO-IN-NET
WHEN PROVIDER-IN-NET ALSO PLAN-POS
PERFORM 3300-PRICE-POS-IN-NET
WHEN PROVIDER-OUT-NET ALSO PLAN-HMO
SET ADJ-DENIED TO TRUE
SET DENY-NOT-COVERED TO TRUE
PERFORM 9500-LOG-DENIAL
GO TO 3000-ADJUDICATE-EXIT
WHEN PROVIDER-OUT-NET ALSO PLAN-PPO
PERFORM 3400-PRICE-PPO-OUT-NET
WHEN PROVIDER-OUT-NET ALSO PLAN-POS
PERFORM 3500-PRICE-POS-OUT-NET
WHEN PROVIDER-TERMED ALSO ANY
SET ADJ-PENDED TO TRUE
SET PEND-NEED-RECORDS TO TRUE
GO TO 3000-ADJUDICATE-EXIT
END-EVALUATE
* -----------------------------------------------
* Phase 5: High-dollar review
* -----------------------------------------------
IF WS-ALLOWED-AMOUNT > 10000
SET ADJ-PENDED TO TRUE
SET PEND-HIGH-DOLLAR TO TRUE
GO TO 3000-ADJUDICATE-EXIT
END-IF
SET ADJ-APPROVED TO TRUE
.
3000-ADJUDICATE-EXIT.
PERFORM 3900-WRITE-ADJUDICATION-RECORD
.
📊 Pattern Analysis: This procedure demonstrates the "waterfall" pattern — a series of checks where each one can short-circuit the process. It processes in order of severity: eligibility (most critical, check first), duplicates, authorization, network/pricing, and finally dollar thresholds. Each phase either allows processing to continue or diverts to a terminal outcome (deny, pend) and exits.
🔵 Industry Context: This pattern — phased adjudication with early exit — is standard across the health insurance industry. Whether you work at a Blue Cross Blue Shield plan, a UnitedHealthcare subsidiary, or a small regional insurer, you will encounter this decision structure. The specific rules vary, but the architectural pattern is universal.
6.10 Common Pitfalls and Debugging Strategies
Pitfall 1: The Dangling ELSE
In deeply nested IF statements, it can be unclear which IF an ELSE belongs to:
* DANGEROUS: Which IF does ELSE belong to?
IF WS-STATUS = 'A'
IF WS-BALANCE > 0
PERFORM 3100-PROCESS
ELSE
PERFORM 9100-LOG-ERROR
END-IF
In COBOL, ELSE binds to the nearest unmatched IF. So this ELSE belongs to the inner IF (WS-BALANCE > 0), not the outer IF. Using END-IF consistently eliminates this ambiguity:
* CLEAR: END-IF makes structure explicit
IF WS-STATUS = 'A'
IF WS-BALANCE > 0
PERFORM 3100-PROCESS
ELSE
PERFORM 9100-LOG-ERROR
END-IF
END-IF
✅ Best Practice: Always use explicit END-IF terminators. Never rely on period-delimited conditionals in new code. Period-terminated IF statements were necessary before COBOL-85 introduced END-IF, but they are a maintenance hazard in modern programs.
Pitfall 2: Incorrect Implied Subject Scope
* WRONG: Programmer intended to check both fields
IF WS-STATE = 'NY' OR WS-COUNTRY = 'CA'
* This actually means: IF WS-STATE = 'NY'
* OR WS-STATE = 'CA'
* (implied subject carries forward from WS-STATE)
Wait — is this actually what happens? Actually no. In this case, WS-COUNTRY = 'CA' is a separate relational condition, not an implied subject. The implied subject rule only applies when an identifier is omitted after OR or AND. The key is whether WS-COUNTRY is present as an explicit subject. Let us be precise:
* Case 1: Explicit subjects — works as intended
IF WS-STATE = 'NY' OR WS-COUNTRY = 'CA'
* Checks: WS-STATE = 'NY' OR WS-COUNTRY = 'CA'
* Case 2: Implied subject — might surprise you
IF WS-STATE = 'NY' OR 'NJ'
* Checks: WS-STATE = 'NY' OR WS-STATE = 'NJ'
* Case 3: The real danger
IF WS-STATE = 'NY' OR 'NJ' AND WS-TYPE = 'A'
* Checks: WS-STATE = 'NY' OR
* (WS-STATE = 'NJ' AND WS-TYPE = 'A')
* NOT: (WS-STATE = 'NY' OR 'NJ') AND WS-TYPE = 'A'
Case 3 is the real trap. The programmer might have intended "if the state is NY or NJ, and the type is A." But AND has higher precedence than OR, so the condition means "if the state is NY (regardless of type) OR if the state is NJ and the type is A." The fix:
IF (WS-STATE = 'NY' OR 'NJ') AND WS-TYPE = 'A'
Pitfall 3: NUMERIC Test on Group Items
01 WS-DATE-GROUP.
05 WS-DATE-YEAR PIC 9(04).
05 WS-DATE-MONTH PIC 9(02).
05 WS-DATE-DAY PIC 9(02).
* WRONG: NUMERIC test on group item
IF WS-DATE-GROUP IS NUMERIC
A group item is always treated as alphanumeric (PIC X) regardless of its subordinate items' pictures. The NUMERIC test on a group item tests whether all bytes are valid digit characters, which may not be what you intend if the group contains signed fields or embedded separators.
Pitfall 4: Forgetting WHEN OTHER
* DANGEROUS: No WHEN OTHER
EVALUATE WS-STATUS
WHEN 'A' PERFORM 3100-ACTIVE
WHEN 'C' PERFORM 3200-CLOSED
WHEN 'S' PERFORM 3300-SUSPENDED
END-EVALUATE
If WS-STATUS contains any value other than 'A', 'C', or 'S', the program silently does nothing. This is almost never the correct behavior. Always include WHEN OTHER, even if it just logs a warning.
Debugging Strategy: Condition Tracing
When debugging complex conditionals, temporarily add DISPLAY statements to trace which branch is taken:
EVALUATE TRUE
WHEN ACCT-IS-ACTIVE
DISPLAY 'DEBUG: Branch ACTIVE taken'
PERFORM 3100-PROCESS-ACTIVE
WHEN ACCT-IS-SUSPENDED
DISPLAY 'DEBUG: Branch SUSPENDED taken'
PERFORM 3200-PROCESS-SUSPENDED
WHEN OTHER
DISPLAY 'DEBUG: Branch OTHER taken, '
'STATUS=' WS-ACCOUNT-STATUS
PERFORM 9100-LOG-ERROR
END-EVALUATE
⚠️ Warning: Always remove or comment out DISPLAY debugging before promoting code to production. Better yet, use a debugging flag:
01 WS-DEBUG-MODE PIC X(01) VALUE 'N'.
88 DEBUG-ON VALUE 'Y'.
* In the logic:
IF DEBUG-ON
DISPLAY 'DEBUG: Branch ACTIVE taken'
END-IF
6.11 Performance Considerations
In most COBOL programs, conditional logic is not a performance bottleneck. The compiler optimizes condition evaluation efficiently, and modern mainframe hardware evaluates conditions in nanoseconds. However, in high-volume batch programs processing millions of records, small optimizations can accumulate.
Order Your Conditions by Likelihood
In an IF-ELSE chain (or EVALUATE), put the most likely condition first:
* If 80% of transactions are deposits, check that first
EVALUATE TRUE
WHEN TXN-DEPOSIT
PERFORM 3200-PROCESS-DEPOSIT
WHEN TXN-WITHDRAWAL
PERFORM 3100-PROCESS-WITHDRAWAL
WHEN TXN-INQUIRY
PERFORM 3400-PROCESS-INQUIRY
WHEN OTHER
PERFORM 9100-LOG-ERROR
END-EVALUATE
This minimizes the average number of condition evaluations. In a program processing 2.3 million transactions per day (GlobalBank's volume), putting the most common transaction type first versus last could save hundreds of thousands of unnecessary condition evaluations.
Short-Circuit Evaluation
COBOL does not guarantee short-circuit evaluation of AND/OR conditions. In languages like C or Java, if (x != null && x.getValue() > 0) is safe because the second condition is not evaluated if the first is false. COBOL may evaluate both conditions:
* DANGEROUS in some compilers:
IF WS-DIVISOR NOT = ZERO
AND (WS-DIVIDEND / WS-DIVISOR) > WS-THRESHOLD
Some compilers may evaluate the division even when WS-DIVISOR is zero, causing an abend. The safe pattern:
IF WS-DIVISOR NOT = ZERO
IF (WS-DIVIDEND / WS-DIVISOR) > WS-THRESHOLD
PERFORM 3100-ABOVE-THRESHOLD
END-IF
END-IF
📊 Compiler Note: IBM Enterprise COBOL can be configured for short-circuit evaluation with certain compiler options, but relying on this is non-portable. Nest your IFs for safety.
6.12 Conditional Logic in the Mainframe Ecosystem
Understanding conditional logic in COBOL is not just about knowing the syntax — it is about understanding how conditional logic interacts with the larger mainframe ecosystem.
Condition Evaluation and EBCDIC Collating Sequence
On IBM mainframes, character data is stored in EBCDIC (Extended Binary Coded Decimal Interchange Code), not ASCII. This affects character comparisons in unexpected ways:
| EBCDIC Order | ASCII Order |
|---|---|
| Spaces (lowest) | Spaces (lowest) |
| Lowercase letters a-z | Numbers 0-9 |
| Uppercase letters A-Z | Uppercase A-Z |
| Numbers 0-9 (highest) | Lowercase a-z |
This means that in EBCDIC, 'A' < 'a' and '9' > 'Z'. If you write:
IF WS-CODE >= 'A' AND WS-CODE <= 'Z'
This works correctly in EBCDIC — it captures only uppercase letters. But in ASCII, this range would also include characters like [, \, ], ^, _, and the backtick. If your program runs on both mainframe (EBCDIC) and distributed (ASCII) platforms (using GnuCOBOL, for example), use class conditions instead:
IF WS-CODE IS ALPHABETIC-UPPER
This is platform-independent because the class condition tests the character's category, not its numeric value.
⚠️ Portability Warning: The ALPHABETIC class condition considers the locale-specific definition of "alphabetic." On an EBCDIC system with a different code page (e.g., international EBCDIC with accented characters), the results may differ. Always test class conditions with your specific runtime environment.
Condition Evaluation and Packed Decimal
COBOL numeric fields can be stored in several formats: DISPLAY (one digit per byte), COMP-3 (packed decimal, two digits per byte plus sign), and COMP/BINARY. The IS NUMERIC class condition works correctly with all formats, but there is a subtle difference:
01 WS-AMOUNT-DISPLAY PIC 9(05) VALUE 12345.
01 WS-AMOUNT-PACKED PIC 9(05) COMP-3 VALUE 12345.
* Both of these work correctly:
IF WS-AMOUNT-DISPLAY IS NUMERIC ... *> TRUE
IF WS-AMOUNT-PACKED IS NUMERIC ... *> TRUE
However, if a COMP-3 field is corrupted (e.g., filled with X'FF' bytes from a file read error), the IS NUMERIC test will return false — which is exactly the behavior you want. This is why NUMERIC checks are essential for defensively validating data from external sources.
Performance of Conditional Evaluation
On modern IBM z/Architecture mainframes (z15 and z16), conditional evaluation is highly optimized:
- Compare Immediate (CLI/CHI): Single conditions against literals compile to one or two machine instructions, executing in a single CPU cycle.
- Branch on Condition (BC): IF/ELSE branches compile to conditional branch instructions with minimal overhead.
- EVALUATE: The compiler may generate a branch table for EVALUATE with sequential literal values, achieving O(1) lookup rather than sequential comparison.
For most programs, conditional logic is not a performance concern. The exception is programs processing tens of millions of records per run, where even nanosecond-level differences accumulate. In those cases:
- Order EVALUATE WHEN clauses by frequency (most common first)
- Avoid complex computed conditions in tight inner loops
- Use condition names (88-levels) rather than complex OR chains — the compiler can optimize them more effectively
- If you must test multiple conditions, consider whether an EVALUATE with ALSO can replace multiple sequential IF statements
📊 Benchmark Data: At GlobalBank, Priya Kapoor measured the performance difference between a nested IF chain (6 levels deep) and an equivalent EVALUATE TRUE with 6 WHEN clauses. Over 10 million iterations, the EVALUATE version was 3.2% faster — a meaningful difference in a tight inner loop, though negligible in most programs.
Conditional Logic and Copybooks
In large systems, condition names (88-levels) should be defined in copybooks (shared data definitions) rather than in individual programs. This ensures that every program uses the same condition names and values:
* COPYBOOK: ACCT-STATUS-DEF.cpy
01 WS-ACCOUNT-STATUS PIC X(01).
88 ACCT-IS-ACTIVE VALUE 'A'.
88 ACCT-IS-CLOSED VALUE 'C'.
88 ACCT-IS-SUSPENDED VALUE 'S'.
88 ACCT-IS-PENDING VALUE 'P'.
88 ACCT-IS-RESTRICTED VALUE 'R'.
88 ACCT-IS-PROCESSABLE VALUE 'A' 'P'.
88 ACCT-STATUS-VALID VALUE 'A' 'C' 'S' 'P' 'R'.
When a new status code is added, the copybook is updated once, and all programs that COPY it automatically get the updated definitions. We will explore copybooks in depth in Chapter 9.
🔗 Cross-Reference: The copybook approach to 88-level definitions is the single most effective technique for preventing the "missing status code" bugs described in the MedClaim case study (Section 6.11). When the 88-level is defined in a copybook and all programs COPY it, adding a new value means updating one file rather than hunting through dozens of programs.
6.13 Putting It All Together: Style Guide for Conditional Logic
Based on everything in this chapter, here is a style guide for writing maintainable conditional logic in COBOL:
-
Define 88-level condition names for every status/type/flag field. This is non-negotiable in professional COBOL. Raw value comparisons (
= 'A',= 'Y') should be rare in your PROCEDURE DIVISION. -
Use EVALUATE TRUE instead of deeply nested IF. If you have more than three conditions to check, EVALUATE TRUE is almost always more readable.
-
Use EVALUATE with ALSO for multi-dimensional decisions. When the action depends on two or more independent factors, a multi-subject EVALUATE is a decision table in code.
-
Always include WHEN OTHER. No exceptions.
-
Always use END-IF and END-EVALUATE. Never use period-delimited conditionals in new code.
-
Use parentheses in all complex conditions. Even when precedence would give the correct result, parentheses communicate intent.
-
Limit nesting to three levels. If you need deeper nesting, refactor into paragraphs or use EVALUATE.
-
Create group condition names. If you frequently check "is this status one of several values," create an 88-level that groups those values.
-
Use SET rather than MOVE to set condition names.
SET ACCT-IS-ACTIVE TO TRUEis self-documenting;MOVE 'A' TO WS-ACCT-STATUSrequires knowledge of the encoding. -
Validate before you calculate. Always use IS NUMERIC before using alphanumeric input in arithmetic.
🔗 Looking Ahead: In Chapter 7, we will explore iteration patterns — PERFORM in all its forms. You will see how the conditional patterns from this chapter combine with loop constructs to build the batch processing engines that power enterprise systems. In Chapter 8, we will zoom out to program design: how paragraphs and sections organize your conditional and iterative logic into maintainable, readable programs.
6.14 Working Through a Complex Business Rule
To solidify everything in this chapter, let us trace through a complete business rule implementation from specification to code. This is the process that professional COBOL programmers follow when translating business requirements into conditional logic.
The Business Rule
GlobalBank's fee schedule for wire transfers is as follows:
Wire Transfer Fee Schedule (effective 2024-01-01): 1. Domestic wires (US only): - Standard (next business day): $15 for balances under $10,000; $10 for balances $10,000-$99,999; FREE for balances $100,000+ - Same-day: $25 for all account tiers 2. International wires: - Standard: $45 regardless of balance - Same-day: $65 regardless of balance 3. Fee waivers: - Platinum customers: all domestic wire fees waived - Gold customers: standard domestic wire fees waived (same-day still applies) 4. Monthly limit: Maximum 20 wire transfers per month per account (all types combined). Attempts beyond 20 must be rejected.
Step 1: Define the Data
01 WS-WIRE-REQUEST.
05 WS-WIRE-TYPE PIC X(01).
88 WIRE-DOMESTIC VALUE 'D'.
88 WIRE-INTERNATIONAL VALUE 'I'.
88 WIRE-TYPE-VALID VALUE 'D' 'I'.
05 WS-WIRE-SPEED PIC X(01).
88 WIRE-STANDARD VALUE 'S'.
88 WIRE-SAME-DAY VALUE 'E'.
88 WIRE-SPEED-VALID VALUE 'S' 'E'.
05 WS-WIRE-AMOUNT PIC S9(11)V99.
01 WS-ACCT-BALANCE PIC S9(11)V99.
88 BAL-UNDER-10K VALUE -999999999.99
THRU 9999.99.
88 BAL-10K-TO-100K VALUE 10000.00
THRU 99999.99.
88 BAL-100K-PLUS VALUE 100000.00
THRU 999999999.99.
01 WS-CUST-TIER PIC X(01).
88 CUST-PLATINUM VALUE 'P'.
88 CUST-GOLD VALUE 'G'.
88 CUST-SILVER VALUE 'S'.
88 CUST-STANDARD VALUE 'R'.
88 CUST-IS-PREMIUM VALUE 'P' 'G'.
01 WS-MONTHLY-WIRE-COUNT PIC 9(03).
01 WS-WIRE-MONTHLY-LIMIT PIC 9(03) VALUE 20.
01 WS-WIRE-FEE PIC S9(05)V99.
01 WS-WIRE-RESULT PIC X(01).
88 WIRE-APPROVED VALUE 'A'.
88 WIRE-REJECTED VALUE 'R'.
Step 2: Validate Inputs
4000-PROCESS-WIRE-REQUEST.
* Validate inputs first
IF NOT WIRE-TYPE-VALID
MOVE 'INVALID WIRE TYPE' TO WS-ERROR-MSG
SET WIRE-REJECTED TO TRUE
PERFORM 9100-LOG-ERROR
GO TO 4000-WIRE-EXIT
END-IF
IF NOT WIRE-SPEED-VALID
MOVE 'INVALID WIRE SPEED' TO WS-ERROR-MSG
SET WIRE-REJECTED TO TRUE
PERFORM 9100-LOG-ERROR
GO TO 4000-WIRE-EXIT
END-IF
IF WS-WIRE-AMOUNT IS NOT POSITIVE
MOVE 'WIRE AMOUNT MUST BE POSITIVE'
TO WS-ERROR-MSG
SET WIRE-REJECTED TO TRUE
PERFORM 9100-LOG-ERROR
GO TO 4000-WIRE-EXIT
END-IF
* Check monthly limit
IF WS-MONTHLY-WIRE-COUNT >= WS-WIRE-MONTHLY-LIMIT
MOVE 'MONTHLY WIRE LIMIT EXCEEDED'
TO WS-ERROR-MSG
SET WIRE-REJECTED TO TRUE
PERFORM 9100-LOG-ERROR
GO TO 4000-WIRE-EXIT
END-IF
* Calculate fee
PERFORM 4100-DETERMINE-WIRE-FEE
* Apply fee waivers
PERFORM 4200-APPLY-FEE-WAIVERS
SET WIRE-APPROVED TO TRUE
ADD 1 TO WS-MONTHLY-WIRE-COUNT
.
4000-WIRE-EXIT.
EXIT.
Step 3: Determine Base Fee
4100-DETERMINE-WIRE-FEE.
EVALUATE TRUE ALSO TRUE
WHEN WIRE-DOMESTIC ALSO WIRE-STANDARD
EVALUATE TRUE
WHEN BAL-100K-PLUS
MOVE 0 TO WS-WIRE-FEE
WHEN BAL-10K-TO-100K
MOVE 10.00 TO WS-WIRE-FEE
WHEN OTHER
MOVE 15.00 TO WS-WIRE-FEE
END-EVALUATE
WHEN WIRE-DOMESTIC ALSO WIRE-SAME-DAY
MOVE 25.00 TO WS-WIRE-FEE
WHEN WIRE-INTERNATIONAL ALSO WIRE-STANDARD
MOVE 45.00 TO WS-WIRE-FEE
WHEN WIRE-INTERNATIONAL ALSO WIRE-SAME-DAY
MOVE 65.00 TO WS-WIRE-FEE
WHEN OTHER
MOVE 0 TO WS-WIRE-FEE
MOVE 'UNEXPECTED WIRE COMBINATION'
TO WS-ERROR-MSG
PERFORM 9100-LOG-ERROR
END-EVALUATE
.
Step 4: Apply Fee Waivers
4200-APPLY-FEE-WAIVERS.
* Platinum: all domestic fees waived
IF CUST-PLATINUM AND WIRE-DOMESTIC
MOVE 0 TO WS-WIRE-FEE
END-IF
* Gold: standard domestic fees waived
IF CUST-GOLD AND WIRE-DOMESTIC AND WIRE-STANDARD
MOVE 0 TO WS-WIRE-FEE
END-IF
.
Analysis
Let us count the condition types used in this solution:
| Condition Type | Usage |
|---|---|
| 88-level condition names | 18 defined, used throughout |
| EVALUATE TRUE | 2 instances (fee determination, balance tier) |
| EVALUATE TRUE ALSO TRUE | 1 instance (wire type + speed) |
| Class conditions | 1 (IS POSITIVE check on amount) |
| Complex conditions | 3 (AND combinations for fee waivers) |
| Guard clauses | 4 (input validation with GO TO exit) |
| Group condition names | 2 (CUST-IS-PREMIUM, WIRE-TYPE-VALID) |
| WHEN OTHER | In every EVALUATE |
Every technique from this chapter is represented. The code is readable — Sarah Kim verified it against the fee schedule document in ten minutes. It is maintainable — adding a new wire speed or a new customer tier requires modifying one or two clearly identified locations. And it is defensive — invalid inputs are caught and rejected before any fee calculation occurs.
🧪 Try It Yourself: Extending the Wire Fee Calculator
Extend the wire fee calculator with these additional requirements:
- Add a "recurring wire" flag (monthly automatic wires). Recurring wires get a 25% fee discount.
- Add a currency conversion surcharge for international wires: 1% of the wire amount, minimum $10, maximum $50.
- Add a fraud check: wires over $50,000 to new recipients (first wire to that destination in the last 90 days) must be flagged for manual review (pended, not rejected).
- Display a formatted fee summary showing the base fee, each adjustment, and the final fee.
Chapter Summary
This chapter has taken you from basic IF statements to the full power of COBOL's conditional logic toolkit. You have learned to manage nested IF complexity through guard clauses, condition flags, and paragraph extraction. You have explored EVALUATE in its many forms — basic, TRUE, ALSO, and with ranges. You have mastered 88-level condition names, the single most important readability tool in the COBOL programmer's arsenal. And you have learned to combine conditions safely using AND, OR, NOT, and parentheses.
The central lesson is this: readability is not opposed to correctness — it enables it. Code that is easy to read is easy to verify, easy to test, easy to debug, and easy to modify. In the world of enterprise COBOL, where programs process billions of dollars and affect millions of people, readable conditional logic is not a stylistic preference. It is a professional obligation.
As Maria Chen tells every new developer on her team: "Write your conditions so that a business analyst can read them. If Sarah Kim can look at your EVALUATE and understand what it does, you have written good code. If she cannot, rewrite it until she can."
Key Terms
| Term | Definition |
|---|---|
| Condition name | An 88-level item that associates a meaningful name with one or more values of a data item |
| EVALUATE | A multi-way branch statement that acts as a decision table |
| Guard clause | A conditional check at the beginning of a procedure that exits early for invalid conditions |
| Implied subject | A COBOL shorthand where the subject of a condition is carried forward from a previous condition in the same statement |
| Class condition | A test of whether a data item's contents belong to a category (NUMERIC, ALPHABETIC, etc.) |
| Sign condition | A test of whether a numeric value is POSITIVE, NEGATIVE, or ZERO |
| Complex condition | A condition formed by combining simple conditions with AND, OR, and NOT |
| Short-circuit evaluation | Optimization where the second operand of AND/OR is not evaluated if the result is determined by the first — not guaranteed in COBOL |
| Decision table | A tabular representation of multi-dimensional conditional logic, implementable via EVALUATE with ALSO |
| WHEN OTHER | The catch-all branch in an EVALUATE statement, analogous to a default case |