한컴테크를 통해 한컴의 기술을 공유합니다. 한컴의 프로그래밍, 프레임워크, 라이브러리 및 도구 등 다양한 기술을 만나보세요. 한컴 개발자들의 다양한 지식을 회사라는 울타리를 넘어 여러분과 공유합니다. 한컴이 제공하는 기술블로그에서 새로운 아이디어와 도전을 마주하고, 개발자가 꿈꾸는 미래를 실현하세요.

한컴테크

Unity를 모르는 프론트엔드 개발자가 AI로 Unity 서비스를 React로 옮긴 방법

요약
이 글은 C#/Unity 경험이 없는 프론트엔드 개발자가 운영 중인 Unity 서비스를 React/Next로 마이그레이션하며, “검증 지점을 코드에서 자연어 명세로 옮긴다”는 발상을 바탕으로 AI 협업 흐름 전체를 설계한 과정을 다룹니다. 특히 읽지 못하는 원본 코드를 AI가 ‘관측 가능한 동작’ 중심의 명세로 번역하고, 그 명세를 구현·검증·리뷰가 참조하는 단일 기준으로 삼는 하네스 엔지니어링 접근을 설명합니다. 또한 iOS Safari의 한글 IME 이슈 사례를 통해 AI가 제시하는 ‘가장 흔한 정답’이 내 상황에 맞는지는 개발자가 판단해야 함을 보여주고, 단락 단위 가상 스크롤 전환 과정에서 AI를 “가설을 싸게 만들고 빠르게 버리는” 도구로 활용하는 구체적인 방법을 다룹니다. 나아가 hook 기반 자동 안전장치와 도메인별 AI 리뷰어 분리, 그리고 자율성을 의도적으로 제한한 트레이드오프 판단까지, 정확성이 중요한 마이그레이션 작업에서 AI에게 어디까지 맡길지 정하는 판단력을 실무적 관점에서 살펴보는 글입니다.

서론


Unity + WebGL로 운영 중인 한컴타자 ‘필사’ 서비스를 React/Next로 마이그레이션하는 일을 맡았습니다. 문제는 제가 C#/Unity 경험이 없는 프론트엔드 개발자라는 점이었습니다. 옮겨야 할 코드를 온전히 이해할 수 없는 상태에서 시작한 것입니다. 이 글은 그 문제를 AI로 어떻게 풀었는지의 기록입니다. 단순히 AI로 코드를 생성한 이야기가 아니라, 온전히 이해하지 못하는 코드의 ‘번역’을 AI에게 맡기고, 그 결과를 개발자가 검증하는 흐름을 설계한 이야기입니다.

배경


한컴타자 필사는 Unity로 운영 중인 타이핑 학습 서비스입니다. 저는 이 서비스를 React/Next로 마이그레이션하면서, 모바일 대응과 페이지 단위 → 스크롤 기반 UX 전환 같은 신규 기능까지 얹는 작업을 진행했습니다.

기존 서비스는 콘텐츠를 페이지 단위로 직접 잘라 JSON으로 등록하는 구조였습니다. 챕터 > 페이지 > 텍스트 구조로, 한 페이지에 들어갈 분량을 관리자가 손으로 나눠 넣는 방식이었죠.

{
  "document": {
    "title": "...",
    "chapters": [
      {
        "title": "1장",
        "pages": [
          { "pageEffect": 0, "text": "첫 페이지 본문…", "marker": [] },
          { "pageEffect": 0, "text": "둘째 페이지 본문…", "marker": [] }
        ]
      }
    ]
  }
}

이번 마이그레이션에서는 txt·epub 파일을 그대로 업로드하면 되도록 바꿔, 페이지를 일일이 나누는 수작업을 없앴습니다. 그러자 페이지로 나눌 이유가 사라졌고, 책 전체를 한 번에 스크롤로 보여주는 방식으로 전환할 수 있었습니다. (스크롤 전환이 기술적으로 가능한지 먼저 검증한 뒤 반영했습니다.)

AI로 생성한 이미지

여기서 두 가지 어려움이 있었습니다.

  • 원본 코드를 온전히 이해할 수 없었습니다. 뭘 유지하고 뭘 바꿀지 판단하려면 먼저 원본이 정확히 뭘 하는지 알아야 하는데, C#/Unity 경험이 없어 그 코드를 제대로 파악할 수가 없었습니다.
  • “그대로 옮길 것”과 “새로 만들 것”이 섞여 있었습니다. 진행률·정확도·완독 처리 같은 건 기존 서비스와 계산 규칙·저장 규격이 동일해야 했습니다. 구현은 새로 짜더라도 결과가 어긋나면 안 되는 거죠. 반면 스크롤 전환·모바일 대응은 처음부터 새로 설계하는 영역이었습니다.

그래서 “AI야 코드 짜줘”로 될 일이 아니었습니다. 어려운 건 코드 생성이 아니라, 읽지 못하는 원본을 이해하고 → 뭘 유지·변경할지 판단하고 → 그 판단대로 구현됐는지 검증하는 흐름 전체였습니다.

핵심 발상 – 검증 지점을 “코드”에서 “명세”로 옮겼다


제가 내린 가장 중요한 결정입니다. 흐름은 이렇습니다.

C# 코드를 검증할 수 없으니, 자연어 명세를 중간에 끼워넣고, 검증 지점을 코드에서 명세로 옮긴다.

AI로 생성한 이미지

C#을 몰라도 “이 명세가 내가 만들 서비스의 동작을 맞게 서술했나”는 판단할 수 있습니다. 그리고 이 명세는 이후 구현·검증·리뷰가 전부 참조하는 단일 기준이 됩니다. AI가 짠 결과물을 “믿을지 말지”의 문제에서 “기준에 맞는지”의 문제로 바꿨습니다.

추가로, 이 명세는 AI에게 건넬 컨텍스트의 기준점이 됐습니다. 대화가 길어져 맥락이 날아가거나 새 세션·다른 에이전트가 붙어도, 코드베이스 전체를 다시 읽힐 필요 없이 스펙 하나만 건네면 “이 기능이 뭘 해야 하는지”가 곧바로 잡힙니다.

여기에 더해, 세션이 시작될 때마다 자동으로 도는 훅(hook)이 현재 브랜치·작성된 스펙 목록·진행 중인 스펙을 먼저 띄웁니다. “어디까지 했는지”를 매번 다시 설명하지 않아도, AI가 방향을 잡은 채로 시작할 수 있었습니다. (훅은 뒤에서 다시 나오는데, 여기서는 ‘차단’이 아니라 ‘맥락 주입’ 용도입니다.)

이걸 반복 가능하게 만들려면 AI가 일하는 환경 자체를 설계해야 했습니다. 최근 이런 접근을 하네스 엔지니어링(harness engineering)이라고 부릅니다. 더 좋은 프롬프트를 찾는 게 아니라, 모델이 일하는 환경(맥락·도구·절차·안전장치)을 설계하는 것이죠. (agent harness, scaffolding이라고도 합니다.)

이 프로젝트에선 이렇게 구성했습니다.

AI로 생성한 이미지

예를 들어 명세 추출은 커맨드 하나로 고정됩니다.

/extract-spec <unity 파일들>
  → 전용 에이전트가 C#을 읽고
  → 정해진 템플릿으로 docs/specs/<name>.md 작성

핵심은 명세를 구현이 아니라 ‘관측 가능한 동작’으로만 기술하게 한 점입니다. 클래스·메서드·생명주기 같은 Unity 구현 디테일은 걷어내고, “어떤 입력·이벤트에 화면·상태·서버가 어떻게 바뀌는가”만 명세로 작성하게 했습니다. 덕분에 ① C#/Unity 내부를 몰라도 명세만으로 검수할 수 있고, ② 구현 스택이 Unity에서 React로 바뀌어도 그대로 유효한 서술이 됩니다. 그래서 명세 추출 에이전트가 아래 템플릿을 따르도록 고정했습니다.

AI로 생성한 이미지

AI 활용 사례


1. C#을 모르는데 동작을 파악해야 했다

AI를 “번역기”로 썼습니다. C#을 모르는 개발자가 읽을 수 있도록 자연어 명세로 번역시키는 전용 에이전트(unity-spec-extractor)를 만들었습니다. 여기엔 Unity 개념을 React/Next 관점으로 옮기는 변환 규칙을 내장했습니다.

AI로 생성한 이미지

그리고 이 에이전트에 “클래스/메서드 이름을 명세에 쓰지 말고, 사용자 관점 동작으로만 서술하라”는 규칙을 박았습니다. 그래서 C#을 몰라도 검수가 가능했습니다.

함정: 코드가 C#에서 끝나지 않는다

C#만 읽어선 안 되는 지점도 있습니다. 콘텐츠·기록 같은 데이터 API는 C#에 그대로 있지만, 전체화면·클립보드·파일 저장처럼 브라우저·호스트 페이지와 맞물리는 일부 동작은 C#이 아니라 빌드에 얹히는 JS 레이어(jslib + 템플릿 html)에 위임돼 있습니다. C#에는 브라우저 함수를 부르는 extern 선언([DllImport("__Internal")])만 보일 뿐, 실제 구현은 그 JS에 있어서 C#만 읽으면 이 경계 너머를 놓칩니다.

그래서 번역 에이전트에 규칙을 하나 더 넣었습니다 — extern 선언을 만나면 매핑된 jslib·호스트 JS 구현까지 따라가 실제 동작을 명세에 채우도록. C# 경계 밖으로 넘어가는 동작까지 추적하게 만든 셈입니다.

💡이해하기 어려운 코드를 붙잡고 해독하는 대신, AI에게 번역을 맡겼습니다. 다만 번역이 닿지 못하는 경계(여기서는 C# 밖의 JS 레이어)를 짚어 보강하는 건 사람의 몫이었습니다.

2. 한글 IME – AI가 준 ‘가장 흔한 정답’이 안 맞을 때

웹에서 한글 IME(조합 입력)를 다루는 건 생각보다 까다롭습니다. ㅇ→아→안처럼 조합 중인 글자를 어떻게 취급하느냐에 정타 판정·통계가 다 걸리는데, 이런 건 단위 테스트로는 거의 못 잡습니다.

AI에게 초안을 짜게 하니 웹에서 제일 흔한 방식(composition 이벤트)으로 짜줬습니다. IME 처리에 익숙하지 않아도 데스크탑에서 동작하는 버전을 빠르게 만들어 바로 검증해 볼 수 있었죠. 데스크탑 Chrome·Safari에서는 잘 동작했습니다. 그런데 iOS Safari 실기기에서는 한글이 자모 단위로 쪼개졌습니다.

파보니 원인은 플랫폼별 이벤트 차이였습니다. 같은 “가” 입력인데 이벤트 시퀀스가 다릅니다.

AI로 생성한 이미지

즉 iOS Safari는 (현재 기준) 한글 입력에서 composition 이벤트를 발생시키지 않습니다. 일본어·중국어는 정상 동작하는 한글 특정 이슈이고, iOS는 Chrome·Firefox도 내부가 WebKit이라 사실상 iOS 전 브라우저 공통입니다. 심지어 input 이벤트의 isComposing 플래그도 내내 false라, ‘조합 중이면 건너뛰자’는 흔한 차선책조차 통하지 않습니다. 게다가 이건 Playwright 합성 이벤트로도 재현되지 않습니다. 합성 이벤트는 실제 OS IME를 거치지 않으니까요. 데스크탑에서도 멀쩡하고 e2e로도 안 잡히는, 실기기에 올려보기 전에는 알아차리기 힘든 문제였습니다.

방향 전환 – 이벤트 대신 “값 변화”를 본다

composition 이벤트 의존을 통째로 걷어냈습니다. 대신 hidden textareavalue를 비우지 않고, 직전 값과 비교해 바뀐 부분만 추렸습니다.

// 직전 값과 현재 값을 비교해, 지워진 글자 수와 새로 붙은 부분만 추린다
function diff(prev, next):
    공통 prefix 길이 i 를 구한다
    return { 지워진 수: len(prev) - i, 붙은 것: next[i:] }
    // 예) diff('안녕하셍', '안녕하세요'): i=3 → { 지워진 수: 1('셍'), 붙은 것: '세요' }
on input:
    value = textarea.value          // 절대 비우지 않는다
    if 끝 글자가 더 조합될 수 있는 한글 && 삭제 입력이 아닐 때:
        pending = 끝 글자           // 조합 중일 수 있으니 판정 보류
        settled = value - pending
    else:
        pending = 없음
        settled = value
    확정_반영( diff(직전 settled, settled) )   // 확정분만 판정·통계에 반영
    조합중_표시(pending)                        // 보류 글자는 화면 미리보기만
    직전 settled = settled

포인트는 두 가지입니다. ① 값 전체를 유지하고 diff만 봅니다. textarea를 비우지 않고 직전 값과 비교해 바뀐 부분만 뽑습니다. composition 이벤트가 안 와도 “값이 어떻게 변했나”는 항상 관측되니까요. ② 마지막 글자는 “조합 중일 수 있다”고 보고 판정을 보류했다가, 다음 글자가 붙어 확정되면 그때 반영합니다. 이렇게 하니 데스크탑과 iOS가 하나의 입력 경로로 합쳐졌습니다.

💡 배운 점: AI는 가장 흔한 정답(표준 방식)을 빠르게 줍니다. 하지만 내 상황이 그 ‘흔한 정답’에 해당하는지는 판단해 주지 않았습니다. 표준이 안 맞을 때 다른 구조를 택하는 건 개발자의 몫이었습니다.

3. page → scroll 전환: 만들어 보며 빠르게 방향 찾기

Unity는 페이지를 넘기는 UX였는데, 운영에서 “콘텐츠를 페이지 단위로 잘라 등록하는 작업이 부담스럽다”는 의견이 나왔습니다. 사람이 페이지 경계를 일일이 정하지 않아도 되게, 본문을 통째로 받아 화면에서 알아서 처리하도록 렌더링 방식을 다시 짜야 했습니다. 게다가 책 한 권이 수십만 자라, 통째로 화면에 올리면 스크롤도 한글 입력도 버벅입니다. 이런 렌더링·입력 성능은 돌려보기 전에는 예측이 잘 빗나가서, 직접 만들어 검증해 보는 쪽을 택했습니다.

그래서 AI를 “가설을 싸게 만들고 빠르게 버리는” 방식으로 접근했습니다. 실험용 코드(spikes/)에 후보를 만들어 성능과 UX를 비교하며 하나씩 좁혀갔습니다.

후보 ① chunk windowing (탈락). 긴 텍스트를 큰 덩어리(chunk)로 잘라 화면 근처만 살리는 방식. 렌더링 자체는 크게 줄었지만, 한글 조합 중에 발목이 잡혔습니다. 조합 중인 글자는 매 키 입력마다 바뀝니다. 그 글자가 큰 덩어리 안에 들어 있으면 브라우저는 그 덩어리 전체의 레이아웃을 다시 계산(reflow)합니다. 즉 덩어리가 클수록 키 하나당 reflow 비용이 커져 일정 크기를 넘자 입력에 체감 지연이 발생했습니다. (한글은 한 글자를 완성하는 동안 같은 자리에서 ㅎ→하→한으로 여러 번 다시 그려지기 때문에, 한 글자당 한 번 그리고 마는 영문보다 이 반복 reflow에 훨씬 민감합니다.)

후보 ② 가상 페이징 (탈락). 책을 1,500자 내외의 페이지로 묶어 한 화면에 한 페이지만 렌더링. 페이지 크기에 상한이 있으니 ①의 IME 비용 문제는 사라졌습니다. 성능만 보면 충분했습니다. 하지만 한 페이지에 여러 단락이 뭉쳐 들어가서, “이 단락만 골라 필사”(부분 필사, 1차 출시 범위)를 얹기 어려웠습니다. 사용자가 고른 단락이 페이지 어디에 묶였는지 한 번 더 찾아 들어가야 했으니까요.

후보 ③ 단락 단위 가상 스크롤 (채택). 단위를 단락으로 잘게 쪼갰습니다. 화면에 보이는 단락 + 위아래 버퍼만 실제 글자로 그리고, 나머지는 높이만 추정한 빈 placeholder로 둡니다. 조합 중 글자가 속한 컨테이너가 항상 작으니 reflow 범위도 작게 유지되고(②의 성능 이점 유지), 단락 자체가 선택 단위라 부분 필사도 자연스럽게 지원됩니다. (②의 한계 해결).

BUFFER = 3   // 화면 위아래로 미리 그려둘 단락 수
on 스크롤 또는 입력:
    보이는 단락 범위를 구한다            // 스크롤 위치 기준
    유지할 단락 = 보이는 범위 ± BUFFER
    유지할 단락에 '입력 중인 단락'을 항상 포함
    범위 밖으로 나간 단락 → placeholder(높이만)로 내림(unmount)
    범위 안으로 들어온 단락 → 실제 글자로 올림(mount)

placeholder 높이는 글자 수 기반 휴리스틱으로 추정하고, 실제로 mount될 때 측정값으로 보정합니다(스크롤 점프 방지). 이 구조로 전환 비용과 조합 중 입력 지연을 모두 한 프레임(60fps 기준 약 16ms) 안에 처리되도록 하여, 스크롤과 입력이 끊기지 않게 했습니다.

💡 배운 점: AI의 진짜 강점은 “정답을 한 번에”가 아니라 “여러 가설을 싸게 만들고 빨리 버리기”에 있었습니다. 특히 해봐야 아는 문제(성능·UX)에서 강했습니다.

4. AI가 위험한 작업을 못하게: 자동 안전장치

운영 중인 서비스이다 보니, AI에게 작업은 맡기되 위험한 실수는 시스템이 막도록 해뒀습니다. AI에게 “하지 마”라고 부탁하는 게 아니라, 애초에 할 수 없도록 자동 차단하는 방식입니다.

① 원본 보호. 원본(Unity) 디렉터리에 쓰기를 시도하면 도구 호출 단계에서 즉시 막습니다.

# 파일 쓰기 직전 실행되는 hook — unity 원본은 read-only
if [[ "$file_path" == */pilsa-unity/* ]]; then
  echo "🚫 원본 디렉터리는 read-only 입니다." >&2
  exit 2   # 작업 자체를 차단
fi

② 민감 코드 격리. 비밀키를 다루는 서버 전용 코드는 파일 규칙(전용 디렉터리 + server-only import)을 강제해, 클라이언트 번들로 새는 것을 빌드/생성 단계에서 차단합니다. 위반하면 파일 생성 자체가 막힙니다.

③ 능력 자체를 제한 – 권한 격리. hook이 “행동을 막는” 축이라면, 다른 축은 “애초에 할 수 있는 도구를 안 주는” 것입니다. 예를 들어 검증 담당 에이전트에는 파일 수정 도구를 아예 주지 않았습니다. “검증한다며 코드를 고쳐 버리는” 사고는 구조적으로 불가능합니다. 위험한 행동을 하지 말라고 부탁하는 게 아니라, 그 행동에 필요한 도구를 손에 쥐여 주지 않는 방식입니다.

핵심은 사후가 아니라 사전입니다. 치명적 실수는 리뷰로 되돌리는 게 아니라 애초에 일어나지 못하게 막습니다. 검사 책임을 개발자의 기억이나 모델의 협조에서 떼어내 환경으로 옮긴 거죠.

💡 배운 점: AI에 자율권을 주는 것과 안전은 반대말이 아닙니다. “하면 안 되는 일”을 개발자의 주의력이 아니라 자동 장치로 막아두면, 오히려 더 과감하게 맡길 수 있습니다.

5. 혼자 놓치는 걸 AI 리뷰어 여러 명이 잡는다

낯선 도메인일수록 “돌아는 가는데 미심쩍은” 코드를 스스로 잡기 어렵습니다. 그래서 코드 리뷰를 AI에 맡기되, 한 명이 아니라 도메인별 전문 리뷰어로 나눴습니다.

code-reviewer 에이전트는 코드베이스를 6개 도메인(보안 · 데이터 · React · 일관성 · 브라우저 · 성능)으로 쪼개 병렬로 점검합니다. 특히 “우연히 동작하는 결합, 취약한 문자열 파싱, 서로 기준이 다른 두 위치 값(예: 챕터 전체 기준 offset vs 단락 내부 기준 offset)을 같은 것처럼 섞어 쓰는 실수” 같은 걸 집중적으로 찾게 했습니다.
중요한 원칙: 리뷰어는 리포트만 하고 코드는 고치지 않습니다. 최종 판단은 개발자가 합니다.

💡 배운 점: 리뷰어 하나에게 보안·성능·React·브라우저를 한꺼번에 맡기면 깊게 못 봅니다. 그래서 도메인을 하나씩 쪼개 각 리뷰어가 자기 관점 하나에만 집중하게 했습니다. 한 명이 넓게 훑는 것보다, 좁게 맡은 리뷰어가 각자 깊게 파고들 때 놓치는 게 훨씬 적었습니다.

왜 끝까지 자동화하지 않았나? 자율성을 의도적으로 제한했다


계획 → 구현 → 검증 → 배포까지 AI가 알아서 이어 처리하는 완전 자동화 데모가 흔해진 요즘입니다. 저는 그 흐름은 대부분 갖췄지만, 자율성은 제한했습니다.

AI로 생성한 이미지

서비스를 마이그레이션하는 작업에서 정확성은 속도보다 중요합니다. 자율성을 낮춘 건 손해가 아니라, 정확성이 중요한 작업에 맞춘 의도된 트레이드오프였습니다. “AI를 얼마나 빨리 돌렸나”가 아니라 “AI에게 어디까지 맡기고 어디부터 개발자가 맡을지”를 판단한 결과입니다.

💡 배운 점: AI를 잘 쓰는 것과 AI에 다 맡기는 것은 다릅니다. 어디까지 자동화할지를 작업 성격에 맞게 정하는 것 자체가 활용 능력입니다.

결론


  • AI를 ‘생성기’로만 보지 마라. 번역·검증·리뷰 등 “읽기” 쪽이 더 강할 때가 많습니다.
  • AI의 답은 ‘가장 흔한 정답’이다. 내 케이스가 예외인지는 개발자가 직접 판단해야 합니다.
  • 해봐야 아는 문제는 AI로 빠르게 만들어 검증하라. 성능·UX처럼 예측이 빗나가는 영역에서 특히 강합니다.
  • 위험한 작업은 부탁이 아니라 자동 장치(hook)로 막아라. 개발자의 주의력이 아니라 시스템에 검사 책임을 둡니다.

결국 AI를 잘 쓴다는 건, 어디까지 맡길지 정하는 판단력이라고 생각합니다.

참조


한글 IME / 브라우저

하네스 엔지니어링

Scroll to Top