Case Study 1: The Mystery Routine
"Before you can improve a thing, you must first be able to say, precisely, what it already does."
Executive Summary
You have inherited a codebase, and buried in it is a subroutine named FILTER with no comments, no
documentation, and a note in the commit history that reads, in full, "works — don't touch." It uses a
COMMON block, a statement function, implicit typing, and labeled DO loops, and you need to know what it
computes before you dare to modernize it. This case study applies the five-step reading method from §17.6
to a genuinely unfamiliar routine — not the PLATE kernel you were guided through, but a fresh one — so
that the method, not the memory of a particular program, is what you carry away. By the end you will have
reconstructed the routine's meaning from its structure, confirmed it against a hand-traced example, and
written a modern reconstruction that proves you understood it.
Skills applied: reading fixed-form source (§17.1); decoding a COMMON data model (§17.2); recognizing
a statement function and labeled loops (§17.4); reasoning through implicit typing (§17.5); the five-step
reading method (§17.6).
Background
Here is the routine, exactly as you found it, plus the small driver that exercises it. It is real,
compilable fixed-form FORTRAN 77 — compile it with gfortran -std=legacy.
C ==============================================================
C (found in the codebase -- no documentation of any kind)
C ==============================================================
PROGRAM SMTEST
COMMON /SIG/ X(6), N
N = 6
X(1) = 0.0
X(2) = 0.0
X(3) = 30.0
X(4) = 0.0
X(5) = 0.0
X(6) = 0.0
CALL FILTER
WRITE (*,900) (X(I), I = 1, N)
900 FORMAT (1X, 6F6.1)
STOP
END
SUBROUTINE FILTER
COMMON /SIG/ X(6), N
DIMENSION Y(6)
AV3(A, B, C) = (A + B + C) / 3.0
Y(1) = X(1)
Y(N) = X(N)
DO 10 I = 2, N-1
Y(I) = AV3(X(I-1), X(I), X(I+1))
10 CONTINUE
DO 20 I = 1, N
X(I) = Y(I)
20 CONTINUE
RETURN
END
Do not run it yet. The discipline of reading is exactly the discipline of not reaching for the compiler to tell you what your own eyes can.
Phase 1 — Read the Shape, Not the Logic
Start with structure, ignoring every executable detail. There are two program units: a driver SMTEST and
a subroutine FILTER. The driver fills six values, calls FILTER, and prints six values. So whatever
FILTER does, it transforms an array of six numbers in place — the input is set before the call and the
same array is printed after. Already, without reading one line of FILTER's body, you know its job
description: it changes the signal X.
Phase 2 — Find the Shared State
The data model lives in the COMMON block. Both units declare COMMON /SIG/ X(6), N — identically, which
is the first thing to verify and here it checks out. So FILTER communicates with its caller not through
arguments but through shared memory: the array X and its length N. This is the classic FORTRAN 77
signature-free routine — it takes "no arguments" and yet operates on data, because the data is global.
COMMON /SIG/ entity |
Type (implicit) | Role |
|---|---|---|
X(6) |
REAL (starts with X) |
the six-element signal, shared in and out |
N |
INTEGER (starts with N) |
the working length, here 6 |
Phase 3 — Identify the Loops
FILTER has two labeled DO loops. The first, DO 10 I = 2, N-1, runs over the interior indices only
(2 through 5), skipping the two endpoints — a strong hint that the endpoints are special. The second,
DO 20 I = 1, N, runs over all indices and copies Y back into X. So the routine computes something
into a scratch array Y, then writes Y over X. The scratch-then-copy pattern is how you transform an
array without corrupting the values you are still reading — the same reason PLATE kept a separate TNEW.
Phase 4 — Decode the Kernel
Two lines carry the meaning. The statement function
AV3(A, B, C) = (A + B + C) / 3.0
names the average of three values, and inside the interior loop it is called as
Y(I) = AV3(X(I-1), X(I), X(I+1)) — the average of each point with its two immediate neighbours. That is a
three-point moving average, the simplest smoothing filter there is. The endpoint lines Y(1) = X(1)
and Y(N) = X(N) pass the first and last values through unchanged (there is no neighbour beyond the edge
to average with). The mystery is solved: FILTER smooths the signal X with a 3-point moving average,
leaving the endpoints fixed.
Phase 5 — Confirm With a Hand Trace
Never trust a reading you have not tested against a number. The driver sets X = [0, 0, 30, 0, 0, 0] — a
single spike of height 30. Trace the interior:
I |
AV3(X(I-1), X(I), X(I+1)) |
Y(I) |
|---|---|---|
| 1 | (endpoint) = X(1) |
0.0 |
| 2 | (0 + 0 + 30) / 3 | 10.0 |
| 3 | (0 + 30 + 0) / 3 | 10.0 |
| 4 | (30 + 0 + 0) / 3 | 10.0 |
| 5 | (0 + 0 + 0) / 3 | 0.0 |
| 6 | (endpoint) = X(6) |
0.0 |
The spike of 30 is spread into three values of 10 — exactly what a smoothing filter does to an impulse. Now run it and confirm your reading:
$ gfortran -std=legacy mystery.f -o mystery && ./mystery
0.0 10.0 10.0 10.0 0.0 0.0
The output matches the trace. You did not need the compiler to tell you what the routine does — you used it to confirm what you had already reconstructed. That is the correct relationship between reader and machine.
Phase 6 — Prove It With a Modern Reconstruction
The final test of understanding is being able to rebuild the thing cleanly. Here is FILTER reconstructed
in modern Fortran — implicit none, a dp kind, an explicit array, no COMMON, no statement function, no
labels — and it produces the identical output, which is your evidence that the reconstruction is faithful.
program smooth_modern
use, intrinsic :: iso_fortran_env, only: dp => real64
implicit none
real(dp) :: x(6) = [0.0_dp, 0.0_dp, 30.0_dp, 0.0_dp, 0.0_dp, 0.0_dp]
real(dp) :: y(6)
integer :: i, n
n = size(x)
y(1) = x(1); y(n) = x(n) ! endpoints pass through
do i = 2, n - 1
y(i) = (x(i-1) + x(i) + x(i+1)) / 3.0_dp ! 3-point moving average
end do
x = y ! whole-array copy replaces the loop
print '(6f6.1)', x
end program smooth_modern
$ gfortran -std=f2018 -Wall smooth_modern.f90 -o sm && ./sm
0.0 10.0 10.0 10.0 0.0 0.0
Identical values, and now the code says what it means: the array is explicit and length-bearing, the filter
is one readable line, and the "copy back" is a whole-array assignment. The only cosmetic difference is the
leading column — the legacy FORMAT carried a 1X, and the modern print does not — which is exactly the
kind of trivial, non-numerical difference you learn to ignore when you check a modernization against its
original.
Discussion Questions
FILTERtakes no arguments yet transforms data. What is the mechanism, and what are two concrete risks of a routine that communicates entirely throughCOMMONrather than through an argument list?- In Phase 4, the interior-only loop bound (
2, N-1) was a clue before you had decoded the kernel. What general reading habit does that illustrate — using loop bounds as evidence of intent? - The modern reconstruction produces the same numbers but a slightly different printed layout. When you validate a modernization, how do you decide which output differences are "the same answer" and which are real regressions?
Your Turn: Extensions
- Option A. Change the driver's input to a linear ramp,
X = [10, 20, 30, 40, 50, 60], and predict the output before running. (Hint: what does a 3-point average of a straight line give? Connect it to why a linear field is a fixed point of neighbour-averaging — the discrete analogue of a linear function being exactly harmonic, the very fact that makes the boundary problems of this book solvable by hand.) - Option B. The routine hard-codes
X(6)andN = 6. Modernize the interface so the filter works on an array of any length, using an assumed-shape argument (Chapter 6) instead of theCOMMONblock. What changes, and what disappears entirely? - Option C. Find a genuinely undocumented routine in a real open-source Fortran code (many are on GitHub), apply the five-step method, and write a one-paragraph "what it does" summary plus a hand-traced example. This is the exact skill you will sell to an employer sitting on legacy code.
Key Takeaways
- The five-step method — shape, shared state, loops, kernel, hand trace — reconstructs an undocumented routine's meaning from its structure, before you change a line or even run it.
- A signature-free FORTRAN 77 routine communicates through
COMMON; finding the shared block is finding the data model, and it is always the second thing to do after reading the shape. - Loop bounds are evidence. An interior-only loop (
2, N-1) advertises special endpoints; a scratch array copied back advertises an in-place transform that must not corrupt its own input. - You own a piece of legacy code when you can rebuild it cleanly and get the same answer. The modern reconstruction is not just tidier — it is your proof of understanding, and the reference for the modernization to come in Chapter 18.