ws2react 로고ws2react

WebSquare → React 마이그레이션, 규칙 기반 컨버터는 왜 80%에서 멈추는가

WebSquare → React 마이그레이션 1편 · 2026-08-25 · ws2react 팀

WebSquare 화면을 React로 옮기는 프로젝트를 규칙 기반 트랜스파일러로 시작했다가 멈춘 팀이 우리만은 아닐 것이다. 이 글은 그 컨버터가 왜 80%에서 멈추는지, 그리고 접근을 어떻게 바꿔야 끝까지 가는지를 우리가 실제로 겪은 순서대로 정리한 것이다.

규칙 기반이 멈추는 세 지점

1. 스크립트 로직은 규칙의 대상이 아니다. WebSquare 화면 XML의 컴포넌트 트리, dataMap/dataList 선언, submission 등록, ev:onclick 바인딩은 구조라서 규칙으로 깔끔하게 옮겨진다. 문제는 <script> 안의 scwin.* 함수다. 화면마다 제각각인 조회·검증·저장 흐름, 그리드 행을 돌며 상태를 판단하는 코드, 팝업에서 값을 돌려받는 콜백 — 이것을 AST 변환 규칙으로 덮으려면 규칙 수가 화면 수만큼 늘어난다. 80%까지는 빠르게 가고, 나머지 20%가 규칙 한 개당 화면 한 개꼴이 된다.

2. 그리드는 1:1 매핑이 안 된다. WebSquare 그리드는 데이터 컬렉션과 묶인 상태 머신이다(rowStatus C/U/D, 수정 셀 트래킹, getMatchedIndex, 헤더 병합, 커스텀 컨텍스트 메뉴). React 생태계의 어느 그리드도 이 API 표면을 그대로 갖고 있지 않다. 태그 하나를 컴포넌트 하나로 바꾸는 매핑 표는 첫 화면에서 깨진다.

3. 엣지 케이스의 비용이 폭발한다. 80% 이후 남는 것은 "이 화면만 쓰는 패턴"들이고, 하나를 통과시키려고 규칙을 손대면 이미 통과한 화면이 깨진다. 회귀 없이 규칙을 키우다 보면 어느 순간 컨버터가 화면보다 더 큰 유지보수 대상이 된다.

세 문제의 공통점은 "규칙으로 100%"라는 목표 자체다. 목표를 바꾸면 세 문제가 같이 풀린다.

하이브리드: 결정론적인 것은 규칙, 나머지는 LLM, 믿는 것은 검증

우리가 다시 만든 파이프라인은 세 계층이다.

[WebSquare XML]
   → ① 파서(규칙): 구조를 중간 표현(IR)으로 — 컬렉션·submission·컴포넌트 트리·이벤트·의존성 목록
   → ② LLM 변환: IR + 런타임 API 명세 + 변환 규약을 프롬프트로 → React TSX 한 파일
   → ③ 검증 루프: tsc → vite build → 렌더 스모크 → 시나리오(클릭·통신 라운드트립) → IR 대조
        실패하면 에러를 LLM에 피드백해 재시도(기본 3회), 한도 초과는 수작업 큐로
[React 화면 + 리포트]

각 계층이 맡는 것이 분명하다.

그리드 문제는 호환 래퍼로 푼다. 외부 API는 WebSquare 스타일(getRowCount, setCellData, getMatchedJSON…), 내부는 React 컴포넌트다. 화면 스크립트가 그리드를 부르던 방식을 그대로 살리므로 LLM도 규칙도 "번역"할 것이 줄어든다.

100%를 포기하면 오히려 끝까지 간다

이 접근의 핵심 결정은 "100% 자동을 좇지 않는다"이다. N회 재시도 후 실패한 화면은 파이프라인이 더 풀려고 확장하지 않고 수작업 큐로 넘긴다. 자동화율은 85%든 95%든 마진의 문제지 상품성의 문제가 아니다 — 통과한 것이 검증됐다는 사실이 상품성이다.

실제로는 어땠나. 실제 업무 시스템 두 곳(해운 시스템 A 39화면, 물류 FMS B 25화면)의 64화면을 이 파이프라인에 넣었을 때, 64화면 전부가 수작업 코드 수정 0건으로 검증을 통과했다. 평균 시도 횟수는 1.8회, 화면당 2~5분. 가장 큰 화면은 11,717줄이었다. 이 숫자들이 어떻게 나왔고 무엇이 실패를 막았는지는 다음 글에서 다룬다.

다음 글: 화면 64개를 자동 변환하며 배운 것 — 검증 루프가 LLM을 믿을 만하게 만드는 방법