테스트를 통과한 AI 코드가 왜 더 고치기 어려울까
얼마 전 Codex 스킬에 코드 품질 평가를 넣는 방법을 고민했다. 코드를 10점 만점으로 평가하고, 부족한 부분을 고치게 하면 어떨까 싶었다.
그런데 점수만 받으면 어떤 코드를 왜 고쳐야 할지 알기 어렵다. 먼저 평가할 대상을 정해야 했다. 코드 품질에는 성능과 보안도 포함되지만, 내가 궁금했던 것은 유지보수성이었다. 지금 만든 기능을 나중에 다시 고칠 때 얼마나 수고가 들지 알고 싶었다.
문구 하나를 바꾸는 일만 생각해 봐도 차이가 있다. 함수 한 곳을 수정하면 끝나는 코드가 있고, 여러 화면을 찾아다녀야 하는 코드가 있다. 둘 다 지금은 똑같이 동작할 수 있다.
문구 하나를 바꾸려는데
설명을 위해 가상의 문서 관리 화면을 생각해 보자. 문서 목록과 상세 화면에서 새 상태 EXPIRED를 ‘만료됨’으로 표시해야 한다. 프로젝트에는 상태를 문구로 바꿔 주는 getDocumentStatusLabel() 함수가 이미 있다.
AI가 두 화면에 다음 조건문을 각각 추가했다고 하자.
1
2
3
4
5
// DocumentList.tsx와 DocumentDetail.tsx에 각각 추가된 코드
const label =
document.status === 'EXPIRED'
? '만료됨'
: getDocumentStatusLabel(document.status);
두 화면에서 ‘만료됨’이 잘 보인다. 기존 상태도 이전처럼 표시되고, 이를 확인하는 테스트도 통과했다. 요청한 작업은 끝난 것처럼 보인다.
며칠 뒤 문구를 ‘기한 만료’로 바꿔야 한다면 어떨까. 이제 두 화면을 찾아 수정해야 한다. 다른 화면에도 같은 조건이 붙었는지 검색해 볼 필요도 있다. 원래 한 함수가 맡던 일을 화면마다 조금씩 나눠 가진 셈이다.
이때 리뷰에서 짚을 것은 구체적이다. 기존 함수에 EXPIRED 처리를 추가하고, 두 화면의 조건문을 없애 다시 그 함수만 호출하게 하면 된다. 그러면 다음 문구 변경은 함수 한 곳에서 끝난다.
물론 두 화면이 같은 문구를 써야 한다는 전제가 있다. 목록에서는 짧게, 상세에서는 길게 설명해야 할 수도 있다. 그런 요구사항이 있다면 문구가 다르다는 이유만으로 합칠 수는 없다. 같은 상태를 다루는 코드라도 같은 역할을 하는지는 확인해야 한다.
테스트가 통과한 뒤에도 볼 것이 있다
위 예시에서 문제는 다음 수정까지 생각했을 때 드러난다. 현재 화면을 확인하는 테스트는 문구가 어디서 만들어졌는지까지 따질 필요가 없다. 두 화면에 조건문이 있어도, 함수 하나에서 문구를 만들어도 기대한 결과는 같기 때문이다.
그래서 리뷰에서는 동작을 확인한 뒤 코드를 한 번 더 읽게 된다. 이미 있는 함수를 두고 비슷한 처리를 새로 만들지는 않았는지, 같은 규칙을 바꿀 때 수정할 곳이 늘지는 않았는지 살펴보는 것이다.
이 과정에서 실제 버그를 찾았다면 먼저 고쳐야 한다. 빌드와 타입 검사, 테스트가 통과했어도 검사하지 않은 입력이나 실패 상황은 남을 수 있다. 지금의 동작을 검증하는 일과 다음 수정을 어렵게 만드는 코드를 찾는 일은 둘 다 필요하다.
8점이라는 평가로는 고칠 곳을 알기 어렵다
Earendil의 코드 품질 측정 글에서도 비슷한 고민을 다룬다. 코드가 올바르게 동작하는지 확인하는 것과 불필요한 중복이나 복잡성을 평가하는 것은 서로 다른 일이다.
앞의 코드에 8점을 주고 10점으로 고쳐 달라고 하면, 무엇을 기준으로 바꿔야 할지 여전히 모호하다. 나는 이런 리뷰를 받고 싶다.
DocumentList.tsx와DocumentDetail.tsx에 동일한EXPIRED조건이 추가됐다. 표시 문구를 바꿀 때 두 파일을 수정해야 한다. 이미 사용 중인getDocumentStatusLabel()에 상태를 추가하면 한곳에서 바꿀 수 있다.
어느 코드가 문제인지, 그대로 두면 다음에 어떤 일이 생기는지, 어떻게 고치면 되는지를 알 수 있다. 점수 없이도 수정할 이유가 충분하다.
코드 줄 수나 수정 파일 수도 그 자체로 결론이 되지는 않는다. 파일을 더 나눠서 읽기 쉬워질 수도 있고, 줄 수를 줄이느라 조건문을 읽기 어렵게 만들 수도 있다. 숫자가 달라졌다면 어떤 코드가 왜 바뀌었는지 확인해야 한다.
AI에게도 이런 비교를 맡길 수 있다. 기존 구현과 사용처를 찾아 새 코드와 비교하게 하고, 지적한 내용이 맞는지 내가 다시 확인하는 방식이다. AI가 기존 함수를 못 찾았거나 화면별 요구사항을 놓쳤다면 리뷰도 틀릴 수 있다. ‘과잉설계’나 ‘아키텍처 위반’이라는 표현이 나왔다는 이유만으로 수정을 시작할 필요는 없다.
함수 하나로 충분한 수정
앞의 사례에서는 두 화면에 복사한 처리를 기존 함수로 옮기면 된다. 별도의 상태 관리 체계나 설정을 도입할 이유는 보이지 않는다. 추가하려는 구조가 실제로 필요한지, 그 구조를 쓸 곳이 있는지까지 설명할 수 없다면 여기서 마치는 편이 낫다.
코드 리뷰가 끝없는 정리 작업이 되지는 않았으면 한다. 필요한 동작을 검증하고, 이번에 추가한 코드가 다음 수정을 번거롭게 만들었다면 그 부분을 고치면 된다. 어떤 역할을 맡아야 할지 모호할 때는 요구사항과 사용처부터 더 확인한다.
다음에 ‘만료됨’이라는 문구를 바꿀 사람은 파일 두 개를 찾아야 할까, 함수 하나만 고치면 될까. 내가 코드 품질 평가에서 알고 싶었던 것은 이런 차이였다.
코드 리뷰를 요청할 때
위 생각을 AI에게 전달한다면 이렇게 적겠다.
1
2
3
4
5
6
7
8
9
10
이 diff를 검토해줘.
1. 먼저 요구사항 대비 동작 오류와 누락된 검증을 확인해줘.
2. 그다음 기존 코드와 비교해 중복된 규칙, 흐려진 책임 경계,
불필요한 추상화가 생겼는지 확인해줘.
3. 각 지적에는 변경 위치, 근거, 어떤 다음 변경에서 문제가 되는지,
가장 작은 수정안을 적어줘.
4. 확인하지 못한 사용처와 추정은 구분하고, 근거가 약하면 지적하지 마.
점수는 매기지 마. 문제가 없다면 없다고 말해줘.