Idiomatic JS
Princípios para Escrever JavaScript de forma Consistente e Idiomática
Referências Originais
O artigo original utiliza a licença CC 3.0 que permite copiar, modificar e restribuir o material.
Acesse os artigos originais aqui:
Boa leitura!
Todo código em qualquer aplicação deve parecer como se tivesse sido escrito por uma única pessoa, independentemente de quantas pessoas tenham contribuído.
A lista a seguir descreve as práticas que eu uso em todo código onde sou o autor original; contribuições em projetos que eu criei devem seguir essas mesmas orientações.
Eu não tenho a intenção de impor minhas preferências por estilos nos códigos ou projetos de outras pessoas; se eles seguem um estilo em comum, isso deve ser respeitado.
"Argumentos além do estilo são inúteis. Deve haver um guia de estilo, e você deve segui-lo" Rebecca Murphey
"Parte de ser um bom gestor de um projeto bem sucedido é perceber que escrever código para si mesmo é uma má ideia™. Se milhares de pessoas estão usando o seu código, escreva-o com máxima clareza, não sob a sua preferência pessoal de como ser esperto com a especificação." Idan Gazit
Conteúdo importante e não idiomático:
Qualidade de código: ferramentas, recursos e referências
Fique ligado
Annotated ECMAScript 5.1 EcmaScript Language Specification, 5.1 Edition
A lista a seguir deve ser considerada: 1) incompleta; e 2) LEITURA OBRIGATÓRIA. Eu não concordo sempre com os estilos escritos pelos autores abaixo, mas uma coisa é certa: eles são consistentes. Além disso, esses são autoridades na linguagem.
- Baseline For Front End Developers
- Eloquent JavaScript
- JavaScript, JavaScript
- Adventures in JavaScript Development
- Perfection Kills
- Douglas Crockford's Wrrrld Wide Web
- JS Assessment
- Leveraging Code Quality Tools (em pt_BR: Tirando Proveito de Ferramentas de Qualidade de Código) por Anton Kovalyov
Processos de build e deploy
Projetos devem sempre tentar incluir algumas formas genéricas nas quais o código podem ser checados com ferramentas de lint, testados e compactados no preparo para uso em produção. Para essa tarefa, o grunt pelo Ben Alman é a melhor opção, além de ter substituído oficialmente o diretório "kits/" neste repositório.
Ambiente de teste
Projetos devem incluir alguma forma de teste unitário, de referência, de implementação ou funcional. Demonstrações de casos de uso NÃO SE QUALIFICAM como "testes". A lista a seguir contém frameworks de testes, nenhuma delas é considerada melhor que as demais.
Índice
- Espaço em branco
- Sintaxe bonita
- Checagem de escrita (cortesia das Recomendações de Estilo do Núcleo do jQuery)
- Avaliação condicional
- Estilo prático
- Nomenclatura
- Miscelâneas
- Objetos nativos e hospedados
- Comentários
- Código em apenas um idioma
Prefácio
As seções a seguir descrevem um guia de estilos razoável para desenvolvimento de JavaScript moderno e não pretendem ser obrigatórias. A conclusão mais importante é a lei da consistência de estilo de código. O que for escolhido como estilo para o seu projeto deverá ser considerado lei. Faça um link para este documento como uma regra do seu projeto sobre comprometimento de consistência, legibilidade e manutenção de estilo de código.
Manifesto de estilo idiomático
- Nunca misture espaços e tabs.
- Quando começar um projeto, antes de escrever qualquer código, escolha entre indentação suave (espaços) ou tabulação real (tabs), considere isso como lei.
- Pela legibilidade, eu sempre recomendo que configure o tamanho de indentação de seu editor para dois caracteres - isso significa dois espaços ou dois espaços representando um tab real.
- Se o seu editor suportar, sempre trabalhe com a configuração de "mostrar caracteres invisíveis" ligada. Os benefícios desta prática são:
- fortalecer a consistência;
- eliminar espaço em branco ao final da linha;
- eliminar espaços em uma linha em branco;
- commits e diffs mais legíveis.
- Sintaxe bonita
A. Parênteses, chaves e quebras de linhas
// if/else/for/while/try sempre tem espaços, chaves e ocorrem em múltiplas linhas// isso facilita a legibilidade// 2.A.1.1// Exemplos de código pouco claro/bagunçadoif(condicao) facaAlgo();while(condicao) iteracao++;for(var i=0;i<100;i++) algumaIteracao();// 2.A.1.1// Use espaço em branco para facilitar a leituraif ( condicao ) {// instruções}while ( condicao ) {// instruções}for ( var i = 0; i < 100; i++ ) {// instruções}// Melhor ainda:var i,length = 100;for ( i = 0; i < length; i++ ) {// instruções}// Ou...var i = 0,length = 100;for ( ; i < length; i++ ) {// instruções}var prop;for ( prop in object ) {// instruções}if ( true ) {// instruções} else {// instruções}
B. Atribuições, declarações, funções (nomenclatura, expressão, construtor)
// 2.B.1.1// Variáveisvar foo = "bar",num = 1,undef;// Notações literais:var array = [],object = {};// 2.B.1.2// Utilizando apenas um `var` por escopo (função) promove legibilidade// e mantém a sua lista de declaração livre de desordem (além de evitar algumas tecladas)// Ruimvar foo = "";var bar = "";var qux;// Bomvar foo = "",bar = "",quux;// ou..var // comentário aquifoo = "",bar = "",quux;// 2.B.1.3// declarações de variáveis devem sempre estar no início de seu respectivo escopo (função)// O mesmo deve acontecer para declarações de `const` e `let` do ECMAScript 6.// Ruimfunction foo() {// algumas instruções aquivar bar = "",qux;}// Bomfunction foo() {var bar = "",qux;// algumas instruções depois das declarações de variáveis}
// 2.B.2.1// Declaração de função nomeadafunction foo( arg1, argN ) {}// Utilizaçãofoo( arg1, argN );// 2.B.2.2// Declaração de função nomeadafunction square( number ) {return number * number;}// Utilizaçãosquare( 10 );// Estilo de passagem artificialmente contínuafunction square( number, callback ) {callback( number * number );}square( 10, function( square ) {// instruções de callback});// 2.B.2.3// Expressão de funçãovar square = function( number ) {// Retorna algo de valor e relevantereturn number * number;};// Expressão de função com identificador// Esse formato preferencial tem o valor adicional de permitir// chamar a si mesmo e ter uma identidade na pilha de comandos:var factorial = function factorial( number ) {if ( number < 2 ) {return 1;}return number * factorial( number-1 );};// 2.B.2.4// Declaração de construtorfunction FooBar( options ) {this.options = options;}// Utilizaçãovar fooBar = new FooBar({ a: "alpha" });fooBar.options;// { a: "alpha" }
C. Exceções, pequenos desvios
// 2.C.1.1// Funções com callbacksfoo(function() {// Veja que não há espaço extra entre os parênteses// da chamada de função e a palavra "function"});// Função recebendo uma array, sem espaçofoo([ "alpha", "beta" ]);// 2.C.1.2// Função recebendo um objeto, sem espaçofoo({a: "alpha",b: "beta"});// String literal como argumento único, sem espaçofoo("bar");// Parênteses internos de agrupamento, sem espaçoif ( !("foo" in obj) ) {}
D. Consistência sempre ganha
Nas seções 2.A-2.C, as regras de espaço em branco são recomendadas sob um propósito simples e maior: consistência. É importante notar que preferências de formatação, tais como "espaço em branco interno" deve ser considerado opcional, mas apenas um estilo deve existir por toda a fonte de seu projeto.
// 2.D.1.1if (condition) {// instruções}while (condition) {// instruções}for (var i = 0; i < 100; i++) {// instruções}if (true) {// instruções} else {// instruções}
E. Aspas
Se você preferir usar simples ou dupla não importa, não há diferença em como o JavaScript analisa elas. O que ABSOLUTAMENTE PRECISA ser aplicado é consistência. Nunca misture diferentes tipos de aspas em um mesmo projeto. Escolha um estilo e fique com ele.
F. Finais de linha e linhas vazias
Espaços em branco podem arruinar diffs e fazer com que changesets sejam impossíveis de se ler. Considere incorporar um gancho de pre-commit que remova espaços em branco ao final das linhas e espaços em branco em linhas vazias automaticamente.
- Checagem de escrita (cortesia das Recomendações de Estilo do Núcleo do jQuery)
A. Tipos existentes
String:
typeof variavel === "string"
Number:
typeof variavel === "number"
Boolean:
typeof variavel === "boolean"
Object:
typeof variavel === "object"
Array:
Array.isArray( variavel )// (quando possível)
null:
variavel === null
null ou undefined:
variavel == null
undefined:Variáveis Globais:
typeof variavel === "undefined"
Variáveis Locais:
variavel === undefined
Propriedades:
object.prop === undefinedobject.hasOwnProperty( prop )"prop" in object
B. Tipos coagidosConsidere as implicações do seguinte...Dado este HTML:
<input type="text" id="foo-input" value="1">
// 3.B.1.1// `foo` foi declarado com o valor `0` e seu tipo é `number`var foo = 0;// typeof foo;// "number"...// Algum momento depois no seu código, você precisa atualizar `foo`// com um novo valor derivado de um elemento `input`foo = document.getElementById("foo-input").value;// Se você testasse `typeof foo` agora, o resultado seria uma `string`// Isso significa que se tivesse uma lógica que testasse `foo` como:if ( foo === 1 ) {importantTask();}// `importantTask()` nunca seria chamado, mesmo que `foo` tivesse um valor "1"// 3.B.1.2// Você pode prevenir problemas utilizando uma coerção automática com os operadores + ou -:foo = +document.getElementById("foo-input").value;// ^ o operador + irá converter o operando do lado direito para um número// typeof foo;// "number"if ( foo === 1 ) {importantTask();}// `importantTask()` será chamado
Aqui temos alguns casos comuns com coerções:
// 3.B.2.1var number = 1,string = "1",bool = false;number;// 1number + "";// "1"string;// "1"+string;// 1+string++;// 1string;// 2bool;// false+bool;// 0bool + "";// "false"
// 3.B.2.2var number = 1,string = "1",bool = true;string === number;// falsestring === number + "";// true+string === number;// truebool === number;// false+bool === number;// truebool === string;// falsebool === !!string;// true
// 3.B.2.3var array = [ "a", "b", "c" ];!!~array.indexOf( "a" );// true!!~array.indexOf( "b" );// true!!~array.indexOf( "c" );// true!!~array.indexOf( "d" );// false// Note que o que está acima deve ser considerado// "desnecessariamente inteligente".// Prefira a aproximação óbvia de comparar o valor retornado do// indexOf, como por exemplo:if ( array.indexOf( "a" ) >= 0 ) {// ...}
// 3.B.2.3var num = 2.5;parseInt( num, 10 );// é o mesmo que...~~num;num >> 0;num >>> 0;// Todos resultam em 2// De qualquer forma, lembre-se que números negativos são tratados// de forma diferente...var neg = -2.5;parseInt( neg, 10 );// é o mesmo que...~~neg;neg >> 0;// Resulta em -2// Porém...neg >>> 0;// Vai resultar em 4294967294
// 4.1.1// Quando estiver apenas avaliando se um array tem tamanho,// ao invés disso:if ( array.length > 0 ) ...// ...avalie a verdade lógica, como isso:if ( array.length ) ...// 4.1.2// Quando estiver apenas avaliando se um array está vazio,// ao invés disso:if ( array.length === 0 ) ...// ...avalie a verdade lógica, como isso:if ( !array.length ) ...// 4.1.3// Quando estiver apenas avaliando se uma string não está vazia,// ao invés disso:if ( string !== "" ) ...// ...avalie a verdade lógica, como isso:if ( string ) ...// 4.1.4// Quando estiver apenas avaliando se uma string está vazia,// ao invés disso:if ( string === "" ) ...// ...avalie se ela é logicamente falsa, como isso:if ( !string ) ...// 4.1.5// Quando estiver avaliando se uma referência é verdadeira,// ao invés disso:if ( foo === true ) ...// ...avalie como se quisesse isso, use a vantagem de suas capacidades primitivas:if ( foo ) ...// 4.1.6// Quando estiver avaliando se uma referência é falsa,// ao invés disso:if ( foo === false ) ...// ...use a negação para coagir para uma avaliação verdadeiraif ( !foo ) ...// ...Seja cuidadoso, isso também irá funcionar com: 0, "", null, undefined, NaN// Se você _PRECISA_ testar um valor falso de tipo booleano, então useif ( foo === false ) ...// 4.1.7// Quando apenas estiver avaliando uma referência que pode ser `null` ou `undefined`, mas NÃO `false`, "" ou 0,// ao invés disso:if ( foo === null || foo === undefined ) ...// ...aproveite a vantagem da coerção de tipo com ==, como isso:if ( foo == null ) ...// Lembre-se, utilizando == irá funcionar em um `null` TANTO para `null` quanto para `undefined`// mas não para `false`, "" ou 0null == undefined
SEMPRE avalie para o melhor e mais preciso resultado - o que está acima é uma recomendação, não um dogma.
// 4.2.1// Coerção de tipo e notas sobre avaliaçõesPrefira `===` ao invés de `==` (ao menos em casos que necessitem avaliação de tipos flexíveis)=== não faz coerção de tipo, o que significa que:"1" === 1;// false== faz coerção de tipo, o que significa que:"1" == 1;// true// 4.2.2// Booleanos, verdades e negações// Booleanos:true, false// Verdades:"foo", 1// Negações:"", 0, null, undefined, NaN, void 0
// 5.1.1// Um módulo prático(function( global ) {var Module = (function() {var data = "segredo";return {// Essa é uma propriedade booleanabool: true,// Algum valor de stringstring: "uma string",// Uma propriedade em arrayarray: [ 1, 2, 3, 4 ],// Uma propriedade em objetoobject: {lang: "pt-BR"},getData: function() {// pega o valor atual de `data`return data;},setData: function( value ) {// atribui o valor a data que é retornadoreturn ( data = value );}};})();// Outras coisas que também podem acontecer aqui// Expor seu módulo ao objeto globalglobal.Module = Module;})( this );
// 5.2.1// Um construtor prático(function( global ) {function Ctor( foo ) {this.foo = foo;return this;}Ctor.prototype.getFoo = function() {return this.foo;};Ctor.prototype.setFoo = function( val ) {return ( this.foo = val );};// Para chamar um construtor sem o `new`, você pode fazer assim:var ctor = function( foo ) {return new Ctor( foo );};// exponha nosso construtor ao objeto globalglobal.ctor = ctor;})( this );
A. Se você não é um compilador humano ou compactador de código, não tente ser um.
O código a seguir é um exemplo de nomenclatura ruim:
// 6.A.1.1// Exemplo de código com nomenclaturas fracasfunction q(s) {return document.querySelectorAll(s);}var i,a=[],els=q("#foo");for(i=0;i<els.length;i++){a.push(els[i]);}
Sem dúvida, você já deve ter escrito código assim - provavelmente isso acaba hoje.
Aqui temos o mesmo trecho lógico, porém com uma nomenclatura simpática e mais inteligente (e uma estrutura legível):
// 6.A.2.1// Exemplo de código com nomenclatura melhoradafunction query( selector ) {return document.querySelectorAll( selector );}var idx = 0,elements = [],matches = query("#foo"),length = matches.length;for( ; idx < length; idx++ ){elements.push( matches[ idx ] );}
Algumas indicações adicionais de nomenclaturas
// 6.A.3.1// Nomes de strings`dog` é uma string// 6.A.3.2// Nomes de arrays`dogs` é uma array de strings `dog`// 6.A.3.3// Nomes de funções, objetos, instancias, etc// funções e declarações de variáveiscamelCase;// 6.A.3.4// Nomes de construtores, protótipos, etc// função construtoraPascalCase;// 6.A.3.5// Nomes de expressões regularesrDesc = //;// 6.A.3.6// Do Guia de Estilos da Biblioteca do Google ClosurefuncoesNomeadasAssim;variaveisNomeadasAssim;ConstrutoresNomeadosAssim;EnumNomeadosAssim;metodosNomeadosAssim;CONSTANTES_SIMBOLICAS_ASSIM;// nota da tradução: não havia tradução no Google Closure, o original é o seguinte:functionNamesLikeThis;variableNamesLikeThis;ConstructorNamesLikeThis;EnumNamesLikeThis;methodNamesLikeThis;SYMBOLIC_CONSTANTS_LIKE_THIS;
B. Faces do `this`Além dos mais conhecidos casos de uso do `call` e `apply`, sempre prefira `.bind( this )` ou um equivalente funcional, para criar definições `BoundFunction` que possam ser invocadas posteriormente. Somente recorra ao aliasing quando não houver disponível uma outra opção preferencial.
// 6.B.1function Device( opts ) {this.value = null;// abre um stream assíncrono,// isso será chamado continuamentestream.read( opts.path, function( data ) {// Atualiza o valor atual dessa instancia// com o valor mais recente do// data streamthis.value = data;}.bind(this) );// Suprime a frequencia de eventos emitidos por// essa instancia de DevicesetInterval(function() {// Emite um evento suprimidothis.emit("event");}.bind(this), opts.freq || 100 );}// Apenas suponha que nós temos herdado um EventEmitter ;)
Quando não disponível, equivalentes funcionais ao `.bind` existem em muitas bibliotecas JavaScript modernas.
// 6.B.2// ex.: lodash/underscore, _.bind()function Device( opts ) {this.value = null;stream.read( opts.path, _.bind(function( data ) {this.value = data;}, this) );setInterval(_.bind(function() {this.emit("event");}, this), opts.freq || 100 );}// ex.: jQuery.proxyfunction Device( opts ) {this.value = null;stream.read( opts.path, jQuery.proxy(function( data ) {this.value = data;}, this) );setInterval( jQuery.proxy(function() {this.emit("event");}, this), opts.freq || 100 );}// ex.: dojo.hitchfunction Device( opts ) {this.value = null;stream.read( opts.path, dojo.hitch( this, function( data ) {this.value = data;}) );setInterval( dojo.hitch( this, function() {this.emit("event");}), opts.freq || 100 );}
Como último recurso, crie uma referência ao `this` utilizando `self` como identificador. Isso é bastante propenso a bugs e deve ser evitado sempre que possível.
// 6.B.3function Device( opts ) {var self = this;this.value = null;stream.read( opts.path, function( data ) {self.value = data;});setInterval(function() {self.emit("event");}, opts.freq || 100 );}
C. Utilize `thisArg`Vários metodos de prototipagem internos do ES 5.1 vem com a assinatura especial de 'thisArg', que deve ser utilizada quando for possível.
// 6.C.1var obj;obj = { f: "foo", b: "bar", q: "qux" };Object.keys( obj ).forEach(function( key ) {// |this| agora se refere a `obj`console.log( this[ key ] );}, obj ); // <-- o último argumento é `thisArg`// Prints...// "foo"// "bar"// "qux"
`thisArg` pode ser utilizado com `Array.prototype.every`, `Array.prototype.forEach`, `Array.prototype.some`, `Array.prototype.map`, `Array.prototype.filter`
- Miscelânea
Esta seção deve servir para ilustrar idéias e conceitos sobre como não se considerar isso como um dogma, mas ao invés disso deve encorajar o questionamento de práticas na tentativa de encontrar formas melhores para executar tarefas comuns na programação em JavaScript.
A. Evite utilizar
switch
, métodos modernos de verificação deverão adicionar funções comswitch
em suas listas negrasParecem haver melhorias drásticas à execução do comando
switch
nas últimas versões do Firefox e do Chrome: http://jsperf.com/switch-vs-object-literal-vs-moduleMelhorias notáveis podem ser observadas aqui também: https://github.com/rwldrn/idiomatic.js/issues/13
// 7.A.1.1// Um exemplo de uma instrução switchswitch( foo ) {case "alpha":alpha();break;case "beta":beta();break;default:// algo para executar por padrãobreak;}// 7.A.1.2// Uma maneira alternativa de dar suporte para facilidade de composição e// reutiilização é utilizar um objeto que guarde "cases" e uma função// para delegar:var cases, delegator;// Retornos de exemplo apenas para ilustração.cases = {alpha: function() {// instruções// um retornoreturn [ "Alpha", arguments.length ];},beta: function() {// instruções// um retornoreturn [ "Beta", arguments.length ];},_default: function() {// instruções// um retornoreturn [ "Default", arguments.length ];}};delegator = function() {var args, key, delegate;// Transforma a lista de argumentos em uma arrayargs = [].slice.call( arguments );// Retira a chave inicial dos argumentoskey = args.shift();// Atribui o manipulador de caso padrãodelegate = cases._default;// Deriva o método para delegar a operação paraif ( cases.hasOwnProperty( key ) ) {delegate = cases[ key ];}// O argumento de escopo pode ser definido para algo específico// nesse caso, |null| será suficientereturn delegate.apply( null, args );};// 7.A.1.3// Coloque a API do 7.A.1.2 para funcionar:delegator( "alpha", 1, 2, 3, 4, 5 );// [ "Alpha", 5 ]// Claro que a argumento de chave inicial pode ser facilmente baseada// em alguma outra condição arbitrária.var caseKey, someUserInput;// Possivelmente alguma maneira de entrada de formulário?someUserInput = 9;if ( someUserInput > 10 ) {caseKey = "alpha";} else {caseKey = "beta";}// ou...caseKey = someUserInput > 10 ? "alpha" : "beta";// E assim...delegator( caseKey, someUserInput );// [ "Beta", 1 ]// E claro...delegator();// [ "Default", 0 ]
B. Retornos antecipados promovem legibilidade de código com mínima diferença de performance
// 7.B.1.1// Ruim:function returnLate( foo ) {var ret;if ( foo ) {ret = "foo";} else {ret = "quux";}return ret;}// Bom:function returnEarly( foo ) {if ( foo ) {return "foo";}return "quux";}
- Objetos nativos e hospedados
O princípio básico aqui é:
Não faça coisas estúpidas e tudo vai ficar bem.
Para reforçar esse conceito, por favor, assista essa apresentação:
“Everything is Permitted: Extending Built-ins” por Andrew Dupont (JSConf2011, Portland, Oregon)
- Uma linha única acima do código que é comentado
- Multiplas linhas é bom
- Comentários ao final da linha são proibidos!
- O estilo do JSDoc é bom, porém requer um investimento de tempo significante
- Código em apenas um idioma
Programas devem ser escritos em um único idioma, não importe o idioma que seja, a ser definido por quem o mantém.
Apêndice
Vírgula inicial.
Qualquer projeto que cite este documento como seu guia base de estilo não aceitará formatação de código com vírgula inicial, a não ser que isso seja explicitamente especificado de outra forma pelo autor do projeto.