<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>Huns.me</title>
        <description>김코딩 님이 잘하고 싶어서 만든 블로그</description>
        <link>https://huns.me/</link>
        <atom:link href="https://huns.me/feed.xml" rel="self" type="application/rss+xml"/>
        <pubDate>Fri, 30 Dec 2022 11:39:55 +0900</pubDate>
        <lastBuildDate>Fri, 30 Dec 2022 11:39:55 +0900</lastBuildDate>
        <generator>Jekyll v3.8.6</generator>
        
        <item>
            <title>네이버에서 보낸 2년과 2023년</title>
            <description>&lt;p&gt;휴가자가 많은 연말이라 팀 슬랙이 고요하다. 혼자 이런저런 생각을 하기 좋은 시간. 내년 목표를 정리하며 지난 2년을 돌아보고 있다. 네이버로 복귀한 후에는 매년 개인 미션을 한 줄로 정리한다. 내가 집중해야 할 일이 더 잘 보이는 느낌이 든다.&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;2021년 - 빠르게 경기를 파악해서 팀의 빈 곳을 메우고 내년을 준비한다.&lt;/li&gt;
  &lt;li&gt;2022년 - 팀 중심 사고 위에 제품팀을 얹는다.&lt;/li&gt;
  &lt;li&gt;2023년 - 고객을 중심에 놓고 팀의 비즈니스 가치를 증명한다.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;써놓고 보니 어떤 줄거리가 떠오르는데 생각이 달아나면 아쉬우니 가볍게 정리할 겸 글로 끄적인다.&lt;/p&gt;

&lt;h2 id=&quot;2021년-빠르게-경기를-파악해서-팀의-빈-곳을-메우고-내년을-준비한다&quot;&gt;2021년, 빠르게 경기를 파악해서 팀의 빈 곳을 메우고 내년을 준비한다&lt;/h2&gt;

&lt;p&gt;2021년 8월에 네이버로 다시 돌아왔다. 가장 먼저 팀이 내게 거는 기대를 이해하는 게 중요했다. 경기를 파악하자. 경기를 한 발 뒤에서 구경했다. 뒤로 물러나면 더 잘 보인다. 구경하다가 빈 곳이 보이면 들어가서 메웠다. 생긴 지 얼마 안 된 팀이라 빈 곳이 많이 보였다. 자연스레 활동 반경이 넓어졌는데 22년을 위한 준비라고 생각했다. 이것저것 해보면서 경기를 몸으로 느낀다. 이 상태를 계속 유지할 수는 없었다. 활동 반경이 넓어지면 일에 임팩트를 주기가 어렵더라. 내가 잘 할 수 있는 일로 팀의 빈 곳을 메우면서 동료와 합을 잘 맞출 수 있는 포지션을 찾으려고 노력했다.&lt;/p&gt;

&lt;p&gt;역할을 찾는 과정에서 간혹 동료와 동선이 겹치며 좌충우돌했다. 실무와 관리, 둘 어딘가에 애매하게 걸친, 팀이 내게 거는 기대도 모호했다. 그러다가 질문을 이렇게 정리하니까 답이 선명해졌다.&lt;/p&gt;

&lt;p&gt;‘우리 팀이 시드 투자 또는 시리즈 A 어딘가에 있는 스타트업이고 내가 CTO라면 지금 나는 뭘 해야 할까?’&lt;/p&gt;

&lt;p&gt;내 역할을 5가지로 정리했다.&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;아키텍처 및 기술 전략 수립&lt;/li&gt;
  &lt;li&gt;개발 총괄&lt;/li&gt;
  &lt;li&gt;개발 문화 조성&lt;/li&gt;
  &lt;li&gt;기술 부채 관리&lt;/li&gt;
  &lt;li&gt;인재 영입&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;2022년-팀-중심-사고-위에-제품팀을-얹는다&quot;&gt;2022년, 팀 중심 사고 위에 제품팀을 얹는다&lt;/h2&gt;

&lt;h3 id=&quot;팀-중심-사고&quot;&gt;&lt;strong&gt;팀 중심 사고&lt;/strong&gt;&lt;/h3&gt;

&lt;p&gt;2021년만 해도 회고를 하면 ‘팀으로 일하는 것 같지 않아요’라는 이야기를 하는 동료가 많았다. 코로나 거리두기가 길어지며 원격 근무도 길어지고 있었다. 코로나가 터지기 직전에 만들어진 우리 팀은 팀워크를 다지기도 전에 원격 근무를 시작하며 각자의 집으로 흩어졌다. 원격 근무 환경은 팀워크를 해치는 원흉으로 지목되곤 했다. 보통 이런 문제를 만나면 정기 스몰토크나 회식 시간을 만들어서 해결하려고 한다. 이번에도 그랬다.&lt;/p&gt;

&lt;p&gt;내 진단은 좀 달랐다. 원격 근무는 문제를 증폭시킬 뿐 문제의 본질은 아니지 않을까? 개인을 고립시키는 업무 설계 방식, 그리고 이로 인해 생기는 구성원 간 커뮤니케이션의 단절이 더 문제로 보였다. 지금 와서 생각해 보니 원격 근무를 선호하는 입장에서 다른 방향으로 문제를 풀고 싶었던 것 같기도 하다. 어쨌든 맞았다.&lt;/p&gt;

&lt;p&gt;일상의 접점을 늘리고 서로를 연결해서 고립을 깨자. 가장 먼저 데일리 미팅을 부활시켰다. 그리고 팀이 모여서 일의 ‘What, Why, How’를 맞추는 시간을 만들었다. 코드 리뷰를 강화했다. 같이 고민하는 시간을 늘렸다. 시간이 아깝다고 느껴진다면 ‘폐지’가 아니라 ‘개선’을 이야기 하자고 설득했다. 3주 간의 아키텍처 설계 워크숍 같은 큰 이벤트를 진행 했다. 같이 모여서 머리를 맞대고 문제를 논의해 본 경험이 없는 누군가는 경험 자체를 신기해 했다. 함께 하면 더 잘할 수 있는 일이 있다는 걸 조금씩 느끼더라. 사람들이 혼자 풀던 문제를 같이 보기 시작했다.&lt;/p&gt;

&lt;p&gt;팀의 변화를 가장 잘 보여주는 상징은 ‘와인바 미팅’이다. 와인바는 게더타운에 만든 회의 공간이다. 문제가 잘 안 풀리면 슬랙에 도움을 요청한다. 조금 기다리면 하나둘 와인바로 사람이 모이고 문제를 같이 논의한다. 규칙이나 장치를 만들지 않았다. 누가 하자고 한 적도 없다. 그냥 자연스레 문화가 생겼다. 와인바는 우리 팀의 문화를 보여주는 상징이다. 동료들은 이제 우리 팀의 장점으로 ‘집단 지성’을 꼽는다.&lt;/p&gt;

&lt;h3 id=&quot;제품팀&quot;&gt;&lt;strong&gt;제품팀&lt;/strong&gt;&lt;/h3&gt;

&lt;p&gt;대게의 기능 조직이 그렇듯 그동안 우리 팀도 팀 바깥의 요청, 즉 다른 네이버 서비스의 문제를 기술적으로 해결하는 ‘해결사’의 역할을 주로 해왔다. 예컨대, 서비스 부서에서 자신의 서비스에 UI 빌더를 새로운 기능으로 넣고 싶다고 요청을 하면 우리가 만들어서 제공하는 식이다. 서비스 부서가 우리의 고객인 셈이다.&lt;/p&gt;

&lt;p&gt;가진 자산을 좋은 제품으로 키우고 싶은 욕심이 있는 팀에게 이런 상황은 불리하다. 고객의 수가 n으로 늘어나면 문제가 커진다. 팀의 규모도 따라서 키워야 하는데 팀이 무한정 커질 수는 없다. 확장이 제한된다. 다양한 서비스의, 서로 다른 비즈니스 도메인을 우리가 다 이해하기도 어렵다. ‘문제를 풀어가며 제품을 조금씩 키워나가기’보다는 ‘요구를 가능한 많이, 빠르게 해결해 주기’가 팀의 생존 전략이 되기 쉽다. 서비스의 리듬에 끌려다니면 팀 고유의 사이클을 갖지 못하고 자기 전략을 구사하기가 어렵다. 자기 제품을 만들때는 팀이 문제에 몰입할 수 있는 환경을 만들어주는 게 좋다. 서비스에 끌려다니면 문제에 몰입하지 못한다.&lt;/p&gt;

&lt;p&gt;2022년은 이 문제를 해결하는 첫 단추를 꿰는 해다. 우리가 먼저 UI 빌더의 전문가가 되어 기준을 제시한다. 그리고 쌓은 지식과 경험을 서비스에 나눠준다. 말은 쉬운데, 프로젝트만 하던 팀이 제품 팀이 된다는 건… 아휴 힘들다 힘들어. 일단 제품 개발의 A부터 Z까지를 경험한 적이 없다. 일부 공정을 맡았을 뿐. 상상은 할 수 있지만 직접한 경험에 비할 수 있을까. 무엇보다 제품 개발에 중요한 기획, 설계, 디자인 같은 역량이 팀에 없다. 엔지니어링과 디자인을 나누어 바라보던 관성은 단단한 벽 같다. 회사의 체계를 뚫어보려 했지만 일개 단위 팀이 이 벽을 깨기는 쉽지 않더라.&lt;/p&gt;

&lt;p&gt;그래서 올해가 중요하다고 생각했다. 만들어보고 가져보면 기류를 바꿀 수 있지 않을까. 이상 보다는 현실에서 가능한 대안을 찾아서 일이 되게 만들기로 했다. 일단 뭐가 나오면 새로운 경험이 부어지고 길이 열리리라. 기능 조직이지만 기능 조직이 아닌 것처럼 일하자. 완벽한 제품팀이 될 수는 없지만 노력은 할 수 있잖아? 프로세스는 내가 좀 만질 줄 아니 해볼게, 하면서 튜닝하자. UX 설계해 줄 사람이 없네? 도와줄 수 있는 사람 다 끌어모아봐. 디자이너가 없네? 오픈소스로 일단 버티고 지원군이 오면 리뉴얼 하자. 그렇게 한해 동안 탈탈탈 쥐어짜는 느낌으로 뛰었다.&lt;/p&gt;

&lt;p&gt;아직 바깥에 공개할 수 없어 아쉽지만 꽤 괜찮은 초기 버전을 만들었고, 이걸로 가능성은 보여주었다고 스스로 평가하고 있다. 지금 와서 생각해 보면 이걸 어떻게 여기까지 끌고 왔나 싶다. 올 한 해 고생한 우리 팀과 나와 매일 투닥거리는 팀 리더 성식 님에게 박수를…!&lt;/p&gt;

&lt;h2 id=&quot;2023년-고객을-중심에-놓고-팀의-비즈니스-가치를-증명한다&quot;&gt;2023년, 고객을 중심에 놓고 팀의 비즈니스 가치를 증명한다&lt;/h2&gt;

&lt;p&gt;2023년은 증명하고 살아남아야 하는 한 해다. 올해 가능성은 보여주었지만 가능성으로 만족할 수 있는 건 올해까지가 아닐까. 누구도 우리를 찾지 않는다면 2024년에도 우리가 존재할 수 있을까? 팔려야 산다. 전 세계가 허리띠를 졸라매는 나날이 쉽게 안 끝날 것 같아 심장이 더 쫄깃하다.&lt;/p&gt;

&lt;p&gt;가능성을 보여준 대가로 제품 개발에 디자인팀이 합류한다. 새로운 에너지를 담을 수 있게 기존 워크플로우를 확장시켜야 한다. 할 일은 많은데 자원은 부족하니, 디자인과 개발이 하나의 목표를 향해 높은 응집도로 협업해야 한다. 둘을 묶을 끈으로 고객을 떠올렸다. 고객의 문제를 중심에 두고 디자인과 개발을 하나로 묶는다. 고객을 믿어보자.&lt;/p&gt;

&lt;p&gt;새해 첫 근무일에는 오랜만에 팀원 모두가 1784 오피스에 모여서 새해 계획을 정리하기로 했다. 여러 이야기를 나누며 목표를 더 상세하게 잡아가겠지만 결국 2023년에 우리를 이끌 중심 축은 고객일 것 같다. 내가 해야 할 일도 고객에게 있을 테고. 올해보다 더 어렵고 낯선 일 투성이겠지만 지금 우리 팀과 함께라면 잘 해낼 수 있다. 올해 우리가 그랬듯이.&lt;/p&gt;
</description>
            <pubDate>Thu, 29 Dec 2022 00:00:00 +0900</pubDate>
            <link>https://huns.me/2022-12-29-44-네이버에서 보낸 2년과 2023년</link>
            <guid isPermaLink="true">https://huns.me/2022-12-29-44-네이버에서 보낸 2년과 2023년</guid>
            
            <category>회고</category>
            
            
            <category>회고</category>
            
        </item>
        
        <item>
            <title>TypeScript에서 전역 개체 타입은 어떻게 정의하나요?</title>
            <description>&lt;p&gt;최근에 동료가 Node.js 애플리케이션에서 mongodb 클라이언트를 이용해서 데이터 삽입 로직을 테스트하는 코드를 작성했다.&lt;/p&gt;

&lt;div class=&quot;language-typescript highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;MongoClient&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;Db&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;ObjectId&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;mongodb&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;nx&quot;&gt;describe&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;insert&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;kd&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;client&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;MongoClient&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;kd&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;db&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;Db&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

  &lt;span class=&quot;nx&quot;&gt;beforeAll&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;nx&quot;&gt;client&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;MongoClient&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;globalThis&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;__MONGO_URI__&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;

    &lt;span class=&quot;k&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;client&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;connect&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;

    &lt;span class=&quot;nx&quot;&gt;db&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;client&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;db&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;globalThis&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;__MONGO_DB_NAME__&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;});&lt;/span&gt;

  &lt;span class=&quot;nx&quot;&gt;afterAll&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;client&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;close&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;});&lt;/span&gt;

  &lt;span class=&quot;nx&quot;&gt;it&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;should insert a doc into collection&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;users&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;db&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;collection&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;users&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;mockUser&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;_id&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;ObjectId&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;some-user-id&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;John&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;};&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;users&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;insertOne&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;mockUser&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;

    &lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;insertedUser&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;users&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;findOne&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;({&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;_id&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;ObjectId&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;some-user-id&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;});&lt;/span&gt;
    &lt;span class=&quot;nx&quot;&gt;expect&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;insertedUser&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;toEqual&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;mockUser&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;});&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;그런데 이 코드에서 &lt;code class=&quot;highlighter-rouge&quot;&gt;globalThis&lt;/code&gt;의 프로퍼티 타입을 TypeScript 컴파일러가 인식하지 못해서 에러가 발생했다. 특정 라이브러리가 전역 개체에 커스텀 프로퍼티를 추가하는데, 타입 정보를 제공하지 않아 컴파일러가 타입 에러를 뿜는 상황이었다. 호기심이 땡겨서 내가 도와주겠다고 선뜻 나섰다.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/images/new/2022-05-22-43-1.png&quot; alt=&quot;IDE가 globalThis의 __MONGO_URI__와 __MONGO_DB_NAME__을 인식하지 못하는 모습&quot; /&gt;&lt;/p&gt;

&lt;p&gt;구글링을 해보니 &lt;code class=&quot;highlighter-rouge&quot;&gt;declare global&lt;/code&gt;로 타입을 선언해주면 된다는데 이게 잘 먹히지 않네? 이런.&lt;/p&gt;

&lt;p&gt;적당히 알고 해결하면 다음에 또 삽질할 것 같아서 원인을 좀 더 깊이 추적해 들어가면서 학습한 내용을 기록한다.&lt;/p&gt;

&lt;h1 id=&quot;globalthis는-무엇인가&quot;&gt;globalThis는 무엇인가?&lt;/h1&gt;

&lt;p&gt;&lt;code class=&quot;highlighter-rouge&quot;&gt;globalThis&lt;/code&gt;는 JS 실행 환경에 상관없이 전역 개체를 참조하는 통일 된 수단을 제공하는 표준 개체이며 ECMAScript 11에 도입되었다. 이 개체는 브라우저 환경에서는 window 개체를 참고하고, Node.js 환경에서는 global 개체를 참조한다.&lt;/p&gt;

&lt;p&gt;한가지 주의할 점은 &lt;code class=&quot;highlighter-rouge&quot;&gt;globalThis&lt;/code&gt;가 전역 개체를 참조할 뿐, 전역 개체 그 자체는 아니라는 점이다. 전역 실행 콘텍스트는 NewGlobalEnvironment를 갖는데, 이 안에 this(thisValue)가 담겨 있다. 이 &lt;code class=&quot;highlighter-rouge&quot;&gt;this&lt;/code&gt;의 내부 슬롯 중에 &lt;code class=&quot;highlighter-rouge&quot;&gt;[[GloblThisValue]]&lt;/code&gt;가 바로 &lt;code class=&quot;highlighter-rouge&quot;&gt;globalThis&lt;/code&gt;다.&lt;/p&gt;

&lt;h1 id=&quot;global-개체의-타입-재정의&quot;&gt;global 개체의 타입 재정의&lt;/h1&gt;

&lt;p&gt;그렇다면 TypeScript를 사용할 때 전역 개체의 프로퍼티 타입은 어떻게 정의를 해야 하는 걸까?&lt;/p&gt;

&lt;p&gt;여기저기 찾아보니 &lt;code class=&quot;highlighter-rouge&quot;&gt;declare global&lt;/code&gt;로 타입을 커스텀하라길래 &lt;code class=&quot;highlighter-rouge&quot;&gt;types/global.d.ts&lt;/code&gt;에 아래와 같은 타입을 만들었다.&lt;/p&gt;

&lt;div class=&quot;language-typescript highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nx&quot;&gt;declare&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;global&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;kd&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;__MONGO_URI__&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;kd&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;__MONGO_DB_NAME__&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;  
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;하지만 여전히 타입을 인식하지 못한다. 왜 안 되는 걸까?&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/images/new/2022-05-22-43-2.png&quot; alt=&quot;여전히 __MONGO_URI__의 타입을 인식하지 못하는 모습&quot; /&gt;&lt;/p&gt;

&lt;p&gt;원인을 정확히 이해하려면 내가 모르는 개념 몇 개를 좀 더 자세히 찾아봐야 할 것 같다.&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;&lt;code class=&quot;highlighter-rouge&quot;&gt;declare&lt;/code&gt;는 정확히 뭔가?&lt;/li&gt;
  &lt;li&gt;왜 &lt;code class=&quot;highlighter-rouge&quot;&gt;declare global&lt;/code&gt;로 타입을 선언해야 하는가?&lt;/li&gt;
  &lt;li&gt;하라는대로 했는데 왜 동작하지 않는가?&lt;/li&gt;
&lt;/ol&gt;

&lt;h1 id=&quot;declare는-정확히-뭔가&quot;&gt;declare는 정확히 뭔가?&lt;/h1&gt;

&lt;p&gt;TypeScript는 Ambient Declaration라는 걸 정의하고 있다. 이는 TypeScript로 작성하지 않은 코드의 타입 정보를 컴파일러에게 알려주는 선언이다. 대게 외부 사용자에게 내가 만든 라이브러리의 타입 정보를 알려줄 목적으로 d.ts 파일을 정의할 때 Ambient Declaration을 이용한다. Ambient Declaration을 작성할 때 &lt;code class=&quot;highlighter-rouge&quot;&gt;declare&lt;/code&gt; 키워드를 사용한다.&lt;/p&gt;

&lt;p&gt;&lt;code class=&quot;highlighter-rouge&quot;&gt;declare&lt;/code&gt;로 선언할 수 있는 타입 유형은 크게 세 가지다.&lt;/p&gt;

&lt;h2 id=&quot;declare-namespace&quot;&gt;declare namespace&lt;/h2&gt;

&lt;p&gt;컴파일러는 namespace로 선언한 TS 코드를 JS 일반 객체로 컴파일 하는데, &lt;code class=&quot;highlighter-rouge&quot;&gt;declare&lt;/code&gt; 키워드를 붙여주면 JS 코드로 컴파일을 하지 않는다. 이렇게 객체의 타입 정보만 알려줄 목적으로 &lt;code class=&quot;highlighter-rouge&quot;&gt;declare namespace&lt;/code&gt;를 사용한다. 이를 Ambient Namespace 또는 Internal Module이라고 부른다.&lt;/p&gt;

&lt;div class=&quot;language-typescript highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nx&quot;&gt;declare&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;namespace&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;D3&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;kr&quot;&gt;interface&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;Selectors&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;nl&quot;&gt;select&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;selector&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;Selection&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
            &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;element&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;EventTarget&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;Selection&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;};&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;kr&quot;&gt;interface&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;Event&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;nl&quot;&gt;x&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;number&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;nl&quot;&gt;y&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;number&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;kr&quot;&gt;interface&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;Base&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;extends&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;Selectors&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;nl&quot;&gt;event&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;Event&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;nx&quot;&gt;declare&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;d3&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;D3&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;Base&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;declare-module&quot;&gt;declare module&lt;/h2&gt;

&lt;p&gt;&lt;code class=&quot;highlighter-rouge&quot;&gt;declare module&lt;/code&gt;로 선언한 타입만 가진 모듈을 Ambient Module이라고 한다. Ambient Module이 컴파일 대상에 포함이 되어있기만 하면 TypeScript 컴파일러는 자동으로 타입을 인지할 수 있다. 이는 Ambient Namespace와 유사한데 import를 할 때 동작한다는 점이 다르다.&lt;/p&gt;

&lt;p&gt;예를 들어 node.js는 자신이 제공하는 개별 모듈의 타입을 &lt;code class=&quot;highlighter-rouge&quot;&gt;node.d.ts&lt;/code&gt; 파일에 아래와 같이 정의하고 있다.&lt;/p&gt;

&lt;div class=&quot;language-typescript highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nx&quot;&gt;declare&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;module&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;url&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;kr&quot;&gt;interface&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;Url&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;nx&quot;&gt;protocol&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;?:&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;nx&quot;&gt;hostname&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;?:&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;nx&quot;&gt;pathname&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;?:&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;parse&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;urlStr&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;parseQueryString&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;?,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;slashesDenoteHost&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;?):&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;Url&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;nx&quot;&gt;declare&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;module&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;path&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;normalize&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;p&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;join&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(...&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;paths&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;any&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[]):&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;sep&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;클라이언트는 외부 라이브러리를, 마치 TypeScript 모듈인 것처럼 &lt;code class=&quot;highlighter-rouge&quot;&gt;import&lt;/code&gt; 해서 사용할 수 있다.&lt;/p&gt;

&lt;div class=&quot;language-typescript highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c1&quot;&gt;/// &amp;lt;reference path=&quot;node.d.ts&quot;/&amp;gt;&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;as&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;URL&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;url&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;kd&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;myUrl&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;URL&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;parse&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&amp;lt;http://www.typescriptlang.org&amp;gt;&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;declare-global&quot;&gt;declare global&lt;/h2&gt;

&lt;p&gt;전역 개체는 특별한 존재이며 &lt;code class=&quot;highlighter-rouge&quot;&gt;import&lt;/code&gt;로 참조할 수 없는 모듈이기 때문에 TypeScript는 전역 개체의 타입을 커스텀하는 별도의 문법을 제공한다.&lt;/p&gt;

&lt;p&gt;declare global 블럭 안에 선언한 타입은 전역 개체의 프로퍼티 타입으로 정의된다.&lt;/p&gt;

&lt;div class=&quot;language-typescript highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c1&quot;&gt;// 컴파일러는 export/import 구문이 없는 파일은 스크립트로 인지한다.&lt;/span&gt;
&lt;span class=&quot;nx&quot;&gt;declare&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;global&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;kd&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;country&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;any&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;kd&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;multiply&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;a&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;number&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;b&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;number&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;number&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;그런데 TypeScript 컴파일러는 고유의 컴파일 규칙을 따라서 export/import 구문이 없는 파일은 일반 스크립트(그렇지 않은 경우는 모듈로 취급)로 취급한다. 위의 &lt;code class=&quot;highlighter-rouge&quot;&gt;declare global&lt;/code&gt; 선언은 스크립트가 존재하는 스코프에 속하기 때문에 다른 모듈은 이 타입을 인지할 수 없다. 이 지점이 실체가 좀 아리까리한데 시간 날 때 더 파볼 생각이다.&lt;/p&gt;

&lt;p&gt;이 문제는 아래와 같이 빈 &lt;code class=&quot;highlighter-rouge&quot;&gt;export&lt;/code&gt; 구문을 하나 넣어주는 걸로 해결할 수 있다. &lt;code class=&quot;highlighter-rouge&quot;&gt;export&lt;/code&gt; 구문이 있기 때문에 TypeScript 컴파일러는 이 파일을 모듈로 처리한다.&lt;/p&gt;

&lt;div class=&quot;language-typescript highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nx&quot;&gt;declare&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;global&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;kd&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;country&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;any&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;kd&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;multiply&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;a&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;number&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;b&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;number&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;number&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;// 가짜 export를 넣어서 외부 모듈로 인식시킬 수 있다.&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;그래서-해결은&quot;&gt;그래서 해결은?&lt;/h2&gt;

&lt;p&gt;&lt;code class=&quot;highlighter-rouge&quot;&gt;types/global.d.ts&lt;/code&gt;에 아래의 &lt;code class=&quot;highlighter-rouge&quot;&gt;declare global&lt;/code&gt; 선언을 넣어주면,&lt;/p&gt;

&lt;div class=&quot;language-typescript highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{};&lt;/span&gt;

&lt;span class=&quot;nx&quot;&gt;declare&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;global&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;kd&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;__MONGO_URI__&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;kd&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;__MONGO_DB_NAME__&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;컴파일러가 타입 정보를 제대로 인지한다. 이걸로 문제 해결. 이 외에도 경로 인식 문제가 있었지만 그건… 이 글의 주제가 아니므로 패스.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/images/new/2022-05-22-43-3.png&quot; alt=&quot;모든 타입 에러가 해결되어 IDE가 __MONGO__URI__를 인식함을 보여주는 이미지&quot; /&gt;&lt;/p&gt;

&lt;p&gt;알고나면 별 거 아니지만 이런 유형의 문제는 원리를 제대로 모른 채 대충 해결하고 넘어가면 다음에 비슷한 문제를 만났을 때 또 헤메기 쉽다. 귀찮더라도 좀 더 깊이 들여다 볼 가치가 있다고 생각해서 굳이 이슈 해결 과정을 정리했다.&lt;/p&gt;

&lt;p&gt;누군가에게 도움이 되었기를. 🙏&lt;/p&gt;
</description>
            <pubDate>Sun, 22 May 2022 00:00:00 +0900</pubDate>
            <link>https://huns.me/2022-05-22-43-TypeScript에서 전역 개체 타입은 어떻게 정의하나요</link>
            <guid isPermaLink="true">https://huns.me/2022-05-22-43-TypeScript에서 전역 개체 타입은 어떻게 정의하나요</guid>
            
            <category>책리뷰</category>
            
            
            <category>책리뷰</category>
            
        </item>
        
        <item>
            <title>회의를 줄이는 두 가지 시도</title>
            <description>&lt;p&gt;팀 구성원이 짝을 지어 기술 리서치를 진행하면서 여러 건의 일을 병렬로 진행하고 있다. 리서치 결과를 건별로 공유하고 논의하는 자리가 만들어지다보니 자연스레 회의가 많아졌다. 프로토콜을 만들어가는 프로젝트 초반이라 회의가 많은 건 어쩌면 ‘당연’한 일인 듯 싶다. 그럼에도 우리가 효과적으로 회의를 ‘제어’할 수 있다면 줄인 비용을 다른 곳에 쓸 수 있지 않을까? 이 상황을 팀이 회고하고 두 가지 방향으로 개선을 해보기로 했다.&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;동시에 진행하는 태스크의 갯수를 제한하자.&lt;/li&gt;
  &lt;li&gt;회의시간을 30분으로 제한하자.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;1-동시에-진행하는-태스크의-갯수를-제한하자&quot;&gt;1. 동시에 진행하는 태스크의 갯수를 제한하자.&lt;/h2&gt;

&lt;p&gt;칸반처럼 동시에 진행하는 태스크의 갯수를 제한해 보기로 했다. 팀 안의 커뮤니케이션 관계를 그대로 유지한 채, 병렬로 진행하는 일의 수를 늘리면 그 일과 직접적인 상관이 없는 사람도 부하를 받기 마련이다. 커뮤니케이션 관계가 그대로니까. 해야 할 코드 리뷰의 양이 늘어나거나, 참여해야 할 논의가 더 많아지거나, 갑자기 이슈를 같이 들여다봐야 할 일이 생기거나. 다른 사람의 일 같지만, 관계를 완전히 차단하지 않는 이상 ‘남의 일’이 아닌 셈이다.&lt;/p&gt;

&lt;p&gt;회의가 많다는 건 어쩌면 팀이 감당하기 어려운 수준의 부하를 받고 있다는 신호일지도. 동시에 진행하는 태스크의 갯수를 조금 줄이고 우리에게 일어나는 변화를 관찰하기로 했다.&lt;/p&gt;

&lt;h2 id=&quot;2-회의시간을-30분으로-제한하자&quot;&gt;2. 회의시간을 30분으로 제한하자.&lt;/h2&gt;

&lt;p&gt;30분 이상 시간을 소비하는 회의를 무조건 잡지 말라는 건 아니고 일종의 상자 안에서 생각하기. 회의 시간을 30분으로 제한하고, 회의를 주최하는 사람은 30분 안에 미팅을 끝낼 수 있도록 회의를 ‘설계’하기로 했다. 제약을 두고 제약 안에서 잘할 수 있는 궁리를 같이 해보자는 취지다.&lt;/p&gt;

&lt;p&gt;‘사전 회의록 공유하기’가 내 기대 이상으로 자리를 잘 잡아가고 있지만, 마치 누가 정한 표준이라도 있는 것처럼, 모든 회의를 1시간씩, 같은 포맷으로 잡는 관성은 그대로다. 그래서 잔잔한 호수에 돌을 하나 던졌다.&lt;/p&gt;

&lt;p&gt;.&lt;/p&gt;

&lt;p&gt;.&lt;/p&gt;

&lt;p&gt;.&lt;/p&gt;

&lt;p&gt;🙏  이제 돌을 던졌으니 어떤 변화가 생기는지 어디 한 번 봅시다.&lt;/p&gt;
</description>
            <pubDate>Fri, 08 Apr 2022 00:00:00 +0900</pubDate>
            <link>https://huns.me/2022-04-08-42-회의를 줄이는 두 가지 시도</link>
            <guid isPermaLink="true">https://huns.me/2022-04-08-42-회의를 줄이는 두 가지 시도</guid>
            
            <category>책리뷰</category>
            
            
            <category>책리뷰</category>
            
        </item>
        
        <item>
            <title>함께 일한다는 느낌을 주기</title>
            <description>&lt;p&gt;새로운 프로젝트를 꾸리면서 구성원이 “함께 일한다”는 느낌을 받을 수 있게 돕는 데에 에너지를 많이 쓰고 있다. 최근에 시도했고, 시도하고 있는 건 두 가지. “잘 했어요”는 아니고 “해보고 있어요”의 의미.&lt;/p&gt;

&lt;h1 id=&quot;멤버-모두가-참여하는-아키텍처-디자인-워크숍&quot;&gt;멤버 모두가 참여하는 아키텍처 디자인 워크숍&lt;/h1&gt;

&lt;p&gt;아키텍처 디자인 워크숍은 김영재 님의 번역서인 &lt;a href=&quot;https://book.naver.com/bookdb/book_detail.nhn?bid=20568444&quot;&gt;‘개발자에서 아키텍트로’&lt;/a&gt; 라는 책에서 영감을 얻어서 만든 프로그램이다.&lt;/p&gt;

&lt;p&gt;2주간 팀 멤버가 모두 참여해서, 비즈니스 목표, 프로젝트 환경, 요구사항, 해결해야 할 문제와 기대하는 수준을 확인하고, 문제를 해결하는 아키텍처를 함께 디자인했다. 나도 처음 해보는 거라 걱정이 많았는데 아키텍처의 태생이 그러하듯 “완벽해”라는 말은 할 수 없지만(그게 이 프로그램의 목표도 아니고), 팀이 일을 시작할 수 있는 수준의 충분한 합의를 만들었다는 걸로 만족스럽다.&lt;/p&gt;

&lt;p&gt;사실 멤버 모두가 참여하는 이런 회의 방식은 효율이 좋지 않다. 각자의 지식수준이 달라서 모두가 같은 수준으로 시스템을 이해하고 의견을 제시하기는 애초에 어렵다. 미팅에 참여하는 인원이 많으면 덩달아 커뮤니케이션 비용도 높아진다. 그럼에도 모두가 소중하게 여겨야 할 큰 합의는, 같이 결정하는 게 효과적이라고 생각했다. 최소한 “우리가 같이 만들었다”라는 느낌은 주고 싶었다. 모르는 걸 같이 배울 수 있는 기회이기도 했고. 그래서 비효율을 감수했다. 멤버가 8명이라 부담이 조금 덜했던 건 다행. 인원이 더 많았다면 깊게 고민했을 거 같다.&lt;/p&gt;

&lt;p&gt;아키텍처라는 건 구성원이 만든 합의인데, 일부가 밀실에서 규칙을 만들고 “이제 우리 모두 이걸 따르자!”라고 선언하는 방식은 합의가 제대로 지켜질 가능성을 낮춘다. 규칙의 의미를 이해하지 못하고 그저 따라가기에 급급하면 변화에 대응하기 어렵다. 규칙이 있다는 걸 몰라서 따를 수 없다면 그건 더 문제고.&lt;/p&gt;

&lt;p&gt;규칙을 만드는 소수가 문제를 제대로 이해하고 있지 못할 가능성도 배제하기 어렵다. 아키텍처는 자신이 놓일 환경의 여러 요소에 영향을 받는다. 그렇다면 관련 지식을 가진 모두가 자신이 가진 지식의 조각을 함께 맞춰야 문제를 제대로 이해할 수 있지 않을까? 속도만 좇다가 엉뚱한 산에 올라가느니, 조금 느리더라도 목표를 함께, 잘 찾아가는 과정을 팀이 경험할 수 있는 계기를 만들어 주는 게 중요했다.&lt;/p&gt;

&lt;p&gt;약간의 비효율이 존재하더라도 합의를 만든다는 본질에 충실하고 싶었는데, 동료의 진솔한 피드백은 다음 주에 1 대 1 스몰토크를 하면서 수집할 생각.&lt;/p&gt;

&lt;h1 id=&quot;태스크-담당자와-짝꿍을-1명-이상-연결하기&quot;&gt;태스크 담당자와 짝꿍을 1명 이상 연결하기&lt;/h1&gt;

&lt;p&gt;풀기 어려운 문제를 혼자 해결해야 할 때 느낄 수 있는 감정 중에 하나는 외로움인 것 같다. 나만 그런가?&lt;/p&gt;

&lt;p&gt;앞으로 팀이 풀어야 할 문제는 지금까지 해보지 않은 프랙티스를 많이 도입해야 한다. 가보지 않은 길이라는 건, 그만큼 어렵다는 뜻이고, 주니어 비중이 높은 우리 팀 구성상 혼자 문제를 풀도록 놔두면 외로움이라는 감정과 싸우는 데에 소모하는 에너지가 클 수밖에 없다. 실제로 이런 이야기를 듣기도 했고.&lt;/p&gt;

&lt;p&gt;역시나 약간의 비효율이 존재하더라도 혼자 문제를 잡고 끙끙하게 두지 않기로 했다. 일단 백로그를 리뷰하면서 각자 자신이 풀고 싶은 문제를 선택한다. 이때 개별 태스크의 담당자를 한 명으로 한정하지 않는다. 같은 문제를 풀고 싶은 사람이 자연스레 2명 또는 3명이 되는데, 가능한 모두가 기회를 갖는다. 물론 어디까지나 개인의 선택.&lt;/p&gt;

&lt;p&gt;하지만 팀 여건상 담당자를 여러 명 배정할 수 없는 상황이 생기는데 이럴 때는 메인 담당자를 두고 다른 사람은 고민을 도와주는 짝꿍으로 티켓에 명시했다. 짝꿍의 역할은, “나도 이 문제에 관심이 있어. 도움이 필요하면 나한테 말해. 혹시 내 일이 빨리 끝나면 도와줄게” 정도일 것 같다.&lt;/p&gt;

&lt;p&gt;잘 동작할지, 어떤 심리적 장벽이 또 작용할지, 엉망진창이 될지 솔직히 나도 잘 모르겠다. 잘 안 되면 그때 다른 길을 찾지 뭐, 이런 생각.&lt;/p&gt;

&lt;p&gt;다만 이 프로젝트의 끝에 구성원 모두가 이런 생각을 할 수 있으면 참 좋겠고 그걸 돕는 게 내 역할이라고 생각 중.&lt;/p&gt;

&lt;p&gt;“오, 우리가 이걸 했다고?”&lt;/p&gt;
</description>
            <pubDate>Mon, 28 Mar 2022 00:00:00 +0900</pubDate>
            <link>https://huns.me/2022-03-30-41-함께 일한다는 느낌을 주기</link>
            <guid isPermaLink="true">https://huns.me/2022-03-30-41-함께 일한다는 느낌을 주기</guid>
            
            <category>책리뷰</category>
            
            
            <category>책리뷰</category>
            
        </item>
        
        <item>
            <title>(리뷰)룬샷 - 전쟁, 질병, 불황의 위기를 승리로 이끄는 설계의 힘</title>
            <description>&lt;p&gt;&lt;img src=&quot;/assets/images/new/2022-03-08-background.jpeg&quot; /&gt;
&lt;em&gt;&lt;a href=&quot;https://book.naver.com/bookdb/book_detail.nhn?bid=16339452&quot;&gt;룬샷(전쟁, 질병, 불황의 위기를 승리로 이끄는 설계의 힘) - 흐름출판&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;룬샷은 잠재력을 가졌음에도 사람들이 그 가치를 무시하고 홀대하는 프로젝트를 뜻한다. 저자가 룬샷을 아기에 비유하여 설명하는 부분이 퍽 와닿았다. 아기는 때 묻지 않은 순수한 존재로서 엄청난 가능성을 품고 있지만, 보채기도 심하고 예측할 수 없다. 밤에는 우리를 잠 못 들게 한다. 아기는 어른의 보호를 받지 못하면 사회의 일원이 될 수 없다. 가진 가치와 상관없이 조롱의 대상인 룬샷도 누군가의 보호를 받아야만 빛을 볼 수 있다.&lt;/p&gt;

&lt;p&gt;우리가 혁신이라고 이야기하는 것들의 초기 모습은 대게 룬샷이다. 혁신을 만드는 아이디어는 급진적일 때가 많고, 등장하는 시점에는 그것이 무엇이 될지 알기 어렵다. 혁신인 것과 아닌 것을 결과로 구분할 수밖에 없다. 룬샷은 애초에 회의론으로 가득한 마구간 안에서 태어날 운명을 가졌다.&lt;/p&gt;

&lt;p&gt;이 책은 룬샷을 잘 보호하고 육성하여 혁신을 이뤄낸 사례와 룬샷을 육성하지 못하고 실패한 사례를 나열하며 룬샷의 가치를 조명한다. 그렇다면 우리는 모두 룬샷을 따라야 하는가? 그럴 리가. 뭐든 지나치면 좋지 않다는 건 진리에 가까운 말일지도. 저자는 룬샷을 찾는 데에만 지나치게 몰두한 나머지 조직을 구렁텅이로 밀어 넣은 사례를 보여주며 ‘균형’을 잡는다.&lt;/p&gt;

&lt;p&gt;룬샷을 보호하고 육성할 수 있는 환경을 잘 설계하되 지나침의 함정에 빠지지 말라는, 어디에서 본듯한 진부한 결론이 이 책의 주제다. 그럼에도 물리학자 출신인 저자가 물리 이론에 빗대어 풀어가는 경영학은 전개가 되게 신선했다. 공동체 규모가 커지면서 혁신이 발생하기 어려운 이유를 방정식으로 풀어낼 때는 지독하다는 생각도 들었다.&lt;/p&gt;

&lt;p&gt;다만 저자와 나의 지식수준 차가 큰 건 어쩔 수 없는지, 낯선 용어가 자주 등장해서 완독하기가 쉽지 않았다. 주장의 근거로 많은 사례를 펼쳐놓는데, 등장 인물의 이름마저 낯설다 보니 진도가 안 나가더라. 앞의 맥락이 잘 생각이 나질 않아서 같은 페이지를 여러 번 읽기를 반복하느라 이 책을 3주나 잡고 있었네?&lt;/p&gt;

&lt;p&gt;대단한 통찰을 보았지만, 선뜻 다시 읽을 용기가 나지는 않는, 그런 책인 걸로.&lt;/p&gt;

</description>
            <pubDate>Tue, 08 Mar 2022 00:00:00 +0900</pubDate>
            <link>https://huns.me/posts/2022-03-08-40-룬샷</link>
            <guid isPermaLink="true">https://huns.me/posts/2022-03-08-40-룬샷</guid>
            
            <category>책리뷰</category>
            
            
            <category>책리뷰</category>
            
        </item>
        
        <item>
            <title>(리뷰)팀 토폴로지 -  빠른 업무 플로우를 만드는 조직 설계</title>
            <description>&lt;p&gt;&lt;img src=&quot;/assets/images/new/2022-02-14-39-background.jpeg&quot; /&gt;&lt;/p&gt;

&lt;p&gt;올해 4번째로 읽은 책, &lt;a href=&quot;https://book.naver.com/bookdb/book_detail.nhn?bid=17689088&quot;&gt;팀 토폴로지&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;콘웨이는 일찍이 “조직은 자신의 커뮤니케이션 경로를 반영한 설계를 하도록 제약받는다”라고 했다. 소프트웨어 아키텍처는 소프트웨어를 개발하는 조직의 커뮤니케이션 역학을 거스르지 못한다는 뜻이다.&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;콘웨이의 법칙이라고 가장 많이 알려진 구절은 다음과 같다.&lt;/p&gt;

  &lt;p&gt;‘조직은 자신의 커뮤니케이션 경로를 반영한 설계를 하도록 제약 받는다.’&lt;/p&gt;

  &lt;p&gt;콘웨이는 초기 전자 컴퓨터 시스템을 구현한 기업들을 관찰했다. 콘웨이가 말한 법칙이란 조직 커뮤니케이션의 실제 경로(플래깅이 언급한 가치 창출 구조)와 조직이 개발한 소프트웨어 아키텍처 사이의 강한 관성, 혹은 알란 켈리(Allan Kelly)가 준동형의 힘(homorphic force)이라 부른 것을 의미한다. 준동형의 힘은 소프트웨어 아키텍처와 팀 구조 사이의 모든 것을 동일한 형태로 만드는 경향이 있다. 다시 말해, 실현 가능한 소프트웨어 아키텍처를 올바로 선택해 구현하려면 팀 커뮤니케이션에 관한 이해부터 선행돼야 한다. 만일 만들고자 하는 이론적 시스템 구조와 실질적 조직 모델이 상충하면 둘 중 하나를 바꿔야 한다.&lt;/p&gt;

  &lt;p&gt;&lt;cite&gt;팀 토폴로지 &amp;gt; 부활한 콘웨이의 법칙 - P.45&lt;/cite&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;소프트웨어를 설계할 때는 조직 커뮤니케이션 구조를 함께 고민해야 한다. 기술, 사람, 그리고 기술과 사람 사이에 존재하는 역학까지. 아키텍트는 설계의, 기술적, 사회적, 환경적 측면을 모두 다뤄야 한다. 이게 안 되면 문제의 아주 지엽적인 면만 겨우 건드리거나, 문제랑 동떨어진, 현실성 없는 해결책을 만들 수밖에 없다. 그렇게 설계는 실패한다.&lt;/p&gt;

&lt;p&gt;1장은 주로 문제를 정의하며 콘웨이 법칙의 중요성을 강조하는데, 경험적으로 공감 가는 내용이 정말 많았다. 내가 책을 쓰면 이렇게 썼을 것 같다 싶을 정도. 2~3장에서는 팀 커뮤니케이션 설계 대안으로 4가지 기본 팀 토폴로지와 3가지 핵심 팀 상호 작용 모드를 제안한다. 이게 핵심. 의도적으로 단순화 한 설계 패턴을 팀 빌딩 블록으로 제시하는데, 이 단순한 조합이 만들어내는 유연성과 탄력성은 많은 인사이트를 준다.&lt;/p&gt;

&lt;p&gt;그런데 번역이 너무 아쉽다. 번역만 아니면 인생 도서에 올릴 책인데. 문장을 여러번 곱씹어야 겨우 저자의 의도를 이해할 수 있더라. 나중에 번역을 개선한 개선판이 나와주면 좋겠다.&lt;/p&gt;
</description>
            <pubDate>Mon, 14 Feb 2022 00:00:00 +0900</pubDate>
            <link>https://huns.me/posts/2022-02-14-39-팀토폴로지</link>
            <guid isPermaLink="true">https://huns.me/posts/2022-02-14-39-팀토폴로지</guid>
            
            <category>책리뷰</category>
            
            
            <category>책리뷰</category>
            
        </item>
        
        <item>
            <title>메타버스에 대한 몇 가지 끄적임</title>
            <description>&lt;p&gt;&lt;img src=&quot;/assets/images/new/2022-01-29-38-background.jpeg&quot; /&gt;&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;
    &lt;p&gt;메타버스는 개념이 모호하고 여러 가지가 뒤얽혀서 진짜를 알기 쉽지 않다. 솔루션은 많지만 문제는 희소하다. 아이디어는 많지만 실체는 부족하다. 열풍에는 분명 거품이 껴 있다.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;거품이 나쁘다는 건 아니다. 거품 안에 기회가 있다는 건 과거의 여러 사례가 증명하니깐. 거품이 생길 때 투자가 일어나고 인프라가 폭발적으로 확장되지 않았나. 인간의 욕망이 여기로 모이고 있다는 게 중요하다.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;거품의 끝에 뭐가 남을지 궁금하다. 그게 이 현상의 본질일 테니까. 본질을 잘 찌르는 기업이 승리하겠지. 페이스북이 소셜을 먹었듯이. 어쩌면 그날의 메타버스는 우리가 지금 아는 거랑 다른 모습일 수도 있겠다는 상상을 해본다. 지금 쏟아지는 것들이 1-2년 성숙되어야 뭐가 좀 보일 것 같다. 로블록스나 제페토 같은 비즈니스가 지속 성장 가능할지도 관건이고. 시시해지면 곧 사라질지도 모르지 않나.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;10대 취항의 아바타형 메타버스가 시장을 이끌고 있지만, 10~20대를 넘어서 30~60대, 그 이상의 참여를 이끌어내야 하지 않을까? 이건 어떻게 풀려나? 이걸 해내지 못하면 결국 마이너 아닌가? 네이버 같은 기업한테는 특히 중요한 문제일 듯.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;메타버스의 개념이 혼란해서 그런지 상대적으로 기술은 선명해 보인다. 교집합에 있는 녀석이 가장 본질에 가깝지 않을까? 3D, XR, AI, 클라우드, NFT 등등등. 이거 그냥 다 가져다 끌어올 기세다. 게임이 이런 요소를 많이 가지고 있다는 데 동의. MS의 액티비전 인수는 임팩트가 크다.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;성장이 정체되어 고심하던 게임 업계가 이 기회를 놓칠 리 없지. 역시나 너도나도 뛰어들며 탑을 쌓는 중. 어떻게 보면 FOMO 같기도. 시선을 잡아끄는 데는 성공.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;근데 아이디어가 게임스러운 모습을 하고 있다고 이게 게임인가 하면 그건 또 아닌 것 같고. 산업과 기술이 폭넓게 융합하면서 디지털 전환 속도가 빨라지고 있다는 관점에서 봐야 하지 않을까? 게임 회사 입장에서도 전에 경험하지 못한 영역으로 뛰어드는 상황이다. 유리하기만 한 경기는 아닐터. 결핍을 어떤 식으로 해소할지 지켜보는 것도 흥미로울 듯.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;이런 상황에서는 용어보다는 기술에 집중하는 게 어떨까? 손에 잡히는 것들을 다양하게 엮는 상상을 하고, 상상을 빠르게, 많이, 만들어 보는 게 좋은 전략이라는 생각이 든다. 물론 기업은 바깥에 뻥카를 잘 쳐야 할 거고. 있어 보이게. 답을 찾을(은) 것처럼.&lt;/p&gt;
  &lt;/li&gt;
&lt;/ol&gt;

</description>
            <pubDate>Sat, 29 Jan 2022 00:00:00 +0900</pubDate>
            <link>https://huns.me/posts/2022-01-29-38</link>
            <guid isPermaLink="true">https://huns.me/posts/2022-01-29-38</guid>
            
            <category>단상</category>
            
            
            <category>단상</category>
            
        </item>
        
        <item>
            <title>2022년에 잘하고 싶은 두 가지</title>
            <description>&lt;p&gt;올해 잘하고 싶은 두 가지.&lt;/p&gt;

&lt;h3 id=&quot;1어깨에-힘-빼기&quot;&gt;1.어깨에 힘 빼기&lt;/h3&gt;

&lt;p&gt;회사에서 하는 일이, 치열함보다는 즐거움에 더 가까웠으면 좋겠다. 팀이 가치를 만들어가는 과정이 고통스럽거나, 누군가의 희생에 의지하지 않기를 바란다. 풀어야 하는 문제와 우리가 가진 역량 사이에서 균형점을 잘 찾아야 한다. 12척도 안 되는 배를 가지고 적과 싸운다. 전선을 최대한 좁히고 유리한 위치를 찾아서 자리를 잡자. 어렵지만 지난주에 약간 힌트를 얻은 것 같다.&lt;/p&gt;

&lt;h3 id=&quot;2-인생을-좀-더-즐기기&quot;&gt;2. 인생을 좀 더 즐기기&lt;/h3&gt;

&lt;p&gt;올해는 꼭 가족과 함께 할 수 있는 소소한 취미를 찾고 싶다. 인생이 짧다고 생각했는데, 나이를 먹을수록 더 짧게 느껴진다. 문득 일을 뺀 내 삶이 참 재미없다는 생각이 들었다. 가족이 생기면서 훨씬 나아졌지만 그래도 삶은 지루하다. 생각해 보면 제대로 된 취미 하나 없네? 올해는 꼭 해도해도 질리지 않는 소소한 취미를 찾아서, 줘도줘도 아깝지 않은 가족과 함께 즐기고 싶다.&lt;/p&gt;
</description>
            <pubDate>Mon, 10 Jan 2022 00:00:00 +0900</pubDate>
            <link>https://huns.me/posts/2022-01-10-37</link>
            <guid isPermaLink="true">https://huns.me/posts/2022-01-10-37</guid>
            
            <category>단상</category>
            
            
            <category>단상</category>
            
        </item>
        
        <item>
            <title>API Gateway + AWS Lambda에서 바이너리를 반환하면 왜 CORS 에러가 발생하죠?</title>
            <description>&lt;p&gt;지난 주에는, Handlerbar 템플릿을 PDF 바이너리로 변환하여 내려주는 간단한 서버 API를 만들었다. 운영 비용을 줄일 생각으로 Serverless 프레임워크(이하 Serverless)로 개발을 해서 AWS Lambda 환경에 배포를 했다. Serverless는 여러 서버리스 인프라 프로바이더를 하나의 인터페이스로 추상화 한 API를 제공하는 프레임워크다. 몇 가지 간단한 설정을 하고 구현 로직을 만들어서 제공하면 빌드, 배포를 모두 알아서 해준다.&lt;/p&gt;

&lt;p&gt;오래 전에 동료가 Serverless를 이용해서 구현한 다른 기능이 잘 돌아가고 있고 빨리 문제를 해결하고 싶었던터라 크게 고민하지 않고 기존 코드를 참고해서 구현을 했다. 레거시랑 Getting Started 정도만 읽어보고 금방 코드를 만들어서 배포할 수 있었다.&lt;/p&gt;

&lt;p&gt;하지만 이런 도구는 내부 원리를 잘 모르면 사용 중에 문제가 생겼을 때 문제를 해결하기가 쉽지 않다. 특히나 AWS 의 클라우드 인프라는 컴포넌트를 다양하게 조합할 수 있다보니, 각 인프라의 동작 방식을 잘 모르면 문제가 생겼을때 미로에 빠지기 쉽다. 이번에 겪은 문제도 그랬다.&lt;/p&gt;

&lt;p&gt;빠르게 만들어서 API를 배포했지만, 클라이언트가 API를 호출하면 브라우저에서 CORS 에러가 발생했다. CORS 설정 가이드를 다시 한 번 확인하고, 설정을 하고, 테스트 하기를 수차례. 해결이 되지 않았다.&lt;/p&gt;

&lt;p&gt;잘 안 풀릴 때는 침착하게 공식 문서를 읽어보는 게 문제를 더 빨리 해결하는 길이라는 걸 경험으로 터득했다. 결국 가이드 문서를 정독해서 내부 구현 원리를 이해하고 나서야 원인을 특정할 수 있었다. 이번에는 경험치가 빨리 동작해서 다행이었다.&lt;/p&gt;

&lt;p&gt;이 글은 Serverless를 이용해 구현한 바이너리 반환 API를 AWS의 API Gateway + AWS Lambda 환경에 배포한 후에 발생한 CORS 에러의 원인을 추적하고 해결한 과정을 기록한다.&lt;/p&gt;

&lt;h2 id=&quot;-현상은-이렇습니다&quot;&gt;😠 현상은 이렇습니다.&lt;/h2&gt;

&lt;p&gt;Lambda에 배포한 코드는 대충 이런 모습이다. 이 함수는 컨트롤러의 역할을 하며, 모든 로직은 toPdfFromHtml 안에 들어있다.&lt;/p&gt;

&lt;div class=&quot;language-javascript highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;template&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;JSON&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;parse&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;event&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;body&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;pdf&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;toPdfFromHtml&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;template&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;...&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;stampImageHref&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;data&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;?&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;stampImageHref&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;''&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;});&lt;/span&gt;

&lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;response&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;APIGatewayProxyResult&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;statusCode&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;200&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;headers&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;Access-Control-Allow-Origin&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;Content-Type&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;application/pdf; charset=utf-8&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;Content-Length&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;Buffer&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;byteLength&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;pdf&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt;
    &lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;Content-Disposition&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;`attachment; filename=&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;encodeURI&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;template&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;.pdf&quot;`&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;
  &lt;span class=&quot;c1&quot;&gt;// NOTE:&lt;/span&gt;
  &lt;span class=&quot;c1&quot;&gt;// https://stackoverflow.com/questions/45348580/aws-lambda-fails-to-return-pdf-file&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;body&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;pdf&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;};&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;response&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Serverless는 이 코드를 빌드하여 Lambda에 배포하고 API Gateway에 연결된 이런 엔드포인트를 만들어 준다.&lt;/p&gt;

&lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;https://&amp;lt;rest_api_id&amp;gt;-&amp;lt;vpc_endpoint_id&amp;gt;.execute-api.&amp;lt;aws_region&amp;gt;.amazonaws.com
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;이제 클라이언트에서 이 API를 호출하면 끝! … 이라고 생각을 했지만, 테스트 과정에서 CORS 에러가 발생했다.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/images/new/44-1.png&quot; alt=&quot;CORS 에러가 발생하고 있음을 보여주는 네트워크 탭&quot; /&gt;&lt;/p&gt;

&lt;p&gt;누구든 CORS 에러를 만나면 가장 먼저 CORS 설정을 살펴보지 않을까.&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;https://www.serverless.com/blog/cors-api-gateway-survival-guide&quot;&gt;Your CORS and API Gateway survival guide&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;나 역시 CORS 설정을 잘못 했나 싶어서 위의 가이드를 다시 보면서 코드를 수정하고 테스트를 했지만 해결을 할 수 없었다. 그렇게 쉽게 문제를 풀었다면 이 글을 쓰지 않았겠지. 삽질이 기운이 스멀스멀 피어오른다.&lt;/p&gt;

&lt;h2 id=&quot;-cloudfront는-왜&quot;&gt;🤔 CloudFront는 왜…?&lt;/h2&gt;

&lt;p&gt;해결이 안 되자 조급해지는 마음을 잠시 가다듬고 네트워크 탭을 뚫어져라 쳐다보며 단서를 수집했다.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/images/new/44-2.png&quot; alt=&quot;Lambda 함수에서 'Access-Control-Allow-Origin': '*'를 응답 헤더에 반환했는데 CORS 에러 로그를 보면 이 헤더가 없음을 보여주는 네트워크 탭 상세보기 화면&quot; /&gt;&lt;/p&gt;

&lt;p&gt;어라, Lambda 함수에서 ‘Access-Control-Allow-Origin’: ‘*‘를 응답 헤더에 반환했는데 CORS 에러 로그를 보면 이 헤더가 없다. 어디로 간걸까? 그러고보니 에러 메시지의 출처가 CloudFront네!?&lt;/p&gt;

&lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;x-amzn-errortype: InternalServerErrorException
x-cache: Error from cloudfront
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;내가 배포한 Serverless 구성이 “API Gateway → Lambda”라고 생각했는데, CloudFront는 어디에서 나타난걸까. Serverless가 만들어 준 인프라 구조를 내가 잘 모르고 있다는 걸 깨달았다. 대충 알아서 해주겠거니…하는 생각에 코드만 보고 문서를 안 읽었다. AWS 문서를 하나씩 읽어보기 시작했다. 머지 않아 힌트를 찾았다.&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;https://docs.aws.amazon.com/apigateway/latest/developerguide/api-gateway-api-endpoint-types.html&quot;&gt;AWS는 API Gateway로 3가지 유형의 Endpoint&lt;/a&gt;를 제공하는데 이 중에 Edge-optimized API Endpoints가 Serverless의 기본 값이었다.&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Edge-optimized API endpoints&lt;/li&gt;
  &lt;li&gt;Regional API endpoints&lt;/li&gt;
  &lt;li&gt;Private API endpoints&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Edge-optimized API Endpoints는 URL을 CloudFront에 연결한다. 이름에서 최적화 삘이 난다. Serverless의 설정 타입을 보면 endpointType 프로퍼티의 유니온 타입을 이렇게 정의하고 있다.&lt;/p&gt;

&lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;endpointType?: 'regional' | 'edge' | 'private' | undefined
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;딱딱 맞는다. Serverless 가이드 문서 역시, 기본 값은 edge라고 명시한다.&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;By default, the Serverless Framework deploys your REST API using the EDGE endpoint configuration. If you would like to use the REGIONAL or PRIVATE configuration, set the endpointType parameter in your provider block.&lt;/p&gt;

  &lt;p&gt;&lt;cite&gt;https://www.serverless.com/framework/docs/providers/aws/events/apigateway#configuring-endpoint-types&lt;/cite&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;내가 착각을 했다. “API Gateway → Lambda”가 아니라 “CloudFront → API Gateway → Lambda” 구조였다.&lt;/p&gt;

&lt;p&gt;가설을 세웠다. CloudFront가 요청을 릴레이 하는 과정에서 API Gateway에서 에러가 발생하자 자신이 대신 에러 응답을 반환했다. 이 과정에서 Lambda가 반환한 cors 헤더를 유실했고, 브라우저는 cors 헤더가 없으니 에러를 발생시켰다.&lt;/p&gt;

&lt;p&gt;그렇다면 CloudFront 뒤에 숨어 음모를 꾸미는 녀석은 누굴까?&lt;/p&gt;

&lt;h2 id=&quot;진짜-범인은&quot;&gt;진짜 범인은…!&lt;/h2&gt;

&lt;p&gt;Lambda 함수에서 출력하는 로그는 CloudWatch에 남는다. 로그를 뒤졌다. 로그는 Lambda가 무죄라는 걸 증명했다.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/images/new/44-3.png&quot; alt=&quot;CloutWatch에 남은 람다 로그&quot; /&gt;&lt;/p&gt;

&lt;p&gt;이제 남은 건 API Gateway. 심증은 가나 물증이 없다. 원인을 특정할 수 없어서 Lambda가 반환하는 응답 개체의 프로퍼티를 하나씩 제거하면서 변화가 생기는지 관찰했다. 어라, body에 빈 문자열을 주면 CORS 에러가 발생하지 않는다. 그렇지.&lt;/p&gt;

&lt;div class=&quot;language-javascript highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;template&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;JSON&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;parse&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;event&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;body&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;pdf&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;toPdfFromHtml&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;template&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;...&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;stampImageHref&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;data&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;?&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;stampImageHref&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;''&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;});&lt;/span&gt;

&lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;response&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;APIGatewayProxyResult&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;statusCode&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;200&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;headers&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;Access-Control-Allow-Origin&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;Content-Type&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;application/pdf; charset=utf-8&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;Content-Length&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;Buffer&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;byteLength&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;pdf&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt;
    &lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;Content-Disposition&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;`attachment; filename=&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;encodeURI&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;template&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;.pdf&quot;`&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;body&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;''&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;};&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;response&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Lambda와 API Gateway 사이에서 범인의 냄새를 맡았다.&lt;/p&gt;

&lt;p&gt;위의 코드에서 pdf는 바이너리 버퍼 타입이다. 바이너리를 반환하는 게 문제일까? Lambda에서 바이너리를 반환할 수 없는 건가? 구글링을 하다가 공식 문서를 보고서야 바이너리 타입은 base64로 인코딩을 해야한다는 걸 깨달았다. 아뿔싸!&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;https://docs.aws.amazon.com/ko_kr/apigateway/latest/developerguide/api-gateway-payload-encodings.html&quot;&gt;REST API에 대한 이진 미디어 유형 작업&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;API Gateway에서 API 요청과 응답은 텍스트 또는 이진 페이로드를 포함합니다. 텍스트 페이로드는 UTF-8 인코딩 형식의 JSON 문자열입니다. 이진 페이로드는 텍스트 페이로드를 제외한 페이로드입니다. 예를 들어 JPEG 파일, GZip 파일, XML 파일 등이 이진 페이로드가 될 수 있습니다. 이진 미디어를 지원하는 데 필요한 API 구성은 API가 프록시 통합을 사용하는지 아니면 비 프록시 통합을 사용하는지에 따라 달라집니다.&lt;/p&gt;

  &lt;p&gt;&lt;cite&gt;https://docs.aws.amazon.com/ko_kr/apigateway/latest/developerguide/api-gateway-payload-encodings.html&lt;/cite&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;바이너리를 base64로 인코딩 해야 하는 이유는 API Gateway의 콘텐츠 유형 변환 규칙 때문이다. 이 내용 역시, 아래의 가이드 문서에 잘 나와 있다.&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;https://docs.aws.amazon.com/ko_kr/apigateway/latest/developerguide/api-gateway-payload-encodings-workflow.html&quot;&gt;API Gateway의 콘텐츠 유형 변환&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;문서를 보면 변환 규칙을 이런 식으로 테이블로 정의해서 안내하고 있다.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/images/new/44-4.png&quot; alt=&quot;콘텐츠 유형 변환 테이블 스크린샷&quot; /&gt;&lt;/p&gt;

&lt;p&gt;API Gateway는 HTTP Payload(Body), Content-type, binaryMediaTypes, contentHandling 설정을 참고해서 런타임에 HTTP Body의 인코딩 타입을 결정한다.&lt;/p&gt;

&lt;h2 id=&quot;그래서-해결은&quot;&gt;그래서 해결은?&lt;/h2&gt;

&lt;p&gt;앞에서 언급한 내용을 종합하면, API Gateway로 들어오고 나가는 HTTP Body는 텍스트나 바이너리를 가질 수 있는데, 이 때 개발자는 개발을 할 때 다음 세 가지 제약을 따라야 한다.&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;텍스트는 UTF-8로 인코딩 한 JSON 문자열이어야 한다.&lt;/li&gt;
  &lt;li&gt;바이너리는 base64로 인코딩 한 값이어야 한다.&lt;/li&gt;
  &lt;li&gt;바이너리로 취급할 콘텐츠 유형을 binaryMediaTypes에 명시해야 한다.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;모두 세 군데를 수정하는 걸로 문제를 해결할 수 있었다.&lt;/p&gt;

&lt;p&gt;1) 응답에 base64로 인코딩한 body를 추가하고, isBase64Encoded 속성을 true로 설정해서 API Gateway에게 이 사실을 알려준다.&lt;/p&gt;

&lt;div class=&quot;language-javascript highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;response&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;APIGatewayProxyResult&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
	&lt;span class=&quot;na&quot;&gt;statusCode&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;200&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
	&lt;span class=&quot;na&quot;&gt;headers&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;Access-Control-Allow-Origin&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;Content-Type&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;application/pdf; charset=utf-8&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;Content-Length&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;encodedPdf&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;length&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;Content-Disposition&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;`attachment; filename=&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;encodeURI&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;template&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;.pdf&quot;`&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
	&lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;
	&lt;span class=&quot;c1&quot;&gt;// NOTE:&lt;/span&gt;
	&lt;span class=&quot;c1&quot;&gt;// https://stackoverflow.com/questions/45348580/aws-lambda-fails-to-return-pdf-file&lt;/span&gt;
	&lt;span class=&quot;na&quot;&gt;body&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;encodedPdf&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;isBase64Encoded&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;2) serverless.ts의 apiGateway 설정에 binaryMediaTypes 설정을 추가한다.&lt;/p&gt;

&lt;div class=&quot;language-javascript highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nx&quot;&gt;provider&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;nl&quot;&gt;apiGateway&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;nl&quot;&gt;binaryMediaTypes&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;application/pdf&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;3) HTTP Request를 요청할 때 Content-type 헤더를 바이너리 타입으로 설정(나의 경우 application/json)한다.&lt;/p&gt;

&lt;div class=&quot;language-javascript highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nx&quot;&gt;headers&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;content-type&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;application/json&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;그러면 응답 콘텐츠 유형 변환 테이블에서 아래에 셀렉트 하여 표시한 조건이 동작하고,&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/images/new/44-5.png&quot; alt=&quot;콘텐츠 유형 변환 테이블에서 이진 데이터, 이진수 형식, 일치하는 미디어 유형이 있는 세트, 정의되지 않음, 이진 데이터 항목이 하이라이팅 되어 있음&quot; /&gt;&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;통합 응답 페이로드: application/pdf → 이진 데이터&lt;/li&gt;
  &lt;li&gt;요청 Accept 헤더: application/pdf → 이진수 형식&lt;/li&gt;
  &lt;li&gt;binaryMediaTypes: application/pdf → 일치하는 미디어 유형이 있는 세트&lt;/li&gt;
  &lt;li&gt;contentHandling: 정의되지 않음&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;네트워크 요청에 성공해서 200 응답을 받고,&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/images/new/44-6.png&quot; alt=&quot;요청이 성공해서 200 응답을 받았음을 보여주는 네트워크 상세보기 화면&quot; /&gt;&lt;/p&gt;

&lt;p&gt;API Gateway가 보낸 예쁜(?) PDF 바이너리를 받을 수 있다.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/images/new/44-7.png&quot; alt=&quot;API Gateway의 응답으로 받은 바이너리&quot; /&gt;&lt;/p&gt;

&lt;p&gt;이때 Request의 Accept를 지정하지 않으면 아래 조건에 걸려서 Base64로 인코딩한 문자열을 반환해버리는데 이 또한 정해진 콘텐츠 유형 변환 규칙을 적용받기 때문이다.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/images/new/44-8.png&quot; alt=&quot;콘텐츠 유형 반환 테이블에서 Base64로 인코딩된 문자열을 반환하는 조건 하이라이팅&quot; /&gt;&lt;/p&gt;

&lt;p&gt;API Gateway가 기대한 결과를 돌려주지 않는다면 이 테이블을 참고해서 설정을 잘 했는지 확인할 수 있다.&lt;/p&gt;
</description>
            <pubDate>Tue, 20 Jul 2021 00:00:00 +0900</pubDate>
            <link>https://huns.me/2021-07-20-44</link>
            <guid isPermaLink="true">https://huns.me/2021-07-20-44</guid>
            
            <category>aws, apigateway, lambda, cors, serverless</category>
            
            
            <category>development</category>
            
        </item>
        
        <item>
            <title>리팩터링에 대한 단상</title>
            <description>&lt;p&gt;&lt;img src=&quot;/assets/images/new/2020-05-06-36-background.jpeg&quot; /&gt;&lt;/p&gt;

&lt;p&gt;팀 스터디로 &lt;a href=&quot;http://www.yes24.com/Product/Goods/89649360?scode=032&amp;amp;OzSrank=1&quot;&gt;“리팩터링 2판의 Chapter 02 - 리팩터링 원칙”&lt;/a&gt;을 읽다가 떠오르는 생각을 정리한 글입니다.&lt;/p&gt;

&lt;hr /&gt;

&lt;h1 id=&quot;리팩터링의-정의&quot;&gt;리팩터링의 정의&lt;/h1&gt;

&lt;p&gt;리팩터링은 &lt;u&gt;&quot;소프트웨어의 겉보기 동작은 그대로 유치한 채, 코드를 이해하고 수정하기 쉽도록 내부 구조를 변경하는 기법&quot;&lt;/u&gt;을 말한다. 변경에 유리한 설계를 확보하면 이후의 작업을 더 쉽게 할 수 있다. 궁극적으로 개발 속도를 높여서, 더 적은 노력으로 더 많은 가치를 창출하려는 활동이다. 따라서 리팩터링은 도덕이 아닌 경제의 관점에서 접근을 해야 한다. 현실에서는 리팩터링을 도덕의 문제로 이야기하는 경우를 많이 보지만.&lt;/p&gt;

&lt;h1 id=&quot;리팩터링의-딜레마&quot;&gt;리팩터링의 딜레마&lt;/h1&gt;

&lt;p&gt;설계를 잘하면 개발 속도가 더 빨라진다는 주장을 책의 저자인 마틴 파울러는 &lt;u&gt;설계 지구력 가설(Design Stamina Hypothesis)&lt;/u&gt;이라고 부른다. 가설인 이유는 그런 것 같다고 느끼는 사람은 많지만 이를 수치로 증명하지 못했기 때문이다. 경제의 문제임에도 현실에서 리팩터링의 가치를 수치로 증명하기는 어렵다. 우리가 부족해서가 아니다. 이 동네가 원래 그렇다. 개발자의 생산성을 증명하기 어려운 것과 같다.&lt;/p&gt;

&lt;p&gt;어쨌든 리팩터링은 경제의 문제이기에 수고 대비 얻을 수 있는 &lt;u&gt;효과가 분명한 영역에서, 필요한 수준&lt;/u&gt;으로 행해져야 한다. 속도보다 품질이 더 중요한 순간, 또는 품질을 높여서 속도를 더 증가시킬 수 있을 때 리팩터링을 한다. 효용을 수치로 증명할 수 없는데, 경제적으로 판단을 해야 한다? 이것이 리팩터링의 딜레마다.&lt;/p&gt;

&lt;p&gt;어렵지만 개발자는 매 순간 판단할 수밖에 없다. 리팩터링을 할 것인가 말 것인가. 판단을 하려면 알아야 한다. 아는 만큼 본다. 알려면 공부를 하고 경험을 해야 한다. 그래도 수치로 증명하기는 어렵다. 그저 설득을 할 수 있을 뿐. 이 지점에서 리팩터링은 정치의 영역과 맞닿아 있다.&lt;/p&gt;

&lt;h1 id=&quot;리팩터링과-개발-프로세스&quot;&gt;리팩터링과 개발 프로세스&lt;/h1&gt;

&lt;p&gt;애자일의 실천 사례 중 하나인 XP는 필요할 때까지 의사결정을 미루는 걸 지향한다. 섣불리 미래를 가정 하지 않는다. 대신에 지금 확실히 알 수 있는 것에 집중한다. 미래가 현실이 되었을 때, 그때야 비로소 의사결정을 한다. 대신에 피드백을 빨리 받는 전략을 택한다. 불확실한 미래로 가득한 환경에서 애자일은 그렇게 낭비를 줄이고, 점진적으로 목표를 향해 나아간다.&lt;/p&gt;

&lt;p&gt;이런 개발 프로세스 아래에서는 설계도 점진적으로 만들어져야 한다. 자주 바뀌는 정책을 계속 반영해야 한다. 점진적으로 설계를 하려면 리팩터링은 필수다. 리팩터링을 자주 하려면 테스트 비용을 최소화해야 한다. 그렇지 않으면 변경 비용을 감당할 수 없다. 적정 수준의 설계, 리팩터링, 그리고 테스트 자동화는 떼어놓을 수 없다.&lt;/p&gt;

&lt;p&gt;불확실한 상황을 적응적으로 헤쳐나가려는 최근의 개발 프로세스와 리팩터링은 이렇게 연결되어 있다.&lt;/p&gt;

&lt;h1 id=&quot;리팩터링은-언제-해야-할까&quot;&gt;리팩터링은 언제 해야 할까?&lt;/h1&gt;

&lt;p&gt;책의 저자인 마틴 파울러는 리팩터링을 하기 좋은 시기를 세 단계로 구분한다.&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;b&gt;준비를 위한 리팩터링&lt;/b&gt;: 새로운 기능을 추가하기 전에 기존 부채를 해소하고 설계를 개선&lt;/li&gt;
  &lt;li&gt;&lt;b&gt;이해를 위한 리팩터링&lt;/b&gt;: 코드를 분석할 때 리팩터링을 하면서 코드를 리뷰&lt;/li&gt;
  &lt;li&gt;&lt;b&gt;쓰레기 줍기 리팩터링&lt;/b&gt;: 다른 작업을 하던 중에 발견한 문제를 해결하고 지나가기&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;사실 리팩터링은 거창한 활동이 아니다. 지역 변수의 이름을 바꾸거나, 변수의 선언 위치를 바꾸는 작은 행동 하나하나가 모두 리팩터링이다. 알게 모르게 우리는 시도 때도 없이 리팩터링을 하고 있다. 리팩터링은 코딩과 분리할 수 있는 별개의 과정이 아니다.&lt;/p&gt;

&lt;p&gt;수시로 리팩터링을 하는 게 가장 좋겠지만, 어렵다면 최소한 “코딩을 시작하기 전에 몸풀기로 리팩터링”을 하는 습관이라도 만들어 보면 어떨까? 일정을 추정할 때 리팩터링을 부작업으로 추가하고, 추정에 반영하는 것도 방법이다.&lt;/p&gt;

&lt;h1 id=&quot;리팩터링과-기술-부채&quot;&gt;리팩터링과 기술 부채&lt;/h1&gt;

&lt;p&gt;기술 부채는 주로 상황이 긴급해서 쌓인다. 몰라서 쌓는 기술 부채는 제외하자. 일단 돌아가게 만들고 나중에 수정하자고 합의하지만, 나중은 오지 않는다. 그렇게 부채는 쌓인다.&lt;/p&gt;

&lt;p&gt;물론 모든 기술 부채를 다 해소해야 하는 것은 아니다. 좋은 개발자라면 비즈니스 목표를 달성하기 위해서 속도와 품질 사이에서 적절한 균형을 찾아야 한다.&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;필요한 순간에 필요한 리팩터링을 한다.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;미루고 쌓아두었다가도 필요한 순간이 오면 필요한 리팩터링을 할 수 있어야 한다. 말은 쉽지만 필요한 순간을 포착하려면 아는 게 있어야 하고, 필요한 리팩터링을 하려면 &lt;u&gt;언제든 기술 부채를 해소할 수 있는 장치를 확보&lt;/u&gt;해 두어야 한다. 비즈니스 관계자를 설득하여 시간을 구할 수 있는 전략과 정치력도 가져야 한다.&lt;/p&gt;

&lt;p&gt;여기까지 생각을 정리하자 우리 팀에 필요한 세 가지가 보였다.&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;설계를 보는 눈 향상&lt;/li&gt;
  &lt;li&gt;기술 부채 관리 전략&lt;/li&gt;
  &lt;li&gt;테스트 자동화&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;1번은 열심히 공부하는 하는 걸로. 2번과 3번은 평소에 고민하고 있었는데 마침 기회가 닿아 동료와 따로 논의하는 자리를 만들기로 했다. 하나씩 부러뜨려 보자.&lt;/p&gt;
</description>
            <pubDate>Wed, 06 May 2020 00:00:00 +0900</pubDate>
            <link>https://huns.me/posts/2020-05-06-36</link>
            <guid isPermaLink="true">https://huns.me/posts/2020-05-06-36</guid>
            
            <category>리팩터링</category>
            
            <category>마틴파울러</category>
            
            <category>소프트웨어</category>
            
            <category>엔지니어링</category>
            
            
            <category>개발 이야기</category>
            
        </item>
        
        <item>
            <title>블랙박스 테스트 기법으로 테스트 케이스 설계하기</title>
            <description>&lt;p&gt;&lt;img src=&quot;/assets/images/new/2020-01-09-35-background.jpg&quot; /&gt;&lt;/p&gt;

&lt;p&gt;테스트 코드를 리뷰하다보면 아래와 같은 명세를 마주칠 때가 자주 있습니다.&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;문자열을 입력하면 적절한 값을 반환해야 한다.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;모든 “문자열”을 입력해도 되는 걸까요? “적절한 값”이란 무엇일까요? 만약 “문자열에 숫자를 포함시킬 수 없다.”라는 요구 사항이 있다면 어떨까요? 명세가 모호해서 코드를 읽어야만 테스트에 담긴 의도를 읽을 수 있습니다.&lt;/p&gt;

&lt;p&gt;하지만 아래의 테스트는 코드를 읽어도 무엇이 올바른 입력이고, 무엇이 올바른 결과인지 이해하기 어렵습니다. 테스트 명세에 모호한 단어가 보인다면 한 번쯤 의심해보는 게 좋습니다.&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-javascript&quot; data-lang=&quot;javascript&quot;&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1
2
3
4
5
6
7
8
9
10
&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;nx&quot;&gt;it&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;문자열을 입력하면 적절한 값을 반환해야 한다.&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;c1&quot;&gt;// given&lt;/span&gt;
  &lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;oldStr&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;hello world&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

  &lt;span class=&quot;c1&quot;&gt;// when&lt;/span&gt;
  &lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;newStr&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;removeSpace&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;oldStr&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;

  &lt;span class=&quot;c1&quot;&gt;// then&lt;/span&gt;
  &lt;span class=&quot;nx&quot;&gt;expect&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;newStr&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;toBe&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;helloworld&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;});&lt;/span&gt;
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;그리고 이 테스트 케이스 만으로는 removeSpace를 충분히 검증할 수 없습니다.&lt;/p&gt;

&lt;p&gt;좋은 테스트를 작성하려면 먼저 요구 사항을 제대로 식별해야 합니다. 식별한 요구 사항을 바탕으로 테스트 케이스를 충분히 만들어야 합니다. 즉, 테스트 케이스를 잘 설계해야 합니다.&lt;/p&gt;

&lt;p&gt;테스트 기법 중에 블랙박스 테스트라는 게 있습니다. 블랙박스 테스트는 세부 구현은 무시하고 입력 값에 대한 결과만 확인하는 테스트 기법입니다. 구현이 아닌 인터페이스를 테스트한다는 점에서 개발자 테스트가 지향해야 하는 바와 잘 맞습니다.&lt;/p&gt;

&lt;p&gt;이 글에서는 블랙박스 테스트를 설계하는 기법 중에서 제가 가장 쉽다고 생각하는 4가지를 간단히 살펴봅니다.&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;동등 분할&lt;/li&gt;
  &lt;li&gt;경계값 분석&lt;/li&gt;
  &lt;li&gt;결정 테이블&lt;/li&gt;
  &lt;li&gt;상태 전이&lt;/li&gt;
&lt;/ol&gt;

&lt;h1 id=&quot;1-동등-분할&quot;&gt;1. 동등 분할&lt;/h1&gt;

&lt;p&gt;동일한 결과를 만들 것으로 예상되는 입력 값의 범위를 정한 다음에 각 범위를 대표하는 값을 선택하는 방식입니다. 예를 들어 점수 범위에 따라 성적 등급을 반환하는 함수가 있을 때, 각 점수 범위의 대푯값을 선택하여 테스트 케이스를 정의하는 방식입니다.&lt;/p&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;점수 범위&lt;/th&gt;
      &lt;th&gt;대표 값&lt;/th&gt;
      &lt;th&gt;성적&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;100 - 91&lt;/td&gt;
      &lt;td&gt;95&lt;/td&gt;
      &lt;td&gt;수&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;90 - 81&lt;/td&gt;
      &lt;td&gt;83&lt;/td&gt;
      &lt;td&gt;우&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;80 - 71&lt;/td&gt;
      &lt;td&gt;72&lt;/td&gt;
      &lt;td&gt;미&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;70 - 61&lt;/td&gt;
      &lt;td&gt;68&lt;/td&gt;
      &lt;td&gt;양&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;60 - 0&lt;/td&gt;
      &lt;td&gt;12&lt;/td&gt;
      &lt;td&gt;가&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;p&gt;이 테이블을 테스트 코드로 이렇게 옮길 수 있습니다.&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-javascript&quot; data-lang=&quot;javascript&quot;&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;nx&quot;&gt;describe&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;성적 판단&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;nx&quot;&gt;it&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;점수가 95점이면 성적은 '수'여야 한다.&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;// given&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;score&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;95&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;// when&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;grade&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;decideGrade&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;score&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;

    &lt;span class=&quot;c1&quot;&gt;// then&lt;/span&gt;
    &lt;span class=&quot;nx&quot;&gt;expect&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;grade&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;toBe&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;수&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;});&lt;/span&gt;

  &lt;span class=&quot;nx&quot;&gt;it&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;점수가 83점이면 성적은 '우'여야 한다.&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;// given&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;score&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;95&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;// when&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;grade&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;decideGrade&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;score&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;

    &lt;span class=&quot;c1&quot;&gt;// then&lt;/span&gt;
    &lt;span class=&quot;nx&quot;&gt;expect&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;grade&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;toBe&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;수&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;});&lt;/span&gt;

  &lt;span class=&quot;nx&quot;&gt;it&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;점수가 72점이면 성적은 '미'여야 한다.&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;// given&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;score&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;95&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;// when&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;grade&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;decideGrade&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;score&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;

    &lt;span class=&quot;c1&quot;&gt;// then&lt;/span&gt;
    &lt;span class=&quot;nx&quot;&gt;expect&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;grade&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;toBe&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;수&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;});&lt;/span&gt;

  &lt;span class=&quot;nx&quot;&gt;it&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;점수가 68점이면 성적은 '양'여야 한다.&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;// given&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;score&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;95&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;// when&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;grade&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;decideGrade&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;score&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;

    &lt;span class=&quot;c1&quot;&gt;// then&lt;/span&gt;
    &lt;span class=&quot;nx&quot;&gt;expect&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;grade&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;toBe&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;수&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;});&lt;/span&gt;

  &lt;span class=&quot;nx&quot;&gt;it&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;점수가 12점이면 성적은 '가'여야 한다.&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;// given&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;score&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;95&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;// when&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;grade&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;decideGrade&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;score&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;

    &lt;span class=&quot;c1&quot;&gt;// then&lt;/span&gt;
    &lt;span class=&quot;nx&quot;&gt;expect&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;grade&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;toBe&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;수&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;});&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;});&lt;/span&gt;
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;h1 id=&quot;2-경계값-분석&quot;&gt;2. 경계값 분석&lt;/h1&gt;

&lt;p&gt;동등 분할은 대푯값을 추출함으로써 테스트 비용을 줄일 수 있다는 게 장점입니다. 하지만 입력 범위의 극단(Edge)에서 발생하는 오류를 검증하지 못합니다.&lt;/p&gt;

&lt;p&gt;경계값 분석은 대푯값의 범위를 값의 경계 범위까지 확장하여 이 문제를 해결합니다. 대신에 테스트 케이스를 조금 더 많이 만들어야 합니다. 에러는 놓치기 쉬운, 눈에 잘 띄지 않는 지점에서 많이 발생한다는 점을 생각해보면, 충분히 투자할만한 비용입니다.&lt;/p&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;점수 범위&lt;/th&gt;
      &lt;th&gt;경계 값&lt;/th&gt;
      &lt;th&gt;기대 결과&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;100 - 91&lt;/td&gt;
      &lt;td&gt;101&lt;/td&gt;
      &lt;td&gt;Error&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt; &lt;/td&gt;
      &lt;td&gt;100&lt;/td&gt;
      &lt;td&gt;수&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt; &lt;/td&gt;
      &lt;td&gt;91&lt;/td&gt;
      &lt;td&gt;수&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;90 - 81&lt;/td&gt;
      &lt;td&gt;90&lt;/td&gt;
      &lt;td&gt;우&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt; &lt;/td&gt;
      &lt;td&gt;81&lt;/td&gt;
      &lt;td&gt;우&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;80 - 71&lt;/td&gt;
      &lt;td&gt;80&lt;/td&gt;
      &lt;td&gt;미&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt; &lt;/td&gt;
      &lt;td&gt;71&lt;/td&gt;
      &lt;td&gt;미&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;70 - 61&lt;/td&gt;
      &lt;td&gt;70&lt;/td&gt;
      &lt;td&gt;양&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt; &lt;/td&gt;
      &lt;td&gt;61&lt;/td&gt;
      &lt;td&gt;양&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;60 - 0&lt;/td&gt;
      &lt;td&gt;60&lt;/td&gt;
      &lt;td&gt;가&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt; &lt;/td&gt;
      &lt;td&gt;0&lt;/td&gt;
      &lt;td&gt;가&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt; &lt;/td&gt;
      &lt;td&gt;-1&lt;/td&gt;
      &lt;td&gt;Error&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-javascript&quot; data-lang=&quot;javascript&quot;&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;nx&quot;&gt;describe&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;성적 판단&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;nx&quot;&gt;it&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;점수가 101점이면 Error를 반환해야 한다.&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;// given&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;score&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;100&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;// when&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;// then&lt;/span&gt;
    &lt;span class=&quot;nx&quot;&gt;expect&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;decideGrade&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;score&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)).&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;toThrow&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;InvalidValueError&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;});&lt;/span&gt;

  &lt;span class=&quot;nx&quot;&gt;it&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;점수가 100점이면 성적은 '수'여야 한다.&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;// given&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;score&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;100&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;// when&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;grade&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;decideGrade&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;score&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;

    &lt;span class=&quot;c1&quot;&gt;// then&lt;/span&gt;
    &lt;span class=&quot;nx&quot;&gt;expect&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;grade&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;toBe&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;수&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;});&lt;/span&gt;

  &lt;span class=&quot;nx&quot;&gt;it&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;점수가 91점이면 성적은 '수'여야 한다.&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;// given&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;score&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;91&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;// when&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;grade&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;decideGrade&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;score&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;

    &lt;span class=&quot;c1&quot;&gt;// then&lt;/span&gt;
    &lt;span class=&quot;nx&quot;&gt;expect&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;grade&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;toBe&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;수&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;});&lt;/span&gt;

  &lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;
  
&lt;span class=&quot;p&quot;&gt;});&lt;/span&gt;
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;h1 id=&quot;3-결정-테이블&quot;&gt;3. 결정 테이블&lt;/h1&gt;

&lt;p&gt;입력과 값을 연결하는 조건이 여러 경우의 수를 고려해야 한다면 의사결정 테이블을 이용할 수 있습니다. 예를 들어, 9급 공무원 시험의 합격 기준이 아래와 같다고 가정합시다.&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;행정학개론, 행정법, 영어, 국어, 국사 평균 80점 이상&lt;/li&gt;
  &lt;li&gt;20점 이하인 과목이 있으면 무조건 탈락&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;조건을 조합하는 이런 테이블을 만들어서 테스트 케이스를 도출하는 방식입니다.&lt;/p&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt; &lt;/th&gt;
      &lt;th&gt;행정학개론&lt;/th&gt;
      &lt;th&gt;행정법&lt;/th&gt;
      &lt;th&gt;영어&lt;/th&gt;
      &lt;th&gt;국어&lt;/th&gt;
      &lt;th&gt;국사&lt;/th&gt;
      &lt;th&gt;평균&lt;/th&gt;
      &lt;th&gt;결과&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;김코딩&lt;/td&gt;
      &lt;td&gt;80&lt;/td&gt;
      &lt;td&gt;80&lt;/td&gt;
      &lt;td&gt;80&lt;/td&gt;
      &lt;td&gt;80&lt;/td&gt;
      &lt;td&gt;80&lt;/td&gt;
      &lt;td&gt;80&lt;/td&gt;
      &lt;td&gt;합격&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;김따뜻&lt;/td&gt;
      &lt;td&gt;81&lt;/td&gt;
      &lt;td&gt;79&lt;/td&gt;
      &lt;td&gt;81&lt;/td&gt;
      &lt;td&gt;79&lt;/td&gt;
      &lt;td&gt;79&lt;/td&gt;
      &lt;td&gt;79.8&lt;/td&gt;
      &lt;td&gt;탈락&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;김냉정&lt;/td&gt;
      &lt;td&gt;100&lt;/td&gt;
      &lt;td&gt;100&lt;/td&gt;
      &lt;td&gt;100&lt;/td&gt;
      &lt;td&gt;100&lt;/td&gt;
      &lt;td&gt;20&lt;/td&gt;
      &lt;td&gt;84&lt;/td&gt;
      &lt;td&gt;탈락&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-javascript&quot; data-lang=&quot;javascript&quot;&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;nx&quot;&gt;describe&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;합격 판단&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;nx&quot;&gt;it&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;평균이 80이고 과락이 없다면 합격이어야 한다.&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;// given&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;report&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;행정학개론&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;80&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;행정법&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;80&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;영어&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;80&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;국어&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;80&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;국사&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;80&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;};&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;// when&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;passed&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;checkIfPass&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;report&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;

    &lt;span class=&quot;c1&quot;&gt;// then&lt;/span&gt;
    &lt;span class=&quot;nx&quot;&gt;expect&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;passed&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;toBeTruthy&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;});&lt;/span&gt;

  &lt;span class=&quot;nx&quot;&gt;it&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;평균이 79.8이면 과락이 없더라도 불합격이어야 한다.&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;// given&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;report&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;행정학개론&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;81&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;행정법&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;79&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;영어&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;81&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;국어&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;79&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;국사&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;79&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;};&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;// when&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;passed&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;checkIfPass&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;report&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;

    &lt;span class=&quot;c1&quot;&gt;// then&lt;/span&gt;
    &lt;span class=&quot;nx&quot;&gt;expect&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;passed&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;toBeFalsy&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;});&lt;/span&gt;

  &lt;span class=&quot;nx&quot;&gt;it&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;평균이 84지만 국사가 20점이라면, 과락으로 불합격이어야 한다.&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;// given&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;report&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;행정학개론&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;100&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;행정법&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;100&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;영어&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;100&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;국어&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;100&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;국사&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;20&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;};&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;// when&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;passed&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;checkIfPass&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;report&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;

    &lt;span class=&quot;c1&quot;&gt;// then&lt;/span&gt;
    &lt;span class=&quot;nx&quot;&gt;expect&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;passed&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;toBeFalsy&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;});&lt;/span&gt;

  &lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;
  
&lt;span class=&quot;p&quot;&gt;});&lt;/span&gt;
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;의사결정 테이블은 요구사항을 설명하는 훌륭한 예제로 쓰일 수 있습니다. 또한 테스트 케이스를 작성하는 과정에서 요구 사항의 문제를 식별하도록 돕습니다. 하지만 작성하는 데에 많은 비용을 써야 합니다. 복잡한 조건과 시스템을 표현하기가 어려운 것도 단점입니다.&lt;/p&gt;

&lt;h1 id=&quot;4-상태-전이&quot;&gt;4. 상태 전이&lt;/h1&gt;

&lt;p&gt;시스템이 1) 상태를 갖고, 2) 한 상태에서 다른 상태로 바뀔 수 있으며, 3) 상태에 따라 다르게 동작하는 경우에 상태가 바뀌는 흐름을 트리로 만들어서 테스트 케이스를 도출하는 방법입니다.&lt;/p&gt;

&lt;p&gt;아래와 같은 상태를 가진 너무나 평범한 동영상 플레이어가 있다고 가정합시다.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/images/new/state-transition.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;상태 전이 과정을 트리로 그려봅니다. 트리를 만들 때 예외 케이스도 고려하는 게 좋습니다.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/images/new/state-transition-tree.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;최상위 노드에서 자식 노드까지 이어지는 상태 전이 과정을 개별 테스트 케이스로 추출합니다. 전이 과정을 하나를 한 개의 케이스로 추출하세요. 그래야 케이스가 단순해집니다.&lt;/p&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;케이스&lt;/th&gt;
      &lt;th&gt;현재 상태&lt;/th&gt;
      &lt;th&gt;입력&lt;/th&gt;
      &lt;th&gt;기대 결과&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;1-1&lt;/td&gt;
      &lt;td&gt;idle&lt;/td&gt;
      &lt;td&gt;play&lt;/td&gt;
      &lt;td&gt;playing&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt; &lt;/td&gt;
      &lt;td&gt;playing&lt;/td&gt;
      &lt;td&gt;pause&lt;/td&gt;
      &lt;td&gt;paused&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt; &lt;/td&gt;
      &lt;td&gt;paused&lt;/td&gt;
      &lt;td&gt;stop&lt;/td&gt;
      &lt;td&gt;idle&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt; &lt;/td&gt;
      &lt;td&gt;idle&lt;/td&gt;
      &lt;td&gt;stop&lt;/td&gt;
      &lt;td&gt;error&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;1-2&lt;/td&gt;
      &lt;td&gt;paused&lt;/td&gt;
      &lt;td&gt;play&lt;/td&gt;
      &lt;td&gt;playing&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt; &lt;/td&gt;
      &lt;td&gt;playing&lt;/td&gt;
      &lt;td&gt;play&lt;/td&gt;
      &lt;td&gt;error&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;1-3&lt;/td&gt;
      &lt;td&gt;playing&lt;/td&gt;
      &lt;td&gt;stop&lt;/td&gt;
      &lt;td&gt;idle&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt; &lt;/td&gt;
      &lt;td&gt;idle&lt;/td&gt;
      &lt;td&gt;stop&lt;/td&gt;
      &lt;td&gt;error&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;1-4&lt;/td&gt;
      &lt;td&gt;idle&lt;/td&gt;
      &lt;td&gt;pause&lt;/td&gt;
      &lt;td&gt;error&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;p&gt;이 테이블을 테스트 케이스로 옮기면 되겠죠.&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-javascript&quot; data-lang=&quot;javascript&quot;&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;nx&quot;&gt;describe&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;멈춤일 때&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;kd&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;player&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

  &lt;span class=&quot;nx&quot;&gt;beforeEach&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;nx&quot;&gt;player&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;VideoPlayer&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;({&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;status&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;idle&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;});&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;});&lt;/span&gt;

  &lt;span class=&quot;nx&quot;&gt;it&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;재생을 하면 재생중(playing) 상태가 되어야 한다.&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;// given&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;// when&lt;/span&gt;
    &lt;span class=&quot;nx&quot;&gt;player&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;play&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;

    &lt;span class=&quot;c1&quot;&gt;// then&lt;/span&gt;
    &lt;span class=&quot;nx&quot;&gt;expect&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;player&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;status&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;toBe&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;playing&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;});&lt;/span&gt;

  &lt;span class=&quot;nx&quot;&gt;it&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;재생을 했다가 일시 정지를 하면 일시정지(paused) 상태가 되어야 한다.&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;// given&lt;/span&gt;
    &lt;span class=&quot;nx&quot;&gt;player&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;play&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;

    &lt;span class=&quot;c1&quot;&gt;// when&lt;/span&gt;
    &lt;span class=&quot;nx&quot;&gt;player&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;pause&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;

    &lt;span class=&quot;c1&quot;&gt;// then&lt;/span&gt;
    &lt;span class=&quot;nx&quot;&gt;expect&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;player&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;status&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;toBe&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;paused&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;});&lt;/span&gt;

  &lt;span class=&quot;nx&quot;&gt;it&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;일시 정지를 한 후에 멈춤을 누르면 유휴(idle) 상태가 되어야 한다.&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;// given&lt;/span&gt;
    &lt;span class=&quot;nx&quot;&gt;player&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;pause&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;

    &lt;span class=&quot;c1&quot;&gt;// when&lt;/span&gt;
    &lt;span class=&quot;nx&quot;&gt;player&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;stop&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;

    &lt;span class=&quot;c1&quot;&gt;// then&lt;/span&gt;
    &lt;span class=&quot;nx&quot;&gt;expect&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;player&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;status&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;toBe&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;idle&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;});&lt;/span&gt;

  &lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;
  
&lt;span class=&quot;p&quot;&gt;});&lt;/span&gt;
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;
</description>
            <pubDate>Wed, 08 Jan 2020 00:00:00 +0900</pubDate>
            <link>https://huns.me/posts/2020-01-09-35</link>
            <guid isPermaLink="true">https://huns.me/posts/2020-01-09-35</guid>
            
            <category>Test</category>
            
            <category>Blackbox</category>
            
            
            <category>개발 이야기</category>
            
        </item>
        
        <item>
            <title>GitHub Actions로 간단히 CI 서버 대신하기</title>
            <description>&lt;p&gt;이 글은 2019년 12월 17일에 작성하였습니다. 시간이 지남에 따라 문서의 내용이 유효하지 않을 수 있습니다.&lt;/p&gt;

&lt;p&gt;…&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/images/new/52e0d0474955ab14ea89807fc62d327a1522dfe054577541702e7ad2_1280.jpg&quot; alt=&quot;책상 위에 파이프라인을 구분해둔 보드판이 보임. 각 단계는 Stories, Todo, In Progress, Testing, Done이라고 이름이 붙어있다.&quot; /&gt;&lt;/p&gt;

&lt;p&gt;회사에서 CI/CD 도구로 Bamboo를, 코드 저장소로 GitHub를 이용하고 있다. CI 파이프라인을 구축하면서 Bamboo를 GitHub와 연결해서 아래의 흐름을 만들려고 했으나 머지 브랜치를 상대로 CI 빌드를 할 수 있는 방법을 찾지 못하였다.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/images/new/expected-pipepline.png&quot; alt=&quot;Pull Request -&amp;gt; Merge Branch 생성 -&amp;gt; Build -&amp;gt; Test 파이프라인을 표현하는 그림&quot; /&gt;&lt;/p&gt;

&lt;p&gt;정확하게, GitHub PR의 머지 브랜치를 참조하는 방법을 Bamboo가 지원하지 않는다. Bamboo는 Atlassian 제품(Bitbucket 같은)과 궁합이 잘 맞는 대신, GitHub에 대한 지원은 빈약하다. GitHub을 이용한다면 Bamboo에서 Pull Request 이벤트를 받아서 베이스 브랜치를 기준으로만 빌드를 할 수 있다. GitHub를 모른 척 할 수 있는 패기에 감탄 :(&lt;/p&gt;

&lt;p&gt;어쨌든 나는 살아야겠으니 대안을 찾자.&lt;/p&gt;

&lt;h1 id=&quot;01-대안을-찾아서&quot;&gt;01. 대안을 찾아서…&lt;/h1&gt;

&lt;p&gt;Jenkins라는 대안이 있지만 직접 설치를 해야 하는 게 부담이다. Bamboo에, 모든 프로젝트의 배포 플랜을 모으는 게 회사 정책이라 CI 서버를 양쪽에 두면 혼란할 것 같다. 손 안대고 코를 풀고 싶다. 방법이 없을까?&lt;/p&gt;

&lt;p&gt;고민을 하다가 배포 파이프라인에서 CI 빌드 단계만 GitHub Actions로 이전하는 걸 검토하기로 했다.&lt;/p&gt;

&lt;h1 id=&quot;02-github-actions-무엇에-쓰는-물건&quot;&gt;02. GitHub Actions, 무엇에 쓰는 물건?&lt;/h1&gt;

&lt;p&gt;&lt;a href=&quot;https://github.com/features/actions&quot;&gt;GitHub Actions&lt;/a&gt;는 GitHub에서 제공하는 워크플로우(Workflow) 자동화 도구다. 워크플로우라는 단어가 의미하듯 테스트, 빌드, 배포  뿐 아니라 다양한 작업을 만들어서 자동으로 실행하게 할 수 있다. PR에 특정 코멘트를 남기면 자동으로 머지를 하게 만들 수 있고, 데이터를 수집하는 데에 이용할 수도 있다. 누구는 부동산 정보를 수집하기도 하더라.&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;GitHub hosts Linux and Windows runners on Standard_DS2_v2 virtual machines in Microsoft Azure with the GitHub Actions runner application installed. The GitHub-hosted runner is a fork of the Azure Pipelines Agent. For more information about the Standard_DS2_v2 machine resources, see “DSv2-series” in the Microsoft Azure documentation. - https://bit.ly/2EilXFd&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;GitHub Actions는 Runner 위에서 실행이 되고, Runner는 가상 머신 위에서 실행이 된다. GitHub는 두 종류의 Runner를 제공한다. GitHub-hosted Runner, 그리고 Self-Hosted Runner.&lt;/p&gt;

&lt;p&gt;GitHub-Hosted Runner는 MS Azure의 가상 머신 위에서 돌아간다. 아래에 보이듯이 아직 가상 머신의 사양이 그렇게 좋지는 않다.&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;2-core CPU&lt;/li&gt;
  &lt;li&gt;7 GB of RAM memory&lt;/li&gt;
  &lt;li&gt;14 GB of SSD disk space&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;로컬에서 9분이 걸리는 CI 빌드를, GitHub-Hosted에서 수행하는 데에 14분이 걸렸다. 만족스럽지는 않지만 쓸만한 정도랄까.
Self-Hosted Runner를 이용하면 직접 가상 머신을 임대해서 GitHub Actions 실행 환경을 만들 수 있다(고 한다). GitHub Actions를 로컬에서 실행할 수도 있다. 해보지 않았으니 자세한 내용은 생략.&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;https://help.github.com/en/actions/automating-your-workflow-with-github-actions/adding-self-hosted-runners&quot;&gt;Adding self-hosted runners - GitHub Help&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;아직 베타 버전이고, 라이센스 플랜에 따라 사용하는 데에 제한이 있지만 프론트엔드 저장소를 빌드하는 목적으로 쓰기에는 충분해 보인다.&lt;/p&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;&lt;strong&gt;GitHub plan&lt;/strong&gt;&lt;/th&gt;
      &lt;th&gt;&lt;strong&gt;Total concurrent jobs&lt;/strong&gt;&lt;/th&gt;
      &lt;th&gt;&lt;strong&gt;Maximum concurrent macOS jobs&lt;/strong&gt;&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;Free&lt;/td&gt;
      &lt;td&gt;20&lt;/td&gt;
      &lt;td&gt;5&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Pro&lt;/td&gt;
      &lt;td&gt;40&lt;/td&gt;
      &lt;td&gt;5&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Team&lt;/td&gt;
      &lt;td&gt;60&lt;/td&gt;
      &lt;td&gt;5&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Enterprise&lt;/td&gt;
      &lt;td&gt;180&lt;/td&gt;
      &lt;td&gt;15&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;h1 id=&quot;03-사용-신청을-해보자&quot;&gt;03. 사용 신청을 해보자!&lt;/h1&gt;

&lt;p&gt;아직 베타 서비스이기에 따로 신청을 해야만 사용할 수 있다. 신청을 하면 저장소 페이지의 상단 탭에 Actions 메뉴가 생긴다.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/images/new/screenshot-with-actions-tab.png&quot; alt=&quot;GitHub의 저장소 페이지 상단 탭에 Actions 메뉴가 보인다.&quot; /&gt;&lt;/p&gt;

&lt;h1 id=&quot;04-action과-workflow&quot;&gt;04. Action과 Workflow&lt;/h1&gt;

&lt;p&gt;Action은 사용자가 작성하는 실행 가능한 코드 덩어리로, 하나의 작업을 설명한다. Workflow는 Action을 실행하는 환경과 단계를 서술한다.&lt;/p&gt;

&lt;p&gt;하나의 Workflow는 여러 개의 Action을 실행할 수 있다. 그래서 이름이 GitHub Actions다. Actions 페이지에 들어가 보면 저장소에서 사용하는 기술에 맞는 Workflow 템플릿을 추천 해준다.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/images/new/github-actions-page.png&quot; alt=&quot;GitHub가 제시하는 추천 Workflow가 보이는 이미지. Node.js, Node.js Package, Rust, Python package 등.&quot; /&gt;&lt;/p&gt;

&lt;p&gt;프론트엔드 애플리케이션의 CI 빌드 워크플로우를 작성하기 위해서 Node.js Workflow를 선택했다.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/images/new/nodejs-workflow.png&quot; alt=&quot;기본으로 제공하는 샘플 Node.js Workflow&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Workflow 파일은 .github/workflows 디렉터리에 위치한다. 화면에 보이는 것처럼 GitHub의 Actions 탭에서 직접 작성할 수 있다. 로컬에서 소스 코드처럼 작성하고 GitHub 원격 저장소로 Push를 할 수도 있다.&lt;/p&gt;

&lt;p&gt;Action을 npm 패키지처럼 Marketplace에  배포를 하면 우측의 &lt;a href=&quot;https://github.com/marketplace&quot;&gt;GitHub Marketplace&lt;/a&gt;에 노출되어 다른 사용자와 공유할 수 있다. 물론 다른 사람이 작성한 Action을 이용하는 것도 가능.&lt;/p&gt;

&lt;h1 id=&quot;05-pull-request--test-실행하기&quot;&gt;05. Pull Request → Test 실행하기&lt;/h1&gt;

&lt;p&gt;Node.js Workflow를 선택하면 아래의 yml이 만들어진다.&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-shell&quot; data-lang=&quot;shell&quot;&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;pre&gt;name: Node CI

on: &lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;push]

&lt;span class=&quot;nb&quot;&gt;jobs&lt;/span&gt;:
  build:

    runs-on: ubuntu-latest

    strategy:
      matrix:
        node-version: &lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;8.x, 10.x, 12.x]

    steps:
    - uses: actions/checkout@v1
    - name: Use Node.js &lt;span class=&quot;k&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{ matrix.node-version &lt;/span&gt;&lt;span class=&quot;k&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
      uses: actions/setup-node@v1
      with:
        node-version: &lt;span class=&quot;k&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{ matrix.node-version &lt;/span&gt;&lt;span class=&quot;k&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
    - name: npm &lt;span class=&quot;nb&quot;&gt;install&lt;/span&gt;, build, and &lt;span class=&quot;nb&quot;&gt;test
      &lt;/span&gt;run: |
        npm ci
        npm run build &lt;span class=&quot;nt&quot;&gt;--if-present&lt;/span&gt;
        npm &lt;span class=&quot;nb&quot;&gt;test
      env&lt;/span&gt;:
        CI: &lt;span class=&quot;nb&quot;&gt;true&lt;/span&gt;
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;YAML 문법으로 작성하기에, 몇 가지 패턴만 익히면 누구나 쉽게 작성할 수 있다. 내가 참여하고 있는 프로젝트는 GitHub Packages + GitHub Private Repository + Yarn을 이용하고 있기 때문에 이 Workflow를 그대로 사용할 수 없다. Wowrkflow를 수정하자.&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-shell&quot; data-lang=&quot;shell&quot;&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;pre&gt;name: CI Build

on: &lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;pull_request]

&lt;span class=&quot;nb&quot;&gt;jobs&lt;/span&gt;:
  build:
    runs-on: ubuntu-latest

    strategy:
      matrix:
        node-version: &lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;12.x]

    steps:
      - uses: actions/checkout@v1

      - name: Use Node.js &lt;span class=&quot;k&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{ matrix.node-version &lt;/span&gt;&lt;span class=&quot;k&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
        uses: actions/setup-node@v1
        with:
          node-version: &lt;span class=&quot;k&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{ matrix.node-version &lt;/span&gt;&lt;span class=&quot;k&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
          registry-url: &lt;span class=&quot;s2&quot;&gt;&quot;https://npm.pkg.github.com&quot;&lt;/span&gt;

      - name: Set up SSH Agent
        uses: webfactory/ssh-agent@v0.1.1
        with:
          ssh-private-key: &lt;span class=&quot;k&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{ secrets.SSH_PRIVATE_KEY &lt;/span&gt;&lt;span class=&quot;k&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;

      - name: Install dependencies
        run: yarn &lt;span class=&quot;nb&quot;&gt;install
        env&lt;/span&gt;:
          NODE_AUTH_TOKEN: &lt;span class=&quot;k&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{ secrets.GPR_AUTH_TOKEN &lt;/span&gt;&lt;span class=&quot;k&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
          CI: &lt;span class=&quot;nb&quot;&gt;true&lt;/span&gt;

      - name: Run Build
        run: yarn build

      - name: Run Tests
        run: yarn run &lt;span class=&quot;nb&quot;&gt;test&lt;/span&gt;:ci
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;이제 저장소로 PR을 보내면 아래와 같이 GitHub이 Workflow를 실행하는 모습을 볼 수 있다. 다른 설정을 할 필요가 없다.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/images/new/progress-in-status-check.png&quot; alt=&quot;PR 페이지에서 Status Check가 진행중이며, Some checks haven't completed yet이라는 문장이 보인다.&quot; /&gt;&lt;/p&gt;

&lt;p&gt;체크 섹션의 Details를 클릭하거나, PR 페이지 → Checks 탭을 클릭하면 실행 로그를 볼 수 있다.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/images/new/github-actions-view.png&quot; alt=&quot;Checks 탭에서 실행하고 있는 Workflow의 진행 로그를 보여주는 화면을 볼 수 있다.&quot; /&gt;&lt;/p&gt;

&lt;p&gt;성공하면 이렇게 바뀐다.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/images/new/status-check-success.png&quot; alt=&quot;Status Check를 통과한 화면. All checks have passed라는 문장이 보인다.&quot; /&gt;&lt;/p&gt;

&lt;h1 id=&quot;06-workflow-파일-하나씩-뜯어-보기&quot;&gt;06. Workflow 파일, 하나씩 뜯어 보기&lt;/h1&gt;

&lt;p&gt;문법이 매우 직관적이고 공식 문서도 잘 정리되어 있어서 누구나 30분만 공부하면 금방 Workflow를 만들 수 있다.&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;https://help.github.com/en/actions/automating-your-workflow-with-github-actions&quot;&gt;Automating your workflow with GitHub Actions&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;그것마저도 귀찮은 누군가를 위해서 내가 위에서 작성한 워크 플로우를 단계별로 간단히 살펴본다.&lt;/p&gt;

&lt;h3 id=&quot;1-워크-플로우-이름&quot;&gt;1) 워크 플로우 이름&lt;/h3&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-shell&quot; data-lang=&quot;shell&quot;&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1
&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;pre&gt;name: CI Build
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;name은 이 워크 플로우의 이름을 의미한다.&lt;/p&gt;

&lt;h3 id=&quot;2-실행-시점&quot;&gt;2) 실행 시점&lt;/h3&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-shell&quot; data-lang=&quot;shell&quot;&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1
&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;pre&gt;on: &lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;pull_request]
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;on은 이 워크 플로우를 실행할 시점을 설정한다. pull_request는 GitHub로 PR을 보낼때, PR에 커밋을 추가할 때마다 실행하라는 뜻이다. Webhook 처럼 이벤트 시점을 등록할 수 있고, POSIX cron 문법으로 직접 스케줄을 정의할 수도 있다.&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-shell&quot; data-lang=&quot;shell&quot;&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1
2
3
&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;pre&gt;on:
  schedule:
    - cron:  &lt;span class=&quot;s1&quot;&gt;'*/15 * * * *'&lt;/span&gt;
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;&lt;a href=&quot;https://help.github.com/en/actions/automating-your-workflow-with-github-actions/events-that-trigger-workflows&quot;&gt;Events that trigger workflows&lt;/a&gt;&lt;/p&gt;

&lt;h3 id=&quot;3-머지-브랜치-체크아웃&quot;&gt;3) 머지 브랜치 체크아웃&lt;/h3&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-shell&quot; data-lang=&quot;shell&quot;&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1
&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;pre&gt;- uses: actions/checkout@v1
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;uses는 사용할 액션의 패키지 이름을 지정한다. &lt;a href=&quot;https://github.com/actions/checkout&quot;&gt;actions/checkout@v1&lt;/a&gt;은 저장소를 설정하고 PR의 머지 브랜치를 체크아웃한다.&lt;/p&gt;

&lt;p&gt;사용할 수 있는 Action은 Marketplace에서 검색할 수 있다. npm package registry와 비슷한 친구다. 직접 로컬에 Action을 작성한 경우에는 로컬 디렉터리를 지정해줄 수도 있다.&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-shell&quot; data-lang=&quot;shell&quot;&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1
&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;pre&gt;- uses: ./.github/actions/coverage-commenter
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;사용자 정의 Action을 만들려면 GitHub이 안내하는 방식을 따라야 한다. 아래 문서에 잘 나와 있다.&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;https://help.github.com/en/actions/automating-your-workflow-with-github-actions/creating-a-javascript-action&quot;&gt;Creating a JavaScript action&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;또는 직접 &lt;a href=&quot;https://github.com/marketplace?type=actions&quot;&gt;Marketplace에&lt;/a&gt; 올라온 Action의 코드(예, &lt;a href=&quot;https://github.com/ziishaned/jest-reporter-action&quot;&gt;jest-reporter-action&lt;/a&gt;)를 살펴보는 것도 좋다.&lt;/p&gt;

&lt;h3 id=&quot;4-nodejs-환경-설정&quot;&gt;4) Node.js 환경 설정&lt;/h3&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-shell&quot; data-lang=&quot;shell&quot;&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1
2
3
4
5
&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;pre&gt;- name: Use Node.js &lt;span class=&quot;k&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{ matrix.node-version &lt;/span&gt;&lt;span class=&quot;k&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
  uses: actions/setup-node@v1
  with:
    node-version: &lt;span class=&quot;k&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{ matrix.node-version &lt;/span&gt;&lt;span class=&quot;k&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
    registry-url: &lt;span class=&quot;s2&quot;&gt;&quot;https://npm.pkg.github.com&quot;&lt;/span&gt;
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;이후 Workflow를 실행할 Node.js 환경을 설정한다. 사설 npm 저장소를 이용하는 경우에 registry-url을 옵션으로 설정해야 한다. https://npm.pkg.github.com은 GitHub Packages Registry를 가리킨다.&lt;/p&gt;

&lt;p&gt;&lt;code class=&quot;highlighter-rouge&quot;&gt;${{ ... }}&lt;/code&gt;은 Runner를 실행하는 &lt;a href=&quot;https://help.github.com/en/actions/automating-your-workflow-with-github-actions/contexts-and-expression-syntax-for-github-actions#contexts&quot;&gt;문맥(Contexts)&lt;/a&gt;에 대한 정보나 &lt;a href=&quot;https://help.github.com/en/actions/automating-your-workflow-with-github-actions/using-environment-variables&quot;&gt;환경 변수(Environment Variables)&lt;/a&gt;를 참조할 때 이용하는 표현식이다.&lt;/p&gt;

&lt;h3 id=&quot;5-ssh-agent-설정&quot;&gt;5) SSH Agent 설정&lt;/h3&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-shell&quot; data-lang=&quot;shell&quot;&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1
2
3
4
&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;pre&gt;- name: Set up SSH Agent
  uses: webfactory/ssh-agent@v0.1.1
  with:
    ssh-private-key: &lt;span class=&quot;k&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{ secrets.SSH_PRIVATE_KEY &lt;/span&gt;&lt;span class=&quot;k&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Private 저장소를 이용한다면 SSH 비밀키를 설정해야 한다. &lt;a href=&quot;https://github.com/marketplace/actions/webfactory-ssh-agent&quot;&gt;webfactory/ssh-agent@v0.1.1&lt;/a&gt;는 SSH Agent에 비밀키를 설정하는 걸 돕는다. 보안에 예민한 접근 제한키나 토큰과 같은 정보는 직접 워크 플로우 파일에 작성하기가 찜찜하다. 그래서 GitHub는 Secrets라는 기능을 제공한다.&lt;/p&gt;

&lt;p&gt;이 기능은 &lt;u&gt;저장소 &amp;gt; Settings &amp;gt; Secrets&lt;/u&gt;에서 이용할 수 있다.&lt;/p&gt;

&lt;p&gt;Secrets에 환경 변수를 등록하면 GitHub는 이를 암호화해서 들고 있다가, 런타임에 secrets의 프로퍼티로 제공한다. 참고로 Secrets에 입력한 값은 자동으로 암호화(Encrypted)가 된다. 토큰을 Secrets에 등록할 때는 암호화하지 않은 버전을 등록해야 한다는 걸 주의하자.&lt;/p&gt;

&lt;h3 id=&quot;6-패키지-설치&quot;&gt;6) 패키지 설치&lt;/h3&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-shell&quot; data-lang=&quot;shell&quot;&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1
2
3
4
5
&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;pre&gt;- name: Install dependencies
  run: yarn &lt;span class=&quot;nb&quot;&gt;install
  env&lt;/span&gt;:
    NODE_AUTH_TOKEN: &lt;span class=&quot;k&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{ secrets.GPR_AUTH_TOKEN &lt;/span&gt;&lt;span class=&quot;k&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
    CI: &lt;span class=&quot;nb&quot;&gt;true&lt;/span&gt;
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;run은 CLI 명령어를 실행할 때 사용한다. env를 이용해 Runner에 환경 변수를 설정할 수 있다. 사설 npm 저장소를 이용하기에 인증 토큰을 NODE_AUTH_TOKEN 환경 변수에 설정했다.&lt;/p&gt;

&lt;h3 id=&quot;7-빌드-테스트&quot;&gt;7) 빌드, 테스트&lt;/h3&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-shell&quot; data-lang=&quot;shell&quot;&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1
2
3
4
5
&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;pre&gt;- name: Run Build
  run: yarn build

- name: Run Tests
  run: yarn run &lt;span class=&quot;nb&quot;&gt;test&lt;/span&gt;:ci
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;마지막으로 소스 코드를 빌드하고 테스트를 실행한다. 미리 작성해둔 npm scripts를 실행하는 일이라 여기에서는 특별히 볼 게 없다. run에 입력한 코드를 그대로 실행하는 게 전부다. 두 개의 스텝을 아래와 같이 하나로 합칠 수도 있다.&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-shell&quot; data-lang=&quot;shell&quot;&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1
2
3
&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;pre&gt;- name: Run build and tests
  run: yarn build &lt;span class=&quot;o&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; yarn run &lt;span class=&quot;nb&quot;&gt;test&lt;/span&gt;:ci
 
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;h1 id=&quot;07-소감은&quot;&gt;07. 소감은…?&lt;/h1&gt;

&lt;p&gt;GitHub와 CI 서버를 연동하기 위해서 번거로운 작업을 하지 않아도 되는 점이 가장 좋았다. Actions를 만들어서 오픈 소스로 쉽게 공유할 수 있는 것도 장점이다. 지금은 실행 속도가 조금 느리지만 앞으로 고사양의 가상 서버를 추가하면서 더 나아질거라 기대한다. 물론 공짜는 아니겠지.&lt;/p&gt;

&lt;p&gt;Repository, GitHub Actions, GitHub Packages 까지. GitHub이 착실하게 자신의 영역을 넓히고 있다. GitHub DevOps를 볼 날이 얼마 남지 않았다.&lt;/p&gt;
</description>
            <pubDate>Tue, 17 Dec 2019 00:00:00 +0900</pubDate>
            <link>https://huns.me/posts/2019-12-17-34</link>
            <guid isPermaLink="true">https://huns.me/posts/2019-12-17-34</guid>
            
            <category>GitHub</category>
            
            <category>DevOps</category>
            
            <category>CI</category>
            
            <category>Build</category>
            
            
            <category>개발 이야기</category>
            
        </item>
        
        <item>
            <title>열심히 보다는, 영리하게!</title>
            <description>&lt;p&gt;&lt;img src=&quot;/assets/images/new/photo-1449247709967-d4461a6a6103.jpg&quot; alt=&quot;하얀 방의 한 가운데에 책상과 의자가 놓여있는 사진&quot; /&gt;&lt;/p&gt;

&lt;p&gt;비즈니스 목표를 달성하기 위해서 우리는 어떤 설계 전략을 가져야 할까 고민을 했다. 회사의 사업 전략과 설계 방향이 동떨어져서 회사나 직원이나 모두 피곤해지는 경우를 보았다. 사업 전략을 고려하니 열심히보다는, 영리하게, 해야 할 일을 해야겠더라.&lt;/p&gt;

&lt;p&gt;팀원들과 설계 논의를 할 때 자주 주고받았던 이야기를 세 가지로 압축해서 설계 원칙을 만들었다. 적어놓고 보니 나 자신에게 하는 이야기 같다 🤔&lt;/p&gt;

&lt;p&gt;.
.
.&lt;/p&gt;

&lt;h2 id=&quot;1-가장-단순하게-문제를-해결합니다&quot;&gt;1. 가장 단순하게 문제를 해결합니다.&lt;/h2&gt;

&lt;p&gt;개발자의 업이란 한정된 자원을 최대한의 결과로 만드는 일입니다. 모든 조건이 완벽하게 갖춰진 순간이나 여유로운 날은 오지 않습니다. 화려하고 멋진, 하지만 쓸모없는 것을 만드는 데에 시간을 낭비하지 않아야 합니다.&lt;/p&gt;

&lt;p&gt;가장 쉽고 빠른 방법을 찾아서 적용합니다. 그리고 필요할 때 고칩니다. 단, 대충 하지 않습니다. 경험하고 학습하며 균형을 찾습니다.&lt;/p&gt;

&lt;h2 id=&quot;2-확신할-수-없는-일은-고려하지-않습니다&quot;&gt;2. 확신할 수 없는 일은 고려하지 않습니다.&lt;/h2&gt;

&lt;p&gt;“만약…“을 가정하면 끝이 없습니다. 시간은 가장 소중한 자원입니다. 기회비용도 고려를 해야 합니다.&lt;/p&gt;

&lt;p&gt;해야 할 일에 집중을 하기 위해서, 하지 않아도 될 일은 과감하게 미루세요. 눈앞에 보이는 문제에 집중을 합니다. 더 이상 미룰 수 없는 날에 대비해 퇴로는 확보해야 합니다. 테스트 자동화 같은 걸로 말이죠.&lt;/p&gt;

&lt;h2 id=&quot;3-말하지-말고-보여주세요&quot;&gt;3. 말하지 말고, 보여주세요.&lt;/h2&gt;

&lt;p&gt;설계 이론은 눈에 보이지 않는 이야기를 늘어 놓는 경우가 많습니다. 보이지 않으면 이해하기 어렵습니다. 모든 문제를 말끔하게 해결하는 설계는 없습니다. 문제가 놓인 맥락을 살펴야 합니다.&lt;/p&gt;

&lt;p&gt;설계를 논의할 때는 1)눈앞에 보이는 우리의 문제와 2)실제로 일어날 상황을 이야기하세요.&lt;/p&gt;

&lt;p&gt;눈에 보이는 코드를 준비합니다. 프로토타입을 만들어서 보여주는 것도 좋습니다. 설계를 변경해야 할 때 어떤 작용이 발생하는지 보여주세요.&lt;/p&gt;

&lt;p&gt;그래야 빠르게 의사결정을 할 수 있습니다.&lt;/p&gt;
</description>
            <pubDate>Thu, 05 Dec 2019 00:00:00 +0900</pubDate>
            <link>https://huns.me/posts/2019-12-05-33</link>
            <guid isPermaLink="true">https://huns.me/posts/2019-12-05-33</guid>
            
            <category>조직운영</category>
            
            <category>조직문화</category>
            
            <category>팀빌딩</category>
            
            <category>설계</category>
            
            
            <category>개발 이야기</category>
            
        </item>
        
        <item>
            <title>팀을 맡고, 한 달 동안 가장 공을 들인 일</title>
            <description>&lt;p&gt;&lt;img src=&quot;/assets/images/new/54e3d042425baa14ea89807fc62d327a1522dfe05452734f762778d3_1280.png&quot; alt=&quot;여러 색깔이 손바닥이 서로 겹쳐있는 그림&quot; /&gt;&lt;/p&gt;

&lt;p&gt;이직을 한 지 한 달이 지났다. 새로운 회사에서 맡은 역할은 프론트엔드 조직의 테크 리드, 팀장 같은 거다.&lt;/p&gt;

&lt;p&gt;이전 회사에서도 소규모 그룹을 리드하는 역할을 하고 있었기에, 새로운 직책을 맡는다는 것에 부담을 느끼지는 않았다. 차이가 있다면 공식 직함이 생겼고 권한이 더 많아졌으니 내 레버리지를 그만큼 더 넓혀야 한다는 것 정도?&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/images/new/IMG_6460.jpg&quot; alt=&quot;메쉬코리아 사무실 모습&quot; /&gt;&lt;/p&gt;

&lt;p&gt;앞으로 어떤 색깔을 가진 친구들과 일을 하게 될지 궁금했다. 출근한 첫 주부터 팀 동료들과 1:1로 이야기를 나누는 걸로 일을 시작했다. 관심사는 무엇이고, 잘하는 것은 무엇이며, 어떤 꿈을 갖고 있는지. 회사, 그리고 서로에게 기대하는 바는 무엇인지. 앞으로 만들어 갈 팀의 모습을 동료들과 함께 그렸다.&lt;/p&gt;

&lt;p&gt;그렇게 8개의 Code of Conduct를 만들었다. 공유하는 정신은 어떤 장치 보다 강하다. 평소의 내 철학과 회사의 인재상이 큰 영향을 미친 것은 사실이지만 팀원들은 흔쾌히 동의를 해주었다.&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;사용자에게 가치를 줄 수 있는 일을 합니다.&lt;/li&gt;
  &lt;li&gt;경계를 넘어 서로 돕습니다.&lt;/li&gt;
  &lt;li&gt;서로 신뢰합니다.&lt;/li&gt;
  &lt;li&gt;수평적인 관계와 수직적인 의사결정의 조화를 추구합니다.&lt;/li&gt;
  &lt;li&gt;동료와 함께 성장합니다.&lt;/li&gt;
  &lt;li&gt;과학적으로 접근하고 경험적으로 학습합니다.&lt;/li&gt;
  &lt;li&gt;자신이 만든 결과물에 대해서 전문가로서의 책임감을 갖습니다.&lt;/li&gt;
  &lt;li&gt;규칙으로 정하지 않은 일은 스스로 생각하고 행동합니다.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;한 달이 지났다.&lt;/p&gt;

&lt;p&gt;8개는 너무 많은 것… 같다. 장황해서 집중을 하기가 어렵다. 우리가 집중해야 할 가치가 무엇일지 다시 고민을 했다. 나도 모르게 자주 쓰는, 내 머릿속의 한편을 굳건히 지키고 있는 문장 두 개가 떠올랐다.&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;서로 돕고 함께 성장하자.&lt;/li&gt;
  &lt;li&gt;일을 기다리지 말자.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;그래, 2개로 줄이자. 줄이는 김에 내 생각도 정리를 하자.&lt;/p&gt;

&lt;h2 id=&quot;하나-서로-돕고-함께-성장하자&quot;&gt;하나, 서로 돕고 함께 성장하자.&lt;/h2&gt;

&lt;p&gt;팀이라는 울타리 안에 사람을 그저 모아 놓으면 알아서 협력할 거라는 생각은 오만하다. 팀이라는 경계는  그 자체로 의미가 없다. 울타리를 치우고 나면 남는 것은 사람뿐이다. 팀에 속한 구성원 사이에 흐르는 에너지의 색깔이 더 중요하다. 한자리에 모여 앉아서 각자의 모니터만 바라보고 있다면, 그걸 팀이라고 말하기는 어렵지 않겠는가. 아무런 시너지도 만들지 못한다. 서로를 엮는 무엇이 있어야 한다.&lt;/p&gt;

&lt;p&gt;입사하고 며칠 동안 팀 동료들이 일하는 모습을 관찰했다. 자기의 책상에 앉아서, 자기의 일을 열심히 했다. 부지런한 친구들이다. 유일한 접점은 코드 리뷰. 팀이라고 부르기에는 부족했다.&lt;/p&gt;

&lt;p&gt;팀원들과 “우리가 하는 일의 본질과 협업의 가치”에 대해서 이야기를 많이 나눴다. 공감대가 어느 정도 형성이 되자 팀원들이 먼저 새로운 활동을 제안하고, 자신이 학습한 것을 팀에 공유하기도 했다. 사람 사이에 교집합을 하나씩, 하나씩 만들었다.&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;일하는 중간중간 잡담을 하도록 유도한다.&lt;/li&gt;
  &lt;li&gt;데일리 미팅에서 서로 도울 수 있는 연결 고리를 찾아서 이어준다.&lt;/li&gt;
  &lt;li&gt;일정을 함께 추정하거나 페어 프로그래밍을 함으로써 생각과 관점을 공유한다.&lt;/li&gt;
  &lt;li&gt;설계를 동료와 함께 논의하도록 유도하여 부딪히는 순간을 만든다.&lt;/li&gt;
  &lt;li&gt;자신이 학습한 것을 공유하고 토론을 하도록 자극한다.&lt;/li&gt;
  &lt;li&gt;간단한 일이라도 담당자를 두 명을 할당해서 협업을 유도한다.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;6번은 언뜻 보면 비효율을 만드는 것처럼 보이지만, 매우 중요하다. 단순히 두 사람이 붙어 앉아있다고 해서, 커피를 같이 마신다고 해서 사람과 사람 사이에 교집합이 생기지는 않기 때문이다. 서로 공유할 수 있는 목표롤 만들어주고 함께 일을 하도록 유도해야 한다. 어느 정도 슬랙(Slack: 물리적, 심리적 여유)을 만들어 주는 것도 중요하다. 마음에 여유가 없는 사람은 주변을 돌아보지 못한다.&lt;/p&gt;

&lt;p&gt;효율이나 생산성에만 집착하면 이 모든 게 허튼 짓으로 보이겠지만 교집합이 점점 단단해져 신뢰가 쌓이고, 쌓인 신뢰가 우리를 더 빠르게 만들어 줄 수 있다면 기꺼이 투자할만하다.&lt;/p&gt;

&lt;h2 id=&quot;둘-일을-기다리지-말자&quot;&gt;둘, 일을 기다리지 말자.&lt;/h2&gt;

&lt;p&gt;프론트엔드만으로 완성할 수 있는 제품은 거의 없다. 여러 과정을 거치며 다양한 직군이 힘을 합쳐야 비로소 하나의 제품이 만들어진다. 협업이 중요하다. 협업을 하기 좋은 조직 구조를 생각하면 목표를 공유하는 교차 기능(Cross-Functional) 팀을 구성하는 게 좋을 것 같다. 하지만 이런 팀은 몇 가지 이유로 만들기가 어렵다.&lt;/p&gt;

&lt;p&gt;교차 기능 팀에 속한 개인은 여러 직군이 모인 조직에서 기능적으로 고립되기가 쉽다. 팀에 자기 혼자만 프론트엔드 개발자인 상황을 떠올려 보라. 다양한 전문성을 가진 집단을, 한 명의 리더가 이끈다는 것 역시 생각만 해봐도 어렵다. 교차 기능 팀에 속한 개인은 탁월해야 하고, 이런 팀은 다른 종류의 리더십으로 이끌어야 한다. 채용 전쟁의 시대에 리소스 불균형도 해결하기 부담스러운 문제다. 교차 기능 팀을 구성하려다 이런 문제를 겪은 회사는 자연스레 기능(Functional) 조직으로 눈을 돌린다. 지금 다니고 있는 회사도 그렇다.&lt;/p&gt;

&lt;p&gt;각각의 장단점이 있다. 누군가는 이중 구조(매트릭스 조직 같은)로 묶는 것만이 이 문제를 해결할 수 있는 유일한 방법이라고 주장한다. 무엇이 정답인지는 잘 모르겠다. 답은 모르지만 살아남는 데에 가장 유리한 구조를 선택하고, 그로 인해서 발생하는 단점을 극복하기 위해서 노력은 할 수 있다.&lt;/p&gt;

&lt;p&gt;기능(Functional) 조직에 속해서 일을 하다 보면 일이 흐르는 과정이 아닌, 일 그 자체만을 쳐다보기가 쉽다. 웹 프론트엔드 파트는, 웹 프론트엔드 기술을 가진 친구들이 모여서, 웹 프론트엔드의 문제를 해결한다. 그러다 보니 눈앞에 보이지 않는 것들은 생각을 하기가 어렵다.&lt;/p&gt;

&lt;p&gt;이런 분위기에서는 협업하는 부서에서 일이 오기를 기다리는 경우를 흔히 볼 수 있다. 내가 한 일과 협업 부서가 한 일이 목표한 지점에 제대로 도달했는지 확인해보지도 않고 QA로 제품을 넘긴다. QA 기간에 버그 리포트를 받고서야 부랴부랴 인터페이스를 맞춰 보고, 디자인을 검토하고, 놓친 스펙을 구현한다. 그렇게 추정은 빗나가고 출시는 미뤄진다.&lt;/p&gt;

&lt;p&gt;기능 조직은 업무 완결성을 갖지 못한다. 어느 한곳이 망가지면, 모든 일이 멈춰버린다. 그래서 기능 조직을 운영할 때는 조직 간의 커뮤니케이션에 더 많은 에너지를 써야 한다. 기능 조직에 속한 프론트엔드 개발자는 더욱 그러하다. 디자인과 서버 사이, 그 어딘가에 있는 타고난 포지션도 한몫을 한다.&lt;/p&gt;

&lt;p&gt;디자인이 없으면 디자이너를 찾아서 요청을 해야 하고, 디자이너가 없으면 더미로 구현을 할 수 있다. 서버 API가 없으면 서버 개발자를 찾아아야 하고, 서버 개발자가 없으면 인터페이스만 협의를 하고 Fake Server를 이용해서 구현을 할 수 있다. 이도 저도 안 된다면 문제가 있음을 관계자에게 알리고 일정을 재조율해야 한다.&lt;/p&gt;

&lt;p&gt;일을 기다려서는 안 된다. 일이 흐르는 전체 흐름을 보고, 내 위치를 자각하고, 적극적으로 일을 챙겨야 한다. 함께 하는 일이니까. 그래야 일이 된다.&lt;/p&gt;

&lt;hr /&gt;

&lt;p&gt;지금은 이 두 가지를 문화로 승화시키는 데에만 집중하기로 했다. 말은 누구나 하지만, 말을 현실로 만들기란 정말 어렵다. 꼰대스럽지 않음 위에 공감을 쌓아야 한다. 말 한마디, 문장 하나 매우 조심스럽게 사용하려고 노력한다. 설득하고 있지만 복종하지는 않았으면 좋겠다. 조급하지만 기다리려 애쓴다.&lt;/p&gt;

&lt;p&gt;신뢰가 쌓여서 팀원 모두가 팀 안에서 소속감과 안정감을 느꼈으면 좋겠다.&lt;/p&gt;
</description>
            <pubDate>Sun, 10 Nov 2019 00:00:00 +0900</pubDate>
            <link>https://huns.me/posts/2019-11-10-32</link>
            <guid isPermaLink="true">https://huns.me/posts/2019-11-10-32</guid>
            
            <category>조직운영</category>
            
            <category>조직문화</category>
            
            <category>팀빌딩</category>
            
            
            <category>개발 이야기</category>
            
        </item>
        
        <item>
            <title>오버엔지니어링과 언더엔지니어링의 경계</title>
            <description>&lt;p&gt;&lt;img src=&quot;/assets/images/new/photo-1491554150239-a9062e24de5c.jpg&quot; alt=&quot;산 정상 호수에 남자가 혼자 서 있는 사진&quot; /&gt;&lt;/p&gt;

&lt;p&gt;YAGNI(You Aren’t Gonna Need it)란 약어에는 오버 엔지니어링에 대한 경계심이 담겨 있다. XP(eXtrem Programming)는 설계 결정의 순간을 최대한 미루는 것을 설계 원칙으로 제시한다. 당장에 필요한 것에만 집중함으로써 지금 하지 않아도 될 일을 하지 말라는 뜻이다. 낭비를 경계하는 일은 실용주의 개발자의 미덕이다.&lt;/p&gt;

&lt;p&gt;잠깐, 실용주의 개발자가 알아야 할 것이 하나 더 있다. 설계 결정을 미루기 위해서는 개발자가, 변화를 수용하는 시스템을 설계할 수 있는 능력을 가져야 한다는 점이다.&lt;/p&gt;

&lt;p&gt;최소한의 설계로만 시스템을 개발하면서 아직은 때가 아니라고 여겨지는 설계 결정을 뒤로 미뤘다. 더 이상 결정을 미룰 수 없는 순간이 왔다. 고치려고 보니 이미 너무 먼 길을 와버렸다. 들어가는 수정 비용이 너무 커서 아무 것도 할 수 없다. 이런 경험을 누구나 한 번쯤은 해 보았으리라. 한 번 데이고 나면 모든 게 설계 문제로 보인다. 이때부터는 오버엔지니어링으로 흐르기 쉽다.&lt;/p&gt;

&lt;p&gt;딜레마에 빠진다. 무작정 미루기에는 내일이 걱정이고, 오늘 하자니 이게 정말 필요한 건지 모르겠다. 오버엔지니어링과 언더엔지니어링. 둘 사이의 경계는 어디인 걸까? 둘 사이의 어디쯤에 우리는 자리를 잡아야 할까?&lt;/p&gt;

&lt;p&gt;시소의 균형을 잡는 이런 문제를 두고 흔히 답이 없다고들 한다. 답은 없다. 하지만 전략은 있어야 한다. 어떤 전략을 가지고 전쟁에 임할 것인가. 몇 번의 삽질을 거치며 내가 선택한 전략은 이렇다.&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;&lt;em&gt;문제를 해결하는 최소한의 설계를 하되, 퇴로는 열어두자.&lt;/em&gt;
&lt;cite&gt;- 내 머릿속&lt;/cite&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;지금 눈 앞에 닥친 문제를 해결하는 데에 필요한 최소한의 수준으로만 설계를 한다. 다만 상황이 바뀌었을 때 발생할 변경 비용은 결정을 내리는 시점에 함께 검토를 한다. 나중에 설계를 변경했을 때, 시스템의 안전성을 보장할 만큼의 테스트 커버리지를 확보하여 퇴로를 항상 열어둔다.&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;&lt;em&gt;자, 바로 이것이다. 오! 소프트웨어 아키텍트여, 당신은 미래를 내다봐야만 한다. 당신은 현명하게 추측해야만 한다. 당신은 비용을 산정하고, 어디에 아키텍처 경계를 둬야 할지, 그리고 완벽하게 구현할 경계는 무엇인지 와 부분적으로 구현할 경계와 무시할 경계는 무엇인지를 결정해야만 한다.&lt;/em&gt;
&lt;cite&gt;- Clean Archictecture, 243 P&lt;/cite&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;미래는 아무도 모른다. 희대의 예언자라는 노스트라다무스의 예언도 틀리지 않던가. 어쩔 수 없다. 수긍하자. 미래는 불확실하고 변경은 상수라는 사실을. 위험을 감지하는 촉을 기르는 수밖에 없다.&lt;/p&gt;

&lt;p&gt;개발을 진행하면서 설계에 잠재적 위험이 없는지 계속 들여다봐야 한다. 조금의 위험이라도 감지를 했다면, 지금 해결해야 할 문제인지, 더 미뤄도 될 일인지 살펴보고 판단해야 한다. 좋은 아키텍트는 오늘과 내일의 경계 위에서 춤을 춰야 한다.&lt;/p&gt;

&lt;p&gt;설계의 촉을 예민하게 세워야 한다. 촉을 세우려면 설계를 알아야 하고 엔지니어링을 알아야 한다. 아는 만큼 본다. 알아야 촉이 생기고, 퇴로를 만들고, 기회를 잡을 수 있다.&lt;/p&gt;
</description>
            <pubDate>Thu, 26 Sep 2019 00:00:00 +0900</pubDate>
            <link>https://huns.me/posts/2019-09-26-overengineering-vs-underengineering</link>
            <guid isPermaLink="true">https://huns.me/posts/2019-09-26-overengineering-vs-underengineering</guid>
            
            <category>개발</category>
            
            <category>설계</category>
            
            <category>의견</category>
            
            
            <category>개발 이야기</category>
            
        </item>
        
        <item>
            <title>설계의 중요성을 설명하기가 왜 어려웠을까?</title>
            <description>&lt;p&gt;&lt;img src=&quot;/assets/images/new/photo-1515187029135-18ee286d815b.jpg&quot; alt=&quot;사람들이 강의실에 모여있고, 가운데에 있는 여자가 무언가를 설명하는 그림&quot; /&gt;&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;&lt;em&gt;설계가 후순위가 되면 시스템을 개발하는 비용이 더 많이 들고,&lt;/em&gt; 
&lt;em&gt;일부 또는 전체 시스템에 변경을 가하는 일이 현실적으로 불가능해진다.&lt;/em&gt;
&lt;cite&gt;- Clean Archictecture, 21P&lt;/cite&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;돌아가는 행위(외부 품질)만 중시하고, 설계(내부 품질)를 등한시하면 유지 보수 비용이 증가할 거라는 믿음은 기술 부채라는 메타포를 만들었다. 내 주변의 개발자 중에서 이를 모르는 사람은 거의 없다.&lt;/p&gt;

&lt;p&gt;Clean Archictecture의 저자, Robert C. Martin은 설계(Archictecture)의 중요성을 설득하는 책임은 개발팀에게 있다고 말한다. 그에게 이는 장인 정신의 문제다. 문제를 잘 아는 사람이, 문제를 잘 모르는 사람을 설득할 수밖에 없다는 점에서 공감한다.&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;&lt;em&gt;따라서 기능의 긴급성이 아닌 아키텍처의 중요성을 설득하는 일은&lt;/em&gt; 
&lt;em&gt;소프트웨어 개발팀이 마땅히 책임져야 한다.&lt;/em&gt; 
&lt;em&gt;이러한 책임을 다하려면 싸움판에 뛰어들어야 하며,&lt;/em&gt; 
&lt;em&gt;더 정확히는 ‘투쟁’해야 한다.&lt;/em&gt;
&lt;cite&gt;- Clean Archictecture, 20P&lt;/cite&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;그럼에도 현실에서 이 문제를 풀기가 어려웠던 이유는, 설계가 비즈니스에 기여하는 바를 실증하는 논거를 만들기가 어려웠기 때문이다.&lt;/p&gt;

&lt;p&gt;비즈니스 이해관계자에게 비즈니스 논리는 대체로 선명하고, 이성적이고, 성실하다. 이에 반해 기술 논리는 모호하고, 감성적이고, 게으르다. 이 때문에 기술 논리는 비즈니스 논리 앞에서 자주 무력해졌다. 상황은 긴박한데 꽃구경을 가자는 이야기 정도로 취급을 받기 십상이다. 눈에 잘 보이지 않는다. 문제가 터지기 전까지는 문제라는 걸 느끼지 못한다.&lt;/p&gt;

&lt;p&gt;느려지는 속도를 개인의 시간으로 메우다가, 진흙탕에 빠져있음을 몸으로 느끼는 상황이 왔을 때는 이미 늦다. 기술 부채가 커질수록 청산에 드는 수고 역시 커지지만 왜 그런지 비즈니스 논리에는 여유가 없다. 이런 상황에서는 과거의 부채를 청산할 여력 자체가 없다. 악화가 악화를 부른다. 악순환이다.&lt;/p&gt;

&lt;p&gt;악순환에 빠져있음을 증명하려면 기술 부채라는 녀석을 눈에 보이는 무언가로 만들어 그것이 가진 비즈니스 가치를 증명해야 한다. 데이터가 필요하다. 데이터를 수집하려면 프로세스를 장악해야 한다. 개발 과정에 흔적을 남기고, 흔적을 수거해서 정보를 얻어야 하니까. 프로세스를 장악하려면? 동료의 신뢰를 얻어야 하고 설득을 해야 한다. 어느 정도의 지적 권위도 필요하다. 그 자체로 어려운 일이다. 연습하는 시간도 필요하다. 그런데 비즈니스 논리는 항상 긴박하다.&lt;/p&gt;

&lt;p&gt;무언가 잘못되어 가고 있음을 직감하지만, 그 느낌을 실증하기가 어려운 이유는 이 때문이었다. 투쟁에서 이기려면 증명을 해야 하는데, 증명을 하는 데에는 대단히 많은 에너지와 시간을 들여야 한다. 시간을 확보하려면 투쟁을 해야 한다. 투쟁에서 이기려면 증명을 해야 한다. 돌고 돈다.&lt;/p&gt;

&lt;p&gt;조직의 상층부로 갈수록 기술보다는 비즈니스 논리에 가까워지는데, 안타깝게도 의사결정권자가 비즈니스 논리에 가까울수록 이 문제는 더 풀기가 어렵다. 설득에 대한 책임은 조직의 계층 구조에서 밑바닥에 있는 자들의 것이 되어 버리기 때문이다. 이야기가 바닥에서만 돌 뿐, 위로 뻗어가지 못한다.&lt;/p&gt;

&lt;p&gt;상황은 어렵지만, 실무자의 입장에서 이 악순환의 연결고리를 끊을 수 있는 방법은 없는 시간이라도 쥐어짜내는 것뿐이라는 생각에 도달했다. 어떤 식으로든 부채를 가시화하고 근거를 만들어서 주장을 하는 것 외에 다른 방법이 있을까? 아무 일도 하지 않으면 아무것도 바꿀 수 없다. Clean Archictecture에서 저자가 말하는 투쟁은 이런 맥락의 이야기일 것이다.&lt;/p&gt;

&lt;p&gt;QA 과정에서 발견한 버그의 유형을 목록화하고 기술 부채와의 연관성을 추적한다. 기술 부채를 관리하는 백로그를 만들고 규모를 추정하여 수치화를 할 수도 있다. 새로운 기능을 구현함에 있어 기술 부채로 인해서 발생한 지연 일정을 수치화하여 보고하는 것도 하나의 방법이다.&lt;/p&gt;

&lt;p&gt;물론 말은 쉽다. 세상의 많은 일은 몰라서 못하는 게 아니다. 나 또한 네이버에서 일을 하면서 이 투쟁을 완성하지 못했으니까. 그럼에도 조금씩, 조금씩 두들기다 보면 언젠가는 저 벽을 허물 수 있지 않을까.&lt;/p&gt;

</description>
            <pubDate>Thu, 19 Sep 2019 00:00:00 +0900</pubDate>
            <link>https://huns.me/posts/2019-09-19-why-is-it-difficult-to-understand-the-importance-of-archictecture</link>
            <guid isPermaLink="true">https://huns.me/posts/2019-09-19-why-is-it-difficult-to-understand-the-importance-of-archictecture</guid>
            
            <category>개발</category>
            
            <category>설계</category>
            
            <category>기술부채</category>
            
            <category>설득</category>
            
            
            <category>개발 이야기</category>
            
        </item>
        
        <item>
            <title>Express.js 서버는 왜 304를 반환하는 걸까?</title>
            <description>&lt;p&gt;
				&lt;!-- wp:paragraph --&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;span style=&quot;font-size: 20pt;&quot;&gt;1.&lt;/span&gt; &lt;br /&gt;&lt;/strong&gt;Express.js는 정적 리소스 요청과 동적 리소스 요청을 구분한다. 정적 리소스에 대한 설정은 &lt;code&gt;express.static&lt;/code&gt;으로 지정하도록 구분해 놓은 것이 그렇다.&lt;/p&gt;
&lt;p&gt;&lt;!-- /wp:paragraph --&gt;&lt;/p&gt;
&lt;p&gt;&lt;!-- wp:paragraph --&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;span style=&quot;font-size: 20pt;&quot;&gt;2.&lt;/span&gt; &lt;br /&gt;&lt;/strong&gt;어떤 리소스 요청이든, Express.js는 모든 응답에 Etag를 자동으로 생성해서 헤더에 추가한다. 기본 설정이다.  다음은 Etag를 가진 응답의 헤더를 보여준다.&lt;/p&gt;
&lt;p&gt;__&lt;/p&gt;
&lt;p&gt;HTTP/1.1 200 OK&lt;br /&gt;Date: Fri, 09 Aug 2019 03:39:50 GMT&lt;br /&gt;Server: Apache&lt;br /&gt;X-Powered-By: Express&lt;br /&gt;Vary: Origin&lt;br /&gt;Access-Control-Allow-Credentials: true&lt;br /&gt;Accept-Ranges: bytes&lt;br /&gt;Cache-Control: public, max-age=0&lt;br /&gt;Last-Modified: Thu, 08 Aug 2019 10:39:30 GMT&lt;br /&gt;ETag: W/&quot;12f8d-16c70cfead0&quot;&lt;br /&gt;Content-Type: image/png&lt;br /&gt;Content-Length: 77709&lt;br /&gt;Referrer-Policy: unsafe-url&lt;br /&gt;Connection: close&lt;/p&gt;
&lt;div class=&quot;wp-block-image&quot;&gt;
&lt;p class=&quot;alignleft is-resized&quot;&gt;&lt;strong&gt;&lt;span style=&quot;font-size: 20pt;&quot;&gt;3.&lt;/span&gt; &lt;br /&gt;&lt;/strong&gt;ETag는 HTTP 응답의 버전을 식별하는 일종의 식별자다. Etag가 같으면 같은 응답이고 다르면 다른 응답인 셈이다. Express는 세 가지의 ETag 생성 방식을 제공한다. strong, weak, 그리고 사용자 정의 방식.&lt;/p&gt;
&lt;/div&gt;
&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-javascript&quot; data-lang=&quot;javascript&quot;&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1
2
3
&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;nx&quot;&gt;app&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;kd&quot;&gt;set&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;etag&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;strong&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;nx&quot;&gt;app&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;kd&quot;&gt;set&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;etag&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;weak&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;nx&quot;&gt;app&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;kd&quot;&gt;set&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;etag&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;body&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;encoding&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;cm&quot;&gt;/* return etag */&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;});&lt;/span&gt;
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;
&lt;p&gt;&lt;!-- /wp:paragraph --&gt;&lt;/p&gt;
&lt;p&gt;&lt;!-- wp:preformatted --&gt;&lt;!-- /wp:preformatted --&gt;&lt;/p&gt;
&lt;p&gt;&lt;!-- wp:paragraph --&gt;&lt;/p&gt;
&lt;p&gt;weak 방식은 &lt;a href=&quot;https://github.com/expressjs/express/blob/3ffceff3ed356a711fce5fee6ed00a03f43affd8/lib/utils.js#L53&quot;&gt;CRC32&lt;/a&gt;, strong 방식은 &lt;a href=&quot;https://github.com/expressjs/express/blob/3ffceff3ed356a711fce5fee6ed00a03f43affd8/lib/utils.js#L28&quot;&gt;MD5&lt;/a&gt; 알고리즘을 이용하여 HTTP Body를 인코딩한다.&lt;/p&gt;
&lt;p&gt;&lt;!-- /wp:paragraph --&gt;&lt;/p&gt;
&lt;p&gt;&lt;!-- wp:paragraph --&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;span style=&quot;font-size: 20pt;&quot;&gt;4.&lt;/span&gt; &lt;br /&gt;&lt;/strong&gt;서버로부터 넘겨받은 응답 헤더에 Etag가 있다면, 브라우저(크롬만 확인)는 Etag의 값과 응답을 받은 시간을 기록해두었다가, 다음 번에 동일한 요청을 전송할 때 아래 두 개의 필드를 헤더에 추가하여 서버로 전송한다.&lt;/p&gt;
&lt;p&gt;&lt;!-- /wp:paragraph --&gt;&lt;/p&gt;
&lt;p&gt;&lt;!-- wp:list --&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;'if-none-match': 'W/&quot;12f8d-16c6ffc26c3”'&lt;/li&gt;
&lt;li&gt;'if-modified-since': 'Thu, 08 Aug 2019 06:48:11 GMT'&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;!-- /wp:list --&gt;&lt;/p&gt;
&lt;p&gt;&lt;!-- wp:paragraph --&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;span style=&quot;font-size: 20pt;&quot;&gt;5.&lt;/span&gt; &lt;br /&gt;&lt;/strong&gt;Express.js는 요청 헤더에 if-none-match 헤더가 있으면, 응답 결과를 이용해 Etag를 다시 생성한다. 그리고 요청 헤더의 if-none-match 값과 Etag를 비교한다. 값이 동일하다면 304 Not Modified를 반환하는데, 304 응답은 HTTP Body가 없다. 물론 개발자가 헤더의 값을 확인하여 수동으로 캐시 전략을 구현할 수도 있다.&lt;/p&gt;
&lt;p&gt;&lt;!-- /wp:paragraph --&gt;&lt;/p&gt;
&lt;p&gt;&lt;!-- wp:paragraph --&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;span style=&quot;font-size: 20pt;&quot;&gt;6.&lt;/span&gt; &lt;br /&gt;&lt;/strong&gt;어쨌든 브라우저는 304 응답을 받으면 이전에 저장해 둔 정보를 참조한다.&lt;/p&gt;
&lt;p&gt;&lt;!-- /wp:paragraph --&gt;&lt;/p&gt;
&lt;p&gt;&lt;!-- wp:paragraph --&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;span style=&quot;font-size: 20pt;&quot;&gt;7.&lt;/span&gt; &lt;br /&gt;&lt;/strong&gt;Express.js에서 Etag를 생성하지 않도록 설정하는 건 쉽다. 다만 동적 요청에 대한 설정과 정적 요청에 대한 설정 방식이 다르다. 둘을 구분하니까.&lt;/p&gt;
&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-javascript&quot; data-lang=&quot;javascript&quot;&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1
2
3
4
5
6
&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;c1&quot;&gt;// 동적 요청에 대한 응답을 보낼 때 etag 생성을 하지 않도록 설정&lt;/span&gt;
&lt;span class=&quot;nx&quot;&gt;app&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;kd&quot;&gt;set&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;etag&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;// 정적 요청에 대한 응답을 보낼 때 etag 생성을 하지 않도록 설정&lt;/span&gt;
&lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;options&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;etag&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;false&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;};&lt;/span&gt;
&lt;span class=&quot;nx&quot;&gt;app&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;use&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;express&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;kd&quot;&gt;static&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;public&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;options&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;));&lt;/span&gt;
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;!-- /wp:paragraph --&gt;&lt;/p&gt;
&lt;p&gt;&lt;!-- wp:preformatted --&gt;&lt;!-- /wp:preformatted --&gt;		&lt;/p&gt;
</description>
            <pubDate>Fri, 09 Aug 2019 11:29:41 +0900</pubDate>
            <link>https://huns.me/development/2306</link>
            <guid isPermaLink="true">https://huns.me/development/2306</guid>
            
            <category>304</category>
            
            <category>cache</category>
            
            <category>etag</category>
            
            <category>express</category>
            
            <category>http</category>
            
            <category>server</category>
            
            <category>til</category>
            
            
            <category>개발 이야기</category>
            
        </item>
        
        <item>
            <title>CORS 실패 시, 응답 개체의 status는 왜 0일까?</title>
            <description>&lt;p&gt;
				&lt;!-- wp:heading --&gt;&lt;/p&gt;
&lt;h2&gt;&lt;strong&gt;문제를 만나다&lt;/strong&gt;&lt;/h2&gt;
&lt;p&gt;&lt;!-- /wp:heading --&gt;&lt;/p&gt;
&lt;p&gt;&lt;!-- wp:paragraph --&gt;&lt;/p&gt;
&lt;p&gt;클라이언트의 도메인과 서버의 도메인이 서로 다른 경우, 브라우저는 크로스 도메인(Cross Domain) 보안 정책에 따라 요청을 차단한다. 이 상황을 우회하는 몇 가지 해법이 있는데, 스마트에디터 원은 주로 CORS를 이용하고 있다. 최근에 네이버 지식인에 스마트에디터 원을 적용하다가 CORS 정책을 위반하는 상황을 만났는데, 아래의 스크린은 이 상황을 보여준다.&lt;/p&gt;
&lt;p&gt;&lt;!-- /wp:paragraph --&gt;&lt;/p&gt;
&lt;p&gt;&lt;!-- wp:image {&quot;linkDestination&quot;:&quot;custom&quot;} --&gt;&lt;/p&gt;
&lt;figure class=&quot;wp-block-image&quot;&gt;&lt;img src=&quot;/assets/images/legacy/SE-13e1dd27-78db-4010-ba17-c139b6350438.png?type=w773&quot; alt=&quot;&quot; /&gt;&lt;/figure&gt;
&lt;p&gt;&lt;!-- /wp:image --&gt;&lt;/p&gt;
&lt;p&gt;&lt;!-- wp:paragraph --&gt;&lt;/p&gt;
&lt;p&gt;개발하는 도중에 종종 만나는 상황이라 그리 놀랄 일은 아니다. 서버 측에서 응답 헤더의 속성 중, Access-Control-Allow-Origin에 접근을 허용하는 클라이언트의 도메인을 넣어주면 끝이다.&lt;/p&gt;
&lt;p&gt;&lt;!-- /wp:paragraph --&gt;&lt;/p&gt;
&lt;p&gt;&lt;!-- wp:paragraph --&gt;&lt;/p&gt;
&lt;p&gt;그런데 네트워크 요청에 대한 응답으로 에러를 전달받은 경우에 안내 메시지를 화면에 노출하도록 처리하고 있음에도 아무런 메시지를 노출하지 않는 것이 마음에 걸렸다. 원인을 찾아서 조금 더 상황을 들여다보다가 재미있는 점을 발견했다. 분명 에러 상황인데 응답 상태 코드(Status Code)가 200이네? 물론 Response의 Header에는 Status가 없지만.&lt;/p&gt;
&lt;p&gt;&lt;!-- /wp:paragraph --&gt;&lt;/p&gt;
&lt;p&gt;&lt;!-- wp:image {&quot;linkDestination&quot;:&quot;custom&quot;} --&gt;&lt;/p&gt;
&lt;figure class=&quot;wp-block-image&quot;&gt;&lt;a href=&quot;https://blog.naver.com/PostView.nhn?blogId=jukrang&amp;amp;Redirect=View&amp;amp;logNo=221534914187&amp;amp;categoryNo=11&amp;amp;isAfterWrite=true#&quot;&gt;&lt;img src=&quot;/assets/images/legacy/SE-c84a1804-fd68-4f1f-85fa-e5d8a529d47a.png?type=w773&quot; alt=&quot;&quot; /&gt;&lt;/a&gt;&lt;/figure&gt;
&lt;p&gt;&lt;!-- /wp:image --&gt;&lt;/p&gt;
&lt;p&gt;&lt;!-- wp:paragraph --&gt;&lt;/p&gt;
&lt;p&gt;더군다나 응답 결과로 만들어진 Response 개체의 status 값은 0이다. 뭘까... 이 부조화한 상황은.&lt;/p&gt;
&lt;p&gt;&lt;!-- /wp:paragraph --&gt;&lt;/p&gt;
&lt;p&gt;&lt;!-- wp:image {&quot;linkDestination&quot;:&quot;custom&quot;} --&gt;&lt;/p&gt;
&lt;figure class=&quot;wp-block-image&quot;&gt;&lt;a href=&quot;https://blog.naver.com/PostView.nhn?blogId=jukrang&amp;amp;Redirect=View&amp;amp;logNo=221534914187&amp;amp;categoryNo=11&amp;amp;isAfterWrite=true#&quot;&gt;&lt;img src=&quot;/assets/images/legacy/what-situation.png&quot; alt=&quot;&quot; /&gt;&lt;/a&gt;&lt;/figure&gt;
&lt;p&gt;&lt;!-- /wp:image --&gt;&lt;/p&gt;
&lt;p&gt;&lt;!-- wp:paragraph --&gt;&lt;/p&gt;
&lt;p&gt;응답 개체의 status가 에러를 표현(401, 404, 500과 같은) 하는 경우에만 대응하도록 코드를 작성하였기에 status가 0인 상황은 제대로 대응하지 못한 셈이다. 조건이야 추가하면 그만인데, 이런 결과가 만들어진 이유가 궁금했고, 밀려오는 호기심을 견디지 못해 스펙을 뒤지기 시작했다.&lt;/p&gt;
&lt;p&gt;&lt;!-- /wp:paragraph --&gt;&lt;/p&gt;
&lt;p&gt;&lt;!-- wp:heading --&gt;&lt;/p&gt;
&lt;h2&gt;&lt;strong&gt;스펙에서 답을 찾다&lt;/strong&gt;&lt;/h2&gt;
&lt;p&gt;&lt;!-- /wp:heading --&gt;&lt;/p&gt;
&lt;p&gt;&lt;!-- wp:paragraph --&gt;&lt;/p&gt;
&lt;p&gt;브라우저에서 서버로 HTTP 요청을 보낼 때, 우리는 XMLHttpRequest나 Fetch API를 주로 이용한다.&lt;/p&gt;
&lt;p&gt;&lt;!-- /wp:paragraph --&gt;&lt;/p&gt;
&lt;p&gt;&lt;!-- wp:code --&gt;&lt;/p&gt;
&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-javascript&quot; data-lang=&quot;javascript&quot;&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1
2
3
4
&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;cm&quot;&gt;/* XHR 개체를 이용하는 과거 방식 */&lt;/span&gt;
&lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;request&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;XMLHttpRequest&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
&lt;span class=&quot;nx&quot;&gt;request&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;open&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;GET&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;http://www.example.org/example.txt&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;nx&quot;&gt;request&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;send&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;
&lt;p&gt;&lt;!-- /wp:code --&gt;&lt;/p&gt;
&lt;p&gt;&lt;!-- wp:code --&gt;&lt;/p&gt;
&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-javascript&quot; data-lang=&quot;javascript&quot;&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1
2
3
4
5
6
7
8
&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;cm&quot;&gt;/* Fetch API를 이용하는 새로운 방식 */&lt;/span&gt;
&lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;myRequest&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;Request&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;http://localhost/flowers.jpg&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;

&lt;span class=&quot;nx&quot;&gt;fetch&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;myRequest&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;then&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;response&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;response&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;blob&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;())&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;then&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;blob&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;nx&quot;&gt;myImage&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;src&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;URL&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;createObjectURL&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;blob&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;});&lt;/span&gt;
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;
&lt;p&gt;&lt;!-- /wp:code --&gt;&lt;/p&gt;
&lt;p&gt;&lt;!-- wp:paragraph --&gt;&lt;/p&gt;
&lt;p&gt;그 어떤 방식을 이용하든, 브라우저는 서버로 요청을 전송할 때 요청의 유형을 판단하여 요청 개체의 mode라는 프로퍼티에 담아 전달한다.  Fetch API 스펙은 총 다섯 개의 mode를 정의한다. 다양한 요청을 몇 가지로 유형화하여 이후의 처리 과정을 추상화하기 위해서 만든 설정으로 보인다.&lt;/p&gt;
&lt;p&gt;&lt;!-- /wp:paragraph --&gt;&lt;/p&gt;
&lt;p&gt;&lt;!-- wp:quote {&quot;align&quot;:&quot;left&quot;} --&gt;&lt;/p&gt;
&lt;blockquote style=&quot;text-align:left&quot; class=&quot;wp-block-quote&quot;&gt;&lt;p&gt;&lt;strong&gt;&quot;same-origin&lt;/strong&gt;&quot;&lt;/p&gt;
&lt;p&gt;Used to ensure requests are made to same-origin URLs. Fetch will return a network error if the request is not made to a same-origin URL.&lt;/p&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&quot;cors&quot;&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Makes the request a CORS request. Fetch will return a network error if the requested resource does not understand the CORS protocol.&lt;/p&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&quot;no-cors&quot;&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Restricts requests to using CORS-safelisted methods and CORS-safelisted request-headers. Upon success, fetch will return an opaque filtered response.&lt;/p&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&quot;navigate&quot;&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;This is a special mode used only when navigating between documents.&lt;/p&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&quot;websocket&quot;&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;This is a special mode used only when establishing a WebSocket connection. Even though the default request mode is &quot;no-cors&quot;, standards are highly discouraged from using it for new features. It is rather unsafe.&lt;/p&gt;
&lt;p&gt;&lt;cite&gt;https://fetch.spec.whatwg.org/#concept-request-mode&lt;/cite&gt;&lt;/p&gt;&lt;/blockquote&gt;
&lt;p&gt;&lt;!-- /wp:quote --&gt;&lt;/p&gt;
&lt;p&gt;&lt;!-- wp:paragraph --&gt;&lt;/p&gt;
&lt;p&gt; XHR이든, Fetch API이든, 우리가 흔히 AJAX라고 부르는 요청은, cors를 기본 mode로 갖는다. &lt;/p&gt;
&lt;p&gt;&lt;!-- /wp:paragraph --&gt;&lt;/p&gt;
&lt;p&gt;&lt;!-- wp:paragraph --&gt;&lt;/p&gt;
&lt;p&gt;이에 반해 이미지나 CSS와 같은 리소스를 가져오는 요청, 즉 클라이언트의 코드 레벨이 아닌 하부 시스템에 의해 네트워크 요청이 만들어지는 경우(link, script, img, audio 등…)에 요청 개체는 no-cors를 기본값으로 갖는다. 다만, crossorigin 속성을 부여하면 mode는 cors가 된다.&lt;/p&gt;
&lt;p&gt;&lt;!-- /wp:paragraph --&gt;&lt;/p&gt;
&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-javascript&quot; data-lang=&quot;javascript&quot;&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1
2
&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;c1&quot;&gt;// 아래의 경우는 mode가 cors&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;link&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;rel&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;manifest&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;href&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;/app.webmanifest&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;crossorigin&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;use-credentials&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt;
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;
&lt;p&gt;&lt;!-- /wp:code --&gt;&lt;/p&gt;
&lt;p&gt;&lt;!-- wp:paragraph --&gt;&lt;/p&gt;
&lt;p&gt;이 내용은 MDN에서 확인할 수 있다.&lt;/p&gt;
&lt;p&gt;&lt;!-- /wp:paragraph --&gt;&lt;/p&gt;
&lt;p&gt;&lt;!-- wp:quote {&quot;align&quot;:&quot;left&quot;} --&gt;&lt;/p&gt;
&lt;blockquote style=&quot;text-align:left&quot; class=&quot;wp-block-quote&quot;&gt;&lt;p&gt;Requests can be initiated in a variety of ways, and the mode for a request depends on the particular means by which it was initiated.&lt;/p&gt;
&lt;p&gt; For example, when a Request object is created using the Request.Request constructor, the value of the mode property for that Request is set to cors&lt;/p&gt;
&lt;p&gt;However, for requests created other than by the Request.Request constructor, no-cors is typically used as the mode; for example, for embedded resources where the request is initiated from markup, unless the crossorigin attribute is present, the request is in most cases made using the no-cors mode — that is, for the &amp;lt;link&gt; or &amp;lt;script&gt; elements (except when used with modules), or &amp;lt;img&gt;, &amp;lt;audio&gt;, &amp;lt;video&gt;, &amp;lt;object&gt;, &amp;lt;embed&gt;, or &amp;lt;iframe&gt;elements.&lt;/p&gt;
&lt;p&gt;&lt;cite&gt;https://developer.mozilla.org/en-US/docs/Web/API/Request/mode#Default_mode&lt;/cite&gt;&lt;/p&gt;&lt;/blockquote&gt;
&lt;p&gt;&lt;!-- /wp:quote --&gt;&lt;/p&gt;
&lt;p&gt;&lt;!-- wp:paragraph --&gt;&lt;/p&gt;
&lt;p&gt; &lt;a href=&quot;https://www.w3.org/TR/cors/&quot; target=&quot;_blank&quot; rel=&quot;noreferrer noopener&quot;&gt;W3C의 Cross-Origin Resource Sharing 스펙&lt;/a&gt;은 cors 모드인 요청을 처리하는 과정에서 CORS 확인에 실패하면 network error 처리 프로세스를 따를 것을 요구한다.&lt;/p&gt;
&lt;p&gt;&lt;!-- /wp:paragraph --&gt;&lt;/p&gt;
&lt;p&gt;&lt;!-- wp:quote {&quot;align&quot;:&quot;left&quot;} --&gt;&lt;/p&gt;
&lt;blockquote style=&quot;text-align:left&quot; class=&quot;wp-block-quote&quot;&gt;
  &lt;p&gt;Perform a resource sharing check. If it returns fail, apply the network error steps.&lt;/p&gt;
  &lt;p&gt;&lt;cite&gt;https://www.w3.org/TR/cors/#network-error-steps&lt;/cite&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;!-- /wp:quote --&gt;&lt;/p&gt;
&lt;p&gt;&lt;!-- wp:paragraph --&gt;&lt;/p&gt;
&lt;p&gt;network error는 또 다른 스펙인 &lt;a href=&quot;https://fetch.spec.whatwg.org/&quot; target=&quot;_blank&quot; rel=&quot;noreferrer noopener&quot;&gt;WHATWG의 Fetch Standard&lt;/a&gt;에서 정의하고 있는데, type이 error이며, status는 0이고,  status message가 빈 문자열을 갖는 응답 개체를 의미한다.&lt;/p&gt;
&lt;p&gt;&lt;!-- /wp:paragraph --&gt;&lt;/p&gt;
&lt;p&gt;&lt;!-- wp:quote {&quot;align&quot;:&quot;left&quot;} --&gt;&lt;/p&gt;
&lt;blockquote style=&quot;text-align:left&quot; class=&quot;wp-block-quote&quot;&gt;&lt;p&gt;A network error is a response whose status is always 0, status message is always the empty byte sequence, header list is always empty, body is always null, and trailer is always empty.&lt;/p&gt;
&lt;p&gt;&lt;cite&gt;https://fetch.spec.whatwg.org/#concept-network-error&lt;/cite&gt;&lt;/p&gt;&lt;/blockquote&gt;
&lt;p&gt;&lt;!-- /wp:quote --&gt;&lt;/p&gt;
&lt;p&gt;&lt;!-- wp:paragraph --&gt;&lt;/p&gt;
&lt;p&gt;꽤 길게 적었지만 요약하자면 이렇다.&lt;/p&gt;
&lt;p&gt;&lt;!-- /wp:paragraph --&gt;&lt;/p&gt;
&lt;p&gt;&lt;!-- wp:quote {&quot;align&quot;:&quot;left&quot;} --&gt;&lt;/p&gt;
&lt;blockquote style=&quot;text-align:left&quot; class=&quot;wp-block-quote&quot;&gt;&lt;p&gt;CORS 실패 → 네트워크 에러 발생 → status가 0&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;!-- /wp:quote --&gt;&lt;/p&gt;
&lt;p&gt;&lt;!-- wp:paragraph --&gt;&lt;/p&gt;
&lt;p&gt;이 내용을 이해하려고 3개의 스펙 문서를 어지럽게 돌아다녀야 하는 게 불만이었지만, XHR → CORS → Fetch로 스펙이 발달해온 과정과 스펙 사이의 의존관계를 생각해보니 조금은 인자해졌다.&lt;/p&gt;
&lt;p&gt;&lt;!-- /wp:paragraph --&gt;&lt;/p&gt;
&lt;p&gt;&lt;!-- wp:paragraph --&gt;&lt;/p&gt;
&lt;p&gt;&lt;!-- wp:paragraph --&gt;&lt;/p&gt;
&lt;p&gt;&lt;!-- wp:paragraph --&gt;&lt;/p&gt;
&lt;p&gt;&lt;a rel=&quot;noreferrer noopener&quot; href=&quot;https://xhr.spec.whatwg.org/&quot; target=&quot;_blank&quot;&gt;XMLHttpRequest&lt;/a&gt;&lt;br /&gt;&lt;a rel=&quot;noreferrer noopener&quot; href=&quot;https://www.w3.org/TR/cors/&quot; target=&quot;_blank&quot;&gt;Cross-Origin Resources Shaaring&lt;/a&gt;&lt;br /&gt;&lt;a rel=&quot;noreferrer noopener&quot; href=&quot;https://fetch.spec.whatwg.org/&quot; target=&quot;_blank&quot;&gt;Fetch Standard&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;!-- /wp:paragraph --&gt;		&lt;/p&gt;
</description>
            <pubDate>Sat, 11 May 2019 14:42:00 +0900</pubDate>
            <link>https://huns.me/development/2297</link>
            <guid isPermaLink="true">https://huns.me/development/2297</guid>
            
            
            <category>개발 이야기</category>
            
        </item>
        
        <item>
            <title>나는 그동안 무엇으로 성장했을까?</title>
            <description>&lt;p&gt;
				&lt;!-- wp:image {&quot;id&quot;:2285} --&gt;&lt;/p&gt;
&lt;img src=&quot;/assets/images/legacy/unsplash_527bf4b4ae00d_1-1024x682.jpeg&quot; alt=&quot;&quot; class=&quot;wp-image-2285&quot; /&gt;
&lt;p&gt;&lt;!-- /wp:image --&gt;&lt;/p&gt;
&lt;div id=&quot;SE-7b5c37ca-e2ee-4893-841d-75f07d1e80a0&quot; class=&quot;se-component se-quotation se-l-quotation_underline&quot;&gt;
&lt;div class=&quot;se-component-content&quot;&gt;
&lt;div class=&quot;se-section se-section-quotation se-l-quotation_underline&quot;&gt;
&lt;div&gt; &lt;/div&gt;
&lt;p&gt;&quot;API 수집가&quot;&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div id=&quot;SE-6b1b5208-57f2-4b5d-9651-ac6b8db42f95&quot; class=&quot;se-component se-text se-l-default&quot;&gt;
&lt;div class=&quot;se-component-content&quot;&gt;
&lt;div class=&quot;se-section se-section-text se-l-default&quot;&gt;
&lt;div class=&quot;se-module se-module-text&quot;&gt;
&lt;p id=&quot;SE-8a99653d-b600-4331-9bad-7f705c526dbb&quot; class=&quot;se-text-paragraph se-text-paragraph-align- &quot;&gt;&lt;span id=&quot;SE-985bfa38-efe1-49ce-9f30-2cbc602592ba&quot; class=&quot;se-fs-fs16 se-ff-   &quot;&gt;개발 어린이 시절에는 다양한 라이브러리와 프레임워크를 사용해 보는 것을 성장이라고 생각했다. 새로운 기술을 찾고, 예제 코드를 작성하고, 사용해봤다는 걸 열심히 자랑했다. 사용해 본, 또는 공부한 프레임워크의 종류는 늘어갔지만 밖에서 자랑한 것만큼 현실이 아름답지는 않았다. 여전히 내 코드는 유지보수하기 힘들었고, 버그가 많았으며, 빠듯한 일정에 허덕였다.&lt;/span&gt;&lt;/p&gt;
&lt;p id=&quot;SE-c4538645-f4f6-445e-808a-b21d8f70098a&quot; class=&quot;se-text-paragraph se-text-paragraph-align- &quot;&gt;&lt;span id=&quot;SE-af1e055f-de1c-494e-8af5-eb3ecf4dbafb&quot; class=&quot;se-fs-fs16 se-ff-   &quot;&gt;깊이는 제쳐두고, 넓은 폭만을 동경했다. 낯선 것에 익숙해지는 데에는 시간이 필요했다. 배운 것을 잘 쓰기 위해서는 많은 수련을 해야 한다는 당연한 사실을 그때는 몰랐다. 그냥 도구가 문제를 제대로 풀지 못한다고 믿었다. 도구를 비난하는 게 가장 쉬웠다. 이 녀석은 내 말에 대꾸를 하지 못한다.&lt;/span&gt;&lt;span id=&quot;SE-92a5b24e-573c-4990-8c37-305fb1b0f210&quot; class=&quot;se-fs-fs16 se-ff-   &quot;&gt; &lt;/span&gt;&lt;/p&gt;
&lt;p id=&quot;SE-8a641f29-3686-4f92-ab84-56a5bcc1e0a4&quot; class=&quot;se-text-paragraph se-text-paragraph-align- &quot;&gt;&lt;span id=&quot;SE-e4fad28b-f073-4531-be60-c92dce90e17e&quot; class=&quot;se-fs-fs16 se-ff-   &quot;&gt;새로운 기술을 탐닉하는 것 자체를 나무랄 일은 아니다. 열정과 호기심이 있기에 가능한 일이다. 더 나은 개발자로 성장해가는 과정의 하나이고, 스펀지처럼 빨아들일 수 있는 그때가 어쩌면 탐닉하기에 가장 좋은 때인지도 모르겠다. 경험이 쌓이면 새로운 걸 받아들이기 힘들어지기도 한다. 장점과 단점은 동전의 앞면과 뒷면이다.&lt;/span&gt;&lt;span id=&quot;SE-9e637b51-7e7c-4a06-9ea6-6c55803ada54&quot; class=&quot;se-fs-fs16 se-ff-   &quot;&gt; &lt;/span&gt;&lt;/p&gt;
&lt;p id=&quot;SE-945b8658-386d-4b1a-a8bf-030f0b3d6ca4&quot; class=&quot;se-text-paragraph se-text-paragraph-align- &quot;&gt;&lt;span id=&quot;SE-65ed196f-43fe-491e-878b-8b61025ebf0e&quot; class=&quot;se-fs-fs16 se-ff-   &quot;&gt;API 수집가. &lt;/span&gt;&lt;/p&gt;
&lt;p id=&quot;SE-eee6f656-6c0c-4d00-9388-be0e78c2b3d5&quot; class=&quot;se-text-paragraph se-text-paragraph-align- &quot;&gt;&lt;span id=&quot;SE-efc8a33b-a171-481b-a779-b77a9eb1f9ef&quot; class=&quot;se-fs-fs16 se-ff-   &quot;&gt;어쨌든 나의 개발 어린이 시절은 새로 나온 API를 수집하는 게 일이었다. 그 시절 내 관심은 오로지 &lt;/span&gt;&lt;span id=&quot;SE-99d38e42-3b5c-498a-b1d3-3aaa52ead3a9&quot; class=&quot;se-fs-fs16 se-ff-   &quot;&gt;&lt;b&gt;코드&lt;/b&gt;&lt;/span&gt;&lt;span id=&quot;SE-21ce96c9-93ef-4539-b958-e5aa9fc97cf3&quot; class=&quot;se-fs-fs16 se-ff-   &quot;&gt;에 머물러 있었다. 코드는 나에게 목적이었다. 코드를 어떻게 작성해야 할지, 어떤 알고리즘이 시간 복잡도가 가장 낮은지, 어떤 라이브러리가 더 예쁜 인터페이스를 제공하는지. 이런 질문을 주로 머리에 담고 살았다. 개발은 나 자신과의 싸움이었다. 나만 잘하면 되는 그런 일.&lt;/span&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div id=&quot;SE-895e34a2-e0d7-4ae1-8573-c42d5857f284&quot; class=&quot;se-component se-quotation se-l-quotation_underline&quot;&gt;
&lt;div class=&quot;se-component-content&quot;&gt;
&lt;div class=&quot;se-section se-section-quotation se-l-quotation_underline&quot;&gt;
&lt;div class=&quot;se-module se-module-text se-quote&quot;&gt;
&lt;h2 id=&quot;SE-64a102cd-e647-467e-9271-cd6f4e7488e3&quot; class=&quot;se-text-paragraph se-text-paragraph-align- &quot;&gt;&lt;span id=&quot;SE-4023c4e2-3d34-4d4d-81ff-3450b0c1af37&quot; class=&quot;se-fs-fs24 se-ff-   &quot;&gt;&quot;꼰대 아니야?&quot;&lt;/span&gt;&lt;/h2&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div id=&quot;SE-9cbda834-3fd9-44a3-a682-b063bdc7952f&quot; class=&quot;se-component se-text se-l-default&quot;&gt;
&lt;div class=&quot;se-component-content&quot;&gt;
&lt;div class=&quot;se-section se-section-text se-l-default&quot;&gt;
&lt;div class=&quot;se-module se-module-text&quot;&gt;
&lt;p id=&quot;SE-d22e53ee-f7ae-42b4-a784-984d5e2e7399&quot; class=&quot;se-text-paragraph se-text-paragraph-align- &quot;&gt;&lt;span id=&quot;SE-43b3955e-3e28-458c-a630-23c1ca25d2e6&quot; class=&quot;se-fs-fs16 se-ff-   &quot;&gt;사람은 자신에게 유리한 쪽으로 현상을 해석하려는 경향이 있나 보다. 새로운 API로 팀이 겪는 모든 문제를 해결하고 싶었다. &lt;/span&gt;&lt;span id=&quot;SE-4be9ddaf-6c42-4605-8c8e-982d194c5172&quot; class=&quot;se-fs-fs16 se-ff-   &quot;&gt;훌륭한 개발자라면 최신의, 아름다운 논리로 무장한 기술을 당연히 칭송해야 하지 않겠는가. 그렇게 낡은 jQuery를 밀어내고 React를 도입하면 꽃길을 걸을 수 있을 거라 믿었다. &lt;/span&gt;&lt;span id=&quot;SE-967100d1-418e-48d6-96bb-81f52a48e227&quot; class=&quot;se-fs-fs16 se-ff-   &quot;&gt; &lt;/span&gt;&lt;/p&gt;
&lt;p id=&quot;SE-984fed08-044f-4625-8084-8afe09abb46f&quot; class=&quot;se-text-paragraph se-text-paragraph-align- &quot;&gt;&lt;span id=&quot;SE-b77539c1-186a-486a-a1df-d3f205f336aa&quot; class=&quot;se-fs-fs16 se-ff-   &quot;&gt;React를 공부했고, 콘퍼런스에서 발표도 했다. 발표했음을 동료에게 자랑하며 지식을 뽐냈다. 팀에 도입을 제안했다. 동료의 반응은 냉담했다. 내 말이 논리적으로 합당하고, 그걸 증명할 수만 있다면 당연히 모두가 따라줄 거라 믿었던 내 기대가 무너졌다. 사실 제대로 증명하지도 못했다. 결론을 내려놓고 근거를 가져다 끼워 맞춘 논리는 어색하기 마련이다.&lt;/span&gt;&lt;span id=&quot;SE-83ca9a0b-6d3e-4396-8378-a756d6645c0f&quot; class=&quot;se-fs-fs16 se-ff-   &quot;&gt; &lt;/span&gt;&lt;/p&gt;
&lt;p id=&quot;SE-3520bffd-e47c-4525-b924-c6b320cbf790&quot; class=&quot;se-text-paragraph se-text-paragraph-align- &quot;&gt;&lt;span id=&quot;SE-d7892a1a-072b-43c5-81b0-bf443d544800&quot; class=&quot;se-fs-fs16 se-ff-   &quot;&gt;기대와 현실의 간극을 동료를 비난하는 걸로 매워야 했다. 내 말을 안 들어주는 사람은 권위적인 꼰대이며, 우리 조직에는 수직적 잔재가 남아있다고 말이다. 킹왕짱 신기술 앞에 조심스러운 동료가 지나치게 보수적이라고 생각했다. 그래야 견딜 수 있었다. &lt;/span&gt;&lt;/p&gt;
&lt;p id=&quot;SE-804e812c-0289-4962-8fc2-76a4f0fb06f2&quot; class=&quot;se-text-paragraph se-text-paragraph-align- &quot;&gt;&lt;span id=&quot;SE-2b126e66-5be2-4113-8e97-f34c87333004&quot; class=&quot;se-fs-fs16 se-ff-   &quot;&gt;한참이 지난 후 엉뚱하게 웹툰을 보다가 깨달았다. 이것이 신뢰의 문제임을.&lt;/span&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div id=&quot;SE-b0a39dba-96a1-4638-b4d0-64073429b188&quot; class=&quot;se-component se-quotation se-l-quotation_underline&quot;&gt;
&lt;div class=&quot;se-component-content&quot;&gt;
&lt;div class=&quot;se-section se-section-quotation se-l-quotation_underline&quot;&gt;
&lt;div class=&quot;se-module se-module-text se-quote&quot;&gt;
&lt;h2 id=&quot;SE-8ebd1d46-07c3-4390-8296-18df58dbabfb&quot; class=&quot;se-text-paragraph se-text-paragraph-align- &quot;&gt;&lt;span id=&quot;SE-7ae3f7eb-4698-419b-86ab-fc11f3e5c9a0&quot; class=&quot;se-fs-fs24 se-ff-   &quot;&gt;&quot;사람들은 옳은 사람 말 안 들어. &lt;/span&gt;좋은 사람 말을 듣지.&quot;&lt;/h2&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;

&lt;p id=&quot;SE-c6d2fff8-e29f-472e-b83f-7c56b096e470&quot; class=&quot;se-text-paragraph se-text-paragraph-align- &quot;&gt;&lt;span id=&quot;SE-2055302b-19a4-4dc2-931a-624b78cd1092&quot; class=&quot;se-fs-fs16 se-ff-   &quot;&gt;어찌어찌 어렵사리 React를 도입했다. 이후로 우리는 꽃길을 걸었을까? 그럴 리가. React만으로는 해결할 수 없는, React를 사용해서 발생하는 문제가 가득했고, 나는, 그리고 우리는 서툴렀다.&lt;/span&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li id=&quot;SE-8d78ccd3-1d97-490c-b9df-8a78b047c4d1&quot; class=&quot;se-text-paragraph se-text-paragraph-align- &quot;&gt;&lt;span id=&quot;SE-3d29e41f-a33c-4a18-b22f-7d50cb992836&quot; class=&quot;se-fs- se-ff-   &quot;&gt;React의 조정(Reconcilation) 비용은 어떻게 줄여야 해요?&lt;/span&gt;&lt;/li&gt;
&lt;li id=&quot;SE-c48abe65-51be-4672-a965-84fecb008910&quot; class=&quot;se-text-paragraph se-text-paragraph-align- &quot;&gt;&lt;span id=&quot;SE-7f54e365-2d80-4d24-a17a-934578ed25fc&quot; class=&quot;se-fs- se-ff-   &quot;&gt;키 입력 처리 성능이 너무 떨어져요!&lt;/span&gt;&lt;/li&gt;
&lt;li id=&quot;SE-5c13199a-062b-43ec-b0cb-b525e10fea45&quot; class=&quot;se-text-paragraph se-text-paragraph-align- &quot;&gt;&lt;span id=&quot;SE-606377e1-f049-402c-af3b-5b7cfc19a76d&quot; class=&quot;se-fs- se-ff-   &quot;&gt;Mobx가 이벤트 구독, 처리하는 비용이 너무 큰 거 아니에요?&lt;/span&gt;&lt;/li&gt;
&lt;li id=&quot;SE-ea58f1dc-3e4e-4983-9961-678f88723891&quot; class=&quot;se-text-paragraph se-text-paragraph-align- &quot;&gt;&lt;span id=&quot;SE-2ac88fb3-e1e5-49c8-8cc7-090f1c04cdfb&quot; class=&quot;se-fs- se-ff-   &quot;&gt;커서 렌더링 할 때 CPU를 너무 많이 점유하네요.&lt;/span&gt;&lt;/li&gt;
&lt;li id=&quot;SE-f45334f4-63e8-4c29-9a45-8ebd1fd66be8&quot; class=&quot;se-text-paragraph se-text-paragraph-align- &quot;&gt;&lt;span id=&quot;SE-548e83ce-5692-42c0-b12a-079a57dc7514&quot; class=&quot;se-fs- se-ff-   &quot;&gt;QA에 들어가면 버그가 너무 많이 나와요.&lt;/span&gt;&lt;/li&gt;
&lt;li id=&quot;SE-e7019cef-7873-4594-a091-84a1d7bd495c&quot; class=&quot;se-text-paragraph se-text-paragraph-align- &quot;&gt;&lt;span id=&quot;SE-d6b7e3e0-7011-4ab6-8939-f11908d96e44&quot; class=&quot;se-fs- se-ff-   &quot;&gt;Undo/Redo 알고리즘이 오류투성이에요.&lt;/span&gt;&lt;/li&gt;
&lt;li id=&quot;SE-c08c1a73-12ef-4d0c-9601-529b94b47063&quot; class=&quot;se-text-paragraph se-text-paragraph-align- &quot;&gt;&lt;span id=&quot;SE-226bedf1-7a8c-41a7-9a3b-b763699a167b&quot; class=&quot;se-fs- se-ff-   &quot;&gt;참고할만한 스펙이 없어요!&lt;/span&gt;&lt;/li&gt;
&lt;li id=&quot;SE-028c08ba-0b90-42e1-8d18-6198a1884187&quot; class=&quot;se-text-paragraph se-text-paragraph-align- &quot;&gt;&lt;span id=&quot;SE-590cf2bd-7962-47fd-b589-6f2a300678bf&quot; class=&quot;se-fs- se-ff-   &quot;&gt;어떤 게 더 나은 UX일까요?&lt;/span&gt;&lt;/li&gt;
&lt;li id=&quot;SE-77548f74-79f2-4f19-a33c-eeb2fedc5f88&quot; class=&quot;se-text-paragraph se-text-paragraph-align- &quot;&gt;&lt;span id=&quot;SE-98c844b0-c074-4e91-af8d-b2f9b9afff65&quot; class=&quot;se-fs- se-ff-   &quot;&gt;커뮤니케이션 관계가 너무 복잡해요.&lt;/span&gt;&lt;/li&gt;
&lt;li id=&quot;SE-df319a8b-a8df-4491-824e-f3a397f5be2c&quot; class=&quot;se-text-paragraph se-text-paragraph-align- &quot;&gt;&lt;span id=&quot;SE-3a7ec1ef-6495-4c12-9c88-4160a804cd6f&quot; class=&quot;se-fs- se-ff-   &quot;&gt;프로젝트 투입 인원이 부족해요.&lt;/span&gt;&lt;/li&gt;
&lt;li id=&quot;SE-cc3c23d2-a48d-4654-a44c-c4a108a1d2db&quot; class=&quot;se-text-paragraph se-text-paragraph-align- &quot;&gt;&lt;span id=&quot;SE-ea730387-8b4f-48a9-a34f-888966284a6f&quot; class=&quot;se-fs- se-ff-   &quot;&gt;일정이 너무 촉박해요.&lt;/span&gt;&lt;/li&gt;
&lt;li id=&quot;SE-c580fe65-87b0-4139-8ef4-50fb42d1a416&quot; class=&quot;se-text-paragraph se-text-paragraph-align- &quot;&gt;&lt;span id=&quot;SE-8cca05af-f269-464a-9f80-4e54e4049e38&quot; class=&quot;se-fs- se-ff-   &quot;&gt;설계 의도를 읽기가 힘들어요.&lt;/span&gt;&lt;/li&gt;
&lt;li id=&quot;SE-95c185e0-ee62-4edc-b9b7-ce23859ac2ff&quot; class=&quot;se-text-paragraph se-text-paragraph-align- &quot;&gt;&lt;span id=&quot;SE-1ab9a08b-bccb-47d8-b989-58b3a6f3f087&quot; class=&quot;se-fs- se-ff-   &quot;&gt;옆 파트에 일이 너무 몰려서, 우리도 일을 진행할 수 없어요.&lt;/span&gt;&lt;/li&gt;
&lt;li id=&quot;SE-3facacee-c883-4e6f-8c22-6d5d90a22bbb&quot; class=&quot;se-text-paragraph se-text-paragraph-align- &quot;&gt;&lt;span id=&quot;SE-74716fea-6e11-4d67-8745-fbefc6e6a105&quot; class=&quot;se-fs- se-ff-   &quot;&gt;예상치 못하게 치고 들어오는 이슈가 너무 많아요.&lt;/span&gt;&lt;/li&gt;
&lt;li id=&quot;SE-2b130a5c-5d9d-4eb2-85f7-29a14c0f7baf&quot; class=&quot;se-text-paragraph se-text-paragraph-align- &quot;&gt;&lt;span id=&quot;SE-75cdb4ea-d839-425d-8dee-3d8f9390803a&quot; class=&quot;se-fs- se-ff-   &quot;&gt;단위 테스트 커버리지가 불충분해요.&lt;/span&gt;&lt;/li&gt;
&lt;li id=&quot;SE-421ba3ae-578e-4f29-ad77-bc518524b513&quot; class=&quot;se-text-paragraph se-text-paragraph-align- &quot;&gt;&lt;span id=&quot;SE-05839727-194e-4774-91a5-7eb8064d22db&quot; class=&quot;se-fs- se-ff-   &quot;&gt;팀에 사람이 너무 많아요.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p id=&quot;SE-14ac11ff-b3d6-4af1-abfd-c2da2c52de12&quot; class=&quot;se-text-paragraph se-text-paragraph-align- &quot;&gt;&lt;span id=&quot;SE-0ecc0263-cdde-49b5-ae16-5afd1c874769&quot; class=&quot;se-fs-fs16 se-ff-   &quot;&gt;나열하자니 끝이 없다.&lt;/span&gt;&lt;/p&gt;
&lt;p id=&quot;SE-813c7ce3-a7e3-4634-9587-caec1a1b51f0&quot; class=&quot;se-text-paragraph se-text-paragraph-align- &quot;&gt;&lt;span id=&quot;SE-41f685e2-7209-4d6f-ad7a-80efa841a837&quot; class=&quot;se-fs-fs16 se-ff-   &quot;&gt;이런 문제는 React, Functional Programming, Webpack, MVVM, NoSQL로는 해결할 수 없다. 어디에도 자랑할 수 있을 기술 스택으로 무장하고 있었지만 일정은 매번 빗나갔으며, 버그는 여전히 많았고, 스펙은 늘 모호하고, 커뮤니케이션은 언제나 어려웠다.&lt;/span&gt;&lt;/p&gt;
&lt;p id=&quot;SE-9e13a4c1-f2d0-41fb-96d1-74cc4ff6acd3&quot; class=&quot;se-text-paragraph se-text-paragraph-align- &quot;&gt;&lt;span id=&quot;SE-ec2f655c-264f-4d0d-a780-55f9b1e324e3&quot; class=&quot;se-fs-fs16 se-ff-   &quot;&gt;답답한 마음에 코드 밖을 떠나 팀이 안고 있는 문제를 고민하고 해법을 찾기 시작했다.&lt;/span&gt;&lt;/p&gt;

  &lt;hr class=&quot;se-hr&quot; /&gt;

&lt;p id=&quot;SE-a6d4da67-8c7c-4692-853a-d0a5c32bca0b&quot; class=&quot;se-text-paragraph se-text-paragraph-align- &quot;&gt;'추정이 매번 빗나가니 구성원이 모여서 상대 추정하는 방식으로 바꿔보면 어떨까?'&lt;/p&gt;
&lt;p class=&quot;se-text-paragraph se-text-paragraph-align- &quot;&gt;'개발과 QA 사이의 간격이 너무 넓은데, 스몰 릴리즈를 해보면 어떨까? 프로세스가 있어야겠네.'&lt;/p&gt;
&lt;p class=&quot;se-text-paragraph se-text-paragraph-align- &quot;&gt;'스펙 작성에 대한 책임은 논외로 하더라도, 인수 테스트 작성은 모두가 할 수 있으면 좋겠다.'&lt;/p&gt;
&lt;p class=&quot;se-text-paragraph se-text-paragraph-align- &quot;&gt;'팀 안에 생기는 문제를 지속적으로 개선해 나갈 수 있게 회고를 만들어야겠다.'&lt;/p&gt;
&lt;p class=&quot;se-text-paragraph se-text-paragraph-align- &quot;&gt;'성능 이슈는 원인이 복잡하게 엮여있으니, 다 같이 모여서 디버깅합시다.'&lt;/p&gt;

&lt;hr /&gt;

&lt;p&gt;&lt;span&gt;해결책을 찾고, 찾은 해결책을 적용하기 위해서 동료들과 토론하고 설득하고 협상하는 과정에서 배우는 게 있었다. 그 즈음이었던 것 같다. 라이브러리나 프레임워크 외에도 개발자가 관심을 가져야 할 것이 많다는 걸 깨달은 때가.&lt;/span&gt;&lt;/p&gt;
&lt;p id=&quot;SE-d285eb84-3a60-40ae-9311-d5d1ed3a8f92&quot; class=&quot;se-text-paragraph se-text-paragraph-align- &quot;&gt;&lt;span&gt;내가 하는 일의 성격을 다시 정의했다. &lt;/span&gt;&lt;/p&gt;

&lt;div id=&quot;SE-93521457-9c3c-454e-b16b-3a519c7908aa&quot; class=&quot;se-component se-quotation se-l-quotation_underline&quot;&gt;
&lt;div class=&quot;se-component-content&quot;&gt;
&lt;div class=&quot;se-section se-section-quotation se-l-quotation_underline&quot;&gt;
&lt;div class=&quot;se-module se-module-text se-quote&quot;&gt;
&lt;h2 id=&quot;SE-89c77ea3-bd1d-4121-a3c9-bf76468ed9e0&quot; class=&quot;se-text-paragraph se-text-paragraph-align- &quot;&gt;&lt;span id=&quot;SE-b39f18de-6782-413b-ba93-305007ff171c&quot; class=&quot;se-fs-fs24 se-ff-   &quot;&gt;&quot;사람이, &lt;/span&gt;사람과 함께, 사람에게 필요한 무언가를 만드는 일&quot;&lt;/h2&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div id=&quot;SE-725c38e3-ad5f-4c73-959b-e514e7f9ee91&quot; class=&quot;se-component se-text se-l-default&quot;&gt;
&lt;div class=&quot;se-component-content&quot;&gt;
&lt;div class=&quot;se-section se-section-text se-l-default&quot;&gt;
&lt;div class=&quot;se-module se-module-text&quot;&gt;
&lt;p id=&quot;SE-71717f90-9293-4d68-99be-9f1ed257b5d3&quot; class=&quot;se-text-paragraph se-text-paragraph-align- &quot;&gt;&lt;span id=&quot;SE-51ef9e52-3397-40ce-812c-b9d510256d9d&quot; class=&quot;se-fs-fs16 se-ff-   &quot;&gt;사람을 모르고 사람과 일을 할 수는 없겠다 싶었다. 설득하고, 협상하고, 인내하는 일련의 과정을 이끌어야만 내가 원하는 것을 얻을 수 있다는 걸 경험으로 배웠다. 이 과정에서 불편한 사람과 대화도 해야 할 것이며, 다수 앞에 나서야 할 일도 있다. 때로는 내가 중요하게 여기는 것을 일부 포기하기도 해야 했다. API를 수집하는 일 외에도 알아야 할, 싫지만 견뎌야 할, 어렵지만 잘 해야 할 일이 있었다. &lt;/span&gt;&lt;/p&gt;
&lt;p id=&quot;SE-14291497-71ef-41c5-9757-24b392de4a67&quot; class=&quot;se-text-paragraph se-text-paragraph-align- &quot;&gt;&lt;span id=&quot;SE-63a9d22c-10d6-4c73-b610-6134843d291a&quot; class=&quot;se-fs-fs16 se-ff-   &quot;&gt;React가 아무리 좋은 기술이고, 내가 React를 잘 안다고 해도 이 녀석을 팀이 받아들일 수 있게 설득할 수 있는 능력이 없다면 React가 다 무슨 소용이람. 당위성을 설명하고, 일정을 확보해야 한다. &lt;/span&gt;&lt;/p&gt;
&lt;p id=&quot;SE-e4932432-5eee-4895-af84-c7cd2b4bbc7c&quot; class=&quot;se-text-paragraph se-text-paragraph-align- &quot;&gt;&lt;span id=&quot;SE-cbaea19c-27cd-4a92-aad2-673ae33413d2&quot; class=&quot;se-fs-fs16 se-ff-   &quot;&gt;어렵사리 도입을 결정해도 꽃길만 걸을 수는 없다. 레거시 위에서 낡은 것과 새로운 것을 엮는 일의 난도는, 인터넷에 떠도는 예제를 작성하는 것에 비해 훨씬 어렵다. 옛것과 새것이 공존하는 혼란의 시기도 견뎌야 한다. 혼란에 지친 동료의 투정을 다독이며 앞으로 나아가는 가시밭길의 쓰라림은 또 어떤가.&lt;/span&gt;&lt;/p&gt;
&lt;p id=&quot;SE-9e05b348-11ea-4f2d-9f29-b5db0f63bc76&quot; class=&quot;se-text-paragraph se-text-paragraph-align- &quot;&gt;&lt;span id=&quot;SE-a09d5837-83f4-4e70-a4ca-d451fb2fa60b&quot; class=&quot;se-fs-fs16 se-ff-   &quot;&gt;생각이 여기에 미치니, 1차원이었던 세상(코딩)이 2차원(팀), 3차원(조직과 조직)으로 확장되어 보이기 시작했다. 제품을 개발하는 전 과정을 아우르는 모든 것들을 관심사에 담았다.&lt;/span&gt;&lt;span id=&quot;SE-5478d763-8c23-4df6-8fcc-81ae7bf3c147&quot; class=&quot;se-fs-fs16 se-ff-   &quot;&gt; &lt;/span&gt;&lt;/p&gt;
&lt;p id=&quot;SE-d399254c-e34f-4ad3-85b0-6dc3d040b596&quot; class=&quot;se-text-paragraph se-text-paragraph-align- &quot;&gt;&lt;span id=&quot;SE-c92d9302-ab60-4884-a69a-c89ba8ffcbc8&quot; class=&quot;se-fs-fs16 se-ff-   &quot;&gt;고민의 결이 바뀌었다.&lt;/span&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div id=&quot;SE-1481f4c9-5f7e-4e3a-852a-40e9367ace8a&quot; class=&quot;se-component se-quotation se-l-quotation_underline&quot;&gt;
&lt;div class=&quot;se-component-content&quot;&gt;
&lt;div class=&quot;se-section se-section-quotation se-l-quotation_underline&quot;&gt;
&lt;div class=&quot;se-module se-module-text se-quote&quot;&gt;
&lt;h2 id=&quot;SE-3526ca9e-a295-4444-b399-3759ab8acccc&quot; class=&quot;se-text-paragraph se-text-paragraph-align- &quot;&gt;&lt;span id=&quot;SE-7f4ee7ae-b0f0-4ca7-8654-00c7e1c00192&quot; class=&quot;se-fs-fs24 se-ff-   &quot;&gt;&quot;어떻게 하면 우리가 제품을 &lt;/span&gt;더 잘 만들 수 있을까?&quot;&lt;/h2&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div id=&quot;SE-0144a30e-69f2-45c3-a0f8-cd0339070ce2&quot; class=&quot;se-component se-text se-l-default&quot;&gt;
&lt;div class=&quot;se-component-content&quot;&gt;
&lt;div class=&quot;se-section se-section-text se-l-default&quot;&gt;
&lt;div class=&quot;se-module se-module-text&quot;&gt;
&lt;p id=&quot;SE-c176567f-778f-43a6-a80b-14a1aa25344c&quot; class=&quot;se-text-paragraph se-text-paragraph-align- &quot;&gt;&lt;span id=&quot;SE-9454ddd3-50d7-4e04-b520-ff782dca1471&quot; class=&quot;se-fs-fs16 se-ff-   &quot;&gt;얼마 전 내 페이스북 타임라인에 올라온 박성철 님의 포스트가 재밌다. 어떤 논문에 나와있는 내용을 근거로 &lt;/span&gt;&lt;span id=&quot;SE-79dc86c3-55c9-4fe8-8607-703ea58748f6&quot; class=&quot;se-fs-fs16 se-ff-   &quot;&gt;&lt;b&gt;학교와 실무에서 배우지 못해, 정작 실무에서 필요로하는 만큼 역량을 쌓기 힘든 분야 10가지&lt;/b&gt;&lt;/span&gt;&lt;span id=&quot;SE-95d66584-bc9a-4d76-aa36-3b9e62248008&quot; class=&quot;se-fs-fs16 se-ff-   &quot;&gt;를 나열하고 있다.&lt;/span&gt;&lt;span id=&quot;SE-b535a8a0-7ccc-40b4-bfde-6da764b9e21a&quot; class=&quot;se-fs- se-ff-   &quot;&gt; &lt;/span&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li id=&quot;SE-9feadb0c-85b4-4728-a945-b356cf7f85d8&quot; class=&quot;se-text-paragraph se-text-paragraph-align- &quot;&gt;&lt;span id=&quot;SE-3f1586ce-6562-464c-80dc-098e951f33d7&quot; class=&quot;se-fs- se-ff-  se-style-unset &quot;&gt;협상&lt;/span&gt;&lt;/li&gt;
&lt;li id=&quot;SE-b0624dd3-35c2-42f0-8961-63d4818cd033&quot; class=&quot;se-text-paragraph se-text-paragraph-align- &quot;&gt;&lt;span id=&quot;SE-afcee27a-bd7e-4ec7-aff3-227df71b96e8&quot; class=&quot;se-fs- se-ff-  se-style-unset &quot;&gt;인간-컴퓨터 상호작용 / 사용자 인터페이스&lt;/span&gt;&lt;/li&gt;
&lt;li id=&quot;SE-9d309dad-7210-4b24-a75f-5f14794738f9&quot; class=&quot;se-text-paragraph se-text-paragraph-align- &quot;&gt;&lt;span id=&quot;SE-83e30ec4-4c9e-4a60-a410-9c3cb16e8ab8&quot; class=&quot;se-fs- se-ff-  se-style-unset &quot;&gt;리더십&lt;/span&gt;&lt;/li&gt;
&lt;li id=&quot;SE-9836d126-ecca-4286-a6a6-5cf8c5ca9741&quot; class=&quot;se-text-paragraph se-text-paragraph-align- &quot;&gt;&lt;span id=&quot;SE-3511bb34-641f-4c0d-896b-db0b42896a26&quot; class=&quot;se-fs- se-ff-  se-style-unset &quot;&gt;실시간 시스템 설계&lt;/span&gt;&lt;/li&gt;
&lt;li id=&quot;SE-657e53dc-9c05-445e-ba80-73ec68a64ca6&quot; class=&quot;se-text-paragraph se-text-paragraph-align- &quot;&gt;&lt;span id=&quot;SE-c2bfdffb-1ef4-43d9-9d86-23f1cb15e3a5&quot; class=&quot;se-fs- se-ff-  se-style-unset &quot;&gt;관리&lt;/span&gt;&lt;/li&gt;
&lt;li id=&quot;SE-d0e1f636-22df-4060-9806-aa369c3a76c8&quot; class=&quot;se-text-paragraph se-text-paragraph-align- &quot;&gt;&lt;span id=&quot;SE-db7599e8-d2f4-4bd6-a6e9-73544bb8ea54&quot; class=&quot;se-fs- se-ff-  se-style-unset &quot;&gt;소프트웨어 비용 추정&lt;/span&gt;&lt;/li&gt;
&lt;li id=&quot;SE-53e1ee50-a512-4fdf-9fb0-48110cf0148d&quot; class=&quot;se-text-paragraph se-text-paragraph-align- &quot;&gt;&lt;span id=&quot;SE-e843295e-6dd6-4a77-8267-db5fd59676dc&quot; class=&quot;se-fs- se-ff-  se-style-unset &quot;&gt;소프트웨어 지표&lt;/span&gt;&lt;/li&gt;
&lt;li id=&quot;SE-9350f814-3099-49cb-9530-414b89fd24d4&quot; class=&quot;se-text-paragraph se-text-paragraph-align- &quot;&gt;&lt;span id=&quot;SE-9f01ce49-df62-4b17-a8af-2f46ec8317d7&quot; class=&quot;se-fs- se-ff-  se-style-unset &quot;&gt;소프트웨어 신뢰성(reliability)과 장애 내성(fault tolerance)&lt;/span&gt;&lt;/li&gt;
&lt;li id=&quot;SE-b3b22f4b-f1ce-4794-b0e0-fac0e6838bc5&quot; class=&quot;se-text-paragraph se-text-paragraph-align- &quot;&gt;&lt;span id=&quot;SE-b7a94418-6b3b-4bdd-99b7-3a606dc73c6d&quot; class=&quot;se-fs- se-ff-  se-style-unset &quot;&gt;윤리와 전문가 의식&lt;/span&gt;&lt;/li&gt;
&lt;li id=&quot;SE-a8ddc877-150e-4dd3-972b-4c85b252afd9&quot; class=&quot;se-text-paragraph se-text-paragraph-align- &quot;&gt;&lt;span id=&quot;SE-499ad730-5d45-4341-b13c-21c887f0fb32&quot; class=&quot;se-fs- se-ff-  se-style-unset &quot;&gt;요구사항 수집/분석&lt;/span&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div id=&quot;SE-53c6e9b7-0074-4ffd-87cc-2a8f172ab51a&quot; class=&quot;se-component se-text se-l-default&quot;&gt;
&lt;div class=&quot;se-component-content&quot;&gt;
&lt;div class=&quot;se-section se-section-text se-l-default&quot;&gt;
&lt;div class=&quot;se-module se-module-text&quot;&gt;
&lt;p id=&quot;SE-24205ab6-6992-4a51-af8f-ca71bcbd96e7&quot; class=&quot;se-text-paragraph se-text-paragraph-align- &quot;&gt;&lt;span id=&quot;SE-e20968cb-6a86-4144-b1ec-fcd54916f6b7&quot; class=&quot;se-fs-fs16 se-ff-   &quot;&gt;모두 다 제품을 잘 만들기 위해서 우리가 챙겨야할 것들이다. 물론 한 개인이 모든 방면에서 뛰어나기는 어렵다. 그게 사람이기에 사람은 분업을, 더 나아가 협업을 한다.&lt;/span&gt;&lt;/p&gt;
&lt;p id=&quot;SE-17df00e7-4149-4f56-87cb-ff7f00bf94fe&quot; class=&quot;se-text-paragraph se-text-paragraph-align- &quot;&gt;&lt;span id=&quot;SE-eb6593ac-13a8-464f-9a8f-8fb68c9ef22c&quot; class=&quot;se-fs-fs16 se-ff-   &quot;&gt;어쨌든 이런 내용은 종합적으로 엮어서 체계적으로 가르쳐주는 데가 없다. 더군다나 나처럼 인문학도에, 비전공자인 경우는 아예 이런 주제가 있다는 걸 모르고 사는 경우도 많다. &lt;/span&gt;&lt;/p&gt;
&lt;p id=&quot;SE-ef658130-55b8-4a10-ad83-ef0d6c7888be&quot; class=&quot;se-text-paragraph se-text-paragraph-align- &quot;&gt;&lt;span id=&quot;SE-92588bc1-e19e-4b5c-af4e-cd7582999d4d&quot; class=&quot;se-fs-fs16 se-ff-   &quot;&gt;운이 좋았다. 복잡한 조직에서, 복잡한 제품을 만들며, 복잡한 문제 속에 놓여있었기에 자연스레 이런 주제에도 관심을 가질 수 있었다. 체계적으로 배울 수 있는 곳이 없었기에 인터넷에 떠도는 글을 찾아서 읽거나, 선배에게 조언을 듣거나, 파편적으로 책을 찾아서 보고 얻은 지식을 연결해서 패턴을 찾았다. &lt;/span&gt;&lt;span id=&quot;SE-1ddff165-078a-48e8-88a7-b1934fcaae47&quot; class=&quot;se-fs-fs16 se-ff-   &quot;&gt; &lt;/span&gt;&lt;/p&gt;
&lt;p id=&quot;SE-b2dc05f9-e290-49f9-b9c3-f2aa50afc00f&quot; class=&quot;se-text-paragraph se-text-paragraph-align- &quot;&gt;&lt;span id=&quot;SE-41f954e2-155d-48cd-a9c6-3ddfaf71de9f&quot; class=&quot;se-fs-fs16 se-ff-   &quot;&gt;하지만 배운 내용을 하루 아침에 실전에서 발휘할 수는 없었다. 리팩터링 책을 아무리 읽고 읽어도 도대체 어디에 써먹어야 할지 알 수 없듯이. 프레젠테이션 교육을 받았다고 내일 당장 내가 스티브 잡스가 될 수는 없듯이.&lt;/span&gt;&lt;/p&gt;
&lt;p id=&quot;SE-245ccdbf-1965-427f-830d-a4e1789264cb&quot; class=&quot;se-text-paragraph se-text-paragraph-align- &quot;&gt;&lt;span id=&quot;SE-4fae268f-ad92-4bde-bb83-c8dbf2543f4f&quot; class=&quot;se-fs-fs16 se-ff-   &quot;&gt;읽어둔 이론을 토대로 현실과 이론 사이의 연결고리를 찾고, 현실에 조금씩 적용해나가며 경험치를 쌓았다. 물론 경험이라는 것 또한 단순히 쌓는 걸로 체득되지는 않았다. 경험은 애써 잡아두지 않으면 흩어져서 사라져버린다. 뒤를 돌아보고 개선하고 시도하고 다시 반복하는 과정. 이게 없으면 흔히 농담으로 말하는 &lt;/span&gt;&lt;span id=&quot;SE-e701b211-bd82-41e9-8b5e-809acee060b7&quot; class=&quot;se-fs-fs16 se-ff-   &quot;&gt;&lt;b&gt;나이(연차)를 똥으로 먹은 사람&lt;/b&gt;&lt;/span&gt;&lt;span id=&quot;SE-ecf3e21c-dbf6-4976-967d-47ed769cd26d&quot; class=&quot;se-fs-fs16 se-ff-   &quot;&gt;이 되기 쉽겠더라. &lt;/span&gt;&lt;/p&gt;
&lt;p id=&quot;SE-4967b626-b08f-4185-bedf-a7391168855c&quot; class=&quot;se-text-paragraph se-text-paragraph-align- &quot;&gt;&lt;span id=&quot;SE-7e89b47c-029d-4599-8678-46a981470df5&quot; class=&quot;se-fs-fs16 se-ff-   &quot;&gt;어제를 돌아보며 경험을 체계화하고 개선하는 사이클을 반복했다. 조금씩 아는 것들이 생겼고 나아졌지만 때로는 미친 척하는 용기도 필요했다. 확신하기 어려운 일을 동료들 앞에 서서 해보자고 제안할 때면 그냥 혼자 코딩하던 시절이 그리울 때도 있다. 사람들 앞에 서는 순간이면 숨이 가빠진다. 그걸 견뎌야만 원하는 걸 얻을 수 있었기에 참고 있을 뿐. 다행히도 견디다 보니 조금은 무뎌지더라.&lt;/span&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div id=&quot;SE-0c1a1988-133b-4a21-87cf-1f5fcd69d0b3&quot; class=&quot;se-component se-quotation se-l-quotation_underline&quot;&gt;
&lt;div class=&quot;se-component-content&quot;&gt;
&lt;div class=&quot;se-section se-section-quotation se-l-quotation_underline&quot;&gt;
&lt;div class=&quot;se-module se-module-text se-quote&quot;&gt;
&lt;h2 id=&quot;SE-5158792a-0e4b-4ac7-847b-52b230d6b368&quot; class=&quot;se-text-paragraph se-text-paragraph-align- &quot;&gt;&lt;span id=&quot;SE-89b3eb15-aff7-45fb-88e6-6e3a0d05baa6&quot; class=&quot;se-fs-fs24 se-ff-   &quot;&gt;&quot;개발자에게 성장이란 무엇일까?&quot;&lt;/span&gt;&lt;/h2&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div id=&quot;SE-776a9218-c35b-4233-bb44-e1347ce32401&quot; class=&quot;se-component se-text se-l-default&quot;&gt;
&lt;div class=&quot;se-component-content&quot;&gt;
&lt;div class=&quot;se-section se-section-text se-l-default&quot;&gt;
&lt;div class=&quot;se-module se-module-text&quot;&gt;
&lt;p id=&quot;SE-90deb3f9-3399-4d48-a9a0-186768dca313&quot; class=&quot;se-text-paragraph se-text-paragraph-align- &quot;&gt;&lt;span id=&quot;SE-7765d178-61f4-401d-9017-9d8e6bfd9de6&quot; class=&quot;se-fs-fs16 se-ff-   &quot;&gt;작년에 팀에서 성장에 대해서 설문조사를 한 적이 있다. 개인의 성장과 조직의 성장 사이의 간극을 찾고 서로의 목표를 최대한 맞춰보려는 시도였다. 그러기 위해서는 저마다가 생각하는 성장의 의미를 읽어야만 했다. 질문을 받았지만 선뜻 답이 나오질 않았다. 진지하게 고민해본 적이 없었다.&lt;/span&gt;&lt;/p&gt;
&lt;p id=&quot;SE-6a1e0126-c59a-4878-b48f-dff202c39f0b&quot; class=&quot;se-text-paragraph se-text-paragraph-align- &quot;&gt;&lt;span id=&quot;SE-c50eb54d-b09c-42a7-a11f-e6f900c9c222&quot; class=&quot;se-fs-fs16 se-ff-   &quot;&gt;언젠가 팀의 업무 프로세스를 개선하려고 사용자 스토리를 도입할 것을 제안했던 적이 있다. 이 제안을 하기 위해서 같은 책을 5번은 읽었고, 관련 글을 인터넷에서 20개도 넘게 찾아서 읽었으며, 의도를 설명하는 전체 &lt;/span&gt;&lt;span id=&quot;SE-26e8634d-8be4-46af-9de1-a7b5eac23363&quot; class=&quot;se-fs-fs16 se-ff-   &quot;&gt;메일을 3번은 쓴 것 같다. 위키를 2번 정도 작성해서 공유했으며, 모든 동료가 모인 자리에서 2번이나 설명을 했다. 아니 3번이었나? &lt;/span&gt;&lt;/p&gt;
&lt;p id=&quot;SE-09175e6d-2136-4539-bad7-9d386da8384c&quot; class=&quot;se-text-paragraph se-text-paragraph-align- &quot;&gt;&lt;span id=&quot;SE-5a5ff957-fcdb-490a-a00a-31777df11a7f&quot; class=&quot;se-fs-fs16 se-ff-   &quot;&gt;어쨌든. 물론 개인 대 개인으로 자잘하게 설명한 경우는 더 많다. 이제는 모두 이해했을까 싶었을 때 누군가 내게 물었다.&lt;/span&gt;&lt;span id=&quot;SE-48a3834b-7eaa-4257-995c-f68c9e6902c6&quot; class=&quot;se-fs-fs16 se-ff-   &quot;&gt; &lt;/span&gt;&lt;/p&gt;
&lt;p id=&quot;SE-26d4f73a-4d3c-4cf0-b911-1a8d8fcaa3c2&quot; class=&quot;se-text-paragraph se-text-paragraph-align- &quot;&gt;&lt;span id=&quot;SE-0ddbdeab-9fbc-4611-9e3d-af96602f9516&quot; class=&quot;se-fs-fs16 se-ff-   se-decoration-unset&quot;&gt;&quot;스토리 포인트가 꼭 필요한가요?&quot;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;.&lt;/p&gt;
&lt;p&gt;.&lt;/p&gt;
&lt;p&gt;.&lt;/p&gt;
&lt;p id=&quot;SE-c6a0622f-62d3-4e51-ae7e-b064bcf85ee4&quot; class=&quot;se-text-paragraph se-text-paragraph-align- &quot;&gt;&lt;span id=&quot;SE-97d469c9-6aa0-440e-9ba2-5afdb97ebc36&quot; class=&quot;se-fs-fs16 se-ff-   &quot;&gt;이제 와서 돌아보니 더 나은 방향을 찾아서 동료와, 리더와, 제품과, 코드와 투닥거리는 이 모든 과정이 성장이었더라.&lt;/span&gt;&lt;span id=&quot;SE-34cda410-93ae-4d3b-9be7-4179acff04b7&quot; class=&quot;se-fs-fs16 se-ff-   &quot;&gt; &lt;/span&gt;&lt;/p&gt;
&lt;p id=&quot;SE-9469453f-d3e2-48bc-87be-211f3f201d65&quot; class=&quot;se-text-paragraph se-text-paragraph-align- &quot;&gt;&lt;span id=&quot;SE-cda979ed-c81a-490b-a112-77352061adb3&quot; class=&quot;se-fs-fs16 se-ff-   &quot;&gt;오늘 나에게 누군가 성장이 무어냐고 묻는다면, &lt;/span&gt;&lt;span id=&quot;SE-6a2c65d6-fa37-41a5-bbef-17815c8cdbef&quot; class=&quot;se-fs-fs16 se-ff-   &quot;&gt;&lt;u&gt;&lt;b&gt;다양한 사람의 의지가 뒤섞이는 개발이라는 큰 운동장에서, 본인의 역할을 질문하고 찾아가는 과정&lt;/b&gt;&lt;/u&gt;&lt;/span&gt;&lt;span id=&quot;SE-5fec5c1c-53e7-463d-bde2-19797a572f92&quot; class=&quot;se-fs-fs16 se-ff-   &quot;&gt;이라고 답하고 싶다. &lt;/span&gt;&lt;/p&gt;
&lt;p id=&quot;SE-1f1e0237-4415-4c86-8941-1b70b453f981&quot; class=&quot;se-text-paragraph se-text-paragraph-align- &quot;&gt;직업 전문인으로서, 개발자에게 코딩은 목적이 아닌 수단이어야 한다는 꼰대 아닌 꼰대 같은 생각을 가끔 한다. 개발이라는 더 큰 관점에서 자신의 업무를 돌아보면 좋겠다. 나를 보고, 동료를 보고, 조직을 보고, 제품을 보고, 일이 흘러가는 흐름을 보자.&lt;/p&gt;
&lt;p class=&quot;se-text-paragraph se-text-paragraph-align- &quot;&gt;모두가 그 안에서 자신의 가치를 찾을 수 있기를 바라며.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
</description>
            <pubDate>Wed, 06 Mar 2019 21:43:13 +0900</pubDate>
            <link>https://huns.me/development/2281</link>
            <guid isPermaLink="true">https://huns.me/development/2281</guid>
            
            <category>개발자</category>
            
            <category>성장</category>
            
            <category>회고</category>
            
            
            <category>개발 이야기</category>
            
        </item>
        
        <item>
            <title>Webpack 4의 Tree Shaking에 대한 이해</title>
            <description>&lt;p&gt;
				회사에서 만들고 있는 새로운 버전의 스마트에디터는 Webpack 4를 빌드 도구로 사용한다. 초기 로딩 성능을 최적화하기 위해서 Webpack 4의 Tree Shaking 지원을 검토하다가 삽질을 많이 했다. 공부 안 하고 대충하면 될 줄 알았는데 안 되더라.&lt;/p&gt;
&lt;p&gt;경험을 내 안에 썩혀두기가 아까워서 팀에 공유할 목적으로 삽질기를 작성했다. 외부의 누군가에게도 도움이 될 것 같아 원문을 조금 다듬어서 블로그로 옮긴다. 의식의 흐름을 따라 쓴 글이라 두서가 없으니, 찰떡같이 읽어주시길.&lt;/p&gt;
&lt;h1&gt;1.Tree Shaking이란?&lt;/h1&gt;
&lt;p&gt;Tree Shaking은 직역하면 &lt;strong&gt;나무 흔들기 &lt;/strong&gt;정도로 표현할 수 있다. Webpack이 JS 모듈을 번들링할 때 사용하지 않는 코드를 제거하는 최적화 과정을 말한다. 나무를 흔들면 잎이 떨어지듯이 사용하지 않는 코드를 털어낸다는 뜻이다. 개념에 이름을 붙일 때 은유를 이용하는 걸 좋아한다. 메타포!&lt;br /&gt;
 &lt;br /&gt;
원래 이 용어를 처음 최적화 개념에 사용한 건 rollup.js지만 Webpack 4가 대단히 영리한 최적화 빌드를 보여주면서 주목받는 사이 rollup.js는 잊힌 느낌이다.&lt;/p&gt;
&lt;p&gt;인터넷에 떠도는 예제와 달리 현실의 설정은 매우 복잡하다. 95개의 패키지를 가지고 있는 Monorepo니 복잡할 수밖에. 그래서 그런지 아무리 설정을 바꿔봐도 최종 번들 파일의 사이즈가 좀처럼 줄어들지를 않았다. 제대로 이해하고 써야겠다는 생각에 &lt;a href=&quot;https://github.com/CoderK/tree-shaking-with-side-effect-test&quot;&gt;예제 코드&lt;/a&gt;를 만들어서 테스트를 하기로 했다.&lt;/p&gt;
&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-javascript&quot; data-lang=&quot;javascript&quot;&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;k&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;a&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;console&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;a&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;b&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;console&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;b&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;c&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;console&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;c&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;a&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;b&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;c&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;./abc&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;add&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;as&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;reexportedAdd&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;multiply&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;as&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;reexportedMultiply&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;./math&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;add&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;sum&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;i&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;args&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;arguments&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;l&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;args&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;length&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;while&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;i&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;lt&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;l&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;nx&quot;&gt;sum&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;args&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;++&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;];&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;sum&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;multiply&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;nx&quot;&gt;console&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;이것은 곱하기!&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;product&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;i&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;args&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;arguments&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;l&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;args&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;length&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;while&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;i&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;lt&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;l&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;nx&quot;&gt;product&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;args&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;++&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;];&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;product&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;list&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;nx&quot;&gt;console&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;이것은 리스트!&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;Array&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;from&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;arguments&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;add&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;./math&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;as&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;library&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;./library&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;nx&quot;&gt;add&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;

&lt;span class=&quot;nx&quot;&gt;library&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;reexportedMultiply&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;
&lt;p&gt;index.js가 엔트리 포인트다. 의도적으로 math.js의 list 함수를 import 하되, 사용하지 않도록 구성했다. Tree Shaking이 정상 동작한다면, 최종 빌드 된 파일에는 list 함수가 없어야한다.&lt;/p&gt;
&lt;p&gt;추적을 쉽게 하기 위해서 콘솔에 문자열을 출력하는 코드를 함수 중간에 추가했다. production 빌드를 하면 소스가 난독화가 되어버려 list 함수를 찾기가 어렵다. 문자열은 난독화되지 않기 때문에 빌드 파일에서 저 문자열을 검색하면 Tree Shaking 수행여부를 확인할 수 있으리라.&lt;/p&gt;
&lt;p&gt;먼저 development 모드로 빌드를 했다. Webpack의 기본 옵션은 development 모드일 때 코드를 최적화하지 않도록 구성되어 있다. 따라서 최적화의 하나인 Tree Shaking을 수행하지 않아야 한다.&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-javascript&quot; data-lang=&quot;javascript&quot;&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;err&quot;&gt;…&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;err&quot;&gt;생략&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;use strict&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;nx&quot;&gt;__webpack_require__&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;r&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;__webpack_exports__&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;cm&quot;&gt;/* harmony export (binding) */&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;__webpack_require__&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;d&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;__webpack_exports__&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;add&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;function&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;add&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;});&lt;/span&gt;
&lt;span class=&quot;cm&quot;&gt;/* harmony export (binding) */&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;__webpack_require__&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;d&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;__webpack_exports__&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;multiply&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;function&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;multiply&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;});&lt;/span&gt;
&lt;span class=&quot;cm&quot;&gt;/* harmony export (binding) */&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;__webpack_require__&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;d&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;__webpack_exports__&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;list&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;function&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;list&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;});&lt;/span&gt;
&lt;span class=&quot;kd&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;add&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;sum&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;i&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;args&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;arguments&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;l&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;args&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;length&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;while&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;i&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;lt&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;l&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;nx&quot;&gt;sum&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;args&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;++&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;];&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;sum&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;kd&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;multiply&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;nx&quot;&gt;console&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;이것은 곱하기!&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;product&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;i&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;args&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;arguments&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;l&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;args&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;length&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;while&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;i&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;lt&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;l&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;nx&quot;&gt;product&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;args&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;++&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;];&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;product&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;kd&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;list&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;nx&quot;&gt;console&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;이것은 리스트!&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;Array&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;from&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;arguments&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;


&lt;span class=&quot;c1&quot;&gt;//…(생략)&lt;/span&gt;
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;
&lt;p&gt;번들 파일 안에 list 함수가 있는 게 보인다. 이번에는 production 모드로 빌드를 해서 비교를 해보자.&lt;/p&gt;
&lt;p&gt;&lt;span style=&quot;font-size: 10pt;&quot;&gt;(가독성 향상을 위해서 beautify하였음)&lt;/span&gt;&lt;/p&gt;
&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-javascript&quot; data-lang=&quot;javascript&quot;&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;c1&quot;&gt;//…(생략)&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}([&lt;/span&gt;&lt;span class=&quot;kd&quot;&gt;function&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;t&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;e&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;n&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;use strict&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;kd&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;r&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kd&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;t&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;e&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;n&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;arguments&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;r&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;n&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;length&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;e&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;lt&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;r&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;)&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;t&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;n&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;e&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;++&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;];&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;t&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;kd&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;o&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;nx&quot;&gt;console&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;이것은 곱하기!&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kd&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;t&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;e&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;n&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;arguments&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;r&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;n&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;length&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;e&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;lt&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;r&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;)&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;t&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;n&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;e&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;++&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;];&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;t&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;nx&quot;&gt;n&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;d&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;e&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;a&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;function&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;r&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}),&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;n&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;d&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;e&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;b&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;function&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;o&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;})&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;},&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;function&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;t&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;e&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;n&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;use strict&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;},&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;function&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;t&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;e&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;n&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;use strict&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;nx&quot;&gt;n&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;r&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;n&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;nx&quot;&gt;n&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;d&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;e&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;a&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;function&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;r&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;b&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;})&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;},&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;function&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;t&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;e&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;n&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;use strict&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;nx&quot;&gt;n&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;r&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;e&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;r&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;n&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt;
        &lt;span class=&quot;nx&quot;&gt;o&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;n&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;nb&quot;&gt;Object&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;r&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;a&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;o&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;a&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}]);&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;//…(생략)&lt;/span&gt;
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;
&lt;p&gt;코드가 난독화되어 읽기 어렵지만 list 함수에 심어놓은 콘솔 출력 문자열이 보이지 않는 걸로 보아 Tree Shaking이 되었다고 유추할 수 있다.&lt;/p&gt;
&lt;p&gt;한 가지 의문이 생긴다. Webpack은 아래와 같은 import 구문도 최적화를 할 수 있는 걸까?&lt;/p&gt;
&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-javascript&quot; data-lang=&quot;javascript&quot;&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1
&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;k&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;as&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;library&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;./library&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;
&lt;p&gt;Webpack 4 이전까지는 이런 구문을 최적화하지 못하였다. 그래서 인터넷에 떠도는 일부 유통기한이 지난 문서에서 최적화를 위해서 이런 구문을 사용하지 말라는 내용을 가끔 볼 수 있었다. 이제는 아닌가 보네?&lt;/p&gt;
&lt;p&gt;확인을 하고 넘어가야 직성이 풀릴 듯하여 검색을 하기 시작했다.&lt;/p&gt;
&lt;h1&gt;3. import * as … from&lt;/h1&gt;
&lt;p&gt;개발을 하다 보면 특정 모듈을 가져와서 import와 동시에 export 하고 싶을 때가 있다. 주로 모듈의 인덱스 파일을 작성할 때 그렇다.&lt;/p&gt;
&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-javascript&quot; data-lang=&quot;javascript&quot;&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1
&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;k&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;as&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;library&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;./library&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;
&lt;p&gt;이런 구문을 뭐라고 불러야 할지 모르겠다. 이 글에서는 &lt;code&gt;re-export&lt;/code&gt;라고 부를 생각이다. Tree Shaking을 스마트에디터에 적용하려고 보니, 이 패턴을 너무 많은 곳에서 쓰고 있어서 퍽 난감했다. 또, 의지의 영역으로 들어온 것인가. 한숨을 쉬고 있던 그때. 응? Tree Shaking이 되네?&lt;/p&gt;
&lt;p&gt;궁금해서 Webpack의 공식 문서를 찬찬히 읽어보다가 흥미로운 내용을 발견했다. Webpack 4에 생긴 providedExports라는 최적화 옵션.&lt;/p&gt;
&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-javascript&quot; data-lang=&quot;javascript&quot;&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1
2
3
4
5
&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;nx&quot;&gt;module&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;exports&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;optimization&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;providedExports&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;true&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;};&lt;/span&gt;
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;
&lt;p&gt;문서는 이 옵션의 용도를 이렇게 설명한다.&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;Tells webpack to figure out which exports are provided by modules to generate more efficient code for export * from .... By default optimization.providedExports is enabled.&lt;br /&gt;
&lt;a href=&quot;https://webpack.js.org/configuration/optimization/#optimization-providedexports&quot; rel=&quot;nofollow&quot;&gt;https://webpack.js.org/configuration/optimization/#optimization-providedexports&lt;/a&gt;&lt;/p&gt;&lt;/blockquote&gt;
&lt;p&gt;&lt;code&gt;export * from&lt;/code&gt;을 위해 만들어진 옵션. 나에게 필요한 그것. Webpack 만세!&lt;/p&gt;
&lt;p&gt;Webpack 4는 optimization 프로퍼티로 최적화 설정을 전달받는다. production 빌드를 할 때, 개발자가 optimization을 별도로 설정하지 않으면 기본 값을 적용한다. providedExports는 기본값이 true다. 따로 설정을 하지 않아도 re-export 구문을 최적화한다. 당연한 이야기지만 false로 설정하면 re-export 구문은 최적화 대상에서 제외된다.&lt;/p&gt;
&lt;p&gt;누가 이걸 false로 설정할까 싶지만, 모든 최적화에는 비용이 들기 마련이다. re-export 구문을 분석하고 최적화하는 과정 역시 그렇다. re-export 구문을 사용하지 않는다면 굳이 빌드 시간을 더 소비할 이유가 없다.&lt;/p&gt;
&lt;p&gt;providedExports를 false로 설정하고 빌드를 해보면, 빌드 버전에서 list 함수를 볼 수 있다.&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-javascript&quot; data-lang=&quot;javascript&quot;&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;p&quot;&gt;([&lt;/span&gt;&lt;span class=&quot;kd&quot;&gt;function&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;n&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;t&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;r&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;use strict&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;kd&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;e&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kd&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;n&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;t&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;r&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;arguments&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;e&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;r&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;length&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;t&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;lt&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;e&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;)&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;n&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;r&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;t&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;++&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;];&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;n&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;kd&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;o&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;nx&quot;&gt;console&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;이것은 곱하기!&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kd&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;n&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;t&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;r&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;arguments&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;e&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;r&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;length&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;t&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;lt&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;e&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;)&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;n&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;r&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;t&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;++&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;];&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;n&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;kd&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;u&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;console&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;이것은 리스트!&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;Array&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;from&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;arguments&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;nx&quot;&gt;r&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;r&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;t&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;r&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;d&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;t&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;add&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;function&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;e&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}),&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;r&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;d&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;t&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;multiply&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;function&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;o&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}),&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;r&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;d&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;t&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;list&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;function&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;u&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;})&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;},&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;function&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;n&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;t&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;r&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;h1&gt;4. lodash와 sideEffects&lt;/h1&gt;
&lt;p&gt;Tree Shaking을 찾아보다가, lodash가 생각이 났다. 사용하지 않는 lodash 모듈을 빌드 버전에서 제외하려고 babel-plugin-lodash를 사용해왔다. Webpack 4의 Tree Shaking이 있으니, 이제 이 플러그인과 작별해도 되겠네? 테스트를 해보자.&lt;/p&gt;
&lt;p&gt;예제에 lodash 패키지를 추가하고,&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-javascript&quot; data-lang=&quot;javascript&quot;&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1
&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;nx&quot;&gt;npm&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;i&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;lodash&lt;/span&gt;
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;index.js의 코드를 아래와 같이 수정한 다음에 development 모드로 빌드를 했다.&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-javascript&quot; data-lang=&quot;javascript&quot;&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1
2
3
4
5
6
7
8
&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;k&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;add&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;./math&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;as&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;library&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;./library&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;defaults&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;lodash&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;nx&quot;&gt;defaults&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;최적화!&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;

&lt;span class=&quot;nx&quot;&gt;add&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;nx&quot;&gt;library&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;reexportedMultiply&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;번들링은 끝이 났고 최종 산출물에서 아까 추가한 코드가 보인다.&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-javascript&quot; data-lang=&quot;javascript&quot;&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1
&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;nb&quot;&gt;Object&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;lodash__WEBPACK_IMPORTED_MODULE_2__&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;defaults&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;])(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;최적화!&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;import 하지 않은 lodash의 다른 모듈은? Webpack의 보일러 플레이트 코드와 뒤섞여 혼란한 와중에서도 찾기 쉬운 이름의 함수를 검색하는 게 좋을 것 같다. &lt;code&gt;differenceWith&lt;/code&gt; 함수를 검색했다.&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-javascript&quot; data-lang=&quot;javascript&quot;&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1
2
3
4
5
6
7
8
9
&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;kd&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;differenceWith&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;baseRest&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kd&quot;&gt;function&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;array&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;values&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;kd&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;comparator&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;last&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;values&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;isArrayLikeObject&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;comparator&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;nx&quot;&gt;comparator&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;undefined&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;isArrayLikeObject&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;array&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;?&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;baseDifference&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;array&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;baseFlatten&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;values&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;isArrayLikeObject&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;undefined&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;comparator&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[];&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;});&lt;/span&gt;
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;development 모드에서는 기본적으로 optimization 옵션이 동작하지 않으니 당연한 결과다. production 빌드를 하면? 없겠지.&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-javascript&quot; data-lang=&quot;javascript&quot;&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1
2
3
4
&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;p&quot;&gt;},&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;he&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;debounce&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;ef&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;he&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;defaults&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;Yf&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;he&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;defaultsDeep&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;Qf&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;he&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;defer&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;uf&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;he&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;delay&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;of&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;he&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;difference&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;so&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;he&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;differenceBy&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;ho&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;he&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;differenceWith&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;po&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;he&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;drop&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;function&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;n&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;t&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;r&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;e&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;null&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;n&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;?&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;n&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;length&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;e&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;?&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;Su&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;n&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;t&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;r&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;||&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;t&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;===&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;i&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;?&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;Mf&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;t&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;lt&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;?&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;t&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;e&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[]&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;},&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;he&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;dropRight&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;function&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;n&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;t&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;r&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;그런데 있다. Tree Shaking이 동작하지 않았다. 왜?&lt;/p&gt;
&lt;p&gt; 한참 삽질을 하던 중, Webpack의 공식 문서를 읽어보고서야 범인을 알 수 있었다. 범인은 sideEffects.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://webpack.js.org/configuration/optimization/#optimization-sideeffects&quot;&gt;https://webpack.js.org/configuration/optimization/#optimization-sideeffects&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;span style=&quot;font-size: 10pt;&quot;&gt;(Webpack 최적화 옵션의 sideEffects와는 다릅니다.)&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;Tree Shaking을 수행하면 사용하지 않는 코드가 탈락된다. 이로 인해 사이드 이펙트가 발생할 수 있는데, 코드를 직접 평가하고 실행해보지 않는 이상 Webpack이 사이드 이펙트 발생 여부를 알 수는 없다. 그래서 Webpack이 찾은 대안이 sideEffects다.&lt;/p&gt;
&lt;p&gt;직접 코드를 평가하고 실행하는 대신에, Webpack은 외부 패키지의 package.json에 있는 sideEffects 설정을 보고 사이드 이펙트 존재 여부를 판단하다. 사이드 발생 여부에 대한 판단 책임을 코드 작성자에게 넘긴 셈이다. 만든 사람이 제일 잘 알테니깐.&lt;br /&gt;
 &lt;br /&gt;
이 옵션을 명시하지 않으면 Tree Shaking 시에 사이드 이펙트가 발생할 수 있다고 판단하여 해당 패키지를 Tree Shaking의 대상에서 제외한다. 따라서 package.json에 sideEffects 프로퍼티를 false로 명시해야만 Three Shaking을 적용받을 수 있다.&lt;/p&gt;
&lt;p&gt;위에서 설치한 lodash의 package.json을 확인하자.&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-json&quot; data-lang=&quot;json&quot;&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;_from&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;lodash&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;_id&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;lodash@4.17.11&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;_inBundle&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;kc&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;_integrity&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg==&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;_location&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;/lodash&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;_phantomChildren&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{},&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;_requested&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;type&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;tag&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;registry&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;kc&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;raw&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;lodash&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;name&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;lodash&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;escapedName&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;lodash&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;rawSpec&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;saveSpec&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;kc&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;fetchSpec&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;latest&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;_requiredBy&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;#USER&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;/&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;],&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;_resolved&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;https://registry.npmjs.org/lodash/-/lodash-4.17.11.tgz&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;_shasum&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;b39ea6229ef607ecd89e2c8df12536891cac9b8d&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;_spec&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;lodash&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;_where&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;/Users/Naver/Workspace/coderk/tree-shaking-test&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;author&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;name&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;John-David Dalton&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;email&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;john.david.dalton@gmail.com&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;url&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;http://allyoucanleet.com/&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;bugs&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;url&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;https://github.com/lodash/lodash/issues&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;bundleDependencies&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;kc&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;contributors&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
      &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;name&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;John-David Dalton&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
      &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;email&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;john.david.dalton@gmail.com&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
      &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;url&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;http://allyoucanleet.com/&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
      &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;name&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;Mathias Bynens&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
      &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;email&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;mathias@qiwi.be&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
      &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;url&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;https://mathiasbynens.be/&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;],&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;deprecated&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;kc&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;description&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;Lodash modular utilities.&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;homepage&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;https://lodash.com/&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;icon&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;https://lodash.com/icon.svg&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;keywords&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;modules&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;stdlib&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;util&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;],&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;license&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;MIT&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;main&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;lodash.js&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;name&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;lodash&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;repository&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;type&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;git&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;url&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;git+https://github.com/lodash/lodash.git&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;scripts&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;test&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;echo &lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;See https://travis-ci.org/lodash-archive/lodash-cli for testing details.&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;version&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;4.17.11&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;역시나 sideEffects 옵션이 없다. lodash의 package.json에 sideEffects:false 옵션을 주고 다시 빌드를 하면 잘 될까?&lt;/p&gt;
&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-javascript&quot; data-lang=&quot;javascript&quot;&gt;&lt;span class=&quot;nx&quot;&gt;sideEffects&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;false&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;
&lt;p&gt;왠지 여전히 트리 쉐이킹이 되지 않는다.&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-javascript&quot; data-lang=&quot;javascript&quot;&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1
2
3
4
&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;p&quot;&gt;},&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;he&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;debounce&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;ef&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;he&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;defaults&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;Yf&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;he&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;defaultsDeep&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;Qf&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;he&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;defer&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;uf&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;he&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;delay&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;of&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;he&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;difference&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;so&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;he&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;differenceBy&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;ho&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;he&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;differenceWith&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;po&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;he&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;drop&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;function&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;n&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;t&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;r&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;e&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;null&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;n&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;?&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;n&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;length&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;e&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;?&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;Su&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;n&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;t&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;r&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;||&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;t&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;===&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;i&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;?&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;Mf&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;t&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;lt&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;?&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;t&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;e&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[]&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;},&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;he&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;dropRight&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;function&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;n&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;t&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;r&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;궁금해서 구글링을 하던 중에 Webpack이 ES Module로 의존성을 관리하는 모듈만 Tree Shaking을 한다는 사실이 갑자기 머릿속에 떠올랐다. 혹시?&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/lodash/lodash/blob/4.17.11-npm/padEnd.js&quot;&gt;https://github.com/lodash/lodash/blob/4.17.11-npm/padEnd.js&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;역시나. &lt;a href=&quot;https://github.com/lodash/lodash/blob/4.17.11-npm/padEnd.js&quot; rel=&quot;nofollow&quot;&gt;lodash-npm&lt;/a&gt;는 CommonJS 스펙의 require를 사용한다. 애초에 Tree Shaking 대상이 아닌 것이다. lodash 팀은 lodash-es에는 sideEffects 옵션을 false로 명시했지만, npm 버전의 lodash에는 해당 옵션을 넣지 않았다. 의미가 없으니까.&lt;/p&gt;
&lt;p&gt;&lt;span style=&quot;font-size: 10pt;&quot;&gt;(lodash-es의 sideEffect 옵션은 &lt;a href=&quot;https://github.com/lodash/lodash/pull/3533/commits/e9384031f6063cbcf7772650731c9984e2d29ca9&quot; rel=&quot;nofollow&quot;&gt;이 커밋&lt;/a&gt;에서 추가되었다)&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;		&lt;/p&gt;
</description>
            <pubDate>Thu, 08 Nov 2018 19:04:40 +0900</pubDate>
            <link>https://huns.me/development/2265</link>
            <guid isPermaLink="true">https://huns.me/development/2265</guid>
            
            <category>optimization</category>
            
            <category>sideEffects</category>
            
            <category>tree shaking</category>
            
            <category>webpack</category>
            
            <category>최적화</category>
            
            
            <category>개발 이야기</category>
            
        </item>
        
        <item>
            <title>추상화를 조심스럽게 선택하라</title>
            <description>&lt;p&gt;요즘에 &lt;a href=&quot;http://www.yes24.com/24/goods/48577718?scode=032&amp;amp;OzSrank=2&quot; target=&quot;_blank&quot; rel=&quot;noreferrer noopener&quot;&gt;&quot;도메인 주도 설계 핵심(Domain-Driven Design Distilled)&quot;&lt;/a&gt;라는 책을 읽고 읽다. DDD를 주제로 하는 책은 이것까지 총 4-5권은 읽은 것 같다. 그중에 몇 권은 두 세번은 넘게 읽은 듯한데, 볼 때마다 새롭고 어렵다. 알듯하다가도 온라인에서 고수들이 하는 이야기를 듣고 있노라면 아직 내가 DDD를 잘 모르는구나 싶을 때가 종종 있다.&lt;/p&gt;
&lt;p&gt;이 책은 어려운 주제를 딱딱하게 설명하는 전형적인 재미없는 책이다. 한 가지 다른 책들과 달리, 프로세스 관점에서 이야기를 풀어나가는 점은 흥미로웠다. 책으로는 이해하기 어려웠던 실무 관점의 이야기를 이 책에서 약간이나마 간접 경험을 할 수 있었다.&lt;/p&gt;
&lt;p&gt;DDD는 단순한 패턴의 모음이나 설계 기법이 아닌, 개발 전 과정을 관통하는 프로세스를 담고 있다. DDD의 개념을 얼추 이해해도 실무에 적용하기는 쉽지 않았던 것이 그 때문이었을까. &lt;/p&gt;
&lt;p&gt;동료와 설계 논의를 하다 보면 추상화의 수준을 놓고 설왕설래할 때가 많다. 결국 코딩이라는 게 추상화로 시작해서 추상화로 끝나는 것 같다는 생각을 요즘 가끔 한다. 그만큼 추상화는 개발자에게 중요한 문제다. 추상화 수준이 너무 높으면 오버엔지니어링이 되어 비효율을 만들고, 추상화 수준이 너무 낮으면 유지보수가 힘들어진다. 적정 수준을 찾으라는데, 대체 그 적정 수준이란 어느 지점을 말하는 건지. 늘 곤란하다.&lt;/p&gt;
&lt;p&gt;DDD는 적정 수준의 기준을 &lt;a href=&quot;https://martinfowler.com/bliki/UbiquitousLanguage.html&quot;&gt;보편 언어(Ubiquitous Language)&lt;/a&gt;에서 찾는다. 물론 만능 키는 아니겠지. 좋은 설계를 고민하는 개발자라면 한 번쯤 읽어볼 만한 내용인 듯해서 한 섹션을 발췌했다. &lt;/p&gt;
&lt;p&gt; &lt;/p&gt;
&lt;p&gt;--&lt;/p&gt;
&lt;p&gt;&lt;span style=&quot;font-size: 14pt;&quot;&gt;&lt;strong&gt;추상화를 조심스럽게 선택하라&lt;/strong&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;span style=&quot;font-size: 12pt;&quot;&gt; &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;span style=&quot;font-size: 12pt;&quot;&gt;효과적인 소프트웨어 모델은 항상 일을 하는 비즈니스의 방식을 고려한 &lt;/span&gt;&lt;span style=&quot;font-size: 12pt;&quot;&gt;일련의 추상화에 기반을 두고 있다. &lt;/span&gt;&lt;span style=&quot;font-size: 12pt;&quot;&gt;이때 모델링하는 각 개념마다 적절한 수준의 추상화를 선택해야 한다. &lt;/span&gt;&lt;span style=&quot;font-size: 12pt;&quot;&gt;만일, 보편 언어와 관련된 가이드를 따른다면 적절한 추상화를 설정할 수 있다. &lt;/span&gt;&lt;span style=&quot;font-size: 12pt;&quot;&gt;적어도 모델링 언어의 기반에 지식을 전달해주는 도메인 전문가가 있기 때문에 &lt;/span&gt;&lt;span style=&quot;font-size: 12pt;&quot;&gt;훨씬 정확하게 추상화를 모델링할 수 있다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;span style=&quot;font-size: 12pt;&quot;&gt; &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;span style=&quot;font-size: 12pt;&quot;&gt;하지만 가끔은 잘못된 문제를 푸는 것에 지나치게 몰두한 나머지, &lt;/span&gt;&lt;span style=&quot;font-size: 12pt;&quot;&gt;소프트웨어 개발자가 지나칠 정도로 추상화를 적용하기도 한다. &lt;/span&gt;&lt;span style=&quot;font-size: 12pt;&quot;&gt;예를 들면, 애자일 프로젝트 관리 컨텍스트에서의 스크럼 관련 사항을 돌이켜보자. &lt;/span&gt;&lt;span style=&quot;font-size: 12pt;&quot;&gt;전에 논의했던 Product, BacklogItem, Release 그리고 Sprint 개념을 모델링하는 것은 타당하다. &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;span style=&quot;font-size: 12pt;&quot;&gt; &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;span style=&quot;font-size: 12pt;&quot;&gt;그렇지만 소프트웨어 개발자가 스크럼의 보편언어를 모델링하는 것에 &lt;/span&gt;&lt;span style=&quot;font-size: 12pt;&quot;&gt;그다지 관심을 기울이지 않은 채, &lt;/span&gt;&lt;span style=&quot;font-size: 12pt;&quot;&gt;현재와 미래의 모든 스크럼 관련 개념을 모델링하는 것에 &lt;/span&gt;&lt;span style=&quot;font-size: 12pt;&quot;&gt;더 많은 관심을 갖고 있다면 어떻게 될까?&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;span style=&quot;font-size: 12pt;&quot;&gt; &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;span style=&quot;font-size: 12pt;&quot;&gt;이런 관점이 계속되면, 개발자들은 ScrumElement, ScrumElementContainer와 같은 개념을 생각해낼 것이다.  &lt;/span&gt;&lt;span style=&quot;font-size: 12pt;&quot;&gt;ScrumElement는 Product와 BacklogItem에 대한 현재의 요구를 만족시키고, &lt;/span&gt;&lt;span style=&quot;font-size: 12pt;&quot;&gt;ScrumElementContainer는 Release와 Sprint의 명확한 개념을 분명히 표현해줄 것이기 때문이다. &lt;/span&gt;&lt;span style=&quot;font-size: 12pt;&quot;&gt;ScrumElement는 typeName 프로퍼티를 가질 것이고, &lt;/span&gt;&lt;span style=&quot;font-size: 12pt;&quot;&gt;아마도 그 값은 상황에 맞게 &quot;Product&quot;나 &quot;BacklogItem&quot;으로 설정될 것이다. &lt;/span&gt;&lt;span style=&quot;font-size: 12pt;&quot;&gt;ScrumElementContainer에 대해서도 같은 종류의 typeName 프로퍼티를 설계하고, &lt;/span&gt;&lt;span style=&quot;font-size: 12pt;&quot;&gt;이를 &quot;Release&quot;나 &quot;Sprint&quot;로 설정할 것이다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;span style=&quot;font-size: 12pt;&quot;&gt; &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;span style=&quot;font-size: 12pt;&quot;&gt;이런 방식에서 나타나는 여러 문제점들이 보이는가? &lt;/span&gt;&lt;span style=&quot;font-size: 12pt;&quot;&gt;여기에는 적지 않은, 그러나 반드시 고려해야 하는 것들이 있다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;span style=&quot;font-size: 12pt;&quot;&gt; &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;span style=&quot;font-size: 12pt;&quot;&gt;1. &lt;/span&gt;&lt;span style=&quot;font-size: 12pt;&quot;&gt;소프트웨어 모델의 언어가 도메인 전문가의 멘탈 모델과 일치하지 않는다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;span style=&quot;font-size: 12pt;&quot;&gt; &lt;/span&gt;&lt;span style=&quot;font-size: 12pt;&quot;&gt;2. &lt;/span&gt;&lt;span style=&quot;font-size: 12pt;&quot;&gt;추상화 수준이 너무 높아서 각 개별적인 형태의 세부 사항을 모델링하기 시작하면 &lt;/span&gt;&lt;span style=&quot;font-size: 12pt;&quot;&gt;어려운 상황에 빠질 것이다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;span style=&quot;font-size: 12pt;&quot;&gt; &lt;/span&gt;&lt;span style=&quot;font-size: 12pt;&quot;&gt;3. &lt;/span&gt;&lt;span style=&quot;font-size: 12pt;&quot;&gt;이것은 각각의 클래스마다 특수한 경우를 정의할 것이고, &lt;/span&gt;&lt;span style=&quot;font-size: 12pt;&quot;&gt;명백한 문제들에 대한 일반적인 접근을 통해 복잡한 클래스 계층 구조를 만들 것이다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;span style=&quot;font-size: 12pt;&quot;&gt; &lt;/span&gt;&lt;span style=&quot;font-size: 12pt;&quot;&gt;4. &lt;/span&gt;&lt;span style=&quot;font-size: 12pt;&quot;&gt;우선적으로 중요하지 않은 문제를 해결하려다가 필요한 것보다 훨씬 많은 코드를 생산할 것이다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;span style=&quot;font-size: 12pt;&quot;&gt; &lt;/span&gt;&lt;span style=&quot;font-size: 12pt;&quot;&gt;5. &lt;/span&gt;&lt;span style=&quot;font-size: 12pt;&quot;&gt;잘못된 추상화 수준은 심지어 사용자 인터페이스까지 영향을 미쳐 &lt;/span&gt;&lt;span style=&quot;font-size: 12pt;&quot;&gt;사용자에게 혼란을 주는 경우도 종종 발생한다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;span style=&quot;font-size: 12pt;&quot;&gt; &lt;/span&gt;&lt;span style=&quot;font-size: 12pt;&quot;&gt;6. &lt;/span&gt;&lt;span style=&quot;font-size: 12pt;&quot;&gt;이로 인해 상당한 시간과 비용을 낭비할 수 있다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;span style=&quot;font-size: 12pt;&quot;&gt; &lt;/span&gt;&lt;span style=&quot;font-size: 12pt;&quot;&gt;7.  &lt;/span&gt;&lt;span style=&quot;font-size: 12pt;&quot;&gt;프로젝트 초반에 미래의 모든 요구를 생각하고 반영할 수는 없다. &lt;/span&gt;&lt;span style=&quot;font-size: 12pt;&quot;&gt;새로운 스크럼 개념들은 앞으로도 계속 추가될 것이고, &lt;/span&gt;&lt;span style=&quot;font-size: 12pt;&quot;&gt;기존 모델은 그 요구사항을 예견하는 데 실패할 수밖에 없기 때문이다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;span style=&quot;font-size: 12pt;&quot;&gt; &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;span style=&quot;font-size: 12pt;&quot;&gt;이런 잘못된 방식을 따르는 상황이 나타날지 의문을 갖는 사람들도 있겠지만, &lt;/span&gt;&lt;span style=&quot;font-size: 12pt;&quot;&gt;이런 &lt;span style=&quot;color: #ff00ff;&quot;&gt;부적절한 추상화 수준은 기술적인 측면으로 구현을 생각하는 상황에서 자주 등장&lt;/span&gt;한다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;span style=&quot;font-size: 12pt;&quot;&gt; &lt;/span&gt;&lt;span style=&quot;font-size: 12pt;&quot;&gt;...(이하 생략)&lt;/span&gt;&lt;/p&gt;
</description>
            <pubDate>Fri, 31 Aug 2018 10:12:42 +0900</pubDate>
            <link>https://huns.me/development/2252</link>
            <guid isPermaLink="true">https://huns.me/development/2252</guid>
            
            <category>book</category>
            
            <category>DDD</category>
            
            <category>Review</category>
            
            <category>책</category>
            
            
            <category>개발 이야기</category>
            
        </item>
        
        <item>
            <title>3년간의 TDD 인생 회고</title>
            <description>&lt;div&gt;
&lt;h2&gt;1막&lt;/h2&gt;
&lt;p&gt;&lt;img class=&quot; wp-image-2209 alignleft&quot; src=&quot;/assets/images/legacy/j39x2xx_8cq-jeremy-yap-1024x683.jpg&quot; alt=&quot;어두운 방에 있는 영사기에서 빛이 새어나오는 사진&quot; /&gt;&lt;/p&gt;
&lt;p&gt;나는 글을 쓸 때, 우선 대충 글의 전체 구조를 완성한 후에 아주 여러 번 마음에 들 때까지 고쳐서 최종본을 완성하는 방식을 선호한다. &lt;span class=&quot;se_ff_nanumgothic&quot;&gt;주제에 매력을 더 많이 느낄수록 공을 더 많이 들인다.&lt;span class=&quot;Apple-converted-space se_ff_nanumgothic&quot;&gt; &lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;se_ff_nanumgothic&quot;&gt;언젠가 한 번은 글을 발행하고 난 후에, 수정 발행을 무려 80번 넘게 한 적도 있었다.&lt;/span&gt;&lt;span class=&quot;se_ff_nanumgothic&quot;&gt; &lt;/span&gt;코드를 작성할 때도 비슷하다. 대충 구현해서 의도한 결과를 빨리 만들고, 마음에 들 때까지 수정하기를 반복한다. 언제부터 이런 습관이 생겼는지는 잘 모르겠다. 신입 시절부터 그랬던 것 같은데, 이 과정을 리팩터링이라고 부른다는 사실을 이해한 건 2년 차쯤이다.&lt;/p&gt;
&lt;p&gt;리팩터링을 한 후에는 당연히 제품에 문제가 없는지 테스트를 통해 확인한다. 프로젝트 초반에는 테스트하기가 쉽다. 구현한 게 적으니까. 코드량이 많아지면서 운동장 사정이 변한다. 작은 수정에도 얽히는 부분이 많다. 수정하고 나서 확인해야 할 지점이 너무 많다 보니 수동으로 테스트하는 과정이 매우 귀찮아진다. 귀찮아서 꼼꼼하게 테스트하지 않고 일부 과정을 생략했다가 미처 확인하지 못한 오류 때문에 곤혹스러운 경험을 여러 번 했다. 자연스레 테스트 자동화에 관심이 생겼다. 하지만 사후에 테스트를 작성한다는 건 밀린 방학 숙제처럼 지겹고 재미가 없었다. 테스트 작성이 구현과 별개의 과정으로 느껴졌다. 테스트 작성에 회의가 생길 때쯤 TDD를 만났다.&lt;/p&gt;
&lt;p&gt;테스트를 먼저 작성한다는 게 이상했다. 이상해서 신기했다. 신기해서 멋있었다. 테스트 작성이 구현 과정과 함께 묶여있다. 테스트 작성을 미루지 않아도 되잖아? 뭔지는 잘 모르겠지만 테스트를 먼저 작성하면 당시 내가 겪는 문제를 해결할 수 있을 것 같았다.&lt;span class=&quot;Apple-converted-space&quot;&gt; &lt;/span&gt;그렇게 TDD에 입문했지만 책만 가지고 혼자 공부하기가 버거웠다. 배경 지식이 너무 없어서, 도통 무슨 이야기인지 잘 모르겠더라. 사실 테스트가 뭔지도 잘 몰랐다. 그저 관심과 호기심만 가득했다. 그렇게 뭣도 없는 TDD 인생의 1막이 끝났다.&lt;/p&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;h2&gt;2막 1장&lt;/h2&gt;
&lt;p&gt;2013년 즈음이었나, 선배 개발자 형한테 짝 코딩으로 TDD를 배울 기회가 있었다. 제대로 배워보려고 당시에 학습한 내용을 블로그에 글로 쓰면서 TDD 인생 2막을 열었다. 열심히 쓰다가 귀차니즘이 돋아서 시리즈를 마지막까지 완성하지 못한 게 아쉽다. 어쨌든 이날의 경험을 짧은 내 개발자 인생에서 가장 짜릿했던 일로 꼽는다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;/posts/2013-11-25-chalenge-javascript-tdd-1&quot;&gt;도전 JavaScript TDD - 1.시작&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;/posts/2013-11-25-chalenge-javascript-tdd-2&quot;&gt;도전 JavaScript TDD - 2. TDD 리듬&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;/posts/2013-11-25-chalenge-javascript-tdd-3&quot;&gt;도전 JavaScript TDD - 3. 점진적 명세 작성&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;2막을 열고 올해로 3년째지만, 정작 실전에서 프로젝트 막판까지 TDD를 놓지 않았던 건 불과 한 달 전이다. 프로젝트 초반에는 항상 테스트를 열심히 작성하지만 코드가 조금만 복잡해지면, 관리를 못 해서 허둥대다가 모든 게 망가졌다. 테스트는 복잡했고, 어려웠다. 일정에 쫓겨서 마음이 급해지면 가장 먼저 테스트를 생략했다. 약간만 코드를 수정해도 빨간색으로 징징거리는 테스트 코드가 되려 짐처럼 느껴졌다. 관리하지 못한 테스트는 결국 방치되다가 버려졌다.&lt;/p&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;p&gt;TDD가 좋은 설계를 유도한다는 글을 많이 봤지만 내 설계는 조금도 나아지지 않았다. 답답했다. 그때는 몰랐다. TDD가 좋은 설계를 보장하지 않는다는 사실을. 망가진 테스트가 높은 커버리지를 보여주는 것도 웃겼다. 정작 별거 아닌 버그도 못 잡아내는 녀석인데. 그렇게 알쏭달쏭한 2막을 보냈다.&lt;/p&gt;
&lt;p&gt;그럼에도 끊임없이 TDD를 시도했다. 지금 생각해보면 TDD를 배우고자 하는 동기가 강했다. 코드 품질에 관심이 많았던 터라, 코드를 변경할 때마다 안전을 보장해 줄 장치가 필요했다. &lt;b&gt;TDD를 절대로 신뢰해서가 아니라, 다른 대안을 찾지 못 해서 매달렸다&lt;/b&gt;.&lt;/p&gt;
&lt;p&gt;재미있게도 매번 실패하면서도 배우는 게 있었는데, 이 과정에서 조금씩 나아지는 느낌을 받았다. 그게 날 자극했다. 단위 테스트 작성에 대한 경험치가 쌓였다. 명세를 어떻게 작성할지 고민하면서 코딩하기 전에 문제를 먼저 정의하는 습관이 생겼다. 디버깅을 하는 요령이 생기면서 버그를 찾는 시간이 짧아졌다. 공용 인터페이스를 중심으로 객체를 설계하는 방식에 익숙해졌다. 테스트 코드와 구현 코드 사이의 의존성을 고민하면서, 부분의 문제를 해결한 후 전체 구조를 생각하는 과정이 자연스러워졌다. 무엇보다 리팩터링에 자신감이 붙었다.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;h2&gt;2막 2장&lt;/h2&gt;
&lt;p&gt;그래서 지금은 TDD를 잘하느냐고 묻는다면 그렇지는 않다. 그동안 TDD에 투자한 비용을 이제야 조금씩 회수하고 있다는 느낌을 받는 정도랄까. 아직도 의식하고 행하지 않으면 구현 코드로 손이 먼저 간다. 아직도 명세를 작성하는 스타일이 자리를 잡지 못했다. 여전히 마음이 급해지면 테스트를 오지 않을 ‘나중’으로 미룬다. 그와중에 빈도가 줄었다는 건 좋은 일.&lt;/p&gt;
&lt;p&gt;단위 테스트 자동화로 리팩터링에 자신감이 붙었지만, 역으로 테스트가 리팩터링에 장애물처럼 느껴질 때가 가끔 있다. 문제의 근본이 테스트 코드와 구현 코드 간의 의존성, 그리고 리팩터링 진행 방식에 있다는 걸 마틴 파울러의 &lt;a href=&quot;http://book.naver.com/bookdb/book_detail.nhn?bid=7047630&quot;&gt;“리팩터링”&lt;/a&gt;을 읽고 나서야 깨달았다. 나는 마구잡이로 리팩터링하는 좋지 않은 습관을 가지고 있었고 테스트는 내가 가지고 있는 문제를 알려줬을 뿐이다.&lt;/p&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;p&gt;여전히 TDD를 ‘수련'하고 있다. ‘수련’이라는 표현에는 &lt;b&gt;'끊임없는 연습을 통해서 지속적인 향상을 추구’&lt;/b&gt;한다는 의미가 담겨있다. 쉽게 얻을 수 없다는 뜻이 묻어있다. 쉽게 얻을 수 있다면 끊임없이 연습을 할 이유가 없으니까.&lt;/p&gt;
&lt;p&gt;TDD를 향해 나아가는 길은 꽤나 더디고 지루하지만, 이 과정에서 얻는 열매는 분명 달콤하다. 이런 사실을 잘 모르고 덤빈다면 TDD의 매력을 맛보기도 전에 나락으로 떨어지기 십상이다. 내가 계속 실패하면서도 아직까지 TDD를 붙잡고 있을 수 있었던 이유는 동기가 분명해서다. 지속적으로 리팩터링하고 싶은 간절함, 뭐 그런 거. 그렇지 않았다면 TDD 적응에 처음 몇 번 실패했을 때, TDD를 욕하며 돌아서지 않았을까?&lt;/p&gt;
&lt;p&gt;TDD를 통해서 좋은 경험을 하고 있음에도, 쉽사리 주변에 권장은 하지 않는 편이다. 단지 TDD에 대해서 물어오면 대답해주는 정도가 내가 반응하는 경계선이다. 동기라는 건 강제할 수 있는 게 아니거니와, 내가 완성하지 못한 지식을 누군가에게 강요한다는 게 조금 께름칙하기도 하고.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;h2&gt;3막으로&lt;/h2&gt;
&lt;p&gt;지난주에는 TDD에 관심있는 동료들과 짝 코딩을 했다. 참여 인원은 5명. 짝 코딩이라기보다는 떼 코딩이 적절하겠다. TDD에 대한 생각을 손과 입으로 누군가에게 전달하려다 보니 좀 더 깊은 영역을 고민하게 되더라. 옆에서 길을 안내하는 입장으로서, 가능한 동료의 생각을 간섭하지 않으려 했는데 아주 많이 인내해야 했다. 나에게 TDD를 알려 준 선배 개발자 형의 당시 심정을 짐작할 수 있어서 작은 웃음이 나왔다. 나의 인내력은 그 형에 한참 못 미쳤다.&lt;/p&gt;
&lt;p&gt;집에서는 작성한지 3년쯤 지난 &lt;a href=&quot;https://github.com/CoderK/Huntris&quot;&gt;레거시 코드&lt;/a&gt;에 테스트를 보완해서 리팩터링하고, 추가 기능을 TDD로 확장해나가는 연습을 하고 있다. 일종의 테스트 연습장인 셈이다.&lt;/p&gt;
&lt;p&gt;한 발 나간다. 뒤를 돌아본다. 문제가 있다. 고친다. 다시 한 발 나간다. 반복. 마치 누군가의 인생 여정처럼. 그렇게 나의 TDD 인생도 2막에서 3막으로 무르익어 간다.&lt;/p&gt;
&lt;/div&gt;
&lt;p&gt;&amp;nbsp;		&lt;/p&gt;
</description>
            <pubDate>Sun, 05 Feb 2017 12:16:31 +0900</pubDate>
            <link>https://huns.me/development/2206</link>
            <guid isPermaLink="true">https://huns.me/development/2206</guid>
            
            <category>TDD</category>
            
            <category>개발</category>
            
            <category>회고</category>
            
            
            <category>개발 이야기</category>
            
        </item>
        
        <item>
            <title>유지보수는 쉬운 일이 아닙니다</title>
            <description>&lt;div&gt;
&lt;p&gt;&lt;a href=&quot;/assets/images/legacy/2017/01/business-1839876_1920.jpg&quot;&gt;&lt;img class=&quot;alignleft wp-image-2186&quot; src=&quot;/assets/images/legacy/business-1839876_1920-1024x683.jpg&quot; alt=&quot;맥북 위에 핸드폰이 올려져있고, 하얀 노트가 좌측에 놓여있는 사진&quot; width=&quot;347&quot; height=&quot;231&quot; /&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;가끔 소프트웨어 유지보수는 신규 개발보다 쉬워서, 어렵지 않게 할 수 있는, 지루한 일이라는 이야기를 들을 때가 있다. 유지하고 보수하는 일이 개인의 성향과 맞지 않아 지루할 수 있다고는 생각하지만, 쉬운 일이라는 데에는 몇 가지 이유로 동의하지 않는다.&lt;/p&gt;
&lt;p&gt;레거시 코드를 유지보수할 때는 아주 작은 부분마저도 쉽게 고치기 어렵다. 코드의 정글을 지나, 어렵게 문제의 심장부로 보이는 곳까지 찾아갔지만 눈앞에는 마치 영화 속 시한폭탄처럼 알 수 없는 빨간 줄, 노란 줄, 파란 줄이 놓여있다. 고민하다가 파란 줄을 잘랐다.  여기저기서 오류가 터진다. 사이드 이펙트가 찾아왔다. 자동화된 테스트라도 있으면 좋으련만, 그마저도 없다면 이때부터는 '웰컴 투 더 헬’이다. 그래서 레거시에 뭔가를 추가하거나 수정하는 작업은 새로운 코드를 작성할 때 보다 더 예민하고 넓은 시야로 코드를 봐야 한다. 한 마디로 어렵다는 소리.&lt;/p&gt;
&lt;p&gt;유지보수할 때는 창의력을 발휘하기도 어렵다. 기존의 체계가 생각을 제약한다. 제약을 더 많이 받기 때문에 더 많은 창의력이 필요하지만, &lt;b&gt;생각을 강하게 제약하는 체계 안에서 새로운 해법을 찾는다는 건 결코 쉬운 일이 아니&lt;/b&gt;&lt;b&gt;다&lt;/b&gt;. 이런 환경을 즐기는 성향이 아니라면 미치고 팔짝 뛸 만한 상황이다. 그만큼 수고롭다. 간단하게 할 수 있는 일을 멀리 돌아가는 느낌 또한 유쾌하지 않다.&lt;span class=&quot;Apple-converted-space&quot;&gt; &lt;/span&gt;기존 체계가 깔끔하고 유연하다면 참 좋으련만, 그런 레거시 코드는 보이지 않는다. 그래서 우리는 보통 레거시라는 단어에, 오랜 세월 쌓이고 쌓여 딱딱하게 굳어져 이제는 어쩔 수 없는 녀석이란 의미를 담는다.&lt;/p&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;p class=&quot;p1&quot;&gt;로버트 L. 글래스는 자신이 저서인 '&lt;a href=&quot;http://www.yes24.com/24/goods/3406730?scode=032&amp;amp;OzSrank=2&quot;&gt;소프트웨어 크리에이티비티&lt;/a&gt;'에서, &lt;b&gt;“소프트웨어 제작이라는 복잡한 세상에서 (위반해서는 안 되는 체계적인 제약을 받으며 창의력을 발휘하는) 유지보수보다 더 복잡한 활동은 없으리라 생각한다&quot;&lt;/b&gt;라고 했다. 유지보수는 이처럼 어렵고, 많은 비용을 필요로하는 활동이다.&lt;span class=&quot;Apple-converted-space&quot;&gt; &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;그래서 유지보수하기 좋은 코드를 작성하는 일은 사업적 측면과 무관하지 않다.&lt;b&gt; &lt;/b&gt;요즘처럼 변경이 상수인 시대에 잦은 변경을 받아들일 수 없어서, 소프트웨어를 재개발해야 한다면 회사는 큰 비용을 치러야만 한다.&lt;span class=&quot;Apple-converted-space&quot;&gt; &lt;/span&gt;클린 코드나 테스트 자동화가 이야기하는 가치는 결코 사업 그 자체와 동떨어져 있지 않다.&lt;span class=&quot;Apple-converted-space&quot;&gt; &lt;/span&gt;그런 의미에서&lt;b&gt; 더 나은 코드를 찾아 끊임없이 고민&lt;/b&gt;하는 일은 내일의 나, 너, 팀, 그리고 회사까지, 관련 있는&lt;span class=&quot;Apple-converted-space&quot;&gt; &lt;/span&gt;&lt;b&gt;모두를 위한 배려이자 직업 개발자의 의무&lt;/b&gt;가 아닐까.&lt;span class=&quot;Apple-converted-space&quot;&gt; &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;span class=&quot;Apple-converted-space&quot;&gt;글을 쓰다보니 유지보수를 가볍게 생각하는 사람은 클린 코드나 테스트 자동화가 말하는 가치에 공감하기는 어렵겠다는 생각이 스친다.&lt;br /&gt;
&lt;/span&gt;&lt;span class=&quot;Apple-converted-space&quot;&gt;&lt;br /&gt;
&lt;/span&gt;&lt;span class=&quot;Apple-converted-space&quot;&gt;아, 그렇구나. 그런 거였어.&lt;/span&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;/div&gt;
</description>
            <pubDate>Fri, 27 Jan 2017 00:53:16 +0900</pubDate>
            <link>https://huns.me/development/2183</link>
            <guid isPermaLink="true">https://huns.me/development/2183</guid>
            
            <category>개발자</category>
            
            <category>레거시</category>
            
            <category>로버트 L. 글래스</category>
            
            <category>소프트웨어</category>
            
            <category>유지보수</category>
            
            
            <category>개발 이야기</category>
            
        </item>
        
        <item>
            <title>정신과 시간의 방, 그리고 실용주의 프로그래머</title>
            <description>&lt;p&gt;
				요즘은 회사에서 매일 4~5시간씩 동료들과 설계 회의를 하고 있다. 내년에 있을 큰미션을 준비하기 위해서다. 하루에 두 시간 정도만 회의를 해도 녹초가 되기 일쑤인데 하루 반나절, 그 이상을 줄기차게 떠든다는 게 여간 힘든 일이 아니다.&lt;/p&gt;
&lt;p&gt;&lt;img class=&quot;alignright wp-image-2160&quot; src=&quot;/assets/images/legacy/v3bwnxeinqa-liane-metzler-1024x683.jpg&quot; alt=&quot;하얀 건물에 하얀 계단이 어지럽게 꼬여있는 사진&quot; /&gt;긴 회의를 마치고 &lt;b&gt;'정신과 시간의 방'&lt;/b&gt;에서 나올 즈음이면 심신이 시루떡이다. 시간도 시간이지만, 논쟁을 해야 하는 문제의 성격이 우리를 더 지치게 만든다. 대부분이 과학적 증명보다는 &lt;b&gt;가치 판단&lt;/b&gt;의 문제에 가깝다. 답이 없다. 철학과 개발이 맞닿은 지점을 어렴풋이 본다. 이런 거구나.&lt;/p&gt;
&lt;p&gt;뭐든 내 맘 같지 않음이 당연하다는 걸 알면서도 누군가와 생각을 놓고 줄다리기하는 과정이 대단히 피곤한 건 어쩔 수 없다. 내 철학을 포기하는 건 쉬운 일이 아니지만 분명한 근거를 바탕으로 상대를 설득하지 못했다면 내가 아는 것들을 의심해보는 게 옳다. 차라리 한 수 접고 더 공부할 수 있는 원동력으로 삼는 게 현명한 길이고, 포기함으로써 성장하는 느낌은 즐겁다. 소주 한 잔을 마시고 떠먹는 아이스크림처럼 달콤 쌉싸래하다.&lt;/p&gt;
&lt;p&gt;&lt;b&gt;실재 없는 이론의 나열은 허상&lt;/b&gt;이라는 걸 몸으로 느낀다. 패턴이나 설계 원칙을 설명하라면 유창하지는 않아도 허접하지 않게 설명할 수 있다. 하지만 안다고 제품에 녹일 수 있는 것도 아니거니와, 실재하는 사례가 곁들여지지 않은 주장은 무뎌서 날이 서지 않는다. 문제는 정작 중요한 순간에는 경험한 사례가 잘 떠오르질 않는다는 점이다. 코드 베이스가 커져서 복잡해지고 나서야 문제가 발생하기에 간단한 코드로 재현하기도 어렵다. 평소에 이슈를 만났을 때 상황과 원인을 잘 메모해서 모아두지 못한 게 아쉽다.&lt;/p&gt;
&lt;p&gt;&lt;img class=&quot;alignleft wp-image-2161&quot; src=&quot;/assets/images/legacy/s_vbdmtsdia-mike-erskine-1024x683.jpg&quot; alt=&quot;어두운 밤, 산에 캠핑온 사람들이 모닥불을 피워놓고 둘러 앉아 있는 모습&quot; /&gt;개발자는 역시 &lt;b&gt;코드로 말하는 게 제일&lt;/b&gt;이다. 긴 논의를 거쳐 서로의 생각이 일치했다고 동의하고 고개 끄덕이는 순간조차도 머릿속에 다른 그림을 그리는 경우가 흔하다. 노트북을 꺼내서 짝코딩으로 얼른 간단한 코드 조각을 만들고 리뷰하니 커뮤니케이션이 훨씬 명확해졌다. 입은 짧게 털고, 코딩은 조금 길게. 그게 실용적이다.&lt;/p&gt;
&lt;p&gt;내가 하는 일이 &lt;b&gt;고도의 사회 활동&lt;/b&gt;(맞는 표현인지는 잘 모르겠지만 뜻은 통하겠지)이라는 사실을 깨달아 가고 있다. 2년 전까지만 해도 나에게 개발은 그저 나 자신과의 싸움이었다. 많은 동료, 여러 부서와 협업을 해야 하는 조직에 합류하고 난 뒤 경험치가 쌓이면서 내 일을 바라보는 나 자신의 관점이 많이 변했다는 걸 느낀다. 그저 ‘좋다’는 느낌적 느낌이나, 바다 건너 이름 모를 누군가의 깨알 같은 사례를 공유하는 걸로는 아무것도 할 수 없다. 모두가 같이 이해하고, 같이 느낄 수 있어야 한다. 요즘 &lt;b&gt;‘콘텍스트’&lt;/b&gt;라는 단어를 머리에 달고 산다.&lt;/p&gt;
&lt;p&gt;전체의 일부가 된다는 건 여러모로 쉬운 일이 아니지만 내년에는 이런 일도 잘하는 나이고 싶다. 애자일한, &lt;b&gt;실용주의 프로그래머&lt;/b&gt;, 뭐 그런 엔지니어 말이다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;		&lt;/p&gt;
</description>
            <pubDate>Thu, 29 Dec 2016 22:28:52 +0900</pubDate>
            <link>https://huns.me/development/2159</link>
            <guid isPermaLink="true">https://huns.me/development/2159</guid>
            
            
            <category>개발 이야기</category>
            
        </item>
        
        <item>
            <title>MS는 ReactiveX를 왜 만들었을까? (feat. RxJS)</title>
            <description>&lt;p&gt;
				&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;img class=&quot;wp-image-2123&quot; src=&quot;/assets/images/legacy/ngrx.png&quot; alt=&quot;컨퍼런스에서 ngrx를 주제로 발표하는 남자의 사진&quot; /&gt;&lt;/p&gt;
&lt;p&gt;요즘 웹 프론트엔드 분야에서 재밌는 주제를 하나 꼽자면, RxJS가 떠오른다. 그 동안 웹 프론트엔드 쪽에서는 큰 관심을 못 받던 기술이었는데 최근에 RxJS와 관련한 이야기가 솔솔 나오는 이유는 Angular2가 RxJS를 기본으로 탑재한 덕인 것 같다.&lt;/p&gt;
&lt;p&gt;내가 RxJS를 처음 알게 된 건 React 커뮤니티를 통해서였다. 이미 Angular2 등장 이전부터 React 커뮤니티에서는 React와 Rx를 결합하려는 시도가 있었다. 그래서 Rx를 가볍게 훑어본 적이 있었는데 언뜻 보기에 옵저버 패턴으로 이벤트를 바인딩하는 API를 제공하는 라이브러리 정도로 보였다. 별로 새로울 게 없다고 그냥 넘겼더랬다. 사실 그땐 사는 게 너무 바빠서 뭘 깊게 들여다 볼 여유가 없었다. 그렇게 Rx는 내 머리에서 잊혔다. 시간이 흐르고, 내 삶에 여유가 조금 찾아왔다. 마침 Angular2도 찾아오셨다.&lt;/p&gt;
&lt;p&gt;응?&lt;/p&gt;
&lt;p&gt;그런데 Angular2가 Rx를 달고 나왔네? Angular2는 Rx를 뭐에 쓰는 거지? 궁금했다. 소문에 의하면 Redux한테 반기를 들고 나온 Cycle.js는 Rx 비슷한(xstream) 무언가로 모든 건 스트림이네, 어쩌네 하는 소리를 떠들고 다닌다던데, 이건 또 뭐람? 궁금했지만 애써 들여다보고 싶지는 않았다. 귀찮았으니까. 그런데 때마침 지인들이 Rx를 스터디를 한다길래 살포시 숟가락 하나 얹었다.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;http://reactivex.io/intro.html&quot; rev=&quot;en_rl_small&quot;&gt;http://reactivex.io/intro.html&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;공식 사이트부터 들어가 봤다. 예제 코드 좀 훑어보고, 소개 글을 읽었다. 여기에 나와있는 설명 그대로, Rx는 &lt;strong&gt;비동기 데이터 스트림을 처리&lt;/strong&gt;하는 API를 제공하는 라이브러리다.&lt;/p&gt;
&lt;p&gt;그렇군. 어려운 단어는 없는데 뭔지는 잘 모르겠다. 그런데 리액티브 프로그래밍(Reactive Programming, RP), 함수형 리액티브 프로그래밍(Functional Reactive Programming, FRP), LINQ(Language-Intergrated Query), 얘네들은 다 뭐람? 들어본 적은 있으나, 설명을 못하겠는 걸 보니 모르는 게 분명하다. 너무 궁금하다. 내 성격에 이걸 이해 못하면 코드 한 줄도 못 짤 분위기다.&lt;/p&gt;
&lt;p&gt;Rx를 만든 배경을 조사해보면, 개념이 좀 명확해지지 않을까? 그래서 Rx의 역사를 찾아 뒤를 캐봤다.&lt;/p&gt;
&lt;h2&gt;&lt;b&gt;Rx(ReactiveX)의 어제&lt;/b&gt;&lt;/h2&gt;
&lt;p&gt;하지만 Rx의 역사를 찾는 일은 쉽지 않았는데, 매끄럽게 정리되어 있는 글을 도통 찾을 수가 없었다. 할 수 없이 인터넷에 떠도는 솜털 구름 같은 정보들을 찾아서 하나씩 가내수공업으로 이어 붙였다. 모호한 이야기가 많아서 이마저도 쉽지 않았다. 어떤 내용은 기사에 달려있는 댓글을 하나씩 읽어서 상황을 유추해야만 했다. 유물 찾으러 다니는 고고학자의 기분이 이런 걸까.&lt;/p&gt;
&lt;p&gt;그렇게 디지털 공간을 헤메고 다니다가 Volta라는 녀석에게서 흥미로운 냄새를 맡았다.&lt;/p&gt;
&lt;h4&gt;&lt;b&gt;2007년 12월, Live Labs Volta&lt;/b&gt;&lt;/h4&gt;
&lt;p&gt;때는 2007년.&lt;/p&gt;
&lt;p&gt;MS의 인터넷 제품과 서비스 연구 조직이었던 Live Labs는 Volta라는 프로젝트를 발표한다. Volta는 지금은 흔적만 남아있는 프로젝트로, 정보의 바다라는 인터넷에서도 Volta에 대한 정보는 찾기 어렵다. 어렵게 수집한 정보를 토대로 Volta를 설명해보자면, &lt;strong&gt;다양한 기술을 하나의 플랫폼 안에서 실행할 수 있는 환경을 제공&lt;/strong&gt;함으로써 기술 습득 비용을 줄이이는 것을 목적으로 하는 프로젝트라고 할 수 있다.&lt;/p&gt;
&lt;p&gt;지금의 GWT(Google Web Toolkit)와 유사하다. 다른 점은 Java로 작성한 코드를 JavaScript로 변환하는 한정적인 범위를 처리하는 GWT와 다르게 Volta는 좀 더 광범위(many to many)한 환경에 대응하려는 기술이었다는 점이다. Volta를 만든 에릭 마이어는 이를 GWT와 Volta는 사과와 오렌지의 차이와 같다고 표현했다. 같은 과일이지만 겉과 속이 다르다는 뜻이다.&lt;/p&gt;
&lt;p&gt;Volta 프로젝트는 갑작스레 어떤 이유로 중단되었고, 아직까지도 부활하지 못했다. 아래의 몇몇 기사를 통해 Volta의 대략적인 컨셉은 살펴볼 수 있었다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;http://Microsoft architect compares Volta and Google's GWT&quot;&gt;Microsoft architect compares Volta and Google's GWT&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;http://www.zdnet.co.kr/news/news_view.asp?artice_id=00000039164019&quot;&gt;MS, 웹 애플리케이션 개발 킷「볼타」릴리스&lt;/a&gt;&lt;/li&gt;
&lt;li class=&quot;entry-title&quot;&gt;&lt;a href=&quot;https://blogs.msdn.microsoft.com/bkchung/2007/12/06/microsoft-live-labs-volta-ctp/&quot;&gt;Microsoft Live Labs의 Volta 프로젝트 CTP&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;http://blog.lemonbrain.net/21&quot;&gt;진정한 웹다중Tier개발의 시작? VOLTA플랫폼&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;개발자가 특정 어노테이션을 이용하여 코드를 작성하면, .NET 언어와 라이브러리/개발도구를 이용해서 MSIL로 컴파일한다. 그러면, MSIL 안에 있는 어노테이션을 Volta가 해석해서 다양한 환경에서 실행할 수 있는 보일러 플레이트 코드를 삽입한다. 이렇게 만들어진 MSIL을 브라우저에서 실행한다. 대략 이런 개념이다.&lt;/p&gt;
&lt;p&gt;한편, Rx와 Volta의 관계에 대한 비밀은 로고에 있다. 두 녀석의 로고가 똑같다.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;/assets/images/legacy/2016/09/voltal-and-reactivex.png&quot;&gt;&lt;img class=&quot;왼쪽에는 Volta의 로고, 오른쪽은 ReactiveX의 로고 사진. 둘의 로고가 같다. aligncenter wp-image-2125 size-full&quot; src=&quot;/assets/images/legacy/voltal-and-reactivex.png&quot; alt=&quot;volta와 reactivex의 로고. 둘의 로고가 같다.&quot; /&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;이는 Rx가 Volta에서 시작되었기 때문이다. Volta는 사라졌지만, 개발팀은 프로젝트의 일부를 Reactive Framework라는 프로젝트로 분리하여 계속 개발을 진행하였는데, 이게 훗날의 Rx다.&lt;/p&gt;
&lt;p&gt;정확히 당시에 어떤 문제를 해결하기 위해서 Reactive Framework를 만들었는지는 알 수 없다. 다만 JavaScript와 같은 이벤트 기반의 동적 언어와 C# 같은 정적 언어 간의 코드 호환성을 맞추려다보면 필연적으로 비동기 로직을 쉽게 처리할 수 있는 방법을 고민했을 것이고, 이를 해결하는 과정에서 Reactive Framework를 만들지 않았을까?&lt;/p&gt;
&lt;p&gt;그저 추측이지만 완전히 근거 없는 소리는 아니다. Rx를 주제로 에릭 마이어가 한 인터뷰에서 Rx를 만든 이유가 비동기 프로그래밍에 있다는 사실을 유추할 수 있다.&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;&quot;Of all the work I’ve done in my career so far, this is the most exciting. […] I know it’s a bold statement, but I really believe that the problem of asynchronous programming events has been solved.&quot;&lt;/p&gt;
&lt;p id=&quot;ph_pcontent3_0_MainHeading&quot; class=&quot;title&quot;&gt;&lt;a href=&quot;https://campustechnology.com/articles/2009/08/10/microsofts-new-.net-rx-framework-tackles-challenges-of-asynchronous-programming.aspx&quot;&gt;Microsoft's New .NET Rx Framework Tackles Challenges of Asynchronous Programming&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h4&gt;&lt;b&gt;2009. 11. 17, Reactive Extensions for .NET의 첫 릴리즈&lt;/b&gt;&lt;/h4&gt;
&lt;p&gt;Volta에서 떨어져 나온 Reactive Framework는 2009년에 Reactive Extensions로 이름을 바꾸고, 아래의 세 가지 버전의 Rx를 공식 출시한다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Reactive Extensions For .NET 3.5 Sp1&lt;/li&gt;
&lt;li&gt;Reactive Extensions For .NET 4 beta 2&lt;/li&gt;
&lt;li&gt;Rx for Silverlight3&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;우리가 아는 Rx의 첫 등장이다. 하지만 이 당시 Rx는 오픈 소스가 아니었다.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://blogs.msdn.microsoft.com/rxteam/2010/10/28/release-notes/&quot;&gt;ReactiveX Release Notes&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;출시 이전까지 사람들은 Reactive Framework라는 코드명을 줄여서 Rx라고 불렀다. 에릭 마이어 또한 Reactive Framework라는 이름으로 개발, 교육, 홍보를 했는데 정작 릴리즈 당시의 제품명은 Reactive Extensions For .NET였다. 이름이 바뀐 자세한 맥락은 알 수 없으나, &lt;a href=&quot;https://channel9.msdn.com/Shows/Going+Deep/E2E-Erik-Meijer-and-Wes-Dyer-Reactive-Framework-Rx-Under-the-Hood-1-of-2&quot; rev=&quot;en_rl_minimal&quot;&gt;이 글&lt;/a&gt;에 있는 &lt;a href=&quot;https://twitter.com/Carmine007&quot; rev=&quot;en_rl_minimal&quot;&gt;Charles Torre&lt;/a&gt;라는 사람의 댓글을 통해 그 이유를 유추해 본다.&lt;/p&gt;
&lt;p&gt;.NET 자체가 프레임워크였기 때문에 Reactive Framework라는 이름은 사용자에게 혼란을 초래할 수 있다. Rx라는 명칭을 그대로 유지하면서, 사용자의 혼란을 줄이고 .NET 프레임워크를 확장한다는 의미를 더해줄 수 있는 Reactive Extensions라는 이름은 어떨까?&lt;/p&gt;
&lt;p&gt;그럴듯하다.&lt;/p&gt;
&lt;h4&gt;&lt;b&gt;2010. 03. 17, RxJS 첫 릴리즈&lt;/b&gt;&lt;/h4&gt;
&lt;p&gt;MS는 이듬해 3월에는 Rx의 JavaScript 버전인 RxJS를 공개한다.&lt;/p&gt;
&lt;p&gt;웹 프론트엔드 UI가 화려하고 복잡해지면서 AJAX 기술의 비중이 높아지던 시점이다. 비동기 행위를 쉽게 처리할 수 있는 방법이 필요했다. RxJS가 만들어질 당시에는 JavaScript의 콜백 지옥을 완화할 Promise 같은 마땅한 대체 기술(Promises/A+가 처음 나온 게 2012년 12월 6일)이 없었다는 점을 생각해보면 RxJS의 등장은 감격스럽다.&lt;/p&gt;
&lt;p&gt;하지만 당시에 JavaScript 개발자들한테 큰 호응을 얻지는 못한 것 같다. 이유는 잘 모르겠으나, RxJS는 어렵고 낯선 기술이 아니었을까? 아니면 RxJS를 사용해야 할 정도의 문제를 겪고 있는 프로젝트가 많지 않았다든지. 그러고 보면 콜백 지옥 문제가 큰 화두로 등장한 건, node.js가 대중적인 인기를 끌면서부터 였던 것 같다.&lt;/p&gt;
&lt;p&gt;(사실 요즘 Rx를 공부하면서 Rx가 인기를 얻지 못한 이유에 대해서 몇 가지 생각을 정리했지만 이 글의 주제는 아니므로 다음을 기약한다)&lt;/p&gt;
&lt;h4&gt;&lt;b&gt;2012. 11. 06, ReactiveX, 오픈 소스로&lt;/b&gt;&lt;/h4&gt;
&lt;p&gt;드디어 2012년 11월에 &lt;a href=&quot;http://www.hanselman.com/blog/ReactiveExtensionsRxIsNowOpenSource.aspx&quot;&gt;MS는 Rx의 세 가지 버전인, Rx .NET, RxJS, Rx++을 오픈 소스로 공개&lt;/a&gt;한다. RxJava를 알고 있는 사람들이 많을 텐데, &lt;a href=&quot;https://github.com/ReactiveX/RxJava/wiki&quot;&gt;RxJava&lt;/a&gt;는 넷플릭스에서 만들어서 공개한 버전이다.&lt;/p&gt;
&lt;h2&gt;&lt;b&gt;Rx는 어떻게 문제를 해결하는가?&lt;/b&gt;&lt;/h2&gt;
&lt;p&gt;앞에서 Rx의 탄생 배경이 비동기 프로그래밍 문제를 해결하는 데 있다고 했다. 비동기 프로그래밍은 어렵다. 비동기 코드가 많아지면 제어의 흐름이 복잡하게 얽혀 코드를 예측하기 어려워진다. 따라서 전통적인 절차적 프로그래밍으로는 이 문제를 풀기가 쉽지 않다.&lt;/p&gt;
&lt;p&gt;그렇다면 Rx는 어떻게 비동기 프로그래밍 문제를 해결한다는 걸까? 이제부터 Rx가 제안하는 대안을 알아보자. 핵심 키워드는 리액티브 프로그래밍과 LINQ다.&lt;/p&gt;
&lt;h4&gt;&lt;b&gt;리액티브 프로그래밍&lt;/b&gt;&lt;/h4&gt;
&lt;p&gt;소프트웨어를 둘러싼 요즘의 환경은 과거와 많이 달라졌다. 인터넷 환경이 발달하면서 트래픽이 전에 비해 엄청나게 증가하였고, 무어의 법칙은 한계에 도달했다. 멀티 코어 프로세서에서 대안을 찾기 시작하면서 동시성 프로그래밍이 중요해졌다. 클라우드 컴퓨팅 환경도 등장했다. 사용자 요구사항은 점점 까다로워져 더 정교하고 화려한 UI 인터랙션과 더 빠른 반응 속도를 요구한다. 전에 비해 훨씬 복잡해진 소프트웨어의 안정성은, 언제나 중요한 문제다.&lt;/p&gt;
&lt;p&gt;리액티브  매니페스토는 이 시대의 소프트웨어는 좋은 반응성(Responsive)을 가져야 하며, 좋은 반응성을 갖기 위해 회복탄력성(Resilient)과 유연성(Elastic)을 갖도록 시스템을 설계해야 한다고 주장한다. 이를 달성할 수 있는 방법으로 메시지(Message-Driven)로 시스템과 시스템, 모듈과 모듈을 연결하는 방법을 제안한다. 결국 비동기 처리를 적극 활용하는 데에서 문제의 해법을 찾는다.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&quot;Reactive Systems rely on asynchronous message-passing to establish a boundary between components that ensures loose coupling, isolation and location transparency.&quot;&lt;/p&gt;
&lt;p&gt;&lt;i&gt;&lt;a href=&quot;http://www.reactivemanifesto.org/&quot;&gt;The Reactive Manifesto&lt;/a&gt;&lt;/i&gt;&lt;/p&gt;&lt;/blockquote&gt;
&lt;p&gt;이런 흐름은 자연스레 리액티브 프로그래밍에 대한 개발자들의 관심으로 이어졌다. 위키에 나와있는 리액티브 프로그래밍의 정의는 이렇다.&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;데이터 플로우와 상태 변경을 전파한다는 생각에 근간을 둔 프로그래밍 패러다임&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://en.wikipedia.org/wiki/Reactive_programming&quot;&gt;Reactive Programming in Wikipedia, The Free Encyclopedia&lt;/a&gt;&lt;/p&gt;&lt;/blockquote&gt;
&lt;p&gt;이 정의에는 리액티브 프로그래밍의 목적이 빠져있다. 무엇을 위해서, 데이터 플로우 관점에서 사고하고, 변경을 전파하는 걸까? 리액티브 프로그래밍이 처음 등장했던 배경을 돌아보자.&lt;/p&gt;
&lt;p&gt;&lt;span style=&quot;font-size: 10pt;&quot;&gt;(데이터 플로우 프로그래밍(Dataflow Programming)의 하위 개념으로서의 리액티브 프로그래밍을 이야기하자면 훨신 더 이전으로 거슬러 올라가야하는데 이해의 수준이 아직 거기까지 가지 못했으므로 이 글에서는 언급하지 않을 생각이다.)&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;리액티브 프로그래밍의 처음 시작이 어디인지는 명확하지 않다. 다만 추측할 수 있는 단서는 있다. 1985년에 David Harel, Amir Pnueil가 발표한 &lt;a href=&quot;http://s3.amazonaws.com/academia.edu.documents/30783693/ReactiveSystems.pdf?AWSAccessKeyId=AKIAJ56TQJRTWSMTNPEA&amp;amp;Expires=1470547942&amp;amp;Signature=4yF7csn8uvK4wjhrRRrYAmivkzo%3D&amp;amp;response-content-disposition=inline%3B%20filename%3DOn_the_development_of_reactive_systems.pdf&quot; rev=&quot;en_rl_minimal&quot;&gt;On the development of reactive systems&lt;/a&gt;라는 논문에 처음으로 리액티브 시스템(Reactive Systems)이라는 용어가 등장한다. 이 논문에서 이야기하는 리액티브 시스템은 아래와 같은 특징을 가진다.&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;Reactive systems… are repeatedly prompted by the outside world and their role is to continusouly respond external inputs&lt;/p&gt;&lt;/blockquote&gt;
&lt;p&gt;즉, 리액티브 시스템이란 외부에서 들어오는 요청에 계속해서 응답하는 시스템이다. 이 논문은 리액티브 시스템을 구현하는 데에 적합한 프로그래밍 방법론에 대한 이야기를 담고 있는데, 이를 리액티브 프로그래밍으로 이해할 수 있다.&lt;/p&gt;
&lt;p&gt;여기에서 힌트를 하나 얻었다. 계속해서 응답한다는 건 &lt;strong&gt;'반응'&lt;/strong&gt;한다는 뜻이다. 그렇다면 리액티브 프로그래밍의 목적이 외부에서 들어온 자극에 반응하는 구조를 만드는 데 있다고 볼 수 있지 않을까? 여기에서 '반응'은 아래 두 가지 의미를 내포한다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;자극은 밖에서 안으로 흐른다.&lt;/li&gt;
&lt;li&gt;자극이 있어야만 반응하는 수동성을 갖는다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;정리하자면 프로그램이 외부와 상호 작용하는 방식을 거꾸로 뒤집어서 수동적 반응성을 획득하는 일, 이것이 리액티브 프로그래밍의 목적이다. 에릭 마이어가 리액티브 프레임워크를 소개하는 강연에서 보여주었던 아래 그림은 리액티브 프로그래밍의 핵심을 잘 보여준다.&lt;/p&gt;
&lt;p&gt;&lt;img class=&quot;wp-image-2126 size-large&quot; title=&quot;리액티브 환경과 인터랙티브 환경에서 제어가 흐르는 모습을 비교하는 그림&quot; src=&quot;/assets/images/legacy/reactive-eric-1024x640.png&quot; alt=&quot;에릭 마이어가 리액티브 프로그래밍을 소개하며 강연에서 사용했던 발표 자료로 외부에서 안으로 자극이 들어오는 리액티브 프로그래밍의 본질을 잘 보여준다.&quot; /&gt;&lt;/a&gt; 출처를 까먹어서 찾을 수가 없다... 찾는 대로 업데이트 할 예정.&lt;/p&gt;
&lt;p&gt;이 설명에 따르면 프로그램이 외부 환경과 커뮤니케이션을 하는 방법은 크게 두 가지가 있다. pull-scenario, 그리고 push-scenario.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;pull-scenario&lt;/strong&gt;는 우리에게 익숙한 방식으로 프로그램이 외부 환경에 명령하여 원하는 결과를 획득하는 방식이다. 이 경우 프로그램이 직접 제어의 흐름을 통제한다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;push-scenario&lt;/strong&gt;는 이를 뒤집어서, 환경이 프로그램 안으로 요청을 밀어넣는다. 외부 환경에 명령을 하고 응답이 오기까지 기다리는 것이 아니라, 외부에서 응답이 오면 그때 반응한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;push-scenario의 장점은 제어의 흐름을 통제할 권한을 외부 환경으로 넘김으로써 응답 대기 비용을 줄일 수 있다. 비동기 처리에 유리하다. 이 모습은 옵저버 패턴이나 리액터 패턴과 유사다. 그런데 리액티브 프레임워크 개발팀은 이 지점에서 한 가지 재밌는 사실을 발견한다. 바로 Iterator 패턴과 Observer 패턴이 쌍대(Duality)관계라는 점이다.&lt;/p&gt;
&lt;p&gt;쌍대관계란 용어는 여러 분야에서 다양하게 쓰이며, 문맥에 따라 미묘한 의미의 차이가 있어 설명하기가 참 어려운데(사실 나도 잘 이해하고 있는건지 모르겠다), &lt;strong&gt;A와 B가 있을 때 A에서 성립하는 정리를 뒤집어서 B에도 적용할 수 있는 경우&lt;/strong&gt;를 말한다. 한 마디로 A와 B의 본질이 같다는 뜻이다.&lt;/p&gt;
&lt;p&gt;Iterator는 연속하는 데이터를 pull-scenario로 가져온다. Observer는 외부에서 데이터를 주입 받는(주로 이벤트로) push-scenario라고 볼 수 있다. 이벤트를 여러 번 호출하면, 연속하는 데이터를 주입할 수 있는데 이는 Iterator와 본질이 같다. 다만 데이터가 흐르는 방향이 다를 뿐이다.  Rx는 외부에서 안으로 연속해서 밀어넣는 데이터를 받을 수 있는 인터페이스를 제공함으로써 리액티브 프로그래밍을 지원한다. Observable이다.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;event&lt;/th&gt;
&lt;th&gt;Iterable (pull)&lt;/th&gt;
&lt;th&gt;Observable (push)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;retrieve data&lt;/td&gt;
&lt;td&gt;&lt;code&gt;T next()&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;onNext(T)&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;discover error&lt;/td&gt;
&lt;td&gt;throws &lt;code&gt;Exception&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;onError(Exception)&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;complete&lt;/td&gt;
&lt;td&gt;&lt;code&gt;!hasNext()&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;onCompleted()&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;여기까지 보면 느낌이 Promise랑 비슷하다. 차이가 있다면 Promise는 단일 값을 처리하고, Observable은 여러 값을 처리한다.&lt;/p&gt;
&lt;h4&gt;&lt;b&gt;LINQ와 이벤트 결합(LINQ To Events)&lt;/b&gt;&lt;/h4&gt;
&lt;p&gt;Rx는 외부에서 들어온 데이터를 단순히 목적지까지 운반하는 데 그치지 않는다. 더 나아가 이벤트와 LINQ라는 개념을 결합한 인터페이스를 제공하는데, 이를 이용하면 Observable로 전달받은 데이터를 LINQ 스타일로 처리할 수 있다. 이를 오퍼레이터(operator)라고 한다.&lt;/p&gt;
&lt;p&gt;LINQ(Language Intergrated Query)는 에릭 마이어가 만든 통합 질의 언어다. LINQ는 C# 3.0에 처음 등장했는데, 쿼리를 언어에 통합하여 코드 상에서 데이터를 질의할 때 SQL 쿼리처럼 표현할 수 있게 도와주는 일종의 확장 문법이다. 이를 이용하면 데이터 콜렉션에 대한 복잡한 절차적 질의를, 마치 SQL로 처리하는 것처럼 간결하게 변경할 수 있다. C#에서 제공하는 LINQ는 아래와 같은 모습이다.&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-c#&quot; data-lang=&quot;c#&quot;&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1
2
3
4
5
6
7
8
9
10
&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;k&quot;&gt;using&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ServiceContext&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;svcContext&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;ServiceContext&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;_serviceProxy&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
 &lt;span class=&quot;kt&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;query_where1&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;a&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;svcContext&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;AccountSet&lt;/span&gt;
                    &lt;span class=&quot;k&quot;&gt;where&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;a&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Name&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;Contains&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;Contoso&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
                    &lt;span class=&quot;k&quot;&gt;select&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;a&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
 &lt;span class=&quot;k&quot;&gt;foreach&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;a&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;query_where1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
 &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;System&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Console&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;WriteLine&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;a&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Name&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot; &quot;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;a&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Address1_City&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
 &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;C#은 쿼리 구문(Query Syntax)과 메서드 구문(Method Syntax)이라는 두 가지 타입의 LINQ를 제공한다. 이 둘의 차이는 아래의 링크에서 확인할 수 있다.&lt;/p&gt;
&lt;p class=&quot;title&quot;&gt;&lt;a href=&quot;https://msdn.microsoft.com/ko-kr/library/bb397947.aspx&quot;&gt;Query Syntax and Method Syntax in LINQ (C#)&lt;/a&gt;&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-c#&quot; data-lang=&quot;c#&quot;&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1
2
3
4
5
6
7
8
9
10
11
&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;c1&quot;&gt;//Query syntax:&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;IEnumerable&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;lt&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;gt&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;numQuery1&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;num&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;numbers&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;where&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;num&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;%&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;2&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;0&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;orderby&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;num&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;select&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;num&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;//Method syntax:&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;IEnumerable&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;lt&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;gt&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;numQuery2&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;numbers&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;Where&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;num&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&amp;amp;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;gt&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;num&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;%&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;2&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;OrderBy&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;n&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&amp;amp;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;gt&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;n&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;JavaScript 개발자라면 underscore나 lodash 같은 함수형 유틸 라이브러리를 접하면서 LINQ에 대한 이야기를 많이 들어봤을 텐데, Rx가 제공하는 LINQ 스타일 오퍼레이터는 메서드 구문의 LINQ와 유사하다.&lt;/p&gt;
&lt;h4&gt;“Your Mouse Is DataBase&quot;&lt;/h4&gt;
&lt;p&gt;Observable은 프로그램이 연산을 수행하는 관점을 뒤집음으로써 비동기 처리에 유리한 구조를 만들 수 있는 토대를 제공한다. LINQ는 외부에서 스트림으로 들어오는 데이터를 쉽게 처리할 수 있는 방법을 제공한다.&lt;/p&gt;
&lt;p&gt;&lt;img class=&quot;wp-image-2068&quot; title=&quot;에릭마이어가 리액티브 프로그래밍을 강연하는 모습&quot; src=&quot;/assets/images/legacy/maxresdefault-1024x576.jpg&quot; alt=&quot;에릭마이어가 리액티브 프로그래밍을 강연하는 모습&quot; /&gt;&lt;/p&gt;
&lt;p&gt;리액티브 프로그래밍과 LINQ의 개념을 바탕으로 Rx가 비동기 데이터를 처리하는 방식을 이해하기 위해 드래그 앤 드롭을 예로 들어보자.&lt;/p&gt;
&lt;p&gt;마우스를 움직일 때마다 변하는 현재 위치 좌표는 외부에서 안으로 들어오는 자극이자, 데이터다. 이 좌표 데이터들은 Rx의 Observable이 만들어 놓은 문을 통해 프로그램 안으로 진입한다. 프로그램 안으로 들어온 데이터는 LINQ로 미리 작성해둔 오퍼레이터 사이를 헤엄쳐 최종 목적지에 도달한다. 프로그램은 최종 목적지로 들어온 데이터를 확인하여 응답한다. 이 과정은 마우스가 이동을 멈추지 않는 한 끊임없이 계속해서 이뤄진다. 마치 강물이 흐르듯이. 이게 Rx가 제안하는, 데이터 관점의 비동기 처리 방식이다.&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-javascript&quot; data-lang=&quot;javascript&quot;&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1
2
3
4
5
6
7
8
9
10
11
&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;dragElement&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;document&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;getElementById&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;dragElement&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;mouseDrag$&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;Rx&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;Observable&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;fromEvent&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;dragElement&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;mouseup&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;map&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;md&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;amp;&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;gt&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;({&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;startX&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;md&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;offsetX&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;startY&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;md&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;offsetY&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}));&lt;/span&gt;

&lt;span class=&quot;nx&quot;&gt;mouseDrag$&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;subscribe&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;pos&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;amp;&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;gt&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;nx&quot;&gt;console&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;pos&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;});&lt;/span&gt;
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;에릭 마이어의 말처럼, Rx 세상 속에서 &lt;a href=&quot;http://queue.acm.org/detail.cfm?id=2169076&quot;&gt;Your Mouse는 DataBase&lt;/a&gt;다.&lt;/p&gt;
&lt;p&gt;(Rx는 이외에도 스케줄러나 함수형 패러다임을 지원하는 다양한 장치를 제공한다. 다만 이 글에서는 핵심만 짚었을 뿐이다)&lt;/p&gt;
&lt;h2&gt;&lt;b&gt;끝으로...&lt;/b&gt;&lt;/h2&gt;
&lt;p&gt;Rx를 조사하면서 리액티브 프로그래밍에 대한 다양한 이해가 존재하는 것만큼 Rx에 대한 이해 또한 다양하다는 사실을 알았다. Rx와 리액티브 프로그래밍을 동일시 하거나, 함수형 리액티브 프로그래밍을 리액티브 프로그래밍 그 자체로 생각하는 견해를 종종 볼 수 있다.&lt;/p&gt;
&lt;div&gt;시대에 따라 용어가 내포하는 개념은 넓어지기도 하고, 좁아지기도 하기에 어느 쪽 해석이 전적으로 틀렸다고 말하긴 어렵다. 그래서 개념과 주장이 난무하여 혼란스러울 때, 잠깐 손을 놓고 지나온 길을 돌아보는 일은 꽤나 의미있다.&lt;/div&gt;
&lt;div&gt;
&lt;div&gt;&lt;/div&gt;
&lt;blockquote&gt;&lt;p&gt;어떤 개념을 정말로 이해하려면 그 개념이 최초로 언급된 당시의 전후 맥락을 재구성해 볼 필요가 있다. 이렇게 해야 개념의 정수가 그 모든 중간자를 거치고도 살아남았음을 확인할 수 있다.&lt;/p&gt;
&lt;p&gt;- 프로그래머의 길, 멘토에게 묻다 -&lt;/p&gt;&lt;/blockquote&gt;
&lt;/div&gt;
&lt;p&gt;Rx는 다양한 문맥에서 다양한 방식으로 쓰인다. 하지만 목적하는 바는 비슷하다. 해법이 조금 다를 뿐이다. 최근의 웹 프론트엔드 분야만 놓고 보자면 XHR을 이용한 비동기 요청을 처리할 때 Promise를 대체하는 정도로 제한적으로 사용하는가 하면, Cycle.js(Rx의 경량화 버전이라 할 수 있는 xstream을 이용) 처럼 아예 프레임워크 설계 수준까지 끌어올려서 사용하는 경우도 있다.&lt;/p&gt;
&lt;p&gt;한 달 정도 공부한, 웹 프론트엔드에서의 RxJS에 대한 나의 느낌은 물음표다. 제한해서 사용한다면 꽤나 괜찮은 녀석일 것 같은데, 이에 비해 들여야하는 학습 비용이 너무 크게 느껴졌다. 오퍼레이터는 이름으로 용도를 유추하기 너무 힘들다. 오퍼레이터를 모르면 코드를 읽을 수가 없다. 협업 상황에서 치명적인 마이너스 요인이다.&lt;/p&gt;
&lt;p&gt;그럼에도 낯선 세계가 주는 경험은 매우 흥미로웠고, Rx를 둘러싼 배경을 들여다보면서 다양한 인사이트를 얻을 수 있었다. 특히 Redux를 다른 관점에서 이해(&lt;strong&gt;Redux는 이미 리액티브하다&lt;/strong&gt;)하게 되었는데 이에 대한 이야기는 다른 글에서 할 생각이다(이렇게 말하고 글을 이어 쓴 적이 한 번도 없더라...). 사실 Rx 보다는 리액티브 프로그래밍에 관심이 더 많다.&lt;/p&gt;
&lt;p&gt;연습 삼아서 간단한 개인 프로젝트를 해봤다. 작성한 코드는 아래 링크에서 볼 수 있다.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/CoderK/github-filter-extension&quot;&gt;github-filter-extension with RxJS&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;순전히 재미와 연습을 목적으로 구현한 거라 오버엔지니어링을 많이 했다. 특히 RxJS의 스트림을 극적으로 활용하는 함수형 Rx의 느낌을 맞보고 싶어서 Cycle.js의 MVI 설계를 네이티브로 따라 해 봤다. 함수형 코드의 구조화를 고민하다 보니 모듈을 좀 과하게 분리해놓은 감이 있다. 역시나 쪼렙인지라. RxJS를 이용해서 코딩을 하다가 대략 멍해지는 순간을 많이 만났다.&lt;/p&gt;
&lt;p&gt;조사를 하면서 많은 자료를 찾았는데 논문 수준의 글은 내가 이해하기에 버거웠고, 쉽게 읽히는 글들은 깊이가 부족했다. 그래서 중간에 제대로 이해하지 못한 내용이 있을 수도 있다. 잘못 설명하고 있는 부분이 있다면 언제든 댓글이나 SNS로 의견 주시기 바라며 글을 마친다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr /&gt;
&lt;ol&gt;
&lt;li&gt;공부하면서 참고했던 자료는 &lt;a href=&quot;https://github.com/CoderK/What-I-Have-Learned/blob/master/README.md#rx와-리액티브-프로그래밍2016&quot;&gt;여기&lt;/a&gt;에 모아두었습니다.&lt;/li&gt;
&lt;li&gt;레진에서 Rx를 주제로 발표할 기회가 있었는데, 이 때 발표한 내용에는 FRP에 대한 설명도 들어가 있으니 관심있는 분은 &lt;a href=&quot;http://www.slideshare.net/jeokrang/rx-70197043&quot;&gt;여기&lt;/a&gt;를 참고하세요.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&amp;nbsp;		&lt;/p&gt;
</description>
            <pubDate>Fri, 09 Sep 2016 13:26:01 +0900</pubDate>
            <link>https://huns.me/development/2051</link>
            <guid isPermaLink="true">https://huns.me/development/2051</guid>
            
            <category>rx</category>
            
            <category>rxjs</category>
            
            <category>리액티브 프로그래밍</category>
            
            
            <category>개발 이야기</category>
            
        </item>
        
        <item>
            <title>리액트 딜레마</title>
            <description>&lt;div&gt;
&lt;p&gt;페이스북은 React를 'MVC에서 View만을 담당하는 작고 가벼운 라이브러리'라고 소개했다. 여기에는 이견이 없다.&lt;/p&gt;
&lt;p&gt;그런데 잠깐, View만 가지고 만들 수 있는 게 뭐가 있을까?&lt;/p&gt;
&lt;p&gt;하나의 애플리케이션 안에는 여러 가지의 관심사가 존재하고, 각각의 관심사를 처리하는 기술 또한 다양하다.  React 프로젝트를 하다 보면 React가 해결할 수 없는 영역을 채워줄 대안을 찾게 되고, 결국에는 단일 프레임워크스러운 무언가를 만들고 있는 자신을 발견한다. 이들을 찾아서 학습하고, 비교하고, 선택하는 과정도 비용이다.&lt;/p&gt;
&lt;p&gt;React만으로 해결할 수 있는 문제의 범위는 생각 보다 좁다. 단순한 위젯이나 범용적으로 만든 라이브러리성 컴포넌트 정도가 떠오른다. 그런데 이렇게 단순한 녀석이라면, React 환경에 맞춰서 구현해야하는 특별한 이유가 없는 한, 그냥 순수 JavaScript나 jQuery를 이용해서 만드는 게 더 생산적일 수도 있다. React 개발 환경을 구축하고 코드를 트랜스파일 하는 과정 역시 지속적으로 지불해야 할 비용이니까.&lt;/p&gt;
&lt;p&gt;결국 'React는 작고 가벼운 라이브러리지만, React만 가지고서 할 수 있는 일은 많지 않기에 진정으로 작고 가볍기 어렵다'는 결론에 도달했고, 나는 이걸 '리액트 딜레마'라고 부른다. 물론 나만 쓰는 용어다. 이런 딜레마가 완전히 단점이라고 말하기는 어렵다. 장점과 단점은 종이 한 장 차이라던가. 개발자에게 다양한 선택지를 준다는 점에서 React는 분명 가볍다.&lt;/p&gt;
&lt;/div&gt;
&lt;p&gt;역으로 이런 생각도 해봤다. Monolithic Framework(Angular같은)는 정말 하나로 모든 문제를 해결할까? 글쎄, 프로젝트 성격에 따라서 다를 수는 있겠지만 보편적으로 어렵지 않을까. 이런 고민을 하던 중에 페이스북에서 만난, 박성철 님의 글이 인상 깊다.&lt;/p&gt;
&lt;p&gt;&lt;iframe width=&quot;500&quot; height=&quot;510&quot; style=&quot;border: none; overflow: hidden;&quot; src=&quot;https://www.facebook.com/plugins/post.php?href=https%3A%2F%2Fwww.facebook.com%2Ffupfin.geek%2Fposts%2F353216358400687&amp;amp;width=500&quot; scrolling=&quot;no&quot; frameborder=&quot;0&quot; allowtransparency=&quot;true&quot;&gt;&lt;/iframe&gt;		&lt;/p&gt;
</description>
            <pubDate>Mon, 22 Aug 2016 13:09:10 +0900</pubDate>
            <link>https://huns.me/development/2011</link>
            <guid isPermaLink="true">https://huns.me/development/2011</guid>
            
            <category>react</category>
            
            <category>단상</category>
            
            
            <category>개발 이야기</category>
            
        </item>
        
        <item>
            <title>로버트 마틴의 클린 코더(The Clean Coder),  그리고 단상</title>
            <description>&lt;p&gt;
				많은 책을 읽는 편은 아니지만 꾸준히 읽으려고 애쓴다. 책을 그저 읽는 수준에서 마무리를 해버리면, 읽으면서 느낀 감정이나 생각이 너무 쉽게 사라져 버리는 게 못내 아쉽다. 그래서 메모를 많이 남기는 편인데, 여기에 더해 짧게 나마 감상도 남겨볼 생각이다. 리뷰라고 하기엔 거창한 것 같아 다른 단어를 고민해봤으나 적당한 단어가 떠오르지 않으니 그냥 '단상'이라고 하자.&lt;/p&gt;
&lt;p&gt;첫 도전은 로버트 마틴의 유명한 책인 &lt;a href=&quot;http://www.acornpub.co.kr/book/clean-coder&quot;&gt;&lt;b&gt;'클린 코더'&lt;/b&gt;&lt;/a&gt;다.&lt;/p&gt;
&lt;p&gt;클린 코더(The Clean Coder)는 &lt;a href=&quot;http://book.naver.com/bookdb/book_detail.nhn?bid=7390287&quot;&gt;클린 코드(Clean Code)&lt;/a&gt;로 유명한 엉클 밥이 프로 개발자에 대한 자신의 생각을 전하는 책이다.&lt;span class=&quot;Apple-converted-space&quot;&gt;  책 표지가 오래전에 나온 책인 클린 코드의 초판 디자인과 비슷해서 같은 책으로 혼동하는 경우가 많은 듯하다. 최신 버전의 클린 코드는 하얀색 표지로 새롭게 단장해서 이전과 전혀 다른 모습을 하고 있다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;클린 코더의 책 표지 디자인은 꽤나 시대에 뒤떨어진 느낌인데, 출판사 측에서 책을 쓸 당시 저자의 의도를 최대한 반영하고 싶었던 거라고, 혼자 추측해 본다. 믿거나 말거나.&lt;/p&gt;
&lt;p&gt;클린 코더는 어떤 코딩 기법을 전하는 책이 아니다.&lt;span class=&quot;Apple-converted-space&quot;&gt; 프로 &lt;/span&gt;개발자로서 업무를 대하는 태도나 자세에 대한, 다소(?) 진부한 이야기를 처음부터 끝까지 잔소리처럼 늘어놓는다. 누군가는 로버트 마틴이나 되는 개발자니까 할 수 있는 소리라고, 책 속에서나 가능한 비현실적인 이야기라고 할지도 모르겠다.&lt;/p&gt;
&lt;p&gt;하지만 '도저히 도달할 수 없는 이상'을 그저 멍하니 바라보는 체념으로 가득한 세상 속에서, 좀 더 나은 순간이 오기를 학수고대하는 삶을 사는 나에게 로버트 마틴의 잔소리는 꽤나 특별했다. 왜 누군가에게는 가능한 현실이, 누군가에게는 불가능한 현실일까? 우리가 이상이라고 생각하는 것들은 정말 이상일까? 이 모든 것이 단순히 환경의 문제일까? 충분히 할 수 있는 일임에도 애둘러 핑계를 대며 회피하고 있는 것은 아닐까? 늘 의문으로 가득하다. 이 책을 읽으며 현실과 이상 사이를 넘나들었던 시간은 좋은 경험이었다.&lt;/p&gt;
&lt;p&gt;이 외에도, 책 중간중간에 섞여있는 솔직 담백한 로버트 마틴의 자전적인 경험담은 유쾌하고 재밌다. 그중에 로버트 마틴이 젊은 시절에 일정 추정에 실패한 후, 속상해서 필름이 끊길 정도로 술을 마셨다는 에피소드가 가장 기억에 많이 남는다. 개발자라면 흔히 겪는 경험임에도 뭔가 묵직하게 와 닿았던 이유는 주인공이 로버트 마틴이어서 그렇다. 로버트 마틴 같은 대가는 태어날 때부터 천재일 것 같고 살면서 실패 한 번 경험해보지 못 했을 거 같았는데, 이런 사람도 깨지고 부딪히는 과정을 거치면서 끊임없이 학습하고 성장했다는 사실은 많은 걸 생각하게 한다.&lt;/p&gt;
&lt;p&gt;그래서 그런 걸까. 또다시 궁금해졌다. 나는 지금 어디쯤 와 있는 걸까. 어디까지 갈 수 있을까.		&lt;/p&gt;
</description>
            <pubDate>Sat, 13 Aug 2016 11:45:34 +0900</pubDate>
            <link>https://huns.me/development/1999</link>
            <guid isPermaLink="true">https://huns.me/development/1999</guid>
            
            <category>단상</category>
            
            <category>책</category>
            
            <category>클린 코더</category>
            
            
            <category>개발 이야기</category>
            
        </item>
        
        <item>
            <title>리덕스(Redux) 애플리케이션  설계에 대한 생각</title>
            <description>&lt;p&gt;
				이 글은 리덕스를 이용하여 애플리케이션을 개발할 때, 설계를 고민하며 했던 생각을 정리한 글입니다. 리덕스 기초를 소개하는 글이 아니니 읽기 전에 참고하세요.&lt;/p&gt;
&lt;p&gt;&lt;b&gt;--&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;img class=&quot;wp-image-1987&quot; src=&quot;/assets/images/legacy/photo-1416453072034-c8dbfa2856b5-1024x676.jpeg&quot; alt=&quot;많은 사람들이 커피숍 테이블에 앉아 왁자지껄 시끄럽게 이야기를 주고 받는 모습의 사진&quot; width=&quot;410&quot; /&gt;&lt;/a&gt; 타인과 생각을 공유하고 의견을 주고 받으며 부딪히는 과정은 피곤하지만, 그 속에서 더 좋은 해법을 발견한다.&lt;/p&gt;
&lt;p&gt;&lt;span class=&quot;se_ff_nanumgothic&quot;&gt;최근 진행하고 있는 모바일 웹 프로젝트는 리액트(React)와 리덕스(Redux) 조합을 이용하고 있다. 모든 &lt;/span&gt;&lt;span class=&quot;se_ff_nanumgothic&quot;&gt;프레임워크가 그렇듯이, 리덕스 역시 모든 문제를 해결하지 않는다. 남은 부분은 개발자의 몫이다. 리덕스 공식 문서의 FAQ는 개발하면서 한 번쯤은 고민해봤을 법한 문제를 리덕스로 해결하는 방법을 친절하게 안내하고 있어 참고할만하지만, 확실하게 딱 잘라서 결정해주는 맛은 없다.&lt;br /&gt;
&lt;/span&gt;&lt;span class=&quot;se_ff_nanumgothic&quot;&gt;&lt;br /&gt;
&lt;/span&gt;&lt;span class=&quot;se_ff_nanumgothic&quot;&gt;사실 가치 판단의 문제인 경우가 대부분이라, 딱 잘라서 정답을 제시한다는 것 자체가 어렵다. 선택지라도 알려주는 게 어딘가. &lt;/span&gt;&lt;span class=&quot;se_ff_nanumgothic&quot;&gt;무엇이 더 좋은 선택인지는 전체 상황을 고려해서 결정해야 하며 &lt;/span&gt;&lt;span class=&quot;se_ff_nanumgothic&quot;&gt;프로젝트 초기에 어느 정도 설계의 방향성을 팀원 간에 합의를 하는 것이 좋다. 그렇지 않으면 일관성을 잃어버린 코드가 제멋대로 자라다가 급기야는 시스템을 좀먹는다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;동료들과 프로젝트를 본격적으로 진행하기 전에 설계 방향을 놓고 많은 이야기를 했다. 고민이 등장할 때마다 메모를 해두었는데, 그렇게 마구 흩어놓은 리덕스 디자인에 대한 생각을 정리하는 차원에서 주저리주저리 떠들어 볼 생각이다. 앞으로 써 내려갈 내용 중에는 모두가 동의한 부분도 있고, 그렇지 않은 부분도 있다. 나는 그것이 옳다고 생각하지만 누군가는 더 나은 다른 방법이 있다고 믿는다. 세상사가 다 그런 거 아닌가. 논의하고 부딪히는 과정은 피곤하지만, 그 속에서 더 좋은 해법을 발견한다.&lt;/p&gt;
&lt;p&gt;리덕스를 이야기하면서 플럭스 유틸즈(Flux-utils)와 비교를 안 할 수가 없다.&lt;/p&gt;
&lt;p&gt;&lt;b&gt;&quot;뭐가 다른 걸까?&quot;&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;제일 먼저 리듀서(reducer)가 눈에 들어왔다. 이름부터 난해하다. 리듀서라니. 리덕스를 이해하려면 리듀서를 이해해야 할 것 같다. 그래서 설계의 시작도 리듀서다.&lt;/p&gt;
&lt;h2&gt;리듀서 들여다보기&lt;/h2&gt;
&lt;p&gt;플럭스 유틸즈와 비교했을 때,  리덕스의 가장 눈에 띄는 특징은 &lt;b&gt;'단일 스토어, 다수의 리듀서&lt;/b&gt;’ 정책이다. 시스템의 도메인 레이어라 할 수 있는 스토어에서 모든(대부분?) 상태를 관리하는 플럭스 아키텍처 위에서는, 커지는 코드 베이스에 맞춰 스토어 역시 쉽게 비대해진다. 이런 상황이 오면 하나였던 스토어를 다수의 스토어로 분리한다.&lt;/p&gt;
&lt;p&gt;이를 위해 플럭스 유틸즈는 스토어를 추상화한 객체를 제공한다. 그게 설계자의 의도인지는 모르겠지만 구글링을 하면 이런 사례들이 많이 눈에 띈다. 액션이 다수의 스토어에 비동기 요청을 던졌을 때, 각 요청을 처리하는 시점을 제어하는 API(waitFor 같은)를 통해서도 설계자의 의도를 추측할 수 있다.&lt;/p&gt;
&lt;div class=&quot;se_editArea&quot;&gt;
&lt;div class=&quot;se_viewArea se_ff_nanumgothic se_fs_T3 se_align-left&quot;&gt;
&lt;div class=&quot;se_editView&quot;&gt;
&lt;div class=&quot;se_textView&quot;&gt;
&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-javascript&quot; data-lang=&quot;javascript&quot;&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1
2
3
4
5
6
7
8
9
10
11
12
&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;nx&quot;&gt;MessageStore&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;dispatchToken&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;ChatAppDispatcher&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;register&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kd&quot;&gt;function&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;action&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;switch&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;action&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;type&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;case&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;ActionTypes&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;CLICK_THREAD&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
            &lt;span class=&quot;c1&quot;&gt;// 여기를 유심히 보자&lt;/span&gt;
            &lt;span class=&quot;nx&quot;&gt;ChatAppDispatcher&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;waitFor&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;([&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;ThreadStore&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;dispatchToken&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]);&lt;/span&gt;
            &lt;span class=&quot;nx&quot;&gt;_markAllInThreadRead&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;ThreadStore&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;getCurrentID&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;());&lt;/span&gt;
            &lt;span class=&quot;nx&quot;&gt;MessageStore&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;emitChange&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
            &lt;span class=&quot;k&quot;&gt;break&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;nl&quot;&gt;default&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;c1&quot;&gt;// do nothing&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;});&lt;/span&gt;
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p class=&quot;__se_code_view se_textarea language-javascript&quot;&gt;이에 반해 리덕스는 단일 스토어를 유지하되, 상태 처리에 대한 책임을 스토어의 하위에 있는 리듀서에게 넘기는 '단일 스토어, 다수의 리듀서' 전략을 택했다. 리덕스에서 스토어는 컴포넌트와 리듀서를 연결하는 아주 얇은 레이어일 뿐이다. 상태와 상태를 처리하는 행위는 스토어가 아닌 리듀서가 책임진다. 스토어는 여러 개의 리듀서를 가질 수 있다. 각각의 리듀서가 처리하는 상태는 최종적으로 최상위 리듀서가 시스템의 상태 트리로 만들어서 스토어를 구독하는 구독자에게 전달한다.&lt;/p&gt;
&lt;div class=&quot;se_editArea&quot;&gt;
&lt;div class=&quot;se_viewArea&quot;&gt;&lt;img id=&quot;SEDOC-1467376846350-892175543_image_2_img&quot; class=&quot;se_mediaImage __se_img_el&quot; src=&quot;/assets/images/legacy/Redux_Archictecture_-_New_Page_.png&quot; alt=&quot;&quot; width=&quot;936&quot; data-attachment-id=&quot;&quot; /&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p class=&quot;se_textarea&quot;&gt;물론 플럭스 유틸즈를 이용해도 하나의 스토어를 두고 상태 처리 책임을 분리하는 방식으로 설계할 수 있고, 리덕스 역시 여러 스토어를 구현할 수 있다. 둘 다 어떤 한 가지 정책을 강제하지는 않는다. 다만 기저에 깔린 설계 철학이 어떤 쪽에 더 가까운지를 생각해봤을 때 그렇다는 뜻이고, 이 철학에 맞춰 구현할 때 가장 자연스럽다.&lt;/p&gt;
&lt;p&gt;이 지점에서 분리해야 하는 대상의 본질이 스토어와 액션을 결합하는 인터페이스가 아니라 스토어가 관리하는 상태 데이터와 그 데이터를 처리하는 행위에 있다는 사실을 알 수 있다. 액션과 스토어를 결합하는 방식은 스토어가 어떤 상태를, 어떻게 책임지는지와는 상관없이 모두 동일하다. 이 공통 인터페이스를 플럭스 유틸즈는 상속으로 해결한다. 아래의 링크를 따라가보면 플럭스 유틸즈가 제공하는 스토어 객체를 확인할 수 있다. 플럭스 유틸즈는 모두 세 종류의 스토어 객체를 제공한다.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;http://facebook.github.io/flux/docs/flux-utils.html#content&quot;&gt;http://facebook.github.io/flux/docs/flux-utils.html#content&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;아래의 코드는 플럭스 유틸즈의 ReduceStore를 상속하여 스토어를 정의하는 예제 코드다. 이 코드는 Flow라는 정적 타입 검증기를 사용하고 있어 코드가 조금 낯설어 보일 수 있지만 타입 선언만 제거하면 그냥 자바스크립트다.&lt;/p&gt;
&lt;div class=&quot;se_editArea&quot;&gt;
&lt;div class=&quot;se_viewArea se_fs_T3 &quot;&gt;
&lt;div class=&quot;se_editView&quot;&gt;
&lt;div class=&quot;se_textView&quot;&gt;
&lt;div class=&quot;__se_code_view se_textarea language-javascript&quot;&gt;
&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-javascript&quot; data-lang=&quot;javascript&quot;&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;k&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;ReduceStore&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;flux/utils&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;kd&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;CounterStore&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;extends&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;ReduceStore&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;lt&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;number&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;gt&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;nx&quot;&gt;getInitialState&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;():&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;number&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

  &lt;span class=&quot;nx&quot;&gt;reduce&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;state&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;number&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;action&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;Object&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;number&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;switch&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;action&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;type&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;k&quot;&gt;case&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;increment&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;state&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

      &lt;span class=&quot;k&quot;&gt;case&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;square&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;state&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;state&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

      &lt;span class=&quot;nl&quot;&gt;default&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;state&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p class=&quot;se_textarea&quot;&gt;리덕스는 스토어를 좀 더 상위 수준으로 추상화하고 대신 하부 로직을 분리해서 책임을 분산시킨다. 아래의 코드를 보고 알 수 있듯이 리덕스에서 스토어는 내부 실체가 보이지 않는다. 그저 리듀서만 보일 뿐이다. 상태 처리 로직만 작성하면 나머지는 리덕스가 알아서 결합한다.&lt;/p&gt;
&lt;div class=&quot;se_editArea&quot;&gt;
&lt;div class=&quot;se_viewArea se_ff_nanumgothic se_fs_T3 se_align-left&quot;&gt;
&lt;div class=&quot;se_editView&quot;&gt;
&lt;div class=&quot;se_textView&quot;&gt;
&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-javascript&quot; data-lang=&quot;javascript&quot;&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;k&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;createStore&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;redux&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;

&lt;span class=&quot;kd&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;counter&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;state&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;action&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;switch&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;action&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;type&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;case&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;increment&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
            &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;state&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

        &lt;span class=&quot;k&quot;&gt;case&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;square&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
            &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;state&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;state&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

        &lt;span class=&quot;nl&quot;&gt;default&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
            &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;state&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;store&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;createStore&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;counter&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;h2&gt;리듀서와 순수 함수에 대한 생각&lt;/h2&gt;
&lt;p&gt;리덕스의 공식 문서에 있는 예제들은 순수 함수를 적극 활용한다. Dan Abramov가 함수형 프로그래밍에 영감을 받아서 리덕스를 만들었기 때문인 듯하다. 순수 함수는 아래와 같은 특징을 갖는 함수를 말한다.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;함수 밖에 있는 데이터나 변수를 변경해서 의도치 않은 결과를 발생시키지 않아야 한다.&lt;/li&gt;
&lt;li&gt;동일한 입력 데이터의 집합을 제공받으면 항상 동일한 연산 결과를 반환해야 한다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;특히 리듀서에 이런 철학이 강하게 묻어있다. 리듀서를 순수하게 유지하면 뭐가 좋은 걸까? 결론적으로 시스템이 복잡해져도 리듀서를 가능한 단순한게 유지할 수 있다.&lt;/p&gt;
&lt;div class=&quot;se_editArea&quot;&gt;
&lt;div class=&quot;se_viewArea se_fs_T3 &quot;&gt;
&lt;div class=&quot;se_editView&quot;&gt;
&lt;div class=&quot;se_textView&quot;&gt;
&lt;div class=&quot;__se_code_view se_textarea language-javascript&quot;&gt;
&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-javascript&quot; data-lang=&quot;javascript&quot;&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1
2
3
4
5
6
7
8
9
10
11
12
13
&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;nx&quot;&gt;resolvers&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;deleteComponent&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;prevState&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;targetId&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;})&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;amp;&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;gt&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;order&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;compMap&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;prevState&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;updatedOrder&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;deleteComponent&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;order&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;targetId&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;

    &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;Immutable&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;fromJS&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;prevState&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;merge&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;({&lt;/span&gt;
            &lt;span class=&quot;na&quot;&gt;order&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;updatedOrder&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
            &lt;span class=&quot;na&quot;&gt;focusCompId&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&quot;&quot;&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;})&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;deleteIn&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;([&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;compMap&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;targetId&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;])&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;toJSON&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;};&lt;/span&gt;
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p class=&quot;se_textarea&quot;&gt;입력을 받으면 입력 값을 처리하여 새로운 상태를 결과로 반환하는 게 전부다. 입력 값을 전달하는 것 외에는 함수 실행 중에 외부의 어떤 조건에도 영향을 받지 않는다. 하는 일이 단순하니 예측하기 쉽고, 테스트하기도 쉽다. 리덕스가 자랑하는 시간 여행 디버깅이 가능한 것도 리듀서의 이런 특성 덕이다.&lt;/p&gt;
&lt;div class=&quot;se_editArea&quot;&gt;
&lt;div class=&quot;se_viewArea se_fs_T3 &quot;&gt;
&lt;div class=&quot;se_editView&quot;&gt;
&lt;div class=&quot;se_textView&quot;&gt;
&lt;div class=&quot;__se_code_view se_textarea language-javascript&quot;&gt;
&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-javascript&quot; data-lang=&quot;javascript&quot;&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1
2
3
4
5
6
7
8
9
10
11
12
&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;nx&quot;&gt;it&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;컴포넌트 목록에서 원하는 컴포넌트 하나를 삭제할 수 있다.&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;amp;&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;gt&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;// given&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;deleteTarget&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;target&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;action&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;compListAction&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;deleteComponent&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;deleteTarget&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;

    &lt;span class=&quot;c1&quot;&gt;// when&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;order&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;compMap&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;compListReducer&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;initState&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;action&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;

    &lt;span class=&quot;c1&quot;&gt;// then&lt;/span&gt;
    &lt;span class=&quot;nx&quot;&gt;order&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;should&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;not&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;include&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;deleteTarget&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;nx&quot;&gt;should&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;not&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;exist&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;compMap&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;deleteTarget&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]);&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;});&lt;/span&gt;
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p class=&quot;se_textarea&quot;&gt;하지만 리듀서를 함수로 구성하다 보니 행위와 데이터를 하나로 결합한 도메인 모델을 구성하기가 어렵고, 이로 인해 설계에 익숙하지 않은 어색한 지점이 발생(데이터의 정체성을 어떻게 정의할지 모호하다든지, 데이터를 가져오는 AJAX 요청을 도메인 레이어의 앞단에서 처리해야한다든지)하는 단점이 있다.&lt;/p&gt;
&lt;p class=&quot;se_textarea&quot;&gt;리듀서가 순수해야 한다는 뜻은 리듀서에 전달하는 값이 동일하다면, 리듀서가 돌려주는 값도 항상 동일해야 함을 의미한다. 그래서 xhr을 이용한 비동기 통신처럼 사이드 이펙트를 만들 수 있는 행위는 리듀서에 담지 않는다. 이는 리듀서가 연산을 수행하는 데 있어 리듀서 외부의 상태에 영향을 받지 않아야 한다는 뜻일 뿐, 그 안에 어떠한 로직도 없어야 한다는 뜻은 아니다. 비즈니스 로직은 처리 대상인 데이터와 가까운 곳에 위치하는 것이 좋기에, 리덕스 애플리케이션의 비즈니스 로직은 리듀서에 위치하는 것이 적절하다. 처리하려는 로직의 성격에 따라 리듀서 내부의 로직도 얼마든지 복잡해질 수 있고, 그러다 보면 리듀서 내에서 다른 여러 함수를 호출하는 경우도 자주 있다.&lt;/p&gt;
&lt;p class=&quot;se_textarea&quot;&gt;이 지점에서 오해하지 말아야 한 가지는, 리듀서를 '순수'하게 만들기 위해서 반드시 함수를 고집해야 하는 건 아니라는 점이다. 객체를 이용해서도 얼마든지 리듀서를 순수하게 만들 수 있다. 모든 건 구현하기 나름이니 리덕스에 함수형 철학이 묻어있다고 '&lt;b&gt;함수형이 &lt;/b&gt;짱짱맨'이라는 생각은 하지 말자. 다만 리덕스를 이용하면 함수 단위로 코드를 작성하는 게 조금 더 자연스러울 뿐이다.&lt;/p&gt;
&lt;h2&gt;상태 트리 설계에 대한 고민&lt;/h2&gt;
&lt;p class=&quot;se_textarea&quot;&gt;리덕스는 리듀서를 트리 구조로 분리함으로써 상태 관리에 대한 책임을 분산시킨다. 분리한 상태는 combineReduce 함수로 조합하여 최상위 리듀서가 시스템의 단일 상태 트리로 조합한다. 이 지점에서 시스템의 상태 구조가 드러난다.&lt;/p&gt;
&lt;p class=&quot;se_textarea&quot;&gt;리듀서를 너무 깊은 트리 구조로 만들면 리듀서 결합이 복잡해지고, 상태의 흐름이 잘 안 읽힌다. 그래서 최대한 평탄한(flat) 구조로 만들어야 한다. 하지만 너무 평탄해버리면 상태 간의 관련성을 가독성 있게 표현하기 어렵다. 어느 지점에서 균형을 잡아야 한다. 상태 트리 구성과 분류 기준을 선택하는 일은 리덕스 시스템의 큰 틀과 방향을 결정하는 중요한 의사결정이다.&lt;/p&gt;
&lt;div class=&quot;se_editArea&quot;&gt;
&lt;div class=&quot;se_viewArea se_fs_T3 &quot;&gt;
&lt;div class=&quot;se_editView&quot;&gt;
&lt;div class=&quot;se_textView&quot;&gt;
&lt;div class=&quot;__se_code_view se_textarea language-javascript&quot;&gt;
&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-javascript&quot; data-lang=&quot;javascript&quot;&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;k&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;combineReducers&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;redux&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;routerReducer&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;react-router-redux&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;toast&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;./toast/reducer.js&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;toolbar&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;./toolbar/reducer.js&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;modalDialog&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;./modal-dialog/reducer.js&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;components&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;./components/reducer.js&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;suggest&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;./suggest/reducer.js&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;metaData&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;./meta-data/reducer.js&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;default&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;combineReducers&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;({&lt;/span&gt;
    &lt;span class=&quot;nx&quot;&gt;toast&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;nx&quot;&gt;toolbar&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;nx&quot;&gt;modalDialog&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;nx&quot;&gt;components&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;nx&quot;&gt;suggest&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;nx&quot;&gt;metaData&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;routing&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;routerReducer&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;});&lt;/span&gt;
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p class=&quot;se_textarea&quot;&gt;'너무 깊은 트리 구조'를 만들지 말라는 걸 중첩 JSON 구조로 데이터를 정의하지 말라는 걸로 오해하면 곤란하다. 단지 리듀서를 지나치게 중첩하지 말라는 소리다. 리듀서가 난립하면 결합한 상태 트리의 모습을 예측하기 어려워진다.&lt;/p&gt;
&lt;p class=&quot;se_textarea&quot;&gt;그렇다면 상태 트리는 어떠한 기준으로 설계를 하는 것이 좋을까? 처음에는 UI를 기준으로 상태 트리를 구성하는 걸 생각해봤다. 헤더가 있고, 푸터가 있고. 메뉴별로 상태 트리를 만들면 어떨까? 하지만 곧 생각을 접었다.&lt;/p&gt;
&lt;div class=&quot;se_editArea&quot;&gt;
&lt;div class=&quot;se_viewArea se_fs_T3 &quot;&gt;
&lt;div class=&quot;se_editView&quot;&gt;
&lt;div class=&quot;se_textView&quot;&gt;
&lt;div class=&quot;__se_code_view se_textarea language-javascript&quot;&gt;
&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-javascript&quot; data-lang=&quot;javascript&quot;&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1
2
3
4
5
6
&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;nl&quot;&gt;header&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{},&lt;/span&gt;
    &lt;span class=&quot;nb&quot;&gt;navigator&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{},&lt;/span&gt;
    &lt;span class=&quot;nx&quot;&gt;contents&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{},&lt;/span&gt;
    &lt;span class=&quot;nx&quot;&gt;footer&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p class=&quot;se_textarea&quot;&gt;UI 단위로 상태 트리를 구성하면 UI와 리듀서가 강하게 묶인다. 어떤 상태를 상태 트리에 추가하면, 그 상태를 관리하는 리듀서가 있어야 한다. 반대 역시 마찬가지다. 결국 UI와 리듀서를 함께 끌고 다녀야 한다. UI는 변경이 잦은 부분이라 불안정한 UI와 리듀서를 엮어 버리면 리듀서 역시 불안정해진다.&lt;/p&gt;
&lt;p class=&quot;se_textarea&quot;&gt;결국 팀원들과 긴 논의를 거친 끝에, 기능을 기준으로 상태 트리를 설계하기로 했다. 기능 단위로 리듀서를 설계하면 UI와 리듀서의 관계를 약하게 만들 수 있고, UI의 변경이 미치는 파급효과를 줄일 수 있다. 상태 트리를 보고 시스템이 제공하는 기능을 유추할 수 있다는 점은 기능 단위로 상태 트리를 설계했을 때 얻을 수 있는 또 하나의 장점이다. 아래의 상태 트리 예제를 보면 도구막대와 컴포넌트 목록 관련 기능을 제공한다는 걸 유추할 수 있다.&lt;/p&gt;
&lt;div class=&quot;se_editArea&quot;&gt;
&lt;div class=&quot;se_viewArea se_fs_T3 &quot;&gt;
&lt;div class=&quot;se_editView&quot;&gt;
&lt;div class=&quot;se_textView&quot;&gt;
&lt;div class=&quot;__se_code_view se_textarea language-javascript&quot;&gt;
&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-javascript&quot; data-lang=&quot;javascript&quot;&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;nl&quot;&gt;toolbar&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;nl&quot;&gt;isShown&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;false&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;
    &lt;span class=&quot;nx&quot;&gt;components&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;nl&quot;&gt;order&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;comp_1&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;comp_2&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
        &lt;span class=&quot;nx&quot;&gt;compMap&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;:{&lt;/span&gt;
            &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;comp_1&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
                &lt;span class=&quot;nl&quot;&gt;ui&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{},&lt;/span&gt;
                &lt;span class=&quot;nx&quot;&gt;doc&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{}&lt;/span&gt;
            &lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;
            &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;comp_2&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
                &lt;span class=&quot;nl&quot;&gt;ui&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{},&lt;/span&gt;
                &lt;span class=&quot;nx&quot;&gt;doc&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{}&lt;/span&gt;
            &lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;
    &lt;span class=&quot;nx&quot;&gt;metaData&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;nl&quot;&gt;document&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{}&lt;/span&gt;
        &lt;span class=&quot;nl&quot;&gt;release&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{}&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p class=&quot;se_textarea&quot;&gt;상태 트리를 기능 단위로 정리했다면, 액션은 사용자의 행위를 기준으로 정리한다. 사용자가 수행하는 하나의 동작은 다수의 기능과 결합할 수 있다. 따라서 너무 당연한 이야기지만 액션과 리듀서의 관계는 &lt;b&gt;1:N&lt;/b&gt;이다.&lt;/p&gt;
&lt;h2&gt;리덕스를 지탱하는 두 가지 리액트 컴포넌트&lt;/h2&gt;
&lt;p class=&quot;se_textarea&quot;&gt;리덕스를 접할 때 애플리케이션의 '모든 상태'를 리듀서에서 관리하고, 컴포넌트 자체는 무상태로 만들어야 한다고 생각하는 경우를 가끔 본다. 결론부터 이야기하자면 이는 오해다. 리덕스는 모든 상태를 리듀서, 즉 스토어에서 관리할 것을 강제하지 않는다. 컴포넌트는 지역 상태를, 리듀서는 전역 상태를 관리할 수 있고 그래야  효율적이고 유연한 애플리케이션을 만들 수 있다.&lt;/p&gt;
&lt;p class=&quot;se_textarea&quot;&gt;그렇다면 무엇이 지역 상태고, 무엇이 전역 상태일까? 이를 이해하려면 우선 표현 컴포넌트와 컨테이너 컴포넌트를 이해해야 한다. &lt;a class=&quot;se_link&quot; href=&quot;http://redux.js.org/docs/basics/UsageWithReact.html&quot; target=&quot;_blank&quot;&gt;리덕스 공식 문서&lt;/a&gt;는 리액트 컴포넌트를 두 종류로 구분한다.&lt;/p&gt;
&lt;ol&gt;
&lt;li class=&quot;se_textarea&quot;&gt;표현(Presentational) 컴포넌트&lt;/li&gt;
&lt;li class=&quot;se_textarea&quot;&gt;컨테이너(Container) 컴포넌트&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;이는 전혀 새로운 개념은 아니며 용어는 조금 다르지만 플럭스 유틸즈도 이런 방식으로 컴포넌트를 구분하고 있다. 이 둘에 대한 자세한 설명은 리덕스의 창시자인 Dan Abramov가 쓴 &lt;a class=&quot;se_link&quot; href=&quot;https://medium.com/@dan_abramov/smart-and-dumb-components-7ca2f9a7c7d0#.5k8kybm16&quot; target=&quot;_blank&quot;&gt;Presentational and Container Components&lt;/a&gt;에 잘 나와있다.  예전 리덕스 공식 문서에서는 smart component, dumb component라는 이름으로 이 둘의 기준을 모호하게 설명하고 있었으나, 최신 문서에서는 presentational, container라는 명칭을 사용하고 설명을 보완하여 이 둘의 경계를 분명하게 구분하고 있다.&lt;/p&gt;
&lt;p&gt;표현 컴포넌는 리덕스 시스템과 별개인 컴포넌트로 화면에 컴포넌트를 어떻게 렌더링 할지를 결정하고, 사용자의 요청을 이벤트로 받아 상위 컴포넌트로 전달하는 역할만을 수행한다. 아래의 코드는 표현 컴포넌트의 예다.&lt;/p&gt;
&lt;div class=&quot;se_editArea&quot;&gt;
&lt;div class=&quot;se_viewArea se_fs_T3 &quot;&gt;
&lt;div class=&quot;se_editView&quot;&gt;
&lt;div class=&quot;se_textView&quot;&gt;
&lt;div class=&quot;__se_code_view se_textarea language-javascript&quot;&gt;
&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-jsx&quot; data-lang=&quot;jsx&quot;&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;k&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;React&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;PropTypes&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;react&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;

&lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;Link&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;({&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;active&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;children&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;onClick&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;})&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;active&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;span&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;children&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;lt;/&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;span&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;gt;&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

  &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;a&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;href=&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;#&quot;&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;onClick=&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;e&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;nx&quot;&gt;e&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;preventDefault&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
        &lt;span class=&quot;nx&quot;&gt;onClick&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
      &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;gt;&lt;/span&gt;

      &lt;span class=&quot;si&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;children&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;&amp;lt;/&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;a&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;gt;&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;nx&quot;&gt;Link&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;propTypes&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;active&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;PropTypes&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;bool&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;isRequired&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;children&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;PropTypes&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;node&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;isRequired&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;onClick&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;PropTypes&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;func&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;isRequired&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;default&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;Link&lt;/span&gt;
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p class=&quot;se_textarea&quot;&gt;이와 달리 컨테이너 컴포넌트는 표현 컴포넌트와 리덕스 시스템 사이의 연결 고리로 하위 컴포넌트에서 올라오는 요청을 해석하여 시스템의 어느 쪽으로 요청을 전달(mapDispatchToProps) 할 것인지를 결정하고, 전체 시스템의 상태 트리를 스토어로부터 넘겨받아 하위 컴포넌트로 전달(mapStateToProps) 하는 책임을 수행한다.&lt;/p&gt;
&lt;div class=&quot;se_editArea&quot;&gt;
&lt;div class=&quot;se_viewArea se_fs_T3 &quot;&gt;
&lt;div class=&quot;se_editView&quot;&gt;
&lt;div class=&quot;se_textView&quot;&gt;
&lt;div class=&quot;__se_code_view se_textarea language-javascript&quot;&gt;
&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-javascript&quot; data-lang=&quot;javascript&quot;&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;k&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;connect&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;react-redux&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;setVisibilityFilter&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;../actions&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;Link&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;../components/Link&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;

&lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;mapStateToProps&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;state&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;ownProps&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;active&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;ownProps&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;filter&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;===&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;state&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;visibilityFilter&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;mapDispatchToProps&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;dispatch&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;ownProps&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;onClick&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;nx&quot;&gt;dispatch&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;setVisibilityFilter&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;ownProps&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;filter&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;FilterLink&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;connect&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
  &lt;span class=&quot;nx&quot;&gt;mapStateToProps&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;nx&quot;&gt;mapDispatchToProps&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;)(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;Link&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p class=&quot;se_textarea&quot;&gt;결국 컨테이너라는 존재에 의해 컨테이너가 아닌 모든 컴포넌트는 리덕스에 얽매이지 않는, 독립적인 개체로서 자율성을 가질 수 있는 셈이다. 필요한 지점에서 컨테이너를 이용해 컴포넌트를 리덕스 시스템과 결합하면 된다.&lt;/p&gt;
&lt;p class=&quot;se_textarea&quot;&gt;리덕스 공식 문서의 예제에 있는 표현 컴포넌트는 모두 무상태 컴포넌트(Stateless Component)다. 하지만 이는 예제가 너무 단순하여 내부에서 관리할 상태가 없기 때문이지, 모든 표현 컴포넌트가 무상태 컴포넌트여야 하는 것은 아니다.  컴포넌트를 드래그 앤 드롭할 때 현재 좌표 값이나 현재 스크롤 좌표 추적같이 매우 빈번한 연산을 요구하는 인터랙션 처리에 필요한 상태는 리듀서에서 관리할 경우 엄청난 비효율을 초래한다. 최대한 연산 수행 시간을 줄여야 하는 상황에서 상태를 변경할 때마다 아래와 같은 과정을 거쳐야 한다는 건 생각만 해도 아찔한 일이다. 결국 컴포넌트와 리듀서, 모두에게 각자의 상태를 들고 있어야 할 나름의 사정이 있다.&lt;/p&gt;
&lt;p class=&quot;se_textarea&quot;&gt;&lt;b&gt;&quot;action -&amp;gt; dispatch -&amp;gt; store -&amp;gt; reducer -&amp;gt; store -&amp;gt; compoent&quot;&lt;/b&gt;&lt;/p&gt;
&lt;p class=&quot;se_textarea&quot;&gt;그렇다면 어떤 상태는 컴포넌트에 위치하고 어떤 상태는 리듀서에 위치해야 하는 걸까? 결론적으로 전역 상태는 리듀서가, 지역 상태는 개별 컴포넌트가 관리한다. 말은 쉽다. 하나씩 풀어보자.&lt;/p&gt;
&lt;h2&gt;전역 상태와 지역 상태, 무엇이 다를까?&lt;/h2&gt;
&lt;p class=&quot;se_textarea&quot;&gt;전역 상태는 다음과 같이 정의할 수 있다.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p class=&quot;se_textarea&quot;&gt;&quot;영속성을 가져야 하는 도메인 데이터, 또는 서로 다른 컨테이너나 컴포넌트 간에 공유해야 하는 UI의 상태&quot;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p class=&quot;se_textarea&quot;&gt;에디터를 예로 들자면, 사용자가 작성한 문서에 대한 정보는 도메인 데이터로 영속성을 가져야 한다. 애플리케이션의 생애 주기 동안 관리하고, 시스템을 종료했다가 다음에 다시 데이터를 불러와서 동일한 상태를 재현할 수 있어야 한다. 이러한 상태는 신뢰할 수 있는 단일한 장소에서, 일관성을 가지고 관리해야 한다. 리덕스에서는 리듀서가 바로 그 지점이다.&lt;/p&gt;
&lt;p class=&quot;se_textarea&quot;&gt;그리고 도메인이 아닌 개별 UI의 상태지만 리듀서에서 관리를 해야 하는 경우가 있다. 서로 다른 컨테이너 간에 공유해야 하는 상태가 그렇다. 어떤 컴포넌트에 포커스가 들어오면 직전에 포커스를 가지고 있던 컴포넌트에서 포커스를 제거해야 하는 경우를 생각해보자. 포커스는 도메인 데이터가 아닌, UI의 상태일 뿐이다. 하지만 단 하나의 컴포넌트만 포커스를 가질 수 있다는 규칙상, 각 컴포넌트가 서로의 상태를 공유해야 한다. 이럴 때 포커스를 가지고 있는 컴포넌트에 대한 정보를 리듀서에서 상태로 관리할 수 있다.&lt;/p&gt;
&lt;p class=&quot;se_textarea&quot;&gt;자, 이제 지역 상태란 무엇일까? 지역 상태란 이렇게 정의할 수 있다.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p class=&quot;se_textarea&quot;&gt;&quot;컴포넌트가 시스템과 상관없이 독립성을 갖고 표현 로직을 처리하는 데 필요한, 컴포넌트 내부에 캡슐화할 수 있는 상태&quot;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p class=&quot;se_textarea&quot;&gt;컴포넌트의 모든 상태를 리듀서에서 관리하면 컴포넌트와 리듀서가 강하게 결합해 종속성이 생긴다. 컴포넌트가 어떤 인터랙션을 처리하려면 리듀서를 통해야만 하는데, 이는 성능 문제와도 연관이 있다. 앞에서 이야기했듯이 컴포넌트를 드래그 앤 드롭할 때, 현재 좌표 값 처리와 같이 매우 빈번한 연산을 수행하는 데 필요한 상태를 리듀서에서 관리하면 상당한 비효율이 발생한다. 따라서 이런 상태는 컴포넌트 내부에 캡슐화하여 컴포넌트가 자율적으로 상태를 처리할 수 있게 두는 게 좋다.&lt;/p&gt;
&lt;p class=&quot;se_textarea&quot;&gt;실제 제품을 개발할 때는 이것을 전역 상태로 둘지, 지역 상태로 둘지 결정하기 모호한 경우를 꽤 자주 만난다. 특히나 요구 사항이 완전치 않은 개발 초기에 이런 상황을 자주 접한다. 이럴 때는 우선 지역 상태로 분류하는 게 좋다. 전역 상태를 처리하는 과정이 지역 상태를 처리하는 과정 보다 번거롭고, 지역 상태가 전역 상태보다 외부와의 접점이 적기 때문에 나중에 상태의 성격을 변경할 때 수정 비용이 더 적게 들어간다. 그리고 지역 상태를 중심으로 자율성을 갖는 컴포넌트가 더 유연하다. 물론 유연하다는 것은 구현에 그만큼의 비용이 더 들어간다는 뜻이기도 하다. 따라서 상황에 따라 적절히 판단해야 하며 이는 개발자의 몫이다.&lt;/p&gt;
&lt;h2&gt;리액트와 리덕스의 결합이란...&lt;/h2&gt;
&lt;p class=&quot;se_textarea&quot;&gt;컨테이너 컴포넌트는 리액트 컴포넌트에서 발생한 이벤트를 해석하여 리듀서로 전달하고, 스토어가 전파하는 상태 변경 이벤트를 받아서 변경한 상태 값을 컴포넌트에 전달하는 역할을 수행한다. 한 마디로, 리덕스 시스템과 리액트 컴포넌트를 결합하는 얇은 레이어다. 아래는 리덕스 공식 문서에 있는 컨테이너 컴포넌트를 설명하는 예제 중 하나다.&lt;/p&gt;
&lt;div class=&quot;se_editArea&quot;&gt;
&lt;div class=&quot;se_viewArea se_fs_T3 &quot;&gt;
&lt;div class=&quot;se_editView&quot;&gt;
&lt;div class=&quot;se_textView&quot;&gt;
&lt;div class=&quot;__se_code_view se_textarea language-javascript&quot;&gt;
&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-javascript&quot; data-lang=&quot;javascript&quot;&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;k&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;connect&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;react-redux&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;setVisibilityFilter&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;../actions&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;Link&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;../components/Link&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;

&lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;mapStateToProps&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;state&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;ownProps&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;amp;&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;gt&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;active&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;ownProps&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;filter&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;===&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;state&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;visibilityFilter&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;mapDispatchToProps&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;dispatch&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;ownProps&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;amp;&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;gt&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;onClick&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;nx&quot;&gt;dispatch&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;setVisibilityFilter&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;ownProps&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;filter&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;FilterLink&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;connect&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
  &lt;span class=&quot;nx&quot;&gt;mapStateToProps&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;nx&quot;&gt;mapDispatchToProps&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;)(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;Link&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;default&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;FilterLink&lt;/span&gt;
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p class=&quot;se_textarea&quot;&gt;리덕스는 connect라는 함수를 제공한다. connect는 이름을 보고 알 수 있듯이, 리액트 컴포넌트와 리덕스 시스템을 결합하는 역할을 수행한다. connect는 첫 번째 호출에서 두 개의 함수를 인자로 받는다.&lt;/p&gt;
&lt;div class=&quot;se_editArea&quot;&gt;
&lt;div class=&quot;se_viewArea se_fs_T3 &quot;&gt;
&lt;div class=&quot;se_editView&quot;&gt;
&lt;div class=&quot;se_textView&quot;&gt;
&lt;div class=&quot;__se_code_view se_textarea language-javascript&quot;&gt;
&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-javascript&quot; data-lang=&quot;javascript&quot;&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1
2
3
4
5
&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;mapStateToProps&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;state&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;ownProps&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;active&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;ownProps&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;filter&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;===&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;state&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;visibilityFilter&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p class=&quot;se_textarea&quot;&gt;mapStateToProps와 mapDispatchToProps다. 함수 이름이 매우 직관적이라, 보기만 해도 어떤 일을 하는 녀석인지 바로 알 수 있다.&lt;/p&gt;
&lt;p class=&quot;se_textarea&quot;&gt;mapStateToProps를 이용하면 스토어에서 전달받은 상태 트리를 컴포넌트에 전달하기 전에 원하는 형태로 가공할 수 있다. 통역가 역할을 수행하는 셈인데, 이 함수 덕에 컴포넌트와 리듀서, 어느 쪽의 상태 구조가 바뀌어도 변경이 미치는 파급을 최소화할 수 있다.&lt;/p&gt;
&lt;div class=&quot;se_editArea&quot;&gt;
&lt;div class=&quot;se_viewArea se_fs_T3 &quot;&gt;
&lt;div class=&quot;se_editView&quot;&gt;
&lt;div class=&quot;se_textView&quot;&gt;
&lt;div class=&quot;__se_code_view se_textarea language-javascript&quot;&gt;
&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-javascript&quot; data-lang=&quot;javascript&quot;&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1
2
3
4
5
&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;mapStateToProps&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;state&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;ownProps&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;active&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;ownProps&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;filter&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;===&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;state&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;visibilityFilter&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p class=&quot;se_textarea&quot;&gt;리덕스처럼 이벤트 통지를 직접 사용자가 제어하지 않는 단일 스토어 방식은 컴포넌트가 구독할 스토어의 이벤트를 직접 지정할 수 없다는 단점을 가지고 있다. 이로 인해 컴포넌트는 자신의 활동과 상관없는 이벤트를 구독해야 한다. 결국 하위 컴포넌트에 불필요한 조정(reconcilation) 프로세스를 발생시켜 이 결과로 애플리케이션의 성능을 저하시킬 가능성이 높아진다.&lt;/p&gt;
&lt;p class=&quot;se_textarea&quot;&gt;이 점이 마음에 걸린다면 &lt;a class=&quot;se_link&quot; href=&quot;https://github.com/reactjs/reselect&quot; target=&quot;_blank&quot;&gt;reseletor&lt;/a&gt; 같은 모듈을 이용하면 좋다. 이전 상태를 캐시하고 있다가 새로운 상태가 들어오면 변경 여부를 확인하여 변경이 있을 때만 하위 컴포넌트로 상태를 전파하는 문지기 역할을 하는 모듈이다. 리듀서의 상태를 불변(Immutable)하게 관리하면 이 지점에서 성능상 이점을 얻을 수 있다.&lt;/p&gt;
&lt;div class=&quot;se_editArea&quot;&gt;
&lt;div class=&quot;se_viewArea se_fs_T3 &quot;&gt;
&lt;div class=&quot;se_editView&quot;&gt;
&lt;div class=&quot;se_textView&quot;&gt;
&lt;div class=&quot;__se_code_view se_textarea language-javascript&quot;&gt;
&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-javascript&quot; data-lang=&quot;javascript&quot;&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;k&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;createSelector&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;reselect&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;

&lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;getVisibilityFilter&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;state&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;props&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;amp;&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;gt&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;nx&quot;&gt;state&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;todoLists&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;props&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;listId&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;].&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;visibilityFilter&lt;/span&gt;

&lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;getTodos&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;state&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;props&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;amp;&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;gt&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;nx&quot;&gt;state&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;todoLists&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;props&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;listId&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;].&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;todos&lt;/span&gt;

&lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;makeGetVisibleTodos&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;amp;&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;gt&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;createSelector&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;getVisibilityFilter&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;getTodos&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;],&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;visibilityFilter&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;todos&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;amp;&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;gt&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;k&quot;&gt;switch&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;visibilityFilter&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;case&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;SHOW_COMPLETED&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
          &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;todos&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;filter&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;todo&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;amp;&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;gt&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;todo&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;completed&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;case&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;SHOW_ACTIVE&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
          &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;todos&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;filter&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;todo&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;amp;&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;gt&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;!&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;todo&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;completed&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;default&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
          &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;todos&lt;/span&gt;
      &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;makeMapStateToProps&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;amp;&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;gt&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;getVisibleTodos&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;makeGetVisibleTodos&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
  &lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;mapStateToProps&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;state&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;props&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;amp;&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;gt&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;todos&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;getVisibleTodos&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;state&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;props&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;mapStateToProps&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p class=&quot;se_textarea&quot;&gt;mapStateToProps가 스토어에서 리액트 컴포넌트로 들어가는 통로라면, mapDispatchToProps는 반대로 리액트 컴포넌트에서 스토어로 들어가는 통로다. mapDispatchToProps는 리액트 컴포넌트에서 발생한 이벤트를 액션과 결합하여 스토어로 전달한다. 스토어로 전달한 액션은 리듀서로 넘어가 전역 상태를 변경한다.&lt;/p&gt;
&lt;div class=&quot;se_editArea&quot;&gt;
&lt;div class=&quot;se_viewArea se_fs_T3 &quot;&gt;
&lt;div class=&quot;se_editView&quot;&gt;
&lt;div class=&quot;se_textView&quot;&gt;
&lt;div class=&quot;__se_code_view se_textarea language-javascript&quot;&gt;
&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-javascript&quot; data-lang=&quot;javascript&quot;&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1
2
3
4
5
6
7
&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;mapDispatchToProps&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;dispatch&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;ownProps&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;amp;&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;gt&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;onClick&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;amp;&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;gt&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;nx&quot;&gt;dispatch&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;setVisibilityFilter&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;ownProps&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;filter&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;));&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p class=&quot;se_textarea&quot;&gt;리덕스는 mapDispatchToProps가 반환하는 객체를 하위 컴포넌트에 props로 전달한다. 결국 이는 애플리케이션의 요구 사항을 하위 컴포넌트에 콜백으로 전달하는 셈이다. 구글에서 예제를 찾아봤더니 리액트와 리덕스를 결합하는 방식을 크게 두 가지로 압축할 수 있었다.&lt;/p&gt;
&lt;ol&gt;
&lt;li class=&quot;se_textarea&quot;&gt;이벤트(onClick, onChange, onUpdate...) 기반으로 결합&lt;/li&gt;
&lt;li class=&quot;se_textarea&quot;&gt;요구 사항을 수행하는 메서드(toggleTodo, updateVideo...)를 전달하여 결합&lt;/li&gt;
&lt;/ol&gt;
&lt;p class=&quot;se_textarea&quot;&gt;1번은 컨테이너에서 react 컴포넌트로 이벤트 콜백 함수를 내려주면, 컴포넌트는 내부에서 발생하는 사건을 이벤트로 컨테이너에게 알려준다. &lt;b&gt;컨테이너가 발생한 이벤트를 해석하여 수행할 동작을 결정&lt;/b&gt;하는방식이다.&lt;/p&gt;
&lt;div class=&quot;se_editArea&quot;&gt;
&lt;div class=&quot;se_viewArea se_fs_T3 &quot;&gt;
&lt;div class=&quot;se_editView&quot;&gt;
&lt;div class=&quot;se_textView&quot;&gt;
&lt;div class=&quot;__se_code_view se_textarea language-javascript&quot;&gt;
&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-javascript&quot; data-lang=&quot;javascript&quot;&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1
2
3
4
5
6
7
8
9
10
&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;mapDispatchToProps&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;dispatch&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;ownProps&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;amp;&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;gt&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;onClickDisplay&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;amp;&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;gt&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;nx&quot;&gt;dispatch&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;setVisibilityFilter&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;ownProps&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;filter&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;));&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;onClickTodo&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;id&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;amp;&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;gt&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;nx&quot;&gt;dispatch&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;toggleTodo&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;id&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;));&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p class=&quot;se_textarea&quot;&gt;2번은 컨테이너에서 dispatch와 액션을 결합한 행위를 조합하여 props로 내려주면, &lt;b&gt;리액트 컴포넌트가&lt;/b&gt;&lt;b&gt;어떤 행위를 어떤 시점에 실행할지 알아서 결정&lt;/b&gt;하는 방식이다.&lt;/p&gt;
&lt;div class=&quot;se_editArea&quot;&gt;
&lt;div class=&quot;se_viewArea se_fs_T3 &quot;&gt;
&lt;div class=&quot;se_editView&quot;&gt;
&lt;div class=&quot;se_textView&quot;&gt;
&lt;div class=&quot;__se_code_view se_textarea language-javascript&quot;&gt;
&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-javascript&quot; data-lang=&quot;javascript&quot;&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1
2
3
4
5
6
7
8
9
&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;mapDispatchToProps&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;dispatch&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;ownProps&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;amp;&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;gt&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;setVisibilityFilter&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;amp;&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;gt&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;nx&quot;&gt;dispatch&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;setVisibilityFilter&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;ownProps&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;filter&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;));&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;toggleTodo&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;amp;&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;gt&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;nx&quot;&gt;dispatch&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;toggleTodo&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;id&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;));&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p class=&quot;se_textarea&quot;&gt;위의 예제 코드를 보면 무슨 말장난인가 싶다. 단순하게 함수 이름 짓는 방식의 차이 정도로 보이기 때문이다. 맞다. 지금 정도의 코드라면 큰 문제가 없고 이 둘 간의 차이는 무시할만하다.문제는 요구사항 변경이 일어났을 때다.&lt;/p&gt;
&lt;p class=&quot;se_textarea&quot;&gt;요구사항이 바뀌어서 할 일을 클릭하면 필터를 변경(setVisibilityFilter)하고 할 일을 토글(toggleTodo) 해야 한다고 가정해보자. 첫 번째 방식의 경우, 아래와 같이 코드를 수정하면 하위 컴포넌트에서는 특별히 다른 처리를 할 필요가 없다. 이벤트 기반으로 props를 구성해서 하위 컴포넌트와 컨테이너를 결합했기 때문에, 컨테이너가 UI에서 발생한 이벤트를 해석하여 적당한 대상에게 전달하는 책임을 지는 것이 자연스럽다.&lt;/p&gt;
&lt;div class=&quot;se_editArea&quot;&gt;
&lt;div class=&quot;se_viewArea se_fs_T3 &quot;&gt;
&lt;div class=&quot;se_editView&quot;&gt;
&lt;div class=&quot;se_textView&quot;&gt;
&lt;div class=&quot;__se_code_view se_textarea language-javascript&quot;&gt;
&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-javascript&quot; data-lang=&quot;javascript&quot;&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1
2
3
4
5
6
7
8
9
10
11
&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;mapDispatchToProps&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;dispatch&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;ownProps&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;amp;&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;gt&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;onClickDisplay&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;amp;&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;gt&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;nx&quot;&gt;dispatch&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;setVisibilityFilter&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;ownProps&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;filter&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;));&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;onClickTodo&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;id&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;amp;&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;gt&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;nx&quot;&gt;dispatch&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;toggleTodo&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;id&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;));&lt;/span&gt;
      &lt;span class=&quot;nx&quot;&gt;dispatch&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;setVisibilityFilter&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;ownProps&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;filter&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;));&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p class=&quot;se_textarea&quot;&gt;이와는 달리 두 번째 방식은 props가 행위를 의미하는 이름을 가지고 있다 보니, 아래의 코드처럼 컨테이너로부터 props로 전달받은 행위를 하위 컴포넌트 내부에서 조합하는 형태로 처리하는 유혹에 빠지기 쉽다. 이는 곧 사용자 인터랙션으로 발생한 이벤트를 해석하는 책임을 컴포넌트 내부에서 지는 모양새다.&lt;/p&gt;
&lt;div class=&quot;se_editArea&quot;&gt;
&lt;div class=&quot;se_viewArea se_fs_T3 &quot;&gt;
&lt;div class=&quot;se_editView&quot;&gt;
&lt;div class=&quot;se_textView&quot;&gt;
&lt;div class=&quot;__se_code_view se_textarea language-javascript&quot;&gt;
&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-javascript&quot; data-lang=&quot;javascript&quot;&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1
2
3
4
5
6
7
8
9
10
11
12
13
14
&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;TodoList&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;({&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;todos&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;setVisibilityFilter&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;toggleTodo&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;})&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;amp;&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;gt&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
  &lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;lt&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;ul&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;gt&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;todos&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;map&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;todo&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;amp;&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;gt&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
      &lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;lt&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;Todo&lt;/span&gt;
        &lt;span class=&quot;nx&quot;&gt;key&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;todo&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;id&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;{...&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;todo&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
        &lt;span class=&quot;nx&quot;&gt;onClick&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;amp;&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;gt&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
            &lt;span class=&quot;nx&quot;&gt;setVisibilityFilter&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
            &lt;span class=&quot;nx&quot;&gt;toggleTodo&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
      &lt;span class=&quot;sr&quot;&gt;/&amp;amp;gt&lt;/span&gt;&lt;span class=&quot;err&quot;&gt;;
&lt;/span&gt;    &lt;span class=&quot;p&quot;&gt;)}&lt;/span&gt;
  &lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;lt&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;&lt;span class=&quot;sr&quot;&gt;/ul&amp;amp;gt&lt;/span&gt;&lt;span class=&quot;err&quot;&gt;;
&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p class=&quot;se_textarea&quot;&gt;이제 컴포넌트가 시스템의 요구 사항과 강한 의존성을 갖게 돼 유연성이 떨어져 버렸다. 아직까지 심각한 수준이 아니지만 이런 게 하나둘 쌓이다 보면 걷잡을 수 없다. 물론 아래처럼 첫 번째 방식과 동일하게 해결할 수도 있으나, 함수나 변수의 이름은 알게 모르게 개발자의 생각에 큰 영향을 미친다. 참여 구성원이 많은 프로젝트일수록 코드에 작성자의 의도를 잘 담아내는 일이 중요하다.&lt;/p&gt;
&lt;div class=&quot;se_editArea&quot;&gt;
&lt;div class=&quot;se_viewArea se_fs_T3 &quot;&gt;
&lt;div class=&quot;se_editView&quot;&gt;
&lt;div class=&quot;se_textView&quot;&gt;
&lt;div class=&quot;__se_code_view se_textarea language-javascript&quot;&gt;
&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-javascript&quot; data-lang=&quot;javascript&quot;&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1
2
3
4
5
6
7
8
9
10
&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;mapDispatchToProps&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;dispatch&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;ownProps&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;amp;&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;gt&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;setVisibilityFilter&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;amp;&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;gt&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;nx&quot;&gt;dispatch&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;setVisibilityFilter&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;ownProps&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;filter&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;));&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;toggleTodo&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;amp;&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;gt&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;nx&quot;&gt;dispatch&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;toggleTodo&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;id&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;));&lt;/span&gt;
        &lt;span class=&quot;nx&quot;&gt;dispatch&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;setVisibilityFilter&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;ownProps&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;filter&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;));&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p class=&quot;se_textarea&quot;&gt;여기에서 이야기하고자 하는 핵심은, 어떤 방법을 사용하든 UI에서 발생하는 &lt;b&gt;인터랙션을 해석하여 어떤 액션과 결합할 것인지를 결정하는 책임은 컨테이너&lt;/b&gt;에 맡겨야 한다는 점이다. 그래야 리액트 컴포넌트와 리덕스 시스템의 결합도를 낮출 수 있다.&lt;/p&gt;
&lt;h2&gt;리덕스에 레이어 설계 더하기&lt;/h2&gt;
&lt;p class=&quot;se_textarea&quot;&gt;MVC 패턴을 설계에 도입해 본 사람이라면 '모델, 뷰, 컨트롤러'에 애플리케이션의 모든 관심사를 담을 수 없다는 사실을 잘 알 것이다. 큰 시스템에서 MVC의 각 구성요소는 전체 애플리케이션을 구성하는 레이어 중 어딘가에 위치하는 좀 더 작은 단위의 레이어 또는 객체일 뿐, 그 자체가 전체 설계를 의미하지 않는다. 리덕스도 마찬가지다. 플럭스 아키텍처에 기반을 둔 리덕스가 안내하는 구성요소들에 모든 책임을 담을 수 있을까? 그렇지 않다. 리듀서의 본질을 정리했고, 상태 트리 설계 규칙을 세웠으며, 지역 상태와 전역 상태 구분 규칙을 만들었지만 여전히 리덕스 안에 담을 수 없는 모호한 책임이 있다.&lt;/p&gt;
&lt;p&gt;비동기 XHR 요청을 예로 들어보자. 리듀서는 XHR 요청이 위치할 자리가 아니라는 건 분명하다. 그렇다면 어디에 있어야 할까? 컨테이너? 컴포넌트? 액션? 미들웨어? 외부에서 데이터를 가져와 개발 중인 시스템이 해석할 수 있는 포맷으로 데이터를 변환하는 전처리는 어디에서 해야 할까? 여러 액션을 하나의  요청 단위로 묶어야 한다면, 이런 처리는 어디에서 하는 게 적절할까?&lt;/p&gt;
&lt;p&gt;개발을 진행하면서 새로운 책임이 계속 등장하는데, 그때마다 이 녀석들을 어디에 둬야 할지를 두고 긴 논쟁이 벌어졌다. 우리가 얻은 결론이 하나 있다면, 어디에 둬도 장단점이 있고 어색하지 않다는 것이다. 다만 비슷한 책임을 수행하는 녀석들을 서로 근접한 곳에 일관성 있게 모아둘 규칙이 필요했다.&lt;/p&gt;
&lt;p&gt;그래서 레이어 설계를 정리했다. 도메인 주도 설계(Domain Driven Design)를 참고했지만 어디까지나 지침으로 받아들였다. 레거시와 리덕스가 놓인 상황을 고려해서 우리 식의 해석을 보탰다. 레이어를 합의하고, 컨테이너, 액션, 리듀서의 역할을 정리했다. 이제 새로운 책임이 등장하면 앞에서 정한 규칙에 맞춰 소속을 결정한다. 다만, 그 조차도 모호할 때가 종종 있다는 건 함정이지만, 큰 틀은 마련한 셈이다.&lt;/p&gt;
&lt;div class=&quot;se_editArea&quot;&gt;
&lt;div class=&quot;se_viewArea se_ff_nanumgothic se_fs_T3 se_align-left&quot;&gt;
&lt;div class=&quot;se_editView&quot;&gt;
&lt;div class=&quot;se_textView&quot;&gt;
&lt;p&gt;&lt;a href=&quot;/assets/images/legacy/2016/07/SE3-Mobile-Layer-Archictecture-Untitled-Page.png&quot;&gt;&lt;img class=&quot;aligncenter size-large wp-image-1960&quot; src=&quot;/assets/images/legacy/SE3-Mobile-Layer-Archictecture-Untitled-Page-1024x1016.png&quot; alt=&quot;SE3-Mobile Layer Archictecture - Untitled Page&quot; /&gt;&lt;/a&gt;각 레이어의 정체성은 아래와 같이 정리했다.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p class=&quot;se_textarea&quot;&gt;&lt;b&gt;UI 레이어&lt;/b&gt;&lt;/p&gt;
&lt;p class=&quot;se_textarea&quot;&gt;&lt;span style=&quot;line-height: 1.5;&quot;&gt;UI 레이어는 사용자에게 정보를 보여준다. 지역 상태와 관련한 사용자의 요청을 해석하여 상위 컴포넌트 혹은 컨테이너로 전달한다. 컨테이너는 UI를 리덕스 시스템과 연결하는 연결 고리 역할을 한다.&lt;/span&gt;&lt;/p&gt;
&lt;p class=&quot;se_textarea&quot;&gt;&lt;b&gt;애플리케이션 레이어&lt;/b&gt;&lt;/p&gt;
&lt;p class=&quot;se_textarea&quot;&gt;&lt;span style=&quot;line-height: 1.5;&quot;&gt;애플리케이션의 활동을 조율하는 얇은 레이어 UI 레이어에서 넘어온 사용자 요청을 해석하여 적절한 레이어로 전달한다. 어떤 상태도 직접 보관하거나 관리하지 않는다. 시스템에 사이드 이펙트를 만드는 외부와의 커뮤니케이션 요청을 처리하는 로직은 애플리케이션 레이어에 위임한다. 사용자의 요청을 해석하는 로직이 복잡하거나, 외부에서 넘겨받은 데이터를 가공해야 하는 등의 복잡한 연산을 수행해야 한다면, 관련 로직을 캡슐화하여 서비스 레이어에 위임할 수 있다.&lt;/span&gt;&lt;/p&gt;
&lt;p class=&quot;se_textarea&quot;&gt;&lt;b&gt;서비스 레이어&lt;/b&gt;&lt;/p&gt;
&lt;p class=&quot;se_textarea&quot;&gt;&lt;span style=&quot;line-height: 1.5;&quot;&gt;XHR을 이용한 외부 데이터 요청, 외부 데이터 변환 같은 애플리케이션 레이어의 임무 수행을 지원하는 모듈이 위치한다. 리듀서에서 사이드 이펙트를 불러오는 외부 데이터 요청을 금지하는 리덕스의 특성상, 외부의 리소스를 요청하는 처리를 이 레이어에서 담당한다.&lt;/span&gt;&lt;/p&gt;
&lt;p class=&quot;se_textarea&quot;&gt;&lt;b&gt;도메인 레이어&lt;/b&gt;&lt;/p&gt;
&lt;p class=&quot;se_textarea&quot;&gt;&lt;span style=&quot;line-height: 1.5;&quot;&gt;도메인 정보를 가지고 있으며, 생성, 변경, 삭제 등을 담당한다. 전역 상태를 관리하고 처리하며, 처리한 결과를 통지한다. 업무 규칙을 관리하며 전역 상태를 처리할 때 적절한 업무 규칙을 적용한다. 리덕스의 특성상 영속성과 관련한 책임은 도메인 레이어에서 수행하지 않는다.&lt;/span&gt;&lt;/p&gt;
&lt;p class=&quot;se_textarea&quot;&gt;&lt;b&gt;인프라스트럭쳐 레이어&lt;/b&gt;&lt;/p&gt;
&lt;p class=&quot;se_textarea&quot;&gt;&lt;span style=&quot;line-height: 1.5;&quot;&gt;다른 레이어 모두를 지원하는 라이브러리로 동작한다. 레이어 간의 통신을 제공하고 전역 상태의 영속성을 책임진다. 사용자 인터페이스 레이어에서 사용하는 내/외부 라이브러리를 포함한다.&lt;/span&gt;&lt;/p&gt;
&lt;h2&gt;리듀서와 액션, 단위 테스트 디자인&lt;/h2&gt;
&lt;p class=&quot;se_textarea&quot;&gt;리액트를 이용할 때 가장 마음에 들었던 점은 테스트가 간결해진다는 부분이다. 다만 이전 플럭스 아키텍처를 이용할 때는 스토어를 테스트하는 코드가 좀 복잡해서 아쉬웠었는데 리덕스는 리듀서로 인해 이 부분에 대한 테스트도 쉬운 편이다. 하지만 두 가지 좀 난해한 부분이 있었다. 아래 내용은 이 부분에 대한 고민의 기록이다.&lt;/p&gt;
&lt;p class=&quot;se_textarea&quot;&gt;처음에는 리듀서와 액션의 테스트를 따로 작성했었다. 하지만 작성하다 보니 뭔가 비효율적이라는 생각을 지울 수가 없었는데 그때 고민했던 포인트는 다음과 같다.&lt;/p&gt;
&lt;ul&gt;
&lt;li class=&quot;se_textarea&quot;&gt;액션의 행위는 너무 단순하여 테스트로 얻을 수 있는 이점이 매우 적다(특히 순수한 액션)&lt;/li&gt;
&lt;li class=&quot;se_textarea&quot;&gt;액션은 결국 리듀서와 결합해야 하고, 액션이 반환하는 값 자체보다 리듀서가 액션이 전달한 요청을 제대로 처리하고 있는지가 더 중요하다.&lt;/li&gt;
&lt;li class=&quot;se_textarea&quot;&gt;리듀서의 행위를 테스트하려면 어쨌든 액션 만드는 페이로드가 필요하다. 더미로 만들 수 있지만 이 경우 실제 액션이 생성하는 페이로드와 더미 페이로드 간의 동기화 문제가 생겨 테스트를 취약(fragile) 하게 만든다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;그래서 리듀서와 액션의 테스트를 하나로 통합했다. 리듀서를 테스트하면 액션은 자연스레 검증된다.&lt;/p&gt;
&lt;div class=&quot;se_editArea&quot;&gt;
&lt;div class=&quot;se_viewArea se_fs_T3 &quot;&gt;
&lt;div class=&quot;se_editView&quot;&gt;
&lt;div class=&quot;se_textView&quot;&gt;
&lt;div class=&quot;__se_code_view se_textarea language-javascript&quot;&gt;
&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-javascript&quot; data-lang=&quot;javascript&quot;&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1
2
3
4
5
6
7
8
9
10
11
12
&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;nx&quot;&gt;it&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;컴포넌트 목록에서 원하는 컴포넌트 하나를 삭제할 수 있다.&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;amp;&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;gt&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;// given&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;deleteTarget&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;target&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;action&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;compListAction&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;deleteComponent&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;deleteTarget&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;

    &lt;span class=&quot;c1&quot;&gt;// when&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;order&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;compMap&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;compListReducer&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;initState&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;action&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;

    &lt;span class=&quot;c1&quot;&gt;// then&lt;/span&gt;
    &lt;span class=&quot;nx&quot;&gt;order&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;should&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;not&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;include&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;deleteTarget&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;nx&quot;&gt;should&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;not&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;exist&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;compMap&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;deleteTarget&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]);&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;});&lt;/span&gt;
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p class=&quot;se_textarea&quot;&gt;액션을 중첩해서 수행한다거나, 비동기 액션 처리를 위해서 redux-thunk라는 모듈을 사용하는데 이때 중첩 액션을 수행하는 코드는 고차 함수를 이용하여 내부에 함수 클로저를 만드는 방식이다 보니 위에서 보여준 예제의 방식으로는 테스트가 어렵다. 말이 좀 복잡한데 아래의 코드를 보면 redux-thunk가 하위 액션을 어떻게 중첩하는지 알 수 있다.&lt;/p&gt;
&lt;div class=&quot;se_editArea&quot;&gt;
&lt;div class=&quot;se_viewArea se_fs_T3 &quot;&gt;
&lt;div class=&quot;se_editView&quot;&gt;
&lt;div class=&quot;se_textView&quot;&gt;
&lt;div class=&quot;__se_code_view se_textarea language-javascript&quot;&gt;
&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-javascript&quot; data-lang=&quot;javascript&quot;&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;k&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;deleteComponent&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;compId&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;type&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;DELETE_COMPONENT&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;compId&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;compId&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;deleteComponentWithFocus&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;dispatch&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;getState&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;amp;&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;gt&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;components&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;getState&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
      &lt;span class=&quot;nx&quot;&gt;dispatch&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;deleteComponent&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;components&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;focusCompId&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;));&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;routeByButtonName&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;dispatch&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;getState&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;amp;&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;gt&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;focusCompId&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;getState&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;().&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;components&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

      &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;name&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;===&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;remove&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;nx&quot;&gt;dispatch&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;deleteComponentWithFocus&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;());&lt;/span&gt;
      &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;name&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;===&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;upward&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;nx&quot;&gt;dispatch&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;moveUpComponent&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;focusCompId&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;UNMOVABLE_COMPONENTS&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;));&lt;/span&gt;
      &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;name&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;===&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;downward&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;nx&quot;&gt;dispatch&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;moveDownComponent&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;focusCompId&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;UNMOVABLE_COMPONENTS&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;));&lt;/span&gt;
      &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p class=&quot;se_textarea&quot;&gt;위의 코드에서 routeBybuttonName -&amp;gt; deleteComponentWithFocus -&amp;gt; deleteComponent에 최종 도착했을 때 최종적으로 반환한 액션이 { type: DELETE_COMPONENT, compId: compId }라는 걸 어떻게 검증해야 할까? sinon을 이용하면 이렇게 테스트할 수는 있다.&lt;/p&gt;
&lt;div class=&quot;se_editArea&quot;&gt;
&lt;div class=&quot;se_viewArea se_fs_T3 &quot;&gt;
&lt;div class=&quot;se_editView&quot;&gt;
&lt;div class=&quot;se_textView&quot;&gt;
&lt;div class=&quot;__se_code_view se_textarea language-javascript&quot;&gt;
&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-javascript&quot; data-lang=&quot;javascript&quot;&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;nx&quot;&gt;it&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;도구막대의 삭제 버튼을 클릭해서 현재 포커스를 가지고 있는 컴포넌트를 삭제할 것을 요청할 수 있다.&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;c1&quot;&gt;// given&lt;/span&gt;
  &lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;btnName&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;remove&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;fakeDispatcher&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;sandboxSinon&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;spy&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;amp;&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;gt&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{});&lt;/span&gt;
  &lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;fakeStore&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;sandboxSinon&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;spy&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;amp;&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;gt&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;({&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;components&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[]&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}));&lt;/span&gt;

  &lt;span class=&quot;c1&quot;&gt;// when&lt;/span&gt;
  &lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;action&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;toolbarActions&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;routeByButtonName&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;btnName&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
  &lt;span class=&quot;nx&quot;&gt;action&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;fakeDispatcher&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;fakeStore&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;

  &lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;deleteComponentWithFocus&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;fakeDispatcher&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;args&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;][&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;];&lt;/span&gt;
  &lt;span class=&quot;nx&quot;&gt;deleteComponentWithFocus&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;fakeDispatcher&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;fakeStore&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;

  &lt;span class=&quot;c1&quot;&gt;// then&lt;/span&gt;
  &lt;span class=&quot;nx&quot;&gt;fakeDispatcher&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;args&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;][&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;].&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;should&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;be&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;eql&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;deleteComponent&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;());&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;});&lt;/span&gt;
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p class=&quot;se_textarea&quot;&gt;하지만 이 코드를 읽고 fakeDispatcher.args[1][0]이 무엇을 의미하는지 단박에 알아차릴 사람이 있을까? when 절의 복잡해서 도대체 무엇을 하고 싶은 건지 이해할 수 없다. 내가 작성했지만 나도 모른다.  이조차도 어려운데, 리듀서와 결합해버리면 더 복잡해진다.&lt;/p&gt;
&lt;p class=&quot;se_textarea&quot;&gt;이 문제는 &lt;a class=&quot;se_link&quot; href=&quot;https://www.npmjs.com/package/redux-mock-store&quot; target=&quot;_blank&quot;&gt;redux-mock-store&lt;/a&gt;를 도입해서 해결할 수 있다. 아래의 코드는 위에 있는 테스트 코드를 redux-mock-store를 이용해서 개선한 코드다. 테스트의 가독성이 훨씬 높아졌다.&lt;/p&gt;
&lt;div class=&quot;se_editArea&quot;&gt;
&lt;div class=&quot;se_viewArea se_fs_T3 &quot;&gt;
&lt;div class=&quot;se_editView&quot;&gt;
&lt;div class=&quot;se_textView&quot;&gt;
&lt;div class=&quot;__se_code_view se_textarea language-javascript&quot;&gt;
&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-javascript&quot; data-lang=&quot;javascript&quot;&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;nx&quot;&gt;it&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;도구막대의 삭제 버튼을 클릭해서 현재 포커스를 가지고 있는 컴포넌트를 삭제할 것을 요청할 수 있다.&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;// given&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;fakeState&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;components&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;na&quot;&gt;focusCompId&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;focus&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;};&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;store&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;createMockStore&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;fakeState&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;

    &lt;span class=&quot;c1&quot;&gt;// when&lt;/span&gt;
    &lt;span class=&quot;nx&quot;&gt;store&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;dispatch&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;toolbarActions&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;routeByButtonName&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;remove&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;));&lt;/span&gt;

    &lt;span class=&quot;c1&quot;&gt;// then&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;actualActions&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;store&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;getActions&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
    &lt;span class=&quot;nx&quot;&gt;actualActions&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;should&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;be&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;eql&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;([&lt;/span&gt;
        &lt;span class=&quot;nx&quot;&gt;compsActions&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;deleteComponent&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;fakeState&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;components&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;focusCompId&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;]);&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;});&lt;/span&gt;
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p class=&quot;se_textarea&quot;&gt;가짜 스토어 객체를 만들어서 수행한 액션을 히스토리로 기록해 뒀다가 단언으로 검증하는 방식이다.&lt;/p&gt;
&lt;h2 class=&quot;se_textarea&quot;&gt;끝으로...&lt;/h2&gt;
&lt;div class=&quot;se_component se_paragraph default&quot;&gt;&lt;/div&gt;
&lt;div class=&quot;se_component se_paragraph default&quot;&gt;
&lt;p&gt;리덕스로 UI를 개발하다 보면 컴포넌트에서 사용자의 요청을 이벤트로 받아 액션을 스토어로 전달해 리듀서에 있는 상태를 변경한 다음에, 변경한 결과를 받아서 다시 화면을 렌더링 하는 과정이 지나치게 번거롭게 느껴질 때가 있다. 이건 리덕스의 문제라기보다는 애플리케이션을 구조화했을 때 발생하는 문제다. 단방향 데이터 흐름을 강제하는 플럭스 아키텍처 하에서는 이 문제가 좀 더 크게 느껴진다. 그래서 간단한 위젯이나 페이지 내의 인터랙션 처리는 그냥 jQuery로 뚝딱 만드는 게 더 생산적일 수 있다.&lt;/p&gt;
&lt;p&gt;그럼에도 이런 번거로운 과정을 견뎌가면서 애플리케이션을 구조화하려고 애쓰는 이유는, 시스템이 복잡해졌을 때 복잡도를 최소화하고 변경이 미치는 파급효과를 최소화하기 위해서다. 당연한 이야기겠지만, 단순히 어떤 프레임워크나 설계 패턴을 도입했다고 해서 시스템의 복잡도가 바로 줄어들지는 않는다. 프로젝트에 참여하는 구성원들이 사용하는 도구의 기저에 깔려있는 철학을 잘 이해해야 하고, 이런 이해를 바탕으로 설계가 해결하지 못하는 사각에 있는 요소들을 찾아서 일관된 방향으로 코드가 자랄 수 있게 또 다른 규칙을 합의해야 한다. 일치한 줄 알았던 서로의 생각이, 훗날 동상이몽이었던 걸로 드러나 허탈해하는 경우는 아주 흔한 일이다. 그래서 꾸준히 생각을 주고받을 수 있는 코드 리뷰 같은 장치가 필요하다. 결국 &lt;b&gt;'좋은 설계를 지탱하는 한 축은 원활한 커뮤니케이션'&lt;/b&gt;이 아닐까.&lt;/p&gt;
&lt;p&gt;오늘 맞다고 생각했던 것들이 내일 뒤집히는 일이 부지기수라 이 글에 적은 생각의 유통기한이 얼마일지는 모르겠다. 좋은 생각이 떠오를 때마다 계속 업데이트를 해야지.&lt;/p&gt;
&lt;/div&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;이 글은 제 &lt;a href=&quot;http://m.post.naver.com/viewer/postView.nhn?volumeNo=4575578&amp;amp;memberNo=1377642&quot;&gt;포스트&lt;/a&gt;와 &lt;a href=&quot;http://blog.naver.com/jukrang/220751641825&quot;&gt;블로그&lt;/a&gt;에서도 보실 수 있습니다.		&lt;/p&gt;
</description>
            <pubDate>Fri, 01 Jul 2016 22:05:29 +0900</pubDate>
            <link>https://huns.me/development/1953</link>
            <guid isPermaLink="true">https://huns.me/development/1953</guid>
            
            <category>Javascript</category>
            
            <category>react</category>
            
            <category>redux</category>
            
            <category>디자인</category>
            
            <category>설계</category>
            
            
            <category>개발 이야기</category>
            
        </item>
        
        <item>
            <title>단위 테스트부터 잘 작성하고 볼 일</title>
            <description>&lt;div class=&quot;se_component se_sectionTitle &quot;&gt;
&lt;div class=&quot;se_sectionArea se_align-left&quot;&gt;
&lt;div class=&quot;se_editArea&quot;&gt;
&lt;div class=&quot;se_viewArea se_ff_nanumgothic se_fs_H2 se_fw_bold&quot;&gt;
&lt;div class=&quot;se_editView&quot;&gt;
&lt;div class=&quot;se_textView&quot;&gt;
&lt;h2 class=&quot;se_textarea&quot;&gt;&lt;strong&gt;삽질의 시작&lt;/strong&gt;&lt;/h2&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;se_component se_paragraph default&quot;&gt;
&lt;div class=&quot;se_sectionArea&quot;&gt;
&lt;div class=&quot;se_editArea&quot;&gt;
&lt;div class=&quot;se_viewArea se_ff_nanumgothic&quot;&gt;
&lt;div class=&quot;se_editView&quot;&gt;
&lt;p class=&quot;se_textView se_fs_T3 &quot;&gt;코드 베이스가 커지면 변경이 미치는 범위를 가늠하기 힘들다. 팀 프로젝트를 하다 보면 작은 변경으로 발생한 사이드 이펙트를 자주 접한다. 많은 걸 신경 써야 하는 상황에서 수동으로 사이드 이펙트를 모두 찾아낸다는 건 알파고라면 가능할지도. 아쉽지만 우리는 알파고가 아닌 인간이고, 코드 구현 시점에 발견하지 못한 버그는 QA까지 가서야 덜미를 잡혀 돌아온다. 버그 발견 시점이 코드 구현 시점에서 멀어질수록 원인을 찾기 어렵다.&lt;/p&gt;
&lt;/div&gt;
&lt;p&gt;&lt;img class=&quot;wp-image-1896 &quot; src=&quot;/assets/images/legacy/ronaldo_inter_FCB_brazil_wallpaer.jpg&quot; alt=&quot;브라질의 축구 영웅 호나우두가 드리블하는 사진&quot; /&gt;&lt;/p&gt;
&lt;div class=&quot;se_editView&quot;&gt; 그래서 테스트 자동화에 열을 올렸다. 단위 테스트를 작성하자고 열심히 외쳤다. 하지만 몸에 배지 않아서인지 바쁜 일정에 치이면 나조차도 테스트부터 뒤로 미루기 일쑤였다. 어떻게 하면 자연스럽게 테스트를 작성할까? 프로세스가 있으면 어떨까?&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;se_component se_paragraph paragraph_wrapping se_inner-big-right&quot;&gt;
&lt;div class=&quot;se_sectionArea se_align-left&quot;&gt;
&lt;div class=&quot;se_editArea&quot;&gt;
&lt;div class=&quot;se_viewArea se_fs_T3&quot;&gt;
&lt;div class=&quot;se_editView&quot;&gt;
&lt;div class=&quot;se_textView&quot;&gt;
&lt;p class=&quot;se_textarea&quot;&gt;&lt;span class=&quot;se_ff_nanumgothic&quot;&gt;그러다 &lt;span class=&quot;Apple-converted-space&quot;&gt;스토리 주도 개발 방법론에 꽂혔다. 사용자 스토리를 기반으로 인수 테스트를 자동화한다. 이 방법론을&lt;/span&gt;&lt;span class=&quot;Apple-converted-space&quot;&gt; 도입하면 테스트 작성을 개발 프로세스에 자연스럽게 녹일 수 있으리라. 책 속의 이야기는 무척이나 달콤했고, 나는 순진했다.&lt;br /&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p class=&quot;se_textarea&quot;&gt;&lt;span class=&quot;se_ff_nanumgothic&quot;&gt;&lt;span class=&quot;Apple-converted-space&quot;&gt;순간 당황한 분이 계실 테다. 단위 테스트도 뒷전이라는데 웬 스토리 주도 개발? 그렇다. 제대로 헛다리를 짚은 순간이다.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p class=&quot;se_textarea&quot;&gt;&lt;span class=&quot;se_ff_nanumgothic&quot;&gt;&lt;span class=&quot;Apple-converted-space&quot;&gt;&lt;span class=&quot;Apple-converted-space&quot;&gt;혹시나 &lt;/span&gt;&lt;/span&gt;단위 테스트와 기능 테스트의 차이가 알쏭달쏭한 누군가를 위해 책의 한꼭지를 인용한다. 마틴 파울러는 자신의 저서인 &quot;리팩터링&quot;에서 단위 테스트와 기능 테스트의 차이를 이렇게 설명했다.&lt;/span&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;blockquote&gt;
&lt;p class=&quot;se_textView&quot;&gt;단위 테스트의 목적은 프로그래밍 생산성 향상이다. 프로그래밍 생산성이 높아지면 부수적을 품질 보증 부서의 업무 효율도 향상된다. 단위 테스트는 매우 국소적이어서, 각 테스트 클래스는 하나의 패키지 안에서만 효력이 있다. 다른 패키지의 인터페이스를 테스트하지만, 그 외에 나머지 코드는 잘 돌아간다고 가정한다.&lt;/p&gt;
&lt;p class=&quot;se_textView&quot;&gt;기능 테스트는 단위 테스트와 전혀 다르다. 기능 테스트의 목적은 소프트웨어 전반이 제대로 돌아가는지 확인하는 것이다. 기능 테스트는 고객에게 품질 보증만 할 뿐 프로그래머의 생산성과는 무관하다. 따라서 기능 테스트 코드는 별도의 버그 발견 전문 팀이 개발해야 한다. 버그 발견 전문 팀은 기능 테스트 작성에 강력한 도구와 기술을 동원한다.&lt;/p&gt;
&lt;p class=&quot;se_textView&quot;&gt;기능 테스트는 대체로 시스템 전반을 최대한 블랙박스처럼 취급한다. 또한 기능 테스트는 GUI 기반 시스템에선 GUI를 통해 이뤄지고, 파일이나 데이터베이스 업데이트 프로그램에선 데이터가 특정 입력에 대해 어떻게 변하는지를 관찰하기만 한다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;div class=&quot;se_component se_paragraph default&quot;&gt;
&lt;div class=&quot;se_sectionArea&quot;&gt;
&lt;div class=&quot;se_editArea&quot;&gt;
&lt;div class=&quot;se_viewArea se_ff_nanumgothic&quot;&gt;
&lt;div class=&quot;se_editView&quot;&gt;
&lt;div class=&quot;se_textView se_fs_T3&quot;&gt;
&lt;p class=&quot;se_textarea&quot;&gt;이 내용에 따르면 단위 테스트와 기능 테스트는 테스트의 범위와 목적하는 바가 다르다. 내가 웹 프런트엔드의 E2E(End-To-End) 도구인 nightwatch.js를 이용하여&lt;span class=&quot;Apple-converted-space&quot;&gt; &lt;/span&gt; 팀에 도입하려 했던 게 바로 기능 테스트 자동화였다.&lt;/p&gt;
&lt;p class=&quot;se_textarea&quot;&gt;열심히 예제 테스트를 작성해서 팀에 공유했지만 당시 동료들의 반응은 싸늘했다. 얘는 지금 바빠죽겠는데 뭘 더 하라는 거니. 버그를 빨리 찾아서 생산성을 향상시키자던 애가 갑자기 기능 테스트를 들고 왔다. 이게 헛다리가 아니면 뭐가 헛다리람.&lt;/p&gt;
&lt;p class=&quot;se_textarea&quot;&gt;어쨌든 지금 아는 것들을 그때는 몰랐으니, 그저 내 마음을 몰라주는 동료들이 서운했다(당시에). &quot;스마트에디터3 오픈에 즈음하여&quot;라는 글을 통해 당시의 심정을 살짝 엿볼 수 있다.&lt;/p&gt;
&lt;p class=&quot;se_textarea&quot;&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;se_component se_quotation quotation_line&quot;&gt;
&lt;div class=&quot;se_sectionArea&quot;&gt;
&lt;div class=&quot;se_editArea&quot;&gt;
&lt;div class=&quot;se_viewArea se_fs_T4&quot;&gt;
&lt;div class=&quot;se_editView&quot;&gt;
&lt;blockquote&gt;
&lt;p class=&quot;se_textView&quot;&gt;...(생략)&lt;/p&gt;
&lt;p class=&quot;se_textView&quot;&gt;네이버로 이직한 후 스마트에디터 3 프로젝트를 시작하면서 의욕 충만하게 테스트를 작성하려 했다. 생각처럼 잘 되지 않았다. 이번에는 고민의 축을 &quot;테스트를 작성해야 한다&quot;에서 &quot;왜 테스트를 작성하지 않을까?&quot;로 옮겼다.&lt;/p&gt;
&lt;p class=&quot;se_textView&quot;&gt;테스트 작성에 대한 팀 공통 기준이 없었고, 테스트가 개발 프로세스에 자연스레 녹아있지 않았다. 테스트 작성이 코딩과 하나로 묶인 작업이 아닌, 추가로 해야 하는 귀찮은 노동 취급을 받고 있었다. 이 문제를 해결하려면 프로세스 개선이 필요하다고 생각했고 사용자 스토리 작성부터 시작해서 기능 테스트 작성까지 이어지는 프로세스를 나름 팀에 적응할 수 있겠다 싶은 수준으로 추렸다.&lt;/p&gt;
&lt;p class=&quot;se_textView&quot;&gt;...(생략)&lt;/p&gt;
&lt;p class=&quot;se_textView&quot;&gt;적응하려면 상당한 연습과 시간이 필요한데 우리는 아직 너무 바쁘다. 결정적으로 &quot;좋다&quot;고 &quot;강요&quot;하기에는 내 경험이 너무 미천하다. 내가 느끼는 그대로를 상대에게 전달하여 설득하는 일은 언제나 힘들다.&lt;/p&gt;
&lt;p class=&quot;se_textView&quot;&gt;...(생략)&lt;/p&gt;
&lt;/blockquote&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;se_component se_paragraph default&quot;&gt;
&lt;div class=&quot;se_sectionArea&quot;&gt;
&lt;div class=&quot;se_editArea&quot;&gt;
&lt;div class=&quot;se_viewArea se_ff_nanumgothic&quot;&gt;
&lt;div class=&quot;se_editView&quot;&gt;
&lt;p class=&quot;se_textView se_fs_T3&quot;&gt;헛다리를 짚었지만 당시에는 단위 테스트와 기능 테스트를 개발자가 모두 책임지는 게 가능하다고 믿었고, 할 수 있다는 걸 어떤 식으로든 증명해 보일 수만 있다면 모두 자연스럽게 나를 따라올 거라 확신했다.&lt;span class=&quot;Apple-converted-space&quot;&gt; &lt;/span&gt;지금의 거부감은 그저 무지에서 비롯된 것일 뿐.&lt;span class=&quot;Apple-converted-space&quot;&gt; 아직은 낯설어서, 혹은 잘 몰라서 그런 거라 생각했다. 그럼 내가 알려줄테다.&lt;/span&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;h2 class=&quot;se_textView se_fs_T3&quot;&gt;&lt;strong&gt;&lt;br class=&quot;none&quot; /&gt;&lt;br class=&quot;none&quot; /&gt;삽질 끝에 얻은 결론&lt;/strong&gt;&lt;/h2&gt;
&lt;p class=&quot;se_textView se_fs_T3&quot;&gt;열심히 기능 테스트를 작성했고, 무지한 것은 나라는 사실을 깨닫기까지 걸린 시간은 불과 한 달. 시간이 지나면서 기능 테스트 작성이 버거워졌다. 언젠가부터는 슬며시 한 쪽 구석에 방치하더니, 그렇게 우리는 남이 되었다.&lt;/p&gt;
&lt;div class=&quot;se_component se_paragraph default&quot;&gt;
&lt;div class=&quot;se_sectionArea&quot;&gt;
&lt;div class=&quot;se_editArea&quot;&gt;
&lt;div class=&quot;se_viewArea se_ff_nanumgothic&quot;&gt;
&lt;div class=&quot;se_editView&quot;&gt;
&lt;p class=&quot;se_textView se_fs_T3&quot;&gt;시간이 흘러 며칠 전에 테스트 코드를 정리하다가 버려진 기능 테스트 코드 조각들을 발견했다. 여러 가지 생각이 교차하다 아래에 기술할 몇 가지 이유로 미련 없이 기능 테스트를 모두 삭제했다. 이제 기능 테스트 자동화에 대한 나의 미련에 안녕을 고하련다.&lt;/p&gt;
&lt;h4 class=&quot;se_textView se_fs_T3&quot;&gt;&lt;strong&gt;&lt;br class=&quot;none&quot; /&gt;테스트 수행 속도가 너무 느리다.&lt;/strong&gt;&lt;/h4&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;se_component se_paragraph default&quot;&gt;
&lt;div class=&quot;se_sectionArea&quot;&gt;
&lt;div class=&quot;se_editArea&quot;&gt;
&lt;div class=&quot;se_viewArea se_ff_nanumgothic&quot;&gt;
&lt;div class=&quot;se_editView&quot;&gt;
&lt;div class=&quot;se_textView se_fs_T3&quot;&gt;
&lt;p class=&quot;se_textarea&quot;&gt;기능 테스트는 성격상 테스트 실행 속도가 느릴 수밖에 없다. 피드백이 늦다는 소리다. 테스트 실행이 느리다는 건 생산성 향상에 단위 테스트만큼 도움을 주지 못한다는 뜻. 자주 실행할 수 없기에 단위 테스트를 대체하기 어렵다. 그렇다면 테스트를 둘 다 작성하거나, 단위 테스트를 포기해야 한다는 소린데. 둘 중에 하나를 택하라면 생산성(과 단위 레벨의 품질)을 택하겠다.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;h4 class=&quot;se_textarea&quot;&gt;&lt;strong&gt;&lt;br class=&quot;none&quot; /&gt;웹 프런트엔드 E2E 테스팅 도구의 완성도가 떨어진다.&lt;/strong&gt;&lt;/h4&gt;
&lt;div class=&quot;se_component se_paragraph default&quot;&gt;
&lt;div class=&quot;se_sectionArea&quot;&gt;
&lt;div class=&quot;se_editArea&quot;&gt;
&lt;div class=&quot;se_viewArea se_ff_nanumgothic&quot;&gt;
&lt;div class=&quot;se_editView&quot;&gt;
&lt;div class=&quot;se_textView se_fs_T3&quot;&gt;
&lt;p class=&quot;se_textarea&quot;&gt;&lt;a href=&quot;http://nightwatchjs.org/&quot;&gt;Nightwtch.js&lt;/a&gt;든 &lt;a href=&quot;http://angular.github.io/protractor/#/&quot;&gt;Protractor&lt;/a&gt;든, 모두 &lt;a href=&quot;http://www.seleniumhq.org/&quot;&gt;셀레늄&lt;/a&gt; 기반이다. 아는 사람은 알겠지만 셀레늄의 IE 드라이버는 버그가 많다. 브라우저 파편화 이슈도 꽤나 성가시다.모던 브라우저라도 예외가 아니어서 크로스 브라우징 테스트를 하려면 일일이 확인해가며 테스트 코드를 작성해야 한다. 한 번은 테스트 케이스 하나 짜는데 3시간이나 걸린 적이 있었는데 기능 하나를 개발하는 기분이었다. 배보다 배꼽이 더 크다.&lt;/p&gt;
&lt;p class=&quot;se_textarea&quot;&gt;우리 조직이 만들고 있는 웹 에디터처럼 인터랙션이 복잡하게 엉켜있는 제품을 대상으로 수행하는 기능 테스트는, 테스트 자체를 신뢰할 수 없는 경우도 꽤 있었다. DOM 셀렉팅 중에 알 수 없는 오류가 발생해서 통과해야 하는 테스트가 실패하는 경우가 그렇다. 제멋대로 오락가락. 리포트를 신뢰할 수 없는 테스트라니. 떡 없는 떡볶이도 아니고 이게 뭐람.&lt;/p&gt;
&lt;p class=&quot;se_textarea&quot;&gt;당시에 내가 사용한 도구인 Nightwatch.js(v0.8.4 기준)도 결함이 많았다. Nightwatch.js가 아주 형편없다는 이야기는 아니다. 기능 테스트를 작성할 때 Nightwatch.js를 애용하는 Eric Elliott(&lt;a href=&quot;http://www.sitepoint.com/javascript-testing-unit-functional-integration/&quot;&gt;JavaScript Testing: Unit vs Functional vs Integration Tests&lt;/a&gt;) 같은 분도 있다(정말 일까...?). 다만 내가 만들고 있는 제품을 테스트하기에는 부족했다. 버그가 있어 코드를 뜯어봐야 했던 경우가 자주 있었다. Nightwatch.js의 기본 테스트 러너는 명세를 1 단계로만 적을 수 있어 명세를 가독성 있게 작성하기 어렵다. mocha를 테스트 러너로 사용하면 describe, it 함수를 이용할 수 있지만, 병렬 테스팅을 제대로 수행하지 못한다. 테스트 작성해야 할 시간에 도구의 코드를 들여다보고 있어야 하다니. 덕분에 내부 구조도 살펴보고, 프로젝트에 기여하기도 했지만 내가 좀 바빠서 미안.&lt;/p&gt;
&lt;h4 class=&quot;se_textarea&quot;&gt;&lt;strong&gt;&lt;br class=&quot;none&quot; /&gt;테스트가 주는 피드백의 질이 낮다.&lt;/strong&gt;&lt;/h4&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;se_component se_paragraph default&quot;&gt;
&lt;div class=&quot;se_sectionArea&quot;&gt;
&lt;div class=&quot;se_editArea&quot;&gt;
&lt;div class=&quot;se_viewArea se_ff_nanumgothic&quot;&gt;
&lt;div class=&quot;se_editView&quot;&gt;
&lt;div class=&quot;se_textView se_fs_T3&quot;&gt;
&lt;p class=&quot;se_textarea&quot;&gt;리포트를 신뢰할 수 없는 것 자체로 이미 치명적이지만, 에러가 발생했을 때 돌려주는 피드백도 만족스럽지 않았다. 테스트 도중 오류가 발생했을 때 어디에서 어떤 오류가 발생했는지 정확히 알기 어렵다. 그냥 '테스트 시나리오를 수행하던 중에 어떤 문제가 발생했구나'정도를 감 잡을 수 있을 뿐이다. 심지어 이마저도 빠르게 받아보기 어렵다.&lt;/p&gt;
&lt;h4 class=&quot;se_textarea&quot;&gt;&lt;strong&gt;&lt;br class=&quot;none&quot; /&gt;테스트 작성에 들어가는 비용이 너무 많다.&lt;/strong&gt;&lt;/h4&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;se_component se_paragraph default&quot;&gt;
&lt;div class=&quot;se_sectionArea&quot;&gt;
&lt;div class=&quot;se_editArea&quot;&gt;
&lt;div class=&quot;se_viewArea se_ff_nanumgothic&quot;&gt;
&lt;div class=&quot;se_editView&quot;&gt;
&lt;div class=&quot;se_textView se_fs_T3&quot;&gt;
&lt;p class=&quot;se_textarea&quot;&gt;앞에 언급한 내용을 이유로 기능 테스트를 작성하고 유지하려면&lt;span class=&quot;Apple-converted-space&quot;&gt; 많은 &lt;/span&gt;비용을 지불해야 한다. 단위 테스트도 작성하기 힘든 여건에서 단위 테스트와 기능 테스트 둘 다 유지하겠다는 건 나, 그리고 우리가 할 수 있는 일이 아니다.&lt;/p&gt;
&lt;h4 class=&quot;se_textarea&quot;&gt;&lt;strong&gt;&lt;br class=&quot;none&quot; /&gt;기능 테스트를 제대로 작성하려면 전문성이 필요하다.&lt;/strong&gt;&lt;/h4&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;se_component se_paragraph default&quot;&gt;
&lt;div class=&quot;se_sectionArea&quot;&gt;
&lt;div class=&quot;se_editArea&quot;&gt;
&lt;div class=&quot;se_viewArea se_ff_nanumgothic&quot;&gt;
&lt;div class=&quot;se_editView&quot;&gt;
&lt;div class=&quot;se_textView se_fs_T3&quot;&gt;
&lt;p class=&quot;se_textarea&quot;&gt;기능 테스트를 제대로 수행하려면 잘 짜인 TC가 필요하고, TC를 잘 작성하려면 전문성이 있어야 한다. QA 조직이 그냥 있는 게 아니지 않은가. 사내에 QA 조직이 아예 없거나 지원을 받을 수 없다면 모를까, 그게 아닌 상황에서 개발자가 작성하는 기능 테스트는 그저 QA를 보완하는 수준에 그칠 가능성이 높다(이마저도 사실 의심스럽지만). QA를 보완하는 수준의 테스트라면 단위 테스트로 충분하다는 결론에 도달했다.&lt;/p&gt;
&lt;h2 class=&quot;se_textarea&quot;&gt;&lt;strong&gt;&lt;br class=&quot;none&quot; /&gt;&lt;br class=&quot;none&quot; /&gt;그래서 고민은...&lt;/strong&gt;&lt;/h2&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;se_component se_paragraph default&quot;&gt;
&lt;div class=&quot;se_sectionArea&quot;&gt;
&lt;div class=&quot;se_editArea&quot;&gt;
&lt;div class=&quot;se_viewArea se_ff_nanumgothic&quot;&gt;
&lt;div class=&quot;se_editView&quot;&gt;
&lt;div class=&quot;se_textView se_fs_T3&quot;&gt;
&lt;p class=&quot;se_textarea&quot;&gt;기능 테스트와 작별한 이후, 단위 테스트 자동화에만 집중했다. 팀 동료 모두가 힘을 모으고, 우주의 기운까지 더하여 저장소에 Pull Request를 보내면 CI 서버에서 단위 테스트를 자동으로 수행하는 단계까지 개발 프로세스에 정착시켰다. 1년 전 상황을 생각해보면 감개무량하다. 이제는 어떻게 하면 단위 테스트를 더 효율적으로 작성할 수 있을지 고민하고 있다. 아직 갈 길이 멀다.&lt;/p&gt;
&lt;p&gt;어디까지나 내가 속한 조직이 처한 상황과 웹 프런트엔드의 개발 환경에 근거하여 판단한 내용일 뿐, 기능 테스트가 전혀 쓸모 없다는 뜻은 아니다. 서 있는 환경이 다르면 해답도 달라야 한다. 이번에 만들고 있는 제품에 새로운 기능을 추가하면서 공통 라이브러리를 리팩터링했다가 버그 폭탄을 맞았다. 이런 일이 발생한 이유는 단위 테스트 커버리지가 부족했던 탓도 있지만, UI 인터랙션 처리는 단위 수준이 아닌 모듈과 모듈 사이에 걸쳐있는 경우가 많고, 마크업 파트에서 스타일을 변경함으로써 발생하는 사이드 이펙트는 성격상 단위 테스트만으로 수정 후 안전을 보장하기 어렵기 때문이다. 이럴 때 부분적으로 기능 테스트를 자동화하면 도움이 될 수도 있겠다는 생각은 아직도 유효하나, 작성에 들어가는 비용을 생각해보면 이게 맞는 건지 잘 모르겠다.&lt;/p&gt;
&lt;p&gt;단위 테스트로 검증하기 어려운 부분이 많다는 건 앞으로 풀어야 할 숙제다. 뾰족한 수가 떠오르지 않는다. QA와 TC를 공유하면 어떨까? 언제나 문제는 시간이다. 해외에서 유행한다는 프로세스가 국내에 잘 정착하지 못하는 이유도 그런 거 아니겠는가. 그래도 언젠가는 도달해야 할 이상향이 있기에 고민은 멈추지 말아야겠지.&lt;/p&gt;
&lt;p&gt;뭐가 되었든, 단위 테스트라도 잘 작성하고 볼 일이다.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p class=&quot;se_textarea&quot;&gt;&lt;br class=&quot;none&quot; /&gt;---&lt;/p&gt;
&lt;p class=&quot;se_textarea&quot;&gt;이 글은 &lt;a href=&quot;http://blog.naver.com/jukrang/220696036490&quot;&gt;네이버 블로그&lt;/a&gt;에서도 보실 수 있습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
</description>
            <pubDate>Sat, 30 Apr 2016 10:29:37 +0900</pubDate>
            <link>https://huns.me/development/1865</link>
            <guid isPermaLink="true">https://huns.me/development/1865</guid>
            
            <category>nightwatch.js</category>
            
            <category>기능 테스트</category>
            
            <category>자동화</category>
            
            <category>테스트</category>
            
            
            <category>개발 이야기</category>
            
        </item>
        
        <item>
            <title>천천히 걸을 수 있을까?</title>
            <description>&lt;div class=&quot;se_component se_paragraph default&quot;&gt;
&lt;div class=&quot;se_sectionArea&quot;&gt;
&lt;div class=&quot;se_editArea&quot;&gt;
&lt;div class=&quot;se_viewArea se_ff_nanumgothic&quot;&gt;
&lt;p class=&quot;se_editView&quot;&gt; 몸에 익지 않은 코딩 지식은 거미줄처럼 복잡하게 엉겨 붙은 코드의 정글, 홍수처럼 쏟아지는 이슈, 당장 내일이라도 찾아올 것만 같은 마감일이 주는 압박을 견디지 못하고 쉽게 무너진다. 그래서 코딩을 잘하려면 지식을 단순히 학습하는 수준에서 그치지 않고, 습득한 지식이 몸에 밸 때까지 꾸준히 수련해야 한다. 말은 쉽다.&lt;/p&gt;
&lt;p class=&quot;se_editView&quot;&gt; 해롭다는 걸 알면서도 쉬운 길을 가고 싶은 유혹은 언제나 달콤하다. 입은 빠르지만 머리는 굳어있고 손은 더디다. 그렇게 부채는 쌓인다. 적당한 기술 부채는 빠른 개발에 필수라지만, 불필요한 부채를 만들어놓고 둘러대는 핑계는 모두를 곤란하게 만들지 않던가. 문제를 애초에 몰랐다면 모를까, 알면서도 그저 쌓아둔 문제는 다시 곱씹어 볼 일이다.&lt;/p&gt;
&lt;div class=&quot;se_editView&quot;&gt; &lt;a href=&quot;/assets/images/legacy/2016/04/5f468e98.jpeg&quot; rel=&quot;attachment wp-att-1859&quot;&gt;&lt;img class=&quot;aligncenter size-large wp-image-1859&quot; src=&quot;/assets/images/legacy/5f468e98-1024x683.jpeg&quot; alt=&quot;5f468e98&quot; /&gt;&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;se_component se_image default&quot;&gt;&lt;/div&gt;
&lt;div class=&quot;se_component se_paragraph default&quot;&gt;
&lt;div class=&quot;se_sectionArea&quot;&gt;
&lt;div class=&quot;se_editArea&quot;&gt;
&lt;div class=&quot;se_viewArea se_ff_nanumgothic&quot;&gt;
&lt;div class=&quot;se_editView&quot;&gt;
&lt;p class=&quot;se_textView se_fs_T3&quot;&gt; 그래서 요 며칠은 알고 있는 지식이 코드에 잘 묻어날 수 있게 조금 느리게 걸으려고 노력하고 있다. 테스트 작성, 리팩터링, 설계 변경, 구현까지. 애쓰고 있지만 먼 길을 돌아가는 느낌은 여전히 날 괴롭힌다. 쉬워 보일 뿐(?)인 길로 달려가고 싶어 안달 난 나 자신을 끊임없이 타이르는 과정은 마치 부모님과 하는 대화처럼 답답하다.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;se_component se_quotation default&quot;&gt;
&lt;div class=&quot;se_sectionArea&quot;&gt;
&lt;div class=&quot;se_editArea&quot;&gt;
&lt;div class=&quot;se_viewArea se_fs_T2&quot;&gt;
&lt;div class=&quot;se_editView&quot;&gt;
&lt;blockquote&gt;
&lt;div class=&quot;se_textView&quot;&gt;&lt;strong&gt;&quot;우리는 천천히 걸을 수 있을까?&quot;&lt;/strong&gt;&lt;/div&gt;
&lt;/blockquote&gt;
&lt;p class=&quot;se_textView&quot;&gt; 필요하다는 걸 알고서 행하는 내가 이렇게 애써야 하는데, 정반대에 서 있는 사람이 느껴야 하는 부담의 깊이는 얼마나 되려나. 나, 그리고 우리는, 언제쯤 숨 쉬듯 천천히 걸을 수 있을까. 오만가지 생각이 스친다.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;se_component se_paragraph default&quot;&gt;&lt;/div&gt;
</description>
            <pubDate>Sat, 09 Apr 2016 10:06:05 +0900</pubDate>
            <link>https://huns.me/development/1851</link>
            <guid isPermaLink="true">https://huns.me/development/1851</guid>
            
            <category>사색</category>
            
            
            <category>개발 이야기</category>
            
        </item>
        
    </channel>
</rss>
