Libraries are useful. Using a library in order to accelerate your development is a great thing. It is fantastic if someone has implemented a generic solution that means that my team does not need to learn and implement the significant details of that solution. The business goal is usually not for a team to learn the deep details of a solution, but rather to achieve the goal. Using a solution – for example an Excel library to export some data to the Excel format – instead of writing it ourselves – meets the business goal faster (hopefully).
But is it always a better idea to use a library than to write it yourself? And how do you choose between one library and another?
Complex vs. Trivial
It feels reasonable to include a library to do something complex that would take a significant amount of time and effort to achieve.
It feels unreasonable to include a library to do something trivial that can quickly and simply be written yourself.
Adding a library introduces a new external dependency to the code base that is currently not there. It will probably introduce the dependency for much of the rest of the lifetime of this feature – which could be a significant length of time. Dependencies come with their own dependencies and those dependencies with theirs, and so on. External dependencies can cause dependency conflicts. They can cause pain when upgrading. They can no longer be supported or work in the future. For example, some Gems (Ruby library dependencies) are no longer supported when moving to Rails 3 from Rails 2 – which causes significant pain in those migrations as the dependencies need replacing – either with code written by the team, or a new dependency. Either way, lots of testing will occur.
Use a high percentage of the library
It feels unreasonable to include a large library when you only use a very small percentage of it.
Including a library that has many more reasons to change than you are using it for will cause the library to be updated and upgraded for many reasons that are unrelated to the reason why the library is in use by your code. This could cause future pain with upgrade churn with no value. The library may even be obsoleted for unrelated reasons to why you are using it.
A software decision is made at a moment in time
Using a library is a software decision made at a moment in time. At that moment the choice made might have been the correct solution to the problem. However at any moment in time in the future it may no longer be the correct solution. That is the reality of supporting changing business requirements and needs. A question that should always be asked is:
What if this decision is the wrong one? How would we refactor out of this decision?
We all know that in software, we learn more as we go. The key question with any software that we develop should be how do we refactor out of this decision if we learn more and the decision is no longer the correct one for the business needs now.
Can you get out of the decision to use a given library? How would the code look to protect against the need to stop using the library and easily replace it with another library or your own code? What would it look like if you tried to do that for a framework choice?
Isolate the External Dependency – Know Thy Seam
Micheal Feathers talks about seams in his book Working Effectively with Legacy Code (http://www.amazon.com/Working-Effectively-Legacy-Michael-Feathers/dp/0131177052). Seams are a generically useful pattern. The seam is the place that you can test around. Which also means you can replace what is behind the seam with any other code that supports the tests and everything should continue to work.
Single use dependency
If this is the first (and probably only (as known right now)) time that we will use this dependency, then wrap the usage of it in tests inside the class that uses it.
For instance if you’re using an Excel exporter library and this is the only place that will ever use it, ensure that it only is used / included in this component / class and not auto-loaded throughout the entire code base. Then ensure that the class that uses it is covered for tests for the scenarios that use the library. This class is then the seam that allows you to keep the library under control.
To continue with the Excel example – make sure you load Excel files in your tests and hence test your code’s usage of the library’s interactions and expectations of how Excel files will be represented to your code. If you do not, and the library changes, you will not know if the library has broken your code.
Multi use dependency
If this is a generic piece of code that will be used in many places, wrap it in your own wrapper, ensure that it only is used / included in the wrapper and not auto-loaded throughout the entire code base. Test the wrapper and its interactions. The wrapper is now the seam that allows you to keep the library under control and easily replaceable.
For the Excel library example, make sure representative Excel files that you expect to interact with are covered with tests.
Don’t be naïve about the altruistic nature of library writers
The thing with frameworks and libraries is that they are written by people who are (most likely) experiencing different forces to what you are experiencing in your code base. Their decisions are dictated by the forces they are experiencing, not the forces the business you are writing code for is experiencing.
Things change over time. Decisions that are made now may change in the future. Your needs of a library may change. The direction and intent of the library may change. You may in fact be using some magic of the library in an unanticipated way, which may break for you in the future due to a library code change. Things change, things migrate and upgrade. It isn’t the intent of a library creator to mess you up, but you are making decisions about using their code while not being in control of their code. This may cause you (or more to the point the business who’s code it is) a bunch of pain in the future. So ensure that you remain in control when their change causes you pain.
Recommended Do Not Do Suggestions…
Do not smear a library around multiple classes. Later you may discover you can no longer use it. In Rails, many Gems provide generic ‘helpful’ solutions, like attachments that hook on to your models. But what if on migrating to a new Rails version the Gem is no longer available? If you are not in control, then a lot of rework is required to dig yourself out of the hole. I have seen this in a Rails 2 to Rails 3 migration, which resulted in significant problems with Gems that were no longer available.
Do not hook yourself so tightly to the library of choice in a lot of places that it is hard to replace. .NET comes with a reporting library which from Visual Studio 2005 to Visual Studio 2008 was completely rewritten by Microsoft and you could no longer load the old reporting components in the new VS2008. With a lot of reports and limited abstraction layer and high coupling between the .NET reporting components, a lot of code needed rewriting to migrate to the newer version. Be aware that even when you think the choice is obvious and will never change, the library writer may force you to change and prove your decision to not protect yourself to be incorrect.
The economics of the issue
Fast now always looks better than being marginally slower now for the potential faster in 2 years time. This may be a valid trade off for a company that has no money and is desperately attempting to survive. But once you’ve moved from survival mode to agility in being able to change as needed, then the trade off becomes easy. And some may argue you might want agility in survival mode just as much – or even more.
Stay in control
Keep control of your code and your dependencies. Make it easy to replace things. Any time a decision that was made is no longer a good one, refactor to replace.
And always question how you will refactor away from any given external dependency. Knowing that will ensure that your code will remain agile.