Imperative vs Declarative Programming

박세영·2022년 5월 13일
0

프로그래머스에서 과제형 테스트를 연습하다 다음과 같은 요구사항을 만났다.

"가급적 컴포넌트 형태로 추상화 하세요."

해설에서는 이 요구사항이 DOM에 접근하는 부분을 최소화하고, 명령형 프로그래밍보다 선언적으로 프로그래밍 방식으로 접근하라는 의미를 지닌다고 설명했다.

명령형 프로그래밍과 선언적 프로그래밍이라는 말은 들어봤지만 그 의미를 명확하게 파악하고 있지는 않았기 때문에 한 번 정리 해봤다.


명령형 프로그래밍과 선언적 프로그래밍을 쉽게 요약하자면 아래와 같다.

imperative/명령형/절차적 프로그래밍은 당신이 어떤 일을 어떻게 할 것인가에 대한 것이다.
declarative/선언적 프로그래밍은 당신이 무엇을 할 것인가에 대한 것이다.

조금 더 쉽게 이해하기 위해 실생활에서의 예시를 들어보자.

  • 명령형 접근(HOW): "주차장 북쪽 출구로 나와서 좌회전을 해. 12번가 출구에 도착할 때까지 I-15 북쪽 도로를 타고 와야 해. 거기서 IKEA에 가는 것처럼 출구에서 우회전을 해. 그리고 거기서 직진하다가 첫 번째 신호등에서 우회전을 해. 그 다음에 나오는 신호등을 통과한 후에 좌회전을 하면 돼. 우리 집은 #298 이야."
  • 선언형 접근(WHAT): "우리 집 주소는 298 West Immutable Alley, Eden, Utah 84310 이야."

프로그래머라면 자연스럽게 명령형 접근을 하게된다. 그래서 이 예시를 들었을 때 다음과 같은 의문이 들 수 있다.

"주소는 알겠는데 그 주소로 가는 방법은 어떻게 알지?"

이에 대한 대답은, 명령형 방식으로 접근하기 이전에 먼저 선언적 방식으로 추상화를 해야하는 것이다. 이 말을 다음 문장으로 다시 정리할 수 있다.

많은 선언적(Declarative)접근 방식들의 기반에는 일종의 명령적(Imperative) 추상화가 존재한다.


이제 프로그래밍의 영역으로 넘어가며 선언적 프로그래밍 언어명령적 프로그래밍 언어의 종류를 살펴보자

  • 명령적(절차적) 언어: C, C++, Java
  • 선언적 언어: SQL, HTML
  • (Can be)Mix: JS, C#, Python

대표적인 선언적 프로그래밍 언어인 SQL, HTML의 코드는 아래와 같다.

  • SQL

SELECT * FROM USERS WHERE country='Mexico';

  • HTML
<article>
  <header>
    <h1>Declarative Programming</h1>
    <p>Sprinkle Declarative in your verbiage to sound smart</p>
  </header>
</article>

두 코드 모두 선언형 프로그래밍이며, 작업을 어떻게 수행하는가 보다는 무엇을 수행하는가에 더 초점이 맞추어져 있다. 즉 얻고자 하는 것에 대해 선언할 뿐, 수행 방식에 대한 부분은 추상화 되어있다.


이제 자바스크립트를 활용한 프로그래밍 예제로 들어가보자.

만약 기술면접에서 다음과 같은 과제를 만난다면 어떻게 풀 것인가?

  1. 숫자 배열을 받아서, 해당 배열의 모든 원소들을 두 배 시킨 새로운 배열을 리턴하는 'double'이라는 이름의 함수를 작성하세요
  2. 숫자 배열을 받아서, 해당 배열의 모든 원소들을 더한 값을 리턴하는 'add'라는 함수를 작성하세요.
  3. jQuery나 Vanilla Javascript를 이용해서, btn이라는 id를 가진 엘리먼트에 이벤트 리스너를 달아보세요.
    • 해당 버튼을 클릭했을 때 highlight라는 class를 toggle(add or remove)해야 하고,
    • 엘리먼트의 현재 상태에 따라 버튼의 텍스트를 'Add Highlight'와 'Remove Highlight'로 바꿔야 합니다.

이 문제들을 만약 명령적 프로그래밍으로 작성한 코드는 아래와 같다.

// 1
function double (arr) {
  let results = []
  for (let i = 0; i < arr.length; i++){
    results.push(arr[i] * 2)
  }
  return results
}

// 2
function add (arr) {
  let result = 0
  for (let i = 0; i < arr.length; i++){
    result += arr[i]
  }
  return result
}

// 3
$("#btn").click(function() {
  $(this).toggleClass("highlight")
  $(this).text() === 'Add Highlight'
    ? $(this).text('Remove Highlight')
    : $(this).text('Add Highlight')
})

이 답변의 다음 3가지 특징들은 이 코드를 명령적으로 만든다.

  1. 이 코드들은 어떤 일을 어떻게 처리하는지에 관해 표현하고 있다.
    : 각각의 예시에서, 명시적으로 배열을 반복하거나(for문), 원하는 기능을 수행하기 위한 단계를 명시적으로 나열하고 있다.

  2. 작업을 수행하는 도중 상태(state)를 변경하고 있다.
    : 처음 두 예시에서는 result라는 변수를 선언해 그것을 계속 수정하고 있다.
    마지막 예시에서는 DOM에 접근해 state를 계속 변경하고 있다.

  3. 코드의 가독성이 떨어진다.
    : 이렇게 절차적/명령적 코드들의 특징은 가독성이 떨어진다는 것이다.


이제 선언적 프로그래밍으로 과제를 수행해보자. 선언적 프로그래밍은 바로 앞에서 언급한 3가지 문제들을 모두 해결한다.
즉 무엇이 일어나는지에 관해 묘사하고, 기존 상태(state)를 변경하지 않으며, 가독성이 좋다.

// 1
function double (arr) {
  return arr.map((item) => item * 2)
}

// 2
function add (arr) {
  return arr.reduce((prev, current) => prev + current, 0)
}

// 3
<Btn
  onToggleHighlight={this.handleToggleHighlight}
  highlight={this.state.highlight}>
    {this.state.buttonText}
</Btn>

처음 두 예제에서, 자바스크립트 내장 함수 map(), reduce()를 활용한 점을 주목하자. 이러한 함수들이 JS를 선언적 프로그래밍 언어로 만든다.

우리는 map()내장 함수가 내부적으로 어떻게 돌아가는지에 대해 관심이 없다. 그저 map()함수가 무엇을 수행하는지에 더 초점이 맞추어져 있다. 다른 말로 명령적/절차적 수행이 추상화 되어있다.

또한, map() 함수는 아무런 상태를 변경하지 않는다. 모든 변경들은 그 내부에 추상화 되어 있다. 또한 map()함수에 대해 더 익숙하다면 가독성 역시 높아진다.

마지막 예제는 React문법을 사용했다. 중요한 점은, React의 문법이 명령형 코드의 세 가지 문제점을 모두 해결했다는 것이다. 위의 Btn 컴포넌트를 살펴보면 이 UI가 어떻게 보여질지 한눈에 알아볼 수 있다. React의 또 다른 강점은 state가 DOM이 아닌, 리액트 컴포넌트 안에 존재한다는 점이다.

리액트와 같은 프론트앤드 라이브러리/프레임워크의 가장 큰 강점은 선언적 방식으로 UI를 작성할 수 있다는 점이다.

[ 출처: https://ui.dev/imperative-vs-declarative-programming ]

0개의 댓글