Updating Old Code
If your dissertation depends on it (and you are allowed to do it) it’s worth the time unless the code is more than 50,000 to 100,000 lines or so.
Step 1: Replace all COMMON blocks with modules. Initially these modules only need to declare the variables.
Step 2: Reorganize subprograms into modules. This gives you a free interface, and checks agreement of type and number of arguments.
Step 3: Change variable names to something more meaningful as you are able.
Step 4: Globals are poison, and a major source of bugs. The author found a bug in a model that was due to a variable being global due to having been originally in COMMON, and ported to a MODULE. This caused the program to have a “memory” when it should not have had one, but since the users had never executed the model for different conditions in the same run, they had never noticed this error. Move variables out of the “common” modules into parameter lists as quickly as you can.
Step 5: Introduce types/classes as appropriate.
Testing
You will need a set of regression tests for this job. A regression occurs when a change to a code creates an error in previously-tested scenarios. Before starting to modify code, determine how you will test it as it stands. You may find the original code was not as well tested as you expected (or hoped). You may need to create a suite of tests. Retain those and run them as you make modifications.
Programming Style
Style makes a big difference, especially in compiled languages that tend to be more verbose than interpreted languages.
Indentation is very important
Variable names are important–choose variable names that communicate some meaning to the reader. Variable names are no longer limited to 8 characters.
We do not use green-and-white striped fanfold paper anymore to separate lines visually.
Insert blank lines between logical chunks of code
Example
Before The obsolete DATA statement was used to initialize variables. The old code below is written in Fortran 77, so still used fixed format, but some newer constructs such as END DO were available.
subroutine biogeoclimate
include "common.txt"
real tx(365),tn(365),ta(365),p(365)
real t1(12),t2(12),p1(12)
real littc1,littc2,littn1,littn2
real prcp_n
data prcp_n/0.00002/
c prcp_n: Nitrogen content for unit rianfall (tN/ha)
c
rain_1=0.0
rain_n=0.0
do i=1,12
tmp1=ynormal(0.0,1.0)
tmp1=max(-1.0,min(tmp1,1.0))
t1(i)=tmin(i)+tmp1*tminv(i)
t2(i)=tmax(i)+tmp1*tmaxv(i)
tmp2=ynormal(0.0,1.0)
tmp2=max(-0.5,min(tmp2,0.5))
c forest cover can increase rainfall by maximum 15%
p1(i)=max(prec(i)+tmp2*precv(i),0.0)
c *(1.0+lai*0.02)
rain_1=rain_1+p1(i)
c write(*,*) tmp1, tmp2, tmin(i),tmax(i),prec(i)
rain_n=rain_n+p1(i)*prcp_n
end do
call cov365(t1,tn)
call cov365(t2,tx)
call cov365a(p1,p)
do i=1,365
ta(i)=0.5*(tn(i)+tx(i))
end do
c
c Daily cycles of C, N, H2O
c
rrr=0.0
ypet=0.0
yaet=0.0
avail_n=0.0
degd=0.0
growdays=0.0
drydays=0.0
drydays1=0.0
flooddays=0.0
c
aoc0=aoc0+go_ao_c
aon0=aon0+go_ao_n
After
module Model
use Parameters
use Constants
use Soil
use Site
use Species
use Tree
use Random
use Climate
use Input
implicit none
real, parameter :: grow_min=0.05
real, parameter :: growth_thresh=0.05
real :: growth_min=0.01
contains
subroutine BioGeoClimate(site,year)
integer, intent(in) :: year
type(SiteData), intent(inout) :: site
integer :: gcm_year
integer :: num_species
real, dimension(NTEMPS) :: tmin, tmax, prcp
real, dimension(NTEMPS) :: tmptmin, tmptmax, tmpprec
real, dimension(days_per_year) :: daytemp, daytemp_min, daytemp_max
real, dimension(days_per_year) :: daynums, dayprecip
real :: litter_c_lev1,litter_c_lev2
real :: litter_n_lev1,litter_n_lev2
real :: rain,rain_n,freeze
real :: temp_f,prcp_f
real :: total_rsp,avail_n,n_avail,C_resp,pet,aet
real :: growdays,drydays_upper,drydays_base
real :: flooddays,degday
real :: outwater
real :: exrad,daylength,exradmx
real :: pot_ev_day
real :: act_ev_day
real :: laiw0_ScaledByMax,laiw0_ScaledByMin
real :: aow0_ScaledByMax,aow0_ScaledByMin
real :: sbw0_ScaledByMax,sbw0_ScaledByMin
real :: saw0_ScaledByFC,saw0_ScaledByWP
real :: yxd3 !used but never set
! used to temporarily hold accumulated climate variables
real :: tmpstep1,tmpstep2
real :: tmp
integer :: i,j,k,m
real, parameter :: min_grow_temp =5.0
real, parameter :: max_dry_parm =1.0001
real, parameter :: min_flood_parm=0.9999
save
num_species=size(site%species)
rain =0.0
rain_n=0.0
! The user is expected to input decr_by values as positive.
if ( linear_cc ) then
if (year .ge.begin_change_year.AND. year .le. &
(begin_change_year+duration_of_change)) then
accumulated_tmin=accumulated_tmin+tmin_change
accumulated_tmax=accumulated_tmax+tmax_change
do m=1,12
tmpstep1 =site%precip(m) +accumulated_precip(m)
tmpstep2 = tmpstep1 *precip_change
accumulated_precip(m) =accumulated_precip(m) + tmpstep2
end do
endif
else if (use_gcm) then
gcm_year=start_gcm+year-begin_change_year
if (gcm_year.ge.start_gcm.and.gcm_year.le.end_gcm) then
call read_gcm_climate(site%site_id,gcm_year,start_gcm,tmin,tmax,prcp)
site%tmin=tmin
site%tmax=tmax
site%precip=prcp*mm_to_cm
endif
endif
end module
Changes
-
Converted to modules.
-
Eliminated COMMON, passing all variables (this not only controls the interface, but eventually eliminated a bug).
-
Renamed most variables to something more descriptive.
-
Added lots of whitespace for visual separation.
-
Aligned typographically
Note that considerable effort went into separating other parts of the code into the modules USEd in this subroutine.