8. 길드 자금고 동시 출금
난이도 상 해설 보기 →
결함을 모두 찾고 원인·수정안·더 나은 설계를 제시하라. 마커
(A)(B) 는 주목 위치 힌트다.
결함 코드 · C#
// ============================================================================
// [코드리뷰 문제] C# - 길드 자금고(금고) 동시 출금
// ----------------------------------------------------------------------------
// 시나리오:
// - 길드는 공용 금고(GuildBank)를 가진다. 자금은 골드 잔액 하나.
// - 길드원은 입금(Deposit)할 수 있고, 권한이 있는 임원(officer)은 출금(Withdraw).
// - 출금에는 두 가지 한도가 있다:
// (1) 금고 잔액을 초과해 출금 불가.
// (2) 임원 1인당 "일일 출금 한도(dailyLimit)" 가 있어 하루 누적 출금이
// 그 이상이면 거부.
// - 출금 로그(audit log)에 모든 출금을 기록한다.
// - 여러 IO 스레드가 동시에 같은 길드의 입출금 패킷을 처리한다
// (여러 임원이 동시에 출금 시도, 한 임원이 두 클라이언트로 동시 시도 등).
//
// 요구사항:
// - 금고 잔액은 음수가 될 수 없다(초과 출금 금지).
// - 임원별 일일 한도를 초과 출금할 수 없다.
// - 출금 로그의 합계는 실제 잔액 변화와 정확히 일치해야 한다.
//
// 과제:
// 이 코드의 잠재적 문제를 모두 찾고, 왜 문제인지 설명하고,
// 수정안과 더 나은 설계를 제시하라.
// (먼저 직접 리뷰를 적은 뒤 answer.md 와 대조할 것)
// ============================================================================
using System;
using System.Collections.Generic;
public class WithdrawLog
{
public long OfficerId;
public long Amount;
public DateTime At;
}
public class GuildBank
{
public long GuildId;
public long Balance; // (A) 금고 잔액
public long DailyLimit; // 임원 1인 일일 한도
// 임원별 오늘 누적 출금
public Dictionary<long, long> WithdrawnToday = new(); // (B)
public List<WithdrawLog> Logs = new(); // (C)
public readonly object Lock = new object();
}
public class GuildBankService
{
private readonly Dictionary<long, GuildBank> _banks;
public GuildBankService(Dictionary<long, GuildBank> banks) { _banks = banks; }
public bool Deposit(long guildId, long playerId, long amount)
{
if (amount <= 0) return false;
GuildBank bank = _banks[guildId];
lock (bank.Lock)
{
bank.Balance += amount; // (D)
}
return true;
}
public bool Withdraw(long guildId, long officerId, long amount)
{
if (amount <= 0) return false;
GuildBank bank = _banks[guildId];
// (E) 잔액 확인
if (bank.Balance < amount)
return false;
// (F) 일일 한도 확인
long usedToday = bank.WithdrawnToday.TryGetValue(officerId, out var u) ? u : 0;
if (usedToday + amount > bank.DailyLimit)
return false;
// (G) 금고에서 차감
lock (bank.Lock)
{
bank.Balance -= amount;
}
// (H) 임원 누적/로그 갱신
bank.WithdrawnToday[officerId] = usedToday + amount;
bank.Logs.Add(new WithdrawLog {
OfficerId = officerId, Amount = amount, At = DateTime.UtcNow
});
return true;
}
} 결함 코드 · C++
// ============================================================================
// [코드리뷰 문제] C++ - 길드 자금고(금고) 동시 출금
// ----------------------------------------------------------------------------
// 시나리오:
// - 길드는 공용 금고(GuildBank)를 가진다. 자금은 골드 잔액 하나.
// - 길드원은 입금(Deposit)할 수 있고, 권한이 있는 임원(officer)은 출금(Withdraw).
// - 출금에는 두 가지 한도가 있다:
// (1) 금고 잔액을 초과해 출금 불가.
// (2) 임원 1인당 "일일 출금 한도(dailyLimit)" 가 있어 하루 누적 출금이
// 그 이상이면 거부.
// - 출금 로그(audit log)에 모든 출금을 기록한다.
// - 여러 IO 스레드가 동시에 같은 길드의 입출금 패킷을 처리한다
// (여러 임원이 동시에 출금 시도, 한 임원이 두 클라이언트로 동시 시도 등).
//
// 요구사항:
// - 금고 잔액은 음수가 될 수 없다(초과 출금 금지).
// - 임원별 일일 한도를 초과 출금할 수 없다.
// - 출금 로그의 합계는 실제 잔액 변화와 정확히 일치해야 한다.
//
// 과제:
// 이 코드의 잠재적 문제를 모두 찾고, 왜 문제인지 설명하고,
// 수정안과 더 나은 설계를 제시하라.
// (먼저 직접 리뷰를 적은 뒤 answer.md 와 대조할 것)
// ============================================================================
#include <cstdint>
#include <vector>
#include <unordered_map>
#include <mutex>
#include <chrono>
struct WithdrawLog {
int64_t officerId;
int64_t amount;
int64_t atEpochMs;
};
struct GuildBank {
int64_t guildId;
int64_t balance = 0; // (A) 금고 잔액
int64_t dailyLimit = 0; // 임원 1인 일일 한도
// 임원별 오늘 누적 출금
std::unordered_map<int64_t, int64_t> withdrawnToday; // (B)
std::vector<WithdrawLog> logs; // (C)
std::mutex lock;
};
class GuildBankService {
public:
explicit GuildBankService(std::unordered_map<int64_t, GuildBank*>& banks)
: banks_(banks) {}
bool Deposit(int64_t guildId, int64_t playerId, int64_t amount) {
if (amount <= 0) return false;
GuildBank* bank = banks_.at(guildId);
{
std::lock_guard<std::mutex> lk(bank->lock);
bank->balance += amount; // (D)
}
return true;
}
bool Withdraw(int64_t guildId, int64_t officerId, int64_t amount) {
if (amount <= 0) return false;
GuildBank* bank = banks_.at(guildId);
// (E) 잔액 확인
if (bank->balance < amount)
return false;
// (F) 일일 한도 확인
int64_t usedToday = bank->withdrawnToday.count(officerId)
? bank->withdrawnToday[officerId] : 0;
if (usedToday + amount > bank->dailyLimit)
return false;
// (G) 금고에서 차감
{
std::lock_guard<std::mutex> lk(bank->lock);
bank->balance -= amount;
}
// (H) 임원 누적/로그 갱신
bank->withdrawnToday[officerId] = usedToday + amount;
int64_t now = std::chrono::duration_cast<std::chrono::milliseconds>(
std::chrono::system_clock::now().time_since_epoch()).count();
bank->logs.push_back(WithdrawLog{officerId, amount, now});
return true;
}
private:
std::unordered_map<int64_t, GuildBank*>& banks_;
}; 내 리뷰 · C#
내 답안 · 자동 저장
작성 후 위 해설 보기에서 모범 해설과 대조하세요.
내 리뷰 · C++
내 답안 · 자동 저장
작성 후 위 해설 보기에서 모범 해설과 대조하세요.