Thursday, June 16, 2011

Raising Visibility in GLOBAL, part 1

One of the key "features" of Fortran is the global, COMMON block. The major plus of the COMMON blocks is that they allow us to write SUBROUTINEs and FUNCTIONs with relatively short lists of arguments. All of the detailed information passes back-channel in the COMMON block.
This is also the major down-side of using COMMON blocks. It is devilishly hard to figure out where variables get set to certain values. In "Working Effectively with Legacy Code", Michael Feathers describes a similar problem as "Sensing--We break dependencies to sense when we can't access values or code computes." Here, Feathers is referring to things we actually can't see, like whether a socket was correctly opened.

To make this work, we need to apply three preparatory refactorings. These are small, but depending upon the

The problem of COMMON blocks is related, the problem of Visibility. We can't see the flows of information, making it impossible to reason about the code. Today I want to describe three techniques that I use to deal with problems of visibility.

1) Pull COMMON blocks into a separate MODULE. Once this is done, delete the COMMON statement(s) from each of the effected SUBROUTINEs/FUNCTIONs, one at a time, and insert a USING statement to bring in the MODULE. Next, using the compiler, verify that the COMMON is actually needed. If you did the earlier refactorings of:
  • Introducing IMPLICIT NONE into each routine
  • Cleaning up all warnings
Now this bears fruit. Removing a COMMON block will yield a set of compiler errors that tell you exactly which variables resided in that COMMON.

At this point, you will probably find out that the COMMON statements you had were, indeed, needed. Not to worry; the information we gather here will be put to good use in later steps.

The other difficulty may be name collisions across the COMMON blocks. Fixing this may seem like more busy work. I would argue that, given the nature of COMMON blocks, naming different variables the same thing is likely to cause (or reflect) confusion in what concepts the separate variables are meant to represent. Flushing that out is part of being able to work meaningfully with the code.

Another approach can be more useful, depending on the exact circumstances. Pull each COMMON into its own MODULE, and include it only where it is needed. This is most useful when we have a few COMMON blocks that are essentially independent of each other. I list this as a secondary strategy not because it is less useful. it is more useful. In my experience, finding code with relatively independent sets of variables is uncommon (pardon the pun). The reasons are two-fold:
  • Older Fortran compiler design was generally based on the idea of memory overlays. In the tight memory configurations of the time, routines would be pulled out and replaced in the same location by the next routine. This swap could include COMMON blocks that were not used by the new routine. By putting all variables in a single COMMON block, we guarantee that there memory will not be swapped and possibly corrupted. Modern OS design is generally stack-based (indeed the language standard is moving in that direction), making this less of an issue.
  • The second, less common reason is that separate blocks generally flows out of a mindset of strongly decomposing the variables into pieces associated with certain routines. This line of thinking is much more object-oriented than was typical of early Fortran.
2) Pull your user-defined types into a separate file, named something like XXX_TYPES.inc. This is a perfect place to stash your SELECTED_KIND definitions that you are using for KIND specifications. Including this file will be less intrusive than specifying the SELECTED_KIND in each and every file. More importantly, it will guarantee that the types specified are coherent and agree across routines.

Overall, today seems like a big step backward. We've taken individual COMMON blocks and essentially created global data out of them.

No comments:

Post a Comment