ستاندوب واحد في Yandex.Taxi ، أو ما تحتاجه لتعليم مطور الواجهة الخلفية

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

قد تتغير أسماء الموظفين ، لكن المهام حقيقية تمامًا!

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

إيفان:

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

بابتسامة آنا المقنعة ، أصبح من الواضح الذي يصححه إيفان.



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

تعليقات مضافة للمستقبل ، بحيث يمكن لأي قارئ معرفة الخوارزمية بسرعة:

for exception in self.exceptions[banknote]: exc_value = value + exception.delta if exc_value - cost >= banknote: continue if exc_value > cost >= exception.banknote: banknote_results.append(exc_value) #       for exception in self.exceptions[banknote]: #          #    exc_value = value + exception.delta #           # (corner case) if exc_value - cost >= banknote: continue if exc_value > cost >= exception.banknote: banknote_results.append(exc_value) 

حسنًا ، بالطبع ، قضيت بقية الوقت في تغطية الكود بالكامل مع الاختبارات.

 RUB = [1, 2, 5, 10, 50, 100, 200, 500, 1000, 2000, 5000] CUSTOM_BANKNOTES = [1, 3, 7, 11] @pytest.mark.parametrize( 'cost, banknotes, expected_changes', [ # no banknotes ( 321, [], [], ), # zero cost ( 0, RUB, [], ), # negative cost ( -13, RUB, [], ), # simple testcase ( 264, RUB, [265, 270, 300, 400, 500, 1000, 2000, 5000], ), # cost bigger than max banknote ( 6120, RUB, [6121, 6150, 6200, 6300, 6500, 7000, 8000, 10000], ), # min cost ( 1, RUB, [2, 5, 10, 50, 100, 200, 500, 1000, 2000, 5000], ), ... ], ) 

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

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

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

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

لكن العودة إلى الموقف. بعد إيفان ، تتحدث آنا.

آنا:

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



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

 SELECT * FROM table_1 JOIN table_2 ON table_1.some_id = table_2.some_id WHERE (table_2.attr1 = 'val' OR table_1.attr2 IN ('val1', 'val2')) AND table_1.deleted_at IS NULL AND table_2.deleted_at IS NULL ORDER BY table_2.created_at 

يوضح الاستعلام الذي يوضح أنه لا يستخدم أحد المؤشرات لسمات attr1 table_2 و attr2 of table_1.
فاديم:
تواجه سلوكًا مشابهًا في MySQL ، المشكلة بالضبط في حالة OR ، بسبب استخدام فهرس واحد فقط ، يقول attr2. والشرط الثاني يستخدم المسح seq - تمريرة كاملة من خلال الجدول. يمكن تقسيم الطلب إلى طلبين مستقلين. كخيار ، قم بتقسيم وتجميد نتيجة الاستعلام على الجانب الخلفي. لكن عليك التفكير في التفاف هذين الطلبين في معاملة ما ، أو دمجها باستخدام UNION - في الواقع ، في الجانب الأساسي:

 SELECT * FROM table_1 JOIN table_2 ON table_1.some_id = table_2.some_id WHERE (table_2.attr1 = 'val') AND table_1.deleted_at IS NULL AND table_2.deleted_at IS NULL ORDER BY table_2.created_at SELECT * FROM table_1 JOIN table_2 ON table_1.some_id = table_2.some_id WHERE (table_1.attr2 IN ('val1' , 'val2')) AND table_1.deleted_at IS NULL AND table_2.deleted_at IS NULL ORDER BY table_2.created_at 
أنيا:
شكرا ، سأحاول ^ _ ^

لتلخيص مرة أخرى:

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

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

آخر من تحدث هو فاديم.

فاديم:

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

من خلال الصمت الحزين لجميع الحاضرين ، فمن الواضح - الجميع بالفعل واجهوا المشكلة بطريقة أو بأخرى .

للحصول على جميع السجلات كجزء من الطلب ، يتم استخدام request_id ، والذي يتم طرحه في جميع السجلات في النموذج التالي:

 #   request_id logger.info( 'my log msg', ) #   request_id logger.info( 'my log msg', extra=log_extra, #   request_id —     ) 

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

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

أولاً ، كتبت غلافًا حول التنفيذ القياسي للطلب:

 async def handle(self, request, handler): log_extra = request['log_extra'] log_extra_manager.set_log_extra(log_extra) return await handler(request) 

كان من الضروري تحديد كيفية تخزين log_extra في طلب واحد. كان هناك خياران. الأول هو تغيير task_factory لـ eventloop من asyncio:

 class LogExtraManager: __init__(self, context: Any, settings: typing.Optional[Dict[str, dict]], activations_parameters: list) -> None: loop = asyncio.get_event_loop() task_factory = loop.get_task_factory() if task_factory is None: task_factory = _default_task_factory @functools.wraps(task_factory) def log_extrad_factory(ev_loop, coro): child_task = task_factory(ev_loop, coro) parent_task = asyncio.Task.current_task(loop=ev_loop) log_extra = getattr(parent_task, LOG_EXTRA_CONTEXT_KEY, None) setattr(child_task, LOG_EXTRA_CONTEXT_KEY, log_extra) return child_task # updating loop, so any created task will # get the log_extra of its parent loop.set_task_factory(log_extrad_factory) def set_log_extra(log_extra: dict): loop = asyncio.get_event_loop() task = asyncio.Task.current_task(loop=loop) setattr(task, LOG_EXTRA_CONTEXT_KEY, log_extra) 

الخيار الثاني هو "دفع" الانتقال إلى Python 3.7 من خلال أمر البنية التحتية لاستخدام سياقات السياق :

 log_extra_var = contextvars.ContextVar(LOG_EXTRA_CONTEXT_KEY) class LogExtraManager: def set_log_extra(log_extra: dict): log_extra_var.set(log_extra) 

حسنا وكذلك كان من الضروري إعادة توجيه المخزنة في سياق log_extra في المسجل.

 class LogExtraFactory(logging.LogRecord): # this class allows to create log rows with log_extra in the record def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) task = asyncio.Task.current_task() log_extra = getattr(task, LOG_EXTRA_CONTEXT_KEY, None) if not log_extra: return for key in log_extra: self.__dict__[key] = log_extra[key] logging.setLogRecordFactory(LogExtraFactory) 

النتائج:

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

أكل معلمو المدرسة الخلفية أكثر من رطل من الملح وملأوا الكثير من المخاريط في العملية غير المتزامنة للخدمات. سوف يخبرون الطلاب حول ميزات عملية بيثون غير المتزامنة - سواء على المستوى العملي أو في تحليل الحزم الداخلية.

الكتب والروابط


تعلم بيثون يمكن أن يساعدك:

  • ثلاثة كتب: Python Cookbook ، و Diving in Python 3 ، و Python Tricks .
  • محاضرات بالفيديو لأركان صناعة تكنولوجيا المعلومات مثل ريموند هيتنجر وديفيد بيسلي. من محاضرات الفيديو في أول واحد ، يمكن تمييز تقرير "ما بعد PEP 8 - أفضل الممارسات للرمز الواضح الجميل". ينصح Beasley بمشاهدة عرض حول التزامن.

للحصول على فهم أعلى مستوى للهندسة المعمارية ، اقرأ الكتب:

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

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

انتهى الموقف ، ذهب الجميع إلى العمل.

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


All Articles