Devin.KR

성공과 실패 예시

90분 안팎

학습 목표

빈 제목과 경계 길이를 포함한 예시를 JSON 입력에서 판정하는 Python 함수를 작성합니다.

개념

예시는 구현을 구분하는 도구입니다

명세에 “유효한 제목만 생성합니다”라고 적었어도 예시가 평범한 제목 하나뿐이면 잘못된 구현을 구분하기 어렵습니다. 공백만 있는 제목, 최대 길이 제목, 한 글자 초과 제목을 함께 써야 조건의 방향과 등호가 맞는지 판단할 수 있습니다. 이 레슨은 전체 앱을 만들기 전에 생성 제목의 계약을 작고 독립적인 판정 함수로 옮깁니다. 목표는 입력을 받아 성공과 실패를 계약대로 구분하는 것입니다.

브라우저 실습 입력은 JSON 객체 한 개입니다. title이 문자열이면 정리한 값을 검사합니다. 성공 출력은 {"ok": true, "title": "복습"}처럼 정리한 제목을 포함합니다. 실패 출력은 {"ok": false, "error": "EMPTY_TITLE"}처럼 오류 코드를 포함합니다. 여기서 성공의 title은 실제 항목 생성 결과가 아닙니다. 번호를 발급하거나 목록을 바꾸지 않는 제목 판정 함수의 결과입니다. 미션 사례의 task 결과와 역할을 구분합니다.

판정 순서는 형식, 앞뒤 공백 제거, 빈 값, 길이 상한입니다. 이 순서를 정하는 이유는 다음 연산이 이전 조건을 전제로 하기 때문입니다. 숫자는 strip이라는 문자열 연산을 할 수 없습니다. 먼저 형식을 확인하면 사용자 입력의 오류를 정해진 코드로 돌려줄 수 있습니다. 형식을 확인하지 않고 속성을 호출해서 생기는 AttributeError는 이 계약에서 기대하는 실패 출력이 아닙니다.

성공 사례는 값을 받아들이는 경우이고 실패 사례는 정해진 방식으로 거절하는 경우입니다. 실패 사례를 통과했다는 말은 프로그램이 실패했다는 뜻이 아니라 거절 결과가 기대와 일치했다는 뜻입니다. 빈 제목에 EMPTY_TITLE을 출력하고 정상 종료한 판정 프로그램은 그 사례를 올바르게 처리했습니다. 실행 자체가 예외로 끊긴 경우와 계약에 따라 거절한 경우를 구분해서 기록합니다.

제공된 Python 골격을 읽습니다

Python에서 함수는 입력을 받아 처리하고 값을 반환하는 이름 붙은 작업입니다. def validate_title(title): 아래의 들여쓰기된 줄이 함수 본문입니다. starter는 이 함수의 내용만 미완성으로 제공합니다. 나머지 코드는 입력을 읽고 함수를 호출한 뒤 반환값을 JSON으로 출력합니다. 함수의 결과를 return으로 반환하고 출력은 마지막 print 한 곳에서 맡습니다. 중간 디버깅 출력을 넣으면 기대 출력과 달라질 수 있습니다.

isinstance(title, str)는 제목이 문자열인지 확인합니다. not은 조건을 뒤집습니다. 문자열이 아니면 딕셔너리 형태의 오류 결과를 반환합니다. 딕셔너리는 이름과 값의 짝을 담는 자료구조이고 여기서는 ok, error 또는 title이라는 이름을 사용합니다. return을 만나면 그 호출은 끝납니다. 따라서 형식이 틀린 값이 이후의 strip 연산에 도달하지 않게 만들 수 있습니다.

title.strip()은 앞뒤 공백을 제거한 새 문자열을 돌려줍니다. clean이라는 변수에 그 결과를 저장합니다. clean == ""는 정리한 문자열이 비었는지 확인합니다. len(clean)은 정리한 문자열의 코드 포인트 수를 구합니다. 길이가 40보다 큰 경우만 거절하므로 정확히 40인 입력은 성공합니다. 비교 연산자를 바꾸면 계약의 상한이 달라지는 점을 경계 사례로 확인합니다.

JSON 파일에는 true와 false를 쓰지만 Python 딕셔너리에는 True와 False를 씁니다. json.dumps는 Python의 값을 JSON 문자열로 바꾸므로 화면에는 소문자 boolean이 나옵니다. ensure_ascii=False는 한글이 읽기 쉬운 형태로 출력되도록 합니다. 이 옵션이 제목의 길이나 의미를 바꾸지는 않습니다. 입력을 읽는 json.load(sys.stdin)과 출력을 만드는 json.dumps의 방향을 헷갈리지 않습니다.

입력 객체에서 payload.get("title")은 title 값이 없으면 None을 반환합니다. None은 문자열이 아니므로 INVALID_TITLE_TYPE으로 처리합니다. 이 레슨은 JSON 객체를 입력으로 받기로 했으며 배열이나 깨진 JSON 문법은 채점 입력 범위에 포함하지 않습니다. 전체 앱에서 전송 형식까지 검증할 일은 이후에 추가합니다. 지금 검사가 다루는 범위를 넓게 표현하면 확인하지 않은 입력까지 처리한다고 오해할 수 있습니다.

경계를 사람이 먼저 계산합니다

코드를 고치기 전에 테스트의 기대값을 읽습니다. “복습”은 성공이고 “ 복습 ”도 성공이지만 결과 title에는 앞뒤 공백이 없습니다. 공백과 탭뿐인 입력은 실패입니다. “가”를 40번 반복한 문자열은 성공하고 41번 반복하면 TITLE_TOO_LONG입니다. 1자는 성공입니다. 숫자와 null, 필드 누락은 INVALID_TITLE_TYPE입니다. 테스트가 많아서가 아니라 서로 다른 실수를 구분하기 때문에 선택한 입력입니다.

길이 경계는 상한 바로 안쪽과 바깥쪽을 짝으로 비교합니다. 39자만 검사하면 40자도 거절하는 잘못된 구현을 놓칠 수 있습니다. 빈 값 경계는 정리 전 길이만 보고 판단하는 오류를 찾습니다. 공백 세 개는 입력 길이가 3이어도 정리한 길이가 0입니다. 앞뒤 공백이 있는 40자 제목은 정리 뒤 40자이므로 허용해야 합니다. 어떤 값을 검사하는지가 등호만큼 중요한 조건입니다.

내부 공백 보존도 직접 봅니다. “자료 정리”의 두 공백은 그대로 남아야 합니다. 입력 전체의 공백을 지우거나 split 뒤 다시 연결하는 구현은 제목을 임의로 바꿉니다. 이번 정규화는 앞뒤 공백만 제거하는 제한된 작업입니다. AI가 더 깔끔해 보이는 문자열을 반환했다고 기대값을 그 결과에 맞춰 수정하지 않습니다. 기대값을 바꾸려면 먼저 사용자 계약을 바꾸기로 결정해야 합니다.

제공된 starter는 모든 입력을 성공으로 처리하는 잘못된 함수입니다. 정상 제목은 일부 통과할 수 있지만 숫자와 공백 제목, 길이 초과 입력에서 실패합니다. 여기서 할 일은 채점 입력을 피하는 특별한 조건을 추가하는 것이 아니라 명세의 일반 규칙을 구현하는 것입니다. 오류 코드를 문자열로 정확히 반환합니다. 코드를 임의로 짧게 줄이기보다 각 분기가 어떤 요구를 맡는지 읽을 수 있게 작성합니다.

실행 오류가 나오면 첫 줄만 보고 조건식을 무작정 바꾸지 않습니다. NameError는 이름 오타나 정의하지 않은 변수를 확인할 신호입니다. IndentationError는 같은 블록의 들여쓰기를 확인할 신호입니다. JSONDecodeError는 JSON 입력의 문법을 먼저 봅니다. 실행이 끝났는데 채점이 다르다면 실패한 입력, 기대 JSON, 실제 JSON을 나란히 비교합니다. 성공과 오류 결과의 필드가 섞여 있지 않은지도 확인합니다.

따라하기에서는 작은 코드를 각각 독립 실행합니다. 앞 단계의 변수가 다음 단계에 남는다고 가정하지 않습니다. 브라우저 실습에서는 starter 전체를 유지하고 validate_title 함수의 본문을 수정합니다. 로컬로 해 보고 싶다면 전체 코드를 main.py로 저장하고 JSON 객체를 표준 입력으로 전달합니다. 실행 출력에는 JSON 한 개만 남겨 채점기가 결과를 비교할 수 있게 합니다.

판정 함수가 모든 테스트를 통과해도 생성·조회·완료 앱이 완성된 것은 아닙니다. 이 함수는 제목 규칙만 확인합니다. 생성 번호와 done 값, 실패 시 목록 보존은 미션 사례와 다음 모듈에서 다룹니다. 결과 기록에는 “제목 판정 11개 입력 확인”처럼 검증한 영역을 씁니다. 완료의 범위를 정확히 말하는 습관이 다음 레슨의 증거 표로 이어집니다.

제출 전에는 세 분기를 자신의 말로 설명합니다. 왜 숫자를 먼저 거절하는지, 공백을 지운 후 왜 길이를 재는지, 40을 포함하려면 어떤 비교가 필요한지 답해 봅니다. Python 조건문과 반복문의 일반 문법은 더 읽기에서 확장합니다. 여기서는 학습 할 일 계약의 입력을 실제 판정할 수 있는 작은 함수와 경계 사례를 완성하는 데 집중합니다.

따라하기

문자열 형식을 먼저 판정합니다

다음 짧은 코드는 독립 실행합니다. 숫자를 문자열로 자동 변환하지 않는 이유를 설명합니다.

for title in ["복습", 12, None]:
    print(isinstance(title, str))

실행 결과

True
False
False

정리한 값으로 경계를 확인합니다

앞뒤 공백, 1자, 40자, 41자에 대한 길이와 허용 여부를 직접 비교합니다.

for title in [" 	 ", "가", "가" * 40, "가" * 41]:
    clean = title.strip()
    print(len(clean), 1 <= len(clean) <= 40)

실행 결과

0 False
1 True
40 True
41 False

작은 판정 함수를 완성합니다

함수에서 값을 반환하고 호출한 쪽이 출력합니다. 함수와 출력 코드 전체를 함께 실행합니다.

import json
def validate_title(title):
    if not isinstance(title, str):
        return {"ok": False, "error": "INVALID_TITLE_TYPE"}
    clean = title.strip()
    if clean == "":
        return {"ok": False, "error": "EMPTY_TITLE"}
    if len(clean) > 40:
        return {"ok": False, "error": "TITLE_TOO_LONG"}
    return {"ok": True, "title": clean}
for title in ["  복습  ", "   ", 12]:
    print(json.dumps(validate_title(title), ensure_ascii=False))

실행 결과

{"ok": true, "title": "복습"}
{"ok": false, "error": "EMPTY_TITLE"}
{"ok": false, "error": "INVALID_TITLE_TYPE"}

브라우저 실습에서 일반 규칙을 구현합니다

아래 starter의 validate_title만 수정합니다. 임시 print는 제거합니다. 출력은 JSON 한 개이며 title 형식·공백·길이 규칙을 순서대로 구현합니다. 실패 입력과 실제 출력을 읽고 테스트 기대값 대신 함수를 고칩니다.

확인 문제

실습

JSON 객체 한 개를 표준 입력으로 받습니다. title을 판정하여 JSON 한 개를 출력합니다. 문자열 아니면 INVALID_TITLE_TYPE, 앞뒤 공백 제거 뒤 빈 값이면 EMPTY_TITLE, 정리한 길이가 40 초과면 TITLE_TOO_LONG입니다. 나머지는 ok:true와 정리한 title을 반환합니다. Python len 기준 1~40이며 내부 공백은 유지합니다. 누락 title은 형식 오류입니다. 함수에서 반환하고 마지막 print만 출력합니다.

모범 답안
import json
import sys


def validate_title(title):
    if not isinstance(title, str):
        return {"ok": False, "error": "INVALID_TITLE_TYPE"}
    clean = title.strip()
    if clean == "":
        return {"ok": False, "error": "EMPTY_TITLE"}
    if len(clean) > 40:
        return {"ok": False, "error": "TITLE_TOO_LONG"}
    return {"ok": True, "title": clean}


payload = json.load(sys.stdin)
print(json.dumps(validate_title(payload.get("title")), ensure_ascii=False))

더 읽기

면접 질문

  • 제목의 형식을 공백 제거보다 먼저 검사하는 이유는 무엇인가요?
  • 제목 길이 상한이 40일 때 어떤 경계 입력을 확인해야 하나요?