AskPerl: The Back-Compat Dilemma
Gentle reader, I need your advice.
Some years ago I created the Net::Appliance::Session Perl module. It allows the user to automate interactions with CLI-based networked devices (think routers, switches) - a sort of Expect but with some helpful domain knowledge. The module has turned out to be quite popular (which is important to my story), in use by many organisations.
There is a shopping-list of problems with the code, though, which makes both bug-fixing and feature development really hard. I've been planning a rewrite for over a year, and finally completed most of that in the past few days.
The new version is completely different internally, but that shouldn't matter much. The problem comes in that I've engineered a new "phrasebook" system for the scripted CLI interactions, and also the programmers' API has changed. [Edit: I should add that lots of long-asked-for features are included, so the rewrite did bring benefits for the user.]
Now, I guess that if I release this as Net::Appliance::Session with a major version bump it could cause havoc some time down the line when an innocent upgrades their server and in comes the new package and some maintenance script fails. Perl doesn't make it easy to deal with this (nor do most operating systems, either, of course).
However I'm loathe to change the module's namespace. I like its name, and it feels like chasing my tail if I just rename a module to indicate an incompatible API change.
Alternatively I could write a BackCompat role, but for starters that's a lot of work (the old API was huge) and I worry that things like error handling simply work too differently in the new version to massage. Also, should that role be enabled by default or via a flag at use-time?
I'm not a professional programmer, nor a key part of some large projects like Catalyst, Moose, DBIx::Class which I'm sure have had to deal with such issues in the past. I don't really know the best course of action, and I also don't have much communication at all with my user base (using this module is a sysadmin-practice "smell" so many don't own up to it).
I need your help: please have a think and let me know in comments what you might do in this situation. Many thanks!
Hi Oliver
I fear there is no easy solution to this dilemma. I’d predict some will say bump the version #, and some will say call it Net::Appliance::Manager (or somesuch), and some will not comment.
I.e. The decision is yours. If you like the name keep it. A major version bump is a commonly-accepted warning that the API has or may have changed, and if that’s the best you can do, do that.
Cheers
You can also force a prompt upon users installing the new version to view the upgrade warning and explicitly accept it. I have seen that done by some modules in the past. Also, you might get more detailed advice if you give a few examples of how the API has changed. I looked at the new branches on your github repository, but didn’t see any Pod.
I think the first thing you have to do is to understand on the behavior of your users.
Some users update regularly, some update once in a while, some check the changes log, while others check a website, a mailing list or are in contact with the author.
Secondly, you need to decide how versioning happens in your code. Are you using the basic x.yy where “x” is a major version and “y” are iterations of it? are you using x.y.z where “x” is a massive change, “y” is a stable release series and “z” are iterations? In Dancer, we use x.yzzz, “x” being the hugely major version which will only change in a very late future, “y” being the release series and “z” being iterations.
Then you decide on your deprecation policy. Dancer now has x.(even number) as a stable release and x.(odd number) as a development release. Same as Perl. How often do you deprecate?
Our deprecation policy is: deprecate, as a warning, in development, which then ripens to stable. Then, the next development version removes it and then it doesn’t appear in the stable release again. You can remove every two versions, if you like.
You can also think of how you’re alerting your users of the change (which is why it’s important to know your users). If you have a mailing list, you post about the issue, you can put it in the changes file, if you think they read it. If there’s a website, write it there. If it’s on CPAN, you can mention it in the POD. You can put it on installations. You can add a warning to start up, which DBIx::Class were very successful with. As soon as someone “uses” the old syntax (in the deprecating version(s)), you can warn them (which hopefully they see in output or logs) about a deprecation of a feature(s).
Hope it helps, and good luck! :)
I read the current documentation on CPAN, and it sounds like the old version supports user-provided phrasebooks. Does the new code support old phrasebooks? It probably needs to, and if it did then the old default phrasebook could be included as an option. You could even leave it as default, and require a method call to switch to the new one.
Here’s an option that my old boss used to call GFD (Gross, Foul, and Disgusting): For the next release, add an import option for :newapi and override the import method. The import method looks for the option and then does a bunch of namespace insertions.
If the newapi option is not requuested, you’ll copy subroutine references from Net::Appliance::Session::OldAPI into Net::Appliance::Session. The OldAPI module would just be a copy of the current Session module. You can copy references like this:
*Net::Appliance::Session::connect = *Net::Appliance::Session::OldAPI::connect;
You can also loop over the keys in %Net::Appliance::Session::OldAPI, which will be shorter and get you any package globals you might be using too. (You’ll probably need to test each value with ref to see what it is before you copy it.)
If the newapi option is requested, you’ll do the same thing but copy the new code from Net::Appliance::Session::NewAPI instead.
You’ll need to create OldAPI and NewAPI for your other modules too, and copy them the same way.
What this will do is make your top-level namespace behave like the old code or the new code based on a single option provided when the caller uses N::A::S, with the default being old behavior. You can also add a warn statement to N::A::S::OldAPI::connect that tells the user that the old api is deprecated. Then next year you can remove this hack and just put the new code where it belongs.
One advantage of renaming is that its easy to have both the old and new version installed simultaneously. That makes migration much easier.
One advantage of renaming is that its easy to have both the old and new version installed simultaneously. That makes migration much easier.
Many thanks, Ron (and everyone) - and it looks like you’re right I got a variety of replies :-)
But it’s interesting to see the reasons people have for each strategy. I’ve not made my mind up yet…
There are problems either way, but I personally find it more confusing to move to a different name than to recognize huge bump in version numbers.
If you are thinking of going from 0.7.9 to 0.8.4 that will still be confusing. Go big or go home? Say 0.7.9 to 2.0.0 Since you have added requested changes I would expect the name to stay the same.
I’ve come to expect big changes when I see that kind of jump and expect compat issues too. When there is a name change I instantly think ‘oh shit! will this even work the same?’ or ‘noooo, they stopped supporting it’ where they is obviously you in this case.
If you added huge feature differences like ‘now works with Commodore64 and VAX/PDP-11 (pre ‘82 releases only)’ or ‘added support for Zune(beta) and MS Excel 2012’ then a name change is appropriate. Anything that essentially makes it a different application requires a name change.
Thanks Mr Z, that’s a good summary of the situation and the examples have helped. I think when I complete the rewrite I’ll look at the programmers’ API and ask myself your question: “is this the same application?”