JavaScript sebagai perwujudan kejahatan

Pengembang JavaScript sering mengeluh bahwa bahasa pemrograman mereka disalahkan secara tidak adil karena memiliki terlalu banyak fitur yang rumit dan membingungkan. Banyak yang berjuang dengan sikap ini terhadap JS, berbicara tentang mengapa mengkritik bahasa ini karena apa yang salah. Penulis materi, terjemahan yang kami terbitkan hari ini, memutuskan untuk tidak membela JS, alih-alih beralih ke sisi gelap bahasa. Namun, di sini ia tidak ingin berbicara, misalnya, tentang jebakan yang dibuat JavaScript untuk programmer yang tidak berpengalaman. Dia tertarik pada pertanyaan tentang apa yang terjadi jika Anda mencoba mengkonfirmasi reputasi buruk bahasa dengan kode yang dapat ditulis oleh seseorang yang tidak peduli sama sekali tentang orang lain.



Dalam contoh bahan ini, banyak mekanisme bahasa akan digunakan. Banyak yang Anda lihat di sini, omong-omong, bekerja dalam bahasa lain, jadi, dengan uji tuntas, Anda juga dapat menemukan sisi gelapnya. Tetapi JavaScript, tentu saja, memiliki hadiah nyata untuk semua jenis intimidasi, dan sangat sulit untuk bersaing dengan bahasa lain di area ini. Jika Anda menulis kode yang harus dikerjakan orang lain, JS memberi Anda banyak kesempatan untuk mengganggu, membingungkan, dan melecehkan serta menipu orang-orang ini. Faktanya, di sini kita akan mempertimbangkan hanya sebagian kecil dari teknik-teknik tersebut.

Pengubah getter


JavaScript mendukung getter - fungsi yang memungkinkan Anda bekerja dengan apa yang mereka kembalikan dengan properti biasa. Dalam penggunaan normal, tampilannya seperti ini:

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 

Jika Anda menggunakan getter, merencanakan kejahatan, maka, misalnya, Anda dapat membuat objek yang dapat merusak diri sendiri:

 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';  } } 

Di sini, dengan setiap panggilan ke obj.evil , salah satu properti lain dari objek akan dihapus. Pada saat yang sama, kode yang bekerja dengan obj.evil tidak akan tahu bahwa sesuatu yang sangat aneh terjadi tepat di bawah hidungnya. Namun, ini hanya awal dari pembicaraan tentang efek samping berbahaya yang dapat dicapai menggunakan mekanisme JavaScript.

Proksi yang tidak terduga


Getters itu hebat, tetapi sudah ada selama bertahun-tahun, banyak pengembang tahu tentang mereka. Sekarang, berkat proxy, kami memiliki alat yang jauh lebih kuat untuk menghibur objek. Proxy adalah fitur ES6 yang memungkinkan Anda membuat pembungkus di sekitar objek. Dengan bantuan mereka, Anda dapat mengontrol apa yang terjadi ketika pengguna mencoba membaca atau menulis properti dari objek yang diproksikan. Ini memungkinkan, misalnya, untuk membuat objek yang, dalam sepertiga upaya untuk mengakses kunci tertentu dari objek tersebut, akan mengembalikan nilai dengan kunci yang dipilih secara acak.

 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); //          console.log(evilObj.a); // 1 console.log(evilObj.b); // 1 console.log(evilObj.c); // 3 console.log(evilObj.a); // 2 console.log(evilObj.b); // 2 console.log(evilObj.c); // 3 

Sayangnya, kekejaman kami sebagian diungkapkan oleh alat pengembang yang mengidentifikasi evilObj sebagai objek bertipe Proxy . Namun, konstruksi yang dijelaskan di atas, sebelum esensinya yang rendah terungkap, mampu memberikan banyak menit yang menyenangkan bagi mereka yang akan bekerja dengannya.

Fungsi menular


Sejauh ini, kita telah berbicara tentang bagaimana objek dapat memodifikasi diri mereka sendiri. Tetapi, di samping itu, kita dapat membuat fungsi yang tampak tidak bersalah yang menginfeksi objek yang diteruskan kepada mereka, mengubah perilaku mereka. Sebagai contoh, misalkan kita memiliki fungsi get() sederhana yang memungkinkan Anda untuk mencari properti di objek yang diteruskan dengan aman, dengan mempertimbangkan fakta bahwa objek seperti itu mungkin tidak ada:

 let get = (obj, property, default) => {  if(!obj) {     return default;  }  return obj[property]; } 

Mudah untuk menulis ulang fungsi seperti itu sehingga menginfeksi objek yang ditransfer ke sana, sedikit mengubahnya. Misalnya, Anda dapat membuat properti yang dibantu untuk mengaksesnya tidak lagi ditampilkan saat mencoba untuk mengulangi kunci objek:

 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'] 

Ini adalah contoh dari intervensi yang sangat halus dalam perilaku suatu objek. Menghitung kunci dari suatu objek bukanlah operasi yang paling terlihat, karena itu tidak terlalu jarang, tetapi tidak terlalu sering digunakan. Karena kesalahan yang dapat menyebabkan modifikasi objek tidak dapat dikaitkan dengan kode mereka, mereka dapat ada dalam proyek tertentu untuk beberapa waktu.

Kekacauan prototipe


Kami membahas berbagai fitur JS di atas, termasuk beberapa yang cukup baru. Namun, terkadang tidak ada yang lebih baik dari teknologi yang sudah teruji oleh waktu. Salah satu fitur JS, yang paling banyak dikritik adalah kemampuan memodifikasi prototipe bawaan. Fitur ini digunakan pada tahun-tahun awal JS untuk memperluas objek yang disematkan, seperti array. Berikut cara memperluas kemampuan array standar dengan, katakanlah, menambahkan metode contains ke objek Array prototipe:

 Array.prototype.contains = function(item) { return this.indexOf(item) !== -1; } 

Ternyata, jika Anda melakukan sesuatu seperti ini di perpustakaan yang benar-benar digunakan, itu dapat mengganggu pekerjaan dengan mekanisme dasar bahasa di seluruh aplikasi yang menggunakan perpustakaan ini. Oleh karena itu, dimasukkannya metode tambahan yang bermanfaat dalam prototipe objek standar dapat dianggap sebagai langkah yang sangat sukses bagi pengembang pasien yang ingin melakukan hal-hal buruk lainnya. Namun, jika kita berbicara tentang sosiopat yang tidak sabar, mereka dapat ditawari sesuatu dengan cepat, tetapi tidak kalah menarik. Modifikasi prototipe memiliki satu sifat yang sangat berguna, yang terdiri dari fakta bahwa modifikasi mempengaruhi semua kode yang dieksekusi di lingkungan tertentu, bahkan yang dimuat dari modul atau sedang ditutup. Akibatnya, jika Anda merancang kode berikut dalam bentuk skrip pihak ketiga (misalnya, itu bisa berupa skrip dari jaringan iklan atau layanan analitik), maka seluruh situs yang menggunakan skrip ini akan rentan terhadap kesalahan kecil.

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

Di sini kita mendefinisikan kembali metode standar Array.prototype.map sehingga, secara umum, itu berfungsi dengan baik, tetapi dalam 5% kasus itu menukar dua elemen array. Inilah yang bisa Anda dapatkan setelah beberapa panggilan ke metode ini:

 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] 

Di sini kami meluncurkannya tiga kali. Apa yang terjadi ketika Anda pertama kali menggunakannya sedikit berbeda dari dua hasil memanggilnya berikut ini. Ini adalah perubahan kecil, tidak selalu menyebabkan semacam kegagalan. Dan bagian terbaiknya adalah bahwa tidak mungkin untuk memahami penyebab kesalahan yang jarang terjadi yang disebabkan oleh metode ini tanpa membaca kode sumbernya, yang merupakan penyebab kesalahan ini. Fungsi kami tidak menarik perhatian saat bekerja dengan alat pengembang, tidak menghasilkan kesalahan saat bekerja dalam mode ketat. Secara umum, dengan bantuan sesuatu seperti ini, sangat mungkin untuk membuat seseorang gila.

Nama yang rumit


Entitas penamaan, seperti yang Anda tahu, adalah salah satu dari dua tugas paling sulit dalam ilmu komputer. Oleh karena itu, nama-nama buruk diciptakan tidak hanya oleh mereka yang secara sadar berusaha untuk menyakiti orang lain. Tentu saja, sulit untuk percaya pada pengguna Linux yang berpengalaman. Mereka memiliki waktu bertahun-tahun untuk mengasosiasikan penyusup TI terburuk (Microsoft) dengan bentuk kejahatan terdalam. Tetapi nama-nama yang tidak berhasil tidak secara langsung merusak program. Kami tidak akan membicarakan hal-hal kecil seperti nama dan komentar yang menyesatkan yang kehilangan relevansinya. Misalnya, tentang seperti:

 //   let arrayOfNumbers = { userid: 1, name: 'Darth Vader'}; 

Untuk mempelajari ini dan memahami bahwa ada sesuatu yang salah dengan komentar dan dengan nama variabel, mereka yang membaca kode di mana ini terjadi harus melambat dan berpikir sedikit. Tapi ini omong kosong. Mari kita bicara tentang hal-hal yang sangat menarik. Tahukah Anda bahwa sebagian besar karakter Unicode dapat digunakan untuk memberi nama variabel dalam JavaScript? Jika Anda, dalam hal menetapkan nama variabel, adalah positif, maka Anda akan menyukai gagasan menggunakan nama dalam bentuk ikon ( Habr cut emoji, meskipun dalam aslinya di sini setelah let adalah emoji kakahi ):

 let = { postid: 123, postName: 'Evil JavaScript'} 

Meskipun, kita berbicara tentang hal-hal buruk yang nyata di sini, jadi sebaiknya kita beralih ke karakter yang mirip dengan yang biasanya digunakan untuk memberi nama variabel, tetapi sebenarnya tidak. Sebagai contoh, mari kita lakukan seperti ini:

 let obj = {}; console.log(obj); // Error! 

Huruf dalam nama obj mungkin terlihat hampir normal, tetapi itu bukan huruf Latin kecil b. Ini adalah apa yang disebut huruf latin dengan huruf besar dan lebar penuh b. Simbol berbeda, sehingga siapa pun yang mencoba memasukkan nama variabel seperti itu secara manual kemungkinan besar akan sangat bingung.

Ringkasan


Terlepas dari kisah berbagai hal buruk yang dapat dilakukan menggunakan JavaScript, materi ini ditujukan untuk memperingatkan programmer dari menggunakan trik seperti yang dijelaskan, dan menyampaikan kepada mereka fakta bahwa ini dapat menyebabkan kerusakan nyata. Penulis materi mengatakan bahwa selalu berguna untuk mengetahui masalah apa yang dapat muncul dalam kode yang ditulis dengan buruk. Dia percaya bahwa sesuatu yang serupa dapat ditemukan dalam proyek nyata, tetapi berharap ada di sana dalam bentuk yang kurang merusak. Namun, fakta bahwa programmer yang menulis kode seperti itu tidak berusaha untuk menyakiti orang lain tidak membuatnya lebih mudah untuk bekerja dengan kode tersebut dan men-debug-nya. Pada saat yang sama, pengetahuan tentang upaya berbahaya apa yang dapat dilakukan dapat memperluas wawasan seorang programmer dan membantunya menemukan sumber kesalahan serupa. Tidak ada yang bisa sepenuhnya yakin bahwa tidak ada kesalahan dalam kode yang digunakannya. Mungkin seseorang, mengetahui kecenderungan mereka untuk terlalu curiga, akan mencoba meyakinkan diri mereka sendiri bahwa kecemasan tentang kesalahan semacam itu hanyalah isapan jempol belaka dari imajinasinya. Namun, ini tidak akan mencegah kesalahan seperti itu, mungkin dengan sengaja dimasukkan ke dalam beberapa kode, untuk membuktikan diri mereka sekali.

Pembaca yang budiman! Sudahkah Anda menemukan sesuatu yang mirip dengan yang dibahas dalam artikel ini?

Source: https://habr.com/ru/post/id417263/


All Articles