• The Four Hundred
  • Subscribe
  • Media Kit
  • Contributors
  • About Us
  • Contact
Menu
  • The Four Hundred
  • Subscribe
  • Media Kit
  • Contributors
  • About Us
  • Contact
  • Guru: Refactoring into Routines

    May 7, 2018 Ted Holt

    In RDi and Refactoring, I illustrated the process of refactoring by taking code of a very old style and converting it little by little into something modern. I promised to write more about the subject, and today I fulfill that promise.

    The things I did in that first article — removing indicators, removing the COMP op code, removing GOTO, and renaming variables — are great, but they are not the only refactoring techniques. One of the best ways to refactor is to create new routines or improve existing routines, especially routines that can stand alone.

    To illustrate, I’ll begin with the next-to-last version of the discount calculations from the previous article.

    FREFACTDT  IF   E           K DISK
    FQSYSPRT   O    F  132        PRINTER
     *
    D DisPct          s              3p 2
     *
    C                   READ      REFACTDT                               LR
    C     *INLR         DOWEQ     *OFF
     *
    C                   select
    C                   when      discod = 0
    C                   eval      dispct = *zero
    C                   when      cuscls = 'C'
    C                   eval      dispct = *zero
    C                   when      cuscls = 'A'
    C                   eval      dispct = 0.08
    C                   when      (discod = 1 and qty >= 12)
    C                             or (discod = 2 and qty >= 24)
    C                   eval      dispct = 0.05
    C                   when      cuscls = 'B'
    C                   eval      dispct = 0.04
    C                   other
    C                   eval      dispct = *zero
    C                   endsl
    C*
    C                   EXCEPT    DTL
    C*
    C                   READ      REFACTDT                               LR
    C                   ENDDO
    OQSYSPRT   E            DTL         1
    O                       KEY
    O                       CUSCLS            +003
    O                       DISCOD            +003
    O                       QTY           1   +003
    O                       DISPCT        1   +003
    

    Let’s make a subroutine of the code that calculates a discount factor. In this case, that’s a single SELECT structure. (If this were a real-world example, there would be more to it than that.)

    C                   READ      REFACTDT                               LR
    C     *INLR         DOWEQ     *OFF
     *
    C                   exsr      CalcDiscount
    C                   EXCEPT    DTL
    C*
    C                   READ      REFACTDT                               LR
    C                   ENDDO
    
    C     CalcDiscount  begsr
    C                   select
    C                   when      discod = 0
    C                   eval      dispct = *zero
    C                   when      cuscls = 'C'
    C                   eval      dispct = *zero
    C                   when      cuscls = 'A'
    C                   eval      dispct = 0.08
    C                   when      (discod = 1 and qty >= 12)
    C                             or (discod = 2 and qty >= 24)
    C                   eval      dispct = 0.05
    C                   when      cuscls = 'B'
    C                   eval      dispct = 0.04
    C                   other
    C                   eval      dispct = *zero
    C                   endsl
    C                   endsr
    

    Has that accomplished anything? Yes, it has, because it leads to another improvement — conversion of the subroutine into a subprocedure.

    When converting to a subprocedure, the main task is to eliminate all global variables. To do so, change each variable to a parameter or a local variable. You do not have to rename them, because the compiler can distinguish between global and local variables.

    Here’s the subprocedure. Notice that customer class, discount code, and quantity are input parameters, and discount percentage is a local variable.

    dcl-proc CalcDiscount;
     dcl-pi *n      packed (3:2);
        CusCls      char   (1)   const;
        DisCod      packed (1)   const;
        Qty         packed (5)   const;
     end-pi;
     dcl-s  DisPct  packed (3:2);
     select;
        when discod = 0;
           dispct = *zero;
        when cuscls = 'C';
           dispct = *zero;
        when      cuscls = 'A';
           dispct = 0.08;
        when    (discod = 1 and qty >= 12)
             or (discod = 2 and qty >= 24);
           dispct = 0.05;
        when  cuscls = 'B';
           dispct = 0.04;
        other;
           dispct = *zero;
        endsl;
        return DisPct;
    end-proc;
    

    Also, since this routine calculates a single value, we can make it serve as a function. That is, it returns a value that can be tested or assigned to a variable, as any other value can.

    dcl-pi *n      packed (3:2);
    

    Here’s the subprocedure with the calling code. Notice that the main routine uses EVAL to invoke the subprocedure and assign the returned value to global variable DisPct.

    C                 READ     REFACTDT                               LR
    C     *INLR       DOWEQ    *OFF
     *
    C                 eval     DisPct = CalcDiscount (CusCls: DisCod: Qty);
    C                 EXCEPT   DTL
    C*
    C                 READ     REFACTDT                               LR
    C                 ENDDO
    
     . . . O specs omitted . . .
    
       dcl-proc CalcDiscount;
        dcl-pi *n      packed (3:2);
           CusCls      char   (1)   const;
           DisCod      packed (1)   const;
           Qty         packed (5)   const;
        end-pi;
        dcl-s  DisPct  packed (3:2);
        select;
           when discod = 0;
              dispct = *zero;
           when cuscls = 'C';
              dispct = *zero;
           when      cuscls = 'A';
              dispct = 0.08;
           when    (discod = 1 and qty >= 12)
                or (discod = 2 and qty >= 24);
              dispct = 0.05;
           when  cuscls = 'B';
              dispct = 0.04;
           other;
              dispct = *zero;
           endsl;
           return DisPct;
      end-proc;
    

    Now that the subprocedure stands on its own, we can rename those short identifiers to improve the readability. This is another place where RDi comes in handy.

    In the previous article, I showed how to use RDi to rename a variable. What I didn’t tell you is that RDi is smart enough to rename a local variable without renaming other variables of the same name. In the following version of the source code, I renamed DisPct, CusCls, DisCod, and Qty in the subprocedure. RDi did not change those names in the main routine. Had it done so, we’d have had a mess, because the six-character-or-shorter names come from the input file.

    D DisPct          s              3p 2
     *
    C                   READ      REFACTDT                               LR
    C     *INLR         DOWEQ     *OFF
     *
    C                   eval      DisPct = CalcDiscount (CusCls: DisCod: Qty)
    
    C                   EXCEPT    DTL
    C*
    C                   READ      REFACTDT                               LR
    C                   ENDDO
    
    . . . O specs omitted . . .
    
    
      dcl-proc CalcDiscount;
    
        dcl-pi *n             packed (3:2);
           inCustomerClass    char   (1)   const;
           inDiscountCode     packed (1)   const;
           inQuantity         packed (5)   const;
        end-pi;
    
        dcl-s  DiscountFactor  packed (3:2);
    
        select;
           when inDiscountCode = 0;
              DiscountFactor = *zero;
    
           when inCustomerClass = 'C';
              DiscountFactor = *zero;
    
           when inCustomerClass = 'A';
              DiscountFactor = 0.08;
    
           when    (inDiscountCode = 1 and inQuantity >= 12)
                or (inDiscountCode = 2 and inQuantity >= 24);
              DiscountFactor = 0.05;
    
           when  inCustomerClass = 'B';
              DiscountFactor = 0.04;
    
           other;
              DiscountFactor = *zero;
    
           endsl;
    
           return DiscountFactor;
    
      end-proc;
    

    The hardest part is far behind us.

    Now that the discount routine is in a subprocedure with no external dependencies, we can move it into a service program. We create a source physical file member for the module.

    ctl-opt nomain;
    
    /copy prototypes,SALESRTNS
    
    dcl-proc CalcDiscount   export;
    
      dcl-pi *n             packed (3:2);
         inCustomerClass    char   (1)   const;
         inDiscountCode     packed (1)   const;
         inQuantity         packed (5)   const;
      end-pi;
    
      dcl-s  DiscountFactor  packed (3:2);
    
      select;
         when inDiscountCode = 0;
            DiscountFactor = *zero;
    
         when inCustomerClass = 'C';
            DiscountFactor = *zero;
    
           when inCustomerClass = 'A';
              DiscountFactor = 0.08;
    
           when    (inDiscountCode = 1 and inQuantity >= 12)
                or (inDiscountCode = 2 and inQuantity >= 24);
              DiscountFactor = 0.05;
    
           when  inCustomerClass = 'B';
              DiscountFactor = 0.04;
    
           other;
              DiscountFactor = *zero;
    
           endsl;
    
           return DiscountFactor;
    
      end-proc;
    

    And one for the prototype. . .

    dcl-pr  CalcDiscount    packed (3:2);
       inCustomerClass    char   (1)   const;
       inDiscountCode     packed (1)   const;
       inQuantity         packed (5)   const;
    end-pr;
    

    Then we use CRTRPGMOD to create the module and CRTSRVPGM to create the service program.

    CRTRPGMOD MODULE(MYLIB/SALESRTNS) 
              SRCFILE(MYLIB/QRPGLESRC) SRCMBR(SALES)
    CRTSRVPGM SRVPGM(SALES) MODULE(*LIBL/SALES)
    

    What’s next? That depends. What would you like to do? Here are some ideas:

    (1) You could expand the service program by adding a subprocedure to calculate the price a customer has to pay for an item.

    dcl-proc CalcCustomerPrice      export;                          
                                                                     
      dcl-pi *n             packed (5:2);                            
         inCustomerID       packed (7)   const;                      
         inItemID           char   (6)   const;                      
         inQuantity         packed (5)   const;                      
      end-pi;                                                        
                                                                     
      dcl-s  CustClass      char(1);                                 
      dcl-s  Price          packed(5:2);                             
      dcl-s  DiscountCode   packed(1);                               
                                                                     
      exec sql select CusCls into :CustClass from refactcus          
                where Account = :inCustomerID;                       
                                                                     
      exec sql select Price, DisCod                                  
                 into :Price, :DiscountCode from refactitem          
                where ItNbr = :inItemID;                             
                                                                     
      return (1 - CalcDiscount (CustClass: DiscountCode: inQuantity))
                * Price;                                             
    end-proc;
    

    (2) You could create an SQL function that returns the unit price that a customer pays for an item.

    create or replace function CustomerPrice
       (dec(7), char(6), dec(5))
       returns dec(5,2)
       language rpgle
       parameter style general
       deterministic
       reads sql data
       returns null on null input
       program type sub
       no final call
       external name 'MYLIB/SALES(CALCCUSTOMERPRICE)'
    

    And use it in queries.

    select OrderNbr, Customer, Item, Qty,
           CustomerPrice (Customer, Item, Qty) as UnitPrice
     FROM salesdata
    

    (3) You could use these routines in Web services.

    And so on and so forth.

    Systemwide, the CalcDiscount routine would handle all customer discounts. A change in policy would require only a change in one routine in a service program.

    And to think it all began with some spaghetti code written in an ancient version of RPG.

    Custom software is a valuable asset and should not be thrown away without good reason. Refactoring puts new life into old code and carries it into the future.

    RELATED STORY

    RDi and Refactoring

    Share this:

    • Share on Reddit (Opens in new window) Reddit
    • Share on Facebook (Opens in new window) Facebook
    • Share on LinkedIn (Opens in new window) LinkedIn
    • Share on X (Opens in new window) X
    • Email a link to a friend (Opens in new window) Email

    Tags: Tags: 400guru, FHG, Four Hundred Guru, IBM i, RDi, RPG, SQL

    Sponsored by
    FalconStor

    Simplify Secure Offsite Data Protection for IBM Power with FalconStor Habanero™

    IBM i teams are under growing pressure to ensure data is protected, recoverable, and compliant—without adding complexity or disrupting stable environments.

    FalconStor Habanero™ provides secure, fully managed offsite data protection purpose-built for IBM Power. It integrates directly with existing IBM i backup tools and processes, enabling reliable offsite copies without new infrastructure, workflow changes, or added operational overhead.

    By delivering and managing the service end-to-end, FalconStor helps organizations strengthen cyber resilience, improve disaster recovery readiness, and meet compliance requirements with confidence. Offsite copies are securely maintained and available when needed, supporting recovery, audits, and business continuity.

    FalconStor Habanero offers a straightforward way to modernize offsite data protection for IBM i: focused on simplicity, reliability, and resilience.

    Learn More

    Share this:

    • Share on Reddit (Opens in new window) Reddit
    • Share on Facebook (Opens in new window) Facebook
    • Share on LinkedIn (Opens in new window) LinkedIn
    • Share on X (Opens in new window) X
    • Email a link to a friend (Opens in new window) Email

    As I See It: The Long And Intertwined Road IBM Wheels And Deals To Boost Power System Sales

    4 thoughts on “Guru: Refactoring into Routines”

    • jonboy49 says:
      May 7, 2018 at 10:55 am

      I love the step by step approach Ted – nice.

      Personally I would have taken the opportunity to replace the SELECT operation with IF/ELSEIF. Reason is that my brain thinks that SELECT should reference different conditions of the same variable. In your example multiple variables are being tested and that breaks my rule and the “right” choice is IF/ELSEIF. I find that if I stick to this approach, reading the code is easier as I know in advance what to expect.So my version (assuming no typos!) would be:

      If ( inDiscountCode = 0 );
      DiscountFactor = *zero;
      ElseIf ( inCustomerClass = ‘C’ );
      DiscountFactor = *zero;
      ElseIf ( inCustomerClass = ‘A’ );
      DiscountFactor = 0.08;
      ElseIf ( inDiscountCode = 1 and inQuantity >= 12 )
      or ( inDiscountCode = 2 and inQuantity >= 24 );
      DiscountFactor = 0.05;
      ElseIf ( inCustomerClass = ‘B’ );
      DiscountFactor = 0.04;
      Else DiscountFactor = *zero;
      EndIf;

      Reply
    • jonboy49 says:
      May 7, 2018 at 10:56 am

      Apologies for the lack of code formatting – indents were there when I typed it honest!

      Reply
    • Rocky Marquiss says:
      May 11, 2018 at 3:20 pm

      Very good! I do have a suggestion. It’s not a big deal, it’s really a personal preference of style that I think adds.

      Use the names throughout – for example:

      dcl-proc CalcDiscount;

      dcl-pi CalcDiscount packed (3:2);
      inCustomerClass char (1) const;
      inDiscountCode packed (1) const;
      inQuantity packed (5) const;
      end-pi CalcDiscount;

      ….

      end-proc CalcDiscount;

      And:

      dcl-pr CalcDiscount packed (3:2);
      inCustomerClass char (1) const;
      inDiscountCode packed (1) const;
      inQuantity packed (5) const;
      end-pr CalcDiscount ;

      I do this with my DCL-DS as well:

      DCL-DS dsname QUALIFIED;
      xyz char(5);
      zyx char(5);
      END-DS dsname;

      This just more self documenting. Also – creating the prototype then is simply copying the DCL-PI through END-PI statements, change the PI to a PR and you’re done – ie.

      dcl-pi CalcDiscount packed (3:2);
      inCustomerClass char (1) const;
      inDiscountCode packed (1) const;
      inQuantity packed (5) const;
      end-pi CalcDiscount;

      becomes:

      dcl-pr CalcDiscount packed (3:2);
      inCustomerClass char (1) const;
      inDiscountCode packed (1) const;
      inQuantity packed (5) const;
      end-pr CalcDiscount;

      It takes 4 seconds to create your PR statements.

      When scrolling through the code it’s clear what the END-PROC belongs to because it has the name of the procedure. The more you self-document the better.

      I realize that this isn’t within the scope of the article – but it does, IMO, help with a philosophical style.

      The coding style should, IMO, have the idea of the code being self-documenting as much as possible.

      As you have coded – subprocedures should have one entry point with all parameters defined with one exit point. what is retuned is known. No external dependencies – ie no global variables.

      Reply
    • Rocky Marquiss says:
      May 11, 2018 at 3:21 pm

      BTW – there would be indentation but the reply doesn’t have that option – as far as I can tell…

      Reply

    Leave a ReplyCancel reply

TFH Volume: 28 Issue: 34

This Issue Sponsored By

  • UCG TECHNOLOGIES
  • Profound Logic Software
  • Software Concepts
  • Computer Keyes
  • Manta Technologies

Table of Contents

  • Keep Your IBM i Baby, Not The Bathwater
  • IBM Wheels And Deals To Boost Power System Sales
  • Guru: Refactoring into Routines
  • As I See It: The Long And Intertwined Road
  • Spring Cleaning In IBM i Land

Content archive

  • The Four Hundred
  • Four Hundred Stuff
  • Four Hundred Guru

Recent Posts

  • Inside The Encryption Key Management Changes In IBM i 7.6
  • FalconStor Moved To The Blue Lagoon, And Is Poised For Growth Because Of It
  • Guru: Claude’s SQL Tip
  • Astera Makes Extracting Legacy Report Data an AI Specialty
  • IBM i PTF Guide, Volume 28, Number 27
  • Welcoming The New IBM i Chief Architect And Other New Top Brass
  • A Deep Dive Into That Power S1112 Entry Power11 Server
  • Guru: Beyond Three-Part Naming – Running SQL Across Remote IBM i Systems
  • How IBM Bolstered IBM i Resilience In The Summer Tech Refreshes
  • IBM i PTF Guide, Volume 28, Number 26

Subscribe

To get news from IT Jungle sent to your inbox every week, subscribe to our newsletter.

Pages

  • About Us
  • Contact
  • Contributors
  • Four Hundred Monitor
  • IBM i PTF Guide
  • Media Kit
  • Subscribe

Search

Copyright © 2025 IT Jungle