Fortran Coding Conventions

This page outlines the coding conventions adhered to in the CLASSIC source code. Some rules are marked as (required), and should be followed at all times unless it is breaking to the code. Other rules are (suggested), meaning it is a good idea to follow them, but failing to do so will not necessarily be grounds for rejecting the code from integration.

Language and Units

  • Program components and comments should be written in simple English unless there exists a very good reason for not doing so (required)
    • Program components include variables, functions, subroutines, modules, filenames, and other constructs
    • Always bear in mind that your code may be read by people who are not proficient English speakers
  • Variables, subroutines, functions, and modules should have names which clearly represent their intended functions (required)
    • Single-letter variables are acceptable only for transient use where the code is easily readable
  • Units must be clearly stated in comments (required)
    • SI units are prefered unless there is a good reason not to use them
    • Standard SI prefixes may be used

Case conventions

  • Case conventions vary by project, and contributors are encouraged to follow whatever case conventions are used. CLASSIC adopts camelCase for most of its subroutine and variable naming, so contributors are expected to do the same (required)

Free-Form Fortran

  • Free-form Fortran 90 / 95 is the standard for this codebase; source code files should have a suffix of .f90 or .F90 (required)
    • F90 is more readable, easier to maintain, and boasts native support for more functionality
    • Fixed-form syntax contains many obsolete features and is more difficult to read

General Structure

  • Modules should always have their own file (required)
  • Related functions, subroutines, and variables should be grouped together into modules with descriptive names (suggested)
  • Modules, functions, and subroutines should always end with its own name to help delineate the extent of that construct within the source code (required)
module foo
      ...
end module foo

subroutine bar( )
      ...
end subroutine bar

integer function moo( )
      ...
end function moo

Whitespace

  • Two-space indentation blocks will be used for this codebase (required)
    • Tabs are not to be used in place of spaces
  • Block comments should follow the same whitespace guideline as the code block they are contained within (required)
  • Trailing whitespace should be eliminated from every line (required)
do i = 1, 50
      if (MODULO(i, 5) == 0) then
        call someRoutine(i)
        ! this is a comment block
        ! using the same whitespace guideline as
        ! the code block in which it is contained
      end if
end do

Alignment

  • Effort should be made to align the attributes of code segments if readability will be improved (suggested)
    • Only do this if it won’t add excessive whitespace
real, intent(in)    :: inputArgument
real, intent(inout) :: inputOutputArgument
real, intent(out)   :: outputArgument

rmlmossac_t   => ctem_tile%rmlmossac_t
gppmossac_t   => ctem_tile%gppmossac_t
litrmsmossrow => vrot%litrmsmoss
Cmossmasrow   => vrot%Cmossmas
dayl_maxrow   => vrot%dayl_max
sdeprot       => class_rot%sdeprot
RADJROW       => class_rot%RADJROW

Line Length and Continuation

  • Maximum line length should be 120 characters including indentation (suggested)
    • This increases readability of code, and applies to comments as well
  • Continuation lines will have an ampersand at the end of the line to be continued (required)
    • A space should be present before the ampersand, and this should all be contained within the suggested 120 character limit
    • The following line will be indented by additional whitespace at the discretion of the developer to maintain a clean look (see below)
character(50) :: person_one, person_two, person_three, &
                         person_four
person_one   = "Ada Lovelace"
person_three = "Alan Turing"

Variable Declaration and Attributes

Variable Description

  • Variables should be described using either inline comments for each variable, or with a comment block describing a group of variables (suggested)
  • Comments should be Doxygen readable, with LaTeX use where appropriate to allow for proper Doxygen rendering (required)
real, intent(out) :: RHOSROT(NL, NM) !< Density of snow \f$[kg m^{-3} ]\f$
real, intent(out) :: SNOROT (NL, NM) !< Mass of snow pack \f$[kg m^{-2} ]\f$

Implicit None

  • implicit none must be declared in every program component (required)
    • This prevents implicit variable declarations, a huge source of bugs

Attributes

  • Variable declarations must use a double colon :: even if no other attributes are present (required)

    • Bad: integer example_variable
    • Good: integer :: example_variable
  • If multiple declarations occur with the same attributes, the attributes should be listed in the same order (required)

integer, allocatable, pointer                 :: foo
integer, allocatable, pointer, dimension(:,:) :: bar

Subroutine Variables

  • Variables used within a subroutine should have the intent() attribute matching their intended use (required)
  • Whenever possible, arguments passed into a subroutine should have names matching those found within the subroutine (suggested)
    • This applies to function calls as well
subroutine square_cube (i, i_squared, i_cubed)
  implicit none
  integer, intent(in)  :: i                    ! input
  integer, intent(out) :: i_squared, i_cubed   ! output
  i_squared = i ** 2
  i_cubed   = i ** 3
end subroutine square_cube

program xx
  implicit none
  integer :: i, i_squared, i_cubed
  i = 4
  call square_cube (i, i_squared, i_cubed)
  print *, "i, i^2, i^3 = ", i, i_squared, i_cubed
end program xx

Fortran Modules

  • Avoid using module level variables (suggested)
  • Always specify use, only when importing from another module (required)
    • This helps developers trace where variables, functions, and subroutines originate and makes the organization of the code much clearer and helps reduce clutter in the namespace.
module foo
          use bar, only : fun1, fun2

          ...

end module foo
  • Entities within a module should be set to private by default, and declared public only when they are intended to be accessed from outside the module
module exampleModule
      implicit none
      ...

      private
      ! Sets default to private

      ...
      integer, parameter :: alpha, beta, delta, gamma
      ...

      public  :: alpha, beta
      ! Sets the specified entities to public

contains
      ...  
end module exampleModule

Operators

Array arithmetic

  • Explicit indexing will be used for array arithmetic (required)
    • This prevents undefined behaviour caused by unintentional operations on array halos

Example (where A, B, C are 2-dimensional arrays)

Bad:

C = A*B

Good:

C(i,j:i,k) = A(i,j:i,k) * B(i,j:i,k)

Assignment and Arithmetic Operators

  • Assignment and arithmetic operators should maintain a space on either side (suggested)
    • This improves interpretability of arithmetic operations and assignments
x = ((15 - y) / (13 * z)) ** 3

Relational and Logical Operators

  • The new standard relational operators will be used, as shown below (suggested)
    • These apply to comparable data types such as real or character, and prevent accidental mixing with logical types
Old (deprecated) syntax New syntax
.eq. ==
.ne. /=
.lt. <
.le. <=
.gt. >
.ge. >=
  • Logical variables and operations will continue to appear similar to the old relational syntax
    • .true. .false. variables and .eqv. .neqv. .not. .and. .or. operators
  • Logical evaluations should not use redundant operations as shown below: (suggested)

Bad:

doProc = .true.
...
if (doProc .eqv. .true.) then
  ...
endif

if (doProc .neqv. .true.) then
  ...
endif

Good:

doProc = .true.
...
if (doProc) then
  ...
endif

if (.not. doProc) then
  ...
endif

Control Flow

Loops

  • Loops will terminate with end do and not continue ____ (required)
    • Each loop must terminate with its own end do (no sharing loop terminators)
    • This is an F90 standard to promote comprehension
  • Loops will not be labelled with numbers as was common in F77 (required)
do i = 1, 10
      ! logic happens in here
      ...
end do
  • In the case of nested or overlapping logic constructs, descriptive labelling may be used to distinguish these constructs (suggested)
outer_loop: do i = 1, 10
      ...
      inner_loop: do j = 1, 20
        ...
      end do inner_loop
      ...
end do outer_loop

If-Else Statements

  • Positive logic should be used in if-evaluations when the contained code blocks are of comparable length (suggested)
    • Positive logic refers to checking that a variable is true rather than false
    • An example is given below:
if (exampleVariable /= 5) then
  call routineX()
else
  call routineY()
end if

Note the negative logic expression in the if test. This is easily refactored:

if (exampleVariable == 5) then
  call routineY()
else
  call routineX()
end if
  • Include a space after the if and before starting the logical expression (suggested)
  • Group compound logical expressions using parentheses (required)
if ( (a == b) .and. (b == c) ) then
end if

Goto

  • Goto statements are to be avoided whenever possible (required)
  • If absolutely necessary, goto statements should be thoroughly documented (required)
    • Including the reason for their use

Commenting and Documentation

  • Liberal use of good comments is encouraged
    • See Language and Units above for language guidelines
    • Inline comments should be descriptive and concise
    • Longer documentation should be placed at the end of the file
  • Changelogs are not necessary due to the use of a Version Control System (VCS) (required)
    • VCSs like git tracks users and changes automatically
  • Doxygen is the documentation tool used in this repository. Comments should conform to Doxygen’s Fortran standards as outlined here

The subroutine example from above has been commented using Doxygen’s specifications here:

!> Computes the square and cube of an input value, and
!! writes them to the output values
!! @param i input value
!! @todo Shame users for passing in negative numbers
subroutine square_cube (i, i_square, i_cube)
  implicit none
  integer, intent(in)  :: i                  !< Our input variable
  integer, intent(out) :: i_square, i_cube   !< Our output variables
  i_square = i ** 2
  i_cube   = i ** 3
end subroutine square_cube

Code templates

Acknowledgements

Several coding conventions were used as a guide for developing this document. Joint UK Land Environment Simulator (JULES) Coding Standards NASA Goddard ModelE Coding Conventions GEM-MACH Coding Standards