عنوان:

‫بازبینی انتقادی تغییرات توسط خود Copilot (Self-Review)


نویسنده: وحید نصیری
تاریخ: ۱۴۰۵/۰۵/۲۹ ۱۳:۰۳
آدرس: www.dntips.ir
پذیرش سریع کدهای تولیدشده یا تغییریافته توسط هوش مصنوعی بدون ارزیابی مرحله دوم، یکی از عوامل اصلی ورود باگ‌های خاموش به مخزن کد است. کدی که در نگاه اول «تمیزتر» یا «مدرن‌تر» به نظر می‌رسد، الزاماً رفتار سیستم را به درستی حفظ نکرده است (Looks cleaner <> Preserves behavior). وادار کردن Copilot به ایفای نقش یک مهندس ارشد و نقاد #C برای بازبینی کارهایی که خودش در مرحله قبل انجام داده، ترفندی بسیار موثر برای کشف ناسازگاری‌های رفتاری، شکستن قراردادها و افت کارایی پیش از مرحله Commit است.

ساختار پرامپت مهندسی برای بازبینی انتقادی (Self-Review Prompt)

بلافاصله پس از اینکه Copilot کدی را ایجاد کرد یا بازآفرینی (Refactor) انجام داد، از این پرامپت استفاده کنید:
تغییراتی که در مرحله قبل اعمال کردی را مانند یک مهندس ارشد، بدبین و سخت‌گیر #C بازبینی کن.
تمرکز ویژه روی موارد زیر:
۱. تغییرات رفتاری ناخواسته (Behavioral Changes): آیا جریان منطقی، ترتیب اجرای شروط یا رفتار در مقادیر مرزی تغییر کرده است؟
۲. پیچیدگی غیرضروری (Accidental Complexity): آیا ساختار بیش‌ازحد انتزاعی شده است؟
۳. خطاهای همزمانی و Race Conditions: آیا تغییرات روی منابع مشترک بدون قفل یا نخ‌های ناهمگام انجام شده است؟
۴. تهی‌پذیری و هشدارها (Nullability & NRT): آیا فرضیات جدیدی در مورد مقادیر null وارد کد شده که قبلاً مدیریت می‌شد؟
۵. افت کارایی (Performance Regressions): آیا تخصیص حافظه، کوئری‌های دیتابیس یا پیمایش‌ها سنگین‌تر شده‌اند؟
۶. شکستن قرارداد API (Breaking Changes): آیا امضای متدها، فیلدهای DTO یا کدهای وضعیت HTTP تغییر یافته‌اند؟
رویکردت کاملاً نقادانه باشد نه تدافعی.

تکنیک مقایسه رفتاری (Behavioral Diffing)

در سناریوهای بازآفرینی، مقایسه دو نسخه به شکل زیر بهترین راه برای کشف مغایرت‌هاست:
پیاده‌سازی اولیه را با نسخه بازنویسی‌شده مقایسه کن و تمام تفاوت‌های رفتاری زمان اجرا (Runtime Behavioral Differences) را با ذکر مثال فهرست کن.

کالبدشکافی یک نمونه واقعی در #C

فرض کنید کد اولیه به شکل زیر بوده است:
public decimal CalculateTotal(Order order)
{
    if (order == null) return 0m;
    if (order.Items == null || order.Items.Count == 0) return 0m;

    decimal sum = 0m;
    foreach (var item in order.Items)
    {
        if (item.Price > 0 && item.Quantity > 0)
            sum += item.Price * item.Quantity;
    }
    return sum;
}
و Copilot در گام اول آن را با یک خط LINQ «تمیز» جایگزین کرده است:
public decimal CalculateTotal(Order? order) =>
    order?.Items.Sum(x => x.Price * x.Quantity) ?? 0m;
هنگامی که پرامپت بازبینی انتقادی اجرا می‌شود، مدل خطاهای پنهان خود را آشکار می‌کند:

  • پرتاب خطای NullReferenceException: در کد بازنویسی‌شده، اگر order نال نباشد اما Items نال باشد، فراخوانی order.Items.Sum(...) کرش می‌کند، زیرا روی Items بررسی نال انجام نشده است (order?.Items?.Sum...).
  • عدم فیلتر مقادیر نامعتبر: در کد اولیه، قیمت‌ها یا تعداد منفی نادیده گرفته می‌شدند (item.Price > 0)، اما در کد جدید، قیمت‌های منفی در محاسبه مجموع لحاظ می‌شوند که یک تغییر رفتار بیزینسی مستقیم است.
  • تخصیص حافظه و Delegate: اجرای LINQ روی مجموعه‌ها سربار ساخت Delegate ایجاد می‌کند که در حلقه‌های پرتکرار ممکن است تخصیص حافظه اضافه داشته باشد.

بازنویسی نهایی پس از فیلتر بازبینی
public decimal CalculateTotal(Order? order)
{
    if (order?.Items is not { Count: > 0 } items)
    {
        return 0m;
    }

    return items
        .Where(item => item is { Price: > 0, Quantity: > 0 })
        .Sum(item => item.Price * item.Quantity);
}

نکات تکمیلی برای فرآیند Self-Review

  • بررسی تست‌های پوشش‌دهنده: بپرسید: «کدام رفتار موجود در کد اولیه توسط تست‌های فعلی پوشش داده نشده که ممکن بود با این تغییر بدون خطا رد شود؟»
  • ارزیابی استثناها: مطمئن شوید ترتیب پرتاب خطاهایی نظیر ArgumentNullException یا InvalidOperationException نسبت به کد اولیه جابه‌جا نشده باشد.
  • بررسی مدیریت منابع (IDisposable / IAsyncDisposable): بررسی کنید که آیا با بازنویسی کد، بلوک‌های using یا آزادسازی سوکت‌ها و اتصالات دستخوش تغییر شده‌اند یا خیر.

قاعده کلیدی: هرگز کدی را صرفاً به این دلیل که زیباتر یا خلاصه‌تر به نظر می‌رسد تأیید نکنید؛ با وادار کردن Copilot به بازبینی نقادانه خروجی خود، مرزهای رفتاری و پایداری سیستم را اعتبارسنجی کنید.