6. RPC 메시지 ID ↔ 핸들러 매핑 테이블

난이도 하 해설 보기 →

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

결함 코드 · C#
// ============================================================================
// [코드리뷰 문제] C# - RPC 메시지 ID ↔ 핸들러 매핑 테이블
// ----------------------------------------------------------------------------
// 시나리오:
//   - 서버/클라는 패킷을 [ushort msgId][payload] 로 주고받는다.
//   - msgId 별로 핸들러를 등록하고, 수신 시 테이블에서 찾아 디스패치한다.
//   - 메시지 종류는 자동 생성된 enum(MsgId)으로 관리한다. 기능이 늘면
//     팀원들이 각자 enum 에 값을 추가하고, 부팅 시 핸들러를 Register 한다.
//   - 최근 두 기능 브랜치가 머지되면서 enum 값이 재정렬됐고,
//     서버는 신버전으로, 일부 클라는 구버전으로 동작하는 구간이 있었다.
//
// 요구사항:
//   - 알 수 없는/미등록 msgId 가 와도 서버가 죽거나 다른 핸들러로 오작동하면 안 된다.
//   - msgId 는 한 번 배포되면 절대 의미가 바뀌면 안 된다(와이어 호환성).
//
// 과제:
//   이 코드의 잠재적 문제를 모두 찾고, 왜/어떻게 발생하는지(특히 버전 공존/머지 시점),
//   수정안과 더 나은 메시지 ID 관리 설계를 제시하라.
// ============================================================================

using System;
using System.Collections.Generic;

// (A) 자동 생성 enum — 값을 명시하지 않고 컴파일러가 0,1,2... 부여
public enum MsgId : ushort
{
    Login,          // 0
    Move,           // 1
    Chat,           // 2
    UseItem,        // 3
    // ... 브랜치 머지로 중간에 새 값이 끼어들 수 있다
}

public class RpcDispatcher
{
    private readonly Dictionary<ushort, Action<byte[]>> _handlers = new();

    public void Register(MsgId id, Action<byte[]> handler)
    {
        // (B) id 에 핸들러를 등록한다
        _handlers[(ushort)id] = handler;
    }

    public void OnReceive(byte[] packet)
    {
        // (C) 앞 2바이트를 msgId 로 읽는다
        ushort msgId = (ushort)(packet[0] | (packet[1] << 8));

        // payload 는 그 뒤
        byte[] payload = new byte[packet.Length - 2];
        Array.Copy(packet, 2, payload, 0, payload.Length);

        // (D) 핸들러 찾아 호출
        var handler = _handlers[msgId];
        handler(payload);
    }
}

public class ServerBootstrap
{
    public void Setup(RpcDispatcher d)
    {
        d.Register(MsgId.Login,   OnLogin);
        d.Register(MsgId.Move,    OnMove);
        d.Register(MsgId.Chat,    OnChat);
        d.Register(MsgId.UseItem, OnUseItem);
    }

    private void OnLogin(byte[] p)   { /* ... */ }
    private void OnMove(byte[] p)    { /* ... */ }
    private void OnChat(byte[] p)    { /* ... */ }
    private void OnUseItem(byte[] p) { /* ... */ }
}
내 리뷰 · C#
내 답안 · 자동 저장

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