코딩 에이전트로 JustHTML을 만든 이야기
게시일: 2025년 12월 21일 | 원문 작성일: 2025년 12월 3일 | 저자: Emil Stenström | 원문 보기
핵심 요약
Python으로 HTML5 파서를 만들면서 코딩 에이전트와 협업한 개발자의 솔직한 경험담이에요. 결과물은 html5lib 테스트를 100% 통과하는, 의존성 없는 순수 Python 라이브러리예요.
- 결과물 — JustHTML: Python 3,000줄, 8,500개 이상의 테스트 통과, CSS 셀렉터 API 포함
- 개발 과정 — 16단계의 반복과 완전 재시작을 거침. 단순 파서에서 Rust 토크나이저 실험, html5ever 포팅까지
- 역할 분담 — 저자는 아키텍처 결정과 코드 리뷰, 에이전트는 모든 구현 작업. “에이전트가 타이핑하고, 저는 생각했어요”
- 핵심 교훈 — 측정 가능한 목표 설정, 생성된 코드 반드시 검토, 실패를 학습 기회로 활용
• • •
시작하며
저는 최근 JustHTML이라는 라이브러리를 공개했어요. Python으로 만든 HTML5 파서인데, html5lib 테스트 스위트를 100% 통과하고, 외부 의존성이 전혀 없으며, CSS 셀렉터 쿼리 API도 포함되어 있어요.
이 프로젝트를 진행하면서 코딩 에이전트와 효과적으로 협업하는 방법에 대해 많이 배웠어요. 그 이야기를 나눠보려고 해요.
솔직히 말하면, 저는 HTML5 파싱에 대해 아는 게 거의 없었어요. 특히 잘못된 마크업을 어떻게 처리해야 하는지 몰랐죠. Henri Sivonen이 Firefox 파서 작업에 대해 쓴 글을 읽고 나서야 “입양 대행 알고리즘”1이라는 게 있다는 걸 알았어요. 이 알고리즘은 잘못 중첩된 서식 요소를 처리하는 엄청나게 복잡한 로직이에요. “노아의 방주 조항”2이나 스택 조작 같은 개념들이 등장하죠.
“저는 여전히 그 문제들을 어떻게 풀어야 하는지 몰라요. 하지만 제가 만든 파서는 레퍼런스 구현인 html5lib보다 그 문제들을 더 잘 해결해요. AI의 힘이에요!”
왜 HTML5인가?
코딩 에이전트와 함께 프로젝트를 할 때는, 이미 테스트가 많이 준비되어 있는 프로젝트를 선택하는 게 좋아요. HTML5에는 상세한 스펙이 있고, html5lib-tests라는 저장소에 수천 개의 테스트 케이스가 있어요. 자율적으로 작업하는 에이전트에게 즉각적인 피드백을 줄 수 있는 완벽한 환경이었죠.
파서 만들기: 반복, 재시작, 성능 작업
저는 VS Code에서 GitHub Copilot을 에이전트 모드로 사용했어요. 자동 명령 승인을 켜고, 수동 검토가 필요한 항목들은 블랙리스트로 관리했어요.
첫 시도는 기본적이고 성능도 형편없는 베이스라인을 만드는 것이었어요.
html5lib-tests를 통합했더니 통과율이 1% 미만으로 나왔어요. 깨진 HTML 구조 같은 수천 가지 엣지 케이스가 드러났죠.
리팩토링과 버그 수정을 거쳐 커버리지가 약 30%까지 올라갔어요.
에이전트가 핸들러 기반 아키텍처로 코드베이스를 재구성했어요:
class TagHandler:
"""모든 태그 핸들러의 기본 클래스."""
def handle_start(self, context, token):
pass
class UnifiedCommentHandler(TagHandler):
"""모든 상태에서 주석을 처리."""
def handle_start(self, context, token):
context.insert_comment(token.data)완전한 테스트 커버리지를 달성하려면 Claude Sonnet 3.7 출시까지 기다려야 했어요.
벤치마킹 결과, 파서가 html5lib보다 3배 느리다는 걸 확인했어요.
에이전트가 토크나이저를 Rust로 다시 작성했어요(저는 Rust를 읽을 줄 몰라요). 690줄짜리 구현이 나왔는데, 겨우 html5lib 속도와 비슷한 수준이었어요.
Servo의 파싱 엔진인 html5ever를 발견하고 나서, “굳이 이걸 직접 만들어야 하나?” 하는 근본적인 의문이 들었어요.
프로젝트를 포기하는 대신, “html5ever 로직을 Python으로 포팅”하기로 했어요. 바이너리 설치 없이 기존 Python 라이브러리들보다 나은 성능을 낼 수 있을 거라 생각했죠.
1% 미만의 커버리지에서 다시 시작했어요. 에이전트가 처음부터 Rust 코드베이스를 참조하면서 작업했고, 반복적인 작업 끝에 결국 100% 커버리지를 달성했어요.
다시 작성한 코드베이스도 여전히 html5lib보다 느렸어요.
프로파일링 도구를 만들고, 실제 벤치마킹을 위해 인기 웹페이지 10만 개 데이터셋을 준비했어요. 새로 출시된 Google의 Gemini 3 Pro가 반복적인 프로파일링과 벤치마킹 사이클을 통해 의미 있는 최적화를 이뤄냈어요. 다른 모델들은 하지 못했던 결과였죠.
def _append_text_chunk(self, chunk, *, ends_with_cr=False):
if not chunk:
self.ignore_lf = ends_with_cr
return
if self.ignore_lf:
if chunk[0] == "\n":
chunk = chunk[1:]코드 커버리지 분석을 돌려보니 테스트되지 않은 코드가 많았어요. 안전하게 삭제할 수 있었죠. 이 접근법으로 성능과 코드 가독성이 동시에 개선됐어요:
- 이전: 트리빌더 코드 786줄
- 이후: 453줄
- 결과: 더 빠르고 깔끔한 구현
에이전트가 무작위로 잘못된 HTML 문서를 생성하는 HTML5 퍼저를 만들어서 파서 취약점을 찾았어요:
def generate_fuzzed_html():
"""완전한 퍼징된 HTML 문서 생성."""
parts = []
if random.random() < 0.5:
parts.append(fuzz_doctype())
num_elements = random.randint(1, 20)퍼저가 생성한 웹페이지 300만 개를 테스트했는데 크래시가 없었어요. 발견된 문제마다 새 테스트 케이스가 추가됐죠.
다른 파서들을 html5lib 스위트로 테스트해봤더니 경쟁 순위가 드러났어요: 어떤 파서도 90% 커버리지를 달성하지 못했고, 인기 있는 Python 옵션인 lxml은 겨우 1%, html5lib 자체도 88%에 불과했어요.
에이전트가 CI/CD 파이프라인, GitHub 릴리스, CSS 셀렉터 API를 설정했어요:
from justhtml import JustHTML, query
doc = JustHTML("<div><p>Hello</p></div>")
elements = query(doc, "div > p")라이브러리 이름을 “turbohtml”에서 “JustHTML”로 바꿨어요. 순수 속도보다 신뢰성을 강조하기 위해서요.
에이전트가 한 일 vs 제가 한 일
“저는 여전히 HTML5를 제대로 몰라요. 에이전트가 대신 작성해줬거든요.”
제가 한 일은 API 설계 결정, 큰 방향에서의 수정, 그리고 코드 리뷰였어요. 힘든 작업과 실제 코딩은 전부 에이전트가 했어요.
모델별로 장단점이 있었어요. Gemini는 한 번에 정답을 맞히는 능력이 뛰어났고, Claude Opus는 반복하면서 점점 좋은 솔루션에 도달하는 데 최고였어요.
코딩 에이전트와 함께 일하는 실용적인 팁
핵심 원칙들
- 측정 가능한 목표로 시작하세요 — “테스트를 통과하게 만들어”가 막연한 개선 목표보다 훨씬 효과적이에요
- 생성된 코드를 전부 검토하세요 — 문제를 발견하고 이해도를 높일 수 있어요
- 접근 방식이 잘못됐다고 느껴지면 비판적 피드백을 주세요
- 버전 관리를 유지하세요 — 쉽게 되돌릴 수 있어요
- 실패를 허용하세요 — 오류가 에이전트에게 귀중한 교훈을 줘요
그래서, 그만한 가치가 있었나요?
최종 결과: JustHTML은 약 3,000줄의 Python 코드로 이루어져 있고, 8,500개 이상의 테스트를 통과해요.
에이전트 도움 없이는 이 속도로 프로젝트를 완료하는 게 불가능했을 거예요.
중요한 건, “빠르게”가 생각하는 시간을 없앤다는 뜻은 아니에요:
“저는 코드를 검토하고, 설계 결정을 내리고, 에이전트를 올바른 방향으로 이끄는 데 많은 시간을 썼어요. 에이전트가 타이핑을 했고, 저는 생각을 했어요. 아마 그게 올바른 역할 분담일 거예요.”
역자 주
- 입양 대행 알고리즘(Adoption Agency Algorithm): HTML5 파싱 스펙에서 가장 복잡한 부분 중 하나예요.
<b>hello <i>world</b> how</i>처럼 잘못 중첩된 태그를 만났을 때, 브라우저가 어떻게 이를 “수양 부모에게 입양 보내듯” 올바른 위치로 재배치해야 하는지를 정의해요. 이름이 특이하지만, 공식 HTML5 스펙에 실제로 이렇게 명명되어 있어요. ↩ - 노아의 방주 조항(Noah’s Ark Clause): 성경의 노아의 방주처럼 “각 종류별로 두 마리까지만”이라는 규칙에서 이름을 따왔어요. HTML5 파서에서 같은 서식 태그(예:
<b>)가 연속으로 3개 이상 중첩되면 하나를 제거해서 최대 2개까지만 유지하도록 해요. 이렇게 하면<b><b><b><b>text같은 마크업이 무한히 깊어지는 것을 방지할 수 있어요. ↩
저자 소개: Emil Stenström은 스웨덴의 소프트웨어 개발자로, Friendly Bit 블로그를 운영하고 있어요.
참고: 이 글은 Emil Stenström이 자신의 블로그에 게시한 아티클을 번역한 것이에요. AI 코딩 에이전트와의 협업 경험을 솔직하게 공유한 귀중한 사례예요.
원문: How I wrote JustHTML using coding agents - Emil Stenström, Friendly Bit (2025년 12월 3일)
생성: Claude (Anthropic)