Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / All-Topics

Progressively Modernizing COBOL Via Adding Methods To Existing Code

0.00/5 (No votes)
28 May 2010CC (ASA 2.5)5 min read 10.1K  
Modernizing procedural COBOL can be done in little steps, each one making a bit difference

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.

License

This article, along with any associated source code and files, is licensed under The Creative Commons Attribution-ShareAlike 2.5 License