47. 펫 소환/소환해제와 스탯 버프 적용 경합 (C#)

난이도 중 해설 보기 →

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

결함 코드 · C#
// [시나리오] MMORPG 펫/소환수 시스템 (서버 권위, 서버-클라)
//
// 플레이어는 펫을 "소환"하면 펫이 필드에 스폰되고, 펫이 제공하는
// 스탯 버프(예: 공격력 +5%)가 플레이어에게 적용된다.
// "소환 해제"하면 펫이 사라지고 그 버프가 제거된다.
//
// [요구사항]
//  - 한 플레이어당 활성 펫은 최대 1마리.
//  - 플레이어에게 적용된 펫 버프의 수는 항상 활성 펫 수와 정확히 일치해야 한다.
//  - 클라는 소환/해제 버튼을 빠르게 연타할 수 있고, 요청은 서로 다른
//    수신 처리 흐름에서 동시에 도착할 수 있다.
//
// [과제]
//  아래 SummonPet / DismissPet 을 리뷰하라. (A)(B) 지점의 동작을 근거로
//  어떤 순서의 요청이 겹칠 때 어떤 잘못된 결과(스탯/엔티티/버프)가 나오는지
//  재현 시나리오와 함께 설명하고, 수정안을 제시하라.

using System.Threading.Tasks;
using System.Collections.Generic;

public class PetService
{
    private readonly Dictionary<long, PetEntity> _activePets = new();
    private readonly PlayerStats _stats;
    private readonly IPetRepository _repo;
    private readonly IWorld _world;

    public async Task SummonPet(long playerId, int petId)
    {
        // (A) 이미 활성 펫이 있으면 무시
        if (_activePets.ContainsKey(playerId))
            return;

        PetData data = await _repo.LoadPet(playerId, petId); // DB/캐시 조회

        var pet = _world.Spawn(data);
        // (B)
        _activePets[playerId] = pet;
        _stats.AddModifier(playerId, data.BuffModifier);
    }

    public async Task DismissPet(long playerId)
    {
        if (!_activePets.TryGetValue(playerId, out var pet))
            return;

        await _world.Despawn(pet);
        _activePets.Remove(playerId);
        _stats.RemoveModifier(playerId, pet.BuffModifier);
    }
}
내 리뷰 · C#
내 답안 · 자동 저장

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