Michael Eriksson
A Swede in Germany
Home » Software development | About me Impressum Contact Sitemap

Dependencies

Keep dependencies to a minimum

Dependencies should be kept to a minimum, be properly abstracted, and only prevent build (installation, running, whatnot) when they are actually vital to the build—not a secondary nice-to-have. This is not limited to third-party software, but includes inter-module and inter-library dependencies within a single organisation or project.


Side-note:

This discussion may seem odd to a Windows user, who is used to get all applications in one giant, bloated installation. The rest of the world has much more efficient install-on-demand solutions with automatic dependency management available. Consider apt-get, Maven, CPAN, ...


Illustration based on an anti-example

An anti-example: A developer has written a small CLI tool to make rudimentary sorts. As an afterthought, he has the idea that it would be neat with a small GUI, where the CLI-fearing could copy-and-paste their data and click on a sort button. He does this using a third-party GUI library, which is several orders greater in size than the tool, and which in turn has other decencies. Another developer tries to install this tool, intending just to use it on the command line—only to find that he can only do this if also installs the GUI library, the libraries it depends on, and possible further recursive dependencies. In the end, he has spent half-an-hour down-loading and installing several hundred MB. This to run a program of just a few dozen code lines—and which he could have written himself from scratch in the same time it took to do the downloads.

What to do instead

How should the original developer have proceeded? Firstly, he should have strongly considered whether the GUI was a good idea at all. Gratuitous GUIs bring little benefit, and they should only be added when there is a serious chance that they will be needed. Secondly, the inclusion of the GUI should have been made conditional, e.g. by dividing the package/module in two parts, one with the core functionality and one (optional) with the GUI. Notably, this has the added benefit of making switches to other GUI-libraries easier, as well as simplifying ports to other platforms.

Note also the concept of indirection: Having a dependency on an abstract GUI interface (which would hide the actual implementation) would reduce the problems noticeably. For one thing, a dummy implementation can be provided for those who do not need to actually use the GUI; for another, switching implementation is easier, as above.


Side-note:

The use of GUI as example is unimportant (but a common case in real-life): Corresponding problems exist with very many “nice-to-haves” and unimportant add-ons that most users will be happy to do without.

The same principles apply here: Consider whether the feature is actually needed. If not, it is likely best not to include it in the first place. If it is included, but not central, use a solution with an optional add-on, a plug-in, or similar.


Libraries are not magical

Generally, one should always bear in mind that libraries are not magical: If a dozen lines of own code calling a library replaces several thousand lines of entirely original code, then those several thousand lines are still present in the library—typically with a hundred thousand other lines that are not used.

Do use libraries

The above does not mean that libraries are to be avoided: On the contrary, libraries are a wonderful thing—and re-inventing the wheel to avoid using an existing library is rarely justifiable.

The above centers on misuse, gratuitous use, and lack of insight into consequences: Use a library because it extends the application in a truly worth-while manner, or to avoid a redundant implementation of existing functionality; not because it is there, would make it easy to add a feature of disputable value, or similar.

Try to use strict hierarchies

In particular in own development it pays to use strict hierarchies of who can call whom, and to enforce these hierarchies with build tools and other checkers. Consider the following simplified example:

A company has a standard platform (sp) which is used as the basis for several projects. One project is pr. The standard platform provides the packages
sp.util,
sp.user,
sp.useradmin;

and the project

pr.util,
pr.user,
pr.useradmin.

One reasonable hierarchy would be to take the packages in that order: pr.useradmin may call e.g. sp.useradmin, sp.util, and pr.user. However sp.useradmin may only call sp.util and sp.user; pr.user may not call pr.useradmin, but e.g. pr.util; etc.


Side-note:

Obviously, in particular for complex projects, tree-like hierarchies will be the rule e.g. where packages A and B are both allowed to depend on package C, but neither is allowed to depend on the other (nor C on either of them). That the above is a linear hierarchy is just a side-effect of the simple example.


This makes it easier to avoid conflicts, keeps modularization higher, makes it easier to replace or remove components, etc. In particular, it removes the risk that one project becomes indirectly dependent on a second project—a dangerous sloppiness that regularly occurs in small organisations.

Excursion on module-based designs

An increasingly common design is to use a smaller, sometimes minimal, core with any non-core functionality handled by optional modules, plug-ins, or add-ons. This is normally a good idea; however, it is often handled poorly from a user’s POV. Typical errors include:

  1. Failing to include central functionality in the released basic versions. A good example is Firefox, which lacks e.g. the ability to mask the referrer or to selectively block content using using URL patterns—both things that any modern browser should provide out of the box.

    That these functionalities are delegated to add-ons from an architecture POV may it self be correct; however, that the user has to download them himself after installation is not. To make matters worse, it is my impression that the functionality is only available through third-party add-ons. (I may be wrong, however, being mainly an Opera user.)


    Side-note:

    Firefox does have a built-in image blocker. This, however, is a very ungeneric and unsatisfactory substitute.


  2. Including too many nice-to-have modules, making the application unnecessary bloated and hard-to-overview—and removing many of the advantage of a modular approach. Far too many companies try to forcefeed the user every last feature they can think of—whether he will benefit from and want them or not.


    Side-note:

    Is this not a paradoxical claim in light of the previous item?

    No, not really: Firstly, the features mentioned for Firefox are “due diligence” issues. I cannot recall the last time I used the “spray water” function on my iron; but I would still consider any iron not having one to be defect. A built-in mp3 player, OTOH, that is truly just a nice-to-have. Secondly, these features are logically far closer to the core than most other add-ons. A control for the amount of steam that an iron gives off is close to the core functionality of an iron, both conceptually and implementation-wise. If it is available, it should reasonably be available with the out-of-the-box product—not over an add-on to buy separately. (And, no, I cannot recall when I last changed the steam setting either.)


  3. Not providing proper controls against unwanted dependencies (for third-party developers) and automatic installation of dependencies (for users). A third-party developer should be kept in sufficiently strict reins that there is no risk for circular dependencies, small add-ons that drag a long tail of other add-ons with it, and so on–at least for add-ons that are distributed through officially and semi-officially channels. A user should never have to manually install dependencies; this should happen automatically (after his confirmation that he still wants to install the add-on, even with the need for further items).