This title might seem a bit like a contradiction to my last post! Well, you really shouldn’t break your API, if you can help it. But sometimes, maintaining backwards compatibility for any area of your application can lead to a point of diminishing returns. This applies to everything about a program, not just its API.
A great example of the backwards-compatibility problem is Perl. If you read the perl5-porters summaries with any regularity, or if you’re familiar with the history of the Perl internals in general, you’ll have some idea of what I mean.
Perl is full of support for strange syntaxes that really, nobody should be using anymore. For example, in Perl, you’re supposed to call methods on an object like
$object->method(). But there’s also a syntax called the “indirect object syntax” where you can do
method $object. Not
method($object)–only the case without the parenthesis is the indirect object syntax.
Really, nobody should be using that syntax, and it’s not that hard to fix applications to call their methods the right way. And yet that syntax is maintained and supported in the Perl binary to keep backwards compatibility.
Perl is full of things like this that block forward progress because of historical problems.
Now obviously, this is a balancing act. When there are a huge number of people using something, and it would be really difficult for them to change, you pretty much have to maintain backwards compatibility. But if maintaining that backwards-compatibility is really stopping forward progress, you need to warn people that the “old cruft” is going away and ditch it. The alternative is infinite backwards-compatibility and no forward progress, which means total death for your product.
This also gives one good reason why you shouldn’t just add features willy-nilly to your program. One day you might have to support backwards-compatibility for that feature that you added “because it was easy to add even though it’s not very useful.” This is an important thing to think about when adding new features–are you going to have to support that feature forever, now that it’s in your system? The answer is: you probably are.
If you’ve never maintained a large system that’s used by lots of people, you might not have a good idea of: (a) How many people can be screwed if you break backwards-compatibility, and (b) How much you can screw yourself over by having to maintain backwards-compatibility. The ideal solution there is: just don’t add features if you don’t want to support them for many, many future versions to come. Sometimes it takes a lot of programming experience to make that sort of decision effectively, but you can also just look at the feature and think, “Is it really so useful that I want to spend at least 10 hours on it in the next three or four years?” That’s a good estimate of how much effort you’re going to put into backwards-compatibility, QA, and everything else for even the smallest feature.
Once you’ve got a feature, then maintaining backwards-compatibility is generally the thing to do. Modern Bugzilla can still upgrade from 2.8, released in 1999. But it can do that because we wrote the upgrader in such a way that old upgrader code doesn’t require any maintenance–that is, we get that backwards-compatibility for free. We only have to add new code as time goes on for new versions of Bugzilla, we almost never have to change old code. Free backwards-compatibility like that should always be maintained.
The only time you should seriously consider ditching backwards-compatibility is when keeping it is preventing you from adding obviously useful and important new features. But when that’s the case, you’ve really got to ditch it.
Totally agree with you on that point. It’s normal to see that kind of mentality on the Perl community, and that way of thinking is affecting the language more that it’s helping it.
I think that if you mark something as deprecated, you have to tell when that deprecated functionality is going to disappear. Maybe that’s the solution to the problem.
Once again, very well said!
Thanks! Yeah, I agree about the deprecated thing. 🙂
How do such features “block forward progress” (as opposed to, say, slow it down a bit because the code is more complex)?
It’s most apparent in languages. For example, Larry decided to have the following code work in Perl:
$var =~ /regex/
Because of that feature, various methods of parsing Perl are difficult to impossible. You can’t know what “/” means until runtime, in some circumstances (sometimes it means division). The actual times when that happens are really small, but Perl will forever have complex code to distinguish the meaning of “/” because of that feature. That’s one where you really can’t break backwards-compatibility.
Also, you can slow forward progress to the point of a crawl, which might as well be blocking it. For example, in Perl 5, we still don’t have formal subroutine arguments after all these years, even though there are many people working on the codebase.