عنوان:

‫بزرگ‌ترین اشتباه: تلقی کردن Copilot به عنوان یک توسعه‌دهنده ارشد مستقل


نویسنده: وحید نصیری
تاریخ: ۱۴۰۵/۰۵/۲۹ ۱۳:۱۱
آدرس: www.dntips.ir
یکی از خطرناک‌ترین سوءبرداشت‌ها پیرامون ابزارهای هوش مصنوعی، فرض کردن آن‌ها به عنوان یک «توسعه‌دهنده ارشد خودگردان و مستقل» است. GitHub Copilot قادر است کدهایی تولید کند که در ظاهر بسیار منطقی، تمیز، دارای تورفتگی‌های دقیق و از نظر سینتکسی کاملاً بی‌نقص به نظر می‌رسند؛ اما کد موجه و قانع‌کننده (Convincing Code) الزاماً کد صحیح و قابل‌اعتماد (Correct Code) نیست.

مدل‌های هوش مصنوعی فاقد درک شهودی از بستر کسب‌وکار هستند و می‌توانند مفاهیم بنیادین زیر را به کلی نادیده بگیرند یا اشتباه تفسیر کنند:

  • قوانین و منطق کسب‌وکار (Business Rules)
  • نامتغیرهای دامنه (Domain Invariants)
  • مرزهای امنیتی و کنترل دسترسی
  • الزامات کارایی و رفتار سیستم زیر بار سنگین
  • محدودیت‌های فنی و تاریخی سیستم (Legacy Constraints)
  • معناشناسی تراکنش‌ها (Transaction Semantics)
  • واقعیت‌های استقرار، مانیتورینگ و عملیات (Operational Realities)

کالبدشکافی یک نمونه واقعی: متد انتقال وجه بانکی

پیاده‌سازی به ظاهر ساده و تمیز زیر را در نظر بگیرید که یک مدل هوش مصنوعی ممکن است بلافاصله پیشنهاد دهد:
public async Task<bool> TransferMoneyAsync(
    int sourceId, 
    int destinationId, 
    decimal amount)
{
    var source = await GetAccountAsync(sourceId);
    var destination = await GetAccountAsync(destinationId);

    source.Balance -= amount;
    destination.Balance += amount;

    await SaveChangesAsync();
    return true;
}
این کد در آزمون‌های سطحی و مسیر موفق (Happy Path) به درستی کار می‌کند؛ اما در یک سناریوی واقعی مالی، انبوهی از فجایع پنهان را به همراه دارد:
  • موجودی ناکافی: هیچ بررسی انجام نشده که آیا حساب مبدأ اصلاً موجودی کافی دارد یا خیر (source.Balance >= amount).
  • انتقال به خود: اگر sourceId == destinationId باشد چه اتفاقی می‌افتد؟
  • شکست در واکشی حساب مقصد: اگر حساب مبدأ خوانده شود اما حساب مقصد وجود نداشته باشد، وضعیت چه خواهد بود؟
  • مرزهای تراکنش و ایزولاسیون (ACID): در صورت بروز خطا در زمان ذخیره‌سازی، آیا تضمینی برای Rollback شدن عملیات وجود دارد؟
  • چالش‌های همزمانی (Race Conditions / Lost Updates): اگر دو درخواست هم‌زمان برای انتقال از یک حساب ارسال شود، پدیده خواندن کثیف یا نادیده گرفته شدن کسر موجودی رخ می‌دهد. آیا از Pessimistic Lock یا Optimistic Concurrency با ردیابی نسخه (RowVersion) استفاده شده است؟
  • تکرارپذیری امن (Idempotency): اگر در زمان ارسال پاسخ شبکه قطع شود و کاربر دکمه انتقال را مجدداً بزند، آیا پول دوباره کسر می‌شود؟
  • حسابرسی و لاگ مالی (Audit Trail): هیچ لاگ تراکنش یا رویداد مالی برای پیگیری‌های بعدی ثبت نمی‌شود.

بازنویسی استاندارد با اعمال نامتغیرهای دامنه و تراکنش‌های امن در #C
public sealed record TransferMoneyResult(bool IsSuccess, string? ErrorMessage = null);

public async Task<TransferMoneyResult> TransferMoneyAsync(
    Guid sourceAccountId,
    Guid destinationAccountId,
    decimal amount,
    string idempotencyKey,
    CancellationToken cancellationToken)
{
    // ۱. اعتبارسنجی مقادیر مرزی و منطق پایه
    if (amount <= 0m)
        return new(false, "Transfer amount must be strictly positive.");

    if (sourceAccountId == destinationAccountId)
        return new(false, "Source and destination accounts cannot be identical.");

    // ۲. مدیریت صریح تراکنش پایگاه داده با سطح ایزولاسیون مناسب
    await using var transaction = await dbContext.Database.BeginTransactionAsync(
        IsolationLevel.ReadCommitted, 
        cancellationToken);

    try
    {
        // ۳. بررسی Idempotency برای جلوگیری از کسر تکراری وجه
        var isDuplicate = await dbContext.TransferRecords
            .AnyAsync(x => x.IdempotencyKey == idempotencyKey, cancellationToken);
            
        if (isDuplicate)
            return new(false, "Duplicate transaction detected.");

        // ۴. واکشی موجودیت‌ها به همراه بررسی همزمانی
        var source = await dbContext.Accounts
            .FirstOrDefaultAsync(x => x.Id == sourceAccountId, cancellationToken);
            
        var destination = await dbContext.Accounts
            .FirstOrDefaultAsync(x => x.Id == destinationAccountId, cancellationToken);

        if (source is null || destination is null)
            return new(false, "One or both accounts do not exist.");

        // ۵. بررسی نامتغیر دامنه (Domain Invariant)
        if (source.Balance < amount)
            return new(false, "Insufficient funds in source account.");

        // ۶. اعمال تغییرات و ثبت رکورد حسابرسی
        source.Balance -= amount;
        destination.Balance += amount;

        dbContext.TransferRecords.Add(new TransferRecord(
            Guid.NewGuid(), 
            sourceAccountId, 
            destinationAccountId, 
            amount, 
            idempotencyKey, 
            DateTime.UtcNow));

        await dbContext.SaveChangesAsync(cancellationToken);
        await transaction.CommitAsync(cancellationToken);

        return new(true);
    }
    catch (DbUpdateConcurrencyException ex)
    {
        logger.LogError(ex, "Concurrency conflict during fund transfer from {SourceId}", sourceAccountId);
        await transaction.RollbackAsync(cancellationToken);
        return new(false, "A concurrent modification occurred. Please retry.");
    }
    catch (Exception ex)
    {
        logger.LogCritical(ex, "Unexpected failure during fund transfer.");
        await transaction.RollbackAsync(cancellationToken);
        throw;
    }
}

جایگاه واقعی GitHub Copilot در مهندسی نرم‌افزار

Copilot یک جایگزین برای تفکر معماری نیست؛ بلکه یک ابزار قدرتمند برای تسریع در نوشتن کد، کشف ابعاد پنهان و فرموله‌سازی پرسش‌ها است.
  • هوش مصنوعی می‌تواند به شما در یافتن حالات مرزی و طرح سوالات نقادانه کمک کند.
  • اما هوش مصنوعی هرگز نمی‌تواند «نیاز به پرسیدن این سوالات» و «مسئولیت ارزیابی نهایی» را از روی دوش مهندس نرم‌افزار بردارد.

قاعده کلیدی و جمع‌بندی نهایی: هوش مصنوعی ابزار دستان شماست، نه مغز تصمیم‌گیر سیستم؛ کدهای تولیدشده را همواره با بدبینی مهندسی بازبینی کنید و مسئولیت صحت دامنه، امنیت و پایداری را شخصاً بر عهده بگیرید.