28. 가방 정렬 중에 다른 경로로 같은 아이템이 변경되는 상황

난이도 하 해설 보기 →

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

결함 코드 · C#
// ============================================================================
// [코드리뷰 문제] C# - 가방 정렬 중에 다른 경로로 같은 아이템이 변경되는 상황
// ----------------------------------------------------------------------------
// 시나리오 (콘텐츠/게임로직 · 서버-클라):
//   - 플레이어는 인벤토리(가방)를 "정렬(Sort)" 할 수 있다. 정렬은 슬롯을 아이템 종류
//     순으로 재배치하고, 수량이 0인 빈 슬롯을 정리(압축)한다.
//   - 정렬은 슬롯 수가 많으면 시간이 걸리는 작업이다(재배치 + 압축).
//   - 정렬을 누른 직후/도중에 같은 플레이어가 다른 경로로 아이템을 변경할 수 있다:
//     사용(Consume)/판매(Sell)/버리기(Drop) 는 모두 "수량 차감, 0이면 슬롯 제거" 로
//     귀결된다. 이 요청들은 서로 다른 요청/스레드로 들어올 수 있다.
//
// 요구사항:
//   - 정렬 전후로 플레이어가 실제로 가진 아이템의 종류·수량 총합은 보존돼야 한다
//     (정렬은 "배치"만 바꾼다. 아이템을 만들거나 없애지 않는다).
//   - 사용/판매/버리기로 차감·제거된 아이템이 정렬 때문에 되살아나면 안 된다.
//   - 동시 요청이 와도 인벤토리 상태가 깨지거나 예외로 죽으면 안 된다.
//
// 과제:
//   이 코드의 잠재적 문제를 모두 찾고, 왜/어떻게 아이템이 복제되거나(되살아나거나)
//   사라지거나, 순회 중 예외가 나는지 설명하고, 수정안과 더 나은 설계를 제시하라.
//   (먼저 직접 리뷰를 적은 뒤 answer.md 와 대조할 것)
// ============================================================================

using System;
using System.Collections.Generic;

public class InventorySlot
{
    public long ItemId;
    public int  Count;
}

public class Inventory
{
    public List<InventorySlot> Slots = new();
}

public class InventoryService
{
    private readonly Dictionary<long, Inventory> _inv = new();   // playerId -> Inventory

    // 가방 정렬: 아이템 종류 순 정렬 + 빈 슬롯 압축
    public void SortBag(long playerId)
    {
        if (!_inv.TryGetValue(playerId, out var inv)) return;

        var working = inv.Slots;                                 // (A)
        working.Sort((a, b) => a.ItemId.CompareTo(b.ItemId));

        // 빈 슬롯 압축(아래는 의도적으로 슬롯 수만큼 도는 작업)
        var compacted = new List<InventorySlot>(working.Count);
        foreach (var s in working)                               // (B)
        {
            if (s.Count > 0)
                compacted.Add(s);
        }
        inv.Slots = compacted;
    }

    // 사용/판매/버리기: 수량 차감, 0이면 슬롯 제거
    public bool ConsumeItem(long playerId, long itemId, int amount)
    {
        if (!_inv.TryGetValue(playerId, out var inv)) return false;

        var slot = inv.Slots.Find(s => s.ItemId == itemId);     // (C)
        if (slot == null || slot.Count < amount) return false;

        slot.Count -= amount;
        if (slot.Count == 0)
            inv.Slots.Remove(slot);
        return true;
    }
}
내 리뷰 · C#
내 답안 · 자동 저장

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