About Jamie on Software

Jamie on Software is the online journal of web developer and writer Jamie Rumbelow.

Jamie likes books, guitars, programming, open source and food. He writes about these things too. This is where he puts the things he writes.

Tags
Tweets
Feeds
We Love
Powered by Squarespace
Monday
Dec202010

You can deprecate *anything*

I've been reading through this very interesting list of feature requests for PHP, and one of the reasons of denial that the PHP team keep giving to some of these crucial ideas is that they "wouldn't be backward compatible". Now, this may be total conjecture, but my honest, educated opinion is that it is possible to make anything backward compatible. Aside from obscure circumstances, it's generally easy. In fact, I'd go as far as to say that deprecating code is one of the easiest things programmers can do.

Let's take a few bits of code and build a few examples to let me illustrate my point. Assume we're building some sort of CMS or other application that has a community surrounding it. This could be pretty much any open-source project, it really doesn't matter. Now, version 1 of our application has an emailing class that looks something like this:

class Email {
    public function send_email($email) {
        $this->_verify_email_format($email);
        $headers = $this->_build_headers($email);

        return mail($email['to'], $email['subject'], $email['message'], $headers);
    }

    protected function _verify_email_format($email) {
        foreach (array('to', 'subject', 'message', 'from') as $field) {
            if (!isset($email[$field]) || empty($email[$field])) {
                die("Error: the '$field' field is required");
            }
        }
    }

    protected function _build_headers($email) {
        $headers = array();

        foreach (array('to', 'subject', 'message') as $field) {
            unset($email[$field]);
        }

        foreach ($email as $key => $value) {
            $headers[] = $key . ": " . $value;
        }

        return implode("\r\n", $headers);
    }
}

Version 1 of our application uses this code absolutely fine, and all the add-ons created for our wonderful system can utilise this too. However, one of our developers comes up with a better idea for an email class. We all agree that his class has a better API, better syntax and will make for much cleaner and concise code. His class looks like this:

class Email {
    public $to = '';
    public $from = '';
    public $subject = '';
    public $message = '';
    public $cc = '';
    public $bcc = '';
    public $reply_to = '';
    public $additional_headers = array();
    public $sent = FALSE;

    public function __construct($config = array()) {
        foreach ($config as $key => $value) {
            if (isset($this->$key)) {
                $this->$key = $value;
            } else {
                $this->additional_headers[] = $key . ": " . $value;
            }
        }
    }

    public function send() {
        $this->_check_required_fields();
        $headers = $this->_build_headers();

        $this->sent = mail($this->to, $this->subject, $this->message, $headers);
    }

    protected function _check_required_fields() {
        foreach (array('to', 'subject', 'message', 'from') as $field) {
            if (!isset($this->$field) || empty($this->$field)) {
                die("Error: the '$field' field is required");
            }
        }
    }

    protected function _build_headers() {
        $headers = array();

        $headers[] = ($this->cc) ? 'Cc: ' . $this->cc : '';
        $headers[] = ($this->bcc) ? 'Bcc: ' . $this->bcc : '';
        $headers[] = ($this->reply_to) ? 'Reply-to: ' . $this->reply_to : '';

        $headers = array_merge($additional_headers, $headers);

        return implode("\r\n", $headers);
    }
}

I think we can all agree that the latter, although slightly more verbose, is a much, much nicer alternative. Now, we're all happy and preparing to go and swap out the old class with the new class, when a solitary add-on developer, browsing through the development forum, alone at night with an all-encompassing feeling of sequestration, cries out:

All my add-ons that use this class are going to break. Anybody else using this class in their development projects are going to have to rewrite big portions of their applications to get this to work. You can't just swap out the classes, they've got to be backward compatible!

Oh, maverick developer, we can't let you down! We'll have to find a better solution to this problem. A solution that allows new add-ons to use the new class and older add-ons to continue using the old class in peace, without disturbing them. This way, since we're busy, we won't have to redevelop the core of the app either, if we can make the code backward compatible, we'll be able to leave the old system in place and convert it when we have more time.

But how would you convert an API that looks like this...

$email = array(
    'to'        => 'sjobs@apple.com',
    'from'      => 'jamie@jamierumbelow.net',
    'subject'   => 'iPhone 5',
    'message'   => "Steve, I am bored with being the lead developer on the iPhone 5.",
    'reply-to'  => 'jamie@jamierumbelow.net',
    'cc'        => 'jive@apple.com'
);

$sender = new Email();

if ($sender->send_email($email)) {
    die('Email sent!');
}

...to something that looks like this...

$email              = new Email();
$email->to          = 'sjobs@apple.com';
$email->from        = 'jamie@jamierumbelow.net';
$email->subject     = 'iPhone 5';
$email->message     = "Steve, I am bored with being the lead developer on the iPhone 5.";
$email->reply_to    = 'jamie@jamierumbelow.net';
$email->cc          = 'jive@apple.com';
$email->send();

if ($email->sent) {
    die('Email sent!');
}

...while managing to retain backward compatibility? Well, it's actually incredibly easy. Simply take your new Email class and add a quick compatibility layer. We want to re-implement the send_email() to mimic the old API but use the new emailing system. It needs to take exactly the same format array and return a boolean response. This way it'll still work for the time being.

public function send_email($email) {
    foreach ($email as $key => $value) {
        if (in_array($key, array('to', 'from', 'subject', 'message', 'cc', 'bcc'))) {
            $this->$key = $value;
        } else {
            $this->additional_headers[] = $key . ": " . $value;
        }
    }

    $this->send();
    return $this->sent;
}

Hey, take a look at that. That was very easy. We now have a perfectly backward compatible API. Both examples of code above will work flawlessly with the new system. And we got that from an extra 11 lines of code. Not too shabby.

The majority of the time when you're deprecating something, you intend to replace it with something else that does the same job. This means that there is a usually a way of mapping the functionality across with only a little effort, because you've got that one-to-one situation. If you're removing something for good and don't intend on replacing it, mark it as deprecated and tell developers that you're doing so. Leave it in for a few versions, and then after a while, remove it.

The fact remains that deprecating functionality is just not that difficult, and you really shouldn't miss out on improving whatever code you've got in place because you're worried about backward compatibility. There are always ways around these sorts of problems.

PrintView Printer Friendly Version

EmailEmail Article to Friend

Reader Comments (3)

Nice article. This will help anyone who has built an API and doesn't want to piss their third-party developers off.

December 21, 2010 | Unregistered CommenterGareth

I find this to be pretty patronizing. You think that the PHP development team don't know how to do this?

That thread was not meant as a serious "what is the ongoing future for PHP" and he specifically said he didn't want to deprecate or change any existing functionality. He just wanted a nice feature to work in for Christmas that would not damage anything else.

That doesn't mean you have to go and write a blog article about how the PHP team do not understand deprecation...

December 21, 2010 | Unregistered CommenterPhil Sturgeon

Its a blog. Take a chillaxative.

The hard part of being a blogger is finding inspiration about what to write about. It is obvious that this is not so much about PHP development but about deprecating API's.

December 23, 2010 | Unregistered CommenterWill

PostPost a New Comment

Enter your information below to add a new comment.

My response is on my own website »
Author Email (optional):
Author URL (optional):
Post:
 
Some HTML allowed: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <code> <em> <i> <strike> <strong>