Professional JavaScript
AntiPatternsBy Mike WilcoxFebruary 2014
if (target) { if (target.targetParent.targetParent) { target.targetParent.targetParent.targetParent = new Event(this); } else { target.targetParent.targetParent = new Event(this); } return target;}
This code sucks!
this.graphs = utilities.settingArrayPropertyProxy( settings.graphs,
function (index, newGraph) {var currentLimits = chartEngine.getActualLimits(), newLimits;chartEngine.addGraph(newGraph, index);chartEngine.render();
},function (newGraph, oldGraph) {
result = chartEngine.updateGraph(newGraph, oldGraph);chartEngine.setLimits(self.xAxis().limits());chartEngine.render();
},function (index, removed) {
chartEngine.setLimits(self.xAxis().limits());chartEngine.render();
},function (setting) {return new Graph(self, chartEngine, setting);
},false);
WTF??
Mike Wilcox
Works there.
Committer!
Natch!
JavaScript ES5HTML5
CSS3UI/UX architecture
Graphic Design
But more importantly...Brendan Eich
me
Knighted methe
Noble Architect*
*not strictly true
What do I know??Seriously…
Being a committer to the Dojo Toolkit JavaScript library means not only having your code and style scrutinized by know-it-all prima donnas, open source experts, but your results also get used by thousands of hacks that expect magic developers who expect quality code.
AntiMatter
In particle physics, antimatter is material composed of antiparticles, which have the same mass as particles of
ordinary matter but have the opposite charge.
Encounters between particles and antiparticles lead to the annihilation of both
AntiPatternA commonly used process, structure or pattern of action that despite initially appearing to be an appropriate and effective response to a problem, typically has more bad
consequences than beneficial results.
Encounters between patterns and antipatterns
lead to the annihilation of both
What to ExpectDiscuss paradigms and patterns.
Good and bad.
Some patterns. Not all.
Focus on communications between components.
Three words:Maintainability, maintainability, maintainability
What is a Pattern?
A design pattern is a general reusable solution to a commonly occurring problem
Gang of Four
Addy Osmani
IntroductionIt is more difficult to build maintainable code in JavaScript than languages such as Java or C# due to its dynamic nature and its original intent to be a simple language with little more to do than validate forms.
There is nothing built in to the language to assist in the construction of large apps: no classes, no modules, no packages.
There is little to no formal developer training in JavaScript
The result of this is programmers attempt to solve coding problems and end up with AntiPatterns
Hypothetical App
The Chart class is the API and main controller.
The Graph class controls the Axes and Series (which draws the lines)
There can be multiple Graphs in a Chart.
There can be multiple Series in a Graph.
Let’s architect a chart application. The structure and functionality should look like:
Chart App
chart engine renderer
graph x axis y axis
series layer painter(s)
Here are the main components of our hypothetical chart app below. How do you think they should be wired up?
Chart App Structure
An example of how circuitous the code path can get if the structure is not carefully considered before hand.
Proxies are to hide
the mess below
chart engine renderer
graph x axis y axis
series layer painter(s)
graph-proxy series-proxy layer-proxy
Chart App Structure
chart engine renderer
graph x axis y axis
series layer painter(s)
When carefully considered, the code flow might look more like below. It may take a little extra code to get to this, but it would be worth it for a result that is easier to follow.
Code FlowMeets acceptance criteria
Done!
Clean
Readable
Maintainable
Extensible
Testable
The quick way is often not the correct way
Causes of Bad Architecture
Management AntiPatternsSlave Driving Management
Sales Driven Development
Waterfall Development
Hiring Practices
Developer AntiPatternsNot enough experience
The lack of working with others
Too much solo programming
Not enough collaboration
Just damn lazy
When you find a problem fix it, don't just drop a TODO
Super Star ProgrammersSmart kids with no life that work 70 hours and generate layers upon layers of code that works... but lacks best practices, code reviews, comments, tests, and sometimes even reason
Code AntiPatterns
ProceduralUsing nothing but functions is the very definition of spaghetti code
jQuery encourages procedural… and $paghetti !
Good for a web "page", not good for a web "app"
If you’re not doing some sort of OOP, you will have a massive climb toward a maintainable large application
Easier for the developer to comprehend small objects, rather than large routines
… or what I have dubbed: SOOP
Too much inheritance
Inheritance breaks encapsulation because the internals of parent classes are visible to subclasses
Tends to counterintuitively create tight coupling
Makes for a very difficult-to-follow API when most of the code is not in the current file
Making classes work together without stepping on methods, etc gets complex
Spaghetti Object Oriented Programming
FilteringSelect MappedTextBox ValidationTextBox TextBox _FormValueWidget _FormWidget _Widget _TemplatedMixin _CssStateMixin _FormWidgetMixin _FormValueMixin _FormWidgetMixin _TextBoxMixin ComboBoxMixin _HasDropDown _FocusMixin _WidgetBase Stateful Destroyable _AutoCompleterMixin SearchMixin
Even MOAH SOOPOver-inheritance is one of the current problems with Dojo
I needed to inspect this code for what the server query should look like. What file do you think that was in?
MOAR SOOPInconsistent object methods
Violation of the Composition pattern
chart.setRect({}); graph.rect({}); settings.rect = {}; series.dimensions({});
Too much functionality
9000-line class files defeat the whole purpose of OOP
Lack of encapsulation
foo.bar.bop.hop.doThing();
Even worse for setting properties
Abstractions / ProxiesDon't do it. This ain't Java.
More overhead than direct coding, wasting precious bytes
Because it is not native to the language, there is a danger of creating an unmaintainable abstraction
Alternative:
Comment your damn code.
Smaller classes
CallbacksYes, callbacks can be an anti-pattern
Even simple callbacks as an argument is weak: foo(x, onDone);
Passing a callback more than once quickly gets messy
You may have heard of… callback hell.
Puts ownership of the callback in the wrong object, which needs to check for existence
We have more modern techniques like Promises*
*Although most implementations suck
Getters and Setters
Potential for crazy side effects
The DOM has it wrong
Not just an anti-pattern, getters and setters are
Class = {get x(){
this.node.style.left = x + ‘px’;this.name = null;delete this.age;document.title = ‘My Crazy Page’;
}};
EVIL!
Private PropertiesCan’t use the debugger to inspect objects
Can cause major problems in inheritance
Uses extra memory (in object instances)
Especially true in library code
Business code is less critical, but then… what are you hiding, and from whom?
http://clubajax.org/javascript-private-variables-are-evil/
Suggest the underscore convention or an interface to emphasize public API
EVIL!
Properties
In ref to setting from an external source
The state of a class should be handled by the class
If multiple objects are setting a property and something goes wrong, you don’t know who dunnit
Settings objects and arrays may lose fields
Accessing a property may not be in sync with the DOM, or necessitate overhead to keep them in sync
Now you may think I’ve gone off the rails. But...
MSDN Properties vs. Methods
Okay, properties aren’t evil. But you should consider when and how you use them.
EVIL!
Refactoring
Why Refactor
All projects at some point need to pay down technical debt
Eventually more time will be spent servicing the debt than on increasing value
Accumulated technical debt becomes a major disincentive to work on a project
Doing things quick and dirty sets us up with a technical debt, which is similar to a financial debt, which incurs interest payments: the extra effort to do in future development
Concepts courtesy of Jeff Atwood, Coding Horror
When to Refactor
It's been six months and you feel you are smarter now
You read about a cool pattern on Hacker News
You found a cool jQuery plugin that has some sweet chaining
You want to completely rewrite the project because the last dev used spaces instead of tabs
Never.
Reasons we refactor:
When your boss lets you refactor:
How to Refactor
For small refactors, do them as you goFor medium refactors, hide them in tasksFor large refactors, use git, work in a branchMajor rewrites should be done in chunks
Don’t panic, do a little at a time, even if meaningless
Reorganize
Run tests often
ABRAlways Be Refactoring
As a project grows (and all projects grow) there will be a continual need to refactor
Solutions
require.jsYou are using it, aren’t you?
Global namespaces are SO 2011
Globals can clash with other code, they are slow to access, and they do not garbage collect
require.js provides a mini-framework, enforcing packages, namespaces, and modules
Also consider:
Browserify - CommonJS
uRequire - UMD
Frameworks
Dojo
Ember
Angular
ExtJS
YUI
Marionette
Good for teams with less experience
But don’t get too excited. Even with the
magical MVC patternframeworks will only get you
part way there.
Coupling
Tight LooseContent coupling
When one module modifies or relies on the internal workings of another module
Common couplingWhen two modules share the same global data
External couplingWhen two modules share an externally imposed data format, communication protocol, or device interface.
Control couplingOne module controlling the flow of another, by passing it information
Data-structured couplingWhen modules share a composite data structure
Data couplingWhen modules share data through, for example, parameters. Each datum is an elementary piece, and these are the only data shared
Message couplingComponent communication is done via parameters or message passing
No couplingModules do not communicate at all with one another.
Encapsulation my boy...Encapsulation is a form of data hiding; data, methods, and properties. Only the interface or API is exposed.
The primary goal:Easier for the developer to comprehend small objects vs. large routines
But also:Ownership of its own concepts, data, and functionalityBreaks up functionality into smaller objectsIt reduces the mystery of which module owns what
Object Composition: no internal details of composed objects need be visible in the code using them
Two or three inheritance levels, or, very small changes
Subclass Coupling
The child is connected to its parent, but the parent is not connected to the child
For a child to talk to its parent violates a basic programming principle of high and low level functionality
Proper Object Oriented Programming*
*I have not consider any acronyms for this
TDD
Reduce dependenciesEncapsulate functionalityCreate an intuitive interfaceKeep the logic simple
Though multiple simple functions may be complex
The Failures of "Intro to TDD"
Proper Test Driven Development means writing the tests first - forcing you to consider the consequences before you commit to them. You’ll find ways to:
Naming and ArrangingDon't expect to cram it all into preconceived model-view-controller folders, it's too limiting, and the app will grow out of it
Remember, the code path flows down, not up, so the deeper modules should not have access to root or higher level modules
Business LogicAs much as possible, business logic should be as separate from application logic, and especially the UI
business
application
UI
low level functionality
Even if this means extra code is needed to maintain the
separation
Important Patterns
! pubsub.publish('/module/loaded', {success:true});
! pubsub.subscribe('/module/loaded', function(object){! ! console.log('load success:', object.success);! });
pubsubPros:
Library code is very simpleCan access distant areas of app
Cons:Not guaranteed - pub can fire before sub is readyCan be hard to follow code paths between unrelated modules Over-use can lead to bad habits and sloppy codeShould only be used with very loosely coupled objects
ObservablesLike events that you can setSimilar to event streams in that they fire on change, and you can also read the current property
myName = obeservable('Mike');console.log(myName); // Mikeobeservable('Bob');console.log(myName); // BobmyName.subscribe(function(value){! if(value !== 'Mike'){! ! console.log('Hey! My name is not ', value);! }});
Template Behaviors
Attach DOM behavior to the templateSeparates behavior from widgetFurther decouples codeA good way to keep business logic separate from DOM logicHelps for unit testingMVVM is a good TDD solution
<div id='menu' data-bind='ko.menu'></div>
ko.bind(‘#menu’, widgetWithMenu);
PluginsUse the concept for separating codeVarious patterns for making a module pluggabledepends on codePlugin may need intimate knowledge of host objectImportance is separation of concepts for maintainabilityPlugins aren't usually portable to other host objects anyway
dataLoader = {! ! onDataLoaded: function(data){! ! ! this.emit('dataloaded', data);! ! }}
function onDataLoaded(data){! ! console.log(data);}dataLoader.on('dataloaded', onDataLoaded);
Event EmittersThe new hotnessMultiple connections can be madeIntent is clear - obvious they are events and what they doMuch better pattern than pubsub for objects that relate
graph = {! removeSeries: function(serieId){! ! var serieData = this.serieMap[serieId].data.clone();! ! this.serieMap[serieId].destroy();! ! return serieData;! }}chart = {! removeSeries: function(seriesId){! ! var graph = this.findSerieOwner();! ! var seriesData = graph.removeSeries(seriesId);! ! engine.removeSeriesData(seriesData);! ! renderer.render();! }};chart.removeSeries(seriesId);
Event Versatility
Although this code is not bad, it is rigid. To operate on the series object, access always needs to be through the top of the hierarchy: the chart.
Without the use of events, this logic is necessary for methods to fire in the correct order.
Event Versatilityseries = {! remove: function(){! ! this.emit('remove-series', this);
this.destroy();! }};graph = {! on('remove-series', function(series){! ! delete this.seriesMap[series.id];! }, this);};chart = {! on('remove-series', function(series){! ! engine.removeSeriesData(series.data);! ! renderer.render();! }, this);};series = chart.getSeriesById(serieId);series.remove();series.isSelected(true);series.addData([4,5,6]);
Here, remove() can be called directly on the series, from anywhere inside or outside of the chart, and events will bubble up in the correct order.
This also makes sense semantically, as series methods are called on the series, and not the chart.
Conclusion
Refactor!
Little projects always become BIG projects
You will never have all the information up front
Sales can turn your app into Frankenstein
Practice defensive coding - build it knowing it will change
Use best practices and your code will be maintainable - and more enjoyable to work with
In spite of what you might think, you don’t have the time to NOT do it right the first time
ABRAlways Be Refactoring
http://www.slideshare.net/anm8tr
http://www.meetup.com/Club-AJAX/
http://clubajax.org/
https://twitter.com/clubajax