Day 23: Isolate Code Dependencies
It is not a secret to experienced developers that most of the work in modern programming consists of interfacing application code with external libraries. For instance, if you work on application programming (as opposed to system development), there is no reason why you should be required to write another logging package or another data base access layer.
To reinforce this paradigm, the order of the day in the commercial world it to adopt languages with big libraries, and use these libraries as much as possible. This has been already referred to as “glue code programming”, and it is the reality of almost everyone working on software development.
There are advantages and disadvantages in this trend, which I will not consider here. Instead I would like to consider the issue of dependency management in application code that is written in such an environment.
One of the big problems we have when using libraries is that importing and calling them is easy, but each time a new library is added it creates a new dependency in the code base. Like with any other piece of code, having several external dependencies is bad for maintenance, especially if you expect the application to last for a long time.
One of the issues is that libraries change. Some of them may disappear for whatever reason (particularly if it is provided by another company). Other libraries are updated, which mean that a few methods are deprecated, a few others are added, and your client code has to adapt. This kind of changes happen everywhere, even in standard libraries, as has been the case of Java for example.
The more libraries you use in an application, the larger the potential for problems related to API changes and deprecations. A program that uses dozens of libraries may experience frequent changes. This is even more true if one uses a library that is still in heavy development, and subject to frequent changes.
Dependencies and Separate Compilation
Another issue may arise if you use languages with separate compilation such as C++: the compilation time required by any module is correlated to the number of libraries you’re incorporating. In C++ the process of library inclusion depends on adding header files. Because header files are traditionally a coarse grained method to include libraries, we may be required to compile many such files to gain access to just a small functionality from a single library.
For example, if we use a graphical library to draw shapes, we might want to draw only a rectangle using a RectangleShape object. However, C++ headers usually include everything provided by a library, including an EllipseShape or even a GLBufferedComplexShape. The issue is that it becomes too time consuming to compile libraries such as this one each time you need to use a small part of its functionality.
Using Library Interfaces
The good news is that there are techniques to avoid the unnecessary coupling between external libraries and application code. The techniques are simple and can be systematically applied to software written in any language. If you happen to use C++, however, there is the extra advantage of reducing compilation times for each translation unit.
The main idea is to create a wrapper class that localizes the usage of a library to a single file. In this way, it doesn’t matter how hard it is to use the external library, since we never really have to deal with it other than in the single class providing access to its functionality.
For example, the library mentioned above provides access to several shape classes, but all we need is a single class. In that case, the basic application of this technique is to create a class that provides access to all the the functionality required, while hiding everything else from the rest of the code base.
For instance, we could create a class called MySquareShape that is responsible for accessing the necessary methods from the shape library. At the same time, the class provides only the methods that the application is really using.
In the C++ world, this translates into a single translation unit that includes the original library header file. For example, if the header is called “shapelib.h”, no other file in the project will include it other than the one that provides the interface to ShapeLib.
The method described above can be used with any library. However, it makes little sense to create wrappers for common libraries that are used all the time, such as classes in the standard library. It takes some judgement to determine when it makes sense to use such a strategy or not.
The main advantage is that the code becomes more generic, in the sense that it doesn’t depend directly on a particular library to be available. It is always possible to redefine the wrapper class to use a different implementation or an alternate library if necessary.
A well known example of this method in use is provided by the Qt library. The designers of that library managed to reduce the dependency on a particular GUI library to a couple of files that could be replaced in different operating systems. The result is that it is relatively easy to port Qt applications to different windowing systems.
A big mistake that many programmers do is to create code that depends directly on libraries that are system-dependent. As a general rule, we should avoid creating code that may quickly become obsolete.
Among the techniques available for improved portability, the use of wrapper classes (or functions) has been used with success in several projects. Even if you decide to use some other method, by isolating code dependencies we can make help code base to respond more naturally to changes in the programming environment and reduce the unavoidable maintenance costs inherent to every programming project.