Post

절차, 객체지향, 함수형 프로그래밍

프로그래밍 패러다임

간단하게 프로그래밍 스타일을 의미하며 전체적인 프로그래밍 방식을 말한다.

프로그램은 순차, 분기, 반복, 참조로 구성되어지며 프로그램 개발을 위하여 전략을 수립해야 한다.

위에서 말한 전략은 어떤 언어를 사용할지, 프로그래밍에서 어떤 것을 지양하고, 지양할지 등등 다양한 방법을 수립하게 된다. 여기서 말한 전략에 해당하는 내용들이 프로그래밍 패러다임이다.

대표적인 프로그래밍 패러다임에는 절차적, 객체지향, 함수형 프로그래밍이 존재한다.


image




명령형, 선언형 프로그래밍

명령형 프로그래밍

무엇(What)을 할 것인지 나타내기보다 어떻게(How)할 것인지 설명하는 방식


예를 들어서 마트에 가서 당근을 1개 산다면 명령형 프로그래밍

  1. 두 블록을 지나 신호등을 건너 마트에 간다.
  2. 가장 안쪽으로 들어가 야채코너에서 당근을 찾는다.
  3. 입구쪽으로 돌아와 계산대에서 당근을 구매한다


  • 예시: 배열에서 ‘banana’ 라는 문자열을 포함하는 요소들을 추출
1
2
3
4
5
6
7
8
9
10
11
// 명령형 프로그래밍

const arr = ["yellow banana", "red apple", "black banana"];
function getBananas() {
  const bananas = [];
  for (let i = 0; i < arr.length; i++) {
    if (arr[i].includes("banana")) bananas.push(arr[i]);
  }
  return bananas;
}
getBananas(); // ["yellow banana", "black banana"]

명령형 프로그래밍으로 작성한 코드는 개발자들에게 친숙한 if, for 등의 예약어들을 사용한다.

for 문을 사용하기 때문에 매 반복문마다 제어하기가 쉽다(ex: 반복문 중간에 중단). 또한 어떤 코드에서는 선언형 프로그래밍에서 사용하는 함수들보다 속도가 빠르기도 하다.

하지만 코드의 길이가 길어져서 가독성이 떨어진다. for 문에서 변수를 초기화하고 조건을 명시적으로 작성해야하기 때문에 잘못된 코드 작성으로 버그가 발생할 가능성이 높아진다.



선언형 프로그래밍

어떻게(How)할 것인지 나타내기보다 무엇(What)을 할 것인지 설명하는 방식


예를 들어서 마트에 가서 당근을 1개 산다면 선언형 프로그래밍

  1. 마트에 간다.
  2. 당근을 구매한다.


  • 예시: 배열에서 ‘banana’ 라는 문자열을 포함하는 요소들을 추출
1
2
3
4
5
// 선언형 프로그래밍

const arr = ["yellow banana", "red apple", "black banana"];
const getBananas = (array) => array.filter((el) => el.includes("banana"));
getBananas(arr); // ["yellow banana", "black banana"]

선언형 프로그래밍으로 작성한 코드는 코드 길이가 짧아서 함수가 무엇을 하려는지 빠르게 파악할 수 있다.

하지만 배열의 반복 중 섬세한 제어가 힘들고, 어떤 함수는 속도가 느리다는 단점이 있다.




절차적 프로그래밍

절차지향(Procedure Oriented) 프로그래밍은 프로시저 콜, 즉 함수 호출을 중심으로 프로그래밍을 생각하는 것이다.

재사용 가능한 코드들은 별도의 함수로 분리하고 함수 간의 호출로 하고자 하는 일을 수행한다. 이런 프로세스는 주로 “함수”와 “조건문”, “루프문” 을 활용하여 코드를 구성한다.


  • 예시: 덧셈 밸셈
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 두 수를 더하는 절차적 함수
function add(a, b) {
  return a + b;
}

// 두 수를 뺴는 절차적 함수
function subtract(a, b) {
  return a - b;
}

const num1 = 10;
const num2 = 5;

const result1 = add(num1, num2);
const result2 = subtract(num1, num2);

console.log("덧셈 결과:", result1);
console.log("뺄셈 결과:", result2);


절차지향 코드는 이렇게 전체 로직을 작은 함수 단위로 나누고 프로그래밍 메인 로직이 시작되는 곳부터 하위 로직이 실행되는 곳 까지 TOP -> DOWN 식으로 구성되곤 한다.


장점

  • TOP -> DOWN 식이고, 함수라는 작은 단위로 나눠져 있기 때문에 일반적으로 이해하기 쉽다.

  • 컴퓨터와 처리 구조가 비슷하여 실행속도가 빠르다.


단점

  • 각각의 코드가 순서에 민감하게 연결 되어있어, 기능 확장이 필요할 때 유지보수 및 분석이 어렵다.

  • 데이터와 함수가 분리되어 있기에 함수가 많아질수록 데이터의 변경 사항을 추적하기도 어렵다.


따라서 절차지향은 프로그램이 수행하는 알고리즘이 명확하고, 기능 확장 등이 자주 일어나지 않는 상황에서 사용하기에 좋다.




객체 지향 프로그래밍

객체 지향(Object Oriented) 프로그래밍객체라고 하는 단위에 책임을 명확히 하고 서로 협력하도록 프로그래밍을 하는 패러다임이다.

모든 것을 객체로 나누어 생각하고, 필요할 때 객체들을 활용하고 서로 협력하여 일을 수행한다.

절차지향과 다르게 객체는 데이터와 함수(메서드)를 함께 가지고 있다. 객체 내부의 데이터는 외부에 공개할 필요가 없거나 해서는 안 되는 데이터라면 모두 자신 내부에 숨겨 외부에서 알지 못하도록 한다.


  • 예시: 덧셈 밸셈
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
class Calculator {
  constructor() {
    this.result = 0;
  }

  add(a, b) {
    this.result = a + b;
  }

  subtract(a, b) {
    this.result = a - b;
  }

  getResult() {
    return this.result;
  }
}

const calculator = new Calculator();

calculator.add(10, 5);
console.log("덧셈 결과:", calculator.getResult());

calculator.subtract(10, 5);
console.log("뺄셈 결과:", calculator.getResult());

코드는 조금 더 복잡해졌지만, 객체 지향은 기능을 확장할 때 효과적이다.

객체의 강력한 기능인 상속을 이용하면 한 번 정의해놓은 메서드를 파생 클래스에서 재사용 가능하다. 또한 상속으로 객체간의 계층 구조를 만들고 데이터와 메서드를 재사용할 수 있다.

객체 지향의 가장 큰 특징은 같은 역할을 하는 객체를 쉽게 바꾸도록 설계할 수 있다.

객체지향 프로그래밍의 주요 특징 4가지인 캡슐화, 추상화, 상속, 다형성을 구체적으로 알아보자.


객체지향의 특징

추상화

  • 객체들의 공통적인 속성이나 기능을 묶는 것이다.

  • 실제로 존재하는 객체들을 프로그램으로 만들기 위한 공통적인 특성을 파악해서 필요없는 특성을 제거하는 과정

  • 예를 들어서 데스크탑, 랩탑, 제조사를 가리지 않고 모든 컴퓨터는 CPU, RAM, SSD를 가지고 있다. 이처럼 종류를 가리지 않고 모든 컴퓨터가 공통적으로 가지고 있는 속성을 묶는 개념을 추상화라고 한다.


캡슐화

  • 추상화를 통해 객체를 정의했다면, 객체에 필요한 데이터나 메서드를 책임이 있는 객체에 그룹화 시키는 것을 캡슐화라 한다.

  • 관련된 데이터와 알고리즘이 하나의 묶음으로 독립된 단위를 구성하는 것이다.

  • 실제로 구현되는 부분을 외부에 드러나지 않도록 하여 정보를 은닉할 수 있다.

  • 데이터를 외부에 보이지 않고 상호작용 할 때는 메서드를 이용하여 통신한다.


상속성

  • 상위 부모 객체의 속성이나 기능을 하위 객체가 물려받는 것이다.

  • 기존 코드를 재사용하는 것으로 객체지향의 중요한 기능 중 하나이다.


다형성

  • 같은 이름의 메서드가 있어도 매개변수에 따라 각자 다른 역할을 한다. 동일 작업에 같은 이름을 부여하고 코드를 더욱 간결하게 한다.

  • 오버로딩 : 하나의 클래스 내에서 같은 이름의 메서드를 여러 개 가질 수 있지만 메서드의 인자는 다름

  • 오버라이딩 : 상속 관계에 있는 부모 클래스에서 이미 정의된 메서드를 자식 클래스에서 같은 시그니쳐를 갖는 메서드로 다시 정의하는 것


장점

  • 객체지향 특징으로 코드 재사용이 가능하다.

  • 절차지향과 달리 독립된 객체로 이루어져 있기 때문에 유지보수가 용이하다(문제가 되는 부분만 찾아서 고치면 된다).

  • 캡슐화로 정보를 은닉할 수 있기에 보안성이 높다.


단점

  • 캡슐화 격리 구조 때문에 절차형 프로그래밍에 비해서 속도가 느리다.

  • 모든 객체의 역할과 기능을 이해해야 하므로 프로그래밍에 많은 시간이 소요된다.




함수형 프로그래밍

함수형(Functional) 프로그래밍외부 상태를 갖지 않는 함수(순수 함수)들의 연속으로 프로그래밍을 하는 패러다임이다.

외부 상태를 갖지 않는다는 의미는, 같은 입력을 넣었을 때 언제나 같은 출력을 내보낸다는 것이다. 즉 함수의 입/출력에 영향을 주는 외부 요인이 없다.

외부 상태를 가지지 않으려고 할까? 일반적으로 통제하지 못하는 외부 상태를 사용한다면 예측하지 못한 결과(side-effect)를 가질 수 있기 때문이다.

또한, 함수형 프로그래밍 코드에서는 한 번 초기화한 변수는 변하지 않는다. 이런 특성을 불변성이라고 하는데, 이 불변성을 통해 안정성을 얻을 수 있다.


순수 함수
출력이 입력에만 의존하는 것을 의미


  • 예시: 덧셈 밸셈
1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 두 수를 더하는 함수형 프로그래밍 예시
const add = (a, b) => a + b;

// 두 수를 뺴는 함수형 프로그래밍 예시
const subtract = (a, b) => a - b;

const num1 = 10;
const num2 = 5;

const result1 = add(num1, num2);
const result2 = subtract(num1, num2);

console.log("덧셈 결과:", result1);
console.log("뺄셈 결과:", result2);

위의 예시를 보고 절차적 프로그래밍이랑 다른게 뭐냐라고 생각이 들 수 있는데 함수형 프로그래밍은 데이터 불변성과 부작용의 제한을 중요하게 생각하며, 함수를 사용하여 문제를 해결하는 방식을 강조한다.

반면, 절차적 프로그래밍은 명령형 코드와 상태 변경을 중심으로 코드를 작성하는 전통적인 방식이란 것을 기억해야 한다.


장점

  • 상태로 인한 사이드 이펙트가 없기 때문에 안정적


단점

  • 상태를 허용하지 않기에 기존 객체 지향과 같은 기능의 코드를 구현하려면 다양한 함수들을 조합해서 사용 해야함.

  • 친숙하지 않은 설계 방식으로 인해 러닝 커브가 높음.




📑 참고자료

모든 개발자의 실무를 위한 필수 기본기 클래스 자료집

절차지향, 객체지향, 함수형 프로그래밍

[JavaScript] JavaScript 는 어떤 언어인가?

This post is licensed under CC BY 4.0 by the author.