---
title: 가산적 프로그래밍
tags: [concept, SDF]
sources: [SDF-ch1-flexibility, SDF-ch2-dsl, SDF-ch3-generic-procedures, SDF-ch4-pattern-matching, SDF-ch5-evaluation, SDF-ch6-layering, SDF-ch7-propagation]
updated: 2026-04-30
---

# 가산적 프로그래밍

## 한 줄 정의

동작하는 코드를 수정하지 않고 새 코드를 *추가*하는 것만으로 새 기능을 구현하거나 기존 기능을 새 요구사항에 맞추는 프로그래밍 스타일.

## 핵심 내용

### 왜 "수정"이 아닌 "추가"인가

소프트웨어 수명주기에서 가장 비싼 비용은 재작성과 리팩토링이다. 요구사항이 변할 때마다 기존 코드를 건드리면 다음과 같은 문제가 발생한다:

- 회귀 버그: 잘 동작하던 부분이 수정 여파로 깨짐
- 예상치 못한 결합(coupling): 수정이 다른 곳에 영향
- 테스트 재작성 부담

가산적 접근은 변경 대신 레이어를 쌓거나(Ch6), 핸들러를 추가하거나(Ch3), 규칙을 추가하거나(Ch4), 전파기를 배선도에 더하는(Ch7) 식으로 기능을 확장한다. 기존 코드는 원래대로 남아 있다.

### 가산성을 가능하게 하는 전제 조건

> "In order for additive programming to be possible, it is necessary to minimize the assumptions about how a program works and how it will be used."

세 가지 전제 조건:

1. **가정의 최소화**: 컴포넌트가 자기 역할 외의 것을 가정하지 않아야 새 컨텍스트에서도 재사용 가능
2. **just-in-time 결정**: 실행 환경에 기반해 결정을 늦춤. 미리 굳힌 가정은 나중에 변경할 수 없음
3. **관심사의 명확한 분리**: 의도치 않은 상호작용을 줄여야 변경이 국소적으로 머뭄

### SDF 전체에 걸친 구현 패턴

각 챕터는 가산성을 다른 차원에서 구현한다:

| 챕터 | 가산성 메커니즘 | 추가 단위 |
|------|----------------|---------|
| Ch2 | 컴비네이터 | 새 컴비네이터/프리미티브 부품 |
| Ch3 | 제네릭 프로시저 | 새 타입 핸들러 |
| Ch4 | 규칙 시스템 | 새 패턴 규칙 |
| Ch5 | 제네릭 인터프리터 | 새 표현식 타입 핸들러 |
| Ch6 | 레이어드 데이터 | 새 메타데이터 레이어 |
| Ch7 | 전파 네트워크 | 새 전파기 노드 |

### 정확성 요구와의 긴장

"모든 것을 증명 가능하게 만들라"는 규율은 역설적으로 시스템을 취약하게 만든다. 일반적 메커니즘의 일반적 속성을 증명하기는 특수 메커니즘의 특수 속성을 증명하기보다 훨씬 어렵기 때문에, 증명 요구는 부품을 가능한 한 특수화하도록 유도한다. 특수화된 부품의 조합은 변화의 여지가 없다.

Postel의 법칙이 여기 적용된다: "Be conservative in what you do, be liberal in what you accept from others." 컴포넌트가 넓은 입력을 수용할수록 재사용성이 높아지고, 출력은 하위 컴포넌트에 최소한의 가정을 강요한다.

## SDF에서의 등장

- [[SDF-ch1-flexibility]]: 개념의 철학적·생물학적 기반을 제시. 퇴화성, 탐색적 행동, 스마트 파츠 원칙으로 동기 부여
- [[SDF-ch2-dsl]]: 컴비네이터와 래퍼를 통한 첫 번째 구체적 구현. 믹스앤매치 부품 체계
- [[SDF-ch3-generic-procedures]]: 핸들러를 추가하는 방식으로 제네릭 프로시저를 확장
- [[SDF-ch4-pattern-matching]]: `attach-rule!`로 나중에 패턴 규칙을 동적으로 추가
- [[SDF-ch5-evaluation]]: `g:eval`을 제네릭 프로시저로 구현하여 새 표현식 타입을 핸들러 추가만으로 도입
- [[SDF-ch6-layering]]: 기존 코드를 전혀 수정하지 않고 단위·의존성 레이어를 추가
- [[SDF-ch7-propagation]]: 배선도에 새 전파기를 추가하는 것이 곧 기능 확장

## 실천 시 주의점

**컴비네이터의 한계**: Ch3에서 지적하듯, "다이아몬드 구조"(컴비네이터로 완성된 아름다운 폐쇄 시스템)는 이후 확장이 매우 어렵다. 가산성과 폐쇄성(closure)은 상충한다. 컴비네이터가 가산적인 것은 새 부품을 *조합*하는 한에서이고, 컴비네이터 자체를 수정해야 할 때는 그렇지 않다.

**순서 의존성**: 제네릭 프로시저에서 핸들러를 추가할 때, 적용 가능성이 겹치는 규칙들의 우선순위가 추가 순서에 따라 달라질 수 있다. 이 의존성은 암묵적이어서 디버깅이 어렵다.

**비용**: 가산성은 공간(여러 레이어를 유지), 계산 시간(디스패치 오버헤드), 프로그래머 시간(복잡한 인프라 이해) 비용을 수반한다. 수명이 짧거나 변경이 없는 코드에서는 과잉 투자다.

**진짜 가산성인지 확인**: "추가만으로" 확장이 이루어지는지 아니면 기존 코드를 수정해야 하는지를 의식적으로 확인해야 한다. 많은 경우 '수정하는 곳'이 숨어있다.

## AI 시대에서의 가산적 프로그래밍

### 왜 더 중요해지는가

AI 코드 생성이 빠를수록 "수정 오염"의 위험이 커진다. LLM은 맥락을 파편적으로 이해하기 때문에, 기존 코드를 수정하도록 지시하면 의도치 않은 부작용을 만들 확률이 높다. 가산적 아키텍처는 AI의 blast radius를 구조적으로 제한하는 메커니즘으로 작동한다.

GitClear(2024) 분석에 따르면 AI 지원 개발 이후 코드 복잡도(churn, coupling)가 오히려 증가했다. 이는 AI가 수정 패턴을 반복 적용할 때 발생하는 현상이며, 추가만 허용하는 구조가 이 문제를 구조적으로 방어한다.

### 검증 부담과 가산성

AI가 코드를 생성하고 아키텍트가 검증하는 워크플로우에서:

- **추가된 코드**: 격리된 변경 → 리뷰 범위가 명확
- **수정된 코드**: 기존 로직 전체 맥락 추적 필요

가산적 코드는 검증 비용이 낮다. 이는 인간-AI 협업에서 중요한 속성이다.

### AI 에이전트 아키텍처와의 동형성

OpenAI Function Calling, Anthropic Tool Use, MCP 서버 — 모두 기존 LLM 코어를 수정하지 않고 새 도구를 추가하는 구조다. 멀티에이전트 시스템, RAG 파이프라인, MCP 서버 합성이 이미 가산적 아키텍처 원칙 위에 설계되어 있다.

```
설계 원칙:  아키텍트가 extension point를 정의
생성 단위:  AI가 그 지점을 채움
```

이 협업 패턴은 가산적 설계와 구조적으로 일치한다.

### "Vibe Coding" 반론에 대해

"AI 시대에 코드는 버리고 재생성하는 것"이라는 주장은 스크립트 수준에서만 성립한다. 데이터가 쌓이고, 팀이 생기고, 시스템이 복잡해지는 순간 재생성 전략은 불가능해진다. 설계 원칙의 필요성은 규모와 함께 증가한다.

## 관련 개념

- [[generic-procedures]] — 가산적 확장의 핵심 메커니즘: 핸들러 추가
- [[combinators]] — 가산적 조합의 기반 도구
- [[layered-data]] — 수정 없는 메타데이터 추가의 구현
- [[propagation]] — 배선도에 노드를 더하는 가산적 계산 모델
- [[degeneracy]] — 가산성의 생물학적 대응물: 여러 메커니즘의 공존
- [[domain-specific-language]] — 가산적 확장이 자연스러운 도메인 언어 구축
