WHY NESTED
TERNARY OPERATORS
MAKE ME WANT TO
KICK INANIMATE
OBJECTS IN THE NUTS
AKA: Making JavaScript Libraries More Approachable




       http://tinyurl.com/ternaryops


                                                     Pamela Fox
                                                     @pamelafox
IN THE WORLD OF JS LIBRARIES...
 



                    AUTHORS
       USERS
I'VE BEEN USING A LOT OF LIBRARIES
THIS PAST YEAR...

 bootstrap.datepicker.js   jquery-1.7.min.js       jsrender.js
 bootstrap.js              jquery.autosuggest.js   lscache.js
 colorslider.js            jquery.calendrical.js   modernizr-1.7.js
 confetti.js               jquery.dateinput.js     personalize.js
 date.format.js            jquery.fancybox.js      phonegap.js
 dateinput.js              jquery.mobile.js        stacktrace.js
 dd_belatedpng.js          jquery.overlay.js       throttle.js
 facebook.js               jquery.sparkline.js     timeago.js
 facebook_js_sdk.js        jquery.tablesorter.js   useragent.js
 handlebars.js             jquery.tagfield.js      zepto.js
 highcharts.js             jquery.tooltip.js
AND SOMETIMES I SEE STUFF LIKE...
 
(aposed
      ? (aposed = !apos, (aposed ? all : '"'))
      : quoted
            ? (quoted = !quot, (quoted ? all : '"'))
            :
      (
            (lftPrn
                  ? (parenDepth++, lftPrn)
                  : "")
            + (space
                  ? (parenDepth
                               ? ""
                               : named
                                            ? (named = FALSE, "b")
                                            : ","
                  )
                  : eq
                        ? (parenDepth && syntaxError(), named = TRUE, 'b' + path + ':')
                        : path
                               ? (path.replace( rPath, parsePath )
                                     + (prn
                                            ? (fnCall[ ++parenDepth ] = TRUE, prn)
                                            : operator)
                               )
                               : operator
                                     ? all
                                     : rtPrn
                                            ? ((fnCall[ parenDepth-- ] = FALSE, rtPrn)
                                                  + (prn
                                                         ? (fnCall[ ++parenDepth ] = TRUE, prn)
                                                         : "")
                                            )
                                            : comma
                                                  ? (fnCall[ parenDepth ] || syntaxError(), ",")
                                                  : lftPrn0
                                                         ? ""
                                                         : (aposed = apos, quoted = quot, '"')
))
AND IT MAKES ME THINK OF....
 
...WOULDN'T THIS BE BETTER?
 
THE STAGES OF BEING A USER:
 
 
  1. Learn how a library works & start using it.
    
  2. Debug a library when something goes wrong.
    
  3. Integrate a library with their workflow.
    
  4. BONUS: Submit a patch to the library.
STEP 1: THIS LOOKS LIKE A USEFUL
LIBRARY, LET'S SEE HOW TO USE IT.
    Document everything.
 
      JSDoc, JSDuck, JoDoc, Dox, NDoc,
      Docco, Docco-Husky,
      AutoObjectDocumentation
 
    Make the documentation easy to patch.
 
NOT ENOUGH DOCUMENTATION.
 
this.options.knownHelpers = {
    'each': true,
    'if': true,
    'unless': true,
    'with': true,
    'log': true
};
                handlebars.js
TOO MUCH DOCUMENTATION.
 



                              vs.




   http://handlebarsjs.com/         https://github.com/wycats/handlebars.js/
DUPLICATE DOCUMENTATION.
 



             vs.
STEP 2: UH OH, SOMETHING WENT
WRONG. TIME TO DEBUG THE LIB.
Make your code readable.
 ○ "Common conventions"
   ■ Idiomatic.js
   ■ Crockford
   ■ Google
   ■ Stoyan Stefanov
 ○ Descriptive variable names


 Make your code debuggable.
 ○ On *all* platforms.
SHORTENED VARIABLE NAMES.
 ar a = s.split("."),
v
        r = "",
        l = a.length;
for (var i = 0; i < l; i++) {
  var item = a[i];
  if (item.length == 2) {
    r += "0" + item;
  } else {
    r += item;
  }
}


function login(a, b) {
    b = b || { perms: '' };
    PhoneGap.exec(function(e) {
      FB.Auth.setSession(e.session, 'connected');
      if (a) a(e);
     }, null, 'com.phonegap.facebook.Connect', 'login', b.
perms.split(',') );
}
SHORTENED VARIABLE NAMES.
 
 FOR:
   The shortened names make sense.
  
 AGAINST:
   Maybe to you. But not to people new to the
 code.
  
   "Common sense" is not common.
SHORTENED VARIABLE NAMES.
 
 FOR:
   It makes for less bytes of code.
  
 AGAINST:
   Code will get compiled to least number of
 bytes anyway.
  
SHORTENED VARIABLE NAMES.
 
 FOR:
   It makes for shorter lines of code.
  
 AGAINST:
   Readability trumps line length.
  
MISSING SEMI-COLONS.
 
MISSING SEMI-COLONS.
 
 AGAINST:
   It's harder to read the code.
  
  "Readability is the single most important quality of a
 piece of code. Semicolons aid readability by eliminating
 ambiguity and ensuring that nobody ever has to spend
 even a second figuring out whether your code will do
 what it looks like it will do."
                                            Jason Kester


 "Relying on implicit insertion can cause subtle, hard to
 debug problems. Don't do it. You're better than that."
                                            Google Style Guide
MISSING SEMI-COLONS.
 
 AGAINST:
  It's harder to make non-breaking changes
 to the code.
 this:

  a = b + c
  (d + e).print()

 is interpreted as:


  a = b + c(d + e).print();

 so you have to:

  ;(d + e).print()
MISSING SEMI-COLONS.
 
 FOR:
   It's what the core team prefers.
  
 AGAINST:
   The core team aren't the only developers
 looking at the code. (But they might be if
 that's the philosophy.)

 Don't use your library code to show off how well you
 know the idiosyncrasies of a language.
MISSING SEMI-COLONS.
 
 AGAINST:
  Hacker News agrees.
NESTED TERNARY OPERATORS.
 
$.qsa = $$ = function(element, selector){
    var found
    return (element === document && idSelectorRE.test(selector)) ?
      ( (found = element.getElementById(RegExp.$1)) ? [found] : emptyArray )
:
      (element.nodeType !== 1 && element.nodeType !== 9) ? emptyArray :
      slice.call(
        classSelectorRE.test(selector) ? element.getElementsByClassName
(RegExp.$1) :
        tagSelectorRE.test(selector) ? element.getElementsByTagName
(selector) :
        element.querySelectorAll(selector)
      )
  }
                               zepto.js
NESTED TERNARY OPERATORS.
 AGAINST:
    They're hard to understand.
 $.qsa = $$ = function(element, selector){
    var found;
    if (element === document && idSelectorRE.test(selector)) {
      found = element.getElementById(RegExp.$1);
      return found && [found] || [];
    } else if (classSelectorRE.test(selector)) {
      return slice.call(element.getElementsByClassName(RegExp.$1));
    } else if (tagSelectorRE.test(selector)) {
      return slice.call(element.getElementsByTagName(selector));
    } else {
      return slice.call(attemptQuerySelectorAll(element, selector));
    }
}




If you have the choice between two styles and
one is more readable, choose that one.
NESTED TERNARY OPERATORS.
 
 AGAINST:
   It's hard to debug the nested statements.
  
 FOR:
   You can debug them with breakpoints.
  
 AGAINST:
    You can't always use fancy debuggers.
     See: Android, IE
NESTED TERNARY OPERATORS.
 
FOR:
  They perform better than if/else.
 
AGAINST:
   Compilers convert if/else to ternary.
   Try in UglifyJS or Closure Compiler.
STEP 3: OK, ALL WORKING. TIME TO
INTEGRATE WITH MY WORKFLOW.
Try your code with popular JS build tools
  ● JSHint
  ● Closure Compiler / UglifyJS
  ● Grunt
 
   If it requires options, specify them or inline
them into the code.
UNSPECIFIED JSHINT OPTIONS.
 
[jshint] Error(s) in ./application/static/js/libs/bootstrap.js:
Missing semicolon. (line: 24, character: 5)
> "use strict"

Comma warnings can be turned off with 'laxcomma' (line: 31, character: 9)
> , thisStyle = thisBody.style

Bad line breaking before ','. (line: 30, character: 48)
> var thisBody = document.body || document.documentElement

...
Too many errors. (8% scanned). (line: 139, character: 73)




/*jshint asi:true, laxbreak:true, laxcomma:true, expr:true, boss:true */
STEP 4: WHEE, IT WORKS, NOW I JUST
WANNA SUBMIT THIS ONE CHANGE.

Make it easy to build.
 
Make the tests easy to run.
 
 ...use familiar tools and languages.
TOO MANY TOOLS?
 
SO, IF YOU'RE A LIBRARY...

WHICH ONE DO YOU WANT TO BE?
            elite                      approachable




                              vs.




                    AUTHORS
         USERS                      USERS       AUTHORS

Making JavaScript Libraries More Approachable

  • 1.
    WHY NESTED TERNARY OPERATORS MAKEME WANT TO KICK INANIMATE OBJECTS IN THE NUTS AKA: Making JavaScript Libraries More Approachable http://tinyurl.com/ternaryops Pamela Fox @pamelafox
  • 2.
    IN THE WORLDOF JS LIBRARIES...   AUTHORS USERS
  • 3.
    I'VE BEEN USINGA LOT OF LIBRARIES THIS PAST YEAR... bootstrap.datepicker.js jquery-1.7.min.js jsrender.js bootstrap.js jquery.autosuggest.js lscache.js colorslider.js jquery.calendrical.js modernizr-1.7.js confetti.js jquery.dateinput.js personalize.js date.format.js jquery.fancybox.js phonegap.js dateinput.js jquery.mobile.js stacktrace.js dd_belatedpng.js jquery.overlay.js throttle.js facebook.js jquery.sparkline.js timeago.js facebook_js_sdk.js jquery.tablesorter.js useragent.js handlebars.js jquery.tagfield.js zepto.js highcharts.js jquery.tooltip.js
  • 4.
    AND SOMETIMES ISEE STUFF LIKE...   (aposed ? (aposed = !apos, (aposed ? all : '"')) : quoted ? (quoted = !quot, (quoted ? all : '"')) : ( (lftPrn ? (parenDepth++, lftPrn) : "") + (space ? (parenDepth ? "" : named ? (named = FALSE, "b") : "," ) : eq ? (parenDepth && syntaxError(), named = TRUE, 'b' + path + ':') : path ? (path.replace( rPath, parsePath ) + (prn ? (fnCall[ ++parenDepth ] = TRUE, prn) : operator) ) : operator ? all : rtPrn ? ((fnCall[ parenDepth-- ] = FALSE, rtPrn) + (prn ? (fnCall[ ++parenDepth ] = TRUE, prn) : "") ) : comma ? (fnCall[ parenDepth ] || syntaxError(), ",") : lftPrn0 ? "" : (aposed = apos, quoted = quot, '"') ))
  • 5.
    AND IT MAKESME THINK OF....  
  • 6.
  • 7.
    THE STAGES OFBEING A USER:     1. Learn how a library works & start using it.   2. Debug a library when something goes wrong.   3. Integrate a library with their workflow.   4. BONUS: Submit a patch to the library.
  • 8.
    STEP 1: THISLOOKS LIKE A USEFUL LIBRARY, LET'S SEE HOW TO USE IT. Document everything.   JSDoc, JSDuck, JoDoc, Dox, NDoc, Docco, Docco-Husky, AutoObjectDocumentation   Make the documentation easy to patch.  
  • 9.
    NOT ENOUGH DOCUMENTATION.   this.options.knownHelpers= { 'each': true, 'if': true, 'unless': true, 'with': true, 'log': true }; handlebars.js
  • 10.
    TOO MUCH DOCUMENTATION.   vs. http://handlebarsjs.com/ https://github.com/wycats/handlebars.js/
  • 11.
  • 12.
    STEP 2: UHOH, SOMETHING WENT WRONG. TIME TO DEBUG THE LIB. Make your code readable. ○ "Common conventions" ■ Idiomatic.js ■ Crockford ■ Google ■ Stoyan Stefanov ○ Descriptive variable names Make your code debuggable. ○ On *all* platforms.
  • 13.
    SHORTENED VARIABLE NAMES.  ara = s.split("."), v r = "", l = a.length; for (var i = 0; i < l; i++) { var item = a[i]; if (item.length == 2) { r += "0" + item; } else { r += item; } } function login(a, b) { b = b || { perms: '' }; PhoneGap.exec(function(e) { FB.Auth.setSession(e.session, 'connected'); if (a) a(e); }, null, 'com.phonegap.facebook.Connect', 'login', b. perms.split(',') ); }
  • 14.
    SHORTENED VARIABLE NAMES.   FOR: The shortened names make sense.   AGAINST: Maybe to you. But not to people new to the code.   "Common sense" is not common.
  • 15.
    SHORTENED VARIABLE NAMES.   FOR: It makes for less bytes of code.   AGAINST: Code will get compiled to least number of bytes anyway.  
  • 16.
    SHORTENED VARIABLE NAMES.   FOR: It makes for shorter lines of code.   AGAINST: Readability trumps line length.  
  • 17.
  • 18.
    MISSING SEMI-COLONS.   AGAINST: It's harder to read the code.    "Readability is the single most important quality of a piece of code. Semicolons aid readability by eliminating ambiguity and ensuring that nobody ever has to spend even a second figuring out whether your code will do what it looks like it will do." Jason Kester "Relying on implicit insertion can cause subtle, hard to debug problems. Don't do it. You're better than that." Google Style Guide
  • 19.
    MISSING SEMI-COLONS.   AGAINST: It's harder to make non-breaking changes to the code. this: a = b + c (d + e).print() is interpreted as: a = b + c(d + e).print(); so you have to: ;(d + e).print()
  • 20.
    MISSING SEMI-COLONS.   FOR: It's what the core team prefers.   AGAINST: The core team aren't the only developers looking at the code. (But they might be if that's the philosophy.) Don't use your library code to show off how well you know the idiosyncrasies of a language.
  • 21.
  • 22.
    NESTED TERNARY OPERATORS.   $.qsa= $$ = function(element, selector){ var found return (element === document && idSelectorRE.test(selector)) ? ( (found = element.getElementById(RegExp.$1)) ? [found] : emptyArray ) : (element.nodeType !== 1 && element.nodeType !== 9) ? emptyArray : slice.call( classSelectorRE.test(selector) ? element.getElementsByClassName (RegExp.$1) : tagSelectorRE.test(selector) ? element.getElementsByTagName (selector) : element.querySelectorAll(selector) ) } zepto.js
  • 23.
    NESTED TERNARY OPERATORS.  AGAINST: They're hard to understand.  $.qsa = $$ = function(element, selector){ var found; if (element === document && idSelectorRE.test(selector)) { found = element.getElementById(RegExp.$1); return found && [found] || []; } else if (classSelectorRE.test(selector)) { return slice.call(element.getElementsByClassName(RegExp.$1)); } else if (tagSelectorRE.test(selector)) { return slice.call(element.getElementsByTagName(selector)); } else { return slice.call(attemptQuerySelectorAll(element, selector)); } } If you have the choice between two styles and one is more readable, choose that one.
  • 24.
    NESTED TERNARY OPERATORS.   AGAINST: It's hard to debug the nested statements.   FOR: You can debug them with breakpoints.   AGAINST: You can't always use fancy debuggers. See: Android, IE
  • 25.
    NESTED TERNARY OPERATORS.   FOR: They perform better than if/else.   AGAINST: Compilers convert if/else to ternary. Try in UglifyJS or Closure Compiler.
  • 26.
    STEP 3: OK,ALL WORKING. TIME TO INTEGRATE WITH MY WORKFLOW. Try your code with popular JS build tools ● JSHint ● Closure Compiler / UglifyJS ● Grunt   If it requires options, specify them or inline them into the code.
  • 27.
    UNSPECIFIED JSHINT OPTIONS.   [jshint]Error(s) in ./application/static/js/libs/bootstrap.js: Missing semicolon. (line: 24, character: 5) > "use strict" Comma warnings can be turned off with 'laxcomma' (line: 31, character: 9) > , thisStyle = thisBody.style Bad line breaking before ','. (line: 30, character: 48) > var thisBody = document.body || document.documentElement ... Too many errors. (8% scanned). (line: 139, character: 73) /*jshint asi:true, laxbreak:true, laxcomma:true, expr:true, boss:true */
  • 28.
    STEP 4: WHEE,IT WORKS, NOW I JUST WANNA SUBMIT THIS ONE CHANGE. Make it easy to build.   Make the tests easy to run.   ...use familiar tools and languages.
  • 29.
  • 30.
    SO, IF YOU'REA LIBRARY... WHICH ONE DO YOU WANT TO BE? elite approachable vs. AUTHORS USERS USERS AUTHORS