Splitting an app into modules is an essential technique for managing software complexity. Modules allow us developers to focus on smaller parts of the app at a time by hiding away complexity.
Although they hide information, a module need to expose some of the code it contains, otherwise other modules couldn’t interact with it. There’s a balance we need to find between exposing too many things and exposing too few. Both have different problems:
- When a module contains many public or open entities, it’s a sign that the module does many unrelated things. This increases the cognitive load, meaning that it get’s complicated to keep in mind what the module offers and how we can use it. At the same time changing something within the module get’s error prone and time consuming. Entities outside of the module could depend on anything.
- Exposing too few things prevents the module from being useful to other modules. It raises the risk of code duplication as other modules might need to re-implement existing but hidden functionality.
Getting a good balance is challenging, but it will create a better module structure with understandable responsibilities and dependencies.
In this post we take a look at the metric Module Deepness as a tool to identify the balance between exposed and hidden parts of a module. We’ll see how a good balance looks like and how we can measure it. After we identified a way to get this information, we’ll see how Sw!ftalyzer supports us in understanding and improving our modules.
This metric was introduced by John Ousterhout in his book “A Philosophy of Software Design”.
What’s Module Deepness?
In this book John argues that a module should be deep in order to be useful. A deep module is one that has a small interface compared to its implementation.
The interface is everything that a developer needs to know about a module to use it. John differentiates between a formal and an informal part of the public interface. The formal part consists of the declarations a module contains. The informal part includes documentation, comments, naming and other kind of knowledge needed. The implementation is the rest, all code that’s not part of the public interface.
When working on a module, we must consider both the current module’s implementation and the interfaces of other modules it depends on. Thus the deeper a modules is, the less one needs to know and think about it and the more one can focus on the current module.
The opposite of a deep module is a shallow module. It’s one that has a large and complicated interface and a small implementation. He considers shallow modules a “Red Flag” as they don’t provide any help in battling complexity.
Use Cases of Module Deepness
Now that we have an idea what this metric is about, let’s explore how we can use it to improve our understanding of a project.
We’ll use a simplified version where the interface consists of all public and the implementation of all non-public code entities. This approach doesn’t take the informal part of the interface into account, which makes collecting the numbers a lot easier. The simplification also makes no difference between a public code entity with 100 public methods or with only 1 public method. Think about module deepness on entity level making it again easier to collect the required information and reason about it.
There are two main use cases of the module deepness metric:
- Understanding the purpose of a module (“How is a module used?”)
- Evaluating the scope of a module (“Does a module contain the right thinks?”)
Let’s start with the first question: How is a module used? To do so, we analyse a real-live production app: IceCubesApp from Thomas Ricouard. On the right you can see the overview of Module Deepness of the project. We can see at first glance that Timeline and Conversations are rather deep modules. Thus they probably contain coherent and self-contained features. Timeline has 12 hidden and 5 exposed code entities, leading to a deepness of 71%. Conversation follows closely with a deepness of 67%.
The modules Models and Network are rather shallow with just 11% and 6% of hidden entities. Most likely they are foundational modules that contain entities shared by many other modules. Changing the entities of these modules may require changes to many other modules that import and depend on these two.
Without looking at the source code or at dependencies between the modules, we already have a rough idea of how the modules are used. Also, we get a feeling for their position in the module hierarchy. This allows us to make assumptions on how much effort is needed to change a module and gives us a great starting point for further investigations.
Of course this assumes that a developer did the right thing and chose reasonable access levels like it’s the case for IceCubesApp. If all entities in every module are public, this would not help us understanding the purpose of the modules. However, even if that’s the case, this is a valuable insight into a new and unknown project. The project lacks information hiding and we can’t rely on the module structure, because everything is accessible.
Let’s now look at the second question: Does a module contain the right thinks? Another use case of the Module Deepness metric is to identify issues in how a feature module is cut.
When extracting a feature module from an existing module, it’s tempting to throw all entities related of a feature together into one module. But this may not result in the best way of structuring this feature.
Sometimes, what appears to be a coherent feature to users might be better implemented as many feature modules. This may lead to a low deepness as different parts of the app use the feature module, requiring many code entities to be public.
Other times we could assume that a feature is stand-alone, but we’d again end up with a low deepness after extracting it into a module. In that case it was not stand-alone but part of a potentially bigger feature module. This bigger feature now accesses different part of the feature module.
In both cases we need to rethink the way a feature module was cut and we can use the module deepness metric to guide us.
Please note that the second point only holds for feature modules. Technical and shared modules like those with common UI components or similar topics will always be more shallow. And that’s fine, not every module needs to be deep and not every deep module will be good. The module deepness metric is just another tool in our toolbox to understand our project in more detail.
It’s possible to calculate the module deepness by hand by counting all entities grouped by their visibility. But this is a time consuming process, especially when we want to check the module deepness more often. Time that could be better allocated in improving our project. Let’s see next how we can use Sw!ftalyzer to see this metric without any work on our side.
Analysing Module Deepness with Sw!ftalyzer
Sw!ftalyzer automatically analyses the deepness of your modules and thus makes it very easy to check this metric. It considers all open or public declarations of a module as part of the interface. All internal, fileprivate or private declarations are part of the implementation.
There are two ways of viewing this metric: The module deepness of the currently selected module with more details and a general overview of the deepness of all modules in the project.
We find detailed information in the first tab of the information panel, which shows information about the currently selected element. We select a module either in the structure overview in the left panel of Sw!ftalyzer or in the dependency graph in the middle panel. We find its deepness as a percentage of hidden entities in relation to all entities. Additionally, it shows the number of entities per category here.
The first image below highlights where to find this information. The second and third one show examples of how the module deepness metric looks like for two different example modules. The middle image shows the deepness of the first module. It contains 3 entities, 2 hidden and 1 exposed one. This results in a deepness of ~67%, which indicates a rather well cut feature module. In contrast to that, the module in the last image doesn’t hide any complexity, all its entities are exposed to other modules making it a shallow module.
We find the overview of all modules, which looks like the one at the top about the modules in IceCubesApp, in the third tab of the information panel. This tab shows general project information like a list of unused entities, an overview of files that are part of multiple targets and much more.