Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
172 changes: 118 additions & 54 deletions .github/REVIEW.md
Original file line number Diff line number Diff line change
@@ -1,94 +1,158 @@
# 코드 리뷰 규칙

`.github/workflows/claude-code-review.yml`이 PR마다 이 파일을 읽어 리뷰 기준으로 삼는다. 로컬에서도 "`.github/REVIEW.md`
기준으로 내 변경 리뷰해줘"로 쓸 수 있다.
`.github/workflows/claude-code-review.yml`이 PR마다 이 파일을 읽어 리뷰 기준으로 삼는다. 로컬에서도 "`.github/REVIEW.md` 기준으로 내 변경 리뷰해줘"로 쓸 수 있다.

프로젝트 구조·설계 원칙·코딩 규약은 [`CLAUDE.md`](../CLAUDE.md)에 있다. 이 파일은 **어떻게 리뷰할지**만 정의한다. 사실 관계는 CLAUDE.md를
따른다.
프로젝트 구조·설계 원칙·코딩 규약은 [`CLAUDE.md`](../CLAUDE.md)에 있다. 이 파일은 **어떻게 리뷰할지**만 정의한다. 사실 관계는 CLAUDE.md를 따른다.

## 리뷰 범위

- 이 PR의 diff에서 **변경된 라인만** 리뷰한다. 기존 코드의 문제는 이번 변경으로 인해 실제로 깨지는 경우에만 언급한다.
- `go.sum`, `vendor/`, 자동 생성 파일은 건너뛴다.
- 판단이 서지 않으면 추측하지 말고 `go build ./...`, `go vet ./...`,
`go test ./...`를 실제로 실행해 확인한 뒤 지적한다.
- 판단이 서지 않으면 추측하지 말고 `go build ./...`, `go vet ./...`, `go test ./...`를 실제로 실행해 확인한 뒤 지적한다.

## 판단 근거와 확신도

다음 순서로 근거를 적용한다.

1. diff와 실제 실행 결과에서 확인한 동작
2. `CLAUDE.md`에 기록된 프로젝트 결정
3. Go 언어의 동작과 표준 라이브러리 계약
4. 저장소에서 반복해서 사용하는 구조와 관례
5. 이 문서의 설계 판단 원칙

- 근거가 부족하거나 운영 전제를 확인할 수 없으면 결함으로 단정하지 않고 `[Question]`으로 남긴다. 무엇을 확인해야 하며 답에 따라 판단이 어떻게 달라지는지 함께 쓴다.
- 미래에 필요할 수 있다는 이유만으로 추상화나 방어 코드를 요구하지 않는다. 반대로 현재 코드가 작다는 이유만으로 도메인 타입이나 책임 분리를 불필요하다고 판단하지 않는다.
- 선호만으로 `[Blocker]`나 `[Major]`를 부여하지 않는다. 실제 실패 조건, 호환성 위반 또는 프로젝트 결정 위반을 제시할 수 있어야 한다.

## 심각도 등급

모든 지적에 접두어를 반드시 붙인다.

| 등급 | 기준 |
|--------------|----------------------------------------------------------------------------------------------------------------------|
| `[Blocker]` | 머지하면 안 됨: 패닉/nil 역참조, 리소스 누수, 에러 무시, 데이터 손상, 자격증명 노출, 동시성 버그, 컴파일 실패 |
| `[Major]` | 동작은 하지만 고쳐야 함: 잘못된 에러 처리, 경계 조건 누락, API 오용, 되돌리기 어려운 설계 문제, 문서와 코드의 불일치 |
| `[Minor]` | 개선 제안: 네이밍, 중복, 가독성, 오탈자 |
| `[Question]` | 의도를 모르겠어서 묻는 것 (지적이 아님) |
| 등급 | 기준 |
|--------------|-----------------------------------------------------------------------------------------------------------------------------------|
| `[Blocker]` | 머지하면 안 됨: 변경 경로에서 실제 발생 가능한 패닉/nil 역참조, 리소스 누수, 데이터 손상, 자격증명 노출, 동시성 버그, 컴파일 실패 |
| `[Major]` | 동작은 하지만 고쳐야 함: 잘못된 에러 처리, 경계 조건 누락, API 오용, 되돌리기 어려운 설계 문제, 문서와 코드의 불일치 |
| `[Minor]` | 동작을 깨뜨리지는 않는 개선: 의미가 모호한 네이밍, 불필요한 중복·복잡도, 가독성 저하, 오탈자 |
| `[Question]` | 의도, 호출 계약 또는 운영 전제를 확인해야 판단할 수 있는 사항 (지적이 아님) |

## 설계 판단 원칙

### 기존 동작과 변경 범위

- 리팩터링은 기존의 입력 검증, 처리 순서, 기본값, 출력, 에러, 상태 변경 시점을 보존해야 한다. 변경되었다면 의도와 호환성 영향을 확인한다.
- 현재 PR의 목적에 필요하지 않은 정리는 별도 변경으로 분리한다. 단, 분리를 요청하려면 현재 변경과 함께 있을 때 검증이 어려워지거나 회귀 위험이 커지는 이유를 제시한다.
- 코드 이동이나 이름 변경에서도 빠진 분기, 호출, 초기화, 정리 작업이 없는지 전후 동작을 비교한다.

### 책임과 도메인 모델

- 상태를 소유한 패키지나 타입이 그 상태의 검증, 변경과 불변식 유지도 담당하는지 확인한다.
- 같은 개념, 불변식, 수명 또는 변경 이유를 공유하는 값은 필드 수와 관계없이 명시적인 타입으로 모델링할 수 있다. 구조체를 단순히 인자를 줄이는 포장으로 보지 않는다.
- 도메인 타입은 가능하면 생성 또는 로드 시 유효한 상태를 만들고, 그 상태를 해석하거나 변경하는 동작을 가까이 둔다.
- 여러 primitive 인자가 함께 사용될 때 잘못된 조합을 만들기 쉽거나 같은 검증이 반복된다면 도메인 타입 도입을 검토한다.
- `cmd/`는 사용자 입력을 해석하고 `internal/`의 도메인 동작을 호출한다. 배포, 상태 관리, 복구 같은 업무 규칙을 `cmd/`에 구현하지 않는다.

### 추상화와 단순성

- 추상화는 현재 코드량이나 인자 수가 아니라 책임, 불변식, 교체 경계와 호출부의 의미를 기준으로 판단한다.
- 함수, 타입, 인자, 콜백, 상태가 실제 의미나 책임 없이 전달 단계만 늘린다면 단순화한다. 도메인 개념을 없애는 방식으로 단순화하지 않는다.
- 같은 값을 두 곳 이상에서 유지하면 생성, 갱신, 부분 실패, 재시도, 삭제의 모든 경로에서 동기화되는지 확인한다.
- 방어 조건과 fallback은 실제 발생 가능한 입력이나 호출 경로로 설명할 수 있어야 한다. 다른 불변식으로 이미 보장되는 조건을 막연한 안전을 이유로 중복하지 않는다.
- 변수 선언과 상태 변경은 관련 로직 가까이에 두고, 처리 순서는 코드가 표현하는 의미와 맞춘다.

### Go 인터페이스와 API

- 인터페이스는 사용하는 쪽에서 필요한 최소 동작을 표현하고, 패키지 경계를 분명하게 할 때 도입한다. 구현체가 하나라는 이유만으로 인터페이스를 금지하거나 테스트 대역만을 위해 무조건 요구하지 않는다.
- 공개 함수와 타입의 이름은 패키지 문맥을 포함해 도메인 의미를 드러내야 한다. 짧은 이름도 문맥이 분명하면 허용하며, 다른 개념과 혼동되면 구체화한다.
- `nil`, zero value, sentinel 값과 생략된 옵션의 의미가 명확한지 확인한다. 호출자가 만들 수 있는 모호한 상태는 타입이나 검증으로 줄인다.
- 공개 API 변경은 기존 호출자의 컴파일 여부뿐 아니라 기본값, 출력, 에러와 동작 의미의 호환성도 확인한다.

### 실패, 상태 전이와 결과 의미

- 성공과 실패는 내부 단계 하나의 결과보다 사용자가 요청한 최종 상태가 달성됐는지를 기준으로 판단한다. 이미 원하는 상태인 경우의 멱등 동작도 확인한다.
- 상태를 변경한 뒤 다음 단계가 실패하면 남는 상태, 재시도 가능 여부와 수동 복구 방법을 확인한다.
- 비동기 또는 동시성 변경은 상태, 이벤트, 전이 조건, 소유자, 실행 순서와 취소 경로를 추적한다. 일부 경로만 정상 종료하는지 확인하지 않는다.
- 여러 대상의 결과를 합칠 때 전체 성공, 부분 성공, 실패의 의미와 사용자에게 전달되는 정보가 일치해야 한다.

### 성능과 운영 비용

- 성능 지적은 호출 빈도, 입력 크기, 호스트 수 또는 외부 요청 횟수 등 구체적인 비용 근거가 있을 때만 한다.
- 여러 호스트에서 반복되는 SSH/ZooKeeper/파일 작업과 대량 토폴로지 처리를 우선 확인한다. 작은 로컬 계산에 근거 없는 미세 최적화를 요구하지 않는다.
- 더 단순한 코드와 자원 사용 사이의 절충은 실제 상한과 운영 빈도를 기준으로 판단한다.

## 스타일 제약

- 한국어로 작성한다. 코드 식별자와 기술 용어는 원문을 유지한다.
- 칭찬, 총평, 변경 요약 나열을 하지 않는다. "좋은 변경입니다" 같은 문장 금지.
- 지적 1건 = 인라인 코멘트 1개. 각 코멘트는 다음 순서로 쓴다.
1. 무엇이 문제인지
2. 어떤 입력·상황에서 실제로 터지는지
3. 구체적 수정안 (코드 조각)
- 인라인 코멘트 하나에는 독립적으로 수정할 수 있는 문제 하나만 담는다. 같은 원인과 수정안을 공유하는 증상은 한 코멘트에 묶고, 수정 위치나 해결 방법이 다르면 별도 코멘트로 분리한다.
- 변경된 특정 라인에 귀속되는 문제는 인라인 코멘트로 남기고, PR 전체의 설계 결정이나 변경 라인 하나로 특정할 수 없는 질문은 최종 요약에 남긴다.
- 각 인라인 코멘트는 아래 형식을 사용한다.

[Major]
문제를 한 문장으로 요약합니다.

**문제 상황**

문제가 드러나는 입력이나 실행 경로와 그 영향을 설명합니다.

**해결 방안**

"왜 문제인지" 없이 "이렇게 바꾸세요"만 쓰지 않는다.
현재 구조에 적용할 수 있는 구체적인 수정 방향을 설명합니다.

**확인 필요**

코드만으로 알 수 없는 요구사항이나 설계 의도가 있을 때만 질문합니다.

첫 줄에는 실제 심각도와 핵심 문제를 함께 쓴다. `문제 상황`에는 재현 조건, 실행 경로, 결과를 필요한 만큼만 적고, `해결 방안`에는 최소한의 구체적인 수정 방향을 적는다. 코드 조각은 API 형태나 제어 흐름을 글만으로 명확히 전달하기 어려울 때만 넣는다. `확인 필요`는 선택 항목이며 일반적인 부연 설명을 넣는 용도로 사용하지 않는다. "왜 문제인지" 없이 "이렇게 바꾸세요"만 쓰지 않는다.
- 인라인 코멘트는 최대 10개. 그 이상이면 심각도 높은 순으로 자른다.
- `gofmt` / `golangci-lint`가 이미 잡는 것 (포매팅, 미사용 변수 등)은 언급하지 않는다. CI가 잡는다.
- 스타일 취향 논쟁 금지. [Effective Go](https://go.dev/doc/effective_go)에 명시된 근거가 있을 때만 지적한다.
- 스타일 취향 논쟁 금지. [Effective Go](https://go.dev/doc/effective_go), 이 문서의 명시적 규칙 또는 저장소에서 일관되게 사용하는 관례에 근거가 있을 때만 지적한다.
- 개수를 억지로 채우지 않는다. **지적할 것이 없으면 "지적 사항 없음"이라고만 쓰는 것이 정답이다.**
- CLAUDE.md에 이미 결정으로 기록된 사항 (예: 실패 시 롤백하지 않는 원칙)을 PR마다 다시 문제 삼지 않는다.

## 코드에서 특히 볼 것

- **에러 처리** — 에러를 `_`로 버리지 않는지, `fmt.Errorf`에서 `%w`로 감싸는지, 사용자에게 보이는 CLI 에러 메시지가 "무엇이 / 왜 실패했고 / 어떻게
복구하는지"를 담는지. 원격 호스트가 관련되면 어느 호스트인지 포함하는지.
- **새로 추가된 `panic()`** — `cmd/`와 `internal/config.go`, `internal/util.go`에 남아 있는 기존 panic은 정리 대상이므로
지적하지 않는다. 이 PR에서 **새로 추가된**
panic만 `[Major]`로 지적하고 `RunE` + error 반환을 제안한다.
- **부분 실패** — 여러 호스트에 순차 적용하는 흐름에서 중간에 실패했을 때 즉시 중단하고 실패 단계 / 원인 / 수동 복구 방법을 출력하는지. (롤백하지 않는 것이 이
프로젝트의 결정이다.)
- **원격 실행** (`internal/ssh`) — 원격 셸에 넘기는 커맨드 문자열에 사용자 입력 (경로, 이름)이 이스케이프 없이 들어가는지, 타임아웃이 있는지,
`exec.ExitError`의 exit code로 "명령 실패"와 "연결 실패"를 혼동하지 않는지.
- **ZooKeeper** (`internal/zk`, `internal/cluster`) — `zk.Conn` close 누락, watcher 누수, znode 생성/삭제의
멱등성, 재귀 헬퍼의 인자 전달 오류.
- **cobra 커맨드** (`cmd/`) — 인자·플래그 검증이 실행 전에 이루어지는지,
`cmd/`가 얇게 유지되고 로직이 `internal/`에 있는지.
- **동시성** — goroutine 종료 보장, `WaitGroup`/`context` 취소 전파, 공유 변수 경합.
- **파괴적 동작** — `delete` / `stop` 계열이 확인 절차 (`internal.Confirm`) 또는
`--force` 없이 실행되지 않는지.
- **테스트** — 순수 로직 (파싱, 검증, 계산)이 추가되었는데 테이블 기반 테스트가 없으면 `[Minor]`로 요청한다. SSH나 실제 ZooKeeper가 필요한 코드에는
요구하지 않는다.
- **문서 동기화** — 사용자에게 보이는 인터페이스 (커맨드, 플래그, YAML 필드, 에러 메시지 문구)를 바꿨는데 `docs/`와 `*-sample-topology.yml`
이 함께 갱신되지 않았으면 `[Major]`.
- **에러 처리** — 사용자 입력 오류, 내부 불변식 위반, 로컬 I/O 실패, 원격 명령 실패와 연결 실패를 구분하는지 확인한다. 에러를 `_`로 버리지 않는지, `fmt.Errorf`에서 `%w`로 감싸는지, 사용자에게 보이는 CLI 에러 메시지가 "무엇이 / 왜 실패했고 / 어떻게 복구하는지"를 담는지 확인한다. 원격 호스트가 관련되면 어느 호스트인지 포함한다.
- **새로 추가된 `panic()`** — `cmd/`와 `internal/config.go`에 남아 있는 기존 panic은 정리 대상이므로 지적하지 않는다. 이 PR에서 **새로 추가된** panic만 `[Major]`로 지적하고 `RunE` + error 반환을 제안한다.
- **부분 실패** — 여러 호스트에 순차 적용하는 흐름에서 중간에 실패했을 때 즉시 중단하고 실패 단계 / 원인 / 수동 복구 방법을 출력하는지. (롤백하지 않는 것이 이 프로젝트의 결정이다.)
- **원격 실행** (`internal/ssh`) — 원격 셸에 넘기는 커맨드 문자열에 사용자 입력 (경로, 이름)이 이스케이프 없이 들어가는지, 타임아웃이 있는지, `exec.ExitError`의 exit code로 "명령 실패"와 "연결 실패"를 혼동하지 않는지.
- **ZooKeeper** (`internal/zk`, `internal/cluster`) — `zk.Conn` close 누락, watcher 누수, znode 생성/삭제의 멱등성, 재귀 헬퍼의 인자 전달 오류.
- **cobra 커맨드** (`cmd/`) — 인자·플래그 검증이 실행 전에 이루어지는지, `cmd/`가 얇게 유지되고 로직이 `internal/`에 있는지.
- **도메인 모델** — 함께 유효성 검증되고 함께 변경되는 값이 primitive 인자로 흩어져 있지 않은지, 타입이 잘못된 상태를 허용하지 않는지, 관련 동작과 불변식이 상태를 소유한 타입이나 패키지에 있는지.
- **동시성** — goroutine 종료 보장, `WaitGroup`/`context` 취소 전파, 공유 변수 경합, 채널 close 책임, 여러 결과의 부분 실패 의미.
- **성능** — 호스트 수만큼 불필요한 원격 연결이나 파일 읽기를 반복하는지, 루프 안에서 피할 수 있는 외부 작업을 수행하는지. 구체적인 입력 규모 없이 allocation이나 짧은 로컬 연산만 문제 삼지 않는다.
- **파괴적 동작** — `delete` / `stop` 계열이 확인 절차 (`internal.Confirm`) 또는 `--force` 없이 실행되지 않는지.
- **테스트** — 순수 로직 (파싱, 검증, 계산)이 추가되었는데 경계값과 실패 동작을 검증하는 테스트가 없으면 `[Minor]`로 요청한다. 테이블 기반 테스트가 자연스러운 경우 이를 사용한다. 구현을 그대로 반복하는 테스트나 SSH·실제 ZooKeeper가 필요한 단위 테스트는 요구하지 않는다.
- **문서 동기화** — 사용자에게 보이는 인터페이스 (커맨드, 플래그, YAML 필드, 기본값, 에러 메시지 문구)를 바꿨는데 `docs/`와 `examples/`이 함께 갱신되지 않았으면 `[Major]`.

## 문서 변경 리뷰

대상: `*.md`, `docs/`, `README.md`, `*-sample-topology.yml`, `config_sample.yaml`. 문서만 바뀐 PR이라도 아래
기준으로 리뷰한다.
대상: `*.md`, `docs/`, `README.md`, `examples/*.yaml`. 문서만 바뀐 PR이라도 아래 기준으로 리뷰한다.

- **코드와의 불일치가 최우선이다.** 문서에 나오는 커맨드, 하위 커맨드, 플래그, YAML 필드명, 기본값, 경로를 실제 코드에서 Grep으로 확인한다.

| 문서에 적힌 것 | 확인할 코드 |
|---|---|
| 커맨드 / 플래그 | `cmd/` 아래 cobra 정의 |
| YAML 스키마 | `internal/topology/` 구조체의 yaml 태그 |
| znode 경로 | `internal/const.go` |
| 메타데이터 경로 | `internal/store/` |
| 설정 파일 키 | `internal/config.go` |

존재하지 않는 커맨드·플래그·필드가 문서에 있으면 `[Major]`다.
- 문서의 예제 명령이 실제로 실행 가능한 형태인지 확인한다 (인자 개수가
`cobra.ExactArgs` 등과 맞는지).
| 문서에 적힌 것 | 확인할 코드 |
|-----------------|-----------------------------------------|
| 커맨드 / 플래그 | `cmd/` 아래 cobra 정의 |
| YAML 스키마 | `internal/topology/` 구조체의 yaml 태그 |
| znode 경로 | `internal/const.go` |
| 메타데이터 경로 | `internal/store/` |
| 설정 파일 키 | `internal/config.go` |

존재하지 않는 커맨드·플래그·필드가 문서에 있으면 `[Major]`다.

- 문서의 예제 명령이 실제로 실행 가능한 형태인지 확인한다 (인자 개수가 `cobra.ExactArgs` 등과 맞는지).
- 상대 링크가 실제 파일을 가리키는지 확인한다.
- 제품명 표기는 **Arcus**다 (`ARCUS` 아님). 위반 시 `[Minor]`.
- 오탈자와 깨진 마크다운 구조는 `[Minor]`로 묶어 한 코멘트에 정리한다.
- **문체·어투·문장 다듬기 제안은 하지 않는다.** 사실이 틀렸을 때만 지적한다.

## 출력 방법

- 구체적 코드 위치가 있는 지적은
`mcp__github_inline_comment__create_inline_comment`로 남긴다 (`confirmed: true`).
- 마지막에 `gh pr comment`로 한 줄 요약만 남긴다. 형식: `리뷰 완료 — Blocker N건, Major N건, Minor N건, Question N건`
GitHub Actions에서 실행할 때:

- 구체적 코드 위치가 있는 지적은 `mcp__github_inline_comment__create_inline_comment`로 남긴다 (`confirmed: true`).
- 지적이 있으면 마지막에 `gh pr comment`로 한 줄 요약을 남긴다. 형식: `리뷰 완료 — Blocker N건, Major N건, Minor N건, Question N건`
- 지적이 없으면 `gh pr comment`로 `지적 사항 없음`이라고만 남긴다.
- 리뷰 내용을 일반 메시지로 출력하지 말고 반드시 GitHub 코멘트로만 남긴다.

로컬에서 요청받아 실행할 때는 파일을 수정하거나 GitHub에 코멘트를 남기지 않고, 같은 형식의 리뷰 결과를 일반 응답으로 전달한다.
Loading
Loading