Guild Companies, Inc.  
 
Midrange Programmer - How-To Advice & Free Code
OS/400 Edition
Volume 1, Number 2 - January 31, 2002

Subprocedures: Better than Subroutines

by Ted Holt

Many RPG programmers no longer use conditioning or resulting indicators in calculation specs. Many no longer use the cycle. Many no longer define variables in C-specs. Maybe it's time RPG programmers quit using subroutines.

My RPG II teacher did not like subroutines and did not teach his students to use them. It wasn't until I began writing production code that I learned the value of subroutines. But since then, I've found something even better than subroutines: subprocedures.

Subprocedures have all the advantages of subroutines and then some. In this article, I point out the advantages of using subprocedures rather than subroutines.

Subroutines Are Wonderful

I think the greatest benefit of using subroutines is intellectual manageability. That is, I can think of a program as a group of related small tasks, all of which are simple enough for me to understand, rather than as one gargantuan task that is more than I can handle. I believe--but cannot prove--that my programs have fewer bugs because I tackle each task separately.

Debugging seems to be easier because I can often determine which subroutine most likely contains an error. Finding a logic error in a program of subroutines is similar to determining why my car won't crank. I don't check to see if there is air in the tires, because I know that a lack of air in the tires won't prevent my car from cranking. I would look to see if there is gas in the tank or check whether the battery is dead. Likewise, if there was a mistake in the discount a customer received, I would begin my search for the error at the routine that calculates discounts.

Subroutines also promote the reusability of code. If I developed a subroutine to calculate a profit margin, I would be able to copy it to other programs that needed to calculate profit margins.

However, subroutines have their limitations, two of which really bother me. The first is that subroutines use global variables only. That is, any variable or constant or indicator that I use in a subroutine may be used anywhere else in the program--in the main calculations, in any subroutine, in output specs. This can lead to undesirable side effects. Changing the value of a variable in one part of the program causes something to go wrong in another part of a program.

Global variables also work against the portability of code. For instance, I may have a dandy subroutine that calculates a scheduling function of some sort, but copying it to another program may require me to rename a lot of variables. For example, maybe the two programs use different input files, in which case field names are different. Or maybe the work variables in the subroutine have already been used for other purposes in the second program. In such cases, I'm less likely to reuse the subroutine.

The other thing that bothers me is that I can't define parameters to pass data to subroutines. A subroutine that verifies a general ledger account number might need to verify several different account number variables within one program, but there is no way to pass each different account number variable to the subroutine.

Subprocedures Are Even More Wonderful

Subprocedures directly address these two problems. First, subprocedures allow you to define local variables. A local variable is one that is understood only with within the subprocedure and cannot be referenced outside of it.

A local variable can have the same name as a global variable. The two are separate variables, and the compiler will not confuse them. The global and local variables of the same name do not have to be defined identically. Do you understand the ramifications of this? You can create a work variable in a subprocedure and not have to worry that there may be another variable of the same name in the main part of the program. This increases my chances of porting a subprocedure from one program to another.

Parameters also make subprocedures more portable by providing internal names (i.e., internal to the subroutine) for required data values. The subprocedure does not have to know the names of variables, constants, and fields in database files or other parts of the program in order to do its assigned task.

An Example Would Be Good

If subprocedures are harder to code than subroutines, it's not by much. A subprocedure needs a procedure prototype, but that's no big deal; I just copy the procedure interface from the subprocedure and change the "pi" line to a "pr" line. The CALLP op code is no harder to code than EXSR.

To illustrate, here are two versions of a routine to calculate income tax in a mythical state. I've included them inside part of a "gross-to-net" payroll program. The first is implemented as a subroutine, the second as a subprocedure. If you compare them, you'll see there is little difference.

First, here's the subroutine. Notice the identifiers that begin with PW. These are fields in the payroll work file, which this subroutine updates. The only work variable, TaxablePay, is defined in the D specs.

Fpaywork   uf   e             disk

D TaxablePay      s              7p 2

C                   read      PayRec
C                   dow       not %eof(PayWork)
C                   exsr      CalcStateTax
C                   update    PayRec
C                   read      PayRec
C                   enddo
C*
C                   eval      *inLR = *on

C     CalcStateTax  begsr
C*
C                   eval      PWStateTax = *zero
C* calculate annual pay
C                   eval      TaxablePay =
C                               (PWGross * PWNbrPer)
C* deduct allowance for employee & spouse
C                   if        PWMarStat = 'M'
C                   eval      TaxablePay =
C                               (TaxablePay - 10000)
C                   else
C                   eval      TaxablePay =
C                               (TaxablePay - 5000)
C                   endif
C* if zero or less, no tax due
C                   if        TaxablePay <= *zero
C                   leavesr
C                   endif
C* deduct allowance for dependents
C                   eval      TaxablePay =
C                               (TaxablePay -
C                                  (PWNbrDep * 2500))
C* if zero or less, no tax due
C                   if        TaxablePay <= *zero
C                   leavesr
C                   endif
C* yearly tax is 3% of first $5,000
C*               4% of remainder
C                   if        TaxablePay <= 5000
C                   eval      PWStateTax =
C                               (TaxablePay * 0.03)
C                   else
C                   eval      PWStateTax =
C                               (150 +
C                                (TaxablePay - 5000) * 0.04)
C                   endif
C* get this period's portion of yearly tax
C                   eval      PWStateTax =
C                               (PWStateTax / PWNbrPer)
C                   endsr

Here's the subprocedure. There are no references to global variables. The TaxablePay variable is now defined in the subprocedure. There may be other variables named TaxablePay in other tax routines, but they will not conflict with or affect this one. The subprocedure does not directly reference the fields from the payroll work file. Instead, all fields are passed to the subprocedure through parameters.

H dftactgrp(*no) actgrp('QILE')

Fpaywork   uf   e             disk

 * prototype for state tax routine
DCalcStateTax     pr
D StateTax                       7p 2
D Gross                          7p 2 value
D NbrPer                         3p 0 value
D MarStat                        1    value
D NbrDep                         3p 0 value

C                   read      PayRec
C                   dow       not %eof(PayWork)
C                   callp     CalcStateTax
C                               (PWStateTax:
C                                PWGross:
C                                PWNbrPer:
C                                PWMarStat:
C                                PWNbrDep)
C                   update    PayRec
C                   read      PayRec
C                   enddo
C*
C                   eval      *inLR = *on

PCalcStateTax     b
 * parameters
D                 pi
D StateTax                       7p 2
D Gross                          7p 2 value
D NbrPer                         3p 0 value
D MarStat                        1    value
D NbrDep                         3p 0 value
 * local variables and constants
D TaxablePay      s              7p 2

C                   eval      StateTax = *zero
C* calculate annual pay
C                   eval      TaxablePay =
C                               (Gross * NbrPer)
C* deduct allowance for employee & spouse
C                   if        MarStat = 'M'
C                   eval      TaxablePay =
C                               (TaxablePay - 10000)
C                   else
C                   eval      TaxablePay =
C                               (TaxablePay - 5000)
C                   endif
C* if zero or less, no tax due
C                   if        TaxablePay <= *zero
C                   return
C                   endif
C* deduct allowance for dependents
C                   eval      TaxablePay =
C                               (TaxablePay -
C                                  (NbrDep * 2500))
C* if zero or less, no tax due
C                   if        TaxablePay <= *zero
C                   return
C                   endif
C* yearly tax is 3% of first $5,000
C*               4% of remainder
C                   if        TaxablePay <= 5000
C                   eval      StateTax =
C                               (TaxablePay * 0.03)
C                   else
C                   eval      StateTax =
C                               (150 +
C                                (TaxablePay - 5000) * 0.04)
C                   endif
C* get this period's portion of yearly tax
C                   eval      StateTax =
C                               (StateTax / NbrPer)
C*
PCalcStateTax     e

Let me point out one more thing. A program with a subprocedure will not run in the default activation group, so I included an H-spec to force it to run in activation group QILE instead.

Returning a Value

Here's another thing subprocedures can do that subroutines can't: A subprocedure can return a value in the same way that a built-in function returns a value. This means that you can reference a subprocedure name in such operations as EVAL, IF, and DOx.

The previous example is a good illustration of this. The CalcStateTax procedure yields one value--the amount of tax to withhold from a paycheck and send to the tax collector. Here is the subprocedure modified to return a value:

H dftactgrp(*no) actgrp('QILE')

Fpaywork   uf   e             disk

DCalcStateTax     pr             7p 2
D Gross                          7p 2 value
D NbrPer                         3p 0 value
D MarStat                        1    value
D NbrDep                         3p 0 value

C                   read      PayRec
C                   dow       not %eof(PayWork)
C                   eval      PWStateTax =
C                                CalcStateTax
C                                  (PWGross:
C                                   PWNbrPer:
C                                   PWMarStat:
C                                   PWNbrDep)
C                   update    PayRec
C                   read      PayRec
C                   enddo
C*
C                   eval      *inLR = *on
  * ========================================================
PCalcStateTax     b
 * parameters
D                 pi             7p 2
D Gross                          7p 2 value
D NbrPer                         3p 0 value
D MarStat                        1    value
D NbrDep                         3p 0 value
 * local variables and constants
D TaxablePay      s              7p 2
D StateTax        s              7p 2

C* calculate annual pay
C                   eval      TaxablePay =
C                               (Gross * NbrPer)
C* deduct allowance for employee & spouse
C                   if        MarStat = 'M'
C                   eval      TaxablePay =
C                               (TaxablePay - 10000)
C                   else
C                   eval      TaxablePay =
C                               (TaxablePay - 5000)
C                   endif
C* if zero or less, no tax due
C                   if        TaxablePay <= *zero
C                   return    *zero
C                   endif
C* deduct allowance for dependents
C                   eval      TaxablePay =
C                               (TaxablePay -
C                                  (NbrDep * 2500))
C* if zero or less, no tax due
C                   if        TaxablePay <= *zero
C                   return    *zero
C                   endif
C* yearly tax is 3% of first $5,000
C*               4% of remainder
C                   if        TaxablePay <= 5000
C                   eval      StateTax =
C                               (TaxablePay * 0.03)
C                   else
C                   eval      StateTax =
C                               (150 +
C                                (TaxablePay - 5000) * 0.04)
C                   endif
C* get this period's portion of yearly tax
C                   return      (StateTax / NbrPer)
C*
PCalcStateTax     e

Notice the differences. The "pi" procedure interface line now includes a data type and size to tell what type of value is being returned. Each RETURN operation includes a value to be sent back to the caller.

A Few More Tips Would Be Nice

I hear that many programmers do not use subprocedures. If you're one of them, I hope I've given you some good reasons to begin using them. And before I go, let me offer a few tips on using subprocedures.

First, subprocedures follow the O-specs, if you have any. Since most RPG programs these days don't have O-specs, subprocedures usually follow the C-specs.

Second, don't start converting subroutines to subprocedures. I do not advocate changing working code unless there is a clear benefit in doing so. You may benefit from converting subroutines that carry out common tasks or tend to break and need a lot of fixing; but otherwise, leave your existing code alone.

Third, if a subroutine is useful throughout an application--if it can be used in more than one program-- consider putting it into a service program, to which other programs can bind at compile time, rather than at the end of each program that needs it. Building service programs is a topic for another article.

Speaking of topics for other articles, subprocedures support recursion. That is, a subprocedure can call itself. Try that with a subroutine sometime. This is not a terribly useful feature, but recursion does have its applications, and maybe I will write about it someday.

There is one additional advantage to subroutines worth mentioning. Subroutines are faster than subprocedures. I have never had to worry about the performance of subprocedures, but in a heavily used program, performance may become an issue. Using subroutines instead of subprocedures may make a difference.

I don't know what my RPG teacher's aversion to subroutines was. Maybe he didn't like having to code SR in columns 7 and 8 of the C-specs, as the System/3 RPG II compiler required. Maybe he thought that using subroutines added too many lines of code to programs. I just hope that you will not have such an aversion to subprocedures.

Ted Holt is a consultant and an editor of Midrange Guru, OS/400 Edition. He welcomes your comments at tholt@itjungle.com.

Sponsored By
JACADA LTD.

Gartner Research on
Legacy Extension & Jacada

See why Jacada is the leading legacy extension solution according to Gartner. This 30 page report includes approaches to legacy extension, vendor rankings in the Magic Quadrant, and case studies. It's a must-read for anyone planning legacy extension projects.

Get your FREE copy at http://www.jacada.com/gartner/quadrant32.

THIS ISSUE
SPONSORED BY:
Help/Systems
SoftLanding Systems
BCD Software Int'l
Jacada Ltd.
RJS Software Systems
WorksRight Software
BACK ISSUES
TABLE OF CONTENTS
The Midrange Programmer Philosophy
Prototyping and Calling Java Methods from RPG
Coding SQL Functions in OS/400 V5R1
Subprocedures: Better than Subroutines
Five Cool Things You Can Do with OpsNav
Let Your Hair Down With Free-Formed C-Specs
  Newsletters | Subscribe | Advertise | About Us | Contact | Search | Home  
  Last Updated: 1/14/02
Copyright © 1996-2008 Guild Companies, Inc. All Rights Reserved.