نرم افزار

مدیریت نسخه‌های نرم‌افزار با Semantic Versioning در پروژه‌های تیمی

24 آبان 1405
8 بازدید
8 دقیقه مطالعه
م
محمد رستمی
موسس سایت
مدیریت نسخه‌های نرم‌افزار با Semantic Versioning در پروژه‌های تیمی

فرض کن تیم شما نسخه ۱.۴.۲ را منتشر کرده است. دو هفته بعد یک قابلیت جدید اضافه می‌شود، هم‌زمان یک باگ کوچک هم رفع می‌شود و یکی از اعضای تیم نسخه را «۱.۵» می‌گذارد؛ نفر دیگر روی تگ گیت «v1.5.0» می‌زند و یک نفر دیگر عدد را «۲.۰» می‌خواند. مشتری هم که انتظار دارد قابلیت جدید در نسخه ۱.۴.۳ باشد، روی نسخه ۱.۵ گیر می‌کند و نمی‌داند کدام نسخه را نصب کند. اینجاست که مشکل از یک عدد ساده شروع می‌شود و به از دست رفتن اعتماد کاربر و سردرگمی تیم می‌رسد.

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

در این مقاله قواعد اصلی SemVer را مرور می‌کنیم، نشان می‌دهیم چطور در Git و فرآیند انتشار تیمی پیاده‌سازی شود، و اشتباه‌هایی را که اغلب تیم‌ها در نسخه‌گذاری تکرار می‌کنند بررسی می‌کنیم. برای اعضای تیم که تازه با فرآیندهای حرفه‌ای انتشار آشنا می‌شوند، این قواعد بخشی از پایه‌های کار تیمی است؛ همان‌قدر که نوشتن کد تمیز اهمیت دارد.

SemVer چیست و چه چیزی را تضمین می‌کند؟

SemVer یک قالب استاندارد برای شماره نسخه است: MAJOR.MINOR.PATCH. هر عدد معنای مشخصی دارد و تغییر آن باید با نوع تغییرات کد هم‌خوان باشد. این استاندارد توسط یک مشخصه عمومی در semver.org منتشر شده و در بسیاری از اکوسیستم‌ها (npm، PyPI، NuGet، Maven و…) پذیرفته شده است.

قاعده اصلی ساده است: MAJOR برای تغییرات ناسازگار یا شکستن سازگاری، MINOR برای افزودن قابلیت جدید با حفظ سازگاری، و PATCH برای رفع باگ یا اصلاح‌های سازگار. به‌همین‌ترتیب، وقتی کاربر نسخه‌ای را می‌بیند، می‌داند آیا ارتقا بی‌خطر است یا نیاز به بازبینی دارد.

نکته مهم این است که SemVer تضمین نمی‌کند کد شما بدون باگ است؛ تضمین می‌کند که شماره نسخه با نوع تغییرات اعلام‌شده هم‌خوان باشد. اگر تیم این قرارداد را جدی بگیرد، کاربر می‌تواند با اطمینان از بازه‌های نسخه در وابستگی‌ها استفاده کند و ابزارهایی مثل pip و npm هم می‌توانند نسخه سازگار را انتخاب کنند.

قواعد نسخه‌گذاری معنایی در عمل

پیش از هر چیز، یک تصمیم تیمی بگیرید: «چه چیزی تغییر ناسازگار محسوب می‌شود؟» در یک کتابخانه پایتون، حذف یک تابع عمومی یا تغییر امضای آن ناسازگار است؛ در یک API، حذف یک فیلد پاسخ ناسازگار است. پس تیم باید فهرست کوتاهی از تغییرات ناسازگار در پروژه داشته باشد و هنگام بازبینی کد به آن مراجعه کند.

افزودن قابلیت جدید با حفظ سازگاری، نسخه MINOR را بالا می‌برد. اگر قابلیت جدید نیازمند تغییر رفتار پیش‌فرض است، این تغییر ناسازگار محسوب می‌شود و باید MAJOR را افزایش دهد؛ حتی اگر ظاهراً کوچک باشد. در مقابل، رفع باگ و بهبود عملکرد بدون تغییر رفتار عمومی، PATCH را افزایش می‌دهد.

SemVer اجازه می‌دهد پیش‌انتشار (pre-release) و فراداده ساخت (build metadata) هم در نسخه بیاید، مثلاً 1.2.0-beta.1 یا 1.2.0+build.45. این بخش‌ها برای انتشارهای آزمایشی، نسخه‌های کاندید و ساخت‌های CI کاربرد دارند و نباید به‌عنوان نسخه پایدار منتشر شوند.

نمودار تصمیم‌گیری افزایش شماره نسخه در Semantic Versioning

جدول مقایسه نوع تغییر و افزایش نسخه

نوع تغییر مثال افزایش نسخه نتیجه برای مصرف‌کننده
رفع باگ سازگار اصلاح شرط مرزی در تابع محاسبه PATCH ارتقای امن
قابلیت جدید سازگار افزودن پارامتر اختیاری MINOR ارتقای امن با قابلیت بیشتر
تغییر ناسازگار حذف یا تغییر امضای تابع عمومی MAJOR نیاز به بازبینی کد مصرف‌کننده
نسخه آزمایشی انتشار برای تست داخلی پیش‌انتشار نباید به‌عنوان پایدار استفاده شود

پیاده‌سازی SemVer در Git و فرآیند انتشار

SemVer بدون نظم در مخزن گیت، فقط یک عدد روی کاغذ است. استاندارد رایج این است که هر انتشار پایدار، یک تگ گیت annotate‌شده با پیشوند v بگیرد، مثلاً v1.4.2. تگ‌های سبک (lightweight) هم کار می‌کنند، اما تگ annotate‌شده پیام، تاریخ و امضای سازنده را نگه می‌دارد و برای انتشار رسمی مناسب‌تر است.

شماره نسخه را در یک مکان مرجع نگه دارید: در pyproject.toml برای پایتون، در package.json برای جاوااسکریپت، یا در یک فایل VERSION. سپس یک اسکریپت کوچک بنویسید که شماره نسخه را از همان منبع بخواند، تگ گیت را بسازد و در صورت نیاز آن را به رجیستری بفرستد. این کار از اختلاف بین نسخه در کد و نسخه روی تگ جلوگیری می‌کند.

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

نمونه کد: خواندن و بررسی نسخه در پایتون

from packaging.version import Version, InvalidVersion


def bump_version(current: str, kind: str) -> str:
    """افزایش شماره نسخه بر اساس نوع تغییر."""
    v = Version(current)
    if kind == "major":
        return f"{v.major + 1}.0.0"
    if kind == "minor":
        return f"{v.major}.{v.minor + 1}.0"
    if kind == "patch":
        return f"{v.major}.{v.minor}.{v.patch + 1}"
    raise ValueError(f"نوع نامعتبر: {kind}")


if __name__ == "__main__":
    print(bump_version("1.4.2", "patch"))  # 1.4.3
    print(bump_version("1.4.2", "minor"))  # 1.5.0
    print(bump_version("1.4.2", "major"))  # 2.0.0

این کد از کتابخانه packaging استفاده می‌کند که در پروژه‌های پایتون رایج است و اعتبارسنجی نسخه را انجام می‌دهد. اگر شماره نسخه‌ای مثل 1.4 وارد شود، Version آن را به‌عنوان 1.4.0 تفسیر می‌کند؛ پس بهتر است از ابتدا قالب سه‌بخشی را رعایت کنید.

نمونه کد: ساخت تگ گیت پس از تأیید نسخه

#!/usr/bin/env bash
set -euo pipefail

VERSION=$(python -c "import tomllib;print(tomllib.load(open('pyproject.toml','rb'))['project']['version'])")
TAG="v${VERSION}"

if git rev-parse "$TAG" >/dev/null 2>&1; then
  echo "تگ $TAG از قبل وجود دارد."
  exit 1
fi

git tag -a "$TAG" -m "انتشار نسخه $VERSION"
git push origin "$TAG"
echo "تگ $TAG ساخته و ارسال شد."

این اسکریپت شماره نسخه را از pyproject.toml می‌خواند، وجود تگ تکراری را بررسی می‌کند و سپس تگ annotate‌شده می‌سازد. در خطوط CI، همین اسکریپت را بعد از تست‌ها اجرا کنید تا انتشار ناخواسته رخ ندهد.

هماهنگی تیم توسعه با SemVer

SemVer وقتی اثر دارد که همه اعضای تیم یک تعریف مشترک از «تغییر ناسازگار» داشته باشند. یک راه ساده این است که در قالب Pull Request، یک فیلد اجباری اضافه کنید: «نوع تغییر: patch / minor / major». این کار بازبینی را ساده می‌کند و جلوی بحث‌های سلیقه‌ای را می‌گیرد.

ابزارهای خودکارسازی هم کمک می‌کنند. ابزارهایی مثل semantic-release یا changesets می‌توانند از پیام کامیت‌های استاندارد (Conventional Commits) نوع تغییر را استنباط کنند و شماره نسخه بعدی را پیشنهاد دهند. اما این ابزارها جای تصمیم انسانی را نمی‌گیرند؛ اگر تیم تشخیص ندهد که یک تغییر ناسازگار است، ابزار هم نمی‌تواند آن را حدس بزند.

مستندسازی انتشار (CHANGELOG) را جدی بگیرید. برای هر نسخه، فهرست کوتاهی از تغییرات بنویسید و آن را در کنار تگ گیت نگه دارید. کاربران و حتی اعضای جدید تیم با خواندن CHANGELOG سریع‌تر متوجه می‌شوند چه چیزی تغییر کرده و آیا باید نگران ارتقا باشند.

  • یک فایل VERSION یا فیلد نسخه در فایل پیکربندی به‌عنوان منبع واحد حقیقت تعیین کنید.
  • هر انتشار پایدار را با تگ annotate‌شده vX.Y.Z مشخص کنید.
  • تگ را فقط پس از سبز شدن تست‌ها و بازبینی کد بزنید.
  • در هر Pull Request نوع تغییر (patch/minor/major) را مشخص کنید.
  • CHANGELOG را همراه هر نسخه به‌روز کنید.
  • نسخه‌های پیش‌انتشار را با پسوند مشخص کنید و به‌عنوان پایدار منتشر نکنید.

نسخه‌بندی معنایی یک قرارداد ارتباطی است، نه یک برچسب تزئینی. اگر شماره نسخه با نوع تغییر هم‌خوان نباشد، کاربران و وابستگی‌ها را گمراه می‌کند.

اشتباه‌های رایج در نسخه‌گذاری

بیشتر مشکلات نسخه‌گذاری از چند اشتباه تکراری ناشی می‌شود. اگر این موارد را در تیم مطرح کنید، بخش زیادی از سردرگمی‌های آینده از بین می‌رود.

  • افزایش MINOR برای تغییرات ناسازگار: وقتی تابعی حذف می‌شود، افزایش MINOR کاربر را به ارتقای بی‌خطر امیدوار می‌کند و بعد کدش می‌شکند.
  • نسخه‌گذاری بر اساس تاریخ یا شماره ساخت: شماره‌هایی مثل 20250824.1 هیچ اطلاعاتی درباره سازگاری نمی‌دهند و SemVer نیستند.
  • تگ گیت بدون هم‌خوانی با فایل نسخه: تگ v1.5.0 روی کدی که فایل نسخه‌اش 1.4.2 است، منبع سردرگمی در نصب و بسته‌بندی می‌شود.
  • انتشار نسخه پایدار از شاخه شخصی: تگ باید روی شاخه اصلی و پس از ادغام و تست زده شود.
  • نادیده گرفتن پیش‌انتشار: نسخه‌های beta و rc باید واضح علامت‌گذاری شوند تا ابزارهای نصب آن‌ها را پایدار تلقی نکنند.
  • نداشتن CHANGELOG: بدون فهرست تغییرات، کاربر مجبور است کد را مقایسه کند تا بفهمد چه چیزی عوض شده است.

نکات کلیدی برای اجرای پایدار SemVer

اجرای SemVer بیش از هر چیز به عادت تیمی نیاز دارد. سه اقدام کوتاه می‌تواند تفاوت ایجاد کند: تعریف فهرست تغییرات ناسازگار در مستندات پروژه، اضافه کردن فیلد نوع تغییر به قالب Pull Request، و خودکارسازی ساخت تگ گیت با اسکریپتی که از منبع واحد نسخه می‌خواند.

اگر در تیم شما هنوز نسخه‌ها به‌صورت سلیقه‌ای تعیین می‌شوند، از همین هفته شروع کنید: فایل نسخه را در یک مکان متمرکز کنید و اولین تگ annotate‌شده را با قالب vX.Y.Z بزنید. همین قدم کوچک، پایه هماهنگی بیشتر در انتشارهای بعدی است.

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

پرسش‌های پرتکرار

آیا SemVer برای همه پروژه‌ها مناسب است؟

SemVer بیشترین فایده را در کتابخانه‌ها و APIهایی دارد که مصرف‌کننده خارجی دارند. برای اپلیکیشن‌های داخلی هم می‌توان از آن استفاده کرد، اما اهمیت آن به میزان وابستگی دیگران به نسخه‌ها بستگی دارد.

تفاوت تگ annotate و lightweight در گیت چیست؟

تگ annotate‌شده پیام، تاریخ و سازنده را ذخیره می‌کند و برای انتشار رسمی مناسب‌تر است؛ تگ lightweight فقط یک اشاره‌گر به کامیت است و اطلاعات اضافی ندارد.

آیا باید شماره نسخه را دستی تغییر دهم؟

می‌توانید دستی تغییر دهید، اما بهتر است تغییر نسخه با اسکریپت یا ابزار خودکار انجام شود تا شماره در فایل پیکربندی و تگ گیت هم‌خوان بمانند.

نسخه پیش‌انتشار چگونه نوشته می‌شود؟

با پسوند بعد از خط تیره، مثل 1.2.0-beta.1 یا 1.2.0-rc.2. این نسخه‌ها نباید به‌عنوان پایدار منتشر شوند و ابزارهای نصب معمولاً آن‌ها را جدا از نسخه پایدار در نظر می‌گیرند.

م

محمد رستمی

اشتراک‌گذاری:

نظرات کاربران (0)

هنوز نظری ثبت نشده است. اولین نفری باشید که نظر می‌دهید!

اشتراک‌گذاری: