When it comes to building programs with RPG IV, storing the source in a single, large source member is no longer a viable programming practice. The use of subprocedures, modules, and service programs can cause some confusion, but by organizing your source code in a sensible way, you can make using these cool new tools much easier.
Below, I've listed the 10 guidelines that I've found to be most helpful when creating new applications with RPG IV. While I've labeled them "rules," they are merely guidelines that you might consider for implementation. Feel free to alter them, avoid them, or add your own.
Rule #1: Do not keep all of your source code in one source file.
This is perhaps the most perplexing practice I have seen. People create a source file named QSOURCE or something similar, and they store all the RPG, DDS, and CL source in that one source member.
This is not a safe thing to do. If you lose the file, you lose everything you have on your system. Thank goodness, the OS/400 operating system is the most reliable on the market. If this were Linux, Windows, or anything else, odds are good that you would have lost your source by now.
Keeping source in the standard QRPGLESRC, QDDSSRC, and QCLSRC source files is a good practice. At minimum, my recommendation is that you keep source in the standard source file names. Then, if you want more flexibility, break them out into libraries for each application category. For example, create an A/R source library and an Order Entry source library, each with its own QRPGLESRC, QDDSSRC, and QCLSRC source files. That way, backups will be faster, only changed source needs to be backed up, finding source will be easier (because you know that the invoicing program's source is in the A/R library's source member), and you have easier integration with current PC-based development tools such as CODE/400 or WDSc.
Rule #2: Do not put all of your subprocedures into a single source member.
With subprocedures, I see a lot of an "all or nothing" technique being practiced. Placing all your subprocedures into one source member is counterproductive. The purpose of subprocedures and modules is to allow you to break up your source into related components. Practice the art of placing related procedures into a source member. This yields multiple source members based on category or purpose. For example, all the pricing subprocedures might go into one source member, and all the subprocedures that calculate the weight of an order might go into another.
If the source member gets too big, start a new one with a similar name and place new procedures in it. A good rule of thumb is that if the source member gets too lengthy--more than about 2,000 lines of code--consider breaking it up or starting a second source member.
Rule #3: Do not limit source members to one subprocedure each.
Going the opposite direction of Rule #2, don't get too caught up in breaking apart your source code. Creating a source member with one or two subprocedures isn't wrong, but it should be done only on an exception basis. For example, when the subprocedures are not related to anything else in the application you are building, then stick them in their own source member. But don't create a one-to-one correspondence between source members and subprocedures. That will just create too much source to manage and actually cause the poor RPG programmer who is still stuck using SEU to have nightmares.
Rule #4: Group related subprocedures together in a single source member.
This is probably the most practical guideline; group related subprocedures in the same source member. If the source member gets too big, then follow the guidelines from rule #2 and break it up. The point of this guideline is really the opposite of what it says; you should avoid placing unrelated subprocedures into the same source member.
Create source members and, in them, store subprocedures that are related or support one another. Avoid placing all the subprocedures for a particular application or service program into the same source member just because there are only a few of them. Down the road, as the application grows, you'll appreciate the ability to maintain it more easily by editing the individual source member.
Rule #5: Put prototypes in a separate source member.
Repeat after me: "I will not hard-code prototypes in my source; I will not hard-code prototypes in my source." When you create a new subprocedure, move the prototype outside of that source member and into a source member that contains only prototypes, no executable code. Then, using /COPY or the new /INCLUDE, import the prototypes into each source member that will be calling your subprocedures.
This technique is key to using subprocedures. I know that many (most?) RPG programmers have never used /COPY--which is disappointing--but /COPY or /INCLUDE is critical to using subprocedures and, consequently, modules in your application development.
Place all prototypes into source members that contain nothing but prototypes. They can also contain declarative statements, if necessary, and while there are tricks available to allow executable code to be specified as well, source members that contain prototypes should not contain any executable code.
Rule #6: Create service programs using multiple modules with related function.
Creating your first service program is a big event, sort of like it was for most of us when we first used a fax machine. After awhile, the newness of the technology will wear off, and you'll get used to using them, without the mystique originally felt.
When creating service programs, you should design them so that they contain related routines or libraries of functions. You have more flexibility with service programs than you do with source members, because you can store multiple modules in the service programs. You should consider creating a service program for your A/R subprocedures. Your A/R subprocedures may be scattered across multiple source members and, hence, multiple module objects. All these modules may be combined into a single-service program.
If you want to provide even more flexibility, you might consider multiple-service programs per category. Just like source members, if a service program contains too much stuff, it can become unmanageable. Creating additional service programs that are related--with fewer modules and hence fewer subprocedures--can solve this manageability issue.
An example of this is the RPG ToolKit. It is currently composed of approximately a dozen modules and dozens of subprocedures. At some point in the future, as the CGI functionality of the ToolKit grows, we plan to break out the CGI functions into their own service programs. We also plan to do that with the IFS functions and the base functions, so you may end up with three or four service programs.
Rule #7: Never use CALLP to call a secondary module in a program.
In the original version of RPG IV on OS/400 V3R1, IBM did not provide the ability to write subprocedures using RPG IV. However, IBM did provide the CALLB operation code in V3R1.
In order to illustrate how the CALLB operation code works, IBM used a program that consisted of two *MODULE objects; both were RPG IV source members with mainline calcs (i.e., the RPG cycle).
The CALLB called the second module by name, taking advantage of a feature in the RPG IV compiler that automatically generates a subprocedure with the same name as the module when a source member is compiled that contains mainline calculations. That is, the compiler wraps the mainline calcs in a subprocedure whose name is the same as the module (which is usually the same name as the source member).
So "calling a module" became a technique that people began believing was a viable design decision. When I see developers implementing this technique, I realize that they don't have a clue about how to use subprocedures. They think they're using "ILE" because they included a CALLB (or CALLP) in their code. This technique reminds me of a person who paints a garage door with a can of latex paint and thinks he has just painted the Mona Lisa.
Bottom line: Don't call a module.
Rule #8: Always use NOMAIN in secondary source members.
To help with Rule #7, this guideline recommends that when you're creating a secondary source member (that is, a source member that will be a part of an application but not the entry point for the application), always, always, always use the NOMAIN keyword on the Header specification.
The NOMAIN keyword will cause a compiler error to be generated if the source member containing it is compiled with any mainline calculations present. This will certainly help maintain a good application design and weed out any of those crazy "calling a module" techniques.
NOMAIN also instructs the compiler to avoid inserting the nearly 12,000 lines of code that are used by the RPG cycle. Without the overhead of the RPG cycle start-up routine, calling procedures is a whole lot faster. In fact, calling a module requires the RPG cycle to be present; hence, anything you save by doing a CALLB to the module is lost when the RPG cycle is run.
Rule #9: Subprocedures should do one task and return.
When creating a subprocedure, try to limit the subprocedure to achieving one task and then returning to the caller. Never create a subprocedure that does everything. It is counterproductive because it makes the code unreadable.
One of the side effects of making your subprocedures do one task and then return is that they are readable by maintenance programmers. If you limit a procedure to one task and that task is only 50 lines of code, the maintenance programmer can quickly comprehend the logic and flow of the subprocedure and more easily do what needs to be done to modify it or enhance it.
A good rule of thumb is if a procedure is more than one page long, it's probably getting too complicated. But what is "one page" today? With comments and declaration statements, a subprocedure should be only a few dozen lines long. If you need a hard number as a guideline, then consider this; if a subprocedure is more than 250 lines of code (and that's a big number), it is too big for a maintenance programmer--and often the original programmer--to easily comprehend. So keep them small and on task.
Rule #10: Habitually export all subprocedures.
When you create a new subprocedure, it is not available to the outside world unless it is exported. Often, programmers will build a standalone application with embedded subprocedures (i.e., subprocedures coded in the same source member as the mainline calcs). Then later, as the application development evolves, they will move those subprocedures into a secondary source member. Often this leads to a "compilation failed during bind phase" error when you compile.
This type of error is usually caused because you did not specify the EXPORT keyword on the Subprocedure specification ("P spec"). You can avoid this type of error by always and automatically entering the EXPORT keyword on the P spec whenever you create a new subprocedure. The only time you want to avoid the EXPORT keyword is when you do not want it to be called from outside of the source member in which is specified. In this case, it is always easier to go back and remove an EXPORT keyword than it is to remember why you're getting that "binder phase" error.
So there you have it, 10 simply rules for using subprocedures. Make them part of your shop's standards.
Bob Cozzi has been programming in RPG since 1978. Since then, he has written many articles and several books, including The Modern RPG Language --the most widely used RPG reference manual in the world. Bob is also a very popular speaker at industry events such as RPG World and is the author of his own Web site and of the RPG ToolKit, an add-on library for RPG IV programmers.
LATEST COMMENTS
MC Press Online