프로그래밍 패러다임(PP,OOP,FP,RP) 간략 정리

승톨·2021년 7월 11일
4
post-thumbnail

들어가며

  • 회사에 입사 후 프로그래밍 패러다임에 대해 간단히 조사해보라는 과제를 받았다.
  • 평소에 들어보았던 함수형 프로그래밍, 반응형 프로그래밍에 대해 간단히 알아볼 수 있어서 좋았는데, 취업 준비하시는 분들에게는 면접에서 쓰일 수 있을 것 같아서 간단히 정리해본다.

명령형 프로그래밍

  • 정의:
    - 프로그래밍의 상태와 상태를 변경시키는 구문의 관점에서 연산을 설명하는 프로그래밍 패러다임
    - 현대의 대부분 컴퓨터가 머신코드를 실행하게 설계되었기 때문에 코드는 명령형으로 쓰여진게 대부분이다.
    - low level에서는 프로그램 상태가 메모리의 내용으로 정의되고 구문들은 기계어를 순차적으로 나열해놓았다고 볼 수 있다.
    - High level에서는 변수,반복문 같은 개념이 들어가게 된다.
    - 쉽게 말하면 컴퓨터가 수행할 명령들을 순차적으로 실행시키기 위해 써놓은 것
    - 현실세계에서는 요리의 레시피와 제일 비슷하다고 생각,
    - 레시피의 단계 별로 지시 사항들이 있고, 지시사항을 수행할 때마다 요리의 상태가 변경된다.
    - 조건문, 할당문, 반복문 등이 프로그래밍 예시이다.

    참고 : https://en.wikipedia.org/wiki/Imperative_programming

절차적 프로그래밍(PP)

  • 명령형 프로그래밍의 일종으로서 프로시저 호출을 바탕으로 한다.
    - 다시 풀어보자면, 특정 작업을 수행하기 위한 프로그램을 작성할 때, "프로시저", "루틴"으로 구성이 되게 프로그래밍하는 것을 절차적 프로그래밍이라고 한다.

    여기서 프로시저란?

    • 프로시저는 루틴이나 서브루틴의 원형으로 연속된 computational 스텝으로 구성되어 있다.
      • 일반적으로는 명령문을 절차적으로 기술해 놓은 것이라고 보면 될 것 같다.
      • 함수랑 비교를 해본다면, 함수는 프로시저의 각 절차를 수행하기 위해 필요한 기능이라고 볼 수 있겠다.
        • 프로시저는 특정 작업을 수행하고, 함수는 특정 계산을 수행한다.(인풋을 넣으면 한개의 아웃풋이 나오는)
        • 프로시저는 리턴값을 여러개 가질 수 있고, 함수는 리턴 값을 하나만 가질 수 있다.
        • 참고 링크 : https://en.wikipedia.org/wiki/Imperative_programming
    • 참고로, 서브루틴이란 일반적으로 아무것도 accept하지 않고 아무것도 리턴하지 않는 function을 의미한다.
      대부분의 프로그래밍 언어에서 서브루틴과 function을 명확히 구분하지는 않는다고한다.
      • 서브루틴은 function이라고 불리긴하는데, function은 subroutine이라 무조건 부를 수 있는건 아니다.
      • 루틴이랑은 무슨 차이가 있나 싶어서 알아보았는데, 루틴 안에서 하위개념으로 동작하는게 서브루틴이라고 볼 수 있겠다.
      • 루틴이 메인 프로그램이라면, 그 안에서 돌아가는 function이 서브루틴이 될 수 있다.
    • 프로시저 호출은 프로그램의 어느 시점이던 간에 가능하다. (자기 자신 혹은 타 프로시저를 호출할 수 있다.)
  • 참고 링크 :
  1. https://stackoverflow.com/questions/721090/what-is-the-difference-between-a-function-and-a-procedure
  2. https://stackoverflow.com/questions/45871261/are-subroutine-and-routine-the-same-concept
  3. https://www.ibm.com/docs/en/cics-ts/5.4?topic=functions-subroutines
  • 절차적 프로그래밍은 modularity라는 특성이 있다.
    • 프로그램이 크고 복잡해질수록 작성해야되는 명령의 양은 많아지는데, 동일한 명령을 여러번 사용해야 하는 경우 코드 작성의 비효율성이 증가한다.
    • 비효율적인 코드를 프로시저를 이용해 모듈화하면 같은 코드를 재활용할 수 있고, 프로그램 흐름을 쉽게 파악하고 분업이 용이해진다.
  • 큰 기능을 만들기 위해 작은 단위의 기능으로 만들어서 처리하는 top-down 방식이라고 볼 수 있다.

→ 개괄적으로 이 글 보면 도움이 된다.

객체 지향 프로그래밍(OOP)

  • 절차적 프로그래밍 관점에서는 프로그램이 데이터와 프로시져로 나뉘게 된다.
  • 그러나 프로그램이 조금만 복잡해지면, 흐름 파악이 힘들어지고 유지보수가 힘들어진다.
    • 모듈화를 통해서 해소할 수 있긴하지만, 네임스페이스 포화 문제 같이 본질적으로 데이터나 상태를 구조화 하지는 못하기 때문에, 객체지향 프로그래밍이 대안으로 등장한다.
    • 작은, 독립적인 단위 중심으로 만든 객체의 상호작용으로 문제를 해결하는 Bottom-Up 방식이다.
  • 따라서 객체지향 프로그래밍은 객체라고 하는, 상태와 행위를 가지고 있는 독립적인 단위 중심으로 객체끼리 상호작용하게 프로그래밍 방법론이다.

객체지향에는 4가지 개념이 존재한다.

  1. 추상화
  • 공통의 속성이나 기능을 묶어 그룹핑하는 것이라고 할 수 있습니다.
  • 객체 지향에서 클래스를 정의하는 것이 추상화 개념입니다.
  1. 다형성
  • 서로 다른 객체를 하나의 추상화 된 클래스나 메소드로 처리해서, 하나의 클래스나 메소드가 다양한 방식으로 동작이 가능한 것을 의미합니다.
  • 객체의 재사용성을 높여줄 수 있습니다.
  • 여기서 오버로딩과 오버라이딩이라는 개념이 나오는데,
    • 오버로딩은 메소드의 이름은 같으나 메소드의 매개변수 유형이나 개수를 바꿔서 다르게 사용하는것을 의미합니다.
    • 오버라이딩은 상위 클래스의 메소드를 하위 클래스가 재정의해서 사용하는 것을 의미합니다.
  1. 상속
  • 상속이란 클래스를 재사용 하는 것을 의미한다.
    • 상위 클래스를 상속 받게 되면 상위 클래스의 멤버 변수나 메소드를 그대로 사용할 수 있다.
    • 코드를 재활용할 수 있고, 유지보수하기 좋다는 장점이 있다.
  1. 캡슐화
  • 내부의 자세한 구현 방법은 객체 안으로 숨기고 사용자에게는 메소드만 노출하고 있는 개념이다. 은닉화라고도 한다.
    • 비슷한 역할을 하는 속성과 메소드들을 하나로 모으는 것이기도 하다. 추상화와 비슷하기도 한데, 추상화는 일반화를 시키는 것이고, 캡슐화는 보여주고 싶은 것만 보이고 나머지는 감추는 것이라고 할 수 있다.

다들 한번쯤 생각해봤듯이, 절차적 프로그래밍과 객체지향 프로그래밍은 무엇이 다른지 고민해보았다.

  • 보통 구글링해보면 2개를 비교하는 글들이 있는데, 2개는 대척점에 있지않다. 결국 명령형 프로그래밍의 범주에 포함되어있는 관계이기 때문에 직접적 비교를 할 필요가 없다.
  • 두 방법 모두 절차가 존재하지만, OOP는 객체의 디자인을 먼저 한뒤 데이터 플로우 → 진행 시나리오를 설계해나가는 식의 개발 방법론이라고 볼 수 있다.
    • 절차적 프로그래밍은 프로그램의 순서와 흐름을 먼저 세우고 그 후 필요한 데이터와 함수를 설계하는 식이다.

참고 :

선언형 프로그래밍

  • 명령형 프로그래밍은 프로그램을 짤 때 어떤 방법으로 해야 하는지를 나타내지만, 선언형 프로그래밍은 내가 무슨 프로그램을 짜야하는지를 설명하는 방법이다.

    • 예를 들어 HTML은 선언형 언어인데, HTML을 이용해 나타낸 웹페이지는 제목, 글꼴, 본문, 그림과 같이 "무엇"이 나타나야하는지를 묘사하는 것이지 "어떤 방법으로" 웹페이지를 나타내야 하는지를 묘사하는 것이 아니다.
  • 명령형 프로그래밍은 알고리즘을 명시하고 목표는 명시하지 않는 데 반해 선언형 프로그래밍은 목표를 명시하고 알고리즘을 명시하지 않는다.

  • 선언형 프로그래밍을 이해하다보면, 이런 의문이 들 수 있다. 아래는 선언형 방식의 예시이다.

    // 명령형 방식 (HOW)
    function double (arr) {
      let results = [];
      for (let i = 0; i < arr.length; i++) {
        results.push(arr[i] * 2)
      }
      return results;
    }
    
    // 선언형 방식 (WHAT)
    function double (arr) {
      return arr.map((item) => item * 2)
    }

    이렇게 보면 선언형 방식도 map이란 built in 함수 사용해서 간결하게 짠 방법 아닌가? map도 결국 코드를 까보면 위 방식처럼 되어 있는거면 선언형 방식도 본질적으로 명령형 방식 아닌가? 라는 생각이 들 수 있다.

    • 물론 머신코드 level까지 내려가면 본질적으로 imperative하긴 하지만, 현실적으로 두 방식은 차이가 있다.
      • 따라서 명령형 프로그래밍 언어로 선언형 프로그래밍을 할 수 있지만, 두 방식이 코드를 보는 관점의 차이가 있기 때문에 선언형 프로그래밍은 기존 명령형 프로그래밍과 처음부터 다르게 설계되어야 한다.

함수형 프로그래밍(FP)

  • 함수형 프로그래밍은 데이터 처리를 수학적인 함수 계산으로 취급해서 개별적인 상태, 데이터 보다는 작은 부분 동작(함수)의 집합 구성에 더 집중하는 프로그래밍 방식이다.
    • 즉, 큰 순서 안에서 하나의 요소(ex.변수 i)가 어떻게 움직이는지에 집중하기 보다는 전체 기능을 작은 기능들(함수)로 쪼개서 그 함수들의 움직임에 집중한다고 볼 수 있다.
    • 일반적으로 함수형 프로그래밍은 문제가 되는 함수만 고치면 되기 때문에 디버깅이나 테스트가 용이하다고 알려져있다.
  • 함수의 특징을 따르기 때문에, 함수형 코드의 출력값은 그 함수에 입력된 인수에만 의존한다. 따라서, 인수 x에 같은 값을 넣고 함수 f를 호출하면 항상 f(x)라는 결과가 나온다.

기본적인 컨셉은 아래와 같다.
1. 변경 가능한 상태를 불변상태(Immutable)로 만들어 Side Effect를 없애자.
2. 모든 것은 객체이다.
3. 코드를 간결하게 하고 가독성을 높여 구현할 로직에 집중 시키자.
4. 동시성 작업을 보다 쉽게 안전하게 구현 하자.

  • 거의 모든 것을 순수함수로 쪼개어서 문제를 해결하기 때문에, 명령형 프로그래밍에서 나타나는 부작용이 없다. 프로그램의 동작을 이해하고 예측하기가 쉬어져 가독성을 높이고 유지보수를 용이하게 해준다.
  • 명령형 프로그래밍의 함수는 프로그램의 상태의 값을 바꿀 수 있는 부작용이 생길 수 있다. 같은 코드라도 실행되는 프로그램의 상태에 따라 다른 결과값을 낼 수 있다.
  • 순수함수란, Memory 혹은 I/O의 관점에서 Side Effect가 없는 함수, 함수의 실행이 외부에 영향을 끼치지 않는 함수이다.
  • 동일한 실행에 대해 동일한 결과를 반환하기 때문에, 병렬 처리 환경에서 개발할 때 Race Condition에 대한 비용을 줄여준다는 특징이 있다.
  • 그러나, 함수형 프로그래밍이 마법의 가루는 아니다. 엔지니어링은 시간과 비용을 반드시 고려할 수 밖에 없는데, 숙련자가 아니라면, 함수형 프로그래밍은 명령형 프로그래밍보다 가성비가 안 나올 것이다.(물론 OOP도 마찬가지일 수 있지만..)
  • 하스켈 같이 순수 함수형 프로그래밍 언어는 제약도 많기 때문에 Java같은 명령형 프로그래밍 언어로 함수형 프로그래밍을 하는 것에 비해 사용하기가 어렵다.

참고
- https://medium.com/@lazysoul/함수형-프로그래밍이란-d881230f2a5e
- https://sungjk.github.io/2017/07/17/fp.html
- https://codechaser.tistory.com/81
- https://easywritten.com/post/real-advantages-of-functional-programming/

반응형 프로그래밍(RP)

  • 데이터를 중심으로 프로그래밍하는 방식이다.

  • OOP도 데이터를 중심으로 프로그래밍하긴 하지만, 반응형 프로그래밍은 데이터의 흐름 추적을 중점적으로 두는 프로그래밍 방식이다.

  • 함수형 프로그래밍에 기반을 두고 있는데, 함수형 프로그래밍이 가진 '불변상태'를 기초로 둔다.

    • 반응형 프로그래밍에서는 변수의 값을 바꾸면 해당 변수를 참조하는 모든 식들이 연쇄적으로 재평가되면서 스스로의 값을 갱신한다. 프로그래머가 명시적으로 재계산 명령을 내리지 않는다.

    • 구체적으로는 모든 데이터를 "스트림"으로 보는데, 여기서 스트림이란 시간순으로 발생하는 이벤트의 나열이다.스트림은 value, error, complete 의 각 시그널을 발생시킬 수 있다.

    • 기본 베이스는 Observer Pattern이다. 하나의 데이터 스트림을 감시(구독)하는 대상이 있다면, 데이터 스트림의 변화가 발생할 경우 변화 전파가 일어나는데 감시하는 대상은 이를 감지하고 필요한 작업을 하게 된다.

      즉, 데이터가 변경될 때마다 관련된 로직을 일일히 호출하는 것이 아니라, 데이터 스트림이 존재하고 이를구독하는 곳에서 변화에 따라 알아서 처리하는 하겠다는 것이다.

      a = 10;
      b = 20;
      c = a + b;
      // c: 30
      a = 20;
      // c: 30

      일반적인 프로그래밍의 경우 a값을 변경해도 c의 결과는 그대로 있다.반응형의 경우 조금 다르다.

      a = 10;
      b = 20;
      c = a + b;
      // c: 30
      a = 20;
      // c: 40

      반응형의 경우 모든 데이터를 스트림으로 보고 스트림의 데이터가 변화되면 이를 전파하여 해당 데이터 스트림을 구독하고 있는 곳도 영향을 받는다.

마치며

이 글에 정리한 내용은 입문 수준이기 때문에 대략 패러다임이 어떤 것인지는 이해했을 것이다. 필요한 내용은 추가로 찾아보길 권한다.

profile
소프트웨어 엔지니어링을 연마하고자 합니다.

0개의 댓글