• 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:

    • Reddit
    • Facebook
    • LinkedIn
    • Twitter
    • Email

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

    Sponsored by
    Computer Keyes

    Fax Directly from your IBM i

    KeyesFax is a full function automated IBM i fax system. Spooled files are burst by fax number and auto transmitted with overlays.  It combines both a send and receive facsimile processing system with a complete image package.

    The fax software will edit, send, receive, display, print, and track fax documents or images using any standard IBM i without additional expensive hardware, software or subscriptions.

    Computer Keyes has been developing Software Solutions since 1978!

    www.computerkeyes.com

    Share this:

    • Reddit
    • Facebook
    • LinkedIn
    • Twitter
    • 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 Reply Cancel 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

  • Liam Allan Shares What’s Coming Next With Code For IBM i
  • From Stable To Scalable: Visual LANSA 16 Powers IBM i Growth – Launching July 8
  • VS Code Will Be The Heart Of The Modern IBM i Platform
  • The AS/400: A 37-Year-Old Dog That Loves To Learn New Tricks
  • IBM i PTF Guide, Volume 27, Number 25
  • Meet The Next Gen Of IBMers Helping To Build IBM i
  • Looks Like IBM Is Building A Linux-Like PASE For IBM i After All
  • Will Independent IBM i Clouds Survive PowerVS?
  • Now, IBM Is Jacking Up Hardware Maintenance Prices
  • IBM i PTF Guide, Volume 27, Number 24

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