AI 개발 · 기본
바이브 코딩 따라하기 - 요청문 그대로 따라 만드는 가계부 앱
예산 경고 - 분류별 한도를 넘으면 알려 주기
예산 설정, 사용률 계산과 반올림, 80%·100% 경고 단계, 경계값 검사
개발자KR · 원고 갱신
이 장에서 배우는 것
앞 장에서 날짜로 지출을 묶고 월별 합계를 구했다. 이번에는 그 합계에 분류별 예산을 붙인다. 식비를 얼마까지 쓸지 정하고, 이번 달에 쓴 돈이 한도에 가까워지면 보고서에 경고를 표시한다. 사용자에게 값을 입력받는 대신 정해진 지출 목록을 사용하므로 같은 코드를 실행하면 같은 결과가 나온다.
이번 장의 핵심은 숫자를 보기 좋게 표시하는 일과 경고를 결정하는 일을 구분하는 데 있다. 화면에 사용률이 80.0%로 보여도 실제 사용 금액이 예산의 80%보다 작을 수 있다. AI가 만든 코드도 이 경계를 제대로 처리하는지 직접 실행하여 확인한다.
- 분류별 예산을 원 단위의 양의 정수로 설정한다.
- 선택한 달의 지출만 모아 분류별 사용 금액을 구한다.
- 사용률을 소수 첫째 자리로 반올림하여 표시한다.
- 실제 사용 금액으로 80%와 100% 경고 단계를 결정한다.
- 경계 바로 아래, 경계와 같은 값, 경계 바로 위를 검사한다.
이번 장에서 만들 것
혼자 쓰는 가계부에 2026년 10월 예산 보고서를 붙인다. 식비·교통·도서의 예산은 각각 10,000원이다. 생활 분류에는 예산을 설정하지 않는다. 지출 목록에는 다른 달의 기록도 넣어서 월별 집계가 유지되는지 확인한다.
식비로 7,999원을 쓰면 실제 사용률은 79.99%다. 소수 첫째 자리까지 반올림한 표시는 80.0%지만, 경고는 아직 ‘정상’이어야 한다. 교통비 8,000원은 정확히 80%이므로 ‘주의’다. 도서비 10,000원은 ‘한도 도달’이다. 예산이 없는 생활 분류는 금액만 보여 주고 ‘예산 미설정’으로 표시한다.
| 조건 | 표시할 상태 | 예산 10,000원일 때 |
|---|---|---|
| 사용 금액이 예산의 80% 미만 | 정상 | 0~7,999원 |
| 80% 이상이며 100% 미만 | 주의 | 8,000~9,999원 |
| 예산과 같은 금액 | 한도 도달 | 10,000원 |
| 예산보다 큰 금액 | 한도 초과 | 10,001원부터 |
| 분류의 예산이 없음 | 예산 미설정 | 사용률은 계산하지 않음 |
예산을 설정하지 않은 것과 예산을 0원으로 넣은 것은 다르게 취급한다. 이 예제에서 예산은 1원 이상의 정수만 허용한다. 예산이 없는 분류는 설정 목록에서 제외한다. 0원이나 음수가 들어오면 설정 오류를 알려 준다. 이렇게 정해 두면 0으로 나누는 문제와 설정의 의미가 뒤섞이지 않는다.
이번 결과물은 알림을 보내는 서비스가 아니라 실행할 때 경고 문구를 보여 주는 프로그램이다. 파일 저장, 검색, 수정·삭제 기능은 추가하지 않는다. 앞 장의 월별 집계를 필요한 만큼 포함하므로 앞 장 파일이 없어도 실행할 수 있다.
따라하기
다음 일곱 단계를 순서대로 진행한다. 요청문은 인용문 안의 내용을 그대로 보내면 된다. AI의 답과 코드 모양은 매번 다를 수 있다. 답변이 아래 요지와 다르더라도 실행 결과와 검사 조건이 맞는지 먼저 확인한다. 중간 코드가 꼬이면 뒤의 완성 코드를 main.py에 넣어 같은 출발점으로 돌아올 수 있다.
터미널은 명령을 입력하여 프로그램을 실행하는 창이다. 아래 실행 명령은 main.py가 있는 폴더에서 사용한다. 변경 전 파일의 사본을 남겨 두면 변경 내역(diff), 즉 어느 줄이 추가되거나 바뀌었는지도 비교할 수 있다.
단계 1. 예산의 규칙부터 고정한다
① 보낼 요청문
혼자 쓰는 가계부의 예산 보고서를 main.py 한 파일로 만들어 줘. Python 3.12 이상 표준 라이브러리만 사용해. 사용자 입력, 네트워크, 현재 시각은 사용하지 마. 대상 월은 2026-10으로 고정해. 식비·교통·도서의 예산은 각각 10000원이며 생활에는 예산이 없어. 예산은 양의 정수만 허용하고 0과 음수, True는 거부해. 우선 예산 검사 함수와 실행 진입 부분을 만들고, 실행하면 ‘예산 설정 확인 완료’만 출력해 줘.
② AI가 돌려줄 내용의 요지
답변은 매번 다를 수 있다. 예산을 분류 이름과 금액의 쌍으로 보관하고, 각 금액이 양의 정수인지 확인하는 코드를 제시한다. 잘못된 설정에는 ValueError를 발생시킨다.
③ 직접 확인할 것
실행 명령: python3 main.py
기대 결과: 예산 설정 확인 완료
양의 정수는 1, 2, 3처럼 0보다 큰 정수다. True는 참을 나타내는 값이며 예산 금액으로 받지 않는다. Python에서는 정수 검사 방법에 따라 True도 통과할 수 있으므로 이 조건을 요청문에 따로 적었다. 변경 내역에서 외부 라이브러리나 입력 대기 코드가 추가되지 않았는지도 확인한다.
④ 체크포인트
유효한 예산으로 실행이 끝나고 설정 확인 문장이 한 번 나오면 다음 단계로 간다. 잘못된 예산을 조용히 0원으로 바꾸는 코드는 고쳐 달라고 요청한다.
단계 2. 앞 장의 월별 집계를 포함한다
① 보낼 요청문
앞 장 파일 없이 실행되도록 정해진 지출 목록과 월별 집계 함수를 추가해 줘. 기록은 날짜·분류·금액을 가진다. 2026-10-03 식비 7999원, 2026-10-05 교통 8000원, 2026-10-09 도서 10000원, 2026-10-12 생활 1200원, 2026-09-30 식비 5000원을 넣어 줘. 날짜는 표준 라이브러리로 해석하고 2026년 10월만 집계해. 기존 확인 문장 대신 식비·교통·도서·생활 순서로 분류와 합계를 한 줄씩 출력해 줘.
② AI가 돌려줄 내용의 요지
답변은 매번 다를 수 있다. 날짜에서 연도와 월을 꺼내 대상 월과 비교한다. 해당하는 기록만 분류별로 더하므로 9월 식비는 10월 합계에 포함되지 않는다.
③ 직접 확인할 것
실행 명령: python3 main.py
기대 결과: 식비 7999, 교통 8000, 도서 10000, 생활 1200이 각각 한 줄에 나온다.
식비가 12,999원이라면 다른 달 기록이 섞인 것이다. 예산 경고를 붙이기 전에 이 오류부터 해결한다. 합계가 잘못되면 경고 계산이 맞아도 보고서는 잘못된 결론을 보여 준다.
④ 체크포인트
네 분류의 합계가 맞고 9월 기록이 제외되면 다음 단계로 간다. 월별 집계를 지우고 전체 기록을 더하는 변경이 없는지 비교한다.
단계 3. 사용률 표시 방법을 정한다
① 보낼 요청문
사용 금액을 예산으로 나누고 100을 곱한 사용률을 표시해 줘. Decimal을 사용하고 ROUND_HALF_UP 방식으로 소수 첫째 자리까지 반올림해. 이는 버리는 부분이 정확히 절반이면 올리는 규칙이야. 금액 정수에서 바로 Decimal을 만들고 중간에 float로 나누지 마. 식비·교통·도서의 사용률을 각각 한 줄씩 출력해 줘. 예산이 없는 생활의 사용률은 ‘-’로 표시해.
② AI가 돌려줄 내용의 요지
답변은 매번 다를 수 있다. 십진수 계산을 위한 Decimal과 반올림 규칙을 가져온다. 사용률을 계산한 뒤 0.1 단위로 반올림하고, 소수 첫째 자리까지 문자열로 만든다.
③ 직접 확인할 것
실행 명령: python3 main.py
기대 결과: 식비 80.0%, 교통 80.0%, 도서 100.0%, 생활 -가 각각 한 줄에 나온다.
Decimal은 십진수를 다루기 위한 표준 라이브러리의 도구다. 여기서는 일반적인 크기의 가계부 금액을 다룬다. 금액 자체는 원 단위 정수로 유지하고, 화면에 보여 줄 비율을 만들 때만 Decimal을 사용한다. 80%가 아니라 80.0%처럼 표시 자릿수를 맞추면 결과를 비교하기 쉽다.
④ 체크포인트
7,999원의 사용률이 80.0%로 보이면 표시 규칙이 적용된 것이다. 이 값을 곧바로 경고 판정에 쓰지는 않는다.
단계 4. 실제 금액으로 경고를 결정한다
① 보낼 요청문
경고 상태를 반환하는 함수를 추가해 줘. 예산보다 많이 쓰면 ‘한도 초과’, 같으면 ‘한도 도달’, 80% 이상이면 ‘주의’, 나머지는 ‘정상’으로 해. 예산이 없으면 ‘예산 미설정’이야. 반올림한 사용률로 판단하지 마. 80% 비교는 사용 금액 곱하기 100과 예산 곱하기 80을 정수로 비교해. 지금은 분류와 상태를 한 줄씩 출력해 줘.
② AI가 돌려줄 내용의 요지
답변은 매번 다를 수 있다. 예산 미설정을 먼저 처리하고, 초과·도달·주의·정상 순서로 조건을 확인한다. 표시용 사용률과 경고 판정은 별도 함수로 나눈다.
③ 직접 확인할 것
실행 명령: python3 main.py
기대 결과: 식비 정상, 교통 주의, 도서 한도 도달, 생활 예산 미설정이 각각 한 줄에 나온다.
7,999 × 100은 799,900이고 10,000 × 80은 800,000이다. 왼쪽이 작으므로 식비는 정상이다. 정수끼리 곱해서 비교하면 반올림을 거치지 않고 80% 경계를 판단할 수 있다. 100%에 해당하는 조건을 먼저 확인해야 그 금액이 ‘주의’로 처리되지 않는다.
④ 체크포인트
표시가 똑같이 80.0%인 식비와 교통의 상태가 서로 다르면 다음 단계로 간다.
단계 5. 경계값을 자동으로 검사한다
① 보낼 요청문
assert를 사용하는 검사 함수를 추가해 줘. 예산 10000원에서 사용 금액 0·7999·8000·9999·10000·10001원의 상태를 검사해. 각각 정상·정상·주의·주의·한도 도달·한도 초과여야 해. 예산 0·-1·True가 각각 ValueError로 거부되는지도 검사해. 예산 미설정 상태 하나와 7999원의 표시가 ‘80.0%’인 경우 하나도 검사해. 총 11개이며 모두 통과하면 ‘경계값 검사: 11개 통과’를 먼저 출력하고 기존 분류별 상태를 이어서 출력해 줘.
② AI가 돌려줄 내용의 요지
답변은 매번 다를 수 있다. 예상 상태 목록을 순서대로 검사한다. 잘못된 예산은 예외가 발생해야 성공으로 세며, 예외가 없으면 검사를 실패시킨다.
③ 직접 확인할 것
실행 명령: python3 main.py
기대 결과: 첫 줄은 경계값 검사: 11개 통과이며, 뒤에는 앞 단계의 네 분류별 상태가 나온다.
assert는 조건이 참인지 확인하는 문장이다. 조건이 거짓이면 실행을 중단하여 예상과 다른 결과를 드러낸다. 여기서는 개발 중 확인 도구로 쓴다. 실제 예산 설정을 거부하는 처리는 별도의 ValueError가 담당한다. 검사 문장과 입력 검증은 역할이 다르다.
④ 체크포인트
검사가 실제 함수를 호출하는지 읽어 본다. 예상 문자열을 그대로 다시 비교하거나 통과 문장만 출력하는 코드는 검사가 아니다. 숫자가 11로 표시되는 것만 믿지 않는다.
단계 6. 보고서를 한 번에 읽히도록 묶는다
① 보낼 요청문
출력을 최종 보고서 형태로 정리해 줘. 첫 줄은 검사 통과 문장, 다음 줄은 ‘2026-10 예산 보고서’로 해. 분류 순서는 식비·교통·도서·생활로 고정하고 금액에는 천 단위 쉼표를 넣어. 각 줄에 분류, 사용 금액, 예산, 사용률, 상태를 세로줄로 구분해 표시해. 예산이 없으면 예산과 사용률은 ‘-’로 표시해. 마지막에는 이번 달 총지출 27,199원을 출력해. 예산이 있지만 지출이 없는 분류도 0원으로 표시할 수 있게 해.
② AI가 돌려줄 내용의 요지
답변은 매번 다를 수 있다. 예산에 있는 분류와 지출에 있는 분류를 합쳐 출력 대상을 만든다. 정해진 순서로 출력하고, 합계가 없는 분류는 0원으로 읽는다.
③ 직접 확인할 것
실행 명령: python3 main.py
기대 결과: 뒤의 ‘실행 결과’와 같은 일곱 줄이 나온다. 마지막 줄은 총지출: 27,199원이다.
생활 분류의 1,200원도 총지출에 포함한다. 예산이 없다는 이유로 지출 자체가 없어지는 것은 아니다. 또한 출력 순서는 기록을 넣은 순서에 맡기지 않는다. 이 예제에서는 익숙한 분류를 먼저 배치하고, 그 밖의 분류가 있으면 이름순으로 이어 붙인다.
④ 체크포인트
경고 상태뿐 아니라 분류 순서, 금액의 쉼표, 예산 없는 줄, 총지출까지 일치하면 마지막 단계로 간다.
단계 7. 변경 내역과 실행을 함께 확인한다
① 보낼 요청문
지금 코드를 검토해 줘. 월별 집계가 유지되는지, 반올림한 값으로 경고를 판단하지 않는지, 0원 예산이 거부되는지, 검사 11개가 실제 조건을 확인하는지 살펴봐. 새 기능은 추가하지 마. 수정이 필요하면 이유와 변경된 부분을 설명하고 main.py 전체를 보여 줘. 수정이 없으면 확인한 항목을 설명해 줘.
② AI가 돌려줄 내용의 요지
답변은 매번 다를 수 있다. 월 필터, 정수 비교, 예산 검증, 검사 호출을 살펴본 결과를 설명한다. 답변에서 실행했다고 말하더라도 독자가 직접 실행한 결과로 최종 확인한다.
③ 직접 확인할 것
문법 확인 명령: python3 -W error -m py_compile main.py
실행 명령: python3 main.py
기대 결과: 문법 확인은 출력 없이 끝나고, 실행 결과는 아래 예시와 정확히 일치한다.
문법 확인은 Python이 코드를 읽을 수 있는지 확인하는 과정이다. 경고를 오류로 취급하는 옵션도 함께 사용한다. 이것만으로 계산이 맞다는 뜻은 아니므로 보고서 실행까지 이어서 확인한다. 편집기의 파일 비교 기능으로 이전 사본과 대조하여 날짜 조건이나 검사 호출이 사라지지 않았는지 읽는다.
④ 체크포인트
문법 확인, 경계값 검사, 최종 출력 비교, 변경 내역 검토가 모두 끝나면 이번 장의 결과를 남긴다. 이후 코드를 고쳤을 때도 같은 실행 명령으로 다시 확인한다.
완성 코드
다음 내용을 main.py에 저장한다. 지출 목록과 예산은 코드 안에 있으므로 입력 파일을 만들 필요가 없다. 줄 번호는 복사할 코드에 넣지 않았다.
from datetime import date
from decimal import Decimal, ROUND_HALF_UP
TARGET_MONTH = "2026-10"
BUDGETS = {"식비": 10000, "교통": 10000, "도서": 10000}
CATEGORY_ORDER = ("식비", "교통", "도서", "생활")
RECORDS = [
{"date": "2026-10-03", "category": "식비", "amount": 7999},
{"date": "2026-10-05", "category": "교통", "amount": 8000},
{"date": "2026-10-09", "category": "도서", "amount": 10000},
{"date": "2026-10-12", "category": "생활", "amount": 1200},
{"date": "2026-09-30", "category": "식비", "amount": 5000},
]
def validate_budgets(budgets):
for category, limit in budgets.items():
if type(limit) is not int or limit <= 0:
raise ValueError(f"{category}: 예산은 양의 정수여야 한다")
def monthly_totals(records, target_month):
target = date.fromisoformat(f"{target_month}-01")
totals = {}
for record in records:
day = date.fromisoformat(record["date"])
if (day.year, day.month) != (target.year, target.month):
continue
category = record["category"]
totals[category] = totals.get(category, 0) + record["amount"]
return totals
def usage_text(spent, limit):
if limit is None:
return "-"
percent = Decimal(spent) * Decimal(100) / Decimal(limit)
rounded = percent.quantize(Decimal("0.1"), rounding=ROUND_HALF_UP)
return f"{rounded:.1f}%"
def budget_status(spent, limit):
if limit is None:
return "예산 미설정"
if spent > limit:
return "한도 초과"
if spent == limit:
return "한도 도달"
if spent * 100 >= limit * 80:
return "주의"
return "정상"
def check_boundaries():
cases = [
(0, "정상"),
(7999, "정상"),
(8000, "주의"),
(9999, "주의"),
(10000, "한도 도달"),
(10001, "한도 초과"),
]
for spent, expected in cases:
assert budget_status(spent, 10000) == expected, (spent, expected)
invalid_limits = (0, -1, True)
for limit in invalid_limits:
try:
validate_budgets({"검사용": limit})
except ValueError:
pass
else:
raise AssertionError(f"잘못된 예산이 통과함: {limit}")
assert budget_status(1200, None) == "예산 미설정"
assert usage_text(7999, 10000) == "80.0%"
return len(cases) + len(invalid_limits) + 2
def main():
validate_budgets(BUDGETS)
checked = check_boundaries()
totals = monthly_totals(RECORDS, TARGET_MONTH)
print(f"경계값 검사: {checked}개 통과")
print(f"{TARGET_MONTH} 예산 보고서")
categories = set(BUDGETS) | set(totals)
ordered = [name for name in CATEGORY_ORDER if name in categories]
ordered += sorted(categories - set(CATEGORY_ORDER))
for category in ordered:
spent = totals.get(category, 0)
limit = BUDGETS.get(category)
limit_text = "-" if limit is None else f"{limit:,}원"
print(
f"{category} | 사용 {spent:,}원 | 예산 {limit_text} | "
f"사용률 {usage_text(spent, limit)} | "
f"{budget_status(spent, limit)}"
)
print(f"총지출: {sum(totals.values()):,}원")
if __name__ == "__main__":
main()
줄별 해설
코드의 빈 줄도 포함하여 첫 줄부터 센다. 모든 기호를 외우기보다 예산 설정에서 보고서 출력까지 값이 이동하는 순서를 따라 읽는다.
| 줄 | 역할 | 직접 확인할 점 |
|---|---|---|
| 1~2 | 날짜와 십진수 계산 도구를 가져온다. | 모두 표준 라이브러리다. |
| 5~14 | 대상 월, 예산, 출력 순서, 지출 목록을 정한다. | 현재 날짜를 읽지 않는다. |
| 17~20 | 각 예산이 양의 정수인지 확인한다. | True도 거부한다. |
| 23~32 | 대상 월의 지출만 분류별로 더한다. | 연도와 월을 함께 비교한다. |
| 35~40 | 사용률을 계산하고 반올림한 문자열을 만든다. | 예산 미설정은 나누기 전에 반환한다. |
| 43~52 | 원래 금액으로 경고를 결정한다. | 초과와 도달을 주의보다 먼저 확인한다. |
| 55~77 | 경계와 잘못된 설정을 검사한다. | 예상과 다르면 통과 문장에 도달하지 못한다. |
| 80~99 | 검사와 집계를 실행하고 보고서를 출력한다. | 예산 없는 지출도 총지출에 포함한다. |
| 102~103 | 파일을 직접 실행할 때 main을 호출한다. | 입력 대기 없이 실행이 끝난다. |
17~20줄의 budgets는 함수가 전달받는 예산 목록이다. 함수는 이름을 붙여 둔 처리 묶음이며, 괄호 안의 값을 받아 일한다. items()로 분류와 금액을 함께 꺼낸다. type(limit) is not int는 값의 자료형이 정확히 정수인지 확인한다. 뒤의 조건은 0 이하인 금액을 거부한다. 실제 설정 검증에는 assert 대신 ValueError를 쓴다.
23~32줄에서 totals는 분류별 합계를 담는 사전이다. 사전은 이름과 값을 짝지어 보관하는 자료형이다. totals.get(category, 0)은 그 분류가 이미 있으면 합계를 읽고, 없으면 0을 사용한다. 다른 달 기록에서 만나는 continue는 그 기록의 나머지 처리를 건너뛰고 다음 기록으로 넘어간다.
35~40줄의 None은 예산 값이 없음을 나타낸다. 예산이 없다면 ‘-’를 반환하므로 나눗셈을 하지 않는다. 예산이 있을 때는 정수 금액에서 Decimal을 바로 만든다. quantize는 표시할 자릿수를 맞추는 계산이며, 여기서 0.1은 소수 첫째 자리까지 남긴다는 뜻이다.
43~52줄은 표시 함수를 호출하지 않는다. 사용률 문자열에서 숫자를 다시 꺼내지도 않는다. 이 함수가 받는 사용 금액과 예산만으로 상태를 결정하기 때문에 표시 자릿수를 바꾸더라도 경고 기준은 유지된다. return을 만나면 함수가 끝나므로 여러 조건에 해당할 수 있을 때는 순서가 중요하다.
55~77줄에서 try는 오류가 날 수 있는 처리를 시도하고, except는 지정한 오류가 발생했을 때 실행된다. 이 검사에서는 잘못된 예산이 거부되어야 하므로 ValueError가 발생하면 다음 값으로 진행한다. 오류가 없을 때 실행되는 else에서는 검사 실패를 알린다.
86줄의 set은 중복 없는 값의 모음이다. 세로줄 기호는 두 모음에 있는 분류를 합친다. 예산에만 있는 분류와 지출에만 있는 분류를 모두 출력하기 위한 처리다. 모음 자체의 순서에 기대지 않고 87~88줄에서 출력 순서를 따로 만든다. 92줄과 94줄의 :,는 정수에 천 단위 쉼표를 넣는다.
이 프로그램은 코드에 적어 둔 정상적인 지출 자료를 사용한다. 지출 금액이나 분류를 사용자가 새로 입력하는 흐름은 여기서 추가하지 않는다. 반면 이번에 추가한 예산 설정은 보고서를 계산하기 전에 검증한다.
실행 결과
main.py가 있는 폴더의 터미널에서 다음 명령을 차례로 실행한다. 명령은 인용문으로 표시하고, 실제 프로그램 출력은 아래에 따로 적었다.
python3 -W error -m py_compile main.py
python3 main.py
첫 명령은 정상이라면 아무것도 출력하지 않는다. 두 번째 명령의 예상 출력은 다음과 같다.
경계값 검사: 11개 통과
2026-10 예산 보고서
식비 | 사용 7,999원 | 예산 10,000원 | 사용률 80.0% | 정상
교통 | 사용 8,000원 | 예산 10,000원 | 사용률 80.0% | 주의
도서 | 사용 10,000원 | 예산 10,000원 | 사용률 100.0% | 한도 도달
생활 | 사용 1,200원 | 예산 - | 사용률 - | 예산 미설정
총지출: 27,199원
식비와 교통의 사용률 표시는 같지만 상태는 다르다. 식비의 80.0%는 반올림한 표시이고 교통은 실제로 80%에 도달했다. 실제 사용 금액도 같은 줄에 보여 주므로 독자는 표시 자릿수 때문에 생긴 차이를 확인할 수 있다.
이 결과를 처음부터 맞다고 가정하지 않는다. AI가 내놓은 완성본을 저장한 뒤 직접 실행하고, 일곱 줄을 비교한다. 실행 환경에서 확인하지 않은 상태를 ‘검사 완료’라고 기록하지 않는다.
실무에서 자주 틀리는 것
반올림한 사용률로 경고를 결정한다
다음은 표시용으로 반올림한 숫자를 경고에도 쓰는 잘못된 예다. 실행하면 ‘주의’가 나오지만 7,999원은 아직 80% 미만이다.
spent = 7999
limit = 10000
display_rate = round(spent * 100 / limit, 1)
print("주의" if display_rate >= 80 else "정상")
고친 코드는 정수 금액을 비교한다. 이 예를 실행하면 ‘정상’이 나온다. 비율 표시는 보고서의 표시 함수가 따로 맡는다.
spent = 7999
limit = 10000
print("주의" if spent * 100 >= limit * 80 else "정상")
일반적인 반올림 함수를 쓰는 것과 이번에 정한 ROUND_HALF_UP 규칙도 혼동하지 않는다. 어떤 규칙으로 몇 자리까지 표시할지 요청문과 코드에 함께 남긴다.
80% 조건을 먼저 검사한다
다음 코드는 10,000원을 써도 ‘주의’가 나온다. 80% 이상 조건을 먼저 만족하므로 아래 조건에 도달하지 않는다.
spent = 10000
limit = 10000
if spent * 100 >= limit * 80:
print("주의")
elif spent >= limit:
print("한도 도달")
고친 코드는 초과와 도달부터 검사한다. 실행 결과는 ‘한도 도달’이다. 10,001원으로 바꾸면 ‘한도 초과’가 나와야 한다.
spent = 10000
limit = 10000
if spent > limit:
print("한도 초과")
elif spent == limit:
print("한도 도달")
elif spent * 100 >= limit * 80:
print("주의")
else:
print("정상")
0원과 미설정을 같은 값으로 처리한다
다음 코드는 0원 예산을 미설정으로 표시한다. 잘못 입력된 설정이 드러나지 않는다.
budgets = {"식비": 0}
limit = budgets.get("식비")
print("예산 미설정" if not limit else "예산 설정됨")
고친 예에서는 없는 값은 None으로 구분하고 0원은 설정 오류로 표시한다. 실행 결과는 ‘설정 오류’다. 완성 코드에서는 같은 상황에 ValueError를 발생시켜 보고서 작성을 중단한다.
budgets = {"식비": 0}
limit = budgets.get("식비")
if limit is None:
print("예산 미설정")
elif type(limit) is not int or limit <= 0:
print("설정 오류")
else:
print("예산 설정됨")
상태 이름을 늘리는 것보다 값의 의미를 먼저 정하는 편이 수정하기 쉽다. 미설정, 잘못된 설정, 유효한 설정을 나눈 뒤에 계산을 시작한다.
한눈에 보기
| 항목 | 이번 장의 선택 | 확인 방법 |
|---|---|---|
| 예산 설정 | 양의 정수만 허용 | 0·-1·True 거부 검사 |
| 대상 기간 | 2026년 10월 | 9월 식비 5,000원 제외 |
| 사용률 표시 | 소수 첫째 자리, 절반이면 올림 | 7,999원은 80.0% |
| 주의 경계 | 원래 금액으로 80% 이상 판정 | 7,999원 정상, 8,000원 주의 |
| 100% 경계 | 도달과 초과를 구분 | 10,000원 도달, 10,001원 초과 |
| 예산 미설정 | 사용률을 계산하지 않음 | 생활은 금액과 미설정 상태 표시 |
| 완료 확인 | 문법·검사·출력·변경 내역 확인 | 직접 실행한 결과를 대조 |
경계값은 상태가 바뀌는 지점과 그 주변 값이다. 임의의 금액 몇 개만 넣으면 조건의 순서나 등호가 잘못되어도 발견하지 못할 수 있다. 80%와 100% 주변을 골라 확인하면 적은 사례로도 이번 기능의 핵심 규칙을 살펴볼 수 있다.
연습 문제
문제마다 완성 코드의 사본에서 시작한다. 한 문제의 변경을 다음 문제에 이어 붙이지 않는다. 바꾼 뒤에는 문법 확인과 실행을 다시 하고, 의도한 부분만 바뀌었는지 비교한다.
- 교통 예산만 20,000원으로 바꾼다. 교통 줄의 사용률과 상태는 어떻게 바뀌는가. 총지출도 바뀌는지 확인한다.
- 현재 예산을 유지하고 2026-10-20 식비 2,002원 기록을 추가한다. 식비 사용 금액, 표시 사용률, 상태, 총지출을 예상한 뒤 실행한다.
- 0원 예산이 거부되는 것을 확인하려고 식비 예산을 0으로 바꾼다. 보고서가 나오기 전에 실행이 중단되는 이유와, 미설정으로 바꾸려면 무엇을 해야 하는지 설명한다.
- 검사 함수에 사용 금액 8,001원과 예상 상태 ‘주의’를 추가한다. 예산 20,000원에서 15,999원은 ‘정상’, 16,000원은 ‘주의’라는 검사도 추가한다. 통과 개수와 보고서 출력의 변화를 확인한다.
정답과 해설
1. 예산만 바꾸면 사용률이 바뀐다. 교통의 사용 금액은 8,000원 그대로다. 8,000 ÷ 20,000 × 100은 40이므로 사용률은 40.0%, 상태는 ‘정상’이다. 예산은 지출 기록이 아니므로 총지출은 27,199원으로 유지된다. 교통 줄의 예산 표시는 20,000원으로 바뀐다.
2. 식비는 한도를 초과한다. 7,999원에 2,002원을 더하면 10,001원이다. 실제 사용률은 100.01%이고 표시 사용률은 100.0%다. 상태는 ‘한도 초과’이며 총지출은 29,201원이다. 표시가 100.0%라고 해서 ‘한도 도달’로 처리했다면 반올림한 값으로 상태를 결정했는지 확인한다.
추가할 기록의 내용은 다음 요청으로 전달할 수 있다.
RECORDS에 날짜 2026-10-20, 분류 식비, 금액 2002인 기록 하나를 추가해 줘. 예산과 경고 규칙은 유지해. 변경된 줄을 설명해 줘.
3. 0원은 유효한 예산이 아니다. main은 보고서를 출력하기 전에 validate_budgets를 호출한다. 식비 예산이 0이면 ValueError가 발생하며 예산 보고서는 나오지 않는다. 오류 메시지에는 ‘식비: 예산은 양의 정수여야 한다’가 포함된다. 예산 미설정으로 바꾸려면 BUDGETS에서 식비 항목을 제거한다. 지출 목록에는 식비가 남아 있으므로 사용 7,999원, 예산 -, 사용률 -, 예산 미설정으로 표시된다.
4. 검사 개수는 14개가 된다. cases에 8,001원 사례를 하나 넣으면 기존 계산식에 의해 한 개가 늘어난다. 여기에 예산 20,000원을 사용하는 assert 두 개를 추가한다. 예산이 다른 두 검사는 기존 반복문 밖에 두고, 반환식 끝의 2를 4로 바꿔 별도 검사 개수도 맞춘다. 보고서 데이터는 바꾸지 않았으므로 첫 줄만 ‘경계값 검사: 14개 통과’로 바뀐다.
두 개의 추가 검사 조건은 다음과 같이 요청할 수 있다.
check_boundaries의 cases에 (8001, ‘주의’)를 추가해 줘. 이어서 budget_status(15999, 20000)이 ‘정상’인지, budget_status(16000, 20000)이 ‘주의’인지 assert로 검사해 줘. 실제 추가한 검사 수에 맞춰 반환 개수도 고쳐 줘. 보고서 자료는 바꾸지 마.
검사 개수는 진행 상황을 보여 주는 숫자일 뿐이다. 각 검사가 실제로 함수를 호출하고 예상 상태를 확인하는지 함께 읽어야 한다. 이번에 만든 경계값 검사를 남겨 두면 다음에 기록을 고치는 기능을 붙인 뒤에도 예산 판정이 유지되는지 다시 확인할 수 있다.
READER FEEDBACK
질문·의견
내용에 관한 질문이나 더 나은 설명을 위한 의견을 남겨 주세요. 오탈자는 위의 제보 양식이 더 빨리 반영됩니다. 이 댓글은 원래 게시글과 같은 자리에 쌓입니다.
댓글 0
아직 댓글이 없습니다. 첫 댓글을 남겨 보세요.