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?