모델에 오래된 코드를 보여주면 오래된 버그까지 작성합니다 — 32회 실행에서 재사용 0%
기존 저장소의 코드가 AI 모델의 다음 코드 작성 방식과 결함까지 결정하는지 160회 생성으로 검증했습니다. 오래된 중복 구현만 보여주면 32회 모두 새 구현을 만들었지만, 문서화된 규칙과 실제 공용 컴포넌트를 제공하자 재사용률과 컴파일 가능성이 크게 달라졌습니다.

본문
작성자는 풋볼 퀴즈 앱에서 같은 검색 상자(search box)가 11개 게임에 각각 복제되어 있던 문제를 정리하는 데 7개의 pull request를 사용했습니다. 복제된 구현은 시간이 지나며 서로 달라졌습니다. 방향키 동작은 네 가지 방식으로 나뉘었고, 11개 중 4개만 라운드 시작 시 모바일 키보드를 내리는 처리가 있었으며, 일부 구현은 검색 결과를 방향키로 탐색하는 동안 페이지를 스크롤했고, 한 구현은 오래된 검색 결과(stale result)를 제출할 수 있었습니다. 마이그레이션 과정에서는 이 구현들을 하나의 공유 컴포넌트(shared component)로 교체했고, 같은 컴포넌트가 12번째로 다시 생기면 실패하는 테스트도 추가했습니다.
저장소의 과거 코드가 생성 결과에 미치는 영향
이 작업은 정답 구현과 과거의 잘못된 구현이 각각 특정 커밋에 남아 있다는 점에서 AI 코드 생성 실험에 적합한 환경을 제공했습니다. 작성자는 새로운 게임 화면에 풋볼 선수 검색 상자를 추가하라는 동일한 작업 지시를 작성하고, 모델이 제시된 코드의 관례를 따르도록 요청했습니다. 각 모델과 조건마다 같은 지시를 8번씩 전달했으며, 생성 결과가 모두 동일해지지 않도록 temperature는 0.7로 설정했습니다.
변경한 것은 작업 지시가 아니라 그 위에 붙인 저장소 코드였습니다. 첫 번째 조건에서는 9개의 게임별 검색 컴포넌트가 존재하고 공유 구현은 아직 없던 커밋 82385a5를 사용했습니다. 두 번째 조건에서는 마이그레이션이 끝난 커밋 cc78828을 사용했으며, 이 커밋에는 공유 검색 컴포넌트와 TeamTies에서 이를 호출하는 코드가 포함되어 있습니다. 두 조건 모두 저장소에서 가져온 실제 소스 코드를 약 400줄씩 제공했기 때문에, 단순히 프롬프트가 더 길어서 생긴 차이는 배제하려고 했습니다.
실행한 모델은 DeepSeek v4 Pro, Llama 4 Maverick, GLM 5.3, Mistral 3 14B입니다. Claude와 GPT는 사용 중인 계정 등급에서 403 응답을 반환했기 때문에 오픈 웨이트 모델을 사용했다고 설명합니다. 오래된 분기형 구현을 보여준 조건에서는 32회 중 공유 검색을 재사용한 결과가 0%, 새 검색 상자를 직접 만든 결과가 100%였습니다. 반대로 마이그레이션 이후 코드를 보여준 조건에서는 32회 모두 공유 컴포넌트를 재사용했고, 직접 구현한 결과는 0%였습니다. 새로 손으로 작성한 구현의 중앙값은 190줄이었고, 공유 컴포넌트를 감싼 구현은 41줄이었습니다.
오래된 구현뿐 아니라 오래된 결함도 재현했습니다
작성자는 단순한 재사용률과 줄 수만 비교하지 않았습니다. 11개 구현이 서로 달라졌던 원인이었던 동작 목록을 생성 결과에도 적용했습니다. 오래된 코드 조건에서 생성된 33개의 직접 구현 중 목록의 양 끝에서 방향키를 누르면 처음과 끝이 연결되는 wrap-around 동작을 구현한 것은 0%였습니다. 저장소 문서가 직접 작성하지 말라고 명시한 SEARCH_MIN_QUERY_LENGTH 상수를 사용한 결과도 0%였고, 97%는 대신 bare >= 2 비교를 다시 작성했습니다. 모바일 키보드가 플레이어의 보드를 가리지 않도록 하는 focus guard는 58%에만 포함됐습니다.
이 결과에서 작성자는 이를 일반적인 AI 오류라기보다 자신의 팀이 2025년에 만들었던 오류가 2026년에 모델을 통해 재현된 것으로 설명합니다. 오래된 분기형 컴포넌트는 모델에게 분기해서 구현하는 방식을 가르쳤고, 그 안에 포함된 결함도 함께 전달했습니다. 더 직접적인 문제도 있었습니다. 오래된 코드 조건에서 생성된 32개 컴포넌트는 모두 이미 마이그레이션 과정에서 삭제된 useFootballerSearch 훅을 호출했습니다. 따라서 제시된 코드베이스에는 완벽히 맞는 것처럼 보였지만, 현재 저장소에 대해서는 전부 컴파일에 실패합니다.
문서화된 규칙과 구현 소스가 하는 일
오래된 코드가 모델을 과거로 끌어당긴다면 저장소에 낡은 코드가 가득한 상황에서 무엇을 할 수 있는지 확인하기 위해 두 조건을 추가했습니다. 첫 번째 추가 조건에서는 기존의 분기형 컴포넌트들을 그대로 보여주면서, 모든 검색은 공유 유틸리티를 거쳐야 하고 다른 typeahead를 직접 구현하지 말라는 CLAUDE.md의 한 문단을 함께 제공했습니다. 이 조건에서는 32회 모두 공유 검색을 선택했고, 직접 구현한 결과는 0%였습니다.
두 번째 조건에서는 공유 컴포넌트의 소스는 보여주지 않고 디렉터리 목록에 파일 이름만 표시했습니다. 이 경우 97%가 공유 검색 컴포넌트를 호출했고 3%가 직접 구현했습니다. 하지만 이름만 아는 것과 실제 API를 아는 것은 달랐습니다. 공유 컴포넌트의 소스까지 볼 수 있었던 조건에서는 32개 호출 모두 존재하지 않는 prop을 사용하지 않았습니다. 규칙만 제공한 조건에서는 28.6%가 최소 하나의 가짜 prop을 만들었고, 파일 이름만 제공한 조건에서는 82.1%가 존재하지 않는 prop을 사용했으며 호출당 평균 4.64개의 가상 prop을 작성했습니다.
예를 들어 모델은 실제로 존재하지 않는 onFootballerSelect와 debounceMs 같은 prop을 자신 있게 넣었습니다. 따라서 파일 이름만 제공했을 때의 97% 재사용은 명목적인 수치에 가깝고, 상당수 결과는 실제 저장소에서 컴파일되지 않습니다. 이 비교에서 문서화된 규칙은 모델이 어떤 경로를 선택할지 결정하고, 실제 컴포넌트 소스는 그 경로의 끝에서 올바른 인터페이스를 호출할 수 있게 하는 역할을 합니다.
나쁜 예시 하나보다 지배적인 패턴이 중요합니다
작성자는 공유 컴포넌트가 보이는 상태에서 오래된 분기형 파일 하나를 함께 노출하면 결과가 다시 나빠질 것으로 예상했습니다. 그러나 공유 컴포넌트와 오래된 파일을 함께 보여준 조건에서도 모든 실행이 공유 컴포넌트를 재사용했고, 결과 길이도 거의 같았습니다. 이 실험에서는 저장소의 가장 나쁜 코드 하나가 모델을 자동으로 끌어내리는 것이 아니라, 모델이 앞에서 더 많이 접하는 지배적인 패턴을 따르는 결과가 나왔습니다. 따라서 모든 오래된 예시를 먼저 제거해야만 AI 보조 개발을 개선할 수 있는 것은 아니며, 좋은 추상화를 저장소에 만들고 도구가 볼 수 있게 하는 것이 핵심이라는 결론을 제시합니다.
측정 도구 자체의 오류와 최종 제안
실험에는 측정 코드의 오류도 있었습니다. 처음에는 실제 컴포넌트 소스를 볼 수 있었던 조건에서도 모든 호출이 존재하지 않는 prop을 사용했다고 보고했습니다. 그러나 실제 prop 이름을 추출해야 하는 정규식(regex)이 아무것도 매칭하지 못해, 실제 prop 목록이 빈 집합으로 처리된 것이 원인이었습니다. 모델이 타입 정의를 눈앞에 두고 매번 아홉 개의 가짜 prop을 만든다는 결과가 타당하지 않다고 판단해 파서를 확인했고, 목록을 올바르게 채운 뒤 해당 조건의 실제 가짜 prop 비율은 0%로 수정했습니다. 작성자는 극단적이면서 자신이 기대하던 결론과 잘 맞는 수치가 나오면 먼저 측정 도구를 의심해야 한다는 교훈도 함께 제시합니다.
전체 실험은 DigitalOcean의 inference API에서 수행했으며, 네 모델과 조건별 8회 실행을 합쳐 총 160개의 completion을 생성했습니다. 결과를 바탕으로 제시한 실무적 순서는 세 가지입니다. 첫째, 오래된 패턴만 저장소에 남아 있다면 그것이 모델에게 사실상 명세가 되므로 제거해야 합니다. 둘째, 도구가 읽을 수 있는 위치에 새로운 규칙을 명시해야 하며, 이 실험에서는 주변에 그 규칙을 위반하는 코드가 900줄 가까이 있어도 한 문단의 문서가 모든 실행을 뒤집었습니다. 셋째, 컴포넌트 이름만 노출하지 말고 실제 구현과 인터페이스까지 접근 가능하게 해야 합니다. 이름은 모델이 직접 구현하는 것을 막을 수 있지만, 소스가 없으면 존재하지 않는 API를 만들어낼 가능성이 크게 높아집니다.
원문: dev.to / 번역·요약: Trawling