Sunday, December 7, 2014

The Paradox of Choice

Less is more

Choice is an interesting problem for Software Engineers.  The user will often request for more choices rather than less.  More choices is often associated with better value, even though its well documented that as consumers we need the opposite. See Barry Schwartz's TED talk on The Paradox of Choice.




Why do users always ask for more? Risk Aversion

Recently on NPR another ted talk came up about Chimpanzee economics.  See Laurie Santos: A monkey economy as irrational as ours.  In her TED Talk, Laurie discussed how chimpanzees behaved similarly to humans.  Specifically, chimpanzees and humans share risk aversion behavior Wikipedia: Risk Aversion .  She and her colleagues performed a simple experiment to test this out.  A team member would give the chimpanzee a chance to purchase grapes for a token.  The chimpanzee had to interact with two different members.

In the first scenario, a chimpanzee was presented 1 grape, which it would exchange for 1 token.  The seller would add a second grape as a bonus to the chimpanzee.

In the second scenario, a chimpanzee was presented 3 grapes, by another seller for 1 token.  The seller then would remove a grape, and give the chimpanzee 2 grapes.

Logically, the chimpanzee had an equal deal from both sellers, so it should buy equally from both sellers.  This wasn't the case, as the chimpanzee associated the second with the risk of losing a grape.

What Laurie had observed was Risk Aversion, a behavior that humans and chimpanzees share.  Its an unavoidable part of our wiring as humans.  When we take away choices, we seemingly introduce risk for a user.  



Learning space grows N! as more features are introduced

This was a pretty shocking revelation for me, to come to as I was reading through The Design of Everyday Things.  This wasn't obvious for me, I never really thought about it.  I assumed a 1-to-1 cost for introducing new functionality.  This just isn't true, the book provides the simple example using a stove top below.

The possibilities for the control which controls each burner

top-left: 4 choices
bottom-left: 3 choices
top-right: 2 choices
bottom-right: 1 choice

So as we're figuring out this interface, we have 4 choices, then 3 choices, etc.  If we calculate this out,  that means there are 24 possible possible options to consider.  4 x 3 x 2 x 1 = 4!

Probably actually worse than factorial, closer to n ^ n

My girlfriend pointed out, that all of this is assuming that we can remember what each control does as it goes along.  This is perfectly possible for most users when the functionality space is small.  But consider something with a great deal of functionality.  We have to assume that it won't be possible for the user to remember exactly each piece of functionality.  Imagine a non-expert user coming into a complicated interface.  For example: a non-pilot looking at airplane controls.


If you're like me, and not a pilot you'd probably be very intimidated at the prospect of having to pilot a plane if you've never seen these controls before.  The commonly cited limit for short term memory is that we can hold 7 + 2 items in our memory.  This doesn't even take into consideration other external factors, anxiety, disruptions, etc.  Essentially, using a new piece of software would be like playing the memory card game.


Strategies for mitigating the problem of choice 

  1. Constraints: The book The Design of Everyday Things talks in depth about this.  We can introduce constraints on an interface, that reduce the size of the learning space.  The user may have certain expectations about how some functionality should flow.  For example, I should have items to purchase before I provide credit card information.  Or, I'm the user is not provided the option based on state.  I really recommend checking out the book for discussion about this.
  2. Natural Mappings: Another thing the book talks about are natural mappings.  These are mappings to functionality that are already well established.  For example icons which can convey universal meaning: A trashcan to remove, arrows, etc.  Natural mappings leverage the users existing knowledge, and eliminate the need to relearn an interface.
  3. Established Domain Language: If the user is forced to learn the interface, then it should be done using the well established language of the user.  We should definitely not force the user to also explore a language space to understand meaning in the application.
  4. Provide Purpose and Meaning (Method of Loci): If the user is forced to learn something new, they should be able to construct a story they can walk through in their mind.  See Method of loci.  For example, creating a bookmark, I can imagine the internet as a very large book, the bookmark functionality as leaving an actual physical marker in the page, that I can find later.  For the purposes of the user, it doesn't matter so much that they're story is correct, just that it works for them to understand the interface.







Wednesday, December 3, 2014

Fundamentals: Get back to basics

Its already been pretty well accepted that JavaScript is not a toy language.  The JavaScript ecosystem has exploded, with many different frameworks and libraries.  Chances are that there are already many different libraries for whatever functionality you need to implement.  Best practices, coding standards, etc have been established.  So why is there seemingly a need to re-invent the wheel?

Just because you can do it, doesn't mean you should


JavaScript is a very powerful language, a lot has been written about more advanced JavaScript techniques.  There are many excellent resources for learning about these techniques.

See JavaScript Patterns by Stoyan Stefanov

The power of these techniques is leveraging them when appropriate, not just for the sake of doing it.  Lets look at some examples:

Immediate functions


Unnecessary wrapping of code with an immediate function to enforce strict mode (example 1)

(function () {
  'use strict';
  angular.module('app').
    controller('AppController', ['$scope', function ($scope) {
      $scope.myVar = 'myvar';

      $scope.fooMethod = function () {

      };
    }]);
  
}());

Simple strict mode declaration

angular.module('app').
  controller('AppController', ['$scope', function ($scope) {
    'use strict';

    $scope.myVar = 'myvar';

    $scope.fooMethod = function () {
    };
  }]);


In the above example, we see two ways of enforcing strict mode on our Angular Controller. In example 1, we are using an immediate function to put the block into strict mode.  The example above is a trivial example, but has the following drawbacks.
  1. We are creating another activation record to push onto the stack, since JavaScript is function scoped.  Its trivial, but there's no need to create an enclosing scope just to wrap the block with strict mode.
  2. Novice developers may be confused by the immediate function syntax.  In any project where you work with other developers, readability and clarity are of utmost importance.

Abusing the "this" keyword

In the short time that I have used angular, I feel very strongly that one of the greatest advantages of angular is that we don't need to use the this keyword which is often poorly understood by most developers, and can still be confusing to more experienced JavaScript developers.  

JavaScript Scoping

One of the biggest pitfalls new JavaScript developers run into is believing that JavaScript is block scoped, it is not block scoped!  


Using the this keyword can easily lead to confusion lets look at some examples.  Imagine we create a factory object, which has the method increment, this increment method should update its count property.  At some point later in time, a controller re-uses this method to increment factory.count.  Using the this keyword can result in an unexpected result.
var factory = {
    count: 0,
    increment: function () {
      this.count++;
    }
  };

  var controller = {
    count: 10,
    increment: factory.increment
  };

  controller.increment();
  console.log(factory.count); // 1
  console.log(controller.count); // 11

See live code in JS BIN

The above example is trivial, but its not hard to imagine examples where we can get unexpected results.  Pretty much anywhere we use the this keyword, we can achieve the same result with closure.
  var factory = {
    count: 0,
    increment: function () {
      factory.count++;
    }
  };

  var controller = {
    count: 10,
    increment: factory.increment
  };

  controller.increment();
  console.log(factory.count); // 1
  console.log(controller.count); // 10

Using this in angular controller makes unit testing weird


angular.module('app').
  controller('AppController', ['$scope', function ($scope) {
    var my = this;
    
    my.count = 0;
    my.increment = function () {
      my.count++;  
    };

  }]);

// jasmine specs

inject(function ($rootScope, $controller) {
  ctrl = $root$scope.$new();
  $controller('AppController', {
   $scope: ctrl
  });  
});

it('ctrl has the increment method', function () {
  expect(ctrl.increment).toBeDefined(); // fails
  expect(ctrl.count).toBe(0); // fails
});

We see that by using the this keyword, accessing the functions to test is not available the standard way we create unit tests for angular in jasmine.

Closure nightmare

An example combining immediate functions, and abuse of the this keyword follows below.

(function () { // immediate function introduces a closure scope
  'use strict'; 

  angular.module('app').
    controller('MyCtrl', ['$scope', MyCtrl]).
    factory('MyFactory', ['OtherService', MyFactory]);

  function MyCtrl($scope) {
    var that = this; // depends on controller()

    $scope.count = 0;
    
    that.factoryIncrement = MyFactory.increment;

    that.increment = function () {
      that.factoryIncrement();      
      that.count++;
    };

  }

  function MyFactory(OtherService) {
    return {
      count: 0,
      increment: function () {
        this.count++;  // depends on factory()
      };
    };
  }

  function initialize() {
    var that = this; // 'this' depends on context
    that.count = 10;
  };

  this.initialize = initialize; // this -> immediate fn

  this.initialize();
}());

In the above example this has 4 possible meanings!

Takeaways

Use advanced JavaScript techniques when appropriate.  Like all advances in programming paradigms, the power of Angular, is the fact that abstracts away unnecessary details and allows the developer to focus on the Business logic.