Devin.KR

작게 나눠 시키고 체크포인트 남기기

개발자KR 조회 0

이 장에서 배우는 것

앞 장에서 탐색·계획·구현·검증을 한 바퀴 돌리는 방법을 살펴보았다. 계획을 세웠더라도 구현 요청이 너무 크면 확인하기 어려운 코드가 한꺼번에 돌아온다. “파일 저장을 붙여 달라”는 요청 안에도 저장할 모양 정하기, 파일 쓰기, 다시 읽기라는 서로 다른 일이 들어 있다. 이 장에서는 그 일을 작게 나누고, 각 단계에서 확인한 상태를 남기는 방법을 다룬다.

예제는 혼자 쓰는 할 일 관리 도구다. 할 일 두 개를 JSON 파일에 저장하는 기능을 세 단계로 넣는다. JSON은 목록과 사전 같은 데이터를 텍스트로 표현하는 형식이다. 완성 프로그램은 각 단계의 전후 상태를 비교해 출력한다. 실제 개발에서는 같은 흐름에 버전 관리 기록을 곁들여, 확인된 코드로 돌아갈 길을 만든다.

  • 한 요청에 한 가지 기능을 담고, 끝났다고 판단할 기준을 붙인다.
  • 단계마다 실행해 확인하고, 확인 결과를 다음 요청의 출발점으로 삼는다.
  • 작업 전후 커밋과 실험용 브랜치로 되돌아갈 지점을 남긴다.
  • AI의 변경과 직접 한 변경을 구분하고, 긴 대화를 짧은 인계 기록으로 이어 간다.

문제 상황

현재 도구는 실행 중에만 할 일을 기억한다. 프로그램을 끝내면 목록이 사라진다. 독자는 AI에게 “할 일을 파일에 저장하고, 완료 표시도 넣고, 검색도 되게 고쳐 달라”고 요청한다. AI는 저장 함수뿐 아니라 할 일 사전의 항목 이름, 출력 순서, 입력 처리까지 바꾼다. 실행은 되지만 저장한 파일을 다시 읽으면 완료 표시가 빠진다.

이때 확인해야 할 후보가 많다. 저장 과정에서 값이 빠졌는지, 읽는 과정에서 기본값으로 덮었는지, 완료 표시를 바꾸는 과정에서 목록을 새로 만들었는지 구분하기 어렵다. 독자가 중간에 출력 문구까지 직접 고쳤다면 어떤 수정 뒤에 문제가 생겼는지도 흐려진다. 실행되는 상태가 있었다는 기억만으로는 그 상태를 복원할 수 없다.

작업을 나누면 질문이 구체적으로 바뀐다. 먼저 목록이 JSON 문자열로 바뀌는지 확인한다. 다음에는 그 문자열이 파일에 기록되는지 확인한다. 마지막에는 파일을 읽어 원래 목록과 같은 값이 나오는지 확인한다. 문제가 생겨도 마지막으로 추가한 단계부터 조사할 수 있다. 단계가 작다는 말은 줄 수가 적다는 뜻보다, 확인할 변화가 하나라는 뜻에 가깝다.

각 단계가 끝날 때 “이번에는 무엇을 바꿨고 무엇으로 확인했는가”를 적는다. 코드가 돌아왔다는 사실과 기능이 확인되었다는 사실은 다르다. 파일이 생겼다는 결과만으로는 제목과 완료 상태가 모두 보존되었다고 판단할 수 없다. 실행 결과와 변경 내용을 함께 살펴본 뒤에 다음 단계로 넘어간다.

한 요청에는 한 가지 결과를 담는다

작은 요청에는 대상, 변경 범위, 확인 기준이 있어야 한다. “조금만 고쳐 달라”는 표현은 범위를 정하지 못한다. 반면 “현재 할 일 목록을 JSON 문자열로 바꾸는 함수만 추가하고, 파일은 아직 쓰지 말라”는 표현은 이번 단계의 끝을 알려 준다. AI가 추가로 제안한 개선이 유용하더라도, 지금 확인할 기능과 관계가 없다면 별도 작업으로 남긴다.

이번 예제의 세 단계는 서로 이어지지만 확인할 결과가 다르다. 첫 단계는 데이터 표현, 두 번째는 파일 기록, 세 번째는 복원을 맡는다. 실제 요청에서는 아래와 같이 단계별로 전달한다. 세 요청을 한꺼번에 보내지 않고, 앞 단계의 실행과 변경 확인을 마친 뒤 다음 요청을 보낸다.

JSON 저장 기능을 세 번의 확인 가능한 요청으로 나눈다
단계요청 범위끝났다는 증거
1. 문자열 준비할 일 목록을 JSON 문자열로 바꾼다.문자열을 해석하면 원래 목록과 같다.
2. 파일 저장준비한 문자열을 지정한 파일에 쓴다.파일을 읽은 문자열이 준비한 문자열과 같다.
3. 목록 복원저장한 파일을 읽어 목록으로 되돌린다.복원한 목록이 원래 목록과 같다.

현재 할 일은 id, title, done 항목을 가진 사전의 목록이다. 이번에는 이 목록을 JSON 문자열로 바꾸는 함수만 추가하라. 한글 제목을 그대로 표현하고, 문자열을 다시 해석했을 때 원래 목록과 같은지 확인하라. 제목이나 완료 상태를 수정하는 기능은 추가하지 말라.

첫 단계가 확인되면 다음 요청에 그 결과를 붙인다. 이렇게 하면 AI도 이미 확인한 부분과 새로 해야 할 부분을 구분할 수 있다.

JSON 문자열 변환과 원래 목록과의 값 비교를 실행해 확인했다. 이번에는 그 문자열을 UTF-8로 파일에 쓰는 함수만 추가하라. 저장한 파일의 내용을 다시 읽어 준비한 문자열과 같은지 확인하라. 기존 변환 함수의 동작은 유지하라.

파일에 기록된 문자열이 준비한 문자열과 같은 것을 확인했다. 이번에는 파일을 읽어 할 일 목록으로 복원하는 함수를 추가하라. 복원 목록의 개수뿐 아니라 id, title, done을 포함한 전체 값이 원래 목록과 같은지 확인하라.

AI의 답변에 “완료했다”가 적혀 있어도 그대로 다음 요청을 보내지는 않는다. 직접 프로그램을 실행하고, 변경된 부분을 살펴본다. 변경 전후의 차이를 보여 주는 것을 diff라고 한다. 여기서는 요청 밖의 변경이 섞였는지 확인하는 데 사용한다. 차이를 자세히 읽는 방법은 다음 장에서 다룬다.

문자열 준비, 파일 저장, 목록 복원은 각각 확인을 마친 뒤 다음 단계로 이어진다

각 단계의 확인 기준은 다음 단계에 필요한 사실을 보장해야 한다. 파일이 존재하는지만 확인하면 내용이 비었는지 알 수 없다. 복원 목록의 개수만 확인하면 제목이 바뀌었는지 알 수 없다. 이번 예제에서는 문자열 전체와 목록 전체를 비교한다. 확인 방법을 요청에 적어 두면 AI의 설명도 그 기준에 맞춰 검토할 수 있다.

다만 확인 문장 하나가 모든 입력을 검증하는 것은 아니다. 이 장의 예제는 고정된 두 개의 할 일을 대상으로 저장 경로를 확인한다. 다양한 입력에 대한 테스트를 설계하는 일은 별도의 주제다. 여기서 중요한 습관은 범위를 작게 정하고, 그 범위에 맞는 증거를 얻고 넘어가는 것이다.

커밋과 브랜치로 돌아갈 지점을 만든다

체크포인트(checkpoint)는 다시 출발할 수 있도록 남겨 둔 지점이다. 코드의 체크포인트를 남길 때는 Git 같은 버전 관리 도구를 쓸 수 있다. 버전 관리는 파일의 변경 기록을 보관하는 방식이다. 커밋(commit)은 선택한 파일 상태를 설명과 함께 기록하는 작업이며, 브랜치(branch)는 별도의 작업 흐름에 붙인 이름이다.

작업 전에는 현재 코드가 실행되는지 확인하고 그 상태를 커밋한다. 작업 후에는 새 기능을 실행하고 변경 내용을 살핀 뒤 다시 커밋한다. 작업 전 커밋은 되돌아갈 출발점이고, 작업 후 커밋은 새로 확인한 도착점이다. 커밋 자체가 코드의 정확성을 보장하지는 않는다. 무엇을 확인했는지가 기록에 함께 남아야 의미가 있다.

아래 명령은 Git 저장소가 이미 준비되어 있다는 전제의 예시다. 각 행을 한꺼번에 실행하지 않고, 해당 시점에서 사용한다. Git은 완성 프로그램의 실행에 필요하지 않다. 완성 프로그램은 Python 표준 라이브러리만으로 체크포인트의 전후 비교 원리를 보여 준다.

작업 전후에 기록을 남기는 Git 명령 예시
시점명령의미
기존 상태 확인 뒤git add main.py
git commit -m "작업 전: 할 일 목록 실행 확인"
출발점의 파일 상태를 기록한다.
저장 기능 실험 시작git switch -c experiment/json-save새 브랜치에서 작업을 이어 간다.
각 단계 검증 뒤git add main.py
git commit -m "JSON 문자열 변환 확인"
검증한 단계의 상태를 기록한다.
변경 범위 확인git diff
git diff --staged
아직 기록하지 않은 변경과 기록 대상으로 고른 변경을 살핀다.

git add는 다음 커밋에 넣을 변경을 고르는 명령이다. 이 선택 영역을 스테이징 영역이라고 한다. git diff는 아직 그 영역에 넣지 않은 변경을, git diff --staged는 넣어 둔 변경을 보여 준다. 처음에는 main.py처럼 파일 이름을 지정해서 추가하는 편이 이번 기록에 무엇이 들어가는지 파악하기 쉽다.

실험용 브랜치는 같은 작업 폴더를 사용한다. 별도의 복사본 폴더가 자동으로 생기는 것은 아니다. 또한 커밋하지 않은 수정이 브랜치를 바꾼다고 자동으로 보관되거나 없어지는 것도 아니다. 되돌아가기 전에 git status로 남아 있는 변경을 확인해야 한다.

실험이 잘못되었을 때는 두 상황을 구분한다. 아직 커밋하지 않은 수정이라면 필요한 부분을 먼저 보관하고, 버릴 변경만 되돌린다. main.py에서 아직 스테이징하지 않은 수정을 버리려면 git restore main.py를 사용한다. 이 명령은 작업 파일을 스테이징 영역의 내용으로 복원하므로, 이미 git add한 미커밋 변경은 남는다. 단순한 상태 확인 명령으로 생각해서 실행하면 안 된다.

이미 커밋한 변경을 취소하고 싶다면 git revert에 해당 커밋 식별자를 지정할 수 있다. 커밋 식별자는 특정 기록을 가리키는 값이다. 이 명령은 기존 기록을 지우는 대신 그 변경을 취소하는 새 기록을 만든다. 여러 단계가 같은 부분을 고쳤다면 취소 과정에서도 충돌이 생길 수 있으므로, 취소 뒤에는 다시 실행해 확인한다.

확인된 기존 브랜치로 돌아가려면 미기록 변경을 먼저 정리한 뒤 git switch에 그 브랜치 이름을 지정한다. 기존 브랜치 이름이 main인 프로젝트에서는 git switch main을 사용한다. 새 브랜치와 전후 커밋을 함께 쓰면 저장 기능 실험을 기존 작업 흐름과 구분하고, 어느 상태에서 다시 시작할지 선택하기 쉬워진다.

확인된 출발점을 기록하고 실험 브랜치에서 단계별 기록을 남기면 돌아갈 상태를 선택할 수 있다

변경의 주체와 다음 출발점을 기록한다

AI가 저장 함수를 고치는 동안 독자가 같은 파일의 제목 처리까지 바꾸면 한 번의 실행에서 두 변경을 함께 확인하게 된다. 문제가 생겼을 때 책임을 나누려는 목적이 아니라, 원인을 찾을 후보를 줄이기 위해 변경을 구분한다. “AI가 제안한 저장 함수 추가”와 “내가 고친 출력 문구”를 각각 기록하면 어떤 변화가 결과에 영향을 주었는지 살펴보기 쉽다.

가장 간단한 방법은 한쪽 변경을 확인하고 커밋한 뒤 다른 쪽을 수정하는 것이다. 이미 섞였다면 git diff로 범위를 살핀다. 파일 전체를 커밋하면 두 변경이 모두 들어갈 수 있다. 필요한 경우 git add -p로 변경 덩어리를 하나씩 골라 기록할 수 있지만, 서로 의존하는 변경을 억지로 나누면 어느 커밋도 실행되지 않을 수 있다. 기록을 나눈 뒤에도 각 상태가 실행 가능한지 확인해야 한다.

이번 기록에는 AI가 제안한 JSON 저장 함수와 내가 추가한 저장 내용 비교가 함께 들어간다. 출력 문구 수정은 다음 작업으로 남겼다. 실행 결과에서 파일 내용과 준비한 문자열이 같은 것을 확인했다.

AI가 작성한 코드라고 해서 검토 책임이 AI로 넘어가지는 않는다. 제안된 변경을 읽고 실행한 사람은 확인 범위와 남은 일을 설명할 수 있어야 한다. 커밋 설명에는 막연한 “수정 완료”보다 “UTF-8 파일 저장과 문자열 일치 확인”처럼 무엇을 확인했는지 적는다. 실행해 보지 않았다면 확인했다고 쓰지 않는다.

대화도 같은 방식으로 정리한다. 대화가 길어지면 오래된 계획, 이미 취소한 요청, 현재 코드에 맞지 않는 설명이 함께 남는다. 몇 번 대화했는지보다 현재 목표를 짧게 설명하기 어려워졌는지가 새로 시작할 신호다. 새 대화에는 전체 기록 대신 현재 코드, 마지막 확인 결과, 다음 한 가지 작업을 전달한다.

현재 상태: 할 일 목록을 JSON 문자열로 바꾸고 UTF-8 파일에 쓰는 단계까지 확인했다. 마지막 커밋은 “파일 저장과 문자열 일치 확인”이다. 현재 main.py를 함께 제공한다. 다음 작업은 파일을 읽어 목록을 복원하는 함수 추가다. 완료 표시는 수정하지 않는다. 복원한 목록 전체가 원래 목록과 같은지 실행해 확인한다.

커밋 이름만 알려 주면 AI가 그 코드에 접근할 수 있다고 가정해서는 안 된다. 같은 작업 폴더를 읽을 수 있는 환경인지 확인하고, 읽을 수 없다면 현재 코드를 함께 전달한다. 파일을 읽고 수정하거나 이전 대화를 활용하는 방식은 도구마다 다를 수 있다. 여기서 소개한 요청문은 2026년 10월 기준 예시이며, 핵심은 제품 기능보다 확인된 상태와 다음 작업을 명시하는 데 있다.

새 대화를 시작하는 것은 체크포인트를 버리는 일이 아니다. 확인된 코드와 짧은 인계 기록을 출발점으로 삼는 일이다. 실제로 실패한 내용도 다음 선택에 영향을 준다면 한 문장으로 남긴다. 반면 취소한 요청을 모두 되풀이하면 다음 작업의 경계가 다시 흐려진다.

완성 코드

아래 내용을 main.py로 저장한다. 프로그램은 고정된 할 일 두 개를 사용하고, 임시 폴더 안에 tasks.json을 만든다. 임시 폴더는 잠시 사용할 파일을 두는 공간이다. 실행이 끝나면 폴더와 파일을 정리하므로 반복 실행해도 이전 결과가 영향을 주지 않는다. 개인 할 일 파일을 보존하는 실제 도구에서는 저장 위치를 별도로 정해야 한다.

세 단계의 스냅숏(snapshot)은 그 시점의 상태를 복사한 값이다. 이 프로그램의 스냅숏은 JSON 준비 여부, 파일 저장 여부, 복원 개수를 보여 준다. 소스 코드 전체를 보관하는 Git 커밋과는 역할이 다르다. 세 단계를 이미 포함한 프로그램을 한 번에 실행하므로, 실제 개발에서 단계마다 코드를 추가하고 커밋하는 과정은 앞의 작업 흐름에 따라 별도로 수행한다.

import json
from pathlib import Path
from tempfile import TemporaryDirectory


def prepare_json(tasks):
    text = json.dumps(tasks, ensure_ascii=False, indent=2)
    assert json.loads(text) == tasks, "JSON 변환 결과가 다르다."
    return text


def save_json(path, text):
    path.write_text(text, encoding="utf-8")
    saved_text = path.read_text(encoding="utf-8")
    assert saved_text == text, "저장한 문자열이 다르다."


def load_tasks(path, original):
    text = path.read_text(encoding="utf-8")
    restored = json.loads(text)
    assert restored == original, "복원한 목록이 다르다."
    return restored


def compare_snapshots(label, before, after):
    print(label)
    for key in before:
        if before[key] != after[key]:
            print(f"  {key}: {before[key]} -> {after[key]}")


def main():
    tasks = [
        {"id": 1, "title": "메모 정리", "done": False},
        {"id": 2, "title": "물 마시기", "done": True},
    ]
    state = {
        "JSON 준비": False,
        "파일 저장": False,
        "복원 개수": 0,
    }

    with TemporaryDirectory() as folder:
        path = Path(folder) / "tasks.json"

        before = state.copy()
        text = prepare_json(tasks)
        state["JSON 준비"] = True
        compare_snapshots("1단계: JSON 문자열 준비", before, state)

        before = state.copy()
        save_json(path, text)
        state["파일 저장"] = True
        compare_snapshots("2단계: JSON 파일 저장", before, state)

        before = state.copy()
        restored = load_tasks(path, tasks)
        state["복원 개수"] = len(restored)
        compare_snapshots("3단계: 할 일 목록 복원", before, state)

        print("검증: 문자열 변환, 파일 내용, 복원 목록 모두 일치")
        print("복원한 할 일:")
        for task in restored:
            status = "완료" if task["done"] else "미완료"
            print(f'  {task["id"]}. {task["title"]} [{status}]')

    print("임시 저장 파일 정리 완료")


if __name__ == "__main__":
    main()

줄별 해설

첫 세 줄은 필요한 표준 라이브러리를 가져온다. json은 목록과 사전을 JSON 문자열로 바꾸거나 문자열에서 되돌린다. Path는 파일 경로를 표현한다. TemporaryDirectory는 실행 중 사용할 임시 폴더를 만들고 정리한다. 외부 패키지를 설치하거나 네트워크에 접속하지 않는다.

prepare_json 함수는 첫 단계다. json.dumps의 ensure_ascii=False는 한글 제목을 읽을 수 있는 문자로 표현하게 한다. indent=2는 들여쓰기를 넣는다. 곧바로 json.loads로 문자열을 해석하고 원래 목록과 비교한다. assert는 뒤의 조건이 거짓이면 실행을 멈추고 오류를 알리는 문장이다. 이 예제에서는 단계의 확인 기준을 표현한다.

save_json 함수는 두 번째 단계다. write_text로 문자열을 쓰고 read_text로 다시 읽는다. 두 작업에 모두 UTF-8을 지정한다. UTF-8은 한글을 포함한 문자를 파일의 바이트로 표현하는 문자 인코딩 방식이다. 파일을 썼다는 사실만 확인하지 않고 실제로 읽힌 내용과 원래 문자열을 비교한다.

load_tasks 함수는 세 번째 단계다. 파일 내용을 읽어 json.loads에 전달하고, 복원 목록을 원래 목록 전체와 비교한다. 할 일 개수만 같아도 통과하도록 만들지 않았다. 이 예제의 비교는 목록 순서와 각 사전의 값까지 포함한다. 원래 목록을 매개변수로 받는 이유는 복원 결과의 확인 기준을 명시하기 위해서다.

compare_snapshots 함수는 before와 after의 같은 항목을 비교한다. 달라진 항목만 출력하므로 이번 단계의 상태 변화가 드러난다. 사전을 만든 순서대로 항목을 확인하고 고정된 값을 사용하므로 출력 순서도 일정하다. 출력 문자열의 화살표는 이전 값에서 이후 값으로 변했다는 뜻이다.

main의 tasks는 고정된 입력이다. 첫 할 일은 미완료이고 두 번째는 완료다. 서로 다른 완료 값을 넣어 두었기 때문에 복원 비교가 한쪽 상태만 확인하는 데 그치지 않는다. state는 진행 상태를 담는 사전이다. 여기에 기록하는 값은 단계가 성공했는지와 복원된 항목 수이며, 할 일 데이터 자체는 tasks와 restored에 따로 둔다.

with TemporaryDirectory() 부분에서는 임시 폴더가 필요한 작업 범위를 정한다. 그 범위를 벗어나면 폴더가 정리된다. 임시 폴더의 실제 이름은 실행마다 달라질 수 있으므로 출력하지 않는다. 시간과 난수도 사용하지 않아 사용자에게 보이는 결과는 일정하다.

각 단계 바로 앞의 state.copy()가 이전 상태를 남긴다. 함수를 실행하고 그 안의 비교가 통과한 뒤에 상태 값을 바꾼다. 저장에 실패했는데도 “파일 저장”이 True가 되는 순서를 피한 것이다. 함수에서 오류가 발생하면 뒤의 성공 출력까지 실행되지 않는다.

state.copy()는 사전 바깥쪽을 복사하는 얕은 복사다. 이 예제의 값은 불리언과 정수이므로 이전 상태를 남기는 데 충분하다. 값 안에 수정 가능한 목록이나 사전이 들어 있다면 그 내부까지 별도로 복사할 필요가 있다. 복사라는 이름만 보고 모든 중첩 데이터가 독립된다고 생각하지 않는다.

마지막 반복문은 복원한 목록을 사람이 읽는 형태로 출력한다. 이 출력은 목록을 읽기 쉽게 보여 주는 결과이고, 정확한 값 비교는 앞의 assert가 맡는다. 마지막 조건문은 이 파일을 직접 실행했을 때 main을 호출한다. Python 3.12 이상에서 python3 main.py로 실행하면 입력을 기다리지 않고 끝난다. 이 예제의 검증 문장을 실행하려면 assert를 생략하는 최적화 옵션을 붙이지 않는다.

실행 결과

main.py를 저장한 폴더에서 다음 명령을 실행한다.

python3 main.py

예상 출력은 다음과 같다. 각 단계에서 바뀐 상태가 하나씩 나타나고, 세 비교가 통과한 뒤 복원한 목록을 출력한다.

1단계: JSON 문자열 준비
  JSON 준비: False -> True
2단계: JSON 파일 저장
  파일 저장: False -> True
3단계: 할 일 목록 복원
  복원 개수: 0 -> 2
검증: 문자열 변환, 파일 내용, 복원 목록 모두 일치
복원한 할 일:
  1. 메모 정리 [미완료]
  2. 물 마시기 [완료]
임시 저장 파일 정리 완료

“파일 저장: False -> True”는 상태 변수의 변화다. 그것만으로 파일 내용의 정확성이 증명되는 것은 아니다. save_json에서 실제 파일 내용을 비교했기 때문에 그 상태를 기록할 수 있다. 이 구분은 작업 기록에서도 같다. 완료 표시를 붙이기 전에 그 표시를 뒷받침하는 실행 결과가 있어야 한다.

실무에서 자주 틀리는 것

이전 상태에 같은 사전을 연결한다

다음 코드는 실행되지만 원하는 전후 비교를 하지 못한다. before와 state가 같은 사전을 가리키므로 state를 수정하면 before에서 읽는 값도 바뀐다. 출력되는 False는 변화가 없었다는 증거가 아니라 이전 상태를 남기지 못한 결과다.

state = {"파일 저장": False}
before = state
state["파일 저장"] = True
print(before["파일 저장"] != state["파일 저장"])

이 예제처럼 값이 불리언인 사전은 copy로 이전 상태를 남긴다. 고친 코드는 True를 출력한다.

state = {"파일 저장": False}
before = state.copy()
state["파일 저장"] = True
print(before["파일 저장"] != state["파일 저장"])

파일 존재만 확인하고 저장을 끝냈다고 한다

다음 코드는 빈 목록을 써도 검증을 통과한다. 파일의 존재는 확인했지만 저장하려던 제목과 완료 값은 확인하지 않았기 때문이다.

from pathlib import Path
from tempfile import TemporaryDirectory

with TemporaryDirectory() as folder:
    path = Path(folder) / "tasks.json"
    path.write_text("[]", encoding="utf-8")
    assert path.exists()
    print("확인 통과")

고친 코드는 실제 입력을 저장하고 다시 읽은 목록과 비교한다. 실행 뒤 “확인 통과”가 출력되면 이 입력의 전체 값이 보존되었다는 근거가 된다.

import json
from pathlib import Path
from tempfile import TemporaryDirectory

tasks = [{"id": 1, "title": "메모 정리", "done": False}]

with TemporaryDirectory() as folder:
    path = Path(folder) / "tasks.json"
    text = json.dumps(tasks, ensure_ascii=False)
    path.write_text(text, encoding="utf-8")
    restored = json.loads(path.read_text(encoding="utf-8"))
    assert restored == tasks
    print("확인 통과")

저장 작업에 데이터 수정까지 섞는다

다음 함수는 JSON 문자열을 만드는 동시에 제목의 공백을 없앤다. 결과 문자열만 보고 저장이 성공했다고 판단하면, 원래 목록까지 바뀌었다는 사실을 놓칠 수 있다. 제목 정리가 필요하더라도 별도의 요청과 확인 기준으로 다룬다.

import json

def prepare_json(tasks):
    for task in tasks:
        task["title"] = task["title"].strip()
    return json.dumps(tasks, ensure_ascii=False)

tasks = [{"id": 1, "title": " 메모 정리 ", "done": False}]
prepare_json(tasks)
print(tasks[0]["title"] == " 메모 정리 ")

고친 함수는 문자열 변환만 맡는다. 원래 제목을 유지하는지 확인하는 마지막 출력은 True다. 요청 범위를 지키면 저장 기능을 확인하는 도중 제목 변경까지 판단할 필요가 줄어든다.

import json

def prepare_json(tasks):
    return json.dumps(tasks, ensure_ascii=False)

tasks = [{"id": 1, "title": " 메모 정리 ", "done": False}]
text = prepare_json(tasks)
assert json.loads(text) == tasks
print(tasks[0]["title"] == " 메모 정리 ")

한눈에 보기

작은 작업을 확인하고 다음 출발점으로 남기는 방법
상황할 일남길 근거
새 기능을 요청한다.한 가지 결과와 확인 기준을 정한다.요청 범위와 기대 결과
AI가 코드를 바꿨다.실행하고 요청 밖의 변경을 살핀다.실행 결과와 변경 내용
단계를 확인했다.작업 후 커밋을 남긴다.무엇을 확인했는지 적은 기록
기능을 실험한다.확인된 출발점에서 브랜치를 만든다.출발 커밋과 실험 기록
실험이 망가졌다.미기록 변경을 확인하고 돌아갈 방법을 고른다.보관할 수정과 복구 뒤 실행 결과
대화가 길어졌다.현재 코드와 다음 한 가지 작업을 전달한다.마지막 확인 결과와 인계 기록

연습 문제

  1. “저장 기능과 완료 표시 수정과 검색을 모두 추가하라”는 요청을 이번 장의 원칙에 맞게 바꾸라. 가장 먼저 보낼 요청 하나만 쓰고, 확인 기준을 포함하라.
  2. 완성 코드의 tasks에 id가 3이고 제목이 “책상 정리”이며 done이 False인 할 일을 추가하라. 달라지는 스냅숏 출력과 목록 출력을 예상한 뒤 실행해 비교하라.
  3. 완성 코드의 load_tasks에서 복원한 첫 할 일의 done을 True로 바꾸는 줄을 비교 직전에 넣으면 어떤 일이 생기는가. 확인한 뒤 그 변경을 제거하라.
  4. 파일 저장까지 확인한 뒤 새 대화를 시작한다고 가정하라. 현재 상태, 확인 근거, 다음 작업, 이번에 바꾸지 않을 기능을 포함한 인계 기록을 작성하라.

정답과 해설

  1. 첫 요청은 JSON 문자열 변환으로 제한할 수 있다. 완료 표시 수정과 검색은 이후의 별도 작업으로 남긴다.

    현재 할 일 목록을 JSON 문자열로 바꾸는 함수만 추가하라. 문자열을 다시 해석하면 id, title, done을 포함한 목록 전체가 원래 값과 같은지 확인하라. 파일 저장과 완료 표시 수정과 검색은 이번에 추가하지 말라.

    먼저 데이터 표현을 확인하면 이후 파일 저장에서 문제가 생겼을 때 변환과 기록을 구분해 살펴볼 수 있다.

  2. 복원 개수는 0에서 3으로 바뀐다. 복원 목록 마지막에는 “3. 책상 정리 [미완료]”가 추가된다. JSON 준비와 파일 저장의 불리언 변화는 그대로다. 전체 목록 비교가 있으므로 새 항목도 확인 대상에 들어간다.

  3. 첫 할 일의 원래 done은 False다. 복원한 값을 True로 바꾸면 restored와 original이 달라져 “복원한 목록이 다르다.”라는 AssertionError가 발생한다. 세 번째 단계의 스냅숏과 최종 성공 문구는 출력되지 않는다. 실패한 값을 눈으로 발견하기 전에 전체 목록 비교가 변경을 잡아낸다. 변경을 제거한 뒤 다시 실행해 원래 결과로 돌아왔는지 확인한다.

  4. 다음과 같이 현재 사실과 다음 요청을 분리해 적을 수 있다.

    현재 main.py는 JSON 문자열 변환과 UTF-8 파일 저장을 수행한다. 문자열을 다시 해석한 목록이 원래 목록과 같고, 저장 파일의 문자열도 준비한 문자열과 같은 것을 실행해 확인했다. 마지막 확인 상태를 커밋했다. 다음 작업은 저장 파일을 읽어 목록으로 복원하는 함수 추가다. 목록 전체를 원래 값과 비교하라. 제목 처리와 완료 표시 기능은 이번에 바꾸지 말라. 현재 main.py를 함께 제공한다.

    실제 기록에는 마지막 커밋의 식별자도 붙인다. AI가 확인된 코드에 접근할 수 있게 하고, 다음 단계가 끝났다고 판단할 기준을 명시하는 것이 핵심이다.

댓글 0

아직 댓글이 없습니다. 첫 댓글을 남겨 보세요.

댓글을 남기려면 로그인이 필요합니다.