Flat Packed Functions

Flat Packed Functions

Okay, so this is more of a design thought process than anything specific (and not an actual list of functions provided by IKEA or MFI for the older generation) but I thought I’d share something with you I come across quite a lot when reviewing code, pull requests and looking at others work, and that’s what I’ve affectionately called the Flat Pack Function (or lack of).

To give you a bit of background, I have OCD (Obsessive Compulsive Disorder). Nothing too major, but just some of the lesser afflictions such as labels facing the same way on tins in the cupboard, hate having dirty hands, I can’t go running in a line and back again it has to be a loop, cutlery has to be “knives, forks and spoons” left to right and not “forks, spoons and knives” etc – just some quirky little things which I’m sure drive my fiancee nuts, but she loves me so puts up with them (thanks honey). Anyway, one of the main things she ribs me about is that if I build something which is flat packed (hence the title) then I like to have everything laid out in front of me to make sure it’s all there and accessible before I start building the item – and that’s the principle I adopt when writing functions in PHP too.

Let’s take my analogy a bit further… to me, there’s absolutely no point in building a chair, putting the legs on, the seat, the back only to find out you’re missing an armrest. That’s a pointless waste of time, and functions in PHP should be exactly the same. I’ve always built functions with the principle that you should die or quit at the earliest possibility opportunity for optimisation. To me that just makes sense, and there may be others reading this saying “well, of course you should” but not everyone thinks the same.

Let’s have an example….

Following on from my analogy, let’s build a chair… (nb: this is a silly example, but you get my drift):

public function buildSplendidChair($seat, $legs, $back, $arms, $tools)
    {
        // Start with building the base
        if (!empty($seat) && !empty($back)) {
            $base = $seat + $legs;
        } else {
            throw new FurnitureException('no base bits');
        }

        // Add the back to the seat:
        if (!empty($back) && !empty($base)) {
            $chair = $base + $back;
        } else {
            throw new FurnitureException('no back');
        }

        //Complete the chair:
        if (!empty($arms) && !empty($chair)) {
            $looseChair = $chair + $arms;
        } else {
            throw new FurnitureException('no arms');
        }

        // Check if we have the tools we need to do the job:
        if (!empty($tools) && !empty($looseChair)) {
            $splendidChair = $looseChair + $this->tightenChair($tools);
        } else {
            throw new FurnitureException('no tools');
        }

        if (!empty($splendidChair)) {
            return $splendidChair;
        } else {
            throw new FurnitureException('errr, no chair!');
        }
    }

Now that’s a perfectly acceptable way of writing a function, you’re checking that you have the parts you need as you need them and if not then you’re doing something about it, but does it really make sense to put together the whole chair before you realise you have no tools? This is a fairly simple example, but in the real world the first few elements of that function could be making DB calls, aggregating values, performing expensive calculations etc, all of which would be pointless when it failed at the end. It’s much better to check for everything you need up front, and exit at the first available opportunity – that way you know that as you progress through the function, you have everything you need already to perform the whole operation – so no further checks are necessary…

public function buildSplendidChair($seat, $legs, $back, $arms, $tools)
    {
        // Make sure we have everything we need:
        if (empty($seat) || empty($legs) || empty($back) || empty($arms) || empty($tools)) {
            throw new FurnitureException('some parts are missing, sorry');
        }

        // We know at this point we have everything we need to build a chair:
        // Start with building the base
        $base = $seat + $legs;

        // Add the back to the seat:
        $chair = $base + $back;

        //Complete the chair:
        $looseChair = $chair + $arms;

        // Tighten with the tools we know we have:
        $splendidChair = $looseChair + $this->tightenChair($tools);

        return $splendidChair;
    }

We have given the application the chance to exit / return / fail at the earliest possible opportunity and that’s what we should be thinking about when writing functions, and also parts of functions. Always try to cater for what the function should do if it fails, rather than focusing on what top do next when it succeeds…

Another almost real world example:

public function myInefficientFunction()
    {
        // Get some stuff from a DB and perform calculations on it:
        $firstData = $this->getDataAndCalculate();

        // Get the next lot we need:
        $secondData = $this->doHugeCalculationOnSomeMoreData();

        // Check if we have our data and finish:
        if (!empty($firstData) && !empty($secondData)) {
            $bigData = $firstData * $secondData;
            return $bigData;
        }
        // Return false if we're here as something went wrong
        return false;
    }

As you can see above, if the first function returned no data, we have still performed our huge second function which we can’t use in the third part of our function – so that’s simply a waste. Much better to be looking at something like this, and GTFO as early as we can:

public function myEfficientFunction()
    {
        // Get some stuff from a DB and perform calculations on it:
        $firstData = $this->getDataAndCalculate();

        if (empty($firstData)) {
            // GTFO:
            return 'no first data, sorry';
        }

        // Get the next lot we need:
        $secondData = $this->doHugeCalculationOnSomeMoreData();

        // Same here - see if we have data first before we progress:
        if (empty($secondData)) {
            // GTFO:
            return 'no second data, sorry';
        }

        // No need to check now at this stage:
        $bigData = $firstData * $secondData;

        return $bigData;
    }

 

I guess in summary:

  1. Make sure you have everything you need to run the function before you start going into it
  2. at each element in the function (if you’re calling other functions or making DB queries for example) think about how to handle what happens if it fails before you look at what to do with the next step.
  3. Give your application the chance to fail, exit or quit at the earliest possible opportunity (along with all your logging etc)

Well, hope that makes sense and hope it helps when designing your functions… turned out to be a bit longer than I thought lol

Thanks.

 

edit: updated failures to alleviate someone else’s OCD 😉

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s