I currently manage a library which has a lot of public usage, and I had a question about semantic versioning. I want to refactor one fairly important part of the library which is implemented incorrectly – and has always been implemented incorrectly. But doing this would mean changes to the public API, which is a major decision.
The change I want to make revolves around how iterators are used. Currently, users have to do this:
while ($element = $iterator->next()) {
// ...
}
Which is incorrect, at least in PHP’s native Iterator interface. I want to replace with this:
while ($iterator->valid()) {
$element = $iterator->current();
// ...
$iterator->next();
}
which is analogous to:
foreach ($iterator as $element) {
// ...
}
If you look at Tom’s guide to semantic versioning, he clearly states that any changes to the public API (i.e. those which are not backward compatible), should justify a major release. So the library would jump from 1.7.3 to 2.0.0 which, for me, is a step too far. We’re only talking about one feature being fixed.
I do have plans to eventually release 2.0.0, but I thought this was when you completely rewrote the library and implemented numerous public API changes. Does the introduction of this refactoring warrant a major version release? I really can’t see how it does – I feel more comfortable releasing it as 1.8.0 or 1.7.4. Does anybody have some advice?
3
You hesitate because you don’t want to make semantic versioning, you want to make “advertisement supporting versioning”. You expect a version number “2.0” to tell the world that you have a bunch of new cool features in your library now, not that you changed the API. That’s ok (many software companies and/or developers do that). IMHO you have the following options:
- stick to semantic versioning and live with the fact that you have to change the version number to 2.0.0
- change your versioning scheme to 4 numbers. “1.1.7.3” is your version now, “1.2.0.0” the next one after changing the API, and “2.0.0.0” the first one of the “completely new 2.x product family”
- make your fix backwards compatible (so don’t change the functionality of
next
, just add thevalid
andcurrent
functions). Then you can use “1.8.0” as the next version number. If you think changing the behaviour ofnext
is really important, do it in 2.0.0.
4
Stick with Tom’s guide to semantic versioning.
Any significant change to a public API must be done at either of the two points:
- Never
- At a major release update
My vote, by the way, is for the first. But I acknowledge it’s only appropriate for trifling things.
The problem is maintaining backwards compatibility and making sure you don’t break things for the previous users of your API.
In essence, you are creating an indexing error for your users who are unaware of the change. Forcing a change like this forces all of your users to do the following:
- Code the fix to use the new approach
- Validate the fix and make sure it didn’t break anything
- Ship new releases of their product to their end users
That can potentially be a lot of effort, especially when you consider just how few projects have test cases in place in order to validate changes like this. The amount of effort compounds when you consider the number of downstream users from your users who will also need to update their installations.
For something this small, I’d let it go and not bother with it.
If it really bothers you (which apparently it does or you wouldn’t have asked) then I would do the following.
- Create the v2.0.0 branch in your code tree
- Make the first contribution to the v2.0.0 branch, which is this change
- Send out a preview
Release Notes
ahead of time broadcasting that the change is coming
And then be patient as it will take a while to accumulate other things that justify upgrading the version number to a new major release. The advanced notification (part 3) gives you time to receive feedback from end users to find out just how much of an impact that change is going to be.
An alternative solution is to add a new function that operates in the way that you want.
If you have foo()
then you would create fooCorrect()
in order to provide the fix but also fully preserve backwards compatibility. And at some point you can deprecate foo()
to let others know not to use it.
The challenge there is that you’ll find something else within fooCorrect()
which necessitates it’s update and you end up with fooCorrectedCorrect()
or some other silly nonsense.
If you really want this fixed now, this alternative approach is probably the best route. Be aware of and wary of creating lots of extra functions this way as it makes the API harder to work with. And that awareness may be enough to prevent the worst of these types of problems.
But this might be the “least bad” approach to consider for something small.
7