조금 오래되었지만, 일전에 프로그램의 구조적 방법론에 대해 작성한 적이 있다. 이번에는 프로그래밍을 작성하는 스타일의 관점에 대한 정리를 해본다. 이번 주제는 명령형 프로그래밍 vs 선언형 프로그래밍 이다.
코드는 실행될 때에는 기계어로 번역되어 명령형처럼 수행된다. 하지만 이 기계어는 프로그래머가 읽고 쓰기에는 매우 어렵다. 때문에 우리는 사람이 이해할 수 있는 방식과 언어로 프로그램을 작성하고 이를 기계어로 번역하여 실행한다. 여기서 사람이 작성하는 프로그램을 어떻게 생각하고 풀어내어 작성하는가에 대한 2가지 방법에 대해 정리해 본다.

명령형 프로그래밍(Imperative Programming)

프로그램의 상태를 변경시키는 것을 구문 관점에서 써내려가는 프로그래밍을 의미힌다. 이를 위한 표현식은 매우 간단하며, 사람이 읽기에도 매우 편하다.

fun sumAll(arr) {
  const len = arr.length;
  let res = 0;
  for (let i=0; i<len; i++) {
    res += arr[i]
  }
  return res;
}

위의 예제는 전형적인 명령형 스타일의 프로그램 코드이다. 함수 sumAll에서는 arr의 길이만큼 반복(for 루프) 하도록 한다. 매 반복마다 누적 변수 res에 element를 더하도록 한다. 여기서 정의된 상태는 현재 반복의 지점누적변수 res의 값으로 볼 수 있다. 이 프로그램은 구체적으로 어떻게 하도록 정의한다.

선언형 프로그래밍(Declarative Programming)

목적(무엇을 해야하는지)에 대해 기술하고 이를 실행시킬 수 있도록 써내려가는 프로그래밍을 의미한다. 이 목적을 위해 함수의 흐름을 정의하며,

fun sumAll(arr) {
  return arr.reduce((a, b) => a + b, 0)
}

위의 예제는 명령형 프로그램 코드와 같은 역할을 수행하지만, 선언형 스타일로 작성되어 있다.

함수 sumArr는 arr에 reduce 연산을 적용하여 모든 값을 합한 결과값을 반환한다. reduce는 배열의 각 요소에 주어진 함수를 적용하여 하나의 결과값을 만들어내는 고차 함수이다. reduce의 콜백 함수는 두 개의 인자를 더한 값을 반환하며, 초기값은 0이다.

이 함수를 설명하기 위해서만 명령형에 비해 많은 내용이 필요하다. 하지만 선언형 프로그래밍에서는 추상화된 사고과정이 필요하다. 선언형은 결과를 어떻게 만들 것인가이 아닌, 어떤 결과를 만들어야 하는가에서 작성하는 프로그래밍 기법이기 때문이다. 즉 과정 보다는 목적을 의식하여야 한다.

  • 명령형에서는 모든 값을 합한 결과를 만들기 위해 for 루프를 이용한 덧셈 연산, 즉 과정에 초점이 맞추어져 있다.
  • 선언형에서는 모든 값을 합한다 라는 목적을 맞추기 위해 고차 함수를 사용한다.

번외. 명령형은 선언형으로 만들 수 있을까?

가능하다. 우리가 작성한 코드들은 기계가 이해할 수 있는 언어로 변환된 후 명령형으로서 작동한다. 여기서 쓰고 있는 명령형, 선언형은 프로그래머가 프로그램을 작성하는 방법론에 대한 부분이기 때문이다.

아래의 선언형 프로그래밍의 예제를 보자

Array.prototype.reduce(callback, initValue) {
  // 실제로는 native code가 들어가 있음
  const len = arr.length;
  let res = 0;
  for (let i=0; i<len; i++) {
    res += arr[i]
  }
  return res;
}
fun sumAll(arr) {
  return arr.reduce((a, b) => a + b, 0)
}

sumAll([1,2,3,4,5])

잘 보면 Array의 프로토타입으로 reduce가 명령형으로 작성되어 있다. 그리고 sumAll은 선언형으로 작성되어 있다. 이 프로그래밍은 sumAll을 호출하는데, 인자는 1~5까지의 숫자가 들어있는 Array이다. 명령형과 선언형이 섞여 있는데 왜 선언형이라고 이야기 할 수 있을까?
호출하는 입장에서 보았을 때, 우리는 sumAll을 정의하고, 그 안에서 선언형으로 작성하였다. 그리고 명령형으로 작성된 reduce의 모든 내용을 Array.prototype.reduce로 추상화해두었다. 결국 sumAll에서는 배열의 모든 인자의 합을 계산해줘 라는 목적만이 명시되어 있고 그에 따라서 작성되어 있고, 다른 부분들은 추상화되어 외부에서는 보이지 않는다. 그리하여 이 코드는 선언형으로 불 수 있다.

프로그램 패러다임에 대한 토막글

함수형 vs 객체지향형
명령형 vs 선언형
동기 vs 비동기 vs 반응형