Tweet
Modularidade A Importância de Desacoplar a Sua Aplicação
Quando dizemos que uma aplicação é modular, geralmente queremos dizer que é composta por um conjunto de peças de funcionalidade altamente desacopladas e distintas armazenadas em módulos. Como você provavelmente sabe, o acoplamento solto facilita a manutenção das aplicações, removendo as dependências sempre que possível. Quando isto é implementado eficientemente, é bastante fácil ver como as mudanças em uma parte de um sistema podem afetar outra.
Não obstante, a iteração atual do JavaScript (ECMA-262) não fornece aos desenvolvedores os meios para importar tais módulos de código de uma maneira limpa e organizada. É uma das preocupações com especificações que não tem exigido grande reflexão até anos mais recentes, onde a necessidade de aplicações JavaScript mais organizadas tornou-se aparente.
Em vez disso, os desenvolvedores no momento são deixados para trás em variações dos padrões literais do módulo ou objeto. Com muitos destes, os scripts de módulo são amarrados juntos no DOM com namespaces sendo descritos por um único objeto global onde ainda é possível incorrer em colisões de nomes em sua arquitetura. Também não há uma maneira limpa de lidar com a gestão de dependências sem algum esforço manual ou ferramentas de terceiros.
Em ES Harmony, a boa notícia é que escrever JavaScript modular nunca foi tão fácil e você pode começar a fazê-lo hoje.
Neste artigo, vamos olhar para três formatos para escrever JavaScript modular: AMD, CommonJS e propostas para a próxima versão do JavaScript, Harmony.
Prelude A Note On Script Loaders
É difícil discutir os módulos AMD e CommonJS sem falar sobre o elefante na sala – carregadores de scripts. Atualmente, o carregamento de scripts é um meio para um objetivo, sendo esse objetivo um JavaScript modular que pode ser usado em aplicações hoje em dia – para isso, infelizmente é necessário o uso de um gerenciador de scripts compatível. Para tirar o máximo proveito deste artigo, recomendo a compreensão básica de como as ferramentas populares de carregamento de scripts funcionam para que as explicações dos formatos de módulos façam sentido no contexto.
Há uma série de grandes carregadores para o carregamento de módulos nos formatos AMD e CJS, mas minhas preferências pessoais são RequireJS e curl.js. Tutoriais completos sobre essas ferramentas estão fora do escopo deste artigo, mas eu posso recomendar a leitura do post de John Hann sobre o curl.js e a documentação da API RequireJS de James Burke para mais.
De uma perspectiva de produção, o uso de ferramentas de otimização (como o otimizador RequireJS) para concatenar scripts é recomendado para deployment quando se trabalha com tais módulos. Curiosamente, com o calço AMD Almond, o RequireJS não precisa ser rolado no site implantado e o que você pode considerar um carregador de scripts pode ser facilmente deslocado para fora do desenvolvimento.
Dito isto, James Burke provavelmente diria que ser capaz de carregar scripts dinamicamente após a carga da página ainda tem seus casos de uso e o RequireJS pode ajudar com isto também. Com estas notas em mente, vamos começar.
AMD A Format For Writing Modular JavaScript In The Browser
O objetivo geral para o formato AMD (Asynchronous Module Definition) é fornecer uma solução para o JavaScript modular que os desenvolvedores podem usar hoje. Ele nasceu da experiência do Dojo no mundo real usando XHR+eval e os proponentes deste formato queriam evitar que qualquer solução futura sofresse com as fraquezas daqueles no passado.
O próprio formato do módulo AMD é uma proposta para definir módulos onde tanto o módulo quanto as dependências podem ser carregados de forma assíncrona. Ele tem uma série de vantagens distintas, incluindo ser assíncrono e altamente flexível por natureza, o que remove o acoplamento apertado que se pode encontrar normalmente entre o código e a identidade do módulo. Muitos desenvolvedores gostam de usá-lo e se pode considerá-lo um trampolim confiável para o sistema de módulos proposto para o ES Harmony.
AMD começou como um rascunho de especificação para um formato de módulo na lista CommonJS, mas como não foi capaz de alcançar um consenso total, o desenvolvimento do formato passou para o grupo amdjs.
Hoje ele é abraçado por projetos incluindo Dojo (1.7), MooTools (2.0), Firebug (1.8) e até mesmo jQuery (1.7). Embora o termo CommonJS formato AMD tenha sido visto na natureza na ocasião, é melhor se referir a ele como apenas suporte a AMD ou Módulo Async, pois nem todos os participantes da lista CJS desejavam segui-lo.
Começando com Módulos
Os dois conceitos-chave que você precisa conhecer aqui são a idéia de um método define
para facilitar a definição de módulos e um método require
para lidar com o carregamento de dependências. define é usado para definir módulos nomeados ou não nomeados com base na proposta usando a seguinte assinatura:
Como você pode dizer pelos comentários em linha, o module_id
é um argumento opcional que normalmente só é necessário quando ferramentas de concatenação não-AMD estão sendo usadas (pode haver alguns outros casos de borda onde é útil também). Quando este argumento é deixado de fora, chamamos o módulo de anônimo.
Quando se trabalha com módulos anônimos, a idéia da identidade de um módulo é DRY, tornando trivial evitar a duplicação de nomes de arquivos e códigos. Como o código é mais portável, ele pode ser facilmente movido para outros locais (ou ao redor do sistema de arquivos) sem a necessidade de alterar o código em si ou mudar seu ID. O module_id
é equivalente a caminhos de pastas em pacotes simples e quando não é usado em pacotes. Os desenvolvedores também podem executar o mesmo código em múltiplos ambientes apenas usando um otimizador AMD que funciona com um ambiente CommonJS como o r.js.
Voltar para a assinatura de definição, o argumento dependências representa um array de dependências que são requeridas pelo módulo que você está definindo e o terceiro argumento (‘função de definição’) é uma função que é executada para instanciar seu módulo. Um módulo de barebone pode ser definido da seguinte forma:
Understanding AMD: define()
require, por outro lado, é normalmente usado para carregar código em um arquivo JavaScript de nível superior ou dentro de um módulo, caso você deseje buscar dependências dinamicamente. Um exemplo de seu uso é:
Understanding AMD: require()
Dynamically-loaded Dependencies
Understanding AMD: plugins
O seguinte é um exemplo de definição de um plugin compatível com AMD:
Loading AMD Modules Using require.js
Carregando Módulos AMD Usando módulos AMD.js
Módulos com dependências diferidas
Por que AMD é uma melhor escolha para escrever JavaScript modular?
- Proporciona uma proposta clara de como abordar a definição de módulos flexíveis.
- significativamente mais limpo do que o actual namespace global e
<script>
soluções de tags em que muitos de nós confiamos. Há uma maneira limpa de declarar módulos independentes e dependências que eles podem ter. - Definições de módulos são encapsuladas, ajudando-nos a evitar a poluição do namespace global.
- Obras melhores que algumas soluções alternativas (por exemplo, CommonJS, que estaremos analisando em breve). Não tem problemas com cross-domain, local ou debugging e não depende de ferramentas do lado do servidor para ser usado. A maioria dos carregadores AMD suporta carregamento de módulos no navegador sem um processo de compilação.
- Provê uma abordagem de ‘transporte’ para incluir múltiplos módulos em um único arquivo. Outras abordagens como CommonJS ainda não concordaram em um formato de transporte.
- É possível carregar scripts preguiçosos se isso for necessário.
Módulos AMD com Dojo
Definir módulos compatíveis com AMD usando Dojo é bastante simples. Conforme acima, defina qualquer dependência de módulo em um array como o primeiro argumento e forneça uma chamada de retorno (de fábrica) que executará o módulo uma vez que as dependências tenham sido carregadas. e.g:
define(, function( Tooltip ){ //Our dijit tooltip is now available for local use new Tooltip(...);});
Note a natureza anônima do módulo que agora pode ser ambos consumidos por um carregador assíncrono Dojo, RequireJS ou o carregador de módulos padrão dojo.require() que você pode estar acostumado a usar.
Para aqueles que se perguntam sobre referências a módulos, existem algumas gotchas interessantes que são úteis para saber aqui. Embora a forma de referenciar módulos AMD os declare na lista de dependências com um conjunto de argumentos correspondentes, isso não é suportado pelo sistema de compilação Dojo 1.6 – ele realmente só funciona para carregadores compatíveis com AMD. e.g:
define(, function( cookie, Tooltip ){ var cookieValue = cookie("cookieName"); new Tree(...); });
Isso tem muitos avanços sobre o agrupamento de nomes aninhados, pois os módulos não precisam mais referenciar diretamente espaços de nomes completos toda vez – tudo que precisamos é o caminho ‘dojo/cookie’ em dependências, que uma vez aliado a um argumento, pode ser referenciado por essa variável. Isso remove a necessidade de digitar repetidamente ‘dojo’ em suas aplicações.
A última coisa que você deve saber é que se você deseja continuar usando o sistema de construção do Dojo ou deseja migrar módulos mais antigos para este novo estilo AMD, a seguinte versão mais verbosa permite uma migração mais fácil. Note que dojo e dijit e referenciados como dependências também:
AMD Module Design Patterns (Dojo)
Se você seguiu algum dos meus posts anteriores sobre os benefícios dos padrões de design, você saberá que eles podem ser altamente eficazes em melhorar a forma como abordamos soluções estruturantes para problemas comuns de desenvolvimento. John Hann fez recentemente uma excelente apresentação sobre padrões de design de módulos AMD cobrindo o Singleton, Decorator, Mediator e outros. Recomendo vivamente que você verifique os slides dele se tiver uma chance.
Amostras destes padrões podem ser encontradas abaixo:
Decorator pattern:
Padrão adaptador
Módulos AMD Com jQuery
O básico
Unlike Dojo, jQuery realmente só vem com um arquivo, porém dada a natureza baseada em plugins da biblioteca, podemos demonstrar como é simples definir um módulo AMD que o usa abaixo.
>
Só falta algo neste exemplo e é o conceito de registo.
Registar jQuery como um módulo compatível com Async
Uma das principais características que aterraram em jQuery 1.7 foi o suporte para registar jQuery como um módulo assíncrono. Existem vários carregadores de scripts compatíveis (incluindo RequireJS e curl) que são capazes de carregar módulos usando um formato de módulo assíncrono e isso significa que menos hacks são necessários para que as coisas funcionem.
Como resultado da popularidade do jQuery, os carregadores AMD precisam levar em conta várias versões da biblioteca sendo carregada na mesma página, pois você idealmente não quer várias versões diferentes carregando ao mesmo tempo. Os carregadores têm a opção de levar este problema especificamente em conta ou instruir seus usuários que existem problemas conhecidos com scripts de terceiros e suas bibliotecas.
O que a adição 1.7 traz para a tabela é que ela ajuda a evitar problemas com outros códigos de terceiros em uma página carregando acidentalmente uma versão do jQuery na página que o dono não estava esperando. Você não quer outras instâncias a atacar a sua própria e por isso isto pode ser benéfico.
A forma como isto funciona é que o carregador de scripts a ser empregado indica que suporta múltiplas versões do jQuery especificando que uma propriedade, define.amd.jQuery
é igual a true. Para aqueles interessados em detalhes de implementação mais específicos, registramos jQuery como um módulo nomeado, pois há o risco de que ele possa ser concatenado com outros arquivos que podem usar o método define()
da AMD, mas não usar um script de concatenação apropriado que entenda as definições anônimas do módulo da AMD.
A AMD nomeada fornece uma manta de segurança de ser robusta e segura para a maioria dos casos de uso.
Plugins de jQuery mais inteligentes
Tenho discutido recentemente algumas ideias e exemplos de como os plugins jQuery poderiam ser escritos usando padrões de Definição Universal de Módulos (UMD) aqui. UMDs definem módulos que podem funcionar tanto no cliente como no servidor, assim como com todos os populares carregadores de scripts disponíveis no momento. Embora esta seja ainda uma área nova com muitos conceitos ainda em fase de finalização, sinta-se à vontade para olhar os exemplos de código na seção título AMD && CommonJS abaixo e me avise se você achar que há algo que poderíamos fazer melhor.
Que Carregadores de Script & Frameworks suportam AMD?
In-browser:
Server-side:
- RequireJS http://requirejs.org
- PINF http://github.com/pinf/loader-js
AAMD Conclusões
Os acima são exemplos muito triviais de quão úteis os módulos AMD podem realmente ser, mas eles, esperançosamente, fornecem uma base para entender como eles funcionam.
Você pode estar interessado em saber que muitas grandes aplicações e empresas visíveis atualmente usam os módulos AMD como parte de sua arquitetura. Estes incluem a IBM e o BBC iPlayer, que destacam o quão seriamente este formato está sendo considerado pelos desenvolvedores a nível empresarial.
Por mais razões pelas quais muitos desenvolvedores estão optando por usar módulos AMD em suas aplicações, você pode estar interessado neste post de James Burke.
CommonJS A Module Format Optimized For The Server
CommonJS são um grupo de trabalho voluntário que tem como objectivo desenhar, protótipo e padronizar APIs JavaScript. Até hoje eles tentaram ratificar padrões tanto para módulos quanto para pacotes. A proposta de módulo CommonJS especifica uma API simples para declarar módulos do lado do servidor e ao contrário das tentativas da AMD de cobrir um conjunto mais amplo de preocupações tais como io, sistema de arquivos, promessas e muito mais.
Arrancar
De uma perspectiva estrutural, um módulo CJS é uma peça reutilizável de JavaScript que exporta objectos específicos disponibilizados para qualquer código dependente – normalmente não existem invólucros de funções à volta desses módulos (por isso não verá define
utilizado aqui, por exemplo).
A um alto nível eles contêm basicamente duas partes primárias: uma variável livre chamada exports
que contém os objetos que um módulo deseja disponibilizar para outros módulos e uma função require
que os módulos podem usar para importar as exportações de outros módulos.
Entendendo CJS: require() e exports
Basic consumption of exports
AMD-equivalent Of The First CJS Example
Consuming Multiple Dependencies
app.js
bar.js
exports.name = 'bar';
foo.js
require('./bar');exports.helloWorld = function(){ return 'Hello World!!''}
Que Carregadores & Suporte de Estruturas CJS?
No Navegador:
- curl.js http://github.com/unscriptable/curl
- SproutCore 1.1 http://sproutcore.com
- PINF http://github.com/pinf/loader-js
- (e mais)
Servidor:
O CJS é Adequado Para O Navegador?
Há desenvolvedores que acham que o CommonJS é mais adequado para o desenvolvimento do lado do servidor, o que é um dos motivos pelos quais existe atualmente um nível de desacordo sobre qual formato deve e será usado como o padrão de fato na era pré-Harmony a avançar. Alguns dos argumentos contra o CJS incluem uma nota de que muitas APIs do CommonJS abordam características orientadas ao servidor que simplesmente não seriam capazes de implementar em nível de navegador em JavaScript – por exemplo, io, system e js poderiam ser considerados não implementáveis pela natureza de sua funcionalidade.
Dito isto, é útil saber como estruturar módulos CJS independentemente, para que possamos apreciar melhor como eles se encaixam na definição de módulos que podem ser usados em todos os lugares. Os módulos que têm aplicações tanto no cliente como no servidor incluem mecanismos de validação, conversão e templating. A forma como alguns desenvolvedores estão se aproximando na escolha do formato a ser utilizado é optando pelo CJS quando um módulo pode ser utilizado em um ambiente server-side e usando AMD se este não for o caso.
As módulos AMD são capazes de usar plugins e podem definir coisas mais granulares como construtores e funções isto faz sentido. Os módulos CJS só são capazes de definir objetos que podem ser tediosos para trabalhar se você estiver tentando obter construtores deles.
Embora esteja além do escopo deste artigo, você também deve ter notado que havia diferentes tipos de métodos ‘necessários’ mencionados quando se discutia AMD e CJS.
A preocupação com uma convenção de nomenclatura semelhante é obviamente confusa e a comunidade está actualmente dividida sobre os méritos de uma função de exigência global. A sugestão de John Hann aqui é que ao invés de chamá-la de ‘require’, que provavelmente não atingiria o objetivo de informar os usuários sobre a diferença entre uma necessidade global e uma necessidade interna, pode fazer mais sentido renomear o método de carga global para outra coisa (por exemplo, o nome da biblioteca). É por esta razão que um carregador como o curl.js usa curl()
em oposição a require
.
AMD && CommonJS Competing, But Equally Valid Standards
Apesar deste artigo ter dado mais ênfase ao uso do AMD em relação ao CJS, a realidade é que ambos os formatos são válidos e têm um uso.
AMD adota uma abordagem de desenvolvimento com navegador, optando por um comportamento assíncrono e uma compatibilidade regressiva simplificada, mas não tem nenhum conceito de File I/O. Ele suporta objetos, funções, construtores, strings, JSON e muitos outros tipos de módulos, rodando nativamente no navegador. É incrivelmente flexível.
CommonJS, por outro lado, adota uma abordagem server-first, assumindo um comportamento síncrono, sem bagagem global como John Hann se referiria a ele e tenta atender ao futuro (no servidor). O que queremos dizer com isto é que como o CJS suporta módulos desembrulhados, ele pode se sentir um pouco mais próximo das especificações do ES.next/Harmony, liberando-o do invólucro define()
que a AMD impõe. Os módulos CJS no entanto só suportam objectos como módulos.
Embora a ideia de mais um formato de módulo possa ser assustadora, poderá estar interessado em alguns exemplos de trabalho em módulos híbridos AMD/CJS e Univerais AMD/CJS.
Basic AMD Hybrid Format (John Hann)
AMD/CommonJS Universal Module Definition (Variation 2, UMDjs)
Extensible UMD Plugins With (Variation by myself and Thomas Davis).
core.js
myExtension.js
;(function ( name, definition ) { var theModule = definition(), hasDefine = typeof define === 'function', hasExports = typeof module !== 'undefined' && module.exports; if ( hasDefine ) { // AMD Module define(theModule); } else if ( hasExports ) { // Node.js Module module.exports = theModule; } else { // Assign to common namespaces or simply the global object (window) // account for for flat-file/global module extensions var obj = null; var namespaces = name.split("."); var scope = (this.jQuery || this.ender || this.$ || this); for (var i = 0; iapp.js
$(function(){ // the plugin 'core' is exposed under a core namespace in // this example which we first cache var core = $.core; // use then use some of the built-in core functionality to // highlight all divs in the page yellow core.highlightAll(); // access the plugins (extensions) loaded into the 'plugin' // namespace of our core module: // Set the first div in the page to have a green background. core.plugin.setGreen("div:first"); // Here we're making use of the core's 'highlight' method // under the hood from a plugin loaded in after it // Set the last div to the 'errorColor' property defined in // our core module/plugin. If you review the code further down // you'll see how easy it is to consume properties and methods // between the core and other plugins core.plugin.setRed('div:last');});ES Harmony Modules Of The Future
>
TC39, o organismo de normalização encarregado de definir a sintaxe e a semântica do ECMAScript e as suas iterações futuras é composto por uma série de programadores muito inteligentes. Alguns destes desenvolvedores (como Alex Russell) têm estado de olho na evolução do uso do JavaScript para desenvolvimento em larga escala ao longo dos últimos anos e estão muito conscientes da necessidade de melhores características da linguagem para escrever mais JS.
Por esta razão, existem atualmente propostas para uma série de adições emocionantes à linguagem, incluindo módulos flexíveis que podem funcionar tanto no cliente como no servidor, um carregador de módulos e muito mais. Nesta seção, eu estarei mostrando alguns exemplos de código da sintaxe para módulos no ES.next para que você possa ter uma idéia do que está por vir.
Nota: Embora o Harmony ainda esteja nas fases de proposta, você já pode experimentar as características (parciais) do ES.next que abordam o suporte nativo para escrever JavaScript modular graças ao compilador Traceur do Google. Para começar a utilizar o Traceur em menos de um minuto, leia este guia de iniciação. Há também uma apresentação do JSConf sobre ele que vale a pena conferir se você estiver interessado em aprender mais sobre o projeto.Modules With Imports And Exports
Se você já leu as seções sobre módulos AMD e CJS você pode estar familiarizado com o conceito de dependências de módulos (importações) e exportações de módulos (ou, a API pública/variáveis que permitimos que outros módulos consumam). No ES.next, esses conceitos foram propostos de uma forma um pouco mais sucinta, com dependências sendo especificadas usando uma palavra-chave
import
.export
não é muito diferente do que podemos esperar e acho que muitos desenvolvedores irão olhar para o código abaixo e instantaneamente 'obtê-lo'.
- declarações de importação ligam as exportações de um módulo como variáveis locais e podem ser renomeadas para evitar colisões/conflitos de nome.
- declarações de exportação declaram que uma ligação local de um módulo é visível externamente, de modo que outros módulos podem ler as exportações, mas não podem modificá-las. Curiosamente, os módulos podem exportar módulos filhos, mas não podem exportar módulos que tenham sido definidos em outro lugar. Você também pode renomear exportações de modo que seu nome externo difira de seus nomes locais.
Módulos carregados de fontes remotas
As propostas de módulos também atendem a módulos que são baseados remotamente (por exemplo, um invólucro API de terceiros) tornando simplista carregar módulos a partir de locais externos. Aqui está um exemplo de nós puxando o módulo que definimos acima e utilizando-o:
Module Loader API
O carregador de módulos proposto descreve uma API dinâmica para carregar módulos em contextos altamente controlados. Assinaturas suportadas no carregador incluem load( url, moduleInstance, error)
para carregamento de módulos, createModule( object, globalModuleReferences)
e outros. Aqui está outro exemplo de nós carregando dinamicamente no módulo que definimos inicialmente. Note que ao contrário do último exemplo onde puxamos um módulo de uma fonte remota, a API do carregador de módulos é mais adequada para contextos dinâmicos.
Loader.load('http://addyosmani.com/factory/cakes.js', function(cakeFactory){ cakeFactory.oven.makeCupcake('chocolate'); });
Módulos do tipo CommonJS para o servidor
Para desenvolvedores que são orientados ao servidor, o sistema de módulos proposto para o ES.next não é limitado apenas a olhar os módulos no navegador. Abaixo para exemplos, você pode ver um módulo do tipo CJS proposto para uso no servidor:
// io/File.jsexport function open(path) { ... };export function close(hnd) { ... };
module lexer from 'compiler/LexicalHandler';module stdlib from '@std'; //... scan(cmdline) ...
Classes With Constructors, Getters & Setters
A noção de classe sempre foi um assunto controverso com os puristas e até agora nos demos bem com a queda na natureza prototipal do JavaScript ou através do uso de frameworks ou abstrações que oferecem a capacidade de usar definições de classe de uma forma que desagrada ao mesmo comportamento prototipal.
No Harmony, as classes vêm como parte da linguagem juntamente com os construtores e (finalmente) alguma sensação de verdadeira privacidade. Nos exemplos a seguir, incluí alguns comentários em linha para ajudá-lo a entender como as classes são estruturadas, mas você também pode notar a falta da palavra ‘função’ aqui. Isto não é um erro de digitação: TC39 tem feito um esforço consciente para diminuir nosso abuso da palavra-chave function
para tudo e a esperança é que isso ajude a simplificar a forma como escrevemos o código.
class Cake{ // We can define the body of a class' constructor // function by using the keyword 'constructor' followed // by an argument list of public and private declarations. constructor( name, toppings, price, cakeSize ){ public name = name; public cakeSize = cakeSize; public toppings = toppings; private price = price; } // As a part of ES.next's efforts to decrease the unnecessary // use of 'function' for everything, you'll notice that it's // dropped for cases such as the following. Here an identifier // followed by an argument list and a body defines a new method addTopping( topping ){ public(this).toppings.push(topping); } // Getters can be defined by declaring get before // an identifier/method name and a curly body. get allToppings(){ return public(this).toppings; } get qualifiesForDiscount(){ return private(this).price > 5; } // Similar to getters, setters can be defined by using // the 'set' keyword before an identifier set cakeSize( cSize ){ if( cSizeES Harmony Conclusions
As you can see, ES.next is coming with some exciting new additions. Although Traceur can be used to an extent to try our such features in the present, remember that it may not be the best idea to plan out your system to use Harmony (just yet). There are risks here such as specifications changing and a potential failure at the cross-browser level (IE9 for example will take a while to die) so your best bets until we have both spec finalization and coverage are AMD (for in-browser modules) and CJS (for those on the server).
Conclusions And Further Reading A Review
In this article we've reviewed several of the options available for writing modular JavaScript using modern module formats. These formats have a number of advantages over using the (classical) module pattern alone including: avoiding a need for developers to create global variables for each module they create, better support for static and dynamic dependency management, improved compatibility with script loaders, better (optional) compatibility for modules on the server and more.
In short, I recommend trying out what's been suggested today as these formats offer a lot of power and flexibility that can help when building applications based on many reusable blocks of functionality.
And that's it for now. If you have further questions about any of the topics covered today, feel free to hit me up on twitter and I'll do my best to help!
Copyright Addy Osmani, 2012. All Rights Reserved.