MODFLOW 6 Fortran style guide

The goal of this guide is to provide a standard for MODFLOW 6 contributors to follow, in pursuit of consistent, readable, well-organized, and unsurprising source code.

MODFLOW 6 is written in Fortran — largely Fortran 2003, with sparse use of a few 2008 language features. This guide assumes familiarity with the Fortran language.

Amendments

Suggestions to change or extend the style conventions are welcome. Suggestions should be accompanied by a good case for the change. Bear in mind that a goal of this guide is to minimize time spent thinking about questions of style.

Conventions


Modules

Use implicit none in all modules.

In old FORTRAN variables types were implicitly assigned to variables depending on the variable name. It easy to create a bug using this old way because the developer is responsible of keeping track of what type each variable is. Using implicit none forces the developer to explicitly assign a type to a variable. This makes it possible to catch certain mistakes during the compilation step e.g. unintended type conversion

Make modules private by default. Mark public types and procedures explicitly.

Instead of declaring everything public it is better to have it declared private by default and explicitly define what is public. This makes it clear to whom is using your module which types and procedures they may use.

Furthermore as the developer of the module it also makes it clear which procedures are internal and you can change without affecting any external code. On the other hand if its public changes should be made with care.

Prefer explicitly specified imports with use ..., only: ..., rather than merely use ....

When only using the use ... syntax you pull in everything a module defines. This can cause name collision. Therefore always use the use ..., only: ... syntax

module SampleModule
  ! don't
  use ConstantsModule

  ! do
  use ConstantsModule, only: DPI
end module SampleModule

Types


Procedures

Don’t end procedures with a return statement; use return only to return early.

Adding a return statement is superfluous as the procedure will return anyway after the last line

! don't
subroutine run_sample()
  ! Some code
  return
end subroutine

! do
subroutine run_sample()
! Some code
end subroutine

Avoid goto statements.

goto statements are something from the past. They are usually used to reach code based on a condition. If you want to achieve the same consider using:

  • exit/continue in your loops, cases and if statements

  • splitting your code in subroutines

  • a loop

  • putting conditional code in if else statements

Specify precision for logicals, integers and reals with the data types defined in src/Utilities/kind.f90.

This ensures that the same types are used throughout the code base

use KindModule, only: DP, I4B, LGP

! don't
logical :: boolean_var
integer :: integer_var
real(8) :: double_var

! do
logical(LGP) :: boolean_var
integer(I4B) :: integer_var
real(DP) :: double_var

Name type-bound procedures’ first dummy argument this. A suitable docstring is !< this instance.

  subroutine method(this)
    ! don't
    class(CustomType) :: this
  end subroutine

  subroutine method(this)
    ! do
    class(CustomType) :: this !< this instance
  end subroutine

Avoid deeply nested control structures where possible.

Deeply nested structures makes it difficult for the reader to keep track of what is happening. Try to make the code as flat as possible.

! don't
subroutine nested()
  if (condition1) then
    if (condition2) then
      if (condition3) then
        call procedure
      end if
    end if
  end if
end subroutine nested

! do
subroutine flat()
  if (.not. condition1) then return
  if (.not. condition2) then return
  if (.not. condition3) then return

  call procedure
end subroutine flat

Prefer verbose names, except where the meaning is obvious from context or precedent. E.g., well-known index variables (i, j, m, n).

You’re writing code for humans. Not for machines. Use clear names so it immediately becomes obvious what a variable means.

! don't 
List :: lst
HashTable :: ht
real(DP) :: t

! do
List :: list
HashTable :: hash_table
real(DP) :: time

Use named constants. Avoid magic numbers.

Related to the rule above. Magic numbers make it difficult to understand code. What does a number mean?

real(DP) constant_velocity
real(DP) delta_time = 5.0
real(DP) distance

! Don't. What does 1.0 mean?
distance = delta_time * 1.0

! Do. It is clear what the number means
constant_velocity = 1.0
distance = delta_time * constant_velocity

Naming

Use CamelCase for source file names.

- modflow
  - src
    - ExampleSourceFile.f90

Use CamelCase for module and derived type names.

module SampleModule

  type :: SampleType
    ...
  end type SampleType

end module SampleModule

Use a noun or noun phrase for module and derived type names.

A (derived) type is an object in object-oriented programming. And objects are described with nouns. The procedures in the type should be verbs and can be thought of actions that can be performed on/by/to the object

Use snake_case for procedure names.

subroutine display_sample_name()
  ...
end subroutine

Use a verb or verb phrase for procedure names.

Procedures should be thought of as actions performed on an object or arguments.As actions are described by verbs or verb phrases so must procedure names be named.

End module names with ...Module.

module SampleModule
  ...
end module SampleModule

Derived type names may, but need not, end with ...Type.

! This is good
type :: FirstSampleType
  ...
end type

! This as well
type :: SecondSampleType
  ...
end type SecondSampleType

If a source file exists primarily to host a module, name the source file and module identically, except for trailing ...Module.

module SampleModule
  ...
end module SampleModule
- modflow
  - src
    - Sample.f90

If a module exists primarily to host a type, name the module and type identically, except for trailing ...Module and ...Type.

module SampleModule

  type :: SampleType
    ...
  end type SampleType

end module SampleModule

Include the module/subroutine/function name in end module, end subroutine and end function statements.

module SampleModule

  type :: SampleType
    ...
  end type SampleType

contains

  subroutine sample_subroutine()
    ...
  end subroutine sample_subroutine

  function sample_function(this) result(res)
    ...
  end functions sample_function

end module SampleModule

Comments

Avoid empty comments.

! don't

!
!  some comment
! do

! some comment

Avoid comments starting with --.

! don't
! -- some comment

! do
! some comment

Formatting

Include blank lines between:

  • module declaration and use... statements

  • use... statements and procedure declarations

  • derived type declaration and member variables

  • member variables and contains statements

module SampleModule

  use KindModule, only: DP

  type :: SampleType
    real(DP) :: value
  contains
    procedure :: do_something
  end type SampleType

contains

  subroutine do_something(this)
    ...
  end subroutine add

end module SampleModule

Prefer importing items used throughout a module with a module-scoped use statement, rather than separately in multiple procedures.

! don't
module SampleModule

contains

  subroutine do_something()
    use KindModule, only: DP
    ...
  end subroutine add

  subroutine do_something_else()
    use KindModule, only: DP
    ...
  end subroutine add
 
end module SampleModule
! do
module SampleModule

  use KindModule, only: DP

contains

  subroutine do_something()
    ...
  end subroutine add

  subroutine do_something_else()
    ...
  end subroutine add
 
end module SampleModule

Use Doxygen format for docstrings. For dummy arguments, use either @param ... above the signature or !< ... next to the dummy argument.

!> Description of the procedure.
!! 
!! @param parameter2 information about parameter2
!! @param parameter3 information about parameter3
!! @todo Handle special case
subroutine intrestbuild(parameter1, parameter2, parameter3)
  implicit none
  integer(I4B), intent(in) :: parameter1 !< information about parameter1
  integer(I4B), intent(in) :: parameter2
  integer(I4B), intent(out) :: parameter3
  
  ...

end subroutine

Sample Module

Below is a minimal module demonstrating some (but not all) of the conventions.

module SampleModule

  use KindModule, only: DP, I4B, LGP
  use ConstantsModule, only: DPI

  implicit none
  private
  public :: run_sample

contains

  subroutine run_sample(verbose)
    logical(LGP), intent(in) :: verbose
    integer(I4B) :: answer
    real(DP) :: pi

    answer = 42
    pi = DPI

    if (verbose) then
      print *, 'pi: ', pi
      print *, 'answer to the ultimate question of life,'&
               ' the universe, and everything: ', answer
    end if
  end subroutine run_sample

end module SampleModule