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++
// ============================================================================
// [코드리뷰 문제] C++ - 가방 정렬 중에 다른 경로로 같은 아이템이 변경되는 상황
// ----------------------------------------------------------------------------
// 시나리오 (콘텐츠/게임로직 · 서버-클라):
// - 플레이어는 인벤토리(가방)를 "정렬(SortBag)" 할 수 있다. 정렬은 슬롯을 아이템 종류
// 순으로 재배치하고, 수량이 0인 빈 슬롯을 정리(압축)한다.
// - 정렬은 슬롯 수가 많으면 시간이 걸린다(정렬 + 압축).
// - 정렬을 누른 직후/도중에 같은 플레이어가 다른 경로로 아이템을 변경할 수 있다:
// 사용/판매/버리기(ConsumeItem) 는 "수량 차감, 0이면 슬롯 제거" 로 귀결된다.
// 이 요청들은 서로 다른 요청/스레드로 들어올 수 있다.
//
// 요구사항:
// - 정렬 전후로 플레이어가 실제로 가진 아이템의 종류·수량 총합은 보존돼야 한다.
// - 사용/판매/버리기로 차감·제거된 아이템이 정렬 때문에 되살아나면 안 된다.
// - 동시 요청이 와도 인벤토리 상태가 깨지거나 비정상 종료되면 안 된다.
//
// 과제:
// 이 코드의 잠재적 문제를 모두 찾고, 동시 변경이 일어날 때 어떤 일이 벌어지는지
// 설명하고, 수정안과 더 나은 설계를 제시하라.
// (먼저 직접 리뷰를 적은 뒤 answer.md 와 대조할 것)
// ============================================================================
#include <vector>
#include <algorithm>
#include <unordered_map>
#include <cstdint>
struct InventorySlot
{
int64_t itemId;
int count;
};
struct Inventory
{
std::vector<InventorySlot> slots;
};
class InventoryService
{
std::unordered_map<int64_t, Inventory> inv_; // playerId -> Inventory
public:
// 가방 정렬: 아이템 종류 순 정렬 + 빈 슬롯 압축
void SortBag(int64_t playerId)
{
auto it = inv_.find(playerId);
if (it == inv_.end()) return;
auto& slots = it->second.slots; // (A)
std::sort(slots.begin(), slots.end(),
[](const InventorySlot& a, const InventorySlot& b) {
return a.itemId < b.itemId;
});
for (auto i = slots.begin(); i != slots.end(); ) // (B)
{
if (i->count <= 0) i = slots.erase(i);
else ++i;
}
}
// 사용/판매/버리기: 수량 차감, 0이면 슬롯 제거
bool ConsumeItem(int64_t playerId, int64_t itemId, int amount)
{
auto it = inv_.find(playerId);
if (it == inv_.end()) return false;
auto& slots = it->second.slots;
InventorySlot* target = nullptr; // (C)
for (auto& s : slots)
{
if (s.itemId == itemId) { target = &s; break; }
}
if (!target || target->count < amount) return false;
target->count -= amount;
if (target->count == 0)
slots.erase(slots.begin() + (target - slots.data())); // (D)
return true;
}
}; 내 리뷰 · C#
내 답안 · 자동 저장
작성 후 위 해설 보기에서 모범 해설과 대조하세요.
내 리뷰 · C++
내 답안 · 자동 저장
작성 후 위 해설 보기에서 모범 해설과 대조하세요.