36. 거래 확정(confirm) 토글과 제안 변경 경합 (미끼 교환 / bait-and-switch, C#)

난이도 하 해설 보기 →

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

결함 코드 · C#
// 시나리오: MMORPG 1:1 직거래(트레이드) 창. 두 플레이어가 각자 아이템을 창에 올리고,
// 서로 "확정(Confirm)" 버튼을 누른다. 양쪽이 모두 확정하면 서버가 거래를 성사시켜
// 아이템을 맞바꾼다. 거래 내용은 확정 전까지 언제든 바꿀 수 있다(품목 추가/교체/제거).
//
// 요구사항:
//   - SetOffer: 자기 쪽 제안 목록을 통째로 교체한다(거래 진행 중 내용 변경 허용).
//   - Confirm: 자기 쪽을 확정 상태로 만든다.
//   - TryExecute: 양쪽이 확정이면 제안 목록대로 소유권을 맞바꾼다.
//   - 한 거래 세션에는 두 플레이어의 요청이 서로 다른 처리 스레드에서 들어올 수 있다.
//
// 과제: (A)(B) 지점을 검토하라. 특정 요청 순서·타이밍에서 한쪽이 "본 것과 다른" 거래를
//       성사시킬 수 있다. 어떤 조건에서 그렇게 되는지 밝히고 고쳐라.

using System.Collections.Generic;

public sealed class Player
{
    public readonly long Id;
    public readonly HashSet<long> Inventory = new();  // 보유 아이템 인스턴스 ID
    public Player(long id) { Id = id; }
}

public sealed class TradeSide
{
    public readonly Player Owner;
    public List<long> Offer = new();   // 이 사람이 내놓은 아이템 인스턴스 ID
    public bool Confirmed = false;
    public TradeSide(Player p) { Owner = p; }
}

public sealed class TradeSession
{
    private readonly TradeSide _a, _b;
    public TradeSession(Player a, Player b) { _a = new TradeSide(a); _b = new TradeSide(b); }

    private TradeSide SideOf(long playerId) => _a.Owner.Id == playerId ? _a : _b;

    // 플레이어가 자기 제안 목록을 교체
    public void SetOffer(long playerId, List<long> items)
    {
        var side = SideOf(playerId);
        side.Offer = items;                 // (A)
    }

    public void Confirm(long playerId)
    {
        SideOf(playerId).Confirmed = true;
    }

    // 양쪽 확정 시 소유권 맞바꿈
    public bool TryExecute()
    {
        if (!_a.Confirmed || !_b.Confirmed) return false;   // (B)

        foreach (var id in _a.Offer) { _a.Owner.Inventory.Remove(id); _b.Owner.Inventory.Add(id); }
        foreach (var id in _b.Offer) { _b.Owner.Inventory.Remove(id); _a.Owner.Inventory.Add(id); }
        return true;
    }
}
내 리뷰 · C#
내 답안 · 자동 저장

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