Your RPGLE Sub‑Procedure Is Doing Too Much. Yes, I’m Talking to You!

  • Home
  • /
  • Blog
  • /
  • Your RPGLE Sub‑Procedure Is Doing Too Much. Yes, I’m Talking to You!

April 12, 2026

“This week I’ve been mostly writing sub-procedures”

If you have spent any time writing modern RPGLE on IBM i, you’ve seen small sub-procedures, bloated sub-procedures, simple, complex and plain crazy sub-procedures.

Over the last week, I’ve been working hard to modernize a whole bunch of legacy RPG and SQL RPG code into cohesive, reusable sub-procedures and ‘Wow it’s been fun!’ playing with various other programmer’s old code. Legacy programmers who had a whole range of design ideas about how complex subprocedures, and subroutines, should be. Sprinkle in a complete lack of common naming standards, indicators rather than variables. some wonderful spaghetti code, and you will get the idea why I started thinking about:

What is the best modern way to design sub-procedures?

The #1 thing to think about when designing an RPGLE sub‑procedure is:

Define the procedure’s single, clear responsibility before you write a single line of code.

Everything else – parameters, return values, error handling, performance, and modularity flows from this one decision.

If you can’t describe the procedure’s purpose in one short sentence, it’s already doing too much.

What is the one thing this procedure is responsible for?

A strong sub‑procedure:

  • performs one conceptual task
  • has one reason to change
  • has one clear outcome
  • has a predictable interface
  • avoids mixing concerns (I/O, business rules, formatting, DB access)

This is the heart of cohesion, and cohesion is the foundation of every good RPGLE procedure.

Having said that, chasing tiny procedures is not the same as writing good ones.

One of the easiest traps in procedure-driven RPG is thinking that “small” automatically equals “well-designed”. Spoiler: it does not. Size and cohesion are related, but they are definitely not the same thing.

A truly cohesive procedure has one clear responsibility. It exists to answer one business question or perform one business action. When it starts trying to do two or three things at once, it stops being a helpful building block and turns into a maintenance headache that will haunt the next developer (probably you in six months time).

If you cannot name it cleanly, it is doing too much

Naming is one of the best cohesion tests we have in RPG. When you struggle to come up with a short, precise name for a procedure, that is usually your code trying to tell you something.

Bad (vague) names hide multiple responsibilities:

  • ProcessOrder
  • HandleInventoryUpdate
  • DoTheThingWithTheCustomer

Good (clear) names force you to keep things focused:

  • ValidateCustomerCredit
  • CalculateOrderTotal
  • CheckItemAvailability

If you find yourself writing a name like ProcessOrderAndUpdateStockAndSendEmail you are likely trying to do too many things at once! This is bad design because parameters become messy and bloated as you keep adding flags and options just to control all the different behaviors. Return values turn inconsistent because the same procedure sometimes returns a status code, sometimes a calculated result, and sometimes nothing at all depending on which hidden path it took. Testing becomes painful since you need a dozen different setups just to cover every possible combination of responsibilities. Reuse becomes almost impossible because calling the procedure from a new context risks triggering unwanted side effects you did not even know existed. Bugs creep in from those hidden side effects, and before you know it the once-simple procedure has grown into a mini-program all by itself instead of staying a clean, focused building block. That is exactly why cohesion matters so much in modern RPGLE.

But when you get it right, the procedure becomes:

  • easy to test
  • easy to reuse
  • easy to explain
  • easy to modernize
  • easy to wrap in a service program
  • easy to expose as an API

It’s the difference between “a chunk of code” and “a clean, modern RPG procedure”.

Business rules versus technical plumbing

Another classic cohesion killer is mixing business logic with technical details. A procedure that decides whether a customer order can be shipped should not also be opening files, handling commitment control, writing audit records, and formatting the email.

Keep them separate:

  • One procedure for the business decision (IsOrderShippable)
  • Another for the technical work (PersistShipmentRecord or SendOrderConfirmation)

Business procedures answer “what” and “why”. Technical procedures handle “how”. When you keep those responsibilities apart, your code becomes far easier to test, reuse, and explain to the new kid on the team.

Keep your procedures healthy

A healthy service program should represent one clear function or capability.

If you cannot describe what the entire service program does in one sensible sentence, it has probably grown too big and lost its way. Cohesive procedures are predictable. They do exactly what their name and interface promise and nothing more. That predictability is what lets your RPG applications scale cleanly over years instead of turning into a bowl of spaghetti code.

Code Sample – Legacy Subroutine vs Modernized Sub-Procedures

Here is a demonstration code sample of a typical old style RPG400 piece of code with a subroutine handling these three main functions:

  • Validate Credit
  • Calculate Order Total
  • Check Item Availability
     FQPRINT   O    F     132        PRINTER

     I* Order Line Structure (Demo Only)
     I            DS
     I                                        1   10  OITEM
     I                                       11   13  OQTY   0
     I                                       14   19  OPRICE 2

     C* Mainline
     C                     EXSR PROCESSORD
     C                     SETON                     LR

     C*-------------------------------------------------------------
     C*  Subroutine: PROCESSORD
     C*  This subroutine does EVERYTHING:
     C*     - Calculates order total
     C*     - Validates customer credit
     C*     - Checks item availability
     C*-------------------------------------------------------------
     C     PROCESSORD    BEGSR

     C* Demo order lines (normally read from file)
     C                     Z-ADD     0             TOTAL
     C                     MOVE      'WIDGET001'   OITEM
     C                     Z-ADD     5             OQTY
     C                     Z-ADD     1995          OPRICE
     C                     MVR       OQTY          QTY
     C                     MVR       OPRICE        PRICE
     C                     MULT      PRICE         LINEAMT
     C                     ADD       LINEAMT       TOTAL

     C                     MOVE      'GADGET002'   OITEM
     C                     Z-ADD     2             OQTY
     C                     Z-ADD     4950          OPRICE
     C                     MVR       OQTY          QTY
     C                     MVR       OPRICE        PRICE
     C                     MULT      PRICE         LINEAMT
     C                     ADD       LINEAMT       TOTAL

     C*-------------------------------------------------------------
     C* Validate Customer Credit (Hardcoded Limit)
     C*-------------------------------------------------------------
     C                     Z-ADD     25000         CREDITLIM
     C                     COMP      TOTAL         CREDITLIM
     C                     IFGT
     C                     MOVE      'N'           OKFLAG
     C                     ELSE
     C                     MOVE      'Y'           OKFLAG
     C                     ENDIF

     C                     IF        OKFLAG = 'N'
     C                     EXCPT     NOCREDIT
     C                     ENDIF

     C*-------------------------------------------------------------
     C* Check Item Availability (Hardcoded Inventory)
     C*-------------------------------------------------------------
     C                     MOVE      'WIDGET001'   ITEM
     C                     Z-ADD     10            ONHAND
     C                     Z-ADD     5             NEED
     C                     COMP      NEED          ONHAND
     C                     IFGT
     C                     EXCPT     NOITEM
     C                     ENDIF

     C                     MOVE      'GADGET002'   ITEM
     C                     Z-ADD     1             ONHAND
     C                     Z-ADD     2             NEED
     C                     COMP      NEED          ONHAND
     C                     IFGT
     C                     EXCPT     NOITEM
     C                     ENDIF

     C*-------------------------------------------------------------
     C* Print Total
     C*-------------------------------------------------------------
     C                     EXCPT     TOTALREC

     C                     ENDSR

     OQPRINT   E            NOCREDIT
     O                       'CUSTOMER CREDIT LIMIT EXCEEDED'

     OQPRINT   E            NOITEM
     O                       'ITEM NOT AVAILABLE IN INVENTORY'

     OQPRINT   E            TOTALREC
     O                       'ORDER TOTAL: '          10
     O                       TOTAL        12 2

Now what might this look like after some code modernization into a modern RPGLE program?

Let’s modernise this from the single subroutine into three separate and cohesive sub-procedures:

**FREE

// ============================================================================
// Program: Order Processing - Modernized with Subprocedures
// Description: Demonstrates proper separation of concerns using subprocedures
//              Refactored from monolithic PROCESSORD subroutine into three
//              focused subprocedures:
//              - CalculateOrderTotal
//              - ValidateCustomerCredit
//              - CheckItemAvailability
// ============================================================================

Ctl-Opt DftActGrp(*No) ActGrp(*New) Option(*SrcStmt:*NoDebugIO);

// File declarations
Dcl-F QPRINT Printer(132) Usage(*Output);

// Data structures
Dcl-Ds OrderLine Qualified;
  Item Char(10);
  Qty Packed(3:0);
  Price Packed(6:2);
End-Ds;

// Global variables
Dcl-S OrderTotal Packed(9:2);
Dcl-S CreditApproved Ind;

// ============================================================================
// Main Program Logic
// ============================================================================

// Step 1: Calculate the order total
OrderTotal = CalculateOrderTotal();

// Step 2: Validate customer credit limit
CreditApproved = ValidateCustomerCredit(OrderTotal);

// Step 3: Process order if credit approved
If CreditApproved;
  // Check availability for each item
  If CheckItemAvailability('WIDGET001': 5);
    If CheckItemAvailability('GADGET002': 2);
      // All items available, print order total
      Write TOTALREC;
    EndIf;
  EndIf;
Else;
  // Credit limit exceeded
  Write NOCREDIT;
EndIf;

*InLR = *On;
Return;

// ============================================================================
// Subprocedure: CalculateOrderTotal
// Purpose: Calculate the total amount for all order lines
// Returns: Total order amount as Packed(9:2)
// Notes: In production, this would read from an order file
//        For demo purposes, uses hardcoded order lines
// ============================================================================
Dcl-Proc CalculateOrderTotal;
  Dcl-Pi *N Packed(9:2) End-Pi;
  
  Dcl-S Total Packed(9:2) Inz(0);
  Dcl-S LineAmount Packed(9:2);
  
  // Process first order line - WIDGET001
  OrderLine.Item = 'WIDGET001';
  OrderLine.Qty = 5;
  OrderLine.Price = 19.95;
  LineAmount = OrderLine.Qty * OrderLine.Price;
  Total += LineAmount;
  
  // Process second order line - GADGET002
  OrderLine.Item = 'GADGET002';
  OrderLine.Qty = 2;
  OrderLine.Price = 49.50;
  LineAmount = OrderLine.Qty * OrderLine.Price;
  Total += LineAmount;
  
  Return Total;
End-Proc;

// ============================================================================
// Subprocedure: ValidateCustomerCredit
// Purpose: Validate if order total is within customer credit limit
// Parameters: pOrderTotal - The total amount to validate (Const)
// Returns: *On if approved, *Off if credit limit exceeded
// Notes: In production, credit limit would be retrieved from customer master
//        For demo purposes, uses hardcoded credit limit constant
// ============================================================================
Dcl-Proc ValidateCustomerCredit;
  Dcl-Pi *N Ind;
    pOrderTotal Packed(9:2) Const;
  End-Pi;
  
  Dcl-C CREDIT_LIMIT Const(250.00);
  Dcl-S Approved Ind Inz(*Off);
  
  If pOrderTotal <= CREDIT_LIMIT;
    Approved = *On;
  EndIf;
  
  Return Approved;
End-Proc;

// ============================================================================
// Subprocedure: CheckItemAvailability
// Purpose: Check if requested quantity is available in inventory
// Parameters: pItem - Item code to check (Const)
//            pQuantity - Quantity needed (Const)
// Returns: *On if available, *Off if insufficient inventory
// Notes: In production, this would query inventory database
//        For demo purposes, uses hardcoded inventory levels
//        Writes NOITEM record if item not available
// ============================================================================
Dcl-Proc CheckItemAvailability;
  Dcl-Pi *N Ind;
    pItem Char(10) Const;
    pQuantity Packed(3:0) Const;
  End-Pi;
  
  Dcl-S OnHand Packed(5:0);
  Dcl-S Available Ind Inz(*Off);
  
  // In real application, this would query inventory database
  // For demo, using hardcoded values matching original logic
  Select;
    When pItem = 'WIDGET001';
      OnHand = 10;
    When pItem = 'GADGET002';
      OnHand = 1;
    Other;
      OnHand = 0;
  EndSl;
  
  If pQuantity <= OnHand;
    Available = *On;
  Else;
    // Item not available - write error record
    Write NOITEM;
  EndIf;
  
  Return Available;
End-Proc;

// ============================================================================
// Output specifications (printer file records)
// ============================================================================
// Note: These would typically be in a separate DDS or DSPF file
// Shown here for completeness of the example

Modernised code is often longer in length, but I’m sure you will agree – it’s much cleaner to read.

Cohesive Code is also ripe code to be plucked and placed into a re-usable service program so it can be shared across the entire sales-order application.

A quick litmus test I use all the time

Before writing the sub-procedure I always ask myself:

  1. Can I describe this procedure’s purpose in one sentence?
  2. Would someone be surprised by anything it does?
  3. If I removed this procedure from the program, would its purpose still make sense on its own?
  4. Could I rewrite this in SQL or Node.js without changing its conceptual responsibility?

If the answer to any of these is “no”, the responsibility isn’t clear enough.

Here is the part that hurts: cohesion is not something you design once at the start of a project and then forget about. It is a decision you make every single time you touch a procedure.

The dangerous thought is always the same: “I will just add this one extra step here because I already have the data loaded.” Sometimes that is fine. More often it is the first tiny crack that eventually splits your nice clean procedure in half.

Next time you hear that voice, pause and ask yourself the uncomfortable question: “Is this still the same responsibility?” If the answer is no, take the slightly longer route and create a new, separate and cohesive sub-procedure. Your future self, and the poor soul who inherits the code when you’ve gone, will thank you.

Until next time, keep your procedures focused, your service programs coherent, and your RPG code maintainable.

NickLitten


IBM i Software Developer, Digital Dad, AS400 Anarchist, RPG Modernizer, Shameless Trekkie, Belligerent Nerd, Englishman Abroad and Passionate Eater of Cheese and Biscuits.

Nick Litten Dot Com is a mixture of blog posts that can be sometimes serious, frequently playful and probably down-right pointless all in the space of a day.

Enjoy your stay, feel free to comment and remember: If at first you don't succeed then skydiving probably isn't a hobby you should look into.

Nick Litten

related posts:

{"email":"Email address invalid","url":"Website address invalid","required":"Required field missing"}

Subscribe NOW
7-day free trial

Take This Course with ALL ACCESS

Unlock your Learning Potential with instant access to every course and all new courses as they are released.
 [ For Serious Software Developers only ]

Online Learning for IBM i Software Technology Professionals

“The more that you read, the more things you will know. The more that you learn, the more places you’ll go.” – Dr. Seuss

>