Software Engineering at Google
Lessons Learned from Programming Over Time
توضیحات
امروزه مهندسان نرم افزار نه تنها باید بدانند که چگونه به طور مؤثری برنامه نویسی کنند بلکه همچنین چگونه میتوانند شیوههای مهندسی مناسبی را توسعه دهند تا کدهای خود را پایدار و سالم کنند را نیز باید بدانند. کتاب مهندسی نرم افزار در گوگل، بر تفاوت بین برنامه نویسی و مهندسی نرم افزار تأکید دارد.
مهندسان نرم افزار چگونه میتوانند پایگاه کد زنده ای را مدیریت کنند که در طول عمرش تکامل مییابد و به تغییرات نیازمندیها و درخواستها پاسخ میدهد؟ مهندسان نرم افزار Titus Winters و Hyrum Wright، به همراه نویسنده فنی Tom Manshreck، بر اساس تجربه خود در گوگل نگاه صریح و روشنگری در مورد چگونگی ساخت و نگهداری نرم افزارها توسط متخصصان برجسته دنیا ارائه میدهند. کتاب Software Engineering at Google، فرهنگ مهندسی، فرآیندها و ابزارهای منحصر به فرد گوگل و چگونگی مشارکت این جنبهها بر اثربخشی یک سازمان مهندسی را بررسی میکند.
در کتاب مهندسی نرم افزار در گوگل، شما سه اصل اساسی را که سازمانهای نرم افزاری در هنگام طراحی، معماری، نوشتن و نگهداری کدها باید در نظر داشته باشند را بررسی خواهید کرد:
- چگونگی تأثیر زمان بر دوام نرم افزار و چگونگی مقاوم کردن کد هایتان در طول زمان
- چگونه بزرگی بر روی کارکرد موثر شیوههای نرم افزار در یک سازمان مهندسی تأثیر میگذارد
- مصالحه هایی که یک مهندس معمولی هنگام ارزیابی تصمیمات طراحی و توسعه باید انجام دهد
نظر
کتاب نکات خوبی را در زمینه نگهداری کد بیان میکند و تفاوت مهندس نرم افزار و برنامهنویس را میگوید
نظر
امتیاز: 09/10به دیگران توصیه میکنم: بلهدوباره میخوانم: بلهایده برجسته: مهندسی نرمافزار یعنی برنامهنویسی که روی محور زمان است.تاثیر در من: دقت بیشتر روی پایداری کد و تفکر دربارهی طول عمر آننکات مثبت: دیدگاه عمیق و عملی به مهندسی نرمافزار در سازمانهای بزرگنکات منفی: بعضی بخشها کمی تکراری بود
مشخصات
نویسنده: Tom Manshreck, Hyrum Wright, Titus Wintersانتشارات: O’Reilly
بخشهایی از کتاب
برنامه فصل ۱ (What Is Software Engineering?)
فصل ۱ محورش تفاوت «برنامهنویسی» و «مهندسی نرمافزار» است؛ سه محور اصلی: زمان، مقیاس، و تصمیمگیری/تریدآفها. برای این فصل، این تقسیم را پیشنهاد میکنم:
- بخش ۱: تعریف مهندسی نرمافزار و سه مزیت/تفاوت اصلی (زمان، مقیاس، تریدآفها).
- بخش ۲: محور «زمان و تغییر» و مفهوم طول عمر کد، پروژههای کوتاهعمر vs بلندمدت، و شروع درد ارتقا.
- بخش 3: پایداری (sustainability) و اینکه چه زمانی یک پروژه مجبور میشود به تغییرات محیط واکنش نشان دهد.
- بخش ۴: «قانون هایروم» (Hyrum’s Law) و اثراتش روی طراحی API و تغییرات.
- بخش ۵: جمعبندی فصل ۱ و چند سؤال تفکری (متمرکز روی تجربه خودت در پروژههای داتنت).
بخش ۱ فصل ۱ – تعریف مهندسی نرمافزار
ایدهی مرکزی فصل
کتاب در ابتدای فصل ۱ میگوید چیزی که مهندسی نرمافزار را از «صرفاً برنامهنویسی» جدا میکند سه چیز است: زمان، مقیاس، و نوع تصمیمهایی که باید بگیری. در یک تمرین دانشگاهی یا اسکریپت یکبار مصرف، فقط کافی است «الآن» کار کند، اما در مهندسی نرمافزار واقعی باید فکر کنی این سیستم در طول سالها چه تغییری میکند، چند نفر روی آن کار خواهند کرد و هر تصمیم امروز چه هزینهای در آینده ایجاد میکند.
برای توضیح این تفاوت، نویسندهها از یک جمله درون گوگل استفاده میکنند: «مهندسی نرمافزار یعنی برنامهنویسی که روی محور زمان انتگرال گرفته شده است»؛ یعنی برنامهنویسی فقط لحظه تولید نرمافزار است، اما مهندسی نرمافزار شامل توسعه اولیه، تغییرات بعدی، نگهداری، ارتقا و زندگی طولانیمدت سیستم است.
محور «زمان»
یکی از اولین سؤالهایی که مطرح میشود این است: «طول عمر مورد انتظار کد تو چقدر است؟».
- بعضی کدها چند دقیقه یا چند ساعت عمر دارند (مثلاً یک اسکریپت یکباره).
- بعضی کدها باید دههها زندگی کنند (مثلاً گوگل سرچ، هسته لینوکس، سرورهای مهم).
برای کد کوتاهعمر، تغییرات محیط (ورژن زبان، سیستمعامل، کتابخانهها) تأثیر چندانی ندارد؛ چون قبل از اینکه آن تغییر برسد، کد دیگر استفاده نمیشود. اما هرچه طول عمر نرمافزار بیشتر میشود، احتمال تغییر وابستگیها، نیازهای کسبوکار، معماری و زیرساخت بیشتر میشود، و همین جاست که مهندسی نرمافزار از برنامهنویسی جدا میشود.
نویسنده این را به یک مثال هندسی تشبیه میکند: همانطور که یک مکعب با فشرده شدن در یک بعد به مربع تبدیل میشود، یک سیستم نرمافزاری که بُعد «زمان» برایش نادیده گرفته شود، در حد یک مسئلهی ساده برنامهنویسی باقی میماند.
محور «مقیاس»
محور دوم، مقیاس است: چند نفر درگیرند، چقدر کد وجود دارد و چقدر طول میکشد تا از توسعه اولیه فراتر بروی. در یک کار چندروزه انفرادی، بیشتر با مسائل فردی سر و کار داری؛ اما در یک سازمان بزرگ، توسعه نرمافزار به یک کار چندنفره، چندنسخهای و بلندمدت تبدیل میشود.
کتاب به یک تعریف قدیمی از مهندسی نرمافزار اشاره میکند: «توسعه چندنفرهی برنامههای چندنسخهای»؛ یعنی از همان ابتدا مفهوم تیم و نسخهنسخه تکامل کد، جزئی از تعریف است. اینجا عواملی مثل ساختار تیم، سیاستهای کنترل نسخه، استراتژی تست، فرایند کدریویو و هزینه ارتباطات بین آدمها روی پیچیدگی کار اثر میگذارند و با بزرگتر شدن سازمان باید مراقب باشی «هزینه تولید نرمافزار» همراه با اندازه سازمان، منفجر نشود.
محور «تریدآفها و تصمیمگیری»
محور سوم، نوع تصمیمهایی است که در مقیاس مهندسی نرمافزار باید بگیری. در این سطح، معمولاً چند گزینه مختلف پیش رو داری که هرکدام هزینهها و ریسکهای متفاوت دارند و دادههایت هم همیشه کامل یا دقیق نیست؛ باید بین «سرعت»، «کیفیت»، «هزینه»، و «آیندهپذیری» تعادل برقرار کنی.
نویسنده روی مفهوم «پایداری» تأکید میکند: شغل مهندس نرمافزار و رهبر فنی این است که سازمان، محصول و فرایند توسعه را طوری مدیریت کنند که در طول زمان بتوانند به تغییرات مهم پاسخ دهند، بدون اینکه سیستم فلج شود. گاهی آگاهانه تصمیم میگیری یک بدهی فنی را فعلاً نپردازی یا یک سیاست غیرمقیاسپذیر را موقتاً بپذیری، اما باید بدانی بعداً باید به این تصمیم برگردی و هزینهاش را بدهی.
نتیجه بخش ۱ – تفاوت «کد زدن» و «مهندسی»
جمعبندی این بخش این است که «مهندسی نرمافزار» یعنی:
- فکر کردن به طول عمر کد و امکان تغییر آن در طول سالها، نه فقط لحظه تحویل.
- کار در مقیاس تیمها، سازمانها و سیستمهای چندنسخهای، نه فقط پروژه فردی.
- تصمیمگیری با در نظر گرفتن تریدآفها، پایداری و هزینههای آینده، نه فقط رسیدن به خروجی امروز.
از دید کتاب، خیلی از تمرینهای دانشگاهی، بوتکمپها و حتی بعضی استارتاپها بیشتر «برنامهنویسی» تمرین میکنند، در حالی که برای ساخت سیستمهای ماندگار، باید این سه بُعد (زمان، مقیاس، تریدآفها) را در مرکز کار قرار بدهی.
بخش ۲ فصل ۱ – زمان و تغییر (Time and Change)
مقدمه: چرا زمان اهمیت دارد؟
یکی از تفاوتهای بنیادی بین «برنامهنویسی» و «مهندسی نرمافزار» این است که برنامهنویسی لحظهای است، اما مهندسی نرمافزار درباره طول زمان است. بخش ۲ فصل ۱ تمام بحث را دور این محور میچرخاند: اگر کد تو باید ۱۰ سال عمر کند، نه فقط ۱۰ روز، همه چیز تغییر میکند.
سطح ابتدایی: کجا از کجا درست شروع کنیم؟
برای شروع، کتاب از این سؤال شروع میکند: «طول عمر مورد انتظار کد تو چقدر است؟»
جواب این سؤال میتواند چیزی از چند دقیقه تا چند دهه باشد. نویسندهها میگویند این تفاوت حدود ۱۰۰,۰۰۰ برابر است! یعنی:
- کد کوتاهعمر: یک اسکریپت تکبار مصرف، یک ابزار برای یک جلسه، یک آزمایش ریاضی.
- کد بلندمدت: سیستمهای اصلی گوگل (مثل Google Search)، هسته لینوکس، زیرساختهای کریتیکل.
اینجا کلید است: برای کد کوتاهعمر (مثل یک اسکریپت ۲ ساعتی)، توجه نکردن به تغییرات محیط (ورژن پایتون، سیستمعامل) ریسک نیست چون قبل از این که آن تغییر برسد، کد همچنان استفاده نمیشود. اما برای کد بلندمدت، گذشت زمان معنیش این است که تقریباً همه وابستگیها (dependency) تغییر خواهند کرد.
مثال عملی: کتابخانهها و سیستمعاملها
فرض کن داتنت پروژهای نوشتی که از کتابخانهی X استفاده میکند و آن کتابخانه برای ۶ ماه آینده کافی است. خوب است، مشکلی نیست.
اما اگر این پروژه باید ۵ سال زندگی کند؟ در این ۵ سال:
- کتابخانهی
X۵ یا ۶ بار نسخه اپدیت میشود. - مایکروسافت شاید
.NETرو از.NET 6به.NET 12و.NET 15ارتقا دهد. - سیستمعاملهای محیط اجرایی (Windows Server) تغییر میکند.
- احتمالاً یک patch امنیتی عمومی (مثل Heartbleed در OpenSSL) اتفاق میافتد و باید آپدیت کنی.
هرچه مدت زمان بیشترتر باشد، احتمال تغییر نمایی بالا میرود، نه خطی.
درک نقطهی اول: تفاوت «کار میکند» و «قابل نگهداری است»
این یکی از بخشهای مهمتر است.
شاید کد تو الآن «کار میکند»: تستها پاس میکنند، فیچرها کار میکنند، یوزرها خوشحالاند. اما آیا این کد زمانی که نسخه جدید کتابخانهی X منتشر شود، هنوز هم قابل نگهداری است؟ اینجا است که مسئله دوم شروع میشود: تفاوت بین «همینالان کار میکند» (works now) و «برای سالها میتوانم تغییرش بدم» (is maintainable).
برای کد کوتاهعمر، این تفاوت مهم نیست. برای کد بلندمدت، بسیار مهم است.
مثال: انتقال از نسخهای موقتی و غیرمستقیم
اگر پروژهات کوتاهعمر است و کد پر از ترفندها است (مثلاً نوشتن چند خط hack کوتاهمدت)، شاید خوب است. سریع و کار میکند.
اما اگر همین کد بعد از ۳ سال هنوز در production است و دیگر تیمها هم وابستهاند؟ حالا هر ترفند میتواند یک بمب وقتگذار شود. کسی برای fix کردناش، باید تمام context را دوباره یاد بگیرد.
پایداری (Sustainability): کلید مهندسی نرمافزار
نویسندهها یک تعریف کلیدی میدهند:
پروژهی شما پایدار است اگر برای طول عمر مورد انتظار نرمافزارتان، شما توانایی واکنش به تغییرات ارزشمند را داشته باشید، چه بخاطر دلایل فنی یا کسبوکاری.
توجه کن: این توانایی است، نه اجبار. ممکن است تصمیم بگیری که یک upgrade را نکنی چون ارزش ندارد. اما بدانی توانایی آن را داری.
اگر نتوانی upgrade کنی یا تغییر بدهی؟ آنوقت ریسک بالا میگیری: که امیدواری میکنی هیچ چیز critical تغییر نشود. برای پروژههای کوتاهعمر این bet ایمن است، اما برای دههها؟ نه.
نمودار: طول عمر و اهمیت upgrade
کتاب یک نمودار دارد (Figure 1-1) که نشان میدهد:
- کد کوتاهعمر: upgrade ضروری نیست
- کد بلندمدت: upgrade کریتیکال است
بین این دو، انتقال اتفاق میافتد. تقریباً بین ۵ تا ۱۰ سال است که یک پروژه باید شروع به واکنش به تغییرات کند.
مسئلهی «اولین upgrade»: اینجا درد است
اگر پروژهای از ابتدا برای upgrade ریخته نشده باشد، اولین upgrade خیلی دردناک است و سه دلیل دارد:
۱. Task جدید است: تا الآن هیچ کسی upgrade نکرده. فرضهای زیادی در کد مخفی شدهاند.
۲. Experience کم: مهندسهای فعلی شاید هیچگاه upgrade نکردهاند و نمیدانند چکار باید کنند.
۳. Size بزرگ: نه یک سال upgrade، بلکه ۵ سال upgrade یکباره (چون ۵ سال missed کردی!).
نتیجهی دردناک: «دوباره نه»
بعد از یک upgrade دردناک، مهندسان اغلب تصمیم میگیرند «دوباره هرگز نه» و یا تصمیم میگیرند تمام چیز را دوباره بنویسند. ولی این تصمیمات مشکل را بزرگتر میکند، نه کوچکتر.
حل درست این است: سرمایهگذاری کن که upgrade را سهلتر کنی. اگر ۱ سال یکبار ۱۰ روز upgrade میکند (چه منطقیتر است)، درد نیست. رویتین است.
تجربهی Google: Compiler Upgrade ۲۰۰۶
کتاب یک مثال واقعی میدهد. Google برای سالهای زیادی compiler خود را upgrade نکرده بود. وقتی ناچار شدند (چون compiler قدیمی شد)، اولاش خیلی دردناک بود:
- هزاران مهندس
- میلیونها خط کد
- هیچ کسی experience نداشت
- Hyrum’s Law (که بعداً میرسیم) تمام فرضهای مخفی را غافلگیر کرد
خلاصهی بخش ۲
محور زمان کل تفاوت بین برنامهنویسی و مهندسی نرمافزار است. اگر کد طولانیمدت نیست، شاید ترفندها خوب است. اما اگر طولانیمدت است، باید تفکر کنی دربارهی پایداری (sustainability): آیا میتوانم این کد را بعد از ۵ سال upgrade کنم؟ اگر جواب «نه» است، اینجا مشکل است.
کلید این است: اولین upgrade سخت است، اما اگر منظم برنامهریزی کنی، هر upgrade بعدی آسانتر میشود.
بخش ۳ فصل ۱ – قانون هایروم (Hyrum’s Law)
مقدمه: از «کار میکند» تا «قابل نگهداری است»
شاید تا حالا فکر میکردی که اگر کد تو مطابق API contract باشد، همه چیز خوب است. اینجا به رسمیت شناخته میشود که این فرض غلط است. قانون هایروم یک عمل ساده اما قدرتمند است که به خصوص برای پروژههای طولانیمدت حیاتی است.
تعریف رسمی: قانون هایروم
اگر یک API تعداد کافی از کاربران داشته باشد، مهم نیست که چه چیز وعده میدهی در contract: تمام رفتارهای observable سیستم تو، توسط کسی وابسته خواهند شد.
یعنی؟ یعنی این که حتی رفتارهایی که قصد نداشتی expose کنی، یا حتی رفتارهایی که ناقص یا undefined هستند، کسی در project خود وابستهاش میکند. وقتی بخواهی آن رفتار را تغییر بدهی (چه خیلی منطقی باشد)، breaking change است و کل ecosystem تو صدمه میخوره.
ربط به پایداری و زمان
نویسندهها این قانون را با Entropy مقایسه میکنند. همانطور که آنتروپی هرگز کاهش نمییابد (ترمودینامیک)، Hyrum’s Law هم هرگز نمیشود “حل شود.” فقط میتوانی آن را کاهش بدهی، نه حذف.
اینجا اهمیت زمان دوباره ظاهر میشود:
- اگر کد کوتاهعمر باشد، اهمیتش کم است.
- اگر کد بلندمدت باشد، هر رفتار observable (حتی تصادفی) احتمالاً کسی وابستهاش میشود.
مثال عملی: Hash Ordering
کتاب یک مثال خیلی خوب میدهد: ترتیبدهی Hash Table.
تصور کن: اگر ۵ عنصر را در یک set قرار بدهی، به چه ترتیبی بیرون میآید؟
1
2
3
4
5
6
7
8
for i in {"apple", "banana", "carrot", "durian", "eggplant"}:
print(i)
# Output:
# durian
# carrot
# apple
# eggplant
# banana
حالا، هر programmer میداند که hash table ترتیب خاصی ندارد. اما در عمل چه اتفاق میافتد؟
اگر کد تو ۱۰ سال عمر بکند:
- یک programmer، کدی مینویسد که وابسته این ترتیب است (شاید بدون دانستن!).
- یک programmer دیگر، از library تو استفاده میکند و نتایج را serialize میکند (مثلاً برای RPC response).
- Client آن RPC، حالا وابسته ترتیب موجود است (چه اینکه documented نبوده!).
مثال واقعی: چرا این اتفاق میافتد؟
نویسندهها ۳ دلیل میدهند که چرا hash ordering میتواند تغییر کند:
۱. Hash Flooding attacks: اگر کسی بخواهد سیستم تو را attack کند، ترتیب deterministic hash خطرناک است.
۲. بهبود الگوریتم: محققان الگوریتمهای بهتر hash مییابند; اگر بخواهی آنها استفاده کنی، ترتیب تغییر میکند.
۳. Hyrum’s Law: اگر اسلحهی جنگی hash ordering را ببینی، حتماً کسی براش استفاده خواهد کرد.
سطح تحلیل: «درست است» در مقابل «کار میکند»
اینجا فرق عمیق آمد:
برای کد کوتاهعمر: وابستگی بر ترتیب hash problem نیست; هر دو چیز (کد تو و hash implementation) با هم زندگی میکنند و میمیرند.
برای کد بلندمدت: وابستگی بر ترتیب risk است. اگر ۵ سال بعد قصد کنی hash implementation تغییر بدهی:
- باید تمام کد dependent را پیدا کنی (شاید dozens یا hundreds جا).
- هر کدام broken است و باید fix شود.
- هر کدام تست نیاز دارد تا مطمئن شوی خراب نشده.
تفاوت بین «hacky» و «clean»
نویسندهها این تفاوت را خوب خلاصه میکنند:
«It’s programming if ‘clever’ is a compliment, but it’s software engineering if ‘clever’ is an accusation.»
یعنی:
برنامهنویسی کوتاهعمر: «clever» = خوب! “جالب، حسابی کاملاً بهینه است!”
مهندسی نرمافزار بلندمدت: «clever» = بدی! “این کد خیلی پر ترفند است؛ کسی بعداً نمیخواهد با این کار کند.”
راهحل: آیا میتونیم چیزها را «ثابت» کنیم؟
سؤال منطقی: آیا میتونیم یک API بسازیم که هیچ چیز تغییر نکند؟
جواب: برای بیشتر پروژهها، نه.
چرا؟
۱. مسائل امنیتی: Heartbleed، Meltdown، Spectre - حتی اگر کد خوب بنویسی، dependencies تو vulnerability داشتند. باید patch کنی.
۲. بهبود عملکرد: الگوریتمهای CPU از دهه ۱۹۹۰ تغییر کردند. Linked-list یا Binary search tree هنوز کار میکنند، اما خیلی slow هستند برای hardware امروز.
۳. تکامل ناشناخته: حتی اگر اشتباه نشده باشی، گذشت زمان و تکامل technology واپسایی “ترجیح بهتر” را میآورد.
خلاصه بخش ۳: Hyrum’s Law و عملیات
Hyrum’s Law یعنی:
تمام رفتارهای observable توسط کسی وابستهاند، نه فقط documented ones.
هرچه کد طولانیتر زندگی کند، احتمال وابستگی بیشتر است.
نمیتوانی این را حذف کنی، فقط میتوانی آن را بدتر یا بهتر مدیریت کنی.
در «برنامهنویسی»: ترفند و clever خوب است.
در «مهندسی نرمافزار»: ترفند یک بمب وقتگذار است.
بخش ۴ فصل ۱ – مقیاس و کارایی (Scale and Efficiency)
مقدمه: محور دوم مهندسی نرمافزار
تا اینجا دربارهی محور «زمان» صحبت کردیم. حالا به محور دوم میرسیم: «مقیاس» (Scale). اگر فقط یک نفر است، بسیاری از مسائل شاید خودبخود حل شوند. اما وقتی سازمان بزرگ شود و صدها یا هزاران مهندس وارد شوند، هزینههای پنهان بزرگ میشود و باید کار را متفاوت انجام دهی.
سوال بنیادی: مقیاسپذیری
سوال اساسی این است:
«آیا سازمان تو هرچه بزرگتر شود، همزمان در تولید نرمافزار کارآمدتر میشود؟ یا هزینهها به همان نسبت بالا میروند؟»
یعنی اگر فقط ۱۰ مهندس داری، شاید برای یک فرایند ۵ ساعت لازم است. اگر ۱۰۰ مهندس داری، آیا ۵۰ ساعت لازم است (linear scale)؟ یا ۵۰۰ ساعت (superlinear، بدتر از خطی)؟ یا شاید ۲۵ ساعت (بهتر شده، sublinear)؟
مقیاسپذیری مثبت یعنی این که هزینه را بر اساس تعداد نمیشمارند؛ بلکه همچنان ثابت میمانند یا حتی کاهش مییابند.
سه منبع موارد نیاز برای مقیاس
کتاب سه حوزه را نام میبرد که باید مقیاسپذیر باشند:
۱. هزینههای انسانی (Human Costs)
اگر هر بار سازمان تو ۲ برابر شود، آیا تمام کارهایی که تکرار میشود (مثل code review، testing، refactoring) هم ۲ برابر میشود؟ این مشکل است.
مثال: اگر تو ۱۰۰ مهندس داری و ۱۰۰۰ مهندس شوی، آیا هزینه code review ۱۰ برابر میشود؟ اگر جواب بله است، این superlinear scaling problem است و نمیتونی اینطور ادامه بدهی.
۲. منابع محاسباتی (Computational Resources)
Build time، test time، version control operations - اگر اینها هر دفعه سازمان بزرگتر شود superlinearly بالا بروند، مشکل است.
۳. اصول کدبیس (The Codebase Itself)
اگر build time، git clone time، یا هزینه upgrade language version superlinear بالا رود، در نهایت به نقطهای میرسی که نمیتونی حرکت کنی (boiled frog problem).
مثال ۱: Deprecation – سیاستی که مقیاس ندارد
کتاب یک مثال واضح میدهد: deprecation کردن یک Widget.
رویکرد ساده (small team):
- تصمیم: “Widget قدیمی را میحذفیم در ۱۵ اگست”
- نتیجه: هر تیم خودبخود کار را انجام میدهد و migration میکند.
- مشکل: نسبتاً خوب کار میکند.
با رشد سازمان:
- حالا صدها Widget وجود دارد و هزاران وابستگی
- هر تیم باید تمام Widgets خود را migrate کند (superlinear work)
- ۱ error شامل ۲۰% تیمهای سازمان میشود
- کل فرایند broken است و نمقیاسپذیر است.
حل Google: Churn Rule
بجای اینکه مسئولیت را به teams push کنی، infrastructure team خود این کار را انجام میدهد (یا backward-compatible میکند):
✅ مقیاسپذیر است چون:
- فقط تیم infrastructure با artifact سر و کار دارد
- Dependent projects بدون کار بیشتری میروند
- Expertise در یک جا concentrated است
نتیجه: Expertise و centralization scale بیشتری دارد تا decentralized work distribution.
مثال ۲: Development Branches – سیاستی که مقیاس ندارد
رویکرد ساده:
- ۵ تا ۱۰ development branch داری.
- هر branch merge شدن expensive work میشود (resyncing و testing).
- برای small team: OK است.
با رشد:
- حالا ۱۰۰ branch یا بیشتر داری
- هر merge بالقوه ۹۹ branch دیگر را تحت تأثیر میگذارد
- سربار merge exponentially بالا میرود
حل: Monorepo + Trunk-Based Development (فصل بعدی در کتاب)
مثال عملی: Compiler Upgrade - تجربه Google ۲۰۰۶
کتاب یک مثال تاریخی خیلی معنادار میدهد: اولین compiler upgrade بزرگ Google.
وضعیت:
- صدها تیم
- millions خط کد
- ۵ سال بدون compiler update
- اکثر engineers هیچ compiler change نکرده بودند
نتیجه:
- Extremely painful
- Hyrum’s Law تمام implicit dependencies آشکار کرد
- ۳ دلیل برای درد:
- Task جدید بود
- Experience نبود
- Size بزرگ (۵ سال upgrade یکباره)
حل Google: تغییر سیاست و فرایند
بعد از درس سخت، Google روی ۵ عامل کار کرد:
| عامل | توضیح |
|---|---|
| Expertise | اول compiler upgrade difficult است؛ ۱۰۰ها بار انجام دادن آن را routine میکند |
| Stability | اگر هر ۱ هفته compiler update کنی (بجای ۵ سال)، delta کوچک است |
| Conformity | زمانی که کد regularly upgrade میشود، brittle behavior کم میشود |
| Familiarity | با تکرار، شاید فرایند را بتوانی automate کنی |
| Policy | مثل “Beyoncé Rule” (اگر CI test نگذاشتی، infrastructure fault نیست) |
نتیجه:
- از ۱۰۰+ engineers volunteer به constant engineers برای perform کردن task
- حتی هر sizebase grow کند، human effort constant باقی ماند (linear scaling!)
Beyoncé Rule: سیاستی که مقیاسپذیر است
«If you liked it, you should have put a CI test on it»
معنی:
- اگر infrastructure change باعث bug شد اما CI test آن را گرفت نشد، infrastructure fault نیست.
- This protects infrastructure teams از tracking down every bespoke test دیگر تیمها.
چرا scale میکند؟
- بدون این rule: infrastructure engineer باید هر تیم رو پیدا کند و آنها رو test کند (impossible)
- با این rule: فقط tests داخل CI count میشود (centralized، scalable)
نتیجه:
- ✅ Infrastructure teams میتوانند upgrade انجام دهند بدون دسترسی به تمام bespoke tests
- ✅ Dependent teams مسئول هستند که tests خود را در CI قرار دهند (accountability shift)
Shifting Left: سیاست درست انجام کار
نویسندهها یک اصل کلی معرفی میکنند:
مشکلات را هرچه بیشتر به سمت «چپ» (early) developer workflow حرکت دهند، **هزینه کمتری دارد.
Timeline developer:
- Design → Implementation → Code Review → Testing → Commit → Canary → Production
Shifting Left:
- مسائل را در Design phase catch کنید: Cheapest
- Code review سریع: Cheap
- در Production: Expensive
چرا؟ چون developer هنوز کد در mind دارد، تغییرش سریع است. اگر ۶ ماه منتظر شوی، دیگر هیچ کسی نمیدانند code چکار میکند.
خلاصه بخش ۴
محور مقیاس:
۱. Superlinear costs are death: اگر هر دفعه سازمان ۱۰ برابر شود، کار تو ۱۰۰ برابر میشود، پایدار نیست.
۲. Centralize expertise: بجای اینکه ۱۰۰ تیم individually کار کنند، centralize expertise و economies of scale از آن بگیر.
۳. Policy > Procedure: درست سیاستهای کارآمد میکند (Beyoncé Rule) تا هر فرایند اضافی نیز مقیاس ندارد.
۴. Shifting left: Catch مشکلات زودتر = کارایی بیشتر.
بخش ۵ فصل ۱ – تریدآفها و تصمیمگیری (Trade-offs and Decision Making)
مقدمه: سه محور را کنار هم بیاور
تا اینجا سه محور را یاد گرفتی: زمان، مقیاس، و Hyrum’s Law. حالا بخش آخر فصل ۱ دربارهی چطور این سه محور را در عمل به کار ببری است تا تصمیمهای درست بگیری.
سوال بنیادی: چرا تصمیمگیری مهم است؟
نویسندهها میگویند:
«اگر بفهمی چطور برنامهنویسی کن، چطور نرمافزار را نگهدار، و چطور با سازمان بزرگ کار کن، تنها چیز باقیمانده تصمیمگیری خوب است.»
یعنی تکنیکهای خوب، بدون تصمیمهای درست، بیفایدهاند. و برعکس، تصمیمهای درست میتونند تکنیکهای ضعیفتر را بهتر کند.
اصل اول: «دلیل برای همه چیز»
درون Google، یک distaste قوی برای «چون من گفتم» وجود دارد. هدف این است:
- هر تصمیمی باید دلیل داشته باشد.
- باید consensus بجای unanimity باشد (نه همه توافق، بلکه بیشتری).
- نه “چون همه انجام میدهند” یا “چون من گفتم.”
انواع هزینهها: «Cost» چه معنی دارد؟
اینجا کلیدی است: «هزینه» فقط پول نیست. نویسندهها ۶ نوع هزینه را نام میبرند:
| نوع هزینه | توضیح |
|---|---|
| Financial | مال (دلار، یورو) |
| Resource | منابع محاسباتی (CPU، RAM، network) |
| Personnel | تلاش مهندس (Engineer hours) |
| Transaction | هزینه انجامدادن تغییر (چقدر طول میکشد؟) |
| Opportunity | هزینه نکردن کار (چه خسارتی داریم؟) |
| Societal | تأثیر جامعهای (کدام users صدمه میخورند؟) |
مثال واقعی: Markers در دفتر
نویسنده یک مثال ساده و معنادار میدهد:
شرکت A (control tight):
- Markers را تحت کنترل قفل میکند
- نتیجه: اکثر markers خشک و بیکار
- هزینه: جلسات شکسته شده، تفکر مختلشده
- Marker هزینه: <$1
Google:
- Closets بازی پر از markers
- نتیجه: brainstorming بدون مانع
- Trade-off: شاید کسی ۲۰ marker ببرد؛ ولی focus بیشتر است
نتیجه: Google گفت: «بهتر است بر روی brainstorming بدون مانع تمرکز کنیم تا Markers را محافظت کنیم.» این یک تریدآف آگاهانه است.
دو نوع تصمیمگیری
نویسنده ۲ سناریو را نام میبرد:
۱. تصمیم قابل اندازهگیری:
جایی که تمام مقادیر measurable یا estimated هستند.
مثال: «اگر ۲ هفته engineer-time بگذارم تا linked-list را به balanced tree تغییر بدهم:
- ۵ GB RAM بیشتر مصرف میکنم
- ۲۰۰۰ CPU ذخیره میکنم
آیا بر سر ارزش برای شام؟»
جواب: بستگی به cost table دارد (چقدر یک CPU = یک GB RAM؟)
۲. تصمیمهای subtle:
جایی که نمیدانی چقدر engineer-time لازم است، یا تأثیرات undefined هستند.
مثال: «هزینهی یک API بدطراحی چقدر است؟»
برای این نوع، نویسنده میگوید: rely on experience, leadership, and precedent.
Input به Decision Making: Conversion Table
اگر تمام شیهای مختلف را میخواهی مقایسه کنی، باید یک conversion table بسازی:
1
۱۰۰ CPU = ۱۰ GB RAM = ۲ Engineer-Weeks = $50,000
با این جدول، هر مهندس میتواند خودبخود analysis انجام دهد:
«اگر ۲ هفته engineer-time خرج کنم و ۲۰۰۰ CPU ذخیره کنم، سود دارم؟»
نتیجه: $۲۰۰۰ (engineer-weeks) vs $۱۰۰۰ (CPU savings) = خیر، خوب نیست.
مثال عملی: Distributed Build System
کتاب یک نقطهی آموزنده میدهد:
قبل: Google engineers محلی میساختند (local build)
- Slow compilation
- Hardware expensive (engineers نیاز به workstations قوی دارند)
بعد: Google یک distributed build system ساخت
- Cost: engineer-time برای توسعه + CPU برای infrastructure
- Benefit: builds خیلی سریعتر
حتی با حساب هزینهی development، سود خیلی بیشتر بود.
اما مشکل: وقتی engineers دیگر محلی slow build احساس نکردند، شروع به اضافه کردن bloated dependencies کردند!
نتیجه: یکی از Jevons Paradox: هرچه efficient بشوی، consumption بیشتر میشود.
Trade-off: Fork vs Share
سؤال نهایی و پیچیده: آیا من باید dependency shared استفاده کنم یا fork کنم؟
| Factor | Fork | Share |
|---|---|---|
| Control | Full control ✅ | Changes dictated by others ❌ |
| Time | Short-lived? OK ✅ | Long-lived? Risk ❌ |
| Scale | Isolated ✅ | Security fix = update all forks ❌ |
| Domain | Domain-specific ✅ | General-purpose ❌ |
جواب: Depends! اگر project short-lived است و fork محدود scope دارد، fork OK است. اما برای data structures، protocols و formats نباید fork کنی.
Decision Making را Revisit کن
مهمترین insight:
«Data تغییر میکند. فرضها غلط ثابت میشوند. تصمیمهای قدیمی امروز غلط باشند.»
Solution: Always Be Deciding – یعنی هر ماه بخش decisions را دوباره ارزیابی کن.
اگر این framing نباشد، teams analysis paralysis میافتند (حل perfect را میخواهند و هرگز تصمیم نمیگیرند).
بهتر: «چند ماه بعد میتونیم این تصمیم را تغییر بدهیم. حالا بریم و ببینیم کجا میرسیم.»
بخش نهایی: برنامهنویسی vs مهندسی نرمافزار
نویسنده یک سوال مهم میپرسد:
«آیا مهندسی نرمافزار بهتر از برنامهنویسی است؟»
جواب: نه، متفاوت است.
| موضوع | برنامهنویسی (کوتاهعمر) | مهندسی نرمافزار (بلندمدت) |
|---|---|---|
| Integration tests | نیازی نیست ✅ | الزامی ❌ |
| Refactoring | نادر | مستمر |
| Semantic Versioning | غیرضروری | Critical |
| Tool flexibility | Choose any | Choose sustainable |
Point: ابزارهای مناسب برای کدام domain متفاوت است. یک اسکریپت ۲ روزه نیازی به integration tests ندارد!
خلاصه فصل ۱ (TL;DR)
نویسنده یک خلاصهی عالی میدهد:
| Point | توضیح |
|---|---|
| Time dimension | مهندسی نرمافزار دربارهی ۳ محور است: زمان، مقیاس، تریدآف |
| ۱۰۰,۰۰۰ times | تفاوت در طول عمر کد بین کوتاه و بلندمدت |
| Sustainability | توانایی واکنش به تغییر، نه اجبار |
| Hyrum’s Law | تمام observable behaviors وابستهاند |
| Scaling | هر تکرار باید linear یا بهتر scale شود |
| Expertise | Centralize knowledge برای scale کردن |
| Data-driven | Decisions بر داده مبتنی، اما نه فقط |
| Revisit regularly | Decisions تغییر مییابند; Always Be Deciding |
خلاصهی کل فصل ۱
فصل ۱ یک سفر بود از:
- تعریف مهندسی نرمافزار
- محور ۱ (Time): Life-span کد و پایداری
- محور ۲ (Scale): Policies، expertise، و Beyoncé Rule
- محور ۳ (Trade-offs): Decision-making، costs، و iteration
Point کلی: مهندسی نرمافزار دربارهی building sustainable systems that can adapt over time with many people working together است. فقط کد نوشتن کافی نیست؛ maintenance، growth، و teamwork کلید است.
فصل ۲: چگونه در تیمها خوب کار کنیم (Culture & Teamwork)
مقدمه: مشکل اول خود تو هستی!
شاید فکر کنی بزرگترین چالش مهندسی نرمافزار، یادگیری الگوریتمها یا فریمورک جدید است، اما این کتاب میگوید: نه! چالش اصلی، تعامل با انسانها است.
برای شروع، نویسنده بیتعارف میگوید:
«اگر میخواهی در تیم موفق باشی، اول باید باگهای خودت را بشناسی.»
همه ما دوست داریم فکر کنیم منطقی هستیم، ولی انسانها پر از احساسات، ترس و غرور هستند. برای اینکه یک مهندس نرمافزار عالی باشی، فقط کد زدن کافی نیست؛ باید یاد بگیری چطور با دیگران کار کنی.
این فصل روی سه ستون اصلی بنا شده: فروتنی (Humility)، احترام (Respect) و اعتماد (Trust) که به اختصار HRT نامیده میشوند.
۱. افسانهی نابغه (The Genius Myth)
ما عاشق قهرمانها هستیم: لینوس توروالدز (خالق لینوکس)، بیل گیتس، استیو جابز. داستانهایی که میشنویم معمولاً اینطور است: «یک نابغه به غار تنهایی میرود، هفتهها کد میزند، و با یک شاهکار بیرون میآید که دنیا را تغییر میدهد.»
واقعیت اما چیز دیگری است: لینوس فقط هستهی اولیه لینوکس را نوشت. عظمت لینوکس نتیجه کار هزاران نفر بود که روی آن کار کردند. مایکل جوردن بدون تیم و مربیاش نمیتوانست قهرمان شود.
چرا این باور خطرناک است؟ چون خیلی از مهندسان (شاید حتی خود من و تو!) تهِ دلمان دوست داریم آن «نابغه» باشیم. فکر میکنیم اگر کدمان را به کسی نشان دهیم و غلط داشته باشد، همه میفهمند ما نابغه نیستیم. پس چه کار میکنیم؟ قایم میشویم!
۲. پنهان کردن کد: یک اشتباه بزرگ (Hiding Considered Harmful)
بسیاری از برنامهنویسها میترسند کد نیمهکارهشان را نشان دهند. میگویند: «صبر کن تمام شود، بعداً نشان میدهم.» یا «نمیخواهم کسی ببیند چقدر باگ دارم.»
چرا کار کردن در خفا (Working in a cave) اشتباه است؟
۱. تشخیص دیرهنگام خطا (Early Detection): فرض کن داری یک دوچرخه طراحی میکنی و هفتهها در گاراژت مخفیانه روی آن کار میکنی. وقتی تمام شد، دوستت میگوید: «اِ، چرا صندلی نگذاشتی؟» اگر زودتر نشان داده بودی، همان روز اول این را میفهمیدی. در نرمافزار هم همینطور است: شاید داری کدی میزنی که اصلا نیاز نیست، یا راه حل خیلی سادهتری دارد.
۲. ریسک “اتوبوس” (The Bus Factor): «ضریب اتوبوس» یعنی: اگر چند نفر از اعضای تیم بروند زیر اتوبوس (یا شرکت را ترک کنند)، پروژه نابود میشود؟ اگر تو تنها کسی هستی که کد را میفهمد، ضریب اتوبوس ۱ است. این یعنی فاجعه. اگر کد را مرتب به اشتراک بگذاری، دیگران هم یاد میگیرند و پروژه امن میشود.
۳. سرعت پیشرفت (Pace of Progress): تنهایی کار کردن معمولاً کندتر است. وقتی گیر میکنی، ممکن است دو روز وقت بگذاری تا مشکلی را حل کنی که همتیمیات در ۵ دقیقه حل میکرد. نترس از اینکه بپرسی.
۳. سه ستون اصلی (HRT)
برای اینکه یک همتیمی عالی باشی، باید این سه ویژگی را در خودت پرورش دهی:
- فروتنی (Humility): باور کن که مرکز جهان نیستی و همهچیز را نمیدانی. تو جایزالخطایی. وقتی اشتباه میکنی، بپذیر. «من اشتباه کردم» جملهی قدرتمندی است.
- احترام (Respect): به همکارانت اهمیت بده. باور کن که آنها هم باهوش و باانگیزه هستند. با آنها مهربان باش، حتی وقتی نظر مخالف داری.
- اعتماد (Trust): باور کن که دیگران هم کارشان را بلدند. به آنها اجازه بده تصمیم بگیرند و کار را پیش ببرند. لازم نیست همه چیز را خودت کنترل کنی.
تمرین عملی: منیت (Ego) را کنار بگذار
یکی از سختترین کارها برای برنامهنویسها، جدا کردن «خودشان» از «کدشان» است. باید مدام به خودت یادآوری کنی:
«من کُدم نیستم.» (You are not your code)
وقتی کسی از کد تو انتقاد میکند (مثلاً در Code Review)، دارد از کد انتقاد میکند، نه از شخصیت تو. اگر این را بپذیری، دیگر از پیدا شدن باگ در کدت ناراحت نمیشوی، بلکه خوشحال میشوی که محصول بهتر شده است.
خلاصه بخش ۱ فصل ۲
- افسانه نابغه را فراموش کن: نرمافزار بزرگ کار تیمی است، نه فردی.
- کد را قایم نکن: زودتر شکست بخور (Fail Fast) تا زودتر یاد بگیری.
- HRT را تمرین کن: فروتنی، احترام، اعتماد.
- تو کُدت نیستی: نقد کد، نقد شخصیت تو نیست.
فصل ۲ – ادامه: هنگامی که کدتان شکست بخورد (Blameless Post-Mortem)
مقدمه: اگر صرف نظر کنید، مشکل بزرگ تر میشود
تا اینجا یاد گرفتی که مهندسی نرمافزار کار تیمی است و نباید کد خود را مخفی کنی. اما حالا سوال مهمی مطرح میشود:
اگر تیم تو کدی شکسته به تولید ببرد، چه اتفاقی میافتد؟
در بسیاری از شرکتها، جواب این سوال ساده است: مقصرگیری شروع میشود. رئیس سر و صدا میکند، ایمیلهای عصبانی فرستاده میشود، و فرد مسئول احساس بدی میکند.
اما Google یک رویکرد متفاوت را انتخاب کرده: فرهنگ Post-Mortem بدون مقصریابی (Blameless Post-Mortem Culture).
اصل اول: شکست یک فرصت یادگیری است
گوگل یک شعار مشهور دارد: «شکست یک گزینه است» (Failure is an option)
اگر تیم شما هرگز ناکام نمیشود، یعنی کافی ریسک نمیگیری. اگر کافی ریسک نمیگیری، یعنی نوآوری نمیکنی. و اگر نوآوری نمیکنی، رقابتهای شما تو را تحت فشار خواهند داد.
Thomas Edison یکی از بهترین نقلهای مشهور را داشت:
«اگر ۱۰,۰۰۰ راه پیدا کنم که چیزی کار نمیکند، ناکام نشدهام؛ فقط ۱۰,۰۰۰ قدم به جلو رفتهام.»
مثال: Google X و Moonshot Projects
در بخش Google X (نام فعلی: X Development)، جایی که روی پروژههای جنگلی مثل خودروهای بدون راننده کار میکنند، شکست عمداً و به اصرار در فرهنگ درج شده است.
در اینجا چنین میدهند:
- تیمها با ایدههای احمقانه و فاقد تجربه میآیند
- تشویق میکنند همتیمیها آن را سریعتر refute کنند
- بهترین ایدهها (که نمیتوان refute کرد) به سراغ تلاش جدی میروند
نتیجه؟ کمتر وقت و منابع در ایدههای بدی تلف میشود.
Post-Mortem چیست؟
Post-Mortem (درحرفی: «بعد از مرگ») یک اجلاس رسمی است که بعد از یک صدمه (مثل down شدن سرور یا باگ بزرگ) برگزار میشود تا یاد بگیریم چه اتفاق افتاد.
در اینجا کلید است: post-mortem درباره جستجوی مقصر نیست، درباره یادگیری است.
چه چیزی یک Post-Mortem خوب دارد؟
طبق کتاب، یک سند «پس از حادثه» (Post-Mortem) خوب باید شامل این بخشهای کلیدی باشد:
| بخش | توضیح |
|---|---|
| خلاصه اجمالی (Brief Summary) | چه اتفاقی افتاد؟ (در حد ۲ تا ۳ جمله کوتاه). |
| خط زمانی (Timeline) | حادثه از کی شروع شد؟ کی کشف شد؟ و کی برطرف شد؟ |
| دلیل اصلی (Root Cause) | ریشه و علت اصلی مشکل چه بود؟ (نه اینکه چه کسی اشتباه کرد). |
| تأثیر (Impact) | چه آسیبی به سیستم یا کاربران وارد شد؟ |
| اقدامات فوری (Immediate Actions) | برای رفع سریع مشکل چه کاری انجام شد؟ |
| اقدامات پیشگیرانه (Action Items) | چه کارهایی باید انجام دهیم تا این اتفاق دیگر تکرار نشود؟ |
| درسهای آموختهشده (Lessons Learned) | چه چیزی یاد گرفتیم که قبلاً نمیدانستیم؟ |
نمونه Post-Mortem خوب در مقابل بد
نکته کلیدی اینجاست که در Post-Mortem نباید دنبال «مقصر» بگردیم، بلکه باید دنبال «ایراد سیستم» باشیم.
❌ Post-Mortem بد (تمرکز بر مقصر):
«حسن یک کوئری (Query) بد روی دیتابیس اجرا کرد و باعث شد کل سیستم پایین بیاید. حسن باید قبل از اجرا، کوئری را با دقت بیشتری بررسی میکرد.»
✅ Post-Mortem خوب (تمرکز بر یادگیری و سیستم):
مشکل: یک کوئری سنگین باعث شد مصرف CPU دیتابیس به ۱۰۰٪ برسد و سرویس از دسترس خارج شود.
دلیل اصلی: ما ابزاری برای پایش (Monitoring) کوئریهای سنگین نداشتیم و در فرآیند «بازبینی کد» (Code Review) هم بررسی پیچیدگی کوئری الزامی نبود.
اقدام پیشگیرانه: ۱. اضافه کردن ابزار مانیتورینگ برای شناسایی خودکار کوئریهای کند. ۲. اصلاح چکلیست Code Review تا بررسی پرفورمنس کوئریها اجباری شود.
میبینید؟ در مدل خوب، اسم «حسن» حذف شده است. چون اگر حسن نبود، ممکن بود شخص دیگری همین اشتباه را بکند. مشکل واقعی «حسن» نبود، بلکه «نبودِ سیستم نظارتی» بود.
چرا فرهنگ «بدون سرزنش» (Blameless) مهم است؟
اگر در تیمتان مدام دنبال مقصر باشید، این اتفاقات میافتد:
- پنهانکاری: مهندسان وقتی اشتباه کنند، آن را قایم میکنند تا توبیخ نشوند. (این خطرناکترین حالت است!)
- ترس از نوآوری: هیچکس جرأت نمیکند ایده جدیدی را امتحان کند، چون میترسد خراب شود و سرزنش شود.
- کاهش روحیه: فضای تیم سمی و پر از استرس میشود.
اما اگر فرهنگ بدون سرزنش داشته باشید:
- شفافیت: مهندسان بدون ترس میگویند: «من اشتباه کردم» و همه سریعتر مشکل را حل میکنند.
- پیشرفت سیستم: به جای تنبیه افراد، سیستمها و ابزارها را قویتر میکنید تا جلوی خطای انسانی را بگیرند.
- یادگیری تیمی: اشتباه یک نفر تبدیل به درس عبرت برای همه میشود.
ارتباط با سه اصل (HRT)
این فرهنگ دقیقاً روی همان سه ستون اصلی فصل ۲ بنا شده است:
- فروتنی (Humility): قبول میکنیم که همه ما (حتی مدیران ارشد) ممکن است اشتباه کنیم.
- احترام (Respect): به کسی که اشتباه کرده احترام میگذاریم و باور داریم که نیت بدی نداشته است.
- اعتماد (Trust): اعتماد داریم که همتیمیهایمان باهوش هستند و از این اشتباه درس میگیرند.
خلاصه این بخش
تا اینجا دو درس مهم از فصل ۲ گرفتیم: ۱. کدتان را پنهان نکنید: کار تیمی یعنی اشتراکگذاری سریع، حتی اگر کار ناقص باشد. ۲. شکست پل پیروزی است (اگر سرزنش نباشد): وقتی سیستم خراب میشود، به جای پیدا کردن «مقصر»، دنبال اصلاح «فرآیند» باشید.
فصل ۲ – بخش آخر: هنر نقد کردن و نقد شنیدن (Code Review & Feedback Culture)
معاملهٔ سختِ نقد و انتقاد
هر کس از انتقاد متنفر است. حتی بهترین برنامهنویسها، وقتی کار شان را برای بررسی مجدد ارائه میدهند، کمی نگران میشوند: «شاید تعداد زیادی اشتباه دارد؟ شاید فکر میکنند من خوب نیستم؟»
مسئلهٔ اصلی این است که در بسیاری از شرکتها، نقد اغلب بیرحمانه و شخصی است. لطفاً به نمونههای زیر توجه کنید:
❌ نقد بد (خصمانه و غیرسازنده)
«مِیخدا، کنترلجریان (Control Flow) این متد کاملاً اشتباه است! همه از پترن xyzzy استفاده میکنند. چرا تو این کار رو نمیکنی؟»
این نوع نقد چه مشکلاتی دارد:
- شخصیسازی: انگار شخص خود غلط است، نه کد
- محکومیت: از کلمهٔ «اشتباه» استفاده میکنی
- فشار: از او میخواهی تغییر کند، بدون جواب
- انزجار ایجاد میکند: فرد بلافاصله دفاعی میشود
✅ نقد خوب (سازنده و احترامآمیز)
«من با کنترلجریان این بخش گیج شدم. آیا استفاده از پترن xyzzy میتواند این قسمت را برای من روشنتر کند؟ شاید کد را هم نگاهداشتن در طول زمان آسانتر کند.»
چرا این بهتر است:
- فروتنی: مسئلهٔ درک من است، نه شما
- پیشنهاد، نه فرمان: «آیا… میتواند» بدتر از «باید»
- توجه به کد: در مورد کد صحبت میکنیم، نه شخصیت
- انتخاب به دست فرد: او میتواند پیشنهاد را قبول یا رد کند
- مشترک: هر دوتان برای بهتری پروژه کار میکنید
تفریق مهم: «تو کدی نیستی»
یکی از مشکلات اساسی برنامهنویسان این است که خود را با کار خود یکی میدانند. اگر کسی کدات را نقد کرد، احساس میکنی که تو شخصاً نقد شدهای.
اما این اشتباه است:
| تصور غلط | واقعیت |
|---|---|
| کد تو = تو | کد تو ≠ تو |
| نقد کد = نقد شخصیت | نقد کد = بهتری شدن |
| اگر کد سوء است، من سوءام | اگر کد سوء است، یاد میگیریم |
کد نوشتن مثل هر مهارتی است—تنیس، نقاشی یا سخنرانی. اگر مربی تنیستت بگوید «سرویسات ضعیف است»، این نمیخواهد بگوید تو نسبت به انسانبودن ناشایست!
نقد گرفتن (از دید گیرنده)
وقتی کسی نقد میکند:
۱. باور کن که نیت خوب دارند:
- آنها (امیدا) میخواهند پروژه بهتر شود
- آنها نمیخواهند تو را دار و دستگیر کنند
- آنها میخواهند از اشتباه ها یاد بگیری
۲. نقد را صفر میز بگیر:
- خود را دفاع نکن
- بپرس: «آیا میتونی مثال بزنی؟»
- فکر کن: «آیا حق دارند؟»
۳. مسالمتآمیز پاسخ بده:
- اگر موافق بودی: «خیلی خوب، درست گفتی!»
- اگر مخالف بودی: «متوجه شدم. البته من فکر میکنم [دلیل بهتر]. آیا میتونی این نقطهٔ نظر رو بررسی کنی؟» (از انگلیسی: PTAL = Please Take Another Look)
نقد دادن (از دید دهنده)
وقتی تو دارای نقد هستی:
۱. زمان صحیح انتخاب کن: دوستتان کار را در تنهایی انجام داده؟ یا در تیم؟ آیا تازه شروع کرد یا تقریباً تمام شد؟
مثال: اگر همکار جدیدی است و نقدسازی در فرهنگ شرکت معمول نیست، قبل از شروع حتی یک نقد، اول بگو چه میخواهی انجام دهی.
۲. قاطعیت را بپرس:
- سؤال کن: «آیا میتونی مثال بدی؟»
- شرح بده: «چرا فکر میکنی این بهتره؟»
- گوش کن: شاید او دلیل خوبی دارد
۳. حفاظت کن: نقد را خصوصی انجام بده، نه علنی. اگر برای تیم درس است، درس بده. اما نقد شخصی را نه!
مثالِ واقعی: داستانِ جو (Joe’s Story)
جو در شرکت جدیدی کار شروع کرد. بعد از یک هفته، شروع به نقد کدِ تیم کرد:
«سلام، متوجه شدم تو از آن الگوی X استفاده کردی. البته میتوانستی از Y هم استفاده کنی…»
اما مشکل:
- جو با بدون آموختن فرهنگ تیم شروع کرد
- فریقش هنوز نقد سازی را برای نقد اصلاح نکرده بود
- تیم احساس کرد: «این یارو داره ما رو نقد میکنه؟»
نتیجه: رئیسش تلفن کرد و گفت: «لطفاً سریعتر نقد نکنید. تیم ناراحت است.»
درس: قبل از نقد دادن، قاعدهٔ بازی را بیاموز. شاید اول در تیم بحث کن: «میتونیم Code Review شروع کنیم؟» و بعدش شروع کن.
خلاصهٔ سه اصل (HRT) در Code Review
| اصل | نقددهنده | نقدگیرنده |
|---|---|---|
| Humility (فروتنی) | «من نمیفهمم» | «من نمیدانستم» |
| Respect (احترام) | «میخواهم کمکت کنم» | «تو نیت خوب داری» |
| Trust (اعتماد) | «تو خوب هستی، فقط این…» | «پذیر، میخواهی بهتر شوی» |
درسِ نهایی این فصل
فصل ۲ کتاب «Software Engineering at Google» درباره سه چیز مهم بود:
۱. بترس نه، بشنو: کار تیمی بهتر از تنهایی است ۲. بیاموز نه، سرزنش کن: شکست فرصت یادگیری است، نه جنایت ۳. نقد کن با عشق: نقد سازنده، نه تخریبکنندهٔ
نکتهٔ پایانی: فریق میل و فقط شماست
به قول نویسندهٔ فصل Brian Fitzpatrick:
«اگر میخواهی موفق باشی، یاد بگیر که با دیگران کار کنی. هر آن چیزی که برنامهنویسی ۱۰۰٪ یاد میگیری، نقش اصلی ندارد. آن چیزی که اهمیت دارد، **نحوهٔ کار کردن با انسانها است.»
فصل ۲ – بخش نهایی: کمونیتی و اشتراک دانش (Knowledge Sharing & Learning Together)
پرسیدن سؤال: درخواست کمک بدون شرم
یکی از مهمترین مهارتهای یک مهندس نرمافزار، خوب پرسیدن سؤال است. اما بسیاری از برنامهنویسها احساس میکنند که اگر سؤال بپرسند، میشود نشانهای بر ضعف و نادانی.
اما این غلط است.
چرا پرسیدن سؤال اهمیت دارد؟
تصور کنید تنهایی با یک مشکل وقتتلفی میکنید. شاید ۲ ساعت یا حتی یک روز بدون پیشرفت. اما اگر به یک همکار بپرسی، شاید در ۵ دقیقه جواب پیدا شود.
ریاضیِ ساده:
- اگر نپرسی: ۸ ساعت تلف
- اگر بپرسی: ۰.۵ ساعت (وقت سؤال) + ۱ ساعت یادگیری = ۱.۵ ساعت
بدتر هم میشود: اگر اول میپرسیدی، همتیمیات هم یاد میگرفتند!
مثال واقعی: داستان بریان
یکی از نویسندگان کتاب، بریان فیتزپاتریک، داستانی را تعریف میکند:
قبلاً در Google یک ابزار برای تبدیل CVS به Subversion نوشت. کارل، دوست و همکار بریان، خیلی خوب CVS را میشناخت.
وقتی بریان و کارل شروع کردند pair programming (برنامهنویسی در دو نفر)، مشکل پیش آمد:
- بریان: یک مهندسِ “پایین به بالا” (Bottom-up) بود—سریع خود را غوطهور میکرد و با آزمایش و خطا کار میکرد.
- کارل: یک مهندسِ “بالا به پایین” (Top-down) بود—میخواست تمام جزئیات و ساختار را اول درک کند.
نتیجه: مشاجره و بحثهای درونکشی!
اما بعداً بریان فهمید: آنها دارای سبکهای متفاوت بودند، نه “درست” و “غلط”.
درس: صبر کن، به دیگری احترام بگذار و یاد بگیر که چطور با سبکهای متفاوت کار کنی.
یادگیری و اشتراکدانش در تیم
برای اینکه یک تیم بسیار خوب باشد، همه باید با هم یادگیری کنند.
۱. گروهچت (Group Chat)
مثل Slack یا Microsoft Teams:
- مزایا: سریع، بیدرخت، همه میتوانند ببینند
- معایب: بیساختار، یافتن پاسخهای قدیمی سخت است
۲. فهرستهای ایمیل (Mailing Lists)
مثل موضوعهای بحث Google Groups:
- مزایا: ساختاردار، قابلجستجو، دائمی
- معایب: کند است، برای سؤالات سریع اسمٹ نیست
۳. سیستمهای سؤالوپاسخ (Q&A Systems)
مثل Stack Overflow یا YAQS در Google:
- مزایا: بهترین پاسخها “up-vote” میشوند، کاملاً سازمانیافته
- معایب: نیاز به وقت برای نوشتن خوب
شاگردی و آموزش (Mentoring & Teaching)
⚠️ حقیقتِ اساسی
هر کس درسهای کچھ را میدانند.
شما نه “خبره” و نه “مبتدی” نیستید—شما در حال حرکت هستید.
همکار جدیدی نیاز به کمک توست. اما یک نفر جدیدتر نیز یک روز از تو یادگیری میکند!
۴ روش برای تدریس و اشتراک دانش:
A. ساعات کاری (Office Hours)
مثل مشاوره دانشگاه: هر هفته ۱ ساعت برای پاسخگویی به سؤالات.
B. سخنرانیهای فنی (Tech Talks)
مثل یک پریزنتیشن نیمهساعتی در مورد موضوع مشخص.
C. کلاسهای یادگیری (Classes)
درس سازمانیافتهتر، اگر موضوع پیچیده است.
D. مستندات (Documentation)
این بخش خیلی مهم است!
اولین بار که چیزی یاد گرفتی، بهترین زمان برای نوشتن است، چون هنوز میدانی کدام قسمتها سخت بود!
درس: بهبود مستندات
در Google، هر مهندس میتواند هر مستند را اصلاح کند—حتی اگر مالک آن نباشد.
اگر خطای کوچک دیدی (مثلاً یک تایپو):
- تصحیح کن
- فائل را ارسال کن (PR)
- در نظری شامل شو
قانون “دختر پیشاهنگ”: راهیابی را تمیزتر از حالتی ترک کن که آن را یافتهای!
احترام و لطف در اشتراک دانش
اصلاح کن: بسیاری از شرکتها تولرانس (یا حتی ستایش!) میکنند “برنامهنویس کند” (Brilliant Jerk)—افرادی که باهوش هستند اما ناخوشایند.
نتیجه؟ تیم روحیه اش پایین میرود.
Google یاد دارد: خبره بودن و لطیف بودن متضاد نیستند.
به قول رهبران Google:
«رهبران بهترِ مردمی اطراف خود را بهتر میکنند. آنها ایمنی روانی تیم را بالا میبرند. مردم جاهل رهبر خوبی نیستند.»
خلاصهٔ فصل ۲ کامل: سه ستون + یک شهر
| عنصر | معنی | در عمل |
|---|---|---|
| Humility (فروتنی) | من همهچیز نمیدانم | سؤال بپرس، مستند بخوان |
| Respect (احترام) | دیگران ارزشمند هستند | آنها را گوش کن، کمک کن، تدریس کن |
| Trust (اعتماد) | دیگران خوب هستند | فرض کن نیت خوبند، تفویض کن |
| Knowledge Sharing | یادگیری مشترک | کمونیتی بساز، مستند نویس، رهنمایی کن |
پیام نهایی فصل ۲
فصل ۲ کتاب «Software Engineering at Google» درباره یک حقیقت ساده است:
«برنامهنویسی درباره نویسندگی است. اما مهندسی نرمافزار درباره کار با انسانها است.»
اگر میخواهی واقعاً موفق باشی: ✅ قایم نکن — کار تیمی کن
✅ شکست را بپذیر — از اشتباهها یاد بگیر
✅ نقد سازنده بده — احترامآمیز باش
✅ نقد گیر — دفاع نکن
✅ پرسیدن و تدریس — کمونیتی بساز
این بود فصل ۲ — یکی از مهمترین فصلهای کتاب. اگر تنها یک فصل از کل کتاب برای مهندسین جدید خواندنی است، این باید همان باشد.
فصل ۳: اشتراک دانش (Knowledge Sharing)
مقدمه: سازمان شما چقدر باهوش است؟
یکی از بزرگترین چالشهای هر سازمان این است: آیا دانش افراد به کل سازمان منتقل میشود؟
اگر در تیم شما ۱۰ مهندس خبره وجود داشته باشد ولی دانش آنها به بقیه منتقل نشود، در واقع شما یک تیم باهوش ندارید؛ بلکه ۱۰ فرد باهوش دارید که جداگانه کار میکنند. هدف فصل ۳ این است که چطور از «افراد باهوش» به «سازمان باهوش» برسیم.
چالش اول: ایمنی روانی (Psychological Safety)
اولین شرط یادگیری، ایمنی روانی است. یعنی اعضای تیم باید احساس کنند که میتوانند بدون ترس از مسخره شدن یا سرزنش شدن، سؤال بپرسند.
- اگر کسی بپرسد «فلان ابزار چطور کار میکند؟» و همکارش بگوید «واقعاً نمیدانی؟! این که خیلی ساده است!»، آن شخص دیگر هرگز سؤال نخواهد پرسید.
- وقتی سؤال پرسیده نشود، دانش منتقل نمیشود.
- نتیجه: هر کس چرخ را برای خودش دوباره اختراع میکند.
چالش دوم: جزایر دانش (Islands of Knowledge)
وقتی دانش به اشتراک گذاشته نمیشود، تیمها مثل جزیرههای جدا از هم عمل میکنند:
- تیم الف با مشکل X روبرو میشود و یک هفته وقت میگذارد تا آن را حل کند.
- تیم ب ماه بعد با همان مشکل X روبرو میشود و آنها هم یک هفته وقت میگذارند.
- اگر تیم الف راهحلش را مستند کرده بود، تیم ب میتوانست در ۱۰ دقیقه مشکل را حل کند.
راهکارها: چهار روش برای اشتراک دانش
۱. پرسش و پاسخ (Q&A)
سادهترین راه، پرسیدن است. اما نکته مهم این است که پاسخ باید در دسترس همه باشد.
- روش بد: ارسال پیام خصوصی به یک متخصص. (فقط یک نفر یاد میگیرد).
- روش خوب: پرسیدن در گروه عمومی (Slack یا Teams) یا یک سامانه پرسش و پاسخ (مثل Stack Overflow داخلی). (دهها نفر یاد میگیرند).
۲. مستندسازی (Documentation)
بسیاری از مهندسان از نوشتن مستندات فراری هستند چون فکر میکنند باید «کامل و بینقص» باشد.
- قانون طلایی گوگل: مستندات باید زنده باشند.
- مالکیت جمعی: هر کسی که یک مستند را میخواند و اشکالی میبیند (حتی یک غلط املایی)، باید همان لحظه آن را اصلاح کند. نباید منتظر «نویسنده اصلی» بماند.
۳. آموزش از طریق کد (Code Review)
کد فقط برای کامپایل شدن نیست؛ کد یک وسیله ارتباطی بین انسانهاست. فرآیند بازبینی کد (Code Review) بهترین فرصت برای آموزش است. وقتی یک مهندس ارشد کد یک تازهکار را نقد میکند، در واقع دارد دانش و تجربیاتش را به او منتقل میکند.
۴. برنامههای آموزشی (Education Programs)
گوگل روشهای خلاقانهای برای آموزش دارد:
Testing on the Toilet (آموزش در سرویس بهداشتی!): مهندسان گوگل برگههای آموزشی یکصفحهای (مثلاً درباره روشهای جدید تستنویسی) را چاپ میکنند و در داخل سرویسهای بهداشتی نصب میکنند. چون آنجا تنها جایی است که افراد وقت خالی دارند و مجبورند چیزی بخوانند! این روش عجیب، بسیار مؤثر بوده است.
Tech Talks (سخنرانیهای فنی): هر کسی میتواند یک ارائه ۳۰ دقیقهای درباره موضوعی که بلد است برگزار کند. این ویدیوها ضبط و آرشیو میشوند.
فرهنگ «خوانایی» (Readability) در گوگل
یکی از جالبترین برنامههای گوگل، مدرک Readability است.
در گوگل، برای اینکه بتوانید کدی را به مخزن اصلی (Repository) ارسال کنید، باید یا خودتان مدرک «خوانایی» آن زبان (مثلاً ++C یا Java) را داشته باشید، یا یک نفر که این مدرک را دارد، کد شما را تأیید کند.
این مدرک چیست؟ این مدرک نشان میدهد که شما نه تنها آن زبان را بلد هستید، بلکه تمام استانداردهای کدنویسی گوگل (Best Practices) را هم رعایت میکنید.
این سیستم باعث میشود که: ۱. کدها یکدست و تمیز باقی بمانند. ۲. افراد در طول فرآیند گرفتن مدرک، کلی نکته جدید یاد بگیرند.
خلاصه فصل ۳
برای اینکه سازمان شما یادگیرنده باشد:
- بترسید اما بپرسید: فضایی بسازید که پرسیدن «نمیدانم» عیب نباشد.
- بنویسید: هر چیزی که یاد میگیرید را جایی (حتی ناقص) یادداشت کنید.
- به اشتراک بگذارید: دانش قدرت نیست؛ اشتراک دانش قدرت است.
- احترام بگذارید: متخصصان واقعی کسانی هستند که دانش خود را با فروتنی به دیگران یاد میدهند، نه کسانی که دیگران را تحقیر میکنند.
فصل ۴: مهندسی برای عدالت (Engineering for Equity)
مقدمه: وقتی فناوری بیطرف نیست
ما اغلب فکر میکنیم کد و ریاضیات «بیطرف» هستند. ۲+۲ همیشه میشود ۴، و کد هم همان کاری را میکند که نوشته شده. اما این فصل یک حقیقت تلخ را به ما یادآوری میکند: فناوری میتواند ناعادلانه باشد.
اگر مهندسان فقط به «اکثریت» کاربران فکر کنند، محصولاتی میسازند که برای اقلیتها کار نمیکنند یا حتی به آنها آسیب میزنند.
سوگیری (Bias) پیشفرض است
اولین درسی که باید بپذیریم این است: «سوگیری پیشفرض است» (Bias is the default).
همه ما سوگیریهای ناخودآگاه (Unconscious Bias) داریم. ما دنیا را از دریچه تجربیات خودمان میبینیم.
- اگر راستدست هستیم، یادمان میرود که قیچیها برای چپدستها سخت هستند.
- اگر بینایی کامل داریم، یادمان میرود که سایتها برای نابینایان غیرقابل استفادهاند.
در نرمافزار هم همینطور است. اگر تیم مهندسی فقط از مردان سفیدپوست تشکیل شده باشد، آنها محصولی میسازند که برای مردان سفیدپوست عالی است، اما ممکن است برای زنان یا رنگینبوستان فاجعه باشد.
مطالعه موردی: فاجعه Google Photos
یکی از تلخترین مثالهای این فصل، اتفاقی است که در سال ۲۰۱۵ برای Google Photos افتاد. هوش مصنوعی گوگل عکسهای دوستان سیاهپوست یکی از کاربران را به اشتباه به عنوان «گوریل» دستهبندی کرد.
چرا این اتفاق افتاد؟ ۱. دادههای ناقص: دادههایی که برای آموزش هوش مصنوعی استفاده شده بود، تنوع نژادی کافی نداشت (اکثر عکسها از سفیدپوستان بود). ۲. تیم یکدست: احتمالاً در تیم مهندسی و تست، افراد سیاهپوست کافی حضور نداشتند تا قبل از انتشار متوجه این مشکل شوند.
این فقط یک «باگ فنی» نبود؛ این یک شکست اخلاقی بود که باعث شرمندگی گوگل شد و اعتماد کاربران را خدشهدار کرد.
چرا باید برای «همه» بسازیم؟
شعار گوگل این است: «برای همه بسازید» (Build for Everyone). اما نویسنده میگوید این شعار کافی نیست. باید بگوییم: «با همه بسازید» (Build WITH Everyone).
اگر میخواهید محصولی عادلانه بسازید:
- تیمهای متنوع استخدام کنید: زن، مرد، سیاهپوست، سفیدپوست، معلول، سالم. تنوع در تیم باعث میشود باگهای فرهنگی و اجتماعی زودتر کشف شوند.
- برای سختترین حالت طراحی کنید: اگر محصولتان را طوری طراحی کنید که برای یک نابینا قابل استفاده باشد، برای یک کاربر عادی هم قابل استفادهتر و تمیزتر خواهد بود (مثلاً دکمههای واضحتر، متنهای خواناتر).
سرعت در برابر عدالت
در سیلیکونولی همیشه شعار این است: «سریع حرکت کن و چیزها را بشکن» (Move fast and break things). اما فصل ۴ میگوید: «اگر چیزهایی که میشکنید، آدمها هستند، سرعتتان را کم کنید.»
گاهی اوقات لازم است انتشار محصول را به تأخیر بیندازیم تا مطمئن شویم:
- آیا این الگوریتم تشخیص چهره روی پوستهای تیره هم کار میکند؟
- آیا این سیستم استخدام خودکار، زنان را به خاطر فاصلههای شغلی (مثلاً مرخصی زایمان) رد نمیکند؟
بهتر است دیرتر منتشر کنید تا اینکه محصولی نژادپرست یا جنسیتزده منتشر کنید.
خلاصه فصل ۴
- کد سیاسی است: هر خط کدی که مینویسید، روی زندگی آدمها تأثیر میگذارد.
- سوگیری وجود دارد: فرض نکنید بیطرف هستید. مدام بپرسید: «این محصول چه کسانی را نادیده گرفته است؟»
- تنوع قدرت است: تیمهای متنوع محصولات قویتری میسازند، نه فقط برای اینکه «کار خوب» کرده باشند، بلکه برای اینکه محصولاتشان برای بازار بزرگتری کار میکند.
- عدالت را اندازه بگیرید: همانطور که پرفورمنس سرور را مانیتور میکنید، عدالت الگوریتمهایتان را هم مانیتور کنید.
فصل ۵: چگونه یک تیم را رهبری کنیم (How to Lead a Team)
مقدمه: کشتی بدون کاپیتان
یک کشتی بدون کاپیتان تنها یک اتاق انتظار شناور است. اگر کسی فرمان را نگیرد و موتور را روشن نکند، کشتی فقط با جریان خواهد رفت. نرمافزار هم دقیقاً همینطور است: اگر رهبری نباشد، گروه از مهندسان فقط وقت تلف میکنند.
این فصل درباره دو نقش رهبری متفاوت در گوگل است:
- مدیر (Manager): رهبر مردم
- رهبر فنی (Tech Lead): رهبر فناوری
دو نقش اصلی رهبری
۱. مدیر مهندسی (Engineering Manager)
مدیر مسئول کارکرد، بهرهوری و خوشحالی هر فرد در تیمش است. همچنین باید اطمینان حاصل کند که نیازهای کسبوکار برآورده شوند.
وظایف اصلی:
- پرورش و توسعه افراد تیم
- حل مشکلات بینفردی
- تعیین اولویتها و منابع
- حفاظت از تیم از سیاست سازمانی
۲. رهبر فنی (Tech Lead)
رهبر فنی مسئول تصمیمات فنی، معماری، و پروژههای پریورتی است. این نفر معمولاً یک مهندس فعال است که علاوه بر رهبری، کد هم مینویسد.
وظایف اصلی:
- معماری و طراحی سیستم
- تقسیمبندی کار بین تیم
- حل مسائل فنی پیچیده
- تعیین سرعت پیشرفت (velocity)
۳. رهبر فنی و مدیر (Tech Lead Manager - TLM)
در تیمهای کوچک یا نوپا، یک نفر هر دو نقش را بازی میکند. این یک نقش بسیار سخت است.
درس کلیدی: تأثیرگذاری بدون اقتدار (Influencing Without Authority)
یکی از مهمترین مهارتهای رهبری این است که افراد را بدون اینکه مستقیماً مسئول آنها باشی، تحت تأثیر قرار دهی.
مثال از گوگل: جف دین (Jeff Dean) یکی از مشهورترین مهندسان گوگل است. او مستقیماً فقط بخش کوچکی از تیم مهندسی گوگل را رهبری میکند، اما تأثیر او بر کل سازمان بسیار بزرگ است. این به خاطر دانش و سخنگویی او است، نه اقتدار رسمی.
یک مثال دیگر: تیم Data Liberation Front (تیمی که برنامه Takeout (خروج داده) را ساخت) با کمتر از شش مهندس، موفق شد که بیش از ۵۰ محصول گوگل را برای صادرات داده تهیه کند! چگونه؟ با این که:
- یک نیاز استراتژیک را شناسایی کردند
- نشان دادند که این با اولویتهای شرکت تطابق دارد
- ابزاری ایجاد کردند که دیگر تیمها به راحتی میتوانستند استفاده کنند
فصل ۶: رهبری در مقیاس بزرگ (Leading at Scale)
وقتی شما رهبر یک تیم میشوید، بعد طبیعی این است که رهبر چند تیم شوید. در این مرحله، شما نامها و تفاصیل فنی کمتر میبینید و بیشتر به استراتژی و راهبری بر مسائل بزرگتر تمرکز میکنید.
سه اصل رهبری در مقیاس بزرگ:
۱. Always Be Deciding (همیشه تصمیم بگیر)
وظیفهٔ شما شناسایی تعادل بین انتخابهای متضاد است. هر تصمیم مهم شامل trade-off است.
مثال: مسئلهٔ سرعت جستجوی گوگل
مسئلهٔ سرعت جستجو را میتوانستند به دو بخش تقسیم کنند:
- علائم سرعت: تسریع کدموجود
- علل سرعت: پیشگیری از مسائل سرعت اصلاً
کسانی که فقط روی تسریع کار میکردند، توانایی خود را هدر میدادند، زیرا هزاران مهندس دیگر به کدهای نتایج جستجو ویژگیهای جدید اضافه میکردند و سرعت بهبودیافتهٔ آنها را منهی میکردند. با داشتن دو تیم که روی دو جنبهٔ متفاوت کار میکردند، مسئله حل شد.
۲. Always Be Leaving (همیشه برای떠ایی آماده باش)
یکی از بزرگترین خطاها برای رهبری این است که خود را غیرقابلجایگزین (Single Point of Failure - SPOF) کنی.
نکتهٔ اساسی: شما باید نه فقط خودتان بلکه رهبریهای جدید را تربیت کنید.
اگر همیشه کارهای مهم را خود انجام دهی:
- ❌ تیمت نمیتواند رشد کند
- ❌ تو یک SPOF میشوی
- ❌ تو خسته و سوخته میشوی
جای اینکه کاری را خود انجام دهی (که تیم سریعتر به انجام برسد)، آن را به دیگری تفویض کن. شاید بیشتر طول بکشد، اما:
- تیمت یاد میگیرد
- تو برای مسائل بزرگتر فارغ میشوی
- سازمان مستقلتر میشود
۳. Always Be Scaling (همیشه مقیاس بده)
سؤال مهمی که باید هر روز از خودت بپرسی:
«چه کاری است که فقط من میتونم انجام دهم؟»
پاسخهای خوب: ✓ دید بلندمدت و استراتژی را تعریف کنی ✓ تیمت را از سیاست سازمانی محافظت کنی ✓ افراد را تشویق کنی ✓ اطمینان حاصل کنی که همه احترام و لطف را رعایت میکنند ✓ با مدیریت بالاتر ارتباط برقرار کنی
جمعبندی
فصل ۵ و ۶ درس این را میدهند:
- رهبری مهارتی است که یاد گرفته میشود، نه شاملدولت
- بهترین رهبران خادم رهبر (Servant Leaders) هستند
- اصلیترین کار شما افراد را تربیت کردن است، نه کد نوشتن
- باید خود را قابلجایگزین کنی، نه غیرقابلجایگزین
فصل ۸: آزمایش نرمافزار (Testing)
مقدمه: بدون تست، فقط امیدواری است
تست نوشتن یعنی: دستی و خودکار بررسی کنیم که کد آنطور که انتظار داریم، کار میکند.
اگر تست ننویسی، تنها میتونی امید بری که کد کار میکند. این روش برای تیمهای بزرگ مرگبار است.
سه نوع تست
۱. Unit Tests (تستهای واحد)
هدف: یک تکه کوچک کد (یک function) را تست کنیم.
مثال:
1
2
3
4
5
6
7
def add(a, b):
return a + b
# Unit Test
assert add(2, 3) == 5
assert add(-1, 1) == 0
assert add(0, 0) == 0
اهمیت: سریع است، اجرا کردن آسان است، شما بلافاصله باگ را میبینید.
۲. Integration Tests (تستهای ادغام)
هدف: چند تکه کد را با هم تست کنیم.
مثال:
1
تست: کاربر login میکند → دادههای کاربری بارگذاری میشود → صفحهٔ اصلی باز میشود
اهمیت: چیزهایی را میگیرد که Unit Tests پنهان میکند.
۳. End-to-End Tests (E2E)
هدف: کل سیستم را مثل یک کاربر واقعی تست کنیم.
مثال:
Selenium script که: باز میکند → typing میکند → click میکند → نتیجه را چک میکند.
موازنهٔ تستها
| نوع | سرعت | هزینهٔ نوشتن | قابلیت اعتماد | تعداد |
|---|---|---|---|---|
| Unit | خیلی سریع | کم | کم شامل | ۷۰% |
| Integration | متوسط | متوسط | متوسط | ۲۰% |
| E2E | خیلی کند | زیاد | خیلی زیاد | ۱۰% |
قاعدهٔ گوگل: تقریباً ۷۰ درصد unit tests، ۲۰ درصد integration، و ۱۰ درصد E2E.
چرا تست نویسی مهم است؟
۱. باگهای دیرتر بگیرید = گرانتر
شما میتونی باگ را ۳ راه بگیری:
- ✅ در توسعه (Unit Test): ۱ دقیقه برای اصلاح
- ⚠️ قبل از انتشار (Integration Test): ۱ ساعت برای اصلاح
- ❌ بعد از انتشار (مشتری): ۱ روز + شرمندگی!
۲. Regression Prevention
«Regression» یعنی: تغییری که بخش درست دیگری را میشکند.
مثال: شما login function را میبندی. Unit test میدهد:
1
2
3
4
5
6
7
8
9
# Old test
def test_login():
assert login("ali", "pass123") == True
# New broken version
def login(username, password):
return False # اوپس! همه چیز شکسته شد
# Test fails immediately!
بدون تست، این یک ماه طول میکشت تا شکار شود.
درس کلیدی: توسعه آزمون-محور (Test-Driven Development - TDD)
روش TDD چیست؟ ۱. اول تست بنویسید: قبل از نوشتن حتی یک خط کد اصلی، تستی بنویسید که شکست بخورد (چون هنوز کدی وجود ندارد). ۲. کد را بنویسید: فقط به اندازهای کد بنویسید که تست پاس شود. ۳. بازآرایی (Refactor) کنید: حالا که کد کار میکند، آن را تمیز و مرتب کنید.
فایدههای TDD:
- تستپذیری از روز اول: وقتی با تست شروع میکنید، مجبورید کدی بنویسید که قابل تست کردن باشد.
- کاهش باگها: تحقیقات نشان میدهد که این روش میتواند تا ۴۰٪ تعداد باگها را کاهش دهد.
- مستندات زنده: تستها به عنوان مستنداتی عمل میکنند که دقیقاً نشان میدهند کد قرار است چه کاری انجام دهد.
فصل ۹: بررسی کد (Code Review)
چرا بررسی کد مهم است؟
بررسی کد فقط برای پیدا کردن غلطها نیست؛ این فرآیند چند هدف مهم دیگر هم دارد:
✅ ۱. صحت کد (Code Correctness): بدیهیترین هدف این است که مطمئن شویم کد درست کار میکند و منطق آن صحیح است. دو جفت چشم همیشه بهتر از یک جفت چشم است.
✅ ۲. خوانایی و درک (Comprehensibility): اگر همکار شما کد را نفهمد، مهندسان آینده هم نخواهند فهمید. اگر بررسیکننده (Reviewer) بپرسد «اینجا چه اتفاقی افتاده؟»، یعنی کد باید سادهتر یا واضحتر نوشته شود.
✅ ۳. اشتراک دانش (Knowledge Sharing): بررسی کد یک کلاس درس دوطرفه است:
- بررسیکننده: با دیدن کدهای جدید یاد میگیرد که همکارش چه تکنیکهایی استفاده کرده است.
- نویسنده: از بازخوردهای بررسیکننده نکات جدیدی یاد میگیرد.
✅ ۴. یکپارچگی (Consistency): مطمئن میشویم که کد جدید با استانداردهای تیم و کدهای قبلی همخوانی دارد. نباید هر کس به سبک خودش کد بزند.
انواع بررسیکنندگان
۱. همکار (Peer Reviewer): این شخص روی جزییات تمرکز دارد: آیا کد خواناست؟ آیا باگ دارد؟ آیا تستها کافی هستند؟
۲. تأییدکننده/مالک (Approver/Owner): این شخص دید وسیعتری دارد: آیا این تغییر با معماری کلی سیستم همخوانی دارد؟ آیا نگهداری این کد در آینده سخت خواهد بود؟
فایلهای OWNERS در گوگل: گوگل از فایلهایی به نام OWNERS استفاده میکند تا مشخص کند چه کسی مسئول کدام بخش از کد است.
1
2
3
4
5
6
/backend/OWNERS:
- alice@google.com
- bob@google.com
/backend/auth/OWNERS:
- charlie@google.com # فقط چارلی مسئول بخش احراز هویت است
فصل ۱۰: مستندات (Documentation)
قانون طلایی: برای خواننده بنویسید، نه برای نویسنده!
مستندات باید برای کسی نوشته شود که امروز به تیم اضافه شده و هیچ چیز نمیداند.
چرا مستندات حیاتی است؟
۱. برای خواننده: بدون مستندات، خواندن کد مثل حل کردن یک پازل سخت است. مستندات به مهندسان جدید کمک میکند سریعتر راه بیفتند. ۲. برای نویسنده: شش ماه بعد، حتی خودتان هم یادتان نمیآید چرا این کد را اینطور نوشتهاید. مستندات حافظهٔ آیندهٔ شماست.
انواع اصلی مستندات:
| نوع | مثال | هدف |
|---|---|---|
| توضیحات کد (Comments) | // This fixes off-by-one error | توضیح میدهد چرا کد اینطور نوشته شده است (نه اینکه چه میکند). |
| README | فایل اصلی پروژه | نقطه شروع: پروژه چیست و چطور نصب میشود. |
| API Docs | مستندات توابع | ورودی و خروجی هر تابع چیست. |
| Design Docs | اسناد معماری | تصمیمات کلان و طراحی سیستم را شرح میدهد. |
نکتهٔ کلیدی: بهینهسازی برای خواننده
اشتباه رایج این است که مستندات را با فرض اینکه خواننده همه چیز را میداند، مینویسیم.
بد:
1
// الگوریتم FizzBuzz را اجرا میکند
خوب:
1
2
3
// این تابع برای مضارب ۳ مقدار "Fizz"، برای مضارب ۵ مقدار "Buzz"
// و برای مضارب هر دو، "FizzBuzz" را برمیگرداند.
// این یک تمرین کلاسیک برنامهنویسی است.
فصل ۱۱: کنترل نسخه (Version Control)
بخش اول: کنترل نسخه، فراتر از «ذخیره کردن»
بسیاری از مهندسان تازهکار تصور میکنند که سیستم کنترل نسخه (مانند Git) صرفاً ابزاری برای «بکآپ گرفتن» از کدهاست تا اگر اشتباهی کردند، بتوانند به عقب برگردند. اما در مهندسی نرمافزار در مقیاس بزرگ، کنترل نسخه نقشی بسیار حیاتیتر دارد: «یگانه منبع حقیقت» (Single Source of Truth).
در یک سازمان بزرگ، کنترل نسخه پاسخگوی سوالات بنیادین است:
- کد فعلی دقیقاً چیست؟
- چه کسی این خط را تغییر داده است؟
- چه زمانی تغییر انجام شده است؟
- چرا این تغییر انجام شده است؟ (از طریق پیامهای Commit و لینک به مستندات).
بدون سیستم کنترل نسخه، تیمها در تاریکی کار میکنند و همکاری مؤثر غیرممکن میشود.
بخش دوم: سیاست «مخزن واحد» (Monorepo)
یکی از متمایزترین و بحثبرانگیزترین تصمیمات فنی گوگل، استفاده از Monorepo است. در حالی که اکثر شرکتها برای هر پروژه یک مخزن جداگانه (مثلاً در GitHub) میسازند، گوگل تقریباً تمام کدهای خود را (میلیاردها خط کد) در یک مخزن عظیم مشترک نگهداری میکند.
چرا گوگل چنین تصمیم دشواری گرفته است؟ دلایل اصلی عبارتند از:
۱. تغییرات اتمی (Atomic Changes)
این بزرگترین مزیت Monorepo است. فرض کنید شما مسئول یک کتابخانهٔ پایه هستید و میخواهید نام یک تابع را تغییر دهید.
- در مدل چند مخزنی: باید ابتدا کتابخانه را تغییر دهید، نسخه جدید منتشر کنید، و سپس تکتکِ صدها پروژهای که از آن استفاده میکنند را جداگانه آپدیت کنید. این فرآیند ممکن است ماهها طول بکشد و در این میان، ناهماهنگیهای زیادی پیش بیاید.
- در مدل Monorepo: شما میتوانید در یک کامیت واحد، هم تابع را در کتابخانه تغییر دهید و هم تمام فراخوانیهای آن تابع را در کل شرکت اصلاح کنید. یا همه چیز با هم کار میکند، یا هیچ چیز کامیت نمیشود.
۲. اشتراک کد و شفافیت
وقتی همه کدها در یک جا هستند، موانع اشتراکگذاری برداشته میشود. مهندسان تیم جستجو میتوانند به راحتی کدهای تیم یوتیوب را ببینند (با رعایت سطوح دسترسی امنیتی) و از نحوهٔ حل مسائل توسط آنها الهام بگیرند. این موضوع از «دوبارهکاری» جلوگیری میکند.
۳. وابستگیهای سادهتر
در مدل Monorepo، همه از آخرین نسخهٔ کد استفاده میکنند. نیازی نیست نگران باشید که «پروژه الف» از نسخه ۱.۰ کتابخانه استفاده میکند و «پروژه ب» از نسخه ۲.۰. همه روی یک نسخه واحد (Head) کار میکنند.
بخش سوم: استراتژی شاخهگذاری (Branching Strategy)
گوگل از مدلی به نام Trunk-Based Development استفاده میکند.
- شاخهٔ اصلی (Trunk/Main): همهٔ توسعهدهندگان کدهای خود را مستقیماً به شاخه اصلی ارسال میکنند (البته پس از عبور از Code Review و تستها).
- پرهیز از شاخههای طولانیمدت (Long-Lived Branches): گوگل به شدت مهندسان را از داشتن شاخههایی که هفتهها یا ماهها از شاخه اصلی جدا میمانند، منع میکند.
چرا؟ وقتی یک شاخه برای مدت طولانی از بدنه اصلی جدا میشود، ادغام (Merge) کردن دوبارهٔ آن بسیار دردناک و پرخطر میشود (Merge Hell). با ادغامهای کوچک و مداوم، تعارضها (Conflicts) زودتر شناسایی و حل میشوند.
جمعبندی فصل ۱۱
پیام اصلی این فصل این است که سیستم کنترل نسخه، ستون فقرات همکاری فنی است. انتخاب ابزار و استراتژی مناسب (مانند استفاده از Monorepo و Trunk-Based Development)، فرهنگ مهندسی را شکل میدهد:
- تشویق به تغییرات گسترده و بهبود مستمر (Refactoring).
- شفافیت و دسترسی همگانی به دانش.
- کاهش پیچیدگیهای ناشی از مدیریت نسخههای متعدد.
فصل ۱۲: سیستمهای ساخت (Build Systems)
مقدمه: چرا دکمه «Run» کافی نیست؟
در پروژههای دانشجویی یا کوچک، اغلب کافی است در محیط برنامهنویسی (IDE) دکمه Run را بزنید. اما در مقیاس صنعتی، تبدیل کد منبع به محصول نهایی (Build) فرآیندی بسیار پیچیده است. سیستم ساخت، مسئولیت کامپایل کدها، اجرای تستها، بستهبندی فایلها (Packaging) و آمادهسازی برای انتشار را بر عهده دارد.
چالش: وقتی اسکریپتها شکست میخورند
بسیاری از تیمها کار را با نوشتن اسکریپتهای ساده (مثل Shell یا Makefile) شروع میکنند:
1
2
gcc main.c -o app
cp ./assets ./dist
اما با رشد پروژه، این روش به بنبست میرسد:
- کند میشود (چون هر بار همه چیز را از اول میسازد).
- پیچیده میشود (مدیریت صدها فایل با اسکریپت غیرممکن است).
- وابسته به ماشین میشود (روی سیستم شما کار میکند، اما روی سرور خیر).
راهکار گوگل: سیستمهای ساخت مبتنی بر «محصول» (Artifact-Based)
گوگل از سیستمی به نام Blaze (که نسخه متنباز آن Bazel است) استفاده میکند. تفاوت این سیستم با روشهای قدیمی (Task-Based) این است که به جای اینکه بگویید «چکار کن» (مثلاً: اول این را کامپایل کن، بعد آن را کپی کن)، شما تعریف میکنید «چه چیزی میخواهید» و سیستم خودش روش رسیدن به آن را پیدا میکند.
ویژگیهای حیاتی یک سیستم ساخت مدرن
یک سیستم ساخت خوب در گوگل باید چهار ویژگی کلیدی داشته باشد:
۱. سرعت (Speed)
سیستم باید آنقدر هوشمند باشد که کارهای تکراری انجام ندهد. اگر شما فقط یک فایل کوچک را تغییر دادید، نباید کل پروژه دوباره کامپایل شود (Incremental Build).
۲. صحت و درستی (Correctness)
خروجی سیستم باید همیشه قابل اعتماد باشد. نباید وضعیتی پیش بیاید که مهندس بگوید: «کد درست است، اما بیلد خراب شده، بگذار یک بار clean کنم شاید درست شود!» سیستم باید دقیقاً بداند چه فایلهایی تغییر کردهاند و چه چیزی باید بهروز شود.
۳. ایزوله بودن (Hermeticity)
این یکی از مهمترین اصول در گوگل است. فرآیند ساخت نباید هیچ دسترسیای به دنیای بیرون داشته باشد که ما از آن خبر نداریم.
- مثال: بیلد نباید بتواند خودسرانه از اینترنت چیزی دانلود کند.
- نتیجه: بیلد روی کامپیوتر من، کامپیوتر شما و سرور بیلد، دقیقاً یک خروجی یکسان (بیتبهبیت) تولید میکند.
۴. مقیاسپذیری (Scalability)
سیستم باید بتواند پروژههای عظیم با میلیونها خط کد را مدیریت کند و در صورت نیاز، فرآیند کامپایل را بین صدها سرور توزیع کند تا سرعت افزایش یابد.
فصل ۱۳: مدیریت وابستگیها (Dependency Management)
مقدمه: کابوس وابستگیها
امروزه هیچکس نرمافزار را از صفر مطلق نمینویسد. ما روی شانههای غولها میایستیم و از هزاران کتابخانه آماده (Open Source) استفاده میکنیم. اما مدیریت این کتابخانهها میتواند به بزرگترین کابوس یک تیم تبدیل شود.
مشکل اصلی: شبکه پیچیده وابستگیها
فرض کنید شما از کتابخانه A استفاده میکنید.
- کتابخانه
Aخودش به کتابخانهBوابسته است. - کتابخانه
Bبه نسخه خاصی از کتابخانهCنیاز دارد.
حالا اگر شما بخواهید کتابخانه D را هم اضافه کنید که آن هم به کتابخانه C نیاز دارد اما به نسخهای متفاوت، چه اتفاقی میافتد؟ به این مشکل «وابستگی لوزیشکل» (Diamond Dependency) میگویند که اغلب باعث شکستن کل پروژه میشود.
راهکار گوگل: قانون تکنسخهای (The One Version Rule)
گوگل برای حل این هرجومرج، قانونی سختگیرانه اما نجاتبخش دارد:
«در کل مخزن کدهای گوگل، از هر کتابخانه خارجی (مثل Log4j، Guava، Openssl) باید دقیقاً و تنها یک نسخه وجود داشته باشد.»
پیامدهای این قانون:
۱. حذف تعارضها: چون همهٔ تیمهای گوگل (از جستجو گرفته تا جیمیل و مپس) موظفاند از یک نسخهٔ واحد از هر کتابخانه استفاده کنند، هرگز مشکل تداخل نسخهها پیش نمیآید. اگر دو قطعه کد در گوگل کامپایل شوند، قطعاً با هم سازگارند.
۲. بهروزرسانی دشوار اما ایمن: وقتی میخواهید نسخهٔ یک کتابخانه را ارتقا دهید (مثلاً برای رفع یک باگ امنیتی)، نمیتوانید فقط پروژه خودتان را آپدیت کنید. باید آن کتابخانه را در کل مخزن گوگل آپدیت کنید. این یعنی باید مطمئن شوید که کد هیچکس در شرکت با این آپدیت خراب نمیشود. گوگل تیمهایی دارد که وظیفهشان انجام این آپدیتهای سیستمی و وسیع است.
۳. تمرکز بر «پایداری» (Stability): این روش باعث میشود مهندسان قبل از اضافه کردن یک کتابخانه جدید، خوب فکر کنند. چون اضافه کردن یک وابستگی جدید، تعهدی برای نگهداری طولانیمدت آن در کل سازمان ایجاد میکند.
جمعبندی این دو فصل
فصلهای ۱۲ و ۱۳ نشان میدهند که مهندسی نرمافزار در مقیاس بالا، بیشتر درباره مدیریت پیچیدگی است تا صرفاً نوشتن الگوریتم.
- سیستم ساخت: تضمین میکند که فرآیند تولید نرمافزار سریع، قابلتکرار و مستقل از محیط باشد.
- مدیریت وابستگی: با اعمال محدودیتهای سختگیرانه (مثل قانون تکنسخهای)، از بروز آشوب در پروژههایی که هزاران قطعه متحرک دارند، جلوگیری میکند.
فصل ۱۴: تستهای بزرگ (Larger Testing)
مقدمه: چرا تستهای کوچک کافی نیستند؟
در فصلهای قبل گفتیم که تستهای واحد (Unit Tests) عالی هستند چون سریع و دقیقاند. اما یک مشکل بزرگ دارند: آنها قطعات را به صورت جداگانه چک میکنند. ممکن است «موتور» سالم باشد و «چرخها» هم سالم باشند، اما وقتی آنها را به هم وصل میکنید، ماشین حرکت نکند! تستهای بزرگ (مانند Integration Tests و End-to-End Tests) برای بررسی همین موضوع هستند: آیا قطعات با هم به درستی کار میکنند؟[1]
چالشهای تستهای بزرگ
نوشتن و نگهداری این تستها بسیار سختتر از تستهای واحد است:
۱. کندی: اجرای یک تست کامل (مثلاً باز کردن مرورگر، لاگین کردن و خرید یک محصول) ممکن است چند دقیقه طول بکشد. ۲. ناپایداری (Flakiness): این بزرگترین دشمن تستهای بزرگ است. تستی که امروز پاس میشود و فردا بدون هیچ دلیل مشخصی (شاید به خاطر کندی شبکه) فیل میشود، اعتماد مهندسان را از بین میبرد. ۳. دشواری دیباگ: وقتی یک تست بزرگ شکست میخورد، پیدا کردن اینکه دقیقاً کدام بخش از سیستم مقصر است، مثل پیدا کردن سوزن در انبار کاه است.
استراتژی گوگل
گوگل با وجود این مشکلات، همچنان از تستهای بزرگ استفاده میکند اما با احتیاط:
- تعداد کم: طبق «هرم تست»، تعداد تستهای بزرگ باید بسیار کمتر از تستهای واحد باشد.
- محیط ایزوله: تلاش میشود تستها در محیطی اجرا شوند که تا حد امکان شبیه به واقعیت (Production) باشد اما از اینترنت واقعی قطع باشد تا پایداری بالا برود.
فصل ۱۵: بازنشستگی (Deprecation)
مقدمه: کد هم پیر میشود
«کد» مثل شیر فاسدشدنی است، نه مثل شراب که با گذشت زمان بهتر شود. سیستمها، کتابخانهها و APIهای قدیمی باید روزی کنار بروند تا جای خود را به نسخههای جدیدتر، امنتر و سریعتر بدهند. به این فرآیند Deprecation میگویند.[1]
چرا بازنشستگی سخت است؟
مهندسان عاشق ساختن سیستمهای جدید هستند، اما هیچکس دوست ندارد سیستمهای قدیمی را خاموش کند.
- چسبندگی: کاربران به سیستم قدیمی عادت کردهاند و تغییر برایشان هزینه دارد.
- قانون هایروم (Hyrum’s Law): حتی اگر بگویید «این سیستم قدیمی است»، باز هم عدهای از باگهای آن به عنوان ویژگی استفاده میکنند!
راهکار گوگل: بازنشستگی اجباری نیست، «مدیریتشده» است
گوگل یاد گرفته است که نمیتواند فقط بگوید «نسخه ۱ را پاک کنید».
- هشدارهای زودهنگام: ماهها یا سالها قبل از خاموش کردن، به کاربران هشدار داده میشود.
- انجام کار برای کاربران: به جای اینکه به ۱۰۰ تیم بگوییم «کدتان را آپدیت کنید»، تیمی متخصص میسازیم که ابزارهایی بسازد تا این مهاجرت را خودکار انجام دهد.
- فلسفه: «ما سیستمها را نمیکشیم، بلکه آنها را به سمت نسخه بهتر هدایت میکنیم.»
فصل ۱۶: جستوجوی کد (Code Search)
مقدمه: گوگل برای کدها هم گوگل دارد
با وجود میلیاردها خط کد در مخزن یکپارچه (Monorepo)، هیچ مهندسی نمیتواند همه چیز را بداند. وقتی میخواهید بدانید «کلاس User کجا تعریف شده؟» یا «چه کسانی از تابع Login استفاده میکنند؟»، grep کردن ساده دیگر جواب نمیدهد.[1]
ابزار Code Search
گوگل ابزاری داخلی به نام Code Search دارد (شبیه به موتور جستجوی گوگل اما برای کد) که ویژگیهای حیاتی دارد:
۱. سرعت فوقالعاده: جستجو در میلیاردها خط کد در کسری از ثانیه انجام میشود. ۲. درک معنایی (Semantic Understanding): این ابزار میفهمد که وقتی دنبال String میگردید، منظور شما نوع داده است، نه کلمه “String” در کامنتها. ۳. لینکدهی متقابل (Cross-Referencing): با کلیک روی هر تابع، میتوانید ببینید کجا تعریف شده و کجاها استفاده شده است.
چرا این ابزار مهم است؟
این ابزار فقط برای «پیدا کردن» نیست؛ بلکه ابزاری برای یادگیری است. مهندسان تازهکار با خواندن کدهای دیگران یاد میگیرند چطور کد خوب بنویسند. این ابزار باعث میشود کل مخزن کد گوگل مثل یک کتاب آموزشی بزرگ و زنده عمل کند.
جمعبندی بخش
در این سه فصل دیدیم که:
- تستهای بزرگ ضروریاند اما پرهزینه؛ پس باید هوشمندانه استفاده شوند.
- بازنشستگی سیستمها یک هنر اجتماعی-فنی است و نیاز به همدلی با کاربران دارد.
- جستوجوی کد کلیدِ بهرهوری در مقیاس بزرگ است و مانع از اختراع دوباره چرخ میشود.
فصل ۱۷: جستوجوی کد (Code Search)
مقدمه: نقشهای برای گنجینه کد
در فصل قبل اشارهای کوتاه به جستوجوی کد داشتیم، اما این فصل به عمق موضوع میپردازد. وقتی سازمانی مثل گوگل با میلیاردها خط کد در یک مخزن واحد (Monorepo) کار میکند، ابزارهای عادی جستوجو (مثل grep یا جستوجوی ساده IDE) به هیچ وجه پاسخگو نیستند.[1]
چرا به ابزاری خاص نیاز داریم؟ ۱. حجم عظیم داده: جستوجوی متنی ساده در میلیاردها خط کد ساعتها زمان میبرد. ۲. ابهام در نامگذاری: اگر دنبال کلمه User بگردید، میلیونها نتیجه بیربط (در کامنتها، رشتهها و نام متغیرها) پیدا میکنید. شما نیاز دارید بدانید کلاس User دقیقاً کجاست.
ابزار Code Search گوگل
گوگل ابزاری تحت وب به نام Code Search (یا CS) ساخته است که نقشی حیاتی در زندگی روزمره هر مهندس گوگل دارد. این ابزار ترکیبی از موتور جستجوی گوگل و یک IDE قدرتمند است.
ویژگیهای کلیدی:
✅ ۱. جستوجوی معنایی (Semantic Search): این ابزار کد را فقط به صورت متن نمیبیند؛ بلکه آن را «درک» میکند. با استفاده از ابزار تحلیل کد Kythe، این سیستم گرافی از تمام کدها میسازد.
- وقتی روی تابعی کلیک میکنید، میتوانید دقیقاً ببینید کجا تعریف شده (Definition) و در چه جاهایی صدا زده شده است (References).
✅ ۲. سرعت باورنکردنی: با وجود حجم عظیم کد، نتایج جستوجو تقریباً آنی (زیر ۱۰۰ میلیثانیه) نمایش داده میشوند. این سرعت باعث میشود مهندسان برای کوچکترین سوالات هم از آن استفاده کنند.
✅ ۳. لینکدهی به تاریخچه: با یک کلیک میتوانید ببینید چه کسی، چه زمانی و چرا (لینک به کامیت و کد ریویو) این خط را تغییر داده است. این ویژگی برای باستانشناسی کد (Code Archaeology) ضروری است.
فصل ۱۸: سیستمهای ساخت (Build Systems)
مقدمه: بازل (Bazel)، ستون فقرات تولید
در فصل ۱۲ با مفاهیم سیستم ساخت آشنا شدیم، اما فصل ۱۸ به جزئیات فنی ابزار Bazel (نسخه متنباز سیستم داخلی گوگل یعنی Blaze) میپردازد.
فلسفه Bazel: بیلد به عنوان تابع
Bazel بیلد را به عنوان یک تابع ریاضی خالص میبیند: Build(Inputs) = Outputs
اگر ورودیها دقیقاً یکسان باشند، خروجی باید دقیقاً یکسان باشد. هیچ متغیر پنهانی (مثل ساعت سیستم یا فایلهای موقت کاربر) نباید روی خروجی اثر بگذارد.
فایلهای BUILD
در Bazel، هر دایرکتوری دارای یک فایل متنی ساده به نام BUILD است که توصیف میکند چه چیزی در آنجا ساخته میشود.
مثال:
1
2
3
4
5
6
7
8
9
# فایل BUILD
java_binary(
name = "MyServer",
srcs = ["MyServer.java"],
deps = [
"//common:util_library", # وابستگی به یک کتابخانه دیگر
"@maven//:com_google_guava", # وابستگی خارجی
],
)
این فایلها به صراحت میگویند که MyServer به چه چیزهایی نیاز دارد. هیچ وابستگی پنهانی وجود ندارد.
گراف وابستگی (Dependency Graph)
Bazel با خواندن این فایلها، یک گراف عظیم از تمام وابستگیها میسازد.
- اگر فایل
Util.javaدر کتابخانه مشترک تغییر کند، Bazel دقیقاً میداند کدام بخشهای سیستم (و فقط همان بخشها) باید دوباره کامپایل و تست شوند. - این ویژگی کلید سرعت در مقیاس بالاست.
فصل ۱۹: ابزار نقد و بررسی (Critique)
مقدمه: ابزاری برای فرهنگ نقد
فصل ۱۹ به معرفی ابزار Critique میپردازد؛ ابزاری که گوگل برای فرآیند بررسی کد (Code Review) ساخته است. این ابزار فقط یک رابط کاربری نیست، بلکه تجسمی از فرهنگ مهندسی گوگل است.
ویژگیهای متمایز Critique
۱. تمرکز بر تغییرات (Diff-Centric): همه چیز حول محور «تغییرات» میچرخد. ابزار به زیبایی نشان میدهد چه چیزی حذف شده و چه چیزی اضافه شده است.
۲. کامنتهای دقیق: بررسیکننده میتواند روی هر خط کد کامنت بگذارد. این کامنتها میتوانند شامل پیشنهاد کد (Suggested Fix) باشند که نویسنده با یک کلیک میتواند آن را اعمال کند.
۳. ادغام با آنالیزورهای استاتیک: قبل از اینکه انسانها کد را ببینند، رباتها (Static Analyzers) آن را بررسی میکنند. اگر کد شما استانداردهای فرمتدهی را رعایت نکرده باشد، ربات در همان ابزار Critique به شما تذکر میدهد (و اغلب خودش آن را اصلاح میکند).
۴. تاریخچه تغییرات: Critique تمام نسخههای (Snapshots) یک تغییر را نگه میدارد. اگر نویسنده کد را اصلاح کرد، بررسیکننده میتواند ببیند «از دفعه قبلی که من دیدم تا الان چه چیزی تغییر کرده است؟» (Delta between patches).
جمعبندی این سه فصل
این سه فصل نشاندهنده مثلث طلایی ابزارهای گوگل هستند:
- Code Search: برای خواندن و فهمیدن کد.
- Bazel: برای ساختن و تست کردن کد.
- Critique: برای بررسی و ارتقای کیفیت کد.
این ابزارها طوری طراحی شدهاند که با مقیاس عظیم (میلیاردها خط کد و دهها هزار مهندس) کار کنند و در عین حال سرعت و چابکی را حفظ کنند.
فصل ۲۰: آنالیز استاتیک (Static Analysis)
مقدمه: رباتهایی که باگها را پیدا میکنند
آنالیز استاتیک به معنای بررسی کد بدون اجرای آن است. بسیاری از کامپایلرها (مثل کامپایلر جاوا یا C++) اخطارهایی (Warnings) میدهند که نوعی آنالیز استاتیک محسوب میشوند. اما گوگل یک گام فراتر رفته است.[1]
ابزار Tricorder
گوگل سیستمی به نام Tricorder دارد که در فرآیند بررسی کد (Code Review) ادغام شده است. هر بار که مهندسی کدی را تغییر میدهد، Tricorder به صورت خودکار اجرا میشود و دهها ابزار آنالیز مختلف را روی کد جدید اجرا میکند.
فلسفه Tricorder:
۱. عدم مزاحمت (No False Positives): اگر ابزار آنالیز مدام هشدارهای غلط بدهد، مهندسان آن را نادیده میگیرند. در گوگل، قانون این است: اگر ابزاری نمیتواند زیر ۱۰٪ خطای مثبت کاذب داشته باشد، نباید در Tricorder فعال شود.
۲. پیشنهاد اصلاح (Suggested Fixes): Tricorder فقط نمیگوید «اینجا اشتباه است»؛ بلکه میگوید «اینجا اشتباه است و این هم کد درست. دکمه را بزن تا درست شود.» این ویژگی باعث میشود مهندسان عاشق استفاده از آن باشند، چون کارشان را راحت میکند.
۳. تمرکز بر تغییرات: Tricorder فقط خطهایی را بررسی میکند که در این تغییر (CL) عوض شدهاند. این باعث میشود مهندس با هزاران اخطار مربوط به کدهای قدیمی (Legacy Code) بمباران نشود.
فصل ۲۱: مدیریت وابستگیها (Dependency Management)
مقدمه: جهنم وابستگیها (Dependency Hell)
در فصل ۱۳ با قانون «تکنسخهای» آشنا شدیم. فصل ۲۱ به جزئیات فنی و چالشهای این قانون میپردازد.
چالش وارد کردن کد متنباز (Open Source)
گوگل از هزاران پروژه متنباز استفاده میکند. اما چگونه؟ ۱. کپی کردن (Vendoring): گوگل کد پروژه متنباز را دانلود کرده و در مخزن خودش (third_party/) کپی میکند. ۲. قانون تغییر ندادن: مهندسان حق ندارند این کدهای کپیشده را تغییر دهند (مگر برای باگهای حیاتی)، چون آپدیت کردن نسخههای بعدی بسیار سخت میشود.
معنای نسخه (Semantic Versioning) در گوگل
در دنیای بیرون، SemVer (مثل v2.0.1) رایج است.
- v2: تغییرات ناسازگار
- 0: ویژگی جدید
- 1: رفع باگ
اما در گوگل، چون همه چیز در یک مخزن واحد است و همه از آخرین نسخه (Head) استفاده میکنند، مفهوم شماره نسخه سنتی رنگ میبازد.
- شعار گوگل: “Live at Head” (زندگی در نوک پیکان).
- همه تیمها موظفاند همیشه با آخرین تغییرات سایر تیمها سازگار باشند. اگر تغییری باعث شکستن کد تیم دیگری شود، مسئولیت با کسی است که تغییر را ایجاد کرده است (نه تیمی که کدش شکسته است).
فصل ۲۲: تغییرات در مقیاس بزرگ (Large-Scale Changes - LSC)
مقدمه: وقتی باید ۱۰۰،۰۰۰ فایل را تغییر دهید
فرض کنید میخواهید یک تابع پرکاربرد را که ناامن تشخیص داده شده، حذف کنید. این تابع در ۵۰،۰۰۰ فایل مختلف استفاده شده است. چه میکنید؟ هیچ مهندسی نمیتواند دستی این کار را انجام دهد.
فرآیند LSC در گوگل
گوگل فرآیندی کاملاً مکانیزه برای این کار دارد:
۱. تولید خودکار تغییرات: ابزاری (مثل Rosie) با استفاده از دستورات Refactoring، هزاران تغییر کوچک (CL) تولید میکند.
۲. تقسیم و توزیع: این ۵۰،۰۰۰ تغییر به دستههای کوچک تقسیم شده و برای صاحبان (Owners) هر پروژه ارسال میشود.
۳. تستهای سراسری: سیستم تست سراسری گوگل (TAP) مطمئن میشود که این تغییرات باعث شکستن بیلد نمیشوند.
۴. ادغام: صاحبان پروژهها تغییرات را بررسی و تایید میکنند. چون تغییرات توسط ماشین تولید شده و تست شدهاند، بررسی آنها سریع است.
این سیستم به گوگل اجازه میدهد مهاجرتهای عظیمی (مثل تغییر نسخه جاوا یا C++) را در عرض چند هفته انجام دهد، کاری که در شرکتهای دیگر ممکن است سالها طول بکشد.
جمعبندی این سه فصل
- Tricorder: رباتی که در حین کدنویسی، اشتباهات را گوشزد و اصلاح میکند.
- مدیریت وابستگی: استراتژی «زندگی در لبه تکنولوژی» و حذف نسخههای قدیمی.
- LSC: قدرت اتوماسیون برای ایجاد تغییرات در مقیاس کل شرکت.
فصل ۲۳: یکپارچهسازی مداوم (Continuous Integration - CI)
مقدمه: سیستم ایمنی بدن نرمافزار
اگر سیستم کنترل نسخه (فصل ۱۱) را «حافظه» سازمان بدانیم، سیستم CI «سیستم ایمنی» آن است. CI سیستمی است که به طور مداوم کدهای جدید را با کدهای قدیمی ترکیب میکند و تستها را اجرا میکند تا مطمئن شود چیزی خراب نشده است.[1]
دو نوع CI در گوگل
گوگل دو نوع مختلف CI دارد که مکمل یکدیگرند:
۱. CI پیش از ارسال (Pre-submit): قبل از اینکه کد شما وارد مخزن اصلی شود، سیستم TAP (Test Automation Platform) تستهای مرتبط را اجرا میکند. اگر تستی قرمز شود، کد شما رد میشود. این خط مقدم دفاع است.
۲. CI پس از ارسال (Post-submit): چون اجرای همه تستها قبل از هر کامیت ممکن نیست (چون خیلی طول میکشد)، برخی تستهای سنگینتر بعد از ورود کد اجرا میشوند. اگر این تستها شکست بخورند، سیستم به طور خودکار به نویسنده ایمیل میزند و باگی ثبت میکند (فرآیند Culprit Finder).
درس مهم: تستهای غیرقابل اعتماد (Flaky Tests)
بزرگترین دشمن CI، تستهایی هستند که گاهی پاس میشوند و گاهی فیل. گوگل سیاست سختگیرانهای دارد: «اگر تستی Flaky باشد، بهتر است اصلاً وجود نداشته باشد.» تستهای Flaky از سیستم CI اخراج میشوند تا اعتماد مهندسان به سیستم از بین نرود.
فصل ۲۴: تحویل مداوم (Continuous Delivery - CD)
مقدمه: از کد تا کاربر
CI کد را تست میکند، اما CD آن را به دست کاربر میرساند. در گذشته، نرمافزارها هر چند ماه یکبار در قالب «نسخه جدید» منتشر میشدند. اما در گوگل، سرویسهایی مثل جستوجو یا جیمیل ممکن است روزانه چندین بار آپدیت شوند.[1]
چالش: انتشار امن در مقیاس جهانی
چگونه گوگل کدی را روی هزاران سرور در سراسر جهان آپدیت میکند بدون اینکه سرویس قطع شود؟
استراتژی انتشار مرحلهای (Staged Rollout): ۱. Canary: ابتدا نسخه جدید فقط روی چند سرور کوچک اجرا میشود (مثل قناری در معدن زغالسنگ). اگر مشکلی پیش بیاید، فقط درصد بسیار کمی از کاربران متوجه میشوند. ۲. Staging: سپس به یک دیتاسنتر کامل گسترش مییابد. ۳. Global: در نهایت، به آرامی در کل جهان پخش میشود.
اگر در هر مرحله خطایی (مثل افزایش Latency یا Crash) دیده شود، سیستم به طور خودکار به نسخه قبل برمیگردد (Rollback).
فصل ۲۵: محاسبات به عنوان سرویس (Compute as a Service)
مقدمه: کامپیوتر من کجاست؟
در گوگل، مهندسان به ندرت میدانند کدشان روی کدام سرور فیزیکی اجرا میشود. آنها با یک «ابرکامپیوتر واحد» طرف هستند.
Borg: سیستمعامل دیتاسنتر
سیستم مدیریت کلاستر گوگل، Borg نام دارد (که پدر معنوی Kubernetes است). شما به Borg میگویید: «من ۵۰ گیگابایت رم و ۲۰ هسته CPU میخواهم تا این سرویس را اجرا کنم.» Borg خودش میگردد و در میان هزاران سرور، جای خالی پیدا میکند. اگر سروری خراب شود، Borg به طور خودکار سرویس شما را روی سرور دیگری زنده میکند.
مزیت بزرگ: این سیستم باعث میشود مهندسان نگران سختافزار نباشند و فقط روی منطق برنامه تمرکز کنند.
فصل ۲۶: نتیجهگیری (Conclusion)
مهندسی نرمافزار = برنامهنویسی + زمان
کتاب با بازگشت به تعریف اولیه خود پایان مییابد: تفاوت برنامهنویسی و مهندسی نرمافزار در «طول عمر» و «تغییر» است.
کدی که امروز مینویسید، ممکن است ۱۰ سال دیگر توسط شخصی که هنوز استخدام نشده، خوانده و تغییر داده شود. تمامی ابزارها و فرآیندهایی که در این ۲۵ فصل بررسی کردیم (از تستنویسی و Code Review گرفته تا CI/CD و مدیریت وابستگی)، همگی یک هدف دارند: پایدار نگهداشتن نرمافزار در برابر گذر زمان و تغییرات اجتنابناپذیر.
