I'm going to rely heavily on the compiler, because it is better at tracking picky details than I am.
- We remove the USE GLOBAL statement in SUBROUTINE ABC.
- We insert an IMPLICIT NONE statement
- We insert a USE TYPES statement, where TYPES is the MODULE that contains:
- the INTEGER parameters that specify the SELECTED_KIND for INTEGER and REAL variables
- The definitions of all of our derived types
At this point we compile. If we previously cleaned up all of the warnings, and explicitly declared all of the variables use in SUBROUTINE ABC, the only errors and warnings we should get come from variables that are defined in GLOBAL.
For every variable that shows up, we create a new argument to SUBROUTINE ABC, and specify its type. As they say, lather, rinse, repeat until the compile is clean.
Don't be surprised if you have an extremely appalling list of parameters (I've generated calls with over 35 arguments using this approach). The nasty dependence was there all along, you've just made it manifest.
Now for the really important step:
Examine GLOBAL and look up the type of every variable. If any of them are ALLOCATABLE and/or POINTERS, you must introduce an INTERFACE (with the signature and types of the variables in SUBROUTINE ABC). This interface has to be included in SUBROUTINE ABC_FACADE. Note that this is another advantage of introducing SUBROUTINE ABC_FACADE;we only have to have the INTERFACE (inline or via an INCLUDE) in SUBROUTINE ABC. All of the client code is blissfully unaware that anything has changed.
In the compilers I've used, failing to do this will not yield an error or even a warning. Everything will be fine until we get to the routine that calls ABC_FACADE. When the caller is called, a nasty and completely uninformative run-time error is generated. For example, using Silverfrost, the .NET run-time gives an "illegal program" error from the JIT compiler. The most heinous part of this is that some compilers won't blink an eye; everything will run just fine. But I've seen another version of the same compiler choke and die. Even if you don't have a problem, fix it. Or be prepared to have previously fine code suddenly go down in flames when you upgrade your compiler or send your "perfect code" to someone else (usually a boss, or someone at a funding agency).
Tidying Up
Next, for each of these parameters, take the time to specify INTENT as part of the declaration. Since we're already mucking around in these variables, it is a perfect time to go ahead and give the compiler the extra information. I've found that the INTENT specification is invaluable. Say I originally do this refactoring, specify INTENT, and get the compile clean. A week later I'm back, trying to restructure this routine, and I assign to a variable that is an argument (say re-using as a temporary variable within the routine). Bang! Nasty bug has appeared out of nowhere, and it's going to be a freaking bear to find. With INTENT(IN) specified, the compiler chokes immediately, showing me the error of my ways. It's not a fool-proof system (as a fool, I can be quite ingenious), but I'll take any help I can get.
So, we've simultaneously:
- Made all of the dependencies of SUBROUTINE ABC on GLOBAL explicit (our goal)
- Insulated the client code from the effects--it calls ABC_FACADE as if nothing has happened. A first, clear example of this is the case where we have to introduce an INTERFACE for
Now we are free to restructure SUBROUTINE ABC, secure in the knowledge that we aren't inadvertently creating side effects (or, more likely, bugs), by messing up a GLOBAL variable that we haven't recognized.