9. 아이템 강화(인챈트) 원자성과 실패 처리
난이도 상 해설 보기 →
결함을 모두 찾고 원인·수정안·더 나은 설계를 제시하라. 마커
(A)(B) 는 주목 위치 힌트다.
결함 코드 · C#
// ============================================================================
// [코드리뷰 문제] C# - 아이템 강화(인챈트) 원자성과 실패 처리
// ----------------------------------------------------------------------------
// 시나리오:
// - 장비를 강화한다. 강화에는 소재(stone)와 골드가 든다.
// - 강화는 확률적으로 성공/실패한다. 성공하면 +1, 실패하면 단계 유지 또는
// 하락(정책에 따라). 어느 쪽이든 소재와 골드는 소모된다(시도 비용).
// - 강화 결과 연출을 위해 클라이언트가 "성공/실패" 와 굴림값을 함께 보낸다
// (클라가 먼저 연출을 돌리고 결과를 서버에 통보하는 구조).
// - 여러 IO 스레드가 같은 플레이어의 강화 패킷을 처리할 수 있다.
//
// 요구사항:
// - 강화는 원자적이어야 한다: (소재/골드 소모 + 단계 변경)이 함께 일어나거나
// 전혀 일어나지 않거나.
// - 소재/골드가 부족하면 아무것도 변하지 않아야 한다.
// - 성공/실패 결과는 서버가 권위 있게 결정해야 한다(조작 불가).
//
// 과제:
// 이 코드의 잠재적 문제를 모두 찾고, 왜 문제인지 설명하고,
// 수정안과 더 나은 설계를 제시하라.
// (먼저 직접 리뷰를 적은 뒤 answer.md 와 대조할 것)
// ============================================================================
using System;
using System.Collections.Generic;
public class Equip
{
public long Uid;
public int Level; // 강화 단계
}
public class Player
{
public long Id;
public long Gold;
public int EnchantStones; // 강화석 보유 수
public Dictionary<long, Equip> Equips = new();
public readonly object Lock = new object();
}
public class EnchantRequest
{
public long EquipUid;
public bool ClientSuccess; // (A) 클라가 보낸 성공 여부
public int ClientRoll; // (B) 클라가 보낸 굴림값(0..9999)
}
public class EnchantService
{
// 강화 처리. 성공하면 true 반환.
public bool Enchant(Player p, EnchantRequest req)
{
lock (p.Lock)
{
Equip e = p.Equips[req.EquipUid]; // (C)
int stoneCost = e.Level + 1; // 단계 비례 비용
long goldCost = (long)(e.Level + 1) * 1000;
// (D) 소재/골드 먼저 소모
p.EnchantStones -= stoneCost;
p.Gold -= goldCost;
// (E) 결과 적용
if (req.ClientSuccess)
{
e.Level += 1; // (F) 성공 → 단계 상승
}
else
{
// 실패 → 단계 유지(정책)
}
// (G) 부족했는지 사후 확인
if (p.EnchantStones < 0 || p.Gold < 0)
return false;
return true;
}
}
} 결함 코드 · C++
// ============================================================================
// [코드리뷰 문제] C++ - 아이템 강화(인챈트) 원자성과 실패 처리
// ----------------------------------------------------------------------------
// 시나리오:
// - 장비를 강화한다. 강화에는 소재(stone)와 골드가 든다.
// - 강화는 확률적으로 성공/실패한다. 성공하면 +1, 실패하면 단계 유지 또는
// 하락(정책에 따라). 어느 쪽이든 소재와 골드는 소모된다(시도 비용).
// - 강화 결과 연출을 위해 클라이언트가 "성공/실패" 와 굴림값을 함께 보낸다
// (클라가 먼저 연출을 돌리고 결과를 서버에 통보하는 구조).
// - 여러 IO 스레드가 같은 플레이어의 강화 패킷을 처리할 수 있다.
//
// 요구사항:
// - 강화는 원자적이어야 한다: (소재/골드 소모 + 단계 변경)이 함께 일어나거나
// 전혀 일어나지 않거나.
// - 소재/골드가 부족하면 아무것도 변하지 않아야 한다.
// - 성공/실패 결과는 서버가 권위 있게 결정해야 한다(조작 불가).
//
// 과제:
// 이 코드의 잠재적 문제를 모두 찾고, 왜 문제인지 설명하고,
// 수정안과 더 나은 설계를 제시하라.
// (먼저 직접 리뷰를 적은 뒤 answer.md 와 대조할 것)
// ============================================================================
#include <cstdint>
#include <unordered_map>
#include <mutex>
struct Equip {
int64_t uid;
int32_t level; // 강화 단계
};
struct Player {
int64_t id;
int64_t gold;
int32_t enchantStones; // 강화석 보유 수
std::unordered_map<int64_t, Equip> equips;
std::mutex lock;
};
struct EnchantRequest {
int64_t equipUid;
bool clientSuccess; // (A) 클라가 보낸 성공 여부
int32_t clientRoll; // (B) 클라가 보낸 굴림값(0..9999)
};
class EnchantService {
public:
// 강화 처리. 성공하면 true 반환.
bool Enchant(Player& p, const EnchantRequest& req) {
std::lock_guard<std::mutex> lk(p.lock);
Equip& e = p.equips[req.equipUid]; // (C)
int32_t stoneCost = e.level + 1; // 단계 비례 비용
int64_t goldCost = (int64_t)(e.level + 1) * 1000;
// (D) 소재/골드 먼저 소모
p.enchantStones -= stoneCost;
p.gold -= goldCost;
// (E) 결과 적용
if (req.clientSuccess) {
e.level += 1; // (F) 성공 → 단계 상승
} else {
// 실패 → 단계 유지(정책)
}
// (G) 부족했는지 사후 확인
if (p.enchantStones < 0 || p.gold < 0)
return false;
return true;
}
private:
std::unordered_map<int64_t, int> dummy_;
}; 내 리뷰 · C#
내 답안 · 자동 저장
작성 후 위 해설 보기에서 모범 해설과 대조하세요.
내 리뷰 · C++
내 답안 · 자동 저장
작성 후 위 해설 보기에서 모범 해설과 대조하세요.