ContinuousIntegration
From APIDesign
There is nothing wrong on having your own ContinuousIntegration server like Jenkins or Hudson set up. Having it contributes to quality of your project. As Chapter 9 of TheAPIBook explains, tests are the sole of good frameworks and the most important part of good projects.
Contents |
U Can't Test This!
On the other hand, people get so used to this comfortable safety net provided by ContinuousIntegration systems that they tend to forget there is also something behind the horizon. The quality departments are well aware of that (regardless of the amount of CodeCoverage) there can always be errors visible to end users.
The situation is even more complicated when it comes to API Design. A common advice to get better quality is to increase your CodeCoverage or add integration tests to verify the real workflow. Yet the biggest problem when designing an API of a framework is: you can't run the users code! Either you don't have it, or you don't even know about it. When your framework gets really successful, then running all projects that use your API wouldn't even scale.
Control the Observables
As such one has to do something else. Rather than testing all the depending code, one has to make sure the externally observable aspects of your framework as stable - e.g. they don't share like the amoeba in the Amoeba Model. What does that mean?
- Don't rename exported symbols
- Don't change method signatures
- Don't change the exposed behavior
Item #1 is important for linkage - e. g. to make sure that various modules (JARs or .so or .js files) built on top of your API find the symbol they expect. Proper linkage is a per-requisite for any other communication. If the users of your API cannot connect to it, the rest is completely useless.
Keeping unchanged method signatures is important in compiled languages. This is often important for linkage as well. In case of Java one can use Sigtest to prevent accidental changes of number or types of parameters. In dynamic languages like JavaScript this check has to be done dynamically and thus it shifts the check closer to the Runtime_Aspects_of_APIs.
It is easy to say to keep existing behavior, but what if it buggy? Well, sometimes even buggy (from one perspective) behavior can be useful (see the Arithmetica example). What can you do when you really need to change it? In such case support AlternativeBehaviors! There are few ways to do it applicable to any language (for example XML does that by encoding its own version in the document header), but the best is when the language or underlaying runtime container provides some support to it.
What's the Goal?
The goal is to make sure that people can write applications, but more importantly libraries and plugins that work with multiple versions of your API. Nobody wants to publish update as soon as you provide new version of the API. Once we can achieve that we can say that your API is keeping BackwardCompatibility.
NetBeans IDE supported plugins since 2000 and the most important feature people asked for what ability to write a plugin that would work with last few versions of NetBeans simultaneously. Their core business wasn't to write plugins, so they wanted to minimize the time spend doing that. The last thing they wanted was to watch us to release new version of NetBeans and at that time publish an update of their plugin suitable to run in that version.
The same situation applies to transitive dependencies between libraries. If somebody builds their API on top of yours then there has to be a configuration for these two APIs to work together. By doing non-BackwardCompatible changes one complicates that a lot (remember LibraryReExportIsNPComplete) which can be justified by everyone who tried to assemble five pieces into a single working system in an environment which pays little attention to BackwardCompatibility.
Conclusion
Having ContinuousIntegration is a must for any project (including ones that provide an API). However in contrast to building final applications (thus let's call it another paradox of APIDesign) one cannot fit the whole code into the ContinuousIntegration check. As such one needs to apply also other techniques when judging correctness of a change. Keeping BackwardCompatibility is a useful initial step that always helps.