jquery & 10,000 global functions: working with legacy javascript

40
JQUERY & 10,000 GLOBAL FUNCTIONS Working with Legacy JavaScript

Upload: guy-royse

Post on 13-Apr-2017

654 views

Category:

Technology


2 download

TRANSCRIPT

Page 1: jQuery & 10,000 Global Functions: Working with Legacy JavaScript

JQUERY & 10,000 GLOBAL FUNCTIONS

Working with Legacy JavaScript

Page 2: jQuery & 10,000 Global Functions: Working with Legacy JavaScript

WHO IS THIS GUY?

@guyroyse [email protected]

guyroyse.com

Page 3: jQuery & 10,000 Global Functions: Working with Legacy JavaScript
Page 4: jQuery & 10,000 Global Functions: Working with Legacy JavaScript

Presentation Business Logic Datastore

Page 5: jQuery & 10,000 Global Functions: Working with Legacy JavaScript

Presentation Business Logic Data Access

Page 6: jQuery & 10,000 Global Functions: Working with Legacy JavaScript

Our Concern The Mapping Their Concern

Page 7: jQuery & 10,000 Global Functions: Working with Legacy JavaScript

Presentation Business Logic Data Access

Page 8: jQuery & 10,000 Global Functions: Working with Legacy JavaScript

WHAT DOES THIS HAVE TO

DO WITH JAVASCRIPT?

Page 9: jQuery & 10,000 Global Functions: Working with Legacy JavaScript

STRATEGY VS DRIFT

Page 10: jQuery & 10,000 Global Functions: Working with Legacy JavaScript

<html><head><meta charset="utf-8"><meta name="viewport" content="width=device-width, initial-scale=1.0;"><title>GuyRoyse.com | Blog</title><link rel="icon" type="image/jpg" href="/images/alien-sunrise-icon.png"><link rel="stylesheet" href=“/site.css">

<script>

💩 </script>

</head>

<body>…

</body></html>

Page 11: jQuery & 10,000 Global Functions: Working with Legacy JavaScript

SOLUTION #1: WE NEED A FRAMEWORK!

Page 12: jQuery & 10,000 Global Functions: Working with Legacy JavaScript

SOLUTION #2: WE NEED A REWRITE!A new tiger team is selected. Everyone wants to be on this team because it’s a green-field project. They get to start over and create something truly beautiful. But only the best and brightest are chosen for the tiger team. Everyone else must continue to maintain the current system.

Now the two teams are in a race. The tiger team must build a new system that does everything that the old system does. Not only that, they have to keep up with the changes that are continuously being made to the old system. Management will not replace the old system until the new system can do everything that the old system does.

This race can go on for a very long time. I’ve seen it take 10 years. And by the time it’s done, the original members of the tiger team are long gone, and the current members are demanding that the new system be redesigned because it’s such a mess.

Martin, Robert C. (2008-08-01). Clean Code: A Handbook of Agile Software Craftsmanship (p. 5). Pearson Education (USA). Kindle Edition.

Page 13: jQuery & 10,000 Global Functions: Working with Legacy JavaScript

THE REAL SOLUTION

Page 14: jQuery & 10,000 Global Functions: Working with Legacy JavaScript

Untestable Code

Testable Code

Tested Code

TDD

Very Careful Refactor

Characterization Tests

Failing Test

Page 15: jQuery & 10,000 Global Functions: Working with Legacy JavaScript

WHAT’S A 27-LETTER WORD FOR “CORN”?

Page 16: jQuery & 10,000 Global Functions: Working with Legacy JavaScript

THE SIZZLE

I have code in the HTML or in an associated JavaScript file that does some sort of fancy UI thing in a dated way.

Problem

Solution Refactor to use modern CSS and test manually. There is very little point in putting automated tests around visual effects.

Page 17: jQuery & 10,000 Global Functions: Working with Legacy JavaScript

index.html<script>function addDropShadow(tag) {tag.style.backgroundColor = "black";

}

function removeDropShadow(tag) {tag.style.backgroundColor = "";

}</script>

<a onmouseenter="addDropShadow(this)" onmouseleave=“removeDropShadow(this)"href="somepage.jsp">Rollover Test</a>

index.html<style>.rollover:hover {background-color: black;

}</style>

<a class="rollover">Rollover Test</a>

Page 18: jQuery & 10,000 Global Functions: Working with Legacy JavaScript

CODE IN THE HTML

I have code in the HTML. It cannot be loaded into a test harness to test it.

Problem

Solution Move code a .js file and add a script tag if needed. Surround code in question with a test if possible or extract a method and test that. If there is code in event attributes in the HTML, used jQuery to bind to those events instead.

Page 19: jQuery & 10,000 Global Functions: Working with Legacy JavaScript

index.html<script>function doAwesomeThing() {thing.doAwesomeStuff("Awesome App is Awesome");

}</script><a onclick="doAwesomeThing()">Awesome Thing</a>

index.html<script src="awesome.js"></script><a id="awesomeThing">Awesome Thing</a>

awesome.js$('#awesomeThing').on('click', onAwesomeThingClicked);function onAwesomeThingClicked() {thing.doAwesomeStuff("Awesome App is Awesome");

}

awesome-spec.jsdescribe("awesome thing", function() {beforeEach(function() {spyOn(thing, 'doAwesomeStuff');

});it("does awesome stuff when clicked", function() {onAwesomeThingClicked();expect(thing.doAwesomeStuff).toHaveBeenCalledWith('Awesome App is Awesome');

});});

Page 20: jQuery & 10,000 Global Functions: Working with Legacy JavaScript

DOM INTERACTION

A lot of the code interacts with the DOM and I don’t have a DOM in my unit test.

Problem

Solution You can have a DOM in your unit tests. Mock out HTML elements using Jasmine jQuery. Use jQuery to trigger events and to validate that the DOM is modified in the expected way.

Page 21: jQuery & 10,000 Global Functions: Working with Legacy JavaScript

index.html<script src="awesome.js"></script><a id="awesomeThing">Awesome Thing</a><div id="toBeAwesome"/>

awesome.js$('#awesomeThing').on('click', onAwesomeThingClicked);

function onAwesomeThingClicked() {$('#toBeAwesome').html("Awesome App is Awesome");

}

awesome-spec.jsdescribe("awesome thing", function() {

beforeEach(function() {setFixtures('<div id="toBeAwesome"/>');

});

it("does awesome stuff when clicked", function() {onAwesomeThingClicked();expect($('#toBeAwesome')).toHaveHtml('Awesome App is Awesome');

});

});

Page 22: jQuery & 10,000 Global Functions: Working with Legacy JavaScript

10,000 GLOBAL FUNCTIONS

I have functions everywhere, man!Problem

Solution Put a test around the global function. Move the global function to a Module and replace the global function with a variable that points to the Module. Later, when no more code is accessing the global, remove the global variable.

Page 23: jQuery & 10,000 Global Functions: Working with Legacy JavaScript

app.jsfunction transmogrify() {};function transmute() {};function transform() {};

app.jsvar Mutators = {};Mutators.transmogrify = function() {};Mutators.transmute = function() {};Mutators.transform = function() {};

var transmogrify = Mutators.transmogrify();var transmute = Mutators.transmute();var transform = Mutators.transform();

app-spec.jsdescribe("Mutators.transform", function() {

it("has mapping to legacy function", function() {expect(transform).toBe(Mutators.transform);

});

it("does stuff", function() {expect(Mutators.transform()).toBe(true);

});

});

Page 24: jQuery & 10,000 Global Functions: Working with Legacy JavaScript

NO UNITS

There are no units to test.Problem

Solution Carefully extract a function and place tests around that function. Be careful not to create a 10,000 Global Functions problem.

Page 25: jQuery & 10,000 Global Functions: Working with Legacy JavaScript

app.js$(function() {var id = $('#id').val();$.get('data/' + id, function(data) {$('#name').val(data.name);$('#rank').val(data.rank);$('#serialNo').val(data.serial);

});});

app.js$(function() {fetchAndUpdateDom();

});

function fetchAndUpdateDom() {var id = $('#id').val();$.get('data/' + id, function(data) {updateDom(data);

});};

function updateDom(data) {$('#name').val(data.name);$('#rank').val(data.rank);$('#serialNo').val(data.serial);

}

Page 26: jQuery & 10,000 Global Functions: Working with Legacy JavaScript

JQUERY EVERYWHERE

I have jQuery calls inline everywhere in my code.Problem

Solution Using the Module pattern, migrate the jQuery that interacts with the DOM into Passive Views. Migrate the jQuery that makes AJAX calls into Adapters. Place tests around these Modules.

Page 27: jQuery & 10,000 Global Functions: Working with Legacy JavaScript

app.jsfunction fetchAndUpdateDom() {var id = $('#id').val();$.get('data/' + id, function(data) {$('#name').val(data.name);$('#rank').val(data.rank);$('#serialNo').val(data.serial);

});};

app.jsvar view = {id : function() { return $('#id').val(); },name : function(val) { $('#name').val(val); },rank : function(val) { $('#rank').val(val); },serialNo : function(val) { $('#serialNo').val(val); }

};

var data = {fetch : function(id, callback) {$.get('data/' + id, callback);

}};

function fetchAndUpdateDom() {data.fetch(view.id(), function(data) {view.name(data.name); view.rank(data.rank); view.serialNo(data.serialNo);

});}

Page 28: jQuery & 10,000 Global Functions: Working with Legacy JavaScript

REALLY UGLY FOR LOOPS

These nested for loops are hideous!Problem

Solution Use Underscore, lodash, or the built in array functions like forEach, find, reduce, and map.

Page 29: jQuery & 10,000 Global Functions: Working with Legacy JavaScript

app.jsvar values = [1,2,3,4,5], sum = 0, evens = [], doubles = [], i;

for(i = 0; i < values.length; i++) {sum += values[i]

}

for(i = 0; i < values.length; i++) {if (values[i] % 2 == 0) evens.push(values[i]);

}

for(i = 0; i < values.length; i++) {doubles.push(values[i] *2);

}

app.jsvar values = [1,2,3,4,5];

var sum = values.reduce(function(prev, value) {return prev + value;

}, 0);

var evens = values.filter(function(value) {return value % 2 === 0;

});

var doubles = values.map(function(value) {return value * 2;

});

Page 30: jQuery & 10,000 Global Functions: Working with Legacy JavaScript

REALLY LONG FUNCTIONS

This function is too long!Problem

Solution Place a test around the function and refactor it to a Revealing Module.

Page 31: jQuery & 10,000 Global Functions: Working with Legacy JavaScript

app.jsfunction doMaths(x, y) {var add = x + y;var sub = x - y;var multi = x * y;var div = x / y;return { add: add, sub: sub, multi: multi, div: div };

};

app.jsvar mather = (function() {var _x, _y;

function doMaths(x, y) {_x = x; _y = y;return { add: add(), sub: sub(), multi: multi(), div: div() };

}

function add() { return _x + _y; }function sub() { return _x - _y; }function multi() { return _x * _y; }function div() { return _x / _y; }

return { doMaths : doMaths };

})();

mather.doMaths(x, y);

Page 32: jQuery & 10,000 Global Functions: Working with Legacy JavaScript

ALMOST DUPLICATION

I have all these functions that do almost the same thing but I can’t break them down.

Problem

Solution Use a Lambda Factory to create the functions for you.

Page 33: jQuery & 10,000 Global Functions: Working with Legacy JavaScript

app.jsfunction getFirstNumberTimes(x) { return Number($('#first').val()) * x; }function getSecondNumberTimes(x) { return Number($('#second').val()) * x; }function getThirdNumberTimes(x) { return Number($('#third').val()) * x; }

app.jsfunction getFirstNumberTimes(x) { return getNumberTimes('#first', x); }function getSecondNumberTimes(x) { return getNumberTimes('#second', x); }function getThirdNumberTimes(x) { return getNumberTimes('#third', x); }

function getNumberTimes(id, x) {return Number($(id).val()) * x;

}

app.jsfunction getTimesFn(id) {return function(x) {var val = $(id).val();return Number(val) * x;

};}

var getFirstNumberTimes = getTimesFn('#first');var getSecondNumberTimes = getTimesFn('#second');var getThirdNumberTimes = getTimesFn('#third');

Page 34: jQuery & 10,000 Global Functions: Working with Legacy JavaScript

SIDE-EFFECTS

When testing an object, I get side effects.Problem

Solution This is really just the problem of using globals. JavaScript’s dynamic nature makes global objects easy to implement. Avoid this and create Factory Functions instead of global Modules.

Page 35: jQuery & 10,000 Global Functions: Working with Legacy JavaScript

app.jsvar statefullThing = (function() {var _name = "Alice";function getName() { return _name; }function setName(name) { _name = name; }

return { getName: getName, setName: setName };})();

app-spec.jsdescribe("statefulThing", function() {

var subject = statefullThing;

it("has a settable name", function() {subject.setName('Bob');expect(subject.getName()).toBe('Bob');

});

it("has expected default name", function() {expect(subject.getName()).toBe('Alice');

});

});

app.jsfunction statefuleThingFactory() {var _name = "Alice";function getName() { return _name; }function setName(name) { _name = name; }

return { getName: getName, setName: setName };};

var statefulThing = statefulThingFactory();

app-spec.jsdescribe("statefulThing", function() {

var subject;

beforeEach(function() {subject = statefulThingFactory();

});

it("has a settable name", function() {subject.setName('Bob');expect(subject.getName()).toBe('Bob');

});

it("has expected default name", function() {expect(subject.getName()).toBe('Alice');

});

});

Page 36: jQuery & 10,000 Global Functions: Working with Legacy JavaScript

PYRAMIDS OF DOOM

My AJAX calls are nested really deeply and I have some timing issues with them.

Problem

Solution Use Promises to remove the nesting and force the call order to what is needed.

Page 37: jQuery & 10,000 Global Functions: Working with Legacy JavaScript

app.js$.get('orders.json', function(orders) {var order = findOrder(orders);updateDom(order);$.get('items/' + order.id, function(items) {var item = findItem(items);updateDom(item);$.get('details/' + item.id, function(details) {udpateDom(details);

});});

});

app.jsget('orders.json').then(function(orders) {var order = findOrder(orders);updateDom(order);return get('items/' + order);

}).then(function(items) {var item = findItem(items);updateDom(item);return get('details/' + item.id);

}).then(function(details) {updateDom(details);

});

Page 38: jQuery & 10,000 Global Functions: Working with Legacy JavaScript

app.jsfunction get(url) {var deferred = Q.defer();$.get({url : url,success: function(data) {return deferred.resolve(data);

},error: function(xhr, status, error) {return deferred.reject(error);

}});return deferred.promise();

}

get('orders.json').then(function(orders) {var order = findOrder(orders);updateDom(order);return get('items/' + order);

}).then(function(items) {var item = findItem(items);updateDom(item);return get('details/' + item.id);

}).then(function(details) {updateDom(details);

});

Page 39: jQuery & 10,000 Global Functions: Working with Legacy JavaScript

NOT SURE IF I SHOULD ASK A QUESTION

OR JUST RUN FOR THE DOOR

Page 40: jQuery & 10,000 Global Functions: Working with Legacy JavaScript

THANKS!@guyroyse

[email protected] guyroyse.com