절차 지향, 객체 지향, 함수형 프로그래밍은 각각의 장단이 명확하기 때문에 어떤 패러다임이 절대적으로 우월하다기보다는, 해결하려는 문제의 성격과 팀의 기술적 맥락에 따라 적절한 방식을 선택하고 조합하는 것이 중요하다는 생각이 들었습니다.
프론트엔드 개발자로서 클래스 컴포넌트(OOP)로 작성된 레거시 코드를 이해해야 할 수도 있고, 최신 리액트 코드(FP)를 작성해야 할 때도 있기 때문에 각 패러다임의 핵심 아이디어를 정확히 이해하고 상황에 맞게 적절히 활용하는 것이, 견고하고 유지보수하기 쉬운 코드를 작성하는 개발자의 핵심 역량이 될 것이라고 생각합니다.
프로그래밍 패러다임 이해하기
소프트웨어 개발은 본질적으로 복잡한 문제를 논리적으로 해결하는 과정입니다. 이러한 문제를 효과적이고 일관되게 해결하기 위해, 개발자들은 오랜 시간에 걸쳐 다양한 접근 방식과 사고의 틀을 정립해왔습니다. 이를 프로그래밍 패러다임(Programming Paradigm)이라고 부릅니다.
패러다임은 개발자가 코드를 바라보는 관점이자, 프로그램의 구조를 설계하는 방법입니다. 어떤 프로그래밍 언어는 특정 패러다임을 강제하기도 하지만, 자바스크립트는 여러 패러다임을 지원하는 다중 패러다임 언어입니다.
프로그래밍 패러다임은 크게 두 가지로 나눌 수 있습니다.
- 명령형 프로그래밍 (Imperative Programming): '어떻게(How)' 문제를 해결할지에 초점을 맞춥니다. 컴퓨터가 수행해야 할 명령어를 명시적이고 순차적으로 나열하며, 프로그램의 상태를 직접 변경합니다.
- 선언형 프로그래밍 (Declarative Programming): '무엇을(What)' 원하는지 기술하는 데 초점을 맞춥니다. 목표를 선언하면, 그 목표를 달성하는 구체적인 방법은 언어나 시스템이 알아서 처리합니다.
이 글에서는 명령형 패러다임에 속하는 절차 지향 및 객체 지향 프로그래밍과, 선언형 패러다임에 속하는 함수형 프로그래밍이라는 세 가지 핵심 패러다임을 살펴보고, 개발에서 많이 사용하는 React가 객체 지향과 어떻게 연결되는지 살펴보겠습니다.
1. 절차 지향 프로그래밍 (Procedural Programming)
절차 지향 프로그래밍은 가장 직관적인 방식입니다. 이름에서 알 수 있듯이 프로그램의 실행 흐름을 절차(Procedure)의 순차적인 호출로 간주합니다. 이 절차는 자바스크립트의 함수와 동일한 개념으로 볼 수 있습니다.
가장 큰 특징은 데이터와 이 데이터를 처리하는 절차(함수)를 분리하는 것입니다. 요리를 예시로 들면 1. 재료 손질(데이터) - 2. 볶기(절차) - 3. 담기(절차)의 순서로 코드가 진행됩니다. 데이터는 함수를 통해 인자(Argument)로 전달되거나, 때로는 프로그램 전역에서 접근 가능한 전역 변수(Global Variable)로 관리됩니다.
// 절차 지향 스타일 예시
let total = 0; // 데이터 (전역 상태)
let numbers = [1, 2, 3, 4, 5];
// 절차 1: 모든 숫자를 더함
function addAll() {
for (let i = 0; i < numbers.length; i++) {
total = total + numbers[i]; // 전역 상태를 직접 수정
}
}
// 절차 2: 총합을 출력함
function printTotal() {
console.log(total);
}
// 정의된 절차(순서)대로 실행
addAll();
printTotal(); // 15
이 방식은 프로그램의 흐름이 명확히 보이기 때문에 간단한 작업에는 효율적입니다. 하지만 프로그램의 규모가 커지고 복잡해질수록, total 같은 전역 데이터를 여러 함수가 동시에 참조하고 수정하게 되면서 심각한 문제가 발생합니다. 데이터의 흐름을 추적하기 어려워지고, 한 함수의 수정이 예상치 못한 다른 함수에 영향을 미치는 부수 효과(Side Effect)를 야기하여 코드의 유지보수성과 확장성을 급격히 저하시킵니다.
2. 객체 지향 프로그래밍 (OOP, Object-Oriented Programming)
객체 지향 프로그래밍은 절차 지향 프로그래밍이 직면한 복잡성 관리의 한계를 극복하기 위해 등장했습니다. 이 패러다임은 흩어져 있던 데이터와 이 데이터를 처리하는 함수(기능)를 하나의 논리적 단위인 객체(Object)로 묶어서 관리합니다.
핵심은 객체입니다. 객체는 자신만의 고유한 속성(Property, 데이터)과 이 속성을 다루는 메서드(Method, 기능)를 가집니다. 예를 들어 '사람' 객체는 '이름', '나이'라는 속성(데이터)을 가지며, '말하기()', '걷기()'라는 메서드(기능)를 스스로 수행할 수 있습니다.
OOP는 다음과 같은 특징을 가지고 있습니다.
- 캡슐화 (Encapsulation): 관련 있는 데이터와 메서드를 하나의 객체로 묶고, 객체 외부에서 내부 데이터에 직접 접근하는 것을 제한합니다 (private, 자바스크립트의 클로저) 이를 통해 데이터의 무결성을 보장하고 내부 구현을 숨길 수 있습니다.
- 상속 (Inheritance): 상위 클래스(부모)의 속성과 메서드를 하위 클래스(자식)가 물려받아 사용할 수 있게 합니다. 코드를 재사용하고 계층 구조를 만들 수 있습니다.
- 다형성 (Polymorphism): 동일한 이름의 메서드 호출(shape.draw())이라도, 해당 객체의 실제 타입(원, 사각형)에 따라 각기 다른 동작을 수행할 수 있게 하여 유연하고 확장 가능한 설계를 지원합니다.
// 객체 지향 스타일 예시 (ES6 Class)
class Calculator {
// '설계도(Class)'
constructor(numbers) {
this.numbers = numbers; // 데이터(속성)를 객체 내부에 캡슐화
this.total = 0;
}
// 기능(메서드)
addAll() {
this.total = this.numbers.reduce((acc, val) => acc + val, 0);
}
printTotal() {
console.log(this.total);
}
}
// '설계도'를 바탕으로 '실물(Instance)' 객체를 생성
const calc = new Calculator([1, 2, 3, 4, 5]);
// 객체가 스스로 자신의 기능을 실행 (메서드 호출)
calc.addAll();
calc.printTotal(); // 15
이 코드에서 total이라는 데이터는 Calculator 클래스에서 생성된 calc 인스턴스(객체) 내부에 캡슐화되었습니다. 절차 지향 예시와 달리 전역 스코프를 오염시키지 않으며, 객체 스스로가 자신의 상태를 관리하므로 코드의 안정성과 관리 용이성이 크게 향상됩니다.
3. 함수형 프로그래밍 (FP, Functional Programming)
함수형 프로그래밍은 복잡한 상태 관리가 필요한 프론트엔드 개발에서 큰 주목을 받고 있는 패러다임입니다. OOP가 상태를 가지는 객체를 중심으로 생각했다면, FP는 수학적 함수에 기반하여 순수 함수(Pure Function)를 사용하는 방식으로 문제를 해결하려 합니다.
FP의 핵심 철학은 부수 효과(Side Effect)를 최소화하고 데이터의 불변성(Immutability)을 유지하는 것입니다.
- 순수 함수 (Pure Function): 같은 입력이 주어지면 항상 같은 출력을 반환하며, 함수 외부의 어떤 상태(전역 변수, 인자로 받은 객체의 원본 등)도 변경하지 않는 함수입니다.
- 불변성 (Immutability): 데이터는 한번 생성되면 변경되지 않아야 합니다. 데이터 수정이 필요할 경우, 원본을 직접 수정하는(Mutation) 대신, 원본의 복사본을 만들고 변경 사항을 적용한 새로운 데이터를 반환합니다.
// 함수형 프로그래밍 스타일 예시
const numbers = [1, 2, 3, 4, 5];
// 순수 함수
// 1. 동일 입력 -> 동일 출력
// 2. 부수 효과 없음 (외부의 'numbers'나 전역 변수를 건드리지 않음)
function addAll(arr) {
return arr.reduce((acc, val) => acc + val, 0);
}
// 불변성을 지키는 데이터 처리
// numbers.push(6); // (X) 원본 'numbers' 배열을 직접 수정 (부수 효과)
// const newNumbers = [...numbers, 6]; // (O) 전개 연산자를 사용해 새 배열을 생성
const total = addAll(numbers);
console.log(total); // 15
이 방식은 데이터의 흐름이 명확하고 예측 가능하게 만듭니다. 원본 데이터가 절대 변하지 않기 때문에, 코드의 간결성, 테스트 용이성, 그리고 복잡한 동시성 문제 해결에 큰 이점을 제공합니다.
4. 리액트(React)와 객체 지향 프로그래밍의 관계
그렇다면 프론트엔드 개발자로서 "리액트는 객체 지향인가요?"라는 질문을 할 수 있습니다. 이 질문에 대한 대답은 "과거에는 OOP의 영향을 강하게 받았지만, 현재는 함수형 패러다임을 전면적으로 수용하고 있습니다"입니다.
4.1. 과거의 리액트: 클래스 컴포넌트 (OOP)
초기 리액트와 '훅(Hook)'이 도입되기 전까지만 해도, 컴포넌트(특히 상태(state)나 생명주기(lifecycle)를 가진 컴포넌트)를 만드는 표준 방식은 '클래스 컴포넌트'였습니다.
// 리액트의 클래스 컴포넌트 (OOP 스타일)
import React from 'react';
class Counter extends React.Component {
constructor(props) {
super(props); // 부모 클래스(React.Component)의 생성자 호출
this.state = { count: 0 }; // 'state'라는 데이터를 캡슐화
}
// 컴포넌트 객체의 메서드
increment() {
this.setState({ count: this.state.count + 1 });
}
// render 메서드
render() {
return (
<div>
<p>Count: {this.state.count}</p>
<button onClick={() => this.increment()}>Click me</button>
</div>
);
}
}
위 코드는 OOP의 특징을 명확하게 보여줍니다. React.Component라는 부모 클래스를 상속(Inheritance)받고, constructor에서 this.state라는 속성(데이터)을 초기화하며 캡슐화합니다. 또한 increment나 render 같은 메서드(기능)를 가진 전형적인 OOP 스타일입니다.
4.2. 현재의 리액트: 함수형 컴포넌트와 훅 (FP)
하지만 Hook이 도입된 이후, 리액트 생태계는 함수형 컴포넌트로 완전히 전환되었습니다.
// 리액트의 함수형 컴포넌트 (FP 스타일)
import React, { useState } from 'react';
function Counter() {
// useState 훅을 사용해 상태([값, 설정함수])를 선언
const [count, setCount] = useState(0);
// 컴포넌트 자체가 '함수'
// props(입력)를 받아 UI(JSX)(출력)를 반환
return (
<div>
<p>Count: {count}</p>
{/* 상태를 변경할 때, count 변수를 직접 수정하는 대신(count++),
새로운 상태 값을 반환하는 함수(setCount)를 사용합니다.
이는 데이터의 불변성을 따르는 함수형 스타일입니다.
*/}
<button onClick={() => setCount(count + 1)}>Click me</button>
</div>
);
}
Counter는 이제 클래스가 아닌 그 자체로 하나의 함수입니다. props라는 입력을 받아 UI(JSX)라는 출력을 반환하는, 순수 함수에 가까운 형태가 되었습니다. 상태 관리는 useState라는 함수(훅)를 통해 이루어지며, setCount를 호출할 때 count 변수 자체를 직접 수정하는 것이 아니라 새로운 값으로 상태를 교체합니다. 이는 함수형 프로그래밍의 순수 함수와 불변성을 지키고 있습니다.
리액트가 이처럼 OOP(클래스 컴포넌트)에서 FP(함수형 컴포넌트)로 전환한 이유는 컴포넌트 로직의 재사용이 훅을 통해 더 쉬워졌고, this 키워드에서 오는 혼란이 사라졌으며, 불변성을 기반으로 한 데이터의 흐름이 코드를 더 단순하고 예측 가능하게 만들었기 때문입니다.
'📚 CS > JavaScript' 카테고리의 다른 글
| [JavaScript] CJS와 ESM으로 자바스크립트 모듈 시스템 이해하기 (0) | 2025.11.12 |
|---|---|
| [JavaScript] 함수 (Function) (0) | 2025.11.11 |
| [JavaScript] 객체, 속성, 메서드, 클래스, 네임스페이스란? (1) | 2025.11.05 |
| [JavaScript] 브라우저 렌더링 파이프라인 (0) | 2025.10.15 |
| [JavaScript] Promise와 async / await (0) | 2025.09.08 |