There are better ways for storing textual information.
As a contract programmer, I have the opportunity to review quite a bit of i-based code that is to be rewritten as part of various modernization efforts. One programming style that I encounter fairly often is the use of compile-time arrays to store textual information for use on displays and reports. These companies' databases may have, for instance, an order-status field (OrdSts) with values such as P, O, C, H, I, R, and S to represent order pending, order, order canceled, order held by accounting, order held due to inventory, order released for processing, and order shipped, respectively. When returning order status information to a user, the application programs may then utilize a compile-time array, as shown below, to provide a textual description of the order status through a display or printer file character variable (Status).
dTxt s 30 dim(8) ctdata
dOrdSts s 1
dStatus s 25
/free
// Read a record containing order status (OrdSts)
select;
// Provide text description of order status
when OrdSts = 'P';
Status = Txt(1);
when OrdSts = 'O';
Status = Txt(2);
when OrdSts = 'C';
Status = Txt(3);
when OrdSts = 'H';
Status = Txt(4);
when OrdSts = 'I';
Status = Txt(8);
when OrdSts = 'R';
Status = Txt(5);
when OrdSts = 'S';
Status = Txt(6);
other;
Status = Txt(7);
endsl;
// Do processing, including output of
// order status in textual form
*inlr = *on;
return;
/end-free
**CTDATA TXT
Order pending
Order scheduled
Order canceled
Order held - Accounting
Order being processed
Order shipped
Order status is unknown
Order held - Inventory
While this approach to providing human-readable text descriptions of the order status is certainly time-proven (I've seen it in various forms of RPG for well over 30 years), it also irritates me (admittedly to a degree perhaps out of proportion, but an irritation all the same).
The major concern I have with this approach is that when a change is needed to the OrdSts textual description, then development must…
- Locate each program mapping OrdSts to a textual description.
- Edit each program and modify the compile-time array source.
- Recompile each changed program.
- For many companies, then run a regression test bucket to ensure no bug was introduced (missing override during compile, incorrect /copy included, inadvertent deletion of a source line, etc.).
There are many ways to minimize this development effort, including approaches such as these:
- Define the textual descriptions in a separate source member and use /copy to include this member into the application program. This does eliminate the need to go into each program; it still leaves us with the steps of locating the impacted programs, recompiling the programs (to include the modified source of the Txt array), and retesting.
- Remove the Txt array from the application program source and add the textual descriptions as conditioned character constants to the display and printer file. This does eliminate the need to edit and recompile each program, but it moves the source maintenance to the display and/or printer file, which could easily make the situation worse as we've now added indicators to condition the character constants (which, as an aside, can eventually lead you to the conclusion that 99 indicators is insufficient for many applications). This method also still requires you to locate, modify, recompile, and retest the display and printer files.
- Remove the Txt array from the application program source and store the Status descriptive text as separate records/rows in a keyed database file. The application program can then access the text using the appropriate key and move the descriptive text to the Status variable.
- Remove the Txt array from the application program source and store the Status descriptive text as separate message descriptions in a message file. The application program can then access the text using the appropriate message ID and move the descriptive text to the Status variable.
Approaches 3 and 4 succeed in externalizing the descriptive text from the application program and eliminate the need to locate application source (whether it be an RPG application program or a DDS display/printer file), edit the source, and recompile the source—which in many cases also results in less need for a full regression test in order to change the text associated with OrdSts.
My preference is to use the fourth approach—message descriptions and message files. This article won't go into the implementation details behind this preference (though future articles will), but one example of why I prefer message descriptions over database fields is due to the ability for the i message-handler support to imbed replacement data within the returned text—without the application program having to explicitly control the concatenation of data and text, which would be more the case using database support.
To move the descriptive text to message descriptions, we can create a message file, named TextMsgs, and add the appropriate message descriptions as shown below.
CRTMSGF MSGF(TEXTMSGS)
ADDMSGD MSGID(DSP0100) MSGF(TEXTMSGS) MSG('Order pending')
ADDMSGD MSGID(DSP0101) MSGF(TEXTMSGS) MSG('Order scheduled')
ADDMSGD MSGID(DSP0102) MSGF(TEXTMSGS) MSG('Order canceled')
ADDMSGD MSGID(DSP0103) MSGF(TEXTMSGS) MSG('Order held - Accounting')
ADDMSGD MSGID(DSP0104) MSGF(TEXTMSGS) MSG('Order held - Inventory')
ADDMSGD MSGID(DSP0105) MSGF(TEXTMSGS) MSG('Order being processed')
ADDMSGD MSGID(DSP0106) MSGF(TEXTMSGS) MSG('Order shipped')
ADDMSGD MSGID(DSP0107) MSGF(TEXTMSGS) MSG('Order status is unknown')
Having added the message description to TEXTMSGS, we now need to access these descriptions from the application program. As you might expect, there's an API for that. The API is Retrieve Message (QMHRTVM) and is documented here. The QMHRTVM API has many capabilities we will not be taking advantage of today. The essential part that we will be using initially is that, given a message ID (and message file), the API can return the first-level text of the message description. In the next article, we'll look at one possible implementation of a *SRVPGM export RtvMsgTxt (Retrieve Message Text) which utilizes this API. In the meantime, feel free to review the API documentation and see if you can write your own implementation of RtvMsgTxt.
The RtvMsgTxt procedure we'll be developing returns first-level message text and will be utilized as shown in the following program.
h bnddir('MSGSPTBD')
dRtvMsgTxt pr
dMsgRcv 65535a options(*varsize)
dLenMsgRcv 10i 0 const
dMsgID 7a const
dOrdSts s 1
dStatus s 25
/free
// Read a record containing order status (OrdSts)
select;
// Provide text description of order status
when OrdSts = 'P';
RtvMsgTxt(Status :%size(Status) :'DSP0100');
when OrdSts = 'O';
RtvMsgTxt(Status :%size(Status) :'DSP0101');
when OrdSts = 'C';
RtvMsgTxt(Status :%size(Status) :'DSP0102');
when OrdSts = 'H';
RtvMsgTxt(Status :%size(Status) :'DSP0103');
when OrdSts = 'I';
RtvMsgTxt(Status :%size(Status) :'DSP0104');
when OrdSts = 'R';
RtvMsgTxt(Status :%size(Status) :'DSP0105');
when OrdSts = 'S';
RtvMsgTxt(Status :%size(Status) :'DSP0106');
other;
RtvMsgTxt(Status :%size(Status) :'DSP0107');
endsl;
// Do processing, including output of
// order status in textual form
*inlr = *on;
return;
/end-free
These are the changes made to the original sample program:
- Specify the binding directory MSGSPTBD on the H-spec. In the next article, we'll create this binding directory and add the *SRVPGM MSGSPT to it.
- Prototype the exported RtvMsgTxt procedure with three parameters. The parameters respectively correspond to the variable where the message text will be returned, the size of the variable where the message text is returned (the size of the first parameter), and the seven-character message ID identifying the message to be returned. In my implementation of RtvMsgTxt, the procedure will determine the appropriate message file based on the three-character prefix (DSP in the sample scenario) of the message ID. Having RtvMsgTxt determine the message file removes the need for the developer to specify the message file (most likely the same) with every call of the RtvMsgTxt procedure.
- Replace all references to the Txt compile-time array entries with calls to the RtvMsgTxt procedure. When calling the procedure, the three parameters passed are Status, %size(Status), and the appropriate message ID. This can be accomplished with minimal effort by simply scanning for the string 'Txt(' and specifying the correct message ID for the third parameter passed to RtvMsgTxt.
- Remove the definition of the Txt array along with the source records associated with the array.
With these changes to how the order status descriptive text is retrieved, we can now change the text with the Change Message Description (CHGMSGD) command and not have to locate, change, and recompile the source of any application program, display file, or printer file that is utilizing the text. We could, going admittedly to an extreme for some companies, even change the English description to Spanish and not have to recompile the application program (though in the case of a language change such as English to Spanish we would most likely want to also translate any *DSPF and *PRTF column headings, prompts, etc.—not related to Order Status though—to also be in Spanish).
Those of you familiar with the DDS keyword MSGID might be wondering why I'm using a RtvMsgTxt approach rather than simply using the variable support of the MSGID keyword to provide the message text. If I knew with certainty that the textual form of OrdSts would be needed only for display files, I might very well take that approach. I don't know with certainty though what the future might hold—and if that future includes printer files, I would then run into the problem that printer files do not support the MSGID DDS function, leaving me still with the need to provide order status descriptive text under application program control. Because I prefer one solution that can be applied across all scenarios, I would go with a RtvMsgTxt-based solution.
The benefit of storing the descriptive text external to the application program is not one that, in my opinion, warrants going out and changing existing applications solely for the purpose of eliminating compile-time arrays (or conditioned text in *DSPFs and *PRTFs). It is, however, a solution that can be easily implemented as you are updating existing applications and/or writing new application programs. And the ease of implementation is one where the additional cost of making such an enhancement is minimal.
Next month, we'll look at some of the specifics in using the QMHRTVM API. In the meantime, if you have any API questions send them to me at
as/400, os/400, iseries, system i, i5/os, ibm i, power systems, 6.1, 7.1, V7,
LATEST COMMENTS
MC Press Online