Below we present some guidelines for new code contributed to AGILE. The goal of these guidelines is to make it easier to understand, modify and maintain AGILE. Although some of the guidelines are simply a matter of preference and convention, sticking to these preferences and conventions is still valuable. The guide below has been inspired by a number of other Fortran style guides: 1 2 3 4
Code should conform to a reasonably modern Fortran standard (e.g., Fortran 90, 95, 2003). Only make use of the latest language features if you really need them, since this can cause problems for users with different compilers.
Do not use deprecated features:
| Deprecated feature | solution |
|---|---|
| common block | use a module |
| implicit typing | explicit typing |
| statement functions | define a real function |
double precision :: x |
real(kind) :: x |
real*N :: x |
real(kind) :: x |
integer*N :: x |
integer(kind) :: x |
character*N :: x |
character(len=N) :: x |
Use self-explanatory names when possible. Only variables that are locally
defined and have a simple meaning, such as loop indices, can have short names
such as i or ix. The farther away the definition of a variable is, the more
important it becomes that is has a self-explanatory name.
Here are some examples, inspired by looking at the current version of AGILE:
| Description | Too short | Better |
|---|---|---|
| Time variable | t |
time |
| Iteration counter | it |
iteration |
| Maximum refinement level | mxnest |
max_refinement_level |
| Subroutine to update AMR grid | resettree |
update_amr_grid |
A list of further naming guidelines:
* Use lowercase for Fortran constructs (do, if etc.)
* Use lowercase for the names you define (also of modules)
* Separate words with underscores (read_snapshot instead of readSnapshot)
* Give source files meaningful names
* Don't use Fortran keywords as names
* Avoid ambiguous names
>=, <=, /= etc. instead of .gt., .lt., .ne.fortran
i = 5
i = 5 + j
i = 5/(a + b) + cfortran
call my_routine(a, b, c)fortran
i = 1; j = i + 1 ! Bad:: in variable declarations
fortran
integer :: iWrite comments such that a reasonably experienced programmer can understand your code as quickly as possible. In practice, this could mean writing the following comments:
When in doubt, you should probably add a comment: it will help the reader to
confirm his/her understanding. Do not write comments around trivial statements,
such as i = i + 1.
The documentation page explains how to write documentation comments, which show up in the generated FORD documentation.
A source file should contain either a module or a program. Try to use modules in the following way:
module mod_name
! Selectively import when needed in the whole module
use mod_first, only: ...
implicit none ! Don't allow implicit types
private ! Entities are private by default
public :: ... ! Explicitly state public entities
contains ! Place subroutines and functions below
subroutine test()
use mod_second, only: ... ! Use local imports
...
end subroutine test
end module mod_name
The selective imports with use ..., only: help the reader with figuring out
where things come from. However, when you need a lot of functionality from
another module, it makes more sense to simply include everything with use
module.
All functions and subroutines should at least have a brief comment describing their functionality, as stated in Comments.
Functions should not have side-effects, meaning that they do not change the state of the program. Functions without such side-effects can be marked with pure. Functions should also not change their arguments. Consider making pure functions operating on scalars elemental, so that you can also use them on arrays.
The result of a function should not be directly used in a print or write
statement, unless you are sure the function itself does no IO (input/output).
The reason for this is that recursive IO can lead to hard to debug hangs /
crashes.
Subroutines are ideally also pure (without side-effects), but they can change their arguments.
All arguments should have their intent declared, using one of intent(in,out,inout).
Ideally, functions and subroutines get their information from their arguments. If that requires a lot of arguments, perhaps the functions and subroutines can be split in smaller pieces. Sometimes it can also help to define types with the relevant information. There should not be more than about 4 or 5 arguments.
Don't use global variables. Instead place variables inside modules, and include
them where needed. In cases where this is not possible, gather the global
variables in a module and give them a recognizable prefix such as GLOBAL_.
To ensure that variables defined in a module are not accidentally changed, you can use the Fortran 2003 protected attribute:
integer, protected :: my_age
Such variables can only be changed from inside the defining module.
The usage of double precision is deprecated. Instead use the kind-parameter
dp to declare double precision numbers:
real(dp) :: x ! good
double precision :: x ! deprecated
Write floating point constants using the _dp suffix:
x = x * 1.1_dp ! good
x = x * 1.1 ! bad: single precision constant
Integers are automatically converted to real numbers when they are mixed, without a loss of precision. However, be careful with expressions such as:
x = 0.5_dp * 5 / 6 ! Bad: is this (0.5_dp * 5)/6 or 0.5_dp * (5/6)?
x = (0.5_dp * 5) / 6 ! Good
Use the default integer type, unless there is a specific reason to use a larger
type. Be aware of the different ways of converting a real number x to an
integer:
int(x): round towards zerofloor(x): round downceiling(x): round upmint(x): round to nearest integerUse named constants instead of magic numbers:
real(dp), parameter :: pi = 3.14_dp
x = sin(pi * x) ! good
x = sin(3.14_dp * x) ! bad: magic number
However, don't use named constants for simple numbers such as 0, or 0.5:
x = 1.0_dp/pi ! good
x = one/pi ! bad: what is one?
Always loop over arrays in the correct order, meaning that the loop indices are ordered from right to left (opposite to loops in e.g., C or C++):
do j = 1, N
do i = 1, N
array(i, j) = 1
end do
end do
Because Fortran stores arrays in column-major order, the loop then follows the 'natural' memory order of the array. For the same reason, it pays off to think about the ordering of your arrays, for example:
array(:, i, j) = 1 ! Fast
array(i, j, :) = 1 ! Slower
Use allocatable arrays when:
Allocatable arrays are automatically deallocated, so you cannot create memory
leaks. They are also automatically reallocated on assignment (with gfortran,
for ifort you have to enable this behavior).
Use assumed-shape arrays when performance is not critical, for example:
subroutine square(values, squares)
integer, intent(in) :: values(:)
integer, intent(out) :: squares(:)
...
end subroutine square
The advantage of assumed-shape arrays is that they allow for run-time bounds checking. When performance is critical, you can use explicit-shape arrays:
subroutine square(values, squares, n_values)
integer, intent(in) :: n_values
integer, intent(in) :: values(n_values)
integer, intent(out) :: squares(n_values)
...
end subroutine square
Another option is to embed the array in a type, and pass the type as argument. See this guide by Intel for more information about the performance differences.
In new code, use the [...] syntax to define array constants:
x = [1, 2, 3] ! Fortran 2003 and later
x = (/1, 2, 3/) ! Old style
Indent with spaces, using the following indentations:
| Construct | number of spaces |
|---|---|
program, module, subroutine, function, associate |
2 |
type, interface |
3 |
do, if, select case, where, forall |
3 |
Include empty lines to visually separate blocks of code. Include a newline
between functions and subroutines, after and before do loops, around if
blocks, after the variable declarations, etc.
Enable all normal warnings during compilation, ideally using different compilers. Fix all warnings, except those that are incorrect, and those for which the fix does more harm than good.
Run the code with different inputs, perhaps using a different number of
processors, to detect the most obvious bugs. Enable all sensible run-time checks
to catch errors. Try to use a tool like valgrind to detect memory problems.
Useful Gfortran flags
| Flag | meaning |
|---|---|
| -Wall | Enable all standard warnings |
| -Wextra | Enable additional warnings |
| -Wimplicit-interface | Warn about procedures with implicit interfaces |
| -fcheck=all | Enable all run-time checks |
| -ffpe-trap=... | Enable floating point traps. Recommended: invalid, zero, overflow |
| -finit-real=snan | Initialize real values with a signalling NaN |
| -finit-integer=-2147483648 | Initialize integers with this value |
| -g | Include debugging info into the executable |
| -pg | Enable support for profiling with a tool like gprof |
| -O0 | Disable optimizations (when debugging) |
| -fimplicit-none | Do not allow implicit types |
| -std=... | Set the Fortran standard to use (f95, f2003, etc.) |
Useful ifort flags
| Flag | meaning |
|---|---|
| -traceback | Generate a backtrace after a run-time error |
| -ftrapuv | Initialize stack variables to unlikely values |
| -warn all | Enable all standard warnings |
| -check all | Enable all run-time checks |
| -check ... | Useful are: bounds, uninit |
| -fpe[0,1,3] | Lower values enable more floating point exceptions |
| -g | Include debugging info into the executable |
| -pg | Enable support for profiling with a tool like gprof |
| -O0 | Disable optimizations (when debugging) |
| -implicitnone | Do not allow implicit types |
| -stand ... | Set the Fortran standard to use (f95, f2003, etc.) |