<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>엘레베이터에 낀 남자</title>
    <link>https://nookpi.tistory.com/</link>
    <description>배움이란 즐거움의 흔적을 남기는 곳</description>
    <language>ko</language>
    <pubDate>Sat, 7 Mar 2026 02:33:56 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>눅눅한 피부</managingEditor>
    <image>
      <title>엘레베이터에 낀 남자</title>
      <url>https://tistory1.daumcdn.net/tistory/3178501/attach/640a5a1a67ea4a278d656e61bb397a61</url>
      <link>https://nookpi.tistory.com</link>
    </image>
    <item>
      <title>장인은 도구를 탓하지 않는다. 도구를 만든다.</title>
      <link>https://nookpi.tistory.com/217</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://jonghakseo.github.io/posts/craftsman-makes-tools/&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://jonghakseo.github.io/posts/craftsman-makes-tools/&lt;/a&gt;&lt;/p&gt;</description>
      <category>잡담</category>
      <author>눅눅한 피부</author>
      <guid isPermaLink="true">https://nookpi.tistory.com/217</guid>
      <comments>https://nookpi.tistory.com/217#entry217comment</comments>
      <pubDate>Thu, 5 Mar 2026 20:26:06 +0900</pubDate>
    </item>
    <item>
      <title>또 잔뜩 늦은 2025년 회고</title>
      <link>https://nookpi.tistory.com/216</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;오랜만에 글을 쓰려고 블로그에 들어오니 세 번째로 최신글이 '사내 AI 챗봇 서비스 구축하기'이고, 네 번째 최신글은 2024년 회고글이라니, 내가 한동안 글을 안 쓰긴 했구나 싶다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;요즘엔 글을 쓰고 싶은 주제들이 휙휙 스쳐지나가기만 하고, 실제로 쓸 시간을 갖지는 못하는 것 같다. 돌아보면 남는건 다 글인데...&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;거두절미하고,&amp;nbsp;또&amp;nbsp;잔뜩&amp;nbsp;늦었지만&amp;nbsp;2025년&amp;nbsp;회고를&amp;nbsp;해보려고&amp;nbsp;한다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;AI Platform 구축기&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;24년도 그랬지만 25년도 AI 이야기를 안 할 수가 없다. 25년은 단순히 AI 사용 뿐만 아니라, 조직 전체가 AI를 쓸 수 있게 플랫폼과 워크플로우를 설계하고 구축하는 해였다. &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;특히 25년 초에는 사내 AI Platform 구축에 많은 에너지를 쏟았다. 사실 AI Platform은 누가 시켜서 만들거나 회사에서 리소스를 받아서 구축한 서비스가 아니다. 내 퇴근 후의 시간과 주말을 갈아넣은 프로젝트였고 &lt;span style=&quot;letter-spacing: 0px;&quot;&gt;구축하면서 구성원들에게 AI 리터러시 함양, 구독비용 절감, 사내 데이터 가시성 확보 등 여러 성과가 있었다고 자평하지만, 사실 내가 얻은 게 제일 많았다.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;현재 AI Platform은 사내 모든 부서(기획, CX, 마케팅, 개발)가 모든 창구(Slack, Web) LLM을 활용할 수 있는 허브가 되었고, 자체 MCP 서버 제공으로 Jira, Google, Slack, Notion, Figma 등의 모든 업무 도구를 쉽게 제공하고 있다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2026-03-01 오후 5.53.52.png&quot; data-origin-width=&quot;1080&quot; data-origin-height=&quot;598&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/AMuaU/dJMb996y1KW/6ZIQzkuX2M4UuU63EWwVLk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/AMuaU/dJMb996y1KW/6ZIQzkuX2M4UuU63EWwVLk/img.png&quot; data-alt=&quot;젠데스크에 들어가는 AI 익스텐션을 만들었는데, 이후 CX팀은 채용 계획을 백지화 했다.&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/AMuaU/dJMb996y1KW/6ZIQzkuX2M4UuU63EWwVLk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FAMuaU%2FdJMb996y1KW%2F6ZIQzkuX2M4UuU63EWwVLk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;500&quot; height=&quot;277&quot; data-filename=&quot;스크린샷 2026-03-01 오후 5.53.52.png&quot; data-origin-width=&quot;1080&quot; data-origin-height=&quot;598&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;젠데스크에 들어가는 AI 익스텐션을 만들었는데, 이후 CX팀은 채용 계획을 백지화 했다.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;구현하면서&amp;nbsp;LLM과&amp;nbsp;관련된&amp;nbsp;애플리케이션을&amp;nbsp;만들&amp;nbsp;때의&amp;nbsp;어려움이나,&amp;nbsp;도구를&amp;nbsp;어떤&amp;nbsp;식으로&amp;nbsp;설계해야&amp;nbsp;하는지,&amp;nbsp;LLM의&amp;nbsp;비결정성을&amp;nbsp;어떻게&amp;nbsp;평가하고&amp;nbsp;검증해야&amp;nbsp;하는지,&amp;nbsp;그게&amp;nbsp;얼마나&amp;nbsp;어려운지&amp;nbsp;등등...&amp;nbsp;배운&amp;nbsp;경험이&amp;nbsp;정말정말&amp;nbsp;많다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;덕분에 5월(&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;Claude Code 1.0 출시 즈음)&lt;/span&gt;에는 자체적으로 구축한 ReasoningLoop를 사용한 DataAgent로 원하는 데이터를 높은 정확도로 가져올 수 있는 환경도 구축했다. DataAgent는 빅쿼리 도구를 들고 있는 AI Platform 내 일종의 서브에이전트다. 지금은 GitHub, DataAgent, BrowserAgent 등 여러 개의 특화 에이전트들이 플랫폼 위에서 잘 동작하고 있다. &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;브라우저 에이전트를 만들(깎을) 때의 고난의 시간도 새삼 생각이 난다. AI Platform은 기본적으로 AWS에서 돌아가고, 브라우저 에이전트는 헤드리스 크로미움을 쓰기 때문에 봇 탐지 등으로 실제 접근이 어려운 리소스들이 참 많았다. 우회하기 위한 여러 가지 해키한 작업들도 많이 했던 것 같다. 요즘엔 로컬 브라우저의 프로필을 그대로 써서 사실상 봇 탐지가 의미가 없어지는 추세이긴 하지만.. 그땐 그랬지.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;12월에는 이 플랫폼이 보안 공격을 당하는 사고도 있었다. RSC 취약점을 통해 크립토마이닝 코드가 심어졌고, 대응 이후 유출 가능성이 있는 환경변수들을 교체하는 작업을 했다. 내가 직접 만든 시스템이 뚫린다는 건 꽤 충격적인 경험이었고, 자책도 많이 했던 것 같다. 그래도 정신을 빠르게 부여잡고 보안패치는 빠르게 해야한다는 뼈저린 교훈을 새겼다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;개발팀 리드, AI 시대의 엔지니어?&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;나 스스로 AI Platform을 구축하면서 AI의 도움을 많이 받긴 했지만(프롬프트나 도구 가시성에 대한 제어, 루프 설계 등 &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;중요한 부분은 내가 하긴 했다&lt;/span&gt;) 이 경험이 나한테만 머무르면 안 되겠다는 생각을 했던 것 같다. 개발팀의 방향성에 대해서도 이야기를 많이 하고 의견도 많이 내게 되었다. 그러면서 자연스럽게 개발팀 리드를 해야겠다는 생각을 했다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start; font-family: 'Noto Serif KR';&quot;&gt;그&amp;nbsp;당시엔&amp;nbsp;비단&amp;nbsp;프론트엔드뿐만&amp;nbsp;아니라&amp;nbsp;우리&amp;nbsp;팀의&amp;nbsp;모든&amp;nbsp;엔지니어들이&amp;nbsp;어떤&amp;nbsp;방향을&amp;nbsp;향해&amp;nbsp;가야&amp;nbsp;하는지가&amp;nbsp;명확하다고&amp;nbsp;생각했다.&amp;nbsp;겪어본&amp;nbsp;바,&amp;nbsp;내년에는&amp;nbsp;개발자가&amp;nbsp;코드를&amp;nbsp;작성하지&amp;nbsp;않게&amp;nbsp;될&amp;nbsp;것이&amp;nbsp;너무나도&amp;nbsp;자명했고,&amp;nbsp;심지어는&amp;nbsp;거의&amp;nbsp;안&amp;nbsp;읽게&amp;nbsp;될&amp;nbsp;것이라고&amp;nbsp;생각했다.&amp;nbsp;엔지니어의&amp;nbsp;직무적&amp;nbsp;경쟁력이라는&amp;nbsp;것이&amp;nbsp;여러&amp;nbsp;가지가&amp;nbsp;있지만,&amp;nbsp;그중&amp;nbsp;큰&amp;nbsp;부분&amp;nbsp;중&amp;nbsp;하나가&amp;nbsp;요구사항을&amp;nbsp;코드로&amp;nbsp;옮기는&amp;nbsp;스킬이었다는&amp;nbsp;것은&amp;nbsp;부정할&amp;nbsp;수&amp;nbsp;없을&amp;nbsp;것이다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;나는&amp;nbsp;엔지니어들이&amp;nbsp;양극화될&amp;nbsp;것으로&amp;nbsp;생각했다.&amp;nbsp;코어&amp;nbsp;레벨에서&amp;nbsp;LLM이&amp;nbsp;알고 있는&amp;nbsp;기존의&amp;nbsp;패턴으로는&amp;nbsp;해결할&amp;nbsp;수&amp;nbsp;없는&amp;nbsp;문제들을&amp;nbsp;정말&amp;nbsp;고도의&amp;nbsp;집중력과&amp;nbsp;사고력으로&amp;nbsp;풀어내는&amp;nbsp;개발자들과,&amp;nbsp;프로덕트&amp;nbsp;관점의&amp;nbsp;엔지니어.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start; font-family: 'Noto Serif KR';&quot;&gt;&lt;a href=&quot;https://blog.pragmaticengineer.com/the-product-minded-engineer&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://blog.pragmaticengineer.com/the-product-minded-engineer/&lt;/a&gt;&lt;/span&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1772354383601&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;The Product-Minded Software Engineer&quot; data-og-description=&quot;Product-minded engineers are developers with lots of interest in the product itself. They want to understand why decisions are made, how people use the product, and love to be involved in making product decisions. They're someone who would likely make a go&quot; data-og-host=&quot;blog.pragmaticengineer.com&quot; data-og-source-url=&quot;https://blog.pragmaticengineer.com/the-product-minded-engineer&quot; data-og-url=&quot;https://blog.pragmaticengineer.com/the-product-minded-engineer/&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/pvHu2/dJMb81fP28a/MsQvJHYfk5jCLfZ1a8hIk0/img.jpg?width=1200&amp;amp;height=797&amp;amp;face=0_0_1200_797,https://scrap.kakaocdn.net/dn/c0nkx7/dJMb83Sggen/6ocdhz78HjNfuM0kvVfsMk/img.jpg?width=1200&amp;amp;height=797&amp;amp;face=0_0_1200_797,https://scrap.kakaocdn.net/dn/cGkvEa/dJMb89yaY3b/6nKD1OMkMMFw6DiVqOgwQk/img.png?width=300&amp;amp;height=338&amp;amp;face=123_123_227_237&quot;&gt;&lt;a href=&quot;https://blog.pragmaticengineer.com/the-product-minded-engineer&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://blog.pragmaticengineer.com/the-product-minded-engineer&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/pvHu2/dJMb81fP28a/MsQvJHYfk5jCLfZ1a8hIk0/img.jpg?width=1200&amp;amp;height=797&amp;amp;face=0_0_1200_797,https://scrap.kakaocdn.net/dn/c0nkx7/dJMb83Sggen/6ocdhz78HjNfuM0kvVfsMk/img.jpg?width=1200&amp;amp;height=797&amp;amp;face=0_0_1200_797,https://scrap.kakaocdn.net/dn/cGkvEa/dJMb89yaY3b/6nKD1OMkMMFw6DiVqOgwQk/img.png?width=300&amp;amp;height=338&amp;amp;face=123_123_227_237');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;The Product-Minded Software Engineer&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Product-minded engineers are developers with lots of interest in the product itself. They want to understand why decisions are made, how people use the product, and love to be involved in making product decisions. They're someone who would likely make a go&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;blog.pragmaticengineer.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;자연스럽게&amp;nbsp;후자의&amp;nbsp;방향을&amp;nbsp;생각했다.&amp;nbsp;전자는&amp;nbsp;LLM과&amp;nbsp;경쟁을&amp;nbsp;해야&amp;nbsp;하고,&amp;nbsp;후자는&amp;nbsp;그래도&amp;nbsp;사람들과&amp;nbsp;경쟁해야&amp;nbsp;하는&amp;nbsp;영역이라고&amp;nbsp;생각했으니까.&amp;nbsp;그런&amp;nbsp;관점에서&amp;nbsp;이런저런&amp;nbsp;이야기를&amp;nbsp;많이&amp;nbsp;했던&amp;nbsp;것&amp;nbsp;같다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;figure id=&quot;og_1772356030616&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;늘 고민이 많은 요즘입니다.
오늘은 부끄럽지만(...) 제가 저희 엔지니어 팀 슬랙 채널에 공유했&quot; data-og-description=&quot;늘 고민이 많은 요즘입니다. 오늘은 부끄럽지만(...) 제가 저희 엔지니어 팀 슬랙 채널에 공유했던 글을 나누려고 합니다. --- 안녕하세요 크리에이트립 엔지니어 여러분 저는 요즘&amp;nbsp;새 시대의 엔&quot; data-og-host=&quot;kr.linkedin.com&quot; data-og-source-url=&quot;https://www.linkedin.com/posts/jong-hak-seo-9142ba200_%EB%8A%98-%EA%B3%A0%EB%AF%BC%EC%9D%B4-%EB%A7%8E%EC%9D%80-%EC%9A%94%EC%A6%98%EC%9E%85%EB%8B%88%EB%8B%A4-%EC%98%A4%EB%8A%98%EC%9D%80-%EB%B6%80%EB%81%84%EB%9F%BD%EC%A7%80%EB%A7%8C-%EC%A0%9C%EA%B0%80-%EC%A0%80%ED%9D%AC-%EC%97%94%EC%A7%80%EB%8B%88%EC%96%B4-activity-7371086850830069760-CaI3?utm_source=share&amp;amp;utm_medium=member_desktop&amp;amp;rcm=ACoAADNNW6AB2kgn3BEW5sUgd8sS2ncRm538MEk&quot; data-og-url=&quot;https://kr.linkedin.com/posts/jong-hak-seo-9142ba200_%EB%8A%98-%EA%B3%A0%EB%AF%BC%EC%9D%B4-%EB%A7%8E%EC%9D%80-%EC%9A%94%EC%A6%98%EC%9E%85%EB%8B%88%EB%8B%A4-%EC%98%A4%EB%8A%98%EC%9D%80-%EB%B6%80%EB%81%84%EB%9F%BD%EC%A7%80%EB%A7%8C-%EC%A0%9C%EA%B0%80-%EC%A0%80%ED%9D%AC-%EC%97%94%EC%A7%80%EB%8B%88%EC%96%B4-activity-7371086850830069760-CaI3&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/XUCix/dJMb9dHltNB/fJYex0kGT7Ju58dJadlrG1/img.jpg?width=800&amp;amp;height=631&amp;amp;face=0_0_800_631,https://scrap.kakaocdn.net/dn/bpjEeX/dJMb9lL8Zgy/M5ysEQuIez9KGWLemP3aMk/img.jpg?width=800&amp;amp;height=631&amp;amp;face=0_0_800_631&quot;&gt;&lt;a href=&quot;https://www.linkedin.com/posts/jong-hak-seo-9142ba200_%EB%8A%98-%EA%B3%A0%EB%AF%BC%EC%9D%B4-%EB%A7%8E%EC%9D%80-%EC%9A%94%EC%A6%98%EC%9E%85%EB%8B%88%EB%8B%A4-%EC%98%A4%EB%8A%98%EC%9D%80-%EB%B6%80%EB%81%84%EB%9F%BD%EC%A7%80%EB%A7%8C-%EC%A0%9C%EA%B0%80-%EC%A0%80%ED%9D%AC-%EC%97%94%EC%A7%80%EB%8B%88%EC%96%B4-activity-7371086850830069760-CaI3?utm_source=share&amp;amp;utm_medium=member_desktop&amp;amp;rcm=ACoAADNNW6AB2kgn3BEW5sUgd8sS2ncRm538MEk&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://www.linkedin.com/posts/jong-hak-seo-9142ba200_%EB%8A%98-%EA%B3%A0%EB%AF%BC%EC%9D%B4-%EB%A7%8E%EC%9D%80-%EC%9A%94%EC%A6%98%EC%9E%85%EB%8B%88%EB%8B%A4-%EC%98%A4%EB%8A%98%EC%9D%80-%EB%B6%80%EB%81%84%EB%9F%BD%EC%A7%80%EB%A7%8C-%EC%A0%9C%EA%B0%80-%EC%A0%80%ED%9D%AC-%EC%97%94%EC%A7%80%EB%8B%88%EC%96%B4-activity-7371086850830069760-CaI3?utm_source=share&amp;amp;utm_medium=member_desktop&amp;amp;rcm=ACoAADNNW6AB2kgn3BEW5sUgd8sS2ncRm538MEk&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/XUCix/dJMb9dHltNB/fJYex0kGT7Ju58dJadlrG1/img.jpg?width=800&amp;amp;height=631&amp;amp;face=0_0_800_631,https://scrap.kakaocdn.net/dn/bpjEeX/dJMb9lL8Zgy/M5ysEQuIez9KGWLemP3aMk/img.jpg?width=800&amp;amp;height=631&amp;amp;face=0_0_800_631');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;늘 고민이 많은 요즘입니다. 오늘은 부끄럽지만(...) 제가 저희 엔지니어 팀 슬랙 채널에 공유했&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;늘 고민이 많은 요즘입니다. 오늘은 부끄럽지만(...) 제가 저희 엔지니어 팀 슬랙 채널에 공유했던 글을 나누려고 합니다. --- 안녕하세요 크리에이트립 엔지니어 여러분 저는 요즘&amp;nbsp;새 시대의 엔&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;kr.linkedin.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;채용, 팀 리빌딩&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;채용&amp;nbsp;과제도&amp;nbsp;AI를&amp;nbsp;적극적으로&amp;nbsp;쓰고,&amp;nbsp;대신&amp;nbsp;프롬프트를&amp;nbsp;첨부해달라고&amp;nbsp;나름&amp;nbsp;그&amp;nbsp;당시엔&amp;nbsp;파격적으로&amp;nbsp;진행해봤는데&amp;nbsp;굉장히&amp;nbsp;경험이&amp;nbsp;좋았다.&amp;nbsp;결국&amp;nbsp;우리가&amp;nbsp;알고자&amp;nbsp;하는&amp;nbsp;건&amp;nbsp;같이&amp;nbsp;일하는&amp;nbsp;구성원이&amp;nbsp;어떤&amp;nbsp;사고를&amp;nbsp;하며&amp;nbsp;일을&amp;nbsp;하는지인데,&amp;nbsp;그게&amp;nbsp;프롬프트에&amp;nbsp;다&amp;nbsp;드러나더라.&amp;nbsp;사실&amp;nbsp;기술&amp;nbsp;수준이나&amp;nbsp;그&amp;nbsp;사람의&amp;nbsp;기준&amp;nbsp;같은&amp;nbsp;것도&amp;nbsp;다&amp;nbsp;프롬프트에&amp;nbsp;드러났던&amp;nbsp;것&amp;nbsp;같다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;figure id=&quot;og_1772357136332&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;[크리에이트립 프론트엔드 채용 과제를 살짝 공개(?)합니다]

크리에이트립의 이번 프론트엔드 &quot; data-og-description=&quot;[크리에이트립 프론트엔드 채용 과제를 살짝 공개(?)합니다] 크리에이트립의 이번 프론트엔드 채용 과제에는 큰 변화가 있었는데요, 문제 해결에 LLM 사용을 적극적으로 권장하고, 제출 폼에 사&quot; data-og-host=&quot;kr.linkedin.com&quot; data-og-source-url=&quot;https://www.linkedin.com/posts/jong-hak-seo-9142ba200_%ED%81%AC%EB%A6%AC%EC%97%90%EC%9D%B4%ED%8A%B8%EB%A6%BD-%ED%94%84%EB%A1%A0%ED%8A%B8%EC%97%94%EB%93%9C-%EC%B1%84%EC%9A%A9-%EA%B3%BC%EC%A0%9C%EB%A5%BC-%EC%82%B4%EC%A7%9D-%EA%B3%B5%EA%B0%9C%ED%95%A9%EB%8B%88%EB%8B%A4-%ED%81%AC%EB%A6%AC%EC%97%90%EC%9D%B4%ED%8A%B8%EB%A6%BD%EC%9D%98-activity-7351433034027487233-Si_A?utm_source=share&amp;amp;utm_medium=member_desktop&amp;amp;rcm=ACoAADNNW6AB2kgn3BEW5sUgd8sS2ncRm538MEk&quot; data-og-url=&quot;https://kr.linkedin.com/posts/jong-hak-seo-9142ba200_%ED%81%AC%EB%A6%AC%EC%97%90%EC%9D%B4%ED%8A%B8%EB%A6%BD-%ED%94%84%EB%A1%A0%ED%8A%B8%EC%97%94%EB%93%9C-%EC%B1%84%EC%9A%A9-%EA%B3%BC%EC%A0%9C%EB%A5%BC-%EC%82%B4%EC%A7%9D-%EA%B3%B5%EA%B0%9C%ED%95%A9%EB%8B%88%EB%8B%A4-%ED%81%AC%EB%A6%AC%EC%97%90%EC%9D%B4%ED%8A%B8%EB%A6%BD%EC%9D%98-activity-7351433034027487233-Si_A&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/G8hsF/dJMb8Z3oG8z/nFskKVL8kHePKTFek4Yla0/img.png?width=1400&amp;amp;height=800&amp;amp;face=0_0_1400_800,https://scrap.kakaocdn.net/dn/7CnuJ/dJMb8SXvcSb/z6Q1sQKMv9gG3wxPtfsNM0/img.png?width=1400&amp;amp;height=800&amp;amp;face=0_0_1400_800&quot;&gt;&lt;a href=&quot;https://www.linkedin.com/posts/jong-hak-seo-9142ba200_%ED%81%AC%EB%A6%AC%EC%97%90%EC%9D%B4%ED%8A%B8%EB%A6%BD-%ED%94%84%EB%A1%A0%ED%8A%B8%EC%97%94%EB%93%9C-%EC%B1%84%EC%9A%A9-%EA%B3%BC%EC%A0%9C%EB%A5%BC-%EC%82%B4%EC%A7%9D-%EA%B3%B5%EA%B0%9C%ED%95%A9%EB%8B%88%EB%8B%A4-%ED%81%AC%EB%A6%AC%EC%97%90%EC%9D%B4%ED%8A%B8%EB%A6%BD%EC%9D%98-activity-7351433034027487233-Si_A?utm_source=share&amp;amp;utm_medium=member_desktop&amp;amp;rcm=ACoAADNNW6AB2kgn3BEW5sUgd8sS2ncRm538MEk&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://www.linkedin.com/posts/jong-hak-seo-9142ba200_%ED%81%AC%EB%A6%AC%EC%97%90%EC%9D%B4%ED%8A%B8%EB%A6%BD-%ED%94%84%EB%A1%A0%ED%8A%B8%EC%97%94%EB%93%9C-%EC%B1%84%EC%9A%A9-%EA%B3%BC%EC%A0%9C%EB%A5%BC-%EC%82%B4%EC%A7%9D-%EA%B3%B5%EA%B0%9C%ED%95%A9%EB%8B%88%EB%8B%A4-%ED%81%AC%EB%A6%AC%EC%97%90%EC%9D%B4%ED%8A%B8%EB%A6%BD%EC%9D%98-activity-7351433034027487233-Si_A?utm_source=share&amp;amp;utm_medium=member_desktop&amp;amp;rcm=ACoAADNNW6AB2kgn3BEW5sUgd8sS2ncRm538MEk&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/G8hsF/dJMb8Z3oG8z/nFskKVL8kHePKTFek4Yla0/img.png?width=1400&amp;amp;height=800&amp;amp;face=0_0_1400_800,https://scrap.kakaocdn.net/dn/7CnuJ/dJMb8SXvcSb/z6Q1sQKMv9gG3wxPtfsNM0/img.png?width=1400&amp;amp;height=800&amp;amp;face=0_0_1400_800');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;[크리에이트립 프론트엔드 채용 과제를 살짝 공개(?)합니다] 크리에이트립의 이번 프론트엔드&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;[크리에이트립 프론트엔드 채용 과제를 살짝 공개(?)합니다] 크리에이트립의 이번 프론트엔드 채용 과제에는 큰 변화가 있었는데요, 문제 해결에 LLM 사용을 적극적으로 권장하고, 제출 폼에 사&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;kr.linkedin.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;지금 생각해도, 덕분에 새 시대에 맞는 정말 훌륭한 프론트엔드 엔지니어분들을 채용해서 참 다행이라고 생각한다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;개발자분들에게는 계속 프로덕트를 더 많이 사용해보고, 타 팀과 이야기해서 병목을 적극적으로 찾아보라고 이야기를 했다. 부끄러운 이야기지만 우리 프로덕트 팀은 도그푸딩을 거의 하지 않았다. 내가 있었던 4년간의 시간 동안 프로덕트의 탐색-예약-구매-리뷰 사이클을 돌려본 구성원이 거의 없었으니까... 변명이지만, 한국을 여행하는 외국인 여행객을 위한 플랫폼이라는 점이 뭔가 좋은 핑계거리가 되었던 것 같다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;아무튼&amp;nbsp;개발&amp;nbsp;리드라는&amp;nbsp;자리에서&amp;nbsp;좀&amp;nbsp;더&amp;nbsp;발언권이&amp;nbsp;생긴&amp;nbsp;참에,&amp;nbsp;개발자분들을&amp;nbsp;이끌고&amp;nbsp;프로덕트의&amp;nbsp;도그푸딩을&amp;nbsp;진행했다.&amp;nbsp;사실&amp;nbsp;정량적인&amp;nbsp;결과로&amp;nbsp;나온&amp;nbsp;프로덕트&amp;nbsp;개선안들보다&amp;nbsp;더&amp;nbsp;중요한&amp;nbsp;건,&amp;nbsp;그&amp;nbsp;경험&amp;nbsp;자체라고&amp;nbsp;생각한다.&amp;nbsp;내가&amp;nbsp;만드는&amp;nbsp;프로덕트가&amp;nbsp;어떻게&amp;nbsp;사용되고,&amp;nbsp;어떤&amp;nbsp;문제를&amp;nbsp;해결하고,&amp;nbsp;어떤&amp;nbsp;지점이&amp;nbsp;불편한지에&amp;nbsp;대한&amp;nbsp;감각.&amp;nbsp;새&amp;nbsp;시대의&amp;nbsp;엔지니어에겐&amp;nbsp;그런&amp;nbsp;감각이&amp;nbsp;필요하다고&amp;nbsp;생각했다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;한편 매번 채용에서 겪는 어려움을 타파하고자, 개발팀 자체의 브랜딩에 대해서도 노력을 쏟았던 것 같다. 그 일환으로 개발팀의 3, 4분기 예산과 일부 엔지니어들의 노력을 통해 Dev Talk라는 이름의 개발 밋업도 진행했다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;figure id=&quot;og_1772356162432&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;Creatrip Dev Talk - 이벤터스&quot; data-og-description=&quot;크리에이트립 개발팀 밋업&quot; data-og-host=&quot;event-us.kr&quot; data-og-source-url=&quot;https://event-us.kr/creatripdev/event/113880?utm_source=sms&amp;amp;utm_medium=dm&amp;amp;utm_campaign=bhzaaaaaza&quot; data-og-url=&quot;https://event-us.kr/creatripdev/event/113880&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/sdKj7/dJMb9b3PucO/RvoZQwY7mzOr2pG5ppZAj1/img.png?width=960&amp;amp;height=540&amp;amp;face=0_0_960_540,https://scrap.kakaocdn.net/dn/ARRQ0/dJMb9fZsKpG/voKWFdrSxr4YI5kfqrSUs0/img.png?width=960&amp;amp;height=540&amp;amp;face=0_0_960_540,https://scrap.kakaocdn.net/dn/jnM48/dJMb9gxiO0o/JvJFjzD1vcXYFD0qk9IgyK/img.png?width=600&amp;amp;height=350&amp;amp;face=0_0_600_350&quot;&gt;&lt;a href=&quot;https://event-us.kr/creatripdev/event/113880?utm_source=sms&amp;amp;utm_medium=dm&amp;amp;utm_campaign=bhzaaaaaza&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://event-us.kr/creatripdev/event/113880?utm_source=sms&amp;amp;utm_medium=dm&amp;amp;utm_campaign=bhzaaaaaza&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/sdKj7/dJMb9b3PucO/RvoZQwY7mzOr2pG5ppZAj1/img.png?width=960&amp;amp;height=540&amp;amp;face=0_0_960_540,https://scrap.kakaocdn.net/dn/ARRQ0/dJMb9fZsKpG/voKWFdrSxr4YI5kfqrSUs0/img.png?width=960&amp;amp;height=540&amp;amp;face=0_0_960_540,https://scrap.kakaocdn.net/dn/jnM48/dJMb9gxiO0o/JvJFjzD1vcXYFD0qk9IgyK/img.png?width=600&amp;amp;height=350&amp;amp;face=0_0_600_350');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;Creatrip Dev Talk - 이벤터스&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;크리에이트립 개발팀 밋업&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;event-us.kr&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;이후 채용에서의 지원이 소폭 늘어난 것과, 크리에이트립 개발팀의 인지도 향상이라는 성과에 비해서 생각보다 더 리소스가 많이 들어갔던 것 같아서.. 미래에 한 번 더 진행이 될지는 모르겠다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;프로덕트&amp;nbsp;엔지니어로의&amp;nbsp;전환&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;하반기에는 개발팀의 프로덕트 엔지니어 전환에 대한 구상도 했다. AI을 사용하면서 생산성이 늘어나도, 결국 백엔드-프론트엔드라는 소통 지점이 병목이 되는 것을 꾸준히 느꼈다. 내가 5년차 프론트엔드 개발자로 하는 것들의 90%를 AI가 해주는 것 같은데, 나머지 10%를 위해 나라는 사람의 리소스를 써야 하나? 엔지니어들은 그 나머지 10%를 메꿔주는 시스템을 구축하는 데 신경 쓰고, 그냥 모든 엔지니어가 직군에 갇히지 않는 기능 구현을 하면 안 되나? 아니 더 나아가서 모든 PM들이 기능을 구현할 수 있는 시스템을 엔지니어들이 만들고 유지보수하는 게 레버리지가 더 높지 않을까?&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;하는 생각이 강하게 들어 관련 작업들을 했다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;프로덕트 레포에서 암묵지를 발견할 때마다 ESLint 룰로 만들어서 잠갔고, 룰로 잡기 어려운 건 가이드라인 문서로 남겨 동적으로 참조할 수 있는 구조를 만들었다.. 돌아보니 한 해 동안 커스텀 ESLint 규칙만 30건 넘게 추가했다. 최근에 다른 글들을 보니 비슷한 경험을 하는 사람들은 비슷한 결론에 도달하는구나 싶다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;a href=&quot;https://www.anthropic.com/engineering/effective-harnesses-for-long-running-agents&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://www.anthropic.com/engineering/effective-harnesses-for-long-running-agents&lt;/a&gt;&lt;/span&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1772354518568&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;Effective harnesses for long-running agents&quot; data-og-description=&quot;Anthropic is an AI safety and research company that's working to build reliable, interpretable, and steerable AI systems.&quot; data-og-host=&quot;www.anthropic.com&quot; data-og-source-url=&quot;https://www.anthropic.com/engineering/effective-harnesses-for-long-running-agents&quot; data-og-url=&quot;https://www.anthropic.com/engineering/effective-harnesses-for-long-running-agents&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/guGd8/dJMb8UHMA3j/f5mbunhX8YjxXBl8KvKNi1/img.png?width=2400&amp;amp;height=1260&amp;amp;face=0_0_2400_1260,https://scrap.kakaocdn.net/dn/bukyT5/dJMb8SXvcKh/7lqGVQ3cWZc2Es5UyS2BVk/img.png?width=2400&amp;amp;height=1260&amp;amp;face=0_0_2400_1260&quot;&gt;&lt;a href=&quot;https://www.anthropic.com/engineering/effective-harnesses-for-long-running-agents&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://www.anthropic.com/engineering/effective-harnesses-for-long-running-agents&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/guGd8/dJMb8UHMA3j/f5mbunhX8YjxXBl8KvKNi1/img.png?width=2400&amp;amp;height=1260&amp;amp;face=0_0_2400_1260,https://scrap.kakaocdn.net/dn/bukyT5/dJMb8SXvcKh/7lqGVQ3cWZc2Es5UyS2BVk/img.png?width=2400&amp;amp;height=1260&amp;amp;face=0_0_2400_1260');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;Effective harnesses for long-running agents&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Anthropic is an AI safety and research company that's working to build reliable, interpretable, and steerable AI systems.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;www.anthropic.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;a href=&quot;https://openai.com/index/harness-engineering/&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://openai.com/index/harness-engineering/&lt;/a&gt;&lt;/span&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1772354362639&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;Harness engineering: leveraging Codex in an agent-first world&quot; data-og-description=&quot;By Ryan Lopopolo, Member of the Technical Staff&quot; data-og-host=&quot;openai.com&quot; data-og-source-url=&quot;https://openai.com/index/harness-engineering/&quot; data-og-url=&quot;https://openai.com/index/harness-engineering/&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/lOM2K/dJMb9aKCxTN/VFjZBQC5SJqgFQMuqTbya0/img.png?width=1600&amp;amp;height=900&amp;amp;face=0_0_1600_900,https://scrap.kakaocdn.net/dn/cve88B/dJMb84p59Bz/QWZ9V7v7bGlYT559wkQKW0/img.png?width=1600&amp;amp;height=900&amp;amp;face=0_0_1600_900&quot;&gt;&lt;a href=&quot;https://openai.com/index/harness-engineering/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://openai.com/index/harness-engineering/&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/lOM2K/dJMb9aKCxTN/VFjZBQC5SJqgFQMuqTbya0/img.png?width=1600&amp;amp;height=900&amp;amp;face=0_0_1600_900,https://scrap.kakaocdn.net/dn/cve88B/dJMb84p59Bz/QWZ9V7v7bGlYT559wkQKW0/img.png?width=1600&amp;amp;height=900&amp;amp;face=0_0_1600_900');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;Harness engineering: leveraging Codex in an agent-first world&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;By Ryan Lopopolo, Member of the Technical Staff&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;openai.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;한편 엔지니어분들도 타 직군의 익숙하지 않은 기술에 대해 빠르게 이해도를 높이고 AI에 대한 제어 혹은 감사 능력을 갖기 위해 매주 세미나를 진행했다(지금도 하고 있다).&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;그래도 사실 나는 코드가 좋다&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;이것들과&amp;nbsp;별개로&amp;nbsp;나는&amp;nbsp;우리&amp;nbsp;프로덕트팀에서&amp;nbsp;아직도&amp;nbsp;제일&amp;nbsp;많은&amp;nbsp;커밋과&amp;nbsp;PR을&amp;nbsp;올리는&amp;nbsp;개인&amp;nbsp;기여자이기도&amp;nbsp;하다.&amp;nbsp;나&amp;nbsp;스스로가&amp;nbsp;문제&amp;nbsp;해결에&amp;nbsp;대한&amp;nbsp;감각을&amp;nbsp;놓치지&amp;nbsp;않아야&amp;nbsp;팀의&amp;nbsp;방향성을&amp;nbsp;가늠할&amp;nbsp;수&amp;nbsp;있는&amp;nbsp;감각이&amp;nbsp;유지되기&amp;nbsp;때문이기도&amp;nbsp;하고,&amp;nbsp;애초에&amp;nbsp;나는&amp;nbsp;일을&amp;nbsp;하는&amp;nbsp;게&amp;nbsp;즐겁다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2026-03-01 오후 5.44.41.png&quot; data-origin-width=&quot;1554&quot; data-origin-height=&quot;1488&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cjvD5v/dJMcagEDCgt/Dk7Ckod3XKOqoYUhUmSVb0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cjvD5v/dJMcagEDCgt/Dk7Ckod3XKOqoYUhUmSVb0/img.png&quot; data-alt=&quot;AI와 함께하는 생산성 향상?&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cjvD5v/dJMcagEDCgt/Dk7Ckod3XKOqoYUhUmSVb0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcjvD5v%2FdJMcagEDCgt%2FDk7Ckod3XKOqoYUhUmSVb0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;600&quot; height=&quot;575&quot; data-filename=&quot;스크린샷 2026-03-01 오후 5.44.41.png&quot; data-origin-width=&quot;1554&quot; data-origin-height=&quot;1488&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;AI와 함께하는 생산성 향상?&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;올해도 굵직한 작업들이 많았다. styled-components에서 TailwindCSS로의 전사 마이그레이션을 AI 자동화 파이프라인까지 만들어가며 10개월에 걸쳐 완주했고, 지도 도메인이라는 완전히 새로운 서비스를 Phase별로 쪼개서 1.5개월 만에 릴리즈하기도 했다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;3년&amp;nbsp;전&amp;nbsp;리액트&amp;nbsp;소스코드를&amp;nbsp;보고&amp;nbsp;공부한&amp;nbsp;지식을&amp;nbsp;바탕으로&amp;nbsp;다른&amp;nbsp;팀원들이&amp;nbsp;감을&amp;nbsp;못&amp;nbsp;잡는&amp;nbsp;복잡한&amp;nbsp;버그를&amp;nbsp;여러&amp;nbsp;종류의&amp;nbsp;렌더&amp;nbsp;페이즈의&amp;nbsp;호출&amp;nbsp;순서를&amp;nbsp;설명하며&amp;nbsp;해결해낼&amp;nbsp;때도&amp;nbsp;짜릿했고,&amp;nbsp;2년&amp;nbsp;전&amp;nbsp;크롬&amp;nbsp;devtools를&amp;nbsp;눈&amp;nbsp;빠지게&amp;nbsp;보며&amp;nbsp;프론트엔드&amp;nbsp;KR이었던&amp;nbsp;코어&amp;nbsp;웹&amp;nbsp;바이탈을&amp;nbsp;90%까지&amp;nbsp;끌어올릴&amp;nbsp;때에도&amp;nbsp;짜릿했고,&amp;nbsp;작년&amp;nbsp;AI&amp;nbsp;Platform을&amp;nbsp;구축하고&amp;nbsp;내가&amp;nbsp;원하는&amp;nbsp;워크플로우를&amp;nbsp;자동화하고,&amp;nbsp;회의록,&amp;nbsp;프로젝트,&amp;nbsp;마켓플레이스,&amp;nbsp;스킬,&amp;nbsp;전용&amp;nbsp;익스텐션&amp;nbsp;등을&amp;nbsp;만들어서&amp;nbsp;구성원들이&amp;nbsp;사용하는&amp;nbsp;모습을&amp;nbsp;볼&amp;nbsp;때도&amp;nbsp;짜릿했다.&amp;nbsp;요즘은&amp;nbsp;내&amp;nbsp;모든&amp;nbsp;LLM&amp;nbsp;사용&amp;nbsp;기록을&amp;nbsp;분석한&amp;nbsp;결과를&amp;nbsp;바탕으로&amp;nbsp;pi&amp;nbsp;agent를&amp;nbsp;내&amp;nbsp;생산성이&amp;nbsp;최대로&amp;nbsp;나올&amp;nbsp;수&amp;nbsp;있는&amp;nbsp;구조로&amp;nbsp;커스텀해서&amp;nbsp;쓰고&amp;nbsp;있는데,&amp;nbsp;one-shot&amp;nbsp;프롬프트&amp;nbsp;하나로&amp;nbsp;수십&amp;nbsp;개의&amp;nbsp;에이전트가&amp;nbsp;뚝딱뚝딱&amp;nbsp;거리면서&amp;nbsp;내가&amp;nbsp;했을법한&amp;nbsp;퀄리티의&amp;nbsp;결과물을&amp;nbsp;내는&amp;nbsp;걸&amp;nbsp;보면&amp;nbsp;또&amp;nbsp;짜릿하다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;업무로서 나에게 주어진, 다소 일상적인 태스크들도 참 많았는데 그런 것들도 다는 아니어도 대부분은 재미있게, 퀘스트를 깨듯 재미있게 했다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;AI&amp;nbsp;시대,&amp;nbsp;나의&amp;nbsp;입장&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;엊그제&amp;nbsp;출근하면서&amp;nbsp;그런&amp;nbsp;생각을&amp;nbsp;했다.&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;사실&amp;nbsp;AI&amp;nbsp;시대에&amp;nbsp;대해서&amp;nbsp;나&amp;nbsp;개인의&amp;nbsp;입장에서는&amp;nbsp;걱정이&amp;nbsp;크게&amp;nbsp;없는데,&amp;nbsp;이유는&amp;nbsp;내가&amp;nbsp;소프트웨어를&amp;nbsp;사용해서&amp;nbsp;어떤&amp;nbsp;문제든&amp;nbsp;푸는&amp;nbsp;걸&amp;nbsp;즐기는&amp;nbsp;사람이라는&amp;nbsp;점&amp;nbsp;때문이다.&amp;nbsp;아무리&amp;nbsp;대&amp;nbsp;딸깍&amp;nbsp;시대에&amp;nbsp;AI가&amp;nbsp;다&amp;nbsp;해줘도,&amp;nbsp;나는&amp;nbsp;'다&amp;nbsp;해주는&amp;nbsp;AI들을&amp;nbsp;어떻게&amp;nbsp;묶어서&amp;nbsp;퀄리티를&amp;nbsp;극대화할&amp;nbsp;수&amp;nbsp;있을까',&amp;nbsp;'에이전트&amp;nbsp;워크플로우를&amp;nbsp;어떻게&amp;nbsp;설계하면&amp;nbsp;더&amp;nbsp;저렴한&amp;nbsp;모델로&amp;nbsp;동일한&amp;nbsp;결과가&amp;nbsp;나오게&amp;nbsp;할&amp;nbsp;수&amp;nbsp;있을까'&amp;nbsp;등을&amp;nbsp;재미있게&amp;nbsp;고민하면서&amp;nbsp;할&amp;nbsp;사람이기&amp;nbsp;때문이다.&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;기본소득&amp;nbsp;시대가&amp;nbsp;와서&amp;nbsp;모든&amp;nbsp;사람들에게&amp;nbsp;동일한&amp;nbsp;보상이(아무것도&amp;nbsp;하지&amp;nbsp;않더라도)&amp;nbsp;주어진다고&amp;nbsp;해도&amp;nbsp;난&amp;nbsp;아마&amp;nbsp;재미를&amp;nbsp;느끼면서&amp;nbsp;이런저런&amp;nbsp;문제를&amp;nbsp;풀어보려고&amp;nbsp;하고&amp;nbsp;있을&amp;nbsp;테니까.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;아 그리고, 결혼도 했다&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;올해 6월6일6시(666), 아주 특별하고 재미있는 결혼식을 했다. 신랑이 마법사 지팡이를 들고, 신부가 쌍검을 들고 들어오는...&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;a href=&quot;https://dasom-jonghak.wedding/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://dasom-jonghak.wedding/&lt;/a&gt;&lt;/span&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1772356632057&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;다솜이와 종학이의 결혼식에 초대합니다&quot; data-og-description=&quot;25년 6월 6일 금요일 오후 6시 명동 온즈드롬&quot; data-og-host=&quot;dasom-jonghak.wedding&quot; data-og-source-url=&quot;https://dasom-jonghak.wedding/&quot; data-og-url=&quot;https://dasom-jonghak.wedding/&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/YDY6r/dJMb86OZfvg/P7Da8d5tLqQxIzNDv5l1kK/img.png?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630,https://scrap.kakaocdn.net/dn/cOsM3a/dJMb83kqmnp/1rZc30Nlc3vShoePJdM16K/img.png?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630&quot;&gt;&lt;a href=&quot;https://dasom-jonghak.wedding/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://dasom-jonghak.wedding/&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/YDY6r/dJMb86OZfvg/P7Da8d5tLqQxIzNDv5l1kK/img.png?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630,https://scrap.kakaocdn.net/dn/cOsM3a/dJMb83kqmnp/1rZc30Nlc3vShoePJdM16K/img.png?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;다솜이와 종학이의 결혼식에 초대합니다&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;25년 6월 6일 금요일 오후 6시 명동 온즈드롬&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;dasom-jonghak.wedding&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;신혼여행은 몽골을 2주 정도 다녀왔는데, 자연을 좋아하는 나에게는 너무나도 즐거운 시간이었다. 해발 2000m 고원을 달리는 차 안에서도 코딩은 할 수 있어서 좋았다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;mongolia.jpg&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;960&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bNLqWS/dJMcaiWHei6/Um2TNQevMrrWY1X0JvIEdk/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bNLqWS/dJMcaiWHei6/Um2TNQevMrrWY1X0JvIEdk/img.jpg&quot; data-alt=&quot;홉스골 가기 전&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bNLqWS/dJMcaiWHei6/Um2TNQevMrrWY1X0JvIEdk/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbNLqWS%2FdJMcaiWHei6%2FUm2TNQevMrrWY1X0JvIEdk%2Fimg.jpg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;600&quot; height=&quot;450&quot; data-filename=&quot;mongolia.jpg&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;960&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;홉스골 가기 전&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;</description>
      <category>잡담</category>
      <category>2025 회고</category>
      <author>눅눅한 피부</author>
      <guid isPermaLink="true">https://nookpi.tistory.com/216</guid>
      <comments>https://nookpi.tistory.com/216#entry216comment</comments>
      <pubDate>Sun, 1 Mar 2026 18:34:55 +0900</pubDate>
    </item>
    <item>
      <title>기준의 간극</title>
      <link>https://nookpi.tistory.com/215</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;올해에는 유독 참 많은 사람들과 이런 저런 계기를 통해 대화를 많이 나눌 기회가 있었는데, 어느샌가 대화하면서 느끼게 되는 부분이 있어 글로 남겨둔다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우리는 대화를 통해서 타인과의 '기준'을 가늠할 수 있게 되는데, 보통은 그 기준의 차이에서 흥미를 느끼기도 하고 거리를 느끼기도 한다. &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;(여기서의 기준은&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;background-color: #ffffff; color: #09090b; text-align: start;&quot;&gt;특정 이슈나 주제에 대한 개인의 '당연함의 선'이나 '중요도의 우선순위'를 나타내는 말로 사용했다)&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;개인의 기준이란 어디에서 비롯되는가?&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span&gt;경험의 밀도&lt;/span&gt;&lt;/b&gt;에서 나온다. 교통사고를 당해본 사람은 안전벨트를 당연하게 매고, 사기를 당해본 사람은 계약서를 꼼꼼히 읽는다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #09090b; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #09090b; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span&gt;계급의 위치&lt;/span&gt;&lt;/b&gt;에서 나온다. 알바를 해본 사람과 안 해본 사람이 '최저임금 인상'을 다르게 본다. 집을 사본 사람과 전세만 살아본 사람이 '부동산 정책'을 다르게 본다. 실업급여를 받으며 어려운 시기를 이겨낸 사람과 실업급여를 받아본 적 없는 사람의 입장은 다르다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #09090b; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #09090b; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span&gt;정보의 출처&lt;/span&gt;&lt;/b&gt;에서 나온다. 유튜브로 뉴스를 보는 사람과 신문으로 보는 사람의 '심각함'이 다르다. 의사 친구가 있는 사람과 없는 사람의 '의료 정책'에 대한 관심도가 다르다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #09090b; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #09090b; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span&gt;소속감의 방향&lt;/span&gt;&lt;/b&gt;에서 나온다. 자녀가 있는 사람은 교육 정책에, 부모를 모시는 사람은 복지 정책에 더 민감하다. 내가 보호해야 할 사람이 기준의 우선순위를 만든다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #09090b; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #09090b; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;대화에서 서로의 기준에 대한 간극이 매우 크게 느껴지는 순간, 기준의 근원이 되는 요인들이 다시 한 번 상기되며 어색한 순간이 찾아온다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #09090b; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #09090b; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;나는 내 기준을 설명할 때 &quot;당연하다&quot;는 말 대신 &quot;나에게는&quot;이라는 말을 의식적으로 붙이곤 한다. &quot;당연히 그런 것&quot;이 아니라 &quot;나에게는 그런 것&quot;, &quot;누구나 알 수 있는 일이야&quot;가 아니라 &quot;내 경험으로는&quot; 혹은 &quot;내 입장에서는 그래&quot;.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #09090b; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #09090b; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #09090b; text-align: start;&quot;&gt;다소 자신감이 없어보이려나 싶은 생각도 들고... &lt;/span&gt;작은 전치사 하나가 이 간극을 줄여줄지는 모르겠다.&lt;/p&gt;</description>
      <category>잡담</category>
      <author>눅눅한 피부</author>
      <guid isPermaLink="true">https://nookpi.tistory.com/215</guid>
      <comments>https://nookpi.tistory.com/215#entry215comment</comments>
      <pubDate>Sun, 12 Oct 2025 17:01:29 +0900</pubDate>
    </item>
    <item>
      <title>먼저 온 미래 | 장강명</title>
      <link>https://nookpi.tistory.com/214</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;2016년 3월, 알파고와 이세돌의 대국이 있었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;9년 후, 이 때의 충격을 바둑계는 어떻게 소화했을까?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ChatGPT를 위시한 수많은 LLM이 우리의 일상과 일자리에 섞여드는 지금, 우리보다 먼저 그 미래를 마주했던 바둑계의 감상과 대응이 퍽 흥미롭다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;우리는 기계와 과학의 시대에 살고 있기에 무슨 일이 있어도 '진보'는 계속되어야 하고 지식은 절대로 억제되어선 안 된다는 관념에 감염되어 있다. 우리는 말로는 기계가 사람을 위해 만들어졌지 사람이 기계를 위해 만들어진 건 아니라고 한다. 하지만 실제로는 기계의 발달을 제어하려는 시도는 지식에 대한 공격이며 곧 일종의 불경으로 간주되는 것 같다.&lt;br /&gt;&lt;br /&gt;- 조지 오웰, 『위건 부두로 가는 길』&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;내가 이 일을 시작하던 시점인 5년 전에는 업무에서 AI를 사용한다는 것을 상상하기 어려웠다. 알파고 이전의 바둑계와 마찬가지로 업무 영역에서 AI가 대체하기 어렵다고 생각했고, 소위 말하는 소프트웨어 공학에 어떤 '예술적 특성'이 있기 때문에 더 그렇다고 생각했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;'아름다운 코드', '코드에서 악취가 난다', '보기 좋은 코드가 유지보수하기 좋은 코드다' 등등... 프로그래밍에서도 암묵지에서 비롯된 모호한 용어들이 많았고, 이런 모호함과 다의성(多義性)은 인간의 고유한 영역이라고 생각했으니까.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;불과 2~3년 후에 Github copilot이 나왔고, 마치 내 생각을 읽는 듯한 자동완성과 코드 추천 기능을 보면서 크게 놀랐다. 개인적으로는 공부를 시작할때부터 달고 살았던 손목 통증이 사라지는 계기가 되어 매우 기쁘기도 했고...&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 작년 말부터 지금까지, 특히 최근에는 코드를 직접 작성하거나 수정하는 일이 극적으로 줄어들었다. Cursor, Windsurf, Kiro등 AI IDE에게 내 의도를 정확하고 자세하게 설명하거나, 그 과정에서 함께 논의하고 세부 구현을 맡기는 일이 나의 (개발쪽 업무에서는)대부분의 업무 방식이 되었다. 최근에는 어떻게 하면 &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;Claude Code를 비롯한 Agent AI에게 내 업무의 맥락을 위임할 수 있을지, 암묵지를 문서화 할 수 있을지에 대한 방법을 계속 고민하고 있다. 맥락이 충분히 담겼다고 판단되면 Agent에게 계획 수립부터 구현까지 전부 맡기기도 한다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;바둑계와 마찬가지로, 프로그래밍 업계도 AI와의 업무에 대한 반응은 어느정도 나뉘는 것 같다. 나는 개인적으로 내가 속한 업계가 이러한 변화에 빠르게 인지할 수 있는 업계이며 때문에 패러다임의 변화를 어느정도 예측하고 한 발 앞서 적응할 수 있는 환경이라 다행이라고 생각하는 편이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;몇 달 전, CX팀의 불편함을 해소시켜줄 수 있는 작은 프로그램을 AI로 구현한 적이 있다. AI의 도움을 받아 매우 적은 나의 리소스로 구현했다는 점에서도 의의가 있지만, 해당 프로그램을 사용한 이후 CX팀의 생산성이 올라 채용 계획이 백지화 되었다는 이야기를 들었을 때에는 기분이 참 묘했다. 그만큼의 비즈니스 임팩트를 짧은 시간에 냈다는 점에서 개인적으로는 고무적이었지만, 어쩌면 우리 회사에서 함께 일할 수 있었던 그 직원분들에게는 개인이 감당 혹은 인지할 수 있는 영향이 아니었을 것이라는 점에서 그렇다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;최근 주니어 개발자 채용을 하는 회사를 보기가 정말 힘들어졌는데, 각 기업들의 현장에서 이러한 일이 일어나고 있을 것으로 예측된다. 아직까지는 AI가 시니어 개발자 만큼의 풍부한 경험과 맥락에서 오는 포괄적 구현, 커뮤니케이션 능력이 없으니까. 현재 수준의 AI를 잘 사용할 수 있는 사람들은 주니어 개발자들을 데리고 개발하던 시니어 개발자일 테니까. 주니어 개발자 정도의 구현은 시니어 개발자들이 추가로 지출하는 월 $200정도면 충분하니까.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;빅테크 기업들이 앞다투어 프로그래밍 AI를 개발하는 이유는 그 시장이 돈이 되기 때문이다. 즉, 프로그래머들이 몸값이 높기 때문이다. 몸값이 훨씬 비싼 시니어 개발자들도 대체하려고 최선을 다 하겠지. 그 때 기업은 어떤 선택을 할까?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;뻔한 질문은 그만두고, 앞으로의 시대에 개발자들은 어떤 역량을 키워야 할까?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AI의 역량에 대해 무엇 하나 장담할 수 있는 시대는 아니지만, 나는 '주도성'이 우리가 갖춰야 할 핵심 역량이라고 믿는다. 문제 발견과 해결책 수립, 그리고 구현이라는 3단계 과정은 비단 개발 영역 뿐만 아니라 비즈니스에서 본질적인 프로세스라고 생각하는데, 이미 문제 발견과 해결책 수립에서 AI의 도움을 받을 수 있고, 구현은 거의 비용이 들지 않을 정도로 AI가 이미 잘 하는 영역이다. 하지만 어떤 문제를 발견하려고 하는지, 본질적으로 문제 자체를 해결하고 싶은지는 오롯이 인간의 주도성(문제 해결에 대한 의지라고 치환해도 될 것 같다)이 기여한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;책에서 던지는 질문들이 참 많은데, 저자와 나를 포함해서 현 시점에는 누구도 자신있게 대답할 수 없는 것들 투성이다. 예술은 무엇인가. 예술성은 무엇인가. 우리가 하는 업무에서 AI의 도움을 받는것과 AI에 의존하는 것의 차이는 무엇인가. 가치에 대한 평가 주체는 누가(무엇이) 될 것인가. 우리가 잃게 될 것, 얻게 될 것들은 무엇일까.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여담이지만, 저커버그가 말하는 개인화된 초지능의 시대가 와도 우리 모두가 행복해지진 않을게 분명하다. 제 2의 1984를 누가 집필해줬으면 좋겠다. 2084같은 제목으로...&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;figure id=&quot;og_1754205637304&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;먼저 온 미래 | 장강명 - 교보문고&quot; data-og-description=&quot;먼저 온 미래 | 나는 바둑계에 미래가 먼저 왔다고 생각한다. 2016년부터 몇 년간 바둑계에서 벌어진 일들이 앞으로 여러 업계에서 벌어질 것이다.2016년 이세돌-알파고 대국 이후 바둑계에 먼저 &quot; data-og-host=&quot;product.kyobobook.co.kr&quot; data-og-source-url=&quot;https://product.kyobobook.co.kr/detail/S000216885989&quot; data-og-url=&quot;https://product.kyobobook.co.kr/detail/S000216885989&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/OWqaq/hyZvyn9Ob6/ULKmlB9oOlT1PpueirpyfK/img.jpg?width=458&amp;amp;height=703&amp;amp;face=0_0_458_703,https://scrap.kakaocdn.net/dn/bZoRkl/hyZrqZFmW2/QZK7mZcsdagKeF4uZSxILK/img.jpg?width=458&amp;amp;height=703&amp;amp;face=0_0_458_703,https://scrap.kakaocdn.net/dn/tqr7b/hyZvmVyLaF/4rbyoyYta9pVAQPgoY1AFK/img.jpg?width=814&amp;amp;height=5092&amp;amp;face=0_0_814_5092&quot;&gt;&lt;a href=&quot;https://product.kyobobook.co.kr/detail/S000216885989&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://product.kyobobook.co.kr/detail/S000216885989&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/OWqaq/hyZvyn9Ob6/ULKmlB9oOlT1PpueirpyfK/img.jpg?width=458&amp;amp;height=703&amp;amp;face=0_0_458_703,https://scrap.kakaocdn.net/dn/bZoRkl/hyZrqZFmW2/QZK7mZcsdagKeF4uZSxILK/img.jpg?width=458&amp;amp;height=703&amp;amp;face=0_0_458_703,https://scrap.kakaocdn.net/dn/tqr7b/hyZvmVyLaF/4rbyoyYta9pVAQPgoY1AFK/img.jpg?width=814&amp;amp;height=5092&amp;amp;face=0_0_814_5092');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;먼저 온 미래 | 장강명 - 교보문고&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;먼저 온 미래 | 나는 바둑계에 미래가 먼저 왔다고 생각한다. 2016년부터 몇 년간 바둑계에서 벌어진 일들이 앞으로 여러 업계에서 벌어질 것이다.2016년 이세돌-알파고 대국 이후 바둑계에 먼저&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;product.kyobobook.co.kr&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 책의 저자이신 장강명씨의 부인이자 그믐 대표이신 김새섬님의 건강을 기원합니다.&lt;/p&gt;</description>
      <category>독후단상</category>
      <category>AI</category>
      <category>먼저 온 미래</category>
      <author>눅눅한 피부</author>
      <guid isPermaLink="true">https://nookpi.tistory.com/214</guid>
      <comments>https://nookpi.tistory.com/214#entry214comment</comments>
      <pubDate>Sun, 3 Aug 2025 17:09:25 +0900</pubDate>
    </item>
    <item>
      <title>사내 AI 챗봇 서비스 구축하기</title>
      <link>https://nookpi.tistory.com/213</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://medium.com/creatrip/%EC%82%AC%EB%82%B4-ai-%EC%B1%97%EB%B4%87-%EC%84%9C%EB%B9%84%EC%8A%A4-%EA%B5%AC%EC%B6%95%ED%95%98%EA%B8%B0-a4fab54f898d&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://medium.com/creatrip/%EC%82%AC%EB%82%B4-ai-%EC%B1%97%EB%B4%87-%EC%84%9C%EB%B9%84%EC%8A%A4-%EA%B5%AC%EC%B6%95%ED%95%98%EA%B8%B0-a4fab54f898d&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1740022158348&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;사내 AI 챗봇 서비스 구축하기&quot; data-og-description=&quot;안녕하세요. 크리에이트립(Creatrip) FE 개발자 서종학입니다.&quot; data-og-host=&quot;medium.com&quot; data-og-source-url=&quot;https://medium.com/creatrip/%EC%82%AC%EB%82%B4-ai-%EC%B1%97%EB%B4%87-%EC%84%9C%EB%B9%84%EC%8A%A4-%EA%B5%AC%EC%B6%95%ED%95%98%EA%B8%B0-a4fab54f898d&quot; data-og-url=&quot;https://medium.com/creatrip/%EC%82%AC%EB%82%B4-ai-%EC%B1%97%EB%B4%87-%EC%84%9C%EB%B9%84%EC%8A%A4-%EA%B5%AC%EC%B6%95%ED%95%98%EA%B8%B0-a4fab54f898d&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/cXG4Bb/hyYjsbKt59/L8EppZK2q5C3iC0l5jcTa0/img.png?width=1200&amp;amp;height=568&amp;amp;face=0_0_1200_568&quot;&gt;&lt;a href=&quot;https://medium.com/creatrip/%EC%82%AC%EB%82%B4-ai-%EC%B1%97%EB%B4%87-%EC%84%9C%EB%B9%84%EC%8A%A4-%EA%B5%AC%EC%B6%95%ED%95%98%EA%B8%B0-a4fab54f898d&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://medium.com/creatrip/%EC%82%AC%EB%82%B4-ai-%EC%B1%97%EB%B4%87-%EC%84%9C%EB%B9%84%EC%8A%A4-%EA%B5%AC%EC%B6%95%ED%95%98%EA%B8%B0-a4fab54f898d&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/cXG4Bb/hyYjsbKt59/L8EppZK2q5C3iC0l5jcTa0/img.png?width=1200&amp;amp;height=568&amp;amp;face=0_0_1200_568');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;사내 AI 챗봇 서비스 구축하기&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;안녕하세요. 크리에이트립(Creatrip) FE 개발자 서종학입니다.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;medium.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>발표자료</category>
      <author>눅눅한 피부</author>
      <guid isPermaLink="true">https://nookpi.tistory.com/213</guid>
      <comments>https://nookpi.tistory.com/213#entry213comment</comments>
      <pubDate>Thu, 20 Feb 2025 12:29:18 +0900</pubDate>
    </item>
    <item>
      <title>5년차 프론트엔드 개발자의 2024년 회고</title>
      <link>https://nookpi.tistory.com/212</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;deepak-gupta-Ok8bWOnw634-unsplash.jpg&quot; data-origin-width=&quot;640&quot; data-origin-height=&quot;427&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bg4nzu/btsL4edJWHy/riPxvkGlLekbx6KIQtr5h0/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bg4nzu/btsL4edJWHy/riPxvkGlLekbx6KIQtr5h0/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bg4nzu/btsL4edJWHy/riPxvkGlLekbx6KIQtr5h0/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbg4nzu%2FbtsL4edJWHy%2FriPxvkGlLekbx6KIQtr5h0%2Fimg.jpg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;640&quot; height=&quot;427&quot; data-filename=&quot;deepak-gupta-Ok8bWOnw634-unsplash.jpg&quot; data-origin-width=&quot;640&quot; data-origin-height=&quot;427&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2023년 회고를 작성했던 기억이 엊그제 같은데 벌써 2025년의 첫 달도 훌쩍 지나갔다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2023년에는 내가 스스로 어떤 사람인지 좀 더 깊이 이해하고 내면의 성장을 이뤘다면 2024년에는 내면의 성장을 바탕으로 보다 즐겁고 의미 있게 한 해를 보냈다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;파트 리더&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;재작년 말, 프론트엔드 파트 리드를 맡게 되었다. 한 해를 돌아보며 나름대로 성공적인 파트 리더 업무 수행이었다고 자평하지만 그 과정에서의 고민과 갈등도 참 많았다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;구성원들의 동기부여를 위해 꾸준히 1:1 면담을 진행하며 이런저런 팀 단합 시간을 만들었고, 소통에서의 오해를 줄이고 합리적으로 커뮤니케이션을 하기 위해 신경을 많이 기울였다. 6개월 간격으로 리더십에 대한 익명 설문을 통해 내가 잘못된 방향으로 가고 있지는 않은지 돌아보았고, 나태해지지 않기 위해 노력했지만 그래도 1년 내내 같은 텐션으로 임하지는 못했던 것 같다. 어쨌든 파트원들과 함께 생산성을 개선하고 한 명의 이탈자도 만들지 않았던 부분은 성공이었다고 자평하고 싶다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;성장에 대한 욕구가 크고 주도적인 인원들이 모이는 스타트업의 특성상 구성원들에게 성장하고 있다는 감각을 주는 것이 유지에 매우 중요하다고 생각하는데, 구성원들의 성장을 위한 여러 스터디(리액트 소스 코드, 라이브러리 소스 코드 탐구, 기술 서적 함께 읽기)를 성공적으로 진행했던 것이 큰 도움이 되었다고 생각한다. 그 밖에도 정비 스프린트에서 구성원들이 자율성을 갖고 진행한 태스크들로 업무 성취감을 얻는다든지, 페어 프로그래밍을 통한 업무 유대감 증진 등도 도움이 되었을 것 같다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;KR 초과 달성&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;작년 프론트엔드 파트의 KR이었던 웹 바이탈 개선 목표를 초과달성하며 큰 효능감을 느꼈다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://nookpi.tistory.com/209&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://nookpi.tistory.com/209&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1738473212481&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;지난 1년간의 웹 바이탈 개선기 1편 - 다국어 동적 로드&quot; data-og-description=&quot;올해 회고를 쓰기 앞서 지난 1년간, 정확히는 지난 약 8개월간의 웹 바이탈 개선을 위한 작업들을 기록해보려고 한다.&amp;nbsp;먼저 결과부터 보자면 코어 웹 바이탈 기준으로도 많은 개선이 있었다.&amp;nbsp;&amp;nbsp;5&quot; data-og-host=&quot;nookpi.tistory.com&quot; data-og-source-url=&quot;https://nookpi.tistory.com/209&quot; data-og-url=&quot;https://nookpi.tistory.com/209&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/bilv5x/hyYcaIMFzZ/NhlMyz6vfQhWok6C0fuw0K/img.png?width=800&amp;amp;height=484&amp;amp;face=0_0_800_484,https://scrap.kakaocdn.net/dn/rCpfN/hyX7ZPJFph/8Z5FA342Jrf12CIjOrjqA0/img.png?width=800&amp;amp;height=484&amp;amp;face=0_0_800_484,https://scrap.kakaocdn.net/dn/zYKuM/hyYb6zCpgl/3MDBkggPlk1BOHjQU6acjK/img.png?width=1518&amp;amp;height=1088&amp;amp;face=0_0_1518_1088&quot;&gt;&lt;a href=&quot;https://nookpi.tistory.com/209&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://nookpi.tistory.com/209&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/bilv5x/hyYcaIMFzZ/NhlMyz6vfQhWok6C0fuw0K/img.png?width=800&amp;amp;height=484&amp;amp;face=0_0_800_484,https://scrap.kakaocdn.net/dn/rCpfN/hyX7ZPJFph/8Z5FA342Jrf12CIjOrjqA0/img.png?width=800&amp;amp;height=484&amp;amp;face=0_0_800_484,https://scrap.kakaocdn.net/dn/zYKuM/hyYb6zCpgl/3MDBkggPlk1BOHjQU6acjK/img.png?width=1518&amp;amp;height=1088&amp;amp;face=0_0_1518_1088');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;지난 1년간의 웹 바이탈 개선기 1편 - 다국어 동적 로드&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;올해 회고를 쓰기 앞서 지난 1년간, 정확히는 지난 약 8개월간의 웹 바이탈 개선을 위한 작업들을 기록해보려고 한다.&amp;nbsp;먼저 결과부터 보자면 코어 웹 바이탈 기준으로도 많은 개선이 있었다.&amp;nbsp;&amp;nbsp;5&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;nookpi.tistory.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기존 웹 바이탈이 워낙 오랫동안 좋지 않았기에 '이게 될까?' 싶은 부분들도 있었지만 여러 시도와 노력으로 드라마틱하게 개선을 할 수 있었다. 무엇보다도 즐거운 일은  관련 작업들(SWR 캐싱 등)이 단순 웹 바이탈 지표 개선에 머무르지 않고 실제 유입량의 증가와 유저 체감 속도에도 큰 영향을 주었다는 점이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;유저페이지 자체의 성능 향상과 이로 인한 고객 경험 개선이라는 측면에서 스스로에게도 매우 보람찬 일이었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;이력서/포트폴리오 정리&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://ahead-vanilla-49e.notion.site/Jonghak-Seo-1475b8aea39380889f81ea588df0d1c7&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://ahead-vanilla-49e.notion.site/Jonghak-Seo-1475b8aea39380889f81ea588df0d1c7&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1738473388879&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;서종학 (Jonghak Seo) | Notion&quot; data-og-description=&quot;포트폴리오&quot; data-og-host=&quot;ahead-vanilla-49e.notion.site&quot; data-og-source-url=&quot;https://ahead-vanilla-49e.notion.site/Jonghak-Seo-1475b8aea39380889f81ea588df0d1c7&quot; data-og-url=&quot;https://ahead-vanilla-49e.notion.site/Jonghak-Seo-1475b8aea39380889f81ea588df0d1c7&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/FMlPd/hyYb8RJLal/fJWOlLIE4c4eZBhOZlkni0/img.png?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630,https://scrap.kakaocdn.net/dn/bzYxyS/hyYb5HtzoA/BNtuOEvgjgKwL32XvK5olK/img.png?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630&quot;&gt;&lt;a href=&quot;https://ahead-vanilla-49e.notion.site/Jonghak-Seo-1475b8aea39380889f81ea588df0d1c7&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://ahead-vanilla-49e.notion.site/Jonghak-Seo-1475b8aea39380889f81ea588df0d1c7&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/FMlPd/hyYb8RJLal/fJWOlLIE4c4eZBhOZlkni0/img.png?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630,https://scrap.kakaocdn.net/dn/bzYxyS/hyYb5HtzoA/BNtuOEvgjgKwL32XvK5olK/img.png?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;서종학 (Jonghak Seo) | Notion&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;포트폴리오&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;ahead-vanilla-49e.notion.site&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://ahead-vanilla-49e.notion.site/Jonghak-Seo-1475b8aea3938087be04e874eb76d6e1?pvs=74&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://ahead-vanilla-49e.notion.site/Jonghak-Seo-1475b8aea3938087be04e874eb76d6e1&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1738473406085&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;서종학 (Jonghak Seo) 포트폴리오  | Notion&quot; data-og-description=&quot;상세한 경력 기술서는 이력서에서 확인해주세요.&quot; data-og-host=&quot;ahead-vanilla-49e.notion.site&quot; data-og-source-url=&quot;https://ahead-vanilla-49e.notion.site/Jonghak-Seo-1475b8aea3938087be04e874eb76d6e1?pvs=74&quot; data-og-url=&quot;https://ahead-vanilla-49e.notion.site/Jonghak-Seo-1475b8aea3938087be04e874eb76d6e1&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/fP96L/hyYcaaWzXL/B3xo7aNBqVTOPugRNbEKK0/img.png?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630,https://scrap.kakaocdn.net/dn/dnGFEP/hyX7TaUfqR/7chsdSCDkTtHD6Zn0qDf5k/img.png?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630&quot;&gt;&lt;a href=&quot;https://ahead-vanilla-49e.notion.site/Jonghak-Seo-1475b8aea3938087be04e874eb76d6e1?pvs=74&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://ahead-vanilla-49e.notion.site/Jonghak-Seo-1475b8aea3938087be04e874eb76d6e1?pvs=74&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/fP96L/hyYcaaWzXL/B3xo7aNBqVTOPugRNbEKK0/img.png?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630,https://scrap.kakaocdn.net/dn/dnGFEP/hyX7TaUfqR/7chsdSCDkTtHD6Zn0qDf5k/img.png?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;서종학 (Jonghak Seo) 포트폴리오 | Notion&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;상세한 경력 기술서는 이력서에서 확인해주세요.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;ahead-vanilla-49e.notion.site&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;올&amp;nbsp;한&amp;nbsp;해를&amp;nbsp;정리하면서&amp;nbsp;이력서와&amp;nbsp;포트폴리오를&amp;nbsp;한&amp;nbsp;번&amp;nbsp;정리했다.&amp;nbsp;웹&amp;nbsp;바이탈&amp;nbsp;개선&amp;nbsp;작업의&amp;nbsp;지표가&amp;nbsp;잘&amp;nbsp;나와서,&amp;nbsp;이력서를&amp;nbsp;정돈한&amp;nbsp;지&amp;nbsp;너무&amp;nbsp;오래되어서&amp;nbsp;한&amp;nbsp;해&amp;nbsp;회고를&amp;nbsp;겸하여&amp;nbsp;이번&amp;nbsp;기회에&amp;nbsp;다시&amp;nbsp;작성해보았다.&amp;nbsp;개인적으로는&amp;nbsp;만족스럽게&amp;nbsp;잘&amp;nbsp;작성한&amp;nbsp;것&amp;nbsp;같아&amp;nbsp;기쁘다.&amp;nbsp;:)&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;오픈소스&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;크롬 익스텐션 보일러플레이트 오픈소스는 상반기에 아키텍처를 갈아엎어 새로 만들다시피 한 이후로 하반기에는 주로 유지보수와 버그 대응 위주로 대응했다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Stars 2K를 달성하여 8월에 블로그 포스팅을 올렸는데 곧 3K가 될 것 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://jonghakseo.github.io/posts/until-2k-stars/&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://jonghakseo.github.io/posts/until-2k-stars/&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1738473738620&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;Open source to 2K stars.&quot; data-og-description=&quot;What I've learned from reaching 2000 Stars on GitHub.&quot; data-og-host=&quot;jonghakseo.github.io&quot; data-og-source-url=&quot;https://jonghakseo.github.io/posts/until-2k-stars/&quot; data-og-url=&quot;https://jonghakseo.github.io/posts/until-2k-stars/&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/edT7vg/hyX74ja9zS/kK0Rp9EzryCmc6iiiThkh0/img.png?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630,https://scrap.kakaocdn.net/dn/ddJ6XE/hyYclKjh1T/EpPAkOgkHp2qlp3gcIRfPk/img.png?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630&quot;&gt;&lt;a href=&quot;https://jonghakseo.github.io/posts/until-2k-stars/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://jonghakseo.github.io/posts/until-2k-stars/&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/edT7vg/hyX74ja9zS/kK0Rp9EzryCmc6iiiThkh0/img.png?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630,https://scrap.kakaocdn.net/dn/ddJ6XE/hyYclKjh1T/EpPAkOgkHp2qlp3gcIRfPk/img.png?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;Open source to 2K stars.&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;What I've learned from reaching 2000 Stars on GitHub.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;jonghakseo.github.io&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;9월에는 디스코드에서 기여해주시는 분들과 팀 미팅을 가지면서 앞으로의 발전 방향에 대해서 논의하는 시간도 가졌는데, 영어 울렁증으로 덜덜 떨면서 시작했으나 다들 제1언어가 영어가 아닌 유럽인들이어서 편하게 이야기를 나눌 수 있었다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;729&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/p7s2g/btsL4ZUtPa2/4u5hRAkG7mWwXWyy9asSgK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/p7s2g/btsL4ZUtPa2/4u5hRAkG7mWwXWyy9asSgK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/p7s2g/btsL4ZUtPa2/4u5hRAkG7mWwXWyy9asSgK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fp7s2g%2FbtsL4ZUtPa2%2F4u5hRAkG7mWwXWyy9asSgK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1280&quot; height=&quot;729&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;729&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;참고로 논의한 내용들은 아직 시작도 못 하고 있다... 올해에는 힘내서 하기로 한 내용들을 다 해봐야지.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;영어공부&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2024년 한 해 꾸준히 영어공부를 했는데 성과가 상당히 있었다. 앞서 언급한 유럽 개발자들과의 팀 미팅은 재작년만 해도 상상할 수 없는 일이었다. 나는 예전부터 영어를 잘하지 못했는데, 개발을 본격적으로 시작하기 전에는 영어 때문에 개발을 제대로 할 수 있을지 고민할 정도였다. 그러니 더더욱 회화는 한 마디도 하지 못했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;재작년 중순, 미국에 있는 스타트업 팀과 협업할 수 있는 좋은 기회가 있었고, 좋은 제안도 받았지만 언어 장벽과 내가 원하는 도메인의 업무가 아니라는 이유로 거절했다. 그때,&amp;nbsp;개발자로서&amp;nbsp;앞으로&amp;nbsp;좋은&amp;nbsp;기회가&amp;nbsp;왔을&amp;nbsp;때&amp;nbsp;영어&amp;nbsp;때문에&amp;nbsp;발목이&amp;nbsp;잡히면&amp;nbsp;안&amp;nbsp;되겠다는&amp;nbsp;생각이&amp;nbsp;들어&amp;nbsp;재작년&amp;nbsp;말에&amp;nbsp;영어&amp;nbsp;과외를&amp;nbsp;시작했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;약 10개월 동안 영어 과외를 하면서 열심히 하거나 많은 시간을 투자하지는 않았지만 꾸준히 했던 것 같고, 영어 회화 실력이 늘어가는 것을 느끼면서 더욱 재미있게 공부했던 것 같다. 작년&amp;nbsp;말부터는&amp;nbsp;링글이라는&amp;nbsp;원어민&amp;nbsp;대화&amp;nbsp;플랫폼을&amp;nbsp;통해&amp;nbsp;유럽과&amp;nbsp;미국&amp;nbsp;등의&amp;nbsp;튜터들과&amp;nbsp;대화를&amp;nbsp;하고&amp;nbsp;있는데,&amp;nbsp;꽤&amp;nbsp;재미있게&amp;nbsp;잘하고&amp;nbsp;있다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1년 넘게 영어 공부를 하면서 실력도 많이 늘었지만 (기존에 워낙 못했으니), 무엇보다도 영어 회화에 대한 두려움이나 망설임이 없어진 것이 가장 큰 자산인 것 같다. 올해도&amp;nbsp;영어&amp;nbsp;공부를&amp;nbsp;열심히는&amp;nbsp;아니어도&amp;nbsp;최소한&amp;nbsp;꾸준히&amp;nbsp;계속할&amp;nbsp;생각이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2024 총평&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;5년차 프론트엔드 개발자라는 타이틀은 참 무서운 것 같다. 3년차, 4년차에는 그렇게 생각하지 않았던 것 같은데... 햇수로 5년이라는 시간 동안 일을 했다면 내가 지금 아는 것, 할 수 있는 것이 높은 수준에 해당하는지 의구심이 든다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이력서와 포트폴리오를 다듬으면서 한 해를 돌아보니 나름 뭔가 많이 그리고 잘한 것 같은데 내가 느끼는 내 성장 곡선은 예전만큼 가파르지 않은 것 같다. 솔직히... 더 많이, 잘할 수 있었는데 나 스스로 '이 정도면 잘했는데?'라는 생각에 빠져 다소 게을렀다고 생각한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;올해는 이런 게으름을 걷어내고 작년의 성장 곡선 이상의 각도를 만드는 한 해가 되도록 노력해봐야겠다.&lt;/p&gt;</description>
      <category>잡담</category>
      <category>2024 회고</category>
      <category>회고</category>
      <author>눅눅한 피부</author>
      <guid isPermaLink="true">https://nookpi.tistory.com/212</guid>
      <comments>https://nookpi.tistory.com/212#entry212comment</comments>
      <pubDate>Sun, 2 Feb 2025 14:38:43 +0900</pubDate>
    </item>
    <item>
      <title>지난 1년간의 웹 바이탈 개선기 3편 - SWR 적용</title>
      <link>https://nookpi.tistory.com/211</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;지난 2편의 글에 이어서 웹&amp;nbsp;바이탈&amp;nbsp;개선을&amp;nbsp;위해&amp;nbsp;했던 세 번째 작업에&amp;nbsp;대해&amp;nbsp;이야기해보려고&amp;nbsp;한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://nookpi.tistory.com/209&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://nookpi.tistory.com/209&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1732112873478&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;지난 1년간의 웹 바이탈 개선기 1편 - 다국어 동적 로드&quot; data-og-description=&quot;올해 회고를 쓰기 앞서 지난 1년간, 정확히는 지난 약 8개월간의 웹 바이탈 개선을 위한 작업들을 기록해보려고 한다.&amp;nbsp;&amp;nbsp;먼저 결과부터 보자면 많은 개선이 있었다.&amp;nbsp;&amp;nbsp;5월 초, 5%까지 내려갔던 Good&quot; data-og-host=&quot;nookpi.tistory.com&quot; data-og-source-url=&quot;https://nookpi.tistory.com/209&quot; data-og-url=&quot;https://nookpi.tistory.com/209&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/QnnGQ/hyXDiOeufu/tRMhPXQ0hl8BUkL26Y5CZk/img.png?width=800&amp;amp;height=484&amp;amp;face=0_0_800_484,https://scrap.kakaocdn.net/dn/dGABcO/hyXzHoCSz3/8kDJuJDDk7HB4qqTLoyb8K/img.png?width=800&amp;amp;height=484&amp;amp;face=0_0_800_484,https://scrap.kakaocdn.net/dn/bavBIb/hyXDbVSKmJ/tSE5GX7KzVddhO37RnoGFk/img.png?width=1062&amp;amp;height=1634&amp;amp;face=0_0_1062_1634&quot;&gt;&lt;a href=&quot;https://nookpi.tistory.com/209&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://nookpi.tistory.com/209&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/QnnGQ/hyXDiOeufu/tRMhPXQ0hl8BUkL26Y5CZk/img.png?width=800&amp;amp;height=484&amp;amp;face=0_0_800_484,https://scrap.kakaocdn.net/dn/dGABcO/hyXzHoCSz3/8kDJuJDDk7HB4qqTLoyb8K/img.png?width=800&amp;amp;height=484&amp;amp;face=0_0_800_484,https://scrap.kakaocdn.net/dn/bavBIb/hyXDbVSKmJ/tSE5GX7KzVddhO37RnoGFk/img.png?width=1062&amp;amp;height=1634&amp;amp;face=0_0_1062_1634');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;지난 1년간의 웹 바이탈 개선기 1편 - 다국어 동적 로드&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;올해 회고를 쓰기 앞서 지난 1년간, 정확히는 지난 약 8개월간의 웹 바이탈 개선을 위한 작업들을 기록해보려고 한다.&amp;nbsp;&amp;nbsp;먼저 결과부터 보자면 많은 개선이 있었다.&amp;nbsp;&amp;nbsp;5월 초, 5%까지 내려갔던 Good&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;nookpi.tistory.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://nookpi.tistory.com/210&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://nookpi.tistory.com/210&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1732112881296&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;지난 1년간의 웹 바이탈 개선기 2편 - SSG 제거, SSR 전환&quot; data-og-description=&quot;지난 1편에 이어 웹 바이탈 개선을 위해 했던 작업에 대해 이야기해보려고 한다.&amp;nbsp;2. SSG&amp;nbsp;제거,&amp;nbsp;SSR&amp;nbsp;전환두 번째 작업은 Nextjs 환경에서의 기존의 SSG(ISR)를 모두 제거하고 SSR로 전환한 것이다.언뜻 &quot; data-og-host=&quot;nookpi.tistory.com&quot; data-og-source-url=&quot;https://nookpi.tistory.com/210&quot; data-og-url=&quot;https://nookpi.tistory.com/210&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/J2M0h/hyXDkZAIF3/eBK0fYJTdcURyTKE5OUTxk/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/cRvYrT/hyXzUO3cRF/7JcC0TCytYsqV6mWxMRVUK/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800&quot;&gt;&lt;a href=&quot;https://nookpi.tistory.com/210&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://nookpi.tistory.com/210&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/J2M0h/hyXDkZAIF3/eBK0fYJTdcURyTKE5OUTxk/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/cRvYrT/hyXzUO3cRF/7JcC0TCytYsqV6mWxMRVUK/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;지난 1년간의 웹 바이탈 개선기 2편 - SSG 제거, SSR 전환&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;지난 1편에 이어 웹 바이탈 개선을 위해 했던 작업에 대해 이야기해보려고 한다.&amp;nbsp;2. SSG&amp;nbsp;제거,&amp;nbsp;SSR&amp;nbsp;전환두 번째 작업은 Nextjs 환경에서의 기존의 SSG(ISR)를 모두 제거하고 SSR로 전환한 것이다.언뜻&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;nookpi.tistory.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3. SWR 적용&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://datatracker.ietf.org/doc/html/rfc5861&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://datatracker.ietf.org/doc/html/rfc5861&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1732112930555&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;RFC 5861: HTTP Cache-Control Extensions for Stale Content&quot; data-og-description=&quot;This document defines two independent HTTP Cache-Control extensions that allow control over the use of stale responses by caches. This document is not an Internet Standards Track specification; it is published for informational purposes.&quot; data-og-host=&quot;datatracker.ietf.org&quot; data-og-source-url=&quot;https://datatracker.ietf.org/doc/html/rfc5861&quot; data-og-url=&quot;https://datatracker.ietf.org/doc/html/rfc5861&quot; data-og-image=&quot;&quot;&gt;&lt;a href=&quot;https://datatracker.ietf.org/doc/html/rfc5861&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://datatracker.ietf.org/doc/html/rfc5861&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url();&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;RFC 5861: HTTP Cache-Control Extensions for Stale Content&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;This document defines two independent HTTP Cache-Control extensions that allow control over the use of stale responses by caches. This document is not an Internet Standards Track specification; it is published for informational purposes.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;datatracker.ietf.org&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;먼저 SWR 이란 stale-while-revalidate의 약자로 프론트엔드 생태계에는 vercel에서 만든 동명의 라이브러리가 있다. 참고로 같은 SWR이 맞다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://swr.vercel.app/ko&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://swr.vercel.app/ko&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1732112962302&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;데이터 가져오기를 위한 React Hooks &amp;ndash; SWR&quot; data-og-description=&quot;SWR is a React Hooks library for data fetching. SWR first returns the data from cache (stale), then sends the fetch request (revalidate), and finally comes with the up-to-date data again.&quot; data-og-host=&quot;swr.vercel.app&quot; data-og-source-url=&quot;https://swr.vercel.app/ko&quot; data-og-url=&quot;https://swr.vercel.app/ko&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/GMIT6/hyXC761dNQ/OYIYYmU9onhguCYffqYn01/img.png?width=1000&amp;amp;height=500&amp;amp;face=0_0_1000_500,https://scrap.kakaocdn.net/dn/zc1N1/hyXzOH4txR/GZs0R8k8zz9Wk406hjHCk1/img.png?width=1000&amp;amp;height=500&amp;amp;face=0_0_1000_500&quot;&gt;&lt;a href=&quot;https://swr.vercel.app/ko&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://swr.vercel.app/ko&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/GMIT6/hyXC761dNQ/OYIYYmU9onhguCYffqYn01/img.png?width=1000&amp;amp;height=500&amp;amp;face=0_0_1000_500,https://scrap.kakaocdn.net/dn/zc1N1/hyXzOH4txR/GZs0R8k8zz9Wk406hjHCk1/img.png?width=1000&amp;amp;height=500&amp;amp;face=0_0_1000_500');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;데이터 가져오기를 위한 React Hooks &amp;ndash; SWR&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;SWR is a React Hooks library for data fetching. SWR first returns the data from cache (stale), then sends the fetch request (revalidate), and finally comes with the up-to-date data again.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;swr.vercel.app&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1266&quot; data-origin-height=&quot;616&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Li88u/btsKQf55FGW/SwQ8uBqUcF7qZhagr0JpPK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Li88u/btsKQf55FGW/SwQ8uBqUcF7qZhagr0JpPK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Li88u/btsKQf55FGW/SwQ8uBqUcF7qZhagr0JpPK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FLi88u%2FbtsKQf55FGW%2FSwQ8uBqUcF7qZhagr0JpPK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1266&quot; height=&quot;616&quot; data-origin-width=&quot;1266&quot; data-origin-height=&quot;616&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;HTTP [RFC2616]는 캐시가 &quot;요청에 적합한 가장 최신의 응답으로 응답해야 한다&quot;고 요구하지만, &quot;신중하게 고려된 상황&quot;에서는 오래된 응답을 반환하는 것이 허용된다고 명시하고 있습니다. 이 문서는 이러한 제어를 가능하게 하는 두 개의 독립적인 Cache-Control 확장, stale-if-error와 stale-while-revalidate를 정의합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;stale-if-error HTTP Cache-Control 확장은 오류가 발생했을 때, 예를 들어 500 Internal Server Error, 네트워크 세그먼트, 또는 DNS 실패가 발생했을 때, &quot;심각한&quot; 오류를 반환하는 대신 오래된 응답을 반환할 수 있도록 캐시에게 허용합니다. 이는 가용성을 향상시킵니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;stale-while-revalidate HTTP Cache-Control 확장은 캐시가 백그라운드에서 응답을 재검증하는 동안 즉시 오래된 응답을 반환할 수 있도록 하여, 클라이언트로부터 네트워크 및 서버의 지연을 숨길 수 있게 합니다.&lt;/b&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;어렵게 생각할 필요는 없고, SWR은 그냥 캐싱 전략이라고 이해하면 된다. 데이터를 최신화 하는 동안, 일단 들어온 요청에 대해 먼저 오래된 데이터를 주는 패턴이다. 같은 말로 '데이터를 재검증하는 동안에, 예전 데이터가 있으면 일단 그걸 준다' 는 말이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://developer.mozilla.org/ko/docs/Web/HTTP/Headers/Cache-Control#%ED%99%95%EC%9E%A5_cache-control_%EB%94%94%EB%A0%89%ED%8B%B0%EB%B8%8C&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://developer.mozilla.org/ko/docs/Web/HTTP/Headers/Cache-Control#%ED%99%95%EC%9E%A5_cache-control_%EB%94%94%EB%A0%89%ED%8B%B0%EB%B8%8C&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1162&quot; data-origin-height=&quot;308&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cDoXrK/btsKPSchxoR/Jnw0EvOU6RDh2owFNSLP7K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cDoXrK/btsKPSchxoR/Jnw0EvOU6RDh2owFNSLP7K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cDoXrK/btsKPSchxoR/Jnw0EvOU6RDh2owFNSLP7K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcDoXrK%2FbtsKPSchxoR%2FJnw0EvOU6RDh2owFNSLP7K%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1162&quot; height=&quot;308&quot; data-origin-width=&quot;1162&quot; data-origin-height=&quot;308&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1624&quot; data-origin-height=&quot;870&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/kEvVM/btsKRfjRTuw/whQGjzKALPAO6kNLCoeIJK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/kEvVM/btsKRfjRTuw/whQGjzKALPAO6kNLCoeIJK/img.png&quot; data-alt=&quot;개발새발&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/kEvVM/btsKRfjRTuw/whQGjzKALPAO6kNLCoeIJK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FkEvVM%2FbtsKRfjRTuw%2FwhQGjzKALPAO6kNLCoeIJK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1624&quot; height=&quot;870&quot; data-origin-width=&quot;1624&quot; data-origin-height=&quot;870&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;개발새발&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이전 편에서 CDN 캐싱을 위해 SSR 응답에서 Cache-Control 헤더에 s-maxage를 넣었듯이, SWR을 사용하기 위해서는 Cache-Control 헤더에 stale-while-revalidate 헤더를 넣고 시간을 적어주면 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1732113267465&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;export const setCDNCacheHeader = (
  res: ServerResponse,
  sMaxAge: number,
  staleWhileRevalidate?: number,
) =&amp;gt; {
  // 1초 이내 요청은 캐시를 사용하고, 1초 이후에는 CDN 캐시를 사용한다.
  // 디스크 캐싱으로 발생할 수 있는 문제를 회피하고 컨텐츠의 최신화 여부를 CDN에 위임하는 방식.
  const cacheValue = [];
  cacheValue.push('public');
  cacheValue.push(`max-age=1`);
  cacheValue.push(`s-maxage=${sMaxAge}`);
  if (staleWhileRevalidate) {
    cacheValue.push(`stale-while-revalidate=${staleWhileRevalidate}`);
  }
  res.setHeader('Cache-Control', cacheValue.join(', '));
};

//...

// getServerSideProps 내부
setCDNCacheHeader(
  res,
  REVALIDATE_INTERVAL_SECONDS.THIRTY_MINUTES, // 30분
  STALE_WHILE_REVALIDATE_SECONDS.ONE_WEEK, // 1주일
);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 코드는 다음과 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. 30분 이내의  페이지는 최신으로 간주하여 CDN에서 캐싱된 페이지를 제공한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. 30분 이내의 페이지가 없다면, 1주일 이내의 페이지를 반환하고 내부적으로 새 페이지를 갱신한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. 1주일 이내의 페이지가 없다면 새 페이지를 생성하길 기다렸다가 반환한다. (생성된 페이지는 캐싱)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;SWR 헤더 적용은 단일 작업으로는 유저 경험에 가장 큰 임팩트를 준 작업이었다. 그야말로 가성비 최강! 9월에 프로덕션에 머지되었는데 이 시기 이후로 프로덕트의 전반적인 체감 속도가 크게 개선되었다. 실제 유저 경험에서도 응답속도가 개선되었다는 의견이 많아 개인적으로 매우 만족스러운 작업이었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>공부내용 공유하기</category>
      <category>core web vitals</category>
      <author>눅눅한 피부</author>
      <guid isPermaLink="true">https://nookpi.tistory.com/211</guid>
      <comments>https://nookpi.tistory.com/211#entry211comment</comments>
      <pubDate>Wed, 20 Nov 2024 23:42:58 +0900</pubDate>
    </item>
    <item>
      <title>지난 1년간의 웹 바이탈 개선기 2편 - SSG 제거, SSR 전환</title>
      <link>https://nookpi.tistory.com/210</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://nookpi.tistory.com/209&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;지난 1편&lt;/a&gt;에 이어 웹 바이탈 개선을 위해 했던 작업에 대해 이야기해보려고 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2. SSG&amp;nbsp;제거,&amp;nbsp;SSR&amp;nbsp;전환&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;두 번째 작업은 Next.js 환경에서의 기존의 SSG(ISR)를 모두 제거하고 SSR로 전환한 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;언뜻 생각하면 SSG는 빌드 시점에 생성된 HTML이고, SSR은 유저의 요청이 있을 때 렌더링을 해서 제공하기 때문에 SSG가 SSR보다 더 빠를 수 없는데 무슨 소리지? 라는 생각이 든다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 작업의 배경에 대해 설명하자면 프로덕트의 배포 환경에 대한 부연 설명이 좀 필요한데, 간단하게 말해 프론트엔드 서버는 esc에 로드밸런싱이 되는 컨테이너들로 떠 있는 상황이었다. 이러한 환경에서의 SSG는 치명적인 문제가 있었으니... 유저의 매 요청이 ELB를 거쳐 각 인스턴스에 배분되다보니 우리가 기대한 ISR의 동작이 제대로 이뤄지지 않았다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그럴 수 밖에 없는게 캐싱 단위가 Next.js 인데 App이 여러개 떠있으니 캐시가 제대로 적중할리가...&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 문제를 해결하기 위해 별의별 트리키한 해결책(노드 클러스터링으로 한 인스턴스에 여러 앱을 띄우기, shared storage를 만들어서 SSG 자원을 각 앱에서 공유하기)을 고민하던 중, 불현듯 깨달음을 얻었으니,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&quot;그냥 더 앞단(CDN)에서 캐싱하고 인스턴스에서는 SSR로 주면 되는거 아녀?!&quot;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그렇다. 어차피 본질이 캐싱이라면 Next.js 내부에서 하려고 눈물의 차력쇼를 할 필요가 없던 거였던 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;SSG를 싹 다 날리고 cloudfront 캐싱을 위한 캐시 헤더(s-maxage)를 붙이고 배포를 하니... 아주 신세계였다.&lt;/p&gt;
&lt;pre id=&quot;code_1732111720726&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;export const setCDNCacheHeader = (res: ServerResponse, maxAge: number) =&amp;gt; {
  // 1초 이내 요청은 로컬 캐시를 사용하고, 1초 이후에는 CDN 캐시를 사용한다.
  // 로컬 캐싱으로 발생할 수 있는 문제를 회피하고 컨텐츠의 최신화 여부를 CDN에 위임하는 방식.
  res.setHeader('Cache-Control', `public, max-age=1, s-maxage=${sMaxAge}`);
};

//...

export const getServerSideProps: GetServerSideProps = async ({ res }) =&amp;gt; {
  //...
  setCDNCacheHeader(res, 60); // 1분 동안 cloudfront에 캐싱된다.
  //...
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 작업을 하면서 깨달음을 하나 얻었는데, 문제에 대한 해결책을 눈 앞의 틀에만 갇혀서 생각하지 않고 좀 더 거시적으로 바라봐야겠다는 것이었다. 캐싱 레이어를 CDN으로 옮기는 비교적 간단한 작업으로 유저들에게 많은 이점을 줄 수 있었는데, SSG의 최적화 자체에만 너무 매몰되어 있었던 것 같아 반성을 많이 했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>공부내용 공유하기</category>
      <category>core web vitals</category>
      <author>눅눅한 피부</author>
      <guid isPermaLink="true">https://nookpi.tistory.com/210</guid>
      <comments>https://nookpi.tistory.com/210#entry210comment</comments>
      <pubDate>Wed, 20 Nov 2024 23:25:50 +0900</pubDate>
    </item>
    <item>
      <title>지난 1년간의 웹 바이탈 개선기 1편 - 다국어 동적 로드</title>
      <link>https://nookpi.tistory.com/209</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;올해 회고를 쓰기 앞서 지난 1년간, 정확히는 지난 약 8개월간의 웹 바이탈 개선을 위한 작업들을 기록해보려고 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;먼저 &lt;/span&gt;결과부터 보자면 코어 웹 바이탈 기준으로도 많은 개선이 있었다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2024-11-20 오후 10.36.39.png&quot; data-origin-width=&quot;1576&quot; data-origin-height=&quot;954&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/w8vey/btsKPSpJwma/U8tWSd6j6b9OeFzPLuAKjK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/w8vey/btsKPSpJwma/U8tWSd6j6b9OeFzPLuAKjK/img.png&quot; data-alt=&quot;드라마틱하게 상승&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/w8vey/btsKPSpJwma/U8tWSd6j6b9OeFzPLuAKjK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fw8vey%2FbtsKPSpJwma%2FU8tWSd6j6b9OeFzPLuAKjK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1576&quot; height=&quot;954&quot; data-filename=&quot;스크린샷 2024-11-20 오후 10.36.39.png&quot; data-origin-width=&quot;1576&quot; data-origin-height=&quot;954&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;드라마틱하게 상승&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2024-11-20 오후 10.34.26.png&quot; data-origin-width=&quot;630&quot; data-origin-height=&quot;256&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Gdnxq/btsKQM95u7J/wazZzV3tE8tbcTt5vpfHr1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Gdnxq/btsKQM95u7J/wazZzV3tE8tbcTt5vpfHr1/img.png&quot; data-alt=&quot;이랬던 아이가&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Gdnxq/btsKQM95u7J/wazZzV3tE8tbcTt5vpfHr1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FGdnxq%2FbtsKQM95u7J%2FwazZzV3tE8tbcTt5vpfHr1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;500&quot; height=&quot;203&quot; data-filename=&quot;스크린샷 2024-11-20 오후 10.34.26.png&quot; data-origin-width=&quot;630&quot; data-origin-height=&quot;256&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;이랬던 아이가&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2024-11-20 오후 10.35.36.png&quot; data-origin-width=&quot;396&quot; data-origin-height=&quot;180&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/by5Pjy/btsKRkk1D6s/s1tqtfkAcjPPbUXwPWsBYk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/by5Pjy/btsKRkk1D6s/s1tqtfkAcjPPbUXwPWsBYk/img.png&quot; data-alt=&quot;이렇게...&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/by5Pjy/btsKRkk1D6s/s1tqtfkAcjPPbUXwPWsBYk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fby5Pjy%2FbtsKRkk1D6s%2Fs1tqtfkAcjPPbUXwPWsBYk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;500&quot; height=&quot;227&quot; data-filename=&quot;스크린샷 2024-11-20 오후 10.35.36.png&quot; data-origin-width=&quot;396&quot; data-origin-height=&quot;180&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;이렇게...&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;5월 초, 5%까지 내려갔던 Good 비율은 11월인 현재 80~90%를 넘나들며 준수한 수준으로 유지되고 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 지표는 모바일 기준인데, 모바일 서비스 사용자가 많은 우리 서비스의 특성과 모바일의 점수 기준이 더 짜다는 점을 고려하여 모바일 지표만 고려하여 대응하고 있다. 물론 당연히 데스크탑 지표는 더 잘 나온다. (Good 비중이 95% 수준)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그렇다면 이런 지표 향상을 위해 기울인 나름의 노력들은 무엇이 있을까?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1. i18n 데이터 동적 로드&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;코어 웹 바이탈 개선을 위해 했던 작업 중에서도 TBT(INP)를 줄이는데 가장 큰 기여를 한 태스크라고 생각한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;구조적으로 App Router 전환이 어려운 지금의 서비스 구조에서 나름의 대응으로 메인 스레드의 병목을 완화한 태스크였다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기존 구현에서는 다국어를 지원하기  위해 특정 언어에 해당하는 모든 다국어 파일을 한 번에 불러오는 방식을 사용하고 있었다. TBT 개선을 위해 하이드레이션 과정에서의 메인스레드 병목을 찾았고, 해당 파일의 큰 용량으로 인한 JSON.parse, decodeURLComponent 동작이 생각보다 큰 영향을 주고 있음을 알게 되었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1232&quot; data-origin-height=&quot;264&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/baMOsw/btsKPBhsuo9/gXvmsMnrX4iCCCTfDU6OAk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/baMOsw/btsKPBhsuo9/gXvmsMnrX4iCCCTfDU6OAk/img.png&quot; data-alt=&quot;실제 퍼포먼스 탭에서 확인 결과 단순히&amp;amp;nbsp; JSON.parse 만으로도 약 5ms의 리소스가 든다는 것을 알 수 있다.&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/baMOsw/btsKPBhsuo9/gXvmsMnrX4iCCCTfDU6OAk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbaMOsw%2FbtsKPBhsuo9%2FgXvmsMnrX4iCCCTfDU6OAk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1232&quot; height=&quot;264&quot; data-origin-width=&quot;1232&quot; data-origin-height=&quot;264&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;실제 퍼포먼스 탭에서 확인 결과 단순히&amp;nbsp; JSON.parse 만으로도 약 5ms의 리소스가 든다는 것을 알 수 있다.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이때 문제 분석과 해결을 위한 가설 수립을 위해 스레드에 끄적인 내용들이 있는데 대략 이런 식이다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2024-11-20 오후 10.46.11.png&quot; data-origin-width=&quot;996&quot; data-origin-height=&quot;1556&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/RCfb7/btsKPVGLo5i/hlmZf9GUw7oBmQPXN3Qyk1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/RCfb7/btsKPVGLo5i/hlmZf9GUw7oBmQPXN3Qyk1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/RCfb7/btsKPVGLo5i/hlmZf9GUw7oBmQPXN3Qyk1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FRCfb7%2FbtsKPVGLo5i%2FhlmZf9GUw7oBmQPXN3Qyk1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;996&quot; height=&quot;1556&quot; data-filename=&quot;스크린샷 2024-11-20 오후 10.46.11.png&quot; data-origin-width=&quot;996&quot; data-origin-height=&quot;1556&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2024-11-20 오후 10.47.51.png&quot; data-origin-width=&quot;1072&quot; data-origin-height=&quot;1700&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cPnt91/btsKQrLULm1/MMWuWMwtOtdSY9nKP9UBd0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cPnt91/btsKQrLULm1/MMWuWMwtOtdSY9nKP9UBd0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cPnt91/btsKQrLULm1/MMWuWMwtOtdSY9nKP9UBd0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcPnt91%2FbtsKQrLULm1%2FMMWuWMwtOtdSY9nKP9UBd0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1072&quot; height=&quot;1700&quot; data-filename=&quot;스크린샷 2024-11-20 오후 10.47.51.png&quot; data-origin-width=&quot;1072&quot; data-origin-height=&quot;1700&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2024-11-20 오후 10.48.25.png&quot; data-origin-width=&quot;1068&quot; data-origin-height=&quot;1528&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/qtfjG/btsKRkrL9TJ/PTraOEIJK3Dck3KJk0DqW0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/qtfjG/btsKRkrL9TJ/PTraOEIJK3Dck3KJk0DqW0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/qtfjG/btsKRkrL9TJ/PTraOEIJK3Dck3KJk0DqW0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FqtfjG%2FbtsKRkrL9TJ%2FPTraOEIJK3Dck3KJk0DqW0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1068&quot; height=&quot;1528&quot; data-filename=&quot;스크린샷 2024-11-20 오후 10.48.25.png&quot; data-origin-width=&quot;1068&quot; data-origin-height=&quot;1528&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2024-11-20 오후 10.48.49.png&quot; data-origin-width=&quot;1062&quot; data-origin-height=&quot;1634&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cUKcQt/btsKQ5Iqr74/iDYb0cSN3A0TbdRwTcXK1K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cUKcQt/btsKQ5Iqr74/iDYb0cSN3A0TbdRwTcXK1K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cUKcQt/btsKQ5Iqr74/iDYb0cSN3A0TbdRwTcXK1K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcUKcQt%2FbtsKQ5Iqr74%2FiDYb0cSN3A0TbdRwTcXK1K%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1062&quot; height=&quot;1634&quot; data-filename=&quot;스크린샷 2024-11-20 오후 10.48.49.png&quot; data-origin-width=&quot;1062&quot; data-origin-height=&quot;1634&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해결을 위한 구현 컨셉을 대략적으로 설계했고&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1260&quot; data-origin-height=&quot;1056&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cpTcyh/btsKPzqGM01/ojBbptQsewtDaoLy1xGW41/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cpTcyh/btsKPzqGM01/ojBbptQsewtDaoLy1xGW41/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cpTcyh/btsKPzqGM01/ojBbptQsewtDaoLy1xGW41/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcpTcyh%2FbtsKPzqGM01%2FojBbptQsewtDaoLy1xGW41%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;561&quot; height=&quot;470&quot; data-origin-width=&quot;1260&quot; data-origin-height=&quot;1056&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1518&quot; data-origin-height=&quot;1088&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/deeoyX/btsKP9EHjDk/lo60M3ylWuIbnlQwkqX9gK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/deeoyX/btsKP9EHjDk/lo60M3ylWuIbnlQwkqX9gK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/deeoyX/btsKP9EHjDk/lo60M3ylWuIbnlQwkqX9gK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdeeoyX%2FbtsKP9EHjDk%2Flo60M3ylWuIbnlQwkqX9gK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1518&quot; height=&quot;1088&quot; data-origin-width=&quot;1518&quot; data-origin-height=&quot;1088&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;SSR/SSG 단계에서 렌더링에 사용되는 i18n key를 체크하여 해당 키값에 해당하는 값만 내려주도록 개선했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;나머지 바로 사용되지 않는 다국어 데이터는 하이드레이션 이후 천천히 가져온다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 작업을 하면서 두 가지 효과가 있었는데, 하나는 하이드레이션 자체에 대한 부하를 줄인 것,&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2024-11-20 오후 10.49.28.png&quot; data-origin-width=&quot;980&quot; data-origin-height=&quot;462&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bBKBsD/btsKPeGJUYJ/zVeXidllT3ijIcUbtTGZ20/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bBKBsD/btsKPeGJUYJ/zVeXidllT3ijIcUbtTGZ20/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bBKBsD/btsKPeGJUYJ/zVeXidllT3ijIcUbtTGZ20/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbBKBsD%2FbtsKPeGJUYJ%2FzVeXidllT3ijIcUbtTGZ20%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;980&quot; height=&quot;462&quot; data-filename=&quot;스크린샷 2024-11-20 오후 10.49.28.png&quot; data-origin-width=&quot;980&quot; data-origin-height=&quot;462&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또 하나는 네트워크 페이로드에 불필요한 다국어가 실리지 않게 되면서 첫 html의 용량이 비약적으로 다이어트가 되었다는 점이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;TBT뿐만 아니라 전반적인 지표에 긍정적인 영향을 줬던 작업이라고 생각한다.&lt;/p&gt;</description>
      <category>공부내용 공유하기</category>
      <category>core web vitals</category>
      <author>눅눅한 피부</author>
      <guid isPermaLink="true">https://nookpi.tistory.com/209</guid>
      <comments>https://nookpi.tistory.com/209#entry209comment</comments>
      <pubDate>Wed, 20 Nov 2024 22:58:17 +0900</pubDate>
    </item>
    <item>
      <title>모노레포에서 HMR 지원하기</title>
      <link>https://nookpi.tistory.com/208</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;우리 회사의 프론트엔드 레포는 모노레포 환경으로 동작하고 있다. 작년 9월쯤 터보레포 기반의 모노레포로 전환했고, 올해 3월에 pnpm을 적용했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;모노레포를 적용하면서 패키지로 추출한 ui, animation 의 경우 해당 패키지의 진입점이 빌드 후 결과물로 명시되어 HMR 동작에 어려움을 겪게 되었다. 사실 일반적인 개발 환경에서는 문제가 없지만, ui 패키지 등을 수정하면서 작업해야 하는 Storybook과 같은 환경에서는 치명적이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어 다음과 같은 경우를 생각해보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. Storybook - dev 실행&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. ui 패키지의 Button 스타일 변경&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. ui 패키지 re-build&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;4. Storybook 에서 ui 패키지의 변경사항 감지&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;5. Full Reload&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;vite의 react plugin 내부에 꼭꼭 숨어있는, 보통은 캡슐화되어 숨겨져있는 HMR 기능의 동작은 대략적으로 다음과 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. 모듈의 의존관계를 파악&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. 모듈의 의존성 트리를 조회하면서 변경사항을 감지하고 외부에 알리는 HMR 스크립트를 inject&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. 특정 모듈에 변경사항이 생기면 HMR 스크립트에서 감지 후 해당 모듈만 리로드 + 캐시 무효화&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2번 항목에서 외부 의존성에 해당하는 모듈인지 내부 소스코드인지 판별 후 inject를 하게 되는데, 문제는 모노레포 환경에서는 특정 패키지가 외부 의존성인지 아닌지를 꼼꼼하게 발라낼 정도로 해당 로직이 똑똑하지 않다는 점이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;이 문제를 빠르게 해결할 수 있는 방법이 무엇일까?&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;개발 환경에서는 외부 모듈이 아닌 내부 의존성임을 HMR 로직에 알려주면 된다. 그걸 어떻게?&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1731729952706&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import { type AliasOptions, defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
import path from 'node:path';

const isDev = process.env.NODE_ENV === 'development';

const devAliases = {
  // 개발 환경에서 패키지들의 HMR을 지원하기 위해 로컬 경로를 통해 참조
  '@ui': path.resolve(__dirname, '../../packages/ui/index'),
  '@icon': path.resolve(__dirname, '../../packages/icon/index'),
  '@style': path.resolve(__dirname, '../../packages/style/index'),
} satisfies AliasOptions;

export default defineConfig({
  plugins: [react()],
  resolve: {
    alias: isDev ? devAliases : undefined,
  },
});&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;심플하게 이렇게 알려주면 된다!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예시에서는 vite의 &lt;a href=&quot;https://ko.vite.dev/config/shared-options#resolve-alias&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;resolve.alias&lt;/a&gt; 를 사용했지만 webpack 등 다른 설정 환경에서도 유효한 해결책이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;로&lt;/b&gt;&lt;b&gt;컬 경로를 통해 참조할때 명시적으로 index.ts 등의 진입점을 꼭! 명시해줘야 한다. 그렇지 않으면 해당 패키지에 있는 package.json의 진입점을 통해 모듈을 파악하기 때문에 의도와 달리 HMR이 제대로 동작하지 않는다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;HMR의 기본적인 동작에 대해 살펴본 경험, JS 환경에서 모듈 리졸버에 대한 기본적인 로직을 알고 있다면 다음과 같이&amp;nbsp;간단한 해결책으로 모노레포 환경에서의 HMR 지원을 추가할 수 있다. :)&lt;/p&gt;</description>
      <category>공부내용 공유하기</category>
      <category>HMR</category>
      <author>눅눅한 피부</author>
      <guid isPermaLink="true">https://nookpi.tistory.com/208</guid>
      <comments>https://nookpi.tistory.com/208#entry208comment</comments>
      <pubDate>Sat, 16 Nov 2024 13:09:11 +0900</pubDate>
    </item>
    <item>
      <title>Leptos 후기</title>
      <link>https://nookpi.tistory.com/207</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://leptos.dev/&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://leptos.dev/&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1730532312480&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;Home - Leptos&quot; data-og-description=&quot;Your favorite language Leptos makes it easy to integrate Rust backend code with your user interface in a few lines of code. #[server] functions let you cross the client-server boundary without the boilerplate of setting up a new API endpoint, making it eas&quot; data-og-host=&quot;leptos.dev&quot; data-og-source-url=&quot;https://leptos.dev/&quot; data-og-url=&quot;https://leptos.dev/&quot; data-og-image=&quot;&quot;&gt;&lt;a href=&quot;https://leptos.dev/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://leptos.dev/&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url();&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;Home - Leptos&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Your favorite language Leptos makes it easy to integrate Rust backend code with your user interface in a few lines of code. #[server] functions let you cross the client-server boundary without the boilerplate of setting up a new API endpoint, making it eas&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;leptos.dev&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2024-11-02 오후 4.39.03.png&quot; data-origin-width=&quot;1462&quot; data-origin-height=&quot;1054&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b5Z0eS/btsKueTTajf/ERDpH4qkpCRNgVKfInZ1u0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b5Z0eS/btsKueTTajf/ERDpH4qkpCRNgVKfInZ1u0/img.png&quot; data-alt=&quot;Counter 컴포넌트는 못참지&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b5Z0eS/btsKueTTajf/ERDpH4qkpCRNgVKfInZ1u0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb5Z0eS%2FbtsKueTTajf%2FERDpH4qkpCRNgVKfInZ1u0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1462&quot; height=&quot;1054&quot; data-filename=&quot;스크린샷 2024-11-02 오후 4.39.03.png&quot; data-origin-width=&quot;1462&quot; data-origin-height=&quot;1054&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;Counter 컴포넌트는 못참지&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;한동안 무심했던 rust 공부도 할 겸, Leptos를 사용해서 간단한 SPA를 만들어봤다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://github.com/Jonghakseo/leptos-study&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://github.com/Jonghakseo/leptos-study&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1730532369533&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;object&quot; data-og-title=&quot;GitHub - Jonghakseo/leptos-study&quot; data-og-description=&quot;Contribute to Jonghakseo/leptos-study development by creating an account on GitHub.&quot; data-og-host=&quot;github.com&quot; data-og-source-url=&quot;https://github.com/Jonghakseo/leptos-study&quot; data-og-url=&quot;https://github.com/Jonghakseo/leptos-study&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/dB52Qk/hyXpoXkul8/5Uvztv3CMbgnC86PojGZ11/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600,https://scrap.kakaocdn.net/dn/eeM3Yg/hyXsYbDzom/yqt8YqO0QHczt0DfkMrxlK/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600&quot;&gt;&lt;a href=&quot;https://github.com/Jonghakseo/leptos-study&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://github.com/Jonghakseo/leptos-study&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/dB52Qk/hyXpoXkul8/5Uvztv3CMbgnC86PojGZ11/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600,https://scrap.kakaocdn.net/dn/eeM3Yg/hyXsYbDzom/yqt8YqO0QHczt0DfkMrxlK/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;GitHub - Jonghakseo/leptos-study&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Contribute to Jonghakseo/leptos-study development by creating an account on GitHub.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;github.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;뭔가 실무적인 유즈케이스를 상상하면서 구현을 해보려고 했는데, 결론부터 말하자면 앞으로 굳이 rust로 웹 프론트엔드 개발을 하게 될 일이 있을까? 싶은 기분이 들었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;생각보다 Leptos 내에 구현된 브라우저 JS 환경과의 접점 인터페이스가 꼼꼼하게 작성되어서 놀랐는데, 그와 별개로 NativeJS 객체를 rust로 다루는게 너무 번거로웠달까.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;JSX style의 렌더링 코드나 props 바인딩을 보니 새삼 React 진영의 최고의 발명품은 JSX라는 생각도 들고...&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;확실히 rust가 재미있긴 하다. rust 에서의 closure 명시 문법이 꼼꼼하다며 감탄하다가도 참조 카운터(RC)로 만든 시그널이 해당 제약 우회하는걸 보며 편의성도 느꼈다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번 공부때는 copilot이 아닌 gpt를 좀 적극적으로 사용하면서 이것저것 물어보고 공부하면서 진행하니 더 도움도 되고 재미있었다. rust를 꾸준히 공부하려면 rust로 뭔가 만들어야 할 것 같은데 web은 생산성 측면에서 큰 재미가 없고 알고리즘도 좀 애매하고.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;결국 성능적으로 이점이 큰 CLI를 만드는게 좋을 것 같은데 문제는 지금 만들고 싶은게 없다.&lt;/p&gt;</description>
      <category>잡담</category>
      <author>눅눅한 피부</author>
      <guid isPermaLink="true">https://nookpi.tistory.com/207</guid>
      <comments>https://nookpi.tistory.com/207#entry207comment</comments>
      <pubDate>Sat, 2 Nov 2024 16:39:28 +0900</pubDate>
    </item>
    <item>
      <title>useQuery의 흐름 끄적끄적</title>
      <link>https://nookpi.tistory.com/205</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;아폴로 클라이언트를 사용하면서 가장 많이 사용하는 훅은 useQuery일 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;선언한 graphql 규격에 맞춰, ApolloClient는 어떻게 데이터를 요청할까? 소스코드 흐름을 보며 따라가보자.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;useQuery.ts&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://github.com/apollographql/apollo-client/blob/v3.11.8/src/react/hooks/useQuery.ts&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://github.com/apollographql/apollo-client/blob/v3.11.8/src/react/hooks/useQuery.ts&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://github.com/apollographql/apollo-client/blob/v3.11.8/src/react/hooks/useQuery.ts#L155&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://github.com/apollographql/apollo-client/blob/v3.11.8/src/react/hooks/useQuery.ts#L155&lt;/a&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1728805261269&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;function _useQuery&amp;lt;
  TData = any,
  TVariables extends OperationVariables = OperationVariables,
&amp;gt;(
  query: DocumentNode | TypedDocumentNode&amp;lt;TData, TVariables&amp;gt;,
  options: QueryHookOptions&amp;lt;NoInfer&amp;lt;TData&amp;gt;, NoInfer&amp;lt;TVariables&amp;gt;&amp;gt;
) {
  const { result, obsQueryFields } = useQueryInternals(query, options);
  return React.useMemo(
    () =&amp;gt; ({ ...result, ...obsQueryFields }),
    [result, obsQueryFields]
  );
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;useQuery 훅은 _useQuery 구현체의 wrapper이고, _useQuery 구현체는 useQueryInternals의 반환값에 대한 메모이제이션임을 알 수 있다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;useQueryInternals&lt;/h3&gt;
&lt;pre id=&quot;code_1728805305216&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;export function useQueryInternals&amp;lt;
  TData = any,
  TVariables extends OperationVariables = OperationVariables,
&amp;gt;(
  query: DocumentNode | TypedDocumentNode&amp;lt;TData, TVariables&amp;gt;,
  options: QueryHookOptions&amp;lt;NoInfer&amp;lt;TData&amp;gt;, NoInfer&amp;lt;TVariables&amp;gt;&amp;gt;
) {
  const client = useApolloClient(options.client);

  const renderPromises = React.useContext(getApolloContext()).renderPromises;
  const isSyncSSR = !!renderPromises;
  const disableNetworkFetches = client.disableNetworkFetches;
  const ssrAllowed = options.ssr !== false &amp;amp;&amp;amp; !options.skip;
  const partialRefetch = options.partialRefetch;

  const makeWatchQueryOptions = createMakeWatchQueryOptions(
    client,
    query,
    options,
    isSyncSSR
  );

  const [{ observable, resultData }, onQueryExecuted] = useInternalState(
    client,
    query,
    options,
    renderPromises,
    makeWatchQueryOptions
  );

  const watchQueryOptions: Readonly&amp;lt;WatchQueryOptions&amp;lt;TVariables, TData&amp;gt;&amp;gt; =
    makeWatchQueryOptions(observable);

  useResubscribeIfNecessary&amp;lt;TData, TVariables&amp;gt;(
    resultData, // might get mutated during render
    observable, // might get mutated during render
    client,
    options,
    watchQueryOptions
  );

  const obsQueryFields = React.useMemo&amp;lt;
    Omit&amp;lt;ObservableQueryFields&amp;lt;TData, TVariables&amp;gt;, &quot;variables&quot;&amp;gt;
  &amp;gt;(() =&amp;gt; bindObservableMethods(observable), [observable]);

  useRegisterSSRObservable(observable, renderPromises, ssrAllowed);

  const result = useObservableSubscriptionResult&amp;lt;TData, TVariables&amp;gt;(
    resultData,
    observable,
    client,
    options,
    watchQueryOptions,
    disableNetworkFetches,
    partialRefetch,
    isSyncSSR,
    {
      onCompleted: options.onCompleted || noop,
      onError: options.onError || noop,
    }
  );

  return {
    result,
    obsQueryFields,
    observable,
    resultData,
    client,
    onQueryExecuted,
  };
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여러 설정과 옵션이 얽혀 실제 요청이 어디서 되는지 명확하진 않지만, useObservableSubscriptionResult 훅을 먼저 살펴보자. onError, onCompleted 등의 콜백도 해당 훅으로 넘어가는 것을 볼 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://github.com/apollographql/apollo-client/blob/v3.11.8/src/react/hooks/useQuery.ts#L321&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://github.com/apollographql/apollo-client/blob/v3.11.8/src/react/hooks/useQuery.ts#L321&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1728805403109&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;function useObservableSubscriptionResult&amp;lt;
  TData = any,
  TVariables extends OperationVariables = OperationVariables,
&amp;gt;(
  resultData: InternalResult&amp;lt;TData, TVariables&amp;gt;,
  observable: ObservableQuery&amp;lt;TData, TVariables&amp;gt;,
  client: ApolloClient&amp;lt;object&amp;gt;,
  options: QueryHookOptions&amp;lt;NoInfer&amp;lt;TData&amp;gt;, NoInfer&amp;lt;TVariables&amp;gt;&amp;gt;,
  watchQueryOptions: Readonly&amp;lt;WatchQueryOptions&amp;lt;TVariables, TData&amp;gt;&amp;gt;,
  disableNetworkFetches: boolean,
  partialRefetch: boolean | undefined,
  isSyncSSR: boolean,
  callbacks: {
    onCompleted: (data: TData) =&amp;gt; void;
    onError: (error: ApolloError) =&amp;gt; void;
  }
) {
  const callbackRef = React.useRef&amp;lt;Callbacks&amp;lt;TData&amp;gt;&amp;gt;(callbacks);
  React.useEffect(() =&amp;gt; {
    // Make sure state.onCompleted and state.onError always reflect the latest
    // options.onCompleted and options.onError callbacks provided to useQuery,
    // since those functions are often recreated every time useQuery is called.
    // Like the forceUpdate method, the versions of these methods inherited from
    // InternalState.prototype are empty no-ops, but we can override them on the
    // base state object (without modifying the prototype).
    callbackRef.current = callbacks;
  });

  const resultOverride =
    (
      (isSyncSSR || disableNetworkFetches) &amp;amp;&amp;amp;
      options.ssr === false &amp;amp;&amp;amp;
      !options.skip
    ) ?
      // If SSR has been explicitly disabled, and this function has been called
      // on the server side, return the default loading state.
      ssrDisabledResult
    : options.skip || watchQueryOptions.fetchPolicy === &quot;standby&quot; ?
      // When skipping a query (ie. we're not querying for data but still want to
      // render children), make sure the `data` is cleared out and `loading` is
      // set to `false` (since we aren't loading anything).
      //
      // NOTE: We no longer think this is the correct behavior. Skipping should
      // not automatically set `data` to `undefined`, but instead leave the
      // previous data in place. In other words, skipping should not mandate that
      // previously received data is all of a sudden removed. Unfortunately,
      // changing this is breaking, so we'll have to wait until Apollo Client 4.0
      // to address this.
      skipStandbyResult
    : void 0;

  const previousData = resultData.previousData;
  const currentResultOverride = React.useMemo(
    () =&amp;gt;
      resultOverride &amp;amp;&amp;amp;
      toQueryResult(resultOverride, previousData, observable, client),
    [client, observable, resultOverride, previousData]
  );

  return useSyncExternalStore(
    React.useCallback(
      (handleStoreChange) =&amp;gt; {
        // reference `disableNetworkFetches` here to ensure that the rules of hooks
        // keep it as a dependency of this effect, even though it's not used
        disableNetworkFetches;

        if (isSyncSSR) {
          return () =&amp;gt; {};
        }

        const onNext = () =&amp;gt; {
          const previousResult = resultData.current;
          // We use `getCurrentResult()` instead of the onNext argument because
          // the values differ slightly. Specifically, loading results will have
          // an empty object for data instead of `undefined` for some reason.
          const result = observable.getCurrentResult();
          // Make sure we're not attempting to re-render similar results
          if (
            previousResult &amp;amp;&amp;amp;
            previousResult.loading === result.loading &amp;amp;&amp;amp;
            previousResult.networkStatus === result.networkStatus &amp;amp;&amp;amp;
            equal(previousResult.data, result.data)
          ) {
            return;
          }

          setResult(
            result,
            resultData,
            observable,
            client,
            partialRefetch,
            handleStoreChange,
            callbackRef.current
          );
        };

        const onError = (error: Error) =&amp;gt; {
          subscription.current.unsubscribe();
          subscription.current = observable.resubscribeAfterError(
            onNext,
            onError
          );

          if (!hasOwnProperty.call(error, &quot;graphQLErrors&quot;)) {
            // The error is not a GraphQL error
            throw error;
          }

          const previousResult = resultData.current;
          if (
            !previousResult ||
            (previousResult &amp;amp;&amp;amp; previousResult.loading) ||
            !equal(error, previousResult.error)
          ) {
            setResult(
              {
                data: (previousResult &amp;amp;&amp;amp; previousResult.data) as TData,
                error: error as ApolloError,
                loading: false,
                networkStatus: NetworkStatus.error,
              },
              resultData,
              observable,
              client,
              partialRefetch,
              handleStoreChange,
              callbackRef.current
            );
          }
        };

        // TODO evaluate if we keep this in
        // React Compiler cannot handle scoped `let` access, but a mutable object
        // like this is fine.
        // was:
        // let subscription = observable.subscribe(onNext, onError);
        const subscription = { current: observable.subscribe(onNext, onError) };

        // Do the &quot;unsubscribe&quot; with a short delay.
        // This way, an existing subscription can be reused without an additional
        // request if &quot;unsubscribe&quot;  and &quot;resubscribe&quot; to the same ObservableQuery
        // happen in very fast succession.
        return () =&amp;gt; {
          setTimeout(() =&amp;gt; subscription.current.unsubscribe());
        };
      },

      [
        disableNetworkFetches,
        isSyncSSR,
        observable,
        resultData,
        partialRefetch,
        client,
      ]
    ),
    () =&amp;gt;
      currentResultOverride ||
      getCurrentResult(
        resultData,
        observable,
        callbackRef.current,
        partialRefetch,
        client
      ),
    () =&amp;gt;
      currentResultOverride ||
      getCurrentResult(
        resultData,
        observable,
        callbackRef.current,
        partialRefetch,
        client
      )
  );
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;뭔가 긴 코드가 나왔지만... 두려워 말고 한 부분씩 살펴보자.&lt;/p&gt;
&lt;pre id=&quot;code_1728805436239&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;  const callbackRef = React.useRef&amp;lt;Callbacks&amp;lt;TData&amp;gt;&amp;gt;(callbacks);
  React.useEffect(() =&amp;gt; {
    // Make sure state.onCompleted and state.onError always reflect the latest
    // options.onCompleted and options.onError callbacks provided to useQuery,
    // since those functions are often recreated every time useQuery is called.
    // Like the forceUpdate method, the versions of these methods inherited from
    // InternalState.prototype are empty no-ops, but we can override them on the
    // base state object (without modifying the prototype).
    callbackRef.current = callbacks;
  });&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;전반적으로 주석도 친절하게 적혀있고, 패턴 자체도 생소하진 않아서 살펴보는게 어렵진 않다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;useRef로 onCompleted, onError 콜백을 callbackRef에 저장하고, 매 렌더링마다 callbackRef에 보관된 콜백들을 최신화하기 위해 deps가 없는 useEffect를 사용했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;onError, onCompleted로 주입받은 콜백을 최신화 시켜주지 않으면 해당 콜백에서 이전 상태를 참조하고 있는 경우 예상치 못한 사이드 이펙트가 발생할 수 있다. 해당 코드를 보니 불현듯 useEvent RFC가 생각이 난다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그 다음 resultOverride ~ currentResultOverride 코드는 생략. 살짝 보자면 ssr 여부, skip 여부 등에 따라 기본 반환 데이터의 타입을 덮어씌우는 코드로 보인다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;클라이막스인 반환부의 useSyncExternalStore + useCallback 코드로 넘어가자.&lt;/p&gt;
&lt;pre id=&quot;code_1728805578404&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;  return useSyncExternalStore(
    React.useCallback(
      (handleStoreChange) =&amp;gt; {
        // reference `disableNetworkFetches` here to ensure that the rules of hooks
        // keep it as a dependency of this effect, even though it's not used
        disableNetworkFetches;

        if (isSyncSSR) {
          return () =&amp;gt; {};
        }
        // ...&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;뜬금없이 disableNetworkFetches 를 적어두는 재미있는 코드가 보인다. 호출도 아니고, 사용도 아니고, 할당도 아닌 말 그대로 참조만 한다. 주석을 보니 disableNetworkFetches의 단순 참조로 해당 훅이 disableNetworkFetches에 대한 의존성을 가지는 것을 강제하는 코드라고 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약 훅의 컨텍스트가 disableNetworkFetches를 참조할 수 있는 범위를 벗어나게 되면 참조 에러를 발생시키려는 의도로 보인다! 재미있고 해키한 구현이다. 만약&amp;nbsp; useCallback으로 선언된 해당 함수가 현재 컨텍스트에 바인딩 되지 않았다면 에러가 날 수도? 근데 이게 어떤 상황에서 발생하는 것인지 유즈케이스까지는 리뷰 코멘트를 봐도 알기가 어려웠다&amp;hellip; &lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해당 변경사항에 대한 리뷰 코멘트&lt;br /&gt;&lt;a href=&quot;https://github.com/apollographql/apollo-client/pull/11869#discussion_r1664889946&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://github.com/apollographql/apollo-client/pull/11869#discussion_r1664889946&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래로 내려가서 onNext 콜백 선언부를 보자.&lt;/p&gt;
&lt;pre id=&quot;code_1728805771301&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;        const onNext = () =&amp;gt; {
          const previousResult = resultData.current;
          // We use `getCurrentResult()` instead of the onNext argument because
          // the values differ slightly. Specifically, loading results will have
          // an empty object for data instead of `undefined` for some reason.
          const result = observable.getCurrentResult();
          // Make sure we're not attempting to re-render similar results
          if (
            previousResult &amp;amp;&amp;amp;
            previousResult.loading === result.loading &amp;amp;&amp;amp;
            previousResult.networkStatus === result.networkStatus &amp;amp;&amp;amp;
            equal(previousResult.data, result.data)
          ) {
            return;
          }

          setResult(
            result,
            resultData,
            observable,
            client,
            partialRefetch,
            handleStoreChange,
            callbackRef.current
          );
        };&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;onNext 메소드의 이름을 정확한 컨텍스트에서 이해하려면 &lt;a href=&quot;https://learn.microsoft.com/en-us/dotnet/standard/events/observer-design-pattern&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;옵저버 패턴&lt;/a&gt;과 Observable 객체에 대한 이해가 필요하다. 간단하게 말하면 옵저버 패턴은 공급자(Provider or Observable)가 관찰자들(Observers)에게 알림을 보내는 디자인 패턴이라고 할 수 있는데, 관찰자들은 onNext, onError, onComplete 등의 이벤트 수신 콜백 메소드를 구현해서 제공되는 이벤트에 대한 처리를 어떻게 할 지 선언한다. 즉, 이 맥락에서 onNext 메소드는 쿼리의 응답 따위를 수신한 상황이라고 할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이전에 이미 가져온 결과가 있고, 이전 결과와 현재 상태의 로딩이 같으면서, 네트워크 status도 동일하고, 그 결과까지 같다면 아무것도 하지 않고 return을 해준다. 렌더링이 추가적으로 필요하지 않은 상황이라고 간주하는 것으로 보인다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그 외의 경우에는 setResult 를 호출해서 현재의 결과를 저장하는 것으로 보이는데, setResult 구현은 다음과 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1728805918032&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;function setResult&amp;lt;TData, TVariables extends OperationVariables&amp;gt;(
  nextResult: ApolloQueryResult&amp;lt;TData&amp;gt;,
  resultData: InternalResult&amp;lt;TData, TVariables&amp;gt;,
  observable: ObservableQuery&amp;lt;TData, TVariables&amp;gt;,
  client: ApolloClient&amp;lt;object&amp;gt;,
  partialRefetch: boolean | undefined,
  forceUpdate: () =&amp;gt; void,
  callbacks: Callbacks&amp;lt;TData&amp;gt;
) {
  const previousResult = resultData.current;
  if (previousResult &amp;amp;&amp;amp; previousResult.data) {
    resultData.previousData = previousResult.data;
  }

  if (!nextResult.error &amp;amp;&amp;amp; isNonEmptyArray(nextResult.errors)) {
    // Until a set naming convention for networkError and graphQLErrors is
    // decided upon, we map errors (graphQLErrors) to the error options.
    // TODO: Is it possible for both result.error and result.errors to be
    // defined here?
    nextResult.error = new ApolloError({ graphQLErrors: nextResult.errors });
  }

  resultData.current = toQueryResult(
    unsafeHandlePartialRefetch(nextResult, observable, partialRefetch),
    resultData.previousData,
    observable,
    client
  );
  // Calling state.setResult always triggers an update, though some call sites
  // perform additional equality checks before committing to an update.
  forceUpdate();
  handleErrorOrCompleted(nextResult, previousResult?.networkStatus, callbacks);
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;현재의 응답 데이터를 internalStore의 previousData로 저장한 후, 현재 응답에 에러가 있다면 ApolloError 객체를 생성하여 error 필드에 넣어준다. 이 에러는 handleErrorOrCompleted로 넘겨서 처리된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;forceUpdate() 는 setResult 호출시 전달된 handleStoreChange 메소드이다. 해당 메소드는 useSyncExternalStore의 첫 번째 인자로 넘긴 useCallback 함수의 인자에 해당하는 함수인데, 코드로 보면 헷갈리지만 useSyncExternalStore의 시그니처를 보면 쉽게 이해가 가능하다.&lt;/p&gt;
&lt;pre id=&quot;code_1728805980764&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;    export function useSyncExternalStore&amp;lt;Snapshot&amp;gt;(
        subscribe: (onStoreChange: () =&amp;gt; void) =&amp;gt; () =&amp;gt; void,
        getSnapshot: () =&amp;gt; Snapshot,
        getServerSnapshot?: () =&amp;gt; Snapshot,
    ): Snapshot;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;바로 저 onStoreChange 부분이 handleStoreChange 라는 이름으로, 그리고 forceUpdate 라는 이름으로 전달된 부분이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;useSyncExternalStore는 subscribe 함수를 통해 전달한 onStoreChange 를 호출할 때, Snapshot 간의 비교를 통해 렌더링 여부를 결정하게 되니 렌더링을 트리거하는 역할을 한다고 볼 수 있겠다. 그러니 엄밀히 말하면 반환된 Snapshot에 대한 동등성 비교 이후 렌더링 여부를 리액트에서 결정하는 셈이니 forceUpdate는 아니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;잠깐 밖으로 새서 handleErrorOrCompleted도 살펴보자.&lt;/p&gt;
&lt;pre id=&quot;code_1728806074645&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;function handleErrorOrCompleted&amp;lt;TData&amp;gt;(
  result: ApolloQueryResult&amp;lt;TData&amp;gt;,
  previousNetworkStatus: NetworkStatus | undefined,
  callbacks: Callbacks&amp;lt;TData&amp;gt;
) {
  if (!result.loading) {
    const error = toApolloError(result);

    // wait a tick in case we are in the middle of rendering a component
    Promise.resolve()
      .then(() =&amp;gt; {
        if (error) {
          callbacks.onError(error);
        } else if (
          result.data &amp;amp;&amp;amp;
          previousNetworkStatus !== result.networkStatus &amp;amp;&amp;amp;
          result.networkStatus === NetworkStatus.ready
        ) {
          callbacks.onCompleted(result.data);
        }
      })
      .catch((error) =&amp;gt; {
        invariant.warn(error);
      });
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;컴포넌트 렌더 중 handleErrorOrCompleted 메소드가 호출될 경우, 바로 콜백들을 호출하지 않고 렌더링을 기다리기 위해 한 틱을 보내는 Promise.resolve()를 사용해준 부분이 흥미롭다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://github.com/apollographql/apollo-client/pull/9801&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://github.com/apollographql/apollo-client/pull/9801&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1728806484412&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;object&quot; data-og-title=&quot;Delay execution of callback functions to fix React errors by dylanwulf &amp;middot; Pull Request #9801 &amp;middot; apollographql/apollo-client&quot; data-og-description=&quot;As described in #9794, calling a setState function (or dispatching a redux action) from inside of onCompleted will sometimes cause React errors to be printed to the console. This appears to be due ...&quot; data-og-host=&quot;github.com&quot; data-og-source-url=&quot;https://github.com/apollographql/apollo-client/pull/9801&quot; data-og-url=&quot;https://github.com/apollographql/apollo-client/pull/9801&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/hvFCD/hyXd4LDcCh/K7SyZ7gcCwysjK4bVMbRmk/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600,https://scrap.kakaocdn.net/dn/b6zztn/hyXhTuVbXR/BNtaUqekjjBShE5imNIaF1/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600&quot;&gt;&lt;a href=&quot;https://github.com/apollographql/apollo-client/pull/9801&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://github.com/apollographql/apollo-client/pull/9801&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/hvFCD/hyXd4LDcCh/K7SyZ7gcCwysjK4bVMbRmk/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600,https://scrap.kakaocdn.net/dn/b6zztn/hyXhTuVbXR/BNtaUqekjjBShE5imNIaF1/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;Delay execution of callback functions to fix React errors by dylanwulf &amp;middot; Pull Request #9801 &amp;middot; apollographql/apollo-client&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;As described in #9794, calling a setState function (or dispatching a redux action) from inside of onCompleted will sometimes cause React errors to be printed to the console. This appears to be due ...&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;github.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다시 useObserverbleSubscriptionResult 훅으로 돌아와 이어서 살펴보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1728806525537&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;        const onError = (error: Error) =&amp;gt; {
          subscription.current.unsubscribe();
          subscription.current = observable.resubscribeAfterError(
            onNext,
            onError
          );

          if (!hasOwnProperty.call(error, &quot;graphQLErrors&quot;)) {
            // The error is not a GraphQL error
            throw error;
          }

          const previousResult = resultData.current;
          if (
            !previousResult ||
            (previousResult &amp;amp;&amp;amp; previousResult.loading) ||
            !equal(error, previousResult.error)
          ) {
            setResult(
              {
                data: (previousResult &amp;amp;&amp;amp; previousResult.data) as TData,
                error: error as ApolloError,
                loading: false,
                networkStatus: NetworkStatus.error,
              },
              resultData,
              observable,
              client,
              partialRefetch,
              handleStoreChange,
              callbackRef.current
            );
          }
        };&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;onError에 대한 콜백이 보인다. 좀 전에 onNext 내부의 setResult에서도 에러에 대한 처리를 하고 onError 콜백을 실행했던 것 같은데... onNext에서 받았던 result 내부의 error와 onError 콜백을 통해 받는 error의 차이는 뭘까?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일단 에러가 발생하면 subscription을 끊어주고, resubscribeAfterError 메소드를 통해 재구독을 해주는 모습이 보인다. resubscribeAfterError 내부 구현을 보니 마지막 데이터를 지우고 새로 구독을 하는 단순한 동작이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Graphql에러가 아닌 경우에는 throw error를 하는걸 보니 GraphqlError인 경우에 onError 콜백이 실행되는 것인지? 의심을 해본다. 다시 한 번 렌더링이 필요한지 확인하는 로직이 있고(여기서는 onNext와 달리 AND 연산자가 아니라 OR 연산자를 사용하는데 단순 가독성의 이유인지?) 마찬가지로 setResult를 통해 렌더링을 트리거한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;바로 아랫 부분을 추가로 살펴보면&lt;/p&gt;
&lt;pre id=&quot;code_1728806642001&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;        // TODO evaluate if we keep this in
        // React Compiler cannot handle scoped `let` access, but a mutable object
        // like this is fine.
        // was:
        // let subscription = observable.subscribe(onNext, onError);
        const subscription = { current: observable.subscribe(onNext, onError) };

        // Do the &quot;unsubscribe&quot; with a short delay.
        // This way, an existing subscription can be reused without an additional
        // request if &quot;unsubscribe&quot;  and &quot;resubscribe&quot; to the same ObservableQuery
        // happen in very fast succession.
        return () =&amp;gt; {
          setTimeout(() =&amp;gt; subscription.current.unsubscribe());
        };&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;subscription에 대한 컴파일러의 평가 문제로 current 객체로 바꾼 부분의 확인이 필요하다&amp;hellip;는 주석이 있고, subscribe 함수의 반환 함수인 구독 해제 함수를 setTimeout으로 감싸두었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;무슨 말인지 살펴보니, 일부러 짧은 지연시간을 두어 '구독 취소 &amp;rarr; 재구독 &amp;rarr; 구독취소' 와 같이 짧은 간격으로 구독 취소와 재구독 이벤트가 발생하는 경우, 구독 취소를 지연시켜 &lt;b&gt;곧 제거될 subscription 이지만 아직 제거되지 않은 subscription을 재사용&lt;/b&gt;하기 위함으로 보인다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정확한 유즈케이스는 모르겠지만 subscription 구독과 동시에 불필요한 네트워크 요청이 발생할 수 있으니 이러한 부분을 방지하기 위한 것 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;getSnapShot 부분에 해당하는 코드는 간단하다.&lt;/p&gt;
&lt;pre id=&quot;code_1728806836548&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;    () =&amp;gt;
      currentResultOverride ||
      getCurrentResult(
        resultData,
        observable,
        callbackRef.current,
        partialRefetch,
        client
      ),&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;특정 조건에 오버라이드 되어야 하는 응답이 있는게 아니라면 getCurrentResult를 사용해서 현재 상태를 가져온다. getCurrentResult는 resultData.current 와 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기까지의 대략적인 흐름을 정리해보면 다음과 같다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;useQuery 훅은 _useQuery 구현체의 wrapper이고, _useQuery 구현체는 useQueryInternals의 반환값에 대한 메모이제이션이다.&lt;/li&gt;
&lt;li&gt;useQueryInternals 에서 의심스러운 부분들을 보다보니 쿼리 결과를 반환하는 useObservableSubscriptionResult 훅이 있었다.&lt;/li&gt;
&lt;li&gt;useObservableSubscriptionResult 훅을 보니 useSyncExternalStore 훅을 사용해서 쿼리 결과에 대한 구독과 onNext, onError 콜백을 가진 옵저버 구현체로 이어진다.&lt;/li&gt;
&lt;li&gt;결국 요청을 보내는 곳을 찾으려면 observable을 구독하는 곳이 아니라 만드는 곳을 봐야 한다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다시 생각해보니 이미 useObservableSubscriptionResult 라는 훅 이름 자체가 구독을 관리하고 결과를 통해 콜백 호출과 상태 업데이트를 하는 곳이라는 이름이 명확하다! 그렇다면 실질적인 요청을 보내는 곳은 인자로 전달되는 observable 객체를 만드는 부분일 것이다. 이를 명심하고 다시 한 번 useQueryInternals 훅을 살펴보니 의심스러운 곳이 보인다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://github.com/apollographql/apollo-client/blob/v3.11.8/src/react/hooks/useQuery.ts#L271&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://github.com/apollographql/apollo-client/blob/v3.11.8/src/react/hooks/useQuery.ts#L271&lt;/a&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1728807016395&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;  const [{ observable, resultData }, onQueryExecuted] = useInternalState(
    client,
    query,
    options,
    renderPromises,
    makeWatchQueryOptions
  );&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;useInternalState 라는 훅의 이름만 대충 보고 내부 상태만 관리하는 줄 알고 넘겼는데 자세히 보니 반환값에 observable이 있다. 세부 구현을 살펴보니 &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;훅 구현체에서&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;observable을 선언해주는 곳을 확인 할 수 있다!&lt;/p&gt;
&lt;pre id=&quot;code_1728807057027&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;     observable:
        // See if there is an existing observable that was used to fetch the same
        // data and if so, use it instead since it will contain the proper queryId
        // to fetch the result set. This is used during SSR.
        (renderPromises &amp;amp;&amp;amp;
          renderPromises.getSSRObservable(makeWatchQueryOptions())) ||
        client.watchQuery(
          getObsQueryOptions(void 0, client, options, makeWatchQueryOptions())
        ),&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;SSR 환경에서 renderPromises에 이미 보관된 SSR Observable이 있는지 확인하고, 있다면 해당 Observable을 반환한다. 클라이언트 기준으로는 client.watchQuery 를 호출하면 Observable이 반환되는 모양이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ApollocCient.watchQuery는 QueryManager의 watchQuery의 wrapper이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://github.com/apollographql/apollo-client/blob/main/src/core/QueryManager.ts#L723&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://github.com/apollographql/apollo-client/v3.11.8//main/src/core/QueryManager.ts#L723&lt;/a&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1728807220927&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;  public watchQuery&amp;lt;
    T,
    TVariables extends OperationVariables = OperationVariables,
  &amp;gt;(options: WatchQueryOptions&amp;lt;TVariables, T&amp;gt;): ObservableQuery&amp;lt;T, TVariables&amp;gt; {
    const query = this.transform(options.query);

    // assign variable default values if supplied
    // NOTE: We don't modify options.query here with the transformed query to
    // ensure observable.options.query is set to the raw untransformed query.
    options = {
      ...options,
      variables: this.getVariables(query, options.variables) as TVariables,
    };

    if (typeof options.notifyOnNetworkStatusChange === &quot;undefined&quot;) {
      options.notifyOnNetworkStatusChange = false;
    }

    const queryInfo = new QueryInfo(this);
    const observable = new ObservableQuery&amp;lt;T, TVariables&amp;gt;({
      queryManager: this,
      queryInfo,
      options,
    });
    observable[&quot;lastQuery&quot;] = query;

    this.queries.set(observable.queryId, queryInfo);

    // We give queryInfo the transformed query to ensure the first cache diff
    // uses the transformed query instead of the raw query
    queryInfo.init({
      document: query,
      observableQuery: observable,
      variables: observable.variables,
    });

    return observable;
  }&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;드디어 observable을 실질적으로 만들어주는 코드를 만났다!&lt;/p&gt;
&lt;pre id=&quot;code_1728807240073&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;    const observable = new ObservableQuery&amp;lt;T, TVariables&amp;gt;({
      queryManager: this,
      queryInfo,
      options,
    });&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;생성자 코드를 빠르게 살펴보면...&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://github.com/apollographql/apollo-client/blob/v3.11.8/src/core/ObservableQuery.ts#L111&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://github.com/apollographql/apollo-client/blob/v3.11.8/src/core/ObservableQuery.ts#L111&lt;/a&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1728807266685&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;  constructor({
    queryManager,
    queryInfo,
    options,
  }: {
    queryManager: QueryManager&amp;lt;any&amp;gt;;
    queryInfo: QueryInfo;
    options: WatchQueryOptions&amp;lt;TVariables, TData&amp;gt;;
  }) {
    super((observer: Observer&amp;lt;ApolloQueryResult&amp;lt;TData&amp;gt;&amp;gt;) =&amp;gt; {
      // Zen Observable has its own error function, so in order to log correctly
      // we need to provide a custom error callback.
      try {
        var subObserver = (observer as any)._subscription._observer;
        if (subObserver &amp;amp;&amp;amp; !subObserver.error) {
          subObserver.error = defaultSubscriptionObserverErrorCallback;
        }
      } catch {}

      const first = !this.observers.size;
      this.observers.add(observer);

      // Deliver most recent error or result.
      const last = this.last;
      if (last &amp;amp;&amp;amp; last.error) {
        observer.error &amp;amp;&amp;amp; observer.error(last.error);
      } else if (last &amp;amp;&amp;amp; last.result) {
        observer.next &amp;amp;&amp;amp; observer.next(last.result);
      }

      // Initiate observation of this query if it hasn't been reported to
      // the QueryManager yet.
      if (first) {
        // Blindly catching here prevents unhandled promise rejections,
        // and is safe because the ObservableQuery handles this error with
        // this.observer.error, so we're not just swallowing the error by
        // ignoring it here.
        this.reobserve().catch(() =&amp;gt; {});
      }

      return () =&amp;gt; {
        if (this.observers.delete(observer) &amp;amp;&amp;amp; !this.observers.size) {
          this.tearDownQuery();
        }
      };
    });&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ObservableQuery 객체는 zen-observable 구현체를 상속받아 구현되어 있는데, 그에 따른 처리들이 되어 있는 것을 볼 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래로 좀 내려오면...&lt;/p&gt;
&lt;pre id=&quot;code_1728807317417&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;      const first = !this.observers.size;
      //...

      // Initiate observation of this query if it hasn't been reported to
      // the QueryManager yet.
      if (first) {
        // Blindly catching here prevents unhandled promise rejections,
        // and is safe because the ObservableQuery handles this error with
        // this.observer.error, so we're not just swallowing the error by
        // ignoring it here.
        this.reobserve().catch(() =&amp;gt; {});
      }&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;observable을 구독하는 observers가 아직 없다는 것은 해당 쿼리를 이제 막 구독하기 시작했다는 의미와 동일하다. 이 경우에 this.reobserve 가 호출된다. catch에 대한 처리가 없는것처럼 보이지만 안심하라는 주석이 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;reobserve 메소드는 reobserveAsConcast 의 wrapper이다. as TODO 타입 단언이 재미있었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://github.com/apollographql/apollo-client/blob/v3.11.8/src/core/ObservableQuery.ts#L1005-L1011&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://github.com/apollographql/apollo-client/blob/v3.11.8/src/core/ObservableQuery.ts#L1005-L1011&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Concast라는 용어가 생소했는데 Concast 객체 선언부에 주석으로 설명이 잘 되어 있었다.&lt;br /&gt;&lt;a href=&quot;https://github.com/apollographql/apollo-client/blob/v3.11.8/src/utilities/observables/Concast.ts#L22-L50&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://github.com/apollographql/apollo-client/blob/v3.11.8/src/utilities/observables/Concast.ts#L22-L50&lt;/a&gt;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;Concast&amp;lt;T&amp;gt; 옵저버블은 주어진 소스들을 하나의 겹치지 않는 T 시퀀스로 연결하고, 자동으로 모든 프로미스를 풀어내며, 그 시퀀스의 T 요소들을 여러 구독자에게 방송합니다. 이 과정에서 중간 옵저버블 래퍼 객체를 많이 생성하지 않습니다.&lt;br /&gt;&lt;br /&gt;Concast에는 여러 옵저버가 구독할 수 있지만, 각 소스 옵저버블은 최대 한 번의 구독 호출만 보장받으며, 그 결과는 모든 옵저버에게 멀티캐스트됩니다.&lt;br /&gt;&lt;br /&gt;이 Concast는 모든 다음/오류 메시지를 this.observers에 방송하는 것 외에도, 가장 최근의 메시지를 this.latest를 사용해 저장합니다. 따라서 새로운 옵저버는 과거에 전달된 메시지라도 즉시 최신 메시지를 받을 수 있습니다. 이 동작은 this.observers에 있는 모든 활성 옵저버가 동일한 최신 메시지를 받았음을 보장합니다.&lt;br /&gt;&lt;br /&gt;이 최신 메시지 재생을 제외하고, Concast는 &quot;핫&quot; 옵저버블입니다. 이는 새로운 옵저버마다 과거의 결과를 처음부터 재생하지 않는다는 의미입니다.&lt;br /&gt;&lt;br /&gt;기존의 RxJS 클래스를 사용할 수 있었을까요? Concast&amp;lt;T&amp;gt;는 BehaviorSubject&amp;lt;T&amp;gt;와 유사합니다. 왜냐하면 멀티캐스트되고 새로운 구독자에게 최신의 다음/오류 메시지를 다시 전달하기 때문입니다. Subject&amp;lt;T&amp;gt;와 달리, Concast&amp;lt;T&amp;gt;는 옵저버&amp;lt;T&amp;gt; 인터페이스를 노출하지 않습니다(this.handlers는 의도적으로 비공개입니다). Concast&amp;lt;T&amp;gt;는 연결된 소스에서 입력을 받기 때문입니다. 만약 우리가 RxJS로 전환하게 된다면 그들의 코드를 재사용하는 데 가치가 있을 수 있지만, 현재로서는 Subject 구현을 포함하지 않는 zen-observable을 사용하고 있습니다.&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;reobserveAsConcast 를 살펴보자.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://github.com/apollographql/apollo-client/blob/v3.11.8/src/core/ObservableQuery.ts#L895&quot;&gt;https://github.com/apollographql/apollo-client/blob/v3.11.8/src/core/ObservableQuery.ts#L895&lt;/a&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1728807480024&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;const { concast, fromLink } = this.fetch(options, newNetworkStatus, query);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;뭔가 요청으로 보이는 fetch 코드가 reobserveAsConcast 함수 내부에서 보이는데...? 호출 결과로concast, fromLink 를 반환한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;fetch&amp;nbsp;메소드는&amp;nbsp;queryManager의&amp;nbsp;fetchConcastWithInfo&amp;nbsp;함수를&amp;nbsp;호출하는데&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://github.com/apollographql/apollo-client/blob/v3.11.8/src/core/QueryManager.ts#L1242&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://github.com/apollographql/apollo-client/blob/v3.11.8/src/core/QueryManager.ts#L1242&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해당 함수에서는 앞서 살펴본 Concast 객체를 생성하고 fetchQueryByPolicy 를 호출해주는 역할을 하고 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;대체 실제 fetch는 언제쯤 하는걸까? fetchQueryByPolicy 구현체를 퀵하게 보니, 여러 fetchPolicy에 따른 실질적인 요청부분을 볼 수 있었다. (그 와중에 switch 최상단 default: 로 cache-first를 기본 설정으로 세팅하는 디테일)&lt;/p&gt;
&lt;pre id=&quot;code_1728807568035&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;  private fetchQueryByPolicy&amp;lt;TData, TVars extends OperationVariables&amp;gt;(
    queryInfo: QueryInfo,
    {
      query,
      variables,
//...
    switch (fetchPolicy) {
      default:
      case &quot;cache-first&quot;: {
        const diff = readCache();

        if (diff.complete) {
          return {
            fromLink: false,
            sources: [resultsFromCache(diff, queryInfo.markReady())],
          };
        }

        if (returnPartialData || shouldNotify) {
          return {
            fromLink: true,
            sources: [resultsFromCache(diff), resultsFromLink()],
          };
        }

        return { fromLink: true, sources: [resultsFromLink()] };
      }

      case &quot;cache-and-network&quot;: {
        const diff = readCache();

        if (diff.complete || returnPartialData || shouldNotify) {
          return {
            fromLink: true,
            sources: [resultsFromCache(diff), resultsFromLink()],
          };
        }

        return { fromLink: true, sources: [resultsFromLink()] };
      }&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이름을 보니 느낌이 온다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;resultsFromCache 는 캐시에서 쿼리 결과를 조회하는 동작으로 보이고, resultsFromLink 는 실질적인 요청을 통해서 데이터를 가져오는 것으로 보인다. resultsFromLink는 QueryManager의 getResultsFromLink로 이어진다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://github.com/apollographql/apollo-client/blob/v3.11.8/src/core/QueryManager.ts#L1154&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://github.com/apollographql/apollo-client/blob/v3.11.8/src/core/QueryManager.ts#L1154&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://github.com/apollographql/apollo-client/blob/v3.11.8/src/core/QueryManager.ts#L1169-L1183&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://github.com/apollographql/apollo-client/blob/v3.11.8/src/core/QueryManager.ts#L1169-L1183&lt;/a&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1728807692718&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;   return asyncMap(
      this.getObservableFromLink(
        linkDocument,
        options.context,
        options.variables
      ),

      (result) =&amp;gt; {
        const graphQLErrors = getGraphQLErrorsFromResult(result);
        const hasErrors = graphQLErrors.length &amp;gt; 0;
        const { errorPolicy } = options;

        // If we interrupted this request by calling getResultsFromLink again
        // with the same QueryInfo object, we ignore the old results.
        if (requestId &amp;gt;= queryInfo.lastRequestId) {&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;asyncMap의 첫번째 인자는 observable, 두 번째 인자는 map function 으로, Observable의 결과를 맵핑하는 함수이니 사실상 콜백이라고 러프하게 생각해도 된다. 그렇다면 getObservableFromLink를 살펴보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://github.com/apollographql/apollo-client/blob/main/src/core/QueryManager.ts#L1082&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://github.com/apollographql/apollo-client/blob/&lt;/a&gt;&lt;a style=&quot;background-color: #e6f5ff; color: #0070d1; text-align: start;&quot; href=&quot;https://github.com/apollographql/apollo-client/blob/main/src/link/http/createHttpLink.ts#L29-L48&quot;&gt;v3.11.8&lt;/a&gt;&lt;a href=&quot;https://github.com/apollographql/apollo-client/blob/v3.11.8/src/core/QueryManager.ts#L1082&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;/src/core/QueryManager.ts#L1082&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;뭔가 요청단의 코드와 점점 가까워진다고 느껴진다.&lt;/p&gt;
&lt;pre id=&quot;code_1728807765967&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;    const { serverQuery, clientQuery } = this.getDocumentInfo(query);
    if (serverQuery) {
      const { inFlightLinkObservables, link } = this;

      const operation = {
        query: serverQuery,
        variables,
        operationName: getOperationName(serverQuery) || void 0,
        context: this.prepareContext({
          ...context,
          forceFetch: !deduplication,
        }),
        extensions,
      };&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;슬슬 실제&amp;nbsp;요청에&amp;nbsp;필요한&amp;nbsp;document와&amp;nbsp;operationName&amp;nbsp;등을&amp;nbsp;가져오기&amp;nbsp;시작한다.&lt;/p&gt;
&lt;pre id=&quot;code_1728807805150&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import { execute } from &quot;../link/core/index.js&quot;;
//...
const concast = new Concast([
  execute(link, operation) as Observable&amp;lt;FetchResult&amp;lt;T&amp;gt;&amp;gt;,
]);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;execute! 뭔가 의미심장한 메소드 이름이 등장했다 .심지어 link에 대한 의존성을 가지고 있으니 기대가 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://github.com/apollographql/apollo-client/blob/main/src/link/core/ApolloLink.ts#L65-L77&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://github.com/apollographql/apollo-client/blob/&lt;/a&gt;&lt;a style=&quot;background-color: #e6f5ff; color: #0070d1; text-align: start;&quot; href=&quot;https://github.com/apollographql/apollo-client/blob/main/src/link/http/createHttpLink.ts#L29-L48&quot;&gt;v3.11.8&lt;/a&gt;&lt;a href=&quot;https://github.com/apollographql/apollo-client/blob/v3.11.8/src/link/core/ApolloLink.ts#L65-L77&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;/src/link/core/ApolloLink.ts#L65-L77&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1728807855966&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;  public static execute(
    link: ApolloLink,
    operation: GraphQLRequest
  ): Observable&amp;lt;FetchResult&amp;gt; {
    return (
      link.request(
        createOperation(
          operation.context,
          transformOperation(validateOperation(operation))
        )
      ) || Observable.of()
    );
  }
  //...
  public request(
    operation: Operation,
    forward?: NextLink
  ): Observable&amp;lt;FetchResult&amp;gt; | null {
    throw newInvariantError(&quot;request is not implemented&quot;);
  }&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;드디어 도착했다&amp;hellip;!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ApolloLink의&amp;nbsp;request&amp;nbsp;인자로&amp;nbsp;query&amp;nbsp;오퍼레이션을&amp;nbsp;payload&amp;nbsp;형태로&amp;nbsp;변환해주는&amp;nbsp;프로세스가 있고,&amp;nbsp;실질적인&amp;nbsp;요청은&amp;nbsp;이&amp;nbsp;곳에서&amp;nbsp;이뤄진다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;요청 코드에서 fetch api 혹은 axios, XMLHttpRequest 등을 찾을 필요는 없다. 아폴로 클라이언트는 http 요청에 필요한 구현체들을 자유롭게 주입할 수 있는 구조로 되어 있으며 이러한 구현체는 아폴로 클라이언트 초기화시 설정하는 httpLink 구성에서 자유롭게 정의할 수 있기 때문이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;물론 이러한 부분을 쉽게 하기 위해 ApolloLink를 상속한 HttpLink와 같은 객체 프리셋들도 이미 제공하고있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;예시 코드&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1728807921534&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import fetch from 'cross-fetch';
//...

const httpLink = new HttpLink({ uri: ENV.APOLLO_API_END_POINT, fetch });

const authLink = setContext((_, { headers }) =&amp;gt; {
	//...
});

const link = ApolloLink.from([authLink, httpLink]);

//...

export const initializeApollo = () =&amp;gt; {
  return new ApolloClient({
    link,
    cache,
    //...&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;HttpLink는 createHttpLink를 통해 생성되는데,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://github.com/apollographql/apollo-client/blob/v3.11.8/src/link/http/HttpLink.ts&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://github.com/apollographql/apollo-client/blob/&lt;/a&gt;&lt;a href=&quot;https://github.com/apollographql/apollo-client/blob/v3.11.8/src/link/http/createHttpLink.ts#L29-L48&quot;&gt;v3.11.8&lt;/a&gt;&lt;a href=&quot;https://github.com/apollographql/apollo-client/blob/v3.11.8/src/link/http/HttpLink.ts&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;/src/link/http/HttpLink.ts&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://github.com/apollographql/apollo-client/blob/v3.11.8/src/link/http/createHttpLink.ts#L29-L48&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://github.com/apollographql/apollo-client/blob/v3.11.8/src/link/http/createHttpLink.ts#L29-L48&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1728808006331&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;const backupFetch = maybe(() =&amp;gt; fetch);

export const createHttpLink = (linkOptions: HttpOptions = {}) =&amp;gt; {
  let {
    uri = &quot;/graphql&quot;,
    // use default global fetch if nothing passed in
    fetch: preferredFetch,
    print = defaultPrinter,
    includeExtensions,
    preserveHeaderCase,
    useGETForQueries,
    includeUnusedVariables = false,
    ...requestOptions
  } = linkOptions;

  if (__DEV__) {
    // Make sure at least one of preferredFetch, window.fetch, or backupFetch is
    // defined, so requests won't fail at runtime.
    checkFetcher(preferredFetch || backupFetch);
  }&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;httpLink 생성시 fetch를 명시적으로 주입하지 않으면 각 환경의 backupFetch를 사용한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://github.com/apollographql/apollo-client/blob/v3.11.8/src/link/http/createHttpLink.ts#L179-L216&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://github.com/apollographql/apollo-client/blob/v3.11.8/src/link/http/createHttpLink.ts#L179-L216&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1728808509559&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;    return new Observable((observer) =&amp;gt; {
      // Prefer linkOptions.fetch (preferredFetch) if provided, and otherwise
      // fall back to the *current* global window.fetch function (see issue
      // #7832), or (if all else fails) the backupFetch function we saved when
      // this module was first evaluated. This last option protects against the
      // removal of window.fetch, which is unlikely but not impossible.
      const currentFetch = preferredFetch || maybe(() =&amp;gt; fetch) || backupFetch;

      const observerNext = observer.next.bind(observer);
      currentFetch!(chosenURI, options)
        .then((response) =&amp;gt; {
          operation.setContext({ response });
          const ctype = response.headers?.get(&quot;content-type&quot;);

          if (ctype !== null &amp;amp;&amp;amp; /^multipart\/mixed/i.test(ctype)) {
            return readMultipartBody(response, observerNext);
          } else {
            return parseAndCheckHttpResponse(operation)(response).then(
              observerNext
            );
          }
        })
        .then(() =&amp;gt; {
          controller = undefined;
          observer.complete();
        })
        .catch((err) =&amp;gt; {
          controller = undefined;
          handleError(err, observer);
        });

      return () =&amp;gt; {
        // XXX support canceling this request
        // https://developers.google.com/web/updates/2017/09/abortable-fetch
        if (controller) controller.abort();
      };
    });
  });&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;마침내 실질적인 fetch 요청을 보내는 곳을 발견했다. currentFetch의 평가에 대해 재미있는 부분이 있는데,&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;linkOptions.fetch(preferredFetch)가 제공된 경우 이를 사용하고, 그렇지 않으면 현재 전역 window.fetch 함수를 사용합니다(문제 #7832 참조). 만약 이것도 불가능하다면, 모듈이 처음 평가될 때 저장된 backupFetch 함수를 사용합니다. 이 마지막 옵션은 window.fetch가 제거되는 상황에 대비한 것으로, 가능성은 낮지만 완전히 배제할 수는 없습니다.&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;fallback으로 전역 window.fetch를 사용하는 것은 놀랍지 않았는데, window.fetch가 없어지는 상황까지 고려하여 backup용으로 모듈 평가 시점의 backupFetch를 가져와서 사용하는 점이 재미있었다. currentFetch 를 평가하는 시점의 전역 fetch를 살펴보고, 만약 존재하지 않는다면 backupFetch가 선언되어 해당 코드를 브라우저에서 읽고 평가하는 순간에 캡쳐된 fetch를 참조하는 세심함이 있었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 했을 때, 설령 `createHttpLink` 호출 시점의 전역 fetch가 어떠한 이유로든 전역 참조에서 제거되더라도 평가 시점에 참조한 fetch 메소드를 사용할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;소감&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;개인적으로 ApolloClient 코드들이 읽기 쉬운 편이라고 생각하는데, 그 중에서도 쉬운 useQuery를 골랐음에도 참 읽을게 많아 후반부엔 좀 지치는 감이 있었다. 그래도 전체적인 흐름이나 Observable 패턴의 구현체들을 봐서 새로웠고, Concast 같은 새로운 개념들도 알게 되어 좋았다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;참고&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;useQuery의 인자는 왜 객체 형태가 아닌걸까?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://github.com/apollographql/apollo-client/pull/11869#discussion_r1665511736&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://github.com/apollographql/apollo-client/pull/11869#discussion_r1665511736&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1728808619783&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;object&quot; data-og-title=&quot;refactor useQuery to not use an internal class by phryneas &amp;middot; Pull Request #11869 &amp;middot; apollographql/apollo-client&quot; data-og-description=&quot;After #11890, I'm fairly confident in this now. This needs to be reviewed commit-by commit, although a bunch of merges added additional commits at the start and end - I'm really sorry for t...&quot; data-og-host=&quot;github.com&quot; data-og-source-url=&quot;https://github.com/apollographql/apollo-client/pull/11869#discussion_r1665511736&quot; data-og-url=&quot;https://github.com/apollographql/apollo-client/pull/11869&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/lRyUA/hyXefM63rA/s54oDYTq0ZO89P85lZ4QcK/img.png?width=1200&amp;amp;height=600&amp;amp;face=80_140_1061_532,https://scrap.kakaocdn.net/dn/ygpN1/hyXd3zboSx/J6NQl4ldIk3LvfKEZ1bX5k/img.png?width=1200&amp;amp;height=600&amp;amp;face=80_140_1061_532&quot;&gt;&lt;a href=&quot;https://github.com/apollographql/apollo-client/pull/11869#discussion_r1665511736&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://github.com/apollographql/apollo-client/pull/11869#discussion_r1665511736&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/lRyUA/hyXefM63rA/s54oDYTq0ZO89P85lZ4QcK/img.png?width=1200&amp;amp;height=600&amp;amp;face=80_140_1061_532,https://scrap.kakaocdn.net/dn/ygpN1/hyXd3zboSx/J6NQl4ldIk3LvfKEZ1bX5k/img.png?width=1200&amp;amp;height=600&amp;amp;face=80_140_1061_532');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;refactor useQuery to not use an internal class by phryneas &amp;middot; Pull Request #11869 &amp;middot; apollographql/apollo-client&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;After #11890, I'm fairly confident in this now. This needs to be reviewed commit-by commit, although a bunch of merges added additional commits at the start and end - I'm really sorry for t...&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;github.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>공부내용 공유하기</category>
      <category>apolloclient</category>
      <category>graphQL</category>
      <category>react</category>
      <author>눅눅한 피부</author>
      <guid isPermaLink="true">https://nookpi.tistory.com/205</guid>
      <comments>https://nookpi.tistory.com/205#entry205comment</comments>
      <pubDate>Sun, 13 Oct 2024 17:37:24 +0900</pubDate>
    </item>
    <item>
      <title>근황</title>
      <link>https://nookpi.tistory.com/204</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;0. 시간이 어떻게 가는지 모르겠다... 눈 깜빡이면 한 달 단위로 지나는 것 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. 최근까지 진행하던 영어 과외를 그만뒀다. 졸업이라기엔 뭐하지만... 과외로 얻을 것들을 충분히 많이 얻은 것 같아 Next step으로 가보려고 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. 회사에서 오픈소스 소스코드 톺아보기 스터디를 진행중인데 재미있는 포인트가 많아서 좋았다. lodash 코어 로직이라던지 vercel/next 레포지토리 세팅 파일에서의 인사이트라던지...&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. 오픈소스는 디스코드 채널과 컨트리뷰터들의 과도한(?) 열정으로 내 몸에 부하가 걸려 잠시 쉬어가고 있다. 최근에는 스위스, 폴란드에서 기여해주시는 개발자분들과 팀 미팅도 가졌다. 영어로 진행해서 두근두근 했는데 나름 잘 해냈다...! 그리고 왠지 2k가 넘는 별을 받고 나니 다른 오픈소스를 해보고 싶다는 욕심도 생긴다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://github.com/Jonghakseo/chrome-extension-boilerplate-react-vite&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://github.com/Jonghakseo/chrome-extension-boilerplate-react-vite&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1728736144120&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;object&quot; data-og-title=&quot;GitHub - Jonghakseo/chrome-extension-boilerplate-react-vite: Chrome Extension Boilerplate with React + Vite + Typescript&quot; data-og-description=&quot;Chrome Extension Boilerplate with React + Vite + Typescript - Jonghakseo/chrome-extension-boilerplate-react-vite&quot; data-og-host=&quot;github.com&quot; data-og-source-url=&quot;https://github.com/Jonghakseo/chrome-extension-boilerplate-react-vite&quot; data-og-url=&quot;https://github.com/Jonghakseo/chrome-extension-boilerplate-react-vite&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/M4vMx/hyXebw8UJ3/ZN6eC7FJSKcKZH8YM7ZTxk/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600,https://scrap.kakaocdn.net/dn/pFGQd/hyXd63HrEi/jIaahGEFFtQIN41izgSsf0/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600&quot;&gt;&lt;a href=&quot;https://github.com/Jonghakseo/chrome-extension-boilerplate-react-vite&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://github.com/Jonghakseo/chrome-extension-boilerplate-react-vite&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/M4vMx/hyXebw8UJ3/ZN6eC7FJSKcKZH8YM7ZTxk/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600,https://scrap.kakaocdn.net/dn/pFGQd/hyXd63HrEi/jIaahGEFFtQIN41izgSsf0/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;GitHub - Jonghakseo/chrome-extension-boilerplate-react-vite: Chrome Extension Boilerplate with React + Vite + Typescript&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Chrome Extension Boilerplate with React + Vite + Typescript - Jonghakseo/chrome-extension-boilerplate-react-vite&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;github.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;4. react-code-finder 사용자가 소리없이 500명을 넘었다. 은근슬쩍 많이 쓰는 이유를 잘 모르겠지만... 오늘 생각난김에 기능 추가(props 조회)도 해놨다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://chromewebstore.google.com/detail/react-code-finder/bbidpgoneibefablhfcnaennjkfbflmk&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://chromewebstore.google.com/detail/react-code-finder/bbidpgoneibefablhfcnaennjkfbflmk&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1728736133866&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;React code finder - Chrome 웹 스토어&quot; data-og-description=&quot;Chrome extension for React Developer&quot; data-og-host=&quot;chromewebstore.google.com&quot; data-og-source-url=&quot;https://chromewebstore.google.com/detail/react-code-finder/bbidpgoneibefablhfcnaennjkfbflmk&quot; data-og-url=&quot;https://chromewebstore.google.com/detail/react-code-finder/bbidpgoneibefablhfcnaennjkfbflmk&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/5Mi5c/hyXhSWYWuK/iqwf92cFPFz4dBkbHNXQN1/img.jpg?width=128&amp;amp;height=128&amp;amp;face=0_0_128_128&quot;&gt;&lt;a href=&quot;https://chromewebstore.google.com/detail/react-code-finder/bbidpgoneibefablhfcnaennjkfbflmk&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://chromewebstore.google.com/detail/react-code-finder/bbidpgoneibefablhfcnaennjkfbflmk&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/5Mi5c/hyXhSWYWuK/iqwf92cFPFz4dBkbHNXQN1/img.jpg?width=128&amp;amp;height=128&amp;amp;face=0_0_128_128');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;React code finder - Chrome 웹 스토어&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Chrome extension for React Developer&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;chromewebstore.google.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;5. 회사에서 CDN 캐싱 + SWR 적용 등 파트 KR 개선을 위한 작업들의 성과가 나름 눈부셔서 기분이가 좋다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;6. 7월에는 조직문화 관련된 간단한 소개?를 해달라는 요청을 받아 타 회사에 방문해서 개발팀 조직문화를 소개하는 자리를 가지기도 했는데, 좋은 조직 문화란 무엇인지 돌아볼 수 있는 좋은 기회였다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;전반적으로 심신이 안정되어 있다. 11월에 이사라는 나름 큰 이슈가 있어서 그것만 잘 넘기면 올해는 선방한게 아닐까 싶다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;올해 회고는 언제 또 쓰지?&lt;/p&gt;</description>
      <category>잡담</category>
      <author>눅눅한 피부</author>
      <guid isPermaLink="true">https://nookpi.tistory.com/204</guid>
      <comments>https://nookpi.tistory.com/204#entry204comment</comments>
      <pubDate>Sat, 12 Oct 2024 21:31:12 +0900</pubDate>
    </item>
    <item>
      <title>코드 리뷰의 원칙</title>
      <link>https://nookpi.tistory.com/203</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://soojin.ro/review/standard&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://soojin.ro/review/standard&lt;/a&gt;&lt;/p&gt;</description>
      <category>스크랩</category>
      <author>눅눅한 피부</author>
      <guid isPermaLink="true">https://nookpi.tistory.com/203</guid>
      <comments>https://nookpi.tistory.com/203#entry203comment</comments>
      <pubDate>Wed, 31 Jul 2024 02:11:36 +0900</pubDate>
    </item>
    <item>
      <title>개발팀 문화</title>
      <link>https://nookpi.tistory.com/202</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;novila-misastra-4BTjXeMwcb8-unsplash.jpg&quot; data-origin-width=&quot;6016&quot; data-origin-height=&quot;4016&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/c2X82g/btsIOUXuEhs/Aqvkv7JoB6y0wHTPKvPuYk/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/c2X82g/btsIOUXuEhs/Aqvkv7JoB6y0wHTPKvPuYk/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/c2X82g/btsIOUXuEhs/Aqvkv7JoB6y0wHTPKvPuYk/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fc2X82g%2FbtsIOUXuEhs%2FAqvkv7JoB6y0wHTPKvPuYk%2Fimg.jpg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;6016&quot; height=&quot;4016&quot; data-filename=&quot;novila-misastra-4BTjXeMwcb8-unsplash.jpg&quot; data-origin-width=&quot;6016&quot; data-origin-height=&quot;4016&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;많은 조직에서는 좋은 개발팀을 갖고 싶어 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;좋은 개발팀이라는 것은 개발자들의 생산성이 높고 리텐션이 유지되며 팀 자체가 성장하고 있어 조직의 문제 해결 능력에 지속적으로 큰 기여를 할 수 있는 팀을 말한다. 이런 팀은 외부로부터의 인재 유입도 비교적 수월하며 새로운 아이디어와 혁신에도 비교적 열려있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;좋은 개발팀을 갖고 싶은 조직은 좋은 개발 문화를 통해 현재의 팀을 좋은 개발팀으로 만들고 유지하길 원한다. Stackoverflow의 서베이나 개발자들을 대상으로 한 여러 조사에서도 나타나듯 개발팀의 문화는 개발자들이 해당 조직에 머무는 이유 중 큰 비중을 차지한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;좋은 개발 문화란 무엇일까?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;모든 조직에 들어맞는 법칙은 없겠지만 일반적으로는 다음과 같은 것들이 물망에 오른다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. 서로에 대한 신뢰&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. 지속적인 학습&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. 실수에 관대하고 실수를 통한 성장을 할 수 있는 환경&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;4. 자기 주도적인 업무&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자 좋다. 이제 매니저의 관점에서 위 4가지를 만들기 위해 노력해 보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;'&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;서로에 대한&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;신뢰? 이건 시간이 해결해 줄 일이니까 패스.'&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;'지속적인 학습? 다음 주까지 팀원들에게 '모던 자바스크립트 Deep Dive'를 읽어오라고 해야겠다.'&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;'실수에 관대하고 실수를 통한 성장을 할 수 있는 환경? 이제 버그 낸 사람한테 조금 덜 뭐라고 해야겠다.'&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;'자기 주도적인 업무? 이건 당연히 알아서 잘해야지.'&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;웃자고 적어놓은 내용이지만 실제로 위 항목들을 보고 실제로 비슷하게 생각하는 사람이 있을까 두렵다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt; 위 항목들은 누군가 시켜서 되는 일이 아니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;각 팀원이 본인의 업무에 대해 책임감 있는 모습을 보인다면 상호 간의 신뢰가 쌓일 것이고 성장하고자 하는 욕구가 있다면 지속적인 학습을 위해 자발적으로 스터디 그룹 등을 만들 것이며 실수에 관대하고 실수를 통한 성장을 할 수 있는 환경은 코드 리뷰, 테스트 코드, 건강한 장애 부검과 같은 건설적인 프로세스를 통해 갖춰지는 것이며 자기 주도적인 업무 역시 본인의 성장 욕구와 인정 욕구, 산출물에 대한 자부심 등을 바탕으로 스스로 해 나가는 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;조직/매니징의 관점에서는 이러한 문화를 조성하기 위해 어떤 일을 할 수 있을까?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;가장 중요하고 또 중요한 첫 번째는 바로 '채용'이다. 실력 있고 책임감 있으며 성장 욕구가 있는 개발자를 채용하는 것이 좋은 개발 문화를 만드는 데 기여하는 비중은 90%가 넘는다고 생각한다. 그다음으로 해볼 수 있는 것들은 앞서 말한 프로세스의 도입, 그리고 그러한 프로세스를 도입하는데 필요한 지원일 것이다. 스터디 그룹을 만든다고 하면 리소스에 대한 지원 등을 해주고 업무에 대한 새로운 툴 도입이나 변화에 대한 시도를 응원해 주는 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;'공부는 좋습니다. 그런데 업무 시간에는 좀... 형평성이...' 굳이 이런 이야기를 하며 기껏 잘 채용한 개발자들의 의욕을 꺾는 조직을 실제로 본 기억이 있다. 현명한 선택이 아니라고 생각한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;물론 조직 내에서 개인으로서 비관적인 이야기와 부정적인 이야기들로 동료 개발자들의 의욕과 사기를 꺾는 사람들도 있다. 또한 발전 의지가 없고 실력이 낮아 동료 개발자들에게 부정적인 영향을 미치는 사람들도 있다. 이러한 사람들은 조직의 관점에서는 빠르게 털고 갈 수 있다면 털고 가는 게 좋다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;말은 쉽다. 현실은 뛰어난 개발자 한 둘, 비관적인 개발자 한 둘, 애매한 개발자 한 둘이 모여 개발팀이 만들어진다. 그리고 지금 저 사람을 내보내면 대체할 인력도 없다. 자, 이런 상황에서 당신이 매니저라면 어떤 것을 해볼 수 있을까?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;첫 번째. 이러니 저러니 해도 인사가 만사다. 좋은 사람을 채용하기 위해 꾸준히 노력하고 발품을 팔아야 한다. 100점짜리 개발자를 데려올 생각은 하지 말자. 뚜렷한 장점이 있고 현재의 개발팀에 좋은 자극을 줄 수 있는 포인트가 있다면 적극적으로 고려해 보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;두 번째. 조직 내에서의 건설적인 프로세스를 구축하기 위해 노력해야 한다. 코드 리뷰, 테스트 코드, 회고 문화, 장애 부검 등 소위 좋은 개발 문화를 만드는 데 도움이 된다고 알려진 것들은 시중에 널리고 널렸다. 현실적으로 조직에 도입할 수 있고 필요하다고 생각되는 것부터 하나씩 차근차근 도입해 보자. 이 과정에서의 반발이 있다면 최대한 점진적으로 도입해 보고 설득력이 부족하다면 본인의 신뢰자산을 레버리지로 사용하자. 끌어다 쓸 신뢰자산이 없다면 전적으로 본인의 책임이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;세 번째. 비관적인 태도를 가진 팀원과 적극적으로 소통하자. 고민이 있다면 들어주고 개선이 필요하다고 생각하는 부분에 대해 그렇게 생각한 이유와 함께 &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;솔직하게&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;피드백을 해야 한다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;네 번째. 본인이 목표로 하는 개발팀의 비전과 이상향에 대해서 끊임없이 구성원들에게 이야기해야 한다. 뛰어난 개발자들은 공감할 것이고 애매한 개발자들은 다소 귀찮아 할 수 있지만 그래도 맞는 방향이라고 생각할 것이다. 비관적인 개발자들은 투덜대다 조직을 떠나거나 적극적으로 합류(전향?)할 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;개발자들이 행복하다고 좋은 개발팀은 아니지만 좋은 개발 문화를 구축하면 그 결과로 개발자들은 더 행복해져야 한다. 그것이 인류 보편적인 가치에 부합해서가 아니라 좋은 개발 문화로 개개인이 행복한 상태가 되어야 그것이 지속 가능하기 때문이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이런저런 사족이 길었지만 결국 돌고 돌아 제일&lt;span&gt;&amp;nbsp;&lt;/span&gt;중요한 건&lt;span&gt;&amp;nbsp;&lt;/span&gt;사람이다. 물질의 기본 단위가 원자이듯 조직은 그 조직을 구성하는 사람들의 구성에 따라 성질이 완전히 달라진다. 나는 '사람은 바꿀 수 없지만 스스로 바뀔 순 있다.'라고 생각한다. 사람을 바꾸려고 노력하기보다는 바뀔 수 있는 환경과 분위기를 제안하고 구축하는데 집중하는 것이 조직 관점에서도 개개인의 관점에서도 경제적인 선택일 것이다.&lt;/p&gt;</description>
      <category>잡담</category>
      <category>개발팀</category>
      <category>문화</category>
      <author>눅눅한 피부</author>
      <guid isPermaLink="true">https://nookpi.tistory.com/202</guid>
      <comments>https://nookpi.tistory.com/202#entry202comment</comments>
      <pubDate>Sat, 27 Jul 2024 00:30:34 +0900</pubDate>
    </item>
    <item>
      <title>React에서의 Modal</title>
      <link>https://nookpi.tistory.com/201</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;리액트의 모달 컴포넌트는 다양한 방식으로 사용된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;모달 렌더링에 대한 책임을 버튼 컴포넌트에 넘기는 방식. 모달의 열림/닫힘 상태를 버튼에서 관리하여 보일러 플레이트 코드를 줄이기 좋다. Props에 대한 타입 전달이 자연스러럽게 되는 장점도 있다.&lt;/p&gt;
&lt;pre id=&quot;code_1720096475930&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;ModalControlButton Modal={ShareModal} modalProps={{ url }}&amp;gt;Share!&amp;lt;/ModalControlButton&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;모달의 열림/닫힘 상태를 제어하면서 모달 컴포넌트를 렌더링하는 방식. 가장 일반적인 방식이고 러닝커브가 없다.&lt;/p&gt;
&lt;pre id=&quot;code_1720096553970&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;const modal = useModal();
//...
&amp;lt;Button onClick={modal.open}&amp;gt;Share!&amp;lt;/Button&amp;gt;
&amp;lt;ShareModal isOpen={modal.isOpen} close={modal.close} url={url} /&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;모달을 함수 호출 시점에 렌더링하는 방식. 호출부와 렌더링 코드가 붙어있어 응집도가 높은 편이다.&lt;/p&gt;
&lt;pre id=&quot;code_1720096630046&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;const { openModal } = useModal();

const handleClick = () =&amp;gt; openModal(ShareModal, { url });
//...
&amp;lt;Button onClick={handleClick}&amp;gt;Share!&amp;lt;/Button&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;모달을 함수 호출 시점에 렌더링하고 JSX구조를 유지하는 방식. 위의 방식과 유사하다.&lt;/p&gt;
&lt;pre id=&quot;code_1720096699559&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import modal from &quot;@~&quot;;
//...
const handleClick = () =&amp;gt; modal.open((isOpen, close) =&amp;gt; &amp;lt;ShareModal isOpen={isOpen} close={close} url={{ url }} /&amp;gt;, );
//...
&amp;lt;Button onClick={handleClick}&amp;gt;Share!&amp;lt;/Button&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;모달 구현체는 일반적으로 Portal을 사용하거나 전용 Provider를 통해 렌더링 위치를 제어한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;`createRoot().render()`를 직접 호출해서 전용 FiberTree를 만들어 제어하는 방식도 가능하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 중에서 개인적으로 편하게 사용하고 있는 ModalControlButton의 구현체를 살펴보자&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1720097928487&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import { FC, useCallback, useState } from 'react';
import { Button, type ButtonProps } from '@/compoents/Button';

type PropsFrom&amp;lt;TComponent&amp;gt; = TComponent extends FC&amp;lt;infer Props&amp;gt; ? Props : never;

type WithModalProps&amp;lt;ModalRenderer&amp;gt; = {} extends RemoveOptional&amp;lt;
  Omit&amp;lt;PropsFrom&amp;lt;ModalRenderer&amp;gt;, 'close'&amp;gt;
&amp;gt;
  ? { modalProps?: Omit&amp;lt;PropsFrom&amp;lt;ModalRenderer&amp;gt;, 'close' | 'isOpen'&amp;gt; }
  : { modalProps: Omit&amp;lt;PropsFrom&amp;lt;ModalRenderer&amp;gt;, 'close' | 'isOpen'&amp;gt; };

type ModalControlButtonProps&amp;lt;ModalRenderer&amp;gt; = {
  ModalRenderer: ModalRenderer;
} &amp;amp; WithModalProps&amp;lt;ModalRenderer&amp;gt; &amp;amp;
  Omit&amp;lt;ButtonProps, 'onClick'&amp;gt;;

export default function ModalControlButton&amp;lt;
  ModalRenderer extends FC&amp;lt;any &amp;amp; { close: () =&amp;gt; unknown }&amp;gt;,
&amp;gt;({ ModalRenderer, children, modalProps, ...restProps }: ModalControlButtonProps&amp;lt;ModalRenderer&amp;gt;) {
  const [isOpen, setIsOpen] = useState&amp;lt;boolean&amp;gt;(false);

  const open = useCallback(() =&amp;gt; {
    setIsOpen(true);
  }, []);

  const close = useCallback(() =&amp;gt; {
    setIsOpen(false);
  }, []);

  return (
    &amp;lt;&amp;gt;
      &amp;lt;Button onClick={open} {...restProps}&amp;gt;
        {children}
      &amp;lt;/Button&amp;gt;
      &amp;lt;ModalRenderer {...(modalProps as any)} isOpen={isOpen} close={close} /&amp;gt;
    &amp;lt;/&amp;gt;
  );
}

/**
 * 타입 T의 키 K를 조건부로 포함하는 맵 타입을 작성합니다.
 * {}가 타입 T에서 K 키를 가진 속성을 Pick한 것에 확장할 수 있는 경우 never를 반환하고, 그렇지 않으면 K를 반환합니다.
 * 결국 필수 속성만 K로 선정됩니다.
 */
type RequiredKeys&amp;lt;T&amp;gt; = {
  [K in keyof T]-?: {} extends Pick&amp;lt;T, K&amp;gt; ? never : K;
}[keyof T];

/**
 * 주어진 타입 T에서 선택적 속성을 제거한 새 타입을 생성합니다.
 * 필수 속성들로만 이루어진 타입을 반환합니다.
 */
type RemoveOptional&amp;lt;T&amp;gt; = Pick&amp;lt;T, RequiredKeys&amp;lt;T&amp;gt;&amp;gt;;&lt;/code&gt;&lt;/pre&gt;</description>
      <category>공부내용 공유하기</category>
      <category>modal</category>
      <category>react</category>
      <author>눅눅한 피부</author>
      <guid isPermaLink="true">https://nookpi.tistory.com/201</guid>
      <comments>https://nookpi.tistory.com/201#entry201comment</comments>
      <pubDate>Thu, 4 Jul 2024 21:53:50 +0900</pubDate>
    </item>
    <item>
      <title>Shout out from Medium</title>
      <link>https://nookpi.tistory.com/200</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;Star 8.5K, 3.2K 녀석들... 딱 기다려라... 내가 간다....&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://tamfocus.medium.com/my-journey-to-the-best-chrome-extension-boilerplate-8b0ae65b95c1&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://tamfocus.medium.com/my-journey-to-the-best-chrome-extension-boilerplate-8b0ae65b95c1&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1716270452787&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;My journey to the best Chrome Extension boilerplate&quot; data-og-description=&quot;Introduction&quot; data-og-host=&quot;tamfocus.medium.com&quot; data-og-source-url=&quot;https://tamfocus.medium.com/my-journey-to-the-best-chrome-extension-boilerplate-8b0ae65b95c1&quot; data-og-url=&quot;https://tamfocus.medium.com/my-journey-to-the-best-chrome-extension-boilerplate-8b0ae65b95c1&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/0eACV/hyV6i5cBJJ/CiXKku2GCfORM6VlMqbXnK/img.jpg?width=1200&amp;amp;height=563&amp;amp;face=0_0_1200_563,https://scrap.kakaocdn.net/dn/CYh89/hyV9VtJrDj/ktN7PvtGyblKdsk4X7qbq0/img.png?width=1358&amp;amp;height=759&amp;amp;face=0_0_1358_759,https://scrap.kakaocdn.net/dn/b2zGvK/hyV55Y9EtO/CvIJQcwPTiNBoucgOgQ1Dk/img.png?width=1358&amp;amp;height=697&amp;amp;face=0_0_1358_697&quot;&gt;&lt;a href=&quot;https://tamfocus.medium.com/my-journey-to-the-best-chrome-extension-boilerplate-8b0ae65b95c1&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://tamfocus.medium.com/my-journey-to-the-best-chrome-extension-boilerplate-8b0ae65b95c1&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/0eACV/hyV6i5cBJJ/CiXKku2GCfORM6VlMqbXnK/img.jpg?width=1200&amp;amp;height=563&amp;amp;face=0_0_1200_563,https://scrap.kakaocdn.net/dn/CYh89/hyV9VtJrDj/ktN7PvtGyblKdsk4X7qbq0/img.png?width=1358&amp;amp;height=759&amp;amp;face=0_0_1358_759,https://scrap.kakaocdn.net/dn/b2zGvK/hyV55Y9EtO/CvIJQcwPTiNBoucgOgQ1Dk/img.png?width=1358&amp;amp;height=697&amp;amp;face=0_0_1358_697');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;My journey to the best Chrome Extension boilerplate&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Introduction&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;tamfocus.medium.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>스크랩</category>
      <category>오픈소스</category>
      <author>눅눅한 피부</author>
      <guid isPermaLink="true">https://nookpi.tistory.com/200</guid>
      <comments>https://nookpi.tistory.com/200#entry200comment</comments>
      <pubDate>Tue, 21 May 2024 14:47:58 +0900</pubDate>
    </item>
    <item>
      <title>네이버 사다리와 함께 했던 워크숍 (3)</title>
      <link>https://nookpi.tistory.com/199</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;저번 편에서 사다리를 원하는 대로 그리기 위해 minified 된 js 파일을 파헤쳐봤다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://nookpi.tistory.com/198&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://nookpi.tistory.com/198&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번 편에서는 기존 사다리 게임 js 파일을 내가 수정한 js 파일로 교체하는 방법에 대해 알아보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;원래는 네이버 검색으로 노출되는 사다리 게임 페이지 자체를 클론코딩(...)하려고 했으나, 동료분의 조언으로 크롬 익스텐션을 통해 주입하는 방법으로 선회했다. 이 방법이 훨씬 간단할 뿐 아니라 눈치채기도 어렵다...!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;유일한 단점이라면 익스텐션이 사전에 설치되어 있어야 한다는 점인데 어차피 클론코딩으로 제공하는 페이지 역시 동일한 이슈가 있기 때문에 망설일 이유는 없었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;내가 누구?&lt;b&gt; 크롬 익스텐션 보일러 플레이트 오너&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;figure id=&quot;og_1711631469937&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;object&quot; data-og-title=&quot;GitHub - Jonghakseo/chrome-extension-boilerplate-react-vite: Chrome Extension Boilerplate with React + Vite + Typescript&quot; data-og-description=&quot;Chrome Extension Boilerplate with React + Vite + Typescript - Jonghakseo/chrome-extension-boilerplate-react-vite&quot; data-og-host=&quot;github.com&quot; data-og-source-url=&quot;https://github.com/Jonghakseo/chrome-extension-boilerplate-react-vite&quot; data-og-url=&quot;https://github.com/Jonghakseo/chrome-extension-boilerplate-react-vite&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/cfh7q6/hyVGPOwciI/BOe5942RSNWLikCzV3rvo1/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600&quot;&gt;&lt;a href=&quot;https://github.com/Jonghakseo/chrome-extension-boilerplate-react-vite&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://github.com/Jonghakseo/chrome-extension-boilerplate-react-vite&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/cfh7q6/hyVGPOwciI/BOe5942RSNWLikCzV3rvo1/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;GitHub - Jonghakseo/chrome-extension-boilerplate-react-vite: Chrome Extension Boilerplate with React + Vite + Typescript&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Chrome Extension Boilerplate with React + Vite + Typescript - Jonghakseo/chrome-extension-boilerplate-react-vite&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;github.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;바로 포크 후 익스텐션을 만들어보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;유저가 보는 페이지에서 실행이 되어야 하기 때문에 실행 컨텍스는 &lt;a href=&quot;https://developer.chrome.com/docs/extensions/develop/concepts/content-scripts?hl=ko&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;콘텐츠 스크립트&lt;/a&gt;로 제한된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;불필요한 다른 페이지들은 다 지우고, content 폴더만 남겨둔다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1편 마지막에 파악했듯, 새로운 사다리 게임을 호출하기 위해서는 전역 객체인 window에 있는 require 메서드를 사용해야 한다. 콘텐츠 스크립트의 실행 환경은 &quot;ISOLATED&quot;와&lt;span style=&quot;background-color: #ffffff; color: #1e1e21; text-align: start;&quot;&gt; &quot;MAIN&quot;으로 나눠지는데, 각 환경에서 접근 가능한 리소스의 범위가 다르다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #1e1e21; text-align: start;&quot;&gt;&quot;ISOLATED&quot; 환경에서는 chrome api에 접근이 가능하지만,  타 모듈에서 선언한 전역 객체 등에 접근할 수 없다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #1e1e21; text-align: start;&quot;&gt;반면, &quot;MAIN&quot; 환경에서는 전역 객체에 접근이 가능하지만 chrome api에 접근이 불가능하다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;figure id=&quot;og_1711631709137&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;chrome.scripting &amp;nbsp;|&amp;nbsp; API &amp;nbsp;|&amp;nbsp; Chrome for Developers&quot; data-og-description=&quot;이 페이지는 Cloud Translation API를 통해 번역되었습니다. chrome.scripting 컬렉션을 사용해 정리하기 내 환경설정을 기준으로 콘텐츠를 저장하고 분류하세요. 설명 chrome.scripting API를 사용하여 다양한 &quot; data-og-host=&quot;developer.chrome.com&quot; data-og-source-url=&quot;https://developer.chrome.com/docs/extensions/reference/api/scripting?hl=ko#type-ExecutionWorld&quot; data-og-url=&quot;https://developer.chrome.com/docs/extensions/reference/api/scripting?hl=ko&quot; data-og-image=&quot;&quot;&gt;&lt;a href=&quot;https://developer.chrome.com/docs/extensions/reference/api/scripting?hl=ko#type-ExecutionWorld&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://developer.chrome.com/docs/extensions/reference/api/scripting?hl=ko#type-ExecutionWorld&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url();&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;chrome.scripting &amp;nbsp;|&amp;nbsp; API &amp;nbsp;|&amp;nbsp; Chrome for Developers&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;이 페이지는 Cloud Translation API를 통해 번역되었습니다. chrome.scripting 컬렉션을 사용해 정리하기 내 환경설정을 기준으로 콘텐츠를 저장하고 분류하세요. 설명 chrome.scripting API를 사용하여 다양한&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;developer.chrome.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;동적으로 특정 js 파일의 경로를 얻기 위해서는 chrome.runtime.getURL api를 사용해야 하니 &quot;&lt;span style=&quot;background-color: #ffffff; color: #1e1e21; text-align: start;&quot;&gt;ISOLATED&quot;환경이 필요하&lt;/span&gt;고, 그 스크립트를 로드하려면 &quot;MAIN&quot; 환경에서 실행되는 스크립트가 필요하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;둘 다 사용하고 postMessage를 통해 소통하면 되잖아라고 생각했다면? 정답이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2024-03-28 오후 10.18.45.png&quot; data-origin-width=&quot;978&quot; data-origin-height=&quot;516&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bhrQuG/btsF9zB08to/wtfIlS3KBh843SwGHdaaS0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bhrQuG/btsF9zB08to/wtfIlS3KBh843SwGHdaaS0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bhrQuG/btsF9zB08to/wtfIlS3KBh843SwGHdaaS0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbhrQuG%2FbtsF9zB08to%2FwtfIlS3KBh843SwGHdaaS0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;500&quot; height=&quot;264&quot; data-filename=&quot;스크린샷 2024-03-28 오후 10.18.45.png&quot; data-origin-width=&quot;978&quot; data-origin-height=&quot;516&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;매니페스트 설정에서 한 스크립트의 world를 &quot;MAIN&quot;으로 바꿔준 다음, &quot;ISOLATED&quot; 환경에서 사다리 파일의 경로를 메시지로 쏴주자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://github.com/Jonghakseo/injected-sadari/blob/main/src/pages/content/ui/app.tsx#L12C1-L20C7&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://github.com/Jonghakseo/injected-sadari/blob/main/src/pages/content/ui/app.tsx#L12C1-L20C7&lt;/a&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1711632022482&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;window.postMessage(
  {
    type: 'LADDER_URL',
    url: chrome.runtime.getURL('ladder.js'),
    victim,
    target,
  } satisfies InitMessage,
  '*',
);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;받는 곳에서는 url을 받아서 실행만 해주면 간단하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://github.com/Jonghakseo/injected-sadari/blob/main/src/pages/content/injected/index.ts#L2-L12&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://github.com/Jonghakseo/injected-sadari/blob/main/src/pages/content/injected/index.ts#L2-L12&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1711632109541&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;window.addEventListener('message', event =&amp;gt; {
  if (event.data.type === 'LADDER_URL') {
    const message: InitMessage = event.data;
    window.__injected_sadari_victim = message.victim;
    window.__injected_sadari_target = message.target;
    window._nx_js_load(message.url, function () {});
  }
});&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;참고로 _nx_js_load 메서드는 require([url]) 과 동일한 역할을 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사다리 파일도 살짝 수정이 필요했는데, 아래와 같이 타깃을 초기화하는 로직과 기존 캠버스를 삭제하고 새 캠버스를 생성하는 로직을 넣었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1711632194160&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;//! 전역으로 공유
let VICTIM = window.__injected_sadari_victim;
let TARGET = window.__injected_sadari_target;

//! 기존 캔버스 삭제
document.getElementById('ladders_canvas').remove();
//! 새로운 캔버스 생성
const newCanvas = document.createElement('canvas');
newCanvas.id = 'ladders_canvas';
newCanvas.width = 580;
newCanvas.height = 320;
document.getElementById('ladders').appendChild(newCanvas);

// 아래는 기존 사다리 코드 + 원하는 결과 나오도록 수정한 부분 포함
// ...&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;완성..!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;figure data-ke-type=&quot;video&quot; data-ke-style=&quot;alignCenter&quot; data-video-host=&quot;kakaotv&quot; data-video-url=&quot;https://tv.kakao.com/v/445612617&quot; data-video-thumbnail=&quot;https://scrap.kakaocdn.net/dn/eFv98/hyVGJU5jZC/22apK8k0WK4obn07CvPCN1/img.jpg?width=1726&amp;amp;height=1080&amp;amp;face=0_0_1726_1080,https://scrap.kakaocdn.net/dn/hWRBy/hyVGJHw7Xe/BSdRfHczREx9EmyL7wYT91/img.jpg?width=1726&amp;amp;height=1080&amp;amp;face=0_0_1726_1080&quot; data-video-width=&quot;860&quot; data-video-height=&quot;538&quot; data-video-origin-width=&quot;860&quot; data-video-origin-height=&quot;538&quot; data-ke-mobilestyle=&quot;widthContent&quot; data-video-play-service=&quot;daum_tistory&quot; data-original-url=&quot;&quot; data-video-title=&quot;&quot;&gt;&lt;iframe src=&quot;https://play-tv.kakao.com/embed/player/cliplink/445612617?service=daum_tistory&quot; width=&quot;860&quot; height=&quot;538&quot; frameborder=&quot;0&quot; allowfullscreen=&quot;true&quot;&gt;&lt;/iframe&gt;
&lt;figcaption style=&quot;display: none;&quot;&gt;&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이후 고도화를 통해 로컬 서버를 띄워놓고 실시간으로 타깃과 키워드를 바꿀 수 있게 추가로 구현했으나... 아쉽게도 그 기능을 쓸 상황은 나오지 않았다...ㅋㅋㅋ&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;저녁 식사 설거지에 당첨된 부대표님이 주섬주섬 고무장갑을 끼는 모습이 안타까워 조작된 사다리라는 걸 다 실토해 버렸다. 역시 장난은 준비하면서 키득거리는 시간이 더 즐거운 것 같다. :)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래도 덕분에 워크샵에서 부대표님 노래도 듣고... 근래 했던 장난 중에 재일 재미있었다.&lt;/p&gt;</description>
      <category>취미로 하는 개발</category>
      <author>눅눅한 피부</author>
      <guid isPermaLink="true">https://nookpi.tistory.com/199</guid>
      <comments>https://nookpi.tistory.com/199#entry199comment</comments>
      <pubDate>Thu, 28 Mar 2024 22:32:10 +0900</pubDate>
    </item>
    <item>
      <title>네이버 사다리와 함께 했던 워크숍 (2)</title>
      <link>https://nookpi.tistory.com/198</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;지난 1편에 이어 계속 진행...!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://nookpi.tistory.com/197&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://nookpi.tistory.com/197&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;본격적으로 사다리게임 핵심 로직이 들어있는 js 파일을 분석해 보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://ssl.pstatic.net/sstatic/fe/sfe/ladder-game/ladders_230912.js&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://ssl.pstatic.net/sstatic/fe/sfe/ladder-game/ladders_230912.js&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;내가 개인적으로 minify 로직을 통해 난독화된 코드를 볼 때 염두에 두는 부분은 다음과 같다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;난독화 된 코드는 기본적으로 읽고 해석하기 어렵기 때문에, 상수와 같이 바꿀 수 없는 지점을 찾는 게 우선이다.&lt;/li&gt;
&lt;li&gt;IDE의 도움을 받아 코드의 형태를 정리하는 것은 큰 도움이 된다.&lt;/li&gt;
&lt;li&gt;어떤 역할을 하는 변수/함수인지 유추되었다면, 이름을 바꿔두는 것이 좋다.&lt;/li&gt;
&lt;li&gt;class 혹은 prototype 메서드의 이름은 난독화 대상에서 빠지는 경우가 많아, 메소드 명을 참고하는 것이 도움이 된다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;코드를 복사해서 IDE에 넣고 줄 바꿈을 해보니 7000줄이 넘는 코드가 나온다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;겁먹지 말고 상수부터 찾아보자. 우리가 알 수 있는 상수는 뭐가 있을까? 우선 1편에서 확인했듯, 모듈의 동작 방식은 특정 id를 가진 캔버스를 찾아 렌더링을 하는 것으로 유추된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그렇다면 캔버스의 아이디를 먼저 찾아보자.&lt;/p&gt;
&lt;pre id=&quot;code_1711263008177&quot; class=&quot;javascript&quot; style=&quot;background-color: #f8f8f8; color: #383a42; text-align: start;&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;javascript&quot;&gt;&lt;code&gt;    var t = (window.LadderGame = function () {
        (o = document.getElementById('ladders_canvas')),
          (nhn.search.ladders.images = nhn.search.ladders.images || {}),
          (nhn.search.ladders.ss = nhn.search.ladders.ss || {}),
          this.handleComplete();
      }),&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;'laddeers_canvas'로 찾은 캔버스 객체는 o라는 변수에 할당되고 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;o의 이름을 canvasElement로 바꾸고 사용하는 곳을 찾아보자.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2024-03-24 오후 3.54.11.png&quot; data-origin-width=&quot;1378&quot; data-origin-height=&quot;368&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bB7RJ3/btsF3la9Zkr/DQ7JvY2Bl76SF3uvJnyCRK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bB7RJ3/btsF3la9Zkr/DQ7JvY2Bl76SF3uvJnyCRK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bB7RJ3/btsF3la9Zkr/DQ7JvY2Bl76SF3uvJnyCRK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbB7RJ3%2FbtsF3la9Zkr%2FDQ7JvY2Bl76SF3uvJnyCRK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1378&quot; height=&quot;368&quot; data-filename=&quot;스크린샷 2024-03-24 오후 3.54.11.png&quot; data-origin-width=&quot;1378&quot; data-origin-height=&quot;368&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;canvasElement를 사용하는 코드들을 보니 익숙한 네이밍의 함수들이 보이는데? 살짝 함수들을 접어서 네이밍을 살펴보자.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2024-03-24 오후 3.55.57.png&quot; data-origin-width=&quot;840&quot; data-origin-height=&quot;818&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bpIoA2/btsF3mVsuOy/XvG4liOKGNsO4W2KVhTAC0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bpIoA2/btsF3mVsuOy/XvG4liOKGNsO4W2KVhTAC0/img.png&quot; data-alt=&quot;아주 직관적이다&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bpIoA2/btsF3mVsuOy/XvG4liOKGNsO4W2KVhTAC0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbpIoA2%2FbtsF3mVsuOy%2FXvG4liOKGNsO4W2KVhTAC0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;500&quot; height=&quot;487&quot; data-filename=&quot;스크린샷 2024-03-24 오후 3.55.57.png&quot; data-origin-width=&quot;840&quot; data-origin-height=&quot;818&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;아주 직관적이다&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;네이밍이 이렇게 중요하다. C는 유저의 클릭 이벤트를 받고 사다리를 실질적으로 그리는 등, 코어 로직을 담고 있는 것으로 보인다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;내 이목을 끄는 부분은 사다리를 실질적으로 그리는 drawladder  함수였다. 카멜 케이스가 아닌 부분은 오타인 것 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사다리를 만들 때 내가 원하는 대로 만들 수 있으면 그다음 로직은 굳이 건드릴 필요도 없다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2024-03-24 오후 4.01.21.png&quot; data-origin-width=&quot;1338&quot; data-origin-height=&quot;286&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/csEobs/btsF1dy0QjF/z3Jp5tykf78gdCAO4GopVk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/csEobs/btsF1dy0QjF/z3Jp5tykf78gdCAO4GopVk/img.png&quot; data-alt=&quot;사다리 시작시 빈 텍스트가 없으면 사다리를 그린다&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/csEobs/btsF1dy0QjF/z3Jp5tykf78gdCAO4GopVk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcsEobs%2FbtsF1dy0QjF%2Fz3Jp5tykf78gdCAO4GopVk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1338&quot; height=&quot;286&quot; data-filename=&quot;스크린샷 2024-03-24 오후 4.01.21.png&quot; data-origin-width=&quot;1338&quot; data-origin-height=&quot;286&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;사다리 시작시 빈 텍스트가 없으면 사다리를 그린다&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;makeLoadder? 이것도 makeLadder의 오타인가? 아무래도 사람이 하는 일이다 보니 오타가 없을 수 없다. 일단 눈에 거슬리니&amp;nbsp;함수 이름을 makeLadder로 바꿔주고 내부 로직을 보자.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2024-03-24 오후 4.03.27.png&quot; data-origin-width=&quot;1624&quot; data-origin-height=&quot;220&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bixu6V/btsF1UljMeL/5oIjHmlPGPZrEdTJpJLok1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bixu6V/btsF1UljMeL/5oIjHmlPGPZrEdTJpJLok1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bixu6V/btsF1UljMeL/5oIjHmlPGPZrEdTJpJLok1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbixu6V%2FbtsF1UljMeL%2F5oIjHmlPGPZrEdTJpJLok1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1624&quot; height=&quot;220&quot; data-filename=&quot;스크린샷 2024-03-24 오후 4.03.27.png&quot; data-origin-width=&quot;1624&quot; data-origin-height=&quot;220&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;제대로 찾은 것 같다! 그런데 여기서 사다리 결과를 어떻게 확인하지...?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위에서 찾은 메서드 중 handleViewResultClick을 확인해 보자.&lt;/p&gt;
&lt;pre id=&quot;code_1711264177116&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;handlerViewResultClick: function (t) {
  d(0, a), (b = !0), I &amp;amp;&amp;amp; I.remove(), (I = null);
  const e = u.ladderItems.length;
  T = [];
  for (let i = 0; i &amp;lt; e; i++) T.push(u.ladderItems[i].itemLineNum);
  u &amp;amp;&amp;amp; (u.remove(), u.destroy(), (u = null)), (c = new k());
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;좋아... 딱 보니 모르겠다...&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래도 중요한 힌트가 몇 가지 있는 것 같은데, 일단 결과를 보여주는 페이지로 이동하는 버튼이니 만큼 cleanup 로직들이 섞여있는 것이 보이고 T = []에 결과를 넣는 부분이 눈에 띈다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사다리의 줄 개수만큼 반복문을 돌면서 결과를 넣어주는 것 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그렇다면 ladderItems 내부에 결과가 있을 가능성이 높다. 디버거로 테스트를 해보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2024-03-24 오후 4.16.43.png&quot; data-origin-width=&quot;546&quot; data-origin-height=&quot;302&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/VEHyf/btsF2T0ixAj/SOEpgRektLqKvFernZiqW0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/VEHyf/btsF2T0ixAj/SOEpgRektLqKvFernZiqW0/img.png&quot; data-alt=&quot;일단 몇 번째 줄에 해당하는지 보이고&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/VEHyf/btsF2T0ixAj/SOEpgRektLqKvFernZiqW0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FVEHyf%2FbtsF2T0ixAj%2FSOEpgRektLqKvFernZiqW0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;300&quot; height=&quot;166&quot; data-filename=&quot;스크린샷 2024-03-24 오후 4.16.43.png&quot; data-origin-width=&quot;546&quot; data-origin-height=&quot;302&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;일단 몇 번째 줄에 해당하는지 보이고&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2024-03-24 오후 4.20.03.png&quot; data-origin-width=&quot;368&quot; data-origin-height=&quot;174&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cZrwnr/btsF2ECh7xV/I0bQF1V8mnYSLc0WX7Appk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cZrwnr/btsF2ECh7xV/I0bQF1V8mnYSLc0WX7Appk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cZrwnr/btsF2ECh7xV/I0bQF1V8mnYSLc0WX7Appk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcZrwnr%2FbtsF2ECh7xV%2FI0bQF1V8mnYSLc0WX7Appk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;300&quot; height=&quot;142&quot; data-filename=&quot;스크린샷 2024-03-24 오후 4.20.03.png&quot; data-origin-width=&quot;368&quot; data-origin-height=&quot;174&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2024-03-24 오후 4.20.32.png&quot; data-origin-width=&quot;394&quot; data-origin-height=&quot;148&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/deXUZY/btsF1aCjlfS/NsU2SdrgzLnCo0KQP8SqRk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/deXUZY/btsF1aCjlfS/NsU2SdrgzLnCo0KQP8SqRk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/deXUZY/btsF1aCjlfS/NsU2SdrgzLnCo0KQP8SqRk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdeXUZY%2FbtsF1aCjlfS%2FNsU2SdrgzLnCo0KQP8SqRk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;300&quot; height=&quot;113&quot; data-filename=&quot;스크린샷 2024-03-24 오후 4.20.32.png&quot; data-origin-width=&quot;394&quot; data-origin-height=&quot;148&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;itemTargetText.text 에는 사다리 아래쪽 텍스트가 보이고, nameTargetText.text에는 사다리 위쪽의 텍스트가 보인다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2024-03-24 오후 4.21.03.png&quot; data-origin-width=&quot;288&quot; data-origin-height=&quot;144&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bbREtA/btsF3EBDRv2/ejp06AN9kXYJAPstwPIBfk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bbREtA/btsF3EBDRv2/ejp06AN9kXYJAPstwPIBfk/img.png&quot; data-alt=&quot;실제 사다리 결과와 동일하다&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bbREtA/btsF3EBDRv2/ejp06AN9kXYJAPstwPIBfk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbbREtA%2FbtsF3EBDRv2%2Fejp06AN9kXYJAPstwPIBfk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;288&quot; height=&quot;144&quot; data-filename=&quot;스크린샷 2024-03-24 오후 4.21.03.png&quot; data-origin-width=&quot;288&quot; data-origin-height=&quot;144&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;실제 사다리 결과와 동일하다&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 laddersItems에 결과를 넣어주는 부분만 찾으면 된다!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해당 코드는 아까 봤던 drawladder 메서드 안에 위치해 있는데, 전체 코드는 다음과 같다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2024-03-24 오후 4.23.41.png&quot; data-origin-width=&quot;1832&quot; data-origin-height=&quot;1032&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bVdcKJ/btsF0H1paik/CSQfDlL0bNAfRnHpGPwp5K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bVdcKJ/btsF0H1paik/CSQfDlL0bNAfRnHpGPwp5K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bVdcKJ/btsF0H1paik/CSQfDlL0bNAfRnHpGPwp5K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbVdcKJ%2FbtsF0H1paik%2FCSQfDlL0bNAfRnHpGPwp5K%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;860&quot; height=&quot;484&quot; data-filename=&quot;스크린샷 2024-03-24 오후 4.23.41.png&quot; data-origin-width=&quot;1832&quot; data-origin-height=&quot;1032&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사다리 게임이 어떻게 동작하는지 핵심 로직에 대해 파악했으니 이제 수정 방법도 간단하다.&lt;/p&gt;
&lt;pre id=&quot;code_1711265189849&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;const success = this.ladderItems.some(item =&amp;gt; {
  const name = item.nameTargetText.text;
  const target = item.itemTargetText.text;
  return name === &quot;목표 이름&quot; &amp;amp;&amp;amp; target === &quot;목표 키워드&quot;;
});

// 목표 이름이 목표 키워드에 할당되는 사다리 조합이 나올때까지 실행

if (!success) {
  this.drawladder();
  return;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;좋아! 다 좋은데... 이걸 어떻게 티가 안 나게 적용하지?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사다리 게임을 하자고 네이버를 켜서 소스 탭에서 끄적끄적 수정할 수는 없는 노릇이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음 편에서 계속.&lt;/p&gt;</description>
      <category>취미로 하는 개발</category>
      <author>눅눅한 피부</author>
      <guid isPermaLink="true">https://nookpi.tistory.com/198</guid>
      <comments>https://nookpi.tistory.com/198#entry198comment</comments>
      <pubDate>Sun, 24 Mar 2024 16:28:37 +0900</pubDate>
    </item>
    <item>
      <title>네이버 사다리와 함께 했던 워크숍 (1)</title>
      <link>https://nookpi.tistory.com/197</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;얼마 전 1박 2일로 회사 워크숍을 다녀왔다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;워크숍 운영진으로서 재미있는 워크숍을 위해서는 뭐가 필요할지 고민했는데, 네이버 사다리 타기 결과를 조작해서 친애하는 부대표님에게 설거지와 노래 부르기(ㅋㅋ)를 몰아주면 재미있을 것 같았다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 네이버 사다리 결과를 원하는 대로 만들기 위한 작업에 착수했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;('네이버 사다리'는 네이버 PC에서 '사다리 타기 게임' 등의 키워드를 통해 검색해서 즐길 수 있는 사다리 게임을 말한다.)&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2024-03-23 오후 4.12.33.png&quot; data-origin-width=&quot;1508&quot; data-origin-height=&quot;1124&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/kHzPb/btsF1ZT4rI5/mNZpuFkMq9MEZ2t9rt2srk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/kHzPb/btsF1ZT4rI5/mNZpuFkMq9MEZ2t9rt2srk/img.png&quot; data-alt=&quot;네이버 사다리 게임. 모바일과 PC가 다르다.&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/kHzPb/btsF1ZT4rI5/mNZpuFkMq9MEZ2t9rt2srk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FkHzPb%2FbtsF1ZT4rI5%2FmNZpuFkMq9MEZ2t9rt2srk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;400&quot; height=&quot;298&quot; data-filename=&quot;스크린샷 2024-03-23 오후 4.12.33.png&quot; data-origin-width=&quot;1508&quot; data-origin-height=&quot;1124&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;네이버 사다리 게임. 모바일과 PC가 다르다.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;구현  요구사항은 아주 심플하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;타깃의 이름과 목표로 하는 키워드(당첨)가 일치하게 출력되는 것&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기술적으로 가능한지를 먼저 검토하기 위해 네이버 사다리 구현을 살펴보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사다리 게임은 캔버스를 통해 렌더링이 되고 있다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2024-03-23 오후 4.14.08.png&quot; data-origin-width=&quot;1198&quot; data-origin-height=&quot;914&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/UUqP5/btsF3fPoawl/b0ZgN9Ba9ttMi9hvpkAxx1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/UUqP5/btsF3fPoawl/b0ZgN9Ba9ttMi9hvpkAxx1/img.png&quot; data-alt=&quot;40&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/UUqP5/btsF3fPoawl/b0ZgN9Ba9ttMi9hvpkAxx1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FUUqP5%2FbtsF3fPoawl%2Fb0ZgN9Ba9ttMi9hvpkAxx1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;400&quot; height=&quot;305&quot; data-filename=&quot;스크린샷 2024-03-23 오후 4.14.08.png&quot; data-origin-width=&quot;1198&quot; data-origin-height=&quot;914&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;40&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;캔버스를 그리는 로직이 있길 바라면서 dom을 살펴보니 바로 아래에 매우 의심스러운 script가 하나 보인다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2024-03-23 오후 4.18.22.png&quot; data-origin-width=&quot;1382&quot; data-origin-height=&quot;254&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/2Jat9/btsF1R2YiEK/EtM3UMHKIGWFDUxdKaBOFK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/2Jat9/btsF1R2YiEK/EtM3UMHKIGWFDUxdKaBOFK/img.png&quot; data-alt=&quot;아주 많은 단서를 주는 코드&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/2Jat9/btsF1R2YiEK/EtM3UMHKIGWFDUxdKaBOFK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F2Jat9%2FbtsF1R2YiEK%2FEtM3UMHKIGWFDUxdKaBOFK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1382&quot; height=&quot;254&quot; data-filename=&quot;스크린샷 2024-03-23 오후 4.18.22.png&quot; data-origin-width=&quot;1382&quot; data-origin-height=&quot;254&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;아주 많은 단서를 주는 코드&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;pre id=&quot;code_1711178384060&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;(function () {
  const ua = jindo.$Agent().navigator();
  if (!(ua.ie &amp;amp;&amp;amp; ua.version &amp;lt; 10)) {
    const sJavascriptFile = 'https://ssl.pstatic.net/sstatic/fe/sfe/ladder-game/ladders_230912.js';
    const fnCallback = function () {};
    nhn.common.load_js(sJavascriptFile, fnCallback, true, 150);
  }
})();&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;유저 에이전트 확인하는 코드는 건너뛰고, 누가 봐도 사다리게임 핵심 로직이 담긴 js 파일이 보인다. 파일명으로 보아하니 23년 9월 12일에 마지막으로 수정한 코드인 듯...?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;전역에 선언된 nhn.common.load_js를 통해 해당 스크립트가 로드되는 방식인 것 같고... nhn.common.load_js에 들어가는 두 번째 인자는 빈 함수로 되어있는 걸 보니 onLoad 혹은 onFail, onError 콜백인 듯싶다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;세 번째 네 번째 인자는 뭔지 모르겠으니 nhn.common.load_js를 찾아보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;콘솔창을 열어 nhn.common.load_js를 출력해 보니 다음과 같은 난독화된 함수가 나온다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2024-03-23 오후 4.24.09.png&quot; data-origin-width=&quot;1784&quot; data-origin-height=&quot;152&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cbg24l/btsF2NliwsX/KJGuwXy3uJAMbWHbojOeX1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cbg24l/btsF2NliwsX/KJGuwXy3uJAMbWHbojOeX1/img.png&quot; data-alt=&quot;프론트는 이게 참 좋아...&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cbg24l/btsF2NliwsX/KJGuwXy3uJAMbWHbojOeX1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fcbg24l%2FbtsF2NliwsX%2FKJGuwXy3uJAMbWHbojOeX1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1784&quot; height=&quot;152&quot; data-filename=&quot;스크린샷 2024-03-23 오후 4.24.09.png&quot; data-origin-width=&quot;1784&quot; data-origin-height=&quot;152&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;프론트는 이게 참 좋아...&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;뭔지 모를 저 텍스트를 더블클릭하면? 쨔잔&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2024-03-23 오후 4.25.11.png&quot; data-origin-width=&quot;1038&quot; data-origin-height=&quot;304&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/tsUVi/btsF1LPkOVk/Y5CySnAKGkN1VIi5bICcs0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/tsUVi/btsF1LPkOVk/Y5CySnAKGkN1VIi5bICcs0/img.png&quot; data-alt=&quot;그래서 이게 뭔데...&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/tsUVi/btsF1LPkOVk/Y5CySnAKGkN1VIi5bICcs0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FtsUVi%2FbtsF1LPkOVk%2FY5CySnAKGkN1VIi5bICcs0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1038&quot; height=&quot;304&quot; data-filename=&quot;스크린샷 2024-03-23 오후 4.25.11.png&quot; data-origin-width=&quot;1038&quot; data-origin-height=&quot;304&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;그래서 이게 뭔데...&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;load_js로 명명된 함수의 모습이 보인다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사다리 게임 js 파일을 호출할 때에는 4개의 인자를 사용했는데, 로직을 보니 5개의 인자를 받을 수 있는 것 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;void 0 === r 은 r === undefined가 축약된 형태이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;!0은 ture의 축약된 형태, !1은 false의 축약된 형태이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(우리의 코드를 1byte라도 아끼려는 처절한 노력)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위의 3줄은 인자에 대한 기본값 처리로 보이고, 우리가 신경 써야 할 부분은 첫 번째 t가 어떻게 처리되고 있는지에 대한 부분이다.&lt;/p&gt;
&lt;pre id=&quot;code_1711179025819&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&quot;string&quot; == typeof t ? o ? r ? v(t, n) : d(t, n) : r || !h() ? v(t, n) : d(t, n) : o ? r ? v(void 0, n) : n() : r || !h() ? v(void 0, n) : n(), m = &quot;&quot;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;... 정신이 나갈 것 같지만 정신을 붙들고 이럴 땐 chatgpt한테 if문으로 바꿔달라고 해보자&lt;/p&gt;
&lt;pre id=&quot;code_1711182476293&quot; class=&quot;less&quot; style=&quot;background-color: #f8f8f8; color: #383a42; text-align: start;&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;javascript&quot;&gt;&lt;code&gt;if (typeof t == 'string') {
  if (o) {
    if (r) {
      v(t, n);
    } else {
      d(t, n);
    }
  } else {
    if (r || !h()) {
      v(t, n);
    } else {
      d(t, n);
    }
  }
} else {
  if (o) {
    if (r) {
      v(undefined, n);
    } else {
      n();
    }
  } else {
    if (r || !h()) {
      v(undefined, n);
    } else {
      n();
    }
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2024-03-23 오후 5.30.09.png&quot; data-origin-width=&quot;298&quot; data-origin-height=&quot;230&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/SnBoU/btsF2DpCvcz/igPQFRAKzIsGfTvALh0ka1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/SnBoU/btsF2DpCvcz/igPQFRAKzIsGfTvALh0ka1/img.png&quot; data-alt=&quot;브라우저 디버거에서 본 모습&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/SnBoU/btsF2DpCvcz/igPQFRAKzIsGfTvALh0ka1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FSnBoU%2FbtsF2DpCvcz%2FigPQFRAKzIsGfTvALh0ka1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;298&quot; height=&quot;230&quot; data-filename=&quot;스크린샷 2024-03-23 오후 5.30.09.png&quot; data-origin-width=&quot;298&quot; data-origin-height=&quot;230&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;브라우저 디버거에서 본 모습&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;브라우저 디버거에서 찍어본 input에 따라 실행 코드를 찾아보니 몇 줄 위의 함수가 실행되고 있었다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2024-03-23 오후 5.32.41.png&quot; data-origin-width=&quot;1354&quot; data-origin-height=&quot;258&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b5YhrV/btsF2MGINFk/LXkpI0pmRGv0V949rKNw80/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b5YhrV/btsF2MGINFk/LXkpI0pmRGv0V949rKNw80/img.png&quot; data-alt=&quot;스크립트를 지연 로드하는 코드로 보인다&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b5YhrV/btsF2MGINFk/LXkpI0pmRGv0V949rKNw80/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb5YhrV%2FbtsF2MGINFk%2FLXkpI0pmRGv0V949rKNw80%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1354&quot; height=&quot;258&quot; data-filename=&quot;스크린샷 2024-03-23 오후 5.32.41.png&quot; data-origin-width=&quot;1354&quot; data-origin-height=&quot;258&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;스크립트를 지연 로드하는 코드로 보인다&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;결국 실행되는 구문은 &lt;b&gt;window.require([&quot;사다리 게임.js&quot;], 빈 함수)&lt;/b&gt;라는 부분을 확인이 가능하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;궁금해서 전역에 선언된 require 함수를 살펴보니 requirejs@2.3.5 구현체가 나왔다.&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://requirejs.org/&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://requirejs.org/&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1711179865388&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;RequireJS&quot; data-og-description=&quot;/* --- RequireJS is a JavaScript file and module loader. It is optimized for in-browser use, but it can be used in other JavaScript environments, like Rhino and Node. Using a modular script loader like RequireJS will improve the speed and quality of your c&quot; data-og-host=&quot;requirejs.org&quot; data-og-source-url=&quot;https://requirejs.org/&quot; data-og-url=&quot;https://requirejs.org/&quot; data-og-image=&quot;&quot;&gt;&lt;a href=&quot;https://requirejs.org/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://requirejs.org/&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url();&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;RequireJS&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;/* --- RequireJS is a JavaScript file and module loader. It is optimized for in-browser use, but it can be used in other JavaScript environments, like Rhino and Node. Using a modular script loader like RequireJS will improve the speed and quality of your c&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;requirejs.org&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;requirejs는 대략 10년 전에 활발히 사용되던 모듈 로드를 위한 라이브러리로, 의존성 처리와 비동기 모듈 로드를 통해 각 모듈의 성능 최적화를 돕는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;window.require 호출을 통해 내가 원하는 사다리 로직을 주입해서 실행시킬 수 있을까? 검증을 위해 다음과 같이 테스트를 해보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. 기존 캔버스 엘리먼트를 복사한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. 기존 캔버스 엘리먼트를 삭제한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. window.require([&quot;사다리 게임.js&quot;])를 콘솔에서 수동으로 트리거한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사다리 게임이 새 캔버스에서 정상적으로 동작하면 PoC는 성공이다!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;figure data-ke-type=&quot;video&quot; data-ke-style=&quot;alignCenter&quot; data-video-host=&quot;kakaotv&quot; data-video-url=&quot;https://tv.kakao.com/v/445486027&quot; data-video-thumbnail=&quot;https://scrap.kakaocdn.net/dn/dbu3Hp/hyVDDVbTaT/UuRUGvTYF2KtK604fBrVTK/img.jpg?width=1920&amp;amp;height=929&amp;amp;face=0_0_1920_929,https://scrap.kakaocdn.net/dn/hUzbc/hyVDy7qbTe/I9SrEvg4YkFuzmERdHxXnK/img.jpg?width=1920&amp;amp;height=929&amp;amp;face=0_0_1920_929&quot; data-video-width=&quot;860&quot; data-video-height=&quot;416&quot; data-video-origin-width=&quot;860&quot; data-video-origin-height=&quot;416&quot; data-ke-mobilestyle=&quot;widthContent&quot; data-video-play-service=&quot;daum_tistory&quot; data-original-url=&quot;&quot; data-video-title=&quot;&quot;&gt;&lt;iframe src=&quot;https://play-tv.kakao.com/embed/player/cliplink/445486027?service=daum_tistory&quot; width=&quot;860&quot; height=&quot;416&quot; frameborder=&quot;0&quot; allowfullscreen=&quot;true&quot;&gt;&lt;/iframe&gt;
&lt;figcaption style=&quot;display: none;&quot;&gt;&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;대충 어떻게 돌아가는지, inject를 통해 기존 게임을 날리고 새로 시작할 수 있다는 사실도 알았으니 이제 &lt;a href=&quot;https://ssl.pstatic.net/sstatic/fe/sfe/ladder-game/ladders_230912.js&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;사다리 게임.js&lt;/a&gt;을 살펴보고 내부 로직을 고쳐보자...&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음 편에서 계속.&lt;/p&gt;</description>
      <category>취미로 하는 개발</category>
      <author>눅눅한 피부</author>
      <guid isPermaLink="true">https://nookpi.tistory.com/197</guid>
      <comments>https://nookpi.tistory.com/197#entry197comment</comments>
      <pubDate>Sat, 23 Mar 2024 17:55:58 +0900</pubDate>
    </item>
    <item>
      <title>useInjectedProps</title>
      <link>https://nookpi.tistory.com/196</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;컴포넌트의 Props를 일부 오버라이드하면서 인터페이스를 바꾸고, 컴포넌트가 동일한 ref를 갖게(re-render 가능하게) 해주는 훅을 만들어보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Implementation&lt;/p&gt;
&lt;pre id=&quot;code_1710913905953&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;export default function useInjectedProps&amp;lt;
  C extends React.FC&amp;lt;any&amp;gt;,
  I extends Partial&amp;lt;Record&amp;lt;keyof ComponentProps&amp;lt;C&amp;gt;, any&amp;gt;&amp;gt;,
&amp;gt;(_Component: C, injectedProps: I) {
  // 훅의 인자로 받은 컴포넌트를 저장합니다.
  const Component = useRef&amp;lt;any&amp;gt;(_Component);

  // 상태 변경이 일어나면서 컴포넌트 호출이 일어날 경우 injectedProps가 컴포넌트로 자연스럽게 주입 될 수 있게 합니다.
  Component.current = (props: Omit&amp;lt;ComponentProps&amp;lt;C&amp;gt;, keyof I&amp;gt;) =&amp;gt; {
    return &amp;lt;_Component {...props} {...injectedProps} /&amp;gt;;
  };

  // 컴포넌트의 ref를 고정시키기 위해 빈 배열의 useCallback 훅을 사용합니다.
  return useCallback((props) =&amp;gt; Component.current(props), []) as React.FC&amp;lt;
    Omit&amp;lt;ComponentProps&amp;lt;C&amp;gt;, keyof I&amp;gt;
  &amp;gt;;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Use Case&lt;/p&gt;
&lt;pre id=&quot;code_1710914193987&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;type ButtonProps = {
  onClick: Function;
};

const Button = ({ onClick }: ButtonProps) =&amp;gt; {
  return &amp;lt;button onClick={onClick}&amp;gt;Hello World&amp;lt;/button&amp;gt;;
};

type ButtonWithTextProps = ButtonProps &amp;amp; {
  text: string;
};

const ButtonWithText = ({ onClick, text }: ButtonWithTextProps) =&amp;gt; {
  return &amp;lt;button onClick={onClick}&amp;gt;{text}&amp;lt;/button&amp;gt;;
};

const Renderer = ({ RenderButton }: { RenderButton: React.FC&amp;lt;ButtonProps&amp;gt; }) =&amp;gt; {
  return &amp;lt;RenderButton onClick={() =&amp;gt; alert('Hello World')} /&amp;gt;;
};

const UseCase = () =&amp;gt; {
  const RenderButton = useInjectedProps(ButtonWithText, {
    text: 'World Hello',
  });
  
  return &amp;lt;Renderer RenderButton={RenderButton} /&amp;gt;;
};

//===&amp;gt; &amp;lt;button&amp;gt;World Hello&amp;lt;/button&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;reference&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://github.com/reactjs/rfcs/blob/useevent/text/0000-useevent.md&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://github.com/reactjs/rfcs/blob/useevent/text/0000-useevent.md&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>코반주반</category>
      <category>Hook</category>
      <category>react</category>
      <author>눅눅한 피부</author>
      <guid isPermaLink="true">https://nookpi.tistory.com/196</guid>
      <comments>https://nookpi.tistory.com/196#entry196comment</comments>
      <pubDate>Wed, 20 Mar 2024 14:58:53 +0900</pubDate>
    </item>
    <item>
      <title>세상은 이야기로 만들어졌다</title>
      <link>https://nookpi.tistory.com/195</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://product.kyobobook.co.kr/detail/S000209670832&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://product.kyobobook.co.kr/detail/S000209670832&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1709368811016&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;세상은 이야기로 만들어졌다 | 자미라 엘 우아실 - 교보문고&quot; data-og-description=&quot;세상은 이야기로 만들어졌다 | 원시 시대 동굴 속에서 나누던 이야기에서부터 디즈니의 애니메이션까지, 『일리아드』와 같은 고전에서부터 정치인 트럼프의 거짓말까지. 강력한 이야기는 삶&quot; data-og-host=&quot;product.kyobobook.co.kr&quot; data-og-source-url=&quot;https://product.kyobobook.co.kr/detail/S000209670832&quot; data-og-url=&quot;https://product.kyobobook.co.kr/detail/S000209670832&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/dChObv/hyVql2JSLX/TMqN0brqL65MnLooUDRPAk/img.jpg?width=458&amp;amp;height=665&amp;amp;face=0_0_458_665,https://scrap.kakaocdn.net/dn/bOyqvt/hyVqiZdw3p/Wj8afTtkSBYp9cziMvT8g1/img.jpg?width=458&amp;amp;height=665&amp;amp;face=0_0_458_665&quot;&gt;&lt;a href=&quot;https://product.kyobobook.co.kr/detail/S000209670832&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://product.kyobobook.co.kr/detail/S000209670832&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/dChObv/hyVql2JSLX/TMqN0brqL65MnLooUDRPAk/img.jpg?width=458&amp;amp;height=665&amp;amp;face=0_0_458_665,https://scrap.kakaocdn.net/dn/bOyqvt/hyVqiZdw3p/Wj8afTtkSBYp9cziMvT8g1/img.jpg?width=458&amp;amp;height=665&amp;amp;face=0_0_458_665');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;세상은 이야기로 만들어졌다 | 자미라 엘 우아실 - 교보문고&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;세상은 이야기로 만들어졌다 | 원시 시대 동굴 속에서 나누던 이야기에서부터 디즈니의 애니메이션까지, 『일리아드』와 같은 고전에서부터 정치인 트럼프의 거짓말까지. 강력한 이야기는 삶&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;product.kyobobook.co.kr&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이야기에 대한 구조적인 분석은 해 본 적이 없는데 이 책을 읽으면서 자연스럽게 인기 있는(소위 잘 팔리는) 이야기의 공통적인 구조에 대해 알 수 있었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;책은 인기 있는 이야기의 구조적인 공통점에서 시작해서, 시대의 변화에 따라 이야기가 나아가는 방향을 충분히 살핀 다음, 빈부 격차와 기후 위기 등 인류의 위기에 필요한 새로운 이야기의 형태는 무엇인지에 대한 질문을 던진다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;재미있게 읽었는데 뒷부분에서 개인적으로 좀 아쉬웠다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이야기가 인류 내면의 가치를 탐색하기 위한 수단으로 사용될 정도로 중요하다는 이야기를 하고, 영웅 서사에 '예방'이라는 키워드가 없다는 이야기까지는 공감이 되고 있었다. 우리 앞에 닥친 위기의 해결책으로서 어떤 내러티브를 제시할지 흥미진진하게 따라가고 있었는데 마무리에서 '그냥 좀 더 우리가 신경 쓰고 더 노력하자'라는 느낌을 받았다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;서양과 동양에서 이야기를 풀어나가는 관점이 다르다는 이야기가 중간중간 나와서 뭔가 그쪽으로 풀어나가지 않을까? 기대했던 것 같기도 하고, 기존의 영웅 서사가 가진 견고하고 강력한 구조에 대해 말했으니 그런 구조를 사용해서 매력적인 내러티브를 만드는 방식을 제안하려나? 싶었는데 김이 새는 느낌이었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;저자가 좀 더 용기를 내봤다면 좋았을 것 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약 용기가 부족한 게 아니었다면? 그럼 다른 사람들과 마찬가지로 안일하게 생각하고 있는 건가?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;모르겠다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>독후단상</category>
      <category>세상은 이야기로 만들어졌다</category>
      <author>눅눅한 피부</author>
      <guid isPermaLink="true">https://nookpi.tistory.com/195</guid>
      <comments>https://nookpi.tistory.com/195#entry195comment</comments>
      <pubDate>Sat, 2 Mar 2024 17:50:29 +0900</pubDate>
    </item>
    <item>
      <title>Nextjs 12.3.4 -&amp;gt; 13.5.6 버전업 후 정적파일 CORS 에러</title>
      <link>https://nookpi.tistory.com/194</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;금일 정기배포 이후 www.creatrip~ 에서 요청하는 js chunk 파일에 대한 CORS 에러가 발생했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 얼마 만에 보는 CORS 에러란 말인가...!&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;image (1).png&quot; data-origin-width=&quot;3450&quot; data-origin-height=&quot;740&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dAJjj7/btsFkIskTAV/37kEel2kZDW8SaSacN95F0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dAJjj7/btsFkIskTAV/37kEel2kZDW8SaSacN95F0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dAJjj7/btsFkIskTAV/37kEel2kZDW8SaSacN95F0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdAJjj7%2FbtsFkIskTAV%2F37kEel2kZDW8SaSacN95F0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;3450&quot; height=&quot;740&quot; data-filename=&quot;image (1).png&quot; data-origin-width=&quot;3450&quot; data-origin-height=&quot;740&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우리 회사에서는  nextjs 빌드 후 생성된 정적 파일은 s3 - cloudfront를 통해서 캐싱 후 제공하고 있는데, 배포 전 후로 설정을 바꾼 부분은 없는 상황이었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일단 cdn에서 제공하는 정적 파일에 대한 응답 헤더에 정책을 생성하고, 해당 정책에서 www가 붙은 도메인, 붙지 않은 도메인에 대해 모두 오리진을 추가해서 대응해 주었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;빠르게 대응은 했는데, 대체 왜 갑자기 문제가 생긴 것인지 파악을 하기가 여간 쉽지 않았다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;처음에는 해당 이슈가 배포 직후에 발생한 문제인지에 대한 확신도 서지 않았고, SSG 등으로 생성된 정적 페이지에서는 해당 이슈가 없는 부분도 의아했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;가장 먼저 발견한 차이점은 Origin 헤더의 유무였는데, CORS 문제가 생기는 자원들은 모두 요청 헤더에 Origin 값을 보내고 있다는 점이 달랐다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;대체 원인이 뭔지 끙끙대던 중, 훌륭하신 팀원 한 분이 관련 이슈를 찾아주셔서 사건의 전말을 파악할 수 있었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://github.com/vercel/next.js/issues/34225#issuecomment-1804831899&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://github.com/vercel/next.js/issues/34225#issuecomment-1804831899&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1709049996445&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;object&quot; data-og-title=&quot;^11.1.2 - Using cdn for assets results in CORS error only for some css files not all &amp;middot; Issue #34225 &amp;middot; vercel/next.js&quot; data-og-description=&quot;Run next info (available from version 12.0.8 and up) &amp;quot;next&amp;quot;: &amp;quot;^11.1.2&amp;quot;, What version of Next.js are you using? &amp;quot;next&amp;quot;: &amp;quot;^11.1.2&amp;quot;, What version of Node.js are you using? 15.0.1 What browser are you ...&quot; data-og-host=&quot;github.com&quot; data-og-source-url=&quot;https://github.com/vercel/next.js/issues/34225#issuecomment-1804831899&quot; data-og-url=&quot;https://github.com/vercel/next.js/issues/34225&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/TdxTV/hyVqnlcVfj/ijukLkNOZSkQ04mbt9LkH1/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600&quot;&gt;&lt;a href=&quot;https://github.com/vercel/next.js/issues/34225#issuecomment-1804831899&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://github.com/vercel/next.js/issues/34225#issuecomment-1804831899&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/TdxTV/hyVqnlcVfj/ijukLkNOZSkQ04mbt9LkH1/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;^11.1.2 - Using cdn for assets results in CORS error only for some css files not all &amp;middot; Issue #34225 &amp;middot; vercel/next.js&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Run next info (available from version 12.0.8 and up) &quot;next&quot;: &quot;^11.1.2&quot;, What version of Next.js are you using? &quot;next&quot;: &quot;^11.1.2&quot;, What version of Node.js are you using? 15.0.1 What browser are you ...&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;github.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;요점만 정리하자면 다음과 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;이번 정기 배포 전의 next 버전은 12.3.4였다.&lt;/li&gt;
&lt;li&gt;해당 버전에서는 CSR 페이지들의 script 태그에 crossorigin 속성이 없었다.&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;12.3.4 버전의 코드에서&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;a href=&quot;https://github.com/vercel/next.js/blob/v12.3.4/packages/next/build/webpack-config.ts#L982&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;crossOrigin을&amp;nbsp;선언하는&amp;nbsp;곳&lt;/a&gt;과&amp;nbsp;&lt;a href=&quot;https://github.com/vercel/next.js/blob/v12.3.4/packages/next/build/webpack-config.ts#L1986&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;넘기는&amp;nbsp;곳&lt;/a&gt;을 보면 next.config 내부의 crossOrigin 값이 없는 경우 undefined를 그대로 넘기는 것을 알 수 있다.&lt;/li&gt;
&lt;li&gt;그런데 버전업&amp;nbsp;이후&amp;nbsp;&lt;a href=&quot;https://github.com/vercel/next.js/blob/v13.5.6/packages/next/src/export/index.ts#L499&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Next 13.5.6에서는 next.config의 crossOrigin이 없을 경우 빈 문자열을 넣어&lt;/a&gt;준다...!&lt;/li&gt;
&lt;li&gt;그리고&amp;nbsp;이렇게&amp;nbsp;들어간&amp;nbsp;crossOrigin&amp;nbsp;값은&amp;nbsp;script&amp;nbsp;렌더링시에&amp;nbsp;사용되는데,&amp;nbsp;&lt;a href=&quot;https://github.com/vercel/next.js/issues/53190&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;이&amp;nbsp;버그&lt;/a&gt;를 수정하기 위해 &lt;a href=&quot;https://github.com/vercel/next.js/pull/56311&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;이 PR&lt;/a&gt;에서 작업되었다. 따라서 config의 crossOrigin 값이 없는 경우, getRequiredScripts 로직을 통해 생성되는 script tag에는 무조건 crossOrigin이 붙게 되는 버그(?)가 생긴 것이다. (그리고 해당 버그는 &lt;a href=&quot;https://github.com/vercel/next.js/releases/tag/v13.5.4-canary.10&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;13.5.4-canary.10&lt;/a&gt;에 머지되었다)&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/HTML/Attributes/crossorigin&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;crossOrigin attribute&lt;/a&gt;는 일부 동적 스크립트에 사용되며 element의 리소스 요청 시 CORS 요청을 사용할 수 있게 만드는 속성이다.&lt;/li&gt;
&lt;li&gt;crossorigin 속성이 아예 없는 경우에는 CORS 요청을 하지 않지만, 빈 문자열일 경우 기본값인 anonymous를 사용하게 되며 anonymous에서는 CORS 요청의 credentials flag가 &amp;lsquo;same-origin&amp;rsquo;으로 지정되면서 CORS 요청이 발생한다.&lt;/li&gt;
&lt;li&gt;즉, 버그다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;renderOpt라는 속성을 리팩토링 하면서 crossOrigin에 대한 기본값을 빈 문자열로 넣어버렸고, 추후 버그를 수정하면서 해당 옵션의 crossOrigin 값을 script의 속성으로 그대로 사용해 버린... 버그인 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;nextjs에 기여할 수 있는 기회인가? 싶어서 살펴봤는데 벌써 &lt;a href=&quot;https://github.com/vercel/next.js/pull/58200&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;수정 PR이&lt;/a&gt; 작년 11월에 올라와 있었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;왜 머지가 안 되고 있는지는 모르겠다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;비슷한 이슈를 마주한다면 일단 cdn에서 응답 헤더 정책에 SimpleCORS 혹은 커스텀 정책으로 도메인에 해당하는 origin을 allow 해서 해결해 보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://medium.com/creatrip/next-js-%EB%B2%84%EC%A0%84%EC%97%85-%EC%9D%B4%ED%9B%84-%EC%85%80%ED%94%84-%ED%98%B8%EC%8A%A4%ED%8C%85-%ED%99%98%EA%B2%BD%EC%97%90%EC%84%9C%EC%9D%98-cors-b7f7192bb9c4&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://medium.com/creatrip/next-js-%EB%B2%84%EC%A0%84%EC%97%85-%EC%9D%B4%ED%9B%84-%EC%85%80%ED%94%84-%ED%98%B8%EC%8A%A4%ED%8C%85-%ED%99%98%EA%B2%BD%EC%97%90%EC%84%9C%EC%9D%98-cors-b7f7192bb9c4&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1710896941967&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;Next.js 버전업 이후 셀프 호스팅 환경에서의 CORS&quot; data-og-description=&quot;오늘은 Next.js 13 버전업 이후 겪었던 CORS 이슈에 대한 경험을 공유하려고 해요.&quot; data-og-host=&quot;medium.com&quot; data-og-source-url=&quot;https://medium.com/creatrip/next-js-%EB%B2%84%EC%A0%84%EC%97%85-%EC%9D%B4%ED%9B%84-%EC%85%80%ED%94%84-%ED%98%B8%EC%8A%A4%ED%8C%85-%ED%99%98%EA%B2%BD%EC%97%90%EC%84%9C%EC%9D%98-cors-b7f7192bb9c4&quot; data-og-url=&quot;https://medium.com/creatrip/next-js-%EB%B2%84%EC%A0%84%EC%97%85-%EC%9D%B4%ED%9B%84-%EC%85%80%ED%94%84-%ED%98%B8%EC%8A%A4%ED%8C%85-%ED%99%98%EA%B2%BD%EC%97%90%EC%84%9C%EC%9D%98-cors-b7f7192bb9c4&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/ba0Zph/hyVDw1714h/DOdCKaVTNxppZDREfWlrLK/img.png?width=1200&amp;amp;height=257&amp;amp;face=0_0_1200_257&quot;&gt;&lt;a href=&quot;https://medium.com/creatrip/next-js-%EB%B2%84%EC%A0%84%EC%97%85-%EC%9D%B4%ED%9B%84-%EC%85%80%ED%94%84-%ED%98%B8%EC%8A%A4%ED%8C%85-%ED%99%98%EA%B2%BD%EC%97%90%EC%84%9C%EC%9D%98-cors-b7f7192bb9c4&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://medium.com/creatrip/next-js-%EB%B2%84%EC%A0%84%EC%97%85-%EC%9D%B4%ED%9B%84-%EC%85%80%ED%94%84-%ED%98%B8%EC%8A%A4%ED%8C%85-%ED%99%98%EA%B2%BD%EC%97%90%EC%84%9C%EC%9D%98-cors-b7f7192bb9c4&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/ba0Zph/hyVDw1714h/DOdCKaVTNxppZDREfWlrLK/img.png?width=1200&amp;amp;height=257&amp;amp;face=0_0_1200_257');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;Next.js 버전업 이후 셀프 호스팅 환경에서의 CORS&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;오늘은 Next.js 13 버전업 이후 겪었던 CORS 이슈에 대한 경험을 공유하려고 해요.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;medium.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>공부내용 공유하기</category>
      <category>cors</category>
      <category>NextJs</category>
      <author>눅눅한 피부</author>
      <guid isPermaLink="true">https://nookpi.tistory.com/194</guid>
      <comments>https://nookpi.tistory.com/194#entry194comment</comments>
      <pubDate>Wed, 28 Feb 2024 01:16:03 +0900</pubDate>
    </item>
    <item>
      <title>Signals</title>
      <link>https://nookpi.tistory.com/193</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;2023.12.23 개발팀 세미나&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://jonghakseo.github.io/posts/signals/&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://jonghakseo.github.io/posts/signals/&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1708498411575&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;Signals&quot; data-og-description=&quot;시그널은 무엇이고 프론트엔드 생태계에서 주목을 받는 이유는 무엇일까?&quot; data-og-host=&quot;jonghakseo.github.io&quot; data-og-source-url=&quot;https://jonghakseo.github.io/posts/signals/&quot; data-og-url=&quot;https://jonghakseo.github.io/posts/signals/&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/cw0DIQ/hyVmRNqXLi/tVOmjLAov8WDlIF5oet3hK/img.png?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630,https://scrap.kakaocdn.net/dn/EZRji/hyVmX1beGF/jngNppdVfPBAEGe710Pwu0/img.png?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630&quot;&gt;&lt;a href=&quot;https://jonghakseo.github.io/posts/signals/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://jonghakseo.github.io/posts/signals/&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/cw0DIQ/hyVmRNqXLi/tVOmjLAov8WDlIF5oet3hK/img.png?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630,https://scrap.kakaocdn.net/dn/EZRji/hyVmX1beGF/jngNppdVfPBAEGe710Pwu0/img.png?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;Signals&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;시그널은 무엇이고 프론트엔드 생태계에서 주목을 받는 이유는 무엇일까?&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;jonghakseo.github.io&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>발표자료</category>
      <author>눅눅한 피부</author>
      <guid isPermaLink="true">https://nookpi.tistory.com/193</guid>
      <comments>https://nookpi.tistory.com/193#entry193comment</comments>
      <pubDate>Wed, 21 Feb 2024 15:53:58 +0900</pubDate>
    </item>
    <item>
      <title>React 근황</title>
      <link>https://nookpi.tistory.com/192</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://react.dev/blog/2024/02/15/react-labs-what-we-have-been-working-on-february-2024&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://react.dev/blog/2024/02/15/react-labs-what-we-have-been-working-on-february-2024&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1708349590722&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;React Labs: What We've Been Working On &amp;ndash; February 2024 &amp;ndash; React&quot; data-og-description=&quot;The library for web and native user interfaces&quot; data-og-host=&quot;react.dev&quot; data-og-source-url=&quot;https://react.dev/blog/2024/02/15/react-labs-what-we-have-been-working-on-february-2024&quot; data-og-url=&quot;https://react.dev/blog/2024/02/15/react-labs-what-we-have-been-working-on-february-2024&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/bnFJfW/hyVmUW6T24/vRY8B3kersvE8ooFuRxrW0/img.png?width=1080&amp;amp;height=567&amp;amp;face=0_0_1080_567,https://scrap.kakaocdn.net/dn/8nCug/hyVmPn4d8c/psM8InkJPCj3AdXH481bZK/img.png?width=1080&amp;amp;height=567&amp;amp;face=0_0_1080_567&quot;&gt;&lt;a href=&quot;https://react.dev/blog/2024/02/15/react-labs-what-we-have-been-working-on-february-2024&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://react.dev/blog/2024/02/15/react-labs-what-we-have-been-working-on-february-2024&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/bnFJfW/hyVmUW6T24/vRY8B3kersvE8ooFuRxrW0/img.png?width=1080&amp;amp;height=567&amp;amp;face=0_0_1080_567,https://scrap.kakaocdn.net/dn/8nCug/hyVmPn4d8c/psM8InkJPCj3AdXH481bZK/img.png?width=1080&amp;amp;height=567&amp;amp;face=0_0_1080_567');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;React Labs: What We've Been Working On &amp;ndash; February 2024 &amp;ndash; React&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;The library for web and native user interfaces&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;react.dev&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;RSC에서 실험적으로 테스트중인 cache&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://react.dev/reference/react/cache&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://react.dev/reference/react/cache&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1708349625488&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;cache &amp;ndash; React&quot; data-og-description=&quot;The library for web and native user interfaces&quot; data-og-host=&quot;react.dev&quot; data-og-source-url=&quot;https://react.dev/reference/react/cache&quot; data-og-url=&quot;https://react.dev/reference/react/cache&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/noetB/hyVmQArKKD/jVGVla0zk4ZcZd8473eA7k/img.png?width=1080&amp;amp;height=567&amp;amp;face=0_0_1080_567,https://scrap.kakaocdn.net/dn/bPRqHq/hyVmUv1po9/Shh1Ejh1uq25QfzKXM6gZK/img.png?width=1080&amp;amp;height=567&amp;amp;face=0_0_1080_567&quot;&gt;&lt;a href=&quot;https://react.dev/reference/react/cache&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://react.dev/reference/react/cache&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/noetB/hyVmQArKKD/jVGVla0zk4ZcZd8473eA7k/img.png?width=1080&amp;amp;height=567&amp;amp;face=0_0_1080_567,https://scrap.kakaocdn.net/dn/bPRqHq/hyVmUv1po9/Shh1Ejh1uq25QfzKXM6gZK/img.png?width=1080&amp;amp;height=567&amp;amp;face=0_0_1080_567');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;cache &amp;ndash; React&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;The library for web and native user interfaces&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;react.dev&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://youtu.be/LL4V8CcEhIo?si=nb5QaPUwKW4kAARp&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://youtu.be/LL4V8CcEhIo&lt;/a&gt;&lt;/p&gt;
&lt;figure data-ke-type=&quot;video&quot; data-ke-style=&quot;alignCenter&quot; data-video-host=&quot;youtube&quot; data-video-url=&quot;https://www.youtube.com/watch?v=LL4V8CcEhIo&quot; data-video-thumbnail=&quot;https://scrap.kakaocdn.net/dn/eqB25j/hyVmYZvgUH/rfgM6PkD5IVMrH40Hu4v3k/img.jpg?width=1280&amp;amp;height=720&amp;amp;face=288_124_1216_466&quot; data-video-width=&quot;860&quot; data-video-height=&quot;484&quot; data-video-origin-width=&quot;860&quot; data-video-origin-height=&quot;484&quot; data-ke-mobilestyle=&quot;widthContent&quot; data-original-url=&quot;&quot; data-video-title=&quot;&quot;&gt;&lt;iframe src=&quot;https://www.youtube.com/embed/LL4V8CcEhIo&quot; width=&quot;860&quot; height=&quot;484&quot; frameborder=&quot;&quot; allowfullscreen=&quot;true&quot;&gt;&lt;/iframe&gt;
&lt;figcaption style=&quot;display: none;&quot;&gt;&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 메타 친구들은 자사 서비스에서 테스트 후 stable로 배포하는게 참 든든하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- memo, useCallback, useMemo로 이어지는 고통이 끝나려나...?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 나는 Solid 접근법이 더 맞는 것 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 오픈소스는 생태계가 깡패다.&lt;/p&gt;</description>
      <category>스크랩</category>
      <category>react</category>
      <category>react cache</category>
      <author>눅눅한 피부</author>
      <guid isPermaLink="true">https://nookpi.tistory.com/192</guid>
      <comments>https://nookpi.tistory.com/192#entry192comment</comments>
      <pubDate>Mon, 19 Feb 2024 22:35:46 +0900</pubDate>
    </item>
    <item>
      <title>4년차 프론트엔드 개발자의 2023년 회고</title>
      <link>https://nookpi.tistory.com/191</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;kajetan-sumila-bxaqUeVIGHU-unsplash.jpg&quot; data-origin-width=&quot;5310&quot; data-origin-height=&quot;3540&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/QQPHv/btsEYKqwCNk/kvQRA9YkgGnabua2prwpRk/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/QQPHv/btsEYKqwCNk/kvQRA9YkgGnabua2prwpRk/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/QQPHv/btsEYKqwCNk/kvQRA9YkgGnabua2prwpRk/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FQQPHv%2FbtsEYKqwCNk%2FkvQRA9YkgGnabua2prwpRk%2Fimg.jpg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;500&quot; height=&quot;333&quot; data-filename=&quot;kajetan-sumila-bxaqUeVIGHU-unsplash.jpg&quot; data-origin-width=&quot;5310&quot; data-origin-height=&quot;3540&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;2023년. 작년 한 해도 쏜살같이 지나갔고, 정말 많고 많은 일이 있었다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;2022년의 내가 새로운 환경에서 크게 성장했다면 2023년의 나는 내면의 갈등과 안정, 그리고 내가 무엇을 추구하는지 스스로를 들여다보는 일에 많은 시간을 쏟았던 것 같다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;상반기&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;새로운 시도와 열정, 동시에 좌절과 고민이 많았던 상반기였다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;숙소... 애증의 숙소&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;2022년 말부터 신규 사업인 숙소 도메인 개발을 위해 숙소 스쿼드에 합류하여 개발을 진행했다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;스쿼드 내에 자리 잡은 무기력함(?)을 타파하기 위해 내 나름대로의 encouraging에 열과 성을 다했다. 스쿼드 체제 내부에서 구성원들의 자율성과 독립성, 추진력을 바탕으로 더 나은 결과를 만들 수 있을 거라는 믿음이 있었고, 애자일 관련 도서들을 읽으면서 여러 시도를 녹여내 보려 노력했던 시기이다. ('함께 자라기'는 정말 정말 좋았다)&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;지금 생각해보면 프론트엔드 개발자인지 스크럼 마스터인지 스스로의 정체성에 혼동이 올 정도로 스쿼드 내부에서 여러 시도와 노력을 했던 것 같다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;결론적으로 나름 파이팅 넘치게 숙소 도메인이 론칭되었고... 결과는? 비즈니스적으로 기대 이하의 성적표를 마주하면서 무력감이 생겨버렸다. 팽팽한 고무줄이 끊어진 기분으로 번아웃과 함께 여러 가지로 힘든 시기가 찾아오고 말았다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;내가 했던 노력과 결정이 더 큰 의사결정으로 인해 무용지물이 될 수 있다는 사실은 나로 하여금 이전에 겪어본 적 없는 무기력감을 느끼게 만들었고, 이어진 수습 과정에서의 추가적인 피드백과 의사결정 또한 아쉬움이 남았다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;나는 일에 스스로를 투영하지 않으려고 노력하는 편인데... 이 시기에는 온몸을 다해 열정을 쏟으며 일에 나 자신을 투영해 버린 게 아닌가 싶다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;AI&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;연초부터 ChatGPT를 위시한 LLM의 범람과 LLM 기반의 AI 서비스들이 우후죽순으로 발표 및 출시되었다. 나는 AI라는 키워드가 이 정도로 우리의 삶에 빠르게 그리고 직접적으로 영향을 끼칠 줄은 전혀 상상하지 못했다. 대체 어떤 일이 일어나고 있는지 알아내기 위해 주의를 기울이고 새로운 정보의 홍수에 허우적거리며 시간을 보냈다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;프롬프트 엔지니어링, 파인 튜닝 등을 통해 비즈니스와 연계하려는 여러 시도들이 눈에 띄었는데, 사실상 연계 서비스의 출시 속도보다 기술의 원천인 OpenAI의 신규 제품 발표 속도가 더 빨라 큰 임팩트를 기대하기에는 어려워 보였다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;LLM을 활용한 서비스들이 어떤 가치를 줄 수 있는지는 아직도 고민 중인 포인트인데, 내 생각은 '원래 잘하는 것이 있는 기업이 LLM을 접목하면 더 큰 가치를 만들 수 있지만, 타사의 LLM과 프롬프트 엔지니어링 만으로는 고유한 비즈니스 가치를 창출하기 어렵다'로 수렴되었다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;회사에 있는 어학당 도메인에 CX비용을 줄이고자 컨텐츠 전처리 + 벡터 임베딩 + 프롬프트 엔지니어링을 통한 챗봇 서비스를 시도해 본 것도 굉장히 새로운 경험이었다. (RAG 개념이 어렴풋하게 정립되려던 시기로 기억한다)&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;figure id=&quot;og_1708161453385&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;크리에이트립, GPT-3 기반 챗봇 '어학당 지니' 출시 : 크리에이트립 공식 블로그&quot; data-og-description=&quot;[더구루=최영희 기자]&amp;nbsp;종합 한국 정보 플랫폼 크리에이트립이&amp;nbsp;GPT-3를 기반으로 한국어학당 정보 제공을 위한 챗봇&amp;nbsp;&amp;lsquo;어학당 지니&amp;rsquo;를 출시했다고&amp;nbsp;25일 밝혔다.&amp;nbsp;어학당 지니는 한국으로 유학&quot; data-og-host=&quot;creatrip.co.kr&quot; data-og-source-url=&quot;https://creatrip.co.kr/blog/?q=YToxOntzOjEyOiJrZXl3b3JkX3R5cGUiO3M6MzoiYWxsIjt9&amp;amp;bmode=view&amp;amp;idx=15008052&amp;amp;t=board&quot; data-og-url=&quot;https://creatrip.co.kr/blog/?bmode=view&amp;amp;idx=15008052&amp;amp;q=YToxOntzOjEyOiJrZXl3b3JkX3R5cGUiO3M6MzoiYWxsIjt9&amp;amp;t=board&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/bhnFZk/hyVi74qT8Z/7KdJ9ghjdnD8VMcQ34u7kk/img.jpg?width=875&amp;amp;height=615&amp;amp;face=0_0_875_615,https://scrap.kakaocdn.net/dn/fENCA/hyVmUvyv4q/zuYcgpZZtVTKTUw8v5UGE1/img.jpg?width=875&amp;amp;height=615&amp;amp;face=0_0_875_615&quot;&gt;&lt;a href=&quot;https://creatrip.co.kr/blog/?q=YToxOntzOjEyOiJrZXl3b3JkX3R5cGUiO3M6MzoiYWxsIjt9&amp;amp;bmode=view&amp;amp;idx=15008052&amp;amp;t=board&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://creatrip.co.kr/blog/?q=YToxOntzOjEyOiJrZXl3b3JkX3R5cGUiO3M6MzoiYWxsIjt9&amp;amp;bmode=view&amp;amp;idx=15008052&amp;amp;t=board&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/bhnFZk/hyVi74qT8Z/7KdJ9ghjdnD8VMcQ34u7kk/img.jpg?width=875&amp;amp;height=615&amp;amp;face=0_0_875_615,https://scrap.kakaocdn.net/dn/fENCA/hyVmUvyv4q/zuYcgpZZtVTKTUw8v5UGE1/img.jpg?width=875&amp;amp;height=615&amp;amp;face=0_0_875_615');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;크리에이트립, GPT-3 기반 챗봇 '어학당 지니' 출시 : 크리에이트립 공식 블로그&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;[더구루=최영희 기자]&amp;nbsp;종합 한국 정보 플랫폼 크리에이트립이&amp;nbsp;GPT-3를 기반으로 한국어학당 정보 제공을 위한 챗봇&amp;nbsp;&amp;lsquo;어학당 지니&amp;rsquo;를 출시했다고&amp;nbsp;25일 밝혔다.&amp;nbsp;어학당 지니는 한국으로 유학&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;creatrip.co.kr&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;초기 PoC 및 구현 방향의 설계를 하고 진행했는데 결과적으로 아쉬운 부분들도 있었지만 좋은 시도였다고 생각된다. 프로덕트 팀을 비롯해 사내에 AI 리터러시가 좀 더 확산되는 계기였고, 이때의 경험은 추후 전체 도메인의 자동 번역을 LLM 기반의 번역으로 전환하는 데에 도움이 되었다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2024-02-17 오후 6.22.36.png&quot; data-origin-width=&quot;708&quot; data-origin-height=&quot;396&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b9XeYi/btsEWNuwF52/2j67D3p7kKcKqoqVIByjdK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b9XeYi/btsEWNuwF52/2j67D3p7kKcKqoqVIByjdK/img.png&quot; data-alt=&quot;Slack에서 사용할 수 있는 chatgpt 봇도 만들어서 배포했다&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b9XeYi/btsEWNuwF52/2j67D3p7kKcKqoqVIByjdK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb9XeYi%2FbtsEWNuwF52%2F2j67D3p7kKcKqoqVIByjdK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;400&quot; height=&quot;224&quot; data-filename=&quot;스크린샷 2024-02-17 오후 6.22.36.png&quot; data-origin-width=&quot;708&quot; data-origin-height=&quot;396&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;Slack에서 사용할 수 있는 chatgpt 봇도 만들어서 배포했다&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;오픈소스&lt;/span&gt;&lt;/h4&gt;
&lt;figure id=&quot;og_1708162173840&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;object&quot; data-og-title=&quot;GitHub - Jonghakseo/chrome-extension-boilerplate-react-vite: Chrome Extension Boilerplate with React + Vite + Typescript&quot; data-og-description=&quot;Chrome Extension Boilerplate with React + Vite + Typescript - Jonghakseo/chrome-extension-boilerplate-react-vite&quot; data-og-host=&quot;github.com&quot; data-og-source-url=&quot;https://github.com/Jonghakseo/chrome-extension-boilerplate-react-vite/issues&quot; data-og-url=&quot;https://github.com/Jonghakseo/chrome-extension-boilerplate-react-vite&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/bFBF9I/hyVjeCt4bw/w9i8Kdd4GdZTXk8rcza6y1/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600&quot;&gt;&lt;a href=&quot;https://github.com/Jonghakseo/chrome-extension-boilerplate-react-vite/issues&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://github.com/Jonghakseo/chrome-extension-boilerplate-react-vite/issues&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/bFBF9I/hyVjeCt4bw/w9i8Kdd4GdZTXk8rcza6y1/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;GitHub - Jonghakseo/chrome-extension-boilerplate-react-vite: Chrome Extension Boilerplate with React + Vite + Typescript&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Chrome Extension Boilerplate with React + Vite + Typescript - Jonghakseo/chrome-extension-boilerplate-react-vite&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;github.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;기존 보일러 플레이트는 꾸준히 유지보수를 하고 있었고, 그 과정에서 새롭게 배우는 것들이 정말 많았다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;내 오픈소스 레포를 보며 기여하거나 이슈를 남기는 다른 개발자들이 때때로 내 스승이 되어주기도 하고, 내가 그들에게 뭔가 인사이트를 주는 경험도 있었다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;원하는 프롬프트 + 드래그 후 클릭으로 ChatGPT를 호출하면 편할 것 같아 오랜만에 익스텐션도 만들었다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;a href=&quot;https://nookpi.tistory.com/166&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://nookpi.tistory.com/166&lt;/a&gt;&lt;/span&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1708162165373&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;DragGPT chrome extension 개발기&quot; data-og-description=&quot;배경 최근 ChatGPT 열풍이 거세다. 연구 영역에 있던 생성 AI의 위치를 대중에 가깝게 옮기면서 대중들로 하여금 급격한 기술 발달이 이뤄지고 특이점이 왔다(?)는 생각을 하게 만드는 것 같다. 기&quot; data-og-host=&quot;nookpi.tistory.com&quot; data-og-source-url=&quot;https://nookpi.tistory.com/166&quot; data-og-url=&quot;https://nookpi.tistory.com/166&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/UdCnU/hyVji5VPTn/81dz2iGOSjTM0mcngbWHfk/img.png?width=528&amp;amp;height=468&amp;amp;face=0_0_528_468,https://scrap.kakaocdn.net/dn/cqYsyw/hyVjln3bA4/YKb0vTJk9t7UYkScgKNN00/img.png?width=528&amp;amp;height=468&amp;amp;face=0_0_528_468,https://scrap.kakaocdn.net/dn/6z1zc/hyVm1Iftzd/FU6mJP3RbudkWt8tSS6JJk/img.jpg?width=550&amp;amp;height=743&amp;amp;face=0_0_550_743&quot;&gt;&lt;a href=&quot;https://nookpi.tistory.com/166&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://nookpi.tistory.com/166&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/UdCnU/hyVji5VPTn/81dz2iGOSjTM0mcngbWHfk/img.png?width=528&amp;amp;height=468&amp;amp;face=0_0_528_468,https://scrap.kakaocdn.net/dn/cqYsyw/hyVjln3bA4/YKb0vTJk9t7UYkScgKNN00/img.png?width=528&amp;amp;height=468&amp;amp;face=0_0_528_468,https://scrap.kakaocdn.net/dn/6z1zc/hyVm1Iftzd/FU6mJP3RbudkWt8tSS6JJk/img.jpg?width=550&amp;amp;height=743&amp;amp;face=0_0_550_743');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;DragGPT chrome extension 개발기&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;배경 최근 ChatGPT 열풍이 거세다. 연구 영역에 있던 생성 AI의 위치를 대중에 가깝게 옮기면서 대중들로 하여금 급격한 기술 발달이 이뤄지고 특이점이 왔다(?)는 생각을 하게 만드는 것 같다. 기&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;nookpi.tistory.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;이때 익스텐션을 개발하면서 처음으로 유한상태머신의 js 구현체인 XState를 사용했는데, 사용 경험이 매우 좋아서 개발팀 내부에도 공유하는 시간을 가졌다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;a href=&quot;https://nookpi.tistory.com/177&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://nookpi.tistory.com/177&lt;/a&gt;&lt;/span&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1708162847059&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;React + xState&quot; data-og-description=&quot;2023.03.20 개발팀 세미나&quot; data-og-host=&quot;nookpi.tistory.com&quot; data-og-source-url=&quot;https://nookpi.tistory.com/177&quot; data-og-url=&quot;https://nookpi.tistory.com/177&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/dZbK7H/hyVjlhgMeZ/ODIaASskDs6qhnXRi4Tmhk/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/bEvgXW/hyVjn0ueMs/7MYUSeOXHodN7IegXLeHek/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800&quot;&gt;&lt;a href=&quot;https://nookpi.tistory.com/177&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://nookpi.tistory.com/177&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/dZbK7H/hyVjlhgMeZ/ODIaASskDs6qhnXRi4Tmhk/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/bEvgXW/hyVjn0ueMs/7MYUSeOXHodN7IegXLeHek/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;React + xState&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;2023.03.20 개발팀 세미나&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;nookpi.tistory.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;CS 스터디&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;4월부터 8월까지 프로덕트 팀원들과 매주 CS스터디를 진행했다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;대표적인 디자인 패턴들과 OS, 네트워크 관련 기본 지식들을 다지는 시간이었고 기대 이상으로 매우 알찼다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;특히 OS 쪽에서 멀티 프로세싱과 스케줄링 부분을 공부하며 핵심 구현체와 그 컨셉은 추상화&amp;nbsp;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;Layer가 올라가더라도 활용할 수 있는 기본기로 남겠다는 생각이 많이 들었다.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;굉장히 즐겁게 공부했던 시기였다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;하반기&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;감정이 널뛰던 상반기와는 다르게 전반적으로 차분한 하반기를 보냈다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;숙소 도메인의 여파가 상반기 초입까지 이어졌고, 의사결정에 대한 불신도 남아있었으나 차츰 체념하면서 내가 할 수 있는 범위에서 최선을 다 하자는 마음을 먹게 된 시기였다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;특히 하반기에 새로운 목표를 세우면서 또 다른 원동력도 생기기 시작했다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;오픈소스&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;미국의 한 스타트업에서 내 보일러 플레이트를 관심 있게 봤다는 연락과, 해당 보일러 플레이트를 사용해서 가장 최근에 만든 DragGPT 익스텐션을 기반으로 크롬 익스텐션 프로덕트를 만들고 있다는 연락을 받았다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #222222; text-align: start;&quot;&gt;Hello JongHak,&lt;/span&gt;&lt;/span&gt;
&lt;div style=&quot;background-color: #ffffff; color: #222222; text-align: start;&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div style=&quot;background-color: #ffffff; color: #222222; text-align: start;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;I'm a big fan of the Chrome extension boilerplate repo you've developed! We are developing ~ and have decided to use your boilerplate for our Chrome extension.&lt;/span&gt;&lt;/div&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;추가 기능 구현에 도움이 줄 의향이 있다면 시간 단위 계약으로 일을 맡기고 싶다는 연락과 함께...&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;첫째로 내 성과물을 지구 반대편의 회사에서 인정해 준다는 점이 날 고양시켰고, 그들이 제시한 시간당 임금도 마음에 들었다.(역시 미국...?)&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;직장생활을 하고 있었고 업무에 지장이 생기길 원치 않았기 때문에 주말마다 적은 시간을 할애할 수 있다고 답했고 그들이 기꺼이 이해해 준 덕분에 미국에 있는 스타트업에 원격으로 조인해서 함께 업무를 해보는 귀중한 경험을 해 볼 수 있었다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;감사하게도 그 이후에도 여러 좋은 제안을 해주셨고, 최근에는 크롬 익스텐션 관련 업무의 니즈가 적어 내가 하는 일은 거의 없지만 이때를 계기로 아직 좋은 인연을 이어 나가고 있다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;a href=&quot;https://nookpi.tistory.com/188&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://nookpi.tistory.com/188&lt;/a&gt;&lt;/span&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1708163675705&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;오픈소스 1K Stars 를 달성하며 느낀 것들&quot; data-og-description=&quot;vite를 사용해보고자 했던 단순한 마음으로 시작했던 오픈소스가 어느덧 1K Stars를 돌파했다. GitHub - Jonghakseo/chrome-extension-boilerplate-react-vite: Chrome Extension Boilerplate with React + Vite + Typescript Chrome Extens&quot; data-og-host=&quot;nookpi.tistory.com&quot; data-og-source-url=&quot;https://nookpi.tistory.com/188&quot; data-og-url=&quot;https://nookpi.tistory.com/188&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/cgomnD/hyVmZXWfbF/nWE4nIQoEEGwscQ3GaML6K/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/GpFfR/hyVmYY2Tmd/QbDCrS0I9wH7RX6K9UwXK1/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/XESCW/hyVjk3L6S6/rBZCK7hDqYE6zpkyEPNv6K/img.png?width=2560&amp;amp;height=662&amp;amp;face=0_0_2560_662&quot;&gt;&lt;a href=&quot;https://nookpi.tistory.com/188&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://nookpi.tistory.com/188&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/cgomnD/hyVmZXWfbF/nWE4nIQoEEGwscQ3GaML6K/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/GpFfR/hyVmYY2Tmd/QbDCrS0I9wH7RX6K9UwXK1/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/XESCW/hyVjk3L6S6/rBZCK7hDqYE6zpkyEPNv6K/img.png?width=2560&amp;amp;height=662&amp;amp;face=0_0_2560_662');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;오픈소스 1K Stars 를 달성하며 느낀 것들&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;vite를 사용해보고자 했던 단순한 마음으로 시작했던 오픈소스가 어느덧 1K Stars를 돌파했다. GitHub - Jonghakseo/chrome-extension-boilerplate-react-vite: Chrome Extension Boilerplate with React + Vite + Typescript Chrome Extens&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;nookpi.tistory.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;10월이 되면서 오픈소스 레포의 Stars가 1000개를 넘겼는데, 이때 내 경험을 공유하면 좋을 것 같아 그간의 기록과 나의 감상을 블로그 글로 남겨두었다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2024-02-17 오후 6.56.50.png&quot; data-origin-width=&quot;1072&quot; data-origin-height=&quot;250&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bBDcM4/btsE0hHHNei/9SeHtyiuP3q1HQ9SEb2yyk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bBDcM4/btsE0hHHNei/9SeHtyiuP3q1HQ9SEb2yyk/img.png&quot; data-alt=&quot;실리콘 밸리에서 일하시는 리크루터였다...ㅎㄷㄷ&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bBDcM4/btsE0hHHNei/9SeHtyiuP3q1HQ9SEb2yyk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbBDcM4%2FbtsE0hHHNei%2F9SeHtyiuP3q1HQ9SEb2yyk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;740&quot; height=&quot;173&quot; data-filename=&quot;스크린샷 2024-02-17 오후 6.56.50.png&quot; data-origin-width=&quot;1072&quot; data-origin-height=&quot;250&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;실리콘 밸리에서 일하시는 리크루터였다...ㅎㄷㄷ&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;최근에는 딱히 관리하지 않는 링크드인 계정으로 이런 메시지도 받아봤다. 이게 다 오픈소스 덕분이다...!&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;고민&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;9월~10월 즈음에 내 인생의 가치와 앞으로의 방향성에 대해 고민이 많아졌다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;미국 스타트업의 정규직 제안도 받고,&amp;nbsp;모 기업의 테크 인터뷰도 통과하면서 솔직히 내가 지금 결정하는 것은 무엇이든 이룰 수 있을 것 같은 자신감으로 가득 차 있었다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;하지만 반대로 내가 뭘 하고 싶은지는 제대로 정리되지 않았고, 어떤 방향성을 가지고 살아야 하는지 고민을 거듭하게 되었다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;남들처럼 평범한 인생의 궤적을 원하기도 하면서 동시에 남들처럼 평범하게 살기는 싫었다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;한국에 있는 남들 다 아는 기업에서 주변 사람들의 인정을 받고 평범하게 결혼 후 가정을 꾸리고 살고 싶기도 했지만, 동시에 모든 걸 내려놓고 해외에 나가 맨땅에서 새로운 도전을 해보고 싶기도 했다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;사실 지금도 내가 진실로 원하는 게 무엇인지 잘 모르겠다. 아니 애초에 둘 중에 하나를 원하는지도 모르겠다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;최근 읽은 '어른의 중력'에서 이러한 구분을 '안정형'과 '의미형'으로 구분하던데, 인간은 결국 두 개의 가치가 조화로워야 행복해진다나 뭐라나...&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;의지력&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;하반기에는 스스로의 의사결정에 있어 내 조악한 의지력을 이겨내기 위한 도구를 만들었다. 그 도구는 3개의 질문을 통해 몸을 강제로 움직이는 프로세스에 가깝다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;1. 하면 좋은 일인가?&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;2. 할 수 있는 일인가?&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;3. 하지 못하는 이유가 있는가?&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;하면 좋고, 할 수 있고, 딱히 하지 못하는 이유가 있는 것들은 내 생각에&amp;nbsp;&lt;b&gt;정말 해야 되는 것, 안 하면 바보 같은 것&lt;/b&gt;이다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;스스로에게 위 3개의 질문을 하면서 게으른 몸을 조금 더 움직이고 나태함과의 싸움에서 승리의 경험을 만드는 것은 험난했지만 그 무엇보다도 큰 성취감을 가져다주었다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;회사&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;10월 즈음에는 회사 동료들과 좀 더 사적인 자리를 많이 가졌다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;영종도, 양평 등 주말에 카페 및 맛집 투어를 다니면서 즐거운 시간을 보냈다. 편하고 친한 사람들이어서 그런지 즐거움이 참 많았다. 오픈소스 프로젝트도 같이 진행하는 등 회사 동료분들과 더 친해지는 계기였다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;이맘때의 활동은 근황 글에 자세히 남겨둔 관계로 해당 글로 갈음한다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;a href=&quot;https://nookpi.tistory.com/187&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://nookpi.tistory.com/187&lt;/a&gt;&lt;/span&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1708165774472&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;근황&quot; data-og-description=&quot;0. 직장 동료들과 함께 주말에(!) 영종도에 유명한 카페에 가고 을왕리 해수욕장에도 갔다. 주말에 직장 동료들과 사적인 시간을 보내는게 처음이라서 어색했으나 굉장히 즐거운 시간을 보냈다. &quot; data-og-host=&quot;nookpi.tistory.com&quot; data-og-source-url=&quot;https://nookpi.tistory.com/187&quot; data-og-url=&quot;https://nookpi.tistory.com/187&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/O1mGO/hyVm1BsQD8/WoIJJmiSNwjKaI94UAswKK/img.png?width=800&amp;amp;height=442&amp;amp;face=0_0_800_442,https://scrap.kakaocdn.net/dn/bY3dLw/hyVmZwSsOO/nRLambRU0dlkLbaxCjpCS1/img.png?width=800&amp;amp;height=442&amp;amp;face=0_0_800_442,https://scrap.kakaocdn.net/dn/jl5PN/hyVjdQ6l69/cXlTuixZ5PfswCCmk4YJR0/img.png?width=1500&amp;amp;height=830&amp;amp;face=0_0_1500_830&quot;&gt;&lt;a href=&quot;https://nookpi.tistory.com/187&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://nookpi.tistory.com/187&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/O1mGO/hyVm1BsQD8/WoIJJmiSNwjKaI94UAswKK/img.png?width=800&amp;amp;height=442&amp;amp;face=0_0_800_442,https://scrap.kakaocdn.net/dn/bY3dLw/hyVmZwSsOO/nRLambRU0dlkLbaxCjpCS1/img.png?width=800&amp;amp;height=442&amp;amp;face=0_0_800_442,https://scrap.kakaocdn.net/dn/jl5PN/hyVjdQ6l69/cXlTuixZ5PfswCCmk4YJR0/img.png?width=1500&amp;amp;height=830&amp;amp;face=0_0_1500_830');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;근황&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;0. 직장 동료들과 함께 주말에(!) 영종도에 유명한 카페에 가고 을왕리 해수욕장에도 갔다. 주말에 직장 동료들과 사적인 시간을 보내는게 처음이라서 어색했으나 굉장히 즐거운 시간을 보냈다.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;nookpi.tistory.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 id=&quot;파트 리드&quot; data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;파트 리드&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;12월 초부터 프론트엔드 파트 리드를 맡게 되었다. 여러 우여곡절이 있었지만, 가장 큰 부분은 스스로의 심경 변화였다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;나는 지금 회사에서 자율과 책임의 원칙 아래 좋은 피드백을 받으며 큰 성장을 이뤘다고 생각한다. 그리고 이런 성장은 동료들의 피드백과 지지 없이 스스로 절대 이룩할 수 없었던 부분이라고 진심으로 생각하며 팀에 감사하고 있다. (말로 다 표현 못하지만 늘 감사합니다...)&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;그런 이유로 나는 팀에 일종의 채무감을 늘 가지고 있었다. 새로 합류한 프론트엔드 파트원들이 내가 받은 것과 동일한, 혹은 더 뛰어난 피드백과 격려, 지지를 받아 스스로 만족할 수 있을 만큼 성장하게 도와드려야 한다고 생각했다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;쉽게 말해 받은 만큼 돌려주고 싶다는 마음이었던 것 같다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;하지만 죄송하게도 그렇게 하지 못하고 있다는 느낌을 늘 받고 있었고, 누군가 총대를 메고 이 부분을 개선해야 한다고 생각하고 있었다. 골치 아픈 일을 피하고 싶은 나의 욕심과 방황에 밀려 크게 신경 쓰지 못했던 것도 죄송하고...&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&quot;그걸 왜 네가 해?&quot;라고 누군가 물어본다면... (다소 건방지게 들릴지 모르지만) '내가 매니징을 하는 것 자체를 귀찮아하는 것과 별개로 잘할 자신은 있다'라고 대답할 수 있겠다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;파트 리드를 맡고 제일 먼저 한 것은 파트원들과의 1:1이었다. 제대로 진행되지 않던 1:1을 다소 촘촘한 간격으로 진행하면서, 파트원 개인이&amp;nbsp; 가지고 있는 각자의 니즈와 만족하는 부분에 촉각을 곤두세웠다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;각자가 원하는 것에 대해 알아내는 것은 정말 어려운 일이었지만, 모두의 니즈를 충족시킬 수 있는 나의 스탠스를 정립하는 것은 그 이상으로 힘든 일이었다. 회사의 비즈니스 방향성과 파트원 개인의 니즈를 일치시켜 윈윈 할 수 있는 구조를 만들기 위해 노력했고 스쿼드 재배치, 업무 분장의 정리 등으로 구성원의 만족도도 올리면서 아웃풋도 개선되는 성과를 거뒀다.(고 생각한다)&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;두 번째로 한 일은 모든 Pull Request에 대한 코드 리뷰였다. 파트 리드에게 요구되는 역량은 조직마다 조금씩 다르겠지만, 우리 팀에서는 프로덕트의 방향성과 코드 퀄리티 등 개선안에 대해 자신감 있게 의견을 낼 수 있는 기술적인 역량도 크게 요구되었다. 더불어 파트 전체를 관통하는 컨텍스트의 공유를 위해 나 스스로가 일종의 허브가 되기로 결심했다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;파트원들의 생산성을 높이고 바퀴를 재발명하는 일이 없게끔 시작한 전체 코드 리뷰였지만, 초반에는 정말 쉽지 않았다. 열심히 일하는 파트원분들 덕분에 하루에 10개에 달하는 코드 리뷰를 보는 날도 있었다. 9시에 출근해서 3시까지 코드 리뷰만 본 날도 있었다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;한 편으로는 모든 PR을 리뷰하기 정말 잘했다는 생각이 들었는데, 리뷰를 하면서 자연스럽게 파트원 5명의 업무 진행 방식이나 진척도 등 업무 진행에 대한 정보도 알게 되었고 각 스쿼드 간에 존재하는 비효율 등을 찾아 생산성을 개선할 수 있었다. 무엇보다도 리뷰어가 없이 소외되는 PR이 생기지 않았고 양질의 리뷰와 성장에 초점을 둔 리뷰 코멘트를 남기려고 노력했다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;추후 구성원들로 하여금 업무에 대한 피드백이 이뤄지고 있으며 이로 인한 성장이 업무 만족도에 영향을 준다는 부분을 알게 되어 뿌듯했다. 궁극적으로는 성장을 통한 자아효능감이 팀원들의 리텐션에도 좋은 영향을 주지 않을까...?&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;세 번째로 한 일은 파트 내부의 유대감을 만드는 일이었다. 파트 내부의 유대감을 빠르게 형성하는 게 신규 입사자에게도 중요하고, 기존 구성원들에게도 심리적인 안정감을 제공해 줄 수 있다고 생각했다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;파트원들의 접점이 적고 대화의 시간 자체가 절대적으로 부족하다고 판단해서 매주 금요일에 1시간씩 '기술 잡담' 시간을 만들었다. '기술 잡담'이라는 이름에서 드러나듯이 구성원들이 부담 없이 기술에 대한 이야기를 할 수 있는 공간이면서 동시에 기술 이야기를 하지 않고 잡담만 해도 되는 시간을 만들었다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;첫 주에는 다소 어색한 분위기에서 기술적인 이야기보다는 잡담 위주로 시간을 보냈지만, 매주 이러한 시간이 지속되니 점차 편한 분위기에서 기술적인 고민을 나누거나 아젠다를 준비해 오는 빈도가 늘어나기 시작했다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;또한 기존 슬랙 프론트엔드 채널에서 업무 이야기를 함께 나누다 보니 가벼운 일상을 공유하기 어렵고, 업무 관계자들이 이미 채널에 많이 들어와 있어 부담스럽다는 건의가 있었다. 이를 수용하여 &lt;span style=&quot;letter-spacing: 0px;&quot;&gt;프론트엔드 파트원들만 수다를 떨 수 있는 편한 채널을 개설해서 외부 인원의 유입을 막고 소소한 일상을 나눌 수 있게 했고, 지금은 파트원들이 해당 채널에서 여행 경험이나 사진 등 활발하게 일상을 공유하고 있다.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;그  밖에도 퇴근 후 회식이나 번개의 빈도도 늘려 좀 더 친하게 지내려고 많이 노력했던 것 같다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;매니저로서도 동료 개발자로서도 좋은 자극을 주고받으며 행복하게 일할 수 있는 환경을 만들기 위해 나름 최선을 다 했다고 생각한다. 물론 모든 부분에서 완벽하진 못했지만 2024년 초 현재까지는 체감적으로는 프론트엔드 파트의 분위기가 상당히 좋아진 것 같아 뿌듯하다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;2023 총평&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;'비 온 뒤에 땅이 굳는다'라고 한다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;비가 온&amp;nbsp;직후, 땅은 걷기도 힘들고 보기에도 안 좋은 진창이 되지만 시간이 지나 물이 마르면 적당히 단단하고 폭신폭신한 걷기 좋은 흙길이 된다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;작년 한 해가 나에게는 그런 과정이었다고 생각한다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;상반기에는 많은 비가 나를 진창에서 허우적대게 만들었고 실제로 내 모습도 썩 보기 좋지 않았다. 다행히 하반기에 이르러 비가 적당히 마르고 내 스스로의 날도 어느 정도 벼려진  보기 좋은 모습으로 마무리했다고 생각한다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;올해는 또 다른 큰 도전을 해보려고 한다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;걷지 않은 길은 늘 두렵지만, 또 늘 그렇듯이 걷고 나서 뒤돌아보면 정말 별거 아니었다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;용기는 낭떠러지 앞에서 꽉 쥐는 주먹이 아니라, 낭떠러지까지 걸어 올라온 두 발에 숨어있다고 생각한다... 내년에도 잘 해보자!&lt;/span&gt;&lt;/p&gt;</description>
      <category>잡담</category>
      <category>2023 회고</category>
      <category>회고</category>
      <author>눅눅한 피부</author>
      <guid isPermaLink="true">https://nookpi.tistory.com/191</guid>
      <comments>https://nookpi.tistory.com/191#entry191comment</comments>
      <pubDate>Sat, 17 Feb 2024 20:36:04 +0900</pubDate>
    </item>
    <item>
      <title>근황</title>
      <link>https://nookpi.tistory.com/190</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;0. 작년 말 즈음부터 프론트엔드 파트 리드를 맡게 되었다. 정신없고 바빠졌지만 그래도 생각했던 범위 안쪽이라 잘 적응해 나가고 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. 2023 회고를 써야 하는데 자꾸 미루고 있다. 너무 많은 일들이 있어서인지 엄두가 안 나는 것 같기도 하고...? '부담을 내려놓고 편하게 써야지'라고 50번째 생각만 하는 중.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. 오랜만에 React 소스코드 톺아보기 스터디를 진행하고 있는데 역시 재미있다. 처음 React 소스코드를 보면서 배우는 점이 참 많고 재밌었는데 그 기분을 동료들과 다시 나누니 더 즐거운 것 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. React 소스코드 스터디를 하기 전에 'React 소스코드의 동작을 이해하면 이런 것들도 할 수 있어요!'와 같은 동기부여 용도로 익스텐션 POC를 하나 만들었다. 개발 환경에서 컴포넌트를 클릭하면 해당 소스코드의 위치로 IDE를 열어주는 기능인데, 생각보다 반응이 좋고 생산성에도 도움이 되는 느낌이라 각 잡고 개발 중이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;당연하겠지만 나중에 찾아보니 비슷한 프로젝트들이 있길래 쬐~끔 더 발전시켜서 만들고 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;figure data-ke-type=&quot;video&quot; data-ke-style=&quot;alignCenter&quot; data-video-host=&quot;kakaotv&quot; data-video-url=&quot;https://tv.kakao.com/v/444614820&quot; data-video-thumbnail=&quot;https://scrap.kakaocdn.net/dn/GsGWq/hyVja7ggb0/kWOnG03YdhD9A7RqKINSHk/img.jpg?width=1280&amp;amp;height=676&amp;amp;face=550_440_596_490,https://scrap.kakaocdn.net/dn/dy8ioq/hyVjcqsMzq/DUFRKjNVpCZdQjJQ1bjAgK/img.jpg?width=1280&amp;amp;height=676&amp;amp;face=550_440_596_490&quot; data-video-width=&quot;757&quot; data-video-height=&quot;400&quot; data-video-origin-width=&quot;860&quot; data-video-origin-height=&quot;454&quot; data-ke-mobilestyle=&quot;widthContent&quot; data-video-play-service=&quot;daum_tistory&quot; data-original-url=&quot;&quot; data-video-title=&quot;&quot;&gt;&lt;iframe src=&quot;https://play-tv.kakao.com/embed/player/cliplink/444614820?service=daum_tistory&quot; width=&quot;757&quot; height=&quot;400&quot; frameborder=&quot;0&quot; allowfullscreen=&quot;true&quot;&gt;&lt;/iframe&gt;
&lt;figcaption style=&quot;display: none;&quot;&gt;&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;4. 뭔가 책을 꾸준하게 읽는 게 어려워서 고민이다. 진득하게 활자에 집중하는 능력이 상당히 떨어진 기분...&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;5. 삶의 방향성과 가치에 대해 고민하는 날이 많은데, 누구도 날 대신해 결정해 줄 수 없는 문제라는 것을 알기에 살짝 외롭다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;6.  처음으로 실리콘밸리 테크 리크루터에게 링크드인으로 연락을 받았다. 대단한 내용은 아니고 github가 흥미로워 포트폴리오를 받을 수 있냐는 내용이었는데 기분이 매우 좋았다.&lt;/p&gt;</description>
      <category>잡담</category>
      <category>근황</category>
      <author>눅눅한 피부</author>
      <guid isPermaLink="true">https://nookpi.tistory.com/190</guid>
      <comments>https://nookpi.tistory.com/190#entry190comment</comments>
      <pubDate>Wed, 14 Feb 2024 14:20:21 +0900</pubDate>
    </item>
    <item>
      <title>HMR의 작은 비밀</title>
      <link>https://nookpi.tistory.com/189</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;개발자의 생산성은 무엇에 달려있을까?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;바로 input과 output 간의 이어지는 사이클을 극단적으로 줄여 빠른 피드백을 반복적으로 얻을 수 있는 환경이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;빌드에 2분이 걸리는 프로젝트가 있고, 코드 변경의 결과물을 확인하기 위해서는 꼭 다시 빌드를 해야 한다고 생각해 보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;내 코드의 변경사항이 어떤 결과로 나타내는지를 알기 위해서는 2분이라는 시간이 필요하다. 만약 실수했다면? 다시 수정 후 빌드를 하는 2분을 기다려야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 시간들은 당신을 좀 더 여유롭게 살 수 있게 해 줄 수 있을지도 모르지만, 적어도 생산적인 사람으로 만들진 못할 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;모든 개발 환경은 이러한 잉여 시간을 줄이고 빠른 피드백을 주기 위해 발전해 왔다. 테스트 코드, HMR(Hot Moudle Replacement), JetPack과 Flutter의 Hot Reload 등등...&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;특히 프론트엔드 개발 생태계에서는 &lt;a href=&quot;https://webpack.kr/guides/hot-module-replacement/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;웹팩에서 주도했던 HMR&lt;/a&gt;가 큰 영향을 미쳤다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;HMR의 기능을 한 문장으로 요약하면 '로컬 파일의 변경사항이 생기면 해당 모듈을 빠르게 교체' 하는 것이다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;내 오픈소스에서도 해당 기능을 아주 살짝? 비슷... 하게 구현하고 있는데, 특정 파일만을 교체하는 것이 아니라 전체 파일을 다시 빌드 후 익스텐션을 새로 로드하는 방식을 사용한다. (그래서 이름도 Hot Rebuild Reload/Refresh인 HRR로 지었다)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;로컬 파일의 아웃풋이 필요한 크롬 익스텐션의 특성상 이러한 방식으로 구현이 되었는데, &lt;a href=&quot;https://github.com/crxjs/chrome-extension-tools&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;vite 크롬 익스텐션 플러그인&lt;/a&gt;의 경우 빌트인 개발서버를 후킹 해서 개발서버에서 서빙되는 모듈을 로컬 파일에 그때그때 쓰고 reload 하는 방식을 사용하고 있다. 후덜덜...&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;어쨌거나 이런 이유로 반쪽짜리인 HRR만을 운영하던 내 오픈소스 레포에 이런 이슈가 올라왔다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://github.com/Jonghakseo/chrome-extension-boilerplate-react-vite/issues/261&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://github.com/Jonghakseo/chrome-extension-boilerplate-react-vite/issues/261&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1699779774402&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;object&quot; data-og-title=&quot;manifest.json file listens for compilation issues &amp;middot; Issue #261 &amp;middot; Jonghakseo/chrome-extension-boilerplate-react-vite&quot; data-og-description=&quot;The manifest.ts file has added listening, but the contents modified after starting local development are not updated in the compiled dist file It is hoped that the contents of the manifest.ts file ...&quot; data-og-host=&quot;github.com&quot; data-og-source-url=&quot;https://github.com/Jonghakseo/chrome-extension-boilerplate-react-vite/issues/261&quot; data-og-url=&quot;https://github.com/Jonghakseo/chrome-extension-boilerplate-react-vite/issues/261&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/B8gEQ/hyUuTLH7li/cIl1TMWFZvIk7GmULkoqH0/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600&quot;&gt;&lt;a href=&quot;https://github.com/Jonghakseo/chrome-extension-boilerplate-react-vite/issues/261&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://github.com/Jonghakseo/chrome-extension-boilerplate-react-vite/issues/261&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/B8gEQ/hyUuTLH7li/cIl1TMWFZvIk7GmULkoqH0/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;manifest.json file listens for compilation issues &amp;middot; Issue #261 &amp;middot; Jonghakseo/chrome-extension-boilerplate-react-vite&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;The manifest.ts file has added listening, but the contents modified after starting local development are not updated in the compiled dist file It is hoped that the contents of the manifest.ts file ...&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;github.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;내 보일러 플레이트에서는 manifest.ts 파일의 내용을 바탕으로 maifest.json을 생성해 주는데, manifest.ts 파일의 변경사항이 rebuild는 발생시키지만 정작 생성된 manifest.json을 보면 최초의 내용을 그대로 가지고 있어서 아무것도 업데이트되지 않는 문제가 있었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, manifest 파일을 수정하고 그 내용을 반영하려면 pnpm dev를 다시 하고 익스텐션도 수동으로 reload를 해줘야 하는 불편함이 있었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;대체 왜 manifest.ts 파일의 변경사항은 빌드 시에 반영되지 않는 건지 궁금해서 알아보니, 아니 글쎄 한 번 가져온 모듈은 리소스 낭비를 위해 캐싱을 한다는 게 아닌가?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://nodejs.org/api/modules.html#caching&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;NodeJS 공식 문서&lt;/a&gt;를 보니 require를 통해 가져온 모듈의 경우 require.cache에 캐싱된 내용이 저장되고, delete require.cache['&amp;lt;moduleName&amp;gt;']을 통해 캐시 무효화를 할 수 있었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ESM을 사용하는 나의 경우에는 어떤 식으로 해결할 수 있을지 찾아보니 일단 import()를 통한 동적 모듈 로딩은 수동으로 캐시 무효화가 불가능했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 방법은 있었으니... 바로 모듈 뒤에 쿼리 URL을 붙이는 것이다. 이게 무슨 뜬금없는 소리지?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://ar.al/2021/02/22/cache-busting-in-node.js-dynamic-esm-imports/&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://ar.al/2021/02/22/cache-busting-in-node.js-dynamic-esm-imports/&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1699780347007&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;Cache busting in Node.js dynamic ESM imports&quot; data-og-description=&quot;I&amp;rsquo;m porting JSDB to EcmaScript Modules (ESM) and one of the issues I had to look into was module cache invalidation. JSDB is my little in-memory native JavaScript Database that writes JavaScript operations to append-only JavaScript logs that have UMD hea&quot; data-og-host=&quot;ar.al&quot; data-og-source-url=&quot;https://ar.al/2021/02/22/cache-busting-in-node.js-dynamic-esm-imports/&quot; data-og-url=&quot;https://ar.al/2021/02/22/cache-busting-in-node.js-dynamic-esm-imports/&quot; data-og-image=&quot;&quot;&gt;&lt;a href=&quot;https://ar.al/2021/02/22/cache-busting-in-node.js-dynamic-esm-imports/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://ar.al/2021/02/22/cache-busting-in-node.js-dynamic-esm-imports/&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url();&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;Cache busting in Node.js dynamic ESM imports&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;I&amp;rsquo;m porting JSDB to EcmaScript Modules (ESM) and one of the issues I had to look into was module cache invalidation. JSDB is my little in-memory native JavaScript Database that writes JavaScript operations to append-only JavaScript logs that have UMD hea&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;ar.al&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;뜬금없다고 생각했으나 &lt;a href=&quot;https://nodejs.org/api/esm.html#file-urls&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;공식문서&lt;/a&gt;를 살펴보니 정말로 그러한 내용이 있었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;i&gt;Modules are loaded multiple times if the&lt;span&gt;&amp;nbsp;&lt;/span&gt;import&lt;span&gt;&amp;nbsp;&lt;/span&gt;specifier used to resolve them has a different query or fragment.&lt;/i&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;vite에서도 해당 내용이 반영된 소스코드를 찾을 수 있었는데, `t=${timeStamp}` 형태로 cache busting과 해당 모듈이 어떤 시점에 로드되었는지 식별이 가능한 형태로 구현하고 있다는 것을 알게 되었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://github.com/vitejs/vite/blob/main/packages/vite/src/node/server/middlewares/indexHtml.ts#L132&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://github.com/vitejs/vite/blob/main/packages/vite/src/node/server/middlewares/indexHtml.ts#L132&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;결론적으로는 나도 동일한 방식을 통해 구현했는데, manifest 파일의 변경 시 rebuild를 하게 되면, 캐시가 무효화된 manifest.js를 가져와 그때그때 새로 manifest.json을 생성하도록 했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;결과적으로 위 이슈를 해결하면서 배운 것들은 다음과 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. require, import와 같이 JS에서의 모듈 로드 시스템은 최적화를 위해서 기존 모듈을 캐싱한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. require의 경우 직접 캐시 객체를 수정하여 cache bursting을 달성한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. import의 경우는 직접 캐시 객체를 비울 수 없어 모듈 이름 뒤의 인자를 수정해줘야 한다. 이 경우 모듈을 다시 로드한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;4. windows 환경에서는 import() 인자에 file schema가 강제된다. 따라서 &lt;a href=&quot;https://nodejs.org/api/url.html#urlpathtofileurlpath&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;url.pathToFileURL&lt;/a&gt; 사용이 강제된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사실 이슈를 봤을 때 이렇게 재밌는 사실을 많이 알게 될 줄은 몰랐는데, 참 이런 경험이 새로운 것들을 재미있고 자연스럽게 익힐 수 있는 계기인 것 같아 기록용으로 남겨준다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;figure id=&quot;og_1699781735419&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;object&quot; data-og-title=&quot;enhance: Modify to let the plugin do the build completion detection not file system. by Jonghakseo &amp;middot; Pull Request #265 &amp;middot; Jongh&quot; data-og-description=&quot;Fixed #184&quot; data-og-host=&quot;github.com&quot; data-og-source-url=&quot;https://github.com/Jonghakseo/chrome-extension-boilerplate-react-vite/pull/265&quot; data-og-url=&quot;https://github.com/Jonghakseo/chrome-extension-boilerplate-react-vite/pull/265&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/bjV3Rj/hyUuURnEog/DxyvCSCKKDAFM9wQhTozRk/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600&quot;&gt;&lt;a href=&quot;https://github.com/Jonghakseo/chrome-extension-boilerplate-react-vite/pull/265&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://github.com/Jonghakseo/chrome-extension-boilerplate-react-vite/pull/265&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/bjV3Rj/hyUuURnEog/DxyvCSCKKDAFM9wQhTozRk/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;enhance: Modify to let the plugin do the build completion detection not file system. by Jonghakseo &amp;middot; Pull Request #265 &amp;middot; Jongh&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Fixed #184&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;github.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;figure id=&quot;og_1699781739607&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;object&quot; data-og-title=&quot;fix: windows dynamic import protocol issue by Jonghakseo &amp;middot; Pull Request #268 &amp;middot; Jonghakseo/chrome-extension-boilerplate-react-v&quot; data-og-description=&quot;Fixed #266 (comment)&quot; data-og-host=&quot;github.com&quot; data-og-source-url=&quot;https://github.com/Jonghakseo/chrome-extension-boilerplate-react-vite/pull/268&quot; data-og-url=&quot;https://github.com/Jonghakseo/chrome-extension-boilerplate-react-vite/pull/268&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/g89qJ/hyUu3t0Wdo/pUMkUWnpJZ6ULhDWVDTI71/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600&quot;&gt;&lt;a href=&quot;https://github.com/Jonghakseo/chrome-extension-boilerplate-react-vite/pull/268&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://github.com/Jonghakseo/chrome-extension-boilerplate-react-vite/pull/268&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/g89qJ/hyUu3t0Wdo/pUMkUWnpJZ6ULhDWVDTI71/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;fix: windows dynamic import protocol issue by Jonghakseo &amp;middot; Pull Request #268 &amp;middot; Jonghakseo/chrome-extension-boilerplate-react-v&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Fixed #266 (comment)&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;github.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>취미로 하는 개발</category>
      <category>Import</category>
      <category>require</category>
      <author>눅눅한 피부</author>
      <guid isPermaLink="true">https://nookpi.tistory.com/189</guid>
      <comments>https://nookpi.tistory.com/189#entry189comment</comments>
      <pubDate>Sun, 12 Nov 2023 18:36:23 +0900</pubDate>
    </item>
    <item>
      <title>오픈소스 1K Stars 를 달성하며 느낀 것들</title>
      <link>https://nookpi.tistory.com/188</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://github.com/Jonghakseo/chrome-extension-boilerplate-react-vite&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;vite를 사용해보고자 했던 단순한 마음으로 시작했던 오픈소스&lt;/a&gt;가 어느덧 1K Stars를 돌파했다.&lt;/p&gt;
&lt;figure id=&quot;og_1699778599432&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;object&quot; data-og-title=&quot;GitHub - Jonghakseo/chrome-extension-boilerplate-react-vite: Chrome Extension Boilerplate with React + Vite + Typescript&quot; data-og-description=&quot;Chrome Extension Boilerplate with React + Vite + Typescript - GitHub - Jonghakseo/chrome-extension-boilerplate-react-vite: Chrome Extension Boilerplate with React + Vite + Typescript&quot; data-og-host=&quot;github.com&quot; data-og-source-url=&quot;https://github.com/Jonghakseo/chrome-extension-boilerplate-react-vite&quot; data-og-url=&quot;https://github.com/Jonghakseo/chrome-extension-boilerplate-react-vite&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/kTpHY/hyUu56rvuW/3Wzka0VUwVqVICepUBZnGK/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600&quot;&gt;&lt;a href=&quot;https://github.com/Jonghakseo/chrome-extension-boilerplate-react-vite&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://github.com/Jonghakseo/chrome-extension-boilerplate-react-vite&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/kTpHY/hyUu56rvuW/3Wzka0VUwVqVICepUBZnGK/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;GitHub - Jonghakseo/chrome-extension-boilerplate-react-vite: Chrome Extension Boilerplate with React + Vite + Typescript&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Chrome Extension Boilerplate with React + Vite + Typescript - GitHub - Jonghakseo/chrome-extension-boilerplate-react-vite: Chrome Extension Boilerplate with React + Vite + Typescript&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;github.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;누군가에게는 사소한 1K일 수 있지만, 1000명이 넘는 개발자들이 내 프로젝트를 '볼 가치가 있다'라고 생각해 준 점이 나에게는 큰 감동으로 다가왔다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;내가 운영 중인 오픈소스는 이전에 블로그에서도 몇 번 소개했던 크롬 익스텐션 제작을 위한 보일러플레이트이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;크롬 익스텐션을 몇 개 만들면서 개발환경 구성이 어렵고 문서를 찾기 어렵다는 점을 느껴, 그 당시 사용해보고 싶었던 vite를 활용해서 얼기설기 만든 것이 시작이었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://nookpi.tistory.com/140&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://nookpi.tistory.com/140&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1699569327134&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;프론트엔드 주니어의 오픈소스 기웃대기&quot; data-og-description=&quot;들어가기 전에... 이 글은 경력 2년도 채 되지 않은 주니어 프론트엔드 개발자가 오픈소스를 기웃거린 경험을 다루고 있습니다. 오픈소스에 기여에 대한 의욕만을 가지고 후안무치(厚顔無恥)의 &quot; data-og-host=&quot;nookpi.tistory.com&quot; data-og-source-url=&quot;https://nookpi.tistory.com/140&quot; data-og-url=&quot;https://nookpi.tistory.com/140&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/drvfnM/hyUrxpm4Va/sVFERzdl8NyogPokTgmdJk/img.png?width=664&amp;amp;height=560&amp;amp;face=0_0_664_560,https://scrap.kakaocdn.net/dn/b2o51C/hyUu1PRfvA/0ESFS4uP4RThvOwdjnIJ4K/img.png?width=664&amp;amp;height=560&amp;amp;face=0_0_664_560,https://scrap.kakaocdn.net/dn/bix6Uy/hyUrz1L4x9/I5kEV7QXkDXl3hZQlHwsm0/img.png?width=2450&amp;amp;height=660&amp;amp;face=0_0_2450_660&quot;&gt;&lt;a href=&quot;https://nookpi.tistory.com/140&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://nookpi.tistory.com/140&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/drvfnM/hyUrxpm4Va/sVFERzdl8NyogPokTgmdJk/img.png?width=664&amp;amp;height=560&amp;amp;face=0_0_664_560,https://scrap.kakaocdn.net/dn/b2o51C/hyUu1PRfvA/0ESFS4uP4RThvOwdjnIJ4K/img.png?width=664&amp;amp;height=560&amp;amp;face=0_0_664_560,https://scrap.kakaocdn.net/dn/bix6Uy/hyUrz1L4x9/I5kEV7QXkDXl3hZQlHwsm0/img.png?width=2450&amp;amp;height=660&amp;amp;face=0_0_2450_660');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;프론트엔드 주니어의 오픈소스 기웃대기&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;들어가기 전에... 이 글은 경력 2년도 채 되지 않은 주니어 프론트엔드 개발자가 오픈소스를 기웃거린 경험을 다루고 있습니다. 오픈소스에 기여에 대한 의욕만을 가지고 후안무치(厚顔無恥)의&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;nookpi.tistory.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://nookpi.tistory.com/147&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://nookpi.tistory.com/147&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1699569345012&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;오픈소스를 통해 후원을 받은 후기&quot; data-og-description=&quot;이전 글인 프론트엔드 주니어의 오픈소스 기웃대기라는 글의 마지막 부분에서 링크드인을 통해 후원 의사가 있는 분의 연락을 받았다는 내용을 적은 적이 있었다. 그분은 github sponsor 기능을 통&quot; data-og-host=&quot;nookpi.tistory.com&quot; data-og-source-url=&quot;https://nookpi.tistory.com/147&quot; data-og-url=&quot;https://nookpi.tistory.com/147&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/CKP6i/hyUrqw0BWl/zpRDHbcXxwvuitI906N5i1/img.png?width=800&amp;amp;height=590&amp;amp;face=0_0_800_590,https://scrap.kakaocdn.net/dn/I2VHN/hyUrwDYWUd/Z86wBeyLqi8gvk6zhaDAQK/img.png?width=800&amp;amp;height=590&amp;amp;face=0_0_800_590,https://scrap.kakaocdn.net/dn/ZllZ0/hyUuYMnUyU/cm41QiCdQf9E422WkYCnCk/img.png?width=1326&amp;amp;height=978&amp;amp;face=0_0_1326_978&quot;&gt;&lt;a href=&quot;https://nookpi.tistory.com/147&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://nookpi.tistory.com/147&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/CKP6i/hyUrqw0BWl/zpRDHbcXxwvuitI906N5i1/img.png?width=800&amp;amp;height=590&amp;amp;face=0_0_800_590,https://scrap.kakaocdn.net/dn/I2VHN/hyUrwDYWUd/Z86wBeyLqi8gvk6zhaDAQK/img.png?width=800&amp;amp;height=590&amp;amp;face=0_0_800_590,https://scrap.kakaocdn.net/dn/ZllZ0/hyUuYMnUyU/cm41QiCdQf9E422WkYCnCk/img.png?width=1326&amp;amp;height=978&amp;amp;face=0_0_1326_978');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;오픈소스를 통해 후원을 받은 후기&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;이전 글인 프론트엔드 주니어의 오픈소스 기웃대기라는 글의 마지막 부분에서 링크드인을 통해 후원 의사가 있는 분의 연락을 받았다는 내용을 적은 적이 있었다. 그분은 github sponsor 기능을 통&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;nookpi.tistory.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해당 오픈소스 레포는 2022년 4월 10일에 처음 만들어져서, 지금까지 1년 반이라는 시간 동안 지속적으로 유지보수를 진행하고 있다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;레포를 처음 만든 이후부터 현재까지 운영하는 과정에서 희노애락과 여러 기회들이 많이 생겼다. 열정 넘치게 유지보수를 하던 시기도 있었고, 때로는 살짝 심드렁해져 한 두 달 동안 방치되는 시간도 있었다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 과정에서 경험하고 느낀 재밌는 부분들을 주로 소개하려고  한다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;초기 홍보가 중요하다?&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;vite 관련 오픈소스가 상대적으로 적었던 작년 4월, 보일러 플레이트를 만들고 나서 얼마 지나지 않아 &lt;a href=&quot;https://github.com/vitejs/awesome-vite&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;awsome vite&lt;/a&gt;에 내 오픈소스 레포를 추가했다. awsome vite는 vite로 만들어진 오픈소스를 리스트업 하고 사람들로 하여금 쉽게 찾을 수 있게 제공하는 곳인데, awsome 시리즈는 언어, 프레임워크, 라이브러리 등 굉장히 많아서 익숙하신 분들이 많을 거라 생각한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그 당시 awsome vite 목록에 chrome extension 키워드가 들어간 레포는 내 오픈소스가 유일했으므로 초기 유입에 굉장히 도움이 되었을 것이라고 추측하고 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사실 그 이후 별다른 홍보는 하지 않았지만, 이때 확보한 초기 이용자들(~100 Stars)로 인해 SEO도 더 잘 되었다고 생각한다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;생각보다 기여해 주시는 분들이 많다.&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;부끄럽게도 나는 스스로의 오픈소스를 운영하기 전부터 지금까지도 다른 오픈소스 생태계에 적극적으로 기여하지 않았다. 물론 실력이 부족한 문제도 있었지만 귀찮음도 한몫을 했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사실 애정이나 관심이 없으면 issue를 올리는 것 자체도 쉬운 일이 아니다. 더군다나 PR로 기여를 하기 위해서는&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&amp;nbsp;&lt;/span&gt;해당 프로젝트를 포크하고, 로컬에서 수정 후 기여하려는 레포의 브랜치를 base로 설정하고 PR을 올리는 과정을 거쳐야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;지난 시간을 돌이켜보면, 이런 귀찮음을 감수하고 오픈소스에 기여를 해주시는 분들이 참 많았던 것 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;단순히 issue 생성이나 PR뿐 아니라 타인의 issue를 보고 내 대신 더 좋은 해결방법이나 아예 다른 관점에서의 해결책을 제시해 주시는 분들도 많았는데 이분들이 내게 큰 동기부여가 되었다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;기여는 너무 감사하지만 검토는 잘해야 한다.&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;초기에는 PR 등의 기여가 발생하면 일단 기여를 해줬다는 사실 자체가 너무 감사해서(?) 믿고 Approve 후 머지를 하는 경향이 있었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;내가 maintainer로써 책임감을 더 가지고 크로스체크 이후에 머지를 하는 게 맞았는데, 어느 날 버그가 있는 PR을 머지하는 경험을 하고 나서야 정신을 차렸다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기여의 선한 의도와는 별개로, 프로젝트의 맥락과 구조를 제일 잘 이해하고 있으며 유지보수의 책임이 있는 내가 더욱 꼼꼼히 검토하는 태도가 필요하다는 사실을 깨달았다. (이상한 PR이 가끔 있다.)&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Issue와 PR의 비율은 체감상 9:1&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실제로는 그렇지 않지만, maintainer인 내가 느끼는 두 기여의 비율은 9:1 정도였다. 많은 사람들은 쉽게 생성할 수 있는 issue를 생성해서 아이디어에 대해 건의하거나 버그를 제보했지만, 발견한 버그나 개선점에 대해 작업 후 PR을 올리는 사람들의 비율은 매우 낮았다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;당연한 일이라는걸 알았지만 가끔 그 부분에서 힘듦을 느꼈던 것 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;한창 다른 관심사에 빠져 유지보수를 게을리하다 보니, 동시에 열려있는 issue가 20개 가까이 되던 시기가 있었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;issue가 쌓이면서 체력과 시간, 그리고 의욕에 한계가 느껴진다는 생각이 들었고 누군가가 도와줬으면 하는 생각이 많이 들던 시기였다. 그 당시 나는 '왜 이 사람들은 이 정도의 이슈라면 스스로 해결하지 않지? 혹은 PR로 직접 해서 제안하지 않지?' 하는 다소 배부른 생각도 가지고 있었다. 사실 issue건 PR이건 기여해 주는 것 자체가 감사한 일인데...&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;최근에는 &lt;a href=&quot;https://github.com/PatrykKuniczak&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;좋은 아이디어와 훌륭한 디버깅 능력, 엄청난 적극성으로 도와주시는 분&lt;/a&gt;이 생겨서 큰 동기부여가 되고 이런 생각은 더이상 하지 않지만, 한창 동기부여가 안 되던 시기에 시간을 충분히 내지 못해 미안하다던 내게 &lt;i&gt;'나도 시간이 없어. 빨리 해결해줘야 해' &lt;/i&gt;라는 코멘트를 남긴 사람도 있었다. 당시에는 해당 글을 보고 회의감이 심하게 왔었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;물론 이런 회의감을 지우고 다시 동기부여가 되게끔 도와주는 다른 훌륭한 개발자분들이 있기에 지금도 꾸준히 유지보수를 할 수 있다고 생각한다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;1K Stars가 넘으면 후계자(successor)를 정해 달라는 배너가 노출된다.&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1K가 넘어가는 순간, 레포 상단에 후계자를 지정하라는 배너가 노출되었다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2023-10-19 오후 2.17.51.png&quot; data-origin-width=&quot;2562&quot; data-origin-height=&quot;1162&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bIb6dq/btsz6dSu9Mq/UAqr2THLYa0unmvmSIDywK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bIb6dq/btsz6dSu9Mq/UAqr2THLYa0unmvmSIDywK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bIb6dq/btsz6dSu9Mq/UAqr2THLYa0unmvmSIDywK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbIb6dq%2Fbtsz6dSu9Mq%2FUAqr2THLYa0unmvmSIDywK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2562&quot; height=&quot;1162&quot; data-filename=&quot;스크린샷 2023-10-19 오후 2.17.51.png&quot; data-origin-width=&quot;2562&quot; data-origin-height=&quot;1162&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;농담이 아니라 정말로 내가 사망하면 사망 진단서(...)등의 서류를 보내 해당 레포를 이어받을 수 있는 후계자를 지정하라는 내용이었다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2023-10-19 오후 2.18.45.png&quot; data-origin-width=&quot;1916&quot; data-origin-height=&quot;288&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cP8y1b/btsAa9tZI5P/8fVsNJigeXelGWd2apQKqk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cP8y1b/btsAa9tZI5P/8fVsNJigeXelGWd2apQKqk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cP8y1b/btsAa9tZI5P/8fVsNJigeXelGWd2apQKqk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcP8y1b%2FbtsAa9tZI5P%2F8fVsNJigeXelGWd2apQKqk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1916&quot; height=&quot;288&quot; data-filename=&quot;스크린샷 2023-10-19 오후 2.18.45.png&quot; data-origin-width=&quot;1916&quot; data-origin-height=&quot;288&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://en.wikipedia.org/wiki/Bus_factor&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;버스팩터&lt;/a&gt; 관리를 해야 할 정도의 소스라고 github 측에 인정받은 것 같아 재미있었다. :)&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;재밌는 연락들이 종종 온다.&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1년 반 정도 오픈소스를 운영하다 보니, 재미있는 연락들이 종종 오곤 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;외국인에게 한글로 감사인사를 받아보기도 하고&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;edited_스크린샷 2023-03-29 오후 10.27.08.png&quot; data-origin-width=&quot;1612&quot; data-origin-height=&quot;394&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/etqU5K/btsAaHkdLeE/klAEZrrjAlDVWXHxIbwaY0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/etqU5K/btsAaHkdLeE/klAEZrrjAlDVWXHxIbwaY0/img.png&quot; data-alt=&quot;아주아주 작은 국위선양?&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/etqU5K/btsAaHkdLeE/klAEZrrjAlDVWXHxIbwaY0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FetqU5K%2FbtsAaHkdLeE%2FklAEZrrjAlDVWXHxIbwaY0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;860&quot; height=&quot;210&quot; data-filename=&quot;edited_스크린샷 2023-03-29 오후 10.27.08.png&quot; data-origin-width=&quot;1612&quot; data-origin-height=&quot;394&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;아주아주 작은 국위선양?&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;너 유지보수 안 하니? 하는 넛지 메일도 받아보고&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;edited_스크린샷 2023-11-11 오후 5.00.28.png&quot; data-origin-width=&quot;1074&quot; data-origin-height=&quot;648&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/2KpzU/btsAcQgFUvY/zBnzYbzQ1poNbqX1YYrgN0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/2KpzU/btsAcQgFUvY/zBnzYbzQ1poNbqX1YYrgN0/img.png&quot; data-alt=&quot;이후 기여를 엄청 해주셨다.&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/2KpzU/btsAcQgFUvY/zBnzYbzQ1poNbqX1YYrgN0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F2KpzU%2FbtsAcQgFUvY%2FzBnzYbzQ1poNbqX1YYrgN0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;860&quot; height=&quot;519&quot; data-filename=&quot;edited_스크린샷 2023-11-11 오후 5.00.28.png&quot; data-origin-width=&quot;1074&quot; data-origin-height=&quot;648&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;이후 기여를 엄청 해주셨다.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;순수한 감사의 메시지도 받아보고&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;edited_스크린샷 2023-11-11 오후 5.00.49.png&quot; data-origin-width=&quot;2574&quot; data-origin-height=&quot;468&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cHBDbZ/btsAboEJDAj/5HglGXlibmAHmIXk5rlSCk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cHBDbZ/btsAboEJDAj/5HglGXlibmAHmIXk5rlSCk/img.png&quot; data-alt=&quot;랜선 허그&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cHBDbZ/btsAboEJDAj/5HglGXlibmAHmIXk5rlSCk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcHBDbZ%2FbtsAboEJDAj%2F5HglGXlibmAHmIXk5rlSCk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;860&quot; height=&quot;156&quot; data-filename=&quot;edited_스크린샷 2023-11-11 오후 5.00.49.png&quot; data-origin-width=&quot;2574&quot; data-origin-height=&quot;468&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;랜선 허그&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;본인의 오픈소스를 소개해주는 메일도 받아보고&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;edited_스크린샷 2023-11-11 오후 5.02.00.png&quot; data-origin-width=&quot;1666&quot; data-origin-height=&quot;458&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/btAEen/btsz8qKSeDl/PESSMjGyHiI5jStCKbnb6K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/btAEen/btsz8qKSeDl/PESSMjGyHiI5jStCKbnb6K/img.png&quot; data-alt=&quot;크롬 익스텐션계의 Next.js를 꿈꾸시는 분&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/btAEen/btsz8qKSeDl/PESSMjGyHiI5jStCKbnb6K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbtAEen%2Fbtsz8qKSeDl%2FPESSMjGyHiI5jStCKbnb6K%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;860&quot; height=&quot;236&quot; data-filename=&quot;edited_스크린샷 2023-11-11 오후 5.02.00.png&quot; data-origin-width=&quot;1666&quot; data-origin-height=&quot;458&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;크롬 익스텐션계의 Next.js를 꿈꾸시는 분&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일을 같이 하자는 메일도 받고&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;edited_스크린샷 2023-11-11 오후 5.02.33.png&quot; data-origin-width=&quot;2560&quot; data-origin-height=&quot;662&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/uedzL/btsz92PHPbv/BEuJOmzPKFuRE8UUfOnAKK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/uedzL/btsz92PHPbv/BEuJOmzPKFuRE8UUfOnAKK/img.png&quot; data-alt=&quot;너 나랑 일 하나 같이하자&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/uedzL/btsz92PHPbv/BEuJOmzPKFuRE8UUfOnAKK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FuedzL%2Fbtsz92PHPbv%2FBEuJOmzPKFuRE8UUfOnAKK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2560&quot; height=&quot;662&quot; data-filename=&quot;edited_스크린샷 2023-11-11 오후 5.02.33.png&quot; data-origin-width=&quot;2560&quot; data-origin-height=&quot;662&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;너 나랑 일 하나 같이하자&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다 기록하진 못했지만, 이런 재미있는 메일들이 종종 오곤 한다. 기재하지 않은 메일 중 하나는 실제 오프라인 모임으로도 인연이 이어진 계기가 되기도 했다. 무엇보다도 기쁘고 보람이 느껴지는 반응은 &lt;i&gt;'덕분에 시간을 아꼈다'&lt;/i&gt;는 내용인데, 내가 쏟은 시간과 노력이 그 이상 누군가의 시간과 노력을 아끼는 계기가 되었다는 건 참 짜릿한 일인것 같다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;지난 1년 반 동안, 참 특별하고 기억에 남는 경험을 했던 것 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;내가 블로그를 쓰기 시작한 계기는 누군가가 적은 문장을 보았기 때문이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&quot;한 달에 블로그 글 하나씩 꾸준히 작성하면, 들인 시간과 노력의 제곱 이상으로 &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;나중에&lt;span&gt; 크게 보상받는 순간이 온다.&quot;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;이 말을 차용해서 나는 이렇게 말해보려고 한다.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;&quot;오픈소스에 꾸준히 기여하면, 들인 시간과 노력의 제곱 이상으로  특별한 경험과 보상을 얻을 수 있다.&quot;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;오픈소스 활동은 개발자 커뮤니티에 보은(?) 하며 동시에 유대감과 보람까지 느낄 수 있는 특별한 활동이라고 생각한다. &lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;이번 주말에 당장 작은 기여라도 시도해보는게 어떨까? 여러분들에게도 적극적으로 권해드리며 이만 글을 마친다.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;</description>
      <category>취미로 하는 개발</category>
      <category>오픈소스</category>
      <author>눅눅한 피부</author>
      <guid isPermaLink="true">https://nookpi.tistory.com/188</guid>
      <comments>https://nookpi.tistory.com/188#entry188comment</comments>
      <pubDate>Fri, 10 Nov 2023 07:47:37 +0900</pubDate>
    </item>
    <item>
      <title>근황</title>
      <link>https://nookpi.tistory.com/187</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;0. 직장 동료들과 함께 주말에(!) 영종도에 유명한 카페에 가고 을왕리 해수욕장에도 갔다. 주말에 직장 동료들과 사적인 시간을 보내는게 처음이라서 어색했으나 굉장히 즐거운 시간을 보냈다. 술 한 잔 마시지 않고 11시간을 넘게 같이 있었다... 수다만 엄청 떨었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. Astro Page Transition을 써보려고 블로그를 새로 만들었다. 각잡고 글 올리는 용도의 블로그로 사용할까 생각중... 아마 티스토리는 편하게(?) 무게감 없이 이것저것 적는 용도로 사용할 것 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;figure id=&quot;og_1696603721268&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;Home&quot; data-og-description=&quot;Jonghak Seo's blog&quot; data-og-host=&quot;jonghakseo.github.io&quot; data-og-source-url=&quot;https://jonghakseo.github.io/&quot; data-og-url=&quot;https://jonghakseo.github.io/&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/XvEFS/hyT53vb9SB/lKVd8xfFFAetL2Mv0uqt41/img.jpg?width=1600&amp;amp;height=630&amp;amp;face=0_0_1600_630,https://scrap.kakaocdn.net/dn/bXyECQ/hyT9AkANZg/P4m42VkHSGgmwYSJDXlKTk/img.jpg?width=1600&amp;amp;height=630&amp;amp;face=0_0_1600_630&quot;&gt;&lt;a href=&quot;https://jonghakseo.github.io/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://jonghakseo.github.io/&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/XvEFS/hyT53vb9SB/lKVd8xfFFAetL2Mv0uqt41/img.jpg?width=1600&amp;amp;height=630&amp;amp;face=0_0_1600_630,https://scrap.kakaocdn.net/dn/bXyECQ/hyT9AkANZg/P4m42VkHSGgmwYSJDXlKTk/img.jpg?width=1600&amp;amp;height=630&amp;amp;face=0_0_1600_630');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;Home&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Jonghak Seo's blog&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;jonghakseo.github.io&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. 프론트엔드 다이빙 클럽에 다녀왔다. 내가 방문한 회차에는 프론트엔드 테스트 관련한 짧은 세션을 2개 들었는데, 새삼 다들 비슷한 고민들을 하고 있다는 사실을 알게 된 것 같다. 개발자간의 교류? 커뮤니케이션 활동은 처음이었는데 나쁘지 않았던 것 같다. 평소 토스에 궁금한 점 두 세 가지를 물어볼 기회가 있어서 재미있었다. (yarn berry 의존성 소스코드 확인할때 불편하진 않은지, 인터널 프로덕트의 인터페이스 정의는 어떤 절차로 하고 있는지 등등)&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2023-10-07 오전 12.07.56.png&quot; data-origin-width=&quot;1500&quot; data-origin-height=&quot;830&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/oUsFU/btsxielWdyu/dsQdkNi65IngihskYUlgk0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/oUsFU/btsxielWdyu/dsQdkNi65IngihskYUlgk0/img.png&quot; data-alt=&quot;뱃지랑 스티커랑 콜드브루를 줬당&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/oUsFU/btsxielWdyu/dsQdkNi65IngihskYUlgk0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FoUsFU%2FbtsxielWdyu%2FdsQdkNi65IngihskYUlgk0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;500&quot; height=&quot;277&quot; data-filename=&quot;스크린샷 2023-10-07 오전 12.07.56.png&quot; data-origin-width=&quot;1500&quot; data-origin-height=&quot;830&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;뱃지랑 스티커랑 콜드브루를 줬당&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;figure id=&quot;og_1696603662325&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;놀러오세요! 프론트엔드 다이빙 클럽&quot; data-og-description=&quot;프론트엔드에 관한 깊은 이야기를 나눌 수 있는 오프라인 커뮤니티, 프론트엔드 다이빙 클럽을 소개합니다.&quot; data-og-host=&quot;toss.tech&quot; data-og-source-url=&quot;https://toss.tech/article/frontend-diving-club&quot; data-og-url=&quot;https://toss.tech/article/frontend-diving-club&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/sZbh8/hyT51qCvog/pfafKRgkupvvOOoaI4ryR1/img.jpg?width=1500&amp;amp;height=1060&amp;amp;face=0_0_1500_1060,https://scrap.kakaocdn.net/dn/kWJf9/hyT9yAknU3/ztJkI3WfRdZNbe1TGmfswK/img.jpg?width=1500&amp;amp;height=1060&amp;amp;face=0_0_1500_1060,https://scrap.kakaocdn.net/dn/bqUQ2z/hyT9Egd8kv/JNYyt3xKNJubTpRDkMrKhK/img.jpg?width=4433&amp;amp;height=4433&amp;amp;face=312_234_527_468&quot;&gt;&lt;a href=&quot;https://toss.tech/article/frontend-diving-club&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://toss.tech/article/frontend-diving-club&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/sZbh8/hyT51qCvog/pfafKRgkupvvOOoaI4ryR1/img.jpg?width=1500&amp;amp;height=1060&amp;amp;face=0_0_1500_1060,https://scrap.kakaocdn.net/dn/kWJf9/hyT9yAknU3/ztJkI3WfRdZNbe1TGmfswK/img.jpg?width=1500&amp;amp;height=1060&amp;amp;face=0_0_1500_1060,https://scrap.kakaocdn.net/dn/bqUQ2z/hyT9Egd8kv/JNYyt3xKNJubTpRDkMrKhK/img.jpg?width=4433&amp;amp;height=4433&amp;amp;face=312_234_527_468');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;놀러오세요! 프론트엔드 다이빙 클럽&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;프론트엔드에 관한 깊은 이야기를 나눌 수 있는 오프라인 커뮤니티, 프론트엔드 다이빙 클럽을 소개합니다.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;toss.tech&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. 추석 연휴를 맞아 싱가폴에 방문했다. 참 더운 나라다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;4. 내가 멘탈이 참 약하다는걸 느낀다. 기분 상한게 바로바로 얼굴에 드러나고(정작 본인은 티가 안 난다고 생각하는게 코메디) 별거 아닌 말이나 행동에도 속쓰려한다. 관리하는 오픈소스 레포에 시간을 충분히 쏟지 못해서 시간이 없어 신경쓰지 못해 미안하다고 코멘트를 달았는데 답변으로 '나도 시간이 없어' 라는 식의 댓글이 달려서 현타가 찐하게 왔다...ㅎㅎ&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;5. 직장 동료와 kitchen table(가제)이라는 오픈소스(로 발전시키려는) 프로덕트를 만들어보고 있는데 재미있다. 해보고 싶었던 프로덕트라서 동기부여가 잘 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;6. 의뢰(?)가 들어와서 unified.js 생태계에서 retext를 보고 있는데 자연어 Node 파싱을 통해 뭔가 하는게 되게 노가다 같다는(?) 생각이 든다. 앵간한 요구조건은 정규식으로 처리하는게 더 유용해보이는...크흠&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;figure id=&quot;og_1696604705528&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;unified&quot; data-og-description=&quot;Content as structured data: unified compiles content and provides hundreds of packages to work with content&quot; data-og-host=&quot;unifiedjs.com&quot; data-og-source-url=&quot;https://unifiedjs.com/&quot; data-og-url=&quot;https://unifiedjs.com/&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/1C1TV/hyT5Vqo48n/esi7UmzR8f5DG7Jl9Onoh0/img.png?width=1200&amp;amp;height=690&amp;amp;face=0_0_1200_690,https://scrap.kakaocdn.net/dn/idnOZ/hyT9HRyOSr/J4AOHbFNhDJF970UlmuHoK/img.png?width=1200&amp;amp;height=690&amp;amp;face=0_0_1200_690,https://scrap.kakaocdn.net/dn/cabIZq/hyT9NjWLTA/IXi4CawJj4O5lWi5KQBox1/img.png?width=2784&amp;amp;height=1824&amp;amp;face=0_0_2784_1824&quot;&gt;&lt;a href=&quot;https://unifiedjs.com/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://unifiedjs.com/&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/1C1TV/hyT5Vqo48n/esi7UmzR8f5DG7Jl9Onoh0/img.png?width=1200&amp;amp;height=690&amp;amp;face=0_0_1200_690,https://scrap.kakaocdn.net/dn/idnOZ/hyT9HRyOSr/J4AOHbFNhDJF970UlmuHoK/img.png?width=1200&amp;amp;height=690&amp;amp;face=0_0_1200_690,https://scrap.kakaocdn.net/dn/cabIZq/hyT9NjWLTA/IXi4CawJj4O5lWi5KQBox1/img.png?width=2784&amp;amp;height=1824&amp;amp;face=0_0_2784_1824');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;unified&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Content as structured data: unified compiles content and provides hundreds of packages to work with content&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;unifiedjs.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;7. 여전히 불확실한게 많고 두려운게 참 많다. 그래서 고민도 많고... 내 욕심에 비해 능력이 따라가지 못한다고 느낀다. 나한테도 열심히 산다고 말해주는 사람들이 있지만 나는 늘 내가 해내는 것들이 불만족스러운 것 같다. 세상엔 왜 이렇게 잘해야 하는 일이 많을까?&lt;/p&gt;</description>
      <category>잡담</category>
      <author>눅눅한 피부</author>
      <guid isPermaLink="true">https://nookpi.tistory.com/187</guid>
      <comments>https://nookpi.tistory.com/187#entry187comment</comments>
      <pubDate>Sat, 7 Oct 2023 00:04:18 +0900</pubDate>
    </item>
    <item>
      <title>AST (Abstract Syntax Tree)</title>
      <link>https://nookpi.tistory.com/186</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;2023.09.07 개발팀 워크샵 세션&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;iframe src=&quot;https://www.icloud.com/keynote/002j2109AE3rLQCIqhSr_oVPQ?embed=true&quot; width=&quot;640&quot; height=&quot;500&quot; frameborder=&quot;0&quot; allowfullscreen=&quot;1&quot;&gt;&lt;/iframe&gt;&lt;/p&gt;</description>
      <category>발표자료</category>
      <author>눅눅한 피부</author>
      <guid isPermaLink="true">https://nookpi.tistory.com/186</guid>
      <comments>https://nookpi.tistory.com/186#entry186comment</comments>
      <pubDate>Sat, 16 Sep 2023 17:48:20 +0900</pubDate>
    </item>
    <item>
      <title>메타프로그래밍 (MetaProgramming)</title>
      <link>https://nookpi.tistory.com/185</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://jonghakseo.github.io/posts/metaprogramming/&quot;&gt;https://jonghakseo.github.io/posts/metaprogramming/&lt;/a&gt;&amp;nbsp;&lt;/p&gt;
&lt;figure id=&quot;og_1694785179821&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;Metaprogramming&quot; data-og-description=&quot;This post is about metaprogramming. What is meta programming?&quot; data-og-host=&quot;jonghakseo.github.io&quot; data-og-source-url=&quot;https://jonghakseo.github.io/posts/metaprogramming/&quot; data-og-url=&quot;https://jonghakseo.github.io/posts/metaprogramming/&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/beydGb/hyTV3nUGYR/bqiih6QlpOv61JkJp6oGak/img.png?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630,https://scrap.kakaocdn.net/dn/3zBJ7/hyTV0raDvd/7yt1x5X5aUGBZoMNQr9Qtk/img.png?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630&quot;&gt;&lt;a href=&quot;https://jonghakseo.github.io/posts/metaprogramming/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://jonghakseo.github.io/posts/metaprogramming/&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/beydGb/hyTV3nUGYR/bqiih6QlpOv61JkJp6oGak/img.png?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630,https://scrap.kakaocdn.net/dn/3zBJ7/hyTV0raDvd/7yt1x5X5aUGBZoMNQr9Qtk/img.png?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;Metaprogramming&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;This post is about metaprogramming. What is meta programming?&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;jonghakseo.github.io&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>공부내용 공유하기</category>
      <category>JavaScript</category>
      <category>meta programming</category>
      <category>Proxy</category>
      <category>reflect</category>
      <author>눅눅한 피부</author>
      <guid isPermaLink="true">https://nookpi.tistory.com/185</guid>
      <comments>https://nookpi.tistory.com/185#entry185comment</comments>
      <pubDate>Fri, 15 Sep 2023 22:40:30 +0900</pubDate>
    </item>
    <item>
      <title>동기부여</title>
      <link>https://nookpi.tistory.com/184</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;돈을 많이 벌게 되면 삶의 질과 미래를 계획할 수 있는 범위가 늘어난다. 또 한편으로는 사회 구성원으로서 나의 기여를 인정받는 기분이 들어서 기쁘다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 나는 돈보다 강력한 동기부여가 있다고 믿는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따뜻한 격려의 말 한마디&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;동료로부터 인정받는다는 기분&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;내 공헌이 누군가에게 도움이 되었다는 것을 내가 인지했을 때&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 모든 순간이 돈으로는 얻을 수 없는 큰 동기부여가 되는 것 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2023-08-12 오후 7.02.16.png&quot; data-origin-width=&quot;1218&quot; data-origin-height=&quot;416&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/3M2WY/btsq0xLSoca/DuFlSd2wmjf7K4XLm9ylO1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/3M2WY/btsq0xLSoca/DuFlSd2wmjf7K4XLm9ylO1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/3M2WY/btsq0xLSoca/DuFlSd2wmjf7K4XLm9ylO1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F3M2WY%2Fbtsq0xLSoca%2FDuFlSd2wmjf7K4XLm9ylO1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;600&quot; height=&quot;205&quot; data-filename=&quot;스크린샷 2023-08-12 오후 7.02.16.png&quot; data-origin-width=&quot;1218&quot; data-origin-height=&quot;416&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;시간을 들여 격려의 말을 해준 지구 반대편의 한 개발자에게 나도 허그를 보내며...&lt;/p&gt;</description>
      <category>잡담</category>
      <author>눅눅한 피부</author>
      <guid isPermaLink="true">https://nookpi.tistory.com/184</guid>
      <comments>https://nookpi.tistory.com/184#entry184comment</comments>
      <pubDate>Sun, 13 Aug 2023 23:30:05 +0900</pubDate>
    </item>
    <item>
      <title>Proxy로 MockData 관리하기</title>
      <link>https://nookpi.tistory.com/183</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;muhammad-zaqy-al-fattah-Lexcm-6FHRU-unsplash.jpg&quot; data-origin-width=&quot;640&quot; data-origin-height=&quot;427&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bJSBby/btsqZyEz251/kDBmarwldaEAk1VAQkT8C0/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bJSBby/btsqZyEz251/kDBmarwldaEAk1VAQkT8C0/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bJSBby/btsqZyEz251/kDBmarwldaEAk1VAQkT8C0/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbJSBby%2FbtsqZyEz251%2FkDBmarwldaEAk1VAQkT8C0%2Fimg.jpg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;400&quot; height=&quot;267&quot; data-filename=&quot;muhammad-zaqy-al-fattah-Lexcm-6FHRU-unsplash.jpg&quot; data-origin-width=&quot;640&quot; data-origin-height=&quot;427&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;회사의 스토리북 유지보수 업무를 하면서 Footer와 같은 공통 컴포넌트를 스토리북에 넣어줘야 하는 일이 있었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;큰 문제없이 작업을 완료하고 원격 저장소에 push를 하니, 로컬에서 빌드되던 스토리북의 배포가 실패하고 있었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;원인을 살펴보니, Footer 내부에 graphql codgen으로 생성된 typescript 파일에 대한 의존성이 있어서 codegen을 하지 않는 배포환경에서의 스토리북 빌드가 실패하던 것이었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정적인 데이터를 위주로 회사의 기본적인 정보를 보여주는 Footer였기 때문에 어째서 서버 타입의 의존성이 있는지 이해하지 못했으나, 자세히 살펴보니 서버 데이터를 기반으로 codgen을 해서 사용하는 일부 enum 값에 대해서 상수처럼 사용하고 있었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어 대표적으로는 다국어 서비스를 하는 우리 회사에서 지원하는 언어와 화폐에 대한 값들이 그에 해당한다. 사실상 상수로 사용되지만 데이터의 출처가 graphql enum인 경우이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1691909389237&quot; class=&quot;clean&quot; style=&quot;background-color: #f8f8f8; color: #383a42; text-align: start;&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import { CurrencyType, LanguageType } from 'graphql/types';&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1691909483473&quot; class=&quot;crystal&quot; style=&quot;background-color: #f8f8f8; color: #383a42; text-align: start;&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;export enum LanguageType {
  Chinese = 'CHINESE',
  English = 'ENGLISH',
  Hongkong = 'HONGKONG',
  Japanese = 'JAPANESE',
  Korean = 'KOREAN',
  Taiwan = 'TAIWAN',
  Thai = 'THAI',
  Vietnamese = 'VIETNAMESE'
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이러한 상수 enum에 대해 의존성을 가지고 있는 코드들이 Footer 내부에도 존재했고, 이 때문에 빌드가 실패했던 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;배포 환경에서 스토리북 빌드 전에 codegen을 하는 방법도 있었지만, 디자인 시스템을 보여주려는 원래 목적과 다르다고 생각했고, 이러한 상수 값들이 아닌 서버 의존성이 존재한다면 해당 컴포넌트를 스토리북에 등록할 상황 자체가 생기지 않는 게 맞다고 판단했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;결론적으로 몇 가지 대표적으로 사용되는 상수들에 대해서만 모킹을 하기로 결정했고, 모킹 자체는 스토리북 웹팩 설정에서 간단하게 해줄 수 있었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1691909765160&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import type { StorybookConfig } from '@storybook/core-webpack';

const config: StorybookConfig = {
  // ...
  webpackFinal: (config) =&amp;gt; {
    if (config.resolve?.alias) {
      config.resolve.alias['graphql/types'] = require.resolve('./__mocks__/graphqlTypes.ts');
    }
    return config;
  },
};

export default config;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;모킹 파일은 대략 아래와 같이 모킹이 필요한 대표적인 enum 값들만 기재해두었다.&lt;/p&gt;
&lt;pre id=&quot;code_1691909821551&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// .storybook/__mocks__/graphqlTypes.ts
enum LanguageType {
  Chinese = 'CHINESE',
  English = 'ENGLISH',
  Hongkong = 'HONGKONG',
  Japanese = 'JAPANESE',
  Korean = 'KOREAN',
  Taiwan = 'TAIWAN',
  Thai = 'THAI',
  Vietnamese = 'VIETNAMESE',
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;LanguageType enum을 export 하는 순간 프로젝트의 모든 경로에서 모킹 파일에 있는 LanguageType을 참조할 수 있기 때문에 한 객체에 모아서 export를 해준다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1693130406688&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;module.exports = {
  LanguageType,
  CurrencyType,
};&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;javascript에서는 export 예약어를 사용할 경우 모듈을 내보낼 범위에 대해서 명시적으로 나타낼 수 없기 때문에 아래와 같은 문제가 흔하게 발생한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2023-08-13 오후 4.00.12.png&quot; data-origin-width=&quot;1430&quot; data-origin-height=&quot;198&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/baEKOm/btsq0Dk4eGr/JW4k5cp7mWQELigyiO6vw0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/baEKOm/btsq0Dk4eGr/JW4k5cp7mWQELigyiO6vw0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/baEKOm/btsq0Dk4eGr/JW4k5cp7mWQELigyiO6vw0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbaEKOm%2Fbtsq0Dk4eGr%2FJW4k5cp7mWQELigyiO6vw0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;600&quot; height=&quot;83&quot; data-filename=&quot;스크린샷 2023-08-13 오후 4.00.12.png&quot; data-origin-width=&quot;1430&quot; data-origin-height=&quot;198&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;모킹 객체를 만들어서 모킹을 했지만, 직접 모킹하지 않은 프로퍼티 접근을 감지하고 에러 메시지를 출력해주기 위해 Proxy를 사용하기로 결정했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;방법은 다음과 같다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;proxy 객체를 만들어서&amp;nbsp;모킹하지 않은(하지 않을) prop 접근을 감지해서 에러를 발생시킨다.&lt;/li&gt;
&lt;li&gt;proxy 객체를 module.exports로 내보낸다.&lt;/li&gt;
&lt;/ol&gt;
&lt;pre id=&quot;code_1691910659374&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;const mockTypes = {
  LanguageType,
  CurrencyType,
  CountryType,
  CountryCallingCodeType,
};

const mockingProxy = new Proxy(mockTypes, {
  get: (target, prop) =&amp;gt; {
    if (prop === '__esModule') {
      return;
    }
    if (Reflect.has(mockTypes, prop)) {
      return Reflect.get(mockTypes, prop);
    }
    throw Error(
      `모킹되지 않은 enum값이 사용되었습니다. 서버 의존성이 있는 컴포넌트를 스토리북에서 사용하는걸 자제해주세요. [${prop.toString()}]`,
    );
  },
});

module.exports = mockingProxy;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;천천히 살펴보자. 먼저 mockTypes 객체의 프로퍼티에 접근하는 요소들을 다루기 위해 get Handler를 커스터미아징 했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;'__esMoudle' 프로퍼티는 bebel의 트랜스파일 과정에서 해당 파일의 모듈 시스템에 따라 선택적으로 추가되는 값이다. 번들링 과정에서 babel이 ESM과 CommonJS 모듈 모두 동일한 인터페이스로 사용할 수 있게 하기 위해 ESM 기반의 모듈에 '__esModule'이라는 프로퍼티를 true로 할당하여 일종의 flag로 사용한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://babeljs.io/repl#?browsers=defaults%2C%20not%20ie%2011%2C%20not%20ie_mob%2011&amp;amp;build=&amp;amp;builtIns=false&amp;amp;corejs=3.21&amp;amp;spec=false&amp;amp;loose=false&amp;amp;code_lz=KYDwDg9gTgLgBAMwK4DsDGMCWEVwBbAA2hEA6tIQCYAUAlHAN4BQTccaOAzvAO4WVwAvHABEfKFRGs4UYDCRRcAAwASREgEI4AEgbiqAXw1KA3EwNMgA&amp;amp;debug=false&amp;amp;forceAllTransforms=false&amp;amp;modules=false&amp;amp;shippedProposals=false&amp;amp;circleciRepo=&amp;amp;evaluate=false&amp;amp;fileSize=false&amp;amp;timeTravel=false&amp;amp;sourceType=module&amp;amp;lineWrap=true&amp;amp;presets=env&amp;amp;prettier=false&amp;amp;targets=&amp;amp;version=7.22.10&amp;amp;externalPlugins=%40babel%2Fplugin-transform-modules-commonjs%407.21.2&amp;amp;assumptions=%7B%7D&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;babel에서 확인&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2023-08-13 오후 4.22.52.png&quot; data-origin-width=&quot;1536&quot; data-origin-height=&quot;450&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/zOgsD/btsq2imU9ON/MVLa5TYOEtSsAecJrlnxbk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/zOgsD/btsq2imU9ON/MVLa5TYOEtSsAecJrlnxbk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/zOgsD/btsq2imU9ON/MVLa5TYOEtSsAecJrlnxbk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FzOgsD%2Fbtsq2imU9ON%2FMVLa5TYOEtSsAecJrlnxbk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1536&quot; height=&quot;450&quot; data-filename=&quot;스크린샷 2023-08-13 오후 4.22.52.png&quot; data-origin-width=&quot;1536&quot; data-origin-height=&quot;450&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;'__esModule'은 다음과 같이 모듈을 불러오는 과정에서 같은 식별 함수를 통해 동일한 인터페이스로 처리될 수 있게 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1691911148938&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;function esModuleInterop(module) {
  return module.__esModule ? module : {default: module};
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;트랜스 파일 과정에서의 CommonJS, ESM 처리는 아래 글에서 자세히 볼 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://ui.toast.com/weekly-pick/ko_20190418#%EA%B7%BC%EB%8D%B0-webpack%EC%9D%80-%EC%99%9C-%EC%9D%B4%EB%A0%87%EA%B2%8C-%EC%B2%98%EB%A6%AC%ED%95%98%EA%B3%A0-%EC%9E%88%EC%9D%84%EA%B9%8C-featbabel%EC%9D%98-%EC%98%81%ED%96%A5%EB%A0%A5&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://ui.toast.com/weekly-pick/ko_20190418&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 babel을 통해 트랜스파일링 된 코드에서 '__esModule' 프로퍼티에 대한 접근은 필연적으로 발생할 수밖에 없으니 해당 프로퍼티의 접근에 대해서는 에러를 던지지 않도록 처리를 해 둔다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;최종적으로 이러한 처리를 통해 만들어진 Proxy 객체를 CommonJS 방식으로 내보내고 webpack 설정에서 require를 통해 가져오면 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;참고&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;figure id=&quot;og_1691911802016&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;Proxy - JavaScript | MDN&quot; data-og-description=&quot;The Proxy object enables you to create a proxy for another object, which can intercept and redefine fundamental operations for that object.&quot; data-og-host=&quot;developer.mozilla.org&quot; data-og-source-url=&quot;https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy&quot; data-og-url=&quot;https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/ds4F4W/hyTCILdted/NpGK8X8WQ7YsQYKSYlsSP0/img.png?width=1920&amp;amp;height=1080&amp;amp;face=0_0_1920_1080&quot;&gt;&lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/ds4F4W/hyTCILdted/NpGK8X8WQ7YsQYKSYlsSP0/img.png?width=1920&amp;amp;height=1080&amp;amp;face=0_0_1920_1080');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;Proxy - JavaScript | MDN&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;The Proxy object enables you to create a proxy for another object, which can intercept and redefine fundamental operations for that object.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;developer.mozilla.org&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;figure id=&quot;og_1691913131303&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;You don't know JS module&quot; data-og-description=&quot;자바스크립트 개발을 하다 보면 다양한 모듈 정의 방법을 마주치게 된다. ES 모듈, CommonJS 모듈, AMD 모듈 등 자바스크립트에는 다양한 모듈 시스템이 공존하는데, 각 모듈은 모두 다른 방식으로 &quot; data-og-host=&quot;ui.toast.com&quot; data-og-source-url=&quot;https://ui.toast.com/weekly-pick/ko_20190418#%EA%B7%BC%EB%8D%B0-webpack%EC%9D%80-%EC%99%9C-%EC%9D%B4%EB%A0%87%EA%B2%8C-%EC%B2%98%EB%A6%AC%ED%95%98%EA%B3%A0-%EC%9E%88%EC%9D%84%EA%B9%8C-featbabel%EC%9D%98-%EC%98%81%ED%96%A5%EB%A0%A5&quot; data-og-url=&quot;https://ui.toast.com/weekly-pick/ko_20190418/undefined/weekly-pick/ko_20190418&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/bymU3w/hyTCGs6uwM/G9WpKlbkt2wsqrDTVsXzf0/img.png?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630&quot;&gt;&lt;a href=&quot;https://ui.toast.com/weekly-pick/ko_20190418#%EA%B7%BC%EB%8D%B0-webpack%EC%9D%80-%EC%99%9C-%EC%9D%B4%EB%A0%87%EA%B2%8C-%EC%B2%98%EB%A6%AC%ED%95%98%EA%B3%A0-%EC%9E%88%EC%9D%84%EA%B9%8C-featbabel%EC%9D%98-%EC%98%81%ED%96%A5%EB%A0%A5&quot; data-source-url=&quot;https://ui.toast.com/weekly-pick/ko_20190418#%EA%B7%BC%EB%8D%B0-webpack%EC%9D%80-%EC%99%9C-%EC%9D%B4%EB%A0%87%EA%B2%8C-%EC%B2%98%EB%A6%AC%ED%95%98%EA%B3%A0-%EC%9E%88%EC%9D%84%EA%B9%8C-featbabel%EC%9D%98-%EC%98%81%ED%96%A5%EB%A0%A5&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/bymU3w/hyTCGs6uwM/G9WpKlbkt2wsqrDTVsXzf0/img.png?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;You don't know JS module&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;자바스크립트 개발을 하다 보면 다양한 모듈 정의 방법을 마주치게 된다. ES 모듈, CommonJS 모듈, AMD 모듈 등 자바스크립트에는 다양한 모듈 시스템이 공존하는데, 각 모듈은 모두 다른 방식으로&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;ui.toast.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;</description>
      <category>공부내용 공유하기</category>
      <category>Proxy</category>
      <author>눅눅한 피부</author>
      <guid isPermaLink="true">https://nookpi.tistory.com/183</guid>
      <comments>https://nookpi.tistory.com/183#entry183comment</comments>
      <pubDate>Sun, 13 Aug 2023 16:58:01 +0900</pubDate>
    </item>
    <item>
      <title>함께 자라기 - 컴퓨터로 대체되기 힘든 일</title>
      <link>https://nookpi.tistory.com/182</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;옥스퍼드 대학교에서 발표한 &amp;lt;고용의 미래&amp;gt; 논문을 참고하여, 직무 역량 DB에 속하는 702개 직종의 컴퓨터 대체 확률을 계산&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;컴퓨터화에&amp;nbsp;병목이&amp;nbsp;되는&amp;nbsp;주요&amp;nbsp;역량은&amp;nbsp;대표적으로&amp;nbsp;다음과&amp;nbsp;같음&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;1.&amp;nbsp;독창성:&amp;nbsp;주어진&amp;nbsp;주제나&amp;nbsp;상황에&amp;nbsp;대해&amp;nbsp;특이하거나&amp;nbsp;독창적인&amp;nbsp;생각을&amp;nbsp;해내기,&amp;nbsp;혹은&amp;nbsp;문제를&amp;nbsp;해결하는&amp;nbsp;창의적인&amp;nbsp;방법들을&amp;nbsp;만들어내기&lt;br /&gt;2.&amp;nbsp;사회적&amp;nbsp;민감성:&amp;nbsp;타인의&amp;nbsp;반응을&amp;nbsp;알아차리고&amp;nbsp;그&amp;nbsp;사람들이&amp;nbsp;왜&amp;nbsp;그렇게&amp;nbsp;반응하는지&amp;nbsp;이해하기&lt;br /&gt;3.&amp;nbsp;협상:&amp;nbsp;사람들을&amp;nbsp;화해(조율)시키고&amp;nbsp;서로&amp;nbsp;간의&amp;nbsp;차이를&amp;nbsp;조정하려고&amp;nbsp;노력하기&lt;br /&gt;4.&amp;nbsp;설득:&amp;nbsp;다른&amp;nbsp;사람들의&amp;nbsp;마음이나&amp;nbsp;행동을&amp;nbsp;바꾸게&amp;nbsp;설득하기&lt;br /&gt;5.&amp;nbsp;타인을&amp;nbsp;돕고&amp;nbsp;돌보기:&amp;nbsp;개인적&amp;nbsp;도움,&amp;nbsp;치료,&amp;nbsp;감정적&amp;nbsp;지지,&amp;nbsp;혹은&amp;nbsp;동료,&amp;nbsp;고객에게&amp;nbsp;기타&amp;nbsp;개인적&amp;nbsp;도움을&amp;nbsp;제공하기&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;논문에서는&amp;nbsp;프로그래머와&amp;nbsp;소프트웨어&amp;nbsp;개발자를&amp;nbsp;아래와&amp;nbsp;같이&amp;nbsp;정의&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;프로그래머&amp;nbsp;(스펙대로&amp;nbsp;코드를&amp;nbsp;만드는&amp;nbsp;사람)&lt;br /&gt;소프트웨어&amp;nbsp;개발자&amp;nbsp;(사용자의&amp;nbsp;요구사항을&amp;nbsp;분석하고&amp;nbsp;그에&amp;nbsp;대한&amp;nbsp;솔루션을&amp;nbsp;설계하는&amp;nbsp;것을&amp;nbsp;포함)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;컴퓨터&amp;nbsp;대체&amp;nbsp;가능&amp;nbsp;확률을&amp;nbsp;계산&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;소프트웨어&amp;nbsp;개발자:&amp;nbsp;0.042&amp;nbsp;(4.2%)&lt;br /&gt;컴퓨터&amp;nbsp;프로그래머:&amp;nbsp;0.48&amp;nbsp;(48%)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;컴퓨터 프로그래머는 다른 사람이 준 스펙대로 개발하는 것을 주 업무로 하며 그 과정에서 협상, 설득이 크게 필요하지 않음.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;반면, 소프트웨어 개발자는 소프트웨어로 뭘 만들지 고민하고 설계하는 과정이 포함되며, 그 과정에서 타인과 상호작용 하는 업무가 많음&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉&amp;nbsp;개발자에게는&amp;nbsp;더&amp;nbsp;높은&amp;nbsp;수준의&amp;nbsp;협상&amp;nbsp;능력이&amp;nbsp;필요하다는&amp;nbsp;뜻&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;내가&amp;nbsp;실제로&amp;nbsp;매일&amp;nbsp;하는&amp;nbsp;일이&amp;nbsp;어떤&amp;nbsp;성격인지&amp;nbsp;돌이켜&amp;nbsp;볼&amp;nbsp;필요가&amp;nbsp;있음&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;직책이&amp;nbsp;선임&amp;nbsp;개발자가&amp;nbsp;되었다고&amp;nbsp;해서&amp;nbsp;안심할&amp;nbsp;일이&amp;nbsp;아니라,&amp;nbsp;자신이&amp;nbsp;주로&amp;nbsp;하는&amp;nbsp;일이&amp;nbsp;남들이&amp;nbsp;시킨&amp;nbsp;대로&amp;nbsp;혼자&amp;nbsp;프로그램을&amp;nbsp;만드는&amp;nbsp;일인지,&amp;nbsp;창의적으로&amp;nbsp;의견을&amp;nbsp;나누며&amp;nbsp;다른&amp;nbsp;사람과&amp;nbsp;협력하는&amp;nbsp;일인지를&amp;nbsp;진지하게&amp;nbsp;생각해&amp;nbsp;볼&amp;nbsp;필요가&amp;nbsp;있음&lt;/p&gt;</description>
      <category>독후단상</category>
      <category>함께자라기</category>
      <author>눅눅한 피부</author>
      <guid isPermaLink="true">https://nookpi.tistory.com/182</guid>
      <comments>https://nookpi.tistory.com/182#entry182comment</comments>
      <pubDate>Tue, 1 Aug 2023 19:58:44 +0900</pubDate>
    </item>
    <item>
      <title>웹 성능 최적화 끄적끄적</title>
      <link>https://nookpi.tistory.com/181</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;브라우저에서의 렌더링 최적화&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;DOM 최적화&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;HTML 구문 오류 최소화 (CPU 리소스 절약)&lt;/li&gt;
&lt;li&gt;HTML 중첩 완화 (depth 깊을수록 layout 계산 리소스 증가)&lt;/li&gt;
&lt;li&gt;&amp;lt;script&amp;gt; 지연 로드 (async, defer)
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;GTM(Google Tag Manager) 스크립트 기본값은 async=true&lt;/li&gt;
&lt;li&gt;GTM 실행 시점을 defer로 미뤄 병목을 방지해야 할까?
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;트레이드오프 고려 필요&lt;/li&gt;
&lt;li&gt;로드 속도를 빠르게 할 수 있지만, 그 효과가 크지 않고 GTM 로드 시점이 늦춰지면서 조기 이탈한 사용자에 대한 정보 누락 가능&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://stackoverflow.com/questions/72531926/defer-attribute-for-the-google-tag-manager&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://stackoverflow.com/questions/72531926/defer-attribute-for-the-google-tag-manager&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1017&quot; data-origin-height=&quot;568&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b1tpqx/btspGLD8bsS/b9gZqUHk3KBE100koKWs0k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b1tpqx/btspGLD8bsS/b9gZqUHk3KBE100koKWs0k/img.png&quot; data-alt=&quot;async, defer&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b1tpqx/btspGLD8bsS/b9gZqUHk3KBE100koKWs0k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb1tpqx%2FbtspGLD8bsS%2Fb9gZqUHk3KBE100koKWs0k%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;500&quot; height=&quot;279&quot; data-origin-width=&quot;1017&quot; data-origin-height=&quot;568&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;async, defer&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;실무에 써먹을 만한 DOM 최적화&lt;/h3&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;windowing&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;라이브러리 사용, 혹은 직접 구현&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;content-visibildity css&lt;/h4&gt;
&lt;pre id=&quot;code_1690886262022&quot; class=&quot;css&quot; data-ke-language=&quot;css&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;.content-block {
  content-visibility: auto;
  contain-intrinsic-size: 1000px; /* Explained in the next section. */
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;display:none 대안으로 content-visibility: hidden 고려&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;Compare it to other common ways of hiding element's contents:&lt;br /&gt;&lt;br /&gt;display: none: hides the element and destroys its rendering state. This means unhiding the element is as expensive as rendering a new element with the same contents. visibility: hidden: hides the element and keeps its rendering state. This doesn't truly remove the element from the document, as it (and it's subtree) still takes up geometric space on the page and can still be clicked on. It also updates the rendering state any time it is needed even when hidden. &lt;br /&gt;&lt;br /&gt;content-visibility: hidden, on the other hand, hides the element while preserving its rendering state, so, if there are any changes that need to happen, they only happen when the element is shown again (i.e. the&amp;nbsp;content-visibility: hidden&amp;nbsp;property is removed).&lt;br /&gt;&lt;br /&gt;Some great use cases for content-visibility: hidden are when implementing advanced virtual scrollers, and measuring layout. They're also great for single-page applications (SPA's). Inactive app views can be left in the DOM with content-visibility: hidden applied to prevent their display but maintain their cached state. This makes the view quick to render when it becomes active again.&lt;/blockquote&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;In an experiment, Facebook engineers observed an up to 250ms improvement in navigation times when going back to previously cached views.&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;지원 브라우저&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2766&quot; data-origin-height=&quot;1122&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bAc1wl/btspH0ulqt9/kpRGsnffrUApphrYrLKpUk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bAc1wl/btspH0ulqt9/kpRGsnffrUApphrYrLKpUk/img.png&quot; data-alt=&quot;그렇게 많지는... 않다&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bAc1wl/btspH0ulqt9/kpRGsnffrUApphrYrLKpUk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbAc1wl%2FbtspH0ulqt9%2FkpRGsnffrUApphrYrLKpUk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;600&quot; height=&quot;243&quot; data-origin-width=&quot;2766&quot; data-origin-height=&quot;1122&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;그렇게 많지는... 않다&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;@supports selector로 fallback 처리 가능&lt;/p&gt;
&lt;pre id=&quot;code_1690886458402&quot; class=&quot;css&quot; data-ke-language=&quot;css&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;    @supports (content-visibility: hidden) {
      content-visibility: hidden;
    }
    @supports not (content-visibility: hidden) {
      display: none;
    }&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2023-08-01 오후 7.56.22.png&quot; data-origin-width=&quot;2758&quot; data-origin-height=&quot;1024&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bntHwF/btspPaiv82l/gpjeBR93TarkN52rKMGkD0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bntHwF/btspPaiv82l/gpjeBR93TarkN52rKMGkD0/img.png&quot; data-alt=&quot;IE 컷&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bntHwF/btspPaiv82l/gpjeBR93TarkN52rKMGkD0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbntHwF%2FbtspPaiv82l%2FgpjeBR93TarkN52rKMGkD0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;600&quot; height=&quot;223&quot; data-filename=&quot;스크린샷 2023-08-01 오후 7.56.22.png&quot; data-origin-width=&quot;2758&quot; data-origin-height=&quot;1024&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;IE 컷&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;참조&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://web.dev/content-visibility/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://web.dev/content-visibility/&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1690886531174&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;content-visibility: the new CSS property that boosts your rendering performance&quot; data-og-description=&quot;The CSS content-visibility property enables web content rendering performance benefits by skipping rendering of off-screen content. This article shows you how to leverage this new CSS property for faster initial load times, using the auto keyword. You will&quot; data-og-host=&quot;web.dev&quot; data-og-source-url=&quot;https://web.dev/content-visibility/&quot; data-og-url=&quot;https://web.dev/content-visibility/&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/bGx5ce/hyTvgIBkkW/KoPjzKDGHOxkmNyYgdyOT0/img.jpg?width=1200&amp;amp;height=360&amp;amp;face=0_0_1200_360,https://scrap.kakaocdn.net/dn/pA93s/hyTwcR7Wak/IDeHwlBenkXEBB92LnH9L1/img.jpg?width=1200&amp;amp;height=360&amp;amp;face=0_0_1200_360,https://scrap.kakaocdn.net/dn/eje7Oc/hyTwcYToeW/kR8nSE5dX5u8X3jtaHqkOk/img.jpg?width=3200&amp;amp;height=960&amp;amp;face=0_0_3200_960&quot;&gt;&lt;a href=&quot;https://web.dev/content-visibility/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://web.dev/content-visibility/&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/bGx5ce/hyTvgIBkkW/KoPjzKDGHOxkmNyYgdyOT0/img.jpg?width=1200&amp;amp;height=360&amp;amp;face=0_0_1200_360,https://scrap.kakaocdn.net/dn/pA93s/hyTwcR7Wak/IDeHwlBenkXEBB92LnH9L1/img.jpg?width=1200&amp;amp;height=360&amp;amp;face=0_0_1200_360,https://scrap.kakaocdn.net/dn/eje7Oc/hyTwcYToeW/kR8nSE5dX5u8X3jtaHqkOk/img.jpg?width=3200&amp;amp;height=960&amp;amp;face=0_0_3200_960');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;content-visibility: the new CSS property that boosts your rendering performance&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;The CSS content-visibility property enables web content rendering performance benefits by skipping rendering of off-screen content. This article shows you how to leverage this new CSS property for faster initial load times, using the auto keyword. You will&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;web.dev&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/API/CSS/supports_static&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://developer.mozilla.org/en-US/docs/Web/API/CSS/supports_static&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1690886551742&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;CSS: supports() static method - Web APIs | MDN&quot; data-og-description=&quot;The CSS.supports() static method returns a boolean value indicating if the browser supports a given CSS feature, or not.&quot; data-og-host=&quot;developer.mozilla.org&quot; data-og-source-url=&quot;https://developer.mozilla.org/en-US/docs/Web/API/CSS/supports_static&quot; data-og-url=&quot;https://developer.mozilla.org/en-US/docs/Web/API/CSS/supports_static&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/j4hvK/hyTwrPh2Qg/rFAfUbEE5xkw0NtWCDkRmK/img.png?width=1920&amp;amp;height=1080&amp;amp;face=0_0_1920_1080&quot;&gt;&lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/API/CSS/supports_static&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://developer.mozilla.org/en-US/docs/Web/API/CSS/supports_static&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/j4hvK/hyTwrPh2Qg/rFAfUbEE5xkw0NtWCDkRmK/img.png?width=1920&amp;amp;height=1080&amp;amp;face=0_0_1920_1080');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;CSS: supports() static method - Web APIs | MDN&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;The CSS.supports() static method returns a boolean value indicating if the browser supports a given CSS feature, or not.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;developer.mozilla.org&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://d2.naver.com/helloworld/59361&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://d2.naver.com/helloworld/59361&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>공부내용 공유하기</category>
      <category>DOM</category>
      <category>최적화</category>
      <author>눅눅한 피부</author>
      <guid isPermaLink="true">https://nookpi.tistory.com/181</guid>
      <comments>https://nookpi.tistory.com/181#entry181comment</comments>
      <pubDate>Tue, 1 Aug 2023 19:41:17 +0900</pubDate>
    </item>
    <item>
      <title>짜릿해</title>
      <link>https://nookpi.tistory.com/180</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;DragGPT 익스텐션을 만들어서 너무 알차게 쓰고 있다 보니 쓸 때마다 짜릿하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;제일 많이 사용하는 기능은 번역인데, 구글 번역 익스텐션이 자체적인 dom&amp;nbsp;manipulation을 해버리다 보니 &amp;lt;code&amp;gt; 나 &amp;lt;strong&amp;gt; 태그 사이에 있는 문자들은 이상하게 번역되는 등 귀찮은 이슈들이 있었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;i&gt;window.getSelection()으로&lt;/i&gt; 정직하게 드래그 한 텍스트만 긁어오는 DragGPT에선? 그런 이슈가 없지 후후...&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;빠른 대화 모드나 gpt4 토글등의 기능도 추가하고서 유용하게 쓰고 있어서 매우 좋다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;대화 기록을 options 창에서 볼 수 있도록 하는 기능은 진행 중인데, 아직까지 큰 필요성을 못 느껴서 진전이 없는 것 같기도 하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;DragGPT 소스코드는 깃헙에 모두 공개되어 잇는데, 신기하게도 미국의 한 스타트업에서 해당 레포를 Fork 해서 프로덕션을 구성한다는 연락을 받았다. 자기만족 코드가 많아 프로덕션에서 사용한다고 하니 좀 찔리지만... 이런 순간들은 참 보람차다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2023-07-25 오후 10.41.55.png&quot; data-origin-width=&quot;600&quot; data-origin-height=&quot;1000&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/yoKu1/btso1uhZCXe/cwaaTsZ44rxUKdCmj1p140/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/yoKu1/btso1uhZCXe/cwaaTsZ44rxUKdCmj1p140/img.png&quot; data-alt=&quot;내가 자주 사용하는 슬롯들&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/yoKu1/btso1uhZCXe/cwaaTsZ44rxUKdCmj1p140/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FyoKu1%2Fbtso1uhZCXe%2FcwaaTsZ44rxUKdCmj1p140%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;300&quot; height=&quot;500&quot; data-filename=&quot;스크린샷 2023-07-25 오후 10.41.55.png&quot; data-origin-width=&quot;600&quot; data-origin-height=&quot;1000&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;내가 자주 사용하는 슬롯들&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;figure id=&quot;og_1690292225290&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;드래그 GPT - 드래그로 쉽게 AI를 시작해보세요!&quot; data-og-description=&quot;드래그 후 버튼 클릭만으로 간단하게 선택한 내용을 ChatGPT에게 물어보거나 요청할 수 있어요!&quot; data-og-host=&quot;chrome.google.com&quot; data-og-source-url=&quot;https://chrome.google.com/webstore/detail/%EB%93%9C%EB%9E%98%EA%B7%B8-gpt-%EB%93%9C%EB%9E%98%EA%B7%B8%EB%A1%9C-%EC%89%BD%EA%B2%8C-ai%EB%A5%BC-%EC%8B%9C%EC%9E%91%ED%95%B4%EB%B3%B4%EC%84%B8/akgdgnhlglhelinkmnmiakgccdkghjbh&quot; data-og-url=&quot;https://chrome.google.com/webstore/detail/%EB%93%9C%EB%9E%98%EA%B7%B8-gpt-%EB%93%9C%EB%9E%98%EA%B7%B8%EB%A1%9C-%EC%89%BD%EA%B2%8C-ai%EB%A5%BC-%EC%8B%9C%EC%9E%91%ED%95%B4%EB%B3%B4%EC%84%B8/akgdgnhlglhelinkmnmiakgccdkghjbh&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/2BTck/hyTqBZ18Xe/34ffprgPIQepcIZBqiWmRk/img.jpg?width=128&amp;amp;height=128&amp;amp;face=0_0_128_128&quot;&gt;&lt;a href=&quot;https://chrome.google.com/webstore/detail/%EB%93%9C%EB%9E%98%EA%B7%B8-gpt-%EB%93%9C%EB%9E%98%EA%B7%B8%EB%A1%9C-%EC%89%BD%EA%B2%8C-ai%EB%A5%BC-%EC%8B%9C%EC%9E%91%ED%95%B4%EB%B3%B4%EC%84%B8/akgdgnhlglhelinkmnmiakgccdkghjbh&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://chrome.google.com/webstore/detail/%EB%93%9C%EB%9E%98%EA%B7%B8-gpt-%EB%93%9C%EB%9E%98%EA%B7%B8%EB%A1%9C-%EC%89%BD%EA%B2%8C-ai%EB%A5%BC-%EC%8B%9C%EC%9E%91%ED%95%B4%EB%B3%B4%EC%84%B8/akgdgnhlglhelinkmnmiakgccdkghjbh&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/2BTck/hyTqBZ18Xe/34ffprgPIQepcIZBqiWmRk/img.jpg?width=128&amp;amp;height=128&amp;amp;face=0_0_128_128');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;드래그 GPT - 드래그로 쉽게 AI를 시작해보세요!&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;드래그 후 버튼 클릭만으로 간단하게 선택한 내용을 ChatGPT에게 물어보거나 요청할 수 있어요!&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;chrome.google.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>잡담</category>
      <category>DragGPT</category>
      <author>눅눅한 피부</author>
      <guid isPermaLink="true">https://nookpi.tistory.com/180</guid>
      <comments>https://nookpi.tistory.com/180#entry180comment</comments>
      <pubDate>Tue, 25 Jul 2023 22:45:03 +0900</pubDate>
    </item>
    <item>
      <title>사람은 변한다</title>
      <link>https://nookpi.tistory.com/179</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1000&quot; data-origin-height=&quot;667&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/2ASxP/btsnGZYcIVN/F1G6V3mFXn9k8zgioSPIUK/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/2ASxP/btsnGZYcIVN/F1G6V3mFXn9k8zgioSPIUK/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/2ASxP/btsnGZYcIVN/F1G6V3mFXn9k8zgioSPIUK/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F2ASxP%2FbtsnGZYcIVN%2FF1G6V3mFXn9k8zgioSPIUK%2Fimg.jpg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;500&quot; height=&quot;334&quot; data-origin-width=&quot;1000&quot; data-origin-height=&quot;667&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;나는 예전부터 피드백이 고팠다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;특히 나를 성장시킬 수 있는 양질의 피드백에 큰 갈증이 있었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;첫 회사에서 CTO님께 종종 여쭤봤던 것 같다. 제가 뭘 더 하면 성장할 수 있을까요? 어떤 점을 채워나가야 할까요?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;CTO님은 무리하지 말고, 페이스 잃지 말고, 성장에 대한 욕구를 변치 않게 지금처럼만 하면 된다고 말했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;지금처럼만 해서 될까요? 그리고 성장에 대한 욕구 변할것 같지 않은데...&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사람은 생각보다 금방 변하더라구요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;씁쓸하게 말씀하시던 CTO님의 그 말이 오래도록 잊혀지지 않는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;지금 생각해보면 그 말을 듣고 오기가 생겼던 것 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;남들은 다 변하더라도 나는 변하지 말아야지.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;늘 치열하게 노력해야지. 진짜 잘해져야지.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;주말 내내 놀지는 말아야지. 최소한 하루는 뭐라도 의미있는 일을 해야지.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;내 주변 사람들에게 도움을 주고 꾸준히 인정받는 사람이 되어야지.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;내 실력을 숫자로, 수준으로 계산하지 말아야지. 늘 벡터로 계산해야지.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;CTO님의 그 말을 들은지도 2년이 훌쩍 넘었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사람은 변한다. 맞는 말인 것 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래도, 더 좋은 방향으로 변하기 위해 하루하루 발버둥을 쳐본다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즐겁기도 고통스럽기도 하지만 어차피 세상 만사가 다 그런걸 뭐.&lt;/p&gt;</description>
      <category>잡담</category>
      <author>눅눅한 피부</author>
      <guid isPermaLink="true">https://nookpi.tistory.com/179</guid>
      <comments>https://nookpi.tistory.com/179#entry179comment</comments>
      <pubDate>Sat, 15 Jul 2023 14:23:36 +0900</pubDate>
    </item>
    <item>
      <title>React + xState</title>
      <link>https://nookpi.tistory.com/177</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;2023.03.20 개발팀 세미나&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;iframe src=&quot;https://www.icloud.com/keynote/0b4nf4KAdkkYctsKmuLs-gXcg?embed=true&quot; width=&quot;640&quot; height=&quot;500&quot; frameborder=&quot;0&quot; allowfullscreen=&quot;1&quot;&gt;&lt;/iframe&gt;&lt;/p&gt;</description>
      <category>발표자료</category>
      <author>눅눅한 피부</author>
      <guid isPermaLink="true">https://nookpi.tistory.com/177</guid>
      <comments>https://nookpi.tistory.com/177#entry177comment</comments>
      <pubDate>Sat, 15 Jul 2023 00:37:02 +0900</pubDate>
    </item>
    <item>
      <title>Typescript의 공변성과 반공변성</title>
      <link>https://nookpi.tistory.com/176</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;공변성과 반공변성&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;'공변성(Covariance)'과 '반공변성(Contravariance)'은 Typescript를 다루는 개발자라면 누구나 매일매일 접하고 또 사용하는 개념이지만, 나를 포함해서 해당 개념을 매번 의식하고 인터페이스를 설계하는 사람은 많지 않을 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;공변성과 반공변성이 타입스크립트에서 가지는 의의를 이해하기 전에, 먼저 공변성이 뭔지, 반 공변성이 뭔지부터 이해하고 가보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;공변성&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;더 많이 파생된(더 구체화된) 형식을 사용할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;I&amp;lt;T'&amp;gt; 인스턴스 타입을 I&amp;lt;T&amp;gt; 형식의 변수에 할당할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;반공변성&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;덜 파생적인(더 제네릭한) 형식을 사용할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;I&amp;lt;T&amp;gt; 인스턴스를 I&amp;lt;T'&amp;gt; 형식의 변수에 할당할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;쉽게 풀어서 설명하려고 해도 되지 않으니 예시를 들어서 한 번 살펴보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음과 같은 타입 A,B,C가 있다.&lt;/p&gt;
&lt;pre id=&quot;code_1689325126943&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;type A = string
type B = string | number
type C = string | number | null&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 함수 Foo라는 타입을 선언해보자.&lt;/p&gt;
&lt;pre id=&quot;code_1689325198943&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;type Foo = (b: B) =&amp;gt; B;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 이 함수의 타입을 지키면서 안전하게 호출할 수 있는 함수는 다음 중 어떤 함수일까?&lt;/p&gt;
&lt;pre id=&quot;code_1689325454950&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;const foo1: Foo = (a: A) =&amp;gt; {
  return {} as A;
};

const foo2: Foo = (a: A) =&amp;gt; {
  return {} as C;
};

const foo3: Foo = (c: C) =&amp;gt; {
  return {} as C;
};

const foo4: Foo = (c: C) =&amp;gt; {
  return {} as A;
};&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;A는 B의 서브타입이며, B는 C의 서브타입이다. 그러므로 A는 C의 서브타입이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(반대의 관계는 슈퍼타입)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;foo1부터 살펴보자. 해당 함수는 안전한가?&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;인자로 받는 A는 B의 서브타입으로, B에서 number가 빠져있다. foo1을 호출하는 곳에서는 Foo의 인터페이스에 맞게 인자로 number를 넘겨줄 수 있을텐데, 그에 대한 처리가 되어있지 않아 위험해보인다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;같은 이유로 같은 인자를 받는 foo2 역시 위험해보인다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;foo3을 살펴보자. 해당 함수는 안전할까?&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;인자로 받는 C는 B의 슈퍼타입으로, B에서 null이 추가된 타입이다. foo3을 호출하는 곳에서는 Foo의 인터페이스에 맞게 string | number를 넘겨줄 수 있다. 이 경우 안전하다고 할 수 있겠다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 반환값을 보자. 반환값인 C는 B에서 null이 추가된 타입이다. foo3을 호출하는 곳에서는 Foo의 인터페이스에 맞게 반환값인 string과 number에 대한 처리를 하게 될 것이다. 예상치 못한 null이 반환될 수 있으니 위험해보인다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;foo4를 살펴보자. 해당 함수는 안전할까?&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;인자로 받는 C는 foo3에서 살펴봤듯 안전하다고 할 수 있고, 반환값을 살펴보자. 반환값인 A는 B에서 number가 제거된 타입이다. foo4를 호출하는 곳에서는 Foo의 인터페이스에 맞게 반환값인 string | number에 대한 처리를 하지만, 실제로 반환되는 타입은 string일 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러므로 foo4는 인자와 반환값 모두 안전한 함수이다!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;뭔소리야?&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;직관적으로 이해하기 쉽지 않아서 설명도 실제 호출을 기준으로 했지만, 설명을 듣고 나서도 쉽사리 이해되지 않는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 위 예시를 통해서 두 가지의 규칙을 발견할 수 있었는데,&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. 함수의 파라미터는 더 좁은 타입을 사용할 수 있다. (반공변성 - 더 구체적인 타입을 사용할 수 있다)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. 함수의 반환값은 더 넓은 타입을 사용할 수 있다. (공변성 - 더 일반적인 타입을 사용할 수 있다)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;타입스크립트에서 함수의 타입은 위의 규칙으로 동작한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;단, 위 가정은 타입스크립트의 strict flag, 정확히는 strictFunctionTypes flag를 활성화 했을때의 동작이며, 해당 flag가 off인 경우 함수의 파라미터는 공변성과 반공변성을 모두 갖도록 동작한다. 이 경우에는 이변성(Bivariance)을 갖고 있다고 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;왜?&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사용 사례에서 살펴보았듯, 파라미터가 반공변성을 가지는 것이 안전한데 왜 타입스크립트의 기본 설정은 그렇지 않은걸까?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사실 모든 사용 사례에서 파라미터가 반공변성을 가지는 것이 적절한 것은 아니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Array.push 메소드의 시그니처를 살펴보자.&lt;/p&gt;
&lt;pre id=&quot;code_1689328220142&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;interface Array&amp;lt;T&amp;gt; {
    //~~~~
    /**
     * Appends new elements to the end of an array, and returns the new length of the array.
     * @param items New elements to add to the array.
     */
    push(...items: T[]): number;
	//~~~~
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Array&amp;lt;string | number&amp;gt; 에 string을 하나 넣고 싶다면 push 메소드를 사용하게 될 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 경우 push()의 타입은 (...items: Array&amp;lt;string | numebr&amp;gt;) =&amp;gt; number 가 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;함수 파라미터의 반공변성에 의거하면, push 메소드에 (...items: Array&amp;lt;string&amp;gt;) =&amp;gt; number를 할당할 수 없다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 뭔가 또 논리적으로 이상하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;숫자와 문자로 이루어진 배열에 문자를 추가하는 동작이 위험하지 않기 때문에 이 경우에는 함수의 파라미터가 공변성을 가지는 것이 맞다는 결론이 나오게 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;--strict 환경에서도 Array.push() 문제 없던데?&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;맞다. 나를 포함한 많은 타입스크립트 개발자들이 TypeSafe한 개발 환경을 위해 strict flag를 켜두지만, Array.push를 사용하면서 반공변성으로 인한 곤란함을 겪지는 않는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;비밀은 메소드를 선언하는 형태에 있는데,&lt;/p&gt;
&lt;pre id=&quot;code_1689328639063&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;interface Array&amp;lt;T&amp;gt; {
    push(...items: T[]): number;
}

interface Array&amp;lt;T&amp;gt; {
    push: (...items: T[]): number;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위의 방식의 선언과, 아래 방식의 선언이 다르게 동작하는(...) 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://github.com/microsoft/TypeScript/pull/18654&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://github.com/microsoft/TypeScript/pull/18654&lt;/a&gt;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;The stricter checking applies to all function types, except those originating in method or construcor declarations. Methods are excluded specifically to ensure generic classes and interfaces (such as Array&amp;lt;T&amp;gt;) continue to mostly relate covariantly.&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정말 미묘하면서 알기 힘든 포인트로 공변성을 다룰 수 있는 길을 열어둔 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;By the way, note that whereas some languages (e.g. C# and Scala) require variance annotations (out/in or&amp;nbsp;+/-), variance emerges naturally from the actual use of a type parameter within a generic type due to TypeScript's structural type system.&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;C#이나 Scala 혹은 Kotlin처럼 이러한 개념을 드러내는게 좋았을까?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2023-07-14 오후 8.12.47.png&quot; data-origin-width=&quot;1470&quot; data-origin-height=&quot;436&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bAst4n/btsnD6xmyC4/ORgZ76d3hKvHrWwPWcALKK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bAst4n/btsnD6xmyC4/ORgZ76d3hKvHrWwPWcALKK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bAst4n/btsnD6xmyC4/ORgZ76d3hKvHrWwPWcALKK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbAst4n%2FbtsnD6xmyC4%2FORgZ76d3hKvHrWwPWcALKK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;600&quot; height=&quot;178&quot; data-filename=&quot;스크린샷 2023-07-14 오후 8.12.47.png&quot; data-origin-width=&quot;1470&quot; data-origin-height=&quot;436&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;적어도 인지하기 어려운 미묘한 문법 차이로 공변성을 제어하는 것 보다는 확실한 예약어를 추가하는게 낫지 않았을까...? 하는 생각이 든다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;추가&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;typescript 4.7 부터 타입 제네릭에 공변, 반공변에 대해 명시적으로 표기가 가능해졌다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1689814654851&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;type Getter&amp;lt;out T&amp;gt; = () =&amp;gt; T;

type Setter&amp;lt;in T&amp;gt; = (value: T) =&amp;gt; void;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.typescriptlang.org/docs/handbook/release-notes/typescript-4-7.html#optional-variance-annotations-for-type-parameters&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://www.typescriptlang.org/docs/handbook/release-notes/typescript-4-7.html#optional-variance-annotations-for-type-parameters&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1689814639447&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;Documentation - TypeScript 4.7&quot; data-og-description=&quot;TypeScript 4.7 Release Notes&quot; data-og-host=&quot;www.typescriptlang.org&quot; data-og-source-url=&quot;https://www.typescriptlang.org/docs/handbook/release-notes/typescript-4-7.html#optional-variance-annotations-for-type-parameters&quot; data-og-url=&quot;https://www.typescriptlang.org/docs/handbook/release-notes/typescript-4-7.html#optional-variance-annotations-for-type-parameters&quot; data-og-image=&quot;&quot;&gt;&lt;a href=&quot;https://www.typescriptlang.org/docs/handbook/release-notes/typescript-4-7.html#optional-variance-annotations-for-type-parameters&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://www.typescriptlang.org/docs/handbook/release-notes/typescript-4-7.html#optional-variance-annotations-for-type-parameters&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url();&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;Documentation - TypeScript 4.7&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;TypeScript 4.7 Release Notes&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;www.typescriptlang.org&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;참고&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://learn.microsoft.com/ko-kr/dotnet/standard/generics/covariance-and-contravariance&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://learn.microsoft.com/ko-kr/dotnet/standard/generics/covariance-and-contravariance&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1689333378380&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;제네릭의 공 분산과 반공 분산&quot; data-og-description=&quot;.NET 제네릭에서 파생된 형식을 더 많이 사용할 수 있는 공변성(Covariance) 및 파생된 형식을 더 적게 사용할 수 있는 반공변성(Contravariance)에 대해 알아봅니다.&quot; data-og-host=&quot;learn.microsoft.com&quot; data-og-source-url=&quot;https://learn.microsoft.com/ko-kr/dotnet/standard/generics/covariance-and-contravariance&quot; data-og-url=&quot;https://learn.microsoft.com/ko-kr/dotnet/standard/generics/covariance-and-contravariance&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/k8w0h/hyTjWpdLbv/hu4nSeV5tPKvLz6yUcg8wk/img.png?width=636&amp;amp;height=300&amp;amp;face=0_0_636_300&quot;&gt;&lt;a href=&quot;https://learn.microsoft.com/ko-kr/dotnet/standard/generics/covariance-and-contravariance&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://learn.microsoft.com/ko-kr/dotnet/standard/generics/covariance-and-contravariance&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/k8w0h/hyTjWpdLbv/hu4nSeV5tPKvLz6yUcg8wk/img.png?width=636&amp;amp;height=300&amp;amp;face=0_0_636_300');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;제네릭의 공 분산과 반공 분산&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;.NET 제네릭에서 파생된 형식을 더 많이 사용할 수 있는 공변성(Covariance) 및 파생된 형식을 더 적게 사용할 수 있는 반공변성(Contravariance)에 대해 알아봅니다.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;learn.microsoft.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;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/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;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/&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1689333386659&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;공변성이란 무엇인가 / seob.dev&quot; data-og-description=&quot;TypeScript의 공변성과 --strictFunctionTypes 옵션에 대해서 알아봅니다.&quot; data-og-host=&quot;seob.dev&quot; data-og-source-url=&quot;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/&quot; data-og-url=&quot;https://seob.dev/posts/공변성이란-무엇인가/&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/z0p3s/hyTjLOVeF4/RVMbgQSKyhUDsom9OIwHpk/img.png?width=2560&amp;amp;height=1440&amp;amp;face=0_0_2560_1440,https://scrap.kakaocdn.net/dn/ovrUi/hyTjT7gzoS/gCxB31diXybD3uaaL790RK/img.png?width=2560&amp;amp;height=1440&amp;amp;face=0_0_2560_1440&quot;&gt;&lt;a href=&quot;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/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;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/&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/z0p3s/hyTjLOVeF4/RVMbgQSKyhUDsom9OIwHpk/img.png?width=2560&amp;amp;height=1440&amp;amp;face=0_0_2560_1440,https://scrap.kakaocdn.net/dn/ovrUi/hyTjT7gzoS/gCxB31diXybD3uaaL790RK/img.png?width=2560&amp;amp;height=1440&amp;amp;face=0_0_2560_1440');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;공변성이란 무엇인가 / seob.dev&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;TypeScript의 공변성과 --strictFunctionTypes 옵션에 대해서 알아봅니다.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;seob.dev&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://edykim.com/ko/post/what-are-covariance-and-contravariance/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://edykim.com/ko/post/what-are-covariance-and-contravariance/&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1689333388721&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;공변성과 반공변성은 무엇인가?&quot; data-og-description=&quot;Stephan Boyer의 What are covariance and contravariance?을 번역한 글이다. 공변성과 반공변성은 무엇인가? 서브타이핑은 프로그래밍 언어 이론에서 까다로운 주제다. 공변성과 반공변성은 오해하기 쉬운 주&quot; data-og-host=&quot;edykim.com&quot; data-og-source-url=&quot;https://edykim.com/ko/post/what-are-covariance-and-contravariance/&quot; data-og-url=&quot;https://edykim.com/ko/post/what-are-covariance-and-contravariance/&quot; data-og-image=&quot;&quot;&gt;&lt;a href=&quot;https://edykim.com/ko/post/what-are-covariance-and-contravariance/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://edykim.com/ko/post/what-are-covariance-and-contravariance/&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url();&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;공변성과 반공변성은 무엇인가?&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Stephan Boyer의 What are covariance and contravariance?을 번역한 글이다. 공변성과 반공변성은 무엇인가? 서브타이핑은 프로그래밍 언어 이론에서 까다로운 주제다. 공변성과 반공변성은 오해하기 쉬운 주&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;edykim.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://github.com/microsoft/TypeScript/pull/18654&quot;&gt;https://github.com/microsoft/TypeScript/pull/18654&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1689333391142&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;object&quot; data-og-title=&quot;Strict function types by ahejlsberg &amp;middot; Pull Request #18654 &amp;middot; microsoft/TypeScript&quot; data-og-description=&quot;With this PR we introduce a --strictFunctionTypes mode in which function type parameter positions are checked contravariantly instead of bivariantly. The stricter checking applies to all function t...&quot; data-og-host=&quot;github.com&quot; data-og-source-url=&quot;https://github.com/microsoft/TypeScript/pull/18654&quot; data-og-url=&quot;https://github.com/microsoft/TypeScript/pull/18654&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/bXqsIs/hyTjOLEuPO/bbIyhpHR2yUZGdCgEyXO0K/img.png?width=1200&amp;amp;height=600&amp;amp;face=980_133_1060_221&quot;&gt;&lt;a href=&quot;https://github.com/microsoft/TypeScript/pull/18654&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://github.com/microsoft/TypeScript/pull/18654&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/bXqsIs/hyTjOLEuPO/bbIyhpHR2yUZGdCgEyXO0K/img.png?width=1200&amp;amp;height=600&amp;amp;face=980_133_1060_221');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;Strict function types by ahejlsberg &amp;middot; Pull Request #18654 &amp;middot; microsoft/TypeScript&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;With this PR we introduce a --strictFunctionTypes mode in which function type parameter positions are checked contravariantly instead of bivariantly. The stricter checking applies to all function t...&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;github.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>공부내용 공유하기</category>
      <category>TypeScript</category>
      <author>눅눅한 피부</author>
      <guid isPermaLink="true">https://nookpi.tistory.com/176</guid>
      <comments>https://nookpi.tistory.com/176#entry176comment</comments>
      <pubDate>Fri, 14 Jul 2023 20:16:43 +0900</pubDate>
    </item>
    <item>
      <title>TCP/IP 인터넷(네트워크) 계층</title>
      <link>https://nookpi.tistory.com/174</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;TCP/IP 인터넷(네트워크) 계층&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;700&quot; data-origin-height=&quot;565&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ehOOUB/btsk0R21j4y/vk7skKGMnXQAm3prJzvhv0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ehOOUB/btsk0R21j4y/vk7skKGMnXQAm3prJzvhv0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ehOOUB/btsk0R21j4y/vk7skKGMnXQAm3prJzvhv0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FehOOUB%2Fbtsk0R21j4y%2Fvk7skKGMnXQAm3prJzvhv0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;500&quot; height=&quot;404&quot; data-origin-width=&quot;700&quot; data-origin-height=&quot;565&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;역할 세 줄 요약&lt;/h3&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;IP 프로토콜을 사용하여 호스트 간의 통신에 필요한 주소 관리와 경로 제어를 수행한다.&lt;/li&gt;
&lt;li&gt;패킷을 라우팅하여 수신 호스트까지 전달한다.&lt;/li&gt;
&lt;li&gt;데이터의 오류 확인 및 수정을 위한 ICMP 프로토콜을 제공한다.&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;주요 프로토콜&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;프로토콜이란?&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;패킷 교환 과정에서 정보를 주고받는 데 사용하는 규약이며, 송/수신 호스트의 주소 지정과 패킷 분할 및 조립 기능을 담당&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span data-token-index=&quot;1&quot;&gt;= 인터넷에서 컴퓨터들이 서로 정보를 주고받는 데 쓰이는 통신규약&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1356&quot; data-origin-height=&quot;624&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bt3vF4/btsk0eEbkCM/hXlcmz86GGmNIEfUsCZaK0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bt3vF4/btsk0eEbkCM/hXlcmz86GGmNIEfUsCZaK0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bt3vF4/btsk0eEbkCM/hXlcmz86GGmNIEfUsCZaK0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbt3vF4%2Fbtsk0eEbkCM%2FhXlcmz86GGmNIEfUsCZaK0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;500&quot; height=&quot;230&quot; data-origin-width=&quot;1356&quot; data-origin-height=&quot;624&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&amp;nbsp;&lt;/h4&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;IP 프로토콜 (Internet Protocol)&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;대표적인 프로토콜 (IP가 이미 인터넷 프로토콜의 약자인데 왜 IP 프로토콜이라 부르는지...?)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;송/수신 호스트가 패킷 교환 네트워크(PSN: 패킷 스위칭 네트워크)에서 정보를 주고받는데 사용&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일명 &lt;b&gt;아이피&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;IP의 정보는 패킷이라는 단위로 쪼개져서 나누어 전송&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;특징&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;비신뢰성: 패킷이 전송되는 과정에서 유실, 중복, 섞임 등이 발생할 수 있으며, 이러한 문제는 인터넷이라는 분산 구조에서 기인함. 이러한 문제에 대해서 IP 프로토콜은 보장해주지 않는다. &lt;span data-token-index=&quot;1&quot;&gt;왜냐? 속도를 위해서.&lt;br /&gt;&lt;br /&gt;&lt;/span&gt;&lt;i&gt;&lt;span data-token-index=&quot;3&quot;&gt;이런 문제를 대응하고자 한다면 상위 프로토콜인 TCP등에서 구현을 하면 된다. 아마 수신자 입장에서 버퍼를 두고 순서를 정렬해서 받기 + 빠진 부분 체크하는 등의 구현이 이뤄지지 않을까 싶다.&lt;/span&gt;&lt;/i&gt;&lt;/li&gt;
&lt;li&gt;&lt;span data-token-index=&quot;3&quot;&gt;비연결성: IP 프로토콜은 데이터 전송 전에 송신자와 수신자가 미리 약속을 하거나 연결을 설정해두지 않고도 패킷을 전송할 수 있다. 인터넷이 유연하고 확장성 높은 구조를 갖출 수 있게 하지만, 전송 과정에서의 제어나 예측 가능성은 포기한다. &lt;br /&gt;&lt;br /&gt;쉽게 말해서 아빠와 아들이 하는 캐치볼이 아니라는 뜻. &lt;i&gt;보낸다?&lt;/i&gt; 같은 이야기 없이 다짜고짜 보낸다. &lt;br /&gt;&lt;br /&gt;&lt;i&gt;&lt;span data-token-index=&quot;1&quot;&gt;상위 프로토콜에서 &lt;/span&gt;&lt;a style=&quot;color: #000000;&quot; href=&quot;https://developer.mozilla.org/en-US/docs/Glossary/Preflight_request&quot; data-token-index=&quot;2&quot;&gt;&lt;span&gt;preflight&lt;/span&gt;&lt;/a&gt;&lt;span data-token-index=&quot;3&quot;&gt;나 TLS handshake 등의 기술이 약간 결은 다르지만 본격적인 연결 전에 먼저 확인 과정을 거친다는 점에서 일종의 비연결성을 보완하는 수단이라고 볼 수 있지 않을까?&lt;/span&gt;&lt;/i&gt;&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span data-token-index=&quot;3&quot;&gt;&lt;span data-token-index=&quot;3&quot;&gt;ICMP 프로토콜 (Internet Control Message Protocol)&lt;/span&gt;&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;데이터 전송 과정에서의 오류 메시지 등을 다루는 데 주로 사용된다고 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;데이터가 대상에 도달했는지, 그리고 도달 시간이 적절한지 등을 확인하는데 사용되며, 네트워크의 건강상태를 확인한다고 보면 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;DDoS 공격에도 사용할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;통신을 하는 경우 ICMP를 사용하여 일부 데이터가 누락된 경우 수신자 &amp;rarr; 송신자로 전송되는 에러 메시지를 생성할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;주요 역할&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;에러 보고: 지나치게 크기가 큰 데이터 패킷이 전송되는 경우 라우터는 해당 패킷을 폐기하고 ICMP 메시지를 발신자에게 전송해서 네트워크 전송에 문제가 생겼음을 알려준다.&lt;/li&gt;
&lt;li&gt;네트워크 진단: trace route(패킷 전송 경로 추적 명령어), ping 모두 ICMP를 사용한다.&lt;/li&gt;
&lt;li&gt;네트워크 성능 저하(공격): &lt;br /&gt;1. ICMP Echo 요청을 담은 ping을 대량으로 보내 서비스를 다운시키는 공격&lt;br /&gt;2. ICMP Redirect 메시지를 수정하여 목적지 주소를 조작하는 중간자 공격&lt;br /&gt;3. ICMP Echo 요청을 대량으로 브로드캐스팅 하여 네트워크 상의 모든 호스트가 ICMP Reply 응답을 하도록 유도한다. 이 때 공격 대상이 되는 서버를 Reply 메시지의 수신자로 하여 대량의 트래픽을 유도하는 공격&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&amp;nbsp;&lt;/h4&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;ARP 프로토콜 (Address Resolution Protocol: 주소 결정 프로토콜)&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;IP 주소를 물리적인 네트워크의 주소(MAC 주소)로 바인딩 시키는 데 사용된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;ARP의 구조&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;송신자의 MAC/IP 주소, 그리고 수신자의 MAC/IP 주소가 모두 기재되어 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;893&quot; data-origin-height=&quot;505&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/OIyTa/btsk1AUdL68/QkadD1PkKYW8yBKPKkWWGK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/OIyTa/btsk1AUdL68/QkadD1PkKYW8yBKPKkWWGK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/OIyTa/btsk1AUdL68/QkadD1PkKYW8yBKPKkWWGK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FOIyTa%2Fbtsk1AUdL68%2FQkadD1PkKYW8yBKPKkWWGK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;500&quot; height=&quot;283&quot; data-origin-width=&quot;893&quot; data-origin-height=&quot;505&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;호스트 A가 호스트 B에게 IP를 통한 패킷을 전송하려고 한다면? MAC 주소를 모르는데 어떻게 전송을 하게 될까?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;895&quot; data-origin-height=&quot;508&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bc504i/btsk2eQVyZU/vfe1SQosERHJO6z2MG27q0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bc504i/btsk2eQVyZU/vfe1SQosERHJO6z2MG27q0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bc504i/btsk2eQVyZU/vfe1SQosERHJO6z2MG27q0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbc504i%2Fbtsk2eQVyZU%2Fvfe1SQosERHJO6z2MG27q0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;500&quot; height=&quot;284&quot; data-origin-width=&quot;895&quot; data-origin-height=&quot;508&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;A와 B가 같은 네트워크에 있는 경우&lt;/b&gt;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;A&lt;/b&gt;는 같은 네트워크 대역에 &lt;b&gt;B&lt;/b&gt;의 IP주소에 해당하는 단말기가 있는지를 광역으로 물어봄(ARP 요청 브로드캐스팅)&lt;/li&gt;
&lt;li&gt;&lt;b&gt;A&lt;/b&gt;는 &lt;b&gt;B&lt;/b&gt;의 MAC 주소를 모르기 때문에 ARP 내부의 타겟 MAC 주소는 기본값인 게이트웨이의 MAC 주소(FF:FF:FF:FF:FF:FF)로 기록해서 일단 전송&lt;/li&gt;
&lt;li&gt;&lt;b&gt;네트워크 스위치&lt;/b&gt;는 &lt;b&gt;A&lt;/b&gt;의 MAC 주소를 자신의 MAC 주소 테이블에 기록(기존에 없었다면)&lt;/li&gt;
&lt;li&gt;&lt;b&gt;네트워크 스위치&lt;/b&gt;는 &lt;b&gt;B&lt;/b&gt;의 IP주소에 해당하는 MAC 주소가 테이블에 있는지 확인
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;B&lt;/b&gt;의 MAC 주소가 테이블에 있으면 &lt;b&gt;A&lt;/b&gt;에게 해당 MAC 주소를 알려줌&lt;/li&gt;
&lt;li&gt;&lt;b&gt;B&lt;/b&gt;의 MAC 주소가 테이블에 없다면, &lt;b&gt;A&lt;/b&gt;가 보낸 ARP 요청이 &lt;b&gt;B&lt;/b&gt;에게도 전달됨. &lt;b&gt;B&lt;/b&gt;는 ARP요청을 받고 자신의 MAC 주소를 알려주는 응답을 보냄. &lt;b&gt;스위치&lt;/b&gt;는 &lt;b&gt;B&lt;/b&gt;의 응답에 있는 MAC 주소를 테이블에 저장&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;A&lt;/b&gt;는 전달받은 MAC 주소를 통해 마침내 &lt;b&gt;B&lt;/b&gt;와 통신 시작&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;862&quot; data-origin-height=&quot;1159&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ydvqT/btsk0SU9tao/qQ3lUopKsVHqkQKT8FxSB0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ydvqT/btsk0SU9tao/qQ3lUopKsVHqkQKT8FxSB0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ydvqT/btsk0SU9tao/qQ3lUopKsVHqkQKT8FxSB0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FydvqT%2Fbtsk0SU9tao%2FqQ3lUopKsVHqkQKT8FxSB0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;500&quot; height=&quot;672&quot; data-origin-width=&quot;862&quot; data-origin-height=&quot;1159&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;A와 B가 다른 네트워크에 있는 경우&lt;/b&gt;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;같은 네트워크에 IP에 해당하는 &lt;b&gt;B&lt;/b&gt;가 존재하는지 확인&lt;/li&gt;
&lt;li&gt;마찬가지로 일단 ARP 내부의 타겟 MAC 주소는 기본값인 게이트웨이의 MAC 주소(FF:FF:FF:FF:FF:FF)로 기록된 상태&lt;/li&gt;
&lt;li&gt;내부 네트워크에서 &lt;b&gt;B&lt;/b&gt;를 찾지 못했다면 ARP 요청은 게이트웨이(라우터)로 전달&lt;/li&gt;
&lt;li&gt;ARP 패킷은 게이트웨이를 거치면서 최종적으로 &lt;b&gt;B&lt;/b&gt;의 IP 주소를 가진 네트워크에 도달. 이 과정에서 ARP target MAC 주소는 계속 변경되지만 IP 주소는 유지&lt;/li&gt;
&lt;li&gt;최종적으로 &lt;b&gt;B&lt;/b&gt;를 찾고, &lt;b&gt;B&lt;/b&gt;는 자신의 MAC 주소를 담아 ARP 응답을 &lt;b&gt;A&lt;/b&gt;에게 전송. 요청인지 응답인지 구분하는 방법은 ARP 패킷 구조에 있는 Operation 값으로 판별. 1은 요청, 2는 응답&lt;/li&gt;
&lt;li&gt;이제 &lt;b&gt;A&lt;/b&gt;와 &lt;b&gt;B&lt;/b&gt;는 MAC 주소를 통해 통신 시작 (ARP cache)&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;ARP를 통한 공격?&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;대표적인 공격은 ARP 스푸핑.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;공격자는 공격하려는 대상의 네트워크에 접근하여 ARP 프로토콜로 모든 장치의 MAC 주소를 확인&lt;/li&gt;
&lt;li&gt;그 후에, 가로채려는 IP 주소에 해당하는 MAC 주소를 자신의 MAC 주소로 교체 (ARP 요청을 통해 ARP cache 테이블에 데이터를 수정한다고 보면 됨)&lt;/li&gt;
&lt;li&gt;사용자가 전송하려는 데이터 혹은 요청이 공격자가 설정한 MAC 주소로 오게 됨&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사용자는 전혀 다른 곳에 접속하는 셈인데 위화감을 느끼지는 않을까?&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;중간자 공격을 통해 트래픽을 원래 보내려던 곳으로 리다이렉트 &amp;rarr; 응답도 본인이 먼저 받은 후 사용자게에 다시 보내는 방식을 사용하기 때문에 아무 일도 일어나지 않은 것처럼 보이게 할 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;그 외 프로토콜&amp;hellip;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;RARP 프로토콜 (Reverse ARP)&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;MAC 주소에서 IP 주소를 찾는 데 사용&lt;/li&gt;
&lt;li&gt;IP주소에서 MAC 주소를 찾는 ARP 프로토콜의 정 반대(Reverse)&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;IGMP 프로토콜&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;다중 캐스트 그룹 관리를 위해 사용&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;IPsec 프로토콜&lt;/b&gt; (Internet Protocol Security)&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;TCP/IP에서 사용되는 인증과 암호화 방법을 제공하는 보안 프로토콜. IPsec은 두 종류의 프로토콜로 구성된다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;AH(Authentication Header) 프로토콜 IP 패킷의 출발지 정보와 패킷 데이터에 대한 인증을 제공해준다. 이를 통해 패킷의 위변조 여부를 검사할 수 있음&lt;/li&gt;
&lt;li&gt;ESP(Encapsulating Security Payload) 프로토콜 IP 패킷 전체를 암호화. 이를 통해 패킷의 내용을 해독할 수 없게 만듦&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;주로 VPN이나 원격 액세스, 안전한 네트워크를 위해 사용하며 일반적으로 기밀성과 무결성을 보장하기 위해 사용된다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;참조&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://ko.wikipedia.org/wiki/%EC%9D%B8%ED%84%B0%EB%84%B7_%ED%94%84%EB%A1%9C%ED%86%A0%EC%BD%9C&quot;&gt;인터넷 프로토콜&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://ko.wikipedia.org/wiki/%EC%9D%B8%ED%84%B0%EB%84%B7_%ED%94%84%EB%A1%9C%ED%86%A0%EC%BD%9C_%EC%8A%A4%EC%9C%84%ED%8A%B8&quot;&gt;인터넷 프로토콜 스위트&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://ko.wikipedia.org/wiki/%EC%9D%B8%ED%84%B0%EB%84%B7_%EC%A0%9C%EC%96%B4_%EB%A9%94%EC%8B%9C%EC%A7%80_%ED%94%84%EB%A1%9C%ED%86%A0%EC%BD%9C#Control_messages&quot;&gt;인터넷 제어 메시지 프로토콜&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.fortinet.com/kr/resources/cyberglossary/internet-control-message-protocol-icmp&quot;&gt;ICMP(인터넷 제어 메시지 프로토콜)란? | Fortinet&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://en.wikipedia.org/wiki/Address_Resolution_Protocol&quot;&gt;Address Resolution Protocol&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.stevenjlee.net/2020/06/07/%EC%9D%B4%ED%95%B4%ED%95%98%EA%B8%B0-arp-address-resolution-protocol-%ED%94%84%EB%A1%9C%ED%86%A0%EC%BD%9C/&quot;&gt;[이해하기] ARP (Address Resolution Protocol) 프로토콜 | STEVEN J. LEE&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://coding-factory.tistory.com/720&quot;&gt;[Network] ARP(주소 결정 프로토콜)에 대하여&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://help.ui.com/hc/en-us/articles/115005984408-Intro-to-Networking-Address-Resolution-Protocol-ARP-&quot;&gt;Ubiquiti Help Center&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.iana.org/assignments/arp-parameters/arp-parameters.xhtml&quot;&gt;Address Resolution Protocol (ARP) Parameters&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://en.wikipedia.org/wiki/IPsec&quot;&gt;IPsec&lt;/a&gt;&lt;/p&gt;</description>
      <category>공부내용 공유하기</category>
      <category>TCP/IP</category>
      <category>인터넷 계층</category>
      <author>눅눅한 피부</author>
      <guid isPermaLink="true">https://nookpi.tistory.com/174</guid>
      <comments>https://nookpi.tistory.com/174#entry174comment</comments>
      <pubDate>Thu, 22 Jun 2023 20:29:30 +0900</pubDate>
    </item>
    <item>
      <title>나는 무엇을 하고 싶은가</title>
      <link>https://nookpi.tistory.com/173</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;정말로 내가 하고 싶은게 무엇일까?&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;돈을 많이 버는 것?&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;얼마가 내가 만족할만한 많은 돈인가?&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;말 그대로 나는 무엇을 하고 살고 싶은지, 앞으로 5년 후, 10년 후에 어떤 일을 하고 싶은지에 대한 고민이 많다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;학창 시절에는 성인이 되고 직장인이 되면 진로고민(?)은 더 이상 하지 않을 줄 알았는데, 나름 획일화된 평가 시스템과 방향성이 제시되던 학창 시절과는 다르게 지금은 어떤 길이 후회가 적은 길일지에 대해 예측하기가 더욱 어렵다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;인생에 있어서 좋은 기회라는 것은 내 선택과 노력으로 어느정도 이끌어낼 수 있는 것이라고 생각하면서도, 찾아온 그것이 기회인지 아닌지를 판단하는 것이 너무나도 어렵다. 정말 좋은 기회인지 아닌지 판단하기 어려우면 기회라고 볼 수 없는게 아닌가? 싶은 생각도 들고...?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;짧은 인생에서 여러 선택을 했고, 결과를 통해 얻은 귀중한 교훈까지 고려한다면 실패한 선택은 없었다고 감히 생각한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;향후 나의 3~5년 삶의 행태와 그 이후의 커리어, 그리고 내 주변 사람들에게도 모두 영향을 미칠 수 있는 선택의 분기라서 더 고민이 되는 것 같다.&lt;/p&gt;</description>
      <category>잡담</category>
      <author>눅눅한 피부</author>
      <guid isPermaLink="true">https://nookpi.tistory.com/173</guid>
      <comments>https://nookpi.tistory.com/173#entry173comment</comments>
      <pubDate>Sun, 11 Jun 2023 18:35:44 +0900</pubDate>
    </item>
    <item>
      <title>프로세스 스케줄링(Process scheduling)</title>
      <link>https://nookpi.tistory.com/172</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;Scheduler?&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기본적으로 모든 프로세스는 크게 두 가지 단계로 이루어져 있다. 하나는 CPU만 사용하는 CPU burst 단계, 다른 하나는 I/O(input/output) 작업만 하는 I/O burst 단계이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;490&quot; data-origin-height=&quot;668&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/IwiNv/btsjqwMKpyJ/tF7vFtKKMFTZfNqf0TYGaK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/IwiNv/btsjqwMKpyJ/tF7vFtKKMFTZfNqf0TYGaK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/IwiNv/btsjqwMKpyJ/tF7vFtKKMFTZfNqf0TYGaK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FIwiNv%2FbtsjqwMKpyJ%2FtF7vFtKKMFTZfNqf0TYGaK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;400&quot; height=&quot;545&quot; data-origin-width=&quot;490&quot; data-origin-height=&quot;668&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;각 프로세스는 CPU에 할당되어 작업을 하게 되고, 해당 CPU의 자원을 점유하여 하고자 하는 동작을 실행한다. 문제는 I/O busrt 단계에서는 CPU가 놀게 된다는 점이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;CPU가 노는 시간을 줄이기 위해 고안된 방법이 바로 스케줄링, 이러한 동작을 하는 모듈을 스케줄러라고 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;목적&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프로세스 스케줄링은 다음 두 가지 목적을 위해 존재한다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;CPU의 성능을 높인다&lt;/li&gt;
&lt;li&gt;프로그램의 성능을 높인다&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;CPU 입장에서의 성능이란? 다음과 같이 나타낼 수 있다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;CPU 이용율: 전체 시간 중 쉬지 않고 CPU가 일한 시간&lt;/li&gt;
&lt;li&gt;처리량: 시간당 수행 완료한 프로세스의 수&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, CPU는 이용률과 처리량이 높아야 성능이 높다고 할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프로세스 혹은 프로그램 입장에서의 성능이란 무엇일까?&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;소요 시간: 프로세스가 대기 큐 &amp;gt; 작업 완료까지 걸리는 시간&lt;/li&gt;
&lt;li&gt;대기 시간: 프로세스가 대기 큐에서 대기한 시간&lt;/li&gt;
&lt;li&gt;응답 시간: 프로세스가 CPU를 할당받는 데까지 걸린 시간&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, 소요시간, 대기시간, 응답시간이 낮아야 프로그램의 성능이 높다고 할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;알고리즘&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일단 아래와 같은 가정을 하고 살펴보자&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;모든 프로세스가 동일한 런타임(수행시간) 가진다&lt;/li&gt;
&lt;li&gt;모든 프로세스는 동일하게 도착한다&lt;/li&gt;
&lt;li&gt;모든 프로세스가 CPU burst만 한다&lt;/li&gt;
&lt;li&gt;이미 프로세스의 런타임이 정해져 있고 cpu가 알고 있다&lt;/li&gt;
&lt;/ol&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&amp;nbsp;&lt;/h4&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span data-token-index=&quot;0&quot;&gt;FCFS Scheduling (First Come First Served)&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;CPU에 오는 순서대로 할당하는 방식. (매우 심플)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;FIFO 방식의 큐(Queue)와 동일한 방식이라고 이해해도 좋다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;438&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b4EV5s/btsjmuIMLmj/lElJ2bTK8yMqxJdEEL7La1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b4EV5s/btsjmuIMLmj/lElJ2bTK8yMqxJdEEL7La1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b4EV5s/btsjmuIMLmj/lElJ2bTK8yMqxJdEEL7La1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb4EV5s%2FbtsjmuIMLmj%2FlElJ2bTK8yMqxJdEEL7La1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;600&quot; height=&quot;205&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;438&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사실 위의 전제조건에서는 어떤 방식을 사용하던 성능상의 차이가 없다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 1번 전제조건인 &lt;b&gt;모든 프로세스가 동일한 수행시간을 가진다&lt;/b&gt; 는 조건이 변경된다면?&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;420&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/LX2Sb/btsjsfjMJLP/oflnZGEkaZHiciGQQDednk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/LX2Sb/btsjsfjMJLP/oflnZGEkaZHiciGQQDednk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/LX2Sb/btsjsfjMJLP/oflnZGEkaZHiciGQQDednk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FLX2Sb%2FbtsjsfjMJLP%2FoflnZGEkaZHiciGQQDednk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;600&quot; height=&quot;197&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;420&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;P1는 즉시 실행, P2, P3은 각각 20,25의 대기 시간을 갖게 되므로 평균 대기 시간이 15가 되어버린다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;어떻게 하면 효율적으로 배치할 수 있을까?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;413&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cwxC8X/btsjlYDnrbx/eO7eJ1QalnbfO8xjkb9wxk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cwxC8X/btsjlYDnrbx/eO7eJ1QalnbfO8xjkb9wxk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cwxC8X/btsjlYDnrbx/eO7eJ1QalnbfO8xjkb9wxk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcwxC8X%2FbtsjlYDnrbx%2FeO7eJ1QalnbfO8xjkb9wxk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;600&quot; height=&quot;194&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;413&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;런타임이 짧은 P2, P3를 앞에 배치했더니 쨔잔! 평균 대기시간이 5로 짧아졌다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위에서 본 바와 같이 하나의 긴 런타임을 가진 프로세스(P1)로 인해 CPU효율성이 낮아지는 문제는 &lt;b&gt;Convoy Effect&lt;/b&gt;라고 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이런 문제가 생길 수 있기 때문에 딱 봐도 FIFO 방식의 스케줄러는 한계가 명확하게 드러난다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;SJF Scheduling (Shortest Job First)&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위에서 본 &lt;b&gt;런타임이 짧은 프로세스를 앞에 배치해서 먼저 실행하는 방식&lt;/b&gt;이 바로 SJF 스케줄링이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;413&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bt8o3j/btsjpEqBscF/MWB2AXhf9byldKT7qkkVV0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bt8o3j/btsjpEqBscF/MWB2AXhf9byldKT7qkkVV0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bt8o3j/btsjpEqBscF/MWB2AXhf9byldKT7qkkVV0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbt8o3j%2FbtsjpEqBscF%2FMWB2AXhf9byldKT7qkkVV0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;600&quot; height=&quot;194&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;413&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 방식의 장점은, 항상 주어진 프로세스에 대해 &lt;b&gt;최소의 평균 대기 시간을 보장&lt;/b&gt;한다는 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;최고의 알고리즘이고 항상 이 방식을 쓰면 좋겠지만&amp;hellip; 두 가지 문제가 있다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;런타임이 긴 프로세스(P1)는 계속 뒤로 밀려나는 문제가 생긴다. 마치 백로그에 있는 우선순위(중) 태스크처럼&amp;hellip; 이러한 현상을 &lt;b&gt;기아(starvation) 현상&lt;/b&gt;이라고 한다.&lt;/li&gt;
&lt;li&gt;앞서 했던 4번 전제조건 &lt;b&gt;&amp;lsquo;이미 프로세스의 런타임이 정해져 있고 cpu가 알고 있다&amp;rsquo;는&lt;/b&gt; 상황은 이상적인 상황으로, 일반적으로는 과거의 런타임을 통한 추정만 가능하다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;SJF 스케줄링을 자세히 쪼개보면 두 가지 방식이 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;Non-preemptive SJF (비선점 방식)&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2번 전제조건 &lt;b&gt;&lt;span data-token-index=&quot;1&quot;&gt;&amp;lsquo;모든 프로세스는 동일하게 도착한다&amp;rsquo;의&lt;/span&gt;&lt;/b&gt; 조건이 만족되지 않는다면 무슨 일이 일어날까?&lt;b&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;412&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/c4PmAz/btsjkHbmx9d/l50IIkb5FfzQ11nX3zzfkK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/c4PmAz/btsjkHbmx9d/l50IIkb5FfzQ11nX3zzfkK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/c4PmAz/btsjkHbmx9d/l50IIkb5FfzQ11nX3zzfkK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fc4PmAz%2FbtsjkHbmx9d%2Fl50IIkb5FfzQ11nX3zzfkK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;600&quot; height=&quot;193&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;412&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;도착 시간이 다르다면, 현재 도착한 프로세스 중 가장 런타임이 짧은 프로세스(P1)가 CPU에 할당된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;한 번 프로세스가 실행되면 완료까지 CPU를 점유하기 때문에 평균 대기시간은 4가 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;Preemptive SJF(선점 방식)&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프로세스를 나누어서 실행할 권한이 CPU에게 있다면 다른 방식을 사용할 수 있는데, 이 방식이 바로 선점 방식이다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;412&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/KPo6H/btsjx2EknnI/cT46WCQC72S9GvvKEAhPqK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/KPo6H/btsjx2EknnI/cT46WCQC72S9GvvKEAhPqK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/KPo6H/btsjx2EknnI/cT46WCQC72S9GvvKEAhPqK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FKPo6H%2Fbtsjx2EknnI%2FcT46WCQC72S9GvvKEAhPqK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;600&quot; height=&quot;193&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;412&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 프로세스를 잘게 쪼개서 실행할 경우 평균 대기 시간은 3이 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;도착 시간 0에 실행 가능한 가장 짧은 런타임을 가진 프로세스 P1을 실행하다가, 시간 2가 되었을 때 더 짧은 남은 런타임을 가진 P2를 실행한다. (이 시점에 P1의 남은 런타임은 5) 시간 4가 되었을 때 도착한 P3을 실행한다. (이 시점에 P2의 남은 런타임은 2)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 방식을 SRTF(Shortest Remaining Time First) 혹은 STCF(Shortest Time-to-Completion First)라고도 부른다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;눈치챘을지도 모르지만, P1는 가장 먼저 도착하고 7의 런타임을 가졌으나 가장 마지막에 완료된다. 앞서 말했던 기아 현상이 발생한 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;Round Robin (RR) scheduling&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;세 번째 방식은 각 프로세스가 언제 끝나는지보다 언제 시작되는지를 중요하게 여기는 방식이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;각 프로세스가 동일한 할당 시간(Time quantum)을 갖고 실행되며, 할당 시간이 끝나면 다시 Ready queue의 뒤로 가서 줄을 선다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이미지로 보면 이해가 바로 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;616&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cwnTt6/btsjmBnxbvI/fCXD8LnwhQSgz0n1fmmT4k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cwnTt6/btsjmBnxbvI/fCXD8LnwhQSgz0n1fmmT4k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cwnTt6/btsjmBnxbvI/fCXD8LnwhQSgz0n1fmmT4k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcwnTt6%2FbtsjmBnxbvI%2FfCXD8LnwhQSgz0n1fmmT4k%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;600&quot; height=&quot;289&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;616&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;도착 순서대로 할당 순서를 돌려가며 큐에 다시 넣기 때문에 기아 현상이 발생하지 않으며, 응답 속도가 빠르다. 다만 평균 소요 시간(Average Turnaround Time)은 길어지는데, 무슨 말인가 하니 P1는 13에, P2는 14에, P3은 15에 끝나는 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;FCFS(FIFO) 혹은 SJF 방식이라면 각각 5,10,15 시점에 끝날 동작이 평균적으로 엄청 늦게 끝나게 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 문제는 할당 시간(프로세스를 쪼개는 시간 단위)을 늘려서 보완할 수 있으나 할당 시간의 크기가 커질수록 FCFS 방식과의 차이점이 사라지고, 할당 시간의 단위가 작아질수록 컨텍스트 스위칭의 오버헤드가 커지기 때문에 적절한(?) 할당 시간 배분이 매우 중요해진다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;Priority Scheduling&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;특정 기준으로 프로세스에 우선순위를 부여하고, 우선순위가 높은 프로세스를 CPU 할당하는 방식이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;역시 우선순위가 낮은 프로세스는 계속 기다리기만 하는 기아 문제가 생길 수 있는데, 이를 방지하기 위해 &lt;b&gt;&lt;span data-token-index=&quot;1&quot;&gt;오래 대기한 프로세스일수록 점점 우선순위를 올려주는 에이징 기법&lt;/span&gt;&lt;/b&gt;을 사용한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;에이징 기법은 백로그에 쌓인 우선순위 Low 태스크들이 쌓이는 일을 방지하기 위해 적용해 보면 좋은 방법이지 않을까...?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;독자적인 스케줄링 방식보다는 다른 스케줄링 알고리즘과 결합하여 사용하는 경우가 많다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span data-token-index=&quot;0&quot;&gt;Multi-Level Queue Scheduling&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Multi-Level Queue는 Ready Queue를 여러 개로 분할한 것이다. 각 큐는 독립적인 스케줄링 알고리즘을 가진다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어, foreground에서 돌아가는 프로그램(메모장 + 그림판)은 응답 시간이 중요하기 때문에 Round Robin 스케줄링으로 동작시키고, background에서 돌아가는 batch task(업데이트, 다운로드 등) 등의 프로그램은 총 소요시간이 중요하기 때문에 FCFS(FIFO) 스케쥴링을 사용하는 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1228&quot; data-origin-height=&quot;882&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/wGsYd/btsjmv8MaER/KkdjpfiKcyycia0u5X7i61/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/wGsYd/btsjmv8MaER/KkdjpfiKcyycia0u5X7i61/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/wGsYd/btsjmv8MaER/KkdjpfiKcyycia0u5X7i61/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FwGsYd%2Fbtsjmv8MaER%2FKkdjpfiKcyycia0u5X7i61%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;600&quot; height=&quot;431&quot; data-origin-width=&quot;1228&quot; data-origin-height=&quot;882&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;당연한 말이지만 이렇게 만들어진 각 큐에 대해서도 스케줄링이 필요하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;유저가 당장 상호작용하는 foreground 큐의 우선순위를 항상 먼저 둘 수도 있고(이 경우 background 태스크에 대한 기아 현상 발생), Time Slice 방식을 통해 80%는 foreground, 20%는 background에 할당할 수도 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;프로세스 스케줄러를 보면서 얻은 인사이트&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자바스크립트는 단일 스레드이다.(자바스크립트 런타임을 총괄한다면 멀티 쓰레드이지만 콜 스택 자체만 놓고 본다는 전제 하에)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;React에서는 자바스크립트를 힘들게 쓰는 동작이 있는데, 바로 가상 돔(VDOM)의 diff를 계산하는 작업이다. React 화면 렌더링의 핵심 개념이며 모든 리액트 개발자들은 이 diff를 계산하는 한 사이클이 16ms를 넘지 않게 하기 위해 렌더링 최적화를 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;diff를 계산하는 로직이 무거워져 16ms를 넘기는 순간, 60 프레임을 보장할 수 없으며 브라우저에서 유저에게 버벅거림을 야기하기 때문이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;무거운 프로젝트일수록 이러한 버벅거림이 심해질 위험성도 늘어나고, 개발자들이 신경 써야 하는 포인트도 늘어난다. React에서도 이러한 문제점을 인지하고 있고, 그래서 React18에서는 내부 구현체를 거의 새로 개발하는 수준의 변경사항을 통해 diff 비교에 사용되는 알고리즘을 스케줄링 방식으로 바꿨다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;변경이 즉시 필요하지 않은 일부 상태 변경에 대해서는 별도의 훅(useTransition, useDeferredValue)을 사용해 업데이트에 들어가는 계산을 마치 우선순위 스케줄링 방식과 같이 미룰 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;react18의 소스코드 변경사항 중 가장 큰 변경사항은 기존에 없던 Lane이라는 개념이 추가된 것인데, 이 Lane은 실제 차선을 예시로 들어 만들어진 개념이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;720&quot; data-origin-height=&quot;533&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/c3CINZ/btsjnbWvTqP/Cfjcw9xekFAyqHG7Nuaqh1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/c3CINZ/btsjnbWvTqP/Cfjcw9xekFAyqHG7Nuaqh1/img.png&quot; data-alt=&quot;(1차선은 추월차선으로 제일 빠른 속도이며, 2,3차선 순으로 속도가 느려진다.)&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/c3CINZ/btsjnbWvTqP/Cfjcw9xekFAyqHG7Nuaqh1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fc3CINZ%2FbtsjnbWvTqP%2FCfjcw9xekFAyqHG7Nuaqh1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;700&quot; height=&quot;518&quot; data-origin-width=&quot;720&quot; data-origin-height=&quot;533&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;(1차선은 추월차선으로 제일 빠른 속도이며, 2,3차선 순으로 속도가 느려진다.)&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;리액트는 몇 개의 Lane을 가지고 있을까?&lt;/p&gt;
&lt;pre id=&quot;code_1686474035138&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;export const TotalLanes = 31;

export const NoLanes: Lanes = /*                        */ 0b0000000000000000000000000000000;
export const NoLane: Lane = /*                          */ 0b0000000000000000000000000000000;

export const SyncHydrationLane: Lane = /*               */ 0b0000000000000000000000000000001;
export const SyncLane: Lane = /*                        */ 0b0000000000000000000000000000010;

export const InputContinuousHydrationLane: Lane = /*    */ 0b0000000000000000000000000000100;
export const InputContinuousLane: Lane = /*             */ 0b0000000000000000000000000001000;

export const DefaultHydrationLane: Lane = /*            */ 0b0000000000000000000000000010000;
export const DefaultLane: Lane = /*                     */ 0b0000000000000000000000000100000;

export const SyncUpdateLanes: Lane = /*                */ 0b0000000000000000000000000101010;

const TransitionHydrationLane: Lane = /*                */ 0b0000000000000000000000001000000;
const TransitionLanes: Lanes = /*                       */ 0b0000000011111111111111110000000;
const TransitionLane1: Lane = /*                        */ 0b0000000000000000000000010000000;
const TransitionLane2: Lane = /*                        */ 0b0000000000000000000000100000000;
const TransitionLane3: Lane = /*                        */ 0b0000000000000000000001000000000;
const TransitionLane4: Lane = /*                        */ 0b0000000000000000000010000000000;
const TransitionLane5: Lane = /*                        */ 0b0000000000000000000100000000000;
const TransitionLane6: Lane = /*                        */ 0b0000000000000000001000000000000;
const TransitionLane7: Lane = /*                        */ 0b0000000000000000010000000000000;
const TransitionLane8: Lane = /*                        */ 0b0000000000000000100000000000000;
const TransitionLane9: Lane = /*                        */ 0b0000000000000001000000000000000;
const TransitionLane10: Lane = /*                       */ 0b0000000000000010000000000000000;
const TransitionLane11: Lane = /*                       */ 0b0000000000000100000000000000000;
const TransitionLane12: Lane = /*                       */ 0b0000000000001000000000000000000;
const TransitionLane13: Lane = /*                       */ 0b0000000000010000000000000000000;
const TransitionLane14: Lane = /*                       */ 0b0000000000100000000000000000000;
const TransitionLane15: Lane = /*                       */ 0b0000000001000000000000000000000;
const TransitionLane16: Lane = /*                       */ 0b0000000010000000000000000000000;

const RetryLanes: Lanes = /*                            */ 0b0000111100000000000000000000000;
const RetryLane1: Lane = /*                             */ 0b0000000100000000000000000000000;
const RetryLane2: Lane = /*                             */ 0b0000001000000000000000000000000;
const RetryLane3: Lane = /*                             */ 0b0000010000000000000000000000000;
const RetryLane4: Lane = /*                             */ 0b0000100000000000000000000000000;

export const SomeRetryLane: Lane = RetryLane1;

export const SelectiveHydrationLane: Lane = /*          */ 0b0001000000000000000000000000000;

const NonIdleLanes: Lanes = /*                          */ 0b0001111111111111111111111111111;

export const IdleHydrationLane: Lane = /*               */ 0b0010000000000000000000000000000;
export const IdleLane: Lane = /*                        */ 0b0100000000000000000000000000000;

export const OffscreenLane: Lane = /*&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;리액트 내부에는 총 31개의 Lane을 가지고 있으며 각 Lane은 비트 연산을 통해 실제 로직에서 활용된다.&lt;/p&gt;
&lt;pre id=&quot;code_1686474050191&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;export function getHighestPriorityLane(lanes: Lanes): Lane {
  return lanes &amp;amp; -lanes;
}

export function pickArbitraryLane(lanes: Lanes): Lane {
  return getHighestPriorityLane(lanes);
}

function pickArbitraryLaneIndex(lanes: Lanes) {
  return 31 - clz32(lanes);
}

function laneToIndex(lane: Lane) {
  return pickArbitraryLaneIndex(lane);
}

export function includesSomeLane(a: Lanes | Lane, b: Lanes | Lane): boolean {
  return (a &amp;amp; b) !== NoLanes;
}

export function isSubsetOfLanes(set: Lanes, subset: Lanes | Lane): boolean {
  return (set &amp;amp; subset) === subset;
}

export function mergeLanes(a: Lanes | Lane, b: Lanes | Lane): Lanes {
  return a | b;
}

export function removeLanes(set: Lanes, subset: Lanes | Lane): Lanes {
  return set &amp;amp; ~subset;
}

export function intersectLanes(a: Lanes | Lane, b: Lanes | Lane): Lanes {
  return a &amp;amp; b;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;useTransition 등의 훅을 활용해서 특정 동작을 동시성 렌더링(Concurrency Feature Render)으로 바꾸게 된다면, 해당 동작은 TransitionLane으로 들어가게 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;리액트의 내부 구현체에서 렌더링을 하며 TransitionLane은 늘 SyncLane 내부에 스케줄링된 업데이트 동작이 끝난 이후에 실행되게 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사실 React18 구현에 대한 코드를 예전에 본 적이 있는데, Lane 개념에 대해서 와닿지 않는 부분이 있었다. 그런데 프로세스 스케줄링에 대해 학습을 하는 과정에서 React 개발자들이 어떤 콘셉트에서 영감을 받았는지 짐작할 수 있었다. 각 Lane은 결국 이름만 다른 하나의 Queue로, Multi-Level Queue Scheduling에서 영감을 받아 동시성 기능을 구현했다고 생각한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;참조&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://johngrib.github.io/wiki/jargon/convoy-effect/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://johngrib.github.io/wiki/jargon/convoy-effect/&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1686474100961&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;Convoy effect&quot; data-og-description=&quot;수송대 효과, 호위 효과&quot; data-og-host=&quot;johngrib.github.io&quot; data-og-source-url=&quot;https://johngrib.github.io/wiki/jargon/convoy-effect/&quot; data-og-url=&quot;https://johngrib.github.io/wiki/jargon/convoy-effect/&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/TgBkm/hySWlYnczn/xyWbBL85IKGO3C8ndH71z1/img.png?width=170&amp;amp;height=170&amp;amp;face=0_0_170_170,https://scrap.kakaocdn.net/dn/bWI09O/hySWlqvOOS/hCW9dmqC8ulWtH3CdBaDcK/img.png?width=1562&amp;amp;height=596&amp;amp;face=0_0_1562_596&quot;&gt;&lt;a href=&quot;https://johngrib.github.io/wiki/jargon/convoy-effect/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://johngrib.github.io/wiki/jargon/convoy-effect/&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/TgBkm/hySWlYnczn/xyWbBL85IKGO3C8ndH71z1/img.png?width=170&amp;amp;height=170&amp;amp;face=0_0_170_170,https://scrap.kakaocdn.net/dn/bWI09O/hySWlqvOOS/hCW9dmqC8ulWtH3CdBaDcK/img.png?width=1562&amp;amp;height=596&amp;amp;face=0_0_1562_596');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;Convoy effect&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;수송대 효과, 호위 효과&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;johngrib.github.io&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.tutorialspoint.com/operating_system/os_process_scheduling.htm&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://www.tutorialspoint.com/operating_system/os_process_scheduling.htm&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1686474100420&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;Operating System - Process Scheduling&quot; data-og-description=&quot;Operating System Process Scheduling - This tutorial covers concepts like overview of Operating System, Types, Services, Properties, Process Scheduling, CPU Scheduling algorithms, Deadlock, Multi-Threading, Memory Management, I/O, Disk Management, Interrupt&quot; data-og-host=&quot;www.tutorialspoint.com&quot; data-og-source-url=&quot;https://www.tutorialspoint.com/operating_system/os_process_scheduling.htm&quot; data-og-url=&quot;https://www.tutorialspoint.com/operating_system/os_process_scheduling.htm&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/gIbdz/hySXdZk0vH/Ak78ehWIyIcL9297otyxtK/img.jpg?width=385&amp;amp;height=590&amp;amp;face=0_0_385_590,https://scrap.kakaocdn.net/dn/b70PcZ/hySWmbTxBZ/LpfTFupqQYMK0MYSWVNOYK/img.jpg?width=491&amp;amp;height=276&amp;amp;face=0_0_491_276&quot;&gt;&lt;a href=&quot;https://www.tutorialspoint.com/operating_system/os_process_scheduling.htm&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://www.tutorialspoint.com/operating_system/os_process_scheduling.htm&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/gIbdz/hySXdZk0vH/Ak78ehWIyIcL9297otyxtK/img.jpg?width=385&amp;amp;height=590&amp;amp;face=0_0_385_590,https://scrap.kakaocdn.net/dn/b70PcZ/hySWmbTxBZ/LpfTFupqQYMK0MYSWVNOYK/img.jpg?width=491&amp;amp;height=276&amp;amp;face=0_0_491_276');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;Operating System - Process Scheduling&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Operating System Process Scheduling - This tutorial covers concepts like overview of Operating System, Types, Services, Properties, Process Scheduling, CPU Scheduling algorithms, Deadlock, Multi-Threading, Memory Management, I/O, Disk Management, Interrupt&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;www.tutorialspoint.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://zoomkoding.github.io/os/2019/04/28/os-5.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://zoomkoding.github.io/os/2019/04/28/os-5.html&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1686474093545&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;줌코딩의 코딩일기&quot; data-og-description=&quot;Zoom in Coding from the Basic.&quot; data-og-host=&quot;zoomkoding.github.io&quot; data-og-source-url=&quot;https://zoomkoding.github.io/os/2019/04/28/os-5.html&quot; data-og-url=&quot;https://zoomkoding.github.io&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/m4HRc/hySXb8hEj1/i4a6pSMzv1QfrppSwJzmHK/img.jpg?width=960&amp;amp;height=960&amp;amp;face=0_0_960_960&quot;&gt;&lt;a href=&quot;https://zoomkoding.github.io/os/2019/04/28/os-5.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://zoomkoding.github.io/os/2019/04/28/os-5.html&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/m4HRc/hySXb8hEj1/i4a6pSMzv1QfrppSwJzmHK/img.jpg?width=960&amp;amp;height=960&amp;amp;face=0_0_960_960');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;줌코딩의 코딩일기&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Zoom in Coding from the Basic.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;zoomkoding.github.io&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://rebro.kr/175&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://rebro.kr/175&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1686474086549&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;[운영체제(OS)] 5. 프로세스 스케줄링(Process Scheduling)&quot; data-og-description=&quot;[목차] 1. CPU Scheduling 2. Scheduling Criteria 3. Scheduling Algorithm 4. Multiple-Processor Scheduling 참고) - https://parksb.github.io/article/9.html - KOCW 공개강의 (2014-1. 이화여자대학교 - 반효경) - Sogang Univ. Operating System Lec&quot; data-og-host=&quot;rebro.kr&quot; data-og-source-url=&quot;https://rebro.kr/175&quot; data-og-url=&quot;https://rebro.kr/175&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/p1vWP/hySWmCXxmC/FNQj3tjnKNLPskamsTz7vK/img.png?width=800&amp;amp;height=537&amp;amp;face=0_0_800_537,https://scrap.kakaocdn.net/dn/ddzwHo/hySW5mICFK/ImHHoen7x30eNCpif6VBTk/img.png?width=800&amp;amp;height=537&amp;amp;face=0_0_800_537,https://scrap.kakaocdn.net/dn/QUwRP/hySWYA6AdM/LnWvU1nO7iltgGvt7jpCXK/img.png?width=1298&amp;amp;height=418&amp;amp;face=0_0_1298_418&quot;&gt;&lt;a href=&quot;https://rebro.kr/175&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://rebro.kr/175&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/p1vWP/hySWmCXxmC/FNQj3tjnKNLPskamsTz7vK/img.png?width=800&amp;amp;height=537&amp;amp;face=0_0_800_537,https://scrap.kakaocdn.net/dn/ddzwHo/hySW5mICFK/ImHHoen7x30eNCpif6VBTk/img.png?width=800&amp;amp;height=537&amp;amp;face=0_0_800_537,https://scrap.kakaocdn.net/dn/QUwRP/hySWYA6AdM/LnWvU1nO7iltgGvt7jpCXK/img.png?width=1298&amp;amp;height=418&amp;amp;face=0_0_1298_418');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;[운영체제(OS)] 5. 프로세스 스케줄링(Process Scheduling)&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;[목차] 1. CPU Scheduling 2. Scheduling Criteria 3. Scheduling Algorithm 4. Multiple-Processor Scheduling 참고) - https://parksb.github.io/article/9.html - KOCW 공개강의 (2014-1. 이화여자대학교 - 반효경) - Sogang Univ. Operating System Lec&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;rebro.kr&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>공부내용 공유하기</category>
      <category>Process</category>
      <category>Scheduling</category>
      <category>스케줄링</category>
      <category>프로세스</category>
      <category>프로세스 스케줄링</category>
      <author>눅눅한 피부</author>
      <guid isPermaLink="true">https://nookpi.tistory.com/172</guid>
      <comments>https://nookpi.tistory.com/172#entry172comment</comments>
      <pubDate>Sun, 11 Jun 2023 18:02:59 +0900</pubDate>
    </item>
    <item>
      <title>[디자인 패턴] 커맨드 패턴, 팩토리 메서드 패턴, 상태 패턴</title>
      <link>https://nookpi.tistory.com/171</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;커맨드 패턴&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;세 줄 개념&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;실행 기능 단위로 캡슐화 함으로써 여러 기능을 실행할 수 있는 재사용성 높은 클래스 설계&lt;/li&gt;
&lt;li&gt;기능 자체를 캡슐화 하기 때문에 Invoker와 Reciver간의 의존성 자체를 제거(기능 자체의 세부구현 변경이 Invoker와 Reciver의 수정 없이 이뤄질 수 있음)&lt;/li&gt;
&lt;li&gt;기능 자체를 다루는 로직이 필요할 때 사용(되돌리기, 히스토리 기록 등)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1484&quot; data-origin-height=&quot;778&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bKRTKa/btshG718mb2/O5Ezdgt1xzsJtyCOMjI2R0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bKRTKa/btshG718mb2/O5Ezdgt1xzsJtyCOMjI2R0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bKRTKa/btshG718mb2/O5Ezdgt1xzsJtyCOMjI2R0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbKRTKa%2FbtshG718mb2%2FO5Ezdgt1xzsJtyCOMjI2R0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;500&quot; height=&quot;262&quot; data-origin-width=&quot;1484&quot; data-origin-height=&quot;778&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;유용한 사례&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;실행 취소가 가능한 명령들을 다양하게 다뤄야 하는 텍스트 에디터&lt;/li&gt;
&lt;li&gt;보상 트랜잭션&lt;/li&gt;
&lt;li&gt;공유 리소스를 바탕으로 병렬 처리를 원할 때(ex. 커맨드 단위로 멀티 스레드에서 동작)&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;느낀 점&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;처음에 커맨드 패턴을 코드상에서 봤을 때 마치 side effect를 적극적으로 사용하는 패턴으로 보였음&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;excute()의 반환 타입이 실질적인 응답 데이터를 가지지 않는다는 점 + 매개변수를 넘기는 방식이 아니라는 점에서 그렇게 보았음&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러나 예제 코드를 자세히 보니 명령 자체를 캡슐화해서 그렇게 보이는 것 뿐이고, 매개변수 혹은 반환값에 대한 처리는 커맨드 객체 내부 구현 및 어플리케이션에서 커맨드를 바인딩 할 때 넘겨준다는 사실을 알았음&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://refactoring.guru/ko/design-patterns/command&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;예시코드&lt;/a&gt;(refactoring.guru)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;작성에 필요한 보일러 플레이트 코드가 있으며, 복잡성이 증가하는 단점은 있으나, 상기 커맨드 패턴이 유용한 사례에서 도입한다면 단점보다는 장점이 더 많은 좋은 패턴으로 보임&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다만 각 커맨드의 인터페이스 설계를 잘 하지 않을 경우 커맨드 기능에 필요한 인터페이스 외에 너무 많은 컨텍스트의 정보를 들고 있게 될지도? (ex. 커맨드 호출 시점에 권한(로그인 여부)등의 확인이 필요하다면? 로그인 여부를 확인할 수 있는 기능을 커맨드 초기화시 주입해줘야함)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://en.wikipedia.org/wiki/Command_pattern&quot;&gt;Command pattern&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://gmlwjd9405.github.io/2018/07/07/command-pattern.html&quot;&gt;[Design Pattern] 커맨드 패턴이란 - Heee's Development Blog&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://refactoring.guru/ko/design-patterns/command&quot;&gt;커맨드 패턴&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;팩토리 메소드 패턴&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;팩토리 패턴과의 차이점&lt;/h3&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;Simple Factory Pattern&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;단순히 인스턴스 생성 로직을 노출하지 않고 팩토리를 통해 객체 생성&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;710&quot; data-origin-height=&quot;243&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/mvt3u/btshz0iYU4B/Eq5f64gmHBwU18Wqx2U6q1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/mvt3u/btshz0iYU4B/Eq5f64gmHBwU18Wqx2U6q1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/mvt3u/btshz0iYU4B/Eq5f64gmHBwU18Wqx2U6q1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fmvt3u%2Fbtshz0iYU4B%2FEq5f64gmHBwU18Wqx2U6q1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;710&quot; height=&quot;243&quot; data-origin-width=&quot;710&quot; data-origin-height=&quot;243&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;pre id=&quot;code_1685264145926&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;SimpleFactory simpleFactory = new SimpleFactory();
IAnimal dog = simpleFactory.CreateDog(); // Create dog
IAnimal tiger = simpleFactory.CreateTiger(); // Create tiger&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;Factory Method Pattern&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;팩토리에 대한 인터페이스를 정의하고, 해당 인터페이스를 지킨 팩토리에서 어떤 인스턴스를 생성할지 결정(위임)&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;858&quot; data-origin-height=&quot;262&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b6c05V/btshE119H74/NCUgyxlal171qMnMgp68iK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b6c05V/btshE119H74/NCUgyxlal171qMnMgp68iK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b6c05V/btshE119H74/NCUgyxlal171qMnMgp68iK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb6c05V%2FbtshE119H74%2FNCUgyxlal171qMnMgp68iK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;858&quot; height=&quot;262&quot; data-origin-width=&quot;858&quot; data-origin-height=&quot;262&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;pre id=&quot;code_1685264184086&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;AnimalFactory dogFactory = new DogFactory(); 
IAnimal dog = dogFactory.CreateAnimal(); // Create dog

AnimalFactory tigerFactory = new TigerFactory();
IAnimal tiger = tigerFactory.CreateAnimal(); // Create tiger&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&amp;nbsp;&lt;/h3&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;조금 다른 예시&lt;/h3&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;Simple Factory Pattern&lt;/h4&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1003&quot; data-origin-height=&quot;365&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bLr08e/btshG8GHx6w/cudq9y1ZKxyLkabQIk8P91/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bLr08e/btshG8GHx6w/cudq9y1ZKxyLkabQIk8P91/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bLr08e/btshG8GHx6w/cudq9y1ZKxyLkabQIk8P91/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbLr08e%2FbtshG8GHx6w%2Fcudq9y1ZKxyLkabQIk8P91%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1003&quot; height=&quot;365&quot; data-origin-width=&quot;1003&quot; data-origin-height=&quot;365&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;Factory Method Pattern&lt;/h4&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1150&quot; data-origin-height=&quot;474&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dFGpDN/btshBk82db7/d9ABlyqkY2TK1okTlYQNEK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dFGpDN/btshBk82db7/d9ABlyqkY2TK1okTlYQNEK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dFGpDN/btshBk82db7/d9ABlyqkY2TK1okTlYQNEK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdFGpDN%2FbtshBk82db7%2Fd9ABlyqkY2TK1okTlYQNEK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1150&quot; height=&quot;474&quot; data-origin-width=&quot;1150&quot; data-origin-height=&quot;474&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Nokia Factory에 현재 클라이언트에서 사용하지 않는(호출되지 않는) 메소드가 추가될 경우&lt;/p&gt;
&lt;pre id=&quot;code_1685264253137&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class NokiaFactory {
    // old code
    public void Foo() { ... }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Simple Factory Pattern에서는 MobileFactory 구현과 클라이언트에 영향을 주지 않고 구현할 수 없음&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Factory Method Pattern에서는 NokiaFactory 구현만 바꾸면 클라이언트는 변경사항을 몰라도 됨&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://stackoverflow.com/questions/53372254/simple-factory-vs-factory-method&quot;&gt;Simple factory vs Factory method&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://stackoverflow.com/questions/13029261/design-patterns-factory-vs-factory-method-vs-abstract-factory&quot;&gt;Design Patterns: Factory vs Factory method vs Abstract Factory&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;상태 패턴&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;세 줄 개념&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;객체의 현재 (유한)상태에 따라 동작을 제한하여 복잡성을 낮추는 것&lt;/li&gt;
&lt;li&gt;전이(transition)를 통해 상태간의 변경을 일으킴&lt;/li&gt;
&lt;li&gt;유한 상태 기계(finite state machine)의 개념에 가까움&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;유용한 사례&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;명확하게 상태별로 가능한 action들이 나눠진 경우 각 상태별 관심사 분리&lt;/li&gt;
&lt;li&gt;조건문과 분기처리가 많은 복잡한 로직 단순화&lt;/li&gt;
&lt;li&gt;컨텍스트의 상태 폭발 제어&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;XState&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;XState로 js+ts 진영에서 간단하게 상태 패턴 사용 가능&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2264&quot; data-origin-height=&quot;1038&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b2nok1/btshE1A5ngm/zOyk8cmRX0vTnxD4Od0cRk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b2nok1/btshE1A5ngm/zOyk8cmRX0vTnxD4Od0cRk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b2nok1/btshE1A5ngm/zOyk8cmRX0vTnxD4Od0cRk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb2nok1%2FbtshE1A5ngm%2FzOyk8cmRX0vTnxD4Od0cRk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2264&quot; height=&quot;1038&quot; data-origin-width=&quot;2264&quot; data-origin-height=&quot;1038&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;pre id=&quot;code_1685264338545&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;createMachine({
  initial: &quot;waiting&quot;,
  states: {
    &quot;waiting&quot;: {
      on: {
        &quot;leave home&quot;: &quot;on a walk&quot;,
      },
    },
    &quot;on a walk&quot;: {
      initial: &quot;walking&quot;,
      states: {
        walking: {
          on: {
            &quot;speed up&quot;: &quot;running&quot;,
          },
        },
        running: {
          on: {
            &quot;slow down&quot;: &quot;walking&quot;,
          },
        },
      },
      on: {
        &quot;arrive home&quot;: &quot;walk complete&quot;,
      },
    },
    &quot;walk complete&quot;: {
      type: &quot;final&quot;,
    },
  });&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://stately.ai/docs/state-machines-and-statecharts&quot;&gt;What are state machines and statecharts? | Stately Docs&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://en.wikipedia.org/wiki/State_pattern&quot;&gt;State pattern&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://refactoring.guru/ko/design-patterns/state&quot;&gt;상태 패턴&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>공부내용 공유하기</category>
      <category>command</category>
      <category>design pattern</category>
      <category>Factory Method</category>
      <category>State Pattern</category>
      <author>눅눅한 피부</author>
      <guid isPermaLink="true">https://nookpi.tistory.com/171</guid>
      <comments>https://nookpi.tistory.com/171#entry171comment</comments>
      <pubDate>Sun, 28 May 2023 18:00:17 +0900</pubDate>
    </item>
    <item>
      <title>[디자인 패턴] 싱글톤, 데코레이터</title>
      <link>https://nookpi.tistory.com/170</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;싱글톤 패턴&lt;/span&gt;&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;세 줄 개념&lt;/span&gt;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;클래스의 인스턴스화를 단일 인스턴스로 제한하여 하나의 객체 사용을 강제하는 패턴&lt;/li&gt;
&lt;li&gt;두 개 이상의 인스턴스화를 방지하는 게 핵심 구현 방법&lt;/li&gt;
&lt;li&gt;메모리 사용에서의 이점 + 하나의 데이터 출처를 강제하는 용도로 사용&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;유용한 사례&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;Logger&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;많은 곳에서 사용되며 사이드 이펙트가 없고 인스턴스를 재활용했을 때의 효용이 높음&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;공유 리소스에 대한 동시 엑세스 제어&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;리소스에 대한 엑세스를 전역에서 요구&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;하나의 객체만 존재해야 하는 경우&lt;/span&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;위 세 개의 조건을 모두 만족하는 경우 &lt;b&gt;고려해 볼 수 있음&lt;/b&gt;(무조건 사용해야 하는것이 아님)&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;문제점&lt;/span&gt;&lt;/h3&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;싱글톤은 SRP를 위반한다&lt;/b&gt; &lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;싱글톤 객체는 자기 자신의 인스턴스를 감시하는 책임 + 스스로의 구성정보를 제공하는 책임. 두 개의 책임을 가지고 있는 객체&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;메모리 관리에 좋지 않다&lt;/b&gt; &lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;인스턴스를 하나로 제한하기 때문에 메모리 관리에 효율적일 수 있으나, 전역 객체로 유지되기 때문에 아무 일도 하지 않는 상황에서도 여전히 메모리를 점유하고 있음&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;싱글턴 내부의 데이터는 곧 전역상태&lt;/b&gt; &lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;전역 상태의 사이드 이펙트로 인해 유닛 테스트의 난이도가 올라가고, 소프트웨어의 복잡성을 늘림&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;싱글톤은 OCP를 위반한다&lt;/b&gt; &lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;전역 함수로 이루어져 서브 클래스 확장을 통한 추가 기능 구현이 어려움. 기능 수정에 따른 사이드 이펙트가 전역으로 전파&lt;/span&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;해결법&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;단일 인스턴스로 사용하고 싶은 객체는 그대로 일반 객체로 선언&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;해당 객체를 생성하는 팩토리를 선언해서 SRP를 지키도록 만들 수 있음&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1685263150856&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class House {
  private final Kitchen kitchen;
  private final Bedroom bedroom;
  private boolean isLocked;
 
   public House(Kitchen kitchen, Bedroom bedroom) {
    this.kitchen = kitchen;
    this.bedroom = bedroom;
  }
 
  private boolean isLocked() {
    return isLocked;
  }
 
  private boolean lock() {
    kitchen.lock();
    isLocked = true;
  }
}

class HouseFactory {
  House build() {
 
     Sink sink = new Sink;
     Dishwasher dishwasher = new Dishwasher;
     Refrigerator refrigerator = new Refrigerator;
     Kitchen kitchen = new Kitchen(sink, dishwasher, refrigerator);
 
		 Bed bed = new Bed;
     Dresser dresser = new Dresser;
     Bedroom bedroom = new Bedroom(bed, dresser);
 
		 House house = new House(kitchen, bedroom);
 
     return house;
  }
}
 
class Main {
  public static void(String...args) {
    House house = new HouseFactory().build();
    house.lock();
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;a href=&quot;https://web.archive.org/web/20201022020333/http://geekswithblogs.net/AngelEyes/archive/2013/09/08/singleton-i-love-you-but-youre-bringing-me-down-re-uploaded.aspx&quot;&gt;Singleton I love you, but you're bringing me down (re-uploaded)&lt;/a&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;a href=&quot;http://misko.hevery.com/2008/08/25/root-cause-of-singletons/&quot;&gt;Root Cause of Singletons&lt;/a&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;데코레이터 패턴&lt;/span&gt;&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;세 줄 개념&lt;/span&gt;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;클래스의 다른 객체의 동작에 영향을 주지 않고 동적으로 개별 객체에 원하는 동작을 하게끔 추가하는 디자인 패턴&lt;/li&gt;
&lt;li&gt;상속을 통한 클래스 확장의 대안으로 컴파일 타임이 아닌 런타임에 새로운 동작 추가 가능&lt;/li&gt;
&lt;li&gt;객체에 동적으로 추가적인 책임을 부여하기 위해 사용&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;유용한 사례&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;런타임 시점에 기존 객체의 동작을 변경하거나 확장하는 경우&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;기존 코드를 훼손하지 않으면서 런타임에 추가 행동을 할당해야 하는 경우&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;상속을 사용해서 객체의 행동을 확장하는 것이 어색하거나 불편한 경우&lt;/span&gt;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;단점&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;데코레이터 선언을 위한 보일러 플레이트 코드 증가&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;데코레이터 행위가 다른 데코레이터의 동작에 영향을 받지 않도록 구현하기가 어려움 (데코레이터 스택 내의 순서에 의존하지 않게 구현해야 함)&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;의문점&lt;/span&gt;&lt;/h3&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;decorator pattern 과 hoc의 차이점?&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;데코레이터 패턴이 하는 일을 들여다 보면, 리액트에서 자주 사용하는 HOC(Higher-Order Component)와 개념이 거의 동일해 보임&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;찾아보니 동일한 의문을 가진 stackoverflow 질문이 있었고, 동일한 개념이라는 의견 존재&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;다만, 타입스크립트의 &lt;b&gt;데코레이터&lt;/b&gt;&amp;nbsp;와&amp;nbsp;&lt;b&gt;데코레이터&amp;nbsp;패턴&lt;/b&gt;&amp;nbsp;은&amp;nbsp;다른&amp;nbsp;개념을&amp;nbsp;나타내는&amp;nbsp;용어라는&amp;nbsp;점에&amp;nbsp;주의&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;함수형 프로그래밍에서의 함수 합성 개념과 정확하게 일치하는 것으로 보이며, 데코레이터 Wrapper들의 평가 및 실행 순서도 함수의 합성과 동일&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;함수 합성&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1685263461450&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;interface Sum {
  (num: number): number;
}

const sum2: Sum = (num) =&amp;gt; num + 2;
const sum3: Sum = (num) =&amp;gt; num + 3;
const sum5: Sum = (num) =&amp;gt; sum2(sum3(num));
const sum10: Sum = (num) =&amp;gt; sum5(sum5(num));&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;HOC(High Order Component)&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1685263486219&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;const withLabel = &amp;lt;P&amp;gt;(Component: ComponentType&amp;lt;P&amp;gt;) =&amp;gt; (props: P) =&amp;gt; {
    return &amp;lt;label&amp;gt;&amp;lt;Component {...props} /&amp;gt;&amp;lt;/label&amp;gt;;
};

const Button = ({ children }: ComponentPropsWithRef&amp;lt;'button'&amp;gt;) =&amp;gt; {
  return &amp;lt;button&amp;gt;{children}&amp;lt;/button&amp;gt;;
};


const ButtonWithLabel = withLabel(Button);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;styled-components 내부 구현도 데코레이터 패턴을 통해 구현된 게 아닐까? 하는 추측&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;(나중에 확인해보니 대략적인 줄기는 비슷했음)&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;추측성 내부 구현 의사코드&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1685263552334&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;const styled = {
  div: (templateStrings: TemplateStringsArray) =&amp;gt; {
    const [css] = templateStrings;
	const styleClassName = hash(css);
    const styleElement = document.createElement('style');
    
    styleElement.innerHTML = `
      .${styleClassName}{
        ${css}
      }
    `;
    
    document.head.appendChild(styleElement);

    return function &amp;lt;P extends ComponentPropsWithRef&amp;lt;'div'&amp;gt;&amp;gt;({ className, ...restProps }: P) {
      return (
        &amp;lt;div
          className={className ? `${className} ${styleClassName}` : styleClassName}
          {...restProps}
        /&amp;gt;
      );
    };
  },
};

// 실제 사용코드
const Container = styled.div`
  display: flex;
  margin: 3px;
`;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;a href=&quot;https://stackoverflow.com/questions/2707401/understand-the-decorator-pattern-with-a-real-world-example&quot;&gt;Understand the &quot;Decorator Pattern&quot; with a real world example&lt;/a&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;a href=&quot;https://refactoring.guru/ko/design-patterns/decorator&quot;&gt;데코레이터 패턴&lt;/a&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;a href=&quot;https://refactoring.guru/ko/design-patterns/decorator/typescript/example&quot;&gt;타입스크립트로 작성된 데코레이터 / 디자인 패턴들&lt;/a&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;a href=&quot;https://stackoverflow.com/questions/48686826/react-js-what-is-the-difference-betwen-hoc-and-decorator&quot;&gt;React js - What is the difference betwen HOC and decorator&lt;/a&gt;&lt;/span&gt;&lt;/p&gt;</description>
      <category>공부내용 공유하기</category>
      <category>Decorator</category>
      <category>design pattern</category>
      <category>singleton</category>
      <author>눅눅한 피부</author>
      <guid isPermaLink="true">https://nookpi.tistory.com/170</guid>
      <comments>https://nookpi.tistory.com/170#entry170comment</comments>
      <pubDate>Sun, 28 May 2023 17:46:53 +0900</pubDate>
    </item>
    <item>
      <title>DragGPT + Stream</title>
      <link>https://nookpi.tistory.com/169</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;DragGPT 익스텐션이 기본적으로 non-stream으로 동작하다 보니, 응답을 다 받기까지의 로딩 속도가 매우 답답하게 느껴지는 부분이 있었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;고쳐야지... 하고 매번 생각만 하며 게으름을 피우다 보니 이제야 작업을 하게 되었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기존에 DragGPT를 만들며 재미있게 개발했던 포인트 중 하나는 XState였는데, 사실 이번에 코드를 오랜만에 보는 만큼 XState가 낯설게 느껴지지 않을까 하는 걱정이 있었다. 그러나 실제 개발 단계에서는 걱정을 무색하게 만들 정도로 편하게 작업할 수 있었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기존 채팅 상태머신에 &lt;b&gt;receiving&lt;/b&gt; 상태를 하나 추가해서 작업하는 것만으로도 관심사를 많이 좁혀서 작업할 수 있었는데, state 패턴의 장점을 극한까지 느낀 것 같았다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2023-05-28 오후 5.15.14.png&quot; data-origin-width=&quot;1490&quot; data-origin-height=&quot;738&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bDNVwO/btshz0XA8T6/0Sb2iPRzNwg32ZRhhyrRHk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bDNVwO/btshz0XA8T6/0Sb2iPRzNwg32ZRhhyrRHk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bDNVwO/btshz0XA8T6/0Sb2iPRzNwg32ZRhhyrRHk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbDNVwO%2Fbtshz0XA8T6%2F0Sb2iPRzNwg32ZRhhyrRHk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1490&quot; height=&quot;738&quot; data-filename=&quot;스크린샷 2023-05-28 오후 5.15.14.png&quot; data-origin-width=&quot;1490&quot; data-origin-height=&quot;738&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2023-05-28 오후 5.14.00.png&quot; data-origin-width=&quot;1508&quot; data-origin-height=&quot;576&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bzNasV/btshAAEygFa/J6LIrpG34dZlwtCDefVIR1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bzNasV/btshAAEygFa/J6LIrpG34dZlwtCDefVIR1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bzNasV/btshAAEygFa/J6LIrpG34dZlwtCDefVIR1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbzNasV%2FbtshAAEygFa%2FJ6LIrpG34dZlwtCDefVIR1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1508&quot; height=&quot;576&quot; data-filename=&quot;스크린샷 2023-05-28 오후 5.14.00.png&quot; data-origin-width=&quot;1508&quot; data-origin-height=&quot;576&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;작업을 하면서 알게 된 사실인데, openai chat completion api에는 steam 모드에서 요청을 취소하는 기능이 없었다;;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;물론 클라이언트 입장에서는 chunk 단위로 유저의 stop 지점에서 끊어서 저장하는 등의 작업을 할 수는 있었으나, api 서버 컴퓨팅 자원의 불필요한 사용을 막기 위해 중단 요청을 보낸다거나 하는 기능은 끝내 찾지 못했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그 밖에도 chrome.port를 사용한 에러 전달 과정에서 겪은 이상한 이슈(?)도 있긴 한데 아직 파악이 되지 않아 어떻게 공유를 해야 할지 모르겠다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;뭐 어쨌든... 미뤄왔던 숙원 사업을 하고 나니 이번 연휴를 너무 무익하게 보내는 것 같지는 않아서 기분이 좋다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;600&quot; data-origin-height=&quot;329&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/EhOaH/btshzBjuRyz/U8oFl9KghsgmVaS9glYF2k/img.gif&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/EhOaH/btshzBjuRyz/U8oFl9KghsgmVaS9glYF2k/img.gif&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/EhOaH/btshzBjuRyz/U8oFl9KghsgmVaS9glYF2k/img.gif&quot; srcset=&quot;https://blog.kakaocdn.net/dn/EhOaH/btshzBjuRyz/U8oFl9KghsgmVaS9glYF2k/img.gif&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;600&quot; height=&quot;329&quot; data-origin-width=&quot;600&quot; data-origin-height=&quot;329&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://nookpi.tistory.com/166&quot;&gt;https://nookpi.tistory.com/166&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1685262612462&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;DragGPT chrome extension 개발기&quot; data-og-description=&quot;배경 최근 ChatGPT 열풍이 거세다. 연구 영역에 있던 생성 AI의 위치를 대중에 가깝게 옮기면서 대중들로 하여금 급격한 기술 발달이 이뤄지고 특이점이 왔다(?)는 생각을 하게 만드는 것 같다. 기&quot; data-og-host=&quot;nookpi.tistory.com&quot; data-og-source-url=&quot;https://nookpi.tistory.com/166&quot; data-og-url=&quot;https://nookpi.tistory.com/166&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/WgT5S/hySLFigbaw/b7QFzgDtKed8KWRisGcwQk/img.png?width=528&amp;amp;height=468&amp;amp;face=0_0_528_468,https://scrap.kakaocdn.net/dn/cuG9wb/hySLClwm4m/NK6couX9U9USgu1tcCFYHK/img.png?width=528&amp;amp;height=468&amp;amp;face=0_0_528_468,https://scrap.kakaocdn.net/dn/qD02h/hySNh07RBl/t8YZ9PkZB5YnGGUdlr6Ey1/img.jpg?width=550&amp;amp;height=743&amp;amp;face=0_0_550_743&quot;&gt;&lt;a href=&quot;https://nookpi.tistory.com/166&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://nookpi.tistory.com/166&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/WgT5S/hySLFigbaw/b7QFzgDtKed8KWRisGcwQk/img.png?width=528&amp;amp;height=468&amp;amp;face=0_0_528_468,https://scrap.kakaocdn.net/dn/cuG9wb/hySLClwm4m/NK6couX9U9USgu1tcCFYHK/img.png?width=528&amp;amp;height=468&amp;amp;face=0_0_528_468,https://scrap.kakaocdn.net/dn/qD02h/hySNh07RBl/t8YZ9PkZB5YnGGUdlr6Ey1/img.jpg?width=550&amp;amp;height=743&amp;amp;face=0_0_550_743');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;DragGPT chrome extension 개발기&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;배경 최근 ChatGPT 열풍이 거세다. 연구 영역에 있던 생성 AI의 위치를 대중에 가깝게 옮기면서 대중들로 하여금 급격한 기술 발달이 이뤄지고 특이점이 왔다(?)는 생각을 하게 만드는 것 같다. 기&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;nookpi.tistory.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>취미로 하는 개발</category>
      <category>ChatGPT</category>
      <category>chrome extension</category>
      <category>DragGPT</category>
      <category>OpenAI</category>
      <author>눅눅한 피부</author>
      <guid isPermaLink="true">https://nookpi.tistory.com/169</guid>
      <comments>https://nookpi.tistory.com/169#entry169comment</comments>
      <pubDate>Sun, 28 May 2023 17:19:09 +0900</pubDate>
    </item>
    <item>
      <title>많이 늦은 2022회고</title>
      <link>https://nookpi.tistory.com/168</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1000&quot; data-origin-height=&quot;667&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/1UutS/btsdsRaSheg/tPSaUrGdWmSBD8xE9GiBsk/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/1UutS/btsdsRaSheg/tPSaUrGdWmSBD8xE9GiBsk/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/1UutS/btsdsRaSheg/tPSaUrGdWmSBD8xE9GiBsk/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F1UutS%2FbtsdsRaSheg%2FtPSaUrGdWmSBD8xE9GiBsk%2Fimg.jpg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1000&quot; height=&quot;667&quot; data-origin-width=&quot;1000&quot; data-origin-height=&quot;667&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;연 초에 회고를 6시간 동안 쓰다가 날려먹은 이후... 회고를 다시 쓸 엄두가 나지 않아 하염없이 미루고 있었다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;문득 이러다가 2년치 회고를 쓰겠다는 생각이 들어 빠르게 압축본으로 적어보려고 한다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;상반기&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;재작년 말에 이직을 한 후, 작년 상반기에는 새로운 조직에 적응하며 배우는 게 참 많았다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;개발팀의 학습 문화나 코드 리뷰 문화에서 얻은 것들을 체화하고, 그 과정에서 사용하지 않았던 근육을 사용하는 듯한 고통과 짜릿함을 동시에 느꼈던 시기였다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;테스트 코드&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;React 유닛테스트 코드 작성을 시작하면서 테스트란 무엇이고 왜 해야 하는지, 그리고 유지보수 가능한 테스트 코드 작성을 위해 무엇을 고려해야 하는지에 대한 고민과 인사이트를 많이 얻은 시기이다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR'; color: #456771;&quot;&gt;역설적으로 테스트 코드를 작성하다가 잘못된 컴포넌트 설계와 구현에 대해 깨닫는 경험이 정말 많았다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;지금 와서 돌이켜보면 그 당시 내가 한 고민들과 그로 인해 얻은 것들이 비단 테스트 코드 작성에만 적용되는 것들이 아니었다는 점에서 아주 값진 시간을 보낸 셈이다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;회사&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;a href=&quot;https://nookpi.tistory.com/136&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://nookpi.tistory.com/136&lt;/a&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;이러한 개발팀의 문화에 매력을 느껴 블로그에 '내가 우리 팀을 좋아하는 이유'라는 글을 남겼는데, 이 글을 보고 왔다고 하셨던 분들이 면접 시에 많아 정말 &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;부끄러웠지만 한 편으로는 좋았다. 아무리 좋은 문화와 내실을 가져도 알리지 않으면 아무도 모르지 않겠는가...?&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start; font-family: 'Noto Serif KR';&quot;&gt;어학당 서비스를 런칭하고 어학당 담당자가 되어 유지보수와 기능 고도화를 담당했다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;회사는 연초부터 스쿼드 제체를 통해 도메인별 커뮤니케이션 비용을 줄이고 목적조직으로의 개편을 시도했다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;성공했는지는...? 아직도 잘 모르겠다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start; font-family: 'Noto Serif KR';&quot;&gt;오픈소스&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start; font-family: 'Noto Serif KR';&quot;&gt;&lt;a href=&quot;https://nookpi.tistory.com/140&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://nookpi.tistory.com/140&lt;/a&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start; font-family: 'Noto Serif KR';&quot;&gt;뭐라도 기여해보겠다는 포부를 늘 가지고 있었고, 운이 좋게 내가 자주 사용하는 라이브러리에 기여할 기회가 있었다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start; font-family: 'Noto Serif KR';&quot;&gt;이를 바탕으로 욕심을 좀 더 내서 평소 관심 있던 크롬 확장 프로그램을 vite로 개발할 수 있는 보일러 플레이트를 만들었는데 굉장히 재미있는 경험이었다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start; font-family: 'Noto Serif KR';&quot;&gt;특히 내가 만든 오픈소스 프로젝트에 PR혹은 issue로 기여해 주는 다른 개발자들, 그리고 issue 내에서 서로 답변하고 이슈를 해결해 주는 모습을 보는 기분이 참 각별했다. 모바일 게임의 자동사냥을 돌려둔 기분...?&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;하반기&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;스쿼드 조직으로 나뉘면서 업무 방식에 대해 혼란이 생겼고 전체적으로 정신이 없었다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;프론트 파트 내부적으로는 컨벤션을 정리하고 리소스를 효율적으로 사용하기 위해 업무에 자동화할 수 있는 것들을 많이 찾아냈던 시기이다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;오픈소스&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;꾸준히 레포에 올라오는 이슈를 해결해나갔는데, 생각보다 여간 번거로운 게 아니었다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;크롬 익스텐션에서 동작하는 짝퉁 HMR을 구현하기 위해 끙끙댔고, 생각한 대로 해내긴 했지만 지금도 반쪽짜리라고 생각한다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;사람들이 원하는건 많은데 내 역량으로는 개인 시간을 내서 해결하는데 확실히 한계가 있었다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;QA프로세스도 빈약해서 가끔은 a문제를 해결한 PR이 기존에 잘 되던 b에 문제를 낳는 경우도 있었다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;그래도 처음으로 오픈소스로 후원도 받고(30유로!) 이슈를 하나하나 줄여나가는 재미가 또 있었다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;은근히 Stars가 꾸준하게 올라서 그 부분도 동기부여가 되었던 것 같다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;JSConf 2022&amp;nbsp;&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;노들섬에서 열린 JSConf2022를 팀원들과 함께 참석했다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;jest preview를 만든 흥 비엣 응우옌의 세션을 들었는데, 두 달 만에 stars 1600개를 받았다고 자랑하길래 부러웠다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;운영이라던지 세션들의 퀄리티는 전반적으로 실망스러웠지만, 그중에도 재미있는 세션들이 있어서 만족했다. &lt;/span&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;회사 돈이 아니라 내 돈을 내고 참석했다면 속상했을지도...?&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;함수형 프로그래밍&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;a href=&quot;https://nookpi.tistory.com/149&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://nookpi.tistory.com/149&lt;/a&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;함수형 프로그래밍 패러다임에 꽂혀 프로덕트 내부에서 이런저런 시도들을 해봤다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;함수형 프로그래밍이 주려는 핵심 가치(action을 제어하고 data, calc를 적절하게 추출하여 소프트웨어의 안정성을 높이는 것)를 알게 된 좋은 계기였고, 실무에 적용하면서 잘 한 부분도 있었고 잘 못 한 부분도 있었던 것 같다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;실제로 코드를 pipe로 체이닝하고, 이터레이터에서 map을 돌리는 게 함수형이 아니라는 걸 알게 되어서 좋았다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;2022 총평&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;새로운 회사에 적응하고, 훌륭한 동료들에게 자극받아 공부하고 논쟁하며 또 한 번 크게 성장한 시기였다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;모든 환경과 프로덕트를 관통하는 정해진 프로그래밍 규칙 혹은 베스트 프랙티스 같은 것은 존재하지 않는다는 것을 알았다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;하지만 동시에 다양한 환경에 적용 가능한 일반적인 법칙과 철학이 있다는 점을 알게 되었고, 그것들을 체화시킨 이후에 작게는 내 코드 퀄리티가 어떻게 개선되는지를 느끼면서 또 굉장히 뿌듯했으며 크게는 일을 대하는 관점에서도 마찬가지의 법칙을 적용하려고 노력했던 것 같다.&lt;/span&gt;&lt;/p&gt;</description>
      <category>잡담</category>
      <category>2022회고</category>
      <author>눅눅한 피부</author>
      <guid isPermaLink="true">https://nookpi.tistory.com/168</guid>
      <comments>https://nookpi.tistory.com/168#entry168comment</comments>
      <pubDate>Sun, 30 Apr 2023 18:17:24 +0900</pubDate>
    </item>
    <item>
      <title>발전속도가 미쳤다</title>
      <link>https://nookpi.tistory.com/167</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;하루하루 AI관련 생태계의 확장과 새로운 기술에 대한 뉴스가 쏟아진다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라가기가 벅차다. 아니, 사실 못 따라간다...&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사내 콘텐츠를 바탕으로 한 챗봇을 만들어 보려고 주말 동안 PoC를 했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;스프린트에 정식으로 반영되어 이번 스프린트부터 타 스쿼드에서 시범적으로 작업에 들어간다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;콘텐츠 텍스트를 임베딩해서 vector store에 갱신하고, 유저 쿼리시 콘텍스트를 함께 제공하는&amp;nbsp; 방식이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;유저에게 꼭 필요한 정보들이 제대로 전달되지 않는 이슈가 있던 스쿼드에 먼저 도입된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;할 수 있는게 너무 많아서 질식할 것 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하나씩 꾸준히 놓치지 않고 따라가야지.... 너무 격변의 시기다&lt;/p&gt;</description>
      <category>잡담</category>
      <author>눅눅한 피부</author>
      <guid isPermaLink="true">https://nookpi.tistory.com/167</guid>
      <comments>https://nookpi.tistory.com/167#entry167comment</comments>
      <pubDate>Wed, 29 Mar 2023 00:55:51 +0900</pubDate>
    </item>
    <item>
      <title>DragGPT chrome extension 개발기</title>
      <link>https://nookpi.tistory.com/166</link>
      <description>&lt;h3 data-ke-size=&quot;size23&quot;&gt;배경&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;최근 ChatGPT 열풍이 거세다. 연구 영역에 있던 생성 AI의 위치를 대중에 가깝게 옮기면서 대중들로 하여금 급격한 기술 발달이 이뤄지고 특이점이 왔다(?)는 생각을 하게 만드는 것 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기존에 ChatGPT에게 회사 면접 질문을 몇 개 해봤다는 글도 올린 적이 있는데, ChatGPT의 이슈몰이와 함께 조회수가 꾸준히 올라가고 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://nookpi.tistory.com/156&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://nookpi.tistory.com/156&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1679128216520&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;ChatGPT에게 회사의 면접 질문 몇 개를 던져보았다&quot; data-og-description=&quot;질문과 답변을 번역기로 돌려서 어색한 부분이 다소 존재합니다 Q: 유지보수 가능한 코드란? A: 유지 관리 가능한 코드는 이해하고 수정하기 쉬운 코드입니다. 여기에는 일반적으로 깨끗하고 잘 &quot; data-og-host=&quot;nookpi.tistory.com&quot; data-og-source-url=&quot;https://nookpi.tistory.com/156&quot; data-og-url=&quot;https://nookpi.tistory.com/156&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/z3n0v/hyRY1q0ifx/zHGcb7mqb31kdAjHjIqke0/img.png?width=800&amp;amp;height=340&amp;amp;face=0_0_800_340,https://scrap.kakaocdn.net/dn/kKRWZ/hyRZeKDM2D/BEt0sBoOY7a9e6mmwVZhBK/img.png?width=800&amp;amp;height=340&amp;amp;face=0_0_800_340,https://scrap.kakaocdn.net/dn/cvBdwJ/hyRXDFhRAp/l8CZJ2lzXmUTvEpCfYeuJ1/img.png?width=1184&amp;amp;height=1916&amp;amp;face=0_0_1184_1916&quot;&gt;&lt;a href=&quot;https://nookpi.tistory.com/156&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://nookpi.tistory.com/156&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/z3n0v/hyRY1q0ifx/zHGcb7mqb31kdAjHjIqke0/img.png?width=800&amp;amp;height=340&amp;amp;face=0_0_800_340,https://scrap.kakaocdn.net/dn/kKRWZ/hyRZeKDM2D/BEt0sBoOY7a9e6mmwVZhBK/img.png?width=800&amp;amp;height=340&amp;amp;face=0_0_800_340,https://scrap.kakaocdn.net/dn/cvBdwJ/hyRXDFhRAp/l8CZJ2lzXmUTvEpCfYeuJ1/img.png?width=1184&amp;amp;height=1916&amp;amp;face=0_0_1184_1916');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;ChatGPT에게 회사의 면접 질문 몇 개를 던져보았다&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;질문과 답변을 번역기로 돌려서 어색한 부분이 다소 존재합니다 Q: 유지보수 가능한 코드란? A: 유지 관리 가능한 코드는 이해하고 수정하기 쉬운 코드입니다. 여기에는 일반적으로 깨끗하고 잘&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;nookpi.tistory.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;개인적으로는 ChatGPT를 위시한 텍스트/이미지 생성 AI들의 사용 자체에 익숙하고 또 능숙해져야만 하는 시대가 오고 있다고 생각한다.&amp;nbsp; 마치 2007년의 아이폰 공개, 2010년을 전후한 스마트폰 대중화와 같이 우리의 일상은 또다시 큰 변곡점을 만나게 될 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그런 맥락에서 회사 내 아직도 ChatGPT를 사용해본 경험이 없는 분들이 있다는 것이 아쉬웠다. 더 많은 팀원들이 AI리터러시가 중요해질 새 시대에 적응하길 바라는 마음이 있었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사내 슬랙에 ChatGPT 봇을 만들어서 배포한 이유도 그러한 마음에서였다. ChatGPT에 쓰이는 언어 모델에 요청하기 위한 api도 이미 공개가 되어있고, 호출 자체도 간단하기 때문에 후딱 만들어서 배포했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2023-03-18 오후 5.39.50.png&quot; data-origin-width=&quot;1188&quot; data-origin-height=&quot;1086&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bcnTZ0/btr4v877ICE/fnRixDPUd9Z9cxBrd9NJd1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bcnTZ0/btr4v877ICE/fnRixDPUd9Z9cxBrd9NJd1/img.png&quot; data-alt=&quot;그럴듯하게 설명해주는데 당연하지만 늘 정답은 아니다&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bcnTZ0/btr4v877ICE/fnRixDPUd9Z9cxBrd9NJd1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbcnTZ0%2Fbtr4v877ICE%2FfnRixDPUd9Z9cxBrd9NJd1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;500&quot; height=&quot;457&quot; data-filename=&quot;스크린샷 2023-03-18 오후 5.39.50.png&quot; data-origin-width=&quot;1188&quot; data-origin-height=&quot;1086&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;그럴듯하게 설명해주는데 당연하지만 늘 정답은 아니다&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2023-03-18 오후 5.41.41.png&quot; data-origin-width=&quot;1204&quot; data-origin-height=&quot;508&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dwqSNE/btr4uPBdj22/eGu78AZyLu54FvGphmQNNK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dwqSNE/btr4uPBdj22/eGu78AZyLu54FvGphmQNNK/img.png&quot; data-alt=&quot;에러 로깅 미들웨어에 붙여두면 이런 식으로 간단히 요약해줄 수 있지 않을까?&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dwqSNE/btr4uPBdj22/eGu78AZyLu54FvGphmQNNK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdwqSNE%2Fbtr4uPBdj22%2FeGu78AZyLu54FvGphmQNNK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;500&quot; height=&quot;211&quot; data-filename=&quot;스크린샷 2023-03-18 오후 5.41.41.png&quot; data-origin-width=&quot;1204&quot; data-origin-height=&quot;508&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;에러 로깅 미들웨어에 붙여두면 이런 식으로 간단히 요약해줄 수 있지 않을까?&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&amp;nbsp;&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;흥미&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ChatGPT에게 역할을 부여하고, 해당 역할에 맞게 행동해 달라고 했을 때 답변의 퀄리티나 방향성이 완전히 달라진다는 이야기를 들었다. 실제로 테스트를 해보니 단순 '번역해 줘'라는 요청보다 '이제부터 번역가처럼 행동하고 주어지는 텍스트를 맥락에 맞게 번역해 줘'라는 프롬프트가 더 나은 결과를 반환한다는 것을 알았다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;요약 전문가(?)의 역할을 부여하고 BBC 등 영어 기사의 내용을 요약했을 때 결과의 퀄리티가 아주 좋았다. 나에겐 이런 특정 프롬프트가 정말 유용하다는 생각이 들었는데 다른 사람들은 각자 유용한 프롬프트가 있겠지? 하는 생각이 들었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;좀 더 많은 사람들이 웹에서 쉽게 사용하기 위해서 크롬 익스텐션을 만들면 재밌겠다는 생각이 들어서 바로 착수했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다행히 나에겐 웹 익스텐션 개발을 위해 만들어둔 보일러 플레이트가 있었는데, &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;정작&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;보일러 플레이트는 만들어두고 아직까지 해당 보일러 플레이트를 활용해서 익스텐션을 만들어 게시하지 않았었다. 이번 기회에 보일러 플레이트도 한 번 제대로 써보고... 하는 마음도 있었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;구상&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;컨셉 자체는 간단했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. 사전에 어떤 요청을 할지 미리 정해둔다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. 텍스트를 드래그해서 요청하고 싶은 영역을 선택하고,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. 마우스 근처에 있는 버튼을 눌러서 요청!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;4. 결과는 바로 화면에서 확인&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;텍스트 선택 후 ChatGPT에게 사전 프롬프트 세팅에 따라 요청을 보내서 결과를 받는 POC를 만들었다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;0318180528277869.jpg&quot; data-origin-width=&quot;550&quot; data-origin-height=&quot;743&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/blCTEm/btr4Fnv7Cqt/rORQ4vqU4SSORbwkWjSAYk/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/blCTEm/btr4Fnv7Cqt/rORQ4vqU4SSORbwkWjSAYk/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/blCTEm/btr4Fnv7Cqt/rORQ4vqU4SSORbwkWjSAYk/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FblCTEm%2Fbtr4Fnv7Cqt%2FrORQ4vqU4SSORbwkWjSAYk%2Fimg.jpg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;550&quot; height=&quot;743&quot; data-filename=&quot;0318180528277869.jpg&quot; data-origin-width=&quot;550&quot; data-origin-height=&quot;743&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;0318180057661854.jpg&quot; data-origin-width=&quot;550&quot; data-origin-height=&quot;403&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cFew6x/btr4v9Txgsc/UgvlVARjG4s65CDFwD0c30/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cFew6x/btr4v9Txgsc/UgvlVARjG4s65CDFwD0c30/img.jpg&quot; data-alt=&quot;요기까지 만드는데 4시간 정도 소요되었다&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cFew6x/btr4v9Txgsc/UgvlVARjG4s65CDFwD0c30/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcFew6x%2Fbtr4v9Txgsc%2FUgvlVARjG4s65CDFwD0c30%2Fimg.jpg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;400&quot; height=&quot;293&quot; data-filename=&quot;0318180057661854.jpg&quot; data-origin-width=&quot;550&quot; data-origin-height=&quot;403&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;요기까지 만드는데 4시간 정도 소요되었다&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;직접 사용해 보니 뭔가 써먹을 수 있겠다는 확신이 들었다. 일단 나부터가 유용하게 쓰겠다는 생각이 들어 이후 며칠 동안 몇 가지 기능을 추가하였다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. 답변 결과에서 이어서 대화하는 기능 (ex. 답변을 한 줄로 요약해줘, 번역해 줘 등)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. 사전 프롬프트 없이 빠른 대화&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. 프롬프트 생성 기능&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;4. 다국어 지원&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;5. 기타 편의기능&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2023-03-18 오후 6.09.14.png&quot; data-origin-width=&quot;1380&quot; data-origin-height=&quot;854&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bHTYht/btr4w6PE0a7/QaRUK4OysgVdTHDGZxXrV0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bHTYht/btr4w6PE0a7/QaRUK4OysgVdTHDGZxXrV0/img.png&quot; data-alt=&quot;디자인은... 역량 부족으로 아예 포기했다&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bHTYht/btr4w6PE0a7/QaRUK4OysgVdTHDGZxXrV0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbHTYht%2Fbtr4w6PE0a7%2FQaRUK4OysgVdTHDGZxXrV0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;600&quot; height=&quot;371&quot; data-filename=&quot;스크린샷 2023-03-18 오후 6.09.14.png&quot; data-origin-width=&quot;1380&quot; data-origin-height=&quot;854&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;디자인은... 역량 부족으로 아예 포기했다&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;현재 크롬 익스텐션 웹스토어에 올려둔 상태이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;피드백은 언제든 환영!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;figure id=&quot;og_1679130197871&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;드래그 GPT - 드래그로 쉽게 AI를 시작해보세요!&quot; data-og-description=&quot;드래그 후 버튼 클릭만으로 간단하게 선택한 내용을 ChatGPT에게 물어보거나 요청할 수 있어요!&quot; data-og-host=&quot;chrome.google.com&quot; data-og-source-url=&quot;https://chrome.google.com/webstore/detail/%EB%93%9C%EB%9E%98%EA%B7%B8-gpt-%EB%93%9C%EB%9E%98%EA%B7%B8%EB%A1%9C-%EC%89%BD%EA%B2%8C-ai%EB%A5%BC-%EC%8B%9C%EC%9E%91%ED%95%B4%EB%B3%B4%EC%84%B8/akgdgnhlglhelinkmnmiakgccdkghjbh?hl=en&quot; data-og-url=&quot;https://chrome.google.com/webstore/detail/%EB%93%9C%EB%9E%98%EA%B7%B8-gpt-%EB%93%9C%EB%9E%98%EA%B7%B8%EB%A1%9C-%EC%89%BD%EA%B2%8C-ai%EB%A5%BC-%EC%8B%9C%EC%9E%91%ED%95%B4%EB%B3%B4%EC%84%B8/akgdgnhlglhelinkmnmiakgccdkghjbh&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/beuUEJ/hyRXA9Curc/oc5hmoWH6VD9PR5AE1QkM1/img.jpg?width=128&amp;amp;height=128&amp;amp;face=0_0_128_128&quot;&gt;&lt;a href=&quot;https://chrome.google.com/webstore/detail/%EB%93%9C%EB%9E%98%EA%B7%B8-gpt-%EB%93%9C%EB%9E%98%EA%B7%B8%EB%A1%9C-%EC%89%BD%EA%B2%8C-ai%EB%A5%BC-%EC%8B%9C%EC%9E%91%ED%95%B4%EB%B3%B4%EC%84%B8/akgdgnhlglhelinkmnmiakgccdkghjbh?hl=en&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://chrome.google.com/webstore/detail/%EB%93%9C%EB%9E%98%EA%B7%B8-gpt-%EB%93%9C%EB%9E%98%EA%B7%B8%EB%A1%9C-%EC%89%BD%EA%B2%8C-ai%EB%A5%BC-%EC%8B%9C%EC%9E%91%ED%95%B4%EB%B3%B4%EC%84%B8/akgdgnhlglhelinkmnmiakgccdkghjbh?hl=en&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/beuUEJ/hyRXA9Curc/oc5hmoWH6VD9PR5AE1QkM1/img.jpg?width=128&amp;amp;height=128&amp;amp;face=0_0_128_128');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;드래그 GPT - 드래그로 쉽게 AI를 시작해보세요!&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;드래그 후 버튼 클릭만으로 간단하게 선택한 내용을 ChatGPT에게 물어보거나 요청할 수 있어요!&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;chrome.google.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;개발 경험&lt;/h3&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;Boilerplate&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;보일러플레이트를 세팅하고 ChatGPT 요청을 보내서 받는데까지 순식간에 끝났다. 버전 0.1.0이 나오는 데 4시간도 채 걸리지 않았으니 나름 빠르게 개발한 셈이다. 물론 빠르게 작성하면서 곳곳에 날림 코드가 있어 이후 리팩토링에 시간을 쏟았고 테스트 코드도 보강했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;내가 만든 보일러플레이트라서 사용하기에 익숙한 부분도 있었고, 실제로 개발에 있어 불편함이 없게끔 많은 부분이 개선된, 나름 좋은 보일러플레이트라고 생각한다. hmr을 좀 야매로 구성해서 정말 큰 익스텐션을 만드는데 적합한지는 모르겠지만, 간단한 앱 정도 만드는 데에는 충분하다는 생각이 들었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;요 레포도 벌써 Star 400개 Fork 60개가 넘었는데, 이슈를 남겨주시는 분들과 대신 답변 해주시는 분들에게 늘 감사하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://github.com/Jonghakseo/chrome-extension-boilerplate-react-vite&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://github.com/Jonghakseo/chrome-extension-boilerplate-react-vite&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1679131139313&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;object&quot; data-og-title=&quot;GitHub - Jonghakseo/chrome-extension-boilerplate-react-vite: Chrome Extension Boilerplate with React + Vite + Typescript&quot; data-og-description=&quot;Chrome Extension Boilerplate with React + Vite + Typescript - GitHub - Jonghakseo/chrome-extension-boilerplate-react-vite: Chrome Extension Boilerplate with React + Vite + Typescript&quot; data-og-host=&quot;github.com&quot; data-og-source-url=&quot;https://github.com/Jonghakseo/chrome-extension-boilerplate-react-vite&quot; data-og-url=&quot;https://github.com/Jonghakseo/chrome-extension-boilerplate-react-vite&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/eZtzj/hyRY0ZW4Lx/WLkBjLIi4H83vOq7XGOeY0/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600&quot;&gt;&lt;a href=&quot;https://github.com/Jonghakseo/chrome-extension-boilerplate-react-vite&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://github.com/Jonghakseo/chrome-extension-boilerplate-react-vite&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/eZtzj/hyRY0ZW4Lx/WLkBjLIi4H83vOq7XGOeY0/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;GitHub - Jonghakseo/chrome-extension-boilerplate-react-vite: Chrome Extension Boilerplate with React + Vite + Typescript&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Chrome Extension Boilerplate with React + Vite + Typescript - GitHub - Jonghakseo/chrome-extension-boilerplate-react-vite: Chrome Extension Boilerplate with React + Vite + Typescript&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;github.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;XState&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;컨텐츠 스크립트 코드는 리액트로 구현되었는데, 상태에 따른 컨텍스트의 얽힘이 코드의 가독성을 매우 떨어뜨렸다. 코드 정리를 위해 어떤 패턴을 써볼까 고민하다가 이번 기회에 게임 개발에 많이 사용되는 유한상태기계(FSM) 패턴의 JS/TS 구현체인 XState를 사용했다. 결론부터 말하자면 굉장히 만족도가 높았다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://xstate.js.org/docs/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://xstate.js.org/docs/&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1679130839544&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;XState Docs&quot; data-og-description=&quot;JavaScript state machines and statecharts (opens new window) JavaScript and TypeScript finite state machines (opens new window) and statecharts (opens new window) for the modern web.   Read the documentation (opens new window)   Explore our catalogue&quot; data-og-host=&quot;xstate.js.org&quot; data-og-source-url=&quot;https://xstate.js.org/docs/&quot; data-og-url=&quot;https://xstate.js.org/docs/&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/zV1nI/hyRXrZb0eo/Hzm55kd2YaW0TXoHG7Ah10/img.png?width=1024&amp;amp;height=699&amp;amp;face=0_0_1024_699&quot;&gt;&lt;a href=&quot;https://xstate.js.org/docs/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://xstate.js.org/docs/&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/zV1nI/hyRXrZb0eo/Hzm55kd2YaW0TXoHG7Ah10/img.png?width=1024&amp;amp;height=699&amp;amp;face=0_0_1024_699');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;XState Docs&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;JavaScript state machines and statecharts (opens new window) JavaScript and TypeScript finite state machines (opens new window) and statecharts (opens new window) for the modern web.   Read the documentation (opens new window)   Explore our catalogue&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;xstate.js.org&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사용성이 너무 좋았고, 특히 시각화를 통한 Flow 검증이 신세계였다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2023-03-18 오후 6.14.50.png&quot; data-origin-width=&quot;1896&quot; data-origin-height=&quot;324&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bbBlg7/btr4DtXAGgK/VbJ3I1MkGLvCGGpdRY9l1K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bbBlg7/btr4DtXAGgK/VbJ3I1MkGLvCGGpdRY9l1K/img.png&quot; data-alt=&quot; contentScript 내부의 drag-state에 처음 사용해보았다.&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bbBlg7/btr4DtXAGgK/VbJ3I1MkGLvCGGpdRY9l1K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbbBlg7%2Fbtr4DtXAGgK%2FVbJ3I1MkGLvCGGpdRY9l1K%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1896&quot; height=&quot;324&quot; data-filename=&quot;스크린샷 2023-03-18 오후 6.14.50.png&quot; data-origin-width=&quot;1896&quot; data-origin-height=&quot;324&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt; contentScript 내부의 drag-state에 처음 사용해보았다.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;상태 간 이벤트를 통한 전이, 사용하기 편한 액션과 가드 덕분에 기존 코드가 깔끔하게 정리되었고, 유지보수에 대한 자신감도 가질 수 있었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번 기회에 유한상태머신(FSM) 이론에 대한 관심도 생겨서, 사내 세미나 순서를 새치기해서 FSM 관련 세미나를 할 예정이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;너무 딱딱한 이론보다는 사용 경험과 실제 코드에 접목했을 때의 관심사 분리에 대해 간단히 진행해보려고 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;다국어 지원&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;크롬 익스텐션에서 다국어 지원은 처음 해봤는데, 다른 i18n 관련 라이브러리등의 지원 방식과 크게 다르지 않아 쉽게 적용할 수 있었다. 크롬에서 익스텐션의 다국어 기능을 내장하고 있어(chrome.i18n) 별도의 의존성 추가는 필요하지 않았고, 공식문서도 잘 나와있었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;충격받았던 부분은 번역 그 자체였는데, 4개 국어(영어/중국어/일본어/한국어) 지원을 위해 영어로 작성한 i18n json 파일 내용을 그대로 긁어다가 ChatGPT에게 메시지만 번역해 달라고 하니 포맷 그대로 메시지만 번역해 줬다!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기존의 기계 번역이 하지 못하던 일을 너무도 쉽게 하는 모습을 보고 감명받았는데, 마찬가지로 html 내부의 텍스트도 딱 텍스트만 골라서 번역하는 모습을 보고 참... 여러 생각이 들었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;앞으로의 계획&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일단은 익스텐션 자체를 홍보하고 사용자를 늘리는 것을 목표로 하고 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또한 현재는 프롬프트에 단순 텍스트를 넣어 지시문을 작성하고 있지만, 추후 고급 설정을 통해 temperature 등의 매개변수도 수정할 수 있게 만들 예정이다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;api 요청 자체가 큰 비용이 드는 게 아닌 만큼 라이트 유저를 위해 일정 요청은 무료로 가능하게 할지도 고민 중이다. 광고 배너 등의 수익화가 가능하다면 자체 서버를 띄우고 무료 요청이 가능하도록 할 수 있을 텐데 아쉽게도 크롬 익스텐션은 광고를 달 수 없다 ㅎㅎ&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>취미로 하는 개발</category>
      <category>ChatGPT</category>
      <category>DragGPT</category>
      <category>react</category>
      <category>XState</category>
      <category>드래그GPT</category>
      <author>눅눅한 피부</author>
      <guid isPermaLink="true">https://nookpi.tistory.com/166</guid>
      <comments>https://nookpi.tistory.com/166#entry166comment</comments>
      <pubDate>Sat, 18 Mar 2023 18:35:24 +0900</pubDate>
    </item>
    <item>
      <title>함께 자라기 - 자기계발</title>
      <link>https://nookpi.tistory.com/165</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;자기 계발이 중요한 이유는? 현재 나에게 무엇을 투자했느냐가 1년, 혹은 2년 후의 나를 결정한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;올해&amp;nbsp;내가&amp;nbsp;인정을&amp;nbsp;받고&amp;nbsp;성과를&amp;nbsp;거뒀다면,&amp;nbsp;1~2년&amp;nbsp;전에&amp;nbsp;열심히&amp;nbsp;자기 투자를&amp;nbsp;했을&amp;nbsp;것이다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;반면&amp;nbsp;올해&amp;nbsp;읽은&amp;nbsp;책도&amp;nbsp;몇&amp;nbsp;권&amp;nbsp;없고&amp;nbsp;새로&amp;nbsp;얻은&amp;nbsp;통찰도&amp;nbsp;없다면&amp;nbsp;지금&amp;nbsp;당장은&amp;nbsp;별&amp;nbsp;문제가&amp;nbsp;없으나,&amp;nbsp;내년&amp;nbsp;혹은&amp;nbsp;내후년에&amp;nbsp;분명&amp;nbsp;추락을&amp;nbsp;경험하게&amp;nbsp;된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;직장인들의&amp;nbsp;약&amp;nbsp;50%가&amp;nbsp;하루&amp;nbsp;평균&amp;nbsp;1~2시간의&amp;nbsp;자기계발에&amp;nbsp;투자하고,&amp;nbsp;약&amp;nbsp;30%는&amp;nbsp;1시간&amp;nbsp;미만의&amp;nbsp;시간을&amp;nbsp;투자한다.&amp;nbsp;즉&amp;nbsp;하루&amp;nbsp;평균&amp;nbsp;1시간도&amp;nbsp;투자하지&amp;nbsp;않는&amp;nbsp;사람은&amp;nbsp;자기 계발의&amp;nbsp;측면에서는&amp;nbsp;하위&amp;nbsp;30%라는&amp;nbsp;것을&amp;nbsp;인지하고&amp;nbsp;있어야&amp;nbsp;한다.&lt;br /&gt;이&amp;nbsp;시간은&amp;nbsp;점차&amp;nbsp;복리로&amp;nbsp;축적되어&amp;nbsp;나중에는&amp;nbsp;엄청난&amp;nbsp;차이를&amp;nbsp;만들게&amp;nbsp;된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;빨리&amp;nbsp;자라고&amp;nbsp;싶다면&amp;nbsp;다음과&amp;nbsp;같은&amp;nbsp;것들을&amp;nbsp;고민해야&amp;nbsp;한다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;어떻게 이율을 높일 것인가&lt;/li&gt;
&lt;li&gt;지속적으로&amp;nbsp;나에게&amp;nbsp;현명한&amp;nbsp;투자를&amp;nbsp;하려면&amp;nbsp;어떻게&amp;nbsp;할&amp;nbsp;것인가&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이율을&amp;nbsp;높이기&amp;nbsp;위한&amp;nbsp;몇&amp;nbsp;가지&amp;nbsp;힌트&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;자신이&amp;nbsp;이미&amp;nbsp;갖고&amp;nbsp;있는&amp;nbsp;것들(지식&amp;nbsp;등)을&amp;nbsp;잘&amp;nbsp;활용하라&lt;br /&gt;-&amp;nbsp;새로&amp;nbsp;받아들인&amp;nbsp;지식을&amp;nbsp;얼마나&amp;nbsp;활용하고&amp;nbsp;있는지&amp;nbsp;확인하라&lt;br /&gt;-&amp;nbsp;이미&amp;nbsp;가지고&amp;nbsp;있는&amp;nbsp;지식들을&amp;nbsp;촘촘히&amp;nbsp;연결하고&amp;nbsp;시너지가&amp;nbsp;나게&amp;nbsp;하라&lt;br /&gt;- 새로운 것이 들어오면 이미 갖고 있는 지식과 충돌을 시도하라&lt;/li&gt;
&lt;li&gt;외부 물질을 체화하라&lt;br /&gt;- 주기적인 외부 자극을 받아 재빨리 자기화 해야 한다&lt;/li&gt;
&lt;li&gt;자신을&amp;nbsp;개선하는&amp;nbsp;프로세스에&amp;nbsp;대해&amp;nbsp;생각하라&lt;br /&gt;- 나의 작업을 되돌아보는 회고/반성 활동을 주기적으로 하는 프로세스를 구축하라&lt;/li&gt;
&lt;li&gt;피드백을&amp;nbsp;자주&amp;nbsp;받아라&lt;br /&gt;-&amp;nbsp;피드백의&amp;nbsp;사이클을&amp;nbsp;줄여라.&amp;nbsp;새로운&amp;nbsp;시도를&amp;nbsp;작게라도&amp;nbsp;빠르게&amp;nbsp;해 보는&amp;nbsp;것이&amp;nbsp;좋다&lt;br /&gt;- 일찍, 그리고 자주 실패하여 실패에서 학습하라&lt;/li&gt;
&lt;/ol&gt;</description>
      <category>독후단상</category>
      <category>자기계발</category>
      <category>함께자라기</category>
      <author>눅눅한 피부</author>
      <guid isPermaLink="true">https://nookpi.tistory.com/165</guid>
      <comments>https://nookpi.tistory.com/165#entry165comment</comments>
      <pubDate>Sat, 25 Feb 2023 14:51:37 +0900</pubDate>
    </item>
  </channel>
</rss>