جنگ برنچ‌ها؛ نبردی برای کنترل کد;

به قلم امیرارسلان یاوری، ورودی 98 مهندسی کامپیوتر صنعتی اصفهان
بازنگری‌شده توسط مهدی قاسمی، ورودی 99 کارشناسی مهندسی کامپیوتر صنعتی اصفهان

فرض کنین تو یه روز گرم و تقریبا تابستونی، همینطور که رو صندلیتون نشستین و دارین به بدبختیاتون فکر می‌کنین، یهو تصمیم می‌گیرین پروژه‌ی فلان درس رو شروع کنین. قطعا به کرّات از سال بالاییاتون شنیدین که بهتره از یه ورژن کنترلر[1] استفاده کنین و بهترینشون هم گیته؛ اگه استفاده می‌کنین که چه عالی؛ اگه نه، چند تا پروژه بزنین تا متوجه بشین چقدر لازمه ازش استفاده کنین :).

خب حالا مبنا رو بر این می‌ذاریم که شما قراره از گیت هم استفاده کنین ولی استراتژی شما برای پیشبرد پروژه‌تون چیه؟ اگه فردی بخواین پروژه رو بزنین چیکار می‌کنین؟ اگه گروهی بخواین بزنین چی؟ تا انتهای این متن با ما همراه باشین تا با استراتژی‌های مختلف Branching از جمله Git Flow ،Feature و Trunk آشنا بشیم.

‏‏Solo[2] Development:

خب اگه بخواین تنهایی پروژه رو بزنین تقریبا کارتون راحته؛ روی سیستمتون گیت رو راه‌اندازی یا یه ریپازیتوری گیت توی گیت‌هاب یا هر جای دیگه می‌سازین و کارتونو شروع می‌کنین؛ بعد از هربار اضافه یا تغییر کد هم یه کامیت می‌زنین. این استراتژی، مبتدیانه‌ترین استراتژی هستش و فقط هم یه برنچ[3] داریم (البته یکی از دوستان میگه تنهایی هم بخوای دو تا فیچر بزنی بهتره دو تا برنچ بسازی و حرفشم درسته ولی استفاده از یک برنچ هم براتون نسبت به توضیحاتی که در ادامه میدم، اونقدر مشکل‌ساز نمیشه).

در واقع توی این سناریو، شما فقط یه برنچ دارین و همه کارهاتون رو روی همون انجام میدین (خیلی هم عالی و قوی). اما سوالی که پیش میاد اینه که اگه قرار بود دو، سه یا حتی n نفری یه پروژه رو بزنین چیکار می‌کنین؟ بازم قراره فقط یه برنچ داشته باشین و همتون با هم روی همون کار کنین؟ اگه تجربه‌ی چنین موردی رو داشته بوده باشین می‌دونین چقدر خوب نیست و چقدر ممکنه کانفلیکت[4] بخورین (برطرف کردن کانفلیکت برای اونایی که گیت بلدن هم نسبتا کار سختیه). علاوه بر این فرض کنین شما چند نفری دارین روی یه برنچ به طور مداوم پوش می‌کنین و یهو می‌فهمین یکیتون یه خرابکاری کرده و باید کدتون رو به چند کامیت قبل‌تر برگردونین. دیگه به این راحتی نمیشه بگیم اصغر سه تا کامیت آخرش باید از بین بره و برگردیم به سه تا کامیت‌ قبل‌ترش؛ چون در همین حین، دوستای اصغر هم کلی کامیت دیگه زدن و کلی فیچر و امثالهم اضافه کردن. پس با برگشتن به سه کامیت قبل‌تر اصغر، همه‌ی اون‌ها رو هم از دست خواهیم داد (برای اینکه از دست ندیم باید زحمت بکشیم و کمی دردسر داره :) ). علاوه بر این تا مادامی که توسعه‌مون تموم نشه یه نسخه‌ی پایدار و قابل استقرار نداریم، چون همیشه ممکنه کدمون هنوز کامل نباشه. خیلی طولانیش نکنم، خودتون فکر کنین کلی مشکل دیگه هم پیدا می‌کنین که می‌تونین حتی اگه موضوع جالبی به ذهنتون خورد، توی کامنت‌های این پست بنویسین. خب بگذریم، استراتژیتون برای یه تیم چند نفره چیه؟


‏‏Feature Based Branching:

مدل دومی که ممکنه به ذهنتون بیاد، اینه که برای هر فردی یه برنچ بسازیم و هر کسی توی برنچ خودش کد یه فیچری رو بزنه و در نهایت این کدها رو با هم ادغام[5] کنیم (البته اگه قرار باشه یک فیچر توسط n نفر زده بشه نیازه برنامه‌نویس‌ها باید با هم سینک باشن مثلا به حالت Pair Programming[6] کار کنن؛ اما باز هم توصیه برنچ جداگونه برای هر توسعه‌دهنده است). این استراتژی، خیلی از مشکلات استراتژی قبلی رو برطرف می‌کنه و در خیلی از تیم‌های نرم‌افزاری هم استفاده میشه. تو این روش از کدی که دارن، یک یا چند برنچ جدید می‌سازن و اون فیچر یا فیچرهای مدنظر رو توسعه میدن و در نهایت با کد اصلی ادغام می‌کنن.

خب ولی واسه استارت پروژه چیکار می‌کنین؟ همتون به یه کد اولیه نیاز دارین، در واقع اول باید سریع یه چیز کلی بالا بیارین تا همتون بتونین روی اون کار کنین (البته این مشکل نیست و عمدتا به یه کد اولیه برای ادامه‌ی توسعه نیاز داریم). خب اما مشکل چیه؟ مشکل یه مشکل بزرگه به اسم Merge Hell.

اینطوری فرض کنین که اگه اصغر توی یه برنچ داشته روی فیچری کار می‌کرده و دوستش هم توی یه برنچ دیگه داشته روی یه فیچر دیگه کار می‌کرده، ولی برای هر دوی این فیچرها، فایل‌ یا فایل‌های یکسانی داشته عوض میشده؛ اینجا هرکی زودتر ادغام کنه به نفعشه چون نفر دوم کلی کانفلیکت خواهد خورد :)

ولی از شوخی که بگذریم مشکل پیش اومده مشکل تیمه، پس خیلی خوشحال‌کننده نیست! حالا فرض کنین به جای دو تا فیچر، قراره n تا فیچر زده بشه و کلی برنچ تو طول t ماه اضافه بشن و با کد اصلی ادغام بشن. حالا قراره اون برنچی که یه فیچری داشته رو بعد t ماه با برنچ اصلی ادغام کنیم که واقعا فاجعه‌ی بزرگی پیش میاره. خب حالا چی؟ دقیقا چه ایده‌ی دیگه‌ای دارین؟


‏‏Git Flow Branching:

برای اینکه مشکل Merge Hell رو بتونیم برطرف کنیم، کاری که به نظر منطقی میاد اینه که با هر بار ادغام شدن یه فیچر با کد اصلی، کد اصلی با برنچ‌های فیچرهای دیگه هم ادغام بشه (rebase بشه) … در واقع بازم مشکل کانفلیکت پیش میاد ولی اگه n تا برنچ باشن و در طول t ماه یا T سال مکررا ادغام شده باشن، کد ما هم توی برنچ n+1 ام به مرور با سینک شدن با برنچ اصلی بعد هر بار ادغام، هی کانفلیکت‌های کوچیک کوچیک ازش برطرف‌ شده و یهویی بعد t ماه یا T سال با یه دریایی از کانفلیکت مواجه نمیشیم. مثلا توی عکس زیر می‌بینین که وقتی برنچ Feature A با برنچ Develop ادغام شده همون موقع برنچ Develop هم با برنچ Feature B که هنوز در دست توسعه‌ست سریعا ادغام شده.

خب حالا که این ایده‌ها رو داریم پس بذارین با Git Flow آشنا بشیم؛ از اونجایی که اگه بخواین هی کد بزنین و با برنچ اصلیتون (main یا master) ادغام کنین همون مشکل عدم وجود یه کد پایدار برای استقرار، که توی Solo Development هم بهش اشاره کردیم رو داریم. خب پس توصیه می‌شه از روی برنچ اصلی یه برنچ با اسم dev یا develop ساخته بشه و این ماجرا روی این برنچ اتفاق بیوفته تا مادامی که تصمیم بگیریم وضعیت develop به جایی رسیده که با برنچ اصلی ادغام بشه (یعنی کد ما به وضعیت قابل استقرار رسیده). در این حالت یه شاخه جدید به اسم release ایجاد می‌کنیم، بررسی نهایی و باگ‌فیکس‌هایی که لازمه رو انجام میدیم، تگ می‌زنیم و با برنچ‌های main و develop ادغامش می‌کنیم و عمر شاخه‌ی release ایجاد شده همینجا تموم میشه.

مطابق عکس بالا برنچ release فقط بوجود اومده تا برنچ develop رو با برنچ main ادغام کنیم و هر تغییری در این برنچ انجام شد بر روی برنچ develop هم اعمال بشه.

حالا اگه چیزی که روی برنچ main داریم، خدایی نکرده باگی براش پیدا بشه و چیزی که داریم روی پروداکشن استفاده می‌کنیم مشکل‌دار باشه سریع از شاخه main یه شاخه جدید به اسم hot fix ایجاد می‌کنیم و باگ رو برطرف و دوباره با برنچ‌های موجود ادغامش می‌کنیم.

خب همه چیز خیلی خوب شد تا اینجا، اما مشکلی که همچنان داریم اینه که برنچ‌ها توی Git Flow به اصطلاح Long-live هستن و ممکنه همونطور که گفتیم و تو شکل زیر، برنچ Feature B رو می‌بینین، عمر یه برنچ ممکنه T سال طول بکشه.

این موضوع گفته شده با [CI[7 در تناقضه چون برنچ اصلیتون دیر به دیر به‌روز می‌شه و برنچ dev هم مناسب نیست که روی پروداکشن[8] بره؛ خب حالا چیکار کنیم؟

علاوه بر مورد فوق استراتژی‌های Github Flow و Gitlab Flow رو هم داریم ولی چون حوصله سر بر میشه ترجیحم اینه خودتون برین بخونینشون (ایده‌ی اصلیشون همینه ولی کمی تعداد برنچ‌ها رو کم و زیاد کردن که ماجرا با ساختار Github و Gitlab سازگارتر باشه)


‏‏Trunk Based Branching:

یکی از مسائلی که کلا توی فضای توسعه نرم‌افزار شاهدش هستیم اینه که یه سری از تیم‌های نرم‌افزاری خیلی علاقه‌مندن که هر فیچری که می‌زنن، سریع قابل دسترس باشه و حتی فرآیند استقرارشون رو هم اتوماتیک می‌کنن که این مسئله هرچه بهتر انجام بشه. از طرفی هم مشکل بخش قبلی رو داشتیم؛ این شد که Trunk[9] Based Branching به وجود اومد. توی این مدل از برنچینگ، برای کوچک شدن کانفلیکت‌ها، کدها زود به زود با شاخه اصلی ادغام میشن. همینطور توی این مدل از برنچینگ دیگه خبری از شاخه‌ی dev نیست و از برنچ اصلی master یا main یا همون trunk هر بار یه شاخه زده میشه و وقتی با trunk ادغام میشه فرآیند CI هم اتفاق میوفته و اینطوری کدی که توی trunk هستش همیشه آماده‌ی استقراره.

توی این مدل از برنچینگ تمرکز روی اینه که شاخه‌ها Short-live باشن برای همین هر برنچ باید تا پایان روز ادغام بشه.

حالا سوالی که پیش میاد، اگه کد اون برنچ کامل نشده باشه یا باگ داشته باشه چی میشه؟ مسئله‌ای که هست اینه که نباید به‌خاطر کامل نبودن ادغامش نکنیم. بلکه ادغام می‌کنیم و از Feature Toggle استفاده می‌کنیم. Feature Toggle قابلیتیه که به ما اجازه میده برای فیچرهای مختلف کدمون تصمیم بگیریم؛ مثلا بگیم این فیچر فقط به کاربرهایی نشون داده بشه که در فلان منطقه سرویس دریافت می‌کنن یا این یکی فیچر فقط به کاربرهای پریمیوم داده بشه و… . خب پس ما از همین قابلیت استفاده می‌کنیم تا اگه کدمون آماده نبود اصلا جایی استفاده نشه که مشکلی هم پیش نیاره :). Trunk Based بیشتر به‌ درد تیم‌های کوچیک و محصولات پویا و Git Flow به درد تیم‌های بزرگ که می‌خوان ساختارمند برنامه تولید کنن میخوره.


تا اینجا با ۳ مدل مهم برنچینگ آشنا شدیم. بازم مدل‌های دیگه هست مثل Mainline یا Fork Branching ،Release Branching ،Task Branching و … .مثلا Fork Branching رو بیشتر توی توسعه‌ی برنامه‌های متن‌باز می‌بینیم که یه fork از کد اصلی می‌زنه و در نهایت کد پایانی رو با یه pull request با کد اصلی ادغام می‌کنن. خب بیشتر ادامه نمیدم، اگه کنجکاو بودین مابقی استراتژی‌ها رو خودتون سرچ کنین، بخونین و از این به بعد بسته به ابعاد پروژه یا تیمتون استراتژی‌ای رو انتخاب کنین که براتون بهینه‌تره…


پانویس‌ها:

‏‏[1] Version Controller:

یک ابزار یا سیستمیه که توسط اون میتونین نسخه‌های قبلی کدتون رو هم نگه‌ دارین و هر وقت خواستین به هر نسخه‌ای که خواستین کدتون رو بازگردانی کنین.

‏‏[2] Solo:

به معنی تنها یا انفرادی هستش؛ یعنی کاری رو بدون همکاری بقیه انجام بدین.

‏‏[3] Branch:

به معنی یک شاخه از کد است که اگر هر تغییری روی این شاخه از کد ایجاد کنید الزاما آن تغییر بر روی شاخه‌های دیگر تاثیری نخواهد داشت.

‏‏[4] Conflict:

وقتی دو چیز با هم مغایرت داشته باشن.

‏‏[5] Merge:

همان ادغام کردن یک شاخه با شاخه‌ی دیگری است.

‏‏[6] Pair Programming:

به حالتی که دو برنامه‌نویس با هم یک کد رو میزنن به طوری که یکی کد میزنه و دیگری داره کد رو میبینه و اگه نظری داره میگه.

‏‏[7] CI:

مخفف واژه‌ی Continuous Integration و به معنای ادغام مکرر می‌باشد.

‏‏[8] Production:

به محیط استقرار کد و کارکرد آن به صورت یک برنامه یا سرویس بر روی یک سرور گفته می‌شود.

‏‏[9] Trunk:

به معنی تنه درخت هستش؛ اسمش هم از اینجا اومده که مثل تنه‌ی درخت یه شاخه‌ی اصلی داریم و مابقی شاخه‌های درخت شاخه‌های فرعی هستن.


منابع:

https://www.atlassian.com/git/tutorials/comparing-workflows/feature-branch-workflow

https://www.atlassian.com/git/tutorials/comparing-workflows/gitflow-workflow

https://www.atlassian.com/continuous-delivery/continuous-integration/trunk-based-development