Last-modified: 01 Sept 2000 |
This is a style guide for the Rexx programming language, primarily aimed at Rexx in the OS/390 environment, but application to Rexx on other platforms, and to variants of Rexx such as NetRexx. Many of the points here have been raised at some time on the TSO-REXX listserv mailing list.
Correctly commenting code is one of the most useful things you can do when writing code. Most style problems can be repaired with a formatter, but useful comments cannot be conjured out of thin air.
Comments come in two flavours, winged and boxed:
/* This is a winged comment */ /**************************************\ * This is a * * boxed comment * \**************************************/ |
Boxed comments should be reserved to highlight major
structural elements, such as the start of procedures. See the Calculate_Factorial() example.
Winged comments are useful for describing the purpose of the
statement(s) immediatly adjacent to the comment.
It may also be worth borrowing from the Javadoc idea. Comments enclosed by /** ... */ may be read by a suitable program, and the text within associated with the immediately following Rexx statement, and used to automate the creation of external documentation for the program.
The use of asterisks (*) as the box border can make the comment too heavy. Consider using minus (-) or another lighter character instead. Also, don't worry about closing the right hand vertical, as realigning the border after text insertion is a tedious affair. You may have a Rexx reformatter that deals with this problem.
A common use of boxed comments is for an informative header at the start of the exec. Here is an example:
/* Rexx ----------------------------------------- AUTOTOOL <A one line description for use by an indexing tool> ------------------------------------------------------- Copyright: <You may want a copyright notice> Change History: <yy/mmm/dd Userid VersId Description> Description: <A long description of the purpose of the exec. Include invocation arguments, examples of call syntax, returned values, etc.> ---------------------------------------------- MEMNAME */ |
It is extremely useful to have a tool to handle the creation of a standard header, and to handle change history information. Note the marker in the top right of the box that may be used by a tool to spot whether this is a standard header or not.
An indexing tool that reads the short description and change history is also useful, so make sure the header format supplies information that would be useful to an indexing tool.
In the change history, put the most recent changes at the top, so they are visible when the exec is opened. It may also be useful to have a version id to mark changed code in small comments later on.
It may not be worth putting some types of information in the header, particularly data that ages poorly. Dataset names and pointers to external information are particularly prone to becoming incorrect.
Things to avoid when creating comments:
The Rexx ANSI standard talks of Rexx 'symbols'. Here I shall
use the more familiar terms 'variable', 'variable name' and 'variable
value'.
Compound variables, stems, and tails refer to the whole and
various parts of compound variables.
Always make your variable names useful and meaningful.
Some programmers use a form of Hungarian Notation to show when a variable is boolean, a loop index, numeric, etc. This may make your variable names not so easy to read, and has other dissadvantages.
Consider using i, j, k, etc as loop control variables. There
are advantages and disadvantages to this:
Faster
execution speed. Single character variable names show a
performance improvment (My simple test measured nearly 10%
improvement).
Compound
variables names are shorter and less likely to make long
statements cross onto multiple lines.
You have
to rely on the stem name to indicate the meaning of the data in
the compound variable. E.g. Consider compound var names record.i
and score.teamIndex.eventIndex . It is obvious
that i is the record number, but would it be
obvious from score.i.j that i
is the team and j is the event?
Global variables can be handled in the following
way:
First, in the opening section of your Exec, set a variable named
global whose contents are the stem names of your global variables:
global = 'gl.' /* set a list of stems that are global variables */ gl.testVar = 'This is a sample global variable.' |
Then, at each procedure start, expose this global, and its contents:
An_Internal_Function: procedure expose (global) say gl.testVar /* ==> This is a sample global variable. */ return 0 |
Do use the procedure keyword with every defined procedure. This hides the procedure's internal variables from the caller, and allows the use of i,j and k as loop control variables throughout your program without any side effects, as in this example:
do i = 5 to 10 say 'Number:'i 'Factorial:'Calculate_Factorial(2,i) end exit /* -------------------------------------- Return the multiple (factorial) of all numbers between arg1 and arg2. -------------------------------------- */ Calculate_Factorial: procedure parse arg startNum,endNum factorial = 1 do i = startNum to endNum factorial = factorial * i end return factorial |
This common problem occurs when a variable is assigned a value, and the variable is then used in a compound variable:
stem.salutation = 'HELLO!' /* literally stem.SALUTATION = 'HELLO!' */ /* Output the intended result */ say stem.salutation /* ==> HELLO! */ /* Set the tail of the compound variable to have a value */ salutation = 'GOODBYE' /* Now show an often unintended result */ say stem.salutation /* ==> STEM.GOODBYE */ exit |
If you don't want this effect, you must use unassigned variable names in the compound variable. You could use non-alphanumeric characters such as ! or ? to prefix the variable name when its used in the compund tail, or use a numeric prefix, as symbols starting with numerics are, by definition, constants.
testVar = 'HELLO' stem.0testVar = testVar stem.!testVar = testVar |
Numeric prefixes are better for code
portability.
The ISPF editor picks special characters (such as !) up in HILTE
mode.
Non-alphanumeric characters may not be portable to foreign EBCDIC
character sets, and are not portable to some Rexx extentions,
such as Object-Oriented Rexx.
Large blocks of variable assignations can be split into columns, with variable names, variable values, and winged comments:
gl.Bdebug = 0 /* Turns debugging on/off */ gl.!pageLength = 60 /* print option - page length */ gl.!pageWidth = 50 /* print option - page width */ |
Here we discuss the case we could use for syntactic elements, what indentation is appropriate, and how we could break blocks of statements up.
But first, What are we trying to achieve, and how do we go about achieving our aim.
We are trying to increase the readability of the code.
We can improve readability by using techniques to differentiate and highlight elements within a statement, show relationships between statements, and lay out the code so that these relationships and statement elements are visible to the reader.
The target audience should be anyone likely to need to change your code in the future, including yourself. Don't assume any knowledge of the inner workings of your code, it may be some years before you revisit it. A reasonable knowledge of Rexx may be assumed, even though the reader may be new to the language. Everyone has to learn something sometime.
Here are some elements that go to make up statements:
Here are some techniques to improve readability using case:
Other techniques to improve readability are:.
We are also concerned with breaking up the code into manageable and reusable units. We can do this by using procedures, and by surrounding blocks of statements that perform a task with white space.
There are some facts about the programming environment that influence what techniques may be most effective.
External function names are related to file names in a file system, or member names in partitioned datasets. MYFUNC() may refer to PDS 'user.EXEC(MYFUNC)' or file name MYFUNC.REXX, depending on your Rexx environment. By using upper case for external function names, you avoid having to quote function names to maintain lower case in the file name. You then, of course, must use upper case in the file names of external functions.
Screen size influences how much code can be on the screen at one time. Using too many blank lines or splitting statements over multiple lines can move code out of the viewable area. Techniques that disperse statements over several lines work well on window based systems that allow the viewable area to be adjusted, but are restricted by the fixed terminal size of 3270 systems. The same is true for boxed comments.
Some editors are Rexx aware and can hilite Rexx code to show keywords, quoted strings and other elements. A Rexx aware editor such as ISPF EDIT with the HILITE utility, can for instance turn all keywords red, and all comments blue. There's no point in using upper case to highlight keywords, when the editor can pick them out with colour.
Keywords appear in isolation in the text, and also tend to appear at the start of statements, so they stand out without any help from the programmer.
Function and variable names tend to appear close together in code. We need to be able to differentiate variable names from function names, and also differentiate between the various flavours of function; internal, external, builtin, etc. For example:
datasetName = STRIP(TRANSLATE(GetDsn(ddName)),,"'") or datasetName = STRIP(TRANSLATE(Get_Dataset_Name(ddName)),,"'") |
Literals should be quoted, unless you can be sure that the literal hasn't been used as a symbol elsewhere.
Commands being passed to external environments often have single quotes in them. E.g. address TSO "ALLOC FI(TEST) DA('MY.DATASET') SHR REUSE".
NetRexx (and presumably other close relatives of Rexx) are sufficiently different from Rexx to make consideration of styles that apply to NetRexx irrelevant. Having said that NetRexx is a case insensitive language, and most style rules should apply to it.
Here is programming style that meets the above criteria in varying degrees. It tries to keep the code dense for 3270 displays, and relies heavily on a highlighting editor to differentiate between keywords, quoted text, variable names, and other statement elements.
Rules specifically related to case:
Rules for quoting:
Rules for procedures:
Rules for comments:
Other miscellaneous rules:
An aside on following a ruleset: One important comment that must be made, is that it is not so important that one set of rules is followed in all cases, but it is important that a set of rules is followed consistently in a piece of work. Leaping from one style to another mid-procedure can hinder a readers understanding of the program.
Here's an example program, with two similar internal functions present, in two slightly different styles. The compress function follows the above example set of guidelines. The uncompress function follows a second consistent set of guidelines. Note how the uncompress function is easier to read when no colour highlighting is used. Copy the program into a Rexx sensitive editor, and the compress function becomes easier to read.
/* REXX ---------------------------------------------------------- SYNOPSIS : LZ78 compressor/decompressor demo VERSION : 1.0 CREATED : April 1999 NOTES : From the uberFish Rexx Style Guide. --------------------------------------------------------------- */ /* Demo normal text compress/decompress */ say 'Normal text demo.' inString = 'If_I_were_a_Linesman,_' , || 'I_would_execute_the_players_who_applauded_my_offsides.' say 'Compressing 'LENGTH(inString)' byte string .....' say inString compString = Compress_LZ78(inString) say 'Compressed as.....' say compString decompString = Uncompress_LZ78(compString) say 'Decompressed as.....' say decompString /* Demo Highly redundant text compress/decompress */ say say 'Highly redundant demo.' inString = 'Oogachacka,Oogachacka,Oogachacka,' inString = inString||inString||inString||inString inString = inString||inString||inString||inString say 'Compressing 'LENGTH(inString)' byte string .....' say inString compString = Compress_LZ78(inString) say 'Compressed as.....' say compString decompString = Uncompress_LZ78(compString) say 'Decompressed as.....' say decompString exit 0 /** ================================================================ Demo LZ78 compressor. In reality, the output would be formatted to take up less space then the original, E,g, in 9bit bytes, with the high order bit set on when the char is a dictionary reference, or something. Oh, and it doesnt like spaces, cos I use WORDPOS() to search the dictionary. ----------------------------------------------------------------- */ Compress_LZ78: procedure parse arg inString /* Initialise the dictionary, output string, etc */ outString = '' /* output string */ d = '' /* Dictionary. Blank delimited 'words' */ w = '' /* Last used phrase */ outCode = '' /* Our current dictionary reference for this phrase */ /* For every char in the input string, output the char, or a dictionary reference */ do i = 1 to LENGTH(inString) /* Get next input char, and make phrase */ thisChar = SUBSTR(inString,i,1) thisPhrase = w||thisChar /* If the new phrase is in the dictionary, queue up this dictionary reference to go into the output string */ if WORDPOS(thisPhrase,d) > 0 then do if outCode = '' then outString = LEFT(outString,LENGTH(outString)-1) outCode = '<'WORDPOS(thisPhrase,d)'>' w = thisPhrase end /* If the new phrase wasnt in the dictionary, output it */ else do outString = outString||outCode||thisChar outCode = '' d = d thisPhrase w = thisChar end end /* return the compressed string */ return outString||outCode /** ================================================================= Demo LZ78 uncompressor. Here Ive switched to another style for comparison. ----------------------------------------------------------------- */ Uncompress_LZ78: procedure parse arg inString /* Initialise stuff */ outString = '' d = '' w = '' /* Loop across each char in input string, building dictionary and output string as we go */ Do i = 1 To Length(inString) /* Get next char from input string */ thisChar = Substr(inString,i,1) /* If the char is a code, then look up code in dictionary, add new phrase to dictionary, add phrase to output */ If thisChar = '<' Then Do thisCode = Substr(inString,i+1,Pos('>',inString,i)-i-1) thisOut = Word(d,thisCode) d = d || ' ' || w || Left(thisOut,1) w = thisOut i = i + Length(thisCode) +1 outString = outString || thisOut End /* Else add phrase to dictionary and output the character */ Else Do d = d || ' ' || w || thisChar w = thisChar outString = outString || thisChar End End /* Return the uncompressed string */ Return outString |
Spot the line that is longer than 80 characters. In ISPF it should be wrapped in whatever way is consistent with your treatment if IF THEN DO constructs:
if outCode = '' then outString = LEFT(outString,LENGTH(outString)-1) |
There's little point in setting guidelines for writing programs if they're going to be ignored. You have to make people want to follow your guidelines.
Here are some ways of encouraging the use of programming standards:
Relying on individuals can fail if lazy or naive programmers fail to follow guidelines. It does allow creative and expert programmers the freedom to use systems they are comfortable and productive with.
Management control is particularly suited to organizations that have a Quality Assurance program.
A tools led approach can be combined with other approaches. There is some development effort to get tools into place, and the tools must be flexible and fit for their purpose. Generally, a handful of ISPF EDIT macros to perform various formatting tasks will suffice.
There are two schools of thought on whether to explicitly use the string concatenation operator ( || ) or not.
say 'Explicit use of operators' newString = oldString || ' Some Text ' || MYFUNC('ham','eggs') say Status_Char(userStatus,'TEXT') || ': ' , ||userName || ' is ' || userStatus say 'Omission of operators.' newString = oldString 'Some Text' MYFUNC('ham','eggs') say Status_Char(userStatus,'TEXT')':' userName 'is' userStatus |
There are some good reasons to use concatenation operators, and some good reasons not to.
Points for the use of the concatenation operator:
Points for ommiting the operator whenever convenient:
Don't use too many levels of indirection.
For instance, there is often little use in storing ISPF panel
names in variables when address ISPEXEC "DISPLAY PANEL(MYPANEL)"
pinpoints exactly where in your exec that panel MYPANEL is
actually used. Having functions call functions that call
functions can equally make debugging an exec very tiresome indeed,
so make sure that you comment what is going on if you do have to
create a complicated structure.
Do make sure abstracted procedures allow full
access to the underlying data.
For instance, say you put a nice shell exec around TSO LISTCAT.
Your shell program should be able to control all arguments for
LISTCAT, and return any information returned by LISTCAT to the
caller. Otherwise you may have to create a new interface for your
old shell if new LISTCAT data is needed. Adding new functionality
to old code creates backwards compatibility problems, and
maintenance problems as the code becomes 'hairy' from unnecessary
revisions.
Don't hide information in variables set far away
from where they are used.
Its one thing to have a nice block of commented assignations near
the top of an exec or procedure, but its another thing entirely
to place assignation statements in obscure parts of the exec, far
away from where they will actually be used.
Do end all internal procedures with an
unconditional RETURN.
This makes it clear to the reader when a procedure ends and may
help code formatting tools to spot the end of internal procedures.
Please drop me a line, and tell me about any improvements you think could be made to this page.
Was the information useful?
Was the information complete and concise?
Did you understand the information?