AI 개발 · 기본
바이브 코딩 시대의 기본서 - 처음 AI 와 함께 만드는 프로그램
저장하고 되돌리기 - 버전을 남기는 습관
AI 가 한 번에 많이 바꿀 때 생기는 위험, 바꾸기 전 사본 남기기, 바뀐 줄만 보기(diff), git 의 기본 개념(커밋·되돌리기)을 비유로, 완성 코드는 difflib 로 두 버전의 차이를 보여 주고 임시 폴더에 버전 사본을 쌓았다가 되돌리는 스냅샷 도구
개발자KR · 원고 갱신
이 장에서 배우는 것
앞 장에서 예시 입력과 기대 출력을 비교해 프로그램이 맞게 동작하는지 확인했다. 확인을 마친 코드는 다음 작업의 출발점이 된다. 그런데 AI에게 수정을 부탁한 뒤 파일을 덮어쓰면, 방금 확인한 출발점이 사라질 수 있다. 새 코드가 실행되더라도 이전에 되던 기능이 빠졌을 수 있다. 바꾸기 전 상태를 남겨 두면 새 결과를 비교할 기준과 돌아갈 자리가 함께 생긴다.
이 장에서는 작은 파일의 사본을 남기고, 변경된 줄을 읽고, 저장한 내용으로 되돌리는 도구를 만든다. 실제 작업 폴더를 건드리지 않도록 프로그램이 임시 폴더를 직접 만든다. 그 안에서 두 버전을 저장하고 하나를 복원한 뒤, 내용이 맞는지 검사하고 종료한다.
- AI가 여러 부분을 한꺼번에 바꿀 때 무엇을 확인해야 하는지 설명한다.
- 수정 전에 사본을 남기고, 이전 사본을 덮어쓰지 않는 이유를 이해한다.
- 줄 단위 차이 비교 결과에서 삭제와 추가를 구별한다.
- 깃의 커밋과 되돌리기를 저장 지점이라는 비유로 이해한다.
- 두 버전의 차이를 출력하고 저장한 사본으로 복원하는 프로그램을 실행한다.
문제 상황
간단한 물품 목록 프로그램을 만들었다고 하자. 파일에는 항목 이름과 개수가 적혀 있고, 화면에 두 값을 보여 주는 기능까지 확인했다. 이제 AI에게 “메모도 표시해 달라”고 부탁한다. AI가 돌려준 코드에는 메모를 다루는 부분뿐 아니라 파일을 읽는 방식과 출력 순서까지 바뀌어 있다.
새 코드를 붙여 넣자 메모는 보인다. 하지만 개수가 원래 값과 달라졌다. 수정 전 파일을 따로 남기지 않았다면 어디서 달라졌는지 알아내기 어렵다. 대화창에서 이전 답을 찾더라도, 실제로 실행하고 확인했던 파일과 같은지 다시 살펴봐야 한다. 답변을 받은 뒤 직접 고친 내용은 대화창에 없을 수도 있다.
문제는 바뀐 줄이 많다는 사실만이 아니다. 요청한 변경과 관계없는 변경이 함께 들어왔는데 이를 구별할 기준이 없다는 점이다. 메모 추가가 목적이었다면 개수 변경은 따로 확인할 사항이다. 실행 성공은 프로그램이 끝까지 실행됐다는 증거이지, 모든 변경이 요청에 맞았다는 증거는 아니다.
목적: 물품 목록에 메모를 한 줄 표시하고 싶다.
입력: 현재 파일에는 항목과 개수가 있다.
출력: 기존 항목과 개수는 유지하고 메모만 추가한다.
확인: 수정 전후에 바뀌는 부분과 그 이유를 설명해 달라. 받은 코드는 사본을 남긴 뒤 실행하고 차이를 검토하겠다.
이 요청문은 변경 범위를 좁히는 데 도움이 된다. 그래도 AI의 설명과 실제 파일이 일치하는지는 직접 확인해야 한다. “기존 내용은 유지했다”는 답변만 읽고 넘어가지 않는다. 저장한 파일과 새 파일의 차이가 그 설명을 뒷받침하는지 살핀다.
수정 전 사본이 돌아갈 자리가 된다
버전(version)은 어느 시점의 파일 상태를 구별하는 이름이다. 여기서는 같은 파일이라도 수정 전 내용과 수정 후 내용을 서로 다른 버전으로 다룬다. 사본은 파일 내용을 다른 파일에 복사해 보관한 것이다. 수정 전에 사본을 만들면 현재 파일을 바꾼 뒤에도 이전 내용을 읽을 수 있다.
예를 들어 작업 파일이 list.txt라면 사본을 모으는 폴더에 version-01.txt를 저장한다. 그다음 작업 파일만 수정한다. 새 상태도 남기려면 version-02.txt를 만든다. 두 번째 내용을 첫 번째 사본에 덮어쓰면 처음 상태가 사라지므로, 저장 번호가 이미 있으면 멈추도록 만드는 편이 낫다.
복원은 보관한 사본의 내용을 작업 파일에 다시 쓰는 일이다. 이때 작업 파일에만 있던 변경은 덮어써진다. 따라서 복원하기 직전의 상태도 나중에 필요하다면 먼저 새 사본으로 남겨야 한다. 이 장의 예제는 수정 후 상태를 두 번째 사본에 저장한 다음 첫 번째 사본을 복원한다. 복원 뒤에도 두 사본은 모두 남아 있다.
사본이 있다고 해서 그 내용이 올바르다는 뜻은 아니다. 잘못된 상태도 저장할 수 있다. 실제 작업에서는 실행하고 검사한 상태에 “메모 추가 전, 기존 출력 확인”처럼 짧은 설명을 붙이는 습관이 유용하다. 파일명만 보고 판단하기 어렵다면 별도의 작업 기록에 저장 이유를 적어 둔다. 예제에서는 흐름을 단순하게 보이기 위해 번호만 쓴다.
| 대상 | 담는 내용 | 이 예제의 처리 |
|---|---|---|
| 작업 파일 | 지금 확인하거나 수정하는 내용 | 수정과 복원으로 내용이 바뀐다. |
| 첫 번째 사본 | 수정 전 내용 | 복원할 기준으로 보관한다. |
| 두 번째 사본 | 수정 후 내용 | 복원 전에 새 상태를 보관한다. |
| 임시 폴더 | 작업 파일과 사본 전체 | 프로그램이 끝날 때 정리한다. |
이 표의 마지막 행은 실습에서 특히 중요하다. 임시 폴더는 잠시 파일을 만들어 실험하는 장소다. 이번 프로그램은 실행할 때마다 새 임시 폴더를 만들고 종료할 때 없앤다. 따라서 저장과 복원의 원리를 연습하는 도구이며, 실행 사이에 내 작업을 계속 보관하는 도구는 아니다. 실제 작업의 기록은 종료 후에도 남는 별도 위치에 저장해야 한다.
바뀐 줄을 읽는 차이 비교
차이 비교(diff)는 두 내용에서 무엇이 달라졌는지 보여 주는 방법이다. 전체 파일 두 개를 번갈아 읽는 대신 바뀐 줄과 그 주변을 함께 본다. 파이썬 표준 라이브러리의 difflib를 사용하면 별도 설치 없이 줄 단위 차이를 만들 수 있다.
이 장에서는 이전 내용에서 현재 내용으로 바뀌는 방향으로 비교한다. 줄 앞의 -는 이전 내용에서 빠지는 줄이고, +는 현재 내용에 들어오는 줄이다. 값 하나가 바뀌면 이전 줄의 삭제와 새 줄의 추가로 표시될 수 있다. 프로그램이 실제로 어느 순간에 줄을 지웠는지 기록하는 것이 아니라, 두 상태를 비교한 결과다.
| 표시 | 뜻 | 확인할 내용 |
|---|---|---|
--- version-01.txt | 비교 기준인 이전 내용의 이름 | 수정 전 사본이 맞는지 본다. |
+++ current.txt | 비교 대상인 현재 내용의 이름 | 어느 쪽을 새 내용으로 보는지 확인한다. |
@@ -1,2 +1,3 @@ | 이전 쪽 1행부터 2줄, 현재 쪽 1행부터 3줄인 비교 구간 | 변경이 있는 위치를 찾는다. |
본문 줄 앞의 공백·-·+ | 각각 같은 줄·빠진 줄·추가된 줄 | 요청과 관계없는 변경이 있는지 살핀다. |
맨 앞의 ---와 +++는 파일 이름을 알려 주는 머리말이다. 본문의 삭제와 추가 표시와 구별해야 한다. 또한 앞에 공백이 붙은 줄은 바뀌지 않은 주변 내용이다. 주변 줄이 있으면 어느 항목 근처가 달라졌는지 이해하기 쉽다.
이번 실습의 수정 전 파일에는 “항목: 사과”와 “개수: 2”가 있다. 수정 후에는 개수가 3으로 바뀌고 “메모: 확인 필요”가 추가된다. 만약 요청이 메모 추가뿐이었다면, 차이 비교에서 개수 변경을 발견하고 AI에게 그 이유를 물어야 한다. 이 예제는 그 변경을 눈으로 찾도록 의도적으로 두 값을 다르게 둔다.
수정 전과 비교하니 개수가 2에서 3으로 바뀌었다. 요청한 것은 메모 추가다. 개수는 2로 유지하도록 고치고, 그 수정 뒤의 차이도 다시 설명해 달라.
차이 비교만으로 프로그램이 맞는지 판단할 수는 없다. 같은 한 줄 변경이라도 계산이나 조건에 영향을 줄 수 있고, 파일 밖의 환경이 동작에 영향을 줄 수도 있다. 차이를 읽어 변경 범위를 확인한 다음, 앞 장에서 했던 예시 입력과 기대 출력 검사도 다시 수행한다.
깃의 커밋과 되돌리기는 무엇인가
깃(Git)은 파일의 변경 이력을 관리하는 도구다. 사본을 직접 번호별로 만드는 일을 더 체계적으로 다룰 수 있게 한다. 여러 파일이 있는 작업에서 어느 상태를 남겼는지 찾아보고, 상태 사이의 차이를 비교하는 데 쓰인다. 여기서는 명령을 외우기보다 저장 지점이라는 비유로 개념을 먼저 이해한다.
커밋(commit)은 선택한 변경을 설명과 함께 이력에 기록하는 일이다. 게임에서 진행 상태를 저장하는 지점과 비슷하게 생각할 수 있다. 다만 파일의 저장 버튼을 누르는 것과 커밋은 서로 다른 동작이다. 파일을 저장하면 디스크의 현재 내용이 바뀌고, 커밋을 만들면 선택한 상태가 변경 이력에 기록된다. 파일을 여러 번 저장해도 그때마다 커밋이 저절로 생기는 것은 아니다.
커밋 설명에는 “수정”처럼 넓은 말보다 “목록에 메모 표시 추가”처럼 무엇을 바꿨는지 적는 편이 찾기 쉽다. 동작을 확인한 작은 변경마다 기록을 남기면, 나중에 어느 지점부터 결과가 달라졌는지 좁힐 수 있다. 반대로 서로 다른 목적의 변경을 한꺼번에 묶으면 일부만 되돌리고 싶을 때 판단할 일이 많아진다.
깃에서 되돌리기는 한 가지 동작만 가리키지 않는다. 작업 파일을 이전 내용으로 복구할 수도 있고, 이미 기록한 변경을 상쇄하는 새 커밋을 만들 수도 있다. 어떤 방법은 아직 기록하지 않은 작업 내용을 덮어쓸 수 있다. 따라서 AI에게 되돌리기를 부탁할 때는 “어느 상태로 돌아갈지”와 “현재 변경도 보관할지”를 함께 말해야 한다.
이 장의 도구는 깃을 구현하지 않는다. 파일 하나를 통째로 복사해 보관하고, 선택한 사본을 작업 파일에 다시 복사한다. 커밋 설명, 여러 파일의 묶음 관리, 다른 사람과의 이력 공유 기능은 없다. 작은 사본 도구로 저장 지점과 복원의 관계를 익힌 뒤 깃을 접하면 각 동작의 목적을 이해하기 쉬워진다.
완성 코드
다음 코드를 main.py에 저장한다. 파이썬 3.12 이상에서 표준 라이브러리만 사용한다. 직접 준비할 입력 파일은 없다. 작업 파일에 들어갈 두 내용도 코드에 정해 두었으므로 실행할 때마다 같은 결과가 나온다.
snapshot이라는 이름은 특정 시점의 상태를 담은 사본이라는 뜻으로 사용한다. 예제는 첫 번째 사본 저장, 작업 파일 수정, 차이 출력, 두 번째 사본 저장, 첫 번째 사본 복원 순서로 진행된다.
from pathlib import Path
from tempfile import TemporaryDirectory
import difflib
import shutil
def save_snapshot(work_file, versions, number):
saved = versions / f"version-{number:02d}.txt"
if saved.exists():
raise FileExistsError("이미 있는 버전 번호다.")
shutil.copyfile(work_file, saved)
return saved
def show_diff(saved, work_file):
before = saved.read_text(encoding="utf-8")
after = work_file.read_text(encoding="utf-8")
lines = list(difflib.unified_diff(
before.splitlines(keepends=True),
after.splitlines(keepends=True),
fromfile=saved.name,
tofile="current.txt",
))
if lines:
print("".join(lines), end="")
else:
print("차이 없음")
def restore_snapshot(saved, work_file):
shutil.copyfile(saved, work_file)
def main():
original = "항목: 사과\n개수: 2\n"
changed = "항목: 사과\n개수: 3\n메모: 확인 필요\n"
with TemporaryDirectory() as folder:
root = Path(folder)
work_file = root / "list.txt"
versions = root / "versions"
versions.mkdir()
work_file.write_text(original, encoding="utf-8")
first = save_snapshot(work_file, versions, 1)
print("저장: " + first.name)
work_file.write_text(changed, encoding="utf-8")
print("수정 전과 현재의 차이:")
show_diff(first, work_file)
second = save_snapshot(work_file, versions, 2)
print("저장: " + second.name)
restore_snapshot(first, work_file)
print("복원: " + first.name)
print("복원한 내용:")
print(work_file.read_text(encoding="utf-8"), end="")
print("복원 후 첫 번째 사본과의 차이:")
show_diff(first, work_file)
if work_file.read_text(encoding="utf-8") != original:
raise RuntimeError("복원 검사 실패")
if second.read_text(encoding="utf-8") != changed:
raise RuntimeError("두 번째 사본 검사 실패")
print("검사 통과: 복원 내용과 두 번째 사본이 맞다.")
names = sorted(item.name for item in versions.iterdir())
print("보관한 사본: " + ", ".join(names))
print("임시 폴더 정리 완료")
if __name__ == "__main__":
main()
줄별 해설
코드의 빈 줄은 기능 덩어리를 구별하기 위한 것이다. 아래에서는 위에서부터 모든 실행문과 정의문을 역할별로 읽는다. 처음에는 문법을 외우기보다 “무엇을 읽고, 어디에 쓰며, 어떤 조건에서 멈추는가”를 따라간다.
파일을 다룰 도구 가져오기
from pathlib import Path는 파일과 폴더의 위치를 다루는 도구를 가져온다. from tempfile import TemporaryDirectory는 사용 후 정리할 임시 폴더를 만드는 도구를 가져온다. import difflib는 차이 비교 도구를, import shutil는 파일 복사 도구를 사용할 준비를 한다. 여기서 가져오는 것은 모두 파이썬에 포함된 표준 라이브러리다.
사본 저장 함수
def save_snapshot(work_file, versions, number):는 여러 문장을 묶어 이름을 붙이는 함수 정의다. 괄호 속 세 이름은 함수를 부를 때 전달받을 작업 파일 위치, 사본 폴더 위치, 버전 번호다. 함수 아래에서 들여쓴 줄들이 이 함수에 속한다.
saved = versions / f"version-{number:02d}.txt"는 사본의 위치를 만든다. 위치를 나타내는 Path에서 /는 폴더와 그 안의 이름을 잇는다. 문자열 앞의 f는 중괄호 안 값을 문자열에 넣겠다는 뜻이다. :02d는 정수를 최소 두 자리로 표시하고 빈자리를 0으로 채우므로 1은 01이 된다.
if saved.exists():는 같은 위치에 파일이 이미 있는지 확인한다. 있다면 바로 아래의 raise FileExistsError(...)가 오류를 일으켜 저장을 멈춘다. 기존 사본을 조용히 덮어쓰지 않기 위한 검사다. shutil.copyfile(work_file, saved)는 첫 번째 위치의 내용을 두 번째 위치로 복사한다. return saved는 만든 사본의 위치를 함수를 부른 쪽에 돌려준다.
차이를 보여 주는 함수
def show_diff(saved, work_file):는 사본과 작업 파일을 비교하는 함수다. 다음 두 줄의 read_text(encoding="utf-8")는 파일을 문자로 읽어 각각 before와 after에 담는다. 문자 인코딩은 파일의 바이트와 글자를 연결하는 규칙이다. 한글을 쓰고 읽을 때 같은 규칙을 쓰도록 utf-8을 명시한다.
lines = list(difflib.unified_diff(...))는 두 내용을 비교해 출력할 줄들을 목록으로 모은다. 목록은 여러 값을 순서대로 담는 자료다. before.splitlines(keepends=True)와 after.splitlines(keepends=True)는 각 내용을 줄별로 나누면서 줄 끝의 줄바꿈 문자도 남긴다. 그래야 출력할 때 줄들이 붙지 않는다.
fromfile=saved.name은 이전 내용의 표시 이름을 사본 파일명으로 정한다. tofile="current.txt"는 현재 내용의 표시 이름이다. 이 이름으로 새 파일을 만드는 것은 아니다. 긴 임시 폴더 위치 대신 일정한 이름으로 비교 결과를 보여 주려는 설정이다. 이어지는 ))는 여러 줄에 걸쳐 적은 두 함수 호출을 닫는다.
if lines:는 차이 목록이 비어 있지 않은지 묻는다. 차이가 있으면 "".join(lines)로 줄들을 하나의 문자열로 연결해 출력한다. 각 줄에 이미 줄바꿈이 있으므로 end=""로 print가 끝에 줄바꿈을 더 붙이지 않게 한다. else: 아래는 차이가 없을 때 실행되어 “차이 없음”을 출력한다.
복원 함수와 실습 준비
def restore_snapshot(saved, work_file):는 복원 함수다. 안의 shutil.copyfile(saved, work_file)는 저장 때와 반대 방향으로 복사한다. 사본을 읽어 작업 파일에 쓰므로 작업 파일의 현재 내용을 덮어쓴다. 이 한 줄은 복원 동작만 맡으며, 현재 내용을 별도로 저장하는 일은 호출하는 쪽에서 해야 한다.
def main():은 실습 전체의 진행 순서를 묶는다. original에는 수정 전 내용, changed에는 수정 후 내용을 담는다. 문자열 속 \n은 파일에서 줄을 바꾸는 문자다. 마지막에도 넣어 각 파일이 줄바꿈으로 끝나게 한다.
with TemporaryDirectory() as folder:는 임시 폴더를 만들고 그 위치를 folder라는 이름으로 받는다. 아래의 들여쓴 범위를 벗어나면 폴더와 안의 파일을 정리한다. root = Path(folder)는 그 위치를 경로 도구로 다루게 한다. 이어서 작업 파일 위치와 사본 폴더 위치를 정하고, versions.mkdir()로 사본 폴더를 실제로 만든다.
work_file.write_text(original, encoding="utf-8")는 준비한 수정 전 내용을 작업 파일에 쓴다. 위치를 정하는 일과 파일을 쓰는 일은 다르다. 앞에서 work_file을 만들었을 때는 위치만 정했고, 이 줄에서 파일 내용이 생긴다.
저장하고 비교하고 복원하기
first = save_snapshot(work_file, versions, 1)은 첫 번째 사본을 만들고 그 위치를 받는다. 다음 print는 저장한 이름을 알린다. 이어지는 write_text(changed, ...)가 작업 파일을 수정 후 내용으로 덮어쓴다. 안내 문장을 출력한 뒤 show_diff(first, work_file)로 수정 전 사본과 현재 내용을 비교한다.
second = save_snapshot(work_file, versions, 2)는 수정 후 내용을 두 번째 사본으로 보관한다. 저장 안내 다음의 restore_snapshot(first, work_file)는 첫 번째 사본을 작업 파일로 복원한다. 뒤의 두 안내 문장은 무엇을 복원했는지 알리고, print(work_file.read_text(...), end="")는 복원한 실제 파일 내용을 읽어 보여 준다.
“복원 후 첫 번째 사본과의 차이:”를 출력한 다음 다시 show_diff를 부른다. 복원이 맞게 됐다면 같은 두 내용을 비교하므로 “차이 없음”이 나온다. 저장 때와 복원 뒤에 같은 비교 함수를 사용해 상태 변화를 확인하는 것이다.
결과 검사와 종료
마지막 두 if문의 !=는 두 값이 다른지 묻는다. 첫 검사는 복원한 작업 파일이 원래 문자열과 같은지 확인한다. 두 번째 검사는 복원하는 동안 두 번째 사본이 바뀌지 않았는지 확인한다. 다르면 각각 RuntimeError로 실행을 멈춘다. 두 검사를 모두 지나야 “검사 통과”가 출력된다.
versions.iterdir()는 사본 폴더 안의 항목을 얻는다. item.name for item in ...은 각 항목에서 이름만 꺼내는 표현이며, sorted(...)는 이름을 정렬한 목록을 만든다. 파일을 나열하는 순서에 기대지 않고 일정한 출력 순서를 정하는 것이다. 다음 줄의 ", ".join(names)는 이름 사이에 쉼표와 공백을 넣어 연결한다.
“임시 폴더 정리 완료”를 출력하는 줄은 with 범위 밖에 있다. 이 위치에 도달했을 때 임시 폴더 정리가 끝난다. 맨 아래 if __name__ == "__main__":는 이 파일을 직접 실행했는지 확인하고, 그 아래 main()이 실습을 시작한다.
실행 결과
터미널에서 main.py가 있는 폴더로 이동한 뒤 다음 명령을 실행한다.
python3 main.py
예상 출력은 다음과 같다. 차이 비교에서 “항목: 사과” 앞에는 바뀌지 않은 줄이라는 뜻의 공백 한 칸이 있다.
저장: version-01.txt
수정 전과 현재의 차이:
--- version-01.txt
+++ current.txt
@@ -1,2 +1,3 @@
항목: 사과
-개수: 2
+개수: 3
+메모: 확인 필요
저장: version-02.txt
복원: version-01.txt
복원한 내용:
항목: 사과
개수: 2
복원 후 첫 번째 사본과의 차이:
차이 없음
검사 통과: 복원 내용과 두 번째 사본이 맞다.
보관한 사본: version-01.txt, version-02.txt
임시 폴더 정리 완료
확인할 증거는 세 가지다. 첫 비교에서는 개수 변경과 메모 추가가 보인다. 복원한 실제 내용에서는 개수가 다시 2이고 메모가 없다. 마지막 검사에서는 복원 결과뿐 아니라 수정 후 사본도 그대로 남았음을 확인한다. “복원”이라는 안내 문장만 출력됐는지 보는 것보다 이 세 가지를 함께 보는 편이 판단 근거가 분명하다.
임시 폴더의 실제 이름은 실행마다 달라질 수 있지만 출력하지 않는다. 출력에는 고정된 파일 이름과 내용만 사용한다. 따라서 다시 실행해도 위 결과와 비교할 수 있다. 실행이 끝난 뒤 사본 파일을 찾을 수 없는 것은 이 실습의 정리 방식에 따른 결과다.
실무에서 자주 틀리는 것
수정한 뒤에 첫 사본을 만든다
저장 순서가 뒤집히면 수정 전 상태가 남지 않는다. 아래 두 코드는 각각 임시 폴더에서 독립적으로 실행할 수 있다. 첫 코드는 사본을 만들지만 이미 바뀐 내용만 저장한다.
from pathlib import Path
from tempfile import TemporaryDirectory
import shutil
with TemporaryDirectory() as folder:
work = Path(folder) / "work.txt"
saved = Path(folder) / "saved.txt"
work.write_text("개수: 2\n", encoding="utf-8")
work.write_text("개수: 3\n", encoding="utf-8")
shutil.copyfile(work, saved)
고친 코드는 사본을 만든 다음 작업 파일을 수정한다. AI에게 큰 수정을 맡기기 전에도 이 순서를 지킨다.
from pathlib import Path
from tempfile import TemporaryDirectory
import shutil
with TemporaryDirectory() as folder:
work = Path(folder) / "work.txt"
saved = Path(folder) / "saved.txt"
work.write_text("개수: 2\n", encoding="utf-8")
shutil.copyfile(work, saved)
work.write_text("개수: 3\n", encoding="utf-8")
파일이 존재하면 복원이 맞았다고 생각한다
파일이 있다는 사실은 그 안의 내용까지 보장하지 않는다. 다음 코드는 수정 후 내용이 남아 있어도 검사에 통과한다. 문자열이 아닌 참과 거짓을 나타내는 값은 파이썬에서 True와 False로 쓴다.
from pathlib import Path
from tempfile import TemporaryDirectory
with TemporaryDirectory() as folder:
work = Path(folder) / "work.txt"
work.write_text("개수: 3\n", encoding="utf-8")
if not work.exists():
raise RuntimeError("복원 검사 실패")
고친 코드는 복원 동작을 수행한 다음 기대한 내용과 실제 내용을 비교한다. 완성 코드도 같은 이유로 파일 내용을 다시 읽는다.
from pathlib import Path
from tempfile import TemporaryDirectory
import shutil
with TemporaryDirectory() as folder:
work = Path(folder) / "work.txt"
saved = Path(folder) / "saved.txt"
expected = "개수: 2\n"
saved.write_text(expected, encoding="utf-8")
work.write_text("개수: 3\n", encoding="utf-8")
shutil.copyfile(saved, work)
if work.read_text(encoding="utf-8") != expected:
raise RuntimeError("복원 검사 실패")
차이를 만들 때 줄바꿈을 없앤다
다음 코드는 비교할 줄의 끝에서 줄바꿈을 버린다. 결과를 빈 문자열로 연결하면 변경 줄들이 붙어서 읽기 어려워진다. 실행 자체가 끝났다는 이유로 출력 형식까지 맞다고 판단하지 않는다.
import difflib
before = "개수: 2\n"
after = "개수: 3\n"
lines = difflib.unified_diff(
before.splitlines(),
after.splitlines(),
fromfile="before.txt",
tofile="after.txt",
)
print("".join(lines), end="")
고친 코드에서는 keepends=True로 원래의 줄바꿈을 남긴다. 완성 예제의 입력은 모두 줄바꿈으로 끝나므로 각 변경 줄도 구별되어 출력된다.
import difflib
before = "개수: 2\n"
after = "개수: 3\n"
lines = difflib.unified_diff(
before.splitlines(keepends=True),
after.splitlines(keepends=True),
fromfile="before.txt",
tofile="after.txt",
)
print("".join(lines), end="")
실제 파일은 마지막 줄에 줄바꿈이 없을 수도 있다. 그런 파일까지 다루도록 확장한다면 표시 방식을 추가로 정하고 시험해야 한다. 이 장에서는 입력을 직접 만들고 마지막 줄바꿈을 포함해 비교 조건을 일정하게 유지한다.
한눈에 보기
| 시점 | 할 일 | 확인할 증거 |
|---|---|---|
| 수정 전 | 확인한 상태의 사본을 새 번호로 저장한다. | 작업 파일과 사본의 내용이 같다. |
| 수정 후 | 이전 사본과 현재 파일의 차이를 읽는다. | 추가·삭제가 요청한 범위에 맞는다. |
| 동작 확인 | 예시 입력과 기대 출력으로 검사한다. | 이전에 되던 기능도 계속 맞는다. |
| 복원 전 | 현재 내용도 필요하면 사본으로 남긴다. | 복원 뒤에도 수정 후 상태를 읽을 수 있다. |
| 복원 후 | 파일을 다시 읽고 기준 내용과 비교한다. | 차이가 없고 내용 검사도 통과한다. |
| 실제 작업 종료 | 기록이 남는 위치에 저장했는지 확인한다. | 임시 폴더 정리로 작업 기록이 사라지지 않는다. |
사본과 차이 비교는 AI의 답을 판단할 자료를 남기는 습관이다. AI가 제안한 변경을 내 파일에 반영하는 순간에는 무엇이 바뀌었고 무엇을 확인했는지 설명할 수 있어야 한다. 또 버전을 남기면 예전에 적었던 내용도 함께 남는다. 다음 장에서는 개인정보와 비밀번호처럼 애초에 맡기거나 기록할 때 주의해야 할 내용을 살펴본다.
연습 문제
- 완성 코드의 첫 차이 비교에서
-개수: 2와+개수: 3이 함께 나온다. 두 줄을 말로 해석하라. 요청이 메모 추가뿐이었다면 무엇을 더 확인해야 하는지도 적어 보라. - 같은 번호의 사본을 다시 저장하면 어떻게 되는지 확인하라. 첫 번째 저장 안내 바로 다음에
save_snapshot(work_file, versions, 1)을 한 번 더 호출하고, 멈추는 이유를 설명하라. 실험 뒤에는 추가한 호출을 지운다. - 완성 코드에서 수정 후 개수를 3이 아니라 5로 바꿔 실행하라. 첫 차이 비교, 복원한 내용, 마지막 검사 중 무엇이 달라지고 무엇이 유지되는지 예상한 뒤 확인하라.
- 첫 번째 사본을 복원하기 직전에 두 번째 사본을 저장하지 않는다고 가정하라. 복원 뒤 수정 후 파일 내용을 사본만으로 다시 확인할 수 있는지 설명하라. 이 예제의 코드에 남은 문자열과 실제 작업에서의 사본을 구별해서 답하라.
정답과 해설
- 이전 내용의 “개수: 2”가 빠지고 현재 내용에 “개수: 3”이 들어왔다는 뜻이다. 개수 값이 2에서 3으로 바뀐 것이다. 메모 추가만 요청했다면 개수 변경은 요청 범위에 맞지 않으므로 이유를 확인하고 수정해야 한다. 고친 뒤에는 차이를 다시 보고 실행 검사도 반복한다.
- 두 번째 호출에서 같은 이름의 사본이 이미 존재하므로
FileExistsError가 발생한다. 첫 사본을 새 내용으로 덮어쓰는 대신 멈추도록 설계한 결과다. 일반 실행은 실패한 것이지만, 이 실험에서는 중복 저장을 막는 조건이 작동했음을 확인한 것이다. 오류가 나도with범위를 나가면서 임시 폴더는 정리된다. changed문자열의 개수만 5로 고치면 첫 차이 비교의 추가 줄이+개수: 5로 달라진다. 첫 번째 사본에서 복원한 내용은 여전히 “개수: 2”다. 두 번째 사본은 바뀐changed와 비교하므로 검사도 통과한다. 이 통과는 저장과 복원 과정이 준비한 문자열과 일치한다는 뜻이며, 개수 5가 사용자 요구에 맞는다는 뜻은 아니다.- 두 번째 사본을 만들지 않으면 사본 폴더에는 수정 전 내용만 남는다. 복원으로 작업 파일도 수정 전 내용이 되므로 사본만으로 수정 후 상태를 확인할 수 없다. 이 예제에서는
changed라는 문자열이 코드에 있어 다시 쓸 수 있지만, 실제 작업의 변경이 모두 코드 속 별도 문자열로 남는 것은 아니다. 보관할 필요가 있는 현재 상태는 복원 전에 저장해야 한다.
READER FEEDBACK
질문·의견
내용에 관한 질문이나 더 나은 설명을 위한 의견을 남겨 주세요. 오탈자는 위의 제보 양식이 더 빨리 반영됩니다. 이 댓글은 원래 게시글과 같은 자리에 쌓입니다.
댓글 0
아직 댓글이 없습니다. 첫 댓글을 남겨 보세요.