You have a C module containing variables and functions that implement two different responsibilities.
Split the two responsibilities into two modules.
The C language provides limited scoping and encapsulation of data and functions. It has no objects, but it does provide encapsulation of data at the module level (i.e. within a single file) with the
static storage class.
When you have a module that implements multiple responsibilities, there is the possibility that the code implementing two distinct responsibilities will become coupled. You can avoid the coupling by separating each distinct responsibility into its own module. Each module becomes its own abstract data type. This is similar to the Extract Class refactoring for objects.
- Identify the reponsbility you want to separate and its associated data and functions.
- Create a new module source file (module
.c) and header file (module
.h) for the extracted responsibility.
- Move the definitions of variables and functions associated with the responsibility to the new module’s source file. Compile the new module to check to dependencies on header files that were included in the original module. Add any necessary
#includelines from the original module to the new module until all referenced identifiers are defined for the new module. If the new module has dependencies on identifiers in the old module, resolve them by creating a new global declaration for the dependencies. Add the new declarations to the original module’s header file. This may require changing some previously static identifiers to non-static so they are visible to the new module.
- Move the declarations for global variables and functions associated with the responsibility to the new module’s header file.
- Include the header of the new module in the original module to expose the declarations of the new module to the old module.
- Compile all the code together to ensure that all references are resolved.
- Run regression tests to ensure that the functionality remains unchanged.
FractInt is a 16-bit DOS program written in C. Its heritage is such that code was organized more by implementation than by concept. As a result, many source files implement more than one responsibility leading to highly unmodular code. We can make the code more modular by separating responsibilities. These modular responsibilities can then be more readily migrated to C++ objects so that object-oriented refactorings can be applied.
1. We identify the responsibility we want to separate.
miscfrac.c contains a large number of different routines for implementing different fractals. Things named “misc” (for miscellaneous) are generally hinting that they have no single responsibility and are a grab bag of functionality. Consider the cellular automata implementation starting at line 1366 of
miscfrac.c. Most fractals computed by fractint are generated by iterating a complex number equation on a grid of sample points in the complex plane. Cellular automata operate under a completely different model in which a simple simulation is performed and pixels are colored according to the state of each cell in the grid. Clearly this kind of fractal rendering is very different from the others.
The implementation goes on for some 500 lines in the source file and declares the following identifiers:
#define BAD_T 1
#define BAD_MEM 2
#define STRING1 3
#define STRING2 4
#define TABLEK 5
#define TYPEKR 6
#define RULELENGTH 7
#define INTERUPT 8
#define CELLULAR_DONE 10
static BYTE *cell_array;
S16 r, k_1, rule_digits;
void abort_cellular(int err, int t);
int cellular ();
static void set_Cellular_palette();
Of all these identifiers, only
cellular() is referenced by any other part of the program.
2. Create new source and header files for the module.
cellular.c in the same directory as
miscfrac.c and create
cellular.h in the same directory as the other headers.
3. Move the definitions to the new source file.
We can cut lines 1366 through 1807 from
miscfrac.c and paste them into
cellular.c. We compile the module to determine which, if any,
#include lines from the original module need to be included. We add the necessary includes to
cellular.c until the module compiles cleanly.
4. Move the declarations for identifiers from the original module to the new module’s header file.
In this case, the only identifier in the new module that is referenced by any other code is
cellular, so we add the following contents to
extern int cellular();
5. Include the header of the new module in the old module.
We add the following line to the old module’s includes section:
6. Compile all the code to ensure all identifiers are resolved.
We compile the whole project. All the identifiers are resolved and linking succeeds.
7. Run regression tests.
FractInt has no automated regression tests. We can test that things still work properly by running the program and selecting the
cellular fractal type with a predetermined set of parameters. We can generate an image from this set of parameters before and after we make the change and compare the two images. They should be the same.
At this point, you may wish to consider changing the linkage of identifiers referenced only in the new module to
static so that they can’t accidentally be referenced by other modules.
This completes the refactoring.