이는 대학 컴퓨터 과학 첫 번째 과정인 프로그래밍 기초:
Python 3을 사용하여 교육됩니다. 사전 프로그래밍 지식은 전제로 하지 않습니다. 이 교재의 모든 프로그램은 완전하여 바로 실행 가능합니다. 직접 입력하고 실행한 후 한 줄만 수정하고 다시 실행하기 전에 결과를 예측하십시오.
예측-실행 루프가 곧 이 과정이 요구하는 핵심 기술입니다.
이 과정은 사다리처럼 구성됩니다:
이 과목은 계단과 같습니다:
Units 1.1 to 1.3 build the mechanics: values and types, selection, iteration.
Unit 1.4 패키지는 함수로 작동합니다.
Unit 1.5과 1.6은 표준 컬렉션 및 파일을 오류 처리와 함께 추가합니다.
Unit 1.7는 문제 분해, 테스트, 디버깅을 다루는 핵심 분야입니다.
각 단원을 읽는 방법: 각 단원이 왜 중요한지 설명하는 짧은 이야기와 그림 또는 경험칙이
구문 규칙보다 먼저 제시됩니다. 그 후 정확한 규칙이 나오고, 난이도가 증가하는 풀이 예제가,
코드를 실행하여 확인할 수 있는 추적 데이터가 각각 있으며, 실제로 사람들이 저지른 실수는
를 설명하고, 세 줄 요약으로 마무리합니다. 모든 예제는 코드를 다시 실행하여
별도의 방법으로 검증할 수 있습니다. 코드를 실행하지 않고 읽는 것은 악보만 보고 소리를 듣지 않는 것과
마찬가지입니다.
쇼핑 앱에 "4.20"가 표시됩니다. 이것이 텍스트인지 숫자입니까? 텍스트라면 두 가격
숫자라면 8.7이 됩니다. 어떤 결과가 나오는지는 유형에 의해 결정되며, 전체 unit 4.203.50은
by the type, and the whole of unit 1.1 is learning to see types before you
무엇을 실행하기 전에.
**값(value)**은 타입을 가지며, 타입은 그 값을 어떻게 사용할 수 있는지를 결정합니다: int(정수,
float(부동소수점), str(텍스트), bool(True/False)). type(...)
function names any value's type — ask it freely while learning.
age = 21 # int
price = 4.20 # float
name = "Ada" # str
open_ = True # bool
print(type(age)) # <class 'int'>
변수는 값에 연결된 이름이며, 할당 age = 22은 해당 이름을 재바인딩합니다. 이는 기존 값을 수정하는 것이 아닙니다. 학위 과정 내내 유지해야 할 핵심 개념:
이름은 상자에 붙은 태그이며, 표현식은 상자를 읽고 새로운 상자를 만듭니다
이름(name)은 상자의 태그이며, 식(expression)은 상자를 읽고 새로운 상자를 만듭니다
식을 읽는 것은 "상자를 열고 계산한 뒤, 새로운 상자를 닫는 것": count * 3.50count = 6는 int 상자 6와 float 상자 3.50를 열며, Python의 혼합 규칙은
int를 Promotion하여 결과로 float21.0를 생성하고, int 21가 되지는 않습니다.
변환은 명시적입니다: int("21")는 21이지만, "4" + 2는 TypeError입니다. — Python은
"42"를 의도했는지 6를 의도했는지 추측하지 않습니다. 출력에는 print(...)를 사용하고, 입력에는
input(...)를 사용하십시오. 이는 항상 문자열을 반환하므로 숫자를 변환해야 합니다:
작업 예제 1 (카페 tills).count = int(input("Coffees? "))한 뒤,
print("Total:", count * 3.50)합니다. 6개 커피일 때 이 코드는 Total: 21.0를 출력합니다 — 이는 .0입니다.
int * float이 부동소수점으로 승격되기 때문에 부동소수점을 표시한다. 표현에서 어떤 유형이 outputs되는지
of an expression is unit 1.1's whole habit. Trace it the state way: input returns
문자열 "6"을 반환함; int(...)은 int 6을 담는 새로운 상자를 열기 때문에 곱셈
float상자 21.0를 생성합니다; print가 이를 보여줍니다.
작업 예제 2 (고전적인 사고, 수리됨). 배너 프린터가 카운트를 메시지 onto 합칩니다:
메시지로 이어짐:
Run it and Python raises TypeError: can only concatenate str (not "int") to str —
규칙은 추측을 거부합니다. 수리는 변환을 명시적으로 명명합니다:
print("Sold " + str(count) + " today"), 또는 더 깔끔한 print("Sold", count, "today"), which inserts the spaces for you. The lesson to carry: +는 숫자에 대해 덧셈이며, 문장열에 대해 결합이며, 무언의 혼합은 절대 없습니다.
for numbers, joining for strings, and never a silent mix.
흔히 하는 실수.input()가 항상 문자열을 반환한다는 것을 잊어,
input() * 2는 숫자를 두 배로 늘리는 것이 아니라 텍스트를 반복합니다. 4.20 + 6와
int 답변을 기대함 — 식에 부동소수점 수 하나가 포함되어 있으면 전체가 부동소수점 수가 됨. 신뢰함
0.1 + 0.2 == 0.3 (이것은 False임; 이진 반올림 — 단위 1.2이 수정 사항을 보여줌).
요약: 네 가지 기본 유형이 모든 연산을 결정하며, 이름은 태그이며 할당은 재결합함;
표현식들은 부동소수점과 함께promotion으로 타입을 혼합하고, 변환은 명시적입니다.
input()는 하나만 필요함.
A theme park turnstile must charge children, adults and seniors differently — one
프로그램, 여러 경로. 선택은 프로그램이 선택하는 방식입니다: if, elif,
else는 조건이 성립할 때만 블록을 실행함.
연령 70, 학생: 첫 번째 참인 분기가 가격을 결정함
조건은 부울 값 표현식이며, 비교 연산(==, !=, <, >=)과
논리 연산(and, or, not)으로 구성됩니다. 모든 결정을 가독성 있게 만드는 두 가지 규칙:
분기들은 위쪽에서 아래쪽으로 테스트되며, 첫 번째 참인 분기가 승리하고 나머지는
건너뜁니다.
Indentation is not decoration — it IS the block. One extra space changes which
줄이 분기에 속하는지가 바뀝니다.
temp = 37.5
if temp >= 38:
print("Fever")
elif temp >= 36 and temp < 38:
print("Normal")
else:
print("Low")
작업 예시 1 (영화관 가격). 12 또는 65+일 경우 5, 학생은 7,
나머지 모든 사람은 10:
age = int(input("Age? "))
student = input("Student? y/n ") == "y"
if age < 12 or age >= 65:
price = 5
elif student:
price = 7
else:
price = 10
print("Pay", price)
순서가 중요합니다: 첫 번째 참인 분기가 선택되므로, 나이나 학생 여부 체크의 순서가
student check — a 70-year-old student should pay 5, and this ordering says so. Run
70와 y을 사용하여 플로우차트의 가장 왼쪽 경로를 따라가는 과정을 확인하십시오.
해설 예제 2 (등급 구간, 확장됨). 동일한 골격이 이제 사슬로 연결되었습니다.
각 elif는 그 위에 있는 테스트가 이미 패했기 때문에 비교를 한 번만 수행하면 됩니다:
실패했습니다:
score = int(input("Score? "))
if score >= 80:
band = "A"
elif score >= 70:
band = "B"
elif score >= 60:
band = "C"
else:
band = "F"
print("Band:", band)
79에서 첫 번째 테스트가 패하고 두 번째 테스트가 성공합니다: Band: B. 확대하여
참고: elif score >= 70에 and score < 80도 필요하지 않습니다—해당 행에 도달하는 것만으로도
점수가 80보다 낮음을 증명합니다. 이러한 하향 흐름(fall-through)을 신뢰하는 것은
조건 체인을 짧게 만드는 이유이며, 모든 조건을 다시 테스트하는 것은 초보자의 더 길고 틀린 버전입니다.
version.
Floating-point comparisons need care: 0.1 + 0.2 == 0.3 is False (binary
rounding), so compare against a tolerance: abs(x - y) < 1e-9.
Common mistakes. Writing = where == is meant — the classic bug, and Python
refuses it in conditions (SyntaxError) precisely because it is so common. Testing
the special case after the general one, so the general one always wins. Using
elif의 영수증을 손으로 처리하는 것은 직업이지만, 루프를 사용하면 프로그램이 됩니다. 반복
잡았다.
요약: 조건이 경로를 선택하고, 첫 번째 참인 결과가 우선하며, 줄바꿈은 블록을 정의한다; 순서
테스트는 구체적에서 일반적으로 진행됩니다; 부동소수점은 허용 오차로 비교하며, ==으로 절대 비교하지 않습니다.
5,000 영수증을 수작업으로 처리하는 것은 경력입니다. 루프를 사용하면 프로그램이 됩니다. 반복
작업을 반복합니다: for 루프는 알려진 순서를 따라가며, while 루프는 조건이 성립하는 동안 실행됩니다. 설계 시 항상 고려해야 할 질문은 ‘반복 횟수가 미리 알려졌는가, 아니면 조건에 의해 결정되는가’입니다.
조건이 성립합니다. 디자인 질문은 항상 다음과 같습니다: 여행 횟수를 알 수 있는지, 아니면
조건에 의해 결정됩니다. 변수의 시작값, 조건문, 그리고 루프 탈출로 이어지는 변수 변경 로직이 코드 상에서 모두 확인되어야 합니다. 그렇지 않으면 무한 루프가 됩니다.
total = 0
for i in range(1, 101): # 1..100 — range stops BEFORE its end
total += i
print(total) # 5050
여행 횟수가 알려졌을 때는 for을 사용하고, 조건에 의해 결정될 때는 while을 사용합니다. 모든
while은 코드 상에서 세 가지 요소를 확인해야 합니다: 변수의 시작값, 조건,
루프를 탈출하도록 변수를 변화시키는 요소입니다. 이것이 없으면 무한 루프가 됩니다.
무한 루프
계승인자 루프에 대한 추적 표
n = 5
factorial = 1
while n > 1:
factorial *= n
n -= 1 # the step toward exiting
print(factorial) # 120
추적 표는 루프를 따라가는 과정의 검증 습관입니다:
변수들을 한 라운드씩 따져보십시오. 먼저 종이에 적으세요. 계승인자 루프의 경우:
(n=5, f=1) → (4, 5) → (3, 20) → (2, 60) → (1, 120). 수작업으로표를 만든 후,
루프 내부에 print(n, factorial)을 추가하여 Python이 채점을 하도록 하십시오.
해설 예제 2 (break을 사용한 검색, 확장됨). 다음 중 첫 번째 배수를 찾으세요,
7의 배수 중 가장 먼저 나타나는 100보다 큰 수를 찾으십시오 — 반복 횟수는 알 수 없으며 조건에 의해 결정됩니다:
n = 100
while True:
if n % 7 == 0:
break # found it — leave the loop now
n += 1
print(n) # 105
추적이 짧습니다: 100, 101, …, 104은 모두 테스트를 통과하지 못합니다; 105은 통과하고 break은
exits. break leaves the loop early; continue skips to the next round. Both are
가독성 도구입니다—무언가를 찾아 멈출 때까지 스캔해야 하는 루프는, 가장 좋은 가독성을 제공하면
최고입니다. 더 높은 확장, 돈 두 배: 연 5%일 때, break이
1000가 2000에 도달하기까지 몇 번의 iteration을 거칩니까?
balance = 1000.0
years = 0
while balance < 2000.0: # the condition itself is the goal
balance *= 1.05
years += 1
print(years, round(balance, 2)) # 15 2078.93
15년이며, 루프는 조건이 반전되는 순간 정지합니다 — 그 시점의 추적 표
2078.93에서 경계를 넘어서 균형을 보여주며, 2000에 정확히 닿지 않습니다.
자주 하는 실수. 목록(단원 1.5) 또는 루프 변수를 편집하는 것
을 for-루핑하여 처리합니다. 100이 의도되었음에도 range(1, 100)을 사용함—마지막 인덱스는 제외됩니다.
A while whose exit step is missing or commented out: the condition never flips,
프로그램이 멈춥니다.
요약: 알려진 반복 횟수에는 for, 조건에는 while; 모든 루프는 시작점,
조건 및 단계를 보여줍니다; 종이에 추적한 후 출력 등급을 확인하고, break과 continue로
깔끔한 조기 탈출을 구현합니다.
1.4
함수: 작업에 이름 붙이기
The same price calculation appears in five places; the tax rule changes; four of the
값이 업데이트됩니다. 함수는 작업을 이름 뒤에 패키징하여 한 번만 작성하고
어디서나 신뢰할 수 있게 합니다: 매개변수가 들어와 반환 값이 나옵니다. def
을 정의하고, 호출하면 실행됩니다.
def total_price(count, unit=3.50):
"""Return the total for count items at unit price."""
return count * unit
print(total_price(6)) # 21.0
print(total_price(6, unit=4.0)) # 24.0
정의에서는 매개변수(count, unit)를 제공하며, 호출에서는
인자(6)를 전달합니다. 기본 값(unit=3.50)은 인자를 선택적으로 만듭니다.
return이 값을 반환하여 호출을 종료합니다; 출력은 반환이 아닙니다—
return이 없는 함수는 None을 반환하며, 이를 연산하려는 호출자는
TypeError을 받습니다.
중첩된 호출: 호출자는 값이 다시 돌아올 때까지 대기함
각 호출은 자체 로컬 scopes를 가지며: 내부의 이름은 외부에서 보이지 않고, 호출이 종료되면 사라집니다. 중첩 호출의 추적 순서는 다음과 같습니다:
호출자가 일시 정지 → 피호출자가 행됨 → 반환값이 호출자로 전달됩니다.
호출자가 일시 정지 → 호출 대상이 실행됨 → 반환 값이 호출자에게 전달됨.
작업 예시 1 (비용 포함 주문).
def add_fee(amount):
"""Return amount plus a fixed 3 pence fee."""
return amount + 3
def order_total(unit_pence, quantity):
"""Return the order total: subtotal plus the fixed fee."""
return add_fee(unit_pence * quantity)
print(order_total(250, 4)) # 1003
도큐스트링 내 계약 규정이 중요합니다: 전액 원화 단위로 입력하고, 전액 원화 단위로 출력해야 합니다. 추적
order_total(250, 4): 합계 1000, add_fee가 1003을 반환 — 그리고 프레임
다이어그램은 일시 정지와 반납 과정을 보여줍니다.
작업 예시 2 (소형 함수 내부의 실제 알고리즘, 확장됨).
2부터 $\sqrt{n}$까지의 값 중 n를 나누는 것이 없으면 is_prime(n)이 True을 반환합니다:
def is_prime(n):
if n < 2:
return False
d = 2
while d * d <= n: # only test up to the square root
if n % d == 0:
return False # found a divisor — answer known, stop
d += 1
return True
print([p for p in range(2, 30) if is_prime(p)])
# [2, 3, 5, 7, 11, 13, 17, 19, 23, 29]
여기서 두 가지 함수 관행이 만납니다: 조기에 return을 반환하면 답이 알려지는 즉시 호출이 종료됩니다
알려져 있어(플래그 변수 불필요), 루프 상한 d * d <= n은 다음에서 유래합니다.
도출 과정 — $a \le b$과 함께 $n = a \times b$이 있으면 $a \le \sqrt{n}$이므로, $\sqrt{n}$ 이상을 테스트하면
이미 확인한 약수를 다시 확인하게 됩니다. 실행해 보세요: 목록은 정확히 30 미만의 소수입니다.
자주 하는 실수. 결과를 반환하는 대신 함수 내부에서 출력하여 결과가
return을 내보냅니다. 외부에서 함수의 로컬 변수에 접근하려 합니다; 호출이 끝나면 이 변수들은 사라집니다.
None로 남습니다. 외부에서 함수의 로컬 변수에 접근할 수 없으며, 호출이
종료되면 사라집니다.
요약: def을 한 번만 호출하고 어디든 사용할 수 있습니다; 매개변수는 들어오고, return을 통해 하나의 값이 나갑니다;
조기 반환은 호출을 종료하며, 로컬 변수는 호출과 함께 생성되고 소멸하며, 도크스트링은 계약을 명시합니다.
계약.
1.5
컬렉션: 리스트, 딕셔너리, 튜플, 세트
카페의 영수증은 오늘 주문이 순서대로 정리되어 있어야 하고, 메뉴가 이름으로 나열되어 있으며, 위치가
must not drift, and today's unique drinks. Four jobs, four containers — choosing
among them is the design decision of unit 1.5.
리스트는 순서가 있고 변경 가능한 시퀀스입니다: marks = [72, 85, 61], 인덱스는
0부터 시작하여, marks[1:3]로 슬라이스합니다. 딕셔너리는 키를 값에 매핑합니다:
price = {"flat white": 3.5, "tea": 2.8}, 키를 통해 읽거나 쓸 수 있습니다. 튜플
고정된 시퀀스 (53.1, -1.5)입니다 — 변경되어서는 안 되는 좌표에 적합합니다. A
set은 순서가 없는 고유한 항목을 저장합니다: {"a", "b"}.
marks = [72, 85, 61]
marks.append(90) # [72, 85, 61, 90]
best = max(marks) # 90
average = sum(marks) / len(marks) # 77.0
복수 명명: 두 명의 이름, 하나의 리스트
대입 연산은 복사하지 않습니다: a = [1]; b = a; b.append(2)은 a을 [1, 2]로 만듭니다 — 즉 list를 공유하기 때문입니다.
is shared, the names are not. This aliasing is unit 1.1's boxes again, now
출력된 개수 표가 그렇다고 말하고 있습니다.
looping over it skips elements — loop over a copy (for m in marks[:]) or build a
new list.
The dictionary's .get(key, 0) supplies 0 for a first sighting — the idiom that
makes counting a one-liner. Verify by hand: "tea" appears three times in orders,
and the printed tally says so.
해설 예제 2 (오늘의 베스트셀러, 확장됨). 세어낸 결과를 바탕으로,
최상위 항목과 그 개수를 보고합니다:
best = max(tally, key=tally.get)
print(best, tally[best]) # tea 3
The key=tally.get argument asks max to compare items by their counts rather than
정렬 — key= 기법은 어떤 측정 기준을 사용하든 모든 집합을 정렬할 수 있습니다. 이
collection 선택은 설계 결정입니다: 순서 + 중복 → list; 이름으로 검색 → dictionary; 고정된 그룹화 → tuple; 고유성 → set.
orders을 두 번 스캔하는 것이 아니라 위 방법을 사용합니다.
수집체의 선택은 설계 결정입니다: 순서 + 중복 → list; 이름 기반 조회 → dictionary; 고정된 그룹화 → tuple; 고유성 → set.
이름 → 사전; 고정된 그룹 → 튜플; 고유성 → 세트.
자주 하는 실수. 아직 사전에 없는 키에 대해 tally[item] += 1를 실행하는 것
(KeyError) — .get가 해결책입니다. 세트나 사정이 순서를 기억할 것이라고 예상하는 것.
b = a가 리스트를 복사한다고 가정할 때, 이는 일부를 공유합니다.
요약: 네 가지 작업에 맞는 네 가지 컨테이너; 대입은 복사하지 않고 공유하며, .get
세 safely하게 만듭니다; key=는 순위를 매기는 것을 한 줄로 처리합니다.
카페가 문을 닫고, 전기가 끊기고 — 아침의 합계가 보존되려면 반드시
program wrote it to disk. A file outlives the program. with open(path) as f: opens, closes on exit, and reads text; open(path, "w") writes
(and empties first — the classic accident).
with open("log.txt", "w") as f:
f.write("opened\n")
with open("log.txt") as f:
for line in f:
print(line.rstrip()) # rstrip removes the trailing newline
The with block is the safety rail: whatever happens inside it, the file closes.
Reading gives lines with their newline, so .rstrip() belongs in every loop.
에러는 Python이 예외로 raise하는 값입니다: FileNotFoundError,
ValueError, KeyError, TypeError. try/except는 명시된 타입을 잡습니다 — 실제 버그를 숨기는
단순한 except:가 아닙니다.
해설 예시 1 (부재하는 설정 파일). 설정에서 텍스트 크기를 읽을 때
파일이 없거나 정수를 포함하지 않을 수 있습니다:
try:
with open("settings.txt") as f:
size = int(f.read().strip())
except FileNotFoundError:
size = 12 # no file yet: a sensible default
except ValueError:
size = 12 # the file holds "big", not a number
print("Text size", size)
세 번 실행하세요 — 파일에 16가 있을 때, 파일이 없을 때, 그리고 파일에 big이 있을 때
파일 — 매번 사용 가능한 크기를 출력하며, 세 가지 다른 경로에서 실행됩니다. 이것이 견고한 코드의 형태입니다: 하나의 합리적인
결과, 그 결과로 이어지는 여러 경로. 예상되는 에러를 처리하고, 대응할 수 있는
지점에서; 나머지는 소란스럽게 실패하게 두십시오.
풀이 예제 2 (견고한 입력 루프, 확장됨). 사용자가 정수 입력을 할 때까지 반복합니다:
whole number:
Feed it x then 4: the first trip raises, is caught, prints the message, and the
while True이 다시 묻습니다; 두 번째 시도의 int(...)이 성공하여 return이 루프를
나오며 동시에 함수도 종료합니다. 여기서 세 가지 요소가 결합됩니다 — 루프(1.3),
함수 계약(1.4) 및 예외(1.6) — 이것이 확장의 핵심입니다.
escalation.
자주 하는 실수. 잘못된 경로에서 open(path, "w") 사용: 파일이 생성되거나 비워질 때까지
emptied before you notice. Catching Exception broadly and continuing with broken
state. Forgetting .rstrip(), so output shows double-spaced lines.
요약: 읽기와 쓰기 모두에 with open(...); "w"은 먼저 비웁니다;
예외는 값입니다 — 기대하는 명명된 타입을.catch하고, 합리적으로 후속 조치를 취하며, 나머지 코드는 실패하게 두십시오.
나머지 코드가 실패하게 두십시오.
두 팀이 동일한 앱을 작성합니다. 한 팀은 일 주일 만에 출시하지만, 다른 팀은 한 달간 고생해야 합니다. 그 차이점은 타이핑 속도가 아닙니다. 빠른 팀의 코드는 각 부분이 정확함을 증명할 수 있는 작은 조각으로 구성되어 있지만, 느린 팀은 한 번에 전체 앱을 디버깅해야 하기 때문입니다.
차이는 타이핑 속도가 아니라, 빠른 팀의 코드가 각각 정확함을 증명할 수 있는 작은 조각들로 구성되어 있다는 점입니다. 반면 느린 팀은 한 번에 전체 앱을 디버깅해야 합니다.
차이는 타이핑 속도가 아닙니다. 빠른 팀의 코드는 각 부분이 정확함을 증명할 수 있는 작은 조각으로 구성되어 있지만, 느린 팀은 한 번에 전체 앱을 디버깅해야 하기 때문입니다.
유닛 테스트 1.7가 바로 이러한 태도입니다.
Decomposition splits a problem into functions of one job each — each small
함수를 나눕니다 — 개별적으로 테스트하기 충분한 크기여야 합니다. 테스트는 함수의 실제 출력물을 예상되는 결과와 비교하며,
예상되는 결과와 비교하며, 경계 조건(0, 1, 빈 값, 음수, 매우 큰 값 등)에서 검증합니다.
assert가 발화하지 않는다는 것은 테스트가 통과했다는 뜻입니다. 가능하면 본체 작성 전에 테스트를 먼저 작성하십시오 — 이는executable로 전환된 약정이며, 위 루프(빨강 → 초록 → 리팩토링)는 이 습관이 지속적으로 작동하는 것입니다.
본체 작성 전에 테스트를 먼저 작성하십시오 — 이는executable로 전환된 약정이며, 위 루프(빨강 → 초록 → 리팩토링)는 이 습관이 지속적으로 작동하는 것입니다.
(빨강 → 초록 → 리팩토링)는 이 습관이 지속적으로 작동하는 것입니다.
Worked example 1 (grow the tests with the function). Extend order_total with a
£10 기준 10% 할인 — 테스트를 먼저 작성한 후, 새로운 경계에서:
Check the middle test by hand: subtotal 1003, ten percent off gives 902.7, and
int(...)은 아래로 반올림되어 902이 됩니다. 첫 assert도 확인하세요: 200 펜스 및 네
컵일 때 총합은 803으로 유지됩니다 — 경계 테스트는 할인이 작동하고
조기 발사가 일어나지 않음
연습 예제 2 (프린트로 버그를 이분하여 추적, 단계별 심화). 1.5의 카운트 결과
{'tea': 3}을 출력하지만 베스트셀러 줄은 잘못된 음료를 출력합니다. 메서드:
(1) 가장 작은 실패 입력으로 재현; (2) 경계에서 값 출력
신뢰 가능한 부분과 의심스러운 부분 사이 — 여기서는 max 직전의 카운트;
(3) 하나 수정; (4) 테스트 재실행. 프린트를 코드 중간으로 이동할 때
각 실행 시 검색 범위가 절반씩 줄어듭니다: 16줄짜리 프로그램에서 4개의 프린트로 버그를 찾지만,
얼마나 숨겨져 있더라도 마찬가지입니다.
디버은 코드가 실제로 하는 것과 믿고 있는 것 사이의 차이를 찾는 것입니다.
신뢰/의심 경계에서의 프린트는 그 도구가 됩니다.
자주 하는 실수. 행운의 경로만 테스트함: 경계(0, 1, 비어있음,
부정 등)가 프로그램이 고장 나는 곳입니다. 첫 테스트 전에 일주일 분량의 코드를,
그 후 디버깅을 위해 산을 쌓습니다. 동시에 두 가지를 수정하면 어느 쪽도 정확하지 않습니다.
아무것도 없습니다.
요약: 모든 부분이 개별적으로 테스트될 때까지 분해하십시오; 함수가 생성되는 즉시 경계를 assert하십시오; 가능하면 먼저 테스트를 작성하십시오; 신뢰 경계에 프린트를 사용하여 이분법으로 디버깅하십시오 — 이것이 학위 과정 전반에 걸친 습관입니다.
함수가 존재함을 모두 증명합니다; 가능하면 먼저 테스트를 작성하십시오; 출력으로 이분법 디버깅을 수행하십시오.
신뢰 경계에서 — 전 학위 과정이 구축하는 습관입니다.