How to modularize a piece of the base image
Last updated at 5:09 pm UTC on 16 January 2006
(This page is still being edited.)
Currently most parts of the standard image are "unclean" modules that cannot be unloaded independently. Here we will present the steps that have to be performed to make a module clean (having delta modules, right dependency mechanism...) that can be unloaded properly as a separate unit.
A clean module has only good-natured dependencies to itself> from other modules, a.k.a "incoming" dependencies. It should also declare its dependencies to> other modules.
3 types of dependency
Dependencies come from two sources: Either other modules depend on messages in your module, or they use global variables that your module defines. Here we will manly discuss global variables since they cover the most pressing problems.
All incoming dependencies come in three types:
- Type 1, "proper dependencies". Dependencies from other modules that really should depend on you, e.g. Speech naturally depends on Sound. These modules should be unloaded before you can be unloaded and their dependeincies on you are good-natured. These should be left as they are, and the other modules should declare that they depend on you (How to declare module dependencies).
- Type 2 dependencies, "class extensions". These are methods that your module "adds" to classes in other modules, in that they don't make sense unless your module is loaded, and therefure really "belong" to your module. For example, a 3D package may add methods to other classes that allow them to use the 3D package. This is not entirely pretty but useful and this is what is done in Smalltalk. These "class extensions" should be collected in DeltaModules belonging to your module. In this way they will be removed from the other modules when your module is removed, which makes them good-natured too.
- The remaining are all of Type 3, "bad dependencies". They are located in modules that shouldn't depend on you. These will have to be refactored in various ways to be resolved (in effect, removed).
The tools and steps to analyze and fix dependencies
Note that this process will not always be this simple but the tools will be the same in any case. The example is StarSqueak, which is easy to analyze and fix, but still shows the general technique.
1. Find all incoming references to this module
We start by getting all incoming references to StarSqueak from the outside.
aModule := Module @ #(Squeak Media StarSqueak).
aModule deepIncomingRefsFromOutside: aModule
"(note that this method might be moved on the class ModuleRefactorer in the future.)"
Use explore rather than doIt so you can see the result. (This applies to most of the analysis methods.)
The resulting Set has all the external methods that refer to any names defined inside the StarSqueak module.
- Note to author: what about subclassing the classes defined here?
- We could use aModule deepIncomingRefsFromOutside: aModule parentModule> to identify only the references that are not made by brother modules. See Useful module code snippets for an example.
- Note that this method lists dependencies of all types above (type 1-3). A new analysis will have to be written to exclude the type 1 and 2 dependencies above from other modules to this module. This is necessary for more complex cases than StarSqueak.
2. Dealing with the 3 types of dependency
In the main majority of the cases you will be facing a big mess of references. You should categorize them into the three types above, and then deal with them as follows.
Type 1. Just make sure that the other modules really declare their dependency on you properly (How to declare module dependencies).
Type 2. For class extensions you create "delta modules" (these are extensions of the other modules that will be loaded when your module is loaded, and removed when it is removed). This is done automatically like this:
aModule := Module @ #(Squeak Media StarSqueak).
aModule defineClassExtensionsOutside: aModule
This method simply collects all dependencies (types 1-3), and defines delta modules for all of them. For StarSqueak there are only methods of type 2, so this is ok. But a method that skips proper dependencies should be written.
Type 3. The bad dependencies will have to be refactored in various ways to be resolved. This is the hard part of the work. See The hard part of refactoring.
Now all dependencies are handled. You should also declare the prerequisites for your module, ie. outgoing dependencies. It is done automatically like this:
| aModule outsideReferences |
aModule := (Module @ #(Squeak Media Movies)).
However, typically handwritten declarations will be needed to give ideal results.
- Note that the current analysis performed by deepDeclaredExternalRefs is weak in the sense that it creates the chain at one level and does not check that it could be introducing cycles. See To do
3 Save on file and reload
See also Storing modular code using module repositories, Unloading modules, and Installing modules from repositories.
- After saving the module to disk, the first check is to see that unloading works without crashing the image. If it didn't work, you will know.
- The second check would be that all of the module is really gone. There is currently no really good way to know that all objects of a module really disappear. Perhaps to put them all in a WeakArray, then unload the module, force GC, and see which ones are still there in the array?
4. Create a subclass of ModuleRefactorer
This class is a script that should be run to refactor your module, performing the same steps that you have done here. The idea is that the scripts should be able to be sent around and rerun by others. For the moment this class only includes moving classes or modules around, so you may need to contribute improvements to this class. Please post them to the list.