도입
프로그래밍에서 함수(function)는 가장 기본적인 추상화 도구 중 하나입니다. 긴 코드를 의미 단위로 쪼개고, 같은 계산을 반복해서 재사용하며, 입력과 출력을 기준으로 로직을 구분하게 해 줍니다.
다만 함수라는 단어는 언어마다 조금씩 다른 색을 띱니다. 어떤 언어는 함수와 프로시저를 엄격히 구분하고, 어떤 언어는 메서드가 그 역할을 함께 흡수하며, 함수형 언어는 순수성까지 강조합니다. 그래서 함수를 제대로 이해하려면 “return 값이 있느냐”만 볼 것이 아니라, 계산 단위, 호출 단위, 추상화 단위라는 세 관점으로 함께 보는 편이 좋습니다.
필요성
함수가 없으면 프로그램은 곧바로 길고 반복적인 코드 덩어리가 됩니다. 같은 계산이 여러 군데 복붙되고, 수정이 생기면 여러 위치를 동시에 바꿔야 하며, 작은 오류 하나도 전역으로 퍼질 가능성이 커집니다.
반면 함수로 나누면 프로그램은 의미 있는 계산 단위로 분해됩니다. 예를 들어 입력 검증, 합계 계산, 문자열 변환, 파일 저장 같은 작업을 각각 함수로 빼면, 호출 관계만 봐도 전체 흐름이 정리됩니다. 그래서 함수는 문법 요소가 아니라 프로그램 구조화의 최소 단위라고 보는 편이 맞습니다.
- 반복되는 계산을 재사용할 수 있음
- 입력과 출력이 드러나 테스트가 쉬워짐
- 코드 의미를 이름으로 추상화할 수 있음
- 큰 로직을 작은 계산 단위로 쪼갤 수 있음
- 함수형 스타일, 객체지향 메서드, API 설계까지 폭넓게 연결됨
정의
가장 넓은 의미에서 함수는 “호출 가능한 코드 단위”입니다. 호출자는 함수에 인자를 전달하고, 함수는 그 입력을 바탕으로 계산을 수행하며, 경우에 따라 값을 돌려줍니다.
이때 중요한 것은 함수가 단순히 코드를 묶는 상자에 그치지 않는다는 점입니다. 함수는 이름, 매개변수, 반환값, 지역 변수, 스코프를 함께 가지며, 프로그램 안에서 독립적인 의미 단위로 취급됩니다. 따라서 함수는 재사용을 위한 문법이면서 동시에 설계 단위이기도 합니다.
핵심 원리
좋은 함수는 내부 구현을 감추고 외부에는 무엇을 넣고 무엇이 나오는지만 보여 줍니다. 호출자는 내부에서 반복문을 돌리는지, 정렬을 하는지, 캐시를 쓰는지 몰라도 결과를 사용할 수 있습니다.
이 점 때문에 함수는 추상화의 핵심 도구가 됩니다. 특히 계산 중심 코드는 함수로 분리할수록 읽기와 테스트가 쉬워지고, 재사용성도 올라갑니다. 결국 함수의 힘은 문법적 편의보다 입력과 결과를 기준으로 로직을 캡슐화한다는 데 있습니다.
기본 구조
| 요소 | 설명 | 실무 포인트 |
|---|---|---|
| 이름 | 함수를 식별하는 이름 | 동작이 드러나는 이름이 좋음 |
| 매개변수 | 호출 시 전달받는 입력 | 입력이 많아질수록 책임이 과한지 의심해야 함 |
| 본문 | 실제 계산이나 처리 로직 | 너무 길면 다시 더 작은 함수로 나누는 편이 좋음 |
| 지역 스코프 | 함수 호출마다 별도로 생기는 지역 변수 공간 | 외부 상태 의존을 줄일수록 예측 가능성이 높아짐 |
| 반환값 | 함수 실행 결과 | 계산 결과를 명확하게 드러내는 핵심 |
Python 문서도 함수 실행이 새로운 로컬 심볼 테이블을 도입한다고 설명합니다. 즉, 함수는 단순히 코드를 점프하는 장치가 아니라, 자기만의 지역 문맥을 만드는 실행 단위입니다.
기본 구현
def add(a, b):
return a + b
result = add(3, 5)
print(result) # 8
function add(a, b) {
return a + b;
}
console.log(add(3, 5)); // 8
static int add(int a, int b) {
return a + b;
}
public static void main(String[] args) {
int result = add(3, 5);
System.out.println(result);
}
이 예시들은 모두 같은 구조를 가집니다. 이름이 있고, 입력이 있고, 내부에서 계산을 수행한 뒤, 결과를 호출자에게 돌려줍니다. 언어 문법은 다르지만 함수의 중심 개념은 같습니다.
패턴 1. 함수와 프로시저, 메서드 차이
전통적인 문맥에서는 함수는 값을 반환하고, 프로시저는 직접적인 반환값 없이 절차를 수행하는 쪽에 가깝다고 설명합니다. 그래서 함수는 식 안에서 쓰기 쉽고, 프로시저는 문장으로 호출되는 경우가 많습니다.
현대 객체지향 언어에서는 이 차이가 메서드 안으로 흡수됩니다. 예를 들어 Java와 C#에서는 메서드가 값을 반환할 수도 있고, void일 수도 있습니다. 이때 반환값이 있는 메서드는 함수형 역할에 가깝고, void 메서드는 프로시저형 역할에 가깝습니다.
Python 문서는 메서드를 “객체에 속한 함수”라고 설명합니다. 즉, 함수가 클래스와 인스턴스 문맥에 묶이면 메서드가 됩니다. 따라서 함수와 메서드는 경쟁 개념이 아니라, 독립된 계산 단위와 객체에 귀속된 계산 단위의 차이로 보는 편이 자연스럽습니다.
| 구분 | 핵심 성격 | 대표 특징 |
|---|---|---|
| 함수 | 값 계산 중심 | 입력 → 결과 반환이 중심 |
| 프로시저 | 절차 수행 중심 | 직접 반환보다 동작 수행과 부수 효과가 중심 |
| 메서드 | 객체에 속한 함수 | 인스턴스나 클래스 문맥에 연결됨 |
패턴 2. 일급 함수와 고차 함수
JavaScript와 Python 문서는 모두 함수가 값처럼 다뤄질 수 있음을 보여 줍니다. 다른 이름이 같은 함수 객체를 가리킬 수 있고, 함수는 변수에 할당되거나 다른 함수에 인자로 전달되거나, 다른 함수의 반환값으로 나올 수 있습니다.
이 성질을 보통 일급 함수(first-class function)라고 부릅니다. 그리고 함수를 인자로 받거나 함수를 반환하는 함수는 고차 함수(higher-order function)라고 부릅니다. 이 개념이 들어오면 정렬 기준, 필터 조건, 콜백, 이벤트 처리, 함수 합성 같은 패턴이 훨씬 자연스럽게 구현됩니다.
function sayHello() {
return "Hello, ";
}
function greeting(helloMessage, name) {
console.log(helloMessage() + name);
}
greeting(sayHello, "JavaScript!");
def linear(a, b):
def result(x):
return a * x + b
return result
f = linear(2, 3)
print(f(10)) # 23
패턴 3. 순수 함수와 부수 효과
일반 프로그래밍의 함수는 종종 로그를 찍고, 파일을 쓰고, 전역 상태를 바꾸고, 시간을 읽고, 난수를 생성합니다. 이런 함수도 실무에서는 매우 흔합니다. 하지만 함수형 프로그래밍 관점에서는 입력만으로 결과가 결정되고 외부 상태를 건드리지 않는 순수 함수를 특별히 중요하게 봅니다.
Haskell은 이 순수성을 언어 철학의 중심에 둡니다. Haskell 문서는 모든 함수가 수학적 의미의 함수, 즉 pure하다고 설명합니다. 반면 일반 언어에서는 순수 함수와 부수 효과가 있는 함수를 개발자가 설계 차원에서 구분해야 합니다.
순수 함수 예시
add(2, 3) -> 항상 5
부수 효과가 있는 함수 예시
saveOrder(order)
printReport(data)
increaseCounter()
실무에서는 모든 함수를 순수하게 만들 수는 없습니다. 하지만 계산 로직을 가능한 한 순수 함수로 분리하고, 입출력과 상태 변경은 바깥 계층으로 밀어내는 설계가 테스트와 유지보수에 매우 유리합니다.
한계와 주의점
함수 분리는 중요하지만, 지나치게 작고 의미 없는 함수가 많아지면 흐름을 따라가기 어려워질 수 있습니다. 함수 하나가 너무 많은 파라미터를 받거나, 함수가 여기저기 전역 상태에 기대고 있으면 오히려 추상화가 실패한 것입니다.
또한 함수라는 이름 아래 무엇이든 숨길 수 있기 때문에, 부수 효과가 많은 함수를 “값 계산처럼” 보이게 만들면 디버깅이 어려워집니다. 좋은 함수 설계는 크기보다 책임의 선명함이 더 중요합니다.
- 함수 안에 너무 많은 부수 효과를 숨기지 않기
- 파라미터 수가 과도하면 책임 분리가 잘못됐는지 의심하기
- 너무 잘게 쪼개 의미 없는 wrapper 함수만 늘리지 않기
- 이름과 실제 동작이 다르면 추상화가 오히려 해로울 수 있음
자주 하는 실수
- 함수 = return 있는 코드로만 이해함
- 프로시저, 메서드, 함수 차이를 전혀 구분하지 않음
- 함수 안에서 전역 상태를 바꾸면서도 순수 계산처럼 사용함
- 너무 많은 파라미터를 받는 함수로 책임을 과도하게 몰아넣음
- 같은 계산이 반복되는데도 함수로 추출하지 않음
- 반대로 너무 사소한 코드까지 함수로 쪼개 흐름을 해침
실무 루틴
- 이 코드가 값을 계산하는지, 상태를 바꾸는지 먼저 나눈다.
- 계산 중심이면 가능한 한 입력과 반환값을 명확히 만든다.
- 동작 중심이면 함수 이름에 부수 효과가 드러나게 짓는다.
- 너무 많은 파라미터가 필요하면 데이터 구조나 책임 분리를 다시 본다.
- 테스트 가능한 순수 계산 부분을 먼저 함수로 분리한다.
- 객체 문맥이 필요하면 메서드로, 독립 계산이면 일반 함수로 두는 편이 좋다.
디버깅
함수 디버깅 체크리스트
- 입력은 올바른가?
- 반환값은 기대한 타입과 의미를 가지는가?
- 외부 상태를 읽거나 쓰는가?
- 함수인가, 사실상 프로시저인가?
- 순수 계산 부분을 더 잘게 분리할 수 있는가?
요약
- ✅ 함수는 호출 가능한 이름 붙은 계산 단위다.
- ✅ 보통 입력 인자를 받아 처리하고 값을 반환한다.
- ✅ 함수는 프로그램을 재사용 가능한 의미 단위로 구조화한다.
- ✅ 프로시저는 절차 수행, 함수는 값 계산 쪽에 더 가깝다고 이해하면 쉽다.
- ✅ 메서드는 객체에 속한 함수라고 볼 수 있다.
- ✅ 일급 함수가 가능한 언어에서는 함수를 값처럼 전달하고 반환할 수 있다.
- ✅ 순수 함수는 입력만으로 결과가 결정되고 외부 상태를 바꾸지 않는다.
- ✅ 좋은 함수 설계는 반환값보다 책임의 선명함과 부수 효과 통제까지 포함한다.