2025-08-09 10:18
Tags:
선언형 프로그래밍 핸드북
1. 만들어진 이유: “어떻게”가 아닌 “무엇을”
선언형 프로그래밍의 탄생을 이해하기 위해 먼저 그 반대편에 있는 **명령형 프로그래밍(Imperative Programming)**을 잠시 살펴보겠습니다.
명령형 프로그래밍은 목표를 달성하기 위한 **과정(How)**을 하나하나 명시하는 방식. 마치 요리사에게 “1. 양파를 썬다. 2. 팬을 달군다. 3. 기름을 두른다…” 와 같이 단계별 레시피를 모두 알려주는 것과 같음. 컴퓨터가 수행할 작업을 순서대로 모두 지시해야 함.
하지만 소프트웨어의 규모가 커지고 복잡해지면서 이 ‘과정’을 일일이 관리하는 것은 수많은 버그의 원인이 됨. 코드의 모든 단계를 추적하기 어려워지고, 작은 변경 하나가 예상치 못한 부작용(Side Effect)을 일으키기 쉬워짐.
**선언형 프로그래밍(Declarative Programming)**은 이러한 복잡성을 해결하기 위해 등장. 과정(How)이 아닌 **목표(What)**에 집중하는 방식. 요리사에게 상세한 레시피 대신 “알리오 올리오 파스타 하나 만들어 줘”라고 최종 결과물만 요청하는 것과 같음. 우리는 ‘무엇을’ 원하는지만 선언하고, 그 ‘어떻게’에 대한 구체적인 실행은 컴퓨터나 프레임워크가 알아서 처리.
이러한 접근 방식은 코드의 양을 줄이고 가독성을 높이며, 가장 중요하게는 결과를 예측하기 쉽게 만들어 프로그램의 안정성을 크게 향상시킴.
2. 핵심 개념: 목표를 코드로 서술하기
선언형 프로그래밍의 핵심은 ‘제어 흐름’을 추상화하는 것. 즉, 반복문, 조건문 등의 실행 순서를 직접 제어하는 대신, 원하는 상태나 결과가 무엇인지를 서술.
간단한 예시로 배열에서 짝수만 골라내는 코드를 비교해 보겠습니다.
명령형 방식 (JavaScript) for
반복문을 사용해 배열의 모든 요소를 순회하고, if
조건문으로 짝수인지 확인한 뒤, 새로운 배열에 직접 추가하는 ‘과정’을 모두 코드로 작성.
const numbers = [1, 2, 3, 4, 5, 6];
const evens = [];
for (let i = 0; i < numbers.length; i++) {
if (numbers[i] % 2 === 0) {
evens.push(numbers[i]);
}
}
// 결과: [2, 4, 6]
선언형 방식 (JavaScript) filter
라는 고차 함수를 사용해 “숫자들 중에서 2로 나누어 떨어지는 수만 남긴다”는 ‘목표’를 선언. 짝수를 어떻게 찾고, 어떻게 새 배열에 담을지에 대한 과정은 filter
함수 내부에 숨겨져 있음.
const numbers = [1, 2, 3, 4, 5, 6];
const evens = numbers.filter(num => num % 2 === 0);
// 결과: [2, 4, 6]
가장 대표적인 선언형 언어인 SQL을 보면 이 특징이 더욱 명확해짐.
선언형 방식 (SQL) “users 테이블에서 나이가 30 이상인 사용자의 이름을 선택하라”는 ‘무엇을’ 원하는지만 명시. 데이터베이스가 내부적으로 어떤 인덱스를 타고, 어떻게 데이터를 순회할지에 대한 ‘어떻게’는 전혀 드러나지 않음.
SELECT name FROM users WHERE age >= 30;
3. 구조: 선언형 프로그래밍의 종류
선언형 프로그래밍은 하나의 특정 기술이 아니라, ‘무엇’을 중심으로 사고하는 프로그래밍 패러다임의 총칭. 여러 하위 분야를 포함.
-
함수형 프로그래밍 (Functional Programming)
-
계산을 수학적 함수의 조합으로 간주. 순수 함수(Pure Function)를 지향하여 부작용을 최소화.
-
예: Lisp, Haskell, JavaScript(ES6+), Python의 일부 기능
-
-
논리형 프로그래밍 (Logic Programming)
-
프로그램을 논리적인 규칙과 사실의 집합으로 표현. 주어진 질문에 대해 논리적 추론을 통해 답을 찾음.
-
예: Prolog
-
-
데이터베이스 질의 언어 (Database Query Languages)
-
데이터 집합에서 원하는 데이터를 추출하거나 조작하는 방법을 선언.
-
예: SQL, LINQ
-
-
마크업 및 스타일링 언어 (Markup & Styling Languages)
-
문서의 구조나 스타일을 선언. 브라우저는 이 선언을 해석하여 화면에 ‘어떻게’ 그릴지 결정.
-
예: HTML (문서의 구조를 선언), CSS (요소의 스타일을 선언)
-
-
인프라 코드화 (Infrastructure as Code)
-
서버, 네트워크 등 인프라의 최종 상태를 코드로 선언.
-
예: Terraform, Ansible, Kubernetes Manifests
-
4. 사용법: 언제, 어떻게 사용할까?
현대 소프트웨어 개발의 많은 영역에서 선언형 접근법이 표준으로 자리 잡고 있음.
-
UI 개발
- React, Vue, SwiftUI 등 현대적인 UI 프레임워크는 모두 선언형. 개발자는 특정 데이터 상태(state)에서 UI가 ‘어떻게 보여야 하는지’를 JSX나 코드로 선언. 사용자와의 상호작용으로 데이터가 바뀌면, 프레임워크가 이전 상태와 비교하여 최소한의 변경사항을 DOM에 효율적으로 반영(어떻게)함.
-
데이터 처리 및 분석
- 대용량 데이터를 다룰 때 SQL이나 Python의 Pandas 같은 라이브러리를 사용. “특정 조건에 맞는 데이터를 그룹화하고 평균을 내라”와 같이 원하는 결과만 선언하면, 내부 최적화 엔진이 가장 효율적인 방법으로 작업을 수행.
-
클라우드 인프라 관리
- Terraform 같은 도구를 사용해 “웹 서버 3대, 데이터베이스 1개가 필요하다”라고 코드로 선언하면, 클라우드 제공업체(AWS, GCP 등) API를 통해 해당 상태를 자동으로 구성. 수동 작업의 실수를 줄이고 인프라 관리를 자동화.
5. 심화: 명령형과의 관계
선언형 프로그래밍이 명령형을 완전히 대체하는 것은 아님. 사실, 선언형은 명령형을 기반으로 만들어진 추상화 계층. 우리가 편리하게 사용하는 JavaScript의 filter
함수나 React의 내부 동작은 결국 저수준의 명령형 코드로 구현되어 있음.
중요한 것은 두 패러다임의 장단점을 이해하고 문제에 맞는 적절한 도구를 선택하는 것.
-
선언형이 유리할 때: 복잡한 시스템의 상태를 관리하고, 코드의 의도를 명확히 드러내고 싶을 때. (UI, 데이터 흐름, 인프라 설정 등)
-
명령형이 유리할 때: 하드웨어를 직접 제어하거나, 알고리즘의 성능을 극한으로 최적화하는 등 실행의 모든 단계를 세밀하게 제어해야 할 때.
결국 좋은 개발자란, 높은 수준에서는 선언적으로 사고하여 시스템의 복잡도를 낮추고, 필요할 때는 그 추상화의 내부로 들어가 명령형 코드를 이해하고 수정할 수 있는 능력을 갖춘 사람이라고 할 수 있음.