غالبًا ما ينشرون أجزاء من الشفرات والميمات المثيرة للاشمئزاز حول البرمجة. لكن ذات يوم رأيت شيئًا رائعًا للغاية هناك.

حاز هذا الجزء من الكود على اللقب الفخري "أفضل عمل" لهذا الأسبوع.
قررت تفكيك هذا الكود ، ولكن هناك الكثير من الأشياء الخاطئة التي يصعب علي حتى اختيار المشكلة الأولى لتحليلها.
إذا كنت مبرمجًا مبتدئًا ، فستساعدك مادتي على فهم الأخطاء الفادحة التي ارتكبها أولئك الذين كتبوا هذا الكود.
28 سطرًا من أخطاء التعليمات البرمجية
لجعله أكثر ملاءمة ، دعنا نكتب هذا الرمز في المحرر:
<script>
function authenticateUser(username, password) {
var accounts = apiService.sql(
"SELECT * FROM users"
);
for (var i = 0; i < accounts.length; i++) {
var account = accounts [i];
if (account.username === username &&
account.password === password)
{
return true;
}
}
if ("true" === "true") {
return false;
}
}
$('#login').click(function() {
var username = $("#username").val();
var password = $("#password").val();
var authenticated = authenticateUser(username, password);
if (authenticated === true) {
$.cookie('loggedin', 'yes', { expires: 1 });
} else if (authenticated === false) {
$("error_message").show(LogInFailed);
}
});
</script>
و ... حسنًا ، حقًا - لا أعرف من أين أبدأ في تحليلها. لكن لا يزال عليك أن تبدأ. قررت تقسيم أوجه القصور في هذا الرمز إلى ثلاث فئات:
- قضايا أمنية.
- مشاكل المعرفة الأساسية بالبرمجة.
- مشاكل تنسيق التعليمات البرمجية.
قضايا أمنية
ربما يتم تشغيل هذا الرمز على العميل. يشار إلى هذا من خلال حقيقة أنه ملفوف في علامة
<script>(بالإضافة إلى ذلك ، يتم استخدام jQuery هنا).
لكن لا تفهموني خطأ. سيبدو هذا الرمز فظيعًا تمامًا إذا كان للخادم. لكن تنفيذه على العميل يعني أن كل شخص قادر على قراءة هذا الرمز سيتمكن من الوصول إلى قاعدة البيانات المستخدمة فيه.
دعنا نلقي نظرة على الوظيفة
authenticateUser:
function authenticateUser(username, password) {
var accounts = apiService.sql(
"SELECT * FROM users"
);
for (var i = 0; i < accounts.length; i++) {
var account = accounts [i];
if (account.username === username &&
account.password === password)
{
return true;
}
}
if ("true" === "true") {
return false;
}
}
من تحليل الكود الخاص به ، يمكننا أن نستنتج أنه يوجد في مكان ما كائن
apiServiceيمنحنا طريقة .sqlيمكننا من خلالها تنفيذ استعلامات SQL في قاعدة البيانات. هذا يعني أنه إذا قمت بفتح وحدة تحكم المطور أثناء عرض الصفحة التي تحتوي على هذا الرمز ، يمكنك تنفيذ أي استعلامات على قاعدة البيانات.
على سبيل المثال ، يمكنك تشغيل استعلام مثل هذا:
apiService.sql("show tables;");
استجابةً لذلك ، سيتم إرجاع قائمة كاملة بجداول قاعدة البيانات. ويتم كل هذا باستخدام واجهة برمجة التطبيقات الخاصة بالمشروع. ولكن ، ما هو موجود بالفعل ، دعونا نتخيل أن هذه ليست مشكلة حقيقية. دعنا نلقي نظرة أفضل على هذا:
if (account.username === username &&
account.password === password)
يخبرني هذا الرمز أنه يتم تخزين كلمات المرور في المشروع دون تجزئة. خطوة عظيمة! هذا يعني أنه يمكنني أخذ مصحح الأخطاء ومشاهدة كلمات مرور مستخدمي المشروع. أعتقد أيضًا أن نسبة كبيرة من المستخدمين يستخدمون نفس اسم المستخدم / كلمة المرور على وسائل التواصل الاجتماعي وخدمات البريد الإلكتروني والتطبيقات المصرفية وأماكن أخرى مماثلة.

أرى أيضًا المشكلة في كيفية تعيين ملفات تعريف الارتباط في هذا الرمز
loggedin:
$.cookie('loggedin', 'yes', { expires: 1 });
اتضح أنه يستخدم jQuery لتعيين ملف تعريف ارتباط يخبر تطبيق الويب إذا كان المستخدم قد تمت مصادقته.
تذكر: لا تقم أبدًا بتعيين ملفات تعريف الارتباط هذه باستخدام JavaScript.
إذا كنت بحاجة إلى تخزين هذا النوع من المعلومات على العميل ، فغالبًا ما يتم استخدام ملفات تعريف الارتباط لهذا الغرض. لا أستطيع أن أقول أي شيء سيئ عن هذه الفكرة. لكن تعيين ملف تعريف ارتباط باستخدام JavaScript يعني أنه لا يمكن تكوين السمة
httpOnly. وهذا بدوره يعني أن أي برنامج نصي ضار يمكنه الوصول إلى ملفات تعريف الارتباط هذه.
ونعم ، أعلم أن شيئًا ما مثل المفتاح فقط: يتم تخزين زوج القيمة هنا ،
'loggedin': 'yes'، لذلك حتى إذا قرأ نص لشخص آخر شيئًا كهذا ، فلن يكون هناك ضرر كبير منه. لكن هذه ، على أي حال ، ممارسة سيئة للغاية.
أيضًا ، عندما أفتح وحدة تحكم Chrome ، يمكنني دائمًا إدخال أمر مثل
$.cookie('loggedin', 'yes', { expires: 1000000000000 });. نتيجة لذلك ، اتضح أنني قمت بتسجيل الدخول إلى الموقع إلى الأبد ، حتى دون أن يكون لدي حساب عليه.
مشاكل المعرفة الأساسية بالبرمجة
يمكننا التحدث والتحدث عن مشكلات مماثلة يمكن العثور عليها في هذا الجزء من الكود ، وليس لدينا الكثير من الوقت.
لذا ، من الواضح أن الوظيفة
authenticateUserهي مثال على كود مكتوب بشكل سيء للغاية. يوضح أن الشخص الذي كتبه يفتقر إلى بعض المعرفة الأساسية بالبرمجة.
function authenticateUser(username, password) {
var accounts = apiService.sql(
"SELECT * FROM users"
);
for (var i = 0; i < accounts.length; i++) {
var account = accounts [i];
if (account.username === username &&
account.password === password)
{
return true;
}
}
if ("true" === "true") {
return false;
}
}
ربما بدلاً من الحصول على قائمة كاملة بالمستخدمين من قاعدة البيانات ، يكفي اختيار مستخدم باسم معين وكلمة مرور؟ ماذا يحدث إذا تم تخزين ملايين المستخدمين في قاعدة البيانات هذه؟
لقد تحدثت بالفعل عن هذا ، لكنني سأكرر نفسي ، أطرح السؤال التالي: "لماذا لا يقومون بتجزئة كلمات المرور في قاعدة البيانات الخاصة بهم؟"
دعنا الآن نلقي نظرة على ما ترجع الدالة
authenticateUser. مما يمكنني رؤيته ، يمكنني أن أستنتج أنه يأخذ وسيطتين من النوع stringويعيد قيمة من نوع واحد boolean.
لذلك ، فإن الجزء التالي من الكود ، على الرغم من كتابته بشكل مروع ، لا يمكن اعتباره بلا معنى:
for (var i = 0; i < accounts.length; i++) {
var account = accounts [i];
if (account.username === username &&
account.password === password)
{
return true;
}
}
إذا قمت بترجمتها إلى لغة عادية ، فستحصل على شيء مثل هذا: "هل هناك مستخدم باسم X وكلمة المرور Y؟ إذا كانت الإجابة بنعم ، فعد صحيحًا ".
الآن دعنا نلقي نظرة على هذا الجزء من الكود:
if ("true" === "true") {
return false;
}
كلام فارغ. لماذا لا تعود الوظائف فقط
false؟ لماذا تحتاج إلى شرط يكون صحيحًا دائمًا؟
الآن دعنا نحلل الكود التالي:
$('#login').click(function() {
var username = $("#username").val();
var password = $("#password").val();
var authenticated = authenticateUser(username, password);
if (authenticated === true) {
$.cookie('loggedin', 'yes', { expires: 1 });
} else if (authenticated === false) {
$("error_message").show(LogInFailed);
}
});
يبدو جزء jQuery من هذا الرمز جيدًا. لكن المشكلة هنا هي تنظيم العمل مع ملفات تعريف الارتباط
loggedin.
لقد قلنا بالفعل أنه حتى بدون حساب ، يمكنك ببساطة فتح وحدة تحكم Chrome وتشغيل الأمر
$.cookie('loggedin', 'yes', { expires: 1 });والمصادقة عليه ليوم واحد.
كيف تعرف هذه الصفحة من هو المستخدم المصادق عليه؟ ربما يعرض فقط شيئًا خاصًا ، مخصصًا فقط لأولئك الذين اجتازوا بنجاح التحقق من اسم المستخدم وكلمة المرور ولا يعرضون أي بيانات شخصية؟ لن نعرف هذا.
مشاكل تنسيق التعليمات البرمجية
ربما يكون التصميم هو أصغر وأهم مشكلة في هذا الكود. لكنه يشير بوضوح إلى أن الشخص الذي أنشأ هذا الرمز قام بنسخ ولصق أجزاءه الفردية من مكان ما.
فيما يلي مثال على استخدام علامات الاقتباس المزدوجة عند تنسيق السلاسل:
var username = $("#username").val();
var password = $("#password").val();
وفي أماكن أخرى ، يتم استخدام علامات الاقتباس الفردية:
$.cookie('loggedin', 'yes', { expires: 1 });
قد يبدو هذا غير مهم ، لكنه يخبرنا في الواقع أن المطور ربما قام بنسخ بعض التعليمات البرمجية من StackOverflow ، على سبيل المثال ، دون حتى إعادة كتابته ، باتباع دليل النمط المستخدم في المشروع. بالطبع ، هذه تفاصيل صغيرة ، لكنها تشير إلى أن المطور لا يهتم حقًا بفهم كيفية عمل الكود. هذا المطور يحتاج فقط إلى الكود ليعمل بطريقة ما.
أود أن أوضح هنا وجهة نظري حول هذه المشكلة. أنا أبحث في Google عن الأشياء المتعلقة بالشفرة كل يوم ، لكنني أعتقد أنه من المهم جدًا فهم كيفية تعيين ملف تعريف الارتباط ، على سبيل المثال ، بدلاً من إنشاء جزء من التعليمات البرمجية يتم نسخه دون تفكير من مكان العمل. ماذا لو توقف البرنامج عن العمل لسبب ما؟ كيف يمكن للمبرمج الذي لا يفهم ما يجري في شفرته أن يجد خطأ؟
النتيجة
أنا متأكد تمامًا من أن هذا الجزء من الكود مزيف. رأيت هنا أولاً مثالاً على التنفيذ المتزامن لاستعلام SQL:
var accounts = apiService.sql(
"SELECT * FROM users"
);
عادة يتم حل هذه المهام على النحو التالي:
var accounts = apiService.sql("SELECT * FROM users", (err, res) => {
console.log(err); //
console.log(res); //
});
يتم حلها أيضًا على النحو التالي:
var accounts = await apiService.sql(
"SELECT * FROM users"
);
حتى إذا
apiService.sqlأعادت الطريقة نتيجة في الوضع المتزامن (وهو ما أشك فيه) ، فإنها تحتاج إلى فتح اتصال بقاعدة البيانات ، وتنفيذ استعلام ، وإرسال النتيجة إلى نقطة الاتصال. ولا يمكن إجراء هذه العمليات (كما قد تتخيل) بشكل متزامن.
لكن حتى لو كان هذا رمزًا حقيقيًا تمامًا ، فأنا متأكد من أنه كتب بواسطة مبرمج مبتدئ ، مبتدئ. أنا متأكد من أنني عندما عملت كمبرمج في الأسابيع الأولى ، كتبت أيضًا رمزًا سيئًا (آسف: D).
لكن هذا ليس خطأ المبرمج المبتدئ.
لنفترض أن هذا رمز حقيقي يعمل في مكان ما. سيبذل بعض المبتدئين قصارى جهدهم لجعل هذا الرمز يعمل. لا يزال يتعين على هذا المطور تعلم كيفية التعامل بشكل صحيح مع استعلامات SQL وملفات تعريف الارتباط وكل شيء آخر. وفي ضوء ذلك ، إنه طبيعي تمامًا.
لكن رئيس هذا المبرمج يجب أن يتحكم في العمل ، ويشير إلى الأخطاء ، ويجعل المبتدئ يفهم أخطائه. سيؤدي هذا إلى حقيقة أن هذا الرمز الرهيب لا يصل إلى الإنتاج.
أعرف على وجه اليقين أن هناك شركات لا تهتم بجودة الكود الذي يدخل حيز الإنتاج. هل الكود يحل المشكلة؟ إذا كان الأمر كذلك ، يتم نشره دون أي أسئلة. هل الكود مكتوب من قبل مبتدئ ولم يتم اختباره من قبل مبرمج أكثر خبرة؟ في إنتاجه!
بشكل عام ، هناك أحزان في الحياة.
التحديث اعتبارًا من 8 أغسطس 2020
عندما كنت أكتب هذا المقال ، اعتقدت أن الكود الذي كنت أقوم بتحليله كان مزيفًا. ولكن بعد مناقشة المادة على Reddit ، أُعطيت رابطًا لهذا الموضوع. كما اتضح ، يتم استخدام هذا الرمز في بعض الأنظمة الداخلية التي تدعم 1500 مستخدم. لذلك من الواضح أنني كنت مخطئا. هذا رمز حقيقي تمامًا.
هل صادفت أجزاء من التعليمات البرمجية السيئة بصراحة أثناء الإنتاج ، مليئة بجميع أنواع المشاكل؟
