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 일괄 치환, 대량 테스트 픽스처 갱신.

跳到安装

来源信息

仓库
blas1n/claude-skills
最近来源活动
2026年8月31日 02:12
检测到的 SKILL.md 语言
韩语
星标
2
分支
0

安装方式

默认使用会先检查来源的 Prompt;你也可以切换为直接命令,或下载本地副本。

检查来源文件

决定是否安装前,请先阅读 SKILL.md,以及 SkillsMP 当前展示的配套文件。

正在显示 SKILL.md

SKILL.md
来源说明 · 只读预览
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`. - 정규식에 `(...)+` 나 `.*` 가 있고 그 그룹을 값 계산에 쓴다. - "테스트 통과하니까 됐다" — 값이 틀려도 통과할 수 있는 변환이었나?
在 GitHub 查看