R/BASIC Coding Standards
Published By | Date | Version | Knowledge Level | Keywords |
---|---|---|---|---|
Revelation Technologies | 10 OCT 1990 | 2.X | INTERMEDIATE | EQUATE, COMMON, LABELLED, VARIABLE, CONVENTIONS |
These are the coding standards used by the programmers at Revelation Technologies Inc. They are provided here as guidelines for your own standards, or you can adopt these as your own.
There are four goals for the standards. By following the standards, your programs will be:
- Easier to read.
- Easier to maintain.
- More efficient.
- Easier to debug.
Naming Conventions
By adhering to these naming conventions, you will avoid accidentally reusing variables.
Variable Names
Variable names should be meaningful.
EQUATE
Suffix all EQUATE variables with a "$".
EQU TRUE$ TO 1 EQU FALSE$ TO 0
COMMON
Suffix all COMMON variables with a "%".
COMMON NAME%, INVAL%
LABELLED COMMON
Suffix all LABELLED COMMON variables with an "@".
COMMON /POINTERS/ F_NAME@, HANDLE@
Variable Delimiter
In order to maintain compatibility with other languages and SQL, use "_" (underscore) to delimit the words in a variable.
EMPLOYEE_NO
OPEN
When opening a file to a file variable, suffix the file variable with "_FILE" and use the name of the real file.
OPEN "CUST" TO CUST_FILE ELSE MSG(NOT_ATTACHED$, "", "", "CUST") DONE = TRUE$ END
If the name of the file is not known ahead of time, the file should be opened to a variable name that describes the file's role. The file variable should be prefixed with "FILE_"
OPEN IN_FILE TO SOURCE_FILE ELSE MSG(NOT_ATTACHED$, "", "", IN_FILE) DONE = TRUE$ END
Messages
Advanced Revelation is a highly customizable product. In order to make it possible for users to customize the system messages, the following guidelines apply to calling MSG.
Note: Revalation Technologies programmers store their messages in the SYS.MESSAGES file. Other programmers may use either SYS.MESSAGES or MESSAGES. Messages stored in SYS.MESSAGES are subject to being overwritten by future upgrades, messages in MESSAGES are not.
Equate message numbers
Message numbers should be assigned equate values in the MESSAGES section of the program header. Be sure to indicate, in a comment, what the message is for.
DO:
EQU NOT_ATTACHED$ TO 201 ;* File not attached. (...) MSG(NOT_ATTACHED$, "", "", FILE_NAME)
NOT:
MSG("201", "", "", FILE_NAME)
UB or U (Message Up) messages
Messages of type UB should initialize the IMAGE variable before calling MSG. This will allow the user to turn off the message without getting a "Variable Not Assigned a Value" error when the DB messsage call is made.
DO:
IMAGE = "" MSG(PROCESSING$, "", IMAGE, "")
NOT:
MSG(PROCESSING$, "", IMAGE, "")
DB or D (Message Down) messages
Programs should test for the IMAGE variable before executing a DB message. If IMAGE is null, then the UB message was never executed, and the program can avoid an unnecessary subroutine call.
DO:
IF IMAGE THEN MSG("", "DB", IMAGE, "") END
NOT:
MSG("", "DB", IMAGE, "")
Reply messages
Unless the program requires a lowercase response, all reply messages should be of type RC.
All reply messages should initialize the reply variable to provide a default response for the user.
Starting with Advanced Revelation version 2.1, the variable should be initialized with the internal representation of the data. MSG will be responsible for converting it to the appropriate text.
DO:
REPLY = 1 ;* Boolean conversion. MSG(DELETE$, "", REPLY, "")
NOT:
MSG(DELETE$, "", REPLY, "")
Program Structure
Structured Coding
Use a structured code approach. Modularize routines and have a simple, concise mainline whenever possible. Avoid GOTOs, because they make the flow of the program difficult to follow.
Under most circumstances, there should be only one exit point from a program, loop, or subroutine. If there are multiple exit points, be sure to document them well.
Standard Top
All programs should use a standard top. Figure 1 provides an example. All parts of the top should be completely filled out. To aid readability, descriptions should be in upper and lower case.
Documentation
Document your code well! At complex parts of your logic, reference a section of your header documentation and describe it there.
Comments should be in upper and lower case.
Comment lines should be indented as if they were normal program lines. In cases where the comment applies to one line of code and the comment is short, put the comment at the end of the line.
Use C-style comment delimiters for multi-line comments. It makes the comments easier to read and modify.
Comment branches in the logic immediately after the branch.
(...) ALPHA = "100" ;* Initialize Alpha IF ALPHA > ZETA THEN /* This is the normal condition. Process accordingly. */ (...) END ELSE /* Error, post message. */ MSG(ERR_ZETA$, "", "", "") END (...)
Subroutine and Function Declaration
Declare all SUBROUTINES and FUNCTIONS at the appropriate section in the header. Do not use the format "CALL program" unless the program is being called indirectly, such as "CALL @PROG.VAR".
DO:
NET_PRESENT_VALUE(A)
NOT:
CALL NET_PRESENT_VALUE(A)
Internal Subroutines
In order to make program listings more readable, the following guidelines apply to internal subroutines.
Use descriptive, alphabetic labels. Do not use numeric labels.
All labels must follow all mainline code.
Subroutines should be arranged alphabetically by label. This is especially important for large programs with many subroutines.
The subroutine section should be set off from the mainline code with a header:
/*SUBROUTINE SECTION*/
Each subroutine should have its own header and a brief description of its purpose.
/*SORT - This subroutine sortsthe array built by the user.*/SORT:
Branching to internal subroutines
R/BASIC requires a colon on subroutine labels. When using internal labels, do not follow the statement initiating the branch with a colon. This allows for quick searches to find the label, not the branch.
DO:
GOSUB COMPUTE_IT
NOT:
GOSUB COMPUTE_IT:
$INSERTS
Never nest $INSERTS. Normally, use $INSERT to insert blocks of code common to 2 or more programs. It should only be used to insert specific program code when source code limits are being hit.
Indentation
Each logical level in a program should be indented 3 spaces.
WHILE and UNTIL keywords should be aligned with the LOOP and REPEAT keywords. This makes it easier to see the exit conditions for the loop. See Figure 2.
CASE statements should be aligned under the word CASE in the BEGIN CASE statement. See Figure 3.
Use of spaces
Proper use of spaces will make it easier to read programs and to navigate through them. Commas in comma-delimited argument lists should be followed by spaces. Operators and operands should be separated by spaces.
DO:
OFFSET = START_POS + FRAME_SIZE KEY_LIST = DELETE(KEY_LIST, 1, 0, 0)
NOT:
OFFSET=START_POS+FRAMESIZE KEY_LIST=DELETE(KEY_LIST,1,0,0)
Revisions
All source code must be checked out before modifications can be made.
All revisions that are made to a program should be reflected in the appropriate parts of the header. This includes updating the THEORY OF OPERATION and PROCEDURES, if necessary.
Your initials and the date should be placed above and below the altered lines as well as in the REVISION section of the heading. Describe the change in comments.
MSG(BAD_DATA$, '', '', '') /*--> 6/1/88 - JAH - should do a RETURN instead of STOP. report #3344 */ * STOP RETURN *--> 6/1/88 - JAH
IF...THEN...ELSE statement
IF…THEN…ELSE statements should be broken into multiple lines, rather than run together on a single line.
DO:
IF COUNTER > MAX_LINES THEN GOSUB ERROR END ELSE GOSUB EDIT END
NOT:
IF COUNTER > MAX_LINES THEN GOSUB ERROR ELSE GOSUB EDIT
CLEAR statement
Do not use the CLEAR statement to initialize variables. Be explicit when initializing each variable.
STOP
When terminating a program in an error condition, do not just issue a STOP. Display a message by calling "MSG" and put the message in the SYS.MESSAGES file. (See "Messages" above.)
DO:
READ INVOICE_REC FROM INVOICE_FILE ELSE MSG(NO_REC$, "", "", "INVOICE") DONE = TRUE$ END
NOT:
READ INVOICE_REC FROM INVOICE_FILE ELSE PRINT "Invoice record missing" STOP END
Do not do a STOP, ABORT, or ABORT ALL from within a subroutine. This will cancel one or more main programs. Instead, set an error flag or post an error message and RETURN to the calling program. Let the calling program deal with the problem.
READNEXT
To locate the end of a select list in a READNEXT loop, do not test the value of the key variable. Use a "DONE" variable instead. You can never be too sure what kind of key will be coming through a select list.
DO:
DONE = FALSE$ LOOP READNEXT KEY ELSE DONE = TRUE$ END UNTIL DONE (...) REPEAT
NOT:
LOOP READNEXT KEY ELSE KEY = "DONE" END UNTIL KEY = "DONE" (...) REPEAT
R/BASIC SELECT
When doing an R/BASIC SELECT statement, make sure that it is surrounded with a PUSH.SAVE and a POP.SELECT. This avoids inadvertently using outside select lists.
(...) PUSH.SELECT(CURSOR, SAVE1, SAVE2, SAVE3) SELECT SOURCE_FILE (...) POP.SELECT(CURSOR, SAVE1, SAVE2, SAVE3) RETURN
@VARIABLES
Use system @variables whenever possible, making sure to guard against clobbering another process when using @DICT, @ID, and @RECORD. For example, use @FM, @VM, @TM, @SVM, @UPPER.CASE, @LOWER.CASE, etc.
Semicolons
Do not use semicolons to put multiple statements on a line (except comments). Keep lines very simple.
DO:
VAL = 0 ; * Initialize value INCR = 1 ; * Initialize increment
NOT:
* Initialize variables VAL = 0 ; INCR = 1
Multipart Keys
When constructing multipart record keys, use an "*" (asterisk) as the delimiter.
CASE statements
If you use a CASE statement, it should be easy to read. If the code inside a particular case branch is long, move it to an internal subroutine and call it via a GOSUB.
DO:
BEGIN CASE CASE CODE = "ALPHA" GOSUB ALPHA_CONDITION CASE CODE = "BETA" GOSUB BETA_CONDITION END CASE
NOT:
BEGIN CASE CASE CODE = "ALPHA" (a page of code) CASE CODE = "BETA" (half a page of code) END CASE
If the CASE statement is testing for a large number of conditions (as in a commuter module) use a LOCATE BY … ON…GOSUB format.
DO:
SUBLIST = "PRE_READ,POST_DEL,OPTIONS" LOCATE BRANCH IN SUBLIST USING ","SETTING POS THEN ON POS GOSUB PRE_READ, POST_DEL, OPT END ELSE MSG(ILLEGAL_BRANCH$, "", "", BRANCH) END
NOT:
BEGIN CASE CASE BRANCH = "PRE_READ" GOSUB PRE_READ CASE BRANCH = "POST_DEL" GOSUB POST_DEL CASE BRANCH = "OPTIONS" GOSUB OPT CASE OTHERWISE$ MSG(ILLEGAL_BRANCH$, "", "",ï BRANCH) END CASE
Use CASE statements in place of IF…THEN statements whenever program logic calls for vertical cascading conditions. The resulting code will be easier to read.
DO:
BEGIN CASE CASE STATUS = "A" A += 1 CASE STATUS = "B" C -= 3 CASE OTHERWISE$ D = 3 END CASE
NOT:
IF STATUS = "A" THEN A += 1 END ELSE IF STATUS = "B" THEN C -= 3 END ELSE D = 3 END END
Delay loops
Delay loops should use the system subroutine DELAY. This allows for differences between CPU speeds and operating systems.
DO:
DECLARE SUBROUTINE DELAY DELAY(5)
NOT:
FOR DELAY = 1 TO 1000 LINEMARK NEXT DELAY
Line length
If possible, keep lines shorter than 80 characters.
Assignment
Use the UNASSIGNED function to check variables that might not have been assigned. This is especially important in subroutines.
DO:
IF UNASSIGNED(VAR) THEN VAR = NULL$ ;* Or default value. END
Efficiency
While programs that are easy to read and easy to maintain are a worthy goal, programs that execute quickly are also desirable. Following these guidelines will make programs more efficient.
EQUATE
Always equate constants instead of assigning them to variables. It will produce less object code. The reverse is usually true if you equate to a variable or expression.
Do not equate a variable to a simple function, because the function must be resolved at each reference.
DO:
CLS = @(-1) : "PGM001"
NOT:
EQU CLS$ TO @(-1) : "PGM001"
Special Operators
Use the special operators (+=, -=, := ) whenever possible. This is especially true for :=, as it does not require a separate copy of the item to be loaded on to the stack. This is quite important when dealing with large items.
DO:
STRING := "More stuff"
NOT:
STRING = STRING : "More stuff"
NOT function
If possible, avoid using the NOT function in an IF…THEN construction. Use IF…ELSE instead.
DO:
IF GRAND_TOTAL > 100 ELSE
NOT:
IF NOT(GRAND_TOTAL > 100) THEN
LOCATE...BY
Use LOCATE…BY only when necessary, because it is much slower than LOCATE.
As a rule, it is faster to sort an array with V119 than with LOCATE…BY. LOCATE…BY will be slightly faster when the array you are building must contain unique values. If in doubt, use V119.
EXTRACT and REPLACE
Use the dynamic form of the extract instead of the function.
DO:
CUST_REC<3> = 4 STATUS = CUST_REC<3>
NOT:
STATUS = EXTRACT(CUST_REC,3,0,0) CUST_REC = REPLACE(CUST_REC,3,0,0,4)
TRANSFER
If you are assigning the value of one variable to another variable and then nulling the first variable, use the TRANSFER statement.
This is a very fast operation, in which only descriptors are changed, not string space.
DO:
TRANSFER A TO B ;* ("A" becomes null)
NOT:
B = A A = ""
CASE vs. IF...THEN
When compiled, CASE statements become nested IF…THEN statements. Therefore, test for the most likely conditions first.
Existence
Check for the existence of a variable by checking for its length.
DO:
IF LEN(KEY) THEN
NOT:
IF KEY THEN
NOT:
IF KEY <> NULL$ THEN
Examples
Figure 1
/*************************** PROGRAM NAME This program is proprietary and is not to be used by or disclosed to others, nor is it to be copied without written permission from REVELATION TECHNOLOGIES, INC. ÷ VERSION : (Version of the program) ÷ PURPOSE : (Short description of why this program exists) ÷ AUTHOR : (Name of the original programmer) ÷ CREATED : MM-DD-YY ÷ PROCEDURES : (How to call the program, setup requirements etc.) ÷ WARNINGS : (What might happen if you're not careful. Include any error conditions that you know about but that are not being checked. ÷ THEORY OF OPERATION : (Description of how the program is constructed. Include descriptions of the algorithm(s) used. If necessary, refer to another record containing more information. ÷ REVISION HISTORY (Most CURRENT first) : DATE IMPLEMENTOR FUNCTION -------- ----------- -------- MM-DD-YY initials Modification ***************************/ *÷ COMMON Variables (Terminate with '%') : *÷ LABELED COMMON Variables (Terminate with '@') : *÷ EQUATE Variables (Terminate with '$') : EQU RTI$ TO "Copyright (C) 1990, REVELATION TECHNOLOGIES, INC." EQU TRUE$ TO 1 EQU FALSE$ TO 0 EQU YES$ TO 1 EQU NO$ TO 0 EQU OTHERWISE$ TO 1 EQU NULL$ TO "" EQU SPACE$ TO " " *÷ MESSAGES called (Terminate with '$') : *÷ DECLARED - FUNCTIONS called : *÷ DECLARED - SUBROUTINES called : /************************** ÷ INDIRECT - FUNCTIONS/SUBROUTINES called if known (Make COMMENTS) : **************************/ *÷ PROGRAM TOP
Figure 2
DONE = FALSE$ NO_REC = FALSE$ RECS_DONE = 0 LOOP READNEXT KEY ELSE DONE = TRUE$ END UNTIL DONE READ @RECORD FROM CUSTOMER_FILE, KEY THEN GOSUB PROCESS_RECORD RECS_DONE += 1 END ELSE NO_REC = TRUE$ GOSUB REC_ERROR END REPEAT
Figure 3.
BEGIN CASE CASE CHARACTER MATCHES '0A' GOSUB ALPHA_INPUT CASE CHARACTER MATCHES '0N' GOSUB NUMERIC_INPUT CASE OTHERWISE$ GOSUB INVALID_INPUT END CASE