Devin.KR

AI 개발 · 기본

바이브 코딩의 정석

요청을 명세로 바꾸기 - 한 장짜리 기획서

막연한 요청이 실패하는 이유, 목적·사용자·기능 목록·제외 범위·입력·출력·완료 기준을 담은 한 장 기획서, 성공 예시와 실패 예시를 먼저 쓰기, 모호한 곳을 AI 가 먼저 질문하게 하는 요청, 완성 코드는 명세의 예시 표를 그대로 검사 데이터로 써서 할 일 추가·완료 기능을 확인한다

개발자KR · 원고 갱신

이 장에서 배우는 것

앞 장에서 어떤 일을 AI에게 맡길지 판단했다면, 이제 맡길 일을 글로 고정할 차례다. “혼자 쓰는 할 일 도구를 만들어 줘”라는 요청만으로는 어떤 결과가 필요한지 충분히 전해지지 않는다. 화면이 필요한지, 파일에 저장해야 하는지, 빈 제목을 받아도 되는지까지 AI가 추측하게 된다. 추측이 많아질수록 실행은 되지만 의도와 다른 프로그램을 받기 쉽다.

이 장에서는 요청을 한 장짜리 기획서로 바꾼다. 여기서 명세란 프로그램이 받아들이는 입력, 내놓는 결과, 지켜야 하는 조건을 적은 약속이다. 긴 문서를 만드는 것이 목적은 아니다. 사람과 AI가 같은 결과를 떠올리도록 필요한 결정을 적는 것이 목적이다. 작은 할 일 관리 도구의 추가·완료 기능을 대상으로 약속을 쓰고, 그 약속의 예시를 완성 코드에서 직접 확인한다.

  • 막연한 요청에서 AI가 추측해야 하는 부분을 찾는다.
  • 목적·사용자·기능·제외 범위·입력·출력·완료 기준을 한 장으로 정리한다.
  • 성공 예시와 실패 예시를 먼저 써서 경계를 정한다.
  • 모호한 부분을 AI가 구현 전에 질문하도록 요청한다.
  • 예시 표와 같은 데이터로 할 일 추가·완료 결과를 확인한다.

문제 상황

개인 메모에 적어 둔 할 일을 Python 프로그램으로 옮기려 한다. 지금 필요한 것은 제목을 추가하고 끝낸 일을 완료로 표시하는 기능이다. AI에게 다음과 같이 요청했다고 하자.

혼자 쓸 간단한 할 일 관리 도구를 만들어 줘. 할 일을 추가하고 완료할 수 있으면 된다. 쓰기 편하게 만들어 줘.

이 요청에는 방향이 있지만 판단 기준이 부족하다. “간단한”은 코드가 짧다는 뜻일 수도 있고, 사용 절차가 적다는 뜻일 수도 있다. “쓰기 편하게”는 메뉴를 계속 보여 주라는 뜻일 수도 있고, 한 번 실행하고 끝나는 명령을 만들라는 뜻일 수도 있다. 제목 대신 목록의 위치로 완료할 일을 고르게 해도 요청을 따랐다고 볼 여지가 있다.

AI가 반복 메뉴와 파일 저장을 포함한 프로그램을 만들었다고 하자. 사용자는 이번 작업에서 파일 저장을 원하지 않았고, 자동으로 예시를 확인한 뒤 종료되는 프로그램을 기대했다. 코드를 실행하면 입력을 기다리므로 확인 작업도 멈춘다. 어느 쪽이 문장을 잘못 읽었다기보다, 문장에 선택을 가르는 조건이 없었던 것이다.

다른 문제도 있다. 제목이 공백뿐인데 추가되거나, 없는 번호를 완료하려다 프로그램이 종료될 수 있다. “추가하고 완료한다”는 문장만으로는 이런 입력을 어떻게 처리할지 알 수 없다. 성공하는 경우만 떠올리면 실패했을 때의 행동은 구현 과정에서 우연히 정해진다.

요청을 구체화한다는 것은 모든 구현 방법을 지정하는 일이 아니다. 반복문을 어디에 쓸지까지 정할 필요는 없다. 다만 관찰할 수 있는 행동은 분명해야 한다. 어떤 입력을 주었을 때 무엇을 반환하고, 목록이 어떻게 바뀌며, 무엇은 바뀌지 않아야 하는지를 적어야 한다.

한 장짜리 기획서로 결정을 고정한다

기획서는 길이보다 빠진 결정이 없는지가 중요하다. 작은 작업이라면 아래 일곱 항목으로 시작할 수 있다. 목적은 왜 만드는지, 사용자는 누가 어떤 상황에서 쓰는지다. 기능 목록은 이번에 제공할 행동이고, 제외 범위는 이번 작업에서 다루지 않을 행동이다. 입력과 출력은 기능의 경계를 정하며, 완료 기준은 작업을 끝냈다고 판단할 근거다.

할 일 추가·완료 도구의 한 장짜리 기획서
항목이번 작업의 결정
목적혼자 관리하는 할 일을 추가하고 완료 상태로 바꾼다.
사용자Python 파일을 실행할 수 있는 개인 사용자다. 이번 버전에서는 정해진 예시로 기능을 확인한다.
기능 목록제목으로 할 일을 추가한다. 번호로 할 일을 완료한다. 결과 문구와 현재 목록 상태를 확인한다.
제외 범위파일 저장, 삭제, 제목 수정, 날짜, 정렬, 로그인, 화면 메뉴, 사용자 입력 대기는 만들지 않는다.
입력추가는 문자열 제목을 받는다. 완료는 1 이상의 정수 번호를 받는다. 참·거짓 값은 번호로 받지 않는다.
출력각 기능은 결과 문구를 반환한다. 실행용 코드는 결과 문구와 목록 상태를 출력한다.
완료 기준예시 표의 여덟 행을 순서대로 실행하여 결과 문구와 전체 목록 상태가 모두 일치한다. 오류 없이 종료된다.

이 기획서에서 “이번 버전”은 범위를 정하는 표현이다. 실제 사용을 위해서는 나중에 입력 방식과 저장 기능이 필요할 수 있다. 그러나 이번 작업은 추가·완료의 약속을 확인하는 데 집중한다. 기능의 결과를 확인하기 전에 저장 위치나 메뉴 구성까지 결정하면, 작은 요청이 여러 선택을 품은 작업으로 커진다.

입력 규칙은 조금 더 구체적으로 적어야 한다. 제목 앞뒤의 공백은 제거한다. 제거한 뒤 빈 문자열이면 추가를 거절한다. 제목은 문자열만 받으며 같은 제목은 여러 번 추가할 수 있다. 번호는 추가에 성공한 순서대로 1부터 붙인다. 실패한 추가는 번호를 소비하지 않는다. 새 할 일은 미완료 상태로 시작한다.

완료 규칙도 따로 정한다. 존재하는 미완료 번호는 완료 상태로 바꾼다. 이미 완료된 번호라면 “이미 완료”라는 결과를 반환하고 상태는 그대로 둔다. 없는 번호나 잘못된 형식은 거절한다. 거절한 경우에는 기존 목록을 바꾸지 않는다. 이러한 규칙은 코드의 내부 모양을 지정하지 않으면서도 결과의 차이를 가른다.

출력은 두 종류로 나누어 생각할 수 있다. 함수가 반환하는 결과 문구는 호출한 코드가 받는 값이다. 화면에 출력하는 문장은 실행을 지켜보는 사람이 읽는 기록이다. 이번 프로그램에서는 추가·완료 함수가 직접 출력하지 않고 결과 문구를 반환한다. 실행용 함수가 그 문구와 목록 상태를 함께 출력한다.

한 장 기획서는 목적과 범위를 입력·출력·완료 기준으로 연결한다

제외 범위도 적극적인 결정이다. “저장은 알아서 해 줘”처럼 남겨 두면 AI는 파일 형식, 저장 위치, 기존 파일을 읽는 방법까지 선택해야 한다. 반면 “파일 저장은 이번 작업에서 제외한다”라고 쓰면 그 선택을 미룰 수 있다. 필요한 기능과 제외할 기능을 함께 적어야 요청의 크기를 조절할 수 있다.

완료 기준에는 “잘 동작한다” 대신 확인할 내용을 쓴다. 이번에는 여덟 예시의 결과 문구와 전체 상태가 일치해야 한다. 제목을 추가했다는 문구만 맞고 목록에는 들어가지 않는 프로그램은 통과하지 못한다. 완료 문구만 반환하고 상태를 바꾸지 않는 프로그램도 같은 방식으로 걸러진다.

성공과 실패를 먼저 예시로 쓴다

규칙을 글로 쓴 뒤에는 구체적인 입력을 넣어 본다. “빈 제목을 거절한다”보다 공백 세 개를 넣었을 때 어떤 결과가 나오는지 적으면 뜻이 선명해진다. 이때 실패 예시는 AI가 일을 잘못한 사례를 뜻하지 않는다. 프로그램이 받아들이지 않아야 할 입력을 주고, 정해진 방식으로 거절하는 사례를 뜻한다.

다음 표는 빈 목록에서 시작하여 위에서 아래로 이어서 실행한다. 각 행마다 새 목록을 만드는 표가 아니다. 상태를 적는 형식은 “번호:제목:상태”이며, 항목 사이는 “ | ”로 구분한다. 빈 목록은 “-”로 표시한다. 이 형식은 확인 기록을 짧게 만들기 위한 약속이다.

완성 코드가 그대로 확인할 입력과 기대 결과
순서입력기대 결과 문구기대 목록 상태
1추가, 제목 “ 장보기 ”추가: 11:장보기:미완료
2추가, 제목 공백 세 개거절: 제목이 비어 있음1:장보기:미완료
3추가, 제목 “메모 정리”추가: 21:장보기:미완료 | 2:메모 정리:미완료
4완료, 정수 1완료: 11:장보기:완료 | 2:메모 정리:미완료
5완료, 정수 1이미 완료: 11:장보기:완료 | 2:메모 정리:미완료
6완료, 정수 9거절: 없는 번호1:장보기:완료 | 2:메모 정리:미완료
7완료, 문자열 “1”거절: 번호는 1 이상의 정수1:장보기:완료 | 2:메모 정리:미완료
8추가, 숫자 123거절: 제목은 문자열1:장보기:완료 | 2:메모 정리:미완료

첫 행은 앞뒤 공백 제거를 보여 준다. 둘째 행은 빈 제목을 거절하면서 기존 항목을 보존하는지 보여 준다. 셋째 행의 번호가 2라는 사실은 실패한 추가가 번호를 소비하지 않았다는 뜻이다. 넷째와 다섯째 행은 처음 완료할 때와 다시 완료할 때의 차이를 보여 준다.

여섯째와 일곱째 행은 비슷해 보이지만 다른 경계다. 정수 9는 번호 형식이 맞지만 해당 할 일이 없다. 문자열 “1”은 화면에서 보기에는 숫자 같아도, 이번 함수가 받기로 한 정수 입력이 아니다. 자동으로 변환할지 거절할지는 선택할 수 있지만, 선택한 행동은 명세와 코드가 같아야 한다.

예시를 먼저 쓰면 결정하지 않은 부분도 드러난다. 같은 제목을 두 번 추가하는 경우, 번호에 0을 주는 경우, 참·거짓 값을 주는 경우를 떠올릴 수 있다. 모든 경우를 한 표에 넣을 필요는 없지만, 관련 규칙은 문서에 적어야 한다. 여덟 행이 맞는다는 사실은 그 여덟 상황의 증거다. 문서 전체의 모든 상황을 확인했다는 뜻은 아니다.

성공과 거절 예시는 결과 문구뿐 아니라 목록의 상태 변화도 정한다

예시의 입력과 기대값은 서로 독립적으로 적는다. 구현 함수가 내놓은 문구를 그대로 기대값으로 복사하면, 잘못된 결과도 맞다고 판정할 수 있다. 기대값의 출처는 먼저 합의한 명세다. 프로그램이 실제로 한 행동과 사람이 요구한 행동을 비교해야 확인에 의미가 생긴다.

AI가 모호한 곳을 먼저 질문하게 한다

기획서를 썼다고 해서 모든 빈칸이 사라지는 것은 아니다. AI에게는 문서를 보완할 기회를 주되, 빈칸을 임의로 채우고 곧바로 구현하지 않도록 요청한다. 질문은 기능 결과를 바꾸는 결정에 집중하게 한다. 변수 이름 같은 선택까지 모두 물으면 작은 작업도 진행하기 어려워진다.

위 기획서와 예시 표를 읽고, 구현 결과가 달라질 수 있는 모호한 부분을 먼저 찾아라. 그런 부분이 있으면 질문을 묶어서 제시하고 내 답을 받은 뒤 구현하라. 이미 문서에 결정된 내용은 다시 묻지 말라.

모호한 부분이 없다면 그 판단을 짧게 밝히고 구현하라. Python 3.12 이상 표준 라이브러리만 사용하고, main.py 한 파일로 작성하라. 네트워크 호출과 사용자 입력 대기 없이 python3 main.py로 실행하여 종료되어야 한다.

예시 표의 여덟 행을 같은 순서와 같은 기대값으로 코드에 넣어라. 각 행에서 결과 문구와 전체 목록 상태를 비교하고, 다르면 정상 완료 문구를 출력하지 말라. 추가한 기능과 제외 범위를 설명하라.

이 요청은 “질문이 없으면 일을 시작하지 말라”는 뜻이 아니다. 결과를 바꿀 빈칸이 있을 때 질문하라는 뜻이다. 예를 들어 제목 중복에 관한 문장이 빠진 기획서를 보냈다면 다음과 같은 대화가 가능하다.

AI의 질문: 같은 제목을 두 번 추가했을 때 두 항목을 만들 것인지, 중복으로 거절할 것인지 정해져 있지 않다. 어느 행동을 원하는가?

사용자의 답: 같은 제목도 별도 할 일로 추가한다. 번호는 각각 다르게 붙인다.

AI의 정리: 기능 규칙에 “같은 제목을 여러 번 추가할 수 있다”를 반영하고 구현하겠다.

답변은 대화 안에만 두지 않고 기획서에 반영한다. 그래야 나중에 코드를 읽는 사람도 그 선택을 알 수 있다. 구현이 끝난 뒤 “대화 중에 그렇게 정했다”는 설명을 찾아다니지 않아도 된다. 질문으로 얻은 결정은 입력 규칙, 기능 규칙, 예시 가운데 알맞은 곳에 남긴다.

AI가 질문했다고 해서 질문의 전제가 맞는 것은 아니다. 이미 제외한 파일 저장에 대해 형식을 묻는다면 제외 범위를 다시 가리키면 된다. 반대로 예시 표와 설명이 충돌한다면 구현 전에 수정해야 한다. 명세는 AI가 만든 답을 평가하는 기준이면서, 사람이 자기 요구의 모순을 찾는 도구이기도 하다.

완성 코드

다음 전체 코드를 main.py로 저장한다. 할 일 목록은 실행 중에만 유지한다. 프로그램은 예시 표를 순서대로 확인하고 끝난다. 명세의 결과 문구와 상태가 코드 안의 기대값에 그대로 들어 있으므로, 실행 기록을 표와 대조할 수 있다.

def add_task(tasks, title):
    if not isinstance(title, str):
        return "거절: 제목은 문자열"

    title = title.strip()
    if not title:
        return "거절: 제목이 비어 있음"

    task_id = len(tasks) + 1
    tasks.append({
        "id": task_id,
        "title": title,
        "done": False,
    })
    return f"추가: {task_id}"


def complete_task(tasks, task_id):
    if type(task_id) is not int or task_id < 1:
        return "거절: 번호는 1 이상의 정수"

    for task in tasks:
        if task["id"] == task_id:
            if task["done"]:
                return f"이미 완료: {task_id}"
            task["done"] = True
            return f"완료: {task_id}"

    return "거절: 없는 번호"


def describe_tasks(tasks):
    parts = []
    for task in tasks:
        status = "완료" if task["done"] else "미완료"
        parts.append(f'{task["id"]}:{task["title"]}:{status}')
    return " | ".join(parts) if parts else "-"


def main():
    cases = [
        ("추가", " 장보기 ", "추가: 1",
         "1:장보기:미완료"),
        ("추가", "   ", "거절: 제목이 비어 있음",
         "1:장보기:미완료"),
        ("추가", "메모 정리", "추가: 2",
         "1:장보기:미완료 | 2:메모 정리:미완료"),
        ("완료", 1, "완료: 1",
         "1:장보기:완료 | 2:메모 정리:미완료"),
        ("완료", 1, "이미 완료: 1",
         "1:장보기:완료 | 2:메모 정리:미완료"),
        ("완료", 9, "거절: 없는 번호",
         "1:장보기:완료 | 2:메모 정리:미완료"),
        ("완료", "1", "거절: 번호는 1 이상의 정수",
         "1:장보기:완료 | 2:메모 정리:미완료"),
        ("추가", 123, "거절: 제목은 문자열",
         "1:장보기:완료 | 2:메모 정리:미완료"),
    ]

    tasks = []
    for number, case in enumerate(cases, start=1):
        action, value, expected_result, expected_state = case

        if action == "추가":
            result = add_task(tasks, value)
        elif action == "완료":
            result = complete_task(tasks, value)
        else:
            raise ValueError(f"예시에 없는 기능: {action}")

        state = describe_tasks(tasks)
        if result != expected_result or state != expected_state:
            raise AssertionError(
                f"{number}번 불일치: "
                f"결과={result!r}, 상태={state!r}"
            )

        print(f"{number}. {result}")
        print(f"   상태: {state}")

    print(f"확인 완료: {len(cases)}개 예시 일치")


if __name__ == "__main__":
    main()

줄별 해설

def add_task(tasks, title):는 목록과 제목을 받아 추가 결과를 반환하는 함수를 정의한다. tasks는 여러 할 일을 담는 리스트이고, 각 할 일은 이름과 값의 쌍을 담는 딕셔너리다. 딕셔너리의 id, title, done에는 각각 번호, 제목, 완료 여부가 들어간다.

첫 조건의 isinstance(title, str)는 제목이 문자열인지 확인한다. 문자열이 아니면 즉시 거절 문구를 반환한다. 이 확인을 먼저 하므로 숫자 123에 문자열 전용 기능을 적용하다가 실행이 멈추는 일을 피한다. 거절할 때는 아직 목록에 손대지 않았다.

title.strip()은 제목의 앞뒤 공백을 제거한 문자열을 만든다. 그 값을 다시 title에 넣는다. if not title:은 그 결과가 빈 문자열인지 확인한다. 제목 중간의 공백은 유지하므로 “메모 정리”는 그대로 저장된다.

len(tasks) + 1은 현재 항목 수 다음의 번호를 만든다. 이번 범위에는 삭제 기능이 없으므로 이 방법으로 성공 순서대로 번호를 붙일 수 있다. 삭제를 추가한다면 번호 규칙을 다시 검토해야 한다. append로 새 딕셔너리를 붙이고, done을 False로 두어 미완료 상태로 시작한다.

complete_task의 첫 조건은 번호의 형식과 범위를 확인한다. type(task_id) is not int는 값의 자료형이 정확히 정수인지 살핀다. Python에서 참·거짓 값은 일부 정수 검사에 포함될 수 있으므로, 이번 명세처럼 이를 번호에서 제외하려면 구분해야 한다. or는 왼쪽 조건이 참이면 오른쪽을 평가하지 않으므로, 문자열 번호를 숫자와 비교하지 않는다.

반복문은 목록에서 번호가 같은 항목을 찾는다. 찾은 항목이 이미 완료라면 별도 문구를 반환한다. 미완료라면 task["done"] = True로 상태를 바꾸고 완료 문구를 반환한다. 반복문이 끝날 때까지 번호를 찾지 못하면 “없는 번호”로 거절한다.

describe_tasks는 목록을 읽어서 상태 문자열을 만든다. 완료 여부에 따라 “완료” 또는 “미완료”를 고르고, 번호와 제목을 함께 parts에 넣는다. " | ".join(parts)는 문자열 목록을 구분자로 연결한다. 이 함수는 목록의 내용을 바꾸지 않는다.

cases의 각 항목은 기능 이름, 입력값, 기대 결과, 기대 상태를 묶은 튜플이다. 튜플은 순서가 있는 값의 묶음이다. 표의 한 행이 튜플 하나에 대응한다. 긴 항목은 여러 줄로 나누었지만 하나의 사례로 처리된다.

tasks = []는 사례 반복문 밖에 있다. 따라서 모든 사례가 같은 목록을 이어서 사용한다. enumerate(cases, start=1)는 사례와 함께 1부터 시작하는 순번을 제공한다. 그다음 줄은 튜플의 네 값을 각각 이름에 넣는다.

기능 이름에 따라 추가 또는 완료 함수를 호출한다. 표에 없는 기능 이름이 들어오면 ValueError를 발생시킨다. 예외란 정상 처리를 이어 갈 수 없음을 알리는 신호다. 여기서는 기능 이름을 잘못 적은 검사 데이터를 조용히 다른 기능으로 처리하지 않도록 사용한다.

실제 결과와 기대 결과, 실제 상태와 기대 상태 중 하나라도 다르면 AssertionError를 발생시킨다. 이때 실행은 마지막 완료 문구에 도달하지 않는다. !r은 값의 표현을 보여 주므로 문자열의 따옴표와 공백을 살펴보는 데 도움이 된다. 비교를 통과한 사례만 결과와 상태를 출력한다.

마지막 조건은 파일을 직접 실행했을 때 main()을 호출하는 관용적인 형태다. 프로그램에는 입력을 기다리는 부분이나 현재 시각을 읽는 부분이 없다. 같은 파일을 다시 실행하면 빈 목록에서 같은 여덟 사례를 시작하므로 출력도 같다.

실행 결과

macOS 또는 Linux의 터미널에서 main.py가 있는 폴더로 이동한 뒤 다음 명령을 실행한다.

python3 main.py

예상 출력은 다음과 같다. 둘째 사례에서 번호가 늘지 않고, 다섯째 사례 이후에는 목록 상태가 유지되는지 함께 읽는다.

1. 추가: 1
   상태: 1:장보기:미완료
2. 거절: 제목이 비어 있음
   상태: 1:장보기:미완료
3. 추가: 2
   상태: 1:장보기:미완료 | 2:메모 정리:미완료
4. 완료: 1
   상태: 1:장보기:완료 | 2:메모 정리:미완료
5. 이미 완료: 1
   상태: 1:장보기:완료 | 2:메모 정리:미완료
6. 거절: 없는 번호
   상태: 1:장보기:완료 | 2:메모 정리:미완료
7. 거절: 번호는 1 이상의 정수
   상태: 1:장보기:완료 | 2:메모 정리:미완료
8. 거절: 제목은 문자열
   상태: 1:장보기:완료 | 2:메모 정리:미완료
확인 완료: 8개 예시 일치

AI에게 코드를 받았을 때는 이 출력이 예상과 같은지 직접 실행하여 확인한다. 코드가 수정되었다면 변경 전후의 차이(diff)도 읽는다. 이번에는 제목 검사보다 목록 변경이 먼저 일어나지 않았는지, 완료 처리가 다른 항목까지 바꾸지 않는지, 기대값이 표와 다르게 수정되지 않았는지를 살핀다. AI의 “명세대로 구현했다”는 설명은 확인할 주장이다.

실무에서 자주 틀리는 것

다음 코드는 각 실수를 따로 살펴보기 위한 짧은 실행 예제다. 완성 코드에 이어 붙이지 않고 각각 실행한다.

공백 제목을 빈 제목과 다르게 취급한다

문자열의 길이가 있다는 이유만으로 제목을 받으면 공백만 있는 제목도 통과한다. 틀린 코드는 원래 문자열이 비었는지만 확인한다.

title = "   "
if title:
    print("추가 가능")
else:
    print("제목 거절")

고친 코드는 앞뒤 공백을 제거한 뒤 판단한다. 판정에 사용한 값과 저장할 값도 같아야 한다. 확인할 때만 공백을 제거하고 원래 제목을 저장하면 첫 사례의 상태와 달라진다.

title = "   "
title = title.strip()
if title:
    print("추가 가능")
else:
    print("제목 거절")

완료 요청을 상태 뒤집기로 구현한다

완료를 두 번 요청했을 때 다시 미완료로 돌아가는 코드는 이번 명세와 맞지 않는다. 틀린 코드는 현재 상태를 반대로 바꾼다.

task = {"id": 1, "title": "장보기", "done": True}
task["done"] = not task["done"]
print(task["done"])

고친 코드는 이미 완료인지 확인하고, 미완료일 때만 완료로 바꾼다. “완료”와 “상태 전환”은 서로 다른 기능이다. 요청에 쓰인 동사가 어떤 상태 변화를 뜻하는지 예시로 구분해야 한다.

task = {"id": 1, "title": "장보기", "done": True}
if task["done"]:
    print("이미 완료: 1")
else:
    task["done"] = True
    print("완료: 1")
print(task["done"])

실제 결과를 기대값으로 사용한다

비교하는 코드가 있어도 양쪽 값의 출처가 같으면 요구를 확인하지 못한다. 다음 틀린 코드는 앞뒤 공백이 남은 결과를 만들고도 자기 자신과 비교하여 통과시킨다.

stored_title = " 장보기 "
expected_title = stored_title
print(stored_title == expected_title)

고친 코드의 기대값은 명세에서 가져온다. 따라서 아직 공백이 남은 결과는 일치하지 않는다고 나온다. 그다음 구현을 고쳐야 하며, 통과시키려고 기대값을 바꾸면 안 된다. 요구 자체가 바뀌었다면 문서와 예시를 먼저 함께 수정한다.

stored_title = " 장보기 "
expected_title = "장보기"
print(stored_title == expected_title)

한눈에 보기

막연한 표현을 확인 가능한 약속으로 바꾸는 방법
막연한 표현구체적인 약속확인할 증거
간단하게 만들어 줘추가·완료만 제공하고 저장과 메뉴는 제외한다.구현에 범위 밖 기능이 없는지 읽는다.
제목을 잘 처리해 줘앞뒤 공백을 제거하고 빈 제목과 비문자열을 거절한다.결과 문구와 저장된 제목을 비교한다.
완료할 수 있게 해 줘번호로 완료하며 반복 완료는 상태를 유지한다.첫 완료와 재요청의 문구·상태를 비교한다.
오류도 처리해 줘잘못된 입력은 정해진 문구로 거절하고 목록을 보존한다.거절 전후 전체 상태를 비교한다.
모르면 물어봐결과를 바꾸는 모호함을 구현 전에 질문한다.답으로 정한 내용을 기획서에 반영한다.
잘 만들었는지 확인해 줘예시 표의 입력과 기대값을 그대로 실행하여 비교한다.실행 출력과 변경 내용을 직접 검토한다.

한 장 기획서는 목적에서 완료 기준까지 이어지는 약속이다. 예시 표는 그 약속 중 일부를 구체적인 값으로 보여 준다. 코드와 예시가 일치해도 확인하지 않은 규칙은 남아 있을 수 있다. 이 구분을 유지하면 실행 결과를 과하게 해석하지 않으면서 다음 작업의 출발점을 정할 수 있다.

다음 장에서는 이렇게 정리한 요청을 바탕으로 작업에 들어가기 전의 계획을 다룬다. 여기서는 기능을 늘리기보다, 구현할 대상과 확인할 결과가 서로 맞는지 정리하는 데 집중한다.

연습 문제

  1. “메모도 편하게 관리하게 해 줘”라는 요청을 받았다. 이번 장의 일곱 항목 가운데 네 항목을 골라 구체적인 결정으로 다시 써라. 파일 저장과 입력 메뉴를 이번 범위에 넣을지도 밝혀라.
  2. 완성 코드의 여덟 사례 뒤에 제목 “장보기”를 다시 추가하는 사례를 적어라. 현재 명세를 유지할 때 결과 문구와 전체 목록 상태를 구하라.
  3. 여덟 사례 뒤에 번호 0과 참 값 True로 각각 완료를 요청한다고 하자. 두 사례의 결과 문구와 목록 상태를 적고, 서로 다른 입력이 같은 거절 문구를 받는 이유를 설명하라.
  4. AI가 빈 제목을 거절하면서도 목록에 빈 항목을 먼저 추가한 코드를 주었다. 둘째 사례에서 어떤 비교가 이를 발견하는지 설명하고, AI에게 보낼 수정 요청을 써라.

정답과 해설

  1. 한 가지 답은 다음과 같다. 목적은 짧은 메모 제목을 실행 중 목록에 모으는 것이다. 사용자는 정해진 예시로 동작을 확인하는 개인이다. 기능은 메모 추가와 목록 확인으로 제한한다. 제외 범위는 파일 저장, 입력 메뉴, 삭제, 수정이다. “편하게”를 화면 모양으로 추측하게 두지 않고 이번에 제공할 행동을 정했다. 다른 선택도 가능하지만 서로 충돌하지 않아야 한다.

  2. 결과는 “추가: 3”이다. 상태는 “1:장보기:완료 | 2:메모 정리:미완료 | 3:장보기:미완료”다. 같은 제목을 허용하며 기존 항목은 둘뿐이므로 다음 번호는 3이다. 새 항목은 기존에 같은 제목이 완료되어 있어도 미완료로 시작한다. 이 사례를 추가하려면 기대값을 먼저 적고 cases 끝에 넣은 뒤 실행하여 확인한다.

  3. 두 입력 모두 “거절: 번호는 1 이상의 정수”를 반환한다. 상태는 각각 “1:장보기:완료 | 2:메모 정리:미완료”로 유지된다. 0은 정수지만 허용 범위 밖이다. True는 이번 명세에서 번호로 받지 않는 참·거짓 값이다. 완성 코드의 형식 검사는 두 입력을 상태 변경 전에 거절한다. 두 사례를 넣으면 완료 문구의 사례 수는 10으로 바뀐다.

  4. 결과 문구 비교만으로는 발견하지 못할 수 있다. 전체 목록 상태 비교가 기대 상태와 실제 상태의 차이를 발견한다. 거절했는데 목록을 바꿨으므로 명세 위반이다. 수정 요청은 다음처럼 쓸 수 있다.

    둘째 사례에서 거절 문구는 맞지만 목록에 빈 항목이 추가된다. 명세에서는 거절 시 기존 목록을 보존한다. 제목의 자료형 검사와 공백 제거 후 빈값 검사를 목록 변경보다 먼저 수행하도록 고쳐라. 기대값은 유지하고 여덟 사례를 다시 실행하여 결과 문구와 전체 상태를 비교하라.

    수정 후에는 실행 결과뿐 아니라 변경된 부분도 읽는다. 검사 순서가 실제로 옮겨졌는지, 기대 상태를 잘못된 결과에 맞춰 바꾸지는 않았는지 확인한다.

오탈자·오류 제보 비공개로 접수되어 원고 수정에 반영됩니다

이메일 등 개인정보는 받지 않습니다. 답변이 필요한 질문은 아래 댓글을 이용해 주세요.

READER FEEDBACK

질문·의견

내용에 관한 질문이나 더 나은 설명을 위한 의견을 남겨 주세요. 오탈자는 위의 제보 양식이 더 빨리 반영됩니다. 이 댓글은 원래 게시글과 같은 자리에 쌓입니다.

댓글 0

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

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