사실은 기계가 뽑고 규범은 사람이 정한다 — LLM이 프로젝트를 모를 때 무엇을 채웠나

2편에서 파이프라인 자체의 결함을 어떻게 찾았는지 썼다. 변경 요청 하나를 분석부터 테스트까지 여섯 단계로 흘려보내는 도구다. 이 글은 그 파이프라인을 실제 코드베이스에 돌렸을 때 무엇이 달라졌는지를 적었다.

돌아가는데 이 프로젝트 것이 아니다

구현을 맡기면 세 가지가 반복됐다.

첫째, 프로젝트에 안 맞는 방식으로 짰다. 네이밍이 기존과 달랐고 레이어를 어디에 둘지가 제각각이었고 에러 처리 방식이 파일마다 갈렸고, 공용 컴포넌트를 가져다 쓰는 방식이 기존 관행과 어긋났다.

둘째, 이미 있는 함수를 다시 만들었다. 이건 특히 고약했는데, 만드는 순간에는 아무도 모르기 때문이다. 만드는 쪽도 시키는 쪽도 그 자리에선 모른다. 리뷰에서 걸리거나 한참 뒤에야 드러났고, 리뷰가 잡아도 이미 짜인 다음이다. 못 잡은 것은 그대로 남았다.

셋째, 시키지 않은 파일까지 건드렸다. 요청한 것은 한 기능인데 손댄 범위가 그보다 넓었다.

셋의 공통점은 「틀렸다」가 아니라는 것이다. 코드는 돌아간다. 다만 이 프로젝트의 것이 아니다. 컴파일도 통과하고 테스트도 통과하는데 리뷰에서 매번 되돌아온다.

「모른다」가 한 종류가 아니었다

한동안 이걸 하나의 문제로 봤다. LLM이 컨텍스트를 모르니 컨텍스트를 주면 된다는 식이었다. 그런데 셋을 나란히 놓으니 모르는 대상이 서로 달랐다.

안 맞는 방식으로 짬   →  이 프로젝트가 어떻게 쓰는지 모름   =  규범을 모름
이미 있는 걸 또 만듦  →  무엇이 어디 있는지 모름            =  사실을 모름
시키지 않은 곳을 건듦 →  이 작업이 어디까지인지 모름        =  범위를 모름

가르고 나니 채우는 방법이 갈렸다. 사실은 코드에 답이 있고 규범은 코드에 답이 없다. 이 차이가 그다음 설계를 전부 정했다.

둘을 파이프라인 단계로 넣지는 않았다. 단계로 만들면 작업할 때마다 돌아야 하는데, 프로젝트 현황과 규약은 작업마다 새로 만드는 것이 아니라 한 번 세워 두고 이후 증분으로 고쳐 가는 물건이기 때문이다. 그래서 여섯 단계 바깥에 두고, 각 단계가 필요할 때 읽어 가는 영속 지식으로 삼았다.

사실은 기계가 뽑는다

현황을 뽑는 일을 따로 떼어 뒀다. 코드베이스를 훑어 개요·구조·컴포넌트·API·데이터를 문서로 떨어뜨린다. 규율은 하나다. 코드에서 관찰된 것만 적고 확인하지 못한 것은 「미확인」으로 표시한다. 추측으로 채우면 이후 작업이 잘못된 그림 위에서 움직인다.

두 가지를 다르게 했다.

하나는 기능 단위로 묶은 것이다. 컴포넌트·API·데이터를 기술 레이어별로 흩어 놓으면, 「이 기능이 무엇을 건드리는가」를 보려고 여러 문서를 오가야 한다. 변경은 기능 단위로 일어나므로 문서도 기능별로 모았다. 함수 중복이 줄어든 것이 여기서 나왔다. 짜기 전에 그 기능에 이미 무엇이 있는지가 한 곳에 있다.

다른 하나는 갱신을 작업 흐름 안에 넣은 것이다. 현황 문서는 코드의 그림자라 코드가 바뀌면 그만큼 낡는다. 낡은 지도는 없는 지도보다 나쁠 수 있다. 없으면 찾아보지만 있으면 믿어 버리기 때문이다. 그래서 신규 개발이나 리팩토링을 이 파이프라인으로 돌릴 때마다, 관련 부분을 증분으로 다시 스캔하게 했다. 따로 해야 하는 일로 두면 아무도 하지 않는다.

세 번째 실패도 여기서 같이 줄었다. 기능별 문서가 있으면 그 작업이 닿는 범위가 이미 적혀 있다. 범위를 몰라서 넓게 헤집을 이유가 없어진다.

규범은 사람이 정한다

규약은 다르게 다뤘다. 코드에서 관찰된 관행을 후보로만 제시하고, 확정은 사람 승인을 받는다. 관찰만으로 자동 확정하지 않는다.

이유는 단순하다. 코드에 있는 것은 관행이지 규범이 아니다. 코드에는 좋은 패턴과 나쁜 패턴이 같이 있고 어느 쪽을 표준으로 삼을지는 코드가 답하지 못한다.

규약끼리 모순되지 않아야 한다는 조건도 걸었다. 전역 규칙과 주제별 세부가 같은 대상을 다르게 규정하면 어느 쪽을 따라도 다른 쪽을 어기게 된다. 그래서 새 규약이 기존과 부딪히면, 그 자리에서 멈추고 해소한다.

형식에도 조건을 걸었다. 「깔끔하게 짜라」·「적절히 처리하라」 같은 문장은 규약으로 받지 않는다. 검증할 수 없는 서술은 지시가 되지 못한다. 무엇을 어떻게 하는지가 예시와 함께 있어야 다음 단계가 그대로 적용할 수 있다.

나쁜 관행을 규약으로 받아들였다

여기서 예상과 다른 판단을 했다.

한 프로젝트의 프런트엔드에서 React 훅 사용과 상태 관리가 어그러져 있었다. 규약을 세울 차례였고, 나는 그 부분을 현재 소스 그대로 규약에 적었다. 나쁜 줄 알면서 그렇게 적었다.

이상적인 방식을 규약으로 적을 수도 있었다. 그러면 어떻게 되나. 규약은 강제라서 다음 구현부터 그 이상적인 방식이 나온다. 그런데 기존 코드는 그대로다. 새 코드와 기존 코드가 서로 다른 규칙 위에 서게 된다. 규약이 정합성을 지키라고 있는 건데 규약 때문에 정합성이 깨진다.

그래서 순서를 반대로 잡았다. 리팩토링에 착수할 때 규약을 먼저 엎고 그다음 소스를 바꿨다. 규약 변경이 리팩토링의 선언이 되는 구조다.

규약을 프로젝트 지식의 단일 출처로 둔다는 게 실제로는 이런 뜻이었다. 규약은 도달하고 싶은 상태가 아니라 지금 합의된 상태다. 바꾸고 싶으면 규약과 코드를 함께 옮긴다.

결과, 그리고 대가

세 가지 다 줄었다. 현황 쪽은 전체 개요와 구조에 더해 기능별 문서가 따로 떨어지고, 규약 쪽은 전역 규칙과 주제별 세부로 갈려 나온다. 주제는 프로젝트에 따라 붙는데 네이밍·에러 처리·안티패턴 같은 것들이다.

문서가 좋다 나쁘다를 판정할 말은 갖고 있지 않다. 수치로 재두지 않았다. 리뷰 지적 횟수나 재작업 시간을 남겨 두지 않았고 지금 와서 만들 수도 없다. 앞선 두 편과 같은 한계다. 다만 구현을 맡기기 전에 읽힐 것이 생겼고, 앞의 세 실패가 줄어든 것 자체가 그 문서들이 제 일을 했다는 증거다.

대가도 분명하다. 규약을 현재 코드에 맞춰 두는 동안, 그 나쁜 패턴을 따라 새 코드가 계속 나온다. 리팩토링으로 규약을 엎기 전까지는 어그러진 방식이 재생산된다. 그 기간을 그대로 안고 간다. 정합성을 택하면 따라오는 값이다. 규약을 이상적으로 세우고 코드를 방치하는 쪽보다 낫다고 보지만, 그냥 주어지는 정합성은 아니었다.

학습

← 전체 글 목록