입력부터 파일 저장, 데이터 검증, 집계와 보고서 출력까지 하나의 프로그램으로 설계하고 완성합니다.
결과물 · 학습 기록 분석 프로그램
◷ 약 6~9시간
✓ 문제의 입력·처리·출력·예외 조건을 코드 작성 전에 분리한다
✓ 검증·계산·저장·출력을 역할별 함수로 설계한다
✓ 리스트와 딕셔너리로 여러 기록을 집계하고 파일로 보존한다
✓ 공개·숨김 테스트와 경계 사례를 사용해 완성도를 확인한다
01 · 3~7분 개념 블록
01
프로젝트는 요구사항을 입출력 계약으로 바꾸는 일
큰 프로그램도 무엇을 입력받고, 어떤 규칙으로 처리하고, 정확히 무엇을 출력할지 적으면 작은 문제들의 연결로 바뀝니다. 구현 전에 정상 입력과 경계값, 잘못된 입력을 표로 정리합니다.
일상에서 보면여행 전에 출발지·경유지·도착지를 정하면 복잡한 이동도 구간별로 계획할 수 있는 것과 같습니다.
# 입력: 목표 시간, 기록 수, 과목/시간/상태
# 처리: 검증, 저장, 집계
# 출력: 합계, 완료 수, 최다 과목, 목표 상태
실행 결과
코드를 쓰기 전 프로그램의 계약이 세 단계로 정리됩니다.
한 줄씩 보기
입력 형식과 자료형을 정합니다.
필요한 처리 단계를 동사로 나눕니다.
출력 문구와 순서를 고정합니다.
직접 값 바꾸기시간이 정확히 목표와 같은 경우 목표 상태가 무엇이어야 하는지 적어 보세요.
흔한 실수출력 형식을 구현 중에 임의로 바꾸면 계산이 맞아도 자동 테스트를 통과하지 못합니다.
짧은 확인코드를 쓰기 전에 가장 먼저 정해야 할 세 가지는 무엇일까요?
02
데이터 흐름을 한 방향으로 연결한다
입력 문자열은 파싱을 거쳐 딕셔너리가 되고, 목록에 모인 뒤 파일에 저장됩니다. 다시 읽은 목록은 분석 함수를 거쳐 보고서 문자열이 됩니다. 단계마다 입력과 출력 형태가 분명해야 다음 단계와 안전하게 연결됩니다.
일상에서 보면원재료가 손질·조리·포장 단계를 한 방향으로 지나 완제품이 되는 생산선과 같습니다.
line = '파이썬 40 done'
record = parse_record(line)
save_records([record], 'records.csv')
records = load_records('records.csv')
analysis = analyze(records, 60)
실행 결과
문자열 → 딕셔너리 → 파일 → 딕셔너리 목록 → 분석 결과
한 줄씩 보기
한 줄 입력을 구조화합니다.
구조화한 목록을 저장합니다.
같은 형식으로 다시 복원합니다.
복원된 데이터만 분석에 전달합니다.
직접 값 바꾸기각 단계의 반환값이 어떤 자료형인지 옆에 주석으로 적어 보세요.
흔한 실수분석 함수가 input이나 파일 읽기까지 맡으면 독립적으로 테스트하기 어려워집니다.
짧은 확인analyze 함수가 받으면 가장 다루기 쉬운 입력 형태는 원문 문자열일까요, 딕셔너리 목록일까요?
03
검증은 잘못된 데이터가 안쪽으로 들어오는 것을 막는다
과목이 비었거나 시간이 음수인 기록은 집계 전에 거절해야 합니다. 검증 조건은 함수 앞부분에 두고 잘못된 경우를 먼저 반환하면 정상 처리 흐름이 단순해집니다.
일상에서 보면공장 입구에서 불량 재료를 걸러야 뒤의 모든 공정이 정상 재료만 다룰 수 있는 것과 같습니다.
def is_valid(record):
if record['subject'] == '':
return False
if record['minutes'] < 0:
return False
return True
실행 결과
과목이 있고 시간이 0 이상인 기록만 True
한 줄씩 보기
빈 과목을 먼저 거절합니다.
음수 시간을 거절합니다.
두 검사를 통과한 기록만 허용합니다.
직접 값 바꾸기상태가 done 또는 pending인지 확인하는 조건을 추가해 보세요.
흔한 실수0분은 음수가 아니므로 요구사항에서 허용했다면 유효한 경계값입니다.
짧은 확인-1분, 0분, 1분 중 경계값 검증에 꼭 포함할 값은 무엇일까요?
04
계산 함수와 파일 함수의 역할을 분리한다
save_records와 load_records는 저장 형식만 책임지고 analyze는 전달받은 목록의 계산만 책임집니다. 역할이 분리되면 파일 없이도 계산을 테스트하고, 계산을 바꾸지 않고 저장 형식만 교체할 수 있습니다.
일상에서 보면창고 담당자와 통계 담당자가 같은 자료 규격으로 협업하되 각자의 일을 따로 맡는 것과 같습니다.
def total_minutes(records):
return sum(record['minutes'] for record in records)
def save_records(records, path):
with open(path, 'w', encoding='utf-8') as file:
# 저장만 담당
pass
실행 결과
계산 규칙과 저장 방식이 서로 섞이지 않습니다.
한 줄씩 보기
계산 함수는 목록을 받아 숫자를 반환합니다.
저장 함수는 목록과 경로를 받습니다.
각 함수는 한 가지 변경 이유만 갖습니다.
직접 값 바꾸기보고서 문장을 만드는 build_report 함수는 계산과 출력 중 어느 역할에 가까운지 생각해 보세요.
흔한 실수모든 코드를 main 함수 하나에 넣으면 일부 계산만 따로 확인하기 어렵습니다.
짧은 확인과목별 합계 규칙을 바꿀 때 save_records도 수정해야 할까요?
05
경계 사례가 프로젝트의 완성도를 결정한다
평범한 입력 하나만 맞는 것으로는 충분하지 않습니다. 기록 한 개, 시간 0, 목표와 정확히 같은 합계, 동점 과목처럼 경계에 있는 사례를 실행해야 숨은 오류를 찾을 수 있습니다.
일상에서 보면우산이 가벼운 비뿐 아니라 강한 비와 바람에서도 버티는지 확인해야 제품 검사가 끝나는 것과 같습니다.