Os desenvolvedores de JavaScript geralmente reclamam que sua linguagem de programação é injustamente culpada por ter muitos recursos excessivamente complicados e confusos. Muitos lutam com essa atitude em relação ao JS, falando sobre o motivo de criticar essa linguagem pelo que ela está errada. O autor do material, cuja tradução publicamos hoje, decidiu não defender JS, voltando-se para o lado sombrio da linguagem. No entanto, aqui ele não quer falar, por exemplo, sobre as armadilhas que o JavaScript define para programadores inexperientes. Ele está interessado na questão do que acontece se você tentar confirmar a má reputação do idioma com um código que poderia ser escrito por alguém que não se importa com os outros.

Nos exemplos deste material, muitos mecanismos de linguagem serão usados. Muito do que você vê aqui, aliás, funciona em outros idiomas, portanto, com a devida diligência, você também pode encontrar seus lados obscuros. Mas o JavaScript, certamente, tem um dom real para todos os tipos de bullying, e é muito difícil competir com outros idiomas nessa área. Se você escrever um código com o qual outras pessoas precisarão trabalhar, o JS oferecerá um número inesgotável de oportunidades para incomodar, confundir, assediar e enganar essas pessoas. De fato, aqui consideraremos apenas uma pequena parte de tais técnicas.
Modificadores Getter
O JavaScript suporta getters - funções que permitem trabalhar com o que eles retornam como em uma propriedade regular. Sob uso normal, fica assim:
let greeter = { name: 'Bob', get hello() { return `Hello ${this.name}`} } console.log(greeter.hello) // Hello Bob greeter.name = 'World'; console.log(greeter.hello) // Hello World
Se você usa getters, plotando o mal, pode criar, por exemplo, objetos autodestrutivos:
let obj = { foo: 1, bar: 2, baz: 3, get evil() { let keys = Object.keys(this); if(keys) { delete this[keys[0]] } return 'Nothing to see here'; } }
Aqui, a cada chamada para
obj.evil
, uma das outras propriedades do objeto será excluída. Ao mesmo tempo, o código que trabalha com
obj.evil
não saberá que algo muito estranho está acontecendo bem debaixo do seu nariz. No entanto, este é apenas o começo da conversa sobre os efeitos colaterais prejudiciais que podem ser alcançados usando mecanismos JavaScript.
Proxies inesperados
Getters são ótimos, mas eles existem há muitos anos, muitos desenvolvedores sabem sobre eles. Agora, graças ao proxy, temos à nossa disposição uma ferramenta muito mais poderosa para entreter objetos. Os proxies são um recurso do ES6 que permite criar wrappers em torno de objetos. Com a ajuda deles, você pode controlar o que acontece quando um usuário tenta ler ou gravar propriedades de objetos em proxy. Isso permite, por exemplo, criar um objeto que, em um terço das tentativas de acessar uma determinada chave desse objeto, retornará um valor por uma chave selecionada aleatoriamente.
let obj = {a: 1, b: 2, c: 3}; let handler = { get: function(obj, prop) { if (Math.random() > 0.33) { return obj[prop]; } else { let keys = Object.keys(obj); let key = keys[Math.floor(Math.random()*keys.length)] return obj[key]; } } }; let evilObj = new Proxy(obj, handler);
Infelizmente, nossa maldade é parcialmente divulgada por ferramentas de desenvolvedor que identificam
evilObj
como um objeto do tipo
Proxy
. No entanto, a construção descrita acima, antes que sua essência humilde seja revelada, é capaz de fornecer muitos minutos agradáveis para quem trabalhar com ela.
Funções contagiosas
Até agora, falamos sobre como os objetos podem se modificar. Além disso, podemos criar funções de aparência inocente que infectam os objetos transmitidos a eles, alterando seu comportamento. Por exemplo, suponha que tenhamos uma função
get()
simples que permite pesquisar com segurança uma propriedade no objeto passado para ela, levando em consideração o fato de que esse objeto pode não existir:
let get = (obj, property, default) => { if(!obj) { return default; } return obj[property]; }
É fácil reescrever essa função para infectar os objetos transferidos para ela, alterando-os levemente. Por exemplo, você pode garantir que a propriedade à qual ajudou a acessar não seja mais exibida ao tentar iterar sobre as chaves de um objeto:
let get = (obj, property, defaultValue) => { if(!obj || !property in obj) { return defaultValue; } let value = obj[property]; delete obj[property]; Object.defineProperty(obj, property, { value, enumerable: false }) return obj[property]; } let x = {a: 1, b:2 }; console.log(Object.keys(x)); // ['a', 'b'] console.log(get(x, 'a')); console.log(Object.keys(x)); // ['b']
Este é um exemplo de uma intervenção muito sutil no comportamento de um objeto. A enumeração de chaves de um objeto não é a operação mais perceptível, pois não é muito rara, mas não é usada com muita frequência. Como os erros aos quais essa modificação de objetos pode levar não podem ser vinculados ao seu código, eles podem existir em um determinado projeto por algum tempo.
Bagunça protótipo
Discutimos vários recursos do JS acima, incluindo alguns bastante recentes. No entanto, às vezes não há nada melhor que a tecnologia antiga e testada pelo tempo. Um dos recursos do JS, devido ao qual é mais criticado, é a capacidade de modificar protótipos internos. Esse recurso foi usado nos primeiros anos do JS para estender objetos incorporados, como matrizes. Veja como estender os recursos padrão da matriz, digamos, adicionando o método
contains
a um objeto
Array
protótipo:
Array.prototype.contains = function(item) { return this.indexOf(item) !== -1; }
Como se vê, se você fizer algo assim em uma biblioteca realmente usada, poderá
interromper o trabalho com os mecanismos básicos da linguagem em todo o aplicativo que usa essa biblioteca. Portanto, a inclusão de métodos úteis adicionais em protótipos de objetos padrão pode ser considerada uma ação muito bem-sucedida para desenvolvedores de pacientes que desejam fazer outras coisas desagradáveis. No entanto, se estamos falando de sociopatas impacientes, eles podem oferecer algo rápido, mas não menos interessante. A modificação de protótipos tem uma propriedade muito útil, que consiste no fato de que a modificação afeta todo o código que é executado em um determinado ambiente, mesmo aquele carregado por módulos ou em fechamentos. Como resultado, se você criar o código a seguir na forma de um script de terceiros (por exemplo, pode ser um script de uma rede de publicidade ou de um serviço analítico), todo o site que usa esse script estará sujeito a pequenos erros.
Array.prototype.map = function(fn) { let arr = this; let arr2 = arr.reduce((acc, val, idx) => { if (Math.random() > 0.95) { idx = idx + 1 } let index = acc.length - 1 === idx ? (idx - 1 ) : idx acc[index] = fn(val, index, arr); return acc; },[]); return arr2; }
Aqui redefinimos o método padrão
Array.prototype.map
para que, em geral, funcione bem, mas em 5% dos casos, troque dois elementos da matriz. Aqui está o que você pode obter após várias chamadas para esse método:
let arr = [1,2,3,4,5,6,7,8,9,10,11,12,13,14,15]; let square = x => x * x; console.log(arr.map(square)); // [1,4,9,16,25,36,49,64,100,81,121,144,169,196,225 console.log(arr.map(square)); // [1,4,9,16,25,36,49,64,81,100,121,144,169,196,225] console.log(arr.map(square)); // [1,4,9,16,25,36,49,64,81,100,121,144,169,196,225]
Aqui o lançamos três vezes. O que aconteceu quando você o usou pela primeira vez é um pouco diferente dos dois resultados a seguir. Essa é uma pequena mudança, nem sempre causa algum tipo de falha. E a melhor parte é que é impossível entender a causa dos erros que ocorrem raramente causados por esse método sem ler o código-fonte, que é a causa desses erros. Nossa função não chama a atenção ao trabalhar com ferramentas de desenvolvedor, não produz erros ao trabalhar no modo estrito. Em geral, com a ajuda de algo assim, é completamente possível deixar alguém louco.
Nomes complicados
Nomear entidades, como você sabe, é uma das duas tarefas mais difíceis da ciência da computação. Portanto, nomes ruins são inventados não apenas por aqueles que conscientemente procuram prejudicar os outros. Obviamente, pode ser difícil acreditar em usuários experientes do Linux. Eles tinham anos à sua disposição para associar o pior intruso de TI (Microsoft) às formas mais profundas do mal. Mas nomes malsucedidos não prejudicam diretamente os programas. Não falaremos sobre coisas pequenas, como nomes enganosos e comentários que perderam relevância. Por exemplo, sobre isso:
// let arrayOfNumbers = { userid: 1, name: 'Darth Vader'};
Para aprofundar isso e entender que há algo errado com o comentário e com o nome da variável, quem lê o código no qual isso ocorre terá que desacelerar e pensar um pouco. Mas isso não faz sentido. Vamos falar de coisas realmente interessantes. Você sabia que a maioria dos caracteres Unicode pode ser usada para nomear variáveis em JavaScript? Se você, na questão de atribuir nomes de variáveis, for positivo, você gostará da ideia de usar nomes na forma de ícones (
Habr corta emoji, embora no original aqui depois let
sido emoji kakahi ):
let = { postid: 123, postName: 'Evil JavaScript'}
Embora estejamos falando de coisas realmente desagradáveis aqui, é melhor recorrermos a caracteres semelhantes aos que normalmente são usados para nomear variáveis, mas não são. Por exemplo, vamos fazer assim:
let obj = {}; console.log(obj);
A letra
b
no nome
obj
pode parecer quase normal, mas não é uma letra latina minúscula b. Essa é a chamada letra latina minúscula de largura total b. Os símbolos são diferentes, portanto, quem tentar digitar o nome dessa variável manualmente provavelmente ficará muito confuso.
Sumário
Apesar da história de várias coisas desagradáveis que podem ser feitas usando JavaScript, este material visa alertar os programadores a usar truques como os descritos e transmitir a eles o fato de que isso pode causar danos reais. O autor do material diz que é sempre útil saber quais problemas podem aparecer em códigos mal escritos. Ele acredita que algo semelhante pode ser encontrado em projetos reais, mas espera que exista de uma forma menos destrutiva. No entanto, o fato de o programador que escreveu esse código não tentar prejudicar outras pessoas não facilita o trabalho com esse código e a depuração. Ao mesmo tempo, o conhecimento de quais tentativas intencionais de prejudicar pode trazer pode expandir os horizontes de um programador e ajudá-lo a encontrar uma fonte de erros semelhantes. Ninguém pode ter certeza absoluta de que não há erro no código com o qual ele trabalha. Talvez alguém, sabendo de sua tendência a suspeitar excessivamente, tente tranquilizar-se de que a ansiedade por esses erros é apenas uma invenção de sua imaginação. No entanto, isso não impedirá que esses erros, possivelmente introduzidos em algum código intencionalmente, se provem uma vez.
Caros leitores! Você encontrou na prática algo semelhante ao discutido neste artigo?
