Upgrade to Pro — share decks privately, control downloads, hide ads and more …

The Plight of Pinocchio

The Plight of Pinocchio

JavaScript's quest to become a real language

JavaScript is no longer a toy language. Many of our applications can't function without it. If we are going to use JavaScript to do real things, we need to treat it like a real language, adopting the same practices we use with real languages.

This framework agnostic talk takes a serious look at how we develop JavaScript applications. Despite its prototypical nature, good object-oriented programming principles are still relevant. The design patterns that we've grown to know and love work just as well in JavaScript as they do any other language. Test driven development forces us to write modular, decoupled code.

Brandon Keepers

May 16, 2012
Tweet

More Decks by Brandon Keepers

Other Decks in Programming

Transcript

  1. PINOCCHIO
    the plight of
    JavaScript's quest to
    become a real language

    View full-size slide

  2. once upon a time...

    View full-size slide

  3. one fine day...

    View full-size slide

  4. we built businesses...

    View full-size slide

  5. ...on this toy language

    View full-size slide

  6. go to school, study, always
    tell the truth, and be obedient.

    View full-size slide

  7. If we are going to use
    JavaScript to do real work,
    then we have to do the
    things that we do with
    real languages.

    View full-size slide

  8. github.com/bkeepers
    @bkeepers

    View full-size slide

  9. in real languages, we...

    View full-size slide

  10. in real languages, we...
    Use Test Driven
    Development

    View full-size slide

  11. in real languages, we...
    Use Test Driven
    Development
    Refactor

    View full-size slide

  12. in real languages, we...
    Use Test Driven
    Development
    Refactor
    Separate
    Concerns

    View full-size slide

  13. in real languages, we...
    Use Test Driven
    Development
    Refactor
    Separate
    Concerns
    Create Abstractions

    View full-size slide

  14. in real languages, we...
    Use Test Driven
    Development
    Apply Design Patterns
    Refactor
    Separate
    Concerns
    Create Abstractions

    View full-size slide

  15. in real languages, we...
    Use Test Driven
    Development
    Apply Design Patterns
    Decouple
    Refactor
    Separate
    Concerns
    Create Abstractions

    View full-size slide

  16. in real languages, we...
    Use Test Driven
    Development
    Apply Design Patterns
    Decouple
    Refactor
    Separate
    Concerns
    Use Encapsulation
    Create Abstractions

    View full-size slide

  17. in real languages, we...
    Use Test Driven
    Development
    Apply Design Patterns
    Decouple
    Refactor
    Separate
    Concerns
    Use Encapsulation
    Create Abstractions
    DRY

    View full-size slide

  18. why?
    Use Test Driven
    Development
    Apply Design Patterns
    Decouple
    Refactor
    Separate
    Concerns
    Use Encapsulation
    Create Abstractions
    DRY

    View full-size slide

  19. These principles and practices
    help us build flexible and
    maintainable systems.

    View full-size slide

  20. Litmus Test
    If your site or application
    breaks without JavaScript,
    then you should treat it
    like a real language.

    View full-size slide

  21. Use Test Driven
    Development
    Apply Design Patterns
    in real languages, we

    View full-size slide

  22. the simplest app ever...
    github.com/bkeepers/monologue

    View full-size slide

  23. jQuery(function($) {
    });
    a typical JavaScript example...
    example lovingly stolen from @searls

    View full-size slide

  24. a typical JavaScript example...
    jQuery(function($) {
    $('#new-status').on('submit', function() {
    return false;
    });
    });
    example lovingly stolen from @searls

    View full-size slide

  25. a typical JavaScript example...
    jQuery(function($) {
    $('#new-status').on('submit', function() {
    $.ajax({
    url: '/statuses',
    type: 'POST',
    });
    return false;
    });
    });
    example lovingly stolen from @searls

    View full-size slide

  26. a typical JavaScript example...
    jQuery(function($) {
    $('#new-status').on('submit', function() {
    $.ajax({
    url: '/statuses',
    type: 'POST',
    dataType: 'json',
    data: {text: $(this).find('textarea').val()},
    });
    return false;
    });
    });
    example lovingly stolen from @searls

    View full-size slide

  27. a typical JavaScript example...
    jQuery(function($) {
    $('#new-status').on('submit', function() {
    $.ajax({
    url: '/statuses',
    type: 'POST',
    dataType: 'json',
    data: {text: $(this).find('textarea').val()},
    success: function(data) {
    }
    });
    return false;
    });
    });
    example lovingly stolen from @searls

    View full-size slide

  28. a typical JavaScript example...
    jQuery(function($) {
    $('#new-status').on('submit', function() {
    $.ajax({
    url: '/statuses',
    type: 'POST',
    dataType: 'json',
    data: {text: $(this).find('textarea').val()},
    success: function(data) {
    $('#statuses').append(
    '' + data.text + '');
    }
    });
    return false;
    });
    });
    example lovingly stolen from @searls

    View full-size slide

  29. Use Test Driven
    Development
    in real languages, we

    View full-size slide

  30. the evolution of testing...

    View full-size slide

  31. Developers initially
    view testing as a
    verification process.

    View full-size slide

  32. A good test suite
    quickly becomes an
    insurance policy.

    View full-size slide

  33. Test driven
    development is a
    design process.

    View full-size slide

  34. Growing Object-Oriented Software Guided by Tests
    Steve Freeman, Nat Pryce
    “Starting with a test means
    that we have to describe what
    we want to achieve before we
    consider how.”

    View full-size slide

  35. Test driven development in
    JavaScript is fast and easy.

    View full-size slide

  36. “ Testing JavaScript is
    not worth it.”

    View full-size slide

  37. “ Testing JavaScript is
    not worth it.”
    “It changes too
    frequently.”

    View full-size slide

  38. “ Testing JavaScript is
    not worth it.”
    “It changes too
    frequently.”
    “The tests break all
    the time.”

    View full-size slide

  39. “ Testing JavaScript is
    not worth it.”
    “It changes too
    frequently.”
    “It’s just view
    code.”
    “The tests break all
    the time.”

    View full-size slide

  40. “ Testing JavaScript is
    not worth it.”
    “It changes too
    frequently.”
    “Browser
    implementations
    are too different.”
    “It’s just view
    code.”
    “The tests break all
    the time.”

    View full-size slide

  41. “ Testing JavaScript is
    not worth it.”
    “It changes too
    frequently.”
    “Browser
    implementations
    are too different.”
    “It’s just view
    code.”
    “The tests break all
    the time.”
    “It’s too hard.”

    View full-size slide

  42. Testing is hard when
    we write bad code.

    View full-size slide

  43. jQuery(function($) {
    $('#new-status').on('submit', function() {
    $.ajax({
    url: '/statuses',
    type: 'POST',
    dataType: 'json',
    data: {text: $(this).find('textarea').val()},
    success: function(data) {
    $('#statuses').append('' + data.text + '');
    }
    });
    return false;
    });
    });
    back to our example...
    example lovingly stolen from @searls

    View full-size slide

  44. jQuery(function($) {
    $('#new-status').on('submit', function() {
    $.ajax({
    url: '/statuses',
    type: 'POST',
    dataType: 'json',
    data: {text: $(this).find('textarea').val()},
    success: function(data) {
    $('#statuses').append('' + data.text + '');
    }
    });
    return false;
    });
    });
    back to our example...
    1. page event
    example lovingly stolen from @searls

    View full-size slide

  45. jQuery(function($) {
    $('#new-status').on('submit', function() {
    $.ajax({
    url: '/statuses',
    type: 'POST',
    dataType: 'json',
    data: {text: $(this).find('textarea').val()},
    success: function(data) {
    $('#statuses').append('' + data.text + '');
    }
    });
    return false;
    });
    });
    back to our example...
    1. page event
    2. user event
    example lovingly stolen from @searls

    View full-size slide

  46. jQuery(function($) {
    $('#new-status').on('submit', function() {
    $.ajax({
    url: '/statuses',
    type: 'POST',
    dataType: 'json',
    data: {text: $(this).find('textarea').val()},
    success: function(data) {
    $('#statuses').append('' + data.text + '');
    }
    });
    return false;
    });
    });
    back to our example...
    1. page event
    2. user event
    3. network IO
    example lovingly stolen from @searls

    View full-size slide

  47. jQuery(function($) {
    $('#new-status').on('submit', function() {
    $.ajax({
    url: '/statuses',
    type: 'POST',
    dataType: 'json',
    data: {text: $(this).find('textarea').val()},
    success: function(data) {
    $('#statuses').append('' + data.text + '');
    }
    });
    return false;
    });
    });
    back to our example...
    1. page event
    2. user event
    3. network IO
    4. user input
    example lovingly stolen from @searls

    View full-size slide

  48. jQuery(function($) {
    $('#new-status').on('submit', function() {
    $.ajax({
    url: '/statuses',
    type: 'POST',
    dataType: 'json',
    data: {text: $(this).find('textarea').val()},
    success: function(data) {
    $('#statuses').append('' + data.text + '');
    }
    });
    return false;
    });
    });
    back to our example...
    1. page event
    2. user event
    3. network IO
    4. user input
    5. network event
    example lovingly stolen from @searls

    View full-size slide

  49. jQuery(function($) {
    $('#new-status').on('submit', function() {
    $.ajax({
    url: '/statuses',
    type: 'POST',
    dataType: 'json',
    data: {text: $(this).find('textarea').val()},
    success: function(data) {
    $('#statuses').append('' + data.text + '');
    }
    });
    return false;
    });
    });
    back to our example...
    1. page event
    2. user event
    3. network IO
    4. user input
    5. network event
    6. HTML templating
    example lovingly stolen from @searls

    View full-size slide

  50. Now try to test that.

    View full-size slide

  51. Testing makes
    design problems
    painfully obvious.

    View full-size slide

  52. “The same structure that
    makes the code difficult to
    test now will make it difficult
    to change in the future.”
    Growing Object-Oriented Software Guided by Tests
    Steve Freeman, Nat Pryce

    View full-size slide

  53. So how do we test this?

    View full-size slide

  54. Well...let’s talk about
    system testing.

    View full-size slide

  55. capybara
    js-test-driver
    buster.js

    View full-size slide

  56. system tests with cucumber...
    Feature: Telling the world about my every breath
    Scenario: Successfully posting a status update
    When I am on the home page
    And I fill in "Status" with "Integration tests are easy"
    And I press "Post Update"
    Then I should see "Integration tests are easy"

    View full-size slide

  57. system tests with cucumber...
    $ cucumber
    Feature: Posting Statuses
    Scenario: Successfully posting a status
    When I am on the home page
    And I fill in "Status" with "Integration tests are easy"
    And I press "Post Update"
    Then I should see "Integration tests are easy"
    1 scenario (1 passed)
    4 steps (4 passed)
    0m0.988s

    View full-size slide

  58. there are many advantages
    to system testing

    View full-size slide

  59. there are many advantages
    to system testing
    easy to write

    View full-size slide

  60. there are many advantages
    to system testing
    easy to write
    relatively easy to
    maintain

    View full-size slide

  61. there are many advantages
    to system testing
    easy to write run in real
    browser
    environment
    relatively easy to
    maintain

    View full-size slide

  62. there are many advantages
    to system testing
    easy to write run in real
    browser
    environment
    verifies system
    actually works
    relatively easy to
    maintain

    View full-size slide

  63. there are many disadvantages
    to system testing

    View full-size slide

  64. there are many disadvantages
    to system testing
    run very slow

    View full-size slide

  65. there are many disadvantages
    to system testing
    test failures are
    difficult to
    decipher
    run very slow

    View full-size slide

  66. there are many disadvantages
    to system testing
    harder to simulate
    failure cases
    test failures are
    difficult to
    decipher
    run very slow

    View full-size slide

  67. there are many disadvantages
    to system testing
    harder to simulate
    failure cases
    test failures are
    difficult to
    decipher
    run very slow
    does not
    encourage good
    design

    View full-size slide

  68. System tests tell you your
    application is functioning,
    but tell you nothing about
    the internal state.

    View full-size slide

  69. Me
    Just Now™
    “With system tests, write as
    few as possible, but no fewer.”

    View full-size slide

  70. Unit tests help ensure
    components are well designed
    and function properly.

    View full-size slide

  71. “To construct an object for a
    unit test, we have to pass its
    dependencies to it, which
    means that we have to know
    what they are.”
    Growing Object-Oriented Software Guided by Tests
    Steve Freeman, Nat Pryce

    View full-size slide

  72. “To keep unit tests understandable
    (and, so, maintainable), we have to
    limit their scope.”
    Growing Object-Oriented Software Guided by Tests
    Steve Freeman, Nat Pryce

    View full-size slide

  73. There are about 1,690,000
    different unit testing
    frameworks in JavaScript.

    View full-size slide

  74. Just pick one that
    looks healthy.

    View full-size slide

  75. a unit test in jasmine
    require('/js/views/status_list.js');
    describe("View.StatusList", function() {
    beforeEach(function() {
    this.view = new View.StatusList();
    });
    it("fetches records from the server", function() {
    expect(this.view.collection.fetch).toHaveBeenCalled();
    });
    it("renders when collection is reset", function() {
    this.view.collection.reset([{text: 'Unit testing is fun'}]);
    expect(this.view.$el.find('li').text()).toEqual('Unit testing is fun');
    });
    it("appends newly added items", function() {

    View full-size slide

  76. We can use unit
    tests to improve our
    design, but first…

    View full-size slide

  77. Apply Design
    Patterns
    in real languages, we

    View full-size slide

  78. Architect
    Christopher Alexander
    “Each pattern describes a problem
    that occurs over and over again in our
    environment, and then describes the
    core of the solution to that problem,
    in such a way that you can use this
    solution a million times over, without
    ever doing it the same way twice.”

    View full-size slide

  79. Design patterns are the basic parts
    of speech that allow us to create
    coherent, well-structured systems.

    View full-size slide

  80. Creational
    Abstract Factory
    Builder
    Factory Method
    Prototype
    Singleton
    Structural
    Adapter
    Bridge
    Composite
    Decorator
    Flyweight
    Proxy
    Behavioral
    Chain of responsibility
    Command
    Interpreter
    Iterator
    Mediator
    Memento
    Observer
    State
    Strategy
    Template method
    Visitor
    “Classical Design Patterns”

    View full-size slide

  81. Advantages of
    Design Patterns

    View full-size slide

  82. Advantages of
    Design Patterns
    removes duplication

    View full-size slide

  83. Advantages of
    Design Patterns
    removes duplication
    shared vocabulary

    View full-size slide

  84. Advantages of
    Design Patterns
    removes duplication
    shared vocabulary
    faster communication

    View full-size slide

  85. Advantages of
    Design Patterns
    removes duplication
    shared vocabulary
    generic, reusable
    components
    faster communication

    View full-size slide

  86. Advantages of
    Design Patterns
    removes duplication
    shared vocabulary
    generic, reusable
    components
    faster communication
    proven paradigms

    View full-size slide

  87. Advantages of
    Design Patterns
    removes duplication
    shared vocabulary
    generic, reusable
    components
    faster communication
    proven paradigms
    easier to test

    View full-size slide

  88. Advantages of
    Design Patterns
    removes duplication
    shared vocabulary
    generic, reusable
    components
    faster communication
    proven paradigms
    easier to test
    supports system changes

    View full-size slide

  89. Addy Osmani
    Learning JavaScript Design Patterns
    “Patterns don’t solve all design
    problems, nor do they replace
    good software designers,
    however, they do support them.”

    View full-size slide

  90. Model, View, Controller
    almost every web-related framework uses

    View full-size slide

  91. Martin Fowler
    http://www.martinfowler.com/
    “The idea behind [MVC] is to
    make a clear division between
    domain objects that model our
    perception of the real world,
    and presentation objects that
    are the GUI elements we see
    on the screen.”

    View full-size slide

  92. Martin Fowler
    http://www.martinfowler.com/
    “...Domain objects should be
    completely self contained and
    work without reference to the
    presentation, they should also
    be able to support multiple
    presentations, possibly
    simultaneously.”

    View full-size slide

  93. Model
    domain or structural
    objects representing
    the application’s state

    View full-size slide

  94. If your application has
    data or logic, then
    you need a model.

    View full-size slide

  95. jQuery(function($) {
    $('#new-status').on('submit', function() {
    $.ajax({
    url: '/statuses',
    type: 'POST',
    dataType: 'json',
    data: {text: $(this).find('textarea').val()},
    success: function(data) {
    $('#statuses').append(
    '' + data.text + '');
    }
    });
    return false;
    });
    });
    back to our example...

    View full-size slide

  96. jQuery(function($) {
    $('#new-status').on('submit', function() {
    $.ajax({
    url: '/statuses',
    type: 'POST',
    dataType: 'json',
    data: {text: $(this).find('textarea').val()},
    success: function(data) {
    $('#statuses').append(
    '' + data.text + '');
    }
    });
    return false;
    });
    });
    back to our example...

    View full-size slide

  97. refactoring to use models...
    // models/status.js
    Model.Status = Backbone.Model.extend({
    });
    // collections/statuses.js
    Collection.Statuses = Backbone.Collection.extend({
    model: Model.Status,
    url: '/statuses'
    });

    View full-size slide

  98. and the result...
    // app.js
    jQuery(function($) {
    var statuses = new Collection.Statuses();
    $('#new-status').on('submit', function() {
    statuses.create({
    text: $(this).find('textarea').val()
    });
    return false;
    });
    statuses.on('add', function(status) {
    $('#statuses').append(
    '' + status.get('text') + '');
    });
    });

    View full-size slide

  99. and the result...
    // app.js
    jQuery(function($) {
    var statuses = new Collection.Statuses();
    $('#new-status').on('submit', function() {
    statuses.create({
    text: $(this).find('textarea').val()
    });
    return false;
    });
    statuses.on('add', function(status) {
    $('#statuses').append(
    '' + status.get('text') + '');
    });
    });
    Observer Pattern

    View full-size slide

  100. Our models don’t have
    logic yet, so we’re skipping
    the unit test for now.

    View full-size slide

  101. but our system test still passes...
    $ cucumber
    Feature: Posting Statuses
    Scenario: Succesfully posting a status
    When I am on the home page
    And I fill in "Status" with "Integration tests are easy"
    And I press "Post Update"
    Then I should see "Integration tests are easy"
    1 scenario (1 passed)
    4 steps (4 passed)
    0m0.988s

    View full-size slide

  102. Controller
    translates user input
    into operations on the
    model

    View full-size slide

  103. back to our example...
    // app.js
    jQuery(function($) {
    var statuses = new Collection.Statuses();
    $('#new-status').on('submit', function() {
    statuses.create({text: $(this).find('textarea').val()});
    return false;
    });
    statuses.on('add', function(status) {
    $('#statuses').append(
    '' + status.get('text') + '');
    });
    });

    View full-size slide

  104. back to our example...
    // app.js
    jQuery(function($) {
    var statuses = new Collection.Statuses();
    $('#new-status').on('submit', function() {
    statuses.create({text: $(this).find('textarea').val()});
    return false;
    });
    statuses.on('add', function(status) {
    $('#statuses').append(
    '' + status.get('text') + '');
    });
    });

    View full-size slide

  105. test driven refactoring...
    describe("PostStatus", function() {
    describe("submitting the form", function() {
    it("creates a status", function() {
    });
    });
    });

    View full-size slide

  106. test driven refactoring...
    describe("PostStatus", function() {
    describe("submitting the form", function() {
    it("creates a status", function() {
    $el.trigger('submit');
    });
    });
    });

    View full-size slide

  107. test driven refactoring...
    describe("PostStatus", function() {
    describe("submitting the form", function() {
    it("creates a status", function() {
    $el.trigger('submit');
    expect(collection.create).toHaveBeenCalledWith(
    {text: "It’s easy!"});
    });
    });
    });

    View full-size slide

  108. test driven refactoring...
    describe("PostStatus", function() {
    var $el;
    beforeEach(function() {
    $el = $("It's easy!");
    });
    describe("submitting the form", function() {
    it("creates a status", function() {
    $el.trigger('submit');
    expect(collection.create).toHaveBeenCalledWith(
    {text: "It’s easy!"});
    });
    });
    });

    View full-size slide

  109. test driven refactoring...
    describe("PostStatus", function() {
    var $el;
    beforeEach(function() {
    $el = $("It's easy!");
    });
    describe("submitting the form", function() {
    it("creates a status", function() {
    $el.trigger('submit');
    expect(collection.create).toHaveBeenCalledWith(
    {text: "It’s easy!"});
    });
    });
    });
    unattached from DOM

    View full-size slide

  110. test driven refactoring...
    describe("PostStatus", function() {
    var $el, collection;
    beforeEach(function() {
    $el = $("It's easy!");
    collection = new Collection.Statuses();
    });
    describe("submitting the form", function() {
    it("creates a status", function() {
    $el.trigger('submit');
    expect(collection.create).toHaveBeenCalledWith(
    {text: "It’s easy!"});
    });
    });
    });

    View full-size slide

  111. test driven refactoring...
    describe("PostStatus", function() {
    var $el, collection;
    beforeEach(function() {
    $el = $("It's easy!");
    collection = new Collection.Statuses();
    });
    describe("submitting the form", function() {
    it("creates a status", function() {
    $el.trigger('submit');
    expect(collection.create).toHaveBeenCalledWith(
    {text: "It’s easy!"});
    });
    });
    });
    do we need this test dependency?

    View full-size slide

  112. test driven refactoring...
    describe("PostStatus", function() {
    var $el, collection;
    beforeEach(function() {
    $el = $("It's easy!");
    collection = {create: jasmine.createSpy('create')};
    });
    describe("submitting the form", function() {
    it("creates a status", function() {
    $el.trigger('submit');
    expect(collection.create).toHaveBeenCalledWith(
    {text: "It’s easy!"});
    });
    });
    });

    View full-size slide

  113. test driven refactoring...
    describe("PostStatus", function() {
    var $el, collection, view;
    beforeEach(function() {
    $el = $("It's easy!");
    collection = {create: jasmine.createSpy('create')};
    view = new View.PostStatus({
    el: $el,
    collection: collection
    });
    });
    describe("submitting the form", function() {
    it("creates a status", function() {
    $el.trigger('submit');
    expect(collection.create).toHaveBeenCalledWith(

    View full-size slide

  114. test driven refactoring...
    describe("PostStatus", function() {
    var $el, collection, view;
    beforeEach(function() {
    $el = $("It's easy!");
    collection = {create: jasmine.createSpy('create')};
    view = new View.PostStatus({
    el: $el,
    collection: collection
    });
    });
    describe("submitting the form", function() {
    it("creates a status", function() {
    $el.trigger('submit');
    expect(collection.create).toHaveBeenCalledWith(
    dependency injection

    View full-size slide

  115. a failing test...

    View full-size slide

  116. refactoring to use controllers...
    // views/post_status.js
    View.PostStatus = Backbone.View.extend({
    events: {
    "submit": "submit"
    },
    submit: function(e) {
    var $input = this.$el.find('textarea');
    this.collection.create({text: $input.val()});
    return false;
    }
    });

    View full-size slide

  117. and the result...
    // app.js
    jQuery(function($) {
    var statuses = new Collection.Statuses();
    new View.PostStatus({
    el: $('#new-status'),
    collection: statuses
    });
    statuses.on('add', function(status) {
    $('#statuses').append(
    '' + status.get('text') + '');
    });
    });

    View full-size slide

  118. and our system test still passes...
    $ cucumber
    Feature: Posting Statuses
    Scenario: Succesfully posting a status
    When I am on the home page
    And I fill in "Status" with "Integration tests are easy"
    And I press "Post Update"
    Then I should see "Integration tests are easy"
    1 scenario (1 passed)
    4 steps (4 passed)
    0m0.988s

    View full-size slide

  119. View
    observes the model’s
    state and generates
    output for the user

    View full-size slide

  120. // app.js
    jQuery(function($) {
    var statuses = new Collection.Statuses();
    new View.PostStatus({
    el: $('#new-status'),
    collection: statuses
    });
    statuses.on('add', function(status) {
    $('#statuses').append(
    '' + status.get('text') + '');
    });
    });
    back to our example...

    View full-size slide

  121. // app.js
    jQuery(function($) {
    var statuses = new Collection.Statuses();
    new View.PostStatus({
    el: $('#new-status'),
    collection: statuses
    });
    statuses.on('add', function(status) {
    $('#statuses').append(
    '' + status.get('text') + '');
    });
    });
    back to our example...

    View full-size slide

  122. test driven refactoring...
    describe("View.StatusList", function() {
    it("appends newly added items", function() {
    collection.add({text: 'this is fun!'});
    expect($el.find('li').length).toBe(1);
    expect($el.find('li').text()).toEqual('this is fun!');
    });
    });

    View full-size slide

  123. test driven refactoring...
    describe("View.StatusList", function() {
    beforeEach(function() {
    $el = $('');
    collection = new Backbone.Collection();
    view = new View.StatusList({
    el: $el,
    collection: collection
    });
    });
    it("appends newly added items", function() {
    collection.add({text: 'this is fun!'});
    expect($el.find('li').length).toBe(1);
    expect($el.find('li').text()).toEqual('this is fun!');
    });
    });

    View full-size slide

  124. refactoring to use views...
    // views/status_list.js
    View.StatusList = Backbone.View.extend({
    initialize: function() {
    _.bindAll(this, 'add');
    this.collection.on('add', this.add);
    },
    add: function(model) {
    this.$el.append(this.template(model))
    },
    template: function(model) {
    return this.make('li', {className:'status'},
    model.get('text'));
    }
    });

    View full-size slide

  125. and the result...
    // app.js
    jQuery(function($) {
    var statuses = new Collection.Statuses();
    new View.PostStatus({
    el: $('#new-status'),
    collection: statuses
    });
    new View.StatusList({
    el: $('#statuses'),
    collection: statuses
    });
    });

    View full-size slide

  126. Let’s add a new feature.

    View full-size slide

  127. adding new features, the cowboy way
    jQuery(function($) {
    $.ajax({
    url: '/statuses',
    dataType: 'json',
    success: function(data) {
    var $statuses = $('#statuses');
    for(var i = 0; data.length > i; i++) {
    $('#statuses').append(
    '' + data[i].text + '');
    }
    }
    });
    });

    View full-size slide

  128. but we’re better than that now...
    Feature: Telling the world about my every breath
    # ...
    Scenario: Viewing previous status updates
    Given the following statuses exist:
    | This is outside-in development |
    | It’s not so bad |
    When I am on the home page
    Then I should see "This is outside-in development"
    And I should see "It’s not so bad"

    View full-size slide

  129. failing system test...
    $ cucumber features/statuses.feature:9
    Feature: Posting Statuses
    Scenario: Viewing previous status updates
    Given the following statuses exist:
    | This is outside-in development |
    | It's not so bad |
    When I am on the home page
    Then I should see "This is outside-in development"
    expected there to be content "This is outside-in development" in "Post
    Update" (RSpec::Expectations::ExpectationNotMetError)
    And I should see "It's not so bad"
    Failing Scenarios:
    cucumber features/statuses.feature:9
    1 scenario (1 failed)
    4 steps (1 failed, 1 skipped, 3 passed)
    0m2.619s

    View full-size slide

  130. then a unit test...
    describe("View.StatusList", function() {
    // …
    it("fetches records from the server", function() {
    expect(collection.fetch).toHaveBeenCalled();
    });
    it("renders when collection is reset", function() {
    collection.reset([{text: 'This is fun!'}]);
    expect($el.find('li').length).toBe(1);
    expect($el.find('li').text()).toEqual('This is fun!');
    });
    });

    View full-size slide

  131. and finally, the implementation...
    // views/status_list.js
    View.StatusList = Backbone.View.extend({
    initialize: function() {
    this.collection.on('add', this.add);
    },
    add: function(model) {
    this.$el.append(this.template(model))
    },
    template: function(model) {
    return this.make('li', {className:'status'},
    model.get('text'));
    }
    });

    View full-size slide

  132. and finally, the implementation...
    // views/status_list.js
    View.StatusList = Backbone.View.extend({
    initialize: function() {
    this.collection.on('add', this.add);
    this.collection.on('reset', this.render);
    this.collection.fetch();
    },
    render: function() {
    this.collection.each(this.add);
    },
    add: function(model) {
    this.$el.append(this.template(model))
    },
    // ...
    template: function(model) {

    View full-size slide

  133. our top level code didn’t change...
    // app.js
    jQuery(function($) {
    var statuses = new Collection.Statuses();
    new View.PostStatus({
    el: $('#new-status'),
    collection: statuses
    });
    new View.StatusList({
    el: $('#statuses'),
    collection: statuses
    });
    });

    View full-size slide

  134. MVC is just one design
    pattern. There are many
    others that can help us.

    View full-size slide

  135. Epilogue
    before we depart, an

    View full-size slide

  136. Robert C. Martin
    Clean Code
    “Writing clean code requires the
    disciplined use of a myriad of little
    techniques applied through a
    painstakingly acquired sense of
    ‘cleanliness.’ ”

    View full-size slide

  137. single responsibility principle
    Every object should have a
    single responsibility, and that
    responsibility should be entirely
    encapsulated by the object.

    View full-size slide

  138. “Our heuristic is that we should
    be able to describe what an
    object does without using any
    conjunctions (‘and’, ‘or’).”
    Growing Object-Oriented Software Guided by Tests
    Steve Freeman, Nat Pryce

    View full-size slide

  139. responsibility & dependencies
    // Responsibility:
    // Create a status from user input
    //
    // Dependencies:
    // * el - A form element with a textarea
    // * collection - the collection that the status will be
    // created in
    View.PostStatus = Backbone.View.extend({
    // ...
    });

    View full-size slide

  140. Carlo Collodi
    Adventures of Pinocchio
    “One day I was about to become a
    boy, a real boy, but on account of
    my laziness and my hatred of
    books, and because I listened to
    bad companions…I awoke to find
    myself changed into a donkey–
    long ears, gray coat, even a tail!”

    View full-size slide

  141. Thanks!
    @bkeepers
    http://bit.ly/pinocchio-slides
    Example App:
    github.com/bkeepers/monologue

    View full-size slide