Devin.KR

바이브 코딩의 스펙트럼 - 맡겨 두는 코딩에서 감독하는 코딩까지

개발자KR 조회 0

이 장에서 배우는 것

AI에게 “혼자 쓸 할 일 도구를 만들어 줘”라고 요청하면 짧은 시간 안에 프로그램을 받을 수 있다. 그러나 코드가 도착했다는 사실과 도구가 제대로 작동한다는 사실은 다르다. 화면에 그럴듯한 문장이 나타나더라도, 할 일을 완료할 때 다른 항목까지 바뀌거나 실행할 때마다 내용이 사라질 수 있다. 이 차이를 알아보는 일이 AI와 함께 코딩하는 출발점이다.

이 책에서 다루는 바이브 코딩은 요청을 던지는 순간에 끝나지 않는다. 무엇을 맡겼는지 이해하고, 받은 결과를 실행하고, 기대한 동작과 비교하는 과정까지 포함한다. 다만 이 말이 처음 쓰인 맥락에서는 코드를 자세히 읽지 않고 AI가 만드는 흐름을 따라가는 태도가 강조되었다. 이 장에서는 그 출발점과 감독하는 작업 방식을 구분하고, 작은 할 일 도구로 차이를 확인한다.

  • 바이브 코딩이라는 말의 출발점과 이후 넓어진 쓰임을 설명한다.
  • 버려도 되는 실험과 다른 사람이 사용할 코드에 필요한 확인 수준을 구분한다.
  • AI에 맡기는 양과 사람이 져야 하는 책임의 관계를 이해한다.
  • 요청, 결과, 검증, 기록의 순서로 첫 프로그램을 점검한다.
  • 간단한 assert 검사로 할 일 추가와 완료 처리의 결과를 확인한다.

문제 상황

책상 위 메모가 늘어나 할 일을 두 개만 적어 두는 프로그램을 만들기로 했다고 하자. 원하는 기능은 단순하다. 할 일을 추가하고, 끝낸 일을 완료로 표시하고, 현재 목록을 출력하면 된다. 혼자 쓸 예정이므로 처음부터 복잡한 화면이나 계정 기능은 필요하지 않다.

AI에게 요청하자 프로그램과 함께 “할 일 추가와 완료 처리를 구현했다”는 답이 온다. 실행하면 두 항목이 보인다. 여기서 바로 사용을 시작할 수도 있지만, 출력만 보고 넘어가면 놓치는 문제가 있다. 첫 번째 항목을 완료했는데 두 번째 항목도 함께 완료되었을 수 있다. 제목 앞뒤에 공백이 남아 있을 수 있다. 존재하지 않는 번호를 완료하려다 프로그램이 중단될 수도 있다.

요청: 혼자 쓰는 작은 할 일 도구를 만들어 줘. 할 일을 두 개 추가하고 첫 번째 일을 완료한 뒤 목록을 보여 줘.

AI의 답 예시: 추가와 완료 표시를 넣었다. 아래 코드를 실행하면 목록을 볼 수 있다.

이 답은 작업 내용에 관한 설명이다. 동작이 맞다는 증거는 아직 없다. “완료 표시를 넣었다”는 문장을 읽는 것과 실제로 한 항목만 바뀌는지 확인하는 것은 서로 다른 일이다. 사람은 결과를 확인하는 기준을 가지고 있어야 한다.

이 도구를 잠깐 실행하고 버릴 생각이라면 확인 범위를 좁힐 수 있다. 반면 다음 주에도 사용할 계획이거나 다른 사람에게 건넬 생각이라면 이야기가 달라진다. 다른 사람은 작성자가 알고 있는 제한을 모른다. 목록이 저장되는 줄 알고 중요한 일을 입력할 수도 있다. 따라서 기능뿐 아니라 “무엇을 하지 않는가”도 전달해야 한다. 이번 첫 버전은 목록을 메모리에만 두며, 실행이 끝나면 목록도 사라진다. 이 제한은 뒤에서 다시 확인한다.

바이브 코딩이라는 말의 출발점

바이브 코딩(vibe coding)이라는 표현은 2025년 초, AI에 코드 작성을 맡기고 코드의 세부 내용보다 실행 결과와 작업 흐름을 따라가는 방식을 묘사하면서 널리 알려졌다. 처음의 핵심은 단순히 AI를 쓴다는 데 있지 않았다. 만들어진 코드를 충분히 읽지 않은 채 받아들이고, 오류 메시지를 다시 AI에 보내며, 작동할 때까지 대화를 이어 가는 태도에 가까웠다.

이 방식은 작은 아이디어를 눈에 보이는 결과로 바꾸는 데 유용할 수 있다. 예를 들어 가짜 할 일 두 개를 놓고 목록 표시가 읽기 쉬운지 살펴보는 실험에서는 구현의 모든 줄을 이해하기 전에 실행해 볼 수 있다. 마음에 들지 않으면 전체를 버리고 다시 시작하면 된다. 되돌리는 비용이 작기 때문이다.

그 뒤 이 표현은 AI와 대화하며 개발하는 활동 전반을 가리키는 말로도 쓰였다. 그러면서 코드를 읽고, 변경 내용을 검토하고, 검사 결과를 확인하는 작업까지 같은 이름으로 묶이는 경우가 생겼다. 이에 원래의 느슨한 방식과 구분하려고 AI 보조 개발이나 감독형 작업이라는 표현을 사용하는 흐름도 나타났다. 용어의 경계가 모두에게 똑같이 정해져 있는 것은 아니다.

이 책에서는 구분을 분명히 하기 위해 “맡겨 두는 코딩”과 “감독하는 코딩”이라는 표현을 쓴다. 전자는 구현의 세부 내용을 거의 확인하지 않고 결과를 받아들이는 쪽이다. 후자는 AI가 작성하더라도 사람이 요구와 변경 범위, 실행 결과를 확인하는 쪽이다. 두 표현은 작업 태도를 설명하기 위한 것이며, 서로 다른 도구를 뜻하지 않는다.

AI에 코드 작성을 맡기는 정도와 별개로 사람이 확인하는 범위를 넓힐 수 있다

그림의 양쪽은 사람의 능력을 평가하는 등급이 아니다. 같은 사람도 잠깐 볼 화면을 만들 때는 왼쪽에 가깝게 일하고, 계속 사용할 도구를 고칠 때는 오른쪽에 가깝게 일할 수 있다. 중요한 것은 지금 어떤 방식으로 일하고 있는지 스스로 아는 것이다. 감독하고 있다고 생각하면서 실제로는 AI의 설명만 읽고 넘어가면 확인의 빈틈을 알아차리기 어렵다.

같은 AI 사용도 확인 방식에 따라 달라진다
관점맡겨 두는 쪽감독하는 쪽
요청 뒤의 행동나온 결과를 바로 사용한다기대와 결과를 비교한다
코드 읽기대부분 건너뛴다중요한 동작과 변경 부분을 읽는다
오류가 생겼을 때수정을 맡기고 다시 실행한다무엇이 달라졌는지도 확인한다
완료의 기준원하는 모습이 보인다정한 동작이 검사로 확인된다

2026년 10월 기준 예시로 AI 도구가 여러 파일을 수정하거나 프로그램을 실행한 결과를 제시하는 경우를 생각할 수 있다. 제품마다 가능한 작업과 표시 방식은 다르며 이후에도 바뀔 수 있다. 이 책에서 유지할 원리는 특정 버튼이나 모델 이름이 아니다. 코드를 받았을 때 사람에게 확인할 기준과 확인한 흔적이 있어야 한다는 점이다.

버릴 수 있는 실험과 남길 코드의 책임

작업을 시작할 때는 “이 코드를 버려도 되는가”를 생각해 보면 좋다. 여기서 버린다는 말은 파일을 지울 수 있다는 뜻만은 아니다. 프로그램이 잘못 작동했을 때 잃는 정보가 없고, 다른 사람이 결과에 의존하지 않으며, 다시 만드는 데 큰 비용이 들지 않는다는 뜻이다. 가짜 제목으로 화면을 살펴보는 프로그램은 여기에 가까울 수 있다.

반대로 혼자 쓰는 도구라도 실제 할 일을 맡기기 시작하면 확인할 이유가 커진다. 제출 날짜나 약속을 기록했다면 목록이 사라지는 일이 생활에 영향을 줄 수 있다. 사용자가 한 명이라는 사실만으로 실험이라고 부를 수는 없다. 다른 사람에게 전달할 코드라면 제한을 설명하고, 정상적인 사용에서 어떤 결과가 나오는지 확인할 책임도 더해진다.

AI가 구현을 많이 담당해도 그 결과를 채택하는 결정은 사람의 몫이다. 직접 입력한 코드가 적다고 해서 도구를 사용하는 사람에게 설명할 책임이 줄어드는 것은 아니다. 오히려 읽지 않은 부분이 많을수록 무엇을 확인했고 무엇을 확인하지 못했는지 구분해야 한다. 이 구분은 법적 책임을 판단하는 이야기가 아니라, 프로그램을 만들고 건네는 사람이 지켜야 할 작업상의 책임이다.

코드를 사용하는 상황이 확인 범위를 바꾼다
상황실패의 영향최소한 확인할 내용
가짜 할 일로 잠깐 실험한다실험을 다시 하면 된다실행 여부와 보고 싶은 동작
혼자 실제 할 일을 관리한다기록을 놓치거나 잃을 수 있다추가·완료 동작과 저장 여부
다른 사람에게 도구를 건넨다사용자가 제한을 오해할 수 있다동작 검사와 사용 범위 설명

감독한다고 해서 모든 줄을 직접 작성할 필요는 없다. 이번 프로그램에서 AI는 함수와 출력 형식을 제안할 수 있다. 사람은 제목이 정리되는지, 완료 처리가 한 항목에만 적용되는지, 없는 번호를 다룰 때 어떤 결과가 나오는지를 판단한다. 판단할 근거가 부족하면 더 작은 예로 확인한다. 코드 작성의 양과 결과를 받아들이는 책임을 따로 생각하는 태도가 필요하다.

여전히 필요한 Python 기초

기초 문법은 AI가 받은 요청을 실제로 어떻게 옮겼는지 읽는 도구다. 변수는 어떤 값을 붙잡고 있는지 보여 준다. 조건문은 어느 상황에서 동작이 갈리는지 보여 준다. 반복문은 여러 항목 가운데 무엇을 바꾸는지 보여 준다. 함수는 한 작업의 입력과 결과를 묶어 보여 준다. 이 네 가지를 읽을 수 있어야 이번 코드의 동작을 따라갈 수 있다.

목록과 사전도 사용한다. 목록은 할 일을 순서대로 담는 자료형이다. 사전은 이름표가 붙은 값을 묶는 자료형이다. 이번 코드에서는 한 할 일을 사전 하나로 표현하고, 그 사전들을 목록에 넣는다. 번호, 제목, 완료 여부가 각각 사전의 값이다. 파일 문법을 알고 있더라도 이번 버전에서 파일을 열거나 쓰지 않는다면 저장 기능은 없는 것이다. “할 일 도구니까 저장하겠지”라는 추측을 코드 대신 근거로 삼아서는 안 된다.

assert 문은 조건이 참인지 확인하는 간단한 검사다. 조건이 거짓이면 프로그램을 중단하고 검사 실패를 알린다. 이 장에서는 “첫 번째 항목만 완료되었다”처럼 기대한 상태를 코드로 적는 데 사용한다. 검사가 통과했다는 말의 범위는 적어 둔 조건까지다. 검사하지 않은 모든 사용 방식까지 확인했다는 뜻은 아니다.

이 책의 실습 방식: 요청, 결과, 검증, 기록

실습에서는 먼저 원하는 행동을 짧게 요청한다. 다음으로 받은 코드와 설명을 읽는다. 이어서 직접 실행하고 기대한 상태를 검사한다. 마지막으로 확인한 내용과 남은 제한을 기록한다. 처음에는 네 단계가 번거롭게 느껴질 수 있지만, 프로그램이 커지면 이전에 무엇을 확인했는지 기억만으로 유지하기 어렵다.

요청한 동작은 결과를 실행하고 검사한 뒤 확인 범위와 제한으로 기록한다

이번 실습에 보낼 요청

Python 3.12 이상에서 실행할 main.py 한 파일을 만들어 줘. 표준 라이브러리 범위에서 작성하고 외부 패키지나 네트워크는 사용하지 마.

할 일은 번호, 제목, 완료 여부를 가진다. 목록에 추가할 때 제목 앞뒤의 공백을 없애고, 빈 제목은 받지 않는다. 번호로 한 항목을 완료할 수 있어야 한다. 없는 번호를 완료하려 하면 False를 반환한다.

예제 제목은 ‘Python 파일 실행하기’와 ‘메모 정리하기’다. 첫 번째 항목만 완료하고 목록을 출력해 줘. 사용자 입력을 기다리지 않고 바로 끝나게 해 줘. 실행할 때마다 같은 결과가 나와야 한다.

핵심 결과를 확인하는 assert 검사를 넣어 줘. 이번 버전은 파일에 저장하지 않는다고 설명해 줘.

이 요청은 도구의 첫 모습을 만들기 위한 작은 약속이다. 많은 기능을 나열하기보다 이번 실행에서 관찰할 수 있는 동작을 정한다. 다음 절의 완성 코드는 이 약속을 확인하도록 구성한 예다. 실제 대화에서 AI가 다른 구조를 제시할 수도 있다. 그때도 요청한 결과가 나오는지 확인하는 기준은 유지한다.

받은 답에서 확인할 것

AI의 답 예시: 추가, 완료 처리, 목록 표시를 함수로 나누었다. 제목 앞뒤 공백을 제거하며, 없는 번호에는 False를 반환한다. 목록은 실행 중에만 유지된다. 검사를 포함했으므로 실행해서 결과를 확인할 수 있다.

이 설명을 받았다고 곧바로 확인란을 채우지 않는다. 공백 제거가 실제 코드에 있는지 읽고, 없는 번호를 사용하는 검사 결과를 확인한다. AI가 기존 파일을 수정했다면 변경 전후의 차이인 diff도 검토한다. 예를 들어 완료 표시를 고치면서 목록 전체를 초기화하는 코드가 새로 들어갔다면 요청 범위를 벗어난 변경이다. 첫 파일에는 비교할 이전 코드가 없으므로 전체를 읽고, 이후 수정부터는 바뀐 부분과 그 영향을 함께 살핀다.

이 장의 검사는 작은 예에 집중한다. 항목 두 개를 추가하고 번호와 제목을 살핀다. 첫 항목을 완료한 다음 없는 번호도 전달한다. 이어서 두 항목의 완료 상태와 화면에 표시할 문자열을 비교한다. 빈 제목을 거부하는 코드도 읽지만, 완성 코드의 기본 검사에는 그 경우를 넣지 않았다. 연습 문제에서 그 빈틈을 직접 확인한다.

확인한 내용을 짧게 남기기

검증 기록 예시: 기본 실행에서 두 항목이 추가되었다. 첫 번째 항목만 완료되었다. 없는 번호에는 False가 반환되었다. 제목 앞뒤 공백은 제거되었다. 출력 문자열은 기대한 결과와 같았다.

남은 범위: 빈 제목 거부는 기본 실행에서 검사하지 않았다. 파일 저장, 삭제, 사용자 입력은 제공하지 않는다. 실행을 다시 시작하면 예제 목록을 새로 만든다.

기록은 길 필요가 없다. “잘 된다”보다 어떤 입력과 결과를 확인했는지가 중요하다. 다음 작업을 시작할 때 이 기록을 보면, 유지할 동작과 아직 살펴보지 않은 동작을 구분할 수 있다. 실패한 검사가 있었다면 성공한 것처럼 바꾸어 적지 않는다. 코드를 고친 뒤 다시 확인하고 기록을 갱신한다.

완성 코드

아래 내용을 main.py에 저장한다. 할 일을 추가하는 함수, 완료 처리하는 함수, 목록을 문자열로 만드는 함수가 있다. 마지막 함수는 예제 동작과 검사를 실행한다. 시간, 난수, 사용자 입력을 사용하지 않으므로 정상 실행의 출력은 매번 같다.

def add_task(tasks, title):
    cleaned_title = title.strip()
    if not cleaned_title:
        raise ValueError("제목은 비어 있을 수 없다.")

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


def complete_task(tasks, task_id):
    for task in tasks:
        if task["id"] == task_id:
            task["done"] = True
            return True
    return False


def render_tasks(tasks):
    lines = []
    for task in tasks:
        status = "완료" if task["done"] else "대기"
        lines.append(f'[{status}] {task["id"]}. {task["title"]}')
    return "\n".join(lines)


def main():
    tasks = []
    first_id = add_task(tasks, "  Python 파일 실행하기  ")
    second_id = add_task(tasks, "메모 정리하기")

    assert (first_id, second_id) == (1, 2)
    assert len(tasks) == 2
    assert [task["title"] for task in tasks] == [
        "Python 파일 실행하기",
        "메모 정리하기",
    ]

    completed = complete_task(tasks, first_id)
    missing = complete_task(tasks, 99)

    assert completed is True
    assert missing is False
    assert [task["done"] for task in tasks] == [True, False]

    rendered = render_tasks(tasks)
    expected = (
        "[완료] 1. Python 파일 실행하기\n"
        "[대기] 2. 메모 정리하기"
    )
    assert rendered == expected

    print("할 일 도구 첫 버전")
    print(rendered)
    print("검증: assert 7개 통과")


if __name__ == "__main__":
    main()

번호는 현재 목록 길이에 1을 더해 정한다. 이 방식은 항목을 추가하기만 하는 이번 버전에서 사용한다. 삭제 기능을 나중에 넣으면 같은 번호가 다시 생길 수 있으므로 번호 규칙도 재검토해야 한다. 아직 없는 기능까지 이 구현이 처리한다고 해석하지 않는다.

줄별 해설

첫 줄의 add_task는 목록과 제목을 받는다. 바로 다음 줄의 strip은 문자열 앞뒤의 공백을 제거한다. 제목 사이에 있는 공백은 그대로 둔다. 따라서 ‘Python 파일 실행하기’에서 단어 사이의 공백은 유지된다. 정리한 제목이 빈 문자열이면 조건문 안으로 들어가 ValueError를 발생시킨다. ValueError는 전달한 값이 작업에 맞지 않음을 알리는 예외다. 예외는 정상적인 진행을 중단하고 문제를 알리는 방식이다.

task_id를 만드는 줄은 목록에 들어 있는 항목 수를 사용한다. 처음에는 길이가 0이므로 번호는 1이다. 이어지는 사전에는 id, title, done이라는 세 이름표가 있다. 새 할 일의 done은 False다. append는 이 사전을 목록 끝에 추가한다. return은 생성한 번호를 함수 밖으로 돌려준다. 목록을 바꾸는 일과 번호를 반환하는 일이 모두 이 함수에서 일어난다.

complete_task는 반복문으로 항목을 하나씩 살핀다. 번호가 일치하는 항목을 만나면 그 항목의 done을 True로 바꾸고 즉시 True를 반환한다. 반환하면 함수 실행이 끝나므로 뒤의 항목은 더 살피지 않는다. 마지막 return False는 반복문 바깥에 있다. 따라서 전체 목록을 살펴도 일치하는 번호가 없을 때만 실행된다. 이 들여쓰기 위치가 동작을 결정한다.

render_tasks는 표시할 문장들을 담을 빈 목록으로 시작한다. 각 항목의 완료 여부에 따라 ‘완료’ 또는 ‘대기’를 선택한다. 한 줄로 적은 조건식은 ‘완료 상태이면 완료라는 문자열을 쓰고, 아니면 대기라는 문자열을 쓴다’는 뜻이다. 이어지는 문자열에는 상태, 번호, 제목을 넣는다. 마지막의 join은 문장 사이에 줄바꿈을 넣어 문자열 하나로 합친다. 이 함수는 할 일의 내용을 바꾸지 않는다.

main의 첫 줄은 빈 할 일 목록을 만든다. 다음 두 줄은 할 일을 추가하고 반환된 번호를 변수에 담는다. 첫 번째 제목에는 의도적으로 앞뒤 공백이 있다. 보기 좋은 입력만 사용하면 공백 제거가 빠져 있어도 알아차리기 어렵기 때문이다. 같은 입력을 반복해서 사용하므로 실행 결과를 비교하기도 쉽다.

첫 assert는 두 번호가 각각 1과 2인지 비교한다. 쉼표로 묶은 두 값은 튜플이라는 자료형으로, 여기서는 두 번호를 한꺼번에 비교하는 데 쓴다. 두 번째 assert는 목록 길이가 2인지 살핀다. 세 번째 assert는 각 사전에서 제목만 꺼내 기대한 목록과 비교한다. 대괄호 안의 for 구문은 목록 내포라는 표현이다. 반복문으로 각 항목의 제목을 꺼내 새 목록을 만드는 짧은 문법이다.

그 다음 두 함수 호출은 실제 작업이다. 첫 번째 번호를 완료 처리한 결과는 completed에, 존재하지 않는 99번을 전달한 결과는 missing에 담는다. 호출을 assert 안에 넣지 않은 이유도 있다. assert는 실행 옵션에 따라 생략될 수 있으므로, 목록을 바꾸는 작업을 검사문 안에 넣으면 실행 방식에 따라 동작 자체가 달라질 수 있다.

네 번째와 다섯 번째 assert는 반환값을 살핀다. is True와 is False는 각각 참과 거짓 값을 돌려받았는지 확인한다. 여섯 번째 assert는 두 항목의 실제 완료 상태를 비교한다. 함수가 True를 반환했더라도 목록을 잘못 바꿨을 수 있으므로 반환값과 저장된 상태를 각각 확인한다. 여기서는 없는 번호를 처리한 뒤에도 상태가 [True, False]인지 함께 살핀다.

rendered에는 목록 표시 결과를 담는다. expected의 괄호 안에는 문자열 두 개가 나란히 있다. Python은 이 문자열들을 하나로 이어 붙인다. 첫 문자열 끝의 \n이 두 항목 사이의 줄바꿈을 만든다. 일곱 번째 assert는 결과 문자열 전체를 비교한다. 마지막 출력 세 줄은 모든 검사를 통과한 다음에 실행된다.

파일 끝의 조건문은 이 파일을 프로그램으로 직접 실행했을 때 main을 호출한다. 함수를 정의하는 것만으로는 함수 안의 예제 작업이 시작되지 않는다. 이 조건문이 실행의 출발점을 연결한다. 코드의 내용은 표준 문법으로 작성되어 있지만, 실제 사용 환경에서의 실행 확인은 다음 절의 명령과 예상 출력을 비교해 수행한다.

실행 결과

main.py를 저장한 폴더에서 다음 명령을 실행한다. 명령은 Python 프로그램을 시작하는 실제 실행 명령이다.

python3 main.py

기본 실행에서 예상하는 출력은 다음과 같다. 첫 항목은 완료, 두 번째 항목은 대기로 표시된다. 마지막 줄까지 나왔다면 이 실행에서 일곱 개의 assert 조건이 모두 참이었다는 뜻이다.

할 일 도구 첫 버전
[완료] 1. Python 파일 실행하기
[대기] 2. 메모 정리하기
검증: assert 7개 통과

이 장에서는 assert를 생략하는 최적화 옵션을 붙이지 않는다. 그러한 옵션으로 실행하면 검사문이 빠질 수 있고, 출력만으로 검사 수행 여부를 알 수 없게 된다. 제시한 기본 명령을 그대로 사용한다. assert는 개발 중 결과를 점검하는 데 사용하며, 빈 제목을 막는 실제 입력 확인은 add_task의 조건문으로 따로 처리한다.

검사가 실패하면 위의 완성된 출력 대신 오류 정보가 나타난다. 이때 마지막 성공 문장을 먼저 출력하도록 옮겨 문제를 감추지 않는다. 어느 조건에서 기대와 결과가 달라졌는지 읽고 코드를 확인해야 한다. 출력이 예상과 같아도 파일 저장 기능이 생기는 것은 아니다. 프로그램을 다시 실행하면 main에서 빈 목록부터 새로 만든다.

실무에서 자주 틀리는 것

검사문 안에서 필요한 작업을 실행한다

다음 두 예는 각각 별도로 실행할 수 있는 작은 프로그램이다. 틀린 예는 항목을 바꾸는 함수를 assert 안에서 호출한다. 기본 실행에서는 작동하지만, 검사문이 생략되는 실행에서는 완료 처리도 함께 빠진다. 필요한 작업은 먼저 실행하고 그 결과를 검사한다.

def mark_done(task):
    task["done"] = True
    return True

task = {"done": False}
assert mark_done(task) is True
print(task["done"])

고친 코드는 다음과 같다.

def mark_done(task):
    task["done"] = True
    return True

task = {"done": False}
result = mark_done(task)
assert result is True
print(task["done"])

목록이 있다는 사실만 확인한다

목록이 비어 있지 않다는 검사는 제목이나 완료 상태가 맞는지 알려 주지 않는다. 틀린 예는 앞뒤 공백이 남은 제목을 그대로 통과시킨다. 고친 예는 기대하는 제목을 구체적으로 비교한다. 검사도 원하는 동작에 맞게 작성해야 한다.

tasks = [{"title": "  메모 정리하기  ", "done": False}]
assert tasks
print("검사 통과")

고친 코드는 다음과 같다.

title = "  메모 정리하기  "
tasks = [{"title": title.strip(), "done": False}]
assert tasks[0]["title"] == "메모 정리하기"
assert tasks[0]["done"] is False
print("검사 통과")

첫 항목만 보고 없는 번호라고 판단한다

False를 반환하는 줄을 반복문 안에 두면, 첫 항목의 번호가 다르다는 이유만으로 검색을 끝낸다. 틀린 예에서 2번 항목은 있지만 결과는 False다. 고친 예는 모든 항목을 살핀 뒤 실패를 반환한다. AI가 작은 들여쓰기를 바꾸었더라도 결과에 영향을 줄 수 있으므로 변경 내용을 읽어야 한다.

def find_task(tasks, task_id):
    for task in tasks:
        if task["id"] == task_id:
            return True
        return False
    return False

tasks = [{"id": 1}, {"id": 2}]
print(find_task(tasks, 2))

고친 코드는 다음과 같다.

def find_task(tasks, task_id):
    for task in tasks:
        if task["id"] == task_id:
            return True
    return False

tasks = [{"id": 1}, {"id": 2}]
assert find_task(tasks, 2) is True
assert find_task(tasks, 99) is False
print("검사 통과")

첫 버전의 확인을 모든 사용에 확대한다

“번호가 맞았다”는 확인만으로는 항목 삭제 뒤에도 번호가 겹치지 않는다고 말할 수 없다. 다음 틀린 예는 항목 하나를 없앤 뒤 목록 길이로 새 번호를 만든다. 남은 항목과 번호가 겹친다. 이번 완성 코드에서는 삭제를 제공하지 않으므로 이 문제가 나타나지 않지만, 기능을 추가하면 이전 판단을 그대로 가져올 수 없다.

tasks = [{"id": 1}, {"id": 2}]
tasks.pop(0)
new_id = len(tasks) + 1
tasks.append({"id": new_id})
print([task["id"] for task in tasks])

다음은 같은 작은 상황에서 번호의 중복을 막는 수정 예다. 발급할 다음 번호를 목록 길이와 따로 관리한다. 실제 도구에 삭제 기능을 넣을 때는 이 변수도 전체 구조에 맞게 유지해야 한다. 여기서는 수정 방향과 확인할 조건만 살핀다.

tasks = [{"id": 1}, {"id": 2}]
next_id = 3
tasks.pop(0)
tasks.append({"id": next_id})
next_id += 1
assert [task["id"] for task in tasks] == [2, 3]
print([task["id"] for task in tasks])

한눈에 보기

첫 프로그램에서 유지할 작업 원칙
핵심이번 예에서의 의미사람이 확인할 근거
용어의 출발점코드를 자세히 읽지 않고 흐름을 따라간다현재 작업에서 실제로 무엇을 읽는지
감독하는 작업작성을 맡기고 결과를 점검한다코드, 실행 결과, 수정 시 diff
사용 범위고정된 예제 목록을 실행 중에만 둔다파일 저장 코드가 없다는 사실
기초 문법조건과 반복이 어느 항목을 바꾸는지 읽는다함수의 반환 위치와 들여쓰기
검증번호, 제목, 완료 상태, 출력이 맞는지 비교한다일곱 개의 assert 조건
기록확인한 동작과 남은 제한을 적는다입력과 기대 결과를 명시한 문장

이 첫 버전에서 AI에 맡길 수 있는 것은 코드 초안을 만드는 일이다. 사람이 남겨 둘 것은 결과를 받아들일 기준과 그 기준을 확인하는 일이다. 다음 장에서는 이러한 구분을 바탕으로, 어떤 작업을 맡기고 어떤 판단을 직접 유지할지 살펴본다.

연습 문제

  1. 가짜 제목 두 개로 목록 모양만 살펴보는 경우와, 실제 약속을 적어 다른 사람에게 전달하는 경우를 비교한다. 각각 실패하면 무엇이 영향을 받는지와 확인할 내용을 두 문장씩 적는다.
  2. 완성 코드가 공백만 있는 제목을 거부하는지 검사한다. add_task를 그대로 두고, main의 첫 번째 할 일 추가 전에 검사를 넣는다. 예외가 발생한 뒤에도 목록이 비어 있는지 확인한다.
  3. 두 번째 항목도 완료하도록 main을 수정한다. 바꿔야 하는 상태 검사와 예상 문자열을 함께 수정하고 실행한다. 첫 번째 항목을 완료하는 동작은 유지한다.
  4. AI가 “검사가 있으므로 바로 사용해도 된다”고 답했다고 가정한다. 이번 첫 버전에서 확인한 동작 두 가지와 확인하지 않은 범위 두 가지를 포함해 짧은 검증 기록을 작성한다.

정답과 해설

1. 실패의 영향을 기준으로 구분한다

목록 모양을 보는 실험은 잘못되어도 가짜 제목으로 다시 실행하면 된다. 실행 여부와 두 항목이 구분되어 표시되는지를 먼저 확인할 수 있다. 실제 약속을 기록한 도구는 내용이 사라지거나 상태가 잘못 바뀌면 사용자가 일을 놓칠 수 있다. 저장 여부와 완료 처리 범위를 확인하고, 전달받는 사람에게 제한을 설명해야 한다.

다른 답도 가능하지만 “혼자 쓴다” 또는 “AI가 작성했다”만으로 확인을 줄인 답은 충분하지 않다. 사용자가 몇 명인지와 함께 어떤 정보가 들어가고 누가 결과에 의존하는지를 살펴야 한다.

2. 빈 제목을 거부하고 목록을 유지하는지 확인한다

다음은 필요한 함수까지 포함해 별도로 실행할 수 있는 검사다. try는 문제가 발생할 수 있는 작업을 실행하고, except는 지정한 예외가 발생했을 때 실행한다. rejected는 기대한 거부가 일어났는지 기록한다. 예외가 발생했는지와 목록이 바뀌지 않았는지를 각각 확인한다.

def add_task(tasks, title):
    cleaned_title = title.strip()
    if not cleaned_title:
        raise ValueError("제목은 비어 있을 수 없다.")
    task_id = len(tasks) + 1
    tasks.append({
        "id": task_id,
        "title": cleaned_title,
        "done": False,
    })
    return task_id

tasks = []
rejected = False

try:
    add_task(tasks, "   ")
except ValueError:
    rejected = True

assert rejected is True
assert tasks == []
print("빈 제목 검사 통과")

완성 코드에 넣을 때는 tasks를 빈 목록으로 만든 다음, 첫 번째 정상 제목을 추가하기 전에 rejected부터 두 assert까지의 부분을 넣는다. 이 검사에는 assert가 두 개 더 있으므로 마지막 성공 문구의 개수도 9개로 바꾼다. 검사 기록에는 빈 제목 거부와 목록 유지가 확인되었다고 추가한다.

3. 동작과 기대 결과를 함께 바꾼다

완성 코드에서 completed를 구한 다음 두 번째 번호로도 complete_task를 호출한다. 그 반환값을 변수에 담고 True인지 검사하면 동작을 더 분명히 확인할 수 있다. 아래 프로그램은 두 항목 완료와 표시 결과를 별도로 실행해 확인하는 예다.

def complete_task(tasks, task_id):
    for task in tasks:
        if task["id"] == task_id:
            task["done"] = True
            return True
    return False

def render_tasks(tasks):
    lines = []
    for task in tasks:
        status = "완료" if task["done"] else "대기"
        lines.append(f'[{status}] {task["id"]}. {task["title"]}')
    return "\n".join(lines)

tasks = [
    {"id": 1, "title": "Python 파일 실행하기", "done": False},
    {"id": 2, "title": "메모 정리하기", "done": False},
]
first_result = complete_task(tasks, 1)
second_result = complete_task(tasks, 2)

assert first_result is True
assert second_result is True
assert [task["done"] for task in tasks] == [True, True]

expected = (
    "[완료] 1. Python 파일 실행하기\n"
    "[완료] 2. 메모 정리하기"
)
rendered = render_tasks(tasks)
assert rendered == expected
print(rendered)

완성 코드에서도 상태의 기대값은 [True, True]로 바꾸고, expected의 두 번째 줄을 ‘완료’로 바꾼다. 두 번째 반환값을 확인하는 assert를 추가했다면 전체 검사 수는 8개가 된다. 기능만 바꾸고 이전 기대값을 그대로 두면 검사 실패가 나타나는 것이 정상이다. 기대값을 바꿀 때는 새 요구에 맞는 변경인지 먼저 판단해야 한다.

4. 확인과 제한을 구분해 기록한다

검증 기록: 기본 실행으로 두 항목의 번호가 1과 2인지 확인했다. 첫 번째 항목을 완료한 뒤 두 번째 항목은 대기로 남는지 확인했다. 없는 번호를 처리한 결과와 표시 문자열도 비교했다.

확인 범위의 제한: 기본 검사에는 빈 제목 거부가 포함되지 않았다. 파일 저장 기능은 없으며, 실제 사용자 입력을 받는 상황도 다루지 않았다. 이 버전을 실제 기록 보관용으로 사용하려면 저장 동작을 추가하고 확인해야 한다.

AI의 확신에 찬 설명보다 직접 확인한 입력과 결과가 기록의 근거가 된다. 이번 코드가 일곱 검사를 통과했다는 사실은 유용하다. 그 사실이 다음 기능이나 모든 사용 상황까지 대신 확인해 주지는 않는다. 작은 결과를 받아들이고, 그 결과를 뒷받침하는 확인 범위를 함께 남기는 것이 이 책의 실습 태도다.

댓글 0

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

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