Fortran style guide

(Original author's: Ben Hillman and Walter Hannah from the ACME-ECP Confluence)

Here we define a basic style guide to enforce some consistency for new code added to E3SM. The guidance for working with old, inherited code has always been to match the existing style. The goal is to maximize readability of new code. This is motivated by the wasted hours/days that many of us have experienced trying to follow the style of other developers when tracing old code and reviewing new pull requests.

Note:  If you would like to convert old code to follow this style, do that in a dedicated feature branch that ONLY does style changes.  Do not mix style updates with other feature development because it will complicate review.

Fortran Standards

Official home:  https://wg5-fortran.org/

The committee that defines the standards:  https://j3-fortran.org/

What standard to use:  You should stick mostly with Fortran95 but we do want to use the ISO-C bindings available in Fortran2003.  So technically that means Fortran2003 is allowed but see below.

Modern language features (classes)

Do not use modern Fortran approaches for abstraction, such as classes. Current OpenACC/OpenMP implementations do not play well with Fortran classes, so we should avoid using these for anything that we think might need to be ported in the future. But in general, it is probably best to avoid their use for this project, and favor more fundamental data structures.

Basic derived types appear to be fine, but using type-bound procedures are not.

In general, you should never use features that are new in the latest Fortran standard.  It takes a long time for compilers to implement them correctly and even longer from them to perform well.  For example, avoid Fortran2003 features for type extension and inheritance, polymorphism, dynamic type allocation, (and type-bound procedures.)

Line width

Source code lines should not exceed 100 characters in length.
Once a line reaches 100 characters, it should be split into multiple lines with a continuation character. This rule applies to comment lines as well.

Indentation

Rules for indentation:

  • Indentation should be 3 spaces for new files 
  • Always try to match indentation to existing code when modifying files (not always easy since many files contain inconsistent indentation)
  • Hard tabs should not be used (set your editor to insert spaces instead of tabs)
  • Loops, logical blocks, and other control structures should all be consistently indented to improve readability. This applies to the structure of modules and subroutines as well.

For example, the following indentation logically separates the different parts of the module:

module radiation

   use mo_gas_optics, only: ty_gas_optics
   use mo_rte_kind  , only: wp

   implicit none
   private
   public :: radiation_read_nl

contains

   subroutine radiation_readnl(filename)
      character(len=*), intent(in) :: filename
      <body of routine>
   end subroutine radiation_readnl

   <other subroutines>

end module radiation    

Code Comments

Code should be well-documented. Comments should be complete and informative, and should nest at the same tab level as the code they describe. For example:

! Calculate binary cloud fraction by looping over columns and levels and checking total water
do iz = 1,nzm
   do iy = 1,ny
      do ix = 1,nx
         ! Check if cloud water threshold exceeded for this column and level
         if (qcl(ix,iy,iz) + qci(ix,iy,iz) > qtot_threshold) then
            cld(ix,iy,iz) = 1
         else
            cld(ix,iy,iz) = 0
         end if
      end do ! ix = 1,nx
   end do ! iy = 1,ny
end do ! iz = 1,nz

Variable Names

  • Descriptive names are preferred over short, terse names for readability. This is especially important for public module variables that are used in many files. 
    • For example, searching for all uses of a variable "a" is very difficult, but "earth_radius" is easy.
  • Avoid use of generic names like "tmp"
    • exceptions are allowed where needed for intermediate calculations to work around compiler bugs 
    • For example, in the OpenACC port of the CRM, this needs to be done to split up any arithmetic from '!$acc atomic update" directives
  • Avoid using single character variable names for indices. 
    • "ix" is preferred over "i" for a loop variable over dimension "crm_nx" 
    • "icol" is preferred over "i" for a loop variable over "ncol"
    • "ilyr" or "ilev" are preferred over "k" for a loop over the vertical coordinate

Avoid "Magic" Numbers

"Magic" numbers are hard-coded numeric values that appear in the code without any explanation of where they came from or what they represent. These can cause problems when someone is trying to understand or modify your code. 

Many examples of this can be found in the original CRM code, which are often similar to this:

if ( x > 1e-20 ) then
   <do stuff>
end if

In this example "1e-20" is used as a subjective definition of a "small" value. Cases like this should be handled by creating a descriptive parameter, like "small_threshold", which is defined at the top of the code. This makes future experimentation with the impact of changing these thresholds much easier and straightforward.

Some numeric values are used in an obvious way. In the example below "2" and "0" are obviously used to get 90 and 0 degree angles, so it is not necessary to create a variable to hold the numeric values 2 and 0. 

if (crm_ny.eq.1) then
   crm_angle(:ncol) = pi/2.
else
   crm_angle(:ncol) = 0.
end if

Avoid GNU specific extensions

For portability, stick to language standards and avoid relying on GNU specific features.

While compiling your code with gfortran, test your code with -std=f2003 etc. (not -std=gnu which is the default)

https://gcc.gnu.org/onlinedocs/gfortran/Extensions-implemented-in-GNU-Fortran.html

Specifying either -std=f95, -std=f2003, -std=f2008, or -std=f2018 disables extensions



Other Sources