
AI에게 '시키지 않을 것'부터 결정하기 — Terraform 리뷰 관점을 6단계로 분해한 이야기
요약
AI 코드 생성 증가로 인한 리뷰 병목 현상을 해결하기 위해, Terraform 환경에서 리뷰 관점을 6단계로 분해하여 적용한 사례를 소개합니다. AI에게 무작정 리뷰를 맡기기보다, 리뷰 관점을 언어화하여 AI가 수행할 영역과 인간이 담당할 영역을 명확히 구분하는 설계 과정을 다룹니다.
핵심 포인트
- AI 리뷰의 한계: 관점이 전달되지 않으면 암묵지나 특정 맥락을 놓칠 수 있음
- 리뷰 관점 매트릭스: 리뷰 단계를 나누고 CI, 기계, 인간의 역할을 정의
- AI에게 시키지 않을 것 결정: 기계적 체크를 먼저 분리하여 AI의 범위를 좁힘
- Terraform 적용: preflight-review와 risk-assessment 도구로 구현
안녕하세요!
주식회사 Canly에서 SRE를 맡고 있는 혼마입니다. 정말 혼마(本間)예요!
이번에는 '코드 리뷰를 6단계로 나누면 AI와 인간의 분업이 보인다 — 이모토 켄 / Zenn'이라는 기사를 읽고, "이거 정말 좋다!"라고 느껴 Terraform 버전으로 적용해 본 이야기에 대해 말씀드리려 합니다.
서론
AI로 코드 생성 속도가 빨라지면, 병목 현상이 리뷰로 옮겨갑니다. 생성되는 코드의 양이 늘어난 만큼, 리뷰 대기 시간이 쌓이게 됩니다. 이는 현재 현장이나 언어에 관계없이 나타나고 있는 과제라고 생각합니다.
또한, 그렇다면 "리뷰도 AI에게 시키자"라고 생각하는 것이 자연스러운 흐름이겠지만, 실제로 해보면 생각만큼 잘 되지 않습니다.
결국, 관점을 전달하지 않으면 잡아내지 못한다: AI는 판단이 유연하기 때문에, 아무것도 전달하지 않아도 나름대로 그럴싸해 보이는 리뷰를 돌려줍니다. 하지만 보고 싶은 관점을 전달하지 않으면, 잡아내길 바랐던 문제는 결국 사전에 발견되지 않습니다. "AI에게 봐달라고 하는 것"과 "봐야 할 관점이 전달되어 있는 것"은 별개의 문제였습니다 -
PR을 제출한 뒤에 리뷰하는 것은 늦다: 리뷰에서 지적되는 내용의 상당수는 본래 로컬에서 미리 해결할 수 있었던 것들이었습니다. 첫 번째로 제출하는 PR의 질을 높이지 않으면, 사람이 검토할 때의 부하를 줄일 수 없습니다. hooks를 통해 기계적으로 걸러내는 방식도 시도해 보았지만, 사소한 수정이 너무 무거운 체크가 되어버리기도 했습니다 -
작동은 하지만, 암묵지(Implicit Knowledge)를 무시한 코드가 통과된다: 지금까지 팀이 암묵적으로 지켜온 작성 방식에서 벗어나더라도, 일단 작동은 합니다. 암묵지는 어디에도 적혀 있지 않기 때문에, AI 리뷰에 맡겨도 잡아낼 수 없습니다. 건건이 보면 작지만, 서서히 부채가 쌓여가는 느낌이 들었습니다 -
어디를 보고 어디를 보지 않는지 알 수 없다: AI 리뷰는 안전하지 않은데, 지적 사항이 0개인 리포트는 "문제 없음"으로 보이기 쉽습니다. 실제로는 단지 "그 부분을 보지 않았을" 뿐일지도 모릅니다
이 4가지는 깊이 파고들면 결국 같은 지점에 도달했습니다. 리뷰에서 무엇을 봐야 하는지가 언어화되어 있지 않기 때문에, AI에게 전달할 관점도 정해지지 않고, 기계에 맡길 수 있는 범위도 정해지지 않으며, 결과적으로 "어디를 보았는지"도 설명할 수 없게 됩니다. 이는 AI의 정밀도를 높인다고 해결될 문제가 아니었습니다.
그래서 "AI에게 리뷰를 시키기" 전에 "리뷰 관점을 정리하여 어디를 AI에게 맡길지 결정하기"부터 다시 시작했습니다. 만든 것은 두 가지입니다.
리뷰 관점 매트릭스 (Review Perspective Matrix): 평소 리뷰에서 보고 있는 관점을 모두 나열하고, 각각이 6단계 중 어느 Stage인지, 현재 그것을 누가 담보하고 있는지(CI / 기계 체크 / 규약 / 인간)를 채워 넣은 표 -
로컬에서 실행하는 리뷰 skill: 위 표에서 기존의 담보 층을 지워나가며, AI에게 "시키지 않을 것"을 먼저 결정한 뒤, 남은 범위만 전달하는 설계로 만들었습니다. 결과적으로 PR을 내기 전에 diff를 확인하는 terraform-preflight-review와, apply 전에 plan의 영향을 분류하는 tfplan-risk-assessment 두 가지로 나누었습니다.
이 기사에서는 관점 표를 어떻게 만들고 무엇을 지워나갔는지, 그리고 남은 범위를 어떻게 skill로 구현했는지에 대해 쓰겠습니다.
빌려온 프레임워크 — 리뷰를 6단계로 나누기
정리 틀은 서두에 소개한 기사의 것을 그대로 빌려왔습니다 (매우 참고가 되었습니다. 감사합니다!)
코드 리뷰를 6단계로 나누고, 각각에서 AI가 커버할 수 있는 비율을 나타내는 방식입니다 (비율은 원문 기사의 수치).
| Stage | 보는 것 | AI 비율 |
|---|---|---|
| 01 Format | 포맷팅 (Formatting) | 100% |
| ... | ... | ... |
가장 와닿았던 점은 "단계가 내려갈수록 정답이 일의적(unambiguous)이지 않은 문제로 변한다"는 정리였습니다. 이것이 있으면 "AI는 정밀도가 부족해서 맡길 수 없다"가 아니라 "이 단계는 정답이 하나로 정해지지 않으므로, 애초에 AI에게 맡기는 형태가 아니다"라고 설명할 수 있게 됩니다.
그리고 Terraform은 이 프레임워크와 궁합이 좋아 보였습니다. 규약이 많은 스택이기도 하고, Stage 1~2는 이미 CI에서 기계적으로 처리하고 있으므로, 단계를 나누었을 때 "이 부분은 이미 채워져 있다"는 점이 명확히 보일 것이라고 생각했기 때문입니다.
우선, 리뷰 관점을 전부 나열했다
skill을 만들기 시작하기 전에, Notion에 "리뷰 관점 매트릭스"를 만들었습니다. 사내 문서이므로 일부만 공개합니다.

중요한 열은 이것뿐입니다.
| 열 | 무엇을 적는가 |
|---|---|
| 관점 | 리뷰에서 실제로 보고 있는 항목 |
| ... |
제가 한 일은 단순했습니다. 평소 Terraform의 PR (Pull Request)에서 보고 있는 관점을 기억나는 대로 전부 나열하고(물론 이 과정에서도 AI를 활용하여 과거 리뷰 코멘트로부터 관점을 추출했습니다), 각각에 Stage와 "지금 그것은 누가 담보하고 있는가"를 채워 넣는 작업이었습니다.
여기서 가장 효과적이었던 것은, "AI에게 무엇을 시킬 것인가" 이전에 "애초에 우리 자신들이 무엇을 보고 있는가"를 언어화할 수 있었다는 점이었습니다. 리뷰 시 체크하는 관점 중 일부는 문서화되어 있었지만, 리뷰어의 머릿속에만 있는 것도 많았기에 이를 나열해 보니 "이것은 규약에 적혀 있지만, 저것은 적혀 있지 않다", "규약으로 정립되지는 않았지만 암묵적으로 맞추고 있었던 것" 등이 시각화되었습니다.
이러한 재고(Inventory) 과정을 거치지 않고 skill을 작성하기 시작했다면, 우연히 떠오른 관점만을 구현하고는 "AI 리뷰를 도입했다"라고 말해버렸을 것입니다.
기존의 담보층으로 제거하기
관점이 나열되면, "누가 담보하는가"를 채워 나갑니다. 담보 레이어는 크게 4가지로 정리했습니다.
- 기계 (Machine): CI (Continuous Integration)에서 자동 판정할 수 있는 것. 향후 hooks / lint로 기계화할 부분도 포함 -
- 인간 (Human): 리뷰에서 사람이 판단하는 것 -
- 사전 합의 (Prior Agreement): ADR (Architecture Decision Record)이나 규약에 적어두면, 생성 시와 리뷰 시 모두 효과가 있는 것 -
- AI: 위의 3가지로 제거한 뒤 남는 것
결과는 다음과 같았습니다.
| Stage | 제거 결과 |
|---|---|
| 01 Format | terraform fmt -recursive (자동 수정) |
| 02 Lint | validate / tflint / trivy config / OPA (conftest, plan 후) |
| 03 Style | 기계로 판정할 수 없는 일부가 남음 |
| 04 Logic | 여기가 남음 |
| 05 Design | 책임 및 분할의 타당성 |
| 06 Architecture | 경계 및 멀티 어카운트 전략 |
Stage 1 (Format)과 Stage 2 (Lint)는 tfaction의 CI에서 terraform fmt / validate / tflint / trivy config / OPA (conftest)가 이미 실행되고 있었습니다. 따라서 AI에게는 맡기지 않기로 결정했습니다.
참고로 fmt와 tflint는 위반 사항을 지적하여 실패시키는 것이 아니라, 자동 수정하여 PR에 commit을 push하도록 설정되어 있습니다. 이 레이어는 애초에 "지적"으로서 사람에게 돌아오지 않습니다. 서두의 첫 번째 항목에서 언급한 "관점을 전달한다"의 반대 개념으로, 전달하지 않을 관점을 결정하는 것도 그만큼 중요했습니다. CI가 확실하게 걸러주는 것을 AI에게 재판정하게 하면, 지적의 총량만 늘어날 뿐 품질은 올라가지 않습니다.
Stage 3 (Style)는 갈렸습니다. 기계 판정이 가능한 것은 기계 쪽으로 몰고 (필수 태그, snake_case, Terraform 모듈의 ref 버전 지정이나 snake_case 등은 hooks / lint로 기계화할 예정), "리소스의 명명(Naming)이 역할을 나타내고 있는가"와 같이 자연어의 타당성 판단이 필요한 것만 AI에게 남겼습니다. 같은 Stage 안에서도 기계에 맡길 수 있는 것과 AI만이 판단할 수 있는 것이 섞여 있다는 점이 직접 해보며 알게 된 사실입니다.
Stage 5와 6은 인간과 사전 합의 (ADR)의 영역으로 남겨두었습니다.
즉, "AI에게 무엇을 시킬 것인가"가 아니라 "시키지 않을 것"을 먼저 결정한 순서입니다. 제거 작업을 마치고 AI에게 남은 것은 Stage 3의 일부와 Stage 4뿐이었습니다 (Stage 5는 ADR과의 대조 보조를 예정하고 있을 뿐입니다).
남은 범위를 skill로 만들기
남은 범위를 Claude Code의 skill로 구현했습니다.
Stage 3 중 AI에게 남긴 규약 체크 (명명의 타당성, 리포지토리 고유 규약)와, Stage 4 중 diff에서 감지할 수 있는 경량 시그널은 terraform-preflight-review가 담당합니다. Stage 4 중 plan
diff에서 감지할 수 없는 범위는 tfplan-risk-assessment로 나누었습니다. 둘 다 PR을 제출하기 전에 로컬에서 실행하는 skill입니다.
즉, AI가 담당하고 있는 것은 Stage 3의 일부와 Stage 4뿐입니다. 위의 Stage 1·2는 기계(CI)에, 아래의 Stage 5·6은 인간과 사전 합의(ADR)로 남겨두었습니다.
Stage 4 (Logic)가 가장 위험하다 — plan이라는 중간 표현
원문에도 "Stage 4의 AI 60%라는 어중간한 높이가 방심을 부른다"라고 적혀 있으며, 실제로 고민했던 부분이기도 합니다. Terraform에서도 정확히 그러하며, 게다가 Terraform에는 고유한 사정이 있었습니다.
diff로는 알 수 없는 것이 plan에서는 확정된다
예를 들어보겠습니다. 다음과 같은 PR이 왔다고 가정합시다.
resource "aws_db_instance" "this" {
- identifier = "prod-canly-api-db"
+ identifier = "prod-canly-api-database"
...
diff로서는 단 한 줄, 글자가 바뀐 것뿐입니다. 리뷰어 입장에서는 "이름을 통일했구나" 하고 넘어가 버릴 법한 변경입니다.
하지만 terraform plan을 실행하면 다음과 같이 됩니다.
# aws_db_instance.this must be replaced
-/+ resource "aws_db_instance" "this" {
~ identifier = "prod-canly-api-db" -> "prod-canly-api-database" # forces replacement
...
RDS의 재생성입니다. 아무 조치도 취하지 않으면 데이터가 삭제됩니다.
이것은 diff의 텍스트를 아무리 정성스럽게 읽어도 판정할 수 없습니다. 특정 속성의 변경이 교체(replacement)를 강제하는지 여부는 리소스 타입별 프로바이더(provider) 구현에 의존하는 정보이며, 코드 어디에도 명시되어 있지 않기 때문입니다. "AI 비율 60%"의 나머지 40%에는 이런 것들이 포함되어 있으며, 심지어 나머지 40% 쪽이 사고로서 더 치명적입니다.
반대로, plan을 기다리지 않고도 diff 단계에서 걸러낼 수 있는 것도 많습니다. prevent_destroy의 삭제, 명명 규칙 위반, 기밀 정보의 하드코딩, PR 규모가 너무 큰 경우 등이 그것입니다. 그리고 plan 실행에는 AWS 인증과 실행 시간이 필요하므로, diff에서 알 수 있는 것을 plan까지 기다리는 것은 단순히 느린 일입니다.
그래서 Stage 4를 두 개로 나누었다
| 타이밍 | 확인 대상 | skill |
|---|---|---|
| 4a Logic 경량 (diff 표층) | PR 제출 전 | prevent_destroy 삭제, stateful 리소스 파괴, 기밀 정보, IAM 과잉 권한, 고단가 리소스 변경, 동일 이름 리소스 선언, PR 규모, 환경 교차, 디렉토리 교차 |
| 4b Logic 본체 (plan 후) | apply 전 | destroy / replace 분류, 드리프트(drift) 감지, 의도한 변경 범위 외의 차분 |
포인트는 Terraform에는 실행 전에 확정 정보를 얻을 수 있는 중간 표현이 있다는 점입니다. plan은 "앞으로 어떤 일이 일어날 것인가"를 기계 판독 가능한 JSON으로 출력해주므로, 영향 범위 판정을 추측이 아닌 사실로서 수행할 수 있습니다. 이 성질을 이용하여 Stage 4를 "diff로 감지할 수 있는 범위"와 "plan으로 확정되는 범위"로 나누었습니다.
나눈 것에 따른 부작용으로서 좋았던 점은 각각의 skill의 책임이 명확해졌다는 것입니다. 4a의 리포트에는 "plan 이후의 영향 분석은 4b의 담당"이라고 적을 수 있게 되었고, 반대로 4b에서는 규칙 체크를 재실행할 필요가 없어졌습니다. 하나의 skill에 전부 몰아넣었다면 아마 "무엇을 어디까지 보았는지"가 모호한 상태였을 것입니다.
다른 스택에서도 "실행 전에 확정 정보를 얻을 수 있는 중간 표현"이 있지 않을까요? DB 마이그레이션의 dry-run, Kubernetes의 --dry-run=server, 타입 검사 결과 등, diff 외부에 답을 가지고 있는 것은 의외로 많을 것 같습니다.
운용을 지속하기 위한 두 가지 설계 판단
시스템으로서 지속 가능한지 여부는, 만든 직후의 정확도보다는 이 두 가지 요소에 달려 있다고 느낍니다.
1. 관점을 늘릴 때, skill은 수정하지 않는다
서두에서 언급한 세 번째 문제, 즉 암묵지(Implicit Knowledge)가 무시되어 부채가 쌓여가는 문제에 대한 대책이기도 합니다. 암묵적인 상태로 두면 AI에게 전달되지 않으므로, 결국 규약(Convention) 파일에 작성할 수밖에 없습니다.
이 skill은 실행 시에 CLAUDE.md 또는 docs/standards/coding.md와 같은 규약 파일을 읽어 들여, 그곳에 적힌 자연어(Natural Language) 규칙을 차분(diff)과 대조하는 설계로 되어 있습니다.
따라서 리뷰 관점을 늘리고 싶을 때 해야 할 일은 규약에 한 줄 쓰는 것뿐이며, skill 측의 구현은 제로(0)입니다.
게다가 이 한 줄은 이중으로 효과를 발휘합니다.
- 생성 시:
CLAUDE.md로부터@import되므로, AI가 애초에 그런 방식으로 작성하지 않도록 유도합니다. - PR 전: skill이 동일한 파일을 읽으므로, 리뷰 단계에서 잡아냅니다.
실제로 리뷰 관점을 정리해 나가는 과정에서 "이것은 skill에 구현하기보다 규약에 쓰는 편이 좋겠다"라는 것들이 나왔습니다. 디렉터리 간 참조(Inter-directory reference) 시 output과 data 블록의 구분 사용에 관한 이야기인데, 이는 별도의 기사로 정리해 두었습니다.
[Terraform의 디렉터리 간 참조를 AI에게 작성하게 하면 output과 data 블록이 흔들리는 문제를 정리한 이야기]
관점의 재고(Inventory) 작업은 단순히 skill을 만들기 위한 작업이 아니라, 규약 측으로 넘길 것과 skill 측에 남길 것을 분류하는 작업이기도 했다는 것이 되돌아보며 느끼는 소회입니다.
2. "체크하지 않은 영역"을 매번 출력하게 한다
서두의 네 번째 과제(어디를 보고 어디를 보지 않는지 알 수 없다)에 대한 대책입니다. 리포트 마지막에 체크하지 않은 영역을 고정 문구로 강제 출력하도록 했습니다.
## 이 스킬로 체크하지 않은 영역 (사람의 리뷰 또는 기계적 체크 필수)
### plan 실행 시에만 검출할 수 있는 에러
### IAM 정책의 로직 취약성
...
이와 함께, skill의 설계 원칙에 "이 스킬의 결과를 Approve(승인)의 근거로 삼지 않는다"를 명시했습니다.
이것들이 어느 정도의 효과가 있을지는 모르겠지만, 체크하지 않은 영역을 명시함으로써 사람의 리뷰가 개입되도록 유도하고, "AI는 여기까지 수행했으니 사람은 어떤 관점을 보면 되는가"를 알 수 있게 하려는 의도로 출력하고 있습니다.
실제로 실행해 보니
terraform-preflight-review 리포트는 다음과 같은 구성으로 출력됩니다.
- 운영 환경 변경 강조 블록 (prod 하위에 변경이 있는 경우에만 서두에 표시)
- 대상 파일 및 스코프(Scope)
- Stage 3 규약 체크 결과
- Stage 4 경량 시그널 (경고로 열거)
- 이 스킬로 체크하지 않은 영역
- 다음에 사용해야 할 다른 skill 안내
- 요약(Summary)
이 skill은 이제 막 제작한 단계라 개선할 점도 있으며, 의도적인 변경에 대해 경고가 발생하는 케이스(예: 신규 환경을 통째로 만드는 PR이 "PR 규모가 크다"는 이유로 걸리는 등)도 역시 존재합니다. 이 부분은 운영하면서 조정해 나갈 예정입니다! (우리의 싸움은 이제부터다!)
아직 되지 않은 것들
현재 보이는 잔여 과제는 크게 두 가지입니다.
- 규약으로 옮기지 못한 관점이 아직 남아 있음: 관점의 재고(Inventory)는 마쳤으나, 명문화와 기계화가 따라가지 못하고 있습니다. 관점 매트릭스(Matrix)에 포함되어 있더라도 규약 파일에 적혀 있지 않으면 생성 단계에서는 작동하지 않습니다.
- "프로덕트 특성에 따른 리뷰"를 어떻게 실현할지 검토 중: 변경 영향도가 높은 지점은 프로덕트마다 다릅니다. 해저드 맵(Hazard Map) 같은 자료를 skill의 컨텍스트(Context)에 포함하는 안이 나왔으나, 그 정보를 어디에 정전(Source of Truth)으로 둘지를 포함하여 검토 중입니다.
다른 스택에 적용할 수 있는 점
Terraform에 특화된 이야기가 많았지만, 스택에 의존하지 않는 부분을 세 가지로 압축하면 다음과 같습니다.
- 관점을 나열하고 기존의 담보 계층에서 소거하기: 남은 것들만 AI에게 전달합니다. 이 만드는 순서 자체가 재사용 가능합니다.
- 관점을 늘릴 때는 툴을 고치지 않고 규약에 쓰기: 툴이 자연어 규칙을 읽도록 설계해 두면, 생성 단계와 리뷰 단계에 동시에 효과를 줍니다.
- "체크하지 않은 영역"을 출력에 강제하기: 지적 사항이 제로(0)인 것을 안전하다고 오판하지 않게 합니다. 가장 저렴하게 효과를 볼 수 있는 장치입니다.
요약
6단계 프레임워크를 실제로 사용해 보니, 이는 'AI 비율 표'라기보다 책임 분계(Responsibility Boundary)의 합의 형성 도구로서 효과적이었습니다. 단순히 "AI 리뷰를 도입했습니다"라고만 말하면, 실제로 무엇이 보장되었는지 팀 내에서도 모호한 상태로 남기 쉽습니다. 단계를 나누어 각각의 보장 계층을 명시함으로써, "이 부분은 기계", "이 부분은 AI", "이 부분은 인간", "이 부분은 사전 합의 (ADR)"를 동일한 언어로 이야기할 수 있게 됩니다.
그리고 Terraform에는 plan 결과라는 중간 표현(Intermediate Representation)이 존재합니다. diff 외부에 확정된 정보를 가지고 있는 스택에서는, Stage 4를 "실행 전"과 "확정 후"로 나누는 것만으로도 리뷰의 정밀도가 달라질 것이라고 생각합니다. 이와 유사한 중간 표현을 가진 스택은 다른 곳에도 있을 것이라 생각하므로, 참고가 된다면 기쁘겠습니다.
참고
Discussion

AI 자동 생성 콘텐츠
본 콘텐츠는 Zenn AI의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기