65.9K
CodeProject is changing. Read more.
Home

Progressively Modernizing COBOL Via Adding Methods To Existing Code

emptyStarIconemptyStarIconemptyStarIconemptyStarIconemptyStarIcon

0/5 (0 vote)

May 28, 2010

CC (ASA 2.5)

5 min read

viewsIcon

10130

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.