عنوان:

‫رویکرد صحیح در توسعه نرم‌افزار: تعادلی هوشمندانه بین شاخه‌ها و یکپارچه‌سازی مداوم


نویسنده: وحید نصیری
تاریخ: ۱۴۰۴/۰۲/۲۱ ۰۹:۱۷
آدرس: www.dntips.ir
انتخاب رویکرد مناسب برای مدیریت تغییرات و یکپارچه‌سازی کد، نقشی حیاتی در سرعت، کیفیت و پایداری پروژه‌ها ایفا می‌کند. یکی از بحث‌های رایج در این حوزه، مقایسه بین توسعه مبتنی بر شاخه (Branch-based Development) با توسعه مبتنی بر تنه اصلی (Trunk-based Development) است. در حالیکه برخی از توسعه‌دهندگان با تجربه، رویکرد تنه اصلی را به عنوان راهکاری ایده‌آل برای دستیابی به تحویل سریع و با کیفیت می‌دانند، بررسی دقیق‌تر نشان می‌دهد که اتخاذ یک رویکرد کاملاً سخت‌گیرانه و بدون انعطاف در قبال تنه اصلی، می‌تواند منجر به از دست رفتن قابلیت‌های مهم و بروز چالش‌هایی در فرایند توسعه شود. این مقاله با بررسی دقیق استدلال‌های مطرح شده، به تبیین یک دیدگاه متعادل در این زمینه می‌پردازد و راهنمایی‌هایی عملی برای انتخاب استراتژی مناسب ارائه می‌دهد.

یکی از استدلال‌های اصلی طرفداران توسعه مبتنی بر تنه اصلی، حفظ یکپارچگی و سادگی تاریخچه کد (Git History) است. آن‌ها معتقدند که با حذف شاخه‌های متعدد، درخواست‌های ادغام (Pull Requests) و ادغام‌های پیچیده (Merge Commits)، می‌توان تاریخچه‌ای خطی و قابل فهم ایجاد کرد. با این حال، این دیدگاه مطلق، مزایای مهم استفاده از شاخه‌ها و ادغام‌ها را نادیده می‌گیرد.

اهمیت شاخه‌ها و ادغام‌ها:
  • گروه‌بندی تغییرات مرتبط: شاخه‌ها این امکان را فراهم می‌کنند تا مجموعه‌ای از تغییرات مرتبط (مانند بازسازی کد، افزودن قابلیت جدید) به صورت یک واحد منسجم در یک شاخه جداگانه توسعه یافته و سپس به صورت یکپارچه به شاخه اصلی (main branch) ادغام شوند. این امر به خوانندگان آتی تاریخچه کد کمک می‌کند تا تغییرات را در دو سطح جزئیات بررسی کنند: ابتدا با استفاده از دستور git log --first-parent مروری بر ادغام‌ها و کامیت‌های (Commits) شاخه اصلی داشته باشند و در صورت نیاز، جزئیات تغییرات اعمال شده در شاخه ویژگی (feature branch) را بررسی کنند. همچنین، این رویکرد امکان تقسیم یک تغییر بزرگ که باید به صورت یکباره در شاخه اصلی اعمال شود (برای جلوگیری از شکستن ساختار کد با انتشار کار ناقص)، به مجموعه‌ای از تغییرات کوچک‌تر با پیام‌های کامیت واضح‌تر را فراهم می‌سازد.
  • کنترل کیفیت از طریق CI: استفاده از درخواست‌های ادغام (PRs) در پلتفرم‌هایی مانند گیت‌هاب (GitHub) این امکان را می‌دهد تا قبل از ادغام تغییرات در شاخه اصلی، فرایندهای یکپارچه‌سازی مداوم (Continuous Integration - CI) به صورت خودکار اجرا شده و از عدم بروز مشکل در شاخه اصلی اطمینان حاصل شود. ثبت دائمی خطاهای ساخت و رفع آن‌ها نیز به حفظ کیفیت کلی کد کمک می‌کند.
  • تست خودکار رگرسیون: سیستم‌های CI می‌توانند برای انجام تست‌های رگرسیون (Regression Tests) به صورت خودکار پیکربندی شوند. این امر نیاز به اجرای دستی تمام تست‌ها به صورت محلی را کاهش داده و از بروز مشکلات ناخواسته پس از ادغام تغییرات جلوگیری می‌کند.
  • آزادی عمل در توسعه و بازسازی: شاخه‌ها به توسعه‌دهندگان اجازه می‌دهند تا مجموعه‌ای از کامیت‌ها را ایجاد کرده و در صورت تشخیص اشتباه در طراحی یا پیاده‌سازی، آن‌ها را قبل از رسیدن به شاخه اصلی دور انداخته یا بازنویسی کنند. در رویکرد توسعه مستقیم بر روی تنه اصلی، این تلاش‌های ناموفق در تاریخچه کد باقی می‌مانند و می‌توانند درک آن را برای توسعه‌دهندگان آینده دشوار سازند.

چه زمانی از رویکرد تنه اصلی، شاخه یا درخواست ادغام استفاده کنیم؟
انتخاب رویکرد مناسب نیازمند در نظر گرفتن ماهیت تغییرات و سطح همکاری تیم است. در اینجا چند قاعده کلی ارائه می‌شود:
  • برنامه‌نویسی دونفره (Pairing): در صورت امکان، برای اکثر تغییرات از برنامه‌نویسی دونفره استفاده کنید. این روش می‌تواند نیاز به درخواست ادغام برای بازبینی همتا (Peer-Review) را کاهش دهد. تنها زمانی به کار انفرادی بپردازید که ارزش افزوده یا انتقال دانش از برنامه‌نویسی دونفره به حداقل رسیده باشد (مانند به‌روزرسانی‌های کتابخانه‌ای).
  • تغییرات کوچک و غیرمناقشه‌برانگیز: تغییرات جزئی و واضح که احتمال بروز مشکل در آن‌ها کم است، می‌توانند مستقیماً به شاخه اصلی ارسال شوند (البته در صورتی که سازمان اجازه ارسال مستقیم به شاخه اصلی را بدهد). حتماً قبل از ارسال، تست‌های محلی را اجرا کنید و به هشدارهای لینترها (Linters) و فرمت‌کننده‌های کد (Formatters) در CI توجه داشته باشید. استفاده از ابزارهای پیش از کامیت (Pre-commit) و پیش از ارسال (Pre-push) می‌تواند از بروز خطاهای ساده در ساخت جلوگیری کند.
  • تغییرات نسبتاً بزرگ (با برنامه‌نویسی دونفره): تغییرات بزرگ‌تر اما همچنان ساده و قابل درک، در صورت انجام با برنامه‌نویسی دونفره، می‌توانند مستقیماً به شاخه اصلی ارسال شوند. با این حال، از ارسال تغییرات بزرگ و پیچیده به صورت مستقیم خودداری کنید.
  • توسعه قابلیت‌های بزرگ: هنگام کار بر روی یک قابلیت بزرگ، سعی کنید تا حد امکان قطعات مستقل و غیرمرتبط را جدا کرده و به صورت جداگانه (با استفاده از رویکرد تنه اصلی یا درخواست ادغام) در شاخه اصلی ادغام کنید. این کار از ایجاد درخواست‌های ادغام بسیار بزرگ و پیچیده جلوگیری می‌کند.
  • تغییرات چند مرحله‌ای: اگر یک تغییر نیازمند چند مرحله منطقی و مرتبط است، آن‌ها را در یک شاخه ویژگی گروه‌بندی کنید. در صورت برنامه‌نویسی دونفره، پس از اجرای تست‌ها و بررسی کد به صورت محلی، یک کامیت ادغام (Merge Commit) در شاخه اصلی ایجاد کرده و آن را ارسال کنید. در این حالت، نیازی به درخواست ادغام جداگانه نیست زیرا کد قبلاً توسط همکار بررسی شده است.
  • استفاده اختیاری از درخواست ادغام: در هر یک از موارد فوق، ممکن است بنا به دلایل زیر تصمیم به باز کردن یک درخواست ادغام بگیرید:
  • اجرای فرایند CI.
  • دریافت بازخورد غیرهمزمان از سایر اعضای تیم یا تیم‌های دیگر.
  • مطرح کردن یک ایده یا تکنیک جدید برای بحث و بررسی. قابلیت پیش‌نویس درخواست ادغام (Draft PR) در گیت‌هاب می‌تواند برای این منظور مفید باشد. همچنین برای پروژه‌های اکتشافی (Spikes) که هدف آن‌ها یادگیری یا آزمایش یک رویکرد جدید است و لزوماً آماده ادغام در شاخه اصلی نیستند، کاربرد دارد.
  • اجتناب از شاخه‌های طولانی مدت: شاخه‌هایی که برای مدت طولانی فعال هستند و شامل تعداد زیادی کامیت هستند، نشانه‌ای از این هستند که حجم تغییرات قابل تحویل (در سطح شاخه ویژگی یا داستان کاربری) بیش از حد بزرگ است. در این موارد، بهتر است رویکرد خود را بازبینی کرده و تغییرات را به قطعات کوچک‌تر تقسیم کنید؛ حتی ممکن است لازم باشد شاخه فعلی را رها کرده و از شاخه اصلی مجدداً شروع کنید.

توجه داشته باشید که این قواعد کلی باید به صورت مداوم و در طول فرایند توسعه مورد بازبینی قرار گیرند، زیرا در یک جلسه کدنویسی ممکن است با انواع مختلفی از تغییرات روبرو شوید (مانند پاکسازی کد، بازسازی، رفع اشکال، افزودن قابلیت جدید، بهبود کیفیت، رفع مشکلات CI، تغییر تنظیمات ویرایشگر، تغییر نام فایل‌ها و پوشه‌ها و غیره) و هر یک از این تغییرات ممکن است نیازمند رویکرد متفاوتی برای ایجاد یک تاریخچه کد با کیفیت باشد.

تقسیم کامیت‌ها قبل از نوشتن کد بیشتر:
ایجاد یک کامیت جداگانه در شاخه اصلی هنگام تشخیص نیاز به تغییری که به شاخه ویژگی فعلی مرتبط نیست، یک عادت و مهارت ارزشمند است. با این حال، این کار نیازمند نظم و تمرین است. بسیاری از توسعه‌دهندگان به طور پیش‌فرض از توسعه مبتنی بر شاخه استفاده می‌کنند و هر تغییری را در شاخه فعلی خود انباشته می‌کنند، که در نهایت منجر به درخواست‌های ادغام بسیار بزرگ با تغییرات گسترده می‌شود. آن‌ها اغلب از دشواری تقسیم این تغییرات شکایت می‌کنند و ترجیح می‌دهند آن‌ها را ادغام و فشرده (Squash) کنند. بهتر است تغییرات را به صورت تدریجی و در حین توسعه به کامیت‌های کوچک‌تر تقسیم کنید تا از مشکلات ناشی از استفاده از دستورات پیچیده گیت مانند git rebase --interactive جلوگیری شود.
برخی از توسعه‌دهندگان ممکن است استدلال کنند که ایجاد کامیت‌های اتمی، منسجم و با توضیحات مناسب، زمان‌بر است. آن‌ها اغلب به کامیت‌های بزرگ و نامنظم یا مجموعه‌ای از کامیت‌های "wip" (مخفف Work in Progress به معنی در حال انجام) در یک شاخه اشاره می‌کنند و تلاش مورد نیاز برای سازماندهی مجدد آن‌ها را دلیلی برای عدم انجام این کار می‌دانند. با این حال، رویکرد صحیح این است که از ابتدا تلاش کنید تا مجموعه‌ای از تغییرات مناسب را ایجاد کنید، در این صورت هزینه استفاده از rebase --interactive به میزان قابل توجهی کاهش می‌یابد.

پرچم‌های ویژگی جایگزین شاخه‌های ویژگی نمی‌شوند:
برخی معتقدند که استفاده از پرچم‌های ویژگی (Feature Flags - قابلیت فعال یا غیرفعال کردن ویژگی‌ها در زمان اجرا) نیاز به شاخه‌های ویژگی را از بین می‌برد. در حالی که پرچم‌های ویژگی ابزاری مفید برای مدیریت ویژگی‌های در حال توسعه هستند که هنوز آماده انتشار نیستند، آن‌ها نمی‌توانند جایگزین غنا و ابزارهای موجود در ادغام کامیت‌ها و درخواست‌های ادغام شوند. وجود مشکلات در استفاده بیش از حد از درخواست‌های ادغام در برخی تیم‌ها، دلیل بر کنار گذاشتن کامل آن‌ها نیست. می‌توان هم از پرچم‌های ویژگی و هم از شاخه‌ها، ادغام کامیت‌ها و درخواست‌های ادغام به عنوان ابزارهای مناسب در زمان مقتضی استفاده کرد.

اهمیت تاریخچه کد:
این ادعا که "هیچ‌کس به تاریخچه کد اهمیت نمی‌دهد" نادرست است. تجربه نشان داده است که تاریخچه کد به دلایل متعددی برای توسعه‌دهندگان حائز اهمیت است:
  • درک چرایی وضعیت فعلی کد: برای دانستن نحوه و امکان تغییر کد موجود، درک دلیل شکل‌گیری آن ضروری است.
  • تکرار تغییرات در سرویس‌های دیگر: درک تغییرات اعمال شده توسط سایر توسعه‌دهندگان برای اعمال مشابه آن‌ها در میکروسرویس‌های (Microservices) دیگر یا تیم‌های مختلف مفید است.
  • بازبینی درخواست‌های ادغام بزرگ: هنگام بررسی یک درخواست ادغام با تعداد زیادی تغییر (بیش از 1000 خط کد)، بررسی کامیت‌های جداگانه در شاخه مربوطه به درک بهتر تغییرات عملکردی مورد نظر (که ممکن است تنها چند خط کد باشند) در مقابل تغییرات ساختاری بزرگ کمک می‌کند.
  • ارزیابی توانایی و بهره‌وری توسعه‌دهندگان: تاریخچه کد می‌تواند به عنوان یکی از معیارهای ارزیابی توسعه‌دهندگان در فرایند استخدام یا ارتقاء مورد استفاده قرار گیرد. یک تاریخچه کد ضعیف می‌تواند قضاوت‌های منفی را به همراه داشته باشد.
  • آشنایی با توانایی‌های همکاران: بررسی تاریخچه مشارکت همکاران در پروژه‌ها می‌تواند به درک بهتر مهارت‌ها و رویکردهای آن‌ها کمک کند.
  • اطلاع از تغییرات پروژه: برای افرادی که به طور مستقیم یا غیرمستقیم در یک پروژه مشارکت دارند (مانند پروژه‌های متعلق به تیم‌های دیگر)، بررسی تاریخچه کد راهی برای آگاهی از تغییرات است.
  • شناسایی بهترین و بدترین رویه‌ها: توسعه‌دهندگان ارشد که در سطح چند تیم فعالیت می‌کنند، از تاریخچه کد برای شناسایی الگوهای خوب و بد و اتخاذ اقدامات لازم استفاده می‌کنند.

جالب اینجاست که حتی برخی از افرادی که ادعا می‌کنند "هیچ‌کس به تاریخچه کد اهمیت نمی‌دهد"، در مواقع لزوم برای یافتن پاسخ سوالات خود به بررسی تاریخچه گیت می‌پردازند.

نتیجه‌گیری
برنامه‌نویسی صرفاً اجرای دستورات توسط رایانه‌ها نیست، بلکه یک فرایند ارتباط بین افراد است. تاریخچه کد با کیفیت و غنایی که شاخه‌ها و ادغام‌ها به ما می‌دهند، بخشی از این تار و پود ارتباط با افراد در گذشته، حال و آینده است. با کسب مهارت در ایجاد یک تاریخچه کد مناسب، بسیاری از objections ها و مشکلات موجود در این زمینه از بین خواهند رفت.


مشاهده مطلب اصلی