당신의 에이전트는 Python을 작성합니다. Ruby 규칙은 이를 3분의 1로 줄여줍니다.
요약
코딩 에이전트에게 일회성 스크립트 작성 시 Python 대신 Ruby를 사용하도록 지시했을 때의 효율성을 실험한 글입니다. Ruby의 간결한 문법이 에이전트의 코드 작성량과 리뷰 용이성에 미치는 영향을 분석합니다.
핵심 포인트
- 코딩 에이전트의 지침 파일에 Ruby 사용 규칙을 추가하여 실험 진행
- Ruby 사용 시 코드의 간결함과 리뷰어로서의 역할 수행에 유리함
- 다양한 스크립트 작업(데이터 가공, 파일 변경 등)을 통한 성능 측정
- 토큰 절약 및 코드 구현의 효율성 측면에서 Ruby의 가치 탐색
Lucian Ghinda는 포스트를 게시하며 코딩 에이전트에게 일회성 스크립트를 작성할 때는 Ruby를 사용하라고 지시해야 한다고 주장했습니다. 다음은 그가 에이전트의 지침 파일(instruction file)에 붙여넣으라고 말한 전체 블록입니다:
## Scripts
Write throwaway and utility scripts (data munging, one-off migrations,
...
나는 그날 저녁 바로 나의 전역 CLAUDE.md에 이를 붙여넣었습니다. 그의 논거는 리뷰(review)에 관한 것입니다. 그는 매일 Ruby를 읽기 때문에, 에이전트가 Ruby를 작성할 때 단순히 diff(차이점)를 보며 고개를 끄덕이는 대신 리뷰어로서 역할을 유지할 수 있다는 것입니다. 그는 세 가지 이유를 제시했는데, 그중 비용(cost)에 관한 이유는 단 하나도 없었습니다. 그래서 나는 그가 남겨두지 않은 숫자를 찾아 나섰습니다.
"이 규칙이 토큰(tokens)을 절약하는가"라는 질문은 에이전트가 평소에 작성하는 방식과 비교했을 때만 의미가 있으므로, 내가 가장 먼저 해야 했던 일은 전역 설정에서 해당 블록을 다시 제거하는 것이었습니다. 이미 그 규칙을 따르고 있는 에이전트는 그 규칙이 없을 때 무엇을 할지 당신에게 말해줄 수 없기 때문입니다.
설정이 없는 상태에서 에이전트 측정하기
아래의 모든 실험은 claude --safe-mode를 통해 Claude Opus 5에서 실행되며, 이는 CLAUDE.md, 기술(skills), 플러그인(plugins), 훅(hooks)을 전혀 로드하지 않습니다. 두 가지 실험군(arms)은 의도적으로 해당 플래그를 건너뛰며, 결과에 내 설정이 어떤 영향을 미치는지 측정하기 위해 나타나는 곳에 이름을 붙여두었습니다. 나의 전역 설정만으로도 에이전트에게 작업을 수행하는 데 필요한 최소한의 코드를 작성하라는 지침과, node보다 bun을 선호하라는 또 다른 지침이 포함되어 있다는 점을 알아둘 가치가 있습니다.
규칙에서 언급한 각 스크립트 유형에 대해 네 가지 작업을 수행합니다: 로그 데이터 가공(munge a log), 설정 파일 트리 전체에서 키 이름 변경(rename a key across a tree of config files), 스크린샷 더미의 번호 재부여(renumber a pile of screenshots), 하나의 CSV를 다른 CSV로 변환(turn one CSV into another). 나는 나만의 작업을 발명하는 대신 그의 목록에서 가져왔으므로, Ruby에 유리한 지형을 몰래 선택할 수 없었습니다.
수치가 나오기 전에 설정에 대해 알아두어야 할 세 가지 사항이 있습니다. 세 가지 모두 내가 내린 결정이며, 세 가지 모두 논쟁의 여지가 있습니다:
- No arm은 규칙 자체를 붙여넣지 않습니다. 각 arm은 언어와 표준 라이브러리 (standard-library) 제약 조건을 직접 명시하며, 이는 규칙 자체의 텍스트가 아니라 규칙이 생성하는 결과물입니다.
- bash arm은
jq를 사용할 수 없습니다. 규칙에서 표준 라이브러리만을 사용하라고 명시했으며,jq는 별도로 설치해야 하는 도구이기 때문입니다. - Ruby arm만이 자신의 간결한 관용구 (idioms) 중 하나를 사용할 수 있다는 안내를 받았습니다. 이는 Ruby에 유리하게 작용하는 요소로, Ruby의 1660 바이트 중 약 20 바이트 정도의 가치가 있습니다.
동등함이 증명되기 전까지는 아무것도 계산되지 않습니다. 모든 작업의 구현체 (implementation)는 하나의 참조 모델 (reference)을 대상으로 실행되며, 표준 출력 (stdout)에 동일한 바이트를 생성하고 생성된 모든 파일에 대해 동일한 다이제스트 (digest)를 만들어내야 합니다. 서로 다른 일을 수행하는 프로그램의 길이를 비교하는 것은 의미가 없으므로, 이 검증 단계가 모든 것을 결정합니다:
ruby src/run.rb text
t1_logs 액세스 로그의 경로별 요청 및 에러 횟수 계산 53 impls identical
t2_config .env 파일 트리 전체에서 하나의 키 이름 변경, 미끼 키는 유지 53 impls identical
t3_rename screenshot-N.png를 zero-padded shot-NNNN.png로 번호 재부여 53 impls identical
53 impls identical
...
기준점: 에이전트는 Python을 작성합니다
동일한 네 가지 사양 (specs)으로 열 번의 실행을 진행했습니다. 어디에도 언어 이름은 명시되지 않았습니다. 다섯 번은 클린 룸 (clean room) 환경에서, 나머지 다섯 번은 규칙이 나온 후 나의 실제 설정 환경에서 진행했습니다.
프롬프트 (prompt)가 완전히 맹목적이지는 않다는 점을 밝혀둡니다. 프롬프트는 에이전트가 왜 해당 선택을 했는지에 대해 한 문장으로 설명할 것을 요구하며, 이는 에이전트에게 자신의 선택이 관찰되고 있음을 알려줍니다. 완전히 맹목적인 버전은 내가 다음에 수행할 실행 방식입니다.
열 번의 실행 모두 Python을 작성했습니다.
단 하나도 Ruby, bash, 또는 JavaScript를 선택하지 않았습니다. 그들이 제시한 이유는 표준 라이브러리에 관한 것이었습니다: 어디에나 사전 설치되어 있으며, 의존성 (dependencies) 없이도 줄 바꿈 기호 보존과 고정 소수점 포맷팅 (fixed-decimal formatting)을 처리할 수 있다는 점입니다. 따라서 이 규칙은 Ruby와 어떤 추상적인 분야 사이에서 선택하는 것이 아닙니다. 이 규칙은 매번 Python을 밀어내고 있습니다.
그중 5번의 실행에서는 제 설정(setup)이 로드된 상태였음에도 여전히 Python을 선택했습니다. 이는 설정이 실제로 반영된 상태였을 때만 보고할 가치가 있는 결과입니다. 실제로 반영되었습니다. 직접적으로 물었을 때, 해당 상태의 세션은 제 CLAUDE.md를 다시 인용하며, 동일한 설정에서 바이트 수(byte counts)는 아래와 같이 최대 59%까지 차이가 납니다. 읽혔던 것입니다. 규칙이 적용되고 나면, 언어에 대해 더 이상 할 말이 남지 않게 됩니다.
이는 비용 문제를 구체화하며, 그 답은 압도적입니다:
| 바이트(bytes), 4개의 스크립트, 중립적 프롬프트 | Ruby | Python | bash | JS |
|---|---|---|---|---|
| 소스(source) | 1660 | 3644 | 2333 | 3231 |
| Ruby 대비 | 0% | +120% | +41% | +95% |
Ruby가 네 가지 중 가장 짧으며, 통제된 환경(clean room)에서 에이전트가 생성한 Python 코드는 동일한 검증된 동작을 수행하면서도 소스 코드의 두 배가 넘는 양을 차지합니다.
이 수치는 조건에 따라 달라지며, 제 실제 환경은 훨씬 덜 인상적입니다. 제 실제 설정(setup)을 로드하고 다시 실행하면, Ruby 대비 Python의 우위는 120%에서 18%로 떨어집니다. 이 규칙을 통해 얻는 이득은 에이전트가 코드를 작성하는 방식에 이미 영향을 미치고 있는 다른 요소가 무엇인지에 따라 달라집니다:
| 규칙의 절감 효과 | 요청되지 않은 기본값 (unprompted default) | Ruby 지정 (told Ruby) | 절감률 (cut) |
|---|---|---|---|
| 통제된 환경 (clean room) | 3595 | 1660 | 54% |
| 실제 환경 (my real environment) | 1932 | 1375 | 29% |
두 행 모두 동일한 방식으로 측정된, 1대 5의 실행 결과입니다. 통제된 환경(clean-room) 행은 규칙의 효과만을 분리하여 보여줍니다. 아래 행은 에이전트가 설정되어 있는 경우 당신이 실제로 처한 상황이며, 제가 계획을 세울 때 기준으로 삼을 수치입니다.
이 규칙은 주석(comments)과 빈 줄(blank lines)은 줄이지만, 임포트(imports)는 유지합니다. 임포트를 해야 한다는 것은 언어의 실제적인 비용이기 때문입니다. 또한 Python의 독스트링(docstrings)도 유지하는데, 이는 # 주석을 사용했다면 비용이 들지 않았을 Python의 단점입니다. 이는 통제된 환경 내 Python 총량의 8%를 차지하며, 이를 제거하면 Python은 Ruby보다 120% 더 큰 것이 아니라 101% 더 큰 상태가 됩니다.
또한 그 격차가 전부 Ruby 때문인 것도 아닙니다. 그 120%의 대부분은 언어 자체의 문제가 아니라, 에이전트가 그 언어를 감싸는 방식 때문입니다. 즉, 독스트링 (docstring), main(), if __name__ 가드, 줄 바꿈 (line-ending) 관리 작업 등입니다. 이러한 습관을 지양하면 Python의 우위는 18%로 떨어지며, 이는 제가 두 언어를 모두 직접 손으로 작성할 때 얻는 36%에 근접한 수치입니다.
차이점이 어떻게 나타나는가
중립적인 프롬프트 (neutral prompt)가 생성한 설정 마이그레이션 (config migration)이라는 하나의 작업을 살펴보겠습니다. 먼저 Ruby입니다:
root = ARGV[0]
Dir.glob(File.join(root, "conf", "*.env")).sort.each do |path|
...
이것이 프로그램의 전부입니다. 한 번 읽어보면 그것이 올바르다는 것을 알 수 있습니다.
동일한 작업에 대한 Python 버전은 38줄에 달합니다. 그중 핵심인 작업 루프 부분은 다음과 같습니다:
renamed = 0
lines = text.splitlines(keepends=True)
for i, line in enumerate(lines):
...
text는 파일의 내용이며, OLD_KEY와 NEW_KEY는 파일 상단에 정의된 두 개의 키 이름입니다. 이는 Ruby와 동일한 루프이지만, 수동적인 줄 바꿈 관리 작업이 추가되었고, 독스트링 (docstring)이 포함된 main() 함수, 4개의 임포트 (import), 그리고 if __name__ 가드로 감싸져 있습니다. 이 중 어느 것도 나쁜 Python 코드는 아닙니다. 단지 양이 더 많을 뿐이며, 통계 수치도 이를 뒷받침합니다:
| neutral prompt | Ruby | Python | bash | JS |
|---|---|---|---|---|
| lines | 56 | 108 | 101 | 89 |
| ... |
Ruby는 읽기 노력 (reading effort)을 추적하는 두 지표에서 승리했고, 가장 긴 줄 (longest-line) 항목에서는 bash에게 패배했습니다. 저는 실행 전 이 네 가지를 선택했으며, 결과가 어떻게 나오든 네 가지 모두를 보고합니다. 왜냐하면 가독성 (readability)을 측정하는 공인된 방법이 없으며, 사후에 선택된 지표는 저자(author)를 측정하게 되기 때문입니다.
bash는 요구할 때만 저렴해 보인다
아무런 지시도 하지 않았을 때, 에이전트는 매우 신중한 bash 코드를 작성합니다: shopt -s nullglob, 명시적인 정렬 (sort), 입력당 하나의 임시 파일 (temp file), 그리고 원본이 줄 바꿈으로 끝났는지 확인하여 다시 복구하는 작업까지 포함합니다. 이는 2333 바이트로, Ruby보다 41% 더 깁니다.
그래서 저는 한 가지를 변경하여 중립적인 프롬프트를 두 번째로 실행했습니다: --safe-mode를 꺼서, 깨끗한 환경 (clean room) 대신 저의 실제 설정이 로드되도록 했습니다. 프롬프트, 사양, 피스처 (fixture)는 모두 동일합니다.
| bytes, neutral prompt | Ruby | Python | bash | JS |
|---|---|---|---|---|
| clean room | 1660 | 3644 | 2333 | 3231 |
| ... | ||||
| 모든 언어의 크기는 줄어들며, 승자는 바뀝니다. 깨끗한 환경 (clean room)에서는 Ruby가 가장 짧습니다. 저의 설정을 로드하면 bash가 31% 더 짧아지는데, 이는 전적으로 저의 환경에 의해 생성된, |
Dir[ARGV[0]+"/conf/*.env"].sort.each{n=0;File.write(it,File.read(it).gsub(/^CACHE_TTL=/){n+=1;"TTL_SECONDS="});puts [File.basename(it),n]*"\t"}
단 한 줄, 409바이트 대비 144바이트이며, 바이트 단위(byte-for-byte) 검사도 동일하게 통과합니다.
그 안에는 해석이 필요한 세 가지 요소가 있습니다:
it는 Ruby의 암시적 블록 매개변수(implicit block parameter)로, 각it는 현재 파일명을 나타냅니다. 이는 Ruby 3.4 이상 버전이 필요합니다. 그보다 낮은 버전에서는 이 줄은 느린 읽기가 아니라 에러를 발생시킵니다.gsub블록은 일치 항목을 카운트하는 동시에 대체 문자열을 반환하므로, 새로운 텍스트를 생성하는 부수 효과(side effect)로서n이 증가합니다.[File.basename(it),n]*"\t"는 배열에*연산자를 사용하여 탭()으로 결합(join)한 것입니다.
제 자신의 언어로도 두 번은 다시 확인해야 했습니다.
줄 수(line counts)는 급감하지만, 줄 자체는 더 나빠집니다. Ruby의 가장 긴 줄은 80자에서 151자로, JavaScript는 73자에서 199자로 늘어나며, 문장 부호의 비중은 공백을 제외한 문자 중 24%에서 37%로 상승합니다. 당신은 컴팩트한(compact) 코드를 사는 것이 아닙니다. 당신은 밀집된(dense) 코드를 사는 것이며, 그 규칙이 채택되었던 바로 그 대가를 치르고 있는 것입니다.
그 후, 저는 152개의 모든 구현체를 이름에 공백이 포함된 디렉토리를 대상으로 테스트했습니다. 대조군으로서 6단계의 단계별 구현과 수동 작성 세트를 포함했습니다. 한 단계가 깨졌는데, 그것은 바로 비용이 많이 드는 단계였습니다. 36단어 지침 하에 작성된 모든 bash 스크립트 4개 모두가 실패한 반면, 더 짧은 단계의 모든 bash 스크립트는 살아남았습니다.
awk '{e[$7]+=$9>=400;t[$7]++}END{for(p in t)print p"\t"e[p]"\t"t[p]}' $1/logs/access.log|LC_ALL=C sort -t$' ' -k2,2nr -k1,1
저 $1에 따옴표가 없습니다. 4개 중 3개는 어쨌든 종료 상태(exit status) 0을 반환하는데, 이는 sort로 파이프(pipe)된 실패한 awk가 sort의 종료 상태를 보고하기 때문입니다. 그 3개 중 2개는 아무것도 출력하지 않고, 세 번째는 -1을 출력합니다. 에러는 stderr(표준 에러)에 도달하므로 터미널을 지켜보는 사람은 이를 볼 수 있습니다. 하지만 종료 상태만 확인하는 시스템은 이를 감지하지 못합니다.
결국 지침의 마지막 34단어는 길이의 5%포인트를 얻는 대신, 그들이 건드린 모든 bash 스크립트를 망가뜨렸습니다. "Golf it(코드를 줄여라)."는 그러지 않았습니다.
한계 (The limits)
- 길이는 바이트 (bytes) 기준입니다. 이 세트들에 대해 토크나이저 (tokenizer)를 실행하지 않았으므로, 여기에 표시된 모든 백분율은 바이트 백분율이며, 이것이 토큰 비용 (token bill)을 얼마나 밀접하게 추적하는지는 제가 테스트하지 않은 가정입니다.
- 언어당, 단계(rung)당 에이전트 실행을 한 번씩 수행했습니다. 방향성은 4개의 언어와 6개의 단계에 걸쳐 일관되게 나타나지만, 정확한 백분율은 그렇지 않습니다.
- 해당 기사의 4가지 카테고리에서 가져온 4가지 작업(tasks)을 사용했습니다. 따라서 제가 유리한 지점을 선택할 수는 없었지만, 여전히 4가지 작업입니다.
- 10번의 실행은 이곳의 기본값이 Python이라는 것을 말하기에는 충분합니다. 하지만 얼마나 자주 발생하는지에 대해 수치를 매기기에는 충분하지 않습니다.
- 클린 룸 (clean room) 방식은 설정 (configuration)을 제거할 뿐, 모델 자체의 사전 지식 (priors)을 제거하지는 않습니다. "설정 없음 (No config)"은 제가 도달할 수 있는 정직한 최저선이지, 중립적인 우주가 아니며, 다른 모델은 완전히 다른 곳을 기본값으로 삼을 수도 있습니다.
- spaced-path 실패는 한 대의 기계에서 발생한 하나의 어색한 경로일 뿐입니다. 이는 실패 모드 (failure mode)를 보여주는 것이지, 전이 가능한 비율을 보여주는 것이 아닙니다.
제가 실제로 당신에게 해주고 싶은 말
규칙을 유지하세요. 이 규칙은 비용 측면에서만으로도 승리합니다. 이는 저자가 내세웠던 논거(Python에 반대하는 것)와는 다르지만 말입니다. 당신이 아무런 지시를 하지 않았을 때 에이전트가 선택하는 언어인 Python에 비해, 이 규칙은 설정된 환경에서는 소스 코드를 약 3분의 1로, 아무 설정이 없는 환경에서는 절반으로 줄여줍니다.
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기