Guru: Growing A More Productive Team With Procedure Driven RPG
June 24, 2024 Gregory Simmons
There are many great benefits to procedure driven RPG, and I have covered many of them in my previous articles. In this article, I want to share with you what I believe is the biggest benefit to implementing procedure driven RPG in your shop; procedures, when written and implemented properly, are reusable.
When you’ve written a new procedure that expertly tackles one task, you should be proud of the accomplishment. But don’t keep that in your program. Export it in a service program for your whole team to use! Now, undoubtedly, you will find yourself at one point thinking: “Yeah, but I can’t see anyone else in our shop needing this procedure.” And you may be right. But the key to cashing in on this benefit is to start writing procedures that are focused on one task and export them all from service programs. ALL of them.
Let’s walk through how to take a procedure in a program and export it in a service program. Here I’ve written a sample program that will retrieve the day of the week (1, 2, 3, etc.).
**Free Ctl-Opt Main(Process_Rebate_Checks); Ctl-Opt ActGrp(*Caller); Dcl-Proc Process_Rebate_Checks; Dcl-s nDayOfWeek Uns(3); // Processing Rebate Check logic… nDayOfWeek = get_day_of_week(%Date()); // Remaining Rebate Check Logic… On-Exit; End-Proc Process_Rebate_Checks; Dcl-Proc get_day_of_week; Dcl-Pi get_day_of_week Uns(3); InputDate Date Const; End-Pi; Dcl-s result Uns(3); Test(e) InputDate; If %Error; Return -1; Endif; Exec SQL Set :result = DayOfWeek(:InputDate); If SQLState <> SQL_Success; Return -1; EndIf; Return result; On-Exit; End-Proc get_day_of_week;
In the above example, I needed to know what day of the week was, so I am accomplishing that in the procedure get_day_of_week. It’s pretty simple. I check that the date passed in is a valid date, and then use the SQL function DayOfWeek to get the number and return the answer.
Now, let’s move this procedure to a new service program called dateutils.
First, we’ll create a new copybook to hold the prototype. This is key, as it lets everyone else who wants to use this procedure use the /copy compiler directive to bring in all of the procedure interfaces into their program. Another benefit of putting procedure types in copy books is that if/when a procedure prototype changes, your program may not need to be recompiled. For example, if someone added a parameter with options(*Nopass), no recompiling would be necessary. So, the copy book, called dateutilsc, looks like this:
**Free Dcl-Pi dateutils_get_day_of_week Uns(3); InputDate Date Const; End-Pi;
Next, we’ll create a new source member which will later be compiled into a module.
**Free Ctl-Opt NoMain; /copy dateutilsc Dcl-Proc dateutils_get_day_of_week; Dcl-Pi dateutils_get_day_of_week Uns(3); InputDate Date Const; End-Pi; Dcl-s result Uns(3); Test(e) InputDate; If %Error; Return -1; Endif; Exec SQL Set :result = DayOfWeek(:InputDate); If SQLState <> SQL_Success; Return -1; EndIf; Return result; On-Exit; End-Proc dateutils_get_day_of_week;
The /copy of the copybook dateutilsc, again, brings in the prototype of the procedure. By default, the RPG compiler looks for that copybook in QRPGLESRC in your library list. In my previous shop, we kept our copy books in QCPYSRC. Either will work fine, just remember that if you choose to store your copy books in a source physical file other than QRPGLESRC or in a different library, you’ll need to specify the /copy with one of these formats:
/copy libraryname/filename,membername /copy filename,membername /copy membername
The only difference for the rest of the procedure, which is totally optional, is that I like naming the procedures in a service program such that they all start with the name of the service program. In that way, when reading code that calls a procedure in a service program, you automatically know in which service program to find that procedure.
Next, this is not necessary, but I find it nicer to create binder source. This simply tells the compiler which procedures or ‘symbols’ to export. I created a dateutils.bnd source in QBNDSRC:
STRPGMEXP PGMLVL(*CURRENT) LVLCHK(*NO) EXPORT SYMBOL(dateutils_get_day_of_week) ENDPGMEXP
This is a very brief introduction to binder language. You can read more on binder language here: https://www.ibm.com/docs/en/i/7.4?topic=concepts-binder-language
Okay, now compile the module. Then create the service program.
Now, the program I pulled this procedure out of looks like this:
**Free Ctl-Opt Main(Process_Rebate_Checks); Ctl-Opt ActGrp(*Caller); Ctl-Opt Bnddir(Utils); /copy dateutilsc Dcl-Proc Process_Rebate_Checks; Dcl-s nDayOfWeek Uns(3); // Processing Rebate Check logic… nDayOfWeek = dateutils_get_day_of_week(%Date()); // Remaining Rebate Check Logic… On-Exit; End-Proc Process_Rebate_Checks;
Note that the only changes are that I added the Ctl-Opt at the top to tell the compiler that it can check the Utils binding directory for the procedures I’m calling. I then added the compiler directive /copy of dateutilsc, changed the name of the procedure from get_day_of_week to dateutils_get_day_of_week and deleted the procedure as it now lives in the service program for all to utilize.
This next step, while not necessary, is a good practice, especially when your programs start needing to call procedures in multiple service programs. In a nutshell, this is just providing a list of service programs and/or modules where the compiler can go find the procedures your program is referencing. If you don’t want to use binding directories, you just need to specify which service programs to reference on the compile command. If you do this, I think you will pretty quickly find that binding directories are the way to go. Creating the binding directory is pretty simple to set up in just two steps:
Create the binding directory: CRTBNDDIR BNDDIR(MYLIB/UTILS) TEXT(‘Binding directory for Utility Service Programs’)
Add the service program to the newly created binding directory: ADDBNDDIRE BNDDIR(MYLIB/UTILS) OBJ((MYLIB/DATEUTILS *SRVPGM *DEFER))
Again, this is a very quick introduction to binding directories. If this is your first exposure to them, I highly recommending doing some further research.
Now, I’m ready to compile my new slimmed down program. Well, to be accurate, the compiled object itself has not shrunk, but the source member has.
Until next time, happy coding.
Gregory Simmons is a software engineer with PC Richard & Son. He started on the IBM i platform in 1994, graduated with a degree in Computer Information Systems in 1997 and has been working on the OS/400 and IBM i platform ever since. He has been a registered instructor with the IBM Academic Initiative since 2007, and holds a COMMON Application Developer certification. When he’s not trying to figure out how to speed up legacy programs, he enjoys speaking at technical conferences, running, backpacking, hunting, and fishing.
RELATED STORIES
Guru: With Procedure Driven RPG, Be Precise With Options(*Exact)
Testing URLs With HTTP_GET_VERBOSE
Guru: Fooling Around With SQL And RPG
Guru: Procedure Driven RPG And Adopting The Pillars Of Object-Oriented Programming
Guru: Getting Started With The Code 4 i Extension Within VS Code
Guru: Procedure Driven RPG Means Keeping Your Variables Local
Guru: Procedure Driven RPG With Linear-Main Programs
Guru: Speeding Up RPG By Reducing I/O Operations, Part 2
Guru: Speeding Up RPG By Reducing I/O Operations, Part 1
Guru: Watch Out For This Pitfall When Working With Integer Columns
While there is a lot of benefit to standardizing “utility” functions like this into a service program, I believe the greatest benefit is to get core business logic functions in a service program so that there is exactly one place where this logic exists and you don’t have 100 programs that use said logic coded in each program. You need to change that core business logic? Would you rather make the change in one place or 100? If the latter, are you sure you got all 100 correctly updated? Oh wait, the logic in some programs use different variable names? I inherited this exact nightmare in a previous job.