Rant #2: Backward-compatibility and API-breaking changes
So, recently, I’ve switched some of the codes I maintained to use this great new version of a common utility (shall not bore you with the details). The new version has tonnes of great, new stuffs. It was exciting. I managed to clean up my config files a lot just by using the new version.
Of course, the new version specifically promised that while it is breaking the API, all of its functionality that already exist in the previous version already exists in the new one and that user (i.e. me) can use an adapter classes to convert between the two versions.
All is well, until today. Today, I had to extend part of my code to work together with an older code that uses the previous API. Guess what? While the old build rules has been updated so that it can depend on the new build rule for the new version, the reverse is not true! Hold on! Why!? Yea, that surprised me a lot! So I had two options, extracting the part I need and update it to use the new API and make the two systems depend on this or update the older code to use the new version.
I chose the latter because it wasn’t a huge amount of work and the design is cleaner if I kept with the old design (breaking up that part of the code seems rather hackish to me). After half an hour of updating the old code, I got both system working together well (I wrote some script to automate some of the task, if you’re wondering why it takes only half an hour). The code links well, though since there are many other codes that depend on the other code that I just modified, I had to run the API in a compatibility mode, but that’s no big deal.
Only minutes later, when I started writing unit tests (before I even started writing my production code), I realized that, umm, once the new API works on compatibility mode, you have to use the adapter class to get the new functionality I need. There is no automatic detection, everything is manual! No convenient methods to convert one to another easily (I mean it’s C++, they could easily write operator() to convert from one to the other pretty darn easily!). Crap! I realized that I had to modify the codes in far too many places to make this worth it. So I just decided to ditch the feature I need.
There are so many lessons I learned from just today’s experience. If I were to write the new API, I would have provide automatic conversion between one to another, I would have backported some of the most important new features (mind you, these new features are very doable with the old version), I would try my best not to make any API-breaking changes. You know, having the old and new data structures extend the same base class would have been much better.
Lastly, seriously, I know there were some serious problems with the old code, but I’d rather have those codes deprecated slowly over time while the new code is being phased in. During this transition, you better don’t just introduce new features on just the new code (or worse, just the old code). You need to support both of them, or otherwise, help people who use the old version migrate. This is no open source code, it’s a privately-owned code. While the code base is arguably huge, there have been evidence of successful deprecation in the past, why can’t this one do that?