مهندسی نرم‌افزار در گوگل، مروری بر فصل اول: مهندسی نرم‌افزار چیست؟

مقدمهسه تفاوت اصلی برنامه‌نویسی و مهندسی نرم‌افزارزمان: مهندس نرم‌افزار نگران گذر زمان و نیاز به تغییر استمقیاس و کارایی (scale and efficiency) برای هم نرم‌افزار و هم سازمان تولید کنندهموازنه در اجرا (tradeoff at play) مهندس نرم‌افزار باید تصمیمات پیچیده‌تر با ریسک بیشتر و بر اساس تخمین‌های غیر دقیق‌تر بگیردزماندر گوگل: مهندسی نرم‌افزار تجمیع برنامه‌نویسی در گذر زمان استبرای بررسی اثر زمان روی یک برنامه،‌ پاسخ به این پرسش را در نظر بگیرید:دوره‌ی زمانی مورد انتظار برای استفاده از این کد چقدر است؟ پاسخ‌ها تا ۱۰۰،۰۰۰ برابر ممکن است با هم تفاوت داشته باشند. - از چند دقیقه تا دهه‌ها.کدهای با دوره‌ی زندگی کوتاه فقط مسائل برنامه‌نویسی هستند، در حالی که هر چه طول این دوره اضافه شود تغییرات مهم‌تر می‌شوند.این تمایز، هسته‌ی «پایداری» نرم‌افزار است. - sustainabilityیک پروژه در صورتی پایدار است که «قادر باشد» در دوره‌ی حیاتش به هر تغییر ارزشمندی (که ممکن است به دلایل فنی یا تجاری ضروری باشد) پاسخ دهد.هرچند این «قادر بودن» ممکن است به دلیل «ارزشمند نبودن» یا «اولویت نداشتن» به کار گرفته نشود، اما وقتی ناتوان از واکنش (پاسخ) به تغییر تکنولوژی مورد استفاده یا جهت حرکت محصول هستیم، انگار روی عدم تغییر آن‌ها شرط‌بندی کرده‌ایم.اگر پروژه کوتاه‌مدت باشد احتمالا شرط را می‌بریم، اما برای چند دهه بعید استمقیاساز دید دیگر، مهندسی نرم‌افزار مقیاس را در نظر می گیرد:چند نفر درگیر کار هستند؟آن‌ها در طول زمان، کدام نقش‌ها را در توسعه و نگهداری بازی می‌کنند؟برنامه‌نویسی اغلب کاری یک نفره است ولی مهندسی نرم‌افزار تلاشی تیمیکار تیمی چالش‌های جدیدی ایجاد می کند اما راه را برای تولید سیستم‌های ارزشمندتری (از آن چه یک برنامه‌نویس به تنهایی می‌تواند بسازد) هموار می‌کند. سازماندهی تیم، ترکیب پروژه، و سیاست‌ها و اَعمال مهندسی نرم‌افزار همگی به این جنبه ازپیچیدگی مهندسی نرم‌افزار نظر دارند.این مسائل همه زیرگروه مساله‌ی مقیاس هستند.موازنه و پیچیدگی تصمیماتتصمیمات بر مبنای موازنه (مثلا اتخاذ سیاست‌هایی که مقیاس‌پذیر نیستند) با دانش این که بعدا لازم است دوباره آن را بررسی کنیم انجام می‌شوند. چنین انتخاب‌هایی باید راجع به هزینه‌های به تعویق انداخته شده صریح و واضح باشند.در این کتاب راجع به تولید و نگهداری نرم‌افزارهایی صحبت می‌شود که قرار است دهه‌ها عمر کنند، و ده‌ها هزار مهندس نرم‌افزار با منابعی در وسعت جهانی برای این منظور در دسترس هستند.زمان و تغییراتبرنامه‌هایی که نوآموزان برنامه‌نویسی می‌نویسند دوران زندگی خیلی کوتاهی دارند. در حد یکی دو ساعت یا چند روز.‫ممکن است در پروژه‌های تیمی دانشگاهی عمر کد بیشتر از یکی دو ماه طول بکشد و در نتیجه نیاز به بازآرایی (refactoring) قسمت‌هایی از کد ایجاد شود.در برنامه‌نویسی تجاری هم ممکن است کدهای با طول عمر کم ایجاد شوند: مثلا بعضی از برنامه‌های موبایلمهندسین نرم‌افزار در شرکت‌های استارت‌آپ ممکن است به درستی روی اهداف سریع تمرکز کنند تا روی سرمایه‌گذاری بلندمدت، زیرا عمر شرکت ممکن است به بهره‌برداری از چنان زیرساختی قد ندهدیک مهندس نرم‌افزار با ۱۰ سال سابقه در استارت‌آپ ممکن است هیچ تجربه‌ای در نگهداری از یک نرم‌افزار برای بیش از یک سال نداشته باشددر سوی دیگر، نرم‌افزارهایی مثل جستجوی گوگل، کرنل لینوکس و سرور وب آپاچی دارای طول عمر نامحدود هستند.اهمیت به‌روزرسانی بر حسب عمر نرم‌افزارهر چه عمر نرم‌افزار به «چند دهه» نزدیک‌تر می‌شود اهمیت به‌روزرسانی هم بیشتر خواهد شد. برای مثال، نمی‌توان انتظار داشت که موتور جستجوی گوگل روی یک سیستم عامل دهه‌ی ۹۰ میلادی گیر کند، بلکه باید قابل به‌روزرسانی به نسخه‌های جدید سیستم عامل، تکنولوژی و از این دست باشد.نمودار بالا این ایده را به دست می‌دهد که جایی بین یک برنامه‌ی یک بار مصرف و برنامه‌ای که دهه‌ها باقی می‌ماند یک تبدیل اتفاق می‌افتد. پروژه باید به عوامل متغیر خارجی واکنش نشان دهد.این تبدیل برای پروژه‌هایی که برای چنین تغییری برنامه‌ریزی نشده‌اند به سه دلیل دردناک خواهد بود:کاری را انجام می‌دهید که تاکنون برای این پروژه انجام نشده است. با کلی فرضیات مخفی طرف هستیدمهندسینی که این به‌روزرسانی را انجام می‌دهند تجربه‌ی چنین کارهایی را ندارنداندازه‌ی به‌روزرسانی غالبا بزرگ‌تر از معمول است: داریم به جای یک به‌روزرسانی افزایشی (برای پروژه‌ای که چنین قابلیتی دارد) به اندازه‌ی چند سال به‌روزرسانی می‌کنیمبه خاطر همین مشکلات ممکن است شرکت‌ها تصمیم بگیرند کل پروژه‌ی قبلی را دور ریخته و از نو شروع کننداین فرآیند که اولین به‌روزرسانی بزرگ انجام شود، سپس به نقطه‌ای برسید که با اطمینان و متداوما بتوانید به‌روزرسانی‌های بعدی را هم انجام دهید، اساس پایداری پروژه‌ی شماست.نویسنده اذعان می‌کند که برای بسیاری از پروژه‌ها در گوگل چنین پایداری عمدتا با آزمایش و خطا به دست آمده است.قانون هایرام - Hyrum's Lawدرس مهم راجع به «کار میکنه» در مقابل «قابل نگه‌داریه» با این قانون به دست می‌آید:با وجود تعداد کافی کاربران یک رابط برنامه‌نویسی (رابک)، مهم نیست که به چه چیزی در قرارداد متعهد شده‌اید: افراد به تمام رفتارهای قابل مشاهده‌ی سیستم شما اتکا می‌کنندیک مثال شاید نه چندان غلوآمیز برای درک این قانون در کمیک زیر نمایش داده شده است:مثال: ترتیب در هشمثالی از این که چطور برنامه‌نویس‌ها عادت کرده‌اند که ساختار نگاشت هش کلید‌ها را مرتب نگه‌داری کند.مثال دیگری هم می‌زند که اگر همیشه پارامترهای یک فراخوانی راه دور را مرتب ارسال کنید ممکن است برنامه‌نویس تابع راه دور شما این را جزء فرضیات قلمداد کند.در برنامه‌های کوتاه‌عمر چنین فرضیاتی چیزی را خراب نمی‌کند اما در یک پروژه‌ی مهندسی نرم‌افزار چنین فرضیاتی ریسک محسوب می‌شوند.چرا «چیزی تغییر نمی‌کند» را هدف قرار ندهیم؟مگر این که از کتابخانه‌های بدون تغییر استفاده می‌کنید که به‌روزرسانی امنیتی برایشان تولید نمی‌شود.همه چیز تغییر می‌کنند!حتی اگر برای افزایش بهره‌وری سی‌پی‌یو را عوض می‌کنید ممکن است دیگر برنامه‌های قدیمی کارایی قبلی را نداشته باشند و نیازمند تغییر باشند.مقیاس و کاراییبه عنوان یک ماشین واحد، سیستم عملیاتی گوگل یکی از پیچیده‌ترین ماشین‌هایی است که تاکنون توسط بشر ساخته شده‌اند.قبلا کتابی راجع به پیچیدگی راهبری چنین ماشینی در مقیاس بالا نوشته‌ایم. (با تشکر از دوستی که کامنت گذاشتند: کتاب Site reliability engineering (SRE) https://sre.google/books)اما موضوع کتاب حاضر، بیشتر روی «پیچیدگی مقیاس سازمانی» است که چنان ماشینی را تولید می‌کند.با در نظر گرفتن مفهوم پایداری، زیر بحث «توانایی تغییر» یک «هزینه» هم پنهان شده: اگر تغییر چیزی باعث هزینه‌ی فراوان شود، به احتمال زیاد به تعویق انداخته خواهد شد. اگر هزینه‌ها در طی زمان فراتر از خطی افزایش یابند، عملیات مقیاس‌پذیر نیست.می‌توان برای بررسی این موضوع این سوال را پرسید: اگر پروژه دو برابر شود و بخواهید یک وظیفه‌ی مشخص را دوباره انجام دهید، آیا دو برابر نیروی کار نیاز خواهد داشت؟ یا بیشتر؟ اصلا چنین تغییری با تمام نیروهای سازمان میسر خواهد بود؟البته نیروی انسانی تنها عامل محدودی نیست که نیاز به مقیاس‌پذیری دارد. همان‌گونه که خود نرم‌افزار نیازمند مقیاس‌پذیری است، پروسه‌ی تولید آن هم باید مقیاس‌پذیر باشد: هم در نیروی انسانی، و هم در منابع کامپوتری که جریان کاری توسعه‌ی شما را انجام می‌دهند.نهایتا خود مخزن کد هم نیازمند مقیاس‌پذیری است.سیاست‌هایی که مقیاس‌پذیر نیستندبرای تشخیص چنین سیاست‌هایی، می‌توان فرض کرد کاری که یک مهندس قرار است انجام دهد با ۱۰ یا ۱۰۰ برابر شدن سازمان چه تغییری خواهد کرد. آیا کار این مهندس ۱۰ برابر می‌شود؟ یا چندین برابر؟ برای هر کدام از این حالات چه مکانیزمی اکنون در سازمان جاری است که این کار را خودکار یا بهینه کند؟‫یک مثال، استفاده از یک شاخه‌ی توسعه (development branch) است. به محض این که سازمان‌ها عقب افتادن کار به خاطر طولانی بودن روند ادغام (مرج) را مشاهده می‌کنند به چند شاخه‌ی توسعه‌ی فرعی رو می‌آورند. اما این باعث کند شدن عملیات به خاطر تاثیر ادغام هر یک از شاخه‌های فرعی (به خاطر rebase, resync تست و از این دست) روی سایر شاخه‌ها خواهد شد.سیاست‌هایی که به خوبی مقیاس‌پذیرندیک مثال از چنین سیاستی را گوگل برای تست‌های مجتمع پیاده کرده است. اگر یک تیم زیرساخت تغییری انجام دهد و تست‌های مجتمع با موفقیت انجام شوند دیگر تیم زیرساخت مسئولیتی در مقابل تست‌های داخلی که با شکست مواجه شده‌اند ندارد.‫«اگه میخوایش یک تست مجتمع براش بذار» که در گوگل با نام قانون بیانسه شناخته می‌شود و از قسمتی از ترانه‌ی معروف Single Ladies همین خواننده الهام گرفته استIf you like it you should have put a ring on itاین قانون جلو مراجعه تیم زیرساخت به تیم‌های مختلف و تقاضا برای بررسی کد مشکل‌دار را می‌گیرد.یک مثال دیگر، نقش فروم‌های ارتباطی مشترک در مقیاس سازمانی در به اشتراک‌گذاری دانش استاگر یک گروه ۱۰۰ نفره برنامه‌نویس جاوا دارید، تنها یک خبره‌ی جاوای کمک‌کننده با رفتار دوستانه کافیست که تمام ۱۰۰ برنامه‌نویس کد جاوای بهتری بنویسند. دانش همه‌گیر است.مثال: به‌روزرسانی کامپایلر‫به‌روزرسانی کامپایلر معمولا تغییرات کوچکی را نیاز دارد: تصحیح خطاهای کامپایل، دستکاری بهینه‌سازی‌ها و تغییر احتمالی نتایجی که قبلا «تعریف‌نشده» بودند.یکی از تاریخی‌ترین به‌روزرسانی‌های کامپایلر در گوگل به ۲۰۰۶ برمی‌گردد که بعد از ۵ سال دوباره کامپایلر به‌روزرسانی شد.مشکلات زیادی به خاطر قانون هایروم به مخزن کد نفوذ کرده بود و وابستگی‌ها به نسخه‌ی کامپایلر عمیق‌تر کرده بود. در آن زمان هنوز قانون بیانسه وجود نداشت، حتی یک تست مجتمع فراگیر هم نبود. پس سخت بود که تاثیر نتایج را پیش از اعمال تغییرات بدانیم.این داستان برای مهندسین شرکت‌های زیادی غیرمعمول نیست. بسیاری می‌توانند داستان‌های مشابهی از به‌روزرسانی‌های دردناک تعریف کنند.هر چه به دفعات بیشتری زیرساخت را تغییر دهید انجام آن برای شما ساده‌تر خواهد شد.فهمیدیم که هر چه کد را برای تغییراتی مثل به‌روزرسانی کامپایلر به‌روز کنیم، شکنندگی آن کمتر می‌شود و به‌روزرسانی‌های آن در آینده آسان‌تر می‌شوند.‫در این تجربیات عوامل زیادی را کشف کردیم که روی انعطاف مخزن کد تاثیر می‌گذارند:خبرگی Expertiseاستحکام Stabilityمطابقت Conformityآشنایی Familiarityسیاست Policyشیفت به چپیکی از واقعیات گسترده‌ای که درستی آن را دیدیم این ایده بود که یافتن زودتر مشکلات در خط زمانی جریان کاری توسعه‌دهنده معمولا هزینه‌ها را کاهش می‌دهد.در نمودار «خط زمانی جریان کاری توسعه‌دهنده»، هر چه مشکلات در سمت چپ‌تر (زودتر) پیدا شوند هزینه‌ی تصحیح کمتری خواهند داشت.به نظر می‌رسد این عبارت از این جا نشات گرفته که امنیت نباید تا انتهای فرایند توسعه معوق شود و باید «شیفت به چپ» داشته باشدخط زمانی جریان کاری توسعه‌دهنده ‫چنین الگویی بارها در این کتاب ظاهر می‌شود: باگ‌هایی که با تحلیل ایستا (static analysis) یا بازبینی کد یافت و برطرف می‌شوند هزینه‌ی بسیار کمتری دارند از آن‌هایی که بعد از تولید پیدا می‌شوند.پس فراهم آوردن ابزارها و اعمالی که کیفیت، قابلیت اعتماد، و امنیت را «زودتر» در فرایند توسعه پررنگ کنند هدف مشترک بسیاری از تیم‌های زیرساخت ما است.موازنه‌ها و هزینه‌هادر مهندسی نرم‌افزار هم مانند زندگی، «انتخاب‌های خوب» نتایج خوب در بر دارند. اما چنین انتخاب‌هایی چگونه انجام می‌شوند؟هر زمان که ممکن باشد باید بتوانیم کارمان را در تصمیم‌گیری میان هزینه‌های عمومی دو انتخاب مهندسی توضیح دهیم. هزینه می‌تواند یکی از انواع زیر باشد:هزینه‌های مالی — مثلا پول‫هزینه‌ی منابع — مثلا زمان روی CPUهزینه‌ی تراکنش — چقدر هزینه می‌برد تا عمل کند؟‫هزینه‌های فرصت (opportunity) — چقدر این که کاری نکنیم هزینه دارد؟هزینه‌ی اجتماعی — این انتخاب چقدر روی جامعه چقدر خواهد بود؟سلامت یک سازمان فقط این نیست که آیا پولی در بانک دارد یا نه، بلکه این هم هست که آیا اعضای آن احساس ارزشمندی و سازندگی دارند یا نه.‫در حوزه‌های با خلاقیت بالا و سود مالی زیاد (مانند مهندسی نرم‌افزار) معمولا هزینه‌های مالی عامل محدودکننده نیستند، اما هزینه‌ی پرسنل چنین عاملی هست.مثال: ماژیک وایت‌برد‫در بسیاری از سازمان‌ها با ماژیک وایت‌برد به صورت کالایی ارزشمند برخورد می‌شود. معمولا کنترل زیادی روی آن اعمال می‌شود و معمولا هم در تنگناست.در گوگل، این که بدون هیچ مانعی بتوان طوفان ذهنی داشت بسیار مهم‌تر است از این که یک نفر با یک مشت ماژیک شرکت را ترک کند. پس ماژیک به صورت فراوان در رنگ‌های مختلف همه جا در دسترس است.چنین موازنه‌هایی (که این جا بین ماژیک و طوفان ذهنی بررسی شد) در همه‌ی امور در گوگل بررسی می‌شود.اغلب گفته می‌شود «گوگل یک فرهنگ مبتنی بر داده است». در واقع حتی وقتی «داده» نیست، «شهود»، «سوابق» و «استدلال» هست‫در پایان، تصمیمات در یک گروه مهندسی باید به این موارد خلاصه شوند:این تصمیم را می‌گیریم چون مجبوریم — به خاطر نیازمندی‌های قانونی یا نیازمندی‌های مشتریاین تصمیم را می‌گیریم چون بهترین گزینه‌ای است که با شهود فعلی داریمورودی‌های تصمیم‌گیری‫با بررسی داده، دو سناریو مشاهده می‌کنیم:تمام مقادیر درگیر قابل اندازه‌گیری هستند،نمی‌دانیم برخی از مقادیر را چطور اندازه‌گیری کنیم.برای موارد نوع دوم، پاسخ آسان وجود ندارد. ما به تجربه، رهبری و سوابق اتکا می‌کنیم تا راجع به آن‌ها مذاکره کنیم.چنین تصمیماتی به اندازه‌ی تصمیمات نوع اول مهم هستند اما مدیریتشان سخت‌تر است.مثال: تولید توزیع‌شده‫در اواسط دهه‌ی اول ۲۰۰۰، گوگل کاملا به «تولید کد محلی» (local build) متکی بود. این تولید کد توسط سیستم‌های قوی که به مهندسین داده شده بود انجام می‌شد.‫مثل سایر مقولات مقیاس‌ناپذیر، این زمان‌های تولید هم کم‌کم طولانی شدند و سربار زیادی روی زمان نیروی انسانی ایجاد کردند.توصیف خوبی از این وضعیت در کاریکاتور زیر نشان داده شده:در نهایت گوگل سیستم تولید کد توزیع‌شده‌ی خودش را ایجاد کرد تا هزینه‌های سیستم‌های محلی و زمان کاربران کاهش پیدا کند — توجه کنید که این جا هم یک موازنه‌ی دیگر بررسی و حل شد.‫با وجود بهبود عظیم در بهره‌وری مهندسی، در گذر زمان تولید توزیع‌شده بیش از حد انتظار اشغال شد. چون برنامه‌نویسان، دیگر به اندازه‌ی زمان «تولید کد محلی» خود را درگیر حذف وابستگی‌های زائد و بهینه‌سازی کامپایلر نمی‌کردند.وقتی همه مستقیما مشکل تولید غیربهینه را حس کنند تشویق می‌شوند که هوشیار باشند.مثال: تصمیم میان زمان و مقیاسگاهی زمان و مقیاس با هم در تضاد قرار می‌گیرند. برای نمونه، پاسخ به این پرسش که باید یک وابستگی جدید اضافه کنیم یا پیاده‌سازی جدید داشته باشیم که نیازهایمان بهتر پوشش داده شوند؟‫از یک سو پیاده‌سازی جدید می‌تواند عملکرد بهتری ایجاد کند و از سوی دیگر اگر هر توسعه‌دهنده‌ای به جای استفاده از آن چه موجود است پیاده‌سازی خودش را به سیستم اضافه کند مقیاس‌پذیری در کنار پایداری آسیب می‌بیند.مانند بیشتر مسائل مهندسی نرم‌افزار برای این وضعیت هم پاسخی یکسان که به درد همه بخورد وجود ندارد‫سازگاری (consistency) ارزش زیادی دارد اما عمومیت (generality) هم هزینه‌های خودش را داردبررسی دوباره‌ی تصمیمات، اشتباه کردنیکی از مزایای فرهنگ مبتنی بر داده که کمتر به آن پرداخته شده، ترکیب توانایی به و ضرورت اقرار به اشتباه استبرای یک سازمان با عمر زیاد، زمان نه تنها باعث تغییرات در وابستگی‌ها و سیستم‌های نرم‌افزاری است، بلکه باعت تغییر در داده‌هایی نیز هست که منجر به اتخاذ تصمیمات شده‌انداز آن جا که به تصمیمات مبتنی بر داده اعتقاد داریم، از آن جا که داده‌ها در طول زمان تغییر می‌کنند، نیاز خواهیم داشت که آن تصمیمات را گاه و بی‌گاه تغییر دهیماین به معنی این است که ضروری است تصمیم‌گیرندگان مجوز اقرار به اشتباه داشته باشندفصل ۵ بیشتر راجع به رهبری صحبت شدهمهندسی نرم‌افزار در مقابل برنامه‌نویسیتفاوت اصلی میان این دو از مدیریت کد در طول زمان، تاثیر زمان روی مقیاس و تصمیم‌گیری در مواجهه با این ایده‌ها نشات می‌گیردنتیجه‌گیری‫گوگل به سختی برای داشتن مخزن کد و فرهنگی که پایدار باشند کار کرده است اما فکر نمی‌کنیم که تنها روش درست انجام این کار روش ما است.‫امیدواریم این کتاب چارچوبی را برای تفکر راجع به این مساله‌ی عمومی ایجاد کند:چطور کدتان را برای دوره‌ی زمانی که قرار است کار کند نگه‌داری می‌کنید؟خلاصهمهندسی نرم‌افزار در ابعاد با برنامه‌نویسی متفاوت است: برنامه‌نویسی راجع به تولید کد است، اما مهندسی نرم‌افزار شامل نگهداری کد برای طول عمر آن می‌شودبین طول عمر کدهای کم عمر و پرعمر حداقل ۱۰۰،۰۰۰ برابر تفاوت هست. این ناآگاهانه است که فرض کنیم یک راه‌کار برای هر دو سوی قضیه بهترین جواب را می‌دهدنرم‌افزار در صورتی پایدار است که بتوانیم در طول عمرش به تغییرات تکنولوژی یا نیازمندی‌های محصول پاسخ دهیم. ممکن است این تغییرات را انجام ندهیم ولی باید قادر به انجامشان باشیمقانون هایروم: با وجود تعداد کافی کاربران یک رابط برنامه‌نویسی (رابک)، افراد به تمام رفتارهای قابل مشاهده‌ی سیستم شما اتکا می‌کنندهر وظیفه‌ی تکراری در سازمان شما باید در گرفتن ورودی انسانی مقیاس‌پذیر باشد (به صورت خطی یا بهتر). سیاست‌ها ابزارهای عالی برای مقیاس‌پذیر کردن فرایندها هستند.ناکارامدی فرایندها و سایر وظایف تولید نرم‌افزار به کندی رشد می‌کنند. مراقب مساله‌ی غورباقه‌ی پخته باشیدخبرگی زمانی که با اقتصاد مقیاس ترکیب شود به خوبی نتیجه می‌دهدوحشتناک‌ترین دلیل برای انجام کارها «چون من می‌گم» هست‫مبتنی بر داده بودن خوب است، اما در واقعیت بیشتر تصمیمات بر پایه‌ی ترکیبی از داده، فرضیات، سوابق و استدلال صورت می‌گیرند.‫مبتنی بر داده بودن در خلال زمان دلالت بر این دارد که هر زمان که داده تغییر کرد (یا فرضیات غلط از آب درآمد) جهت را تغییر دهیم. اشتباه اجتناب‌ناپذیر است.در پایان لازمه از Kian تشکر کنم که کتاب رو در اختیارم گذاشت.Cover photo by ?? Janko Ferlič on Unsplash