كان عام 2016. لم يتم انتخاب ترامب رئيسًا بعد ، لذا لم تبدأ حركة #DeleteUber بعد. ظل ترافيس كالانيك جنسًا ، كنا نمر بمرحلة من النمو المفرط مع افتتاح فروع في بلدان أخرى ، والمشاعر العامة إيجابية بشكل عام ، والجميع سعداء ، وأوبر في أفضل حالاتها.
لكن فرط النمو لم يخلو من المشاكل ، وبدأ التطبيق نفسه يتعطل. قبل ذلك ، تضاعف عدد المطورين كل عام تقريبًا ، وعندما تنمو بهذه السرعة ، تحصل على مجموعة مذهلة من المهارات. بالاقتران مع عقلية الهاكر التي أطلقنا عليها اسم "Let builder's build" ، كان هذا يعني بنية تطبيق معقدة وهشة. في ذلك الوقت ، كان تطبيق أوبر ذا منطق ثقيل للغاية ، لذلك غالبًا ما كان يتعطل. كنا نصدر باستمرار إصلاحات عاجلة ، وتصحيحات ، وإصدارات غير مخطط لها ، وما إلى ذلك. كما أن البنية لم تتسع بشكل جيد.
كنتيجة لكل هذه المشاكل ، بدأت حركة متنامية على جميع مستويات المنظمة احتشدت حول فكرة "إعادة كتابة التطبيق من الصفر". تم تشكيل فريق لإنشاء بنية محمولة جديدة للتطبيق الجديد. كانت الفكرة هي إنشاء بنية "من شأنها أن تدعم تطوير Uber للأجهزة المحمولة على مدار السنوات الخمس المقبلة". قمنا بتطوير كلا النظامين في وقت واحد. بدأت دورة التطوير بأكملها من جديد.
انتهز قسم iOS هذه الفرصة لتطبيق Swift (ثم في الإصدار 2.x). كانت Uber قد جربت Swift في الماضي ، ولكن مثل العديد من الآخرين في تلك المرحلة المبكرة من تطوير التكنولوجيا ، واجهت العديد من المشكلات وأخرت التنفيذ.
ومع ذلك ، كان الشعور العام هو أن معظم مشاكل Swift في ذلك الوقت كانت بسبب ضعف التشغيل البيني مع Objective-C. وإذا كتبنا تطبيق Swift خالصًا ، يمكننا تجنب المشاكل الرئيسية.
كانت هناك أيضًا فكرة لاستخدام نفس الأنماط المعمارية الأساسية على كل من Android و iOS. كان مطورو Android من أشد المعجبين بـ RxJava في ذلك الوقت. استفادت مكتبة RxSwift المقابلة من نموذج البرمجة الوظيفية في Swift. بدا كل شيء بسيطًا.
وهكذا ، قضى فريق تطوير صغير (التصميم والمنتج والهندسة المعمارية) عدة أشهر متهورًا في أنماط وظيفية / تفاعلية جديدة ، ولغة جديدة وتطبيق جديد. كان كل شيء يسير على ما يرام. اعتمدت الهندسة المعمارية بشكل كبير على القدرات اللغوية المتقدمة لـ Swift.
يمكن لواجهة المستخدم أن تتسع لعدد كبير من تطبيقات أوبر ، وبدا نموذج البرمجة الوظيفية قويًا (وإن كان صعب التعلم بعض الشيء) ، واستندت البنية إلى بروتوكول شبكة دفق جديد في الوقت الفعلي (كتبت هذا الجزء).
بعد شهرين والعديد من العروض التوضيحية المدهشة ، اكتسبت الحركة زخمًا. بدا المشروع ناجحًا. مع عدد قليل من المهندسين ، كان من الممكن تطوير وظائف ممتازة في وقت قصير. معظم المنتج جاهز. الدليل جميل.
ثم بدأ النشر في الشركة بأكملها. بدأت فرق مختلفة في إضافة ميزاتها الخاصة إلى التطبيق الجديد. في البداية ، أدت إثارة الجديد إلى موجة من التحفيز والإنتاجية. قدمت البنية لعزل الوظائف ، مما سمح بالتقدم السريع.
ولكن بمجرد أن أتقن أكثر من عشرة مهندسين Swift ، بدأت الآلية المنسقة جيدًا في الانهيار. لا يزال مترجم Swift أبطأ بكثير من Objective-C اليوم ، ولكنه كان غير قابل للاستخدام عمليًا. ذهب وقت التجميع خارج النطاق. التصحيح توقف تماما.
يوجد في مكان ما مقطع فيديو من أحد العروض التوضيحية ، حيث يكتب مهندس Uber بيانًا من سطر واحد في Xcode ، ثم ينتظر 45 ثانية حتى تظهر الرسائل ببطء ، واحدة تلو الأخرى ، في المحرر.
ثم اصطدمنا بالحائط باستخدام رابط ديناميكي. في ذلك الوقت ، كان يمكن ربط مكتبات Swift ديناميكيًا فقط. لسوء الحظ ، تم تشغيل الرابط في وقت متعدد الحدود ، لذلك كان الحد الأقصى الذي أوصت به Apple من المكتبات في ثنائي واحد هو 6. كان لدينا 92 وظل العدد في تزايد ...
نتيجة لذلك ، بعد النقر على أيقونة التطبيق ، استغرق الأمر من 8 إلى 12 ثانية قبل الاتصال بـ main. تبين أن تطبيقنا الجديد اللامع أبطأ من التطبيق القديم المحرج. ثم كانت هناك مشكلة حجم الثنائي.
لسوء الحظ ، عندما بدأت المشاكل تظهر بجدية ، كنا قد تجاوزنا بالفعل نقطة اللاعودة. هذه هي المغالطة المنطقية لمغالطة التكلفة الغارقة. في تلك المرحلة ، كانت الشركة بأكملها تضع كل طاقتها في التطبيق الجديد.
آلاف الأشخاص من اتجاهات مختلفة ، ملايين وملايين الدولارات (لا يمكنني إعطاء الرقم الحقيقي ، ولكن أكثر من واحد). كل الإدارة أجمعت على دعم المشروع. أجريت محادثة خاصة مع رئيسي حول ضرورة التوقف.
قال إنه إذا فشل هذا المشروع ، فسيتعين عليه حزم أمتعته. كان الشيء نفسه ينطبق على رئيسه حتى نائب الرئيس. لم يكن هناك مخرج.
لذلك قمنا بتجميع سواعدنا وحصلنا على أفضل المطورين لمعالجة كل مشكلة من المشكلات ، ووضعنا الأولوية للمشكلات الحرجة (الارتباط الديناميكي ، والحجم الثنائي). تم تعيين كل من الارتباط الديناميكي وحجم الملف الثنائي بهذا الترتيب.
اكتشفنا بسرعة أنه يمكن حل مشكلة الربط عند بدء تشغيل التطبيق عن طريق وضع كل التعليمات البرمجية في الملف التنفيذي الرئيسي. ولكن كما نعلم جميعًا ، يجمع Swift بين مساحات الأسماء والأطر ؛ لذلك ستكون هناك حاجة لتغييرات كبيرة في التعليمات البرمجية ، بما في ذلك عمليات التحقق من مساحة الاسم التي لا تعد ولا تحصى.
عندها قام ريتشارد هويل اللامع بفحص مخرجات بناء Xcode واكتشف أنه بعد اكتمال البناء ، يمكنه أخذ جميع ملفات الكائنات الوسيطة وإعادة ربطها مرة أخرى بالثنائي الرئيسي باستخدام برنامج نصي مخصص.
نظرًا لأن Swift يشوه مساحة اسم الكائنات أثناء التجميع ، فهذا يعني أنه يمكن أن يعمل عليها. سمح لنا ذلك بربط مكتباتنا بكفاءة ثابتة وتقليل وقت بدء التشغيل الرئيسي من 10 ثوانٍ إلى الصفر تقريبًا.
المشكلة التالية هي الحجم. في ذلك الوقت ، كشبكة أمان ، خططنا لتجميع التطبيق الجديد مع التطبيق القديم - ونشره بعناية في وقت التشغيل. لتقليل الحجم ، كان أول شيء فعلناه هو إلغاء تثبيت التطبيق القديم. أطلقنا على هذه الاستراتيجية اسم "Yolo". أعطى ترافيس الضوء الأخضر بنفسه.
لقد استبدلنا أيضًا جميع هياكل Swift بالفئات . تعطي أنواع القيم عمومًا قدرًا كبيرًا من النفقات نظرًا لمحاذاة الكائن ورمز الجهاز الإضافي المطلوب لنسخ السلوك والمُحركات التلقائية وما إلى ذلك. هذه المساحة المحفوظة.
لكن التطبيق استمر في النمو. قريبًا ، وصلنا إلى حد التنزيل (100 ميجابايت) للثنائيات في نظام التشغيل iOS 8 والإصدارات الأقدم. يُترجم هذا إلى عدد كبير من عمليات التثبيت المفقودة (10 ملايين دولار أمريكي في خسارة الإيرادات بسبب عدم تحديث العديد من مستخدمي iOS).
في هذه المرحلة ، كان هناك عدة أسابيع قبل الإطلاق العام. كان علينا إما العودة إلى Objective-C أو إسقاط دعم iOS 8. نظرًا لأن iOS 9 قدم القدرة على تقسيم البنية ، كان هذا الإصدار في الواقع نصف الحجم (عطاء أو أخذ). عندما لم يتبق سوى أسبوع واحد ، قررنا التخلص من عشرات الملايين من الدولارات - وإلغاء دعم نظام التشغيل iOS 8.
كان الرأي العام أنه عندما تم تخفيض الحجم إلى النصف ، كان لدينا مجال كبير للمناورة ، ويمكن حل مشكلة الحجم في وقت ما في المستقبل. عندما نجمع الباقي. لسوء الحظ ، كنا مخطئين للغاية.
بعد إطلاق التطبيق ، كان لدينا حفلة ضخمة. لاقى التطبيق استحسان المستخدمين والصحافة. كانت سريعة بتصميم جديد مشرق.
تمت ترقية الكثير من الأشخاص. كلنا تنفسنا الصعداء. بعد 90 أسبوعًا من العمل المتواصل ، حصل الرجال أخيرًا على استراحة.
لكن بعد ذلك بدأ الرأي العام يتغير. ركز التطبيق الجديد على حساب السعر الدقيق للرحلة لطريق معين (في الأيام الخوالي ، رأيت الأجرة والمضاعف الحالي). لحساب السعر ، كان عليك إدخال موقعك الحالي.
من أجل راحة المستخدمين ، قمنا أيضًا بتثبيت التحديد التلقائي للموقع ، مما يسمح بجمع بيانات الموقع في الخلفية حتى يتمكن السائق من معرفة مكان اصطحاب الراكب بالضبط في الوقت الحالي. بدأ الناس بالجنون. حثني بعض زملائي السابقين على Twitter على ترك الشركة الشريرة التي تتعقب أشخاصًا مثل هؤلاء.
نتيجة لهذا الاضطراب ، بدأ الناس في تعطيل إذن الموقع في iOS. لكن التطبيق الجديد لم ينص على حالة الاستخدام هذه.
لذلك بذلنا قصارى جهدنا لإعادة الإصدار القياسي. ناقشنا أنه من الممكن إيقاف تشغيل تتبع الموقع في الخلفية ، ولكن هذا مرة أخرى يدمر سهولة الاستخدام قبل ركوب سيارة أجرة.
ثم وصل ترامب إلى السلطة (حدث هذا بعد حوالي ثلاثة أشهر من إطلاق التطبيق الجديد) ، مما أدى إلى سلسلة من ردود الفعل التي أدت إلى حركة #DeleteUber .
طوال هذا الوقت ، نمت قاعدة بيانات Swift بسرعة. أدت المشاكل المستمرة و IDE البطيء إلى ظهور فصيلين متحاربين بين مطوري iOS لدينا. سأسميهم متعصبين سويفت ومهووسين بأهداف سي.
أدى مجموع الضغط الخارجي والداخلي إلى رفع التوتر إلى أقصى حد. نفى المتعصبون مشاكل سويفت. اشتكى المهووسون من كل شيء يمكن تخيله دون تقديم أي حلول خاصة.
في هذا الوقت تقريبًا ، واجهتنا مشكلة في حجم الملف الثنائي. كنت تحت الطلب عندما واجه الفريق مشاكل في الإصدار. اتضح أن حلنا الرائع لمشكلة الارتباط الديناميكي أنشأ ملفًا تنفيذيًا كان كبيرًا جدًا بالنسبة لبعض البنى.
بعد حل المشكلة في هذه البنى ، أنا وزميلي aqua_geekقام ببعض البحث ووجد أن حجم الشفرة المترجمة ينمو بمعدل 1.3 ميغابايت في الأسبوع. لقد رفعت ناقوس الخطر. إذا لم يتم فعل أي شيء ، بهذه السرعة ، فسنواجه حد التنزيل عبر الشبكة الخلوية في غضون ثلاثة أسابيع.
لكن التوتر الداخلي وصل لدرجة أن المتعصبين نفوا كل شيء. كتب أحد قادة التكنولوجيا من معسكر Swift مقالة من صفحتين حول عدم أهمية حدود التنزيل الخلوي (Facebook ، بعد كل شيء ، تجاوزها منذ فترة طويلة). لقد سئمنا أنفسنا من إطفاء الحرائق.
لذلك ، طور أحد علماء البيانات لدينا اختبارًا عن طريق التحويل المصطنع لإحدى الطبقات المعمارية خارج الحد - وقياس التأثير على أداء الأعمال. في الأسبوع التالي ، سحبنا تلك الطبقة للخارج ودفعنا طبقة أخرى خارج الحد الأقصى (للتحكم في البنى).
كان التأثير كارثيًا. تبين أن التأثير السلبي على الأعمال التجارية كان أكبر بعدة أوامر من حجم جميع تكاليف تنفيذ Swift السنوي. اتضح أن الكثير من الأشخاص خارج نطاق WiFi عندما يقومون بتنزيل تطبيق Uber لأول مرة (من كان يظن؟)
لذلك شكلنا مجموعة ضاربة أخرى. بدأنا في تفكيك ملفات الكائن وفحص سطر بسطر لتحديد سبب نمو كود Swift بشكل كبير. إزالة الوظائف غير المستخدمة. كان على Tyler إعادة كتابة تطبيق watchOS مرة أخرى إلى الكائن.
(كان طول تطبيق الساعة 4400 سطر فقط ، ولكن نظرًا لبنية المعالج المختلفة وعدم توافق ABI ، يجب تضمين وقت تشغيل Swift بالكامل في التطبيق.)
كنا في حدودنا. تعبان جدا. لكنهم اجتمعوا. في ذلك الوقت أظهر المهندسون اللامعون أنفسهم. اكتشف أحد المطورين في أمستردام كيفية إعادة ترتيب ممرات تحسين المترجم. بالنسبة لأولئك الذين ليسوا خبراء في المترجمات ، سأشرح.
المترجمون الحديثون يصنعون الكثير من التمريرات. على سبيل المثال ، يمكن للمرء أن يتضمن وظائف. آخر هو استبدال التعبيرات الثابتة بقيمها. اعتمادًا على ترتيب التنفيذ ، قد يكون رمز الجهاز أصغر أو أكبر.
إذا قامت الوظائف المضمنة بتمرير قيمة ، يمكن للمترجم أن يتعرف عليها ويستبدل الكتلة بأكملها. فمثلا:
int x = 3 func(x) { X + 4 }
تصبح مجرد قيمة ثابتة قدرها 7 إذا مر المترجم عبر الوظائف المضمنة أولاً (مما يعني رمزًا أقل بكثير).
إذا كان تمرير المترجم هذا هو الثاني ، فقد لا يتعرف على هذه الوظائف ، وستحصل على المزيد من التعليمات البرمجية. كل هذا ، بالطبع ، يعتمد كليًا على شكل الكود المحدد ، لذلك من الصعب تحسين ترتيب التمريرات بشكل عام.
هكذا قال المهندس العبقري من أمستردام ، الذي بنى الخوارزمية في بنية الإصدار لإعادة ترتيب تصاريح التحسين وتقليل الحجم. لقد أدى ذلك إلى تقليل حجم رمز الجهاز الإجمالي بمقدار 11 ميجا بايت ومنحنا القليل من الوقت لمواصلة التطوير.
لكن هذا النهج أرعب متخصصي مترجم Swift ، فقد كانوا خائفين من أن تمريرات المترجم التي لم يتم التحقق منها ستكشف أخطاء لم يتم اختبارها (على الرغم من أن كل مسار يجب أن يكون آمنًا في جوهره ، إلا أنه من الصعب التفكير في مجموعات محتملة من التمريرات) ومع ذلك ، لم نواجه أي مشاكل كبيرة.
طبقنا أيضًا مجموعة من الحلول الأخرى (الفحص لقوالب الأكواد باهظة الثمن بشكل خاص). قمنا بقياس كل منهم في عدد أسابيع التطوير التي يقدمونها لنا. لكن المشكلة الحقيقية كانت منحنى النمو. في النهاية ، تم دائمًا التهام جميع المكاسب.
نتيجة لذلك ، حصلنا على وقت كافٍ لانتظار تحرك Apple ، مما رفع حد التنزيل عبر الاتصال الخلوي إلى 150 ميجابايت. أضافوا أيضًا عددًا من وظائف المترجم للمساعدة في تحسين الحجم (-Osize). من خلال قبولهم الخاص ، لن تكون Swift صغيرة بعد التجميع مثل Objective-C.
ولكن اعتبارًا من هذا العام ، قمنا بتحسين Swift إلى 1.5 ضعف حجم كود آلة Objective-C ، وفي النهاية رفعت Apple الحد الاختياري إلى 200 ميغابايت مرة أخرى. هذا يكفي لإبقائنا مستمرين لبضع سنوات أخرى.
لكننا كادنا نفشل. إذا لم تقم Apple بزيادة الحد ، فسيتعين إعادة كتابة تطبيق Uber مرة أخرى إلى ObjC. في النهاية ، تمكنا من حل مشاكل أخرى أيضًا. لامعة alanzeinoحرص وفريقه على تضمين دعم Swift في أداة Buck build ، مما أدى إلى تقليل أوقات الإنشاء بشكل كبير.
لقد فقدنا مجموعة من الأشخاص المحترقين على طول الطريق. أنفق الكثير من المال وتعلم الدروس الصعبة. والمثير للدهشة ، حتى يومنا هذا ، أن معظمهم يصرون على أن إعادة الكتابة كانت تستحق العناء. الاتساق المعماري شائع بين المهندسين الجدد الذين يأتون إلى الشركة. إنهم لا يعرفون حتى مقدار الألم الذي استغرقه تحقيق ذلك.
لقد استفاد المجتمع من معرفتنا. قدم @ ellsk1 عرضًا تقديميًا رائعًا وذهب في جولة محاضرة لمشاركة معرفته. لقد تمكنت أيضًا من الاستفادة من هذه التجربة لمساعدة الشركات الجديدة وفرق التطوير على اتخاذ قرارات أفضل.
إذن هذه نصيحة. كل شيء في البرمجة يدور حول التسوية. لا توجد لغة أفضل على مستوى العالم. مهما فعلت ، افهم ما هو الحل الوسط ولماذا تقوم به. تجنب الحرب السياسية بين الفصائل العنيدة داخل الشركة.
جاهد في نقاط الفشل. اكتشف كيفية تحديد المفاضلات وترك التراجع إذا وصلت إلى نقطة ما وأدركت أنك ارتكبت خطأ. يأتي بذل الكثير من الجهد مقابل تكلفة ، ولكن كلما أدركت لاحقًا التسوية الخاطئة ، ارتفعت التكلفة.
لا تكن مملًا يتذمر فقط ولا يساهم. لا تكن متعصبًا يسبب مشاكل كبيرة للجميع. لا يقع أفضل المهندسين في أي من هذه الفخاخ.