정보

프로그래밍 응용

실행된다고 끝이 아닙니다.
답이 맞는지 확인하고, 얼마나 일하는지 측정해 보고,
남이 쓸 수 있게 넘겨주는 데까지가 한 벌입니다.

학습 목표

  1. 문법 오류 · 실행 오류 · 논리 오류를 구별하고, 잘못을 찾아 가는 방법을 설명할 수 있다.
  2. 실행 시간과 연산 횟수라는 두 기준으로 프로그램이 얼마나 일하는지 측정해 보고, 둘이 어떻게 다른지 말할 수 있다.
  3. 문제를 여러 조각으로 나누어 함께 만들고, 만든 것을 남이 쓸 수 있는 형태로 넘겨줄 수 있다.

한 번에 완성되는 프로그램은 드물다

되돌아가는 것이 실패가 아니라 과정입니다.

프로그램을 만드는 일은 문제를 이해하고 → 절차를 짜고 → 코드로 옮겨 실행하고 → 결과를 평가하는 네 단계로 이루어집니다. 그런데 이 네 단계는 한 방향으로만 흐르지 않습니다.

실행해 봐야 절차의 빈틈이 보이고, 절차를 짜다가 문제를 다시 살펴보게 됩니다. 실행해 보고 고치고 다시 실행해 보는 일이 프로그래밍의 대부분입니다. 처음부터 완벽한 코드를 쓰려고 애쓰기보다, 빨리 실행해 보고 빨리 고치는 편이 낫습니다.

실행된 뒤에 확인할 것 셋

  1. 답이 맞는가 — 평범한 값뿐 아니라 드문 값에서도 맞는가
  2. 얼마나 일하는가 — 실제로 쓸 만큼의 데이터에서도 견디는가
  3. 남이 쓸 수 있는가 — 나 말고 다른 사람도 실행해 볼 수 있는가

이 세 가지를 차례로 살펴보겠습니다.

스스로 확인

무언가를 만들다가 앞 단계로 되돌아간 경험을 하나 떠올려 보세요. 프로그램이 아니어도 좋습니다. 되돌아간 덕분에 무엇이 나아졌나요?

멈추는 오류와 멈추지 않는 오류

멈추는 쪽이 오히려 다행입니다.

프로그램의 잘못을 버그(bug)라고 하고, 그 원인을 찾아 고치는 일을 디버깅(debugging)이라고 합니다. 버그는 어긋나는 방식에 따라 세 가지로 나뉩니다. 문법 오류는 실행 전 검사에서 걸려 한 줄도 실행되지 않고, 실행 오류는 그 줄에 이르렀을 때 프로그램이 멈추며, 논리 오류는 끝까지 실행되고 답만 틀립니다. 앞의 둘은 메시지를 남기고 논리 오류는 남기지 않으므로, 고치는 방법도 둘로 나뉩니다.

멈추는 오류 — 문법 오류 · 실행 오류

문법 오류는 실행하기 전에 걸리고 실행 오류는 그 줄에 이르러 멈추며, 둘 다 메시지를 남깁니다.

어디서, 왜 멈췄는지 알려 주므로 고치기가 그나마 수월합니다.

멈추지 않는 오류 — 논리 오류

프로그램은 멀쩡히 끝까지 실행되고 답만 틀립니다.

아무도 알려 주지 않으므로 사람이 결과를 의심하지 않으면 끝내 모릅니다.

멈추는 오류는 메시지를 읽는다

오류 메시지가 영어로 길게 나오면 겁이 나서 그냥 넘기기 쉽습니다. 그런데 거기 고치는 데 필요한 것이 거의 다 들어 있습니다. 몇 번째 줄인지와 어떤 종류의 잘못인지만 봐도 됩니다.

자주 만나는 오류의 종류와 그 뜻
오류 이름 무엇이 잘못된 것인가 흔한 경우
SyntaxError
문법 오류
파이썬이 문장을 읽어 내지 못했다. 실행 전에 걸려 한 줄도 실행되지 않는다 콜론 :을 빠뜨렸거나 괄호가 짝이 맞지 않는 경우
IndentationError
들여쓰기 오류
들여쓰기가 어긋났다. 문법 오류의 하나라 실행 전에 걸린다 같은 블록 안에서 들여쓴 공백 수가 다른 경우
TypeError
자료형 오류
자료형이 맞지 않는 값을 다루려 했다 문자열과 수를 더하려 한 경우
IndexError
인덱스 오류
없는 인덱스의 값을 꺼내려 했다 요소가 다섯 개인데 인덱스 5를 꺼내려 한 경우
NameError
이름 오류
그런 이름이 없다 철자를 틀렸거나, 함수 안에서 만든 변수를 밖에서 사용한 경우

표의 오류는 대부분 이미 아는 규칙이 어긋난 것입니다. 문자열과 수는 더할 수 없다는 것, 요소가 다섯 개면 마지막 인덱스가 4라는 것, 함수 안에서 만든 변수는 밖에서 쓸 수 없다는 것. 오류 이름은 새로 외울 것이 아니라, 배운 것이 어긋났다는 알림입니다.


멈추지 않는 오류는 좁혀 간다

아래 코드는 점수 다섯 개의 평균을 구하려는 것입니다. 실행해 보면 63.6이 나옵니다.

scores = [88, 74, 95, 61, 79]

total = 0
for i in range(len(scores) - 1):
    total = total + scores[i]

print(total / len(scores))

오류 메시지는 없습니다. 그런데 눈으로 봐도 이상합니다. 점수가 61에서 95 사이인데 평균이 63.6이라니, 너무 낮습니다. 「이상하다」고 느끼는 것이 디버깅의 첫걸음입니다. 결과를 한 번도 의심하지 않으면 여기서 끝나 버립니다.

이제 어디가 잘못인지 좁혀야 합니다. 가장 쉬운 방법은 중간에 값을 출력해 보는 것입니다.

scores = [88, 74, 95, 61, 79]

total = 0
for i in range(len(scores) - 1):
    total = total + scores[i]
    print(i, scores[i], total)

print(total / len(scores))

출력된 것을 보면 i가 0, 1, 2, 3까지만 나오고 4가 없습니다. 마지막 점수 79를 한 번도 더하지 않은 것입니다. 범인은 range(len(scores) - 1)의 - 1이었습니다. 그것을 지우면 79.4가 나옵니다.

scores = [88, 74, 95, 61, 79]

total = 0
for i in range(len(scores)):
    total = total + scores[i]

print(total / len(scores))

디버깅은 범위를 좁혀 가는 일이다

코드를 처음부터 끝까지 노려본다고 잘못이 보이지는 않습니다. 중간에서 값을 출력해 보고, 맞는 구간과 틀린 구간을 구분해 의심할 범위를 줄여 가는 편이 훨씬 빠릅니다. 코드가 길면 가운데쯤에서 먼저 출력해 보고, 틀린 쪽 절반만 다시 보는 식으로 범위를 반씩 줄일 수 있습니다. 정렬된 목록에서 가운데 값을 보고 절반을 버려 가며 찾는 이진 탐색과 같은 생각입니다. 잘못을 찾을 때도 같은 방법이 통합니다.

스스로 확인

평균이 63.6으로 나왔을 때 「이상하다」고 알아채려면 무엇을 미리 짐작하고 있어야 했을까요?

답이 맞았으면 이제 얼마나 일하는지 측정한다

측정하는 기준이 둘이고, 각각 알려 주는 것이 다릅니다.

같은 일을 하는 프로그램이라도 실행 시간이 짧고 메모리를 적게 쓰는 쪽이 좋습니다. 그것을 알아보는 일을 성능 평가라고 합니다. 측정하는 방법은 크게 둘입니다.

하나 — 시계로 측정한다

파이썬에는 시간을 다루는 라이브러리 time이 있습니다. 시작할 때와 끝날 때의 시각을 적어 두고 빼면 걸린 시간이 나옵니다.

import time

start = time.time()

total = 0
for i in range(1000000):
    total = total + i

end = time.time()

print(round(end - start, 3), '초')

몇 초가 나올지는 여기 적을 수 없습니다

실행하는 컴퓨터마다 다르고, 같은 컴퓨터에서도 측정할 때마다 다릅니다. 다른 프로그램이 함께 실행되고 있으면 느려지기 때문입니다. 그래서 시계로 측정한 값은 「이 코드가 이 기계에서 지금 얼마나 걸렸는가」까지만 말해 줍니다. 다른 사람의 결과와 그대로 비교하면 안 됩니다. 같은 컴퓨터에서 두 방법을 번갈아 측정해 서로 비교하는 데 쓰는 것이 알맞습니다.


둘 — 일한 횟수를 센다

시계가 기계 사정을 타는 기준이라면, 몇 번 일했는지 세는 것은 어느 기계에서 측정해도 같은 값이 나오는 기준입니다. 세는 방법은 간단합니다. 횟수를 저장할 변수를 하나 두고 비교하는 줄 바로 앞에서 1씩 늘립니다.

books = [42, 17, 63, 8, 35]
count = 0

for turn in range(4):
    for i in range(4 - turn):
        count = count + 1
        if books[i] > books[i + 1]:
            books[i], books[i + 1] = books[i + 1], books[i]

print(books)
print('비교', count, '번')

정렬이 끝나고 비교 10번이 출력됩니다. 같은 방법으로 다섯 권을 손으로 정렬해도 비교가 꼭 열 번입니다. 이 숫자는 누가 어디서 실행해도 10입니다.

두 가지 성능 기준의 차이
측정하는 방법 알려 주는 것 조심할 점
시계로 측정하기 실제로 걸린 시간 기계와 그때 사정에 따라 값이 달라집니다. 같은 컴퓨터에서 두 방법을 비교할 때 씁니다.
횟수 세기 일한 양 그 자체 어디서 측정해도 같습니다. 다만 한 번의 연산에 드는 시간까지 알려 주지는 않습니다.

둘 중 하나만 쓰는 것이 아니라 함께 봅니다. 횟수로 어느 쪽이 원리상 적게 일하는지를 보고, 시계로 실제로 견딜 만한지를 확인합니다. 그리고 측정하기 전에 답이 맞는지부터 봐야 합니다. 틀린 답을 빨리 내는 것은 아무 쓸모가 없습니다.

스스로 확인

친구가 「내 프로그램은 0.2초밖에 안 걸린다」고 합니다. 이 말만으로 내 프로그램보다 낫다고 할 수 있을까요? 무엇을 더 물어야 할까요?

나누어 만들고, 남이 쓸 수 있게 넘긴다

혼자 만들 수 있는 크기에는 한계가 있습니다.

「일주일 걸음 수를 입력받아 요약을 알려 주는 프로그램」을 넷이서 만든다고 해 봅시다. 넷이 한 화면을 같이 볼 수는 없으니 나누어 맡아야 합니다. 그런데 어떻게 나눌 것인가가 문제입니다.

문제를 작은 문제로 쪼개고, 조각마다 함수를 하나씩 맡으면 됩니다.

걸음 수 요약 프로그램을 네 조각으로 나눈 예
맡은 조각 무엇을 전달받는가 무엇을 반환하는가
값 입력받기 전달받지 않는다 일곱 날의 걸음 수가 저장된 목록
합계와 평균 걸음 수 목록 합계와 평균 두 수
가장 많이 걸은 날 걸음 수 목록 며칠째인지 나타내는 수 하나
보기 좋게 출력하기 다른 조각이 반환한 값들 반환하지 않고 화면에 출력한다

나누기 전에 정해 두어야 할 것

조각마다 무엇을 전달받고 무엇을 반환할지를 먼저 약속해야 합니다. 이것만 맞춰 두면 안을 어떻게 짜든 상관없이 합쳐집니다. 거꾸로 이 약속 없이 각자 만들면, 한 사람은 목록을 반환하는데 다른 사람은 목록을 전달받도록 만들지 않은 일이 생깁니다. 합치는 날 밤에 밝혀지는 종류의 사고입니다.

나눈다고 언제나 빨라지지는 않습니다

나누려면 약속을 정하는 시간이 들고, 합칠 때 어긋난 것을 맞추는 시간도 듭니다. 아주 작은 프로그램이라면 혼자 만드는 편이 빠를 때가 많습니다. 여럿이 함께하는 것이 제값을 하는 때는 따로 있습니다. 혼자서는 감당하기 어려울 만큼 큰 것을 만들 때, 그리고 서로 다른 눈이 필요할 때입니다. 내가 못 보던 빈틈을 옆 사람이 보는 일은 자주 일어납니다.


넘겨줄 때는 코드만으로 부족하다

완성한 프로그램을 남에게 준다고 해 봅시다. 파일만 건네면 받은 사람은 열어 보고 한참 헤맵니다. 함께 주어야 할 것이 있습니다.

1

무엇을 하는 것인가

한두 줄로 충분합니다. 이것이 없으면 코드를 다 읽고 나서야 알게 됩니다.

2

어떻게 실행하는가

무엇을 입력해야 하고 무엇이 출력되는지를 적습니다. 예를 하나 보여 주면 가장 좋습니다.

3

무엇을 못 하는가

아직 안 되는 것과 아는 빈틈을 밝힙니다. 숨기면 받은 사람이 나중에 곤란해집니다.

셋 다 「무엇을 넣으면 무엇이 나오는가」를 밝히는 일입니다. 함수를 만들 때도, 조각을 나눌 때도, 프로그램을 넘길 때도 같은 물음이 나옵니다. 프로그래밍 전체에 걸쳐 거듭 나오는 생각은 안이 어떻게 생겼는지 몰라도 쓸 수 있게 만드는 것입니다.

스스로 확인

내가 만든 프로그램을 옆 반 친구에게 준다면, 「무엇을 못 하는가」에 무엇을 적겠습니까? 한 가지를 솔직하게 적어 보세요.

핵심 정리

한 번에 되지 않는다

실행해 보고 고치고 다시

  • 되돌아가는 것은 실패가 아니라 과정이다
  • 완벽하게 쓰려 애쓰기보다 빨리 실행해 보고 빨리 고친다
  • 확인할 것 셋 — 맞는가 · 얼마나 일하는가 · 남이 쓸 수 있는가

디버깅

debugging

  • 문법 오류 · 실행 오류는 메시지가 줄 번호와 종류를 알려 준다
  • 논리 오류는 사람이 의심하지 않으면 끝내 모른다
  • 중간에 값을 출력해 보며 범위를 좁힌다

성능 평가

기준이 둘이다

  • 시계는 그 기계의 사정까지 반영한다 — 남의 결과와 비교하지 않는다
  • 횟수는 어디서 측정해도 같다
  • 측정하기 전에 답이 맞는지부터 본다

함께 만들고 넘긴다

약속이 먼저다

  • 쪼갠 조각마다 무엇을 전달받고 무엇을 반환할지 먼저 약속한다
  • 나눈다고 언제나 빨라지지는 않는다
  • 넘길 때는 하는 일 · 실행하는 법 · 못 하는 것을 함께 준다

확인 퀴즈

배운 내용을 확인해 봅시다.

Q1

멈추지 않는 오류가 멈추는 오류보다 곤란한 까닭은?

Q2

성능을 실행 시간으로 측정하는 것에 대한 설명으로 옳은 것은?

Q3

여럿이 프로그램을 나누어 만들 때 가장 먼저 정해야 할 것은?