4장 기억의 과학과 나의 학습 OS
이번 주의 도구: 도구③ 스터디 플래너, 그리고 학습 OS의 첫 통합
다음 상황을 생각해 보자. 화공양론 중간고사를 두 주 앞둔 학생이 3주 전 강의노트를 펼친다. 그날 분명히 이해하고 넘어간 습도 계산인데, 지금 보니 처음 보는 내용처럼 낯설다. 불안해진 학생은 노트를 처음부터 다시 읽기 시작한다. 두 시간 뒤 남는 것은 "읽었다"는 안도감뿐이다. 노트를 덮고 문제를 풀어 보면 손이 멈춘다.
이 학생에게 부족한 것은 노력도 자료도 아니다. 2주차에 만든 도구① 강의노트 정리기는 수업 내용을 위키에 쌓아 왔고, 3주차의 도구② 플래시카드 생성기는 그 노트를 질문 카드로 바꿔 놓았다. 없는 것은 하나, "오늘 무엇을 복습해야 하는가"에 답해 주는 시스템이다.
이번 장이 그 마지막 조각을 채운다. 앞 절반에서는 뇌가 기억을 다루는 방식, 즉 왜 다시 읽기는 잘 남지 않고 꺼내 보기는 남는가를 다루고, 뒤 절반에서는 그 원리를 파일과 JSON이라는 기술로 옮긴다. 도구 셋이 파일 규약으로 맞물리는 순간, 흩어져 있던 산출물이 처음으로 하나의 학습 OS로 돌아가기 시작한다.
이 장에서 다루는 것
- 간격반복·인출연습·시험 효과의 원리를 과장 없이 정리한다
- 위키 노트의 성장단계 태그 #seed → #growing → #evergreen과 승격 기준을 정한다
- 파일 입출력과 JSON의 최소 문법을 익힌다. 도구가 꺼져도 기억을 잃지 않게 만드는 기술이다
- 도구③ 스터디 플래너를 만들고, 도구①→wiki→도구②→도구③ 파이프라인을 처음으로 통합한다
학습목표 이 장을 마치면 다음을 할 수 있다.
- 다시 읽기보다 인출연습이 장기 기억에 유리한 이유를 시험 효과로 설명할 수 있다.
- 복습 간격 규칙을 코드로 옮겨 임의의 노트의 다음 복습일을 계산할 수 있다.
- 위키 노트를 #seed·#growing·#evergreen 3단계로 분류하고 승격 기준을 적용할 수 있다.
- 파이썬으로 텍스트 파일과 JSON 파일을 읽고 쓸 수 있다.
- 도구①~③을 파일 규약으로 연결한 파이프라인을 구성하고, 이음매를 테스트로 검증할 수 있다.
이번 주 실습에서는 §4.3의 파일·JSON 문법과 §4.4의 파이프라인 설계를 사용해 도구③ 스터디 플래너를 만들고, 도구①·②와 처음으로 연결한다. 여러 파일이 규약으로 맞물려 저장소 전체가 하나로 작동해야 하는 이 형식은, 최종 시험(2시간 실기)이 요구하는 결과물의 축소판이다.
4.1 잊는 뇌, 꺼내는 뇌
시험을 망친 뒤 가장 흔한 진단은 "덜 읽어서"다. 그래서 다음 시험에는 더 여러 번 읽는다. 교육심리학의 진단은 방향이 반대다. 문제는 덜 읽은 것이 아니라 덜 꺼낸 것이다.
4.1.1 시험 효과: 꺼내는 행위가 기억을 만든다
기억에서 정보를 스스로 재구성해 꺼내는 행위를 인출(retrieval)이라 한다. 다시 읽기는 정보를 눈앞에 가져다 놓는 일이고, 인출은 눈앞에 없는 것을 머리에서 복원하는 일이다. 같은 내용을 다뤄도 뇌가 하는 일이 다르다.
인출 행위 자체가 그 기억을 강화하는 현상을 시험 효과(testing effect)라 하고, 시험 효과를 의도적으로 학습에 쓰는 전략을 인출연습(retrieval practice)이라 한다. 같은 시간을 쓴다면 다시 읽기보다 스스로 꺼내 보는 쪽이 장기 기억에 남는다는 것이 시험 효과 연구들의 공통 결론이다. 시험은 실력을 재는 장치이면서, 실력을 만드는 장치이기도 하다.
다시 읽기가 기대만큼 남지 않는 이유도 여기서 나온다. 세 번째 읽는 노트는 술술 넘어간다. 그 매끄러움은 "노트가 눈에 익었다"는 신호이지 "시험장에서 백지 위에 재구성할 수 있다"는 신호가 아니다. 익숙함을 이해로 착각하는 순간, 공부 시간과 성적이 따로 놀기 시작한다.
구체적인 장면으로 옮겨 보자. 3주차에 만든 플래시카드 한 장의 앞면에 "절대습도와 상대습도의 정의 차이는?"이 적혀 있다. 뒷면을 보기 전에 멈춰서 답을 말해 보는 그 몇 초에 학습이 일어난다. 답이 틀렸어도 좋다. 인출을 시도한 뒤에 본 정답은 그냥 읽은 정답보다 오래 남는다. 도구② 플래시카드 생성기는 처음부터 인출연습 장치로 설계된 것이다.
4.1.2 간격반복: '언제'의 설계
인출연습이 "어떻게"의 답이라면, 남은 질문은 "언제"다. 복습 사이의 간격을 점점 늘려 가며 인출을 반복하는 전략을 간격반복(spaced repetition)이라 한다.
논리는 이렇다. 방금 본 것을 다시 꺼내는 일은 쉽고, 쉬운 인출은 기억을 거의 바꾸지 못한다. 조금 잊힐 때까지 기다렸다가 꺼내면 인출은 힘들어지지만, 성공했을 때 기억이 훨씬 단단해진다. 학습에 오히려 유익한 이런 어려움을 바람직한 어려움(desirable difficulty)이라 한다. 몰아서 열 번 읽는 벼락치기가 시험 다음 날 증발하는 이유가, 뒤집으면 간격반복이 작동하는 이유다.
그렇다면 간격은 정확히 며칠이어야 하는가. 최적값은 자료의 난도와 사람에 따라 달라서, 하나의 정답 수열은 없다. 이 책은 출발 규칙으로 1일 → 3일 → 7일 → 14일을 채택한다. 복습에 성공할 때마다 다음 간격이 두 배 안팎으로 늘어난다는 구조가 본질이고, 수열의 값 자체는 조정 대상이다. 그래서 4.3절의 예제는 이 수열을 코드에 박아 넣지 않고 데이터로 분리한다. 나중에 본인 기록을 보고 값을 바꾸더라도 코드는 건드리지 않게 하기 위해서다.
한 가지는 분명히 해 두자. 간격반복은 이해를 대체하지 않는다. 이해한 것이 새 나가지 않게 막는 유지 장치로 보면 된다. 이해는 수업과 문제 풀이의 몫이고, 이 장의 도구는 그 뒤를 맡는다.
스스로 점검
- 노트가 "술술 읽힌다"는 느낌이 시험 준비 상태의 증거가 되지 못하는 이유는 무엇인가?
- 같은 카드를 하루에 10번 복습하는 것과 열흘에 걸쳐 3번 복습하는 것 중 간격반복 관점에서 어느 쪽이 유리하며, 그 근거는 무엇인가?
생각해보기 복습 시간이 하루 20분으로 고정되어 있다고 하자. 매일 덱 전체를 훑는 전략과, 간격 규칙이 골라 준 일부 카드만 깊게 인출하는 전략이 경쟁한다. "복습에서 빠지는 카드가 생기는 위험"과 "카드 한 장당 인출 난도"를 근거로 두 전략을 각각 방어해 보라. 어느 쪽 논거가 더 튼튼한가?
4.2 위키 구조화: 씨앗에서 상록수까지
인출연습은 카드에만 적용되는 원리가 아니다. 1주차부터 운영해 온 위키 노트에도 같은 원리를 심을 수 있다. 핵심 장치는 노트의 성장단계를 표시하는 세 개의 태그 #seed → #growing → #evergreen이다. 노트를 완성된 문서가 아니라 자라는 생물로 취급하는 이 관례를 이 수업의 위키 규약으로 채택한다.
| 태그 | 상태 | 승격 기준 (검증 가능한 형태) |
|---|---|---|
| #seed | 수업 당일 던져 넣은 날것의 메모 | 존재 자체. 한 줄 요약·에러 스크린샷·미완성 문장 전부 허용 |
| #growing | 내 언어로 소화된 노트 | 원문을 덮고 내 말로 다시 쓴 설명 + 예제나 실행 결과 1개 + 다른 노트로 가는 링크 1개 이상 |
| #evergreen | 재사용되는 지식 | 노트 2개 이상(타 과목 포함)과 연결 + 실습·시험에서 실제 인용 + "내가 틀렸던 것" 반영 |
승격 기준을 이렇게 박아 두는 이유는 승격 작업 자체가 인출연습이기 때문이다. #seed를 #growing으로 올리려면 원문을 덮고 내 말로 다시 써야 하고, 그것이 곧 인출이다. #growing을 #evergreen으로 올리려면 다른 노트·다른 과목과 연결해야 하는데, 연결은 나중에 그 지식을 꺼낼 인출 단서를 늘린다. 태그는 "이 노트에 어떤 학습 행위가 일어났는가"의 기록으로 쓴다.
채점과의 연결도 명확하다. 위키 채점 기준(마일스톤 3회, 주당 의미 있는 커밋)은 1장에 정리해 두었다. 마일스톤마다 evergreen 승격 수와 링크 밀도가 집계되며, 첫 집계인 마일스톤①이 바로 다음 주다.
도구가 위키를 읽으려면 사람만 알아보는 노트로는 부족하다. 노트 머리에 기계가 읽을 수 있는 형식으로 메타데이터를 적는 관례를 프런트매터(frontmatter)라 한다. 이 수업의 최소 규약은 세 필드다.
---
title: 이상기체
tags: [seed]
updated: 2026-09-27
---
# 이상기체
오늘 화공양론: PV = nRT가 잘 맞는 조건이 따로 있다. (내일 내 말로 다시 쓸 것)
구획선 --- 두 줄 사이가 프런트매터다. tags는 성장단계, updated는 마지막 수정일이다. 이 세 줄이 4.4절에서 도구들을 잇는 접착제가 된다.
스스로 점검
- #growing 승격 기준에 "다른 노트로 가는 링크 1개 이상"이 들어 있는 이유는 무엇인가?
- 어제 수업 노트 하나를 골라 보라. 표 4-1 기준으로 지금 어느 단계이며, 다음 단계로 올리려면 정확히 무엇을 추가해야 하는가?
4.3 파일 입출력과 JSON: 도구에 기억을 심는 법
지금까지 만든 도구에는 공통된 한계가 있다. 실행이 끝나면 전부 잊는다는 것이다. 변수에 담긴 값은 프로그램이 종료되는 순간 사라진다. 스터디 플래너는 "이 노트를 마지막으로 언제 복습했는가"를 다음 실행 때도 기억해야 하므로, 데이터를 프로그램 바깥에 남기는 수단이 필요하다. 프로그램이 꺼져도 데이터가 남는 성질을 영속성(persistence)이라 하고, 그 가장 기본적인 수단이 파일이다.
4.3.1 텍스트 파일 읽고 쓰기
파일을 열 때 받는 것은 파일 내용 자체가 아니라 파일로 통하는 손잡이다. 이 손잡이를 파일 핸들(file handle)이라 한다. 핸들을 통해 한 줄씩 읽고, 다 쓰면 닫는다. 최소 문형부터 보자.
fhand = open('wiki/이상기체.md', 'r', encoding='utf-8') # 'r' = 읽기 모드
count = 0
for line in fhand: # 파일 핸들을 순회하면 한 줄씩 나온다
count = count + 1
fhand.close() # 다 썼으면 닫는다
print('줄 수:', count)
encoding='utf-8'은 이 책의 전 코드 공통 규약이다. 윈도우의 기본 인코딩은 UTF-8이 아닐 수 있어서, 이 인자를 빼면 한글 노트가 깨져 읽히는 사고가 실습실에서 자주 일어난다. AI가 생성한 코드에 이 인자가 빠져 있으면 추가를 요구하라.
없는 파일을 읽기 모드로 열면 어떻게 되는가. 직접 겪어 보는 편이 빠르다.
>>> open('없는파일.md', 'r', encoding='utf-8')
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
FileNotFoundError: [Errno 2] No such file or directory: '없는파일.md'
프로그램이 그 자리에서 멈춘다. 에러 이름 FileNotFoundError를 기억해 두자. 실습에서 plan.json이 아직 없을 때 정확히 이 에러를 만나게 되고, 그때 "파일이 없으면 빈 계획으로 시작하라"는 예외 처리를 AI에게 요구하게 된다.
쓰기는 모드만 바꾸면 된다. 다만 쓰기 모드에는 함정이 있다.
fout = open('오늘의_복습.md', 'w', encoding='utf-8') # 'w' = 새로 쓰기
fout.write('# 2026-09-28 복습 목록\n') # \n = 줄바꿈 문자
fout.write('- 이상기체\n')
fout.close() # 닫아야 마지막 내용까지 디스크에 기록된다
'w' 모드는 파일이 이미 있으면 기존 내용을 전부 지우고 새로 시작한다. 매번 새로 만드는 "오늘의 복습 목록"에는 적합하지만, 쌓아 가야 하는 데이터에 쓰면 기존 내용을 잃는다. 끝에 덧붙이는 모드는 'a'다. 이 구분이 얼마나 중요한지는 아래 사례가 보여 준다.
4.3.2 JSON: 구조가 있는 기억
복습 이력은 줄글이 아니라 구조다. 노트마다 마지막 복습일과 진행 단계가 붙어 있어야 한다. 이런 데이터를 담는 파이썬 자료형이 딕셔너리(dictionary)다. 키(key)를 넣으면 값(value)이 나오는 자료형으로, plan['이상기체']처럼 대괄호 안에 키를 써서 값을 꺼낸다. 문법 해부는 5장의 몫이고, 지금은 읽을 수 있으면 충분하다.
딕셔너리와 리스트를 중첩해 만든 구조를 텍스트로 저장하고 교환하는 표준 형식을 JSON(JavaScript Object Notation)이라 한다. 이름과 달리 자바스크립트 전용이 아니며, 오늘날 프로그램 사이의 데이터 교환에서 사실상 표준으로 쓰인다. 파이썬 표준 라이브러리 json이 딕셔너리를 파일로, 파일을 다시 딕셔너리로 왕복시켜 준다.
import json
plan = {
'이상기체': {'last': '2026-09-27', 'stage': 0}, # 날짜 형식: 'YYYY-MM-DD'
'습도 계산': {'last': '2026-09-21', 'stage': 2}, # stage: 0부터 시작하는 정수
}
with open('plan.json', 'w', encoding='utf-8') as f:
json.dump(plan, f, ensure_ascii=False, indent=2) # 사람도 읽을 수 있게 저장
with open('plan.json', encoding='utf-8') as f:
plan2 = json.load(f) # 파일 → 딕셔너리 복원
print(plan2['습도 계산']['stage']) # 2
새 문형이 하나 등장했다. with open(...) as f: 블록은 블록이 끝나는 순간 파일을 자동으로 닫는다. close()를 잊는 실수를 구조적으로 차단하므로, 이후 모든 코드에서 이 문형을 기본으로 쓴다. ensure_ascii=False는 한글을 사람이 읽을 수 있는 그대로 저장하게 하는 옵션이다.
생성 코드에서 자주 마주치는 함정이 하나 있다. json.load에는 파일 핸들이 들어가야 하는데, 경로 문자열을 바로 넘기는 코드가 종종 생성된다. 결과는 이렇다.
>>> json.load('plan.json')
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
...
AttributeError: 'str' object has no attribute 'read'
에러 문구가 원인을 말해 주지 않는 전형적인 경우다. "문자열에는 read가 없다"는 말의 진짜 뜻은 "파일 핸들 자리에 문자열이 들어왔다"이다. 이 에러를 만나면 open()을 거쳤는지부터 확인하라.
예제 4.3-1 다음 복습일 계산기
문제 코드 4-4 형식의 plan.json을 읽어, 간격 규칙 1·3·7·14일에 따라 각 노트의 다음 복습일을 계산하고, 오늘(2026-09-28) 복습해야 할 노트 목록을 출력하는 프로그램을 만들어라. 단계가 간격 개수를 넘어선 노트는 마지막 간격(14일)을 계속 쓴다.
풀이 전략 문제를 셋으로 분해한다. ① JSON 읽기는 코드 4-4의 문형을 그대로 재사용하므로 직접 쓴다. ② 날짜 계산과 오늘 목록 필터는 표준 라이브러리 datetime이 필요하므로 AI에게 함수 단위로 맡긴다. ③ 검증, 즉 달력 손 대조와 극한값 주입은 직접 한다. 프롬프트에는 데이터 형식(날짜 문자열, stage 정수)과 경계 조건(단계 초과)을 명시한다.
plan.json에 노트별로 last(마지막 복습일, 'YYYY-MM-DD' 문자열)와 stage(0부터 시작하는 정수)가 있어. 복습 간격 [1, 3, 7, 14]일 규칙으로 다음 복습일을 계산하는 함수 next_review(last, stage)와, 오늘 날짜를 받아 복습할 노트 이름 목록을 돌려주는 함수 due_today(plan, today)를 만들어 줘. 표준 라이브러리만 쓰고, stage가 간격 개수를 넘으면 마지막 간격을 계속 쓰게 하고, 주석은 한국어로 달아 줘.
from datetime import date, timedelta
INTERVALS = [1, 3, 7, 14] # 복습 간격 (일) — 규칙은 데이터로 분리
def next_review(last, stage):
"""마지막 복습일(ISO 문자열)과 단계로 다음 복습일을 계산한다."""
last_day = date.fromisoformat(last) # 'YYYY-MM-DD' → date 객체
idx = min(stage, len(INTERVALS) - 1) # 단계 초과 시 마지막 간격 유지
return last_day + timedelta(days=INTERVALS[idx])
def due_today(plan, today):
"""오늘 복습해야 할 노트 이름 목록을 돌려준다."""
due = []
for name in plan: # 딕셔너리 순회 = 키 순회
item = plan[name]
if next_review(item['last'], item['stage']) <= today:
due.append(name) # 예정일이 지난 노트도 포함
return due
검증 코멘트: next_review가 문자열이 아닌 date 객체를 돌려주므로 비교(<=)가 날짜끼리 이루어진다. 문자열 비교였다면 형식이 다를 때 조용히 틀렸을 것이다. 단계 초과를 min으로 막은 것 확인. 부등호가 <=라서 예정일이 "지난" 노트(밀린 복습)도 목록에 포함된다(의도와 일치).
실행해 보자.
>>> with open('plan.json', encoding='utf-8') as f:
... plan = json.load(f)
...
>>> due_today(plan, date(2026, 9, 28))
['이상기체', '습도 계산']
'이상기체'는 9월 27일에 0단계였으니 1일 뒤인 28일이 예정일이고, '습도 계산'은 21일에 2단계였으니 7일 뒤인 28일이 예정일이다. 둘 다 오늘 목록에 들어온 것이 맞다.
- 형식 체크: 입력은 'YYYY-MM-DD' 문자열, 내부 계산은 date 객체, 간격은 일 단위(
timedelta(days=...))다. 형식 혼용이 없다. - 대조값: 달력 손 계산으로 9월 21일의 7일 뒤는 9월 28일이다. 함수 출력과 일치한다.
- 극한값: stage=99를 넣어도 IndexError 없이 마지막 간격 14일이 적용되고, 빈 계획
{}에는 빈 목록[]가 반환된다. 실행으로 확인했다.
분석 배울 것은 두 가지다. 첫째, 규칙(INTERVALS)을 데이터로 분리하면 간격 조정이 코드 수정 없이 이루어진다. 4.1절에서 "수열의 값은 조정 대상"이라 한 말의 코드 번역이다. 둘째, 생성 코드에서 가장 먼저 의심할 곳은 경계 조건이다. 부등호 하나(<였다면 밀린 복습이 영영 목록에 안 뜬다)와 단계 초과 처리(min이 없었다면 크래시)가 도구의 동작을 좌우했다. What-if: INTERVALS를 [1, 2, 4, 8]로 바꾸면 due 목록이 어떻게 달라질지 예측한 뒤, 실행으로 확인해 보라.
스스로 점검
- 코드 4-3에서
'w'를'a'로 바꾸고 두 번 실행하면 파일 내용은 어떻게 달라지는가? json.dump와json.load의 인자에 공통으로 들어가는 것은 무엇이며, 그 자리에 경로 문자열을 넣으면 어떤 에러가 나는가?
4.4 학습 OS: 도구를 파이프라인으로 잇는다
도구가 셋이 되었다. 이제 이들을 하나의 시스템으로 묶는다. 한 도구의 출력 파일이 다음 도구의 입력이 되도록 연결한 사슬을 파이프라인(pipeline)이라 한다.
강의노트(텍스트 붙여넣기)
│ 도구① 강의노트 정리기
▼
wiki/*.md (프런트매터: title, tags, updated)
│ │
│ 도구② 플래시카드 생성기 │ 도구③ 스터디 플래너 ←── plan.json (복습 이력)
▼ ▼
Anki 덱 오늘의_복습.md
이 파이프라인과 위키, 그리고 그것을 매일 굴리는 복습 습관 전체를 이 수업에서는 학습 OS라 부른다. 컴퓨터의 운영체제가 프로그램에게 저장과 메모리를 제공하듯, 학습 OS는 공부에게 저장(위키)·인출(플래시카드)·스케줄(플래너)을 제공한다. 시스템의 요점은 순환이다. 도구를 쓰면 위키가 자라고, 위키가 자라면 카드와 복습 목록이 저절로 늘어난다.
통합의 실체는 규약이다. 폴더 구조, 파일 이름, 프런트매터 필드, plan.json의 스키마가 도구 사이의 인터페이스다. 도구①이 tags 필드를 빼먹으면 도구③이 위키를 읽지 못한다. 그래서 규약은 README에 명문화하고, AI에게 연결 코드를 시킬 때마다 그 규약을 프롬프트에 그대로 붙여넣는다. 이것이 파이프라인 작업의 표준 프롬프트 패턴이다(요약 S 4-4).
- 문제 정의
- 분해
- 프롬프트
- 생성 코드 읽기
- 테스트
- 화공 검증
파이프라인의 고장은 각 도구 안이 아니라 이음매에서 난다. 도구 셋이 각자 완벽히 돌아가도, 도구①이 쓰는 날짜 형식과 도구③이 기대하는 형식이 다르면 시스템 전체가 멈춘다. 그래서 통합 작업의 마지막은 언제나 이음매 검사다.
예제 4.4-1 파이프라인 이음매 검사기
문제 위키 폴더의 노트 목록과 plan.json의 등록 목록을 비교해, ① 위키에는 있는데 계획에 없는 노트(복습 누락 위험)와 ② 계획에는 있는데 위키에서 사라진 노트(유령 항목)를 찾아 보고하는 검사 스크립트를 만들어라.
풀이 전략 폴더에서 파일 목록을 얻는 부분은 pathlib이 필요하므로 AI에게 맡기되, 비교 논리는 지금 읽을 수 있는 for와 if로 풀어 달라고 프롬프트에 명시한다. 검증은 정상 상태가 아니라 일부러 고장 낸 상태에서 한다. 검사기가 실제로 고장을 잡는지 봐야 하기 때문이다.
위키 폴더의 .md 파일 이름 목록(확장자 제외)과 plan.json의 키 목록을 비교해서, 한쪽에만 있는 이름을 각각 리스트로 돌려주는 함수를 만들어 줘. 표준 라이브러리만 쓰고, 비교 부분은 set 말고 for와 if로 풀어 써 줘. 주석은 한국어로.
import json
from pathlib import Path
def check_seams(wiki_dir, plan_file):
"""wiki 노트와 복습 계획의 이음매를 검사한다."""
with open(plan_file, encoding='utf-8') as f:
plan = json.load(f)
notes = []
for p in Path(wiki_dir).glob('*.md'): # wiki 폴더의 모든 노트
notes.append(p.stem) # p.stem = 확장자를 뺀 이름
missing = [] # wiki에 있는데 계획에 없음
for name in notes:
if name not in plan:
missing.append(name)
ghost = [] # 계획에 있는데 wiki에 없음
for name in plan:
if name not in notes:
ghost.append(name)
return missing, ghost
검증 코멘트: 일부러 위키에 새 노트 증류.md를 만들고 실행하자 missing에 '증류'가 잡혔다. 이번에는 plan.json은 그대로 두고 노트 파일 하나를 지우자 ghost에 잡혔다. 고장을 심어야 검사기가 살아 있음을 확인할 수 있다. 비교 대상의 형식도 확인했다. 파일 쪽은 p.stem(확장자 제거), plan 쪽은 키이므로 '이상기체.md'와 '이상기체'를 비교하는 불일치가 없다.
- 형식 체크: 양쪽 다 "확장자 없는 노트 이름"으로 통일해 비교한다.
p.stem이 이를 보장한다. - 대조값: 노트 3개짜리 미니 위키로 손으로 센 결과(누락 1건, 유령 0건)와 함수 출력이 일치한다.
- 극한값: 빈 위키 폴더 → (빈 목록, 전체 유령), 빈 plan.json → (전체 누락, 빈 목록). 둘 다 크래시 없이 반환된다.
분석 통합 테스트의 본체는 "고장을 심고 검출되는지 확인"이다. 검사기가 아무것도 잡지 못했다는 결과는 파이프라인이 건강하다는 뜻일 수도, 검사기 자신이 작동하지 않는다는 뜻일 수도 있다. 후자를 배제하려면 고장을 직접 심어 보아야 한다. What-if: 프런트매터의 updated가 plan의 last보다 최신인 노트(복습 후 수정된 노트)를 찾는 세 번째 검사를 AI로 추가 생성해 보라.
생각해보기 학습 OS의 어느 고리가 끊겼을 때 시스템 전체가 죽는가? 그림 4-1에서 단일 장애점이라 생각하는 지점을 하나 짚고, 그 고리가 끊겼음을 사람이 아니라 코드가 알아채게 만드는 방법을 두 가지 제안하라.
실습 4: 도구③ 스터디 플래너 + 학습 OS 미니 통합
예상 소요 90분 + 퇴실 전 위키 15분. 타이머는 없다. 시간제한과 공개 지목 구술은 5주차부터 시작된다. 이번 주 결과는 통과/재도전 2단계로만 처리하며, 재도전은 다음 실습 전까지 허용된다.
준비물 도구① 강의노트 정리기 저장소, 도구② 플래시카드 생성기 저장소, 위키 저장소 최신 상태(git pull), 파이썬 실행 확인(python --version). 이번 실습은 새 코드를 처음부터 짜는 것이 아니라 이전 도구 두 개를 재사용하는 것이 절반이다.
- 실습 4.1 데이터 규약을 확정하라. 프런트매터 필드(코드 4-1)와 plan.json 스키마(코드 4-4)를 플래너 저장소 README에 표로 기록하라. (완료 확인: README에 필드 이름·형식·쓰는 도구/읽는 도구가 명시된 표가 있다) 커밋하라.
- 실습 4.2 플래너 코어를 생성하라. 예제 4.3-1의 프롬프트를 출발점으로
next_review·due_today를 만들고, 오늘의 복습 목록을 오늘의_복습.md 파일로 저장하는 기능까지 붙여라. (완료 확인: 실행 한 번으로 오늘의_복습.md가 생성된다) 커밋하라. - 실습 4.3 위키 읽기를 연결하라. 플래너가 위키 폴더의 프런트매터를 읽어, plan.json에 없는 새 노트를 stage 0으로 자동 등록하게 하라. plan.json이 아직 없을 때 FileNotFoundError로 죽지 않고 빈 계획으로 시작하는 처리를 포함하라. (완료 확인: 새 노트 1개를 만들고 실행하면 plan.json에 항목이 늘어난다) 커밋하라.
- 실습 4.4 파이프라인 데모를 실행하라. 이번 주 강의노트 1편을 도구①로 정리하고, 위키에 커밋하고, 도구②로 덱을 만들고, 도구③이 그 노트를 오늘의 복습 목록에 올리는 전 과정을 순서대로 실행한 뒤, 각 단계의 산출물 경로를 README에 기록하라. (완료 확인: README의 데모 기록 4단계가 실제 파일과 일치한다)
- 실습 4.5 이음매를 검사하라. 예제 4.4-1의 검사기를 본인 위키에 돌리고, 불일치가 나오면 원인을 고쳐 누락 0건·유령 0건을 만들어라. 검증 3종(형식·대조값·극한값) 수행 내용을 README에 기록하라. 커밋하라.
- 실습 4.6 페어에게 1분 설명하라. 그림 4-1을 손으로 그려 가며 "내 학습 OS에서 파일이 어떻게 흐르는지"를 설명하라. (완료 확인: 페어가 plan.json의 역할을 되물었을 때 답할 수 있다)
완성 기준 아래를 모두 만족하면 통과다.
- 플래너 실행 한 번으로 오늘의 복습 목록이 파일로 생성된다
- 플래너를 두 번 연속 실행해도 plan.json의 기존 항목이 사라지지 않는다 (실행 전 사본과 항목 수 비교로 증명)
- 새 위키 노트가 자동으로 plan.json에 등록된다
- 도구①→wiki→도구②→도구③ 데모 기록이 README에 있다
- 검증 3종 기록이 README에 있다
- 의미 있는 커밋 3건 이상
막혔는가?
- 에러가 나면: 에러 메시지 전문을 그대로 붙여넣고 "이 에러의 원인 후보 3개와 각각의 확인 방법을 알려 줘"라고 물어라. 요약해서 옮기지 마라. 전문에 단서가 있다.
- 프런트매터 파싱이 막히면: "YAML 라이브러리 없이, --- 구획선 사이의 key: value 줄만 읽는 최소 파서를 만들어 줘. 외부 라이브러리는 쓰지 마." 라고 요구하라.
- plan.json이 자꾸 초기화되면: "이 코드가 파일을 여는 모드를 전부 표로 정리하고, 기존 데이터를 보존하려면 어느 줄을 어떻게 바꿔야 하는지 설명해 줘."라고 물어라. 표 4-2와 대조하라.
퇴실 전 위키 기록(15분) 오늘 만든 것·막힌 것·틀린 것 중 1건을 위키에 커밋해야 퇴실이다. 오늘의 유력 후보는 'w'와 'a'를 헷갈려 잃을 뻔한(또는 잃은) 데이터 이야기다. 에러나 사고 화면을 그대로 붙여라.
요약
- S 4-1 (규칙의 데이터 분리)
INTERVALS = [1, 3, 7, 14]. 정책은 코드가 아니라 데이터에 둔다. 값이 바뀌어도 코드는 그대로다. - S 4-2 (파일 열기 표준 문형)
with open(경로, 모드, encoding='utf-8') as f:. 인코딩을 명시하고 파일을 자동으로 닫는다. - S 4-3 (JSON 왕복) 저장
json.dump(자료, f, ensure_ascii=False, indent=2)/ 복원자료 = json.load(f). 인자는 둘 다 파일 핸들이다. - S 4-4 (규약 프롬프트 패턴) "아래 데이터 규약을 그대로 지켜라: (README의 규약 붙여넣기)". 파이프라인 연결 코드를 시킬 때의 고정 서두다.
| 모드 | 동작 | 파일이 이미 있으면 | 파일이 없으면 | 플래너에서 쓰는 곳 |
|---|---|---|---|---|
'r' | 읽기 | 그대로 읽는다 | FileNotFoundError | 위키 노트·plan.json 읽기 |
'w' | 새로 쓰기 | 내용을 지우고 새로 쓴다 | 새로 만든다 | 오늘의_복습.md 생성 |
'a' | 덧붙이기 | 끝에 이어 쓴다 | 새로 만든다 | 복습 로그 누적 |
이 장으로 Phase 1이 끝났다. AI 협업 사이클에서는 ② 분해(한 문제를 도구 경계로 나누기)와 ⑤ 테스트(이음매에 고장을 심어 검사하기)가 강해졌고, 도구 사다리는 9개 중 3개가 찼다. 무엇보다 도구들이 처음으로 서로를 읽기 시작했다. 이제 위키에 쌓는 모든 노트는 카드가 되고 복습 일정이 된다. 다음 주부터는 이 학습 OS를 밑에 깔고, 화공 도구의 사다리를 오르기 시작한다.
용어 정리
- 인출(retrieval)
- 기억에서 정보를 스스로 재구성해 꺼내는 행위. 다시 읽기와 구별된다.
- 시험 효과(testing effect)
- 인출 행위 자체가 그 기억을 강화하는 현상.
- 인출연습(retrieval practice)
- 시험 효과를 의도적으로 학습 전략으로 쓰는 것. 플래시카드가 대표 장치다.
- 간격반복(spaced repetition)
- 복습 사이의 간격을 점점 늘려 가며 인출을 반복하는 학습 전략.
- 바람직한 어려움(desirable difficulty)
- 인출을 힘들게 하는 대신 성공 시 기억을 더 강화하는, 학습에 유익한 어려움.
- 영속성(persistence)
- 프로그램이 종료되어도 데이터가 남는 성질. 파일이 기본 수단이다.
- 파일 핸들(file handle)
- 열린 파일로 통하는 손잡이 객체. 내용 자체가 아니라 읽고 쓰는 통로다.
- 딕셔너리(dictionary)
- 키를 넣으면 값이 나오는 파이썬 자료형. 키-값 쌍의 모음.
- JSON(JavaScript Object Notation)
- 딕셔너리·리스트의 중첩 구조를 텍스트로 저장·교환하는 표준 형식.
- 프런트매터(frontmatter)
- 노트 머리의 구획선 사이에 기계가 읽을 수 있는 메타데이터를 적는 관례.
- 파이프라인(pipeline)
- 한 도구의 출력 파일이 다음 도구의 입력이 되도록 연결한 사슬.
- 학습 OS
- 도구 파이프라인 + 위키 + 복습 습관 전체를 가리키는 이 수업의 용어. 공부에 저장·인출·스케줄을 제공한다.
연습문제
Q군: 개념·읽기 (AI 없이 풀 것: 시험1 대비)
- Q4.1 다시 읽기 중심의 시험 공부가 시험장에서 실패하는 이유를 인출연습 관점에서 3문장 이내로 설명하라.
- Q4.2 코드 4-2를 실행할 때 wiki/이상기체.md 파일이 없다면 무슨 일이 벌어지는가? 발생하는 에러의 이름까지 정확히 쓰라.
- Q4.3 (EiPE) 코드 4-5의 두 함수가 하는 일을 한국어 2~3문장으로 설명하라. 그리고
min(stage, len(INTERVALS) - 1)이 없다면 어떤 입력에서 어떤 에러가 나는지 밝혀라.
P군: 제작·계산 (AI 사용 전제: 시험2·최종 시험 대비)
- P4.1 과목마다 다른 복습 간격을 쓰도록 플래너를 확장하라. 간격 수열을 코드가 아니라 별도 JSON 설정 파일에 두고, 검증 3종을 README에 기록하라.
- P4.2 "복습 완료" 기능을 추가하라: 오늘 복습을 마친 노트의 stage를 1 올리고 last를 오늘로 갱신한다. 두 번 연속 실행해도 이력이 사라지지 않음을 실행 전후 파일 비교로 증명하라.
- P4.3 (메타인지) 페어의 플래너에 심을 수 있는 "조용한 데이터 손상 버그"를 두 가지 설계하고, 각각이 검증 루틴 3종 중 무엇에 걸리는지 표로 정리하라. 실제로 서로 교환해 심고, 상대의 버그를 잡아내라.
위키 기록 과제 이번 주 필수 커밋 3항: ① 이 장의 핵심을 1페이지로 정리한 노트(#seed로 시작하되, 표 4-1 기준으로 #growing까지 올려 보라) ② "내가 틀렸던 것" 1건. 파일 모드나 JSON에서 만난 에러 화면을 그대로 붙이고 원인을 한 문장으로 적어라. ③ 타 과목 연결 1건. 화공양론 시험범위 노트에 복습 간격을 실제로 적용한 기록을 남겨라. 주당 의미 커밋 1건 이상이 채점 지표이며, 다음 주에 위키 마일스톤①(2점)의 첫 집계가 돌아간다.