
업무용 Excel에 실전 투입하며 느낀 점 ─ xlsm_devkit의 완성도를 높이기
요약
xlsm_devkit을 실제 업무용 Excel 환경에 적용하며 겪은 성능 저하, 수식 신뢰성, 워크플로우 문제를 다룹니다. AI 주도 개발 과정에서 발생한 속도 문제를 VBA 최적화 코드를 통해 해결하는 과정을 상세히 기술합니다.
핵심 포인트
- 대규모 Excel 데이터 처리 시 화면 업데이트 및 계산 모드 최적화 필수
- AI 주도 개발 시 규모에 따른 경험칙 기반의 최적화 코드 누락 주의
- PoC 단계와 실제 업무용 환경(Production)의 데이터 복잡도 차이 인지
- Codex와 Claude Code를 활용한 AI 기반 개발 사이클 구축
이 기사에서 전달하는 내용
지난 기사에서는 행·열의 삽입/삭제나 셀 범위의 이동을 AI가 추종하도록 하는 InsertDelete / Move 기능과 런처, 다국어화(27개 언어)를 소개했습니다.
이번에는 이러한 기능들을 실제 업무용 Excel ── 지난 기사에서도 언급했던, 8000개 셀의 알고리즘이 구현된 파일 ── 에 본격적으로 투입한 경험을 바탕으로 xlsm_devkit에 추가한 개선 사항을 보고합니다. 주요 테마는 다음 세 가지입니다.
속도: import·export 모두 실용적으로 쓰기에는 너무 느렸다 -
roundtrip의 신뢰성: IDE 측에서 수식을 편집하고 import 했더니, Excel의 수식이 깨졌다 -
DEV/Release 워크플로우: 개발용 모듈을 추가하여 개발하고, 배포 시에 분리하는 프로세스가 매번 수작업이었다
제1장 실전 투입의 전제
PoC에서 본番(Production)으로
xlsm_devkit의 개발은 pptx_yaml_excel을 사용한 PowerPoint 번역 워크플로우를 발판 삼아 시작되었습니다. pptx_yaml_excel은 소규모 도구로서 완성되어 있어, xlsm_devkit의 동작을 검증하기에 다루기 쉬운 '깔끔한' 프로젝트입니다. 하지만 그것은 PoC(Proof of Concept)로서의 유효성일 뿐, 장기간에 걸쳐 개수가 거듭되어 온 업무용 Excel과는 성격이 다릅니다.
이번에 투입한 것은 지난 기사에서 소개한 8000개 셀의 알고리즘을 가진 업무용 Excel입니다. 업무 시스템의 Web UI에서 붙여넣은 데이터를 즉시 검증하고, 다른 시스템과 대조를 수행하는, 그야말로 '실체는 앱'인 파일입니다. 업무 시스템 측의 변화에 맞춰 반복적으로 개수되어 온 이 파일에는, 깔끔한 샘플 프로젝트에서는 나타나지 않는 다양한 현실적인 사정들이 포함되어 있었습니다.
xlsm_devkit 자신의 개발에도 xlsm_devkit을 사용
이번 개선은 8000개 셀의 Excel 측에 xlsm_devkit의 모듈 일체를 도입한 후, 실제로 사용해 보며 발견된 문제점에 대처하기 위해 진행되었습니다. 주로 Codex를 사용하여 xlsm_devkit의 코드를 수정하거나 신기능을 추가하고, 완성된 코드를 xlsm_devkit의 리포지토리에 가져와 Claude Code로 리뷰하며, 피드백을 바탕으로 수정한 뒤 다시 실제 파일로 검증하는 사이클로 진행했습니다. xlsm_devkit이 가진 VBA 모듈의 에스포트(export)와 시트 맵(sheet map) + 코드를 한꺼번에 AI에게 전달하는 메커니즘을 xlsm_devkit 자신의 개발에 사용하고 있다는 구조는 지난 기사에서도 언급한 바와 같습니다.
제2장 처음에 마주한 난관: 속도
import가 느리다
업무용 Excel에 적용을 시도하며 가장 먼저 눈에 띈 것은 import의 느린 속도였습니다. 시트 맵을 MD에서 읽어와 Excel에 반영하는 처리에 예상보다 훨씬 많은 시간이 걸렸습니다. 원인을 조사해 보니 허무할 정도로 단순한 원인이었습니다.
Application.ScreenUpdating = False
Application.EnableEvents = False
Application.Calculation = xlCalculationManual
이 세 줄이 들어있지 않았습니다. 셀에 값이나 수식을 쓸 때마다 화면이 재그려지고, 이벤트가 발생하며, 워크시트 전체의 재계산이 실행되고 있었던 것입니다. VBA를 직접 작성한다면 규모가 커지기 전에 깨닫고 가장 먼저 작성했을 정형화된 코드입니다.
xlsm_devkit은 AI 주도로 개발해 왔습니다. AI는 동작하는 코드를 생성하는 데는 능숙하지만, 다루는 처리의 규모가 일정 수준 이상이 된다고 해서 작성해야 할 것을 경험칙에 따라 자발적으로 보완해 주지는 않습니다. 테스트에 사용했던 pptx_yaml_excel은 비교적 소규모였기에 이 결여가 문제로 드러나지 않았고, 업무용 Excel에 투입하고 나서야 비로소 보인 사각지대였습니다.
export도 느리다. 그리고 그 이상으로 의미 있었던 것
import와 마찬가지로 export의 느린 속도도 실용상의 문제였습니다. 여기에는 두 가지 대처가 필요했습니다.
O(n²)이었던 문자열 연결의 해소
GenerateSheetMapMarkdown
해당 함수는 시트의 셀 정보를 한 줄씩 문자열로 쌓아 올려 Markdown을 구축하고 있었으나, VBA에서의 문자열 연결(str = str & newPart)은 문자열이 길어질수록 느려집니다. 8,000 셀 규모가 되면 이러한 O(n²) 특성을 무시할 수 없게 됩니다.
대처 방안으로, VBA의 Join() 함수를 활용한 DevkitStringBuilder UDT를 구현했습니다. 부분 문자열을 배열에 추가해 나가고, 마지막에 Join으로 단 한 번만 연결하는 O(n) 구성입니다.
' 사용 예시
Dim sb As DevkitStringBuilder
StringBuilderInit sb
...
VBA에는 표준 StringBuilder가 없습니다. .NET의 StringBuilder를 사용할 수도 있지만, COM을 경유하여 사용해야 하므로 직접 구현하는 것이 더 빠릅니다. 확실히 이 구현을 통해 export 속도는 향상되었습니다. 하지만 "극적으로 빨라졌다"고 느낄 정도는 아니었습니다.
체감상 의미가 컸던 것은 진행 상황 표시
속도 개선보다 실제로 사용하며 효과를 실감한 것은 상태 표시줄(Status Bar)에 진행 상황을 표시하는 것이었습니다.
Exporting sheet map 3/12: 대조 시트
대규모 파일에서는 export에 몇 분이 걸릴 수도 있습니다. 아무런 표시 없이 Excel이 계속 멈춰 있으면, 개발자는 "무한 루프 버그가 아닌가"라고 의심하기 시작하며, 처리가 완료될 때까지 기다려야 할지 강제 종료해야 할지 고민하게 됩니다. 하지만 진행 상황이 보이기만 해도 이러한 불안은 사라집니다.
퍼포먼스의 "체감"은 순수하게 속도만의 문제가 아니라, 대기 시간을 예측할 수 있는지 여부에도 좌우됩니다. 에스포트(Export)가 완료된 후에는 상태 표시줄을 원래 상태로 되돌리고, 도중에 에러가 발생한 경우에도 마찬가지로 복구하도록 구현했습니다.
제3장 근본적인 문제: roundtrip이 깨지다
설계 경위
xlsm_devkit은 원래 export 전용 도구로 설계를 시작했습니다. 시트맵을 Markdown으로 내보내어 VBA 코드와 함께 AI에게 전달하는 것이 첫 번째 목적이었습니다.
그런데 실제로 사용해 보니, AI가 시트 위의 수식 또한 아무리 연쇄가 복잡하더라도 정확하게 읽어낸다는 것을 알게 되었습니다. 그래서 코드뿐만 아니라 시트 위의 수식이나 값의 편집에도 AI의 도움을 받고 싶다고 생각했습니다. Excel 시트의 편집에 AI의 힘을 빌리는 것은 어렵지만, Markdown 파일이라면 IDE 쪽에서 편집할 수 있으므로 AI의 도움을 받을 수 있습니다. 따라서 Markdown 파일 또한 VBA 코드와 마찬가지로 Excel로 import 할 수 있다면, AI의 힘을 빌려 Excel 시트를 편집할 수 있게 됩니다. 이에 따라 Markdown 파일을 Excel 시트로 import 하는 기능도 대응하기로 했습니다.
문제는 이 추가 기능이 사후에 도입되었다는 점입니다. export 기능은 Excel의 상태를 충실하게 문자로 써내는 것을 목표로 만들어졌습니다. 하지만 import 기능을 추가하려면 수식이나 값이 Excel 시트와 Markdown 사이에서 export와 import를 반복하게 되므로, roundtrip safe한 설계가 필수적이 됩니다. 이를 위해 export 기능의 사양 변경이 필요해졌습니다.
실제로 무엇이 깨졌는가
IDE 상에서 시트맵의 수식을 편집하고 import 해보니, Excel 상의 수식이나 값이 깨져버린다는 것을 알게 되었습니다. 그 원인을 조사해 보니 다음과 같은 원인을 발견했습니다.
Excel의 값과 표시 형식은 불가분
매우 근본적인 문제인데, Excel에서 다루는 수치는 표시 형식(NumFmt)과 불가분의 관계에 있으며, roundtrip safety를 실현하기 위해서는 표시 형식을 Markdown에 반영하는 것이 필수적이라는 것을 알게 되었습니다. 따라서 표시 형식(NumFmt)을 다루는 기능을 추가했습니다.
표시 형식과 값이 불가분하다는 것을 알 수 있는 대표적인 예가 날짜와 시간입니다. Excel은 1900/1/1을 1로, 24시간을 1.0으로 계산한 수치로 날짜와 시간을 유지하고 있습니다. 이를 수치로 출력하면 값은 1이 됩니다. 따라서 1900/1/1이라는 데이터가 담긴 셀의 값을 써내면 1이 됩니다. 이를 읽어오면 셀에 1900/1/1이 아니라 1로 표시되어 버립니다. 또한, 시간의 경우 표시 형식(Display Format)이 예를 들어 (HH:MM)라면, 시간이 같을 경우 날짜가 달라도 표시상으로는 구분이 불가능해지므로, 수치 대신 표시 문자열로 값을 다룰 수도 없습니다. 다양한 예를 생각해 보았지만, 역시 값과 표시 형식 (NumFmt)을 함께 다루는 것이 필수적이라는 결론에 도달했습니다.
세미콜론을 포함하는 서식 문자열의 충돌
Markdown 파일의 Style 열에는 세미콜론으로 구분하여 여러 종류의 정보를 한 셀에 저장하고 있습니다. 예를 들어 아래는 배경색, 표시 형식, 폰트 크기를 저장하고 있습니다.
BG:#FFFF00; NumFmt:#,##0.00;[Red]-#,##0.00; FontSize:11
하지만 #,##0.00;[Red]-#,##0.00이라는 표시 형식 문자열은 그 자체로 세미콜론을 포함합니다. 단순한 Split(styleStr, "; ")로 파싱하면 중간에 분할되어 데이터가 깨집니다. 이에 대한 대책으로, 스타일 값 안의 세미콜론은 \;로 이스케이프(Escape) 처리하고, 파서(Parser) 측은 이스케이프를 고려한 ParseStyleTokens로 분할하도록 개선했습니다.
"-"라는 리터럴 값과 센티널(Sentinel) 값의 충돌
시트 맵(Sheet map)에서는 값이 존재하지 않음을 나타내는 센티널 값으로 -를 사용하고 있습니다. 그런데 셀이나 셰이프(Shape)의 레이블에 문자열로서 "-"가 들어있는 경우, 이를 구분하지 못하고 있었습니다. 대책으로, 리터럴 "-"는 \-로 이스케이프하여 써내고, import 시에 복원하도록 변경했습니다.
앞뒤 공백의 소실
Markdown 테이블의 셀 값을 파싱할 때 Trim()으로 앞뒤 공백을 제거하고 있었는데, 이는 테이블의 구분자 | 사이의 정렬용 공백을 제거하기 위한 것이었습니다. 셀 값 자체에 앞뒤 공백이 포함되어 있으면 그것까지 함께 지워져 버렸습니다. 대책으로, Trim을 사용하지 않고 "앞뒤의 공백 1칸만 제거하는" TrimMDTableField 함수로 교체했습니다.
roundtrip 완전 일치를 릴리스 조건으로 설정
이러한 수정 사항들을 쌓아 올린 후, 최종적인 릴리스 조건으로 "export → import → export를 통해 생성된 Markdown이 완전히 일치할 것"을 설정했습니다.
구체적으로는, 실제 업무용 Excel에 대해 export를 실행하고, 그 Markdown을 import로 반영한 뒤, 다시 export하여 diff를 비교합니다. 차분이 제로(0)가 되는 것을 확인한 후 릴리스합니다. 이 검증을 거쳐야 비로소 "IDE에서 편집한 내용을 안심하고 Excel로 되돌릴 수 있다"는 보장을 얻을 수 있습니다.
roundtrip의 신뢰성은 눈에 띄지 않지만, 도구로서 실용성을 갖추었는지를 좌우하는 중요한 속성입니다. export 전용으로 시작된 이 도구가 양방향 워크플로우를 담당할 수 있게 된 것은, 실전 투입을 통해 보인 과제들을 하나씩 해결해 온 결과입니다.
제4장 실제 업무 파일이기에 마주한 문제들
pptx_yaml_excel과 같은 "깨끗한" 프로젝트에서는 나타나지 않았던 문제들이, 장기 운용 및 반복 수정된 업무용 Excel에 투입되면서 차례차례 모습을 드러냈습니다.
에러 값 셀에서 크래시 발생
업무 시스템에서 붙여넣은 데이터의 검증을 수행하는 파일이기에, 데이터 상태에 따라 #REF!나 #DIV/0!와 같은 에러 값을 가진 셀이 존재합니다. xlsm_devkit의 export는 셀의 값을 rng.Value로 가져오고 있었는데, 에러 값 셀에 대해 이를 호출하면 VBA 런타임 에러가 발생하여 export가 도중에 중단되었습니다.
대책은 간단합니다. IsError()로 에러 값인지 먼저 확인하고, 에러인 경우에는 rng.Text(Excel이 표시하고 있는 문자열, 예를 들어 #REF!
)를 폴백 (fallback)으로 사용하도록 변경했습니다. 이를 통해 에러 셀은 해당 표시 문자열로서 시트맵 (sheetmap)에 기록됩니다.
다만, VBA는 다른 많은 언어와 달리 식A AND 식B를 평가할 때 단락 평가 (short-circuit evaluation)를 하지 않고, 식A가 False이더라도 식B를 평가하므로 주의가 필요합니다. 안타깝게도 AI는 이 점을 간과하여 단락 평가가 이루어질 것이라고 가정했던 모양입니다. 식A 쪽에 Not IsError()라고 작성되어 있었는데, 식B 안에서 에러가 발생했을 경우 식A AND 식B 전체의 평가가 에러가 되어버리고 말았습니다.
보호된 시트가 혼재하는 경우
장기 운용되는 파일에는 오조작 방지를 위해 시트 보호 (sheet protection)가 걸려 있는 시트가 포함되어 있을 수 있습니다. 보호된 시트에 셀을 쓰는 것은 당연히 에러가 발생하지만, 기존 구현에서는 import를 시작한 후 도중에 에러가 발생하기 때문에 일부 시트만 어중간하게 업데이트된 상태가 되는 경우가 있었습니다.
대응책으로, import를 시작하기 전에 모든 대상 시트의 보호 상태를 확인하고, 보호되어 있는 시트가 있다면 해당 목록을 표시한 뒤 임포트를 중지하는 AbortIfProtectedImportSheets를 추가했습니다. '도중에 멈추는' 것이 아니라 '처음부터 멈춤'으로써, 어중간한 상태를 만들지 않도록 하고 있습니다.
Shape의 import 대응
xlsm_devkit의 시트맵은 이전부터 Shape (버튼이나 도형)의 레이블 (label)이나 OnAction 설정을 export 해왔지만, import 측의 대응이 누락되어 있었습니다. export는 가능하지만, IDE에서 편집한 내용을 되돌릴 수단이 없는 단방향 상태였습니다.
업무용 Excel에는 버튼이 많이 배치되어 있으며, 버튼의 레이블이나 할당된 매크로를 IDE에서 변경할 수 없는 것은 불편합니다. 이번에 ApplyShapeMapMarkdown를 구현하여, 시트맵의 ## Shapes 섹션을 읽어 셰이프 (shape)의 레이블, 수식, OnAction을 반영할 수 있도록 했습니다. 시트 위에 동일한 이름의 셰이프가 존재하면 업데이트하고, 존재하지 않으면 사각형 플레이스홀더 (placeholder)를 생성합니다.
셀의 보호 상태 대응
셀 보호 해제 상태 (Range.Locked)는 원래 export 되지 않았습니다. 하지만 시트를 잠금(lock)하여 사용하는 업무용 Excel 시트에서는 입력에 사용하는 셀만 잠금을 해제하여 입력할 수 있게 하므로, 시트 해석 시 이 정보가 매우 중요해집니다. 그래서 Locked를 Markdown의 Style 열에 반영하는 것을 고려했습니다. 다만, Style에 올리는 항목 중 예를 들어 Bold, Italic, Wrap 등은 기본값이 False이므로, 기재량을 줄이기 위해 True일 때만 기재하는 사양으로 했습니다. 하지만 Locked의 기본값은 True입니다. 그래서 기본값을 False로 만들기 위해, Locked 대신 Unlocked를 기재하기로 했습니다.
import 실패 시의 상태 복구
안정성과 관련된 수정도 추가했습니다. import 처리 중에 Application.ScreenUpdating, Calculation, EnableEvents를 비활성화하지만, 에러로 인해 처리가 중단될 경우 이것들이 비활성화된 상태로 남는 문제가 있었습니다. Excel 화면이 업데이트되지 않거나 계산이 돌아가지 않는 상태로 조작이 계속되는 것입니다. 에러 핸들러 (error handler)를 정비하여, 정상 종료와 이상 종료 어느 경우에도 반드시 이것들을 복구하도록 수정했습니다.
제5장 DEV / Release 워크플로우
수작업이었던 프로세스
xlsm_devkit을 사용한 개발 흐름은 대략 다음과 같습니다.
- 배포 대상인
.xlsm을 열고,xlsm_devkit.bas를 수동으로 import한다. - VS Code와 VBE를 오가며 개발을 진행한다.
- 완성되면, 배포용 파일에서
xlsm_devkit및devkit_*모듈을 모두 삭제한다. - 깨끗해진 파일을 배포한다.
이 중 3단계가 문제였습니다. 모듈을 하나씩 수동으로 삭제하며, 삭제를 누락하지 않았는지 매번 확인하면서 작업해야 했기 때문입니다. 개발 도구가 포함된 채로 배포하게 될 리스크도 있습니다. 그래서 이 단계의 안전성·확실성·효율성을 향상시키기 위해 이 프로세스를 자동화하기로 했습니다.
CallInitDevMode
─ DEV 복사본 만들기
CallInitDevMode는 운영용(Production) 북에서 개발용 복사본을 생성하는 매크로입니다.
절차는 다음과 같습니다. 우선, 배포 대상인 운영용 북(예: MyTool.xlsm)에 xlsm_devkit.bas를 수동으로 임포트하고, src/ 폴더에 필요한 devkit_* 파일들을 배치해 둡니다. 그 상태에서 CallInitDevMode를 실행하면, 동일한 폴더에 DEV_MyTool.xlsm이 생성되며, src/ 내의 devkit_* 파일들이 모두 임포트된 상태로 열립니다.
MyTool.xlsm ← 운영용 (devkit 없음)
DEV_MyTool.xlsm ← 개발용 (devkit 모두 포함)
src/
...
CallInitDevMode를 실행한 후에는, 원래의 MyTool.xlsm을 저장하지 않고 닫습니다. 이렇게 함으로써 운영용 파일에는 devkit 모듈이 남지 않습니다. 이후의 개발은 DEV_MyTool.xlsm에서 진행합니다.
CallSaveAsRelease
─ 릴리스 복사본 만들기
개발이 완료되면, DEV_MyTool.xlsm에서 CallSaveAsRelease를 실행합니다. 이를 통해 xlsm_devkit 및 devkit_* 모듈이 모두 삭제된 복사본이 MyTool.xlsm으로 저장됩니다. DEV_ 북 자체는 그대로 남기 때문에, 계속해서 개발을 이어갈 수 있습니다.
런처 다이얼로그(Launcher Dialog)에도 「릴리스로 저장」 버튼을 추가했습니다. 이 버튼은 북 이름이 DEV_로 시작하는 경우에만 활성화되도록 설계했습니다. 운영용 북에서 실수로 실행하는 것을 방지하고, 절차의 오류를 인지할 수 있도록 하기 위함입니다.
DEV_MyTool.xlsm ← 개발을 계속함
MyTool.xlsm ← devkit 없는 릴리스 복사본 (신규 생성)
「저장하지 않고 닫기」라는 조작에 대하여
CallInitDevMode 실행 후 운영용 북을 「저장하지 않고 닫는」 절차는 다소 직관에 어긋날 수 있습니다. 하지만 이는 의도적인 설계입니다. CallInitDevMode 실행 중에 devkit 모듈을 일시적으로 임포트하는 것은 어디까지나 DEV_ 복사본을 만들기 위함이며, 운영용 파일 측에 변경을 가하고 싶지 않기 때문입니다. 저장하지 않고 닫으면 운영용 파일을 깨끗하게 유지할 수 있습니다. 이 조작을 잊지 않도록 하기 위해, CallInitDevMode 완료 시 메시지에도 명시적으로 안내를 표시하고 있습니다.
제6장 요약 및 전망
실전 투입을 통해 보인 것들
이번 일련의 개선 사항을 되돌아보면 공통된 패턴이 보입니다. 모든 문제는 소규모의 클린한 pptx_yaml_excel에서는 나타나지 않았던 것들입니다.
속도 문제는 대규모 Excel 파일을 다루면서 비로소 드러났습니다. Roundtrip의 미비함은 「IDE에서 편집하고 되돌리기」라는 실제 사용 방식을 적용했을 때 비로소 나타났습니다. 에러 값·보호된 시트(Protected Sheet)·도형(Shape) 문제는 장기간 운용되어 온 파일 특유의 현실적인 상태에서 기인했습니다. DEV/Release 워크플로우의 필요성은 이 도구를 사용하여 작업을 반복하는 과정에서 보이기 시작했습니다.
PoC(Proof of Concept) 단계에서는 보이지 않던 것이 본番 투입을 통해 보인다는 것은 당연한 일입니다. 다만, xlsm_devkit의 경우에는 거기에 더해 「AI 주도로 개발했다는 것」이라는 요인도 있었습니다. AI가 생성하는 코드는 동작은 하지만, 개발자의 경험칙이나 실운용상의 고려가 빠지기 쉬우며, 그 빈틈을 실전 투입이 가르쳐 주었다고도 할 수 있습니다.
pptx_yaml_excel로의 반영
이번 개선 사항은 모두 pptx_yaml_excel에도 적용했습니다. pptx_yaml_excel은 xlsm_devkit의 구현 검증을 위한 "깔끔한 샘플"이라는 성격이 강하지만, xlsm_devkit 자체의 완성도가 높아짐에 따라 그 혜택을 받는 형태가 되고 있습니다.
향후 전망
조건부 서식 (Conditional Formatting) 읽기도 검토하고 있습니다. 조건부 서식은 Excel 파일의 동작에 큰 영향을 미치지만, 그 설정을 AI에게 전달할 수단이 아직 없습니다. 쓰기(Write-back)를 포함한 완전한 라운드트립 (Roundtrip)은 복잡하기 때문에, 우선은 읽기 전용의 요약 출력부터 착수하는 것을 고려하고 있습니다. 그 외에도 매크로가 포함된 Excel 파일의 내용은 매우 다양하므로, 그러한 파일들에 적용해 보면서 필요한 기능이 나타나면 추가하게 될 것입니다.
xlsm_devkit 입수
xlsm_devkit의 리포지토리(Repository)는 GitHub (minipoisson/xlsm_devkit)에 공개되어 있습니다.
시리즈
Discussion

AI 자동 생성 콘텐츠
본 콘텐츠는 Zenn AI의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기