planning javascript for larger teams - draft & handout version
DESCRIPTION
This is the original draft of my talk at @mediaAjax, I discarded it as it is way too much for one hour :) Hopefully it still is of use for youTRANSCRIPT
Planning JavaScript and Ajax for larger teams
Christian Heilmann@media Ajax, London, November 2007
Achtung alles Lookenpeepers!Dies Machine is nicht fur gefingerpoken
und mittengraben. Is easy schnappen der springenwerk, blowenfusen und poppencorken mit spitzensparken. Is nicht fur gewerken by das dummkopfen. Das rubbernecken sightseeren keepen Cottenpickenen hands in das pockets - relaxen und Watch Das Blinken Lights.
Do not fiddle with other people’s knobs unless you know what you are doing.
Spoilers:– Some of the following advice will
seem very basic to you.– This is not based on me thinking
you need this.– It is meant as a reminder. Next
time you encounter these problems you won’t have to think about them.
… and Harry Potter dies, but comes back using a magic stone which is a third of the Deathly Hallows.
There is one main fatal mistake any developer
can make:
There is one main fatal mistake any developer
can make:
Make assumptions
“I don’t need to tell people that, they know
already.”
“Surely this has been done already, and by
people better than me.”
“This works right now, there won’t be a need to
change it.”
“This never worked in the past, it won’t work now.”
“We hack that now, and will get time later to fix
it.”
“This is a minor issue only for this instance, no need
to file a bug.”
All these mistakes are yours to make.
Sometimes they need to be made in order to prove a new point or prove an issue and its solution.
However, avoiding them means you help all of us
working together.
No more heroes!
A good developer is not a very gifted and
impressive developer.
It is a developer that can work with others and
works for the next developer to take over.
People will move from product to product or leave the company.
Web products are never finished.
Don’t leave a mess behind; work as if you
won’t see the code ever again.
Mistakes we make:
1. Mixing structure, presentation and behaviour.
2. Trusting the browser.3. Trusting the markup.4. Not planning for inclusion.5. Not planning for failure.6. Premature optimization.
This should be a no-brainer by now but we
still do it.
Separation means:
–Working in parallel (to an extend)
–Re-skinning an application by changing the style sheet
–Caching, Minimizing, Optimizing for speed and delivery.
Not separating means:
–Harder maintenance –Larger and slower sites –Several sources for bugs without an easy way to track them.
1. Mixing structure, presentation and behaviour.
2. Trusting the browser.3. Trusting the markup.4. Not planning for inclusion.5. Not planning for failure.6. Premature optimization.
The last thing to trust is the browser.
They all have shifty eyes and will steal your loose change when you don’t
look.
Testing for a browser name and assuming it
can do certain things is a bad plan.
Browsers come in all kind of setups and with all kind of extensions.
Assume the browser will fail and keep poking it
until it tells you all is OK.
Making assumptions:
function showMessage(elm, message){
elm.firstChild.nodeValue = message;
}
function showMessage(id,message){ if(document.getElementById){ var elm = document.getElementById(id); if(elm){ if(elm.firstChild){ if(elm.firstChild.nodeType === 3){ elm.firstChild.nodeValue = message; } else { var t = document.createTextNode(message); elm.insertBefore(t,elm.firstChild); } } else { var t = document.createTextNode(message); elm.appendChild(t,elm.firstChild); } } }}
function showMessage(id,message){ if(document.getElementById){ var elm = document.getElementById(id); if(elm){ if(elm.firstChild){ if(elm.firstChild.nodeType === 3){ elm.firstChild.nodeValue = message; } else { var t = document.createTextNode(message); elm.insertBefore(t,elm.firstChild); } } else { var t = document.createTextNode(message); elm.appendChild(t,elm.firstChild); } } }}
function showMessage(id,message){ if(document.getElementById){ var elm = document.getElementById(id); if(elm){ if(elm.firstChild){ if(elm.firstChild.nodeType === 3){ elm.firstChild.nodeValue = message; } else { var t = document.createTextNode(message); elm.insertBefore(t,elm.firstChild); } } else { var t = document.createTextNode(message); elm.appendChild(t,elm.firstChild); } } }}
function showMessage(id,message){ if(document.getElementById){ var elm = document.getElementById(id); if(elm){ if(elm.firstChild){ if(elm.firstChild.nodeType === 3){ elm.firstChild.nodeValue = message; } else { var t = document.createTextNode(message); elm.insertBefore(t,elm.firstChild); } } else { var t = document.createTextNode(message); elm.appendChild(t,elm.firstChild); } } }}
function showMessage(id,message){ if(document.getElementById){ var elm = document.getElementById(id); if(elm){ if(elm.firstChild){ if(elm.firstChild.nodeType === 3){ elm.firstChild.nodeValue = message; } else { var t = document.createTextNode(message); elm.insertBefore(t,elm.firstChild); } } else { var t = document.createTextNode(message); elm.appendChild(t,elm.firstChild); } } }}
function showMessage(id,message){ if(document.getElementById){ var elm = document.getElementById(id); if(elm){ if(elm.firstChild){ if(elm.firstChild.nodeType === 3){ elm.firstChild.nodeValue = message; } else { var t = document.createTextNode(message); elm.insertBefore(t,elm.firstChild); } } else { var t = document.createTextNode(message); elm.appendChild(t,elm.firstChild); } } }}
function showMessage(id,message){ if(document.getElementById){ var elm = document.getElementById(id); if(elm){ if(elm.firstChild){ if(elm.firstChild.nodeType === 3){ elm.firstChild.nodeValue = message; } else { var t = document.createTextNode(message); elm.insertBefore(t,elm.firstChild); } } else { var t = document.createTextNode(message); elm.appendChild(t,elm.firstChild); } } }}
function showMessage(id,message){ if(document.getElementById){ var elm = document.getElementById(id); if(elm){ if(elm.firstChild){ if(elm.firstChild.nodeType === 3){ elm.firstChild.nodeValue = message; } else { var t = document.createTextNode(message); elm.insertBefore(t,elm.firstChild); } } else { var t = document.createTextNode(message); elm.appendChild(t,elm.firstChild); } } }}
function showMessage(id,message){ if(document.getElementById){ var elm = document.getElementById(id); if(elm){ if(elm.firstChild){ if(elm.firstChild.nodeType === 3){ elm.firstChild.nodeValue = message; } else { var t = document.createTextNode(message); elm.insertBefore(t,elm.firstChild); } } else { var t = document.createTextNode(message); elm.appendChild(t,elm.firstChild); } } }}
function showMessage(id,message){ if(document.getElementById){ var elm = document.getElementById(id); if(elm){ if(elm.firstChild){ if(elm.firstChild.nodeType === 3){ elm.firstChild.nodeValue = message; } else { var t = document.createTextNode(message); elm.insertBefore(t,elm.firstChild); } } else { var t = document.createTextNode(message); elm.appendChild(t,elm.firstChild); } } }}
Bit overkill, isn’t it?
–Add tests for the browser early (init method)
–Use library functions that do the testing for you.
1. Mixing structure, presentation and behaviour.
2. Trusting the browser.3. Trusting the markup.4. Not planning for inclusion.5. Not planning for failure.6. Premature optimization.
HTML markup of the document is never set in
stone.
Don’t rely on it.
document.getElementById('nav').getElementsByTagName('li')[3].firstChild.getElmentsByTagName('span')[2].nodeValue = 'offline';
document.getElementById('nav').getElementsByTagName('li')[3].firstChild.getElmentsByTagName('span')[2].nodeValue = 'offline';
1. Mixing structure, presentation and behaviour.
2. Trusting the browser.3. Trusting the markup.4. Not planning for inclusion.5. Not planning for failure.6. Premature optimization.
Assuming your script is the only one included in
the document:
Assuming your script is the only one included in
the document:
hasfail = true;
Namespace your scripts, follow a naming
convention and don’t leave any global variables
or methods.
That way your script will run even if it has to work
alongside bad code.
Bad code will be added to your documents, most of the time in the form of
advertisements or tracking code.
1. Mixing structure, presentation and behaviour.
2. Trusting the browser.3. Trusting the markup.4. Not planning for inclusion.5. Not planning for failure.6. Premature optimization.
Things will fail, plan for it!
– JavaScript will not be available.–Ajax connections will take too long or return wrong data.
–Plan for a fallback option (redirect to a static page, show an error message)
<a href="javascript:ajaxmagic('panda')">
Show results for 'panda'
</a>
<a href="javascript:ajaxmagic('panda')">
Show results for 'panda'
</a>
<a href="search.php?term=panda" id="searchlink">
Show results for 'panda‘
</a>
var YE = YAHOO.util.Event;
YE.on('searchlink','click',ajaxmagic);
function ajaxmagic(e){
var t = YE.getTarget(e);
var url = t.href.split('=')[1];
ajaxcode(url);
YE.preventDefault(e);
}
Re-use the backend script
function ajaxmagic(e){
var t = YE.getTarget(e);
var url = t.href + &output=json';
ajaxcode(url);
YE.preventDefault(e);
}
1. Mixing structure, presentation and behaviour.
2. Trusting the browser.3. Trusting the markup.4. Not planning for inclusion.5. Not planning for failure.6. Premature optimization.
Each of us has a little hacker inside who wants
to get out.
This little hacker wants to make things as fast, small
and cool as possible.
This little hacker is also very competitive and doesn’t trust reused
code or libraries.
Our job is not to give in to it when we produce production code.
Production code does not need to be optimized
from the start.
It needs to be understandable and
maintainable.
–use library code, even if it appears huge (a lot of the size is a myth)
–Use comments to explain what is going on
–Use explanatory variable and method names
–Don’t reinvent the wheel even if you consider yours superior.
Enough bad. Give us some tips!
1. Follow a diet plan.2. Have a build process.3. Have a code standard and
documentation process.4. Review and reuse code.5. Plan for extension.6. Think maintenance
Try to avoid traps that make your code hungry:
–Cut down on DOM interaction –Minimize the amount of loops –Build tool methods to deal with reoccurring problems
Use shortcut notations in JavaScript.
var links = new Array();
links[0]='http://icant.co.uk';
links[1]='http://wait-till-i.com';
links[2]='http://onlinetools.org';
var links = new Array();
links[0]='http://icant.co.uk';
links[1]='http://wait-till-i.com';
links[2]='http://onlinetools.org';
var links = [
'http://icant.co.uk', 'http://wait-till-i.com', 'http://onlinetools.org‘
];
var contact = new Object();
contact.name = 'Christian';
contact.surName = 'Heilmann';
contact.age = 32;
contact.hair = 'slightly red';
var contact = new Object();contact.name = 'Christian';contact.surName = 'Heilmann';contact.age = 32;contact.hair = 'slightly red';
var contact = {name:'Christian',surName:'Heilmann',age:32,hair:'slightly red'
};
if(hasCheeseburger){cat.mood = 'happy';
} else {cat.mood = 'sad';
}
if(hasCheeseburger){cat.mood = 'happy';
} else {cat.mood = 'sad';
}
cat.mood = hasCheeseburger?'happy':'sad';
if(hasCheeseburger){cat.mood = 'happy';
} else {cat.mood = 'sad';
}
cat.mood = hasCheeseburger?'happy':'sad';
condition
if(hasCheeseburger){cat.mood = 'happy';
} else {cat.mood = 'sad';
}
cat.mood = hasCheeseburger?'happy':'sad';
true case
if(hasCheeseburger){cat.mood = 'happy';
} else {cat.mood = 'sad';
}
cat.mood = hasCheeseburger?'happy':'sad';
false case
if(obj.getClass()==='full'){var x = screen.left;
} else {var x = current;
}
if(obj.getClass()==='full'){var x = screen.left;
} else {var x = current;
}
var x = (obj.hasClass()==='full') ? screen.left : current;
var cat;
if(canHasCheeseburger){
cat = canHasCheeseburger;
};
if(garfield){
cat = garfield;
};
var cat;
if(canHasCheeseburger){
cat = canHasCheeseburger;
};
if(garfield){
cat = garfield;
};
var cat = canHasCheeseburger || garfield;
var cat;
if(canHasCheeseburger){
cat = canHasCheeseburger;
};
if(garfield){
cat = garfield;
};
var cat = canHasCheeseburger || garfield;
one or the other, defining a fallback
var massive.name.with.long.namespace = { ...private stuff... return{ init:function(){ massive.name.with.long.namespace.show();
}, show:function(){ massive.name.with.long.namespace.test();
}, test:function(){ } }}();
var massive.name.with.long.namespace = { var pub = {};...private stuff...pub.init = function(){
pub.show();};pub.show = function(){
pub.test();};pub.test = function(){};return pub;
}();massive.name.with.long.namespace.init();
var massive.name.with.long.namespace = {function init(){
show();};function show(){
test();};function test(){};return {
init:init;show:show;test:test;
};}();massive.name.with.long.namespace.init();
1. Follow a diet plan.2. Have a build process.3. Have a code standard and
documentation process.4. Review and reuse code.5. Plan for extension.6. Think maintenance
–Production code is not live code.
–That doesn’t happen on the back-end and it shouldn’t happen on the front-end.
–Live code is there for machines, production code is there for humans.
Build process:–Validation (Tidy, JSLint)–Minification (JSMin, CSS minifier)
–Consolidation (one CSS and one JS instead of dozens)
–Tagging as “live code” – do not edit!
1. Follow a diet plan.2. Have a build process.3. Have a code standard and
documentation process.4. Review and reuse code.5. Plan for extension.6. Think maintenance
Following a code standard means you can:–Assess quality of code –Find bugs easily and create reproducible bug reports
–Have quick handover from developer to developer
–Have reliable version control
What code standard?
Whatever the team agrees on and feels comfortable with.
Document it and other teams can validate what
you have done.
In the perfect world, we’ll all follow the same code
standard.
Let’s meet again in a year! :-)
Comments are good, but they are not
documentation.
Write documentation in peer review - as the
developer you are too close to the subject.
Plan and ask for time to document what you have
done.
1. Follow a diet plan.2. Have a build process.3. Have a code standard and
documentation process.4. Review and reuse code.5. Plan for extension.6. Think maintenance
Conduct Code reviews–You find problems and solutions you can share in the team.
–You find out who knows what well and who needs training in what
–You share the knowledge throughout the team = no Divas.
– If you keep finding the same problem and different solutions for it, find a generic solution and re-use that one.
–Look at what other people have done (libraries).
–Bounce your solutions off other development teams.
1. Follow a diet plan.2. Have a build process.3. Have a code standard and
documentation process.4. Review and reuse your code.5. Plan for extension.6. Think maintenance
Whenever you write some code, don’t think that
your implementation will be the end of it.
Make sure that your code can be extended by
other people and try to build it as modular as
possible.
1. Follow a diet plan.2. Have a build process.3. Have a code standard and
documentation process.4. Review and reuse your code.5. Plan for extension.6. Think maintenance
Not everybody maintaining your code will be as good as you
are.
Trying to find where to change what in a piece of
code is terribly frustrating.
Separate out as much of the look and feel as you
can.
Also separate out labels that are likely to change and the names of CSS classes and IDs in use.
So how do you plan JavaScript and Ajax for
larger teams?
Get the team to talk and agree on what works
best.
Involve Design and Engineering in the
process and explain the rationale of your plan.
Communication and sharing information is
better than any architectural blueprint you or I could come up
with.