There are several tools to make a codebase more structured. In your example, the subroutine within big() is called without arguments, but changes something in the outer scope, as you said. To prevent something like this, you may of course explicitly state the argument to the function:
subroutine big()
implicit none
integer :: a
a=1
call f(a)
print *,a
contains
subroutine f( argument )
implicit none
type(Complex), intent(inout) :: argument
argument = 2
end subroutine f
end subroutine big
This shows the reader that something will happen to a! There are also explicit intent statements ready to specify what kind of arg argument actually is. (Is it only an input which should not be changed?!)
Generally speaking the more you expose the -intent- of what the code is supposed to be doing, the easier you make it for the reader to work with it.
There is also the module system, where you may sort different subroutines/functions into a module, and thereby structure your code into different segments. What these segments are depends strongly on your case, but using modules may greatly clearify what the subroutine is supposed to be doing, without even looking at it.
lets say you sorted all your linear algebra stuff into a module called linAlg. You may then use:
use linAlg, only : vectorAddition,scalarMultiply
in some other place, and the reader will have enough hints to get whats going on.
So my two cents of keeping a monstrosity of a fortran codebase at bay is:
- encapsulate into comparatively small modules to keep namespaces small and self-document your intent with each module (linear_algebra_mod, finiteElement_mod,solvers_mod, vectorTypes_mod,matrixTypes_mod, inputOutput_mod etc.). Once you did that, you can then explicitly state what
you want to use out of them, eg.: "use a_mod, only : foo, bar"
- explicitly state the inputs/outputs of subroutines and functions
and make use of the intent statements
- Use concise but meaningful subroutine/function names which are self-explanatory (If there is to much happening in one sub, then split it up into smaller parts)
- use expressive variable names. Do not go down the road of naming your
variables: i, ii, ij, ji, ijk, ikk, ikkijasdf :-)
- Use a reasonable ammount of input checks within important subroutines.
If your method gets lots of NaN's as an input, or you try to calculate
the square root of -1.0, you better be informed right there and not after
a 3 hour debugging session!
- Very important: avoid premature optimization. Just because the nerd in
your department knows all the tricks to tickle out another 10% of perf
doesn't mean that readability of your codebase is irrelevant. Focus on
performance only when you actually run into problems.
Depending on how big your project is, you may also port it to even newer versions of Fortran (2008), where you can encapsulate into classes! (OOP in Fortran)