7. 한정 상점 구매와 재고 정합성

난이도 중 해설 보기 →

결함을 모두 찾고 원인·수정안·더 나은 설계를 제시하라. 마커 (A)(B) 는 주목 위치 힌트다.

결함 코드 · C#
// ============================================================================
// [코드리뷰 문제] C# - 한정 상점 구매와 재고 정합성
// ----------------------------------------------------------------------------
// 시나리오:
//   - 한정 상점(flash sale)에서 아이템을 판다. 각 상품은 잔여 재고(stock)가 있다.
//   - 구매 시: 재고가 있으면 골드를 차감하고 재고를 1 줄이고 아이템을 지급한다.
//   - 인당 구매 한도(perPlayerLimit)가 있다. 한 플레이어가 그 이상 살 수 없다.
//   - 수많은 IO 스레드가 오픈 직후 같은 상품에 동시에 구매 요청을 쏟아붓는다.
//
// 요구사항:
//   - 재고보다 더 팔리면 안 된다(oversell 금지).
//   - 골드 차감과 재고 차감과 아이템 지급은 함께 일어나야 한다(원자성).
//   - 인당 한도를 초과 구매할 수 없다.
//   - 골드 부족/재고 소진/한도 초과 시 아무것도 변하지 않아야 한다.
//
// 과제:
//   이 코드의 잠재적 문제를 모두 찾고, 왜 문제인지 설명하고,
//   수정안과 더 나은 설계를 제시하라.
//   (먼저 직접 리뷰를 적은 뒤 answer.md 와 대조할 것)
// ============================================================================

using System;
using System.Collections.Generic;
using System.Threading;

public class Player
{
    public long Id;
    public long Gold;
    public readonly object Lock = new object();
    public void GiveItem(long itemId, int count) { /* 인벤토리 추가 (생략) */ }
}

public class ShopItem
{
    public long ItemId;
    public long Price;
    public int  Stock;                                   // (A) 잔여 재고
    public int  PerPlayerLimit;
    // 플레이어별 누적 구매 수량
    public Dictionary<long, int> Bought = new();         // (B)
}

public class ShopService
{
    // 구매. 성공하면 true.
    public bool Buy(ShopItem item, Player buyer, int qty)
    {
        if (qty <= 0) return false;

        // (C) 재고 확인
        if (Volatile.Read(ref item.Stock) < qty)
            return false;

        // (D) 인당 한도 확인
        int already = item.Bought.TryGetValue(buyer.Id, out var c) ? c : 0;
        if (already + qty > item.PerPlayerLimit)
            return false;

        // (E) 골드 확인/차감
        long cost = item.Price * qty;
        lock (buyer.Lock)
        {
            if (buyer.Gold < cost)
                return false;
            buyer.Gold -= cost;
        }

        // (F) 재고 차감
        Interlocked.Add(ref item.Stock, -qty);

        // (G) 누적 구매 갱신
        item.Bought[buyer.Id] = already + qty;

        // (H) 아이템 지급
        buyer.GiveItem(item.ItemId, qty);
        return true;
    }

    private readonly object _shopLock = new object();   // (I)
}
내 리뷰 · C#
내 답안 · 자동 저장

작성 후 위 해설 보기에서 모범 해설과 대조하세요.