Context
Well Written Code Should Look Elegant. Here I am searching for an elegant way to ease legacy section/paragraph based COBOL into using methods with all the associated benefits.
When discussing COBOL, what I hear over and over again as the number 1 disadvantage (from real COBOL programmers) compared to C, C# and Java, etc. is the lack of local data. When writing a large COBOL program, it becomes progressively more difficult to work out what is happening because all the parts of the program share the same working storage. It is possible to use local storage, but the natural unit of sub-division in a COBOL program is the paragraph or section (depending on house style). Writing a system with hundreds or even thousands of tiny, individual programs is poor style, and may well result in lacklustre performance. This means working storage becomes huge and complex and unwieldy.
Managed COBOL from Micro Focus has a complete solution to this issue. It supports classes, objects and methods in a very intuitive way.
But, what happens if we don't want to make the whole leap into OO programming at one go? What happens if you just want to move away from global storage and progressively modernize?
Whilst this is a huge and fascinating subject, my aim here is just to take a first step and show some tricks and ideas. Maybe this can act to start of a bigger discussion or I might well come back to the subject in the future.
Taking Advantage of Managed COBOL Syntax can be done One Tiny Step at a Time.
Methods offer loads of advantages over performed ranges or even calls. However, I don't want to have to rave about that for now. I just want to show the first step - moving from a pure procedural program to a mix of procedures and classes. To do that, let's take an example COBOL procedural program. I have written one which is deliberately not that nice. We can imagine that this program was written in a few minutes by someone and then has been extended a few times by someone else who did not really understand what it does.
We can get a pretty good idea of what the program and its history is about from the header comments:
********************************************
* Yield table creator
* ===================
*
* This program creates a yield table to show the
* yield from our investment policy type Qr24.
* It does this using the old computational system and
* the new one as mandated by reg 123456 ordinance 988765.
* Author: A.N. Other (Retired)
*
* Altered to produce several tables:
* Author: A. Newbie
********************************************
Further, we can see that A. Newbie used 'perform through' style whilst A.N. Other did not. This has lead to a mix of programming styles and the inevitable confusion. To make my example work as a story, we need to pretend this is a huge program which would take several days to unpick. But - we don't want to spend several days over this post, so the short example and a dose of imagination will have to do:
********************************************
* Yield table creator
* ===================
*
* This program creates a yield table to show the
* yield from our investment policy type Qr24.
* It does this using the old computational system and
* the new one as mandated by reg 123456 ordinance 988765.
* Author: A.N. Other Retired-Programmer
*
* Altered to produce several tables:
* Author: A. Newbie
********************************************
program-id. yield-table-creator.
working-storage section.
01 money-info.
03 yield-old pic 9(9)v99 comp.
03 yield-new pic 9(9)v99 comp.
03 money pic 9(9).99.
01 calc-internals.
03 n-current-balance pic 9(9)v9(7) comp.
03 n-interest-c pic 9(9)v9(7) comp.
03 n-intermediate pic 9(9)v9(7) comp.
03 o-current-balance pic 9(9)v9(5) comp.
03 o-interest-c pic 9(9)v9(5) comp.
03 o-intermediate pic 9(9)v9(5) comp.
03 calc-day pic 9(9) comp.
01 yield-info.
03 start-balance pic 9(9)v9(2) comp.
03 yield pic 9(9)v9(2) comp.
03 interest pic v9(4) comp.
03 days pic 9(9) comp.
03 years pic 9(2) comp.
01 calc-info.
03 start-at pic 9(9)v9(2) comp.
03 end-at pic 9(9)v9(2) comp.
03 step-by pic 9(9)v9(2) comp.
procedure division.
perform table-01 through table-04
display "---== END RUN ==---"
goback.
table-01 section.
move 0.0625 to interest
move 9132 to days
move 25 to years
move 1 to start-at
move 100 to end-at
move 1 to step-by
perform make-table
.
table-02 section.
move 0.05 to interest
move 9132 to days
move 25 to years
move 1 to start-at
move 100 to end-at
move 1 to step-by
perform make-table
.
table-03 section.
move 0.0625 to interest
move 9132 to days
move 25 to years
move 100000 to start-at
move 110000 to end-at
move 100 to step-by
perform make-table
.
table-04 section.
move 0.05 to interest
move 9132 to days
move 25 to years
move 100000 to start-at
move 110000 to end-at
move 100 to step-by
perform make-table
.
make-table section.
display "Conversion for:"
multiply interest by 100 giving money
display "Interest= " money "%"
display "Days = " days
display "Years = " years
move start-at to money
display "Start at= " money
move end-at to money
display "End at = " money
move step-by to money
display "Step by = " money
display "+--------------+--------------+--------------+--------------+"
perform varying start-balance from start-at by step-by
until start-balance = end-at
perform comp-yield-old
move yield to yield-old
perform comp-yield-new
move yield to yield-new
if yield-new not = yield-old
move start-balance to money
display "| " money " | " with no advancing
move yield-old to money
display money " | " with no advancing
move yield-new to money
display money " | " with no advancing
compute money = yield-old - yield-new
display money " |"
end-if
end-perform
display "+--------------+--------------+--------------+--------------+"
display " "
.
comp-yield-new section.
move start-balance to n-current-balance
move interest to n-interest-c
compute n-interest-c rounded = (n-interest-c * years) / days
compute n-intermediate rounded = n-interest-c
move n-intermediate to n-interest-c
move days to calc-day
perform varying calc-day from 1 by 1 until calc-day greater days
compute n-intermediate rounded
= n-current-balance + (n-current-balance * n-interest-c)
move n-intermediate to n-current-balance
end-perform
move n-current-balance to yield
.
comp-yield-old section.
move start-balance to o-current-balance
move interest to o-interest-c
compute o-interest-c rounded = (o-interest-c * years) / days
compute o-intermediate rounded = o-interest-c
move o-intermediate to o-interest-c
move days to calc-day
perform varying calc-day from 1 by 1 until calc-day greater days
compute o-intermediate rounded
= o-current-balance + (o-current-balance * o-interest-c)
move o-intermediate to o-current-balance
end-perform
move o-current-balance to yield
.
end program yield-table-creator.
Which produces output like this:
Conversion for:
Interest= 000000006.25%
Days = 000009132
Years = 25
Start at= 000000001.00
End at = 000000100.00
Step by = 000000001.00
+--------------+--------------+--------------+--------------+
| 000000001.00 | 000000004.72 | 000000004.77 | 000000000.05 |
| 000000002.00 | 000000009.44 | 000000009.54 | 000000000.10 |
| 000000003.00 | 000000014.16 | 000000014.31 | 000000000.15 |
| 000000004.00 | 000000018.88 | 000000019.08 | 000000000.20 |
| 000000005.00 | 000000023.61 | 000000023.85 | 000000000.24 |
| 000000006.00 | 000000028.33 | 000000028.62 | 000000000.29 |
| 000000007.00 | 000000033.05 | 000000033.39 | 000000000.34 |
| 000000008.00 | 000000037.77 | 000000038.16 | 000000000.39 |
| 000000009.00 | 000000042.50 | 000000042.93 | 000000000.43 |
| 000000010.00 | 000000047.22 | 000000047.70 | 000000000.48 |
| 000000011.00 | 000000051.94 | 000000052.47 | 000000000.53 |
| 000000012.00 | 000000056.66 | 000000057.24 | 000000000.58 |
| 000000013.00 | 000000061.39 | 000000062.01 | 000000000.62 |
| 000000014.00 | 000000066.11 | 000000066.78 | 000000000.67 |
| 000000015.00 | 000000070.83 | 000000071.55 | 000000000.72 |
| 000000016.00 | 000000075.55 | 000000076.32 | 000000000.77 |
| 000000017.00 | 000000080.27 | 000000081.09 | 000000000.82 |
Making Changes - Methods To The Rescue!
To continue our story, we are tasked with updating the new algorithm because we have found out that " reg 123456 ordinance 988765" requires that no interest in paid on every 7th day. Here is the solution:
comp-yield-new section.
move start-balance to n-current-balance
move interest to n-interest-c
compute n-interest-c rounded = (n-interest-c * years) / days
compute n-intermediate rounded = n-interest-c
move n-intermediate to n-interest-c
move days to calc-day
move 1 to day-flagger
perform varying calc-day from 1 by 1 until calc-day greater days
if day-flagger = 7
move 1 to day flagger
else
compute n-intermediate rounded
= n-current-balance + (n-current-balance * n-interest-c)
move n-intermediate to n-current-balance
add 1 to day-flagger
end-if
end-perform
move n-current-balance to yield .
I have added in 'day-flagger' to the algorithm. The snag is that I will now have to go and define it in working storage. In a real program - that working storage may well be defined in a copy book; do I add day-flagger to that copy book or mess up the format of this program by adding it to the code source file? This is only a simple example, in a massive program having to keep going back to working storage and adding stuff is a pain and increases the memory footprint of the program even when those added items are not being used.
The solution is to put in a bit of effort now for a lot benefit later. The solution is to move the guts comp-yield-new into a method and use 'invoke' to do my calculation:
comp-yield-new section.
invoke comp-class::comp-yield-new(yield-info)
Here is the code for the com-class and the comp-yield-new method in it.
class-id comp-class public.
method-id comp-yield-new public static.
local-storage section.
01 calc-internals.
03 n-current-balance pic 9(9)v9(7) comp.
03 n-interest-c pic 9(9)v9(7) comp.
03 n-intermediate pic 9(9)v9(7) comp.
03 calc-day pic 9(9) comp.
linkage section.
01 yield-info.
03 start-balance pic 9(9)v9(2) comp.
03 yield pic 9(9)v9(2) comp.
03 interest pic v9(4) comp.
03 days pic 9(9) comp.
03 years pic 9(2) comp.
procedure division using yield-info.
move start-balance to n-current-balance
move interest to n-interest-c
compute n-interest-c rounded = (n-interest-c * years) / days
compute n-intermediate rounded = n-interest-c
move n-intermediate to n-interest-c
move days to calc-day
perform varying calc-day from 1 by 1 until calc-day greater days
compute n-intermediate rounded
= n-current-balance + (n-current-balance * n-interest-c)
move n-intermediate to n-current-balance
end-perform
move n-current-balance to yield
end method.
end class.
This is really just a cut and paste of the code from the perform, so I have had to put in a minimal amount of effort. I can put the 'yield-info' group in a copy file and that avoids me having to put any effort into the linkage section either!
linkage section.
copy "yield-info.cpy"
To demonstrate the behaviour of the program is completely unchanged, here is a shot of the output:
Both the comp-class and the comp-yield-new method are marked as public
so that they can be accessed from anywhere. More sophisticated OO styles like protected
and private
members are not required at this early stage of modernization. Also of note is that the comp-yield-new method is marked static
. This means it can be invoked in a similar way that COBOL entry points can be called. If it was not static
, we would need to create an instance of comp-class and invoke the method on the instance. This is another more sophisticated OO style which we don't need to think about at this stage.
Now we get the pay off!
OK, we have gone to the (small amount of) effort to make our section (or paragraph) into a method. Now we must add in this 7 day rule. Wow - we don't need to mess with the working-storage of our main program, or even in the class; we can work with the local-storage in our method!
method-id comp-yield-new public static.
local-storage section.
01 calc-internals.
03 n-current-balance pic 9(9)v9(7) comp.
03 n-interest-c pic 9(9)v9(7) comp.
03 n-intermediate pic 9(9)v9(7) comp.
03 calc-day pic 9(9) comp.
01 day-flagger pic 9.
linkage section.
copy "yield-info.cpy"
procedure division using yield-info.
move start-balance to n-current-balance
move interest to n-interest-c
compute n-interest-c rounded = (n-interest-c * years) / days
compute n-intermediate rounded = n-interest-c
move n-intermediate to n-interest-c
move days to calc-day
move 1 to day-flagger
perform varying calc-day from 1 by 1 until calc-day greater days
if day-flagger = 7
move 1 to day flagger
else
compute n-intermediate rounded
= n-current-balance + (n-current-balance * n-interest-c)
move n-intermediate to n-current-balance
add 1 to day-flagger
end-if
end-perform
move n-current-balance to yield
end method.
COBOL's biggest problem just went away!
I hope that I have shown how the transition from procedural COBOL to Managed OO COBOL can be really gentle, safe and easy. It is also really important that modernization can make real differences straight away.
Once the techniques for progressively modernizing procedural COBOL have been mastered, all that intellectual property locked away in COBOL programs can be re-used in new and exciting ways. Lots of little steps with lots of massive pay-offs.