<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>dev-hann</title>
        <link>https://velog.io/</link>
        <description>通 하는 개발자 Hann 입니다.</description>
        <lastBuildDate>Thu, 02 Apr 2026 14:17:35 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <image>
            <title>dev-hann</title>
            <url>https://velog.velcdn.com/images/dev-hann/profile/d6e399a1-db4e-47f3-a046-1c18cdc9de10/image.png</url>
            <link>https://velog.io/</link>
        </image>
        <copyright>Copyright (C) 2019. dev-hann. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/dev-hann" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[프론트엔드 리더가 되었다]]></title>
            <link>https://velog.io/@dev-hann/%ED%94%84%EB%A1%A0%ED%8A%B8%EC%97%94%EB%93%9C-%EB%A6%AC%EB%8D%94%EA%B0%80-%EB%90%98%EC%97%88%EB%8B%A4</link>
            <guid>https://velog.io/@dev-hann/%ED%94%84%EB%A1%A0%ED%8A%B8%EC%97%94%EB%93%9C-%EB%A6%AC%EB%8D%94%EA%B0%80-%EB%90%98%EC%97%88%EB%8B%A4</guid>
            <pubDate>Thu, 02 Apr 2026 14:17:35 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>Flutter만 하던 개발자가
갑자기 웹 맡고, 배포 갈아엎고,
정신 차려보니 리더가 되어 있었다.</p>
</blockquote>
<h2 id="트럭에-치여-이세계로-간-이야기-곧-시작됩니다">트럭에 치여 이세계로 간 이야기 곧 시작됩니다.</h2>
<h2 id="🧩-나는">🧩 나는..</h2>
<p>나는 6년차 개발자다.</p>
<p>Flutter로 시작했지만 이것저것 관심이 많았다.</p>
<ul>
<li>서버도 좀 해보고</li>
<li>리눅스도 사용하고</li>
<li>삽질도 기가막히게 하고</li>
</ul>
<p>진짜 잡부중의 잡부였다</p>
<p>한때는 매킨토시 써보겠다고
OS 설치만 100번 넘게 해본 적도 있고,</p>
<p>TUI 좋아해서 프레임워크도 만들어보고 (<a href="https://github.com/dev-hann/radartui">Radartui</a> 만들기 글도 정리해야하는데..),
Vim 좋다고 여기저기 전파하고 다니고...</p>
<p>정리하면 이거다.</p>
<blockquote>
<p>“필요하면 뭐든 하는 개발자”</p>
</blockquote>
<p>근데 이게 나중에 이렇게 연결될 줄은 몰랐다.</p>
<hr>
<h2 id="💣-사건은-갑자기-터졌다">💣 사건은 갑자기 터졌다</h2>
<p>2025년 중순,</p>
<p>웹 개발자 한 분이 퇴사하셨다.</p>
<p>그리고 자연스럽게
내가 웹 개발에 투입됐다.</p>
<p>문제는 이거였다.</p>
<blockquote>
<p>“나 웹 제대로 해본 적 없는데?”</p>
</blockquote>
<p>근데 선택지는 없었다.</p>
<p>해야 했다. 오히려 좋았다. </p>
<hr>
<h2 id="👀-프로젝트를-처음-봤을-때">👀 프로젝트를 처음 봤을 때</h2>
<p>딱 이 생각 들었다.
<img src="https://velog.velcdn.com/images/yoehwan/post/1f140e52-b369-454e-a86a-043ed512a241/image.png" alt=""></p>
<blockquote>
<p>“좇댔다. 이게 움직인다고?”</p>
</blockquote>
<p>기능은 돌아가는데 구조가 너무 위험했다. </p>
<ul>
<li>확장 고려 X</li>
<li>구조 정리 X</li>
<li>규칙 없음</li>
</ul>
<p>이 상태에서 인원 늘어나면 무조건 터진다.</p>
<p>그래서 건드리기 시작했다.</p>
<hr>
<h2 id="🔥-건드리면-터지고-고치면-또-터지고">🔥 건드리면 터지고, 고치면 또 터지고</h2>
<ul>
<li>아키텍처 다시 잡고,</li>
<li>컨벤션 잡고,</li>
<li>개발 방식 정리하고
<img src="https://velog.velcdn.com/images/yoehwan/post/8c63d691-06d6-4d25-84d8-04ab9b28e208/image.png" alt=""></li>
</ul>
<p>이 과정에서 진짜 많이 터졌다.</p>
<p>특히 Next.js에서 쿠키 기반 인증.</p>
<blockquote>
<p>“이건 왜 안 되지?”</p>
</blockquote>
<p>배포 테스트만 진짜 100번은 가까이 했다..</p>
<p>아니 근데 배포도 문제였다.</p>
<hr>
<h2 id="🚨-배포-8분-→-2분-만든-이유-이건-진짜-빡침">🚨 배포 8분 → 2분 만든 이유 (이건 진짜 빡침)</h2>
<p>Amplify로 배포했는데</p>
<blockquote>
<p>한 번에 8분</p>
</blockquote>
<p>처음엔 참았다.</p>
<p>근데 개발하면서 계속 배포해야 하는데 이건 진짜 아니다 싶었다.
<img src="https://velog.velcdn.com/images/yoehwan/post/f54b64c6-aceb-4a29-ba88-ad813fbce0b4/image.png" alt=""></p>
<p>그래서 결론:</p>
<blockquote>
<p>“이건 못 쓴다”</p>
</blockquote>
<p>그냥 갈아엎었다.</p>
<p>OpenNext 도입해서 배포 시간을 2분대로 줄였다.</p>
<p>이게 왜 중요하냐면,</p>
<p>👉 팀 전체 개발 속도가 달라진다</p>
<p>이때 느꼈다.</p>
<blockquote>
<p>“아, 내가 바꾸면 진짜 바뀌는구나”</p>
</blockquote>
<hr>
<h2 id="👥-어느-순간-팀이-나를-보고-있었다">👥 어느 순간, 팀이 나를 보고 있었다</h2>
<p>하나씩 정리하다 보니까 프론트 개발자가 6명이 됐다.</p>
<p>이때부터 중요한 변화가 생긴다.</p>
<ul>
<li>코드 스타일</li>
<li>폴더 구조</li>
<li>기술 선택</li>
</ul>
<p>이걸 누가 정하냐?</p>
<p>누군가는 해야 한다. 그리고 그걸 내가 하고 있었다.</p>
<p>의도한 건 아니었다. 그냥 계속 하다 보니까 그 자리에 서 있었다.</p>
<hr>
<h2 id="🧠-그리고-결국-리더가-되었다">🧠 그리고 결국 리더가 되었다</h2>
<p>2026년 초,</p>
<p>정식으로 프론트엔드 리더가 되었다.</p>
<hr>
<h2 id="🤯-리더가-되니까-더-어려워졌다">🤯 리더가 되니까 더 어려워졌다</h2>
<p>이건 예상 못 했다.</p>
<p>코딩보다
이게 더 어렵다.</p>
<ul>
<li>이 방향 맞나?</li>
<li>팀원들이 납득할까?</li>
<li>이 선택 나중에 후회 안 할까?</li>
</ul>
<p>예전엔</p>
<blockquote>
<p>“코드만 잘 짜면 된다”</p>
</blockquote>
<p>였는데, 지금은</p>
<blockquote>
<p>“팀이 잘 가야 된다”</p>
</blockquote>
<p>가 됐다. </p>
<p>( 진짜 잘못 판단하고 결정을 내려버리면 겉잡을수없게, 마을에 독을 푼것처럼 퍼져나가버린다..)</p>
<hr>
<h2 id="🎯-지금-목표-이건-진짜">🎯 지금 목표 (이건 진짜)</h2>
<p>솔직히 아직 부족하다. 근데 하나는 확실하다.</p>
<blockquote>
<p>이 프로젝트, 제대로 끝내고 싶다</p>
</blockquote>
<p>올해는 그냥</p>
<blockquote>
<p>“죽었다 생각하고 한다”</p>
</blockquote>
<p>이 모드로 간다.</p>
<hr>
<h2 id="📌-앞으로-풀-이야기">📌 앞으로 풀 이야기</h2>
<p>이번 글은 시작이다.</p>
<p>앞으로 이 시리즈로 계속 쓸 생각이다.</p>
<h3 id="📚-프론트-리더-성장기-시리즈">📚 [프론트 리더 성장기 시리즈]</h3>
<ol>
<li><p><strong>FSD 아키텍처 도입기</strong>
→ 왜 구조를 갈아엎었는지 (그리고 욕 먹은 썰)</p>
</li>
<li><p><strong>Next.js 쿠키 인증 삽질기</strong>
→ “왜 안 되지?”를 수십 번 반복한 이유</p>
</li>
<li><p><strong>OpenNext로 배포 시간 8분 → 2분 만든 과정</strong>
→ 빡쳐서 인프라 바꾼 이야기</p>
</li>
<li><p><strong>디자인 시스템 구축기</strong>
→ 이건 진짜 팀 개발의 핵심이었다</p>
</li>
<li><p><strong>통화 시스템 개발기 (VoIP)</strong>
→ 이건… 할 말 많다 (제일 재밌을 예정)</p>
</li>
</ol>
<hr>
<h2 id="🧾-마무리">🧾 마무리</h2>
<p>리더가 됐다고 해서
갑자기 잘하게 된 건 아니다.</p>
<p>그냥</p>
<ul>
<li>더 많이 고민하고</li>
<li>더 많이 책임질 뿐이다</li>
</ul>
<p>근데 하나는 확실하다.</p>
<blockquote>
<p>이 과정, 생각보다 재밌다</p>
</blockquote>
<hr>
]]></description>
        </item>
        <item>
            <title><![CDATA[태크 리더가 되었다]]></title>
            <link>https://velog.io/@dev-hann/become-leader</link>
            <guid>https://velog.io/@dev-hann/become-leader</guid>
            <pubDate>Sun, 19 Oct 2025 04:55:27 GMT</pubDate>
            <description><![CDATA[<p><strong>🚨 본 글은 사실을 기반으로 한 약간의 MSG가 첨가된 픽션임을 알려드립니다.</strong></p>
<hr>
<h2 id="금요일-오전">금요일 오전</h2>
<p>개발팀 주간회의가 있는 날.</p>
<pre><code class="language-txt">오늘 회의 마지막으로 말씀드릴 게 있는데,
&lt;중략&gt;
A 님이 앞으로 개발 담당 기술 리더를 해주시기로 했습니다.</code></pre>
<p>나보다 2년쯤 늦게 입사하신, 경력도 비슷한 서버 개발자 분이 개발 리더로 임명되었다.
기분이 뭔가 떨떠름했다.</p>
<p>회의를 마치고 아무렇지 않은 척 키보드를 두드리며 생각했다.
나름 회사에 기여도 많이 했고, 열심히 했다고 생각했는데...
경력이나 실력에서도 크게 밀리지 않았을 텐데,
뭐가 부족했던 걸까?</p>
<p>곰곰이 생각하다 보니 괜히 마음이 불편해지기 시작했다.
그래서 일단 생각을 멈추고, 다시 일에 몰두했다.</p>
<p>퇴근 후 집에서 맥주 한 캔을 따며 다시 생각이 이어졌다.
드라마에서나 보던 ‘승진 실패한 만년 과장’의 모습이 떠올랐다.
전엔 전혀 공감되지 않던 장면이었는데,
이제는 그 감정이 조금은 이해됐다.</p>
<p>‘나는 감투 같은 건 신경 안 쓰는 줄 알았는데… 아니었나 보다.’
생각이 꼬리를 물자 또다시 마음이 불편해졌다.
그래서 이번엔 그냥 멈췄다.
다른 일에 집중하면서 흘려보냈다.</p>
<hr>
<h2 id="토요일-오후">토요일 오후</h2>
<p>혼자 회사로 나왔다.
지금 진행 중인 프로젝트의 기틀을 잡아두기 위해서였다.
그래야 평일에 팀원들과 각자 역할을 이어붙이며 병목 없이 진행할 수 있으니까.</p>
<p>물론 평일에 해도 되는 일이지만,
솔직히 이 작업이 꽤 재미있었다.</p>
<p>어찌 보면 ‘회사를 위한 일’이라기보다
‘내 성장을 위한 일’이었다.</p>
<p>일을 마무리하고 뿌듯한 마음으로 집에 돌아오는 길,
어제의 그 기분이 다시 떠올랐다.
이번엔 조금 다르게 느껴졌다.</p>
<p>조금은 담담했고,
조금은 이성적으로 생각할 수 있었다.</p>
<hr>
<h2 id="일요일-오전">일요일 오전</h2>
<p>주일 설교 중 “원망하지 말고, 좌절하지 말라”는 말씀이 있었다.
그 말씀이 이상하리만큼 내 마음속 깊은 곳을 건드렸다.
그제서야 깨달았다.</p>
<p>나는 그동안 결과로만 나를 증명하려 했던 것 같다.
누가 리더가 되고, 누가 인정을 받는지가 중요한 줄 알았지만,
결국 더 중요한 건 그 자리에 있지 않아도 성장할 수 있는 마음의 태도였다.</p>
<p>그리고 문득, 나에게 진짜 중요한 게 무엇인지 떠올랐다.
직책이 아니라, 개발 그 자체를 즐기는 마음이었다.</p>
<p>새로운 코드를 짜고, 문제를 해결하고,
팀의 기술이 한 단계 나아가는 그 순간들.
그게 내가 이 일을 좋아하는 이유였다.</p>
<p>그래서 이제는 이렇게 생각한다.
리더의 자리에 오르는 것보다
리더의 마음을 갖는 사람이 되고 싶다고.</p>
<p>그리고 무엇보다,
외부의 평가나 타인의 결정에 흔들리지 않고
개발을 좋아하는 마음만큼은 잃지 않으려 한다.</p>
<p>그 마음 하나면,
앞으로 어떤 자리에서든
나는 계속 개발자로 있을 수 있을 것 같다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[바이브 코딩과 결혼 💍]]></title>
            <link>https://velog.io/@dev-hann/%EB%B0%94%EC%9D%B4%EB%B8%8C-%EC%BD%94%EB%94%A9%EA%B3%BC-%EA%B2%B0%ED%98%BC</link>
            <guid>https://velog.io/@dev-hann/%EB%B0%94%EC%9D%B4%EB%B8%8C-%EC%BD%94%EB%94%A9%EA%B3%BC-%EA%B2%B0%ED%98%BC</guid>
            <pubDate>Wed, 16 Jul 2025 06:29:23 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/dev-hann/post/ca24e195-7939-44ea-a47d-a7880ff047fa/image.png" alt=""></p>
<p>곧 결혼을 앞두고, 단순히 종이 청첩장을 넘어서</p>
<p>우리만의 디지털 청첩장을 만들어보고 싶었다.</p>
<p>기존 청첩장 서비스들도 충분히 훌륭했지만,</p>
<p>결혼이라는 특별한 이벤트를 직접 기획하고 개발해보면</p>
<p>더 오랫동안 기억에 남을 것 같았다. </p>
<p>( 사실은 개발자니까 돈 아끼려고.. 근데 한번 더한다면 그냥 돈주고 살것같다..ㅎ) </p>
<p>해당 레포는 👉 <a href="http://github.com/dev-hann/wedding">github.com/dev-hann/wedding</a></p>
<hr>
<h2 id="🛠-사용-기술">🛠 사용 기술</h2>
<ul>
<li>ChatGPT - 사실상 구원투수 겸 메인 개발자</li>
<li>Next.js – SSR 기반 웹 프레임워크</li>
<li>TypeScript – 타입 안정성과 유지보수성 확보</li>
<li>Tailwind CSS – 빠른 스타일링</li>
<li>Vercel – 배포 및 프리뷰 공유</li>
<li>Prettier + ESLint – 코드 스타일 통일</li>
<li>카카오맵 SDK – 위치 안내</li>
</ul>
<hr>
<h2 id="1-구조-설계-및-폴더-구성">1. 구조 설계 및 폴더 구성</h2>
<pre><code>📁 public/        정적 이미지들
📁 src/
┣ 📁 components/ Header, Section 등 공용 컴포넌트
┣ 📁 constants/  텍스트, 계좌번호 등 정적 데이터
┣ 📁 pages/      Next.js 라우트 페이지들
┗ 📁 styles/     Tailwind config 및 글로벌 스타일</code></pre><p>전체 구조는 컴포넌트 단위로 쪼개서 유지보수가 쉽게 구성했다.</p>
<p>특히 constants 폴더에 텍스트를 모아두어 한글 수정도 편하게 했고</p>
<p>향후 다른 커플이 이 구조를 복사해서 쓰기도 쉽게 설계했다.</p>
<hr>
<h2 id="2-반응형-고려와-접근성">2. 반응형 고려와 접근성</h2>
<p>모바일 퍼스트로 작업했고,</p>
<p>헤더부터 카드 섹션, 맵까지 모두 세로 스크롤 기반으로 배치했다.</p>
<p>예시 코드:</p>
<pre><code class="language-jsx">&lt;section className=&quot;flex flex-col items-center text-center&quot;&gt;
  &lt;h1 className=&quot;text-3xl font-bold mt-10&quot;&gt;저희 결혼합니다&lt;/h1&gt;
  &lt;p className=&quot;text-lg mt-2&quot;&gt;2025년 10월 25일 토요일 오후 2시&lt;/p&gt;
&lt;/section&gt;;</code></pre>
<p>Tailwind 유틸리티 클래스들을 조합해 빠르게 스타일링</p>
<ul>
<li>스크롤 내릴 때 자연스럽게 읽히도록 시각 순서도 고려</li>
</ul>
<hr>
<h2 id="3-공유를-위한-og-태그-설정">3. 공유를 위한 OG 태그 설정</h2>
<p>카카오톡, 문자 등으로 링크를 보냈을 때</p>
<p>썸네일과 제목이 깔끔하게 뜨도록 설정했다.</p>
<p>예시 코드:</p>
<pre><code class="language-jsx">export const metadata = {
  title: &quot;여환 ❤️ 지영 결혼합니다&quot;,
  description: &quot;2025년 10월 25일 토요일 오후 2시, 많은 축복 부탁드립니다.&quot;,
  openGraph: {
    title: &quot;여환 ❤️ 지영 결혼합니다&quot;,
    description: &quot;2025년 10월 25일 토요일 오후 2시, 많은 축복 부탁드립니다.&quot;,
    images: [&quot;/og-image.png&quot;],
  },
};</code></pre>
<hr>
<h2 id="4-배포는-vercel로-깔끔하게">4. 배포는 Vercel로 깔끔하게</h2>
<p>Vercel CLI로 배포:</p>
<pre><code class="language-bash">vercel login
vercel --prod</code></pre>
<ul>
<li>커스텀 도메인도 연결해서 배포</li>
<li>프리뷰 링크 기능 덕분에 지인들 피드백도 쉽게 받을 수 있었음</li>
</ul>
<hr>
<h2 id="5-인상-깊었던-부분">5. 인상 깊었던 부분</h2>
<ul>
<li>Tailwind로 빠르게 UI 구성 가능했던 점</li>
<li>src/constants.ts에 정보들을 모아두니 구조적으로 좋았음</li>
<li>카카오맵 iframe 삽입이 조금 귀찮았지만, 최소한의 기능만 넣어 심플하게 유지</li>
</ul>
<hr>
<h2 id="6-회고-✍️">6. 회고 ✍️</h2>
<p>이번 청첩장 프로젝트는 단순한 웹페이지 제작을 넘어</p>
<p>개발자로서의 결혼 준비라는 느낌이었다.</p>
<p>사람에게 보여주기 위한 프로젝트였기에</p>
<p>디자인, 문구, 동작 하나하나에 더 신경을 쓰게 되었고</p>
<p>결과적으로 매우 만족스러운 결과물이 나왔다.</p>
<p>앞으로 이 프로젝트를 기반으로</p>
<p>다른 예비부부도 쉽게 커스터마이징해서 쓸 수 있는 템플릿으로 확장해보면 좋을 것 같다.</p>
<hr>
<p>📂 GitHub: <a href="http://github.com/dev-hann/wedding">github.com/dev-hann/wedding</a></p>
<p>✨ 배포 URL은 비공개 or 지인 한정으로 공유 중</p>
<h1 id="7-블로그-주인장">7. 블로그 주인장</h1>
<p><img src="https://velog.velcdn.com/images/dev-hann/post/52a4217e-b5f4-4bb9-9b0a-f97cf47e1509/image.png" alt=""></p>
<p>지피티 혼자서 만들고 배포하고 후기까지 쓰는 세상이 왔습니다 여러분.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[여보세요? (Flutter 로 통화 기능 구축기) - 1]]></title>
            <link>https://velog.io/@dev-hann/%EC%97%AC%EB%B3%B4%EC%84%B8%EC%9A%94-Flutter-%EB%A1%9C-%ED%86%B5%ED%99%94-%EA%B8%B0%EB%8A%A5-%EA%B5%AC%EC%B6%95%EA%B8%B0-1</link>
            <guid>https://velog.io/@dev-hann/%EC%97%AC%EB%B3%B4%EC%84%B8%EC%9A%94-Flutter-%EB%A1%9C-%ED%86%B5%ED%99%94-%EA%B8%B0%EB%8A%A5-%EA%B5%AC%EC%B6%95%EA%B8%B0-1</guid>
            <pubDate>Wed, 16 Jul 2025 05:55:10 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/dev-hann/post/a9c4ef54-4237-419c-ac0b-d870ed86c547/image.png" alt=""></p>
<h2 id="intro"><em>Intro</em></h2>
<p>우리 회사 서비스의 핵심기능인 통화 기능을 드.디.어 리팩토링 할 시간이 주어졌다.</p>
<p>기존에 있던 코드가 오래 됐기도 했고, 기능 개선 &amp; 오류 대응이 필요했던 상황이었던 차,</p>
<p>겸사겸사 시간을 내어 진행하게 되었다. 통화 기능을 처음부터 만들어본건 이번이 처음이라</p>
<p>부담감이 있었지만, ‘이 또한 지나가리’ 란 생각으로 이악물고 극복한 이야기를 써보려한다.</p>
<h2 id="어디로-가야하죠"><em>어디로 가야하죠?</em></h2>
<p><img src="https://velog.velcdn.com/images/dev-hann/post/68bdcc8c-e7de-497e-8f22-1c53cb4298ba/image.png" alt=""></p>
<p> 통화 기능 리팩토링이 들어가기 초기 단계에 개발팀 쌤들과 회의..</p>
<p>시작부터 갈림길을 마주했다. </p>
<ul>
<li>기존 프로토콜에서 가능한선까지 유지보수를 하자!</li>
<li>리소스가 투입 될거면, 조금 더 모던한 구조로 재구성 하자!</li>
</ul>
<p>치열한 공방전... </p>
<p>서비스의 핵심 기능이다보니, 보수적 의견도 있었고, 진보적인 의견도 있었다.</p>
<p>두가지 의견 모두 나쁜 의견은 없었다. 어떤 선택이 최소한의 리소스로 최선의 선택인지를 판단했어야 했다. </p>
<p>여러분들이 위 상황이라면 어떤 판단을 내렸을지 한번 같이 고민해보는것도 좋을것 같다. </p>
<h2 id="시작은-미약하나-그-끝은-창대하리라"><em>시작은 미약하나 그 끝은 창대하리라</em></h2>
<pre><code>콜롬버스는 신대륙을 발견하기 위해 여행을 떠나기로 마음을 먹었다.</code></pre><p><img src="https://velog.velcdn.com/images/dev-hann/post/b92c374f-b307-4fb2-9b9c-a97bfde8c25d/image.png" alt=""></p>
<p>시작은 가벼운 마음으로 시작되었다. </p>
<p>그저 물 흐르듯이 스무쓰(th) 기존의 통화 코드부터 파악하기 시작했다. </p>
<p><img src="https://velog.velcdn.com/images/dev-hann/post/bf76f085-ff1c-48f8-a886-bebaa651daac/image.png" alt=""></p>
<p>++;</p>
<p>목적을 알 수 없는 코드 &amp; 약타입으로 작성되어 파라미터로 인해 타입 추정 불가, </p>
<p>메서드 안에 메서드( or 프로바이더) 안에 메서드( or 프로바이더) 호출... </p>
<p>타고 가다보면 길을 잃기 쉽상이었다..</p>
<p>누굴 욕하고 탓하기보단, 이 난관을 어떻게 극복해나갈지 걱정이 앞섰다. </p>
<p>한참을 보다 내린 결론은.. ‘이거 못 살린다.’ 였다. </p>
<p>주어진 시간과 리소스안에서 내 역량으론 리팩토링은 불가능하다고 판단되었다.  </p>
<p>서비스 입장에서 타협할건 무조건 타협하는 입장인데, 이건 타협이 안됐다.</p>
<p>결국 아쉬움을 뒤로한체, 처음부터 다시 작성했다. </p>
<p><img src="https://velog.velcdn.com/images/dev-hann/post/66654662-3ed4-4b22-8cc5-7a8920a80055/image.png" alt=""></p>
<h2 id="신호-받기"><em>신호 받기</em></h2>
<p>일단의 기존 통화 신호를 받는부분부터 분기되어있었다. ( aos 는 fcm 으로 ios 는 apns 로 ) 그래서 이부분을 일단 fcm 으로 통합해줌으로 코드의 통일성을 지켜주었다.</p>
<p>(이부분이 추후에 엄청난 재앙을 가져올줄은 꿈에도 몰랐다..)</p>
<h2 id="통화-알림-띄우기"><em>통화 알림 띄우기</em></h2>
<p>이제 fcm 으로 들어온 통화 신호를 각 플랫폼에 맞게 알림을 띄워줘야했다. </p>
<p>이부분도 기존에 ios 에서는 callkit 을 사용했고, aos 는 LocalNotification 을 이용해 처리를 했다.</p>
<p>native 를 작성해야할것같은 쌔한 느낌이 엄습해오는 가운데..</p>
<p> <img src="https://velog.velcdn.com/images/dev-hann/post/f266db19-03c0-40c2-8d96-99da1bf3f063/image.png" alt=""></p>
<p>구원 투수 등장 (사실상  두번째 재앙 등장)</p>
<p><a href="https://pub.dev/packages/flutter_callkit_incoming">flutter_callkit_incoming | Flutter package</a></p>
<p>전화 UI 를 각 네이티브 별로 잘 작성해놓은 라이브러리가 있었다.</p>
<p>하지만 그대로 사용하기엔 우리 서비스와 조금 안맞는곳이 있어, 부분적으로 손보긴 했지만, </p>
<p>그래도 바퀴를 통째로 만드는 수고는 덜게되었다. </p>
<p>(라고 하지만 이 라이브러리드 프로모션 서비스에서 이용하기에는 빈틈이 너무 많았다…)</p>
<h2 id="전화-받기"><em>전화 받기</em></h2>
<p>기존의 통화 프로토콜은 WebRTC방식을 이용해 서비스를 구축하고싶었지만,
기존에 레거시에선 외부 RTC 서비스를 이용하고있어서.. 아쉽게도 코드 몇줄로 호다닥 넘어가버렸다.</p>
<p>(내 궁극의 목적은 이부분을 직접 구현하는것이다..)</p>
<h2 id="신경-꺼줄래"><em>신경 꺼줄래</em></h2>
<p><img src="https://velog.velcdn.com/images/dev-hann/post/4f312726-6b2c-4944-a6b5-ea192424bd7f/image.png" alt=""></p>
<p>기존의 통화 프로토콜에는 서버쪽에서 통화방 &amp; 각 프론트의 상태를 너무나도 깊히 관여하고있었다.</p>
<p>아마 어떤 히스토리가 있었던것 같은데 ( 이유 없는 코드는 없으니까..),</p>
<p>그 덕분에 프론트에서도 목적을 알 수 없는 코드들이 덕지 덕지 붙어 있었고, </p>
<p>종종 알 수 없는 상황들이 연출되기도 했다. </p>
<p>( 원하지 않는 상황에서 통화가 종료되거나, 통화가 제대로 연결되지 않는 불상사가… )</p>
<p>그래서 이번기회에 서버 개발자쌤과 회의끝에 서버쪽에서의 통화방 개입을 최소화 하기로 결정했다.</p>
<p>( 갑작스런 상황에서 통화 참여자의 연결이 끊어진다던가, </p>
<p>연결이 불안정 한다던가 하는 상황들 모두 프론트쪽에서 처리하고, </p>
<p>프론트에서 판단 못하는 상황만 서버쪽에서 처리 해주기로 했다 )</p>
<h2 id="로그-남기기"><em>로그 남기기</em></h2>
<p> 당연한 얘기지만, 개발할때 개발자들은 시나리오대로만 테스트 한다. 온실속 화초처럼..
그리고 개발 완료후 테스트를 거치는데, 진짜 상상도 못할 시나리오로 기괴한 테스트가 시행된다.</p>
<p>( 같이 일하시는 분중에 한분 계신데, 그분 별명이 ‘파괴자’ 이다. 거의 노코드 해커 같은 느낌. )</p>
<p>테스트후, 자식같은 앱이 처참히 발리고 돌아오는 모습을 보면, 뭔가 마음이 아프다.ㅎ </p>
<p>위 단계를 몇번 거치고 어느정도 번듯한 앱이 되면 이제 실제 배포를 하게 된다. </p>
<p>배포 환경은 정말 전쟁터다. 예측 불가능한 수 많은 시나리오들이 존재한다.</p>
<p>물론 개발 단계에서 이 모든걸 대처하면 좋겟지만, 사실상 불가능한일이다. </p>
<p>그래서 이런 불상사를 대비하기위해 로그를 남기는데, </p>
<h2 id="자잘한-애러들"><em>자잘한 애러들</em></h2>
<p><img src="https://velog.velcdn.com/images/dev-hann/post/63de8f91-2296-452d-8ff0-8aca35f75432/image.png" alt=""></p>
<p>자잘한 애러들이라고 하기엔, 너무나도 핵심 기능에 영향을 주는것들이 많았다.</p>
<p>aos 에서는 스피커 모드로 고정이 된다던가, </p>
<p>ios 에서는 통화중 알람이 온후 audio session 에 문제가 생긴다던가,</p>
<p>FSI( Full Screen Intent ) 가 디버그땐 잘 되다가, 빌드후 배포시 안뜬다던가..</p>
<p>진짜 자잘한 애러가 너…무많아서 원인 파악만 하루 꼬박 걸린게 대부분이다.</p>
<p>이런 부분에서 생기는 어쩔 수 없는 덕지덕지 붙은 코드에는 꼭 주석을 남겨주었다. </p>
<p>미래의 나를 위해…</p>
<h2 id="결론"><em>결론</em></h2>
<p> 배포를 한후, 통화 기능 브랜치의 첫 커밋을 확인하니 약 6개월 전으로 나왔다.</p>
<p>(물론 6개월동안 다른 기능 추가 &amp; 자잘한 UI 수정 으로 배포가 이루어졌지만..)</p>
<p>정말 인고의 시간을 통해 많은 것들을 배웠다. </p>
<p>기존 코드 &amp; 통화 구조를 개선하면서 기존에 존재했던 여러 애러들을 맞닥드렸고,</p>
<p>이 상황에서 누구의 잘잘못을 따지며, 무조건 적으로 누구를 고쳐야 한다는것보다, </p>
<p>그 상황에서의 최선의 답을 도출해 내는것이 개발자가 해야하는것이 아닐까 생각한다.</p>
<p> 또 이번 리팩토링중 너무나도 많은 히스토리에 의해 작성된 주석이 없는 코드들을 보며,</p>
<p>다시한번 코드의 목적성을 뚜렷하게 나타내거나, 그 목적을 나타낼 수 없는 코드일경우 </p>
<p><strong>주석을 남겨야 한다는것</strong>을 너무너무 절실하게 느꼈다. </p>
<p>2편에서 폭풍재앙이 밀려온다..</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Retry 처리하기]]></title>
            <link>https://velog.io/@dev-hann/Retry-%EC%B2%98%EB%A6%AC%ED%95%98%EA%B8%B0</link>
            <guid>https://velog.io/@dev-hann/Retry-%EC%B2%98%EB%A6%AC%ED%95%98%EA%B8%B0</guid>
            <pubDate>Sat, 10 Aug 2024 05:46:53 GMT</pubDate>
            <description><![CDATA[<h2 id="인트로"><em>인트로</em></h2>
<p>개발자에게있어 애러를 마주하는 자세는 굉장히 중요한것같다.</p>
<p>개린이 시절에는 이녀석과 마주하기를 굉장히 꺼려했다.</p>
<p>사실 정말 개린이 시절에는 애러라는 녀석에 대해 고려할 여력이 없었다.</p>
<p>애러를 핸들링 한다는것 자체가 이미 기본적인 기능 구현만으론 숨쉴만 한 상황이고,</p>
<p>이제 한숨 돌리나 싶어서 다른곳에 눈을 돌리다 보니 
<img src="https://velog.velcdn.com/images/yoehwan/post/7732d957-38f7-4c97-ab5d-6ccf843caea9/image.png" alt=""></p>
<p>어느센가 저쪽에 산더미처럼 쌓여있는 애러들의 눈초리를 느낄 수 있다.</p>
<p>이때 선택할 수 있는건 2가지뿐, 프로젝트를 갈아 엎던가, 아니면 덮던가.. </p>
<p><a href="https://www.notion.so/Handling-Error-9319b4dea3f5471bb27249b035e4027f?pvs=21">애러를 처리하기위한 고군분투 이야기</a> 는 여기있으니 한번 참고해보시고..</p>
<p>애러 처리에 관한 방법론중 하나인 ‘재시도’ 에 관한 생각을 얼마전 겪은 경험을 통해</p>
<p>글로 남기고자한다.</p>
<h2 id="🪲애러-발견"><em>🪲애러 발견!</em></h2>
<p>앱 개발자인 hann씨는 오늘도 열심히 기능 개발에 열을 올렸다. </p>
<p>이번에 구현할 기능은 Push Notification 기능! 두둥등장.
<img src="https://velog.velcdn.com/images/yoehwan/post/b5e8cfc2-3ecb-456b-b903-ddecc73767f7/image.png" alt=""></p>
<p>&lt; 푸바오 성은 푸씨라고 한다 &gt;</p>
<p>간단한 기능 구현에 대해 설명 하자면, Firebase Cloud messaging (FCM) 을 이용할 것이며,</p>
<p>FCM instance 를 생성 및 initialize 를 한후, instance 를 통해 token 을 발급 요청한다.</p>
<p>그리고 이 발급 받은 token 을 통해 알림을 받을 수 있다. </p>
<p>기능 구현 끗! 퇴근! .. 이라면 좋곘지만..  </p>
<p>역시나 발급중 애러 발생!!.. </p>
<p>한참을 원인 파악하던 hann씨의 구원투수 <a href="https://stackoverflow.com/questions/77089496/flutter-apns-token-has-not-been-set-yet-please-ensure-the-apns-token-is-avail">스택오버플로우</a> 왈..
<img src="https://velog.velcdn.com/images/yoehwan/post/b033fdbc-7ed5-43ae-ab14-ba0ddbf5873f/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/yoehwan/post/585461b0-a5e2-464a-81f2-1566645dca00/image.png" alt=""></p>
<h2 id="그래서-어쩔건데"><em>그래서 어쩔건데</em></h2>
<p>정리하자면.. </p>
<p>이 버그는 인스턴스 생성의 동기 &amp; 비동기 쪽에 문제가 있어, 인스턴스 생성후 delay 1초를 주었지만..  </p>
<p>여전히 버그는 간헐적으로 발생했다. </p>
<p>그래서 선택한 방법은 ‘재시도’. 1초가 해당 기기의 성능 &amp; 네트워크 환경에 따라 충분하지 않을수 있기에 </p>
<p>애러가 발생했을때 delay 후 재시도를 하는것으로 덧칠을 해주었다.</p>
<p>그럼 재시도는 어떻게 할것인가!? ‘재귀함수’  혹은 ‘반복’ 문을 이용해서 처리하면 될것인데, </p>
<p>재귀함수는 개인적으로는 가독성 &amp; 메모리 차원에서 별로 사용하고 싶지 않았고..</p>
<p>반복문을 어떻게 쓰면 좋을까 고민하던차, 옆에 날 유심히 보시던 서버 개발자분이 씨-익 웃으시면서</p>
<p>이렇게 써보시죠? 라고 재미난 코드를 하나 알려주셨다.</p>
<p><img src="https://velog.velcdn.com/images/yoehwan/post/38591d0a-e874-4ae7-bbac-8e252c8eea30/image.png" alt=""></p>
<h3 id="재귀함수">재귀함수</h3>
<pre><code class="language-js">String token=&quot;&quot;
int count=0;
void loadToken()async{
    count++;
    if(count&gt;10){
        break;
    }
    token = await fcmService.loadToken();
    if(token.isEmpty){
        loadToken();
    }
}</code></pre>
<p>일단은 내가 생각한 재귀함수의 방법은 이랬다. 그냥 평범한 재귀함수</p>
<h3 id="반복문">반복문</h3>
<pre><code class="language-js">String token=&quot;&quot;;
for(int index=0;index&lt;10;index++){
    try{
        token = await fcmService.token();
        if(token.isNotEmpty){
            break;
        }
    }catch(e){
        continue;
    }
}</code></pre>
<p>이건 서버 개발자 분이 알려주신 코드 ( 난 이걸 사파(邪派)식 코드라고 부른다 ) </p>
<p>처음봤을때, 정말 띠용 했다! ‘이게 뭐야!?
<img src="https://velog.velcdn.com/images/yoehwan/post/00a4b1b0-40f5-4897-ab28-d528be037843/image.png" alt=""></p>
<p>한참 살펴보고 고민끝에 서버 개발자분이 권해주신 방식을 적용해보기로했다.</p>
<h2 id="마무리"><em>마무리</em></h2>
<p> 사실 나는 나도 몰랐던 곤조(근성)가 있는것 같다. 개발을 하게되면서 생긴것인지 모르겠는데, </p>
<p>나만의 규칙을 만들고 그것을 되도록이면 지키려고 하는 버릇이 곤조가 되어버린것이다.</p>
<p><img src="https://velog.velcdn.com/images/yoehwan/post/7c10585f-c073-4b40-beb4-56dfb6c8d3bb/image.png" alt=""></p>
<p>  &lt;수많은 코드를 모여, 자아가 되어버렸다&gt;</p>
<p>이번 글은 이런 것까지 굳이 남길 필요가 있을까? 라는 생각을 할수도 있지만, </p>
<p>나에겐 한번 더 돌아볼 수 있는 기회가 된것같다.</p>
<p>어느 순간부터는 내가만든 룰에 빠져, 매번 틀에 박힌 생각없는 코드만 찍어내는 일상이었다.</p>
<p>입으로만 다른 개발자와 문제를 공유하고, 서로의 의견을 받아드림으로 </p>
<p>한층 더 성숙한 개발자가 되겠다는 소리는 하고 다니지만, 정작 그런 마인드가 없었던것 같았다.</p>
<p>이번 기회에 그런 나에게 한번의 출렁이는 파도가 온것이 아닐까 싶다.</p>
<p>코드가 나쁘다 좋다를 떠나, 그분은 나에게 의견을 주었고, 서로의 의견을 공유하며, </p>
<p>뭔지 모를 많은 반성과 깨우침을 하게되는 시간이었다. </p>
<p>시간이 지나 다시금 나의 개발일상이 틀에 갇혀버릴즘, 이때를 돌아보고, 이 글을 돌아보며, </p>
<p>그 틀을 무너트릴 수 있는 용기를 갖기를 바란다. </p>
<p>개발 일상에서 발생하는 오류에 대해 적절한 Retry 로 성장하는 개발자가 될 수 있기를..</p>
<pre><code class="language-dart">Future devLife() async {
  const tryCount = double.maxFinite;
  for (int index = 0; index &lt; tryCount; index++) {
    try {
      final isOK = await beGoodDev();
      if (isOK) {
        break;
      }
    } catch (e) {
      await Future.delayed(takeRest);
    }
  }
}</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[2023 회고록]]></title>
            <link>https://velog.io/@dev-hann/2023-%ED%9A%8C%EA%B3%A0%EB%A1%9D</link>
            <guid>https://velog.io/@dev-hann/2023-%ED%9A%8C%EA%B3%A0%EB%A1%9D</guid>
            <pubDate>Sat, 13 Jan 2024 02:42:34 GMT</pubDate>
            <description><![CDATA[<h2 id="인트로">인트로</h2>
<p>2024년 청룡의 해가 다가왔다.
개발을 시작한지도 4년차, 아직 개린이인 나에겐 하고싶은것, 해야할것들이 너무 많았다.</p>
<p>그래도 2023년은 좋았다. 지난 한해는 마침과 시작, 목표와 성취가 모두 있었다.</p>
<p>이 회고록을 통해 지나온 한해를 돌아보고 반성하며, 성장할 수 있는 시간이 됐으면 좋겠다.</p>
<h2 id="안녕-그리고-안녕">안녕, 그리고 안녕</h2>
<h3 id="대만">대만</h3>
<p><img src="https://velog.velcdn.com/images/yoehwan/post/5b31efa0-df48-4888-899c-47eed06129bf/image.png" alt=""></p>
<p><strong>&lt;종종 연락오는 친절했던 팀장님&gt;</strong></p>
<p>일단 가장 큰 변화는 6년간의 대만 생활을 마치고, 한국으로 귀국했다.</p>
<p>한국과는 반대로 대만은 여유롭다. 그 사회 속에서 나는 꽤나 고통받았다.</p>
<p>결과적으론 괜찮았지만 좋진 않았고, 좋지 않은만큼 더 노력 할 수 있었다.</p>
<p>그리고 노력한만큼 배우고 성장했다.</p>
<p>대만에서의 생활을 정리하며, 아쉬운건 없었다.</p>
<h3 id="한국">한국</h3>
<p>한국에서의 생활은 시작하며, 새로운건 없었다.</p>
<p>운이 좋았던지, 꾸준한 노력의 결과물인지, 아니면 둘다였던지.. </p>
<p>귀국전에 과분한 회사와 컨택이 되었고, 만족스러운 개발 라이프를 즐기고있다. </p>
<p>개발 == 재밌다, 회사 == 개발, 회사 == 재밌다.</p>
<p>개발은 여전히 즐거웠고, 회사일도 재미있었다.</p>
<p>같은 팀원들과 의견을 나누고, 존중하고 배려하며 <a href="https://velog.io/@dev-hann/if-else">즐거운 개발</a>을 하고있다.</p>
<p><img src="https://velog.velcdn.com/images/yoehwan/post/b90f7ae0-3f18-4f57-856b-16f11625c0a0/image.png" alt=""></p>
<p>**&lt; 배포할때는 아직도 가슴이 콩닥콩닥 거린다. &gt; **</p>
<h2 id="마치며">마치며</h2>
<p>2024 올한 해는 GED (Google Developer Expert) 를 본격적으로 도전해보고싶다.</p>
<p>일차적으로 영어 면접을 위해 회화도 공부해보고, ( 회사 지원 짱 👍 )</p>
<p>컨퍼런스나 발표같은것도 할수있으면 해보려고 올해에는 많이 노력해보고싶다.</p>
<p>아직 하고싶은것, 해야할것들이 너무 많다. 욕심부리지 말고, 차근차근</p>
<p>올 한해도 노오력 해보자.</p>
<p>2023 안녕,</p>
<p>2024 안녕.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[if - else 그리고 return ( feat. 코드리뷰 )]]></title>
            <link>https://velog.io/@dev-hann/if-else</link>
            <guid>https://velog.io/@dev-hann/if-else</guid>
            <pubDate>Sat, 09 Dec 2023 06:11:23 GMT</pubDate>
            <description><![CDATA[<h2 id="intro">Intro</h2>
<p>대만에서의 기나긴 여정을 마치고, 한국에 입국과 동시에 새 회사에 들어가게되었다.</p>
<p>(사실 대만에서 화상으로 면접을 봤고, 운이 좋게 좋은 결과를 받았다.)</p>
<p>귀국하고나서 너무 정신 없이 지냈다. </p>
<p>그리고 <strong>드.디.어.</strong> 새로 입사한 이곳에서 간단한 기능을 추가 첫 배포후,</p>
<p>개발 팀원들과 간단한 “코드 리뷰”를 진행했다.</p>
<p>사실 코드 리뷰는 처음이라 어떻게 해야할지 잘 몰랐는데, 팀원분들이 잘 도와주셨고, </p>
<p>첫 코드 리뷰를 통해 얻었던 나의 코드에 관한 고찰을 남기려고한다.</p>
<p>(사실 코드 리뷰를 엄두해두지 않은 체로, 커밋관리를 엉망으로 한 상태여서 너무 창피했다😢.)</p>
<h2 id="이게-맞나-아니-이게-틀렷나">이게 맞나?! 아니, 이게 틀렷나??</h2>
<pre><code class="language-dart">if(isLogin){
    initStudentLogic();
}else{
    initOtherLogic();
}</code></pre>
<p>원래 코드를 그대로 가져오긴 좀 그래서,  예제 코드로 느낌만 살짝 빌려왔다. </p>
<p><code>isLogin</code> 일경우, <code>initStudentLogic</code> 을 실행하고</p>
<p>아니면, <code>initOtherLogic</code> 을 실행하는 간단한 분기문이었다.</p>
<pre><code class="language-dart">if(isLogin){
    // 추가 분기 isTeacher
    if(isTeacher){
        initTeacherLogic();
        return;
    }
    initStudentLogic();
}else{
    initOtherLogic();
}</code></pre>
<p>여기서 내가 추가한건 비교적 간단한 부분인,</p>
<p><code>isLogin</code> 안쪽에서 한번 더 분기를 줘서 <code>isTeacher</code> 일 경우, <code>initTeacherLogic</code> 을 실행하고, </p>
<p><code>return</code> 으로 함수를 종료해버려 뒤에 로직을 진행하지 않는 비교적 간단한 작업이었다.</p>
<p>그렇게 행복하게 살았습니다! 끗! .. 이면 좋겠지만! 😇</p>
<h3 id="분기문에-대한-나의-자세--feat-패턴-">분기문에 대한 나의 자세 ( feat. 패턴 )</h3>
<p>잠시 쉬어가는 타임으로..( 아니 뭘 했다고 쉬어가? )</p>
<p>내가 분기문을 마주했을때 리팩토링을 진행하는 과정을 단계별로 세분화 해서 정리해봤다. </p>
<ol>
<li>분기분 발견 ( C기문 분발 <del>다 뒤졌어</del> )</li>
</ol>
<pre><code class="language-dart">String? awesomeMethod() {
  if (isOK) {
    // init OK Logic
    // ..
    // ..
    return awesomeData;
  } else {
    // !isOK 일 경우
    // logging 이나 간단한 작업
  }
}</code></pre>
<ol start="2">
<li>간단한 로직을 처리하는 부분 &amp; 우선적으로 처리해야하는 부분을 위쪽으로 올려준다.
<code>Bounce Pattern</code>  : 유효하지 않은 경우를 먼저 처리하여 &#39;핵심&#39; 부분에 집중할 수 있게 해준다.</li>
</ol>
<pre><code class="language-dart">String? awesomeMethod() {
  if (!isOK) {
    // !isOK 일 경우
    // logging 이나 간단한 작업
  } else {
    // init OK Logic
    // ..
    // ..
    return awesomeData;
  }
}</code></pre>
<ol start="3">
<li>가독성을 위해 <code>else</code> 문을 빼고 <code>return</code> 으로 함수를 종료시켜준다.</li>
</ol>
<p><code>Early Return Pattern</code> : 조건문이 만족할때 우선적으로 반환해 Depth 를 줄여 가독성을 높여준다.</p>
<pre><code class="language-dart">String? awesomeMethod() {
  if (!isOK) {
    // !isOK 일 경우
    return;
  }

// init OK Logic
// ..
// ..
  return awesomeData;
}</code></pre>
<p><img src="https://velog.velcdn.com/images/dev-hann/post/b42fec7e-cef2-4f51-9a23-6c3ac7f28ecf/image.png" alt=""></p>
<p>나 자신도 모르는 사이에 벌써 알지도 못하는 패턴을 2개나 사용하고 있었다. </p>
<p>아무튼간 위와 같은 규칙하에 리팩토링을 진행했던것 같다.</p>
<h2 id="그래서-뭐가-문제야">그래서 뭐가 문제야?</h2>
<p><img src="https://velog.velcdn.com/images/dev-hann/post/ec37406f-1c77-434c-9ede-b5a0ef0f0fb5/image.png" alt=""></p>
<p>다시 돌아와서 위 작성된 코드를 봐보자.</p>
<pre><code class="language-dart">if(isLogin){
    // 추가 분기 isTeacher
    if(isTeacher){
        initTeacherLogic();
        return;
    }
    initStudentLogic();
}else{
    initOtherLogic();
}</code></pre>
<p>내가 분기문을 마주했을때 진행한 패턴과 딱 맞아 떨어진다. 오로지 가독성만을 위한 로직.</p>
<p>하지만 전체 기획 관점에서 보면 학생과 선생은 동등한 계층이었다. </p>
<p>선생일때는 선생 로직, 학생일때는 학생 로직을 타는게 맞았다.</p>
<pre><code class="language-dart">if(isLogin){
    // 추가 분기 isTeacher
    if(isTeacher){
        initTeacherLogic();
    }else{
        initStudentLogic();
    }
}else{
    initOtherLogic();
}</code></pre>
<h3 id="의미있는-코드">의미있는 코드</h3>
<p>결과적으로 보면 작동 결과는 같다고 별거 아닌거 가지고 유난 떤다고 생각 할 수도 있다.</p>
<p>하지만 기존에는 우선적 기능구현, 후 가독성 &amp; 통일된 코드 스타일을 추구 하던 나에게는 </p>
<p>이 기회를 통해 코드 한줄 한줄에 프로젝트의 방향성과 의도를 담을 수 있다는걸 배우게 되었다.</p>
<p>이러한 마인드셋을 가지고 있다면, 내가 지향하던 ‘근거있는 코드’ ( 의미를 가진 코드 ) 에 </p>
<p>한발짝 더 가까워질 수 있지 않을까 하는 고민의 시간을 가지게 되었다.  </p>
<h2 id="마치며">마치며..</h2>
<p><img src="https://velog.velcdn.com/images/dev-hann/post/a01a7c12-ae1a-41d5-9bb5-3f8c53e9fb0b/image.gif" alt=""></p>
<p>개발자에게 있어서 코드 리뷰는 민감한 부분이며, 정말 필요한 과정인것같다.</p>
<p>내가 작성한 코드를 도마위에 올려놓고, 다같이 돌려보며 어떻다 저떻다 평가를 받으니..</p>
<p>민감할 수 밖에 없는것같다. 이 과정에서 자칫 잘못했다간 민감한 부분을 건드리고, </p>
<p>서로 감정이 상한 경우도 더러 있다고 들었다. </p>
<p>그에 비해 내 개발자 생에 첫 코드 리뷰는 너무 성공적이었고, 팀원들께 감사했다.</p>
<p>서로 배려하고 존중하며, ‘틀렸다’, ‘맞았다’ 를 판단하기보단, </p>
<p>내가 작성한 코드를 통해 서로의 의견을 ‘공유’하고 알아가는 시간이었고, </p>
<p>그를 통해 한 단계 성장 할 수 있었던 시간이었다.</p>
<p>각자의 바쁘신 와중에도 감사하게도 리뷰 시간을 내주신것에 감사하며,</p>
<p>이상 첫 코드 리뷰 후기 였습니다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Garbage Collection (번역)]]></title>
            <link>https://velog.io/@dev-hann/Dart-Garbage-Collection</link>
            <guid>https://velog.io/@dev-hann/Dart-Garbage-Collection</guid>
            <pubDate>Mon, 23 Oct 2023 08:48:11 GMT</pubDate>
            <description><![CDATA[<p>원글: <a href="https://dart.googlesource.com/sdk/+/refs/tags/3.3.0-32.0.dev/runtime/docs/gc.md">https://dart.googlesource.com/sdk/+/refs/tags/3.3.0-32.0.dev/runtime/docs/gc.md</a>
참고영상: <a href="https://www.youtube.com/watch?v=3gp2H7h0qO4">https://www.youtube.com/watch?v=3gp2H7h0qO4</a>
업데이트: 2023.10.26</p>
<h1 id="가비지-컬렉션">가비지 컬렉션</h1>
<p>Dart VM 은 2세대(two generations)로 나누어진 <code>garbage collector</code> 를 가지고 있습니다. new generation은  parallel, stop-the-world semispace <a href="https://github.com/dart-lang/sdk/blob/main/runtime/vm/heap/scavenger.h">scavenger</a> 에 의해 수집(collect)됩니다. old generation 은 concurrent-<a href="https://github.com/dart-lang/sdk/blob/main/runtime/vm/heap/marker.h">mark</a>-concurrent-<a href="https://github.com/dart-lang/sdk/blob/main/runtime/vm/heap/sweeper.h">sweep</a> 이나 concurrent-mark-parallel-<a href="https://github.com/dart-lang/sdk/blob/main/runtime/vm/heap/compactor.h">compact</a> 에 의해 수집됩니다. It supports the one-way version of <a href="https://github.com/dart-lang/sdk/blob/main/runtime/vm/heap/become.h">become</a>.</p>
<h2 id="object-representation">Object representation</h2>
<p>Object pointers는 ‘immediate objects’ 또는 ‘heap objects’를 가리킵니다. 이 두 ‘objects’는 ‘pointer’의 하위 비트에 있는 태그로 구분됩니다. Dart VM 은 한 종류의 ‘immediate objects’를 가지고 있는데, 바로 ‘Smis’(small integers)이며, ‘pointers’의 태그는 0입니다. </p>
<p>‘Heap objects’ 의 ‘pointer’ 태그는 1입니다. ‘Smi pointer’의 상위 비트는 해당 값을 나타내며, ‘heap object pointer’의 상위 비트는 해당 주소의 가장 중요한 비트입니다(힙 객체는 항상 2바이트 이상의 정렬을 갖기 때문에 최하위 비트는 항상 0임).</p>
<p>태그가 0이면 ‘untagging’ 및 ‘retagging’ 없이도 ‘Smis’에서 많은 작업을 수행할 수 있습니다.</p>
<p>태그가 1이면 ‘load’ 및 ‘store’ 명령어에서 사용하는 오프셋에 태그를 제거할 수 있으므로 힙 객체 접근에는 불이익이 없습니다.</p>
<p>힙 객체는 항상 ‘double-word’(32비트) 단위로 할당됩니다. ‘old-space’ 에서 객체는 ‘double-word’ 정렬(address % double-word == 0)이 유지되며, ‘new-space’에 있는 객체는 ‘double-word’ 정렬(address % double-word == word)에서 오프셋이 유지됩니다. 이렇게 하면 ‘boundary address’와 비교하지 않고도 객체의 나이를 확인할 수 있으므로 힙 영역에 대한 제한을 피하고 ‘thread-local’ 저장소에서 경계를 로드하지 않아도 됩니다. 게다가 ‘scavenger’는 단일 브랜치로 바로 앞의 객체와 오래된 객체를 모두 빠르게 건너뛸 수 있습니다.</p>
<table>
<thead>
<tr>
<th>Pointer</th>
<th>Referent</th>
</tr>
</thead>
<tbody><tr>
<td>0x00000002</td>
<td>Small integer 1</td>
</tr>
<tr>
<td>0xFFFFFFFE</td>
<td>Small integer -1</td>
</tr>
<tr>
<td>0x00A00001</td>
<td>Heap object at 0x00A00000, in old-space</td>
</tr>
<tr>
<td>0x00B00005</td>
<td>Heap object at 0x00B00004, in new-space</td>
</tr>
</tbody></table>
<p>힙 객체는 객체의 클래스, 사이즈, 몇가지 상태 플래그등을 인코딩한 ‘single-word’ 해더를 가지고 있습니다.</p>
<p>64비트 아키텍처에서 힙 객체의 헤더에는 32비트 ID 해시 필드도 포함됩니다. 32비트 아키텍처에서는 힙 객체의 ID 해시가 별도의 해시 테이블에 보관됩니다.</p>
<h2 id="handles">Handles</h2>
<p>Dart VM 의 GC 는 ‘precise’, ‘movimg’ 한 특성이 있습니다.</p>
<h3 id="precise">precise</h3>
<p> 힙 영역에서 수집(collection) 이 일어날때, 수집 대상이 무엇인지, 포인터가 인지 아닌지 정확히 알고있을때, 정확(precise) 하다고 불려집니다. 예를들어 컴파일된 Dart 코드 VM 은 스택 슬롯에 담겨진 ‘object pointers’ , ‘unboxded values’ 들을 추적합니다. 이는 ‘unboxed value’일지라도 포인터 크기의 값은 힙 영역에 대한 포인터일 수 있다고 간주하는 보수적 GC와는 반대되는 방식입니다.</p>
<h3 id="moving">moving</h3>
<p> 객체의 주소가 변경되어 해당 객체의 포인터도 업데이트 될 수도 있습니다. Dart VM 에서, 객체들은 ‘<a href="https://dart.googlesource.com/sdk/+/refs/tags/3.3.0-32.0.dev/runtime/docs/gc.md#scavenge">scavenge</a>’, ‘<a href="https://dart.googlesource.com/sdk/+/refs/tags/3.3.0-32.0.dev/runtime/docs/gc.md#mark-compact">compaction</a>’, ‘<a href="https://dart.googlesource.com/sdk/+/refs/tags/3.3.0-32.0.dev/runtime/docs/gc.md#become">become</a>’ 작업에서 움직(moving)일수 있습니다. ‘moving’한 GC 는 꼭 ’precise’해야합니다. 보수적인 GC 가 보장되지 않은 ‘pointer’를 업데이트 할때, 그 값이 ‘pointer’가 아닐경우, 실행에 에러가 발생합니다.</p>
<p>VM 은 C++ 로 구현된 자체 런타임을 비롯한, 스택 슬롯, 전역, 외부언어로 된 Dart 힙 포인터를 담고있는 객체 필드를 구별할수 없습니다. </p>
<p>GC 는 ‘precise’를 유지하기위해, “handles” 를 통해 간접적으로 Dart 객체를 참조합니다. ‘Handles’는 ‘pointer’의 ‘pointer’로 생각하면 됩니다. 그들은 VM 으로 부터 할당되며, GC 는 수집 중에 ‘handles’에 포함된 ‘pointer’를 방문(가능하면 업데이트)합니다.</p>
<h2 id="safepoints">Safepoints</h2>
<p> 힙 영역에서 읽거나 쓸수있는 GC가 아닌 ‘thread’를 ‘mutator’라고 부릅니다(because it can mutate the object graph). GC의 일부 단계에선 ‘mutator’가 힙을 사용하지 못하는데, 이를 “safepoint operations(연산)” 라고 부릅니다. ‘safepoint’ 연산의 예로는 marking roots at the beginning of concurrent marking and the entirety of a scavenge.</p>
<p>이러한 연산들을 수행하려면, 모든 ‘mutator’가 일시작으로 힙 영역에 접근을 멈춰야 합니다. 이때를 ‘mutator’가 “safepoint” 에 도달했다 라고 말합니다.</p>
<p>‘safepoint’에 도달한 ‘mutator’는 ‘safepoint’ 연산이 완료될때까지 힙 영역 접근 재개를 하지 못합니다.  게다가 ‘safepoint’ 에 도달한 ‘mutator’는 힙 영역의 어떤 ‘pointers’ 도 잡고(holding) 있으면 안됩니다, 그렇지 않으면 GC가 포인터를 방문(visit) 할 수 없습니다.</p>
<p>VM 런타임 코드로 봤을때, 위 속성의 의미는, ‘handle’만 가지고 있으며, ‘ObjectPtr’나 ’UntaggedObject’는 가지고 있지 않는걸 의미합니다.</p>
<p>‘Safepoint’에 도달하는 상황을 예로 보면, ‘allocations’, ‘stack overflow checks’, 컴파일된 코드와 런타임 및 네이티브 코드간 트랜지션이 있습니다.</p>
<p>‘mutator’는 중지 되지 않은채로 ‘safepoint’에 도달할수 있습니다. 이 작업은 아마 힙 영역에 접근하지 않는 긴 작업을 수행중일수 있습니다. ‘safepoint’에 벗어나 힙 영역에 접근 재개를 하기위해선 이 ‘safepoint’ 연산이 끝나길 기다려야 합니다.</p>
<p>‘safepoint’ 연산은 Dart 코드의 실행은 제외하기 때문에, 이 속성만 필요한 ‘non-GC’ 작업에 사용되기도 합니다. 예를들면, 백그라운드 컴파일이 완료되어 그 결과를 설치할때, ‘safepoint’ 연산을 이용하여 설치중에 Dart 실행이 중간 상태를 보지 못하도록 합니다.</p>
<h2 id="scavenge">Scavenge</h2>
<p><a href="https://en.wikipedia.org/wiki/Cheney%27s_algorithm">Cheney&#39;s algorithm</a> 를 참고하세요.</p>
<h2 id="parallel-scavenge">Parallel Scavenge</h2>
<p>‘FLAG_scavenger_tasks(default 2) workers’는 별도의 스레드에서 시작됩니다. 각 ‘worker’는 루트 세트의 일부(기억된 세트 포함)를 처리하기 위해 경쟁합니다. ‘worker’가 객체를 ‘to-space’에 복사하면 ‘worker-local bump allocation’ 영역에서 할당합니다. 동일한 ‘worker’가 복사된 객체를 처리합니다. worker가 개체를 ‘old-space’로 승격하면 ‘worker-local freelist’에서 할당하며, 이 ‘freelist’는 ‘large free block’에 범프 할당을 사용합니다. </p>
<p>승격된 개체는 ‘work stealing’를 구현하는 작업 목록에 추가되므로 다른 ‘worker’가 승격된 개체를 처리할 수 있습니다. 객체가 비워진 후 ‘worker’는 ‘compare-and-swap’을 사용하여 ‘forwarding pointer’를 ‘from-space’ 객체의 헤더에 설치합니다. 경쟁에서 패배하면 방금 할당했던 공간 또는 ‘old-space’ 개체의 할당을 취소하고 승자의 개체를 사용하여 처리 중이던 포인터를 업데이트합니다. ‘worker’는 모든 작업 세트가 처리되고 모든 ‘worker’가 승격된 작업 목록의 ‘to-space’ 객체와 해당 로컬 부분을 처리할 때까지 실행됩니다.</p>
<h2 id="mark-sweep">Mark-Sweep</h2>
<p>모든 객체에는 헤더에 ‘mark bit’라는 비트가 있습니다. 수집 주기(collection cycle)가 시작될 때 모든 객체는 이 비트를 지웁니다.</p>
<p> ‘Marking’ 단계 동안, ‘collector’ 는 각각의 ‘root pointers’를 방문한다. 그리고 대상 객체의 ‘mark bit’ 가 비워져있으면, ‘mark bit’를 설정하고 마킹 스택(또는 gray set)에 추가합니다. 그후 ‘collector’ 는 마킹 스택에 있는 객체를 하나씩 지우고 방문하며, 다시 그 객체가 참조하고 있는 다른 객체를 마킹 스택에 추가를 하고 ‘mark bit’를 세팅하고 삭제한다. 위 동작을 마킹 스택이 비어있을때까지 반복한다. 이 과정에서, 모든 접근 가능한 객체는 ‘mark bit’ 이 세팅되고, 접근 불가능한 객체는 ‘mark bit’ 이 비어있는 상태이다.</p>
<p> ‘Sweeping’ 단계 동안, ‘collector’ 는 각 객체를 방문합니다. ‘mark bit’ 이 비어있다면, 그 객체의 메모리는 ‘<a href="https://github.com/dart-lang/sdk/blob/main/runtime/vm/heap/freelist.h">free list</a>’ 에 추가되어 다른 객체의 메모리 할당에 사용됩니다. 일부 페이지의 모든 객체에 접근할 수 없는 경우 해당 페이지는 OS에 의해 해제됩니다.</p>
<h2 id="mark-compact">Mark-Compact</h2>
<p>Dart VM 은 ‘sliding compactor’를 포함하고 있습니다. ‘forwarding table’은 힙 영역을 블록 단위로 나누고, 각 블록의 ‘target address’와 메모리에서 해제되지 않고 유지되는 64비트(surviving double-word)의 비트벡터 정보들을 빼곡히 담고있습니다. ‘table’ 은 힙 페이지의 정렬된 상태를 유지함으로써 고정된 시간(constant time)안에 접근 될수 있고, 모든 객체의 마스킹을 통해 ‘page header’ 접근할 수 있습니다.  </p>
<h2 id="concurrent-marking">Concurrent Marking</h2>
<p>‘old-space’의 GC들은 ‘mutator’가 멈추는 시간을 줄이기위해, 대부분의 마킹 작업중의 ‘mutator’가 계속 실행되도록 허락합니다.</p>
<h3 id="barrier">Barrier</h3>
<p>‘mutator’ 와 ‘marker’ 는 동시간대(concurrently) 실행되면, 뮤테이터가 이미 마킹되어 방문한 객체(SOURCE)에 마킹되지 않은 객체(TARGET)에 대한 포인터를 쓸 수 있으며, 이로 인해 대상 수집이 잘못될 수 있습니다. To prevent this, the write barrier checks if a store creates a pointer from an old-space object to an old-space object that is not marked, and marks the target object for such stores. ‘new-space’ 객체를 ‘roots’라 판단하고 마킹을 완료하기위해 다시 방문하기때문에 ‘new-space’ 객체의 포인터는 무시합니다. </p>
<p>‘header’와 ‘Slot’에 대한 액세스의 재정렬로 인해 마킹을 건너뛰지 않도록 하는 데 필요한 메모리 장벽을 피하기 위해 소스 객체의 마킹 상태를 무시하고, 마킹 중에 액세스한 객체는 마킹이 완료될 때에도 활성 상태로 유지될 가능성이 높다는 가정하에 마킹을 무시합니다.</p>
<p>‘barrier’은 아래와 같습니다.</p>
<pre><code>StorePointer(ObjectPtr source, ObjectPtr* slot, ObjectPtr target) {
  *slot = target;
  if (target-&gt;IsSmi()) return;
  if (source-&gt;IsOldObject() &amp;&amp; !source-&gt;IsRemembered() &amp;&amp; target-&gt;IsNewObject()) {
    source-&gt;SetRemembered();
    AddToRememberedSet(source);
  } else if (source-&gt;IsOldObject() &amp;&amp; target-&gt;IsOldObject() &amp;&amp; !target-&gt;IsMarked() &amp;&amp; Thread::Current()-&gt;IsMarking()) {
    if (target-&gt;TryAcquireMarkBit()) {
      AddToMarkList(target);
    }
  }
}
</code></pre><p>‘shift-and-mask’ 를 ‘generational’ 과 ‘incremental’ 체크와 결합시킵니다.</p>
<pre><code>enum HeaderBits {
  ...
  kNotMarkedBit,            // Incremental barrier target.
  kNewBit,                  // Generational barrier target.
  kAlwaysSetBit,            // Incremental barrier source.
  kOldAndNotRememberedBit,  // Generational barrier source.
  ...
};

static constexpr intptr_t kGenerationalBarrierMask = 1 &lt;&lt; kNewBit;
static constexpr intptr_t kIncrementalBarrierMask = 1 &lt;&lt; kNotMarkedBit;
static constexpr intptr_t kBarrierOverlapShift = 2;
COMPILE_ASSERT(kNotMarkedBit + kBarrierOverlapShift == kAlwaysSetBit);
COMPILE_ASSERT(kNewBit + kBarrierOverlapShift == kOldAndNotRememberedBit);

StorePointer(ObjectPtr source, ObjectPtr* slot, ObjectPtr target) {
  *slot = target;
  if (target-&gt;IsSmi()) return;
  if ((source-&gt;header() &gt;&gt; kBarrierOverlapShift) &amp;&amp;
      (target-&gt;header()) &amp;&amp;
      Thread::Current()-&gt;barrier_mask()) {
    if (target-&gt;IsNewObject()) {
      source-&gt;SetRemembered();
      AddToRememberedSet(source);
    } else {
      if (target-&gt;TryAcquireMarkBit()) {
        AddToMarkList(target);
      }
    }
  }
}

StoreIntoObject(object, value, offset)
  str   value, object#offset
  tbnz  value, kSmiTagShift, done
  lbu   tmp, value#headerOffset
  lbu   tmp2, object#headerOffset
  and   tmp, tmp2 LSR kBarrierOverlapShift
  tst   tmp, BARRIER_MASK
  bz    done
  mov   tmp2, value
  lw    tmp, THR#writeBarrierEntryPointOffset
  blr   tmp
done:
</code></pre><h3 id="data-races">Data races</h3>
<p>‘headers’ 와 ‘slots’에 대한 작업은 ‘<a href="https://en.cppreference.com/w/cpp/atomic/memory_order">relaxed ordering</a>’을 사용하며 동기화를 제공하지 않습니다.</p>
<p>동시성을 지닌 마커는 ‘acquire-release’ 작업과 같이 시작되므로 마킹이 시작될 때까지 ‘mutator’에 의한 모든 쓰기가 마커에 표시됩니다.</p>
<p>마킹이 시작되기 전 생성된 ‘old-space’ 객체의 경우 각 슬롯에서 마커는 마킹이 시작된 시점의 값 또는 슬롯에 정렬된 모든 후속 값을 볼 수 있습니다. 포인터가 포함된 슬롯은 객체의 수명 동안 유효한 포인터를 계속 포함하므로 마커가 어떤 값을 보더라도 포인터가 아닌 것을 포인터로 해석하지 않습니다. (The one interesting case here is array truncation, where some slot in the array will become the header of a filler object. We ensure this is safe for concurrent marking by ensuring the header for the filler object looks like a Smi.) 마커에 이전 값이 표시되면 정밀도가 떨어지고 죽은 객체가 유지될 수 있지만, ‘mutator’에 의해 새 값이 표시되었으므로 정확성을 유지합니다.</p>
<p>마킹이 시작된 후에 생성된 ‘old-space’ 개체의 경우 ‘slots’에 대한 작업이 동기화되지 않기 때문에 마커에 초기화되지 않은 값이 표시될 수 있습니다. 이를 방지하기 위해 마킹하는 동안 마커가 ‘old-space’ 객체를 방문하지 않도록 <a href="https://en.wikipedia.org/wiki/Tracing_garbage_collection#TRI-COLOR">검은색(marked)</a>을 할당하고, ‘new-space’ 객체와 ‘roots’는 ‘safepoint’ 중에만 방문하며 ‘safepoints’는 동기화를 설정합니다.</p>
<p>‘mutator’의 마크 블록이 가득 차게되면, ‘acquire-release’ 작업을 통해 마커로 전송되므로, 마커는 블록 안에 저장소를 볼 수 있습니다.</p>
<h2 id="write-barrier-elimination">Write barrier elimination</h2>
<p>힙 영역에 <code>container.slot = value</code> 이 저장되어있을때마다, 저장소가 GC에 알려야 하는 참조를 생성하는지 확인해야 합니다.</p>
<p>‘scavenger’가 필요로 하는 ‘The generational write barrier’는 다음 사항을 확인해야 합니다.</p>
<ul>
<li><code>container</code> 는 ‘old’ 이며, ’remembered set’에 있으면 안됩니다.</li>
<li><code>value</code> 은 새로운 값이어야 합니다.</li>
</ul>
<p>이런 경우, ‘remembered set’ 에 <code>container</code> 를 반드시 넣어야합니다.</p>
<p>마커에 필요한 ‘incremental marking write barrier’은 다음을 확인합니다. </p>
<ul>
<li><code>container</code> 는 ‘old’ 이며</li>
<li><code>value</code> 는 ‘old’ 이며, 마크되지 않아야 합니다.</li>
<li>마킹중이어야 합니다.</li>
</ul>
<p>이런경우, ‘marking worklist’ 에 <code>value</code> 를 반드시 넣어야합니다.</p>
<p>컴파일러가 이러한 경우가 발생하지 않거나 런타임에 의해 보정된다는 것을 증명할 수 있는 경우 이러한 검사를 제거할 수 있습니다. 컴파일러는 다음과 같은 경우에 이를 증명할 수 있습니다.</p>
<ul>
<li><code>value</code> 는 ‘constant’ 입니다. ‘Constant’는 항상 ‘old’ 이고., <code>container</code>를 통해 마킹하지 못하더라도 ‘constant’ 풀을 통해 마킹됩니다.</li>
<li><code>value</code> 는 ’static bool’(‘null’, ‘false’, ‘true’) 타입 의 ‘constants’ 값을 가지고 있습니다.</li>
<li><code>value</code> 는 힙 객체가 아닌 ‘Smi’입니다.</li>
<li><code>container</code> 는 <code>value</code>와 같은 객체 입니다. GC는 ‘self-reference’를 발견하면 ‘additional’ 객체를 유지할 필요가 없으므로 ‘self-reference’를 무시해도 접근 가능한 객체를 해제할 수 없습니다.</li>
<li><code>container</code> 는 ‘new’ 객체 이거나, 마킹 과정중일경우, ‘remembered set’ 안의 마킹된 ‘old’ 객체 입니다.</li>
</ul>
<p>We can know that <code>container</code> meets the last property if <code>container</code> is the result of an allocation (instead of a heap load), and there is no instruction that can trigger a GC between the allocation and the store. This is because the allocation stubs ensure the result of AllocateObject is either a new-space object (common case, bump pointer allocation succeeds), or has been preemptively added to the remembered set and marking worklist (uncommon case, entered runtime to allocate object, possibly triggering GC).</p>
<pre><code>container &lt;- AllocateObject
&lt;instructions that do not trigger GC&gt;
StoreInstanceField(container, value, NoBarrier)</code></pre><p>We can further eliminate barriers when <code>container</code> is the result of an allocation, and there is no instruction that can create an additional Dart frame between the allocation and the store. This is because after a GC, any old-space objects in the frames below an exit frame will be preemptively added to the remembered set and marking worklist (Thread::RestoreWriteBarrierInvariant).</p>
<pre><code>container &lt;- AllocateObject
&lt;instructions that cannot directly call Dart functions&gt;
StoreInstanceField(container, value, NoBarrier)</code></pre><h2 id="weakness">Weakness</h2>
<h2 id="finalizers">Finalizers</h2>
<h2 id="become">Become</h2>
<p>‘Become’ 은 원자적(atomically)으로 객체 집합의 ‘identity’를 전달하는 연산입니다. ‘heap walk’는 ‘<em>before’</em> 객체의 모든 포인터가 <em>‘after’</em> 객체의 포인터로 대체되고, <em>‘after’</em> 객체는 해당 <em>‘before’</em> 객체의 &#39;ID hash&#39;를 얻는 방식으로 수행됩니다. Dart VM 에서는 기존 크기의 ‘old program and instances’ 와 새 크기의 ‘new program and instances’를 맵핑하기위해 ‘<a href="https://github.com/dart-lang/sdk/wiki/Hot-reload">reload</a>’ 중에 사용됩니다.</p>
<p>이 연산은 초기 ‘Smalltalk’ 구현에 사용된 방법입니다. 포인터가 객체 테이블을 통해 간접적으로 사용되었고 컬렉션의 크기를 조정하는 데 사용되었기 때문에 O(1)이었습니다.</p>
<p>‘ID’를 교환하는 여러 ‘become’ 연산이 존재하지만, Dart VM 에서는 사용되지 않습니다. 프록시를 하위 그래프 앞에 설치하고 프록시 뒤에 있는 객체에 대한 참조를 유지해야 하는 경우에 유용합니다. ‘paging’방식 이전엔, 이 방식이 가상 메모리에 대한 접근 방식이었습니다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Handling Error (2)]]></title>
            <link>https://velog.io/@dev-hann/Handling-Error-2</link>
            <guid>https://velog.io/@dev-hann/Handling-Error-2</guid>
            <pubDate>Wed, 04 Oct 2023 08:48:32 GMT</pubDate>
            <description><![CDATA[<h3 id="answer-모든-layer-에서-처리해야한다">Answer) 모든 Layer 에서 처리해야한다.</h3>
<p>오랜시간 고민한 결과 내가 생각하는 예외 처리에 적합한 Layer 는..</p>
<p><strong>‘모든 Layer’</strong> 인것같다.</p>
<p>이유인 즉, 각 Layer 의 목적과 관심사에 맞게 예외 처리를 해줘야 했다.
<img src="https://velog.velcdn.com/images/dev-hann/post/5699a692-396c-4334-83ec-d307dc614ac2/image.png" alt=""></p>
<p>&lt; 다른 좋은 의견 있으신분들은 댓글로 남겨주세요~ &gt;</p>
<h2 id="clean-architecture로-예제-코드-보기">Clean Architecture로 예제 코드 보기</h2>
<p><img src="https://velog.velcdn.com/images/dev-hann/post/f7f60cd3-5528-4dec-b3c3-1347647990c5/image.png" alt=""></p>
<p>&lt;Clean Architecture 에 관한 글은 다른 글로 자세히 찾아뵙겠습니다!&gt;</p>
<p>이해를 돕기위해 간단한 카운터 예제를 준비해봤다.</p>
<p>Dart 언어를 사용했으며, 중요한 부분만 의미 전달을 위해 작성했다.</p>
<ul>
<li><strong>Presentation Layer</strong></li>
</ul>
<p>Domain Layer 에서 전달받은 예외를 사용자에게 적절한 내용으로 전달해준다.</p>
<p>UseCase 를 통해 [decrease()] 함수를 실행하고, 반환받은 [Either] 객체를 통해,</p>
<p>함수가 정상적으로 실행된 경우, 예외가 발생한 경우를 알수있고, </p>
<p>그에따라 View 에 맞게 사용자에게 내용을 전달해줄 수 있다.</p>
<p>( 일반적으로 UseCase 를 직접 사용하지 않고, ViewModel 이나 Controller 를 통해 View Logic 을 따로 관리해준다 )</p>
<pre><code class="language-dart">// View.dart
class CountView {
  final useCase = CountUseCase(CountImplement());
    int get currentCount =&gt; useCase.count;

  Widget minusButton() {
    return AwesomeButton(
      onPressed: () {
        final either = useCase.decrease();
        either.fold(
          (exception) {
            //handle Exception Alert
          },
          (count) {
            // updateState.
            currentCount = count;
          },
        );
      },
      child: Text(&quot;Minus Button&quot;),
    );
  }
}</code></pre>
<ul>
<li><strong>Domain Layer</strong></li>
</ul>
<p>UseCase 에서는 Repository 를 의존하며, Repository 를 통해 Data Layer 와 소통하고,</p>
<p>Data Layer 에서 혹은 비지니스 로직에 의해 발생한 예외를</p>
<p><code>try-catch</code> 문을 통해 <code>catch</code> 후, [Either] 객체에 담아 반환한다.</p>
<pre><code class="language-dart">// UseCase.dart
class CountUseCase {
  CountUseCase(this.repository);
  final CountRepository repository;

  int get count =&gt; repository.loadCount();

  Either&lt;CustomException, int&gt; increase() {
    try {
      final value = count + 1;
      repository.updateCount(value);
      return Right(value);
    } catch (e) {
      return Left(CustomException.fromException(e));
    }
  }

  Either&lt;CustomException, int&gt; decrease() {
    try {
      final value = count - 1;
      // Custom Bussiness Logic
      if (value &lt; 0) {
        return Left(CustomException.nagetiveNumber());
      }
      repository.updateCount(value);
      return Right(value);
    } catch (e) {
      return Left(CustomException.fromException(e));
    }
  }
}</code></pre>
<ul>
<li><strong>Data Layer</strong></li>
</ul>
<p>데이터, 네트워크, 파일 시스템등과의 상호작용에서 일어나는 애러들을 처리한다.</p>
<p>아래 예제는 [getValue()] 함수를 통해 값을 불러온후, int 타입으로 parse 해 반환해준다.</p>
<p>parse 과정중 Exception 이 발생할시, DB 값을 0 으로 초기화 해주고, 0을 반환해준다.</p>
<p><code>DB 값을 0으로 초기화 해주고, 0을 반환 해준다</code> 이 부분이 개발자가 판단하에,</p>
<p>상황에 따라, 기획 의도에 따라 바뀔수 있는 비지니스 로직에 해당된다면 UseCase 에 작성해주는게 적합하다.</p>
<pre><code class="language-dart">// Repository.dart
abstract class CountRepository {
  int loadCount();
  void updateCount(int value);
}

// Implement.dart
class CountImplement extends CountRepository {

    // When occur Error throw [DataBaseException]
  final _db = CountDataBase();

  @override
  int loadCount() {
        try{
            final value = _db.getValue();
            if(value==null){
                return 0;
            }
            return int.parse(_db.getValue());
        }on FormatException catch(e){
            // if occur Format Exception, value reset 0 and return 0;
            updateCount(0);
            return 0;
        }
  }

  @override
  void updateCount(int value) {
    _db.update(value);
  }
}</code></pre>
<h2 id="마치며"><em>마치며</em></h2>
<p><img src="https://velog.velcdn.com/images/dev-hann/post/3d031de2-5d06-4a6f-8681-d006c8c52fbf/image.png" alt=""></p>
<p> 기존의 수많은 Error Handling 관련된 글들은 단순히 <code>try-catch</code> 문의 사용법만 설명해놨다.
그런 기본적인 사용법이 아닌, 한단계 더 나아가서 소프트웨어의 구조적 관점에서 사용법에 대해 고민해봤다.
몇몇 분들에겐 부질없어 보일 수도 있겠지만, 이 기회를 통해 각 Layer 의 역할을 분명히 이해 할 수 있게 되었고, 
보다 안정적인 소프트웨어를 만들게 될 수 있어 소중한 시간이었다.</p>
<p> 나는 가끔 개발이라는 자체를 <code>망치로 무언가를 만드는것</code> 과 같다고 생각한다.
망치질만 잘한다고 무언가를 만들 수 있는것은 아니다. 
망치질을 하는건 쉽다. 아무 생각없이 손잡이를 잡고, 휘두르면 된다.
하지만 우리의 목표는 망치질을 하는것이 아닌 무언가를 만드는 것이다. 
만들려고 하는 객체를 이해하고, 설계하고, 분석해야 한다.
그렇지 않고 망치를 마구자비로 휘두르게되면, 물건을 만들기는 커녕 망가트리고 있을것이다.
 개발도 마찬가지인것 같다. 코드만 쓴다고 프로그램이 만들어지는게 아니다.
기획 의도를 이해하고, 설계하고, 분석한후, 코드로 구체화 시키는게 개발의 묘미가 아닐까 생각해 본다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Handling Error (1)]]></title>
            <link>https://velog.io/@dev-hann/Handling-Error-1</link>
            <guid>https://velog.io/@dev-hann/Handling-Error-1</guid>
            <pubDate>Wed, 04 Oct 2023 08:45:54 GMT</pubDate>
            <description><![CDATA[<h2 id="intro">Intro</h2>
<p> 개발을 시작함과 동시에 내 옆에서 딱 달라붙어 항상 날 괴롭히던 수많은 Error 들..</p>
<p>이제는 오히려 한참 작성한 코드가 Error 없이 잘 작동하면,  허전하고 느낌이 든다.
<img src="https://velog.velcdn.com/images/dev-hann/post/bb9de70c-f3f4-4936-a922-3b03080c9678/image.png" alt=""></p>
<p>그렇게 날 괴롭히던 Error 들을 잘 다루기 위해, 그간 고민했던 것들을 정리해보았다.</p>
<p>어떻게 해야 내가 만든 프로그램에 피해 없이, Error 들과 사이좋게 지낼 수 있을까?</p>
<p>이 글은 단순한 Error Handling 에 대한 글이 아닌, 구조적 관점에서의 Error Handling을 다루는 글입니다.</p>
<h2 id="error-exception">Error? Exception?</h2>
<p>개발을 하다보면 자연스럽게 마주하는 골칫거리 중 하나 Error 와 Exception. (두갠가?)</p>
<p>매번 이것들을 처리하기 급급했지만, 오늘은 이녀석들에 대해 톺아보자.</p>
<p>그런데 여기서 등장하는 Error 와 Exception 이 뭘까?</p>
<p>개념적 정의를 좀 살펴보면 아래와 같은데..</p>
<table>
<thead>
<tr>
<th>특징</th>
<th>Error</th>
<th>Exception</th>
</tr>
</thead>
<tbody><tr>
<td>발생 원인</td>
<td>시스템 오류</td>
<td>프로그래머 오류</td>
</tr>
<tr>
<td>예측 가능성</td>
<td>예측 불가능</td>
<td>예측 가능</td>
</tr>
<tr>
<td>처리 방식</td>
<td>시스템에서 자동 처리</td>
<td>프로그래머가 직접 처리</td>
</tr>
<tr>
<td>종류</td>
<td>스택 오버 플로우, 메모리 부족등</td>
<td>null 참조, 0 으로 나누기등</td>
</tr>
</tbody></table>
<p>프로그래밍적 정의를 살펴보면 언어마다 조금씩 다르지만..</p>
<table>
<thead>
<tr>
<th>언어</th>
<th>Error</th>
<th>Exception</th>
<th>Etc</th>
</tr>
</thead>
<tbody><tr>
<td>Java</td>
<td>Throwable</td>
<td>Throwable</td>
<td>Throwable 을 상속받은 두 객체 모두 throw 할수있음</td>
</tr>
<tr>
<td>C#</td>
<td>x</td>
<td>Throwable</td>
<td>Exception 만 throw 할수있음</td>
</tr>
<tr>
<td>Dart</td>
<td>Throwable</td>
<td>Throwable</td>
<td>모든 객체를 throw 할수있음</td>
</tr>
</tbody></table>
<p>위 내용을 종합해보면..</p>
<p>시스템상 오류 &amp; 프로그램상 오류 가 발생할경우, Error 혹은 Exception 을 발생(throw)시킨다.</p>
<p>그러면 개발자는  <code>try-catch</code> 문을 통해 발생된 오류를 <code>catch</code> 하여 상황에 맞게 <strong>예외 처리 한다</strong>.</p>
<p>( 플렛폼마다 어느정도 차이는 있습니다.. )
<img src="https://velog.velcdn.com/images/dev-hann/post/32faa58f-5a14-455c-b266-937a2321f7bc/image.png" alt=""></p>
<pre><code class="language-dart">try {
        final file = File(&quot;./AwesomeFile.txt&quot;); 
        final data = file.readAsStringSync();  
        print(data); 
  } on FileSystemException catch (exception,stackTrace) {
        // if file not exist, &#39;Code&#39; throw [FileSystemException].
    // Handle FileSystemException

    } on OutOfMemoryError catch (e){
        // if file is really Big, &#39;System&#39; throw [OutOfMemoryError].
        // Handle StackOverflowError

    }
}</code></pre>
<p>끗! </p>
<p>하면 좋겠지만..  </p>
<p>그럼 어떻게 해야 <strong>예외 처리</strong>를 잘 했다고 소문이 날까?</p>
<p>그냥 <code>try-catch</code> 문만 쓰면 되는거 아니야? 라고 생각하겠지만..</p>
<p>타임머신을 타고 개린이 시절로 잠시 돌아가보자.. ( 대나무 헬리콥터~ )</p>
<p><img src="https://velog.velcdn.com/images/dev-hann/post/21d3b646-61cb-4cba-928c-138194723ce4/image.png" alt=""></p>
<h2 id="개린이-시절">개린이 시절</h2>
<p>아무것도 모르던 개린이 시절..  뒤도 안보고 코드를 작성하곤했다.</p>
<p>함수 하나에 모든 비지니스 로직 &amp; DB Query 등등 다 때려넣고, </p>
<p>의도대로 작동하는 코드를 보며 내심 뿌듯해 했었다.</p>
<p>당연히 이런 덩어리 코드에 Error 처리는 더 큰 덩어리를 만들뿐이었다.</p>
<p><img src="https://velog.velcdn.com/images/dev-hann/post/e5687dc9-4f44-4877-a81f-9e7b7b1223ce/image.png" alt=""></p>
<p> &lt; 아무튼 돌아가면 된거지 &gt;</p>
<p>그렇게 아무 생각없이 무자비한 코딩을 하고있었다. 나는 점점 더 큰 스파게티 코드를 만들고 있었고, </p>
<p>결국 유지보수가 불가능 할정도의 스파게티 괴물이 탄생했다.</p>
<p>그리고 결정했다, 잠시 코딩을 멈추고 공부하기 시작하기로.. </p>
<p>무엇이 문제인지 돌아보고, 이 괴물을 어떻게  물리쳐야할지 한참을 고민했다. </p>
<p>그렇게 한참을 고민 &amp; 공부하고 나서야 해결책을 찾았다.</p>
<p>‘디자인 패턴’ 과 ‘시스템 아키텍처’. </p>
<p>난 이것들을 완전히 내것으로 만들기 전까지 손에서 코딩을 멈추었다.</p>
<p>결국 프로그램에 전체 구조를 생각하게 되었고, 계층, 관심사 분리등 여러 개념들을 알게되었다.</p>
<h2 id="구조적-관점에서-error-처리">구조적 관점에서 Error 처리</h2>
<p><img src="https://velog.velcdn.com/images/dev-hann/post/c35474ed-e36f-40f1-9284-dc522b440bab/image.png" alt=""></p>
<Clean Architecture>

<p>그렇게 프로그램에 구조를 짜게 되면서, 문득 드는 생각.. </p>
<h3 id="quiz-어느-layer-에서-error-처리를-해야할까">Quiz) 어느 Layer 에서 Error 처리를 해야할까?</h3>
<ol>
<li>Presentation Layer</li>
<li>Domain Layer</li>
<li>Data Layer</li>
</ol>
<p>잘 생각해보시고, 다음글로 ㄱㄱ</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[API 서버 개발기]]></title>
            <link>https://velog.io/@dev-hann/api-server</link>
            <guid>https://velog.io/@dev-hann/api-server</guid>
            <pubDate>Fri, 29 Sep 2023 03:37:50 GMT</pubDate>
            <description><![CDATA[<h2 id="intro"><em>Intro</em></h2>
<p> 기존 WebView 방식의 앱을 Flutter 로 완전히 바꾸면서, 각 기능에 대한 API가 필요했다. </p>
<p>그렇게 서버 개발자들과 같이 협업을 하게 되었고, 시간이 지나며 문제가 생기기 시작했다. </p>
<p>예를들면, </p>
<p>일정이 지켜지지 않다거나.. 개발이 딜레이 되거나.. 결과물이 늦게 나온다던가..</p>
<p>(심지어 2주동안 그대로였던..)
<img src="https://velog.velcdn.com/images/dev-hann/post/1a1c15ac-7b95-491b-b823-4da8bf1e681a/image.png" alt=""></p>
<p>팀장이 주장했던 <strong>‘자율적으로 개발하기’,</strong> 그건 이상적인 <del>공산주의</del>같은 것이었다.</p>
<p>결국 팀장과 회의 끝에 이번 <strong>‘API 서버 개발 프로젝트’</strong>는 내가 리드 하기로했다.</p>
<h2 id="나를-죽이지-못하는-legacy는-나를-힘들게-한다"><em>나를 죽이지 못하는 Legacy는 나를 힘들게 한다</em></h2>
<p> Flutter 개발자로 입사 했지만, 서버 코드를 보는시간이 더 많았다.</p>
<p>기존 서버에 API 문서도 없었고, 중요한건 기존의 앱이 WebView 방식이었다.</p>
<p>모든 로직 &amp; 화면이 서버에 있었기에, 일단은 그쪽에 하루빨리 익숙해져야 했다.</p>
<p>(이렇다 저렇다 불평할 시간에 코드 한줄 더 보고 빨리 해결하고 싶은 마음이 더 깊었다)</p>
<p>기존 서버를 한마디로 표현하자면..
<img src="https://velog.velcdn.com/images/dev-hann/post/fe7cc20a-a753-4c33-a91b-d9d8a10faaed/image.png" alt=""></p>
<p>&lt;<strong>혼돈의 카오스&gt;</strong>  </p>
<p><strong>‘핑계없는 무덤은 없다’</strong>라고 모든 코드에도 각자의 상황과 사정이 있었을것이다.</p>
<p>(그래도 “함수” 하나에 몇천줄.. “변수명”이 한자.. 는 정말 이해가 안됐다)</p>
<p>기존 서버를 유지보수 하는 방법도 있엇지만, 동료들이 너무 낡은 Framework 로 고통받는걸 봐왔기에,</p>
<p>협의 하에 당시 최신 버전인 dotnet7 으로 개발을 시작했다.</p>
<p>( 추후 LTS 버전인 dotnet 8 으로 마이그레이션 했다, 두 버전간 호환이 너무 자연스러웠다. 역시 마소.. )
<img src="https://velog.velcdn.com/images/dev-hann/post/d1264498-bff2-4a47-b2ab-5907e5f8f634/image.png" alt=""></p>
<p>( .NET Framework 4.5 는 이만 놔줘.. 16년 1월에 이미 Expired )</p>
<h2 id="나무-말고-숲"><em>나무 말고 숲</em></h2>
<p>1차 목표는 Only For App. 앱에서 필요한 기능을 우선적으로 작성하는것으로 잡았다.</p>
<p>앱에 필요한 API 들만 따로 정리후, 빠르게 구조부터 잡았다.</p>
<p>비지니스 로직 없이 더미 모델만 Response 해주는 API 를 만든후, </p>
<p>동료들에게 기존 서버를 참고해 로직 부분만 채운후 데이터를 모델에 담아 리턴 해달라고 부탁했다.</p>
<p>짧은 스탭으로 목표를 잡아주고, 짧은 스탭로 성취감을 느낄수 있어서였는지..</p>
<p>이후로는 신기하게도 일정이 거의 밀리지 않았다. </p>
<h3 id="architecture">Architecture</h3>
<p><img src="https://velog.velcdn.com/images/dev-hann/post/c370e816-6b7b-4427-9627-9e9533512909/image.png" alt=""></p>
<p>그럼 서버 전체 구조를 간단하게 살펴보자.</p>
<p>한문장으로 표현하면.. <strong>‘Clean Architecture 를 기반으로 MVC 를 이용해 Restful 한 API 서버’</strong> </p>
<p>역할에 따라 Layer 을 나눴고, Layer 간 브릿지를 통해 응집성을 낮추는 유연한 설계를 했다.</p>
<p>( <strong>Clean Architecture</strong> 에 관해서는 다른 글로 자세히 찾아뵙겠습니다 )</p>
<p>그럼 간략하게 각 Layer 를 살펴보자.</p>
<p>( 공지 기능을 예로 들었으며, 실제코드와는 조금 차이가  있습니다 )</p>
<ul>
<li><strong><em>Presentation Layer</em></strong></li>
</ul>
<pre><code class="language-csharp">// AnnounceController.cs
public class AnnounceController : ControllerBase
    {
        private readonly AnnounceUseCase useCase = new AnnounceUseCase(
             new AnnounceRepository()
        );

        [HttpGet(&quot;list&quot;)]
        public ResponseModel AnnounceList()
        {
            var either = useCase.AnnounceList();
            return either.Match(
                Left: (fail) =&gt;
                {
                    return new ResponseModel&lt;IEnumerable&lt;AnnounceModel&gt;&gt;(fail);
                },
                Right: (list) =&gt;
                {
                    return new ResponseModel&lt;IEnumerable&lt;AnnounceModel&gt;&gt;(list);
                }
            );
        }
}</code></pre>
<p>사용자에게 보여지는 화면 (View) 계층.</p>
<p>화면 로직이 복잡할 경우 ViewModel 통해 로직과 뷰를 분리하는 경우도 있다.</p>
<p>API 서버가 사용자에게 보여지는 부분은 API 로 판단하고, 이쪽으로 Presentation Layer 로 잡아주었다.</p>
<p>UseCase 를 통해 불러온 데이터를 Functional 하게 처리해 주었다.</p>
<ul>
<li><strong><em>Domain Layer</em></strong></li>
</ul>
<pre><code class="language-csharp">// AnnounceUseCase.cs
class AnnounceUseCase : IUseCase&lt;IAnnounceRepository&gt;{

    public AnnounceUseCase(IAnnounceRepository repository) : base(repository) { }

    public Either&lt;FailureModel, IEnumerable&lt;AnnounceModel&gt;&gt; AnnounceList()
    {
            try
            {
                List&lt;AnnounceModel&gt; list = new List&lt;AnnounceModel&gt;();
                IEnumerable&lt;dynamic&gt; data = repository.AnnounceList(
                      // Request Data  
                      );
                foreach (var row in data)
                {
                    var item = new AnnounceModel(
                           // Response Data
                    );
                    list.Add(item);
                }
                return list;
            }
            catch (Exception e)
            {
                logger.Error(e);  // log4net
                return new FailureModel(e); // Handle Error
            }
    }
}</code></pre>
<p>비지니스 로직을 담당하고있는 UseCase 를 담고있으며, 추상화된 Repository 를 통해 Data Layer 와 소통 할 수 있다.</p>
<p>( <a href="https://velog.io/@dev-hann/dependency-injection-1">의존성 주입</a>을 통해 유연하게, 테스트 가능하게 설계했다 )</p>
<p>&lt;테스트 코드 생활화합시다&gt;</p>
<p>Repository 를 통해 받아온 데이터는 DTO 객체를 통해 Model 로 바꿔줄수도 있다.</p>
<p>전반적인 애러 처리는 UseCase 에서 처리했으며, Functional 하게 API 쪽으로 Response 해줌.</p>
<p>&lt;에러 핸들링 &amp; 로그 관련해서는 다른 글에서 자세히 찾아뵙겠습니다&gt;</p>
<pre><code class="language-csharp">// IAnnounceRepository.cs
public interface IAnnounceRepository : IRepository
{
        public abstract IEnumerable&lt;dynamic&gt; AnnounceList();
}

public class AnnounceRepository : IAnnounceRepository
{
    protected readonly DataBase dataBase = DataBase.GetInstance;

    public IEnumerable&lt;dynamic&gt; AnnounceList()
    {
            // Awesome SQL Query
            string sql = @&quot; SELECT * FROM Announce&quot;;
          return dataBase.Query(sql);
    }
}</code></pre>
<p>IAnnounceRepository 를 상속받은 AnnounceRepository 는 각 데이터를 불러는 로직을 구체화 시켜준다.</p>
<p>이렇게 설계함으로, 비지니스 로직을 테스트를 해야하거나, DB Framework 를 교체 해야 할경우, </p>
<p>다른 Layer 에 코드 변경을 최소화 하며 진행 할 수 있게 해줬다.</p>
<ul>
<li><strong><em>Data Layer</em></strong></li>
</ul>
<p>DB &amp; 다른 Service 와 CRUD 로직을 담당하는 계층.</p>
<p>ORM 부분에서 팀원들과 정말 많은 의견들을 나눠봤다.</p>
<p>Entity Framework 나 Dapper 를 사용하면 재사용성 &amp; 생산성 같은 여러 장점들이 있지만,</p>
<p>복잡한 Query 를 작성하거나 추후 성능 문제에 관해 대응할만큼의 각 ORM 에 이해도가 깊지 않아,</p>
<p>이 부분에서는 일단 사용하지 않기 합의 보았다.</p>
<h2 id="살-붙이기"><em>살 붙이기</em></h2>
<p><img src="https://velog.velcdn.com/images/dev-hann/post/ef93005d-93e4-436b-a1f5-946a1b50532b/image.png" alt=""></p>
<p>기본적인 API 서버의 뼈대는 거의 완성 되었다. </p>
<p>뼈대를 완성하나니, 이곳저곳 불편한 점이 보였다.</p>
<p>한번 쓰고 버릴 코드라면 감수하고 쓰겠지만, 그게 아닌이상.. </p>
<p>투머치 하지 않게 <strong>‘벌크업’</strong> 하기로 했다.</p>
<h3 id="swagger"><em>Swagger</em></h3>
<p><img src="https://velog.velcdn.com/images/dev-hann/post/42a55033-4bf3-4531-b747-1dbe3659a9e6/image.png" alt=""></p>
<p>힘들게 작성한 코드, 잘 작동하는지 테스트 해봐 하지 않겠는가?</p>
<p>Postman 을 통해 테스트 하고, API 문서도 작성했는데.. </p>
<p>코드가 수정될때마다 업데이트 해줘야하는게 여간 귀찮은게 아니엇다.</p>
<p>결국 코드 기반의 Swagger를 달아주었다. <a href="https://learn.microsoft.com/en-us/aspnet/core/tutorials/getting-started-with-swashbuckle?view=aspnetcore-7.0&amp;tabs=visual-studio">코드 몇줄</a>이면 정말 간단하 사용 할수 있었다.</p>
<p>( 역시 갓 마소.. )</p>
<h3 id="health-check"><em>Health Check</em></h3>
<p><img src="https://velog.velcdn.com/images/dev-hann/post/b82900ba-8b47-48e9-b955-aedf5b9bfbf6/image.png" alt=""></p>
<p>몇몇 고객들은 보안상 이유로 서버를 자사의 로컬 환경에서 서비스 되는 경우도 있다.</p>
<p>이럴 경우 애러 발생하면, Trace 하기도 까다롭고 가끔은 알수없는 애러들이 발생할때가 있다.</p>
<p>심지어 애러 로그에도 기록이 안되있고, 이럴때는 정말 내가 탐정이라도 된 느낌이다.</p>
<p>특히나 로컬 환경의 고객 인경우, (동의 하에) 고객 서비 환경에 원격으로 접속하여 </p>
<p>뭐가 문제인지 하나하나 체크 해봐야 될때가 있는다.</p>
<p>( <a href="https://www.clien.net/service/board/park/15494635">전설의 냉장고때문에 컴퓨터 화면</a>이 꺼지는 일을 아시는가..? )  </p>
<p>이 부분을 고려해서, 동료들에게 채크 리스트를 요청후, </p>
<p>Health Check 기능에 넣어 원격이 아니더라도 1차 진단이 가능하게 만들었다.</p>
<ul>
<li>SQL Server Version</li>
<li>Driver Space</li>
<li>Internet Status</li>
</ul>
<pre><code class="language-jsx">public class ASHealthCheckSQLServer : IHealthCheck
{
    public Task&lt;HealthCheckResult&gt; CheckHealthAsync(HealthCheckContext context, CancellationToken cancellationToken = default)
    {
        try
        {
            var dataBase = DataBase.GetInstance;
            var data = dataBase.Query(&quot;SELECT @@version&quot;);
            var version = data.First();

            IDictionary&lt;string, object&gt; result = new Dictionary&lt;string, object&gt;();
            result[&quot;version&quot;] = version;
            return Task.FromResult(
                HealthCheckResult.Healthy(data: new ReadOnlyDictionary&lt;string, object&gt;(result))
             );
        }
        catch (Exception e)
        {
            return Task.FromResult(
                HealthCheckResult.Unhealthy(exception: e)
            );
        }
    }
}</code></pre>
<p>위 처럼 IHealthCheck 인터페이스를 상속받아, 구현했다.</p>
<pre><code class="language-jsx">services.AddHealthChecks()
.AddCheck&lt;HealthCheckSQLServer&gt;(&quot;DB&quot;)
.AddCheck&lt;HealthCheckDrive&gt;(&quot;Drive&quot;)
.AddCheck&lt;HealthCheckInternet&gt;(&quot;Internet&quot;);
</code></pre>
<p>그리고 위 처럼 서비에 각 서비스를 등록했다. </p>
<pre><code class="language-json">{
  &quot;status&quot;: &quot;Healthy&quot;,
  &quot;totalDuration&quot;: &quot;00:00:00.0795347&quot;,
  &quot;entries&quot;: {
    &quot;DB&quot;: {
      &quot;data&quot;: {
        &quot;version&quot;: &quot;Microsoft Azure SQL Edge Developer (RTM) - 15.0.2000.1565 (ARM64) \n\tJun 14 2022 00:37:12 \n\tCopyright (C) 2019 Microsoft Corporation\n\tLinux (Ubuntu 18.04.6 LTS aarch64) &lt;ARM64&gt;&quot;
      },
      &quot;duration&quot;: &quot;00:00:00.0082505&quot;,
      &quot;status&quot;: &quot;Healthy&quot;,
      &quot;tags&quot;: []
    },
    &quot;Drive&quot;: {
      &quot;data&quot;: {
        &quot;driveList&quot;: [
          {
            &quot;name&quot;: &quot;/&quot;,
            &quot;freeSize&quot;: 215112691712,
            &quot;totalSize&quot;: 494384795648
          },
          {
            &quot;name&quot;: &quot;/System/Volumes/VM&quot;,
            &quot;freeSize&quot;: 215112691712,
            &quot;totalSize&quot;: 494384795648
          },
         // skip..
          {
            &quot;name&quot;: &quot;/System/Volumes/Data/home&quot;,
            &quot;freeSize&quot;: 0,
            &quot;totalSize&quot;: 0
          }
        ]
      },
      &quot;duration&quot;: &quot;00:00:00.0002856&quot;,
      &quot;status&quot;: &quot;Healthy&quot;,
      &quot;tags&quot;: []
    },
    &quot;Internet&quot;: {
      &quot;data&quot;: {},
      &quot;duration&quot;: &quot;00:00:00.0791675&quot;,
      &quot;status&quot;: &quot;Healthy&quot;,
      &quot;tags&quot;: []
    }
  }
}</code></pre>
<p>다시한번 느끼는 거지만, 마소의 설계 정말 감동한다.</p>
<p>Middleware 나, Service 등록 등 정말 간편하게 사용할 수 있게 개발자들을 배려 한게 느껴졌다.</p>
<p>추후 이 API 를 통해서 HealthCheck UI page 를 만들 계획이다.</p>
<h2 id="마치며"><em>마치며</em></h2>
<p><img src="https://velog.velcdn.com/images/dev-hann/post/ed75b140-99ca-44ea-ba4f-466593b977c6/image.png" alt=""></p>
<p>글에 다 담진 못했지만, 개발 과정에서 많은 고민과 노력이 있었다. </p>
<p>기술적인 부분뿐만 아니라, 팀원 &amp; 일정 관리등.. 여러 부분에서 신경쓸게 많았다. </p>
<p>그래도 끝까지 믿고 따라와준 팀원에게 고마울 뿐이다.</p>
<p>돌아보면 개발 기간동안 고생한 만큼 성장했고, 단단해졌다.</p>
<p>그리고 <strong>‘성숙한 리더’</strong> 가 무엇인지 돌아볼수있는 기회이기도 했다.</p>
<p>소중했던 경험들중 <strong>기술적인 부분</strong>만 이 글을 통해 공유해보고자 한다.</p>
<p>( 팀원들과 있었던 이야기는 다른 글로 찾아뵙겠습니다 )</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[누들누들 타이쿤 (feat, 의존성 주입) (2)]]></title>
            <link>https://velog.io/@dev-hann/dependency-injection-2</link>
            <guid>https://velog.io/@dev-hann/dependency-injection-2</guid>
            <pubDate>Thu, 21 Sep 2023 06:05:54 GMT</pubDate>
            <description><![CDATA[<h2 id="answer-noodle-구현-방법의-정답은">Answer) Noodle 구현 방법의 정답은..</h2>
<p>(1) Method 1 ⇒ 추상화 패턴
(2) Method 2 ⇒ 팩토리 패턴</p>
<pre><code class="language-dart">// Method 1
abstract class Noodle {
  Noodle({
    required this.name,
    required this.waterML,
    required this.cookTime,
  });
  final String name;
  final int waterML;
  final Duration cookTime;
  bool _cook = false;
  bool get isCooked =&gt; _cook;
  void cook() {
    _cook = true;
  }
}

class SinNoodle extends Noodle {
  SinNoodle()
      : super(
          name: &quot;Sin&quot;,
          waterML: 550,
          cookTime: const Duration(minutes: 5, seconds: 30),
        );
}

// Method 2
class Noodle {
  Noodle({
    required this.name,
    required this.waterML,
    required this.cookTime,
  });
  final String name;
  final int waterML;
  final Duration cookTime;
  bool _cook = false;
  bool get isCooked =&gt; _cook;
  void cook() {
    _cook = true;
  }

  factory Noodle.sin() {
    return Noodle(
      name: &quot;Sin&quot;,
      waterML: 550,
      cookTime: const Duration(minutes: 5, seconds: 30),
    );
  }
}</code></pre>
<p><strong>추상화</strong>를 통한 <strong>상속관계</strong>가 맞지 않을까 생각합니다!
팩토리 패턴의 목적은 생성 과정을 캡슐화 하는것 이며, </p>
<blockquote>
<p>‘Noodle’ 과 ‘SinNoodle’ 은 is-a 관계이기에 
추상화를 통한 상속이 더 적합하다고 생각되네요.</p>
</blockquote>
<p>다른 의견은 댓글로 남겨주시면 감사하겠습니다!</p>
<h2 id="awesome-한-학생관리-프로그램">Awesome 한 학생관리 프로그램</h2>
<p>그럼 다시 학생 관리 프로그램으로 돌아와서..</p>
<pre><code class="language-dart">//
class Student {
  Student({
    required this.name,
    required this.className,
  });
  final String name;
  final String className;
}

//
Widget studentListTile({
    required String name,
    required String className,
  }) {
    return ListTile(
      title: Text(name),
      subtitle: Text(className),
    );
 }</code></pre>
<p>그럼 일단 ‘studentListTile’ 이 오용되는것을 막아줍시다.
(누가 잘못했는지 따지기 보단, 다시 발생하지 않도록 하는게 중요하다고 생각합니다)
앞에 배운 의존성 주입을 이용해서.. 따란~</p>
<pre><code class="language-dart">Widget studentListTile({
    required Student student,
  }) {
    return ListTile(
      title: Text(student.name),
      subtitle: Text(student.className),
    );
 }</code></pre>
<p>‘studentListTile’ 에 ‘Student’ 의 의존성을 주입해줌으로써, 
이제 ‘Message’ 는 이제 들어올수 없어요! </p>
<p>하지만 이렇게 <strong>콘크리트 클래스</strong>에 의존할경우 재사용성이 떨어집니다.
(기계만 수백대인 윤사장님 라면가게처럼..) </p>
<p>그렇기에 한단계 추상화를 해주면 조금 더 유연한 코드가 되지 않을까 합니다.
나머지 부분은 여러분이 직접 완성해보세요!</p>
<h2 id="마치며">마치며</h2>
<p>이 글을 쓰면서, DI(Dependency Injection) 에 관련된 글을 많이 찾아봤는데, 
생각보다 많은 글들이 이해하기 어렵고, 1차원적인 설명뿐이라 조금 아쉬웠습니다.
<strong>어떻게 적용하면 실전에서 잘 사용할 수 있을까?</strong>  라는 출발점에서 고민하다 
윤사장님의 라면가게에 빗대어 풀어봤습니다. 기술적 의미가 잘 전달 됐으면 좋겠네요.
끝으로 윤사장님 라면가게 대성하시길 바라겠습니다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[누들누들 타이쿤 (feat, 의존성 주입) (1)]]></title>
            <link>https://velog.io/@dev-hann/dependency-injection-1</link>
            <guid>https://velog.io/@dev-hann/dependency-injection-1</guid>
            <pubDate>Thu, 21 Sep 2023 05:52:00 GMT</pubDate>
            <description><![CDATA[<h2 id="intro"><em>intro</em></h2>
<p>오늘도 열심히 학생 관리 프로그램을 만들던 Hann님..</p>
<pre><code class="language-dart">//
class Student {
  Student({
    required this.name,
    required this.className,
  });
  final String name;
  final String className;
}

//
Widget studentListTile({
    required String name,
    required String className,
  }) {
    return ListTile(
      title: Text(name),
      subtitle: Text(className),
    );
 }</code></pre>
<p>‘Student’ 모델을 만들고, 
Awesome 한 화면을 만들기 위해 ‘studentListTile’ 을 만들었습니다.</p>
<pre><code class="language-dart">// student model
final hann = Student(
    name: &quot;Hann&quot;,
    className: &quot;dev&quot;,
  );

// student listTile Widget
 return studentListTile(
    name: hann.name,
    className: hann.className,
 );</code></pre>
<p>그리고 ‘hann’ 이라는 &#39;Student&#39; 를 만들고,
‘studentListTile’ 에 넣어줬습니다. 따란~</p>
<p><img src="https://velog.velcdn.com/images/dev-hann/post/2f2d4891-1560-491b-a605-413688da21dd/image.jpg" alt=""></p>
<p>그렇게 시간이 흘러..</p>
<pre><code class="language-dart">// Message model
final msg = Message(
        title=&quot;Hello&quot;,
        text=&quot;World!&quot;,
    );

// student? message? listTile Widget
return studentListTile(
   name: msg.title,
   className: msg.text,
);</code></pre>
<p>미래의 Hann님은 채팅 기능을 추가하던중,  Awesome 한 Widget을 발견했다며, 
‘Message’ 를 냅다 ‘studentListTile‘에 넣어버렸습니다.</p>
<p><img src="https://velog.velcdn.com/images/dev-hann/post/a89ea96c-774c-4cae-aeff-636253eae33d/image.png" alt="">
그럼 이건 studentListTile 일까요? messageListTile 일까요?</p>
<p>이런 사태를 어떻게 방지할수 있을까요?</p>
<hr>
<h2 id="누들누들-타이쿤">누들누들 타이쿤</h2>
<h3 id="신장개업">신장개업</h3>
<p>(앞에 대충 라면 좋아하는 윤사장님 성장 스토리..)
결국 윤사장님은 그렇게 라면가게를 시작하는데..
신.장.개.업! 뚜둥
<img src="https://velog.velcdn.com/images/dev-hann/post/373f753c-ba77-4bec-9580-19e069935e33/image.png" alt="">
&lt;MBC 예능 신동엽의 신장개업&gt;</p>
<h3 id="고민">고민</h3>
<p>뒷골목식당 애청자인 윤사장님은 그분의 말에따라 단일 메뉴 식당을 하기로 결심합니다.<br>단일 메뉴 <strong>‘신라면’</strong> 을 끓이기 위해, 라면 끓이는 기계를 산 윤사장님.</p>
<pre><code class="language-dart">class SinNoodle {
  final String name = &quot;Sin&quot;;
  final int waterML = 550;
  final Duration cookTime = const Duration(minutes: 5, seconds: 30);
  bool _cooked = false;
  bool get isCooked =&gt; _cooked;
  void cook() {
    _cooked = true;
  }
}

class SinNoodleMachine {
  Future&lt;SinNoodle&gt; cook(SinNoodle noodle) async {
    print(&quot;Start cook ${noodle.name} Noodle&quot;);
    await Future.delayed(noodle.cookTime);
    noodle.cook();
    return noodle;
  }
}</code></pre>
<p>단일 메뉴니까 신라면만 끓이는 기계면 되겠지? 라는 생각으로
<strong>의존성 높은</strong> ‘SinNoodleMachine’ 을 구매합니다.</p>
<blockquote>
<p>‘SinNoodle’ 이 코드가 바뀌면 ‘SinNoodleMachine’ 의 코드도 바뀔 수 있기에, 이를 의존 한다고 표현합니다.</p>
</blockquote>
<p>그렇게 가게는 번성하고, 행복하게 살았답니다.. 면 좋겠지만,</p>
<p><img src="https://velog.velcdn.com/images/dev-hann/post/35ddeb2d-315d-4eef-baf2-deca2b28bb2f/image.png" alt="">
&lt;윈터 이즈 커밍&gt;</p>
<p>겨울이 다가오고.. 손님들은 뜨끈한 우동을 찾기 시작했습니다.
결국 우동을 끓이는 기계를 사기로 합니다.</p>
<pre><code class="language-dart">class WudongNoodle {
  final String name = &quot;Wudong&quot;;
  final int waterML = 600;
  final Duration cookTime = const Duration(minutes: 6, seconds: 00);
  bool _cooked = false;
  bool get isCooked =&gt; _cooked;
  void cook() {
    _cooked = true;
  }
}

class WudongNoodleMachine {
  Future&lt;WudongNoodle&gt; cook(WudongNoodle noodle) async {
    print(&quot;Start cook ${noodle.name} Noodle&quot;);
    await Future.delayed(noodle.cookTime);
    noodle.cook();
    return noodle;
  }
}</code></pre>
<p>이렇게 메뉴가 하나 둘씩 늘어나며, 관리해야하는 기계가 늘어났습니다. 
그렇게 겨울이 지나고 우동을 찾는 손님이 잦아들며, 우동 기계는 먼지만 쌓였습니다.
그렇게 시간이 지나 여름이 되자 새콤달콤 냉면을 찾는 손님이 늘고.. 
결국 냉면 기계를 사들이고..
가게는 어느새 각종 기계들로 가득찼습니다.  면 끓이는 기계의 효율성이 너무 낮았습니다. </p>
<blockquote>
<p>프로그래밍에선 코드의 재사용성이 낮다 라고 합니다.</p>
</blockquote>
<p>결국 이도저도 못하고 .. 고민하던 윤사장님에게.. 한통의 문자가 도착합니다.</p>
<h3 id="희망">희망</h3>
<p>[광고] 이 기계는 무료로 해줍니다!
어떤 면이든 넣기만 하면 알아서 끓여주는 뉴-우 제너레이션 머신!</p>
<pre><code class="language-dart">class NewGenerationMachine {
  Future&lt;Noodle&gt; cook(Noodle noodle) async {
    print(&quot;Start cook ${noodle.name}&quot;);
    await Future.delayed(noodle.cookTime);
    noodle.cook();
    return noodle;
  }
}</code></pre>
<p>그런데 작은 문제점이 하나 있습니다. 면이 조금 특별해야합니다.</p>
<h3 id="quiz-그러면-noodle-은-어떻게-만들어-줘야할까요">Quiz) 그러면 Noodle 은 어떻게 만들어 줘야할까요?</h3>
<p>(1) Method 1 ⇒ 추상화 패턴
(2) Method 2 ⇒ 팩토리 패턴</p>
<pre><code class="language-dart">// Method 1
abstract class Noodle {
  Noodle({
    required this.name,
    required this.waterML,
    required this.cookTime,
  });
  final String name;
  final int waterML;
  final Duration cookTime;
  bool _cook = false;
  bool get isCooked =&gt; _cook;
  void cook() {
    _cook = true;
  }
}

class SinNoodle extends Noodle {
  SinNoodle()
      : super(
          name: &quot;Sin&quot;,
          waterML: 550,
          cookTime: const Duration(minutes: 5, seconds: 30),
        );
}

// Method 2
class Noodle {
  Noodle({
    required this.name,
    required this.waterML,
    required this.cookTime,
  });
  final String name;
  final int waterML;
  final Duration cookTime;
  bool _cook = false;
  bool get isCooked =&gt; _cook;
  void cook() {
    _cook = true;
  }

  factory Noodle.sin() {
    return Noodle(
      name: &quot;Sin&quot;,
      waterML: 550,
      cookTime: const Duration(minutes: 5, seconds: 30),
    );
  }
}</code></pre>
<h3 id="커밍쑨">커밍쑨!</h3>
<p>정답과 이유를 한번 잘 생각해보시고 다음 글에서 다시 찾아뵙겠습니다!</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Jenkins를 이용한 Flutter 배포 자동화(feat. Fastlane)]]></title>
            <link>https://velog.io/@dev-hann/Jenkins</link>
            <guid>https://velog.io/@dev-hann/Jenkins</guid>
            <pubDate>Wed, 20 Sep 2023 11:55:52 GMT</pubDate>
            <description><![CDATA[<h3 id="intro"><em>Intro</em></h3>
<p>내가 Jenkins를 처음 만난건 첫 회사에서였다.
Flutter 로 앱을 개발하여 IOS 와 AOS 두 플랫폼 배포를 해야하니,
한두번은 그러려니 하겠지만, 매번 배포하려면 꽤나 귀찮고 오랜시간이 걸렸다.
(특히 IOS는 TestFlight를 통해 배포했는데, 시간낭비가 유독 심했다)
결국 배포 자동화를 결심했고, 내가 상황에 딱 맞는 Jenkins 를 발견했다.</p>
<blockquote>
<p>이 글은 Jenkins 의 관한 자세한 설치 &amp; 사용법은 설명하지 않습니다.
전체적인 흐름도 &amp; 파이프 라인 코어부분만 설명하려 합니다.</p>
</blockquote>
<h3 id="나만의-집사-jenkins처럼"><em>나만의 집사 &#39;Jenkins처럼&#39;</em></h3>
<p>위 사진 좌측에 깔끔한 양복차림의 집사님(?)이 바로 Jenkins.
Jenkins 의 탄생은 이렇다. Hudson 이란 CI 툴이 Oracle 에 인수된후,
원래 개발자들이 Hudsond을 포크해서 탄생한게 Jenkins 라고 한다.</p>
<img width="100%" src="https://upload.wikimedia.org/wikipedia/commons/thumb/0/07/Hudson_Screenshot.png/500px-Hudson_Screenshot.png">
Hudson(Jenkins 선조격 CI 툴)


<hr>
<p>Jenkins 작업 단위는 Job 이라 부르며, Job 의 내부는 크게 2 부분으로 나뉜다.</p>
<ul>
<li>Build Triggers</li>
<li>Pipeline</li>
</ul>
<h3 id="build-triggers">Build Triggers</h3>
<p>개인적으로 Jenkins 를 사용하는 가장 큰 이유중 하나인 Build Triggers.
설정된 Trigger 의 조건이 만족되면, Pipeline의 스크립트가 실행되는 방식인데,
여러 옵션중, ‘Build Periodically’ 를 선택해준다.
<a href="https://crontab.cronhub.io/">Unix cron</a> 포멧으로 일정 시간 간격으로 Trigger 해준다.</p>
<img src ="https://velog.velcdn.com/images/flutter_dev_han/post/c431fc6a-df58-4049-899f-0344ebe707ae/image.png">
<평일 오후 8시 정각으로 맞춰놨다. (퇴근후 자동빌드)>


<h3 id="pipeline">Pipeline</h3>
<p>이제 Pipeline 을 작성할 차례이다. 파이프 라인에 대해서 자세한건 <a href="https://www.jenkins.io/doc/book/pipeline/">여기</a>를 참고바란다.</p>
<img src="https://velog.velcdn.com/images/flutter_dev_han/post/812c95a6-e2f8-4764-81d0-dc62ecaa58f6/image.png">
<전체 구조 흐름도>


<blockquote>
<p>일단 전체 Pipneline 스크립트부터 투척!
대충 훑어본후 각 부분별로 톺아보자</p>
</blockquote>
<pre><code class="language-groovy">pipeline {
    agent any

    environment {
        WORK_DIRECTORY = &#39;repo_name&#39;
        GIT_TOKEN = &#39;GitToken&#39;
        FAILURE_STAGE=&#39;&#39;
        PUSH_MESSAGE=&#39;&#39;
    }

    stages {
        stage(&#39;Warm Up&#39;) {
            steps {
                script{
                    if (!fileExists(WORK_DIRECTORY)) {
                        echo &quot;Not Exist Repository.&quot;
                        sh &#39;git clone &lt;https://${GIT_TOKEN}@github.com/dev-hann/repo_name.git&gt;&#39;
                    }
                }
                dir(WORK_DIRECTORY) {
                    sh &quot;git pull&quot;
                }
            }
            post {
                failure {
                    script{
                        FAILURE_STAGE=&quot;Warm Up&quot;
                    }
                }
            }
        }
        stage(&#39;Run Test&#39;) {
            steps {
                dir(WORK_DIRECTORY) {
                    sh &quot;flutter test&quot;
                }
            }
            post {
                failure {
                    script{
                        FAILURE_STAGE=&quot;Run Test&quot;
                    }
                }
            }
        }
        stage(&#39;Build/Deploy&#39;) {
            steps {
                dir(&#39;${WORK_DIRECTORY}/ios&#39;) {
                    sh &quot;fastlane deploy&quot;
                }
                dir(&#39;${WORK_DIRECTORY}/android&#39;) {
                    sh &quot;fastlane deploy&quot;
                }
            }
            post {
                failure {
                    script{
                        FAILURE_STAGE=&quot;Build/Deploy&quot;
                    }
                }
            }
        }
    }
    post{
        failure {
            script{
                def message = &quot;Fail on ${FAILURE_STAGE} stage.&quot;
                PUSH_MESSAGE+=&quot;\\n ${message}&quot;
            }
            echo message
        }
        success {
            script{
                def message = &quot;Succsee CI &amp; CD.&quot;
                PUSH_MESSAGE+=&quot;\\n ${message}&quot;
            }
            echo message
        }
        always{
            // push Line message with [PUSH_MESSAGE]
            echo PUSH_MESSAGE
        }
    }
}
</code></pre>
<h3 id="environment">Environment</h3>
<pre><code class="language-groovy">environment {
        WORK_DIRECTORY = &#39;repo_name&#39;
        GIT_TOKEN = &#39;GitToken&#39;
        FAILURE_STAGE=&#39;&#39;
        PUSH_MESSAGE=&#39;&#39;
}
</code></pre>
<p>스크립트의 전역 변수 선언부이다.</p>
<ul>
<li>WORK_DIRECTORY: Git 에서 받아온 Repository 의 경로</li>
<li>GIT_TOKEN: Repository 가 Private 인 관계로 토큰을 통해 인증한다.</li>
<li>FAILURE_STAGE: 애러 발생시 해당 스테이지의 이름을 저장한다.</li>
<li>PUSH_MESSAGE: 빌드가 완료 되면, 결과를 저장한다.</li>
</ul>
<h3 id="warm-up">Warm Up</h3>
<pre><code class="language-groovy">stage(&#39;Warm Up&#39;) {
    steps {
        script{
            if (!fileExists(WORK_DIRECTORY)) {
                echo &quot;Not Exist Repository.&quot;
                sh &#39;git clone &lt;https://${GIT_TOKEN}@github.com/dev-hann/repo_name.git&gt;&#39;
            }
        }
        dir(WORK_DIRECTORY) {
            sh &quot;git pull&quot;
        }
    }
    post {
        failure {
            script{
                FAILURE_STAGE=&quot;Warm Up&quot;
            }
        }
    }
}
</code></pre>
<p>Git 에 커밋된 최신 코드를 Clone &amp; Pull 해오는 과정이다.
(Repository 가 존재할시, pull, 존재하지 않으면 clone 해온다)
외부 플러그인을 써도 되지만, 간단한 작업이기에 Shell을 사용했다.</p>
<h3 id="run-test">Run Test</h3>
<pre><code class="language-groovy">stage(&#39;Run Test&#39;) {
    steps {
        dir(WORK_DIRECTORY) {
            sh &quot;flutter test&quot;
        }
    }
    post {
        failure {
            script{
                FAILURE_STAGE=&quot;Run Test&quot;
            }
        }
    }
}
</code></pre>
<p><strong>flutter cli</strong>를 통해 이미 작성된 테스트 코드를 실행하는 부분이다.</p>
<p><em>테스트 코드를 작성을 습관화 합시다.</em></p>
<h3 id="build--deploy">Build / Deploy</h3>
<pre><code class="language-groovy">stage(&#39;Build/Deploy&#39;) {
    steps {
        dir(&#39;${WORK_DIRECTORY}/ios&#39;) {
            sh &quot;fastlane deploy&quot;
        }
        dir(&#39;${WORK_DIRECTORY}/android&#39;) {
            sh &quot;fastlane deploy&quot;
        }
    }
    post {
        failure {
            script{
                FAILURE_STAGE=&quot;Build/Deploy&quot;
            }
        }
    }
}
</code></pre>
<p>Fastlane 을 통해 Build / Deploy 를 처리했다.
이유인 즉, Build / Deploy 동작은 수동적으로 진행할 경우가 있고,
플랫폼 별로 진행 될수도 있기에 (그밖에 여러 변수들이 존재) Fastlane에 의존했다.
(Jenkins 에서 fastlane 을 못찾을경우, 따로 한경변수 설정을 해줘야한다)</p>
<h3 id="post-actions">Post Actions</h3>
<pre><code class="language-groovy">post {
    failure {
        script{
            def message = &quot;Fail on ${FAILURE_STAGE} stage.&quot;
            PUSH_MESSAGE+=&quot;\\n ${message}&quot;
        }
        echo message
    }
    success {
        script{
            def message = &quot;Succsee CI &amp; CD.&quot;
            PUSH_MESSAGE+=&quot;\\n ${message}&quot;
        }
        echo message
    }
    always{
        // push Line message with [PUSH_MESSAGE]
        echo PUSH_MESSAGE
    }
}
</code></pre>
<p>Golang을 통해 간단한 Line 메세지를 보내는 프로그램을 만들어
결과를 바탕으로 성공 &amp; 실패 메세지를 통보해준다</p>
<h3 id="커밍순">커밍순..</h3>
<center>
<img src="https://t1.daumcdn.net/thumb/R720x0/?fname=http://t1.daumcdn.net/brunch/service/user/1p8k/image/jWQ66AHx46HIuzZyhY6DaAPbdww">
</center>

<p>사실 Jenkins 기능의 대부분도 사용 못하였고,
결정적으로 Framework 에 대한 이해를 전혀 하지 못했다.
(사용은 했지만, 어떻게 돌아가는지 내부 구조 파악을 전혀 하지 못했다)
이렇게 사용하는건 화장실 나온뒤 뒷처리를 안한 느낌이다.
나중에 꼭 Jenkins 톺아보기 시리즈 만들어보겠다.</p>
]]></description>
        </item>
    </channel>
</rss>