Você está na página 1de 51

Capítulo 5.

Objetivos básicos de refatoração

No Capítulo 1 , discutimos a refatoração como um processo de alteração de código


com segurança e sem alterar o comportamento para melhorar a qualidade.No Capí-
tulo 2 , examinamos a complexidade do ecossistema JavaScript e a consequente difi-
culdade em definir o que estilo e qualidade significam para nós. Nos Capítulos 3 e 4 ,
estabelecemos as bases para o teste, que é a maneira mais fácil de ter confiança em
nosso código e um pré-requisito para alterar o código com segurança (também conhe-
cido como refatoração).

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

Devido à natureza multiparadigmática do JavaScript, responder a essa pergunta nem


sempre é fácil. Mesmo sem usar bibliotecas especializadas, podemos nos esforçar
para (ou ser forçados a) fazer nosso código seguir alguns estilos: orientado a objeto
(prototípico ou baseado em classe), funcional ou assíncrono (com promessas ou retor-
nos de chamada).

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:

O JavaScript no arquivo, chamado main.js , é executado no carregamento da pá-


gina. São cerca de 2.000 linhas. Existem algumas funções declaradas, principal-
mente anexadas a um objeto como $ para que o jQuery faça sua mágica. Outros
são declarados com para que estejam no escopo global. Objetos, arrays e outras
variáveis ​são criados conforme necessário ao longo do caminho e alterados livre-
mente em todo o arquivo. Uma tentativa tímida de Backbone, React ou alguma
outra estrutura foi feita em um ponto para alguns recursos cruciais e complica-
dos. Essas partes quebram com frequência, pois o membro da equipe que estava
inicialmente empolgado com essa estrutura mudou para uma startup que cria
caixas de areia para IoT. O código também depende muito do JavaScript embutido
dentro de index.html . function whatever(){}

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:

Bulk (linhas de código e caminhos de código)


Entradas
Saídas (valores de retorno)
Efeitos colaterais
this : a entrada implícita
Privacidade
“JAVASCRIPT JENGA”

Se a história assustadora da base de código JavaScript do front-end não ressoou com


você, talvez uma descrição de como é trabalhar com um seja mais familiar.

Os programadores são encarregados de fazer alterações no frontend. Cada vez que o


fazem, fazem a menor alteração possível, adicionando um pouco de código e talvez
alguma duplicação. Não há suíte de testes para executar, portanto, em vez de confi-
ança, as ferramentas que eles têm disponíveis para garantir a qualidade da base de
código são 1) executar tantas verificações manuais de funcionalidades críticas quanto
for permitido pelo tempo e paciência e 2) esperança. A base de código fica maior e um
pouco mais instável a cada alteração.

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.

Esta é a dívida técnica em ação. Este é JavaScript Jenga.

Ao longo do restante deste capítulo, usaremos uma ferramenta para diagramação de


JavaScript chamada Trellus.Todos os diagramas deste capítulo foram gerados com ele,
e você pode diagramar suas próprias funções em trell.us .

É assim que começa: as funções são apenas um círculo ( Figura 5-2 ).

Figura 5-2. Apenas um círculo

Simples o suficiente. Adicionaremos partes para representar o seguinte:


Volume
Entradas
Saídas (valores de retorno)
Efeitos colaterais
Contexto
Privacidade

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

Usamos o termo bulk para descrever corpos de funções.Refere-se a duas característi-


cas diferentes, mas relacionadas:

Complexidade
Linhas de código

Os linters JavaScript (descritos no Capítulo 3 ) captam essas duas coisas. Portanto, se


você estiver usando um como parte de um processo de compilação (ou de preferência
em seu editor), deverá ver avisos para ambos se eles se tornarem excessivos.

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){

return error; // branch 1

};

// 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

Embora as técnicas clássicas de refatoração e as tendências em estruturas compostas


dentro de frameworks e pacotes JavaScript apontem para pequenas funções (assim
como objetos e “componentes”) como uma abordagem preferível, existem detratores.

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.

Se cada função fosse nomeada com alguma variante de


passResultOfDataToNextFunction , a extração de funções apenas dispersaria a
implementação, o que significa que o volume seria preferível.

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:

function outer(x, y){

return inner(x, y);

};

function inner(x, y){

// setup

return // something with x and y

};

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:

function outer(x, y){

// setup

  return // something with x and y

};

Ou se a // setup parte for complexa o suficiente e houver muitas chamadas para


inner que não vêm de outer , convém excluir outer , alterar quaisquer chamadas
de outer para inner e, conforme necessário, negar ou ajustar o // setup que você
usou para inner .

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).

Figura 5-3. 7 linhas, com um dos dois caminhos de código testados

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. 

Figura 5-4. 45 linhas com oito caminhos de código, todos testados

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:

function add(a, b){

return a + b;

};

Aliás, se chamamos add(2, 3) mais tarde, 2 e 3 somos “parâmetros reais”, “argu-


mentos reais” ou apenas “argumentos” porque  eles são usados ​na chamada da função
, em oposição aos “parâmetros formais”, que ocorrem na definição da função . As pes-
soas confundem argumentos versus parâmetros o tempo todo, então não se preocupe
muito com isso se você misturar também. O principal é que o que estamos chamando
de “parâmetros explícitos” aparece dentro da definição da função (veja a Figura 5-5 ).

Figura 5-5. Uma função add com dois parâmetros explícitos

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 = {

add: function(a, b){

return a + b;

},

addTwo: function(a){

    return this.add(a, this.two);

},

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";

var punctuation = "!";

function sayHi(){

return "Hi " + name + punctuation;

};

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.

"NOME" NÃO É UM GRANDE NOME

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:

function add(a, b){

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.

Um último truque que os parâmetros formais podem usar em JavaScript é permitir


não apenas tipos simples, mas também objetos e funções como parâmetros.Às vezes,
essa quantidade extrema de flexibilidade pode ser útil, mas considere o seguinte caso:

function doesSomething(options){

if(options.a){

var a = options.a;

if(options.b){

var b = options.b;

...

Se você tiver um objeto ou função misterioso que é passado em tempo de execução,


você potencialmente inchou seus casos de teste sem perceber como. Quando você
oculta os valores necessários dentro de um objeto com um nome genérico como
params ou options , seu corpo de função deve fornecer orientação sobre como esses
valores são usados ​e dicas sobre o que são. Mesmo com clareza dentro do corpo da
função, é definitivamente preferível usar parâmetros com nomes reais para manter a
interface menor e ajudar a documentar a função no topo.

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

Discutimos brevemente a especificação ECMAScript (ES) no Capítulo 2como o que observar


para atualizações no JavaScript (tendo em mente que bibliotecas e implementações podem ficar
para trás ou realmente estar à frente da curva). O que não falamos é como as convenções de no-
menclatura do ES funcionam. Como uma convenção relativamente nova, o corpo de padrões
abandonou números de versão como “ES6” em favor de lançamentos anuais como “ES2015”
(que corresponde ao ES6). A versão que será lançada a seguir é descrita como “ESNext”. No mo-
mento da redação deste artigo, essa convenção não está em uso há muito tempo, portanto, não
fique chocado se isso mudar novamente no futuro.

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.

search({query: 'something', pageSize: 20});

A primeira definição de função necessariamente incluiria parâmetros nomeados ex-


plicitamente. O segundo provavelmente teria um parâmetro chamado, na melhor das
hipóteses, searchOptions e na pior das hipóteses options . O primeiro nome (
searchOptions ) não oferece muito mais detalhes para documentar a chamada, mas
é pelo menos potencialmente exclusivo.

No entanto, existe uma forma (graças ao ES2015) que você pode ter clareza nas cha-
madas e nas definições:

function search({query: query, pageSize: pageSize}){

console.log(query, pageSize);

};

search({query: 'something', pageSize: 20});

É 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

Discutimos anteriormente como a coberturanão necessariamenteigualarà confiança.


Tristes caminhos são uma razão particular. O problema pode ser um problema de in-
tegridade de dados de uma interação do usuário que deu errado (por exemplo, uma
etapa de entrada de formulário formatada incorretamente que permite que dados in-
corretos entrem no banco de dados).

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.

Em linguagens com sistemas rígidos de verificação de tipo (diferente do JavaScript),


um bom número desses caminhos tristes desaparecem. No entanto, as ferramentas de
cobertura automatizadas não o ajudarão aqui. Caminhos tristes provavelmente esta-
rão ocultos no código (e casos de teste) que você não escreveu.

O teste de mutação, conforme descrito no Capítulo 3 , pode ser de alguma ajuda


aqui.Mas considerando que existem entradas potencialmente infinitas causadoras de
erros para suas funções em JavaScript (e quanto mais flexibilidade você insistir,
maior a probabilidade de acertar uma), a melhor defesa contra caminhos tristes é ga-
rantir que suas entradas sejam avaliadas corretamente pela sua função antes do
tempo. Caso contrário, você ficará preso a ver o bug acontecer ao vivo e, em seguida,
escrever um teste de regressão e um código para lidar com isso.

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:

function add(a, b){

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

Se nenhum valor for retornado explicitamente em JavaScript (usando a palavra-


return chave), undefined será retornado.Como isso fornece pouca ou nenhuma informação
sobre o que a função fez,mesmo no caso tradicional de usar uma função void para funções pro-
dutoras de efeitos colaterais, provavelmente há alguma informação de valor produzida pelo
efeito colateral - mesmo que seja apenas o resultado ou simplesmente o sucesso do efeito cola-
teral - que seria preferível voltar.Quando o efeito colateral altera especificamente o contexto (
this ), retornar this também é uma opção viável.

É recomendado que você retorne valores reais quando possível ao invés de retornar explicita-
mente undefined / null ou retornar implicitamente undefined .

Esta função, no entanto, retorna algo (veja a Figura 5-9 ):

function add(a, b){

  return a + b;

};

Figura 5-9. Uma função add que retorna um número


De um modo geral,queremos um valor de retorno decente (não null ou
undefined ), e queremos que os tipos de vários caminhos de código correspondam —
retornar uma string algumas vezes e outras vezes significa que você provavelmente
terá algumas if-else instruções no futuro. Retornar uma matriz com tipos mistos
pode ser igualmente complicado quando comparado a retornar um valor simples ou
uma matriz de tipos semelhantes.

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.

Falaremos mais sobre isso no Capítulo 11 .

O recomendadoabordagem para valores de saída é, sempre que possível, retornar um


valor consistente e simples, e evitar esse valorsendo null ou undefined . Com fun-
ções que causam ações destrutivas (como alterar um array ou alterar o DOM), pode
ser bom retornar um objeto que descreva qual efeito ocorreu. Às vezes, isso significa
apenas retornar this . Retornar algo informativo, mesmo quando nada foi solicitado
explicitamente, é um bom hábito e ajuda nos testes e na depuração.

Uma complicação adicional para saídas são funções que retornam tipos diferentes.
Considere esta função:

function moreThanThree(number){

if(number > 3){

return true;

} else {

return "No. The number was only " + number + ".";

};

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.

Contexto Parte 1: A Entrada Implícita

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.

Então o que é this ?

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

global.console.log('blah'); // in a node shell

Curiosamente, se você salvar um arquivo de nó e executá-lo, this imprime como um


objeto vazio, {} , mas global funciona como no shell do nó e global objetos como
console ainda estão disponíveis para uso. Embora this.console.log não funci-
one em um arquivo de nó que você executa, funcionará global.console.log . Isso
está relacionado a como o sistema do módulo de nó funciona.É um pouco complicado,
mas saiba que o escopo de nível superior na maioria dos ambientes também é this ,
mas em node, é o escopo do módulo. De qualquer forma, tudo bem, porque na maio-
ria das vezes você não quer definir funções ou variáveis ​no namespace global de
qualquer maneira.

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”

isso no modo estrito

Quando você estiver no modo estrito, this se comportará de maneira diferente.Para


este código:

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();

Você verá o resultado ( this é undefined ). No entanto, digitandoeste segundo tre-


cho de código linha por linha em um nó REPL (basta digitar node no terminal) ou em
um console do navegador não aplicará o modo estrito a x , e o objeto global será
retornado.

O diagrama de funções do Trellus x no primeiro trecho se parece com a Figura 5-13 .

Figura 5-13. Uma função com um valor this de indefinido

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).

Uma maneira de criar um novo contexto é usando um simplesobjeto assim:

var anObject = {

  number: 5,

  getNumber: function(){ return this.number }

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 ).

Figura 5-14. getNumber anexado a um this de anObject

Temos mais algumas maneiras de escrever código que seguirá o diagrama, bem como
a interface.Primeiro é o Object.create padrão:
var anObject =

Object.create(null, {"number": {value: 5},

"getNumber": {value: function(){return this.number}}});

console.log(anObject.getNumber());

A seguir é como você se anexaria getNumber ao anObject uso de classes:

class AnObject{

  constructor(){

    this.number = 5;

    this.getNumber = function(){return this.number}

  }

anObject = new AnObject;

console.log(anObject.getNumber());
ALGUMAS PESSOAS REALMENTE ODEIAM AULAS

Praticamente todo mundo gosta de literais de objeto. Algumas pessoas gostam


Object.create . Algumas pessoas gostam de aulas.Algumas pessoas gostam de es-
crever funções construtoras que se comportam muito como classes, sem todo o “açú-
car sintático”.

Algumas objeções às classes são baseadas em obscurecer a pureza e o poder do sis-


tema prototípico do JavaScript... mas, por outro lado, uma demonstração típica desse
poder e flexibilidade é a criação de um sistema de classes ad hoc estranho.

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 = {

  getNumber: function(){ return this.number }

console.log(anotherObject.getNumber.call(anObject));

console.log(anotherObject.getNumber.apply(anObject));

var callForTheFirstObject = anotherObject.getNumber.bind(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”

A novidade aqui é que, embora a função resida em anotherObject , bind , call , e


apply seja usada para atribuir a entrada “implícita” ( this ) de forma “explícita” a
anObject .

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);

Estamos diagramando da perspectiva de uma função. Também poderíamos diagra-


mar a seguinte chamada de função:

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 .

Vejamos mais um exemplo:

var anObject = {

  number: 5

var anotherObject = {

  getIt: function(){ return this.number },

  setIt: function(value){ this.number = value; return this; }

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).

No estilo FP (e nos padrões OOP wrapper), você verá mais disso:

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.

Contexto Parte 2: Privacidade

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.

Outra maneira de projetar métodos privados é através do padrão de módulo


revelador:

var diary = (function(){

  var key = 12345;

  var secrets = 'rosebud';

  function privateUnlock(keyAttempt){

    if(key===keyAttempt){

      console.log('unlocked');

      diary.open = true;

    }else{

      console.log('no');

    }

  };

  function privateTryLock(keyAttempt){

    privateUnlock(keyAttempt);

  };

  function privateRead(){

    if(this.open){

      console.log(secrets);

    }else{

      console.log('no');

    }

  };

  return {

    open: false,

    read: privateRead,

    tryLock: privateTryLock

  }

})();

// 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.

diary A primeira propriedade de é open , que é um booleano inicialmente definido


como false . Em seguida, ele tem duas outras propriedades que mapeiam para as de-
finições de função fornecidas anteriormente. A parte interessante é que temos algu-
mas coisas escondidas aqui. Nem as variáveis key ​e nem a função têm como ser aces-
sadas diretamente por meio do . secrets privateUnlock diary

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 .

Figura 5-18. A função de leitura do diário


Figura 5-19. A função tryLock do diário

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.

A tryLock função também apenas aponta para outra função ( privateTryLock ),


então usamos essa definição de função. Como read , tem uma entrada não local, mas
desta vez é uma função ( privateUnlock ) em vez de um valor simples ( secrets )
como no read caso de . Quanto aos valores de retorno e efeitos colaterais, a própria
definição da função não indica isso, mas não precisamos trabalhar muito para ver
quais efeitos colaterais ela tem. No entanto, de acordo com o que a maioria das ferra-
mentas de cobertura de código relataria, essa função tem apenas um caminho de có-
digo, embora seus efeitos colaterais dependam de dois caminhos dentro
de privateUnlock . Observe que o mesmo caminho de código único também seria in-
dicado pelo diagrama caso esses dois caminhos de código afetassem o valor de re-
torno em vez de, ou além dos efeitos colaterais.

Agora vamos ver privateUnlock ( Figura 5-20 ).

Figura 5-20. A função privateUnlock do diário

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();

// ReferenceError: hello is not defined

var 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 :

var diary = (function(){

// does everyone in here know about diary?

Mas, na verdade, funciona exatamente como a hi função. Quando privateUnlock é


declarado, ainda não sabe o que diary é, mas isso não importa. Uma vez diary de-
clarado no escopo de nível superior, tudo sabe sobre ele, incluindo funções declaradas
anteriormente, que incluem privateUnlock . Isso ainda pode parecer mágico, mas
basicamente, você pode declarar entradas não locais para funções depois que essas
funções forem declaradas . Contanto que as entradas não locais estejam em um escopo
que a função possa acessar quando as funções forem chamadas , você ainda poderá
usá-las nas declarações de função.

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).

É tentador apenas expor a privateUnlock função ao objeto (adicionando outro atri-


buto ao objeto retornado), mas não poderemos mantê-la “privada” (fora de um escopo
diretamente endereçável) dessa forma.

Para contornar o constrangimento de repetir um nome como estamos fazendo com


diary , o primeiro instinto de algumas pessoas é passar this uma variável chamada
that :
var diary = (function(){

  var key = 12345;

  var secrets = 'programming is just syntactic sugar for labor';

  function privateUnlock(keyAttempt, that){

    if(key===keyAttempt){

      console.log('unlocked');

      that.open = true;

    }else{

      console.log('no');

    }

  };

  function privateTryLock(keyAttempt){

    privateUnlock(keyAttempt, this);

  };

  function privateRead(){

    if(this.open){

      console.log(secrets);

    }else{

      console.log('no');

    }

  }

  return {

    open: false,

    read: privateRead,

    tryLock: privateTryLock

  }

})();

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.

O CONSOLE NÃO É UMA ENTRADA NÃO LOCAL?

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:

var diary = (function(){

  var key = 12345;

  var secrets = 'sitting for 8 hrs/day straight considered harmful';

  function privateUnlock(keyAttempt){

    if(key===keyAttempt){

      console.log('unlocked');

      this.open = true;

    }else{

      console.log('no');

    }

  };

  function privateTryLock(keyAttempt){

    privateUnlock.call(this, keyAttempt);

  };

  function privateRead(){

    if(this.open){

      console.log(secrets);

    }else{

      console.log('no');

    }

  }

  return {

    open: false,

    read: privateRead,

    tryLock: privateTryLock

  };

})();

E na bind versão nossa privateTryLock função ficaria assim:

  function privateTryLock(keyAttempt){

    var boundUnlock = privateUnlock.bind(this);

    boundUnlock(keyAttempt);

  };

Ou podemos inline essa boundUnlock variável chamando a função de limite


imediatamente:

  function privateTryLock(keyAttempt){

    privateUnlock.bind(this)(keyAttempt);

  };

o que nos coloca de volta a ser bem parecido com a call sintaxe.

De qualquer forma, nosso diagrama privateUnlock com a this -fixação no lugar


não deve ser muito chocante (veja a Figura 5-22 ).

Figura 5-22. Um parâmetro explícito (número) e um parâmetro implícito (diário)

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 ).

Figura 5-23. A função de criação de diário é bastante simples

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 :

var diary = (function(){

Da mesma forma, o tipo de retorno é um objeto: {} . Podemos ser específicos e dizer


que ele retorna um objeto com propriedades específicas, ou podemos dizer que é um
diary objeto. No entanto, vale ressaltar que nossa função não tem ideia do que
diary é a até que seu resultado seja atribuído à variável.

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(){

    this.open = false;

    this._key = 12345;

    this._secrets = 'the average human lives around 1000 months';

  };

  _unlock(keyAttempt){

    if(this._key===keyAttempt){

      console.log('unlocked');

      this.open = true;

    }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();

Agora nossas variáveis ​e _unlock funções privadas estão expostas na classe.Além


disso, adicionamos um sublinhado para indicar funções e variáveis ​que não devem
ser acessadas diretamente.Se considerarmos que nossa função private/underscore é
um detalhe de implementação privada e, portanto, não requer testes, agora temos
uma dica visual para nos ajudar a transmitir isso aos outros e a nós mesmos no fu-
turo. Por outro lado, nesta forma, nossos testes devem ser muito fáceis de escrever
porque todos os nossos métodos privados ainda são endereçáveis.
No entanto, se realmente quiséssemos não expor nossas informações “ocultas”, falha-
mos. Vamos dar um passo no que parece ser a direção errada:

var key = 12345;

var secrets = 'rectangles are popular with people, but not nature';

function globalUnlock(keyAttempt){

  if(key===keyAttempt){

    console.log('unlocked')

    this.open = true;

  }else{

    console.log('no')

  }

};

class Diary {

  constructor(){

    this.open = false;

  };

  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 :

var key = 12345;

var secrets='how to win friends/influence people is for psychopaths';

function globalUnlock(keyAttempt){

  if(key===keyAttempt){

    console.log('unlocked')

    this.open = true;

  }else{

    console.log('no')

  }

};

module.exports = class Diary {

  constructor(){

    this.open = false;

  };

  tryLock(keyAttempt){

    globalUnlock.bind(this)(keyAttempt);

  };

  read(){

    if(this.open){

      console.log(secrets);

    }else{

      console.log('no')

    }

  }

A única linha que alteramos é:

module.exports = class Diary {

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:

const Diary = require('./diary_module.js');

let d = new Diary();

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).

Existe privacidade em JavaScript?

No momento da redação deste artigo, não há privacidade real em JavaScript.As coisas


ou estão no escopo, ou não estão. Infelizmente, ter um objeto e simplesmente declarar
alguns atributos como públicos e outros como privados não é realmente possível.
Como todo atributo (toda propriedade de um objeto) tem um “this” ao qual se anexa,
para que as funções sejam “privadas” em JavaScript, elas também são necessaria-
mente inacessíveis.

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.

A segunda convenção (reconhecidamente desajeitada) é deixar nossas funções se vin-


cularem alegremente aos mesmos this membros públicos, mas dar dicas visuais
(prefixar o nome da função com um _ é o mais comum) para indicar quando algo
deve ser privado.

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”.

No entanto, existem propostas do TC39 (o comitê que avalia e determina os recursos


do JavaScript) relacionadas à privacidade que estão em andamento. Esses incluem:

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;

  #sum() { return #a + #b; }

  printSum() { console.log(#sum()); }

  constructor(a, b) { #a = a; #b = b; }

};

Então, #a e #b são ambos “campos” privados (não-função/método) e #sum é um “mé-


todo” privado. Esses identificadores com a # estariam indisponíveis fora do
class bloco de contexto. Assim, uma nova foo instância de classe Foo não teria
acesso tradicional de estilo de propriedade à la foo.#a (ou talvez foo.a —não sabe-
mos, e estamos assumindo que isso não funcionaria de qualquer maneira), foo.#b ,
ou foo.#sum() . No entanto, foo.printSum() seria bom, já que isso não é privado.
Além disso, os detalhes são incompletos no momento (por exemplo, foo.#a gera um
erro? Podem a e #a ambos podem ser usados ​como nomes de campo? Existe uma so-
lução alternativa para acessar os campos privados e métodos para teste?).

“ M É TO D O S ” V E R S U S “ F U N Ç Õ E S ”

Ao longo deste livro, nos referimos principalmente ao JavaScriptfunçõescomo sendo funções .


Para algumas pessoas, isso implica funções no sentido de entradas e saídas explícitas, enquanto
um método é usado para significar algum procedimento ou “apenas um pedaço de código” ane-
xado a um objeto.

De qualquer forma, mesmo sem todos os fatos, as propostaspara campos e métodos


privados têm implicações para o futuro do JS em geral:

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

Neste capítulo,cobrimos muitos detalhes sobre como o JavaScript funciona. Centra-


mos nossa conversa em funções, pois elas são a construção mais importante e compli-
cada em JavaScript (em qualquer paradigma que vale a pena seguir).

Mas como o objetivo era ser prescritivo e descritivo, aqui estão algumas dicas que
vale a pena repetir:

Tente manter o volume (complexidade e linhas de código) baixo.


Tente manter o número total de entradas baixo.
Prefira entradas explícitas a entradas não locais.
Faça escolhas entre passar this explicitamenteversus vinculá-lo à chamada de
função, em vez de codificar nomes de objetos como entradas não locais.
Prefira valores de retorno reais e significativos aos efeitos colaterais.
Mantenha os efeitos colaterais no mínimo ou inexistentes.
Tenha um bem definido this quando possível para funções e outras variáveis ​
(atributos), tornando-as parte de classes (ou pelo menos objetos) para reduzir en-
tradas não locais e variáveis ​globais.
Em JavaScript, os mecanismos de privacidade necessariamente impactam o acesso,
o que pode complicar o código, principalmente quando se trata de testes.

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

© 2022 O'REILLY MEDIA, INC.  TERMOS DE SERVIÇO POLÍTICA DE PRIVACIDADE

Você também pode gostar