Jekyll2024-01-15T02:05:03+00:00https://16yongjin.github.io//feed.xmlYONGJIN LABAn amazing website.조용진yongjin0802@gmail.com2024년 사용 중인 맥 생산성 유틸리티2024-01-13T07:00:00+00:002024-01-13T07:00:00+00:00https://16yongjin.github.io//utility/2024-mac-utils<p>잘 쓰고 있는 맥 유틸리티들</p>
<h1 id="maccy">Maccy</h1>
<p><img src="https://github.com/16Yongjin/16Yongjin.github.io/assets/22253556/875bd2a6-0a6c-442b-94a1-b8d707bbd83c" alt="maccy" /></p>
<p>복붙을 한 번에 여러 번 할 수 있는 클립보드 매니저</p>
<h2 id="설치">설치</h2>
<p><a href="https://maccy.app/">https://maccy.app/</a></p>
<h2 id="나의-사용-사례">나의 사용 사례</h2>
<p>문서 작성할 때, 이곳저곳에서 텍스트를 복사해다가 붙여넣기 할 때 유용하다.</p>
<p>방금 복사 해놓고 까먹고 있던 코드도 다시 찾아볼 수 있다.</p>
<p>팁: 단축키로 <code class="language-plaintext highlighter-rouge">CMD + SHIFT + V</code>를 설정하는 게 편하다.</p>
<p> </p>
<h1 id="fig">Fig</h1>
<p><img src="https://github.com/16Yongjin/16Yongjin.github.io/assets/22253556/88d1acbc-6646-46c9-bfcb-2e1483d5f01b" alt="fig" /></p>
<p>VS Code 터미널에서 명령어 자동완성을 도와준다</p>
<h2 id="설치-1">설치</h2>
<p><a href="https://fig.io/">https://fig.io/</a></p>
<h3 id="참고">참고</h3>
<p>Fig가 23년 8월에 AWS에 인수돼서 CodeWhisperer 라는 앱이 설치된다.</p>
<p>AWS 로그인해야 해서 귀찮긴 하지만 무료로 쓸 수 있다.</p>
<h2 id="나의-사용-사례-1">나의 사용 사례</h2>
<p>아래와 같이 긴 yarn 모노레포 명령어도 자동완성해줘서</p>
<p><code class="language-plaintext highlighter-rouge">yarn workspace server add package</code></p>
<p>명령어를 기억할 필요 없이 방향키로 선택만 하면 된다.</p>
<p>또한, npm 패키지 설치할 때 이름이랑 버전까지 알려준다.</p>
<p><code class="language-plaintext highlighter-rouge">cw ai</code> 명령어를 입력하면 자연어로 명령어를 입력할 수 있음</p>
<p><img src="https://github.com/16Yongjin/16Yongjin.github.io/assets/22253556/205fab1d-ed64-454f-bc4b-0c5c567d1b2b" alt="cw ai" /></p>
<p> </p>
<h1 id="oslash">OSlash</h1>
<p><img src="https://github.com/16Yongjin/16Yongjin.github.io/assets/22253556/bec97640-92fc-4fd1-9755-37540a11d005" alt="oslash-1" /></p>
<p>자주 가는 페이지를 o/ 로 시작하는 링크로 짧게 입력해서 접속할 수 있다.</p>
<h2 id="설치-2">설치</h2>
<p>최근에 무료 오픈소스로 풀렸다. 원래 1년에 5만원씩 내야 했는데 사업이 잘 안됐나보다.</p>
<p><a href="https://github.com/getoslash/oslash">https://github.com/getoslash/oslash</a></p>
<ol>
<li>릴리즈 탭에서 <code class="language-plaintext highlighter-rouge">oslash-chrome-extension.zip</code> 파일을 다운로드한다.</li>
<li>확장프로그램 관리 페이지에서 압축해제한 폴더를 불러오면 된다.</li>
</ol>
<h2 id="나의-사용-사례-2">나의 사용 사례</h2>
<ul>
<li><code class="language-plaintext highlighter-rouge">o/jira</code>: 개인 지라 보드로 이동</li>
<li><code class="language-plaintext highlighter-rouge">o/qa</code>: 현재 QA 중인 지라 보드로 이동</li>
<li><code class="language-plaintext highlighter-rouge">o/prs</code>: 깃헙 요청받은 PR 목록 페이지로 이동</li>
<li><code class="language-plaintext highlighter-rouge">o/repo/<my-project></code>: 레포 깃헙 페이지로 이동</li>
<li><code class="language-plaintext highlighter-rouge">o/pr/<my-project></code>: 레포의 PR 페이지로 이동</li>
<li><code class="language-plaintext highlighter-rouge">o/wiki/<my-project></code>: 프로젝트의 위키 페이지로 이동</li>
<li><code class="language-plaintext highlighter-rouge">o/scrum</code>: 스크럼 작성 페이지로 이동</li>
</ul>
<p> </p>
<h1 id="cleanshot-x">CleanShot X</h1>
<iframe width="560" height="315" src="https://www.youtube.com/embed/FZbICrBKWIU?si=qNB-iz2PB6EgRCxM" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen=""></iframe>
<p>스크린샷, GIF, 비디오 캡쳐와 그림판, 사진 크롭, 이어붙이기 등 다양한 편집 기능을 제공한다.</p>
<h2 id="설치-3">설치</h2>
<p><a href="https://cleanshot.com/">https://cleanshot.com/</a></p>
<h2 id="나의-사용-사례-3">나의 사용 사례</h2>
<p>튜토리얼 문서에 들어갈 이미지를 만들 때 정말 편하다.</p>
<p><img src="https://github.com/16Yongjin/16Yongjin.github.io/assets/22253556/d72ddb93-bae0-4ab0-82ba-02e021597c79" alt="Cleanshot x usage" /></p>
<p>사진 찍자마자 내장된 그림판으로 설명 달아서 위키에 드래그&드롭하면 끝난다.</p>
<h3 id="참고-1">참고</h3>
<p>여기서 소개하는 프로그램 중에 유일한 유료앱이다.</p>
<p>4만 원 주면 버전 하나를 평생 쓸 수 있다.</p>
<p> </p>
<h1 id="karabiner-elements">Karabiner Elements</h1>
<p>키보드 단축키를 설정할 수 있다.</p>
<h2 id="설치-4">설치</h2>
<p><a href="https://karabiner-elements.pqrs.org/">https://karabiner-elements.pqrs.org/</a></p>
<h2 id="나의-사용-사례-4">나의 사용 사례</h2>
<h3 id="앱-단축키">앱 단축키</h3>
<ul>
<li><code class="language-plaintext highlighter-rouge">Option + 1</code>: VS Code 에디터 열기</li>
<li><code class="language-plaintext highlighter-rouge">Option + 2</code>: Arc 브라우저 열기</li>
<li><code class="language-plaintext highlighter-rouge">Option + 3</code>: Warp 터미널 열기</li>
<li><code class="language-plaintext highlighter-rouge">Option + 4</code>: Obsidian 메모장 열기</li>
<li><code class="language-plaintext highlighter-rouge">Option + F</code>: Figma 열기</li>
<li><code class="language-plaintext highlighter-rouge">Option + D</code>: Finder 열기</li>
</ul>
<h3 id="코드-수정">코드 수정</h3>
<ul>
<li><code class="language-plaintext highlighter-rouge">Caps Lock</code> + <code class="language-plaintext highlighter-rouge">wasd</code> 나 <code class="language-plaintext highlighter-rouge">ijkl</code>로 방향키 이동</li>
<li>새끼손가락에 무리가 가는 ESC, 백틱, 지우기, <code class="language-plaintext highlighter-rouge">=</code> 키 등을 손 위치를 바꾸지 않고 입력</li>
<li>한 손으로 VS Code에서 지원하는 코드 편집 기능 90% 수행 가능</li>
</ul>
<h3 id="개인-설정파일">개인 설정파일</h3>
<ul>
<li><a href="https://gist.github.com/16Yongjin/764fbf74e8ea4d52ec4e22c643030351">karabiner.json</a></li>
</ul>
<p> </p>
<h1 id="warp">Warp</h1>
<p><img src="https://github.com/16Yongjin/16Yongjin.github.io/assets/22253556/828cd0aa-99ca-4c29-99eb-d92e0f4146b5" alt="warp" /></p>
<p>써본 터미널 중에 제일 편하다.</p>
<p>자동완성도 잘 됨</p>
<p>Rust로 작성돼서 빠르다고 함</p>
<h2 id="설치-5">설치</h2>
<p><a href="https://warp.dev/">https://warp.dev/</a></p>
<p> </p>
<h1 id="obsidian">Obsidian</h1>
<p><img src="https://github.com/16Yongjin/16Yongjin.github.io/assets/22253556/36bfd404-1b3f-41d5-acad-6ddbcb08c563" alt="Obsidian" /></p>
<p>메모앱</p>
<p>마크다운 기반이라 작성도 편하고 파일 동기화가 쉽다.</p>
<p>플러그인도 많지만 단순 기록용으로 쓰기에도 훌륭하다.</p>
<h2 id="설치-6">설치</h2>
<p><a href="https://obsidian.md/">https://obsidian.md/</a></p>
<p> </p>
<h1 id="vimac">Vimac</h1>
<p><img src="https://github.com/16Yongjin/16Yongjin.github.io/assets/22253556/18d03d90-86e4-49ca-b77e-c056d799ab01" alt="vimac" /></p>
<p>키보드로 화면의 버튼을 클릭할 수 있다.</p>
<h2 id="설치-7">설치</h2>
<p><a href="https://vimacapp.com/">https://vimacapp.com/</a></p>
<h3 id="참고-2">참고</h3>
<p>아래에 있는 두 번째 버튼이 진짜 다운로드 버튼이다.</p>
<p><a href="https://www.homerow.app/">Homerow</a>라는 이름으로 새 앱이 나왔는데 유료다.</p>
<p>기존 Vimac은 무료로 사용가능</p>
<p> </p>
<h1 id="이외">이외</h1>
<h2 id="bartender-맥-메뉴바-아이콘-정리"><a href="https://www.macbartender.com/">Bartender</a>: 맥 메뉴바 아이콘 정리</h2>
<p><img src="https://github.com/16Yongjin/16Yongjin.github.io/assets/22253556/519d1626-2497-4636-b1f6-09bb4bcc2aac" alt="Bartender" /></p>
<h2 id="audio-switcher-오디오-기기별-음량-설정"><a href="https://apps.apple.com/us/app/audioswitcher/id561712678">Audio Switcher</a>: 오디오 기기별 음량 설정</h2>
<p><img src="https://github.com/16Yongjin/16Yongjin.github.io/assets/22253556/a6226b06-e88a-421e-8439-23872e8afeb9" alt="Audio Switcher" /></p>
<h2 id="orbstack-엄청-빠른-도커-런타임"><a href="https://orbstack.dev/">OrbStack</a>: 엄청 빠른 도커 런타임</h2>
<p><img src="https://github.com/16Yongjin/16Yongjin.github.io/assets/22253556/1adad7e0-f4b5-4c80-9cdb-6d4da78e7a5d" alt="orbstack" /></p>
<ul>
<li><a href="https://twitter.com/junhyeok_dev/status/1678704817454354432?s=20">트위터 출처</a></li>
</ul>
<h2 id="arc-브라우저"><a href="https://arc.net/">Arc 브라우저</a></h2>
<p><img src="https://github.com/16Yongjin/16Yongjin.github.io/assets/22253556/aa840dbf-e560-4fb4-a562-8d0ae8f1aabb" alt="arcbrowser" /></p>
<h2 id="raycast-맥-spotlight-대체"><a href="https://www.raycast.com/">Raycast</a>: 맥 Spotlight 대체</h2>yongjin0802잘 쓰고 있는 맥 유틸리티들2023년 돌아보기2023-12-31T12:00:00+00:002023-12-31T12:00:00+00:00https://16yongjin.github.io//etc/2023-look-back<p>23년 돌아보기</p>
<h2 id="성과평과-최고점을-받다">성과평과 최고점을 받다</h2>
<p>받을 수 있는 가장 좋은 평가를 받았다.</p>
<p>연봉도 좀 오르고 성과급도 많이 나와서 2주 정도 기분이 좋다가</p>
<p>급속도로 불행해지기 시작했다.</p>
<p>다음에도 좋은 평가를 받기 위해 내가 해야 하는 것을 고민하기 시작했는데</p>
<p>몇 달 동안 생각을 해도 딱히 답이 나오지 않았다.</p>
<p>내가 할 수 있는 게 없다는 무기력감에 빠져 한동안 힘들게 지냈다.</p>
<p>한편,</p>
<p>회사에 사이드 프로젝트 노하우를 공유하려고 지금까지 만든 것을 정리할 기회가 있었다.</p>
<p><img src="https://github.com/16Yongjin/16Yongjin.github.io/assets/22253556/edd7127e-5adc-4a81-a106-5d8eadbd0e81" alt="회사에서 한 사이드프로젝트" /></p>
<p><img src="https://github.com/16Yongjin/16Yongjin.github.io/assets/22253556/31c29655-e589-457d-ab34-2f97e1c7bb1f" alt="예전에 한 사이드프로젝트" /></p>
<p>사내 위키와 개인 블로그에서 지금껏 해온 것을 돌아보니</p>
<p>나는 프로그램을 만드는 것을 좋아하는 사람이고</p>
<p>그 일을 오랫동안 해왔고</p>
<p>꽤나 잘하는 사람이라는 걸 잊고 있었다.</p>
<p> </p>
<p>성과급을 더 받는다고 인생이 크게 바뀌지 않는데</p>
<p>그 돈을 받기 위해 싫어하는 일을 억지로 할바에</p>
<p>내가 좋아하고 잘할 수 있는 일에 집중해야겠다는 생각이 팍 들었다.</p>
<h2 id="하고-싶은-것">하고 싶은 것</h2>
<p>30년 뒤에 성공적으로 은퇴해서</p>
<p>노후 보장 다 해놓아서 돈 걱정도 집 걱정도 없고</p>
<p>인생의 모든 미션을 다 마친 뒤 남의 시선을 신경 쓸 것도 없고</p>
<p>체력도 남아 돌아서 아무거나 해도 괜찮다면</p>
<p>나는 뭘 하고 싶은가? 하고 생각해 봤다.</p>
<p> </p>
<p>그렇다면 무조건 코딩. 정확히는 제품 개발</p>
<p>돈은 못 벌어도 사람들의 문제를 제대로 해결하고</p>
<p>아름다운 디자인과 말도 안 되게 편한 UI를 가진 제품을</p>
<p>만들기 위해 하루종일 고민하고 몰입해서 개발하는 일만 하고 싶다.</p>
<p> </p>
<p>그 일을 왜 하고 싶은지 이유를 찾기 위해</p>
<p>내 마음속을 파고들다 보면 논리적으로 설명할 수 없는 지점에 다다른다.</p>
<p>이유는 없다. 그냥 좋다.</p>
<p> </p>
<p>근데 이걸 하기 위해 돌고 돌아 30년을 기다릴 필요가 있을까?</p>
<p>지금 당장 할 수 있을 거 같은데..</p>
<h2 id="좋아서-한다에서-싫어도-한다로">“좋아서 한다”에서 “싫어도 한다”로</h2>
<blockquote>
<p>좋아하는 일을 계속하는 것</p>
<p>그것은 즐겁지 만은 않아</p>
<p>- 요아소비 <군청> 가사 중</군청></p>
</blockquote>
<p>나중에 회사를 차리고 싶을 때</p>
<p>아무리 좋은 아이디어, 기술력, 충분한 자본이 있더라도</p>
<p>내가 중간에 싫증을 내거나 지쳐서 그만두면 망할게 확실하다.</p>
<p> </p>
<p>이에 대한 예방책으로 작은 습관을 들였다.</p>
<p>일단 토요일이 되면 점심 챙겨 먹고 바로 여의도 카페로 간다.</p>
<p>아무리 하기 싫어도, 컨디션 안 좋아도 일단 간다.</p>
<p>카페에서 노트북을 펴기만 하면 그날 하루는 성공이다.</p>
<p>가서 뭐라도 하기 때문이다.</p>
<p> </p>
<p>이렇게 습관을 들이기까지 정말 정말 힘들었다.</p>
<p>카페로 가는 매 걸음마다</p>
<p>‘다른 사람들은 가족, 친구와 행복한 주말을 보내고 있을 텐데 나는 지금 뭐 하고 있는 거지?’</p>
<p>‘내가 지금 하고 있는 게 진짜 도움이 될까? 그 시간에 주식 코인 부동산 공부하는 게 맞지 않나?’</p>
<p>같은 생각이 계속 들었지만 나에 대한 의심을 잠시 접어두고</p>
<p>당장 할 수 있는 일을 조금씩 해봤다.</p>
<p> </p>
<p>이렇게 세 달 정도 지나니까</p>
<p>순수하게 내 생각으로, 내 손으로 만든 작고 소중한 제품 하나가 만들어졌고</p>
<p>그 과정에서 노하우가 쌓여서 나만의 개발 프로세스가 정립됐다.</p>
<p>제품 개발 프로세스를 몇 번 반복하니까 나름 빠르고 안정적으로 제품을 뽑아내는 기술도 생겼다.</p>
<p>그 바탕엔 꾸준하게 시간을 쏟는 습관이 인프라로 깔려있다.</p>
<h2 id="잘하고-싶다-보단-전하고-싶다">잘하고 싶다 보단 전하고 싶다</h2>
<blockquote>
<p><a href="https://www.youtube.com/@373OFF">미나미</a> 선생님의 오랜 무명가수 생활을 끝낸 한마디</p>
</blockquote>
<p>뭔가 잘 안 되고 힘만 든다면 너무 잘하려고 해서가 아닐까</p>
<p>기술 발표를 하든, 작은 서비스를 만드려고 하든</p>
<p>그저 잘하려고만 하면 할 일이 끝도 없고, 딱히 그걸 사람들이 원하지도 않는 거 같다..</p>
<p>내가 전하고 싶은 메시지를 딱 하나라도 명확히 알고</p>
<p>나만이 할 수 있는 방식으로 표현하기만 해도 충분해 보인다.</p>
<p>그게 힘도 덜 들고 효과도 확실한 듯</p>
<h2 id="인생-루프에-작은-함수-끼워넣기">인생 루프에 작은 함수 끼워넣기</h2>
<p>작지만 큰 효과를 봤던 습관들이 좀 있다.</p>
<h3 id="1-하고-싶은-걸-눈에-잘-보이게-하기">1. 하고 싶은 걸 눈에 잘 보이게 하기</h3>
<p>Atomic Habits 한 장 요약이다.</p>
<p><img src="https://github.com/16Yongjin/16Yongjin.github.io/assets/22253556/205c9d6d-74a5-4128-a4f8-f1c2818e1087" alt="Atomic Habits" /></p>
<p>해야 할 일이 있으면 눈에 잘 보이는 곳에 적는다. (방문에 붙인 부착식 화이트보드)</p>
<p>적으니까 300일째 98% 확률로 목표 달성하고 있다.</p>
<p>이게 아침에 일어나서부터 계속 눈에 보이니까</p>
<p>조금 벅찬 목표도 어떻게든 할 방법이 찾아지는 거 같다.</p>
<p><img src="https://github.com/16Yongjin/16Yongjin.github.io/assets/22253556/c0001261-63d4-428c-b603-d79f72542e48" alt="할일 보드" /></p>
<h3 id="2-가장-중요한-일부터-하기">2. 가장 중요한 일부터 하기</h3>
<p>오전의 1시간이 오후의 4시간보다 가치 있다.</p>
<p>정말 하고 싶은 일이 있으면</p>
<p>어떻게든 시간 내서 가장 먼저 처리하고 있다.</p>
<p>오후에 하면 3시간 걸릴 일도</p>
<p>가장 머리가 맑을 때, 계획 잘 세워서 하면 20~30분 안에 끝나는 거 10번 넘게 경험했다.</p>
<h3 id="3-오늘-하루-돌아보기">3. 오늘 하루 돌아보기</h3>
<p>어제의 나와 오늘의 나를 <code class="language-plaintext highlighter-rouge">diff</code> 떠서 차이가 없다면 슬플 거 같다.</p>
<p>지금 능력이 많이 부족한데</p>
<p>유일하게 기댈 수 있는 건 성장 기울기 뿐이라서</p>
<p>내가 잘 성장하고 있는지 확인하기 위해</p>
<p>오늘 새롭게 배우고 생각하게 된 것, 실수한 거, 더 잘할 방법을 매일 적고 있다.</p>
<p><img src="https://github.com/16Yongjin/16Yongjin.github.io/assets/22253556/cb475280-1934-4fff-bcb1-412bce68d98e" alt="매일성장하기첫날" />
(5월부터)</p>
<p><img src="https://github.com/16Yongjin/16Yongjin.github.io/assets/22253556/7a14b4f8-f2bc-48b0-bfba-5c99760da468" alt="매일성장하기최근" />
(어제까지)</p>
<h3 id="하루를-요약하면-이런-느낌">하루를 요약하면 이런 느낌</h3>
<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">while</span> <span class="p">(</span><span class="nx">timeLeft</span><span class="o">--</span><span class="p">)</span> <span class="p">{</span>
<span class="nx">잠자기</span><span class="p">();</span>
<span class="nx">가장_중요한_일부터_하기</span><span class="p">();</span>
<span class="nx">일하기</span><span class="p">();</span>
<span class="nx">밥먹기</span><span class="p">();</span>
<span class="nx">운동하기</span><span class="p">();</span>
<span class="nx">하루_성장_돌아보기</span><span class="p">();</span>
<span class="nx">내일할일_정하기</span><span class="p">();</span>
<span class="k">if</span> <span class="p">(</span><span class="nx">day</span> <span class="o">===</span> <span class="nx">Saturday</span><span class="p">)</span> <span class="p">{</span>
<span class="nx">카페가서_코딩하기</span><span class="p">();</span>
<span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>
<p>함수 하나하나는 작고 하기 쉬운데</p>
<p>시간이 한참 지난 뒤에 보면 크게 성장해 있을 거라 믿는다.</p>
<h2 id="기능은-사라지고-느낌만-남는다">기능은 사라지고 느낌만 남는다</h2>
<blockquote>
<p>우리는 기능보다 그 기능이 불러일으키는 감정에 더 관심 있다.</p>
</blockquote>
<p>프로젝트를 완료하는 비결 한 가지를 뽑으라면 바로 ‘느낌’이다.</p>
<p>이게 완성되고 나서 내가 어떤 느낌을 받을지 미리 상상해 본다.</p>
<p>기분이 좋거나 편안하다면 어떻게든 하는 게 맞고</p>
<p>뭔가 쓰흡..하고 걸리는 느낌이라면 안 하는 게 맞다.</p>
<p>그 느낌, <a href="https://www.youtube.com/watch?v=1mSTAGRCtXE">젠틀몬스터 김한국 대표님</a>의 말을 빌리면 ‘설렘’이라고 할 수 있는데</p>
<p>그 설렘 쫓아가다 보면 프로젝트 중간에 길을 잃지 않을 수 있고</p>
<p>완성된 게 내가 느꼈던 설렘을 주는지로 성공 여부를 알 수 있다.</p>
<h2 id="지라-태스크">지라 태스크</h2>
<p>최소한의 입력으로 지라 이슈를 만들 수 있는 크롬 확장이다.</p>
<p>이슈를 만드려고 지라에 들어갈 때마다 느린 로딩과 복잡한 UI 때문에 스트레스를 받아서 만들었다.</p>
<video width="100%" controls="controls">
<source src="
https://github.com/16Yongjin/16Yongjin.github.io/assets/22253556/34d8cc30-ae27-4eb8-9114-43c6ae1d399f" type="video/mp4" />
</video>
<p>태스크, 서브 태스크, 에픽 모두 지원하고</p>
<p>스프린트, 에픽, 컴포넌트, 레이블도 하나하나 키보드로 입력할 필요 없이 클릭만으로 추가할 수 있다.</p>
<p>40명 정도 쓰고 이걸로 이슈가 500개 넘게 만들어졌다.</p>
<h3 id="참고">참고</h3>
<ul>
<li>블로그 글: <a href="https://yongj.in/development/jira-task/">사이드 프로젝트로 성장하기 - Jira Task</a></li>
<li>발표 자료: <a href="https://docs.google.com/presentation/d/1uwmfcrOptlvqR_P1Jx9ezLmEqkhWIUBOy7wKRTVBpQ4/edit?usp=sharing">사이드 프로젝트 노하우 모음.zip</a></li>
</ul>
<h2 id="경험치-시스템">경험치 시스템</h2>
<p>위키 문서를 읽고 경험치를 얻을 수 있는 시스템이다.</p>
<p>위키 문서 하단에 임베딩해서 쓴다.</p>
<p><img src="https://github.com/16Yongjin/16Yongjin.github.io/assets/22253556/0d50faa9-c817-4b30-8563-efc717531a69" alt="경험치 시스템 데모" /></p>
<h3 id="만든-과정">만든 과정</h3>
<p><a href="https://www.yes24.com/Product/Goods/94462254">일상 속 사물이 알려주는 웹 API 디자인</a>를 보고 필요한 엔티티를 찾고, API를 설계했다.</p>
<p><img src="https://github.com/16Yongjin/16Yongjin.github.io/assets/22253556/0ceea908-91fa-4468-b7ad-2c144e6a3b1d" alt="경험치 시스템 API 설계" /></p>
<p>내가 재밌게 쓸 수 있는가?를 기준으로 UI 동선도 짰다.</p>
<p><img src="https://github.com/16Yongjin/16Yongjin.github.io/assets/22253556/18d13d9a-63d3-44c1-a3b3-e1c720aec306" alt="경험치 시스템 UI 동선" /></p>
<p>UI 동선을 기반으로 피그마로 디자인도 했다.</p>
<p>컨셉은 장난스러운 분위기가 맞춰서 네오 브루탈리즘으로 채택했다.</p>
<p><img src="https://github.com/16Yongjin/16Yongjin.github.io/assets/22253556/1edb3d12-2857-4bb5-9594-c0d20e1b356e" alt="경험치 시스템 디자인" /></p>
<p>이후에 마크업 > API 구축 > FE 구현 순으로 진행했음</p>
<p><img src="https://github.com/16Yongjin/16Yongjin.github.io/assets/22253556/59f9b592-83e3-49ea-87e3-666dbc4fe8a4" alt="경험치 시스템 목록" /></p>
<h3 id="얻은-것">얻은 것</h3>
<p>사용자에게 주는 가치는 약하지만 기술적으로 얻고 싶은 게 있어서 만들었다.</p>
<p>사실 그것보다 <a href="https://github.com/catdad/canvas-confetti">폭죽 라이브러리</a>를 쓸 일을 만들고 싶었음 😆</p>
<p>요거 3주 동안 만들어서 배운 거:</p>
<ul>
<li>여러 페이지에 걸친 사용자 동선을 짜는 방법</li>
<li>기능을 분석해서 필요한 API를 발견하는 방법</li>
<li>SvelteKit, Sqlite, DrizzleORM으로 데이터 저장하고 불러오는 방법</li>
<li>사내 SSO 시스템 연동하는 방법</li>
<li>쿠키 기반 인증</li>
</ul>
<h2 id="책장-플랫폼">책장 플랫폼</h2>
<p>Dribbble에서 본 <a href="https://dribbble.com/shots/16279204-Book-Web-Store-Concept">온라인 서점 디자인</a>을 보고</p>
<p>내가 읽었던 책을 모아서 볼 수 있고, 다른 사람한테도 보여줄 수 있는 공간이 있으면 좋겠다 하고 만들었다.</p>
<p><img src="https://github.com/16Yongjin/16Yongjin.github.io/assets/22253556/1f7a16e0-8f88-4f66-921a-e443dc93da28" alt="드리블 책장" /></p>
<p>책을 보기에도 좋고, 정리하기 편한 UI를 일주일 내내 생각해서 만들었다.</p>
<p><img src="https://github.com/16Yongjin/16Yongjin.github.io/assets/22253556/86ad8367-5a92-4bd6-a95b-ee70d2fb53cb" alt="책장 플랫폼 다이어그램" /></p>
<p>컨셉은 편함과 따뜻함</p>
<p>오두막집 벽난로의 따뜻함과 그 앞의 소파에 앉아서 책을 읽고 있을 때의 편안함을 상상하면서 디자인했다.</p>
<p><img src="https://github.com/16Yongjin/16Yongjin.github.io/assets/22253556/4b0fcf8e-f5f9-4864-8870-e1279fd21785" alt="책장 플랫폼 디자인" /></p>
<p>이후에 마크업 > FE 구현 > BE 구현 > 배포를 진행했다.</p>
<p>책을 검색해서 추가할 수도 있고</p>
<p><img src="https://github.com/16Yongjin/16Yongjin.github.io/assets/22253556/fb792398-aebf-4891-ac05-58434d133382" alt="책장 플랫폼 수정" /></p>
<video width="100%" controls="controls">
<source src="https://github.com/16Yongjin/16Yongjin.github.io/assets/22253556/de175df9-d646-48ca-bad0-af165cad4e0a" />
</video>
<p>내가 책장 목록도 요렇게 볼 수 있다.</p>
<p><img src="https://github.com/16Yongjin/16Yongjin.github.io/assets/22253556/30a7db89-006a-4d70-8636-65b93a902664" alt="책장 플랫폼 메인" /></p>
<h3 id="얻은-거">얻은 거</h3>
<p>전에 경험치 시스템 만들면서 배운 지식을 활용하고 + 디자인만 새롭게 해서 3주 만에 만들었다.</p>
<ul>
<li>Figma의 Auto Layout 사용법</li>
<li>적당히 만족할 만한 디자인을 만들고 다음으로 넘어가기
<ul>
<li>한 부분에 너무 많은 시간을 쏟지 않기</li>
</ul>
</li>
<li>관계형 DB에서 순서정보를 저장하는 방법</li>
<li>살짝 복잡한 DB 쿼리 짜는 법</li>
<li>사용자 편의와 서버 부하 간 균형 맞추기</li>
</ul>
<h2 id="사이트-배포-플랫폼">사이트 배포 플랫폼</h2>
<p><img src="https://github.com/16Yongjin/16Yongjin.github.io/assets/22253556/182cccfd-4ff7-4072-ad31-07d6621f5c14" alt="사이트 배포 플랫폼 히어로" /></p>
<p>회사 인프라가 정말 잘 되어 있어서 서버 개발자에겐 천국이지만</p>
<p>FE 개발자가 편하게 쓸 수 있는 건 많이 없었다.</p>
<p>특히, 간단하게 사이트 배포해서 테스트 해보려면</p>
<p>도커 파일은 무조건 만들어야 하고, k8s 클러스터 띄우거나 구글 클라우드 런 비슷한 서비스를 이용해야 하는 등 최소 2~3시간은 걸렸다.</p>
<p><a href="https://app.netlify.com/drop">Netlify drop</a>이나 <a href="https://surge.sh/">Surge</a> 처럼 그냥 파일을 올리면 바로 배포되는 서비스가 하나 있으면 좋겠다고 생각하던 차에</p>
<p>사내 과제로 진행할 기회가 생겨서 바로 참여했다.</p>
<h3 id="초기-버전">초기 버전</h3>
<p>일단 돌아가는지 확인하는 게 중요했기에</p>
<p>처음부터 잘 만드려고 하지 않고</p>
<ol>
<li>파일을 업로드할 수 있는 CLI</li>
<li>업로드한 파일을 폴더에 저장하는 노드 서버</li>
<li>폴더에 저장된 파일을 서빙해 주는 NGINX 서버</li>
</ol>
<p>3개로 구성된 초간단 버전을 팀원들과 만들었다.</p>
<h3 id="초기-버전-배포">초기 버전 배포</h3>
<p>이 정도만 돼도 실사용에 문제가 없어서</p>
<p>팀원들에게 홍보했는데 아무도 쓰질 않았다…</p>
<p>사실, 나도 쓰고 싶은 마음이 들지 않았다.</p>
<h3 id="뭐가-문제일까">뭐가 문제일까</h3>
<p>문제는 안정감과 신뢰감이라는 생각이 들었다.</p>
<p>FE개발자는 CLI보다 웹 UI에 더 익숙하다.</p>
<p>지금 만든 앱은 CLI만 딸랑있고, 파일이 어떻게 저장되고, 다시 접근하려면 어떻게 해야 하는지 불투명했다.</p>
<p>편하게 쓸 수 있고, 고급스러운 디자인으로 신뢰감을 주는 웹 UI와</p>
<p>올린 파일을 내가 통제하고 있다는 느낌과 안정감을 주는 각종 기능이 필요했다.</p>
<h3 id="제작-과정">제작 과정</h3>
<p>일주일 동안 온몸을 비틀면서 고민하며 내가 받고 싶은 느낌을 구체화하고</p>
<p>그 느낌을 줄 수 있는 UI 구성과 페이지 동선을 생각해 냈다.</p>
<p><img src="https://github.com/16Yongjin/16Yongjin.github.io/assets/22253556/b3d56fe4-757a-40ee-ad43-24c414200afd" alt="IMG_2559" />
<img src="https://github.com/16Yongjin/16Yongjin.github.io/assets/22253556/e005c5de-aa0b-4dba-bfad-6c61bc352263" alt="IMG_2560" /></p>
<p>사용자가 능력 있는 사람임을 느끼게 주고 싶어서 <a href="https://www.shadcn-vue.com/">shadcn ui</a>, vercel의 무겁고 고급진 디자인을 메인 컨셉으로 채택했다.</p>
<p><img src="https://github.com/16Yongjin/16Yongjin.github.io/assets/22253556/66c86ef4-b1c3-40cb-9435-f12aa89ee839" alt="shadcn" /></p>
<p>책장 플랫폼 만들면서 배운 Auto Layout을 요긴하게 쓰면서 Figma로 디자인했다.</p>
<p>화려하지도 재밌지도 않지만 오랫동안 질리지 않을 디자인을 하고 싶었다.</p>
<p><img src="https://github.com/16Yongjin/16Yongjin.github.io/assets/22253556/1bf0d6a6-5a8c-44f8-b71f-6d107731886c" alt="사이트 배포 플랫폼 디자인" /></p>
<p>추석 연휴 기간 동안, 코딩하다 누웠다 코딩하다 눕기만 2주 정도 반복하다 보니 완성됐다.</p>
<video width="100%" controls="controls">
<source src="https://github.com/16Yongjin/16Yongjin.github.io/assets/22253556/ad00353b-c818-4662-b461-ba2b3b8e0def" type="video/mp4" />
</video>
<p>폴더만 올려놓으면 배포가 끝난다.</p>
<p>HTTPS가 적용된 사이트 주소가 자동으로 생성되고</p>
<p>SPA 라우팅, 자동 삭제 타이머까지 설정할 수 있다.</p>
<h3 id="홍보하기">홍보하기</h3>
<p><a href="https://www.yes24.com/Product/Goods/116413780">1페이지 마케팅 플랜</a>의 고객의 고통을 공략하라 챕터를 보고</p>
<p>FE개발자라면 공감할 수 있는 이야기를 만들었다.</p>
<p>한 FE개발자가 웹사이트를 배포하다가 복잡한 사내 인프라의 벽에 절망한다.</p>
<p>우리 서비스를 만나 편하게 웹사이트를 배포하고 행복하게 퇴근하는 스토리다.</p>
<p><img src="https://github.com/16Yongjin/16Yongjin.github.io/assets/22253556/db0cb364-a621-4b1e-8a71-216dffebca49" alt="사이트 배포 플랫폼 포스터" /></p>
<p>미팅 때 좀 더 몰입감 있는 버전을 써서 홍보하니까 사람들이 엄청 집중하는게 보였다.</p>
<h3 id="상상-이상으로-잘됨">상상 이상으로 잘됨</h3>
<p>지금 작업하고 있는 걸 기획자에게 확인받는 용으로</p>
<p>다른 팀원들에게 기능을 바로 테스트할 수 있는 링크를 건네주는 용으로</p>
<p>가볍게 쓰기 편해서 많은 팀원들이 사용해주고 계신다.</p>
<p>지금 사이트 100개 넘게 배포됐다.</p>
<h3 id="배포-자동화-기능">배포 자동화 기능</h3>
<p>프로젝트라는 공용 폴더 비슷한 기능 단위를 추가하고 CLI 기능을 강화해서</p>
<p>CI 상에서 빌드한 사이트를 바로 배포할 수 있게 했다.</p>
<p>PR 올리면 댓글에 레포 + 브랜치 이름으로 된 사이트 주소가 달린다.</p>
<p><img src="https://github.com/16Yongjin/16Yongjin.github.io/assets/22253556/117c94cc-812d-4d48-bf2b-a5f71daf361b" alt="사이트 배포 PR댓글" /></p>
<p>전엔 코드가 잘 동작하는지 보려면 로컬에서 브랜치 체크아웃하고 직접 빌드해야 했는데</p>
<p>이젠 사이트 링크에 접속해서 바로 확인하면 된다.</p>
<h2 id="점을-찍고보니-선이었다">점을 찍고보니 선이었다</h2>
<p>당장 도움이 안 되고, 아무도 쓰지 않을 거 같지만</p>
<p>재밌어보여서 시도한 프로젝트가</p>
<p>다음 프로젝트를 하는데 꼭 필요한 기술을 익히게 해줬다.</p>
<p>그렇게 프로젝트를 여러 개 하다보니까</p>
<p>쓸 만한 게 하나 걸려들기도 한다.</p>
<p>지금까지의 경험을 살려서 또 뭘 해볼까?</p>
<h2 id="읽은-책">읽은 책</h2>
<p><img src="https://github.com/16Yongjin/16Yongjin.github.io/assets/22253556/822b2558-6df8-468e-9444-a7054ae15eca" alt="23년 읽은 책" /></p>
<h2 id="24년-목표">24년 목표</h2>
<ul>
<li>매일 할 수 있는 일에 집중하기</li>
</ul>yongjin080223년 돌아보기ESP32와 LED 매트릭스로 디지털 명패 만들기2023-06-10T12:00:00+00:002023-06-10T12:00:00+00:00https://16yongjin.github.io//development/digital-nameplate<p>사무실의 시대가 돌아왔다. 회사에 내 자리가 생기면 가장 갖고 싶었던 게 이름표다. 테크 감성 오지는 디지털 명패 만드는 법을 알아보자.</p>
<p><img src="https://github.com/16Yongjin/16Yongjin.github.io/assets/22253556/76625900-9c14-44dd-b43d-4c46fdcdf134" alt="데스크 셋업" /></p>
<h2 id="데모">데모</h2>
<video width="100%" controls="controls">
<source src="https://github.com/16Yongjin/16Yongjin.github.io/assets/22253556/6d730f21-6473-40a3-b467-1a0bd0a0c7f2" type="video/mp4" />
</video>
<p>회의 등으로 자리를 비웠을 때, 내 상태를 표시할 수 있다.</p>
<ul>
<li>⚫️ - 출근 안 함 / 재택 중 (화면 꺼짐)</li>
<li>⚪️ - 근무 중 (이름 표시)</li>
<li>🔴 - 바쁨 / 방해금지</li>
<li>🟢 - 티타임 중</li>
<li>🔵 - 회의 중</li>
</ul>
<h2 id="필요한-부품">필요한 부품</h2>
<p><img src="https://github.com/16Yongjin/16Yongjin.github.io/assets/22253556/8ca688aa-af3a-472f-b2b1-d14066e37f5a" alt="디지털 명패 부품" /></p>
<h3 id="부품-가격">부품 가격</h3>
<ul>
<li>배송비 포함 약 41,000원 (23. 6. 10 기준)</li>
</ul>
<table>
<thead>
<tr>
<th>부품명</th>
<th>가격</th>
<th>구매 링크</th>
</tr>
</thead>
<tbody>
<tr>
<td>LED 매트릭스</td>
<td>20,000원</td>
<td><a href="https://ko.aliexpress.com/item/4000002686894.html">알리익스프레스</a></td>
</tr>
<tr>
<td>ESP32 + 어댑터</td>
<td>6,700원</td>
<td><a href="https://ko.aliexpress.com/item/1005004268911484.html">알리익스프레스</a></td>
</tr>
<tr>
<td>점퍼 와이어 120개</td>
<td>2,200원</td>
<td><a href="https://ko.aliexpress.com/item/4000203371860.html">알리익스프레스</a></td>
</tr>
<tr>
<td>빵판</td>
<td>2,000원</td>
<td><a href="https://ko.aliexpress.com/item/1005004373602861.html">알리익스프레스</a></td>
</tr>
<tr>
<td>버튼 25개</td>
<td>3,000원</td>
<td><a href="https://ko.aliexpress.com/item/32834276752.html">알리익스프레스</a></td>
</tr>
<tr>
<td>막대저항</td>
<td>3,000원</td>
<td><a href="https://smartstore.naver.com/mechasolution_com/products/2955124756">네이버 쇼핑</a></td>
</tr>
<tr>
<td>U자 점퍼 와이어</td>
<td>4,000원</td>
<td><a href="https://ko.aliexpress.com/item/1005004181586889.html">알리익스프레스</a></td>
</tr>
</tbody>
</table>
<h2 id="조립-방법">조립 방법</h2>
<h3 id="디스플레이-조립">디스플레이 조립</h3>
<p>아래 영상을 보고 LED Matrix와 ESP32를 조립한다.</p>
<iframe width="560" height="315" src="https://www.youtube.com/embed/hmmGhepMbSs" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen=""></iframe>
<p>이때! 영상 나오는 전원 어댑터는 따로 필요가 없다.</p>
<p>ESP32의 5V핀(VIN)을 LED 매트릭스의 POWER +5V에 꽂아도 작동한다.</p>
<p>LED 매트릭스 전원 핀에 일반 점퍼 와이어를 힘줘서 잘 끼우면 겨우겨우 들어간다.</p>
<p><img src="https://github.com/16Yongjin/16Yongjin.github.io/assets/22253556/dec577db-6a87-4a5e-9fdd-d734c38f960c" alt="전원연결" /></p>
<h3 id="컨트롤러-조립">컨트롤러 조립</h3>
<p>아래 영상을 보고 컨트롤러를 조립한다.</p>
<p>버튼마다 핀을 하나씩 배정하면 ESP32 - 컨트롤러 간 케이블이 7개 필요하지만,</p>
<p>영상의 방식대로 하면 케이블이 3개만 있으면 돼서, 선정리가 편하다.</p>
<iframe width="560" height="315" src="https://www.youtube.com/embed/Y23vMfynUJ0" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen=""></iframe>
<p>납작한 U자 점퍼 와이어를 쓰면 깔끔한 컨트롤러를 만들 수 있다.</p>
<p><img src="https://github.com/16Yongjin/16Yongjin.github.io/assets/22253556/821440cd-03e3-4f51-9e37-f9945d155175" alt="컨트롤러" /></p>
<p>3.3v, 접지를 연결하고 빵판 J1번 소켓을 ESP32 33번핀과 연결한다.</p>
<h3 id="실제-연결된-모습">실제 연결된 모습</h3>
<p>ESP32 어댑터를 써서 선 연결도 쉽고, LED 매트릭스에 결합하기도 쉬웠다.</p>
<p><img src="https://github.com/16Yongjin/16Yongjin.github.io/assets/22253556/07af4efb-9cd1-4812-8b7a-889c59555cf8" alt="선연결" /></p>
<h2 id="코드-짜기">코드 짜기</h2>
<h3 id="아두이노-환경-설정">아두이노 환경 설정</h3>
<ol>
<li>
<p><a href="https://www.arduino.cc/en/software">아두이노 IDE</a>를 설치한다.</p>
</li>
<li>
<p>설정에서 아래 ESP32 펌웨어 설정을 추가한다.</p>
<ul>
<li>https://raw.githubusercontent.com/espressif/arduino-esp32/gh-pages/package_esp32_index.json</li>
</ul>
<p><img src="https://github.com/16Yongjin/16Yongjin.github.io/assets/22253556/6b85e112-48b9-48a9-bb9f-1c1b377c84b4" alt="아두이노 IDE 설정" /></p>
</li>
<li>
<p>아래 깃헙에서 zip 파일 다운로드 후, 아두이노 IDE 라이브러리에 zip 파일을 추가한다.</p>
<ul>
<li><a href="https://github.com/mrfaptastic/ESP32-HUB75-MatrixPanel-DMA">ESP32-HUB75-MatrixPanel-DMA</a></li>
<li><a href="https://github.com/adafruit/Adafruit-GFX-Library">Adafruit-GFX-Library</a></li>
<li><a href="https://github.com/adafruit/Adafruit_BusIO">Adafruit_BusIO</a></li>
</ul>
</li>
<li>포트 USB 어쩌구저쩌구로 설정</li>
<li>보드는 ESP32 Dev Module 설정</li>
<li>업로드 속도는 115200</li>
</ol>
<h3 id="테스트-해보기">테스트 해보기</h3>
<p>예제 파일을 열어서 ESP32에 업로드 해본다.</p>
<p><img src="https://github.com/16Yongjin/16Yongjin.github.io/assets/22253556/dad76c63-fbd4-4934-b465-6e2797806a38" alt="예제 파일 열기" /></p>
<p>아래 사진 뜨면 정상이다.</p>
<p><img src="https://github.com/16Yongjin/16Yongjin.github.io/assets/22253556/f8b1bb89-dca1-4958-8ef0-c21d063ee431" alt="예제 파일 작동 사진" /></p>
<h3 id="소스-코드">소스 코드</h3>
<p>눌린 버튼 색상에 따라 텍스트를 그리는 코드이다. (너무 자세한 로직은 생략했음)</p>
<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cp">#include <ESP32-HUB75-MatrixPanel-I2S-DMA.h>
</span>
<span class="c1">// 버튼 설정</span>
<span class="cp">#define BUTTON_PIN 33
</span>
<span class="k">const</span> <span class="kt">int</span> <span class="n">None</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>
<span class="k">const</span> <span class="kt">int</span> <span class="n">BlackButton</span> <span class="o">=</span> <span class="mi">1</span> <span class="o"><<</span> <span class="mi">0</span><span class="p">;</span>
<span class="k">const</span> <span class="kt">int</span> <span class="n">WhiteButton</span> <span class="o">=</span> <span class="mi">1</span> <span class="o"><<</span> <span class="mi">1</span><span class="p">;</span>
<span class="k">const</span> <span class="kt">int</span> <span class="n">RedButton</span> <span class="o">=</span> <span class="mi">1</span> <span class="o"><<</span> <span class="mi">2</span><span class="p">;</span>
<span class="k">const</span> <span class="kt">int</span> <span class="n">GreenButton</span> <span class="o">=</span> <span class="mi">1</span> <span class="o"><<</span> <span class="mi">3</span><span class="p">;</span>
<span class="k">const</span> <span class="kt">int</span> <span class="n">BlueButton</span> <span class="o">=</span> <span class="mi">1</span> <span class="o"><<</span> <span class="mi">4</span><span class="p">;</span>
<span class="kt">int</span> <span class="n">currentButtonColor</span> <span class="o">=</span> <span class="n">WhiteButton</span><span class="p">;</span>
<span class="c1">// LED 매트릭스 설정</span>
<span class="n">MatrixPanel_I2S_DMA</span> <span class="o">*</span><span class="n">dma_display</span> <span class="o">=</span> <span class="n">nullptr</span><span class="p">;</span>
<span class="kt">uint16_t</span> <span class="n">myBLACK</span> <span class="o">=</span> <span class="n">dma_display</span><span class="o">-></span><span class="n">color565</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">);</span>
<span class="kt">uint16_t</span> <span class="n">myWHITE</span> <span class="o">=</span> <span class="n">dma_display</span><span class="o">-></span><span class="n">color565</span><span class="p">(</span><span class="mi">128</span><span class="p">,</span> <span class="mi">128</span><span class="p">,</span> <span class="mi">128</span><span class="p">);</span>
<span class="kt">uint16_t</span> <span class="n">myRED</span> <span class="o">=</span> <span class="n">dma_display</span><span class="o">-></span><span class="n">color565</span><span class="p">(</span><span class="mi">128</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">);</span>
<span class="kt">uint16_t</span> <span class="n">myGREEN</span> <span class="o">=</span> <span class="n">dma_display</span><span class="o">-></span><span class="n">color565</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span> <span class="mi">128</span><span class="p">,</span> <span class="mi">0</span><span class="p">);</span>
<span class="kt">uint16_t</span> <span class="n">myBLUE</span> <span class="o">=</span> <span class="n">dma_display</span><span class="o">-></span><span class="n">color565</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">128</span><span class="p">);</span>
<span class="cp">#define R1_PIN 25
#define G1_PIN 27
#define B1_PIN 26
#define R2_PIN 14
#define G2_PIN 13
#define B2_PIN 12
#define A_PIN 23
#define B_PIN 19
#define C_PIN 5
#define D_PIN 17
#define E_PIN -1
#define LAT_PIN 4
#define OE_PIN 15
#define CLK_PIN 16
</span>
<span class="n">HUB75_I2S_CFG</span><span class="o">::</span><span class="n">i2s_pins</span> <span class="n">_pins</span><span class="o">=</span><span class="p">{</span><span class="n">R1_PIN</span><span class="p">,</span> <span class="n">G1_PIN</span><span class="p">,</span> <span class="n">B1_PIN</span><span class="p">,</span> <span class="n">R2_PIN</span><span class="p">,</span> <span class="n">G2_PIN</span><span class="p">,</span> <span class="n">B2_PIN</span><span class="p">,</span> <span class="n">A_PIN</span><span class="p">,</span> <span class="n">B_PIN</span><span class="p">,</span> <span class="n">C_PIN</span><span class="p">,</span> <span class="n">D_PIN</span><span class="p">,</span> <span class="n">E_PIN</span><span class="p">,</span> <span class="n">LAT_PIN</span><span class="p">,</span> <span class="n">OE_PIN</span><span class="p">,</span> <span class="n">CLK_PIN</span><span class="p">};</span>
<span class="kt">void</span> <span class="nf">setup</span><span class="p">()</span> <span class="p">{</span>
<span class="n">Serial</span><span class="p">.</span><span class="n">begin</span><span class="p">(</span><span class="mi">115200</span><span class="p">);</span>
<span class="n">HUB75_I2S_CFG</span> <span class="n">mxconfig</span><span class="p">(</span><span class="mi">64</span><span class="p">,</span> <span class="mi">32</span><span class="p">,</span> <span class="mi">1</span><span class="p">,</span> <span class="n">_pins</span><span class="p">);</span>
<span class="c1">// Display 설정</span>
<span class="n">dma_display</span> <span class="o">=</span> <span class="n">new</span> <span class="n">MatrixPanel_I2S_DMA</span><span class="p">(</span><span class="n">mxconfig</span><span class="p">);</span>
<span class="n">dma_display</span><span class="o">-></span><span class="n">begin</span><span class="p">();</span>
<span class="n">dma_display</span><span class="o">-></span><span class="n">setBrightness8</span><span class="p">(</span><span class="mi">120</span><span class="p">);</span>
<span class="n">dma_display</span><span class="o">-></span><span class="n">clearScreen</span><span class="p">();</span>
<span class="n">updateButton</span><span class="p">(</span><span class="n">BlackButton</span><span class="p">);</span>
<span class="p">}</span>
<span class="kt">void</span> <span class="nf">loop</span><span class="p">()</span> <span class="p">{</span>
<span class="kt">int</span> <span class="n">buttonValue</span> <span class="o">=</span> <span class="n">analogRead</span><span class="p">(</span><span class="n">BUTTON_PIN</span><span class="p">);</span>
<span class="kt">int</span> <span class="n">buttonColor</span> <span class="o">=</span> <span class="n">getButtonColor</span><span class="p">(</span><span class="n">buttonValue</span><span class="p">);</span>
<span class="n">updateButton</span><span class="p">(</span><span class="n">buttonColor</span><span class="p">);</span>
<span class="n">delay</span><span class="p">(</span><span class="mi">100</span><span class="p">);</span>
<span class="p">}</span>
<span class="c1">// 눌려진 버튼 색깔 가져오기</span>
<span class="kt">int</span> <span class="nf">getButtonColor</span><span class="p">(</span><span class="kt">int</span> <span class="n">value</span><span class="p">)</span> <span class="p">{</span>
<span class="k">if</span> <span class="p">(</span><span class="n">value</span> <span class="o"><</span> <span class="mi">200</span><span class="p">)</span> <span class="p">{</span>
<span class="k">return</span> <span class="n">None</span><span class="p">;</span>
<span class="p">}</span> <span class="k">else</span> <span class="k">if</span> <span class="p">(</span><span class="n">value</span> <span class="o"><</span> <span class="mi">300</span><span class="p">)</span> <span class="p">{</span>
<span class="n">Serial</span><span class="p">.</span><span class="n">println</span><span class="p">(</span><span class="s">"Black Button Clicked"</span><span class="p">);</span>
<span class="k">return</span> <span class="n">BlackButton</span><span class="p">;</span>
<span class="p">}</span> <span class="k">else</span> <span class="k">if</span> <span class="p">(</span><span class="n">value</span> <span class="o"><</span> <span class="mi">700</span><span class="p">)</span> <span class="p">{</span>
<span class="n">Serial</span><span class="p">.</span><span class="n">println</span><span class="p">(</span><span class="s">"White Button Clicked"</span><span class="p">);</span>
<span class="k">return</span> <span class="n">WhiteButton</span><span class="p">;</span>
<span class="p">}</span> <span class="k">else</span> <span class="k">if</span> <span class="p">(</span><span class="n">value</span> <span class="o"><</span> <span class="mi">1100</span><span class="p">)</span> <span class="p">{</span>
<span class="n">Serial</span><span class="p">.</span><span class="n">println</span><span class="p">(</span><span class="s">"Red Button Clicked"</span><span class="p">);</span>
<span class="k">return</span> <span class="n">RedButton</span><span class="p">;</span>
<span class="p">}</span> <span class="k">else</span> <span class="k">if</span> <span class="p">(</span><span class="n">value</span> <span class="o"><</span> <span class="mi">1800</span><span class="p">)</span> <span class="p">{</span>
<span class="n">Serial</span><span class="p">.</span><span class="n">println</span><span class="p">(</span><span class="s">"Green Button Clicked"</span><span class="p">);</span>
<span class="k">return</span> <span class="n">GreenButton</span><span class="p">;</span>
<span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
<span class="n">Serial</span><span class="p">.</span><span class="n">println</span><span class="p">(</span><span class="s">"Blue Button Clicked"</span><span class="p">);</span>
<span class="k">return</span> <span class="n">BlueButton</span><span class="p">;</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="c1">// 버튼 색깔에 따라 화면 업데이트하기</span>
<span class="kt">void</span> <span class="nf">updateButton</span><span class="p">(</span><span class="kt">int</span> <span class="n">buttonColor</span><span class="p">)</span> <span class="p">{</span>
<span class="k">if</span> <span class="p">(</span><span class="n">buttonColor</span> <span class="o">==</span> <span class="mi">0</span><span class="p">)</span> <span class="k">return</span><span class="p">;</span>
<span class="k">if</span> <span class="p">(</span><span class="n">buttonColor</span> <span class="o">==</span> <span class="n">currentButtonColor</span><span class="p">)</span> <span class="k">return</span><span class="p">;</span>
<span class="n">currentButtonColor</span> <span class="o">=</span> <span class="n">buttonColor</span><span class="p">;</span>
<span class="n">updateScreen</span><span class="p">(</span><span class="n">currentButtonColor</span><span class="p">);</span>
<span class="p">}</span>
<span class="c1">// 화면에 텍스트 작성하기</span>
<span class="kt">void</span> <span class="nf">updateScreen</span><span class="p">(</span><span class="kt">int</span> <span class="n">buttonColor</span><span class="p">)</span> <span class="p">{</span>
<span class="n">dma_display</span><span class="o">-></span><span class="n">setCursor</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">);</span>
<span class="n">dma_display</span><span class="o">-></span><span class="n">setTextSize</span><span class="p">(</span><span class="mi">2</span><span class="p">);</span>
<span class="n">dma_display</span><span class="o">-></span><span class="n">setTextWrap</span><span class="p">(</span><span class="nb">true</span><span class="p">);</span>
<span class="n">dma_display</span><span class="o">-></span><span class="n">fillScreen</span><span class="p">(</span><span class="n">myBLACK</span><span class="p">);</span>
<span class="k">if</span> <span class="p">(</span><span class="n">BlackButton</span> <span class="o">&</span> <span class="n">buttonColor</span><span class="p">)</span> <span class="p">{</span>
<span class="n">dma_display</span><span class="o">-></span><span class="n">fillScreen</span><span class="p">(</span><span class="n">myBLACK</span><span class="p">);</span>
<span class="p">}</span>
<span class="k">if</span> <span class="p">(</span><span class="n">WhiteButton</span> <span class="o">&</span> <span class="n">buttonColor</span><span class="p">)</span> <span class="p">{</span>
<span class="n">dma_display</span><span class="o">-></span><span class="n">setTextColor</span><span class="p">(</span><span class="n">myWHITE</span><span class="p">);</span>
<span class="n">dma_display</span><span class="o">-></span><span class="n">println</span><span class="p">(</span><span class="s">"Name"</span><span class="p">);</span>
<span class="p">}</span>
<span class="k">if</span> <span class="p">(</span><span class="n">RedButton</span> <span class="o">&</span> <span class="n">buttonColor</span><span class="p">)</span> <span class="p">{</span>
<span class="n">dma_display</span><span class="o">-></span><span class="n">setTextColor</span><span class="p">(</span><span class="n">myRED</span><span class="p">);</span>
<span class="n">dma_display</span><span class="o">-></span><span class="n">println</span><span class="p">(</span><span class="s">"BUSY"</span><span class="p">);</span>
<span class="p">}</span>
<span class="k">if</span> <span class="p">(</span><span class="n">GreenButton</span> <span class="o">&</span> <span class="n">buttonColor</span><span class="p">)</span> <span class="p">{</span>
<span class="n">dma_display</span><span class="o">-></span><span class="n">setTextColor</span><span class="p">(</span><span class="n">myGREEN</span><span class="p">);</span>
<span class="n">dma_display</span><span class="o">-></span><span class="n">println</span><span class="p">(</span><span class="s">"TEA TIME"</span><span class="p">);</span>
<span class="p">}</span>
<span class="k">if</span> <span class="p">(</span><span class="n">BlueButton</span> <span class="o">&</span> <span class="n">buttonColor</span><span class="p">)</span> <span class="p">{</span>
<span class="n">dma_display</span><span class="o">-></span><span class="n">setTextColor</span><span class="p">(</span><span class="n">myBLUE</span><span class="p">);</span>
<span class="n">dma_display</span><span class="o">-></span><span class="n">println</span><span class="p">(</span><span class="s">"MEETING"</span><span class="p">);</span>
<span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>
<h2 id="기타">기타</h2>
<ul>
<li>그냥 부품 사서 코드만 짜면 될 줄 알았는데, 시행착오가 많아서 완성에 한 달 넘게 걸렸다.
<ul>
<li>5v 4a 어댑터를 연결해도 화면은 안 나오고 발열만 생겨서 고생했다.</li>
<li>우연히 5v 선이 LED 파워 핀에 닿았는데, 화면이 제대로 나와서 겨우 완성함</li>
</ul>
</li>
<li>소량 생산해서 팔아볼까 생각해봤다.
<ul>
<li>근데 <code class="language-plaintext highlighter-rouge">원가 < 가격 < 가치</code>의 부등식을 통과하기 힘들 것 같아서 패스</li>
<li>원가 5만 원에 판매가격 7만 원으로 잡았는데, 과연 이게 구매자에게 7만 원 이상의 가치를 주는가?</li>
<li>기술혁신을 해서 원가를 3만 원 아래로 내리고 5만 원에 판매할 수도 있지만</li>
<li>시장이 너무 작고, 그 시간에 다른 일 하는 게 나을 듯…</li>
</ul>
</li>
<li>GIF 재생도 가능하다.
<ul>
<li>32 x 16 크기의 GIF 중 괜찮은 걸 못 찾았고</li>
<li>GIF 프레임 렌더링 중에 버튼을 눌러서 인터럽트 하는 코드를 못 짜겠어서 패스</li>
</ul>
</li>
</ul>yongjin0802사무실의 시대가 돌아왔다. 회사에 내 자리가 생기면 가장 갖고 싶었던 게 이름표다. 테크 감성 오지는 디지털 명패 만드는 법을 알아보자.사이드 프로젝트로 성장하기 - Jira Task2023-02-19T12:00:00+00:002023-02-19T12:00:00+00:00https://16yongjin.github.io//development/jira-task<p>나만의 문제 해결과 성장, 두 마리 토끼를 잡기 위해 사이드 프로젝트하기</p>
<h2 id="생각의-변화">생각의 변화</h2>
<p>어떤 프로젝트를 해야 하는지에 대한 나만의 명확한 기준이 생겼다.</p>
<h3 id="변화-1-해결책-중심-사고-️-문제-중심-사고">변화 1. 해결책 중심 사고 ➡️ 문제 중심 사고</h3>
<p>한동안 하고 싶은 게 너무 많은데 뭘 해야 할지 몰랐다.</p>
<p>뭘 할지 결정하는데 많은 시간을 써도 좋은 답은 나오지 않았다.</p>
<p>각종 아이디어와 해결책으로 머리가 터지기 직전,</p>
<p>단 하나의 생각이 떠올라서 머리가 맑아졌다</p>
<p>“그래서 뭘 해결하려고 하는 거지..?”</p>
<p>“…(아무 생각 없음)”</p>
<h4 id="해결책-중심-사고">해결책 중심 사고</h4>
<p>신기술, 새로운 언어, 신박한 아이디어로 머리가 가득 차지만, 그걸 가지고 할 수 있는 건 생각보다 많이 없다.</p>
<p>일단 해결책을 만들고 적용할 문제를 찾기는 정말정말 힘들다.</p>
<p>수요 없는 공급은 피해야 한다.</p>
<p><img src="https://user-images.githubusercontent.com/22253556/219933675-363fcdd6-857e-4cd5-8786-9ade6b169efa.png" alt="해결책 vs 문제" /></p>
<h4 id="문제-중심-사고">문제 중심 사고</h4>
<p>문제를 찾으면 해결책을 찾기는 쉽다. 그리고 즐겁다.</p>
<p>다만, 문제를 떠올리기 정말 힘들다는 것이다. 진짜 아무 생각도 떠오르지 않아 막막하다</p>
<p>대신, 머리가 쓸모없는 생각들로 가득 차는 것을 막아준다.</p>
<p>그래서 정말 중요한 것에 생각을 집중할 수 있다.</p>
<p><img src="https://user-images.githubusercontent.com/22253556/219933673-dcf0f4af-2757-4e07-b207-b205cc917025.png" alt="문제 vs 해결책" /></p>
<h3 id="변화-2-남의-문제-해결하기-️-나의-문제-해결하기">변화 2. 남의 문제 해결하기 ➡️ 나의 문제 해결하기</h3>
<p>문제를 찾았는데도, 해결하려는 의지가 생기지 않았다.</p>
<p>해결책을 만들어도 내가 쓰고 싶은 생각이 들지 않았기 때문이다.</p>
<h4 id="남의-문제-해결하기">남의 문제 해결하기</h4>
<p>이걸 해결하면 남들이 좋아하겠지..? 사람들이 많이 쓰겠지..?</p>
<p>이런 생각만 가지고 고된 개발 과정을 버틸 수 없다.</p>
<p><img src="https://user-images.githubusercontent.com/22253556/219933671-5e547923-51c5-47d6-9d5d-783101cf9a52.png" alt="남의 문제" /></p>
<h4 id="나의-문제-해결하기">나의 문제 해결하기</h4>
<p>나의 문제는 그렇게 대단하지 않다.</p>
<p>나의 해결책이 기후 위기나 무역 수지 적자 같은 거대한 문제를 해결하지도 못한다.</p>
<p>하지만, 해결하는 과정이 즐겁고 해결하면 내가 행복해진다.</p>
<p>여기에서 끝까지 해낼 힘이 나온다.</p>
<p><img src="https://user-images.githubusercontent.com/22253556/219933667-7fefe089-4dc6-49af-a68b-13d7d03bc10c.png" alt="나의 문제" /></p>
<h3 id="변화-3-나의-해결책-️-모두의-해결책">변화 3. 나의 해결책 ➡️ 모두의 해결책</h3>
<p>신기한 건, 나와 같은 문제를 겪고 있는 사람이 있다는 것이다.</p>
<p>나의 문제를 해결하는 데 집중했을 뿐인데, 남의 문제도 해결되는 상황이 생기기도 한다.</p>
<p>뇌 부위 중, 나를 생각하는 데 쓰이면서 동시에 남을 생각하는데도 쓰이는 전전두피질 때문일지도 모른다.</p>
<p><img src="https://user-images.githubusercontent.com/22253556/219933672-af2d28fe-51f9-4a45-b3d3-7657fb30e640.png" alt="모두의 문제 해결" /></p>
<h3 id="나의-사이드-프로젝트-가이드라인">나의 사이드 프로젝트 가이드라인</h3>
<p>나와 관련 있는 문제를 즐겁게 해결한다.</p>
<p>(이 이야기를 EO에서 몇 번 들은 거 같은데, 직접 깨닫는데 너무 오래 걸렸다.)</p>
<p> </p>
<h2 id="문제의-발단">문제의 발단</h2>
<p>지라 티켓을 만들기 너무 귀찮았다.</p>
<p><img src="https://user-images.githubusercontent.com/22253556/219936452-5d5f463b-150c-476c-b000-fbcb5b50974a.png" alt="지라 티켓 생성 힘들" /></p>
<p>티켓 하나 만드는데 입력할 게 왜케 많은지..</p>
<p>지라 접속부터 귀찮고</p>
<p>실수로 프로젝트 잘못 선택하면 티켓 옮기느라 열불남</p>
<h2 id="가설">가설</h2>
<p>내가 쓰는 할일 관리 앱은 <code class="language-plaintext highlighter-rouge">Cmd</code> + <code class="language-plaintext highlighter-rouge">Shift</code> + <code class="language-plaintext highlighter-rouge">A</code>를 누르면 바로 할일을 추가할 수 있다.</p>
<p><img src="https://user-images.githubusercontent.com/22253556/219936526-ada53e3a-feab-42e0-82e8-20002b55fa6d.png" alt="ticktick" /></p>
<p>지라 티켓도 이렇게 쉽게 만들 수 있다면..?</p>
<p><img src="https://user-images.githubusercontent.com/22253556/219936450-e27bbd4b-488b-4464-b2bf-92b0d4af7fe8.png" alt="지라 티켓 생성 쉬움" /></p>
<p>엄청나게 유용하지 않을까?</p>
<h2 id="프로토타입">프로토타입</h2>
<p>개발자의 장인정신과 예술가의 혼을 쏟기 전 이게 정말 쓸모 있는지 확인하고 싶었다.</p>
<p>지라 이슈를 생성하는 함수 하나를 구현하고</p>
<p>HTML <code class="language-plaintext highlighter-rouge">input</code>, <code class="language-plaintext highlighter-rouge">select</code> 만 써서 허술 끝판왕 프로토타입을 만들어봤다.</p>
<p><img width="316" alt="대충 구현" src="https://user-images.githubusercontent.com/22253556/219936786-a065700a-f579-4c18-a8fe-261e3ea749dd.png" /></p>
<p>(암튼 완성)</p>
<p><img src="https://user-images.githubusercontent.com/22253556/219936853-3e4305d7-f50a-416c-9a74-42e9622fdeb1.png" alt="image" width="450px" /></p>
<h2 id="실험결과">실험결과</h2>
<p>프로토타입을 일주일 정도 써봤다.</p>
<p>지라 티켓 하나 만들 때마다 1~2분씩 걸렸던 게 5초로 줄어드니까 너무 편했다.</p>
<p>티켓 만들려고 지라에 들어가는 일이 거의 없어질 정도</p>
<p>(현재는 지라 티켓 생성 화면을 한 달째 써본 적이 없다.)</p>
<p>유용함은 확인했으니 제대로 만들어보기로 결심했다.</p>
<h2 id="전략">전략</h2>
<blockquote>
<p>창조와 개선을 분리하자. 크리에이터와 에디터가 동시에 될 수 없다.</p>
</blockquote>
<p><a href="https://selfstudynote.tistory.com/337">케빈 캘리 옹</a>이 하신 말씀이다.</p>
<p>지금까지 사이드 프로젝트를 하면서 코딩하면서 기획, 디자인까지 동시에 하다가 실패한 적이 많았다.</p>
<p>개발 과정도 느리고 중간에 멈춰서 결정을 내리는 모든 순간이 고통스러웠다.</p>
<p>그래서 처음에 고통스러워도, 기획 단계부터 확실히 하기로 다짐했다.</p>
<p>계획을 바탕으로 딴 길로 빠지지 않고 앞으로 직진하고 싶었기 때문이다.</p>
<blockquote>
<p>코딩을 해야 한다는 강박을 버리기</p>
</blockquote>
<p>개발자의 자아가 강해서 항상 사물의 작동원리와 구현방식을 고민한다.</p>
<p>이런 사고방식이 개발자의 역할을 수행할 때는 도움이 된다.</p>
<p>하지만, 기획과 디자인을 할 때는 방해가 되는 것 같다.</p>
<p>사용자를 위해 최선의 UX를 구성해야 하지만, 개발자의 자아가 계속 딴지를 건다.</p>
<p>“이러면 구현하기 복잡할 텐데…”</p>
<p>“굳이 화면을 여러 개를 만들 필요가 있을까? 화면 하나만 만들면 코드도 적을 텐데…”</p>
<p>이런 생각이 자유로운 상상을 막는 것 같아 잠시 코딩을 해야 한다는 생각을 내려놓았다.</p>
<p>반의반 평생 끊임없이 해온 코드 생각을 관둬야 한다니.. 정말 어색했다.</p>
<h2 id="전술">전술</h2>
<p>회사에서 배운 기획 - 디자인 - 마크업 - 개발 프로세스를 적용해봤다.</p>
<h2 id="기획">기획</h2>
<h3 id="토스의-ux-원칙-하나">토스의 UX 원칙 하나</h3>
<blockquote>
<h3 id="one-thing-per-one-page">One Thing per one Page</h3>
<p>하나의 화면은 하나의 메시지만 표현한다. 한 화면에 너무 많은 정보를 전달하지 않도록 지우고, 제외하고, 제거한다.</p>
</blockquote>
<p>앱 사용을 위해 2가지 입력을 받아야 한다. (① 지라 토큰 ② 사용할 프로젝트 키 목록)</p>
<p>이렇게 한 페이지에 모든 입력을 받을 수 있다.</p>
<p><img width="500" alt="CleanShot 2023-02-19 at 17 34 21@2x" src="https://user-images.githubusercontent.com/22253556/219937551-86756101-bbc5-4dc0-bae4-d376e81a54ea.png" /></p>
<p>이러면 구현은 쉽다.</p>
<p>하지만, 개발자가 아니고선 이런 수수께끼 같은 입력을 완료하긴 힘들 것이다.</p>
<p>여러가지 내용을 텍스트로 입력하는 것은 잘못된 입력을 하지 않을까 하는 불안감도 준다.</p>
<h3 id="한-번에-하나의-입력을-받는-flow">한 번에 하나의 입력을 받는 Flow</h3>
<p>토스의 UX 원칙에 따라 사용자와 한마디씩 대화를 주고받는 듯한 설정 경험을 디자인했다.</p>
<p><img src="https://user-images.githubusercontent.com/22253556/219937324-4b5a1be5-de33-4682-b9bc-ee232325bb84.JPG" alt="IMG_1609" /></p>
<p>여기엔 아무런 입력을 받지 않고, 그저 잘하고 있다고 말하는 화면도 있다.</p>
<p>개발자의 마인드라면 절대 넣지 않았을 화면이다. (코드 낭비 아닌가..?)</p>
<p>하지만, 사용자에게 편안한 화면을 상상하니 자연스럽게 추가됐다.</p>
<h2 id="디자인">디자인</h2>
<p>종이와 펜으로 그린 Flow를 바탕으로 디자인을 완성했다.</p>
<p><img src="https://user-images.githubusercontent.com/22253556/219938685-b36efa5b-6085-4325-be5d-b8a6af4e8942.png" alt="화면 모아보기" /></p>
<p>피그마도 FE 프레임워크처럼 컴포넌트 기능이 있는데, 중복 요소를 줄여줘서 좋다.</p>
<h2 id="마크업">마크업</h2>
<h3 id="컴포넌트-구조-그리기">컴포넌트 구조 그리기</h3>
<p>디자인을 바탕으로 어떤 컴포넌트를 구현해야 하는지 쭉 적었다.</p>
<p><img src="https://user-images.githubusercontent.com/22253556/219937372-ff716f20-a2c4-497c-937b-3187708c6536.JPG" alt="IMG_1610" /></p>
<h3 id="드라이-컴포넌트-만들기">드라이 컴포넌트 만들기</h3>
<p>컴포넌트 파일(svelte)을 생성하고 아래처럼 가짜로 구현을 했다.</p>
<div class="language-html highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt"><div></span>
<span class="nt"><slot/></span>
<span class="nt"></div></span>
</code></pre></div></div>
<h3 id="컴포넌트-구조-잡기">컴포넌트 구조 잡기</h3>
<p>페이지 파일을 생성하고 컴포넌트를 불러와서 구조를 잡았다. (구현 X)</p>
<div class="language-html highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt"><Box></span>
<span class="nt"><Title></span>설정을 시작합니다.<span class="nt"></Title></span>
<span class="nt"><MainButton></span>시작하기<span class="nt"></MainButton></span>
<span class="nt"></Box></span>
</code></pre></div></div>
<h3 id="컴포넌트-하나씩-구현하기">컴포넌트 하나씩 구현하기</h3>
<p>Tailwind를 사용해서 컴포넌트와 페이지를 하나씩 완성했다.</p>
<p><img src="https://user-images.githubusercontent.com/22253556/219939003-ac90edf3-03c7-49d5-a7dc-3b2a7b4f8ecf.png" alt="지라 cli 마크업 작업 중" /></p>
<h2 id="개발">개발</h2>
<p>컴포넌트 구현 -> 각 화면 구현 -> 화면 연결</p>
<p>빠르게 테스트가 가능한 컴포넌트부터, 테스트에 시간이 오래걸리는 화면 이동 작업 순으로 작업했다.</p>
<h3 id="컴포넌트-구현">컴포넌트 구현</h3>
<p>텍스트 필드, 메뉴, 셀렉트를 스타일부터 기능까지 직접 구현했다.</p>
<p>컴포넌트 라이브러리를 사용해서 시간을 절약할 수 있었지만,</p>
<p>문제에 딱 알맞은 디자인을 해놨는데 라이브러리가 제공하는 디자인에 끼워 맞춰야 하는게 맘에 안 들었다.</p>
<h3 id="각-화면별-구현">각 화면별 구현</h3>
<h3 id="api-구현-result-타입과-목업으로-안전하게-코딩하기">API 구현: Result 타입과 목업으로 안전하게 코딩하기</h3>
<p>사용자가 입력한 값을 검증하고 처리하기 위해 지라 API와 통신을 해야 한다.</p>
<p>그런데 API 호출은 언제든 실패할 수 있고</p>
<p>페이지가 많은 앱 특성상, 오류가 발생했을 때 적절한 경로를 제공하지 않으면 사용자는 화면에 갇히게 된다.</p>
<p>(얼마나 짜증날까)</p>
<p>모든 에러 상황에 대처하기 위해 Result 타입을 사용해서 API를 모델링했다.</p>
<h4 id="result-타입-정의">Result 타입 정의</h4>
<p><a href="https://imhoff.blog/posts/using-results-in-typescript">Using Results in TypeScript</a>를 참고했다.</p>
<p>계산의 성공이나 실패를 모델링한다.</p>
<div class="language-ts highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">type</span> <span class="nx">Result</span><span class="o"><</span><span class="nx">T</span><span class="p">,</span> <span class="nx">E</span> <span class="o">=</span> <span class="kr">any</span><span class="o">></span> <span class="o">=</span>
<span class="o">|</span> <span class="p">{</span> <span class="na">ok</span><span class="p">:</span> <span class="kc">true</span><span class="p">;</span> <span class="nl">value</span><span class="p">:</span> <span class="nx">T</span> <span class="p">}</span>
<span class="o">|</span> <span class="p">{</span> <span class="na">ok</span><span class="p">:</span> <span class="kc">false</span><span class="p">;</span> <span class="nl">error</span><span class="p">:</span> <span class="nx">E</span> <span class="p">}</span>
</code></pre></div></div>
<h4 id="api-정의">API 정의</h4>
<p>API 타입을 적을 때 반환 타입과 에러 타입을 모두 적는다.</p>
<div class="language-ts highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">const</span> <span class="nx">createIssue</span> <span class="o">=</span> <span class="k">async</span> <span class="p">(</span><span class="nx">data</span><span class="p">):</span> <span class="nb">Promise</span><span class="o"><</span><span class="nx">Result</span><span class="o"><</span><span class="nx">CreatedIssue</span><span class="p">,</span> <span class="nx">JiraError</span><span class="o">>></span> <span class="o">=></span> <span class="p">{}</span>
</code></pre></div></div>
<h4 id="api-사용">API 사용</h4>
<p>반환된 <code class="language-plaintext highlighter-rouge">result</code>의 성공/실패에 따라 분기 처리한다.</p>
<div class="language-ts highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">const</span> <span class="nx">result</span> <span class="o">=</span> <span class="k">await</span> <span class="nx">createIssue</span><span class="p">({...})</span>
<span class="k">if</span> <span class="p">(</span><span class="nx">result</span><span class="p">.</span><span class="nx">ok</span> <span class="o">===</span> <span class="kc">true</span><span class="p">)</span> <span class="p">{</span>
<span class="nx">data</span> <span class="o">=</span> <span class="nx">result</span><span class="p">.</span><span class="nx">value</span>
<span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
<span class="nx">errorMessage</span> <span class="o">=</span> <span class="nx">result</span><span class="p">.</span><span class="nx">error</span>
<span class="p">}</span>
</code></pre></div></div>
<h4 id="result-타입의-장점">Result 타입의 장점</h4>
<p><code class="language-plaintext highlighter-rouge">try-catch</code>를 사용하지 않아도 에러를 처리할 수 있고 에러 타입까지 바로 알 수 있다.</p>
<p>사용자에게 실패 가능성을 알려서 항상 에러를 처리하도록 강제하는 것은 덤이다.</p>
<h4 id="목업하기">목업하기</h4>
<p>API가 실패할 수 있다는 것을 아는 것만으론 충분하지 않다.</p>
<p>실제로 API를 실패시켜서 어떻게 앱이 작동하는지 확인해야 한다.</p>
<p>이를 쉽게 하기 위해 간단한 목업함수를 사용했다.</p>
<h4 id="목업함수-구현">목업함수 구현</h4>
<p>(타입은 많이 생략했다)</p>
<p>API 함수를 받아서 같은 인터페이스를 가진 함수를 반환한다.</p>
<p><code class="language-plaintext highlighter-rouge">test</code> 옵션에 따라 목업을 하거나, 실제 함수를 호출한다.</p>
<p><code class="language-plaintext highlighter-rouge">fail</code> 옵션에 따라 성공/실패값을 반환한다.</p>
<p><code class="language-plaintext highlighter-rouge">delay</code>로 얼마나 로딩할지 결정한다.</p>
<div class="language-ts highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">const</span> <span class="nx">mockup</span> <span class="o">=</span> <span class="p">(</span>
<span class="nx">fn</span><span class="p">:</span> <span class="nx">T</span><span class="p">,</span>
<span class="p">{</span>
<span class="nx">test</span> <span class="o">=</span> <span class="kc">false</span><span class="p">,</span>
<span class="nx">fail</span> <span class="o">=</span> <span class="kc">false</span><span class="p">,</span>
<span class="nx">delay</span> <span class="o">=</span> <span class="mi">0</span><span class="p">,</span>
<span class="nx">onSuccess</span><span class="p">,</span>
<span class="nx">onFail</span><span class="p">,</span>
<span class="p">}</span>
<span class="p">)</span> <span class="o">=></span> <span class="p">{</span>
<span class="k">return</span> <span class="k">async</span> <span class="p">(...</span><span class="na">args</span><span class="p">:</span> <span class="nx">Parameters</span><span class="o"><</span><span class="nx">T</span><span class="o">></span><span class="p">):</span> <span class="nb">Promise</span><span class="o"><</span><span class="nx">Result</span><span class="o"><</span><span class="nx">R</span><span class="p">,</span> <span class="nx">E</span><span class="o">>></span> <span class="o">=></span> <span class="p">{</span>
<span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="nx">test</span><span class="p">)</span> <span class="k">return</span> <span class="nx">fn</span><span class="p">(...</span><span class="nx">args</span><span class="p">)</span>
<span class="k">if</span> <span class="p">(</span><span class="nx">delay</span><span class="p">)</span> <span class="k">await</span> <span class="nx">wait</span><span class="p">(</span><span class="nx">delay</span><span class="p">)</span>
<span class="k">if</span> <span class="p">(</span><span class="nx">fail</span><span class="p">)</span> <span class="k">return</span> <span class="nx">Err</span><span class="p">(</span><span class="nx">onFail</span><span class="p">(...</span><span class="nx">args</span><span class="p">))</span>
<span class="k">return</span> <span class="nx">Ok</span><span class="p">(</span><span class="nx">onSuccess</span><span class="p">(...</span><span class="nx">args</span><span class="p">))</span>
<span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>
<h4 id="목업함수-사용">목업함수 사용</h4>
<ol>
<li>
<p>성공/실패값을 적는다. <code class="language-plaintext highlighter-rouge">Result</code> 타입 덕분에 반자동으로 값을 채울 수 있다.</p>
</li>
<li>
<p><code class="language-plaintext highlighter-rouge">test</code> 옵션을 켜서 목업한다.</p>
</li>
<li>
<p>API를 실패시키고 싶으면 <code class="language-plaintext highlighter-rouge">fail</code> 옵션을 켠다.</p>
</li>
</ol>
<div class="language-ts highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">const</span> <span class="nx">createIssue</span> <span class="o">=</span> <span class="nx">mockup</span><span class="p">(</span><span class="nx">createIssue</span><span class="p">,</span> <span class="p">{</span>
<span class="na">test</span><span class="p">:</span> <span class="kc">true</span><span class="p">,</span>
<span class="na">fail</span><span class="p">:</span> <span class="kc">false</span><span class="p">,</span>
<span class="na">delay</span><span class="p">:</span> <span class="mi">1000</span><span class="p">,</span>
<span class="nx">onSuccess</span><span class="p">()</span> <span class="p">{</span>
<span class="k">return</span> <span class="p">{</span>
<span class="na">issueKey</span><span class="p">:</span> <span class="dl">'</span><span class="s1">BIZFE-9999</span><span class="dl">'</span><span class="p">,</span>
<span class="p">}</span>
<span class="p">},</span>
<span class="nx">onFail</span><span class="p">()</span> <span class="p">{</span>
<span class="k">return</span> <span class="p">{</span>
<span class="na">errorMessages</span><span class="p">:</span> <span class="p">[],</span>
<span class="na">errors</span><span class="p">:</span> <span class="p">{</span>
<span class="na">projectKey</span><span class="p">:</span> <span class="dl">'</span><span class="s1">프로젝트키가 존재하지 않습니다.</span><span class="dl">'</span><span class="p">,</span>
<span class="p">},</span>
<span class="p">}</span>
<span class="p">},</span>
<span class="p">})</span>
</code></pre></div></div>
<p>API를 목업하면 성공/실패 시 앱이 어떻게 작동하는지 바로바로 테스트할 수 있다.</p>
<p>에러 시 동선이 막히는 부분이 있으면 API 재시도 버튼을 추가하는 식으로 대응할 수 있다.</p>
<p>서버 연결 없이도 앱을 테스트할 수 있다.</p>
<h3 id="화면-연결-상태-모델링으로-스토어와-라우터를-동시에-표현하기">화면 연결: 상태 모델링으로 스토어와 라우터를 동시에 표현하기</h3>
<h4 id="일반적인-상태-정의">일반적인 상태 정의</h4>
<p>보통은 객체의 모든 값을 <code class="language-plaintext highlighter-rouge">nullable</code>하게 정의하고 모든 컴포넌트끼리 상태를 공유한다.</p>
<div class="language-ts highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">type</span> <span class="nx">State</span> <span class="o">=</span> <span class="p">{</span>
<span class="na">accessToken</span><span class="p">:</span> <span class="kr">string</span> <span class="o">|</span> <span class="kc">null</span><span class="p">;</span>
<span class="nl">username</span><span class="p">:</span> <span class="kr">string</span> <span class="o">|</span> <span class="kc">null</span><span class="p">;</span>
<span class="nl">projectKeys</span><span class="p">:</span> <span class="kr">string</span><span class="p">[]</span> <span class="o">|</span> <span class="kc">null</span><span class="p">;</span>
<span class="nl">projectKey</span><span class="p">:</span> <span class="kr">string</span> <span class="o">|</span> <span class="kc">null</span><span class="p">;</span>
<span class="p">};</span>
</code></pre></div></div>
<p>근데 이러면 상태가 어떻게 바뀌는지 추적하기 진짜 힘들다.</p>
<p>상태를 읽을 때마다 값이 있을지 없을지 불안에 떨면서 코딩해야 한다.</p>
<h4 id="유효한-상태만-정의하기">유효한 상태만 정의하기</h4>
<p>상태를 단계별로 나누고 필요한 속성만 정의한다.</p>
<p>예시로 <code class="language-plaintext highlighter-rouge">로그인 필요</code>, <code class="language-plaintext highlighter-rouge">로그인 완료</code>, <code class="language-plaintext highlighter-rouge">프로젝트 선택</code>으로 상태를 나눴다.</p>
<p>그리고 Sum 타입으로 상태를 하나로 합쳤다.</p>
<div class="language-ts highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">type</span> <span class="nx">LoginRequired</span> <span class="o">=</span> <span class="p">{</span>
<span class="na">type</span><span class="p">:</span> <span class="dl">'</span><span class="s1">LoginRequired</span><span class="dl">'</span><span class="p">;</span>
<span class="p">};</span>
<span class="kd">type</span> <span class="nx">LoggedIn</span> <span class="o">=</span> <span class="p">{</span>
<span class="na">type</span><span class="p">:</span> <span class="dl">'</span><span class="s1">LoggedIn</span><span class="dl">'</span><span class="p">;</span>
<span class="nl">data</span><span class="p">:</span> <span class="p">{</span>
<span class="na">accessToken</span><span class="p">:</span> <span class="kr">string</span><span class="p">;</span>
<span class="nl">username</span><span class="p">:</span> <span class="kr">string</span><span class="p">;</span>
<span class="p">};</span>
<span class="p">};</span>
<span class="kd">type</span> <span class="nx">ProjectSelect</span> <span class="o">=</span> <span class="p">{</span>
<span class="na">type</span><span class="p">:</span> <span class="dl">'</span><span class="s1">ProjectSelect</span><span class="dl">'</span><span class="p">;</span>
<span class="nl">data</span><span class="p">:</span> <span class="p">{</span>
<span class="na">accessToken</span><span class="p">:</span> <span class="kr">string</span><span class="p">;</span>
<span class="nl">username</span><span class="p">:</span> <span class="kr">string</span><span class="p">;</span>
<span class="nl">projects</span><span class="p">:</span> <span class="kr">string</span><span class="p">[];</span>
<span class="p">};</span>
<span class="p">};</span>
<span class="kd">type</span> <span class="nx">State</span> <span class="o">=</span> <span class="nx">LoginRequired</span> <span class="o">|</span> <span class="nx">LoggedIn</span> <span class="o">|</span> <span class="nx">ProjectSelect</span><span class="p">;</span>
</code></pre></div></div>
<p>이제 상태는 반드시 이 타입 중에서 하나의 타입만 갖게 된다.</p>
<p>값을 읽을 때마다, 현재 상태가 어떤 값이 있는지 분명히 알 수 있다.</p>
<h4 id="페이지-라우팅">페이지 라우팅</h4>
<p>페이지별로 상태를 나누니까 상태의 <code class="language-plaintext highlighter-rouge">type</code> 속성을 라우팅할 때 사용할 수 있다.</p>
<div class="language-jsx highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p"><</span><span class="nt">script</span> <span class="na">lang</span><span class="p">=</span><span class="s">"ts"</span><span class="p">></span>
let state = <span class="si">{</span> <span class="nx">type</span><span class="p">:</span> <span class="dl">'</span><span class="s1">LoginRequired</span><span class="dl">'</span> <span class="si">}</span>;
const onAction = (<span class="si">{</span> <span class="nx">detail</span><span class="p">:</span> <span class="nx">newState</span> <span class="si">}</span>) => <span class="si">{</span>
<span class="nx">state</span> <span class="o">=</span> <span class="nx">newState</span>
<span class="si">}</span>
<span class="p"></</span><span class="nt">script</span><span class="p">></span>
<span class="p">{</span><span class="err">#</span><span class="k">if</span> <span class="nx">state</span><span class="p">.</span><span class="nx">type</span> <span class="o">===</span> <span class="dl">'</span><span class="s1">LoginRequired</span><span class="dl">'</span><span class="p">}</span>
<span class="p"><</span><span class="nc">LoginRequired</span> <span class="na">on</span><span class="err">:</span><span class="na">action</span><span class="p">=</span><span class="si">{</span><span class="nx">onAction</span><span class="si">}</span> <span class="p">/></span>
<span class="p">{:</span><span class="k">else</span> <span class="k">if</span> <span class="nx">state</span><span class="p">.</span><span class="nx">type</span> <span class="o">===</span> <span class="dl">'</span><span class="s1">LoggedIn</span><span class="dl">'</span><span class="p">}</span>
<span class="p"><</span><span class="nc">LoggedIn</span> <span class="na">on</span><span class="err">:</span><span class="na">action</span><span class="p">=</span><span class="si">{</span><span class="nx">onAction</span><span class="si">}</span> <span class="p">/></span>
<span class="p">{:</span><span class="k">else</span> <span class="k">if</span> <span class="nx">state</span><span class="p">.</span><span class="nx">type</span> <span class="o">===</span> <span class="dl">'</span><span class="s1">ProjectSelect</span><span class="dl">'</span><span class="p">}</span>
<span class="p"><</span><span class="nc">ProjectSelect</span> <span class="si">{</span><span class="p">...</span><span class="nx">state</span><span class="p">.</span><span class="nx">data</span><span class="si">}</span> <span class="na">on</span><span class="err">:</span><span class="na">action</span><span class="p">=</span><span class="si">{</span><span class="nx">onAction</span><span class="si">}</span> <span class="p">/></span>
<span class="p">{</span><span class="o">/</span><span class="k">if</span><span class="p">}</span>
</code></pre></div></div>
<h4 id="화면-이동">화면 이동</h4>
<p>상태 데이터를 그대로 만들어서 <code class="language-plaintext highlighter-rouge">action</code> 이벤트로 쏴주면 된다.</p>
<p>그럼 루트 컴포넌트에서 상태를 새로 덮어씌운다.</p>
<p>상태가 바뀌면 그려지는 컴포넌트도 달라지면서 화면이 바뀐다.</p>
<div class="language-vue highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt"><</span><span class="k">script</span> <span class="na">lang=</span><span class="s">"ts"</span><span class="nt">></span>
<span class="kd">const</span> <span class="nx">dispatch</span> <span class="o">=</span> <span class="nx">createEventDispatcher</span><span class="o"><</span><span class="p">{</span> <span class="na">action</span><span class="p">:</span> <span class="nx">LoggedIn</span> <span class="p">}</span><span class="o">></span><span class="p">()</span>
<span class="kd">const</span> <span class="nx">onClick</span> <span class="o">=</span> <span class="p">()</span> <span class="o">=></span> <span class="p">{</span>
<span class="kd">const</span> <span class="p">{</span><span class="nx">username</span><span class="p">,</span> <span class="nx">accessToken</span><span class="p">}</span> <span class="o">=</span> <span class="k">await</span> <span class="nx">login</span><span class="p">()</span>
<span class="kd">const</span> <span class="na">action</span><span class="p">:</span> <span class="nx">LoggedIn</span> <span class="o">=</span> <span class="p">{</span>
<span class="na">type</span><span class="p">:</span> <span class="dl">'</span><span class="s1">LoggedIn</span><span class="dl">'</span><span class="p">,</span>
<span class="na">data</span><span class="p">:</span> <span class="p">{</span> <span class="nx">username</span><span class="p">,</span> <span class="nx">accessToken</span> <span class="p">}</span>
<span class="p">}</span>
<span class="nx">dispatch</span><span class="p">(</span><span class="dl">'</span><span class="s1">action</span><span class="dl">'</span><span class="p">,</span> <span class="nx">action</span><span class="p">)</span>
<span class="p">}</span>
<span class="nt"></</span><span class="k">script</span><span class="nt">></span>
<span class="nt"><Button</span> <span class="na">on:click=</span><span class="s">{onClick}</span><span class="nt">></span>로그인<span class="nt"></Button></span>
</code></pre></div></div>
<h4 id="상태-저장">상태 저장</h4>
<p>항상 모든 상태가 온전하다.</p>
<p>상태를 그대로 저장했다가 불러와서 사용해도 괜찮다.</p>
<p>코드 두 줄만 추가하면 앱을 Resumable 하게 만들 수 있다.</p>
<div class="language-ts highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">let</span> <span class="nx">state</span> <span class="o">=</span> <span class="k">await</span> <span class="nx">storage</span><span class="p">.</span><span class="nx">load</span><span class="p">()</span> <span class="o">||</span> <span class="nx">initialState</span>
<span class="kd">const</span> <span class="nx">onAction</span> <span class="o">=</span> <span class="k">async</span> <span class="p">({</span> <span class="na">detail</span><span class="p">:</span> <span class="nx">newState</span> <span class="p">})</span> <span class="o">=></span> <span class="p">{</span>
<span class="k">await</span> <span class="nx">storage</span><span class="p">.</span><span class="nx">save</span><span class="p">(</span><span class="nx">state</span><span class="p">)</span>
<span class="nx">state</span> <span class="o">=</span> <span class="nx">newState</span>
<span class="p">}</span>
</code></pre></div></div>
<p>설정 중에 갑자기 앱을 꺼도, 기존 단계를 그대로 진행할 수 있다.</p>
<h2 id="마무리">마무리</h2>
<h3 id="앱-스크린샷">앱 스크린샷</h3>
<p>* 브라우저에서 CMD + J를 누르면 지라 티켓을 만들 수 있다.</p>
<p>* 서버 API를 모두 목업한 상태다.</p>
<video width="100%" controls="controls">
<source src="https://user-images.githubusercontent.com/22253556/219941705-a58e2db5-0dbd-4ca4-a878-9763fa6d7b86.mp4" type="video/mp4" />
</video>
<h3 id="3줄-정리">3줄 정리</h3>
<ol>
<li>내 문제를 해결하는 사이드 프로젝트를 진행하는 건 즐겁고, 남에게 도움이 되기도 한다.</li>
<li>제품 구현을 단계별로 진행하면 효율적이다. 대신 기획을 철저히 해야 한다.</li>
<li>나만의 프로젝트를 하면 미친 척하고 새로운 시도를 해볼 수 있다. Comfort Zone을 벗어나면서 성장한다.</li>
</ol>yongjin0802나만의 문제 해결과 성장, 두 마리 토끼를 잡기 위해 사이드 프로젝트하기LG 듀얼업 모니터 사용기 (LG DualUp 28MQ780)2023-01-01T08:00:00+00:002023-01-01T08:00:00+00:00https://16yongjin.github.io//etc/lg-dualup<p>16:18 비율의 LG 듀얼업 모니터. 27인치 4K 피벗 모니터와 비교해서 얼마나 생산성이 올라갈까?</p>
<h2 id="예전-셋업">예전 셋업</h2>
<ul>
<li>메인모니터: 34인치 울트라와이드 커브드 모니터 (LG 34WL85C)</li>
<li>피벗모니터: 27인치 4K 모니터 (LG 27UL650)</li>
</ul>
<p><img src="https://user-images.githubusercontent.com/22253556/210162749-e2e70f92-fc9f-4015-8acf-eed8a4fc4f29.jpg" alt="옛날 셋업" /></p>
<h2 id="현재-셋업">현재 셋업</h2>
<ul>
<li>메인모니터: 34인치 모니터</li>
<li>보조모니터: 28인치 듀얼업 모니터</li>
</ul>
<p><img src="https://user-images.githubusercontent.com/22253556/210162536-0a7db578-b61e-4827-a1f3-fa09440f84b4.jpg" alt="전체 셋업 샷" /></p>
<h2 id="왜-바꿨을까">왜 바꿨을까</h2>
<h3 id="27인치-169-피벗-모니터의-불편한-점">27인치 16:9 피벗 모니터의 불편한 점</h3>
<ul>
<li>좌우가 애매하게 좁아서 코드를 좌우 나눠서 보기 불편함</li>
<li>위아래로 너무 길어서 메인 모니터에서 문서를 작성하다가 피벗 모니터를 보려면 고개를 많이 들어야 함</li>
</ul>
<h3 id="lg-듀얼업-모니터를-쓰고-느낀점">LG 듀얼업 모니터를 쓰고 느낀점</h3>
<ul>
<li>모니터가 적절하게 커서 코드를 볼 때 걸리적거리는 게 없음</li>
<li>메인 모니터 옆에 자료를 띄워두기 크기가 적당함</li>
</ul>
<p> </p>
<h2 id="듀얼업-vs-27인치-모니터-비교">듀얼업 vs 27인치 모니터 비교</h2>
<h3 id="바탕화면-비교">바탕화면 비교</h3>
<ul>
<li>듀얼업 모니터가 실면적이 더 넓다.</li>
<li>가로가 15cm 정도 넓어졌다.</li>
</ul>
<p><img src="https://user-images.githubusercontent.com/22253556/210162535-5d74cf18-fa48-4e83-9459-c92588fd0f51.jpg" alt="바탕화면 비교" /></p>
<h3 id="유튜브-비교">유튜브 비교</h3>
<p>영상 개수가 27인치는 가로 3개, 세로 5개 / 듀얼업은 가로세로 각 4개</p>
<p><img src="https://user-images.githubusercontent.com/22253556/210162518-6af0b76b-b8e4-4ba2-92b4-2be4d217b068.jpg" alt="유튜브 비교" /></p>
<h3 id="유튜브-전체화면-비교">유튜브 전체화면 비교</h3>
<ul>
<li>메인 모니터에서 작업하면서 간단한 영상 띄워두기 훨씬 좋아졌다.</li>
<li>(영상출처: <a href="https://www.youtube.com/watch?v=lNIDz8kIcC4">AERO.75 by Gray Studio</a>)</li>
</ul>
<p><img src="https://user-images.githubusercontent.com/22253556/210162534-441a1a78-b16f-4ce4-af32-8bb155f6854f.jpg" alt="유튜브 전체화면" /></p>
<h3 id="코드-파일-하나-비교">코드 파일 하나 비교</h3>
<ul>
<li>파일 하나만 보기엔 27인치가 더 아담해서 편하다.</li>
<li>듀얼업은 고개를 왼쪽으로 더 돌려야 코드가 보이는 단점이 있다. 파일탐색기를 왼쪽에 둬서 해결했다.</li>
<li>(소스코드 출처는 좌 React, 우 Vue)</li>
</ul>
<p><img src="https://user-images.githubusercontent.com/22253556/210162519-a4807a35-e0c9-41ba-8380-80d6149095f2.jpg" alt="코드 파일 하나 비교" /></p>
<h3 id="코드-좌우분할-비교">코드 좌우분할 비교</h3>
<ul>
<li>사실 이것 때문에 80만 원 넘게 태웠다.</li>
<li>27인치는 좌우분할하면 코드가 상당량 잘리게 된다.
<ul>
<li>그로 인해 디버깅하다가 몰입이 자주 끊긴다.</li>
<li>파일창을 끄면 잘 보이지만 코드 볼 때마다 그러기 귀찮다.</li>
</ul>
</li>
<li>듀얼업은 핵심적인 코드는 다 보인다.
<ul>
<li>파일창 + 코드 좌우분할 + 터미널까지 다 켜도 코드를 보는 데 방해되는 요소가 없다.</li>
</ul>
</li>
</ul>
<p><img src="https://user-images.githubusercontent.com/22253556/210162521-e57ac8b8-5bbf-4812-a927-501be7132972.jpg" alt="코드 좌우분할 비교" /></p>
<h3 id="깃헙-pr-리뷰-비교">깃헙 PR 리뷰 비교</h3>
<ul>
<li>듀얼업엔 파일탐색창이 하나 더 보인다.</li>
</ul>
<p><img src="https://user-images.githubusercontent.com/22253556/210162524-1b6bfc0c-06f5-466e-89fd-d877df397452.jpg" alt="깃헙 PR 리뷰 비교" /></p>
<h3 id="pdf-비교">PDF 비교</h3>
<ul>
<li>보이는 영역은 비슷하다.</li>
</ul>
<p><img src="https://user-images.githubusercontent.com/22253556/210162525-4071c43f-2fbf-483e-a47d-bf23529de505.jpg" alt="PDF 비교" /></p>
<h3 id="개발자-도구-비교">개발자 도구 비교</h3>
<ul>
<li>구글 검색화면에서 개발자도구를 띄웠다.</li>
<li>듀얼업이 HTML을 보기 더 편하다.</li>
</ul>
<p><img src="https://user-images.githubusercontent.com/22253556/210162527-66c77653-14b7-4cb6-94c7-69a8206a31ee.jpg" alt="개발자도구 비교" /></p>
<h3 id="피그마-비교">피그마 비교</h3>
<ul>
<li>27인치 피벗을 보조 모니터로 사용하기 애매한 부분이 이것이다.</li>
<li>상하로 긴 모니터가 코드 작성하는 것 외에 다른 작업을 하기 좋은 환경은 아닌 것 같다.</li>
<li>듀얼업은 피벗 모니터만의 장점인 가독성 + 적당한 생산성을 가지고 있다.</li>
</ul>
<p><img src="https://user-images.githubusercontent.com/22253556/210162529-9dc0cb14-c25f-42d8-976c-921fa9522640.jpg" alt="피그마 비교" /></p>
<h3 id="다음지도-비교">다음지도 비교</h3>
<ul>
<li>27인치에서는 좌우가 좁아서 지도 상단바가 충돌하기도 한다. (엇;)</li>
</ul>
<p><img src="https://user-images.githubusercontent.com/22253556/210162530-e5a61eba-04d0-4230-9f48-f267e941f986.jpg" alt="IMG_1433" /></p>
<p> </p>
<h2 id="저렴하게-사는-방법">저렴하게 사는 방법?</h2>
<h3 id="1-안-산다">1. 안 산다</h3>
<ul>
<li>듀얼업은 27인치 4k 모니터 두 대 가격이다.</li>
<li>피벗 모니터가 이미 있다면 굳이 바꿀 필요가 있을까?</li>
</ul>
<h3 id="2-할인할-때-산다">2. 할인할 때 산다</h3>
<ul>
<li>국내/해외 유튜브 댓글을 보면 다들 비싸다는 평이 많다.</li>
<li>다행히 한 달에 한 번씩 세일을 한다.</li>
<li>네이버에 <strong>LG 듀얼업</strong>을 <strong>최신순</strong>으로 검색해서 세일 공고가 올라오면 사는 것을 추천</li>
</ul>
<h2 id="기존-4k-모니터와-화질-차이">기존 4K 모니터와 화질 차이?</h2>
<p>27인치 4K 모니터 쓰다가 21인치 QHD를 2개 붙인 모니터를 쓰면 화질이 저하되지 않을까 고민이 됐는데</p>
<p>해상도가 낮아지는 만큼 화면크기도 작아지는 셈이라 별 차이가 없는 것 같다.</p>
<p>실제로 픽셀 크기가 27인치 4K: 0.1554mm², 듀얼업 모니터: 0.18195mm²로 30% 정도 차이가 나긴 하는데</p>
<p>HiDPI 옵션 켜니까 불편한 점은 못 느꼈다.</p>
<h2 id="better-display">Better Display</h2>
<ul>
<li><a href="https://github.com/waydabber/BetterDisplay">Better Display</a>라는 HiDPI 설정 툴이 있다.</li>
<li>이 유틸을 사용해서 메인과 보조 모니터 간 텍스트 확대 비율을 일치할 수 있다.</li>
<li>메인 모니터에서 보고 있던 브라우저 화면을 보조 모니터로 옮기면 글자가 커져서, 화면 확대 비율을 조절하는 게 귀찮았는데</li>
<li>이 유틸을 사용하니, 모든 모니터에서 모든 화면의 크기가 같으니까 확대비 조절 지옥에서 벗어났다. 왜 지금까지 불편하게 살았던 걸까.</li>
<li>맥에선 듀얼업 모니터의 HiDPI를 1x와 2x 비율만 지원해서 이 유틸이 필수다.</li>
<li>윈도에선 기본 지원하는 기능인데 맥이라 15달러 내고 유틸을 따로 써야 하는 건 좀 마음 아프다.</li>
</ul>yongjin080216:18 비율의 LG 듀얼업 모니터. 27인치 4K 피벗 모니터와 비교해서 얼마나 생산성이 올라갈까?2022년 돌아보기2022-12-31T12:00:00+00:002022-12-31T12:00:00+00:00https://16yongjin.github.io//etc/2022-look-back<p>22년 돌아보기</p>
<h2 id="회사생활-시작">회사생활 시작</h2>
<p>3월부터 카카오에서 일을 시작했다.</p>
<p>카카오톡 안에서 설문조사를 할 수 있는 <a href="https://business.kakao.com/info/talkbizform/">비즈니스폼</a> 프로젝트에 배정됐다.</p>
<p>Nuxt 2와 Composition Api, TypeScript로 구성된 프로젝트였는데</p>
<p>열심히 코드를 읽다가 중복된 코드와 타입 추론이 부족한 부분이 보였다.</p>
<p>마침 DDD에 빠져있어서 컴포넌트 간 코드 중복은 유스케이스별 함수 분리로, 타입 챌린지를 풀면서 얻은 지식으로 Vuex 타입 추론 문제 해결할 수 있어 보였다.</p>
<p>DDD를 전파하고 코드 개선하는 게 생각보다 쉬운 일이라는 것을 알리기 위해 개선사항을 정리해서 깃헙 이슈에 올렸다.</p>
<p>팀에서 반응이 좋았는지 진행하면 좋겠다는 의견이 나왔다.</p>
<p><img width="300px" src="https://user-images.githubusercontent.com/22253556/210127501-f7acb8f6-a77c-4dc8-bd08-86d3dc8d544d.png" /></p>
<h2 id="갑자기-프로젝트를-맡게-되다">갑자기 프로젝트를 맡게 되다</h2>
<p>팀원들 학습용으로 작성한 개선사항을 실제로 프로젝트에 적용하는 일을 맡게 됐다.</p>
<p>우선, 진행하려는 일이 팀원들에게 어떻게 도움이 되는지 설득하기 위해 PPT를 만들었다. (<a href="https://sli.dev/">Slidev</a> 사용)</p>
<p>UI와 비즈니스 로직은 분리될 수 있고, 로직을 분리하면 재활용할 수 있으므로 나중에 작성할 코드가 줄어들어 개발 속도가 빨라진다는 내용이었다.</p>
<p>“전술이 없는 전략은 승리로 가능 가장 느린 길”이라는 말이 있다.</p>
<p>해야 할 이유가 생겼으므로, 실제로 어떻게, 어떤 방식으로 하는지에 대한 구체적인 전술이 있어야 했다.</p>
<p>개선이 필요한 컴포넌트 목록을 구글 스프레드시트에 정리해서 각각에 담당자, 완료 여부를 체크할 수 있게 했다.</p>
<p>또한, 실제로 코드를 어떻게 수정해야 하는지 스텝 바이 스텝 가이드를 작성했다.</p>
<p>달성할 목표를 세우고 개선이 얼마나 이뤄졌는지 지표를 뽑아서 매주 공유했다.</p>
<p><img width="500" alt="CleanShot 2022-12-31 at 15 56 03@2x" src="https://user-images.githubusercontent.com/22253556/210128219-e27f8711-12e4-40d8-9914-a35917976253.png" /></p>
<p>코드베이스 전체를 리팩터링하는 규모가 있는 작업이었는데 버그도 거의 없이 잘 마무리됐다.</p>
<p>진행 상황이 눈에 보이니까 프로젝트 막판엔 엄청난 속도로 작업이 마무리되기도 했다.</p>
<h3 id="프로젝트-진행-후기">프로젝트 진행 후기</h3>
<ul>
<li>
<p>처음 프로젝트를 맡아서 진행했는데 팀원들에게 도움을 엄청 많이 받았다. 좋은 회사는 따로 없고 팀원들이 좋은 회사인 거 같다.</p>
</li>
<li>
<p>함수만 분리한다고 모든 게 분리되는 게 아니었다. 함수가 반환하는 데이터 형식에 의존한다면 분리해도 분리된 게 아니다.</p>
</li>
<li>
<p>Vuex를 열심히 분리하면 따로 떼서 테스트가 쉬울 줄 알았는데, 실제 테스트를 위해서는 결국 Vuex에 필요한 전체 데이터를 넣어줘야 한다. 목업 라이브러리를 쓰면 사정이 나아지지만, 별도의 타입스크립트 플러그인을 작동시켜야 한다. 현재 사용 중인 Nuxt 타입스크립트 컴파일러 버전 낮아서 적용이 힘들다.</p>
</li>
<li>
<p>그래도 코드 재사용이 많이 늘어났고, 컴포넌트 코드가 가벼워졌다.</p>
</li>
</ul>
<h2 id="번아웃이-오다">번아웃이 오다</h2>
<p>코딩하는게 너무 재밌어서 평일에도 일하고 주말에는 사이드프로젝트를 진행했다.</p>
<p>모나코 에디터 기반 HTTP 프록시 앱을 만들었는데</p>
<p>서버의 모든 API를 목업해서 반환 데이터를 로컬에 저장하고</p>
<p>저장된 데이터를 VS Code의 에디터로 맘껏 조작하면서 FE웹앱을 테스트할 수 있다.</p>
<p><img src="https://user-images.githubusercontent.com/22253556/210128701-63a4b432-c0eb-4b4c-8c2f-b3ac1c9cd91a.png" alt="CleanShot 2022-12-31 at 16 13 22@2x" /></p>
<p>일렉트론 기반에 trpc, Vue 3, fxts 등등 원하는 기술스택을 모두 가져다 쓰고</p>
<p>주말에 아무 눈치 안 보고 내가 원하는 대로 개발하니까 도파민이 미친 듯이 뿜어져 나오면서 너무 재밌었다.</p>
<p>이러다가 평일엔 매일 반복되고 덜 재미있는 회사 일을 하니까 (이걸 20년 더 하라고..?) 도파민 불균형 때문인지 급 우울증이 왔다.</p>
<p>열심히 만든 위의 앱이 서버 인증 방식 변경으로 못 쓰게 되는 상황이 오기도 했다..</p>
<h2 id="번아웃-극복-with-금융치료">번아웃 극복 (with 금융치료)</h2>
<p>어느날 도저히 일을 못하겠어서 키보드도 쳐다보기 싫어졌다.</p>
<p>그래서 오프를 내고 키보드를 바꾸러 용산에 갔다.</p>
<p><img src="https://user-images.githubusercontent.com/22253556/210129095-d5bec0df-d89b-4734-b092-69e2108e2953.png" alt="image" />
(출처 - <a href="https://m.blog.naver.com/razershop/222888590198">레이저 용산 아이파크몰 스토어</a>)</p>
<p>용산 레이저 스토어에 가서 키보드, 마우스, 패드까지 50만원 어치를 지르고 왔다.</p>
<p><img src="https://user-images.githubusercontent.com/22253556/210129234-b9b5365d-5d49-42ae-a442-00595234129b.png" alt="IMG_0598 (1)" /></p>
<p>칙칙하고 때가 탄 키보드만 보다가 형형색색의 키보드와 마우스패드를 보니 기분이 약간 좋아졌다.</p>
<p><img src="https://user-images.githubusercontent.com/22253556/210129240-49aa607d-25ec-4d48-aa60-d613fadde3cf.png" alt="IMG_0604 (1)" /></p>
<p>그래서 번아웃 극복 팁 1: 환경을 바꿔본다. 급진적으로</p>
<p>주말엔 동기형이랑 한강에 돗자리 펴고 술 마시면서 이야기를 나눴는데, 확실히 고민을 나누면 마음이 가벼워진다는 것을 느꼈다.</p>
<p>번아웃 극복 팁 2: <del>술 마시기</del> 터놓고 말하기</p>
<h2 id="인생책을-찾다">인생책을 찾다</h2>
<p><a href="http://www.yes24.com/Product/Goods/110243880">이펙티브 엔지니어</a>를 읽고 진짜 인생이 바뀐 것 같다.</p>
<p><img width="748" alt="CleanShot 2022-12-31 at 16 51 51@2x" src="https://user-images.githubusercontent.com/22253556/210129697-898dc8db-7003-4a31-9c54-ed4a8449ef04.png" /></p>
<p>내가 들이는 시간 대비 가장 성과가 높은 일을 하라는 레버리지 개념을 이해하게 됐는데</p>
<p>매일 가장 중요한 일을 먼저 처리하고 남은 시간에 부차적인 업무를 하기로 결심하고 나서부터는</p>
<p>업무에 여유가 생기기 시작하고, 미뤄왔던 사이드프로젝트들이 저절로 마무리됐다.</p>
<p><img width="708" alt="CleanShot 2022-12-31 at 16 51 40@2x" src="https://user-images.githubusercontent.com/22253556/210129698-8ea52884-e7f8-4022-94e6-cc5f1ce9339d.png" /></p>
<p>무엇보다, 팀원들의 시간을 줄여주는 도구들을 많이 만들 수 있었다.</p>
<ul>
<li>브랜치의 지라 티켓 이름 기반으로 깃헙 PR 제목을 자동 입력해주는 확장 프로그램</li>
<li>매일 적어야 하는 데일리스크럼을 구글 캘린더와 지라 기반으로 자동 생성해주는 프로그램</li>
<li>HTML 마크업 비교기, 캘린더 기반 일정 계산기 등등</li>
</ul>
<p>이렇게 줄인 팀원들의 시간이 1년에 800시간 정도된다.</p>
<p>이런 도구들을 만들면서 쿠버네티스 같은 사내 인프라도 써보고, TDD도 숙련시킬 수 있던 건 덤이다.</p>
<p>외에도, 불필요한 회의를 없애고 줄여서 1년에 1000시간이 낭비되는 것도 예방했다.</p>
<h3 id="현재-업무방식에-큰-영향을-끼친-책">현재 업무방식에 큰 영향을 끼친 책</h3>
<ol>
<li><a href="http://www.yes24.com/Product/Goods/110243880">이펙티브 엔지니어</a></li>
<li><a href="http://www.yes24.com/Product/Goods/38286918">딥워크</a></li>
<li><a href="http://www.yes24.com/Product/Goods/53214530">나는 4시간만 일한다.</a></li>
<li><a href="http://www.yes24.com/Product/Goods/13657193">피플웨어</a></li>
</ol>
<p> </p>
<h2 id="책">책</h2>
<p>회사 다니면 책 읽을 시간이 없을 줄 알았는데, 역대급으로 책을 많이 읽은 한해가 됐다. (90권 정도)</p>
<p>책을 읽고 행동이 바뀌어야 제대로 읽었다고 할 수 있는 것 같다. 안 그러면 그냥 본거다.</p>
<h3 id="개발-관련">개발 관련</h3>
<ul>
<li>좋은 코드 나쁜 코드</li>
<li>밑바닥부터 만드는 인터프리터 in Go</li>
<li>가상 면접 사례로 배우는 대규모 시스템 설계 기초</li>
<li>게임 엔진 블랙 북: 울펜슈타인 3D</li>
<li>레거시 코드 활용 전략</li>
<li>쏙쏙 들어오는 함수형 코딩</li>
<li>함께 자라기</li>
<li>프로그래머의 뇌</li>
<li>죽을 때까지 코딩하며 사는 법</li>
<li>프로그래머, 수학의 時代 :프로그래머의 길을 생각한다</li>
<li>클린 코드 :애자일 소프트웨어 장인 정신</li>
<li>(유지보수 가능한) 코딩의 기술 :클린 코드의 비결,자바편</li>
<li>구글 엔지니어는 이렇게 일한다 : 구글러가 전하는 문화, 프로세스, 도구의 모든 것</li>
<li>프로그래머가 알아야 할 97가지</li>
<li>개발자 리부트 : 새 판을 리드하는 개발자 미래의 모든 것</li>
<li>이펙티브 엔지니어</li>
<li>컴파일러 개발자가 들려주는 C 이야기 : 아무도 알려주지 않던 심오한 C의 비밀</li>
<li>테스트 주도 개발</li>
<li>(이펙티브) 타입스크립트 : 동작 원리의 이해와 구체적인 조언 62가지</li>
<li>소프트웨어 장인 :프로페셔널리즘/실용주의/자부심</li>
<li>안드로이드 뜻밖의 역사 : 세상을 뒤흔든 모바일 OS에 담긴 숨은 이야기</li>
<li>피플웨어</li>
<li>(미래를 만든) Geeks</li>
<li>(직장인을 위한) 실무 구글 스프레드시트 : 실전! 비즈니스 구글 스프레드시트 완전 정복</li>
</ul>
<h3 id="디자인">디자인</h3>
<ul>
<li>실무 피그마</li>
<li>(한 끗으로 달라지는) PPT 디자인 공식 :직장인을 위한 파워포인트 실전 노하우</li>
<li>최고의 주택 평면 : 집짓기 전에 봐서 다행이야</li>
<li>정보는 아름답다</li>
<li>디자인, 일상의 경이</li>
<li>UX/UI의 10가지 심리학 법칙 :사용자의 마음을 읽는 인간 중심 제품과 서비스 디자인</li>
<li>비주얼 씽킹 : 일 잘하는 사람들이 쓰는 간단한 표현의 기술</li>
<li>명작 의자 유래 사전 :한눈에 알 수 있는 350가지 의자의 역사와 디자인</li>
</ul>
<h3 id="경제">경제</h3>
<ul>
<li>파이어족이 온다 :금융위기 후 전 세계 젊은이들을 사로잡은 라이프스타일 혁명</li>
<li>이웃집 백만장자 변하지 않는 부의 법칙 :흔들리지 않는 부는 어떻게 축적되는가</li>
<li>블록체인 트렌드 2022-2023 : 기초 개념부터 투자 힌트까지 쉽게 쓰인 블록체인 교과서</li>
<li>거인의 포트폴리오 : 월급을 쪼개서 경제적 자유를 만드는 23가지 전략</li>
<li>(파이어족 강환국의) 하면 된다! 퀀트 투자 : 부와 자유를 꿈꾸는 직장인을 위한 주식투자의 정석</li>
<li>(운명을 바꾸는) 부동산 투자 수업 : 내 집 마련부터 실전 아파트 투자까지, 결국 돈 버는 부동산 투자 트레이닝: 기초편</li>
<li>세이노의 가르침</li>
</ul>
<h3 id="일">일</h3>
<ul>
<li>전략적 공부기술</li>
<li>일하면서 성장하고 있습니다</li>
<li>나는 4시간만 일한다</li>
<li>일놀놀일 : 일하듯이 놀고 놀듯이 일하는 마케터의 경계 허물기</li>
<li>코딩 몰라도 됩니다 : IT 기업에서 비개발자로 살아남기</li>
<li>나는 애플로 출근한다 =I work for Apple</li>
<li>언리시 =Unleash : 내가 지금 가진 것들을 성장의 무기로 만드는 법</li>
<li>30대에 하지 않으면 후회할 것들</li>
<li>휴먼 스킬 :인공 지능은 감히 넘볼 수 없는 인간의 기술</li>
<li>딥 워크 :강렬한 몰입, 최고의 성과</li>
<li>인생 직업</li>
<li>솔로 워커 : 미치지 않고 혼자 일하는 법</li>
<li>신입사원 상식사전 :옆자리 선배도 모르는 회사생존 매뉴얼 115!</li>
<li>보고는 요약이다 : C.O.R.E. 단숨에 일머리를 키우는 생각 정리의 기술</li>
<li>똑바로 일하라 :성과는 일벌레를 좋아하지 않는다</li>
<li>판교의 젊은 기획자들 : 존재하지 않던 시장을 만든 사람들</li>
<li>실리콘밸리에선 어떻게 일하나요 ; 직원 만족과 경쟁력을 함께 키우는 조직문화 7</li>
<li>PM 입문 : 프로덕트 기획을 위한 UX적 발상법과 사내외 커뮤니케이션</li>
</ul>
<h3 id="심리학철학">심리학/철학</h3>
<ul>
<li>통찰, 평범에서 비범으로</li>
<li>데이터는 어떻게 인생의 무기가 되는가 : 당신의 모든 선택에서 진짜 원하는 것을 얻는 법</li>
<li>인생의 가장 결정적 시기에서 : 20대가 중요한 이유와 그 시기를 지금 최대한 활용하는 법</li>
<li>지속가능한 삶을 모색하는 사피엔스를 위한 가이드</li>
<li>생각하지 않는 사람들 :인터넷이 우리의 뇌 구조를 바꾸고 있다</li>
<li>(강원국의) 어른답게 말합니다 :품격 있는 삶을 위한 최소한의 말공부</li>
<li>말은 딱 깔끔하고 센스 있게 : ‘좋아요’를 부르는 전달의 법칙</li>
<li>(밥 프록터) 부의 확신</li>
</ul>
<h3 id="마케팅">마케팅</h3>
<ul>
<li>보랏빛 소가 온다</li>
<li>믹스 =Mix : 세상에서 가장 쉬운 차별화</li>
<li>머물고 싶은 순간을 팝니다 : 지속 가능한 일상이 그리워지는 지금, 우리가 원하는 공간에 대한 모든 것</li>
</ul>
<p> </p>
<h2 id="내년-목표">내년 목표</h2>
<ul>
<li>프로그래밍을 한지 7년이 됐다. 이제 경력은 2년차가 되지만, 최소 5년차 개발자처럼 능력을 발휘하고 책임감있게 행동한다.</li>
<li>지금까지 읽은 책들을 활용해서 비즈니스 가치를 창출한다. 시장과 사용자를 분석해서 문제를 발견한다. 빠르게 제품을 구현하거나 구현한 척만 한다. 실험과 지표를 기반으로 제품을 발전시킨다.</li>
<li>기초를 단단히 하고 깊이 판다. 제품과 관련된 iOS, Android, Java Spring 레포의 아키텍처와 코드 흐름을 파본다. 언어와 무관하게 프로그램 확장성에 도움이 되는 구조를 발견한다.</li>
</ul>yongjin080222년 돌아보기첫 회사에서 한 5가지 실수2022-06-18T12:00:00+00:002022-06-18T12:00:00+00:00https://16yongjin.github.io//development/mistakes-by-noob<p>아직도 이불킥을 찬다.</p>
<h2 id="첫-회사에서-저지른-5가지-실수">첫 회사에서 저지른 5가지 실수</h2>
<ol>
<li>테스트 없이 프로덕션 배포를 했다.</li>
<li>사고를 쳐놓고 인정하지 않았다.</li>
<li>요구사항을 제대로 확인하지 않고 개발을 진행했다.</li>
<li>배포를 남한테 떠넘겼다.</li>
<li>코드 리뷰를 싸가지 없게 했다.</li>
</ol>
<h2 id="1-테스트-없이-프로덕션-배포를-했다">1. 테스트 없이 프로덕션 배포를 했다.</h2>
<p>간단한 코드를 수정하고 바로 프로덕션 배포를 한 적이 있다.</p>
<p>머릿속으로 실행을 해봤을 땐, 완벽하게 작동했기 때문에 따로 테스트는 하지 않았다.</p>
<p>그리곤 운영팀에서 난리가 났다. 😱</p>
<p>사용자 불만 문의가 유입되고, 불편을 겪은 사용자에게 사과 메일도 보냈다.</p>
<p>원인은 입력 폼 검증 로직에 삼항 연산자를 썼는데, 연산자 우선순위를 제대로 고려하지 않아서 모든 사용자의 폼 제출이 막혔다.</p>
<p>그 이후론 코드 수정 후, 잘 돌아가는 것을 반드시 눈으로 확인해보고 PR을 올리고, 배포한다.</p>
<p>또한, 버그를 발견하기 위해 더욱 공격적으로 코드 리뷰를 하게 됐다.</p>
<p>남의 코드를 보고 찜찜한 부분이 있으면, 귀찮아도 직접 실행해서 테스트하곤 한다.</p>
<h3 id="개인적-해결책">개인적 해결책</h3>
<ul>
<li>배포 전에 꼭 테스트한다. 5분 귀찮다고 테스트 안 하면 5일 동안 정신 나간다.</li>
<li>모든 일은 생각보다 급하지 않다. 차분하게 해도 큰일이 나지 않는다.</li>
</ul>
<h3 id="팀차원-해결책">팀차원 해결책</h3>
<ul>
<li>코드 리뷰를 통해 다른 팀원이 코드 레벨에서 문제를 미리 파악한다.</li>
<li>스테이징, CBT 환경에서 반드시 테스트해보고 배포한다.</li>
</ul>
<h2 id="2-사고를-쳐놓고-인정하지-않았다">2. 사고를 쳐놓고 인정하지 않았다.</h2>
<p>+ 비즈니스 로직을 모두 파악하지 못했다.</p>
<p>내가 모로고 있던 유저 플로가 있었는데,</p>
<p>그 플로를 실행하면, 한 사용자의 데이터를 다른 사용자의 데이터로 덮어져서 기존의 데이터가 소실되는 사고가 발생했다.</p>
<p>긴급회의로 팀원들이 소집됐을 때, 분명히 내 잘못인데도,</p>
<p>유저 플로를 전혀 몰라서 대응할 수 없던 터라 “나도 당했다”라는 느낌 때문에 팀원들에게 제대로 사과하지 못했다.</p>
<p>문제가 발생했으니 상황 파악을 하고 해결책을 제시했어야 했는데, 멘붕 상태라 얼빠진 행동을 했다.</p>
<p>다음엔, 큰 사고를 치면 멘탈을 제대로 부여잡고 빠르게 해결부터 한 뒤에 사과하겠다고 다짐하고 있다.</p>
<p>그리고 문제가 재발하지 않기 위해 포스트모르템을 작성하고, 개인 차원을 넘어 조직 차원에서 문제를 예방을 방법을 생각하려고 한다.</p>
<p>온보딩 때, 누가 비즈니스 로직을 1:1로 가르쳐주거나, 잘 정리된 문서만 있었어도 지금까지 이불킥 찰 일은 없었을 텐디…</p>
<h3 id="개인적-해결책-1">개인적 해결책</h3>
<ul>
<li>실수를 빨리 인정하고 해결한다.</li>
<li>실수했으면 사과한다.</li>
<li>실수를 예방할 대책을 세운다.</li>
</ul>
<h3 id="팀차원-해결책-1">팀차원 해결책</h3>
<ul>
<li>신입사원이 놓치는 비즈니스 로직이 없도록 온보딩 때 교육을 하거나 문서를 잘 정리한다.</li>
<li>사고를 쳐서 당황한 팀원이 멘탈을 붙잡을 수 있도록, 지금 당장 해야 할 행동을 인지시킨다. (문제 파악, 해결)</li>
</ul>
<h2 id="3-요구사항을-제대로-확인하지-않고-개발을-진행했다">3. 요구사항을 제대로 확인하지 않고 개발을 진행했다.</h2>
<p>요구사항을 착각해서 설정만 수정해서 5분이면 끝날 일을, 잘못된 기능을 개발하느라 3일을 날렸다.</p>
<p>또한, 잘못 구현한 기능이 다른 모듈에 영향을 끼쳐서 그것대로 민폐였다.</p>
<p>1분이라도 들여서 요구사항을 제대로 확인했으면 이런 일이 일어나지 않았을 것이다.</p>
<h3 id="개인적-해결책-2">개인적 해결책</h3>
<ul>
<li>요구사항을 제대로 확인하고 또 확인한다.
<ul>
<li>프로젝트를 동시에 많이 진행하다 보면 어떤 프로젝트의 어떤 기능을 구현해야 하는지 정말 헷갈린다.</li>
<li>그럴수록, 정신 똑바로 차리고 내가 하고 있는 게 맞는지 확인해야 한다.</li>
</ul>
</li>
</ul>
<h3 id="팀차원-해결책-2">팀차원 해결책</h3>
<ul>
<li>구현할 요구사항을 리뷰한다.
<ul>
<li>현 회사에서는 팀원이 돌아가면서 피쳐리스트를 작성하고 모든 팀원이 검토를 한다.</li>
<li>기획에서 요구한 사항을 충족하는지와 문제가 발생할 부분은 없는지 확인할 수 있다.</li>
</ul>
</li>
</ul>
<h2 id="4-배포를-남한테-떠넘겼다">4. 배포를 남한테 떠넘겼다.</h2>
<p>배포 담당자가 있었지만, 내가 배포를 직접할 수 있었다면 고객의 요구사항에 유연하게 대체할 수 있었을 것이다.</p>
<p>당시엔 급하게 배포해야 할 게 있을 때마다, 담당자에게 부탁을 했어야 했다.</p>
<p>그게 귀찮아서 당장 배포해도 괜찮을 것도 정기 배포 때 같이 태워서 배포했다.</p>
<p>그렇게 어려운 게 아니라, 그냥 내가 하면 됐는데 왜 안 했을까?</p>
<h3 id="개인적-해결책-3">개인적 해결책</h3>
<ul>
<li>배포를 직접 해본다.</li>
</ul>
<h3 id="팀차원-해결책-3">팀차원 해결책</h3>
<ul>
<li>배포를 쉽게 할 수 있는 환경을 만든다.</li>
</ul>
<h2 id="5-코드-리뷰를-싸가지-없게-했다">5. 코드 리뷰를 싸가지 없게 했다.</h2>
<p>코드 리뷰에 재미가 붙기 시작한 때였다.</p>
<p>코드 속에 숨어 있는 버그와 요구사항을 충족하지 못한 부분을 잡아내서 리뷰를 남기곤 했는데,</p>
<p>다른 회사에 가서 코드 리뷰를 남기기만 하던 입장에서 받아 보는 입장이 되니, 잘못을 지적받는 게 정신적으로 힘든 일임을 알게 됐다.</p>
<p>리뷰에 말이 이쁘게 적혀 있더라도, 코드에 부족한 부분을 들키게 되면 머쓱할 때도, 맘이 덜컹할 때도 많은 거 같다.</p>
<p>코드 리뷰가 다른 개발자들의 마음에 비수를 난사하는 행위가 되지 않도록 조심해야겠다.</p>
<p>그리고 나쁜 것만 지적하지 않고 잘한 점도 발견해서 칭찬해야 하는데,, 쉽지는 않지만 노력해야 한다.</p>
<h3 id="개인적-해결책-4">개인적 해결책</h3>
<ul>
<li>🌸🌷🌹말 좀 이쁘게 하자🌻🌺💐</li>
<li>칭찬 좀 하자</li>
<li>구글의 지혜: 겸손하게 다른 팀원의 지적 창조물을 존중하고 신뢰한다. (Humble, Respect, Trust)</li>
</ul>
<h3 id="팀차원-해결책-4">팀차원 해결책</h3>
<ul>
<li>좋은 리뷰를 선정해서 공유한다.</li>
</ul>
<h2 id="마무리">마무리</h2>
<p>남의 실수를 봐도 별 감흥이 없었지만, 직접 실수를 해보니 큰 깨달음을 얻을 수 있었다.</p>
<p>실수 했을 당시엔 지옥 같았지만, 지금은 <strong>미리 실수해서 다행</strong>이라고 생각한다.</p>
<p>앞으로도 많은 실수를 하겠지만, 전에 저지른 실수 덕분에 전보다 더 잘 대처할 자신감이 생겼다.</p>yongjin0802아직도 이불킥을 찬다.2021년 돌아보기2021-12-29T12:00:00+00:002021-12-29T12:00:00+00:00https://16yongjin.github.io//etc/2021-look-back<p>2021년 돌아보기</p>
<h2 id="첫-퇴사">첫 퇴사</h2>
<p>딥네츄럴에서 반년 인턴 생활을 마쳤다.</p>
<p>실무를 하면서 책에서 보던 것을 실제로 해보고 몸으로 겪으면서 많이 배울 수 있었다.</p>
<p>내부에서 만드는 프로그램들이 정말 다양하고 재미있는데 학업 때문에 더 다니지 못한 게 아쉽다.</p>
<p>한편, 내가 만든 도구가 <a href="https://aihub.or.kr/aidata/30735">AI Hub</a>에 실려서 뿌듯하다.</p>
<h2 id="카카오-여름-인턴">카카오 여름 인턴</h2>
<p>운 좋게 인턴 기회가 생겨서 방학 때 코딩만 하면서 행복한 시간을 보냈다.</p>
<p>바닐라 JS만 사용해서 프로그램을 만들었는데, 프로그램 실행 단위를 분자 단위로 쪼개서 신박한 패턴도 만들어보고 계층형 아키텍처를 적용해서 복잡성도 제어해 봤다.</p>
<p>중요하게 생각하는 코드 리뷰도 필수에다 기술 스택도 다양하고, 잘하시는 분들이 많아서 개발하기 좋은 곳인 것 같다.</p>
<h2 id="대학교-4학년-생활">대학교 4학년 생활</h2>
<p>온라인 수업 최고다</p>
<h3 id="창업-동아리">창업 동아리</h3>
<p>좋은 아이디어 있으면 개발을 도와주기 위해 창업 동아리에 가입했다.</p>
<p>동아리 활동을 지켜보면서 창업엔 시장성, 전문성, 자본력 셋 중에 둘 이상 만족해야 한다는 생각이 들었다.</p>
<p>아이디어가 좋아서 시장성이 있어도 실현할 전문성이 없으면 꽝인 것 같다. 외주를 맡기면 그게 다 돈이라 자본력이 필요하다.</p>
<blockquote>
<p>코딩 호러의 책을 보면 아이디어는 비싸야 10만 원짜리이고(인터넷에서 10만 원 주고 아이디어 한 바가지 얻을 수 있다.), 여기에 본인과 팀의 능력만큼을 곱하면 제품의 가치가 된다고 한다.</p>
</blockquote>
<p>동아리 모임 때는 서로 PPT로 상황보고하고 끝나는데, 프로그램을 개편해서 시장분석법 기초 강의, 창업 성공사례 분석, 멘토 초빙해서 컨설팅받기 등을 했으면 어땠을까 싶다.</p>
<h3 id="차력쇼">차력쇼</h3>
<p>종합설계와 소프트웨어공학 5인 팀플에서 혼자 차력쇼했다.</p>
<p>종설때는 그나마 개발만 맡고 발표는 위임했는데</p>
<p>소웨공 팀플에서는 요구명세서, 시스템설계명세서, 최종보고서, 회의록까지 문서 80장을 작성하고 백엔드, 프론트 개발까지 혼자 다 했다.</p>
<p>다른 팀원들도 능력을 발휘할 수 있도록 환경을 조성했어야 했는데 어렵다..</p>
<h3 id="빅데이터-팀플">빅데이터 팀플</h3>
<p>소웨공은 만들 프로그램이 정해져 있어서 빨리 문서를 작성하고 개발만 하면 되므로 100m 달리기를 하는 느낌인데</p>
<p>빅데이터 팀플은 스스로 질문을 정하고 답을 찾는 거라 망망대해에서 표류하는 느낌이었다.</p>
<p>어떻게 해야 할지 모르겠는 상황에서 팀원끼리 아이디어를 쌓아 올리고 답을 구체화하다 보니 괜찮은 결과물이 나와서 신기했다.</p>
<p>좋은 팀원을 만나면 의지할 수 있어서 서로 편해지는 것 같다.</p>
<h2 id="읽은-책">읽은 책</h2>
<h3 id="개발-관련">개발 관련</h3>
<ul>
<li><a href="https://www.aladin.co.kr/shop/wproduct.aspx?ItemId=38786788">실용주의 프로그래머</a></li>
<li><a href="https://www.aladin.co.kr/shop/wproduct.aspx?ItemId=236186172">리팩터링 2판 (리팩토링 개정판)</a></li>
<li><a href="https://www.aladin.co.kr/shop/wproduct.aspx?ItemId=193681076">오브젝트 - 코드로 이해하는 객체지향 설계</a></li>
<li><a href="https://www.aladin.co.kr/shop/wproduct.aspx?ItemId=84000742">DDD Start! - 도메인 주도 설계 구현과 핵심 개념 익히기</a></li>
<li><a href="https://www.aladin.co.kr/shop/wproduct.aspx?ItemId=112387204">You don’t know JS</a></li>
<li><a href="https://www.aladin.co.kr/shop/wproduct.aspx?ItemId=274143194">그림으로 이해하는 AWS 구조와 기술</a></li>
<li><a href="https://www.aladin.co.kr/shop/wproduct.aspx?ItemId=24817127">코딩 호러의 이펙티브 프로그래밍 :스택 오버플로우 공동 창립자가 알려주는 소프트웨어 개발의 비밀</a></li>
<li><a href="https://www.aladin.co.kr/shop/wproduct.aspx?ItemId=246390920">유닉스의 탄생: 세상을 바꾼 운영체제를 만든 천재들의 숨은 이야기</a></li>
<li><a href="https://www.aladin.co.kr/shop/wproduct.aspx?ItemId=254236998">게임 엔진 블랙 북 : 울펜슈타인 3D</a></li>
<li><a href="https://www.aladin.co.kr/shop/wproduct.aspx?ItemId=276041776">가상 면접 사례로 배우는 대규모 시스템 설계 기초 </a></li>
<li><a href="https://www.aladin.co.kr/shop/wproduct.aspx?ItemId=231347166">소프트웨어 공학의 모든 것</a></li>
<li><a href="https://www.aladin.co.kr/shop/wproduct.aspx?ItemId=235367394">개발자 오디세이아 - 더 나은 개발자의 삶을 위해</a></li>
<li><a href="https://www.packtpub.com/product/mobx-quick-start-guide/9781789344837">MobX Quick Start Guide</a></li>
</ul>
<h3 id="교양">교양</h3>
<ul>
<li><a href="https://www.aladin.co.kr/shop/wproduct.aspx?ItemId=191619501">팀 쿡</a></li>
<li><a href="https://www.aladin.co.kr/shop/wproduct.aspx?ItemId=275470339">기획자의 독서 : 오늘도 책에서 세상과 사람을 읽는 네이버 브랜드 기획자의 이야기</a></li>
<li><a href="https://www.aladin.co.kr/shop/wproduct.aspx?ItemId=45535408">슬로씽킹 :잠시 멈추고 제대로 생각하는 법</a></li>
<li><a href="https://www.aladin.co.kr/shop/wproduct.aspx?ItemId=276376596">아무것도 하지 않는 사람</a></li>
<li><a href="https://www.aladin.co.kr/shop/wproduct.aspx?ItemId=260131663">올웨이즈 데이 원 - 2030년을 제패할 기업의 승자 코드, 언제나 첫날</a></li>
<li><a href="https://www.aladin.co.kr/shop/wproduct.aspx?ItemId=218530929">해서는 안 되는 디자인</a></li>
</ul>
<h2 id="2022년-목표">2022년 목표</h2>
<ul>
<li>아키텍처 관련 도서 5권 읽기
<ul>
<li><a href="https://www.aladin.co.kr/shop/wproduct.aspx?ItemId=58740186">오픈 소스 소프트웨어 아키텍처</a></li>
<li><a href="https://www.aladin.co.kr/shop/wproduct.aspx?ItemId=281760928">소프트웨어 아키텍처 101</a></li>
<li><a href="https://www.oreilly.com/library/view/building-microservices-2nd/9781492034018/">Building Microservices, 2nd Edition</a></li>
<li><a href="https://www.aladin.co.kr/shop/wproduct.aspx?ItemId=68433810">엔터프라이즈 애플리케이션 아키텍처 패턴</a></li>
<li><a href="https://abseil.io/resources/swe-book">SWE at Google</a></li>
</ul>
</li>
<li>기초 도서 복습
<ul>
<li><a href="https://www.aladin.co.kr/shop/wproduct.aspx?ItemId=34083680">클린 코드</a></li>
<li><a href="https://www.aladin.co.kr/shop/wproduct.aspx?ItemId=37469717">TDD</a></li>
</ul>
</li>
<li><a href="https://webgl2fundamentals.org/webgl/lessons/ko/">WebGL 기초 끝내기</a>
<ul>
<li><a href="https://github.com/PavelDoGreat/WebGL-Fluid-Simulation">WebGL Fluid Simulation</a> 구현</li>
</ul>
</li>
<li><a href="https://www.aladin.co.kr/shop/wproduct.aspx?ItemId=277193668">Go로 인터프리터 만들기</a></li>
<li><a href="https://github.com/type-challenges/type-challenges">Type Challenges</a></li>
</ul>yongjin08022021년 돌아보기Macintosh Classic II 개조 프로젝트2021-12-22T12:00:00+00:002021-12-22T12:00:00+00:00https://16yongjin.github.io//macintosh/macintosh-classic-II-mod<p>매킨토시 클래식II를 미니 PC로 개조하기</p>
<h2 id="개요">개요</h2>
<ul>
<li><a href="#개요">개요</a></li>
<li><a href="#프로젝트-시작-이유">프로젝트 시작 이유</a></li>
<li><a href="#중고-매킨토시-구매">중고 매킨토시 구매</a></li>
<li><a href="#매킨토시-분해하기">매킨토시 분해하기</a></li>
<li><a href="#crt-모니터-방전시키기">CRT 모니터 방전시키기</a></li>
<li><a href="#분해-완료">분해 완료</a></li>
<li><a href="#황변-제거하기">황변 제거하기</a></li>
<li><a href="#lcd-설치">LCD 설치</a></li>
<li><a href="#lcd-테스트">LCD 테스트</a></li>
<li><a href="#전원부-설치">전원부 설치</a></li>
<li><a href="#내부-부품-설치">내부 부품 설치</a></li>
<li><a href="#화면-설정">화면 설정</a></li>
<li><a href="#완성">완성</a></li>
</ul>
<h2 id="프로젝트-시작-이유">프로젝트 시작 이유</h2>
<p><img src="https://user-images.githubusercontent.com/22253556/147051641-3908bc91-fa3e-43a8-8715-e8e9db7d3b77.png" alt="macintosh 128k" /></p>
<p>평소에 PC와 GUI의 시대를 연 매킨토시에 대한 로망이 있었다. 김종민님의 <a href="https://blog.cmiscm.com/?p=5765">Macintosh Classic iPad Air Stand</a>와 닥터케이님의 <a href="https://m.blog.naver.com/drangra/221979390145">30년 전 올드맥에 새 생명을</a> 글을 보고 나도 심미성과 기능성을 모두 만족하는 매킨토시를 만들어보고 싶다는 생각을 했다.</p>
<h2 id="중고-매킨토시-구매">중고 매킨토시 구매</h2>
<p>이베이에서 중고 매킨토시를 구매했다. 황변은 쉽게 제거되므로 사진을 꼼꼼히 보고 부서진 곳이나 스크래치가 없는 물품을 골랐다.</p>
<p><img src="https://user-images.githubusercontent.com/22253556/147050389-40cb3645-a244-4fef-be9d-183b7e18b5a9.png" alt="image" /></p>
<p>도착한 맥이다. 다행히 배송 중에 부서진 곳은 없었다. 생각보다 무겁고 크다.</p>
<p><img src="https://user-images.githubusercontent.com/22253556/147055712-e958d104-e928-4d5a-bb23-acf74b5f0189.jpg" alt="IMG_20210925_215507" /></p>
<p>같이 따라온 키보드는 애플 로고와 키캡에 인쇄된 이탤릭체 덕분에 이쁘다. 신기한 점은 캡스락 키가 볼펜처럼 누르면 딸깍거리면서 들어가고, 다시 누르면 튀어나온다. 아쉬운 점은 연결부가 PS2 규격이지만, 내부적으로 애플 전용 인터페이스를 써서 윈도에서 사용이 안 된다.</p>
<p><img src="https://user-images.githubusercontent.com/22253556/147055704-4dfbf12e-6760-45bc-9360-a195a33c46c8.jpg" alt="IMG_20210923_171804" /></p>
<p>중간고사 때문에 바빠서 바로 개조는 못 하고 한동안 책꽂이로 썼다.</p>
<p><img src="https://user-images.githubusercontent.com/22253556/147055710-50725fea-1efc-44fe-b2d1-31fdef5b826c.jpg" alt="IMG_20211005_025317" /></p>
<h2 id="매킨토시-분해하기">매킨토시 분해하기</h2>
<p>분해 전에 부품 사진을 찍었다.</p>
<ul>
<li>멀티탭</li>
<li>아이패드 1 액정 + HDMI 연결을 위한 <a href="https://ko.aliexpress.com/item/4000078058633.html">AD 보드</a></li>
<li>안 쓰는 노트북 메인보드 (→ 이건 고장 나서 미니 PC로 변경한다.)</li>
</ul>
<p><img src="https://user-images.githubusercontent.com/22253556/147058811-990e3bb5-e038-4cff-a751-877e960e83d0.jpg" alt="IMG_20211017_173452" /></p>
<p>15T 별 드라이버를 이용해서 뒷면 나사 4개를 빼면 되는데, 위쪽 나사 구멍이 엄청 깊어서 가지고 있는 드라이버를 집어넣을 수가 없었다.</p>
<p><img src="https://user-images.githubusercontent.com/22253556/147058803-e7e8e539-6282-4b3a-86f3-f8df8e37394e.jpg" alt="IMG_20211017_174042" /></p>
<p>롱 드라이버 바로 구입
<img src="https://user-images.githubusercontent.com/22253556/147058801-e0670321-e5b0-4170-b98c-7d684fc939e9.jpg" alt="IMG_20211020_181120" /></p>
<p>딱 대
<img src="https://user-images.githubusercontent.com/22253556/147058796-f3b48696-22b4-46e7-b994-9e5a90eda055.jpg" alt="IMG_20211020_181238" /></p>
<p>케이스 분리 성공
<img src="https://user-images.githubusercontent.com/22253556/147058789-e0086db3-d1b0-4eea-879f-29ef76eb987c.jpg" alt="IMG_20211020_181845" /></p>
<h2 id="crt-모니터-방전시키기">CRT 모니터 방전시키기</h2>
<p>CRT 모니터에 고전압이 흐르고 있어서, 분해 전에 내부 방전시켜야 한다. 안 하고 양손으로 만지다 감전되면 심장에 전기가 통해서 사망할 수도 있다고 한다. <a href="https://youtu.be/x1DeMOl_nK4">Discharging the CRT in a compact Macintosh</a> 유튜브 영상을 참고했다. 전선을 모니터 접지부와 일자 드라이버에 연결하고, 모니터 옆 빨간선이 달려있는 애노드캡을 제거한다. 전류가 남아있으면 드라이버가 닿자마자 스파크가 튄다는데, 자연적으로 방전되어서 아무 반응이 없었다.</p>
<p><img src="https://user-images.githubusercontent.com/22253556/147058777-2e3e1fc7-5eaf-4a75-842c-fce3ca62d2b9.jpg" alt="IMG_20211020_183123" /></p>
<h2 id="분해-완료">분해 완료</h2>
<p>열심히 분해해 준다.
<img src="https://user-images.githubusercontent.com/22253556/147058771-ded33a5a-c857-486b-bd0c-6319d19aaf39.jpg" alt="IMG_20211020_184134" /></p>
<p>30년된 하드
<img src="https://user-images.githubusercontent.com/22253556/147058765-e6caf6ac-e066-43d1-b59f-22a1ca629830.jpg" alt="IMG_20211020_184418" /></p>
<p>메인보드. 칩들이 큼직큼직하다.</p>
<p><img src="https://user-images.githubusercontent.com/22253556/147058733-d547ec26-1116-4c16-95fe-dc625f8c5337.jpg" alt="IMG_20211020_185324" /></p>
<p>분해된 부품. 분해에 2시간 정도 걸렸다.</p>
<p><img src="https://user-images.githubusercontent.com/22253556/147058742-8495d21a-c0c7-4638-bcc8-73bef5645861.jpg" alt="IMG_20211020_185255 (1)" /></p>
<h2 id="황변-제거하기">황변 제거하기</h2>
<p>플라스틱에 탈색제를 바르고 자외선에 노출하면 황변이 제거된다.</p>
<p>준비물</p>
<ul>
<li>탈색제</li>
<li>비닐장갑: 탈색제에 닿은 피부가 하얗게 괴사하는 것을 막는다.</li>
<li>비닐랩: 탈색제가 마르는 것을 막는다.</li>
</ul>
<p><img src="https://user-images.githubusercontent.com/22253556/147058722-654caa3e-be12-4470-8641-48bc7695ab15.jpg" alt="IMG_20211104_175628" /></p>
<p>탈색제를 꼼꼼하게 바르고
<img src="https://user-images.githubusercontent.com/22253556/147058719-4e20ac13-38e4-4ecc-a1cc-c337c9cc1fa2.jpg" alt="IMG_20211104_180825" /></p>
<p>비닐랩으로 싸면 된다.
<img src="https://user-images.githubusercontent.com/22253556/147058715-92f8c496-49bb-4707-acdc-6d20622aa93d.jpg" alt="IMG_20211104_181209" /></p>
<p>래핑 완료</p>
<p><img src="https://user-images.githubusercontent.com/22253556/147058702-1e0317ac-7f70-411d-ac5c-92991d75b2a1.jpg" alt="IMG_20211104_190714" />
가을이라 햇빛에 3일 동안 뒀다.
<img src="https://user-images.githubusercontent.com/22253556/147058691-54e5ed28-d772-48c4-bd2a-6ab2b3910088.jpg" alt="IMG_20211105_160901" /></p>
<p>비닐을 벗기고 나면 색이 공장초기화된다. 애플 로고엔 영향이 없다.
<img src="https://user-images.githubusercontent.com/22253556/147058688-5597f714-3283-41c6-9b2f-7c26cbf8bf21.jpg" alt="IMG_20211107_164309" /></p>
<p>크
<img src="https://user-images.githubusercontent.com/22253556/147058683-e4a203d4-d929-41e5-b92a-8c0d3727e6e6.jpg" alt="IMG_20211107_164318" /></p>
<h2 id="lcd-설치">LCD 설치</h2>
<p>LCD가 앞판에 밀착하도록 플라스틱 구조물을 떼어낸다. 플라스틱이 물러서 원예가위로 잘 잘린다.</p>
<p><img src="https://user-images.githubusercontent.com/22253556/147066902-6a7937db-36cd-4315-8f75-a39b9cc04064.jpg" alt="IMG_20211104_1751452" /></p>
<p>아이패드 1에서 떼낸 액정을 부착한다. 위쪽과 아래쪽 나사 기둥 덕분에 딱맞게 고정된다.</p>
<p><img src="https://user-images.githubusercontent.com/22253556/147058675-abe62f1c-e211-4b70-9b03-d2d173d8744f.jpg" alt="IMG_20211107_191334" /></p>
<h2 id="lcd-테스트">LCD 테스트</h2>
<p>PC에 HDMI로 연결해서 <a href="https://winxp.vercel.app/">웹 WinXP</a>와 <a href="https://macos.vercel.app/">웹 맥 OS</a>를 띄워봤다. 레티나 디스플레이가 아니라 픽셀이 보이지만 10년 된 LCD치고 색은 잘 나온다.</p>
<p><img src="https://user-images.githubusercontent.com/22253556/147058669-7dae2edb-5904-412b-99b9-0c7c007b2ff5.jpg" alt="IMG_20211107_193804" /></p>
<p><img src="https://user-images.githubusercontent.com/22253556/147058656-f9685572-a636-474d-ac5a-cc88c3e63d56.jpg" alt="IMG_20211107_194137" /></p>
<h2 id="전원부-설치">전원부 설치</h2>
<p>기존 부품에서 전원 연결부와 스위치를 뗐다.</p>
<p><img src="https://user-images.githubusercontent.com/22253556/147058647-b0514d24-8d13-4e61-957c-8b3d31d8d82a.jpg" alt="IMG_20211110_194041" /></p>
<p>배선은 <a href="https://youtu.be/a5XxrUhoXqE?t=508">닥터케이님의 영상</a> 8분대를 참조해서 납땜했다.</p>
<p><img src="https://user-images.githubusercontent.com/22253556/147058642-ac46d558-4b3b-4e39-b3fb-e2274d0059d2.jpg" alt="IMG_20211110_210930" /></p>
<p>멀티탭은 이렇게 매킨토시 내부에서 미니 PC, 모니터, 스피커의 전원을 공급한다.</p>
<p><img src="https://user-images.githubusercontent.com/22253556/147058638-974f8da6-55c6-4709-9c73-bad601bd74c2.jpg" alt="IMG_20211110_212057" /></p>
<p>스위치와 전원부 고정을 위해 전선쫄대를 가공해서 거치대를 만들었다.
<img src="https://user-images.githubusercontent.com/22253556/147058636-df59dd27-57a0-4968-bea2-9940ea1dd51f.jpg" alt="IMG_20211112_142543" />
부품을 끼우고
<img src="https://user-images.githubusercontent.com/22253556/147058634-62fbb94b-f69b-470b-a7c7-4ab3781e38fa.jpg" alt="IMG_20211112_142618" /></p>
<p>매킨토시 뒷면에 고정한다. 근데 접착력이 약해서 너무 잘 떨어진다.. 하단의 USB 단자도 같은 방법으로 부착했다.</p>
<p><img src="https://user-images.githubusercontent.com/22253556/147058631-1c6b3872-fc7a-476f-970c-5aa458cb0800.jpg" alt="IMG_20211112_153834" /></p>
<h2 id="내부-부품-설치">내부 부품 설치</h2>
<p>손바닥만한 <a href="https://ko.aliexpress.com/item/1005001921619318.html">미니 PC</a>를 샀다. 가격은 18만원에 스펙은 인텔 셀러론 J4125 / 8GB RAM / 128GB SSD이다.</p>
<p><img src="https://user-images.githubusercontent.com/22253556/147058649-472fe891-9876-444d-b498-c1a0316748a9.jpg" alt="IMG_20211110_125024" /></p>
<p><a href="https://ko.aliexpress.com/item/1005001679890985.html">USB 스피커</a>도 샀다.
<img src="https://user-images.githubusercontent.com/22253556/147070152-c9c03e05-5b00-42e5-8a2a-9e97371b0830.png" alt="image" /></p>
<p>내부에 미니 PC, 스피커, AD 보드, 멀티탭을 설치한다.
<img src="https://user-images.githubusercontent.com/22253556/147058625-81f46844-5979-405c-8114-2fbbddc374ba.jpg" alt="IMG_20211123_164636" /></p>
<p>미니PC와 AD보드 전원 버튼을 매킨토시의 옆구리 버튼이랑 잘 끼워맞췄다. 덕분에 추가 버튼 설치없이 PC와 모니터를 켤 수 있다.</p>
<p><img src="https://user-images.githubusercontent.com/22253556/147071057-415c61de-7024-467b-95dd-6a90b515ce76.jpg" alt="IMG_20211123_16422654 (1)" /></p>
<h2 id="화면-설정">화면 설정</h2>
<p>아이패드 화면이 매킨토시 모니터보다 커서 화면이 잘린다. 인텔 그래픽 설정에서 스케일링을 조절해서 좌우로 잘리는 부분을 없앤다.
<img src="https://user-images.githubusercontent.com/22253556/147058517-daf8a9e8-092d-4944-b1a0-36c23e37dbc8.jpg" alt="IMG_20211221_154214" /></p>
<h2 id="완성">완성</h2>
<p>뮤직 플레이어로 사용하기도 하고, 나중에 WebGL로 만든 작품들을 전시하는 용으로 사용할 계획이다. 키보드는 <a href="http://www.vortexgear.tw/vortex2_2.asp?kind=47&kind2=224&kind3=&kind4=1033">Vortex Core</a>인데 맥과 잘 어울린다.</p>
<p><img src="https://user-images.githubusercontent.com/22253556/147058583-6341a27a-e31f-4e54-8fba-e3bd46fdb5ba.jpg" alt="IMG_20211127_122119" /></p>
<p>어울리는 마우스로 아래에 50 Fifty Concepts Retro Computer Mouse를 사려고 했는데, 배송비까지 5만 원이나 해서 도저히 못 사겠다(마우스 1만 원, 배송비 4만 원).</p>
<p><img src="https://user-images.githubusercontent.com/22253556/147072828-9f893d91-bf37-4e9f-9ee2-5907f2b9ca54.png" alt="image" /></p>
<p>그래서 그냥 <a href="https://fluentsearch.net/">Fluent Search</a>의 Screen Search 기능으로 마우스 없이 쓴다.</p>
<p><img src="https://user-images.githubusercontent.com/22253556/147073693-fe39c0b7-361f-4445-891c-a05928c3dd6d.png" alt="image" /></p>
<p>그리고 그 돈으로 아케이드 스틱을 구매했다.</p>
<p><img src="https://user-images.githubusercontent.com/22253556/147058550-5afee388-9381-4be3-9b79-c6980bac69b2.jpg" alt="IMG_20211215_195354" /></p>
<p>슬라이딩 서랍에 스틱을 올려놓고 쓴다.</p>
<p><img src="https://user-images.githubusercontent.com/22253556/147058531-de681e8f-b7ac-41d0-b388-c645c95317dc.jpg" alt="IMG_20211222_151452" /></p>
<p>심심할 때 메탈슬러그 한 판씩 하면 재밌다.</p>
<p><img src="https://user-images.githubusercontent.com/22253556/147058493-0013563c-26ce-4084-a8cc-cc3746757f2c.jpg" alt="IMG_20211222_151656" /></p>
<p>끝</p>yongjin0802매킨토시 클래식II를 미니 PC로 개조하기한국어 튜터링 플랫폼2021-06-26T12:00:00+00:002021-06-26T12:00:00+00:00https://16yongjin.github.io//development/tutoring-platform<p>WebRTC를 활용한 실시간 1:1 강의를 지원하는 튜터링 플랫폼</p>
<h2 id="구현-동기">구현 동기</h2>
<ul>
<li>개인적으로 테이블 10개, 페이지 10개 이상인 서비스를 제작해보고 싶었다.</li>
<li>창업 동아리에 들어가서 적당한 규모의 아이템인 튜터링 플랫폼을 선택했다.</li>
<li>특히, 실시간 화상 대화 기능을 구현해보고 싶은 마음이 컸다.</li>
<li>기말고사 전 한 달 동안 수업 듣고 남는 시간에 만들었다.</li>
</ul>
<h2 id="기능">기능</h2>
<h3 id="사용자">사용자</h3>
<ul>
<li>튜터링 약속 잡기/취소</li>
<li>튜터 리뷰</li>
</ul>
<h3 id="튜터">튜터</h3>
<ul>
<li>튜터링 스케쥴 관리</li>
<li>튜터링 후 사용자에게 피드백 주기</li>
</ul>
<h3 id="관리자">관리자</h3>
<ul>
<li>튜터 프로필 관리</li>
<li>튜터 승인</li>
<li>교재 관리</li>
<li>추천 리뷰 설정</li>
</ul>
<h3 id="튜터링-기능">튜터링 기능</h3>
<ul>
<li>실시간 화상 대화</li>
<li>문자 채팅</li>
<li>보고 있는 교재 동기화</li>
</ul>
<h3 id="인증">인증</h3>
<ul>
<li>회원가입, 로그인</li>
<li>이메일 인증</li>
<li>비밀번호 변경</li>
</ul>
<p> </p>
<h1 id="서버-구현"><a href="https://github.com/16Yongjin/tutoring-server">서버</a> 구현</h1>
<h2 id="기술-스택">기술 스택</h2>
<ul>
<li>Nest.js</li>
<li>TypeScript</li>
<li>TypeORM</li>
<li>Postgres</li>
</ul>
<h2 id="nestjs">Nest.js</h2>
<ul>
<li><a href="https://docs.nestjs.com/">Nest.js 공식 문서</a>를 정독하면서 필요한 기능을 구현했다.</li>
<li>기능을 서비스, 컨트롤러, 모듈로 나누고 합칠 수 있는 게 복잡성 증가 속도를 늦춘다.
<ul>
<li>한 도메인에서 다른 도메인의 엔티티가 아닌 서비스를 사용함으로써 도메인 간 경계가 확실해지고 기능이 섞이는 일이 예방됐다.</li>
</ul>
</li>
<li>다양한 데코레이터로 코드 중복을 줄일 수 있었다.
<ul>
<li>DTO에 붙은 <a href="https://github.com/typestack/class-validator"><code class="language-plaintext highlighter-rouge">class-validator</code></a>로 속성을 검증하는 <code class="language-plaintext highlighter-rouge">ValidationPipe</code></li>
<li><code class="language-plaintext highlighter-rouge">JWT</code>를 확인하는 <code class="language-plaintext highlighter-rouge">JwtAuthGuard</code></li>
<li>요청 내 사용자 정보의 권한을 확인하는 <code class="language-plaintext highlighter-rouge">RoleGuard</code></li>
</ul>
</li>
</ul>
<h3 id="레포지터리-레이어의-필요성">레포지터리 레이어의 필요성</h3>
<ul>
<li>서비스 레이어에서 TypeORM의 레포지터리를 바로 불러와서 사용했다.</li>
<li>TypeORM의 <code class="language-plaintext highlighter-rouge">createQueryBuilder</code>, <code class="language-plaintext highlighter-rouge">where</code> 같은 API를 목업할 수 없어서 서비스 레이어 유닛 테스트를 포기했다.</li>
<li><code class="language-plaintext highlighter-rouge">TypeORM</code>을 쓰고 있는지 모르게 레포지터리 레이어를 뒀다면, 목업하기 쉬워서 테스트도 쉽고, 라이브러리 변경에도 대처하기 쉬웠을 것 같다.</li>
</ul>
<h2 id="엔티티-구성">엔티티 구성</h2>
<p><a href="https://github.com/eugene-manuilov/typeorm-uml">typeorm-uml</a> 라이브러리로 그린 엔티티 관계도이다.</p>
<p><img src="https://user-images.githubusercontent.com/22253556/132950349-03c853f4-960e-4014-9543-f1531a42ebdc.png" alt="typeorm-uml" /></p>
<ul>
<li>튜터는 튜터링 스케쥴(<code class="language-plaintext highlighter-rouge">Schedule</code>)을 생성한다.</li>
<li>사용자는 스케쥴에 따라 튜터링 약속(<code class="language-plaintext highlighter-rouge">Appointment</code>)을 생성한다.</li>
<li>튜터링을 마치고 튜터는 사용자에게 피드백(<code class="language-plaintext highlighter-rouge">Feedback</code>)을 남긴다.</li>
<li>사용자는 유저에게 리뷰(<code class="language-plaintext highlighter-rouge">Review</code>)를 남긴다.</li>
<li>관리자는 교재(<code class="language-plaintext highlighter-rouge">Material</code>)을 관리한다.</li>
<li>회원 가입 시 이메일 인증(<code class="language-plaintext highlighter-rouge">EmailVerification</code>)을 거친다.</li>
</ul>
<h3 id="보완할-점">보완할 점</h3>
<ul>
<li>모델링의 편함을 위해 <code class="language-plaintext highlighter-rouge">User</code>와 <code class="language-plaintext highlighter-rouge">Tutor</code> 엔티티를 나눈 게 후회된다.</li>
<li><code class="language-plaintext highlighter-rouge">Tutor</code>가 <code class="language-plaintext highlighter-rouge">User</code> 엔티티를 <a href="https://orkhan.gitbook.io/typeorm/docs/entity-inheritance#single-table-inheritance">상속</a>받아야 했다.</li>
<li>두 엔티티에 대해 지원해야 하는 기능이 거의 비슷해서 기능을 두 개씩 구현해야 했다.
<ul>
<li>로그인, 회원가입, 이메일 인증 기능도 유저 따로 튜터 따로 2개씩</li>
<li>그로 인해 테스트도 2배, UI도 2배로 구현했다.</li>
</ul>
</li>
</ul>
<h2 id="테스트">테스트</h2>
<ul>
<li>56개가 되는 컨트롤러를 Postman으로 하나하나 테스트할 수 없어서 테스트 코드를 열심히 작성했다.</li>
<li>암호화나 이메일 전송 등 비용이 큰 코드는 목업했다.
<ul>
<li>참조한 블로그: <a href="https://wanago.io/2020/07/13/api-nestjs-testing-services-controllers-integration-tests/">Testing NestJS services with integration tests</a></li>
</ul>
</li>
<li>서비스 유닛 테스트는 목업할 게 너무 많아서 <code class="language-plaintext highlighter-rouge">supertest</code>를 이용한 통합 테스트 코드를 주로 작성했다.</li>
</ul>
<h3 id="보완할-점-1">보완할 점</h3>
<ul>
<li>통합 테스트 전부를 실행하면 데드락이 발생한다.
<ul>
<li>TypeORM의 트랜잭션 매니저를 사용해도 마찬가지인데, 이건 좀 더 경험이 필요한 부분인 것 같다.</li>
</ul>
</li>
<li>테스트 DB를 인메모리 Sqlite로 교체해서 테스트 속도를 높이고 싶다.
<ul>
<li><code class="language-plaintext highlighter-rouge">timestamptz</code>가 sqlite에 없는게 문제다.</li>
</ul>
</li>
</ul>
<h2 id="예외로-코드-단순화하기">예외로 코드 단순화하기</h2>
<ul>
<li>메서드의 깊이에 상관없이 예외로 컨트롤 플로우를 탈출할 수 있는 점을 활용했다.</li>
<li>없는 엔티티 참조나 비즈니스 로직 위반 발생 시 4XX 예외를 일단 발생시킨다.
<ul>
<li>예외가 발생하지 않았으면 안전하게 엔티티를 참조하고 도메인 로직을 수행할 수 있다.</li>
</ul>
</li>
<li>명확한 에러 메시지와 함께 예외 처리 책임을 클라이언트에 넘긴다.</li>
</ul>
<h2 id="튜터-별점-실시간-평균-구하기">튜터 별점 실시간 평균 구하기</h2>
<ul>
<li>튜터 리뷰가 추가될 때마다 튜터의 평균 별점을 구해야 한다.</li>
<li>그때마다 모든 리뷰 목록을 가져와서 계산하는 건 비효율적이다.</li>
<li>튜터의 별점과 리뷰 개수만 알면, 실시간으로 평균 별점을 구할 수 있다.</li>
</ul>
<div class="language-ts highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">if</span> <span class="p">(</span><span class="nx">tutor</span><span class="p">.</span><span class="nx">reviewCount</span> <span class="o">===</span> <span class="mi">0</span><span class="p">)</span> <span class="p">{</span>
<span class="nx">tutor</span><span class="p">.</span><span class="nx">reviewCount</span> <span class="o">=</span> <span class="mi">1</span>
<span class="nx">tutor</span><span class="p">.</span><span class="nx">rating</span> <span class="o">=</span> <span class="nx">rating</span>
<span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
<span class="nx">tutor</span><span class="p">.</span><span class="nx">rating</span> <span class="o">=</span>
<span class="p">(</span><span class="nx">tutor</span><span class="p">.</span><span class="nx">rating</span> <span class="o">*</span> <span class="nx">tutor</span><span class="p">.</span><span class="nx">reviewCount</span> <span class="o">+</span> <span class="nx">rating</span><span class="p">)</span> <span class="o">/</span> <span class="p">(</span><span class="nx">tutor</span><span class="p">.</span><span class="nx">reviewCount</span> <span class="o">+</span> <span class="mi">1</span><span class="p">)</span>
<span class="nx">tutor</span><span class="p">.</span><span class="nx">reviewCount</span> <span class="o">+=</span> <span class="mi">1</span>
<span class="p">}</span>
</code></pre></div></div>
<ul>
<li>기존 별점 * 리뷰 개수는 이전 별점의 총합이다.</li>
<li>여기에 새 별점을 더하고, 새 리뷰 개수로 나누면 된다.</li>
</ul>
<p> </p>
<h1 id="클라이언트-구현"><a href="https://github.com/16Yongjin/tutoring-app">클라이언트</a> 구현</h1>
<h2 id="기술-스택-1">기술 스택</h2>
<ul>
<li>React + TypeScript</li>
<li>Ant Design</li>
<li>Formik</li>
<li>MobX</li>
<li>React Query</li>
<li>Simple Peer WebRTC</li>
<li>Socket.io</li>
</ul>
<h2 id="typesafe한-axios-api-구현">TypeSafe한 Axios API 구현</h2>
<ul>
<li>내용이 길어서 <a href="http://yongj.in/tutorials/typesafe-api/">해당 포스트</a>에 따로 작성했다.</li>
<li>API의 정의와 호출를 분리해서 코드 재사용성을 높였다.</li>
</ul>
<h2 id="튜터링-예약">튜터링 예약</h2>
<ul>
<li>사용자는 튜터 스케줄을 예약해서 약속을 잡을 수 있다.</li>
<li>이때, 사용할 교재와 요청 사항을 입력한다.</li>
</ul>
<video width="100%" controls="controls">
<source src="https://user-images.githubusercontent.com/22253556/132957380-7e6d5d3c-b534-4a06-8ee5-af1390d7f58d.mp4" type="video/mp4" />
</video>
<h3 id="빠른-유튜브-임베드">빠른 유튜브 임베드</h3>
<ul>
<li>튜터 프로필 이미지 클릭 시, 튜터 소개 유튜브 영상이 나오게 된다.</li>
<li>유튜브 삽입 시 <a href="https://github.com/paulirish/lite-youtube-embed">lite-youtube-embed</a>를 사용하면 기본 임베드 보다 200배 빠르다.</li>
</ul>
<h2 id="튜터링-진행">튜터링 진행</h2>
<ul>
<li>약속 1시간 전부터, 튜터링 페이지에 접근할 수 있다.</li>
<li><code class="language-plaintext highlighter-rouge">Ready</code> 버튼을 눌러 화상 채팅을 연결한다.</li>
<li>좌측의 교재를 선택하면, 상대의 교재도 변경된다.</li>
<li>문자 채팅도 지원한다.</li>
<li>그리드 레이아웃을 사용해서 데스크톱과 모바일을 모두 지원한다.</li>
</ul>
<iframe width="560" height="315" src="https://www.youtube.com/embed/7idm4AtpPk8" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen=""></iframe>
<h2 id="화상-대화-기능-구현">화상 대화 기능 구현</h2>
<iframe width="560" height="315" src="https://www.youtube.com/embed/oxFr7we3LC8" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen=""></iframe>
<ul>
<li>위 강의를 보고 <a href="https://github.com/feross/simple-peer">Simple Peer</a>와 React의 <a href="https://reactjs.org/docs/context.html">Context API</a>를 사용해서 비디오 챗을 구현했다.</li>
<li><code class="language-plaintext highlighter-rouge">Peer</code> 생성 후 시그널링 데이터가 만들어지면 소켓 통신을 통해 상대방과 시그널을 주고받는다.</li>
<li>P2P 연결 후, 스트림이 생성되면 이를 비디오 요소의 <code class="language-plaintext highlighter-rouge">srcObject</code>로 설정하면 된다.</li>
</ul>
<h3 id="비디오오디오-켜기끄기">비디오/오디오 켜기/끄기</h3>
<div class="language-ts highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// 비디오 토글</span>
<span class="nx">stream</span><span class="p">.</span><span class="nx">getVideoTracks</span><span class="p">().</span><span class="nx">forEach</span><span class="p">((</span><span class="nx">track</span><span class="p">)</span> <span class="o">=></span> <span class="p">{</span>
<span class="nx">track</span><span class="p">.</span><span class="nx">enabled</span> <span class="o">=</span> <span class="o">!</span><span class="nx">track</span><span class="p">.</span><span class="nx">enabled</span>
<span class="p">})</span>
<span class="c1">// 오디오 토글</span>
<span class="nx">stream</span><span class="p">.</span><span class="nx">getAudioTracks</span><span class="p">().</span><span class="nx">forEach</span><span class="p">((</span><span class="nx">track</span><span class="p">)</span> <span class="o">=></span> <span class="p">{</span>
<span class="nx">track</span><span class="p">.</span><span class="nx">enabled</span> <span class="o">=</span> <span class="o">!</span><span class="nx">track</span><span class="p">.</span><span class="nx">enabled</span>
<span class="p">})</span>
</code></pre></div></div>
<h3 id="가끔-리액트의-클래스-컴포넌트가-필요한-경우">가끔 리액트의 클래스 컴포넌트가 필요한 경우</h3>
<ul>
<li>튜터링 페이지를 나가면, 미디어 스트림을 끊어야 한다. 안 그러면 카메라가 계속 켜진 상태가 유지된다.</li>
<li><code class="language-plaintext highlighter-rouge">useEffect(fn, [])</code>는 렌더링된 비디오 요소가 사라진 뒤에 자원 해제가 실행된다.</li>
<li>클래스 컴포넌트의 <code class="language-plaintext highlighter-rouge">componentWillUnmount</code>를 사용해서 비디오 요소의 미디어 스트림을 해제했다.</li>
<li>근데, 가장 확실한 자원 해제 방법은 새로고침인 것 같다.</li>
</ul>
<h2 id="플랫한-네비게이션-라우터">플랫한 네비게이션 라우터</h2>
<p><img height="500px" src="https://user-images.githubusercontent.com/22253556/132973413-e999684b-cfe6-4f98-acb1-4099ed43e742.png" alt="mobile-navigation" /></p>
<ul>
<li>모바일 네비게이션 드로어는 사용자, 튜터, 관리자 등의 역할에 따라 다르게 보인다.</li>
<li>처음에는 아래처럼 하나 프래그먼트 안에서 역할에 따라 분기했다.</li>
</ul>
<div class="language-tsx highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p"><</span><span class="nc">Drawer</span><span class="p">></span>
<span class="p"><</span><span class="nc">Card</span><span class="p">></span>Home<span class="p"></</span><span class="nc">Card</span><span class="p">></span>
<span class="si">{</span><span class="nx">user</span> <span class="o">&&</span> <span class="p"><</span><span class="nc">Card</span><span class="p">></span>About<span class="p"></</span><span class="nc">Card</span><span class="p">></span><span class="si">}</span>
<span class="si">{</span><span class="o">!</span><span class="nx">isTutor</span> <span class="o">&&</span> <span class="p"><</span><span class="nc">Card</span><span class="p">></span>Tutors<span class="p"></</span><span class="nc">Card</span><span class="p">></span><span class="si">}</span>
<span class="si">{</span><span class="nx">user</span> <span class="o">&&</span> <span class="o">!</span><span class="nx">isTutor</span> <span class="o">&&</span> <span class="p"><</span><span class="nc">Card</span><span class="p">></span>Appointments<span class="p"></</span><span class="nc">Card</span><span class="p">></span><span class="si">}</span>
<span class="si">{</span><span class="nx">isAdmin</span> <span class="o">&&</span> <span class="p"><</span><span class="nc">Card</span><span class="p">></span>Dashboard<span class="p"></</span><span class="nc">Card</span><span class="p">></span><span class="si">}</span>
<span class="si">{</span><span class="cm">/** 다른 네비게이션 */</span><span class="si">}</span>
<span class="p"></</span><span class="nc">Drawer</span><span class="p">></span>
</code></pre></div></div>
<ul>
<li>갈수록 코드 분기가 복잡해져서 다음처럼 변경했다.</li>
<li>약간의 중복을 허용함으로써 코드는 보기 편해졌다.</li>
</ul>
<div class="language-tsx highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p"><</span><span class="nc">Drawer</span><span class="p">></span>
<span class="si">{</span><span class="nx">isPublic</span> <span class="o">&&</span> <span class="p">(</span>
<span class="p"><></span>
<span class="p"><</span><span class="nc">Card</span><span class="p">></span>Home<span class="p"></</span><span class="nc">Card</span><span class="p">></span>
<span class="p"><</span><span class="nc">Card</span><span class="p">></span>About<span class="p"></</span><span class="nc">Card</span><span class="p">></span>
<span class="p"><</span><span class="nc">Card</span><span class="p">></span>Material<span class="p"></</span><span class="nc">Card</span><span class="p">></span>
<span class="p"></></span>
<span class="p">)</span><span class="si">}</span>
<span class="si">{</span><span class="nx">isUser</span> <span class="o">&&</span> <span class="p">(</span>
<span class="p"><></span>
<span class="p"><</span><span class="nc">Card</span><span class="p">></span>Home<span class="p"></</span><span class="nc">Card</span><span class="p">></span>
<span class="p"><</span><span class="nc">Card</span><span class="p">></span>My Page<span class="p"></</span><span class="nc">Card</span><span class="p">></span>
<span class="p"></></span>
<span class="p">)</span><span class="si">}</span>
<span class="si">{</span><span class="nx">isTutor</span> <span class="o">&&</span> <span class="p">(</span>
<span class="p"><></span>
<span class="p"><</span><span class="nc">Card</span><span class="p">></span>Home<span class="p"></</span><span class="nc">Card</span><span class="p">></span>
<span class="p"><</span><span class="nc">Card</span><span class="p">></span>Main Page<span class="p"></</span><span class="nc">Card</span><span class="p">></span>
<span class="p"></></span>
<span class="p">)</span><span class="si">}</span>
<span class="si">{</span><span class="nx">isAdmin</span> <span class="o">&&</span> <span class="p">(</span>
<span class="p"><></span>
<span class="p"><</span><span class="nc">Card</span><span class="p">></span>메인<span class="p"></</span><span class="nc">Card</span><span class="p">></span>
<span class="p"><</span><span class="nc">Card</span><span class="p">></span>교재 관리<span class="p"></</span><span class="nc">Card</span><span class="p">></span>
<span class="p"><</span><span class="nc">Card</span><span class="p">></span>튜터 관리<span class="p"></</span><span class="nc">Card</span><span class="p">></span>
<span class="p"></></span>
<span class="p">)</span><span class="si">}</span>
<span class="p"></</span><span class="nc">Drawer</span><span class="p">></span>
</code></pre></div></div>
<ul>
<li>DRY 원칙을 가끔 어겨도 좋을 때가 있는 것 같다.</li>
</ul>
<h3 id="상태-기반-렌더링">상태 기반 렌더링</h3>
<ul>
<li>위의 방식을 적용해서 코드의 복잡성을 제어한 사례가 하나 더 있다.</li>
</ul>
<p><img height="500px" src="https://user-images.githubusercontent.com/22253556/132973952-a3cb262b-8ee9-43a7-93ef-1fe75e7db141.png" alt="appointment-control" /></p>
<ul>
<li>튜터링 페이지는 시간과 상황에 따라 6가지 상태가 있다.
<ul>
<li>시작 전</li>
<li>1시간 안에 시작</li>
<li>시작됨 (화상 연결 전)</li>
<li>진행 중</li>
<li>시간 초과</li>
<li>끝남</li>
</ul>
</li>
<li>튜터링까지 남은 시간과 화상대화 연결 여부에 따라 상태 중 하나를 결정한다.</li>
<li>각 상태에 맞게 필요한 컴포넌트를 렌더링한다. (위 사진의 빨간줄 부분)</li>
</ul>
<h2 id="코드-분할로-초기-로딩-속도-높이기">코드 분할로 초기 로딩 속도 높이기</h2>
<p><img src="https://user-images.githubusercontent.com/22253556/132974087-9975654c-817e-4319-9e9a-e4391274a4d7.png" alt="material" /></p>
<ul>
<li>관리자는 교재의 연습문제를 수정할 수 있다.</li>
<li>연습문제 수정은 <a href="https://www.npmjs.com/package/react-draft-wysiwyg"><code class="language-plaintext highlighter-rouge">react-draft-wysiwyg</code></a>를 사용한다.</li>
<li>해당 라이브러리는 번들 크기를 300KB 정도 차지해서 초기 로딩 속도를 늦춘다.</li>
<li>해당 컴포넌트는 모든 사람이 사용하는게 아니므로 코드 분할을 통해 번들 크기를 줄이고 초기 로딩 속도를 높였다.</li>
</ul>
<div class="language-tsx highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">const</span> <span class="nx">CourseDetail</span> <span class="o">=</span> <span class="nx">React</span><span class="p">.</span><span class="nx">lazy</span><span class="p">(()</span> <span class="o">=></span> <span class="k">import</span><span class="p">(</span><span class="dl">'</span><span class="s1">../admin/CourseDetail</span><span class="dl">'</span><span class="p">))</span>
<span class="o"><</span><span class="nx">Suspense</span> <span class="nx">fallback</span><span class="o">=</span><span class="p">{</span><span class="o"><</span><span class="nx">div</span><span class="o">></span><span class="nx">Loading</span><span class="p">...</span><span class="o"><</span><span class="sr">/div>}</span><span class="err">>
</span> <span class="o"><</span><span class="nx">CourseDetail</span> <span class="o">/></span>
<span class="p"><</span><span class="err">/</span><span class="na">Suspense</span><span class="p">></span>
</code></pre></div></div>
<h2 id="배포">배포</h2>
<ul>
<li><a href="https://www.netlify.com/">Netlify</a>로 배포했다.</li>
<li>레포에 Push만 하면 자동으로 배포돼서 편하다.</li>
<li>HTTPS 설정도 쉽다.</li>
<li>빌드 시 <code class="language-plaintext highlighter-rouge">CI=false</code> 설정을 해야, 경고가 발생해도 빌드가 멈추지 않는다.</li>
</ul>
<p> </p>
<h1 id="마무리">마무리</h1>
<h2 id="느낀-점">느낀 점</h2>
<ul>
<li>시간 압박 때문에 기능 구현에 집중하다가 테스트가 힘든 코드가 작성됐다.
<ul>
<li>전 회사 코드가 이랬는데, 왜 그렇게 됐는지 이제 이해가 된다.</li>
<li>비디오 채팅 같이 테스트가 힘든 부분도 테스트할 방법을 찾고 싶다.</li>
</ul>
</li>
<li>작은 서비스라도 구현할 기능은 산더미다
<ul>
<li>인증 이메일 다시 보내기와 약관 동의 등 빠뜨린 기능이 쌓여있다.</li>
<li>교재 안에 토픽, 강의, 연습이 있는데 각각의 읽기, 생성, 수정, 삭제 기능 등, 반복되는 구현이 많았다.
<ul>
<li>반복되는 CRUD 기능 구현을 자동화할 방법이 필요하다.</li>
</ul>
</li>
</ul>
</li>
<li>가장 중요한 건 서비스에 대한 애정과 책임감인 것 같다.
<ul>
<li>이거 없으면 못하겠다… 디자인도 예쁘게 안 나옴.</li>
</ul>
</li>
</ul>yongjin0802WebRTC를 활용한 실시간 1:1 강의를 지원하는 튜터링 플랫폼