Tuesday, December 1, 2015

Error handling can make or break an experience

This post is a bit of an adventure in building a very small piece of a rather cool demo.
It is really an adventure in developing thorough unit tests.

Some of you have probably noticed that my blog has been kind of dead for a pretty long stretch.  Needless to say, I have been busy.

This week is HP Discover in London.  It is a really big show for HP.

There is a demo called Collaboration Cubed.  And I must say, it is pretty cool.
https://twitter.com/JordanTechPro/status/671698781965021188

This is all about building an experience more than it is about building software.

There is this big glass cube, with two monitors, a table, three Surface tablets, and more.
You walk in with a phone, you are recognized. 
You are welcomed by a synthetic voice. 
The room is tagged as occupied. 
Your cohorts come into the room.
A Lync meeting begins on the wall monitor.
A tweet is sent out.
You sit down at the table and your tablets are 'called' through Lync. 
You collaborate and save stuff. 
You exit the cube, the Lync meeting is exited and a follow-up email is sent.

Aruba handles the device detection.
Octoblu handles the automation of the actions.
Microsoft provides OneDrive and Lync.

Why describe all of this?  Well, for one thing I am kind of excepted about it.  But at the same time, there are lots of moving parts.  And each moving part must play its role.

I had a simple request around the tweet.  Since not everyone provides their twitter handle, can we substitute their first name instead?

Sure, sounds easy enough.
I observed some of the data I had captured.
I thought I was being rather cleaver with my solution and keyed off whether or not there was an '@' in the twittername field.
I tested it, it was great.
I deployed it.

Poo, a new case arose.  The app sending me data is suddenly sending me a twittername that is no more than '@'.  Now I am sending tweets with @ @ @ in them (and hoping that the Twitter spam bots don't kick in and suspend the account).

Suddenly I find myself focusing on my unit tests and the variations that I have seen as well as dreaming up ones that I have not seen but expect to.

In the end to handle this simple request I had 9 conditions to cover the cases of the expected data from the other application.
The golden case where everything is good, and eight cases of other stuff.

Since I use PowerShell to do everything, my JSON looks like this:

$TwitterHandles += @{ “firstname”=”Brian”; "twittername"="@testhandle" }
$TwitterHandles += @{ “firstname”=”Brian”; "twittername"="@" }
$TwitterHandles += @{ “firstname”=”Moheeb”; "twittername"="" }
$TwitterHandles += @{ “firstname”=””; "twittername"="@" }
$TwitterHandles += @{ “firstname”=”Thomas” }
$TwitterHandles += @{ “firstname”=”Joe”; "twittername"=$null }
$TwitterHandles += @{ “firstname”=$null; "twittername"=$null }
$TwitterHandles += @{ “firstname”=”Joe”; "twittername"="null" }
$TwitterHandles += @{ “firstname”="null"; "twittername"="null" }


I had two data fields to cover issues with; twittername always being my first key and the possibility of substituting the firstname.
Now, the resulting code I wrote in JavaScript.  And there is the possibility for 'null' and null.  This is null as a string vs. null as an object / value.  These go right along with missing or blank values.

Suddenly I have something with much better coverage of the possible cases and thus a much better product.
One hour to get the first version and three to work out all the error handling cases.

the end result is a solid product.  But the lesson is to not focus on getting it done, it is to make it solid.

In case anyone want sot see the javascript, here is it;

// Requirement: If there isn't a twitter handle provided, replace with the first name
if(typeof (msg.params.twittername) !== "undefined" && (msg.params.twittername) !== null) {
 if ( (msg.params.twittername).substring(0,1) === "@" && (msg.params.twittername).substring(1) && (msg.params.twittername) !== "null" ){
  return msg;
 } else {
  if ( (msg.params.firstname).substring(0) && (msg.params.firstname) !== "null" ) {
   return {
    params:{
     twittername: msg.params.firstname
    }
   };
  }
 }
} else {
 if(typeof (msg.params.firstname) !== "undefined" && (msg.params.firstname) !== null && (msg.params.firstname) !== "null") {
  return {
   params:{
    twittername: msg.params.firstname
   }
  };
 }
}