50. 경매장 즉시구매와 가격 인하의 동시 처리 (이중 판매 / 가격 불일치)

난이도 하 해설 보기 →

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

결함 코드 · C#
// ============================================================================
// [코드리뷰 문제] 경매장 즉시구매(buyout)와 판매자 가격 인하가 동시에 일어나는 상황
// ----------------------------------------------------------------------------
// 시나리오:
//   MMORPG 경매장에서 각 매물(Listing)은 즉시구매가(BuyoutPrice)를 가진다.
//   - 구매자는 "즉시구매" 버튼을 눌러 표시된 가격에 매물을 산다.
//   - 판매자는 매물이 팔리기 전 언제든 즉시구매가를 내려(또는 올려) 수정할 수 있다.
//   여러 지역/스레드에서 오는 요청을 서버가 동시에 처리한다.
//
// 요구사항:
//   - 하나의 매물은 정확히 한 번만 판매되어야 한다(이중 판매/이중 지급 금지).
//   - 구매자가 실제로 지불하는 금액과 아이템 지급, 판매자 정산은 서로 어긋나지 않아야 한다.
//   - 여러 요청이 동시에 같은 매물을 건드려도 상태가 깨지지 않아야 한다.
//
// 과제:
//   아래 AuctionHouse 의 BuyoutAsync 와 EditBuyoutPrice 를 검토하라.
//   (A)(B) 위치를 중심으로 동시 요청이 겹칠 때 어떤 결과가 나올 수 있는지,
//   그리고 어떻게 고쳐야 하는지 설명하라.
// ============================================================================

using System.Collections.Generic;

public sealed class Listing
{
    public long Id;
    public long SellerId;
    public int ItemId;
    public long BuyoutPrice;
    public bool Sold;
}

public sealed class AuctionHouse
{
    private readonly Dictionary<long, Listing> _listings = new();
    private readonly IWallet _wallet;
    private readonly IInventory _inv;

    public AuctionHouse(IWallet wallet, IInventory inv) { _wallet = wallet; _inv = inv; }

    public BuyResult Buyout(long buyerId, long listingId)
    {
        if (!_listings.TryGetValue(listingId, out var listing))   // (A)
            return BuyResult.NotFound;
        if (listing.Sold)
            return BuyResult.AlreadySold;

        long price = listing.BuyoutPrice;
        if (_wallet.GetGold(buyerId) < price)
            return BuyResult.NotEnoughGold;

        _wallet.Deduct(buyerId, price);
        listing.Sold = true;                                      // (B)
        _inv.Give(buyerId, listing.ItemId, 1);
        _wallet.Add(listing.SellerId, price);
        return BuyResult.Ok;
    }

    public void EditBuyoutPrice(long sellerId, long listingId, long newPrice)
    {
        if (_listings.TryGetValue(listingId, out var listing) && listing.SellerId == sellerId)
            listing.BuyoutPrice = newPrice;
    }
}

public enum BuyResult { Ok, NotFound, AlreadySold, NotEnoughGold }
public interface IWallet { long GetGold(long id); void Deduct(long id, long amt); void Add(long id, long amt); }
public interface IInventory { void Give(long id, int itemId, int count); }
내 리뷰 · C#
내 답안 · 자동 저장

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