Here are some more suggested best practices, also listed here on GitHub, based on my own experience and reading the posted questions and codes of others. Some are matters of taste.
Use free source form rather than fixed source form
Use the .f90 suffix for free source form code
The .f90 suffix indicates free source form, not that the code conforms to the Fortran 90 standard. Don’t use suffixes such as .f95, .f03, .f08, or .f18, which not all compilers and build systems recognize as free source form Fortran files.
Write code in lower case
Code in lower case is more readable than code in all upper case. Comments should be capitalized as normal written language.
Use format strings instead of format
statements
It is easier for the reader to understand a write
statement if the format string is on the same line, rather than in a separate numbered format statement. If the same format string is used in multiple write
statements, define it as a parameter
.
Put functions and subroutines in modules
This enables type-checking of arguments at compile time. If this is not possible, provide an explicit interface for the procedure.
Put implicit none
at the beginning of the main program or module
If this is done, it is unnecessary to write implicit none
in each procedure.
Use intent
for all procedure arguments
Any argument that is strictly an input should be declared intent(in)
.
All function arguments should be intent(in)
A function with an intent(out)
argument should be rewritten as a subroutine.
Declare functions and subroutines pure
when possible
This tells the reader that they do not have side effects.
Do not have more than three or four required arguments for a procedure
Use optional
arguments and derived types (which can store multiple entities) to reduce the number of required arguments.
The order of subroutine arguments should be required intent(in)
arguments, followed by required intent(out)
or intent(in out)
arguments, followed by optional
arguments
This will make it easier to call such a subroutine without naming the arguments. Argument declarations should be in the same order as the argument list.
Declare a function result
on a separate line
It should appear after the declarations of function arguments but before the declarations of local variables.
Avoid using saved variables when possible. When they are necessary use the save
atribute.
In a procedure, integer :: i = 0
has a different meaning in Fortran than the analogous statement in other languages, and one should write integer, save :: i = 0
instead to alert the reader.
A procedure call with many arguments should use named arguments
For example, call reg(lhs=a,rhs=b,coeff=x,stderr=c,sse=d)
is clearer than call reg(a,b,x,c,d)
In subroutine calls, document the intent(out)
and intent(in out)
arguments with a comment
For example, call sub(x1,x2,x3,x4) ! out: x3,x4
clarifies what the outputs of sub
are.
Avoid intent(in out)
arguments when possible
It was common in Fortran 77 libraries to overwrite an input matrix with output to conserve memory, but this is rarely necessary now. Instead use separate intent(in)
and intent(out)
array arguments.
Do not write a function with a pointer
result
This is prone to memory leaks when the function is used in an expression.
Use allocatable
arrays rather than pointer
arrays when possible
Pointer arrays must be explicitly deallocated to avoid a memory leak.
At the beginning of a function or subroutine, have at least one comment line explaining what it does
Have a comment explaining the meaning of each procedure argument, either separately or on the same line as the argument declaration
When importing entities from a module, add the only
qualifier
For example, use m, only: func,sub
instead of use m
. This clarifies for the reader what is being imported from each module.
Put a private
statement at the beginning of a module. Only make public
what is necessary.
This reduces namespace clashes and tells the reader what entities of a module are exported to other program units.
Do not put a stop
statement before the end of the main program or a return
statement before the end of a procedure.
Such statements are unnecessary. Use stop
only to end a program early or return
only to exit a procedure early.
Do not deallocate
allocatable
arrays just before the end of a program or procedure
This is unnecessary since such arrays are deallocated when they go out of scope.
If a derived type has allocatable
components that have related shapes, write a subroutine to allocate them consistently
Otherwise the code will need to be updated in several places if a new allocatable component is added to the definition of the derived type. Also write subroutines to read and write the derived type.
When opening a file, specify the action
as read
or write
Do not write to standard output in a subroutine, except for error handling
It is more flexible to pass a unit number to the subroutine and to write to that unit. The unit number argument can be optional
, and the local unit number can be set to standard output if the argument is not present
.
Use local variables with a _ suffix to shadow optional
arguments
Set x_
to optional
argument x
if it is present
and to a default value otherwise. Consider using the optval
function of stdlib to set such shadow variables.
Make a variable whose value is fixed a parameter
This clarifies to the reader and the compiler that the value is fixed and prevents inadvertent changes.
Parametrize file units with meaningful names
For example, set a parameter
such as data_unit
and output_unit
and use it in read
and write
statements. If you use integer literals to specify the unit in read
or write
statements, it’s easy to mix them up.
Explicitly leave spaces between numbers in formatted output
Otherwise the numbers may be “mushed” together if they are larger than expected. Instead of
write (*,"(100f12.6)") x(1:n)
write
write (*,"(100(1x,f12.6))") x(1:n)
Use list-directed read
to read data.
A formatted read
depends on the data file matching the format exactly and is error-prone.
Indent loops, if
blocks, and select case
blocks
Label deeply nested loops so that it is clear what the end do
corresponds to
Use one-line-if statements instead of if blocks when possible, to be concise
Short variable names should follow implicit typing rules, even though implicit none
should be used
The reader of a Fortran program expects variables such as i
or i1
to be integers and x
or x1
to be real.
If a program or procedure uses a published algorithm or is derived from another code, cite the publication or code
Use compiler options that detect unused variables or parameters, and delete them
Avoid goto
, except possibly for error handling at the end of a main program or procedure. Avoid numbered lines except to delineate a block of code for error handling.
Avoid the dimension
keyword in declarations, except to declare multiple arrays of the same shape
real :: vec(n1),mat(n1,n2)
is more concise than
real, dimension(n1) :: vec
real, dimension(n1,n2) :: mat
However,
real, dimension(n1,n2) :: a,b,c
is a good use of dimension
Declare multiple variables of the same type on the same line, for concision
An exception is when you want an inline comment for each variable after its declaration.
Use informative loop variable names similar to the name of the upper bound
For example, do iday=1,ndays
or do iatom=1,natoms
Don’t use array sections when the whole array is referenced
Refer to real :: x(n)
with x
instead of x(:)
or x(1:n)
. Use array sections for subsets of an array.
Use whole arrays in read
and write
statements instead of implied do loops when possible
Use write (*,*) x
instead of write (*,*) (x(i),i=1,n)
if they have the same effect.
If you are passing multiple arrays to a procedure that must have the same dimensions, consider putting them in a derived type
For example, if a subroutine accepts arguments days(:)
, months(:)
, years(:)
that have a common length, probably a derived type should be defined containing (day,month,year). Then an array of that type can be passed.
Use explicit type conversions
For real(kind=wp) :: x
and integer :: i
write x = real(i,kind=wp)
instead of x = i
to make it clear that the conversion from integer to real is intentional. Do not use x = dble(i)
which is less flexible.
Separate procedures in a module with a blank or comment line
This aids legibility.
Use end subroutine foo
or end function foo
or end program foo
instead of just end
It helps the reader see what program unit is being terminated.
Separate terms in an arithmetic expression and the LHS and RHS with spaces
For example write z = c*x + d*y
Use integer exponentiation when possible
Write x**2
not x**2.0
. The compiler may produce more efficient code for the former, which makes clear that the variable is raised to an exact integer power.
Beware of integer overflow
Write (1.0/i)**n
instead of 1.0/i**n
since the denominator may overflow in the latter expression.
Collect physical and mathematical constants in a single module that is used where needed
Avoid defining a constant such as pi in multiple places.
Use [] instead of (//) for array constructors
This has been standard since Fortran 2003 and is supported by currently maintained compilers. The syntax is shorter and resembles that of other languages.
Do not rely on list-directed output having the same appearance across compilers
Use formatted output instead when this is desired.
Use the * unlimited repeat count edit descriptor of Fortran 2008 instead of a large integer repeat count
The danger of write (*,"(a,1000(1x,i0))") "abc",ivec(:)
is that size(ivec) > 1000
will cause an error. Instead use the format string
"(a,*(1x,i0))"
Do not rely on integer or real variables being initialized to zero
The Fortran standard does not require this. Set such variables to zero explicitly if the code depends on this.
Do not rely on short-circuiting in compound logical expressions
It is not safe, for example, to write if (present(a) .and. a > 0) ainv = 1/a
. A nested if
where the condition present(a)
is tested first should be used instead.
Compile with a standard conformance option and fix code that is not conformant. Consider fixing code that uses obsolescent features.
Use for example gfortran -std=f2018
or ifort -stand:f18
Compile with bounds-checking and other debugging options, until speed is needed for a production version.
Regularly compile and run your code with multiple compilers
Gfortran and Intel Fortran are both free and mature and should both be used, at the minimum. If you have a Fortran 95 code, consider testing it with g95, a mature but unmaintained Fortran 95 compiler.