AI 개발 · 기본
바이브 코딩 실습 - 작은 서비스 만들기
배포 전 점검과 회고 - 설정 분리·되돌리기 계획·작업 기록
설정과 비밀값을 코드에서 분리, 실행 방법 문서화, 배포 전 점검표(테스트·오류 처리·로그·백업), 리허설과 되돌리기 계획, 작업 기록으로 회고하기(무엇을 맡겼고 무엇을 판단했나), 완성 코드는 프로젝트 상태(가상 데이터)를 점검표로 검사해 통과·보류 항목을 출력한다
개발자KR · 원고 갱신
이 장에서 배우는 것
앞 장에서 구조를 정리한 공간 예약 서비스를 다른 사람이 실행할 수 있는 상태로 마무리한다. 함수가 잘 나뉘고 테스트가 통과해도 바로 배포할 수 있는 것은 아니다. 실행할 때 필요한 설정이 어디에 있는지, 오류가 나면 무엇을 확인할지, 저장한 예약을 어떻게 복구할지까지 정해져 있어야 한다. 이 장에서는 실제 서버에 접속하지 않고 가상의 프로젝트 상태를 검사한다. 검사 결과는 통과와 보류로 나누어 출력한다.
배포는 준비한 프로그램을 사용자가 이용할 환경에 놓는 작업이다. 여기서는 그 직전의 판단을 연습한다. 검사를 자동화하면 빠뜨리는 항목을 줄일 수 있지만, 프로그램이 검사하지 못한 운영 조건까지 확인한 것으로 여기면 안 된다. 마지막 결정에는 실행 결과를 읽고 남은 문제를 설명하는 사람의 판단이 필요하다.
- 일반 설정과 비밀값을 코드에서 분리하고, 예제 설정과 실제 설정의 역할을 구분한다.
- 실행 방법과 배포 전 점검 항목을 문서로 남긴다.
- 테스트, 오류 처리, 로그, 백업을 확인하여 통과와 보류를 출력한다.
- 임시 데이터베이스로 복구를 리허설하고 되돌리기 순서를 정한다.
- 작업 기록을 읽으며 AI에 맡긴 일과 직접 판단한 일을 구분한다.
문제 상황
동아리 운영자가 다음 모임부터 공간 예약 서비스를 함께 쓰자고 제안했다. 개발자의 컴퓨터에서는 예약 등록과 목록 조회가 잘 된다. 그런데 실행 방법은 대화 기록에 흩어져 있다. 데이터베이스 경로는 코드 안에 들어 있고, 오류가 났을 때 화면에 어떤 메시지가 나오는지 기록되어 있지 않다. 예약을 담은 파일은 한 번 복사했지만, 그 복사본으로 실제 복구가 되는지는 확인하지 않았다.
운영자는 “테스트가 통과했으니 오늘부터 써도 되는가”라고 묻는다. 테스트는 예약 규칙이 기대대로 동작한다는 근거다. 그러나 저장 위치, 실행 방법, 오류 대응, 복구 가능성은 서로 다른 질문이다. 예약 충돌 테스트만으로 이 질문에 모두 답할 수는 없다. 점검표에는 항목마다 통과 기준과 확인 근거를 함께 적어야 한다.
AI에는 빠진 항목을 찾아 점검 프로그램의 초안을 만들게 할 수 있다. 다만 “배포할 수 있다”라는 답만 받아서는 판단 근거가 남지 않는다. 검사 대상과 통과 조건을 먼저 정하고, 생성된 코드를 실행한 뒤 결과가 실제 조건과 일치하는지 확인해야 한다.
공간 예약 서비스의 배포 전 점검 프로그램을 작성하라. 외부 접속 없이 임시 파일만 사용하라. 일반 설정과 비밀값의 전달 경로를 나누고, 테스트·오류 처리·로그·백업·복구 리허설의 결과를 출력하라. 오류 처리와 로그에는 일부러 미완료 상태를 남겨 보류가 표시되게 하라. 보류를 통과로 바꾸기 위해 검사 조건을 약하게 만들지 말라.
이 요청에는 확인해야 할 실패 사례가 들어 있다. AI가 만든 결과에서 모든 줄이 통과한다면 오히려 요청과 어긋났을 가능성을 살펴야 한다. 이 장의 목표는 초록색 표시를 늘리는 것이 아니라, 아직 끝내지 않은 일을 결과에서 알아볼 수 있게 만드는 것이다.
설정과 비밀값을 나누고 실행 방법을 고정한다
설정(configuration)은 실행 환경에 따라 달라지는 값이다. 데이터베이스 파일 경로, 로그 수준, 서비스 이름이 예다. 비밀값(secret)은 노출되면 곤란한 인증 정보다. 비밀번호나 외부 서비스의 인증 토큰이 여기에 해당한다. 두 종류 모두 함수 안에 직접 박아 넣지 않지만, 보관과 공유 기준은 다르게 정한다.
일반 설정은 별도 파일로 읽을 수 있다. JSON은 문자열, 숫자, 목록 등을 텍스트로 표현하는 형식이며 Python의 json 모듈로 읽는다. 예제 설정에는 필요한 항목 이름과 안전한 기본값을 적는다. 실제 운영 경로를 예제와 구분하면 다른 사람이 자신의 환경에 맞게 실행하기 쉽다.
비밀값은 공유하는 설정 예제에 넣지 않는다. 환경 변수(environment variable)는 프로그램을 실행하는 환경에서 이름과 값을 전달하는 방법이다. 실제 프로그램에서는 os.environ으로 읽을 수 있다. 환경 변수도 그 자체로 보관 안전성을 보장하지 않는다. 터미널 기록이나 실행 환경을 누가 볼 수 있는지 따로 관리해야 한다.
이번 프로그램은 실제 환경 변수를 읽지 않는다. 독자의 컴퓨터 설정에 따라 결과가 달라지지 않도록, 환경 변수를 흉내 낸 사전에서 가짜 비밀값을 전달한다. 인증이나 네트워크 호출에는 사용하지 않는다. 설정 파일에서 비밀값을 읽는 경로와 분리되어 있는지만 검사한다. 이 검사는 실제 저장소에 비밀값이 없는지 확인하는 검사까지 대신하지 않는다.
| 대상 | 이 장의 전달 방법 | 확인할 것 |
|---|---|---|
| 일반 설정 | 임시 JSON 파일 | 필수 항목과 값이 맞는가 |
| 비밀값 | 가상 환경 변수 사전 | 설정 파일에 섞이지 않았는가 |
| 실행 방법 | 임시 README 파일 | 준비 조건과 실행 명령이 있는가 |
| 운영 기록 | 가상 로그와 작업 기록 | 실행 사건과 판단 근거를 구분했는가 |
실행 문서에는 Python 버전, 실행 명령, 설정 항목, 데이터 저장 위치, 점검 결과를 읽는 방법을 적는다. “개발자에게 물어본다”는 문장은 실행 방법이 아니다. 새로 준비한 환경에서도 문서만 보고 같은 절차를 따라갈 수 있어야 한다. 문서의 명령은 실제로 실행해서 확인한다.
이 실습에서는 별도의 파일을 독자가 준비하지 않도록 main.py가 임시 설정 파일과 실행 문서를 만든다. 이는 외부 파일을 읽는 과정을 한 파일 안에서 재현하기 위한 장치다. 운영에서도 프로그램이 자신의 설정과 문서를 매번 만들어야 한다는 뜻은 아니다. 실제 서비스를 마무리할 때는 설정 예제와 실행 문서를 프로젝트에 따로 두고 관리한다.
점검표와 복구 리허설로 배포 여부를 판단한다
점검표(checklist)는 해야 할 일을 나열하는 데서 그치지 않는다. “백업 있음”보다 “복사본을 열어 예약 행이 원본과 같은지 확인함”이 판단에 도움이 된다. 백업(backup)은 복구에 사용할 데이터 사본이다. 파일이 존재해도 읽을 수 없거나 필요한 데이터가 빠져 있으면 복구 근거로 부족하다.
리허설(rehearsal)은 실제 작업 전에 절차를 시험하는 것이다. 이 장에서는 원본 데이터베이스를 만든 뒤 SQLite의 백업 기능으로 사본을 만든다. 원본에 시험용 예약을 추가하고, 사본을 새 데이터베이스로 복원한다. 복원한 예약 목록이 변경 전 목록과 같은지 비교한다. 모든 파일은 임시 폴더에 있으므로 독자의 실제 예약 파일을 건드리지 않는다.
되돌리기(rollback)는 문제가 생겼을 때 정해 둔 이전 상태로 돌아가는 작업이다. 코드만 이전 것으로 바꿔도 데이터 형식이 달라졌다면 서비스가 실행되지 않을 수 있다. 따라서 계획에는 이전 코드와 맞는 설정 및 데이터가 함께 필요하다. 변경 후 들어온 새 예약을 어떻게 보존하거나 처리할지도 결정해야 한다.
여기서의 복구 실험은 배포 전 기준 데이터를 되살리는 제한된 연습이다. 배포 뒤 들어온 예약을 보존하는 절차는 포함하지 않는다. 실제 운영에서는 쓰기 중단 시점, 백업 이후의 변경분, 복구 담당자, 이용자 안내를 별도로 정해야 한다. 데이터가 바뀌는 작업은 연습에 성공했다는 이유만으로 즉시 실행하지 않는다.
점검 결과의 보류는 아직 사용자가 겪을 문제를 설명하거나 확인하지 못했다는 뜻이다. 이번 가상 상태에서는 오류 응답 검토가 완료되지 않았고 로그에 사건 식별자가 빠져 있다. 사건 식별자는 같은 요청이나 작업의 기록을 연결하는 표식이다. 오류가 난 작업을 관련 로그에서 찾아갈 수 있도록 사용한다.
로그(log)는 실행 중 일어난 사건의 기록이다. 작업 기록은 개발 중 누가 무엇을 바꾸고 어떤 근거로 판단했는지를 담는다. 두 기록은 목적이 다르다. 로그에는 비밀값과 예약자의 불필요한 개인정보를 남기지 않는다. 작업 기록에는 “AI가 해결함” 대신 요청한 범위, 검토한 변경, 실행한 검사와 남은 결정을 적는다.
작업 기록으로 맡긴 일과 판단한 일을 돌아본다
회고는 결과가 좋았는지 나빴는지만 평가하는 시간이 아니다. 어떤 일을 자동화했고, 어느 지점에서 사람이 기준을 정했으며, 무엇이 확인되지 않았는지 되짚는 과정이다. 이번 작업에서 AI에 맡길 수 있는 일은 점검표 초안, 파일 읽기 코드, 결과 출력 형식의 제안이다. 통과 기준을 정하고 근거가 충분한지 판단하는 일은 작성자가 책임지고 확인한다.
차이 비교(diff)는 수정 전후의 코드에서 달라진 부분을 보는 것이다. 보류 항목을 해결하는 수정이라면 관련 기능이나 검증 근거가 바뀌어야 한다. 조건문을 지워서 통과시키거나, 테스트가 확인하던 기대값을 이유 없이 바꾸었다면 변경 목적을 다시 따져야 한다. 실행 결과와 함께 차이 비교를 읽어야 이런 수정을 알아볼 수 있다.
| 작업 | AI에 맡긴 범위 | 직접 확인한 근거 | 남은 판단 |
|---|---|---|---|
| 설정 분리 | 파일 읽기 초안 | 필수 값 검사와 변경 비교 | 운영 값의 관리 담당자 |
| 예약 테스트 | 사례 제안 | 충돌·경계·다른 공간 검사 | 추가할 사용 사례 |
| 복구 연습 | 복사와 비교 코드 | 복원한 예약 목록 일치 | 새 예약의 보존 방법 |
| 배포 판단 | 결과 정리 | 보류 두 항목의 근거 | 이번 배포 보류 |
짧은 기록도 나중에 다시 판단할 수 있다면 가치가 있다. “로그 수정 완료”보다 “사건 식별자가 없는 상태를 보류로 출력하는지 실행해 확인했다”가 더 구체적이다. 아직 로그 생성 기능을 고치지 않았다면 완료라고 적지 않는다. 점검 프로그램을 만든 것과 서비스의 미완료 기능을 해결한 것은 별개의 작업이다.
완성 코드
다음 코드를 main.py로 저장한다. 필요한 예약 규칙과 테스트를 함께 포함하므로 앞 장의 파일은 필요 없다. 시작과 끝 시각은 비교하기 쉬운 고정된 정수로 표현한다. 프로그램은 임시 폴더에 설정, 문서, 데이터베이스를 만들고 점검한 다음 삭제한다. 출력에는 임시 경로나 현재 시각을 넣지 않는다.
import io
import json
import sqlite3
import tempfile
import unittest
from pathlib import Path
def overlaps(old, new):
return (
old["room"] == new["room"]
and old["start"] < new["end"]
and new["start"] < old["end"]
)
class ReservationTests(unittest.TestCase):
def test_conflict(self):
old = {"room": "A", "start": 10, "end": 12}
new = {"room": "A", "start": 11, "end": 13}
self.assertTrue(overlaps(old, new))
def test_touching(self):
old = {"room": "A", "start": 10, "end": 12}
new = {"room": "A", "start": 12, "end": 14}
self.assertFalse(overlaps(old, new))
def test_other_room(self):
old = {"room": "A", "start": 10, "end": 12}
new = {"room": "B", "start": 11, "end": 13}
self.assertFalse(overlaps(old, new))
def run_tests():
suite = unittest.defaultTestLoader.loadTestsFromTestCase(
ReservationTests
)
result = unittest.TextTestRunner(stream=io.StringIO()).run(suite)
return result.wasSuccessful(), result.testsRun
def rows(connection):
return connection.execute(
"SELECT room, start, end FROM reservations "
"ORDER BY room, start, end"
).fetchall()
def rehearse(database_path, folder):
backup_path = folder / "backup.sqlite3"
restored_path = folder / "restored.sqlite3"
with sqlite3.connect(database_path) as source:
source.execute(
"CREATE TABLE reservations "
"(room TEXT, start INTEGER, end INTEGER)"
)
source.execute(
"INSERT INTO reservations VALUES (?, ?, ?)",
("A", 10, 12),
)
with sqlite3.connect(database_path) as source:
before = rows(source)
with sqlite3.connect(backup_path) as backup:
source.backup(backup)
with sqlite3.connect(backup_path) as backup:
backup_ok = rows(backup) == before
with sqlite3.connect(database_path) as source:
source.execute(
"INSERT INTO reservations VALUES (?, ?, ?)",
("B", 14, 16),
)
with sqlite3.connect(database_path) as source:
changed = rows(source) != before
with sqlite3.connect(backup_path) as backup:
with sqlite3.connect(restored_path) as restored:
backup.backup(restored)
restore_ok = changed and rows(restored) == before
return backup_ok, restore_ok
def inspect_project(folder):
config_path = folder / "config.json"
config_path.write_text(
json.dumps({
"service_name": "공간 예약",
"database_file": "reservations.sqlite3",
"log_level": "INFO",
}, ensure_ascii=False),
encoding="utf-8",
)
config = json.loads(config_path.read_text(encoding="utf-8"))
fake_environment = {"RESERVATION_TOKEN": "demo-token"}
secret = fake_environment.get("RESERVATION_TOKEN", "")
secret_ok = bool(secret) and "token" not in config
readme_path = folder / "README.txt"
readme_path.write_text(
"Python 3.12 이상\n"
"실행: python3 main.py\n"
"설정: config.json의 서비스 이름, DB 파일, 로그 수준\n"
"데이터: 점검용 임시 폴더, 종료 후 삭제\n"
"판단: 보류가 있으면 배포 보류\n",
encoding="utf-8",
)
readme = readme_path.read_text(encoding="utf-8")
docs_ok = all(
text in readme
for text in ("Python 3.12", "python3 main.py", "설정:", "데이터:", "판단:")
)
tests_ok, test_count = run_tests()
backup_ok, restore_ok = rehearse(
folder / config["database_file"], folder
)
state = {
"error_response_reviewed": False,
"logs": [{"level": "INFO", "message": "예약 점검 시작"}],
"rollback_steps": ["쓰기 중단", "이전 코드 선택", "데이터 복구", "재점검"],
}
logs_ok = all(
bool(entry.get("event_id"))
and secret not in json.dumps(entry, ensure_ascii=False)
for entry in state["logs"]
)
rollback_ok = restore_ok and state["rollback_steps"] == [
"쓰기 중단", "이전 코드 선택", "데이터 복구", "재점검"
]
return [
("설정", config == {
"service_name": "공간 예약",
"database_file": "reservations.sqlite3",
"log_level": "INFO",
}, "필수 설정과 고정 예제 값 확인"),
("비밀값", secret_ok, "가상 환경에서 전달, 설정에 token 없음"),
("실행 문서", docs_ok, "버전·명령·설정·데이터·판단 기준 확인"),
("테스트", tests_ok and test_count == 3, f"예약 규칙 {test_count}개 실행"),
("오류 처리", state["error_response_reviewed"], "오류 응답 검토 미완료"),
("로그", logs_ok, "사건 식별자 누락"),
("백업", backup_ok, "사본의 예약 목록 일치"),
("복구 리허설", restore_ok, "시험 변경 후 기준 데이터 복원"),
("되돌리기 계획", rollback_ok, "순서 확인과 데이터 복구 연습 완료"),
]
def main():
with tempfile.TemporaryDirectory() as temporary:
checks = inspect_project(Path(temporary))
print("공간 예약 서비스 배포 전 점검")
for name, passed, reason in checks:
status = "통과" if passed else "보류"
print(f"[{status}] {name}: {reason}")
passed_count = sum(passed for _, passed, _ in checks)
held_count = len(checks) - passed_count
print(f"요약: 통과 {passed_count}개, 보류 {held_count}개")
decision = "배포 보류" if held_count else "점검 범위 내 배포 준비 완료"
print(f"판단: {decision}")
print("회고: AI에는 점검 초안을 맡기고, 기준·실행 결과·변경 비교는 직접 확인한다.")
if __name__ == "__main__":
main()
줄별 해설
가져오기 부분. io는 테스트 실행 기록을 메모리에 받아 두는 데 쓴다. json은 설정 파일을 읽고 쓰며, sqlite3는 데이터베이스와 사본을 만든다. tempfile은 임시 폴더를 관리한다. unittest는 테스트를 실행하는 표준 라이브러리다. Path는 파일 경로를 다루는 객체다. 설치할 외부 패키지는 없다.
overlaps 함수. 두 예약의 공간이 같고 시간 구간이 서로 겹치는지 반환한다. 시작은 포함하고 끝은 포함하지 않는 기준이므로, 한 예약이 끝나는 시각에 다른 예약이 시작하면 충돌하지 않는다. 입력값 자체의 유효성 검사까지 수행하는 함수는 아니다. 이번 장은 배포 점검을 다루므로 이미 유효한 고정 예제 값으로 규칙을 확인한다.
ReservationTests와 run_tests. TestCase를 상속한 클래스는 unittest가 실행할 테스트를 모은다. 상속은 기존 클래스의 기능을 이어받는 방식이다. 세 메서드는 겹치는 예약, 맞닿는 예약, 다른 공간의 예약을 검사한다. assertTrue와 assertFalse는 기대한 참·거짓과 다르면 테스트 실패로 기록한다. run_tests는 성공 여부와 실행 개수를 반환한다. 상세 기록은 StringIO에 담아 두므로 테스트 도구의 시간 표시가 최종 출력에 섞이지 않는다.
rows 함수. 필요한 열만 조회하고 정렬한다. 데이터베이스가 우연히 같은 순서로 행을 돌려준다고 가정하지 않기 위해 ORDER BY를 붙인다. 이번 예제는 행이 적으므로 목록 전체를 비교한다. 데이터가 커지면 모든 행을 메모리에 올리는 방식의 비용을 별도로 검토해야 한다.
rehearse의 첫 연결. 예약 표를 만들고 기준 예약 한 건을 저장한다. 물음표는 SQL에 값을 전달할 자리를 나타낸다. 값은 별도 튜플로 전달한다. with 블록을 정상적으로 벗어나면 sqlite3 연결의 변경 내용이 확정된다. 예외가 발생하면 해당 작업의 변경 내용을 취소한다.
사본 만들기와 비교. 확정된 데이터를 새 연결에서 읽어 before에 저장한다. source.backup은 대상 연결에 데이터베이스 내용을 복사한다. 사본을 다시 열어 같은 행이 있는지 비교한 결과가 backup_ok다. 사본의 파일 이름만 확인하는 것보다 한 단계 더 구체적인 근거를 얻는다.
시험 변경과 복원. 원본에 다른 공간의 예약을 한 건 추가한다. changed는 원본이 실제로 달라졌는지 확인한다. 이어 사본을 restored.sqlite3로 복사한다. restore_ok는 시험 변경이 있었고 복원 목록이 기준 목록과 같을 때만 참이다. 원본 파일을 덮어쓰지 않고 새 파일로 복원하여 연습 결과를 비교한다.
연결의 수명. sqlite3 연결의 with 구문은 변경 내용의 확정과 취소를 다루며 연결을 닫는 기능은 아니다. 이 예제의 연결은 함수가 끝난 뒤 남겨 두지 않고, macOS/Linux에서 임시 파일을 정리한다. 오래 실행하는 서비스에서는 연결을 명시적으로 닫거나 contextlib.closing으로 닫는 범위를 정해야 한다. 파일 정리와 변경 내용 확정은 구분해서 이해한다.
inspect_project의 설정 부분. 임시 JSON 파일을 쓰고 다시 읽는다. 뒤의 설정 항목은 읽은 사전이 정해 둔 예제와 같은지 확인한다. 운영 설정을 검사한다면 서비스 이름을 고정 문자열과 비교하기보다 필수 항목, 값의 형식, 허용 범위를 검사해야 한다. 이번 비교는 결정적인 실습 자료가 예상대로 읽혔는지 확인하기 위한 것이다.
비밀값과 문서 부분. 가상 환경에서 값을 읽고 설정의 token 항목에 들어 있지 않은지 확인한다. 이 조건은 알려진 한 항목만 확인한다. 다른 이름으로 숨긴 비밀값까지 찾아내는 검사는 아니다. docs_ok 역시 필요한 문구의 존재만 확인하므로 문서 전체의 정확성은 직접 읽고 실행하여 확인한다.
state와 점검 목록. state는 아직 해결하지 않은 프로젝트 상태를 나타낸다. 오류 응답 검토는 거짓이고 로그에는 event_id가 없다. logs_ok는 사건 식별자와 가짜 비밀값의 미포함 여부를 검사한다. 반환 목록의 각 항목은 이름, 통과 여부, 근거로 구성된다. 근거 문장은 이번 고정 상태에 맞춰 쓴 것이다. 여러 프로젝트를 검사하는 도구로 확장한다면 실패 원인에 따라 근거 문장도 만들어야 한다.
main의 출력 부분. 임시 폴더가 정리된 뒤에는 점검 결과만 남는다. 참을 더하면 통과 개수를 셀 수 있다. 보류가 하나라도 있으면 배포 보류를 출력한다. 마지막 회고 문장은 이번 작업의 책임 구분을 요약한다. 실제 변경 비교가 자동으로 수행되었다는 뜻은 아니다. 파일 끝의 조건문은 main.py를 직접 실행했을 때 main을 호출한다.
실행 결과
macOS/Linux에서 다음 명령으로 실행한다. 첫 명령은 코드를 실행하지 않고 문법을 검사한다. 문법 검사가 성공하면 출력 없이 끝난다. 두 번째 명령이 점검 결과를 출력한다. 문법 검사로 생성되는 캐시 파일은 예약 데이터베이스와 관계가 없다.
python3 -W error -m py_compile main.py
python3 main.py
예상 출력은 다음과 같다. 테스트가 통과해도 오류 처리와 로그가 보류이므로 전체 판단은 배포 보류다. 이 프로그램은 가상 상태를 검사하는 학습 도구이므로 보류 결과를 출력한 뒤 정상적으로 종료한다.
공간 예약 서비스 배포 전 점검
[통과] 설정: 필수 설정과 고정 예제 값 확인
[통과] 비밀값: 가상 환경에서 전달, 설정에 token 없음
[통과] 실행 문서: 버전·명령·설정·데이터·판단 기준 확인
[통과] 테스트: 예약 규칙 3개 실행
[보류] 오류 처리: 오류 응답 검토 미완료
[보류] 로그: 사건 식별자 누락
[통과] 백업: 사본의 예약 목록 일치
[통과] 복구 리허설: 시험 변경 후 기준 데이터 복원
[통과] 되돌리기 계획: 순서 확인과 데이터 복구 연습 완료
요약: 통과 7개, 보류 2개
판단: 배포 보류
회고: AI에는 점검 초안을 맡기고, 기준·실행 결과·변경 비교는 직접 확인한다.
확인할 때는 문법 검사, 실행, 출력 비교, 변경 비교를 각각 수행한다. 예컨대 AI가 로그 조건을 삭제했다면 출력은 달라지지만 사건 식별자 누락 문제는 그대로다. 코드의 변경이 서비스 상태를 개선했는지, 표시만 바꾸었는지 직접 확인한다. 이 원고의 예상 출력을 실제 실행 기록과 구분하여, 자신의 환경에서 얻은 결과를 작업 기록에 남긴다.
실무에서 자주 틀리는 것
비밀값까지 설정 출력에 포함한다
다음 코드는 가짜 토큰을 쓰지만, 실제 값을 넣으면 출력 기록에 남는다. 디버깅을 위해 설정 전체를 출력하는 습관이 원인이다.
config = {"service_name": "공간 예약", "token": "demo-token"}
print(config)
고친 코드는 비밀값을 별도 경로로 받고, 출력할 항목을 직접 고른다. 실제 환경 변수 대신 가상 사전을 사용하므로 그대로 실행할 수 있다.
config = {"service_name": "공간 예약"}
environment = {"RESERVATION_TOKEN": "demo-token"}
secret = environment.get("RESERVATION_TOKEN", "")
print({"service_name": config["service_name"]})
print("비밀값 준비:", bool(secret))
비밀값의 존재를 확인하는 출력과 비밀값 자체를 출력하는 것은 다르다. 실제 운영 로그에 무엇이 들어가는지도 함께 검토한다.
빈 검사 목록을 통과로 처리한다
all은 목록의 모든 값이 참인지 확인한다. 빈 목록에서는 참을 반환한다. 따라서 대상이 누락된 상태가 통과로 표시될 수 있다.
checks = []
ready = all(checks)
print("통과" if ready else "보류")
필수 검사 개수와 검사 결과를 함께 확인한다. 이 예제는 필수 항목이 두 개라는 작은 상황이다.
checks = []
required_count = 2
ready = len(checks) == required_count and all(checks)
print("통과" if ready else "보류")
항목이 많아지면 개수만 같아도 다른 검사가 들어갈 수 있다. 그때는 필수 검사 이름이 모두 있는지 확인한다. 같은 이유로 완성 코드의 로그 검사를 확장할 때는 로그가 한 건 이상 있는지도 조건에 넣는다.
예외를 숨기고 복구 성공을 출력한다
예외(exception)는 실행 중 정상 흐름을 이어갈 수 없을 때 전달되는 오류다. 다음 코드는 복원 검사가 실패해도 성공을 출력한다.
try:
raise ValueError("복원 데이터 불일치")
except ValueError:
pass
print("복구 통과")
실패를 상태에 반영하고 성공 조건을 충족했을 때만 통과로 출력한다.
restored_ok = False
try:
raise ValueError("복원 데이터 불일치")
except ValueError as error:
print("복구 보류:", error)
else:
restored_ok = True
print("통과" if restored_ok else "보류")
예제에서는 정해 둔 오류 문장을 출력한다. 운영에서는 오류 객체에 경로나 민감한 값이 들어 있는지 검토하고, 이용자에게 보여 줄 메시지와 내부 기록을 구분한다. 완성 코드에서 뜻밖의 파일 오류가 발생하면 점검을 중단한다. 실행조차 끝나지 않은 점검을 통과로 해석하지 않는다.
보류를 없애려고 근거 없이 상태만 바꾼다
다음 코드는 검토하지 않은 상태를 바로 참으로 바꾼다. 점검표의 표시를 바꾸었을 뿐 오류 응답을 확인한 근거는 없다.
error_response_reviewed = False
error_response_reviewed = True
print("통과" if error_response_reviewed else "보류")
고친 코드는 기대하는 응답을 검사한 결과에서 상태를 계산한다. 아래 응답은 직접 만든 작은 검사용 자료이며, 실제 요청 처리 함수를 호출한 결과는 아니다.
expected = {"status": 400, "message": "시작 시각을 확인한다"}
actual = {"status": 400, "message": "시작 시각을 확인한다"}
error_response_reviewed = actual == expected
print("통과" if error_response_reviewed else "보류")
서비스에 적용할 때는 actual을 실제 요청 처리 함수의 반환값으로 바꾼다. 같은 사전을 양쪽에 적은 검사는 오류 처리 구현의 근거가 될 수 없다. AI가 제안한 검사에서 입력이 실제 기능을 통과하는지까지 살펴야 한다.
한눈에 보기
| 항목 | 이 장의 근거 | 운영에서 추가할 확인 |
|---|---|---|
| 설정과 비밀값 | 파일 읽기와 전달 경로 분리 | 실제 값의 보관 및 접근 관리 |
| 실행 문서 | 필수 문구 존재 | 새 환경에서 문서대로 실행 |
| 테스트 | 예약 규칙 세 사례 | 입력부터 저장까지 주요 흐름 |
| 오류와 로그 | 미완료 상태를 보류로 표시 | 실제 응답과 기록 내용 검토 |
| 백업과 복구 | 사본 조회와 기준 데이터 복원 | 새 예약 보존과 담당자 확인 |
| 작업 기록 | 맡긴 일과 판단의 구분 | 명령·결과·변경 근거 보관 |
이번 점검표의 통과는 각 행에 적힌 범위의 통과다. 전체 서비스의 모든 상황을 검사했다는 의미는 아니다. 보류를 해결한 뒤에도 검사 범위를 바꾸었거나 기능을 수정했다면 관련 테스트와 복구 연습을 다시 실행한다. 기록에는 실제로 확인한 범위를 적는다.
연습 문제
- state의 로그에 event_id를 추가하라. 오류 응답 검토 상태는 유지한다. 어떤 항목과 요약이 바뀌는지 먼저 적고, 실행 결과와 비교하라.
- 비밀값을 읽기만 하고 사용하는 인증 기능이 없다면, 이번 비밀값 검사가 무엇을 증명하고 무엇을 증명하지 못하는지 두 문장으로 설명하라.
- 복구 리허설에서 원본에 시험 예약을 추가하는 부분을 제거하면 restore_ok가 어떻게 되는지 설명하라. 검사 조건을 바꾸지 않고 실행하여 확인하라.
- “AI가 배포 준비를 끝냈다”라는 기록을 구체적인 작업 기록으로 다시 쓰라. 요청 범위, 실행한 확인, 직접 내린 판단, 남은 일을 포함하라.
정답과 해설
로그 사전에 event_id라는 이름으로 예약 내용과 무관한 고정 문자열을 추가한다. 예를 들어 점검 사건을 뜻하는 check-001을 사용한다. 가짜 비밀값은 로그에 없으므로 로그 항목이 통과로 바뀐다. 요약은 통과 8개, 보류 1개다. 오류 처리 항목이 남아 있어 판단은 배포 보류다. 사건 식별자를 넣는 것과 실제 서비스에서 같은 작업의 로그를 연결하는 기능은 별도로 확인한다.
이번 검사는 가상 환경에서 값이 전달되고 설정 사전에 token 항목이 없다는 것을 확인한다. 실제 인증의 성공, 저장소 전체의 비밀값 미포함, 운영 환경의 접근 관리까지 증명하지는 못한다. 점검표 이름보다 검사 코드의 조건을 읽어 범위를 설명하는 것이 핵심이다.
원본에 변경이 없으므로 changed가 거짓이 된다. restore_ok는 changed와 목록 일치를 함께 요구하므로 거짓이다. 복구 리허설과 되돌리기 계획이 추가로 보류되어 통과 5개, 보류 4개가 된다. 변경이 일어나지 않은 채 같은 데이터를 읽는 것만으로 복구 연습을 통과시키지 않도록 만든 조건이다.
다음과 같이 기록할 수 있다. “AI에는 임시 파일을 사용하는 점검 코드와 결과 형식의 초안을 요청했다. 문법 검사와 실행을 수행하고 예약 테스트 세 개, 백업 목록 비교, 복원 결과를 확인했다. 변경 비교에서 검사 조건이 빠지지 않았는지 검토했다. 오류 응답 검토와 로그 사건 식별자가 미완료여서 배포를 보류했다. 다음 작업은 실제 오류 응답을 검사하고 사건 식별자를 생성하는 기능을 확인하는 것이다.” 이 문장은 완료 범위와 남은 일을 구분하며, 실제 수행한 확인만 기록해야 한다.
서비스를 마무리할 때 남겨야 할 것은 코드만이 아니다. 실행 방법, 점검 근거, 복구 절차, 작업 기록이 함께 있어야 다음 사람이 같은 판단을 이어갈 수 있다. AI가 만든 초안을 실행하고 테스트하며 변경을 읽는 습관은 이 마지막 작업에서도 유지한다. 작은 서비스의 완성은 모든 칸을 통과로 만드는 데 있지 않다. 지금 사용할 수 있는 범위와 아직 해결해야 할 일을 근거와 함께 설명할 수 있는 상태로 남기는 데 있다.
READER FEEDBACK
질문·의견
내용에 관한 질문이나 더 나은 설명을 위한 의견을 남겨 주세요. 오탈자는 위의 제보 양식이 더 빨리 반영됩니다. 이 댓글은 원래 게시글과 같은 자리에 쌓입니다.
댓글 0
아직 댓글이 없습니다. 첫 댓글을 남겨 보세요.