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++
// ============================================================================
// [코드리뷰 문제] C++ - 우편함 선물/보상 수령
// ----------------------------------------------------------------------------
// 시나리오:
// - 플레이어는 우편함에서 첨부된 보상(골드/아이템)을 "수령(Claim)" 한다.
// - 한 통의 우편은 한 번만 수령할 수 있다(첨부물은 1회성).
// - "전체 수령" 버튼은 우편함의 모든 우편을 한 번에 수령한다.
// - 클라이언트는 응답이 늦으면 같은 수령 요청을 재전송할 수 있다.
// - 여러 IO 스레드가 같은 플레이어의 우편 패킷을 동시에 처리할 수 있다
// (예: 모바일에서 두 기기로 동시 접속, 또는 더블클릭).
//
// 요구사항:
// - 같은 우편의 첨부 보상은 정확히 한 번만 지급되어야 한다(중복 수령 금지).
// - 수령에 성공하면 우편은 "수령됨" 으로 표시되고 첨부물은 비워진다.
// - 보상 지급(골드 가산/아이템 추가)이 끝난 뒤에 "수령됨" 으로 확정돼야 한다.
//
// 과제:
// 이 코드의 잠재적 문제를 모두 찾고, 왜 문제인지 설명하고,
// 수정안과 더 나은 설계를 제시하라.
// (먼저 직접 리뷰를 적은 뒤 answer.md 와 대조할 것)
// ============================================================================
#include <cstdint>
#include <vector>
#include <utility>
#include <unordered_map>
#include <mutex>
#include <algorithm>
struct MailAttachment {
int64_t gold = 0;
std::vector<std::pair<int, int>> items; // (itemId, count)
};
struct Mail {
int64_t mailId;
MailAttachment attachment;
bool claimed = false; // (A)
};
struct Player {
int64_t id;
int64_t gold = 0;
std::mutex lock;
std::vector<Mail> mailbox;
void GiveItem(int itemId, int count) { /* 인벤토리 추가 (생략) */ }
};
class MailService {
public:
explicit MailService(std::unordered_map<int64_t, Player*>& players)
: players_(players) {}
// 우편 한 통 수령
bool Claim(int64_t playerId, int64_t mailId) {
Player* p = players_.at(playerId);
Mail* mail = nullptr;
for (auto& m : p->mailbox) { // (B)
if (m.mailId == mailId) { mail = &m; break; }
}
if (mail == nullptr) return false;
if (mail->claimed) return false; // (C)
// (D) 보상 지급
auto& att = mail->attachment;
p->gold += att.gold;
for (auto& it : att.items)
p->GiveItem(it.first, it.second);
// (E) 수령 표시
mail->claimed = true;
return true;
}
// 전체 수령
void ClaimAll(int64_t playerId) {
Player* p = players_.at(playerId);
for (auto& mail : p->mailbox) { // (F)
if (mail.claimed) continue;
Claim(playerId, mail.mailId); // (G)
}
}
private:
std::unordered_map<int64_t, Player*>& players_;
}; 내 리뷰 · C#
내 답안 · 자동 저장
작성 후 위 해설 보기에서 모범 해설과 대조하세요.
내 리뷰 · C++
내 답안 · 자동 저장
작성 후 위 해설 보기에서 모범 해설과 대조하세요.