Dominik Honnef

Breaking backwards compatibility in good conscience

Last modified:

A couple of days ago I released Cinch 2.0 – version 2.0 of the most-used IRC library written in Ruby. The purpose of this article, however, is not to talk about all the new features and improvements in Cinch (check the changelog if you’re curious) but why it breaks backwards compatibility and how that’s not a bad thing.

The Ruby community basically has two main camps: Those who think that breaking backwards compatibility is no problem, as long as your version numbers signalise it (cf. Semantic Versioning), and those who think that breaking backwards compatibility is proof of bad API design and should be avoided at all costs.

Loren Segal, creator of YARD, belongs to the latter camp. Notably, the current version of YARD (0.7.5 as of writing) is still compatible with the earliest releases. According to him, he still uses the same plugins and templates that he wrote for version 0.2.x, released roughly 4 years ago.

He says that this is possible because he put a lot of thought into the API, planning before writing code. While I agree that maintaining backwards compatibility is a noble cause, and that planning can help, I don’t think that everything can be planned perfectly. I think that Loren accomplished a great feat by planning the API in such a solid way that it worked out for him, but it might not for others, and it might not be a sign of bad will.

I myself put quite some thought into the API of Cinch before releasing version 1.0 – I tried to consider as many scenarios as possible, planning ahead and avoiding future conflicts. And I failed. Cinch was my first IRC library with focus on writing multithreaded bots, and while I tried to get everything right, I overlooked many little details. Details that only became apparent when users came to me, pointing out problems they were having.

While I could’ve maintained some parts of the flawed API in version 2.0, other parts had to be removed completely because they were plain broken and unfixable. And I didn’t want to maintain the flawed parts, either. Why? Because they added unnecessary complexity to the code base of Cinch, and because on the long run it won’t matter. Cinch is now 1.5 years old and it will be around for much longer. In a couple of years, nobody will care that the API changed. In fact it will probably not take more than a couple of months.

My point is that it’s a good idea to try and avoid breaking the API, but only as long as it makes sense and as long as it doesn’t have a negative effect on your code, or the rest of the API, in the long run. And if you’ve made a mistake while designing the API, it is better to admit the mistake and fix it than to live with it for the rest of your project’s life.

Everyone who has taken a look at the code base of YARD knows that it indeed has been designed very well, but I think this is an exceptional case and not the norm and it shouldn’t be assumed that everyone can achieve the same. Maybe it was even a bit of luck that it worked out so well.

Of course this isn’t a free ticket for breaking APIs every second release, either. If you have to constantly break backwards compatibility, maybe you should put at least some thought into design first.