2. 캐릭터 정보 패킷 직렬화 / 스키마 진화

난이도 중 해설 보기 →

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

결함 코드 · C#
// ============================================================================
// [코드리뷰 문제] C# - 캐릭터 정보 패킷 직렬화/역직렬화 (스키마 진화)
// ----------------------------------------------------------------------------
// 시나리오:
//   - 서버는 클라이언트에 캐릭터 정보(S_CharInfo)를 직렬화해 보낸다.
//   - v1.0 출시 후, v1.1 에서 "길드ID" 필드를 추가하기로 했다.
//   - 배포는 점진적이다: 서버를 먼저 v1.1 로 올리고, 클라이언트는
//     앱스토어 심사/사용자 업데이트 때문에 며칠~몇 주에 걸쳐 v1.0 과 v1.1 이 공존한다.
//   - 즉, "v1.1 서버 ↔ v1.0 클라" 와 "v1.1 서버 ↔ v1.1 클라" 가 동시에 존재한다.
//
// 요구사항:
//   - 구버전 클라이언트가 신버전 서버에 붙어도 캐릭터 정보를 정상 파싱해야 한다
//     (적어도 크래시/연결 끊김은 없어야 한다).
//   - 직렬화는 직접 작성한 바이너리 Writer/Reader 를 쓴다(아래).
//
// 아래는 v1.1 서버의 직렬화 코드와, 배포되어 현장에 남아있는 v1.0 클라의
// 역직렬화 코드다.
//
// 과제:
//   이 코드의 잠재적 문제를 모두 찾고, 왜/어떻게 발생하는지(특히 버전 공존 시점),
//   수정안과 더 나은 스키마 진화 전략을 제시하라.
// ============================================================================

using System;
using System.Text;

// 공용 바이너리 라이터/리더 (양 진영이 공유)
public ref struct PacketWriter
{
    private Span<byte> _buf;
    public int Pos;
    public PacketWriter(Span<byte> buf) { _buf = buf; Pos = 0; }

    public void WriteInt(int v)
    {
        _buf[Pos++] = (byte)(v);
        _buf[Pos++] = (byte)(v >> 8);
        _buf[Pos++] = (byte)(v >> 16);
        _buf[Pos++] = (byte)(v >> 24);
    }
    public void WriteString(string s)
    {
        byte[] bytes = Encoding.UTF8.GetBytes(s);
        WriteInt(bytes.Length);                 // 길이
        foreach (var b in bytes) _buf[Pos++] = b;
    }
}

public ref struct PacketReader
{
    private ReadOnlySpan<byte> _buf;
    public int Pos;
    public PacketReader(ReadOnlySpan<byte> buf) { _buf = buf; Pos = 0; }

    public int ReadInt()
    {
        int v = _buf[Pos] | (_buf[Pos + 1] << 8) | (_buf[Pos + 2] << 16) | (_buf[Pos + 3] << 24);
        Pos += 4;
        return v;
    }
    public string ReadString()
    {
        int len = ReadInt();
        var s = Encoding.UTF8.GetString(_buf.Slice(Pos, len));
        Pos += len;
        return s;
    }
}

// ---------------------------------------------------------------------------
// v1.1 서버: 직렬화 (길드ID 필드 추가됨)
// ---------------------------------------------------------------------------
public class S_CharInfo_Server_v11
{
    public int CharId;
    public string Name;
    public int Level;
    public int GuildId;     // (A) v1.1 에서 새로 추가한 필드

    public void Serialize(PacketWriter w)
    {
        w.WriteInt(CharId);
        w.WriteString(Name);
        // (B)
        w.WriteInt(GuildId);
        w.WriteInt(Level);
    }
}

// ---------------------------------------------------------------------------
// v1.0 클라이언트: 역직렬화 (길드ID 를 모른다 — 현장에 이미 배포됨)
// ---------------------------------------------------------------------------
public class S_CharInfo_Client_v10
{
    public int CharId;
    public string Name;
    public int Level;

    public void Deserialize(PacketReader r)
    {
        // (C) v1.0 클라는 필드 3개만 읽는다. 패킷에 버전 정보는 없다.
        CharId = r.ReadInt();
        Name   = r.ReadString();
        Level  = r.ReadInt();   // (D)
    }
}
내 리뷰 · C#
내 답안 · 자동 저장

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