☕ Caffeine Overload: When RPG Programs Do Too Much

 Today's Coffee: Death Wish Coffee — billed as the world's strongest brew. Bold. Intense. Not for the faint of heart. Like overloaded RPG programs, it promises raw power, but abuse it and you're left with jitters, crashes, and regret.


The Monolithic RPG Program: A Death Wish in Code

Every IBM i shop has one (or ten):

  • 6,000+ lines of tangled RPG, half fixed-form, half free-form.
  • Indicators 01–99 flickering like a Christmas tree.
  • Database reads, writes, and updates scattered between screen handling and report logic.
  • Business rules from the 1990s patched with quick fixes in 2005 and "just one more IF" in 2020.

It runs, sure. But every time you touch it, your developers whisper: "This program is cursed."

Like a double Death Wish espresso before midnight — it feels like power, but it's really just technical debt in disguise.


The Anatomy of a Monster Program

Let me paint you a picture. You inherit a program called CUSTMAINT. Innocent enough name, right? Wrong.

This beast handles:

  • Customer maintenance screens (3 different formats)
  • Order entry and validation
  • Credit limit checking and approval workflows
  • Invoice generation and printing
  • Email notifications to sales reps
  • Automated backup customer creation
  • Historical reporting (last 18 months of activity)
  • Integration with the shipping system
  • Tax calculation for 12 different jurisdictions
  • Loyalty points calculation and redemption

The kicker? It's a single source member with 47 subroutines, 23 copybook includes, and comments like "// TODO: Fix this later - GV 03/15/2018"

Sound familiar? That's because every IBM i developer has met this program's cousin, brother, or evil twin.


Why RPG Monoliths Still Exist

1. Legacy Mindset

Old RPG II/III days demanded cramming everything into one program. Memory and disk were expensive, so efficiency meant monoliths. Back then, a 64K program was huge. Today's systems laugh at 64K, but our programming habits haven't evolved.

2. Shortcut Culture

Adding an IF statement or an extra subroutine was faster than designing new modules. "I'll just add this one little feature..." becomes the famous last words of clean architecture.

3. The Hero Developer Problem

"Only Bob understands this program" becomes an excuse for not refactoring. Bob becomes a single point of failure, and when Bob retires, you're left with archaeological code excavation.

4. Fear of Change

"If it compiles, don't touch it." Until it doesn't. Then you're debugging at 2 AM wondering why changing a display field broke the invoice calculation routine.

5. Deadline Pressure

Business needs features yesterday. Clean design takes time. Duct tape and bubble gum take minutes. Guess which one wins when the VP is breathing down your neck?


Warning Signs: How to Spot "Death Wish" RPG

Compilation Red Flags

  • The compile takes so long you actually do make another pot of coffee.
  • The listing is longer than a Stephen King novel.
  • You need to increase your job's memory allocation just to compile it.

Developer Behavior

  • Developers fight over who has to debug it.
  • New team members avoid it like expired milk.
  • Senior developers make "sacrifice to the code gods" jokes before touching it.
  • Code reviews become group therapy sessions.

Functional Chaos

  • The program updates customers, creates invoices, prints reports, and emails sales reps — all in the same cycle.
  • Adding a single field means tracing 500 lines just to avoid breaking something.
  • Error messages like "Something went wrong in routine XYZ" tell you nothing.
  • The program has three different ways to do the same calculation.

Business Impact

  • Simple changes take weeks instead of hours.
  • Testing requires a PhD in program archaeology.
  • Onboarding new developers becomes a six-month commitment.
  • Business rules are buried so deep that domain experts can't validate them.

How to Fix It: Brew Small, Brew Strong

Death Wish coffee is powerful because it's brewed right, not because it's dumped into one massive pot. RPG code should be the same:

1. Separate Concerns

Database Access: Move into service programs or dedicated procedures.

// Instead of scattered CHAINs throughout your program

dcl-pr getCustomerData likeds(customer_t);

  custId packed(7: 0) const;

end-pr;

 

dcl-pr updateCustomerStatus ind;

  custId packed(7: 0) const;

  newStatus char(1) const;

end-pr;

Business Logic: Keep pure — no UI or report clutter.

// Clean business rules, testable and reusable

dcl-pr calculateDiscountPercent packed(5: 2);

  orderTotal packed(11: 2) const;

  customerType char(10) const;

  loyaltyPoints packed(9: 0) const;

end-pr;

Presentation: Screens, APIs, reports — separate layer.

// UI concerns isolated from business logic

dcl-pr displayCustomerScreen;

  customer likeds(customer_t) const;

  mode char(10) const; // ADD, CHANGE, DISPLAY

end-pr;

2. Refactor to Free-Form

Converting from fixed-form forces clarity. You'll find dead code, duplicate logic, and hidden assumptions. Free-form syntax supports modular design — procedures, subprocedures, clean parameters.

Before (Fixed-Form Madness):

C     CustId        CHAIN     CUSTFILE

C                   IF        %FOUND(CUSTFILE)

C                   EVAL      WS_Name = CF_Name

C                   EVAL      WS_Addr = CF_Addr

C     OrderId       CHAIN     ORDERFILE

C                   IF        %FOUND(ORDERFILE)

C                   EVAL      WS_Total = OF_Total

C                   ENDIF

C                   ENDIF

After (Free-Form Clarity):

customer = getCustomer(custId);

if customer.id > 0;

  orders = getCustomerOrders(custId);

  displayCustomerDetail(customer : orders);

endif;

3. Use SQL for Set Logic

Don't loop through 100,000 records with READE. Replace nested CHAINs with JOINs and aggregations.

Before (Loop of Death):

// Reading every invoice to calculate customer totals

setll custId INVFILE;

reade custId INVFILE;

dow not %eof(INVFILE);

  totalDue += INV_Amount;

  if INV_Status = 'OVERDUE';

    overdueCount += 1;

  endif;

  reade custId INVFILE;

enddo;

After (SQL Power):

exec SQL

  SELECT COALESCE(SUM(INV_AMOUNT), 0),

         COALESCE(SUM(CASE WHEN INV_STATUS = 'OVERDUE'

                          THEN 1 ELSE 0 END), 0)

    INTO :totalDue, :overdueCount

    FROM INVFILE

   WHERE INV_CUSTID = :custId;

4. Modularize with Service Programs

Customer utilities in one service program:

// CUSTSRV - Customer Service Program

dcl-pr validateCustomer ind export;

  customer likeds(customer_t) const;

end-pr;

 

dcl-pr calculateCreditLimit packed(11: 2) export;

  custId packed(7: 0) const;

end-pr;

Orders in another:

// ORDSRV - Order Service Program 

dcl-pr createOrder likeds(order_t) export;

  custId packed(7: 0) const;

  items likeds(orderItem_t) dim(999) const;

end-pr;

Logging and error handling in a reusable module:

// LOGSRV - Logging Service Program

dcl-pr logError export;

  program char(10) const;

  procedure char(30) const;

  message char(256) const;

end-pr;

This makes code testable, portable, and easier to maintain.


Real-World Refactoring Strategy

Phase 1: Stop the Bleeding

  • No new features in the monolith
  • All new functionality goes into separate modules
  • Establish coding standards for new components

Phase 2: Extract and Isolate

  • Move database I/O into service programs
  • Extract business calculations into pure functions
  • Create dedicated error handling

Phase 3: Replace Core Functions

  • Rebuild critical paths with clean architecture
  • Migrate data validation to service programs
  • Implement comprehensive logging

Phase 4: Sunset the Monster

  • Route new transactions through modern components
  • Keep legacy for historical data only
  • Plan retirement ceremony (seriously, celebrate wins)

Real-World Before/After

Before (Monolithic RPG):

C     READ      CustFile

C             IF        NOT %EOF

C     CustId    CHAIN     CustOrders 

C             IF        %FOUND

C             EXFMT     CustScreen

C             UPDATE    CustFile

C             WRITE     Invoice

C             CALL      'EmailNotify'

C             ENDIF

C             ENDIF

After (Decomposed RPG + SQL):

// Get customer data

custData = getCustomer(custId);

 

if custData.status = 'ACTIVE';

  // Get orders with SQL

  orders = getOrdersByCustomer(custId);

 

  // Apply business logic

  discount = calculateDiscount(custData : orders);

 

  // Process transaction

  invoice = createInvoice(custId : orders : discount);

 

  // Handle notifications

  callp notifyCustomerService(custId : invoice);

  callp logCustomerAccess(custId);

endif;

Business Impact:

  • Before: 47 lines, 15 active indicators, 3 developers afraid to touch it
  • After: 12 lines, clear intent, testable components, junior developers can contribute

The Testing Problem (And Solution)

Monolithic programs are testing nightmares. How do you unit test a 6,000-line program that reads files, updates databases, sends emails, and prints reports?

Answer: You can't. But you can test modular components:

// Testable business logic

dcl-pr test_calculateDiscount;

end-pr;

 

dcl-proc test_calculateDiscount;

  dcl-s customer likeds(customer_t);

  dcl-s expectedDiscount packed(5: 2);

  dcl-s actualDiscount packed(5: 2);

 

  // Arrange

  customer.type = 'PREMIUM';

  customer.loyaltyPoints = 5000;

  expectedDiscount = 15.00;

 

  // Act

  actualDiscount = calculateDiscount(customer);

 

  // Assert

  assert(actualDiscount = expectedDiscount :

         'Premium customer discount failed');

end-proc;

Clean modules enable automated testing, which enables confident refactoring, which enables sustainable development.


The Manager's Role: Stop Brewing Monsters

Developers don't set standards — managers do. If you allow "just one more fix" in the giant monolith, you're approving technical debt at scale.

Code Reviews: Block PRs that add to the monster.

Insist on modularization. Make it clear that adding to technical debt requires explicit approval and documented business justification.

Refactoring Time: Budget it.

Technical debt is business risk. Schedule "architecture weeks" where teams focus solely on decomposing monoliths. Track velocity improvements and reduced bug counts.

Standards: Enforce patterns for I/O, error handling, and program size.

  • Maximum program size: 1,000 lines
  • Dedicated service programs for all database access
  • Standardized error handling and logging
  • Mandatory code reviews for programs over 500 lines

Coaching: Teach juniors why smaller programs scale better.

Instead of letting them repeat bad habits, show them the business impact of clean architecture. Pair them with senior developers during refactoring exercises.

Metrics That Matter:

  • Time to implement new features
  • Bug count per 1,000 lines of code
  • Developer onboarding time
  • Code coverage in automated tests

Common Refactoring Mistakes (And How to Avoid Them)

Mistake 1: Big Bang Rewrite

Wrong: "Let's rewrite the entire CUSTMAINT program from scratch." Right: Extract one function at a time, test thoroughly, then move to the next.

Mistake 2: Over-Engineering

Wrong: Creating 47 service programs for a simple customer maintenance function. Right: Start with clear separation of concerns, then optimize based on actual complexity.

Mistake 3: No Migration Plan

Wrong: Building new components without a path to retire legacy code. Right: Plan the sunset from day one. Legacy code should have an expiration date.

Mistake 4: Ignoring Performance

Wrong: Assuming modular code is automatically slower. Right: Profile before and after. Clean SQL often outperforms nested loops by orders of magnitude.


Final Sip

Death Wish Coffee is marketed as the world's strongest brew. One cup is bold. Two cups is a thrill. But guzzling it nonstop? That's a death wish.

RPG programs are the same. A powerful utility is great. A monolith that tries to handle everything? That's a recipe for developer burnout, onboarding failure, and technical debt that compounds like interest on a credit card.

The strongest programs aren't the biggest ones. They're the ones that do one thing exceptionally well, play nicely with others, and can be understood by developers who didn't write them.

Keep your RPG lean. Brew small. Brew strong.

Don't let your codebase crash harder than a triple espresso at midnight.

Your future self (and your team) will thank you for choosing clarity over complexity, modularity over monoliths, and sustainable architecture over short-term fixes.

Because in the end, the best programs aren't the ones that impress with their size — they're the ones that deliver business value without giving anyone nightmares.


Follow The RPG Blend for spicy insights on RPG modernization, AI in the IBM i shop, and leadership strategies that actually work.

Comments