<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>seoyong-lee.log</title>
        <link>https://velog.io/</link>
        <description></description>
        <lastBuildDate>Sun, 04 Jan 2026 12:17:56 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <image>
            <title>seoyong-lee.log</title>
            <url>https://velog.velcdn.com/images/seoyong-lee/profile/048df4bb-6e7d-43d5-81a0-7c20f5e24593/image.jpg</url>
            <link>https://velog.io/</link>
        </image>
        <copyright>Copyright (C) 2019. seoyong-lee.log. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/seoyong-lee" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[개발자 인생 중간 점검.]]></title>
            <link>https://velog.io/@seoyong-lee/%EA%B0%9C%EB%B0%9C%EC%9E%90-%EC%9D%B8%EC%83%9D-%EC%A4%91%EA%B0%84-%EC%A0%90%EA%B2%80</link>
            <guid>https://velog.io/@seoyong-lee/%EA%B0%9C%EB%B0%9C%EC%9E%90-%EC%9D%B8%EC%83%9D-%EC%A4%91%EA%B0%84-%EC%A0%90%EA%B2%80</guid>
            <pubDate>Sun, 04 Jan 2026 12:17:56 GMT</pubDate>
            <description><![CDATA[<p>2021년 개발자로 다시 시작하겠다고 다짐한 이후 벌써 5년이라는 시간이 지났습니다. 지금까지 개발자의 길을 제대로 걸어온 걸까요.</p>
<p>연말 연휴에 쉬면서 떠오르는 생각들을 짧게 적어보았습니다.</p>
<h1 id="신사업과-반복-훈련">신사업과 반복 훈련</h1>
<p>프론트엔드 개발자로 시작해서 지금까지 했던 일들을 생각해 보니 연차에 비해 서비스를 처음부터 개발하고 완성해본 경험이 유독 많았던 것 같습니다. 이렇게 초기 단계에 대한 경험만 많은 게 좋은 건지는 모르겠습니다. 안정적인 서비스 운영 경험이 부족하다는 이유로 커리어에 문제가 생길까 봐 걱정은 됩니다.</p>
<p>그러나 반복적인 0 to 1 경험에서 얻은 것은 확실히 있습니다. 바로 완성을 반복할수록 더 높은 위치에서 전체를 내려다볼 수 있는 여유가 생긴다는 점입니다. </p>
<p><img src="https://velog.velcdn.com/images/seoyong-lee/post/6d871464-b70c-47af-9d97-6e9c484e37f5/image.png" alt=""></p>
<p>처음 웹사이트를 하나 개발할 때는 아는 건 전부 구현해야 한다고 생각해서 완성에 대한 기준이 스스로 너무나 높았습니다. 결국 첫 도전은 바퀴만 달린 자동차와 같이 밸런스를 잃고 부분의 완성으로 끝나고 말았습니다. 그러나 2번째 시도에선 이전 경험을 통해 익숙해진 부분은 더 빨리 끝낼 수 있었고, 힘을 주고 빼야 할 부분을 파악해 가면서 단계를 진행하니 여유가 생기기 시작했습니다. 이러한 경험을 반복하면서 지금은 AI의 도움을 받아 모바일 앱과 인프라, 백엔드까지 전체 서비스 개발을 자유롭게 도전할 수 있게 되었습니다.</p>
<p>어떤 일이든 처음엔 어렵더라도 반복 훈련 사이클을 최대한 돌리다 보면 심리적 장벽이 낮아지면서 자연스럽게 쉬워지는 것 같습니다. 물론 이를 꾸준히 진행하기 위해서는 포기하지 않을 만큼 적절하게 목표를 설정하는 것이 중요하다고 생각합니다.</p>
<h1 id="ai의-현실-충격">AI의 현실 충격</h1>
<p>올해 AI는 본격적으로 현실에 침투하면서 많은 것들을 바꾸기 시작했습니다. 실무에서도 AI를 적극적으로 활용하기 시작하였고 Cursor와 함께 코드를 짜면서 이젠 다신 이전으로 되돌아갈 수 없을 것 같은 느낌을 강하게 받았습니다.</p>
<p>AI가 외부 지식에 접근하는 방법이 다양해지기 시작하면서 전문분야에 대한 질문에도 준수한 답변을 주기 시작했습니다. AI와 문답을 반복하다 보면 마치 나의 지식을 확장시켜 줄 진정한 멘토를 만난 것 같은 느낌을 받기도 합니다.</p>
<p>그러나 한편으로는 더 이상 지식이 중요하지 않은 시대가 온 것은 아닐까 라는 불안감도 몰려옵니다. 회사 다니면서 힘들게 대학원을 졸업했는데 수업에서 배운 내용을 GPT가 더 잘 요약해서 알려주니 그동안 쫓아왔던 &#39;지식&#39;이라는 것은 과연 무엇일지 허무하기도 합니다.</p>
<blockquote>
<p>개발자에게 필요한 지식이란 무엇일까요?</p>
</blockquote>
<h1 id="지식의-정의">지식의 정의</h1>
<p><img src="https://velog.velcdn.com/images/seoyong-lee/post/cb9cc320-9e93-4f9e-9b93-302a79d5328c/image.jpg" alt=""></p>
<p>몇 년 전 헌책방에서 가져온 후 책장 한구석에 잠들어 있던 피터 드러커의 &#39;자본주의 이후의 사회&#39;에서 진정한 지식에 대한 힌트를 얻을 수 있었습니다.</p>
<blockquote>
<p>지식은 책이나 자료은행 그리고 소프트웨어 프로그램 안에 머물러 있는 것이 아니다. 책이나 자료은행 그리고 소프트웨어 프로그램들은 오직 정보만을 담고 있다. 지식은 언제나 사람 속에 구현되어 있고 사람이 갖고 다니며, 사람에 의해 창조되고 증대되거나 개선되어진다. <br/>
— 피터 F. 드러커, 『자본주의 이후의 사회』, 308쪽. </p>
</blockquote>
<p>평소 책을 읽다가 중요하다고 생각되는 문구에는 밑줄이나 메모를 붙여두지만 위 문장은 너무나 당연한 이야기라고 생각했었는지 아무런 표시도 없었습니다. 그러나  AI 시대에 다시 읽어본 위 글은 이전과는 다르게 다가왔습니다.</p>
<p>이제 누구나 GPT에게 물어보면 쉽게 &#39;정보&#39;를 얻을 수 있는 세상이 되었습니다. 그러나 진정한 &#39;지식&#39;은 결국 사람의 맥락 안에서 생성되고 전달될 수밖에 없습니다.</p>
<p>이는 개발자도 다르지 않을 것 같습니다. 정보에 기반한 단순 작업을 넘어, 사람과 제품의 맥락 속에서 지식을 적용하고 판단할 수 있는 개발자가 혼란 속에서도 경쟁력을 유지할 것이라고 생각합니다.</p>
<ul>
<li>요구사항을 “문장”이 아니라 “의도/제약/리스크”로 번역하는 능력</li>
<li>트레이드오프를 기록하고 관계자를 합의시키는 능력</li>
<li>사용자/운영 등 제품 맥락을 코드 결정에 반영하는 능력</li>
<li>AI가 준 정보를 검증하는 능력</li>
</ul>
<blockquote>
<p>지식사회에서의 사람들은 배우는 방법을 배워야 한다. <br/>
— 피터 F. 드러커, 『자본주의 이후의 사회』, 295쪽.</p>
</blockquote>
<h1 id="앞으로의-방향">앞으로의 방향</h1>
<p>앞으로의 인생은 아직 잘 모르겠습니다. </p>
<p>전공인 소프트웨어 주변을 맴돌며 먹고살 것 같다는 어렴풋한 느낌은 들지만, 구체적으로 어떤 방향으로 갈지는 아직 제한을 두고싶지는 않습니다. </p>
<p>다만 상상을 실체로 만드는 일이라면 뭐든지 즐겁게 할 것 같습니다.</p>
<h4 id="references">References</h4>
<p>Drucker, Peter F. (1993). Post-Capitalist Society. New York: HarperCollins.
<a href="https://www.niceideas.ch/roller2/badtrash/entry/the-search-of-product-market">The Search for Product-Market Fit by Jerome Kehrli</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[MVC 제대로 알기]]></title>
            <link>https://velog.io/@seoyong-lee/MVC-%EC%A0%9C%EB%8C%80%EB%A1%9C-%EC%95%8C%EA%B8%B0</link>
            <guid>https://velog.io/@seoyong-lee/MVC-%EC%A0%9C%EB%8C%80%EB%A1%9C-%EC%95%8C%EA%B8%B0</guid>
            <pubDate>Sat, 01 Feb 2025 06:45:06 GMT</pubDate>
            <description><![CDATA[<p>2025년에 React로 프론트엔드 개발을 하면서 가장 제대로 이해하기 어려운 개념 중의 하나가 바로 MVC가 아닐까 합니다. 인터넷의 글들은 대부분 ‘MVC, MVP, MV* 구현하기’와 같이 본질보다는 구현에 집중하는 경우가 대부분이라 제대로 이해할 기회가 없었던 것도 한몫한다고 생각합니다. 그러던 중 우연히 최근 출간된 <a href="https://product.kyobobook.co.kr/detail/S000213880201">‘자바스크립트 + 리액트 디자인 패턴’</a> 서적을 읽게 되었고 여기서 언급된 <a href="https://martinfowler.com/eaaDev/uiArchs.html">마틴 파울러의 ‘GUI Architectures’</a> 글(매우 길고 난해하지만)을 읽고 MVC의 개념에 대해 조금 더 다가갈 수 있었습니다. 마지막으로 <a href="https://blog.naver.com/jukrang/221597914483">&#39;김코딩’님의 블로그 글</a>은 MV* 패턴 전체에 대한 이해에 많은 도움이 되었습니다. 부족하지만 제가 이해한 버전을 공유해 드림으로써 다른 분들이 MV* 을 이해하는 데 조금이나마 도움이 되길 바랍니다.</p>
<h1 id="mvc란">MVC란?</h1>
<p>MVC는 다음과 같이 Model-View-Controller로 관심사를 분리하여 설계하는 설계 패턴(Archictecture Pattern)으로 알려져 있습니다.</p>
<ul>
<li>Model: 비즈니스 관련 도메인 모델</li>
<li>View: 모델의 정보를 화면에 표현</li>
<li>Controller: 사용자 상호작용을 처리하고 로직으로 연결</li>
</ul>
<p>위 구조만 보면 MVC는 각 요소를 정리하는 방법에 대한 패턴일 것 같지만 마틴 파울러에 따르면 MVC의 본질은 <strong>Seperated Presentation</strong>입니다.</p>
<blockquote>
<p>The idea behind <a href="https://martinfowler.com/eaaDev/SeparatedPresentation.html">Separated Presentation</a> is to make a clear division between domain objects that model our perception of the real world, and presentation objects that are the GUI elements we see on the screen. Domain objects should be completely self contained and work without reference to the presentation, they should also be able to support multiple presentations, possibly simultaneously. This approach was also an important part of the Unix culture, and continues today allowing many applications to be manipulated through both a graphical and command-line interface. <br/>
분리된 프레젠테이션(Separated Presentation)의 개념은, <strong>현실 세계를 모델링하는 도메인 객체(Domain Objects)와 화면에서 보이는 GUI 요소인 프레젠테이션 객체(Presentation Objects)를 명확하게 분리</strong>하는 것입니다. 도메인 객체(Domain Objects)는 완전히 독립적(self-contained)이어야 하며, 프레젠테이션과의 직접적인 참조 없이 동작 가능해야 합니다. 또한, 여러 개의 프레젠테이션을 동시에 지원할 수 있어야 합니다. 이러한 접근 방식은 유닉스(Unix) 문화에서도 중요한 개념이었으며, 오늘날에도 많은 애플리케이션이 GUI와 명령줄 인터페이스(Command-Line Interface, CLI)를 동시에 지원할 수 있도록 합니다.<br/>
<a href="https://martinfowler.com/eaaDev/uiArchs.html"><strong>Martin Fowler</strong> - <strong>GUI Architectures</strong></a></p>
</blockquote>
<p>위 관점에서 MVC를 생각해 보면 MVC는 각 요소(뷰, 모델, 컨트롤러)의 구체적인 구현보다는 뷰-모델의 관계에 집중하여 만들어졌다는 것을 알 수 있습니다. 그렇다면 MVC가 최초에 뷰(UI)와 모델(도메인)의 분리를 통해 해결하려는 문제는 무엇이었을까요? </p>
<p>이를 이해하기 위해서는 먼저 1970년대에 Xerox PARC에서 MVC를 최초로 공식화했던 <strong>Trygve Reenskaug(트리그베 렌스카우그)</strong>의 <a href="https://folk.universitetetioslo.no/trygver/themes/mvc/mvc-index.html">글(<strong>MVC</strong> <em>XEROX PARC 1978-79)</em></a>을 살펴볼 필요가 있습니다.</p>
<blockquote>
<p>The essential purpose of MVC is to bridge the gap between the human user&#39;s mental model and the digital model that exists in the computer. The ideal MVC solution supports the user illusion of seeing and manipulating the domain information directly. The structure is useful if the user needs to see the same model element simultaneously in different contexts and/or from different viewpoints. <br/>
MVC의 본질적인 목적은 인간 사용자의 정신적 모델과 컴퓨터에 존재하는 디지털 모델 간의 격차를 좁히는 것입니다. 이상적인 MVC 솔루션은 사용자가 도메인 정보를 직접 보고 조작하는 환상을 지원합니다. 이 구조는 동일한 모델 요소를 서로 다른 컨텍스트 및/또는 관점에서 동시에 볼 필요가 있을 때 유용합니다.<img src="https://velog.velcdn.com/images/seoyong-lee/post/11df8801-165f-4411-98ca-a82195cb6fe4/image.png" alt="">
<a href="https://folk.universitetetioslo.no/trygver/themes/mvc/mvc-index.html">MVC
XEROX PARC 1978-79</a></p>
</blockquote>
<p>위와 같이 최초 MVC는 컴퓨터 모델과 인간의 정신적 모델 사이의 차이를 인정하고 이를 서로 다르게 접근하도록 하여 차이를 좁히는 것을 제안합니다. 컴퓨터 관점의 복잡한 데이터 셋(모델)을 인간의 관점에서 쉽게 조작하도록 돕는(UI) 문제를 해결하기 위해 고안된 것이었죠. 이 시점은 GUI 자체가 흔하지 않았던 시대였으며, UI 작업을 체계적으로 수행하려는 최초의 시도 중 하나였다는 점을 생각한다면 MVC는 당시 관점으로도 혁신적인 개념이었습니다.</p>
<p>그렇다면 이러한 분리를 통해 개발 관점에서 얻는 효과는 무엇일까요? 많은 사람들이 ‘코드의 재사용’이 MVC를 통해 얻는 가장 큰 장점이라고 생각하는 경우가 많습니다. 그러나 <a href="https://blog.naver.com/jukrang/221414570067">김코딩님의 통찰력 있는 글</a>에 따르면 MVC는 &#39;도메인&#39;을 보호하기 위해 이를 나머지와 분리한 것이 핵심으로, 분리된 나머지 계층의 동기화 문제를 해결하려다 여러 패턴이 파생되었다고 설명합니다.</p>
<blockquote>
<p>이어져 온 흐름을 보면 전체 역사를 관통하는 한 가지가 눈에 띈다. 너무나 당연한 이야기지만 도메인(도메인 모델)과 UI는 항상 존재해왔다는 사실이다. 간혹 MVC를 이야기하면서 코드 재사용성을 언급하는 경우를 본다. 코드 재사용성 증대는 도메인을 보호하고 관심사를 분리하는 과정에서 우연히 덤으로 얻은 이득일 뿐 MVC의 본질은 아니다.</p>
<p>도메인을 보호하기 위해서 관심사를 분리하였다. 이 과정에서 UI와 도메인 계층이 만들어졌고, 두 계층에 존재하는 상태를 동기화해야 하는 문제가 생겼다. 시스템이 놓인 맥락에서  더 나은 동기화 전략을 만들어 문제를 해결한다. MVC에서 WebMVC로 이어지는 역사는 도메인과 UI의 분리, 분리로 인해 만들어진 문제를 해결, 그리고 더 나은 해결책을 찾는 과정 속에서 발전했다.</p>
<p><a href="https://blog.naver.com/jukrang/221414570067">M-V-Whatever 정리 - 1.MVC</a></p>
</blockquote>
<p>MVC는 인간 관점에서 UI를 통해 편리하게 데이터를 조작할 수 있도록 돕는 면도 있지만 컴퓨터 관점에서 <strong>도메인(모델)을 사용자 인터페이스와 분리해 순수한 상태로 보호</strong>하려는 측면도 중요하다고 보는 것입니다.</p>
<p>지금까지 살펴보았던 내용을 바탕으로 MVC를 다시 정리해 보면 다음과 같습니다.</p>
<ul>
<li>MVC는 최초 인간과 컴퓨터의 차이를 극복하고 효과적으로 데이터를 컨트롤하기 위해 모델과 뷰를 분리하면서 시작되었습니다.</li>
<li>이후 모델과 뷰 사이의 동기화 문제를 해결하기 위해 여러 개념이 정립되었고 컨트롤러, 프레젠터, 뷰모델 등의 개념이 등장하게 됩니다.</li>
</ul>
<h1 id="mvc와-리액트">MVC와 리액트</h1>
<p>그렇다면 React는 MVC와 어떤 사이일까요? 결론부터 말하자면 리액트는 전통적인 MVC와 거리가 있습니다. 이를 이해하기 위해선 MVC 패턴을 다시 데스크탑 기반의 전통적인 MVC와 Web MVC로 구분해야 합니다.</p>
<p>1970년대 데스크탑 어플리케이션을 구현하는 과정에서 처음 도입된 전통적인 MVC는 옵저버(Observer)를 기반으로 합니다. 모든 뷰(Views)와 컨트롤러(Controllers)는 모델(Model)을 관찰(Observe)하며 모델이 변경되면 뷰가 자동으로 업데이트 되는 방식으로 구현되었습니다. </p>
<p>그러나 이후 도입된 Web MVC는 다음과 같이 서버-클라이언트 구조를 기반으로 합니다. </p>
<ul>
<li>Model: 데이터 소스 (ex. Java Beans)</li>
<li>View: 서버에서 브라우저로 전달되는 완성된 뷰 (ex. JSP)</li>
<li>Controller: 데이터 조회 등 요청 전달 (ex. Servlet)</li>
</ul>
<p>Web MVC는 기존 MVC와 조금 다른 특성을 가집니다.</p>
<ul>
<li>기술적인 이유로 Model과 View 사이에 Observer Pattern을 사용하는 것이 어렵습니다</li>
<li>Controller가 사용자의 인터랙션이 아닌 HTTP 요청을 처리합니다</li>
</ul>
<p>Web MVC의 구조는 대부분 서버 사이드에서 구현되었기 때문에 현대의 SPA와는 완전히 다른 개념이었습니다. 그렇다면 리액트를 MVC 관점에 대입해 보면 ‘뷰(View)’를 담당한다고 볼 수 있지 않을까요?</p>
<p>엄밀히 말하면 리액트는 서버에서 만든 ‘뷰’를 그대로 보여주는 것이 아닌, ‘데이터’를 받아 브라우저에서 생성하기 때문에 전통적인 의미의 MVC 프레임워크로 볼 수 없습니다. 그러나 Next.js의 SSR을 이용한다면 비슷하게 구현할 수는 있습니다.</p>
<p>또한 React를 이용해 작은 MVC를 구현한다고 하면 다음과 같이 생각할 수도 있습니다.</p>
<ul>
<li>Model: 비동기 데이터</li>
<li>View: 컴포넌트</li>
<li>Controller: Hooks</li>
</ul>
<p>이러한 구분은 개념적인 비유는 될 수 있지만 엄밀한 의미의 MVC와는 차이가 있습니다. 그렇다면 React가 아니더라도 JS로 엄밀한 MVC를 구현하는 것은 가능할까요?</p>
<h1 id="mvc와-js">MVC와 JS</h1>
<p>실제로 과거 순수한 MVC 패턴에 충실한 JS 프레임워크가 존재합니다. 바로 Maria.js로 레포의 <a href="https://github.com/petermichaux/maria/tree/master/eg/checkit/src/js">Todo 예제</a>를 보면 전통적인 Smalltalk-80 MVC의 구현을 쉽게 살펴볼 수 있습니다.</p>
<pre><code class="language-js">// TodoModel.js
maria.Model.subclass(checkit, &#39;TodoModel&#39;, {
    attributes: {
        content: {
            type: &#39;string&#39;,
            trim: true
        },
        done: {
            type: &#39;boolean&#39;
        }
    }
});</code></pre>
<p>모델의 경우 content 및 일정의 완료(done) 상태와 같은 attribute로 이루어져 있습니다.</p>
<pre><code class="language-js">// TodoInputController.js
maria.Controller.subclass(checkit, &#39;TodosInputController&#39;, {
    properties: {
        onFocusInput: function () {
            this.onKeyupInput();
        },
        onBlurInput: function () {
            this.getView().setPending(false);
        },
        onKeyupInput: function () {
            var view = this.getView();
            view.setPending(!checkit.isBlank(view.getInputValue()));
        },
        onKeypressInput: function (evt) {
            if (evt.keyCode == 13) {
                var view = this.getView();
                var value = view.getInputValue();
                if (!checkit.isBlank(value)) {
                    var todo = new checkit.TodoModel();
                    todo.setContent(value);
                    this.getModel().add(todo);
                    view.clearInput();
                }
            }
        }
    }
});</code></pre>
<p>컨트롤러는 FocusInput, KeyPressInput과 같은 사용자 인터랙션을 처리합니다.</p>
<pre><code class="language-js">// TodoInputView.js
maria.ElementView.subclass(checkit, &#39;TodosInputView&#39;, {
    uiActions: {
        &#39;focus    .content&#39;: &#39;onFocusInput&#39;   ,
        &#39;blur     .content&#39;: &#39;onBlurInput&#39;    ,
        &#39;keyup    .content&#39;: &#39;onKeyupInput&#39;   ,
        &#39;keypress .content&#39;: &#39;onKeypressInput&#39;
    },
    properties: {
        getInputValue: function () {
            return this.find(&#39;.content&#39;).value;
        },
        clearInput: function () {
            this.find(&#39;.content&#39;).value = &#39;&#39;;
        },
        setPending: function (pending) {
            aristocrat[pending ? &#39;addClass&#39; : &#39;removeClass&#39;](
                this.find(&#39;.TodosInput&#39;), &#39;TodosInputPending&#39;);
        }
    }
});</code></pre>
<p>뷰는 content의 value 등을 UI로 보여줍니다.</p>
<h3 id="references">References</h3>
<p><a href="https://martinfowler.com/eaaDev/uiArchs.html">Martin Fowler - GUI Architectures</a>
<a href="https://folk.universitetetioslo.no/trygver/themes/mvc/mvc-index.html">Trygve Reenskaug - MVC XEROX PARC 1978-79</a>
<a href="https://blog.naver.com/jukrang/221597914483">김코딩 님이 잘하고 싶어 만든 블로그 - MV-Whatever 정리</a>
<a href="https://product.kyobobook.co.kr/detail/S000213880201">애디 오스마니 - 자바스크립트 + 리액트 디자인 패턴</a>
<a href="https://github.com/petermichaux/maria">Peter Michaux - Maria.js</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[번역] Martin Fowler - GUI Architectures]]></title>
            <link>https://velog.io/@seoyong-lee/%EB%B2%88%EC%97%AD-Martin-Fowler-GUI-Architectures</link>
            <guid>https://velog.io/@seoyong-lee/%EB%B2%88%EC%97%AD-Martin-Fowler-GUI-Architectures</guid>
            <pubDate>Sat, 01 Feb 2025 02:44:26 GMT</pubDate>
            <description><![CDATA[<p>[Martin Fowler의 2006년 글 &#39;GUI Architectures&#39;에 대한 번역입니다.](<a href="https://martinfowler.com/eaaDev/uiArchs.html">https://martinfowler.com/eaaDev/uiArchs.html</a>)</p>
<h1 id="서문">서문</h1>
<p>그래픽 사용자 인터페이스(GUI)는 사용자와 소프트웨어 시스템 간에 풍부한 상호작용을 제공합니다. 하지만 이러한 풍부한 상호작용은 관리하기 복잡하기 때문에, 신중한 아키텍처 설계를 통해 복잡성을 제어하는 것이 중요합니다. Forms and Controls 패턴은 단순한 흐름을 가진 시스템에서는 잘 동작하지만, 복잡성이 증가하면 무너지는 경향이 있습니다. 그래서 대부분의 사람들은 “Model-View-Controller” (MVC) 패턴을 선택합니다. 그러나 안타깝게도 MVC는 가장 오해를 많이 받는 아키텍처 패턴 중 하나이며, 이 이름을 사용하는 시스템들은 서로 중요한 차이를 보입니다. 이러한 차이점을 설명하기 위해 Application Model, Model-View-Presenter(MVP), Presentation Model, MVVM 등의 용어가 사용되기도 합니다. MVC를 이해하는 가장 좋은 방법은 그것을 하나의 구체적인 구현이 아니라, &quot;프레젠테이션과 도메인 로직을 분리하는 원칙&quot; 및 &quot;이벤트(옵저버 패턴)를 통해 프레젠테이션 상태를 동기화하는 방식&quot; 으로 보는 것입니다.</p>
<p><strong><a href="https://martinfowler.com/">Martin Fowler</a> - 18 July 2006</strong></p>
<p>그래픽 사용자 인터페이스(GUI)는 사용자와 개발자 모두에게 익숙한 소프트웨어 환경의 일부가 되었습니다. 디자인 관점에서 보면, GUI는 시스템 설계에서 특정한 문제들을 제기하며, 이러한 문제를 해결하기 위해 여러 가지 유사하지만 서로 다른 해결책들이 등장해 왔습니다.</p>
<p>내 관심사는 리치 클라이언트 개발(Rich-Client Development)을 위한 애플리케이션 개발자들이 활용할 수 있는 공통적이고 유용한 패턴을 식별하는 것입니다. 프로젝트 리뷰를 통해 다양한 설계를 보았고, 보다 공식적으로 문서화된 설계들도 접했습니다. 이러한 설계 안에는 유용한 패턴들이 포함되어 있지만, 이를 명확하게 설명하는 것은 종종 쉽지 않습니다. 예를 들어 Model-View-Controller(MVC)를 생각해 보겠습니다. MVC는 흔히 하나의 패턴이라고 불리지만, 나는 이를 단순한 패턴으로 보기 어렵다고 생각합니다. 왜냐하면 MVC는 여러 가지 다른 개념들을 포함하고 있기 때문입니다. 사람들이 서로 다른 출처에서 MVC에 대해 읽으면서 각기 다른 개념을 받아들이고, 이를 &#39;MVC&#39;라고 설명하는 경우가 많습니다. 여기에 더해 Semantic Diffusion(의미 확산)이라는 현상까지 발생합니다. 즉, 사람들이 MVC를 오해하면서 시간이 지나면서 원래의 개념과는 다른 방식으로 변형되어 사용되는 문제가 생깁니다.</p>
<p>이 글에서 나는 여러 흥미로운 아키텍처를 탐구하고, 그중 가장 흥미로운 특징들에 대한 나의 해석을 설명하고자 합니다. 이를 통해, 내가 설명하는 패턴들을 이해하는 데 도움이 될 수 있는 맥락을 제공하고자 합니다.</p>
<p>어느 정도 이 글을 UI 디자인의 개념이 여러 아키텍처를 거치며 발전해 온 지적 역사(intellectual history)로 볼 수도 있습니다. 하지만 이에 대해 한 가지 주의를 당부하고 싶습니다. 아키텍처를 이해하는 것은 쉽지 않은데, 그 이유는 많은 아키텍처가 변화하거나 사라지기 때문입니다. 아이디어의 확산 경로를 추적하는 것은 더욱 어렵습니다. 같은 아키텍처를 보고도 사람들이 서로 다른 해석을 하기 때문입니다. 특히, 나는 여기서 다루는 아키텍처들에 대해 철저한 조사를 수행한 것이 아닙니다. 대신, 널리 알려진 설명들을 참고하였으며, 만약 그 설명들이 어떤 중요한 요소를 빠뜨렸다면, 나 역시 그것에 대해 전혀 알지 못합니다. 따라서 내가 제공하는 설명을 절대적인 권위(authoritative) 있는 해석으로 받아들이지 마십시오. 또한, 내가 다루는 내용 중 일부는 관련성이 낮다고 판단하면 생략하거나 단순화하였습니다. 왜냐하면, 내 주요 관심사는 이 아키텍처들의 역사 자체가 아니라, 그 속에 담긴 근본적인 패턴들이기 때문입니다.</p>
<p>(여기에는 약간의 예외가 있는데, 나는 Smalltalk-80 환경에서 실행되는 MVC를 직접 살펴볼 기회가 있었습니다. 물론 이 조사 역시 철저한 분석(exhaustive)이라고 할 수는 없지만, 일반적인 설명에서 다루지 않는 요소들을 발견할 수 있었습니다. 이러한 경험은 내가 이 글에서 설명하는 다른 아키텍처들에 대한 묘사에도 더욱 신중해야 함을 깨닫게 해주었습니다. 만약 여러분이 이러한 아키텍처 중 하나에 익숙하고, 내가 중요한 내용을 잘못 설명했거나 누락했다는 점을 발견한다면, 꼭 알려주셨으면 합니다. 또한, 이 주제에 대한 보다 철저한 연구가 학문적으로도 가치 있는 연구 대상이 될 수 있다고 생각합니다.)</p>
<h1 id="forms-and-controls">Forms and Controls</h1>
<p>나는 이 탐구를 단순하면서도 익숙한 아키텍처로부터 시작하려 합니다. 이 아키텍처는 일반적으로 통용되는 이름이 없기 때문에, 이 글에서는 “Forms and Controls”라고 부르겠습니다. 이 아키텍처는 익숙한 방식인데, 그 이유는 1990년대의 클라이언트-서버 개발 환경에서 장려되었기 때문입니다. 예를 들면 Visual Basic, Delphi, PowerBuilder 같은 도구들이 이를 기반으로 했습니다. 이 방식은 여전히 널리 사용되지만, 디자인을 중시하는 사람들(나 같은 디자인 마니아들)에게는 종종 비판을 받기도 합니다.</p>
<p>이 아키텍처를 탐구하는 데 있어, 그리고 이후에 다룰 다른 아키텍처들을 설명하는 데 있어서도 공통된 예제를 사용할 것입니다. 내가 살고 있는 뉴잉글랜드(New England) 지역에는 대기 중의 아이스크림 입자 농도를 모니터링하는 정부 프로그램이 있습니다. 만약 이 농도가 너무 낮다면, 이는 우리가 충분한 아이스크림을 먹고 있지 않다는 신호이며, 이는 경제와 공공 질서에 심각한 위협이 됩니다. (나는 보통 이런 주제의 책에서 나오는 예제만큼 현실적인 예제를 사용하는 걸 좋아합니다.)</p>
<p><img src="https://velog.velcdn.com/images/seoyong-lee/post/e01ff616-af49-436d-b508-a7837ddcdb97/image.png" alt="Figure 1: The UI I&#39;ll use as an example."></p>
<p>이 화면을 살펴보면, 이를 구성하는 요소들 사이에 중요한 구분이 있다는 것을 알 수 있습니다.</p>
<ul>
<li><strong>폼(Form)</strong>: 특정 애플리케이션에 맞춰져 있음.</li>
<li><strong>컨트롤(Controls)</strong>: 보다 범용적인 요소들로 구성됨.</li>
</ul>
<p>대부분의 GUI 환경은 이미 다양한 공통 컨트롤(예: 버튼, 텍스트 입력 필드, 체크박스 등)을 제공하며, 우리는 이를 애플리케이션에서 바로 활용할 수 있습니다. 또한, 필요에 따라 새로운 컨트롤을 직접 만들어 추가하는 것도 가능하며, 이는 종종 바람직한 방법이 되기도 합니다.</p>
<p>그러나 새로운 컨트롤을 만들더라도, &quot;재사용 가능한 컨트롤&quot;과 &quot;특정 폼&quot; 사이에는 여전히 명확한 구분이 존재합니다. 즉, 특별히 작성된 컨트롤이라 하더라도, 여러 개의 폼에서 재사용할 수 있다는 점이 중요합니다.</p>
<p>폼(Form)은 두 가지 주요한 역할을 담당합니다.</p>
<ul>
<li><strong>화면 레이아웃(Screen Layout):</strong> 계층적 구조(hierarchic structure)를 설정하여 화면에서 컨트롤들을 어떻게 배치할 것인지 정의합니다.</li>
<li><strong>폼 로직(Form Logic):</strong> 개별 컨트롤에 직접 프로그래밍하기 어려운 동작을 처리합니다.</li>
</ul>
<p>대부분의 GUI 개발 환경에서는 그래픽 편집기(Graphical Editor)를 제공하여, 개발자가 드래그 앤 드롭(Drag and Drop) 방식으로 폼에 컨트롤을 배치할 수 있도록 합니다. 이 방식은 폼 레이아웃(Form Layout)을 직관적으로 구성할 수 있게 해주며, 사용자 친화적인 UI를 빠르게 설계하는 데 유용합니다. (그러나, 이 방식이 항상 최선의 방법은 아닐 수도 있습니다. 이에 대해서는 나중에 더 자세히 다룰 것입니다.)</p>
<p>컨트롤들은 데이터를 표시하는 역할을 합니다. 이 경우에는 측정값에 대한 데이터를 표시합니다. 이 데이터는 거의 항상 어딘가에서 가져오는 것이며, 여기서는 대부분의 클라이언트-서버 도구 환경에서 가정하는 것처럼 SQL 데이터베이스에서 온다고 가정하겠습니다. 대부분의 상황에서 데이터의 세 가지 복사본이 존재합니다.</p>
<ul>
<li>데이터의 한 복사본은 데이터베이스 자체에 존재합니다. 이 복사본은 데이터의 영구적인 기록이므로, 이를 레코드 상태(Record State)라고 부릅니다. 레코드 상태는 일반적으로 여러 사람이 다양한 메커니즘을 통해 공유하고 볼 수 있는 데이터입니다.</li>
<li>또 다른 복사본은 애플리케이션 내 메모리의 레코드 세트(Record Sets) 안에 존재합니다. 대부분의 클라이언트-서버 환경에서는 이를 쉽게 처리할 수 있도록 도구를 제공합니다. 이 데이터는 애플리케이션과 데이터베이스 간의 특정 세션(session) 동안에만 유효하기 때문에, 나는 이를 세션 상태(Session State)라고 부릅니다. 본질적으로, 세션 상태는 사용자가 데이터를 작업하는 동안 임시 로컬 버전을 제공하며, 사용자가 데이터를 저장(Save)하거나 커밋(Commit)하면, 이 데이터는 다시 데이터베이스와 병합되어 레코드 상태(Record State)로 반영됩니다. (레코드 상태와 세션 상태를 조정하는 문제는 여기서 다루지 않겠습니다. 이와 관련된 다양한 기법들은 [P of EAA]에서 설명했습니다.)</li>
<li>마지막 복사본은 GUI 컴포넌트 자체에 존재합니다. 엄밀히 말하면, 이것이 화면에서 사용자에게 보이는 데이터이므로, 나는 이를 스크린 상태(Screen State)라고 부릅니다. UI에서 중요한 점은 스크린 상태와 세션 상태가 어떻게 동기화되는가 하는 것입니다.</li>
</ul>
<p>스크린 상태(Screen State)와 세션 상태(Session State)를 동기화하는 것은 중요한 작업입니다. 이를 더 쉽게 만들기 위해 사용된 도구 중 하나가 데이터 바인딩(Data Binding)입니다. 데이터 바인딩의 개념은 컨트롤의 데이터 또는 기본 레코드 세트가 변경될 때, 즉시 상대방에도 변경 사항이 반영되는 것입니다. 예를 들어, 사용자가 화면에서 측정값을 변경하면, 텍스트 필드 컨트롤이 자동으로 기본 레코드 세트의 해당 컬럼을 업데이트하는 방식입니다.</p>
<p>일반적으로 데이터 바인딩(Data Binding)은 까다로운 작업이 됩니다. 그 이유는, 컨트롤의 변경이 레코드 세트를 변경시키고, 그 결과로 레코드 세트가 다시 컨트롤을 업데이트하며, 다시 레코드 세트를 업데이트하는 순환(cycle)이 발생할 가능성이 있기 때문입니다. 그러나 사용 흐름(flow of usage)이 이러한 문제를 방지하는 데 도움이 됩니다. 화면을 열 때, 데이터를 세션 상태(Session State)에서 스크린 상태(Screen State)로 로드합니다. 그 이후에는 스크린 상태에서 세션 상태로 변경 사항이 전파됩니다. 화면이 표시된 이후, 세션 상태가 직접 수정되는 경우는 드뭅니다. 이러한 이유로, 데이터 바인딩이 완전히 양방향(Bi-Directional)으로 이루어지는 것은 아닙니다. 대부분의 경우, 데이터 바인딩은 초기 업로드(세션 상태 → 스크린 상태)와 이후 변경 사항 전파(스크린 상태 → 세션 상태)에 한정됩니다.</p>
<p>데이터 바인딩(Data Binding)은 클라이언트-서버 애플리케이션의 많은 기능을 꽤 잘 처리합니다. 실제 값을 변경하면, 컬럼이 업데이트됩니다. 심지어 선택된 측정소(station)를 변경하는 것만으로도 현재 선택된 행이 레코드 세트에서 변경되며, 그 결과 다른 컨트롤들이 새롭게 갱신(refresh)됩니다.</p>
<p>이러한 동작의 대부분은 프레임워크 개발자들이 미리 구현해 둡니다. 그들은 일반적인 요구 사항을 분석하고, 이를 쉽게 해결할 수 있도록 설계합니다. 특히, 이러한 기능은 컨트롤에 값을 설정하는 방식으로 이루어집니다. 이 값들은 일반적으로 속성(Properties)이라고 불립니다. 컨트롤은 속성 편집기(Property Editor)를 통해 특정 레코드 세트(Record Set)의 컬럼(Column)과 연결됩니다. 즉, 컬럼 이름을 간단한 속성으로 설정하면, 해당 컬럼과 컨트롤이 자동으로 바인딩됩니다.</p>
<p>데이터 바인딩을 사용하면, 적절한 매개변수 설정(parameterization)을 통해 많은 작업을 처리할 수 있습니다. 그러나 모든 경우를 처리할 수 있는 것은 아닙니다. 대부분의 경우, 매개변수 옵션으로 해결되지 않는 로직이 일부 존재합니다. 예를 들어, 분산(variance) 계산은 내장된 데이터 바인딩 기능으로 처리할 수 없는 작업입니다. 이것은 애플리케이션에 특화된(application-specific) 로직이므로, 일반적으로 폼(Form) 내에 구현됩니다.</p>
<p>이 기능이 제대로 동작하려면, 폼(Form)은 실제 필드 값이 변경될 때마다 이를 감지해야 합니다. 이를 위해, 범용적인(Generic) 텍스트 필드가 폼 내의 특정 동작을 호출하도록 해야 합니다. 이 과정은 단순히 클래스 라이브러리를 가져와 호출하는 것보다 더 복잡한 작업입니다. 그 이유는 제어의 역전(Inversion of Control, IoC)이 개입되기 때문입니다.</p>
<p>이러한 기능을 구현하는 방법에는 여러 가지가 있습니다. 클라이언트-서버 툴킷에서 일반적으로 사용된 방법은 이벤트(Events) 개념입니다. 각 컨트롤(Control)은 발생시킬 수 있는 이벤트 목록을 가지고 있습니다. 어떤 외부 객체든 특정 이벤트에 관심이 있다고 등록하면, 컨트롤이 그 이벤트를 발생시킬 때 해당 객체를 호출하게 됩니다. 본질적으로, 이것은 옵저버 패턴(Observer Pattern)의 변형입니다. 즉, 폼(Form)이 컨트롤을 관찰(Observe)하는 구조가 됩니다. 프레임워크는 일반적으로, 개발자가 폼 내에서 특정 이벤트가 발생했을 때 실행할 서브루틴(Subroutine) 형태의 코드를 작성할 수 있도록 메커니즘을 제공했습니다. 이벤트와 서브루틴이 어떻게 연결되는지는 플랫폼마다 다르며, 이 글의 논의에서 중요한 부분은 아닙니다. 핵심은, 이러한 기능을 가능하게 하는 어떤 방식이 존재했다는 점입니다.</p>
<p>폼(Form) 내의 서브루틴(Subroutine)이 제어권을 갖게 되면, 필요한 동작을 수행할 수 있습니다. 폼은 특정 동작을 실행하고, 필요한 경우 컨트롤(Control)을 수정할 수 있습니다. 이후, 데이터 바인딩(Data Binding)을 활용하여 변경 사항이 자동으로 세션 상태(Session State)에 반영되도록 합니다.</p>
<p>이것이 필요한 또 다른 이유는 데이터 바인딩이 항상 존재하는 것은 아니기 때문입니다. 윈도우 컨트롤 시장은 크며, 모든 컨트롤이 데이터 바인딩을 지원하는 것은 아닙니다. 만약 데이터 바인딩이 존재하지 않는다면, 폼이 동기화를 수행해야 합니다. 이것은 다음과 같은 방식으로 작동할 수 있습니다.</p>
<ul>
<li>초기에는 레코드 세트에서 데이터를 가져와 위젯(Widgets)에 채웁니다.</li>
<li>저장(Save) 버튼이 눌리면, 변경된 데이터를 다시 레코드 세트(Record Set)로 복사(Copy Back) 합니다.</li>
</ul>
<p>데이터 바인딩이 존재한다고 가정하고, 실제 값(actual value)을 편집하는 과정을 살펴보겠습니다. 폼(Form) 객체는 범용적인(Generic) 컨트롤에 대한 직접적인 참조(Direct References)를 보유하고 있습니다. 화면에 있는 각 컨트롤마다 하나씩 참조가 존재하지만, 여기서는 실제 값(Actual), 분산(Variance), 목표 값(Target) 필드에만 집중하겠습니다.</p>
<p>텍스트 필드는 &quot;텍스트 변경(Text Changed)&quot; 이벤트를 선언합니다. 폼(Form)이 화면을 초기화할 때, 해당 이벤트에 자신을 구독(Subscribe) 시킵니다. 그리고 자신의 특정 메서드(actual_textChanged)에 바인딩합니다.</p>
<p>사용자가 실제 값을 변경하면, 텍스트 필드 컨트롤은 해당 이벤트를 발생시키고, 프레임워크 바인딩의 마법을 통해 actual_textChanged가 실행됩니다. 이 메서드는 실제 값과 목표 값을 텍스트 필드에서 가져와서 빼기를 수행한 후, 그 값을 분산(variance) 필드에 넣습니다. 또한, 그 값이 어떤 색으로 표시되어야 하는지를 결정하고 텍스트 색상을 적절하게 조정합니다.</p>
<p>이 아키텍처를 몇 가지 핵심 문장으로 요약할 수 있습니다.</p>
<ul>
<li>개발자는 애플리케이션에 특화된 폼을 작성하며, 이는 범용적인 컨트롤(Generic Controls)을 사용합니다.</li>
<li>폼은 자신 안의 컨트롤들의 레이아웃을 정의합니다.</li>
<li>폼은 컨트롤을 관찰(Observe)하며, 컨트롤이 발생시키는 중요한 이벤트에 반응하는 핸들러 메서드(Handler Methods)를 가집니다.</li>
<li>단순한 데이터 편집은 데이터 바인딩(Data Binding)을 통해 처리됩니다.</li>
<li>복잡한 변경 사항은 폼의 이벤트 핸들링 메서드에서 수행됩니다.</li>
</ul>
<h1 id="model-view-controller">Model View Controller</h1>
<p>아마도 UI 개발에서 가장 널리 인용되는 패턴은 모델-뷰-컨트롤러(Model-View-Controller, MVC)일 것입니다. 하지만 동시에 가장 잘못 인용되는 패턴이기도 합니다. MVC라고 설명된 것을 수없이 보았지만, 실제로는 전혀 MVC가 아닌 경우가 많았습니다. 솔직히 말해, 이러한 혼란이 발생하는 주요 이유 중 하나는 고전적인 MVC(Classic MVC)의 일부 개념이 현대의 리치 클라이언트(Rich Client) 환경에서는 잘 맞지 않기 때문입니다. 하지만 지금은 일단 MVC의 기원(Origins)부터 살펴보도록 하겠습니다.</p>
<p>MVC를 살펴볼 때, 이 패턴이 UI 작업을 체계적으로 수행하려는 최초의 시도 중 하나였다는 점을 기억하는 것이 중요합니다. 1970년대에는 그래픽 사용자 인터페이스(GUI) 자체가 흔하지 않았습니다. 앞서 설명한 폼과 컨트롤(Forms and Controls) 모델은 MVC 이후에 등장했습니다. 이를 먼저 설명한 이유는 더 단순하기 때문이며, 단순하다고 해서 항상 좋은 것은 아닙니다. 다시 한 번, Smalltalk-80의 MVC를 평가(Assessment) 예제를 통해 설명하겠습니다. 다만, 이를 설명하는 과정에서 실제 Smalltalk-80의 세부 사항과는 다소 차이가 있을 수 있습니다. 예를 들어, Smalltalk-80은 기본적으로 모노크롬(monochrome, 흑백) 시스템이었습니다.</p>
<p>MVC의 핵심 개념이자 이후의 다양한 프레임워크에 가장 큰 영향을 준 아이디어는 &quot;분리된 프레젠테이션(Separated Presentation)&quot;입니다. 분리된 프레젠테이션(Separated Presentation)의 개념은, 현실 세계를 모델링하는 도메인 객체(Domain Objects)와 화면에서 보이는 GUI 요소인 프레젠테이션 객체(Presentation Objects)를 명확하게 분리하는 것입니다. 도메인 객체(Domain Objects)는 완전히 독립적(self-contained)이어야 하며, 프레젠테이션과의 직접적인 참조 없이 동작 가능해야 합니다. 또한, 여러 개의 프레젠테이션을 동시에 지원할 수 있어야 합니다. 이러한 접근 방식은 유닉스(Unix) 문화에서도 중요한 개념이었으며, 오늘날에도 많은 애플리케이션이 GUI와 명령줄 인터페이스(Command-Line Interface, CLI)를 동시에 지원할 수 있도록 합니다.</p>
<p>MVC에서 도메인 요소(Domain Element)는 모델(Model)이라고 불립니다. 모델 객체(Model Objects)는 UI에 대해 완전히 무지(ignorant)해야 합니다. 우리의 평가 UI(Assessment UI) 예제를 시작하기 위해, 모델을 측정값(Reading)으로 가정하고, 이 객체가 관련 데이터를 저장하는 필드(fields)를 가진다고 하겠습니다. (잠시 후에 설명하겠지만, 리스트 박스(List Box)의 존재로 인해 &quot;모델이 무엇인가?&quot;라는 질문이 더 복잡해질 것입니다. 하지만 지금은 리스트 박스를 무시하고 논의를 진행하겠습니다.)</p>
<p>MVC에서는 도메인 모델(Domain Model)을 일반적인 객체(Regular Objects) 기반으로 가정합니다. 이는 앞서 설명한 폼과 컨트롤(Forms and Controls) 모델의 &quot;레코드 세트(Record Set)&quot; 개념과는 다릅니다. 이러한 차이는 각 모델이 전제하는 기본 가정의 차이를 반영합니다. 폼과 컨트롤(Forms and Controls)은 대부분의 사람들이 관계형 데이터베이스(Relational Database)의 데이터를 쉽게 조작하는 것을 원한다고 가정합니다. MVC(Model-View-Controller)는 우리가 조작하는 것이 일반적인 Smalltalk 객체(Smalltalk Objects)라고 가정합니다.</p>
<p>MVC의 프레젠테이션(Presentation) 부분은 남은 두 요소인 뷰(View)와 컨트롤러(Controller)로 구성됩니다. 컨트롤러(Controller)의 역할은 사용자의 입력(User Input)을 받아 이를 어떻게 처리할지 결정하는 것입니다.</p>
<p>이 시점에서 강조해야 할 점은, 뷰(View)와 컨트롤러(Controller)가 단 하나만 존재하는 것이 아니라는 것입니다. 화면의 각 요소마다 뷰-컨트롤러(View-Controller) 쌍이 존재합니다. 즉, 각 컨트롤(Control)과 전체 화면(Screen)에도 개별적인 뷰와 컨트롤러가 할당됩니다. 따라서 사용자의 입력(User Input)에 반응하는 첫 번째 단계는 여러 컨트롤러들이 협력하여 어느 컨트롤이 편집되었는지를 확인하는 과정입니다. 이 예제에서는 &quot;실제 값(Actuals)&quot; 텍스트 필드가 편집되었기 때문에, 이제 해당 텍스트 필드의 컨트롤러가 다음 동작을 처리하게 됩니다.</p>
<p>다른 이후의 환경들과 마찬가지로, Smalltalk도 재사용 가능한(Generic) UI 컴포넌트가 필요하다는 점을 깨달았습니다. 이 경우, 재사용 가능한 컴포넌트는 &quot;뷰-컨트롤러(View-Controller) 쌍&quot;이 됩니다. 두 요소 모두 범용적인(Generic) 클래스이므로, 이를 애플리케이션별(Application-Specific) 동작에 맞게 연결해야 합니다. 평가 화면(Assessment View)은 전체 화면을 나타내며, 하위 컨트롤들의 레이아웃을 정의합니다. 이 점에서 보면, 이는 폼과 컨트롤(Forms and Controls) 모델에서의 &quot;폼(Form)&quot;과 유사합니다. 그러나 폼과 달리, MVC에서는 평가 컨트롤러(Assessment Controller)가 하위 구성 요소들의 이벤트 핸들러(Event Handlers)를 포함하지 않습니다.</p>
<p>텍스트 필드의 설정은 모델인 reading과의 링크를 제공하고, 텍스트가 변경될 때 호출할 메서드를 지정하는 것에서 시작됩니다. 화면이 초기화될 때, 이 메서드는 #actual:로 설정됩니다. (앞에 붙은 # 기호는 Smalltalk에서 기호(Symbol) 또는 내부 문자열(Interned String)을 의미합니다.) 텍스트 필드 컨트롤러는 reading 객체에서 해당 메서드를 반사적 호출(Reflective Invocation)하여 변경 사항을 적용합니다. 본질적으로, 이 방식은 데이터 바인딩(Data Binding)과 동일한 메커니즘을 따릅니다. 즉, 컨트롤은 기본 객체(행, Row)에 연결되며, 해당 객체에서 조작할 메서드(열, Column)를 지정받는 구조입니다.</p>
<p>따라서 전체적인 객체가 하위 위젯(Low-Level Widgets)을 관찰하는 것이 아니라, 하위 위젯들이 모델(Model)을 관찰하며, 모델 자체가 폼(Form)이 수행해야 할 많은 결정을 처리하게 됩니다. 이 경우, 분산(Variance)을 계산하는 작업은 reading 객체 자체가 수행하는 것이 자연스럽습니다.</p>
<p>MVC에서 옵저버(Observer)는 중요한 역할을 하며, 실제로 MVC가 발전시키는 데 기여한 개념 중 하나로 여겨집니다. 이 경우, 모든 뷰(Views)와 컨트롤러(Controllers)는 모델(Model)을 관찰(Observe)합니다. 즉, 모델이 변경되면, 뷰가 이에 반응합니다. 예를 들어, 실제 값(Actual) 텍스트 필드 뷰는 reading 객체의 변경 사항을 감지하고, 해당 텍스트 필드의 속성(Aspect)으로 정의된 메서드(#actual)를 호출하여 그 결과를 자신의 값(Value)으로 설정합니다. (이와 유사한 방식으로 색상(Color)도 업데이트되지만, 이 과정에는 별도의 문제가 발생할 수 있으며, 이에 대해서는 곧 다루겠습니다.)</p>
<p>여기서 주목해야 할 점은, 텍스트 필드 컨트롤러가 직접 뷰(View)의 값을 설정하지 않았다는 것입니다. 대신 모델(Model)을 업데이트한 후, 옵저버(Observer) 메커니즘이 변경 사항을 처리하도록 맡겼습니다. 이것은 폼과 컨트롤(Forms and Controls) 방식과 상당히 다른 접근 방식입니다. 폼과 컨트롤(Forms and Controls) 방식에서는 폼이 컨트롤(Control)의 값을 직접 업데이트하고, 데이터 바인딩(Data Binding)이 이를 기본 레코드 세트(Record Set)로 반영합니다. 이 두 가지 방식을 각각 &quot;흐름 동기화(Flow Synchronization)&quot;와 &quot;옵저버 동기화(Observer Synchronization)&quot; 패턴(Pattern)으로 설명해 보겠습니다. 이 패턴들은 스크린 상태(Screen State)와 세션 상태(Session State)의 동기화를 트리거(Trigger)하는 서로 다른 방법을 설명합니다.</p>
<ul>
<li>폼과 컨트롤(Forms and Controls) 방식<ul>
<li>애플리케이션의 흐름(Flow)에 따라 업데이트가 필요한 여러 컨트롤을 직접 조작하는 방식을 사용합니다.</li>
</ul>
</li>
<li>MVC(Model-View-Controller) 방식<ul>
<li>모델(Model)에 변경 사항을 적용하고, 이를 관찰하는(Observing) 뷰(View)가 자동으로 업데이트되도록 합니다.</li>
</ul>
</li>
</ul>
<p>흐름 동기화(Flow Synchronization)는 데이터 바인딩이 존재하지 않을 때 더욱 뚜렷하게 나타납니다. 애플리케이션이 직접 동기화(Synchronization)를 수행해야 할 경우, 보통 애플리케이션 흐름(Flow)에서 중요한 지점에서 동기화가 이루어집니다. 예를 들면, 화면을 열 때(Screen Opening), 저장 버튼을 눌렀을 때(Hitting the Save Button) 등의 시점에서 애플리케이션이 데이터를 동기화하는 방식이 일반적입니다.</p>
<p>옵저버 동기화(Observer Synchronization)의 결과 중 하나는, 컨트롤러(Controller)가 사용자가 특정 위젯을 조작할 때 다른 위젯들이 어떻게 변경되어야 하는지 거의 알 필요가 없다는 것입니다. 폼과 컨트롤(Forms and Controls) 방식에서는 폼(Form)이 변경 사항을 추적하고, 화면 전체의 상태(Screen State)가 일관성을 유지하도록 직접 조정해야 합니다. 특히 화면이 복잡할수록(Form이 관리해야 할 요소가 많아질수록) 이 과정은 더욱 복잡해집니다. 반면, 옵저버 동기화 방식에서는 컨트롤러는 이 모든 것을 신경 쓰지 않아도 됩니다. 변경 사항이 모델(Model)에 반영되면, 옵저버 패턴(Observer Pattern)에 의해 필요한 뷰(View)들이 자동으로 업데이트됩니다.</p>
<p>이러한 &quot;유용한 무지(Useful Ignorance)&quot;는 하나의 모델 객체(Model Object)를 여러 개의 화면(Screen)에서 동시에 보고 있을 때 특히 유용합니다. 클래식한 MVC의 예제(Classic MVC Example)로는 스프레드시트(Spreadsheet) 스타일의 데이터 화면과 동일한 데이터를 시각화하는 여러 개의 그래프 창을 예로 들 수 있습니다. 이러한 경우, 스프레드시트 창은 자신과 함께 열려 있는 다른 창들을 신경 쓸 필요가 없습니다. 그저 모델만 변경하면, 옵저버 동기화(Observer Synchronization)에 의해 자동으로 다른 뷰들이 업데이트됩니다. 반면, 흐름 동기화(Flow Synchronization) 방식에서는 현재 열려 있는 다른 창들을 파악할 방법이 필요하며, 각 창에 직접 &quot;새로고침(Refresh)&quot;을 요청해야 합니다.</p>
<p>옵저버 동기화(Observer Synchronization)는 편리하지만, 단점도 존재합니다. 옵저버 동기화의 문제점은 옵저버 패턴(Observer Pattern) 자체의 근본적인 문제와 동일합니다. 즉, 코드를 읽는 것만으로는 무슨 일이 일어나고 있는지 파악하기 어렵다는 점입니다. 나는 과거에 Smalltalk-80에서 특정 화면들이 어떻게 동작하는지 분석하려고 했을 때, 이 문제를 매우 강하게 실감했습니다. 코드를 읽는 것만으로는 어느 정도까지만 이해할 수 있었습니다. 하지만 옵저버 메커니즘이 작동하는 순간부터, 더 이상 코드만으로는 무엇이 일어나는지 알 수 없었습니다. 결국 디버거와 Trace Statements을 사용해야만 실제 동작을 파악할 수 있었습니다. 이처럼 옵저버 기반 동작(Observer Behavior)은 &quot;암묵적 동작(Implicit Behavior)&quot;이기 때문에 이해하고 디버깅하기 어렵습니다.</p>
<p>동기화(Synchronization)에 대한 접근 방식의 차이는 시퀀스 다이어그램(Sequence Diagram)을 보면 특히 두드러지지만, MVC와 폼과 컨트롤(Forms and Controls) 방식 간의 가장 중요한 차이점이자 가장 큰 영향력을 가진 요소는 &quot;분리된 프레젠테이션(Separated Presentation)&quot;입니다. 예를 들어, 실제 값(Actual)과 목표 값(Target)의 차이를 계산하는 분산(Variance) 연산은 도메인(Domain) 로직이며, UI와는 관련이 없습니다. 따라서 &quot;분리된 프레젠테이션(Separated Presentation)&quot; 원칙을 따르면, 이 연산은 시스템의 도메인 계층(Domain Layer)에 위치해야 합니다. 그리고 바로 이 역할을 수행하는 것이 reading 객체입니다. 실제로 reading 객체를 살펴보면, 그 안의 &quot;분산(Variance) 계산 기능&quot;은 UI와는 무관하게 도메인 로직으로서 완전히 자연스럽게 존재합니다.</p>
<p>그러나 이제부터 몇 가지 복잡한 문제들을 살펴볼 수 있습니다. MVC 이론을 적용하는 데 방해가 되는 두 가지 난해한 부분이 있으며, 지금까지 이를 생략해 왔습니다. 첫 번째 문제는 분산(Variance)의 색상을 설정하는 것입니다. 값을 어떤 색으로 표시할지는 도메인의 일부가 아니므로, 이는 도메인 객체(Domain Object)에 포함되기 어렵습니다. 이를 해결하는 첫 번째 단계는, 논리의 일부가 도메인 로직(Domain Logic)에 속한다는 점을 인식하는 것입니다. 우리가 하는 작업은 분산 값(Variance)에 대한 정성적 판단(Qualitative Assessment)입니다. 예를 들어,</p>
<ul>
<li>좋음(Good) → 목표보다 5% 이상 초과</li>
<li>나쁨(Bad) → 목표보다 10% 이상 미달</li>
<li>보통(Normal) → 그 외의 경우</li>
</ul>
<p>이처럼 분산을 평가(Assessment)하는 것은 도메인 로직의 일부입니다. 하지만,</p>
<ul>
<li>이 평가 결과를 색상(Color)으로 매핑하는 것</li>
<li>화면의 분산 필드(Variance Field) 색상을 변경하는 것</li>
</ul>
<p>이 과정은 뷰 로직(View Logic)에 속합니다. 문제는 이 뷰 로직을 어디에 배치할 것인지입니다. 이 기능은 표준적인 텍스트 필드(Standard Text Field)의 일부가 아니므로, MVC 구조에서 이를 적절히 배치할 방법을 고민해야 합니다.</p>
<p>이러한 문제는 초기 Smalltalk 개발자들도 직면했던 문제이며, 그들은 이를 해결하기 위한 몇 가지 방법을 고안했습니다. 앞서 설명한 방식은 비교적 &quot;지저분한(dirty)&quot; 해결책으로, 즉, 도메인의 순수성을 일부 타협하여 문제를 해결하는 방법입니다. 나 또한 가끔은 이러한 &quot;순수하지 않은(Impure) 방식&quot;을 사용할 때가 있지만, 이를 습관처럼 사용하지 않도록 노력합니다.</p>
<p>우리는 폼과 컨트롤(Forms and Controls) 방식과 유사한 방법을 사용할 수도 있습니다. 즉, 평가 화면 뷰(Assessment Screen View)가 분산 필드 뷰(Variance Field View)를 관찰(Observe)하도록 설정하고, 분산 필드가 변경될 때 평가 화면이 반응하여 해당 필드의 텍스트 색상을 설정하는 방식입니다. 그러나 이 접근 방식에는 몇 가지 문제가 있습니다. 옵저버(Observer) 메커니즘을 더욱 많이 사용해야 하며, 옵저버를 많이 사용할수록 구조가 기하급수적으로 복잡해집니다. 또한, 여러 뷰(View) 간의 결합도(Coupling)가 증가하여 유지보수가 어려워질 수 있습니다.</p>
<p>내가 선호하는 방법은 새로운 유형의 UI 컨트롤을 만드는 것입니다. 본질적으로 우리가 필요한 것은, 도메인 객체에 질의하여 정성적(qualitative) 값을 가져오고, 내부에 저장된 값과 색상의 매핑 테이블과 비교한 후, 그에 따라 폰트 색상을 설정하는 UI 컨트롤입니다. 이 접근 방식에서는, 평가 화면 뷰(Assessment View)가 UI 컨트롤을 조립하는 과정에서, 이 컨트롤이 참조할 값-색상 매핑 테이블과 도메인 객체에 보낼 메시지를 함께 설정하면 됩니다. 이는 마치 모니터링할 필드의 속성(Aspect)을 설정하는 방식과 유사합니다. 이 방식은 특히 텍스트 필드(Text Field)를 쉽게 서브클래싱(Subclassing)하여 추가 동작을 구현할 수 있다면 효과적으로 작동할 수 있습니다. 물론, 이 접근 방식의 실현 가능성은 UI 컴포넌트가 서브클래싱을 얼마나 쉽게 허용하는지에 따라 달라집니다. Smalltalk 환경에서는 서브클래싱이 매우 용이했지만, 다른 환경에서는 구현이 더 어려울 수도 있습니다.</p>
<p>마지막 방법은 새로운 유형의 모델 객체(Model Object)를 만드는 것입니다. 이 모델 객체는 화면(Screen)을 중심으로 동작하지만, 개별 위젯(Widgets)에는 독립적인 구조를 가집니다. 즉, 화면 전체의 모델 역할을 수행하는 객체가 되는 것입니다. 이 모델 객체는 reading 객체와 동일한 메서드(Method)를 가지되, 내부적으로 해당 요청을 reading 객체로 위임(Delegate)합니다. 하지만, 추가적으로 UI에만 관련된 동작(예: 텍스트 색상 변경)을 지원하는 메서드도 포함할 수 있습니다.</p>
<p>이 마지막 방법은 여러 경우에서 효과적으로 작동하며, 실제로 Smalltalk 개발자들 사이에서 널리 사용된 방식이 되었습니다. 나는 이 개념을 프레젠테이션 모델(Presentation Model)이라고 부릅니다. 왜냐하면 이 모델은 UI(프레젠테이션 계층)에 맞춰 설계되었으며, 그 일부로 동작하기 때문입니다. (이 패턴은 MVVM(Model-View-ViewModel)이라는 이름으로도 알려져 있습니다.)</p>
<p>프레젠테이션 모델(Presentation Model)은 또 다른 프레젠테이션 로직 문제, 즉 프레젠테이션 상태(Presentation State)를 해결하는 데도 유용합니다. 기본적인 MVC 개념에서는, 뷰(View)의 모든 상태는 모델(Model)의 상태로부터 유도될 수 있다고 가정합니다. 하지만, 리스트 박스(List Box)에서 선택된 측정소(Station)를 어떻게 추적할 것인가? 같은 문제가 발생합니다. 프레젠테이션 모델은 이러한 문제를 해결하는 방법을 제공합니다. 즉, 이러한 상태를 저장할 적절한 위치를 제공하는 것입니다. 비슷한 문제는 &quot;저장(Save) 버튼이 데이터가 변경되었을 때만 활성화되는 경우&quot;에서도 발생합니다. 이 버튼이 활성화되는지 여부는 도메인 모델의 상태가 아니라, 모델과의 상호작용(Interaction)에서 발생하는 상태(State)입니다. 프레젠테이션 모델은 이러한 UI 관련 상태를 관리할 수 있는 공간을 제공하여 문제를 해결합니다.</p>
<p>이제 MVC에 대한 핵심 원칙(Soundbites)을 정리해 보겠습니다.</p>
<ul>
<li>프레젠테이션(View &amp; Controller)과 도메인(Model)을 강력하게 분리합니다 – Separated Presentation</li>
<li>GUI 위젯을 컨트롤러(사용자 입력 반응)와 뷰(모델의 상태 표시)로 나눕니다. 컨트롤러와 뷰는 직접적으로 통신하지 않고, 모델을 통해 간접적으로 상호작용해야 합니다.</li>
<li>뷰(View)와 컨트롤러(Controller)는 모델(Model)을 관찰(Observe)하여 변경 사항을 반영해야 합니다. 이를 통해 여러 위젯이 직접 통신할 필요 없이 동기화(Synchronization)될 수 있습니다 – Observer Synchronization</li>
</ul>
<h1 id="visualworks-application-model">VisualWorks Application Model</h1>
<p>앞서 논의했듯이, Smalltalk-80의 MVC는 매우 영향력이 컸으며 훌륭한 기능들을 갖추고 있었지만, 몇 가지 단점도 존재했습니다. 1980~1990년대 동안 Smalltalk이 발전하면서, 고전적인 MVC(Classic MVC) 모델에서 중요한 변형(Variations)이 등장하게 되었습니다. 실제로, MVC라는 개념이 거의 사라졌다고 말할 수도 있습니다. 특히, &quot;MVC에서 뷰(View)와 컨트롤러(Controller)의 분리가 필수적인 요소&quot;라고 본다면, 이는 MVC라는 이름 자체가 암시하는 구조와도 어긋나기 때문입니다.</p>
<p>MVC에서 확실히 효과적이었던 개념은 &quot;분리된 프레젠테이션(Separated Presentation)&quot;과 &quot;옵저버 동기화(Observer Synchronization)&quot;였습니다. 그렇기 때문에, Smalltalk이 발전하면서도 이 두 가지 개념은 유지되었습니다. 사실, 많은 사람들에게 이 개념들이 MVC의 핵심 요소라고 여겨졌습니다.</p>
<p>이 시기에 Smalltalk은 여러 갈래로 분화됩니다. Smalltalk의 기본 개념과 (최소한의) 언어 정의(Language Definition)는 그대로 유지되었지만, 서로 다른 라이브러리를 가진 다양한 Smalltalk 버전들이 등장하게 되었습니다. 특히 UI 관점에서 이러한 변화는 중요한 의미를 가졌습니다. 몇몇 Smalltalk 라이브러리는 네이티브 위젯(Native Widgets)을 사용하기 시작했으며, 이는 폼과 컨트롤(Forms and Controls) 스타일에서 사용되던 컨트롤(Control)과 유사한 방식이었습니다.</p>
<p>Smalltalk은 원래 Xerox PARC 연구소에서 개발되었으며, 이후 ParcPlace라는 별도의 회사가 분사하여 Smalltalk의 개발과 상업화를 진행했습니다. ParcPlace Smalltalk는 VisualWorks라고 불렸으며, 크로스플랫폼(Cross-Platform) 지원을 핵심 목표로 삼았습니다. 즉, Windows에서 작성한 Smalltalk 프로그램을 그대로 Solaris에서 실행할 수 있었습니다. (이는 Java보다 훨씬 이전부터 가능했던 기능이었습니다.) 이러한 특성 때문에, VisualWorks는 네이티브 위젯을 사용하지 않았으며,GUI를 완전히 Smalltalk 내에서 자체적으로 구현했습니다.</p>
<p>내가 MVC에 대해 논의하면서 마지막으로 지적한 문제점들은 특히 뷰 로직(View Logic)과 뷰 상태(View State)를 어떻게 다룰 것인가에 대한 문제였습니다. VisualWorks는 이러한 문제를 해결하기 위해 &quot;애플리케이션 모델(Application Model)&quot;이라는 개념을 도입하여 이를 프레임워크에 통합하는 방향으로 발전시켰습니다. 이 개념은 프레젠테이션 모델(Presentation Model)과 유사한 방향으로 나아가는 구조였습니다. 사실, 프레젠테이션 모델과 유사한 접근 방식은 VisualWorks에서 새롭게 등장한 개념이 아니었습니다. 원래 Smalltalk-80의 코드 브라우저(Code Browser)도 이와 매우 유사한 구조를 가지고 있었습니다. 하지만 VisualWorks의 애플리케이션 모델은 이를 프레임워크 내에서 완전한 형태로 정착시켰습니다.</p>
<p>이러한 종류의 Smalltalk에서 핵심 요소(Key Element) 중 하나는 속성(Properties)을 객체(Objects)로 변환하는 개념이었습니다. 우리는 일반적으로 객체(Object)가 속성을 가진다고 생각합니다. 예를 들어, 사람(Person) 객체는 이름(Name)과 주소(Address) 같은 속성을 가질 수 있습니다. 이러한 속성들은 필드(Fields)일 수도 있지만, 다른 방식으로 구현될 수도 있습니다. 속성에 접근하는 방식은 보통 언어별 표준 관례(Convention)를 따릅니다. 예를 들어,</p>
<ul>
<li><p><strong>Java에서는</strong></p>
<pre><code class="language-java">  temp = aPerson.getName();
  aPerson.setName(&quot;martin&quot;);</code></pre>
</li>
<li><p><strong>C#에서는</strong></p>
<pre><code class="language-csharp">  temp = aPerson.name;
  aPerson.name = &quot;martin&quot;;</code></pre>
</li>
</ul>
<p>이처럼 <strong>속성 접근 방식은 언어마다 차이가 있지만, 기본적으로 객체가 내부 데이터를 제공하는 역할을 합니다</strong>.</p>
<p>속성 객체(Property Object)는 기존 방식과 다르게, 속성이 실제 값을 직접 반환하는 것이 아니라 실제 값을 감싸는 객체(Wrapping Object)를 반환합니다. 따라서 VisualWorks에서는 이름(Name) 속성을 요청하면, 실제 값이 아니라 래핑된 객체(Wrapping Object)를 받게 됩니다. 그리고, 실제 값을 얻으려면 이 래핑 객체에 다시 값을 요청해야 합니다. 예를 들어, 사람(Person) 객체의 이름(Name) 속성에 접근하는 방식은 다음과 같습니다.</p>
<pre><code class="language-smalltalk">temp = aPerson name value.   &quot;이름 속성의 실제 값을 가져옴&quot;
aPerson name value: &#39;martin&#39;. &quot;이름 속성의 값을 &#39;martin&#39;으로 설정&quot;</code></pre>
<p>즉, <code>aPerson name</code>은 이름 값을 포함하는 래핑 객체를 반환하고, <code>value</code> 메시지를 보내면 그 래핑 객체로부터 실제 값이 반환됩니다. 마찬가지로 <code>value:</code> 메시지를 사용하면 새로운 값을 설정할 수 있습니다. 이 방식은 기존의 속성 접근 방식과는 다르게, 속성을 단순한 값이 아니라 객체로 다룸으로써 보다 유연한 동작이 가능하도록 합니다.</p>
<p>속성 객체(Property Objects)는 위젯(Widget)과 모델(Model) 간의 매핑을 보다 쉽게 만들어 줍니다. 이 방식에서는, 위젯에 &quot;어떤 메시지를 보내야 해당 속성을 가져올 수 있는지&quot;만 지정하면 됩니다. 위젯은 속성 객체에서 <code>value</code>와 <code>value:</code> 메시지를 사용하여 올바른 값을 가져오거나 설정할 수 있습니다. 또한, VisualWorks의 속성 객체(Property Objects)는 옵저버(Observer)를 설정할 수 있도록 지원합니다. <code>onChangeSend: aMessage to: anObserver</code> 메시지를 사용하면, 속성이 변경될 때 특정 메시지를 지정된 옵저버(Observer) 객체에 보낼 수 있습니다. 즉, 속성 객체는 위젯과 모델 간의 연결을 단순화할 뿐만 아니라, 변화 감지(Observation) 기능까지 제공하는 구조입니다.</p>
<p>VisualWorks에서 Property Object라는 이름의 클래스를 직접 찾을 수는 없습니다. 대신, <code>value</code> / <code>value:</code> / <code>onChangeSend:</code> 프로토콜을 따르는 여러 클래스들이 존재합니다. 가장 단순한 형태는 <code>ValueHolder</code>입니다. <code>ValueHolder</code>는 단순히 값을 저장하고 관리하는 역할을 합니다. 하지만 이번 논의와 더 관련이 깊은 클래스는 <code>AspectAdaptor</code>입니다. <code>AspectAdaptor</code>는 다른 객체의 속성을 완전히 감싸(Property Wrapping) 속성 객체처럼 사용할 수 있도록 해줍니다.  이를 활용하면, <code>PersonUI</code> 클래스에서 <code>Person</code> 객체의 속성을 감싸는 속성 객체를 정의할 수 있습니다. 예를 들어, 다음과 같은 코드로 구현할 수 있습니다.</p>
<pre><code class="language-smalltalk">adaptor := AspectAdaptor subject: person.
adaptor forAspect: #name.
adaptor onChangeSend: #redisplay to: self.</code></pre>
<p>이제, 이 애플리케이션 모델(Application Model)이 우리가 사용하고 있는 예제에서 어떻게 적용될 수 있는지 살펴보겠습니다.</p>
<p>애플리케이션 모델과 클래식 MVC의 가장 큰 차이점은, 도메인 모델 클래스(Reader)와 위젯(Widget) 사이에 중간 계층(Intermediate Layer)으로서의 애플리케이션 모델 클래스가 추가되었다는 점입니다. 즉, 위젯(Widgets)은 더 이상 도메인 객체(Domain Objects)에 직접 접근하지 않습니다. 위젯의 모델(Model)은 애플리케이션 모델입니다. 이처럼 애플리케이션 모델이 중간 계층으로 작동함으로써, UI와 도메인 로직 간의 결합도(Coupling)를 낮추고, 보다 유연한 데이터 바인딩을 가능하게 합니다. 여전히 위젯들은 뷰(View)와 컨트롤러(Controller)로 구분되지만, 새로운 위젯을 직접 개발하지 않는 이상, 이 구분은 크게 중요하지 않습니다.</p>
<p>UI를 구성할 때는 UI 페인터(UI Painter)를 사용하며, 이 과정에서 각 위젯의 애스펙트(Aspect)를 설정합니다. 애스펙트는 애플리케이션 모델의 특정 메서드와 연결되며, 이 메서드는 속성 객체(Property Object)를 반환합니다.</p>
<p>그림 10(Figure 10)은 기본적인 업데이트 순서가 어떻게 작동하는지를 보여줍니다. 텍스트 필드에서 값을 변경하면, 해당 필드는 애플리케이션 모델(Application Model) 내부의 속성 객체(Property Object)를 업데이트합니다. 그런 다음, 이 업데이트는 기본 도메인 객체(Domain Object)로 전파되어, 실제 값(Actual Value)이 변경됩니다.</p>
<p>이 시점에서 옵저버(Observer) 관계가 작동하기 시작하며, 실제 값(Actual Value)이 변경될 때 reading 객체가 변경되었음을 알릴 수 있도록 설정해야 합니다. 이를 위해, actual 값을 수정하는 수정자(Modifier) 메서드에서 reading 객체가 변경되었음을 표시하는 호출을 추가하며, 특히 분산(Variance) 애스펙트가 변경되었음을 명확히 해야 합니다. 분산의 <code>AspectAdaptor</code>를 설정할 때 이를 reading 객체를 관찰하도록 지정하면, reading이 변경될 때 업데이트 메시지를 수신하고 이를 다시 텍스트 필드로 전달하게 되며, 텍스트 필드는 <code>AspectAdaptor</code>를 통해 새로운 값을 가져오는 과정을 시작합니다.</p>
<p>이처럼 애플리케이션 모델과 속성 객체(Property Objects)를 활용하면, 많은 코드를 작성하지 않고도 업데이트를 연결(Wire Up)할 수 있습니다. 또한, 세밀한(Fine-Grained) 동기화(Synchronization)를 지원하지만, 개인적으로 이것이 꼭 좋은 방식이라고 생각하지는 않습니다.</p>
<p>애플리케이션 모델(Application Models)은 UI에 특화된 동작(Behavior)과 상태(State)를 실제 도메인 로직(Domain Logic)과 분리할 수 있도록 해줍니다. 예를 들어, 앞서 언급한 문제 중 하나인 리스트(List)에서 현재 선택된 항목을 유지하는 문제는, 도메인 모델의 리스트(List)를 감싸면서, 현재 선택된 항목도 함께 저장하는 특정 유형의 <code>AspectAdaptor</code>를 사용하여 해결할 수 있습니다.</p>
<p>그러나 이러한 방식의 한계(Limitation)는 더 복잡한 동작(Behavior)을 구현하려면 특별한 위젯(Widgets)과 속성 객체(Property Objects)를 추가로 만들어야 한다는 점입니다. 예를 들어, 기본적으로 제공되는 객체들만으로는 분산(Variance)의 정도에 따라 텍스트 색상을 변경하는 기능을 구현할 수 없습니다. 애플리케이션 모델(Application Model)과 도메인 모델(Domain Model)을 분리하면, 올바른 방식으로 의사 결정을 구조화할 수 있습니다. 하지만, 위젯이 <code>AspectAdaptor</code>를 관찰(Observe)하도록 하려면 새로운 클래스를 만들어야 합니다.이 과정이 너무 많은 작업(Too Much Work)처럼 느껴지는 경우가 많았으며, 이를 더 쉽게 해결하는 방법으로 애플리케이션 모델이 위젯(Widgets)에 직접 접근할 수 있도록 허용하는 방식이 있었습니다. 그림 11(Figure 11)은 이러한 구조를 보여줍니다.</p>
<p>이처럼 애플리케이션 모델(Application Model)이 위젯(Widgets)을 직접 업데이트하는 방식은 프레젠테이션 모델(Presentation Model)의 개념과 일치하지 않습니다. 즉, VisualWorks의 애플리케이션 모델은 엄밀한 의미에서 프레젠테이션 모델이 아닙니다. 위젯을 직접 조작해야 하는 필요성은 많은 사람들에게 일종의 &quot;지저분한 우회 방법(Dirty Workaround)&quot;으로 여겨졌으며, 이러한 문제를 해결하기 위해 모델-뷰-프레젠터(Model-View-Presenter, MVP) 아키텍처가 발전하는 계기가 되었습니다.</p>
<p>애플리케이션 모델에 대한 핵심 원칙(Soundbites)</p>
<ul>
<li>MVC와 마찬가지로 &quot;분리된 프레젠테이션(Separated Presentation)&quot;과 &quot;옵저버 동기화(Observer Synchronization)&quot;를 따릅니다.</li>
<li>프레젠테이션 로직과 상태를 관리하는 중간 계층으로 애플리케이션 모델을 도입하였으며, 이는 프레젠테이션 모델의 부분적 발전으로 볼 수 있습니다.</li>
<li>위젯(Widgets)은 도메인 객체(Domain Objects)를 직접 관찰하지 않고, 애플리케이션 모델을 관찰합니다.</li>
<li>속성 객체(Property Objects)를 광범위하게 활용하여 레이어 간 연결을 돕고, 옵저버를 통한 세밀한 동기화(Fine-Grained Synchronization)를 지원합니다.</li>
<li>애플리케이션 모델이 위젯을 직접 조작하는 것이 기본 동작(Default Behavior)은 아니었지만, 복잡한 경우에는 흔히 사용되었습니다.</li>
</ul>
<h1 id="model-view-presenter-mvp">Model-View-Presenter (MVP)</h1>
<p>MVP(Model-View-Presenter)는 1990년대 IBM에서 처음 등장했으며, Taligent에서 더욱 두드러지게 발전한 아키텍처입니다. 이 개념은 주로 Potel 논문(Potel Paper)을 통해 알려졌으며, 이후 Dolphin Smalltalk 개발자들에 의해 더욱 대중화되고 체계적으로 설명되었습니다. 그러나, MVP에 대한 Potel 논문과 Dolphin Smalltalk의 설명은 완전히 일치하지 않으며, 그럼에도 불구하고 기본적인 개념 자체는 널리 확산되며 인기를 얻게 되었습니다.</p>
<p>MVP(Model-View-Presenter)를 이해하는 데 있어, UI 설계에서 나타나는 두 가지 주요 흐름 간의 불일치(Mismatch)를 고려하는 것이 도움이 됩니다. 한편으로는 폼과 컨트롤(Forms and Controls) 아키텍처가 있으며, 이는 UI 설계의 주류 접근 방식으로, 이해하기 쉽고 재사용 가능한 위젯과 애플리케이션 특화 코드(Application-Specific Code)를 효과적으로 분리합니다. 반면, MVC 및 그 변형들(MVC and its Derivatives)은 폼과 컨트롤 방식이 부족한 &quot;분리된 프레젠테이션(Separated Presentation)&quot;을 강력하게 지원하며, 도메인 모델(Domain Model) 기반 프로그래밍을 수행할 수 있는 구조적 맥락을 제공합니다. 나는 MVP를 이 두 흐름을 통합하려는 시도로 보며, 각 접근 방식의 장점을 결합하여 최적의 설계를 만들고자 하는 과정이라 생각합니다.</p>
<p>Potel의 MVP에서 첫 번째 요소는 뷰(View)를 위젯(Widgets)들의 구조(Structure)로 취급하는 것입니다. 여기서 위젯(Widgets)은 폼과 컨트롤(Forms and Controls) 모델의 컨트롤(Controls)에 해당하며, MVC에서의 뷰/컨트롤러(View/Controller) 분리를 제거합니다. 즉, MVP의 뷰(View)는 단순히 위젯들로 구성된 구조일 뿐이며, 사용자 상호작용(User Interaction)에 대한 반응을 정의하는 동작(Behavior)은 포함하지 않습니다.</p>
<p>사용자 행동(User Acts)에 대한 실제 반응(Active Reaction)은 별도의 프레젠터(Presenter) 객체에서 처리됩니다. 기본적인 사용자 입력 핸들러는 여전히 위젯 내에 존재하지만,이 핸들러들은 단순히 프레젠터에게 제어권(Control)을 넘기는 역할만 수행합니다.</p>
<p>프레젠터는 이벤트에 대한 반응(Reaction)을 결정하는 역할을 합니다. Potel은 이 상호작용을 주로 모델(Model)에 대한 액션(Actions) 관점에서 설명하며, 이를 위해 명령(Commands)과 선택(Selections) 시스템을 활용합니다. 특히 주목할 만한 점은, 모델에 대한 모든 수정 작업(Edits to the Model)을 하나의 명령(Command)으로 포장하는 방식으로, 이러한 구조는 실행 취소(Undo) 및 다시 실행(Redo) 기능을 구현하는 데 매우 유용한 기반을 제공합니다.</p>
<p>프레젠터가 모델을 업데이트하면, MVC에서 사용했던 것과 동일한 옵저버 동기화(Observer Synchronization) 방식을 통해 뷰도 함께 업데이트됩니다.</p>
<p>Dolphin의 MVP 설명도 유사한 구조를 따릅니다. 가장 큰 공통점은 역시 프레젠터의 존재입니다. 하지만 Dolphin에서는 프레젠터가 명령(Commands)과 선택(Selections)을 통해 모델에 작용하는 구조가 명확히 정의되지 않았습니다. 또한, 프레젠터가 뷰를 직접 조작하는 것에 대해 명시적으로 다루고 있습니다. Potel의 설명에서는 프레젠터가 뷰를 직접 조작해야 하는지에 대해 언급이 없지만, Dolphin에서는 이 기능이 필수적이며, 특히 애플리케이션 모델에서 분산(Variance) 필드의 텍스트 색상을 변경하는 것이 어려웠던 문제를 해결하는 데 중요한 역할을 한다고 보았습니다.</p>
<p>MVP를 바라보는 다양한 관점 중 하나는 프레젠터가 뷰의 위젯을 얼마나 제어하는가에 대한 정도의 차이입니다. 한쪽에서는 모든 뷰 로직(View Logic)을 뷰 자체에 남겨두고, 프레젠터는 모델을 렌더링하는 과정에 관여하지 않는 방식이 있습니다. 이 스타일은 Potel이 암시한 방식에 가깝습니다. 반면, Bower와 McGlashan이 제시한 방향은 내가 &quot;감독 컨트롤러(Supervising Controller)&quot;라고 부르는 스타일입니다. 여기서는 뷰가 선언적으로 표현할 수 있는 뷰 로직을 직접 처리하며, 프레젠터는 보다 복잡한 경우만 개입하는 방식입니다.</p>
<p>프레젠터가 모든 위젯의 조작을 담당하는 방식으로 확장할 수도 있습니다. 이러한 스타일을 나는 &quot;패시브 뷰(Passive View)&quot;라고 부릅니다. 패시브 뷰는 MVP의 원래 설명에는 포함되지 않았지만, 테스트 가능성(Testability) 문제를 탐구하는 과정에서 발전한 개념입니다. 이 방식에 대해서는 나중에 더 자세히 다룰 예정이지만, 패시브 뷰 역시 MVP의 한 가지 변형(Flavor of MVP)으로 볼 수 있습니다.</p>
<p>MVP를 이전에 논의한 개념들과 비교하기 전에, 우선 여기서 다루는 두 MVP 논문(Potel과 Dolphin)이 기존 개념들과 비교를 시도했다는 점을 언급할 필요가 있습니다. 그러나, 그 해석 방식은 내가 보는 관점과는 다소 차이가 있습니다. Potel은 MVC의 컨트롤러(Controller)를 전체적인 조정자(Coordinator)로 암시하고 있지만, 나는 그렇게 보지 않습니다. Dolphin에서는 MVC의 문제점에 대해 많이 다루고 있지만, 여기서 말하는 MVC는 내가 설명한 &quot;고전적인 MVC(Classic MVC)&quot;가 아니라, VisualWorks의 애플리케이션 모델 설계를 의미합니다. (이 부분에 대해서는 그들을 탓할 수는 없습니다.고전적인 MVC에 대한 정보를 찾는 것은 지금도 쉽지 않지만, 당시에는 더욱 어려웠기 때문입니다.)</p>
<p>이제 MVP와 기존 아키텍처들을 비교(Contrasts)해 보겠습니다.</p>
<ul>
<li><strong>폼과 컨트롤(Forms and Controls)</strong>:<ul>
<li>MVP는 모델을 중심으로 동작하며, 프레젠터는 모델을 조작하고, 옵저버 동기화(Observer Synchronization)를 통해 뷰를 업데이트합니다. 위젯에 직접 접근하는 것이 허용되긴 하지만, 이것은 보조적인 방식이며, 모델을 통해 동작하는 것이 우선시되어야 합니다.</li>
</ul>
</li>
<li><strong>MVC</strong>:<ul>
<li>MVP에서는 &quot;감독 컨트롤러(Supervising Controller)&quot;가 모델을 조작합니다. 위젯은 사용자 입력(User Gestures)을 감독 컨트롤러로 전달합니다. MVP에서는 위젯을 뷰와 컨트롤러로 분리하지 않습니다. 프레젠터는 컨트롤러와 유사하지만, 사용자 입력을 직접 처리하지 않습니다. 프레젠터는 일반적으로 위젯 수준이 아니라 폼(Form) 수준에서 동작하며, 이것이 MVC와의 가장 큰 차이점 중 하나입니다.</li>
</ul>
</li>
<li><strong>애플리케이션 모델(Application Model)</strong>:<ul>
<li>뷰는 이벤트를 프레젠터에게 전달하는 방식이 애플리케이션 모델과 유사합니다. 하지만, 뷰는 도메인 모델(Domain Model)로부터 직접 업데이트를 받을 수도 있습니다. 즉, 프레젠터가 &quot;프레젠테이션 모델(Presentation Model)&quot;처럼 동작하지 않습니다. 프레젠터는 옵저버 동기화(Observer Synchronization) 방식에 맞지 않는 경우, 위젯을 직접 조작할 수도 있습니다.</li>
</ul>
</li>
</ul>
<p>MVP의 프레젠터와 MVC의 컨트롤러 사이에는 분명한 유사점이 있으며, 프레젠터는 느슨한 형태의(More Flexible) MVC 컨트롤러라고 볼 수 있습니다. 그 결과, 많은 설계에서 MVP 스타일을 따르면서도 &quot;프레젠터&quot; 대신 &quot;컨트롤러&quot;라는 용어를 동의어처럼 사용하기도 합니다. 또한, &quot;사용자 입력(User Input) 처리&quot;를 논의할 때,일반적으로 &quot;컨트롤러(Controller)&quot;라는 용어를 사용하는 것이 합리적인 주장이라고 볼 수도 있습니다.</p>
<p>MVP(감독 컨트롤러, Supervising Controller) 방식의 아이스크림 모니터 구현(Figure 12)를 살펴보겠습니다.이 방식은 폼과 컨트롤(Forms and Controls) 방식과 유사하게 시작됩니다.</p>
<ol>
<li>실제 값(Actual Value) 텍스트 필드에서 텍스트가 변경되면 이벤트가 발생합니다.</li>
<li>프레젠터는 이 이벤트를 감지(Listens)하고, 변경된 필드 값을 가져옵니다.</li>
<li>이 시점에서 프레젠터는 reading 도메인 객체를 업데이트합니다.</li>
<li>분산(Variance) 필드는 reading을 옵저버(Observer)로 등록하고 있으므로, 업데이트된 값을 감지하고 자신의 텍스트를 변경합니다.</li>
<li>마지막 단계는 분산 필드의 색상(Color)을 설정하는 것입니다.<ul>
<li>프레젠터가 reading에서 카테고리(Category)를 가져오고,</li>
<li>그에 따라 분산 필드의 색상을 업데이트합니다.</li>
</ul>
</li>
</ol>
<p>이 방식에서 주목할 점은, 모델의 변경 사항은 옵저버 동기화(Observer Synchronization)를 통해 자동으로 뷰에 반영되지만, 뷰의 일부 요소(예: 색상 변경)는 프레젠터가 직접 처리한다는 점입니다.</p>
<p><strong>MVP의 핵심 원칙(Soundbites)</strong></p>
<ul>
<li>사용자 입력(User Gestures)은 위젯에서 &quot;감독 컨트롤러(Supervising Controller)&quot;로 전달됩니다.</li>
<li>프레젠터는 도메인 모델(Domain Model)의 변경을 조정(Coordinate)합니다.</li>
<li>MVP의 다양한 변형(Variants)은 뷰 업데이트를 처리하는 방식이 다릅니다. 옵저버 동기화(Observer Synchronization)를 사용하는 방식부터, 프레젠터가 모든 업데이트를 직접 처리하는 방식까지 다양한 스펙트럼이 존재합니다.</li>
</ul>
<h1 id="humble-view">Humble View</h1>
<p>지난 몇 년 동안 자체 테스트 가능한 코드(Self-Testing Code)를 작성하는 것이 강력한 흐름이 되었습니다. 나는 패션 감각에 대해 조언할 마지막 사람이지만, 이런 흐름에 깊이 몰입하고 있으며, 적극적으로 참여하고 있습니다. 내 동료들 중 다수는 xUnit 프레임워크, 자동 회귀 테스트(Automated Regression Tests), 테스트 주도 개발(Test-Driven Development, TDD), 지속적 통합(Continuous Integration, CI) 등과 같은 개념의 열렬한 지지자들입니다.</p>
<p>자체 테스트 가능한 코드(Self-Testing Code)를 논의할 때, 사용자 인터페이스(UI)는 빠르게 문제점으로 떠오릅니다. 많은 사람들이 GUI 테스트는 어렵거나, 거의 불가능하다고 느낍니다. 그 주된 이유는, UI가 전체 UI 환경과 강하게 결합(Tightly Coupled)되어 있으며, 이를 분리하여 개별적으로 테스트하기가 매우 어렵기 때문입니다.</p>
<p>때때로 GUI 테스트의 어려움은 과장되기도 합니다. 실제로, 위젯을 생성하고 테스트 코드에서 이를 조작하는 방식만으로도 상당한 수준까지 테스트할 수 있습니다. 그러나, 중요한 사용자 상호작용(Interactions)을 놓치거나 스레딩(Threading) 문제 발생, 테스트 실행 속도가 지나치게 느려지는 등의 문제들로 인해 이 접근 방식이 불가능하거나 비효율적인 경우도 존재합니다.</p>
<p>그 결과, 테스트하기 어려운 객체(Object)의 동작을 최소화하는 방식으로 UI를 설계하려는 움직임이 꾸준히 이어져 왔습니다. Michael Feathers는 이러한 접근 방식을 &quot;The Humble Dialog Box&quot;에서 명확하게 정리했으며, Gerard Meszaros는 이를 확장하여 &quot;Humble Object&quot; 개념을 제시했습니다. 즉, 테스트하기 어려운 객체는 가능한 한 최소한의 동작(Behavior)만 가져야 한다는 것입니다. 이렇게 하면, 해당 객체를 테스트 스위트(Test Suite)에 포함할 수 없더라도, 미처 감지하지 못한 실패(Undetected Failure) 가능성을 최소화할 수 있습니다.</p>
<p>&quot;The Humble Dialog Box&quot; 논문에서는 프레젠터(Presenter)를 훨씬 더 적극적으로 활용하는데, 이는 원래의 MVP보다 훨씬 깊이 있는 방식입니다. 프레젠터는 단순히 사용자 이벤트(User Events)에 반응하는 역할뿐만 아니라, UI 위젯에 데이터를 채우는 작업(Population of Data)까지 담당합니다. 이러한 접근 방식의 결과, 위젯들은 더 이상 모델에 대한 접근 권한(Visibility)을 가지지 않으며, 가질 필요도 없습니다. 즉, 위젯들은 완전히 &quot;패시브 뷰(Passive View)&quot; 형태를 가지게 되며, 프레젠터가 UI를 조작하는 유일한 엔티티(Manipulator) 역할을 수행합니다.</p>
<p>이것이 UI를 &quot;겸손하게(Humble)&quot; 만드는 유일한 방법은 아닙니다. 또 다른 접근 방식으로는 프레젠테이션 모델을 활용하는 방법이 있습니다. 그러나 이 방식에서는 위젯이 약간 더 많은 동작(Behavior)을 필요로 합니다. 즉, 위젯이 스스로 프레젠테이션 모델과 어떻게 매핑(Map)되는지를 이해할 수 있어야 합니다.</p>
<p>이 두 가지 접근 방식(패시브 뷰와 프레젠테이션 모델)의 핵심은, 프레젠터(Presenter) 또는 프레젠테이션 모델을 테스트함으로써 직접 테스트하기 어려운 위젯에 손대지 않고도 UI의 대부분의 위험 요소를 검증할 수 있다는 점입니다.</p>
<p>프레젠테이션 모델(Presentation Model)에서는 모든 의사 결정을 프레젠테이션 모델이 담당하도록 설계합니다. 모든 사용자 이벤트(User Events)와 화면 표시 로직(Display Logic)이 프레젠테이션 모델로 전달되며, 위젯은 단순히 프레젠테이션 모델의 속성(Properties)과 매핑(Map)하는 역할만 수행합니다. 이 방식에서는, 위젯 없이도 프레젠테이션 모델의 대부분의 동작(Behavior)을 테스트할 수 있습니다. 유일한 남은 위험 요소는 위젯과 프레젠테이션 모델 간의 매핑이 올바르게 이루어지는지 여부입니다. 그러나 이 매핑이 단순한 구조라면 굳이 직접 테스트하지 않아도 충분히 관리할 수 있습니다. 즉, 이 방식에서는 화면이 패시브 뷰 방식만큼 &quot;겸손(Humble)&quot;하지는 않지만, 그 차이는 크지 않습니다.</p>
<p>패시브 뷰(Passive View)는 위젯을 완전히 겸손하게(Humble) 만들기 때문에, 매핑조차 존재하지 않으며, 프레젠테이션 모델에서 발생할 수 있는 작은 위험조차 제거합니다. 그러나, 그 대가로 테스트 실행중 화면을 모방(Mimic)할 테스트 더블(Test Double)이 필요하며,이는 추가적으로 구축해야 하는 추가적인 기계적 장치(Extra Machinery)입니다.</p>
<p>감독 컨트롤러(Supervising Controller) 방식에서도 유사한 트레이드오프(Trade-Off)가 존재합니다. 뷰가 단순한 매핑(Mapping)을 수행하도록 하면 약간의 위험이 따르지만, 프레젠테이션 모델과 마찬가지로, 단순한 매핑을 선언적으로 지정할 수 있다는 장점이 있습니다. 그러나, 감독 컨트롤러의 경우 매핑의 범위가 프레젠테이션 모델보다 더 작아지는 경향이 있습니다. 프레젠테이션 모델은 모든 복잡한 업데이트를 결정하고 매핑을 통해 전달하지만, 감독 컨트롤러는 복잡한 경우 직접 위젯을 조작하므로 매핑 없이도 해결할 수 있습니다.</p>
<h2 id="감사의-말acknowledgements"><strong>감사의 말(Acknowledgements)</strong></h2>
<p>Vassili Bykov께서 Smalltalk-80 버전 2(1980년대 초반)의 구현인 Hobbes를 제공해 주신 덕분에, 최신 VisualWorks 환경에서 실행할 수 있었습니다. 이를 통해, Model-View-Controller의 실제 예제를 직접 살펴볼 수 있었으며, MVC가 기본 이미지(Default Image)에서 어떻게 동작하고 사용되었는지에 대한 세부적인 질문들을 해결하는 데 큰 도움이 되었습니다. 그 당시에는 가상 머신(Virtual Machine)을 사용하는 것이 비현실적이라고 생각하는 사람들이 많았지만, 지금 돌아보면, 과거의 우리가 Ubuntu에서 실행되는 VMware 가상 머신 속에서 Windows XP가 실행되고, 그 위에서 VisualWorks 가상 머신이 실행되며, 그 위에서 다시 VisualWorks로 작성된 Smalltalk-80 가상 머신이 돌아가는 모습을 본다면 어떤 반응을 보였을지 궁금합니다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[FSD 아키텍처에 컴파운드 패턴 적용해보기]]></title>
            <link>https://velog.io/@seoyong-lee/FSD-%EC%95%84%ED%82%A4%ED%85%8D%EC%B2%98%EC%97%90-%EC%BB%B4%ED%8C%8C%EC%9A%B4%EB%93%9C-%ED%8C%A8%ED%84%B4-%EC%A0%81%EC%9A%A9%ED%95%B4%EB%B3%B4%EA%B8%B0</link>
            <guid>https://velog.io/@seoyong-lee/FSD-%EC%95%84%ED%82%A4%ED%85%8D%EC%B2%98%EC%97%90-%EC%BB%B4%ED%8C%8C%EC%9A%B4%EB%93%9C-%ED%8C%A8%ED%84%B4-%EC%A0%81%EC%9A%A9%ED%95%B4%EB%B3%B4%EA%B8%B0</guid>
            <pubDate>Thu, 26 Dec 2024 08:53:43 GMT</pubDate>
            <description><![CDATA[<h1 id="fsd-아키텍처">FSD 아키텍처</h1>
<h2 id="fsd의-특징">FSD의 특징</h2>
<p><a href="https://feature-sliced.design/">FSD(Feature-Sliced Design)</a>는 최근 주목받는 프론트엔드 아키텍처로 폴더를 &#39;역할(role)&#39;에 더해 &#39;기능(feature)&#39; 단위로 분할하는 것이 주요 특징입니다. 그렇다면 역할과 기능은 어떤 차이가 있을까요?</p>
<blockquote>
<p>역할(role): api, utils, components, hooks ...</p>
</blockquote>
<p>위와 같이 역할은 코드 관점에서 동작에 따라 나누어진 계층이라고 볼 수 있습니다. CRA 등의 보일러플레이트가 api, hooks, components 등으로 구성된 것도 바로 이러한 역할을 중심으로 폴더구조를 구성했기 때문입니다. </p>
<pre><code>일반적인 역할 중심의 폴더 구조
├── public
├── src
│   ├── api
│   │   └── order     ex) getOrderList.ts
│   ├── assets
│   ├── components
│   │   └── order     ex) OrderList.tsx
│   ├── config
│   ├── hooks
│   │   └── order     ex) useOrderList.ts
│   ├── styles
│   ├── utils
│   ├── App.js
│   └── index.js
├── package.json
└── README.md</code></pre><p>이러한 방식은 계층별로 구분하여 시스템의 복잡성을 줄일 수 있었지만, 점차 프로젝트의 규모가 커지기 시작하면서 components 폴더 내부가 비대해지고, 특정 기능이(ex. order) 여러 폴더로 파편화되는 문제(/model/order, /api/order, /components/order)가 발생하게 됩니다.</p>
<blockquote>
<p>기능(feature): profile, order, login ...</p>
</blockquote>
<p>기능은 실제 서비스 관점에서 비즈니스 도메인을 중심으로 구분됩니다. 역할에 더해서 이러한 기능을 중심으로 프로젝트를 구성하도록 제안된 아키텍처가 바로 FSD입니다. </p>
<pre><code>기능 중심의 FSD 폴더 구조
├── public
├── src
│   ├── app
│   ├── pages
│   │   └── order
│   ├── widgets
│   │   └── order-history
│   │       └── ui       ex) OrderHistory.tsx (주문내역 컴포넌트)
│   ├── features
│   │   └── order-list
│   │       ├── model    ex) useOrderList.ts (API 결과 처리 커스텀 훅)
│   │       └── ui       ex) OrderListTable.tsx (주문내역 테이블 컴포넌트)
│   ├── entities
│   │   └── purchase
│   │       ├── api      ex) getOrderList.ts (API 요청 코드)
│   │       └── model       ex) order-list.ts (응답 결과 스키마)
│   └── shared
│           └── ui       ex) Table.tsx (공통 컴포넌트)
├── package.json
└── README.md</code></pre><p>위 구조를 보면 주문과 관련된 기능이 계층에 따라 구분된 상태로 각 층을 관통하는 것을 확인할 수 있습니다. 역할 중심의 구조에선 order라는 폴더를 위계 없이 여러 곳에서 만들었던 것과 달리 FSD는 각 계층의 명확한 정의를 따르고 있으며 위에서 아래로, 단방향으로만 import 하도록 제한하는 특징을 가집니다.</p>
<p><img src="https://velog.velcdn.com/images/seoyong-lee/post/16de18e8-05f7-410b-9587-4b735109abdd/image.png" alt=""></p>
<ul>
<li>레이어(Layer): 역할에 따른 수직적 관심사 분리</li>
<li>슬라이스(Slice): 기능(비즈니스 도메인)에 따른 관심사 분리</li>
<li>세그먼트(Segment): 기술적 관심사 분리</li>
</ul>
<p>그렇다면 기능을 중심으로 구분하는 FSD는 어떤 이점이 있을까요?</p>
<h2 id="fsd의-장점">FSD의 장점</h2>
<p>FSD는 프로젝트의 규모가 커지더라도 관심사에 따른 분류를 유지하도록 도와줍니다. 이를 통해 결과적으로 하나의 기능을 담당하는 코드가 여러 폴더로 파편화되지 않도록 막아줍니다. 또한 명확하게 정의된 규칙에 따른 분류는 코드의 일관성을 높이고, 유지보수 및 확장성에도 기여합니다.</p>
<p><img src="https://velog.velcdn.com/images/seoyong-lee/post/e14eff57-e864-40e0-a77f-dbe54d557ef9/image.png" alt=""></p>
<h3 id="예측-가능성">예측 가능성</h3>
<ul>
<li>/ui라는 세그먼트에 속한 파일은 어떤 슬라이스에 속하더라도 그것이 UI를 담당하는 컴포넌트 파일일 것이라는 <strong>&#39;예측 가능성&#39;</strong>을 높여줍니다.</li>
<li>마찬가지로 각 레이어에 따라 같은 이름의 세그먼트라도 담당하는 역할이 달라질 것으로 예측이 가능합니다.<ul>
<li>ex) /entities/api: 기본 데이터 페칭 함수가 정의되어 있을 것 같습니다.</li>
<li>ex) /feature/api: Tanstack Query 등을 이용한 API 호출 함수가 있을 것 같습니다.</li>
</ul>
</li>
</ul>
<h3 id="public-api를-이용한-모듈화">public API를 이용한 모듈화</h3>
<ul>
<li><p>FSD는 다음과 같이 <a href="https://feature-sliced.design/docs/reference/public-api">public API</a>를 이용해 특정 슬라이스에서 내보낼 부분을 명확히 정의합니다.</p>
<pre><code class="language-ts">// widgets/main/header/index.ts

export { HeaderMain } from &#39;./ui/HeaderMain&#39;;</code></pre>
</li>
<li><p>이러한 방식을 통해 무분별한 import를 방지하고 각 모듈 단위로 독립적으로 교체가 가능하게 합니다.</p>
</li>
</ul>
<h3 id="컨벤션과-확장성">컨벤션과 확장성</h3>
<ul>
<li>특정 기능의 코드가 비대해지면 이를 적당한 크기로 나누기 편한 기준점을 제시해 줍니다.</li>
<li>나아가 팀 컨벤션으로 작용하여 코드의 유지보수를 돕고 확장성을 높이는 역할을 합니다.</li>
</ul>
<p>그러나 이러한 FSD도 단점은 있었습니다.</p>
<h3 id="fsd-적용의-어려움">FSD 적용의 어려움</h3>
<p>FSD를 처음 문서로 접했을 때는 장점들이 너무나 명확해서 적용하지 않을 이유가 없어 보였지만 실제 프로젝트에 적용해 보니 다음과 같은 문제에 마주하게 되었습니다.</p>
<ul>
<li>기존 프로젝트에 FSD를 적용하려면 전체적인 구조를 역할이 아닌 기능 중심으로 다시 만들어야 합니다.<ul>
<li>이는 프로젝트 전체 구조에 대한 이해도를 높일 기회라고 생각하고 감수해야 하는 것 같습니다.</li>
</ul>
</li>
<li>실제로 어떤 코드가 /feature 나 /widget, /entities로 들어가야 하는지에 대해 구분이 어렵습니다.<ul>
<li>이 부분은 공식 사이트의 예시를 봐도 강력하게 정해진 룰은 없는 것으로 보아 서비스의 특성에 따라 각자에게 맞는 방식을 스스로 찾아야 하는 것으로 보입니다.</li>
</ul>
</li>
<li>이렇게 구분이 어렵다 보니 Page -&gt; Widgets -&gt; Features 순으로 위는 점차 비대해지고 Feature에 어떤 것을 두어야 할지 애매하다 보니 일관성을 유지하기 어렵습니다.<ul>
<li>처음에는 Post, Delete 등 동사로 정의될 수 있는 비즈니스 액션만 feature로 두려다 보니 get 요청과 같은 단순 조회 및 UI관련 코드는 대부분 widget에만 모이게 되었습니다.</li>
<li>이를 줄이기 위해 다시 Atomic Design Pattern과 같이 UI를 기반으로 쪼개서 내려보내자니 ui가 아닌 다른 세그먼트도 한 단위로 묶어줄 수 있는 무언가가 필요했습니다.</li>
</ul>
</li>
</ul>
<p>특히 widget과 feature를 명확하게 구분하는 부분은 가장 어려운 점이었습니다. 이러한 문제를 해결하기 위해서 <a href="https://patterns-dev-kr.github.io/design-patterns/compound-pattern/">컴파운드 패턴(Compound Pattern)</a>을 적용해 모듈을 적당한 크기로 분할하고 조합해보기로 하였습니다.</p>
<h1 id="컴파운드-패턴">컴파운드 패턴</h1>
<h2 id="특징과-장점">특징과 장점</h2>
<p>컴파운드 패턴은 컴포넌트 동작의 세부 구현이나 상태를 내부적으로 감추고 이를 사용하는 쪽에서는 드러나지 않도록 제한하는 패턴입니다. 이러한 방식을 통해 외부에서 import 되는 컴포넌트를 제한하고 추상화 수준에 따른 명확한 구분을 가능하게 합니다.</p>
<pre><code>&lt;select&gt;
    &lt;option value=&quot;value1&quot;&gt;key1&lt;/option&gt;
    &lt;option value=&quot;value2&quot;&gt;key2&lt;/option&gt;
    &lt;option value=&quot;value3&quot;&gt;key3&lt;/option&gt;
&lt;/select&gt;</code></pre><p>위 예시에서 select 태그는 key1을 선택했다는 사실을 어떻게 알 수 있을까요? select와 option 태그는 명시적인 표현 없이 암묵적으로 상태를 공유합니다. </p>
<p><a href="https://kentcdodds.com/blog/compound-components-with-react-hooks">Kent C. Dodds은 다음과 같이 컴파운드 패턴을 설명</a>합니다. </p>
<blockquote>
<p>중요한 측면은 &quot;암묵적 상태(implicit state)&quot;의 개념입니다. &lt;select&gt; 요소는 선택된 옵션에 대한 상태를 암묵적으로 저장하고 이를 하위 항목과 공유하므로 해당 상태에 따라 자체적으로 렌더링하는 방법을 알 수 있습니다. 그러나 상태 공유는 HTML 코드에 상태에 액세스할 수 있는 항목이 없기 때문에 암묵적입니다(그리고 그럴 필요도 없습니다).</p>
</blockquote>
<p>컴파운드 패턴은 위와 같이 ui와 이와 관련된 상태를 하나로 묶는 방법을 제공하고 있기에 FSD의 피처와 위젯에서 각 슬라이스를 정의하고 내보내는 기준으로 사용하기에 적합하다고 생각했습니다.</p>
<p>다음은 Header 컴포넌트를 구현한 예시로 이를 통해 컴파운드 패턴을 구체적으로 살펴보겠습니다.</p>
<p><strong>요구사항</strong></p>
<ul>
<li>Header 컴포넌트는 헤더의 내용인 Content와 Banner, SearchIcon으로 구분됩니다.</li>
<li>Banner는 특정 조건을 만족하면 보여집니다.</li>
<li>SearchIcon은 사용되는 곳에 따라 숨길 수 있어야 합니다.</li>
</ul>
<p>먼저 일반적인 방식으로 단일 Header 컴포넌트를 구성한 경우입니다.</p>
<pre><code class="language-ts">export const Header = ({
  showSearchIcon,
}: {
  showSearchIcon: boolean;
}): JSX.Element =&gt; {
  const { showBanner, onClickSearch, onClickBack } = useHeader();

  return (
    &lt;header&gt;
      {/* Banner */}
      &lt;Transition.Root show={showBanner}&gt;
        &lt;Transition.Child&gt;
          &lt;div&gt;
            &lt;h1&gt;
              &lt;Image height={32} src=&quot;/assets/logo.png&quot; width={32} /&gt;
            &lt;/h1&gt;
            &lt;div&gt;&lt;/div&gt;
          &lt;/div&gt;
          &lt;Button&gt;
            &lt;div&gt;
              &lt;span&gt;download app&lt;/span&gt;
            &lt;/div&gt;
          &lt;/Button&gt;
        &lt;/Transition.Child&gt;
      &lt;/Transition.Root&gt;
      {/* Content */}
      &lt;div&gt;
        &lt;button onClick={onClickBack} type=&quot;button&quot;&gt;
          &lt;Image height={16} src=&quot;/assets/logo.svg&quot; width={76} /&gt;
        &lt;/button&gt;
      &lt;/div&gt;
      {/* SearchIcon */}
      {showSearchIcon &amp;&amp; (
        &lt;button onClick={onClickSearch} type=&quot;button&quot;&gt;
          &lt;Image
            alt=&quot;search&quot;
            height={24}
            src=&quot;/assets/icons/ic_search.png&quot;
            width={24}
          /&gt;
        &lt;/button&gt;
      )}
    &lt;/header&gt;
  );
};</code></pre>
<pre><code class="language-ts">function Profile() {
    return (
        &lt;Layout&gt;  
            &lt;Header showSearchIcon={false} /&gt;
              ...
        &lt;/Layout&gt;
    )
}</code></pre>
<p>요구사항은 만족하였지만, 다음과 같은 단점들이 보입니다.</p>
<ul>
<li>사용하는 곳(Profile)에서는 Header 컴포넌트의 내부 구조를 확인할 방법이 없습니다.</li>
<li>showSearchIcon이라는 prop을 직접 넘겨주어 SearchIcon 노출 여부를 결정하기 때문에 Header 컴포넌트를 사용하는 모든 곳에서 해당 정보를 반복적으로 전달해야 하는 단점을 가지고 있습니다.</li>
<li>코드가 길어지면서 Header의 구조를 직관적으로 파악하기 어렵습니다.</li>
<li>저수준의 동작이 뒤섞여 재사용이 어렵습니다.</li>
</ul>
<p>그렇다면 각각의 컴포넌트를 기능에 따라 분할하기만 하면 이러한 문제가 개선될까요?</p>
<pre><code class="language-ts">import { HeaderBanner } from &#39;@/features/search/ui/HeaderBanner&#39;;
import { HeaderContent } from &#39;@/features/search/ui/HeaderContent&#39;;
import { HeaderSearchIcon } from &#39;@/features/search/ui/HeaderSearchIcon&#39;;

function Profile() {
  const { showBanner, showSearchIcon } = useHeader();

  return (
    &lt;Layout&gt;
      &lt;HeaderBanner showBanner={showBanner} /&gt;
      &lt;HeaderContent /&gt;
      &lt;HeaderSearchIcon showSearchIcon={showSearchIcon} /&gt;
      ...
    &lt;/Layout&gt;
  );
}</code></pre>
<ul>
<li>이제 사용하는 곳에서 구조는 볼 수 있지만 각각의 기능을 직접 import 해야 합니다.</li>
<li>분할을 하다 보니 헤더의 동작을 관장하는 useHeader 커스텀 훅이 밖으로 노출 돼버렸습니다.</li>
<li>각 컴포넌트를 묶어줄 방법이 필요해 보입니다.</li>
</ul>
<p>이를 개선하기 위해 같은 컴포넌트를 컴파운드 패턴을 적용하여 바꿔보겠습니다.</p>
<pre><code class="language-ts">// widgets/profile/ui/ProfileMain.tsx
import { Header } from &#39;@/features/header&#39;;

function Profile() {
    return (
        &lt;Layout&gt;  
            &lt;Header&gt;
                &lt;Header.Banner/&gt;
                &lt;Header.Content/&gt;
            &lt;/Header&gt;
              ...
        &lt;/Layout&gt;
    )
}</code></pre>
<p>위와 같이 컴파운드 패턴이 적용되면 사용하는 곳에서도 Header 컴포넌트의 구성을 확인할 수 있고 prop 등을 받지 않아 훨씬 깔끔합니다. profile이라는 widget에서 사용되는 Header 컴포넌트는 Header 자신만 import 하고 Content와 같은 개별 컴포넌트는 직접 노출되지 않는 것을 확인할 수 있습니다. 또한 Banner가 열리는 조건과 세부 구현 사항은 사용하는 곳에서는 보이지 않도록 암묵적(implicit)으로 feature 레벨에 숨겨져 있습니다. 이를 통해 저수준의 구현에 시선을 뺐지 않으면서도 전체적인 구조를 명확하게 볼 수 있다는 장점을 가지게 되었습니다. 이러한 구성은 구체적인 코드의 동작을 정의한 feature 레벨 코드의 조합을 통해 widget을 구성하는 FSD의 정의와도 비슷한 부분이 있습니다.</p>
<p>만약 다른 곳에서 사용할 때는 Banner 대신 Search 컴포넌트를 추가하고 싶다면 별도의 Header 수정 없이 다음과 같이 재조합만으로 목적에 맞게 사용이 가능합니다.</p>
<pre><code class="language-ts">// widgets/contents/ui/ContentsMain.tsx
import { Header } from &#39;@/features/header&#39;;

function Main() {
    return (
        &lt;Layout&gt;  
            &lt;Header&gt;
                &lt;Header.Content/&gt;
                &lt;Header.Search/&gt;
            &lt;/Header&gt;
              ...
        &lt;/Layout&gt;
    )
}</code></pre>
<p>Header 컴포넌트의 세부 구현사항은 다음과 같이 feature 레이어에 구현하였습니다. </p>
<pre><code class="language-ts">// features/header/ui/Header.tsx
interface HeaderContextValue {
  showBanner: boolean;
}

export const HeaderContext = createContext&lt;HeaderContextValue&gt;(
  {} as HeaderContextValue,
);

export const Header = ({ children }: { children: ReactNode }): JSX.Element =&gt; {
  const { showBanner } = useHeaderBanner();

  return (
    &lt;HeaderContext.Provider value={{ showBanner, onClickBack }}&gt;
      &lt;header&gt;
        {children}
      &lt;/header&gt;
    &lt;/HeaderContext.Provider&gt;
  );
};

Header.Banner = HeaderBanner;
Header.Content = HeaderContent;
Header.Search = HeaderSearch;</code></pre>
<p>각 세부 기능에 따라 컴포넌트를 다시 분할하였고 Context API를 통해 각 컴포넌트가 내부적으로 상태를 공유하는 것을 확인할 수 있습니다. 이를 통해서 전역상태 등을 사용하지 않으면서도 내부적으로 통제된 단방향 상태 흐름을 유지하는 것을 확인할 수 있습니다. 또한 상태가 사용되고 변경되는 영역을 컴파운드 컴포넌트 안으로 한정하여 다른 모듈과 결합되지 않도록 분리하고 예상하지 못한 버그를 방지하며 손쉽게 교체가 가능합니다.</p>
<p>컴파운드 패턴은 이렇게 사용하는 곳에서는 전체 구조를 보여주면서도 통제된 방식으로 컴포넌트를 불러오도록 제한하고, 저수준의 구현은 하위레벨로 두도록 하여 좀 더 명확한 레벨 구분을 가능하게 합니다.</p>
<p>이러한 장점을 살려 FSD의 계층에 컴파운드 패턴을 실제로 적용해 보겠습니다.</p>
<h1 id="fsd에-컴파운드-패턴-적용하기">FSD에 컴파운드 패턴 적용하기</h1>
<h2 id="실전-예제">실전 예제</h2>
<p><a href="https://ko.react.dev/">react.dev</a> 사이트를 예시로 구성해 보겠습니다.</p>
<p><img src="https://velog.velcdn.com/images/seoyong-lee/post/b567bec6-dd27-458e-8aca-348053e3bf3d/image.png" alt=""></p>
<h3 id="1-page-식별">1. Page 식별</h3>
<p>먼저 App을 제외한 가장 상위 레이어인 Pages를 식별합니다. </p>
<ul>
<li>home</li>
<li>learn</li>
<li>reference</li>
<li>community</li>
<li>blog</li>
</ul>
<h3 id="2-home-page-구현">2. Home page 구현</h3>
<p>가장 메인 화면인 Home page부터 구현해 보겠습니다.</p>
<pre><code class="language-tsx">export default function Home(): JSX.Element {
  return (
    &lt;Layout&gt;
      &lt;Header&gt;
        &lt;Header.Logo /&gt;
        &lt;Header.SearchBar /&gt;
        &lt;Header.NavMenu /&gt;
        &lt;Header.Buttons /&gt;
      &lt;/Header&gt;
      &lt;Content /&gt;
      &lt;Footer /&gt;
    &lt;/Layout&gt;
  );
}</code></pre>
<p>위와 같이 메인 페이지는 크게 UI를 기준으로 Header, Content, Footer로 구성하였습니다. 내부 구조를 밖에서 볼 필요성이 낮거나 구조를 변경해서 다른 곳에서 재사용하지 않는 Footer와 같은 컴포넌트는 굳이 컴파운드 패턴으로 구성되지 않은 것을 확인할 수 있습니다. 이제 Header widget을 통해 구체적인 컴파운드 패턴을 구현합니다.</p>
<h3 id="3-header-widget-구현">3. Header widget 구현</h3>
<pre><code class="language-tsx">// widgets/header/ui/Header.tsx

interface HeaderContextValue {
  currentPage: string;
}

export const HeaderContext = createContext&lt;HeaderContextValue&gt;(
  {} as HeaderContextValue,
);

export const Header = ({ children }: { children: ReactNode }): JSX.Element =&gt; {
  const { currentPage } = useHeader();

  return (
    &lt;HeaderContext.Provider value={{ currentPage }}&gt;
      &lt;header&gt;{children}&lt;/header&gt;
    &lt;/HeaderContext.Provider&gt;
  );
};

Header.Logo = HeaderLogo;
Header.SearchBar = HeaderSearchBar;
Header.NavMenu = HeaderNavMenu;
Header.Buttons = HeaderButtons;</code></pre>
<p>현재 페이지에 해당하는 메뉴 버튼 표시를 위해 ContextAPI를 이용해 currentPage 상태를 추가하였습니다. currentPage 상태는 Header 밖에서는 불필요하며 내부적으로만 사용됩니다. 사실 Header 컴포넌트 밖에서는 이런 상태가 존재했다가 소리 소문 없이 사라져도 외부에는 아무런 변화를 주지 않아야합니다. 이제 이 중에서 SearchBar 컴포넌트를 자세히 살펴보겠습니다. SearchBar는 다음과 같이 클릭 시 실제 입력 가능한 검색 모달을 열어야 합니다.</p>
<p><img src="https://velog.velcdn.com/images/seoyong-lee/post/bc661e77-32b9-42c5-a504-00dc86e799f4/image.png" alt=""></p>
<h3 id="4-headersearchbar-구현">4. Header.SearchBar 구현</h3>
<p>UI에 따라 구성한 SearchBar 컴포넌트입니다. 아직 input 영역은 버튼으로만 기능하며 실제 입력은 Modal 내부에서 가능합니다.</p>
<pre><code class="language-tsx">// widgets/header/ui/HeaderSearchBar.tsx

import { SearchModal } from &#39;@/features/search-modal&#39;;

export const HeaderSearchBar = (): JSX.Element =&gt; {
  const [isModalOpen, setIsModalOpen] = useState(false);

  const onClick = () =&gt; {
    setIsModalOpen(true);
  };

  const onClose = () =&gt; {
    setIsModalOpen(false);
  };

  return (
    &lt;div&gt;
      &lt;div&gt;
        &lt;img src=&quot;assets/search-icon.png&quot; /&gt;
      &lt;/div&gt;
      &lt;button onClick={onClick}&gt;
        &lt;span&gt;검색&lt;/span&gt;
        &lt;input disabled /&gt;
      &lt;/button&gt;
      &lt;SearchModal isOpen={isModalOpen} onClose={onClose}&gt;
        &lt;SearchModal.Input /&gt;
        &lt;SearchModal.Results /&gt;
        &lt;SearchModal.Bottom /&gt;
      &lt;/SearchModal&gt;
    &lt;/div&gt;
  );
};
</code></pre>
<p>구성을 진행하다 보니 검색 모달은 직접 키보드로 입력한 키워드를 기반으로 API 요청 등을 진행해야 하기 때문에 코드량이 많아질 것으로 예상됩니다. 자연스럽게 이를 SearchModal feature로 분리합니다. <a href="https://feature-sliced.design/docs/reference/layers#features">FSD 문서</a>에서 정의한 feature의 특성은 다음과 같습니다.</p>
<blockquote>
<p>This layer is for the main interactions in your app, things that your users care to do. These interactions often involve business entities, because that&#39;s what the app is about.
이 레이어(feature)는 앱의 주요 상호 작용, 즉 사용자가 관심을 갖는 작업을 위한 것입니다. 이러한 상호 작용에는 비즈니스 주체(entities)가 포함되는 경우가 많습니다. 왜냐하면 이것이 바로 앱의 목적이기 때문입니다.</p>
</blockquote>
<p>사용자가 검색어를 입력하고, 이를 기반으로 API를 통해 검색 결과를 가져오는 SearchModal은 feature에 두기에 매우 적합해 보입니다.</p>
<p>위 사례처럼 widget 레벨에서 코드의 복잡도가 갑자기 높아지거나, 사용자와 구체적인 상호작용을 하는 코드가 섞여 있다면 이를 feature로 분리해 가는 것을 추천합니다.</p>
<h3 id="5-searchmodal-feature-구현">5. SearchModal feature 구현</h3>
<pre><code class="language-tsx">// features/search-modal/ui/SearchModal.tsx

interface SearchModalContextValue {
  isOpen: boolean;
  onClose: () =&gt; void;
  searchResults: SearchResult[];
  setKeyword: (keyword: string) =&gt; void;
}

export const SearchModalContext = createContext&lt;SearchModalContextValue&gt;(
  {} as SearchModalContextValue,
);

export const SearchModal = ({
  children,
  isOpen,
  onClose,
}: {
  children: ReactNode;
  isOpen: boolean;
  onClose: () =&gt; Void;
}): JSX.Element =&gt; {
  const { keyword, setKeyword } = useInput();
  const { searchResults } = useSearchResults(keyword);

  return (
    &lt;SearchModalContext.Provider
      value={{ isOpen, onClose, searchResults, keyword, setKeyword }}&gt;
      {children}
    &lt;/SearchModalContext.Provider&gt;
  );
};

SearchModal.Input = SearchModalInput;
SearchModal.Results = SearchModalResults;
SearchModal.Bottom = SearchModalBottomSeciton;</code></pre>
<p>SearchModal은 다시 모달에서만 사용되는 컴포넌트와 상태로 묶입니다. 검색어(keyword)나 검색 결과(searchResults) 등의 상태는 SearchModal 내부에서만 사용되도록 다시 ContextAPI로 공유합니다. 외부에서 버튼을 통해 변경되는 isOpen과 모달 닫기에 대한 함수 onClose는 불가피하게 외부에서 props를 통해 전달받습니다.</p>
<ul>
<li>_ 이 부분에서 prop 대신 zotai나 recoil 등의 마이크로 상태 관리 라이브러리 등을 사용할 수는 있지만 주의해야 할 점은 여전히 상위에서 하위 레벨로만 import 하는 룰을 지켜야 한다는 점입니다. 이 경우 atom 등의 상태는 되도록 하위 레벨에 두는 것이 이 룰을 지키기에 수월합니다._</li>
</ul>
<p>더 깔끔하게 정리하고 싶다면 모달을 여는 버튼도 feature 모듈 내부로 포함시킬 수 있습니다. 이러한 방법을 이용하면 HeaderSearchBar에 노출되는 상태는 모두 SearchBar로 흡수됩니다.</p>
<pre><code class="language-tsx">// widgets/header/ui/HeaderSearchBar.tsx

import { SearchBar } from &#39;@/features/search-bar&#39;;

export const HeaderSearchBar = (): JSX.Element =&gt; {
  return (
    &lt;SearchBar&gt;
      &lt;SearchBar.Button /&gt;
      &lt;SearchBar.Modal&gt;
        &lt;SearchBar.ModalInput /&gt;
        &lt;SearchBar.ModalResults /&gt;
        &lt;SearchBar.ModalBottom /&gt;
      &lt;/SearchBar.Modal&gt;
    &lt;/SearchBar&gt;
  );
};</code></pre>
<pre><code class="language-tsx">// features/search-bar/ui/SearchBar.tsx

interface SearchBarContextValue {
  isOpen: boolean;
  onClose: () =&gt; void;
  searchResults: SearchResult[];
  setKeyword: (keyword: string) =&gt; void;
}

export const SearchBarContext = createContext&lt;SearchBarContextValue&gt;(
  {} as SearchBarContextValue,
);

export const SearchBar = ({
  children,
}: {
  children: ReactNode;
}): JSX.Element =&gt; {
  const { isOpen, onClose } = useModal();
  const { keyword, setKeyword } = useInput();
  const { searchResults } = useSearchResults(keyword);

  return (
    &lt;SearchBarContext.Provider
      value={{ isOpen, onClose, searchResults, keyword, setKeyword }}&gt;
      {children}
    &lt;/SearchBarContext.Provider&gt;
  );
};

SearchBar.Button = SearchBarButton;
SearchBar.Modal = SearchBarModal;
SearchBar.Input = SearchBarModalInput;
SearchBar.Results = SearchBarModalResults;
SearchBar.ModalBottom = SearchBarModalResults;</code></pre>
<p>SearchModal feature의 model에선 keyword를 기반으로 실제 API 요청을 진행하는 커스텀 훅을 구성합니다. 이러한 hook이 제공하는 데이터는 내부적으로 공유될 가능성이 높기에 Context가 정의된 곳에서 주로 사용됩니다.</p>
<pre><code class="language-ts">// features/search-modal/model/useSearchModal.tsx

export const useSearchResults = (
  keyword: string,
): {
  searchResults: SearchResult[];
} =&gt; {
  const { data: searchResults } = useQuery(
    searchRepository.results({ keyword }),
  );

  return { searchResults };
};</code></pre>
<p>TanstackQuery의 쿼리 설정은 entities에서 구성합니다.</p>
<pre><code class="language-ts">// entities/search/api/searchRepository.ts

import { getSearchResults } from &#39;../get-search-results&#39;;

export const searchRepository = {
  results: ({ keyword }: SearchQuery) =&gt;
    queryOptions({
      queryKey: [&#39;search&#39;, &#39;results&#39;, keyword],
      queryFn: () =&gt; getSearchResults({ headers }),
      staleTime: 0,
    }),
};</code></pre>
<p>이제 SearchModal이라는 독립적인 모듈이 완성되었습니다. 이제 이 모듈은 index.ts에서 정의된 항목만 밖으로 열려있습니다. 따라서 잘못된 import로 인해 의도하지 않은 종속이 발생하지 않도록 방지합니다.</p>
<pre><code class="language-ts">export { SearchModal } from &#39;./ui/SearchModal&#39;;</code></pre>
<p>또한 다음과 같은 하나의 진입로를 통해서 ui와 hook 등의 기능 세트에 접근할 수 있습니다. </p>
<pre><code class="language-ts">import { SearchModal } from &#39;@/features/search-modal&#39;;</code></pre>
<p>전체적인 구조를 다시 정리하면 다음과 같습니다.</p>
<pre><code>react.dev
├── public
├── src
│   ├── app
│   ├── pages
│   │   └── home
│   ├── widgets
│   │   └── header
│   │       └── ui       ex) Header.tsx (헤더 컴포넌트)
│   ├── features
│   │   └── search-modal
│   │       ├── model    ex) useSearchModal.ts (검색 결과 API 요청 커스텀 훅)
│   │       └── ui       ex) SearchModal.tsx (헤더 검색 모달 컴포넌트)
│   ├── entities
│   │   └── search
│   │       ├── api      ex) searchRepository.ts (API 요청 코드)
│   │       └── model       ex) search-results.ts (응답 결과 스키마)
│   └── shared
│           └── ui       ex) Input.tsx (공통 컴포넌트)
├── package.json
└── README.md</code></pre><p>지금까지 실제 예시를 통해 FSD에 컴파운드 패턴을 적용해 보았습니다.
마지막으로 FSD + 컴파운드 패턴의 장점과 단점에 대해 다시 정리하면서 마무리 하겠습니다.</p>
<h2 id="fsd--컴파운드-패턴의-장점">FSD + 컴파운드 패턴의 장점</h2>
<ul>
<li>기존 FSD의 계층별 분리를 더욱 강화합니다. </li>
<li>하위 레벨에서 import 되는 컴포넌트의 수를 줄이고 진입점을 통제합니다.</li>
<li>불필요하게 넓은 범위에 걸쳐있는 상태를 없애고 필요한 상태만 모듈 단위로 가집니다.</li>
<li>컴포넌트의 재조립을 용이하게 하여 변경과 재사용에 더욱 유연합니다.</li>
</ul>
<h2 id="fsd--컴파운드-패턴의-단점">FSD + 컴파운드 패턴의 단점</h2>
<ul>
<li>지나치게 통제된 방식으로 컴포넌트를 만들면 사용이 어렵고 유연함이 떨어집니다.</li>
<li>props를 명시적으로 전달하는 것이 더 적합한 경우도 있습니다. (ex 외부에서 받는 정보 등)</li>
<li>잘 정리되지 않으면 오히려 컴포넌트 내부의 복잡함을 더욱 가중시킬 수 있습니다.</li>
</ul>
<h3 id="참고">참고</h3>
<p><a href="https://velog.io/@teo/separation-of-concerns-of-frontend">테오의 프론트엔드 - 프론트엔드 개발자 관점으로 바라보는 관심사의 분리와 좋은 폴더 구조 (feat. FSD)</a>
<a href="https://feature-sliced.design/">Feature-Sliced Design</a>
<a href="https://patterns-dev-kr.github.io/design-patterns/compound-pattern/">patterns-dev - Compound 패턴</a>
<a href="https://kentcdodds.com/blog/compound-components-with-react-hooks">Kent C. Dodds - React Hooks: Compound Components</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Rollup을 이용한 React 패키지 번들링]]></title>
            <link>https://velog.io/@seoyong-lee/Rollup%EC%9D%84-%EC%9D%B4%EC%9A%A9%ED%95%9C-React-%ED%8C%A8%ED%82%A4%EC%A7%80-%EB%B2%88%EB%93%A4%EB%A7%81</link>
            <guid>https://velog.io/@seoyong-lee/Rollup%EC%9D%84-%EC%9D%B4%EC%9A%A9%ED%95%9C-React-%ED%8C%A8%ED%82%A4%EC%A7%80-%EB%B2%88%EB%93%A4%EB%A7%81</guid>
            <pubDate>Mon, 06 May 2024 12:36:18 GMT</pubDate>
            <description><![CDATA[<p>사내에서 리액트 관련 패키지를 개발하면서 Rollup에 대해 간단히 알아보았습니다.</p>
<h2 id="패키지-번들링이란">패키지 번들링이란</h2>
<p>리액트로 개발을 진행하다 보면 다양한 형태의 파일을 생성하여 작업하지만, 실제 배포를 위해서는 브라우저가 이해할 수 있도록 js, css 등의 형태로 합친 이후 압축하여야 합니다. 이러한 과정을 번들링이라고 하며 리액트는 대표적으로 웹팩(Webpack) 번들러를 이용하는 경우가 많았습니다. 그러나 웹팩은 Tree Shaking이 잘 이루어지지 않으며 ESM(ES Module) 번들이 불가능한 문제가 있어 이를 해결하기 위해 Rollup이 등장하였습니다. Rollup은 가벼운 무게로 번들링이 가능하며 개별 패키지 개발 등에 유용하게 사용이 가능합니다. </p>
<h2 id="rollup-설치-및-설정">Rollup 설치 및 설정</h2>
<p>먼저 Rollup을 설치합니다.</p>
<p><code>yarn add -D rollup</code></p>
<p>이후 루트 경로에 설정 파일(rollup.config.js)을 추가합니다.</p>
<pre><code>// rollup.config.js

export default {
  input: &#39;./src/index.js&#39;, // 진입 경로
  output: {
    file: &#39;./dist/bundle.js&#39;, // 출력 경로
    format: &#39;es&#39;, // 출력 형식
    sourcemap: true, // 소스 맵(디버깅)
  },
};</code></pre><p>이후 rollup -c 명령어를 이용해 루트 설정 파일(rollup.config.js)을 사용하도록 설정합니다.</p>
<pre><code>// package.json

{
  &quot;main&quot;: &quot;./dist/bundle.js&quot; // 진입 경로
  // ...
  &quot;scripts&quot;: {
    &quot;build&quot;: &quot;rollup -c&quot;,
    &quot;watch&quot;: &quot;rollup -cw&quot;
  }
}</code></pre><h2 id="react-설정">React 설정</h2>
<p>리액트 설치는 주의해야 할 부분이 있습니다. 리액트를 dependency로 바로 추가하게 되면 실제로 만든 패키지를 사용하는 쪽(호스트)의 리액트와 충돌이 일어날 수 있으므로 호스트 리액트를 사용하도록 설정해야 합니다. 이는 (PeerDependency)를 이용해 설정이 가능합니다.</p>
<pre><code>// package.json
{
  // ...
  &quot;peerDependencies&quot;: {
    &quot;react&quot;: &quot;^18.3.1&quot;,
    &quot;react-dom&quot;: &quot;^18.3.1&quot;
  }
}</code></pre><p>이후 JSX를 인식할 수 있도록 바벨 관련 패키지를 추가합니다.</p>
<pre><code># 바벨 설치
yarn add -D @babel/core @babel/preset-env @babel/preset-react

# 롤업에서 바벨을 사용하게 해주는 플러그인도 설치
yarn add -D @rollup/plugin-babel</code></pre><p>rollup.config.js에 바벨 관련 설정을 추가합니다.</p>
<pre><code>// rollup.config.js

import babel from &#39;@rollup/plugin-babel&#39;;

export default {
  input: &#39;./src/index.js&#39;,
  output: {
    file: &#39;./dist/bundle.js&#39;,
    format: &#39;es&#39;,
    sourcemap: true,
  },
  plugins: [
    // 바벨 트랜스파일러 설정
    babel({
      babelHelpers: &#39;bundled&#39;,
      presets: [&#39;@babel/preset-env&#39;, &#39;@babel/preset-react&#39;],
    }),
  ],
};</code></pre><h2 id="typescript-추가">TypeScript 추가</h2>
<p>TS 추가를 위해서는 다음의 패키지를 추가합니다.</p>
<pre><code># 롤업 타입스크립트 플러그인 설치
yarn add -D @rollup/plugin-typescript

# 롤업 타입스크립트 플러그인의 피어 디펜던시 설치
yarn add -D typescript tslib

# 바벨에서도 이를 해석하게 추가
yarn add -D @babel/preset-typescript

# 리액트, 리액트 DOM 타입 패키지 추가
yarn add -D @types/react @types/react-dom</code></pre><p>config 파일에 TS를 추가합니다.</p>
<pre><code>// rollup.config.js

import babel from &#39;@rollup/plugin-babel&#39;;
import typescript from &#39;@rollup/plugin-typescript&#39;;

export default {
  input: &#39;./src/index.ts&#39;,
  output: {
    file: &#39;./dist/bundle.js&#39;,
    format: &#39;es&#39;,
    sourcemap: true,
  },
  plugins: [
    // 바벨 트랜스파일러 설정
    babel({
      babelHelpers: &#39;bundled&#39;,
      presets: [
        &#39;@babel/preset-env&#39;,
        &#39;@babel/preset-react&#39;,
        &#39;@babel/preset-typescript&#39;,
      ],
      extensions: [&#39;.js&#39;, &#39;.jsx&#39;, &#39;.ts&#39;, &#39;.tsx&#39;],
    }),

    // 타입스크립트
    typescript(),
  ],
};</code></pre><p>tsconfig.json을 추가합니다.</p>
<pre><code>// tsconfig.json
{
  &quot;compilerOptions&quot;: {
    &quot;target&quot;: &quot;es5&quot;,
    &quot;lib&quot;: [&quot;dom&quot;, &quot;esnext&quot;],
    &quot;jsx&quot;: &quot;react&quot;,
    &quot;module&quot;: &quot;es6&quot;,
    &quot;moduleResolution&quot;: &quot;node&quot;,
    &quot;baseUrl&quot;: &quot;./&quot;,
    &quot;strict&quot;: true,
    &quot;esModuleInterop&quot;: true
  }
}</code></pre><p>위 설정까지 진행하면 TypeScript + React 패키지 개발을 위한 번들러 설정이 완료됩니다.</p>
<h2 id="html-파일-serve-하기">HTML 파일 Serve 하기</h2>
<p>패키지 개발을 위해 DEV 환경을 구성해서 실제 만든 패키지를 테스트 해보려면 rollup-plugin-serve 를 이용할 수 있습니다. 먼저 다음과 같이 테스트 폴더를 추가합니다.</p>
<pre><code>// example/template.html

&lt;html&gt;
  &lt;head&gt;
  &lt;/head&gt;
  &lt;body&gt;
    &lt;div id=&quot;app&quot;&gt;&lt;/div&gt;
    &lt;script src=&quot;./example.js&quot;&gt;&lt;/script&gt;
  &lt;/body&gt;
&lt;/html&gt;</code></pre><pre><code>// example/index.js

import React from &quot;react&quot;;
import ReactDOM from &quot;react-dom&quot;;
// 테스트하려는 패키지를 불러옵니다 ex) import ShakaPlayer from &#39;shaka-player-react&#39;;

function App() {
  return &lt;div&gt;HELLO!!!&lt;/div&gt;;
}

ReactDOM.createRoot(document.getElementById(&quot;app&quot;)).render(
  &lt;React.StrictMode&gt;
    &lt;App /&gt;
  &lt;/React.StrictMode&gt;
);</code></pre><p>이후 개발 환경에서의 빌드 시 html serve를 통해 확인해 볼 수 있도록 config 파일을 수정합니다.</p>
<pre><code>// rollup.config.js

import babel from &quot;@rollup/plugin-babel&quot;;
import typescript from &quot;@rollup/plugin-typescript&quot;;
import alias from &#39;@rollup/plugin-alias&#39;;
import serve from &quot;rollup-plugin-serve&quot;;
import replace from &quot;@rollup/plugin-replace&quot;;
import commonjs from &quot;rollup-plugin-commonjs&quot;;
import resolve from &quot;@rollup/plugin-node-resolve&quot;;
import html from &quot;@rollup/plugin-html&quot;;
import fs from &quot;fs&quot;;

const plugins = [
  replace({
    preventAssignment: true,
    // process undefined 관련 에러 방지를 위해 추가
    &quot;process.env.NODE_ENV&quot;: JSON.stringify(&quot;production&quot;),
  }),
  babel({
    babelHelpers: &quot;bundled&quot;,
    presets: [
      &quot;@babel/preset-env&quot;,
      &quot;@babel/preset-react&quot;,
      &quot;@babel/preset-typescript&quot;,
    ],
    extensions: [&quot;.js&quot;, &quot;.jsx&quot;, &quot;.ts&quot;, &quot;.tsx&quot;],
    exclude: &quot;node_modules/**&quot;,
  }),
  typescript(),
  resolve({ browser: true }),
  commonjs(),
];

const lib = {
  input: &quot;src/index.tsx&quot;,
  output: {
    file: &quot;dist/bundle.js&quot;,
    format: &quot;es&quot;,
  },
  external: [&quot;react&quot;],
  plugins,
};

const builds = [];

builds.push(lib); 

// 개발환경
if (process.env.DEV) {
  const devtool = {
    input: &quot;example/index.js&quot;,
    output: {
      file: &quot;dist/example.js&quot;,
      format: &quot;umd&quot;,
    },
    plugins: [
      ...plugins,
      // 기본 포트는 localhost:10001으로 설정되어 있습니다.
      serve({
        open: true,
        contentBase: &quot;dist&quot;,
      }),
      // dist 경로에 index.html을 추가합니다.
      html({
        dest: &quot;dist&quot;,
        filename: &quot;index.html&quot;,
        template: () =&gt; fs.readFileSync(&quot;./example/template.html&quot;),
      }),
       alias({
        entries: [
          {
            find: &#39;내 패키지 이름&#39;,
            replacement: &#39;src/index.js&#39; // 내 패키지 위치
          }
        ]
      })
    ],
  };
  builds.push(devtool);
}

export default builds;</code></pre><p>package.json의 script를 다음과 같이 수정합니다.</p>
<pre><code>// package.json
{
  // ...
  &quot;scripts&quot;: {
    &quot;build&quot;: &quot;rollup -c&quot;,
    &quot;start&quot;: &quot;DEV=1 rollup -c --watch&quot;
  },
}</code></pre><p>빌드 결과
<img src="https://velog.velcdn.com/images/seoyong-lee/post/6fdbffa4-8c27-4e49-b78b-80e5c0a373d2/image.png" alt=""></p>
<p>실행 결과 → <a href="http://localhost:10001/">http://localhost:10001/</a>
<img src="https://velog.velcdn.com/images/seoyong-lee/post/2086ee86-cbf1-49e9-a50d-a3bc7fc8277c/image.png" alt=""></p>
<p>References </p>
<p><a href="https://rollupjs.org/">https://rollupjs.org/</a><br><a href="https://wormwlrm.github.io/2021/11/07/Rollup-React-TypeScript.html">https://wormwlrm.github.io/2021/11/07/Rollup-React-TypeScript.html</a> 
<a href="https://github.com/matvp91/shaka-player-react/tree/master">https://github.com/matvp91/shaka-player-react/tree/master</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[메이커 팀으로 빠르게 서비스 개발해보기]]></title>
            <link>https://velog.io/@seoyong-lee/%EB%A9%94%EC%9D%B4%EC%BB%A4-%ED%8C%80%EC%9C%BC%EB%A1%9C-%EB%B9%A0%EB%A5%B4%EA%B2%8C-%EC%84%9C%EB%B9%84%EC%8A%A4-%EA%B0%9C%EB%B0%9C%ED%95%B4%EB%B3%B4%EA%B8%B0-l8wfscxq</link>
            <guid>https://velog.io/@seoyong-lee/%EB%A9%94%EC%9D%B4%EC%BB%A4-%ED%8C%80%EC%9C%BC%EB%A1%9C-%EB%B9%A0%EB%A5%B4%EA%B2%8C-%EC%84%9C%EB%B9%84%EC%8A%A4-%EA%B0%9C%EB%B0%9C%ED%95%B4%EB%B3%B4%EA%B8%B0-l8wfscxq</guid>
            <pubDate>Sun, 28 Apr 2024 11:51:36 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>2/26 <a href="https://eopla.net/magazines/12455#">EO</a>와 <a href="https://disquiet.io/@our_records/makerlog/%EB%A9%94%EC%9D%B4%EC%BB%A4-%ED%8C%80%EC%9C%BC%EB%A1%9C-%EB%B9%A0%EB%A5%B4%EA%B2%8C-%EC%84%9C%EB%B9%84%EC%8A%A4-%EA%B0%9C%EB%B0%9C%ED%95%B4%EB%B3%B4%EA%B8%B0">Disquiet에</a> 기고했던 글입니다.</p>
</blockquote>
<p>어쩌다 보니 PM 없이 메이커만으로 구성된 팀으로 신사업을 진행하게 되었습니다. 디자이너와 FE, BE 개발자 3명이 1달 반 동안 어디까지 갈 수 있었을까요? 지금부터 그 후기를 공유드리겠습니다.</p>
<h2 id="새로운-시작">새로운 시작</h2>
<p>2024년 새해의 시작과 함께 새로운 팀과 새로운 프로젝트가 시작되었습니다. 역시 언제나 그렇듯이 예상대로 흘러가는 것은 아무것도 없습니다. 새로운 팀에 함께 하기로 했던 PM 분이 퇴사하게 되면서 저희는 PM 없이 신규사업을 진행할지 선택해야 하는 상황이 되었습니다. </p>
<p>과연 메이커만으로 구성된 팀의 장점과 단점은 무엇일까요? </p>
<ul>
<li><p>장점: 빠르게 개발할 수 있다. 개발한 실제 결과물을 가지고 피드백을 수집해서 개선해 갈 수 있다. 모든 사람이 기획과 의사결정에 조금 더 깊이 참여하게 된다.</p>
</li>
<li><p>단점: 모두의 의견을 듣고 중심을 잡아줄 사람이 없다. 의사결정 과정에서 누군가가 명확하게 결론을 내기가 어렵다. 자신의 전문 영역 밖의 영역들도 같이 고민하고 시간을 투입해야 한다(비즈니스 모델, 마케팅 등).</p>
</li>
</ul>
<p>이러한 장단점을 모두 고려한 결과 저희는 그래도 저희끼리 한 번 갈 수 있는 데까지 가보기로 하였습니다.</p>
<h2 id="한계-파악하기">한계 파악하기</h2>
<p>적은 시간(3개월)과 리소스를 이용해서 하나의 서비스를 만들기 위해 먼저 가능한 범위에 대해 파악해 보았습니다. 우선 아이템 선정과 관련된 제약사항을 먼저 파악해 보았습니다. </p>
<ul>
<li>아이템: 적은 노력으로 큰 파급력을 얻을 수 있는 분야<ul>
<li>할 수 있는 아이템: 소셜, 커뮤니티, 유틸, 플랫폼, ...</li>
<li>하기 어려운 아이템: 제조, 유통, 커머스, 협상이 필요한 B2B, ...</li>
</ul>
</li>
</ul>
<p>역시 원천기술이 필요하거나 물건을 직접 판매하는 등의 아이템은 저희에게 적합하지 않다고 판단하였고, 커뮤니티와 같이 많은 사람이 모이는 곳을 만들고 이 안에서 가설들을 테스트하기로 정하였습니다.</p>
<p>다음으로 구체적인 업무에서의 한계에 대해서 고민해 보았습니다. </p>
<ul>
<li>기획 및 프로젝트 진행 방법: 구성원이 이해하기 어렵지 않으면서도 유연함과 일관성을 가질 수 있는 방법<ul>
<li>쉬운 방향: 피드백 루프, MVP, 애자일</li>
<li>어려운 방향: 치밀한 비즈니스 모델 설계, 대규모 waterfall</li>
</ul>
</li>
<li>기술: 기술적으로 개발 가능한 분야<ul>
<li>개발 가능한 분야: 간단한 웹, 간단한 앱, 커뮤니티, 사진 및 글 업로드</li>
<li>개발이 부적합한 분야: 자체 데이터로 학습이 필요한 AI 관련 아이템, 기술적 난이도가 높은 기능</li>
</ul>
</li>
<li>디자인: 적은 노력으로 큰 효과를 낼 수 있는 아이템<ul>
<li>효율적인 아이템: 재사용 가능한 디자인, 확장 가능한 디자인</li>
<li>비효율적 아이템: 지속적으로 컨텐츠 추가가 필요한 아이템</li>
</ul>
</li>
</ul>
<p>이를 종합하여 다음의 목표를 도출하였습니다. </p>
<blockquote>
<p>소셜 분야에서 간단한 웹 서비스를 피드백 루프와 MVP 방법론을 이용해서 빠르고 효율적으로 개발한다.</p>
</blockquote>
<h2 id="팀-전략-세우기">팀 전략 세우기</h2>
<p>팀이 앞으로 나가기 위한 전략으로 가장 크게 참고했던 방법은 피드백 루프(Feedback Loop)와 MVP입니다. </p>
<h3 id="피드백-루프">피드백 루프</h3>
<p>피드백 루프는 Build -&gt; Measure -&gt; Learn 과정을 반복하면서 초기 아이디어를 검증해가는 방법으로 저희가 지켜야 할 큰 틀이라고 생각했습니다. 먼저 최초 아이디어를 빠르게 웹으로 만들어서 출시하고(Build), 수집된 결과(Measure)를 통해 앞으로 만들 기능에 대한 힌트를 얻는(Learn) 방식으로 프로젝트를 진행하였습니다. 이러한 루프를 3개월 안에 최소 2회 완주할 수 있도록 목표를 설정하였습니다.</p>
<p><img src="https://velog.velcdn.com/images/seoyong-lee/post/1253308c-62f9-432e-b46f-998a2b4684b1/image.png" alt="niceideas.ch: The Search for Product-Market Fit"></p>
<p>참고 - <a href="https://www.niceideas.ch/roller2/badtrash/entry/the-search-of-product-market#sec24">niceideas.ch: The Search for Product-Market Fit </a></p>
<h3 id="mvp">MVP</h3>
<p>다음으로 참고했던 방법론은 MVP(Minimum Viable Product)입니다. MVP란 용어는 이미 많은 곳에서 자주 사용되고 있지만 모호한 의미로 사용되는 경우가 많은 것 같습니다. 저희 팀은 이번 기회에 MVP의 M과 V가 무엇을 의미하는지 집중해 보기로 하였습니다. </p>
<ul>
<li>Minimum: 제품이 가져야 하는 최소한의 기능. 이를 지키지 못하면 사용할 수 없는 조악한 결과물이 됩니다.</li>
<li>Viable: 제품의 생존 능력. 너무 약하면 생존하지 못하며 너무 과하면 규모만 크고 효율이 떨어지는 제품이 됩니다.</li>
</ul>
<p><img src="https://velog.velcdn.com/images/seoyong-lee/post/66047b96-fbf5-47c7-a39e-98e5f5d88057/image.png" alt=""></p>
<p>참고 - <a href="https://www.niceideas.ch/roller2/badtrash/entry/the-search-of-product-market#sec24">niceideas.ch: The Search for Product-Market Fit </a></p>
<p>결론적으로 저희는 <strong>MVP를 M과 V 사이의 외줄 타기</strong>로 정의했습니다. 또한 MVP는 아래 그림처럼 바퀴만 만들고 끝나거나 최종 결과물과 완전 다른 제품을 만드는 것이 아니라는 점도 팀 안에서 확실하게 인식을 맞추고 시작하였습니다.</p>
<p><img src="https://velog.velcdn.com/images/seoyong-lee/post/e2450a82-9d22-4b98-a5ee-a15f4c5b34c3/image.png" alt=""></p>
<p><a href="http://www.expressiveproductdesign.com/minimal-viable-product-mvp/">Fred Voorhorst - Expressive Product Design</a></p>
<h2 id="아이템-선정하기">아이템 선정하기</h2>
<p>사실 가장 고통스러운 과정은 바로 아이템을 선정 과정이었습니다. 만약 시장 분석 전문가가 팀에 함께했다면 현재 한국이나 미국에서 가장 유망한 아이템을 분석해서 빠르게 카피하는 방식으로 접근하는 것이 성공 확률을 가장 높일 방법일 것입니다. 그러나 저희는 그렇게 트렌드에 대한 레이더를 체계적으로 가동하기 어렵다고 판단했습니다. 따라서 저희 주변을 기준으로 필요할 만한 것들을 생각해 보았습니다. </p>
<ul>
<li>실제 내 삶에 도움이 되면서 (Feasibility) </li>
<li>친구들과 같이 사용이 가능한 (Desirability)</li>
<li>오래 남을 아이템 (Viability)</li>
</ul>
<p><img src="https://velog.velcdn.com/images/seoyong-lee/post/11abe331-f8ff-4458-9eef-d81ad5fad1ff/image.png" alt=""></p>
<p><a href="https://briantod.medium.com/about-product-market-fit-what-ive-learned-about-the-goal-the-process-and-the-nuance-e7b317740f43">About Product/Market Fit — what I’ve learned about the goal, the process and the nuance</a></p>
<p>먼저 며칠 동안 계속해서 브레인스토밍을 진행하였고,
<img src="https://velog.velcdn.com/images/seoyong-lee/post/3754f4eb-aff2-4e17-ab53-042f1009d947/image.png" alt=""></p>
<p>위 과정을 통해 키워드 3개를 도출하게 되었습니다. </p>
<ul>
<li>일정</li>
<li>그룹</li>
<li>사진</li>
</ul>
<p><img src="https://velog.velcdn.com/images/seoyong-lee/post/beb43bc1-0be2-42aa-8ccc-3b8403916ead/image.png" alt=""></p>
<p>이를 조합하여 설정한 첫 번째 아이템은 다음과 같습니다.</p>
<ul>
<li>아이템: 친구들과 함께 사용하는 카카오톡 그룹 채팅방을 하나의 서비스로 만들어서 일정과 사진을 공유하는 서비스를 만들어 보자</li>
<li>문제: <ul>
<li>카카오톡은 그룹만을 위해 만들어진 것은 아니므로 여러 가지 불편한 점들이 있다.</li>
<li>시간이 지나면 단톡방의 사진이 사라진다.</li>
<li>일정 투표 시에 응답 현황 등이 보여 눈치가 보인다.</li>
<li>채팅 내역이 많아지거나 사진과 뒤섞이면 이전 내용을 찾기 어렵다.</li>
</ul>
</li>
<li>해결방안:<ul>
<li>그룹 일정 관리와 사진 저장에 특화된 서비스를 만들자</li>
<li>사진을 영원히 남길 수 있도록 한다.</li>
<li>일정에 특화된 다양한 기능을 제공한다.</li>
<li>기존 채팅 방식과 다른 캘린더 피드 등을 제공한다.
그러나 처음부터 완전한 제품을 만들어 테스트하기는 어려울 것으로 보였습니다. 따라서 3개의 키워드 중에서 <code>일정</code> 부분만 웹을 통해 간단히 테스트해 보기로 합니다.</li>
</ul>
</li>
</ul>
<h2 id="첫-번째-루프">첫 번째 루프</h2>
<h3 id="서비스-만들기-build">서비스 만들기 (Build)</h3>
<p>사실 메이커로 구성된 팀이 가장 신나는 시점이 바로 만드는(Build) 단계라고 생각합니다. 디자이너가 전체 서비스 구조를 설계하는 과정에서 API를 설계해야 하는 백엔드 개발자와 함께 논의하고, 최종 시안이 나오기 전에 프론트엔드 개발자는 먼저 스타일을 제외한 컴포넌트 개발을 진행해두는 식으로 빠르고 유연하게 작업하였습니다. 만약 PM이 있었다면 모든 과정을 조율해 주었겠지만, 저희는 그런 사치를 누릴 수 없었기에 앉은 자리에서 서로가 완벽하게 이해될 때까지 빠르게 의사소통하였습니다.</p>
<p>긴밀하게 서로 한자리에 모여 개발하면서 추가되면 좋을 것 같은 기능을 자유롭게 제안하였고 이를 통해 제품을 더욱 구체화할 수 있었습니다. 또한 서로 우려되는 부분을 미리 체크해 주기도 하면서 각 담당자가 체크하기 어려운 빈틈을 메꿔주기도 하였습니다.</p>
<p>이러한 과정을 거쳐 처음으로 만든 결과물은 바로 OURR(Our Record)입니다.</p>
<p><img src="https://velog.velcdn.com/images/seoyong-lee/post/91873f63-25ed-4e05-987b-3de5dc81f2ca/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/seoyong-lee/post/8e73af25-3e70-479a-bfc5-804b8a91a023/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/seoyong-lee/post/f664e513-e91c-43db-b445-b23f00863ebd/image.png" alt=""></p>
<p>2주 동안의 작업 결과 일정을 만들고 초대장을 생성해서 친구들에게 참석 여부를 확인하는 서비스를 개발하였고 참여 인증샷도 남길 수 있도록 하였습니다. 마케팅 관련해선 먼저 사용자 인터뷰 대상자들을 모집하였고 서비스에 대해 알려드린 뒤 주변 사람들에게도 일정을 공유하도록 유도하였습니다. </p>
<p>그렇다면 결과는 어땠을까요?</p>
<h3 id="1주일-데이터-종합-결과-measure">1주일 데이터 종합 결과 (Measure)</h3>
<ul>
<li>(SignUp) 총 207명이 서비스에 접속하였으며<ul>
<li>이 중에서 106명이 로그인 창을 열었고<ul>
<li>다시 이 중에서 60명이 가입에 성공하였습니다.</li>
</ul>
</li>
</ul>
</li>
<li>(Activation) 가입한 사람의<ul>
<li>58.6%는 일정 참석 여부에 응답하였습니다.</li>
<li>41.4%는 일정 캘린더 추가 버튼을 클릭하였습니다.</li>
<li>34.5%는 초대장을 만들었고 같은 비율로 이를 공유하였습니다.</li>
<li>8.62%만 인증샷을 실제로 업로드 하였습니다.</li>
</ul>
</li>
<li>(Event) 생성된 일정은 총 62개로 1명 당 약 1개 일정이 생성되었으며<ul>
<li>이에 대한 참석 여부 응답은 177건 발생하였습니다.<ul>
<li>참석 138건, 모름 23건, 거절 16건.</li>
</ul>
</li>
</ul>
</li>
<li>(Retention) 가입 이후에 다시 돌아온 사람은<ul>
<li>다음날 4.35% 였습니다.</li>
<li>그다음 날은 2.33% 였습니다.</li>
</ul>
</li>
</ul>
<p>첫 루프의 결과로 판단할 수 있는 것은 무엇이었을까요?</p>
<h3 id="데이터를-통해-알-수-있었던-점-learn">데이터를 통해 알 수 있었던 점 (Learn)</h3>
<p>데이터를 통해 다음 인사이트를 얻을 수 있었습니다. </p>
<ul>
<li>회원가입 방식에 따라 사용자가 이탈하는 비율이 엄청나게 달라질 수 있다는 것을 확인하였습니다.<ul>
<li>초반에 알림 문자 발송을 위해 전화번호 가입 방식만 지원하였으나 절반 이상이 이탈하였습니다.</li>
<li>카카오 로그인을 추가한 이후 이탈 유저가 훨씬 감소하였습니다.</li>
</ul>
</li>
<li>일정을 만들고 공유로 이어지는 부분에 대한 설계 및 측정이 부족하였습니다.<ul>
<li>일정을 만들고 공유를 통해 다시 다음 가입자를 모집해야 서비스가 자연스럽게 확장될 수 있었습니다.</li>
<li>생각보다 일정 생성 이후 공유하기 버튼을 클릭하는 경우가 적어서 critical path에 대한 점검을 하게 되었습니다.</li>
<li>정확한 데이터 측정을 위해 초대코드를 함께 보내도록 수정이 필요하다는 것을 알게 되었습니다.</li>
</ul>
</li>
<li>생각보다 인증샷 올리기 기능을 잘 이해하지 못한 사용자들이 많았습니다.<ul>
<li>실제로 사진을 업로드 한 사용자는 10%도 되지 않았습니다.</li>
<li>자연스러운 흐름에 따라 특정 행동이 발생하도록 기능을 설계해야 한다는 점을 확인하였습니다. </li>
</ul>
</li>
</ul>
<p>지금까지의 데이터 결과를 보면 생각보다 적극적인 행동이 발생하진 않은 것으로 보입니다. 이탈 비율도 높고 공유도 많이 발생하지 않았으며 서비스를 다음 날 다시 방문하지도 않았기 때문입니다. 그렇다면 이제 서비스를 버리고 처음부터 다시 시작해야 할까요? 아니면 여기서 다시 문제점을 찾고 보완하기 위한 가설을 세우고 앞으로 나아가야 할까요? </p>
<p>저희 팀은 일단 현재 상태에 대한 가설을 만들어 보기로 하였습니다. </p>
<ol>
<li>만약 일정 기능 자체는 사용자에게 중요하지 않다면?
a. 이미 구글 캘린더나 카카오 일정 등을 편하게 잘 이용하고 있어서 별도의 서비스를 사용할 니즈가 없다.
b. 그렇다면 일정 관련된 기능은 아무리 잘 만들어도 사용하지 않을 것이다. → 아이데이션으로 돌아가기</li>
<li>만약 일정 관련해서 아직 발견하지 못한 다른 니즈가 있다면?
a. 사실 일정을 고르고 초대하고 조율하는 부분을 더 편하게 하고 싶다.
b. 그렇다면 기능을 보완해서 조금 더 필요한 기능을 찾는다면 많이 사용될 것이다. → 기능 개발로 돌아가기</li>
<li>만약 현재 상태의 기능도 충분히 원하는 사용자들이 있지만 아직 그들에게 도달하지 못했다면?
a. 만약 이런 서비스가 있다는 것을 알고 있다면 충분히 사용해 볼 만 하다.
b. 그렇다면 서비스를 알리는 채널을 늘리고 더 멀리 퍼뜨리면 많이 사용할 것이다. → 현재 상태에서 마케팅 보완하기</li>
</ol>
<p>위 가설 중에서 한 가지를 선택하기로 하였고 가설을 토대로 얼마나 다시 시작할지를 결정하기로 합니다. 이제 여기까지가 첫 루프의 Build-Measure-Learn의 결론입니다. 역시 한 번에 대박이 나는 경우는 없었습니다. 비록 좋은 결과가 나오지는 않았을지 몰라도 앞으로의 방향을 결정할 수 있는 단서는 찾았다고 생각합니다. 다음 루프에선 배운 내용을 반영해서 더 나은 제품이 만들어질 것입니다.</p>
<h2 id="결론">결론</h2>
<p>지금까지 팀이 한 번의 루프를 완료하기까지 겪었던 일들을 적어보았습니다. 여기까지 오는 것도 힘들지만 이 과정을 무한 반복한다고 생각하면 막막하게 느껴질 수도 있을 것 같습니다. 그러나 이러한 경험을 통해 팀은 짧은 주기로 현재의 문제점을 계속 확인하고 학습하는 방법을 배울 수 있었습니다. 또한 이 모든 과정이 1달 반 안에 완료되었다고 생각하면 긍정적이라고 생각합니다. 주어진 3달을 모두 아이데이션(Build)만 하다가 끝날 수도 있었기 때문입니다.</p>
<p>만약 누군가가 기획 전문가 없이 새로운 서비스를 만들어야 한다면 일단 빨리 만들고 이를 통해 측정해 보시기를 추천해 드리고 싶습니다. 직접 측정한 데이터를 얻을 수 있다면 앞으로 나아갈 방향에 대한 확실한 단서를 얻을 수 있을 것입니다. </p>
<h4 id="references">References</h4>
<p><a href="https://www.niceideas.ch/roller2/badtrash/entry/the-search-of-product-market">niceideas.ch: The Search for Product-Market Fit</a>
<a href="https://briantod.medium.com/about-product-market-fit-what-ive-learned-about-the-goal-the-process-and-the-nuance-e7b317740f43">About Product/Market Fit — what I’ve learned about the goal, the process and the nuance</a>
<a href="https://product.kyobobook.co.kr/detail/S000001772861">알렉스 오스터왈더, 『밸류 프로포지션 디자인』, 아르고나인미디어그룹(2016)</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[
FSD(Feature-Sliced Design) Architecture]]></title>
            <link>https://velog.io/@seoyong-lee/FSDFeature-Sliced-Design-Architecture</link>
            <guid>https://velog.io/@seoyong-lee/FSDFeature-Sliced-Design-Architecture</guid>
            <pubDate>Tue, 09 Apr 2024 04:46:28 GMT</pubDate>
            <description><![CDATA[<p>FSD(Feature-Sliced Design)에 대한 리서치 문서입니다.
<a href="https://dev.to/m_midas/feature-sliced-design-the-best-frontend-architecture-4noj">Feature-Sliced Design: The Best Frontend Architecture</a>
<a href="https://feature-sliced.design/">Feature-Sliced Design</a></p>
<h2 id="fsd의-3가지-컨셉">FSD의 3가지 컨셉</h2>
<p>FSD는 Layers, Slices, Segments 세 가지 파트로 나누어집니다.</p>
<h3 id="layers">Layers</h3>
<p>Layers는 top-level 디렉토리이며 서비스 분해의 첫 번째 단계에 속합니다.
Layers는 엄격하게 규격화된 구조로 되어있지만 이 중에서 몇 가지는 선택적으로 사용할 수 있습니다.</p>
<pre><code>└── src/
    ├── app/
    ├── processes/ (deprecated)
    ├── pages/
    ├── widgets/
    ├── features/
    ├── entities/
    └── shared/</code></pre><ul>
<li>app: <ul>
<li>어플리케이션 로직이 초기화 되는 entry point 입니다.</li>
<li>포함되는 것들의 예시<ul>
<li>Providers</li>
<li>라우트</li>
<li>글로벌 스타일 및 타입 선언 등…</li>
</ul>
</li>
</ul>
</li>
<li>processes(optional layer):<ul>
<li>이 계층은 다단계 등록과 같은 여러 페이지에 걸쳐 있는 프로세스를 처리합니다.</li>
</ul>
</li>
<li>pages: <ul>
<li>페이지들을 포함합니다.</li>
<li>포함되는 것들의 예시<ul>
<li>/404 페이지 컴포넌트</li>
<li>/home 페이지 컴포넌트 등 …</li>
</ul>
</li>
</ul>
</li>
<li>widgets:<ul>
<li>페이지에서 사용되는 standalone UI 컴포넌트 들입니다.</li>
<li>포함되는 것들의 예시<ul>
<li>header</li>
<li>navigation</li>
<li>layouts</li>
</ul>
</li>
</ul>
</li>
<li>features(optional layer):<ul>
<li>비즈니스적 가치를 가지는 기능단위입니다.</li>
<li>포함되는 것들의 예시<ul>
<li>Like</li>
<li>Review</li>
<li>ProductRatings</li>
<li>player</li>
</ul>
</li>
</ul>
</li>
<li>entities(optional layer):<ul>
<li>비즈니스 주체를 나타냅니다.</li>
<li>포함되는 것들의 예시<ul>
<li>users</li>
<li>reviews</li>
<li>friend</li>
<li>comments</li>
<li>feed </li>
<li>auth</li>
</ul>
</li>
</ul>
</li>
<li>shared:<ul>
<li>특정한 비즈니스 로직에 속하지 않는 컴포넌트나 유틸을 포함합니다.</li>
<li>포함되는 것들의 예시<ul>
<li>UI kit </li>
<li>axios 관련 설정</li>
<li>utils</li>
<li>helpers</li>
</ul>
</li>
</ul>
</li>
</ul>
<p>여기서 중요한 점은 FSD는 위계적 구조(hierarchical structure)를 따른다는 점입니다.</p>
<p><img src="https://velog.velcdn.com/images/seoyong-lee/post/11dd6c70-4473-49c8-9bec-4d7c3ad52fa2/image.png" alt=""></p>
<p><strong>Layer 원칙 1: 자신보다 아래 단계의 개체에만 접근할 수 있다
Layer 원칙 2: 위계가 낮은 컴포넌트일수록 영향을 받는 곳이 많기 때문에 변경은 위험하다</strong></p>
<h3 id="참고---layer-구분을-위한-비즈니스-로직">참고 - Layer 구분을 위한 비즈니스 로직</h3>
<p>FSD에서 widgets, features, entities등의 레이어 구분은 비즈니스 로직의 포함 정도로 판단합니다. 그렇다면 비즈니스 로직이란 무엇일까요? </p>
<blockquote>
<p>In computer software, business logic or domain logic is the part of the program that encodes the real-world business rules that determine how data can be created, stored, and changed.컴퓨터 소프트웨어에서 비즈니스 로직 또는 도메인 로직은 데이터 생성, 저장 및 변경 방법을 결정하는 실제 비즈니스 규칙을 인코딩하는 프로그램의 일부입니다.<br/>
Wikipedia - Business logic</p>
</blockquote>
<p>소프트웨어 공학에서 말하는 비즈니스는 일반적인 맥락과는 다르게 ‘소프트웨어가 풀고자 하는 현실의 문제’를 뜻합니다. 비즈니스 로직은 도메인 로직이라고 부르기도 합니다. 만약 우리가 만드는 서비스가 비디오 스트리밍 도메인이라면 비디오 재생 및 공유, 결제 등이 바로 소프트웨어가 풀어야 하는 현실 문제가 됩니다. 따라서 FSD로 코드를 작성할 때 ‘이 코드가 실제로 비즈니스적 의사결정을 하고 있는지’ 정도에 따라 해당되는 Layer 폴더에 작성되어야 합니다.</p>
<blockquote>
<p>만약 어떤 코드를 명확하게 비즈니스 로직인지 판단할 수 없다면 이는 해당 코드를 더 작개 쪼개야 한다는 신호입니다.</p>
</blockquote>
<p>다음은 비디오 스트리밍 서비스에 대한 FSD 레이어 분리 예시입니다.</p>
<ul>
<li>/app → entry point</li>
<li>/pages → 각 페이지입니다<ul>
<li>/home</li>
<li>/video-player</li>
<li>/contents-detail</li>
<li>/user</li>
</ul>
</li>
<li>/widgets → 혼자 독립적인 단위로 존재할 수 있는 컴포넌트 묶음입니다<ul>
<li>/contents-list</li>
<li>/video-player</li>
</ul>
</li>
<li>/features → 비즈니스 밸류를 가지는 사용자 시나리오나 기능을 다룹니다<ul>
<li>/login</li>
<li>/player-controller</li>
<li>/player-captions</li>
<li>/banner-swiper</li>
</ul>
</li>
<li>/entities → 비즈니스 주체입니다<ul>
<li>/user</li>
<li>/video</li>
<li>/like</li>
<li>/contents</li>
<li>/banner </li>
</ul>
</li>
<li>/shared → 특정 비즈니스 로직에 종속되지 않는 재사용 컴포넌트입니다<ul>
<li>/api</li>
<li>/ui</li>
<li>/type</li>
</ul>
</li>
</ul>
<p>위 사례와 같이 FSD를 실제 적용해보면 widgets과 features, entities의 구분이 모호해지는 경우가 발생할 수 있습니다. 이를 보완하기 위해 다음의 가이드를 제안합니다.</p>
<ul>
<li>widgets → pages 컴포넌트에서 조합하여 사용하기 위한 거의 완성된 독립 기능들</li>
<li>features → widgets를 구성하기 위한 비즈니스 로직의 구체적인 표현 기능들 (결제, 재생, 좋아요, 환불 등)</li>
<li>entities → features에서 구체적인 동작이 부여되기 전인 비즈니스 주체들 (유저, 비디오, 라이크버튼 등)</li>
<li>shared → 특정 비즈니스 로직에 속하지 않는 재사용 대상들 (AxiosInstance, types, ButtonBase, layout…)</li>
</ul>
<h3 id="slices">Slices</h3>
<p>각각의 Layer 마다 Slices라는 서브 디렉토리를 가집니다. 이는 두 번째 분해 레벨로 코드를 그것들의 가치로 묶는 것(group code by its value)을 목적으로 합니다.</p>
<pre><code>└── src/
    ├── app/
    │   ├── providers/ 
    │   ├── styles/
    │   └── index.tsx/
    ├── pages/
    │   ├── home/
    │   ├── profile/
    │   └── about/
    ├── widgets/
    │   ├── newsfeed/
    │   ├── header/
    │   └── footer/
    ├── features/
    │   ├── user/
    │   ├── auth/
    │   └── favorites/
    ├── entities/
    │   ├── user/
    │   └── sessions/
    └── shared/</code></pre><h3 id="segments">Segments</h3>
<p>각각의 Slice는 다시 Segment로 구성됩니다. Segment는 목적 기반으로 Slice의 코드를 분할(divide the code within a slice based on its purpose)하도록 돕습니다. </p>
<ul>
<li>api - 서버 요청</li>
<li>UI - Slice의 UI 컴포넌트</li>
<li>model - 비즈니스 로직, 상태 관련 인터랙션 (actions and selectors)</li>
<li>lib - Slice 안에서 사용되는 보조 함수</li>
<li>config - Slice에 대한 설정 (그러나 흔하게 발생하지는 않음)</li>
<li>consts - 필요한 상수</li>
</ul>
<h3 id="public-api">Public API</h3>
<p>Public API는 index.ts와 같은 entry point 파일을 두어 필요한 기능만 Slice나 Segment에서 불러올 수 있도록 설정하는 것입니다. Public API는 다음의 원칙을 따릅니다.</p>
<p><strong>Public API 원칙 1: 앱의 Slice와 Segment들은 Public API index 파일에 정의된 기능과 컴포넌트만 사용한다
Public API 원칙 2: Public API에 정의되지 않은 내부적인 부분은 격리된 것으로 간주하여 그 자신의 Slice나 Segment만 여기에 접근할 수 있다</strong></p>
<h3 id="아키텍처-심화">아키텍처 심화</h3>
<p><strong>추상화와 비즈니스 로직</strong></p>
<ul>
<li>위계가 높아질수록 특정 비즈니스 노드와 강하게 결합되며 더 많은 비즈니스 로직을 담게됩니다.</li>
<li>위계가 낮아질수록 더욱 추상화 되며, 더욱 재사용 가능하게 되고 자율성은 줄어듭니다.</li>
</ul>
<p><strong>FSD가 해결할 수 있는 문제</strong></p>
<ul>
<li>Loose coupling: 느슨한 결합</li>
<li>High cohesion: 높은 응집력</li>
</ul>
<p>Layer를 통해 추상화(Abstraction)와 다형성(Polymorphism)을 확보!</p>
<ul>
<li>낮은 단계의 레이어는 추상적이며 높은 레벨에서 재사용 가능</li>
<li>특정 props에 따라 레이어는 다양한 동작 가능</li>
</ul>
<p>Public API를 통해 캡슐화(Encapsulation) 확보!</p>
<ul>
<li>불필요하게 노출할 필요가 없는 부분은 격리</li>
</ul>
<p>높은 Layer의 낮은 Layer 재사용을 이용한 상속(Inheritance) 구현!</p>
<h3 id="기존-아키텍처와의-비교">기존 아키텍처와의 비교</h3>
<ul>
<li>기존 아키텍처는 컴포넌트나 모듈 사이의 연결에 대한 명시적 연결이 부족하며 이는 규모가 커질수록 심각해진다.</li>
<li>FSD는 이러한 기존 구조의 문제점을 보완하여 더욱 명확한 정리를 돕는다.</li>
<li>FSD를 적용하려면 문제를 ‘나중’이 아닌 ‘지금’ 다룰 수 있어야 한다.</li>
</ul>
<p><strong>FSD 적용의 장점</strong></p>
<ul>
<li>아키텍처 구성 요소를 쉽게 교체, 추가 또는 제거할 수 있다</li>
<li>아키텍처의 표준화 가능</li>
<li>확장성</li>
<li>개발 스택과 무관한 방법론</li>
<li>예상치 못한 부작용 없이 모듈 간 제어 및 명시적인 연결</li>
<li>비즈니스 중심의 아키텍처 방법론</li>
</ul>
<p><strong>FSD 적용의 단점</strong></p>
<ul>
<li>다른 많은 아키텍처 솔루션에 비해 진입 장벽이 높다</li>
<li>팀 인식, 문화 및 개념 준수가 필요</li>
<li>문제를 즉시 해결하지 못하고 나중에 해결하려 한다면 적합하지 않다</li>
</ul>
<p>참고</p>
<p><a href="https://feature-sliced.design/">https://feature-sliced.design/</a> 
<a href="https://velog.io/@eddy_song/domain-logic">https://velog.io/@eddy_song/domain-logic</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[프론트엔드 성능 최적화 Cheat Sheet]]></title>
            <link>https://velog.io/@seoyong-lee/%ED%94%84%EB%A1%A0%ED%8A%B8%EC%97%94%EB%93%9C-%EC%84%B1%EB%8A%A5-%EC%B5%9C%EC%A0%81%ED%99%94-Cheat-Sheet</link>
            <guid>https://velog.io/@seoyong-lee/%ED%94%84%EB%A1%A0%ED%8A%B8%EC%97%94%EB%93%9C-%EC%84%B1%EB%8A%A5-%EC%B5%9C%EC%A0%81%ED%99%94-Cheat-Sheet</guid>
            <pubDate>Mon, 08 Apr 2024 11:22:31 GMT</pubDate>
            <description><![CDATA[<p>참고 - <a href="https://product.kyobobook.co.kr/detail/S000200178292">프론트엔드 성능 최적화 가이드(유동균)</a></p>
<p><img src="https://velog.velcdn.com/images/sy3783/post/1cbd491b-32c4-419f-a4cb-8d1eac4413c1/image.png" alt=""></p>
<p>웹 팀에서 성능 최적화를 위한 방향을 빠르게 찾기 위해 간단하게 Cheat Sheet을 만들어 보았습니다.</p>
<h2 id="최적화가-필요한-부분이-언제인가">최적화가 필요한 부분이 언제인가?</h2>
<p>렌더링 이전 → O1. 로딩 성능 최적화
렌더링 과정 → O2. 렌더링 성능 최적화</p>
<h2 id="o1-로딩-성능-최적화">O1. 로딩 성능 최적화</h2>
<h3 id="이미지">이미지</h3>
<ul>
<li>이미지 사이즈 최적화<ul>
<li>Lighthouse - Properly size images 확인</li>
<li>이미지 사이즈 조절 (최소 2배)</li>
<li>이미지 형식 변경 (PNG → JPG)</li>
<li>이미지 스프라이트 적용</li>
</ul>
</li>
<li>이미지 지연 로딩<ul>
<li>react-lazyload</li>
<li>Intersection Observer API</li>
</ul>
</li>
</ul>
<h3 id="폰트">폰트</h3>
<ul>
<li>폰트 최적화<ul>
<li>적용 시점 제어 (CSS font-display)</li>
<li>폰트 포맷 변경 (가능하다면 TTF → WOFF)</li>
</ul>
</li>
</ul>
<h3 id="캐시-및-cdn">캐시 및 CDN</h3>
<ul>
<li>캐시 최적화<ul>
<li>header ‘Cache-Control’ (서버)<ul>
<li>max-age로 유효 시간 조절 가능</li>
</ul>
</li>
</ul>
</li>
<li>CDN<ul>
<li>이미지 등 사용자와 가까운 위치의 Content Delivery Network에 저장</li>
<li>AWS CloudFront 등 사용</li>
</ul>
</li>
</ul>
<h3 id="기타">기타</h3>
<ul>
<li>코드 분할<ul>
<li>webpack-bundle-analyzer 확인</li>
<li>lazy import (파일)</li>
<li>preloading</li>
</ul>
</li>
<li>컴포넌트 분할<ul>
<li>lazy import (컴포넌트)</li>
<li>성능 저하시 preload 구현 (ex 마우스오버 시 로드, 클릭 시 이동)</li>
</ul>
</li>
<li>텍스트 압축<ul>
<li>Lighthouse Opportunities - Enable text compression 확인</li>
</ul>
</li>
<li>불필요 파일 제거<ul>
<li>Lighthouse Opportunities - Reduce unused CSS 확인</li>
</ul>
</li>
</ul>
<h2 id="o2-렌더링-성능-최적화">O2. 렌더링 성능 최적화</h2>
<ul>
<li>병목 코드 최적화<ul>
<li>Lighthouse Diagnostics - Reduce JavaScript execution time 확인</li>
<li>중복 제거, 적절한 메소드 활용, 당장 필요한 데이터만 처리</li>
<li>복잡한 연산 memo 사용</li>
</ul>
</li>
<li>애니메이션 최적화<ul>
<li>리플로우, 리페인트 방지</li>
<li>transform / opacity 사용 → GPU 가속</li>
</ul>
</li>
<li>레이아웃 이동 최적화<ul>
<li>CLS(Cumulative Layout Shift) 확인</li>
<li>미리 요소 사이즈 지정</li>
</ul>
</li>
<li>리렌더링 방지<ul>
<li>hook 리팩토링</li>
</ul>
</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[Matter.js로 브라우저에서 물리엔진 구현하기]]></title>
            <link>https://velog.io/@seoyong-lee/Matter.js%EB%A1%9C-%EB%B8%8C%EB%9D%BC%EC%9A%B0%EC%A0%80%EC%97%90%EC%84%9C-%EB%AC%BC%EB%A6%AC%EC%97%94%EC%A7%84-%EA%B5%AC%ED%98%84%ED%95%98%EA%B8%B0</link>
            <guid>https://velog.io/@seoyong-lee/Matter.js%EB%A1%9C-%EB%B8%8C%EB%9D%BC%EC%9A%B0%EC%A0%80%EC%97%90%EC%84%9C-%EB%AC%BC%EB%A6%AC%EC%97%94%EC%A7%84-%EA%B5%AC%ED%98%84%ED%95%98%EA%B8%B0</guid>
            <pubDate>Fri, 08 Dec 2023 06:52:22 GMT</pubDate>
            <description><![CDATA[<p><a href="https://brm.io/matter-js/">Matter.js </a></p>
<blockquote>
<p>Matter.js is a 2D physics engine for the web</p>
</blockquote>
<p>Matter.js는 브라우저 환경에서 물리 엔진 구현을 위한 라이브러리입니다. canvas로 그린 요소에 대한 중력 및 충돌, 드래그를 이용한 조작 등을 구현할 수 있습니다.
<img src="https://velog.velcdn.com/images/seoyong-lee/post/0ed71f49-9d9f-4419-b1b6-df3a5f94a499/image.gif" alt=""></p>
<h2 id="기본-요소들">기본 요소들</h2>
<h3 id="engine">Engine</h3>
<ul>
<li>화면에 구현될 세계를 조작하기 위한 기본적인 엔진을 생성하고 조작하는 방법이 포함된 모듈입니다. </li>
<li>엔진은 뒤에서 설명할 world의 시뮬레이션 업데이트를 관리하는 컨트롤러입니다. </li>
<li><code>Matter.Engine</code>으로 선언합니다.</li>
</ul>
<h3 id="render">Render</h3>
<ul>
<li>Engine 인스턴스를 시각화하기 위한 간단한 HTML5 캔버스 기반 렌더러입니다.</li>
<li>와이어프레임, 스프라이트 및 뷰포트를 지원하는 벡터 등 다양한 드로잉 옵션을 포함합니다.</li>
<li><code>Matter.Render</code>로 선언합니다.</li>
</ul>
<h3 id="body">Body</h3>
<ul>
<li>body 모델을 생성하고 조작하는 방법이 포함된 모듈입니다. </li>
<li>body는 render 모듈이 그리는 대상이 되는 삼각형, 사각형, SVG 등의 요소입니다.</li>
<li>실제 구현에는 자주 사용되는 요소들을 모아 만든 factory methods인 Bodies를 사용할 수 있습니다.</li>
</ul>
<h3 id="world">World</h3>
<ul>
<li>render를 통해 그리는 대상물을 모은 world composite 생성과 조작을 위한 모듈입니다.</li>
<li>body로 만든 모든 요소들은 world라는 세계 위에서 표현됩니다.</li>
<li>world에는 중력을 조절하는 gravity와 axis-aligned bounding boxes (AABB) 조절을 위한 bounds 프로퍼티가 추가로 존재합니다.</li>
</ul>
<h2 id="사용법">사용법</h2>
<p>먼저 canvas와 이를 감싼 container를 선언하고 useRef을 이용해 참조를 구성합니다.</p>
<pre><code class="language-ts">import { useRef } from &quot;react&quot;;

const Canvas = () =&gt; {
  const containerRef = useRef&lt;HTMLDivElement | null&gt;(null);
  const canvasRef = useRef&lt;HTMLCanvasElement | null&gt;(null);

  return (
    &lt;section
      ref={containerRef}
    &gt;
      &lt;canvas
        ref={canvasRef}
        id=&quot;viewport&quot;
        width=&quot;500&quot;
        height=&quot;500&quot;
      /&gt;
    &lt;/section&gt;
  );
};

export default Canvas;</code></pre>
<p>이후 Matter.Bodies모듈을 통해 요소를 생성합니다. 사각형, 원, SVG, 이미지 등의 생성이 가능합니다.</p>
<pre><code class="language-ts">const Bodies = Matter.Bodies;

const element = Bodies.rectangle(
  x,
  y,
  width,
  height
  {
    chamfer: {
      radius: [10, 10] // border-radius 설정이 가능합니다.
    },
    render: {
      fillStyle: &quot;#fff&quot;, 
      sprite: {
          texture: &quot;/assets/main/logo.png&quot;, // 이미지를 입힐 수 있습니다.
          xScale: 0.75,
          yScale: 0.75,
       },
    },
    restitution: 0.6, // 충격을 받으면 튀어오르는 정도를 조절합니다.
  }
);</code></pre>
<p>이제 이를 엔진을 통해 그려봅니다.</p>
<pre><code class="language-ts">useEffect(() =&gt; {
  const Engine = Matter.Engine;
  const Render = Matter.Render;
  const World = Matter.World;
  const engine = Engine.create();
  engine.gravity.y = 1.5; // 중력의 세기를 설정합니다.

  const render = Render.create({
    element: containerRef.current,
    engine: engine,
    canvas: canvasRef.current,
    bounds: {
      min: { x: 0, y: 0 },
      max: { x: canvasWidth, y: canvasHeight },
    },
    options: {
      showSeparations: true,
      width: canvasWidth,
      height: canvasHeight,
      background: &quot;&quot;,
      wireframes: false,
    },
  });

  // 마우스를 이용해 조작을 가능하게 해줍니다.
  const mouse = Matter.Mouse.create(render.canvas),
    mouseConstraint = Matter.MouseConstraint.create(engine, {
      mouse: mouse,
      constraint: {
        stiffness: 0.2,
        render: {
          visible: false,
        },
      },
    });

  // 그릴 요소들을 world에 모읍니다.
  World.add(engine.world, [
    element,
    mouseConstraint,
  ]);

  Matter.Runner.run(engine); // 엔진을 구동합니다.
  Render.run(render); // 렌더를 진행합니다.
  Body.rotate(element, Math.PI / 6);

  return () =&gt; {
    Render.stop(render);
    World.clear(engine.world, false);
    Engine.clear(engine);
    render.canvas.remove();
  };
}, []);</code></pre>
<p>구현이 완료되면 아래와 같이 아래로 떨어지는 요소를 확인할 수 있습니다.</p>
<p><img src="https://velog.velcdn.com/images/seoyong-lee/post/4f4e5eaa-61f2-41df-a5e8-c6e2c13dca89/image.gif" alt=""></p>
<p>만약 요소가 떨어져서 사라지는 것이 아닌 특정 지점에 고정하고 싶다면 아래에 벽을 만들어 떨어지지 않도록 막아야합니다.</p>
<p>좌/우/위/아래를 막을 벽을 세워줍니다.</p>
<pre><code class="language-ts">// props: (x: number, y: number, width: number, height: number)
const floor = Bodies.rectangle(clientWidth / 2, clientHeight, clientWidth, 100, {
  isStatic: true,
  render: {
    fillStyle: &quot;#121212&quot;,
  },
});

const floorLeft = Bodies.rectangle(0, clientHeight / 2, 50, clientHeight, {
  isStatic: true,
  render: {
    fillStyle: &quot;#121212&quot;,
  },
});

const floorRight = Bodies.rectangle(clientWidth, clientHeight / 2, 50, clientHeight, {
  isStatic: true,
  render: {
    fillStyle: &quot;#121212&quot;,
  },
});

const floorTop = Bodies.rectangle(clientWidth / 2, -10, clientWidth, 50, {
  isStatic: true,
  render: {
    fillStyle: &quot;#121212&quot;,
  },
});

useEffect(()=&gt;{
  // ...
    World.add(engine.world, [
      // 벽을 world에 추가합니다.
      floor,
      floorLeft,
      floorRight,
      floorTop,
      logo,
      mouseConstraint,
    ]);
  // ...
}, [])</code></pre>
<p>이제 다음과 같이 특정 공간 안에 요소를 가둬 둘 수 있게됩니다.</p>
<p><img src="https://velog.velcdn.com/images/seoyong-lee/post/fead0cea-bd54-407e-9678-6a539d3bd402/image.gif" alt=""></p>
<h2 id="여러-구현-사례들">여러 구현 사례들</h2>
<p>만약 구현하고 싶은 대상을 찾아보고 싶다면 공식으로 지원하는 데모를 확인해 보실 수 있습니다. </p>
<p><a href="https://brm.io/matter-js/demo/#mixed">Matter.js Demo · code by @liabru</a>
<img src="https://velog.velcdn.com/images/seoyong-lee/post/370a2465-aef3-4201-b6ca-6f28f3cc6976/image.png" alt=""></p>
<p>여러가지 옵션들을 제공하며 직접 만져보면서 느낌을 확인해 볼 수 있습니다. 
공식 깃허브를 통해 Matter.js를 사용해 만든 다양한 사이트 예시도 확인 가능합니다.</p>
<p><a href="https://gameoftheyear.withgoogle.com/">Google - Game of the Year</a>
<a href="https://www.fuse.kiwi/">Fuse.kiwi – Interesting Internet</a>
<a href="https://patrickheng.com/">Patrick Heng - Creative Developer Portfolio</a>
<a href="https://useless.london/">USELESS</a></p>
<p>참고
<a href="https://github.com/liabru/matter-js/tree/master">GitHub - liabru/matter-js</a>
<a href="https://brm.io/matter-js/">Matter.js</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[리액트 Hook과 조금 더 친해지기]]></title>
            <link>https://velog.io/@seoyong-lee/%EB%A6%AC%EC%95%A1%ED%8A%B8-Hook%EA%B3%BC-%EC%A1%B0%EA%B8%88-%EB%8D%94-%EC%B9%9C%ED%95%B4%EC%A7%80%EA%B8%B0</link>
            <guid>https://velog.io/@seoyong-lee/%EB%A6%AC%EC%95%A1%ED%8A%B8-Hook%EA%B3%BC-%EC%A1%B0%EA%B8%88-%EB%8D%94-%EC%B9%9C%ED%95%B4%EC%A7%80%EA%B8%B0</guid>
            <pubDate>Thu, 07 Dec 2023 11:26:19 GMT</pubDate>
            <description><![CDATA[<p>개인적으로 리액트의 작동방식은 한 번에 이해하기 어렵다고 생각합니다. 보통 어려운 것들은 참고할 만한 멘탈모델이 없는 경우가 많습니다. 저도 처음 공부할 때는 이상하게 느껴졌지만, 그냥 외웠습니다. 그러나 react.dev를 정독하면서 리액트와 조금 더 가까워지기 시작했고 여러 가지 재미있는 비유들이 떠올랐습니다.</p>
<h2 id="컴포넌트와-렌더링">컴포넌트와 렌더링</h2>
<blockquote>
<p>지금부터 조금 이상한 이야기를 해보겠습니다.</p>
</blockquote>
<p>한 강줄기가 있습니다. 강은 줄기를 따라 여러 갈래로 갈라지면서 잔잔한 하류로 이어집니다. 강 가장 위쪽 상류에서부터 배를 타고 하류로 내려오는 사람들이 있다고 생각해 봅시다. 이 사람들은 특이한 노동을 합니다. 바로 강의 상류에서 개발자들이 만든 배의 설계도를 들고 출발해 흘러가는 강 위에서 배를 만들어 잔잔한 바다가 있는 하류에 정박시키는 일입니다. 자칫 설계가 잘못되면 누수가 발생해 배가 침몰할 수도 있는 매우 위험하고 어려운 작업이지만 완벽주의자인 이 사람들은 자신들의 명성을 위해 끝까지 설계도대로 배를 완성하려 노력합니다. 성공하면 보상으로 미지의 외부 세계와 통신할 수 있게 됩니다.</p>
<blockquote>
<p>각 비유가 의미하는 것은 무엇일까요?</p>
</blockquote>
<p>리액트 컴포넌트를 배라고 생각해봅시다. 우리는 코드라는 설계도를 통해서 컴포넌트라는 배를 그립니다. 배는 여러 개일 수도 있고 서로 연결되어 있을 수도 있습니다. 배의 색과 모양은 스타일링을 통해 마음대로 바꿀 수 있지만 어느 시점에는 고정되어야 합니다. 우리가 만든 배는 렌더링이라는 강물을 따라 흘러가 최종적으로 하류에 도착하면 모든 변화가 완료됩니다. </p>
<p>하나 주의할 점은 배에 조금이라도 변경 사항이 발생하면 배를 부수고 처음부터 다시 만들어야 한다는 점입니다. 조금 이상한 법칙이지만 배를 만드는 리액트라는 일꾼들은 완벽주의자라 그렇다고 합니다. 강의 흐름 속에서 우리는 배 안이나 밖에 무언가(변수)를 둘 수도 있지만 갈고리로 잘 묶어둔 정보만 남고 나머지는 배가 다시 만들어지는 과정에서 모두 사라지게 됩니다.</p>
<p>여기서 생각해 볼 점 중의 하나는 강은 하류 한 방향으로만 흐른다는 점입니다. 상류로 다시 물이 흐르게 되면 배는 하류로 가지 못하고 강 중간에 갇혀 버리게됩니다. 그럼 이제 배를 하나 만들어 보겠습니다.</p>
<pre><code class="language-jsx">const Boat = () =&gt; {
  const name = &quot;Liberty&quot;;
  return (
    &lt;section&gt;
      &lt;div&gt;
        &lt;h1&gt;{name}&lt;/h1&gt;
      &lt;/div&gt;
    &lt;/section&gt;
  );
};

export default Boat;</code></pre>
<p>Liberty 라는 이름의 보트 설계도가 완성되었습니다. 이제 이 설계도를 들고 리액트라는 일꾼에게 맡기면 강을 따라 멋진 보트를 완성합니다.</p>
<h2 id="컴포넌트의-변경">컴포넌트의 변경</h2>
<p>그런데 이때 야망 있는 클라이언트에 의해 보트에 요구 사항이 추가됩니다.</p>
<blockquote>
<p>하류로 내려가면 외부인들이 보트를 탈 수 있도록 여닫는 문을 만들어주세요!</p>
</blockquote>
<p>일단 위 문장을 리액트식으로 해석해 보겠습니다. 우선 하류로 내려가려면 렌더링을 모두 종료하고 컴포넌트를 완성해야합니다. 두 번째로 컴포넌트에 외부인이 들어올 수 있도록 열리고 닫히는 문을 추가해야 하므로 외부 이벤트를 감지할 방법을 마련해야합니다. 마지막으로 문이라는 ‘상태’가 변경되면 배의 모습은 이전과 달라지기 때문에 변경 시마다 배를 다시 만들어 주어야 합니다. </p>
<p>이렇게 렌더링 이외의 사유로 변경이 발생하면 리액트는 이를 별로 좋아하지 않기 때문에 main(렌더링)에서 벗어난 Side Effect라고 취급합니다.</p>
<p>요구사항은 다시 다음 3가지로 정리해 볼 수 있겠습니다.</p>
<blockquote>
<ol>
<li>렌더링 완료</li>
<li>이벤트 감지 및 처리</li>
<li>상태 변경 이력을 저장하고 변경 발생 시 컴포넌트 리렌더링</li>
</ol>
</blockquote>
<p>먼저 1번의 경우 정상적으로 배를 그렸다면 쉽게 달성할 수 있지만 2번은 외부 이벤트를 다룰 방법이 필요합니다. 이는 클릭 등을 처리해주는 이벤트 핸들러를 이용하면 가능합니다. 3번은 상태라는 변수를 선언하고 이 변수를 이벤트에 맞춰서 바꿔주면 될 것 같습니다. </p>
<p>그럼 이제 위 요구사항을 바탕으로 배를 수정해 보겠습니다.</p>
<pre><code class="language-jsx">import { useState } from &quot;react&quot;;

const Boat = () =&gt; {
  const name = &quot;Liberty&quot;;

  let isDoorOpen = false; // 문은 처음엔 닫혀있습니다.

  const handleClickDoor = () =&gt; {
    isDoorOpen = true; // 과연 문이 열릴까요?
  };

  return (
    &lt;section&gt;
      &lt;div&gt;
        &lt;h1&gt;{name}&lt;/h1&gt;
      &lt;/div&gt;
      &lt;button onClick={handleClickDoor}&gt;Door&lt;/button&gt;
    &lt;/section&gt;
  );
};

export default Boat;</code></pre>
<p>과연 문은 열릴까요? 이렇게 하면 아무 일도 일어나지 않습니다. 왜일까요?</p>
<p>사실 리액트는 자신들의 방식대로 갈고리(Hook)로 배에 묶어두지 않은 어떤 상태가 변경되면 합의된 방식을 지키지 않았다고 생각해서 이를 그냥 무시해 버립니다…. 그렇다면 위 코드를 리액트가 이해하는 방식대로 다시 바꿔보겠습니다.</p>
<pre><code class="language-jsx">import { useState } from &quot;react&quot;;

const Boat = () =&gt; {
  const name = &quot;Liberty&quot;;
  const [isDoorOpen, setIsDoorOpen] = useState(false);

  const handleClickDoor = () =&gt; {
    setIsDoorOpen(!isDoorOpen);
  };

  return (
    &lt;section&gt;
      &lt;div&gt;
        &lt;h1&gt;{name}&lt;/h1&gt;
      &lt;/div&gt;
      &lt;button onClick={handleClickDoor}&gt;Door&lt;/button&gt;
    &lt;/section&gt;
  );
};

export default Boat;</code></pre>
<p>이제 문이 열리고 닫힐 때마다 컴포넌트가 새롭게 그려집니다. 위처럼 리액트는 리액트의 방식으로 무언가를 하기 위해 hook이라는 갈고리를 이용합니다. 컴포넌트 내부에 어떤 상태를 두고 이 상태가 변경될 때마다 컴포넌트를 다시 그리기 위해 사용하는 대표적인 hook이 바로 useState입니다.</p>
<h2 id="hook의-종류">Hook의 종류</h2>
<p>리액트에서 ‘use-’ 로 시작하는 것들은 hook이라고 부릅니다. 이름이 hook인 이유는 리액트에 따르면 다음과 같습니다. </p>
<blockquote>
<p><em>Hooks</em> are special functions that are only available while React is rendering… They let you “hook into” different React features.
<em>Hook</em>은 리액트가 렌더링되는 동안에만 사용할 수 있는 특수한 기능입니다… 이를 통해서 리액트의 다양한 기능에 “연결할(hook into)” 수 있습니다.</p>
</blockquote>
<p>그냥 이름 그대로 연결을 위한 갈고리라고 생각하면 이해하기 쉬울 것 같습니다.</p>
<p>리액트와 같이 사용하기로 약속된 대표적인 hook들은 다음과 같습니다.</p>
<ul>
<li>useState: 컴포넌트 내부적인 상태를 관리하기 위해 사용됩니다.</li>
<li>useEffect: 이벤트 이외의 사유로 리렌더링이 필요한 경우 사용됩니다.</li>
<li>useRef: 특정 DOM 요소를 렌더링 전후로 계속 참조하기 위해 사용됩니다.</li>
<li>useMemo: 계산된 특정 값을 렌더링 전후로 계속 캐싱해두기 위해 사용합니다.</li>
<li>useCallback: 특정 함수를 렌더링 전후로 계속 캐싱해두기 위해 사용합니다.</li>
</ul>
<p>다음은 위 hook들 보다는 자주 사용되지는 않지만 유용한 hook입니다.</p>
<ul>
<li>useContext: 컴포넌트 트리 깊은 곳의 컴포넌트에 context를 전달하기 위해 사용합니다.</li>
<li>useReducer: useState와 비슷하지만 reducer를 이용해서 이벤트 핸들러와 분리된 전문적인 처리를 할 수 있게 해줍니다.</li>
<li>useLayoutEffect: 브라우저 리페인트 이전에 필요한 처리를 할 수 있도록 해줍니다.</li>
</ul>
<h2 id="hook의-실행순서">Hook의 실행순서</h2>
<p>그렇다면 이러한 hook 들은 어떤 순서로 실행될까요? </p>
<ol>
<li>useState, useMemo</li>
</ol>
<ul>
<li>먼저 useState와 useMemo는 가장 먼저 렌더링 단계에서 실행됩니다. Hook은 내부적으로 호출 순서를 기억해두었다가 리렌더링 시마다 동일한 순서로 작동하도록 설계되어있기 때문에 이 순서를 유지하도록 항상 리액트 함수 최상단에 선언되어야 합니다.</li>
</ul>
<ol>
<li>useLayoutEffect</li>
</ol>
<ul>
<li>useLayoutEffect는 DOM 렌더링 직후 Critical Rendering Path의 리페인트 직전에 동기적으로 실행됩니다. UI 블로킹을 유발할 수 있기 때문에 꼭 필요한 경우에만 사용해야 합니다.</li>
</ul>
<ol>
<li>useEffect</li>
</ol>
<ul>
<li>useEffect는 렌더링 완료 이후 실행됩니다. 이후에 Dependency로 제공된 변수의 변화가 일어난 경우에만 작동합니다.</li>
</ul>
<p>위처럼 리액트 hook은 여러 개가 선언되어 있더라도 컴포넌트 안에서 자신들의 실행 순서를 정확하게 지킵니다. 만약 실행 시마다 순서가 뒤섞인다면 일관적인 결과를 얻지 못하게 될 수 있습니다. 따라서 리액트는 정책적으로 조건문 안에서의 hook 사용을 금지합니다.</p>
<h2 id="hook-잘-사용하기">Hook 잘 사용하기</h2>
<p>그렇다면 hook을 잘 사용하기 위한 원칙은 무엇이 있을까요? 먼저 다음을 기억하면 좋습니다.</p>
<blockquote>
<p>꼭 필요한 경우에만 hook을 쓰고 있는지 고민해보세요</p>
</blockquote>
<p>먼저 불필요한 hook은 불필요한 리렌더링을 발생시킨다는 점을 이해해야 합니다. 다음과 같이 어떤 useEffect는 애초에 사용할 필요가 없을 수도 있습니다.</p>
<pre><code class="language-jsx">const [state, setState] = useState(false);
const [state2, setState2] = useState(false);

const handleUpdateState = () =&gt; {
  setState(true);
};

useEffect(() =&gt; {
  if (state) {
    setState2(true);
  }
}, [state]);

useEffect(() =&gt; {
  if (state2) {
    // ...
  }
}, [state2]);</code></pre>
<p>위 사례를 보면 여러 가지 hook이 서로 맞물려 불필요한 상태 변경을 발생시키고 있지만 결국 하나로 합칠 수 있습니다. 너무 당연하지만 실제로 실무에서 종종 여러가지 hook이 맞물리면 이런 경우를 놓치는 경우가 생길 수 있습니다. 이러한 부분은 문법적으로는 문제가 없기 때문에 lint를 통해 발견하기 어렵습니다. </p>
<p>또 다른 예는 이벤트 핸들러만으로 처리가 가능한 사례입니다. 만약 어떤 버튼을 클릭하면 POST 요청을 보낸다고 가정하면 POST 요청을 useEffect가 아닌 이벤트 핸들러 안에서 보내도록 하는 것이 좋습니다. 관련 내용은 <a href="https://react.dev/learn/you-might-not-need-an-effect">공식 문서</a>에서도 자세히 다루고 있습니다.</p>
<h2 id="마무리">마무리</h2>
<p>리액트가 나의 일을 도와주는 사람이라면 리액트의 방식을 존중하고 합의된 방식대로 일을 해주는 것이 동료를 위한 기본적인 도리라고 생각됩니다. 과연 나는 오늘도 동료를 위한 도리를 다했을까요? </p>
<p>가끔 반복적인 업무에 지쳤을 때 여러 가지 재미있는 관점에서 익숙했던 기술을 바라보는 것도 좋을 것 같습니다.</p>
<h3 id="참고">참고</h3>
<p><a href="https://react.dev/learn/state-a-components-memory">react.dev - State: A Component&#39;s Memory</a>
<a href="https://medium.com/@ryardley/react-hooks-not-magic-just-arrays-cd4f1857236e">Rudi Yardley - React hooks: not magic, just arrays</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[리액트 컴포넌트 불순하게 만들어보기]]></title>
            <link>https://velog.io/@seoyong-lee/%EB%A6%AC%EC%95%A1%ED%8A%B8-%EC%BB%B4%ED%8F%AC%EB%84%8C%ED%8A%B8-%EB%B6%88%EC%88%9C%ED%95%98%EA%B2%8C-%EB%A7%8C%EB%93%A4%EC%96%B4%EB%B3%B4%EA%B8%B0</link>
            <guid>https://velog.io/@seoyong-lee/%EB%A6%AC%EC%95%A1%ED%8A%B8-%EC%BB%B4%ED%8F%AC%EB%84%8C%ED%8A%B8-%EB%B6%88%EC%88%9C%ED%95%98%EA%B2%8C-%EB%A7%8C%EB%93%A4%EC%96%B4%EB%B3%B4%EA%B8%B0</guid>
            <pubDate>Mon, 04 Dec 2023 03:25:06 GMT</pubDate>
            <description><![CDATA[<p>아래 글은 <a href="http://react.dev/">React.dev - Keeping Components Pure</a> 의 내용을 기반으로 작성되었습니다. </p>
<p><a href="http://React.dev">React.dev</a>에 따르면 React의 컴포넌트는 순수(Pure)해야 한다고 합니다. 그렇다면 순수함(Purity)이란 무엇이고 왜 리액트 컴포넌트는 순수해야 하는 것일까요? 만약 생각을 뒤집어서 컴포넌트를 의도적으로 불순하게 만들려면 어떻게 해야 할까요? 발상의 전환을 통해 리액트에서의 컴포넌트와 순수함수를 더 잘 이해해보는 시간을 가져보려 합니다.</p>
<h2 id="순수함의-정의">순수함의 정의</h2>
<p>문서에 따르면 일반적으로 Computer Science 에서 순수함수는 다음 특징을 가집니다.</p>
<ol>
<li><strong>It minds its own business(자기 일에만 집중)</strong> <ul>
<li>함수 호출 전에 존재했던 다른 어떤 것에도 변화를 주지 않습니다</li>
</ul>
</li>
<li><strong>Same Inputs, same output(동일 입력 동일 출력)</strong> <ul>
<li>순수 함수는 항상 입력이 같다면 같은 결과를 반환합니다</li>
</ul>
</li>
</ol>
<p>사실 순수 함수는 우리가 수학에서 배웠던 함수와 같은 개념입니다.</p>
<pre><code class="language-ts">y = 2x</code></pre>
<p>만약 위 식에서 x가 2라면 y는 무조건 4입니다. 같은 입력에 항상 같은 값을 반환하기 때문에 위 공식을 함수로 만들면 순수함수의 정의를 만족하게 됩니다. </p>
<pre><code class="language-ts">function double(num:number) {
    return 2 * num;
}</code></pre>
<h2 id="리액트와-순수함수">리액트와 순수함수</h2>
<p>그렇다면 이러한 순수함수와 리액트는 무슨 관계일까요? 기본적으로 <strong>리액트는 우리가 작성한 모든 컴포넌트가 순수함수라고 가정</strong>합니다.</p>
<p>위에서 보았던 순수함수의 정의대로 컴포넌트(함수)를 생각해보면 다음 특징을 만족해야 합니다.</p>
<blockquote>
</blockquote>
<ol>
<li>자기 일에만 집중한다 - 컴포넌트 렌더링 이전에 존재했던 변수가 변하면 안 됩니다</li>
<li>동일 입력, 동일 출력 - 입력이 같다면 항상 같은 결과를 렌더링 해야 합니다</li>
</ol>
<h3 id="불순함수-1---prop을-사용하지-않고-외부-변수-참조">불순함수 1 - prop을 사용하지 않고 외부 변수 참조</h3>
<p>이러한 특징을 파괴하고 컴포넌트를 불순하게 만드는 방법의 하나는 바로 하라는 대로 prop을 사용하지 않고 외부 변수를 사용하는 것입니다.</p>
<pre><code class="language-ts">let guest = 0;

function Cup() {
  guest = guest + 1; // 1번 위반 - 자기 밖의 일인 guest를 변경함!
  return &lt;h2&gt;Tea cup for guest #{guest}&lt;/h2&gt;;
}

export default function TeaSet() {

    // 2번 위반 - 입력이 모두 동일한 Cup의 렌더링 결과가 모두 다름
    // Tea cup for guest #2
  // Tea cup for guest #4
  // Tea cup for guest #6
  return (
    &lt;&gt;
      &lt;Cup /&gt;
      &lt;Cup /&gt;
      &lt;Cup /&gt;
    &lt;/&gt;
  );
}</code></pre>
<p>위 사례를 보면 Cup 컴포넌트가 2의 배수로 guest의 숫자를 변경하는 것을 보실 수 있습니다. 왜일까요? 이는 리액트의 ‘Strict Mode’가 컴포넌트를 2번씩 호출하기 때문으로, 만약 컴포넌트가 순수하고 제정신이었다면 2번째 호출에서도 정상적으로 1, 2, 3 을 반환했어야 한다는 점을 친절하게 알려주기 위해 설정된 안전장치입니다. </p>
<p>prop을 사용하도록 바꾸면 다음과 같이 컴포넌트는 순수해집니다.</p>
<pre><code class="language-ts">function Cup({ guest }) {
    // 1번 만족 - 자기 밖의 일은 신경쓰지 않음
  return &lt;h2&gt;Tea cup for guest #{guest}&lt;/h2&gt;;
}

export default function TeaSet() {
    // 2번 만족 - 동일 입력, 동일 출력
    // Tea cup for guest #1
  // Tea cup for guest #2
  // Tea cup for guest #3
  return (
    &lt;&gt;
      &lt;Cup guest={1} /&gt;
      &lt;Cup guest={2} /&gt;
      &lt;Cup guest={3} /&gt;
    &lt;/&gt;
  );
}</code></pre>
<p>여기서 하나 알아두면 좋은 리액트 사고방식 중의 하나는 바로 컴포넌트가 렌더링 되는 순서에 대해 가정하지 말아야 한다는 점입니다. 문서에서 이러한 이야기를 하는 이유는 바로 리액트에선 컴포넌트 렌더링이 동기적으로 진행되지 않을 수 있기 때문입니다.  리액트 문서에선 다음과 같은 비유적인 표현으로 이를 설명합니다.</p>
<blockquote>
<p>Rendering is like a school exam: each component should calculate JSX on their own!
렌더링은 학교 시험과 비슷합니다: 각 컴포넌트는 JSX에 대한 계산을 각자 진행합니다</p>
</blockquote>
<h3 id="불순함수-2---prop-state를-직접-변경하려는-시도">불순함수 2 - prop, state를 직접 변경하려는 시도</h3>
<p>리액트에서 지켜야 하는 또 하나의 중요한 원칙은 바로 컴포넌트 렌더링 시에 받을 수 있는 3가지 형태의 input(prop, state, context)은 read-only로 취급해야 한다는 것입니다. 리액트를 처음 배우는 과정에서 prop, state(setState), context를 직접 변경하려는 창의적인 시도를 해보셨던 분들은 아시겠지만 리액트는 에러를 통해 이를 직접 교정합니다. 그중에서 <a href="https://daveceddia.com/why-not-modify-react-state-directly/">state를 직접 변경하면 어떻게 되는지는 이 글</a>에서 자세히 설명하고 있습니다.</p>
<p>만약 컴포넌트 밖에 있는 이러한 변수를 직접 변경하려고 시도한다면 리액트는 이를 mutation(돌연변이)로 규정하고 배척합니다. 리액트에서의 mutation에 대한 정의는 다음과 같습니다.</p>
<blockquote>
<p>In the above example, the problem was that the component changed a preexisting variable while rendering. This is often called a “mutation” to make it sound a bit scarier.
위 사례에서 문제점은 바로 컴포넌트가 렌더링 시점에 이미 존재하는 변수를 변경하려 했던 점입니다. 이것은 흔히 무섭게 들리기 위해 “돌연변이”라고 부릅니다.</p>
</blockquote>
<p>그러나 모든 mutation이 금지되는 것은 아닙니다. 다음 사례와 같이 렌더링 시점에 같이 일어나는 변화는 “local mutation”이라고 부르며 이는 허용됩니다.</p>
<pre><code class="language-ts">function Cup({ guest }) {
  return &lt;h2&gt;Tea cup for guest #{guest}&lt;/h2&gt;;
}

export default function TeaGathering() {
  let cups = [];
  for (let i = 1; i &lt;= 12; i++) {
    cups.push(&lt;Cup key={i} guest={i} /&gt;);
  }
  return cups;
}</code></pre>
<p>cups라는 배열 변수는 Cup 컴포넌트 입장에선 외부에 있지만 TeaGathering 함수 내부에 같이 있기 때문에 for문 연산과 렌더링이 동시에 진행되며 이러한 작업은 외부에 영향을 주지 않습니다.</p>
<h2 id="순수함을-유지하면서도-변경이-필요한-것들">순수함을 유지하면서도 변경이 필요한 것들</h2>
<p>지금까지 컴포넌트의 순수함에 대해 알아보았습니다. 그렇다면 이러한 원칙을 충실히 지키기만 하면 좋은 서비스를 만들 수 있을까요? 실제 삶은 이론과 달리 여러 가지 기출변형의 연속입니다. 마찬가지로 리액트를 이용해서 실제 서비스를 만들기 위해서는 컴포넌트의 순수함을 유지하면서도 변화가 필요한 일들이 생깁니다. 이러한 변화를 리액트는 “side effects”라고 부릅니다. 렌더링 과정에서 벗어난 side에서 변화가 일어나기 때문입니다.</p>
<p>side effect의 대표적인 예는 바로 이벤트 처리입니다. 사용자는 렌더링 시점에 맞춰서 순수하게 버튼을 클릭하지는 않습니다. 생각해보면 너무 당연한 말이지만 이벤트 처리가 side effects라고 생각하고 개발해 보지는 못했던 것 같습니다. 엄밀히 말하면 이벤트 핸들러는 애초에 렌더링과 무관하게 동작하므로 순수할 필요까지는 없습니다. 역시 현실 문제를 다루려면 어느 정도는 불순한 생각을 해야 하나 봅니다.</p>
<p>만약 이벤트 핸들러로 도저히 어떻게 할 수 없는 애매한 변화가 필요한 순간에는 어떻게 해야 할까요? 이때 사용되는 것이 바로 “useEffect”입니다. 리액트에게 렌더링이 안전하게 끝나면 그때 무언가 변화를 주라고 만들어졌습니다. 그러나 리액트 문서는 우리에게 다음과 같은 일침을 날립니다.</p>
<blockquote>
<p><strong>However, this approach should be your last resort. When possible, try to express your logic with rendering alone. You’ll be surprised how far this can take you!
그러나 이 접근 방식은 최후의 수단이 되어야 합니다. 가능하다면 렌더링만으로 로직을 표현해 보세요. 이것이 당신을 얼마나 멀리 데려갈 수 있는지 놀라게 될 것입니다!</strong></p>
</blockquote>
<p>과연 useEffect가 최후의 수단이라고 생각하고 개발해 본 적이 있나요? 저는 개인적으로 반성합니다. 렌더링만으로 로직 표현이 가능하다면 굳이 useEffect를 사용해서 부수 효과를 발생시킬 필요가 없다는 점은 많은 생각이 들게 합니다.</p>
<h2 id="결론">결론</h2>
<p>지금까지 리액트에서의 순수함수에 대해 문서를 기반으로 다뤄보았습니다. 그렇다면 마지막으로 궁금한 점이 생깁니다. 리액트는 왜 컴포넌트가 순수하길 바랄까요? 문서에선 다음 3가지 이유를 설명합니다. </p>
<ul>
<li>컴포넌트는 서버 등의 환경에서 동작할 수 있기 때문에 동일 입력, 동일 출력 원칙을 지키면 한 컴포넌트로 많은 사용자 요청을 처리할 수 있게 됩니다.</li>
<li>입력이 변하지 않은 순수한 컴포넌트는 렌더링 생략(memo)을 할 수 있기 때문에 안전하게 캐싱 및 퍼포먼스 향상이 가능합니다.</li>
<li>깊은 컴포넌트 트리를 렌더링하는 와중에도 특정 데이터가 변경되면 리액트는 언제든지 기존 렌더링을 중단하고 다시 시작할 수 있습니다. 이는 각 컴포넌트가 순수하기 때문에 가능합니다.</li>
</ul>
<p>리액트는 지금도 많이 사용되며 당분간은 그럴 것이기에 리액트 개발자들이 리액트를 만든 의도는 무엇이었는지 알아보고 이를 따르려 노력하는 것은 나쁘지 않을 것 같습니다.</p>
<h3 id="참고">참고</h3>
<p><a href="https://react.dev/learn/keeping-components-pure">react.dev - keeping components pure</a>
<a href="https://daveceddia.com/why-not-modify-react-state-directly/">Dave Ceddia - Why Not To Modify React State Directly</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[공학대학원 입학 후기]]></title>
            <link>https://velog.io/@seoyong-lee/%EA%B3%B5%ED%95%99%EB%8C%80%ED%95%99%EC%9B%90-%EC%9E%85%ED%95%99-%ED%9B%84%EA%B8%B0</link>
            <guid>https://velog.io/@seoyong-lee/%EA%B3%B5%ED%95%99%EB%8C%80%ED%95%99%EC%9B%90-%EC%9E%85%ED%95%99-%ED%9B%84%EA%B8%B0</guid>
            <pubDate>Sun, 26 Nov 2023 15:34:03 GMT</pubDate>
            <description><![CDATA[<p>늦었지만 이번 학기부터 컴퓨터 관련 전공으로 공학대학원(야간)에 입학하게 되어 관련 후기를 작성합니다. 비전공자 출신 개발자분들이라면 궁금해하실 수 있을 것 같아서 이 후기가 조금이나마 도움이 되었으면 좋겠습니다.</p>
<h2 id="입학-계기">입학 계기</h2>
<p>대학원은 다음과 같이 크게 3가지 목적 때문에 가는 것 같습니다.</p>
<ol>
<li>학부에서 배운 내용을 조금 더 깊이 연구하고 싶은 경우</li>
<li>현재 학부 전공과 다른 일을 하고 있거나 앞으로 다른 분야로의 이직을 결심한 경우</li>
<li>학부 간판(네임밸류)이 아쉬운 경우</li>
</ol>
<p>보통 1의 경우에는 자대 일반대학원에 진학하여 연구 경력을 가지고 취업하거나 박사과정으로 진학합니다. 일반적으로 랩실의 대학원생은 대부분 1에 해당한다고 보면 되겠습니다. 그러나 이미 직장을 다니고 계신 전공자분들이 1의 목적으로 야간대학원에 다시 진학하시는 경우도 종종 있는 것 같습니다.</p>
<p>제 경우는 학부 전공은 디자인이었으나 개발자로 일하고 있어 2에 해당하였습니다. S대를 제외하면 미대 중에서 높은 평가를 받는 대학들이 일반적인 대학 순위에서는 상대적으로 약한 편이라 3의 목적도 약간 있었지만, 주요 목적은 공학 전공자로의 포지셔닝이었습니다.</p>
<p>만약 취업이 목적이라면 빅데이터나 인공지능 관련 직무가 아닌 이상 대학원 진학은 개인적으로 적합하지 않다고 생각합니다. 그러나 저의 경우처럼 이미 관련 분야에서 일하고 있지만 더욱 전문성을 가지고 싶은 경우에는 미래를 위해 충분히 고려해 볼 만한 선택이라고 생각합니다. 저는 꼭 실무적인 내용은 아니더라도 컴퓨터 관련 전공자들이 일반적으로 접하는 수준이 궁금하기도 했고, 인공지능 관련 수업도 몇 가지 들어보고 싶어서 지원을 결심하였습니다.</p>
<p>학부 전공이 공학이 아니었던 분들의 경우에는 대학원 지원 이전에 다음을 다짐하시는 것을 추천드립니다. </p>
<blockquote>
<ol>
<li>나는 이제 공대생이다. 공학이 좋고 공부하는 것이 좋다</li>
<li>앞으로 몇 년 동안 주말 하루 정도는 공부에 투자할 의향이 있다</li>
</ol>
</blockquote>
<h2 id="대학-선택">대학 선택</h2>
<p>먼저 이미 재직 중인 상황에서 대학원을 다니기로 한 이상 특별한 경우가 아니라면 대부분 주간에 수업이 있는 일반대학원으로의 진학은 현실적으로 어려울 것입니다. 코로나 때 한정으로 회사에 다니면서 비대면으로 일반대학원을 졸업하는 경우가 있었다고 합니다만 일반적인 경우는 아니고 교수님의 재량에 달린 것 같습니다.</p>
<p>따라서 야간에 수업을 진행하는 컴퓨터 관련 대학원을 찾아보면 대부분 공학대학원(혹은 정보통신대학원)이라는 특수 대학원에 속해있는 경우가 많습니다. 공학대학원은 직장인의 전문성 향상을 목표로 하는 과정이기 때문에 수업의 목적이 일반대학원과는 조금 다릅니다. 만약 박사과정 진학이 목표라면 논문작성을 하지 않는 일부 특수대학원 과정은 적합하지 않을 수 있습니다.</p>
<p>서울에서 선택할 수 있는 주요 공학(특수)대학원은 다음과 같습니다.</p>
<pre><code>- 건국대학교 정보통신대학원
  - 보안관련 학과만 존재
- 고려대학교 공학대학원 (2.5년)
  - 전기전자컴퓨터공학과로 묶여 있으며 졸업논문 작성
- 고려대학교 컴퓨터정보통신대학원 (2.5년)    
  - 빅데이터, 인공지능 및 소프트웨어&#39;보안&#39;학과 개설
- 국민대학교 소프트웨어융합대학원 (2년)
  - 인공지능 및 소프트웨어학과가 있으며 논문/프로젝트 중 선택?
  - 주말에 수업 진행 (토요일)
- 서강대학교 정보통신대학원 (2.5년)
  - 인공지능과 소프트웨어공학학과 개설
- 연세대학교 공학대학원 (2년)
  - 인공지능과 컴퓨터소프트웨어학과 개설
  - 논문작성이 없는 4학기제
- 한양대학교 공학대학원 (2.5년)
  - 전기·전자·컴퓨터공학과 개설
  - 졸업논문 작성</code></pre><p>저의 경우는 국민대, 연세대, 한양대 공학대학원에 지원했었고 추가로 컴퓨터 관련은 아니지만, 홍익대학교 인터랙션디자인전공에도 지원했습니다(이 외 학교에 대한 정보는 확실하지 않을 수 있습니다).</p>
<p>학과에 따라 논문을 작성하는 과정의 경우 연구에 집중된 커리큘럼을 가지며, 논문 작성을 프로젝트로 대체하는 학과의 경우 조금 더 실무적인 내용을 다루는 편입니다. 지원하기 전에 개설과목 정보를 확인할 수 있다면 꼭 먼저 확인해 보고 지원하시는 것을 추천드립니다.</p>
<p>만약 인공지능 관련 학과를 생각하고 계신다면 여기는 야간이어도 일반대학원 못지않게 입학이 어려운 것으로 알고 있습니다. 저의 경우에는 컴퓨터 관련 학과에만 지원하였기 때문에 인공지능 관련 학과에 대해서 말씀드리기는 어렵겠지만 지원해보신 분들의 후기에 따르면 면접에서 관련 지식을 깊이 있게 물어보는 것으로 보입니다.</p>
<h2 id="지원-과정">지원 과정</h2>
<p>먼저 제가 지원했던 학교들은 모두 1차 서류평가 &gt; 2차 면접 순으로 진행되었습니다. 서류전형의 경우 간단한 이력 및 자기소개, 학업계획서 등의 작성이 필요합니다. 학업계획서 내용은 면접에서 관련 내용에 관한 질문을 받을 수 있어서 미리 공개된 커리큘럼을 기반으로 계획을 잘 세우는 것이 중요합니다.</p>
<p>특수대학원은 연구를 목적으로 하는 곳은 아니지만, 만약 평소에 학문적으로 깊이 연구하고 싶었던 분야가 있었다면 아무래도 학교인 만큼 학업계획서를 통해서 이를 잘 어필하는 것이 중요하다고 생각합니다. 저의 경우에는 서비스·경험디자인기사 자격증을 취득하는 등 UX 및 HCI 분야에 관심이 있었기에 이러한 부분을 개설된 수업과 최대한 연결하여 학업 계획을 작성하려 노력하였습니다.</p>
<p>만약 서류에서 합격한다면 면접은 제 경험상 그렇게 어렵지 않았던 것으로 기억합니다. 면접에서 당락이 결정된다기보다는 이미 합격할 분들을 정한 후 확인을 위해서 보는 느낌이었습니다. 주요 질문은 회사를 다니면서 수업 참여에 어려움은 없을지, 비전공자라 수업을 따라가는 것이 괜찮은지 등을 물어보셨습니다. 전공 관련해서 기초적인 데이터베이스 관련 지식을 물어보는 경우가 있었는데 이직 시에 경험했던 입사 면접에 비하면 어려운 편은 아니었습니다.</p>
<h2 id="합격-이후">합격 이후</h2>
<p>최종 결과가 발표되면 등록을 진행하게 됩니다. 특수대학원은 대체로 비싸고 장학금이 거의 없는 관계로 한국장학재단에서 학자금대출을 받는 경우가 많을 것으로 예상됩니다. 학자금대출은 학부와 마찬가지로 1%대 금리로 10년 동안 상환이 가능합니다(+ 거치기간 설정 가능). 만약 등록하지 못하게 되면 합격이 취소되니 유의가 필요합니다. 등록 이후에는 여러 가지 회비 납부를 요청받을 텐데 대학원의 목적이 인맥이 아니신 분들은 의무적으로 납부하실 필요는 없습니다.</p>
<p>수강 신청의 경우 보통 학부처럼 수업에 대한 선택권이 주어지는 편은 아니고 그해에 개설된 강의에 따라 신청하게 됩니다. 대학에 따라 다르겠지만 최대 1~2개 정도의 타과 수업도 신청할 수 있습니다. 수업 요일은 수강 신청 여부에 따라 결정되며 보통 주 2회(ex. 화, 목) 안에 몰아서 수강하는 경우가 많습니다.</p>
<p>지금까지 한 학기 학교생활을 해본 결과 생각보다 놀랐던 점 중의 하나는 젊은(30대 초중반) 분들이 많았다는 점입니다. 다른 학과 분들을 보니 확실히 컴퓨터 관련 학과의 평균 연령이 더 낮은 것 같습니다. 조금이라도 진학에 관한 생각이 있으시다면 최대한 빨리 지원해보는 것이 좋을 것 같습니다. 합격하신 분들은 대체로 스타트업 보다는 대기업 출신 분들이 많았습니다.</p>
<h2 id="후기">후기</h2>
<p>만약 대학원에서 얻고자 하는 것이 확실하다면 원하는 것을 충족시켜줄 대학 위주로 꼭 지원해 보시기를 추천합니다. 대학은 역시 대학인지라 학원과는 달리 취업, 이직 등을 보장해 주지는 않습니다. 그러나 이직이나 승진, 혹은 창업을 위한 투자를 받을 때마다 우리는 항상 우리가 어떤 사람인지를 보여줘야 합니다. 많은 사람이 학교는 중요하지 않다고 하지만 학위라는 것은 몇 년간의 노력으로써 얻어진다는 사실은 변하지 않습니다. 중요한 순간에 남들과 차별화된 이력을 보여줄 수 있는 수단으로 학위는 나쁘지 않은 선택이라고 생각합니다.</p>
<p>또한 대학원 안에 인공지능 관련 학과가 함께 묶여있다면 타과 수강을 통해 요즘 인기 있는 인공지능 관련 기초 지식도 배울 수 있고, 직장인 입장에선 일반대학원 같은 랩실 출근 없이도 동일한 석사 학위를 취득할 수 있다는 것도 장점입니다. </p>
<p>특수대학원의 단점에 대해서도 솔직하게 말씀드리면 직장인 특성상 수업을 깊이 있게 진행하기 어려워 학부 수업의 연장선 같은 느낌이 있습니다. 또한 논문을 쓰지 않는 과정이라면 논문을 체계적으로 읽고 쓰는 방법을 배울 수 없습니다. 만약 학문적인 깊이를 원하신다면 어쩔 수 없이 일반대학원에 진학하시는 것이 맞다고 생각합니다. 이는 상황에 따라 적절히 선택하시기를 추천합니다.</p>
<p>이 글을 읽으시는 분들에게 모두 좋은 결과가 있기를 바라면서 마무리하겠습니다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[i18n 관련 라이브러리 총정리]]></title>
            <link>https://velog.io/@seoyong-lee/i18n-%EA%B4%80%EB%A0%A8-%EB%9D%BC%EC%9D%B4%EB%B8%8C%EB%9F%AC%EB%A6%AC-%EC%B4%9D%EC%A0%95%EB%A6%AC</link>
            <guid>https://velog.io/@seoyong-lee/i18n-%EA%B4%80%EB%A0%A8-%EB%9D%BC%EC%9D%B4%EB%B8%8C%EB%9F%AC%EB%A6%AC-%EC%B4%9D%EC%A0%95%EB%A6%AC</guid>
            <pubDate>Thu, 23 Nov 2023 05:11:37 GMT</pubDate>
            <description><![CDATA[<p>i18n(국제화) 관련 작업을 위해 비슷한 이름의 여러 라이브러리를 설치해 본 경험이 있다면 각각이 어떤 역할을 하는지 궁금해 질 수 있습니다. 이번 기회에 각각의 라이브러리가 무엇을 하는지 정리해 보았습니다.</p>
<h2 id="i18n의-개념">i18n의 개념</h2>
<p>i18n은 internationalization(i와 n 사이의 18글자)을 줄인 것으로 한글로 번역하면 ‘국제화’입니다. W3C의 정의에 따르면 국제화는 다음을 의미합니다.</p>
<blockquote>
<p>국제화(Internationalization)는 문화, 지역, 언어가 다양한 대상 고객을 위해 쉽게 현지화할 수 있는 제품, 애플리케이션 또는 문서 콘텐츠를 설계하고 개발하는 것입니다. - W3C</p>
</blockquote>
<blockquote>
</blockquote>
<p><em>i18n은 웹이나 JS에 종속된 개념이 아닌 컴퓨터 소프트웨어(혹은 그 이상 분야) 전반에서 범용적으로 사용되는 단어입니다. i18n을 쉽게 적용하기 위해 만들어진 JS 라이브러리의 이름은 i18next입니다!</em></p>
<p>국제화를 위해서는 다음 사항에 대한 지원을 필요로합니다.</p>
<ul>
<li>문자 모음 (일반적으로 유니코드를 통함)</li>
<li>측정 단위 (통화, °C/°F, km/miles 등)</li>
<li>시간 및 날짜 형식</li>
<li>키보드 레이아웃</li>
<li>텍스트 방향</li>
</ul>
<p>i18n(국제화) 적용을 위한 JS 라이브러리는 다음과 같이 여러 종류가 있지만 일반적으로 i18next가 많이 사용됩니다.</p>
<ul>
<li><strong>i18next</strong></li>
<li>FormatJS</li>
<li>Polyglot</li>
<li>...</li>
</ul>
<h2 id="i18next">i18next</h2>
<p><a href="https://www.i18next.com/">i18next - documentation</a>
i18next는 i18n 적용을 위한 JS 라이브러리로 보통 다음을 위해 사용됩니다.</p>
<ul>
<li>국가별 스트링 관리</li>
<li>언어 인지</li>
<li>캐싱</li>
</ul>
<p>이 외에도 다음과 같이 많은 기능을 포함합니다.
<img src="https://velog.velcdn.com/images/seoyong-lee/post/65b8e479-afb3-4abc-a697-25ef606b0b24/image.png" alt=""></p>
<p>사용 방법은 다음과 같습니다.</p>
<pre><code class="language-ts">i18next.init({
  resources: {
    en: {
      translation: {
        &quot;key&quot;: &quot;Welcome to React and react-i18next&quot;
      }
    },
    ko: {
      translation: {
        &quot;key&quot;: &quot;React 와 react-i18next&quot;
      }
    }
  }
}).then(function(t) {
  document.getElementById(&#39;output&#39;).innerHTML = i18next.t(&#39;key&#39;);
});</code></pre>
<p>미리 국가별로 지정한 ‘key’를 사용해 실제 텍스트를 렌더링 하는 부분에서 i18next.t로 감싸는 방식으로 번역을 지원합니다.</p>
<h2 id="react-i18next">react-i18next</h2>
<p><a href="https://react.i18next.com/">react-i18next - documentation</a></p>
<p>react-i18next는 i18next를 React/React-Native에서 사용하기 적합하게 만들어진 라이브러리입니다. 
react-i18next은 다음과 같이 React에 적합한 <code>useTranslation</code> 이라는 커스텀 훅을 제공합니다.</p>
<pre><code class="language-ts">import i18n from &quot;i18next&quot;;
import { useTranslation, initReactI18next } from &quot;react-i18next&quot;;

i18n
  .use(initReactI18next)
  .init({
    resources: {
      en: {
        translation: {
          &quot;Welcome to React&quot;: &quot;Welcome to React and react-i18next&quot;
        }
      },
      ko: {
        translation: {
          &quot;웰컴 투 리액트&quot;: &quot;React 와 react-i18next&quot;
        }
      }
    },
    fallbackLng: &quot;en&quot;,
  });

function App() {
  const { t } = useTranslation();
  return &lt;h2&gt;{t(&#39;Welcome to React&#39;)}&lt;/h2&gt;;
}</code></pre>
<p>커스텀 훅을 이용하니 리액트스러운 i18n 적용이 가능해졌습니다.</p>
<h2 id="next-i18next">next-i18next</h2>
<p><a href="https://github.com/i18next/next-i18next">next-i18next - github</a></p>
<blockquote>
<p>해당 글은 Next.JS 13의 App Router가 아닌 Pages Router를 기준으로 작성되었습니다.
App Router의 i18n 적용은 아래와 다를 수 있습니다.</p>
</blockquote>
<p>next-i18next는 위에서 살펴본 react-i18next를 Next.js 환경에서 사용하기 편리하게 만든 라이브러리입니다. Next.js에선 지금까지와는 다르게 SSR, 즉 서버 측면에서의 고려가 추가로 필요합니다. 국가 정보(locale)을 얻기 위한 방법은 다음과 같이 클라이언트와 서버 측면으로 나누어 볼 수 있습니다.</p>
<ul>
<li>클라이언트에선 보통 i18n을 위한 국가 정보를 브라우저의 Web API인 <code>window.navigator</code> 조회를 통해 가져옵니다.</li>
<li>서버에서 접근하는 경우(SSR)에는 window가 없기 때문에 HTTP Header의 &#39;Accept-Language&#39;를 통해 국가 정보를 조회합니다. </li>
</ul>
<p>Next.js 에선 이렇게 확인된 유저의 국가(locale) 정보를 자동으로 인식하여 getServerSideProps 컨텍스트를 통해 클라이언트로 넘깁니다. </p>
<p>next-i18next 사용을 위해서는 아래코드와 같이 Home에 해당하는 컴포넌트를 appWithTranslation HOC로 감싸줍니다.</p>
<pre><code class="language-ts">import { GetServerSideProps } from &quot;next&quot;;
import { appWithTranslation } from &quot;next-i18next&quot;;
import { serverSideTranslations } from &quot;next-i18next/serverSideTranslations&quot;;

export const getServerSideProps: GetServerSideProps = async ({ locale }) =&gt; {
  return {
    props: {
      ...(await serverSideTranslations(locale ?? &quot;ko&quot;, [&quot;common&quot;], null, [
        &quot;en&quot;,
        &quot;ja&quot;,
        &quot;ko&quot;,
      ])),
    },
  };
};

const Home = (props: any) =&gt; {
  console.log(props);

  return (
    &lt;LayoutWrapper&gt;
      &lt;HeadMeta /&gt;
      &lt;Header /&gt;
      &lt;Section /&gt;
      &lt;Footer /&gt;
    &lt;/LayoutWrapper&gt;
  );
};

export default appWithTranslation(Home);</code></pre>
<p>메인 페이지 <code>getServerSideProps</code> 함수에서 locale을 받아 <code>serverSideTranslations</code> 함수에 넘겨준 후 클라이언트에서 받은 props를 console로 확인하면 다음과 같이 public/locales에 두었던 스트링 파일과 locale 관련 정보가 내려오는 것을 확인할 수 있습니다.
<img src="https://velog.velcdn.com/images/seoyong-lee/post/a6034863-f239-4fde-a708-ab889830a2c2/image.png" alt=""></p>
<p>참고로 Next.JS는 다음과 같이 두 가지 방식의 Locale Strategies를 지원합니다.</p>
<ul>
<li><p>Sub-path Routing</p>
<pre><code class="language-ts">module.exports = {
  i18n: {
    locales: [&#39;en-US&#39;, &#39;kr&#39;],
    defaultLocale: &#39;en-US&#39;,
  },
}</code></pre>
<ul>
<li>URL 뒤에 /ko 와 같이 Sub-path 를 통해 국가를 구분하는 방법입니다.</li>
<li>도메인 하나로 국가를 구분할 수 있습니다.</li>
</ul>
</li>
<li><p>Domain Routing</p>
<pre><code class="language-ts">module.exports = {
  i18n: {
    locales: [&#39;en-US&#39;, &#39;kr&#39;],
    defaultLocale: &#39;en-US&#39;,
    domains: [
      {
        domain: &#39;example.com&#39;,
        defaultLocale: &#39;en-US&#39;,
      },
      {
        domain: &#39;example.kr&#39;,
        defaultLocale: &#39;kr&#39;,
      },
    ],
  },
}</code></pre>
<ul>
<li>.co 등 최상의 도메인을 서로 다르게 하여 구분하는 방법입니다. </li>
<li>국가별 도메인 구입이 필요합니다.</li>
</ul>
</li>
</ul>
<br/>

<h4 id="참고">참고</h4>
<p><a href="https://developer.mozilla.org/ko/docs/Glossary/Internationalization">국제화 (internationalization, I18N) - MDN</a>
<a href="https://en.wikipedia.org/wiki/Internationalization_and_localization">Internationalization and localization - Wikipedia</a>
<a href="https://www.i18next.com/">i18next - documentation</a>
<a href="https://react.i18next.com/">react-i18next - documentation</a>
<a href="https://github.com/i18next/next-i18next">next-i18next - github</a>
<a href="https://velog.io/@jeonbyeongmin/Next.js%EB%A1%9C-%EB%8B%A4%EA%B5%AD%EC%96%B4i18n-%EC%A0%9C%EA%B3%B5%ED%95%98%EA%B8%B0">Next.js로 다국어(i18n) 제공하기 - jeonbyeongmin님의 블로그</a> </p>
]]></description>
        </item>
        <item>
            <title><![CDATA[GPT의 T는 무슨 T?]]></title>
            <link>https://velog.io/@seoyong-lee/GPT%EC%9D%98-T%EB%8A%94-%EB%AC%B4%EC%8A%A8-T</link>
            <guid>https://velog.io/@seoyong-lee/GPT%EC%9D%98-T%EB%8A%94-%EB%AC%B4%EC%8A%A8-T</guid>
            <pubDate>Mon, 09 Oct 2023 14:08:54 GMT</pubDate>
            <description><![CDATA[<p>‘트랜스포머를 활용한 자연어 처리’를 참고하였습니다.</p>
<h2 id="gpt와-트랜스포머">GPT와 트랜스포머</h2>
<p>현재 많은 사람에게 유명해진 Chat-GPT는 GPT 언어모델을 사용하고 있다. 그렇다면 GPT는 과연 무엇의 약자일까? 바로 <a href="https://en.wikipedia.org/wiki/Generative_pre-trained_transformer">Generative Pretrained Transformer</a>로, 말 그대로 번역하면 생성하는 사전학습된 트랜스포머라는 뜻이다. 그렇다면 트랜스포머는 무엇일까? 빠르고 간단하게 알아보자.</p>
<h2 id="트랜스포머">트랜스포머</h2>
<p>2017 구글 연구원들은 시퀀스 모델링(sequence modeling)을 위한 새로운 신경망 아키텍처를 제안하였다. 이 아키텍처가 바로 트랜스포머(Transformer)로, ‘Attention Is All You Need’ 라는 유명한 이름의 논문을 통해 소개되었다. </p>
<p>트랜스포머는 기존에 사용되던 RNN에 비해 기계 번역 작업의 품질과 훈련 비용 면에서 압도적인 성능을 보여주었다. 여기에 더해 효율적 전이학습 방법인 ULMFiT가 적용되기 시작하면서 매우 적은 양의 레이블 된 데이터로도 최고 수준의 텍스트 분류 모델을 만들어낼 수 있게 되었다. </p>
<p>이렇게 해서 탄생한 것이 바로 GPT(Generative Pretrained Transformer)와 BERT(Bidirectional Encoder Representations from Transformers)이다. 이러한 트랜스포머 기반 모델들의 특징은 특정 작업에 특화된 훈련이 불필요하다는 것이다.</p>
<h2 id="rnn과-인코더-디코더-프레임워크">RNN과 인코더-디코더 프레임워크</h2>
<p>트랜스포머 이전 NLP에서 최고 성능을 달성했던 방법은 순환 신경망 구조였다. 이러한 구조는 피드백 루프를 통해 정보를 한 스텝에서 다음 스텝으로 전파하도록 설계되었고, 출력된 정보를 다시 입력에 사용하는 것이 특징이었다. 이러한 순환 방식은 텍스트와 같은 순차 데이터 모델링에 이상적이었다.</p>
<p>RNN은 단어 시퀀스를 한 언어에서 다른 언어로 매핑하는 기계 번역의 발전에 중요한 역할을 해왔다. 인코더-디코더 또는 시퀀스-투-시퀀스(seq2seq) 구조로 처리되어 입력과 출력이 임의의 길이를 가진 시퀀스일 때 잘 작동하는 것이 특징으로, RNN의 뛰어난 성능은 <a href="http://karpathy.github.io/2015/05/21/rnn-effectiveness/">안드레이 카패시(Andrej Karpathy)의 블로그</a>를 통해 확인할 수 있다.</p>
<h2 id="어텐션">어텐션</h2>
<p>하지만 RNN은 정보 병목(information bottleneck)이라는 치명적인 문제점을 가지고 있었다. 디코더는 인코더의 마지막 은닉 상태만을 참조해 출력을 만들기 때문에 시작 부분의 정보 손실이 발생하는 것이었다. 이를 해결하기 위해 디코더가 인코더의 모든 은닉 상태에 접근하는 메커니즘인 어텐션이 고안되었다.</p>
<p>모든 상태를 동시에 사용하려면 어떤 상태를 먼저 사용할지 우선순위를 정하는 메커니즘이 필요하다. 이를 해결하기 위해 어텐션에선 디코더가 모든 디코딩 타임스텝(timestep)마다 인코더의 각 상태에 다른 가중치(어텐션)를 할당한다. 이렇게 하면 가장 관련이 있는 입력 토큰에 초점을 맞출 수 있어서 복잡한 단어 정렬 문제 등의 학습이 가능해진다.</p>
<h2 id="셀프-어텐션">셀프 어텐션</h2>
<p>어텐션을 통해 번역 성능이 좋아졌지만, 인코더-디코더 순환 모델의 단점은 여전히 존재했다. 위 방식들은 태생적으로 계산이 순차적으로 수행되기 때문에 입력 시퀀스 전체에 걸쳐 병렬화가 불가능했다. 이러한 문제 해결을 위해 순환을 모두 없앤 셀프 어텐션(self-attention)이 고안되었다. </p>
<p>셀프 어텐션은 기본적으로 신경망의 같은 층에 있는 모든 상태에 대해 어텐션을 작동시킨다. 어텐션의 출력은 피드포워드 신경망(Feed-Foward Neural Network, FF NN)에 주입되며 순환 모델에 비해 훨씬 빠르게 훈련이 가능해 NLP의 혁신을 일으키게 된다.</p>
<p>트랜스포머 원논문에선 처음부터 다양한 언어의 문장 쌍으로 구성된 대규모 말뭉치에서 번역 모델을 훈련하였다. 그러나 실무에서 모델 훈련에 사용할 레이블링 된 대규모 텍스트 데이터의 확보가 쉽지 않았다. 이는 마지막으로 전이 학습을 통해 해결하게 된다.</p>
<h2 id="전이-학습">전이 학습</h2>
<p>컴퓨터 비전(Computer Vision) 분야에선 전통적으로 전이 학습을 통해 한 작업에서 훈련한 지식을 새로운 작업에 적용하는 경우가 많았다. 전통적인 지도 학습(supervised learning)과 비교하면 전이 학습은 적은 양의 레이블 데이터로 훨씬 효과적으로 훈련하는 높은 품질의 모델을 만들 수 있다는 장점을 가진다.</p>
<p>전이 학습은 먼저 대규모 데이터셋을 이용한 모델 훈련인 사전 훈련(pretraining)을 진행한 후, 미세 튜닝(fine-tuning)을 진행한다. 컴퓨터 비전에선 전이 학습이 표준이 되었지만, NLP에선 전이 학습과 유사한 사전 훈련 과정이 무엇인지 수년간 특정하지 못하였다.</p>
<p>그러나 ULMFiT 프레임워크를 통해 이러한 부분을 해결하게 된다. 2018년 셀프 어텐션과 전이 학습을 결합한 두 개의 트랜스포머 모델이 릴리즈 된다.</p>
<ul>
<li>GPT<ul>
<li>트랜스포머 아키텍처의 디코더 부분만 사용한 것이 특징으로 ULMFiT 같은 언어 모델링 방법 사용하였다. BookCorpus 데이터셋을 통해 다양한 장르를 망라한 7,000권의 미출판 도서를 가지고 사전 훈련을 진행하였다.</li>
</ul>
</li>
<li>BERT<ul>
<li>트랜스포머 아키텍처의 인코더 부분만 사용하였고 마스크드 언어 모델링(masked language modeling) 기법이 사용되었다. 이는 텍스트에서 랜덤하게 마스킹된 단어를 예측하는 방법으로 BookCorpus와 영문 위키피디아 데이터를 기반으로 사전 훈련을 진행하였다.</li>
</ul>
</li>
</ul>
<p>참고
<a href="https://product.kyobobook.co.kr/detail/S000200330771">트랜스포머를 활용한 자연어 처리</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[자바스크립트 심화편 - 2. 클로저]]></title>
            <link>https://velog.io/@seoyong-lee/%EC%9E%90%EB%B0%94%EC%8A%A4%ED%81%AC%EB%A6%BD%ED%8A%B8-%EC%8B%AC%ED%99%94%ED%8E%B8-2.-%ED%81%B4%EB%A1%9C%EC%A0%80</link>
            <guid>https://velog.io/@seoyong-lee/%EC%9E%90%EB%B0%94%EC%8A%A4%ED%81%AC%EB%A6%BD%ED%8A%B8-%EC%8B%AC%ED%99%94%ED%8E%B8-2.-%ED%81%B4%EB%A1%9C%EC%A0%80</guid>
            <pubDate>Fri, 06 Oct 2023 02:06:00 GMT</pubDate>
            <description><![CDATA[<ul>
<li>자바스크립트를 어느 정도 공부하다 보면 클로저라는 장벽을 만나게 될 것이다.</li>
</ul>
<blockquote>
<p>자바스크립트를 사용해봤지만 단 한 번도 클로저 개념을 완전히 이해한 적이 없는 이들에게는 클로저가 열반에 드는 것처럼 고된 노력을 들여야 이해할 수 있는 것일지도 모르겠다. <br/>
…깨달음의 순간이 이럴 것이다. “아, 클로저는 내 코드 전반에서 이미 일어나고 있었구나! 이제 난 클로저를 볼 수 있어.” <br/>
카일 심슨 , 『You Don’t Know JS - 타입과 문법, 스코프와 클로저』, 한빛미디어(2017), p240.</p>
</blockquote>
<ul>
<li>클로저를 모르고 넘어가기엔 너무나 많은 사람들이 중요성을 강조해왔다.</li>
</ul>
<blockquote>
<p>클로저는 프로그래밍 언어의 긴 역사 속에서도 중요한 발견 중 하나입니다. 스킴(Scheme) 언어에서 처음 발견되었죠. 그리고 자바스크립트의 주류에까지 이르렀습니다. 클로저 덕분에 자바스크립트가 더 흥미로운 언어가 되었습니다.<br/>
더글러스 크락포드 , 『자바스크립트는 왜 그 모양일까?』, 인사이트(2020), p140-141.</p>
</blockquote>
<ul>
<li>앞서 스코프에 대해 이해했다면 이제 클로저에 대해 자세히 살펴볼 차례이다.</li>
</ul>
<br/>

<h1 id="클로저의-정의">클로저의 정의</h1>
<ul>
<li>클로저란 정확히 무엇일까?</li>
</ul>
<blockquote>
<p><strong>클로저</strong>는 주변 상태(렉시컬 환경)에 대한 참조와 함께 묶인(포함된) 함수의 조합입니다.<br/>
<a href="https://developer.mozilla.org/ko/docs/Web/JavaScript/Closures">MDN, 클로저</a></p>
</blockquote>
<ul>
<li>MDN의 클로저에 대한 정의는 신기하게도 한글이지만 이해가 되지 않는다.</li>
</ul>
<blockquote>
<p>클로저는 함수가 속한 렉시컬 스코프를 기억하여 함수가 렉시컬 스코프 밖에서 실행될 때에도 이 스코프에 접근할 수 있게 하는 기능을 뜻한다.<br/>
카일 심슨 , 『You Don’t Know JS - 타입과 문법, 스코프와 클로저』, 한빛미디어(2017), p240.</p>
</blockquote>
<ul>
<li>렉시컬 스코프를 기억했다 접근한다? 이는 코드를 통해 확인하면 더 쉽게 이해할 수 있다.</li>
</ul>
<br/>

<h1 id="코드로-살펴보기">코드로 살펴보기</h1>
<pre><code class="language-jsx">const increase = function() {
    let num = 0; // 상태 변수

    return ++num; // 1 증가
}

console.log(num); // num is not defined
console.log(increase()); // 1
console.log(increase()); // 1
console.log(increase()); // 1</code></pre>
<ul>
<li>위 코드를 스코프 관점에서 살펴보면 let은 블록 레벨 스코프를 가지므로 num이라는 지역 변수는 increase 함수 밖에서 참조할 수 없다.</li>
<li>지역 변수 num의 상태를 변경할 수 있는 유일한 방법은 increase 함수를 호출하는 것이다. 그러나 의도한 대로 숫자가 증가하지 않는다. 그 이유는 increase 함수 호출 시마다 num이 다시 선언되기 때문이다. 다시 말해, 상태가 변경되기 이전 상태를 유지하지 못한다.</li>
</ul>
<pre><code class="language-jsx">const increase = (function() {
    let num = 0; // 상태 변수
    // 클로저
    return function() {
        return ++num; // 1 증가
    };
}());

console.log(increase()); // 1
console.log(increase()); // 2
console.log(increase()); // 3</code></pre>
<ul>
<li><p>위 코드는 num의 이전 상태를 기억하고 숫자가 1씩 증가한다. 단계별로 살펴보면 다음과 같다.</p>
<ol>
<li>위 코드가 실행되면 즉시 실행 함수가 호출된다.</li>
<li>호출 즉시 실행 함수가 반환한 함수(클로저)가 increase 변수에 할당된다.</li>
<li>이때 반환된 클로저는 자신이 정의된 위치에 의해 결정된 상위 스코프인 즉시 실행 함수의 렉시컬 환경을 기억하고 있다. 따라서 클로저는 num을 언제 어디서 호출하든지 참조할 수 있다.<ul>
<li>정확하게 렉시컬 환경은 클로저 함수 객체의 내부 슬롯 [[Environment]]에서 참조한다.</li>
</ul>
</li>
</ol>
</li>
<li><p>신기하게도 다음과 같이 함수가 중첩되어 있더라도 모든 스코프에 접근이 가능하다.</p>
<pre><code class="language-jsx">  // 전역 범위 (global scope)
  const e = 10;
  function sum(a) {
    return function (b) {
      return function (c) {
        // 외부 함수 범위 (outer functions scope)
        return function (d) {
          // 지역 범위 (local scope)
          return a + b + c + d + e;
        };
      };
    };
  }

  console.log(sum(1)(2)(3)(4)); // 20</code></pre>
</li>
<li><p>그렇다면 왜 이런 클로저를 사용해야 할까?</p>
</li>
</ul>
<blockquote>
<p>변수 값은 누군가에 의해 언제든지 변경될 수 있어 오류 발생의 근본적 원인이 될 수 있다. 외부 상태 변경이나 가변(mutable) 데이터를 피하고 불변성(immutability)을 지향하는 함수형 프로그래밍에서 부수 효과를 최대한 억제하여 오류를 피하고 프로그램의 안정성을 높이기 위해 클로저는 적극적으로 사용된다.<br/>
이웅모 , 『모던 자바스크립트 Deep Dive』, 위키북스(2020), p405.</p>
</blockquote>
<br/>

<h1 id="클로저의-활용">클로저의 활용</h1>
<ul>
<li><p>클로저는 상태를 안전하게 은닉하고 특정 함수에게만 상태 변경을 허용하기 위해 사용한다.</p>
<ul>
<li>따라서 외부에 변수를 노출하고 싶지 않으면서 상태 변경의 통제가 필요한 상황에 적용할 수 있다.</li>
<li>ES2019에서 #가 추가되기 전에는 <a href="https://developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Classes/Private_class_fields#private_methods">private class</a> 선언을 위해 많이 사용되었다.</li>
</ul>
</li>
<li><p>다음은 <a href="https://developer.mozilla.org/ko/docs/Web/JavaScript/Closures#%EC%8B%A4%EC%9A%A9%EC%A0%81%EC%9D%B8_%ED%81%B4%EB%A1%9C%EC%A0%80">MDN의 예시</a>로, 글자 크기를 바꾸는 DOM 관련 예시이다. size가 그대로 글자 크기 px이 되지만 이를 특정 함수만을 사용해서 변경하도록 캡슐화 되어있다.</p>
<pre><code class="language-jsx">  function makeSizer(size) {
    return function () {
      document.body.style.fontSize = `${size}px`;
    };
  }

  const size12 = makeSizer(12);
  const size14 = makeSizer(14);
  const size16 = makeSizer(16);

  document.getElementById(&quot;size-12&quot;).onclick = size12;
  document.getElementById(&quot;size-14&quot;).onclick = size14;
  document.getElementById(&quot;size-16&quot;).onclick = size16;</code></pre>
</li>
<li><p>다음과 같이 싱글톤 패턴을 응용해 간단한 로거를 구현해 볼 수 있다.</p>
<pre><code class="language-jsx">  const LoggingService = (function () {
    const infoMessage = &#39;Info: &#39;;
    const warningMessage = &#39;Warning: &#39;;
    const errorMessage = &#39;Error: &#39;;

    return {
      info: function (str) {
        console.log(`${infoMessage}${str}`);
      },
      warning: function (str) {
        console.log(`${warningMessage}${str}`);
      },
      error: function (str) {
        console.log(`${errorMessage}${str}`);
      },
    };
  })();

  // someOtherFile.js

  LoggingService.info(&#39;one&#39;); // Info: one
  LoggingService.warning(&#39;two&#39;); // Warning: two
  LoggingService.error(&#39;three&#39;); // Error: three</code></pre>
</li>
<li><p>고차함수에도 사용할 수 있다. 예시는 <a href="https://www.ilyameerovich.com/3-use-cases-for-closures/">여기</a>를 참고하였다.</p>
<pre><code class="language-jsx">  const floatingPoint = 3.456789;

  const someInt = Math.round(floatingPoint); // 3
  const withDecimals = Number(floatingPoint.toFixed(2)); // 3.46</code></pre>
<ul>
<li>위 함수를 유틸 함수로 바꿔서 가독성을 높인다면 다음과 같이 클로저를 사용할 수 있다.<pre><code class="language-jsx">function rounder(places) {
return function (num) {
return Number(num.toFixed(places));
};
}
</code></pre>
</li>
</ul>
<p>const rounder2 = rounder(2);
const rounder3 = rounder(3);</p>
<p>rounder2(floatingPoint); // 3.46
rounder3(floatingPoint); // 3.457</p>
<pre><code></code></pre></li>
</ul>
<br/>

<p><strong>References</strong></p>
<p>더글러스 크락포드 , 『자바스크립트는 왜 그 모양일까?』, 인사이트(2020)
카일 심슨 , 『You Don’t Know JS - 타입과 문법, 스코프와 클로저』, 한빛미디어(2017) 
이웅모 , 『모던 자바스크립트 Deep Dive』, 위키북스(2020)
MDN
<a href="https://www.ilyameerovich.com/3-use-cases-for-closures/">Ilya Meerovich, 3 Use Cases for Closures (in JavaScript)</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[자바스크립트 심화편 - 1. 스코프]]></title>
            <link>https://velog.io/@seoyong-lee/%EC%9E%90%EB%B0%94%EC%8A%A4%ED%81%AC%EB%A6%BD%ED%8A%B8-%EC%8B%AC%ED%99%94%ED%8E%B8-1.-%EC%8A%A4%EC%BD%94%ED%94%84</link>
            <guid>https://velog.io/@seoyong-lee/%EC%9E%90%EB%B0%94%EC%8A%A4%ED%81%AC%EB%A6%BD%ED%8A%B8-%EC%8B%AC%ED%99%94%ED%8E%B8-1.-%EC%8A%A4%EC%BD%94%ED%94%84</guid>
            <pubDate>Fri, 06 Oct 2023 02:02:49 GMT</pubDate>
            <description><![CDATA[<h1 id="스코프">스코프</h1>
<ul>
<li>스코프는 참조 대상 식별자를 찾아내기 위한 규칙이다.</li>
<li>스코프는 모든 프로그래밍 언어의 기본 개념으로 확실한 이해가 중요하다.</li>
</ul>
<br/>

<h1 id="스코프의-존재-이유">스코프의 존재 이유</h1>
<ul>
<li>스코프는 변수와 밀접한 관련이 있다.</li>
</ul>
<blockquote>
<p>변수를 프로그램에 추가하면 다음과 같은 재미있는 질문이 생긴다.<br/></p>
</blockquote>
<ul>
<li><p>변수는 어디에 살아있는가? (어디에 저장되는가?)</p>
</li>
<li><p>필요할 때 프로그램은 어떻게 변수를 찾는가?<br/>
이 질문을 통해 알 수 있는 것은 특정 장소에 변수를 저장하고 나중에 그 변수를 찾는 데는 잘 정의된 규칙이 필요하다는 점이다. 바로 이런 규칙을 ‘스코프(Scope)’라 한다.<br/>
카일 심슨 , 『You Don’t Know JS: 타입과 문법, 스코프와 클로저』, 한빛미디어(2017), p193.</p>
</li>
<li><p>만약 스코프가 없었다면 다음과 같이 개발자의 워라밸은 하락했을 것이다.</p>
</li>
</ul>
<blockquote>
<p>스코프가 없다면 같은 식별자 이름은 충돌을 일으키므로 프로그램 전체에서 하나밖에 사용할 수 없다. 디렉터리가 없는 컴퓨터를 생각해보자. 디렉터리가 없다면 같은 이름을 갖는 파일을 하나밖에 만들 수 없다.<br/>
이웅모 , 『모던 자바스크립트 Deep Dive』, 위키북스(2020), 스코프</p>
</blockquote>
<br/>

<h1 id="스코프의-구분">스코프의 구분</h1>
<ul>
<li>자바스크립트의 스코프는 다음과 같이 2가지로 나눌 수 있다.<ul>
<li>전역 스코프(Global Scope)</li>
<li>지역 스코프(Local Scope)</li>
</ul>
</li>
</ul>
<h3 id="전역-스코프">전역 스코프</h3>
<ul>
<li>전역에서 선언된 변수는 전역 스코프를 갖는 전역 변수이다.</li>
<li>전역 변수는 코드 어디서든 참조가 가능하다.</li>
</ul>
<h3 id="지역-스코프">지역 스코프</h3>
<ul>
<li>지역에서 선언된 변수는 지역 스코프를 갖는 지역 변수이다.</li>
<li>지역 변수는 그 지역과 그 지역의 하부 지역에서만 참조가 가능하다.</li>
</ul>
<br/>

<h1 id="스코프의-작동-방식">스코프의 작동 방식</h1>
<ul>
<li>프로그래밍 언어의 스코프의 작동 방식은 다음과 같이 2가지로 나뉜다.<ul>
<li>렉시컬 스코프(Lexical Scope)</li>
<li>동적 스코프(Dynamic Scope)</li>
</ul>
</li>
</ul>
<h3 id="렉시컬-스코프">렉시컬 스코프</h3>
<ul>
<li>대부분의 프로그래밍 언어가 사용하는 일반적인 방식이다.</li>
<li>렉싱 타임(Lexing Time)에 정의되는 스코프로, 함수를 어디서 선언하였는지에 따라 상위 스코프가 결정된다.<ul>
<li>렉싱 타임에 대해 조금 더 자세히 알아보면 자바스크립트는 언어의 처리 과정인 컴파일레이션(Compilation)을 거치며, 이는 렉싱(Lexing), 파싱(Parsing), 코드 생성(Code-Generation)의 3단계로 나뉜다.</li>
<li>이 중 렉싱은 토크나이징(Tokenizing)이라고도 하며, 문자열을 나누어 의미 있는 조각으로 만드는 과정을 말한다.</li>
<li>정리하면 렉싱 타임에 정의된다는 뜻은 바로 컴파일레이션의 첫 단계인 렉싱 시점에 스코프가 확정된다는 뜻이다.</li>
</ul>
</li>
</ul>
<h3 id="동적-스코프">동적 스코프</h3>
<ul>
<li>Bash Scripting이나 Perl의 일부 모드와 같은 소수의 언어에서 사용하는 방식이다.</li>
<li>함수를 어디서 호출하였는지에 따라 상위 스코프가 결정된다.</li>
</ul>
<h3 id="결론">결론</h3>
<ul>
<li>자바스크립트는 렉시컬 스코프를 따른다.</li>
<li>따라서 함수의 호출 위치가 아닌 선언 위치에 따라 상위 스코프가 결정된다.</li>
</ul>
<h3 id="참고-렉시컬-스코프를-속이는-방법">(참고) 렉시컬 스코프를 속이는 방법</h3>
<ul>
<li><p>다음 두 방법을 통해 렉시컬 스코프를 속일 수 있다. 그러나 이러한 방법은 엔진의 최적화 작업을 무효화 하여 결과적으로 성능을 하락시키기 때문에 사용이 금지된 사악한 방법들이다.</p>
<ul>
<li><p>eval()은 하나 이상의 변수 또는 함수 선언문을 포함하는 코드를 만나면 그 코드를 실행하면서 eval()이 호출된 위치에 있는 렉시컬 스코프를 런타임에 수정한다.</p>
</li>
<li><p><a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/with">with</a>는 객체 참조를 하나의 스코프로, 속성을 확인자로 간주하여 런타임에 완전히 새로운 렉시컬 스코프를 생성한다. 그러나 with의 사용은 현재 완전히 금지되었다.</p>
<br/>

</li>
</ul>
</li>
</ul>
<h1 id="함수--블록-스코프">함수 / 블록 스코프</h1>
<blockquote>
<p>대부분의 C-family language는 <strong>블록 레벨 스코프(block-level scope)</strong>를 따른다. 블록 레벨 스코프란 코드 블록({…}) 내에서 유효한 스코프를 의미한다. 여기서 “유효하다”라는 것은 “참조(접근)할 수 있다”라는 뜻이다.<br/>
…하지만 자바스크립트는 <strong>함수 레벨 스코프(function-level scope)</strong>를 따른다. 함수 레벨 스코프란 함수 코드 블록 내에서 선언된 변수는 함수 코드 블록 내에서만 유효하고 함수 외부에서는 유효하지 않다(참조할 수 없다)는 것이다.<br/>
이웅모 , 『모던 자바스크립트 Deep Dive』, 위키북스(2020)</p>
</blockquote>
<ul>
<li>자바스크립트는 함수 레벨 스코프를 따르기 때문에 함수 내에서 선언된 매개변수와 변수는 함수 외부에서는 유효하지 않다.</li>
<li>그러나 자바스크립트에서도 블록 레벨 스코프를 구현할 수 있는 방법이 있다. 바로 ECMAScript 6에서 도입된 let, const keyword를 사용하는 방법이다.</li>
</ul>
<h3 id="전역변수-문제">전역변수 문제</h3>
<ul>
<li>그렇다면 함수 밖에서 선언된 변수는 어떨까? 자바스크립트는 블록 레벨 스코프를 사용하지 않으므로 함수 밖에서 선언된 변수는 모두 전역 스코프를 갖게된다.<ul>
<li>바로 이러한 문제 때문에 자바스크립트는 전역 변수의 남발을 주의해야 한다. 전역 변수의 이름이 중복되면 의도치 않은 재할당이 발생해 코드 예측을 어렵게 만드므로 사용을 자제해야 한다.</li>
</ul>
</li>
</ul>
<h3 id="전역변수를-줄이는-방법">전역변수를 줄이는 방법</h3>
<ul>
<li><p>이러한 전역변수의 사용을 최소화 할 수 있는 방법은 다음과 같다.</p>
<ul>
<li><p>전역변수 객체를 만들어서 사용</p>
<ul>
<li>더글라스 크락포드가 제안한 방법이다.</li>
</ul>
<pre><code class="language-jsx">const GLOBAL = {};

GLOBAL.me = {
  name: &#39;Lee&#39;
}

console.log(GLOBAL.me.name);</code></pre>
</li>
<li><p>즉시실행함수(IIFE)를 사용</p>
<ul>
<li>전역변수를 만들지 않아 라이브러리 등에서 자주 사용되는 방법이다.</li>
</ul>
<pre><code class="language-jsx">(function () {
  const GLOBAL = {};

  GLOBAL.me = {
      name: &#39;Lee&#39;
  }

  console.log(GLOBAL.me.name);
}());

console.log(GLOBAL.me.name);</code></pre>
</li>
</ul>
</li>
</ul>
<br/>

<p><strong>References</strong></p>
<p>카일 심슨 , 『You Don’t Know JS: 타입과 문법, 스코프와 클로저』, 한빛미디어(2017) 
이웅모 , 『모던 자바스크립트 Deep Dive』, 위키북스(2020)
MDN</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[자바스크립트 교과서 - 10. 함수]]></title>
            <link>https://velog.io/@seoyong-lee/%EC%9E%90%EB%B0%94%EC%8A%A4%ED%81%AC%EB%A6%BD%ED%8A%B8-%EA%B5%90%EA%B3%BC%EC%84%9C-10.-%ED%95%A8%EC%88%98</link>
            <guid>https://velog.io/@seoyong-lee/%EC%9E%90%EB%B0%94%EC%8A%A4%ED%81%AC%EB%A6%BD%ED%8A%B8-%EA%B5%90%EA%B3%BC%EC%84%9C-10.-%ED%95%A8%EC%88%98</guid>
            <pubDate>Thu, 05 Oct 2023 00:28:25 GMT</pubDate>
            <description><![CDATA[<h2 id="함수의-기원">함수의 기원</h2>
<blockquote>
<p>최초의 프로그램은 루틴(routine)이라고 불렸습니다. 루틴은 주문서의 나열이었습니다. …단일 목록 형태로 루틴을 관리하는 것은 굉장히 어려웠습니다. 동일한 명령어 목록이 여러 루틴에서 발견되기도 하고, 같은 루틴에서 같은 명령어 목록이 여러번 나오기도 했으니까요. 그래서 서브루틴(subroutine)이 만들어졌습니다. 그리고 유용한 여러 서브루틴을 묶어서 라이브러리라고 부르기 시작했습니다. <br/>
…수학적인 함수와 서브루틴이 연관성이 있다는 사실이 알려지면서, 포트란 II에는 SUBROUTINE 선언과 서브루틴을 쓰기 위한 CALL 문, 그리고 표현식에 값을 주입할 수 있도록 값을 반환할 수 있는 FUNCTION 선언문이 도입되었습니다.<br/>
더글러스 크락포드 , 『자바스크립트는 왜 그 모양일까?』, 인사이트(2020), p137.</p>
</blockquote>
<ul>
<li>최초의 함수는 서브루틴에서 기원한다. 서브루틴이 반복되는 것들을 묶어보려는 시도에서 나왔던 것 처럼 함수도 한 번 정의하면 반복해서 호출할 수 있다.</li>
<li>함수는 다음과 같이 코드의 재사용이라는 측면에서 매우 유용하다.</li>
</ul>
<blockquote>
<p>함수를 사용하지 않고 같은 코드를 중복해서 여러 번 작성하면 그 코드를 수정해야 할 때 중복된 횟수만큼 코드를 수정해야 한다. 따라서 중복된 횟수에 비례해서 코드 수정에 걸리는 시간이 증가한다. …코드의 중복을 억제하고 재사용성을 높이는 함수는 <strong>유지보수의 편의성</strong>을 높이고 실수를 줄여 <strong>코드의 신뢰성</strong>을 높이는 효과가 있다.<br/>
이웅모 , 『모던 자바스크립트 Deep Dive』, 위키북스(2020), p156.</p>
</blockquote>
<br/>

<h2 id="자바스크립트의-함수">자바스크립트의 함수</h2>
<ul>
<li>자바스크립트의 함수는 입력을 전달받는 매개변수(parameter), 인수(argument), 반환값(return value)으로 구성된다.</li>
</ul>
<pre><code class="language-jsx">function add(x, y) { // 매개변수 parameter x, y
    return x + y; // 반환값 return value
}
add(2, 5); // 인수 argument 2, 5</code></pre>
<blockquote>
<p>자바스크립트 함수는 매개변수화(parameterized)됩니다. 함수 정의에는 매개변수(parameter)라고 불리는 식별자 리스트가 있는데, 이들은 함수 바디에서 로컬 변수처럼 동작합니다. <br/>
데이비드 플래너건 , 『자바스크립트 완벽 가이드』, 인사이트(2022), p211.</p>
</blockquote>
<ul>
<li>매개변수와 인수는 서로 다른 개념이다. 함수 내부에서는 로컬 변수처럼 동작하는 매개변수를 바라보지만 실제 함수의 호출 시에는 인수라는 값을 넘긴다.</li>
</ul>
<br/>

<h2 id="함수의-정의생성">함수의 정의(생성)</h2>
<ul>
<li><p>자바스크립트는 다음 4가지 방법으로 함수를 정의하며 각 방식마다 조금씩 차이가 있다.</p>
<ul>
<li><p>함수 선언문</p>
<ul>
<li>함수 이름을 생략할 수 없으며 표현식이 아닌 문으로 취급된다.</li>
<li>호이스팅(hoisting)을 통해 함수 선언문 이전에도 호출이 가능하다. 즉, 자바스크립트 엔진은 런타임 이전에 미리 함수 이름과 동일한 식별자를 암묵적으로 생성하고 생성된 함수 객체를 할당한다.</li>
<li>선언 이전에 호출이 가능한 부분은 조금 이상할 수 있다. 실제로 더글라스 크락포드의 경우에는 함수 표현식만 사용할 것을 권장한다.</li>
</ul>
<pre><code class="language-jsx">function add(x, y) {
  return x + y;
}</code></pre>
</li>
<li><p>함수 표현식</p>
<ul>
<li>자바스크립트의 함수는 일급 객체이므로 함수를 변수에 할당할 수 있다.</li>
</ul>
<pre><code class="language-jsx">const add = function(x, y) {
  return x + y;
};</code></pre>
</li>
<li><p>Function 생성자 함수</p>
<ul>
<li>빌트인 함수인 Function 생성자를 이용할 수 있으나 클로저를 생성하지 않는 등, 다른 정의 방법과 다르게 동작하므로 바람직하지 않은 방법이다.</li>
</ul>
<pre><code class="language-jsx">const add = new Function(&#39;x&#39;, &#39;y&#39;, &#39;return x + y&#39;);</code></pre>
</li>
<li><p>화살표 함수(ES6)</p>
<ul>
<li>기존 함수에서 표현과 내부 동작이 간략화된 방식으로, 기존과 this 바인딩 방식이 다르며 prototype 프로퍼티가 없고 arguments 객체를 생성하지 않는다.</li>
</ul>
<pre><code class="language-jsx">const add = (x, y) =&gt; x + y;</code></pre>
</li>
</ul>
</li>
</ul>
<pre><code>&lt;br/&gt;</code></pre><h2 id="함수의-호출">함수의 호출</h2>
<ul>
<li>함수는 호출 표현식(괄호)을 통해 삼수 또는 메서드로 호출된다.</li>
<li>일반적으로 return 문 다름 표현식이 함수의 반환 값이며, return 문에 값이 없거나 return 문 자체가 없다면 반환 값은 undefined이다.</li>
</ul>
<pre><code class="language-jsx">const add = function(x, y) {
    return x + y;
};

const res = add(1, 2); // 함수 호출</code></pre>
<blockquote>
<p>함수가 호출되면 활성 객체(activation object)가 만들어집니다. 활성 객체는 눈에 보이지 않습니다. 숨겨진 데이터 구조로서 호출된 함수의 반환 주소와 실행에 필요한 정보를 저장하고 이를 호출된 함수에 바인딩해 줍니다. <br/>
C와 같은 언어에서는 활성 객체가 스택에 저장됩니다. 그리고 함수가 종료되고 반환되면 스택에서 제거합니다. 자바스크립트는 좀 다르게 동작합니다. 자바스크립트는 활성 객체를 다른 객체와 마찬가지로 힙에 저장합니다. 함수가 종료된다고 활성 객체를 자동으로 비활성화하진 않습니다. 활성 객체는 해당 객체에 대한 참조가 있는 한 계속 살아있으며, 다른 객체와 마찬가지로 가비지 컬렉터에 의해 처리됩니다.<br/>
더글러스 크락포드 , 『자바스크립트는 왜 그 모양일까?』, 인사이트(2020), p139.</p>
</blockquote>
<br/>

<h2 id="함수의-형태">함수의 형태</h2>
<h3 id="즉시실행함수">즉시실행함수</h3>
<ul>
<li><a href="https://developer.mozilla.org/ko/docs/Glossary/IIFE">즉시실행함수</a>(IIFE, Immediately Invoked Function Expression)는 함수의 정의와 동시에 즉시 호출되는 함수로, 단 한 번만 호출되며 다시 호출할 수 없다.</li>
<li>즉시실행함수는 불필요한 전역 변수 생성을 줄이고 private한 변수를 만들 수 있다는 장점을 가진다.</li>
</ul>
<pre><code class="language-jsx">(() =&gt; {
  // 초기화 코드
  let firstVariable;
  let secondVariable;
})();

// firstVariable와 secondVariable은 이 함수 실행 후에 사용할 수 없다.</code></pre>
<h3 id="재귀함수">재귀함수</h3>
<ul>
<li>함수가 자기 자신을 호출하는 것을 재귀 호출(recursive call)이라 한다. 재귀 함수(recursive function)는 재귀 호출을 수행하는 함수를 말한다.</li>
<li>재귀 호출을 멈출 수 있는 탈출 조건을 반드시 만들어야 한다. 탈출 조건이 없으면 무한 호출되어 스택 오버플로 에러(Maximum call-stack size exceeded)가 발생한다.</li>
</ul>
<pre><code class="language-jsx">function loop(x) {
  if (x &gt;= 10)
    // &quot;x &gt;= 10&quot; 는 탈출 조건 (&quot;!(x &lt; 10)&quot;와 동일)
    return;
  // 뭔가 합니다.
  loop(x + 1); // 재귀 호출
}
loop(0);</code></pre>
<h3 id="중첩함수">중첩함수</h3>
<ul>
<li>함수 내부에 정의된 함수를 중첩함수(nested function) 혹은 내부함수(inner function)라 한다.</li>
<li>중첩 함수는 함수 내부에서만 호출할 수 있으며 보통 헬퍼함수(helper function)의 역할을 한다.</li>
<li>중첩함수는 클로저를 생성한다. 클로저는 후에 따로 다룰 예정이다.</li>
</ul>
<pre><code class="language-jsx">function addSquares(a, b) {
  function square(x) {
    return x * x;
  }
  return square(a) + square(b);
}
a = addSquares(2, 3); // 13</code></pre>
<h3 id="콜백함수">콜백함수</h3>
<ul>
<li>함수의 매개변수를 통해 다른 함수의 내부로 전달되는 함수를 <a href="https://developer.mozilla.org/ko/docs/Glossary/Callback_function">콜백함수</a>(callback function)라고 한다.</li>
<li>매개변수를 통해 함수의 외부에서 콜백함수를 전달받은 함수 자신은 고차함수(HOF)라고 한다. 두 개념의 차이를 잘 알아두자.</li>
</ul>
<br/>

<h2 id="함수형-프로그래밍">함수형 프로그래밍</h2>
<ul>
<li><a href="https://en.wikipedia.org/wiki/Functional_programming">함수형 프로그래밍(functional programming)</a>은 데이터 처리를 수학적 함수의 계산으로 취급하고 상태의 변경을 피하기 위해 함수의 응용을 강조하는 패러다임이다.</li>
<li>자바스크립트는 함수형 프로그래밍 언어는 아니지만 함수형 프로그래밍 기법을 사용할 수 있다. 배열에서 살펴보았던 고차함수가 바로 이러한 함수형 프로그래밍의 예로 볼 수 있다.</li>
</ul>
<blockquote>
<p>자바스크립트는 리스프(Lisp)나 하스켈(Haskell) 같은 함수형 프로그래밍 언어는 아니지만, 함수를 객체처럼 조작할 수 있으므로 함수형 프로그래밍 기법을 사용할 수 있습니다. map()과 reduce() 같은 배열 메서드는 특히 함수형 프로그래밍 스타일에 알맞습니다.<br/>
데이비드 플래너건 , 『자바스크립트 완벽 가이드』, 인사이트(2022), p246.</p>
</blockquote>
<ul>
<li>함수형 프로그래밍의 또 하나 중요한 개념으로 순수 함수(pure function)가 있다.</li>
</ul>
<blockquote>
<p>순수 함수는 동일한 인수가 전달되면 언제나 동일한 값을 반환하는 함수다. 즉, 순수 함수는 어떤 외부 상태에도 의존하지 않고 오직 매개변수를 통해 함수 내부로 전달된 인수에게만 의존해 값을 생성해 반환한다. <br/>
…함수형 프로그래밍은 결국 순수 함수를 통해 부수 효과를 최대한 억제해 오류를 피하고 프로그램의 안정성을 높이려는 노력의 일환이라고 할 수 있다. 자바스크립트는 멀티 패러다임 언어이므로 객체지향 프로그래밍 뿐만 아니라 함수형 프로그래밍을 적극적으로 활용하고 있다.<br/>
 이웅모 , 『모던 자바스크립트 Deep Dive』, 위키북스(2020), p187-188.</p>
</blockquote>
<br/>

<p><strong>References</strong></p>
<p>데이비드 플래너건 , 『자바스크립트 완벽 가이드』, 인사이트(2022)
더글러스 크락포드 , 『자바스크립트는 왜 그 모양일까?』, 인사이트(2020)
카일 심슨 , 『You Don’t Know JS - 타입과 문법, 스코프와 클로저』, 한빛미디어(2017) 
이웅모 , 『모던 자바스크립트 Deep Dive』, 위키북스(2020)</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[자바스크립트 교과서 - 9. 배열]]></title>
            <link>https://velog.io/@seoyong-lee/%EC%9E%90%EB%B0%94%EC%8A%A4%ED%81%AC%EB%A6%BD%ED%8A%B8-%EA%B5%90%EA%B3%BC%EC%84%9C-9.-%EB%B0%B0%EC%97%B4</link>
            <guid>https://velog.io/@seoyong-lee/%EC%9E%90%EB%B0%94%EC%8A%A4%ED%81%AC%EB%A6%BD%ED%8A%B8-%EA%B5%90%EA%B3%BC%EC%84%9C-9.-%EB%B0%B0%EC%97%B4</guid>
            <pubDate>Tue, 03 Oct 2023 07:34:42 GMT</pubDate>
            <description><![CDATA[<h2 id="배열의-특징">배열의 특징</h2>
<ul>
<li><p>배열(array)은 여러 개의 값을 순차적으로 나열한 자료구조다.</p>
</li>
<li><p>배열은 요소(element)로 이루어지며 모든 값은 배열의 요소가 될 수 있다.</p>
</li>
<li><p>배열은 사실 객체의 한 종류라고 볼 수 있다.</p>
<blockquote>
<p>자바스크립트는 처음 배포 당시 배열을 포함시키지 못했습니다. 자바스크립트의 객체가 워낙 강력해서 배열이 빠졌다는 사실을 알아채는 경우는 드물었지만요. 성능 문제만 무시한다면 객체는 배열이 할 수 있는 모든 것을 할 수 있습니다. <br/>
  더글러스 크락포드 , 『자바스크립트는 왜 그 모양일까?』, 인사이트(2020), p81.</p>
</blockquote>
</li>
<li><p>일반적으로 자료구조(data structure)에서 말하는 배열은 밀집 배열(dense array) 이지만 자바스크립트 배열은 희소 배열(sparse array)을 허용하며 둘은 다음과 같은 차이가 있다.</p>
<ul>
<li>밀집 배열은 배열의 요소를 위한 각각의 메모리 공간은 동일한 크기를 가지지만 희소 배열은 그렇지 않다.</li>
<li>밀집 배열은 연속적으로 이어져 있지만 희소 배열은 일부가 비어 있을 수 있다.</li>
</ul>
</li>
<li><p>희소 배열인 경우 배열의 length와 실제 요소의 개수가 일치하지 않을 수 있으므로 굳이 이렇게 만들지 않는 것이 좋다.</p>
<blockquote>
<p>이처럼 자바스크립트의 배열은 엄밀히 말해 일반적 의미의 배열이 아니다. 자바스크립트의 배열은 일반적인 배열의 동작을 흉내 낸 특수한 객체다. <br/>
  이웅모 , 『모던 자바스크립트 Deep Dive』, 위키북스(2020), p496.</p>
</blockquote>
</li>
</ul>
<br/>

<h2 id="인덱스와-길이">인덱스와 길이</h2>
<ul>
<li><p>배열의 요소는 자신의 위치를 나타내는 인덱스(index)를 갖는다.</p>
</li>
<li><p>인덱스는 0부터 시작하며 이는 대부분의 프로그래밍 언어가 동일하다.</p>
<blockquote>
<p>1960년대 중반, 작지만 영향력 있는 개발자 그룹이 1 대신 0부터 시작하면 어떻겠느냐고 제안했습니다. 그래서 오늘날 거의 모든 개발자는 수를 셀 때 0부터 시작합니다. …0에서 시작하는 것이 off-by-one 에러를 유발하기 때문에 정확하지 않다는 주장도 있지만, 이 또한 확실하지는 않습니다. <br/><br>  더글러스 크락포드 , 『자바스크립트는 왜 그 모양일까?』, 인사이트(2020), p82.</p>
</blockquote>
</li>
<li><p>위에서 언급된 <a href="https://en.wikipedia.org/wiki/Off-by-one_error">off-by-one 에러</a>는 반복문 등에서 종료 조건을 명시할 때, 그 값을 1 작게 혹은 1 크게 지정해서 문제가 생기는 경우로 최근에는 배열을 함수처럼 다루려는 경향 때문에 크게 문제가 되지는 않을 것 같다.</p>
<blockquote>
<p>배열을 다룰 때는 한 번에 하나의 요소만 처리해야 한다는 생각은 최소한 포트란 시절까지는 거슬러 올라가야 맞는 말입니다. 최근에는 배열 요소를 한 번에 하나씩 처리하기보다, 배열을 좀 더 함수처럼 처리해야 한다는 생각이 더 지배적입니다. 이렇게 해야 명시적인 반복문 처리가 없어져서 코드가 단순해지고, 멀티프로세서에 작업을 분산해서 처리할 수 있는 능력이 생깁니다. 잘 설계된 언어로 잘 짜이니 프로그램은 배열이 0에서 시작하든 1에서 시작하든 신경 쓸 필요가 없습니다. <br/>
  더글러스 크락포드 , 『자바스크립트는 왜 그 모양일까?』, 인사이트(2020), p82-83.</p>
</blockquote>
</li>
<li><p>배열의 길이를 반환하는 length 프로퍼티는 바로 이 인덱스 중 가장 큰 값에 1을 더한 값이다. length는 배열에 요소를 추가하거나 삭제하면 자동 갱신된다.</p>
</li>
<li><p>참고로 length 프로퍼티에 임의의 숫자 값을 할당할 수도 있다.</p>
<ul>
<li><p>현재 length 프로퍼티 값보다 작은 숫자를 할당하면 배열의 길이는 줄어든다.</p>
<pre><code class="language-jsx">const arr = [1, 2, 3];
arr.length = 2;
console.log(arr); // [1, 2]</code></pre>
</li>
<li><p>현재 length 프로퍼티 값보다 큰 숫자를 할당하면 배열 마지막에 빈 영역이 생기지만 실제 새 요소가 추가되지는 않는다.</p>
<pre><code class="language-jsx">const arr = [1];
arr.length = 3;
console.log(arr); // [1, empty * 2]</code></pre>
</li>
</ul>
</li>
</ul>
<br/>

<h2 id="배열-생성">배열 생성</h2>
<h3 id="배열-리터럴">배열 리터럴</h3>
<ul>
<li>가장 일반적인 방식이다. 주의할 점은 요소 없이 콤마만 작성해도 중간이 빈 희소 배열로 생성된다.</li>
</ul>
<pre><code class="language-jsx">const arr = [1, 2, 3];
const arr2 = [1, ,3];
console.log(arr2); // [1, empty, 3]</code></pre>
<h3 id="array-생성자">Array 생성자</h3>
<ul>
<li>Array 생성자는 다음 세 가지 방법으로 호출이 가능하다.<ul>
<li>인자 없이 호출<ul>
<li><code>let a = new Array();</code></li>
</ul>
</li>
<li>배열 길이 인자 하나로 호출<ul>
<li><code>let a = new Array(10);</code></li>
</ul>
</li>
<li>배열 요소를 두 개 이상 쓰거나 숫자가 아닌 요소를 하나만 넘겨 호출<ul>
<li><code>let a = new Array(1, 2, 3, &quot;testing&quot;);</code></li>
</ul>
</li>
</ul>
</li>
</ul>
<h3 id="arrayof">Array.of</h3>
<ul>
<li>위 생성자 방식으로는 숫자 요소가 하나만 있는 배열의 생성이 불가능해 ES6에서 추가되었다.</li>
</ul>
<pre><code class="language-jsx">Array.of(10) // [10]</code></pre>
<h3 id="arrayfrom">Array.from</h3>
<ul>
<li>ES6에서 도입된 팩토리 메서드로 첫 번째 인자로 이터러블 객체(iterable object)나 유사 배열 객체(array-like object)를 받아 새 배열을 만들어 반환한다.<ul>
<li>유사 배열 객체란 length 프로퍼티를 가지면서 인덱스로 프로퍼티 값에 접근할 수 있는 객체를 말한다.</li>
</ul>
</li>
</ul>
<pre><code class="language-jsx">Array.from({ length: 2, 0: &#39;a&#39;, 1: &#39;b&#39; }); // [&#39;a&#39;, &#39;b&#39;]</code></pre>
<br/>

<h2 id="배열-메서드">배열 메서드</h2>
<ul>
<li>배열의 다양한 메서드를 알아두면 유용하게 쓸 수 있다.</li>
<li>배열 메서드의 결과물 반환 패턴은 다음 두 방식이 있다.<ul>
<li>원본 배열을 직접 변경하는 메서드(mutator method)</li>
<li>원본 배열을 직접 변경하지 않고 새로운 배열을 생성하여 반환하는 메서드(accessor method)</li>
</ul>
</li>
</ul>
<h3 id="검색">검색</h3>
<ul>
<li>원본 배열에서 인수로 전달된 요소를 검색하여 인덱스를 반환한다.</li>
<li>그러나 NaN이 포함되어 있는지 확인할 수 없다는 문제가 있다.</li>
<li>ES7에서 도입된 includes 메서드를 사용하면 불리언 형태를 반환하기 때문에 가독성이 더 좋다.</li>
</ul>
<pre><code class="language-jsx">const arr = [1, 2, 3];
arr.indexOf(2); // 1
arr.indexOf(4); // -1
arr.includes(2); // true
arr.includes(4); // false
[NaN].indexOf(NaN) !== -1; // false
[NaN].includes(NaN); // true</code></pre>
<h3 id="스택과-큐">스택과 큐</h3>
<ul>
<li>스택과 큐에 해당하는 메서드들은 모두 원본 배열을 직접 변경한다.</li>
<li>스택에 해당하는 메서드는 다음과 같다.<ul>
<li>pop 메서드는 배열의 가장 마지막 요소를 제거하고 반환한다.</li>
<li>push 메서드는 새로운 요소를 배열 끝에 추가하고 변경된 length 프로퍼티 값을 반환한다.</li>
</ul>
</li>
<li>큐에 해당하는 메서드는 다음과 같다.<ul>
<li>shift 메서드는 0번째 요소를 제거하고 반환한다.</li>
<li>unshift 메서드는 배열의 가장 앞에 새로운 요소를 추가하고 변경된 length 프로퍼티 값을 반환한다.</li>
</ul>
</li>
</ul>
<h3 id="결합과-분리">결합과 분리</h3>
<ul>
<li>concat 메서드는 인수로 전달된 값들을 원본 배열 마지막 요소로 추가한 새로운 배열을 반환한다.</li>
<li>원본 배열은 변경되지 않는다.</li>
</ul>
<pre><code class="language-jsx">const arr1 = [1, 2];
const arr2 = [3, 4];
console.log(arr1.concat(arr2)); // [1, 2, 3, 4]</code></pre>
<ul>
<li><p>splice 메서드는 원본 배열의 중간에 요소를 추가하거나 중간 요소를 제거하는 경우 사용한다.</p>
</li>
<li><p>3개의 매개변수를 받으며 원본 배열을 직접 변경한다.</p>
<p>  <code>.splice(start, deleteCount, items)</code></p>
<ul>
<li>start: 제거를 시작할 인덱스</li>
<li>deleteCount: start에서 부터 제거할 요소의 개수</li>
<li>items: 제거 위치에 삽입할 요소들의 목록</li>
</ul>
</li>
</ul>
<pre><code class="language-jsx">const arr = [1, 2, 3, 4];
const res = arr.splice(1, 2, 20, 30);
console.log(res); // [2, 3]
console.log(arr); // [1, 20, 30, 4]</code></pre>
<ul>
<li>slice 메서드는 인수로 전달된 범위의 요소들을 복사하여 배열로 반환한다. 원본 배열은 변경되지 않는다.</li>
<li>다음 2개의 매개변수를 받는다.<ul>
<li>start: 복사를 시작할 인덱스</li>
<li>end: 복사를 종료할 인덱스</li>
</ul>
</li>
</ul>
<pre><code class="language-jsx">const arr = [1, 2, 3];
arr.slice(0, 1); // [1]</code></pre>
<h3 id="채우기">채우기</h3>
<ul>
<li>fill 메서드는 인수로 전달받은 값을 배열의 처음부터 끝까지 요소로 채운다. 원본 배열은 변경된다.</li>
</ul>
<pre><code class="language-jsx">const arr = [1, 2, 3];
arr.fill(0); // [0, 0, 0]</code></pre>
<h3 id="기타">기타</h3>
<ul>
<li>join 메서드는 원본 배열의 모든 요소를 문자열로 변환한 후, 인수로 전달받은 구분자(seperator)로 연결한 문자열을 반환한다.</li>
<li>구분자는 생략 가능하며 기본 구분자는 콤마(,)다.</li>
</ul>
<pre><code class="language-jsx">const arr = [1, 2, 3, 4];
arr.join(); // &#39;1,2,3,4&#39; </code></pre>
<ul>
<li>reverse 메서드는 원본 배열의 순서를 반대로 뒤집어 변경된 배열을 반환한다. 원본 배열은 변경된다.</li>
<li>flat 메서드는 ES10에서 도입되었으며, 인수로 전달한 깊이만큼 재귀적으로 배열을 평탄화한다.</li>
</ul>
<pre><code class="language-jsx">[1, [2, 3, 4, 5]].flat(); // [1, 2, 3, 4, 5]
[1, [2, [3, [4]]]].flat(2); // [1, 2, 3, [4]]</code></pre>
<br/>


<h2 id="배열-고차-함수">배열 고차 함수</h2>
<ul>
<li>고차 함수(Higher-Order Function)는 함수를 인수로 전달받거나 함수를 반환하는 함수를 말한다.</li>
</ul>
<blockquote>
<p>고차 함수는 외부 상태의 변경이나 가변(mutable) 데이터를 피하고 <strong>불변성(immutable)을 지향</strong>하는 함수형 프로그래밍에 기반을 두고 있다. <br/>
함수형 프로그래밍은 순수 함수(pure function)와 보조 함수의 조합을 통해 로직 내에 존재하는 <strong>조건문과 반복문을 제거</strong>하여 복잡성을 해결하고 <strong>변수의 사용을 억제</strong>하여 상태 변경를 피하려는 프로그래밍 패러다임이다. 조건문이나 반복문은 로직의 흐름을 이해하기 어렵게 하여 가독성을 해치고, 변수는 누군가에 의해 언제든지 변경될 수 있어 오류 발생의 근본적 원인이 될 수 있기 때문이다. 함수형 프로그래밍은 결국 <strong>순수 함수를 통해 부수 효과를 최대한 억제</strong>하여 오류를 피하고 프로그램의 안정성을 높이려는 노력의 일환이라고 할 수 있다.<br/>
이웅모 , 『모던 자바스크립트 Deep Dive』, 위키북스(2020), p529.</p>
</blockquote>
<h3 id="arrayprototypesort">Array.prototype.sort</h3>
<ul>
<li>sort 메서드는 배열의 요소를 정렬한다. 그러나 안정성이 높은 편은 아니라고 한다.</li>
</ul>
<blockquote>
<p>자바스크립트의 sort 메서드에는 몇 가지 문제가 있습니다. sort 메서드는 추가 메모리 공간을 사용하지 않고 배열 자체를 수정합니다. …다음 문제점은 안정성이 부족하다는 것입니다. 배열의 요소를 비교했을 때 같은 값, 즉 비교 함수가 영(0)을 반환하는 경우 두 요소의 상대적인 위치를 그대로 유지한다면 sort 함수는 안정적입니다. 하지만 자바스크립트는 안정성을 보장하지 않습니다. <br/>
더글러스 크락포드 , 『자바스크립트는 왜 그 모양일까?』, 인사이트(2020), p89-90</p>
</blockquote>
<ul>
<li>참고로 sort 메서드는 quicksort 알고리즘을 사용했으나 동일한 요소가 중복된 경우 초기 순서와 변경될 수 있는 불안정성 때문에 ECMAScript2019(ES10)에서 timesort 알고리즘을 사용하도록 변경되었다.</li>
<li>주의점으로 sort의 기본 정렬 순서는 유니코드 포인트의 순서를 따르므로 숫자를 정렬하는 경우 정렬 순서를 정의하는 비교 함수를 인수로 전달해야 한다.</li>
</ul>
<h3 id="arrayprototypeforeach">Array.prototype.forEach</h3>
<ul>
<li>for 문을 대체할 수 있는 고차 함수로 수행할 처리를 콜백 함수로 전달받아 반복 호출한다.</li>
<li>forEach 메서드는 원본 배열을 변경하지 않는다. 반환값은 언제나 undefined이다.</li>
<li>forEach 메서드의 폴리필을 살펴보면 결국 내부적으로는 for 반복문을 통해 순회하는 것을 볼 수 있다. 반복문을 메서드 내부에 숨겨 로직의 흐름을 이해하기 쉽도록 처리하는 고차 함수의 특징을 확인할 수 있다.</li>
</ul>
<pre><code class="language-jsx">if (!Array.prototype.forEach) {
    Array.prototype.forEach = function(callback, thisArg) {
        // 첫 번째 인수가 함수가 아니면 TypeError 발생
        if(typeof callback !== &#39;function&#39;){
            throw new TypeError(callback + &#39;is not a function&#39;);
        }
        // this로 사용할 두 번째 인수를 받지 못하면 전역 객체를 this로 사용한다.
        thisArg = thisArg || window;

        for(var i = 0; i &lt; this.length; i++) {
            // call 메서드를 통해 thisArg를 전달하면서 콜백 함수를 호출한다.
            callback.call(thisArg, this[i], i, this);    
        }
    };
}</code></pre>
<ul>
<li>for 문과 다른 점은 break, continue 문을 사용할 수 없다는 점이다. 배열의 모든 요소를 빠짐없이 모두 순회해야 하며 순회를 중단할 수 없다.</li>
</ul>
<h3 id="arrayprototypemap">Array.prototype.map</h3>
<ul>
<li>map은 forEach와 비슷하게 배열의 모든 요소를 순회하지만 전달받은 콜백 함수의 반환값들로 구성된 새로운 배열을 반환한다.</li>
<li>원본 배열은 변경되지 않는다.</li>
</ul>
<h3 id="arrayprototypefilter">Array.prototype.filter</h3>
<ul>
<li>filter 메서드는 배열의 모든 요소를 순회하면서 인수로 전달받은 콜백 함수의 반환값이 true인 요소로만 구성된 새로운 배열을 반환한다.</li>
<li>원본 배열은 변경되지 않는다.</li>
</ul>
<h3 id="arrayprototypereduce">Array.prototype.reduce</h3>
<ul>
<li>reduce 메서드는 배열의 모든 요소를 순회하며 인수로 전달받은 콜백 함수의 반환값을 다음 순회 시에 콜백 함수의 첫 번째 인수로 전달하면서 하나의 결괏값을 만들어 반환한다.</li>
<li>원본 배열은 변경되지 않는다.</li>
</ul>
<pre><code class="language-jsx">const arr = [1, 2, 3, 4];
const sum = arr.reduce((accumulator, currentValue, index, array) =&gt; {
    return accumulator + currentValue
}, 0);
console.log(sum); // 10</code></pre>
<h3 id="arrayprototypesome">Array.prototype.some</h3>
<ul>
<li>some 메서드는 배열 요소를 순회하면서 인수로 전달받은 콜백 함수의 반환값이 단 한 번이라도 true라면 true, 모두 거짓이면 false를 반환한다.</li>
</ul>
<h3 id="arrayprototypeevery">Array.prototype.every</h3>
<ul>
<li>every 메서드는 배열 요소를 순회하면서 인수로 전달받은 콜백 함수의 반환값이 모두 true라면 true, 단 한 번이라도 거짓이면 false를 반환한다.</li>
</ul>
<h3 id="arrayprototypefind">Array.prototype.find</h3>
<ul>
<li>find 메서드는 자신을 호출한 배열 요소를 순회하면서 인수로 전달받은 콜백 함수의 반환값이 true인 첫 번째 요소를 반환한다. true인 요소가 존재하지 않는다면 undefined를 반환한다.</li>
</ul>
<h3 id="arrayprototypefindindex">Array.prototype.findIndex</h3>
<ul>
<li>find 메서드는 자신을 호출한 배열 요소를 순회하면서 인수로 전달받은 콜백 함수의 반환값이 true인 첫 번째 요소의 인덱스를 반환한다. true인 요소가 존재하지 않는다면 -1을 반환한다.</li>
</ul>
<h3 id="arrayprototypeflatmap">Array.prototype.flatMap</h3>
<ul>
<li><p>flatMap 메서드는 ES10에서 도입되었으며 map 메서드를 통해 생성된 새로운 배열을 평탄화한다. 단, flat 메서드와 달리 1단계만 평탄화가 가능하다.</p>
<pre><code class="language-jsx">  const arr = [&#39;hello&#39;, &#39;world&#39;];
  arr.map(x =&gt; x.split(&#39;&#39;)).flat(); 
  // [&#39;h&#39;, &#39;e&#39;, &#39;l&#39;, &#39;l&#39;, &#39;o&#39;, &#39;w&#39;, &#39;o&#39;, &#39;r&#39;, &#39;l&#39;, &#39;d&#39;]
  arr.flatMap(x =&gt; x.split(&#39;&#39;));
  // [&#39;h&#39;, &#39;e&#39;, &#39;l&#39;, &#39;l&#39;, &#39;o&#39;, &#39;w&#39;, &#39;o&#39;, &#39;r&#39;, &#39;l&#39;, &#39;d&#39;]</code></pre>
</li>
</ul>
<br/>


<p><strong>References</strong></p>
<p>데이비드 플래너건 , 『자바스크립트 완벽 가이드』, 인사이트(2022)
더글러스 크락포드 , 『자바스크립트는 왜 그 모양일까?』, 인사이트(2020)
카일 심슨 , 『You Don’t Know JS - 타입과 문법, 스코프와 클로저』, 한빛미디어(2017) 
이웅모 , 『모던 자바스크립트 Deep Dive』, 위키북스(2020)</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[
자바스크립트 교과서 - 8. 객체]]></title>
            <link>https://velog.io/@seoyong-lee/%EC%9E%90%EB%B0%94%EC%8A%A4%ED%81%AC%EB%A6%BD%ED%8A%B8-%EA%B5%90%EA%B3%BC%EC%84%9C-8.-%EA%B0%9D%EC%B2%B4</link>
            <guid>https://velog.io/@seoyong-lee/%EC%9E%90%EB%B0%94%EC%8A%A4%ED%81%AC%EB%A6%BD%ED%8A%B8-%EA%B5%90%EA%B3%BC%EC%84%9C-8.-%EA%B0%9D%EC%B2%B4</guid>
            <pubDate>Mon, 02 Oct 2023 14:19:23 GMT</pubDate>
            <description><![CDATA[<h2 id="객체의-특징">객체의 특징</h2>
<blockquote>
<p>자바스크립트는 객체(object) 기반의 프로그래밍 언어이며, 자바스크립트를 구성하는 거의 “모든 것”이 객체다. 원시 값을 제외한 나머지 값(함수, 배열, 정규 표현식 등)은 모두 객체다. <br/>
이웅모 , 『모던 자바스크립트 Deep Dive』, 위키북스(2020), p124.</p>
</blockquote>
<ul>
<li>객체는 0개 이상의 프로퍼티로 구성된 집합으로 프로퍼티는 키(key)와 값(value)로 구성된다.</li>
<li>프로퍼티 값이 함수인 경우 메서드(method)라 부른다.</li>
</ul>
<pre><code class="language-jsx">const person = {
    name: &#39;lee&#39;, // 프로퍼티 키: name, 프로퍼티 값: &#39;lee&#39;
    age: 32,
    sayHi() {
      console.log(&quot;hi&quot;); // 메서드
    }
}</code></pre>
<br/>

<h2 id="원시-타입과-객체의-차이점">원시 타입과 객체의 차이점</h2>
<ul>
<li>자바스크립트는 7가지 원시 타입을 제외하면 모두 객체 타입으로 볼 수 있다.</li>
<li>모던 자바스크립트 Deep Dive 11장에서 원시 타입과 객체 타입의 차이점을 다음과 같이 잘 설명하고 있다.</li>
</ul>
<blockquote>
<ul>
<li>원시 타입의 값, 즉 원시 값은 변경 불가능한 값(immutable value)이다. 이에 비해 객체(참조) 타입의 값,  즉 객체는 변경 가능한 값(mutable value)이다.</li>
</ul>
</blockquote>
<ul>
<li><p>원시 값을 변수에 할당하면 변수(확보된 메모리 공간)에는 실제 값이 저장된다. 이에 비해 객체를 변수에 할당하면 변수(확보된 메모리 공간)에는 참조 값이 저장된다.</p>
</li>
<li><p>원시 값을 갖는 변수를 다른 변수에 할당하면 원본의 원시 값이 복사되어 전달된다. 이를 값에 의한 전달(pass by value)이라 한다. 이에 비해 객체를 가리키는 변수를 다른 변수에 할당하면 원본의 참조 값이 복사되어 전달된다. 이를 참조에 의한 전달(pass by reference)이라 한다.<br/>
이웅모 , 『모던 자바스크립트 Deep Dive』, 위키북스(2020), p137.</p>
</li>
<li><p>원시 타입은 각 타입에서 본 것과 같이 모두 변경이 불가능한 값이다. 한 번 생성된 원시 값은 읽기 전용으로 변경이 불가능하다.</p>
<ul>
<li>여기서 오해하면 안 되는 점은 변수는 얼마든지 변경할 수 있다. 다만 변수의 재할당이 원시 값 자체를 변경하는 것은 아니다. 변경이 불가능한 것은 ‘값에 대한 진술’이다</li>
</ul>
</li>
</ul>
<br/>

<h2 id="프로퍼티-어트리뷰트">프로퍼티 어트리뷰트</h2>
<ul>
<li>위에서 설명한 객체 내부의 프로퍼티는 사실 생성과 동시에 상태를 나타내는 프로퍼티 어트리뷰트를 기본값으로 자동 정의한다. 프로퍼티 어트리뷰트를 이해하기 위해서는 내부 슬롯과 내부 메서드에 대해 먼저 이해해야 한다.</li>
</ul>
<blockquote>
<p>내부 슬롯(internal slot)과 내부 메서드(internal method)는 ECMAScript 사양에 정의된 대로 구현되어 자바스크립트 엔진에서 실제로 동작하지만 개발자가 직접 접근할 수 있도록 외부로 공개된 객체의 프로퍼티는 아니다. …예를 들어, 모든 객체는 [[Prototype]]이라는 내부 슬롯을 갖는다. 내부 슬롯은 자바스크립트 엔진의 내부 로직이므로 원칙적으로 직접 접근할 수 없지만 [[Prototype]] 내부 슬롯의 경우, __proto__를 통해 간접적으로 접근할 수 있다. <br/>
이웅모 , 『모던 자바스크립트 Deep Dive』, 위키북스(2020), p220.</p>
</blockquote>
<ul>
<li><p>즉, 프로퍼티 어트리뷰트는 자바스크립트 엔진이 관리하는 내부 상태 값인 내부 슬롯이다. Prototype과 마찬가지로 직접 접근이 불가능하지만 Object.getOwnPropertyDescriptor 메서드를 사용해 간접적으로 확인할 수 있다.</p>
<pre><code class="language-jsx">const person = {
    name: &#39;lee&#39;
};

console.log(Object.getOwnPropertyDescriptor(person, &#39;name&#39;));
// {value: &quot;lee&quot;, writable: true, enumerable: true, configurable: true}</code></pre>
</li>
<li><p>프로퍼티는 다음 두 형태로 구분할 수 있다.</p>
<ul>
<li>데이터 프로퍼티(data property): 키와 값으로 구성된 일반적인 프로퍼티<ul>
<li>다음의 프로퍼티 어트리뷰트를 가진다.<ul>
<li>[[Value]]: 키를 통해 값에 접근하면 반환되는 값</li>
<li>[[Writable]]: 값의 변경 여부(boolean)</li>
<li>[[Enumerable]]: 프로퍼티 열거 가능 여부(boolean)</li>
<li>[[Configurable]]: 프로퍼티 재정의 가능 여부(boolean)</li>
</ul>
</li>
</ul>
</li>
<li>접근자 프로퍼티(accessor property): 다른 데이터 프로퍼티 값을 읽거나 저장할 때 호출되는 접근자 함수로 구성된 프로퍼티<ul>
<li>다음의 프로퍼티 어트리뷰트를 가진다.<ul>
<li>[[Get]]: 접근자 프로퍼티를 통해 데이터 프로퍼티 값을 읽을 때 호출되는 접근자 함수</li>
<li>[[Set]]: 접근자 프로퍼티를 통해 데이터 프로퍼티 값을 저장할 때 호출되는 접근자 함수</li>
<li>[[Enumerable]]: 프로퍼티 열거 가능 여부(boolean)</li>
<li>[[Configurable]]: 프로퍼티 재정의 가능 여부(boolean)</li>
</ul>
</li>
</ul>
</li>
</ul>
</li>
<li><p>각 프로퍼트 어트리뷰트에 대해 조금 더 자세히 알고 싶다면 You Don’t Know JS의 2번째 책 ‘this와 객체 프로토타입, 비동기와 성능’ 3장 객체 부분을 추천한다.</p>
</li>
</ul>
<br/>

<h2 id="객체-변경-방지">객체 변경 방지</h2>
<ul>
<li><p>객체는 변경 가능한 값이므로 직접 변경이 가능하지만 이러한 변경을 방지하는 메서드도 존재한다.</p>
<ul>
<li>Object.preventExtension 메서드는 객체의 확장(프로퍼티 추가)을 금지한다.</li>
<li>Object.seal 메서드는 객체를 밀봉한다. 밀봉(seal)이란 프로퍼티 추가 및 삭제, 프로퍼티 어트리뷰트 재정의 금지를 의미한다. 밀봉된 객체는 읽기와 쓰기만 가능하다.</li>
<li>Object.freeze 메서드는 객체를 동결한다. 동결(freeze)이란 프로퍼티 추가 및 삭제와 프로퍼티 어트리뷰트 재정의 금지, 프로퍼티 값 갱신 금지를 의미한다. 동결된 객체는 읽기만 가능하다.</li>
</ul>
</li>
<li><p>이러한 변경 방지 메서드는 얕은 변경 방지(shallow only)로 직속 프로퍼티만 변경되며 중첩 객체까지 영향을 주지는 못 한다.</p>
<pre><code class="language-jsx">  const person = {
      name: &#39;Lee&#39;,
      address: { city: &#39;Seoul&#39; }
  };
  Object.freeze(person);
  console.log(Object.isFrozen(person)); // true
  console.log(Object.isFrozen(person.address)); // false</code></pre>
</li>
</ul>
 <br/>

<p><strong>References</strong></p>
<p>데이비드 플래너건 , 『자바스크립트 완벽 가이드』, 인사이트(2022)
더글러스 크락포드 , 『자바스크립트는 왜 그 모양일까?』, 인사이트(2020)
카일 심슨 , 『You Don’t Know JS - 타입과 문법, 스코프와 클로저』, 한빛미디어(2017) 
이웅모 , 『모던 자바스크립트 Deep Dive』, 위키북스(2020)</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[자바스크립트 교과서 - 7. 문법]]></title>
            <link>https://velog.io/@seoyong-lee/%EC%9E%90%EB%B0%94%EC%8A%A4%ED%81%AC%EB%A6%BD%ED%8A%B8-%EA%B5%90%EA%B3%BC%EC%84%9C-7.-%EB%AC%B8%EB%B2%95</link>
            <guid>https://velog.io/@seoyong-lee/%EC%9E%90%EB%B0%94%EC%8A%A4%ED%81%AC%EB%A6%BD%ED%8A%B8-%EA%B5%90%EA%B3%BC%EC%84%9C-7.-%EB%AC%B8%EB%B2%95</guid>
            <pubDate>Mon, 02 Oct 2023 09:30:59 GMT</pubDate>
            <description><![CDATA[<h2 id="문과-표현식">문과 표현식</h2>
<ul>
<li><p>자바스크립트는 문과 표현식을 모두 가진 문장 언어이다.</p>
<blockquote>
<p>프로그래밍 언어는 표현 언어(expression language)와 문장 언어(statement language)로 나눌 수 있습니다. 문장 언어는 문장과 표현식을 가지고 있지만, 표현 언어는 표현식만 가지고 있습니다. …자바스크립트를 포함한 대부분의 인기 있는 언어들은 문장 언어입니다. <br/>
더글러스 크락포드 , 『자바스크립트는 왜 그 모양일까?』, 인사이트(2020), p127.</p>
</blockquote>
</li>
<li><p>문과 표현식은 서로 다른 개념이다.</p>
<blockquote>
<p>문(Statement)과 표현식(Expression)을 대충 같은 의미라고 넘겨버리는 개발자가 허다하다. 그러나 자바스크립트에서 두 용어는 아주 중요한 차이가 있으므로 명확하게 분별하자. <br/>
카일 심슨 , 『You Don’t Know JS - 타입과 문법, 스코프와 클로저』, 한빛미디어(2017), p150.</p>
</blockquote>
</li>
</ul>
<h3 id="문">문</h3>
<ul>
<li><p>문(Statement)은 프로그램을 구성하는 기본 단위이자 최소 실행 단위이다.</p>
<ul>
<li>문의 집합이 프로그램이며, 문은 여러 토큰으로 구성된다.</li>
<li>토큰은 문법적으로 더 이상 나눌 수 없는 코드의 기본 요소를 의미한다.</li>
</ul>
</li>
<li><p>문은 문장(Sentence), 표현식은 어구(Phrase)에 해당된다고 볼 수 있다.</p>
<pre><code class="language-jsx">const a = 3 * 6;
b;</code></pre>
</li>
<li><p>위 예시에서 3 * 6 은  표현식이며, 위 아래 모두 표현식이 포함된 문으로 볼 수 있다. 위의 경우 변수를 선언 하므로 선언문(Declaration Statement)이라고 한다.</p>
</li>
</ul>
<h3 id="표현식">표현식</h3>
<blockquote>
<p>표현식(expression)은 값으로 평가될 수 있는 문(statement)이다. 즉, 표현식이 평가되면 새로운 값을 생성하거나 기존 값을 참조한다. <br/>
이웅모 , 『모던 자바스크립트 Deep Dive』, 위키북스(2020), p52.</p>
</blockquote>
<ul>
<li>즉 표현식은 읽히는 즉시 값을 평가한다. 값(value)은 표현식 평가의 결과물로 볼 수 있다.</li>
<li>아래와 같이 표현식이 아닌 문의 경우도 존재한다. 평가 대상이 없다면 표현식이 아니다.</li>
</ul>
<pre><code class="language-jsx">var x; // 이와 같은 변수 선언문은 표현식이 아닌 문이다.
x = 100; // 할당문은 그 자체가 표현식이면서 완벽한 문이다.</code></pre>
<br />
<br />

<h2 id="표현식의-종류">표현식의 종류</h2>
<blockquote>
<p>자바스크립트에서는 문장 위치에 어떤 표현식을 쓰든 상관없습니다. 조잡하지만, 인기가 많은 언어 설계 방식이죠. 문장이 올 자리에 쓰기 좋은 세 가지 자바스크립트 표현식으로는 할당문, 호출문, delete가 있습니다. 아쉽게도 자바스크립트에서는 이 세 가지 외 다른 모든 종류의 표현식을 문장 위치에 쓸 수 있기 때문에 컴파일러가 에러를 찾아내기가 쉽지 않습니다. <br/>
더글러스 크락포드 , 『자바스크립트는 왜 그 모양일까?』, 인사이트(2020), p130.</p>
</blockquote>
<ul>
<li>위 설명과 같이 사실 자바스크립트의 표현식은 생각 이상으로 범위가 넓다.</li>
</ul>
<h3 id="기본-표현식">기본 표현식</h3>
<ul>
<li>단독으로 존재하는 가장 단순한 표현식으로 리터럴, 상수, 변수 참조 등이 있다.<ul>
<li>1.23</li>
<li>“hello”</li>
<li>true</li>
<li>undefined (전역 객체의 ‘undefined’ 프로퍼티 값)</li>
</ul>
</li>
</ul>
<h3 id="초기화-및-정의-표현식">초기화 및 정의 표현식</h3>
<ul>
<li><p>객체와 배열의 초기화 표현식은(initializer)는 그 값이 새로 생성된 객체나 배열인 표현식이다.</p>
<ul>
<li>[ ]</li>
<li>[1 , , , 4]</li>
<li>let q = { };</li>
</ul>
</li>
<li><p>함수 정의 표현식은 함수를 정의하며 그 값은 함수이다.</p>
<pre><code class="language-jsx">  let square = function(x) { return x * x; };</code></pre>
</li>
</ul>
<h3 id="프로퍼티-접근-표현식">프로퍼티 접근 표현식</h3>
<ul>
<li>객체 프로퍼티나 배열 요소의 값으로 평가된다.</li>
<li>다음 두 가지 접근 문법을 사용할 수 있다.<ul>
<li>expression.identifier</li>
<li>expression[identifier]</li>
</ul>
</li>
<li>프로퍼티 이름에 스페이스나 구두점이 들어있거나 숫자인 경우 반드시 두 번째 방법을 사용해야 한다.</li>
<li>ES2020에서 추가된 옵셔널 체이닝은 다음과 같이 평가 결과가 null과 undefined인 경우 undefined를 반환한다.<ul>
<li>expression?.identifier</li>
<li>expression?.[identifier]</li>
</ul>
</li>
</ul>
<h3 id="호출-표현식">호출 표현식</h3>
<ul>
<li><p>함수나 메서드를 호출(실행)하는 문법이다.</p>
</li>
<li><p>ES2020에서 추가된 조건부 호출을 사용하면 표현식이 null이나 undefined인 경우 호출 표현식 전체를 undefined로 평가한다.</p>
<pre><code class="language-jsx">  function square (x, log) {
      log?.(x); // 함수를 받은 경우에만 호출
      return x * x;
  }</code></pre>
</li>
</ul>
<h3 id="산술-표현식">산술 표현식</h3>
<ul>
<li>단항 산술 연산자<ul>
<li>+, -, ++, — 가 여기에 속한다.</li>
<li>오른쪽에서 왼쪽으로 연산을 수행하며 피연산자를 숫자로 변환한다.</li>
</ul>
</li>
<li>비트 연산자<ul>
<li>이진 비트 대상 저수준 조작을 위한 연산자이다.</li>
<li>AND(&amp;), OR(|), XOR(^), NOT(~), 왼쪽 시프트(&lt;&lt;), 부호 붙은 오른쪽 시프트(&gt;&gt;), 0으로 채우는 오른쪽 시프트(&gt;&gt;&gt;) 등이 존재한다.</li>
</ul>
</li>
</ul>
<h3 id="관계-표현식">관계 표현식</h3>
<ul>
<li>일치, 불일치 연산자(==, ===, ≠. ≠=)와 비교 연산자(&lt;, &gt;, ≤, ≥)가 포함된다.</li>
<li>in, instanceof 연산자도 관계 표현식으로 볼 수 있다.</li>
</ul>
<h3 id="논리-표현식">논리 표현식</h3>
<ul>
<li>&amp;&amp;, ||, ! 등의 불 연산을 수행하는 논리 연산자.</li>
</ul>
<h3 id="할당-표현식">할당 표현식</h3>
<ul>
<li>= 를 이용해 변수나 프로퍼티에 값을 할당한다.</li>
<li>다음과 같이 할당과 다른 연산자를 합친 단축 표현을 지원한다.<ul>
<li>+=, -=, *=, /=, %=</li>
</ul>
</li>
</ul>
<h3 id="평가-표현식">평가 표현식</h3>
<ul>
<li>인터프리터 언어 특성상 문자열 해석 및 평가를 위해 존재하는 eval( )이 여기에 속한다.</li>
<li>eval은 다음 방식으로 동작한다.</li>
</ul>
<blockquote>
<p>자바스크립트의 eval( ) 함수는 문자열을 인자로 받아들여 실행 시점에 문자열의 내용을 코드의 일부분처럼 처리한다. 즉, 처음 작성한 코드에 프로그램에서 생성한 코드를 집어넣어 마치 처음 작성될 때부터 있던 것처럼 실행한다. <br/>
카일 심슨 , 『You Don’t Know JS - 타입과 문법, 스코프와 클로저』, 한빛미디어(2017), p208.</p>
</blockquote>
<ul>
<li>그러나 strict mode를 사용하면 eval 관련 여러 제한을 두고 있으므로 개발 과정에서 실제로 eval을 사용할 일은 없을것이다.</li>
</ul>
<blockquote>
<p>동적으로 문자열을 평가해 소스 코드로 바꾸는 것은 지나치게 강력한 기능이며, 현실에서는 (거의) 절대 필요하지 않습니다. 만약 eval( )을 사용하고 있다면, 정말로 필요해서 사용하고 있는지 심사숙고해야 합니다. 특히 eval( )은 보안 허점을 만듭니다. 사용자가 입력한 문자열을 eval( )에 전달해서는 절대 안 됩니다. <br/>
데이비드 플래너건 , 『자바스크립트 완벽 가이드』, 인사이트(2022), p101.</p>
</blockquote>
 <br />

<p><strong>References</strong></p>
<p>데이비드 플래너건 , 『자바스크립트 완벽 가이드』, 인사이트(2022)
더글러스 크락포드 , 『자바스크립트는 왜 그 모양일까?』, 인사이트(2020)
카일 심슨 , 『You Don’t Know JS - 타입과 문법, 스코프와 클로저』, 한빛미디어(2017)
이웅모 , 『모던 자바스크립트 Deep Dive』, 위키북스(2020)</p>
]]></description>
        </item>
    </channel>
</rss>