팀 도입 체크리스트 - 보안·비밀값·기록·평가
이 장에서 배우는 것
앞 장에서 작성자와 검토자의 역할을 나누고 작업 폴더를 격리했다. 이제 작은 팀이 그 작업 방식을 계속 유지할 수 있도록 도입 기준을 정한다. 사람이 주의하겠다는 약속만으로는 부족하다. 저장소에 기준을 남기고, 변경을 검토하고, 실제 실행 결과와 비교해야 한다.
이 장의 완성 프로그램은 저장소 설정을 읽어 통과·경고·실패를 출력하는 도입 점검기다. 실제 AI 도구나 외부 서비스는 호출하지 않는다. 임시 저장소에 일부러 문제가 있는 설정을 만들고, 어떤 항목을 고쳐야 하는지 보여 준다. 결과를 보고 설정을 수정하는 과정까지가 도입 점검의 목적이다.
- 허용 도구, 권한 기본값, 비밀값 처리, 로그 보관 기준을 문서로 정한다.
- 지침 파일 변경과 AI가 작성한 코드 변경에 각각 검토 기준을 적용한다.
- 되돌림과 검토 지적을 분모·관찰 기간과 함께 기록한다.
- 설정에서 확인한 사실과 실제 운영에서 확인해야 할 사실을 구분한다.
- 표준 라이브러리만으로 재현 가능한 도입 점검기를 실행하고 결과를 해석한다.
문제 상황
세 사람이 유지하는 Python 저장소에 터미널 AI 코딩 도구를 도입했다. 한 사람은 기능을 작성하고, 다른 사람은 테스트를 보강하며, 나머지 사람은 변경 사항을 검토한다. 처음에는 작업이 순조로웠다. 그러나 몇 주 뒤부터 코드보다 운영 방식에서 문제가 나타났다.
개발자마다 실행 권한이 달랐다. 한 사람은 변경 전에 확인을 받았지만 다른 사람은 넓은 권한을 기본값으로 두었다. 연결 오류를 분석하며 붙여 넣은 환경 설정에는 접근 토큰이 들어 있었다. 대화 기록을 어디까지 보관하는지도 합의하지 않았다. 지침 파일에는 “테스트가 오래 걸리면 생략해도 된다”라는 문장이 추가됐지만, 코드 검토자는 이를 일반 문서 수정으로 보고 지나쳤다.
성과 보고도 흔들렸다. 생성한 코드 줄 수는 늘었지만 검토자가 다시 작성한 부분은 집계하지 않았다. 되돌린 변경이 적다는 설명에는 관찰 기간이 없었다. 어제 병합한 변경과 한 달 동안 운영한 변경을 같은 표에서 비교하고 있었다.
팀의 요청: “도구별 개인 설정을 모두 통일하기보다, 저장소에서 지켜야 할 최소 기준부터 정하자. 기준을 위반하면 무엇을 고쳐야 하는지도 함께 보여 달라.”
이 요청에는 두 종류의 답이 필요하다. 하나는 누가 어떤 책임을 지는지 정하는 운영 기준이다. 다른 하나는 그 기준의 일부를 반복해서 확인하는 프로그램이다. 프로그램이 통과했다고 해서 운영 전체를 검증한 것은 아니다. 반대로 모든 판단을 사람에게 맡기면 같은 누락을 계속 발견하게 된다.
도입 기준을 저장소의 약속으로 만든다
도입 전에는 허용 범위와 책임자를 먼저 정한다. “업무에 AI를 써도 된다”는 문장은 범위가 너무 넓다. 어떤 도구와 계정 유형을 허용하는지, 어떤 저장소와 데이터를 다룰 수 있는지, 외부 연결을 누가 승인하는지까지 적어야 실제 판단에 쓸 수 있다. 설치를 허용하는 일과 저장소 내용을 외부로 보내는 일을 같은 승인으로 취급하지 않는다.
| 항목 | 정할 내용 | 확인할 증거 | 담당 |
|---|---|---|---|
| 허용 도구 | 도구·계정 유형·작업 범위 | 승인 목록과 검토일 | 도입 책임자 |
| 권한 기본값 | 읽기·쓰기·외부 연결의 경계 | 실제 실행 설정과 예외 기록 | 저장소 관리자 |
| 비밀값 | 주입 위치와 노출 대응 | 추적 파일 목록과 검사 결과 | 보안 담당자 |
| 로그 | 수집 범위·접근자·보관 기간 | 삭제 작업과 접근 기록 | 운영 담당자 |
작은 팀에서는 한 사람이 여러 역할을 맡을 수 있다. 그래도 결정 항목마다 책임자가 있어야 한다. “팀 전체가 담당한다”는 표현만 있으면 예외가 생겼을 때 누구에게 물어야 하는지 알 수 없다. 예외에는 사유, 범위, 종료 조건을 남긴다. 만료되지 않는 예외는 시간이 지나 기본 정책처럼 굳어진다.
권한 기본값은 작업에 필요한 최소 범위에서 출발한다. 읽기만 필요한 조사와 파일을 고치는 구현 작업을 구분하고, 외부 연결이나 넓은 파일 접근이 필요하면 해당 작업의 예외로 기록한다. 승인 질문이 적다는 사실과 실행 범위가 좁다는 사실은 서로 다르다. 승인 방식과 실행 제한을 함께 확인해야 한다.
| 도구 | 지침 파일 | 확인할 설정 | 팀의 검토 질문 |
|---|---|---|---|
| Claude Code | CLAUDE.md | 권한 모드와 도구 허용·차단 | 실행 가능한 작업이 승인 범위에 맞는가 |
| Gemini CLI | GEMINI.md | 사용 환경의 권한·실행 제한 | 공식 문서와 실제 설정을 대조했는가 |
| Codex CLI | AGENTS.md | 샌드박스와 승인 정책 | 파일 접근 범위와 승인 방식이 함께 적절한가 |
이 표의 도구 사실은 모두 2026년 10월 기준 예시다. Gemini 계열에서 Antigravity CLI를 사용하는 팀은 해당 도구의 규칙 파일 이름을 공식 문서에서 확인해야 한다. 이 장의 점검기는 Gemini CLI를 사용하는 가상 팀을 대상으로 한다. 제품의 설정 이름을 하나의 공통 설정으로 변환하는 프로그램이 아니라, 팀이 별도로 작성한 도입 기준을 점검하는 프로그램이다.
비밀값은 요청문에 붙여 넣기 전에 차단하는 편이 낫다. 예제에는 실제 자격 증명 대신 명시적인 가짜 값을 사용한다. 실행 시 필요한 비밀값은 승인된 경로로 주입하고, 도구가 읽을 수 있는 파일 범위를 따로 검토한다. 무시 파일에 등록했다는 사실만으로 이미 추적 중인 파일이나 과거 기록에서 값이 사라지지는 않는다. 노출됐다면 파일 수정과 별개로 해당 자격 증명을 폐기하거나 교체하는 절차가 필요하다.
로그에는 요청문과 응답뿐 아니라 도구가 읽은 코드, 명령 출력, 오류 내용이 포함될 수 있다. 원문 전체를 오래 보관하기보다 목적에 필요한 작업 식별자, 도구 식별자, 변경 위치, 검증 결과부터 정한다. 원문 보관이 필요하면 접근 범위와 삭제 기한을 함께 정한다. 민감한 내용이 섞였을 때의 삭제·교체 절차도 있어야 한다. 보관 기간을 문서에 적는 것과 실제로 삭제되는지는 별도로 확인한다.
정책 파일은 도입 기준을 재현하기 위한 입력이다. 정책 파일에 “비밀값 없음”이라고 적었다고 해서 비밀값이 없어진 것은 아니다. 자동 점검은 확인 가능한 증거를 사용하고, 확인하지 못한 범위를 결과 설명에 남겨야 한다. 이 구분이 없으면 점검 결과가 근거 없는 안심으로 바뀐다.
지침 변경과 코드 변경을 함께 검토한다
지침 파일은 다음 작업의 행동에 영향을 준다. 따라서 맞춤법 수정과 권한 확대를 같은 수준으로 검토하지 않는다. 테스트 생략, 외부 연결 허용, 비밀값 취급 변경처럼 행동 범위를 바꾸는 문장은 운영 기준의 변경으로 본다. 작성자는 변경 이유와 영향을 적고, 다른 사람이 변경 차이(diff)를 확인한다.
검토 순서는 단순하게 유지한다. 먼저 어떤 행동이 달라지는지 확인한다. 다음으로 상위 지침이나 다른 도구의 지침과 충돌하는지 살핀다. 마지막으로 작은 예제 작업에서 새 지침이 의도대로 적용되는지 실행해 본다. 검토 기록은 파일명만으로 남기지 않고, 가능하면 검토한 커밋이나 내용의 해시와 연결한다. 파일명이 같아도 내용은 달라질 수 있기 때문이다.
지침 변경 제안: “실패한 테스트는 무시한다”를 추가하는 대신, “실패한 테스트와 재현 명령을 보고하고 병합 판단을 검토자에게 넘긴다”로 수정한다.
AI가 작성한 풀 리퀘스트(pull request, PR)도 실행·테스트·변경 차이 검토를 거친다. 설명이 자연스럽다는 이유로 검토를 줄이지 않는다. 작성자가 누구든 실제 변경이 요구 사항을 충족하는지 확인하고, AI 사용 때문에 추가로 생길 수 있는 추정이나 누락을 살핀다.
| 검토 항목 | 확인 질문 | 필요한 증거 |
|---|---|---|
| 범위 | 요청 밖의 변경이 섞였는가 | 요구 사항과 전체 변경 차이 |
| 동작 | 정상 입력과 실패 입력을 처리하는가 | 실행 결과와 관련 테스트 |
| 보안 | 비밀값·권한·외부 연결이 달라졌는가 | 관련 설정과 변경 이유 |
| 유지보수 | 팀원이 수정 이유를 설명할 수 있는가 | 설계 이유와 남은 제약 |
테스트 수보다 테스트가 확인하는 성질이 중요하다. 함수 내부의 계산을 그대로 복사한 테스트는 같은 오류를 반복할 수 있다. 대표 입력의 기대값, 경계 조건, 실패 시 처리처럼 요구 사항에서 나온 기준을 사용한다. AI에게 검토를 맡길 수는 있지만, 다른 도구의 동의가 사람의 병합 책임을 대신하지는 않는다.
완성 코드의 지침 점검은 검토 목록에 파일명이 있는지만 확인한다. 이는 절차를 설명하기 위한 축소 모델이다. 실제 운영에서는 파일 내용이 바뀌었는데 이전 검토 기록이 남아 있는 경우까지 탐지해야 한다. 같은 이유로 “테스트 실행”이라는 설정값도 실제 실행 로그를 대신하지 못한다.
효과는 같은 조건의 기록으로 비교한다
생성한 줄 수나 요청 횟수는 활동량이다. 그것만으로 팀의 생산성이나 품질을 설명하기 어렵다. 짧은 수정으로 문제를 해결할 수도 있고, 많은 코드를 생성한 뒤 대부분 버릴 수도 있다. 효과 측정은 “더 많이 만들었는가”보다 “검토하고 운영할 수 있는 변경을 어떤 비용으로 전달했는가”에 가깝게 설계한다.
되돌린 변경 비율을 쓰려면 먼저 단위를 정한다. PR 단위인지 커밋 단위인지, 일부 수정도 되돌림에 포함하는지, 요구 사항 변경으로 인한 취소를 어떻게 분류하는지 기록한다. 관찰 기간을 채운 변경만 분모에 넣고, 되돌린 이유를 함께 남긴다. 이 값은 팀 내부의 변화 신호이며 다른 팀과 순위를 매기기 위한 점수가 아니다.
검토 지적 수도 해석이 필요하다. 지적이 늘어난 이유가 코드 품질 저하일 수도 있지만 검토가 더 꼼꼼해졌기 때문일 수도 있다. 표현 수정과 동작 오류를 구분하고, 반복 지적을 어떻게 셀지도 정한다. 변경 규모와 위험 수준이 다른 작업을 한 숫자로 섞지 않는다.
| 내부 지표 | 분모 또는 단위 | 함께 기록할 맥락 | 피할 해석 |
|---|---|---|---|
| 되돌린 변경 비율 | 관찰 기간을 채운 병합 PR | 되돌린 이유와 관찰 기간 | 낮으면 항상 우수하다 |
| 검토 지적 수 | PR과 지적 유형 | 변경 규모와 검토 기준 | 많으면 작성자가 부족하다 |
| 검토 소요 시간 | 동일 정의로 기록한 작업 시간 | 대기 시간과 실제 검토 시간 | 짧으면 검토가 충분하다 |
| 재작업 기록 | 합의한 변경 단위 | 요구 변경과 결함 수정의 구분 | 모든 재작업이 AI 탓이다 |
도입 전후 비교도 인과관계를 바로 증명하지 못한다. 같은 시기에 테스트가 개선되거나 작업 난도가 달라질 수 있다. 적은 표본에서는 수치를 크게 해석하지 말고 사례를 함께 읽는다. 개인별 성과 점수로 연결하면 기록을 줄이거나 작업을 쪼개는 행동을 유도할 수 있다. 지표의 목적은 사람을 평가하는 것이 아니라 팀의 절차를 수정하는 데 둔다.
이 장의 점검기는 지표를 계산하지 않는다. 지표를 해석하는 데 필요한 이름, 분모, 관찰 기간, 사유 기록 기준이 설정돼 있는지 확인한다. 기준이 빠졌다면 숫자를 출력하는 대신 경고를 낸다. 정의가 불분명한 숫자를 먼저 만드는 일을 피하기 위해서다.
완성 코드
다음 내용을 main.py로 저장한다. 입력 파일과 가상 지침 파일은 임시 폴더 안에 생성되며 실행이 끝나면 삭제된다. 외부 패키지, API 키, 네트워크 연결은 사용하지 않는다. 정책의 키는 이 예제를 위한 팀 내부 형식이며 세 도구의 공식 설정 키가 아니다.
통과는 이 점검기가 확인하는 조건을 충족했다는 뜻이다. 경고는 도입 책임자가 보완 여부를 판단해야 한다는 뜻이며, 실패는 예제에서 정한 도입 필수 조건을 충족하지 못했다는 뜻이다. 이 프로그램은 시연을 위해 결과를 모두 출력하고 정상 종료한다. 실패 건수를 프로세스 종료 상태로 바꾸는 자동화 연계는 별도 설계 대상이다.
import json
import tempfile
from collections import Counter
from pathlib import Path
APPROVED_TOOLS = {"Claude Code", "Gemini CLI", "Codex CLI"}
REQUIRED_GUIDES = ("CLAUDE.md", "GEMINI.md", "AGENTS.md")
SECRET_FILES = {".env", "credentials.json"}
def make_sample(root):
policy = {
"tools": ["Claude Code", "Gemini CLI", "Codex CLI"],
"permissions": {
"default": "unrestricted",
"exception_review": True,
},
"tracked_files": ["main.py", ".env", "AGENTS.md"],
"logs": {
"retention_days": 0,
"restricted_access": True,
"redact_secrets": True,
},
"reviewed_guides": ["CLAUDE.md", "AGENTS.md"],
"pr_review": {
"human_reviewer": True,
"test_evidence": True,
"diff_review": True,
},
"metrics": {
"names": ["revert_ratio", "review_findings"],
"denominator_defined": False,
"window_days": 14,
"reason_recorded": True,
},
"validation": {
"run_recorded": True,
"tests_recorded": True,
"diff_recorded": True,
},
}
path = root / "team-policy.json"
path.write_text(
json.dumps(policy, ensure_ascii=False, indent=2),
encoding="utf-8",
)
for name in REQUIRED_GUIDES:
(root / name).write_text(
"変更理由と検証結果を記録する。\n".replace(
"変更理由と検証結果を記録する。",
"변경 이유와 검증 결과를 기록한다.",
),
encoding="utf-8",
)
return path
def inspect_repository(root, policy):
results = []
def add(label, ok, level, fix):
status = "통과" if ok else level
direction = "유지" if ok else fix
results.append((status, label, direction))
tools = policy["tools"]
add(
"허용 도구",
bool(tools) and set(tools) <= APPROVED_TOOLS,
"실패",
"승인된 도구 목록으로 제한",
)
permissions = policy["permissions"]
add(
"권한 기본값",
permissions["default"] in {"read-only", "scoped-write"}
and permissions["exception_review"] is True,
"실패",
"최소 권한 기본값과 예외 검토 지정",
)
tracked_names = {
Path(name).name for name in policy["tracked_files"]
}
add(
"비밀값 파일",
not (tracked_names & SECRET_FILES),
"실패",
"추적 목록에서 분리하고 노출 여부 확인",
)
logs = policy["logs"]
add(
"로그 보관",
1 <= logs["retention_days"] <= 30
and logs["restricted_access"] is True
and logs["redact_secrets"] is True,
"경고",
"보관 기한·접근 제한·가림 처리 확인",
)
reviewed = set(policy["reviewed_guides"])
add(
"지침 검토",
all(
(root / name).is_file() and name in reviewed
for name in REQUIRED_GUIDES
),
"경고",
"필수 지침 파일의 존재와 검토 기록 확인",
)
pr_review = policy["pr_review"]
add(
"PR 검토",
all(
pr_review[key] is True
for key in (
"human_reviewer", "test_evidence", "diff_review"
)
),
"실패",
"사람 검토·테스트 증거·diff 검토 요구",
)
metrics = policy["metrics"]
add(
"효과 측정",
{"revert_ratio", "review_findings"} <= set(metrics["names"])
and metrics["denominator_defined"] is True
and metrics["window_days"] > 0
and metrics["reason_recorded"] is True,
"경고",
"분모·관찰 기간·사유 기록 기준 정의",
)
validation = policy["validation"]
add(
"검증 기록",
all(
validation[key] is True
for key in (
"run_recorded", "tests_recorded", "diff_recorded"
)
),
"실패",
"실행·테스트·diff 확인 기록 연결",
)
return results
def main():
with tempfile.TemporaryDirectory() as directory:
root = Path(directory)
path = make_sample(root)
policy = json.loads(path.read_text(encoding="utf-8"))
results = inspect_repository(root, policy)
print("팀 도입 점검")
for status, label, direction in results:
print(f"[{status}] {label}: {direction}")
counts = Counter(status for status, _, _ in results)
print(
f"합계: 통과 {counts['통과']} / "
f"경고 {counts['경고']} / 실패 {counts['실패']}"
)
decision = "보류" if counts["실패"] else "검토 가능"
print(f"도입 판정: {decision}")
if __name__ == "__main__":
main()
줄별 해설
가져오기 부분은 JSON 파일 처리, 임시 폴더 생성, 상태 집계, 경로 처리를 담당한다. 날짜나 난수는 사용하지 않는다. 임시 경로도 출력하지 않으므로 실행할 때마다 같은 결과가 나온다. 출력 순서는 집합의 순회 순서가 아니라 점검 항목을 추가한 순서로 정한다.
APPROVED_TOOLS는 승인된 도구 이름의 집합이다. 실제 설치 여부나 계정 권한을 확인하지는 않는다. REQUIRED_GUIDES는 이 가상 팀이 검토하기로 한 파일 목록이다. SECRET_FILES는 추적 목록에 있으면 실패로 처리할 파일 이름이다. 파일명 기반 검사는 제한적이며, 임의 파일에 들어간 비밀값까지 탐지하지 않는다.
make_sample()은 정책을 사전으로 만든 뒤 JSON으로 저장한다. 기록한 파일을 다시 읽기 때문에 메모리의 사전만 검사하는 예제와 달리 파일 입출력 경로도 거친다. 가상 지침 파일의 내용은 검토 대상이 존재한다는 상황을 만들기 위한 짧은 문장이다. 문자열 치환은 실행 결과에 영향을 주지 않지만 실제 코드에서는 한국어 문장을 직접 쓰는 편이 더 단순하다.
tracked_files는 실제 저장소에서 얻었다고 가정한 추적 파일 목록이다. 이 예제는 git을 실행하지 않으며 .env 파일 자체도 만들지 않는다. 실제 저장소에 적용하려면 목록을 저장소의 실제 추적 상태에서 수집하도록 바꿔야 한다. 사람이 적은 목록을 검사하는 것과 실제 저장소를 검사하는 것은 증거의 강도가 다르다.
inspect_repository() 안의 add()는 조건이 참이면 통과와 “유지”를 기록한다. 거짓이면 호출한 쪽에서 정한 심각도와 수정 방향을 기록한다. 조건 판정과 출력 문구를 한곳에 모아 결과 형식을 일정하게 유지한다. 실패해도 즉시 중단하지 않으므로 한 번 실행해서 여러 보완 항목을 볼 수 있다.
허용 도구 검사는 빈 목록을 거부하고 승인 목록의 부분집합인지 확인한다. 권한 검사의 read-only와 scoped-write는 예제 정책에서 정의한 값이다. 특히 scoped-write는 제품 옵션이 아니다. 실제 도구 설정과 연결할 때는 각 도구의 공식 문서에서 의미를 확인하고 대응 관계를 명시해야 한다.
비밀값 파일 검사는 경로의 마지막 이름을 비교한다. 따라서 설정 폴더 아래의 .env도 탐지한다. 그러나 대소문자 변형이나 다른 이름의 비밀값 파일은 놓칠 수 있다. 본문을 검사하지 않기 때문에 우연히 실제 값이 출력되는 위험은 줄지만, 탐지 범위 역시 좁다.
로그 검사의 1일부터 30일까지는 가상 팀이 정한 허용 범위다. 권장 표준이나 제품의 보관 정책을 뜻하지 않는다. 0은 이 예제에서 “기한 미정”이므로 경고다. 원문을 보관하지 않는 정책을 허용하려면 0에 여러 의미를 담지 말고 별도 상태로 표현하는 편이 낫다.
지침 검사는 파일 존재와 검토 목록 포함 여부를 함께 요구한다. PR 검사와 검증 기록 검사는 필수 키를 명시해 모든 값이 참인지 확인한다. 사전의 모든 값에 무작정 all()을 적용하면 빈 사전도 참이 되는 문제가 있다. 다만 현재 코드는 자체 생성한 올바른 입력 구조를 전제로 하므로, 외부 JSON을 받는 버전에는 자료형과 필수 키 검증을 추가해야 한다.
main()은 임시 폴더가 살아 있는 동안 검사하고 결과만 밖으로 가져온다. Counter는 상태별 개수를 센다. 실패가 하나라도 있으면 보류로 출력하며, 실패가 없더라도 “검토 가능”까지만 표시한다. 정책 점검이 실제 운영 승인까지 대신하지 않도록 판정 문구를 제한한 것이다.
실행 결과
저장한 폴더에서 다음 명령을 실행한다. Python 3.12 이상을 사용한다.
python3 main.py
팀 도입 점검
[통과] 허용 도구: 유지
[실패] 권한 기본값: 최소 권한 기본값과 예외 검토 지정
[실패] 비밀값 파일: 추적 목록에서 분리하고 노출 여부 확인
[경고] 로그 보관: 보관 기한·접근 제한·가림 처리 확인
[경고] 지침 검토: 필수 지침 파일의 존재와 검토 기록 확인
[통과] PR 검토: 유지
[경고] 효과 측정: 분모·관찰 기간·사유 기록 기준 정의
[통과] 검증 기록: 유지
합계: 통과 3 / 경고 3 / 실패 2
도입 판정: 보류
이는 코드에서 정한 예상 출력이다. 실제 사용 전에는 직접 실행하고 출력과 대조한다. 구문과 경고 확인에는 다음 명령을 사용할 수 있다. 성공하면 별도 출력 없이 끝난다.
python3 -W error -m py_compile main.py
컴파일 성공은 정책 판정의 정확성을 증명하지 않는다. 실행 결과를 확인한 뒤 정책 조건과 메시지의 변경 차이를 검토해야 한다. 예를 들어 권한 기본값을 read-only로 바꿨을 때 권한 항목만 통과하는지 확인한다. 한 번에 여러 설정을 바꾸면 어떤 변경이 결과에 영향을 줬는지 확인하기 어려워진다.
실무에서 자주 틀리는 것
빈 설정을 통과로 처리한다
다음은 잘못된 검사다. all()은 빈 입력에서도 참을 반환한다. 따라서 설정이 비어 있는데도 필수 검토가 갖춰졌다고 판정한다.
review = {}
ok = all(review.values())
print(ok)
고친 코드는 필수 키를 직접 확인한다. 값이 없으면 통과하지 않는다. 이 조각은 완성 코드와 별도로 실행할 수 있다.
review = {}
required = ("human_reviewer", "test_evidence", "diff_review")
ok = all(review.get(key) is True for key in required)
print(ok)
첫 코드는 True, 두 번째 코드는 False를 출력한다. 외부 입력을 받는 경우에는 누락을 단순한 실패로 표시할지, 입력 형식 오류로 별도 보고할지도 정해야 한다.
분모가 없는데 비율을 0으로 만든다
관찰 대상이 없는데 기본값 0을 넣으면 “되돌림이 없었다”는 인상을 준다. 다음 코드는 데이터가 없는 상태와 비율이 0인 상태를 구분하지 못한다.
reverted = 0
eligible = 0
ratio = reverted / eligible if eligible else 0.0
print(ratio)
고친 코드에서는 계산 불가를 별도 상태로 유지한다. 실제 지표에는 되돌린 건수가 분모보다 크지 않은지도 확인해야 한다.
reverted = 0
eligible = 0
if not 0 <= reverted <= eligible:
raise ValueError("집계 범위를 확인한다.")
ratio = reverted / eligible if eligible else None
print("계산 보류" if ratio is None else f"{ratio:.1%}")
계산 보류는 성과가 나쁘다는 뜻이 아니다. 판단에 필요한 관찰 대상이 아직 없다는 뜻이다. 이 상태를 숨기지 않아야 작은 표본에서 결론을 서두르지 않는다.
진단을 위해 설정 원문을 출력한다
다음 코드는 예제의 가짜 값만 출력하지만, 같은 습관을 실제 설정에 적용하면 로그에 비밀값을 복사할 수 있다.
settings = {"token": "DEMO_ONLY", "retention_days": 14}
print(settings)
고친 코드는 진단에 필요한 필드만 골라 출력한다. 모든 키를 출력하고 일부 이름을 가리는 방식보다 출력 범위가 분명하다.
settings = {"token": "DEMO_ONLY", "retention_days": 14}
summary = {"retention_days": settings["retention_days"]}
print(summary)
예외 메시지나 외부 명령의 오류 출력에도 값이 섞일 수 있다. 정상 출력만 줄여 놓고 오류 시 원문 전체를 출력하면 같은 문제가 남는다.
실패가 없으면 검토를 끝낸다
경고를 모두 무시하면서 도입 승인으로 표시하면 자동 점검의 범위를 넘어선다. 다음 코드는 경고가 세 건이어도 승인 문자열을 만든다.
failures = 0
warnings = 3
decision = "승인" if failures == 0 else "보류"
print(decision)
고친 코드는 결과 수준을 구분한다. 경고를 받아들일 경우에는 담당자와 근거를 남긴다.
failures = 0
warnings = 3
if failures:
decision = "보류"
elif warnings:
decision = "경고 검토 필요"
else:
decision = "운영 증거 확인 단계"
print(decision)
설정이 모두 통과해도 로그 삭제가 실제로 동작하는지, 검토 기록이 해당 변경과 연결되는지, 권한 제한이 실행 환경에 반영됐는지는 추가로 확인해야 한다.
한눈에 보기
| 영역 | 프로그램이 확인한 것 | 사람이 확인할 것 |
|---|---|---|
| 도구·권한 | 승인 목록과 기본값 | 실제 실행 설정과 예외 범위 |
| 비밀값 | 추적 목록의 특정 파일명 | 내용·과거 기록·노출 대응 |
| 로그 | 기한과 접근 제한의 선언 | 수집 범위와 실제 삭제 결과 |
| 지침 | 파일 존재와 검토 목록 | 내용 변경과 검토 대상의 일치 |
| PR | 검토 요건과 기록의 선언 | 실행·테스트·전체 변경 차이 |
| 효과 | 측정 기준의 존재 | 표본·작업 성격·사유의 해석 |
팀 도입의 기준은 도구를 얼마나 자주 켜는지가 아니다. 어떤 범위에서 실행했는지, 무엇을 바꿨는지, 어떤 근거로 받아들였는지를 다시 확인할 수 있어야 한다. 점검기는 반복 확인을 맡고, 팀은 기준의 적절성과 실제 증거를 검토한다.
연습 문제
- 샘플에서 권한 기본값만 read-only로 바꾼다. 어떤 항목과 합계가 달라지는지 실행 전에 적고 실제 출력과 비교한다.
- tracked_files에 config/.env를 넣었을 때 비밀값 파일 검사가 실패하는 이유를 설명한다. 이 검사로 발견할 수 없는 사례도 두 가지 적는다.
- 검토 목록에 파일명만 저장하는 방식의 한계를 설명하고, 지침 내용이 바뀌면 재검토를 요구하는 기록 형식을 제안한다.
- 관찰 기간을 채운 PR이 8건이고 그중 결함 때문에 되돌린 PR이 2건이라고 가정한다. 비율을 구하고, 이 수치만으로 도구의 효과를 판단할 수 없는 이유를 설명한다. 숫자는 연습용 가상 자료다.
정답과 해설
- 권한 기본값 항목이 실패에서 통과로 바뀐다. 예외 검토 값이 이미 참이므로 다른 조건을 고칠 필요는 없다. 합계는 통과 4, 경고 3, 실패 1이다. 비밀값 파일 항목이 계속 실패하므로 도입 판정은 보류다.
- Path(name).name은 경로의 마지막 이름을 반환한다. config/.env에서도 .env를 얻으므로 금지 목록과 일치한다. 다른 이름의 파일에 들어간 토큰과 이미 삭제된 파일의 과거 기록은 발견하지 못한다. 실제 내용 검사와 기록 검토는 별도 절차가 필요하다.
- 파일명은 같아도 내용이 달라질 수 있다. 파일 경로, 내용 해시, 검토자, 검토한 커밋을 함께 기록할 수 있다. 현재 내용의 해시와 검토 기록의 해시가 다르면 재검토 대상으로 표시한다. 해시는 내용의 동일성을 확인하며 검토의 충분성까지 증명하지는 않는다.
- 2를 8로 나눈 25%다. 다만 표본이 작고, 변경 규모·작업 난도·검토 방식이 함께 주어지지 않았다. 도구를 쓰지 않은 유사 작업과의 비교 조건도 없다. 이 수치는 결함 사례를 찾아 절차를 개선하는 신호로 사용하며, 원인을 도구 하나로 단정하지 않는다.