이 글에 대해
Claude Code로 개발하다 보면 시키지 않은 게 딸려온다. 구현체가 하나뿐인 인터페이스, 아무도 안 바꾸는 config, 나중에 쓸 거라며 미리 깔아둔 스캐폴딩. 매번 지우기도 귀찮아서 아예 안 만들게 할 방법이 없나 찾다가 ponytail이라는 플러그인을 봤다.
ponytail 플러그인 로고. 안경을 쓰고 머리를 뒤로 묶은 얼굴을 선으로 그린 그림이며, 옆에 저장소가 첫 화면에 걸어둔 문구와 룰셋이 정의한 게으름의 범위가 적혀 있다
이름값 하는 로고를 달아놓고 코드 라인이 54% 준다고 한다. 벤치마크도 저장소에 올려놨다.
ponytail 저장소의 공식 벤치마크. 12개 태스크에서 아무 스킬도 쓰지 않은 baseline 대비 코드 라인 46퍼센트, 토큰 78퍼센트, 비용 80퍼센트, 시간 73퍼센트를 기록했고 별도 6개 태스크의 안전성 검사에서는 100퍼센트를 유지했다는 막대 차트
읽어보니 조건은 숨기지 않았다. 12개 태스크를 새로 만들면서 잰 값이고 네 번 돌렸다고 적혀 있다. 그러니까 빈 파일에 코드를 쓸 때 이야기다. 이미 다 쓰여 있고 리뷰까지 받은 코드는 어떻게 되는지가 궁금했다.
§1설치하기 전에 직접 해봤다
설치부터 하려다 말았다. 스킬이라는 게 결국 모델한테 읽히는 문서인데, 그 문서를 내가 읽고 직접 하면 비슷하게 나오는 거 아닌가 싶었다.
그래서 저장소만 클론했다. 감사 스킬 문서를 열어보니 규칙이 짧다. 태그 다섯 개(delete, stdlib, native, yagni, shrink)로 분류하고, 한 줄에 하나씩 쓰고, 마지막에 몇 줄 줄일 수 있는지 적으라고 되어 있다. 정확성 버그나 보안은 자기 담당이 아니니 다른 리뷰로 보내라는 말도 있었다.
본체 룰셋도 같이 읽었다. 사다리 일곱 칸을 위에서부터 밟다가 걸리는 데서 멈추라고 한다. 만들 필요가 있나, 코드베이스에 이미 있나, 표준 라이브러리가 하나, 플랫폼이 하나, 깔려 있는 의존성이 하나, 한 줄로 되나. 다 아니면 그때 최소한만 쓴다.
게으름의 범위를 못박아둔 부분이 마음에 들었다. 게으르다는 게 효율적이라는 뜻이지 대충 한다는 뜻이 아니라면서, 절대 줄이면 안 되는 걸 따로 적어놨다. 문제를 끝까지 읽는 것, 신뢰 경계 입력 검증, 데이터 날아가는 걸 막는 에러 처리, 보안, 접근성. "이해하지 못한 채 만든 작은 diff는 효율로 위장한 두 번째 버그"라는 문장이 그대로 들어 있다.
이 기준을 들고 최근에 만든 모듈을 봤다. 33파일 3,924줄인데, 적대적 리뷰를 두 라운드 넘게 받고 배포까지 끝난 상태다. 큰 파일 위주로 넷을 열어 읽었고 네 건을 찾았다. 30줄 정도 줄일 수 있겠다는 결론이었다.
§2설치하고 돌렸더니
그다음에 플러그인을 깔았다. hook 세 개가 붙는다. 세션 시작할 때, 프롬프트 보낼 때, subagent 뜰 때. 하는 일은 같다. 아까 읽은 룰셋을 매 응답 컨텍스트에 밀어 넣는다. 강도는 네 단계고, 감사 커맨드는 지적만 하고 코드를 고치지는 않는다.
같은 모듈에 감사를 돌렸다. 13건, 290줄이 나왔다.
감사가 찾아낸 13건을 태그별로 나눈 가로 막대. yagni 5건, shrink 4건, delete 2건, stdlib 1건, native 1건이며 각 태그의 뜻과 대표 사례를 함께 적었다
종류를 보면 이 도구가 뭘 하는지 짐작이 간다. 그냥 지우라는 건 두 건뿐이고 나머지는 이미 있는 걸로 갈아끼우거나 합치라는 얘기다.
제일 걸렸던 건 호출처가 한 곳도 없는 public 메서드였다. javadoc에는 어디에 쓰인다고 적혀 있는데 부르는 데가 없다. 검증 규칙이 내림에서 올림으로 바뀌면서 새 메서드가 옆에 생겼고, 옛날 것을 안 지운 거였다. 주석은 그대로 남아서 아직 쓰이는 것처럼 보였다.
나머지도 비슷하다. 표준 라이브러리 enum 변환을 손으로 다시 만들어놓고 그걸 또 try/catch로 감싼 게 네 벌 있었다. 컨트롤러 넷에 로거가 붙어 있는데 실제로 로그를 찍는 건 다른 클래스 한 곳뿐이었다.
어려운 코드는 하나도 없다. 위험한 것도 없다. 그냥 읽다 보면 눈이 미끄러지는 것들이다.
§330줄과 290줄
같은 규칙을 썼는데 열 배 가까이 차이가 났다.
플러그인 감사 실행을 100퍼센트로 둔 상대 비교. 손으로 적용한 실행은 읽은 소스 파일 4개, 찾아낸 발견 4건, 절감 가능 라인 30줄로 각각 12퍼센트, 31퍼센트, 10퍼센트에 머물렀다
이유는 단순했다. 나는 큰 파일 넷만 골라 읽었고 도구는 33개를 다 읽었다. 규칙을 아는 것과 그 규칙을 전부에 적용하는 건 다른 일이었다.
그런데 놓친 걸 하나씩 세어보다가 다른 게 걸렸다.
발견 16건이 손으로만 3건, 양쪽 공통 1건, 도구만 12건으로 갈리고, 그 12건이 다시 열어보지 않은 파일 9건과 열고도 놓친 3건으로 나뉘는 스택 막대
열두 건 중 아홉은 안 열어본 파일에 있었다. 이건 시간 문제라 치자. 남은 셋이 문제였다. 내가 이미 읽은 파일 안에 있었다.
그중 하나는 지금 봐도 아쉽다. 상태 게이트 메서드 셋이 이름도 모양도 거의 같았는데, 허용하는 상태 집합이 서로 달라서 합칠 수 없다고 봤다. 도구는 허용 집합을 가변인자로 받으면 된다고 했다. 조건은 그대로 두고 호출부에서 넘기면 그만이다.
허용 상태 집합이 서로 다른 게이트 셋을 두고, 사람은 조건을 메서드에 붙박인 성질로 보아 셋을 유지했고 도구는 조건을 넘길 수 있는 값으로 보아 하나로 합친 두 경로의 대비
닮은 상태 게이트 셋을 합칠 것인가
둘 다 안전한 답이다. "합친다"를 어디까지로 봤느냐가 달랐을 뿐이다. 나는 조건이 다르면 못 합친다고 생각했고 거기서 멈췄다.
§4안 건드린 데가 있었다
감사 결과 맨 끝에 한 줄이 붙어 있었다. 검증기 789줄은 손대지 않았다는 말이었다.
이 모듈에서 제일 긴 파일이다. 줄 수만 보면 제일 먼저 손댈 만한 곳이다. 그런데 이유까지 적어놨다. 여러 필드에 걸치거나 스키마를 넘나드는 규칙이 전부 실제로 호출되는 신뢰 경계 방어라 자기 범위가 아니라는 것이다.
검증을 한 파일에 몰아넣은 건 일부러 그렇게 한 거였다. 등록이랑 전체 수정, 부분 변경이 각자 다른 방어를 갖게 되는 쪽이 파일 길어지는 것보다 무섭다고 봤다. 감사 스킬 문서에도 정확성이랑 보안, 성능은 범위 밖이니 다른 리뷰로 보내라고 적혀 있긴 했다. 문서에 써 있는 것과 실제로 그렇게 도는 건 또 다른 문제인데, 이번엔 지켰다.
죽은 메서드 얘기를 하나 더 하자면, 그건 다른 사람이랑 같이 손댄 데서 나왔다. 언제 왜 생겼는지 나는 모른다. 이런 건 코드가 어려워서 남는 게 아니다. 서로 자기 담당이라고 생각 안 하는 데서 남는다.
§5그래서 쓰기로 했다
3,924줄에서 290줄이면 7%다. 벤치마크의 54%랑은 거리가 멀다. 당연하다. 저쪽은 빈 파일에 새로 쓸 때 잰 거고 이쪽은 이미 여러 사람 손을 탄 코드다.
다만 감사를 나중에만 돌리면 늦는다. 290줄은 이미 다 쓰이고 리뷰받고 배포된 코드다. 쓰기 전에 걸렀으면 애초에 없었을 것들이다.
그래서 만들 때부터 켜두되 강도는 제일 낮은 걸로 잡았다. 최저 단계는 시킨 대로 만들고 더 간단한 방법이 있으면 한 줄 알려주는 정도라 판단은 내가 한다. 검증기 789줄 같은 건 도구가 마음대로 뒤집게 두고 싶지 않았다.
과잉설계 억제를 언제 거는가
솔직히 지울 게 몇 줄 나오는지는 별로 안 중요했다. 시간 들여 다 읽으면 언젠가 나올 것들이다. 계속 쓰기로 한 건 검증기를 안 건드려서다. 손대지 않았고 왜 안 했는지까지 써놨다. 무작정 줄이라고만 했으면 한 번 돌려보고 껐을 것 같다.