선언적 프로그래밍(Declarative Programming)

선언적 프로그래밍은 프로그램이 ‘무엇을’ 해야 하는지를 명시하는 프로그래밍 패러다임으로, ‘어떻게’ 수행해야 하는지에 대한 세부 단계를 명시적으로 기술하지 않는다. 이 접근 방식은 계산의 로직을 표현하되, 그것이 어떻게 수행되는지에 대한 제어 흐름은 추상화한다.

선언적 프로그래밍의 핵심 특성은 다음과 같다:

  1. 목표 중심: 원하는 결과나 목표 상태를 명시한다.
  2. 추상화: 구현 세부 사항을 숨긴다.
  3. 비상태적(Stateless): 외부 상태에 덜 의존한다.
  4. 표현적: 코드가 더 읽기 쉽고 의도를 명확히 표현한다.
  5. 부작용 감소: 함수나 표현식이 주변 상태에 미치는 영향이 제한적이다.

선언적 프로그래밍은 단순한 기술적 접근 방식을 넘어 소프트웨어 개발에 대한 철학적 관점을 제공한다.
이는 우리가 문제를 해결하는 방식을 ‘어떻게’에서 ‘무엇을’로 전환시킨다.
이러한 전환은 코드를 더 읽기 쉽고, 추론하기 쉽게 만들며, 시스템의 자율성과 최적화 가능성을 높인다.

선언적 프로그래밍의 핵심 철학은 세부 구현보다 의도와 목표를 우선시하는 것이다. 이는 소프트웨어 개발자가 저수준의 메커니즘보다 고수준의 문제 도메인에 집중할 수 있게 해주며, 결과적으로 더 유지보수하기 쉽고 변화에 강한 시스템을 구축할 수 있게 한다.

선언적 프로그래밍의 역사적 발전

선언적 프로그래밍의 개념은 수십 년에 걸쳐 발전해 왔다:

선언적 프로그래밍의 장단점

장점

  1. 가독성: 코드가 더 간결하고 목적이 명확하다.
  2. 유지보수성: 구현 세부 사항보다 의도에 집중하므로 변경이 쉽다.
  3. 추상화: 복잡한 로직을 숨기고 중요한 개념에 집중할 수 있다.
  4. 병렬화 기회: 세부 단계를 명시하지 않으므로 시스템이 최적화할 수 있는 여지가 많다.
  5. 버그 감소: 부작용이 제한되어 있어 예상치 못한 동작의 가능성이 줄어든다.
  6. 재사용성: 구현보다 기능에 집중하므로 컴포넌트를 재사용하기 쉽다.

단점

  1. 성능 제어 제한: 프로그램의 실행 방식을 세밀하게 제어하기 어려울 수 있다.
  2. 학습 곡선: 특히 명령형 프로그래밍에 익숙한 개발자에게는 새로운 사고 방식이 필요하다.
  3. 디버깅 복잡성: 추상화 수준이 높아 때로는 문제 진단이 어려울 수 있다.
  4. 오버헤드: 일부 경우에는 간단한 작업에 불필요한 복잡성이 추가될 수 있다.
  5. 제한된 표현력: 매우 특수한 로직은 선언적으로 표현하기 어려울 수 있다.

선언적 프로그래밍의 하위 카테고리

  1. 함수형 프로그래밍(Functional Programming)
    순수 함수, 고차 함수, 불변성을 강조하는 패러다임으로, Haskell, Clojure, Scala 등이 대표적인 언어.
    함수를 값으로 취급하고 부작용을 최소화하는 특징이 있다.

  2. 논리 프로그래밍(Logic Programming)
    관계와 사실을 정의하고 이를 바탕으로 쿼리를 수행하는 패러다임으로, Prolog가 대표적인 언어.
    문제의 논리적 구조에 초점을 맞추고 구체적인 알고리즘보다는 제약 조건을 정의한다.

  3. 제약 프로그래밍(Constraint Programming)
    문제에 대한 제약 조건을 명시하고 시스템이 이를 만족하는 해결책을 찾도록 하는 패러다임이다.
    일정 관리, 리소스 할당, 최적화 문제 등에 적용된다.

  4. 리액티브 프로그래밍(Reactive Programming)
    데이터 스트림과 변화의 전파를 선언적으로 정의하는 패러다임으로, 비동기 이벤트 처리와 데이터 흐름 관리에 중점을 둔다. RxJS, Akka Streams 등이 대표적인 라이브러리이다.

  5. 도메인 특화 언어(Domain-Specific Languages, DSLs)
    특정 도메인의 문제를 해결하기 위해 설계된 선언적 언어로, SQL(데이터베이스), CSS(스타일링), RegExp(패턴 매칭) 등이 있다.

  6. 데이터베이스 프로그래밍(Database Programming)
    SQL과 같은 언어를 사용하여 데이터를 선언적으로 쿼리하고 조작하는 방식.
    데이터베이스 시스템이 쿼리 최적화와 실행을 처리한다.

  7. 구성 프로그래밍(Configuration Programming)
    YAML, JSON, XML 등을 사용하여 시스템 구성이나 워크플로우를 선언적으로 정의하는 방식.
    인프라스트럭처 도구, CI/CD 파이프라인 등에서 널리 사용된다.

선언적 프로그래밍의 주요 예시

  1. SQL (Structured Query Language)
    SQL은 관계형 데이터베이스와 상호 작용하기 위한 대표적인 선언적 언어.

    1
    2
    3
    4
    5
    6
    7
    8
    
    -- 명령형 접근 방식으로 이 작업을 생각한다면, 레코드를 하나씩 검색하고
    -- 필터링하고 그룹화하는 등의 단계를 명시해야 합니다.
    -- 하지만 SQL은 선언적으로 '무엇을' 원하는지만 명시합니다.
    SELECT department, AVG(salary) as avg_salary
    FROM employees
    WHERE hire_date > '2020-01-01'
    GROUP BY department
    HAVING AVG(salary) > 50000;
    
  2. HTML & CSS
    웹 페이지의 구조와 스타일을 선언적으로 정의한다.

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    
    <!-- HTML: 페이지 구조를 선언적으로 정의 -->
    <div class="card">
      <h2>제목</h2>
      <p>내용</p>
    </div>
    
    <!-- CSS: 스타일을 선언적으로 정의 -->
    <style>
    .card {
      width: 300px;
      padding: 16px;
      border-radius: 8px;
      box-shadow: 0 2px 4px rgba(0,0,0,0.1);
    }
    </style>
    
  3. React.js
    React는 UI 상태를 선언적으로 정의하는 JavaScript 라이브러리.

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    
    // React 컴포넌트: UI 상태를 선언적으로 정의
    function Counter() {
      const [count, setCount] = useState(0);
    
      return (
        <div>
          <p>현재 카운트: {count}</p>
          <button onClick={() => setCount(count + 1)}>
            증가
          </button>
        </div>
      );
    }
    
  4. 정규 표현식(Regular Expressions)
    텍스트 패턴을 선언적으로 정의한다.

    1
    2
    3
    
    // 이메일 주소 패턴을 선언적으로 정의
    const emailPattern = /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/;
    const isValidEmail = emailPattern.test("example@email.com");
    
  5. GraphQL
    API에서 원하는 데이터 구조를 선언적으로 요청한다.

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    
    # 필요한 데이터 구조를 정확히 선언
    query {
      user(id: "123") {
        name
        email
        posts {
          title
          commentCount
        }
      }
    }
    

실제 업계에서의 선언적 프로그래밍 활용 사례

  1. 웹 개발

    • React, Vue, Angular: 이러한 프레임워크들은 UI를 선언적으로 정의한다.
    • CSS 프레임워크: Tailwind CSS와 같은 프레임워크는 스타일을 선언적으로 적용한다.
    • GraphQL: 클라이언트가 필요한 데이터 구조를 선언적으로 요청할 수 있게 한다.
  2. 데이터 처리

    • SQL과 NoSQL 데이터베이스: 데이터 쿼리를 선언적으로 표현한다.
    • Apache Spark: 대규모 데이터 처리를 선언적으로 정의한다.
    • Pandas, dplyr: 데이터 변환과 분석을 선언적으로 표현한다.
  3. 인프라스트럭처 관리

    • Terraform, CloudFormation: 클라우드 인프라를 선언적으로 정의한다.
    • Kubernetes: 컨테이너 오케스트레이션을 선언적으로 구성한다.
    • Ansible, Chef: 서버 구성을 선언적으로 관리한다.
  4. 워크플로우 자동화

    • GitHub Actions, GitLab CI/CD: CI/CD 파이프라인을 선언적으로 정의한다.
    • Apache Airflow: 데이터 파이프라인을 선언적으로 구성한다.
    • Jenkins 파이프라인: 빌드 및 배포 과정을 선언적으로 정의한다.

선언적 vs. 명령형 프로그래밍

선언적 프로그래밍과 명령형 프로그래밍의 차이를 이해하는 것은 매우 중요하다.
텍스트 파일에서 가장 자주 등장하는 단어 찾기에 대한 문제를 통해 선언적 vs. 명령형 프로그래밍을 비교해보면,

명령형 프로그래밍

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
def find_most_common_word(filename):
    # 파일 열기
    file = open(filename, 'r')
    text = file.read().lower()
    file.close()
    
    # 구두점 제거
    for char in '.,!?;:()[]{}""\'':
        text = text.replace(char, ' ')    
    # text = re.sub(r'.,!?;:\(\)\[\]\{\}"\'', "", text)
    # 단어 분할 및 카운트
    words = text.split()
    word_count = {}
    
    for word in words:
        if word in word_count:
            word_count[word] += 1
        else:
            word_count[word] = 1
    
    # 가장 자주 등장하는 단어 찾기
    max_count = 0
    most_common = None
    
    for word, count in word_count.items():
        if count > max_count:
            max_count = count
            most_common = word
    
    return most_common, max_count

선언적 프로그래밍

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
import re
from collections import Counter

def find_most_common_word(filename):
    # 파일에서 텍스트 읽기
    with open(filename, 'r') as file:
        text = file.read().lower()
    
    # 단어 추출 및 카운트
    words = re.findall(r'\b\w+\b', text)
    word_counts = Counter(words)
    
    # 가장 자주 등장하는 단어 반환
    return word_counts.most_common(1)[0]

선언적 버전은 더 간결하고 의도가 명확하다.
Counter 객체와 most_common 메서드를 활용하여 “가장 많이 등장하는 단어를 찾는다"는 목표를 직접적으로 표현한다.

명령형 접근 방식에서는 ‘어떻게’(반복문을 사용하여 각 요소를 검사하고, 조건부로 새 배열에 추가하는 등)에 중점을 두는 반면, 선언적 접근 방식에서는 ‘무엇을’(짝수를 필터링하고 각 요소를 두 배로 만드는 것)에 중점을 둔다.

선언적 프로그래밍의 향후 전망

선언적 프로그래밍은 여러 면에서 계속 발전하고 있다:

  1. AI와의 통합: ChatGPT와 같은 LLM을 활용한 자연어 인터페이스를 통해 더욱 선언적인 방식으로 프로그램을 작성할 수 있게 된다.
  2. 저코드/노코드 플랫폼: 선언적 접근 방식을 활용하여 비개발자도 애플리케이션을 구축할 수 있게 된다.
  3. 자율 시스템: 시스템의 원하는 상태를 선언하면 시스템이 자율적으로 그 상태에 도달하는 방법을 결정한다.
  4. 도메인별 최적화: 특정 도메인을 위한 선언적 DSL이 더욱 발전하여 생산성을 향상시킨다.
  5. 분산 시스템 관리: 복잡한 분산 시스템의 구성과 동작을 선언적으로 정의하는 도구가 발전한다.

용어 정리

용어설명

참고 및 출처