도입
프로그래밍에서 프로시저(procedure)는 오래된 개념이지만 지금도 매우 중요합니다. 코드 여러 줄을 의미 있는 이름 아래 묶고, 필요할 때마다 다시 호출할 수 있게 만든다는 점에서 프로그램 구조화의 핵심이기 때문입니다.
다만 현대 언어에서는 이 개념이 함수, 메서드, 서브루틴, 저장 프로시저 같은 이름으로 분화되어 보이기 때문에 초보자가 헷갈리기 쉽습니다. 그래서 프로시저를 제대로 이해하려면 “무엇을 돌려주느냐”보다 어떤 절차를 캡슐화하느냐를 중심으로 보는 편이 더 좋습니다.
필요성
큰 프로그램이 읽기 어려운 가장 큰 이유는 모든 동작이 한곳에 섞여 있기 때문입니다. 같은 출력 작업, 파일 저장, 상태 갱신, 초기화 절차가 여러 군데 반복되면 유지보수도 어려워집니다.
프로시저는 이런 반복 절차를 하나의 이름으로 묶어 줍니다. 덕분에 코드는 더 짧아지고, 의미가 드러나며, 같은 작업을 여러 번 재사용하기 쉬워집니다. 즉, 프로시저는 단순 문법이 아니라 절차적 추상화의 기본 도구입니다.
- 반복되는 작업 절차를 이름으로 묶을 수 있음
- 프로그램을 더 작은 단위로 나눠 읽기 쉽게 만들 수 있음
- 공통 동작을 여러 곳에서 재사용할 수 있음
- 값 계산이 아닌 “행동” 중심 코드를 정리하기 좋음
- 함수, 메서드, 저장 프로시저 같은 다른 개념과의 차이를 잡는 기준이 됨
정의
가장 넓은 의미에서 프로시저는 입력을 받아 어떤 절차를 수행하는 이름 붙은 코드 블록입니다. 상황에 따라 매개변수를 받을 수 있고, 내부 지역 변수를 가질 수 있으며, 다른 프로시저나 함수를 호출할 수도 있습니다.
전통적으로는 프로시저가 직접적인 반환값이 없는 서브프로그램이라는 구분이 자주 사용됩니다. 따라서 호출식(expression) 안에서 값으로 쓰기보다, 별도의 호출문(statement)으로 실행하는 경우가 많습니다.
핵심 원리
좋은 프로시저는 호출자가 내부 단계를 모두 알 필요 없게 만듭니다. 예를 들어 화면을 초기화하고, 데이터를 읽고, 검증하고, 저장하고, 로그를 남기는 복잡한 작업도 saveOrder() 같은 이름 아래 감출 수 있습니다.
이 점에서 프로시저는 절차적 추상화의 핵심입니다. 호출자는 “무엇을 할 것인가”만 알고, “그 작업이 내부에서 몇 단계로 이뤄지는가”는 몰라도 됩니다. 결국 프로그램 구조화의 기본은 긴 코드를 쪼개는 것이 아니라 의미 있는 절차 단위로 추상화하는 것입니다.
기본 구조
| 요소 | 설명 | 실무 포인트 |
|---|---|---|
| 이름 | 프로시저를 식별하는 이름 | 동작을 드러내는 동사형 이름이 잘 맞음 |
| 매개변수 | 호출 시 넘겨받는 입력값 | 입력, 출력, 입출력 모드를 구분하는 언어도 있음 |
| 본문 | 실제 실행되는 절차 | 길어질수록 다시 더 작은 절차로 나누는 편이 좋음 |
| 지역 변수 | 절차 내부에서만 쓰는 상태 | 전역 상태 의존을 줄일수록 테스트가 쉬움 |
| 호출문 | 프로시저를 실행하는 문장 | 함수처럼 식 안에 끼워 넣기보다 문장 단위 호출이 흔함 |
| 결과 전달 | 직접 반환값이 없으면 부수 효과나 출력 파라미터 사용 | 값 계산인지 상태 변경인지 구분이 중요함 |
기본 구현
// Pascal 스타일에 가까운 예시
procedure PrintProfile(name: string; age: integer);
begin
writeln('name = ', name);
writeln('age = ', age);
end;
// 호출
PrintProfile('Kim', 30);
// Java에서는 별도 procedure 문법 대신 void 메서드가 같은 역할을 함
static void printProfile(String name, int age) {
System.out.println("name = " + name);
System.out.println("age = " + age);
}
public static void main(String[] args) {
printProfile("Kim", 30);
}
위 두 예시는 문법은 다르지만 역할은 같습니다. 둘 다 특정 작업을 수행하고, 호출자는 결과값을 계산식 안에서 받기보다 절차를 실행합니다.
패턴 1. 함수와의 차이
전통적인 언어 문맥에서 함수(function)는 값을 반환하고, 프로시저(procedure)는 값을 직접 반환하지 않는다고 설명하는 경우가 많습니다. 그래서 함수는 식(expression) 안에 자연스럽게 들어가고, 프로시저는 실행문(statement)으로 호출되는 경우가 흔합니다.
물론 현실에서는 이 경계가 완전히 절대적인 것은 아닙니다. 출력 파라미터를 통해 여러 값을 전달하는 프로시저도 있고, 반환값이 있으면서도 강한 부수 효과를 가지는 함수도 있습니다. 그래도 개념을 잡을 때는 “함수는 계산, 프로시저는 절차”라는 구분이 가장 이해하기 쉽습니다.
| 구분 | 프로시저 | 함수 |
|---|---|---|
| 핵심 목적 | 작업 수행 | 값 계산 및 반환 |
| 반환값 | 직접 반환이 없거나 부차적 | 명시적 반환이 핵심 |
| 호출 방식 | 주로 실행문 형태 | 식이나 대입문 안에서 자주 사용 |
| 대표 예 | 출력, 저장, 상태 변경, 초기화 | 합계 계산, 판정, 변환 결과 생성 |
// 함수형 예시
function Add(a, b): integer;
begin
Add := a + b;
end;
// 프로시저형 예시
procedure PrintSum(a, b: integer);
begin
writeln(a + b);
end;
패턴 2. 메서드와의 관계
Java, C++, C# 같은 언어를 배우는 개발자는 종종 “프로시저는 어디 있지?”라고 느낍니다. 이유는 이들 언어가 보통 별도 procedure 키워드를 두지 않고, 메서드(method)가 값을 반환할 수도 있고 반환하지 않을 수도 있게 설계되어 있기 때문입니다.
따라서 이런 언어에서는 반환형이 있는 메서드가 함수와 비슷한 역할을 하고, void 메서드가 프로시저와 비슷한 역할을 합니다. 즉, 개념은 남아 있지만 문법 레벨에서는 메서드 하나로 통합된 셈입니다.
class Calculator {
int add(int a, int b) { // 함수에 가까운 메서드
return a + b;
}
void printSum(int a, int b) { // 프로시저에 가까운 메서드
System.out.println(a + b);
}
}
이 차이를 이해해 두면, 왜 어떤 메서드는 식 안에서 자연스럽게 쓰이고 어떤 메서드는 그저 호출만 해야 하는지 더 명확하게 보입니다.
패턴 3. 저장 프로시저와의 차이
저장 프로시저(stored procedure)는 일반 애플리케이션 코드 안의 절차가 아니라, 데이터베이스 안에 저장되는 이름 붙은 프로그램 단위입니다. Oracle PL/SQL과 PostgreSQL은 모두 프로시저와 함수를 별도 객체로 다루며, 데이터베이스 안에서 직접 실행할 수 있게 합니다.
특히 PostgreSQL은 프로시저를 함수와 분명히 구분합니다. 프로시저는 CREATE PROCEDURE로 정의하고, 함수처럼 RETURNS 절이 없으며, CALL로 실행합니다. 또한 출력 파라미터를 통해 호출자에게 데이터를 돌려줄 수 있습니다. 이 점에서 “프로시저는 직접 반환값이 없다”는 전통적인 감각이 DB에서도 비교적 잘 살아 있습니다.
| 구분 | 일반 프로그래밍의 프로시저 | 저장 프로시저 |
|---|---|---|
| 위치 | 애플리케이션 코드 내부 | 데이터베이스 서버 내부 |
| 주 역할 | 프로그램 절차 캡슐화 | DB 작업 절차 캡슐화 |
| 호출 방식 | 언어 문법으로 호출 | DB 명령 또는 드라이버 호출 |
| 대표 예 | 출력, 상태 변경, 초기화, 저장 처리 | 배치 처리, 트랜잭션 절차, 데이터 정리 |
CREATE PROCEDURE log_message(IN msg text)
LANGUAGE SQL
AS $$
INSERT INTO app_log(message) VALUES (msg);
$$;
CALL log_message('hello');
한계와 주의점
프로시저는 본질적으로 동작을 수행하는 쪽에 가깝기 때문에 상태를 바꾸는 경우가 많습니다. 이 점이 장점이기도 하지만, 동시에 남용하면 어디서 값이 바뀌는지 추적하기 어려워집니다.
특히 하나의 프로시저가 출력, 저장, 검증, 네트워크 호출, 전역 상태 변경까지 모두 해 버리면 테스트가 어려워지고 예측 가능성도 낮아집니다. 따라서 프로시저는 함수와 경쟁 개념이라기보다, 부수 효과를 어디까지 허용할 것인가와 함께 설계해야 하는 절차 단위입니다.
- 너무 많은 부수 효과를 한 프로시저에 몰아넣지 않기
- 출력, 저장, 계산, 상태 변경을 전부 한 번에 하지 않기
- 거대한 절차를 작은 절차 단위로 다시 분리하기
- 반환값이 없다고 해서 테스트가 필요 없다고 착각하지 않기
자주 하는 실수
- 프로시저 = 함수에서 return만 뺀 것이라고만 이해함
- 프로시저와 저장 프로시저를 같은 것으로 혼동함
- 메서드와 프로시저 차이를 문법 차이로만 이해함
- 반환값이 없으니 설계가 단순하다고 착각함
- 프로시저 안에 너무 많은 상태 변경을 숨겨 둠
- 출력 파라미터와 부수 효과를 뒤섞어 API를 혼란스럽게 만듦
실무 루틴
- 이 코드가 값을 계산하는지, 절차를 수행하는지 먼저 구분한다.
- 절차 수행 중심이라면 프로시저형 설계를 우선 고려한다.
- 이름은 동작이 드러나게 짓는다. 예:
initializeCache,printReport,saveOrder - 부수 효과가 무엇인지 문서나 코드에서 명확히 드러낸다.
- 너무 큰 프로시저는 더 작은 절차 단위로 나눈다.
- DB 저장 프로시저와 일반 코드 프로시저를 문맥상 분리해 이해한다.
디버깅
프로시저 디버깅 체크리스트
- 입력은 무엇인가?
- 상태를 바꾸는가?
- 출력 파라미터가 있는가?
- 외부 자원을 건드리는가?
- 너무 많은 일을 한 번에 하고 있는가?
- 함수로 분리해야 할 계산 로직이 섞여 있는가?
요약
- ✅ 프로시저는 이름 붙은 절차 단위다.
- ✅ 전통적으로는 직접 반환값이 없는 서브프로그램으로 설명되는 경우가 많다.
- ✅ 함수는 값 계산, 프로시저는 동작 수행이라는 감각으로 구분하면 이해가 쉽다.
- ✅ 현대 객체지향 언어에서는 void 메서드가 프로시저 역할을 흡수하는 경우가 많다.
- ✅ 저장 프로시저는 데이터베이스 안에 저장되는 별도의 프로그램 단위다.
- ✅ 프로시저 설계에서는 반환값보다 부수 효과와 상태 변경 범위를 더 주의해서 봐야 한다.
- ✅ 좋은 프로시저는 내부 절차를 감추고 외부에는 의미 있는 이름과 입력만 노출한다.
- ✅ 프로시저를 이해하면 함수, 메서드, 저장 프로시저 차이도 함께 정리된다.