티스토리 뷰

728x90
SMALL

배경

개발을 하다보면 추상화라는 용어를 굉장히 많이 보게 된다. 그리고 개인적으로도 추상화를 하기 위한 노력들을 많이 해왔다. 모호하게 잡혀있던 개념들을 정리해야겠다는 생각이 들어서 이 글을 쓰게 되었다. 이 글은 사전적 정의부터 추상화를 해야하는 이유, 코드에 적용하는 방법, 웹 프론트엔드에 적용하는 방법까지를 다루는 글이다.( + 개인적인 잡담)

추상화란

추상화의 사전적 정의

- 미술에서 추상화(抽象畫)는 대상의 구체적인 형상을 나타낸 것이 아니라 점, 선, 면, 색과 같은 순수한 조형 요소로 표현한 미술의 한가지 흐름이다.

- 컴퓨터 과학에서 추상화(abstraction)는 복잡한 자료, 모듈, 시스템 등으로부터 핵심적인 개념 또는 기능을 간추려 내는 것을 말한다.

 

현실 세계의 추상화

사전적 정의로 보면 조금 어려울 수 있다. 우리가 사는 현실 세계로 들어가보자.

 

ex1)

현실세계

엄마: 어디가니??
나: 네! 놀다올게요~

 

이게 추상화다. 구체적인 답변을 하지 않고 간단하게 요약해서 말했다. 그리고 이걸 함수로 표현하면 다음과 같다.

function 어디가니(){
  return 놀다올게요();
}

 

ex2)

지하철 노선도를 생각해보자. 지하철 노선도에 나와 있는 위치는 실제 위치와 일치하지 않는다. 즉 지하철 노선도는 실제 역의 좌표를 고려하지 않은 잘못된 지도다. 왜 그렇게 만들었을까?? 위성으로 로드뷰를 볼 수 있는 시대에 그게 불가능해서 그렇게 만들었을까?? 그렇지 않다.

우리는 복잡한 지하철 노선도를 원하지 않는다. 목적지를 찾아가는 게 가장 큰 목적이다. 그리고 그걸 방해하는 불필요한 요소들은 알고 싶지 않다. 이게 추상화다. 

개발에서 추상화 적용하기(기본편)

먼저 쉬운것부터 생각해보자. 사실 우리 모두 추상화를 적용하고 있다. 함수 하나, 변수 하나라도 만들었으면 그건 이미 추상화를 적용한 것이다. 다만 추상화의 수준이 다를 뿐이다.

상수

상수가 추상화라는 것에 의문점이 드는 사람도 있을 것 같다. 하지만 상수도 추상화다. 중복을 줄이기 위해서 모듈화를 위해서 상수를 사용하기도 하지만 추상화를 위해서 사용하기도 한다. 예시를 통해서 살펴보자.

 

ex)

추상화 하기 전 코드

if (
  user.permission === 'ADMIN' ||
  (user.permission === 'PARTICIPANT' && user.createdAt <= new Date('2020-01-01').getTime())
) {
  history.push('/content');
} else {
  alert('권한이 없습니다.');
}

 

위 코드를 살펴보면 유저의 권한이 admin이거나 participant이면서 2020년 1월 1일 이전에 가입한 사람인 경우만 url을 이동시키고 아닌 경우에는 alert를 띄워주고 있다.

 

사실 많이 볼 수 있는 코드다. 이를 간단하게 추상화할 수 있을 것 같다.

보니까 if문이 너무 지저분하다. 유지보수하려면 if문 전체 코드를 읽어야 한다. 물론 이것만 봤을 때는 어렵지 않지만 실무에서 이런 코드가 모이고 모여서 이 코드를 짠 개발자가 아니면(아니 만든 개발자도..) 유지보수하기가 굉장히 어려운 상황까지 간다. 

 

개선 코드

// 1단계
const isAdmin = user.permission === 'ADMIN';
const isOldParticipant = user.permission === 'PARTICIPANT' && user.createdAt <= new Date('2020-01-01').getTime();
if (isAdmin || isOldParticipant) {
  history.push('/content');
} else {
  alert('권한이 없습니다.');
}

// 2단계
const isAdmin = user.permission === 'ADMIN';
const isParticipant = user.permission === 'PARTICIPANT';
const isOldUser = user.createdAt <= new Date('2020-01-01').getTime();
const isOldParticipant = isParticipant && isOldUser;
if (isAdmin || isOldParticipant) {
  history.push('/content');
} else {
  alert('권한이 없습니다.');
}


// 3단계
const isAdmin = user.permission === 'ADMIN';
const isParticipant = user.permission === 'PARTICIPANT';
const isOldUser = user.createdAt <= new Date('2020-01-01').getTime();
const isOldParticipant = isParticipant && isOldUser;
const canAccessContent = isAdmin || isOldParticipant;
if (canAccessContent) {
  history.push('/content');
} else {
  alert('권한이 없습니다.');
}

 

추상화 레벨을 높여가면서 추상화를 했다. 3단계까지가면 조금 과해보인다는 생각이 들기도 한다. 하지만 실무에서는 예시보다 몇배는 복잡한 분기문들이 나오기 때문에 추상화레벨을 높이는건 유의미하게 코드 가독성을 높이고 유지보수를 쉽게 해준다.

 

p.s.1

지금은 if문에서 성공하는 경우고 else문에서 실패하는 경우의 코드가 작성되어 있다. 보통 이럴때는 앞단에 실패시 로직을 넣는다. 그리고 맨마직막에 성공로직을 넣는다. 이걸 가드패턴(?)이라고 한다.

 

p.s.2

if else문이 반복적으로 나오는 코드에는 early return을 넣으면 가독성이 올라간다.

함수

이제 진짜다. 함수 추상화는 추상화의 꽃이다. (그냥 내 생각에..)

개발을 처음 시작할때 나는 노마드코더(니콜라스)의 강의를 들은 적이 있다. 그 당시에 별것도 아닌 거로 함수를 나누길래 뭐지? 하면서 나는 그렇게까지 함수를 여러 개로 나누지 않았다. 그리고 추상화에 관해서 공부하다가 깨달았다. 그때의 내 수준에서는 받아들일 수 없었다는 것을..

 

사실 함수는 예시를 들려면 너무나도 많이 들 수 있다. 그냥 우리가 만든 함수를 아무거나 가져와도 추상화 수준을 판단할 수 있다.

그래서 내가 과거 작성했던 코드를 가져와 봤다. (사실 클래스긴한데 어차피 메서드부분은 함수이기 때문에 그냥 가져왔다. )

  private update() {
    const newMarkup = this.markup();

    const newDom = document.createRange().createContextualFragment(newMarkup);

    this.appendComponent(newDom);

    if (!newDom.firstElementChild) return;
    const newElements = this.inside
      ? [...newDom.firstElementChild.querySelectorAll('*')]
      : [...newDom.querySelectorAll('*')];
    const currentElements = [...this.target.querySelectorAll('*')];

    if (newElements.length !== currentElements.length) {
      this.target.innerHTML = this.inside
        ? [...newDom.firstElementChild.children].map((el) => el.outerHTML).join('')
        : newDom.firstElementChild.outerHTML;
      return;
    }

    for (let i = 0; i < newElements.length; i++) {
      const newEl = newElements[i];
      const curEl = currentElements[i];
      if (newEl.childElementCount !== curEl.childElementCount) {
        this.target.innerHTML = this.inside
          ? [...newDom.firstElementChild.children].map((el) => el.outerHTML).join('')
          : newDom.firstElementChild.outerHTML;
        return;
      }
      if (!newEl.isEqualNode(curEl)) {
        if (newEl.tagName !== curEl.tagName) {
          curEl.replaceWith(newEl);
        } else {
          if (curEl.firstChild?.nodeName === '#text' && newEl.firstChild?.nodeName === '#text') {
            if (curEl.firstChild.nodeValue !== newEl.firstChild.nodeValue) {
              curEl.firstChild.nodeValue = newEl.firstChild.nodeValue;
            }
          } else if (curEl.firstChild?.nodeName === '#text') {
            curEl.removeChild(curEl.firstChild);
          } else if (newEl.firstChild?.nodeName === '#text') {
            const text = document.createTextNode(newEl.firstChild.nodeValue as string);
            curEl.appendChild(text);
          }

          const curAttributes = curEl.attributes;
          const newAttributes = newEl.attributes;

          [...curAttributes].forEach((curAttr) => {
            if (!newAttributes.getNamedItem(curAttr.name)) curEl.removeAttribute(curAttr.name);
          });

          [...newAttributes].forEach((newAttr) => {
            const currentAttribute = curAttributes.getNamedItem(newAttr.name);
            if (!currentAttribute || currentAttribute.value !== newAttr.value) {
              curEl.setAttribute(newAttr.name, newAttr.value);
              if (curEl.nodeName === 'INPUT' && newAttr.name === 'value') {
                (curEl as HTMLInputElement).value = newAttr.value;
              }
            }
          });
        }
      }
    }
  }

 

추상화를 생각하면서 코드를 보면 바로 잘못된 부분이 보인다. update 메서드 부분이 하나의 함수로 이루어져 있다. 내부를 보니 굉장히 복잡한 로직들이 들어있다. 물론 update라는 메서드로 한번 추상화시킨 거지만 이 정도로는 부족하다. 이 클래스의 사용 방법을 알기 위해서는 update라는 메서드정도로만 추상화가 되어있어도 상관이 없다. 그런데 update 부분에 버그가 발생했다고 생각해보자. 사실 이런 복잡한 코드는 버그가 없는 게 이상하다. 그리고 그걸 디버깅하면서 문제를 해결하는 게 정상적인 절차다. 그런데 이렇게 코드가 되어있으면 유지보수가 힘들다. (근본적으로 update 방법에 문제가 있긴 하지만 지금은 추상화 관점에서 바라보자)

 

그 당시에 인지 하지 못했는데 면접에서 지적 받고 나서야 코드를 수정했다. 그 당시에는 이걸 추상화라고 느끼지 못했는데 객체지향, 추상화에 대해 공부를 하고 나서야 깨닫게 되었다.

(아래 코드도 작성한 지 거의 2년이 된 코드라서 좋은 추상화라고는 말하지 못하겠다. 그래도 이전 코드와 비교했을 때는 개선되었음이 느껴진다.)

  private update() {
    /*
      이전 dom과 새로운 dom을 비교해서 text, attributes를 업데이트
      elements count가 다른 경우에 newDom으로 전체를 렌더링(renderNewDom)
    */
    const newDom = this.getNewDom();
    if (!newDom.firstElementChild) {
      console.error('markup needs at least one element');
    }
    const newElements = this.getNewElements(newDom);

    const currentElements = [...this.target.querySelectorAll('*')];

    if (newElements.length !== currentElements.length) {
      this.renderNewDom(newDom);
      return;
    }

    for (let i = 0; i < newElements.length; i++) {
      const [newEl, curEl] = [newElements[i], currentElements[i]];
      if (newEl.childElementCount !== curEl.childElementCount) {
        this.renderNewDom(newDom);
        return;
      }
      if (!newEl.isEqualNode(curEl)) {
        this.changeText(curEl, newEl);
        this.changeAttributes(curEl, newEl);
      }
    }
  }

  getNewDom() { 
    // 생략
  }
  getNewElements(newDom: DocumentFragment) { 
    // 생략 
  }
  renderNewDom(newDom: DocumentFragment) { 
    // 생략
  }
  changeText(curEl: Element, newEl: Element) { 
    // 생략
  }
  changeAttributes(curEl: Element, newEl: Element) { 
    // 생략
  }
}

 

컴포넌트

컴포넌트도 다를게 없다. 애초에 현재 리액트에서는 대부분 함수형 컴포넌트를 사용하기 때문에 함수로 되어있기도 하다.

예전에 써놨던 글에서 컴포넌트글이 있어서 예시는 아래 글로 대체한다. 

 

https://ms3864.tistory.com/433

 

컴포넌트를 잘 만드는 방법(리액트)

컴포넌트란 프론트엔드 개발자라면 컴포넌트라는 말을 들어봤을 것이다. 컴포넌트가 뭘까?? 간단하게 말하면 컴포넌트는 독립적인 단위 모듈이다. 리액트를 예시로 들면 컴포넌트를 여러개 만

ms3864.tistory.com

 

근데 이렇게 넘어가기는 아쉬우니까 어떤 예시를 가져올까 고민하다가 내 깃헙 레포에서 하나 가져왔다.

(20년도 7 8월쯤에 만들었으니 웹을 시작한지 몇 달 안되었을 때 만든거다... 코드 퀄리티는 당연하게도 좋지 않다)

import React, { useState } from "react";
import { Link, useParams } from "react-router-dom";
import axios from "axios";

const BoardPost = ({ post, user_id, goodbadRefresh }) => {
  const params = useParams();
  const category = params.category;
  const postId = params.postId;
  const [userPost, setUserPost] = useState(null);
  const [userComment, setUserComment] = useState(null);
  const [selected, setSelected] = useState(false);
  const selecting = (email) => () => { 
    // 생략
  };
  const good = () => { 
    // 생략
  };
  const bad = () => { 
    // 생략
  };
  const hit = () => {
    // 생략
  };
  const report = () => {
    // 생략  
  };
  return (
    <>
      <h2 className="blind">갤러리 본문 영역</h2>
      <div className="view_content_wrap">
        <header>
          <div className="gallview_head clear ub-content">
            <h3 className="title ub-word">
              <span className="title_headtext"></span>
              <span className="title_subject">{post.title}</span>
            </h3>
            <div className="gall_writer ub-writer">
              <div className="fl">
                <span className="nickname in" onClick={selecting(post.email)}>
                  <em>{post.displayName}</em>
                </span>
                {selected === true && (
                  <div
                    className="user_data"
                    style={{ display: "inline-block" }}
                  >
                    <ul className="user_data_list">
                      <li>
                        <Link to={`/info/${post.email}/posting`}>
                          글<em className="num font_lightred">{userPost}</em>
                        </Link>
                      </li>
                      <li>
                        <Link to={`/info/${post.email}/comment`}>
                          댓글
                          <em className="num font_lightred">{userComment}</em>
                        </Link>
                      </li>
                      <li className="bg_grey">
                        <Link
                          to={`/forum/${category}/search/displayName/Keyword/${post.displayName}`}
                        >
                          작성글 검색
                          <em className="sp_img icon_go"></em>
                        </Link>
                      </li>
                      <li className="bg_grey">
                        <Link to={`/info/${post.email}`}>
                          갤로그 가기
                          <em className="sp_img icon_go"></em>
                        </Link>
                      </li>
                    </ul>
                  </div>
                )}
                <span className="gall_date" title={post.created}>
                  {post.created}
                </span>
              </div>
              <div className="fr">
                <span className="gall_count">조회 {post.count}</span>
                <span className="gall_reply_num">추천 {post.good}</span>
                <span className="gall_comment">댓글 {post.comment}</span>
              </div>
            </div>
          </div>
        </header>
        <div className="gallview_contents">
          <div className="inner clear">
            <div className="ql-container ql-snow">
              <div
                className="ql-editor"
                dangerouslySetInnerHTML={{ __html: post.content }}
              ></div>
            </div>
          </div>
          <div className="btn_recommend_box clear">
            <h3 className="blind">추천 비추천</h3>
            <div className="inner fl">
              <div className="up_num_box">
                <p className="up_num font_red">{post.good}</p>
              </div>
              <button type="button" className="btn_recom_up" onClick={good}>
                <span className="blind">개념 추천</span>
                <em className="sp_img icon_recom_up"></em>
              </button>
            </div>
            <div className="inner fr">
              <button type="button" className="btn_recom_down" onClick={bad}>
                <span className="blind">개념 비추천</span>
                <em className="sp_img icon_recom_down"></em>
              </button>
              <div className="down_num_box">
                <p className="down_num">{post.bad}</p>
              </div>
            </div>
            <div className="recom_bottom_box clear">
              <button type="button" className="btn_hitgall" onClick={hit}>
                <em className="sp_img icon_hitgall"></em>힛추
              </button>
              <button
                type="button"
                className="btn_snsmore"
                onClick={() => (
                  navigator.clipboard.writeText(window.location.href),
                  alert("현재 url이 복사되었습니다.")
                )}
              >
                <em className="sp_img icon_snsmore"></em>공유
              </button>
              <button type="button" className="btn_report" onClick={report}>
                <em className="sp_img icon_report"></em>신고
              </button>
            </div>
          </div>
        </div>
      </div>
    </>
  );
};
export default BoardPost;

https://github.com/yoonminsang/react_20_07_forum/blob/master/src/components/BoardPost.js

 

가볍게 봐도 추상화할 수 있는 부분이 많이 보인다.

 

1. 컴포넌트 분리

컴포넌트를 분리했다. 굉장히 간단한 작업이지만 유지보수하는 입장에서는 훨씬 편해졌다. 

const BoardPost = ({ post, user_id, goodbadRefresh }) => {
  // 생략
  return (
    <>
      <h2 className="blind">갤러리 본문 영역</h2>
      <div className="view_content_wrap">
        <header>
          <div className="gallview_head clear ub-content">
            <h3 className="title ub-word">
              <span className="title_headtext"></span>
              <span className="title_subject">{post.title}</span>
            </h3>
            <PostData
              post={post}
              category={category}
              selected={selected}
              selecting={selecting}
              userComment={userComment}
              userPost={userPost}
            />
          </div>
        </header>
        <GallViewContent post={post} good={good} bad={bad} hit={hit} report={report} />
      </div>
    </>
  );
}
  
  
const GallViewContent = ({ post, good, bad, hit, report }) => { 
  return (
   // 생략
  );
};

const PostData = ({ post, selected, selecting, userPost, userComment, category }) => {
  return (
    <div className="gall_writer ub-writer">
      <div className="fl">
        <span className="nickname in" onClick={selecting(post.email)}>
          <em>{post.displayName}</em>
        </span>
        {selected && <UserData post={post} userPost={userPost} userComment={userComment} category={category} />}
        <span className="gall_date" title={post.created}>
          {post.created}
        </span>
      </div>
      <div className="fr">
        <span className="gall_count">조회 {post.count}</span>
        <span className="gall_reply_num">추천 {post.good}</span>
        <span className="gall_comment">댓글 {post.comment}</span>
      </div>
    </div>
  );
};

const UserData = ({ post, userPost, userComment, category }) => { 
  return (
   // 생략
  );
}

 

추가적으로 userPost, userCommnet, selected, handleSelect라는 코드는 상위컴포넌트에서 필요없게 되었다. 즉 하위 컴포넌트로 코드를 이전할 수 있다.

const PostData = ({ post }) => {
  const params = useParams();
  const category = params.category;
  const [userPost, setUserPost] = useState(null);
  const [userComment, setUserComment] = useState(null);
  const [selected, setSelected] = useState(false);
  const select = (email) => () => { 
    // 생략
  };
  return (
    <div className="gall_writer ub-writer">
      <div className="fl">
        <span className="nickname in" onClick={select(post.email)}>
          <em>{post.displayName}</em>
        </span>
        {selected && <UserData post={post} userPost={userPost} userComment={userComment} category={category} />}
        <span className="gall_date" title={post.created}>
          {post.created}
        </span>
      </div>
      <div className="fr">
        <span className="gall_count">조회 {post.count}</span>
        <span className="gall_reply_num">추천 {post.good}</span>
        <span className="gall_comment">댓글 {post.comment}</span>
      </div>
    </div>
  );
};

 

한단계 더 나아가면 UserData 내부로 데이터를 이전할 수도 있다.

const PostData = ({ post }) => {
  const [selected, setSelected] = useState(false);
  return (
    <div className="gall_writer ub-writer">
      <div className="fl">
        <span className="nickname in" onClick={() => setSelected((selected) => !selected)}>
          <em>{post.displayName}</em>
        </span>
        {selected && (
          <UserData post={post} email={post.email} selected={selected} onSelectTrue={() => setSelected(true)} />
        )}
        <span className="gall_date" title={post.created}>
          {post.created}
        </span>
      </div>
      <div className="fr">
        <span className="gall_count">조회 {post.count}</span>
        <span className="gall_reply_num">추천 {post.good}</span>
        <span className="gall_comment">댓글 {post.comment}</span>
      </div>
    </div>
  );
};

const UserData = ({ post, email, category, selected, onSelectTrue }) => {
  const params = useParams();
  const category = params.category;
  const [userPost, setUserPost] = useState(null);
  const [userComment, setUserComment] = useState(null);
  useEffect(() => {
    if (!selected) return;
    axios
      .get(`/forum//info/${email}`)
      .then(function (res) {
        setUserPost(res.data.post);
        setUserComment(res.data.comment);
        onSelectTrue();
      })
      .catch(function (err) {
        console.log(err);
      });
  }, [selected, email]);

  if (!selected) return null;
  return ( 
    // 생략
  );
};

 

 

예상 질문

만약 이전한 데이터를 다른 컴포넌트에서 사용해야하면 어떻게 하나요??

=> state끌어올리기, 상태관리 라이브러리 도입, 이전 코드로 롤백 등 상황에 따라 다양한 선택을 할 수 있습니다. 개방폐쇄 원칙을 깨지 않으면서 해결할 수 있습니다.

 

하위 컴포넌트에서 데이터를 주입받지 않고 내부에서 다루게 되면 컴포넌트 재사용이나 jest, storybook등을 사용하기 어려울 것 같아요

=> presentation과 data를 분리하는 작업은 분명히 가치가 있는 작업입니다. 하지만 해당 이유로 상위 컴포넌트에서 모든 데이터를 가지고 있게 되면 득보다 실이 더 크다고 생각합니다. 만약 data를 꼭 분리해야되는 상황이라면 과거 redux에서 사용하던 방법처럼 presentational component와 container component로 나누는 방법을 사용할 수도 있고 커스텀훅을 props로 넘겨서 데이터를 받을 수도 있고 (링크 참조) vac 패턴 (링크 참조)을 사용할 수도 있습니다.

 

 

컴포넌트 분리 추가 설명

사실 컴포넌트는 수평적인 확장이 좋다. 계층이 깊게 들어가게 되면 코드를 파악하기가 어렵다. (+ props 드릴링 문제가 발생하기도 함)

결국 컴포넌트안에서 다른 컴포넌트를 호출하는건 추상화계층을 한 단 계 더 만드는 것을 의미한다. 이 계층이라는게 때때로 필요하지만 무분별한 사용은 지양하는 것이 좋다.

출처 위키피디아

위의 예시에서는 PostData에서 UserData를 렌더링하고 있다. 이럴때는 PostData의 props에 selectedComponent라는 props를 추가하면 컴포넌트의 수평적 확장이 가능해진다. 그런데 그러기 위해서는 상위 컴포넌트에서 모든 데이터를 가지고 있어야된다는 단점(?)이 있다. 위에서 PostData컴포넌트에서만 사용하는 데이터들을 PostData컴포넌트로 이전했기 때문에 이 방법을 사용할 수는 없다.

 

2. 변수명 수정

selecting, good, bad, hit, report 라는 함수명이 명확하지 않다. 변수명만 봤을때는 함수로 보이지 않는다.

루트 컴포넌트에서 handle이라는 변수명을 추가하면 함수라는게 좀 더 명확해진다.

또한 내부 컴포넌트에서는 onClick이라는 변수명을 추가하면 어떤 이벤트를 실행했을 때 어떤 동작이 실행되는지 여부를 쉽게 알 수 있다.

 

완성코드

import React, { useEffect, useState } from 'react';
import { Link, useParams } from 'react-router-dom';
import './stylesheets/BoardPost.css';
import 'react-quill/dist/quill.snow.css';
import axios from 'axios';

const BoardPost = ({ post, user_id, goodbadRefresh }) => {
  const params = useParams();
  const postId = params.postId;
  const handleGood = () => { 
    // 생략
  };
  const handleBad = () => { 
    // 생략
  };
  const handleHit = () => { 
    // 생략
  };
  const handleReport = () => { 
    // 생략
  };
  return (
    <>
      <h2 className="blind">갤러리 본문 영역</h2>
      <div className="view_content_wrap">
        <header>
          <div className="gallview_head clear ub-content">
            <h3 className="title ub-word">
              <span className="title_headtext"></span>
              <span className="title_subject">{post.title}</span>
            </h3>
            <PostData post={post} />
          </div>
        </header>
        <GallViewContent
          post={post}
          onClickGood={handleGood}
          onClickBad={handleBad}
          onClickHit={handleHit}
          onClickReport={handleReport}
        />
      </div>
    </>
  );
};
export default BoardPost;

const GallViewContent = ({ post, onClickGood, onClickBad, onClickHit, onClickReport }) => {
  return ( 
    // 생략
  );
};

const PostData = ({ post }) => {
  const [selected, setSelected] = useState(false);
  return (
    <div className="gall_writer ub-writer">
      <div className="fl">
        <span className="nickname in" onClick={() => setSelected((selected) => !selected)}>
          <em>{post.displayName}</em>
        </span>
        {selected && (
          <UserData post={post} email={post.email} selected={selected} onSelectTrue={() => setSelected(true)} />
        )}
        <span className="gall_date" title={post.created}>
          {post.created}
        </span>
      </div>
      <div className="fr">
        <span className="gall_count">조회 {post.count}</span>
        <span className="gall_reply_num">추천 {post.good}</span>
        <span className="gall_comment">댓글 {post.comment}</span>
      </div>
    </div>
  );
};

const UserData = ({ post, email, category, selected, onSelectTrue }) => {
  const params = useParams();
  const category = params.category;
  const [userPost, setUserPost] = useState(null);
  const [userComment, setUserComment] = useState(null);
  useEffect(() => {
    if (!selected) return;
    axios
      .get(`/forum//info/${email}`)
      .then(function (res) {
        setUserPost(res.data.post);
        setUserComment(res.data.comment);
        onSelectTrue();
      })
      .catch(function (err) {
        console.log(err);
      });
  }, [selected, email]);

  if (!selected) return null;
  return ( 
    // 생략 
  );
};

 

추상화를 해야하는 이유

이제 추상화가 뭔지는 알겠다. 근데 굳이 해야 할까??

해야 한다. 그리고 사실 추상화의 수준이 다를 뿐이지 이미 우리 모두 추상화를 하고 있다.

그런데 해야 하는 이유가 뭘까??

생산성

내 생각에 추상화를 하는 가장 큰 이유는 생산성이다. 조금 의문이 드는 답변일지도 모른다. 유지보수를 잘하기 위해서, 클린 코드를 위해서 모두 맞는 말이지만 그게 가장 큰 목적은 아니다. 가장 큰 목적은 생산성이다. 즉, 추상화 레벨을 높이는 것은 좋은 일이지만 상황에 따라서는 조금 더 낮은 수준의 추상화를 해야된다. 추상화 수준을 높이기 위해서 추가적인 작업을 하다가 데드라인을 넘기지 말라는 것을 염려해서 하는 말이다. (나에게 한 번 더 되새기는 의미도 있다.)

 

이제 추상화를 했을 때 생산성이 높아지는 이유를 생각해보자.

 

예를 들어서 특정 페이지에서 버튼을 눌렀을 때 url이 이동하지 않는 버그가 발생했다고 생각해보자. 그래서 특정 페이지 컴포넌트 파일을 들어갔는데 1500줄이라고 해보자. 그리고 모든 코드는 명령형으로 되어있다. 이때 정말 막막하다. 어디서부터 어떻게 시작해야 될지 알 수 없다. 그저 처음부터 모든 코드를 읽어야 한다. 그리고 중간중간 디버거나 로그를 찍어보면서 여기가 맞나?? 확인을 할 것이다. 정말 간단한 버그라고 할지라도 이 버그를 고치는데 1시간의 시간이 걸릴지도 모른다.

 

이제 추상화가 잘 된 경우를 생각해보자. 컴포넌트가 잘 나뉘어져있고 식별자 역시 처음 보는 사람도 쉽게 알아볼 수 있도록 추상화되어있다. 이런 경우라면 1분 만에 버그를 해결할 수 있다. 

 

추상화가 되어있지 않은 코드를 리팩터링하는데 시간이 오래 걸리는 것은 사실이다. 하지만 프로그램이 유지보수되면서 위와 같은 상황이 한 달에 한 번꼴로 발생한다고 생각해보자. 한 달에 한 번씩 1시간의 시간이 소모된다. 게다가 조금 큰 변화가 있으면 그걸 테스트하는데도 많은 비용이 든다. (strategy pattern이 생각나는데 디자인 패턴을 다음 글에서 다룰 예정이다) 그리고 인증같이 굉장히 중요한 페이지라고 생각해보자. 이런 상황에서 소모되는 비용을 생각하면 추상화를 공부해서 적용해야겠다는 생각이 들지않는가? 

 

맺음말

솔직히 지금의 나에게는 조금 어려운 주제이기도 하다. 그리고 위에서 적은 것 말고도 할 말이 많은데 정리가 필요하다. 하나 하나 천천히 정리하고 후속글을 올리겠다.

 

참고글 및 이미지

https://www.youtube.com/channel/UCKXBpFPbho1tp-Ntlfc25kA

https://www.yes24.com/Product/Goods/116469364

https://ko.wikipedia.org/wiki/%EC%B6%94%EC%83%81%ED%99%94

https://ko.wikipedia.org/wiki/%EC%B6%94%EC%83%81%ED%99%94_%EA%B3%84%EC%B8%B5

728x90
LIST
댓글
공지사항