이 페이지의 목차
함수는 왜 필요한가
코드 예제: Python · C · 예상 50분
학습 목표
- 함수의 개념과 필요성을 설명한다.
- 라이브러리 함수와 사용자 정의 함수를 구분한다.
관련 성취기준
- 프그02-05
함수의 개념과 종류, 필요성을 이해하고, 함수 정의 및 사용자 정의 함수를 호출하고 결괏값을 반환하는 프로그램을 작성할 수 있다.
생각 열기 — 세 반의 평균을 구하려면
반마다 평균을 내야 한다. 4차시에서 배운 것으로 하면 이렇게 된다.
일반 = [88, 92, 75]
합계 = 0
for 점수 in 일반:
합계 += 점수
print("1반 평균:", 합계 / len(일반))
이반 = [70, 80, 90]
합계 = 0
for 점수 in 이반:
합계 += 점수
print("2반 평균:", 합계 / len(이반))
삼반 = [100, 60, 80]
...
같은 다섯 줄이 세 번 나온다.
복사해 붙인 코드의 값
같은 코드가 세 곳에 있으면 문제가 셋 생긴다.
| 문제 | 어떻게 나타나는가 |
|---|---|
| 고칠 곳이 늘어난다 | 평균 계산을 바꾸려면 세 곳을 고쳐야 한다 |
| 한 곳만 고치게 된다 | 두 곳은 고치고 한 곳은 놓친다. 오류 없이 답만 달라진다 |
| 읽기 어려워진다 | 무엇이 같고 무엇이 다른지 눈으로 찾아야 한다 |
두 번째가 가장 위험하다. 3단원 6차시에서 is_valid 를 세 사람이 각자 만들어 조건이 달라졌던 문제가 바로 이것이다. 프로그램이 멈추지도 않고 답만 조용히 틀린다.
함수 — 하는 일에 이름을 붙인다
함수(function) — 하는 일에 이름을 붙여 묶어 놓은 코드 덩어리. 이름으로 불러 쓴다는 코드 덩어리에 이름을 붙여, 필요할 때마다 이름으로 불러 쓰는 장치다.
3단원 3차시의 「부품」이 코드에서는 함수다. 그때 부품의 겉모습을 다섯 칸으로 못 박았다 — 이름·받는 것·돌려주는 것·하는 일·실패하면. 그 다섯 칸이 함수를 만들 때 그대로 필요하다.
def 평균(점수들):
return sum(점수들) / len(점수들)
일반 = [88, 92, 75]
이반 = [70, 80, 90]
삼반 = [100, 60, 80]
print(f"1반 평균: {평균(일반):.1f}")
print(f"2반 평균: {평균(이반):.1f}")
print(f"3반 평균: {평균(삼반):.1f}")1반 평균: 85.0 2반 평균: 80.0 3반 평균: 80.0
#include <stdio.h>
double average(const int a[], int n)
{
int sum = 0;
for (int i = 0; i < n; i++) sum += a[i];
return (double)sum / n;
}
int main(void)
{
int c1[] = {88, 92, 75};
int c2[] = {70, 80, 90};
int c3[] = {100, 60, 80};
printf("1반 평균: %.1f\n", average(c1, 3));
printf("2반 평균: %.1f\n", average(c2, 3));
printf("3반 평균: %.1f\n", average(c3, 3));
return 0;
}1반 평균: 85.0 2반 평균: 80.0 3반 평균: 80.0
무엇이 좋아졌나
- 평균 계산이 한 곳에만 있다. 고칠 때 한 곳만 고친다.
평균(일반)이라는 말이 무엇을 하는지 그대로 읽힌다. 주석이 필요 없다.- 반이 열 개로 늘어도 계산 부분은 그대로다.
두 갈래 — 만들어져 있는 것과 내가 만드는 것
| 라이브러리 함수 | 사용자 정의 함수 | |
|---|---|---|
| 누가 만들었나 | 언어나 남이 미리 만들어 둠 | 내가 만든다 |
| 보기 (Python) | print, len, sum, max, int, input | 평균, is_valid |
| 보기 (C) | printf, scanf, strlen, pow | average, summarize |
| 쓰기 전에 | 어떤 것은 #include 나 import 가 필요 | 먼저 정의해 두어야 한다 |
| 안을 보는가 | 안 본다. 무엇을 받고 무엇을 주는지만 알면 된다 | 내가 만들었으니 안다 |
우리는 이미 라이브러리 함수를 계속 써 왔다. print, len, sum, int, input 이 전부 함수다. sum(점수들) 은 안이 어떻게 되어 있는지 몰라도 쓸 수 있었다.
C는 쓰기 전에 알려야 한다
C 예제에서 average 가 main 위에 있다. 이유가 있다. C는 위에서 아래로 읽으며 컴파일하므로, 부르는 곳보다 함수가 먼저 나와야 한다.
아래에 두려면 위쪽에 어떤 함수가 있을지 미리 알려 주는 한 줄을 적는다.
double average(const int a[], int n); ← 이런 함수가 있다는 예고
int main(void) { ... average(...) ... }
double average(const int a[], int n) { ... } ← 실제 내용
Python은 이런 예고가 필요 없다. 다만 부르는 시점에는 정의가 이미 실행되어 있어야 하므로, 관례로 함수를 위쪽에 모아 둔다.
활동
중복된 코드를 함수로 묶어 정리하기
약 25분1단계 — 고칠 곳을 세어 본다 (5분)
생각 열기의 복사해 붙인 코드와 본문의 함수 코드를 견줘 보자. 아래 요구가 들어왔을 때 고칠 곳이 몇 군데인가.
| 새 요구 사항 | 복사판에서 고칠 곳 | 함수판에서 고칠 곳 |
|---|---|---|
| 평균을 소수 둘째 자리까지 | ||
| 0점을 계산에서 뺀다 | ||
| 반이 다섯 개로 늘어난다 |
2단계 — 함수를 하나 더 만든다 (10분)
최고점을 구하는 함수를 만들어 붙인다.
- Python에서는
max를 쓰면 한 줄이다. 그래도 함수로 감싸는 것이 나은 경우가 있을까? (0점을 뺀 최고점을 구해야 한다면?) - C에서는 반복으로 직접 구한다. 3단원 4차시에서 다룬 첫 값 처리를 어떻게 할 것인가?
3단계 — 일부러 한 곳만 고쳐 본다 (5분)
생각 열기의 복사판 코드에서 세 곳 중 한 곳만 계산을 바꿔 본다(예: 하나만 / len(...) 을 / 3 으로).
- 오류가 나는가?
- 결과가 틀린 것을 무엇으로 알아챌 수 있는가?
- 3단원 6차시에서 「가장 찾기 어려운 어긋남」이라고 한 것과 어떻게 이어지는가?
4단계 — 이름을 지어 본다 (5분)
아래 일들에 함수 이름을 붙여 보자. 한 낱말이나 짧은 구로 짓는다.
| 하는 일 | 내가 지은 이름 |
|---|---|
| 점수 목록에서 90점 이상인 사람 수를 센다 | |
| 점수 하나가 0~100 범위인지 판정한다 | |
| 점수를 등급 문자로 바꾼다 | |
| 점수 목록을 받아 표로 찍는다 |
- 이름 짓기가 어려운 것이 있었는가? 있다면 한 가지 일이 아닐 가능성을 살펴본다.
개념 확인 문제
보기를 누르면 바로 채점된다. 틀려도 해설을 읽으면 된다.
같은 코드를 여러 곳에 복사해 붙였을 때 가장 위험한 문제는?프그02-05
3단원 3차시의 「부품」에 해당하는 것은?프그02-05
다음 중 라이브러리 함수가 아닌 것은?프그02-05
sum(점수들) 을 안이 어떻게 되어 있는지 모르고도 쓸 수 있는 까닭은?프그02-05
C에서 함수를 main 아래에 두려면 무엇이 필요한가?프그02-05
함수 이름을 짓기 어려울 때 살펴봐야 하는 것은?프그02-05
더 알아보기
심화같은 코드가 두 번이면 아직 참을 만하다
“같은 코드가 보이면 무조건 함수로 묶어라”가 늘 맞는 것은 아니다. 두 곳에 비슷한 코드가 있는데 앞으로 서로 다르게 바뀔 것 같으면, 억지로 묶은 함수가 오히려 짐이 된다. 두 쪽 요구를 다 만족시키려고 조건과 매개변수가 계속 늘어나기 때문이다.
실무에서 흔히 쓰는 기준은 세 번째로 같은 코드를 적게 될 때 묶는다는 것이다. 세 번 나타났다면 앞으로도 나타날 가능성이 크고, 세 곳의 공통점이 무엇인지도 그때쯤 분명해진다. 다만 이 차시의 평균처럼 하는 일이 명확한 계산은 두 번째부터 묶어도 좋다.
실무함수로 나누면 테스트할 수 있다
3단원 7차시에서 단위 테스트를 다뤘다. 부품 하나를 따로 떼어 정한 입력에 정한 결과가 나오는지 확인하는 것이었다. 함수가 아니면 그것을 할 수 없다. 코드가 한 덩어리로 흘러 있으면 중간의 평균 계산만 떼어 시험할 방법이 없다.
그래서 “테스트하기 쉬운 코드”와 “함수로 잘 나뉜 코드”는 거의 같은 말이다. 나중에 테스트를 붙이려면 지금 나눠 두어야 한다. 16차시에서 프로그램 전체를 함수로 나눌 때 이 이야기를 다시 다룬다.
심화함수를 부르는 값
함수를 부르는 일이 공짜는 아니다. 값을 넘기고 돌아올 자리를 기억해 두는 준비가 필요하다. 그래서 아주 짧은 계산을 수백만 번 부르면 그 준비 비용이 눈에 띌 수 있다.
그렇다고 함수를 아끼는 것은 대체로 잘못된 판단이다. 1단원 6차시에서 다룬 대로 프로그램이 느린 원인은 대개 알고리즘이지 함수 호출이 아니다. 읽기 쉬운 코드로 먼저 만들고, 정말 느리면 어디가 느린지 재어 본 뒤 고치는 것이 순서다. 재어 보지 않고 짐작으로 고치는 것을 실무에서는 가장 흔한 낭비로 본다.
이어지는 학습
이 차시를 이해하려면 다음을 먼저 알아야 한다.
- 12차시까지 작성한 반복되는 코드 — 프로그래밍 프그02-04
교과서에서 찾기
| 교과서 | 해당 위치 | 쪽수 |
|---|---|---|
| 성림출판(Python) | 프로그래밍의 기초 › 05 함수 | 156쪽 |
| 씨마스커뮤니케이션(Python) | 프로그래밍 기초 › 5 함수 | 146쪽 |
| 삼양미디어(Python) | 프로그래밍의 기초 › 04 함수 | 126쪽 |
| 삼양미디어(C) | 프로그래밍의 기초 › 04 함수 | 130쪽 |
| 씨마스(C) | C 프로그래밍의 기초 › 6 함수 | 126쪽 |
| 웅보출판사(C) | 프로그래밍의 기초 › Chapter 05 함수 | 141쪽 |