Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Conceitos do livro "Clean Code" adaptados para TypeScript

License

NotificationsYou must be signed in to change notification settings

vitorfreitas/clean-code-typescript

 
 

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

54 Commits
 
 
 
 
 
 
 
 

Repository files navigation

Conceitos deCódigo Limpo adaptados para TypeScript.Inspirado emclean-code-javascript

Conteúdos

  1. Introdução
  2. Variáveis
  3. Funções
  4. Objetos e Estruturas de dados
  5. Classes
  6. SOLID
  7. Testando
  8. Concorrência
  9. Tratamento de erros
  10. Formatação
  11. Comentários

Introdução

Humorous image of software quality estimation as a count of how many expletives you shout when reading code

Principios da engenharia de software, do livro de Robert C. MartinCódigo Limpo, para TypeScript. Isto não é um style guide.É um guia para desenvolver softwarelegível, reutilizavel, e refatorável em TypeScript.

Nem todos principios contidos aqui tem de ser seguidos estritamente,e muito menos irão ser universalmente aprovados.Estes são apenas guias e nada mais, mas que foram codificados durante muitoanos por experiências coletivas dos autores deCódigo Limpo.

Nosso trabalho de engenharia de software tem aproximadamente 50 anos de idade,e ainda estamos aprendendo muito. Quando arquitetura de software foralgo antigo como uma arquitetura em si, talvez então teremos regras maisrígidas para serem seguidas. Por enquanto, deixe esses guias servirem comoreferências pelo qual podemos avaliar a qualidade do código JavaScript quevocê e seu time produz.

Mais uma coisa: Saber de tudo isso não vai te tornar um melhor desenvolvedorimediatamente, e trabalhar com isso por muitos anos não significa que vocênão irá cometer erros. Todo pedaço de código começa como um rascunho, comoargila se moldando para sua forma final. E finalmente, cortamos fora asimperfeições quando revemos isso com nossos parceiros. Não se deixe abalarpelos primeiros rascunhos que precisam de melhorias. Mande ver no seu código aoinvés disso!

⬆ ir para o topo

Variáveis

Use nomes significantes em suas variáveis

Diferencie os nomes de tal forma que o leitor saiba as diferença entre eles

Ruim:

functionbetween<T>(a1:T,a2:T,a3:T){returna2<=a1&&a1<=a3;}

Bom:

functionbetween<T>(value:T,left:T,right:T){returnleft<=value&&value<=right;}

⬆ ir para o topo

Use nomes pronunciáveis

Se você não consegue pronunciar sua variável, você não consegue argumentar semparecer um idiota.

Ruim:

classDtaRcrd102{privategenymdhms:Date;privatemodymdhms:Date;privatepszqint='102';}

Bom:

classCustomer{privategenerationTimestamp:Date;privatemodificationTimestamp:Date;privaterecordId='102';}

⬆ ir para o topo

Use o mesmo vocabulário para o mesmo tipo de variável

Ruim:

functiongetUserInfo():User;functiongetUserDetails():User;functiongetUserData():User;

Bom:

functiongetUser():User;

⬆ ir para o topo

Use nomes fáceis de pesquisar

Nós vamos ler mais código do que escrever. É importante que o código que escrevemos seja legível e fácil de achar. Ao não nomear variáveis que acabam sendo inúteis para entender nosso programa, machucamos nossos leitores. Faça seus nomes pesquisáveis. Ferramentas comoTSLint podem te ajudar a identificar constantes sem nome.

Ruim:

// What the heck is 86400000 for?setTimeout(restart,86400000);

Bom:

// Declare them as capitalized named constants.constMILLISECONDS_IN_A_DAY=24*60*60*1000;setTimeout(restart,MILLISECONDS_IN_A_DAY);

⬆ ir para o topo

Use variáveis explicativas

Ruim:

declareconstusers:Map<string,User>;for(constkeyValueofusers){// iterate through users map}

Bom:

declareconstusers:Map<string,User>;for(const[id,user]ofusers){// iterate through users map}

⬆ ir para o topo

Evite mapear mentalmente

Explicito é melhor que implito.Clareza é um Rei.

Ruim:

constu=getUser();consts=getSubscription();constt=charge(u,s);

Bom:

constuser=getUser();constsubscription=getSubscription();consttransaction=charge(user,subscription);

⬆ ir para o topo

Não adicione contextos desnecessários

If your class/object name tells you something, don't repeat that in your variable name.

Se o nome da sua classe/objeto expressa algo, não repita isso no nome da variável.

Ruim:

typeCar={carMake:string;carModel:string;carColor:string;};functionprint(car:Car):void{console.log(`${this.carMake}${this.carModel} (${this.carColor})`);}

Bom:

typeCar={make:string;model:string;color:string;};functionprint(car:Car):void{console.log(`${this.make}${this.model} (${this.color})`);}

⬆ ir para o topo

Use argumentos padrões ao invés de encadear condicionais

Argumentos padrões são normalmente mais limpos que condicionais.

Ruim:

functionloadPages(count?:number){constloadCount=count!==undefined ?count :10;// ...}

Bom:

functionloadPages(count:number=10){// ...}

⬆ ir para o topo

Funções

Argumentos de funções (2 ou menos, idealmente)

Limitar a quantidade de parametros de uma função é incrivelmente importantanteporque isso torna sua função fácil de testar.Ter mais de três de leva em uma explosão onde você tem que testar várioscasos diferentes, com argumentos separados.

Um ou dois argumentos é o caso ideal, e três deve ser evitado se possível.Algo além disso deve ser deixado de lado.Usualmente, se você tem mais de dois argumentos, suas funções estão tentando fazercoisas demais.Nos casos que não estão, na maior parte do tempo um objeto irá ser o suficiente como argumento.

Considere usar objetos caso sinta necessidade de enviar muitos argumentos.

Para deixar explicitos quais propriedades suas funções esperam, você pode usardesestruturação.Aqui vão algumas vantagens:

  1. Quando alguém olhar a assinatura da função, imediatamente será claro quais propriedades estão sendo usadas.

  2. Desestruturação também clone os valores primitivos especificados do objeto passado como argumento para a função. Isso ajuda a evitar efeitos colaterais. Nota: Objetos e Arrays que são desestruturados do objetoargument não são clonados.

  3. TypeScript irá te avisar quando haver propriedades não utilizadas, o que seria impossivel sem usar desestruturação.

Ruim:

functioncreateMenu(title:string,body:string,buttonText:string,cancellable:boolean,){// ...}createMenu('Foo','Bar','Baz',true);

Bom:

functioncreateMenu(options:{title:string;body:string;buttonText:string;cancellable:boolean;}){// ...}createMenu({title:'Foo',body:'Bar',buttonText:'Baz',cancellable:true,});

Você ainda pode aumentar a legibilidade ao utilizar ostype aliases do TypeScript.

typeMenuOptions={title:string;body:string;buttonText:string;cancellable:boolean;};functioncreateMenu(options:MenuOptions){// ...}createMenu({title:'Foo',body:'Bar',buttonText:'Baz',cancellable:true,});

⬆ ir para o topo

Funções devem fazer somente uma coisa

Esta é, de longe, a regra mais importante da engenharia de software. Quando funções fazem mais de uma coisa, elas são mais difíceis de compor, testar e pensar sobre. Quando você consegue isolar a função para apenas uma ação, elas podem ser refatoradas fácilmente e teu código será fácilmente lido. Se você ignorar todo o resto deste guia além dessa dica, você já estará a frente de vários outros desenvolvedores.

Ruim:

functionemailClients(clients:Client[]){clients.forEach(client=>{constclientRecord=database.lookup(client);if(clientRecord.isActive()){email(client);}});}

Bom:

functionemailClients(clients:Client[]){clients.filter(isActiveClient).forEach(email);}functionisActiveClient(client:Client){constclientRecord=database.lookup(client);returnclientRecord.isActive();}

⬆ ir para o topo

Nomes das funções devem dizer o que elas fazem

Ruim:

functionaddToDate(date:Date,month:number):Date{// ...}constdate=newDate();// It's hard to tell from the function name what is addedaddToDate(date,1);

Bom:

functionaddMonthToDate(date:Date,month:number):Date{// ...}constdate=newDate();addMonthToDate(date,1);

⬆ ir para o topo

Funções devem estar em apenas um nível de abstração

Quando você tem mais de um nível de abstração possívelmente sua função está fazendo coisa demais. Dividir suas funções desencadeia em código reusável e fácil de testar.

Ruim:

functionparseCode(code:string){constREGEXES=[/* ... */];conststatements=code.split(' ');consttokens=[];REGEXES.forEach(regex=>{statements.forEach(statement=>{// ...});});constast=[];tokens.forEach(token=>{// lex...});ast.forEach(node=>{// parse...});}

Bom:

constREGEXES=[/* ... */];functionparseCode(code:string){consttokens=tokenize(code);constsyntaxTree=parse(tokens);syntaxTree.forEach(node=>{// parse...});}functiontokenize(code:string):Token[]{conststatements=code.split(' ');consttokens:Token[]=[];REGEXES.forEach(regex=>{statements.forEach(statement=>{tokens.push(/* ... */);});});returntokens;}functionparse(tokens:Token[]):SyntaxTree{constsyntaxTree:SyntaxTree[]=[];tokens.forEach(token=>{syntaxTree.push(/* ... */);});returnsyntaxTree;}

⬆ ir para o topo

Remove código duplicado

Faça o seu melhor para evitar código duplicado.Código duplicado é ruim pois significa que há mais de um lugar para ser alterado se houver alguma mudança na lógica.

Imagine que você tem um restaurante que mantém uma lista do seu inventário: todos seus tomates, cebolas, alho, pimentas, etc.Se você tem multiplas listas que contém esses dados, então todas irão ser modificadas quando você servir um prato com tomates, por exemplo.Se você tem apenas uma lista, então este será o único lugar a ser alterado.

Algumas vezes você tem códigos duplicados porque há duas ou mais coisas diferentes, mas que compartilham muito em comum, mas suas diferenças os forçam a ter duas ou mais funções separadas que fazem muito das mesmas coisas. Remover código duplicado significa criar uma abstração que pode lidar com essas diferenças com apenas uma função/módulo/classe.

Ter sua abstração do jeito certo é algo crítico, por isso você deve seguir os principiosSOLID. Más abstrações podem ser pior que código duplicado, então tome cuidado! Com isto dito, se você pode fazer uma boa abstração, faça! Não repita você mesmo, ou então você se encontrará atualizando vários lugares toda vez que desejar alterar uma coisa.

Ruim:

functionshowDeveloperList(developers:Developer[]){developers.forEach(developer=>{constexpectedSalary=developer.calculateExpectedSalary();constexperience=developer.getExperience();constgithubLink=developer.getGithubLink();constdata={      expectedSalary,      experience,      githubLink,};render(data);});}functionshowManagerList(managers:Manager[]){managers.forEach(manager=>{constexpectedSalary=manager.calculateExpectedSalary();constexperience=manager.getExperience();constportfolio=manager.getMBAProjects();constdata={      expectedSalary,      experience,      portfolio,};render(data);});}

Bom:

classDeveloper{// ...getExtraDetails(){return{githubLink:this.githubLink,};}}classManager{// ...getExtraDetails(){return{portfolio:this.portfolio,};}}functionshowEmployeeList(employee:Developer|Manager){employee.forEach(employee=>{constexpectedSalary=developer.calculateExpectedSalary();constexperience=developer.getExperience();constextra=employee.getExtraDetails();constdata={      expectedSalary,      experience,      extra,};render(data);});}

Você deve ser duro quando o assunto for código duplicado. As vezes há uma troca entre código duplicado e complexidade aumentada quando introduz abstrações desnecessárias. Quando duas implementações de módulos diferentes se parecem bastante mas vivem em diferentes lugares, código duplicado pode ser aceitável e preferível à extrair para um código comum entre os lugares. Nesse caso, o código que seria extraido iria criar uma dependência indireta entre os dois módulos.

⬆ ir para o topo

Define objetos padrões utilizando Object.assign ou desestruturação

Ruim:

typeMenuConfig={title?:string;body?:string;buttonText?:string;cancellable?:boolean;};functioncreateMenu(config:MenuConfig){config.title=config.title||'Foo';config.body=config.body||'Bar';config.buttonText=config.buttonText||'Baz';config.cancellable=config.cancellable!==undefined ?config.cancellable :true;}constmenuConfig={title:null,body:'Bar',buttonText:null,cancellable:true,};createMenu(menuConfig);

Bom:

typeMenuConfig={title?:string;body?:string;buttonText?:string;cancellable?:boolean;};functioncreateMenu(config:MenuConfig){constmenuConfig=Object.assign({title:'Foo',body:'Bar',buttonText:'Baz',cancellable:true,},config,);}createMenu({body:'Bar'});

Alternativamente, você pode usar desestruturação com valores predefinidos:

typeMenuConfig={title?:string;body?:string;buttonText?:string;cancellable?:boolean;};functioncreateMenu({  title='Foo',  body='Bar',  buttonText='Baz',  cancellable=true,}:MenuConfig){// ...}createMenu({body:'Bar'});

Para evitar efeitos colaterais e comportamentos indesejados ao passar explicitamenteundefined ounull, você pode dizer ao compilador TypeScript para não permitir isso. Veja mais em--strictNullChecks.

⬆ ir para o topo

Não use flags como parâmetros de funções

Flags indicam ao seu usuário que a função faz mais de uma coisa.Funções devem fazer apenas uma coisa. Divida sua função se ela segue diferentes caminhos baseados em uma condição.

Ruim:

functioncreateFile(name:string,temp:boolean){if(temp){fs.create(`./temp/${name}`);}else{fs.create(name);}}

Bom:

functioncreateFile(name:string){fs.create(name);}functioncreateTempFile(name:string){fs.create(`./temp/${name}`);}

⬆ ir para o topo

Evite efeitos colaterais (parte 1)

Uma função produz efeitos colaterais se ela faz algo além de receber um valor e retornar outro valor ou valores.Efeitos colaterais poderia ser escrever em um arquivo, modificar alguma variável global, ou acidentamente transferir todo seu dinheiro para um estranho.

Agora, você precisa ter efeitos colaterais em algumas ocasiões. Como no exemplo anterior, onde você precisa escrever em um arquivo.O que você deseja fazer é centralizar onde você está fazendo isto, ao invés de ter várias funções e classes que escrevem em um só arquivo.Tenha um serviço que faça isso. Um, e apenas um.

O ponto principal é evitar alguns vacilos como compartilhar o estado entre dois objetos sem nenhuma estrutura, usando tipo de dados mutáveis que podem ser escritos por qualquer coisas, e não centralizar onde seus efeitos colaterais vão ocorrer. Se você pode fazer isso, você será mais feliz que a maioria dos outros programadores.

Ruim:

// Global variable referenced by following function.// If we had another function that used this name, now it'd be an array and it could break it.letname='Robert C. Martin';functiontoBase64(){name=btoa(name);}toBase64();// produces side effects to `name` variableconsole.log(name);// expected to print 'Robert C. Martin' but instead 'Um9iZXJ0IEMuIE1hcnRpbg=='

Bom:

// Global variable referenced by following function.// If we had another function that used this name, now it'd be an array and it could break it.constname='Robert C. Martin';functiontoBase64(text:string):string{returnbtoa(text);}constencodedName=toBase64(name);console.log(name);

⬆ ir para o topo

Evite efeitos colaterais (parte 2)

No JavaScript, primitivos são passados por valores e objetos/arrays são passados por referência. No caso dos objetos e arrays, se sua função faz uma mudança em um array de carrinho de loja, por exemplo, adicionando um item à compra, então todas outras funções que usam esse array serão afetadas por esta adição. Isso pode ser bom, mas pode ser ruim também. Vamos imaginar um cenário ruim:

O usuário clica em "Comprar", botão que chama uma função de compra, que envia uma requisição com o array de carrinho de compras ao servidor. Por conta de uma conexão ruim, a função de comprar precisa ficar tentando novamente a requisição. Agora, e se o usuário, neste meio tempo, clicar no botão "Adicionar ao carrinho", em um item que ele não quer, antes da requisição começar? Se isso acontecer e a requisição começar, a função de compra irá enviar o item adicionado acidentalmente, pois este tem a referência do mesmo array anterior e que a funçãoaddItemToCart modificou, ao adicionar um item novo.

Uma ótima solução seria a funçãoaddItemToCart sempre clonar o carrinho, editar, e retornar o clone. Isso assegura que nenhuma outra função que tem a referência do carrinho será afetada pelas mudanças.

Dois avisos ao mencionar essa abordagem:

  1. Podem haver casos onde você quer modificar o objeto inputado, mas quando você adota esse prática você verá que esses casos são bem raros. A maiorias das coisas podem ser refatoradas para não terem efeitos colaterais! (vejafunções puras)

  2. Clonar grandes objetos pode ser bem caro em termos de performance. Com sorte, isso não é um grande problema na prática pois há ótimas bibliotecas que permitem esse tipo de abordagem serem rápidas e não tão intensivas no consumo de memória, como seria em clonar os objetos manualmente.

Ruim:

functionaddItemToCart(cart:CartItem[],item:Item):void{cart.push({item,date:Date.now()});}

Bom:

functionaddItemToCart(cart:CartItem[],item:Item):CartItem[]{return[...cart,{item,date:Date.now()}];}

⬆ ir para o topo

Não escreva em funções globais

Poluir escopos globais é uma má prática em JavaScript pois você pode colidir com código de outra biblioteca, e o usuário da sua API não será esclarecido até ele receber uma erro em produção. Vamos pensar no exemplo a seguir: E se você quiser extender o código nativo do Array em JavaScript, para ter uma função diff que pode mostrar a diferença entre dois arrays? Você pode escrever sua nova função emArray.prototype, mas isso iria colidir com código de outra biblioteca que tenta fazer a mesma coisa. E se essa biblioteca usa o métododiff para achar a diferença entre o primeiro e o último elemento de um array? Por isso seria muito melhor usar classes e simplesmente extender oArray global.

Ruim:

declare global{interfaceArray<T>{diff(other:T[]):Array<T>;}}if(!Array.prototype.diff){Array.prototype.diff=function<T>(other:T[]):T[]{consthash=newSet(other);returnthis.filter(elem=>!hash.has(elem));};}

Bom:

classMyArray<T>extendsArray<T>{diff(other:T[]):T[]{consthash=newSet(other);returnthis.filter(elem=>!hash.has(elem));}}

⬆ ir para o topo

Priorize programação funcional à programação imperativa

Use esse tipo de paradigma quando puder.

Ruim:

constcontributions=[{name:'Uncle Bobby',linesOfCode:500,},{name:'Suzie Q',linesOfCode:1500,},{name:'Jimmy Gosling',linesOfCode:150,},{name:'Gracie Hopper',linesOfCode:1000,},];lettotalOutput=0;for(leti=0;i<contributions.length;i++){totalOutput+=contributions[i].linesOfCode;}

Bom:

constcontributions=[{name:'Uncle Bobby',linesOfCode:500,},{name:'Suzie Q',linesOfCode:1500,},{name:'Jimmy Gosling',linesOfCode:150,},{name:'Gracie Hopper',linesOfCode:1000,},];consttotalOutput=contributions.reduce((totalLines,output)=>totalLines+output.linesOfCode,0,);

⬆ ir para o topo

Encapsular condicionais

Ruim:

if(subscription.isTrial||account.balance>0){// ...}

Bom:

functioncanActivateService(subscription:Subscription,account:Account){returnsubscription.isTrial||account.balance>0;}if(canActivateService(subscription,account)){// ...}

⬆ ir para o topo

Evite condicionais negativas

Ruim:

functionisEmailNotUsed(email:string){// ...}if(isEmailNotUsed(email)){// ...}

Bom:

functionisEmailUsed(email){// ...}if(!isEmailUsed(node)){// ...}

⬆ Ir para o topo

Evite condicionais

Isso parece uma tarefa impossivel. Quando se escuta isso pela primeira vez, a maioria das pessoas dizem, "Como eu posso fazer qualquer coisa sem declarar umif?" A resposta é que você pode usar polimorfismo para alcançar o mesmo objetivo em muitos casos. A segunda pergunta normalmente é,"Bom isso é otimo, mas por que eu iria querer fazer isso?" A resposta é um conceito anterior de codigo limpo que aprendemos antes: Uma função deve fazer apenas uma coisa. Quando você tem classes e funções comif declarados, você está falando para seu usuario que sua função faz mais que uma coisa. Lembre-se apenas faça uma coisa.

Ruim:

classAirplane{privatetype:string;// ...getCruisingAltitude(){switch(this.type){case'777':returnthis.getMaxAltitude()-this.getPassengerCount();case'Air Force One':returnthis.getMaxAltitude();case'Cessna':returnthis.getMaxAltitude()-this.getFuelExpenditure();default:thrownewError('Unknown airplane type.');}}}

Bom:

classAirplane{// ...}classBoeing777extendsAirplane{// ...getCruisingAltitude(){returnthis.getMaxAltitude()-this.getPassengerCount();}}classAirForceOneextendsAirplane{// ...getCruisingAltitude(){returnthis.getMaxAltitude();}}classCessnaextendsAirplane{// ...getCruisingAltitude(){returnthis.getMaxAltitude()-this.getFuelExpenditure();}}

⬆ ir para o topo

Evite verificação de tipo

TypeScript é um superconjunto sintático estrito de JavaScript e adciona verificação de tipo estático para a linguagem.Prefira sempre especificar tipos de variáveis, parâmetros e retornar valores para aproveitar todo o potencial dos recursos do TypeScript.Isso torna refatoração mais fácil.

Ruim:

functiontravelToTexas(vehicle:Bicycle|Car){if(vehicleinstanceofBicycle){vehicle.pedal(this.currentLocation,newLocation('texas'));}elseif(vehicleinstanceofCar){vehicle.drive(this.currentLocation,newLocation('texas'));}}

Bom:

typeVehicle=Bicycle|Car;functiontravelToTexas(vehicle:Vehicle){vehicle.move(this.currentLocation,newLocation('texas'));}

⬆ ir para o topo

Não otimize demais

Navegadores modernos fazem muita otimização por baixo dos panos na hora da execução. Muitas vezes, se você está otimizando então, você está apenas perdendo seu tempo. Há bonsrecursos para ver aonde está faltando otimização. Procure esses, até que eles sejam corrigidos caso possam ser.

Ruim:

// On old browsers, each iteration with uncached `list.length` would be costly// because of `list.length` recomputation. In modern browsers, this is optimized.for(leti=0,len=list.length;i<len;i++){// ...}

Bom:

for(leti=0;i<list.length;i++){// ...}

⬆ ir para o topo

Remover código morto

Código morto é tão ruim quanto código duplicado. Não há razão alguma para mantê-lo na sua base de códigos.Se não está sendo chamado, livre-se dele! Ainda continuará seguro no seu histórico de versões se você ainda precisar.

Ruim:

functionoldRequestModule(url:string){// ...}functionrequestModule(url:string){// ...}constreq=requestModule;inventoryTracker('apples',req,'www.inventory-awesome.io');

Bom:

functionrequestModule(url:string){// ...}constreq=requestModule;inventoryTracker('apples',req,'www.inventory-awesome.io');

⬆ ir para o topo

Use iterators e generators

Use generators e iterables quando trabalhar com coleções de dados que se comportam como uma corrente/fluxo.Há alguns bons motivos para isso:

  • desacopla quem está chamando o generator, deixando a ele a opção de quantos itens deseja acessar
  • 'lazy execution', os itens são chamados por demanda
  • suporte nativo para iterar itens utilizando a sintaxefor-of
  • 'iterables' permitem implementar um padrão mais otimizado de 'iterators'

Ruim:

functionfibonacci(n:number):number[]{if(n===1)return[0];if(n===2)return[0,1];constitems:number[]=[0,1];while(items.length<n){items.push(items[items.length-2]+items[items.length-1]);}returnitems;}functionprint(n:number){fibonacci(n).forEach(fib=>console.log(fib));}// Printa os 10 primeiros números de Fibonnaci.print(10);

Bom:

// Gera uma corrente infinita de números de Fibonacci.// O generator não mantém um array com todos os números.function*fibonacci():IterableIterator<number>{let[a,b]=[0,1];while(true){yielda;[a,b]=[b,a+b];}}functionprint(n:number){leti=0;for(constfiboffibonacci()){if(i++===n)break;console.log(fib);}}// Printa os 10 primeiros números de Fibonnaci.print(10);

Tem diversas bibliotecas que permitem trabalhar com iterables de uma forma similar aos arrays nativos, ao encadear métodos comomap,slice,forEach etc. Vejaitiriri para um exemplo avançado de manipulação utilizando iterables (ouitiriri-async para manipulação de iterables assíncronos).

importitiririfrom'itiriri';function*fibonacci():IterableIterator<number>{let[a,b]=[0,1];while(true){yielda;[a,b]=[b,a+b];}}itiriri(fibonacci()).take(10).forEach(fib=>console.log(fib));

⬆ ir para o topo

Objetos e estruturas de dados

Use getters e setters

TypeScript suporta sintaxe de getter/setter.Usar getters e setters para acessar dados de objetos que encapsulam comportamento pode ser melhor do que simplesmente procurar por uma propriedade em um objeto."Por que?" você pode perguntar. Bom, aqui está uma lista de razões:

  • Quando você quer fazer mais além de pegar uma propriedade de um objeto, você não precisa olhar e mudar todos os acessores na sua base de códigos.
  • Faz adicionar validação simples quando está setando.
  • Encapsula a representação interna;
  • Fácil de adicionar logging e tratamento de erros quando usar get e set.
  • Você pode carregar preguiçosamente as propriedades do seu objeto, vamos dizer pegar de um servido.

Ruim:

classBankAccount{balance:number=0;// ...}constvalue=100;constaccount=newBankAccount();if(value<0){thrownewError('Cannot set negative balance.');}account.balance=value;

Bom:

classBankAccount{privateaccountBalance:number=0;getbalance():number{returnthis.accountBalance;}setbalance(value:number){if(value<0){thrownewError('Cannot set negative balance.');}this.accountBalance=value;}// ...}constaccount=newBankAccount();account.balance=100;

⬆ Ir para o topo

Fazer objetos ter membros privados/protegidos

TypeScript suporta acessoresPublico(Padrão),Protegido ePrivado em membros das classes.

Ruim:

classCircle{radius:number;constructor(radius:number){this.radius=radius;}perimeter(){return2*Math.PI*this.radius;}surface(){returnMath.PI*this.radius*this.radius;}}

Bom:

classCircle{constructor(privatereadonlyradius:number){}perimeter(){return2*Math.PI*this.radius;}surface(){returnMath.PI*this.radius*this.radius;}}

⬆ ir para o topo

Prefira propriedades de apenas leitura

TypeScript's type system allows you to mark individual properties on an interface / class as readonly. This allows you to work in a functional way (unexpected mutation is bad).For more advanced scenarios there is a built-in typeReadonly that takes a typeT and marks all of its properties as readonly using mapped types (seemapped types).

O sistema de tipagem do TypeScript permite que você marque propriedades individuais em uma interface/classe como de apenas leitura. Isso permite que você trabalhe de uma maneira funcional (mutações inesperadas são ruins).Para cenários mais avançados há um tipo integradoReadonly que recebe um tipoT e marca todas suas propriedades como de apenas leitura, usando tipos mapeados (mapped types) (vejamapped types).

Ruim:

interfaceConfig{host:string;port:string;db:string;}

Bom:

interfaceConfig{readonlyhost:string;readonlyport:string;readonlydb:string;}

⬆ ir para o topo

Classes

Classes devem ser pequenas

O tamanho de uma clase é mensurado por sua responsabilidade. Seguindo o princípio de única responsabilidade, uma classe deve ser pequena.

Ruim:

classDashboard{getLanguage():string{/* ... */}setLanguage(language:string):void{/* ... */}showProgress():void{/* ... */}hideProgress():void{/* ... */}isDirty():boolean{/* ... */}disable():void{/* ... */}enable():void{/* ... */}addSubscription(subscription:Subscription):void{/* ... */}removeSubscription(subscription:Subscription):void{/* ... */}addUser(user:User):void{/* ... */}removeUser(user:User):void{/* ... */}goToHomePage():void{/* ... */}updateProfile(details:UserDetails):void{/* ... */}getVersion():string{/* ... */}// ...}

Bom:

classDashboard{disable():void{/* ... */}enable():void{/* ... */}getVersion():string{/* ... */}}// divida as responsábilidades movendo os métodos restantes para outras classes// ...

⬆ ir para o topo

Classes coesas e desacopladas

Coesão define o grau de parentesco que um membro de uma classe tem com os outros. Idealmente, cada campo de uma classe deve ser usado por cada um dos métodos.Nós dizemos, então, que uma classe é super coesa. Na prática, nem sempre isso é possível, e nem recomendado em alguns casos. Você deve preferir, entretanto, classes com alta coesão.

Acoplamento se refere ao quanto duas classes são relacionadas ou dependentes umas das outras. Classes são pouco acopladas/desacopladas quando mudanças em uma não afeta a outra.

Um bom design de software temcoesão edesacoplamento

Ruim:

classUserManager{// Bad: each private variable is used by one or another group of methods.// It makes clear evidence that the class is holding more than a single responsibility.// If I need only to create the service to get the transactions for a user,// I'm still forced to pass and instance of emailSender.constructor(privatereadonlydb:Database,privatereadonlyemailSender:EmailSender,){}asyncgetUser(id:number):Promise<User>{returnawaitdb.users.findOne({id});}asyncgetTransactions(userId:number):Promise<Transaction[]>{returnawaitdb.transactions.find({userId});}asyncsendGreeting():Promise<void>{awaitemailSender.send('Welcome!');}asyncsendNotification(text:string):Promise<void>{awaitemailSender.send(text);}asyncsendNewsletter():Promise<void>{// ...}}

Bom:

classUserService{constructor(privatereadonlydb:Database){}asyncgetUser(id:number):Promise<User>{returnawaitdb.users.findOne({id});}asyncgetTransactions(userId:number):Promise<Transaction[]>{returnawaitdb.transactions.find({userId});}}classUserNotifier{constructor(privatereadonlyemailSender:EmailSender){}asyncsendGreeting():Promise<void>{awaitemailSender.send('Welcome!');}asyncsendNotification(text:string):Promise<void>{awaitemailSender.send(text);}asyncsendNewsletter():Promise<void>{// ...}}

⬆ ir para o topo

Prefira composição à herança

Como foi muito bem pontuado emDesign Patterns, pelaGang of Four, você deve preferir composição à herança quando puder. Há ótimos motivos para usar herança, e ótimos motivos para usar composição. O ponto aqui é, se sua mente instintivamente pensa em heranças, tente pensar em como composições poderiam resolver seu problema de forma melhor. Em muitos casos isso é possível.

Você pode estar pensando, "quando devo usar herança?" E isso depende do seu problema, mas aqui vai uma lista de quando usar herança faz mais sentido de que composição:

  1. Sua herança representa uma relação de "é um..." e não "tem um..." (Humano->Animal vs Usuário->Detalhes).

  2. Você pode reutilizar código das classes base (Humanos podem se mover como animais).

  3. Você deseja fazer mudanças globais ao alterar a classe base. (Alterar o gasto de calorias de todos os animais quando se movimentam).

Ruim:

classEmployee{constructor(privatereadonlyname:string,privatereadonlyemail:string){}// ...}// Ruim pois Employees "tem" taxas. EmployeeTaxData não é um tipo de EmployeeclassEmployeeTaxDataextendsEmployee{constructor(name:string,email:string,privatereadonlyssn:string,privatereadonlysalary:number,){super(name,email);}// ...}

Bom:

classEmployee{privatetaxData:EmployeeTaxData;constructor(privatereadonlyname:string,privatereadonlyemail:string){}setTaxData(ssn:string,salary:number):Employee{this.taxData=newEmployeeTaxData(ssn,salary);returnthis;}// ...}classEmployeeTaxData{constructor(publicreadonlyssn:string,publicreadonlysalary:number){}// ...}

⬆ ir para o topo

Use cadeia de métodos

Esse padrão é bem útil e usado normalmente em muitas bibliotecas. Seu uso permite que seu código seja mais expressivo, e menos verboso. Por esse motivo, use cadeia de métodos e olhe depois como seu código irá estar mais limpo.

Ruim:

classQueryBuilder{privatecollection:string;privatepageNumber:number=1;privateitemsPerPage:number=100;privateorderByFields:string[]=[];from(collection:string):void{this.collection=collection;}page(number:number,itemsPerPage:number=100):void{this.pageNumber=number;this.itemsPerPage=itemsPerPage;}orderBy(...fields:string[]):void{this.orderByFields=fields;}build():Query{// ...}}// ...constquery=newQueryBuilder();query.from('users');query.page(1,100);query.orderBy('firstName','lastName');constquery=queryBuilder.build();

Bom:

classQueryBuilder{privatecollection:string;privatepageNumber:number=1;privateitemsPerPage:number=100;privateorderByFields:string[]=[];from(collection:string): this{this.collection=collection;returnthis;}page(number:number,itemsPerPage:number=100): this{this.pageNumber=number;this.itemsPerPage=itemsPerPage;returnthis;}orderBy(...fields:string[]): this{this.orderByFields=fields;returnthis;}build():Query{// ...}}// ...constquery=newQueryBuilder().from('users').page(1,100).orderBy('firstName','lastName').build();

⬆ ir para o topo

SOLID

Principio da única responsabilidade (Single Responsabiliy Principle)

Como dito em Código Limpo, "Não deve haver mais de um motivo para alterar uma classe". É tentador lotar uma classe com várias funcionalidades, como se você só pudesse carregar uma mala em uma viagem. O problema disso é que sua classe não será conceitualmente coesiva e posteriormente te trará vários motivos para mudar. Reduzir a quantidade de vezes que você precisa mudar uma classe é importante. É importante pois se muitas funcionalidades estão contidas em uma classe e você altera um pedaço disso, pode ser difícil entender como isso irá afetar os módulos dependentes da sua classe/do seu código.

Ruim:

classUserSettings{constructor(privatereadonlyuser:User){}changeSettings(settings:UserSettings){if(this.verifyCredentials()){// ...}}verifyCredentials(){// ...}}

Bom:

classUserAuth{constructor(privatereadonlyuser:User){}verifyCredentials(){// ...}}classUserSettings{privatereadonlyauth:UserAuth;constructor(privatereadonlyuser:User){this.auth=newUserAuth(user);}changeSettings(settings:UserSettings){if(this.auth.verifyCredentials()){// ...}}}

⬆ ir para o topo

Prinpio do Aberto/Fechado (Open/Closed Principle)

Como dito por Bertrand Meyer, "entidades em software (classes, módulos, funções, etc.) devem ser abertas para extenções, mas fechadas para modificações." Mas o que isso significa? Este princípio diz, basicamente, que você deve permitir que seus usuários adicionem novas funcionalidades sem alterar código já existente.

Ruim:

classAjaxAdapterextendsAdapter{constructor(){super();}// ...}classNodeAdapterextendsAdapter{constructor(){super();}// ...}classHttpRequester{constructor(privatereadonlyadapter:Adapter){}asyncfetch<T>(url:string):Promise<T>{if(this.adapterinstanceofAjaxAdapter){constresponse=awaitmakeAjaxCall<T>(url);// transforma a resposta e retorna}elseif(this.adapterinstanceofNodeAdapter){constresponse=awaitmakeHttpCall<T>(url);// transforma a resposta e retorna}}}functionmakeAjaxCall<T>(url:string):Promise<T>{// faz a requisição e retorna uma Promise}functionmakeHttpCall<T>(url:string):Promise<T>{// faz a requisição e retorna uma Promise}

Bom:

abstractclassAdapter{abstractasyncrequest<T>(url:string):Promise<T>;}classAjaxAdapterextendsAdapter{constructor(){super();}asyncrequest<T>(url:string):Promise<T>{// faz requisição e retorna uma Promise}// ...}classNodeAdapterextendsAdapter{constructor(){super();}asyncrequest<T>(url:string):Promise<T>{// faz requisição e retorna uma Promise}// ...}classHttpRequester{constructor(privatereadonlyadapter:Adapter){}asyncfetch<T>(url:string):Promise<T>{constresponse=awaitthis.adapter.request<T>(url);// transforma a resposta e retorna}}

⬆ ir para o topo

Principios da Substituição de Liskov (Liskov Substitution Principle)

Este é um termo muito assustador para um conceito bem simples. É formalmente definido como "Se S é um subtipo de T, então os objetos do tipo T podem ser substituidos com objetos do tipo S(ou seja, objetos do tipo S podem substituir objetos do tipo T) sem alterar nenhuma propriedade desejáveis daquele programa (correção, tarefa executada, etc.)." E essa é uma definição ainda mais assustadora.

A melhor explicação para isso é se você tem uma classe pi e uma classe filho, então a classe pai e a classe filho podem ser usada sem ocorrer resultados incorretos. Isso pode ainda estar sendo confuso, então vamos dar uma olhada no exemplo clássico Quadrado-Retângulo. Matemáticamente, o quadrado é um retângulo, mas se você modelar o quadrado usando o relacionamento "é-um" via herança, você terá problemas.

Ruim:

classRectangle{constructor(protectedwidth:number=0,protectedheight:number=0){}setColor(color:string){// ...}render(area:number){// ...}setWidth(width:number){this.width=width;}setHeight(height:number){this.height=height;}getArea():number{returnthis.width*this.height;}}classSquareextendsRectangle{setWidth(width:number){this.width=width;this.height=width;}setHeight(height:number){this.width=height;this.height=height;}}functionrenderLargeRectangles(rectangles:Rectangle[]){rectangles.forEach(rectangle=>{rectangle.setWidth(4);rectangle.setHeight(5);constarea=rectangle.getArea();// BAD: Returns 25 for Square. Should be 20.rectangle.render(area);});}constrectangles=[newRectangle(),newRectangle(),newSquare()];renderLargeRectangles(rectangles);

Bom:

abstractclassShape{setColor(color:string){// ...}render(area:number){// ...}abstractgetArea():number;}classRectangleextendsShape{constructor(privatereadonlywidth=0,privatereadonlyheight=0){super();}getArea():number{returnthis.width*this.height;}}classSquareextendsShape{constructor(privatereadonlylength:number){super();}getArea():number{returnthis.length*this.length;}}functionrenderLargeShapes(shapes:Shape[]){shapes.forEach(shape=>{constarea=shape.getArea();shape.render(area);});}constshapes=[newRectangle(4,5),newRectangle(4,5),newSquare(5)];renderLargeShapes(shapes);

⬆ ir para o topo

Principio da Segragação de Interface (PSI)

PSI afima que "Clientes não deveriam ser forçados a serem dependentes de interfaces que eles não usam". Esse princípio é muito relacionado ao Princípio da única responsabilidade.O que isso realmente significa é que você deve sempre projetar suas abstrações de uma maneira que os clientes que estão usando os métodos expostos não obtenham a "a torta inteira". Isto também inclui aos clientes o dever implementar metódos que eles, na realidade, não precisam.

Ruim:

interfaceISmartPrinter{print();fax();scan();}classAllInOnePrinterimplementsISmartPrinter{print(){// ...}fax(){// ...}scan(){// ...}}classEconomicPrinterimplementsISmartPrinter{print(){// ...}fax(){thrownewError('Fax not supported.');}scan(){thrownewError('Scan not supported.');}}

Bom:

interfaceIPrinter{print();}interfaceIFax{fax();}interfaceIScanner{scan();}classAllInOnePrinterimplementsIPrinter,IFax,IScanner{print(){// ...}fax(){// ...}scan(){// ...}}classEconomicPrinterimplementsIPrinter{print(){// ...}}

⬆ ir para o topo

Princípio da Inversão de Dependência (DIP) (Dependency Inversion Principle)

Esse princípio afirma duas coisas essenciais:

  1. Modulos de alto nível não deveriam ser dependentes de módulos de baixo nível. Ambos devem depender de abstrações.

  2. Abstrações não deveriam ser dependentes de detalhes. Detalhes devem depender de abstrações.

Isso pode ser dificil de entender de primeira, porém se você já trabalhou com Angular, você já viu a implementação desse principio na forma de Injeção de Dependência (Dependency Injection). Entretanto, não são conceitos idênticos, DIP mantém modulos de alto nível conhecendo os detalhes dos módulos de baixo nível e os configura. Isso pode ser feito através da Injeção de Depedencia. Um grande benficio disso é que o acoplamento entre módulos é reduzido. Acoplamento é um padrão muito ruim de desenvolvimento porque faz o seu código ser dificil de refatorar.

DIP é normalmente alcançado através do uso de um container de controle de inversão (IoC). Um exemplo de um container IoC poderoso para o TypeScript é oInversifyJs

Ruim:

import{readFileasreadFileCb}from'fs';import{promisify}from'util';constreadFile=promisify(readFileCb);typeReportData={// ..}classXmlFormatter{parse<T>(content:string):T{// Converts an XML string to an object T}}classReportReader{// BAD: We have created a dependency on a specific request implementation.// We should just have ReportReader depend on a parse method: `parse`privatereadonlyformatter=newXmlFormatter();asyncread(path:string):Promise<ReportData>{consttext=awaitreadFile(path,'UTF8');returnthis.formatter.parse<ReportData>(text);}}// ...constreader=newReportReader();awaitreport=awaitreader.read('report.xml');

Bom:

import{readFileasreadFileCb}from'fs';import{promisify}from'util';constreadFile=promisify(readFileCb);typeReportData={// ..}interfaceFormatter{parse<T>(content:string):T;}classXmlFormatterimplementsFormatter{parse<T>(content:string):T{// Converts an XML string to an object T}}classJsonFormatterimplementsFormatter{parse<T>(content:string):T{// Converts a JSON string to an object T}}classReportReader{constructor(privatereadonlyformatter:Formatter){}asyncread(path:string):Promise<ReportData>{consttext=awaitreadFile(path,'UTF8');returnthis.formatter.parse<ReportData>(text);}}// ...constreader=newReportReader(newXmlFormatter());awaitreport=awaitreader.read('report.xml');// or if we had to read a json report:constreader=newReportReader(newJsonFormatter());awaitreport=awaitreader.read('report.json');

⬆ ir para o topo

Testar

Testar é mais importante do que lançar o software. Se você não tem nenhum teste, ou poucos testes, todas as vezes que você lançar um software você não vai ter certeza de que não quebrou nada.Decidir o que seria uma boa quantidade de testes é de responsabilidade do seu time, mas ter uma cobertura ampla das funcionalidades do seu código é como você atinge confiança e certa paz de espirito. Isso implica que, além de uma boa ferramente para fazer seus testes, precisa de algo para promover essa total cobertura.

Não há desculpas para não escrever testes. Há uma vasta leva de frameworks para testar JavaScript com suporte a tipos com TypeScript, então ache um que seu time prefira. Quando achar um que funcione pro seu time, escreva testes para toda funcionalidade ou módulo que você introduzir. Se seu método preferido é o TDD (Test Driven Development - Desenvolvimento guiado à testes), isso é ótimo, mas o ponto é você ter certeza do código que está lançando, antes de lança-lo, ou refatorar códigos antigos.

As três leis do TDD

  1. Você não pode escrever nenhum código de produção a não ser que seja para fazer um teste unitário quebrado, passar.

  2. Você não pode escrever mais de um teste que falhe; e erros de compilação são erros.

  3. Você não pode esrever mais de um código de produção do que o suficiente para passar o teste unitário que falhou.

⬆ ir para o topo

Regras F.I.R.S.T

Testes limpos devem seguir essas regras:

  • Fast - Rápido testes devem ser rápidos pois queremos roda-los frequentemente.

  • Independent - Independente testes não devem depender um dos outros. Eles devem retornar algo, seja ele rodado sozinho ou com outros testes.

  • Repeatable - Repetitivos testes devem ser repetitivos em qualquer ambiente e não devem haver motivos para eles falharem.

  • Self-Validating - Auto-validado um teste devem responder comPassed ouFailed. Você não tem que comparar com arquivos de log para saber se passaram ou não.

  • Timely - Pontuais testes unitários devem ser escritos antes do código de produção. se você escrever depois, pode parar muito complicado escrever testes.

⬆ ir para o topo

Único conceito por teste

Testes devem seguir também o principio da única responsabilidade. Faça apenas uma asserção por teste unitário.

Ruim:

import{assert}from'chai';describe('AwesomeDate',()=>{it('handles date boundaries',()=>{letdate:AwesomeDate;date=newAwesomeDate('1/1/2015');date.addDays(30);assert.equal('1/31/2015',date);date=newAwesomeDate('2/1/2016');date.addDays(28);assert.equal('02/29/2016',date);date=newAwesomeDate('2/1/2015');date.addDays(28);assert.equal('03/01/2015',date);});});

Bom:

import{assert}from'chai';describe('AwesomeDate',()=>{it('handles 30-day months',()=>{constdate=newAwesomeDate('1/1/2015');date.addDays(30);assert.equal('1/31/2015',date);});it('handles leap year',()=>{constdate=newAwesomeDate('2/1/2016');date.addDays(28);assert.equal('02/29/2016',date);});it('handles non-leap year',()=>{constdate=newAwesomeDate('2/1/2015');date.addDays(28);assert.equal('03/01/2015',date);});});

⬆ ir para o topo

O nome do teste deve revelar sua intenção

Quando um teste falha, o seu nome é a primeira indicação do que deu errado.

Ruim:

describe('Calendar',()=>{it('2/29/2020',()=>{// ...});it('throws',()=>{// ...});});

Bom:

describe('Calendar',()=>{it('should handle leap year',()=>{// ...});it('should throw when format is invalid',()=>{// ...});});

⬆ ir para o topo

Concorrência

Prefira promises à callbacks

Callbacks não são claros, e eles causam uma quantidade desnecessária de agrupamento(callback hell).Há utilitários que transformam funções existentes que usam callbacks para uma versão que retorna promises(Em Node.js háutil.promisify, para propositos gerais, vejapify,es6-promisify)

Ruim:

import{get}from'request';import{writeFile}from'fs';functiondownloadPage(url:string,saveTo:string,callback:(error:Error,content?:string)=>void,){get(url,(error,response)=>{if(error){callback(error);}else{writeFile(saveTo,response.body,error=>{if(error){callback(error);}else{callback(null,response.body);}});}});}downloadPage('https://en.wikipedia.org/wiki/Robert_Cecil_Martin','article.html',(error,content)=>{if(error){console.error(error);}else{console.log(content);}},);

Bom:

import{get}from'request';import{writeFile}from'fs';import{promisify}from'util';constwrite=promisify(writeFile);functiondownloadPage(url:string,saveTo:string):Promise<string>{returnget(url).then(response=>write(saveTo,response));}downloadPage('https://en.wikipedia.org/wiki/Robert_Cecil_Martin','article.html',).then(content=>console.log(content)).catch(error=>console.error(error));

Promises suportam alguns padrões que podem ser úteis em alguns casos:

PadrãoDescrição
Promise.resolve(value)Converte um valor para uma promise resolvida.
Promise.reject(error)Converte um erro para uma promise rejeitada.
Promise.all(promises)Retorna uma nova promise, que é preenchida com um array of fulfillment values for the passed promises or rejects with the reason of the first promise that rejects.
Promise.race(promises)Returns a new promise which is fulfilled/rejected with the result/error of the first settled promise from the array of passed promises.

Promise.all is especially useful when there is a need to run tasks in parallel.Promise.race makes it easier to implement things like timeouts for promises.

⬆ ir para o topo

Async/Await são ainda mais claros do que Promises

Com a sintaxe Async/Await você pode escrever um código muito mais claro e compreensível do que com promises. Dentro de uma função prefixada comasync você tem uma maneira de dizer ao tempo de execução do JavaScript pausar a execução do código quando utilizado o prefixoawait (quando usando em um promise).

Ruim:

import{get}from'request';import{writeFile}from'fs';import{promisify}from'util';constwrite=util.promisify(writeFile);functiondownloadPage(url:string,saveTo:string):Promise<string>{returnget(url).then(response=>write(saveTo,response));}downloadPage('https://en.wikipedia.org/wiki/Robert_Cecil_Martin','article.html',).then(content=>console.log(content)).catch(error=>console.error(error));

Bom:

import{get}from'request';import{writeFile}from'fs';import{promisify}from'util';constwrite=promisify(writeFile);asyncfunctiondownloadPage(url:string,saveTo:string):Promise<string>{constresponse=awaitget(url);awaitwrite(saveTo,response);returnresponse;}// somewhere in an async function (Alguma parte de uma função async)try{constcontent=awaitdownloadPage('https://en.wikipedia.org/wiki/Robert_Cecil_Martin','article.html',);console.log(content);}catch(error){console.error(error);}

⬆ ir para o topo

Tratamento de erros

Erros lançados são uma coisa boa! Eles significam que o tempo de execução identificou com sucesso quando alguma coisa no seu programa deu errado e está deixando você saber parando a execução da função atual, matando o processo (em Node), e notificando você no console.

Sempre use Erro para jogar (throwing) ou rejeitar (rejecting)

JavaScript assim como o TypeScript te permite jogar (throw) qualquer objeto. Um promise também ser rejeitado com qualquer objeto de razão.É aconselhavel usar a sintaxe jogar(throw) com um tipo Erro (Error). Isto é porque seu erro pode ser pego em um código de alto nível com uma sintaxe pegar (catch).Seria muito confuso pegar uma mensagem string la e fariadebugar mais doloroso.Pela mesma razão você deveria rejeitar promises com tipo de erro (Error).

Ruim:

functioncalculateTotal(items:Item[]):number{throw'Not implemented.';}functionget():Promise<Item[]>{returnPromise.reject('Not implemented.');}

Bom:

functioncalculateTotal(items:Item[]):number{thrownewError('Not implemented.');}functionget():Promise<Item[]>{returnPromise.reject(newError('Not implemented.'));}// Ou equivalente a:asyncfunctionget():Promise<Item[]>{thrownewError('Not implemented.');}

O benefício de user o tipoError é que este é suportado portry/catch/finally e implicitamente todos os erros tem a propriedadestack, que é uma ferramenta poderosa para debug.Há também outras alternativas, não usarthrow e, ao invés disso, sempre retornar objetos de erro.TypeScript deixa isso ainda mais fácil. Considere o exemplo abaixo:

typeResult<R>={isError:false;value:R};typeFailure<E>={isError:true;error:E};typeFailable<R,E>=Result<R>|Failure<E>;functioncalculateTotal(items:Item[]):Failable<number,'empty'>{if(items.length===0){return{isError:true,error:'empty'};}// ...return{isError:false,value:42};}

Para entender mais disso, leia apublicação original.

⬆ ir para o topo

Não ignore erros capturados

Não fazer nada com um erro capturado não te da a habilidade para consertar ou reagir ao erro. Mostrar o erro no console (console.log) não é muito bom, já que fácilmente pode ser perdido no meio de tanta coisa mostrada no console. Se você coloca todo pedaço de código em umtry/catch, significa que você acha que um erro pode acontecer, então você deve ter um plano, ou criar uma saída, pra quando acontecer.

Ruim:

try{functionThatMightThrow();}catch(error){console.log(error);}// ou ainda piortry{functionThatMightThrow();}catch(error){// ignorar o erro}

Bom:

import{logger}from'./logging';try{functionThatMightThrow();}catch(error){logger.log(error);}

⬆ ir para o topo

Não ignore promises rejeitadas

Pelo mesmo motivo que você não deve ignorar erros que vem dotry/catch.

Ruim:

getUser().then((user:User)=>{returnsendEmail(user.email,'Welcome!');}).catch(error=>{console.log(error);});

Bom:

import{logger}from'./logging';getUser().then((user:User)=>{returnsendEmail(user.email,'Welcome!');}).catch(error=>{logger.log(error);});// ou usando async/await:try{constuser=awaitgetUser();awaitsendEmail(user.email,'Welcome!');}catch(error){logger.log(error);}

⬆ ir para o topo

Formatação

Formatação é subjetivo. Como todas outras regras aqui, não há uma mais rápida ou mais difícil que você deva seguir. O ponto principal éNÃO ARGUMENTE formatação do código. Há várias ferramentas que automatizam isso. Escolha uma! É uma perda de tempo e dinheiro para engenheiros (de software) argumentar em cima de formatação de código. A regra geral éseguir e manter consistente as regras de formatação.

Em TypeScript, tem uma ótima ferramenta chamadaTSLint. É uma ferramenta de análise estática que pode te ajudar a melhorar drásticamente a legibilidade e manutenibilidade do seu código. Há algumas configurações prontas para serem utilizadas com TSLint.

Vale mencionar também este ótimo artigoTypeScript StyleGuide and Coding Conventions.

Capitalize de forma consistente

Capitalização diz muito sobre suas variáveis, funções, etc. Essas regras são subjetivas, seu time pode escolher o que eles quiserem. Indepentente da forma que escolherem,sejam consistentes.

Ruim:

constDAYS_IN_WEEK=7;constdaysInMonth=30;constsongs=['Back In Black','Stairway to Heaven','Hey Jude'];constArtists=['ACDC','Led Zeppelin','The Beatles'];functioneraseDatabase(){}functionrestore_database(){}classanimal{}classContainer{}

Bom:

constDAYS_IN_WEEK=7;constDAYS_IN_MONTH=30;constSONGS=['Back In Black','Stairway to Heaven','Hey Jude'];constARTISTS=['ACDC','Led Zeppelin','The Beatles'];functioneraseDatabase(){}functionrestoreDatabase(){}classAnimal{}classContainer{}

UsePascalCase para classes, interfaces, tipos e namespaces.UsecamelCase para variáveis, funções e classes.

⬆ ir para o topo

Function callers and callees should be close

Funções que chamam outras funções, ou que são chamadas, devem estar próximas uma das outras.

Se uma função chama outra, mantenha essas funções verticalmente perto no seu arquivo. Idealmente, mantenha a função que chama a outra, logo acima da função chamada.

Tendemos a ler código de cima para baixo, como um jornal. Por conta disso, faça seu código ser fácil de ler dessa maneira.

Ruim:

classPerformanceReview{constructor(privatereadonlyemployee:Employee){}privatelookupPeers(){returndb.lookup(this.employee.id,'peers');}privatelookupManager(){returndb.lookup(this.employee,'manager');}privategetPeerReviews(){constpeers=this.lookupPeers();// ...}review(){this.getPeerReviews();this.getManagerReview();this.getSelfReview();// ...}privategetManagerReview(){constmanager=this.lookupManager();}privategetSelfReview(){// ...}}constreview=newPerformanceReview(employee);review.review();

Bom:

classPerformanceReview{constructor(privatereadonlyemployee:Employee){}review(){this.getPeerReviews();this.getManagerReview();this.getSelfReview();// ...}privategetPeerReviews(){constpeers=this.lookupPeers();// ...}privatelookupPeers(){returndb.lookup(this.employee.id,'peers');}privategetManagerReview(){constmanager=this.lookupManager();}privatelookupManager(){returndb.lookup(this.employee,'manager');}privategetSelfReview(){// ...}}constreview=newPerformanceReview(employee);review.review();

⬆ ir para o topo

type vs. interface

Usetype quando você precisar de uma união ou interseção. Use interface quando precisar usarextends ouimplements. Não há uma regra a ser seguida, entretanto. Use aquela que funciona para você.

Dê uma olhada nestaexplicação sobre as diferenças entretype einterface.

Ruim:

interfaceEmailConfig{// ...}interfaceDbConfig{// ...}interfaceConfig{// ...}//...typeShape{// ...}

Bom:

typeEmailConfig{// ...}typeDbConfig{// ...}typeConfig=EmailConfig|DbConfig;// ...interfaceShape{}classCircleimplementsShape{// ...}classSquareimplementsShape{// ...}

⬆ ir para o topo

Organize imports

Com declarações de import fáceis e limpas de ler, você consegue rapidamente ver as dependências do seu código. Assegure-se de que está aplicando as boas práticas para fazer imports:

  • Imports devem ser em ordem alfabética e agrupados.
  • Imports não utilizadas tem de ser removidos.
  • Imports com nomes devem estar em ordem alfabética (ex.:import {A, B, C} from 'foo';).
  • As fontes do seu imports devem estar em ordem alfabética dividido em grupos, ex.:import * as foo from 'a'; import * as bar from 'b';
  • Grupos de imports são separados por espaços em branco.
  • Grupos devem respeitar a seguinte ordem:
    • polyfills (ex.:import 'reflect-metadata';)
    • módulos do node (ex.:import fs from 'fs';)
    • módulos externos (ex.:import { query } from 'itiriri';)
    • módulos internos (ex.:import { UserService } from 'src/services/userService';)
    • módulos de um diretório pai (ex.:import foo from '../foo'; import qux from '../../foo/qux';)
    • módulos de um mesmo diretório (ex.:import bar from './bar'; import baz from './bar/baz';)

Ruim:

import{TypeDefinition}from'../types/typeDefinition';import{AttributeTypes}from'../model/attribute';import{ApiCredentials,Adapters}from'./common/api/authorization';importfsfrom'fs';import{ConfigPlugin}from'./plugins/config/configPlugin';import{BindingScopeEnum,Container}from'inversify';import'reflect-metadata';

Bom:

import'reflect-metadata';importfsfrom'fs';import{BindingScopeEnum,Container}from'inversify';import{AttributeTypes}from'../model/attribute';import{TypeDefinition}from'../types/typeDefinition';import{ApiCredentials,Adapters}from'./common/api/authorization';import{ConfigPlugin}from'./plugins/config/configPlugin';

⬆ ir para o topo

Use alias do typescript

Faça imports mais agradáveis definindo caminhos e a propriedade baseUrl na seção compilerOptions emtsconfig.json

Isso irá evitar caminhos relativos longos quando fizer imports.

Ruim:

import{UserService}from'../../../services/UserService';

Bom:

import{UserService}from'@services/UserService';
// tsconfig.json..."compilerOptions":{    ..."baseUrl":"src","paths":{"@services":["services/*"]}...}...

⬆ ir para o topo

Comentários

O uso de comentários é uma indicação que você falhou ao se expressar sem eles. Seu código deve ser sua única fonte.

Don’t comment bad code—rewrite it. (Não comente código ruim - Reescreva-o.)—Brian W. Kernighan and P. J. Plaugher

Use código que se auto-explica ao invés de comentários

Comentários são desculpas, não um requisito. Bom código normalmente se auto-documentam.

Ruim:

// Checa se subscriptions estão ativas.if(subscription.endDate>Date.now){}

Bom:

constisSubscriptionActive=subscription.endDate>Date.now;if(isSubscriptionActive){/* ... */}

⬆ ir para o topo

Não deixe código comentado na sua base de código (codebase)

Versionamento de código existe por um motivo. Deixe código antigo no seu histórico.

Ruim:

classUser{name:string;email:string;// age: number;// jobPosition: string;}

Bom:

classUser{name:string;email:string;}

⬆ ir para o topo

Não tenha códigos que marcam datas

Lembre-se, versione seu código! Não há motivo para manter código morto, comentado, e especialmente, datado. Usegit log para ter o histórico.

Ruim:

/** * 2016-12-20: Removed monads, didn't understand them (RM) * 2016-10-01: Improved using special monads (JP) * 2016-02-03: Added type-checking (LI) * 2015-03-14: Implemented combine (JR) */functioncombine(a:number,b:number):number{returna+b;}

Bom:

functioncombine(a:number,b:number):number{returna+b;}

⬆ ir para o topo

Evite marcadores posicionados

Normalmente eles só sujam o código. Deixe que os nomes das funções e varáveis, juntamente com uma identação e formatação apropriada, dê uma boa estrutura visual no seu código.Opcionalmente você pode usar o suporte da sua IDE paracode folding (VSCodefolding regions)

Ruim:

////////////////////////////////////////////////////////////////////////////////// Client class////////////////////////////////////////////////////////////////////////////////classClient{id:number;name:string;address:Address;contact:Contact;////////////////////////////////////////////////////////////////////////////////// public methods////////////////////////////////////////////////////////////////////////////////publicdescribe():string{// ...}////////////////////////////////////////////////////////////////////////////////// private methods////////////////////////////////////////////////////////////////////////////////privatedescribeAddress():string{// ...}privatedescribeContact():string{// ...}}

Bom:

classClient{id:number;name:string;address:Address;contact:Contact;publicdescribe():string{// ...}privatedescribeAddress():string{// ...}privatedescribeContact():string{// ...}}

⬆ ir para o topo

Comentários de TODO

Quando você reparar que precisa deixar notas no código, para alterar coisas futuramente, faça isso utilizando comentários// TODO. A maioria das IDEs tem um suporte especial para esse tipo de comentário para que possa voltar nesses comentários mais fácilmente depois.

Tenha em mente queTODOs não são uma desculpa para código ruim.

Ruim:

functiongetActiveSubscriptions():Promise<Subscription[]>{// garantir que `dueDate` está indexadoreturndb.subscriptions.find({dueDate:{$lte:newDate()}});}

Bom:

functiongetActiveSubscriptions():Promise<Subscription[]>{// TODO: garantir que `dueDate` está indexado.returndb.subscriptions.find({dueDate:{$lte:newDate()}});}

⬆ ir para o topo

Um grande obrigado ao meu amigoLuís Gustavo que me deu uma força ao traduzir esse conteúdo maravilhoso!

About

Conceitos do livro "Clean Code" adaptados para TypeScript

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages

  • TypeScript100.0%

[8]ページ先頭

©2009-2025 Movatter.jp