메뉴
HN
Hacker News 29일 전

AI와 협업하기: 구체적인 사례

IMP
7/10
핵심 요약

개발자가 AI(Claude)를 활용해 오픈소스 언어인 하이퍼스크립트의 파싱 버그를 해결한 실제 사례를 공유합니다. AI는 방대한 코드에서 버그의 근본 원인을 신속하게 찾아내는 데 탁월한 성능을 보였지만, 맥락 이해가 필요한 실제 코드 수정에서는 한계를 보였습니다. 개발자가 AI에 전적으로 의존할 경우 발생할 수 있는 '호랑이 담배 피우는 격(사수의 제자 문제)'의 위험성을 경고하며, AI를 도구로서 비판적으로 활용하는 것이 중요합니다.

번역된 본문

AI와 협업하기: 구체적인 사례 Carson Gross 2026년 6월 29일

나는 대체로 AI에 대해 복잡한 감정을 가지고 있다. 지난 1년 동안 AI가 개발에 있어 매우 강력한 도구가 되었다는 것은 의심의 여지가 없지만, 동시에 개인적으로(예: 지적 능력의 점진적인 둔화) 그리고 사회적으로(예: 환경 문제, 개인용 컴퓨팅 비용 증가 등) 많은 위험을 동반한다.

「Code is Cheap(er)」라는 글에서, 나는 개발자가 AI에 의존하게 되어 자신이 구축하는 시스템에서 발생하는 문제를 이해하고 제대로 해결할 수 없게 되는 '소서러스 애플런티스(견습 마법사)' 문제에 대해 경고한 바 있다. 이 글에서는 하이퍼스크립트(hyperscript)를 유지 보수하면서 AI와 가졌던 구체적인 상호작용을 살펴보고, AI가 가진 일반적인 장단점을 보여줌과 동시에 내가 간신히 피한 '견습 마법사' 문제를 특별히 설명하고자 한다.

하이퍼스크립트 파서

배경을 설명하자면, 하이퍼스크립트는 웹을 위한 대체 인터프리터 스크립팅 언어이다. 아이러니하게도 전적으로 자바스크립트(JavaScript)로 작성되었다. 이것은 좀 이색적인 소프트웨어다. 내가 실험의 일환으로 작성하는 동안 파싱의 많은 규칙을 의도적으로 깨버렸기 때문이다. 몇 가지 예는 다음과 같다:

파싱 로직이 파싱 요소(element)들과 함께 배치되어 있다. 파서가 플러그인 형태로 구성되어 있으며, 문법이 동적으로 정의된다. 속성 접근을 위해 여러 구문을 지원한다.

대부분의 프로그래밍 언어에는 추천하고 싶지 않은 접근 방식이지만, 이 프로젝트에서는 꽤 잘 작동했다. 이는 소프트웨어 세계에서 어떤 문제를 해결하는 데 분명 여러 가지 방법이 존재한다는 것을 보여주는 또 하나의 증거다.

버그 리포트

우리의 이야기는 한 사용자가 0.9.91 릴리스로 업그레이드했을 때 발생한 회귀 버그를 보고하면서 시작된다. 다음 표현식이 더 이상 제대로 파싱되지 않았다:

fetch {% url ' trade :get_symbol_data' %}?symbol=${symbol} as JSON

특히, as JSON 부분이 너무 강하게 바인딩되어, 주어진 URL을 가져오고(fetch) 결과를 JSON으로 처리하는 사용자의 기대(그리고 이전의 정상 동작) 대신, 문자열 리터럴을 fetch에 전달하기 전에 JSON으로 변환하려고 시도하는 문제였다. 이런 종류의 바인딩 충돌은 파싱에서 전형적으로 발생하는 문제다. 하이퍼스크립트는 xTalk 스타일의 언어이고 영어의 모호함을 많이 상속받았기 때문에, 이 문제는 훨씬 더 심각하게 나타난다.

원인 조사

가장 먼저 해야 할 일은 이 회귀 버그가 왜 발생했는지 조사하는 것이었다. 이것은 내가 보통 AI의 도움을 의지하는 영역이다. 나는 Claude를 사용했고, 근본 원인을 찾는 데 있어 훌륭한 성과를 보여주었다.

0.9.91 버전에서 나는 go 명령을 리팩토링하여 fetch 명령과 로직을 재사용하고 공유하는 데 너무 공격적으로 나섰다. 나는 이 두 명령이 모두 사용할 수 있는 공통 메서드인 parseURLOrExpression()을 추출했는데, 이 과정에서 실수로 fetch 명령어 이후의 문법을 일반적인 표현식(expression)까지 아울러 포함하도록 확장해버렸다.

as 키워드는 표현식 내에서 타입 간 변환을 허용하는 '변환 표현식(conversion expression)'으로서의 의미를 가진다:

set x to "42" as Int

하지만 as 키워드는 동시에 fetch 명령의 수식어(modifier)이기도 하며, 응답을 어떻게 변환할지 알려준다:

fetch https://hyperscript.org as Text

(아마도 이 사실이 당신을 역겹게 만들었을 수도 있다. 그렇다면 다행이다.)

문제의 핵심은 리팩토링 과정에서 의도치 않게 fetch 키워드 뒤의 표현식을 파싱하도록 만들었고, 이 표현식 파서가 as 키워드를 fetch의 수식어로 허용하는 대신 자기 자신의 표현식으로 소비해버렸다는 것이다. Claude의 도움 덕분에 나는 이 사실을 몇 분 안에 파악할 수 있었고, 내가 직접 알아내야 했다면 훨씬 빨리 해결한 셈이다.

문제 해결

문제의 원인을 찾는 데 있어 AI는 매우 유용했다. 하지만 문제를 해결(수정)하는 데 있어서는 훨씬 취약했다. 여기서 인정하자면, 내가 게으름을 피워 AI에게 해결책을 요청했기 때문에 그 해결책에 대해 불평하는 것이 다소 게으른 행동처럼 보일 수 있다. 그럼에도 불구하고 나는 이 일련의 사건들이 유익하다고 생각하므로, 정확히 무슨 일이 일어났는지 살펴보자.

제안된 수정 방안 1: 해키(Hack)한 편법

가장 먼저 제시된 제안은 이른바 '문자열 유사(string-like)'로 불리는 것을 파싱하는 것이었다...

원문 보기
원문 보기 (영어)
Working With AI: A Concrete Example Carson Gross June 29, 2026 I am, generally, ambivalent towards AI. There is no doubt it has become a very powerful tool for development in the last year, but it also comes with many dangers, both for us individually (e.g. the slow dulling of our intellects) as well as collectively (e.g. environmental concerns, increasingly expensive personal computing, etc.) In “Code is Cheap(er)” , I warn about The Sorcerer’s Apprentice problem, where a developer becomes reliant on AI and is unable to understand and properly address issues that come up in the systems they are building. In this article I want to go through a specific interaction that I had with AI while maintaining hyperscript to show the strengths and weaknesses of AI in general and to demonstrate The Sorcerer’s Apprentice problem (which I narrowly avoided) in particular. The Hyperscript Parser For some background, hyperscript is an alternative interpreted scripting language for the web. It is, ironically, written entirely in JavaScript . It is a strange piece of software: I intentionally broke many of the rules of parsing when writing it as an experiment to see how things would work out. Some examples: Parsing logic is colocated on parse elements The parser is pluggable, and the grammar is defined dynamically It supports multiple syntaxes for property access . It is not an approach I would recommend for most programming languages, but it has worked out pretty well for this project. Yet another demonstration that there are indeed multiple ways to skin the cat in software. A Bug Report Our story begins when a user reported a regression when upgrading to the 0.9.91 release. The following expression no longer parsed properly: fetch `{% url ' trade :get_symbol_data' %}?symbol=${symbol}` as JSON In particular, the as JSON was binding too tightly and trying to convert the string literal into JSON before it was handed to fetch instead of doing what the user expected (and what it did previously) namely fetching the given url with the results treated as JSON. This sort of binding conflict is a classic problem in parsing. Because hyperscript is an xTalk style language and inherits many of the ambiguities of English, this problem is all the worse in it. Investigating The Cause The first thing to do was to investigate why this regression occurred. This is an area where I am typically going to lean on AI to help. I use Claude, and it did an admirable job finding the root cause: in 0.9.91 I had been overly aggressive in refactoring the go command to reuse/share logic with the fetch command. I had extracted a common method for both of these commands to use, parseURLOrExpression() , but, in doing so, I accidentally expanded the grammar after the fetch command to include the general expression , er, expression. The as keyword has a meaning in expressions: it is a conversion expression , allowing you to convert between types: set x to "42" as Int But the as keyword is also a modifier of the fetch command, telling it how to convert the response: fetch https://hyperscript.org as Text (Perhaps this fact makes you throw up a little bit in your mouth. Good.) The crux of the issue was that, inadvertently in the refactor, I had made the parser parse an expression after a fetch keyword which was now consuming the as keyword as an expression, rather than allowing it to be a modifier for fetch . With the help of Claude I was able to figure this out in a few minutes, much faster than if I had had to figure it out on my own. Fixing The Issue AI was very helpful in finding the cause of the problem. In fixing the problem, however, it was much weaker. I will admit here I was being lazy and asked AI for a solution, so complaining about those solutions feels a bit, well, lazy, but I still think the string of events is informative, so let’s go through exactly what happened. Proposed Fix 1: A Hack The first suggestion that was given was to parse what is called a “string-like” leaf first, then fall back to a full expression: return this . parseElement ( "stringLike" ) || this . requireElement ( "expression" ); This fix would have solved the immediate problem presented by the user. However, it was very specific to the reported bug and wouldn’t have fixed the general case, such as if someone uses a variable as the target of a fetch: fetch $url as JSON I rejected this proposal because of this: too hacky and not general enough. (Note that the hyperscript parser has plenty of organically supplied hacks in it, so this may have been the pot calling the kettle black.) Proposed Fix 2: Better But Unnecessary Complexity The second proposal was more interesting: add a noConversions flag on the parser, set it around the URL parse, and have AsExpression.parse bail when it is set: // AsExpression.parse() if ( parser . noConversions ) return ; This will horrify many parser engineers because it makes the hyperscript parser context-sensitive . Good. The hyperscript parser was already context-sensitive. In looking at this fix and thinking for a second, I realized that we already had the hacky context-sensitive infrastructure we needed without introducing a new flag on the parser, but Claude had missed it. “Follows” In The Hyperscript Parser In the hyperscript parser we have a notion of “follows” , that is, tokens that are claimed by a “higher up” parse element as a follow token. The hyperscript parser is (a somewhat strange) recursive descent parser, and this allows a parse element (usually a command) to “claim” a keyword, and expressions won’t match against them during parsing. As an example, the when feature uses or as a separator rather than as a logical connective in its declaration: < div _ = "when $x or $y changes put it into me" ></ div > (I can hear many parser engineers closing this window in anger. Good.) It turns out that this feature could be used to achieve what we wanted: rather than adding a new flag to the parser we could push as as a follow, then parse the expression, then pop it as a follow. This would prevent the AsExpression from parsing, while still allowing most general expressions such as variables to work. Proposed Fix 3: Close, But No Cigar I pointed this out to Claude and, in a frisson of excitement, it told me that I was “absolutely right!” and set about using this technique to fix the bug. Claude added the correct code to the parseURLOrExpression() which fixed the issue generally without adding any additional parser infrastructure. Good to go. The Final, Semi-Organic Fix However, as I was reviewing the change, I realized that the new fix was overly broad: both fetch and go shared this method, but only fetch used as to signal a modifier. The existing fix prevented the perfectly valid use of as conversion expressions in go commands as well. So I implemented the final fix myself, in FetchCommand#parse() : parser . pushFollow ( "as" ); try { var url = parser . parseURLOrExpression (); } finally { parser . popFollow (); } if ( parser . matchToken ( "as" )) { ... Here I narrowed the special case to only the fetch command, leaving go parsing unaffected. This ended up being my final answer to the bug. Tests Along the way I had Claude generate some tests for the various cases. There is a good existing test suite for hyperscript, and Claude did a good job of creating small, focused tests that showed the problem and that the fix was working properly. Another area AI appears to work well. The Moral of The Story OK, so what is interesting about this fairly mundane bug fix story? I think it is interesting to see where AI did well, namely in investigation and test creation, and to contrast that with where it didn’t do so well: coming up with a clean solution. If I had not been familiar with the hyperscript parser and its infrastructure this fix could have easily led to technical debt being accrued in the project: another hacky parsing corner case, another bit of state on the parser, etc. Technical debt, I