What makes a good (Swift) software project? That depends on a vast number of variables. In this article I don’t want to start a broad discussion on the „best“ architecture to choose or pinpoint Signal’s weaknesses. My aim is to demonstrate SwiftAlyzer and explain its unique strength as review tool. Therefore I will analyse Signal’s iOS app – but without ever looking at the source code or doing any analysis beforehand.
As mentioned above, I focus on dependencies as a main root of evil. This is explained further below. I try to give more information in these yellow boxes whenever I feel like my arguments could do with some more explanations.
(I checked out the develop branch at commit 962369b2676a10da9cf130aa27c87f7c45c325cb)
Analysing the building blocks on 1st levelThe first impression are the modules the app is separated into. Swift’s standard libraries (and Cocoapods dependencies) can be shown too, but are hidden by default. These modules are the building blocks on the highest level. We will dive deeper into those later on and see building blocks for every Xcode group down to the classes at the lowest level.
What we see here is the four modules the app consists of and what is immediately noticeable, is the interconnection of each pair of modules. While it’s a good idea to break down big projects into several modules, these should have as few dependencies as possible. Modules should (as far as possible) work on its own, consisting of features that belong together. Features of different modules should (as far as possible) stay independent and therefor should have no connection among each other. The current situation leads to a very high CCD value which is definitely a thing to avoid.
Dependencies are unavoidable, but the number of dependencies should be as low as possible. Since every dependency introduces some complexity. If a class A depends on class B, changes on class B could lead to bugs and incompatibilities in class A. If class B has 10 dependent classes, every change on B could lead to followup changes on those 10 further classes. If furthermore class B depends on a class C, these dependencies sum up and can lead to maintainability nightmares.
The correct term is Cumulative Component Dependency or CCD in short.
It’s defined as follows:
CCD is the sum over all components Ci in a subsystem of the number of components needed in order to test each Ci incrementally. This is true for direct and indirect dependencies.
SwiftAlyzer calculates this metric for classes, Xcode groups and modules.
As mentioned before, I didn’t take a look at the source code. But that would not help here too much, since we’re looking at bigger structures than classes. At this level it’s not as easy to guess the purpose of a structuring unit. We can make an educated guess looking at the names and hope that the content follows the semantic proposed by the naming.
At this level I usually resort to architecture documentation like ARC42. But in one of SwiftAlyzer’s upcoming versions, there’ll be a documentation feature. Hovering over or selecting a unit fades in its description – provided as contained readme or so.
Analysing the Signal module and its packagesA deeper look into the Signal module reveals six packages.
Actually they’re Xcode groups but I like to call it packages which should not be confused with Swift packages. In the Java world packages are the means to structure classes into groups of common functionality. In Swift projects we got Xcode groups to structure classes into bigger units. SwiftAlyzer makes use of classes, Xcode groups and modules to give a higher level overview of the project and its dependencies.Four resource packages filled with fonts, sounds, Lottie animations and support files. The remaining two are distinct into test and production code. Hiding all resource and test packages and further extending the src package shows the following: The first thing that comes to mind, is that the Signal messaging module has a bi-directional dependency to almost every package. And a circular dependency to every package due to inter-dependencies among the packages. As mentioned earlier, this breeds a maintenance nightmare and reads as very high CCD value.
The cause of the dependencies between these packages and the Signal Messaging module is left as exercise for the reader 😉 I’ll take a closer look at the inner-package dependencies of the src package further blow.
The second thing that strikes me as odd, is the organisation of content by source type, i.e., user interface, model, Jobs, etc, instead of a feature-based separation. This type of partition does not grow well. When implementing a new feature, it’s models, user interfaces, etc are distributed among several packages, mixed with classes of every other feature. This has two main drawbacks. Firstly, there’s no limit on the growth of a package, which makes it harder to navigate around the Xcode project. Secondly, there’s no (semantic) correlation between the classes of a package, which makes it impossible to plan dependencies on a package or feature level.
Deep dive into a package to discover applied design patterns and infringements
Where to go from here:
If it made you curious feel free to sign up on swiftAlyzer.com and try it out on your own! There are more things that can and should be investigated, e.g. why the util package is not a sink.
Also feel free to reach out if you have any questions or comments. Love to hear feedback from other developers ❤️