Deprecation

at 2009-09-06 in Editorial by friebe (0 comments)

Every now and then, we find out our decisions from the past weren't the most elegant ones, limit us in unnecessary ways, or don't fit into the bigger picture anymore. This is a natural thing, as we move on and gather experience, we see our solutions in a different light - and we see how a problem could have been solved in other ways. In reflective moments we start thinking "If I had to do this again today, ...". This happens in personal life but also - for programmers - with code we have written.

In these situations, we face a tough decision: What we wish to do is to start changing all the code, upgrading it to our new and larger spectrum of wisdom, and if we we trigger-happy folks, we'd probably do just that. But on the other side, we know this might cause problems in other places using the code in all kinds of ways (some of them we might not even have envisioned). We realize we need a migration path, a workaround, a plan, communication, discussions and a time frame for all of this. Citing an earlier quote that programmers are lazy, this is the stuff we don't like to do; we'd rather just implement the change and then continue to do what we're best at: writing code to solve technical problems.

The programming world has a term for this: Breaking changes. What we usually break is backwards compatibility - software written against the version before our change will break when trying to use the newer one. And what programmers hate more than conceptually designing well-worked out migration plans is to have stuff broken. Not necessarily in the moment it happens but once they want or have to recompile, install or deploy (depending on what stage this break would surface in the technologies we use).

The compromise


This shows the conflict of interest between framework developers and developers using these: On the one side, we want progress, on the other one we want stability. On the other hand, we don't want one at the cost of the other, and have therefore come up with deprecation as a makeshift. By deprecating functionality we tell other people out there we think this feature was a mistake, or insecure, or whatever caused our rethinking in the first case. Well, actually we hope people will notice, and take the measures and adjust their code accordingly. At least we can say "I told you":-) But in reality, deprecated things tend to get used anyways, now matter how desperately the programming language or tool chain around it cries, so software providers, in fear of users complaining, usually drag deprecated sourcecode around with them for years.

  • There is a quite impressive list of deprecated things in the Java programming language nowadays, and Sun also ships a guideline for deprecating.
  • The PHP project introduced a so-called "error-level" named E_DEPRECATED in PHP 5.3.0 to get rid of design mistakes from the past, including magic quotes, register globals and call-time pass by reference.
  • Bigger vendors usually guarantee to continue support for deprecated functionality for a limited span of time - Google gives one to three years, Microsoft's mainstream support phase extends over five years; smaller vendors and open-source projects usually have tighter schedules.
  • Some thoughtful framework providers even provide migration tools.

A real-life example


In the XP Framework, we also deprecate functionality every now and then. This happens - depending on the importance of the functionality being deprecated - inside patch-level, minor or major releases and is announced in the ChangeLog's "Heads up" section, which also forms the first section inside the release notes. If developers read this section carefully and investigated all their code for uses of the deprecated items, we could safely assume their removal wouldn't hurt anybody. In reality though, this still happens.

One recent example is the FtpConnection class' put() and get() methods. They were deprecated as part of RFC #0140 (see its "dependencies" section) in October 2007. With the rewrite of the FTP API earlier this year, their implementation was replaced by the following one-liner:

<?php 
raise
('lang.MethodNotImplementedException', 'Deprecated', 'FtpConnection::get');
?>
...but the methods themselves continue to exist, preventing the dreaded Fatal errors PHP gives you otherwise. These method implementations were released inside the 5.7.3-RELEASE.

At the company I work for, we use XP framework releases in our production environment instead of SVN head, and maintain an association between products and framework releases they use. When I found a commit message stating a certain product was downgraded from 5.7.3 to 5.7.2, I was astonished - why would anyone do that?! - and asked the developer what the reasoning was for this. It came out that the product was still using the old and FTP functionality that had been deprecated almost two years ago, and that the product was now spewing MethodNotImplementedExceptions and their respective stack traces into its logfile. So even with all the careful measures taken beforehand, RFCs, deprecation notices, mailing list discussions, release announcements the issue surfaced at a time that couldn't be worse: While fixing a bug using new 5.7.3 functionality, at the same time 5.7.3 "broke" the application in another place.

Discussion


Could the framework developers have been more cautious? No. Could the product's developer have been more attentive of framework changes? It's not that easy: There's so much to remember in your work every day, and that something that seemed like a harmless change (upgrading to a patch-level release) would effect exactly this part of the a product that worked seamlessly for over two years in a row seemed unlikely. In the moment the breakage occured, it was a fast find, and the developer remembered, and then found the quickes fix: Downgrading. The story has a happy end though: The sourcecode was refactored shortly after that and updated to use the new API. It did raise some questions on the general procedure of deprecating things and how we'd get people to realize this, as well as how we could provide them with workarounds for the delicate situations where one can't do without an upgrade but by upgrading will also start facing other problems.

One idea that came up would be to provide BC xars. These archive files would contain the old functionality (which would still work inside a newer release) and could be added to the class path in front of the newer backward incompatible version, thus being preferred by the classloader and keeping things intact. It would be an easy thing to track products using these, since - as mentioned before - we know which product uses which version of the framework and the developers would need to put the BC xars into the same place. The downside of this approach are that these xar files would need to be maintained by the (progress loving) framework developers and that they might give developers using the framework a false sense of security.

Extension methods


While trying to make up my mind if extension methods were a good thing to have in the XP framework or not I found that they could help us with our deprecation issues. The following situations exist:
  1. We deprecate an entire class or interface.
  2. We deprecate one or more methods inside a class.
  3. We deprecate a certain method signature.
Most of the time we face #2 and mark a method as "deprecated", that is: We discourage its usage, in the hope of being able to remove it one day. And, once again, we see the problems with doing so: Somebody might use it somewhere, and will - for various reasons - probably not see our deprecation hints, and will then end up with a fatal error in - Darwin's law applied - the situation it's the least expected. Again, we find ourselves in the "progress versus stability" dilemma.

With extension methods in place, though, we can work around this issue quite easily. Let's pick up the example from before with FtpConnection::put(), a method we deprecated in favor of a more generic, testable and flexible API.

<?php 
// The old way
$conn->put($local, '/'.$filename);

// New way
$conn->rootDir()->file($filename)->uploadFrom(new FileInputStream(new File($local)));
?>

Now if someone was still using the old way in their product (stability: "It's worked like that forever"), and the framework developers wanted to remove it (progress: "It's not flexible and untestable"), I figured extension methods could make both sides happy. We'd simply remove the method and provide an extension method reimplementing its behaviour with the new functionality. Best of all: No code changes besides adding the extension method class to uses().

Here we go:
<?php 
// Declaration
class DeprecatedFtpMethods extends Object {
static function __static() {
xp::extensions
('peer.ftp.FtpConnection', __CLASS__);
}

public
static function put(FtpConnection $self, $local, $remote) {
$dir= $self->rootDir()->getDir(dirname($remote));
$dir->file($remote)->uploadFrom(new FileInputStream(new File($local)));
return TRUE;
}
}

// Usage
uses('...', 'xp.bclayers.ftp.DeprecatedFtpMethods');

$conn->put(...); // Now calls extension method
?>

What we get is the following:
  • Framework developers can remove deprecated methods instead of dragging them around for ages.
  • Products' developers need to be explicit about using deprecated functionality and add the extension method classes to their uses() list.
  • We can easily grep for sourecode using these BC layers instead of having to perform static code analysis or add logging to old methods.
  • There is an incentive for upgrading - if not for the new features then at least for one guaranteed reason: extension methods are slower than their "real" counterparts.
  • ...but most important: No complete refactoring of old code way down in the depths of an application!



Subscribe

You can subscribe to the XP framework's news by using RSS syndication.


Categories

News
General
PHP5
Announcements
RFCs
Further reading
Examples
Editorial
EASC
Experiments
Unittests
Databases
5.8-SERIES

Related

Find related articles by a search for «Deprecation».