1. 골드 이체 서비스
난이도 중 해설 보기 →
결함을 모두 찾고 원인·수정안·더 나은 설계를 제시하라. 마커
(A)(B) 는 주목 위치 힌트다.
결함 코드 · C#
// ============================================================================
// [코드리뷰 문제] C# - 골드(재화) 이체 서비스
// ----------------------------------------------------------------------------
// 시나리오:
// - 플레이어가 다른 플레이어에게 골드를 보낼 수 있다(우편/직거래).
// - 거래소 정산, 퀘스트 보상 지급 등에서도 같은 이체 로직을 재사용한다.
// - 클라이언트는 "보내기" 버튼을 눌렀을 때 네트워크가 끊기면 재전송한다
// (같은 requestId 로 같은 요청을 여러 번 보낼 수 있다).
//
// 요구사항:
// - 여러 게임 스레드(IO 워커)가 동시에 서로 다른/같은 플레이어의 골드를
// 이체할 수 있다.
// - 서버 전체 골드 총합은 이체로 인해 늘거나 줄어선 안 된다(총합 보존).
// - 음수 골드는 존재할 수 없다.
// - 클라이언트 재전송으로 인한 중복 이체는 한 번만 반영되어야 한다(멱등성).
//
// 과제:
// 이 코드의 잠재적 문제를 모두 찾고, 왜 문제인지 설명하고,
// 수정안과 더 나은 설계를 제시하라.
// (먼저 직접 리뷰를 적은 뒤 answer.md 와 대조할 것)
// ============================================================================
using System;
using System.Collections.Generic;
public class Player
{
public long Id { get; }
public long Gold; // (A)
public readonly object Lock = new object();
public Player(long id, long gold) { Id = id; Gold = gold; }
}
public class GoldService
{
private readonly Dictionary<long, Player> _players;
// 이미 처리한 거래 요청 ID(멱등성 용도)
private readonly HashSet<Guid> _processed = new HashSet<Guid>();
public GoldService(Dictionary<long, Player> players)
{
_players = players;
}
// requestId: 클라이언트가 생성한 거래 요청 식별자(재전송 시 동일)
public bool Transfer(Guid requestId, long fromId, long toId, long amount)
{
if (amount <= 0) return false;
// (B) 멱등성 체크
if (_processed.Contains(requestId))
return true;
Player from = _players[fromId];
Player to = _players[toId];
// (C) 보내는 쪽 차감
lock (from.Lock)
{
if (from.Gold < amount)
return false;
from.Gold -= amount;
}
// (D) 받는 쪽 가산
lock (to.Lock)
{
to.Gold += amount;
}
// (E)
_processed.Add(requestId);
return true;
}
// 퀘스트 보상 등 시스템 지급(보내는 사람 없음)
public void Grant(long toId, long amount)
{
Player to = _players[toId];
lock (to.Lock)
{
to.Gold += amount; // (F)
}
}
public long TotalGold()
{
long sum = 0;
foreach (var p in _players.Values)
sum += p.Gold; // (G)
return sum;
}
} 결함 코드 · C++
// ============================================================================
// [코드리뷰 문제] C++ - 골드(재화) 이체 서비스
// ----------------------------------------------------------------------------
// 시나리오:
// - 플레이어가 다른 플레이어에게 골드를 보낼 수 있다(우편/직거래).
// - 거래소 정산, 퀘스트 보상 지급 등에서도 같은 이체 로직을 재사용한다.
// - 클라이언트는 "보내기" 버튼을 눌렀을 때 네트워크가 끊기면 재전송한다
// (같은 requestId 로 같은 요청을 여러 번 보낼 수 있다).
//
// 요구사항:
// - 여러 게임 스레드(IO 워커)가 동시에 서로 다른/같은 플레이어의 골드를
// 이체할 수 있다.
// - 서버 전체 골드 총합은 이체로 인해 늘거나 줄어선 안 된다(총합 보존).
// - 음수 골드는 존재할 수 없다.
// - 클라이언트 재전송으로 인한 중복 이체는 한 번만 반영되어야 한다(멱등성).
//
// 과제:
// 이 코드의 잠재적 문제를 모두 찾고, 왜 문제인지 설명하고,
// 수정안과 더 나은 설계를 제시하라.
// (먼저 직접 리뷰를 적은 뒤 answer.md 와 대조할 것)
// ============================================================================
#include <cstdint>
#include <string>
#include <mutex>
#include <unordered_map>
#include <unordered_set>
struct Player {
int64_t id;
int64_t gold; // (A)
std::mutex lock;
Player(int64_t id_, int64_t gold_) : id(id_), gold(gold_) {}
};
class GoldService {
public:
explicit GoldService(std::unordered_map<int64_t, Player*>& players)
: players_(players) {}
// requestId: 클라이언트가 생성한 거래 요청 식별자(재전송 시 동일)
bool Transfer(const std::string& requestId, int64_t fromId, int64_t toId, int64_t amount) {
if (amount <= 0) return false;
// (B) 멱등성 체크
if (processed_.count(requestId) > 0)
return true;
Player* from = players_.at(fromId);
Player* to = players_.at(toId);
// (C) 보내는 쪽 차감
{
std::lock_guard<std::mutex> lk(from->lock);
if (from->gold < amount)
return false;
from->gold -= amount;
}
// (D) 받는 쪽 가산
{
std::lock_guard<std::mutex> lk(to->lock);
to->gold += amount;
}
// (E)
processed_.insert(requestId);
return true;
}
// 퀘스트 보상 등 시스템 지급(보내는 사람 없음)
void Grant(int64_t toId, int64_t amount) {
Player* to = players_.at(toId);
std::lock_guard<std::mutex> lk(to->lock);
to->gold += amount; // (F)
}
int64_t TotalGold() {
int64_t sum = 0;
for (auto& kv : players_)
sum += kv.second->gold; // (G)
return sum;
}
private:
std::unordered_map<int64_t, Player*>& players_;
// 이미 처리한 거래 요청 ID(멱등성 용도)
std::unordered_set<std::string> processed_;
}; 내 리뷰 · C#
내 답안 · 자동 저장
작성 후 위 해설 보기에서 모범 해설과 대조하세요.
내 리뷰 · C++
내 답안 · 자동 저장
작성 후 위 해설 보기에서 모범 해설과 대조하세요.