Bits of code: abusing the switch statement
edited January 07 2015

When working on code that's error-prone and needs to complete a series of steps, where each step depends on the previous one working successfully, it's easy to end up with something like:

$this->_connection = socket_create(AF_INET, SOCK_STREAM, SOL_TCP);
if ( $this->_connection !== false )
{
    if ( $this->_connection < 1 )
    {
        $this->_connection = false;
    }
    else
    {
        //  Now have a working socket on $this->_connection; further error handling
        //  is done a little bit differently.
        socket_set_nonblock($this->_connection);    //  Required for socket_bind(), default state for all sockets anyway.
        //  Handle any socket timeout values that may have been set by the application.
        $this->set_timeout($this->get_timeout());
        if ( socket_bind($this->_connection, $address, $port) )
        {
            if ( socket_listen($this->_connection, SOMAXCONN) )
            {
                $this->_status = 1;
                $this->_inbound = array();
            }
            else
            {
                $this->_error(-1, 'Error listening on socket for ' . $address . ':' . $port, __SOCKET_ERROR__);
            }
        }
        else
        {
            $this->_error(-1, 'Failed to bind socket for ' . $address . ':' . $port, __SOCKET_ERROR__);
        }
        if ( $this->_status == -1 )
        {
            socket_close($this->_connection);
        }
    }
}
    

Yuck.

The code block in this example was already nested a few if / then statements deep. This is standard practice, but it's ugly so I hate it.

Programmers have a variety of approaches for dealing with this when they feel like doing so. A common modern approach is to add exception handling, try statements, and maybe a buffet of function calls or classes or factories or whatever the hip thing is today.

I prefer to keep line counts low, software tight and fast and efficient, and code readable. And I really get more pleasure than I should from abusing language constructs. So I've used this for years:

switch ($step = 1)
{
    case 1:
        $this->_connection = socket_create(AF_INET, SOCK_STREAM, SOL_TCP);
        if ( $this->_connection === false || $this->_connection < 1 )
        {
            $this->_error($errno = socket_last_error(), socket_strerror($errno), __SOCKET_ERROR__);
            $this->_connection = false;
            break;
        }
    case 2:
        //  socket_set_nonblock() is required for socket_bind() and is the default state for all sockets anyway.
        socket_set_nonblock($this->_connection);
        //  The application may have set a timeout value on the socket before calling listen();
        //  the easy way to handle this is to call set_timeout with the socket's current timeout value.
        $this->set_timeout($this->get_timeout());
    case 3:
        if ( ! socket_bind($this->_connection, $address, $port) )
        {
            $this->_error($errno = socket_last_error(), socket_strerror($errno), __SOCKET_ERROR__);
            socket_close($this->_connection);
            $this->_connection = false;
            break;
        }
    case 4:
        if ( ! socket_listen($this->_connection, SOMAXCONN) )
        {
            $this->_error($errno = socket_last_error(), socket_strerror($errno), __SOCKET_ERROR__);
            socket_close($this->_connection);
            $this->_connection = false;
            break;
        }
    case 5:
        $this->_status = __SOCKET_LISTENING__;
        $this->_inbound = array();
        break;
}
    

This abuses the fall-thru property of the switch statement in a really satisfying way. (If a case label in a switch doesn't end with a break statement, then execution continues into the next case label. This is one of the keys to the infamous Duff's Device.) The switch statement starts out with a throwaway variable that's only there to tell future me what I'm doing here; execution starts at the top, and then each step falls through naturally to the next, unless one of the steps encounters an error. Then, the break statement at the end of the error-handling block causes execution to immediately jump out of the switch. This gets you all the benefits of wrapping your sanity-checking code in a separate function with early returns, without the overhead of having an additional function just to sanity-check some inputs.

With the traditional nested if / then / else approach, the "end" of the code — the point at which it has accomplished whatever it was supposed to successfully accomplish — is in the center of an inscrutably hairy mess of function calls and error handling. Using switch is so much more pleasant in part because the last step is at the bottom of the block of code, right where you'd like it to be, and not playing hide-and-go-seek in the middle.

The case labels aren't really necessary, but like the $step variable, they're there to save me a moment sometime in the future.

If you have a block of code that needs to retry execution on the failure of a particular step, this approach works very nicely. Just wrap it in a loop, and you're good to go. Better yet, switch naturally lends itself to better error handling with a lot less clutter, and the line count is about the same as the traditional nested if / then / else approach.

It seems obvious, but it's not common — I don't think I've seen this in anybody else's code so far.