تحليل ثابت للكميات الكبيرة من كود Python: تجربة Instagram. الجزء 2

ننشر اليوم الجزء الثاني من ترجمة المواد المخصصة للتحليل الثابت للكميات الكبيرة من كود Python من جانب الخادم في Instagram.



الجزء الأول

المبرمجين الذين تعبوا من الوبر


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

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


يعتبر Linter اختيارًا جيدًا جدًا ويمكن أن تضيع "الإشارة المفيدة" بسهولة في "الضجيج"

ماذا سنفعل مع هذا؟

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

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

تعديل قانون


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

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


نريد الابتعاد عن استخدام get_global واستخدام متغيرات المثيل بدلاً من ذلك

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

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

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


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

الآن وقد أصبحنا على دراية تامة بتعديلات الكود ، دعونا نلقي نظرة على مثال عملي. يعمل موظفو Instagram بجد لجعل قاعدة رموز المشروع مكتوبة بالكامل. Kodmody تساعدهم بجدية في هذا الشأن.

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


معرفة أنواع القيم التي يتم إرجاعها بواسطة الوظائف

ولكن ماذا لو لم تُرجع الدالة أي قيمة بشكل صريح ، أو None تُرجع None ضمنيًا؟ إذا لم تُرجع الدالة أي شيء بشكل صريح ، فيمكن تعيين النوع بلا.

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


وظائف التي لا تعيد شيئا

توسيع وحدات رمز مع Pyre


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

 $ pyre ƛ Found 2 type errors! testing/utils.py:7:0 Missing return annotation [3]: Returning `SomeClass` but no return type is specified. testing/utils.py:10:0 Missing return annotation [3]: Returning `testing.other.SomeOtherClass` but no return type is specified. 

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

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

 def get_description(page: WikiPage) -> Optional[str]:    if page.draft:        return None    return page.metadata["description"]  # <-    ? 

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

بالإضافة إلى ذلك ، لا يتحقق Pyre من التشغيل الصحيح لجسم الوظيفة إذا لم يتم شرح الوظيفة بالكامل. في المثال التالي ، ستفشل الدعوة إلى some_function . سيكون من الجيد معرفة ذلك قبل بدء تشغيل الكود.

 def some_function(in: int) -> bool:    return in > 0 def some_other_function():    if some_function("bla"): # <-             print("Yay!") 

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

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

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


الوظيفة هي طريقة الفصل

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

تحليل ثابت أكثر تقدما



لدى Python أربعة أنواع من النطاقات: النطاق العالمي ، نطاق المستوى ومستوى الوظيفة ، النطاق المتداخل

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

النتائج


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

أعزائي القراء! هل تستخدم تعديل الكود؟


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


All Articles