<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>sharlotte_04.log</title>
        <link>https://velog.io/</link>
        <description>샤르르르</description>
        <lastBuildDate>Sun, 19 May 2024 23:52:42 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <image>
            <title>sharlotte_04.log</title>
            <url>https://velog.velcdn.com/images/sharlotte_04/profile/4d1e627f-cdb0-42b4-894c-6a40f6e2cfb6/social_profile.png</url>
            <link>https://velog.io/</link>
        </image>
        <copyright>Copyright (C) 2019. sharlotte_04.log. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/sharlotte_04" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[NAT를 넘어 P2P 통신하기]]></title>
            <link>https://velog.io/@sharlotte_04/P2P-NAT</link>
            <guid>https://velog.io/@sharlotte_04/P2P-NAT</guid>
            <pubDate>Sun, 19 May 2024 23:52:42 GMT</pubDate>
            <description><![CDATA[<h1 id="nat를-넘어-p2p-통신하기">NAT를 넘어 P2P 통신하기</h1>
<p><em>유저들이 서로에게 3byte의 가벼운 데이터를 최대 20ms 레이턴시 내로 주고받게 하기 위하여.</em></p>
<blockquote>
<p><strong>!Notice</strong>
이 글에 첨부된 모든 이미지는 클릭 시 출처 사이트로 이동됩니다.</p>
</blockquote>
<p><strong>TL;DR</strong></p>
<ol>
<li>일반적으로 그냥 P2P할려면 NAT가 페킷 내다버림</li>
<li>NAT가 내다버리지 않게 규칙을 만들어야 함. ← 홀펀칭</li>
<li>규칙을 만드는 과정에서 논리적으로 홀펀칭이 불가능할 수 있음.
→ UPnP(자동 포트포워딩), Relay(중계 서버 거치기)같은 다른 방법 쓰면됨</li>
</ol>
<h2 id="p2p---peer-to-peer">P2P - Peer to Peer</h2>
<blockquote>
<p>P2P(peer-to-peer network) 혹은 동등 계층간 통신망(同等階層間通信網)은 비교적 소수의 서버에 집중하기보다는 망구성에 참여하는 기계들의 계산과 대역폭 성능에 의존하여 구성되는 통신망이다. 
(...)
순수 P2P 파일 전송 네트워크는 클라이언트나 서버란 개념 없이, 오로지 동등한 계층 노드들(peer nodes)이 서로 클라이언트와 서버 역할을 동시에 네트워크 위에서 하게 된다.
<a href="https://www.wikiwand.com/ko/P2P">https://www.wikiwand.com/ko/P2P</a></p>
</blockquote>
<p>Peer to Peer 네트워크는 송신자인 서버와 수신자인 클라이언트의 역할이 나뉜 CS(client-server)와 대조적으로, 모두가 서로에게 송/수신을 하는 하는 개념입니다.</p>
<p>P2P 통신은 멀티미디어(치지직/아프리카의 그리드 같은), 파일 공유, 블록체인, 게임 등 많은 분야에서 응용되어왔습니다. 게임에선 P2P를 아래와 같은 장점때문에 체택하는 경우가 있습니다.</p>
<blockquote>
<p><strong>P2P 게이밍의 고유한 이점</strong></p>
<ol>
<li><strong>신속한 응답과 지연 시간 감소</strong>: P2P 게임에서는 플레이어가 직접 연결을 설정하므로 멀리 떨어진 중앙 집중식 서버를 통해 통신이 이루어지지 않습니다. 이러한 즉각성은 밀리초가 중요한 빠른 속도의 게임에서 특히 유용하며, 보다 유동적인 게임 플레이를 가능하게 합니다.</li>
<li><strong>경제성과 효율성</strong>: 게임 회사의 가장 큰 과제 중 하나는 중앙 집중식 서버 유지와 관련된 높은 오버헤드입니다. P2P 시스템은 이러한 비용을 획기적으로 절감하여 게이머에게 비용을 절감하고 게임 개발에 더 많은 투자를 할 수 있는 경제적인 솔루션을 제시합니다.</li>
<li><strong>적응형 확장성</strong>: 플레이어 수가 증가하면 P2P 네트워크는 연결을 추가하여 손쉽게 조정하므로 플레이어 수에 관계없이 게임플레이를 원활하게 유지할 수 있습니다. 반대로 중앙 집중식 서버는 종종 과부하가 걸릴 수 있습니다.</li>
<li><strong>강력한 복원력</strong>: P2P의 분산형 설계는 네트워크의 일부에 문제가 발생하더라도 게임 경험이 위태로워지지 않도록 보장합니다. 중앙 서버에 문제가 발생하면 모든 플레이어의 경험이 중단될 수 있는 중앙 집중식 시스템과 달리 P2P 네트워크는 일관된 게임플레이를 제공합니다.
<code>DeepL에 의해 번역됨</code>
<a href="https://medium.com/tashi-gg/peer-to-peer-gaming-9991600c6707">https://medium.com/tashi-gg/peer-to-peer-gaming-9991600c6707</a></li>
</ol>
</blockquote>
<p>물론 강력한 중앙 서버가 없으므로 동기화와 핵에 관련된 문제가 있을 수 밖에 없고, 이 문제를 해결하기 위해 peer 하나가 호스트 격을 맡는 방법도 있습니다.
게임 멀티플레이어는 이것까지 합하여 총 3가지 유형이 있습니다.
<a href="https://medium.com/tashi-gg/peer-to-peer-gaming-9991600c6707"><img src="https://velog.velcdn.com/images/sharlotte_04/post/1f0d1c34-3ea0-453d-a886-9d557a187711/image.png" alt=""></a></p>
<p>그런데 저는 여기서 한가지 의문이 들었습니다.
어릴 적 해왔던 스타크래프트나 마인크레프트에서 친구와 게임하기 위해 서버를 열려면 포트포워딩을 해야 했습니다. 그때 알게 된 포트포워딩은 단순히 <em>공유기의 특정 포트가 내 핸드폰의 주소로 향하게 만든다</em> 라고만 알고 있었습니다. 즉, 공유기 설정에서 수동으로 규칙을 설정했던겁니다.</p>
<p>그런데 P2P 네트워크로 통신중인 게임들은 어떻게 내가 설정하지 않아도 알아서 공유기를 거쳐 내 핸드폰과 인터넷 사이 플레이어들을 연결해준걸까요?</p>
<p>그때 저는 한 스택 오버플로우 답변에서 P2P입장에서 NAT에 의해 생긴 문제를 해결하기 위해 UDP 홀펀칭이란 개념을 봤습니다.</p>
<blockquote>
<p>둘째, UDP 홀 펀칭에 대해 이야기하고 있을 수 있습니다. 이는 세 번째 랑데부 서버를 통해 세 번째 댓글 호스트를 사용하여 NAT 라우터/게이트웨이 뒤에 있는 두 호스트 간의 연결을 유지하는 데 사용되는 기술 또는 알고리즘입니다.
<code>DeepL에 의해 번역됨</code>
<a href="https://stackoverflow.com/a/16909905/24182996">https://stackoverflow.com/a/16909905/24182996</a></p>
</blockquote>
<h2 id="nat">NAT</h2>
<p>NAT(Network Address Translator, 네트워크 주소 변환기)는 통신을 할 때 IP 패킷에 담긴 IP 및 포트, 목적지의 IP 및 포트를 재기록하여 트레픽을 주고받는 기술입니다. 대표적으로 집에 있는 라우터가 이 기술을 통해 하나의 공인 주소로 여러 기기가 인터넷과 통신할 수 있게 만듭니다.</p>
<h3 id="mapping-behavior">Mapping Behavior</h3>
<p>NAT가 어떻게 일련의 주소들을 다른 주소로 바꾸는지에 대해선 주로 세가지 동작 유형이 있습니다.
일반적으로 이 동작 유형들은(아래의 필터링도 그렇지만) 목적지 주소에 의존치 않거나, 목적지 주소의 IP에만 의존하거나, 목적지 주소의 IP와 Port 모두에 의존하는 세가지의 경우에 따라 나뉩니다. Mapping Behavior의 경우엔 조건에 따라 포트가 바뀌죠.</p>
<p>이 글에선 굳이 Mapping Behavior를 자세히 다룰 필요가 없으므로 더 자세히 알고 싶다면 <a href="https://www.netmanias.com/ko/?m=view&amp;id=blog&amp;no=5833">이 포스트</a> 또는 <a href="https://datatracker.ietf.org/doc/html/rfc4787">RFC4787 문서</a>를 읽어보세요.</p>
<h3 id="filtering-behavior">Filtering Behavior</h3>
<p>NAT는 주소를 다른 주소로 바꾸는 기술이지만, 또한 수신한 트레픽의 주소에 따라 트레픽을 드랍하는 일종의 방화벽 역할도 겸합니다. 이를 Filtering Behavior라 하여 수신지 출처 주소에 무관히 허용하거나, 정해진 IP만 허용하거나, 정해진 IP와 port만 허용하는 조건들이 있습니다.</p>
<p>바로 이 Filtering Behavior가 무조건(Endpoint Independent Filtering)이지 않을 때, P2P 통신을 하기 위해 상대 주소에 패킷을 보낼려 하면 패킷이 도달하지 못하고 드랍되는 경우가 발생합니다.</p>
<blockquote>
<p>NATs on the path allocate temporary public endpoints for outgoing connections, and translate the addresses and port numbers in packets comprising those sessions, while generally blocking all incoming traffic unless otherwise specifically configured.
 경로의 NAT는 나가는 연결에 임시 공용 엔드포인트를 할당하고 해당 세션을 구성하는 패킷의 주소와 포트 번호를 변환하는 한편, <strong>특별히 구성하지 않는 한 일반적으로 들어오는 모든 트래픽을 차단합니다.</strong>
<code>DeepL에 의해 번역됨</code>
<a href="https://bford.info/pub/net/p2pnat/">https://bford.info/pub/net/p2pnat/</a> Introduction - 1번째 문단</p>
</blockquote>
<p>Filtering Behaviour는 구체적으로 세가지 유형이 있으며 대체로 NAT를 통해 트레픽을 송신한 주소에 대해 허용한단 특징이 있습니다.</p>
<ul>
<li><strong>Endpoint-Independent Filtering</strong>: 어디서든지 트레픽이 올 수 있습니다.</li>
<li><strong>Address-Dependent Filtering</strong>: 수신한 주소의 IP에서만 올 수 있습니다.</li>
<li><strong>Address and Port-Dependent Filtering</strong>: 수신한 주소의 IP/Port에서만 올 수 있습니다.</li>
</ul>
<p><a href="https://bford.info/pub/net/p2pnat">Bryan Ford의 Peer-to-Peer Communication Across Network Address Translators</a>에선 이와 같이 조건부로 트레픽을 드랍하는 것에 대해 outbound NAT라 말하며 이 NAT가 들어오는 트래픽이 이 사설 네트워크로부터 시작된 &quot;세션&quot;이 아닌 이상 드랍한다고 말합니다.</p>
<blockquote>
<p> Outbound NAT by default allows only outbound sessions to traverse the NAT: incoming packets are dropped unless the NAT identifies them as being part of an existing session initiated from within the private network.</p>
</blockquote>
<h2 id="nat를-거쳐-p2p-통신하기">NAT를 거쳐 P2P 통신하기</h2>
<blockquote>
<p>Outbound NAT conflicts with peer-to-peer protocols because when both peers desiring to communicate are “behind” (on the private network side of) two different NATs, whichever peer tries to initiate a session, the other peer&#39;s NAT rejects it. 
아웃바운드 NAT는 피어 투 피어 프로토콜과 충돌하는데, 이는 통신하려는 두 피어가 서로 다른 두 NAT의 &quot;뒤에&quot;(사설 네트워크 쪽에) 있을 때 어느 쪽이 세션을 시작하려고 하면 다른 쪽의 NAT가 이를 거부하기 때문입니다. 
<code>DeepL에 의해 번역됨</code>
<a href="https://bford.info/pub/net/p2pnat">https://bford.info/pub/net/p2pnat</a></p>
</blockquote>
<p>그리고 이 Filtering Behavior의 특성때문에 만약 NAT가 EIF(Endpoint-Independent Filtering)가 아니라면 무조건 NAT 자신이 페킷을 보내야만 보낸 곳으로부터 페킷을 받을 수 있으니 같은 유형의 NAT는 서로가 서로에게 세션을 걸 수 없는 일이 벌어집니다.</p>
<h3 id="릴레이relay">릴레이(Relay)</h3>
<p>이 문제를 해결할 수 있는 가장 간단한 방법은 그냥 언제든지 통신이 가능한 중개 서버를 거치는 것입니다. 두 클라이언트가 서로 직접 통신하지 않고, 중개 서버를 통해 통신한다면 그것은 P2P가 아니라 P2P를 위한 Client/Server에 불과합니다. 네, 느립니다.</p>
<h3 id="connect-reversal">Connect Reversal</h3>
<p>애초에 이 문제는 &quot;같은 유형의 NAT&quot;는 서로가 서로에게 세션을 걸 수 없어서 생긴 문제입니다.
그러니깐 둘 중 한쪽이라도 NAT가 없는 - 공인 IP를 가지고 있는 상태라면 NAT가 있는 쪽에서 그 공인 IP로 페킷을 보내어 세션을 시작하면 됩니다.</p>
<h3 id="홀-펀칭">홀 펀칭</h3>
<p>앞선 두 방법은 확실하지만 큰 대가가 있거나 제한이 있습니다.</p>
<p><a href="https://www.netmanias.com/ko/post/blog/6263/nat-network-protocol-p2p/p2p-nat-nat-traversal-technic-rfc-5128-part-2-udp-hole-punching">넷메니아즈의 P2P와 NAT: NAT 통과 기법 소개 (RFC 5128) - 2편: UDP Hole Punching 포스트</a>가 이 기술을 쉽게 설명하고 있습니다. (뿐만이 아니라 상술한 개념들에 대해서도 모두 자세히 설명하고 있습니다.)</p>
<p><a href="https://www.netmanias.com/ko/post/blog/6263/nat-network-protocol-p2p/p2p-nat-nat-traversal-technic-rfc-5128-part-2-udp-hole-punching"><img src="https://velog.velcdn.com/images/sharlotte_04/post/a7694e3d-e13d-4017-bde1-e56d4525e7e0/image.png" alt=""></a></p>
<p>앞서 말했듯이 NAT는 filtering behavior라 하여 NAT가 수신받은 페킷의 출발지가 송신했던 목적지와 어떻게 같냐에 따라 페킷을 드랍하는 동작을 가지고 있습니다. 즉, 내부 사설망으로부터 시작된 세션의 페킷이 아니면 외부 페킷을 받지 않겠단 것입니다.
<strong>그러나 상대 호스트의 NAT가 내 NAT로부터 도착한 페킷을 드랍했더라도 내 NAT는 상대 호스트에 대한 필터링 규칙이 등록되어서 상대 호스트로부터 오는 패킷을 허용할 수 있게 됩니다.</strong></p>
<p>즉,</p>
<ol>
<li>두 호스트가 중계 서버에 자신의 내부/외부 IP를 보냅니다.</li>
<li>두 호스트가 각자 상대의 호스트와 연결하기 위해 중계 서버로부터 서로의 IP를 가져옵니다.</li>
<li>가져온 IP로 홀펀칭을 시도합니다.</li>
</ol>
<p>3-1. 위 사진의 오른쪽에서 Host A가 먼저 페킷을 보내어 NAT A에게 구멍을 만들고, 
3-2. HOST B가 NAT B를 거쳐 그 구멍을 통해 NAT A를 통과하여 HostA에 페킷을 건내는 동시에 
3-3. NAT B에 구멍을 만들어서 Host A가 드디어 페킷을 넘길 수 있게 만듭니다.
3-4. 그럼 Host A도 Host B에 페킷을 보내게 되며 P2P 통신이 성공하게 됩니다.</p>
<h4 id="단점">단점</h4>
<p>이 방법은 서로 다른 NAT 뒤에 숨은 두 기기들이 P2P를 실현할 수 있단 점에서 강력합니다.
그러나 몇가지 단점이 있습니다.</p>
<ol>
<li>타임아웃을 막기 위해 keep-alive 패킷을 지속적으로 보내야 합니다.<blockquote>
</blockquote>
</li>
</ol>
<p><strong>Since the UDP transport protocol provides NATs with no reliable, application-independent way to determine the lifetime of a session crossing the NAT</strong>, most NATs simply associate an idle timer with UDP translations, closing the hole if no traffic has used it for some time period. There is unfortunately no standard value for this timer: some NATs have timeouts as short as 20 seconds.</p>
<blockquote>
</blockquote>
<p><strong>UDP 전송 프로토콜은 NAT를 통과하는 세션의 수명을 확인할 수 있는 안정적이고 애플리케이션에 독립적인 방법을 NAT에 제공하지 않기 때문</strong>에 대부분의 NAT는 단순히 유휴 타이머를 UDP 변환에 연결하여 일정 기간 동안 트래픽이 사용되지 않으면 구멍을 닫습니다. 안타깝게도 이 타이머에 대한 표준 값은 없으며, 일부 NAT는 20초의 짧은 시간 제한을 설정하기도 합니다. </p>
<blockquote>
<p><code>DeepL에 의해 번역됨</code>
<a href="https://bford.info/pub/net/p2pnat/">https://bford.info/pub/net/p2pnat/</a></p>
</blockquote>
<ol start="2">
<li>비 EIM-NAT에 대해선 사용할 수 없습니다.</li>
</ol>
<h5 id="ymmetric-nat">ymmetric NAT</h5>
<blockquote>
<p>UDP hole punching will not work with symmetric NAT devices (also known as bi-directional NAT) which tend to be found in large corporate networks. In symmetric NAT, the NAT&#39;s mapping associated with the connection to the well known STUN server is restricted to receiving data from the well-known server, and therefore the NAT mapping the well-known server sees is not useful information to the endpoint. 
UDP 홀 펀칭은 대규모 기업 네트워크에서 흔히 볼 수 있는 대칭형 NAT 장치(양방향 NAT라고도 함)에서는 작동하지 않습니다. 대칭형 NAT에서는 잘 알려진 STUN 서버와의 연결과 관련된 NAT의 매핑이 잘 알려진 서버로부터 데이터를 수신하는 것으로 제한되므로 잘 알려진 서버가 보는 NAT 매핑은 엔드포인트에 유용한 정보가 되지 못합니다. 
<code>DeepL에 의해 번역됨</code>
<a href="https://www.wikiwand.com/en/UDP_hole_punching">https://www.wikiwand.com/en/UDP_hole_punching</a></p>
</blockquote>
<p>Symmetric NAT는 RFC3489에서 정의된 APDM, APDF(Address &amp; Port Dependent Mapping/Filtering) 동작을 가지는 NAT 유형입니다.
이 NAT는 동작에서 짐작하듯이 <strong>하나의 IP가 오직 하나의 IP와만 통신이 가능</strong>하게 규칙이 정해져 있습니다.
그러니깐 앞서 설명한 홀펀칭의 단계에서, 애초에 Host A가 중계 서버로 보낸 외부 IP(155.99.25.11:62000)와 실제로 Host B가 보낼 Host A의 외부 IP(155.99.25.11:62001)와 다르다는겁니다.
중계 서버에서 가져온 Host A의 IP인 155.99.25.11:62000는 중계 서버 IP인 100.100.100:12341 에서만 보낼 수 있으니 <strong>Host B의 IP인 132.12.33.11:8000 에선 155.99.25.11:62000 로 페킷을 보낼 수 없단겁니다. 100.100.100:12341 전용이니깐요.</strong></p>
<hr>
<h5 id="eim-nat">EIM-NAT</h5>
<p><a href="https://www.netmanias.com/ko/?m=view&amp;id=blog&amp;no=5847"><img src="https://velog.velcdn.com/images/sharlotte_04/post/f364eb41-6f6b-4437-bcec-583115b2def1/image.png" alt=""></a>
새로 개정된 RFC 5780 정의에선 총 9가지(Mapping과 Filtering로 만든 가능한 모든 경우의 수)의 NAT 유형들을 소개합니다.
사실 Mapping가 보낼 호스트가 다를 때마다 포트가 달라지는 동작이라면, 그 호스트에 매칭될 내 포트를 <em>미리</em> 중계 서버에 보내줘야 한단건데... 타임 스톤도 아니고 미래에 NAT가 무슨 포트로 내부 IP를 바꿀줄 알고 그 포트를 미리 압니까?
<strong>그러므로 비-EIM-NAT들은 홀펀칭 기술을 사용할 수 없습니다.</strong></p>
<p>참고로 많은 자료들이 홀펀칭을 EIM에 한정치 않고 Symmetric NAT에선 작동하지 않단 말 위주로 이야기를 하는데, 이는 위 사진의 RFC-3489에서 정의된 NAT 유형의 이름들 중 EIM-NAT와 Symmetric NAT가 아닌 NAT들은 이름이 없기 때문이라고 봅니다...</p>
<hr>
<h3 id="대체-솔루션">대체 솔루션</h3>
<p>따라서, 홀펀칭만으로 P2P 통신을 해결하는 것은 무리가 있습니다. 오히려 P2P 통신에서 홀펀칭으로 해결할 수 없는 환경에 대해 다른 일반적인/베타적인 해결법을 도입하는게 맞습니다.</p>
<h4 id="upnp">UPnP</h4>
<p><a href="https://www.wikiwand.com/ko/%EC%9C%A0%EB%8B%88%EB%B2%84%EC%84%A4_%ED%94%8C%EB%9F%AC%EA%B7%B8_%EC%95%A4_%ED%94%8C%EB%A0%88%EC%9D%B4">유니버설 플러그 앤 플레이</a>는 포트포워딩을 자동화하는 프로토콜인 SSDP를 이용하고 있는 범용 표준 프로토콜입니다.
upnp 프로토콜은 기본적으로 켜져 있으며 유저에 의해 꺼질 수 있습니다.</p>
<h4 id="relay">Relay</h4>
<p>P2P 구현에 있어서 가장 최후의 보루에 위치한 방법.
말이 P2P지 clients-server를 엮어다 놓은 것에 불과합니다.
p2p의 장점을 내려놓게 되겠지만(특히 속도라던가..속도라던가) 100% 성공한다는 무적의 안전성을 지닙니다. 이게 안되면 뭘 해도 그 서버와 통신이 안되는겁니다.</p>
<h2 id="결론">결론</h2>
<p>NAT를 넘어 P2P 통신을 하는 방법에서 희귀도를 매긴다면 <strong>Connect Reversal &lt; Hole Punching &lt; UPnP &lt; Relay</strong>가 된다.
실제로 P2P를 구현하게 된다면 이 일련의 방법들을 희귀도 순으로 시도하는걸 상상해볼 수 있겠다. (connect reversal는 한 기기가 공인 ip여야 하니 그냥 제외할수도 있겠다)</p>
<p><img src="https://velog.velcdn.com/images/sharlotte_04/post/fe994a74-39c6-45b5-913a-31745e270c89/image.png" alt=""></p>
<h2 id="ref">Ref.</h2>
<p><a href="https://datatracker.ietf.org/doc/html/rfc4787">RFC 4787 - Network Address Translation (NAT) Behavioral Requirements for Unicast UDP</a>
<a href="https://bford.info/pub/net/p2pnat/">Peer-to-Peer Communication Across Network Address Translators</a>
<a href="https://www.enjoyalgorithms.com/blog/peer-to-peer-networks">Peer-to-Peer (P2P) Architecture</a>
<a href="https://yeonduing.tistory.com/12">Tistory - 분산시스템) 시스템 아키텍쳐(2) - 비중앙 집중식</a>
<a href="https://medium.com/tashi-gg/peer-to-peer-gaming-9991600c6707">Medium - Peer-to-Peer Gaming</a>
<a href="http://post.procademy.co.kr/archives/312">온라인 게임 Peer to Peer (P2P) 방식과 Client/Server (CS)</a>
<a href="https://stackoverflow.com/questions/16908714/how-do-you-create-a-peer-to-peer-connection-without-port-forwarding-or-a-centera">StackOverflow - How do you create a peer to peer connection without port forwarding or a centeralized server?</a></p>
<p><a href="https://cjwoov.tistory.com/5">Tistory - [UDP 홀펀칭(Hole Punching)] - UDP 홀펀칭(1/2)</a>
<a href="https://cjwoov.tistory.com/6">Tistory - [UDP 홀펀칭(Hole Punching)] - UDP 홀펀칭(2/2)</a>
<a href="https://program-factory.tistory.com/7">Tistory - 3. HOLE PUNCHING이란</a>
<a href="https://support.dh2i.com/docs/Archive/kbs/general/understanding-different-nat-types-and-hole-punching/">Understanding Different NAT Types and Hole-Punching</a>
<a href="https://www.wikiwand.com/en/UDP_hole_punching">Wikipedia - UDP hole punching</a>
<a href="https://medium.com/@girish1729/nat-with-udp-hole-punching-39d952cbc075">Medium - NAT with UDP hole punching</a>
<a href="https://stackoverflow.com/questions/2387835/fully-decentralized-p2p">StackOverflow - Fully Decentralized P2P?</a></p>
<p><a href="https://www.wikiwand.com/ko/%EB%84%A4%ED%8A%B8%EC%9B%8C%ED%81%AC_%EC%A3%BC%EC%86%8C_%EB%B3%80%ED%99%98?summary=true">Wikipedia - 네트워크 주소 변환</a>
<a href="https://m.blog.naver.com/ppp7009/70156676686">Naver Blog - 방화벽 NAT(Network Address Translation)</a>
<a href="https://brunch.co.kr/@sangjinkang/61">Brunch - NAT는 무엇이며, 왜 필요한 것인가?</a>
<a href="https://velog.io/@combi_areum/14.-NAT%EC%99%80-%ED%8F%AC%ED%8A%B8%ED%8F%AC%EC%9B%8C%EB%94%A9">Velog - 14.-NAT와-포트포워딩</a>
<a href="https://velog.io/@taypark/%EB%84%A4%ED%8A%B8%EC%9B%8C%ED%81%AC-%EA%B8%B0%EB%B3%B8-NAT">Velog - 네트워크 기본: NAT</a>
<a href="https://forum.photonengine.com/discussion/1893/public-ip-address-of-client">photon forum - public ip address of client</a>
<a href="https://www.netmanias.com/ko/?m=view&amp;id=blog&amp;no=5826">Netmanias - NAT (Network Address Translation) 소개 (RFC 3022/2663)</a>
<a href="https://www.netmanias.com/ko/?m=view&amp;id=blog&amp;no=5833">Netmanias - NAT 장비는 이렇게 만들어야 하는데... (RFC 4787) - 1편: Mapping Behavior</a>
<a href="https://www.netmanias.com/ko/?m=view&amp;id=blog&amp;no=5839">Netmanias - NAT 장비는 이렇게 만들어야 하는데... (RFC 4787) - 2편: Filtering Behavior</a>
<a href="https://www.netmanias.com/ko/?m=view&amp;id=blog&amp;no=5841">Netmanias - NAT 장비는 이렇게 만들어야 하는데... (RFC 4787) - 3편: Deterministic Properties</a>
<a href="https://www.netmanias.com/ko/?m=view&amp;id=blog&amp;no=5847">Netmanias - STUN(RFC 3489)과 STUN(RFC 5389/5780)의 차이</a>
<a href="https://www.netmanias.com/ko/?m=view&amp;id=blog&amp;no=6264">Netmanias - P2P와 NAT: NAT 통과 기법 소개 (RFC 5128) - 1편: Relaying &amp; Connection Reversal</a>
<a href="https://www.netmanias.com/ko/?m=view&amp;id=blog&amp;no=6263">Netmanias - P2P와 NAT: NAT 통과 기법 소개 (RFC 5128) - 2편: UDP Hole Punching</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[스크롤바 이쁘게 스타일링하기]]></title>
            <link>https://velog.io/@sharlotte_04/%EC%8A%A4%ED%81%AC%EB%A1%A4%EB%B0%94-%EC%9D%B4%EC%81%98%EA%B2%8C-%EC%8A%A4%ED%83%80%EC%9D%BC%EB%A7%81%ED%95%98%EA%B8%B0</link>
            <guid>https://velog.io/@sharlotte_04/%EC%8A%A4%ED%81%AC%EB%A1%A4%EB%B0%94-%EC%9D%B4%EC%81%98%EA%B2%8C-%EC%8A%A4%ED%83%80%EC%9D%BC%EB%A7%81%ED%95%98%EA%B8%B0</guid>
            <pubDate>Tue, 30 Jan 2024 15:42:20 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/sharlotte_04/post/6bb45fd2-1446-48f3-829c-63f2ba7d55b3/image.png" alt=""></p>
<p><a href="https://developer.mozilla.org/en-US/docs/Web/CSS/::-webkit-scrollbar">-webkit-scrollbar</a>는 스크롤바 스타일링을 가능케 해준다.
이걸로 IE마냥 구시대적이고 꾸진 스크롤바 스타일을 모던하고 깔끔하게 만들 수 있다.
가령 </p>
<pre><code class="language-ts">globalStyle(&quot;::-webkit-scrollbar&quot;, {
  backgroundColor: &quot;transparent&quot;,
});</code></pre>
<p>스크롤바 배경을 투명하게 만들거나,</p>
<pre><code class="language-ts">globalStyle(&quot;::-webkit-scrollbar-thumb&quot;, { 
  borderRadius: &quot;99999px&quot;,
  backgroundColor: themeVars.color.gray[200],
});</code></pre>
<p>스크롤바를 둥글게 만들고, 색깔을 바꿀 수도 있다.</p>
<p>아니면 여기서 더 나아가서 <a href="https://stackoverflow.com/questions/21684101/css-vertical-scrollbar-padding-left-right-in-ul-possible">스크롤바를 화면 경계에서 약간 멀리 둘 수 있다</a>. 
이것은 약간의 기믹으로, <code>background-clip</code>를 <code>padding-box</code>로 만들어서 콘텐츠의 크기에서 외곽선을 잘려나게 하고, 외곽선 색을 투명색으로 정하면 마치 패딩이 생긴 것과 같은 효과가 생긴다.</p>
<p>그래서 외곽선을 투명색 외의 색상으로 정하면 아래와 같이 외곽선이 느려난다.
<img src="https://velog.velcdn.com/images/sharlotte_04/post/b09561a0-72e4-4f24-9a07-880ed8839ca3/image.png" alt=""></p>
<p>아래 코드는 이 기믹을 추상화한 것으로, 스크롤바의 간격과 폭을 정하면 세로 및 가로 스크롤에 적용된다.</p>
<pre><code class="language-ts">const SCROLLBAR_WIDTH = &quot;8px&quot;;
const SCROLLBAR_GAP = &quot;16px&quot;;

globalStyle(&quot;::-webkit-scrollbar&quot;, {
  // 모종의 이유로, 이 선택자에 스타일링을 하지 않으면 스크롤바 스타일일이 무조건 기본 스타일이 됩니다.
  backgroundColor: &quot;transparent&quot;,
});

globalStyle(&quot;::-webkit-scrollbar:vertical&quot;, {
  width: `calc(${SCROLLBAR_GAP} * 2 - ${SCROLLBAR_WIDTH})`,
});

globalStyle(&quot;::-webkit-scrollbar:horizontal&quot;, {
  height: `calc(${SCROLLBAR_GAP} * 2 - ${SCROLLBAR_WIDTH})`,
});

globalStyle(&quot;::-webkit-scrollbar-thumb&quot;, {
  border: `calc(${SCROLLBAR_GAP} - ${SCROLLBAR_WIDTH}) solid transparent`,
  backgroundClip: &quot;padding-box&quot;,
  borderRadius: &quot;99999px&quot;,
  backgroundColor: themeVars.color.gray[200],
});</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[hidden(?) feature in velog]]></title>
            <link>https://velog.io/@sharlotte_04/hidden-feature-velog</link>
            <guid>https://velog.io/@sharlotte_04/hidden-feature-velog</guid>
            <pubDate>Sat, 02 Dec 2023 22:52:02 GMT</pubDate>
            <description><![CDATA[<h1 id="katex">KaTeX?</h1>
<p><a href="https://katex.org">https://katex.org</a></p>
<blockquote>
<p>The fastest math $\bold \color{#329894}{fastest}$ library for the web.
<code>&gt;The fastest math $\bold \color{#329894}{fastest}$ library for the web.</code></p>
</blockquote>
<h1 id="syntax">Syntax</h1>
<p><a href="https://jamiebutler.notion.site/KaTeX-Formatting-8f2a6470b9d34fa98be19ad25dfc587d">노션 참고</a>
<a href="https://katex.org/docs/supported#style-color-size-and-font">공문도 있음</a></p>
<p>$\dot{a}$</p>
<p><code>$\mathsf \color{#05D6B6}{KaTexFormat}$</code>
$\mathsf \color{#05D6B6}{KaTexFormat}$</p>
<p>$\color{pink}{pink}$
$\color{pink}{분홍색}$</p>
<p>$\color{tomato} \fcolorbox{tomato}{white}{ㅂ}$ㅏㄴ블록
$\color{tomato} \fcolorbox{tomato}{white}{블록}$
$\color{tomato} \colorbox{white}{이거 재밌네요}$</p>
<p><del>취소선</del>
$\cancel{오른쪽 멋진취소선!}$
$\bcancel{왼쪽 멋진취소선!}$
$\xcancel{더멋진취소선!}$</p>
<pre><code>$\dot{a}$

`$\mathsf \color{#05D6B6}{KaTexFormat}$`
$\mathsf \color{#05D6B6}{KaTexFormat}$

$\color{pink}{pink}$
$\color{pink}{분홍색}$

$\color{tomato} \fcolorbox{tomato}{white}{ㅂ}$ㅏㄴ블록
$\color{tomato} \fcolorbox{tomato}{white}{블록}$
$\color{tomato} \colorbox{white}{이거 재밌네요}$

~~취소선~~
$\cancel{오른쪽 멋진취소선!}$
$\bcancel{왼쪽 멋진취소선!}$
$\xcancel{더멋진취소선!}$</code></pre><p>$
\Large \def\arraystretch{1.5}
   \begin{array}{c:c:c:c:c}
   \mathsf {이건} &amp; \mathsf {+} &amp; \mathsf {굉장히} &amp; \mathsf {+} &amp; \mathsf {혁명적입니다.} \ \hline
\end{array}
$</p>
<pre><code>$
\Large \def\arraystretch{1.5}
   \begin{array}{c:c:c:c:c}
   \mathsf {이건} &amp; \mathsf {+} &amp; \mathsf {굉장히} &amp; \mathsf {+} &amp; \mathsf {혁명적입니다.} \\ \hline
\end{array}
$</code></pre><p><span style="color:#2EB6FF;">원래 span element로도 색칠은 가능했습니다.</span>
<span style="color:#2EB6FF;font-weight:bold">원래 span element로도 색칠은 가능했습니다.</span>
$\color{#2EB6FF} {하지만 이건 어떤가요?}$
$\color{#2EB6FF} \bold {하지만 이건 어떤가요?}$</p>
<pre><code>&lt;span style=&quot;color:#2EB6FF;&quot;&gt;원래 span element로도 색칠은 가능했습니다.&lt;/span&gt;
&lt;span style=&quot;color:#2EB6FF;font-weight:bold&quot;&gt;원래 span element로도 색칠은 가능했습니다.&lt;/span&gt;
$\color{#2EB6FF} {하지만 이건 어떤가요?}$
$\color{#2EB6FF} \bold {하지만 이건 어떤가요?}$</code></pre><h3 id="text-colors">Text Colors</h3>
<p>$\color{blueviolet}{blueviolet}$
$\color{aquamarine}{aquamarine}$
$\color{brown}{brown}$
$\color{cadetblue}{cadetblue}$
$\color{darkorchid}{darkorchid}$
$\color{chocolate}{chocolate}$
$\color{cornflowerblue}{cornflowerblue}$
$\color{cyan}{cyan}$
$\color{darkgray}{darkgray}$
$\color{darkslateblue}{darkslateblue}$
$\color{forestgreen}{forestgreen}$
$\color{fuchsia}{fuchsia}$
$\color{gold}{gold}$
$\color{goldenrod}{goldenrod}$
$\color{gray}{gray}$
$\color{green}{green}$
$\color{greenyellow}{greenyellow}$
$\color{lavender}{lavender}$
$\color{lightgray}{lightgray}$
$\color{limegreen}{limegreen}$
$\color{magenta}{magenta}$
$\color{maroon}{maroon}$
$\color{mediumpurple}{mediumpurple}$
$\color{midnightblue}{midnightblue}$
$\color{navajowhite}{navajowhite}$
$\color{olive}{olive}$
$\color{orange}{orange}$
$\color{orangered}{orangered}$
$\color{orchid}{orchid}$
$\color{pink}{pink}$
$\color{plum}{plum}$
$\color{purple}{purple}$
$\color{red}{red}$
$\color{royalblue}{royalblue}$
$\color{salmon}{salmon}$
$\color{seagreen}{seagreen}$
$\color{silver}{silver}$
$\color{skyblue}{skyblue}$
$\color{springgreen}{springgreen}$
$\color{tan}{tan}$
$\color{thistle}{thistle}$
$\color{tomato}{tomato}$
$\color{turquoise}{turquoise}$
$\color{violet}{violet}$
$\color{yellow}{yellow}$
$\color{yellowgreen}{yellowgreen}$</p>
<h3 id="background-colors">Background Colors</h3>
<p>$\colorbox{aquamarine}{1234567890}$
$\colorbox{cadetblue}{1234567890}$
$\colorbox{cornflowerblue}{1234567890}$
$\colorbox{gold}{1234567890}$
$\colorbox{lightgray}{1234567890}$
$\colorbox{pink}{1234567890}$
$\colorbox{plum}{◃ use any of the colors to the left}$</p>
<p>$\fbox {add a simple border}$</p>
<p>$\color{lavender} \fcolorbox{midnightblue}{orchid}{use color boxes with borders}$
$\color{tomato} \fcolorbox{tomato}{white}{color borders and text only}$
$\color{midnightblue} \fcolorbox{green}{greenyellow}{color backgrounds and text}$
$\mathsf{} \color{navajowhite} \fcolorbox{darkslateblue}{orange}{experiment with combinations}$
$\color{pink} \fcolorbox{pink}{#8b4e96}{have fun with symbols ♠ ♡ ♣ ♢}$</p>
<h3 id="fonts">Fonts</h3>
<p>  $\texttt {Typewriter}$
  $\mathscr {Typewriter}$</p>
<p>  $\mathsf {Sans~Serif}$</p>
<p>  $\mathbb {CHALKBOARD}$</p>
<p>  $\mathrm {Roman}$</p>
<p>  $\mathcal {Caligraphic}$</p>
<pre><code>  $\texttt {Typewriter}$
  $\mathscr {Typewriter}$

  $\mathsf {Sans~Serif}$

  $\mathbb {CHALKBOARD}$

  $\mathrm {Roman}$

  $\mathcal {Caligraphic}$</code></pre><h3 id="sizes">Sizes</h3>
<p>$\tiny {Tiny}$</p>
<p>$\scriptsize Script~Size$</p>
<p>$\footnotesize Footnote~Size$</p>
<p>$\small Small$</p>
<p>$\normalsize Normal~Size$</p>
<p>$\large Large$</p>
<p>$\Large Large$</p>
<p>$\LARGE Large$</p>
<p>$\huge Huge$</p>
<p>$\Huge Huge$</p>
<h3 id="text-decorations">Text Decorations</h3>
<p>$\utilde{swooshy~underline}$</p>
<p>$\underrightarrow{underline<del>right</del>arrow}$</p>
<p>$\underleftarrow{underline<del>left</del>arrow}$</p>
<p>$\underline{underline}$</p>
<p>$\undergroup{group1}$   $\undergroup{group2}$   $\undergroup{group3}$</p>
<p>$\sout{strikethrough}$</p>
<p>$\xcancel{Xcancel}$</p>
<h1 id="3rd-party-service">3rd party service</h1>
<p>상남자 판독기
!youtube[pCOBmmJARPE]</p>
<hr>
<p><a href="https://velog.io/@velog/katex-and-embed-support">사실 주인장은 알렸었음</a>
!twitter[velog_official/status/1238490689752535045]</p>
<hr>
<p>코드 모래장도 있음</p>
<p>!codesandbox[cool-mountain-tffvw?fontsize=14&amp;hidenavigation=1&amp;theme=dark]</p>
<hr>
<p>코드팬도 됨 그런데 이게 더 이쁜듯</p>
<p>!codepen[aaroniker/embed/NWqyego?height=265&amp;theme-id=light&amp;default-tab=css,result]</p>
<p>아 그런데 수정마다 리로드하니 랙오짐</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[자식은 부모의 보호된 맴버에 접근할 수 없습니다.]]></title>
            <link>https://velog.io/@sharlotte_04/%EC%9E%90%EC%8B%9D%EC%9D%80-%EB%B6%80%EB%AA%A8%EC%9D%98-%EB%B3%B4%ED%98%B8%EB%90%9C-%EB%A7%B4%EB%B2%84%EC%97%90-%EC%A0%91%EA%B7%BC%ED%95%A0-%EC%88%98-%EC%97%86%EC%8A%B5%EB%8B%88%EB%8B%A4</link>
            <guid>https://velog.io/@sharlotte_04/%EC%9E%90%EC%8B%9D%EC%9D%80-%EB%B6%80%EB%AA%A8%EC%9D%98-%EB%B3%B4%ED%98%B8%EB%90%9C-%EB%A7%B4%EB%B2%84%EC%97%90-%EC%A0%91%EA%B7%BC%ED%95%A0-%EC%88%98-%EC%97%86%EC%8A%B5%EB%8B%88%EB%8B%A4</guid>
            <pubDate>Tue, 14 Nov 2023 10:17:26 GMT</pubDate>
            <description><![CDATA[<h2 id="자식은-부모의-보호된-맴버에-접근할-수-있습니다">자식은 부모의 보호된 맴버에 접근할 수 있습니다.</h2>
<h3 id="사실-아닙니다">사실 아닙니다.</h3>
<p><img src="https://velog.velcdn.com/images/sharlotte_04/post/e09a13ee-4e5a-49d5-8d54-da3e9299a40d/image.png" alt="">
Java, C#, TypeScript 이 세가지 언어에서 동일하게 구현된 코드들은 동일한 케이스에서 유사한 에러를 발생시킵니다.</p>
<p>일반적으로, 자식 클래스에선 부모 클래스의 <code>protected</code>맴버에 접근할 수 있습니다. 그러나 이는 상반된 결과를 내포하는 생략이 들어간 말입니다.
자식 클래스에선 부모 클래스의 <strong>자신</strong>의 <code>protected</code>맴버에 접근할 수 있지만, 부모 클래스의 <strong>다른 인스턴스</strong>의 <code>protected</code>맴버에 접근할 수 없습니다.<img src="https://velog.velcdn.com/images/sharlotte_04/post/4ba8a780-98dc-4cfe-8f8e-9a729ff13c72/image.png" alt=""></p>
<p><a href="https://stackoverflow.com/questions/45025041/unable-to-access-protected-method-in-derived-class">MSDN에 따르면 <code>protected</code>의 의미</a> 는 </p>
<blockquote>
<p><code>protected</code> - Access is limited to the <strong>containing class</strong> or <strong>types derived from the containing class</strong>.</p>
</blockquote>
<p>이므로 <em>&quot;어느 한 클래스에서 자신 또는 파생 클래스의 <code>protected</code>맴버에 접근할 수 있다&quot;</em> 라는 원칙으로 정리할 수 있습니다.</p>
<p>즉, 부모 클래스의 <strong>다른 인스턴스</strong>의 <code>protected</code>맴버에 접근할 수 없는건 두가지 관점에서 해석될 수 있습니다.</p>
<ul>
<li>타입의 관점에서: 인스턴스의 형식이 자신 또는 파생의 형식이 아니므로 <code>protected</code>로 보호되어 접근할 수 없습니다.</li>
<li>안전성의 관점에서: 런타임에 이 인스턴스가 자신 또는 파생의 인스턴스인지 아닌지가 <a href="https://learn.microsoft.com/ko-kr/dotnet/csharp/language-reference/compiler-messages/cs1540?f1url=%3FappId%3Droslyn%26k%3Dk(CS1540)">불확실하므로 컴파일러가 사전에 차단합니다</a>.</li>
</ul>
<p>따라서 자식 클래스에서 부모 클래스 타입의 <code>protected</code>맴버에 접근하고 에러를 맞이하는 것은 근본적으로 위 원칙에 해당하지 않았지만 그렇게 되길 기대하는 개발자의 오인으로 인해 생긴 문제이므로 위 원칙에 해당되도록 타입 캐스팅을 하면 됩니다.</p>
<pre><code class="language-cs">class Super
{
    protected float prot;
    private float priv;
}

class Sub2 : Super
{
    public void SubMethod(Sub1 sub1, Sub2 sub2, Super super)
    {
         // NOT error, because of private access&#39;s limitation
        Debug.Log(sub2.prot);

        // error, because of private access&#39;s limitation
        Debug.Log(super.priv);

        // error, not because of protected access&#39;s limitation, but because of type of a qualifier 
        Debug.Log(super.prot); 

        // because the error is not because of protected access, can be solved via upcasting
        Debug.Log(((Sub2) super).prot); 
    }
}</code></pre>
<p><img src="https://velog.velcdn.com/images/sharlotte_04/post/221c9753-3f52-4467-abac-3c7f21bfab24/image.png" alt="">
흥미로운 점은 이것이 <code>protected</code>뿐만이 아니라 <code>private</code>에도 해당된다는 것입니다.
따라서 이 케이스가 유의미한 점은 접근 제한자를 단순히 &quot;자신, 자식, 패키지(네임스페이스), 전역&quot;으로 <strong>사분등하는 일반적인 이해의 허점을 찌른다</strong>는 것입니다. 즉, 이 일반적인 이해에선 &quot;자신&quot;과 &quot;자식&quot;을 &quot;자신(의 인스턴스)&quot;, &quot;자식(의 인스턴스)&quot;로 생략하지만, 접근을 하는 장소인 클래스에도 해당이 된다는 뜻입니다. 이 케이스를 공유했을 때, 경험 많은 개발자들은 이를 알고 있거나 논리적으로 추측했지만 초보자들은 이에 당황했습니다.
이 일반적인 이해로 교육하는 것에 정면으로 비판하는게 아닙니다 - 다만 접근 제한자를 생각할 때 추가로 클래스까지 고려된다는걸 알려줘야 한다는 것입니다. (언젠가 밟게 될 억까 지뢰를 피하기 위해서라도)</p>
<p>실용적으로 보았을 때: 이 케이스로 얻을 교훈은 <em>&quot;자식 클래스에서 다른 부모 인스턴스의 보호된 맴버에 접근할 수 없다&quot;</em> 입니다. 우리가 늘 써온 <code>super.member</code> 나 <code>base.member</code> 따위의 코드들은 다른 부모 인스턴스가 아니라 바로 자기 자신이 부모 클래스에 접근하기 위한 키워드들입니다.</p>
<h2 id="side-note-this를-upcast한다면">side note: this를 upcast한다면?</h2>
<p>만약 <code>this</code> 키워드를 부모 클래스로 형변환다면 놀랍게도 컴파일러는 &quot;같은 부모 인스턴스&quot;가 아니라 &quot;다른 부모 인스턴스&quot;로 인식하는 것인지 에러를 일으킵니다.
<img src="https://velog.velcdn.com/images/sharlotte_04/post/071703ce-c970-4bba-a9d9-26fd19a4690a/image.png" alt="">
뇌피셜이지만 이것이 바로 super, base 키워드가 그저 syntax sugar가 아니라는 것의 증거지 않을까 싶습니다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[딥 링크 (feat. unity)]]></title>
            <link>https://velog.io/@sharlotte_04/%EB%94%A5-%EB%A7%81%ED%81%AC-feat.-unity</link>
            <guid>https://velog.io/@sharlotte_04/%EB%94%A5-%EB%A7%81%ED%81%AC-feat.-unity</guid>
            <pubDate>Mon, 13 Nov 2023 14:08:07 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>⚠️ <strong>Notice!</strong>
일부 이미지는 외부 웹페이지로부터 가져온 이미지입니다. 이미지에 출처 링크가 걸려 있으므로 이미지를 클릭하면 원본 웹페이지로 이동하게 됩니다.</p>
</blockquote>
<h1 id="deep-link">Deep Link</h1>
<blockquote>
<p><em>In the context of <a href="https://www.wikiwand.com/en/Mobile_apps">mobile apps</a>, <a href="https://www.wikiwand.com/en/Deep_linking">deep linking</a> consists of using a <a href="https://www.wikiwand.com/en/Uniform_resource_identifier">uniform resource identifier (URI)</a> that links to a specific location within a mobile app rather than simply launching the app.
<a href="https://www.wikiwand.com/en/Mobile_apps">모바일 앱</a>의 경우 <a href="https://www.wikiwand.com/en/Deep_linking">딥 링킹</a>은 단순히 앱을 실행하는 것이 아니라 모바일 앱 내의 특정 위치로 연결되는 <a href="https://www.wikiwand.com/en/Uniform_resource_identifier">유니폼 리소스 식별자(URI)</a>를 사용하는 것입니다.</em></p>
<p><a href="https://www.wikiwand.com/en/Mobile_deep_linking">Mobile deep linking Wikipedia</a></p>
</blockquote>
<p>딥링크는 특정 위치로 이동하기 위한 일종의 유니폼 리소스 식별자 사용법입니다. 딥링크를 사용하면 해당 콘텐츠의 최상위 경로 (ex: <a href="https://github.com/Sharlottes">https://github.com/Sharlottes</a> 는 <a href="https://github.com">https://github.com</a>, 카카오톡은 직접 열기)에서 직접 콘텐츠를 찾아 들어갈 필요 없이, 콘텐츠에 곧바로 도달할 수 있습니다. </p>
<p>딥링크를 사용하면 콘텐츠의 접근성을 극대화할 수 있으며 UX를 크게 향상시킬 수 있습니다. 딥링크는 웹사이트, 모바일 앱, 윈도우즈 앱, 맥OS 앱 등 여러 환경에서 각자 다른 정책으로 제공되고 있습니다. 정책이 제각각인 이유는 플렛폼 자체의 파편화, 보안적 문제, 자신과 관련된 기능 부여(플레이스토어로 이동 등)에 의한 것입니다.</p>
<h2 id="웹-딥링킹과-앱-딥링킹">웹 딥링킹과 앱 딥링킹</h2>
<table>
<thead>
<tr>
<th><img src="https://velog.velcdn.com/images/sharlotte_04/post/60ec25c2-1a42-4313-8859-faeefbc3edae/image.png" alt=""></th>
<th><img src="https://velog.velcdn.com/images/sharlotte_04/post/21f94df5-b254-4c83-9d80-6f9d11f21456/image.png" alt=""></th>
</tr>
</thead>
<tbody><tr>
<td>일반적으로 유저는 위와 같이 <strong>URI</strong>를 타고 <strong>웹사이트</strong>로 이동</td>
<td>앱 딥링크는 똑같이, 위와 같이 <strong>URI</strong>를 타고 <strong>어플리케이션</strong>으로 이동</td>
</tr>
</tbody></table>
<p>웹 딥링크와 앱 딥링크는 그 흐름이 사뭇 다르지 않습니다. 웹 딥링크로 특정 링크를 타고 목적지로 지정된 해당 웹사이트의 콘텐츠로 도달할 수 있듯이, 앱 딥링크 역시 특정 링크를 타고 목적지로 지정된 해당 앱의 콘텐츠로 도달할 수 있습니다.</p>
<blockquote>
<p><em><strong>deep linking</strong> is the use of a <a href="https://www.wikiwand.com/en/Hyperlink">hyperlink</a> that links to a specific</em>
<a href="https://www.wikiwand.com/en/Hyperlink">deep linking Wikipedia</a></p>
</blockquote>
<p>그러나 한가지 차이점은 웹 딥링크가 하이퍼링크를 사용하므로 프로토콜이 대개 HTTP나 HTTPS인 것에 비해 앱 딥링크는 임의의 스키마를 사용한다는 것입니다. 그러므로 우리는 먼저 스키마를 이해해야 합니다.</p>
<h2 id="uri-uniform-resource-identifier">URI (Uniform Resource Identifier)</h2>
<p>…그 전에 스키마를 포함하는 URI, 통합 자원 식별자에 대해 알아야 합니다.</p>
<blockquote>
<p><em>A <strong>Uniform Resource Identifier (URI)</strong> is a unique sequence of characters that identifies a logical or physical resource used by web technologies.
<strong>URI(Uniform Resource Identifier)</strong>는 웹 기술에서 사용하는 논리적 또는 물리적 리소스를 식별하는 고유한 문자 시퀀스입니다.</em>
<a href="https://en.wikipedia.org/wiki/Uniform%20Resource%20Identifier">Uniform Resource Identifier Wikipedia</a></p>
</blockquote>
<p>URI는 뭐든지 식별하는 데 사용될 수 있습니다. 물리적이든 넷상에 있든 간에 그것을 식별하기 위한 서식입니다. 예를 들어, 깃허브 웹페이지에서 제 프로필의 위치를 가리키는 식별자는 <a href="https://github.com/Sharlottes">https://github.com/Sharlottes</a> 입니다. 또, 국제표준도서번호(ISBN) 시스템에서 로미오와 줄리엣의 특정 판본을 가리키는 식별자는 urn:isbn:0-486-27557-4입니다.</p>
<h3 id="uri-url">URI? URL?</h3>
<p><img src="https://velog.velcdn.com/images/sharlotte_04/post/f68e65b0-6d94-4b1a-aac4-074de7aa2923/image.png" alt="">
종종 URI와 URL를 햇갈리는데, URI는 URL과 URN를 내포하는 상위 개념입니다. 앞선 예에서 깃허브가 URL(ocation)이고 로미오와 줄리엣이 URN(ame)입니다. 이름 그대로, 저 깃허브 링크는 고유한 위치를, 로미오와 줄리엣은 고유한 이름을 가리킵니다.</p>
<h3 id="syntax">Syntax</h3>
<p>URI로 표현될 딥링크의 구성 요소를 이해할려면 먼저 문법부터 알아야겠죠?</p>
<p><a href="https://www.wikiwand.com/en/Uniform%20Resource%20Identifier#Syntax"><img src="https://velog.velcdn.com/images/sharlotte_04/post/c99c92ad-5a26-42cb-a5fe-42db1e6e26fa/image.png" alt=""></a></p>
<p>위 이미지는 URI의 <a href="https://www.wikiwand.com/en/Syntax_diagram">구문 다이어그램</a>입니다. 구문 다이어그램은 철도 다이어그램으로도 불리며, 왼쪽에서 오른쪽으로 기찻길을 따라 기차가 지나가는 것을 생각하며 이해하면 됩니다.</p>
<p>그러므로 아래 예시들은 모두 유효한 문법입니다.</p>
<pre><code>https://john.doe@www.example.com:123/forum/questions/?tag=networking&amp;order=newest#top
ldap://[2001:db8::7]/c=GB?objectClass?one
mailto:John.Doe@example.com
news:comp.infosystems.www.servers.unix
tel:+1-816-555-1212
telnet://192.0.2.16:80/
urn:oasis:names:specification:docbook:dtd:xml:4.1.2
unitydl://mylink?parameter
kakaotalk://me</code></pre><p>이제 각 구성 요소들이 무엇을 의미하는지 알아보겠습니다.</p>
<p><img src="https://velog.velcdn.com/images/sharlotte_04/post/a3e39827-d3ce-46b8-a570-d478c445201c/image.png" alt=""></p>
<p><strong>스키마 또는 프로토콜(scheme)</strong>는 : 앞에 위치하는 구성요소입니다. 이 URI이 어떤 형식으로 구성되고 해석되는지, 즉 URI의 종류를 알려줍니다. data, http, ftp, javascript, mainto, tel, urn, <strong>unitydl</strong>, ws 등 다양한 프로토콜과 스키마가 있습니다. </p>
<p><strong>권한(userinfo, host, port)</strong>은 // 뒤에 위치하는 구성요소입니다. 이 URI이 누구의 것인지 알려줍니다. userinfo, host, port로 구성되어 있습니다.</p>
<ul>
<li>userinfo는 리소스를 보유한 유저의 정보를 알려줍니다. username와 password로 나뉘며 SQL나 <code>mailto</code> 에서 흔히 볼 수 있는 선택적 유저 정보입니다.<ul>
<li><code>mysql+mysqldb://scott:tiger@localhost/test?charset=utf8&amp;use_unicode=0</code></li>
<li><code>mailto:John.Doe@example.com</code></li>
</ul>
</li>
<li>host는 리소스를 제공하고 있는 호스트의 정보를 알려줍니다. 주로 IP 또는 도메인이며 웹페이지 링크에서 흔히 볼 수 있습니다.</li>
<li>port는 리소스를 제공하고 있는 호스트의 포트를 알려줍니다. 환경에 따라 기본값이 있어서 port는 선택적입니다.</li>
</ul>
<p><strong>경로(path)</strong>는 / 뒤에 위치하는 구성요소입니다. URI가 가르키는 리소스의 위치 경로를 알려줍니다. 경로가 최상단(<code>/</code>) 인 경우 생략되는 경우도 있습니다. 종종 위치 경로 뿐만이 아니라 계층적인 정보(parameter)를 넘기는 용도로도 쓰입니다. (ex: <a href="https://stackoverflow.com/questions/41966092">https://stackoverflow.com/questions/41966092</a> 에서 questions는 path이나 41966092는 parameter로 쓰임)</p>
<p><strong>쿼리(query)</strong>는 ? 뒤에 위치하는 선택적 구성요소입니다. URI가 가르키는 리소스에 추가적인 동적 정보를 전달하고 싶을 때 주로 사용됩니다. 표준 문법은 없지만 대개 <code>key1=value1&amp;key2=value2</code> 와 같이 key-value 형식으로 이용됩니다. path에서 계층적인 parameter로 쓰이는 것과 대조적으로, 비(非)계층적인 parameter를 넘기고 싶을 때 쓰입니다.</p>
<p><strong>조각(fragment)</strong>는 # 뒤에 위치하는 선택적 구성요소입니다. HTML 문서에서 자주 보입니다 (ex: <a href="https://www.wikiwand.com/en/Uniform%20Resource%20Identifier#Design">https://www.wikiwand.com/en/Uniform Resource Identifier#Design</a> 에서 Design 문단을 가리키는 #Design fragment) path parameter, query처럼 선택적인 부가정보를 주지만 <em>유일한 보조 리소스</em> 라는 점에서 특별함이 있습니다.</p>
<h3 id="app-link--universal-link">App link &amp; Universal Link</h3>
<p>딥링크는 그저 커스텀 스키마(scheme)로 이루어진 URI Scheme가 전부지 않습니다.</p>
<blockquote>
<p>딥링크는 웹사이트, 모바일 앱, 윈도우즈 앱, 맥OS 앱 등 여러 환경에서 각자 다른 정책으로 제공되고 있습니다. 정책이 제각각인 이유는 플렛폼 자체의 파편화, 보안적 문제, 자신과 관련된 기능 부여(플레이스토어로 이동 등)에 의한 것입니다.</p>
</blockquote>
<p>제가 앞서 정책이 제각각이라고 말씀드린 것이 바로 이것입니다. 딥링크는 URI Scheme 뿐만이 아니라 안드로이드용 App Link, iOS용 Universal Link가 있습니다. (참고로 Unity에선 App Link 대신 Intent Scheme를 사용하는데, 더 구체적인 정보를 지니는 안드로이드 딥링크입니다.)</p>
<p><a href="https://www.airbridge.io/blog-ko/deeplink-101-for-marketers-and-developers"><img src="https://velog.velcdn.com/images/sharlotte_04/post/4d470bec-c131-44d1-9f8b-e136159f6d12/image.png" alt=""></a></p>
<p>이 딥링크들은 여러 부가적인 효과를 지닙니다.</p>
<ul>
<li><p><strong>고유합니다.</strong> 앞서 설명한 URI의 스키마가 세상에서 단 하나 밖에 없을지 증명할 수단이 없습니다. 안드로이드는 같은 스키마를 가진 두 앱이 있다면 선택지를 주지만, iOS는 그렇지 않다고 합니다. 보안상으로도 악성 앱이 같은 스키마를 가진다면 잘못 클릭하여 위험해질 수도 있습니다.
그러므로 고유함은 여러모로 유용합니다.
<img src="https://velog.velcdn.com/images/sharlotte_04/post/b602cd55-4aac-4bde-8f50-310740cf5464/image.png" alt="">
세 앱들은 모두 <code>market://</code> 스키마를 가지고 있어서 이런 일이 자주 발생합니다.</p>
</li>
<li><p><strong>커스텀 스키마 대신 웹페이지를 사용합니다.</strong> 고유함을 어떻게 실현할지 고민하던 안드로이드와 애플 개발자들은 도메인을 써먹자고 결정했습니다. 이제 개발자는 폴백 상태에서 이 웹페이지에서 웹페이지 콘텐츠를 보여줄 수도 있고,  <a href="https://deeplink.heydealer.com/.well-known/assetlinks.json">Android</a>와 <a href="https://deeplink.heydealer.com/.well-known/apple-app-site-association">iOS</a>에게 줘야 할 앱의 고유 정보를 포함한  JSON을 주어 앱 마켓으로 이동시킬 수도 있습니다.</p>
</li>
<li><p><strong>플렛폼 어드벤티지를 받을 수 있습니다.</strong> 만약 이동할려는 앱이 없으면 어떻게 해야 할까요? 이 개선된 딥링크들은 이런 폴백(fallback)상태에서 앱 마켓으로 이동하여 앱 설치를 유도하는 기능을 제공합니다.</p>
</li>
</ul>
<p>그러나 OS마다 브라우저(심지어 카톡같은 자체 브라우저)마다 지원 유무가 갈리는 문제, <strong>유저가 직접 링크를 눌러야만 작동</strong>하는 문제 등으로 인해 여전히 URI Scheme도 제공해주어야 합니다.</p>
<p><a href="https://docs.tosspayments.com/common/glossary/deep-link#%EB%94%A5%EB%A7%81%ED%81%AC%EC%9D%98-%EC%9C%A0%ED%98%95"><img src="https://velog.velcdn.com/images/sharlotte_04/post/9a48115b-833d-4229-8f45-7b7c4fde34b2/image.png" alt=""></a></p>
<table>
<thead>
<tr>
<th><a href="https://www.airbridge.io/blog-ko/deeplink-101-for-marketers-and-developers"><img src="https://velog.velcdn.com/images/sharlotte_04/post/c3ae4b06-dd11-44ee-b22d-593272db72e7/image.png" alt=""></a></th>
<th><a href="https://help.dfinery.io/hc/ko/articles/360039757433-%EB%94%A5%EB%A7%81%ED%81%AC-Deeplink-URI%EC%8A%A4%ED%82%B4-%EC%9C%A0%EB%8B%88%EB%B2%84%EC%85%9C-%EB%A7%81%ED%81%AC-%EC%95%B1%EB%A7%81%ED%81%AC-%EA%B5%AC%EB%B6%84%EA%B3%BC-%EC%9D%B4%ED%95%B4#toc11"><img src="https://velog.velcdn.com/images/sharlotte_04/post/a414c56a-baf3-4994-abf7-909afb45b1b0/image.png" alt=""></a></th>
<th><a href="https://help.dfinery.io/hc/ko/articles/360039757433-%EB%94%A5%EB%A7%81%ED%81%AC-Deeplink-URI%EC%8A%A4%ED%82%B4-%EC%9C%A0%EB%8B%88%EB%B2%84%EC%85%9C-%EB%A7%81%ED%81%AC-%EC%95%B1%EB%A7%81%ED%81%AC-%EA%B5%AC%EB%B6%84%EA%B3%BC-%EC%9D%B4%ED%95%B4#toc11"><img src="https://velog.velcdn.com/images/sharlotte_04/post/493b5b29-42a1-4a78-be49-bf0d839a21ec/image.png" alt=""></a></th>
</tr>
</thead>
</table>
<h2 id="unity">Unity</h2>
<p>앞서 설명한 것에 따르면 딥 링크는 세가지 종류가 있고, 유니티는 네가지 플렛폼 (android, ios, windows, mac) 에서 딥 링크를 지원하고 있습니다. 플렛폼마다 딥 링크를 지원하는 방식이 다르니 일일이 설정을 해보아야 합니다.</p>
<h3 id="uwp-build-setting--deep-link">UWP Build Setting (+ deep link)</h3>
<p><a href="https://docs.unity3d.com/kr/2022.3/Manual/deep-linking.html">Unity 딥 링크 공식문서</a>에서 플렛폼마다 활성화하는 방법과 프로그래밍적으로 딥 링크를 처리하는 방법을 모두 설명해주고 있습니다. 제 컴퓨터가 윈도우인 관계로 UWP(Universal Windows Platform)만을 시도해보겠습니다.</p>
<blockquote>
<p>⚠️ <strong>유니티 버전을 잘 확인하세요!</strong>
<a href="https://docs.unity3d.com/kr/2021.1/Manual/enabling-deep-linking.html">2021.1 이전 공식문서</a>에는 macOS 딥링크가 없지만 이후 공식문서인 <a href="https://docs.unity3d.com/kr/2022.3/Manual/deep-linking.html">2022.3 공식문서</a>에는 있습니다.
문서 자체가 달라서 버전 선택에도 잡히지가 않으니 공식문서를 읽을 때 유의하세요.</p>
<table>
<thead>
<tr>
<th><img src="https://velog.velcdn.com/images/sharlotte_04/post/8471cacc-1c2c-440e-b0e6-b67aee1b5ddf/image.png" alt=""></th>
<th><img src="https://velog.velcdn.com/images/sharlotte_04/post/81b7d8e9-6283-4380-807e-bd34941525fb/image.png" alt=""></th>
</tr>
</thead>
<tbody><tr>
<td>2021.1 이전, 딥 링크 활성화 문서</td>
<td>2021.2 이후, 딥 링크 문서</td>
</tr>
</tbody></table>
</blockquote>
<p>그 전에 추가적인 개발 환경을 설정해야 합니다.</p>
<p>먼저, Unity Hub에서 Unity Editor에 UWP 빌드 지원 모듈을 설치합니다.</p>
<p><img src="https://velog.velcdn.com/images/sharlotte_04/post/10143888-1e7b-4d25-9842-8a6c2ad4fc58/image.png" alt=""></p>
<p>모듈 설치가 끝났으면 빌드 설정을 마저 해야합니다.</p>
<p>우리는 UWP 빌드를 하여 UWP 딥 링크를 시도할 것이니 빌드 설정에서 UWP로 설정해야 합니다.</p>
<table>
<thead>
<tr>
<th><img src="https://velog.velcdn.com/images/sharlotte_04/post/09d8f8f0-6667-455f-9b9e-142b97628aa0/image.png" alt=""></th>
</tr>
</thead>
<tbody><tr>
<td>여기선 devcelopment build가 켜져있는데 사실 없어도 됩니다. 윈도우에서 개발자 모드도 켜야하고 여튼 복잡해져요.</td>
</tr>
</tbody></table>
<p>위 빌드 설정창을 보면 “Selected Visual Studio is missing required components” 라 경고가 뜹니다. 현재 비주얼 스튜디오에 필요한 UWP 개발 컴포넌트가 없다는 뜻입니다.</p>
<p>그러므로 Visual Studio Installer로 Visual Studio에 UWP 개발 워크로드를 설치합니다.
<img src="https://velog.velcdn.com/images/sharlotte_04/post/5eacf069-567c-4efb-8e75-cc29e13c2a82/image.png" alt=""></p>
<p>이어서 Player Settings에 들어가 UWP 탭의 Publishing Setting - Protocol - Name에서 유니티 딥 링크에 쓸 프로토콜(스키마) 이름을 정합니다. 전 예제대로 unitydl를 쓰겠습니다.</p>
<p><img src="https://velog.velcdn.com/images/sharlotte_04/post/f7ea77b7-6519-445a-b678-bf2f460b9d81/image.png" alt=""></p>
<blockquote>
<p><em><strong>Build And Run</strong> 을 클릭하면 Unity가 독립적으로 실행할 수 있는 앱 실행 파일을 빌드합니다.</em></p>
<p><a href="https://docs.unity3d.com/kr/2021.3/Manual/windowsstore-buildsettings.html">유니터설 Windows 플랫폼(UWP) 빌드 설정</a></p>
</blockquote>
<p>마지막으로, 공식문서에 따라 <strong>Build가 아니라 Build And Run를 해야 합니다.</strong> </p>
<p>Build 결과에선 원하시는 exe 파일을 찾을 수 없을겁니다.
Build만 하고 딥링크로 launch하는 것도 안됩니다. 대신 Build and Run를 한 다음 끄고 나서 launch하는건 됩니다.</p>
<p>제가 직접 해보지 않았기에 이 글에는 담지 않았지만 유니티 딥링크 공식문서에선 iOS, macOS, Android에 대한 세팅 방법도 알려줍니다. 제가 이를 상기시키는 이유는 <strong>유니티가 Android의 인텐트 스키마와 iOS/macOS의 유니버설 링크를 지원</strong>하기 때문입니다. 즉, 앱 마켓으로 이동시키는 폴백 기능과 고유성 및 소유권 증명을 유니티 앱으로도 할 수 있습니다.</p>
<h3 id="deep-link-사용">Deep Link 사용</h3>
<p>딥 링크를 Unity에서 사용하는 방법은 두가지가 있습니다.</p>
<ul>
<li>애플리케이션이 시작할 때 <code>Application.absoluteURL</code> 를 확인한다.<ul>
<li>즉, 딥링크로 인해 앱이 시작할 때 absoluteURL를 확인하여 처리할 수 있습니다.</li>
</ul>
</li>
<li>애플리케이션이 실행 중일 때 <code>Application.deepLinkActivated</code> 이벤트를 구독한다.<ul>
<li>즉, 애플리케이션을 켜두고 다른 창에서 딥링크를 타고 들어갈 때 이 이벤트로 확인하여 처리할 수 있습니다.</li>
</ul>
</li>
</ul>
<p>아래는 딥링크 예제 코드입니다.</p>
<pre><code class="language-cs">public class DeepLinkManager : MonoBehaviour
{
    public static DeepLinkManager Main { get; private set; }

    private void Awake()
    {
        if (Main == null)
        {
            Main = this;
            Screen.fullScreen = false;
            Application.deepLinkActivated += OnDeepLinkActivated;
            if (!string.IsNullOrEmpty(Application.absoluteURL))
            {
                // Cold start and Application.absoluteURL not null so process Deep Link.
                OnDeepLinkActivated(Application.absoluteURL);
            }
            DontDestroyOnLoad(gameObject);
        }
        else
        {
            Destroy(gameObject);
        }
    }

    private void OnDeepLinkActivated(string url)
    {
        Debug.Log($&quot;DEEPLINK URL: {url}&quot;);
    }
}</code></pre>
<p><img src="https://velog.velcdn.com/images/sharlotte_04/post/2e0f5f43-dc52-40f5-aae9-ad40587d71a6/image.png" alt=""></p>
<p>유니티 앱을 다른 창에 열어두고 지정된 딥링크로 유저가 이동했을 때, 유니티는 <code>Application.deepLinkActivated</code>이벤트를 호출합니다. 임의로 구현한 <code>DeepLinkManager</code> 에서 미리 <code>Application.deepLinkActivated</code> 에 <code>OnDeepLinkActivated</code> 메서드를 추가했으므로 이벤트가 호출된 즉시 구독한 <code>OnDeepLinkActivated</code> 메서드도 호출됩니다. <a href="https://docs.unity3d.com/kr/2022.3/ScriptReference/Application-deepLinkActivated.html"><strong><code>deepLinkActivated</code></strong></a> 이벤트는 파라미터로 딥링크 URL를 가지고 있으며 이는 <code>Application.absoluteURL</code> 과 같습니다.</p>
<p>유니티 앱을 열어두지 않고 지정된 딥링크로 유저가 이동하면 앱이 시작되고 <code>Application.absoluteURL</code>이 null이 아님을 확인한 다음 <code>OnDeepLinkActivated</code> 메서드를 호출합니다.</p>
<h3 id="테스트---딥링크로-씬-열기">테스트 - 딥링크로 씬 열기</h3>
<p>아래 예제는 <a href="https://docs.unity3d.com/kr/2022.3/Manual/deep-linking.html">유니티 딥 링크 공식문서</a>에서 제공하고 있는 예제 코드를 약간 개조한 버전입니다.</p>
<pre><code class="language-cs">using UnityEngine;
using UnityEngine.SceneManagement;

public class DeepLinkManager : MonoBehaviour
{
    public static DeepLinkManager Instance { get; private set; }
    private void Awake()
    {
        if (Instance == null)
        {
            Instance = this;
            Screen.fullScreen = false;
            Application.deepLinkActivated += onDeepLinkActivated;
            if (!string.IsNullOrEmpty(Application.absoluteURL))
            {
                // Cold start and Application.absoluteURL not null so process Deep Link.
                onDeepLinkActivated(Application.absoluteURL);
            }
            DontDestroyOnLoad(gameObject);
        }
        else
        {
            Destroy(gameObject);
        }
    }

    private void onDeepLinkActivated(string url)
    {
        // url is unitydl://mylink or unitydl://mylink?{sceneName}
        string[] urlAndQuery = url.Split(&#39;?&#39;);
        if(urlAndQuery.Length == 1)
        {
            return;
        }

        string sceneName = urlAndQuery[1];
        if (!(sceneName == &quot;scene1&quot; || sceneName == &quot;scene2&quot;))
        {
            return;
        }

        Screen.fullScreen = false;
        SceneManager.LoadScene(sceneName);
    }
}</code></pre>
<p>씬을 열기 위한 웹페이지도 필요하겠죠?</p>
<p>간단히 메모장에 아래 코드를 적고, index.html로 저장한 다음 브라우저로 엽니다.</p>
<pre><code class="language-html">&lt;html&gt;
    &lt;head&gt;
       &lt;meta http-equiv=Content-Type content=&quot;text/html; charset=utf-8&quot;&gt;
    &lt;/head&gt;
    &lt;body&gt;
       &lt;h1&gt;My Deep Link Test page&lt;/h1&gt;
       &lt;p&gt;&lt;a href=&quot;unitydl://mylink&quot;&gt;Launch&lt;/a&gt;&lt;/p&gt;
       &lt;p&gt;&lt;a href=&quot;unitydl://mylink?scene1&quot;&gt;Launch to Scene1&lt;/a&gt;&lt;/p&gt;
       &lt;p&gt;&lt;a href=&quot;unitydl://mylink?scene2&quot;&gt;Launch to Scene2&lt;/a&gt;&lt;/p&gt;
    &lt;/body&gt;
&lt;/html&gt;</code></pre>
<p>유니티에서 Build and Run를 한 <strong>다음</strong> 웹페이지에서 세가지 링크 중 하나를 눌러 딥 링크를 타고 빌드한 앱으로 이동합니다.</p>
<p> <img src="https://velog.velcdn.com/images/sharlotte_04/post/26fcc540-4f4d-41bb-8d72-eb194707b947/image.gif" alt=""></p>
<p>좋습니다! 저흰 방금 query를 파라미터로 삼아 특정 씬으로 딥링크했습니다.</p>
<p><code>Application.absoluteURL</code> 또는 <code>Application.deepLinkActivated</code> 의 파라미터는 딥링크의 절대(전체)경로를 나타내므로 이 URL 문자열을 원하는 만큼 자르고 나눠서 사용하면 됩니다. </p>
<h2 id="마치며">마치며</h2>
<p>이상으로 URI, 딥링크, 유니티에서의 적용과 예제를 통해 유니티 딥링크를 알려드렸습니다. </p>
<p>보다시피 유니티 딥링크의 활용도는 무궁무진합니다. 딥링크로 들어온 유저에게 혜택 선물을 줄 수도 있고, 특정 콘텐츠를 공유할 수도 있으며, 특정 페이지 또는 씬으로 바로 이동시킬 수도 있습니다. 물론, 중간에 필수 과정(플레이어 설정이라던가)이 필요하다면 이 이동을 지연시킬 수도 있겠죠!</p>
<p>유니티 개발 자체도 중요하지만, 개발한 작품에 유저가 접근할 수 있는 표면적을 늘리는 접근성도 중요합니다. 이 아티클로 접근성 향상에 대한 방법을 익히셨으면 좋겠습니다. </p>
<h1 id="📖reference">📖<strong>Reference</strong></h1>
<p><a href="https://docs.tosspayments.com/common/glossary/deep-link#%EB%94%A5%EB%A7%81%ED%81%AC%EC%9D%98-%EC%9C%A0%ED%98%95">딥링크(Deep Link) | 토스페이먼츠 개발자센터</a></p>
<p><a href="https://help.dfinery.io/hc/ko/articles/360039757433-%EB%94%A5%EB%A7%81%ED%81%AC-Deeplink-URI%EC%8A%A4%ED%82%B4-%EC%9C%A0%EB%8B%88%EB%B2%84%EC%85%9C-%EB%A7%81%ED%81%AC-%EC%95%B1%EB%A7%81%ED%81%AC-%EA%B5%AC%EB%B6%84%EA%B3%BC-%EC%9D%B4%ED%95%B4">딥링크(Deeplink) : URI스킴, 유니버셜 링크, 앱링크 구분과 이해</a></p>
<p><a href="https://www.airbridge.io/blog-ko/deeplink-101-for-marketers-and-developers">[딥링크101] 마케터와 개발자를 위한 딥링크 시작하기 | 에어브릿지 블로그</a></p>
<p><a href="https://developer.mozilla.org/ko/docs/Web/HTTP/Basics_of_HTTP/Identifying_resources_on_the_Web">웹의 리소스 식별하기 - HTTP | MDN</a></p>
<p><a href="https://www.adjust.com/ko/glossary/deep-linking/">딥링킹이란 무엇이며 어떻게 작동하나요? | Adjust | Adjust</a></p>
<p><a href="https://www.wikiwand.com/en/Deep_linking">Wikiwand - Deep linking</a></p>
<p><a href="https://www.wikiwand.com/en/Mobile_deep_linking">Wikiwand - Mobile deep linking</a></p>
<p><a href="https://www.wikiwand.com/en/Syntax_diagram">Wikiwand - Syntax diagram</a></p>
<p><a href="https://www.wikiwand.com/en/Uniform%20Resource%20Identifier">Wikiwand - Uniform Resource Identifier</a></p>
<p><a href="https://learn.microsoft.com/en-us/windows/uwp/launch-resume/web-to-app-linking">Enable apps for websites using app URI handlers - UWP applications</a></p>
<p><a href="https://blog.workflowy.com/deep-links-open-links-directly-in-desktop-and-mobile-apps/">Deep Links: Open links directly in Desktop and Mobile Apps</a></p>
<p><a href="https://stackoverflow.com/questions/41966092/database-uri-or-url">Database URI or URL?</a></p>
<p><a href="https://docs.unity3d.com/kr/2022.3/Manual/deep-linking.html">딥 링크 - Unity 매뉴얼</a></p>
<p><a href="https://docs.unity3d.com/kr/2021.3/Manual/windowsstore-buildsettings.html">유니버설 Windows 플랫폼(UWP) 빌드 설정 - Unity 매뉴얼</a></p>
<p><a href="https://blog.unity.com/engine-platform/add-deep-links-to-your-unity-mobile-apps-for-better-user-experience">Add deep links to your Unity mobile apps for better user experience | Unity Blog</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[MUI + emotion에 vanilla-extract 사용하기 (WIP)]]></title>
            <link>https://velog.io/@sharlotte_04/MUI-emotion%EC%97%90-vanilla-extract-%EC%82%AC%EC%9A%A9%ED%95%98%EA%B8%B0</link>
            <guid>https://velog.io/@sharlotte_04/MUI-emotion%EC%97%90-vanilla-extract-%EC%82%AC%EC%9A%A9%ED%95%98%EA%B8%B0</guid>
            <pubDate>Tue, 07 Nov 2023 03:08:45 GMT</pubDate>
            <description><![CDATA[<h1 id="introduction">Introduction</h1>
<blockquote>
<p><strong>참고</strong>
이 글은 아직 결론 문단이 부재중인 미완성 글입니다!
갑자기 끝이 사라지더라도 놀라지 말아주세요 :(</p>
</blockquote>
<p>  리액트는 UI를 다룰 때 발생하는 여러 문제들을 해결하기 위해 개발된 가장 유명한 UI 라이브러리 중 하나입니다. 리액트 생태계가 커짐에 따라, 리액트 개발자들에겐 부트스트랩과 같은 UI-KIT의 필요성이 일찍이 다가왔을 것입니다. 개발자는 바퀴의 재발명을 귀찮아하고, MVP는 빠를수록 좋으니깐요 :D</p>
<p>  그리고 오늘에 이르어서, 리액트 UI-KIT의 갯수는 정말 많아졌습니다. <a href="https://dev.to/fredy/top-5-reactjs-ui-components-libraries-for-2023-4673">2023년 Top 5 리액트 UI-KIT 라이브러리들을 다루는 한 Dev 블로그 포스트</a>에 따르면 상위 5개 이상의 리액트 UI-KIT 라이브러리로 HorizonUI, MaterialUI, ChakraUI, AntDesign, NextUI를 소개하고 있으며 추가로 제가 아는 UI-KIT들만 10개가 넘게 있습니다.</p>
<p>  이처럼 수많은 UI 라이브러리들에서 가장 유명한 라이브러리 중 하나인 MaterialUI는 제 포트폴리오의 주력 UI 라이브러리로 사용되고 있습니다. MaterialUI는 구글의 <a href="https://m2.material.io/">Material Design 2</a>를 리액트 컴포넌트로 구현한 오픈소스 UI 라이브러리입니다. 매우 거대한 커뮤니티, 안정적인 라이브러리, 머터리얼 디자인을 그대로 구현함으로써 생성된 엄청난 양의 UI 컴포넌트들은 <strong>리액트 입문자였던 저에게 좋은 스타트</strong>로 느껴졌습니다.</p>
<p>MUI의 수많은 UI 컴포넌트들은 개발을 보조해주면서 배워 만들고자 하는 것에 더 쉽게 도달하도록 도와줬었습니다. 물론 그 UI 컴포넌트들은 미래에 제가 직접 만들어봐야 할 또다른 과제지만 당장의 과제와 재미를 위해 <em>빌려온</em> 것입니다.</p>
<p>그러나 대여에 이자가 붙은 것일까요? 시간이 지남에 따라, MUI(Material UI)는 점점 부담스럽게 느껴졌습니다.</p>
<h2 id="출항한-배는-어느새-고립지로-변모했습니다">출항한 배는 어느새 고립지로 변모했습니다.</h2>
<p>  개발을 하면 할수록 <strong>하고자 하는 것과 MUI가 의도한 것이 충돌하는 경우</strong>가 많이 생겨났습니다. 그 중 일부는 지역적으로 해결할 수 있었지만, 일부는 구조적으로 불가능할 정도로 심각한 충돌이였습니다. </p>
<p>  그래서 MUI로부터 탈출하려고 하니 프로젝트 전체가 이미 MUI에 잠식된 상태였습니다. 처음에는 MUI의 틀 내에서 어디로든 나아갈 수 있었지만, 역설적으로 MUI 틀 밖으로 나아갈 수 없는 상태인 것입니다. <strong>처음부터 확장성이 낮은 라이브러리를 사용한 대가</strong>였습니다.</p>
<p> 이렇게 하고자 하는 것과 MUI가 의도한 것이 부딪친 경우가 생각보다 많았습니다. 결국 MUI를 점진적으로 퇴출시킬 생각을 결심했고, 여러 충돌 케이스를 하나하나씩 해결하여 점진적 마이그레이션을 취하기로 결정했습니다.</p>
<h1 id="스타일-시스템-마이그레이션">스타일 시스템 마이그레이션</h1>
<p>이번 글에선 그런 점진적 마이그레이션 중 첫번째인 스타일 시스템 마이그레이션에 대해 다루고자 합니다. 이 글을 시작한 계기기도 하고, 마이그레이션하면서 겪은 경험이 생각보다 다채롭고 재미있었기 때문입니다.</p>
<h2 id="emotion">Emotion</h2>
<blockquote>
<p>Emotion is a library designed for writing css styles with JavaScript. It provides powerful and predictable style composition in addition to a great developer experience with features such as source maps, labels, and testing utilities. Both string and object styles are supported.
<a href="https://emotion.sh/">https://emotion.sh/</a></p>
</blockquote>
<p>  MUI는 스타일 시스템의 스타일 앤진으로 emotion이나 <del>styled-component</del>를 가지고 있습니다. (<a href="https://github.com/mui/material-ui/issues/29742">styled-component는 SSR를 지원하지 않는 이슈</a>가 있어서 emotion를 권장한다고 합니다.) 두 라이브러리는 Runtime Css-In-Js입니다. 이들은 자바스크립트 범주에서 스타일링을 할 수 있음으로써 리액트 props와 state 또는 여러 함수 및 변수 등을 사용할 수 있고, 지역 스타일링으로 예기치 않은 클레스네임 중복을 방지할 수 있습니다.</p>
<p>  제가 처음 MUI를 사용할 때에는 별다른 스타일 라이브러리를 잘 몰랐고, emotion로 런타임 CssInJs를 사용하는 것이 딱히 부담된다고 느껴지지 않았습니다. 아마 정말로 UX에 피해가 갈 정도로 부담되진 않았을 것입니다.</p>
<p>  시간에 따라 개발 기술을 점점 익히면서 emotion의 단점이 시야에 들어왔었습니다. <a href="https://junghan92.medium.com/%EB%B2%88%EC%97%AD-%EC%9A%B0%EB%A6%AC%EA%B0%80-css-in-js%EC%99%80-%ED%97%A4%EC%96%B4%EC%A7%80%EB%8A%94-%EC%9D%B4%EC%9C%A0-a2e726d6ace6">Emotion의 메인테이너 중 일인인 Sam Magura는 emotion의 장단점와 떠나야 하는 이유</a>를 아티클로 투고했는데, 이에 따르면 emotion은 오버헤드, 번들 부담 그리고 데브툴 혼란 야기를 단점으로 가지고 있습니다.</p>
<p>  사실 emotion이 런타임에서 DOM를 수정한다는 원리에서 이미 우리는 이 오버헤드와 번들 부담을 짐작했을 것입니다. 그럼에도 불구하고 계속 사용했던 이유는 달리 대체제가 없었기 때문입니다. 자바..아니 정확힌 타입스크립트의 매력을 유지하면서 인기 있으며 안정적인 emotion의 경쟁자를 찾을 수 없었습니다. 경쟁자가 동족인 styled-component인게 웃프죠.</p>
<h2 id="vanilla-extract">Vanilla-Extract</h2>
<blockquote>
<p>Use TypeScript as your preprocessor. Write type‑safe, locally scoped classes, variables and themes, then generate static CSS files at build time.
<a href="https://vanilla-extract.style/">https://vanilla-extract.style/</a></p>
</blockquote>
<p>  그러나 어느날 vanilla-extract라는 다른 스타일 라이브러리를 적극적으로 홍보하는 한 개발자를 목격했습니다. 런타임 없이 정적으로 css를 만들되 동적인 부분들은 css variable, function로 대처하는 모습이 emotion만 사용하던 저에겐 vanilla-extract에서 꽤나 매력적인 부분들이였습니다. </p>
<p>  만약에 emotion에서 vanilla-extract로 이동한다면, 즉 emotion로 구성된 스타일을 VE(vanilla-extract)로 완벽히 재구성할 수 있다면 전 기꺼이 그럴 것입니다. 그러나 잠시 생각해보아야 할 것들이 있었습니다.</p>
<ul>
<li><p>VE는 css module를 기본으로 삼습니다. emotion의 지역 스타일링 - <code>css</code> prop는 VE에 존재하지 않기 때문에 css module로 분리를 해야 할 것입니다. </p>
</li>
<li><p>styled-component처럼 emotion도 <code>styled(elem)</code> 함수를 제공해줍니다. 이 함수는 스타일링에 필요한 동적 값을 props로 받아와서 <code>elem</code>파라미터에 스타일을 입힌 컴포넌트를 반환합니다. 위 <code>css</code> prop에서도 생기는 경우인데, css variable로 해결할 수 있습니다.</p>
</li>
<li><p>emotion의 무한한 nested selector는 depth와 범주가 한정된 VE에서 진절머리가 날 것입니다. emotion은 &quot;&amp;&quot; 선택자로 자식의 자식에 거듭된 자식들까지... 무한히 중첩 선택을 거듭할 수 있는 반면에, VE의 스타일 함수는 자식을 선택할 수 없기 때문입니다. 오직 자신의 가상 클래스/엘리먼트와 자신을 선택하는 복합 선택자들만 가능합니다.
그러므로 nested selector들이 선택하는 엘리먼트들을 일일이 분리해야 할 것입니다. 귀찮지만 불가능한 점은 아닙니다. 예를 들자면...</p>
<pre><code class="language-ts">// Sidebar.styled.ts
export const LinksContainer = styled(&quot;div&quot;)({
  display: &quot;flex&quot;,
  justifyContent: &quot;space-evenly&quot;,
  &quot;&amp; &gt; a&quot;: {
    transition: &quot;transform 200ms&quot;,
    transform: &quot;translateY(0)&quot;,
    color: &quot;inherit&quot;,
    &quot;&amp;:hover&quot;: { // 선택된 a tag element의 :hover 선택자입니다.
        transform: &quot;translateY(-5px)&quot;
    }
  }
});</code></pre>
<p>위 emotion 스타일 코드는 아래로 바뀌어야 할 것입니다.</p>
<pre><code class="language-ts">// Sidebar.css.ts
export const linksContainer = style({
  display: &quot;flex&quot;,
  justifyContent: &quot;space-evenly&quot;,
})
export const link = style({
    transition: &quot;transform 200ms&quot;,
    transform: &quot;translateY(0)&quot;,
    color: &quot;inherit&quot;,
      selectors: {
        &quot;&amp;:hover&quot;: {
            transform: &quot;translateY(-5px)&quot;
        }
    }
})</code></pre>
<p>스타일 코드 수가 많아진다고 불평할 수도 있습니다. 그러나 한 스타일이 한 요소만을 스타일한다는 원칙을 강제받음으로써 엘리먼트 트리에서 좀 더 쉽게 엘리먼트의 스타일을 추적할 수 있습니다. (그리고 <code>globalStyle</code>로 우회할 수도 있습니다.)</p>
</li>
<li><p>자바스크립트 함수를 emotion 스타일링에 직접적으로 사용하는 경우가 두번째로 까다로운데, 두가지 솔루션이 있습니다.</p>
<ul>
<li>첫째는 위 문제와 같이 css variable로 해결하는 것입니다.</li>
<li>두번째는 css function를 사용하여 해결하는 것입니다. 종종 간단한 수학 함수들(min, max, 사칙연산 등)이나 다른 함수로 대체 가능한 경우가 있기 때문입니다. 가능한 css 계층에서 처리하는 것이 DX적으로, 미약하게 성능적으로 이점을 보기 때문에 이 방법을 가장 선호합니다.</li>
</ul>
</li>
<li><p>마지막으로 조건부 스타일링이 있습니다. 이 또한 두가지 해결책이 있습니다.</p>
<ul>
<li>고전적인 방법으로, <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/Attribute_selectors">CSS 속성 선택자</a>로 조건부 스타일링을 한 다음 조건에 따라 요소에 속성을 부여하는 방법입니다. 대개 커스텀 속성은 <a href="https://developer.mozilla.org/ko/docs/Learn/HTML/Howto/Use_data_attributes">data-* 속성</a>으로 전달합니다. (물론, <a href="https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Attributes">aria-* 속성</a> 등 여러 속성들도 상황에 따라 두루 사용됩니다.)<ul>
<li>최신 방법으로, <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/@container">@container CSS @규칙</a>이 있습니다. 그러나 조건에 사용할 수 있는게 제한적인 점, 아직 css variable의 동치 조건이 실험적인 점때문에 자주 사용하진 못할 것 같습니다.</li>
</ul>
</li>
</ul>
</li>
</ul>
<p>이와 같이 걱정되는 부분에선 저마다의 해결책이 있습니다. 사실, 간단하게 말해서 동적인 부분만 CSS 변수로 빼다놓는게 대부분입니다. 그럼에도 불구하고 이것이 까다로운건 모든 동적 로직을 css변수로 빼다놓으면 난잡할게 뻔하기 때문에 css함수를 통해 해결하고자 하기 때문입니다.</p>
<p>이제 실제로 VE로 마이그레이션한 코드들과 함께 그 방법들을 소개해드립니다.</p>
<h2 id="css-variable로-동적-값-전달하기">CSS Variable로 동적 값 전달하기</h2>
<p>앞서 소개했다시피 VE는 <em>zero</em> runtime css in js 라이브러리고, 기본적으로 정적입니다. 그러나 <code>@vanilla-extract/dynamic</code>의 <code>assignInlineVars</code>는 css variable를 런타임에서 동적으로 받게끔 도와줍니다.</p>
<pre><code class="language-tsx">// Content.css.ts
export const i = createVar();
export const shower = style({
  //...
  selectors: {
    &quot;&amp;::before&quot;: {
      //...
      backgroundColor: `color-mix(
        in srgb, 
        ${variableMap.palette.primary.main},
        rgba(1,1,1,0) calc(100% - 100% * (0.4 + 0.3 * (2 - ${i})))
      )`,
    }
  }
})</code></pre>
<pre><code class="language-tsx">// Content.tsx
&lt;div
  className={styles.shower}
  style={assignInlineVars({ [styles.i]: &quot;2&quot; })}
  /&gt;</code></pre>
<p>그 원리는 생각보다 간단합니다. 해당 엘리먼트의 style 속성에 직접 css variable를 할당하는 스타일을 할당하는 것입니다. css variable은 css의 기본 기능이므로 트레이드 오프를 걱정할 필요가 없을 것입니다.</p>
<blockquote>
<p><strong>과연 트레이드 오프가 없을까요?</strong></p>
<p><a href="https://legacy.reactjs.org/docs/faq-styling.html#are-inline-styles-bad">리액트 구 공식문서</a>와 위 Sam Magura가 <em>인라인 스타일은 동일한 스타일이 여러 요소에 적용돼야 할 경우 성능상 좋지 않습니다.</em> 라고 말한 것이 거슬려서 <a href="https://www.reddit.com/r/reactjs/comments/u22hmx/why_are_css_classes_generally_better_for/">저 공식문서 구절에 대한 레딧 포스트</a>도 찾아봤지만 유의미한 성능 리스크는 볼 수 없었습니다. 다른 객체 prop에도 있을법한 이야기 뿐이였어요.
만약 inline style에 가시적인 리스크가 있다면, 매우 슬픈 소식이므로 프로파일러를 돌리고 메모라이징을 하거나 그 수를 최대한 줄이도록 노력해야 할 것입니다. 물론 평소에도 css variable의 의존성을 줄이는게 좋겠죠.</p>
</blockquote>
<p>아무튼 vanilla-extract에서 동적 스타일링의 대부분은 이런 css variable로 대처할 수 있습니다. (나머지는 속성 선택자에요)</p>
<h2 id="css-function으로-동적-로직-대체하기">CSS Function으로 동적 로직 대체하기</h2>
<p>그러나 앞서 말했듯이, 모든 동적 로직을 css variable로 대체하는 것은 마음에 들지 않습니다.
그래서 css function로 대체할 수 있는 로직은 css variable로 대체하지 않으려고 합니다.</p>
<h3 id="투명도-동적-스타일링">투명도 동적 스타일링</h3>
<p>특정 엘리먼트가 살짝 투명하게 만들되 내용물까지 투명해지지 않았으면 좋을 때 배경 색만 반투명화하는 방법은 괜찮은 선택입니다. 문제라 하자면 <code>background-opacity</code> 같은 속성은 없고, 대신 <code>background-color</code>의 alpha channel은 있지만 hex color인 값에 어떻게 float 타입의 alpha를 부여할지가 문제였죠.</p>
<p>아래 스타일 코드는 그러한 문제를 겪었던 제가 <code>@mui/system/colorManipulator</code>에서 제공하는 <code>alpha</code> 함수로 문제를 대신 해결한 코드입니다. (<code>alpha</code>는 <code>@mui/system</code>에서도 제공합니다.)</p>
<pre><code class="language-ts">export const StyledAppBar = styled((props: AppBarProps &amp; MotionProps) =&gt; (
    &lt;AppBar {...props} component={motion.div} /&gt;
  ))&lt;{
    alpha: number;
  }&gt;(({ theme, alpha: alphaAmount }) =&gt;
    theme.unstable_sx({
      transition: &quot;all 300ms&quot;,
      backdropFilter: &quot;blur(5px)&quot;,
      zIndex: Layouts.HEADER,
      left: 0,
      right: 0,
      boxShadow: `0px 2px 4px -1px rgba(0,0,0,${
        alphaAmount * 0.2
      }), 0px 4px 5px 0px rgba(0,0,0,${
        alphaAmount * 0.14
      }), 0px 1px 10px 0px rgba(0,0,0,${alphaAmount * 0.12})`,
      backgroundColor: (theme) =&gt;
        alpha(theme.palette.primary.main, alphaAmount * 0.75),
     // more...
  }))</code></pre>
<hr>
<p>설명해 드리기 전에, 먼저 위 코드가 깔끔해진 결과부터 보여드리겠습니다.</p>
<pre><code class="language-ts">export const alphaAmount = createVar();
export const appBar = style({
  position: &quot;fixed&quot;, // StyledAppBar에서 position를 prop로 넘기던걸 style에 가져옴
  left: 0,
  right: 0,
  zIndex: Layouts.HEADER,
  boxShadow: `
    0px 2px 4px -1px rgba(0, 0, 0, calc(${alphaAmount} * 0.2)), 
    0px 4px 5px 0px rgba(0, 0, 0, calc(${alphaAmount} * 0.14)), 
    0px 1px 10px 0px rgba(0, 0, 0, calc(${alphaAmount} * 0.12))`,
  backgroundColor: `color-mix(in srgb,
    ${variableMap.palette.primary.main},
    transparent calc(${alphaAmount} * 0.75 * 100%)
  )`,
  backdropFilter: &quot;blur(5px)&quot;,
  transition: &quot;all 300ms&quot;,
  /// more...
})</code></pre>
<p>스타일 자체의 양과 모습은 라이브러리의 변화에 큰 상관이 없지만, 전 선언부가 인상적이였습니다. <code>styled</code> 함수는 최소한 위 VE 코드처럼 될 수 있는 반면, 최대한 위 emotion 코드처럼 될 수 있기 때문에 그 편차가 생각보다 높았습니다. 그에 비해 VE는 하나로 일관적이니 많이 깔끔해보입니다.</p>
<p>앞서 말씀드렸다시피, 동적 값인 <code>alphaAmount</code>는 css variable로 처리했습니다. 그러나 똑같이 동적 값인 투명도는 그렇게 하지 않았습니다. 왜냐하면 이미 CSS에서 <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/color_value/color-mix">color-mix() 함수</a>를 통해 충분히 <a href="https://una.im/color-mix-opacity/"><strong>상대적 색 투명도 부여</strong></a>가 가능하기 때문입니다.</p>
<blockquote>
<p>참고로 실험하다 알게 된 것인데, 어쩐 영문인진 몰라도 % 단위가 아니면 color-mix가 제 기능을 못해서 100%를 의도적으로 곱했습니다.</p>
</blockquote>
<p><code>color-mix()</code> 함수 뿐만이 아니라, <code>alphaAmount</code> 값의 재가공을 위하여 <code>calc()</code> 함수도 사용했습니다. 사칙연산을 css에서 할 수 있게 해주는 아주 고마운 함수입니다.</p>
<blockquote>
<p>---------------- WIP -----------------
이 글은 미완성입니다. 케이스들을 소개하다가 결론을 생각하고 있습니다.</p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[항해 플러스 코육대를 해봤다]]></title>
            <link>https://velog.io/@sharlotte_04/%ED%95%AD%ED%95%B4-%ED%94%8C%EB%9F%AC%EC%8A%A4-%EC%BD%94%EC%9C%A1%EB%8C%80%EB%A5%BC-%ED%95%B4%EB%B4%A4%EB%8B%A4</link>
            <guid>https://velog.io/@sharlotte_04/%ED%95%AD%ED%95%B4-%ED%94%8C%EB%9F%AC%EC%8A%A4-%EC%BD%94%EC%9C%A1%EB%8C%80%EB%A5%BC-%ED%95%B4%EB%B4%A4%EB%8B%A4</guid>
            <pubDate>Tue, 03 Oct 2023 14:56:26 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/sharlotte_04/post/ff9e6cf2-dbd8-4929-b9d2-e8476a5fa2d1/image.png" alt="">
<a href="https://hanghaeplus-coyukdae.oopy.io/">https://hanghaeplus-coyukdae.oopy.io/</a></p>
<p>이번 추석 때 간단히 토이 프로젝트를 하고 싶었는데 어느 오픈채팅방 공지에 해커톤같지만 해커톤같지 않은 이벤트를 홍보하고 있었다.</p>
<p>주제가 꽤나 간단했는데 상품은 그렇지 않은 모니터라서 갑자기 끌렸다.</p>
<h2 id="생각하기">생각하기</h2>
<p>그냥 행맨만 만드는건 그냥 Array#include 짬처리밖에 되지 않는다, 너무 간단해서 하루만에 끝날게 뻔했다. 사람이 좀 간절해져야 한다고 생각해서 <strong>유니코드</strong>와 <strong>디스코드 봇</strong>, <strong>웹페이지</strong>를 모두 뭉치는 혼종을 떠올렸다. 그렇게 해서 만들어진게 아래 다이어그램이다.</p>
<p><img src="https://velog.velcdn.com/images/sharlotte_04/post/c4fd7e29-36dc-41da-b5e9-03c851f40b0a/image.png" alt=""></p>
<p>서버단에서 행맨 게임 코어가 제공하는 인터페이스를 웹과 디코봇에서 따로 자신들의 클라이언트와 결합하게끔 호환 코드를 구체화하도록 만들었고, 이렇게 구체화된 코드는 프론트에선 express를 통한 data fetching과 socket.io, 디스코드 봇에선 라이브러리와 discord API 간의 내부적 통신으로 클라이언트에 도달한다.
그리고 왜 그랬는지 몰라도 플렛폼에서 만족 못했던 난 유니코드로 행맨을 하자는 기상천외한 미친 생각을 내버렸다.</p>
<p>즉, 이번 프로젝트는 무려 <strong>크로스 플렛폼 유니코드 행맨 멀티플레이게임</strong>인 것이다.</p>
<h2 id="구현하기">구현하기</h2>
<h3 id="행맨-코어-만들기">행맨 코어 만들기</h3>
<p>사실 행맨은 아주 간단한 게임이다.
주어진 글자들 중 하나를 제출하면 글자를 없애고, 정답 글자들에 있으면 같은 글자들을 모두 공개하는 대신 만약 없다면 기회가 하나 차감되어서 0에 도달하면 지는 구조다.
이 코어에서 한번 비틀기 위해 &quot;주어진 글자들&quot;의 범주를 &quot;알파뱃&quot;에서 &quot;유니코드&quot;로 확장시켰다.</p>
<p>처음엔 유니코드에 대해 전혀 몰라서 이런저런 시행착오를 겪었는데, 그 중 가장 끔직한게 유니코드가 여러개인 문자가 존재한단 것이였다. 문자를 유니코드 배열로 간주하고, 문자열 배열을 유니코드 이차원 배열로 간주해야 하므로 문자 동치와 같은 대부분의 영역에서 나쁜 DX를 경험했다.
그러나 MDN에 따르면, <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/charCodeAt">굳이 unicode</a>가 아니더라도 단일 숫자로 UTF-16에 호환되는 문자들을 인코딩/디코딩할 수 있는 함수가 있었다. 이름이 <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/codePointAt">charPointAt</a>인 함수인데, 비슷한 charCodeAt가 먼저 보여서 놓쳤던 것이다. 인코딩은 <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/fromCodePoint">fromCodePoint</a>다.</p>
<p>결국 유니코드에서 &quot;UTF-16로 인코딩되는 유니코드&quot;로 바뀌었지만 그래도 많으니 만족한다.</p>
<p>추가로, 주어진 단어들의 범주가 기하급수적으로 늘어났는데 어떻게 선별하냐는 의문도 자연스래 생겼었다. 그냥 중복이 안될때까지 계속 랜덤으로 돌렸다. 비효율의 극치이라서 조금 아쉬운 엑션이긴 하다.</p>
<p>이렇듯이 행맨 코어는 행맨이란 게임의 기본적인 로직과 유니코드와의 결합을 이룬 상태인데, 여러 플렛폼에서 똑같이 호환되기 위해선 행맨 게임 객체가 스스로 id를 만들어 갖고 있어야 한다는 결론이 났다. uuid는 또 어떻게 뽑나 하고 귀찮니즘이 쇄도했지만 crypto 내장 라이브러리의 <code>randomUUID</code> 함수가 이미 있어서 생각보다 정말 쉽게 해결했다.</p>
<p>유저 시스템은 따로 만들지 않았다. 서버와 클라이언트가 있으니 어쩌다가 결국 필요해지지 않을까 싶었지만 결국 필수적인 상황은 오지 않았다. 부가기능을 위해 필요할 것 같다.</p>
<h3 id="디스코드-봇">디스코드 봇</h3>
<p>일반적으로 어떠한 프로그램을 작성할 때, 첫번째로 만들기 편한게 CLI고 두번째로 편한게 이러한 챗봇이라고 생각한다. 공통점은 클라이언트에 대한 관심이 비교적 크게 줄어든다는 점이다. 때문에 디스코드 봇으로 먼저 개발을 주도했다. </p>
<p>디스코드 봇의 개발은 생각보다 순조로웠다. 애초에 처음부터 설계를 해두고 들어가니 어디서 책임을 잘라내야 하는지가 명확하게 보이는 느낌이였다. 명령어 구조까지 직접 짜기엔 부담스러워서 <a href="https://discordx.js.org/">Discordx 프레임워크</a>를 사용했다. 데코레이터로 명령어를 구사할 수 있도록 마련해주는 도구다.</p>
<p>또한 명령어에 맞춰 구현할 때 멀티플레이를 계속 의식하면서 디스코드 봇에서 핵심적인 로직을 담당하지 않도록 작성했다. 봇 자신 또는 디스코드와 연관된 작업 외의 게임적인 부분들은 모두 코어 인터페이스를 호출하여 해결했다.
물론 쌍방향 통신은 한쪽에서 보내기만 해서 될 일이 아니다. 행맨 코어에서도 디스코드 봇으로 데이터를 보내기 위해 디스코드 봇이 행맨 코어에 이벤트를 구독하도록 만들었다. 이제 웹페이지 유저가 게임을 플레이해도 중요한 이벤트들은 디스코드 봇에게도 알려져서 똑같이 디스코드 유저도 볼 수 있게 된다.</p>
<h3 id="프론트">프론트</h3>
<p>물론 이런 방법은 웹페이지에선 통하지 않으므로 거기선 거기대로의 통신 방법을 구축했다.
아래와 같이 아주 끔찍하고 결합적인 콜백 지옥의 함수 하나가 소켓 통신을 모두 담당하고 있다.
서버쪽에서 게임 이벤트를 클라이언트로 전달해주고, 클라이언트에서 오는 이벤트를 중간에 처리한 다음 게임에 보내주는 중계기의 역할을 소켓이 하고 있다.</p>
<pre><code class="language-ts">io.on(&quot;connection&quot;, (socket) =&gt; {
  console.log(&quot;a user connected&quot;);
  socket.on(&quot;join&quot;, (gameId, callback) =&gt; {
    console.log(&quot;a user joined to&quot;, gameId);
    const game = GameManager.games[gameId];
    if (!game) return;

    const getGamePayload = (): WebsocketGamePayload =&gt; ({
      words: Array.from(game.words),
      correctWords: Array.from(game.correctWords),
      misCorrectWords: Array.from(game.misCorrectWords),
      life: game.life,
      currentAnswer: Array.from(game.correctAnswerWords)
        .map((answerCharPoint) =&gt;  game.correctWords.has(answerCharPoint) ? displaySupportedUnicode(answerCharPoint) : &quot;_&quot;)
        .join(&quot;&quot;),
    });
    callback(getGamePayload());
    game.on(&quot;WORD_TRIED&quot;, (word, isSuccessed) =&gt; {
      socket.emit(&quot;WORD_TRIED&quot;, word, isSuccessed, getGamePayload());
    });
    game.once(&quot;GAME_ENDED&quot;, (isWin) =&gt; {
      socket.timeout(3000).emit(&quot;GAME_ENDED&quot;, isWin, getGamePayload());
    });
  });
  // for real-time game list updating
  GameManager.on(&quot;GAME_STARTED&quot;, (gameId) =&gt; {
    socket.emit(&quot;GAME_STARTED&quot;, gameId);
  });

  socket.on(&quot;START_GAME&quot;,
    (
      correctAnswer: string,
      wordAmount: number,
      callback: (id: string) =&gt; void
    ) =&gt; {
      const game = GameManager.startGame(correctAnswer, wordAmount);
      callback(game.id);
    }
  );
  socket.on(&quot;WORD_TRIED&quot;, (gameId, word) =&gt; {
    GameManager.games[gameId].try(word);
  });
  socket.on(&quot;disconnect&quot;, () =&gt; {
    console.log(&quot;user disconnected&quot;);
  });
});</code></pre>
<p>서버와 클라이언트 모두 만들어야 하니 평소와 같았더라면 Next.js로 퉁치면 될 일이였다.
그러나 이번에는 그렇게 할 수 없다! <strong>Next.js는 웹소켓 통신, 더 나아가 Socket.io 통신과 호환되지 않는다.</strong> ...물론 page dir에서 커스텀 서버로 만들거나 next.js에서 RCC만 해대는 식으로 가능은 하지만 불쾌한 어거지로 느껴졌다.
그래서 마감일 하루 전에 vite로 전환하기로 결정했다. 그리고 10분만에 끝났다. 맙소사!</p>
<p>Vite는 next.js와 같은 page 라우팅 방식이 없어 아쉬웠는데 <a href="https://github.com/hannoeru/vite-plugin-pages">역시나 이미 있었다!</a>. Vite plugin pages 플러그인으로 손쉽게 next.js pages dir과 같은 형태로 구축했다.</p>
<p>또한 쌍방향 통신을 위해 <a href="https://socket.io/">socket.io</a>를 사용했고, 간편한 모달창 보여주기를 위해 <a href="https://sweetalert2.github.io/">SweetAlert2</a>를 사용했다.
스타일 라이브러리나 UIKit까진 필요없어서 css module를 사용했다. </p>
<h2 id="배포하기">배포하기</h2>
<p>그거 아는가? Vercel는 Next.js 뿐만이 아니라 여러 번들러들도 자유롭게 지원해준다.
이것 덕분에 Vite로 Vercel에 배포할 수 있게 되었다.
웹프론트는 이렇게 간단히 해결됐지만 Node.js 서버가 정말 문제였다. github education도 전에 따놓아서 heroku를 통해 배포할 생각이였지만 모노레포라서 heroku가 최상위 경로만 고집해 미칠 것 같았다. 결국 포기하고 Google Cloud Platform으로 이동했는데 많이 힘들 것 같다.</p>
<h2 id="끝">끝</h2>
<p>여러모로 억까도 많고 힘들고 이게 3일만의 결과물이 맞나 싶을 정도로 크지만 역으로 이정도로 짜릿한 적은 올해에 들어 오랜만인 것 같았다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[포트폴리오 다시 만들기]]></title>
            <link>https://velog.io/@sharlotte_04/%ED%8F%AC%ED%8A%B8%ED%8F%B4%EB%A6%AC%EC%98%A4-%EB%A7%8C%EB%93%A4%EA%B8%B0</link>
            <guid>https://velog.io/@sharlotte_04/%ED%8F%AC%ED%8A%B8%ED%8F%B4%EB%A6%AC%EC%98%A4-%EB%A7%8C%EB%93%A4%EA%B8%B0</guid>
            <pubDate>Thu, 22 Jun 2023 16:07:23 GMT</pubDate>
            <description><![CDATA[<h1 id="옛-포트폴리오-프로젝트-되짚기">옛 포트폴리오 프로젝트 되짚기</h1>
<p><img src="https://velog.velcdn.com/images/sharlotte_04/post/73ec0368-3258-4447-aacb-12ec25f6d803/image.png" alt=""></p>
<p>웹 공부를 하는 동시에 만들었던 이전의 포트폴리오 프로젝트는 거의 동반자 급으로 다양한 인터렉션들을 구현하는 놀이터가 되어주었고 많은 교훈과 경험을 선사해줬다. 그러나 이건 말 그대로 놀이터일 뿐 포트폴리오로써 평가하자면 쓰래기보다 못한 망작이다. 스타일은 줏대없이 남발되고 인터렉션은 뇌절에 뇌절까지 닿아 혼미해질 지경이며 버그는 사방천지에 깔려있어 뭐부터 손대야 할지 감이 안잡힌다. 즉, 회생 불가 상태에 도달한 프로젝트인 것이다. </p>
<p>대개 이런 프로젝트는 애착때문에 더 회생이 안되는건데, 그래서 다른 프로젝트들은 충분한 시간을 두고 나중에 돌아와서 갈아엎곤 했으나 이번엔 조금 다르다. 다른 프로젝트와 달리 이 포트폴리오는 내 창창할 청년의 앞날을 보여줄 간판이자 자기소개서여야 한다. 그런데 나 자신을 투사하는데 회생 불가인 프로젝트를 보여줄수야 없다. 결국 시간을 두고 내버려두자니 계속 후회만 쌓이고 <strong>오히려 애착이 더 심해지는</strong> 역효과가 발생했다.</p>
<p><strong>그래서 극단적으로 그냥 버리기를 선택했다.</strong></p>
<p>애초에 시작부터 잘못됐다. 원래 프로젝트는 기획 -&gt; 디자인 -&gt; 구현의 삼박자를 따라 완만하게 시작하는게 일반적이지만 나는 구현하면서 공부하기를 원해서 구현 -&gt; 디자인 -&gt; 기획 역순을 따라버린 것이다. 이제 경험을 했으니 이걸 가지고 처음부터 다시 시작해야 한다.</p>
<h1 id="포트폴리오는-엄연히-정적-웹사이트다">포트폴리오는 엄연히 정적 웹사이트다.</h1>
<p>이전 포트폴리오는 아주 혼파망이였다. 포트폴리오 주제에 프로젝트 자체 웹사이트까지 내포할려고 했으며 로그인에 SNS연동(OAuth2)에 방문 통계까지 있었다. 심지어 이 때문에 Next.js로 했었는데... 아니 이렇게까지 할 이유가 있나? 굳이 Next.js에서 SSR로 내려보낼 이유가 있는가? 그냥 Github Page에서 SSG로 똑같은걸 바로바로 보내주면 안되는가? 애당초 왜 포트폴리오가 서버 기능을 가지고 로그인을 지원해야 하는가?</p>
<p>포트폴리오는 포트폴리오 그 자체로만 기능하면 되지 그 이상을 가지면 배보다 배꼽이 커질 뿐이다. <strong>여러 잡다한 기능들을 모두 빼고 담백하게 시작하자.</strong></p>
<h2 id="그러니-노션부터-시작하자">그러니 노션부터 시작하자.</h2>
<p>당장 포트폴리오 프로젝트를 다시 만들어서 모든걸 처음부터 다시 시작하는건 과거를 반복하는 일과 같다. 이제 정상적으로 기획 -&gt; 디자인 -&gt; 구현 스탭을 따라야 한다. 그러나 기획은...지루하다. 그냥 끄적이고 계획하기만 해선 답이 안보인다. 즉각적인 결과물은 여전히 요구된다. 
그러니 노션부터 시작하자.</p>
<h2 id="사전-조사">사전 조사</h2>
<p>일단 노션 포트폴리오를 쓰기 위해 많은 수의 노션 포트폴리오를 찾아봤다. 대부분의 포트폴리오가 공통적으로 가지고 있는 특징들이 있는데,</p>
<ol>
<li>기술 스택: 언어, 프레임워크, 라이브러리 등 개발 환경에 관해 나열하거나 경험을 섞어 설명한다.</li>
<li>대외활동<ul>
<li>프로젝트: 팀 프로젝트들. 주체가 확실하니 명확한 기술 스택과 기간 등의 정보들을 가지고 있다.</li>
<li>수상 경력: 위 프로젝트와 엮어서 수상을 소개하거나 그냥 독립적으로 나열한 경우도 있다.</li>
<li>그냥 활동: 프로젝트도 수상도 없지만 활동 자체로도 유의미하긴 하다.</li>
</ul>
</li>
<li>연락처: 자신에게 바로 도달할 수 있는 여러 SNS, 이메일, 이름을 공개한다. 솔직히 이건 기본이긴 하다.</li>
<li>경력: 나와는 관련없는 일이지만 경력자들은 연수 기간이나 인턴, 업무 기간을 같이 나열한다. 이력서로 쓰기 위함인듯 하다.</li>
<li>공부한 것들: TIL(Today I Learn)이나 여러 자잘한 노트 또는 포스트들을 아카이빙한 노션 문서들을 링크로 달아놓는데 실용성에 의문이 든다. 나라면 내 블로그 최근 포스트를 달 것 같다.</li>
</ol>
<p>그 외 자잘한 것들로</p>
<ul>
<li>취미: 자신의 성격이나 게임, SNS들을 공개한다.</li>
<li>학력: 대학교부터 고등학교나 유학, 심지어 실시간 학점 공개까지 하는 경우도 있다.</li>
</ul>
<p>이렇게 정리를 해보니 내가 할 수 있는게 많이 없어보이는데, <strong>오히려 이것이 정상이다.</strong> 코후 34개월 개발자인 대학교 1학년이 괴수가 아닌 이상 어떻게 많은 커리어를 쌓겠는가? 포트폴리오는 곧 그 사람의 시간을 나타내니 오히려 내 짧은 시간을 덧없이 보여주는게 더 깔끔할 것이다. </p>
<h2 id="설계">설계</h2>
<p>맨 위엔 이름과 3줄 이내의 간단한 소개가 있을 것이다. 이름은 많은 포트폴리오가 페이지 제목 자체를 사용하던데 생각보다 좋은 것 같다. 추가로 본명을 먼저 쓰는게 바로바로 눈에 띄니 <a href="https://dding-g.notion.site/dding-g/db01cccfe7b2421c9907c70e29c4055f">본명 + 포트폴리오</a>가 더 좋은 것 같다. 앞서 링크된 포트폴리오에선 <a href="https://donghwi-93.notion.site/donghwi-93/IU-fdad75fe243a4fd9958aae2e7b22ac24">다른</a> <a href="https://joel-lee.notion.site/Geon-Lee-0a2ead807ec24791b5f75a5d0974fca8">포트</a><a href="https://local-taurus-f80.notion.site/Hae-Chan-Lee-34ab94cb0673439ebb0318913406d5c9">폴리오</a><a href="https://parallel-pulsar-dc0.notion.site/f262bb5e5ad547e08cc1986c26cddc39">들</a> <a href="https://dev-kimwest00.notion.site/dev-kimwest00/cf05372d1bc94b5da6127f3e34d8bf76">처럼</a> 프로필 사진을 페이지 아이콘으로 쓰던데 이덕분에 레이아웃을 잡아먹는 거대한 어셋이 사라져서 좋다. 다만 대조적으로 <a href="https://mingyu94.notion.site/mingyu94/Kim-Mingyu-e1eabc1b47a24f91a584a8b5b2313989">다른</a> <a href="https://tngusmiso.notion.site/tngusmiso/fd824605fe6045d6928369a7d8cadcd4">포트폴리오</a>에선 오히려 프로필 사진을 왼쪽에 두고 빈 오른쪽 공간에 연락처와 자기소개를 하던데 이것도 좋은 방법이다.</p>
<p>먼저 <strong>소개</strong>에선 젊은 주제에 명품화가 되겠자고 시나 명언을 쓰는건 오히려 본질과 멀어질 위험이 있다. 솔직하게 적자. 이메일, SNS, 블로그를 적으면 좋을 것 같다.
이제 <strong>기술</strong>을 적는다. 경험한 것중에 원하는만큼 다 적는다. &quot;이것까지 적어야 할까?&quot;는 나에겐 배터진 소리니깐.
이제 <strong>프로젝트</strong>를 적는다. 대외활동으로 만든 프로젝트가 겨우 2개밖에 안되고 수상 경력은 당연히 없으니 생략한다. 이것때문에 많은 레포지토리의 정상화를 꿈꾸며 리팩토링을 거쳤는데, 일단 가능한 것만 먼저 나열한다. 개인프로젝트라 할지언정 그 수는 적지 않다.
이제 <strong>활동</strong>을 적는다. 대학교, 외주, 학회같이 굵직하고 유의미한 것들을 적는다. 과장없이 그거로도 이미 충분하다.</p>
<h2 id="이행">이행</h2>
<p><a href="https://sharlottes.notion.site/bef294c618b141268217bea8aa0c8ba3?pvs=4">결과 포트폴리오</a>
노션이 생각보다 더 다양한 기능들을 제공해주고 있긴 한데 그만큼 아쉬운 부분들이 많아서 아깝다. 역시 포폴의 끝은 웹사이트가 맞다.
주변으로부터 이쁘단 소릴 많이 들어서 뿌듯하다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Typescript로 IoT 개발하기]]></title>
            <link>https://velog.io/@sharlotte_04/Typescript%EB%A1%9C-IoT-%EA%B0%9C%EB%B0%9C%ED%95%98%EA%B8%B0</link>
            <guid>https://velog.io/@sharlotte_04/Typescript%EB%A1%9C-IoT-%EA%B0%9C%EB%B0%9C%ED%95%98%EA%B8%B0</guid>
            <pubDate>Fri, 02 Jun 2023 16:30:13 GMT</pubDate>
            <description><![CDATA[<h1 id="동기-span-stylecolorgrayemmotivationemspan">동기 <span style="color:gray"><em>Motivation</em></span></h1>
<p>저는 중학교 자유학기제때 아두이노를 접하고 고등학교에서 동아리에 들어가며 아두이노 개발을 경험해보고 이 아두이노에 대해서 몇가지 단점을 느꼈습니다.</p>
<ol>
<li>본격적인 C 입문이 아니면 그 개발 자체에 근본적인 제약이 걸립니다. <strong>분야 입문에 있어서 C 공부가 강제됩니다</strong>.</li>
<li>즉, 라이트한 목적으로 IoT나 임베디드를 찍먹할 사람에게 C라는 저급 언어를 본격적으로 입문해야 한단 압박감은 <em>비교적 가벼운</em> 이미지를 가진 아두이노에 모순된 태도를 지니고 있습니다.</li>
<li>가볍게 IoT를 만들 수단 자체가 존재하지 않습니다. 파이썬으로 데스크톱과 연결하는 방법이 있다지만 업로드가 불가하니 개발 환경에 종속적입니다.</li>
</ol>
<p>많은 교육과 키트들은 모듈과 받아쓰라는듯한 코드만 제공해주고 C에 대한 세부적인 설명은 하지 않아서 뭔갈 응용할려면 C를 따로 배울 필요가 있었습니다. 물론, <a href="https://arduino.stackexchange.com/a/824">아두이노의 C는 완전한 C가 아닙니다</a>. 아두이노 IDE를 통한 임베디드 개발도 극한의 성능과 메모리를 다룰 임베디드 세상에선 꽤나 고급이겠지만 그럼에도 불구하고, 여전히 IoT 입문자들에게 C는 어렵습니다. 러닝 커브가 요구 사항에 비해 너무 비대합니다. Javascript를 줄곧 개발해오던 저는 Microsoft가 하고 있는 새로운 시도를 발견했습니다.</p>
<h1 id="해결-a-hrefhttpsmicrosoftgithubiodevicescriptspan-stylecolor2eb6ffdevicescriptspana">해결: <a href="https://microsoft.github.io/devicescript/"><span style="color:#2EB6FF">DeviceScript</span></a></h1>
<p><img src="https://velog.velcdn.com/images/sharlotte_04/post/e37c87eb-d60e-4899-aa58-05421cf0a19b/image.png" alt=""></p>
<p><a href="https://microsoft.github.io/devicescript/">DeviceScript</a>는 TypeScript의 서브셋 언어로, Microsoft가 작년 12월부터 릴리즈를 시작한 정말 따끈따끈한 반년된 언어입니다. 이제 타입스크립트를 통해 <a href="https://microsoft.github.io/devicescript/devices/rp2040">라즈베리파이 Pico</a>나 <a href="https://microsoft.github.io/devicescript/devices/esp32">esp32 개발 보드</a>에서 IoT 개발을 진행할 수 있습니다.</p>
<h2 id="특징">특징</h2>
<h3 id="런타임은-nodejs가-아닙니다">런타임은 Node.js가 아닙니다.</h3>
<p>DeviceScript는 <a href="https://microsoft.github.io/devicescript/api/vm">wasm 가상머신</a>에서 실행됩니다. 웹어셈블리어로 되었기 때문에 브라우저와 Node.js 런타임에서 실행할 수도 있습니다. 그래도 DeviceScript 내부에선 이러한 Node.js의 부재 때문에 Node.js에서 <code>fetch</code>를 호출할 수 없듯이, <code>process</code>같은 Node.js API를 활용할 수 없습니다.</p>
<h3 id="그러나-nodejs에서-개발할-수도-있습니다">그러나 Node.js에서 개발할 수도 있습니다.</h3>
<p>물론 Node.js로 돌릴 방법이 아에 없는건 아닙니다. <a href="https://microsoft.github.io/devicescript/developer/simulation#nodejs-simulation">Node.js Simulation 문서</a>에선 <code>src/sim/app.js</code>를 Node.js 진입점으로 삼아 기기 또는 시뮬레이터와 Websocket 연결을 하여 실행을 한다고 설명합니다.
<img src="https://velog.velcdn.com/images/sharlotte_04/post/750881f1-194d-42d1-b422-ccb36c0657fa/image.png" alt=""></p>
<h3 id="typescript와-완전히-동일하진-않습니다">TypeScript와 완전히 동일하진 않습니다.</h3>
<p><a href="https://microsoft.github.io/devicescript/language">DeviceScript 언어 참조 문서</a>에 따르면 DeviceScript는 TypeScript와 완전히 같진 않은데, 내부적으로 실행되는 환경이 다르다보니 생긴 차이로 추측됩니다. 이러한 차이는 생각보다 많지는 않은데 개발자의 낭만을 채워주는 부분들이 몇몇 보였습니다.</p>
<ul>
<li><code>with</code>문, <code>eval</code>함수, 네임스페이스, <code>arguments</code> 키워드와 같이 비권장되고 deprecated된 문법이 없습니다. <del>레거시에서 해방!</del></li>
<li>제너레이터 함수가 없습니다. 비동기에 대해선 아래 문단에서 추가로 설명하겠습니다.</li>
<li>속성을 비열거형으로 표시할 수 없습니다.</li>
<li><code>for in</code> 문이 없어지고 <code>for of</code>문만이 남았습니다. <del>이제 안햇갈린다!</del></li>
<li><code>==</code>와 <code>!=</code> 연산자가 없는 대신 <code>===</code>, <code>!===</code>로 완전 비교만이 남았습니다. <del>동치 비교에서의 억까를 근본적으로 퇴치!</del></li>
<li>RAM 절약을 위해 일부 객체(Fiber, Register, Event, 정적 buffer, 함수, 문자열과 숫자)는 임의로 속성을 제어할 수 없습니다.</li>
</ul>
<p><strong>아래는 향후 개발될 기능들입니다.</strong></p>
<ul>
<li><code>getter</code>, <code>setter</code>가 없습니다.</li>
<li>템플릿 리터럴은 있는데 태그된 템플릿 리터럴은 없습니다. (<code>styled.div``</code> 같은...)</li>
<li>클래스의 정적 필드의 초기화가 없습니다.</li>
<li>이넘(enum)을 런타임 배열로 사용할 수 없습니다.</li>
</ul>
<h3 id="비동기가-약간-다릅니다">비동기가 약간 다릅니다.</h3>
<p>이 이야기는 따로 문서가 있는데, 요약해서 말하자면 <strong>DeviceScript의 비동기는 진짜로 <a href="https://www.wikiwand.com/ko/%ED%8C%8C%EC%9D%B4%EB%B2%84%20(%EC%BB%B4%ED%93%A8%ED%84%B0%20%EA%B3%BC%ED%95%99)">멀티스레드(파이버)</a>입니다.</strong> 그런데 <strong>await async는 비동기가 아닙니다.</strong> await/async 동작 자체는 달라지지 않지만 모든 비동기 함수는 await를 사용해야 하는 특징이 있습니다. 비동기로 실행하고 싶다면 <code>Function.start</code> 함수를 사용할 수 있습니다.
또한 <code>Promise</code>는 타입스크립트처럼 사용할 수 있으나 런타임에 없기 때문에 속성을 가질 수 없습니다. </p>
<h3 id="observables를-기본적으로-지원합니다">Observables를 기본적으로 지원합니다.</h3>
<p>DeviceScript는 특이하게도 Rxjs의 <a href="https://microsoft.github.io/devicescript/api/observables">Observables를 내장</a>으로 지니고 있습니다. Rxjs의 이해는 <a href="https://velog.io/@teo/rxjs">테오님의 포스트</a>를 참고해보세요.
옵저버블은 데이터 흐름을 파이프라인(pipe)의 조합과 연결로 제어할 수 있습니다. 임베디드에선 센서를 통한 데이터 흐름이 많기 때문에 <a href="https://microsoft.github.io/devicescript/api/core/registers">레지스터(Register)</a>에 <code>read</code>, <code>write</code> 다음으로 <code>subscribe</code>를 두었는데, 이덕분에 observables로 확장할 수 있던 것 같습니다.</p>
<p>이러한 옵저버블의 사용은 아래와 같이 </p>
<table>
<thead>
<tr>
<th><img src="https://velog.velcdn.com/images/sharlotte_04/post/6cad50d8-7950-45d4-bc41-5666e1df7cce/image.png" alt=""></th>
<th><img src="https://velog.velcdn.com/images/sharlotte_04/post/a5324465-f857-4eb6-95bd-fe9a8bf9dfe5/image.png" alt=""></th>
</tr>
</thead>
</table>
<p>왼쪽의 연속된 데이터들에서 <em>중복을 거르고 싶다</em> 할 때 </p>
<pre><code class="language-ts">temperature
    .pipe(threshold(1))
    .subscribe(temp =&gt; console.log(temp))</code></pre>
<p>와 같이 pipe를 통해 옵저버블 파이프라인 함수를 연속적으로 사용할 수 있습니다. 이러한 파이프라인은</p>
<table>
<thead>
<tr>
<th align="center"><img src="https://velog.velcdn.com/images/sharlotte_04/post/26ada141-310f-4a9c-b866-7ac424933293/image.png" alt=""></th>
<th align="center"><img src="https://velog.velcdn.com/images/sharlotte_04/post/2e188dac-d5e8-4088-9d21-368e10d17ee4/image.png" alt=""></th>
<th align="center"><img src="https://velog.velcdn.com/images/sharlotte_04/post/1e8b3a66-e2ec-4ba9-b71b-de81790fc41a/image.png" alt=""></th>
</tr>
</thead>
<tbody><tr>
<td align="center">filter나</td>
<td align="center">map이나</td>
<td align="center">reduce도 있고</td>
</tr>
</tbody></table>
<table>
<thead>
<tr>
<th align="center"><img src="https://velog.velcdn.com/images/sharlotte_04/post/91f9643b-8980-426b-84a5-b1a75fc386e5/image.png" alt=""></th>
<th align="center"><img src="https://velog.velcdn.com/images/sharlotte_04/post/4ea6557e-c915-4644-92fe-6b0a46b05cb2/image.png" alt=""></th>
</tr>
</thead>
<tbody><tr>
<td align="center">debunce나</td>
<td align="center">throttle도 있습니다.</td>
</tr>
</tbody></table>
<p>옵저버블의 파이프라인 함수는 그저 주어진 값을 처리하여 반환하는 일개 함수에 불과하므로 직접 만들수도 있습니다. 개인적으로 IoT개발도 새로운데 옵저버블 실전 응용이란 경험까지 얻어 많이 신납니다.</p>
<h3 id="테스트-라이브러리를-기본적으로-지원합니다">테스트 라이브러리를 기본적으로 지원합니다.</h3>
<p>DeviceScript는 Jest같은 <a href="https://microsoft.github.io/devicescript/api/test">테스트 코드도 지원</a>합니다. 간단하게 <code>describe</code>, <code>test</code>, <code>expect</code>, <code>beforeEach</code>, <code>afterEach</code> 등이 있습니다. IoT에서 완전히 통제된 테스트를 하는건 힘들지만 그래도 있으니 좋은 것 같습니다.</p>
<h3 id="버퍼를-직접-만들-수-있습니다">버퍼를 직접 만들 수 있습니다.</h3>
<p>임베디드에선 극한의 메모리 절약이 중요합니다. <del>이미 타스를 끌어들인 마당에 뭘 줄이겠냐만은</del> 그래도 <code>Buffer</code>를 통해 직접적인 메모리 컨트롤도 지원하고 있습니다.
<a href="https://microsoft.github.io/devicescript/api/core/buffers">DeviceScript의 Buffers 문서</a>에 따르면 <code>Buffer</code>를 통해 메모리를 할당받아 읽고 쓸 수 있다고 합니다. 버퍼를 만드는 받벚은 다양한데, 일반적으로 <code>new Buffer()</code> 생성자 함수를 사용하는 방법이 있고 템플릿 리터럴로 빠르게 읽기 전용 버퍼를 만드는 방법도 있습니다.</p>
<pre><code class="language-ts">const buf: Buffer = hex`00 ab 12 2f 00`</code></pre>
<p>추가로 길이가 가변적인 패킷이 있는데, 극한의 절약에 유용할 것 같습니다.</p>
<pre><code class="language-ts">const lamp = new ds.Led()
ds.packet.setLength(2)
ds.packet.setAt(0, &quot;u0.16&quot;, 0.7)
lamp.intensity.write(ds.packet)</code></pre>
<h2 id="이제-시작해봅시다">이제 시작해봅시다.</h2>
<p>이 글에선 DeviceScript의 Lightbulb blinking 예제에서 더 나아가 survo motor의 제어 + 가변 저항을 통한 제어까지 구현한 코드를 보여드리지만 DeviceScript가 워낙에 추상화가 잘된 덕에 혼자 문서를 읽어도 쉽게 적응할 수 있습니다. 공식문서의 Getting Start도 잘 마련되어있고, Github issue의 응답률도 매우 좋은 편입니다. (경험상 약 2시간) 그러므로 이 글에선 모든걸 다루지 않고, 문서 위주로 첨언을 합니다.</p>
<h3 id="visualstudiocode로-시작하기">VisualStudioCode로 시작하기</h3>
<p>Visual Studio Code에는 DeviceScript Extension이 있어서 매우 편리한 명령우 팔레트 도구와 dashboard를 사용할 수 있습니다. 이 확장은 <strong>지역 CLI</strong>를 기반으로 실행되므로 <code>nnm install @devicescript/cli</code>를 통해 지역적으로 cli를 설치해야 합니다.
<img src="https://velog.velcdn.com/images/sharlotte_04/post/faa93afb-34d8-4d16-abd1-8cd1bb56c710/image.png" alt="">
cli가 설치되지 않으면, 즉 <code>node_modules/.bin</code>이 없거나 비어있으면 deviceScript에게 뭔갈 실행시킬 때 위와 같은 에러 메시지가 등장합니다.</p>
<p>그 외엔 딱히 할 게 없습니다. 그 다음 문서들을 계속 읽어보는걸 잊지 마세요.
시작은 f5로 디버깅을 하는 방법도 있고, 오른쪽 위 툴바에서 버튼을 눌러도 됩니다.
<img src="https://velog.velcdn.com/images/sharlotte_04/post/81c75ca8-1373-4b70-991f-3293c1078587/image.png" alt=""></p>
<h3 id="commandlineinterface로-시작하기">CommandLineInterface로 시작하기</h3>
<p><del>..왜?</del>
CLI로 시작할 경우 연결과 대쉬보드에 관해선 따로 웹페이지를 재공해줍니다. 기본적으로 <code>http://localhost:8081/</code> 이며 들어가면 아래와 같이 연결, 대쉬보드가 모두 보입니다.
<img src="https://velog.velcdn.com/images/sharlotte_04/post/303786aa-74d5-4c9e-91ba-64aae98845ff/image.png" alt=""></p>
<p>참고로 <strong>@devicescript/cli는 전역 전용이 아닙니다.</strong></p>
<p>DeviceScript vscode 확장과의 상호작용을 상시로 하는데, 만약 전역으로 둔다면 <strong>언제 어디서든지 vscode의 devicescript가 멋대로 실행하는</strong> 대참사가 발생할 수도 있습니다. 메인테이너도 비권장하고, 그 외에 여러 문제가 있어서 개인적으로 vscode를 권장드립니다. </p>
<h2 id="예제">예제</h2>
<h3 id="led-깜빡이기">LED 깜빡이기</h3>
<pre><code class="language-ts">import { pins } from &quot;@dsboard/adafruit_qt_py_c3&quot;
import { startLightBulb } from &quot;@devicescript/servers&quot;
const lightBulb = startLightBulb({
    pin: pins.A1, //보드에 따라 알아서 pins 가져오세요.
})
setInterval(async () =&gt; {
    await lightBulb.toggle()
}, 500)</code></pre>
<p>DeviceScript 공식문서에서 알려주는 것과 별달리 차이가 없지만 여기서 마이크로소프트를 믿고 interval를 1로 때려박다가 불상사가 터질지도 모르니 궁금하면 한번 해보세요. 전 아래 &quot;loopback-rx-ovf&quot;가 터지며 거의 4일을 날렸습니다.</p>
<h3 id="servo-motor-돌리기">Servo motor 돌리기</h3>
<pre><code class="language-ts">import { startServo } from &quot;@devicescript/servers&quot;
import { pins } from &quot;@dsboard/adafruit_qt_py_c3&quot;

const servo = startServo({
    pin: pins.A2,
})
await servo.enabled.write(true)

let i = 0
setInterval(async () =&gt; {
    i++
    await servo.angle.write((i % 180) - 90)
}, 100)</code></pre>
<p>서보모터는 기본적으로 꺼져있고 모터의 회전각은 60분법의 &quot;도&quot;입니다.</p>
<h3 id="가변저항으로-servo-motor-돌리기">가변저항으로 servo motor 돌리기</h3>
<pre><code class="language-ts">import { debounceTime } from &quot;@devicescript/observables&quot;
import { startPotentiometer, startServo } from &quot;@devicescript/servers&quot;
import { pins } from &quot;@dsboard/adafruit_qt_py_c3&quot;

// 오차범위
const Max_Error_Range = 5

const servo = startServo({
    pin: pins.A2,
})
await servo.enabled.write(true)

const potentio = startPotentiometer({
    pin: pins.A3,
})

potentio.reading
    .pipe(
        debounceTime(10) // 0.01초마다
    )
    .subscribe(async rot =&gt; {
        const prev = await servo.angle.read()
        const curr = rot * 180 - 90
        await servo.angle.write(
            prev + Math.clamp(-Max_Error_Range, curr - prev, Max_Error_Range)
        )
    })</code></pre>
<p>대부분의 부품들은 저마다의 Register를 지니고 있습니다. 기본적으로 <code>reading</code>가 그러한데, <code>@devicescript/observables</code> 모듈을 가져오면 모듈에 들어있는 타입 정의에 따라 pipe 메서드를 추가적으로 활용할 수 있게 됩니다. <del>없었는데 있었어요</del>
이번 예제에선 가변 저항이 매순간 튀는 값이 많아서 0.01초마다 오차범위 내로만 받도록 이중 처리 방식을 사용했습니다.</p>
<h2 id="throubleshooting">ThroubleShooting</h2>
<p>DeviceScript는 이미 자체적인 throuble shooting 문서를 지니고 있으나 이 레포지토리의 나이가 겨우 반년인 점을 보았을 때 아직 등장하지 않은 엣지 케이스가 수두룩할 것입니다. <a href="https://github.com/microsoft/devicescript/issues/432"><strong>실제로 제가 겪었습니다.</strong></a></p>
<h3 id="loopback-rx-ovf">&quot;loopback-rx-ovf&quot;</h3>
<p>위 메시지가 반복되면서 어느순간 아래와 같이 로그가 뜨며 터미널이 강제 종료됩니다.
<img src="https://velog.velcdn.com/images/sharlotte_04/post/164c4016-2253-418c-b00a-93f41efb426a/image.png" alt=""></p>
<p><strong>무엇을 재부팅하든 해결되지 않습니다.</strong>
<a href="https://github.com/microsoft/devicescript/issues/432">관련 Github Issue</a>에 따르면 board를 다시 flash하지 않고 먼저 clean를 한 뒤 flash를 해야 한다고 알려줍니다.</p>
<h3 id="cli-명령어를-찾을-수-없어요">CLI 명령어를 찾을 수 없어요.</h3>
<p>6.7 제가 겪고 있는 문제인데, 일시적인 해결책으로서 <code>npm run devs</code>나 <code>yarn devs</code>로 CLI 명령어에 접근할 수 있습니다. bash나 cmd가 명령어를 찾을 수 없으니 yarn이 대신 찾게 해주는거라 결국 설치는 해야 합니다. 설치 확인은 <code>node_modules/.bin/</code> 에서 직접 확인할 수 있습니다.</p>
<h2 id="더-알아야-할-것">더 알아야 할 것</h2>
<h3 id="devicescript는-공식적으로-npm을-사용합니다">DeviceScript는 공식적으로 npm을 사용합니다.</h3>
<p><a href="https://github.com/microsoft/devicescript/issues/432#issuecomment-1580844386">Github Issue 코멘트</a>에 따르면 가끔 등장할 yarn는 outdated된 것이라고 합니다. npm 사용을 권장드립니다.</p>
<h3 id="모든-하드웨어-모듈을-지원하진-않습니다">모든 하드웨어 모듈을 지원하진 않습니다.</h3>
<p>아두이노처럼 pin digital/analog read/write만을 생각해오신 분들에겐 이상한 말일 수 있을겁니다. DeviceScript는 기본적으로 지원하는 모듈들이 몇가지 있습니다. <a href="https://microsoft.github.io/devicescript/api/servers">DeviceScript의 Servers API 문서</a>에서 지원하는 모든 모듈들을 확인할 수 있습니다. 이들은 아두이노에 있던 라이브러리를 대체할 수 있을지도 모릅니다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[변수는 외부 스코프에 대해 수명을 어떻게 가져야 하는가?]]></title>
            <link>https://velog.io/@sharlotte_04/%EB%B3%80%EC%88%98%EB%8A%94-%EB%8B%A4%EB%A5%B8-%EC%8A%A4%EC%BD%94%ED%94%84%EC%97%90-%EC%88%98%EB%AA%85%EC%9D%84-%EC%96%B4%EB%96%BB%EA%B2%8C-%EA%B0%80%EC%A0%B8%EC%95%BC-%ED%95%98%EB%8A%94%EA%B0%80</link>
            <guid>https://velog.io/@sharlotte_04/%EB%B3%80%EC%88%98%EB%8A%94-%EB%8B%A4%EB%A5%B8-%EC%8A%A4%EC%BD%94%ED%94%84%EC%97%90-%EC%88%98%EB%AA%85%EC%9D%84-%EC%96%B4%EB%96%BB%EA%B2%8C-%EA%B0%80%EC%A0%B8%EC%95%BC-%ED%95%98%EB%8A%94%EA%B0%80</guid>
            <pubDate>Mon, 22 May 2023 03:37:36 GMT</pubDate>
            <description><![CDATA[<h1 id="기본적인-스코프간-수명-차이">기본적인 스코프간 수명 차이</h1>
<h2 id="javascript">Javascript</h2>
<p>자 여기 간단한 javascript 코드가 있습니다. 크롬 개발자도구의 콘솔에서 실행해보죠.</p>
<pre><code class="language-js">for(let i = 0; i &lt; 10; i++) {
  setTimeout(() =&gt; console.log(i), 1000);
}</code></pre>
<p>그리고 이 코드는 예상하셨다시피 1초 뒤에 0부터 9까지의 수를 순서대로 출력할 것입니다. 그리고 이것은 우리에게 상당히 직관적이며 일리 있는 코드입니다.
<img src="https://velog.velcdn.com/images/sharlotte_04/post/c75866ba-c8eb-4aa6-97e6-d4f674e313b5/image.png" alt=""></p>
<h2 id="c">C#</h2>
<p>그러나 아래 c# 코드를 봅시다. 한번 Unity에서 실행해보죠.</p>
<pre><code class="language-cs">class MyMonoBehaivour : MonoBehaviour {
    [Button(&quot;start&quot;)]
    private void Run()
    {
        for (int i = 0; i &lt; 10; i++)
        {
            // coroutine with WaitForSeconds
            SetTimeout(() =&gt; Debug.Log(i), 1000);
        }
    }

    private IEnumerator SetTimeoutCoroutine(float delay, Action callback)
    {
        yield return new WaitForSeconds(delay / 1000);
        callback();
    }

    private void SetTimeout(float delay, Action callback)
    {
        IEnumerator coroutine = SetTimeoutCoroutine(delay, callback);
        StartCoroutine(coroutine);
    }
}</code></pre>
<p>그리고 이 코드는 javascript와 달리, 아래와 같은 출력을 냅니다.
<img src="https://velog.velcdn.com/images/sharlotte_04/post/88e4f99f-d622-441b-83e1-b6d5c839a053/image.png" alt=""></p>
<p>javascript와는 다른 출력이 나옵니다. for 반복문을 모두 끝낸 i 변수만을 출력합니다.
뭔가 이상합니다. 다른 언어... Java를 한번 보죠.</p>
<h2 id="java">Java</h2>
<p>아래 Java 코드는 <a href="https://stackoverflow.com/a/56225206">StackOverflow 답변</a>에 따라 <a href="https://docs.oracle.com/javase/7/docs/api/java/util/Timer.html">Timer</a>를 통해 setTimeout를 구현하여 <a href="https://www.programiz.com/java-programming/online-compiler/">online java complier</a>에서 실행시켰습니다.</p>
<pre><code class="language-java">import java.util.*;

class HelloWorld {
    public static void main(String[] args) {
        for(int i = 0; i &lt; 10; i++) {
            setTimeout(() -&gt; System.out.println(i), 1000);
        }
    }

    public static void setTimeout(Runnable runner, long delay) {
        new Timer().schedule(new TimerTask() {
            @Override
            public void run() {
                runner.run();
            }
        }, delay);
    }
}

interface Runnable {
    void run();
}</code></pre>
<p>그러면 아래와 같이 에러가 발생합니다.</p>
<pre><code class="language-txt">ERROR!
javac /tmp/DUnT7cR2vD/HelloWorld.java
/tmp/DUnT7cR2vD/HelloWorld.java:8: 
    error: local variables referenced from a lambda expression must be 
           final or effectively final
    setTimeout(() -&gt; System.out.println(i), 1000);
                                                ^
1 error</code></pre>
<p>보아하니 Java는 c#, javascript와 달리 외부 스코프로부터 가변적인 변수의 참조를 거부하고 있습니다. Jetbrain의 IntelliJ IDEA IDE는 이러한 에러에 대해 몇가지 솔루션을 제안하는데, final 변수에 매번 할당하여 넘기거나 배열을 사용하여 넘기는 방법입니다.</p>
<p>아래와 같이 배열을 사용하면 C#과 유사한 출력이 나옵니다.</p>
<pre><code class="language-java">public static void main(String[] args) {
    int[] arr = {0};

    for(int i = 0; i &lt; 10; i++) {
        arr[0] = i;
        setTimeout(() -&gt; System.out.println(arr[0]), 1000);
    }
}</code></pre>
<pre><code class="language-txt">java -cp /tmp/DUnT7cR2vD HelloWorld
9
9
9
9
9
9
9
9
99</code></pre>
<p>두고 보면 C#에서 10이 나오는게 이상하게 느껴질 수 있지만 중요한 문제가 아니니 넘어갑시다.</p>
<p>final 방식을 사용하면 javascript와 유사한 출력이 나옵니다.</p>
<pre><code class="language-java">public static void main(String[] args) {
    for(int i = 0; i &lt; 10; i++) {
        final int j = i;
        setTimeout(() -&gt; System.out.println(j), 1000);
    }
}</code></pre>
<pre><code class="language-txt">java -cp /tmp/DUnT7cR2vD HelloWorld
24
5
0
1
3
6
7
98</code></pre>
<p>출력이 순서대로가 아닌 이유는 아마 Timer의 내부적인 스케쥴 처리에 있을 것이지만 이것도 여기선 딱히 중요한 문제가 아니니 넘어갑시다.</p>
<h1 id="스코프에서-참조하는-외부-변수의-수명은-스코프와-독립적입니다">스코프에서 참조하는 외부 변수의 수명은 스코프와 독립적입니다.</h1>
<p>결과적으로 전 몇가지 결론을 얻을 수 있었습니다.</p>
<p>위 코드에서 공통적으로 출연한 for문의 변수 <code>i</code>는 매 루프마다 선언된 것이 아니라, 루프마다 재할당되고 있습니다. 이는 성능 관점에서 합리적이며 대중적인 while를 통한 for문 구현문을 봤을 때 일리있습니다.</p>
<p>위 이유로 인해 C#과 Java의 첫번째 시도에서 내부 스코프에서 참조된 변수 <code>i</code>는 루프가 모두 끝났을 때 최종값 <code>9</code>를 가지므로 1초 뒤에 <code>i</code>를 출력했을 때 9만 나왔습니다.</p>
<p>그러나 Java의 두번째 시도에선 지역변수 <code>j</code>를 통해 출력했는데, 이때는 매 루프마다 <code>j</code>가 선언되었으므로 매 루프마다 있었던 <code>i</code>를 고스란히 출력하기 때문에 0부터 9까지의 수들이 나왔습니다.</p>
<p>이를 통해 C#과 Java에선 외부 변수의 수명이 스코프에 종속적이지 않음을 알 수 있습니다. 이건 꽤나 중요한 주제이며 <strong>왜 Java가 외부 스코프로부터 가변적인 변수의 참조를 거부</strong>하는지에 대한 이유기도 합니다. 높은 직관성을 줄지언정 대신 개발자에게 변수를 의도적으로 캐싱하게 만들어 안정성을 추구하고자 한 것입니다.</p>
<p>C#은 이러한 불편을 문법에서 축출한 것으로 보이나 Javascript처럼 외부 변수의 수명이 내부 스코프에 종속적이게 만들지도 않았습니다. <del>사실 종속적이게 만들면 메모리 누수가 우려되긴 합니다.</del></p>
<h2 id="javascript는-이상합니다">Javascript는 이상합니다.</h2>
<p>Javascript는 이상하게도 C#과 같은 방법으로 출력했음에도 불구하고 변수 <code>i</code>의 수명이 setTimeout의 람다 함수의 스코프에 종속적입니다. 이에 대한 이유를 추가적으로 알아볼 필요는 있으나 일단 언어 철학 관점에서 javascript가 맥락에 의존하는 언어라는 점을 두고 봤을 때 그럴 듯한 현상처럼 보입니다.</p>
<h2 id="c에선-매우-조심해야-합니다">C#에선 매우 조심해야 합니다.</h2>
<p>아무튼 이러한 점 때문에, Java에선 <strong>문법 차원</strong>의 제재를 가하고 있고 Javascript는 <strong>문법 차원</strong>의 근본적인 문제 해결(오히려 수명을 종속시킴)을 하고 있으나 C#은 <strong>둘 다 하지 않음</strong>으로 인해 각별히 주의를 할 필요가 있습니다.</p>
<blockquote>
<p><strong>물론 Javascript도 유의해야 합니다.</strong></p>
<p>수명을 종속시킨다는건 <strong>해당 변수의 값</strong>입니다. 그리고 일반적으로 그 값이란 원시값 입장에선 그 값 자체지만 참조값(객체) 입장에선 <strong>객체의 주소</strong>입니다.
C#에선 이 문제가 원시값에게도 해당되어 더 심각할 뿐, Javascript도 객체에 대해서 유의미한 문제가 발생합니다.
Typescript에서 가끔 null check를 다시 해야 하는 경우가 생기는데, 이게 그러한 경우입니다.</p>
</blockquote>
<p>이게 왜 문제냐면, Javascipt에서도 그랬듯이 C#에서 외부 객체의 수명이 직관적으로 이뤄지지 않기 때문입니다. 내부 스코프가 참조된 외부 객체를 잡고 있다(bind)고 생각해선 안됩니다. 성능 설계상 이건 매모리 누수를 초례하므로 그 자체로 이미 좋지 않은 상상입니다.
심지어, Javascript와 달리, C#에선 <strong>원시값조차</strong> 이러한 문제에 직면하므로 더 각별히 주의를 주어야 합니다. 
비단 for문에서의 이야기가 아닙니다. 이벤트리스너와 같은 <strong>비동기적인 모든 것</strong>에 대하여 이러한 문제가 발생합니다.</p>
<h3 id="unity에서의-예시">Unity에서의 예시</h3>
<p>위 C# 예제에서 <code>SetTimeout</code> 메서드를 유틸리티화하여 <code>Timer</code> 싱글톤 클래스를 만들어봤습니다.</p>
<pre><code class="language-cs">internal class Timer : LazyDDOLSingletonMonoBehaviour&lt;Timer&gt;
{
    IEnumerator SetTimeoutCoroutine(float durationInSecond, Action callback)
    {
        yield return new WaitForSeconds(durationInSecond/1000);
        callback();
    }

    public Action SetTimeout(float durationInSecond, Action callback)
    {
        IEnumerator coroutine = SetTimeoutCoroutine(durationInSecond, callback);
        StartCoroutine(coroutine);
        return () =&gt; StopCoroutine(coroutine);
    }

    IEnumerator SetIntervalCoroutine(float durationInSecond, Action callback)
    {
        while(true)
        {
            yield return new WaitForSeconds(durationInSecond);
            callback();
        }
    }

    public Action SetInterval(float durationInSecond, Action callback)
    {
        IEnumerator coroutine = SetIntervalCoroutine(durationInSecond, callback);
        StartCoroutine(coroutine);
        return () =&gt; StopCoroutine(coroutine);
    }
}</code></pre>
<p>작동 방식은 크게 다르지 않습니다. <del>delay가 초단위가 된 것 빼고요</del></p>
<p>이 유틸리티를 활용하여 <code>10초 뒤에 특정 유닛의 이름을 출력하는 코드</code>를 구현해봅시다.</p>
<pre><code class="language-cs">class Unit : MonoBehaviour {
    public void PrintUnit()
    {
        Timer.Main.SetTimeout(10, () =&gt; Debug.Log(this.name));
    }
}</code></pre>
<p>아마 일반적인 상황에서, 이 코드는 정상적으로 10초 뒤에 유닛의 이름을 출력할 것입니다. 
그러나 <strong>만약 10초 안에 그 유닛이 삭제된다면?</strong>
unit는 null이 될테고 name는 null를 참조했으므로 에러를 내놓을 것입니다.</p>
<p>이 경우에는 Timer가 코루틴을 실행시키는 대신 Unit가 직접 코루틴을 실행시켜서 Unit가 삭제되면 코루틴 실행도 취소되도록 설계하면 됩니다.</p>
<pre><code class="language-cs">class Unit : MonoBehaviour {
    public void PrintUnit()
    {
        StartCoroutine(
            Timer.Main.SetTimeoutCoroutine(10, () =&gt; Debug.Log(this.name))
        );
    }
}</code></pre>
<p>이렇게 외부 변수의 수명이 스코프에 종속적이지 않은 문제 때문에 변수가 어느순간 null이 될지 모릅니다. C#은 기본적으로 null safty를 지향하지만 Unity에선 그럴 일이 너무나도 드뭅니다.
예를 들어, 다른 경우인 가령 이벤트 리스너에선 일반적으로 제지할 방법이 없습니다. 그 변수가 절대 null이 되지 않는단 보장을 하거나, 기본값을 주거나, 그 변수가 변화할 때 이벤트 리스너도 같이 변화하도록 부가적인 설계 레이어를 구축해야 합니다.
이 과정이 귀찮게 느껴질 수도 있지만 어찌보면 당연한 일입니다. 이런 부가적인 과정이 요구된다면 설계 미스를 한번 의심해보세요.</p>
<p>아 물론, 객체가 아니라 원시값의 경우에는 캐싱 말곤 답이 없습니다. 이러한 문제를 예방하는 가장 간단한 마인드는 <strong>자바처럼 사고하기</strong>라고 생각합니다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[이진 트리를 통한 빠른 팩토리얼 구현 모델 분석]]></title>
            <link>https://velog.io/@sharlotte_04/%EC%9D%B4%EC%A7%84-%ED%8A%B8%EB%A6%AC%EB%A5%BC-%ED%86%B5%ED%95%9C-%EB%B9%A0%EB%A5%B8-%ED%8C%A9%ED%86%A0%EB%A6%AC%EC%96%BC-%EA%B5%AC%ED%98%84-%EB%AA%A8%EB%8D%B8-%EB%B6%84%EC%84%9D</link>
            <guid>https://velog.io/@sharlotte_04/%EC%9D%B4%EC%A7%84-%ED%8A%B8%EB%A6%AC%EB%A5%BC-%ED%86%B5%ED%95%9C-%EB%B9%A0%EB%A5%B8-%ED%8C%A9%ED%86%A0%EB%A6%AC%EC%96%BC-%EA%B5%AC%ED%98%84-%EB%AA%A8%EB%8D%B8-%EB%B6%84%EC%84%9D</guid>
            <pubDate>Fri, 14 Apr 2023 00:06:56 GMT</pubDate>
            <description><![CDATA[<h1 id="서론">서론</h1>
<p>백준 문제 중 모든 브론즈5 문제를 풀고 딱 하나, <a href="https://www.acmicpc.net/problem/27434">27434번 팩토리얼 3</a> 문제가 남아있었다.
이전 문제였던 <a href="https://www.acmicpc.net/problem/27433">27433번 팩토리얼 2</a> 문제와 비슷해서 <em>아 이거 BigInt면 끝나겠네</em>라고 간과하며 풀었으나 <strong>시간 초과</strong>로 퇴짜맞았다! </p>
<p><img src="https://velog.velcdn.com/images/sharlotte_04/post/01ab18a1-aeab-4b0b-9f68-c685ae5ae1a7/image.png" alt=""></p>
<p>왜그런지 문제를 다시 읽어보니 10만 팩토리얼을 4초 안에 계산해야 하는 문제였던 것이다.
주변인들에게 물어봐도 <em>어 팩토리얼? big integer면 되겠네</em> 라는 답변만 들을 수 있었다. 
왜냐하면 이 문제는 실제로 <strong>pypy3로 쉽게 풀 수 있기 때문이다</strong>.</p>
<table>
<thead>
<tr>
<th><img src="https://velog.velcdn.com/images/sharlotte_04/post/855284a5-3287-478d-b2b4-33c6b30401ba/image.png" alt=""></th>
<th><img src="https://velog.velcdn.com/images/sharlotte_04/post/a3ff938b-de89-4af0-a30c-9a9a0978153e/image.png" alt=""></th>
</tr>
</thead>
<tbody><tr>
<td>팩토리얼 함수를 쓰든</td>
<td>직접 for문으로 하든</td>
</tr>
</tbody></table>
<p>상관없이 모두 시간초과 없이 정답 처리가 된다. 놀랍게도 <strong>pypy3은 정답 언어의 높은 비율을 차지하고 있다</strong>.</p>
<p>정답 799개 중 </p>
<ul>
<li>710개가 pypy3로 구성된 코드다. (<strong>88.8%</strong>)</li>
<li>10개가 cpp로 구성된 코드다. (1.2%)</li>
<li>29개가 java/kotlin로 구성된 코드다. (3.6%)</li>
<li>20개가 go로 구성된 코드다. (2.5%)</li>
<li>18개가 rust로 구성된 코드다. (2.2%, 그런데 2명이 여러번 제출했다)</li>
<li>6개가 ruby로 구성된 코드다. (0.7%)</li>
<li>1개가 swift 구성된 코드다. (0.1%)</li>
<li>1개가 node.js로 구성된 코드다. (<strong>0.1%</strong>)</li>
</ul>
<p>이러한 기형적인 비율엔 이유가 있으니...
<img src="https://velog.velcdn.com/images/sharlotte_04/post/7ce5a3fb-55c8-4fa6-83d1-7f32de1d7ce3/image.png" alt="">
<strong>애당초 의도된 일</strong>이였던 것이다!</p>
<p>그래서 pypy3에 굴복하고 끝내도 되지만 node.js로 끝까지 밀어붙여놓고 포기하는건 너무 억울했다. 그래서 가장 최근의 자바 제출을 보니 <strong>PriorityQueue</strong>를 사용하고 있던 것이다. <del>언어 치트라니</del></p>
<h1 id="priorityqueue가-왜-빠른가">PriorityQueue가 왜 빠른가</h1>
<p>를 알기 전에 몇가지 정리가 필요하다.</p>
<ol>
<li><strong>개념 정리</strong> - 아래의 벤치마킹에서 사용할 함수는 총 5개이므로 이를 설명하고 시간복잡도와 함께 설명할 예정이다.</li>
<li><strong>벤치마킹</strong> - 벤치마킹은 일종의 성능 테스트 실험이다. 그러므로 몇가지 변인을 설정해야 한다.<ul>
<li>통제변인: 입출력</li>
<li>조작변인: 자료구조</li>
</ul>
</li>
</ol>
<h1 id="개념-정리">개념 정리</h1>
<p>팩토리얼 벤치마킹에서 사용할 방법은 총 다섯개로, <strong>반복문</strong>, <strong>배열</strong>, <strong>반전된 배열</strong>, <strong>최소 힙</strong>, <strong>최대 힙</strong>이 있다. 순서대로 설명하기 전에 추가로 설명할 것이 있다.</p>
<h2 id="bigint-js의-bigint-폴리필-분석---jsbi">BigInt: JS의 BigInt 폴리필 분석 - JSBI</h2>
<p>BigInt는 일반적으로 number 자료형이 담을 수 있는 상한선을 넘겨버린 <strong>매우 큰 수</strong>다루기 위한 자료형이다. 자바스크립트에서 BigInt가 어떻게 동작하는지 알기 위하여 몇가지 조사와 분석을 해보았다.</p>
<p>그러기 위해서 JSBI를 접했다. <a href="https://github.com/GoogleChromeLabs/jsbi/">JSBI</a>는 <strong>J</strong>ava<strong>S</strong>cript <strong>B</strong>ig<strong>I</strong>nteger의 Polyfill이다. JSBI의 설명을 보면 자신들이 만든 <code>JSBI</code> 클래스가 ES2020에 있는 BigInt 내용이라고 설명한다.</p>
<blockquote>
<p>JSBI is a pure-JavaScript implementation of the <a href="https://tc39.es/proposal-bigint/">ECMAScript BigInt proposal</a>, which officially became a part of the JavaScript language in ES2020.
JSBI는 ES2020에서 공식적으로 자바스크립트 언어의 일부가 된 ECMAScript BigInt 제안을 순수 자바스크립트로 구현한 것입니다.</p>
</blockquote>
<p>JSBI 클래스를 처음 보면 바로 알 수 있는 점은 <strong>배열이다!</strong> 추가적으로 조사해보면 정말 많은 곳에서 Big Integer를 <strong>자릿수 단위의 배열로 구현</strong>하는걸 적지않게 볼 수 있다.</p>
<table>
<thead>
<tr>
<th><img src="https://velog.velcdn.com/images/sharlotte_04/post/ed99be3e-eed0-49fe-bdb0-9b41c24d9313/image.png" alt=""></th>
</tr>
</thead>
</table>
<p>JSBI의 곱셈 구현 코드는 아래와 같은데 <del>전문가</del>의 도움으로 이것이 다항식의 곱 구현방식인 FFT와 카라추바 중 카라추바 알고리즘을 사용했단 사실을 알 수 있었다.</p>
<table>
<thead>
<tr>
<th><img src="https://velog.velcdn.com/images/sharlotte_04/post/9cfdb736-cc77-48c4-9e30-2c3457fd228e/image.png" alt=""></th>
<th><img src="https://velog.velcdn.com/images/sharlotte_04/post/170ee179-5b7a-446b-a8e7-5f6914081788/image.png" alt=""></th>
</tr>
</thead>
</table>
<p>더 자세한 확답을 받기 위해 ChatGPT에게 물어보면 카라추바 알고리즘이나 톰-쿡 알고리즘을 사용한다고 말한다.
<img src="https://velog.velcdn.com/images/sharlotte_04/post/f41e9f70-d8ff-4674-a050-6e7565f480a9/image.png" alt=""></p>
<p>또한 <a href="https://www.wikiwand.com/ko/%ED%86%B0-%EC%BF%A1_%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%98">톰 쿡 알고리즘 wikipidia</a>와 <a href="https://www.wikiwand.com/ko/%EC%B9%B4%EB%9D%BC%EC%8A%88%EB%B0%94_%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%98">카라슈바 알고리즘 wikipidia</a> 위키피디아 문서와, <a href="https://medium.com/leaningtech/a-fast-bigint-js-in-an-evening-compiling-c-to-javascript-db61ae733512">medium - A fast BigInt.js in an evening, compiling C++ to JavaScript</a> 블로그의 <code>Test &amp; benchmark</code> 문단 끝자리에서 카라슈바와 톰-쿡 알고리즘이 언급되고 그 특성을 설명한다.</p>
<blockquote>
<p><a href="https://www.wikiwand.com/ko/%ED%86%B0-%EC%BF%A1_%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%98">톰 쿡 알고리즘 wikipidia</a>
이런 부가적인 연산 때문에 톰-쿡 알고리즘은 <em>작은 정수의 곱셈에 적용하면 일반 곱셈법보다 느려지기에</em> 이 알고리즘은 적당히 큰 정수에 사용되며, 정수 크기가 훨씬 더 커질 경우는 시간복잡도가 Θ(n log n log log n)인 쇤하게-슈트라센 알고리즘이 더 빠르게 된다.</p>
</blockquote>
<blockquote>
<p><a href="https://www.wikiwand.com/ko/%EC%B9%B4%EB%9D%BC%EC%8A%88%EB%B0%94_%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%98">카라슈바 알고리즘 wikipidia</a>
충분히 큰 n에 대해, 카라추바 알고리즘은 고전적인 곱셈법보다 적은 횟수의 시프트 연산과 한 자리 곱셈을 행한다. <em>하지만 작은 n에 대하여는 추가적인 덧셈과 시프트 연산 때문에 고전적인 곱셈법보다 속도가 느려진다.</em> 그 경계는 컴퓨터의 플랫폼에 따라 달라진다. 대략적으로 곱하는 수가 2320 ≈ 2×1096 이상일 때 카라추바 알고리즘이 더 빠르다.</p>
</blockquote>
<blockquote>
<p> <a href="https://medium.com/leaningtech/a-fast-bigint-js-in-an-evening-compiling-c-to-javascript-db61ae733512">medium - A fast BigInt.js in an evening, compiling C++ to JavaScript</a> 
The results is a 5771 line of generated JavaScript code, comprising Karatsuba’s and Toom-Cook’s algorithm for fast multiplications, usable freely for any (not) so serious scope.
그 결과 5771줄의 자바스크립트 코드가 생성되며, 이 코드에는 Karatsuba와 Toom-Cook의 빠른 곱셈을 위한 알고리즘이 포함되어 있으며, 그다지 심각하지 않은 범위에서 자유롭게 사용할 수 있습니다.</p>
</blockquote>
<p>이를 통해 얻은 정보들이 시사하는 바는 아래와 같다.</p>
<ul>
<li>JavaScript의 BigInt는 자릿수 배열을 가지고 큰 수를 표현하도록 구현됐으며 큰 수의 곱셈에 카라슈바와 톰-쿡 알고리즘이 사용되었다.</li>
<li><strong>BigInt의 곱셈은 작은 수에 사용할수록 비효율적이다.</strong></li>
</ul>
<blockquote>
<p><strong>자릿수 배열을 통해 어떻게 큰 수를 표현했을까?</strong></p>
</blockquote>
<p>단순히 12를 [1, 2] 과 같이 자릿수 단위로 배열에 나눠담은 것이다. 이를 숫자로 되돌린다면 예전 초중학교에서 했듯이 1 * 10 + 2하는 과정이 필요하므로 숫자로 되돌리는 수식은 Σ(index * 10 ^ length-index-1) 일 것이다.
문제는 이렇게 숫자로 표현할 수 없어 배열을 통해 다항식처럼 표현한 상태에서 곱셈을 한다면 우리가 직접 다항식을 곱하듯이 해야 할텐데 이러면 이중 반복문, 즉 O(N^2)의 상태가 되어버린다.
이때 FFT 및 카라슈바 알고리즘은 이 다항식의 곱을 <strong>N^2에서 NlogN으로 절감</strong>시킨 혁신적인 알고리즘이다.</p>
<h3 id="의문-격차가-작은-수들끼리-곱하는건-성능에-유의미한-결과를-낳는가">의문: 격차가 작은 수들끼리 곱하는건 성능에 유의미한 결과를 낳는가?</h3>
<p>위 내용에 따르면 결국 수가 커질수록 증가폭이 커지는 점은 여전하다. 그게 크게 줄어들었을 뿐. 이게 추가적으로 일으키는 의문은 <strong>서로 다른 두 자릿수에서 BigO는 무얼 기준으로 계산되는가?</strong> 이다. BigO는 항상 최악의 경우를 상정해야 하므로 가장 길이가 큰 수를 기준으로 둘 것이다. 그렇다면 O(N)의 N은 max(logA, logB) 일 것이다. 이에 따르면 수가 클수록 시간이 더 오래걸리니 차라리 작은 수들끼리 따로 곱하면 효율이 높을 것이란 추측에 도달한다.</p>
<p>그리고 덧붙여 격차가 아니라 실제로 값이 <strong>작은 수들끼리 <em>먼저</em> 곱하면 cpu 가속화를 유도하는가</strong>라는 의문도 있었는데 이에 대한 반례 실험을 할 필요가 있다. 그냥 단순히 min-heap가 아닌 max-heap를 구현하여 벤치마킹을 실험해보면 알 수 있을것이다.</p>
<h2 id="시간-복잡도">시간 복잡도</h2>
<p>시간복잡도는 주로 <a href="https://www.geeksforgeeks.org/types-of-asymptotic-notations-in-complexity-analysis-of-algorithms/">세가지 기준</a>에 따라 최선의 경우인 Big-오메가와 최악의 경우인 Big-오, 평균의 경우인 Big-세타로 분류할 수 있다. 이번 분석에선 최악의 경우를 상정하여 최대의 효율을 보려고 하기 때문에 <a href="https://velog.io/@iberis/%EC%8B%9C%EA%B0%84-%EB%B3%B5%EC%9E%A1%EB%8F%84-BigO">시간 복잡도 Big-O</a>를 사용할 것이다. 그리고 앞서 말한 BigInt의 곱연산 시간 복잡도까지 생각을 해야 하므로 끝에에서 톰-쿡 알고리즘의 카라슈바 알고리즘 케이스인(k=2) c를 붙이겠다.</p>
<h2 id="반복문으로-팩토리얼-구현하기">반복문으로 팩토리얼 구현하기</h2>
<p>가장 처음에 백준에 제출한 문제의 핵심 함수다.</p>
<pre><code class="language-js">function factorialByLoop(number) {
  let num = 1n;
  for (let i = 0; i &lt; number; i++) {
    num *= BigInt(number - i);
  }
  return num;
}</code></pre>
<p>정석대로 n * ... * (n - i) 의 형태인 단순 반복문의 흐름이다.
이 함수의 시간복잡도는 O(N)이다. 단순 계산은 O(1)이기 때문이다.</p>
<h2 id="배열로-팩토리얼-구현하기">배열로 팩토리얼 구현하기</h2>
<pre><code class="language-js">function factorialByReversedArray(number) {
  const arr = new Array();
  for (let i = number; i &gt;= 0; i--) arr.push(BigInt(i == 0 ? 1 : i));
  while (arr.length &gt; 1) {
    arr.push(arr.pop() * arr.pop());
  }
  return arr.pop();
}</code></pre>
<p>다른 자료구조(PriorityQueue)와 비교하기 위해 알고리즘 과정을 통일시킬려고 배열화 시켰다.
이 함수의 시간복잡도는 O(N)이다. 삽입과 제거가 pop/push여서 O(1)이기 때문이다.</p>
<h2 id="반전-배열로-팩토리얼-구현하기">반전 배열로 팩토리얼 구현하기</h2>
<pre><code class="language-js">
function factorialByReversedArray(number) {
  const arr = new Array();
  for (let i = number; i &gt;= 0; i--) arr.push(BigInt(i == 0 ? 1 : i));
  while (arr.length &gt; 1) {
    arr.push(arr.pop() * arr.pop());
  }
  return arr.pop();
}</code></pre>
<p>몇가지 시도 이후에 나는 BigInt가 작은 수들끼리 곱할수록 cpu 가속화가 이뤄진단 이론을 증명하기 위해 기존 array의 반례인 reversed-array를 추가했다. 단순히 초기화된 배열의 순서만 반전이니 여전히 O(N)이다.</p>
<h3 id="배열의-개형">배열의 개형</h3>
<p><img src="https://velog.velcdn.com/images/sharlotte_04/post/665b3896-a7de-4cff-8664-5f19c0c8361f/image.png" alt=""></p>
<p>배열은 일반적으로  정렬 여부에 따라 삽입과 삭제에 대한 시간복잡도가 제각각이다. 
<img src="https://velog.velcdn.com/images/sharlotte_04/post/ed607d4d-6346-4dd8-a4f3-1c201cee6ab0/image.png" alt=""></p>
<p>그러나 O(n)이 O(n)인 이유는 대개 중간 삽입 및 삭제를 worst case로 상정해두고 생각해서인데, 이번 구현에선 <strong>끝부분에서만 작업이 이뤄</strong>지므로 삽입과 삭제가 O(1)임을 알 수 있다.</p>
<h3 id="side-note-자바스크립트의-배열은-근본-배열이-아니다">side note: 자바스크립트의 배열은 근본 배열이 아니다?</h3>
<p><a href="https://poiemaweb.com/js-array-is-not-arrray">자바스크립트 배열은 배열이 아니다</a>, <a href="https://stackoverflow.com/a/9338040/19561566">StackOverflow 답변</a>에선 자바스크립트의 배열은 C와 같은 dense array(메모리가 연속적인)가 아닌 <strong>spharse array</strong>(메모리가 연속적이지 않은)로써 <strong>hash table를 기반</strong>으로 두고 있다고 한다.
<img src="https://velog.velcdn.com/images/sharlotte_04/post/78beadb2-87ea-42ea-a4f0-25d2248e62a7/image.png" alt=""></p>
<p>실제로 ChatGPT에게 물어보면 이와 같이 답변하며 첫번째 링크의 블로그 끝부분 테스트에서도 <code>[]</code>와 <code>{}</code>에서 배열 인덱스를 키로 두고 제어했을 때의 속도 차이를 설명하고 있다.</p>
<h3 id="side-note-자바스크립트의-배열은-해시-테이블-기반이니-느리다">side note: 자바스크립트의 배열은 해시 테이블 기반이니 느리다?</h3>
<p><img src="https://velog.velcdn.com/images/sharlotte_04/post/80383267-9c7b-405f-b5e6-e563b2622b08/image.png" alt="">
위 의문에 따르면 자바스크립트의 배열은 hash table이란 소린데 hash table 자체의 공간 복잡도는 전부 O(n)이므로 개발자가 기대했던 성능의 n의 곱절은 더 느려져버리는 것이다. 그래서 엔진과 런타임에서 이러한 구조적인 문제를 해결하여 O(1)로 만든 것 같다.</p>
<p><a href="https://stackoverflow.com/a/22615787/19561566">StackOverflow 답변</a>, <a href="https://medium.com/@ashfaqueahsan61/time-complexities-of-common-array-operations-in-javascript-c11a6a65a168">medium - Time Complexities Of Common Array Operations In JavaScript</a> 그리고 <a href="https://dev.to/lukocastillo/time-complexity-big-0-for-javascript-array-methods-and-examples-mlg">dev - Time complexity Big 0 for Javascript Array methods and examples</a> 와 같은 여러 개발 블로그 및 커뮤니티에서 공통적으로 <strong>array의 <code>push</code>와 <code>pop</code>은 <code>O(1)</code>의 시간 복잡도</strong>를 가지고 있다고 말한다.</p>
<p>자바스크립트의 배열이 근본은 아니지만 시간복잡도 관점에선 피차일반으로 별 상관이 없음을 알 수 있다.</p>
<h2 id="minmaxpriorityqueue로-팩토리얼-구현하기">Min/MaxPriorityQueue로 팩토리얼 구현하기</h2>
<pre><code class="language-js">function factorialByMinHeap(number) {
  const queue = new MinPriorityQueue();
  for (let i = 0; i &lt;= number; i++) queue.enqueue(BigInt(i == 0 ? 1 : i));
  while (queue.heap.length &gt; 1) {
    queue.enqueue(queue.dequeue() * queue.dequeue());
  }
  return queue.dequeue();
}

function factorialByMaxHeap(number) {
  const queue = new MaxPriorityQueue();
  for (let i = 0; i &lt;= number; i++) queue.enqueue(BigInt(i == 0 ? 1 : i));
  while (queue.heap.length &gt; 1) {
    queue.enqueue(queue.dequeue() * queue.dequeue());
  }
  return queue.dequeue();
}</code></pre>
<p>각자 min-heap와 max-heap로 구현한 priority queue로 팩토리얼 알고리즘을 구현한 코드다.
이 함수의 시간복잡도는 O(NlbN)이다. 삽입과 제거가 O(lbN)이기 때문이다. 자세한 설명은 아래에서.</p>
<h3 id="heap이란">Heap이란?</h3>
<p>heap은 완전 이진 트리의 일종이 될 수 있는 트리 자료구조 중 하나다. min-heap는 우선순위가 낮은 순서로, max-heap는 높은 순서로 가지가 뻗어나간다. <a href="https://evan-moon.github.io/2019/10/12/introduction-data-structure-heap/">최소 값과 최대 값을 빠르게 찾을 수 있게 도와주는 힙(Heap) - evan-moon</a></p>
<h3 id="priorityqueue이란">PriorityQueue이란?</h3>
<p>PriorityQueue는 Heap에 기반한 <strong>우선순위 큐</strong>다. 일반적인 선입선출 구조인 Queue와 달리 Priority Queue는 아이템이 들어오고 나갈 때마다 우선순위에 따라 재배치되는 특징이 있다. 그래서 큐에 값들을 넣고 최솟값 또는 최댓값을 빠르게 꺼내고 싶을 때 유용하다.</p>
<p>PriorityQueue가 factorial 연산에서 반복문에 비해 이점을 취할 땐 Heap로 구현되었을 때밖에 없다. <a href="https://chanhuiseok.github.io/posts/ds-4/">[주의] 왜 우선순위 큐는 배열이나 연결리스트로 구현하지 않을까?</a>
Heap로 PriorityQueue를 구현할 수 있던 방법은 <a href="https://develop-dream.tistory.com/91">Heap가 인덱스 규칙성</a>을 가지기 때문이다.
<img src="https://velog.velcdn.com/images/sharlotte_04/post/752cece1-8cbf-4576-a1f2-4b67a9ab56c9/image.png" alt=""></p>
<p>](<a href="https://develop-dream.tistory.com/91">출처: https://develop-dream.tistory.com/91</a></p>
<h3 id="min-heap의-개형">min-heap의 개형</h3>
<p><img src="https://velog.velcdn.com/images/sharlotte_04/post/4bfe2aa1-fb09-4fe6-995b-0d985ece5017/image.png" alt=""></p>
<p>PriorityQueue를 사용한 <code>factorial</code> 함수에서 <code>number</code>에 10을 넣었다고 쳤을 때 while 반목문이 한번 동작할 때의 min-heap 흐름은 위 사진과 같다.</p>
<p>첫번째와 두번째 회색 그룹에서, 함수 <code>dequeue</code>가 호출되어 가장 위에 있는 <strong>최솟값</strong>이 뽑혀나가고 마지막에 있던 값이 최솟값의 자리로 대체된 다음 <strong>min-heap의 규칙에 따라 재조정</strong>된다. 이땐 자신 아래에 자신보다 적은 수가 없어야 하므로 재조정 과정에선 자신보다 낮은 수를 찾아 자기 자리(index)와 바꿔치는 작업을 보이는 낮은 수가 없을 때까지 한다.</p>
<p>세번째 회색 그룹에서, 함수 <code>enqueue</code>가 호출되어 주어진 값이 트리의 가장 아래에 주입되고 다시 <strong>min-heap의 규칙에 따라 재조정</strong>된다. 이땐 자신 위에 자신보다 큰 수가 없어야 하므로 큰 수가 없을 때까지 부모와 자리(index)를 교체한다.</p>
<p>두 함수에서 흥미로운 점은 초록색 박스, 즉 <strong>탐색된 아이템이 한 가지(branch)</strong> 에 불과하단 점이다. 즉, <strong>모든 아이템이 탐색되는게 아님</strong> 이 이 Heap가 취할 수 있는 최대 장점이다. 그럼 BigO를 알아봐야 하는데...</p>
<h3 id="min-heap는-왜-olog-n일까">min-heap는 왜 O(log N)일까?</h3>
<p>시간 복잡도엔 <a href="https://www.geeksforgeeks.org/types-of-asymptotic-notations-in-complexity-analysis-of-algorithms/">여러 측정법</a>이 있지만 한가질 뽑자면 worst case, 즉 최악의 가정에서의 시간 복잡도를 기준으로 삼는 Big-O가 있다. 이러한 빅-오 기준에 따르면 위 min-heap의 <code>dequeue</code> 함수에서 최악은 첫번째 회색 그룹, 최악이 아님은 두번째 회색 그룹이라 볼 수 있겠다. 아무튼 최악의 개형을 보자면 <strong>최악의 경우인 트리의 끝에 도달</strong>하는건, 즉 <strong>트리의 높이가 곧 시간복잡도</strong>임을 알 수 있다. 그러므로 O(높이)이라 단정지을 수 있으나 BigO 표기법에선 N 하나만 유효하므로 높이를 N으로 계산해야 한다.</p>
<p>이때 logN이 등장한다.</p>
<blockquote>
<p>참고: 컴퓨터 과학에서의 logN은 <strong>대개 밑이 2인 로그</strong>를 뜻한다. 
이진적인게 많아서 밑을 2로 두고 사용하는 경향이 생긴 듯 하다. 햇갈릴 여지가 있어서 <code>lb</code>를 대신 쓰기도 한다.
시간복잡도에서 등장하는 log는 대개 밑이 2이나 lb가 있을 때의 log는 상수로그일 수 있으므로 유의하자. </p>
</blockquote>
<p>층마다 2개씩 갈려나가고 거기서 각 끝부분이 2배로 늘어나는 모습은 2^(높이) 인걸 추측하게 만들어준다. (2 -&gt; 4 -&gt; 8 -&gt; 16) <code>총량 = 2^높이</code>임을 알았으니 높이가 <code>log2총량</code>임을 알 수 있다. 팩토리얼 함수에서 트리의 총량은 트리의 최댓값이며 최댓값은 곧 주어진 수와 같으니 높이는 <code>log2N</code>이다. 이걸 BigO에 맞춰 바꾸면 <code>lbN</code>이니 결론적으로 <strong>min-heap의 BigO 시간복잡도는 <code>O(lbN)</code></strong> 이라 말할 수 있다.</p>
<h2 id="벤치마킹-각-함수의-시간복잡도">벤치마킹: 각 함수의 시간복잡도</h2>
<p><img src="https://velog.velcdn.com/images/sharlotte_04/post/767c4914-f647-4c16-afd3-c3bb72e646be/image.jpg" alt=""></p>
<ul>
<li>X축은 팩토리얼 함수에 넣은 파라미터 N, Y축은 걸린 시간(ms)다.</li>
</ul>
<p><a href="https://gist.github.com/Sharlottes/a3c43c00a644889a619ce8132c2e5436">이전 벤치마킹 소스코드 확인하기</a>
<a href="https://github.com/Sharlottes/facotiral-analysis">새 벤치마킹 프로젝트 확인하기</a></p>
<p>앞서 말한 <strong>작은 수들끼리 먼저 곱하면 cpu 가속화를 유도하는가?</strong> 는 아래 사진을 보고 이해할 수 있다. 
<img src="https://velog.velcdn.com/images/sharlotte_04/post/dc9c9577-36e5-4532-b558-16052717b7a9/image.png" alt="">
이 사진은 각 함수의 흐름을 확인하기 위해 매 반복마다 출력시키도록 변경한 벤치마킹의 일부 결과다.
array는 &quot;배열의 개형&quot; 문단에서 보여준 다이어그램과 완전히 동일한 순서를 보여주고 있으며 max-heap 또한 큰 숫자들 먼저 꺼내서 넣는 모습을 볼 수 있고 min-heap도 작은 숫자들끼리 먼저 꺼내서 넣는 흐름을 볼 수 있다. 주목해야 할 점은 <strong>max-heap와 배열은 소요 시간이 불규칙적이고 비교적 큰 반면 min-heap는 규칙적(linear)이고 비교적 작다</strong>는 점이다. 게다가 실제 표를 보면 <strong>min-heap만이 그렇지 않다.</strong></p>
<p>Heap의 삽입/삭제는 O(logN)고 Array의 삽입/삭제는 O(1)이므로 <strong>이론상 힙이 무조건 느려야 한다.</strong> 그러나 이건 삽입/삭제의 속도가 문제가 아니다 min-heap는 BigInt 곱연산의 비-상수 시간복잡도에서 두 수의 격차를 최대한 줄임으로써 N를 최소화한 결과 저렇게 안정적이고 빠른 속도를 생산해낼 수 있다고 생각한다. 
그래서 작정하고 배열로 어떻게든 min-heap가 하던 동작을 구현한다면 그게 가장 빠른 알고리즘일 것이다.</p>
<h3 id="side-note-벤치마킹-그래프의-교차점에-관하여">side note: 벤치마킹 그래프의 교차점에 관하여</h3>
<blockquote>
<p>참고: 다른 것들도 다 뇌피셜이지만 이건 근거없는 삘만 가득한 심각한 뇌피셜이다.</p>
</blockquote>
<p><img src="https://velog.velcdn.com/images/sharlotte_04/post/174a7e90-5ae0-4858-a9e3-8561354cb65f/image.jpg" alt=""></p>
<p>처음 그래프를 보면 교차점 이후가 쭉 늘어진걸 볼 수 있는데, 그건 10000~100000 사이를 다시 10등분하여 측정한 결과이므로 그 부분을 모두 걷어내면 이렇게 지수함수 증가 개형을 볼 수 있다. 그런데 N=10^4를 기준으로 min-heap와 기타 함수들이 서로 효율이 교차되는데 이 특정 지점이 어떠한 의미를 지니고 있는지 시간복잡도 관점에서 이해해보자.</p>
<p><img src="https://velog.velcdn.com/images/sharlotte_04/post/0ff925bd-1062-4468-87f4-87c6b6b89e9e/image.png" alt="">
이 그래프는 지오지브라에서 k=2인 톰-훅 알고리즘의 시간복잡도 O(N^(log3/log2))를 선형적으로 n번 반복한 팩토리얼 함수인 f(x)와 반대로 힙을 통해 n번 반복한 팩토리얼 함수인 g(x)를 그리고 그 함수의 미분의 역을 그리고 있다.</p>
<p>일반적으로 시간복잡도란 단위 요소 당 시간이 얼마나 복잡해지는지, 즉 <strong>시간 증가량이 얼마나 가파라지는지</strong>를 표현한다. 즉 이것이 의미하는 바는 해당 수식의 도함수의 역(미분 역 == 부정적분)을 구하면 그게 곧 그 <strong>시간복잡도를 적분하는 것</strong>이기에 시간복잡도에 기인한 연속적인 그래프에 부합하다. 이게 그래프에 부합하다고 했을 때 x=4에서 교차점이 보이는건 맨 위 10000(== 10^4)와의 동질감을 느끼게 해주기에 부족함이 없다.</p>
<p>이에 따르면 자바스크립트의 BigInt 곱연산이 카라슈발 알고리즘과 k=2인 톰-쿡 알고리즘 기반인걸 증명할 수 있을 것 같다.</p>
<h2 id="벤치마킹-극단적인-대소-차이를-지닌-bigint-곱연산">벤치마킹: 극단적인 대소 차이를 지닌 BigInt 곱연산</h2>
<p><img src="https://velog.velcdn.com/images/sharlotte_04/post/d110b97e-9796-45f0-af66-e867444d03f7/image.png" alt="">
매우 흥미로운 자료다. 
첫번째와 두번째 줄을 보면 <strong>격차가 심각하게 클 경우엔 bigInt가 오히려 더 느리다.</strong>
네번째와 다섯번째 줄을 보면 <strong>1n * 1n 끼리의 곱은 십조n와 1n의 곱과 비슷할 정도로 느리다.</strong>
이건 어느정도 큰 수인 임의의 13자릿수에도 해당된다. 
bigint의 곱은 항상 느린 것처럼 보이나 그건 또 아닌게 애당초 bigint의 곱이 효율을 발휘할 곳(정확히 bigint으로만 존재 가능한 범위)에서 비교군은 존재하지 않다. 그정도로 수가 커지면 자연수로는 곱할 수 없고, 직접 O(N^2)의 다항식으로 bigint를 구현해야 하기 때문이다.</p>
<p>이 데이터가 시사하는 바는 BigInt 문단에서 톰-쿡과 카라슈바 알고리즘, 미디움 블로그에서 공통적으로 말하고 내가 정리한 <code>BigInt의 곱셈은 작은 수에 사용할수록 비효율적이다.</code>를 직접 증명했단 점에서 의미가 있다고 생각한다.</p>
<h2 id="소감">소감</h2>
<ul>
<li>최하위 등급의 문제에서 이러한 개념을 깨달은건 굉장히 드문 경험이다.</li>
<li>솔직히 이런 노가다 하면서 자료구조를 익힐 줄은 몰랐고 꽤나 놀라웠다. </li>
<li>이진트리는 내 생각 이상으로 놀라운 효율성을 지니고 있었다. 혁신적이다.</li>
<li>성능보고 벤치마킹해야지 하고 안하는 일이 부지기수했는데 실제로 해보니 재밌었고 흥미로웠다. 추상이 아닌 실체를 보고 본질을 이해하는 과정은 절대 이상적인게 아니였다. 실현 가능한 일이다.</li>
<li>팩토리얼 해결법에 subfactorial이나 다른 방법들도 있어보이는데 나중에 여유가 생기면 이것도 다시 봐야겠다.</li>
<li>주변인에 따르면 이정도면 플레티넘에 갈법한 것이라 하던데 체감상 골드 내지 실버감인 것 같다. 그런데 다시보니깐 플레티넘이 맞는것 같다.</li>
<li>하루종일 여러 사람들과 토론해가며 쓴 글이다. 이를 통해 커피 약 3.5L가 증발했으며 시간 약 40시간이 소요되었다.</li>
<li>난생 이렇게 자주 크게 글을 수정하고 갈아엎은 적이 없었다. 애초에 일기장이고 기록장이기에 빈번해지는게 당연한가 싶은데 오히려 역설적으로 이렇게나 열정적이였던 적이 근래에 들어 처음인 것 같다.</li>
<li>안타깝지만 정답을 찾지는 못했다. 그래도 가장 가깝고 신빙성있는 이론을 얻어서 많이 기쁘다.</li>
<li>아래 출처 및 참고 문단은 이 글을 쓰면서 이해 및 인용 목적으로 조회한 글들의 목록이다. 직접 가서 읽어보는것도 나쁘지 않다고 생각한다.</li>
</ul>
<h2 id="출처--참고">출처 &amp; 참고</h2>
<p><a href="https://www.acmicpc.net/problem/27434">27434번 팩토리얼 3</a>
<a href="https://chanhuiseok.github.io/posts/ds-4/">[주의] 왜 우선순위 큐는 배열이나 연결리스트로 구현하지 않을까?</a> 
<a href="https://develop-dream.tistory.com/91">Priority Queue(우선 순위 큐), Heap(Max Heap, Min Heap)</a>
<a href="https://evan-moon.github.io/2019/10/12/introduction-data-structure-heap/">최소 값과 최대 값을 빠르게 찾을 수 있게 도와주는 힙(Heap) - evan-moon</a>
<a href="https://velog.io/@iberis/%EC%8B%9C%EA%B0%84-%EB%B3%B5%EC%9E%A1%EB%8F%84-BigO">시간 복잡도 BigO</a>
<a href="https://drive.google.com/file/d/19gqGbSttMrG3fNNEsaLeXv8iUL90O2Ro/view?usp=sharing">직접 만든 분석 다이어그램</a>
<a href="https://poiemaweb.com/js-array-is-not-arrray">자바스크립트 배열은 배열이 아니다</a>
<a href="https://stackoverflow.com/a/9338040/19561566">StackOverflow - Are JavaScript Arrays actually implemented as arrays?</a>
<a href="https://www.geeksforgeeks.org/types-of-asymptotic-notations-in-complexity-analysis-of-algorithms/">geeks for geeks - Types of Asymptotic Notations in Complexity Analysis of Algorithms</a>
<a href="https://stackoverflow.com/a/22615787/19561566">StackOverflow - JavaScript runtime complexity of Array functions</a>
<a href="https://medium.com/@ashfaqueahsan61/time-complexities-of-common-array-operations-in-javascript-c11a6a65a168">medium - Time Complexities Of Common Array Operations In JavaScript</a>
<a href="https://dev.to/lukocastillo/time-complexity-big-0-for-javascript-array-methods-and-examples-mlg">dev - Time complexity Big 0 for Javascript Array methods and examples</a>
<a href="https://www.wikiwand.com/en/Hash_table">Hash Table wikiwand</a>
<a href="https://stackoverflow.com/questions/65941724/hashmaps-and-time-complexity">StackOverflow - Hashmaps and Time Complexity [closed]</a>
<a href="https://medium.com/leaningtech/a-fast-bigint-js-in-an-evening-compiling-c-to-javascript-db61ae733512">medium - A fast BigInt.js in an evening, compiling C++ to JavaScript</a> 
<a href="https://www.wikiwand.com/ko/%ED%86%B0-%EC%BF%A1_%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%98">톰 쿡 알고리즘 wikipidia</a>
<a href="https://www.wikiwand.com/ko/%EC%B9%B4%EB%9D%BC%EC%8A%88%EB%B0%94_%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%98">카라슈바 알고리즘 wikipidia</a>
<a href="https://github.com/GoogleChromeLabs/jsbi/">JSBI</a>
<a href="https://tc39.es/proposal-bigint/">ECMAScript BigInt proposal</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[컴포넌트의 관심사 분리에 대한 고민]]></title>
            <link>https://velog.io/@sharlotte_04/%EC%BB%B4%ED%8F%AC%EB%84%8C%ED%8A%B8%EC%9D%98-%EA%B4%80%EC%8B%AC%EC%82%AC-%EB%B6%84%EB%A6%AC%EC%97%90-%EB%8C%80%ED%95%9C-%EA%B3%A0%EB%AF%BC</link>
            <guid>https://velog.io/@sharlotte_04/%EC%BB%B4%ED%8F%AC%EB%84%8C%ED%8A%B8%EC%9D%98-%EA%B4%80%EC%8B%AC%EC%82%AC-%EB%B6%84%EB%A6%AC%EC%97%90-%EB%8C%80%ED%95%9C-%EA%B3%A0%EB%AF%BC</guid>
            <pubDate>Thu, 06 Apr 2023 12:32:41 GMT</pubDate>
            <description><![CDATA[<h2 id="동기">동기</h2>
<img src="https://velog.velcdn.com/images/sharlotte_04/post/466ee229-fafd-42bc-afb4-274a7035d067/image.png" />

<p>현재 내 포트폴리오의 <code>/component</code> 디렉토리에 있는 <em>일부분<em>만 펼친 사진이다. depth를 한단계씩 내리고 내려도 눈이 어지러울 지경에 다다랐다. *</em>종속성에 의존한 컴포넌트의 엄격한 분리는 한계에 도달했다.**
처음엔 마냥 종속성을 기준으로 해당 컴포넌트에만 쓰이는 컴포넌트를 하위 디렉토리에 분리하는게 정답이라고 생각했었다. 그런데 *정말 정답일까?</em> 이러한 엄격한 분리는 몇가지 문제와 고질적인 불편함을 선사해주었다.</p>
<h2 id="경험한-몇가지-단점들">경험한 몇가지 단점들</h2>
<h3 id="컴포넌트-이름을-찾기가-힘들다">컴포넌트 이름을 찾기가 힘들다</h3>
<p>아래 사진을 보다시피 디렉토리의 이름이 곧 모듈의 이름이므로 컴포넌트 스크립트는 <code>index.tsx</code>로 처리해서 임포트에서 간편함을 챙길 수 있는건 사실이다. 그러나</p>
<img src="https://velog.velcdn.com/images/sharlotte_04/post/da350c30-6c16-47b5-81e1-7be261aa9fe4/image.png" />

<p>이건 도를 넘었다. index의 무분별한 과다 사용이 초례한 도배는 가장 위 첫 사진처럼 애당초 <strong>index가 뭔지 알려고 디렉토리를 찾아야 하는 수고</strong>가 부가적으로 발생해버린다.
또한 디렉토리 depth가 너무 커서 디렉토리 트리 창을 보지 않고 바로 검색창을 여는 일이 빈번해졌다. 이름을 까먹는 순간 지옥행인거다.</p>
<h3 id="파일-수가-너무-많다">파일 수가 너무 많다</h3>
<p>재사용을 염두해두지 않고 막연하게 <em>관심사 분리를 위하여!</em> 를 외치며 무작정 분리에 분리를 거듭하니 재사용을 할 수 없는 파일 수가 무의미하게 불어나서 파일의 가치가 급락했다. 그 자체만으로 중요한 것도 아닌게 재사용도 못한다면 무슨 의미가 있단 말인가?</p>
<h3 id="분리가-무의미한-경우도-있다">분리가 무의미한 경우도 있다</h3>
<p>아래 이미지는 index와 style만 있어서 파일명을 먼저 보는 사람들 입장에선 스트레스가 여간 적지 않을 것이다. 디렉토리의 분리가 생각보다 심각하게 남용되고 있었다.</p>
<img src="https://velog.velcdn.com/images/sharlotte_04/post/02e1a076-bf0d-4d81-a1fe-7f8427376bc8/image.png" />


<p>애당초 <code>/components</code>란 컴포넌트들을 모아둔 디렉토리의 명칭이고, 컴포넌트란 재사용을 목적으로 만들어지는게 일반적이다. 어딘가에 종속되는건 어디까지나 특이케이스여야 하지 합리화되어 눈감아줘야 할 관례가 아니다. 물론 규모가 커질수록 디렉토리는 커질 수 밖에 없다. 하지만 이건 고작 한 사람에 대한 포트폴리오다. 겨우 포트폴리오가 이정도 사이즈를 가지고 있는건 대단한게 아니라 잘못된 것이다.</p>
<h2 id="해결법">해결법</h2>
<h3 id="종속성이-아닌-재사용성을-기준으로-분리하라"><strong>종속성이 아닌 재사용성을 기준으로 분리하라</strong></h3>
<p>재사용성이 매우 낮은 파일들이 부지기수로 늘어난 이유는 그것이 특정 컴포넌트에 종속된 컴포넌트 트리를 마음대로 나눠버렸기 때문이다. 그러면 왜 나눴는가? 그것은 초보때 <em>잦은 재랜더링은 성능 저하를 초례한다</em> 라는 정보에서 <em>재랜더링은 최대한 줄여야 한다</em> 라는 오해와 고정관념을 받았기 때문이다. 잦은 재랜더링은 안좋은게 맞다, 아니 정확히는 <strong>뭐든지 잦으면 안좋다</strong>. 성능 테스트조차 안해보고 멋대로 나누니 이사단이 난거다. 성능 최적화를 할려면 컴포넌트 자체에 집중해야지 <em>훅 기준으로 컴포넌트 나누기</em> 가지고 성능이 나아질 리가 없다. 그 훅이 원인이 아니라면.</p>
<h3 id="의문-스타일-분리는-어떻게-해결하는가">의문: 스타일 분리는 어떻게 해결하는가?</h3>
<p>컴포넌트만 관심사 분리 대상이 아니다. 그것이 CIJ든 CSS든간에 산더미같은 css 덩어리들도 처리할 필요가 있다. <strong>컴포넌트와 밀접히</strong> 두면서 다른 파일로 두어 <strong>관심사 분리</strong>를 유도하는 방법을 생각하던 와중에 <code>Nest.js</code>에서 겪어본 MVC패턴을 차용한 <code>app.controller.ts</code>가 생각나서 스타일 파일을 <code>&lt;component&gt;.styled.tsx</code> 로 명명해봤다.</p>
<table>
<thead>
<tr>
<th align="center"><img src="https://velog.velcdn.com/images/sharlotte_04/post/4ed51167-06e4-4386-b2c8-4f178ca4d80b/image.png" alt=""></th>
</tr>
</thead>
<tbody><tr>
<td align="center">생각보다 나쁘진 않아보인다.</td>
</tr>
</tbody></table>
<h4 id="side-note-vscode에서-스니펫-유용하게-쓰기">side note: vscode에서 스니펫 유용하게 쓰기</h4>
<details>

<p><img src="https://velog.velcdn.com/images/sharlotte_04/post/e6d11db6-bec0-43f6-aae9-7a71019fdf62/image.png" alt="">
파일 -&gt; 기본 설정 -&gt; 사용자 코드 조각 구성
File -&gt; Preferences -&gt; User Snippets
<img src="https://velog.velcdn.com/images/sharlotte_04/post/fd9425d4-0988-4cf3-a9a6-b81f9b39eef2/image.png" alt=""></p>
<p>주석에서 겁나 친절하게 알려주는 덕분에 딱히 더 찾아볼 것도 없이 바로 이행했다. 
<img src="https://velog.velcdn.com/images/sharlotte_04/post/1bb29388-3390-49b1-bf8b-f1818a8e2911/image.png" alt="">
위 사진을 보다시피 탭은 <code>\t</code> 백스페이스 이스케이프 문자를 쓰자. <code>\n</code> 개행 이스케이프 문자는 어차피 <code>body</code>가 줄 단위로 구분된 배열이기 때문에 안써도 괜찮다.</p>
<table>
<thead>
<tr>
<th><img src="https://velog.velcdn.com/images/sharlotte_04/post/64574bc3-5b60-4c20-aeef-7c5398ca3893/image.png" alt=""></th>
<th><img src="https://velog.velcdn.com/images/sharlotte_04/post/44bbf689-58bc-4ba9-a686-7d258c25d33c/image.png" alt=""></th>
</tr>
</thead>
<tbody><tr>
<td>prefix는 이와 같이 자동완성에 뜬다. 스니펫 객체의 키도 여기에 쓰이는듯 하다. 엔터를 치면 $ 순서에 따라 커서가 자동이동한다.</td>
<td></td>
</tr>
</tbody></table>
<table>
<thead>
<tr>
<th align="center"><img src="https://velog.velcdn.com/images/sharlotte_04/post/0061a2a3-862e-404c-bfd7-cc7725ed88a5/image.png" alt=""></th>
<th align="center"><img src="https://velog.velcdn.com/images/sharlotte_04/post/aee64061-7bcb-42f0-9cc1-fa0e021fd815/image.png" alt=""></th>
</tr>
</thead>
<tbody><tr>
<td align="center">이처럼 기본값을 두면</td>
<td align="center">스니펫을 사용할 때 알아서 넣어진다.</td>
</tr>
</tbody></table>
</details>

<h3 id="문제-the-anchorel-prop-provided-to-the-component-is-invalid">문제: The <code>anchorEl</code> prop provided to the component is invalid.</h3>
<p>세가지 메뉴를 리팩토링하다보니 흥미롭게도 특정 부분이 공통됨을 발견했다. 기본적으로 이들은 버튼에 의해 토글된다는 특징을 갖고 있다. 그래서 난 아래 코드와 같이 <code>MenuWrapper</code>라는 유틸리티 래퍼 컴포넌트를 만들었다. MenuProps를 확장 가능하게 만들어 외부에서 flexible한 menu 커스터마이징을 할 수 있도록 가능성을 열어뒀다.
<img src="https://velog.velcdn.com/images/sharlotte_04/post/09b058b8-a78e-4875-9589-bae05fdc9585/image.png" alt=""></p>
<p>사용법은 아래와 같이 <code>IconDrawer</code>에서 주어진 onClick 리스너를 처리하고 목적에 따라 props를 추가한다.
<img src="https://velog.velcdn.com/images/sharlotte_04/post/0e6192fa-939d-4c9b-a8d1-e26fd427ef04/image.png" alt=""></p>
<p>문제는 MUI가 이 anchorEL이 잘못된 컴포넌트를 갖고 있다고 말한다는 것이다. 정확히<code>the achorEl prop - provided to the component - is invalid.</code> 라고 19줄의 속성을 콕 집어 말했다. 에러 뿐만이 아니라 실제 동작 자체도 비정상적이게 이뤄진다.
<img src="https://velog.velcdn.com/images/sharlotte_04/post/d09288ac-4bed-40e5-a0da-9b24c5b08cc5/image.gif" alt=""></p>
<p><img src="https://velog.velcdn.com/images/sharlotte_04/post/616750fc-3a0f-4918-810e-0271a812dfb9/image.png" alt=""></p>
<h4 id="가설-1---외부-요인에-의한-element-갱신">가설 1 - 외부 요인에 의한 Element 갱신</h4>
<p>설명을 보기 전에 콘솔에 warn을 띄운 위치를 되돌아보자.
<img src="https://velog.velcdn.com/images/sharlotte_04/post/bf58db34-3963-4abd-b47d-d250d15e2af0/image.png" alt="">
간단히 코드 리딩을 하자면 resolved된 anchorEL의 Rect를 가져왔는데 크기가 0 * 0이면 레이아웃에 위치하지 않은 것으로 판단하고 경고를 띄우는 것이다.</p>
<blockquote>
<p>resolvedAnchorEL는 (() =&gt; Element) | Element 형태인 anchorEL 타입을 Element로 resolve한 상태다.</p>
</blockquote>
<p>그럼 어째서 크기가 0 * 0이고 해당 Element가 <strong>레이아웃에 위치하고 있지 않는가?</strong> 심지어 눈 앞의 UI에서 엘리먼트가 떡하니 있음에도 불구하고?</p>
<p>여기서 가설의 주제가 등장한다. 재랜더링, 정확힌 재조정 과정에서 VDOM이 이 <code>anchorEL</code>를 <strong>교체해야 할 노드</strong>로 판단하고 기존 엘리먼트를 지우고 새로운 엘리먼트를 만들어 넣은 것이다.
그럼 옛 <code>anchorEL</code>를 참조하고 있던 <code>Menu</code> 컴포넌트는 레이아웃에 위치하지 않은 지워진 엘리먼트를 참조하고 있으므로 위 에러가 터진 것이다.</p>
<p>그럼 <code>anchorEL</code>이 정확히 무엇인지 추적할 필요가 있다. 위 코드에서 <code>anchorEL</code>는 <code>onClick</code> 리스너에서 <code>currentTarget</code>를 할당받는다. 이 <code>currentTarget</code>는 클릭한 대상 엘리먼트를 가리키므로 <code>IconDrawer</code> prop를 할당한 Function Component를 찾으면 된다. </p>
<p>이번엔 <code>HeaderMenu</code> 컴포넌트를 예로 들겠다.
<img src="https://velog.velcdn.com/images/sharlotte_04/post/98a16a7b-6d11-4d3a-8efc-ff08594daa19/image.png" alt="">
이처럼 Function Component가 매마다 새로 생성된다. VDOM이 재조정 절차에서 이것을 새로 생성시키지 않게 만들려면 두가지 실험이 필요하다.</p>
<ol>
<li>Function Component를 개별로 선언하여 불필요한 재선언 방지</li>
<li>이것도 아니라면 <code>React.memo</code>를 통해 컴포넌트를 통째로 메모라이징</li>
</ol>
<p>...그리고 첫 실험에서 성공했다! 결국 원인은 매마다 새로운 FC를 만들어 참조중이던 <code>anchorEL</code>이 새 <code>anchorEL</code>과 근본적으로 다른 컴포넌트여서 재조정 과정에서 교체가 되어버린 것이다.
<img src="https://velog.velcdn.com/images/sharlotte_04/post/6441d7ba-4c17-41c8-a659-f384aeeab974/image.png" alt="">
컴포넌트가 하나 더 늘어난게 불편하지만 구조상 합리적이라고 느껴졌고 이틀 내내 발목을 잡던 억까를 해결했으니 충분히 만족스럽다.</p>
<blockquote>
<p><strong>추가 의문: 익전의 <em>재랜더링에 의한 갱신</em>은 올바른 가설인가?</strong>
여기에 올바른 예시가 하나 있다.
<img src="https://velog.velcdn.com/images/sharlotte_04/post/525e0075-f0d2-4604-a287-760ad20fda57/image.png" alt="">
테마 색 선택 메뉴에 쓰이는 컴포넌트인데 <code>theme</code>를 얻기 위해 <code>useTheme</code>를 쓴 것을 볼 수 있다. 이 <code>useTheme</code>는 테마 context의 훅이라서 테마가 바뀌면 해당 컴포넌트도 재랜더링한다. 의문인 가설에 의하면 오류가 여전히 발생해야 하지만 놀랍게도 아무런 일도 일어나지 않았다.</p>
</blockquote>
<h3 id="의문-종속된-컴포넌트들은-어떻게-나눠야-하는가">의문: 종속된 컴포넌트들은 어떻게 나눠야 하는가?</h3>
<p>컴포넌트 트리에서 멋대로 땐게 아니라 실제로 나눠야 할 필요가 있는 컴포넌트들도 있기 마련이다. 
이 외에도 너무 많은 의문이 있어 이 컨벤션이 맞는지 의문이 들 지경이다.
수많은 코드 조각을 어떻게 헨들링해야 할지 좋은 선례가 필요할 것 같다.
이 의문은 나중으로 미뤄두기로..</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[변성 (Variant)]]></title>
            <link>https://velog.io/@sharlotte_04/Variant</link>
            <guid>https://velog.io/@sharlotte_04/Variant</guid>
            <pubDate>Fri, 03 Feb 2023 00:14:56 GMT</pubDate>
            <description><![CDATA[<p>종종 객체 지향 언어에서 공변성과 반공변성이란 단어가 나옵니다. 대개 서브타입 관계를 논할 때 등장하는데, 저는 주로 클래스의 메서드 오버라이딩과 타입스크립트의 타입 응용에서 경험했습니다.</p>
<p>공변성과 반공변성은 뭘까요? <a href="https://learn.microsoft.com/ko-kr/dotnet/standard/generics/covariance-and-contravariance">Microsoft .NET 설명서</a>는 <em>&quot;원래 지정된 것보다 더 많이 파생되거나(더 구체적인) 더 적게 파생된 형식(덜 구체적인)을 사용할 수 있는 능력을 지칭하는 용어&quot;</em> 라고 말합니다. 타입의 확장을 고민해야 할 개발자에게 가장 현실적인 표현이죠.</p>
<p>그러나 변성에는 공변성과 반공변성만이 전부가 아닙니다. 무공변성(불변성)과 이변성 또한 존재합니다. 대부분의 경우에서 이 개념들은 베타적 개념이므로, 실제 개발에서 중요하게 여길 필요는 없습니다. 여러 케이스에서 독특한 부분들을 담당하고 있지만 지엽적으로 이해해도 무방하기 때문입니다.
그럼에도 불구하고 이 글에선 네 가지 변성을 모두 설명하는 것을 목표로 두겠습니다.</p>
<h2 id="정의">정의</h2>
<p>변성에는 총 네가지 유형의 변성이 있습니다. 크게 공변성과 반공변성으로 나뉘고, 이들을 동시에 충족하거나 그렇지 않은 게 있습니다.</p>
<blockquote>
<p><strong>정의가 불확실할 수 있습니다.</strong>
개인적으로 변성들에 대한 완벽한 정의 출처를 찾지 못했습니다. 만약 알고 계신다면 부디 댓글로 알려주세요.
이 글에선 <a href="https://www.wikiwand.com/en/Covariance_and_contravariance_(computer_science)#Formal_definition"><strong>위키피디아에 기재된 정의</strong></a>를 서브타입 관계로 설명하겠습니다.</p>
</blockquote>
<ul>
<li>공변성, T ≤ S라면 <code>F&lt;T&gt;</code> ≤ <code>F&lt;S&gt;</code> 이다.</li>
<li>반공변성, T ≤ S라면 <code>F&lt;S&gt;</code> ≤ <code>F&lt;T&gt;</code> 이다. 즉 공변의 반대다.</li>
<li>이변성, 공변하면서 반공변하다.</li>
<li>변성, 공변하거나 반공변하거나 이변하면 변성이다.</li>
<li>불변성(또는 무공변성), 공변하지도 반공변하지도 않다.</li>
</ul>
<blockquote>
<p>참고: T ≤ S라 함은 T가 S의 <strong>서브타입</strong>(또는 T가 S)이란 뜻입니다. 동의어로 <strong>부분집합</strong>, <strong>파생된 타입</strong>, <strong>S에 할당할 수 있는 타입</strong> 등이 있습니다. 클래스에서 <code>T extends S</code>를 생각해보세요.</p>
</blockquote>
<p>변성은 서브타입 관계와 서브타입 관계의 관계에 대한 성질을 일컫습니다.
공변성을 예로 들어 보면, <code>T</code>가 <code>S</code>의 서브타입이면서 <code>F&lt;T&gt;</code>가 <code>F&lt;S&gt;</code>의 서브타입이면 이 서브타입 관계의 관계를 보고 공변하다고 말합니다. 반공변성은 이 관계의 반대에 해당하며, 이변성은 이 관계 또는 반대의 관계(반공변성)에 해당합니다. 불변성은 이 관계도, 그 반대의 관계도 아닙니다. 즉, 애초에 아무런 관계가 없다고 정의합니다.</p>
<h2 id="응용">응용</h2>
<p>앞서 말씀드렸다시피 저는 메서드 오버라이딩과 타입스크립트의 타입 응용에서 공변과 반공변을 경험했습니다. 실제 사례를 통해 공변과 반공변과 같은 변성들이 어떻게 개발에 영향을 끼치는지 이해해 봅시다.</p>
<h3 id="메서드-오버라이딩">메서드 오버라이딩</h3>
<p>OOP에서 가장 유명한 원칙 중 하나인 SOLID 원칙을 아시나요? 그 중 하나인 <a href="https://www.wikiwand.com/en/Liskov_substitution_principle#Principle">리스코프 치환 원칙</a>에선 메서드 오버라이딩에 대해 리턴 타입의 공변성, 매개변수 타입의 반공변성을 강제하고 있습니다. 앞선 설명에 따르면 오버라이딩할 메서드의 리턴 타입은 기존 타입 또는 그 서브타입들로 좁혀야 하고, 반대로 파라미터 타입은 기존 타입 또는 그 슈퍼타입들로 넓혀야 합니다.</p>
<p>근본적으로 왜 이래야 할까요? 왜 메서드를 오버라이딩할 때 그 메서드의 슈퍼타입에 의존해야 하죠?
아래 TypeScript 예제 코드를 봅시다.</p>
<pre><code class="language-ts">class Parent {
    // TypeScript에선 lambda의 파라미터 타입만이 반공변성을 지닙니다.
    public method = (param: string | number) =&gt; {

    }
}

class Child1 extends Parent {
    public override method = (param: string | number | symbol) =&gt; {

    }
}
class Child2 extends Parent {
    // 파라미터 타입은 반공변적이여야 하므로 param의 타입은 Parent#method의 param의 슈퍼타입이여야 합니다.
    public override method = (param: string) =&gt; {

    }
}</code></pre>
<p>먼저 리스코프 치환 원칙에 대입하여 코드의 문제를 설명해 봅시다. 
<code>Parent</code>는 <code>method</code>가 <code>string</code> 또는 <code>number</code> 타입의 인자를 받아 뭔갈 하기로 계약했음에도 불구하고 <code>Child2</code>는 <code>method</code>가 <code>string</code> 타입의 인자만 받기로 역할을 수정했습니다. 리스코프 치환 원칙은 서브타입<code>S</code>의 객체가 그것의 슈퍼타입<code>T</code>의 객체로 치환될 수 있단 원칙인데, <code>(param: string) =&gt; void</code> 함수는 <code>(param: string | number) =&gt; void</code> 함수에 할당할 수 없어서 <code>Child2</code> 객체가 <code>Parent</code>에 할당할 수 없게 됩니다. 즉, 원칙을 위반했습니다.</p>
<p>그러나 원칙을 어긴 건 크게 와닿지 않습니다. 원칙을 어긴 게 근본적으로 무슨 문제죠?
이 문제를 그냥 무시한다면 어떻게 될지 생각해 봅시다. </p>
<pre><code class="language-ts">class Parent {
    public method = (param: string | number) =&gt; {
        console.log(String(param).slice(0));
    }
}

class Child extends Parent {
    public override method = (param: string) =&gt; {
        // param은 무조건 string이니 아무런 문제가 없어야겠죠?
        console.log(param.slice(0));
    }
}

function doSmhWithParent(parent: Parent) {
    parent.method(12);
}

doSmhWithParent(new Child());</code></pre>
<p>위 코드는 <code>Parent</code> 타입으로 뭔갈 할 <code>doSmhWithParent</code> 함수가 <code>Parent#method</code>는 number를 인자로 넘겨도 된다고 생각하여 <code>12</code>를 인자로 넘긴 코드입니다.
그러나 정작 그 함수에 인자로 넘긴 객체는 원칙을 어긴 <code>Child</code>의 객체였고, 심지어 <code>number</code>를 인자로 넘겨선 안 되도록 함수를 고쳐놓은 상태입니다.
<img src="https://velog.velcdn.com/images/sharlotte_04/post/5ffceb43-298a-4fc2-ab3b-ffeb0513c71f/image.png" alt="">
이것이 매개변수가 서브타입이 아니라 슈퍼타입이어야 하는 이유입니다. <code>Parent</code>의 모든 자식 클래스의 객체들은 <code>Parent</code>에 할당할 수 있으므로 <code>method</code> 함수의 인자 타입은 <strong>최소</strong> <code>string | number</code> 이여야 합니다. </p>
<blockquote>
<p><strong>주의: 항상 그렇진 않습니다.</strong>
여러 언어들은 자체적으로 매개변수 타입의 공변성과 반공변성을 선택적으로 주거나 동시에 주는 등의 차별을 두고 있습니다. 종종 매개변수의 서브타입 관계가 클래스의 서브타입 관계와 일치하는 게 더 직관적인 경우가 있기 때문입니다. 이런 예외적인 경우에 대해서 C#, kotlin, java 같은 언어들은 제너릭을 통해 공변과 반공변을 명시케 하고 있습니다.</p>
</blockquote>
<table>
<thead>
<tr>
<th align="center">공변/반공변</th>
<th align="center">Java</th>
<th align="center">Kotlin/C#</th>
<th align="center">TypeScript</th>
</tr>
</thead>
<tbody><tr>
<td align="center">공변성</td>
<td align="center"><code>T extends Parent</code></td>
<td align="center"><code>out Parent</code></td>
<td align="center"><code>T extends Parent</code></td>
</tr>
<tr>
<td align="center">반공변성</td>
<td align="center"><code>T super Parent</code></td>
<td align="center"><code>in Parent</code></td>
<td align="center"><code>arrow function으로 선언</code></td>
</tr>
<tr>
<td align="center">이에 대해선 함수 줄여 쓰기에서의 이변 문단에서 추가로 이야기하겠습니다.</td>
<td align="center"></td>
<td align="center"></td>
<td align="center"></td>
</tr>
</tbody></table>
<p>그럼, 반환 타입은 왜 매개변수 타입과 달리 공변적이여야 할까요? 반환값을 사용할 경우를 생각해 보면 좀 더 손쉽게 이해할 수 있습니다. <code>Parent#method</code>가 <code>{foo:string}</code> 타입만 반환하도록 정한 상태에서 <code>Child#method</code>가 <code>object</code> 타입만 반환하도록 멋대로 바꾸면 위 에러와 같은 외통수가 똑같이 터지겠죠.</p>
<h3 id="union-to-intersection">Union to Intersection</h3>
<p>앞서 설명한 메서드의 공변성과 반공변성을 타입스크립트의 타입에선 좀 더 유연하게 응용할 수 있습니다. 이해를 위해 겸사겸사 이 글을 처음 쓸 때 다뤘던 U2I(Union to Intersection)를 예로 들어보겠습니다.</p>
<pre><code class="language-ts">type UnionToIntersection&lt;U&gt; = 
  (U extends unknown ? (k: U) =&gt; void : never) extends (k: infer I) =&gt; void 
    ? I 
    : never;</code></pre>
<p><code>UnionToIntersection</code>는 타입 분배와 반공변적인 매개변수 타입의 추론을 이용하여 U의 가능한 모든 슈퍼 타입 I를 추론하는 타입입니다. 예를 들어 <code>{ foo: &quot;bar&quot; } | { bar: &quot;foo&quot; }</code>타입을 인자로 받아 <code>{ foo: &quot;bar&quot; } &amp; { bar: &quot;foo&quot; }</code> 타입을 내뱉습니다. 저 타입에선 타입 <code>U</code>에 할당 가능한 타입 <code>I</code>를 추론하는데, 매개변수 타입은 반공변적이므로 <code>I</code>는 <code>U</code>의 슈퍼타입입니다. 그런데 어떻게 알지도 모르는 슈퍼타입을 추론할 수 있을까요? 어떻게 반공변성의 성질로 합집합을 교집합으로 바꿀 수 있죠?</p>
<p>답은 타입스크립트가 조건부 타입을 추론하는 알고리즘에 있습니다.
조건부 타입이 처음 나온 <a href="https://www.typescriptlang.org/docs/handbook/release-notes/typescript-2-8.html">typescript 2.8v 릴리즈 노트</a>에는 조건부 타입을 해석하거나 연기하는 내용에 관해 설명합니다.</p>
<blockquote>
<p>Next, for each type variable introduced by an <code>infer</code> (more later) declaration within <code>U</code> collect a set of candidate types by inferring from <code>T</code> to <code>U</code> (using the same inference algorithm as type inference for generic functions). For a given <code>infer</code> type variable <code>V</code>, if any candidates were inferred from co-variant positions, the type inferred for <code>V</code> is a union of those candidates. Otherwise, if any candidates were inferred from contra-variant positions, the type inferred for <code>V</code> is an intersection of those candidates. Otherwise, the type inferred for <code>V</code> is <code>never</code>.
다음으로, U 내에서 <code>infer</code>(나중에 자세히 설명) 선언에 의해 도입된 각 타입 변수에 대해 (일반 함수에 대한 타입 추론과 동일한 추론 알고리즘을 사용하여) <code>T</code>에서 <code>U</code>로 추론하여 후보 타입 집합을 수집합니다. 주어진 타입 변수 <code>V</code>에 대해 공변적인 위치에서 유추된 후보가 있는 경우, V에 대해 유추된 타입은 해당 후보들의 유니온 타입이 됩니다. 그렇지 않고, 반공변적인 위치에서 유추된 후보가 있는 경우, V에 대해 유추된 타입은 해당 후보들의 인터섹션 타입이 됩니다. 그렇지 않으면, V에 대해 추론된 타입은 <code>never</code>입니다.</p>
</blockquote>
<p>이 내용은 조건부 타입이 <code>T extends infer V ? X : Y</code> 처럼 <code>infer</code> 선언이 섞여있을 때, <code>infer</code>로 선언한 타입 변수를 어떻게 추론하는지에 대한 내용입니다. 우리가 가장 주목해야 할 부분이죠. </p>
<p>이 내용은 문서에서 후술한 <a href="https://www.typescriptlang.org/docs/handbook/release-notes/typescript-2-8.html#type-inference-in-conditional-types">Type inference in conditional types</a> 문단의 예제 코드들을 통해 이 문서가 무엇을 말하는 것인지를 이해할 수 있습니다. 아래 코드는 그 예제 코드들을 살짝 수정한 임의의 코드입니다. <a href="https://www.typescriptlang.org/play?#code/C4TwDgpgBAggPAFQHxQLxQVCAPYEB2AJgM5QDeUAhgFxQAUAlGigJb4BmEATlAKoDcUAEa1GzKG049eUAL4AoKFAD8fRVFr4IAN2795oSFTSw4FGvSaoUxYFzYBzQSMvj8AVwC2Q7nKT6AegClKAA9ZXkDcGgAIUQUdEwcPCJSc1FsWklfXisUbQB7FkJnDKyOHLyoQuK5dVVedU0dPSijIRM49PpMqABtW3t8BwBdKpqS4TL+j29uMfEJv0Eg-sHHEagAMhmvHy4R+VWlcMigA">typescript playground에서 확인해 보세요.</a></p>
<pre><code class="language-ts">type A&lt;T&gt; = T extends { a: () =&gt; infer U; b: () =&gt; infer U }
  ? U
  : never;
type a = A&lt;{ a: () =&gt; string; b: () =&gt; number }&gt;;

type B&lt;T&gt; = T extends { a: (x: infer U) =&gt; void; b: (x: infer U) =&gt; void }
  ? U
  : never; // string | number
type b = B&lt;{ a: (x: [string]) =&gt; void; b: (x: [number]) =&gt; void }&gt;; // [string] &amp; [number]</code></pre>
<p><code>A</code>는 &quot;공변적인 위치&quot;에서, B는 &quot;반공변적인 위치&quot;에서 &quot;후보 타입들&quot;을 추론합니다. 이 후보 타입들이란 조건부 타입에서 조건을 성립시킬 수 있는 타입의 후보들이란 뜻입니다. 문서에 따라, 공변적인 위치인 <code>A</code>는 후보들을 유니온 타입(합집합)으로 묶었고 반공변적인 위치인 <code>B</code>는 인터섹션 타입(교집합)으로 묶었습니다. (타입 <code>b</code>에서 <code>string</code>과 <code>number</code>를 배열로 감싼 이유는, 원시형 타입들은 서로소 타입이기 때문에 <code>string &amp; number</code>같은 원시형 타입들의 교집합은 있을 수 없으므로 공집합(<code>never</code>)이 나오기 때문입니다.)</p>
<p>즉, 이 문단의 처음에서 <code>{ foo: &quot;bar&quot; } | { bar: &quot;foo&quot; }</code>의 슈퍼타입이 어떻게 나왔냐 하면 <code>I</code> 타입 변수가 공변적인 위치에서 _<code>{ foo: &quot;bar&quot; }</code>와 <code>{ bar: &quot;foo&quot; }</code>로 후보가 추론_되었고, <strong>infer로 선언된 타입 변수의 문법적 성질에 의해 인터섹션 타입으로 추론</strong>되었다고 말할 수 있습니다.</p>
<p>그러나 아직 의문이 남아 있습니다. 도대체 어떻게 <code>{ foo: &quot;bar&quot; } | { bar: &quot;foo&quot; }</code>의 슈퍼타입으로 <code>{ foo: &quot;bar&quot; }</code>와 <code>{ bar: &quot;foo&quot; }</code>가 추론된 거죠? 상식적으로 <code>{ foo: &quot;bar&quot; } | { bar: &quot;foo&quot; }</code>의 슈퍼타입은 자기 자신 또는 자신을 포함한 제 3의 타입이어야 합니다. <code>{ bar: &quot;foo&quot; }</code>는 오히려 서브타입이잖아요!</p>
<p>이 의문의 해답은 <strong><a href="https://www.typescriptlang.org/docs/handbook/release-notes/typescript-2-8.html#distributive-conditional-types">분배 조건부 타입(Distributive conditional types)</a></strong>에 있습니다. (대개 타입을 분배한다고 말합니다.)</p>
<blockquote>
<p>Conditional types in which the checked type is a naked type parameter are called distributive conditional types. Distributive conditional types are automatically distributed over union types during instantiation. For example, an instantiation of <code>T extends U ? X : Y</code> with the type argument <code>A | B | C</code> for <code>T</code> is resolved as <code>(A extends U ? X : Y) | (B extends U ? X : Y) | (C extends U ? X : Y)</code>.
검사된 타입이 <strong>네이키드 타입</strong> 매개변수인 조건부 타입을 <strong>분배 조건부 타입</strong>이라고 합니다. 분배 조건부 타입은 인스턴스화 중에 유니온 타입에 자동으로 분배됩니다. 예를 들어, 타입 파라미터 <code>T</code>에 <code>A | B | C</code>가 있다면 <code>T extends U ? X : Y</code>의 인스턴스화는 <code>(A extends U ? X : Y) | (B extends U ? X : Y) | (C extends U ? X : Y)</code>로 해석됩니다.</p>
</blockquote>
<p>정보: <strong>네이키드 타입(naked type)</strong>이란 배열, 튜플, 함수, 또는 제너릭 타입과 같은 타입들로 래핑되지 않은 타입을 말합니다. (&quot;naked&quot;자체가 &quot;<em>벌거벗은</em>&quot;이란 뜻) - <a href="https://stackoverflow.com/a/51651684">StackOverFlow 참조</a></p>
<p>이 이해를 기반으로 앞서 선언한 <code>UnionToIntersection</code>를 봅시다.</p>
<pre><code class="language-ts">type UnionToIntersection&lt;U&gt; = 
  (U extends unknown ? (k: U) =&gt; void : never) extends (k: infer I) =&gt; void 
    ? I 
    : never;</code></pre>
<p>그리고 이 타입을 사용한 타입 <code>a</code>도 봅시다.</p>
<pre><code class="language-ts">type a = UnionToIntersection&lt;{ foo: string } | { numbericBar: number }&gt;</code></pre>
<p>이 타입 <code>a</code>는 타입 분배에 의해 내부적으로 아래와 같은 꼴이 된 것입니다.</p>
<pre><code class="language-ts">type a1 =
  ((k: { foo: string }) =&gt; void) | 
  ((k: { numbericBar: number }) =&gt; void) 
    extends (k: infer I) =&gt; void ? I : never;</code></pre>
<p>그리고 놀랍게도 그 결과는 똑같습니다. - <a href="https://www.typescriptlang.org/play?#code/C4TwDgpgBAqgdgSwPZwCpIJJ2BATgZwgGNhk4AeGAPigF4oAoKKAChiggA8c4ATfKAFc4AazhIA7nCgB+ViIBcsAJR0aANyQJeUJXAjq8qrj37ylCOADM8UDKtoatOps1l3Gb3VH2HcAbgYGUEgoAEM6WEQUdCwcAmJSFHIAbygrJCQlfGBcSwBzKABfKAAfKDS4QQBbACM8BCIAITDcPRr63GKqBgB6XrcAPRkgkOgwgEZIlhZFCvTM7NyC4ocnbVVymbnKjobm1va62yK1qE0Njm4IPgFZi2tbezVz53cMb188QP6hmSA">typescript playground에서 직접 확인해 보세요.</a></p>
<p>즉, <code>{ foo: &quot;bar&quot; } | { bar: &quot;foo&quot; }</code>에서 <code>{ foo: &quot;bar&quot; }</code>의 슈퍼타입 따로, <code>{ bar: &quot;foo&quot; }</code>의 슈퍼타입 따로 추론하는 것입니다. 그리고 그것들의 슈퍼타입은 달리 알 바가 없으니 <strong>자기 자신이 추론됩니다.</strong> 그렇게 추론의 후보 타입들로 유니온 타입을 구성하는 타입들이 나오는 것입니다. 그리고 <code>infer</code>의 문법적 특성에 의해 이렇게 나온 후보 타입들이 인터섹션 타입으로 묶여서 타입 <code>I</code>로 추론되는 것이고요. 그리고 최종적으로 이렇게 추론된 타입 <code>I</code>가 조건부 타입에 의해 결과로 나오는 것입니다.</p>
<p><code>UnionToIntersection</code>은 매우 흥미롭고 대표적인 타입스크립트 조건부 타입의 예입니다. 변성을 이해하는 겸사겸사 이 U2I에 대해서도 분석했는데, 꽤 재미있어서 분량 조절을 실패해버렸네요 ;P</p>
<h3 id="함수-줄여-쓰기에서의-이변">함수 줄여 쓰기에서의 이변</h3>
<p>이변성에 대해선 그 모습을 흔히 보기가 힘듭니다. 공변성 또는 반공변성을 만족시키는, 즉 자신을 포함한 모든 슈퍼타입과 모든 서브타입을 관계에 포함하는 성질이기 때문입니다. 그러나 타입스크립트에는 유명한 케이스가 하나 존재합니다.</p>
<p>바로 함수 줄여 쓰기 선언입니다. <a href="https://seob.dev/posts/%EA%B3%B5%EB%B3%80%EC%84%B1%EC%9D%B4%EB%9E%80-%EB%AC%B4%EC%97%87%EC%9D%B8%EA%B0%80/#%EC%9D%B4%EB%B3%80%EC%84%B1bivariance">strictFunctionTypes TS컴파일러 옵션을 다루는 현섭 님의 아티클</a>의 이변성 문단에선 줄여 쓰기 방식에서 매개변수 타입이 이변적이라고 설명합니다.</p>
<blockquote>
<p><em>정리하자면, 줄여 쓰기(shorthand) 방식(<code>set(item: T): void;</code>)은 메서드 파라미터를 이변적으로 동작시키기 위한 표기법이고, 프로퍼티 방식(<code>set: (item: T) =&gt; void;</code>)은 메서드 파라미터를 반공변적으로 동작시키기 위한 표기법이라고 볼 수 있겠습니다.</em></p>
</blockquote>
<p>정리하자면 <code>Array&lt;string | number&gt;</code>의 <code>push</code> 메서드의 타입은 <code>(...item: Array&lt;string | number&gt;) =&gt; void</code>이지만, 서브타입인 <code>Array&lt;number&gt;</code>의 <code>push</code> 메서드의 타입은<code>(...item: Array&lt;number&gt;) =&gt; void</code>이므로 타입 에러가 발생하는 문제가 생깁니다. 정작 number를 넣어도 문제가 전혀 없으니 <em>현실적인 문제</em>도 없고, 결국 유명무실한 문제가 발생한 것입니다.
따라서 이러한 현실과 원칙의 괴리를 해결하기 위해 이변성의 탈출구를 작위적으로 마련한 것입니다. C#같은 다른 언어는 이변성 대신 예약어로 해결했는데 말이죠... 🤔</p>
<p>참고로 이렇게 작위적으로 만든 탈출구는 strict하게 타입을 검사해도 유효해서, 타입 체계의 큰 취약점으로 작용할 수 있습니다. 그래서 <a href="https://github.com/typescript-eslint/typescript-eslint/blob/main/packages/eslint-plugin/docs/rules/method-signature-style.md">eslint는 이를 경고하는 규칙</a>을 가지고 있습니다.</p>
<h2 id="끝">끝</h2>
<p>이 글에선 공변성과 반공변성, 이변성에 대해서 메서드 오버라이딩과 U2I, 타스의 특별한 이변성 구멍을 예로 들어 설명해 드렸습니다. 단순히 예에 그치지 않고 완벽한 이해를 위한 설명을 야무지게 채워 넣었습니다. 개인적으로 변성의 이해에 대해 확신이 들었고, 여지껏 갈팡질팡한 U2I도 완벽히 이해해서 속이 후련합니다.</p>
<p>추가로 불변성에 대해선 따로 이야길 안 했는데, 애초에 변성이 없으면 다 무공변성이기 때문입니다. 매서드 오버라이딩의 파라미터를 빼면 대부분이 공변과 무공변일테니 특별히 예를 드는 게 무의미하죠.</p>
<h2 id="참고">참고</h2>
<ul>
<li><a href="https://learn.microsoft.com/ko-kr/dotnet/standard/generics/covariance-and-contravariance">제네릭의 공변성(Covariance) 및 반공변성(Contravariance)</a></li>
<li><a href="https://www.wikiwand.com/ko/%EB%A6%AC%EC%8A%A4%EC%BD%94%ED%94%84_%EC%B9%98%ED%99%98_%EC%9B%90%EC%B9%99">리스코프 치환 원칙</a></li>
<li><a href="https://www.wikiwand.com/ko/%EA%B3%B5%EB%B3%80%EC%84%B1%EA%B3%BC_%EB%B0%98%EA%B3%B5%EB%B3%80%EC%84%B1_(%EC%BB%B4%ED%93%A8%ED%84%B0_%EA%B3%BC%ED%95%99)">공변성과 반공변성 (컴퓨터 과학)</a>, <a href="https://www.wikiwand.com/en/Covariance_and_contravariance_(computer_science)">Covariance and contravariance (computer science)</a></li>
<li><a href="https://seob.dev/posts/%EA%B3%B5%EB%B3%80%EC%84%B1%EC%9D%B4%EB%9E%80-%EB%AC%B4%EC%97%87%EC%9D%B8%EA%B0%80/#%EC%9D%B4%EB%B3%80%EC%84%B1bivariance">공변성이란 무엇인가</a></li>
<li><a href="https://velog.io/@lsb156/covariance-contravariance">공변성, 반공변성, 무공변성이란?</a></li>
<li><a href="https://www.typescriptlang.org/docs/handbook/release-notes/typescript-2-8.html">typescript 2.8v 릴리즈 노트</a></li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[Visual Studio 시작하기]]></title>
            <link>https://velog.io/@sharlotte_04/Visual-Studio-%EC%8B%9C%EC%9E%91%ED%95%98%EA%B8%B0</link>
            <guid>https://velog.io/@sharlotte_04/Visual-Studio-%EC%8B%9C%EC%9E%91%ED%95%98%EA%B8%B0</guid>
            <pubDate>Tue, 24 Jan 2023 13:12:01 GMT</pubDate>
            <description><![CDATA[<p>유니티 개발을 위해 visual studio를 열었는데 이상한 낌새가 들어 찾아보니 2019 visual studio였다. 이 버전으로 개발하는 것도 나쁘진 않지만 신버전을 두고 구버전에 머무는건 불편하다.</p>
<h1 id="기본-설치">기본 설치</h1>
<p><a href="https://visualstudio.microsoft.com/ko/thank-you-downloading-visual-studio/?sku=Community">https://visualstudio.microsoft.com/ko/thank-you-downloading-visual-studio/?sku=Community</a>
에선 Visual Studio의 Community 버전을 설치할 수 있다. Professional과 Enterprise는 개인 개발 입문자에겐 불필요하고 유료라서 버겁기도 하다.</p>
<p>visual studio는 visual studio installer에서 설치 및 관리되므로 다운받은 Setup 프로그램에선 installer부터 자동으로 다운받는다.</p>
<p><img src="https://velog.velcdn.com/images/sharlotte_04/post/1a98dda7-6b7a-4ade-86c5-540b8d98f411/image.png" alt="">
메이저 IDE 아닐라까봐 워크로드만 해도 엄청 많다. 난 오직 유니티를 위해 visual studio를 사용할 생각이므로 Unity 워크로드를 선택했다. 게임 엔진용 워크로드 치고 1.3기가만 늘어난거면 괜찮은 것 같다.</p>
<p><img src="https://velog.velcdn.com/images/sharlotte_04/post/136705f8-bcb4-46f3-981a-05ce4797c31d/image.png" alt="">
멋지고 세련된 마이크로소프트의 대표 IDE 편집기를 얻었다!</p>
<h1 id="확장">확장</h1>
<blockquote>
<p>튜링의 끝은 순정이다</p>
</blockquote>
<p>라는 말이 있지만 끝을 보지 못한 난 추가 확장 설치에 돌입한다.
기본적으로 <strong>확장 -&gt; 확장 관리</strong>에서 검색으로 확장을 찾아 다운로드할 수 있다. 필요에 따라 리로드하기도 한다.</p>
<h2 id="wakatime">wakatime</h2>
<p><img src="https://velog.velcdn.com/images/sharlotte_04/post/47c3ad89-1aca-4dfd-8ec1-cc19d5151ef8/image.png" alt=""></p>
<p><a href="https://wakatime.com/">wakatime</a>는 대표적인 코드시간 측정 서비스로 약 <a href="https://wakatime.com/plugins">50개의 환경</a>에서 사용 시간을 측정할 수 있다.
비단 사용시간 뿐만이 아니라, <strong>개발 프로젝트</strong>, <strong>개발 언어</strong>, <strong>개발 환경 플렛폼</strong>, <strong>개발 환경 기기</strong>, <strong>개발 시간대</strong>, <strong>개발 환경 운영체제</strong> 등을 매주 통계로 보여준다.
<img src="https://velog.velcdn.com/images/sharlotte_04/post/749d6e6e-08b8-4ec1-b95d-9dad29ffea5b/image.png" alt=""></p>
<blockquote>
<p><a href="https://wakatime.com/@sharlottes">https://wakatime.com/@sharlottes</a>
프로필도 공유할 수 있다!</p>
</blockquote>
<p>2주 전 통계는 <a href="https://wakatime.com/settings/billing?upgrade=true">프리미엄 정액제</a>를 구독해야 한다.
기록을 위해선 API 키가 필요한데,
<img src="https://velog.velcdn.com/images/sharlotte_04/post/75f185f7-d645-43d1-bdce-1b2d27d2c1c5/image.png" alt="">
에서 재설정할 수 있다.</p>
<h2 id="material-theme">Material Theme</h2>
<p><img src="https://velog.velcdn.com/images/sharlotte_04/post/befd9146-3682-47fb-b7ff-6d948cd93fb1/image.png" alt="">
해당 확장을 설치하고
<img src="https://velog.velcdn.com/images/sharlotte_04/post/2b2afcac-cd0a-4bf1-be06-eaa9ffc2686b/image.png" alt="">
원하는 테마로 설정하면 된다.</p>
<p>역시나 이쁘다.</p>
<h1 id="unity에서-기본-편집기로-설정하기">Unity에서 기본 편집기로 설정하기</h1>
<p><img src="https://velog.velcdn.com/images/sharlotte_04/post/8961c31c-e5cf-46ab-b787-70b3b8bd2034/image.png" alt="">
VisualStudio는 윈도우 기본 앱 설정에서 위와 같이 .cs(C#) 확장자의 자리를 차지하고 있지만 유니티에서 직접적으로 기본 편집기를 특정하는 방법이 있다.</p>
<p><strong>Edit -&gt; Preference -&gt; External Tools</strong>
<img src="https://velog.velcdn.com/images/sharlotte_04/post/e896b6b8-c663-4543-886e-9c91a25a062e/image.png" alt="">
에서 Visual Studio Code....가 아니라 없네?
<img src="https://velog.velcdn.com/images/sharlotte_04/post/bb0f966f-4687-4303-8171-b7113f3ee310/image.png" alt=""></p>
<p>이건 Unity를 실행한 상태에서 Visual Studio를 설치했기 때문이며 마치 visual studio code를 켜놓고 yarn를 설치하니 작동이 안되던 일과 같은 설치 프로그램의 고충이다.
<img src="https://velog.velcdn.com/images/sharlotte_04/post/bd2d168c-b832-4804-a1d2-4c41240b27c8/image.png" alt="">
재실행하니 잘 나와있다.</p>
<h1 id="결과">결과</h1>
<p><img src="https://i.imgur.com/im75Hbw.png" alt="">
잘 작동한다.
유니티 워크플로를 추가했으므로 Unity 메시지와 참조 위치, 솔루션 탐색기가 기본적으로 갖춰진 모습도 볼 수 있었다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Error building Player: Incompatible color space with graphics API]]></title>
            <link>https://velog.io/@sharlotte_04/Error-building-Player-Incompatible-color-space-with-graphics-API</link>
            <guid>https://velog.io/@sharlotte_04/Error-building-Player-Incompatible-color-space-with-graphics-API</guid>
            <pubDate>Tue, 24 Jan 2023 11:48:05 GMT</pubDate>
            <description><![CDATA[<h1 id="발단">발단</h1>
<p>Unity 필수 과정의 살펴보기에서 프로젝트 배포를 이수하던 중
<img src="https://velog.velcdn.com/images/sharlotte_04/post/6ea44ac6-4cff-462f-936a-cba3c0856f09/image.png" alt=""></p>
<p>WebGL 빌드를 시도하다가 발생한 문제다.
<img src="https://velog.velcdn.com/images/sharlotte_04/post/be4047e7-1548-41aa-a01e-a4f5fa6433a5/image.png" alt=""></p>
<h1 id="해결--원인">해결 / 원인</h1>
<p><strong>File -&gt; Build Settings -&gt; Player Settings -&gt; Other Settings</strong> <img src="https://velog.velcdn.com/images/sharlotte_04/post/a00cc436-0f7c-4197-af94-ba31f613b715/image.png" alt="">
Color Space에서 <code>Linear ColorSpace는 WebGL2를 필요로 하므로 WebGL1를 제거하기 위해 Auth Graphics를 끄시오</code>라 경고한다.
에러의 원인은 <code>WebGL1로 인한 ColorSpace의 Linear 호환 충돌</code>이였던 것이다.</p>
<h2 id="webgl-20만을-쓰면-문제가-발생할까">WebGL 2.0만을 쓰면 문제가 발생할까?</h2>
<p><img src="https://velog.velcdn.com/images/sharlotte_04/post/bfe3e646-5c0a-4306-bc79-82723f2adba5/image.png" alt=""></p>
<p>애초에 유니티 기본 설정에서 <code>Auto Graphic API</code>를 통해 Deprecated된 WebGL 1.0까지 호환시킬려고 한 이유는 브라우저 호환의 우려때문이다.
Can I Use는 MDN에서 브라우저 호환성 표만 개별적으로 보여주는 통계 웹페이지이며 WebGL2의 호환성은 <a href="https://caniuse.com/webgl2">https://caniuse.com/webgl2</a> 에서 확인하다시피 일부 마이너 브라우저를 제외한 대부분의 메이저에서 지원한다. 옛날이면 몰라도 지금은 배포를 통해 제품이 입을 손상은 거의 없으며 사용자가 업데이트를 게을리 한 문제로 이관된다.</p>
<h2 id="side-note-auto-graphic-api">side note: auto graphic api</h2>
<p>WebGL 2는 WebGL 1를 <em>완벽히</em> 역호환한다. 즉 WebGL 1기능은 모두 다 WebGL 2에 있다. <code>auto graphic api</code>를 활성화하면 호환성을 위해 WebGL 1만 하는 셈이며, 이로 인해 Color Space의 <code>Linear</code>나 Lightmap Encoding에서 <code>High Quality</code>를 사용할 수 없는 문제가 있다.
그래서 이 문제는 두 속성을 WebGL 1호환 버전으로 내림으로써 해결할 수도 있지만, deprecated된 버전까지 호환한다며 제품성을 낮추는 짓은 정말 좋지 않은 선택이다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[타입 스코프와 src/@types의 오해]]></title>
            <link>https://velog.io/@sharlotte_04/%ED%83%80%EC%9E%85-%EC%8A%A4%EC%BD%94%ED%94%84%EC%99%80-srctypes%EC%9D%98-%EC%98%A4%ED%95%B4</link>
            <guid>https://velog.io/@sharlotte_04/%ED%83%80%EC%9E%85-%EC%8A%A4%EC%BD%94%ED%94%84%EC%99%80-srctypes%EC%9D%98-%EC%98%A4%ED%95%B4</guid>
            <pubDate>Sun, 22 Jan 2023 21:22:01 GMT</pubDate>
            <description><![CDATA[<h1 id="types">@types</h1>
<p>@types는 <code>@types/&lt;module&gt;</code>와 같이 서드파티 모듈의 타입 명세를 모아둔 타입 모듈에서 자주 볼 수 있지만 일부 개발자들이 이걸 개발 환경에서 같은 이름으로 쓰는 모습이 보였다.</p>
<h2 id="왜-쓰는가">왜 쓰는가?</h2>
<p>@types 디렉토리는 <del>무려 material icons에서 아이콘까지 줄 정도로</del> 꽤나 유명한 방식이고 패턴이다. 하지만 물어본 결과 대부분 개발자들이 아무 생각없이, 즉 자세한 원리나 이유 없이 그렇게 쓰고 있었다. 왜냐하면 <strong>진짜로 원리가 없으니깐!</strong></p>
<p>하지만 여기선 @types라는 이름의 디렉토리에 대한 타입스크립트의 특별한 처리가 아니라 <em>왜 타입을 모아두는가</em>, <em>왜 .d.ts 파일을 사용하는가</em>에 대한 이유를 나열할텐데, 대표적으로 두 방식과</p>
<ul>
<li><strong>전역 스코프 타입 명시</strong> - @types/module가 module의 타입 명세를 모아둔 것처럼, 로컬의 @types는 로컬의 타입 명세를 모아둔 것이다. </li>
<li><strong>타입 모듈 모음</strong> - 내가 이전까지 쓰던 방식이였는데, @types를 하나의 모듈로 보고 타입을 모두 export하여 사용처에서 import type 문으로 가져오던 방식이다.</li>
</ul>
<p>공통적으로 아래와 같은 장점이 있다.</p>
<ul>
<li><strong>일관된 타입</strong> - 선언된 타입이 여러곳에서 쓰이는건 불필요한 import / export를 초례하며 장래에 코드의 비대화와 관심사 분산을 일으킬지도 모른다. 타입 선언 파일에 모아두면 관심사 분산이 줄어들고, 전역 스코프에 선언하면 import / export 문들이 사라지니 코드 비대화도 예방된다. </li>
<li><strong>관심사 분리</strong> - 타입스크립트 개발자에게 중요한건 개발할 때 타입 명시되는 것이지 타입 또한 개발하는 것이 아니다 <del>물론 반대로 타입을 개발하는 사람들이 있긴 하지만</del>. 때문에 스크립트를 짤 때 거대한 타입 선언이 시야에 들어오는건 불필요하며 신경쓰인다. 즉, 관심사가 분산되므로 이럴 땐 타입 선언 파일에 분리해둘 필요가 있는 것이다.</li>
<li><strong>쉬운 유지보수와 관리</strong> - 일관된 타입을 지키면 얻을 수 있는 부수이득이자 정말 중요한 장점이다. 타입 선언이 어느정도 알차면, 가령 API 명세에서 에러 형식이 달라지더라도 사전에 <code>ErrorResponse</code>와 같은 타입으로 일관되게 사용해왔더라면 해당 타입만 편집하면 된다. 이것이 왜 타입 선언 파일의 장점이냐면 바로 <em>분산된 사용처가 한 곳으로 모여들었기 때문</em>이다. 가끔 이 타입이 있는지도 모르고 다시 선언하는 경우가 있는데, 이런 일을 막아준다.</li>
</ul>
<p>장황하게 늘여놓았지만 결국 다들 납득하는 <em>당연한</em> 부분이고 이러라고 @types를 쓰는 것이다. 패턴의 장점은 확실하다. 그럼 문제는 무엇인가?</p>
<h3 id="전역-타입-명시">전역 타입 명시</h3>
<p>앞서 말한 <em>타입 모듈 모음</em> 방식을 잘못되게 사용하는 경우가 있는데, 우선 아래 사진을 보라.
<img src="https://velog.velcdn.com/images/sharlotte_04/post/f2669d08-bbf6-4d72-8324-bad7b7931084/image.png" alt="">
<img src="https://velog.velcdn.com/images/sharlotte_04/post/2f4fc082-777e-4c31-a7cb-d1c06b8edfd0/image.png" alt=""></p>
<blockquote>
<p>스크립트에서 export문을 사용하지 않으면 최상위 스코프 위치는 <strong>스크립트 모듈 스코프 -&gt; 전역 스코프</strong>로 이전된다. 그러므로:</p>
<ul>
<li>export문이 없는 스크립트에선 사용되지 않은 const문이 비활성 처리되지 않는다. 즉 <strong>어딘가에서 사용되고 있다.</strong> (전역)</li>
<li>다른 스크립트에서 export문이 없는 스크립트에서 선언한 변수를 참조할 수 있다. 즉 <strong>같은 스코프다.</strong></li>
<li><code>.d.ts</code> 또한 export문을 배제하면 <strong>전역 스코프</strong> 이므로 자연스레 타입들은 <strong>전역 declare</strong>가 된다.
+ 그래서 전역 스코프에서 타입 명시를 할 때 declare하는건 불필요하다.<img src="https://velog.velcdn.com/images/sharlotte_04/post/18f43120-48b4-45b0-8eda-7d6045617edc/image.png" alt=""></li>
</ul>
</blockquote>
<p>아마 자바스크립트를 사골까지 우려먹었더라면 첫번째와 두번째, 즉 모듈과 스코프의 관계는 알고 있을 것이다. 나 또한 <del>잠시 잊었으나</del> 알고 있었다. 하지만 중요한건 이것이 <strong>타입과 상호작용한 결과</strong>, 즉 <strong>전역 타입 명시</strong>다.</p>
<h3 id="잘못된-사용법">잘못된 사용법</h3>
<p><img src="https://velog.velcdn.com/images/sharlotte_04/post/27f60f92-0712-420f-8f6d-2aa6f9cd9b6a/image.png" alt=""><img src="https://velog.velcdn.com/images/sharlotte_04/post/e0a034b7-fc0e-436a-874d-10a8f3ad82b6/image.png" alt=""></p>
<p>과거의 난 위와 같은 방식으로 직접 타입을 export하여 import type하는 방식을 사용했으나, export/import를 하지 않음으로써 모듈의 최상위 스코프를 전역 스코프로 끌어올린다면 굳이 import하지 않더라도 우리가 <code>Number</code>를 import 없이 쓰는 것마냥 사용할 수 있다.
즉, <strong>불필요한 타입 모듈화는 코드를 더럽게 만든다</strong>.</p>
<p>전역 스코프의 타입 명시는 머릿 속에서 대혁명처럼 느껴졌다. 하지만 이 전역 명시도 필요할 때 적절하게 써야 하니, 그 오용의 예를 들자면...</p>
<h3 id="전역-스코프의-declare와-모듈-스코프의-declare는-다르다">전역 스코프의 declare와 모듈 스코프의 declare는 다르다</h3>
<p><img src="https://velog.velcdn.com/images/sharlotte_04/post/886140a4-635a-4e91-94b6-5719ab3198d9/image.png" alt="">
이전 글에서 가끔 출연하던 <code>declare module</code>문은 위와 같이 모듈의 인터페이스를 declare함으로써 <strong>타입 보강</strong>을 한다.
<img src="https://velog.velcdn.com/images/sharlotte_04/post/b4438f94-a544-4746-93a0-1604eb583216/image.png" alt="">
따라서 <code>declare module</code>문에 있던 field2 속성은 ./ov1 모듈의 style 인터페이스 타입에 <em>보강</em> 되었다.
이것이 일반적인 모듈 스코프에서 <code>declare module</code>문의 사용처인데, 문제는 이게 전역 스코프에선 <strong>타입 보강</strong>이 아니라 <strong>타입 선언</strong>이 된다는 것이다.</p>
<p>예를 들자면
<img src="https://velog.velcdn.com/images/sharlotte_04/post/c8c47f91-29a4-4598-9b3d-e9155ab95eec/image.png" alt="">NextAuth는 이와 같이 모듈 스코프에서 선언된 상태였는데
<img src="https://velog.velcdn.com/images/sharlotte_04/post/0506a9b9-7aef-43ee-9d9c-29ce5f632727/image.png" alt="">
전역 스코프에서 해당 모듈을 통째로 재선언하는 바람에 진짜 <code>next-auth</code>에 있던 NextAuth 함수는 씹혀버렸다.</p>
<p>타입 체커의 동작 알고리즘을 정확히 알 수가 없지만 확실한건 <strong>전역 스코프에서 declare module를 하면 해당 모듈 타입이 통째로 갈아치워진다</strong>라는 것이다.</p>
<p>처음에 원하던대로 타입 보충, 즉 기존 타입에 덧씌우기/주입하기만 하고 싶다면 선언할 모듈 레벨을 전역에서 블록으로 내리면 된다.
<img src="https://velog.velcdn.com/images/sharlotte_04/post/3e3a5e82-6693-463f-b2ff-272ce5691299/image.png" alt="">
난 <code>override.d.ts</code>라는 타입 보강용 타입 모듈을 따로 만들었다.
+ 특정 모듈에 국한되는 사정이 아니라 타입 보강이 쓰이는 어디든지 해당된다. 가령 <code>process.env</code> 라든지...</p>
<h2 id="오해">오해</h2>
<h3 id="typeroots에-둬야-한다">typeRoots에 둬야 한다?</h3>
<p>도대체 어디서 시작된건지 모를 고정관념과 오해인데 <code>tsconfig</code>의 <code>complierOptions</code>에 있던 <code>typeRoots</code>를 비롯한 <code>types</code> 속성은 <code>node_modules/@types</code>에 있는 타입 모듈들 중 전역 스코프에 포함될 모듈들을 특정하는 배열 속성이다. 타입 모듈들은 기본적으로 포함되어 있는데 이 이상한 오해로 인해 <code>@types/모듈</code>을 설치해도 가끔 제 일을 못하던 것이였다.
<img src="https://velog.velcdn.com/images/sharlotte_04/post/8a37ef40-0be6-4c10-9cc6-85ce598cdc82/image.png" alt="">
예전에 <code>@types/node</code>를 설치하고 <code>node_modules</code>에 있는걸 확인하고도 타입이 없다고 말하니 너무 억울해서 파해쳐도 힘들어서 그냥 넘어갔던 적이 잇었는데 이제 와서 돌아보니 이런 원인이 있었던 것을 깨달았다.
역시 고정관념이 가장 무섭다..</p>
<p>참고로 두 속성의 차이는 공식 문서에도 있지만 하위 모듈들도 전역에 포함시키냐 아니냐다.</p>
<p>결론적으로 <strong>개발 환경의 @types와는 관련이 전혀없다</strong>.</p>
<h3 id="경로에-따라-다르다">경로에 따라 다르다?</h3>
<p>@types는 깃허브의 README나 prettier의 .prettierrc처럼 typescript의 .tsconfig같은 존재가 아니다. 그냥 말 그대로 types가 있는 디렉토리일 뿐이다.
그러므로 사실이 아니다.</p>
<h2 id="side-note-module-path">side note: module path</h2>
<p>모듈 경로는 꽤나 특이하다. 
<code>asdf/index.d.ts</code> 와 <code>@asdf/index.d.ts</code>는 <code>asdf.ts</code>와 같게 취급된다. 
<img src="https://velog.velcdn.com/images/sharlotte_04/post/d04a52b5-1590-453a-84d5-f8a350a3b13c/image.png" alt="">
하지만 그렇다고 둘이 합쳐지는건 아니라 필요하면 import해야 하는 점은 여전하다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[visual studio code 개발 상태카드 만들기]]></title>
            <link>https://velog.io/@sharlotte_04/visual-studio-code-%EA%B0%9C%EB%B0%9C-%EC%83%81%ED%83%9C%EC%B9%B4%EB%93%9C-%EB%A7%8C%EB%93%A4%EA%B8%B0</link>
            <guid>https://velog.io/@sharlotte_04/visual-studio-code-%EA%B0%9C%EB%B0%9C-%EC%83%81%ED%83%9C%EC%B9%B4%EB%93%9C-%EB%A7%8C%EB%93%A4%EA%B8%B0</guid>
            <pubDate>Sat, 21 Jan 2023 13:01:49 GMT</pubDate>
            <description><![CDATA[<h1 id="기획">기획</h1>
<p>Spotify 다음으로 만들 상태 콘텐츠는 바로 visual studio code 상태 카드, 정확힌 <a href="https://marketplace.visualstudio.com/items?itemName=icrawl.discord-vscode">Discord presence</a>의 coding 프로필과 같이 <strong>현재 개발 상태</strong>를 보여주는 것이 목적이다.
단순히 작업 경로, 커서 위치, 레포지토리 uri만을 얻으면 된다.</p>
<h1 id="구현">구현</h1>
<h2 id="문제-이런-서비스는-존재하지-않는다">문제: 이런 서비스는 존재하지 않는다</h2>
<p>Discord rich presence 확장은 <em>직접</em> 데이터를 수집하여 연동한 디스코드 계정으로 전달한다. 처음엔 vscode RESTful API라도 있지 않을까 싶었지만 구조적으로 vscode 유저의 특정성이 확립되지 않아 표준 API일리가 없음을 깨달았다. </p>
<p>결국 이 문제를 해결할려면 <em>직접</em> vscode 확장을 만들어야 한다. * *긴 한숨...* *</p>
<h2 id="첫-vscode-확장">첫 vscode 확장</h2>
<p>visual studio code의 확장 프로그램을 만드는 일은 생각보다 매우 간단했다.
<a href="https://code.visualstudio.com/api/get-started/your-first-extension">https://code.visualstudio.com/api/get-started/your-first-extension</a>
알찬 가이드라인 뿐만이 아니라 <a href="https://yeoman.io/">생성</a> <a href="https://www.npmjs.com/package/generator-code">툴킷</a>까지 모두 제공해주고 있다! 개발자는 그저 몇가지 입력만 해주면 될 뿐이다. 확장계의 CRA를 만난 느낌이다.</p>
<h3 id="에디터-실행-즉시-activate되기">에디터 실행 즉시 activate되기</h3>
<p>처음 프로젝트가 생성되면 package.json의 <code>activation</code>에는 오직 명령어 이벤트만 있어서 꼭 명령어를 실행해야만 activate되는 번거로움이 있었다.</p>
<p><a href="https://code.visualstudio.com/api/references/activation-events#onStartupFinished"><code>onStartupFinished</code></a> 이벤트는 vscode 시작이 모두 끝나면 activate되는 이벤트로써 에디터가 실행될 때 확장이 활성화되어야 하는 지금에 적절한 이벤트다.</p>
<h3 id="데이터-수집">데이터 수집</h3>
<p>vscode 확장은 통상적으로 vscode 모듈에서 에디터의 모든 것을 제어하고 관찰할 수 있다.
<code>vscode.window.onDidChangeTextEditorSelection</code> 이벤트 리스너 함수는 텍스트 편집기의 선택, 즉 커서 위치가 바뀔 때 호출된다. 커서 위치를 얻어야 하니 가장 적절한 리스너가 아닐 수가 없다.
이벤트는 <code>textEditor</code>, 커서가 있는 에디터와 <code>selections</code>, 선택값 데이터와 <code>kind</code>, 선택 종류로 총 3가지 데이터를 지닌다. 여기서 작업 경로와 커서 위치를 가공하여 얻었다.</p>
<pre><code class="language-ts">//editor: vscode.TextEditorSelectionChangeEvent
const body: VSCodeStatusData = {
  workspaceName:
  `${vscode.workspace.name}/${vscode.workspace.asRelativePath(
    editor.textEditor.document.fileName
  )}`,
  position: editor.selections.map((selection) =&gt; ({
    start: {
      char: selection.start.character,
      line: selection.start.line,
    },
    end: {
      char: selection.end.character,
      line: selection.end.line,
    },
  })),
  githubUrl: getGithubUrl(),
};</code></pre>
<p><code>asRelativePath</code>는 주어진 절대 경로를 에디터의 루트 경로의 상대적인 경로로 변환해준다. <code>/src/index.tsx</code>와 같이 되는데, 이 앞에 프로젝트 이름을 넣으면 <code>Sharjects/src/index.tsx</code> 작업 경로가 완성된다.</p>
<p>깃허브 레포지토리 링크를 가져오는건 생각보다 번거로웠는데, <code>vscode.extension.getExtension(&lt;string&gt;);</code> 함수는 타 확장을 그대로 가져올 수 있었으나 그것이 내장된 확장임에도 불구하고 <a href="https://github.com/microsoft/vscode/blob/main/extensions/git/src/api/git.d.ts">타입이 명시</a>되지 않았기 때문이다. 그래서 직접 명시 파일을 라이선스와 같이 프로젝트에 가져와서 쓰고 있다.</p>
<p><code>git.getAPI(1).repositories[0].state.remotes[0].fetchUrl</code>
몇가지 시도로 메인 브렌치와 연결된 원격의 fetch url를 얻는 데 성공했다.</p>
<h3 id="데이터-송신">데이터 송신</h3>
<p>지금껏 네트워크 통신에선 <code>fetch</code>를 사용했지만 이번엔 웹 표준 API가 없는 완전히 독립된 Node.js 환경에서 네트워크 통신을 해야 하므로 <code>axios</code>를 대신 사용했다.</p>
<pre><code class="language-ts">axios.post(
    &quot;https://sharjects-sharlottes.vercel.app/api/vscode/presence&quot;,
    body
  );</code></pre>
<p>사전에 정의해둔 body를 백엔드 엔드포인트로 POST한 다음 포트폴리오 프로젝트로 돌아가 엔드포인트 헨들러를 작성하면 수신도 끝이다. 아주 잠깐 맛봤는데도 <code>fetch</code>를 두고 <code>axios</code>를 쓰는 사람들의 기분이 공감됐었다.</p>
<h4 id="이벤트-리스너-씹기">이벤트 리스너 씹기(?)</h4>
<p>위 코드에선 절대 놓쳐선 안되는 치명적인 결함이 있었다. 바로 이벤트 리스너를 받을 때마다 무조건적인 수집 및 수신을 하는 것이다. 어차피 나 혼자 쓸 것이지만 커서가 바뀌는 빈도는 상상을 초월해서 예기치 못한 리소스 낭비가 될지도 모른다.
더 높은 안정성을 위해 일정 주기 내에 중복 호출되는 리스너는 무시될 필요가 있다.
이를 위해서 전에 만들어둔 <code>throttle</code> 함수를 사용했다.</p>
<pre><code class="language-ts">type throttleType = &lt;PT extends Array&lt;any&gt;, RT = void&gt;(
  callback: (...params: PT) =&gt; RT,
  duration?: number
) =&gt; (...params: PT) =&gt; RT | undefined;

export const throttle: throttleType = (callback, duration = 100) =&gt; {
  let id: NodeJS.Timeout | undefined;

  return (...params) =&gt; {
    if (id) return;
    id = setTimeout(() =&gt; (id = undefined), duration);
    return callback(...params);
  };
};</code></pre>
<p>함수가 실행되면 어떤 값으로 입구를 막고 <code>duration</code>밀리초 후에 값을 비워두는 원리다.</p>
<h3 id="확장-페키징">확장 페키징</h3>
<p>vscode 확장을 불러오는 방법은 VSIX 압축파일로 로컬에서 가져오는 방법과 마켓플레이스로 클라우드에서 가져오는 방법이 있다. 이 확장은 오직 나 혼자만 쓸 계획이며 별도의 보안 처리를 해두지 않아 마켓플레이스에 퍼블리싱하는건 위험하다고 생각해서 페키징만 했다.</p>
<p>페키징 방법도 매우 쉽다. 확장을 툴킷으로 시작했으니 툴킷으로 끝낸다, <a href="https://github.com/microsoft/vscode-vsce">vsce</a>는 아주 쉽고 간단하게 확장을 페키징하여 VSIX 파일을 생성한다.</p>
<pre><code class="language-bash">npm install -g @vscode/vsce
vsce package</code></pre>
<p>로 빠르게 페키징할 수 있는데 <code>package.json</code>에 <code>repository</code>와 <code>license</code> 키가 없으면 경고하니 참고바란다. 아마 퍼블리싱 때 써먹는 것 같은게, 무시하고 계속 진행할 수도 있다.</p>
<h3 id="확장-임포팅">확장 임포팅</h3>
<p><img src="https://velog.velcdn.com/images/sharlotte_04/post/a4791b72-88d0-4d97-80a0-5c81216a3ea4/image.png" alt=""> vscode는 명령 팔레트에서 vsix 가져오기를 이미 지원해주고 있어서 명령어 실행 후 파일 선택만 하면 알아서 설치된다.</p>
<h2 id="다시-포트폴리오로">다시 포트폴리오로</h2>
<h3 id="데이터-수신">데이터 수신</h3>
<pre><code class="language-ts">import type { NextApiRequest, NextApiResponse } from &quot;next&quot;;
import type { VSCodeStatusData } from &quot;src/@types&quot;;

let latestRecord: VSCodeStatusData | undefined;

export default (req: NextApiRequest, res: NextApiResponse) =&gt; {
  if (req.method === &quot;POST&quot;) {
    latestRecord = req.body;
  } else {
    res.status(200).json({ item: latestRecord });
  }
};</code></pre>
<p>POST로 들어오면 케시를 업데이트하고, GET로 들어오면 케시를 보내주는 간단한 api 헨들러 함수다.
더 높은 안정성을 위해 DB에 <code>laetestRecord</code>를 저장할 필요가 있겠지만 배포 사이트는 무중단 배포로 이뤄지고 있고, 그리 중요한 데이터가 아니라 판단해 케싱으로 마무리했다.</p>
<h3 id="데이터-요청">데이터 요청</h3>
<pre><code class="language-tsx">const VscodeStatus: React.FC = () =&gt; {
  const [data, setData] = React.useState&lt;VSCodeStatusData&gt;();
  React.useEffect(() =&gt; {
    fetch(&quot;/api/vscode/presence&quot;)
      .then&lt;{
        item: VSCodeStatusData | undefined;
      }&gt;((data) =&gt; data.json())
      .then((res) =&gt; setData(res.item));
  }, []);

  return &lt;&gt;{/* TODO: data로 이리저리 가공하기 */}&lt;/&gt;;
}</code></pre>
<p>앞서 만든 엔드포인트에 이번엔 POST가 아니라 GET로 요청하여 케싱된 데이터를 가져온다. 생각해보니 백엔드에서 케싱 데이터가 없으면 에러 코드를 던지고 프론트에서 <code>catch</code>로 헨들링하는게 더 적절한지는 고민이 필요할 것 같다.</p>
<h2 id="결과">결과</h2>
<p><img src="https://velog.velcdn.com/images/sharlotte_04/post/d2e7984c-9b69-4844-ac18-a43bb002c4a0/image.png" alt="">
점점 뭔가가 늘어나니 뿌듯하다. 심지어 그 늘어난게 허울 좋은게 아닌 내실 탄탄하고 묵직한 시련의 산물이라 생각하니 세삼 뿌듯하다. 다음엔 wakatime api와 github api로 코딩 시간/일일 활동 통계를 내어볼 생각인데 이번엔 순탄하게 지나갔으면 좋겠다. 카드 하나 대비 들어간 노력이 근례에 가장 엄청나서 너무 지친다...</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Spotify refresh token 자동화하기]]></title>
            <link>https://velog.io/@sharlotte_04/Spotify-refresh-token-%EC%9E%90%EB%8F%99%ED%99%94%ED%95%98%EA%B8%B0</link>
            <guid>https://velog.io/@sharlotte_04/Spotify-refresh-token-%EC%9E%90%EB%8F%99%ED%99%94%ED%95%98%EA%B8%B0</guid>
            <pubDate>Fri, 20 Jan 2023 15:31:38 GMT</pubDate>
            <description><![CDATA[<p>이전 글의 <a href="https://velog.io/@sharlotte_04/Spotify%EC%9D%98-%EB%82%B4-%ED%94%8C%EB%A0%88%EC%9D%B4%EB%A6%AC%EC%8A%A4%ED%8A%B8-%EB%B3%B4%EC%97%AC%EC%A3%BC%EA%B8%B0#%ED%8E%B8%EB%B2%95-refresh-token">제 4안</a>에서 <em>수동적인 방법</em> 으로 refresh token를 얻어 access token가 expired될 때마다 refresh하는 flow를 소개했었다.
하지만 이 <em>수동적인 방법</em>은 너무나도 번거로운 방법이였다.</p>
<h1 id="계획">계획</h1>
<ol>
<li><code>https://accounts.spotify.com/authorize</code>에 들어가 리다이렉트된 다음 authorization code를 얻는다</li>
<li>얻은 code로 <code>https://accounts.spotify.com/api/token</code>에 요청하여 refresh token를 얻는다</li>
<li>코드에 수동적으로 복사 붙여넣기하고 업데이트한다</li>
</ol>
<h2 id="구체화">구체화</h2>
<h3 id="직접-리다이렉트되기">직접 리다이렉트되기</h3>
<p><a href="https://apipheny.io/spotify-api-google-sheets/">https://apipheny.io/spotify-api-google-sheets/</a>
에서 Step 4 부분에선 <code>https://accounts.spotify.com/authorize</code>의 폼 형식을 알려준다.</p>
<blockquote>
<p>In your internet browser’s address/URL bar, copy and paste the following link:</p>
</blockquote>
<pre><code>https://accounts.spotify.com/authorize?
client_id=your_client_id&amp;
response_type=code&amp;
redirect_uri=https://apipheny.io/&amp;
scope=user-read-private%20user-read-email&amp;
state=34fFs29kd09</code></pre><blockquote>
<p>Make sure to replace the your_client_id parameter with your client id from Step 2.</p>
</blockquote>
<p>client id와 scope를 알맞게 맞추고, state는 선택적이니 생략했다. redirect_uri는 <code>http://localhost:3000/callback/spotify</code>로 잠시 맞춰두었다.
<img src="https://velog.velcdn.com/images/sharlotte_04/post/9c2cd3e3-7129-4e12-85c3-23bf2eb36350/image.png" alt="">
처음 해당 url로 들어가면 로그인 페이지 -&gt; auth 페이지 -&gt; 리다이렉트 uri 로 순차적인 리다이렉트를 거친다. 하지만 쿠키에 의해 한번 로그인되었고, 한번 auth 승인을 했더라면 이 수동적인 과정이 <strong>전부 생략</strong>된다.
즉, 한번 auth하고 나면 redirect uri로 알아서 리다이렉트된다.</p>
<h3 id="---자동으로-입장하기">--&gt; 자동으로 입장하기</h3>
<p>하지만 직접 url를 복사하고, 브라우저를 키고, 붙여넣고, 엔터를 누르고, 뭔가가 나오길 기다리는건 번거롭다. redirect uri초기값 설정은 생각 이상으로 최대한 간단해져야 한다. 힘겨운 spotify api flow 구현의 보상 차원에서.</p>
<p>이를 위해 처음엔 batch script를 차용했지만 구글링을 하면서 bash script 명령어가 더 쉽게 보여서 방향성을 .bat에서 .bash로 바꿨다.
bash script에선 무려 <strong>환경변수 파일에서 환경변수를 불러올 수 있다!</strong></p>
<pre><code class="language-bash">scriptDir=$(dirname -- &quot;$(readlink -f -- &quot;$BASH_SOURCE&quot;)&quot;)

source &quot;$scriptDir&quot;/../.env.local
start &quot;https://accounts.spotify.com/authorize?response_type=code&amp;client_id=$SPOTIFY_CLIENT_ID&amp;scope=user-read-playback-state&amp;redirect_uri=https://sharjects-sharlottes.vercel.app/callback/spotify&quot;</code></pre>
<p>scriptDir로 현재 디렉토리 경로를 저장하고 source 명령어로 .env.local 파일을 불러와서 start 명령어로 해당 url를 열게끔 한다. source 명령어 덕분에 환경변수는 깃허브에 올라오지 않는 .env.local에 안전하게 있으므로 bash script 파일에서도 은닉성을 지키며 사용할 수 있게 됐다.</p>
<h3 id="리다이렉트된-페이지-팝업화하기">리다이렉트된 페이지 팝업화하기</h3>
<p><code>callback/spotify</code> 페이지에서 얻을건 오직 url에 담긴 <code>code</code> 쿼리값이다. <code>next/router</code>의 <code>useRouter</code>를 통해 파싱된 query를 얻어서 <code>code</code>가 있으면 클립보드에 자동 복사를 시도한다. 클립보드 복사가 성공하면 창을 닫는다.</p>
<pre><code class="language-tsx">import React from &quot;react&quot;;
import CSR from &quot;src/components/CSR&quot;;
import { useRouter } from &quot;next/router&quot;;
import { copy } from &quot;src/utils/copy&quot;;

const SpotifyCallbackPage = () =&gt; {
  const { query } = useRouter();

  React.useEffect(() =&gt; {
    if (!query[&quot;code&quot;]) return;
    copy(query[&quot;code&quot;].toString())
      .then(() =&gt; window.close())
      .catch(console.log);
  }, [query[&quot;code&quot;]]);

  return &lt;&gt;{query[&quot;code&quot;]}&lt;/&gt;;
};
export default () =&gt; (
  &lt;CSR&gt;
    &lt;SpotifyCallbackPage /&gt;
  &lt;/CSR&gt;
);</code></pre>
<p>단순히 복사를 하는 최신 방법은 <code>navigator.clipboard.write</code>이나 문제는 <strong>권한이 없거나 브라우저 호환이 안될 때</strong> 발생한다. 이번 테스트에선 사전에 복사 권한을 주지 않아 단순히 <code>clipboard.write</code>로는 작동하지 않았다.
<a href="https://stackoverflow.com/questions/69438702/why-does-navigator-clipboard-writetext-not-copy-text-to-clipboard-if-it-is-pro/74528564#74528564">관련 Stack overflow 답변</a>이 클립보드 복사가 안되는 다양한 가능성 및 원인을 나열하고 이들을 모두 아우르는 유틸 함수를 첨부해서 큰 도움이 되었다.</p>
<blockquote>
<p><strong>Typescript의 `navigator.permissions.query 파라미터 타입 문제</strong>
타입스크립트로 위 답변에 첨부된 <code>copy</code> 함수를 사용한다면 
<code>&#39;&quot;clipboard-write&quot;&#39; 형식은 &#39;PermissionName&#39; 형식에 할당할 수 없습니다.</code>
와 같은 타입 에러를 맞이할 것이다. 이는 <a href="https://developer.mozilla.org/en-US/docs/Web/API/Permissions_API#browser_compatibility">MDN 호환성 표</a>에서 <code>clipboard-write</code>가 파이어폭스와 사파리에서 호환되지 않기 때문이다.
<img src="https://velog.velcdn.com/images/sharlotte_04/post/7f052553-35bd-4b0f-b8ca-364f6581ffbf/image.png" alt="">단순히 as로 타입을 뭉개어 해결했다. 파이어폭스와 사파리에선 당초에 보안상 이유때문인지 불가능하고, 이건 오직 나만 쓸 것이며 난 크로미움 브라우저를 사용하기 때문이다.`</p>
</blockquote>
<p>useEffect에서 document가 무조건 있어야 하므로 이 페이지는 무조건 client side에서 렌더되어야 한다. Next13에선 렌더 방식을 명시화할 수 있지만 이 프로젝트는 Next12이므로 <a href="https://velog.io/@sharlotte_04/no-ssr-component">사전에 만들어둔 CSR 컴포넌트</a>를 통해 CSR화했다.</p>
<p>이제 <code>https://accounts.spotify.com/authorize</code> 페이지에 가기만 하면 알아서 authorzation code가 클립보드에 들어가있게 된다!</p>
<h3 id="토큰-얻기">토큰 얻기!</h3>
<p>자고로 윈도우에서 http 요청을 할 때 가장 원시적이고도 단순한 방법이 있으니, 바로 CURL이다.</p>
<pre><code class="language-bash">response=$(curl -X POST -d grant_type=authorization_code -d code=$code -d client_id=$SPOTIFY_CLIENT_ID -d client_secret=$SPOTIFY_CLIENT_SECRET -d redirect_uri=https://sharjects-sharlottes.vercel.app/callback/spotify https://accounts.spotify.com/api/token)
if [[ &quot;$response&quot; =~ \&quot;refresh_token\&quot;:\&quot;([0-9A-Za-z_-]*)\&quot;, ]]; then
  echo &quot;new refresh token granted!&quot;
  echo &quot;${BASH_REMATCH[1]}&quot;
fi</code></pre>
<p>bash는 여기에 한술 더 떠서 결과값을 쉽게 문자열화하여 변수에 할당할 수 있다.
<code>string =~ regex</code>는 정규표현식 테스트 연산자로써 매칭여부가 결과값이다. 매칭이 된다면 <code>BASH_REMATCH</code>로 매칭된 값들을 얻을 수 있는데, group expression으로 토큰 부분만 그룹으로 감싸서 따냈다. <code>=~</code> 연산자와 if문에 오기까지 sed, grep, exer 등 여러 명령어들과 혼선을 겪어서 토큰 표현식이 길어졌는데 이제보니 <code>.*</code>로 간략화해도 될 것 같다.</p>
<h2 id="결과">결과</h2>
<p><img src="https://velog.velcdn.com/images/sharlotte_04/post/04bf2097-af8f-466f-8586-6d168f08f1f0/image.gif" alt="">
스크립트를 입력하고, ctrl+v로 자동복사된 코드를 붙여넣고, 출력된 refresh token를 사용하면 된다.
내심 완전한 자동화를 원했지만, 이정도로 부분적인 자동화를 구현하는 것으로도 만족한다. <img src="https://velog.velcdn.com/images/sharlotte_04/post/edf9de6a-f1fa-4c60-9b81-86e67a0d7f42/image.png" alt="">
스크립트 언어가 조건문과 변수 할당까지 자유로이 할 정도로 큰 세상인걸 깨달았고, CLI 구현이 마냥 먼 산이 아님을 느꼈다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Spotify의 내 플레이리스트 보여주기]]></title>
            <link>https://velog.io/@sharlotte_04/Spotify%EC%9D%98-%EB%82%B4-%ED%94%8C%EB%A0%88%EC%9D%B4%EB%A6%AC%EC%8A%A4%ED%8A%B8-%EB%B3%B4%EC%97%AC%EC%A3%BC%EA%B8%B0</link>
            <guid>https://velog.io/@sharlotte_04/Spotify%EC%9D%98-%EB%82%B4-%ED%94%8C%EB%A0%88%EC%9D%B4%EB%A6%AC%EC%8A%A4%ED%8A%B8-%EB%B3%B4%EC%97%AC%EC%A3%BC%EA%B8%B0</guid>
            <pubDate>Thu, 19 Jan 2023 20:44:13 GMT</pubDate>
            <description><![CDATA[<h1 id="기획">기획</h1>
<p>메인 페이지를 리메이크하면서 타이틀, 콘텐츠 아래에 페이지의 끝을 장식할 잡잘한 정보들이 필요했다. 다른 유즈케이스라면 SNS와 같은 여러 링크들을 첨부했겠지만 이들은 이미 사이드바에 있고 푸터에 또한 넣어질 예정이라 중복된 정보이기에 부적절했다. 그러다 문득 디스코드의 프로필이 떠올랐고, 오늘 하루의 실시간/정적 통계를 다양한 api로 보여주는게 어떨까? 라는 생각이 들었다.</p>
<h2 id="spotify-webapi">Spotify webAPI</h2>
<p>Spotify는 풍부한 web API를 가지고 있고, 짧은 검색으로 Spotify가 유저의 <a href="https://developer.spotify.com/documentation/web-api/reference/#/operations/get-information-about-the-users-current-playback">현재 재생중인 플레이리스트를 API로 제공</a>해주고 있는 것을 확인했다.</p>
<p>플레이리스트 데이터를 받아와 카드 컴포넌트에 렌더링시키면 되는 <em>아주 간단한</em> 작업이다.</p>
<h1 id="구현">구현</h1>
<p>webAPI는 대체로 Authorization 헤더로 요청을 제한한다. Spotify webAPI 또한 이와 같아서 OAuth를 통해 얻은 access_token를 헤더에 넣어 요청을 보내야 한다.</p>
<blockquote>
<p>Authorization refers to the process of granting a user or application access permissions to Spotify data and features. Spotify implements the OAuth 2.0 authorization framework
<a href="https://developer.spotify.com/documentation/general/guides/authorization/">https://developer.spotify.com/documentation/general/guides/authorization/</a></p>
</blockquote>
<p>spotify는 OAuth에 세가지 방향성을 제공해준다. </p>
<ul>
<li>첫번째는 유저의 <a href="https://developer.spotify.com/documentation/general/guides/authorization/code-flow/">직접적인 OAuth승인</a>, 즉 계정 연동이다. </li>
<li>두번째는 유저의 <a href="https://developer.spotify.com/documentation/general/guides/authorization/implicit-grant/">암묵적인 OAuth승인</a>, secret 키를 사용하지 않아 클라이언트에서도 완전히 작동이 가능하다. </li>
<li>세번째는 서버의 <a href="https://developer.spotify.com/documentation/general/guides/authorization/client-credentials/">OAuth승인</a>, 유저 특정성을 지니지 않아 유저 데이터와 연관된 API는 사용할 수 없다.</li>
</ul>
<p>유저의 데이터는 필요가 없다. 서버 주인의 데이터만 얻으면 되니 세번째 방법을 선택했다.</p>
<h2 id="하지만">하지만...</h2>
<p>당초에 Credential OAuth flow를 구현하고자 한 이유는 비슷한 사례의 Github PersonalAccessToken를 통한 Github API 요청 사례가 기억났기 때문이다. <em>어플리케이션 주인은 당연히 어플리케이션 key를 통한 요청으로 이미 Auth된 상태다.</em> 라는 논거 하에 선택한 이 방법은 안타깝게도,</p>
<blockquote>
<p>유저 특정성을 지니지 않아 유저 데이터와 연관된 API는 사용할 수 없다.</p>
</blockquote>
<p>앞서 언급한 &quot;유저 특정성을 지니지 않는다&quot;의 유저엔 어플리케이션 주인 또한 포함되어 있는 것이였다. 즉, 자신의 데이터를 얻기 위해 자신이 만든 서버로 자신을 증명해야 하는 비직관적이나 당연한 일이 벌여진 것이다.</p>
<p><img src="https://velog.velcdn.com/images/sharlotte_04/post/8d1b7473-a2f6-4776-8011-dd949cfd5be4/image.png" alt=""></p>
<p>이로써 희망차고 간단해보이던 1안은 처참하게 부정되었다.</p>
<h2 id="우회-discord-연동">우회: Discord 연동?</h2>
<p>처참하게 부정되는걸 인지하기까지, 부정을 납득하는건 꽤나 긴 시간이 걸렸다. 앞선 논거는 깊은 신뢰를 가지고 있었고 이것이 부정되는건 비이상적이고 불편하기 때문에 내심 부정되는게 거짓이길 빌었다. 하지만 결국 부정되었고 멘탈은 누더기가 되었다.</p>
<p><img src="https://velog.velcdn.com/images/sharlotte_04/post/b74fbd21-4fb9-4c23-908c-040569f698e1/image.png" alt=""></p>
<p>2안은 정신나간 머리로 구성한 flow다. 내 디스코드 계정은 Spotify와 연동되었기 때문에 Discord Application 또는 Discord bot를 통해 내 디스코드 계정과 연동하여 Spotify의 데이터를 중도탈취할 수 있지 않을까 싶었지만 안타깝게도 디스코드 팀이 내 비이성적인 논리만큼 멍청하진 않았다.</p>
<hr>
<p><strong>edit: 실제로 가능은 하다?</strong>
가상 브라우저, 가령 <a href="https://pptr.dev/">puppeteer</a>를 통해 디스코드 웹에서 계정 로그인을 한 다음 프로필의 spotify 상태를 크롤링할 수 있긴 하다.
<img src="https://velog.velcdn.com/images/sharlotte_04/post/cc287e70-c97b-466c-8ae5-e95d47db7008/image.png" alt="">
하지만 이건 API 속도에 치명적인 피해를 가할 것이며 서버 리소스 낭비를 초례할지도 모른다.</p>
<hr>
<h2 id="타협-oauth-구현">타협: OAuth 구현</h2>
<p><img src="https://velog.velcdn.com/images/sharlotte_04/post/b261e053-0b3e-47d0-92d1-e033bc16881a/image.png" alt="">
spotify가 제공한 첫번째 OAuth 방법을 클라이언트와의 상호작용<strong>없이</strong> 서버에서만 자동으로 실행하도록 한다면 이것은 단순히 거대한 access_token getter 함수와 다를 바 없다는 생각이 머리를 스쳤다.
하지만 여러번의 시행착오와 인지 및 테스트 오류와 같이 다양하고도 부정된 억까들을 이미 충분히 당해왔기에 이 이상을 모험하는건 굉장히 위험한 방법이였다. 그래서 약간의 휴식을 취하며 flow를 도식화하며 구현을 정리해봤다.
draw.io는 도식화 및 diagram 스케치에 자주 활용했었지만 이정도로 제대로된 network flowchart는 처음 그려봤는데 새로운 경험이였고 완성하고 나니 꽤나 뿌듯했다.</p>
<p>flow를 완성시키고 직접 구현에 나섰으나 금세 다시 벽에 부딪쳤다. OAuth 페이지로 리다이렉트하고 redirect uri로 다시 리다이렉트하면 해당 url에 담긴 <code>code</code> query 값을 가져와서 <code>/api/token</code> spotify webAPI에 요청하여 OAuth access token를 얻는게 최종 계획이였으나 <strong>OAuth 페이지가 로그인 페이지에 가로막힌다는 점을 간과했다.</strong></p>
<h2 id="편법-refresh-token">편법: refresh token</h2>
<p><img src="https://velog.velcdn.com/images/sharlotte_04/post/c058c2db-4530-42b0-a568-7e92c5e5f49e/image.png" alt="">
OAuth에서 /authorize 페이지와 관련된 부분들은 모두 <strong>초기값 설정</strong> 코드다. 만약 수동적인 방법으로 이 초기값을 미리 얻고 반복되는 부분, 즉 토큰 새로고침만 자동화하면 어떨까?
flow를 그려보니 생각 이상으로 크기가 단순해졌고 구현도 어렵지 않았다. 약간의 시행착오와 CORS와 같은 시련을 맞이했으나 꿋꿋이 시도해본 끝에 최종적으로 refresh url를 이용한 제 4안으로 기획을 구현하는 데 성공했다!</p>
<h1 id="side-note-spotify-embed">Side note: spotify embed</h1>
<p>spotify는 아주 고맙게도 <a href="https://developer.spotify.com/documentation/embeds/guides/creating-an-embed/">자체 embed</a> 엘리먼트를 지원해준다. 
하지만 리액트에선, 특히 지금 포트폴리오인 Next.js에선 SSR이 주된 상태이므로 바닐라 DOM과 매우 근접한 코드는 작동하지 않았고 여러번의 검색을 한 결과 이미 리액트로 래핑한 사람을 발견했다.</p>
<p><a href="https://www.npmjs.com/package/react-spotify-embed">https://www.npmjs.com/package/react-spotify-embed</a>
엘범 link url만 넣어주면 아래와 같이 이쁜 모습을 보여준다.
<img src="https://velog.velcdn.com/images/sharlotte_04/post/c62f6b90-8f18-42e5-a308-50750a739186/image.png" alt=""></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[React portal]]></title>
            <link>https://velog.io/@sharlotte_04/React-portal</link>
            <guid>https://velog.io/@sharlotte_04/React-portal</guid>
            <pubDate>Sun, 08 Jan 2023 20:06:14 GMT</pubDate>
            <description><![CDATA[<h1 id="portal">Portal</h1>
<p>리액트 포탈은 리액트 공식 문서의 고급 안내서에 기제된 API 중 하나다. </p>
<blockquote>
<p>Portal은 부모 컴포넌트의 <strong>DOM 계층 구조 바깥에 있는 DOM 노드로 자식을 렌더링</strong>하는 최고의 방법을 제공합니다.
<a href="https://ko.reactjs.org/docs/portals.html">https://ko.reactjs.org/docs/portals.html</a></p>
</blockquote>
<h2 id="왜-portal인가">왜 Portal인가</h2>
<p>처음 Dialog를 구현할 땐 root DOM 컨테이너 안에서 엘리먼트를 렌더링하려 했다. 여러 Dialog의 렌더링을 동시다발적으로 이루기 위해선 Dialog를 보여주고 숨겨주는 기능을 담당할 singleton Manager가 필요하다고 생각했고, 곧바로 이행했지만 좀처럼 만족스러운 코드와 결과가 나오지 못했다. libGDX처럼 리액트의 특수한 가상 돔 시스템이 없는 곳에선 괜찮은 구조였을진 몰라도 이 가상 돔의 환경에선 다르게 적용해야 하는 것이다.</p>
<p>Portal은 여러가지 강점을 지니는데, 일단</p>
<ul>
<li>그 성질 자체가 메인 돔 트리와의 여러가지 부작용을 사전에 방지 <ul>
<li>CSS의 상속 스타일링</li>
<li>가상 돔의 개발자가 예기치 못한 재랜더링</li>
</ul>
</li>
<li>매우 간편한 적용 방법</li>
<li>컴포넌트의 유연한 제어</li>
</ul>
<h3 id="그래서-manager는-portal보다-좋지-않다">그래서 Manager는 Portal보다 좋지 않다.</h3>
<p>이 Manager 구조가 잘못되고, 작동하지 않는다는 말이 아니다. 다만 Portal을 적용하면 <strong>Manager가 불필요</strong>하기 때문에 그 가독성과 양적인 면에서 Portal가 우위를 점한다는 말이다.</p>
<p>Manager는 Dialog 컴포넌트들을 자식들로부터 받아 일괄적으로 랜더링하는 구조를 가지고 있다. 이러한 Manager는 Portal의 장점과 완벽히 상반된 모습을 띄고 있다.</p>
<ul>
<li>Manager는 루트 돔 컨테이너에 속해있기 때문에 예기치 못한 부작용을 받을 수 있다.</li>
<li>Manager가 랜더링하게끔 하려면 고려해야 하는 요소가 최소 3가지로 정말 많다.</li>
<li>Manager가 모든 Dialog 컴포넌트를 저장하여 일괄적으로 랜더링하기 때문에 컴포넌트의 유연한 제어는 바랄 수 없다.</li>
</ul>
<p>마치 Portal는 이러한 Manager의 문제를 겪은 개발진들이 만들어낸 혜안과 같이 느껴졌다.</p>
<h2 id="어떻게-적용하는가">어떻게 적용하는가</h2>
<p>공식 문서에선 더 정확하고 확장적인 주제를 다루기 위해 CC를 통해 설명하지만 매우 단순하게 정리하자면 아래와 같다.</p>
<ol>
<li>루트, 즉 <code>index.html</code>에서 <strong><code>root</code> 엘리먼트와 같은 위치에 루트 엘리먼트를 만든다</strong>.
<img src="https://velog.velcdn.com/images/sharlotte_04/post/c05615b5-964c-4f20-817e-670f53154818/image.png" alt=""></li>
</ol>
<ol start="2">
<li>생성한 div 엘리먼트를 가져와서 <strong>새 div 엘리먼트를 만든 뒤 루트 엘리먼트에 추가한다</strong>. 
<img src="https://velog.velcdn.com/images/sharlotte_04/post/74cf65d5-8f05-4bc7-832d-db85905bdd00/image.png" alt=""></li>
</ol>
<ol start="3">
<li>컴포넌트의 랜더링 위치에서 <code>ReactDOM.createPortal(children, container)</code> api 함수로 포탈을 생성하여 반환한다.
첫번째 인자는 포탈의 자식, 두번째 인자는 포탈이다. <code>modal-root</code>는 포탈을 생성하기 위한 좌표인 셈이다. ReactDOM는 <code>react-dom</code>에서 default로 가져온다.
<img src="https://velog.velcdn.com/images/sharlotte_04/post/63f28df6-1745-431c-8aba-c941cdc06625/image.png" alt=""></li>
</ol>
<p>실제로 테스트해보면 컴포넌트를 랜더링할 때마다 <code>modal-root</code>의 자식 div가 갱신되는 모습을 볼 수 있다.</p>
<h2 id="여담">여담</h2>
<ul>
<li>사실 MUI의 자매품 컴포넌트인줄 알았는데 원산지가 리액트인 기술이였던 것에 놀라웠다. MUI를 못쓰니 Dialog Manager를 만들어야겠단 생각의 흐름으로 간건데 이리 쉬울 줄은 몰랐다.</li>
<li>기존 Dialog 컴포넌트에 덧씌우는 모습이라 HOC나 래퍼 컴포넌트로 분리해도 좋을 것 같다.</li>
<li>기존 Dialog 컴포넌트에 그대로 적용하기 때문에 새로운 파일이 필요 없다. Manager보다 더 나은 점이기도 하다.</li>
</ul>
]]></description>
        </item>
    </channel>
</rss>