2. 인벤토리 스택/수량 관리

난이도 중 해설 보기 →

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

결함 코드 · C#
// ============================================================================
// [코드리뷰 문제] C# - 인벤토리 스택/수량 관리
// ----------------------------------------------------------------------------
// 시나리오:
//   - 플레이어 인벤토리는 슬롯 배열이다. 각 슬롯에는 (itemId, count) 가 있다.
//   - 같은 itemId 끼리는 한 슬롯으로 합칠 수 있다(스택). 스택 상한은 999.
//   - 아이템 획득(AddItem), 두 슬롯 합치기(MergeStacks),
//     사용/판매로 수량 차감(RemoveCount) 을 지원한다.
//   - amount 는 서버 로직이 넘기는 값이다. 예: 레이드 보상 일괄 지급으로
//     같은 소재 아이템을 한 번에 5000 개 지급(AddItem(itemId, 5000)),
//     상점에서 묶음 판매로 한 번에 차감 등 "정상 도메인" 입력이 들어온다.
//
// 요구사항:
//   - 여러 게임 스레드가 같은 인벤토리에 동시에 접근할 수 있다(전투 보상 +
//     상점 구매가 동시에 들어옴).
//   - 한 스택 수량은 1..999 범위를 벗어날 수 없다(상한 강제).
//   - 인벤토리 전체 아이템 개수(서버 보유량)는 잘못 늘거나 줄어선 안 된다.
//
// 과제:
//   이 코드의 잠재적 문제를 모두 찾고, 왜 문제인지 설명하고,
//   수정안과 더 나은 설계를 제시하라.
//   (먼저 직접 리뷰를 적은 뒤 answer.md 와 대조할 것)
// ============================================================================

using System;

public struct Slot
{
    public uint ItemId;      // 0 = 빈 슬롯
    public ushort Count;     // (A)
}

public class Inventory
{
    private const ushort MAX_STACK = 999;

    private readonly Slot[] _slots;
    private readonly object _lock = new object();

    public Inventory(int slotCount) { _slots = new Slot[slotCount]; }

    // 슬롯 하나를 조회(UI 표시 등에서 사용)
    public Slot GetSlot(int index)
    {
        lock (_lock)
        {
            return _slots[index];     // (B) 값 복사 반환
        }
    }

    // 아이템 획득: 같은 itemId 슬롯이 있으면 거기에 더하고, 없으면 빈 슬롯에 생성
    public bool AddItem(uint itemId, ushort amount)
    {
        lock (_lock)
        {
            for (int i = 0; i < _slots.Length; i++)
            {
                if (_slots[i].ItemId == itemId)
                {
                    _slots[i].Count += amount;        // (C)
                    return true;
                }
            }
            for (int i = 0; i < _slots.Length; i++)
            {
                if (_slots[i].ItemId == 0)
                {
                    _slots[i].ItemId = itemId;
                    _slots[i].Count = amount;         // (D)
                    return true;
                }
            }
            return false; // 인벤토리 가득 참
        }
    }

    // 두 슬롯 합치기: src 의 수량을 dst 로 옮긴다(같은 아이템이라고 가정)
    public bool MergeStacks(int src, int dst)
    {
        lock (_lock)
        {
            Slot a = _slots[src];                     // (E) 값 복사
            Slot b = _slots[dst];

            ushort total = (ushort)(a.Count + b.Count);   // (F)
            if (total <= MAX_STACK)
            {
                b.Count = total;
                a.ItemId = 0;
                a.Count = 0;
            }
            else
            {
                b.Count = MAX_STACK;
                a.Count = (ushort)(total - MAX_STACK);     // (G)
            }
            _slots[src] = a;
            _slots[dst] = b;
            return true;
        }
    }

    // 수량 차감(사용/판매)
    public bool RemoveCount(int slot, ushort amount)
    {
        lock (_lock)
        {
            _slots[slot].Count -= amount;             // (H)
            if (_slots[slot].Count == 0)
                _slots[slot].ItemId = 0;
            return true;
        }
    }
}
내 리뷰 · C#
내 답안 · 자동 저장

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