3. 우편 대량 발송 재시도 시 중복 지급 (멱등성 단위 오류)

난이도 상 해설 보기 →

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

결함 코드 · C#
// ============================================================================
// [코드리뷰 문제] C# - 우편 대량 발송(운영자 보상) 재시도 시 중복 지급
// ----------------------------------------------------------------------------
// 시나리오 (영속화/DB · 운영 도구):
//   - 운영자가 이벤트 보상을 수천 명에게 우편으로 일괄 발송한다(한 배치 = batchId).
//   - 각 수신자에게: 보상(아이템/골드)을 우편함에 넣고 DB에 기록한다.
//   - 발송 도중 일부 수신자에서 DB 타임아웃/일시 오류가 날 수 있고, 그러면 운영자(또는
//     자동 재시도)가 "같은 배치"를 다시 발송한다.
//
// 요구사항:
//   - 한 배치의 각 수신자는 보상을 정확히 한 번만 받아야 한다(중복 지급 금지).
//   - 중간에 실패해 재시도해도, 이미 받은 사람은 다시 받지 않아야 한다.
//
// 과제:
//   이 코드의 잠재적 문제를 모두 찾고, 1000명 중 600번째에서 예외가 나 배치를 재시도할 때
//   누가 보상을 몇 번 받는지 설명하고, 수정안과 더 나은 설계를 제시하라.
//   (먼저 직접 리뷰를 적은 뒤 answer.md 와 대조할 것)
// ============================================================================

using System;
using System.Collections.Generic;

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

public interface IMailDb
{
    bool IsBatchSent(long batchId);
    void InsertMail(long recipientId, long batchId, RewardSpec reward);
    void MarkBatchSent(long batchId);
}

public interface IInventory
{
    void Grant(long playerId, RewardSpec reward);   // 아이템/골드 지급
}

public class MailSender
{
    private readonly IMailDb    _db;
    private readonly IInventory _inv;

    public MailSender(IMailDb db, IInventory inv) { _db = db; _inv = inv; }

    // 운영자/스케줄러가 호출. 실패하면 같은 인자로 다시 호출될 수 있다.
    public void SendBatch(long batchId, List<long> recipients, RewardSpec reward)
    {
        if (_db.IsBatchSent(batchId))           // (A) 배치 단위 발송 여부
            return;

        foreach (var r in recipients)
        {
            _inv.Grant(r, reward);              // (B) 보상 지급
            _db.InsertMail(r, batchId, reward); // (C) 우편 기록
        }

        _db.MarkBatchSent(batchId);             // (D) 배치 완료 표시
    }
}
내 리뷰 · C#
내 답안 · 자동 저장

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