-
Notifications
You must be signed in to change notification settings - Fork 18
Code Guidelines
"When a project reaches the point at which no one completely understands the impact that code changes in one area will have on other areas, progress grinds to a halt."
- Steve McConell, Code Complete
- Always use 3 space indentation. Don't use tabs since (1) these are special characters with special functions (see how makefiles work) and (2) can be interpreted and shown differently depending on text processor configuration.
- Indent at every change of scope and loop/conditional construct. If you loop through two or more indexes consecutively, you may advance indentation only once for the contained code (as long as there is no commands being executed in between loops). The same applies when checking for different conditions, but try not to do it when mixing loops and conditions.
- In general, comments should have the same indentation as the section referred to. The commenting symbol should, however, be kept at the start of the line.
-
In general, preprocessor commands should be treated as regular code for indentation purposes. So, for example, an
#ifdef
section should cause the contained code to have an additional indentation level. Correspondingly, if the#ifdef
is inside a loop, it should be indented accordingly (the # symbol remains in the first line). -
Spacing should be used to improve readability whenever possible: in mathematical and logical expresions separating variables/operators (
num1 = num2 * ( num3 + num4 )
); in lists separating variables (call subrout( var1, var2, var3 )
). - Indentation/spacing may be used more liberally when it improves readability: in line continuations, when similar expresions/declarations are being used consecutively.
- Try to use descriptive names, specially for variables/procedures that will be used in different sections.
- Try not to use single letter variables in long code sections, not even for indeces. Searching for
ii
instead of simplyi
makes it much easier to find all instances of looping, for example. - Try to consistently use
snake_case
rather thanCamelCase
. - Try to use all lower case for variables (
number = number_matrix(ii,jj)
), all upper case for constants (circle_perim = CONST_PI * circle_diam
), and consider using verb capitalization for procedures (number = Sum_integers(ii,jj)
; may be relevant to diferentiate it from an array).
- Fortran Framework Reference and blogpost about it. And another one
- Try to apply the concept of single responsability principle. Check this wikipedia article.
-
Use the
implicit none
statement in all program units: main programs, loose subroutines and module scope. Procedures inside modules will automatically inherit it (exception: procedure specifications defined within interface blocks). -
Always use the
only:
statement to specify which variables/procedures of a module you are using. Sometimes it can be useful to rename variables when importing them from a module:use modname, only: new_varname => old_varname
. - Avoid initializing internal subroutine variables when declaring them because they will have the save attribute and it can get messy with, for example, recursive calls. It is ok to do so for setting default values in data-only modules.
- Always initialize non-allocatable module variables where they are defined. If these are input keywords, these should be the default value and they should be used in the namelist module.
- Fortran matrices have a column major order: it stores in memory first the array of the first index, then the second, etc. That means that if you need to sweep through a matrix, it is usually more efficiently to do so having the innermost loops run though the first index.
-
Make your functions pure (and your subroutines whenever possible) and always use the
result
keyword to specify the output. Making functions pure helps with compiler optimization and specifying result may be relevant in cases such as recursive functions. - Always use subroutines for runtime sized outputs. This is because a function will create an unnecessary temporary variable to store the result of the operation. Moreover, some compilers will default to create that variable from stack memory instead of heap memory, which is faster but scarcer. It's easier to just pass the output as a parameter (by reference) and have the calling procedure choose how to deal with that memory. Functions should be reserved for fixed size outputs.
- Dealing with complex numbers (TBD).
znum = complex( rpart, ipart ) ! will have the same kind as rpart and ipart
rpart = real( znum, kind=8 ) ! will have the kind specified
-
Every distinct feature should have its own separated module(s): if your feature needs to affect variables in another feature, it should do so through subroutines stored inside a
feature_subs
module. If your feature needs to store data/information, it should do so inside a separatefeature_data
module. Try to balance out minimizing modifications needed in external sections to use the feature and maximizing flexibility in how the feature can be used. -
Subroutines inside
feature_subs
should only use data from its ownfeature_data
module. If a feature needs to use data from another module, it should use it through its data module (in which case you may want to rename the variable to make it clear it is not from your module):use feature0_mod, only: ext_var => var
infeature_data
anduse feature_data, only: ext_var
in the subroutine. -
Variables inside
feature_data
should not be modified directly but viafeature_subs
subroutines. Be mindful and put some thought into what is the best way and where the best location to set this variables up (using init/setup subroutines, keeping internal logical status variables, etc). -
Store many-files modules inside a
feature_subs
folder. There you can have afeature_subs.f90
file that has the module that includes all other relevant files, and afeature_subs.mk
that specifies the dependency for all those included files.
-
Use customized types to create useful data structures. DISCUSION: How should we use procedures and types?
setup(typeobj,data)
VStypeobj%setup(data)
: how do we keep the implementation of the type hidden? -
Naming conventions:
NEW_typename
functions for creators,KILL_typename
subroutines for destroyers.
Type of variable Value Assigned
integer INT(expr , KIND = KIND (variable))
real REAL(expr , KIND = KIND (variable))
complex CMPLX(expr , KIND = KIND (variable))
Note that these work as functions that return the corresponding value (Source)
If you #include (ask the preprocessor to include), then preprocessor directives will be processed in the included file. If you include (ask fortran compiler to include), then the included file will not be preprocessed. Don't know if there is an analogous situation with C.
IDEA: the most basic modules should not have preprocessing inside. For example, there could be a separate module for cpu math and gpu math, and the preprocessing that decides which to use is external to those modules.
- Fortran Best Practices
- Modern Fortran: Style and Usage - N. S. Clerman, W. Spector