프로그래머스에서 과제형 테스트를 연습하다 다음과 같은 요구사항을 만났다.
"가급적 컴포넌트 형태로 추상화 하세요."
해설에서는 이 요구사항이 DOM에 접근하는 부분을 최소화하고, 명령형 프로그래밍보다 선언적으로 프로그래밍 방식으로 접근하라는 의미를 지닌다고 설명했다.
명령형 프로그래밍과 선언적 프로그래밍이라는 말은 들어봤지만 그 의미를 명확하게 파악하고 있지는 않았기 때문에 한 번 정리 해봤다.
명령형 프로그래밍과 선언적 프로그래밍을 쉽게 요약하자면 아래와 같다.
imperative/명령형/절차적 프로그래밍은 당신이 어떤 일을 어떻게 할 것인가에 대한 것이다.
declarative/선언적 프로그래밍은 당신이 무엇을 할 것인가에 대한 것이다.
조금 더 쉽게 이해하기 위해 실생활에서의 예시를 들어보자.
프로그래머라면 자연스럽게 명령형 접근을 하게된다. 그래서 이 예시를 들었을 때 다음과 같은 의문이 들 수 있다.
"주소는 알겠는데 그 주소로 가는 방법은 어떻게 알지?"
이에 대한 대답은, 명령형 방식으로 접근하기 이전에 먼저 선언적 방식으로 추상화를 해야하는 것이다. 이 말을 다음 문장으로 다시 정리할 수 있다.
많은 선언적(Declarative)접근 방식들의 기반에는 일종의 명령적(Imperative) 추상화가 존재한다.
이제 프로그래밍의 영역으로 넘어가며 선언적 프로그래밍 언어와 명령적 프로그래밍 언어의 종류를 살펴보자
대표적인 선언적 프로그래밍 언어인 SQL, HTML의 코드는 아래와 같다.
SELECT * FROM USERS WHERE country='Mexico';
<article>
<header>
<h1>Declarative Programming</h1>
<p>Sprinkle Declarative in your verbiage to sound smart</p>
</header>
</article>
두 코드 모두 선언형 프로그래밍이며, 작업을 어떻게 수행하는가 보다는 무엇을 수행하는가에 더 초점이 맞추어져 있다. 즉 얻고자 하는 것에 대해 선언할 뿐, 수행 방식에 대한 부분은 추상화 되어있다.
이제 자바스크립트를 활용한 프로그래밍 예제로 들어가보자.
만약 기술면접에서 다음과 같은 과제를 만난다면 어떻게 풀 것인가?
이 문제들을 만약 명령적 프로그래밍으로 작성한 코드는 아래와 같다.
// 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가지 특징들은 이 코드를 명령적으로 만든다.
이 코드들은 어떤 일을 어떻게 처리하는지에 관해 표현하고 있다.
: 각각의 예시에서, 명시적으로 배열을 반복하거나(for문), 원하는 기능을 수행하기 위한 단계를 명시적으로 나열하고 있다.
작업을 수행하는 도중 상태(state)를 변경하고 있다.
: 처음 두 예시에서는 result라는 변수를 선언해 그것을 계속 수정하고 있다.
마지막 예시에서는 DOM에 접근해 state를 계속 변경하고 있다.
코드의 가독성이 떨어진다.
: 이렇게 절차적/명령적 코드들의 특징은 가독성이 떨어진다는 것이다.
이제 선언적 프로그래밍으로 과제를 수행해보자. 선언적 프로그래밍은 바로 앞에서 언급한 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 ]