6. 우편함 선물/보상 수령

난이도 하 해설 보기 →

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

결함 코드 · C#
// ============================================================================
// [코드리뷰 문제] C# - 우편함 선물/보상 수령
// ----------------------------------------------------------------------------
// 시나리오:
//   - 플레이어는 우편함에서 첨부된 보상(골드/아이템)을 "수령(Claim)" 한다.
//   - 한 통의 우편은 한 번만 수령할 수 있다(첨부물은 1회성).
//   - "전체 수령" 버튼은 우편함의 모든 우편을 한 번에 수령한다.
//   - 클라이언트는 응답이 늦으면 같은 수령 요청을 재전송할 수 있다.
//   - 여러 IO 스레드가 같은 플레이어의 우편 패킷을 동시에 처리할 수 있다
//     (예: 모바일에서 두 기기로 동시 접속, 또는 더블클릭).
//
// 요구사항:
//   - 같은 우편의 첨부 보상은 정확히 한 번만 지급되어야 한다(중복 수령 금지).
//   - 수령에 성공하면 우편은 "수령됨" 으로 표시되고 첨부물은 비워진다.
//   - 보상 지급(골드 가산/아이템 추가)이 끝난 뒤에 "수령됨" 으로 확정돼야 한다.
//
// 과제:
//   이 코드의 잠재적 문제를 모두 찾고, 왜 문제인지 설명하고,
//   수정안과 더 나은 설계를 제시하라.
//   (먼저 직접 리뷰를 적은 뒤 answer.md 와 대조할 것)
// ============================================================================

using System;
using System.Collections.Generic;

public class MailAttachment
{
    public long Gold;
    public List<(int itemId, int count)> Items = new();
}

public class Mail
{
    public long MailId;
    public MailAttachment Attachment;
    public bool Claimed;                 // (A)
}

public class Player
{
    public long Id;
    public long Gold;
    public readonly object Lock = new object();
    public List<Mail> Mailbox = new();
    public void GiveItem(int itemId, int count) { /* 인벤토리 추가 (생략) */ }
}

public class MailService
{
    private readonly Dictionary<long, Player> _players;
    public MailService(Dictionary<long, Player> players) { _players = players; }

    // 우편 한 통 수령
    public bool Claim(long playerId, long mailId)
    {
        Player p = _players[playerId];
        Mail mail = p.Mailbox.Find(m => m.MailId == mailId);   // (B)
        if (mail == null) return false;

        if (mail.Claimed) return false;                        // (C)

        // (D) 보상 지급
        var att = mail.Attachment;
        p.Gold += att.Gold;
        foreach (var (itemId, count) in att.Items)
            p.GiveItem(itemId, count);

        // (E) 수령 표시
        mail.Claimed = true;
        return true;
    }

    // 전체 수령
    public void ClaimAll(long playerId)
    {
        Player p = _players[playerId];
        foreach (var mail in p.Mailbox)                        // (F)
        {
            if (mail.Claimed) continue;
            Claim(playerId, mail.MailId);                      // (G)
        }
    }
}
내 리뷰 · C#
내 답안 · 자동 저장

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