Devin.KR

명세로 생각하기 - 입력·출력·예시·완료 기준

개발자KR 조회 0

이 장에서 배우는 것

앞 장에서 남이 쓴 코드를 읽으며 값이 어떻게 바뀌는지 따라갔다. 이번에는 코드를 읽기 전에 무엇을 확인할지 적는다. 프로그램이 실행된다는 사실과 원하는 일을 한다는 사실은 다르다. 화면에 그럴듯한 문장이 나와도, 내가 정한 기준과 다르면 고쳐야 한다. 그 차이를 알아보려면 먼저 원하는 결과를 말로 분명하게 써야 한다.

명세(specification)는 프로그램이 받아야 할 입력과 내놓아야 할 결과, 지켜야 할 조건을 적은 문서다. 길고 복잡한 문서일 필요는 없다. 이 장에서는 학습 시간을 분류하는 작은 기능을 한 쪽으로 정리하고, 거기에 적힌 예시로 학생이 쓴 함수를 검사한다. 함수를 직접 작성하기보다 명세와 코드를 나란히 읽고 실행해 보는 것이 목표다.

  • 막연한 부탁에서 빠진 결정을 찾아낸다.
  • 무엇을 만들지와 어떻게 만들지를 구분한다.
  • 입력·출력·성공 예시·실패 예시·완료 기준으로 한 쪽 명세를 쓴다.
  • 명세의 예시 표와 실제 결과를 비교해 통과와 실패를 확인한다.

예제의 학습 시간과 분류 기준은 모두 설명용 가상값이다. 학습 효과나 개인의 역량을 평가하는 기준이 아니다. 작은 도구의 약속을 구체적으로 적기 위해 설정한 값이다.

문제 상황

개발 공부를 시작한 학생이 학습 기록장에 붙일 기능을 만들려고 한다. 하루 동안 공부한 시간을 입력하면 짧은 상태 문구를 보여 주고 싶다. 학생은 AI에게 다음과 같이 요청한다.

공부 시간을 보고 오늘 잘했는지 알려 주는 함수를 만들어 줘.

AI는 시간을 입력받아 격려 문장을 돌려주는 코드를 제안한다. 실행하면 문장이 나온다. 그런데 학생이 생각한 결과와는 다르다. 학생은 60분부터 ‘충분’이라고 표시되길 원했지만, 코드는 60분을 ‘진행’으로 분류한다. 문장도 입력할 때마다 달라져서 기록장에 일정한 상태를 저장하기 어렵다.

이 상황에서 먼저 확인할 것은 누가 코드를 잘못 썼는지가 아니다. 요청에 어떤 결정이 빠졌는지다. ‘공부 시간’은 분인지 시간인지 정해지지 않았다. ‘잘했다’는 말도 30분을 뜻하는지 60분을 뜻하는지 알 수 없다. 숫자 대신 문자가 들어오거나 시간이 음수일 때의 행동도 없다. 요청을 받은 쪽은 빈칸을 자기 방식으로 채울 수밖에 없다.

사람에게 일을 맡길 때도 같은 일이 생긴다. 학습 기록장의 화면을 만드는 사람은 짧은 상태 단어가 필요하고, 기록을 정리하는 사람은 일정한 분류 규칙이 필요하다. 그런데 요청이 ‘좋은 문장을 보여 달라’에 머물면 각자 다른 결과를 떠올린다. 작업이 진행된 뒤에야 차이가 드러나면 코드뿐 아니라 화면과 설명도 다시 고쳐야 한다.

AI가 코드 초안을 빠르게 제시해도 이 빈칸이 저절로 사라지지는 않는다. 코드 작성에 쓰는 시간이 줄어들수록, 어떤 결과를 받아들일지 결정하고 확인하는 일이 더 눈에 띈다. 개발자가 하는 일도 이런 판단을 포함한다. 이번 장에서는 판단의 기준을 먼저 적어 두는 방법을 배운다.

무엇을 만들지와 어떻게 만들지를 나눈다

‘무엇을 만들지’는 밖에서 관찰할 수 있는 약속이다. 정수 60을 넣었을 때 문자열 ‘충분’을 돌려준다는 문장이 여기에 속한다. 정수는 소수 부분이 없는 수이고, 문자열은 글자를 담는 값이다. 입력의 단위가 분이라는 약속도 무엇을 만들지에 해당한다.

‘어떻게 만들지’는 그 약속을 코드로 구현하는 방법이다. 조건문을 몇 개 쓰는지, 어떤 조건을 먼저 확인하는지, 결과를 담을 변수 이름을 무엇으로 정하는지가 여기에 속한다. 같은 약속을 지키는 코드가 여러 가지일 수 있다. 따라서 결과의 약속과 작성 방법을 한 문장에 섞으면 검토가 어려워진다.

원하는 동작과 구현 방법을 구분하는 예
문장구분확인할 대상
입력은 분 단위 정수다.무엇을 만들지받을 수 있는 값의 종류
60분 이상이면 ‘충분’을 반환한다.무엇을 만들지입력과 결과의 관계
잘못된 입력이면 ‘입력 오류’를 반환한다.무엇을 만들지허용하지 않는 값의 처리
큰 시간 구간부터 조건문으로 확인한다.어떻게 만들지코드 안의 판단 순서

반환(return)은 함수가 자신을 부른 곳으로 결과를 돌려주는 일이다. 화면에 글자를 표시하는 것과는 다르다. 이 기능을 학습 기록장에 붙이려면 다른 코드가 결과를 받아 저장하거나 표시할 수 있어야 한다. 따라서 ‘화면에 충분이라고 나온다’보다 ‘문자열 충분을 반환한다’가 이 기능의 약속으로 더 정확하다.

명세에서 구현 방법을 전부 없애야 한다는 뜻은 아니다. 이번 프로그램처럼 정해진 함수에 값을 넣어 검사하려면 함수 이름과 받는 값의 개수도 맞아야 한다. 함수 이름은 서로 연결하기 위한 약속으로 정하고, 함수 안의 조건문 구조는 작성자가 선택하도록 둘 수 있다. 무엇을 고정해야 함께 작업할 수 있는지 생각하는 것이 핵심이다.

입력과 기대 결과의 약속을 먼저 정하면 여러 구현을 같은 기준으로 확인할 수 있다

처음부터 코드의 세부 모양에 집중하면 ‘조건문이 있으니 맞다’고 생각하기 쉽다. 그러나 조건문이 있다는 사실은 60분을 올바르게 분류한다는 증거가 아니다. 먼저 결과의 약속을 읽고, 그다음 코드가 그 약속을 지키는지 따라가야 한다.

한 쪽 명세에는 다섯 가지를 적는다

한 쪽 명세는 분량을 제한하는 서식이라기보다 중요한 결정을 한곳에서 읽게 하는 도구다. 이번 기능에서는 입력, 출력, 성공 예시, 실패 예시, 완료 기준을 적는다. 먼저 규칙을 문장으로 쓰고, 그 문장이 구체적인 값에서 어떤 뜻인지 예시로 보여 준다.

학습 시간 분류 함수의 한 쪽 명세
항목약속
목적하루의 학습 시간을 기록장에 저장할 짧은 상태로 분류한다.
입력student_status라는 함수가 값 하나를 받는다. 단위는 분이다. 허용하는 값은 0 이상의 정수이며, 참·거짓 값은 제외한다.
출력문자열 하나를 반환한다. 0~29분은 ‘시작’, 30~59분은 ‘진행’, 60분 이상은 ‘충분’이다.
잘못된 입력음수와 정수가 아닌 값은 ‘입력 오류’를 반환한다. 문자로 쓴 숫자를 정수로 바꾸지 않는다.
성공 예시0 → ‘시작’, 30 → ‘진행’, 60 → ‘충분’처럼 허용하는 입력의 결과를 적는다.
실패 예시-1 → ‘입력 오류’, 문자열 ‘30’ → ‘입력 오류’처럼 입력을 받아들이지 않는 경우의 결과를 적는다.
완료 기준아래의 예시 여덟 개가 모두 통과하고, 코드를 읽어 입력 제한과 모든 구간의 처리가 명세와 일치함을 확인한다.

여기서 ‘성공 예시’는 정상적으로 받아들이는 입력의 예시다. ‘실패 예시’는 받아들이지 않는 입력의 예시다. 실패 예시라고 해서 프로그램이 멈춰야 하는 것은 아니다. 잘못된 입력에 ‘입력 오류’를 돌려주는 것도 명세가 요구하는 정상적인 처리다.

검사 결과의 ‘실패’는 다른 뜻이다. 예를 들어 -1을 넣었을 때 ‘입력 오류’가 나오면 그 검사는 통과다. 반대로 60을 넣었는데 ‘진행’이 나오면 검사는 실패다. 입력이 허용되는지와 구현이 약속을 지키는지는 별개의 판단이다.

검사기에 옮길 입력과 기대 출력의 예시 표
입력기대 출력예시의 의미
0시작학습 시간이 없는 날도 기록한다.
29시작시작 구간의 마지막 값이다.
30진행진행 구간의 첫 값이다.
59진행진행 구간의 마지막 값이다.
60충분충분 구간의 첫 값이다.
90충분60보다 큰 허용 입력이다.
-1입력 오류음수는 허용하지 않는다.
문자열 ‘30’입력 오류숫자처럼 보여도 정수가 아니다.

기대 출력은 프로그램을 실행하기 전에 명세를 보고 적은 결과다. 실행한 코드가 내놓은 값을 그대로 기대 출력으로 적으면 비교 기준이 사라진다. 이번에는 사람이 정한 표를 코드의 데이터로 옮긴다. 검사기는 그 표를 읽어 같은 입력을 함수에 넣고, 실제 결과가 기대 출력과 같은지 확인한다.

입력 제한에도 선택이 들어간다. 어떤 도구는 문자열 ‘30’을 정수 30으로 바꾸는 편이 편리할 수 있다. 이 도구는 바꾸지 않기로 정했다. 어느 선택이든 사용할 사람과 만드는 사람이 같은 내용을 알아야 한다. ‘알아서 처리한다’는 표현으로 남겨 두면 그 판단을 실행 결과에서 뒤늦게 발견하게 된다.

완료 기준은 언제 작업을 끝낼 수 있는지 정한다. ‘잘 작동하면 완료’라고 쓰면 무엇을 보여 줘야 끝나는지 알기 어렵다. 이 명세는 예시가 모두 통과해야 한다는 조건과 코드 읽기로 규칙을 확인한다는 조건을 함께 둔다. 여덟 예시만으로 가능한 모든 입력을 확인했다고 말할 수는 없기 때문이다.

명세는 함께 확인할 수 있는 계약이다

이 장에서 계약이라는 말은 법률 문서를 뜻하지 않는다. 요청한 사람과 구현한 사람이 결과를 판단할 때 함께 펼쳐 볼 약속이라는 뜻이다. 구현한 사람은 무엇을 지켜야 하는지 알고, 요청한 사람은 어떤 결과를 받아들일지 설명할 수 있다. 결과가 다를 때도 막연히 ‘마음에 들지 않는다’고 말하는 대신 어긋난 항목을 가리킬 수 있다.

AI에게 요청할 때도 한 쪽 명세를 전달한다. 다음 요청문은 앞에서 정한 규칙을 짧게 옮긴 예다. 명세의 표를 함께 전달하면 문장으로 쓴 조건과 구체적인 결과를 동시에 볼 수 있다.

학습 시간을 분류하는 student_status 함수를 작성해 줘. 값 하나를 받아 문자열 하나를 반환해야 한다. 입력은 분 단위의 0 이상 정수이며 참·거짓 값은 제외한다. 0~29는 시작, 30~59는 진행, 60 이상은 충분이다. 음수나 다른 종류의 값은 입력 오류를 반환한다. 문자열 숫자를 변환하지 않는다. 함께 제공한 입력과 기대 출력 표를 기준으로 작성하고, 각 조건이 어느 규칙을 구현하는지 설명해 줘.

구체적인 요청이 코드의 정확성을 보장하지는 않는다. AI의 설명과 코드가 서로 다를 수도 있고, 조건 기호 하나가 명세와 어긋날 수도 있다. 그래서 답을 받은 뒤에는 실행하고, 예시와 비교하고, 코드의 조건을 읽는다. 이번 명세 검사기는 그중 예시와 비교하는 일을 눈으로 확인하게 해 준다.

명세를 읽다가 모호한 점이 발견되면 먼저 약속을 고친다. 예를 들어 0분에도 ‘시작’이라고 표시하는 것이 기록장의 목적과 맞지 않을 수 있다. 그렇다면 출력 규칙과 0의 기대 출력을 함께 바꿔야 한다. 코드만 바꾸면 문서가 옛 약속을 담게 되고, 표만 바꾸면 구현이 옛 동작을 유지할 수 있다.

명세의 기대 출력과 함수의 실제 출력을 비교해야 약속을 지켰는지 판단할 수 있다

예시 표는 명세 전체를 대신하지 않는다. 표에 없는 45분도 문장으로 적은 규칙에 따라 ‘진행’이어야 한다. 명세 문장은 처리할 범위를 설명하고, 예시는 그 문장의 뜻을 구체화한다. 둘이 충돌하면 어느 쪽이 의도인지 결정한 뒤 함께 수정해야 한다.

이번 장에서는 이미 정한 예시를 구현과 대조하는 데 집중한다. 더 다양한 잘못된 경우를 어떻게 찾아낼지는 다음 장에서 다룬다. 지금 필요한 습관은 코드를 받은 다음 기준을 떠올리는 대신, 기준을 먼저 적어 결과와 나란히 보는 것이다.

완성 코드

아래 코드를 main.py라는 파일에 저장한다. 학생이 쓴 student_status 함수에는 명세와 다른 조건 하나를 의도적으로 남겨 두었다. 프로그램 자체는 정상적으로 실행되고 끝나지만, 검사 결과에는 실패가 나온다. 실행 오류와 명세 불일치가 다르다는 점을 보여 주기 위한 구성이다.

examples에는 앞의 예시 표를 그대로 옮긴다. 함수와 예시의 시간 값은 설명용 가상값이다. 외부 패키지나 네트워크 연결 없이 Python 3.12 이상에서 실행할 수 있다.

def student_status(minutes):
    if type(minutes) is not int:
        return "입력 오류"
    if minutes < 0:
        return "입력 오류"
    if minutes > 60:
        return "충분"
    if minutes >= 30:
        return "진행"
    return "시작"


examples = [
    (0, "시작"),
    (29, "시작"),
    (30, "진행"),
    (59, "진행"),
    (60, "충분"),
    (90, "충분"),
    (-1, "입력 오류"),
    ("30", "입력 오류"),
]


def main():
    passed = 0
    print("학습 시간 명세 검사")
    for minutes, expected in examples:
        actual = student_status(minutes)
        if actual == expected:
            result = "통과"
            passed += 1
        else:
            result = "실패"
        print(
            f"{result} | 입력={minutes!r} | "
            f"기대={expected} | 실제={actual}"
        )
    print(f"결과: {len(examples)}개 중 {passed}개 통과")
    if passed == len(examples):
        print("예시 검사 완료: 코드 읽기로 명세도 확인한다.")
    else:
        print("수정 필요: 명세와 다른 결과가 있다.")


main()

줄별 해설

줄 번호는 빈 줄도 포함해 위 코드의 첫 줄부터 센다. 들여쓰기는 줄 앞의 공백이다. Python은 이 공백으로 어떤 문장이 함수나 조건문에 속하는지 구분한다. 아래 설명에서는 한 문장이 여러 줄에 걸친 경우 함께 읽는다.

완성 코드의 각 줄이 맡은 일
줄읽는 방법
1def는 함수를 정의한다. student_status라는 이름을 붙이고, 받은 값을 minutes라는 이름으로 사용한다.
2type은 값의 종류를 알려 준다. 종류가 int, 즉 정수와 같은 종류가 아니면 아래 줄을 실행한다. 이 조건은 참·거짓 값도 제외한다.
3‘입력 오류’를 반환하고 함수 실행을 끝낸다. 뒤의 시간 비교는 하지 않는다.
4정수인 입력이 0보다 작은지 확인한다.
5음수라면 ‘입력 오류’를 반환하고 끝낸다.
660보다 큰지 확인한다. 명세의 ‘60 이상’과 다른 부분이다.
7앞 조건이 참이면 ‘충분’을 반환한다.
8아직 반환하지 않았다면 30 이상인지 확인한다.
930 이상인 값에 ‘진행’을 반환한다. 현재 코드에서는 60도 이 줄에 도착한다.
10앞에서 반환되지 않은 허용 입력에 ‘시작’을 반환한다.
11~12빈 줄로 함수와 예시 데이터를 구분한다. 실행할 문장은 없다.
13여러 값을 순서대로 담는 목록인 리스트를 만들고 examples라는 이름을 붙인다.
14입력 0과 기대 출력 ‘시작’을 한 쌍으로 담는다. 괄호로 묶은 이 값의 묶음을 튜플이라고 한다.
15입력 29와 기대 출력 ‘시작’을 담는다.
16입력 30과 기대 출력 ‘진행’을 담는다.
17입력 59와 기대 출력 ‘진행’을 담는다.
18입력 60과 기대 출력 ‘충분’을 담는다.
19입력 90과 기대 출력 ‘충분’을 담는다.
20입력 -1과 기대 출력 ‘입력 오류’를 담는다.
21문자열 ‘30’과 기대 출력 ‘입력 오류’를 담는다. 따옴표가 있으므로 정수 30과 다르다.
22예시 목록을 닫는다.
23~24예시 목록과 실행 함수를 빈 줄로 구분한다.
25검사 과정을 묶은 main 함수를 정의한다. 이 줄만으로 검사가 시작되지는 않는다.
26통과한 예시의 개수를 0에서 시작한다.
27검사 제목을 화면에 출력한다.
28for로 예시를 하나씩 읽는다. 각 쌍의 첫 값은 minutes, 둘째 값은 expected에 담긴다.
29학생의 함수에 입력을 넣고, 반환된 실제 결과를 actual에 담는다.
30==는 두 값이 같은지 비교한다. 기대 결과와 실제 결과가 같은지 확인한다.
31같으면 표시할 검사 결과를 ‘통과’로 정한다.
32passed += 1은 passed의 기존 값에 1을 더해 다시 저장한다.
33~34else는 앞 조건이 맞지 않을 때의 처리다. 검사 결과를 ‘실패’로 정한다.
35~38검사 결과를 한 줄로 출력한다. f가 붙은 문자열은 중괄호 안의 값을 글자 사이에 넣는다. 두 문자열이 이어져 하나의 출력 문장이 된다. !r은 문자열 입력의 따옴표도 보이게 한다.
39반복이 끝난 뒤 전체 개수와 통과 개수를 출력한다. len은 목록에 든 항목의 개수를 구한다.
40통과 개수가 전체 예시 개수와 같은지 확인한다.
41모두 통과하면 예시 검사가 끝났으며 코드 읽기도 필요하다는 문장을 출력한다.
42~43모두 통과하지 못했다면 수정이 필요하다는 문장을 출력한다.
44~45함수 정의와 마지막 실행 문장을 빈 줄로 구분한다.
46main을 호출한다. 호출은 정의해 둔 함수의 일을 실제로 실행하도록 요청하는 것이다.

60을 넣었을 때만 흐름을 따로 따라가 보자. 60은 정수이므로 첫 조건에서 반환하지 않는다. 음수도 아니므로 다음 조건을 지난다. 60은 60보다 크지 않으므로 ‘충분’을 반환하지 않는다. 그다음 30 이상이라는 조건은 참이어서 ‘진행’을 반환한다. 검사기는 기대한 ‘충분’과 실제 ‘진행’을 비교해 실패를 표시한다.

이 설명은 코드에 적힌 행동을 읽은 결과다. 작성자의 의도나 AI가 붙인 설명이 ‘60분 이상’이라고 말하더라도 실제 조건은 따로 확인해야 한다. 코드에서 >는 ‘보다 크다’이고, >=는 ‘이상이다’. 명세의 단어와 코드의 기호를 연결해 읽는 일이 필요하다.

실행 결과

터미널에서 main.py를 저장한 폴더로 이동한 뒤 다음 명령을 입력한다. 터미널은 글자로 명령을 입력하고 결과를 보는 창이다.

python3 main.py

출력은 다음과 같다. 입력 문자열 ‘30’에는 따옴표가 표시되어 정수 30과 구별된다. 시간이나 난수에 의존하지 않으므로 같은 코드를 실행하면 같은 결과가 나온다.

학습 시간 명세 검사
통과 | 입력=0 | 기대=시작 | 실제=시작
통과 | 입력=29 | 기대=시작 | 실제=시작
통과 | 입력=30 | 기대=진행 | 실제=진행
통과 | 입력=59 | 기대=진행 | 실제=진행
실패 | 입력=60 | 기대=충분 | 실제=진행
통과 | 입력=90 | 기대=충분 | 실제=충분
통과 | 입력=-1 | 기대=입력 오류 | 실제=입력 오류
통과 | 입력='30' | 기대=입력 오류 | 실제=입력 오류
결과: 8개 중 7개 통과
수정 필요: 명세와 다른 결과가 있다.

일곱 개가 통과했다는 사실만으로 완료라고 판단하지 않는다. 완료 기준은 여덟 개 모두의 통과를 요구한다. 이 결과는 수정할 위치를 좁혀 준다. 60의 기대 출력이 명세와 맞는지 먼저 확인하고, 맞다면 함수의 비교 조건을 고친다. 출력 표를 보기 좋게 만드는 일보다 약속과 다른 결과를 바로잡는 일이 먼저다.

실무에서 자주 틀리는 것

‘이상’을 ‘보다 크다’로 옮긴다

명세의 ‘60 이상’에는 60도 포함된다. 다음 함수는 60을 입력하면 ‘진행’을 반환한다. 작은 비교 기호 차이가 결과의 범위를 바꾼다. 아래 조각들은 문법을 확인할 수 있는 함수 정의이며, 잘못된 쪽과 고친 쪽을 각각 읽는다.

def upper_status(minutes):
    if minutes > 60:
        return "충분"
    return "진행"

고친 함수는 60을 포함한다. 완성 코드도 6번째 줄의 >를 >=로 바꾸면 해당 예시가 통과한다.

def upper_status(minutes):
    if minutes >= 60:
        return "충분"
    return "진행"

허용하지 않는 입력을 편의상 바꾼다

입력을 정수로 바꾸면 편해 보일 수 있다. 그러나 이번 약속에서는 문자열 숫자를 받아들이지 않는다. 다음 함수는 문자열 ‘30’을 정수로 바꿔 ‘진행’을 반환하므로 명세와 다르다. 바꿀 수 없는 문자열을 넣으면 실행 중 오류도 발생한다.

def input_status(minutes):
    minutes = int(minutes)
    if minutes >= 30:
        return "진행"
    return "시작"

고친 함수는 먼저 입력의 종류와 음수 여부를 확인한다. 이 조각은 입력 확인과 30분 구분만 보여 준다. 전체 시간 분류는 완성 코드의 나머지 규칙과 함께 읽어야 한다.

def input_status(minutes):
    if type(minutes) is not int:
        return "입력 오류"
    if minutes < 0:
        return "입력 오류"
    if minutes >= 30:
        return "진행"
    return "시작"

입력을 바꾸는 기능이 필요해졌다면 명세부터 수정한다. 편리한 처리를 추가했더라도 기존 약속과 다르면 사용자는 결과를 예측하기 어렵다.

출력한 것을 반환한 것으로 생각한다

다음 함수는 화면에 ‘충분’을 표시하지만, 호출한 곳에 그 문자열을 돌려주지 않는다. 명시적으로 반환하지 않은 함수의 결과는 None이다. None은 여기서 반환된 결과값이 없음을 나타내는 값이다. 검사기가 actual에 받는 것은 화면의 글자가 아니라 함수의 반환값이다.

def enough_status():
    print("충분")

고친 함수는 문자열을 반환한다. 화면에 표시할지는 결과를 받은 쪽에서 결정한다.

def enough_status():
    return "충분"

이 구분이 명세에 없으면 만드는 사람은 화면 표시만 확인하고 일을 끝낼 수 있다. 출력이라는 항목에는 표시할 글자뿐 아니라 결과를 전달하는 방식도 적어야 한다.

실제 결과에 맞춰 기대 출력을 고친다

60에서 실패가 나오자 기대 출력을 ‘진행’으로 바꾸면 검사 결과는 통과로 바뀐다. 그러나 ‘60 이상은 충분’이라는 약속은 여전히 지키지 못한다. 다음 목록은 그 약속과 어긋난 기대 출력을 담고 있다.

examples = [
    (60, "진행"),
]

명세가 바뀌지 않았다면 기대 출력도 유지해야 한다. 다음 목록이 기존 명세와 맞는다.

examples = [
    (60, "충분"),
]

기대 출력이 틀릴 수도 있으므로 무조건 고정하는 것은 아니다. 다만 수정의 근거는 현재 코드가 내놓은 값이 아니라, 확인하거나 새로 합의한 규칙이어야 한다. 검사 통과와 요구 충족을 혼동하지 않는 태도가 필요하다.

한눈에 보기

명세를 쓰고 결과를 확인할 때의 핵심 질문
항목질문이 장의 답
입력어떤 값을 어떤 단위로 받는가?분 단위의 0 이상 정수 하나다. 참·거짓 값은 제외한다.
출력무엇을 어떤 방식으로 돌려주는가?정해진 상태 문자열 하나를 반환한다.
성공 예시허용하는 입력에는 무엇이 나오는가?30 → 진행, 60 → 충분이다.
실패 예시허용하지 않는 입력은 어떻게 처리하는가?-1과 문자열 ‘30’에는 입력 오류를 반환한다.
완료 기준무엇을 확인하면 작업을 끝내는가?예시 모두의 통과와 코드 읽기로 규칙의 일치를 확인한다.
역할 구분어느 부분이 약속이고 어느 부분이 구현인가?입력과 결과의 관계는 약속이며, 조건문의 구성은 구현이다.

명세는 작성한 뒤 보관만 하는 문서가 아니다. 요청할 때 전달하고, 결과를 받으면 대조하고, 규칙을 바꾸면 함께 고친다. 사람이 만든 코드든 AI가 제안한 코드든 같은 기준으로 확인할 수 있도록 만드는 것이 명세의 역할이다.

연습 문제

  1. ‘공부한 시간을 보고 적당한 상태를 알려 준다’라는 문장에서 아직 정하지 않은 사항을 세 가지 적어 보라. 이번 장의 명세를 보지 않은 사람이 질문할 내용을 떠올리면 된다.
  2. 완성 코드에서 명세와 다른 비교 조건을 고쳐 보라. 다시 실행했을 때 마지막 두 줄이 어떻게 바뀌는지 먼저 적고 실행 결과와 비교하라.
  3. 다음 두 입력의 기대 출력을 명세만 보고 적어 보라. 하나는 정수 45이고, 다른 하나는 소수점이 있는 수 30.0이다. 두 결과가 다른 이유도 설명하라.
  4. 요청한 사람이 ‘0분은 시작 대신 미기록으로 표시하자’고 결정했다. 명세, 예시 표, 함수에서 각각 무엇을 바꿔야 하는지 적어 보라. 1~29분의 상태는 시작으로 유지한다.

정답과 해설

  1. 입력의 단위, 상태를 나누는 시간 기준, 잘못된 입력의 처리 등을 적을 수 있다. 반환할 글자의 정확한 형태나 화면 표시와 반환 중 어느 방식인지도 빠져 있다. ‘적당한’이라는 말은 공동으로 확인할 결과를 정하지 못하므로 구체적인 조건으로 바꿔야 한다.

  2. 6번째 줄의 minutes > 60을 minutes >= 60으로 고친다. 그러면 60에서도 ‘충분’을 반환해 여덟 예시가 모두 통과한다. 마지막 두 줄은 아래와 같다. 예시 검사가 끝나도 코드와 명세의 대응을 읽는 확인은 남아 있다.

    결과: 8개 중 8개 통과
    예시 검사 완료: 코드 읽기로 명세도 확인한다.
  3. 45의 기대 출력은 ‘진행’이다. 정수이며 30~59 구간에 속한다. 30.0의 기대 출력은 ‘입력 오류’다. Python에서 30.0은 소수 부분을 표현할 수 있는 수의 종류이고 int가 아니다. 수의 크기가 30과 같더라도 이번 명세가 허용하는 입력의 종류와 다르다. 판단의 근거는 숫자의 겉모양이 아니라 입력 규칙이다.

  4. 명세의 출력 규칙을 ‘0은 미기록, 1~29는 시작’으로 바꾼다. 예시 표와 examples의 0에 대한 기대 출력도 ‘미기록’으로 고친다. 함수에는 입력 종류와 음수를 확인한 뒤, 시간 구간을 분류하기 전에 0을 따로 처리하는 조건을 넣는다. 다음은 그 변경까지 반영한 함수다.

    def student_status(minutes):
        if type(minutes) is not int:
            return "입력 오류"
        if minutes < 0:
            return "입력 오류"
        if minutes == 0:
            return "미기록"
        if minutes >= 60:
            return "충분"
        if minutes >= 30:
            return "진행"
        return "시작"
    

    함수만 바꾸면 기존 예시의 0이 실패하고, 예시만 바꾸면 기존 함수의 결과가 실패한다. 변경한 규칙을 문서와 예시와 코드에 함께 반영한 뒤 실행해서 확인해야 한다.

댓글 0

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

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