My journey through time to see how the software design principles I teach on Codemanship courses could have been applied in the past continues to 1979, 40 years ago. Although it was already 22 years old by then, Fortran was still one of the most popular languages – particularly in scientific and engineering computing.
The language had an upgrade in 1977, but was still recognisably the very procedural language that was first envisaged in 1957. I used Fortran 77 in my degree studies, and I last used it 27 years ago. How many of the design principles I recommend to developers could I have been applying in it?
Let’s start with the 4 principles of Simple Design:
- Should Fortran 77 code work? Well, I think so. Don’t you? In the early 1990s when I was writing code to do computational maths, I was a pretty naive programmer. I wrote no automated tests, and typically wrote all the code for a program – usually just 100-200 lines of it – before trying t to see if it gave the answers I expected. Can you automically test Fortran 77 code? Of course you can. If you can write code that calls code, you can write automated tests.
- Should Fortran 77 code clearly communicate its intent? Again, does anyone believe that readability doesn’t matter in Fortran 77? The language has all the mechanisms we need to endow our code with meaning – i.e., opportunities to name things.
- Should Fortran 77 code be free of duplication (unless that makes it harder to undersand)? The question is really one of language design: does Fortran 77 allow us to reuse code instead of repeating it? Yes, it does. Functions and subprocedures can encapsulate repeated code. And – in most implementations – code can be split into separate reusable “modules”. In the GNU77 version I used for this example, multiple “library” files can be separately compiled and linked to a main program, allowing for relatively easy reuse.
- Should Fortran 77 code be composed out of the simplest parts? It’s entirely possible to write Fortran 77 programs that are made out of functions and subprocedures that are very simple, if you choose to. Whether the language scales to very large composed programs of tens of thousands of lines of code is an interesting challenge. There are some limitations on naming in particular (Fortran 77 names aren’t case-sensitive, and cannot be qualified with namespaces or module names, so more thought is needed to prevent us running out of unique and meaningful names as we add more and more parts. But in 1979, this was less of a problem because of hardware limitations)
By and large, Simple Design is perfectly feasible in Fortran 77.
What about Tell, Don’t Ask? Let’s consider a familar example of a function that knows too much.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
real function quote(width, length, pricePerSqMtr, roundUp) | |
real width, length, pricePerSqMtr | |
logical roundUp | |
real area | |
area = width * length | |
if (roundUp) then | |
area = ceil(area) | |
endif | |
quote = area * pricePerSqMtr | |
return | |
end |
The dialect of Fortran 77 implemented for the GNU77 compiler doesn’t support data structures (other dialects, like the one I used at university running on Sun hardware did). So we have to pass in all of the data about the room and the carpet as individual parameters to calculate a quote. We are somewhat limited in what we can do as far as data encapsulation is concerned as a result.
If this was Fortran 90 or later (or Sun F77), we could have user-defined types for the room’s dimensions and the carpet’s pricing data, and we could encapsulate their creation in the same modules that access that data. In F90, we also have some control over visibility of module features. (See how similar language features enabled data encapsulation in C in a previous post.) So, if this was Fortran 90, things would be much easier.
In F77, we have a teeny bit of wiggle room to represent an “object” (e.g., a room) as a single entity that quote() doesn’t need to know the internal details of: we could represent the room’s dimensions as an array.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
real function quote(room, pricePerSqMtr, roundUp) | |
real pricePerSqMtr | |
logical roundUp | |
real area | |
area = area_of_room(room) | |
if (roundUp) then | |
area = ceil(area) | |
endif | |
quote = area * pricePerSqMtr | |
return | |
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
real function area_of_room(room) | |
real room(2) | |
real width, length | |
width = room(1) | |
length = room(2) | |
area_of_room = width * length | |
return | |
end |
And our test client just passes in the room array.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
program CarpetQuoteTest | |
implicit none | |
real quote | |
real price | |
real room(2) | |
room(1) = 2.5 | |
room(2) = 2.5 | |
price = quote(room, 10.0, .FALSE.) | |
print*, "Quote (not rounded) = ", price | |
price = quote(room, 10.0, .TRUE.) | |
print*, "Quote (rounded) = ", price | |
end |
It’s not so easy for the carpet pricing data. Fortran 77 arrays can only be of a single data type, so we can’t easily represent a real and boolean value in the same array without adding considerable complexity (e.g., a function for translating 0.0 an 1.0 into FALSE and TRUE). Is it worth it?
But it would be worth extracting a function in its own module for calculating the price of a carpet for an area of room, so that each module has a Single Responsibility.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
real function price_of_carpet(area, pricePerSqMtr, roundUp) | |
real area, pricePerSqMtr | |
logical roundUp | |
if (roundUp) then | |
area = ceil(area) | |
endif | |
price_of_carpet = area * pricePerSqMtr | |
return | |
end |
So quote() can be simplied to:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
real function quote(room, pricePerSqMtr, roundUp) | |
quote = price_of_carpet(area_of_room(room), pricePerSqMtr, roundUp) | |
return | |
end |
Now, could we make these dependencies swappable? Well, surprisingly for this language, we can. Fortran 77 allows function references to be passed as parameters. So we could, for example, swap our area_of_room() function with a different implementation that has the same signature.
What if we also want to calculate the area of carpet required to fill a circular room?
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
real function area_of_circular_room(room) | |
real room(1) | |
real radius | |
radius = room(1) | |
area_of_circular_room = (radius * 2)**2 | |
return | |
end |
If we add a function parameter to quote()…
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
real function quote(room, pricePerSqMtr, roundUp, area_function) | |
quote = price_of_carpet(area_function(room), pricePerSqMtr, roundUp) | |
return | |
end |
…we can substitute this implementation by injection from the test program.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
program CarpetQuoteTest | |
implicit none | |
real quote | |
real price | |
real rectangular_room(2) | |
real circular_room(1) | |
external area_of_rectangular_room | |
external area_of_circular_room | |
rectangular_room(1) = 2.5 | |
rectangular_room(2) = 2.5 | |
price = quote(rectangular_room, 10.0, .FALSE., area_of_rectangular_room) | |
print*, "Quote for rectangular room (not rounded) = ", price | |
price = quote(rectangular_room, 10.0, .TRUE., area_of_rectangular_room) | |
print*, "Quote for rectangular room (rounded) = ", price | |
circular_room(1) = 2.5 | |
price = quote(circular_room, 10.0, .FALSE., area_of_circular_room) | |
print*, "Quote for circular room (not rounded) = ", price | |
end |
This is only possible if the data required by each implementation either doesn’t change – so the parameters stay the same – or can be encapsulated (e.g., in an array) as a single parameter. Fortran 77 has very little support for data encapsulation, and this limits the scope for swappability. So I give it 50% for swappability.
Finally, since Fortran 77 has no explicit concept of modules, interfaces and visibility, we can’t control what features are exposed to a client. We can control what features that client uses, of course. But in my G77 set-up, if I make any change to any module, all the modules that depend on it have to be recompiled, even if they don’t use the feature I changed.
So, to sum up: Fortran 77 ticks quite a few of my boxes and has some limited SOLID credentials, but it’s not quite there. Fortran 90 fixed some of these problems, with derived types, explicit modules and explicit interfaces, making it more like C in those respects.
I give Fortran 77 6.5/10 for ease of applying these design principles.
You can find the complete source code at https://github.com/jasongorman/fortran-77-SOLID