يعتبر العديد من المبرمجين في بداية حياتهم المهنية أن موضوع تسمية المتغيرات والوظائف والملفات والمكونات الأخرى ليس مهماً جداً. ونتيجة لذلك، غالبًا ما يكون منطق تصميمهم صحيحًا - فالخوارزميات تعمل بسرعة وتنتج التأثير المطلوب، بينما بالكاد يمكن قراءتها. سأحاول في هذه المقالة أن أصف بإيجاز ما يجب أن نسترشد به عند تسمية عناصر الكود المختلفة وكيف لا ننتقل من تطرف إلى آخر.
لماذا إهمال مرحلة التسمية سيطيل (في بعض الحالات - بشكل كبير) من أمد تطوير مشروعك؟
لنفترض أنك أنت و الفريق يستولون على الكود من المبرمجين الآخرين. إن المشروع التي ترثها تم تطويرها بدون أي حب على الإطلاق - لقد نجحت بشكل جيد، ولكن كان من الممكن كتابة كل عنصر من عناصرها بطريقة أفضل بكثير.
عندما يتعلق الأمر بالهندسة المعمارية، في حالة توريث التعليمات البرمجية، فإن ذلك يثير دائمًا كراهية وغضب المبرمجين الذين حصلوا عليها. ويرجع ذلك أحيانًا إلى استخدام تقنيات مندثرة (أو منقرضة)، وأحيانًا بسبب طريقة التفكير الخاطئة في التطبيق في بداية التطوير، وأحيانًا بسبب قلة معرفة المبرمج المسؤول.
على أي حال، مع مرور الوقت على المشروع، من الممكن أن يصل المبرمجون إلى مرحلة يكون فيها المبرمجون غاضبون من البنى والتقنيات. ففي نهاية المطاف، كل تطبيق يحتاج إلى إعادة كتابة بعض الأجزاء أو مجرد تغييرات في أجزاء معينة بعد مرور بعض الوقت - وهذا أمر طبيعي. لكن المشكلة التي ستجعل شعر المبرمجين يشيب هو صعوبة قراءة وفهم الكود الذي ورثوه.
خاصةً في الحالات القصوى عندما تتم تسمية المتغيرات بأحرف مفردة لا معنى لها وتكون الدوال طفرة مفاجئة من الإبداع، ولا تتسق بأي حال من الأحوال مع بقية التطبيق، فقد يصاب المبرمجون بالجنون. في مثل هذه الحالة، فإن أي تحليل للرمز الذي يمكن أن يعمل بسرعة وكفاءة مع التسمية الصحيحة يتطلب تحليلاً إضافيًا للخوارزميات المسؤولة عن إنتاج نتيجة الدالة، على سبيل المثال. ومثل هذا التحليل، على الرغم من أنه غير واضح - إلا أنه يضيع قدرًا كبيرًا من الوقت.
إن تنفيذ وظائف جديدة على امتداد أجزاء مختلفة من التطبيق يعني المرور بكابوس تحليلها، بعد مرور بعض الوقت عليك العودة إلى الكود وتحليله مرة أخرى لأن نواياه غير واضحة، والوقت السابق الذي قضيته في محاولة فهم تشغيله كان مضيعة لأنك لم تعد تتذكر ما هو الغرض منه.
وهكذا، نغرق في إعصار من الفوضى التي تسود التطبيق وتستهلك ببطء كل مشارك في تطويره. يكره المبرمجون المشروع، ويكره مديرو المشروع شرح سبب زيادة وقت تطويره باستمرار، ويفقد العميل الثقة ويغضب لأن لا شيء يسير وفق الخطة.
كيف تتجنب ذلك؟
دعونا نواجه الأمر - بعض الأشياء لا يمكن تخطيها. إذا كنا قد اخترنا تقنيات معينة في بداية المشروع، فعلينا أن ندرك أنه مع مرور الوقت إما أن يتوقف دعمها أو أن المبرمجين سيقلون أو يقل عدد المبرمجين الذين يجيدون تقنيات من بضع سنوات مضت والتي أصبحت قديمة ببطء. تتطلب بعض المكتبات في تحديثاتها تغييرات أكثر أو أقل في الكود البرمجي، والتي غالبًا ما تستلزم دوامة من التبعيات التي يمكن أن تعلق فيها أكثر.
من ناحية أخرى، ليس هذا هو السيناريو الأسود، بالطبع، فالتقنيات تتقدم في السن، لكن العامل الذي يبطئ بالتأكيد من وقت تطوير المشاريع التي تتضمنها هو الكود البرمجي القبيح إلى حد كبير. وبالطبع، لا بد أن نذكر هنا كتاب روبرت سي مارتن - وهو كتاب مقدس للمبرمجين، حيث يقدم المؤلف الكثير من الممارسات والمبادئ الجيدة التي يجب اتباعها لإنشاء كود يسعى إلى الكمال.
إن الشيء الأساسي عند تسمية المتغيرات هو نقل مقصدها بوضوح وببساطة. يبدو الأمر بسيطًا جدًا، لكن في بعض الأحيان يتم إهماله أو تجاهله من قبل العديد من الأشخاص. سيحدد الاسم الجيد ما يفترض أن يخزنه المتغير بالضبط أو ما يفترض أن تفعله الدالة - لا يمكن أن تكون التسمية عامة جدًا، لكن من ناحية أخرى، لا يمكن أن تصبح التسمية طويلة جدًا بحيث تسبب مجرد قراءتها تحديًا كبيرًا للدماغ. بعد مرور بعض الوقت مع التعليمات البرمجية ذات الجودة العالية، نختبر تأثير الانغماس، حيث نكون قادرين على ترتيب التسمية وتمرير البيانات إلى الدالة بطريقة لا شعورية بحيث لا يترك الأمر برمته أي أوهام حول النية التي تحركها والنتيجة المتوقعة من استدعائها.
شيء آخر يمكن العثور عليه في JavaScript، من بين أمور أخرى، هو محاولة الإفراط في تحسين الشيفرة، مما يجعلها في كثير من الحالات غير قابلة للقراءة. من الطبيعي أن تحتاج بعض الخوارزميات إلى عناية خاصة، وهو ما يعكس في كثير من الأحيان حقيقة أن القصد من الشيفرة البرمجية قد يكون أكثر تعقيدًا. ومع ذلك، فإن الحالات التي نحتاج فيها إلى تحسينات مفرطة نادرة للغاية، أو على الأقل تلك التي تكون فيها شفرتنا البرمجية قذرة. من المهم أن نتذكر أن العديد من التحسينات المتعلقة باللغة تحدث على مستوى أقل قليلًا من التجريد؛ على سبيل المثال، يمكن لمحرك V8، مع التكرارات الكافية، تسريع الحلقات بشكل كبير. الشيء الذي يجب التأكيد عليه هو حقيقة أننا نعيش في القرن الحادي والعشرين ولا نكتب برامج لمهمة أبولو 13. لدينا مساحة أكبر بكثير للمناورة في موضوع الموارد - فهي موجودة لاستخدامها (ويفضل أن تكون بطريقة معقولة :>).
في بعض الأحيان يكون تقسيم الكود إلى أجزاء يعطي الكثير. عندما تشكل العمليات سلسلة هدفها تنفيذ إجراءات مسؤولة عن تعديل معين للبيانات - من السهل أن تضيع. لذلك، وبطريقة بسيطة، بدلًا من القيام بكل شيء في سلسلة واحدة، يمكنك تقسيم الأجزاء الفردية من الشيفرة المسؤولة عن شيء معين إلى عناصر فردية. هذا لن يجعل القصد من العمليات الفردية واضحًا فحسب، بل سيسمح لك أيضًا باختبار الأجزاء البرمجية المسؤولة عن شيء واحد فقط، ويمكن إعادة استخدامها بسهولة.
بعض الأمثلة العملية
أعتقد أن التمثيل الأكثر دقة لبعض العبارات أعلاه هو إظهار كيفية عملها من الناحية العملية - في هذه الفقرة، سأحاول أن أحدد بعض الممارسات السيئة في التعليمات البرمجية التي يمكن تحويلها بشكل أو بآخر إلى ممارسات جيدة. سأشير إلى ما يعيق سهولة قراءة الكود في بعض اللحظات وكيفية منعه.
لعنة متغيرات الحرف الواحد
هناك ممارسة رهيبة شائعة جدًا للأسف، حتى في الجامعات، وهي تسمية المتغيرات بحرف واحد. من الصعب عدم الموافقة على أنه في بعض الأحيان يكون حلاً مناسبًا تمامًا - نتجنب التفكير غير الضروري في كيفية تحديد الغرض من المتغير وبدلاً من استخدام عدة أحرف أو أكثر لتسميته، نستخدم حرفًا واحدًا فقط - على سبيل المثال، i، j، k.
ومن المفارقات أن بعض تعريفات هذه المتغيرات قد حظي بتعليق أطول من ذلك بكثير، وهو ما يحدد ما كان يدور في ذهن المؤلف.
من الأمثلة الجيدة هنا تمثيل التكرار على مصفوفة ثنائية الأبعاد تحتوي على القيم المتناظرة عند تقاطع العمود والصف.
const array = [[0, 1, 2], [3, 4, 5], [6, 7, 8]];
// سيء جدًا
ل (دع i = 0؛ i < [i] [i] ؛ i++) {
ل (دع j = 0؛ j < array[i][j]; j++) {
// هذا هو المحتوى، لكن في كل مرة يُستخدم فيها i و j يجب أن أعود وأحلل ما يُستخدمان فيه
}
}
// لا تزال سيئة ولكن مضحكة
دع i؛ // صف
دع ي؛ // عمود
بالنسبة إلى (i = 0؛ i < مصفوفة[i]؛ i++) {
بالنسبة إلى (j = 0؛ j < array[i][j]; j++) {
// هنا هو المحتوى، ولكن في كل مرة يُستخدم فيها i و j يجب أن أعود وأتحقق من التعليقات التي تُستخدم فيها
}
}
// أفضل بكثير
const rowCount = array.length;
لـ (دع مؤشر الصف = 0؛ مؤشر الصف < عدد الصفوف؛ مؤشر الصف ++) {
const row = array[rowIndex];
const columnCount = row.length;
بالنسبة إلى (دع مؤشر العمود = 0؛ مؤشر العمود <عدد الأعمدة؛ ++مؤشر العمود) { {
يشكل العمود = الصف[columnIndex];
// هل لدى أي شخص أي شكوك حول ما هو؟
}
}
الإفراط في التحسين المتسلل
في أحد الأيام الجميلة، صادفتُ رمزًا متطورًا للغاية كتبه مهندس برمجيات. وقد اكتشف هذا المهندس أن إرسال أذونات المستخدم كسلاسل تحدد إجراءات محددة يمكن تحسينها إلى حد كبير باستخدام بعض الحيل على مستوى البت.
من المحتمل أن يكون هذا الحل مقبولًا إذا كان الهدف هو Commodore 64، ولكن الغرض من هذا الرمز هو تطبيق ويب بسيط، مكتوب بلغة JS. حان الوقت للتغلب على هذه المشكلة: لنفترض أن المستخدم لديه أربعة خيارات فقط في النظام بأكمله لتعديل المحتوى: إنشاء، قراءة، تحديث، حذف. من الطبيعي جدًا أن نرسل هذه الأذونات في شكل JSON كمفاتيح لكائن له حالات أو مصفوفة.
ومع ذلك، لاحظ مهندسنا الذكي أن الرقم أربعة هو قيمة سحرية في العرض الثنائي وتوصل إلى ذلك على النحو التالي:
يحتوي جدول القدرات بأكمله على 16 صفًا، وقد أدرجت 4 صفوف فقط لإيصال فكرة إنشاء هذه الأذونات. قراءة الأذونات تسير على النحو التالي:
ما تراه أعلاه ليس كود WebAssembly. لا أريد أن يساء فهمي هنا - فمثل هذه التحسينات أمر طبيعي بالنسبة للأنظمة التي تحتاج فيها بعض الأشياء إلى وقت أو ذاكرة قليلة جدًا (أو كليهما). أما تطبيقات الويب، من ناحية أخرى، فهي بالتأكيد ليست المكان الذي تكون فيه مثل هذه التحسينات المفرطة منطقية تمامًا. لا أريد التعميم، لكن في عمل مطوري الواجهة الأمامية نادراً ما يتم تنفيذ عمليات أكثر تعقيداً تصل إلى مستوى تجريد البتات.
إنه ببساطة غير قابل للقراءة، ومن المؤكد أن المبرمج الذي يمكنه إجراء تحليل لمثل هذا الرمز سيتساءل عن المزايا الخفية التي يتمتع بها هذا الحل وما الذي يمكن أن يتلفه عندما فريق التطوير يريد إعادة كتابتها إلى حل أكثر منطقية.
ما هو أكثر من ذلك - أظن أن إرسال الأذونات ككائن عادي سيسمح للمبرمج بقراءة القصد في ثانية أو ثانيتين، بينما سيستغرق تحليل هذا الشيء بأكمله من البداية بضع دقائق على الأقل. سيكون هناك العديد من المبرمجين في المشروع، وسيتعين على كل واحد منهم أن يصادف هذا الجزء من التعليمات البرمجية - سيتعين عليهم تحليلها عدة مرات، لأنهم بعد مرور بعض الوقت سينسون ما هو السحر الذي يحدث هناك. هل يستحق الأمر حفظ تلك البايتات القليلة؟ في رأيي، لا.
فرّق تسد
تطوير الويب ينمو بسرعة ولا يوجد ما يشير إلى أن أي شيء سيتغير قريبًا في هذا الصدد. وعلينا أن نعترف بأن مسؤولية مطوري الواجهة الأمامية قد ازدادت في الآونة الأخيرة بشكل ملحوظ - فقد تولوا الجزء المنطقي المسؤول عن عرض البيانات في واجهة المستخدم.
في بعض الأحيان يكون هذا المنطق بسيطًا، وتكون الكائنات التي توفرها واجهة برمجة التطبيقات ذات بنية بسيطة وقابلة للقراءة. لكن في بعض الأحيان، تتطلب أنواعًا مختلفة من التعيين والفرز والعمليات الأخرى لتكييفها مع أماكن مختلفة على الصفحة. وهذا هو المكان الذي يمكن أن نقع فيه بسهولة في المستنقع.
في كثير من الأحيان، ضبطت نفسي في كثير من الأحيان على جعل البيانات في العمليات التي كنت أجريها غير قابلة للقراءة تقريبًا. على الرغم من الاستخدام الصحيح لطرق المصفوفات والتسمية الصحيحة للمتغيرات، إلا أن سلاسل العمليات في بعض النقاط كادت أن تفقد سياق ما أردت تحقيقه. أيضًا، كانت بعض هذه العمليات تحتاج أحيانًا إلى استخدامها في مكان آخر، وأحيانًا كانت عمليات عامة أو معقدة بما يكفي لتتطلب كتابة اختبارات.
أعرف، أعرف - هذا ليس جزءًا تافهًا من التعليمات البرمجية التي توضح بسهولة ما أريد نقله. وأعلم أيضًا أن التعقيدات الحسابية للمثالين مختلفة قليلاً، بينما في 99% من الحالات لا داعي للقلق بشأنها. الفرق بين الخوارزميات بسيط حيث أن كلاهما يعد خريطة للمواقع وأصحاب الأجهزة.
الأولى تعد هذه الخريطة مرتين، بينما تعدها الثانية مرة واحدة فقط. وأبسط مثال يوضح لنا أن الخوارزمية الثانية أكثر قابلية للنقل يكمن في أننا نحتاج إلى تغيير منطق إنشاء هذه الخريطة في الأولى على سبيل المثال، إجراء استبعاد مواقع معينة أو أشياء غريبة أخرى تسمى منطق العمل. في حالة الخوارزمية الثانية، نقوم بتعديل طريقة الحصول على الخريطة فقط، بينما تبقى بقية تعديلات البيانات التي تحدث في الأسطر اللاحقة دون تغيير. في حالة الخوارزمية الأولى، نحتاج إلى تعديل كل محاولة لإعداد الخريطة.
وهذا مجرد مثال - في الممارسة العملية، هناك الكثير من هذه الحالات عندما نحتاج إلى تحويل أو إعادة هيكلة نموذج بيانات معين حول التطبيق بأكمله.
أفضل طريقة لتجنب مواكبة تغيرات الأعمال المختلفة هي إعداد أدوات عالمية تسمح لنا باستخراج المعلومات ذات الأهمية بطريقة عامة إلى حد ما. حتى لو كان ذلك على حساب ما بين 2 إلى 3 أجزاء من الثانية التي قد نخسرها على حساب عدم التحسين.
الملخص
أن تكون مبرمجًا هي مهنة مثل أي مهنة أخرى - كل يوم نتعلم أشياء جديدة، وغالبًا ما نرتكب الكثير من الأخطاء. أهم شيء هو أن تتعلم من هذه الأخطاء، وأن تصبح أفضل في مهنتك ولا تكرر تلك الأخطاء في المستقبل. لا يمكنك أن تؤمن بأسطورة أن العمل الذي نقوم به سيكون دائمًا بلا أخطاء. ولكن يمكنك، استنادًا إلى تجارب الآخرين، أن تقلل من العيوب وفقًا لذلك.
آمل أن تساعدك قراءة هذا المقال على الأقل في تجنب بعض من ممارسات الترميز السيئة التي اختبرتها في عملي. في حالة وجود أي أسئلة تتعلق بأفضل ممارسات التعليمات البرمجية، يمكنك التواصل مع طاقم The Codest الخروج لاستشارة شكوكك.