One of the most important tasks that you need to do before writing the code for a subprocedure is to write the documentation for its interface. The interface is the description of how the subprocedure communicates with its callers. It includes an accurate and complete description of any parameters passed to it and any value returned from it. By creating the documentation for the interface before writing the subprocedure code, you've clearly defined a plan for using the subprocedure. Once you know how it will be used, you'll find it easier to write the code that does the work.
Another advantage of writing the documentation first is that it leads you quite naturally to the first step of creating the subprocedure, which is creating an empty subprocedure that contains only the parameter list and a return statement. When I write code for a module, I create as many shell subprocedures as possible before putting any working code in them. I create the module, simply to verify that it compiles at that point. I can then go back into the module and start adding code, knowing that I have a working skeletal module.
The comment block shown below is from one of my subprocedures. The documentation is included with the subprocedure prototype up in the D-specs, and it is also included immediately before the P-spec that begins the subprocedure. Needless to say, any changes to the documentation are made to both copies, before any other coding is done. Although I used to use a somewhat complicated scheme with conditional include compiler directives to maintain only copy of the documentation, I've found it easier to simply use the two copies.
The reason for using two copies of the documentation is that I want the documentation to be near where I'm working. As you'll see in later articles, the D-spec prototypes are handled with conditional includes, so that I can easily copy the prototype and its documentation into other modules. When I'm working with the actual subprocedure itself (P-spec onwards), I want its documentation nearby.
****************************************************************
* Procedure MESSAGES_getMessage
*
* Get message text for a specified message ID in a
* system message file.
*
* Parameters
*
* p_messageID - the message ID to retrieve.
*
* (optional) p_substData - substitution data for message.
*
* (optional) p_messageFile - name of the message file where
* the message is located.
*
* (optional) p_messageLibrary - name of the library where
* the message file is located. Value:
* MESSAGES_LIBRARY_LIBL (default)
* MESSAGES_LIBRARY_CURLIB
* library name
*
* Returns
*
* MESSAGES_Message - the message data structure
****************************************************************
The first line of the documentation is simply the subprocedure name. By coding the name, I am clearly indicating that this comment block is for the particular subprocedure. Because I routinely copy and paste comment blocks, it is important to include the subprocedure name. That forces me to change the name and also make any other changes to match the copied comment block to the subprocedure.
I write a brief description of the subprocedure next. For most subprocedures, I only need 1 to 3 sentences to describe what it does. If it takes more than that to describe what it does, it may be doing too much, in which case I'll see if it makes sense to split it into more subprocedures. Occasionally you'll need to describe some exceptional condition or usage restriction, but for ordinary subprocedures, you'll just write a sentence and move on. By design, subprocedures are meant to be easy to understand and use.
If the subprocedure has any parameters passed to it, the parameters are listed on successive lines. I tend to name my parameters with a p_ prefix, to clearly distinguish the parameter name from other names in the subprocedure. The parameter name is followed by a brief description of its purpose.
If the parameter is optional, I include the (optional) notation before its name. I routinely code parameters using the options(*nopass : *omit) keywords as soon as possible in the parameter list, so I interpret the (optional) notation as meaning "does not need to be passed" or "can be passed as the special value *omit". In a later article, I'll describe how to use those options.
If there is a default value for a parameter or if there is a constant that can be passed as the parameter value, I list those values following the parameter description. For example, the parameter p_messageLibrary does not have to be passed. If it is not passed, it is set to the value of the constant MESSAGES_LIBRARY_LIBL. Another constant that can be passed is MESSAGES_LIBRARY_CURLIB. Finally, an actual library name can be passed, as is indicated by the third value listed for the parameter.
If the subprocedure does not have any parameters, I simply code (none) in place of the list of parameters. By explicitly coding that, I am emphasizing that the lack of parameters is deliberate, and not a code writing error.
By the way, all parameters in my subprocedures are input-only. Because I have adopted and enforced that convention in my code, I don't have to document if a parameter is input, output or input/output. I will have more to say about this in later articles.
If the subprocedure returns a value, I write the name of the return value and a brief description of it. In this example, the value returned is a data structure, MESSAGES_Message. Because the data structure is defined with the module name as a prefix, it is, in my framework, a publicly known data structure. This is my preferred technique for returning more than one value from a subprocedure, and will be described later.
If the subprocedure does not return anything, I code (nothing) in place of the return value name and description.
If the subprocedure can produce strange errors or if there is setup work that you should do before calling it, you can include additional comment areas, clearly labeled as such. For example, you might want to indicate what the subprocedure will do if the caller passes invalid parameter values to it.
The subprocedure interface documentation is meant to be easy to read, understand and maintain. As such, I don't include any extraneous information, such as notes about interim bug fixes, initials of other programmers who may have tweaked the code, and other useless information that typically decorates RPG programs. If you're trying to use the code, you'll simply want to know how to call it, what to pass it, and what you'll get back. The genesis and exodus of the subprocedure is most likely of no interest to you. I've never found any use for extraneous documentation, so I don't code it. If you're maintaining and changing the code, well, you own it now, so there's no need to continue to acknowledge any of your predecessors who worked on it.
If you need to indicate that some aspect of the code is temporary, for example, code commented out at a certain point, put the comment immediately before the statements that are affected. As a user of a subprocedure, I really don't want to be exposed to its internal tempests.
Craig Pelkie
October 15, 2004
Copyright © 2004, 2005, Craig Pelkie, ALL RIGHTS RESERVED