<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>yisu.log</title>
        <link>https://velog.io/</link>
        <description></description>
        <lastBuildDate>Sun, 07 Jan 2024 14:46:24 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <image>
            <title>yisu.log</title>
            <url>https://velog.velcdn.com/images/yisu-kim/profile/efc3efa3-0d2c-449b-b101-6f475f429860/image.png</url>
            <link>https://velog.io/</link>
        </image>
        <copyright>Copyright (C) 2019. yisu.log. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/yisu-kim" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[This Year I Failed]]></title>
            <link>https://velog.io/@yisu-kim/this-year-i-failed</link>
            <guid>https://velog.io/@yisu-kim/this-year-i-failed</guid>
            <pubDate>Sun, 07 Jan 2024 14:46:24 GMT</pubDate>
            <description><![CDATA[<h1 id="2023-회고">2023 회고</h1>
<p>올해 성공한 일들은 어느 정도 주변에 알렸기 때문에 회고에서는 실패한 것들만 돌아보고 기록으로 남기고자 한다. 실패를 돌이켜 보고 글로 적는 게 뭐 그리 어려울까? 알고 보니 정말 쉽지 않았다.</p>
<h2 id="올해-내가-실패한-일들">올해 내가 실패한 일들</h2>
<h3 id="1-실무-경험-기록하기">1. 실무 경험 기록하기</h3>
<p>올해 블로그에 작성한 글을 보면 도서 리뷰와 오픈소스 기여 경험, 이 두 가지 주제뿐이다. 실무에서 맞닥뜨렸던 문제와 그 문제를 기술적으로 어떻게 해결했는지에 대한 글은 단 한 편도 작성하지 않았다.</p>
<p>문제와 해결 방법 모두 인터넷에 잘 정리되어 필요성을 느끼지 못한 경우도 많다. 혹은 이미 해결한 문제를 다시 방문하는 건 봤던 영화를 다시 보는 것과 마찬가지라고 느꼈을 수도 있다. 전문 리뷰어가 아니라면 두 번 보는 게 낭비라는 생각이 들 거다.</p>
<p>그럼에도 불구하고 몇 자라도 적어본 적이 있지만 마무리를 짓지 못했다. 그 이유를 생각해 보면 첫째, 마감을 정하지 않았고 둘째, 글쓰기에 투자할 시간을 따로 빼내어 일정에 포함하지 않았기 때문이다. 글쓰기를 프로젝트처럼 바라봤다면 절대 이렇게 방치하지 않았을 텐데.</p>
<h3 id="2-구현-전에-설계하기">2. 구현 전에 설계하기</h3>
<p>기획서 리뷰를 마치고 협의가 필요한 부분이 정리되고 나면 바로 구현으로 다이빙하곤 했다. 추가하거나 수정해야 하는 부분을 찾으면 머릿속에서 대략 구상만 하고 즉시 키보드로 손을 뻗쳤다. 이 방법은 초기에는 나름대로 잘 작동했지만 장기적으로는 모래성 상태였던 코드 베이스를 점점 엉망으로 만들고 있었다.</p>
<p>가령 관심사가 적절히 분리되었는지 고려하지 않았다. 하나의 기능을 위해 다소 관련성이 부족한 여러 파일에 변경이 일어나는 경우가 있었다. 이 때문에 확인해야 할 범위가 늘어나도 구조가 이상하다고 느끼기보다 원래 개발은 힘든 거라고 생각했다.</p>
<p>이처럼 설계의 중요성을 느끼지 못한 이유는 개발자가 하는 일을 관계자들과 소통하며 기능을 만들고 버그를 고치는 데에만 한정했던 것이 원인인 듯하다. 개발자가 뭘 할 수 있는지 더 넓은 가능성을 살펴보기보다 눈앞에 당장 주어지는 문제만 쳐내고 만족했다.</p>
<h3 id="3-이직하기">3. 이직하기</h3>
<p>사수 없이는 조금도 성장할 수 없다는 건 당연히 말도 안 되는 이야기다. 하지만 2년 차에 접어들자 했던 일을 반복하는 것 같고 다음 스테이지로 넘어가지 못하고 있다는 생각이 종종 들었다.</p>
<p>스터디나 커뮤니티 그리고 행사 참여와 같은 외부 활동을 통해 다양한 사람들과 만나며 시야를 넓혀 보려 했고 이직도 시도해 보았다. 이직에 성공하지는 못했는데 준비가 부족한 적도 있고 옮겨도 메리트가 없다고 느껴진 경우도 있으며 역량을 더 쌓아야겠다고 반성한 케이스도 있었다.</p>
<p>이중 인상적인 면접관 분들과 좋은 채용 프로세스를 경험한 회사가 있었는데 면접 경험을 통해 회사에 코드 리뷰 문화를 도입해 보고 싶다는 생각이 들어 탈락 후 메일로 조언을 구했다. 놀랍게도 면접관 중 한 분이 직접 전화를 통해 팁을 알려주며 응원해 주셨고 실제로 코드 리뷰를 도입할 용기를 낼 수 있었다.</p>
<h3 id="4-코드-리뷰-활성화하기">4. 코드 리뷰 활성화하기</h3>
<p>코드 리뷰 문화를 도입하는 것까지는 그래도 어렵지 않았다. 개발 문화에 대해 목소리를 자주 내는 편이었고 동료들도 코드 리뷰에 대해 긍정적인 반응을 보였다. 용기가 떠나기 전에 <a href="https://google.github.io/eng-practices/review/">구글 코드 리뷰 가이드</a>와 기타 글을 참고로 회의에서 논의한 사항을 정리해 노션에 가이드를 만들고 본격적으로 코드 리뷰를 시작했다.</p>
<p>하지만 문화라는 건 on/off 할 수 있는 스위치가 아니다. 동기가 충만한 나 스스로도 처음에 합의한 대로 프로세스를 따르고 습관을 들이는 게 어려웠다. 나와 팀원들이 장애물을 마주할 때마다 아래와 같은 조치들을 취해보았다.</p>
<ul>
<li>신속한 리뷰와 리마인드를 위해 슬랙에 깃허브 연동 알림 설정</li>
<li>[필수], [제안], [질문] 등의 태그를 말머리에 붙여 코멘트를 작성</li>
<li><a href="https://engineering.linecorp.com/en/blog/effective-code-review#keep-changes-small">라인 코드 리뷰 관련 글</a>을 통해 변경 사항이 클 때 발생하는 어려움을 상기시키고 작은 단위로 올리는 방법 안내</li>
<li>작성자와 리뷰어의 시간을 함께 아낄 수 있도록 PR 템플릿을 만들고 상세한 정보를 작성</li>
<li>리뷰어로 지정된 경우 가능한 한 신속하게 리뷰하고 늦어지는 경우 예정 날짜를 알리거나 리뷰어 변경</li>
<li>리뷰어와 작성자가 느낄 수 있는 부담이나 거부감을 줄이기 위해 기술적인 칭찬 코멘트를 여러 개 만들어 공유</li>
</ul>
<p>하지만 결론적으로 다들 꿈꾸는 것처럼 코드 리뷰가 멋지게 진행되지는 않았다. 잘 지켜지지 않는 부분들에 대해 피드백도 해보았고 반대로 개선할 점이 없는지 의견도 물어보았으나 그냥 리뷰를 받아서 정말 좋다는 답변뿐이라 진퇴양난인 상태로 도입을 취소해야 할지 고민만 깊어졌다.</p>
<h3 id="5-도움-구하기">5. 도움 구하기</h3>
<p>코드 리뷰를 도입하고 여러 어려움을 겪으면서도 이런저런 방법론을 따르고 모범을 보이면 될 거라고만 생각했다. 이전에 면접 이후 멘토링 해주셨던 개발자분에게 연락해 상황을 설명하고 도움을 구해볼 생각은 전혀 하지 못했다. 아니, 정말 왜 안 물어봤을까?</p>
<p>회사에서는 매주 KPT 회고를 진행했다. 잘하고 있으며 계속 해야할 것, 문제가 되어 개선이 필요한 것, 해결을 위해 시도해 볼 것 등을 서로 이야기하는데 여기에서 문제점으로 언급되는 것들에 대해서도 스스로 얘기하고 스스로 고치거나 다른 사람들의 고민을 풀어나가려고 한 적이 많았던 것 같다.</p>
<p>스타트업에 입사하고 거의 입사 기간이 비슷한 주니어 개발자 두 분과 함께 어찌저찌 헤쳐 나가는 데 익숙해지며 기댈 곳이 없으니 혼자 책임져야 한다는 생각이 굳어진 걸 수도 있다. 혹은 도움을 구하면 부족한 자신의 모습을 보여야 한다는 게 부담스러웠을지도 모른다.</p>
<h3 id="6-피드백-요청하기">6. 피드백 요청하기</h3>
<p>TMI이지만 회사의 내부적인 사정으로 팀 전체가 권고사직으로 처리되어 12월부로 실직 상태다. 🫠 갑작스러운 통보를 받고 퇴사하기까지 팀 동료분들에게 커피챗을 신청해 한 분 한 분과 이야기를 나누었다.</p>
<p>원래 상반기쯤부터 퇴사하는 분들에게 커피를 사드리기 시작했는데 퇴사하기까지 많은 번뇌와 굳은 결심이 필요하다는 걸 알기 때문에 배웅하는 의미에서 앞날을 응원했었다. 이번에는 남아 있는 분들에게 작별 인사를 한다는 점이 달랐지만 앞으로의 계획을 이야기하고 서로를 응원하며 인사를 나눈 건 똑같았다.</p>
<p>이 기억을 떠올리다 문득 이런 커피챗 시간이 1:1 피드백을 요청할 수 있는 마지막 순간이 아니었나 하는 후회가 들어 실패 항목에 넣어 보았다. 회사에 피드백 프로세스가 전무했기 때문에 부족한 점을 물어볼까 싶어도 늘 망설이며 용기를 내지 못했다. 그런데 부담 없이 솔직해질 수 있는 순간에 개선해야 할 행동에 대해 피드백을 요청하지 않고 그냥 스쳐 지나갔다고 생각하니 참 아쉬운 마음이 들었다.</p>
<h2 id="실패에도-종류가-있다">실패에도 종류가 있다</h2>
<p>정리하면 다음과 같은 기준으로 실패를 나눌 수 있다.</p>
<p>시도도 안 해서 실패:</p>
<ul>
<li>실무 경험 기록하기</li>
<li>구현 전에 설계하기</li>
<li>도움 구하기</li>
<li>피드백 요청하기</li>
</ul>
<p>시도는 했지만 실패:</p>
<ul>
<li>이직하기</li>
<li>코드 리뷰 활성화하기</li>
</ul>
<p>시도를 한 실패들은 물론 뼈아팠지만 그 과정에서 좋은 인연도 만나고 무엇이라도 배운 게 있었다. 일단 첫 걸을 내딛고 나면 다음 걸음은 자연스럽게 따라온다. 하지만 시도도 하지 않은 실패에서는 아무것도 얻은 게 없었다. 사실 정말 뻔한 이야기지만 내 경험을 기반으로 하니 느껴지는 바가 달랐다.</p>
<h2 id="마치며">마치며</h2>
<p>글을 처음 써나가기 시작했을 때는 실패를 떠올리는 것도 힘들었다. 막상 하나씩 실패한 일들이 떠오르자 썼다 지우기를 반복하며 망설임이 차올랐다. 이것만 보고 나에 대한 의견이 바뀌지 않으면 어쩌지? 스스로 돌아보면서 많이 배웠으니까 만족하고 임시 저장으로 둘까? 이런 생각들이 머릿속에 맴돌았다.</p>
<p>그래도 이렇게 실패를 마주하고 나니 마음이 가벼워졌다. 특히 넘어지고 나서 뭔가 줍고 일어났다는 걸 깨닫게 되니 기분이 좋았다. 좋은 방향으로든 나쁜 방향으로든 나를 변화시켜 왔기 때문에 사람은 변할 수 있다고 생각한다. 앞으로도 내가 계속 변하리라고 믿는다. 그냥 올해는 다양하게 더 많이 실패하자.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[hypothesis/client 오픈 소스에 기여하기]]></title>
            <link>https://velog.io/@yisu-kim/contribute-to-hypothesis</link>
            <guid>https://velog.io/@yisu-kim/contribute-to-hypothesis</guid>
            <pubDate>Sat, 23 Sep 2023 16:08:30 GMT</pubDate>
            <description><![CDATA[<h2 id="사용-중인-오픈소스의-버그를-발견하다">사용 중인 오픈소스의 버그를 발견하다</h2>
<p><a href="https://velog.io/@yisu-kim/the-react-docs-study-with-hypothesis">React 공식 문서 스터디 with Hypothesis</a> 글에서 Hypothesis라는 브라우저 익스텐션을 소개한 적이 있다. 웹 문서에 하이라이트를 남기고 실시간으로 주석을 공유할 수 있는 툴인데 지난 React 스터디에서 매우 유용하게 사용했다.</p>
<p>그런데 사용하던 중 한 가지 불편한 점을 맞닥뜨리게 되었다.</p>
<ol>
<li>Deep Dive 파트에서 버튼을 클릭해 세부 내용을 펼치고 하이라이트를 남긴다.</li>
<li>접힌 상태에서 하이라이트 카드를 선택하면 엉뚱한 위치로 스크롤 된다.</li>
</ol>
<p><img src="https://velog.velcdn.com/images/yisu-kim/post/a6b46b3f-9955-4013-a38a-ff6cc9637982/image.gif" alt=""></p>
<p>개발자여서 한 가지 좋은 점은 버그를 발견했을 때 불편하다고 느끼기만 하는 것이 아니라 무엇이 문제일지 호기심이 일고 내가 개선할 수 있지 않을까 하는 기대감이 함께 한다는 점이다. 그러면 이제부터 구체적인 사례를 통해 오픈소스에 기여하는 방법을 알아보자.</p>
<h2 id="오픈소스에-코드로-기여하는-방법">오픈소스에 코드로 기여하는 방법</h2>
<blockquote>
<p>🚩 개인적인 경험을 기반으로 하고 있습니다. 보다 체계적인 가이드는 <a href="https://opensource.guide/ko/">오픈 소스 가이드</a>를 참고해 보세요.</p>
</blockquote>
<p>이 이슈는 간단한 테스트만 해봐도 내가 직접 코드를 작성해 PR을 올릴 수 있는지 아니면 보고만 가능한 이슈인지 빠르게 짐작할 수 있을 것 같았다.</p>
<p>펼친 상태에서 하이라이트 카드를 선택하면 정상적인 위치로 스크롤되는가? 된다.</p>
<p>개발자 도구를 열어 어떻게 하면 펼친 상태로 바꿀 수 있을지 확인해 보았다. Deep Dive 파트에는 details 태그가 사용되었는데 시맨틱한 HTML이므로 라이브러리에서 범용적으로 대응해도 무방해 보인다.</p>
<p><img src="https://velog.velcdn.com/images/yisu-kim/post/d65e8f40-a914-4685-81ee-73af40a8d863/image.png" alt=""></p>
<p>그렇다면? 스크롤 하기 전에 details 태그를 미리 펼치기만 하면 된다.</p>
<p><a href="https://developer.mozilla.org/en-US/docs/Web/HTML/Element/details">MDN 문서</a>를 보니 details 태그를 펼치려면 open 속성만 추가하면 되므로 충분히 스스로 해결할 수 있다는 생각이 들어 직접 코드를 수정하기로 결정했다.</p>
<h3 id="0-오픈소스-심박수-살펴보기">0. 오픈소스 심박수 살펴보기</h3>
<p>잠깐! 유명하고 활동이 왕성한 레포 예를 들어, React라면 이 단계를 건너뛰어도 되지만 그렇지 않은 경우에는 주의해야 할 점이 있다. 모든 대화가 그렇듯이 상대방이 듣고 답할 준비가 되었을 때 말을 걸어야 한다는 것이다.</p>
<p>깃허브 조직을 살펴보는 경우라면 하단 Repositories 부분에서 레포들의 심장이 생생하게 뛰고 있는지 훑어보면 된다. 열려 있는 이슈와 PR 그리고 최근 업데이트 일시를 참고해 보자.</p>
<p><img src="https://velog.velcdn.com/images/yisu-kim/post/084c2aec-a578-48c3-bd41-e6492ef1b113/image.png" alt=""></p>
<p>기여할 레포가 명확해서 바로 들어가 볼 수 있다면 마지막 커밋은 언제인지, 컨트리뷰터는 몇 명인지, 최근에 열리거나 닫힌 PR의 날짜는 언제인지 등을 확인해 보는 것이 좋다. README에도 공지가 없는지 잘 읽어보자. 더 이상 유지보수를 하지 않는 레포일 수도 있다.</p>
<h3 id="1-기여할-레포-찾기">1. 기여할 레포 찾기</h3>
<p>활발하게 오픈소스 활동이 이루어지고 있다면 본격적으로 어떤 레포에서 코드를 수정해야 하는지 찾아야 한다. 보통 첫 화면에서 Pinned 부분을 확인하면 주요 레포가 무엇인지 알 수 있다.</p>
<p><img src="https://velog.velcdn.com/images/yisu-kim/post/2a07159e-6e3a-4753-9deb-48746d74ba25/image.png" alt=""></p>
<p><a href="https://github.com/hypothesis">hypothesis</a> 조직에는 100개의 레포가 있지만 그 중 핵심은 API 서비스인 h 레포, 클라이언트 서비스인 client 레포, 그리고 브라우저 익스텐션 서비스인 browser-extension 레포인 것 같다.</p>
<p>처음에는 하이라이트 카드를 클릭하는 기능을 브라우저 익스텐션에서 제공하므로 browser-extension 레포를 보면 되나 싶었는데 README를 읽어보니 다음과 같은 문구가 있어 <a href="https://github.com/hypothesis/client">client</a> 레포를 살펴보기로 했다.</p>
<blockquote>
<p>Note that the browser extensions are for the most part just a wrapper around the Hypothesis client. Depending on what you&#39;re interested in working on, you may need to check out the client repository too.</p>
</blockquote>
<h3 id="2-로컬-환경-설정-및-서비스-실행하기">2. 로컬 환경 설정 및 서비스 실행하기</h3>
<p>레포에 접근하면 가장 먼저 README를 살펴봐야 한다. 오픈소스의 사용자 또는 개발자를 위한 가이드가 제공되기 때문이다. client 레포의 README에서 안내하는 <a href="https://h.readthedocs.io/projects/client/en/latest/developers/developing.html">개발자 가이드</a>를 따라가 보니 다음과 같이 로컬 환경을 세팅하도록 안내하고 있다.</p>
<blockquote>
<p>To build the client for development:</p>
<pre><code class="language-sh">git clone &#39;https://github.com/hypothesis/client.git&#39;
cd client
make</code></pre>
</blockquote>
<p>레포의 멤버가 아니기 때문에 권한이 없어 본인의 계정으로 복사한 레포에서 작업해야 하므로 포크한 다음 그 주소를 사용해 clone 했다.</p>
<p>보통 라이브러리를 사용할 때는 npm 명령어를 사용하기 때문에 make라는 명령어를 보고 걱정했는데 아니나 다를까 치자마자 help 메시지가 뜨는 것을 보고 당황했다. 하지만 천천히 읽어보니 make dev 명령어를 사용하면 로컬 서버를 실행할 수 있다는 걸 알 수 있었다.</p>
<p>그러면 가이드에 make dev로 적혀 있어야 하지 않을까 라고 생각했는데 Makefile을 읽어보니 아래와 같이 의도된 것임을 알 수 있었다.</p>
<blockquote>
<pre><code class="language-make">.PHONY: default
default: help</code></pre>
</blockquote>
<h3 id="2-1-연관된-서비스-연결-및-빌드하기">2-1. 연관된 서비스 연결 및 빌드하기</h3>
<p>그런데 여기서 끝이 아니다. 클라이언트의 동작을 브라우저에서 확인하기 위해서는 브라우저 익스텐션 아니면 h 서비스를 로컬에서 실행해 연결해야만 했다.</p>
<blockquote>
<p>To run your development client in a browser you’ll need a local copy of either the Hypothesis Chrome extension or h. Follow either Running the Client from the Browser Extension or Running the Client From h below. If you’re only interested in making changes to the client (and not to h) then running the client from the browser extension is easiest.</p>
</blockquote>
<p>브라우저 익스텐션을 통해 확인해야 하는 이슈이니 익스텐션을 빌드하는 방법을 마저 읽어 보았다.</p>
<blockquote>
<ol>
<li>Check out the browser extension and follow the steps in the browser extension’s documentation to build the extension and configure it to use your local version of the client and the production Hypothesis service.</li>
<li>Start the client’s development server to rebuild the client whenever it changes:</li>
</ol>
<pre><code class="language-sh">make dev</code></pre>
<ol start="3">
<li>After making changes to the client, you will need to run make in the browser extension repo and reload the extension in Chrome to see changes. You can use Extensions Reloader to make this easier.</li>
</ol>
</blockquote>
<p>로컬의 클라이언트를 사용해 익스텐션을 빌드하려면 익스텐션 레포의 문서를 먼저 확인하라고 한다. 일단 <a href="https://github.com/hypothesis/browser-extension">browser-extension</a> 레포를 로컬에 받아보자. 여기서는 그냥 포크 없이 클론했는데 그 이유는 이 레포의 코드를 수정할 예정이 없기 때문이다.</p>
<p>다음으로 README를 읽어보니 두 프로젝트를 연결하기 위해서는 우선 client 폴더에서 link 명령어를 수행한 다음 browser-extension 폴더로 가서 link hypothesis 명령어를 수행하면 된다고 한다.</p>
<blockquote>
<p>Depending on what you&#39;re interested in working on, you may need to check out the client repository too. If you do that, you can get the browser extension repository to use your checked-out client repository by running</p>
<pre><code class="language-sh">yarn link</code></pre>
<p>in the client repository, and then</p>
<pre><code class="language-sh">yarn link hypothesis</code></pre>
<p>in the browser-extension repository. After that, a call to make build will use the built client from the client repository.</p>
</blockquote>
<h4 id="트러블슈팅-1-yarn-link">트러블슈팅 1: yarn link</h4>
<p>다소 생소하지만 지금까지처럼 따라 하면 문제 없겠지라고 생각했다. 그런데 막상 yarn link 명령어를 실행해 보니 아래와 같은 에러 메시지가 나타났다.</p>
<pre><code class="language-sh">Unknown Syntax Error: Not enough positional arguments.

$ yarn link [-A,--all] [-p,--private] [-r,--relative] &lt;destination&gt;</code></pre>
<p>당황스럽겠지만 이럴 때는 에러 메시지를 찬찬히 읽어보자. &lt;destination&gt;이 필요하다고 안내하고 있다.</p>
<p>명령어의 정확한 사용법을 확인하기 위해 공식 문서를 찾아보기로 했다. <a href="https://classic.yarnpkg.com/lang/en/docs/cli/link">yarn v1 공식 문서</a>에서는 link 명령어에 대해 다음과 같이 소개하고 있다. 그리고 이후 명령어를 실행하는 방법이 나와 있는데 가이드와 동일하게 yarn link 뒤에 arguments를 더 넣을 필요가 없었다.</p>
<blockquote>
<p>Symlink a package folder during development.</p>
<p>For development, a package can be linked into another project. This is often useful to test out new features or when trying to debug an issue in a package that manifests itself in another project.</p>
</blockquote>
<p>그러면 에러가 발생하지 않아야 하는데 뭐가 문제일까 고민하다가 문득 공식 문서가 1버전인 것을 깨달았다. 현재 로컬의 yarn 버전은 2버전으로 더 높다. 분명 바뀐 부분이 있어서 실행이 안 된다고 생각해 <a href="https://yarnpkg.com/cli/link">yarn v2+ 버전의 공식 문서</a>를 확인해 보니 아니나 다를까 구체적인 경로를 작성하도록 문법이 약간 바뀌었다.</p>
<blockquote>
<p>Register one or more remote workspaces for use in the current project :</p>
<pre><code class="language-sh">yarn link ~/ts-loader ~/jest</code></pre>
</blockquote>
<p>로컬에 레포를 다운받은 구조는 다음과 같다.</p>
<pre><code>hypothesis
├── client
└── browser-extension</code></pre><p>browser-extension에서 client를 사용하는 것이므로 browser-extension 폴더로 이동해 다음과 같이 명령어를 실행했다. 다행히 에러 없이 잘 수행되었다!</p>
<pre><code class="language-sh">yarn link ../client</code></pre>
<p>그러면 이제 자연스럽게 수행 결과를 확인하고 싶어진다. 공식 문서에 따르면 project-level manifest에 resolutions 필드가 설정될 거라고 한다. 찰떡같이 알아들어 보자면 package.json을 뜻하는 것 같다.</p>
<blockquote>
<p>This command will set a new resolutions field in the project-level manifest and point it to the workspace at the specified location (even if part of another project).</p>
</blockquote>
<p>package.json 파일을 확인해보니 그 말대로 아래와 같이 resolutions 필드에 로컬 경로가 설정된 것을 확인할 수 있었다. 또한 yarn.lock 파일 내에서도 변동이 발생했다.</p>
<pre><code class="language-json">&quot;resolutions&quot;: {
  &quot;hypothesis&quot;: &quot;portal:/mnt/d/Develop/hypothesis/client&quot;
}</code></pre>
<pre><code class="language-json">&quot;hypothesis@portal:/mnt/d/Develop/hypothesis/client::locator=hypothesis-browser-extension%40workspace%3A.&quot;:
  version: 0.0.0-use.local
  resolution: &quot;hypothesis@portal:/mnt/d/Develop/hypothesis/client::locator=hypothesis-browser-extension%40workspace%3A.&quot;
  languageName: node
  linkType: soft</code></pre>
<p>이렇게 또 한고비를 넘겼다. 이제 브라우저 익스텐션을 빌드해서 정말 로컬 client를 바라보는지 확인할 시간이다. <a href="https://github.com/hypothesis/browser-extension/blob/main/docs/building.md">빌드 가이드</a>를 읽고 그대로 명령어를 실행하자 오류 없이 build 폴더에 결과물이 잘 생성되었다.</p>
<blockquote>
<p>The extension build is configured by a JSON settings file, some examples of which are supplied in the settings/ directory. To build the extension using the default settings file (settings/chrome-dev.json), run make build:</p>
<pre><code class="language-sh">$ make build</code></pre>
</blockquote>
<p>그런데 빌드한 익스텐션을 어떻게 크롬에서 확인할 수 있을까? 당연히 가이드에서 친절하게 설명해 주고 있다.</p>
<blockquote>
<p>Once you&#39;ve built the extension, you will be able to load the build/ directory as an unpacked extension:</p>
<ol>
<li>Go to chrome://extensions/ in Chrome.</li>
<li>If you used the chrome-prod.json settings file to build a production extension, you will need to remove the &quot;real&quot; production extension from Chrome before loading your locally built one or create a new Chrome profile without the real one installed.</li>
<li>Tick Developer mode.</li>
<li>Click Load unpacked extension.</li>
<li>Browse to the build/ directory where the extension was built and select it.</li>
</ol>
</blockquote>
<p>Developer mode라는 게 있다는 걸 처음 알았다. 모드를 활성화하면 Load unpacked 버튼이 나타나게 되는데 browser-extension 폴더로 이동해 build 폴더 자체를 선택하면 정말로 익스텐션이 추가된다.</p>
<p><img src="https://velog.velcdn.com/images/yisu-kim/post/00f3f0ce-6120-400e-9381-bfacf747cd65/image.png" alt=""></p>
<h4 id="트러블슈팅-2-this-site-cant-be-reached">트러블슈팅 2: This site can’t be reached</h4>
<p>그런데 또다시 문제가 발생했다. 익스텐션 추가와 동시에 이동되는 http<area>://localhost:5000/welcome 페이지가 정상적으로 실행되지 않고 있었다. 익스텐션도 마찬가지로 계속 로딩 중 표시만 뜨면서 정상적으로 동작하지 않았다.</p>
<p>그래서 이 5000 포트가 뜬금없이 어디서 등장한 건지 한참 고민했다. <a href="https://github.com/hypothesis/browser-extension/blob/main/docs/troubleshooting.md">트러블슈팅 문서</a>도 읽어보았지만 5000포트가 정확히 무얼 의미하는지 설명하는 내용이 없었다. 아까 가이드에서 기본 명령어를 실행할 때 settings/chrome-dev.json 파일을 사용한다고 했으므로 해당 파일을 살펴보았다. 이 중 apiUrl을 보면 로컬 호스트의 5000 포트를 바라보고 있는 걸 알 수 있다.</p>
<pre><code class="language-json">{
  &quot;buildType&quot;: &quot;dev&quot;,
  &quot;manifestV3&quot;: true,

  &quot;apiUrl&quot;: &quot;http://localhost:5000/api/&quot;,
  &quot;authDomain&quot;: &quot;localhost&quot;,
  &quot;bouncerUrl&quot;: &quot;http://localhost:8000/&quot;,
  &quot;serviceUrl&quot;: &quot;http://localhost:5000/&quot;,

  &quot;browserIsChrome&quot;: true,
  &quot;appType&quot;: &quot;chrome-extension&quot;
}</code></pre>
<p>문득 주요 서비스 3개 중 하나인 h 레포가 API 서비스를 제공하고 있으므로 로컬에서 실행한 API 서버를 바라보기 위한 설정이 아닐까 하는 짐작이 들었다. <a href="https://h.readthedocs.io/en/latest/developing/install/">h 레포의 가이드</a>를 읽어보니 역시나 서버가 5000 포트로 실행되는 것을 알 수 있었다.</p>
<blockquote>
<p>This will start the server on port 5000 (http<area>://localhost:5000)</p>
</blockquote>
<p>서버 레포를 수정하는 것이 아니므로 그냥 프로덕션 서버를 바라보아도 무방하다. chrome-prod.json 파일을 살펴보니 역시나 apiUrl이 프로덕션 도메인을 가리키고 있다.</p>
<pre><code>{
  &quot;buildType&quot;: &quot;production&quot;,
  &quot;manifestV3&quot;: true,
  &quot;key&quot;: &quot;{key 값}&quot;,


  &quot;apiUrl&quot;: &quot;https://hypothes.is/api/&quot;,
  &quot;authDomain&quot;: &quot;hypothes.is&quot;,
  &quot;bouncerUrl&quot;: &quot;https://hyp.is/&quot;,
  &quot;serviceUrl&quot;: &quot;https://hypothes.is/&quot;,

  &quot;oauthClientId&quot;: &quot;{id 값}&quot;,

  &quot;sentryPublicDSN&quot;: &quot;{DSN 값}&quot;,

  &quot;browserIsChrome&quot;: true,
  &quot;appType&quot;: &quot;chrome-extension&quot;
}</code></pre><p>그러면 명령어를 바꿔서 다시 빌드해보자.</p>
<blockquote>
<p>To build the extension from a different settings file, provide a SETTINGS_FILE path to make build:</p>
<pre><code>$ make build SETTINGS_FILE=settings/chrome-prod.json</code></pre></blockquote>
<h4 id="트러블슈팅-3-dirty-git-state">트러블슈팅 3: dirty git state</h4>
<p>음 이제 정말 끝이라고 생각했는데 또다시 에러가 발생한다. 이번에는 git state에 대한 클레임이 들어왔다. 아까 link 명령어를 실행하면서 package.json과 yarn.lock 파일에 변동이 있었는데 당연히 올릴 예정이 없으므로 커밋을 하지 않았다. 그런데 이 부분이 막상 빌드할 때 문제가 되는 것으로 보인다.</p>
<pre><code>Error: cannot create production build with dirty git state!</code></pre><p>변경 사항을 임시로 커밋하고 명령어를 재실행해 보니 아무 문제 없이 빌드가 종료되고 이번에는 정상적으로 welcome 페이지가 표시된다.</p>
<p><img src="https://velog.velcdn.com/images/yisu-kim/post/d97d0812-eac1-4d3a-acfb-d1654fc16336/image.png" alt=""></p>
<p>이제 정말 모든 세팅이 끝났다. 하지만 client 코드를 아직 건드리지 않았기 때문에 로컬을 바라보는 게 맞는지 확신이 안 든다. 프론트엔드 개발자라서인지 눈으로 확인해야 마음이 편해서 client 프로젝트에서 사이드바 부분을 찾아 # 문자를 추가해 보았다. 이때, 가이드에 설명되어 있듯이 client와 browser-extension을 다시 빌드해야 한다는 점에 유의했다.</p>
<p>추가한 #이 사이드바에 잘 나타난다. 드디어, 정말로, 마침내 로컬에서 개발할 환경이 갖춰졌다. 사실 막힐 때마다 그냥 이슈만 보고하고 끝낼 걸 그랬나 하는 생각이 들었지만 역시 포기하지 않길 잘했다. 🙌</p>
<p><img src="https://velog.velcdn.com/images/yisu-kim/post/3ab7d1c9-03eb-44f8-bf06-41db8a493f21/image.png" alt=""></p>
<h3 id="3-버그를-수정할-위치-찾기">3. 버그를 수정할 위치 찾기</h3>
<p>본격적으로 문제가 발생한 코드의 위치를 대략 찍어보자. 일단 사이드바를 출발점으로 삼기로 했다.</p>
<p>src/sidebar/index.tsx &gt; HypothesisApp.tsx &gt; SidebarView.tsx &gt; ThreadList.tsx</p>
<p>여기까지 찾았을 때 scroll이라는 단어가 보이기 시작해 제대로 찾아가고 있다고 생각하면서 카드를 클릭하는 부분을 찾기 시작했다.</p>
<p>... &gt; ThreadList.tsx &gt; ThreadCard.tsx &gt; Card.tsx</p>
<pre><code class="language-ts">&lt;Card
  onClick={e =&gt; {
    // Prevent click events intended for another action from
    // triggering a page scroll.
    if (!isFromButtonOrLink(e.target as Element) &amp;&amp; thread.annotation) {
      scrollToAnnotation(thread.annotation);
    }
  }}
  ...
/&gt;</code></pre>
<p>Card의 onClick 이벤트를 확인해보니 scrollToAnnotation이라는 메서드가 보인다. 참고로 이해를 위해 하이라이트라고 계속 표기했지만 이 서비스에서는 다른 사용자에게 공유되는 하이라이트 기능의 이름을 어노테이션이라고 한다.</p>
<p>... &gt; src/sidebar/services/frame-sync.ts</p>
<p>메서드가 정의된 부분으로 넘어가 보니 frame-sync라는 복잡한 파일을 맞닥뜨리게 되었다. 거의 근접한 것 같다고 생각했는데 역시 한 번에 쉽게 찾아지지는 않는다.</p>
<pre><code class="language-ts">/**
 * Scroll the frame to the highlight for an annotation.
 */
scrollToAnnotation(ann: Annotation) {
  ...
  guest.call(&#39;scrollToAnnotation&#39;, ann.$tag);
}</code></pre>
<p>guest를 어디에서 생성하는지 따라가 볼 수도 있지만 여기에서는 &#39;scrollToAnnotation&#39;를 검색해서 점프가 가능할 것 같다.</p>
<p>... &gt;&gt;&gt; src/annotator/guest.ts</p>
<pre><code class="language-ts">this._hostRPC.on(&#39;scrollToAnnotation&#39;, (tag: string) =&gt; {
  this._scrollToAnnotation(tag);
});

...

private async _scrollToAnnotation(tag: string) {
  ...
  await this._scrollToAnchor(anchor);
}

...

private async _scrollToAnchor(anchor: Anchor) {
  ...
    await this._integration.scrollToAnchor(anchor);
  }
}</code></pre>
<p>scrollToAnchor 메서드는 세 군데에 선언되어 있는데 각 파일의 이름은 html.ts, pdf.tsx, vitalsource.ts이다. 딱 봐도 html.ts가 정답이다.</p>
<p>... &gt;&gt;&gt; src/annotator/integrations/html.ts</p>
<pre><code class="language-ts">async scrollToAnchor(anchor: Anchor) {
  ...
  await scrollElementIntoView(highlight);
}</code></pre>
<p>파일 이름이 scroll.ts인 걸 보니 마침내 종착점에 도착했다. 이럴 거면 그냥 scrollIntoView를 바로 검색해 볼 걸 그랬다. 그래도 컨트리뷰터 분들이 이름을 단 하나도 허투루 짓지 않는다는 걸 뼈저리게 느꼈고 전반적으로 코드 구경을 한 것에 만족한다.</p>
<p>... &gt; src/annotator/util/scroll.ts</p>
<pre><code class="language-ts">/**
 * Smoothly scroll an element into view.
 */
export async function scrollElementIntoView(
  ...
  await new Promise(resolve =&gt;
    scrollIntoView(element, { time: maxDuration }, resolve),
  );
}</code></pre>
<h3 id="4-코드-읽고-해석하기">4. 코드 읽고 해석하기</h3>
<p>위치는 찾았으니 스크롤 하기 직전에 details 태그를 펼쳐주는 코드를 추가할 차례다.</p>
<p>하지만 그 전에 당연한 얘기지만 코드를 잘 읽고 이해하는 것이 먼저다. 손님의 입장에서 예의를 지키고 기존 컨트리뷰터들의 컨벤션을 파악해 잘 따라보자.</p>
<pre><code class="language-ts">/**
 * Smoothly scroll an element into view.
 */
export async function scrollElementIntoView(
  element: HTMLElement,
  /* istanbul ignore next - defaults are overridden in tests */
  { maxDuration = 500 }: DurationOptions = {},
): Promise&lt;void&gt; {
  // Make the body&#39;s `tagName` return an upper-case string in XHTML documents
  // like it does in HTML documents. This is a workaround for
  // `scrollIntoView`&#39;s detection of the &lt;body&gt; element. See
  // https://github.com/KoryNunn/scroll-into-view/issues/101.
  const body = element.closest(&#39;body&#39;);
  if (body &amp;&amp; body.tagName !== &#39;BODY&#39;) {
    Object.defineProperty(body, &#39;tagName&#39;, {
      value: &#39;BODY&#39;,
      configurable: true,
    });
  }

  await new Promise(resolve =&gt;
    scrollIntoView(element, { time: maxDuration }, resolve),
  );
}</code></pre>
<ol>
<li>필요한 경우 주석을 달았다.</li>
<li>HTML 태그를 찾아 저장할 때 태그명을 그대로 변수명으로 사용했다.</li>
<li>HTML 태그의 어트리뷰트를 확인할 때 ?. 연산자를 사용하지 않고 &amp;&amp; 연산자를 사용했다.</li>
</ol>
<p>매우 간단한 코드를 추가할 예정이므로 이 정도만 살펴봐도 될 것 같다.</p>
<h3 id="5-문제-해결하기">5. 문제 해결하기</h3>
<p>이제 코딩 시간이다. 부모 엘리먼트에 details 태그가 있는지 확인하고 open 어트리뷰트를 가지고 있지 않으면, 즉 열려 있지 않으면 open 어트리뷰트를 설정해 상세 내용이 펼쳐져 보이도록 해야 한다. 다음과 같이 scrollIntoView 메서드를 호출하는 Promise 바로 위에 코드를 추가하고 역할에 대해 주석을 달았다.</p>
<pre><code class="language-ts">// Ensure that the details are open before scrolling, in case the annotation
// is within the details tag. This guarantees that the user can promptly view
// the content on the screen.
const details = element.closest(&#39;details&#39;);
if (details &amp;&amp; !details.hasAttribute(&#39;open&#39;)) {
  details.setAttribute(&#39;open&#39;, &#39;&#39;);
}

await new Promise(resolve =&gt;
  scrollIntoView(element, { time: maxDuration }, resolve),
);</code></pre>
<p>재빌드한 다음 스크롤 문제가 더 이상 발생하지 않는 것을 확인했다.</p>
<p><img src="https://velog.velcdn.com/images/yisu-kim/post/e85c207b-1a2b-4cbf-ab86-b23f25ce2432/image.gif" alt=""></p>
<h3 id="6-테스트-작성하기">6. 테스트 작성하기</h3>
<p>오픈소스마다 다르지만 테스트를 요구하는 경우가 꽤 있다. 내 경우에는 미처 테스트를 생각하지 못하고 바로 PR을 올렸다가 Codecov 리포트에서 테스트 커버리지가 낮아진 것을 확인하고 자발적으로 테스트를 작성하겠다고 컨트리뷰터에게 이야기해서 순서가 뒤바뀌게 되었다.</p>
<p><img src="https://velog.velcdn.com/images/yisu-kim/post/d4219669-b875-4fbb-94d4-162f3165439b/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/yisu-kim/post/ac31a3bd-20ee-4e3a-bae3-d76b8ece29b6/image.png" alt=""></p>
<p>테스트 파일은 보통 test 폴더에 모여 있다. scroll.ts와 동일한 레벨에 위치한 test 폴더를 열어보니 scroll-test.js 파일을 찾을 수 있었다.</p>
<pre><code>util
├── test
│   └── scroll-test.js
└── scroll.ts</code></pre><p>실무에서 테스트 코드를 작성한 경험이 없어 걱정했지만 주변 코드를 토대로 그럭저럭 첫 번째 테스트 코드를 완성하게 되었다.</p>
<pre><code class="language-ts">it(&#39;scrolls element into view when the target is within the details tag&#39;, async () =&gt; {
  const container = document.createElement(&#39;div&#39;);
  const details = document.createElement(&#39;details&#39;);
  container.append(details);

  const summary = document.createElement(&#39;summary&#39;);
  summary.append(&#39;Summary&#39;);
  details.append(summary);

  const target = document.createElement(&#39;div&#39;);
  target.style.height = &#39;20px&#39;;
  target.style.width = &#39;100px&#39;;
  details.append(target);

  await scrollElementIntoView(target, { maxDuration: 1 });

  const containerRect = container.getBoundingClientRect();
  const targetRect = target.getBoundingClientRect();

  assert.isTrue(containerRect.top &lt;= targetRect.top);
  assert.isTrue(containerRect.bottom &gt;= targetRect.bottom);
});</code></pre>
<p>테스트를 실행하기 위해 어떤 명령어를 실행해야 하는지 확인하기 위해 다시 <a href="https://h.readthedocs.io/projects/client/en/latest/developers/developing.html#running-the-tests">개발자 가이드</a>를 읽어보았다.</p>
<blockquote>
<p>Hypothesis uses Karma and mocha for testing. To run all the tests once, run:</p>
<pre><code class="language-sh">make test</code></pre>
<p>You can filter the tests which are run by running yarn test --grep &lt;pattern&gt;. Only test files matching the regex &lt;pattern&gt; will be executed.</p>
</blockquote>
<h4 id="트러블슈팅-4-karma-headless-chrome">트러블슈팅 4: karma headless chrome</h4>
<p>가이드대로 명령어를 실행했는데 또다시 에러가 발생했다. 슬슬 문서가 좀 불친절하구나 하는 생각이 든다. 실제로 오픈소스에서 신규 사용자와 컨트리뷰터를 유치하는 데에는 문서화가 얼마나 잘 되어 있는지도 꽤 중요하다고 한다.</p>
<blockquote>
<p>23 09 2023 22:28:14.299:INFO [karma-server]: Karma v6.4.2 server started at ...
...
23 09 2023 22:28:14.306:ERROR [launcher]: No binary for ChromeHeadless browser on your platform.
 Please, set &quot;CHROME_BIN&quot; env variable.</p>
</blockquote>
<p>package.json에서 karma 패키지를 찾아보니 karma-chrome-launcher가 딱 눈에 들어온다.</p>
<blockquote>
<p>&quot;karma&quot;: &quot;^6.4.2&quot;,
&quot;karma-chai&quot;: &quot;^0.1.0&quot;,
&quot;karma-chrome-launcher&quot;: &quot;^3.2.0&quot;,</p>
</blockquote>
<p><a href="https://github.com/karma-runner/karma-chrome-launcher">karma-chrome-launcher</a> 레포에서 README를 읽어보니 karma.conf.js라는 파일에서 테스트를 수행할 브라우저를 설정할 수 있다고 한다.</p>
<p>프로젝트의 karma.config.js 파일에서는 ChromeHeadless만 단독으로 설정되어 있는데 README와 조합해서 이해한 바로는 puppeteer 설치가 필요하다.</p>
<blockquote>
<pre><code class="language-sh">$ npm i -D puppeteer karma-chrome-launcher</code></pre>
</blockquote>
<blockquote>
<pre><code class="language-js">// karma.conf.js
process.env.CHROME_BIN = require(&#39;puppeteer&#39;).executablePath()

module.exports = function(config) {
  config.set({
    browsers: [&#39;ChromeHeadless&#39;]
  })
}</code></pre>
</blockquote>
<p>puppeteer를 설치하고 CHROME_BIN 환경변수를 설정하고 다시 한번 테스트 명령어를 실행했다. 그런데 뭐가 문제인지 또 에러가 발생한다.</p>
<blockquote>
<p>23 09 2023 23:52:05.337:ERROR [launcher]: Cannot start ChromeHeadless</p>
</blockquote>
<p>생각해보니 꼭 headless로 실행할 필요가 없을 것 같다. 이미 크롬이 설치되어 있는데 그냥 그걸 활용하자 싶어 아래와 같이 설정을 변경하고 환경 변수로는 로컬의 chrome 경로를 찾아 설정해 주었다.</p>
<pre><code class="language-sh">export CHROME_BIN=&#39;/mnt/c/Program Files (x86)/Google/Chrome/Application/chrome.exe&#39;</code></pre>
<pre><code class="language-js">// karma.config.js
module.exports = function(config) {
  config.set({
    browsers: [&#39;Chrome&#39;]
  })
}</code></pre>
<p>오류 없이 브라우저가 자동 실행되면서 3000개가 넘는 테스트가 수행되었다.</p>
<p><img src="https://velog.velcdn.com/images/yisu-kim/post/9328ceeb-b1f4-454a-98a8-87291f5fdbbe/image.png" alt=""></p>
<p>아니 그런데 너무 많다. 내가 작성한 테스트의 결과만 보고 싶으니 가이드에서 알려준 대로 원하는 파일만 지정해서 테스트해 보자.</p>
<blockquote>
<pre><code class="language-sh"> yarn test --grep **/scroll-test.js</code></pre>
</blockquote>
<p><img src="https://velog.velcdn.com/images/yisu-kim/post/37ed0545-19d5-473d-b166-9d11e3a8ce64/image.png" alt=""></p>
<p>테스트가 통과하는 것을 확인했으니 커밋해서 올렸다. 이후 테스트 코드를 리뷰 받고 나자 100% 커버리지를 회복할 수 있었다.</p>
<p><img src="https://velog.velcdn.com/images/yisu-kim/post/bc8f746a-f798-4312-b4a1-c0a17dc8cd37/image.png" alt=""></p>
<h3 id="7-pr-작성하기">7. PR 작성하기</h3>
<p>테스트 코드까지 모두 완성해서 브랜치에 올렸다면 이제 PR을 작성할 시간이다. 디폴트 브랜치에 자신의 작업 브랜치를 머지하는 PR을 열어보자.</p>
<p><img src="https://velog.velcdn.com/images/yisu-kim/post/a74a524c-8ae1-4978-bc7a-0e87fc6a80dc/image.png" alt=""></p>
<p>템플릿을 제공하는 경우에는 가이드를 따라 작성하면 되지만 템플릿이 없는 경우에는 어떻게 해야 할까? 이전에 작성된 PR을 살펴보고 상황에 맞게 따라 하면 된다. 버그 리포트이기 때문에 이슈가 발생한 상황을 Description에 설명하고 Changes Made에는 작업한 내용을 설명하는 형식을 채택했다. 또한, 리뷰어의 이해를 돕기 위한 동영상도 첨부했다.</p>
<p>자세한 PR은 <a href="https://github.com/hypothesis/client/pull/5701">여기</a>에서 확인할 수 있다. 참고로 PR을 영작할 때 <a href="https://www.deepl.com/translator">DeepL</a>이라는 번역기의 도움을 많이 받았다.</p>
<p><img src="https://velog.velcdn.com/images/yisu-kim/post/53873657-b2c6-4841-9133-a515e3489cc1/image.png" alt=""></p>
<h3 id="8-코드-리뷰에-피드백하고-승인받기">8. 코드 리뷰에 피드백하고 승인받기</h3>
<p>가장 기대되는 파트다. 이런 기회가 아니면 언제 오픈소스 컨트리뷰터에게 코드 리뷰를 받을 수 있을까?</p>
<p><img src="https://velog.velcdn.com/images/yisu-kim/post/bc8210a2-660f-42a9-88ce-fd0107e57193/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/yisu-kim/post/51f260bd-8114-4e0a-8e5d-bb6fa969dfcf/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/yisu-kim/post/ba5d770f-4766-4457-839b-035d94563863/image.png" alt=""></p>
<p>얼마 지나지 않아 컨트리뷰터에게 답변을 받았고 사소한 수정 사항이 있어 빠르게 반영했다. 작업한 코드가 워낙 심플해서 리뷰는 이대로 끝일 줄 알았으나 6번에서 말했듯이 뒤늦게 올린 테스트 코드에서 추가적인 지적을 받게 되었다.</p>
<p><img src="https://velog.velcdn.com/images/yisu-kim/post/05cdb52f-dad8-492b-8dd3-05982af48d90/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/yisu-kim/post/cdf912b8-5cc4-4593-af92-17f2021fec55/image.png" alt=""></p>
<p>이 외에도 받은 몇몇 조언에 따라 코드를 수정했다. 결과적으로 @robertknight와 @acelaya 두 분 덕분에 탈이 많았던 테스트 코드가 아래와 같이 심플하게 정리되었다. 즉, 스크롤 전후로 details 태그가 open 상태인지 확인한다.</p>
<pre><code class="language-ts">it(&#39;opens containing `&lt;details&gt;` tag to make content visible&#39;, async () =&gt; {
  const container = createContainer();
  const details = document.createElement(&#39;details&#39;);
  container.append(details);

  const summary = document.createElement(&#39;summary&#39;);
  summary.append(&#39;Summary&#39;);
  details.append(summary);

  const target = document.createElement(&#39;div&#39;);
  details.append(target);

  assert.isFalse(details.open);
  await scrollElementIntoView(target, { maxDuration: 1 });
  assert.isTrue(details.open);
});</code></pre>
<p>오픈소스 컨트리뷰터들은 보통 본업이 따로 있는 경우가 많아 확인이 늦어지곤 한다. 조급해하지 말고 지적받은 부분을 고쳐서 올리고 코멘트를 주고받다 보면 어느 순간 컨트리뷰터의 승인을 받을 수 있다.</p>
<p><img src="https://velog.velcdn.com/images/yisu-kim/post/54242f3c-8256-4538-8cf8-dbd208b5cf14/image.png" alt=""></p>
<h3 id="9-메인-브랜치에-병합-및-릴리즈-되기">9. 메인 브랜치에 병합 및 릴리즈 되기</h3>
<p>이제부터는 인내심만 가지고 기다리면 된다. 보통 다른 PR과 함께 다음 버전에 포함되므로 짧으면 며칠에서 길면 몇 주가 지나면 어느 순간 PR이 머지되어 기분 좋은 보라색 아이콘으로 바뀐 것을 발견할 수 있다.</p>
<p><img src="https://velog.velcdn.com/images/yisu-kim/post/c04171db-8d1f-47b9-b5f3-42479e5404e0/image.png" alt=""></p>
<p><a href="https://github.com/hypothesis/client/releases/tag/v1.1336.0">v1.1336.0 릴리즈 노트</a>에 본인의 이슈 넘버가 포함되었는지 확인해보자.</p>
<p><img src="https://velog.velcdn.com/images/yisu-kim/post/8e151ca3-a79b-457f-bcf1-9209944da1a4/image.png" alt=""></p>
<p>당시에 익스텐션은 따로 확인하지 않아 이제야 버전을 살펴보았는데 어느새 1.1350.0.3까지 업데이트되었다. 물론 최신 버전에서는 스크롤 이슈가 해결된 것을 확인할 수 있었다. 🎉</p>
<p><img src="https://velog.velcdn.com/images/yisu-kim/post/53c62023-432a-4e06-8128-f257a578957b/image.png" alt=""></p>
<h2 id="시간을-들인-가치가-있었나">시간을 들인 가치가 있었나?</h2>
<ol>
<li><p>현재 진행 중인 <a href="https://github.com/read-with-us/react-docs-reference">React 레퍼런스 스터디</a>에서 잘 사용 중이다. 이전 스터디에서 팀원들과 함께 사용할 때 스크롤 문제로 불편함을 겪었는데 이렇게 직접 버그를 고치고 나니 어깨가 으쓱한다.</p>
</li>
<li><p>link 명령어를 알게 된 덕분에 실무에서 내부 라이브러리 패키지를 수정할 때 유용하게 써먹었다. 매번 수정하고 결과를 확인하기 위해 1) 빌드해서 2) 배포한 다음 사용하는 서비스에서 3) 버전을 업그레이드했는데 link 명령어 덕분에 불필요한 과정을 두 단계 뛰어넘을 수 있었다.</p>
</li>
<li><p>처음으로 테스트 코드를 작성해 보았다. 회사 서비스에 테스트 코드가 한 줄도 없다 보니 도입은 엄두에도 못 내고 있었는데 이미 환경이 갖춰진 상태에서 하나의 테스트를 추가하는 것이라 상대적으로 수월했다.</p>
</li>
<li><p>소스 코드를 쭉 살펴보다가 gRPC라는 한 번쯤 이름을 들어본 기술을 사용하는 것을 알게 되었다. 굉장히 멀게 느꼈던 기술인데 궁금해져서 <a href="https://grpc.io/">gRPC 사이트</a>에도 한 번 들어가 보았다. 분산 서비스를 위한 기술 같은데 hypothesis를 브라우저 익스텐션으로만 생각했다가 새삼 다시 보게 되었다.</p>
</li>
</ol>
<h2 id="마치며">마치며</h2>
<p>글을 적다 보니 양이 많아 깜짝 놀랐다. 코드 몇 줄 추가하고 싶었을 뿐인데 프로젝트 설정하는 부분이 낯설어 시행착오도 많이 했고 한참 헤맸다. 물론 컨트리뷰터에게 도움을 요청했다면 훨씬 빠르게 해결했겠지만 급할 것이 없으니 천천히 가더라도 내 힘으로 해결하고 싶었다. 하지만 만약 설정하다가 내 역량으로 계속 나아가기 어렵다고 느꼈다면 직접 문의했을 것이고 분명 도움을 받을 수 있었을 것이다.</p>
<p>혹여라도 이 글을 읽고 나서 오픈소스 기여가 너무 어렵다고 여기지는 않았으면 한다. 이 오픈소스는 유명한 편이 아니기 때문에 신규 기여자를 위해 친절하게 문서화되어 있지 않았다고 생각한다. 하지만 기여자들에게 커뮤니티는 항상 열려 있다. 보통 README에 컨택할 수 있는 링크가 공유되어 있으므로 디스코드 같은 커뮤니티에 참여해 컨트리뷰터와 실시간으로 소통해 보는 것도 매우 흥미로운 경험이 될 것이다.</p>
<blockquote>
<p>오픈소스에 기여한다는 게 생각보다는 쉽고 또 생각보다는 어려웠습니다. 그래도 누군가에게 이 경험이 도움이 되길 바라며 궁금하신 점이 있다면 편하게 댓글이나 메일로 문의주세요!</p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[복붙 개발자의 벼락 성공기]]></title>
            <link>https://velog.io/@yisu-kim/the-unlikely-success-of-a-copy-paste-developer</link>
            <guid>https://velog.io/@yisu-kim/the-unlikely-success-of-a-copy-paste-developer</guid>
            <pubDate>Sun, 25 Jun 2023 12:02:34 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>한빛미디어 &lt;나는 리뷰어다&gt; 활동을 위해서 책을 제공받아 작성된 서평입니다.</p>
</blockquote>
<p><img src="https://velog.velcdn.com/images/yisu-kim/post/44aa6372-cb3b-42bb-91d1-170e95b19f72/image.png" alt=""></p>
<h2 id="선정-이유">선정 이유</h2>
<p>개발자를 주인공으로 한 소설은 잘 들어보지 못했던 터라 그냥 끌리는 마음에 선택하게 되었다. 영국에서 일하는 스웨덴 개발자 리오 라르손과 그 주변 인물을 중심으로 펼쳐지는 이야기인데 사는 곳은 달라도 같은 개발자로서 공감할 만한 부분들이 많으리라 생각했다.</p>
<blockquote>
<p>프로젝트는 다양한 이유로 엉망진창이 되었지만 현재 이들이 직면한 가장 큰 문제는 처참한 성능이었다. 과거에 외부 컨설턴트들과 함께 프로젝트를 진행할 만큼 팀이 컸을 당시, 누군가가 캐시를 직접 구현하자는 훌륭한 아이디어를 냈다.</p>
<p>그게 어려워 봐야 얼마나 어렵겠는가?
알고 보니 정말 어려웠다.</p>
<p>컴퓨터 과학에는 진정으로 어려운 두 가지 문제가 있다. 이름 짓기, 캐시 무효화, 오프 바이 원(off by-one) 오류다.
지난 몇 달간 이들은 느리고 일관성 없는 캐시 문제의 해결책을 찾으려 노력했으나 진전이 거의 없었다. 데이비드는 자신이 프로그래밍하겠다며 온라인에서 찾은 코드를 복사해 붙여 넣었다가 본의 아니게 엄청난 메모리 누수 문제를 일으켰고, 팀에서는 그 원인을 찾는 데 한 달이 걸렸다.</p>
<p>— p. 33</p>
</blockquote>
<h2 id="주제-및-구성">주제 및 구성</h2>
<p>주인공인 리오는 10년이 지나도 이삿짐 상자를 다 풀지 않을 만큼 게으르지만 작업 환경만큼은 여느 개발자 못지않다. 커브드 모니터와 타워형 컴퓨터를 갖추고 체리 MX 청축이 들어간 백라이트 WASD 키보드와 게임용 마우스를 사용한다. 마지막으로 인체공학적 의자를 곁들이면 작업 공간이 완성된다.</p>
<p>옥토캣이나 고퍼처럼 개발 관련 스티커가 잔뜩 붙은 노트북을 들고 출근하는 길에는 사람들과의 교류를 피하는 너드같은 모습을 보인다. 출근하자마자 채널에 뜨는 스크럼 미팅 메시지에 부랴부랴 회의실로 달려가 만난 프로젝트 매니저는 리오에게 캐시 작업에 대한 진척 상황을 묻는다.</p>
<p>캐시 문제를 고치기 위해 동료 개발자와 페어 프로그래밍을 하던 중 리오는 대학 시절 구현했던 CacheIsKing 라이브러리를 떠올리게 되는데...</p>
<p>벌써부터 개발 용어가 꽤 등장하는데 다행히 이야기를 시작하기 전 IT 용어에 대해 간단히 설명하는 소개 페이지가 있어 개발 분야에 익숙하지 않은 사람들의 이해를 돕고 있다. 그리고 이어지는 50개의 에피소드는 커밋 메시지처럼 제목이 붙여졌는데 이는 변화하는 타임라인에서 특정 지점을 순간 포착하는 커밋의 특징을 적절히 활용한 것이다.</p>
<h2 id="유익한-점">유익한 점</h2>
<p>마치 시트콤을 보는 것처럼 에피소드마다 등장하는 다양한 인물들의 경험 속에서 개발자로서 공감 가는 부분을 흥미롭게 읽었다. 리오 본인이 자초한 사건이지만 익숙한 안전지대를 벗어나면서 실패하고 고군분투하는 모습과 스스로 깨닫지 못했던 잠재력을 발휘하게 되는 여정을 따라가다 보면 어느새 리오를 응원하게 된다.</p>
<h2 id="아쉬운-점">아쉬운 점</h2>
<p>그리 두껍지 않은 책인데 너무 많은 이야기를 담으려 한다고 느꼈고 결과적으로 무슨 이야기를 하고 싶은 건지 알 수가 없었다. 사실 소설책이야 재미만 있으면 그만인데 저자와 유머 코드가 맞지 않는 탓인지 종종 불편해서 그만 읽고 싶다는 생각이 들기도 했다. 다행히 끝으로 갈수록 차분한 분위기와 현실적인 경험에 공감이 되어 책장을 무사히 덮을 수 있었다.</p>
<h2 id="추천-대상">추천 대상</h2>
<p>다시 읽고 싶은 책이 아니라 추천은 하지 않으려 한다. 그래도 다른 사람의 의견을 받아들이는 것보다 직접 읽고 판단하는 것이 좋은 사람들은 한 번 읽어보길 권한다.</p>
<h2 id="사족">사족</h2>
<p>모든 사람의 마음에 드는 책이 어디 있겠느냐마는 &#39;나는 리뷰어다&#39;라는 활동을 통해 받은 책이다 보니 안 좋은 평을 써도 되나 싶어 글을 쓰기까지 좀 망설였다. 블로그는 일기장이 아니니까 솔직함이 미덕은 아니지만 이 책을 읽을까 말까 고민하며 이 리뷰에 도달한 독자들에게 예의를 지키는 것이 더 우선이라고 본다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[React 공식 문서 스터디 with Hypothesis]]></title>
            <link>https://velog.io/@yisu-kim/the-react-docs-study-with-hypothesis</link>
            <guid>https://velog.io/@yisu-kim/the-react-docs-study-with-hypothesis</guid>
            <pubDate>Mon, 29 May 2023 16:07:10 GMT</pubDate>
            <description><![CDATA[<h2 id="react-공식-문서-런칭">React 공식 문서 런칭</h2>
<p>2023년 3월 16일 오랫동안 베타에 머물러 있던 <a href="https://react.dev/blog/2023/03/16/introducing-react-dev">공식 문서가 정식으로 런칭</a>되었다. 새로운 리액트 공식 문서는 함수 컴포넌트와 훅을 중심으로 새롭게 쓰였으며 다이어그램, 삽화, 챌린지 그리고 수많은 예제를 포함한다.</p>
<div align="center">
    <img src="https://velog.velcdn.com/images/yisu-kim/post/117d6e4a-0602-49ef-b9c0-3c4169e818f4/image.png" style="height: 300px" />
  <a href="https://ko.react.dev/images/docs/illustrations/i_browser-paint.png">Illustrated by Rachel Lee Nabors</a>
</div>

<h2 id="공식-문서-함께-읽기-스터디-주최">공식 문서 함께 읽기 스터디 주최</h2>
<p>항상 리액트를 제대로 쓰고 있는지 의구심이 있던 차에 소식을 듣고 공식 문서를 혼자 읽어보려 했다. 그러나 초반부인 Describing the UI까지 읽다가 이런저런 핑계로 미루게 되고 만다.</p>
<p>결국 목마른 사람이 우물을 판다고 지난번에 주최했던 <a href="https://github.com/read-with-us/refactoring">리팩터링 2판 스터디</a>의 경험을 토대로 모여서 문서를 각자 읽고 토의하는 <a href="https://github.com/read-with-us/react-docs">React 공식 문서 읽기 스터디</a>를 모집하게 되었다.</p>
<p>이번이 두 번째로 주최하는 스터디인데 지난 스터디보다는 조금 더 개선되고 유익한 스터디를 꾸려봐야지 하는 마음으로 5월 초에 시작해 현재 1/3가량 진행 중이다.</p>
<h2 id="웹-문서-토의의-어려움">웹 문서 토의의 어려움</h2>
<p>이 스터디는 한 사람이 화면을 띄워두고 읽어나가는 것이 아니라 각자 속도에 맞게 읽고 정해둔 독서 시간이 끝나면 중요하게 생각했던 부분을 공유하며 토의하는 방식으로 진행된다. 그런데 책으로 하는 스터디와 달리 몇 가지 난관에 부딪히게 되었는데 무엇보다도 문장의 위치를 공유하기가 쉽지 않았다.</p>
<p>책의 경우 각자 밑줄 친 부분을 공유할 때 몇 쪽 &gt; 몇 번째 문단 &gt; 몇 번째 줄인지 차례대로 이야기하면 위치를 찾기 어렵지 않았던 반면, 웹 문서의 경우 스크롤로 이어지기 때문에 위치를 지정하기가 애매해지며 헤딩(h1, h2, ...)을 알려준다 해도 목차(TOC)가 없는 이상 해당 지점으로 바로 이동할 수 없어 헤매게 된다.</p>
<p>첫날 발표를 맡았을 때 미처 생각지 못한 위기에 허둥지둥하며 다소 산만한 화면을 공유하게 되었다. 이러다 스터디 망할지도(?) 라는 위기감을 느끼고 방법을 고민하다가 웹 사이트에 하이라이트를 해보자 하는 생각이 들어 서비스를 찾기 시작했다.</p>
<h2 id="hypothesis-하이라이트-툴">Hypothesis: 하이라이트 툴</h2>
<p>요구 사항은 1) 화면에 하이라이트를 할 수 있을 것 2) 가능하면 실시간으로 하이라이트를 공유할 수 있을 것 이렇게 두 가지였다.</p>
<p>몇몇 서비스가 있었지만 Hypothesis라는 서비스에 눈길이 갔는데 우선 필요로 하는 기능이 모두 있는 데다 핵심 기능 몇 가지에 집중하고 있었고 <a href="https://web.hypothes.is/education/annotated/">교육 분야에서 많이 쓰이는 것으로 보여</a> 스터디에 적합하다는 생각이 들었다. 이건 개인적인 의견이지만 디자인이 유려하지 않은데 오히려 그런 허름함이 숨겨진 맛집을 찾은 것 같아 더 믿음이 갔다.</p>
<p><img src="https://velog.velcdn.com/images/yisu-kim/post/87386996-c7d7-416a-92e9-53ae6a9f43f4/image.png" alt=""></p>
<p>가입 후 익스텐션을 설치하기만 하면 무료로 이용할 수 있다는 점이 가벼운 마음으로 테스트할 수 있는 환경을 제공했다. 특히 오픈 소스이고 비영리 조직에서 운영하기 때문에 광고나 유료 결제를 과하게 유도하지 않을 것 같았고 스터디원들에게 권유하기에 부담이 없었다.</p>
<p><img src="https://velog.velcdn.com/images/yisu-kim/post/9aebadf4-be70-415b-9521-c89c9a810901/image.png" alt=""></p>
<p>시범 삼아 도입해보니 스터디 경험이 개선되어 반응이 좋았고👍 이후 스터디에서 적극적으로 활용하려 한다. 아래 이미지는 스터디에서 팀원들과 하이라이트를 하며 문서를 읽어나간 모습인데 우측을 보면 각자 하이라이트하고 주석을 남겨 둔 과정을 확인할 수 있다. 서로 읽는 속도가 다르기 때문에 다른 사람이 남긴 하이라이트에 댓글을 달기도 한다.</p>
<p>각 카드는 위치 순으로 정렬되며 카드를 클릭하면 해당 하이라이트를 남긴 위치로 스크롤이 이동되기 때문에 매끄럽게 화면을 이동하며 발표를 진행할 수 있다. 또한 각자의 의견이 이미 웹상에 작성되어 있기 때문에 기록을 맡은 팀원도 추가적인 의견만 받아쓰고 토의에 더 집중할 수 있다는 점이 큰 장점이라고 본다.</p>
<p><img src="https://velog.velcdn.com/images/yisu-kim/post/b7705167-4c1f-41a4-899d-cfde74bbfdb4/image.png" alt=""></p>
<p>다만 한 가지 주의할 점은 URL 단위로 하이라이트가 통합되어 보이기 때문에 반드시 같은 사이트에서 진행해야 한다는 것이다. 당연히 같은 사이트에서 진행하지 않냐고 생각할 수 있지만 현재 리액트 문서의 번역이 다 끝나지 않은 상태이고 선호하는 언어가 다른 경우 각자 다른 사이트에서 보게 될 수도 있다. 예를 들어, <a href="https://react.dev/">공식 문서(영어)</a>, <a href="https://ko.react.dev/">공식 문서(한국어)</a>, <a href="https://react-ko.dev/">비공식 문서(영어와 한국어 병기)</a> 이렇게 3가지에 하이라이트가 나눠지는 난감한 경우가 발생할 수 있다.</p>
<h2 id="참고자료">참고자료</h2>
<ul>
<li><a href="https://hypothes.is">Hypothesis</a></li>
</ul>
<blockquote>
<p>유사한 스터디를 진행 중이거나 진행할 예정인 분들에게 적극 추천합니다. 실시간으로 공유되는 하이라이트를 통해 일방적인 전달이 아닌 서로의 경험과 다양한 시각을 나누며 깊어지는 스터디를 즐겨보세요!</p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[소프트웨어 아키텍처 The Hard Parts]]></title>
            <link>https://velog.io/@yisu-kim/software-architecture-the-hard-parts</link>
            <guid>https://velog.io/@yisu-kim/software-architecture-the-hard-parts</guid>
            <pubDate>Sun, 26 Mar 2023 13:28:59 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>한빛미디어 &lt;나는 리뷰어다&gt; 활동을 위해서 책을 제공받아 작성된 서평입니다.</p>
</blockquote>
<p><img src="https://velog.velcdn.com/images/yisu-kim/post/3c14ee71-2aec-4d44-9171-ff535f2ae83d/image.jpeg" alt=""></p>
<h2 id="선정-이유">선정 이유</h2>
<p>사실 이번 책의 선정 이유는 특별할 것이 없는데 고를 수 있는 책 중에 끌리지 않는 책을 소거법으로 지우다 보니 선택되었다. 하지만 벌써 실망하고 뒤로 갈 필요는 없다. 책 내용이 생각보다 흥미로웠기 때문이다. 늘 새롭고 정답이 없는 문제에 맞닥뜨리는 기술자들이 최선의 선택을 내리고자 고군분투하는 이야기다.</p>
<blockquote>
<p>소프트웨어 아키텍트 같은 기술자가 콘퍼런스에 참석하거나 책을 쓰는 이유는 뭘까요? … 용어야 어쨌든 기술자는 일반적인 문제에 대한 새로운 솔루션(해결책)을 찾고 그것을 더 많은 사람에게 알리고자 책을 씁니다.
하지만 솔루션이 없는 문제들은 어떻게 다뤄야 할까요? 소프트웨어 아키텍처 분야는 하나같이 모든 영역에 걸쳐 제네릭한 솔루션이 없습니다. 온갖 지저분한 문제투성이에 거의 똑같이 지저분한 트레이드오프(상충 관계)만 잔뜩 널려 있죠.</p>
<p>소프트웨어 개발자는 인터넷을 검색해 자신이 맞닥뜨린 문제를 해결하는 데 일가견이 있는 사람들입니다. 가령, 자신의 개발 환경에서 어떤 플러그인을 설정하는 방법이 궁금하면 재빨리 구글에서 답을 찾아낼 수 있죠.
그러나 아키텍트는 그렇게 할 수가 없습니다.
… 아키텍트는 일반적인 문제보다는 새로운 상황에서 창의적인 의사 결정을 하느라 끊임없이 고군분투하는 사람들입니다. 그들에게 모든 문제는 마치 눈송이와도 같아서 어떤 조직은 물론 전 세계적으로도 새로운 것들이 대부분입니다.</p>
<p>— p. 25-26</p>
</blockquote>
<h2 id="주제-및-구성">주제 및 구성</h2>
<p>책 표지에도 나와 있지만 이 책은 『소프트웨어 아키텍처 101』이라는 책의 심화 편으로 실무에서 분산 아키텍처를 설계할 때 트레이드오프 측정에 도움이 되는 심층적인 가이드를 제공한다. 코드 변동성, 확장성, 유지보수성 등 분해할 때 고려해야 할 지침과 트랜잭션, 워크플로, 공유 코드 등 통합할 때 고려해야 할 지침을 각각 제시하고 둘 사이의 균형점을 찾기 위한 분석 방법을 상세히 소개하고 있다.</p>
<p>그뿐만 아니라 상황에 따라 선택할 수 있는 트랜잭셔널 사가 패턴을 제공하는데 이 패턴들은 통신, 일관성, 조정이라는 세 가지 커플링 힘의 조합에 따라 생성된다. &#39;하드 파트&#39;라는 부제가 붙은 이유는 이렇게 다양한 힘들 사이에서 소프트웨어 아키텍처에 대한 결정을 내리는 것은 &#39;어렵고&#39; 이 구조를 한 번 정하면 &#39;단단해서&#39; 쉽게 바뀌지 않기 때문이다.</p>
<p>책은 크게 1부 따로 떼어놓기와 2부 다시 합치기로 나뉜다. 1부에서는 구조(structure)를 중심으로 아키텍처를 이루는 부품들을 한 조각씩 분리해 구성 요소를 이해할 수 있도록 돕는다. 한편 2부에서는 통신(communication)을 중심으로 구성 요소들이 어떻게 상호작용하고 하나의 단위로 작동하는지 살펴볼 수 있도록 한다.</p>
<p>마지막으로 한빛가이버 사가라는 책 속의 이야기를 통해 기술적인 선택에 대해 이해관계자들과 소통하고 설득하는 모습을 엿볼 수 있도록 한다. 이야기에 수반되는 아키텍처 결정 레코드(ADR)를 곰곰이 읽어 보면 아키텍처 결정을 효과적으로 문서화하는 방식까지 함께 배울 수 있다.</p>
<h2 id="유익한-점">유익한 점</h2>
<p>가장 유용했던 것을 꼽으라면 풍부한 도표였다. 그림을 통해 개념에 대해 정말 명확하게 전달하기 때문에 아키텍처에 대해 배경지식이 거의 없는 사람도 이해하기 쉬웠다. 다양한 데이터나 트레이드오프를 정리한 표도 패턴 간에 비교를 용이하게 해 장단점을 따져보는 것을 돕는다.</p>
<p>그 밖에 생각을 전환시켜준 것을 딱 하나 꼽자면 중복을 죄악시하지 않는 점이다. 유명한 원칙 중에 DRY(Don&#39;t Repeat Yourself)라는 원칙이 있는데 말 그대로 반복하지 말라는 뜻이다. 물론 이 말을 절대적으로 받아들이지는 않았지만 중복이 없어야 더 좋은 코드라는 것이 기본적인 사고방식이었는데 분산 아키텍처에서는 중복을 오히려 권장하는 것을 알고 깜짝 놀랐다. 물론 중복을 허용하면 동기화 문제가 따르게 되지만 분산 아키텍처에서는 중복보다 커플링이 더 나쁜 영향을 끼칠 수 있기 때문에 WET(Write Every Time or Write Everything Twice)하라고 역 조언하기도 한다.</p>
<p>프론트엔드 개발자로서 뷰와 로직의 분리, 클라이언트 상태와 서버 상태의 분리, 컴포넌트 재사용 등 다양한 문제에 대한 해결책으로 분리와 재사용이 최고의 해결 방법이라 생각해왔다. 하지만 이 책 덕분에 현재 구조에서 난항을 겪고 있을 때 좌절하지 않고 분산이라는 다른 해결책을 떠올려볼 수 있다는 점을 알게 되어 안심이 된다.</p>
<h2 id="아쉬운-점">아쉬운 점</h2>
<p>놀랍게도 크게 아쉬운 점이 없었다. 아마 백엔드 개발자도 아니고 아키텍트도 아니라서 부족한 점이나 허점을 눈치채지 못한 게 아닌가 싶다. 그래도 굳이 꼽아보자면 모놀리식 아키텍처를 분산 아키텍처로 분해할 수 있는 Playground를 제공했다면 좋았을 것 같다. 요구사항에 따라 아키텍처를 변경하고 토론을 나누고 ADR까지 남길 수 있게 한다면 책을 넘어서 더 생생한 가치를 제공할 수 있지 않을까?</p>
<h2 id="총평">총평</h2>
<p><img src="https://velog.velcdn.com/images/yisu-kim/post/20f73199-32c0-4d5e-a714-d7e595fea2a2/image.jpeg" alt=""></p>
<p>개발 도서들을 읽다 보면 항상 &#39;은 탄환은 없다&#39;라는 말을 자주 보게 된다. 이 책에서는 특히나 이 점을 경계하고 있는데 모든 문제를 해결하는 마법 같은 아키텍처가 없기 때문이다. 마치 리팩터링이 개발과 별개로 독립되어야 하는 것이 아니라 일부여야 하는 것처럼 자신이 맡은 시스템을 분석하고 반복해서 설계함으로써 점진적으로 더 좋은 아키텍처를 만들어 나갈 수 있다고 강조한다.</p>
<p>사실 정답이 있는 문제라면 이미 딱 맞는 솔루션이 나와서 개발자나 아키텍트를 대체했을 텐데 그런 점에서 정답이 없는 것을 부정적으로 바라볼 필요는 없다.</p>
<h2 id="추천-대상">추천 대상</h2>
<ul>
<li>현재 아키텍처 구조의 한계를 느껴 다양한 아키텍처의 장단점을 알고 싶은 사람</li>
<li>커플링을 확인하고 결합점을 분석해서 트레이드오프를 평가하는 방법을 배우고 싶은 사람</li>
<li>난해하고 답이 없는 문제를 맞닥뜨렸을 때 최선의 선택을 찾아가는 방법을 배우고 싶은 사람</li>
<li>기술을 모르는 이해관계자들에게 의사 결정을 도울 수 있는 기준을 제시하는 방법을 익히고 싶은 사람</li>
</ul>
<h2 id="사족">사족</h2>
<p>이번 책은 500여 쪽인 데다 그림이 많아서 읽기 수월한 편이었다. 처음에 너무 어려운 책을 골랐더니 상대적으로 쉽게 느껴져서 오히려 좋다고 할까? 물론 오늘 하루를 다 바치긴 했지만 서평을 쓸 때도 지난 글보다 조금은 술술 써 내려가는 느낌이 들었다.</p>
<p>팀을 꾸리는 것도 분산 아키텍처를 제대로 작동시키는 것만큼이나 답이 없고 트레이드오프가 존재하는 문제가 아닐까 하는 생각이 들었다. 팀원 한 사람은 각자의 지식(데이터베이스)을 가지고 저마다 특화된 기능(서비스)을 제공하며 이러한 여러 사람들이 직접(동기) 또는 메신저(비동기)로 소통하면서 조직이 굴러가게 된다. 모든 팀원을 슈퍼스타로 채운다고 승리를 보장하지 않는 것처럼 과제에 따라 적절한 조합이 중요하다는 점이 재미있는 포인트다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[자바스크립트로 배우는 SICP]]></title>
            <link>https://velog.io/@yisu-kim/structure-and-interpretation-of-computer-programs-javascript-review</link>
            <guid>https://velog.io/@yisu-kim/structure-and-interpretation-of-computer-programs-javascript-review</guid>
            <pubDate>Sun, 26 Feb 2023 14:28:09 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>한빛미디어 &lt;나는 리뷰어다&gt; 활동을 위해서 책을 제공받아 작성된 서평입니다.</p>
</blockquote>
<p><img src="https://velog.velcdn.com/images/yisu-kim/post/c8807977-8c30-4ed3-8c5f-b30f1b5ebfb1/image.jpeg" alt=""></p>
<h2 id="선정-이유">선정 이유</h2>
<p>별명이란 그 대상에 대한 애정이 있어야 생길 수 있다. &#39;마법사 책(Wizard Book)&#39;이라는 멋진 별명이 붙은 책이라면 제대로 소화를 못 할지라도 시도할 가치가 있다고 생각했다. 이러한 별명이 붙은 이유는 원서의 표지 때문이기도 하지만 저자들이 프로그램을 마법으로 프로그래머를 마법사로 바라보기 때문이기도 하다.</p>
<p><img src="https://velog.velcdn.com/images/yisu-kim/post/eba86ba4-cdfd-4738-a246-49245f61d21c/image.png" alt=""></p>
<blockquote>
<p>이제부터 우리가 공부할 것은 계산적 과정(computational process)이라는 아이디어이다. 계산적 과정은 컴퓨터 안에 사는 추상적인 존재이다. 과정은 점차 전개되면서 데이터라고 부르는 또 다른 존재를 조작하게 된다. 하나의 과정은 일정한 규칙들의 패턴에 따라 전개되는데, 그러한 패턴이 바로 프로그램이다. 인간은 프로그램을 작성함으로써 과정의 전개를 이끈다. 본질적으로 프로그래머는 자신의 주문(spell)들로 컴퓨터의 영혼을 불러낸다.</p>
<p>실제로 계산적 과정은 마법사가 생각하는 영혼의 개념과 아주 비슷하다. 영혼은 볼 수도 없고 만질 수도 없으며, 애초에 어떠한 물질로 만들어진 것이 아니지만, 마법사가 보기에 그 영혼은 엄밀히 실재한다. 마법사가 불러낸 영혼처럼 계산적 과정은 어떤 지적인 일을 할 수 있다. 질문에 답할 수 있고, 은행에서 돈을 지급하거나 공장에서 로봇 팔을 제어해서 세상에 영향을 미칠 수 있다. 우리가 계산적 과정을 불러내기 위해 사용하는 프로그램은 마법사의 주문과 비슷하다. 프로그램은 난해하고 비밀스러운 프로그래밍 언어로 만들어진 기호 표현식들로 세심하게 구성되며, 과정이 수행해야 할 과제들을 상세하게 서술한다.</p>
<p>— p. 43-44</p>
</blockquote>
<p>또 다른 이유는 1984년 초판이 나올 때 사용된 Scheme이라는 언어가 아니라 JavaScript로 다시 쓰였기 때문이다. 프론트엔드 개발자로서 매일 자바스크립트를 다루기 때문에 익숙한 언어로 예제를 볼 수 있다는 점이 메리트로 느껴졌다.</p>
<h2 id="주제-및-구성">주제 및 구성</h2>
<p>이 책은 계산적 과정 다시 말해 프로그래밍의 원리에 대한 이해를 돕기 위해 추상화, 데이터, 모듈, 시간, 동시성, 비결정론 등 중요한 주제들을 포괄적으로 다룬다. 단순한 함수부터 작성하기 시작해 차근차근 복잡도를 높여가며 마침내 컴파일러까지 구현하는 과정을 따라가게 되는데 현실 세계의 복잡한 현상들을 프로그래밍하기 위해 사고방식이 어떻게 변화하는지 고민해볼 수 있다.</p>
<p>1장부터 5장까지 계산적 과정에 대한 모형이 변화하고 발전하는 모습을 소개하는데 이는 각 모형이 현실 세계의 복잡한 개념을 표현하기에 한계를 가지기 때문이다. 1장에서는 치환(대입) 모형, 3장에서는 환경 모형, 4장에서는 메타순환적 평가기, 5장에서는 명시적 제어 평가기 그리고 우리가 흔히 아는 컴파일러가 등장한다.</p>
<p>책은 이러한 모형들에 관해 설명할 때 다음과 같은 순서를 따르고 있다. 먼저 표현식을 사용해 개념을 이해하는 데서 출발한다. 이렇게 만들어진 설계를 자바스크립트 코드를 통해 동작하도록 구현한다. 마지막으로 이 구현에 대한 시뮬레이션을 만들어 검증하는 것으로 마무리한다. 중간중간 등장하는 풍부한 연습 문제는 독자들에게 스스로 학습하고 도전할 기회를 제공한다.</p>
<h2 id="유익한-점">유익한 점</h2>
<p>무엇을 기대했던 내 예상을 벗어났다. 1장에서부터 등장하는 함수는 주인공으로서 책의 마지막 장까지 함께한다. 데이터도 객체도 함수로 표현할 수 있다는 건 다소 충격적이었다. 컴퓨터 프로그램의 구조와 해석에 관해 설명하는 도구가 함수가 될 줄은 몰랐지만 함수에 대한 저자들의 깊은 이해와 함수의 무한한 가능성을 엿볼 수 있었다.</p>
<p>책 전반적으로는 여러 가지 새로운 관점을 제시하고 많은 질문들을 일깨워 주었다. 상수도 함수도 모두 추상화라는 것, 함수와 데이터는 다르지 않다는 것, 시간을 표현하는 방법에 상태뿐 아니라 스트림이 있다는 것, 추상화를 위해서 장벽이 필요하다는 것, 형식을 넘나드는 연산을 구현하는 것이 얼마나 어려운지, ...</p>
<p>프론트엔드 개발자로서 JavaScript나 React를 통해 알게 된 개념에 대해서도 다시 생각해볼 수 있었다. 용어가 직접적으로 등장하지 않는 경우도 있지만 호이스팅, 람다, TDZ, 실행 컨텍스트, 제너레이터, 디스패치, 제너릭 등 내가 이해하는 개념에 대해 설명하는 것이 맞나 추리해 나가는 것은 책을 읽으며 느낀 즐거움 중 하나였다.</p>
<h2 id="아쉬운-점">아쉬운 점</h2>
<p>1장부터 등장하는 수학 예제들은 큰 진입 장벽이라고 본다. 궁극적인 목표인 대규모 시스템 설계를 위해서는 수학적 엄밀함에 익숙해지는 것이 좋겠지만 대부분의 사람들에게 개념을 이해하기 위해 넘기에는 너무 큰 산으로 느껴질 것 같다. 하지만 쉽게 읽히지 않는다는 것을 염두에 두고 적극적으로 리소스를 활용한다면 아무리 어려운 책이라도 읽지 못할 이유는 없다고 본다.</p>
<p>또 하나의 아쉬운 점은 자바스크립트를 자바스크립트라 부를 수 없다는 것이다! 대부분의 개념을 설명할 때 함수 표현을 빌리기 때문에 파이썬이나 그 어떤 다른 언어로 쓰였더라도 크게 달라 보이지 않았을 것 같다. 이는 이 책이 특정 언어에 종속되지 않고 프로그래밍 그 자체의 원리를 설명하는 데 중점을 두고 있기 때문이다. 책이 다루는 주제가 쉽지 않기 때문에 스킴과 비교하면 어떨지 모르겠으나 자바스크립트라서 더 이해하기 쉬울 거라는 가정은 잘못된 것으로 판명 났다.</p>
<h2 id="총평">총평</h2>
<p><img src="https://velog.velcdn.com/images/yisu-kim/post/015cd51b-26d9-4b16-9218-7104cc0be42b/image.jpeg" alt=""></p>
<p>책을 읽다 보면 저자들이 프로그래밍을 마법과 같이 경이로운 것으로 여기며 한 줄 한 줄마다 마법사가 스펠을 쓰듯이 주의를 기울인다는 걸 깨닫게 된다. 하지만 오늘날 대부분의 개발자들은 추상화 덕분에 하드웨어 수준 또는 로우 레벨의 소프트웨어 수준에 대한 이해 없이도 원하는 결과물을 만들어낼 수 있다. 최적화 덕분에 성능에 이상이 생기기까지는 성능에 대해 고민하지 않아도 된다. 이러한 경향은 프로그래밍을 이미 규격화된 공장에서 상품을 찍어내는 것처럼 느껴지게 한다. 하지만 어린 시절 드라이버를 손에 쥐고 전자 제품을 뜯어본 적이 있는 사람이라면 추상화라는 블랙박스를 한번 열어보고 싶을지 모른다.</p>
<h2 id="추천-대상">추천 대상</h2>
<ul>
<li>최근 인기를 끄는 함수형 프로그래밍의 가능성과 한계를 알고 싶은 사람</li>
<li>작고 단순한 프로그램에서 복잡하고 큰 프로그램을 만들어 나가는 방법을 알고 싶은 사람</li>
<li>다른 사람의 것을 가져다 쓰기보다 본인만의 언어나 컴파일러를 만드는 데 관심이 있는 사람</li>
<li>이미 알려진 문제를 빠르게 해결하는 것뿐만 아니라 정답을 알지 못하는 문제를 해결하는 능력을 기르고 싶은 사람</li>
</ul>
<h2 id="참고-자료">참고 자료</h2>
<p>이 책에 정말 관심이 있다면 책만 읽는 것보다 아래 리소스와 함께 활용하는 것을 추천한다.</p>
<p>책에서는 ECMAScript 명세에 따른 JavaScript를 그대로 사용하지 않고 하위언어인 Source라는 언어를 사용하는데 Source Academy의 <a href="https://sourceacademy.org/playground">Playground</a>에서 이 프로그램을 실행할 수 있는 환경을 제공한다.</p>
<p>마찬가지로 Source Academy에는 <a href="https://sourceacademy.org/sicpjs/1">SICP JS 원문</a>도 공개되어 있으므로 영어로 읽는 데 거부감이 없거나 확신이 없어 내용을 훑어보고 싶은 사람들에게 추천한다.</p>
<h2 id="사족">사족</h2>
<p>800쪽이 넘는 책을 약 2주 만에 읽어야 한다니. 연습 문제를 건너뛴 덕분이지만 사실 끝까지 읽을 수 있었던 것이 놀랍다. 한 달에 1권 읽는 것이 그리 어렵지 않을 거라 생각했는데 역시 쉬운 일은 없나 보다. 예제와 연습 문제를 볼 시간도 부족했고 이 서평도 하루 만에 쓰려니 잘못된 정보를 담고 있을까 걱정이다.</p>
<p>chatGPT가 등장한 시대에 추상화란 어떤 의미일까? React를 사용하는 프론트엔드 개발자로서 선언적(declarative) 프로그래밍이라는 개념에 익숙하다. 이처럼 이제는 원하는 결과가 &#39;무엇&#39;인지 설명하고 &#39;어떻게&#39; 프로그램을 구현할지는 chatGPT라는 마법사에게 맡길 날도 머지않은 것 같다.</p>
<blockquote>
<p>감사하게도 2월 우수 리뷰어로 선정되었습니다! 뽑힌 리뷰만 해도 이렇게 많은데 각 도서마다 제출된 리뷰를 하나 하나 검토하기란 쉽지 않겠구나라고 느꼈습니다.</p>
<p>공개를 원하지 않는 분들이 있을 수 있으므로 다른 리뷰어분들의 링크는 숨김 처리하였습니다.</p>
</blockquote>
<p><img src="https://velog.velcdn.com/images/yisu-kim/post/bd0bfae2-0e09-4b34-8067-118c8dda19f4/image.png" alt=""></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[나는 리뷰어다 2023]]></title>
            <link>https://velog.io/@yisu-kim/i-am-a-reviewer-2023</link>
            <guid>https://velog.io/@yisu-kim/i-am-a-reviewer-2023</guid>
            <pubDate>Mon, 13 Feb 2023 13:00:48 GMT</pubDate>
            <description><![CDATA[<h2 id="나는-리뷰어다">나는 리뷰어다</h2>
<p>&#39;나는 리뷰어다&#39;는 한 달에 1권씩 한빛미디어로부터 책을 받아 서평을 공유하는 활동이다. 우연한 기회로 모집 공고를 보고 책을 실컷 읽어보자 하는 마음에 신청하게 되었다. 매월 전달되는 추천 도서 중 보고 싶은 책 3권을 선택하면 그중 1권을 랜덤으로 발송해주는데 약 2, 3주간 책을 읽고 리뷰를 작성하면 된다.</p>
<p>책이 매우 두꺼운 경우가 있어 짧은 기한 내에 다 읽기 쉽지 않아 보인다. 그래도 가능한 책을 80% 이상 읽고 리뷰하는 것이 목표다. 이전에 <a href="https://velog.io/@yisu-kim/the-pragmatic-programmer">북스터디에 참여</a>하거나 <a href="https://github.com/read-with-us/refactoring">직접 운영</a>해본 적이 있는데 책이 랜덤하게 선택되고 시간도 한정되어 올해는 스터디보다는 온전히 혼자만의 독서 시간을 갖게 될 것 같다.</p>
<p><img src="https://velog.velcdn.com/images/yisu-kim/post/5e154aec-fb31-4af4-871b-e9324e4a037f/image.png" alt="&#39;나는 리뷰어다&#39; 모집공고"></p>
<h2 id="행동-수칙">행동 수칙</h2>
<ol>
<li>먼저 책에서 얻고 싶은 것과 버려도 되는 것에 대해 고민한다.</li>
<li>분량에 상관 없이 마감 기간 내에 80% 이상 읽는다.</li>
<li>이때, 완전히 이해하려 하지 말고 모르는 부분은 넘어간다.</li>
<li>리뷰를 쓸 때는 책을 그냥 요약하는 것이 아니라 꼭 개인적인 견해를 넣는다.</li>
<li>관심 가는 도서가 없거나 바쁜 달은 무리하지 말고 넘어간다.</li>
</ol>
]]></description>
        </item>
        <item>
            <title><![CDATA[2022 개발 일지 결산]]></title>
            <link>https://velog.io/@yisu-kim/2022-closing</link>
            <guid>https://velog.io/@yisu-kim/2022-closing</guid>
            <pubDate>Sat, 31 Dec 2022 14:58:22 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>If you can&#39;t measure it, you can&#39;t manage it.</p>
<p>— Peter Drucker</p>
</blockquote>
<p><img src="https://velog.velcdn.com/images/yisu-kim/post/2c008532-340c-4c96-ba9a-2ee3c70e85bf/image.gif" alt="개발 일지 파노라마"></p>
<p><a href="https://velog.io/@yisu-kim/daily-work-records-v2">지난 글</a>에서 템플릿을 업데이트한 뒤로 꾸준히 개발 일지를 작성해 어느덧 1년을 넘겼다. 한 해를 돌아보니 너무나 많은 변화가 일어났고 지금도 어떻게 이렇게 멀리 온 건지 놀라울 뿐이다. 하지만 훗날 돌이켜보면 그저 작은 발걸음을 내디딘 한 해로 기억될 것 같다.</p>
<p>회고이긴 한데 내년에는 측정할 수 있는 데이터를 쌓자는 의미에서 결산이라고 제목을 지었다. 생산성이라는 추상적인 개념을 직접 계산하기는 어렵겠지만 그 주변에 따라붙는 데이터를 좀 모아 보고 싶다.</p>
<h2 id="숫자로-보는-개발-일지">숫자로 보는 개발 일지</h2>
<h3 id="노션-api로-개발-일지-조회하기">노션 API로 개발 일지 조회하기</h3>
<p>노션에서는 integration이라는 기능을 제공하는데 <a href="https://developers.notion.com/docs">노션 Developers 문서</a>를 참고하면 내가 원하는 데이터베이스에 조회/수정/생성 작업을 수행할 권한을 얻을 수 있다. 다양한 기능들이 존재하지만 내게 필요한 건 특정 데이터베이스에 담긴 전체 페이지를 조회하는 API이므로 <a href="https://developers.notion.com/reference/post-database-query">Query a database</a> 노션 문서를 열심히 읽어보았다.</p>
<p>VS Code에서 Thunder Client 익스텐션을 사용해 데이터를 조회했다. 한 번 조회할 때 최대 100개의 페이지를 가져올 수 있고 결과로 받은 next_cursor를 start_cursor 파라미터에 담으면 다음 페이지를 가져올 수 있다. 자세한 사용 방법은 노션 문서를 읽어보면 나오므로 이 글에서는 생략하려 한다.</p>
<p><img src="https://velog.velcdn.com/images/yisu-kim/post/4008d990-fc0e-40fd-b7c5-6318df6ec27c/image.jpg" alt="데이터베이스 조회"></p>
<h3 id="업무-만족도와-태그">업무 만족도와 태그</h3>
<p>이제 조회한 데이터에서 업무 만족도와 태그를 찾아 각각 합산해보니 다음과 같은 결과를 얻을 수 있었다.</p>
<p><img src="https://velog.velcdn.com/images/yisu-kim/post/46dacfb8-fe91-4746-9a59-9b57091cc88b/image.png" alt="업무 만족도와 태그 결산"></p>
<p>우선 업무 만족도를 살펴보자. 2021년 12월부터 2022년 6월까지는 5단계 만족도를 사용했고 2022년 7월부터 2022년 12월 현재까지는 3단계 만족도를 사용했는데 이를 좋음/보통/나쁨 그룹으로 나눠볼 수 있겠다.</p>
<p>좋음 그룹(🟩, 😀, 🙂)의 만족도 개수는 총 205개로 전체 대비 약 80%를 차지하고 보통 그룹(🟨, 😑)의 만족도 개수는 총 35개로 전체 대비 약 14%를 차지한다. 마지막으로 나쁨 그룹(🟥, 😨, 😱)은 총 16개로 전체 대비 약 6%를 차지하는데 이렇게 정리해보니 생각보다 힘들었던 시기는 많지 않고 전반적으로 생산성 있는 한 해였다는 게 눈에 보인다.</p>
<p>이어서 태그들도 살펴보자. 테크 리뷰가 보이는데 중간에 중단된 시기가 있었지만 하반기부터 다시 활성화되어 20회를 진행했다. 온라인으로 컨퍼런스도 2회 참여했고 운 좋게도 면접관으로 참석해 귀한 경험을 쌓았다. 야근 31회라. 1년 중 한 달 정도 야근한 셈이다. 재택근무 중 5일은 코로나로 아픈 와중이었다.</p>
<h3 id="목적-없는-숫자들">목적 없는 숫자들</h3>
<p>숫자와 함께 기억들이 새록새록 떠오르지만 이 수치에 큰 의미를 부여하기는 어렵다.</p>
<p>업무 만족도는 말 그대로 만족도이므로 점수를 매기는 기준이 주관적이고 태그 또한 임의로 달았다보니 누락된 경우도 많다. 예를 들어 배포는 비정기적으로 이루어지니 태그를 다는 걸 깜빡하기도 하고 쉬는 날에는 아예 일지를 적지 않으니 연차 태그는 반차만 기록된다.</p>
<p>무엇보다 이 모든 걸 정확히 기록했다 치더라도 목적이 없다.</p>
<p>작년 12월에 입사하면서 개발 일지를 시작할 때만 해도 이렇게 꾸준히 작성해 데이터가 쌓이리라고는 생각지 못했고 기록을 통해 업무 역량을 증진할 수 있다면 더 바랄 게 없었다. 하지만 막상 데이터를 취합해보니 더 수치화가 가능하고 의미 있는 데이터를 쌓을 수 있지 않았을까 하는 아쉬움이 남는다.</p>
<p>어떤 데이터를 어떻게 쌓을까? 내년의 나에게 고민을 맡긴다.</p>
<h2 id="2022년-에피소드">2022년 에피소드</h2>
<p><img src="https://velog.velcdn.com/images/yisu-kim/post/894a2061-5546-436f-baa0-a487681b8961/image.png" alt="회사 깃허브 잔디"></p>
<p>개인 깃허브의 잔디밭이 이렇게 파릇파릇하다면 좋겠지만 회사 깃허브다. 보면서 곰곰이 생각해보면 커밋하지 않은 날들이 도리어 풀리지 않는 문제에 머리를 싸매고 가장 많이 고민한 날들이었다. 다른 사람의 잔디밭에 잔디를 심으러 간 날도 있고 회의에 참석하느라 하루를 다 보낸 날도 있다. 다시 말해서 커밋으로 나타나지 않는 하루들도 함께 쌓여 오늘 이 자리에 서 있게 된 것이다. 물론 푹 쉬며 재충전한 연차도 포함이다!</p>
<p>이제 작은 에피소드들을 간략히 소개하고 싶다. 보이지 않는 커밋들처럼 나라면 어떻게 했을지 무엇을 느꼈을지 상상해보는 재미를 느끼기 바란다.</p>
<h3 id="🏃-기록하고-리뷰하기">🏃 기록하고 리뷰하기</h3>
<p>가장 먼저 개발 일지에 관해 얘기하지 않을 수 없다. 처음 시작할 때만 해도 작성 시간이 불규칙했는데 어느새 다음 날 오전 출근 직후로 고정되었다. 8시 반에 사무실에 들어서 불을 켜면 고요한 침묵으로 반겨준다. 그 가운데 어제 작업한 커밋과 코드를 살피며 일지를 작성하다 보면 한 번쯤은 꼭 오류나 개선할 부분이 보인다. 멘토도 코드 리뷰와 같은 피드백도 없는 환경이라 스스로 책임질 수밖에 없으니 깊이 안도하면서 얼른 코드를 고쳐 다시 올리곤 했다.</p>
<h3 id="🏃-결정하고-책임지기">🏃 결정하고 책임지기</h3>
<p>한 번은 신규 서비스를 런칭하며 새로운 스택을 여럿 도입해 체험해 볼 기회가 있었다. 그런데 디자이너분들이 마침 입사하면서 디자인이 완전히 뒤바뀌게 되었고 선택했던 라이브러리가 커스터마이징이 힘든 라이브러리라 나를 포함해 동료 개발자분들의 생산성을 낮추는 것이 눈에 보였다. 이미 개발은 끝나가는 상태라 밀고 나가기로 결정하고 해당 라이브러리 문서와 DOM을 샅샅이 살펴보며 다소 억지를 써서라도 디자인에 맞게 구현했다. 요구사항을 맞추기가 가장 까다로웠던 Input 컴포넌트는 아예 라이브러리를 쓰지 않고 직접 만들어 사용했다.</p>
<h3 id="🏃-루틴으로-롱런하기">🏃 루틴으로 롱런하기</h3>
<p>8시 반에 출근해 5시 반에 퇴근한다. 아침에는 일부러 지하철역 한 정거장 거리를 20분간 걷는다. 걸어가면서 개발 트렌드를 살피거나 전자책을 보곤 하는데 도서 종류는 가리지 않고 내키는 대로 골라 읽는다. 회사에 도착하면 어김없이 개발 일지를 열고 오늘 할 일들을 떠올려 본다. 이제 칼같이 퇴근하려면 한눈팔지 말고 아쉬움 없이 일해야 한다. 퇴근할 즈음에는 아직 길이 막히지 않아 1시간이면 집에 도착한다. 저녁 먹고 휴식하다가 알람을 맞춰둔 8시에 일어나 할 일을 하고 12시 전에는 취침한다. 이상이 약 1년간 유지해온 루틴이지만 매일 지켰던 것은 결코 아니고 큰 흐름만 벗어나지 않도록 조심했다.</p>
<h3 id="🤼-조직에서-신뢰쌓기">🤼 조직에서 신뢰쌓기</h3>
<p>월요일 오전에는 지난주에 작성한 개발 일지를 보며 잘한 점, 잘못한 점, 개선할 점을 정리하는데 매주 카페에서 회고 시간을 갖기 때문이다. 작은 조직이라 프론트엔드, 백엔드, 디자인까지 팀원들이 모두 모여 한 주를 되돌아본다. 맡은 일을 해결하거나 팀에 도움이 되는 일을 처리한 경험을 공유하고 까다로운 고민을 나누는 시간이다. 초기에는 정말 회고만 했는데 동료 개발자분이 개선할 점을 재확인하면 좋겠다는 의견을 내주었고 이에 착안해 액션 아이템 테이블을 만들어 목록을 함께 보기 시작했다. 회고 시간이 끝나면 액션 아이템을 훑어보며 내 일이 아니더라도 도울 수 있는 문제가 없을까 고민해본다. 서로의 약점을 보완하고 기회를 주며 협력하는 팀원들에게 늘 감사하다.</p>
<h3 id="🤼-위임하고-기다리기">🤼 위임하고 기다리기</h3>
<p>초기 스타트업이라 일은 많고 사람은 적다 보니 신입이 들어왔을 때 세세한 케어가 어려웠다. 사실상 방목형으로 교육했는데 그 과정에서조차 내 약점을 직시할 기회가 많았다. 태스크를 보고 요구사항을 빠르게 파악해 적절한 답변을 주어야 하는데 컨텍스트를 확실히 모르는 상황에서 잘못된 방향으로 가이드했다가 뒤늦게 수정한 적이 있는가 하면 모르는 문제와 마주쳐 바로 답변을 줄 수 없을 때는 속으로 뜨끔하고는 어떻게 문제에 접근해야 할지 어떤 키워드로 구글링하면 되는지 보여주기도 했다. 때로는 질문에 바로 답변하는 방식이 스스로 성장할 기회를 빼앗는 걸까 하는 걱정이 생기곤 하지만 본인 스스로 충분히 고민해봤다고 믿고 있다. 반대로 디버깅하는 과정을 옆에서 보면서 조언을 통해 스스로 문제를 찾아 나가는 모습을 지켜볼 때는 믿고 기다린다는 감각을 경험해볼 수 있었다. 멘티가 입사한 지 약 6개월이 지났을 무렵 프로젝트를 독립적으로 맡겼고 이제는 질문을 받았을 때 서포트만 하면 된다. 다른 사람이 발전하는 과정의 일부가 될 기회를 준 멘티에게 감사의 마음을 전한다.</p>
<h3 id="🤼-통제보다-지원하기">🤼 통제보다 지원하기</h3>
<p>9월 말 우연히 <a href="https://www.wanted.co.kr/events/devcrew_study">원티드 이벤트</a>를 보고 스터디 그룹 리더에 자원했다. 스터디도 처음이고 스터디 리더도 처음인데 학습과 교류에 목이 말랐던지 무턱대고 지원했고 뜻밖에 합격의 기쁨을 누리게 되었다. 부랴부랴 스터디 커리큘럼을 정리해 전달했고 원티드에서 <a href="https://www.wanted.co.kr/events/devcrew_study1th_refactoring">스터디 모집 페이지</a>를 만들어 홍보에 도움을 주었다. 주말 스터디라서인지 마감 며칠 전에 스터디 인원이 채워졌고 아슬아슬하게 스터디가 시작되었다. 팀원들을 만나기 전 정한 스터디 룰은 단 하나 &#39;모르는 것은 물어보고 아는 것은 공유하기&#39;였다. 그 밖에 출석하지 않은 사람에게 패널티를 주거나 출석한 사람에게 인센티브를 주지는 않았다. 하지만 리팩터링 책의 코드를 커밋해 공유하거나 스터디가 끝나고 남는 시간 동안 회사 생활에 관해 이야기를 나누는 코너를 만드는 등 스터디를 중도에 포기하지 않고 계속 참여하도록 여러 방법을 고안해 시도해보았다. 초보 리더를 따라 함께 스터디를 완주하며 즐거운 기억으로 남게 도와준 팀원들에게 그저 감사할 뿐이다.</p>
<h3 id="🤸-삽질하고-발표하기">🤸 삽질하고 발표하기</h3>
<p>나는 발표를 할 수 없다. 오랫동안 이렇게 생각해왔는데 이 시각을 바로잡은 것만으로 올 한 해를 잘 보냈다고 본다. 매주 목요일에는 프론트엔드, 백엔드, 디자인 팀원들이 모두 모여 내부적으로 워크샵을 진행한다. 처음에는 개발팀에서만 진행했는데 디자이너분들이 합류한 이후로 다 함께 지식을 공유하고 있다. 보통 지난주에 새롭게 알게 된 지식이나 해결했던 문제를 요약해 돌아가며 짧게 발표하는데 주제 선정도 참여도 모두 자유롭다. 아래 사진에서 태그가 달린 날들은 듣기만 하지 않고 태그에 작성된 주제로 발표한 날이다. 떨리거나 말거나 소리 내어 내가 가진 지식을 공유하고 다른 사람들에게 도움이 될 수 있다는 걸 깨달았다는 점이 올해의 큰 소득이다.</p>
<p><img src="https://velog.velcdn.com/images/yisu-kim/post/aef8b2f8-a0a7-42ff-a7c4-a7befe94bc4b/image.png" alt="테크 리뷰 목록"></p>
<h3 id="🤸-두려움을-넘어서기">🤸 두려움을 넘어서기</h3>
<p>오픈소스는 늘 다가가기 어려운 거대한 산이라는 느낌을 받았는데 우연히 업무 중 소셜 로그인을 구현하다 Next Auth 라이브러리의 오류를 발견한 것을 계기로 처음으로 PR을 날리게 되었다. 문서 수정 <a href="https://github.com/nextauthjs/next-auth/pull/4361">PR</a>과 코드 수정 <a href="https://github.com/nextauthjs/next-auth/pull/4365">PR</a>을 각각 1개씩 올려보니 마음속 허들을 많이 낮출 수 있었다. node_modules 내에 있는 라이브러리 코드들을 살펴보지 않았던 시간이 아까울 뿐이다. 이후 토스에서 만든 오픈소스 slash의 깃허브를 구경하다 깨진 링크를 발견했을 때도 머뭇거리지 않고 문서 수정 <a href="https://github.com/toss/slash/pull/119">PR</a>을 보낼 수 있었다.</p>
<h3 id="🤸-뛰어들어-도전하기">🤸 뛰어들어 도전하기</h3>
<p>노마드코더 슬랙 채널에 올라온 공고로부터 이지스퍼블리싱과 인연이 시작됐다. 개발 도서를 읽고 강의 자료를 만드는 일이었는데 가장 처음 맡았던 책은 《Do it! 클론 코딩 영화 평점 웹 서비스》로 노마드코더에서 강의를 들었던 터라 내용이 낯설지 않아 자료를 만드는 것이 그리 어렵지 않았다. 생각보다 시간을 엄청나게 소모했지만 기한이 한 달 정도로 여유가 있었고 강의하는 사람의 입장에서 한편으로는 강의를 듣는 학생의 입장에서 생각해볼 수 있다는 점이 새로웠다. 강의자료를 강사와 학생 사이의 인터페이스로 바라보면 프론트엔드와 관련이 있다고 봐도 무리는 아닐 것 같다. 강의 자료 제작을 무사히 마친 뒤 계속 소개를 받아 《Do it! 조코딩의 프로그래밍 입문》, 《Do it! 자료구조와 함께 배우는 알고리즘 입문 - C언어》, 《Do it! 깃&amp;깃허브 입문 - 개정판》 이렇게 3개의 자료를 추가로 제작했다. 색다른 경험이었지만 스터디와 병행할 수 없어 한 차례 고사했고 스터디를 마치고 나니 주어지는 책보다는 원하는 책을 읽고 싶은 마음이 더 커져 버렸다. 결국 마지막 제작 의뢰도 정중히 거절하며 꼼꼼한 편집자분들과는 다음 만남을 기약하기로 했다.</p>
<p><img src="https://velog.velcdn.com/images/yisu-kim/post/71dc5272-649e-43df-86b8-60a1aa8d7d4b/image.png" alt="제작한 강의자료 PPT 표지들"></p>
<h2 id="2023년-프리뷰-없음">2023년 프리뷰 없음</h2>
<p>사실 이것저것 내년 목표를 세워서 목차를 쭉 적어두었다가 미래는 알 수 없다는 생각에 결국 모두 지워버렸다. 작년 12월만 돌이켜봐도 계획대로 이룬 건 개발 일지를 계속해서 작성한 것밖에는 없지만 상상도 못 한 경험과 도전을 마주하게 되었다.</p>
<p>프론트엔드 개발자가 되기 위해 준비할 때부터 가장 강렬하게 느낀 감정은 &#39;재미&#39;였다. 프로가 된 지금도 그 마음은 변함이 없고 사실 실력이 늘수록 더욱 발전하는 재미를 느끼고 있다. 그러나 즐거움을 느끼는 것과 동시에 &#39;멍청이 산 정상&#39;에 오른 게 아닌가 하는 기분도 느꼈다. 2022년 에피소드에는 좋은 추억만 써놓았지만 사실 부족했던 점들도 엄청 많은데다 속도를 멈추고 조금만 주변을 둘러보면 아직 멀었구나 하는 생각이 들기 때문이다.</p>
<p>1년간 React를 사용했는데 내가 React에 대해 아는 게 뭘까? 어제보다 더 나은 코드를 작성하고 있나? 왜 이 기능을 구현해야 하는 걸까? 사용자들에게 효과가 가 닿고 있을까? 비즈니스를 고려하고 그에 따라 우선순위를 결정하고 있는 걸까? 생산성을 높이려면 어떻게 해야 하지? 팀이 유기적으로 움직일 수 있도록 뭘 할 수 있을까? ...</p>
<p>2023년에는 어떤 질문에 답하게 될까</p>
<p><img src="https://velog.velcdn.com/images/yisu-kim/post/53d168bc-50d6-4371-a0cf-c5ced1ddebdd/image.png" alt="더닝 크루거 효과"></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[antd-mobile 비동기와 메모리 누수]]></title>
            <link>https://velog.io/@yisu-kim/antd-mobile-promise-and-memory-leaks</link>
            <guid>https://velog.io/@yisu-kim/antd-mobile-promise-and-memory-leaks</guid>
            <pubDate>Tue, 27 Sep 2022 15:24:47 GMT</pubDate>
            <description><![CDATA[<h2 id="에러-및-배경">에러 및 배경</h2>
<p>아래 에러 메시지는 React 개발자들에게는 꽤 낯익을 것 같다. 언마운트된 컴포넌트에 대해 수행할 수 없는 상태 업데이트가 일어나고 있으며 이는 메모리 누수를 나타낸다는 뜻이다. 일반적으로 이 문제를 해결하기 위해서는 useEffect의 cleanup 함수를 사용해 모든 구독과 비동기 작업을 취소하면 된다.</p>
<blockquote>
<p>Can&#39;t perform a React state update on an unmounted component. This is a no-op, but it indicates a memory leak in your application. To fix, cancel all subscriptions and asynchronous tasks in a useEffect cleanup function.</p>
</blockquote>
<p>입력을 마치고 폼을 제출한 후 다음 페이지로 이동하는 시점에서 위 에러가 발생했으므로 컴포넌트에 선언한 상태 중 navigate로 이동한 후에 업데이트되는 상태가 있는지 확인하기로 했다.</p>
<h2 id="삽질">삽질</h2>
<p>문제는 아무리 살펴봐도 해당 컴포넌트에 선언한 상태 중 언마운트된 후에 업데이트되는 상태가 없었다는 점이다. 이 시점에서 잘못된 선택을 하게 되는데 에러 메시지로 되돌아가는 것이 아니라 무턱대고 구글링을 시작하고 말았다.</p>
<p>안타깝게도 한참을 엉뚱한 곳에서 헤맨 후에야 아래 원칙을 떠올리고 원점으로 돌아갔다. </p>
<blockquote>
<p>길을 잃었을 때는 제자리로 돌아가야 한다.</p>
</blockquote>
<h2 id="문제-해결">문제 해결</h2>
<p>결국 스택 트레이스를 자세히 살펴봄으로써 해결할 수 있었다. 노란 박스 안에 있는 링크를 클릭해 첫 번째 에러 발생 지점의 코드를 살펴보기로 했다.</p>
<p><img src="https://velog.velcdn.com/images/yisu-kim/post/0bcf774a-0e7e-46a7-828e-2997924a5444/image.png" alt="stack trace"></p>
<p>에러가 발생한 지점은 antd-mobile의 Button 컴포넌트 내 innerLoading 상태(노란 박스 참고)였다. 이 부분을 보자마자 <a href="https://mobile.ant.design/components/button#props">Button 컴포넌트 문서</a>에서 읽었던 loading과 onClick에 전달되는 비동기 함수 사이의 관계가 기억났다.</p>
<p><img src="https://velog.velcdn.com/images/yisu-kim/post/5a102a42-ddee-4f26-a40c-5fce34a41382/image.png" alt="adm-button"></p>
<p>파란 박스친 부분의 코드를 더 살펴보면 onClick에 promise가 전달될 경우 innerLoading 상태를 true로 바꾸고 promise 실행이 완료되면 다시 false로 바꾸는 것을 볼 수 있다.</p>
<p>결과적으로 onClick에 비동기 함수를 전달하는 것이 아니라 내부에서 비동기 함수를 선언하고 호출함으로써 에러 메시지를 없앨 수 있었다.</p>
<pre><code class="language-jsx">import { Button } from &quot;antd-mobile&quot;;
import { useForm } from &quot;react-hook-form&quot;;

export default function ExampleComponent() {
  const { ..., handleSubmit } = useForm();

  const onSubmit = () =&gt; {
    const submit = async () =&gt; { // 내부에서 비동기 함수 선언
      try {
        const response = await ...
      }
    };

    submit(); // 비동기 함수 호출
    // promise를 반환하지 않으므로 innerLoading 상태가 업데이트되지 않게 됨
  }

  return (
    ...
    &lt;Button onClick={handleSubmit(onSubmit)}&gt; 
      확인
      &lt;/Button&gt;
  )
}</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[병목을 찾아라]]></title>
            <link>https://velog.io/@yisu-kim/daily-work-records-v2</link>
            <guid>https://velog.io/@yisu-kim/daily-work-records-v2</guid>
            <pubDate>Sun, 31 Jul 2022 12:26:24 GMT</pubDate>
            <description><![CDATA[<h2 id="지난-이야기">지난 이야기</h2>
<p><a href="https://velog.io/@yisu-kim/daily-work-records">지난 글</a>에서 프론트엔드 개발자로 취직한 후 매일 개발일지를 쓰고 있다고 말했었다. 8개월이 지났지만 다행히 하루도 빼먹지 않고 기록을 이어 나가고 있다.</p>
<p>글을 쓰던 당시 일지를 개선하고자 아이디어를 몇 개 떠올렸지만 최근까지 초기 버전을 유지했다. 성급하게 변경해서 습관을 깨고 싶지 않았기 때문이다.</p>
<p>그래도 고심 끝에 변경 사항과 추가 사항을 각각 1개로 제한해 새롭게 적용했다. 바로 &#39;업무 만족도&#39;와 &#39;오늘의 병목&#39;이다.</p>
<p><img src="https://velog.velcdn.com/images/yisu-kim/post/7e3b3b41-94ce-4777-a0b8-bca43ffc9b50/image.png" alt="rule"></p>
<h2 id="업무-만족도">업무 만족도</h2>
<p>지난 한 달을 되돌아보아야겠다고 마음을 먹고 캘린더 뷰를 추가했을 때 기존 업무 만족도 아이콘(😀 🙂 😑 😨 😱)은 작아서 표정이 잘 보이지 않았다. 문득 신호등이 떠올라 3가지 상태 아이콘(🟥 🟨 🟩)으로 변경하기로 했다. 색상으로 구분하니 크기에 구애받지 않아 한 달간의 상태가 한눈에 들어왔다.</p>
<p><img src="https://velog.velcdn.com/images/yisu-kim/post/c1985aa3-9a19-4467-87d2-fc92677abeac/image.png" alt="calendar view"></p>
<p>단계도 5에서 3으로 줄었기 때문에 그날에 대한 총평을 결정하는 데 걸리는 시간이 조금은 감소한 것 같다. 어떻게 하면 더 짧은 시간에 지금과 같이 일지를 작성할 수 있을까가 늘 고민거리다.</p>
<h2 id="오늘의-병목">오늘의 병목</h2>
<p>병목에 대해 생각하게 된 계기는 개발일지를 쓰면서가 아니라 신입으로 들어온 부사수의 질문을 받은 덕분이다. JavaScript 문제는 대부분 즉시 해결책을 알고 답해줄 수 있었지만 CSS 문제는 시간이 좀 걸리고 검색을 종종 필요로 했다.</p>
<p>문제 해결은 문제 정의에서부터 시작된다. CSS만 문제였던 것은 아니므로 어디에서 개발 시간이 예상보다 길어지는지 측정할 방법을 고민하기 시작했다. 개개의 태스크마다 소요 시간을 측정하면 될까? 한 가지 일을 진득하게 할 때도 있지만 회의나 커뮤니케이션 때문에 흐름이 끊기고 컨텍스트가 계속 바뀌는 일이 다반사다. 이런 상황에서 정확히 시간을 추적해 기록하는 건 어려운 일이다.</p>
<p>하지만 그날 무슨 일이 가장 까다로웠는지 기억하는 건 어떨까. 이미 어떤 일을 하는지 매일 기록하고 있으니까 그중 가장 많은 시간을 쓴 작업을 집어내기만 하면 된다. 그래서 &#39;오늘의 병목&#39;이라는 속성을 추가하기로 했다.</p>
<p><img src="https://velog.velcdn.com/images/yisu-kim/post/88b67c86-9616-4ead-a681-ce590ce5541f/image.png" alt="bottleneck of the day"></p>
<p>&#39;오늘의 병목&#39;은 도입한 지 고작 2주째인데 기록한 내용에서 정수를 뽑아내는 데 큰 도움을 주었다. 크게 나누면 다음과 같은 항목에서 많은 시간을 소요한다는 점을 깨닫게 되었다.</p>
<ol>
<li>충분히 습득 못한 기술</li>
<li>커뮤니케이션 증가</li>
<li>잘못된 설계</li>
<li>단순 노가다</li>
</ol>
<p>4가지 항목 모두 당연히 시간을 어느 정도 필요로 하지만 조금이나마 시간을 단축할 방안을 떠올릴 수 있었다.</p>
<ol>
<li>기술적으로 부족한 과목을 정확히 알게 되었으니 한 주제씩 단기간에 집중적으로 학습해보자.</li>
<li>최근 디자이너분들이 합류하면서 커뮤니케이션 채널이 늘어났는데 다른 조직에서는 어떻게 소통하는지 알아보자.</li>
<li>무턱대고 개발부터 하지 말고 어떻게 해야 주어진 요구사항에 대응할 수 있는지 먼저 고민하자.</li>
<li>노가다는 위임하자. 예를 들어, 약관 페이지를 만들 때 1시간 가까이 HTML을 하나하나 작성하다가 디자인이 정해져 있지 않으므로 노션에서 간단히 export한 HTML을 사용할 수 있다는 사실을 발견해 10분 만에 해결한 적이 있다.</li>
</ol>
<h2 id="템플릿">템플릿</h2>
<blockquote>
<p>2022년 11월 5일자로 <a href="https://yisu-kim.notion.site/4622e822050f484ab60d1b8844aacab9">템플릿</a>이 추가되었습니다.</p>
</blockquote>
<p><img src="https://velog.velcdn.com/images/yisu-kim/post/f05a72b3-165e-4589-be72-97587d6495cd/image.png" alt="1편 댓글"></p>
<p>특별한 양식은 아니다보니 템플릿까지 만들 생각은 없었는데 우연히 1편에 달린 댓글을 발견하고 부랴부랴 템플릿을 공유하게 되었다.
더해서 댓글에만 달아두면 템플릿에 관심이 있지만 놓치는 분들이 있을 수 있어 본문도 수정해두기로 했다.</p>
<p>사족이지만 정작 질문한 분은 템플릿을 공유받지 못하고 다른 분이 공유받는 걸 보면서 더더욱 답을 얻을 수 없어도 질문을 멈추지 말아야겠다는 생각이 들었다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[antd-mobile Form 상태 다루기]]></title>
            <link>https://velog.io/@yisu-kim/antd-mobile-form-state</link>
            <guid>https://velog.io/@yisu-kim/antd-mobile-form-state</guid>
            <pubDate>Thu, 30 Jun 2022 14:51:24 GMT</pubDate>
            <description><![CDATA[<h1 id="ant-design-mobile">Ant Design Mobile</h1>
<blockquote>
<p>Explore the limits of mobile web experience</p>
<p>by <a href="https://mobile.ant.design/">Ant Design Mobile</a></p>
</blockquote>
<p>회사에서 신규 프로젝트에 antd-mobile을 사용하기로 했다. 페이지 소개에 적힌 문구를 보면 자신감이 넘치는데 광범위하게 제공하는 컴포넌트를 보면 그럴만하다는 생각이 든다. 다만 아직 사용해본 지 며칠 지나지 않았으므로 앞으로 여러 가지 문제를 맞닥뜨리게 될지도 모르겠다.</p>
<p>이 글에서는 입력 폼을 구현하던 도중 부딪힌 어려움에 대해 다룬다. gist 하나 저장해두면 끝인 간단한 문제지만 혹시 같은 고통을 겪는 분들에게 도움이 되기를 바라며 블로그로 공유해본다.</p>
<h2 id="form">Form</h2>
<blockquote>
<p>High-performance form controls with built-in data field management. Including data entry, verification and corresponding styles.</p>
<p>by <a href="https://mobile.ant.design/components/form">antd-mobile Form</a></p>
</blockquote>
<p>antd-mobile에서는 자체적으로 Form의 필드를 관리하는 유용한 기능을 제공한다.  Form.useForm()으로 생성한 FormInstance 객체를 받아 컨트롤을 돕는 form props가 있고 그 밖에도 onFieldsChange, onValuesChange, onFinish, onFinishFailed props 등을 사용해 다양한 이벤트를 관리할 수 있게 도와준다.</p>
<p><img src="https://velog.velcdn.com/images/yisu-kim/post/fffa4d38-a060-40f2-bd8a-4e469f891595/image.png" alt="props of Form"></p>
<h2 id="react-hook-form">React Hook Form</h2>
<p>그러나 antd-mobile과 react-hook-form의 상성이 좋은지는 의문이다. 회사의 다른 서비스에서는 모두 이 라이브러리를 사용하고 있었고 mui와 잘 어울리므로 antd에도 쉽게 적용할 수 있을 거라 생각했다. 그런 생각에 가벼운 마음으로 도전했으나 value와 onChange 이벤트 핸들링을 거쳐 에러 메시지를 표시하는 부분에서 난관에 빠졌다.</p>
<p>이리저리 구글링해보고 깃허브 이슈에서 검색해보아도 이전 버전에 대한 해결 방법만 찾을 수 있었다. 약 반나절을 소모한 뒤 antd-mobile의 버전을 내리기보다는 react-hook-form을 배제하고 antd-mobile만 순수하게 사용하기로 했다.</p>
<h2 id="form-state를-관리해보자">form state를 관리해보자</h2>
<blockquote>
<p>📦 <a href="https://codesandbox.io/s/antd-form-state-icy3xh">codesandbox</a>에서 실습이 가능합니다.</p>
</blockquote>
<p>문서를 참고해 Form.item의 rules로 에러 메시지를 쉽게 표현할 수 있었다. 그런데 react-hook-form처럼 버튼을 비활성화하려고 하니 isDirty나 isValid와 같은 form state를 따로 제공하지 않는 게 아닌가! 다행히 직접 상태를 관리함으로써 해결할 수 있어 실습 코드와 설명을 남긴다.</p>
<h3 id="form-validation">Form validation</h3>
<p>일단 validation을 추가한 간단한 Form을 살펴보자. rules에 필드가 required여야 한다는 간단한 조건을 추가했다.</p>
<pre><code class="language-jsx">export default function SampleForm() {
  return (
    &lt;Form
      footer={
        &lt;Button block type=&quot;submit&quot; color=&quot;primary&quot;&gt;
          Submit
        &lt;/Button&gt;
      }
    &gt;
      &lt;Form.Item
        name=&quot;name&quot;
        label=&quot;Name&quot;
        rules={[{ required: true, message: &quot;Please input your name&quot; }]}
      &gt;
        &lt;Input placeholder=&quot;Enter your name&quot; /&gt;
      &lt;/Form.Item&gt;
    &lt;/Form&gt;
  );
}</code></pre>
<h3 id="isdirty--isvalid">isDirty &amp; isValid</h3>
<p>먼저 form을 컨트롤하기 위해 Form.useForm()으로 FormInstance를 생성한다.
이어서 isDirty와 isValid를 각각 state로 선언하고 둘 중 하나라도 false라면 버튼이 비활성화되도록 조건을 추가한다.
Form의 onFieldsChange 이벤트가 발생할 때 form.isFieldsTouched()에 따라 isDirty를, form.getFieldsError()의 에러 개수에 따라 isValid를 업데이트하면 된다.</p>
<pre><code class="language-jsx">export default function SampleForm() {
  const [form] = Form.useForm();
  const [isDirty, setIsDirty] = useState(false);
  const [isValid, setIsValid] = useState(false);

  return (
    &lt;Form
      form={form}
      footer={
        &lt;Button
          block
          type=&quot;submit&quot;
          color=&quot;primary&quot;
          disabled={!isDirty || !isValid} // isDirty와 isValid를 사용해 값이 입력되지 않았거나 입력된 값이 유효하지 않으면 버튼을 비활성화
        &gt;
          Submit
        &lt;/Button&gt;
      }
      onFieldsChange={() =&gt; {
        setIsDirty(form.isFieldsTouched()); // 필드가 하나라도 터치(작동)되었다면 form에 변화가 있다고 판단
        const hasError = form
          .getFieldsError()
          .some(({ errors }) =&gt; errors.length); // 필드별 에러 메시지가 하나라도 존재하는지 확인
        setIsValid(!hasError); // 에러 메시지가 하나도 없으면 form이 유효하다고 판단
      }}
    &gt;
      &lt;Form.Item
        name=&quot;name&quot;
        label=&quot;Name&quot;
        rules={[{ required: true, message: &quot;Please input your name&quot; }]}
      &gt;
        &lt;Input placeholder=&quot;Enter your name&quot; /&gt;
      &lt;/Form.Item&gt;
    &lt;/Form&gt;
  );
}</code></pre>
<p>아래 이미지처럼 에러 메시지와 버튼 활성화 상태가 자연스럽게 변경되는 것을 확인할 수 있다.</p>
<p><img src="https://velog.velcdn.com/images/yisu-kim/post/2cf67194-fab9-45df-9a7f-08f7b04e2976/image.gif" alt="antd form"></p>
<h2 id="참고자료">참고자료</h2>
<ul>
<li><a href="https://mobile.ant.design/components/form">https://mobile.ant.design/components/form</a></li>
<li><a href="https://github.com/ant-design/ant-design/issues/15674#issuecomment-1059949950">https://github.com/ant-design/ant-design/issues/15674#issuecomment-1059949950</a></li>
</ul>
<blockquote>
<p>잘못된 부분에 대한 지적은 언제든 환영합니다! 😉</p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[next-auth 오픈 소스에 기여하기]]></title>
            <link>https://velog.io/@yisu-kim/contribute-to-next-auth</link>
            <guid>https://velog.io/@yisu-kim/contribute-to-next-auth</guid>
            <pubDate>Sat, 30 Apr 2022 14:58:11 GMT</pubDate>
            <description><![CDATA[<h2 id="oauth">OAuth</h2>
<h3 id="oauth-20-프로토콜">OAuth 2.0 프로토콜</h3>
<blockquote>
<p>OAuth 2.0 is the industry-standard protocol for authorization. OAuth 2.0 focuses on client developer simplicity while providing specific authorization flows for web applications, desktop applications, mobile phones, and living room devices. This specification and its extensions are being developed within the IETF OAuth Working Group.</p>
<p>by <a href="https://oauth.net/2/">OAuth</a></p>
</blockquote>
<p>클라이언트 개발자들은 업계 표준 프로토콜인 OAuth를 통해 표준화된 인가 방식을 따르게 되면서 고유한 서비스 개발에 집중할 수 있게 되었다. 이 시리즈에서는 2.0 버전에 대한 간단한 소개와 함께 OAuth 기반의 소셜 로그인 방법을 알아보고자 한다.</p>
<p>OAuth 공식 페이지에서는 아래 그림과 함께 <a href="https://datatracker.ietf.org/doc/html/rfc6749#section-1.2">프로토콜의 흐름</a>에 대해 설명하고 있다. 여기서 4개의 <a href="https://datatracker.ietf.org/doc/html/rfc6749#section-1.1">역할들</a>이 등장하는데 먼저, Client는 사용자를 대신해 보호받는 리소스에 대한 권한을 요청하는 어플리케이션이고 Resource Owner는 그 권한을 부여하는 사용자이다. 또한, Authorization Server는 사용자 인증이 성공하면 액세스 토큰을 발급하는 서버이고 Resource Server는 이 토큰을 전달받아 보호받는 리소스를 반환해주는 서버이다.
<img src="https://velog.velcdn.com/images/yisu-kim/post/5ff94a56-697c-489f-bc3c-dfcf3f8b8fae/image.png" alt="추상적인 프로토콜 흐름"></p>
<p>(A) Client는 Resource Owner에게 권한을 요청한다. 현재 이 글을 쓰고 있는 velog에서도 깃허브 등 소셜 로그인을 제공하는데 아래 이미지와 같이 사용자는 GitHub에 로그인하고 velog에서 요청하는 권한을 허용할 수 있다.
<img src="https://velog.velcdn.com/images/yisu-kim/post/382de25b-8f15-439a-b12f-dc1a0e02b659/image.png" alt="소셜 로그인 및 권한 부여"></p>
<p>(B) Client는 요청한 권한을 부여받는다. 보통 소셜 로그인에서는 Redirect URL을 통해 Authorization Code를 발급받는 방식을 사용하는데 이러한 허가 방식에는 여러 유형이 있으므로 이에 대해 자세히 알고 싶다면 <a href="https://oauth.net/2/grant-types/authorization-code/">Grant Types</a> 문서를 참고하면 된다.</p>
<p>(C) Client는 Authorization Server에 액세스 토큰을 요청한다. Authorization Code를 발급받은 경우 이를 전달하면 된다.</p>
<p>(D) Authorization Server는 권한 부여가 유효한지 확인하고 유효하면 액세스 토큰을 발급한다.</p>
<p>(E) Client는 Resource Server에 보호받고 있는 리소스를 요청하고 액세스 토큰을 제시하여 인증받는다.</p>
<p>(F) Resource Server는 액세스 토큰이 유효한지 확인하고 유효하면 요청받은 리소스를 반환한다.</p>
<h3 id="왜-oauth인가">왜 OAuth인가</h3>
<blockquote>
<p>This is the problem OAuth solves. It allows you, the User, to grant access to your private resources on one site (which is called the Service Provider), to another site (called Consumer, not to be confused with you, the User). While OpenID is all about using a single identity to sign into many sites, OAuth is about giving access to your stuff without sharing your identity at all (or its secret parts).</p>
<p>by <a href="https://oauth.net/about/introduction/">OAuth</a></p>
</blockquote>
<p>그렇다면 OAuth를 왜 사용해야 할까? 오늘날 많은 서비스들은 다른 서비스의 기능을 연결하여 자신의 서비스에서 제공하는 기능을 한층 풍성하게 만든다. OAuth가 등장하기 전에는 다른 서비스와 연결을 원하는 사용자에게 서비스마다 제각각의 방법으로 권한을 요구했다. 다른 서비스의 아이디와 패스워드를 요구하는 경우 사용자는 자신의 계정에 대한 모든 권한을 넘겨주게 되고 이는 보안상의 위험이나 계정에 대한 상실로 이어질 수 있다.</p>
<p>반면에 OAuth 프로토콜을 따르게 되면 사용자는 한 서비스(Provider)에서 다른 서비스(Consumer)로 자신이 공유하길 원하는 정보를 안전하게 넘겨줄 수 있게 된다. 이 시리즈에서 살펴볼 소셜 로그인은 대표적인 활용 케이스로 사용자는 소셜 로그인을 통해 원하는 서비스에 편리하게 가입하고 로그인할 수 있다.</p>
<h2 id="nextauthjs">NextAuth.js</h2>
<p>OAuth는 2.0 버전으로 올라가면서 1.0 보다 쉬운 구현 방법을 제공하고 있지만 서비스를 개발하기에 바쁜 개발자들이 일일이 구현하기에는 시간이 소요된다. 만약 Next.js 프레임워크를 사용해서 개발 중이라면 NextAuth를 사용해서 손쉽고 빠르게 소셜 로그인 기능을 개발할 수 있다.</p>
<blockquote>
<p>If you have an existing database with user data, you&#39;ll likely want to utilize an open-source solution that&#39;s provider agnostic.
If you want a full-featured authentication system with built-in providers (Google, Facebook, GitHub…), JWT, JWE, email/password, magic links and more… use next-auth.</p>
<p>by <a href="https://nextjs.org/docs/authentication#bring-your-own-database">Next.js</a></p>
</blockquote>
<p>Next.js 공식 페이지에서는 소셜 로그인(구글, 페이스북, 카카오, 네이버 등) 기능을 원한다면 NextAuth를 사용하는 것을 추천하고 있다. 많은 기능과 장점들을 갖추고 있으니 찬찬히 문서를 둘러보고 원하는 대로 사용하면 된다.</p>
<blockquote>
<p>Flexible and easy to use</p>
</blockquote>
<ul>
<li>Designed to work with any OAuth service, it supports OAuth 1.0, 1.0A, 2.0 and OpenID Connect</li>
<li>Built-in support for many popular sign-in services</li>
<li>Supports email / passwordless authentication</li>
<li>Supports stateless authentication with any backend (Active Directory, LDAP, etc)</li>
<li>Supports both JSON Web Tokens and database sessions</li>
<li>Designed for Serverless but runs anywhere (AWS Lambda, Docker, Heroku, etc…)<blockquote>
<p>by <a href="https://next-auth.js.org/getting-started/introduction">NextAuth.js</a></p>
</blockquote>
</li>
</ul>
<h3 id="오픈소스-기여">오픈소스 기여</h3>
<p>사실 이렇게 추천하는 이유에는 약간의 😈사심이 있는데 NextAuth를 사용하는 과정에서 처음으로 오픈소스에 기여하게 되었기 때문이다!</p>
<h4 id="redirect-callback-문서-수정">redirect callback 문서 수정</h4>
<p>원하는 정보를 세션에 추가하기 위해 문서를 닳도록 보다가 redirect 콜백함수에 async 키워드가 누락된 것을 찾아내고 <a href="https://github.com/nextauthjs/next-auth/pull/4361">PR</a>을 날렸다. 사소하지만 최초로 날린 PR이라 의미가 있다.</p>
<p><img src="https://velog.velcdn.com/images/yisu-kim/post/9e71ad67-df75-423c-ab3e-ec3ecd65d64c/image.png" alt="NextAuth 콜백 문서 수정 PR"></p>
<h4 id="카카오-프로바이더-reference-error-관련-코드-수정">카카오 프로바이더 reference error 관련 코드 수정</h4>
<p>카카오 소셜 로그인 개발 중 profile 객체가 없음에도 불구하고 nickname을 참조하는 오류를 발견했고 부끄럽지만 처음으로 node_modules에 들어가 코드를 살펴보게 되었다. 다행히 단순한 객체 프로퍼티 접근 오류라서 원인을 파악하고 두근거리는 마음으로 fork한 다음 <a href="https://github.com/nextauthjs/next-auth/pull/4365">PR</a>을 올렸다.</p>
<p>메인테이너분이 approve 하면서 친절하게 관련 링크를 남겨주었는데 원래라면 타입스크립트에서 잡아내야 하는 오류인데 타입에 문제가 있어 PR을 올렸다는 내용이었다. 다음에는 문제를 더 본질적으로 해결할 수 있기를 바란다.</p>
<p><img src="https://velog.velcdn.com/images/yisu-kim/post/2eab1051-8a76-4051-a379-860c797e24bd/image.png" alt="NextAuth 카카오 reference 에러 코드 수정 PR"></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[실용주의 프로그래머]]></title>
            <link>https://velog.io/@yisu-kim/the-pragmatic-programmer</link>
            <guid>https://velog.io/@yisu-kim/the-pragmatic-programmer</guid>
            <pubDate>Thu, 31 Mar 2022 14:54:17 GMT</pubDate>
            <description><![CDATA[<h2 id="노마드코더-북클럽">노마드코더 북클럽</h2>
<p>노마드북클럽이란 한 달에 1권을 목표로 좋은 개발 도서를 선정해 다 함께 책을 읽고 감상평을 공유하는 챌린지이다. 스케줄이 정해져 있어 늘어지지 않을 수 있게 도와주고 중간 중간 퀴즈와 미션을 제공해 지속적으로 흥미를 유지시켜준다는 점이 장점이다. 또한 별도의 요금 없이 책만 있다면 누구나 참여할 수 있어 부담없이 참여하게 되었다.</p>
<p><a href="https://nomadcoders.co/pragmatic-programmer">노개북 실용주의 프로그래머</a> 챌린지에 3/18부터 참여해 3/31 오늘까지 2주차를 지나고 있는데 하루도 빠짐없이 출석하면서 스케줄을 따라 가고 있다. 매일 책을 읽고 감상평을 쓰는 일이 쉽지는 않았지만 나의 페이스를 지키면서 할 수 있는 만큼 소화해서인지 즐거운 마음으로 한 장 한 장 넘겨가고 있다.</p>
<h2 id="실용주의-프로그래머">실용주의 프로그래머</h2>
<p><a href="http://ebook.insightbook.co.kr/book/113">실용주의 프로그래머(20th)</a>를 아직 끝까지 읽지도 않은 시점에서 책에 대해 소개하는 것은 매우 어려운 일이다. 그래도 저자의 서문을 빌려서 얘기해보자면 이 책은 무엇을 &#39;하는&#39; 것에 관한 책이다. 프로그래머로서 애매모호한 고객의 요구사항을 헤쳐나가고 변화하는 상황에 적응할 수 있도록 수많은 개발자들의 경험을 토대로 실용적인 선택을 할 수 있도록 돕는다.</p>
<p>매 장을 구성하는 &#39;토픽&#39;들, 순서에 구애받지 않도록 돕는 상호 참조, 유용한 100가지 &#39;팁&#39;, 생각해볼 거리를 던져주는 &#39;연습문제&#39;와 답, 그리고 정답이 없는 &#39;도전해 볼 것&#39;. 이처럼 풍부한 컨텐츠로 가득찬 책인데 이번 챌린지를 통해 단 한가지 방법이라도 내 삶에서 실천한다면 큰 가치를 얻었다고 본다.</p>
<h2 id="챌린지-기록">챌린지 기록</h2>
<p>블로그에 올릴 정도로 알찬 내용을 잔뜩 쓰거나 내 주관이 강하게 들어가지 않을 것 같아 노션에 가볍게 정리하고 있는데 혹시라도 궁금하다면 <a href="https://yisu-kim.notion.site/The-Pragmatic-Programmer-cb35a52de86342c7927eccbd43893083">여기</a>에서 확인할 수 있다. 하지만 그보다는 <a href="https://nomadcoders.co/slack">노마드코더 슬랙</a>과 <a href="https://nomadcoders.co/community/pragmatic">노마드코더 커뮤니티</a>를 둘러보는 것을 더 추천한다. 정성이 가득 담긴 기록이나 열정적인 참가자들을 만날 수 있다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[개발 일지 feat. Notion]]></title>
            <link>https://velog.io/@yisu-kim/daily-work-records</link>
            <guid>https://velog.io/@yisu-kim/daily-work-records</guid>
            <pubDate>Mon, 28 Feb 2022 14:43:11 GMT</pubDate>
            <description><![CDATA[<h2 id="to-do-리스트">TO DO 리스트</h2>
<p>현재 회사에 다니기 전까지는 업무 내용에 대해 일반적인 TO DO 리스트를 작성했다. 그냥 칸반 보드에 그날그날 할 일을 적고 체크하고 처리하지 못한 일은 다음 날로 넘기며 그렇게 흘러갔다. 문제 인식 → 커뮤니케이션과 개발 → 문제 해결 그리고 다시 처음으로 돌아간다. 물론 이 기록을 다시 방문하는 일은 매우 드물었다.</p>
<p>이직 결심을 하고 블로그를 시작하면서 기록하는 방식에 변화를 주기 시작했다. 단지 작업한 내용만 정리하는 것이 아니라 내 생각과 경험 또는 시간을 쏟아 만든 예제를 곁들인 것이다. 내 생각이 남들의 생각과 크게 다른 것은 아니지만 인터넷에 넘쳐나는 훌륭한 글들을 어설프게 복제하고 싶지 않았기 때문이다.</p>
<h2 id="개발-일지">개발 일지</h2>
<p>이러한 기록 방식의 변화는 매일 업무를 기록하는 데에도 영향을 미쳤다. 출근 첫날부터 지금까지 3개월간 빠짐없이 작성한 개발 일지는 점차 개인 저장소로 변해갔다. 기능이나 이슈와 관련하여 어떤 어려움이 있었고 어떻게 해결했는지, 오늘 작성한 코드에서 개선할 부분은 없는지(당연히 있다!), 누구와 상의하고 협업할지, 작성해서 공유한 개발 문서들, 해결하기 위해 참고한 링크들, 마지막으로 그날에 대한 총평까지 종합적으로 내용을 정리하고 있다.</p>
<p>이렇게 기록한 업무 일지를 통해 내가 무엇을 얻었을까? 매일 작업을 되돌아봄으로써 내가 뭘 해왔고 뭘 하고 있으며 앞으로 뭘 해야 하는지 맥락을 잃어버리지 않게 되었다. 매일 쏟아지는 작업 목록들 속에서 우선순위를 설정해 중요하고 급한 일은 먼저 처리하고 사소한 일은 빼먹지 않고 중간중간 끼워 넣을 수 있었다(✨기억력이 좋아 보이는 느낌을 주는 건 덤이다). 
덕분에 주 1회 있는 스프린트에서 회고하기 위해 내가 해야 할 일은 지난 한 주간의 업무 일지를 살펴보는 것뿐이다. 내가 배운 것, 인식한 문제점, 개선을 위한 아이디어 중에 팀원들과 공유하고 싶은 내용을 염두에 두면 보다 주도적으로 회의에 참여할 수 있게 된다.</p>
<h2 id="템플릿">템플릿</h2>
<p>개발 일지는 간단한 일기 형식으로 작성하는 것도 괜찮지만 참고한 템플릿과 내 입맛에 맞게 어떤 항목을 추가했는지 공유하고 싶다. 내 경우에는 <a href="https://www.choigouisaram.com/22">노션에 진심인 사람이 만든 업무일지 2</a> 노션 템플릿을 기반으로 변형하여 사용 중이다. 어디까지나 내 스타일에 맞춘 것이므로 참고하여 본인에게 맞는 스타일을 찾아가길 바란다.</p>
<p><img src="https://images.velog.io/images/yisu-kim/post/b9dec8df-40a9-43b8-bb73-b271787dbef4/daily%20work%20records%20page.png" alt="개발 일지 페이지"></p>
<p>먼저, 개발 일지 페이지를 살펴보면 3가지 항목이 있는데 각각 Backlog, Issue, Idea이다. 페이지에 들어오자마자 한눈에 보이게 하려고 중앙에 배치했다. Backlog에는 학습/조사/분석/문서화/공유할 사항을 모아두었다가 시간을 내서 처리한다. Issue에는 개발 과정에서 발생한 이슈 중 까다로운 문제들을 뽑아 놓았는데 팀 노션과 내용이 중복되지만 리마인드를 위한 것이다. Idea에는 말 그대로 생각난 아이디어를 적고 기회를 봐서 업무 과정에 반영한다.</p>
<p><img src="https://velog.velcdn.com/images/yisu-kim/post/277f1856-1677-4666-b336-f43da261ce7b/image.png" alt="개발 일지 상세 페이지"></p>
<p>개발 일지 상세 페이지는 가져온 템플릿과 크게 다르지 않다. 태그라는 속성을 추가했는데 &#39;live 배포&#39;, &#39;스프린트&#39; 등으로 이벤트를 강조할 수 있도록 했다.</p>
<ol>
<li>출근하면 출근 시간을 기록한다.</li>
<li>어제 개발 일지와 팀 노션을 보고 우선순위를 정해 오늘 할 일에 추가한다.</li>
<li>업무 상세에 작업한 내용을 자세히 작성한다. 이때, 무엇을 개발하고 어떻게 문제를 해결했는지뿐만 아니라 그 과정에서 무슨 생각이 들었고 왜 그런 판단을 내렸는지 최대한 &#39;나&#39;를 중심으로 적는다.</li>
<li>오늘 마무리하지 못한 일이 있다면 내일 할 일에 간단히 정리한다.</li>
<li>특별한 이벤트가 있었다면 태그를 추가한다.</li>
<li>아이콘(😀 🙂 😑 😨 😱)을 통해 업무 만족도를 평가한다.</li>
<li>야근했을 경우 발생한 이슈 속성에 야근한 시간과 어떤 문제 때문이었는지 작성한다.</li>
<li>퇴근 시간을 기록하고 퇴근한다.</li>
</ol>
<h2 id="개선-아이디어">개선 아이디어</h2>
<ul>
<li>구글 검색 히스토리: 온종일 어떤 걸 검색했는지만 봐도 문제 &amp; 솔루션을 동시에 확인할 수 있을 것 같다.</li>
<li>월간 리뷰: 블로그 글감도 찾을 겸 좀 더 긴 기간의 기록을 되돌아보고 정리하는 시간을 가지고 싶다.</li>
<li>예상 소요 시간 vs 실제 소요 시간: 내 능력에 맞는 개발 일정을 산정하는 능력을 기를 수 있지 않을까?</li>
<li>병목 지점: 업무를 방해하는 요소는 없는지 체크해볼 필요가 있다.</li>
</ul>
<p>개발 일지를 더 활용할 수 있는 아이디어를 떠올려 보았는데 적다 보니 과유불급이 아닌가 싶어 우선 병목 지점이 있는지만 체크하기로 했다. 무엇보다도 일지인 만큼 지금처럼 매일 기록을 남기는 것이 중요한데 항목을 늘리면 부담을 줄 수 있다는 생각이 든다. 다음 3개월은 오히려 일지에서 뺄 부분이 있는지 고민해보고 덜어내는 시간을 가지려 한다.</p>
<blockquote>
<p>2022년 7월 31일자로 개선 사항이 있습니다. 자세한 내용이 궁금하시다면 <a href="https://velog.io/@yisu-kim/daily-work-records-v2">다음 글</a>을 참고해주세요.</p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[블로그 비긴 어게인]]></title>
            <link>https://velog.io/@yisu-kim/blog-begin-again</link>
            <guid>https://velog.io/@yisu-kim/blog-begin-again</guid>
            <pubDate>Sun, 19 Dec 2021 13:21:31 GMT</pubDate>
            <description><![CDATA[<h2 id="블로그-그-시작">블로그, 그 시작</h2>
<h3 id="그땐-그랬지">그땐 그랬지</h3>
<p>이 블로그를 시작하기 전 한때 Github에 TIL(Today I Learned)을 작성했다. TIL이란 말 그대로 오늘 배운 것을 기록하는 것으로 현재까지도 유행하는 글쓰기 방식이다. 많은 사람들이 일일 커밋으로 Github 잔디를 채우거나 매일 배운 내용을 잊지 않도록 기록하기 위해 또는 꾸준하게 글 쓰는 습관을 기르기 위해서 TIL을 실천하곤 한다.</p>
<p>나 역시 과거에 하루하루 배운 내용을 정리하며 파란 잔디를 하나씩 심어본 적이 있다. 그러나 효과를 보고 긍정적인 결과를 얻은 많은 사람들과 달리 TIL을 그만둘 때쯤 내게 남은 건 허탈함이었다. 매일 빠짐없이 글을 써야 하다 보니 기계적으로 배운 내용을 요약해서 따라 치고 있었고 어느 순간 내가 뭘 하고 있는 건지 회의감을 느꼈다.</p>
<p>그 당시를 돌아보면 잔디를 채우기 위해 고민 없이 배운 내용을 정리&#39;만&#39; 했던 것이 회의감의 원인이었던 것 같다. 개인적인 노트 정리나 다를 바가 없다고 느껴 TIL을 그만두게 되었고 그 이후로 한참 동안 글쓰기에서 손을 놓았다.</p>
<h3 id="블로그-재도전">블로그 재도전</h3>
<p>프론트엔드 교육 프로그램에 참여한 것이 계기가 되어 멘토님의 열정적인 추천으로 다시금 블로그를 시작하게 되었다.</p>
<p>이번에는 과거의 실패를 되풀이하지 않고 싶어 작성하려는 글의 카테고리를 정한 다음 나름대로 원칙을 세우고 블로그를 시작했다. 지금 생각하면 카테고리라기보단 블로그 컨텐츠에 대해 고민했던 건데 그 덕분에 코스가 종료되는 날까지 글쓰기 소재로 고민할 필요는 없었다. 당시에는 이를 깨닫지 못했지만 운이 좋았던 것 같다.</p>
<p>결과적으로 코스 내내 글을 한 편 한 편 꾸준히 작성할 수 있었는데 아이러니하게도 글 쓰는 대상에 초점을 맞추는 것이 아니라 내가 어떻게 느끼고 내가 얼마나 이해했는지에 집중하면서 자연스럽게 글이 써지곤 했다.</p>
<p>더불어서 성취하고 싶은 작은 목표가 있어 힘들 때 포기하지 않을 수 있었다. 교육 과정에서 매주 의미 있게 작성한 블로그를 하나씩 뽑아 작성자에게 스타벅스 기프티콘을 선물했는데 성실함으로 승부를 봐서 마지막 주에 간신히 당첨되었다. 생각보다 꾸준하게 글을 쓰지 않은 사람이 많아 놀랐고 60여 명 중 한 사람으로 뽑혀 뿌듯했다.</p>
<h3 id="블로그-컨텐츠">블로그 컨텐츠</h3>
<p>교육 프로그램을 진행하는 동안 병행할 수 있는 소재를 선택했다. 다양한 블로그를 보고 글의 목차와 뼈대를 참고했으며 알맹이는 내가 소화한 바를 최대한 채워 넣었다.</p>
<h4 id="프로젝트-후기">프로젝트 후기</h4>
<p>글을 Project Overview와 Project Review로 크게 나눠 진행한 프로젝트를 소개하며 잘한 점은 칭찬하고 개선할 점을 고민해보았다.</p>
<ul>
<li>예시: <a href="https://velog.io/@yisu-kim/pre-onboarding-assignment-8">W5 - 기업과제 8 | Todo List</a></li>
</ul>
<h4 id="기술-정리">기술 정리</h4>
<p>프로젝트에서 사용한 기술을 하나 골라 새로 예제 코드를 작성하고 codesandbox를 활용한 실습 환경을 제공했다.</p>
<ul>
<li>예시: <a href="https://velog.io/@yisu-kim/drag-and-drop">W5 - 기술정리 | Drag &amp; Drop</a></li>
</ul>
<h3 id="나만의-원칙">나만의 원칙</h3>
<p>실패의 경험을 기반으로 세웠던 원칙들이다. Input을 기반으로 하되 Output을 만들어 내는 데 중점을 두었다.</p>
<ol>
<li>인용 외에는 절대 그대로 따라 쓰지 않는다. 한 문장이라도 이해하고 생각한 바를 직접 작성한다.</li>
<li>내가 기억하고 싶거나 공유되어야 한다고 필요성을 느끼는 주제에 대해 글을 쓴다.</li>
<li>모든 내용을 담아야 한다거나 틀린 내용이 없어야 한다는 부담감을 가지지 않는다.</li>
<li>다른 사람에게 쉽게 읽히고 가독성 좋은 글을 작성한다.</li>
<li>시작한 글은 반드시 끝을 낸다.</li>
</ol>
<h2 id="지속적인-글쓰기">지속적인 글쓰기</h2>
<p>어느덧 이직 기간을 지나 회사에 출근하며 업무를 파악하기에 바쁘다 보니 다시 블로그에 소홀해졌다. 이대로 블로그 시즌 2 종료?! 😠 그럴 수는 없다. 마음을 다잡고 변화한 환경에서 글쓰기를 이어나가기 위해 향후 컨텐츠를 고르고 추가적인 원칙을 세워 보았다.</p>
<h3 id="미래-컨텐츠">미래 컨텐츠</h3>
<h4 id="react-이론-파고들기">React 이론 파고들기</h4>
<p>리액트의 활용뿐 아니라 원리에 대해서도 깊게 파고들자.</p>
<ul>
<li><a href="https://reactjs.org/">React 공식문서</a></li>
<li><a href="https://www.youtube.com/watch?v=FZ0cG47msEk&amp;list=PLNG_1j3cPCaZZ7etkzWA7JfdmKWT0pMsa">React Conf 2021</a></li>
</ul>
<h4 id="javascript-깊이-이해하기">JavaScript 깊이 이해하기</h4>
<p>결국 모든 건 자바스크립트 위에서 돌아가는 것!</p>
<ul>
<li><a href="https://developer.mozilla.org/ko/docs/Web/JavaScript">MDN</a></li>
<li><a href="https://ko.javascript.info/">모던 JavaScript 튜토리얼</a></li>
<li>(도서) 자바스크립트 완벽 가이드</li>
</ul>
<h3 id="새로운-원칙">새로운 원칙</h3>
<p>지속적인 글쓰기에 초점을 두고 추가적인 원칙들을 정리해보았다. 무엇보다 균형 있게 시간을 쏟는 것이 중요하다고 보았다.</p>
<ol>
<li>평일 최대 1시간, 주말 최대 3시간만 글쓰기에 투자한다.</li>
<li>한 달에 최소 1편, 최대 2편의 글을 완성한다. 이때, 선택과 집중을 통해 꼭 쓰고 싶은 글을 쓴다.</li>
<li>글감을 위해 다양한 기업의 기술 블로그, 컨퍼런스를 정기적으로 살펴본다.</li>
<li>1년에 한 번 결산하여 80% 정도 성취했다면 나에게 작은 선물을 준다.</li>
</ol>
]]></description>
        </item>
        <item>
            <title><![CDATA[W5 - 기술정리 | Drag & Drop]]></title>
            <link>https://velog.io/@yisu-kim/drag-and-drop</link>
            <guid>https://velog.io/@yisu-kim/drag-and-drop</guid>
            <pubDate>Thu, 30 Sep 2021 02:54:43 GMT</pubDate>
            <description><![CDATA[<h1 id="drag--drop">Drag &amp; Drop</h1>
<blockquote>
<p>The user may select draggable elements with a mouse, drag those elements to a droppable element, and drop them by releasing the mouse button. A translucent representation of the draggable elements follows the pointer during the drag operation.</p>
<p>by <a href="https://developer.mozilla.org/en-US/docs/Web/API/HTML_Drag_and_Drop_API">MDN</a></p>
</blockquote>
<p>Drag and Drop API를 사용하면 <strong>draggable</strong> 엘리먼트를 드래그해 <strong>droppable</strong> 엘리먼트에 드랍할 수 있다.</p>
<h2 id="draggable과-drag-events">draggable과 Drag Events</h2>
<p><code>draggable</code> 속성을 통해 엘리먼트의 드래그 가능 여부를 설정할 수 있다. true일 때 드래그가 가능하다.</p>
<p>드래그 이벤트 중 구현 시 사용된 3가지 이벤트만 소개해 본다. 이 외에 다양한 드래그 이벤트는 <a href="https://developer.mozilla.org/en-US/docs/Web/API/HTML_Drag_and_Drop_API#drag_events">HTML Drag and Drop API</a>에서 확인할 수 있다.</p>
<ul>
<li><code>ondragstart</code>: 사용자가 엘리먼트를 드래그하기 시작할 때 발생하는 이벤트이다.</li>
<li><code>ondragend</code>: 사용자가 엘리먼트를 더 이상 드래그하지 않을 때 발생하는 이벤트이다. 마우스 버튼을 떼거나 ESC 키를 누를 경우에 해당한다.</li>
<li><code>ondragenter</code>: 사용자가 드래그한 엘리먼트가 드랍하기에 적합한 대상 위에 올라갔을 때 발생한다.</li>
</ul>
<h2 id="라이브러리-없이-drag--drop을-구현해보자">라이브러리 없이 Drag &amp; Drop을 구현해보자</h2>
<blockquote>
<p>📦 <a href="https://codesandbox.io/s/drag-n-drop-q587f">codesandbox</a>에서 실습이 가능합니다.</p>
</blockquote>
<p>이 글에서는 하나의 리스트에서 아이템을 드래그하는 드래그 &amp; 드랍 컴포넌트를 구현하려 한다.</p>
<p>먼저 Context API를 활용해 아래와 같은 상태를 관리해 보자. 이 상태들은 드랍 가능한 컴포넌트에서 유지된다.</p>
<ul>
<li>dragging: 현재 드래그 중인지 아닌지</li>
<li>dragItemIndex: 드래그 중인 아이템의 인덱스</li>
</ul>
<p>IDragContext 인터페이스로 필요한 상태와 액션을 정의한다.</p>
<pre><code class="language-ts">interface IDragContext {
  state: {
    dragging: boolean;
    dragItemIndex: number | null;
  };
  actions: {
    setDragging: Dispatch&lt;SetStateAction&lt;boolean&gt;&gt;;
    updateDragItemIndex: (index: number | null) =&gt; void;
  };
}</code></pre>
<p>다음으로 기본값과 함께 <code>createContext()</code>를 통해 DragContext를 생성한다.</p>
<pre><code class="language-ts">const defaultValue: IDragContext = {
  state: {
    dragging: false,
    dragItemIndex: null,
  },
  actions: {
    setDragging: () =&gt; {},
    updateDragItemIndex: () =&gt; {},
  },
};

const DragContext = createContext&lt;IDragContext&gt;(defaultValue);</code></pre>
<p>이제 상태를 하위 컴포넌트에 제공하는 Provider를 정의해 보자.</p>
<pre><code class="language-ts">interface DragProviderProps {
  children: React.ReactNode;
}

const DragProvider: React.FC&lt;DragProviderProps&gt; = ({ children }) =&gt; {
  const [dragging, setDragging] = useState&lt;boolean&gt;(false);
  const dragItem = useRef&lt;number | null&gt;(null);

  const updateDragItemIndex = (index: number | null) =&gt; {
    dragItem.current = index;
  };

  const value: IDragContext = {
    state: { dragging, dragItemIndex: dragItem.current },
    actions: { setDragging, updateDragItemIndex },
  };

  return &lt;DragContext.Provider value={value}&gt;{children}&lt;/DragContext.Provider&gt;;
};</code></pre>
<p>마지막으로 Context의 Provider와 Consumer를 export해서 사용하기 용이하도록 한다.</p>
<pre><code class="language-ts">const DragConsumer = DragContext.Consumer;

export { DragProvider, DragConsumer };</code></pre>
<p>상태를 관리할 DragContext가 완성되었으므로 본격적으로 DragNDrop 컴포넌트를 만들어보자. 참고로 이 예제에서는 데이터 타입을 number로 가정했다.</p>
<p>DragNDrop 인터페이스는 다음과 같다.</p>
<ul>
<li>itemArray: 전체 아이템 리스트</li>
<li>itemIndex: DragNDrop 컴포넌트가 감싸는 자식 컴포넌트(아이템)의 인덱스</li>
<li>updateItemArray(data): 드래그해서 아이템의 순서가 변경될 경우 아이템 리스트를 업데이트하기 위한 함수</li>
<li>children: 자식 컴포넌트</li>
</ul>
<pre><code class="language-ts">interface DragNDropProps {
  itemArray: number[];
  itemIndex: number;
  updateItemArray(data: number[]): void;
  children: React.ReactNode;
}</code></pre>
<p>그리고 DragNDrop 컴포넌트는 다음과 같은 구조를 반환한다.</p>
<p><code>draggable</code> 가능한 div로 전달받은 children을 감싸고 이 div는 dragstart, dragend, dragenter 이벤트를 핸들링한다. 드래그 중이면 isDraggingItem()으로 현재 드래그 중인 아이템과 인덱스가 일치하는 아이템의 스타일을 변경한다.</p>
<pre><code class="language-ts">const DragNDrop ... =&gt; {
  ...
  const isDraggingItem = (currentIndex: number): string =&gt; {
    if (dragItemIndex === currentIndex) {
      return &quot;dragging&quot;;
    }
    return &quot;&quot;;
  };

  return (
    &lt;div
      draggable
      onDragStart={(e) =&gt; handleDragStart(e, itemIndex)}
      onDragEnd={handleDragEnd}
      onDragEnter={(e) =&gt; handleDragEnter(e, itemIndex)}
      className={`draggable ${dragging &amp;&amp; isDraggingItem(itemIndex)}`}
    &gt;
      {children}
    &lt;/div&gt;
  );
}</code></pre>
<p>이제 처음으로 돌아가자. 먼저 useContext()로 아까 만든 DragContext를 가져온다. 그리고 드래그 중인 노드를 저장하기 위해 dragNode 변수를 선언한다.</p>
<pre><code class="language-ts">const DragNDrop ... =&gt; {
  const {
    state: { dragging, dragItemIndex },
    actions: { setDragging, updateDragItemIndex }
  } = useContext(DragContext);

  let dragNode: EventTarget | null = null;
  ...
}</code></pre>
<p>드래그하기 시작할 때 발생하는 이벤트를 핸들링하기 위해 handleDragStart()를 작성해 보자. DragEvent와 아이템의 index를 각각 인자로 받는다. DragEvent의 target은 dragNode에 저장하고 아이템 index는 Context의 updateDragItemIndex에 전달해 갱신한다.</p>
<p>dragging 상태를 true로 변경하는 부분을 setTimeout에 넣은 이유는 드래그를 시작할 때 반투명하게 마우스 포인트를 따라다니는 draggable 엘리먼트의 스타일에 드래그 시작 전 스타일이 적용되게 하기 위해서이다.</p>
<pre><code class="language-ts">const handleDragStart = (e: DragEvent&lt;HTMLDivElement&gt;, dragIndex: number) =&gt; {
  updateDragItemIndex(dragIndex);
  dragNode = e.target;
  setTimeout(() =&gt; {
    setDragging(true);
  }, 0);
};</code></pre>
<p>드래그가 끝나고 드랍할 때 발생하는 이벤트를 핸들링하기 위해 handleDragEnd()를 작성해 보자. dragItemIndex와 dragNode를 모두 null로 만들고 dragging 상태를 false로 설정하면 된다.</p>
<pre><code class="language-ts">const handleDragEnd = () =&gt; {
  updateDragItemIndex(null);
  dragNode = null;
  setDragging(false);
};</code></pre>
<p>마지막으로 드래그한 엘리먼트가 드랍하기에 적합한 대상 위에 올라갔을 때 발생하는 이벤트를 핸들링하기 위해 handleDragEnter()를 작성해 보자. 현재 대상 엘리먼트가 드래그 중인 엘리먼트가 아닐 때 드랍할 위치에 있는 아이템 인덱스를 dragItemIndex로 갱신하고 itemArray는 splice를 사용해 업데이트한다.</p>
<pre><code class="language-ts">const handleDragEnter = (e: DragEvent&lt;HTMLDivElement&gt;, dropIndex: number) =&gt; {
  if (!dragging) {
    return;
  }
  if (e.target !== dragNode &amp;&amp; dragItemIndex !== null) {
    updateDragItemIndex(dropIndex);
    const newItemArray = [...itemArray];
    const [dragItem] = newItemArray.splice(dragItemIndex, 1);
    newItemArray.splice(dropIndex, 0, dragItem);
    updateItemArray(newItemArray);
  }
};</code></pre>
<p>이제 완성된 DragNDrop 컴포넌트를 실제로 활용해 보자.</p>
<p>먼저 Context로 만든 DragProvider로 드래그 &amp; 드랍 기능을 사용할 리스트 컴포넌트를 감싼다. 그리고 리스트의 아이템은 DragNDrop 컴포넌트로 감싸고 알맞은 props를 넘겨주면 완성된다.</p>
<pre><code class="language-ts">interface ListProps {
  datas: number[];
  handleListItems: (datas: number[]) =&gt; void;
}

const List: React.FC&lt;ListProps&gt; = ({ datas, handleListItems, enableDrag }) =&gt; {
  return (
    &lt;DragProvider&gt;
      &lt;ul className=&#39;list&#39;&gt;
        {datas.map((data, index, array) =&gt; (
          &lt;DragNDrop
            key={index}
            itemArray={array}
            itemIndex={index}
            updateItemArray={handleListItems}
          &gt;
            &lt;Item data={data} /&gt;
          &lt;/DragNDrop&gt;
        ))}
      &lt;/ul&gt;
    &lt;/DragProvider&gt;
  );
};</code></pre>
<p>완성!</p>
<p><img src="https://images.velog.io/images/yisu-kim/post/4d8f0c2c-d2fb-4d02-a2b9-06039396a38d/drag%20n%20drop.gif" alt="drag and drop"></p>
<h2 id="참고자료">참고자료</h2>
<ul>
<li><a href="https://www.youtube.com/watch?v=Q1PYQPK9TaM&amp;ab_channel=asat">Drag And Drop With React Hooks From Scratch - YouTube</a></li>
</ul>
<blockquote>
<p>잘못된 부분에 대한 지적은 언제든 환영합니다! 😉</p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[W6 - 기업과제 9 | Todo List 앱]]></title>
            <link>https://velog.io/@yisu-kim/pre-onboarding-assignment-9</link>
            <guid>https://velog.io/@yisu-kim/pre-onboarding-assignment-9</guid>
            <pubDate>Tue, 07 Sep 2021 16:30:50 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>📍 원티드 프리온보딩 과정에 참여하여 페이워크 기업과제를 받아 구현해보았습니다.</p>
</blockquote>
<h1 id="🛫-project-overview">🛫 Project Overview</h1>
<blockquote>
<p>🔗 <a href="https://github.com/yisu-kim/pre-onboarding-paywork">GitHub</a>
🚀 <a href="https://paywork-todo-app.netlify.app/">View Web Demo</a></p>
</blockquote>
<h2 id="투두-리스트-앱">투두 리스트 앱</h2>
<h3 id="작업기간">작업기간</h3>
<p>2021.8.30 ~ 2021.9.2</p>
<h3 id="기술스택">기술스택</h3>
<ul>
<li>React Native (expo)</li>
<li>TypeScript</li>
<li>react-redux</li>
<li>redux-saga</li>
</ul>
<h3 id="주요-구현사항">주요 구현사항</h3>
<blockquote>
<p>이번 프로젝트는 개인 프로젝트로 모두 직접 작업하였습니다.</p>
</blockquote>
<ul>
<li>서버 URL이 있다는 가정 하에 Todo 서비스 구현<ul>
<li>Todo 생성: <code>POST /todo</code></li>
<li>Todo 목록 조회: <code>GET /todo</code></li>
<li>Todo 수정/체크: <code>PATCH /todo/:id</code></li>
<li>Todo 삭제: <code>DELETE /todo/:id</code></li>
</ul>
</li>
<li>react-redux로 전역 상태 관리</li>
<li>redux-saga 미들웨어로 mock 서버와 비동기 작업 처리</li>
</ul>
<h3 id="결과-화면">결과 화면</h3>
<p><img src="https://images.velog.io/images/yisu-kim/post/4e190261-b963-438f-bebe-e44e238ad836/todo%20list%20app.gif" alt="todo list app"></p>
<h1 id="🚥-project-review">🚥 Project Review</h1>
<h2 id="칭찬하고-싶은-점">칭찬하고 싶은 점</h2>
<h3 id="react-native-한번-해보겠습니다">React Native 한번 해보겠습니다</h3>
<p>이번 기회가 아니면 React Native를 접할 일이 자주 있지 않을 것 같아 과감하게 앱 개발에 도전했고 기간 내에 요구 사항을 충족하는 투두 앱을 만들었다. 모든 것이 다 낯설고 새로웠고 그래서 재미있었다.</p>
<p>expo를 설치한 다음 안드로이드 에뮬레이터를 실행해 개발 환경을 설정하고 &lt;View&gt;, &lt;Text&gt;와 같은 새로운 컴포넌트들을 알아갔다. map 대신 사용한 &lt;FlatList&gt;는 화면에 표시되는 요소만 렌더링 하는데 내용이 길어지면 자동으로 스크롤을 적용해 준다. 이처럼 모바일에 특화된 컴포넌트가 기본 제공된다는 점이 신기했다.</p>
<p>당연히 다양한 문제들을 맞닥뜨렸는데 remote debugger나 터미널을 확인하고 방금 작성한 코드를 뜯어보며 원인을 찾아 나갔다. SVG 아이콘을 쓰려는데 웹과 달리 모바일에서는 추가적인 설정이 필요해서 react-native-svg를 설치하기도 하고 locale 날짜가 정상적으로 표현되지 않아서 intl을 활용하는 등 몇 가지 이슈들을 해결해나갔다.</p>
<p>한 번 경험해봤으니 다음에 앱 개발할 일이 있으면 좀 더 가벼운 마음으로 뛰어들 수 있을 것 같다.</p>
<h3 id="redux와-redux-saga">redux와 redux-saga</h3>
<p>redux도 익숙하지 않지만 redux-saga는 처음 쓰는 상황이라 빠르게 학습하면서 개발을 진행했다. redux와 redux-saga를 동시에 구현하기 어렵다고 판단했기 때문에 일단 비동기 작업을 배제하고 redux에서 출발했다.</p>
<p>학습 겸 redux toolkit은 사용하지 않았다. Todo 앱에 필요한 기능들을 하나씩 action 타입으로 정의하고 action creator 함수를 작성했다. 이어서 reducer에서는 액션 타입별로 새로운 상태를 리턴하도록 했고 최종적으로 store를 생성해 Provider에 넘겨줬다.</p>
<p>redux 작업을 완료한 후 만들어 둔 Todo API에 맞춰 redux-saga를 만들어 나갔다. 제너레이터에 대해 깊이 이해하지는 못했지만 call, put 등의 함수만 적절히 사용하면 되기 때문에 생각보다 어렵지 않았다. 이제 API 호출 성공/실패에 따라 다른 상태를 리턴해야 하므로 reducer를 수정하고 store에도 saga 미들웨어를 적용했다.</p>
<p>Best Practice라고 할 수는 없지만 열심히 고민하고 하나하나 직접 작성해서 결국 기능이 동작하는 걸 본 순간이 정말 기억에 남는다.</p>
<h2 id="개선하고-싶은-점">개선하고 싶은 점</h2>
<h3 id="디테일-부족">디테일 부족</h3>
<p>아무래도 React Native, redux, redux-saga, TypeScript까지 모두 익숙하지 않아 개발에 꽤 시간이 걸렸다. 중간 중간 발생하는 React Native 이슈들과 redux &amp; redux-saga 구조와 흐름에 대한 고민 그리고 계속해서 등장하는 TypeScript의 경고들까지! 이 때문에 기본적인 기능은 구현했지만 디테일이 부족해 높은 완성도를 보여주지는 못했다.</p>
<h3 id="미배포">미배포</h3>
<blockquote>
<p>2021.10.5 기준 웹으로 배포했으며 mock 서버 또한 로컬에서 My JSON Server로 변경해 접근 가능하도록 했다.</p>
</blockquote>
<p>mock 서버인 json-server가 로컬에서만 테스트가 가능하고 시간이 부족하기도 해서 앱을 빌드하지는 못했다. json-server를 백엔드로 배포하거나 다른 mock 서버를 구축한 다음 앱을 빌드해서 apk 파일까지 만들었다면 좋았을 텐데 하는 아쉬움이 남는다.</p>
<h2 id="개인-프로젝트-후기">개인 프로젝트 후기</h2>
<p>마지막 프로젝트이자 개인 프로젝트라서 가지고 있는 역량을 보여줄 수 있는 기회였다. 하지만 내 선택으로 새로운 기술을 많이 사용해야 했기 때문에 지금까지 익혀온 기술들을 보여주기는 어려운 입장이었다. 그래서 프로젝트 구조나 컴포넌트 분리 및 재사용 그리고 JavaScript와 React 활용 등에 신경을 쏟고 그 외에는 그냥 마음 놓고 혼자서 어디까지 학습하고 개발할 수 있는지 스스로를 시험해보며 즐겁게 개발에 임했던 것 같다.</p>
<p>지난 과제에서 이슈나 PR 작성이 부실했다는 점을 반성하며 이번 과제가 개인 프로젝트일지라도 이슈와 PR을 간단하게 기록해보았다. 팀 프로젝트에서 작성해야 하는 스타일과는 다르지만 나중에 이 레포를 다시 봤을 때 발생한 문제를 어떻게 해결했고 어떤 자료를 참고했는지 파악할 수 있도록 했다. 배포까지 할 수 없었던 프로젝트라 이후 시간을 내서 추가 개발할 때 기록한 이슈와 PR이 도움이 되기를 바란다.</p>
<hr>
<p><a href="https://www.wanted.co.kr/">#wanted</a> <a href="https://wecode.co.kr/">#wecode</a> <a href="https://rootimpact.org/">#rootimpact</a> <a href="https://paywork.io/">#paywork</a> <a href="https://www.rocketpunch.com/@YeriKim">#멘토 김예리님</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[W5 - 기업과제 8 | Todo List]]></title>
            <link>https://velog.io/@yisu-kim/pre-onboarding-assignment-8</link>
            <guid>https://velog.io/@yisu-kim/pre-onboarding-assignment-8</guid>
            <pubDate>Sun, 05 Sep 2021 14:47:31 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>📍 원티드 프리온보딩 과정에 참여하여 모두컴퍼니 기업과제를 받아 구현해보았습니다.</p>
</blockquote>
<h1 id="🛫-project-overview">🛫 Project Overview</h1>
<blockquote>
<p>🔗 <a href="https://github.com/yisu-kim/pre-onboarding-moducompany">GitHub</a>
🚀 <a href="https://moducompany.netlify.app">View Demo</a></p>
</blockquote>
<h2 id="투두-리스트">투두 리스트</h2>
<h3 id="작업기간">작업기간</h3>
<p>2021.8.23 ~ 2021.8.28</p>
<h3 id="기술스택">기술스택</h3>
<ul>
<li>React</li>
<li>TypeScript</li>
<li>emotion</li>
</ul>
<h3 id="주요-구현사항">주요 구현사항</h3>
<blockquote>
<p>제가 개발에 참여한 기능은 ✅로 표시했습니다.</p>
</blockquote>
<ul>
<li>Task 추가
할일 내용을 작성하고 중요도, 진행 기간을 선택할 수 있다.</li>
<li>Task 목록 조회
기본 정렬 외 생성일순, 중요도순으로 정렬할 수 있다.</li>
<li>Task 수정 및 삭제
할일 내용, 중요도, 진행 상태, 진행 기간을 변경할 수 있다.</li>
<li>✅ Task 순서 변경
기본 정렬일 때 Task를 drag &amp; drop하여 순서를 바꿀 수 있다.</li>
</ul>
<h3 id="결과-화면">결과 화면</h3>
<p><img src="https://images.velog.io/images/yisu-kim/post/77e1ec28-158e-4d42-965a-6d3506aa8b12/drag%20n%20drop.gif" alt="drag n drop"></p>
<h1 id="🚥-project-review">🚥 Project Review</h1>
<h2 id="칭찬하고-싶은-점">칭찬하고 싶은 점</h2>
<h3 id="drag--drop-기능-구현">Drag &amp; Drop 기능 구현</h3>
<p>드래그 &amp; 드랍 기능을 구현했다. 처음엔 JavaScript에서 드래그 &amp; 드랍을 구현하는 영상을 봐도 막막했는데 React에서는 어떻게 구현할 수 있을지 고민하다 보니 감이 오기 시작했다.</p>
<p>React는 선언형 프로그래밍을 추구한다. 즉, 뷰를 선언하면 데이터 변경에 따라 자동으로 갱신 및 렌더링 된다. 따라서 드래그 이벤트가 발생할 때 적절하게 배열을 변경하면 보이는 아이템의 순서가 따라서 바뀌게 된다.</p>
<p>드래그 &amp; 드랍을 구현하면서 드래그 이벤트, <code>ref</code>, <code>splice</code>, <code>context API</code> 등 유용한 기능들을 사용해보게 되어 꼭 이를 정리한 글을 작성할 예정이다.</p>
<h3 id="git에-능숙해지다">Git에 능숙해지다</h3>
<p>이제 git을 사용할 때 막힘이 없어지는 것을 느꼈다. remote에 올릴 때는 조심하지만 local에서는 자유롭게 커밋 하며 작업한다. 커밋 메시지를 올리기 전에 검토할 때 변경이 필요하면 <code>rebase</code>를 사용하고 특히 이번에는 커밋을 합칠 일이 있어 <code>squash</code>를 사용하기도 했다.</p>
<p>그동안 다른 사람 코드 리뷰를 위해 체크아웃할 때 <code>stash</code>로 작업하던 내용을 임시 저장했는데 <code>squash</code>를 사용해보기 위해 작업 중이던 상태를 메시지와 함께 커밋 했다. 코드 리뷰 후 돌아와 기능 개발을 완료한 내용을 커밋 했고 두 커밋을 합쳐보았다. 앞으로 커밋 히스토리를 관리하고 싶을 때 유용하게 사용할 것 같다.</p>
<h2 id="개선하고-싶은-점">개선하고 싶은 점</h2>
<h3 id="뒤늦은-전역-상태-관리">뒤늦은 전역 상태 관리</h3>
<p>이번 과제의 Todo List는 Header에서 투두 아이템을 생성하고 Container에서 투두 리스트의 조회, 투두 아이템의 수정과 삭제가 이루어지는 구조였다. 문제는 처음 개발을 시작할 때 Todo List 상태를 어디에서 관리할지 미리 합의하지 않아서 Container에서만 상태를 관리하는 일이 발생했다.</p>
<p>나중에 문제를 인지하고 팀원들과 게더타운에 모여 Todo List 상태가 전역 관리되도록 변경했지만 설계 시점에서 당연히 고려했어야 하는 부분이라 부실하게 설계하고 개발하고 있는 게 아닌가 하는 부끄러움을 느꼈다. 바로 개발을 시작하는 것이 아니라 상태가 어디에서 어떻게 관리되어야 하는지 고민하는 데 더 시간을 쏟아야겠다.</p>
<h3 id="좋은-pr이란">좋은 PR이란</h3>
<p>좋은 질문을 해야 좋은 답을 얻을 수 있는 것처럼 좋은 PR을 올려야 좋은 코드 리뷰를 받을 수 있다. 하지만 이번 프로젝트에서는 코드를 확인하고 어떤 리뷰를 할지만 신경 썼을 뿐 어떻게 리뷰하기 쉬운 PR을 올릴까에 대한 고민이 부족했다.</p>
<p>아무리 코드를 클린하게 작성하고 훌륭한 주석을 남겨도 전체적인 흐름을 파악하는 데에는 좋은 코드 이력 다시 말해서, 좋은 PR이 중요한 역할을 한다. 어떤 문제를 어떻게 해결했고 해결 방법의 근거는 무엇인지 설명하는 사전 정보를 제공해서 시간을 내주는 리뷰어에게 예의를 지킬 수 있도록 해야겠다.</p>
<h2 id="팀-프로젝트-후기">팀 프로젝트 후기</h2>
<p>갑작스럽게 팀이 변경되었지만 다들 바로 프로젝트에 집중해 기능을 구현해나갔다. miro라는 실시간 화이트보드 협업 툴을 사용한 경험이 기억에 남는다. 서로서로 아이디어를 내고 이미지나 아이콘을 붙여가며 다 함께 디자인을 구상했다.</p>
<p>새벽까지 고생한 팀원들 덕분에 마지막으로 진행한 팀 프로젝트를 무사히 마무리할 수 있었다.</p>
<h3 id="thanks-to">Thanks to</h3>
<p><a href="https://github.com/yoonhe">yoonhe님</a>, <a href="https://github.com/nvrtmd">nvrtmd님</a>, <a href="https://github.com/gildydtjd">gildydtjd님</a> 새벽까지 작업하느라 수고 많으셨습니다!</p>
<hr>
<p><a href="https://www.wanted.co.kr/">#wanted</a> <a href="https://wecode.co.kr/">#wecode</a> <a href="https://rootimpact.org/">#rootimpact</a> <a href="http://www.moduparking.com/">#moducompany</a> <a href="https://www.rocketpunch.com/@YeriKim">#멘토 김예리님</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[W4 - 자료구조 | Linked List 구현하기]]></title>
            <link>https://velog.io/@yisu-kim/linked-list-implementation-in-typescript</link>
            <guid>https://velog.io/@yisu-kim/linked-list-implementation-in-typescript</guid>
            <pubDate>Sun, 29 Aug 2021 06:50:48 GMT</pubDate>
            <description><![CDATA[<h1 id="linked-list">Linked List</h1>
<blockquote>
<p>In computer science, a linked list is a linear collection of data elements whose order is not given by their physical placement in memory. Instead, each element points to the next. It is a data structure consisting of a collection of nodes which together represent a sequence. In its most basic form, each node contains: data, and a reference (in other words, a link) to the next node in the sequence.</p>
<p>by <a href="https://en.wikipedia.org/wiki/Linked_list">Wikipedia</a></p>
</blockquote>
<p>링크드 리스트란 노드라는 기본 요소를 사용해 선형적인 데이터 묶음을 추상적으로 표현한 것이다. 노드에는 데이터뿐만 아니라 다음 노드를 가리키는 참조(링크)가 포함되어 있다.</p>
<h2 id="어디에-사용하면-좋을까">어디에 사용하면 좋을까?</h2>
<p>링크드 리스트는 일련의 데이터를 차례대로 저장하지만 배열과 달리 실제 메모리 상에서 연속적인 위치에 저장하지 않는다. 다음 노드를 가리키는 참조를 사용해 유연하게 데이터를 할당한다.</p>
<p>배열은 처음이나 중간 지점에 데이터를 추가하거나 삭제하려면 모든 요소를 이동시켜야 하지만 링크드 리스트는 참조(링크)만 변경하면 되므로 데이터의 추가나 삭제가 빈번한 경우에 사용된다. 주식 호가창을 예로 들 수 있다.</p>
<p><img src="https://images.velog.io/images/yisu-kim/post/f5ab2aeb-24c2-4615-94d6-f9cc7d532f49/image.png" alt="stock quote"></p>
<h2 id="singly-linked-list">Singly Linked List</h2>
<p>이 글에서는 링크드 리스트 중 단일 링크드 리스트에 대해서만 다루고자 한다. 구체적인 구조를 그림을 통해 살펴보자.</p>
<p><img src="https://images.velog.io/images/yisu-kim/post/746f5529-1d3d-4e76-8715-e4c36bbcb0d9/singly%20linked%20list.png" alt="singly linked list"></p>
<ul>
<li><code>Node</code>: <code>data</code>와 <code>pointer</code>로 이루어진 요소로서 모여서 리스트를 형성</li>
<li><code>data</code>: 데이터</li>
<li><code>pointer(link)</code>: 다음 <code>Node</code>를 가리키는 참조</li>
<li><code>head</code>: 가장 앞에 있는 <code>Node</code></li>
<li><code>tail</code>: 가장 끝에 있는 <code>Node</code></li>
</ul>
<h2 id="typescript로-구현해보자">TypeScript로 구현해보자</h2>
<blockquote>
<p>📦 <a href="https://codesandbox.io/s/linked-list-exercise-2pju2">codesandbox</a>에서 전체 코드를 확인하고 실습해볼 수 있습니다.</p>
</blockquote>
<p>타입스크립트의 제네릭을 활용해 원하는 타입의 데이터를 담을 수 있는 링크드 리스트를 만들어보자.</p>
<h3 id="node">Node</h3>
<p>먼저 <code>Node</code>를 만든다.</p>
<ul>
<li><strong>val</strong>: <code>data</code>를 담는다.</li>
<li><strong>next</strong>: 다음 노드를 가리키는 <code>pointer</code>다. 다음 노드가 없으면 null 값을 가진다.</li>
</ul>
<pre><code class="language-ts">type Node&lt;T&gt; = {
  val: T;
  next: Node&lt;T&gt; | null;
};</code></pre>
<h3 id="linked-list-인터페이스">Linked List 인터페이스</h3>
<p>다음으로 구현하고자 하는 링크드 리스트의 인터페이스를 정의한다.</p>
<ul>
<li><strong>getAtIndex()</strong>: 전달받은 인덱스 위치에 있는 노드의 데이터를 가져온다. 인덱스가 리스트의 범위를 벗어난 경우 null을 반환한다.</li>
<li><strong>addAtHead()</strong>: <code>head</code>에 노드를 추가한다.</li>
<li><strong>addAtTail()</strong>: <code>tail</code>에 노드를 추가한다.</li>
<li><strong>addAtIndex()</strong>: 전달받은 인덱스 위치에 노드를 추가한다. 인덱스가 리스트의 범위를 벗어난 경우 노드를 추가하지 않는다.</li>
<li><strong>deleteAtIndex()</strong>: 전달받은 인덱스 위치에서 노드를 삭제한다. 인덱스가 리스트의 범위를 벗어난 경우 노드를 삭제하지 않는다.</li>
<li><strong>size</strong>: 리스트의 길이를 반환한다.</li>
</ul>
<pre><code class="language-ts">interface ILinkedList&lt;T&gt; {
  getAtIndex(index: number): T | null;
  addAtHead(val: T): void;
  addAtTail(val: T): void;
  addAtIndex(index: number, val: T): void;
  deleteAtIndex(index: number): void;
  size: number;
}</code></pre>
<h3 id="linked-list-클래스">Linked List 클래스</h3>
<ul>
<li><p><strong>head</strong>: null로 초기화한다.</p>
</li>
<li><p><strong>_size</strong>: 0으로 초기화한다.</p>
</li>
<li><p><strong>size</strong>: _size의 getter 메서드이다.</p>
<pre><code class="language-ts">class LinkedList&lt;T&gt; implements ILinkedList&lt;T&gt; {
constructor(private head: Node&lt;T&gt; | null = null, private _size: number = 0) {}

/**
 * Time: O(1)
 */
public get size() {
  return this._size;
}
// ...
}</code></pre>
</li>
<li><p><strong>getNodeAtIndex()</strong></p>
</li>
</ul>
<p>전달받은 인덱스 위치에 있는 노드를 찾아 반환하는 메서드이다. 인덱스가 리스트의 범위를 벗어날 경우 null을 반환한다. 이 메서드는 다른 메서드에서 노드를 찾기 위해 사용되므로 클래스 내에서만 접근할 수 있도록 private으로 설정한다.</p>
<pre><code class="language-ts">/**
 * Time: Worst - O(n), Best - O(1)
 */
private getNodeAtIndex(index: number): Node&lt;T&gt; | null {
  if (index &lt; 0 || index &gt;= this.size) {
    return null;
  }

  let curr = this.head;  // head에서 출발한다.
  let count = 0;
  while (count &lt; index) {  // index-1 노드에 도달할 때까지 반복
    curr = curr!.next;  // 현재 노드에서 다음 노드로 이동한다.
    count++;
  }
  // while문이 종료되면 index 노드가 curr에 담긴다.
  return curr; // 이 노드를 반환한다.
}</code></pre>
<ul>
<li><strong>getAtIndex()</strong></li>
</ul>
<p>전달받은 인덱스에 있는 노드의 데이터를 반환하는 메서드이다.</p>
<pre><code class="language-ts">/**
 * Time: Worst - O(n), Best - O(1)
 */
getAtIndex(index: number): T | null {
  const node = this.getNodeAtIndex(index);  // index 노드를 가져온다.
  if (node) {  // 노드가 있으면
    return node.val;  // 그 노드의 데이터를 반환한다.
  }
  return null;  // 노드가 없으면 null을 반환한다.
}</code></pre>
<ul>
<li><strong>addAtHead()</strong></li>
</ul>
<p>리스트의 맨 앞에 노드를 추가하는 메서드이다.</p>
<p><img src="https://images.velog.io/images/yisu-kim/post/1052ea26-0242-4fd0-bccb-5ebc16e3070f/add%20at%20head.png" alt="add at head"></p>
<pre><code class="language-ts">/**
 * Time: O(1)
 */
addAtHead(val: T): void {
  // 기존 head를 가리키는 새로운 노드를 만들고 이를 head로 바꾼다.
  this.head = { val, next: this.head };
  this._size++;  // 리스트의 사이즈를 하나 증가시킨다.
}</code></pre>
<ul>
<li><strong>addAtTail()</strong></li>
</ul>
<p>리스트의 맨 끝에 노드를 추가하는 메서드이다.</p>
<p><img src="https://images.velog.io/images/yisu-kim/post/aad0c3fe-866e-4ce8-8ec0-0affda28cce3/add%20at%20tail.png" alt="add at tail"></p>
<pre><code class="language-ts">/**
 * Time: Worst - O(n), Best - O(1)
 */
addAtTail(val: T): void {
  if (this.size === 0) {  // 리스트가 비어 있으면
    this.addAtHead(val);  // 리스트의 맨 앞에 노드를 추가한다.
    return;
  }

  const lastNode = this.getNodeAtIndex(this.size - 1)!;  // tail을 가져온다.
  lastNode.next = { val, next: null };  // 기존 tail이 새로 만든 노드를 가리키도록 하여 이를 tail로 바꾼다.
  this._size++;  // 리스트의 사이즈를 하나 증가시킨다.
}</code></pre>
<ul>
<li><strong>addAtIndex()</strong></li>
</ul>
<p>전달받은 인덱스 위치에 노드를 추가하는 메서드이다.</p>
<p><img src="https://images.velog.io/images/yisu-kim/post/b817213d-0ebc-4dd2-88a3-ede59dd0f9a7/add%20at%20index.png" alt="add at index"></p>
<pre><code class="language-ts">/**
 * Time: Worst - O(n), Best - O(1)
 */
addAtIndex(index: number, val: T): void {
  if (index &lt; 0 || index &gt; this.size) {
    return;
  }

  if (index === 0) {  // 리스트가 비어 있으면
    this.addAtHead(val);  // 리스트의 맨 앞에 노드를 추가한다.
    return;
  }

  const prevNode = this.getNodeAtIndex(index - 1)!;  // index-1 노드를 가져온다.
  // 새로운 노드를 만들고 index 노드를 가리키도록 한다.
  // index-1 노드는 이 새로운 노드를 가리키도록 한다.
  prevNode.next = { val, next: prevNode.next };
  this._size++;  // 리스트의 사이즈를 하나 증가시킨다.
}</code></pre>
<ul>
<li><strong>deleteAtIndex()</strong></li>
</ul>
<p>전달받은 인덱스 위치에서 노드를 삭제하는 메서드이다.</p>
<p><img src="https://images.velog.io/images/yisu-kim/post/0fed1136-1a64-4132-b04c-d002bdcd48a6/delete%20at%20index.png" alt="delete at index"></p>
<pre><code class="language-ts">/**
 * Time: Worst - O(n), Best - O(1)
 */
deleteAtIndex(index: number): void {
  if (index &lt; 0 || index &gt;= this.size) {
    return;
  }

  if (index === 0) {  // 맨 앞에 있는 노드를 삭제하는 경우
    this.head = this.head!.next;  // head의 다음 노드를 head로 바꾼다.
  } else {  // 그렇지 않은 경우
    const prevNode = this.getNodeAtIndex(index - 1)!;  // index-1 노드를 가져온다.
    prevNode.next = prevNode.next!.next;  // index-1 노드가 index+1 노드를 가리키도록 한다.
  }
  this._size--;  // 리스트의 사이즈를 하나 감소시킨다.
}</code></pre>
<h2 id="보충학습">보충학습</h2>
<blockquote>
<p>📢 Big-O 표기법을 소개하고 Array와 Linked List의 차이점을 비교해봅니다.</p>
</blockquote>
<h3 id="time-complexity">Time Complexity</h3>
<blockquote>
<p>In computer science, the time complexity is the computational complexity that describes the amount of computer time it takes to run an algorithm.</p>
<p>by <a href="https://en.wikipedia.org/wiki/Time_complexity">Wikipedia</a></p>
</blockquote>
<p>시간 복잡도란 알고리즘의 실행 시간을 수치화한 것이다.</p>
<h3 id="big-o-notation">Big-O Notation</h3>
<blockquote>
<p>In computer science, big O notation is used to classify algorithms according to how their run time or space requirements grow as the input size grows.</p>
<p>by <a href="https://en.wikipedia.org/wiki/Big_O_notation">Wikipedia</a></p>
</blockquote>
<p>그리고 Big-O 표기법은 시간 복잡도를 나타내는 방법 중 하나인데 최악의 경우를 고려하는 표기법이다. 다시 말해서 아무리 오래 걸려도 이 시간 내에는 끝난다는 뜻이다.</p>
<p><img src="https://images.velog.io/images/yisu-kim/post/4541e2f4-bdc7-445f-a008-7d4ed2a4e5c9/image.png" alt="Big-O notaion"></p>
<ul>
<li>O(1): 실행시간이 입력 크기에 의존하지 않는다. 상수 시간이 걸린다.</li>
<li>O(n): 실행시간이 입력 크기에 따라 선형적(linear)으로 증가한다. 입력 크기에 비례하는 시간이 걸린다.</li>
</ul>
<h3 id="array-vs-linked-list">Array vs Linked List</h3>
<p><img src="https://images.velog.io/images/yisu-kim/post/48a13ec4-e9ae-4d81-a3a4-ec4570392fc9/image.png" alt="array vs linked list"></p>
<h2 id="참고자료">참고자료</h2>
<ul>
<li><a href="https://en.wikipedia.org/wiki/Linked_list">Linked list</a></li>
<li><a href="https://en.wikipedia.org/wiki/Big_O_notation">Big-O notaion</a></li>
<li><a href="https://medium.com/@yk392/what-is-a-linked-list-linked-list-vs-array-92f0db4015cc">What is a Linked List? Linked List vs Array</a></li>
</ul>
<blockquote>
<p>잘못된 부분에 대한 지적은 언제든 환영합니다! 😉</p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[W4 - 기업과제 7 | Todo List 개선]]></title>
            <link>https://velog.io/@yisu-kim/pre-onboarding-assignment-7</link>
            <guid>https://velog.io/@yisu-kim/pre-onboarding-assignment-7</guid>
            <pubDate>Thu, 26 Aug 2021 03:54:11 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>📍 원티드 프리온보딩 과정에 참여하여 솔라커넥트 기업과제를 받아 구현해보았습니다.</p>
</blockquote>
<h1 id="🛫-project-overview">🛫 Project Overview</h1>
<blockquote>
<p>📦 <a href="https://codesandbox.io/s/long-haze-9v8jt">Base Code</a>
🔗 <a href="https://github.com/yisu-kim/pre-onboarding-solarconnect">GitHub</a>
🚀 <a href="https://solar-connect-todo-list.netlify.app/">View Demo</a></p>
</blockquote>
<h2 id="투두-리스트-서비스-개선">투두 리스트 서비스 개선</h2>
<h3 id="작업기간">작업기간</h3>
<p>2021.8.19 ~ 2021.8.20</p>
<h3 id="작업대상-및-방법">작업대상 및 방법</h3>
<p>TypeScript ReactJS 베이스 코드가 주어지고 버그가 포함되어 있다. 요구사항에 따라 버그 수정과 필요한 기능을 추가한다. 이때 주어진 모듈과 기능만을 사용하며 제시된 라이브러리 외에는 추가하지 않도록 한다.</p>
<h3 id="기술스택">기술스택</h3>
<ul>
<li>React</li>
<li>TypeScript</li>
<li>styled-components</li>
<li>antd</li>
</ul>
<h3 id="주요-구현사항">주요 구현사항</h3>
<blockquote>
<p>이번 프로젝트는 개인 프로젝트로 모두 직접 작업하였습니다.</p>
</blockquote>
<ul>
<li>기능 완성<ul>
<li>Todo List 화면에 현재 날짜 표시</li>
<li>Todo 아이템의 완료 버튼을 누르면 Todo 완료 상태 변경</li>
</ul>
</li>
<li>기능 추가<ul>
<li>입력 항목 근처에 목표일을 입력받을 수 있게 <a href="https://ant.design/components/date-picker/">Datepicker</a>로 UX를 구성</li>
<li>완료 목표일은 필수값이 아님</li>
<li>Todo 아이템에 완료 목표일이 보임</li>
</ul>
</li>
<li>예외 추가<ul>
<li>Todo 아이템 삭제 버튼을 누르면 <a href="https://ant.design/components/modal/">Modal</a>을 띄워 삭제 여부 확인</li>
<li>입력값에 공백문자만 입력할 수 없도록 검증</li>
<li>입력값의 앞/뒤에 존재하는 공백문자 trimming</li>
<li>입력값이 존재하면 입력 버튼의 색상이 변경되고 Todo 아이템 등록 가능</li>
</ul>
</li>
<li>버그 수정<ul>
<li>Todo 아이템이 삭제되지 않는 버그 해결</li>
<li>새로고침하고 추가하면 Todo 아이템의 id가 중복될 수 있는 버그 해결</li>
<li>초기 로드 시 로컬스토리지가 비어있을 때 JSON parsing 오류 해결</li>
</ul>
</li>
</ul>
<h3 id="결과-화면">결과 화면</h3>
<p><img src="https://images.velog.io/images/yisu-kim/post/3885dabd-80d3-411d-82f5-8ca47bda663e/todo%20list.gif" alt="todo list"></p>
<h1 id="🚥-project-review">🚥 Project Review</h1>
<h2 id="칭찬하고-싶은-점">칭찬하고 싶은 점</h2>
<h3 id="타입스크립트를-만나다">타입스크립트를 만나다</h3>
<p>이 프로젝트를 통해 처음으로 타입스크립트를 접하게 되었는데 코드를 작성하면서 &#39;와~ 진짜 유용한데?&#39;라는 생각이 들었다. 마우스를 갖다 대면 타입이 다 보이고 객체 타입 같은 경우 어떤 프로퍼티를 가졌는지 어디서든 확인이 가능해서 코드를 뒤져볼 필요도 없고 전체적으로 코드에 대해 파악하는 속도도 약간 빨라진 것 같다.</p>
<p>잘못 입력했을 때는 빨간 줄이 그어지면서 타입을 어떻게 맞춰야 하는지 알려주니까 정말 편하고 이런 식으로 컴파일 타임에서 버그가 잡히겠구나 생각하니 안심도 됐다. 다만 이 글을 쓰는 시점에서는 베이스 코드 없이 코딩하다 보니 수많은 빨간 줄들이 그어져 마음에 스크래치를 남기고 있다.</p>
<p>그 외에도 아직 체감하지 못했지만 인터페이스 등이 도입돼 객체지향 프로그래밍을 지향하기에 좋고 타입 추론, 타입 호환성, 제네릭 등 유용하게 활용할 수 있는 기능들이 많이 있어 타입스크립트에 대해 더 알고 싶다고 느꼈다.</p>
<h2 id="개선하고-싶은-점">개선하고 싶은 점</h2>
<h3 id="좋은-ux란">좋은 UX란</h3>
<p>좋은 UX란 무엇일까. 예외 처리 구현사항이 정해진 바 없이 자유로웠기 때문에 나름대로 고민 끝에 사용 중인 투두 리스트 앱을 참고해 입력값 검증 기능과 삭제 시 모달창을 띄우는 기능을 구현했다.</p>
<p>하지만 다시 돌아보면 이게 최선인지 항상 고민하게 된다. 처음엔 입력값 검증도 모달을 띄웠다가 사용자 입력을 방해하는 것 같아 버튼의 색상으로 알 수 있도록 변경하기도 했고 지금은 삭제 확인을 묻는 모달창을 띄우도록 했지만 일단 삭제 후 일정 시간 내에 취소할 수 있게 만들면 어땠을까 하는 생각도 든다.</p>
<p>단 하나의 페이지, 적은 수의 컴포넌트에서도 사용자와 인터렉션하는 부분을 잘 만드는 건 정말 어렵다는 걸 느꼈고 앞으로 서비스를 이용할 때 어떻게 상호작용하도록 만들어져 있는지 꼼꼼하게 살펴봐야겠다.</p>
<h2 id="개인-프로젝트-후기">개인 프로젝트 후기</h2>
<p>팀 프로젝트를 좋아한다고 생각했는데 요새 계속 팀 프로젝트만 해서 그런지 간만에 개인 프로젝트를 하니 해방감을 느꼈다. 자유롭게 원하는 속도로 원하는 대로 개발할 수 있고 하나하나 내 손으로 작업했다는 데서 오는 성취감이 있다.</p>
<p>내 경우에 열정과 동기부여를 높이는 데에는 홀로 개발하는 시간이 꼭 필요한 것 같다. 지금 참여하는 프리온보딩 코스가 끝나더라도 지금까지 작업한 코드를 리팩토링하고 배운 점을 정리해서 반영하는 시간을 꼭 가지고 싶다.</p>
<hr>
<p><a href="https://www.wanted.co.kr/">#wanted</a> <a href="https://wecode.co.kr/">#wecode</a> <a href="https://rootimpact.org/">#rootimpact</a> <a href="https://www.solarconnect.kr/">#solarconnect</a> <a href="https://www.rocketpunch.com/@YeriKim">#멘토 김예리님</a></p>
]]></description>
        </item>
    </channel>
</rss>