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#
내 답안 · 자동 저장

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