متى تكون الشفرة رائعة؟



غالبًا ما يسبب موضوع الشفرة المثالية جدلاً بين المبرمجين المخضرمين. كان من المثير للاهتمام الحصول على رأي مدير تطوير Parallels RAS Igor Marnat. بموجب الخفض ، رأي المؤلف في الموضوع المعلن. استمتع!



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

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

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

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

مثال 90s


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

وجد مؤلفو الخوارزمية حلاً أنيقًا ، يأخذ وصفه حرفياً عدة صفحات . لقد مرت سنوات عديدة ، لكنني ما زلت معجبًا بجمال وروعة النهج الذي اقترحه مطورو الخوارزمية.

لا يزال هذا المثال لا يشير إلى الشفرة على هذا النحو ، بل يشير إلى الخوارزمية ، لذلك لن نتناولها بمزيد من التفصيل.

لينكس هو رأس كل شيء!


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

على سبيل المثال ، ضع في اعتبارك ملف memory.c من Linux kernel ، الذي ينتمي إلى النظام الفرعي لإدارة الذاكرة.

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

static void tlb_remove_table_one(void *table) {     /*      * This isn't an RCU grace period and hence the page-tables cannot be      * assumed to be actually RCU-freed.      *      * It is however sufficient for software page-table walkers that rely on      * IRQ disabling. See the comment near struct mmu_table_batch.      */     smp_call_function(tlb_remove_table_smp_sync, NULL, 1);     __tlb_remove_table(table); } 


2. لا يوجد الكثير من التعليقات في الشفرة ، ولكن التعليقات التي تكون مفيدة عادةً. إنهم ، كقاعدة عامة ، لا يصفون إجراءً واضحًا بالفعل من الشفرة (مثال كلاسيكي للتعليق عديم الفائدة هو "cnt ++؛ // increment counter") ، ولكن سياق هذا الإجراء - لماذا يتم هنا ما تم فعله ، لماذا تم القيام بذلك ، لماذا هنا ، مع الافتراضات التي يتم استخدامها ، وما هي الأماكن الأخرى في الكود التي يتصل بها. على سبيل المثال:

 /** * tlb_gather_mmu - initialize an mmu_gather structure for page-table tear-down * @tlb: the mmu_gather structure to initialize * @mm: the mm_struct of the target address space * @start: start of the region that will be removed from the page-table * @end: end of the region that will be removed from the page-table * * Called to initialize an (on-stack) mmu_gather structure for page-table * tear-down from @mm. The @start and @end are set to 0 and -1 * respectively when @mm is without users and we're going to destroy * the full address space (exit/execve). */ void tlb_gather_mmu(struct mmu_gather *tlb, struct mm_struct *mm,            unsigned long start, unsigned long end) 


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

 /* * demand-loading started 01.12.91 - seems it is high on the list of * things wanted, and it should be easy to implement. - Linus */ /* * Ok, demand-loading was easy, shared pages a little bit tricker. Shared * pages started 02.12.91, seems to work. - Linus. * * Tested sharing by executing about 30 /bin/sh: under the old kernel it * would have taken more than the 6M I have free, but it worked well as * far as I could see. * * Also corrected some "invalidate()"s - I wasn't doing enough of them. */ 


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

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

 void unmap_page_range(struct mmu_gather *tlb, ….     unsigned long next;    BUG_ON(addr >= end);    tlb_start_vma(tlb, vma); int apply_to_page_range(struct mm_struct *mm, unsigned long addr, …    unsigned long end = addr + size;    int err;    if (WARN_ON(addr >= end))        return -EINVAL; 


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

4. جميع المكونات الرئيسية للنواة تزود المستخدمين بمعلومات عن حالتهم من خلال واجهة بسيطة ، نظام الملفات الظاهري / proc /.

على سبيل المثال ، تتوفر معلومات حالة الذاكرة في ملف / proc / meminfo

 user@parallels-vm:/home/user$ cat /proc/meminfo MemTotal:    2041480 kB MemFree:      65508 kB MemAvailable:   187600 kB Buffers:      14040 kB Cached:      246260 kB SwapCached:    19688 kB Active:     1348656 kB Inactive:     477244 kB Active(anon):  1201124 kB Inactive(anon):  387600 kB Active(file):   147532 kB Inactive(file):  89644 kB …. 


يتم جمع المعلومات الواردة أعلاه ومعالجتها في عدة ملفات مصدر للنظام الفرعي لإدارة الذاكرة. لذا ، فإن حقل MemTotal الأول هو قيمة حقل Totalram لبنية sysinfo ، والتي تمتلئ بوظيفة si_meminfo لملف page_alloc.c .

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

يجب أن يبدأ تطوير أي نظام تقريبًا بنظام لجمع وتوفير معلومات حول الحالة الداخلية للشفرة والبيانات الخاصة بك. هذا سوف يساعد بشكل كبير في عملية التطوير والاختبار ، وبعد ذلك في العملية.

كما قال لينوس ، "يقلق المبرمجون السيئون بشأن الشفرة. المبرمجون الجيدون قلقون بشأن تراكيب البيانات وعلاقاتهم ".

5. تتم قراءة كافة التعليمات البرمجية ومناقشتها من قبل العديد من المطورين قبل الالتزام. يتم تسجيل محفوظات تغييرات التعليمات البرمجية المصدر ومتاحة. يمكن تتبع التغييرات على أي خط إلى حدوثه - ما الذي تغير ، من قبل من ، متى ، لماذا ، ما هي القضايا التي تمت مناقشتها من قبل المطورين. على سبيل المثال ، التغيير في https://github.com/torvalds/linux/commit/1b2de5d039c883c9d44ae5b2b6eca4ff9bd82dac#diff-983ac52fa16631c1e1dfa28fc593d2ef في ذاكرة الكود مستوحى من https://bgzb_b.b. تم إجراء تحسين صغير للكود (لا تحدث مكالمة لتمكين الحماية ضد الكتابة في الذاكرة إذا كانت الذاكرة محمية بالفعل ضد الكتابة).

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

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

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

ما أود أن أقوله في الختام. المطورين الأساسيين أذكياء وذوي خبرة ، وقد نجحوا. ثبت بمليارات أجهزة لينكس

كن مطور kernel ، استخدم أفضل الممارسات واقرأ Code Complete!

Z.Y. بالمناسبة ، ما هي معايير الرمز المثالي لديك شخصيًا؟ شارك افكارك في التعليقات

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


All Articles