Dependencies, I Love You, Me Neither
We could ask ourselves these questions all the time. No matter the field of expertise, no matter the subject. It could just be in our own lives, generally speaking, or in a specific area. About someone we care about, maybe regarding our health. Or, you saw it coming, in software development.
Dependencies. How do we feel about them? Are they good? Bad? Does it depend? Are there exceptions? Good practices to deal with them?
I remember this day perfectly, it was only a few months ago. I was wandering on the RxSwift GitHub repository, reading issues and stuff. Suddenly, something got my attention: projects using RxSwift and compiled with a specific version of the Swift Compiler crashed in release. The subject is obviously thicker than this and I won’t pretend to have any high knowledge in the matter. The idea still hit me though.
This dependency relationship, regardless of how awesome RxSwift is — it really is awesome, and regardless of the fact that it had nothing to do with the issue, prevented us to do things, such as bumping our environment. We were tied — or at least more than I’m used to — dealing with the consequences of this event.
We could also remember what happened a few years ago when the left-pad npm package was unpublished by its author. This action lead to temporarily breaking hundreds of dependent projects and their own dependents projects as well.
Once again this dependency relationship is at the center of everything, a close-coupled interdependence that stiffens the whole, or a step forward at building the future together based on the efforts from our community.
Let’s take some time to examine the subject together.
Dependencies are easy to introduce, with the noblest intents, but as in every strong relation there are pros and cons.
A new project developed from scratch? An old one with its own peculiar history, like geological stratum? Technological stacks and old habits? Dependencies are our daily routine. Should they be UIKit, Foundation, Alamofire or an obscure library, they occupy many levels in our architectures for better or for worse.
A quick overview
Okay, we talked about dependencies a lot, but what are they? Without quoting any Wikipedia definitions, let’s just say that it is something that our program, or part of our program, needs. Without this little something, the program as a whole won’t work, it won’t even compile.
We all have dependencies: water is a good one for me actually. The projects I work on have others, such as something that allows me to work with reactive programming, or something that helps me to use animations and transitions…
They can be internal or external. Internal means that one element in our project needs another element which also appears to be in our project. Organs need blood. On the other hand, one element of a project may refer to another element, such as a popular library, coming from the outside — water again.
Oh, dependencies can have their own dependencies too. And we can also use dependencies to make dependency injection easier!
The impact in modern mobile software development
Metrics are difficult to gather and it’s not enough to simply proclaim the impact is huge.
Let’s take the three most popular CocoaPods packages, according to libraries.io. These are: AFNetworking (850,000+ app installations), Alamofire (650,000+ app installations) and SDWebImage (600,000+ app installations).
Verdict: millions of projects, just for CocoaPods, just for those three; no private pods, no other dependency manager. External dependencies are — almost — everywhere.
The balance
Most of the time, the struggle with dependencies lies in a series of questions that can be summed up by this one:
How the heck should we implement this feature?
A very good friend once told me this. When asked to analyze a whole new solution for his project, he answered:
- Has anyone open-sourced it yet? Is it good? Is the license compliant?
- Mmmmmh, can we buy it?
- (Do we need a custom solution for our needs?)
- Can we afford to adapt, rewrite, or re-implement the solution? With all the pros and cons that go along, such as development and maintenance costs?
In my little experience, it often falls in one of the first two. In fact, all of them lead to add a dependency.
The balance could lie in the cohabitation of homemade solutions and the integration of outsourced ones. It’s also possible — and encouraged — to separate homemade solutions in frameworks, also known as dependencies…
So what’s the point in all this?
For everything is dependency, and to dependency it shall return… Before we talk about ways to use dependencies elegantly, we have to see the truth: all of our project use, and is, a dependency.
The idea is clear: by using another package, we save time. We save time, we save money. Okay, the financial argument sadly is on paper, let’s move on.
For me, it all lies in this: why do we need to reinvent the wheel? An often very complicated wheel, crafted with care and love by dozens or hundreds of brilliant people, during a period probably longer than the development of the project itself, battle-tested, supported, not perfect but quite almost.
RxSwift and its RxSwiftCommunity is a good example of how groups of people can battle-test a product and build a whole ecosystem around them, guaranteeing a cohesion and a quality to their users.
By delegating all the tricky — and often common — parts of our products, we can focus on what’s essential: the core business of the application.
True, if this is the path we choose, we will then become more part developer, part integrator than just pure developers; like kids playing with Lego, stacking ready-made bricks. Who doesn’t have their technical stack ready for a new project? With what to use for this, what to use for that, and how it all fits together in a unique goal to capitalize on what’s been achieved before.
A daily life working with them
Now we know, we deal with dependencies, it’s a fact. The word frequency count on this word is also gigantic; I agree, my apologies.
How do they appear in our projects?
Some hints can inform us about what to expect in terms of dependencies. For instance, we could begin by looking for traces of dependency managers, such as Podfiles
orCartfiles
. Their presence does inform us pretty well, but their content is much more revealing. It simply tells us how things are made if we already know the project, or what the project does if we don’t know it yet. This is a formidable indicator of the health of the project. Does it use open-source, up-to-date, uncontested tools? Private ones developed internally? Nothing at all?
A good friend of mine explained to me how he could gauge, in his humble opinion, the sanity of a project by simply looking at its gradle
file. The lack of certain families of packages is, for him, the sign of upcoming — and maybe ongoing — issues, could they be architectural or more general. The fact that some concepts, let’s pick asynchronicity, could find their implementation greatly secured and eased by some packages, Rx for instance, is a nice example.
Paradigms like reactive programming, and by extension popular packages such as RxJava, are the answers to some of the challenges platforms are facing.
Maintaining the evolution, keeping in touch with forks
Working with dependencies implies to always monitor what happens with them. Security patches, major improvements, minor fixes… These pieces of software do have a life of their own.
We have to spend the necessary time to follow their evolution and upgrade.
It’s especially true with forks. Forking a project to customize it — maybe in the hope to see our changes be merged upstream — demands a constant eye on the source repository.
Having modifications applied on top of the repo only works at precise points in space and time. We don’t want our patches to become incompatible because they were left behind by massive changes in the source repo.
Using dependencies is a trade-off. We benefits from all the perks we’re talking about here, but we have to deal with keeping in touch with all of the dependencies. This hygiene is mandatory, and it becomes more complicated as the number of deps grows.
Dependency relation: “It’s complicated”
In order for us to use dependencies, we have to make a deal: to protect ourselves from the coupling. Let’s view that as a safeguard. We can use them but, let’s be honest: we’d like to commit, but not so much, don’t exaggerate.
Separation of concerns, abstraction layers, interfaces and protocols, dependency injection — a quite ironic name don’t you think?. These are ways for us to put them in corners. By working with Clean Architecture, by applying good patterns and techniques, we then get the most of our packages, but we acknowledge the consequences in the meantime. We want this union to happen, but it’s better with a safety net.
We could see it as oxygen. Our muscles need it — dependency expression; our lungs get it — acquisition, production; blood brings it — injection. I don’t imagine my muscles importing the lungs
package to get oxygen from it. But still: no lungs, no oxygen; no oxygen, no me; no me, no me.
My little brain does need oxygen too, but it doesn’t want to know how it’s produced, nor by whom; it actually doesn’t care. It delegates all this and only cares about the stuff that brings it, without knowing anything about it, aside from it being an OxygenProvider
.
And here’s the thing about dependencies. We all know the famous quote from Bruce Eckel: “If it’s not tested, it’s broken”. Well here, if the dependency is broken, we’re broken. If I can’t manage to link my lungs to myself, then I, as a whole, have a problem; a significantly huge one. But I can’t be a unicellular cell either, so I need dependencies, no matter what.
It’s complicated.
Moving forward
Life is based on dependencies. The food chain. The world is based on dependencies. Import-export. Happiness is based on dependencies. Science-fiction franchises and me — yes, I’m looking at you, Stargate.
Some of the dependencies of our projects are mandatory, but others can be chosen. Just like in real life. Let’s choose wisely.
We’re in an era of banalization regarding the use of packages. This context is highlighted by the accumulation of open-source frameworks and competition between agencies. But let’s not be ashamed or feel guilty. Let’s embrace this era. Foundations are made to be built upon, but with full knowledge of how to build properly.
The interdependence of all things couldn’t be set aside of software engineering. It’s okay to be dependent, it’s how things work out there. What’s not okay is to ignore it.
They’re a necessary evil, a helpful good. Allowing us to focus and to delegate. Making us at our own mercy. Linking us to each other. In community, fighting for the future, together.
Let’s keep this virtuous circle running. Let’s try to make it spin faster and faster. Let’s contribute more.
As software engineers, we all have to choose: where do we spend our time, efforts, and resources? We live in a world of finite means. We can’t be everywhere, taking care of everything.
For me, the most important thing is our ability to focus.
I humbly choose to focus on what makes a product what it is, by relying on community-battle-tested dependencies to accompany me, to help me focus.
Maybe one day I’ll work on my proper dependency, to achieve a little something I couldn’t find, hoping to help others in return…
The author would like to thank Ryan Gordon, Thibault Wittemberg, Hadrien De Sousa, Morgane Juan, Vincent Labrecque, and his team for their proofread, support and advice.