Devin.KR

정리하기 - 함수 나누고 이름 바꾸기

개발자KR 조회 0

이 장에서 배우는 것

가계부에 기능을 하나씩 붙이다 보면 처음에는 짧았던 코드가 길어진다. 월을 고르는 부분, 금액을 더하는 부분, 보고서 문장을 만드는 부분이 한곳에 모인다. 실행은 되지만, 어느 부분을 고쳐야 하는지 찾는 시간이 늘어난다. 이번에는 기능을 추가하지 않고 코드의 모양을 정리한다.

동작을 유지하면서 코드의 내부 구성을 바꾸는 일을 리팩터링(refactoring)이라고 한다. 이름이 낯설어도 할 일은 구체적이다. 먼저 현재 결과를 검사로 남기고, 길어진 코드를 작은 함수로 나눈 뒤, 같은 결과가 나오는지 확인한다. 마지막으로 바뀐 줄을 직접 읽는다. AI가 “동작은 같다”라고 답한 것만으로 작업을 끝내지 않는다.

  • 한 함수에 모인 일을 역할별로 나누도록 요청한다.
  • 검사에 쓸 기대 결과를 먼저 정하고 정리 전후를 비교한다.
  • 함수와 변수에 역할을 드러내는 이름을 붙인다.
  • 변경 내역(diff)을 읽으며 의도하지 않은 수정이 없는지 확인한다.

이번 장에서 만들 것

이번 장의 결과물은 혼자 실행되는 main.py다. 정해진 지출 네 건을 임시 폴더의 JSON 파일에 저장하고 다시 읽는다. 이어서 검사 여섯 개를 실행하고, 2026년 10월의 분류별 합계와 전체 합계를 출력한다. 사용자 입력이나 별도 준비 파일은 필요하지 않다. 표준 라이브러리만 사용한다.

이번 작업에서는 월별 보고서 부분만 정리 대상으로 삼는다. 앞서 만든 가계부의 모든 기능을 다시 늘어놓으면 어떤 변화가 정리 작업 때문인지 확인하기 어렵다. 대신 저장과 불러오기, 월별 합계, 검사라는 필요한 앞부분을 포함한다. 앞 장 파일이 없어도 실행할 수 있다.

실무에서는 “보고서 문구 하나만 바꾸려는데 계산 코드까지 읽어야 하는” 상황이 자주 생긴다. 이 예제에서도 정리 전 함수는 월 선택부터 출력 문장 작성까지 맡는다. 이를 세 역할로 나누되, 정렬 순서와 빈 달의 표시까지 유지하는 것이 목표다.

이번 정리에서 유지할 약속
항목고정할 내용확인 방법
입력지출 네 건, 대상 월 2026-10준비된 목록을 읽는다.
계산대상 월만 골라 분류별로 더한다.식비 20,000원, 교통 3,000원을 확인한다.
표시분류 이름순, 쉼표가 있는 금액기대 문자열과 전체를 비교한다.
실행검사 뒤 보고서를 출력하고 종료한다.실행 결과를 아래 예시와 맞춘다.

따라하기

아래 요청문을 순서대로 AI에게 보낸다. AI의 실제 답과 코드 구성은 매번 다를 수 있다. 각 단계의 답변 요지는 방향을 확인하기 위한 예시다. 다른 코드가 나오면 이름이 같은지만 보지 말고, 직접 확인할 것에 적힌 동작을 기준으로 판단한다. 대화를 따라 만든 코드가 헷갈리면 뒤의 완성 코드로 main.py를 맞춘 뒤 다시 진행한다.

단계 1. 바꾸지 않을 결과부터 정한다

먼저 작업 범위를 줄인다. “코드를 깔끔하게 해 줘”라는 요청만 보내면 AI가 파일을 여러 개로 나누거나 출력 문구를 바꿀 수 있다. 이번에는 한 파일 안에서 월별 보고서의 역할만 나눈다는 조건을 준다.

혼자 쓰는 가계부의 월별 보고서 코드를 정리하려 한다. Python 3.12 이상, 표준 라이브러리만 사용하고 main.py 한 파일로 작성해 줘. 입력은 2026-10-02 식비 12000원, 2026-10-03 교통 3000원, 2026-10-05 식비 8000원, 2026-09-30 식비 5000원이다. 날짜와 분류, 금액을 가진 사전 목록으로 준비해 줘. 대상 월은 2026-10이다. 새 기능은 추가하지 말고, 유지해야 할 계산 결과와 표시 순서를 먼저 적어 줘.

답변 요지 예시다. 10월 식비는 20,000원, 교통은 3,000원, 전체는 23,000원이다. 9월 기록은 제외한다. 분류 이름순으로 교통 다음 식비를 표시한다. 날짜와 금액은 고정한다.

직접 확인할 것. 계산은 손으로도 확인한다. 12,000에 8,000을 더하면 20,000이고, 여기에 3,000을 더하면 23,000이다. 준비용 main.py를 받았다면 터미널에서 다음 명령을 실행한다.

python3 main.py

기대 결과는 10월 합계가 23,000원이며 9월의 5,000원이 포함되지 않는 것이다. 아직 출력 장식이 다르더라도 다음 단계에서 사용할 정확한 문자열을 먼저 합의한다.

체크포인트. 입력 네 건과 합계 23,000원을 설명할 수 있으면 다음으로 간다. 이 숫자가 다르면 함수 나누기를 시작하지 않는다.

단계 2. 정리 전 함수와 기대 문자열을 남긴다

정리 전 결과를 비교 대상으로 보관한다. 이 예제에서는 여러 일을 한꺼번에 하는 함수를 legacy_report라는 이름으로 둔다. legacy는 여기서 이전 구현을 가리킨다. 이 함수는 학습 중 비교용으로만 남기는 코드다.

월 선택, 분류별 합계, 보고서 문자열 만들기를 한 함수에서 처리하는 legacy_report를 만들어 줘. 함수는 출력하지 않고 문자열을 반환해야 한다. 보고서 첫 줄은 “2026-10 지출 보고서”, 다음은 “교통: 3,000원”, “식비: 20,000원”, 마지막은 “합계: 23,000원”이다. 줄은 줄바꿈 하나로 연결하고 끝에는 줄바꿈을 붙이지 마. 이 결과를 expected_report라는 고정 문자열로도 남겨 줘.

답변 요지 예시다. legacy_report는 입력 목록을 읽어 대상 월의 금액을 모은 뒤 문자열을 반환한다. expected_report는 함수로 계산하지 않고 정해진 문장을 직접 적는다. 두 문자열을 비교할 준비가 된다.

직접 확인할 것. 터미널에서 문법 검사와 실행을 차례로 한다.

python3 -W error -m py_compile main.py
python3 main.py

첫 명령은 정상일 때 출력 없이 끝난다. 두 번째 명령에서는 합의한 네 줄이 나와야 한다. 문법 검사는 파일을 읽고 파이썬 문법에 맞는지 확인하지만 합계의 정답까지 판단하지는 않는다.

체크포인트. 기존 함수가 고정 문자열과 같고, 문자열 마지막에 불필요한 빈 줄이 없으면 다음으로 간다.

단계 3. 실행할 검사 여섯 개를 붙인다

앞 장에서 만든 확인 목록을 이번 변경에도 적용한다. 정리 전 함수와 정리 후 함수가 같다는 검사만 두면 두 함수가 함께 틀렸을 때 놓칠 수 있다. 손으로 정한 기대 문자열도 별도로 비교한다. 또한 기록이 없는 달과 원본 보존도 살핀다.

검사는 외부 패키지 없이 실행되게 해 줘. 최종적으로 여섯 가지를 확인하려 한다. 기존 함수와 고정 기대 문자열의 일치, 새 함수와 고정 기대 문자열의 일치, 두 함수 결과의 일치, 기록이 없는 달의 “기록 없음”과 “합계: 0원”, 입력 목록과 내부 사전의 값이 바뀌지 않음, JSON 저장 전후 값의 일치다. 검사에 실패하면 RuntimeError로 이유를 알리고, 모두 성공하면 “검사: 6개 통과”를 한 번 출력해 줘. 아직 새 함수가 없다면 기존 함수로 첫 검사부터 실행할 수 있게 해 줘.

답변 요지 예시다. 검사는 현재 구현부터 확인하고, 새 함수가 생기면 나머지 비교를 연결한다. 실패하면 실행을 중단하므로 통과 문구가 나오지 않는다. 성공 개수는 실제로 실행한 비교 수로 센다.

직접 확인할 것. 현재까지 만든 main.py를 실행한다.

python3 main.py

기존 함수만 있는 시점에는 연결된 검사만 통과하면 된다. 이때부터 여섯 개 통과라고 미리 표시해서는 안 된다. 완성 코드에서는 여섯 비교를 실제로 실행한 다음 그 수를 출력한다.

체크포인트. 기대 합계를 일부러 23,001원으로 고치면 검사가 실패하는지 확인한다. 확인 뒤에는 23,000원으로 되돌린다. 틀린 기대값을 잡아내야 검사에 의미가 있다.

단계 4. 일을 세 역할로 나눈다

함수는 이름을 붙여 다시 부를 수 있게 묶은 코드다. 여기서는 월의 기록을 고르는 함수, 분류별로 더하는 함수, 보고서 문장을 만드는 함수를 둔다. 함수마다 무엇을 받고 무엇을 돌려주는지 요청문에 적는다.

legacy_report는 비교용으로 유지하고 새 구현을 추가해 줘. select_month는 기록 목록과 월을 받아 그 달의 기록 목록을 반환한다. sum_by_category는 기록 목록을 받아 분류별 합계 사전을 반환한다. make_report는 앞의 두 함수를 호출하고 기존과 같은 보고서 문자열을 반환한다. 원본 목록과 사전의 값을 바꾸지 마. 분류 이름순, 금액의 쉼표, 빈 달 표시를 유지하고 여섯 검사를 모두 연결해 줘.

답변 요지 예시다. 기존 함수의 월 선택과 합계 계산을 별도 함수로 옮긴다. make_report는 처리 흐름과 문장 구성을 맡는다. 새로운 검사를 실행해 결과가 같은지 확인한다.

직접 확인할 것. 다음 두 명령을 다시 실행한다.

python3 -W error -m py_compile main.py
python3 main.py

문법 검사에 출력이 없어야 하고, 프로그램 실행에서는 “검사: 6개 통과” 다음에 보고서 네 줄이 나와야 한다. 검사가 실패하면 다음 정리 작업을 더하지 않는다. 실패 메시지가 가리키는 결과부터 비교한다.

체크포인트. 세 함수의 역할을 “고른다, 더한다, 문장으로 만든다”로 구별할 수 있고 검사 여섯 개가 통과하면 다음으로 간다.

하나에 모였던 월 선택과 합계 계산을 함수로 나누어 같은 보고서를 만든다

단계 5. 이름만 읽고 역할을 확인한다

코드를 나눈 뒤 이름을 살핀다. data나 result 같은 이름이 늘 잘못된 것은 아니다. 다만 같은 함수 안에 여러 종류의 자료가 있으면 무엇을 담는지 구별하기 어렵다. 이번에는 records, month_records, totals처럼 담긴 값을 읽을 수 있는 이름을 쓴다. 이름은 길이보다 구별되는 역할이 중요하다.

새 구현의 이름을 검토해 줘. data, x, result처럼 뜻이 넓은 이름이 있다면 records, month_records, totals, lines처럼 담긴 내용을 드러내는 이름으로 바꿔 줘. select_month, sum_by_category, make_report는 유지해 줘. JSON에 저장되는 date, category, amount 키와 보고서 문자열은 바꾸지 마. 바꾼 이름과 이유를 표로 설명해 줘.

답변 요지 예시다. 함수 안에서만 쓰는 변수 이름은 더 구체적으로 바꾼다. 저장 파일의 키는 자료를 읽는 약속이므로 유지한다. 함수 이름을 바꾼 경우에는 호출하는 곳도 함께 확인한다.

직접 확인할 것. main.py에서 날짜 키가 여전히 date이고 금액 키가 amount인지 읽는다. 그다음 실행한다.

python3 main.py

기대 결과는 이름을 다듬기 전과 같다. 화면에 보이는 “교통”, “식비”, “합계”까지 바뀌었다면 변수 이름만 정리한 것이 아니다.

체크포인트. 새 이름을 보고 어떤 값인지 설명할 수 있고, 검사와 출력이 그대로면 다음으로 간다.

단계 6. 바뀐 줄을 읽는다

변경 내역은 바꾸기 전후의 차이를 줄 단위로 보여 준다. 보통 앞에 붙은 빼기 표시는 지운 줄, 더하기 표시는 새로 넣은 줄이다. 다만 코드를 옮겨도 삭제와 추가로 보인다. 추가된 줄 수만 보고 기능이 늘었다고 판단하지 않는다.

이번 정리의 변경 내역을 설명해 줘. 삭제와 추가를 구별하되, 월 선택 조건, 분류별 덧셈, 정렬, 문자열 작성이 각각 어디로 옮겨졌는지 짚어 줘. 조건이나 출력 문구가 달라진 줄이 있다면 따로 밝혀 줘. 검사를 통과했다는 이유만으로 변경 검토를 생략하지 마.

답변 요지 예시다. 월 선택은 select_month로, 분류별 덧셈은 sum_by_category로 옮겼다. make_report는 두 함수를 부른다. 정렬과 금액 표시 형식은 유지했다. legacy_report는 비교용으로 남아 있다.

직접 확인할 것. 편집기에 변경 비교 화면이 있다면 저장해 둔 정리 전 파일과 비교한다. 없다면 아래 표의 핵심 줄을 두 구현에서 직접 찾는다. 도구에 따라 변경 화면을 여는 방법은 다르므로, 줄의 의미를 읽는 데 집중한다.

정리 전후에 대응해서 읽을 핵심 줄
살필 일정리 전정리 후판단
월 선택if record["date"][:7] == month:select_month 안의 같은 조건조건이 유지되었는가
분류별 합계totals.get(category, 0)에 금액을 더함sum_by_category에서 같은 계산금액을 한 번씩 더하는가
표시 순서sorted(totals)sorted(totals)이름순을 유지하는가
보고서 구성한 함수에서 모두 처리두 함수 호출 뒤 문장 작성줄바꿈과 문구가 같은가

이 단계에서도 터미널에서 실행해 결과를 확인한다.

python3 main.py

여섯 검사는 준비한 사례를 확인한다. 모든 가능한 입력에서 동작이 같다는 증명은 아니다. 따라서 날짜 조건이 느슨해지지 않았는지, 정렬이 빠지지 않았는지 변경된 줄도 함께 읽는다.

체크포인트. 각 변경을 “옮김”, “이름 변경”, “호출 추가”로 설명할 수 있으면 다음으로 간다. 새 계산 규칙이 보이면 원래 요청 범위에 맞게 되돌린다.

검사 결과와 변경된 줄을 함께 확인한 뒤 정리 작업을 끝낸다

단계 7. 최종 파일을 한 번에 확인한다

이제 아래 완성 코드로 파일을 맞춘다. 임시 폴더의 이름은 실행마다 달라질 수 있지만 화면에 출력하지 않으므로 결과에는 영향을 주지 않는다. 임시 폴더는 작업이 끝나면 자동으로 지워진다. 현재 폴더에 기존 가계부 파일이 있어도 읽거나 덮어쓰지 않는다.

main.py를 최종 점검해 줘. 정해진 지출 네 건을 tempfile의 임시 폴더에 JSON으로 저장한 뒤 읽도록 해 줘. 검사 여섯 개를 먼저 실행하고, 성공하면 2026-10 보고서를 출력한 뒤 종료해 줘. 현재 날짜, 사용자 입력, 외부 패키지, 네트워크 호출은 사용하지 마. 기존 함수는 이번 비교 학습을 위해 남겨 두고, 실제 보고서는 make_report로 만들어 줘.

답변 요지 예시다. main이 준비, 저장과 읽기, 검사, 출력을 순서대로 실행한다. 임시 폴더를 벗어나면 파일이 정리된다. 보고서 생성에는 새 함수를 사용한다.

직접 확인할 것. 파일을 저장하고 다음 명령을 차례로 실행한다.

python3 -W error -m py_compile main.py
python3 main.py
python3 main.py

첫 명령은 출력 없이 끝나고, 뒤의 두 실행은 아래 실행 결과와 똑같아야 한다. 경고를 오류로 취급하는 옵션을 붙였으므로 경고가 발생해도 첫 확인을 통과하지 못한다.

체크포인트. 두 번의 출력이 같고, 여섯 검사가 통과하며, 변경된 줄의 역할까지 확인했다면 이번 작업을 마친다.

완성 코드

다음 전체를 main.py로 저장한다. 날짜는 이 예제에서 준비한 YYYY-MM-DD 형식만 사용한다. 입력 형식을 새로 검사하는 기능은 이번 정리에 추가하지 않는다.

import json
import tempfile
from pathlib import Path


def legacy_report(records, month):
    totals = {}
    for record in records:
        if record["date"][:7] == month:
            category = record["category"]
            totals[category] = totals.get(category, 0) + record["amount"]
    lines = [f"{month} 지출 보고서"]
    for category in sorted(totals):
        lines.append(f"{category}: {totals[category]:,}원")
    if not totals:
        lines.append("기록 없음")
    lines.append(f"합계: {sum(totals.values()):,}원")
    return "\n".join(lines)


def select_month(records, month):
    return [record for record in records if record["date"][:7] == month]


def sum_by_category(records):
    totals = {}
    for record in records:
        category = record["category"]
        totals[category] = totals.get(category, 0) + record["amount"]
    return totals


def make_report(records, month):
    month_records = select_month(records, month)
    totals = sum_by_category(month_records)
    lines = [f"{month} 지출 보고서"]
    for category in sorted(totals):
        lines.append(f"{category}: {totals[category]:,}원")
    if not totals:
        lines.append("기록 없음")
    lines.append(f"합계: {sum(totals.values()):,}원")
    return "\n".join(lines)


def run_checks(records, original_records):
    before = [record.copy() for record in records]
    expected_report = (
        "2026-10 지출 보고서\n"
        "교통: 3,000원\n"
        "식비: 20,000원\n"
        "합계: 23,000원"
    )
    old_report = legacy_report(records, "2026-10")
    new_report = make_report(records, "2026-10")
    empty_report = make_report(records, "2026-11")
    checks = [
        ("기존 결과", old_report == expected_report),
        ("새 결과", new_report == expected_report),
        ("전후 비교", old_report == new_report),
        (
            "빈 달",
            empty_report == "2026-11 지출 보고서\n기록 없음\n합계: 0원",
        ),
        ("원본 보존", records == before),
        ("저장과 읽기", records == original_records),
    ]
    for name, passed in checks:
        if not passed:
            raise RuntimeError(f"검사 실패: {name}")
    print(f"검사: {len(checks)}개 통과")


def main():
    original_records = [
        {"date": "2026-10-02", "category": "식비", "amount": 12000},
        {"date": "2026-10-03", "category": "교통", "amount": 3000},
        {"date": "2026-10-05", "category": "식비", "amount": 8000},
        {"date": "2026-09-30", "category": "식비", "amount": 5000},
    ]
    with tempfile.TemporaryDirectory() as folder:
        path = Path(folder) / "expenses.json"
        path.write_text(
            json.dumps(original_records, ensure_ascii=False),
            encoding="utf-8",
        )
        records = json.loads(path.read_text(encoding="utf-8"))
        run_checks(records, original_records)
        print(make_report(records, "2026-10"))


if __name__ == "__main__":
    main()

줄별 해설

가져오기 세 줄. json은 목록과 사전을 JSON 문자열로 바꾸고 다시 읽는다. tempfile은 임시 폴더를 만들며, Path는 폴더와 파일 이름을 연결하고 파일을 읽고 쓰는 데 쓴다. 모두 파이썬에 포함되어 있다.

legacy_report의 첫 반복. totals는 분류를 열쇠로, 누적 금액을 값으로 담는 사전이다. 날짜 문자열의 [:7]은 앞 일곱 글자를 꺼낸다. 준비한 날짜에서 “2026-10”을 꺼내 month와 비교한다. get(category, 0)은 해당 분류의 합계가 아직 없으면 0을 사용한다.

legacy_report의 나머지 줄. lines에 보고서 문장을 차례로 넣는다. sorted(totals)는 분류 이름을 정렬한다. 금액 뒤의 :,는 세 자리마다 쉼표를 넣는 표시 방법이다. 합계가 비어 있으면 “기록 없음”을 추가하고, 마지막에 전체 합계를 넣는다. join은 문장 사이에 줄바꿈을 넣어 한 문자열로 돌려준다.

select_month. 대괄호 안의 반복 표현은 조건을 만족하는 기록만 새 목록에 모은다. 원래 목록에서 기록을 삭제하지 않는다. 다만 내부의 각 사전까지 새로 복사하는 코드는 아니다. 따라서 이 함수가 돌려준 기록의 금액을 직접 수정하면 원래 사전도 바뀐다. 이번 프로그램은 선택한 기록을 읽기만 한다.

sum_by_category. 이미 월을 골랐다는 전제에서 분류별 덧셈만 한다. 월 조건도 없고 보고서 문장도 없다. 이렇게 역할을 나누면 합계가 틀렸을 때 날짜 선택 문제인지 덧셈 문제인지 따로 살펴보기 쉽다.

make_report. 첫 두 줄은 월의 기록을 고르고 분류별로 더하는 순서를 보여 준다. 뒤의 줄은 기존 표시 규칙을 유지한다. 함수마다 한 줄만 남기는 것이 목표는 아니다. 여기서는 보고서 문장 작성이라는 한 역할이 모이도록 나눈다.

run_checks의 before. 각 기록의 사전을 복사해 실행 전 값을 보관한다. 이 예제의 사전 값은 문자열과 정수이므로 이 복사로 비교에 필요한 값을 남길 수 있다. 값 안에 또 다른 목록이나 사전이 들어가면 복사 방법도 다시 검토해야 한다.

expected_report와 checks. 기대 문자열은 보고서 함수에서 가져오지 않고 직접 적었다. checks의 각 항목에는 검사 이름과 참 또는 거짓인 비교 결과가 들어간다. 원본 보존 비교는 두 보고서와 빈 달 보고서를 만든 다음 수행하므로 그 호출들이 입력을 바꾸었는지 확인한다.

실패 처리와 성공 출력. 비교 결과가 거짓이면 RuntimeError를 발생시킨다. 그러면 뒤의 성공 문구와 보고서는 출력되지 않는다. 모든 비교를 통과해야 실제 항목 수인 6을 표시한다. 성공 문구 자체가 검사를 대신하는 것은 아니다.

main의 저장과 읽기. original_records가 고정 입력이다. with 안에서 임시 폴더를 만들고 UTF-8로 저장한다. ensure_ascii=False는 한글을 JSON 파일에서도 읽기 쉽게 남긴다. 읽은 records로 검사와 보고서를 실행하고, with 구간이 끝나면 임시 파일과 폴더를 정리한다.

마지막 두 줄. main.py를 직접 실행했을 때 main을 호출한다. 함수 정의만 작성하고 호출을 빠뜨리면 아무것도 실행되지 않으므로 이 진입 부분까지 확인한다.

실행 결과

macOS 또는 Linux 터미널에서 main.py가 있는 폴더로 이동한 뒤 실행한다. 다음 명령은 터미널에 입력할 내용이다.

python3 main.py

예상 표준 출력은 다음과 같다. 임시 폴더 경로나 실행 시간은 출력하지 않는다.

검사: 6개 통과
2026-10 지출 보고서
교통: 3,000원
식비: 20,000원
합계: 23,000원

이 원고의 예상 출력은 완성 코드의 고정 입력과 문자열 구성을 기준으로 제시한 것이다. 따라 하는 환경에서는 문법 검사와 실제 실행으로 확인한다. 출력이 맞더라도 변경된 줄을 읽는 단계까지 마쳐야 한다.

실무에서 자주 틀리는 것

기대값을 검사 대상에서 가져온다

아래 코드는 실행되지만, 같은 결과를 자기 자신과 비교한다. 보고서 함수가 틀려도 통과한다. 두 예시는 별도로 실행할 수 있는 짧은 Python 코드다.

def make_report():
    return "합계: 24,000원"


expected = make_report()
print(make_report() == expected)

고친 예에서는 손으로 확인한 약속을 기대값으로 적는다. 이 경우 비교 결과가 False이므로 잘못된 합계를 발견한다.

def make_report():
    return "합계: 24,000원"


expected = "합계: 23,000원"
print(make_report() == expected)

함수로 나누면서 입력 목록을 비운다

필요한 기록을 꺼내는 과정에서 pop을 쓰면 원본 목록에서 기록이 빠진다. 보고서 한 번을 만든 뒤 두 번째 결과가 달라질 수 있다.

records = [{"amount": 12000}, {"amount": 8000}]
total = 0
while records:
    total += records.pop()["amount"]
print(total, len(records))

고친 코드는 목록을 읽기만 한다. 합계는 같지만 기록 수도 그대로 남는다.

records = [{"amount": 12000}, {"amount": 8000}]
total = 0
for record in records:
    total += record["amount"]
print(total, len(records))

이름을 바꾸면서 저장 키까지 바꾼다

변수 이름과 저장 자료의 키는 구별해야 한다. 아래 코드는 amount로 저장한 값에 total이라는 키로 접근해서 KeyError가 발생한다.

record = {"amount": 12000}
print(record["total"])

읽는 쪽의 변수 이름은 바꿀 수 있지만, 저장 키는 기존 약속을 지킨다. 저장 형식까지 바꾸려면 이전 자료를 어떻게 읽을지 별도 작업이 필요하다.

record = {"amount": 12000}
expense_amount = record["amount"]
print(expense_amount)

정리 중에 정렬을 생략한다

사전은 값이 들어온 순서를 유지한다. 그러나 그것이 보고서에서 약속한 분류 이름순과 같지는 않다. 아래 코드는 식비 다음 교통으로 출력한다.

totals = {"식비": 20000, "교통": 3000}
for category in totals:
    print(category)

분류 이름순이라는 기존 약속을 유지하려면 정렬을 남긴다. 이 코드는 교통 다음 식비로 출력한다.

totals = {"식비": 20000, "교통": 3000}
for category in sorted(totals):
    print(category)

한눈에 보기

코드를 정리할 때의 행동과 확인 기준
행동이번 예제확인 기준
동작 고정고정 입력과 기대 문자열 준비10월 합계 23,000원
역할 분리월 선택, 합계 계산, 문장 작성각 함수의 입력과 반환값이 분명함
이름 정리month_records, totals, lines담긴 내용을 이름으로 구별함
검사 실행전후 비교와 독립된 기대값 비교여섯 비교를 실제로 통과함
변경 검토조건, 덧셈, 정렬, 문구 대조요청하지 않은 동작 변경이 없음

정리 전 함수를 계속 보관하면 계산 규칙을 고칠 때 두 구현을 관리해야 한다. 이번 파일에는 비교 학습을 위해 남겼지만, 실제 작업에서는 변경 기록과 독립된 기대값 검사를 확보한 뒤 비교용 함수를 제거할 수 있다. 제거 작업도 별도 변경으로 확인한다.

연습 문제

  1. 완성 코드의 sum_by_category만 따로 확인하려 한다. 식비 12,000원과 식비 8,000원을 전달했을 때 어떤 사전이 나와야 하는지 쓰고, 보고서 문자열을 만들지 않는 검사를 제안하라.
  2. AI가 make_report의 month_records를 selected로 바꾸고, 저장 키 amount를 money로 바꾸었다. 두 변경을 같은 이름 정리로 받아들여도 되는지 설명하라.
  3. 빈 달 검사에 제목과 “합계: 0원”만 남기고 “기록 없음”을 기대값에서 지웠다. 검사도 맞춰 고치면 정리 작업이 완료된 것인지 판단하라.
  4. make_report에서 sorted를 지웠는데도 준비한 검사 일부가 통과한다. 변경 검토에서 무엇을 확인하고, 어떤 입력으로 순서 차이를 드러낼지 쓰라.

정답과 해설

  1. 기대값은 {"식비": 20000}이다. sum_by_category에 분류와 금액을 가진 두 사전의 목록을 전달하고 반환 사전을 이 값과 비교한다. 날짜 선택과 문장 작성이 끼어들지 않으므로 덧셈 역할을 직접 확인할 수 있다. 이 검사를 실제로 추가하면 성공 개수도 늘어나야 한다.

  2. 같게 받아들이지 않는다. month_records는 함수 안의 변수이므로 사용하는 곳을 함께 바꾸면 같은 값을 가리킬 수 있다. 다만 selected보다 month_records가 무엇을 선택했는지 더 분명하다. amount는 JSON 자료의 키다. money로 바꾸면 기존 자료와 읽는 코드 사이의 약속이 달라진다. 이번 요청에서는 유지한다.

  3. 완료된 것이 아니다. “기록 없음”은 처음 고정한 출력 규칙이다. 기대값을 새 출력에 맞춰 바꾸면 동작 변경을 검사로 가린 셈이 된다. 기존 기대값을 복원하고 make_report가 그 문장을 계속 만드는지 확인한다. 표시 변경이 필요하다면 정리 작업과 구별해 요청한다.

  4. 반복 대상이 sorted(totals)에서 totals로 바뀌었는지 확인한다. 식비 기록을 먼저 넣고 교통 기록을 나중에 넣으면, 정렬을 생략한 경우 식비가 먼저 나온다. 완성 코드의 입력도 이 순서이므로 고정 기대 문자열 검사는 실패해야 한다. 일부 검사만 통과한 상태를 전체 성공으로 판단하지 않는다.

이제 main.py에는 처리 순서를 읽을 수 있는 함수 이름과 결과를 확인할 검사가 함께 있다. 다음에는 실제로 확인한 실행 방법과 동작 범위를 사용 설명서에 적고, 앞으로 만들 기능을 별도 목록으로 정리한다.

댓글 0

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

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