Skip to main content

bulk-code-transform-costs-more-than-it-saves

리팩터에서 N곳을 자동 변환(ast.unparse · 넓은 정규식 · sed)하면 대상 밖까지 바꾸거나 값을 틀리게 계산한다 — 그리고 테스트는 통과하므로 diff 를 한 줄씩 읽어야만 드러난다. 손익분기점은 생각보다 훨씬 높다(N≈20+). 트리거 - "N곳을 한 번에 고치자", ast.unparse/NodeTransformer 로 파일 재작성, `re.sub` 로 리터럴 변환, sed -i 일괄 치환, 대량 테스트 픽스처 갱신.

Aller à l'installation

Informations de source

Dépôt
blas1n/claude-skills
Dernière activité de la source
31 août 2026 à 02:12
Langue détectée de SKILL.md
coréen
Étoiles
2
Forks
0

Options d'installation

Le prompt qui vérifie d'abord la source est sélectionné par défaut. Vous pouvez passer à une commande directe ou télécharger une copie locale.

Vérifiez les fichiers source

Lisez SKILL.md et les fichiers associés affichés par SkillsMP avant de décider de l'installer.

Affichage de SKILL.md

SKILL.md
Instructions source · Aperçu en lecture seule
name
bulk-code-transform-costs-more-than-it-saves
description
리팩터에서 N곳을 자동 변환(ast.unparse · 넓은 정규식 · sed)하면 대상 밖까지 바꾸거나 값을 틀리게 계산한다 — 그리고 테스트는 통과하므로 diff 를 한 줄씩 읽어야만 드러난다. 손익분기점은 생각보다 훨씬 높다(N≈20+). 트리거 - "N곳을 한 번에 고치자", ast.unparse/NodeTransformer 로 파일 재작성, `re.sub` 로 리터럴 변환, sed -i 일괄 치환, 대량 테스트 픽스처 갱신.
# 일괄 코드 변환은 아끼는 것보다 많이 부순다 ## Problem 리팩터가 20곳쯤을 같은 모양으로 바꿔야 한다. 손으로 하기 지겨우니 자동화한다. 2026-08-31 한 PR 에서 **세 번 연속** 당했다. ### ① `ast.unparse` 는 편집 도구가 아니라 **전체 재작성** 도구다 ```python tree = ast.parse(src); Transformer().visit(tree) path.write_text(ast.unparse(tree)) # ❌ ``` * **증상**: diff 가 `570 deletions / 147 insertions`. 바꾼 건 11곳인데. * **원인**: AST 에 **주석이 없다.** unparse 는 주석·빈 줄·서식·따옴표 스타일을 전부 잃는다. docstring 은 AST 노드라 살아남으므로 **손실이 눈에 잘 안 띈다.** * 이 저장소처럼 주석이 *왜 이렇게 했는지*를 담는 곳에서는 코드보다 주석이 더 비싸다. ### ② 넓은 정규식은 대상 밖을 문다 — 그리고 **값을 틀리게 계산한다** ```python re.sub(r'\{((?:[\w-]+\s*:\s*(?:true|false)\s*,?\s*)+)\}', flat, src) # ❌ ``` * `prefs({ quiet_hours_enabled: true })` → `prefs(true)` — 매트릭스가 아닌데 잡혔다 * `expect.objectContaining({ telegram: true })` → `objectContaining(true)` — 같은 이유 * 그리고 `(...)+` 는 **마지막 반복만** 그룹에 남긴다. `{a:true, b:true, c:false}` 에서 본문을 `c:false` 로만 보고 **false 를 만들었다.** 문법은 맞고 값만 틀렸다. **타입체커도 테스트도 이걸 다 잡지 못한다** — 문법이 유효하고 타입이 맞으면 "의도와 다른 값"은 통과한다. 잡은 것은 **diff 를 한 줄씩 읽은 것**뿐이었다. ## Solution **손익분기점을 다시 계산하라.** 자동 변환의 비용은 *쓰는 시간*이 아니라 **diff 전체를 검수하는 시간 + 오적용 복구 시간**이다. | 대상 수 | 권장 | |---|---| | ~20곳 이하 | **손으로.** Edit 도구로 하나씩. 대개 자동화 스크립트 쓰는 시간보다 빠르다 | | 그 이상 | 자동화하되 **아래 규칙 전부** | 자동화한다면: 1. **`ast.unparse` 금지.** 대신 AST 로 **위치만** 찾고 텍스트를 그 줄에서 바꿔라. ```python for kw in node.keywords: if kw.arg == "matrix": lines_to_touch.add(kw.value.lineno) # ✅ 위치만 ``` 2. **치환마다 `count == 1` 을 단언하라.** 문자열이 여러 곳에 있으면 실패하게. ```python assert s.count(old) == 1, old[:60] ``` 3. **`git diff` 를 눈으로 전부 읽어라.** 줄 수가 예상과 다르면 멈춰라 — 이게 ①을 잡은 유일한 신호였다. 4. **되돌릴 수 있게 하라**: 커밋 안 된 상태에서만 돌리고, 이상하면 `git checkout --`. ## Key Insights * **주석은 AST 에 없다.** 코드를 보존하는 도구가 *이유*를 보존한다는 보장은 없다. * **문법 유효 + 타입 통과 ≠ 의미 보존.** 정규식이 만든 `false` 는 완벽히 유효했다. * **diff 크기가 첫 번째 센서다.** "11곳 바꿨는데 570줄이 지워졌다" 는 읽기 전에 이미 이상하다. 변경 규모를 **예상하고** 나서 diff 를 봐라. * 이건 [[deletion-pr-needs-an-absence-guard-and-a-control]] 의 자매 문제다 — 거기서는 *마커 사이*를 자르다 사이의 것을 가져갔고, 여기서는 *패턴 주변*을 바꾸다 주변의 것을 가져갔다. **범위를 텍스트로 정하면 범위는 우연에 맡겨진다.** ## Red Flags - diff 의 삭제 줄 수가 변경 대상 수와 자릿수가 다르다. - `ast.unparse` / `NodeTransformer.visit` 후 `write_text`. - 정규식에 `(...)+` 나 `.*` 가 있고 그 그룹을 값 계산에 쓴다. - "테스트 통과하니까 됐다" — 값이 틀려도 통과할 수 있는 변환이었나?
Voir sur GitHub