자바스크립트 코드를 리팩토링 하는 방법 ' ES2015'

자바스크립트 코드를 리팩토링하는 방법, ES2015

ES2015 리팩토링 - 빠레꽁(Var Let Const)

필자가 ‘JavaScript는 쉽지만 어렵기도 하구나’라고 생각을 하게 된 것은 변수가 생각대로 작동하지 않았던 그 때부터인 것 같습니다. 자바스크립트를 본격적으로 알기 전, var를 변수 선언해도 작동이 되고 생략해도 작동이 된다는 사실에 그냥 작성했던 때가 있었습니다. 하지만 이로 인해 나의 코드가 전역 변수가 되어 팀원 모두가 고생을 해야 했고, 변수 선언을 위에서 했는데 아래에서 올라오는 등 필자의 생각과 다르게 동작하는 경험을 하면서 혼란을 겪었습니다.
아마 여러분도 오늘 이 글에서 스코프, 호이스팅 등을 접하고 나면 정말로 쉽지 않다는 사실을 깨닫게 될 것입니다. ES2015부터 두 가지의 변수가 새로 추가되었습니다. 오늘은 그 변수들을 어떻게 이해하고 언제 사용해야 하는지를 다뤄보겠습니다.

ES2015 리팩토링 빠레꽁

JavaScript 변수 var에 대하여

JavaScript 변수 var에 대해 여기에서는 몇 가지 특징만 짚어 보고 넘어가겠습니다. JavaScript 변수에 대한 조금 더 자세한 내용은 JavaScript Definite Guide를 추천합니다.

1. 변수의 var 선언이 없으면 코드의 복잡성이 증가한다.
일단 코드를 살펴보겠습니다.

JavaScript는 나름 중첩시킨 함수 임에도 불구하고 x=4를 출력하도록 되어 있습니다. var 선언을 하지 않고 작성하면 기본적으로 직전의 상위 스코프의 변수를 찾게 되어 있지만 만약 직전에도 선언이 되어 있지 않으면 한차례 더 상위를 찾아가게 됩니다. 결국 이런식으로 의도하지 않은 전역 변수를 만들어 내게 되는 거죠.
그렇다고 ‘선언하기 전에 마구 사용해도 되느냐’하는 것은 아닙니다. 왜냐면 x=4 위에 사용할 경우 undefined를 출력하도록 되어 있기 때문입니다.

2. 호이스팅에 대해서
아래 두 코드는 위의 전역 변수에 대한 개념과 변수 스코프 끌어올림(호이스팅)에 대한 개념이 섞여 있는 예제입니다.
두 코드의 차이점을 한눈에 알아채셨나요?
- var 선언을 하지 않은 경우

var 선언을 하지 않은 코드 - function outer(){ x=3; function inner(){ console.log(

- var 선언을 한 경우

var 선언을 한 코드-  function outer(){ x=3; function inner(){ console.log(

var 선언을 하지 않은 경우에는 (inner =3, outer = 4) 의 결과가, var 선언을 한 경우에는 inner는 undefined 가 outer는 3 이 떨어집니다.

왜 그럴까요? var 선언을 하지 않은 경우, 결국 x의 스코프는 inner 안에서 찾을 수 없기 때문에 outer까지 가서 찾아 보게 됩니다. ‘호이스팅’이라는 어려운 말로 적혀있지만 일단 "찾아보게 된다"라는 개념으로 이해하고 넘어가시면 됩니다. 조금 더 확실한 의미로 ‘변수 선언을 끌어올림’이 더 적절한 풀이가 되겠지만, 이렇게 이해하고 나면 두 번째의 "var 선언을 한 경우 "는 오히려 더 잘 이해됩니다. var 선언을 한 시점에서 스코프는 해당 함수 블록만 찾게 될 것이고 선언이 안된 채로 사용되었기 때문입니다.

코딩 인터뷰에나 나올 만한 이 문제는 우리가 일반적으로 JavaScript를 처음 접할 때의 난해함을 떠올리게 합니다. 블록이 스코프를 결정하지 않는다니, 기상천외한 언어라고 볼 수 있습니다. 하지만 이렇게 함으로써 브라우저 상에서 사람들이 처음으로 HTML의 스펙과 적절히 섞어 사용할 수 있게 되었고(script 태그가 여기저기서 얼마나 많이 import 되고 있는지 생각해 보세요) 이 호이스팅과 전역변수를 활용한 팁들도 생겨나기 시작했습니다.
하지만 현재는 자바스크립트의 코드 베이스가 예전과는 비교도 할 수 없을 만큼 커져버렸습니다. 따라서 전역변수를 사용한다는 것은 점점 죄악처럼 여겨지고 있고, 실제로 좋은 패턴도 아닙니다.

3. 비동기 함수에서의 변수 사용
아래의 코드를 살펴보면 비동기 함수의 경우, 변수가 내가 원하는 시점과 원하는 바와는 전혀 다르게 나올 때가 있다는 것을 알 수 있습니다.

비동기 함수에서의 변수 코드- var number=[1,2,3,]; for (var i=0; i < number.length; i++) { setTimeout(function () { console. log(number[i]); }, 0); }// 결과는 underfined가 세번 나온다

물론 저런 코드가 프로젝트에서 사용될리 없지만 setTimeout이 아니라 AJAX 호출이라고 생각해보세요. JavaScript가 눈에 익은 사람은 저 코드가 당연하게 느껴지겠지만 콜백 함수라든지 받아서 실행해야 하는 경우라면 코드에 대한 명확한 스펙을 알고 있더라도 실수할 수 있습니다.

let, const에 대한 오해
이해를 하기도 전에 오해라니 무슨 이야기인가 싶을 수도 있겠으나, ES2015 의 스펙이 나오면서 let 과 const 가 어떤 전역변수와 지역변수를 해결하는 전가의 보도처럼 이야기되는 경우가 있어서 꼭 그런 것 만은 아닐 수도 있다는 이야기를 먼저 해두겠습니다.
여전히 ES2015에도 변수를 선언하지 않고 작업을 하면 함수 스코프를 따라 체이닝 작업(찾아보게 된다)을 통해 변수에 값을 할당하게 됩니다. 이것은 바꿀 수도 없고 바꿔서도 안된다고 생각하지만 아래 코드를 보면, 위의 var로 선언한 코드에서 단지 let으로 선언했을 뿐이라는 걸 알 수 있습니다.

var선언한 코드에서 let선언 - function outer(){ x=3; function inner(){ console.log(

이 경우, 두 가지의 결과로 다르게 나오는데 var로 선언을 할 경우는 undefined라는 값이 나오고(에러가 아닙니다!) let의 경우는 에러를 내뱉게 됩니다. (명시적인 에러를 뱉는 것과 undefined를 처리하는 것은 개발 차원에서 다뤄야 할 수준이 달라집니다)

let은 무엇이 다를까?
첫째, 앞서 언급했던 ‘선언을 하지 않고 사용을 하면 에러가 나도록 설계가 되어 있다’는 점과 두 번째로 ‘반복문 안에서 비동기 동작이 다르게 작동한다’는 점입니다. 이 부분도 코드를 먼저 확인해볼까요?
앞서 소개한 코드에서 var를 let으로 바꿨습니다.

var코드 let으로 변환 - var number=[1,2,3,]; for (let i=0; i < number.length; i++) { setTimeout(function () { console. log(number[i]); }, 0); }

개인적으로 이 부분이 조금 더 헷갈리지 않을까 싶습니다. 비동기의 경우, var처럼 처리되던 것을 기본으로 생각해오던 JavaScript 개발자들에게는 오히려 혼란스럽게 다가올 수 있지만 let으로 작성하면 비동기에도 변수가 따로 놀지 않는 상황을 만들 수 있다는 점에서 의미가 있습니다.

const는 무엇이 다를까
다른 언어와 마찬가지로 const의 경우에는 다시 할당하지 않는 상수의 역할을 하고, 만약 재할당을 하려고 하면 에러가 납니다. let과 비교했을 때 값을 재할당 할 수 없다는 것 말고는 다를 바가 없습니다.

외우자, 이제는 무조건 let, const다.
브라우저는 지속적으로 발전하고 있고 그에 따른 성능도 올라가지만 한가지 확실한 것은 표준 스펙에 대한 성능은 언제나 일순위라는 것입니다.
그 과정에서 let, const는 기존의 var를 완전히 대체할 수 있는 새로운 표준입니다. 현재는 var 가 아주 조금 (그것도 아주 조금) 성능적인 우위를 점령하고 있지만 장기적으로 let, const 와도 성능 차이가 거의 없을 것으로 (오히려 더 나아질 것으로) 예상됩니다.
실제 적용해본 것을 블로그에 포스팅한 사례도 속속 등장하고 있습니다.
성능 비교: https://gomugom.github.io/let-vs-var-performance-compare/>
그리고 앞서 언급된 에어비앤비의 사례에서도 보듯이 엔지니어링에서 표준을 따라 작업을 하는 것이 현재의 성능을 쫓아가는 것보다 장기적으로 이득인 점도 간과할 수는 없습니다.

프로젝트 리팩토링

프로젝트 리팩토링 이미지

이제 standup 프로젝트를 한번 들여다볼까요?(GitHub 페이지에 접속해서 standup 검색을 하거나 아래 URL을 클릭하면 됩니다.)
링크: https://github.com/ehrudxo/standup
주소를 복사해서 git clone 명령을 통해 사용하고 있는 로컬 PC에 복사한 후, 가장 마지막 작업인 day7을 체크아웃 받아봅니다.



git clone https://github.com/ehrudxo/standup & cd standup & git chekcout day7

이후 editor를 구동시킨 후에 탐색기를 열고 프로젝트에서 var를 검색해보면, 다음 8개의 파일이 var로 이루어져 있습니다.

REAMDE.md
src/__tests__/CloudDao.js
src/actions/Article.js
src/config.js
src/Editor.js
src/FileUtil.js
src/FirebaseDao.js
src/Login.js

이 중 README.md 파일은 다른 파일을 설명하면서 만든 파일이므로 나머지 파일을 바꾸고 리팩토링 해 보겠습니다. 먼저 FileUtil.js를 살펴보면 storageRef 와 ulpoadTask 가 var로 지정되어 있습니다. 그런데, storageRef를 살펴보니 다른 곳에서는 사용하고 있지 않습니다. uploadTask만 재사용 되므로 storageRef는 삭제하고 uploadTask는 다시 값이 할당되지 않으므로 const로 변경해 봅니다.
이번에는 src/actions/Article.js를 살펴 보시죠. 독자들을 위해 코드 스니펫을 가지고 와서 확인해보겠습니다.

코드 스니펫 - export function getArticles(articles){ var items = []; articles.forEach(function(article) { vat item = article.val(); item['key'] = article.key; item.push(item); }) if(item && items.length>0) { return { type : ALL, articles : items.reverse() } } }

여기서 items는 push 명령어를 통해서 지속적으로 변경되니까 let으로 지정해야 할 것 같지만 참조형은 변경이 가능하므로 const를 사용합니다.
그리고 item도 다시 할당하지 않으므로 모두 const로 변경합니다.

item코드 const로 변경 -  export function getArticles(articles){ const items = []; articles.forEach(function(article) { const item = article.val(); item['key'] = article.key; item.push(item); }) if(item && items.length>0) { return { type : ALL, articles : items.reverse() } } }

이후 나머지는 독자들이 변경해 보는 것이 좋겠습니다.
마지막으로 제대로 동작하는지 확인하려면 다음의 명령어를 실행하면 됩니다.



$npm install & npm start


▶   해당 콘텐츠는 저작권법에 의하여 보호받는 저작물로 기고자에 저작권이 있습니다.
▶   해당 콘텐츠는 사전 동의없이 2차 가공 및 영리적인 이용을 금하고 있습니다.

공유하기
도경태 프로
도경태 프로 IT테크놀로지 전문가
삼성SDS 개발실

SCP(SDS Certified Professional)로 삼성SDS에서 테크리드를 담당하고 있습니다. OSGeo에서 오픈소스 활동을 하고 있습니다. 잘 모르는 기술에 대해서 낯선 사람들과 이야기 하면서 배워 나가는 것을 좋아합니다.