قم بتنفيذ تحليل ثابت في العملية، بدلاً من استخدامه للعثور على الأخطاء

لقد دُفعت لكتابة هذا المقال بسبب الكم الهائل من المواد المتعلقة بالتحليل الثابت والتي تلفت انتباهي بشكل متزايد. أولا، هذا مدونة PVS-studio، التي تروج لنفسها بشكل نشط على حبري بمساعدة مراجعات الأخطاء التي وجدتها أداتها في المشاريع مفتوحة المصدر. تم تنفيذ استوديو PVS مؤخرًا دعم جافاوبالطبع مطورو IntelliJ IDEA، الذي ربما يكون محلله المدمج هو الأكثر تقدمًا لـ Java اليوم، لا يمكن البقاء بعيدا.

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

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

قم بتنفيذ تحليل ثابت في العملية، بدلاً من استخدامه للعثور على الأخطاء
السقاطة (المصدر: ويكيبيديا).

ما لا يمكن للمحللين الثابتين فعله أبدًا

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

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

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

التحليل الثابت لا يتعلق بالعثور على الأخطاء

مما سبق نستنتج أن التحليل الثابت ليس وسيلة لتقليل عدد العيوب في البرنامج. أجرؤ على القول: عند تطبيقه على مشروعك لأول مرة، سيجد أماكن "مثيرة للاهتمام" في الكود، ولكن على الأرجح لن يجد أي عيوب تؤثر على جودة برنامجك.

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

هل هذا يعني أنه لا ينبغي استخدام التحليل الثابت؟ بالطبع لا! وللسبب نفسه تمامًا، من المفيد التحقق من كل كلمة مرور جديدة للتأكد من إدراجها في قائمة التوقف لكلمات المرور "البسيطة".

التحليل الثابت هو أكثر من مجرد العثور على الأخطاء

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

  • التحقق من أسلوب الترميز بالمعنى الأوسع للكلمة. يتضمن ذلك التحقق من التنسيق، والبحث عن استخدام الأقواس الفارغة/الإضافية، ووضع حدود للمقاييس مثل عدد الأسطر/التعقيد الدوري لطريقة ما، وما إلى ذلك - أي شيء من المحتمل أن يعيق إمكانية قراءة التعليمات البرمجية وصيانتها. في Java، هذه الأداة هي Checkstyle، في Python - Flake8. عادةً ما تسمى برامج هذه الفئة بـ "linters".
  • لا يمكن تحليل التعليمات البرمجية القابلة للتنفيذ فقط. يمكن (ويجب!) فحص ملفات الموارد مثل JSON وYAML وXML و.properties تلقائيًا للتأكد من صلاحيتها. بعد كل شيء، من الأفضل معرفة أن بنية JSON معطلة بسبب بعض علامات الاقتباس غير المقترنة في مرحلة مبكرة من التحقق التلقائي من طلب السحب مقارنةً بتنفيذ الاختبار أو وقت التشغيل؟ الأدوات المناسبة متاحة: على سبيل المثال. ياملينت, JSONLint.
  • يعد التجميع (أو التحليل للغات البرمجة الديناميكية) أيضًا نوعًا من التحليل الثابت. بشكل عام، المترجمون قادرون على إنتاج تحذيرات تشير إلى وجود مشاكل في جودة كود المصدر ويجب عدم تجاهلها.
  • في بعض الأحيان يكون التجميع أكثر من مجرد تجميع تعليمات برمجية قابلة للتنفيذ. على سبيل المثال، إذا كان لديك وثائق بالتنسيق AsciiDoctor، ثم في لحظة تحويله إلى HTML/PDF معالج AsciiDoctor (البرنامج المساعد مخضرم) يمكنه إصدار تحذيرات، على سبيل المثال، بشأن الروابط الداخلية المعطلة. وهذا سبب وجيه لعدم قبول طلب السحب مع تغييرات الوثائق.
  • يعد التدقيق الإملائي أيضًا نوعًا من التحليل الثابت. جدوى تعويذة قادر على التدقيق الإملائي ليس فقط في الوثائق، ولكن أيضًا في أكواد مصدر البرنامج (التعليقات والنصوص الحرفية) في لغات البرمجة المختلفة، بما في ذلك C/C++، وJava، وPython. الخطأ الإملائي في واجهة المستخدم أو الوثائق يعد أيضًا عيبًا!
  • اختبارات التكوين (حول ماهيتها - انظر. هذا и هذا التقارير)، على الرغم من تنفيذها في وقت تشغيل اختبار الوحدة مثل pytest، إلا أنها في الواقع أيضًا نوع من التحليل الثابت، نظرًا لأنها لا تنفذ أكواد المصدر أثناء تنفيذها.

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

أي من هذه الأنواع من التحليل الثابت يجب أن تستخدمه في مشروعك؟ وبطبيعة الحال، كلما كان ذلك أفضل! الشيء الرئيسي هو تنفيذه بشكل صحيح، والذي سيتم مناقشته أكثر.

خط أنابيب التسليم كمرشح متعدد المراحل والتحليل الثابت كمرحلة أولى

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

  1. تحليل ثابت
  2. التحويل البرمجي
  3. اختبارات الوحدة
  4. اختبارات التكامل
  5. اختبارات واجهة المستخدم
  6. فحص يدوي

لا يتم نقل التغييرات المرفوضة في المرحلة N من خط الأنابيب إلى المرحلة N+1.

لماذا بالضبط بهذه الطريقة وليس غير ذلك؟ في جزء الاختبار من المسار، سيتعرف القائمون على الاختبار على هرم الاختبار المعروف.

قم بتنفيذ تحليل ثابت في العملية، بدلاً من استخدامه للعثور على الأخطاء
الهرم التجريبي. مصدر: مقالة مارتن فاولر.

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

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

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

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

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

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

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

التنفيذ في مشروع تراث

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

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

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

الطرق التالية لإدخال بوابات الجودة معروفة:

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

اسئلة

يعمل مثل هذا:

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

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

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

قم بتنفيذ تحليل ثابت في العملية، بدلاً من استخدامه للعثور على الأخطاء

أستخدم نسخة معدلة من هذه الطريقة، مع حساب التحذيرات بشكل منفصل بواسطة وحدة المشروع وأداة التحليل، مما يؤدي إلى ملف YAML مع بيانات تعريف البناء التي تبدو كما يلي:

celesta-sql:
  checkstyle: 434
  spotbugs: 45
celesta-core:
  checkstyle: 206
  spotbugs: 13
celesta-maven-plugin:
  checkstyle: 19
  spotbugs: 0
celesta-unit:
  checkstyle: 0
  spotbugs: 0

في أي نظام CI متقدم، يمكن تنفيذ السقاطة لأي أدوات تحليل ثابتة دون الاعتماد على المكونات الإضافية وأدوات الطرف الثالث. يقوم كل محلل بإنتاج تقريره الخاص بنص بسيط أو بتنسيق XML يسهل تحليله. كل ما تبقى هو كتابة المنطق الضروري في البرنامج النصي CI. يمكنك أن ترى كيف يتم تنفيذ ذلك في مشاريعنا مفتوحة المصدر المستندة إلى Jenkins وArtifactory هنا أو هنا. كلا المثالين يعتمدان على المكتبة راتشيتليب: طريقة countWarnings() يحسب علامات XML في الملفات التي تم إنشاؤها بواسطة Checkstyle وSpotbugs بالطريقة المعتادة، و compareWarningMaps() ينفذ نفس السقاطة، مما يؤدي إلى حدوث خطأ عند زيادة عدد التحذيرات في أي من الفئات.

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

حول أهمية إصلاح نسخة المحلل

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

النتائج

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

مراجع

  1. التسليم المستمر
  2. أ. كودريافتسيف: تحليل البرنامج: كيف تفهم أنك مبرمج جيد تقرير عن الطرق المختلفة لتحليل الكود (ليس فقط ثابتًا!)

المصدر: www.habr.com

إضافة تعليق