جافا سكريبت تجسيدًا للشر

غالبًا ما يشتكي مطورو جافا سكريبت من أن لغة البرمجة الخاصة بهم يتم إلقاء اللوم بشكل غير عادل على وجود الكثير من الميزات المعقدة والمربكة. يعاني الكثير من هذا الموقف تجاه JS ، ويتحدثون عن سبب انتقاد هذه اللغة بسبب خطأها. قرر مؤلف المادة ، التي ننشر ترجمتها اليوم ، عدم الدفاع عن شبيبة ، والتحول بدلاً من ذلك إلى الجانب المظلم من اللغة. ومع ذلك ، هنا لا يريد التحدث ، على سبيل المثال ، عن الفخاخ التي تحددها JavaScript للمبرمجين عديمي الخبرة. إنه مهتم بمسألة ما يحدث إذا حاولت تأكيد السمعة السيئة للغة برمز يمكن أن يكتبه شخص لا يهتم على الإطلاق بالآخرين.



في الأمثلة على هذه المادة ، سيتم استخدام العديد من الآليات اللغوية. الكثير مما تراه هنا ، بالمناسبة ، يعمل بلغات أخرى ، لذلك ، مع العناية الواجبة ، يمكنك أيضًا العثور على جوانبهم المظلمة. لكن جافا سكريبت ، بالتأكيد ، لديها هدية حقيقية لجميع أنواع التنمر ، ومن الصعب جدًا التنافس مع اللغات الأخرى في هذا المجال. إذا كتبت رمزًا سيحتاج الأشخاص الآخرون للعمل معه ، يمنحك JS عددًا لا ينضب من الفرص لإزعاج هؤلاء الأشخاص وإرباكهم ومضايقتهم وخداعهم. في الواقع ، سننظر هنا فقط في جزء صغير من هذه التقنيات.

معدّلات أفضل


تدعم JavaScript الحروف - الوظائف التي تسمح لك بالعمل مع ما تعود عليه مع خاصية عادية. تحت الاستخدام العادي ، يبدو كما يلي:

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 

إذا كنت تستخدم الحروف ، بالتآمر على الشر ، على سبيل المثال ، يمكنك إنشاء كائنات مدمرة للذات:

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

هنا ، مع كل استدعاء لـ obj.evil ، سيتم حذف إحدى خصائص الكائن الأخرى. في الوقت نفسه ، لن تعرف الشفرة التي تعمل مع obj.evil أن شيئًا غريبًا جدًا يحدث تحت أنفه. ومع ذلك ، هذه ليست سوى بداية المحادثة حول الآثار الجانبية الضارة التي يمكن تحقيقها باستخدام آليات JavaScript.

وكلاء غير متوقعين


Getters رائعة ، لكنها موجودة منذ سنوات عديدة ، يعرفها العديد من المطورين عنها. الآن ، بفضل الوكيل ، لدينا تحت تصرفنا أداة أكثر قوة للتسلية مع الأشياء. الخوادم الوكيلة هي ميزة ES6 تسمح لك بإنشاء أغلفة حول الكائنات. بمساعدتهم ، يمكنك التحكم في ما يحدث عندما يحاول المستخدم قراءة أو كتابة خصائص الكائنات التي تم إنشاء وكيل لها. يسمح هذا ، على سبيل المثال ، بإنشاء كائن ، في ثلث محاولات الوصول إلى مفتاح معين لمثل هذا الكائن ، سيعيد قيمة بمفتاح محدد عشوائيًا.

 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 

لسوء الحظ ، تم الكشف عن evilObj جزئيًا من خلال أدوات المطورين التي تحدد evilObj ككائن من نوع Proxy . ومع ذلك ، فإن البناء الموصوف أعلاه ، قبل الكشف عن جوهره المتواضع ، قادر على تقديم العديد من الدقائق الممتعة لأولئك الذين سيعملون معه.

وظائف معدية


حتى الآن ، تحدثنا عن كيفية تعديل الأشياء نفسها. ولكن ، بالإضافة إلى ذلك ، يمكننا إنشاء وظائف تبدو بريئة تصيب الأشياء التي تنتقل إليهم ، وتغيير سلوكهم. على سبيل المثال ، لنفترض أن لدينا وظيفة get() بسيطة تسمح لك بالبحث بأمان عن خاصية في الكائن الذي تم تمريره إليها ، مع الأخذ في الاعتبار حقيقة أن مثل هذا الكائن قد لا يكون موجودًا:

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

من السهل إعادة كتابة هذه الوظيفة بحيث تصيب الأشياء المنقولة إليها ، وتغييرها قليلاً. على سبيل المثال ، يمكنك التأكد من أن الخاصية التي ساعدت في الوصول إليها لم تعد معروضة عند محاولة التكرار فوق مفاتيح الكائن:

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

هذا مثال على تدخل خفي للغاية في سلوك الجسم. لا يعد تعداد مفاتيح الكائن العملية الأكثر بروزًا ، حيث إنه ليس نادرًا جدًا ، ولكنه لا يستخدم كثيرًا. نظرًا لأن الأخطاء التي يمكن أن يؤدي إليها هذا التعديل للكائنات لا يمكن ربطها بكودها ، فقد توجد في مشروع معين لبعض الوقت.

فوضى النموذج الأولي


ناقشنا ميزات JS أعلاه ، بما في ذلك بعض الميزات الحديثة إلى حد ما. ومع ذلك ، في بعض الأحيان لا يوجد شيء أفضل من التكنولوجيا القديمة التي تم اختبارها عبر الزمن. واحدة من ميزات JS ، التي يتم انتقادها بسببها ، هي القدرة على تعديل النماذج الأولية المدمجة. تم استخدام هذه الميزة في السنوات الأولى من JS لتوسيع الكائنات المضمنة ، مثل المصفوفات. إليك كيفية توسيع إمكانات الصفيف القياسية ، على سبيل المثال ، بإضافة الأسلوب contains على كائن نموذج Array :

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

كما اتضح ، إذا فعلت شيئًا كهذا في مكتبة مستخدمة حقًا ، يمكن أن يعطل العمل بالآليات الأساسية للغة في التطبيق بأكمله الذي يستخدم هذه المكتبة. لذلك ، يمكن اعتبار تضمين طرق مفيدة إضافية في النماذج الأولية للكائنات القياسية خطوة ناجحة للغاية لمطوري المرضى الذين يرغبون في القيام بأشياء سيئة أخرى. ومع ذلك ، إذا كنا نتحدث عن مرضى اجتماعيين نفد صبرهم ، فيمكن تقديم شيء سريع لهم ، ولكن ليس أقل إثارة للاهتمام. تعديل النماذج الأولية له خاصية واحدة مفيدة للغاية ، والتي تتكون في حقيقة أن التعديل يؤثر على جميع التعليمات البرمجية التي يتم تنفيذها في بيئة معينة ، حتى لو تم تحميله من الوحدات النمطية أو في الإغلاق. ونتيجة لذلك ، إذا قمت بتصميم الكود التالي في شكل نص برمجي تابع لجهة خارجية (على سبيل المثال ، يمكن أن يكون نصًا برمجيًا من شبكة إعلانية أو خدمة تحليلية) ، فإن الموقع بأكمله الذي يستخدم هذا النص البرمجي سيكون عرضة لأخطاء صغيرة.

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

هنا قمنا إعادة تعريف الطريقة القياسية Array.prototype.map بحيث تعمل بشكل عام على ما يرام، ولكن 5٪ من مبادلات اثنين من مجموعة. إليك ما يمكنك الحصول عليه بعد عدة مكالمات بهذه الطريقة:

 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] 

هنا أطلقناها ثلاث مرات. ما حدث عندما استخدمته لأول مرة يختلف قليلاً عن النتيجتين التاليتين للاتصال به. هذا تغيير طفيف ، لا يسبب دائمًا نوعًا من الفشل. وأفضل جزء هو أنه من المستحيل فهم سبب الأخطاء التي تحدث نادرًا بسبب هذه الطريقة دون قراءة التعليمات البرمجية المصدر الخاصة بها ، والتي هي سبب هذه الأخطاء. لا تجذب وظيفتنا الانتباه عند العمل باستخدام أدوات المطورين ، ولا تنتج أخطاء عند العمل في وضع صارم. بشكل عام ، بمساعدة شيء من هذا القبيل ، من الممكن تمامًا دفع شخص ما إلى الجنون.

أسماء معقدة


تعد تسمية الكيانات ، كما تعلم ، واحدة من أصعب مهمتين لعلوم الكمبيوتر. لذلك ، يتم اختراع الأسماء السيئة ليس فقط من قبل أولئك الذين يسعون بوعي إلى إيذاء الآخرين. بالطبع ، قد يكون من الصعب تصديق مستخدمي لينكس ذوي الخبرة. كان لديهم سنوات تحت تصرفهم لربط أسوأ متسلل لتكنولوجيا المعلومات (مايكروسوفت) بأعمق أشكال الشر. لكن الأسماء غير الناجحة لا تضر البرامج بشكل مباشر. لن نتحدث عن أشياء صغيرة مثل الأسماء والتعليقات المضللة التي فقدت أهميتها. على سبيل المثال ، حول هذا:

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

من أجل الخوض في هذا وفهم أن هناك خطأ في التعليق واسم المتغير ، يجب على أولئك الذين يقرؤون الرمز الذي يحدث فيه أن يتباطأوا ويفكروا قليلاً. لكن هذا هراء. دعونا نتحدث عن أشياء مثيرة للاهتمام حقا. هل تعلم أنه يمكن استخدام معظم أحرف Unicode لتسمية المتغيرات في JavaScript؟ إذا كنت ، فيما يتعلق بتعيين أسماء المتغيرات ، إيجابيًا ، فستعجبك فكرة استخدام الأسماء على شكل أيقونات ( Habr cut emoji ، على الرغم من أنها في الأصل هنا بعد let emoji kakahi ):

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

على الرغم من أننا نتحدث عن أشياء سيئة هنا ، لذلك من الأفضل أن ننتقل إلى الشخصيات التي تشبه تلك التي تستخدم عادة لتسمية المتغيرات ، لكنها ليست كذلك. على سبيل المثال ، لنفعل ذلك على النحو التالي:

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

قد يبدو الحرف في الاسم obj طبيعيًا تقريبًا ، ولكنه ليس حرف لاتيني صغير b. هذا هو ما يسمى بالحرف اللاتيني الصغير بعرض كامل b. تختلف الرموز ، لذا من المرجح أن يكون أي شخص يحاول إدخال اسم هذا المتغير يدويًا مرتبكًا للغاية.

الملخص


على الرغم من القصة حول الأشياء السيئة المختلفة التي يمكن القيام بها باستخدام JavaScript ، تهدف هذه المادة إلى تحذير المبرمجين من استخدام الحيل مثل تلك الموصوفة ونقلها إلى حقيقة أن هذا يمكن أن يسبب ضررًا حقيقيًا. يقول مؤلف المادة أنه من المفيد دائمًا معرفة المشاكل التي يمكن أن تظهر في كود مكتوب بشكل سيئ. يعتقد أن شيئًا مشابهًا يمكن العثور عليه في المشاريع الحقيقية ، لكنه يأمل أن يكون موجودًا بشكل أقل تدميراً. ومع ذلك ، فإن حقيقة أن المبرمج الذي كتب مثل هذا الكود لم يسع إلى إيذاء الآخرين لا يسهل العمل مع هذا الكود وتصحيحه. في الوقت نفسه ، فإن معرفة ما يمكن أن تجلبه المحاولات الهادفة للضرر يمكن أن توسع آفاق المبرمج وتساعده في العثور على مصدر لأخطاء مماثلة. لا يمكن لأحد أن يتأكد تمامًا من عدم وجود خطأ في الرمز الذي يعمل به. ربما شخص ما ، يعرف عن ميله إلى الشك المفرط ، سيحاول طمأنة نفسه بأن القلق من مثل هذه الأخطاء هو مجرد صورة من خياله. ومع ذلك ، فإن هذا لن يمنع مثل هذه الأخطاء ، التي ربما يتم إدخالها في بعض التعليمات البرمجية عن قصد ، لإثبات نفسها مرة واحدة.

أعزائي القراء! هل صادفت عمليًا شيئًا مشابهًا لما تمت مناقشته في هذه المقالة؟

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


All Articles