|
|
Home » Tech Center » Cobol Style Guide
TABLE OF CONTENTS
INTRODUCTIONThis document provides coding style guidelines for Cobol/400 and ILE Cobol/400 source code published in NEWS/400 magazine and distributed via electronic media.
Purpose
Cobol/400 and ILE Cobol/400
GENERAL LAYOUT
Introduction
Consistency and flexibility Flexibility, on the other hand is no excuse for sloppiness or laziness. Don’t use flexibility as an excuse for not taking the time to align code or to revise variable names so they use consistent abbreviations and structure. One of the secrets to highly readable code is re-reading and revising your code to improve the clarity and layout. Flexibility also means you may decide to use a different standard than the ones suggested in this document when you believe there’s a clearer alternative for writing easily comprehended code. In many places in this guide, alternative styles are suggested. Whichever style you decide to follow, be sure you follow it consistently, especially within all source members for a single application. Over time, you may find ways to improve your style. Don’t change your style with every program you write, and never mix inconsistent styles in the same source member. But it’s OK to change your cosing "standards" from time to time, as you improve your coding style.
Blank lines, whitespace, and other separators
Dashed line comments * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - * = = = = = = = = = = = = = = = = = = = = = = = = = = = = Solid rows of * should be used sparingly, if at all, since they create a dense image on the screen or listing that makes adjacent code harder to read. As an example of how to use dashed lines:
Use a blank line to separate different parts of a program, and to make it easier to tell which statements form a related group:
When you need to provide even more visual separation for "chunks" of code, use two blank lines — but don’t overdo it. Use the two-blank-line separator sparingly — such as when you need to indicate a larger grouping of code within which you’ve used single blank lines to separate sub-groups. SpacesUse spaces (or Tabs, if you have a PC-based editor) to provide indentation and alignment (see below). Optionally, you may want to improve the readability of arithmetic and Boolean expressions by using a space after each ( and before each ), as in the following examples: Compute AmtDue = Principal * ( 1 - Discount ) if ( AmtDue > CreditLimit ) And
IDENTIFIERS AND RESERVED WORDSCobol allows upper and lower case characters in identifiers and reserved words, as well as the dash character (–) as a separator. Use mixed case because it’s easier to read than all lower or upper case.
Reserved words
Alternative style: If a reserved word has a dash, capitalize only the first letter.
Connectors
Alternative style: Capitalize the first letter of all connectors, too. IdentifiersCapitalize the first letter of each part of an identifier. Do not use the dash separator. Use lowercase for the of in qualified names.
CustomerName of InpRcd Alternative style: Capitalize only the first letter of the identifier. Use the dash separator between parts of the identifier.
Customer-name of Input-record The basis for using dashes to separate parts of a multi-word identifier is this style’s similarity to the use of a space to separate English words. With this style, it’s better to use mostly full words rather than lots of abbreviations. Consider the examples below as you decide which style to adopt.
Parts of identifiers and abbreviations
Parts of an identifier
Abbreviations For most abbreviations, use at least 3 characters. Where there’s a standard CL abbreviation, such as Dsp for "display", use that abbreviation. If there’s a good 3-character abbreviation, use it rather than a longer abbreviation (e.g., use Idx instead of Indx). For new, 3-character abbreviations, form most abbreviations with the first letter of the abbreviated word and the two consanants that are most important in pronouncing the name. For example, use Hdr for "header". Don’t simply foreshorten words to create abbreviations. The following examples demonstrate this point:
The rules for forming abbreviations aren’t absolute. In some cases there may be an obviously more familiar or clearer abbreviation; for example, Buf is a familiar abbreviation for "buffer", and is preferable to Bfr It’s also preferable to use Cust (for "customer") and Cost (unabbreviated) in the same application, rather than Cst and Cost because you reduce the chance for confusion. Generally, don’t mix abbreviated and unabbreviated parts in the same name. If you do use both, generally you should use all abbreviations for the first part(s) of the identifier and use all unabbreviated terms for the last part(s) of the identifier. Here are some examples of acceptable identifiers:
Most importantly, review your identifers to make sure you haven’t used a variety of approaches to abbreviation — especially in the same source member. Prefixes and SuffixesWhen a set of identifiers need to be grouped, use either a group item declaration and qualified names (of), or use a common prefix or suffix as part of the name. With this style, choose either prefixes or suffixes to group related identifiers—don’t use prefixes for some groups and suffixes for other groups. Using prefixes is generally preferable to suffixes. Do not use WS or similar prefix to identify variables declared in Working-Storage—this is just "noise". Do not use numbers as prefixes to program, section, or paragraph names. Names such as 100-READ-CUST-FILE are an antiquated, less readable style. The historical purpose of the numeric prefix was to make it easier to find a routine in a listing; however, alphabetizing routines within groups (see below) achieves the same effect.
Mnemonics (named constants) You can group mnemonics under one or more 01-level items.
01 Constants. You may want to put frequently-used mnemonics in a Copy member that you include at the beginning of the Working-Storage Section. Note that for constant declarations, which will all have a Value phrase, but not many other phrases, you can usually put the Value phrase on the same line as the Pic or Like phrase. The Value phrases should be aligned. Alternative style: Define frequently-used mnemonics in the standard Replace directive (see below).
Zero
Compute RcdCnt = RcdCnt + One Similarly, there's no need to use a word for the number 0. Zero just makes code more cumbersome to read. Note that you should use a meaningful mnemonic for 1, when it’s isn’t just used as a "unit". For example, if the minimum purchase on an order is one dollar, code::
01 MinOrdTot Like Dollar 1.00.
PROGRAM LAYOUTAt the beginning of each source member, include a standard header that describes the program purpose, interface, and maintenance history. Immediately follow the source member header with a Copy for the standard Replace directive. Here’s an example from the beginning of a source member: * = = = = = = = = = = = = = = = = = = = = = = = = = = = = = Id Division. Program-Id. CustMaint. * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - * Purpose. Customer maintenance * * Allows browse and update of Customer master file. * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - * Parameters. * Name Type Usage Comment * -------- ---------- ------ ------------------------------ * CustId CustIdType In If valid, only this customer * is displayed. Otherwise, a * Customer list is presented for * user selection * RtnSts RtnStsType Out Standard return status structure * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - * History. * Version Date Person Purpose * ------- --------- --- -------------------------------- * 1.00.01 13-Jun-95 PTC Original version * 1.00.02 25-Aug-95 PTC Added discount calc * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - • Copy StdReplace. For ILE nested programs, you can use an abbreviated version of the header. Note that the Id Division only contains the Program-Id paragraph. The other paragraphs are optional and are generally better omitted.
ILE Cobol nested programs In ILE Cobol, a program can contain "nested" programs. Nested programs provide a better alternative than paragraphs for organizing programs because they allow parameters and local variables. You should arrange nested programs so you get the intended scope of variables and nested program names. Local variables (no Global attribute) can be referenced only within the same program. Global variables can be referenced anywhere within the same program or any program it contains. Nested program names without the Common attribute can be Call’d only in the containing program. Nested program names with the Common attribute can be Call’d in the containing program and any programs contained by the containing program. In general, don’t use global variables. Use parameters to pass data between routines and use local variables for each routine’s private work variables. The major exceptions to this guideline are file names, which you may want to share among a set of I/O routines. When using global variables, declare them at the lowest level that provides the necessary sharing; do not simply declare them in the outer program.
Flat vs. Hierarchical program Nesting The flat approach is similar to the way paragraphs are used. It’s also similar to C’s organization of functions. This approach is simpler to manage, but provides less control over the visibility of routine names within a single source member. With well-chosen nested program names, however, name collisions are not usually a serious problem. With the flat approach, all global variables are visible throughout the source member, so you must use at least a partially hierarchical approach if you need to declare shared variables that have a more limited scope than member-wide. The hierachical approach is similar to the way Pascal’s organization of procedures. It permits a high degree of control over the scope of both variables and nested program names.
Ordering Nested Programs and Paragraphs
Do not try to order nested programs or paragraphs based on where Calls or Performs appear (i.e., attempting to put a nested program after the nested program in which it’s called). There’s no simple method of sequencing nested routines based on a source file’s static invocation structure, and this approach can make locating a routine difficult.
Environment Division
Select statement
Select CustFile
Assign Database-Cust
Organization Indexed
Access Dynamic
Record Key Externally-Described-Key
File Status CustFileSts. Use a consistent suffix (e.g., FileSts) for the File Status variable name. Optionally, you can simplify file declarations by using a set of Copy members, one for each combination of Organization and Access. The internal file name (and optionally external name) can be Replacing parameters (see section on Copy). Working-Storage Section
Copy statements for standard declarations
Working-Storage.
Copy StdDtaType.
Copy StdWrkVar.
Copy Dds-All-Formats of CustMast with Alias.
Order of declarations If you have any Global declarations, group them alphabetically at the beginning of Working-Storage (after standard Copy’s). If you use External declarations (not recommended), group them alphabetically before the Global declarations. Use a comment and dashed line to set Global declarations off from local variable declarations:
* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - * Global declarations. 01 CustFileSts Global Like FileStatus. * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - * Local declarations. .... If you have no global declarations, you don’t need the "Local declarations" comment. For scalar variables (not structures, arrays, or pointers), you can either declare them as a series of 01-level variables (do not use the vestigial 77-level), or declare them as a series of 02-level variables under one or more encompassing groups. Within each set of variables, order the declarations alphabetically. Using 02-level declarations may save some storage (because Cobol aligns 01 declarations), which may be significant if you have lots of scalar variables. However, don’t overuse grouping as a way of collecting loosely-related variables. Instead, use a common prefix in the variable identifiers. Groups should normally be used to define record structures which are aggregates of closely-related variables that are passed between routines.
Level numbers
Incrementing level numbers by 1 reduces the potential for inadvertent errors in group Move and Move Corresponding operations, as well as for incorrect grouping of subordinate items. If a group item is re-structured, renumber the levels to maintain the proper level numbers for each item. For levels 02 and higher, use a single space after the level number.
Indentation Alternative style: Put 2 spaces between the level number and the identifier, and indent each level by 4 columns.
Alignment
Filler keyword
Order of keywords
This approach has the following rules:
Simplify declarations and reduce truncation and type-mismatch errors by using a small number of standard data types. Place standard "type" declarations in a Copy member such as the following:
01 StdDataTypes.
02 Ptr Global Pointer.
02 Integer Global Pic S9(9) Binary.
02 Char Global Pic X(1).
02 Str50 Global Pic X(50).
02 Bool Global Pic 1.
... Use a Copy to include these definitions at the beginning of Working-Storage.
Working-Storage Section.
Copy StdDtaType. Following this Copy, add other type declarations, if needed for variables in the specific program:
Then use the Like keyword for subsequent declarations:
You can use Like to refer to previous declarations that are "type" declarations or to fields in database records that are Copy’d into the program. Note that you don’t need to declare a "data type" variable for a unique declaration. However, most program variables are either of some general type (e.g., integer), or they’re defined in or based on a field in a file. Thus, you should have very few Pic declarations in most programs.
Condition names (88-level)
Note the following conventions:
You can follow other consistent conventions for forming condition names, but don’t use a mixture of styles. For example, don't use NotApiFldOverrides (Not prefix) in one case and ApiRtnErrorOff (Off suffix) in another case. Optionally, you can create a Copy member with a single Replacing parameter to define Boolean conditions (see below). With these conventions, you can use the following easily comprehended assignments and tests:
Set ApiFldOverrides to True Set NotApiFldOverrides to True If ApiFldOverrides ... If NotApiFldOverrides ... or If Not ApiFldOverrides ... Note how the Not prefix convention lets you use either of two similar approaches to testing for the false condition. Use one or the other of these styles consistently. You can also use simple assignment of one condition to another (where sensible):
Move AllowCreditIncreaseCnd to
ShowCreditLimitCndFor non-Boolean enumerated conditions, use a style like the following:
01 ApiFldUsageEnum Like Char. This convention uses Enum as the suffix of the variable to distinguish it from a Boolean variable, and all values begin with the same root name as the variable and have a descriptive suffix that begins with Is. This provides readable tests like:
Don’t use condition names for non-Boolean variables unless you can define a complete enumeration (all values can be covered by an 88-level condition name).
Initial values If Ptr Not = Nullto guard pointer operations. Do not depend on the declared initial value (the Value phrase) of a variable (e.g., a counter) as a way to initialize the variable before it’s used. Always explicitly initialize variables in the Procedure Division. This approach makes your code correct for repeated execution, whereas depending on declared initial values makes your code correct only the first time it’s Call’d or Perform’d. For counters, sums, and other simple scalar variables, use Compute or Move to intialize a variable:
For output records, you can use the Initialize statement to move zero or spaces to fields in preparation for building a new record. Don’t use Initialize to set a counter to zero (it’s an unnecessarily indirect way to change the starting value).
Linkage Section Declare program parameters first, in the same order as they appear on the Procedure Division Using clause. Follow parameter declarations with local, based variable declarations in alpabetical order.
Procedure Division
Procedure Division Using ...
BeginProc.
... statements
Goback.Note that with nested programs, there’s no need for sections or paragraphs (other than BeginProc). Instead of explicitly coding Goback, you can use a Replace "macro" (i.e., EndProc), as decribed below, which includes the Goback..
The Procedure Division of a Cobol/400 program should have the following layout:
Procedure Division Using ...
BeginProc
... statements
Goback.
... paragraphs A Goback statement functions like a Stop Run when executed in the main program of a run unit, and functions like an EXIT when executed in a subprogram.
Use of the period and comma characters Periods are not required after most Cobol Procedure Division statements. Follow one of two styles: either use a period after every statement, or use a period only when required, e.g., after the last statement in a program. (Note that the AS/400 debugger recognizes statements based on periods, so you may want to use periods to provide individual breakpoint targets.) With either style, use explicit scope terminators (e.g., End-If) as the primary means of specifying the program’s control structure.
Indentation Use "adaptive" indentation and alignment for multi-line statements, such as Call.
Align scope delimiters (e.g., End-If) with the beginning of the statement (e.g., If). (Don’t indent the scope delimiter to align with the subordinate code.)
Alignment
For a series of Compute statements, always align the =. Also align the other operands when the alignment improves readability of the sequence of operations, as shown below:
Here are some more examples of good alignment:
Move CreditLimit of CustRcd
to CreditLimit of CustDsp
String "FieldList for " Delimited by Size
FileLib Delimited by Space
"/" Delimited by Size
FileName Delimited by Space
":" Delimited by Size
RcdFmt Delimited by Space
into UsrSpcText
End-String
Search MapTableEntry
When MapKey = KeyIn
Move MapValue(MapIdx) to ValueOut
Set KeyFound to True
End-SearchNote how the Move, String, and Search examples use right-alignment for the to, into, and When keywords. This type of alignment avoids overly deep indentation and keeps the operands in an easy-to-read columnar layout.
Symbols vs. reserved words
Arithmetic assignment
There are a two cases where the specific arithmetic statement (or Move) may be required:
When you have a compound logical expression, unless it is very short, place each part of the expression on a separate line. Place the And and Or logical operators at the end of the line. Align the operands, the comparison operators, and the And and Or keywords. Optionally, enclose each part in parentheses. Use one blank line after the last part of the condition.
If ( AmtDue > CreditCheckMinAmtDue ) And
( AmtDue > CreditLimit ) And
( CreditSts Not = CreditStsGood )
Call "CreditExceeded" Using ...
...
End-If.
Never use Cobol's "abbreviated" logical expressions, such as:
This is error-prone and unclear. Always fully parenthesize any compound expression that has both an And and an Or. This avoids errors caused by misunderstanding the compiler's default precedence.
CODING PRACTICESIn most cases, don’t share data among ILE Cobol routines by using Global or External data. Instead, pass shared data as arguments to called nested or external programs. Keeping data private reduces the possibility for accidental modification, and lets your code clearly show the interfaces between different parts of the program. One exception to this guideline is for internal file names that you want to reference in multiple nested programs that provide I/O routines. Since Cobol doesn’t allow you to pass a file name as an argument, you must declare the file name with Global on its FD entry.
Don't use Goto's
Don't Use Next Sentence
"Guarding" Code
Loops
Division by zero
Arithmetic overflow
I/O errors
Pointer operations
If Ptr Not = Null
Call results
Modifying parameters and passing arguments by content In a called program, you shouldn’t modify output or input-output parameters until the very end of the program (or after you’ve made a copy of all input and input-output parameters). The following example shows how problems can occur if you modify an output parameter too soon.
Called program
* - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
* Parameters.
* Name Type Usage
* -------- ---------- ------
* InParm Integer In
* OutParm Integer Out
...
OutParm = 0
OutParm = OutParm + InParm
Calling program
01 TmpVar Like Integer.
...
Compute TmpVar = 1
Call "Pgm" Using TmpVar
TmpVar
In this example, the value of TmpVar after the call will be 0, not 1. The safe way to code this example is:
This example is highly simplified to illustrate the problem and solution, but you must be very careful anytime you have a called program that has an input or input-output parameter of the same type as an output or input-output parameter, since the calling program may pass the same variable for both.
REUSABLE CODEILE Cobol supports a Replace directive, which you can use to define one set of commonly-used "shorthand" terms (these are known as "macros" in some other languages). Put your Replace directive in a Copy member that you copy at the beginning of your program. Here’s a sample StdReplace Copy member:
*Control NoSource
* = = = = = = = = = = = = = = = = = = = = = = = = = = = = =
* Copy member: StdReplace
* - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
* Purpose.
* Defines standard Replace terms.
* - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
* Replacing parameters. None.
* - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
* Dependencies. None.
* - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
* History.
* Version Date Person Purpose
* ------- --------- --- --------------------------------
* 1.00.01 13-Jun-95 PTC Original version
* = = = = = = = = = = = = = = = = = = = = = = = = = = = = =
Replace
==Proc== by ==
Id Division.
Program-Id. ==
==EndProc== by ==
Goback.
End Program ==
==TrueVal== by ==B"1"==
==FalseVal== by ==B"0"==
.
*Control Source
The definitions of Proc and EndProc enable you to declare nested programs as follows:
Proc LookUpPart.
...
Procedure Division Using ...
BeginProc.
...
EndProc LookUpPart. The StdReplace Copy member is a good place to define mnemonics for commonly used constants, e.g., TrueVal and FalseVal.
Copy members Each Copy member should have a header as shown in the following example:
*Control NoSource
* = = = = = = = = = = = = = = = = = = = = = = = = = = = = =
* Copy member: MapTable
* - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
* Purpose.
* Declare a table that maps a key to a value.
* - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
* Replacing parameters.
* Name Rqd Comment
* ------------- --- -------- --------------------------
* TableName Yes Group item identifier
* TableSize Yes Max elements in table
* KeyType Yes Key data type, e.g., Integer
* This is referenced by Like clause
* ValueType Yes Value data type, e.g., Str50
* This is referenced by Like clause
* - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
* Dependencies.
* 1. StdDtaType Copy member.
* 2. Accessed by MapKeyToValue nested program, which is
* defined in MapProc Copy member.
* 3. Must be initialized before accessing.
* - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
* History.
* Version Date Person Purpose
* ------- --------- --- --------------------------------
* 1.00.01 13-Jun-95 PTC Original version
* = = = = = = = = = = = = = = = = = = = = = = = = = = = = =
*Control Source
01 TableName.
02 TableMax Like Integer
Value TableSize.
02 TableEntry Occurs TableSize.
03 MapKey Like KeyType.
03 MapValue Like ValueType.The *Control Source/NoSource eliminates the header comments from the compiler source listing (regardless of whether or not you specify Supress on the Copy statement). When you use the Replacing phrase on a Copy directive, align the from and to strings, as shown below:
Copy MapTable Suppress Replacing
TableName by JobIdToTitleMap
TableSize by 1000
KeyType by JobId
ValueType by JobTitle. The optional Suppress keyword excludes the contents of the Copy member from the compiler listing. Generally, you shouldn’t need to see the generated code once you have a well-tested Copy member. Note that ILE Cobol allows nested Copy members (a Copy member can contain a Copy directive), but you cannot use the Replacing phrase with nested Copy’s.
Replacing part of an identifier
Select (File)File
Assign Database-(File)
Organization Indexed
Access Dynamic
Record Key Externally-Described-Key
File Status (File)FileStatus.
...
Copy SelIdxDyn Replacing
==(File)== by Cust. Cobol recognizes the () as delimiters when it preprocesses the source and matches the three tokens in the ==(File)== pseudo text in the Replacing clause of the Copy. The resulting code is:
Select CustFile
Assign Database-Cust
Organization Indexed
Access Dynamic
Record Key Externally-Described-Key
File Status CustFileStatus.
ILE Copy with file formats
ADDITIONAL NOTESRecursive program calls (a program calls itself directly or indirectly) are supported by Cobol/400, but not by ILE Cobol.
Default linkage type in ILE Cobol If you plan to use bound procedure calls for most ILE Cobol applications, you may want to consider changing the CrtCblMod command default to LinkLit(*Prc) or placing a Process LinkPrc directive at the beginning of most source members. Then code a Linkage Type Program clause in the Special-Names paragraph to explcitly specify any dynamic program calls. The advantage to this approach is that the CrtPgm command will catch unresolved bound procedure calls during the bind process. If you forget to designate a dynamic program call, you’ll get an error during the bind step, which is easy to correct. On the other hand, when Linkage Type Program is the default, if you forget to designate a bound procedure call, the error won’t be caught until the Call executes and the system can’t locate a program with the name on the Call. |
| Sponsored Links | Featured Links | |
Penton Technology Media Connected Home | SQL Server Magazine | Windows IT Pro Report Bugs | Contact Us | Comments/Suggestions | Terms of Use | Privacy Statement | Trademarks See Membership Levels | Subscribe | Free E-mail Newsletters | Free RSS Feeds | My Profile | Upgrade Now | Renew Now © 2009 Penton Media, Inc. System i is a trademark of International Business Machines Corporation and is used by Penton Media, Inc., under license. SystemiNetwork.com is published independently of International Business Machines Corporation, which is not responsible in any way for the content. Penton Media, Inc., is solely responsible for the editorial content and control of the System iNetwork. |