TransitivityOfIncompatibleChange
From APIDesign
TransitivityOfIncompatibleChange happens when reusing APIs provided by others as described in Chapter 10. However the whole concept is only meaningful when using semantic versioning - in case of general RangeDependencies, it looses its meaning.
Transitivity in Semantic World
Imagine you have a module M1.0 which re-exports APIs provided by module N1.0. The re-exported APIs of module N become part of the API of module M as they may be visible in M's own class signatures as parameter or return types, or super types.
Let's now continue by envisioning that the author of module N decides to release new, incompatible version. According to rules of semantic versioning the version should have different major version - e.g. let it be called N2.0.
Often, the producer of module M would like to run on the most recent version of module N. As such it may consider creating new version of own module which would depend on N2.0. Can this version be called M1.1? Well, if we allow it to be called M1.1, then:
- we run into NP-Complete problems as the LibraryReExportIsNPComplete proof shows
- we go against the motto of semantic versioning
Some parts of interface of N are re-exported and thus basically part of the public signatures of M. As there was an incompatible change in these interfaces, semantic versioning urges us to increment the major version of module M. The incompatible change in N transitively influences M. Its author should release M2.0!
When Things Go Relative
The whole thing with transitivity gets quite complicated with use of RangeDependencies. There is no pre-defined meaning of various ranges and thus a general (and simple) advice of semantic versioning is not easily applicable to describe stability of APIs. It took me a while to grasp this difference and analyse it, but at the end it is not bad to give more power to end users:
How one specifies how stable just published API is? The semantic versioning suggests to treat all releases up to changed major version (e.g. up to 2.0.0) as being BinaryCompatible. The NetBeans API Stability proposal allows each API to be attributed as stable, devel or private. The netbeans:NbmPackageStability goes even further and suggests to use different ranges for different stability of the API. Is this the right approach?
In ideal, rationalistic world, it is. Under the assumption that API designers are rationalistic and understand the versioning issues and the importance of proper dependency management, it is fine to expect that they will do everything to follow principles of semantic versioning (or netbeans:NbmPackageStability guidelines) and either keep BackwardCompatibility or bump the major version. In rationalistic world, it is enough to believe our application will sort of work and at least link and the rest will be achieved by the good work of the other, rationalistic people.
The trouble is that we live in clueless world and most of the people (including API designers) don't have time to fully evaluate negative influence of their changes. When producers of up-stream library make a mistake, there should be a way for the consumers of such API to recover and shield themselves. From this thought there is just a small step to the idea of giving the users of an API right to classify stability of the API they use. Here the range dependencies come into the play.
Should there be a repository of modules and their dependencies including module M in versions 1.1,1.2,1.5,2.0,2.5,3.0. If majority of other modules having a dependency on M uses range [1.1,2.0) (e.g. the classical range for semantic versioning), then we can deduce that the owner of module M is doing good job keeping BackwardCompatibility. If many dependencies on M use for example [1.1,1.5), then we can deduce that something unnatural (from the point of semantic versioning happened in version 1.5) and that the owner of the module M underestimated the impact of changes in version 1.5. On the other hand, if many dependencies on M use range like [1.1,3.0) then the incompatible change was probably not as incompatible as it might have seen.
The beauty of analysing the overall dependencies on a module is that it clearly expresses the statistical probability of BackwardCompatibility as seen by all users. It also allows the users to be sure application really runs as expected if the dependency is satisfied. Of course, the author of such API should set the expectations up by using stability category, but the ultimate decision remains in the hands of the API users.
Real vs. Ideal
While the TransitivityOfIncompatibleChange is a clearly understandable feature of semantic versioning, in real, clueless world we can get further by giving the ultimate power of deciding whether something is or is not compatible to users of API modules.
Expressing dependencies as ranges is an ideal tool to achive that. The meaning of transitivity then gets more fuzzy and less clear, but that may be a natural consequence of the complexity of real world module dependencies. Rather than impossing idealistic rules on API designers, it is more beneficial to give users of those APIs a right to vote (e.g. to select narrow or wide range).
Semantic versioning should still remain the basic (and ideal) approach for classification of incompatiblities. However, when there is an error in the StabilityOfAPI classification, there are ranges to help everyone to recover from it.