المسار إلى فحص 4 ملايين سطر من كود Python. الجزء 1

نلفت انتباهك اليوم إلى الجزء الأول من ترجمة المادة حول كيفية تعامل Dropbox مع التحكم في نوع كود Python.

المسار إلى فحص 4 ملايين سطر من كود Python. الجزء 1

يكتب Dropbox كثيرًا في Python. إنها لغة نستخدمها على نطاق واسع للغاية ، لكل من الخدمات الخلفية وتطبيقات عميل سطح المكتب. نستخدم أيضًا Go و TypeScript و Rust كثيرًا ، لكن Python هي لغتنا الرئيسية. بالنظر إلى حجمنا ، ونحن نتحدث عن ملايين الأسطر من كود Python ، اتضح أن الكتابة الديناميكية لمثل هذه الشفرة تعقد فهمها دون داعٍ وبدأت تؤثر بشكل خطير على إنتاجية العمل. للتخفيف من هذه المشكلة ، بدأنا في تحويل الكود الخاص بنا تدريجيًا إلى التحقق من النوع الثابت باستخدام mypy. من المحتمل أن يكون هذا هو نظام التحقق من النوع المستقل الأكثر شيوعًا في Python. Mypy هو مشروع مفتوح المصدر ، يعمل مطوروه الرئيسيون في Dropbox.

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

اقرأ الجزء الثاني

لماذا يعد فحص النوع ضروريًا؟

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

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

  • يمكن أن تعود هذه الوظيفة None?
  • ماذا يجب أن تكون هذه الحجة؟ items?
  • ما هو نوع السمة id: int فعلا، str، أو ربما نوع مخصص؟
  • هل يجب أن تكون هذه الحجة قائمة؟ هل من الممكن تمرير tuple إليه؟

إذا نظرت إلى مقتطف الشفرة التالي ذي النوع التوضيحي وحاولت الإجابة عن أسئلة مشابهة ، فقد تبين أن هذه هي أبسط مهمة:

class Resource:
    id: bytes
    ...
    def read_metadata(self, 
                      items: Sequence[str]) -> Dict[str, MetadataItem]:
        ...

  • read_metadata لا يعود None، لأن نوع الإرجاع ليس كذلك Optional[…].
  • حجة items هو سلسلة من الخطوط. لا يمكن تكراره بشكل عشوائي.
  • ينسب id سلسلة من البايت.

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

بينما تتفوق Python في المراحل المبكرة أو المتوسطة من المشاريع ، قد تواجه المشاريع والشركات الناجحة التي تستخدم Python في مرحلة ما السؤال الحيوي: "هل يجب إعادة كتابة كل شيء بلغة مكتوبة بشكل ثابت؟"

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

استخدام مثل هذه الأنظمة له مزايا أخرى ، وهي بالفعل غير تافهة تمامًا:

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

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

خلفية mypy

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

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

def Fib(n as Int) as Int
  if n <= 1
    return n
  else
    return Fib(n - 1) + Fib(n - 2)
  end
end

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

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

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

بدا وكأنه مزيج من Java و Python:

int fib(int n):
    if n <= 1:
        return n
    else:
        return fib(n - 1) + fib(n - 2)

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

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

def fib(n: int) -> int:
    if n <= 1:
        return n
    else:
        return fib(n - 1) + fib(n - 2)

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

products = []  # type: List[str]  # Eww

أصبحت تعليقات الكتابة مفيدة أيضًا لدعم Python 2 ، والتي لا تحتوي على دعم مضمن للتعليقات التوضيحية من النوع:

f fib(n):
    # type: (int) -> int
    if n <= 1:
        return n
    else:
        return fib(n - 1) + fib(n - 2)

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

أقنعني Guido أيضًا بالانضمام إلى Dropbox بعد أن أكملت أطروحة التخرج. هذا هو المكان الذي يبدأ فيه الجزء الأكثر إثارة للاهتمام من قصة mypy.

يتبع ...

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

المسار إلى فحص 4 ملايين سطر من كود Python. الجزء 1
المسار إلى فحص 4 ملايين سطر من كود Python. الجزء 1

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

إضافة تعليق