PINTofALE Programming Style Guide

Last modified: March 10, 2008


Some rules of thumb that we follow when writing PINTofALE programs. None of these are mandatory (except the bit about documentation). But they represent wisdom harvested over years of frustrating missteps, so ignore them at your peril.

Index

Names
Parameters versus Keywords
Procedures versus Functions
Documentation
Failing safe
Indenting
Good programming techniques


Names
  1. Avoid common names that can create namespace conflicts with programs from other software suites
  2. Pick memorable names that give a hint as to functionality (an unfortunate side-effect of this rule is that it leads to excessive punning, but like they say, can't make an omelette without breaking eggs)
  3. As much as possible, keep the names short and use underscores ("_") to improve readability
  4. Do not use capital letters in the name, because IDL is cross-platform and case-insensitive once the program is compiled, but program access for compilation is case-sensitive in UNIX
  5. Do not use spaces or non-ascii characters within the name
  6. Avoid numbers, especially to denote program versions. If you have come up with a radically new algorithm or structure that differs from a previous version, make up a new name!
 
Parameters versus Keywords
  1. Use parameters for inputs and outputs that are necessary for the program to function, and keywords if they can be optional
  2. Always define default values for the keywords inside the program such that the program will compile and run without the user having to specify it
    1. Where possible, set the defaults from system-wide variables
    2. The system-wide variables are not necessarily always defined, so (a) check to make sure they exist with, e.g., defsysv, and (b) have a backup hardcoded default to go in its place
  3. Use scientific necessity as the primary criterion and programmatic necessity secondarily (e.g., in calculating fluxes, the instrument effective area is not always needed, even though it can be a major part of the calculation and could be a humongously large array; make that a keyword!)
  4. Allow parameters to be optional if at all possible, especially outputs
  5. If output parameters are optional, avoid calculating them unless they are present in the call (ditto keywords)
  6. DO NOT overwrite input variables (either parameters or keywords) within the program unless the possible overwrite has been extensively documented and flagged
  7. Always include the keywords verbose=verbose and _extra=e (the latter is a double-edged sword: with it in place, misspelled keywords are not reported, so if you find people typing in the wrong keyword too often, consider adding the misspelt keyword as a duplicate)
 
Procedures versus Functions
  1. Use functions for atomic lower level calls and procedures for collectivistic higher level tasks
  2. If any program is set up to compute one item, make that a function
  3. If multiple items are computed as a matter of course within the program (e.g., spatial coordinates x,y), then make it a procedure
  4. If multiple items are computed, consider returning them in a well-defined anonymous structure, either as the primary output of a function or at least via a named keyword
 
Documentation
Every program must be self-documenting.
  1. When called without arguments, the program should display the usage syntax along with all the parameter names and keywords and quit gracefully
    1. If a program needs no inputs and requires no parameters, then it would be nice if the usage syntax is accessible via keyword /help and/or /usage
  2. Every program must have a header at the top of the file that is organized in the following fashion between beginning `;+' and closing `;-' (note that in detail this is different from the standard IDL system -- use that if you wish, as long as all of the required information is present):
    	;+
    	;procedure/function	name
    	;	description of program, including what it does, what it returns, and in what units
    	;
    	;syntax
    	;	calling sequence, including all optional parameters and keywords,
    	;	as well as keywords that are passed through to subroutines
    	;
    	;parameters
    	;	par1	[INPUT]/[OUTPUT]/[I/O] description of parameter 1
    	;	...
    	;	parN	[INPUT]/[OUTPUT]/[I/O] description of parameter N
    	;
    	;keywords
    	;	key1	[INPUT]/[OUTPUT]/[I/O] description of keyword 1
    	;	...
    	;	key1	[INPUT]/[OUTPUT]/[I/O] description of keyword N
    	;	verbose	[INPUT] controls chatter
    	;	_extra	[INPUT ONLY] pass defined variables to subroutines
    	;		* LIST of such keywords, if any
    	;
    	;restrictions
    	;	requires subroutines: LIST
    	;	other restrictions, if any
    	;
    	;description
    	;	a brief description of the algorithm, how the program works
    	;
    	;example
    	;	a typical worked example which can also serve for regression tests
    	;
    	;etymology
    	;	why is the program named the way it is
    	;
    	;history
    	;	who wrote it and when
    	;	who modified it, when, and why
    	;-
    	
  3. Flag the variable as INPUT or OUTPUT as the case may be. If there is any chance that an input variable can be altered inside the program, then flag it as [I/O] and say how it can get changed.
  4. It is very important to note all bug fixes and code modifications in the history part. It is the easiest way to maintain version control, without having to mess with all the CVS stuff which is more trouble than it is worth for small ops like us.
 
Failing safe
  1. It is better to crash and burn if given bad input than to silently produce perfect garbage
  2. Quit gracefully if possible and report why in the process
  3. Do not assume that that the inputs will be of the correct size or type, and provide alternatives (force a crash, or switch to default, or complain loudly and quit gracefully)
  4. Use of the IDL error catching routines on_error and its ilk are deprecated. It is better to stop right where the error occured than return to the calling program, so that the problem can be easily diagnosed.
  5. Make the code as robust as possible by considering the effect of changes in the input. e.g., the user should not really care how to set the verbosity -- /verbose and verbose='yes' are perfectly equivalent inputs, and should be caught and dealt with silently
 
Indenting
People usually have strong religious feelings about when and how much to indent, and often their editing software imposes its own will. I don't generally care, though I find indented code easier to read, provided that the indentation is not excessive. YMWV.
  1. Indent any code that lies inside a control block, such as `for .. endfor', `if .. endif', `while .. endwhile', etc.
  2. Include comments at the end with additional brackets to show where the block begins and ends, like so:
    	for i=0,N do begin	;{here begins a for loop
    
    	  if A ne B then begin	;(is A ne B?
    
    	    case C of		;{checking on C
    	      opt1: begin	;(
    	      end		;)
    	    endcase		;C}
    
    	  endif else begin	;A.ne.B)(A=B
    
    	    while go_on do begin	;{for as long as go_on is going on
    	    endwhile			;GO_ON}
    
    	  endelse		;A=B)
    
    	endfor			;I=0,N}
    	
 
Good programming techniques
  1. Avoid `goto's
  2. Always use readable, sensible, variable names and comment liberally
  3. Sometimes, in the interests of efficiency, the code may get obfuscated. At such times, keep the non-obfuscated code in the source file, but commented out
  4. Use variables to store numbers, even constants
  5. Avoid using single-letter variable names like x, y, etc.
  6. Never use variable names `temp', `junk', and suchlike on the rhs of a calculation unless it is blindingly obvious (and don't do it even then)
  7. Always reassign the inputs to new variables first thing inside the program, so that there is no danger of ever unintentionally changing the inputs
  8. If there are multiple exclusive branches to the code that are controlled by keywords, specify the precedence explicitly in the documentation and make sure the code can handle the user setting multiple conflicting keywords
  9. Beware of the difference between keyword_set() and n_elements() when checking whether a keyword has been set. The former fails if the keyword is set to zero.
  10. How to deal with some typical "user flexibility":
    • verbose=1, verbose='set', verbose=variable, verbose=array
      		vv=0L & if keyword_set(verbose) then vv=long(verbose[0])>1 
    • clev=fraction, clev=percent, clev=1/number, clev=1-fraction
      		crlev=0.68 & if keyword_set(clev) then crlev=0.0+clev[0]
      		if crlev lt 0 then crlev=abs(crlev)
      		if crlev ge 1 and crlev lt 100 then crlev=crlev/100.
      		if crlev ge 100 then crlev = 1.0D - 1.0D/crlev 
    • /key1, /key2 are exclusive and conflicting. Say key2 takes precedence over key1
      		if keyword_set(key1) then ikey1=1 else ikey1=0
      		if keyword_set(key2) then ikey2=1 else ikey2=0
      		if ikey2 then begin
      		  message,'KEY2 takes precedence over KEY1',/informational
      		  ikey1=0
      		endif
      		...
      		if ikey1 then begin
      		  code corresponding to KEY1
      		endif
      		if ikey2 then begin
      		  code corresponding to KEY2
      		endif 
 


pintofale()head.cfa.harvard.edu (VLK)