One thing I add to this is that the one general purpose binding directory is great for building your programs, but when building the individual service programs, it can cause issues with duplicate procedure names. So I have special purpose binding directories, one per service program just for the purpose of building that service program. The special purpose binding directory is named the same as the service program.
Here is the issue I have with using the universal binding directory to build a service program (that is contained in the binding directory). If service program SP1 contains procedures Proc1 and Proc2, and we add a procedure Proc3 to module SP1, when we bind the service procedure against the universal binding directory, Proc1 and Proc2 appear in both SP1 (from the binding directory), and SP1 (from the module), and a duplicate procedure name error is thrown. But if I create a special purpose binding directory SP1 which is only to be used to build SP1, then I can leave service program SP1 out of the binding directory, but I can include any other service programs that I need to build SP1. And I can maintain the duplicate procedure name check.
So to reconcile this with the code smell mentioned by Buck, "Don't create one *BNDDIR for every *SRVPGM, ew.". It all depends on the purpose of the binding directory. I keep a single universal binding directory for creating programs (*PGM), but for service programs (*SRVPGM) I create individual special purpose binding directories.
Mark Murphy
Atlas Data Systems
mmurphy@xxxxxxxxxxxxxxx
-----Buck Calabro <kc2hiz@xxxxxxxxx> wrote: -----
To: rpg400-l@xxxxxxxxxxxx
From: Buck Calabro <kc2hiz@xxxxxxxxx>
Date: 08/04/2017 03:21PM
Subject: Re: Service program / procedure design and naming conventions
On 8/4/2017 2:24 PM, Dan wrote:
Suggestions / corrections / opinions are welcome. I'd also welcome any
online resources.
Have you read the RPG Sorcerer's Redbook? It's old, but it's more
tutorial-ish than many references.
http://www.redbooks.ibm.com/abstracts/sg245402.html I got the most out
of it by:
a) Reading it all the way through. When I got to parts that were too
much for me, I put a bookmark there and kept going.
b) Reading the bookmarked sections again.
c) Writing sample code. The real learning started when I modified that
code - added new sub-procedures, made mistakes, had to trouble-shoot.
d) Read the Redbook all the way through again. It was amazing how much
clearer it all was this time.
tl;dr There's no silver bullet.
With respect to naming, I have come to a very strong appreciation for
the following:
One binding directory for the whole company.
If that's unacceptable, then one *BNDDIR for each application
(A/R, A/P, G/L, etc)
Don't create one *BNDDIR for every *SRVPGM, ew.
One source member for each service program.
QRPGLESRC
One prototype member for each service program.
QPROTOSRC (David Morris' idea)
One set of *CURRENT exports for each *SRVPGM
QSRVSRC
NO *PRV exports (Barbara Morris' idea)
All of these have the same member name - the name of the service
program. So for a service program called MATH, it would be:
Prototypes for /copy go in QPROTOSRC member MATH
PI (actual sub-procedures) go in QRPGLESRC member MATH
Binder language / exports go in QSRVSRC member MATH
With this arrangement, maintenance is pretty simple. Find the sub-proc
you want to change by searching all the members in QPROTOSRC. There
will be one member for each service program in the system/application.
Once you have that member, all the other parts are named the same.
To use these sub-procs in a *PGM,
ctl-opt bnddir('MYCOMPANY');
/copy QPROTOSRC,math
result = add(x: y);
I also have a very strong preference for doing all this new work in
**free. Every supported release can use **free. If colleagues are
jittery, it's not an issue at all to make the function call in a more...
columnar form. For example I have code in production that looks like this:
* remove special characters from name
c eval mfirst=cleanMbrName(mfirst)
c eval mlast =cleanMbrName(mlast)
* assemble the full name from components
C MOVE *BLANKS CATNME 27
C MFIRST CAT MMI:1 CATNME
C CATNME CAT MLAST:1 CATNME
and the sub-procedures themselves, in the service program, are in /free
(predates **free). Now some people may find this hideous - I've sort of
glued a modern concept into a craptastical mess of existential angst.
Here was my thought process:
I could drop in and out of /free like this
// remove special characters from name
mfirst=cleanMbrName(mfirst)
mlast =cleanMbrName(mlast)
* assemble the full name from components
C MOVE *BLANKS CATNME 27
C MFIRST CAT MMI:1 CATNME
C CATNME CAT MLAST:1 CATNME
...and the traditionalists would say that the style is off-putting. By
leaving the whole thing in the Extended Factor-2 style, I at least got
them to accept that adding sub-procedures doesn't break the, er, style
of the existing code. A complete re-write was off the timetable.
The key thing here isn't the sub-procedure calls per se. Rather, it's
the point that all of the work: the arrays, the temporary variables, the
work variables like X, Y and K (wha?) are all hidden away. Unseen by
the *PGM. And much, much, much more importantly, I was able to drop a
pretty complex function right into the middle of that thousand line mess
without having to worry at all about accidentally reusing a field name,
an indicator, the ever-present X, nothing. If I'd tried to do that with
a subroutine... well it's just adding another couple dozen more lines of
global scoped code to a monster.
By using a sub-procedure, a complex change was made with virtually no
risk that I'd upset any of the other code that was already working.
That 'low risk' to existing code is the secret sauce that makes the
infrastructure of service programs worth doing.
There's one more naming convention that I don't myself use, but wish I
did: DIY 'namespace'. Imagine I'm a clever boy (yeah, that's a lot of
imagination to burn through, lol) and I have a service program AP, for
my accounts payable system. I need to get vendor names, so I write a
sub-procedure called getName. Sweet. Time passes and I start working
on AR for accounts receivable. Which needs customer names, so I write
(wait for it) getName. There's no problem because the two systems have
no overlap.
Until they do. And I need to write a report of which customers got
products from which vendors. And... then I get a problem because the
binder can't work out which getName() to use. Whoops.
If I'd named my sub-procedures getVendorName and getCustomerName, that
would have been better... until the boss loaded up a 3rd party service
program that uses the same names. For their own purposes. blaaaa.
So, namespaces. We haven't got them in RPG (yet), but essentially, the
namespace is a higher-level 'qualifier' if you will. But because we
haven't got it, we have to roll our own. So... instead of
getVendorName(), it would be ap_getVendorName(). Or
mycompany_getVendorName()... something that's going to make the
procedure name unique enough that it won't get confused with anything else.
As an Amazon Associate we earn from qualifying purchases.