[TIL] day 11. 추상화

kcm dev blog·2021년 8월 17일
0

TIL

목록 보기
7/19
post-thumbnail

Intro

지난주에 이어 프로그래밍 방식에 대해 계속 학습해오고 있다. 함수형, 선언형, 명령형, 객체지향형 .. 종류는 많지만, 특정 방식이 더 우월하기 보다는 상황에 따라 적절한 방식이 있기 마련이다. 지난주엔 함수형 프로그래밍 방식하나로 골머리를 앓았지만 그래도 공부해보면서 상황에 따라 무엇이 더 적절한지에 대한 감각은 좀더 생긴것 같다. 대학에서 프로그래밍을 배우기 시작한 이후로 그냥 "이렇게 생긴게 있다"에서 무엇이 더 적절한지에 대해 고민하는 수준에 도달한 것 같다. 한층 더 높은 고민을 할 기회를 제공해준 '프로그래머스' 분들께 감사를...

선언적 프로그래밍

선언형 프로그래밍은 '어떤 방법으로' 보다는 '무엇'을 나타내야 하는지를 묘사

'어떤 방법으로'는 명령형 프로그래밍에 가까운 방식이고, 선언적 프로그래밍은 '무엇'을 나타내야 할지를 묘사한다. 둘이 무슨 차이냐 싶지만 간단한 코드를 통해서도 둘의 차이를 알수 있다.

//명령형
function double(arr){
    let results=[];
    for(let i=0;i<arr.length;i++){
        if(typeof arr[i] === 'number'){
            results.push(arr[i]*2)
        }
    }
    return results;
}
Selector('body').innterHTML=double([1,2,3,'a']);

//선언형
function double(arr){
    return arr
        .filter(param => typeof param === 'number')
        .map(number=>number *2);
}

두 함수는 같은 일을 처리하는 함수이다. 명령형이 결과값을 만들기 위해 여러개의 방법(for, if, *2)을 여러 방법을 조합하는 방식이라 한다면, 선언형은 순차적으로 표현할 대상을 순차적으로 표현(filter, map)하는 방법이다.

js는 배열을 활용하는데 있어 선언형 프로그래밍 방식을 사용하길 권장한다. 선언형 프로그래밍 방식을 통해 가독성, 간결성, 확장성이라는 장점을 얻을 수 있기 때문이다.

함수를 추상화하자

함수를 추상화하는 것은 레고에 비유할 수 있다. 특정 상황에만 맞는 것이 아닌 다양한 경우에 대처할 수 있고, 특정 부분이 다르다면 해당 부분만 갈아낄 수 있는 레고와 같다고 볼 수 있다.

const button1=document.createElement('button');
button1.textContext='Button1';
button1.addEventListener('click',()=>{
  if($button1.style.textDecoration==='text-through'){
    $button1.style.textDecoration='none';
  }else{
    $button1.style.textDecoration='text-through';
  }
}
                         
const button2=document.createElement('button');
button2.textContext='Button2';
button2.addEventListener('click',()=>{
  if($button2.style.textDecoration==='text-through'){
    $button2.style.textDecoration='none';
  }else{
    $button2.style.textDecoration='text-through';
  }
}

const button3=document.createElement('button');
button3.textContext='Button3';
button3.addEventListener('click',()=>{
  if($button3.style.textDecoration==='text-through'){
    $button3.style.textDecoration='none';
  }else{
    $button3.style.textDecoration='text-through';
  }
}

다음 코드는 버튼 3개를 만들고, 각 버튼별로 '취소선/없음'이 토글이 되도록 하는 코드이다. 각 버튼별로 하는일이 같은데 각 버튼별로 같은 기능을 수행하기 위해 같은 구조의 코드를 반복한다. 불필요한 반복, 가독성 저하 라는 단점들을 가지고 있다. 이러한 특징은 함수의 추상화를 통해 해결 가능하다.


  
function ToggleButton({$target,text}){
  const $button=document.createElement('button');
  
  $target.appendChild($button);
  $button.addEventListener('click',()=>{
    if($button.style.textDecoration==='text-through'){
      $button.style.textDecoration='none';
    }else{
      $button.style.textDecoration='text-through';
    }
  });
  this.render=()=>{
    $button.textContent=text;
  }
  this.render();
}

const $app=document.querySelector('#app');

new ToggleButton({$target:$app, text:'button1');
new ToggleButton({$target:$app, text:'button2');
new ToggleButton({$target:$app, text:'button3');

ToggleButton()이라는 함수로 반복되었던 일들을 추상화하여 달라지는 부분들만 인자로 넣어지면 완전히 같은 기능을 하는 코드가 된다. 가독성은 높이면서, 불필요한 반복과 실수를 줄일수 있게 된 것이다.

ToggleButton()에 들어가는 인자를 추가하여 단순히 토글만 되는 함수가 아닌 다른 기능을 하는 함수를 만드는 것도 가능하다.



function MakeButton({$target,text,onClick}){
  const $button=document.createElement('button');
  
  $target.appendChild($button);
  $button.addEventListener('click',onClick);
  this.render=()=>{
    $button.textContent=text;
  }
  this.render();
}

const $app=document.querySelector('#app');
const toggle=()=>{
    if($button.style.textDecoration==='text-through'){
      $button.style.textDecoration='none';
    }else{
      $button.style.textDecoration='text-through';
    }
}
const timer=()=>{
  setTimeout(() => {
    alert("timer is ringed");
  }, 3000);
}
  
new MakeButton({$target:$app, text:'button1', onClick:toggle);
new MakeButton({$target:$app, text:'button2', onClick:timer);
new MakeButton({$target:$app, text:'button3', onClick:toggle);

기존의 ToggleButton()에서 toggle 기능을 구현한 callback함수를 onClick로 추상화하여 인자로 받음으로써, toggle, timer 기능을 할 수 있는 버튼을 만들 수 있는 함수 MakeButton()으로 추상화 할 수 있다.

이렇게 함수의 세부적인 부분들을 추상화 함으로써 코드를 효율적으로 관리할 수 있다.

Defer: 평가보류

이 내용은 오늘 수업때 배운 내용은 아니지만 수업 내용을 따라하면서 생긴 문제를 해결하면서 알게 된 것이다.

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>

</head>
<body>
    <script src="./mkToggle.js"></script>
    <div id="app"></div>
</body>
</html>

mkToggle에는 #app안에 추가할 요소들에 대한 정보들이 들어 있다. 그런데 위와 같이 script#app 위에 선언될 경우 해당 코드는 에러가 발생한다. 정확히는 mkToggle에서 구현한 요소들이 웹 페이지에 랜더링이 되지 않는다.

그 이유는 랜더링의 순서 때문이다. 일반적으로 브라우저는 html를 파싱하여 dom tree를 만들어 랜더링한 뒤, css를 파싱하여 cssom을 만든 다음, 마지막으로 script 파일을 실행한다. 그런데 script#app보다 위에 있는 경우, 스크립트가 실행될 때 #app보다 위에 있기 때문에 #app를 참조할 수 없다. 참조가 불가능하기 때문에 script 입장에서는 #app의 위치를 파악할 수 없고, 그로 인해 랜더링을 할 수 없다.

이러한 상황에서 방법은 2가지가 있다. 하나는 script#app보다 밑에 선언하거나, script안에 defer를 선언하는 것이다. defer보류하다라는 뜻이다. 즉, script의 실행을 보류했다가 html 랜더링이 끝난뒤에 실행하겠다는 의미이다. dom tree가 만들어진 뒤에 실행하기 때문에 script#app의 위치를 파악할 수 있고, 랜더링이 정상적으로 이루어 진다.

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>

</head>
<body>
    <script src="./mkToggle.js" defer></script>
    <div id="app"></div>
</body>
</html>

하루를 마치며

지난 한주동안 함수형 프로그래밍을 배우면서 힘들다고 노래를 불렀었다. 근데 확실히 한번 고비를 겪고 나니 생각보다 함수형 프로그래밍 방식이 유용하게 쓰인다. 각 기능을 추상화 함으로써 가독성과 효율성을 모두 챙기는 일석이조의 효과를 톡톡히 누릴 수 있게 되었다. 지난 한주간의 고생이 헛되지 않았던 것이었다.

오늘은 vanilaJS 첫날이라 그런지 상대적으로 덜 부담스러웠다. 그러나 아직 배울 내용이 많고, 높은 난이도의 내용이 즐비할 것이다. 그럼에도 지난주의 고생이 좋은 밑거름이 될 것 같아, 좀더 산듯한 출발이 가능할 것 같다.

profile
오늘 배운건 오늘 소화하자!

0개의 댓글