|
| ||
|   | Home->Services->Software Development->IDL->Code Standard | ||
|   |   |
Version: 1.33
Release Date: Oct 23, 2002
The following topics will be discussed in this document:
This document is especially written for IDL (Interactive Data Language) but many guidelines applies to most declarative programming languages.
It is assumed that the reader has basic knowledge of the IDL language and development environment.
This standard is aimed at building applications (or libraries) in IDL. This means that the goal is to produce very high quality code that is easy to maintain. To follow these practices takes more time than just jumping directly into coding, ad hoc style. At least for a minor project. But as soon as the task grows (as they always do), it is well worth the effort.
Many issues described in this document may seem superfluous to a programmer with a working knowledge of C++ (or similar). The difference is that C++ is strongly typed and puts emphasis on catching errors during compile time to avoid runtime errors. IDL on the other hand is dynamically typed and interpretated which means that a certain amount of safety precautions must be employed by any programmer doing applications in IDL.
The file extension for IDL files should be: .pro
Class (or Structure) definition files must be named: <ClassName>__define.pro
For cross platform portability it is recommended that file names are written in lower case.
It is not necessary to limit file names to 8 characters unless specifically dictated by the project.
Warning: This is a potential source for serious errors since IDL accepts several functions with the same name! (The last one processed/compiled is the prevailing one.)
Warning: Also make sure to avoid names that are already defined as IDL functions or procedures. The code will compile and execute, but the user defined functions and/or procedures will never be called!
Note that this is an ambiguity in it self. Referring back to the note above which states that the last instance of a function / procedure is the prevailing one, this would indicate that the IDL libraries are the last to be compiled. This can of course not be true, since then no user defined code would compile!
| Type | Prefix | Example |
| Boolean | b | bChanged |
| Integer | n | nIndex |
| Long | l | lWidgetID |
| Float | f | fWeight |
| Double | d | dVelocity |
| String | str | strPersonName |
| Array | Arr | strArrNames |
| Pointer | p | pDialogWindow |
| Object | obj | objPlotData |
| Structure | struct | structCompensationData |
| Global | g | ngUserCount |
| Member (of class) | m_ | m_pEmployeeList |
Note that type prefixes may and should be combined. I.e. a global integer may be called "ngCalculationsDone”.
Warning: This is a potential source for serious errors since IDL only catches multiple declarations of common blocks with the same name if subsequent declarations contain more variables, i.e. an extension of the common block! Otherwise this is not caught at all. (Another reason not to use them.)
This section describes the basic guidelines for source code formatting.
The following information should always be listed before each function or procedure in the source code:
It is of importance to follow a template exactly since this facilitates easy (automated) extraction of source code documentation. (You don't want to do that by hand afterwards do you?)
The following text depicts an example layout of a function or procedure header (‘;’ denotes a comment) :
;***********************************************************************
;+
; Name : LineIsMonotonic
; Category : Data Manipulation
; Design Ref. : <xxx>
; Purpose : Tests if a line is monotonic (increasing or decreasing)
;
; Dependencies :
;
; Name Type Pass by Purpose
; Inputs : fArrLine Float Array Value Line to test
;
; Outputs :
;
; Keyword Type Default Purpose
; Keyword Inputs :
;
; Type Comment
; Return value : Boolean !TRUE if line is monotonic
;
; Global data :
;
; Comment :
;
; This function checks if a line is monotonic
; Example:
;
; |
; | / Will return !FALSE
; | \/
; -------
;
; |
; | / Will return !TRUE
; |/
; -------
;
; Revisions : J. Borg, 21.06.2002 - Created function
;-
;***********************************************************************
For classes we specify the following block in the beginning of the class definition file:
; **********************************************************************
; * Class : FiFo
; * File : fifo__define.pro
; * Inherits From :
; * Purpose : Just a First in, First out stack.
; * Dependencies : DynArray
; * Known Limitations : None
; * Possible Enhancements :
; * Created : 21/07/02, J. Borg
; * Test Program : LiFo__Test
; **********************************************************************
; **********************************************************************
; Description:
; Class to provide a simple FiFo stack.
;
; Typical Example (no error handling):
;
; fifo = Obj_New("FiFo")
; fifo->Push(42)
; fifo->Push(4242)
; fifo->Push(424242)
; fifo->Pop(data) & Print, data ; Will print '42'
; fifo->Pop(data) & Print, data ; Will print '4242'
; fifo->Pop(data) & Print, data ; Will print '424242'
; fifo->Pop(data) & Print, data ; Error. Will return -!FALSE
; Obj_Destroy, fifo
; **********************************************************************
In front of the class member functions that should be accessable by users of the class we put the normal function documentation as per above.
Code indentation must conform to the follow standards:
The following example illustrates the recommended indentation of a function or procedure:
Procedure Indent
<Statement>
<Statement>
<Statement>
Loop A
<Statement>
<Statement>
Select Case X
Case 1:
<Statement>
<Statement>
Case 2:
<Statement>
End Case
<Statement>
End ; End of Loop A
<Statement>
End ; End of Procedure Indent
Note: For anybody who has received the source code from somebody else, the rule about always replacing tabs with spaces is obvious. Regardless of what one does, the tab setting will be different for different users and one can only be certain that it looks good on one's own machine. Spaces on the other hand are very portable!
Source code line formatting must conform to the follow standards:
Single line comments
The comment character is ‘;’. The rest of the line is treated as a comment.
Multiple line comments
No characters for multiple line comments exists.
Comments must conform to the follow standards:
The following example illustrates how code could be commented:
;****************************************
;* The following code does good things. *
;****************************************
<Statement>
<Statement>
<Statement>
; Loop over *valid* instances in list only
Loop
<Statement>
<Statement>
Select Case X
Case 1:
<Statement>
;
;Following statement added because
;of new requirement, ref. PRD 173
;
<Statement>
Case 2:
<Statement>
End Case
<Statement>
End Loop
;
; Clean up and exit.
;
<Statement>
<Statement>
; Release all memory.
<Statement>
It is recommended to always use spaces around all tokens of the code, see examples:
nInt=5 ; Not good!
nInt = 5 ; Preferred
If(nInt==5) ; Not good!
If ( nInt == 5 ) ; Preferred
If (nInt == 5) ; Also accepted but not as good
The reason for this is that it makes searching after variables much easier. If the programming style without spaces is used, a search after "nInt” would also find all instances of "nInteger”, "nIntTemp”, etc. If adequate spacing is used, it is possible to search for "nInt " (with trailing space) and only find the relevant variable.
Apart from aiding searching, proper use of separating spaces also makes the code easier to read! It has been common practice since a thousand years to separate words by spaces. Until 1957 when FORTRAN came along and changed all that...
IDL does not enforce the use of parenthesis around conditional expression. It is recommended to use them anyway to enhance the appearance of the source code.
Example:
If nIndex EQ 4 Then ; Allowed syntax. Should not be used.
If (xIndex EQ 4) Then ; Preferred usage
The IDL syntax allows access to array elements by using both square brackets ‘[ ]’ and normal brackets ‘( )’. This is very unfortunately since function calls also use ordinary brackets.
To minimise the ambiguity, square brackets must always be used for array access.
Example:
nArr = IntArr(5) ; Create integer array with five elements
nA = nArr(1) ; Allowed syntax. Do not use!
nB = nArr[1] ; Preferred usage.
Functions and procedures must conform to the follow standards and guidelines:
Note: This might sound like a harsh rule that all procedures must be turned into functions that return their result status. But our experience is that it is very rare that there is not something that might go wrong within a function. And if it goes wrong, the caller of the function must be able to find out. In a recent project of medium size (but high complexity) there was 2 (two!) out of ~350 procedures that we let be as procedures. The rest were functions.
Variables must conform to the follow standards:
Note: Variables should not be declared until they are actually needed. The typical Fortran or Standard Pascal way of declaring all used variables at the top of the function is considered poor programming style. Don't declare them until you need them!
Due to a limitation in the IDL language it is not possible to define constant variables (i.e. variables that retain the same value throughout the program execution) except as system variables with the command: DefSysV, Name, Value, Read_Only, where Read_Only should be set to 1.
The disadvantages of IDL system variables are:
Nevertheless the use of constant system variables are in certain cases very useful and the following should always be declared:
!TRUE
Variable of type Integer that should be used as a return value from functions. Necessary since no built in Boolean type exists within IDL.
Set by the command:
DefSysV, ‘!TRUE’, 1, 1
!FALSE
Variable of type Integer that should be used as a return value from functions. Necessary since no built in Boolean type exists within IDL.
Set by the command:
DefSysV, ‘!FALSE’, 0, 1
It is also recommended to define the following:
!DEBUG
Variable used to execute conditional code for debugging purposes.
Set by the command:
DefSysV, ‘!DEBUG’, 1, 0
Example:
If (!DEBUG) Then Begin
Print, "Variable fVariable = ” + String( fVariable )
EndIf
Note that the !DEBUG flag is not set to read only since typically this is handled by a pair of functions: SetDebugModeOn & SetDebugModeOff, to be able to toggle the state at runtime.
Type definitions, i.e. user defined types, are not available in IDL. Use classes.
All classes should have a member procedure named PrintSelf that is to be used for debugging, trace output, etc.
The signature is: Pro <Class>::PrintSelf, File = File, Indent = Indent
An example:
;***********************************************************************
;+
; Name : PrintSelf
; Purpose : Prints the contents of the object.
;
; Name Type Default Purpose
; Keywords : File AsciiFile Obj_New() File to write to
; Indent String "" Any indentation
;
; Type Comment
; Return value :
;
; Comment : Print the contents of the object. For debug or trace.
;-
;***********************************************************************
Pro FiFo::PrintSelf, File = File, Indent = Indent
strArrTxt = ""
strArrTxt = [strArrTxt, "FiFo Stack"]
strArrTxt = [strArrTxt, "----------"]
strArrTxt = [strArrTxt, "No Of Items: " + self.m_objStack->GetCount()]
strArrTxt = [strArrTxt, ""]
If (N_Elements(File) GT 0) Then objFile = File Else objFile = Obj_New()
If (Obj_Valid(objFile)) Then Begin
If (N_Elements(Indent) GT 0) Then strIndent = Indent Else strIndent = ""
strArrTxt = strIndent + strArrTxt
res = objFile->WriteLineArray(strArrTxt)
EndIf Else Begin
For nLoop = 0, N_Elements(strArrTxt) - 1 Do Begin
Print, strArrTxt[nLoop]
EndFor
EndElse
self.m_objStack->PrintSelf, File = File, Indent = Indent
End
Comments:
Loops must conform to the follow standards:
Example:
bBreak = !FALSE
While (Not bBreak) Do Begin
DoSomeStuff()
If (Not DoSomeOtherStuff()) Then $
bBreak = !TRUE
EndWhile
Another problem with loop control within IDL can be the problem with dynamic typing. A typical FOR loop looks like this:
For I = 1, 5 Do...
A problem can occur if the literal 5 is replaced by a computed MaxValue that during the calculation may suffer from a small rounding error so that it ends up as for example 4.9999998. In some case the loop will only execute 4 times and not 5 as intended. Tests on a Digital Alpha workstation show that a value of 4.99999998 will execute the loop five times, whereas a value of 4.999998 will only generate four loops!
Always check computed upper or lower bounds of loops!
Note: There are other problems with loops in IDL when using Byte or Long index variables. Refer to the IDL documentation for more information.
Note: It is possible to specify the increment in an IDL FOR loop. If the increment is a computed value it has to be ensured that the value is not zero (causes an infinite loop).
Example:
Case str Of
"A": GOTO, FirstFour
"B": GOTO, FirstFour
"C": GOTO, FirstFour
"D": Begin
FirstFour:
; Do stuff here...
End
"E": Begin
; Do other stuff here...
; Etc.
End
EndCase
Global data should be used as little as possible. If use is unavoidable, global data must conform to the follow standards:
The use of global data is not allowed for user interface components.
Example:
A generic widget procedure is used to retrieve data from the user and stores the values in a common block. If more than one of these generic widgets are used at the same time, there exists no way to determine the value of the common block.
In general the use of common blocks is discouraged within IDL.
Text messages intended for the user should if possible be stored in a string resource file. This will enable easy creation of localised versions of the software.
Note: This holds true regardless whether you believe that the application will only be used in one location! This is such an easy modifiction that it should always be performed.
Structures are a very good way of grouping associated data together and should be used as much as possible if the construct is allowed in the selected programming language. Example of uses are:
NOTE: Structures should only be used for very simple constructs. Any data that has associated behaviour should be contained in a class.
Recursion should be avoided, especially if constructing a safety critical application. If that is not possible or even recommended due to the nature of the problem (don’t try to traverse a linked list without recursion!), the use of recursion must be well documented and carefully verified.
And if you do, make sure to include the "FORWARD_FUNCTION" declaration inside your function:
Function RecurseMe
Forward_Function RecurseMe
<Do Stuff>
End
(Of course there are problems when recursion is the most suitable solution for the problem. But if you know that this is the case, then you as a programmer are probably familiar with the concept and you also know how to properly terminate. Then by all means use it. But don't say we didn't warn you...)
The following general guidelines apply for memory allocation / memory management:
The preferred way to catch memory allocation errors is as follows:
Catch, nErrorStatus
If (nErrorStatus) Then Begin
<Do some error handling. Simple example below:>
Print, "Could not allocate memory!"
Return, !FALSE
EndIf
pArrInput = Ptr_New(Temporary(FltArr(nCols, lRows)))
Catch, /Cancel
Note: Failure to include the last line (Catch, /Cancel) will cause the program to use this error catch for all subsequent errors. This is not recommended since it makes it impossible to identify individual errors.
IDL allows the use of the C style conditional operator. The conditional operator is only shorthand for a standard IF THEN ELSE and must not be used. It saves a few keystrokes but can make reading of the code much harder, especially within complex contexts.
Example (the following statements produce identical results):
z = (a GT b) ? a : b ; Legal syntax. Must not be used.
If (a GT b) Then z = a Else z = b ; Preferred usage.