Secrets of Awesome JavaScript API Design

Post on 14-Jan-2015

922 views 3 download

Tags:

description

As developers, we know what good and bad JavaScript APIs "feel" like, and yet we struggle with designing the kind of APIs that we enjoy using. But principles of good JavaScript API design do exist, and it's possible to extract them from several key libraries in the the proliferating JavaScript landscape. In this session, Brandon Satrom will do exactly that, digging into the design aspects of popular libraries like jQuery, Backbone, Knockout, Modernizer, Kendo UI and others to enumerate the designed-in qualities of these libraries that make them not only popular, but a pleasure to use.

Transcript of Secrets of Awesome JavaScript API Design

Secrets of Awesome JavaScript API Design

Brandon Satrom@BrandonSatrom

brandon@KendoUI.com

Read it @ bit.ly/ZS5VYv

3 Big Ideas

Three ideas...

Three ideas...

1.APIs are “Developer UX”

Three ideas...

1.APIs are “Developer UX”

2.API Design is a universal practice

Three ideas...

1.APIs are “Developer UX”

2.API Design is a universal practice

3.API Design is a “principle-driven” art form

What is design?

What is design?

“form[ing] a plan or scheme of [some thing]… for later execution”

- Oxford English Dictionary

Design is universal

We judge designs to be...

...elegant

...beautiful

...utilitarian

...simple

...or not...Images from: http://www.greatbuildings.com/ and http://list25.com/25-ugliest-buildings-in-the-world/

We judge designs to be...

...elegant

...beautiful

...utilitarian

...simple

...or not...Images from: http://www.greatbuildings.com/ and http://list25.com/25-ugliest-buildings-in-the-world/

We judge designs to be...

...elegant

...beautiful

...utilitarian

...simple

...or not...Images from: http://www.greatbuildings.com/ and http://list25.com/25-ugliest-buildings-in-the-world/

We judge designs to be...

...elegant

...beautiful

...utilitarian

...simple

...or not...Images from: http://www.greatbuildings.com/ and http://list25.com/25-ugliest-buildings-in-the-world/

“Mediocre design provably wastes the world’s resources, corrupts the environment, affects international

competitiveness. Design is important, teaching design is

important.”

- Fred Brooks

Image of Fred Brooks from: http://en.wikipedia.org/wiki/File:Fred_Brooks.jpg

Poor Design is Costly

We apply design practices to User Interfaces...

Images from: http://pinterest.com/fromupnorth/gui/

We call this practice, User Experience (UX)

design...Images from: http://pinterest.com/fromupnorth/gui/

What about API Design?

API Design is “Developer UX”

API Design is not JUST For Library Authors...

...modular JS is written to be consumed...

In JavaScript, API design is critically important...

Dynamic Language...

Anonymity of consumers...

http://i.qkme.me/3qesq1.jpg

Ambiguity of Requirements...

Your users are smart...

A Tale of Ice & Water

A Tale of Ice & Water

A Tale of Ice & Water

A Tale of Ice & Water

A Tale of Ice & Water

Your users are smart...

Example:

$("article.blogPost").fadeIn();

Use jQuery to select all article tags with the class “blogPost”

Example:

$("article.blogPost").fadeIn();

Use jQuery to select all article tags with the class “blogPost”

article.blogPost  {    border-­‐radius:  10px;    background-­‐color:  salmon;    box-­‐shadow:  0px  0px  10px  2px  #ccc;}

Goals are not enough...

...we need guiding principles...

Principles, not Rules

Principles, not Rules

Rules are rote, often applied without context

Principles encourage application of context

Principles on display in popular libraries...

Backbone

jQuery

Kendo UI

Modernizr

Moment.js

Underscore

The Four Principles

Unity & Harmony

Balance

Proportion

Emphasis

WARNING! This is ART, not SCIENCE

Unity and Harmony (art)

Unity: The concept behind a work, or how the composer brings everything together into a coherent whole.

Harmony: The placement of similar elements throughout a work, yielding an uncomplicated and simple feel.

Painting by Robert Delauny, from: http://char.txa.cornell.edu/language/principl/principl.htm

Unity & Harmony (API Design):Familiarity & Comfort

Extended Object creation in Backbone

Widget Instantiation in Kendo UI

Use similar and/or unifying elements through your library to create familiarity and comfort

Example: Create Kendo UI Widgets from jQuery-selected DOM Elements

$("ul.tree").kendoTreeView();

$("ul.panel").kendoPanelBar();

$("div").kendoGrid();

Example: Create Kendo UI Widgets from jQuery-selected DOM Elements

Each Widget is prefixed with “kendo” and named in a consistent, camel-cased style

$("ul.tree").kendoTreeView();

$("ul.panel").kendoPanelBar();

$("div").kendoGrid();

Example: Create Extended Objects with Backbone

var  Book  =  Backbone.Model.extend({    initialize:  function()  {  ...  },    author:  function()  {  ...  },    pubDate:  function()  {  ...  },});

var  DocumentRow  =  Backbone.View.extend({    tagName:  "li",    className:  "row",    events:  {        "click  .icon":  "open",        "click  .button.edit":  "openEditDialog"    },    render:  function()  {  ...  }});

Example: Create Extended Objects with Backbone

[Object].extend is used to “inherit” the built-in functionality of Backbone Models, Views, Collections and Routers

var  Book  =  Backbone.Model.extend({    initialize:  function()  {  ...  },    author:  function()  {  ...  },    pubDate:  function()  {  ...  },});

var  DocumentRow  =  Backbone.View.extend({    tagName:  "li",    className:  "row",    events:  {        "click  .icon":  "open",        "click  .button.edit":  "openEditDialog"    },    render:  function()  {  ...  }});

Balance (art)

The arrangement of elements to ensure that no one part of the work overpowers other parts, or causes it to feel unstable.

Image of Italian Textile, 18th Centuty from: http://char.txa.cornell.edu/language/principl/principl.htm

Balance (API Design):Weight & Predictability

Browser Feature Tests in Modernizr

DOM Selection Syntax in jQuery

Ensure that each function of your library exhibits consistent behavior, or aids in meeting a complimentary goal.

Example:

Modernizr.geolocationModernizr.localstorageModernizr.webworkersModernizr.canvasModernizr.borderradius

Test Browser Capabilities using Modernizr

Example:

Modernizr.geolocationModernizr.localstorageModernizr.webworkersModernizr.canvasModernizr.borderradius

Test Browser Capabilities using Modernizr

Each property matches an HTML5/CSS-related API and returns a boolean

Example:

$("#grid")  //  Selects  by  ID

$("ul.nav  >  li")  //  All  LIs  for  UL  w/class  "nav"

$("ul  li:nth-­‐child(2)")  //  2nd  item  in  each  list

Select DOM Elements using jQuery’s Selector Syntax

Example:

$("#grid")  //  Selects  by  ID

$("ul.nav  >  li")  //  All  LIs  for  UL  w/class  "nav"

$("ul  li:nth-­‐child(2)")  //  2nd  item  in  each  list

Select DOM Elements using jQuery’s Selector Syntax

Many jQuery Selectors map directly to equivalent CSS selectors

Proportion (art)

A measurement of the size and quantity of elements within a work, relative to the whole.

Image of Salisbury Cathedral from: http://char.txa.cornell.edu/language/principl/principl.htm

Proportion (API Design):Scope that matches capability

Moment.js

Underscore

Make sure that every interface of the library matches its intended purpose & that no extraneous elements exist.

Example: Moment.js is working working with dates... and that’s it

moment().format('dddd');moment().startOf('hour').fromNow();moment().format('[Hello from] YYYY'); // Hello from 2013moment().startOf('day').fromNow();

Example: Moment.js is working working with dates... and that’s it

Moment is designed to make working with the JavaScript Date object tolerable, and it provides no functionality beyond that scope.

moment().format('dddd');moment().startOf('hour').fromNow();moment().format('[Hello from] YYYY'); // Hello from 2013moment().startOf('day').fromNow();

_.each(["Todd",  "Burke",  "Derick"],  function(name){      alert(name);  });

_.map([1,  2,  3],  function(num){      return  num  *  3;  });

_.isNumber("ten");  //  False

Example: Underscore.js, designed to add functional programming support to JS

_.each(["Todd",  "Burke",  "Derick"],  function(name){      alert(name);  });

_.map([1,  2,  3],  function(num){      return  num  *  3;  });

_.isNumber("ten");  //  False

Example: Underscore.js, designed to add functional programming support to JS

Underscore provides utility functions that help devs work with JS collections, arrays, functions and objects. Larger API surface, for a broader purpose.

Emphasis

Image of the FlatIron Building from: http://www.greatbuildings.com/

Emphasis (art)

The point of focus or interruption of a work. The use of contrast to cause an aspect of the work to stand out and capture the viewer’s

attention.

Image from: http://char.txa.cornell.edu/language/principl/principl.htm

Emphasis (API Design):Creating a focal point

Plugin development using jQuery’s fn namespace

Method chaining in jQuery

Object extensibility in Backbone

Provide a gateway method that anchors your library, a chained or fluent API, or create extensibility hooks for consuming devs

Example: jQuery enables a fluent programming style by returning a jQuery object from most functions.

$(‘ul.first’).find(‘.overdue’)    .css(‘background-­‐color’,‘red’)    .end()    .find(‘.due-­‐soon’)    .css(‘background-­‐color’,  ‘yellow’);

Example: jQuery enables a fluent programming style by returning a jQuery object from most functions.

This style enables devs to accomplish a great deal of work in a terse, yet readable manner.

$(‘ul.first’).find(‘.overdue’)    .css(‘background-­‐color’,‘red’)    .end()    .find(‘.due-­‐soon’)    .css(‘background-­‐color’,  ‘yellow’);

(function($)  {    $.fn.kittehfy  =  function()  {        return  this.each(function(idx,  el)  {                            var  width  =  el.width,                height  =  el.height;            var  src=  "http://placekitten.com/";            el.src=  src  +  width  +  "/"  +  height;        });    };})(jQuery);

$("img").kittehfy();

Example: jQuery plugins are connected to jQuery via the fn (“effin”) namespace...

(function($)  {    $.fn.kittehfy  =  function()  {        return  this.each(function(idx,  el)  {                            var  width  =  el.width,                height  =  el.height;            var  src=  "http://placekitten.com/";            el.src=  src  +  width  +  "/"  +  height;        });    };})(jQuery);

$("img").kittehfy();

Example: jQuery plugins are connected to jQuery via the fn (“effin”) namespace...

jQuery Plugins “feel” like natural extensions to jQuery itself, and behave in similar ways

Example:

   var  Book  =  Backbone.Model.extend({        initialize:  function()  {  ...  },    author:  function()  {  ...  },    pubDate:  function()  {  ...  },});

var  DocumentRow  =  Backbone.View.extend({    tagName:  "li",    className:  "row",    events:  {        "click  .icon":  "open",        "click  .button.edit":  "openEditDialog"    },    render:  function()  {  ...  }});

Create Extended Objects with Backbone

Example:

   var  Book  =  Backbone.Model.extend({        initialize:  function()  {  ...  },    author:  function()  {  ...  },    pubDate:  function()  {  ...  },});

var  DocumentRow  =  Backbone.View.extend({    tagName:  "li",    className:  "row",    events:  {        "click  .icon":  "open",        "click  .button.edit":  "openEditDialog"    },    render:  function()  {  ...  }});

Create Extended Objects with Backbone

[Object].extend is used to “inherit” the built-in functionality of Backbone Models, Views, Collections and Routers

The Four Principles

Unity & Harmony

Balance

Proportion

Emphasis

The HORROR!aka What might go wrong...

What might go wrong...

Inconsistency Disproportionality

Imbalance...Callback signatures on $.map, $.each & $(el).map

Imbalance...Callback signatures on $.map, $.each & $(el).map

Imbalance...

var  letters  =  [“a”,  “b”,  “c”,  “d”,  “e”];

Callback signatures on $.map, $.each & $(el).map

Imbalance...

var  letters  =  [“a”,  “b”,  “c”,  “d”,  “e”];$.each(letters,  function(index,  val)  {    console.log(index  +  “:  “  +  val.toUpperCase());

Callback signatures on $.map, $.each & $(el).map

Imbalance...

var  letters  =  [“a”,  “b”,  “c”,  “d”,  “e”];$.each(letters,  function(index,  val)  {    console.log(index  +  “:  “  +  val.toUpperCase());});  

Callback signatures on $.map, $.each & $(el).map

Imbalance...

var  letters  =  [“a”,  “b”,  “c”,  “d”,  “e”];$.each(letters,  function(index,  val)  {    console.log(index  +  “:  “  +  val.toUpperCase());});  

Callback signatures on $.map, $.each & $(el).map

Imbalance...

var  letters  =  [“a”,  “b”,  “c”,  “d”,  “e”];$.each(letters,  function(index,  val)  {    console.log(index  +  “:  “  +  val.toUpperCase());});  

var  uppers  =  $.map(letters,  function(val,  index)  {    return  (val.toUpperCase());

Callback signatures on $.map, $.each & $(el).map

Imbalance...

var  letters  =  [“a”,  “b”,  “c”,  “d”,  “e”];$.each(letters,  function(index,  val)  {    console.log(index  +  “:  “  +  val.toUpperCase());});  

var  uppers  =  $.map(letters,  function(val,  index)  {    return  (val.toUpperCase());});

Callback signatures on $.map, $.each & $(el).map

Imbalance...

var  letters  =  [“a”,  “b”,  “c”,  “d”,  “e”];$.each(letters,  function(index,  val)  {    console.log(index  +  “:  “  +  val.toUpperCase());});  

var  uppers  =  $.map(letters,  function(val,  index)  {    return  (val.toUpperCase());});

Callback signatures on $.map, $.each & $(el).map

Imbalance...

var  letters  =  [“a”,  “b”,  “c”,  “d”,  “e”];$.each(letters,  function(index,  val)  {    console.log(index  +  “:  “  +  val.toUpperCase());});  

var  uppers  =  $.map(letters,  function(val,  index)  {    return  (val.toUpperCase());});

$(‘li’).map(function(index,  val)  {  //WAT  });

Callback signatures on $.map, $.each & $(el).map

Imbalance...

var  letters  =  [“a”,  “b”,  “c”,  “d”,  “e”];$.each(letters,  function(index,  val)  {    console.log(index  +  “:  “  +  val.toUpperCase());});  

var  uppers  =  $.map(letters,  function(val,  index)  {    return  (val.toUpperCase());});

$(‘li’).map(function(index,  val)  {  //WAT  });

Callback signatures on $.map, $.each & $(el).map

Not only do $.map and $.each diverge, but $.map and $(el).map order the callback params differently, depending on how the method is called.

Proportion: Too Large or Too Small...

Image of Fred Brooks from: http://www-set.win.tue.nl/UnsungHeroes/files/PTERA-Kosten-Poel.jpg

“... Every instruction carried out the same operation. He demonstrated the sufficiency of his operation—his machine could do anything any other computer could do...”

Image of Fred Brooks from: http://www-set.win.tue.nl/UnsungHeroes/files/PTERA-Kosten-Poel.jpg

“...the delight that came from using it was similar to... working out a crossword puzzle—a construct of intentional complexity and no intended utility.”

- Fred Brooks

AwesomeLib.do(prams);

AwesomeLib.do(prams);

We can all agree that this is bad

jQuery();

jQuery();

Then, so is this...

Overloads on the jQuery() method...

jQuery(  selector  [,  context]  )  //  Select

Overloads on the jQuery() method...

jQuery(  selector  [,  context]  )  //  Select

jQuery(  element  )  //  Wrap

Overloads on the jQuery() method...

jQuery(  selector  [,  context]  )  //  Select

jQuery(  element  )  //  Wrap

jQuery(  object  )  //  Wrap

Overloads on the jQuery() method...

jQuery(  selector  [,  context]  )  //  Select

jQuery(  element  )  //  Wrap

jQuery(  object  )  //  Wrap

jQuery()  //  Empty  $  Object

Overloads on the jQuery() method...

jQuery(  selector  [,  context]  )  //  Select

jQuery(  element  )  //  Wrap

jQuery(  object  )  //  Wrap

jQuery()  //  Empty  $  Object

jQuery(  elementArray  )  //  Wrap

Overloads on the jQuery() method...

jQuery(  selector  [,  context]  )  //  Select

jQuery(  element  )  //  Wrap

jQuery(  object  )  //  Wrap

jQuery()  //  Empty  $  Object

jQuery(  elementArray  )  //  Wrap

jQuery(    jQuery  object  )  //  Clone

Overloads on the jQuery() method...

jQuery(  selector  [,  context]  )  //  Select

jQuery(  element  )  //  Wrap

jQuery(  object  )  //  Wrap

jQuery()  //  Empty  $  Object

jQuery(  elementArray  )  //  Wrap

jQuery(    jQuery  object  )  //  Clone

jQuery(  html  [,  ownerDocument  ]  )  //  Create  DOM  Elements

Overloads on the jQuery() method...

jQuery(  selector  [,  context]  )  //  Select

jQuery(  element  )  //  Wrap

jQuery(  object  )  //  Wrap

jQuery()  //  Empty  $  Object

jQuery(  elementArray  )  //  Wrap

jQuery(    jQuery  object  )  //  Clone

jQuery(  html  [,  ownerDocument  ]  )  //  Create  DOM  Elements

jQuery  (  html,  props  )  //  Create  DOM  Elements

Overloads on the jQuery() method...

jQuery(  selector  [,  context]  )  //  Select

jQuery(  element  )  //  Wrap

jQuery(  object  )  //  Wrap

jQuery()  //  Empty  $  Object

jQuery(  elementArray  )  //  Wrap

jQuery(    jQuery  object  )  //  Clone

jQuery(  html  [,  ownerDocument  ]  )  //  Create  DOM  Elements

jQuery  (  html,  props  )  //  Create  DOM  Elements

jQuery  (  callback  )  //  Bind  DOM  loaded  function  

Overloads on the jQuery() method...

jQuery(  selector  [,  context]  )  //  Select

jQuery(  element  )  //  Wrap

jQuery(  object  )  //  Wrap

jQuery()  //  Empty  $  Object

jQuery(  elementArray  )  //  Wrap

jQuery(    jQuery  object  )  //  Clone

jQuery(  html  [,  ownerDocument  ]  )  //  Create  DOM  Elements

jQuery  (  html,  props  )  //  Create  DOM  Elements

jQuery  (  callback  )  //  Bind  DOM  loaded  function  

Overloads on the jQuery() method...

11 ways to call jQuery, with 6 different contexts!

Now, for a question...

YES

But hey, jQuery’s not perfect...

... so you don’t have to be either.

Questions?

@BrandonSatrombrandon@kendoui.com