Basic Refactoring Goals - Refactoring JavaScript
Basic Refactoring Goals - Refactoring JavaScript
Neste capítulo, finalmente nos voltamos para a relação específica entre refatoração e
qualidade. O capítulo anterior incluiu um diagrama ( Figura 4-1 , reproduzido como
Figura 5-1 ) que descrevia a relação entre teste e refatoração.Considere as três princi-
pais perguntas no diagrama:
Qual é a situação?
Você tem cobertura suficiente?
O código está ruim?
A primeira pergunta deve ser muito fácil de responder. A segunda pode ser abordada
por uma ferramenta de cobertura que é executada com seu conjunto de testes e gera
locais que estão especificamente ausentes de cobertura.Há momentos em que as fer-
ramentas de cobertura podem perder casos complexos, portanto, uma linha de código
coberta não significa necessariamente que estamos confiantes nela. Alcançar a quali-
dade do código por meio do processo de refatoração é absolutamente dependente da
confiança.
Neste capítulo, estamos desenvolvendo uma estratégia para responder à terceira per-
gunta: o código é ruim?
Figura 5-1. Fluxograma de teste e refatoração
Antes de abordarmos o que a refatoração nesses estilos implica, devemos lidar com
um paradigmaque opiores bases de código tendem a empregar: programação impera-
tiva não estruturada . Se esses itálicos não o assustaram, talvez uma descrição desse
tipo de base de código em um contexto de frontend o faça. Hora de uma história as-
sustadora de JavaScript:
Embora os frameworks possam ajudar a tornar esse tipo de código menos provável,
eles não podem e não devem impedir completamente a possibilidade de JavaScript de
forma livre. No entanto, esse tipo de JavaScript não é inevitável, nem impossível de
corrigir. A primeira ordem absoluta do negócio é entender as funções, e não qualquer
um dos tipos extravagantes.Neste capítulo, exploraremos seiscomponentes das
funções:
Ocasionalmente, um bug crítico se infiltra e toda a pilha de blocos cai, bem na frente
de um usuário do site. A única graça salvadora sobre este sistema é que o controle de
versão permite que a versão anterior instável (mas na maioria das vezes ok... prova-
velmente?) dos blocos seja configurada sem muitos problemas.
Antes de terminar o capítulo, exploraremos todos esses componentes por meio dessa
técnica de diagramação, anotando as diferenças qualitativas entre eles ao longo do
caminho.
Função em massa
Complexidade
Linhas de código
Não há regras rígidas sobre o volume. Algumas equipes gostam que as funções te-
nham 25 ou menos linhas de código. Para outros, 10 ou menos é a regra.Quanto à
complexidade (também conhecida como complexidade ciclomática ), ou “o número de
caminhos de código que você deve testar”, o limite superior tende a ser em torno de
seis.Por “caminho de código” ou “ramo de código”, queremos dizer um possível fluxo
de execução.Ramos de código podem ser criados de algumas maneiras, mas a maneira
mais simples é por meio de if instruções. Por exemplo:
if(someCondition){
// branch 1
} else {
// branch 2
};
function(error){
if(error){
};
// branch 2
};
Muito de um tipo de volume provavelmente indicará o outro tipo. Uma função de 100
linhas provavelmente também tem muitos caminhos de código em potencial. Da
mesma forma, uma função com várias switch instruções e atribuições de variáveis
provavelmente também terá uma contagem de linhas volumosa.
O problema com o volume é que ele torna o código mais difícil de entender e mais di-
fícil de testar. A falta de confiança resultante é o cenário exato que leva ao JavaScript
Jenga.
EM DEFESA DO MASSA
Ninguém está correndo para defender funções de 200 linhas em princípio, mas você
pode ocasionalmente encontrar alguns que criticam “funções minúsculas que não fa-
zem nada além de dizer a alguma outra função para fazer algo”, o que torna a lógica
“difícil de seguir .”
Isso é complicado. Embora seguir a lógica de uma função para outra exija um pouco
de paciência e prática quando comparado à leitura direta de uma função, o ideal é
que haja menos contexto para manter em mente e testar pequenas funções é muito
mais fácil.
Tudo isso dito, após o teste, ser capaz de extrair novas funções e reduzir o volume das
existentes é a habilidade mais importante a ser aprendida para refatoração. Ao fazer
isso, no entanto, você deve criar o código de nível superior levando em consideração
a capacidade da interface de ocultar sua implementação adequadamente. Isso exige
nomear bem as coisas e ter entradas/saídas sensatas.
Este livro argumenta contra o volume, mas em sua equipe, você pode ter que tolerar
um pouco mais de volume do que sua preferência pode ser. Esteja ciente de que a re-
dução do volume, como acontece com qualquer meta de refatoração, pode ser rece-
bida com objeções com base nas preferências estilísticas e em sua importância (real
ou percebida) em relação a outros objetivos de desenvolvimento.
Embora seja uma forma menos comum de código ruim, se você achar que seu código
vai longe demais na delegação de funções para seu conforto, apenas inline as funções.
Não é difícil de fazer:
};
// setup
};
Você tem algumas opções aqui.Se outer é a única coisa que chama inner , você
pode simplesmente mover o corpo da função de inner into outer , tendo em mente
que você precisaria:
// setup
};
Se você estiver confuso com as funções internas, inline-as e depois extraí-las é uma
ótima maneira de explorar o código. Como sempre, faça os testes e esteja pronto para
reverter para uma versão anterior.
Vamos adicionar algumas partes ao nosso diagrama para nos ajudar a representar o
volume e, enquanto estamos nisso, daremos nomes às nossas funções. Nós simples-
mente temos uma caixa que indica o nome da função e o número de linhas que a fun-
ção possui. Para representar os caminhos do código em (complexidade) nossa função,
podemos adicionar fatias de pizza. As fatias sombreadas mais escuras são testadas e
as mais claras não são testadas. As Figuras 5-3 e 5-4 são dois exemplos.
A Figura 5-3 tem dois caminhos de código e sete linhas de código. Um dos caminhos de
código é testado (escuro) e um não é (claro).
A Figura 5-4 é mais volumosa, com 45 linhas de código e 8 caminhos de código (fatias
de pizza).Mas como todas as fatias de pizza são escuras, sabemos que cada um dos
oito caminhos de código é testado.
Entradas
Para nossos propósitos, consideraremos três tipos de entradas para uma função: ex-
plícita, implícita e não local.Na função a seguir, diríamos a e b são entradas explícitas
(também conhecidas como parâmetros explícitos ou parâmetros formais ), porque es-
sas entradas fazem parte da definição da função:
return a + b;
};
Observe que estamos descrevendo o tipo das entradas ( number ), em vez de valores
específicos ou referências aos parâmetros formais da assinatura da função ( a e b ).
Como o JavaScript não se importa com os tipos que passamos, nossos nomes também
não serão necessariamente mapeados para tipos específicos de primitivos ou objetos
que estamos passando.
Emboranão estivessemmergulhando profundamente em objetos aqui,a entrada implí-
cita ou parâmetro implícito na função a seguir addTwo é o calculator objeto dentro
do qual a função é definida (veja a Figura 5-6 ):
var calculator = {
return a + b;
},
addTwo: function(a){
},
two: 2
};
Figura 5-6. Função addTwo com entrada explícita (número) e entrada implícita (calculadora)
Em JavaScript, o parâmetro implícito é referido por this . Você pode ter visto como
self em outros idiomas.Manter o controle this é provavelmente a coisa mais con-
fusa em JavaScript, e falaremos sobre isso um pouco mais em “Contexto Parte 1: A En-
trada Implícita” .
O terceiro tipode entradas, entradas não locais (comumente conhecidas como “variá-
veis livres”), podem ser especialmente complicadas,especialmente em sua forma fi-
nal,a temida variável global. Aqui está um exemplo de aparência inocente (veja a Fi-
gura 5-7 ):
var name = "Max";
function sayHi(){
};
Figura 5-7. A função sayHi tem duas entradas não locais: nome e pontuação
Mas é tão inocente? name e punctuation pode ser redefinido, e a função ainda pode
ser chamada em qualquer ponto. A função não tem opinião ou proteção contra isso.
Pode não aparecer como um problema nessas 5 linhas, mas quando um arquivo tem
200 ou 300 linhas, variáveis flutuando assim tornam a vida mais difícil, pois podem
mudar a qualquer momento.
Embora não seja perfeitamente suportado em todas as implementações, name é uma proprie-
dade de objetos de função para muitos contextos. Na dúvida, evite.
Nos testes, descobrir quais entradas você precisa configurar pode levar mais tempo
do que qualquer outra tarefa envolvida. Quando entradas explícitas são objetos com-
plexos, isso pode ser bastante difícil (embora possa ser ajudado por fábricas e acessó-
rios, como discutido no Capítulo 3 ), mas se sua função depende muito de entrada im-
plícita (ou pior, entradas não locais/globais), então você tem muito mais configuração
para fazer.O estado implícito (usando this ) não é tão ruim quanto a entrada não lo-
cal. Na verdade, a programação orientada a objetos (OOP) depende do uso inteligente.
A recomendação aqui é que suas funções, tanto quanto possível, dependam de entra-
das explícitas (que sugerem programação funcional, ou estilo FP), seguidas de entra-
das implícitas (também conhecidas como this , que sugere o estilo OOP), seguidas
em um terceiro distante por estado não local/global.Uma maneira fácil de se proteger
contra o estado não local é envolver o máximo possível do código em módulos, fun-
ções e classes (que, sim, são, na verdade, funções disfarçadas).
Observe que, mesmo quando o estado é explícito, o JavaScript permite uma ampla va-
riedade de flexibilidade na forma como os parâmetros são passados.Em algumas lín-
guas, formalparâmetros (as partes de uma definição de função que especificam entra-
das explícitas) exigem que os tipos ( int , boolean , MyCoolClass , etc.) sejam espe-
cificados, bem como o nome do parâmetro. Não existe tal restrição em JavaScript. Isso
leva a algumas possibilidades convenientes. Considere o seguinte :
function trueForTruthyThings(sometimesTruthyThing){
return !!(sometimesTruthyThing);
};
Ao chamar esta função, poderíamos passar um parâmetro de qualquer tipo que qui-
séssemos. Pode ser um booleano, um array, um objeto ou qualquer outro tipo de va-
riável. Tudo o que importa é que podemos usar a variável dentro da função. Esta é
uma abordagem muito flexível, que pode ser útil, mas também pode dificultar o co-
nhecimento de quais tipos de parâmetros são necessários para exercer uma função
em teste.
O JavaScript oferece duas abordagens adicionais que aumentam ainda mais a flexibi-
lidade. A primeira é que o número de parâmetros formais não precisa corresponder
ao número passado na chamada da função.Pegue uma função como:
return a + b;
};
Isso retornará com prazer 5 se você o chamar de add(2, 3) , mas também se o cha-
mar de add(2, 3, 4) . Os parâmetros formais não se importam com o que você
passa; apenas o corpo da função faz. Você pode até fornecer menos argumentos, como
neste: add(2) . Isso retornará NaN (“Not a Number”) porque 2 está sendo adicionado
a undefined , embora no Capítulo 11 , exploraremos uma técnica chamada currying
que torna útil fornecer menos argumentos do que os especificados pelos parâmetros
formais.
Quanto aos argumentos extras fornecidos a uma chamada de função, é possível recu-
perá-los e usá-los no corpo da função, mas essa funcionalidade deve ser usada com
cautela, pois complica um procedimento de teste que se beneficia de entradas simples
e menos volume no corpo da função.
function doesSomething(options){
if(options.a){
var a = options.a;
if(options.b){
var b = options.b;
...
Não há nada de errado em passar um objeto inteiro para uma função em vez de divi-
dir tudo em parâmetros individuais nomeados, mas chamá-lo params ou
options pode sugerir que a função está fazendo muito. Se a função aceita um tipo es-
pecífico de objeto, ela deve ter um nome específico. Veja mais sobre como renomear
coisas em “Renomear coisas” .
UM POUCO SOBRE ECMASCRIPT
Antes do ES2015, passar um objeto para uma chamada de função oferecia a vantagem
de ilustrar um pouco a assinatura da função (quais parâmetros são usados), em vez de
ter o que poderia ser apenas strings ou números mágicos. Compare esses dois:
search('something', 20);
// vs.
No entanto, existe uma forma (graças ao ES2015) que você pode ter clareza nas cha-
madas e nas definições:
console.log(query, pageSize);
};
É um pouco estranho, mas permite que você seja específico em ambos os lugares e
evita as armadilhas de enviar os tipos errados de argumentos ou passar um
params objeto sem pensar para outra chamada de função (talvez depois de algumas
manipulações primeiro). Honestamente, esse segundo padrão é tão ruim e desenfre-
ado que essa construção um tanto estranha ainda parece muito boa em comparação.
Se você está curioso sobre o recurso que faz isso funcionar, é chamado de desestrutu-
ração , e há muito nisso.É para atribuições em geral (não apenas params ), e você
pode usá-lo com arrays e também com objetos.
Se você usar uma função como parâmetro (um retorno de chamada ) na mistura, po-
derá adicionar significativamente mais volume.Cada chamada de função agora tem o
potencial de exigir qualquer número de novos casos de teste para suportá-la.
TRISTES CAMINHOS
Mesmo que seu código teste cada branch de uma if-else cláusula dentro de sua
função, algumas entradas causarão problemas. Isso inclui não apenas parâmetros
inesperados passados para a função, mas também entradas não locais, como hora ou
data, bem como números aleatórios.
Observe que, devido à validação de entrada (ou ao uso de mecanismos mais robustos
dentro de uma função), um caminho triste não significa necessariamente um novo ca-
minho de código que exigiria uma nova fatia de pizza em nosso método de
diagramação.
A passagem de funções como entradas para uma função pode ser feita de maneira ra-
cional, mas não reconhecer as vantagens e desvantagens entre simplicidade no teste e
flexibilidade é um erro.
A recomendação geral para esta seção é ter entradas simples e explícitas sempre que
possível, tanto nas definições de funções quanto nas chamadas. Isso torna o teste
muito mais fácil.
Aqui estão algumas recomendações para encerrar nossa discussão sobre insumos:
No geral, quanto menos entradas você tiver, mais fácil será manter o volume sob
controle e testar seu código.
Quanto menos entradas não locais você tiver, mais fácil será entender e testar seu
código.
Toda função tem a this como entrada implícita; no entanto, this pode não ser
utilizado, ou mesmo undefined em muitos casos. Se this não for usado, sinta-se
à vontade para não adicionar a thisType ~> parte do diagrama.
Acima de tudo, as entradas explícitas são mais confiáveis do que as this entradas
não locais.
Saídas
Por saída, queremos dizer o valor que é retornado de uma função.No nosso estilo
ideal, sempre queremos devolver algo.Há casos em que isso é difícil ou não é verdade
(alguns estilos assíncronos e código processual, controlado por efeitos colaterais), mas
vamos lidar com código assíncrono no Capítulo 10 e discutir os efeitos colaterais mais
adiante neste capítulo (assim como no Capítulo 11 ).
Um dos erros mais comuns que você verá em uma função é ignorar a saída ao não re-
tornar nada:
a + b;
};
Nesse caso, fica bem claro que a return palavra-chave foi omitida,o que significa que
esta função simplesmenteretorno undefined ( Figura 5-8 ). Isso pode ser fácil de per-
der, por duas razões.Em primeiro lugar, nem todas as línguas são iguais. Os rubistas
(muitos dos quais escrevem JavaScript como sua segunda linguagem) às vezes esque-
cem a return declaração porque a última linha em uma função Ruby é retornada
implicitamente.
Em segundo lugar, se o estilo da maior parte da base de código consiste em efeitos co-
laterais (abordados a seguir), o valor de retorno é menos importante que esse efeito.
Isso é especialmente comum em bases de código suportadas por jQuery, onde quase
todas as linhas são um manipulador de cliques que executa um retorno de chamada
(que geralmente é um código de efeito colateral sofisticado). Os programadores acos-
tumados a códigos baseados em efeitos colaterais também tendem a não retornar
nada.
Figura 5-8. Esta função add não especifica um valor de retorno, portanto, o padrão é indefinido
F U N Ç Õ E S VA Z I A S
É recomendado que você retorne valores reais quando possível ao invés de retornar explicita-
mente undefined / null ou retornar implicitamente undefined .
return a + b;
};
L I N G U A G E N S F O R T E M E N T E T I PA D A S
Algumas linguagens têm mecanismos para prescrever que valores de retorno (e entradas) se-
jam de um tipo específico (ou explicitamente não retornem nada).Tendo visto como o JavaS-
cript lida com entradas, não devemos nos surpreender ao encontrar uma flexibilidade seme-
lhante para valores de retorno.
Uma complicação adicional para saídas são funções que retornam tipos diferentes.
Considere esta função:
function moreThanThree(number){
return true;
} else {
};
Esta função retorna um booleano ou uma string (veja a Figura 5-10 ). Isso não é ótimo
porque o código que chama essa função provavelmente terá suas próprias condicio-
nais para verificar qual tipo foi retornado.
Figura 5-10. Esta função pode retornar um booleano ou uma string
Como é o tema até agora para este capítulo, mais simples é melhor quando se trata de
saídas (tipos de retorno). Retornar diferentes tipos de valores pode complicar o có-
digo. Além disso, queremos evitar retornar um null ou undefined , pois uma solu-
ção melhor provavelmente estará disponível.Finalmente, devemos nos esforçar para
retornar valores que sejam do mesmo tipo (ou tipos que implementam interfaces que
não exigirão uma verificação condicional após ou em torno da chamada da função),
independentemente de qual valor é retornado.
Efeitos colaterais
Algumas línguas consideram os efeitos colaterais tão perigosos que tornam muito difí-
cil apresentá-los.JavaScript não se importa com o perigo. Como é usado na prática, um
dos principais trabalhos do jQuery (se não sua principal responsabilidade) é manipu-
lar o DOM, e isso acontece por meio de efeitos colaterais.
A parte boa dos efeitos colaterais é que eles geralmente são os responsáveis diretos
por todo o trabalho com o qual alguém se importa. Os efeitos colaterais atualizam o
DOM. Os efeitos colaterais atualizam os valores no banco de dados.Os efeitos colate-
rais fazem console.log acontecer.
Apesar de todos os efeitos colaterais, nosso objetivo é isolá-los e limitar seu alcance.
Por quê? Existem duas razões. Uma é que as funções com efeitos colaterais são mais
difíceis de testar. A outra é que eles necessariamente têm algum efeito sobre (e/ou de-
pendem) do estado que complica nosso projeto.
Discutiremos os efeitos colaterais muito mais no Capítulo 11 , mas, por enquanto, va-
mos atualizar nossa diagramação simbólica de efeitos colaterais. Aqui está um exem-
plo simples (veja a Figura 5-11 ):
function printValue(value){
console.log(value);
};
Figura 5-11. Uma função com um efeito colateral muito comum: logging
Observe que, como essa função não retorna nada, o valor de retorno é undefined .
Idealmente, quanto menos efeitos colaterais melhor, e onde eles devem existir, eles
devem ser isolados, se possível.Uma atualização para alguma interface bem definida
(digamos, uma única linha do banco de dados) é mais fácil de testar do que várias atu-
alizações para ela ou para várias linhas do banco de dados.
Ao explicar entradas e saídas, passamos por algo um tanto complexo, mas muito im-
portante, que abordaremos agora:a “entrada implícita”, que aparece à esquerda dos
diagramas como someThisValue ~> (veja, por exemplo, Figura 5-6 ).
someThisValue é o this que temos falado até agora.
Dependendo do seu ambiente, fora de qualquer outro contexto, this pode se referir
a um objeto base específico do ambiente. Tente digitar this (e pressionar Enter) no
interpretador de um navegador (console). Você deve receber de volta o window objeto
que fornece os tipos de funções e subobjetos esperados, como console.log . Em um
shell de nó, a digitação this produzirá um tipo diferente de objeto base, com uma fi-
nalidade semelhante. Então, nesses contextos, digitar qualquer uma dessas coisas lhe
dará 'blah' .
console.log('blah');
this.console.log('blah');
window.console.log('blah'); // in a browser
De qualquer forma, podemos esperar que nosso this seja o escopo de nível superior
quando o verificamos dentro das funções declaradas no escopo de nível superior:
var x = function(){
console.log(this);
x(); // here, we'll see our global object, even in node files
Então, esse é o escopo de nível superior em poucas palavras. Vamos diagramar esta
última listagem de código com sua entrada implícita ( Figura 5-12 ).
Figura 5-12. Esta função tem o objeto global como seu valor “this”
var x = function(){
'use strict'
console.log(this);
x();
undefined será registrado. No modo estrito, nem toda função tem um arquivo
this . Se você criar este script e executá-lo com node:
'use strict'
var x = function(){
console.log(this);
x();
Agora a função tem quatro linhas e ainda retorna undefined como antes. O efeito co-
lateral (que agora registra undefined em vez do objeto global) ainda está presente. A
diferença mais crucial é que isso não é mais anexado a nenhum this objeto que não
seja undefined .
Esteja você escrevendo um módulo de nó ou qualquer programa de tamanho decente
no frontend, você geralmente desejará que apenas uma ou um pequeno punhado de
variáveis tenham escopo dentro do escopo de nível superior (algo precisa ser definido
lá, ou você não seria capaz de acessar nada do contexto externo).
var anObject = {
number: 5,
console.log(anObject.getNumber());
Aqui, this não é o objeto global, mas anObject . Existem outras maneiras de definir
um contexto, mas esta é a mais simples.A propósito, como você está criando um ob-
jeto literal com a {} sintaxe, isso é chamado de literal de objeto .
Tendo em mente que estamos diagramando funções, não objetos, vamos ver como é
nossa getNumber função ( Figura 5-14 ).
Temos mais algumas maneiras de escrever código que seguirá o diagrama, bem como
a interface.Primeiro é o Object.create padrão:
var anObject =
console.log(anObject.getNumber());
class AnObject{
constructor(){
this.number = 5;
}
console.log(anObject.getNumber());
ALGUMAS PESSOAS REALMENTE ODEIAM AULAS
Outras objeções às classes são baseadas em herança sendo pior do que delegação e/ou
composição, o que é válido, embora bastante não relacionado ao uso apenas da pala-
vra- class chave.
Se você estiver usando a palavra- new chave, seja de uma classe ou de uma função
construtora, this ela será anexada a um novo objeto que será retornado pela cha-
mada para new .
Em uma abordagem OOP, você pode se encontrar usando objetos para coisas simples
e classes de produção de objetos, funções de fábrica e assim por diante para coisas
mais complexas, enquanto no estilo FP você provavelmente usará menos classes.
JavaScript não parece se importar como você escreve. Talvez você ache o estilo FP
mais sustentável, mas um determinado projeto ou equipe pode ter um investimento
significativo em um estilo OOP.
O seu this também pode mudaratravés do uso das funções call , apply , e bind .
call e apply são usados exatamente da mesma maneira se você não estiver pas-
sando nenhuma entrada explícita para a função. bind é como call ou apply , mas
para salvar a função (com o limite this ) para uso posterior:
var anObject = {
number: 5
var anotherObject = {
console.log(anotherObject.getNumber.call(anObject));
console.log(anotherObject.getNumber.apply(anObject));
console.log(callForTheFirstObject());
Observe que nenhum objeto tem o número e a função. Eles precisam um do outro.
Como estamos usando bind , call , ou apply , nosso diagrama na verdade não pre-
cisa mudar. Como estamos usando, this ainda se refere a anObject , mesmo que a
função esteja definida em anotherObject ( Figura 5-15 ).
Figura 5-15. Nada é realmente alterado aqui: getNumber ainda tem anObject como seu “this”
Você pode estar confuso sobre por que a Figura 5-15 tem anObject como parâmetro
implícito, mesmo que a função seja definida dentro de anotherObject . É porque es-
tamos diagramando as chamadas de função que são assim:
anotherObject.getNumber.call(anObject);
anotherObject.getNumber();
E então anotherObject seria o parâmetro implícito (o this ), mas seu tipo de re-
torno seria undefined , não number .
var anObject = {
number: 5
var anotherObject = {
console.log(anotherObject.setIt.call(anObject, 3));
Observe que o setIt código retorna seu this valor, portanto, se você executar esse
código, verá o anObject objeto completo com o valor atualizado: { number: 3 } .
Isso é o que return this a setIt função faz. Caso contrário, teríamos apenas o
efeito colateral (mutando o anObject 's number ) e nenhuma confirmação clara do
que aconteceu. Retornar this torna o teste (manual ou automático) muito mais fácil
do que apenas retornar undefined de métodos causadores de efeitos colaterais.
Vejamos o diagrama setIt como é chamado pela call função ( Figura 5-16 ).
Figura 5-16. Tem um efeito colateral, mas ainda retorna algo útil
Observe que, embora seja um método produtor de efeitos colaterais, retornamos algo:
especificamente, nosso this from anotherObject .Conforme descrito anterior-
mente, isso pode ajudar a simplificar a verificação e o teste em comparação com o
simples retorno de undefined . Além disso, retornar this nos abre a possibilidade
de ter uma interface fluente , o que significa que poderíamos encadear funções como
esta:
object.setIt(3).setIt(4).setIt(5);
I N T E R FA C E S F L U E N T E S
Interfaces fluentes podem ser úteis para coisas como agregar condições de consulta de banco
de dados ou agrupar manipulações de DOM (o jQuery faz isso). Você também pode ver isso des-
crito como funções de encadeamento . Este é um padrão comum e útil nos estilos OOP e FP. Em
OOP, tende a ser resultado de retornar this , enquanto em FP, geralmente é resultado de map ,
que retorna um objeto (ou functor , na verdade) do mesmo tipo (por exemplo, arrays “map”
para outros arrays e promessas) “então” em outras promessas).
f(g(h()));
Isso pode parecer muito estranho em comparação com interfaces fluentes, mas o FP tem ótimas
estratégias para combinar/compor funções. Veremos isso mais no Capítulo 11 .
Tudo isso para dizer: se você está apenas retornando undefined de qualquer ma-
neira, retornar this fornecerá mais informações e melhores interfaces.
No que diz respeito ao contexto, isso é o mais complicado possível sem se aprofundar
em protótipos (e nas três ou quatro coisas que significam), herança, mixins, módulos,
construtores, funções de fábrica, propriedades, descritores, getters e setters.
Após este capítulo, trata-se de um código melhor por meio de interfaces melhores . Não
nos esquivaremos dos tópicos do parágrafo anterior, mas também não faremos ídolos
de nenhum padrão. Este livro pretende explorar muitas maneiras diferentes de me-
lhorar o código.
Qualquer estilo de codificação pelo qual você se apaixone certamente será a heresia
de outra pessoa. JavaScript apresenta muitas oportunidades para ambas as reações.
O último tópico deste capítulo é o das funções “privadas”, eo queisso significa em Ja-
vaScript. Escopo é um tópico mais amplo de ocultar e expor comportamentos que ex-
ploraremos mais adiante por meio de exemplos.Por enquanto, estamos preocupados
apenas com a privacidade das funções, porque, como observamos anteriormente, as
funções privadas têm implicações exclusivas para testes .
Ou seja, alguns acham que as funções privadas são “detalhes de implementação” e,
portanto, não requerem testes. Se aceitarmos essa premissa, idealmente podemos
ocultar a maior parte de nossa funcionalidade em funções privadas e ter menos có-
digo exposto que precisamos testar. Menos testes podem significar menos manuten-
ção. Além disso, podemos separar claramente nossa “interface pública” do restante do
código, o que significa que qualquer pessoa que use nosso código ainda pode se bene-
ficiar ao aprender ou fazer referência a uma pequena parte dele.
Então, como criamos funções privadas? Fingindo que não sabemos nada sobre obje-
tos por um minuto, poderíamos fazer isso:
(function(){
console.log('hi');
})();
ou isto:
(function(){
(function(){
console.log('hi');
})();
})();
Aqui temos algumas funções anônimas.Eles são criados e depois desaparecem. Qual-
quer this um que colocarmos dentro será vinculado ao contexto de nível superior.
Como são anônimos, tudo o que podem fazer é correr quando mandarmos. Mesmo
que conheçamos as this funções anônimas, não podemos executá-las (exceto imedi-
atamente ou como retorno de chamada), porque elas não têm nome e não podem ser
endereçadas.Aliás, elas são chamadas de expressões de função imediatamente invoca-
das (IIFEs), e falaremos mais sobre elas no Capítulo 7 .
Exploraremos mais alguns tipos úteis de funções privadas em um minuto, mas pri-
meiro temos uma nova peça para adicionar aos nossos diagramas (veja a Figura 5-17
). Os diagramas para ambas as funções anteriores são os mesmos (exceto pelas linhas
de código, que você pode argumentar que são cinco em vez de três para a segunda).
Figura 5-17. Uma função anônima “privada”
O que há de novo aqui é que temos um anel escuro ao redor do círculo principal para
indicar que esta é uma função “privada”, e você pode ou não querer (ou poder) testá-
la. Quaisquer fatias de pizza ainda seriam aparentes em uma função com mais cami-
nhos de código e, como nas funções públicas, as fatias seriam escurecidas quando
testadas.
function privateUnlock(keyAttempt){
if(key===keyAttempt){
console.log('unlocked');
}else{
console.log('no');
}
};
function privateTryLock(keyAttempt){
privateUnlock(keyAttempt);
};
function privateRead(){
if(this.open){
console.log(secrets);
}else{
console.log('no');
}
};
return {
}
})();
// run with
diary.tryLock(12345);
diary.read();
Ler isso de cima para baixo é um erro. Em sua essência, isso é apenas criar um objeto
com três propriedades e atribuí-lo a diary . Acontece que estamos cercando-o com
uma função anônima (e imediatamente executada), mas isso é apenas para criar um
contexto onde podemos ocultar as coisas. A maneira mais fácil de ler esta função é
primeiro olhar para o objeto que ela retorna. Caso contrário, é bastante semelhante
ao último exemplo, pois tudo o que estamos fazendo é envolver algum código com
uma função anônima.
Uma coisa que pode parecer estranha é que na função “private” privateUnlock , ao
invés de this.open , temos diary.open . Isso ocorre porque quando estamos exe-
cutando privateUnlock através da privateTryLock função, perdemos nosso
this contexto. Para ser claro, this está diary dentro da privateTryLock função,
mas é o objeto global dentro de privateUnlock .
Como diagramas Trellus, essas funções se pareceriam com as Figuras 5-18 e 5-19 .
A read função apenas aponta para privateRead , então usamos essa definição para
nosso diagrama. Não requer parâmetros explícitos. Its this (o parâmetro implícito) é
o diary objeto (que é retornado da chamada de função anônima). Ele retorna
undefined e chama console.log como efeito colateral. Mas secrets e a entrada
não local? É tentador pensar nisso secrets como parte do diary objeto , mas não é.
Faz parte do escopo dentro do qual o objeto retornado diary foi criado. Contraste
secrets com this.open , que é um atributo do diary próprio objeto.
O diagrama desta função se parece muito com tryLock 's. Uma grande diferença é
que ele tem o círculo escuro envolvendo seus caminhos de código. Isso indica que es-
tamos considerando isso como uma função privada. Discutiremos isso um pouco
mais, mas por enquanto, aqui está uma ideia de trabalho de funções “privadas” em
JavaScript: em JavaScript, não há realmente uma boa maneira de criar funções priva-
das. Basicamente, você tem variáveis e funções que estão no escopo e são endereçá-
veis ou não.
Outra coisa pode ter saltado do diagrama como interessante. O diary objeto (que é
usado para diary.open ) não é o da função this , nem é uma entrada explícita: é
uma entrada não local. O mecanismo aqui é um pouco complicado, mas isso deve ilus-
trar o que está acontecendo:
function hi(){
console.log(hello);
};
hi();
hi();
// logs "hi"
Parece estranho que diary esteja no escopo, e pode parecer que tem algo a ver com a
função que é declarada dentro de ser atribuída diary :
Se isso não afundar, tudo bem. Estamos prestes a parar de usar diary essa função
porque é um pouco estranho (também, é codificado e será interrompido se alterarmos
o nome da variável).
if(key===keyAttempt){
console.log('unlocked');
}else{
console.log('no');
}
};
function privateTryLock(keyAttempt){
};
function privateRead(){
if(this.open){
console.log(secrets);
}else{
console.log('no');
}
}
return {
}
})();
Vamos ver o que isso faz com o nosso diagrama privateUnlock (veja a Figura 5-21 ).
Figura 5-21. Com duas entradas explícitas
Nada mudou muito aqui. A única diferença é que agora temos duas entradas explíci-
tas para a privateUnlock função e uma entrada não local, que é uma melhoria em
relação a antes.
Em outras palavras: não deveria estar listado à direita dos diagramas também?
Quando é usado dentro da função, sim, ele age como uma entrada não local. Nós o omitimos
nesses diagramas para simplificá-los, mas, de qualquer forma, quando você estiver escrevendo
suas próprias funções, adicione entradas para console e qualquer outra coisa que não seja
this ou seja passada como um parâmetro explícito.
Perceba também que não estamos adicionando entradas não locais para cada objeto e subob-
jeto global que não estamos usando . Isso tornaria os diagramas muito barulhentos.
Alternativamente, podemos usar uma de nossas this funções -fixing: call , apply ,
ou bind . Para call , você mudaria privateUnlock e ficaria
privateTryLock assim:
function privateUnlock(keyAttempt){
if(key===keyAttempt){
console.log('unlocked');
}else{
console.log('no');
}
};
function privateTryLock(keyAttempt){
};
function privateRead(){
if(this.open){
console.log(secrets);
}else{
console.log('no');
}
}
return {
};
})();
function privateTryLock(keyAttempt){
boundUnlock(keyAttempt);
};
function privateTryLock(keyAttempt){
privateUnlock.bind(this)(keyAttempt);
};
o que nos coloca de volta a ser bem parecido com a call sintaxe.
Agora nossa função tem uma entrada implícita de diary . key ainda está preso como
um não-local. Ele, como secrets e privateUnlock , está lá para qualquer um pegar
quando a diary função anônima -creating for executada, mas não está anexada a ne-
nhum objeto (qualquer this ) de significância.
Algumas variáveis (incluindo funções) têm um útil this ao qual estão anexadas. Ou-
tros apenas têm um escopo onde estão disponíveis e endereçáveis.
Antes de deixar nosso diary exemplo, há uma função importante que negligencia-
mos no diagrama: nossa diary função -creating ( Figura 5-23 ).
Na verdade, é o que não está neste diagrama que é surpreendente. Em primeiro lugar,
a função é anônima. É o resultado de chamar a função que é atribuída a uma variável
que é chamada diary :
Neste ponto, você pode estar se perguntando sobre as classes.Talvez as classes tenham
alguma maneira mágica de implementar métodos privados? Não. Houve propostas
para que o ECMAScript peticionasse por essas coisas, mas até o momento em que es-
crevo, elas ainda não são uma aposta certa.
Se fôssemos realmente insistentes nesse comportamento para as aulas, como podería-
mos escrevê-lo?
class Diary {
constructor(){
};
_unlock(keyAttempt){
if(this._key===keyAttempt){
console.log('unlocked');
}else{
console.log('no')
}
};
tryLock(keyAttempt){
this._unlock(keyAttempt);
};
read(){
if(this.open){
console.log(this._secrets);
}else{
console.log('no');
}
}
d = new Diary();
d.tryLock(12345);
d.read();
var secrets = 'rectangles are popular with people, but not nature';
function globalUnlock(keyAttempt){
if(key===keyAttempt){
console.log('unlocked')
}else{
console.log('no')
}
};
class Diary {
constructor(){
};
tryLock(keyAttempt){
globalUnlock.bind(this)(keyAttempt);
};
read(){
if(this.open){
console.log(secrets);
}else{
console.log('no')
}
}
};
d = new Diary();
d.tryLock(12345);
d.read();
Agora nossas informações ocultas estão fora de nossa classe. Na verdade (assumindo
que estamos no escopo de nível superior), criamos variáveis globais! Que bom é isso?
Bem, na verdade isso está muito próximo de algo ótimo que resolve nosso problema
de uma maneira diferente. Salve o seguinte como diary_module.js :
function globalUnlock(keyAttempt){
if(key===keyAttempt){
console.log('unlocked')
}else{
console.log('no')
}
};
constructor(){
};
tryLock(keyAttempt){
globalUnlock.bind(this)(keyAttempt);
};
read(){
if(this.open){
console.log(secrets);
}else{
console.log('no')
}
}
Para fazer uso disso, precisaremos de outro arquivo (você pode chamá-lo de
diary_reader.js ) para importar o módulo. Veja como esse arquivo se parece:
d.tryLock(12345);
d.read();
Neste arquivo, entre a “ Diary classe” ou a “instância” d , não podemos ver key ou
ler o diário secrets sem ele. Infelizmente, isso também significa que, se quisermos
testá-lo usando um require mecanismo semelhante, ficaremos presos voltando à
técnica de sublinhado pré-anexado, colocando nossas funções privadas em seus pró-
prios módulos de alguma forma, ou incluindo-as condicionalmente para testes (e ex-
cluindo-as por outro lado).
Então, praticamente falando, neste momento temos duas convenções para escolher. A
primeira é desistir desse sonho edeixe atributos anexados a algum outro this , de
olho nas subconvenções de fazê-lo em uma função anônima de encapsulamento (à la
o padrão de módulo revelador), ou permitindo this anexar ao global this para mó-
dulos que são exportados. Como a exportação é uma operação de lista de permissões,
apenas as funções que especificamos serão importadas por outros scripts. Isso é útil
para ter uma API menor, mas complica um pouco o teste.
Então, por enquanto, o significado de privado depende mais ou menos de você, bem
como de como você decide o que chama de “privado”.
Campos privados
Métodos privados
Campos e métodos estáticos privados
Neste livro, estamos fazendo a mesma coisa com essas adições que estamos fazendo
com os prováveis lançamentos async e await recursos: estamos mencionando-os,
mas não entrando em detalhes. A partir da especificação, esta é a nova sintaxe pro-
posta a partir desta escrita:
class Foo {
#a;
#b;
printSum() { console.log(#sum()); }
constructor(a, b) { #a = a; #b = b; }
};
“ M É TO D O S ” V E R S U S “ F U N Ç Õ E S ”
As classes estão recebendo mais recursos que criam construções exclusivas e não
apenas “açúcar sintático” para funções de construtor.
JavaScript está dobrando em OOP. A programação funcional pode ser o “futuro” do
JavaScript, mas a OOP é pelo menos também o futuro.
Escolher “seu JavaScript” provavelmente será provado novamente como uma fun-
ção do tempo. O JavaScript de cinco anos atrás está começando a parecer estranho
aos olhos modernos. É provável que essa tendência continue.
Se não houver nenhuma solução alternativa fornecida pela
#privateFunction sintaxe, você poderá ver pessoas ainda preferindo o
_privateFunction hack de sublinhado para compatibilidade com versões anteri-
ores e nos casos em que o teste de funções privadas é desejado.
Empacotando
Mas como o objetivo era ser prescritivo e descritivo, aqui estão algumas dicas que
vale a pena repetir:
Você deve achar que essas ideias ajudam a tornar seu código (assim como os diagra-
mas) mais simples.
Como nota final, o estilo que usamos aqui é “ this -amigável” em comparação com
algumas outras abordagens. Em particular, o uso de objetos que alteram seus valores
entra em conflito com aspectos do estilo funcional que discutiremos no Capítulo 11 .
No estilo orientado a objetos (baseado em classe ou protótipo), no entanto, você usará
this com frequência.
Apoiar Sair