<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>dev.sia.log</title>
        <link>https://velog.io/</link>
        <description></description>
        <lastBuildDate>Fri, 19 Sep 2025 06:10:03 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <image>
            <title>dev.sia.log</title>
            <url>https://velog.velcdn.com/images/mr_brightside/profile/9dc7eaaf-04d2-437f-947d-08092001e139/image.jpeg</url>
            <link>https://velog.io/</link>
        </image>
        <copyright>Copyright (C) 2019. dev.sia.log. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/mr_brightside" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[클로드 코드(Claude Code) 다운그레이드하기]]></title>
            <link>https://velog.io/@mr_brightside/%ED%81%B4%EB%A1%9C%EB%93%9C-%EC%BD%94%EB%93%9CClaude-Code-%EB%8B%A4%EC%9A%B4%EA%B7%B8%EB%A0%88%EC%9D%B4%EB%93%9C%ED%95%98%EA%B8%B0</link>
            <guid>https://velog.io/@mr_brightside/%ED%81%B4%EB%A1%9C%EB%93%9C-%EC%BD%94%EB%93%9CClaude-Code-%EB%8B%A4%EC%9A%B4%EA%B7%B8%EB%A0%88%EC%9D%B4%EB%93%9C%ED%95%98%EA%B8%B0</guid>
            <pubDate>Fri, 19 Sep 2025 06:10:03 GMT</pubDate>
            <description><![CDATA[<p>최근 클로드 코드의 품질이 저하됨을 느끼던 차였는데, 트위터에서 클로드 코드 버전을 1.0.88로 다운그레이드하면 성능이 다시 좋아진다는 글을 보게 되었다.</p>
<p>호기심이 생겨 다운그레이드를 진행해보기로 했다.</p>
<p>처음엔 구글링 해서 나온 블로그 글을 보고 단순히 npm uninstall 후 원하는 버전을 설치하면 끝날 줄 알았다... 하지만 실제로는 삭제가 제대로 되지 않았다.</p>
<p>⸻</p>
<h2 id="시도했던-방법">시도했던 방법</h2>
<p>처음 구글링한 방법은 아주 단순했다.</p>
<pre><code class="language-bash">### npm
npm uninstall -g @anthropic-ai/claude-code
npm install -g @anthropic-ai/claude-code@1.0.88</code></pre>
<pre><code class="language-bash">### brew
brew uninstall claude-code
brew install claude-code@1.0.88</code></pre>
<p>이 방법만 따르면 문제없이 될 줄 알았는데... 내 환경에서는 조금 달랐다.</p>
<p>⸻</p>
<h3 id="1-문제가-발생했다">1. 문제가 발생했다</h3>
<p>삭제를 했는데도 claude 버전이 계속 최신 버전으로 고정되었다.
즉, 다른 경로 어딘가에 여전히 실행 파일과 패키지가 남아 있었던 것이다.
nvm을 설치하고 관리하며 zsh에서 자바 경로 설정 문제를 겪은 적이 있었다.
비슷한 삽질을 했던 경험이 있어서 이번에도 결국 경로 꼬임이 문제일 거라고 짐작했다.</p>
<p>⸻</p>
<h3 id="2-삽질의-흔적">2. 삽질의 흔적</h3>
<p>다음과 같은 시도들이 있었다......</p>
<pre><code class="language-bash">2005  npm uninstall -g @anthropic-ai/claude-code
2006  npm install -g @anthropic-ai/claude-code@1.0.88
2013  brew uninstall claude-code
2020  sudo rm -rf /Users/johndoe/.npm-global/lib/node_modules/@anthropic-ai/claude-code
2022  rm -f ~/.npm-global/bin/claude
2097  sudo rm -rf /Users/johndoe/.nvm/versions/node/v20.10.0/lib/node_modules/@anthropic-ai/claude-code
2101  sudo rm -rf /Users/johndoe/.nvm/versions/node/v20.10.0/lib/node_modules/@anthropic-ai/claude-code
2103  which claude
2105  npm install -g @anthropic-ai/claude-code@1.0.88
2106  which claude
2110  claude --version</code></pre>
<p>여기서 중요한 단서를 발견했다. .npm-global 경로를 지우려 계속 시도했지만, 사실 내 컴퓨터에는 그 디렉토리가 아예 존재하지 않았다. 그걸 어떻게 알았냐면</p>
<p>cd ~/.npm-global을 찍어봤는데,
</br>
<strong>cd: no such file or directory: /Users/johndoe/.npm-global</strong>
</br>
???</p>
<p>나는 없는 디렉토리를 지우려고 헛수고를 하고 있었던 것이다.</p>
<p>⸻</p>
<h3 id="3-원인과-해결">3. 원인과 해결</h3>
<p>아... 나 nvm쓰지;</p>
<p>결국 진짜 원인은 내 환경에서 nvm을 사용하고 있다는 사실이었다.</p>
<p>패키지가 설치된 위치는 <code>~/.nvm/versions/node/v20.10.0/lib/node_modules/</code> 아래였다.</p>
<hr>
<h2 id="해결-과정">해결 과정</h2>
<h3 id="1----실행-경로-확인">1.    실행 경로 확인</h3>
<pre><code class="language-bash">which claude
# -&gt; /Users/johndoe/.nvm/versions/node/v20.10.0/bin/claude</code></pre>
<h3 id="2----실제-설치-경로에서-삭제">2.    실제 설치 경로에서 삭제</h3>
<pre><code class="language-bash">sudo rm -rf ~/.nvm/versions/node/v20.10.0/lib/node_modules/@anthropic-ai/claude-code</code></pre>
<h3 id="3----원하는-버전1088-재설치">3.    원하는 버전(1.0.88) 재설치</h3>
<pre><code class="language-bash">npm install -g @anthropic-ai/claude-code@1.0.88</code></pre>
<h3 id="4----정상-확인">4.    정상 확인</h3>
<pre><code class="language-bash">claude --version
# -&gt; 1.0.88</code></pre>
<p>⸻</p>
<h2 id="느낀-점">느낀 점</h2>
<p>•    삭제가 안 될 때는 먼저 which로 실행 경로를 확인했어야 하는데... nvm에서 JAVA_HOME 경로 설정해 줄 때랑 데자뷰가 느껴졌음
•    경로를 의심하기 전에, 실제로 그 디렉토리가 존재하는지 먼저 확인할 것
•    nvm을 쓰는 환경에서는 전역 패키지가 <code>.nvm/versions/node/.../lib/node_modules/</code>에 설치된다는 점을 기억해야 했다.</p>
<hr>
<p>다운그레이드 해서 사용해보니 확실히 응답의 품질이 달라진 것 같다.
클로드 업데이트가 안정화될 때 까지는 당분간 1.0.88 버전으로 사용해야 할 듯!</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[프론트엔드에서의 테스트 - 실무에서 작성해보며 느낀 점]]></title>
            <link>https://velog.io/@mr_brightside/%ED%94%84%EB%A1%A0%ED%8A%B8%EC%97%94%EB%93%9C%EC%97%90%EC%84%9C%EC%9D%98-%ED%85%8C%EC%8A%A4%ED%8A%B8-%EC%8B%A4%EB%AC%B4%EC%97%90%EC%84%9C-%EC%9E%91%EC%84%B1%ED%95%B4%EB%B3%B4%EB%A9%B0-%EB%8A%90%EB%82%80-%EC%A0%90</link>
            <guid>https://velog.io/@mr_brightside/%ED%94%84%EB%A1%A0%ED%8A%B8%EC%97%94%EB%93%9C%EC%97%90%EC%84%9C%EC%9D%98-%ED%85%8C%EC%8A%A4%ED%8A%B8-%EC%8B%A4%EB%AC%B4%EC%97%90%EC%84%9C-%EC%9E%91%EC%84%B1%ED%95%B4%EB%B3%B4%EB%A9%B0-%EB%8A%90%EB%82%80-%EC%A0%90</guid>
            <pubDate>Fri, 05 Sep 2025 07:35:26 GMT</pubDate>
            <description><![CDATA[<p>새롭게 회사 어드민 페이지에서 관심매물 CRUD 기능을 개발하게 되었다.</p>
<p>우리 서비스는 아직 QA팀이 따로 없고, 기존 코드베이스도 테스트 커버리지가 없었던 상태였다.</p>
<p>최근에 jest와 playwright 환경을 구축했으니, 신규 기능부터라도 테스트 작성을 병행하기로 다짐했다.</p>
<p>기능 개발과 함께 테스트 작성을 해보며 느낀 점과 테스트가 어떻게 도움이 되었는지 정리해보려고 한다.</p>
<p>⸻</p>
<h3 id="1-테스트-작성은-품이-든다">1. 테스트 작성은 품이 든다</h3>
<p>테스트를 작성과 기능 개발을 같이 하려니 초기 개발 속도는 느려지긴 하는 것 같았다.</p>
<p>하지만 장기적으로 유지보수나 리팩토링을 고려하면 테스트를 작성하는 리소스가 훨씬 적다고 생각했다.
특히 요즘같은 AI 시대에... 바이브 코딩을 하려면 테스트가 필수적이라고 다시금 느꼈다.</p>
<p>⸻</p>
<h3 id="2-playwright로-qa-자동화">2. Playwright로 QA 자동화</h3>
<p>Playwright를 사용하면서 가장 크게 느낀 점은 반복적인 테스트 과정을 자동화할 수 있다는 점이었다.
기존에는 직접 브라우저를 켜고 번거롭게 일일이 확인해야 했는데, 이제는 스크립트만으로 정말 브라우저에서 테스트하는 것 처럼 대체할 수 있다니 신세계였다.</p>
<p>특히 우리처럼 아직 QA팀이 없는 환경에서는, 개발 시간 단축에 크게 도움이 됐다!</p>
<p><img src="https://velog.velcdn.com/images/mr_brightside/post/6f06016d-c5b5-43e6-a1eb-3536d72bf2c1/image.gif" alt="">
<em>나 대신 QA 뺑뺑이 돌려주는 Playwright야 고마워...</em> </p>
<p><img src="https://velog.velcdn.com/images/mr_brightside/post/6d1fc1ad-5942-4683-8981-a592795e25fa/image.png" alt=""></p>
<p>But! 초반에 성능 문제가 있었다. 초기 로그인 시나리오 테스트가 지나치게 느려서 30초 타임아웃 에러가 발생하기도 했다 💧</p>
<p>로그인 등 공통 시나리오는 최대한 캐싱/최적화하고, 모든 케이스를 커버하기보다는 핵심 시나리오 위주로 작성해야겠다는 교훈을 얻었다.</p>
<p>⸻</p>
<h3 id="3-유닛-vs-통합-테스트-범위-기준">3. 유닛 vs 통합 테스트 범위 기준</h3>
<p>테스트를 공부하며 가장 혼란스러웠던 부분은 유닛 테스트와 통합(E2E) 테스트의 작성 범위였는데, 직접 작성해 보며 나름의 기준이 생겼다.
    •    구체적이고 사소한 동작 (버튼 클릭 시 상태 변경, util 함수 결과 등) → 유닛 테스트 (Jest)
    •    전체적인 시나리오 (관심매물 등록 → 수정 → 삭제 흐름) → 통합/E2E 테스트 (Playwright)</p>
<p>먼저 테스트를 작성할 경계를 잡고 나니, 새로운 테스트를 작성할 때 테스트 케이스를 고민하는 시간이 줄었다.
앞으로는 팀원들의 합의를 구하고 어느정도 정형화된 컨벤션으로 굳혀두면 좋을 것 같았다.</p>
<p>⸻</p>
<h3 id="4-실제로-테스트가-도움이-된-사례">4. 실제로 테스트가 도움이 된 사례</h3>
<p><img src="https://velog.velcdn.com/images/mr_brightside/post/ca0e8f3f-27b3-4098-8c32-e61c36a8dcfc/image.gif" alt="">
Jest와 React-Testing-Library로 작성한 유닛테스트.
생각보다 성능이 만족스럽고, 도움도 많이 주었다.</p>
<h4 id="4-1-사소하지만-ui-에러를-조기에-잡아냄">4-1. 사소하지만 UI 에러를 조기에 잡아냄!</h4>
<p>테스트 작성 덕분에 놓칠 뻔한 UI 버그를 발견했다.
관심매물 카드에서 가격 변동이 없는 경우 화살표(up, down)가 표시되면 안 되는데, 직접 개발할 때는 그 부분을 놓쳤다.</p>
<p>작성한 유닛 테스트가 실패하면서 문제를 바로 확인할 수 있었고, 코드를 수정해 반영했다.</p>
<pre><code class="language-javascript">it(&#39;변동 정보가 없을 때 화살표가 표시되지 않는지 확인&#39;, () =&gt; {
  const apartmentDataWithoutDeltas: typeof mockApartmentData = {
    ...mockApartmentData,
    salePrice: {
      ...mockApartmentData.salePrice,
      salePriceMinDealPrice: 75000,
      salePriceMaxDealPrice: 75000,
    },
  };

  render(
    &lt;ComplexPriceCard
      apartmentData={apartmentDataWithoutDeltas}
      expanded={true}
    /&gt;
  );

  const priceSection = screen.getAllByText(&#39;7억 5,000&#39;)[0].closest(&#39;div&#39;);
  const arrowImages = priceSection?.querySelectorAll(&#39;img&#39;);
  expect(arrowImages).toHaveLength(0);
});</code></pre>
<h4 id="4-2-리팩토링에-자신감을-줌">4-2. 리팩토링에 자신감을 줌</h4>
<p>관심매물을 등록할때, 매물을 선택하는 모달(단지이름, 평형, 타입 등)과 중복되는 부분이 많아서 공통 컴포넌트로 분리해야 했다.
이 과정에서 기존에 모달을 사용하는 부분도 수정이 불가피했다.</p>
<p>이전 같으면 “혹시 기존 동작이 깨지진 않을까?” 하는 불안감으로 직접 QA를 몇 번이고 해 봤을 텐데, 테스트가 있었기 때문에 안심하고 자신있게 리팩토링할 수 있었다.</p>
<p>⸻</p>
<p>이번 작업을 하며 프론트엔드에서 테스트의 역할을 체감할 수 있었다.
그리고 테스트를 실무에서 작성하고 사용해 본 첫 프로젝트라서, 개인적으로도 기억에 남을 프로젝트가 되었다!</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[TDD로 나아가는 길]]></title>
            <link>https://velog.io/@mr_brightside/TDD%EB%A1%9C-%EB%82%98%EC%95%84%EA%B0%80%EB%8A%94-%EA%B8%B8</link>
            <guid>https://velog.io/@mr_brightside/TDD%EB%A1%9C-%EB%82%98%EC%95%84%EA%B0%80%EB%8A%94-%EA%B8%B8</guid>
            <pubDate>Fri, 29 Aug 2025 07:34:21 GMT</pubDate>
            <description><![CDATA[<p>드디어 테스트 도입 성공!</p>
<p>그동안 우리 서비스의 클라이언트는 앱도, 웹도 테스트가 전혀 없는 상태였다. 망망대해에서 노를 젓는 기분으로 개발을 진행해왔다...
<em>(새로 합류하신 동료분이 지금까지 클라이언트 쪽에서 테스트 하나 없이 서비스를 굴려왔다는 게 오히려 대단하다고 하실 정도였다.)</em></p>
<p>항상 클라이언트에도 테스트를 도입하고 싶었지만, 일정에 쫓겨 구현하지 못했었다.
하지만 이번에 네이버 지도api를 사용하는 어플리케이션의 레거시 코드를 모두 걷어내는 방대한 리팩토링을 시작하게 되었고, 안전한 리팩토링을 위해 테스트코드의 필요성을 절실히 느꼈다.
그리고 마침 우리 팀에는 클로드 코드가 결제되어있었다.</p>
<p>그래서 일단 테스트를 구축하기 비교적 수월한 Next.js 기반의 웹 서비스부터 테스트 환경 구축을 시작했다.</p>
<p>⸻</p>
<h3 id="playwright--jest-환경-구축">Playwright + Jest 환경 구축</h3>
<h4 id="클로드-코드야-고마워">클로드 코드야 고마워...</h4>
<p>이번엔 claude code 도움을 받아 <strong>e2e는 Playwright로, 유닛테스트는 Jest로</strong> 환경을 세팅했다.
생각보다 훨씬 빨리 끝났다. 예전에 admin 페이지에서 Jest를 쌩으로 세팅할 때랑 비교하면 차이가 컸다.</p>
<p>그때는 babel, ts-jest, transform 설정부터 막히고, config를 어떻게 나눠야 할지 애매해서 시간을 많이 날렸는데
클로드 코드와 함께하니 기본 구조를 내가 알고 있고, LLM이 실행하고 디테일을 채워주는 느낌이었다.</p>
<p>예전에 jest를 냅다 도입하느라 삽질했던 경험 덕분에, LLM이 준 답이 맞는지 틀린지 판단할 수 있었던 것이 큰 차이를 가져왔다.</p>
<h4 id="하지만-역시-설계는-사람이-직접-해야죠">하지만 역시 설계는 사람이 직접 해야죠</h4>
<p>LLM이 큰 도움은 됐지만, 초기 설계는 아직 사람이 직접 하는 게 낫다는 걸 느꼈다.
폴더 구조나 테스트 전략 설계를 LLM한테 맡겨봤는데, 컨텍스트가 조금만 복잡해지면 다소 조잡한 설계를 제시하는 같았다.</p>
<h4 id="tdd의-현실화">TDD의 현실화</h4>
<p>그동안 TDD는 리소스가 부족한 우리 팀에선 다소 사치스러웠다
하지만 클로드 코드가 mock 코드 생성이나 반복적인 작업을 해주니 진짜 TDD가 눈 앞에 다가왔다.</p>
<h4 id="그래도-공부는-당연히-해야함">그래도 공부는 당연히 해야함!</h4>
<p><img src="https://velog.velcdn.com/images/mr_brightside/post/c9f203e2-fd20-4f85-8e72-2955c561ecdb/image.gif" alt=""></p>
<p>테스트를 직접 세팅하면서 삽질해본 경험이나 javascript의 동작 원리에 대한 지식 등 기본기는 무조건 필요하다는 걸 느꼈다.
특히 디버깅할 때, 예전에 Jest를 직접 만져본 경험이 큰 도움이 됐다.</p>
<p>내가 뭘 쓰고 있는지 모른 채로 LLM을 쓰는 것은 개발자로서 지양하고 싶고, 그래야 한다고 생각한다.</p>
<p>⸻</p>
<h3 id="시맨틱-마크업의-중요성">시맨틱 마크업의 중요성</h3>
<p>테스트를 작성하면서 HTML 기본기의 필요성도 크게 느꼈다.
특히 e2e 테스트에선 지금 마크업 구조로는 도저히 요소를 찾아오기가 어려워서... 직접 코드를 수정하는 일도 있었다.
테스트 때문에 코드를 수정하는 것은 지양하고 싶었지만, 어차피 리팩토링이 필요한 구조였기도 했다. 이제부터라도 시맨틱 마크업을 철저히 지키자고 다짐했다.</p>
<p>⸻</p>
<h3 id="테스트-친화적인-코드-≒-클린-코드">테스트 친화적인 코드 ≒ 클린 코드</h3>
<p>테스트를 고려하면서 컴포넌트를 작성하면 코드가 자연스럽게 클린코드에 다가가는 것 같았다.
그동안은 비즈니스 로직과 UI가 뒤엉킨 코드가 많아서 테스트하기도 어려웠는데, 앞으로는 테스트를 기반으로 점진적으로 리팩토링을 해야겠다.
테스트 가능성과 코드 품질은 상관관계가 크다는 것을 느꼈다.</p>
<p>⸻</p>
<h3 id="대규모-리팩토링-전-테스트는-필수">대규모 리팩토링 전, 테스트는 필수!</h3>
<p>내가 진행중인 지도 서비스는 UI+비즈니스 로직+복잡한 상태들+외부 API 호출+중복된 코드등이 얽혀있는 레거시 코드다. 이전에도 지도 코드 리팩토링을 진행 했었으나, 자신감이 없어서 운영환경에 붙이지 못한 브랜치들도 많았다.</p>
<p>이제는 테스트를 기반으로 시도할 수 있다는 자신감이 생겼다. 
특히 LLM과 개발하는 환경에서 테스트는 선택이 아니라 필수!! 클로드 코드가 있으니 테스트 작성은 더 이상 리소스 낭비가 아니라 투자라고 느껴진다.</p>
<p>⸻</p>
<p>드디어 테스트 없는 클라이언트에서, TDD로 나아가는 첫걸음을 뗐다. 너무 감격스럽고 마치 망망대해에서 유람선에 올라탄 기분이다...</p>
<p>사실 2~3년 전에는 프론트엔드에서 테스트의 필요성 자체에 대한 논쟁이 분분했는데, LLM의 등장 이후부터는 거의 논쟁이 종결된 것 같다.
LLM으로 개발하다보면 왜 테스트가 필수인지 자연히 알게 되어서 그런 듯 (나 역시도 그랬다).</p>
<p>다음엔 지도 리팩토링이나 신규 개발하는 기능에서
테스트가 어떤 역할을 하는지, 어떤 도움을 받았는지 등을 더 구체적으로 기록해 볼 예정이다!!</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Deep Link 도입 삽질기 - 네? SHA-256 인증을 도입하라고요? 제가요?]]></title>
            <link>https://velog.io/@mr_brightside/sha-256-in-deeplink</link>
            <guid>https://velog.io/@mr_brightside/sha-256-in-deeplink</guid>
            <pubDate>Tue, 19 Aug 2025 09:29:15 GMT</pubDate>
            <description><![CDATA[<p>React Native로 구현된 크로스 플랫폼 앱에 딥링크를 도입해 달라는 요구사항을 받았다.</p>
<p>sdk 라이브러리 설치, appsflyer에 앱 추가하고 sdk 연결하기 까지는 무난하게 되었다.</p>
<p>onelink 도메인도 appsflyer에서 제공해주어서 쉽게 새로 팠다.</p>
<p>문제는 안드로이드에서 앱링크를 사용하려면 <strong>SHA-256 서명</strong>을 RN프로젝트에 추가하라는데?</p>
<p>이 서명이 왜 필요한지, 어떻게 추가할 수 있는지를 전혀 몰랐다는 것 …</p>
<p><a href="https://reactnative.dev/docs/security#oauth2-and-redirects">공식문서</a>를 봐도,</p>
<p><img src="https://velog.velcdn.com/images/mr_brightside/post/bd408b80-60f0-4c44-883d-ca351e98a847/image.png" alt=""></p>
<p>참고 그림을 봐도 전혀 무슨 소리인지 이해가 되지 않았고,</p>
<p>지피티에 질문해서 하기와 같은 결론을 얻어내었다.</p>
<blockquote>
<p>📌 <a href="https://oauth.net/2/pkce/">PKCE</a>를 거치면 얻게 되는 것</p>
</blockquote>
<ul>
<li><strong>code_verifier</strong> - a large random string generated by the client</li>
<li><strong>code_challenge</strong> - the SHA 256 of the code_verifier<blockquote>
</blockquote>
</li>
</ul>
<p>요약하자면</p>
<blockquote>
<ul>
<li><strong>SHA-256</strong>은 어떤 프로토콜이나 정책이 아닌 단순한 <strong>‘해시 함수’.</strong></li>
</ul>
</blockquote>
<ul>
<li>code_verifier를 전달하면 SHA-256 해시함수를 통해 해시 값을 반환하는구나.</li>
<li>이 해시 값을 처리하는 것은 OAuth 2.0 인증 과정과 서버.</li>
<li>AppsFlyer 서버에서는 정확히 어떻게 처리하는지 모르지겠만, <code>code_verifier</code>와 <code>code_challenge</code> 의 <strong>일치성을 검증</strong>하는 로직이 있겠구나.<blockquote>
</blockquote>
라는 결론에 도달함.</li>
</ul>
<p>이것을 이해하고 다시 위의 RN공식문서를 보니 이해가 잘 되었다.</p>
<p>그런데 이제 어떻게 구현하지?</p>
<p>인증서를 내 앱에 구현하기 위해 많은 서치를 해보았다…</p>
<h2 id="💥-그러나-sha-256-인증을-구현할-필요-없었다-">💥 그러나! SHA-256 인증을 구현할 필요 없었다 …</h2>
<p>참고 문서 : <a href="https://dev.appsflyer.com/hc/docs/dl_android_init_setup#generating-a-sha256-fingerprint">Android initial setup</a>
<img src="https://velog.velcdn.com/images/mr_brightside/post/b7ba9144-2ce4-4b81-b396-5c012bfeed31/image.png" alt=""></p>
<p>우리 프로젝트는 이미 플레이 스토어에 서비스 중인 상태였다.</p>
<p>→ 당연히 keystore 파일이 플레이스토어에 올라간 상태였음.</p>
<p>→ keystore를 업로드했다면 해당 keystore에 종속된 SHA-256 인증도 있을것이고…</p>
<p>→ appsFlyer 공식문서를 확인하니 SHA-256 인증 관련 내용이 있었음.</p>
<p>→ 문서를 보고 우리 서비스 플레이 콘솔에 들어가보니 … 당연히 서명키 인증이 있었더랬다 😂</p>
<h2 id="✏️-느낀-점">✏️ 느낀 점</h2>
<p>공식 문서를 진작 봤으면 될 걸 너무 멀리 가서, 이미 구현되어 있던 것의 원리를 파고 들었다.</p>
<p>그치만 해시함수를 추상적으로만 배우고 알고리즘이나 달달 외웠지… 이렇게 실제로 사용하는 예시를 보니 헛으로 공부한건 아니라서 재밌기도 했다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[AppsFlyer를 활용한 React Native 앱 내 딥링크 구현]]></title>
            <link>https://velog.io/@mr_brightside/appsflyer-reactnative-deeplink</link>
            <guid>https://velog.io/@mr_brightside/appsflyer-reactnative-deeplink</guid>
            <pubDate>Thu, 14 Aug 2025 02:01:05 GMT</pubDate>
            <description><![CDATA[<p>React Native로 구현된 크로스 플랫폼 앱에 딥링크를 도입해 달라는 요구사항을 받았다.</p>
<p>Branch IO와 고민했으나, 내부에서 상의 후 우세한 사용량 + 무료라는 메리트로 <strong>AppsFlyer 솔루션</strong>으로 결정!</p>
<p>sdk 라이브러리 설치, appsflyer에 앱 추가 후 sdk 연결하고, onelink 도메인도 appsflyer에서 제공해주어서 쉽게 새로 팠다.</p>
<p>아래 세팅 과정을 참고하시어 간단히 구현하시길 바란다.</p>
<h2 id="📌-세팅-과정">📌 세팅 과정</h2>
<h3 id="1-appsflyer에-우리-서비스-앱-추가-및-sdk연동">1. AppsFlyer에 우리 서비스 앱 추가 및 sdk연동</h3>
<p>sdk설치, AppsFly 앱 추가 후 연동 같은 것은 다른 솔루션들과 비슷하기 때문에 어렵지 않게 세팅할 수 있음.
SDK연동은 하기 라이브러리를 사용하였다.</p>
<p><a href="https://github.com/AppsFlyerSDK/appsflyer-react-native-plugin/tree/master">GitHub - AppsFlyerSDK/appsflyer-react-native-plugin: AppsFlyer plugin for React Native</a></p>
<h3 id="2-app-id-bundle-id-sha256-해시값-가져오기">2. App ID, Bundle ID, SHA256 해시값 가져오기</h3>
<p>apple developer에서 <strong>App ID와 Bundle ID</strong>를, google play console에서 <strong>SHA256 해시값</strong>을 찾는다.
(SHA256 해시값 관련하여 거대한 삽질 일기가 있는데... 이것은 <a href="https://velog.io/@mr_brightside/sha-256-in-deeplink">별도 포스팅</a>으로 발행하였다)</p>
<h3 id="3-원링크-템플릿에-해당-값들-추가">3. 원링크 템플릿에 해당 값들 추가</h3>
<p><a href="https://support.appsflyer.com/hc/ko/articles/207032246-%EC%9B%90%EB%A7%81%ED%81%AC-%ED%85%9C%ED%94%8C%EB%A6%BF#add-redirection-logic-for-existing-app-users">원링크 템플릿</a>을 추가하면서 2에서 찾아온 값들을 각각 유니버설 링크, 앱링크에 세팅해주면 됨.
<img src="https://velog.velcdn.com/images/mr_brightside/post/23a5fa74-8659-4bab-8c87-436c0da83eb6/image.png" alt=""></p>
<h3 id="4-fallback-커스텀-스킴-설정">4. fallback 커스텀 스킴 설정</h3>
<p>URI scheme fallback에는 우리 앱의 커스텀 스킴 ur를 넣어주었다.</p>
<p>유니버설링크 or 딥링크는 아직까지 호환성 문제에서 완전히 자유롭지 못하기 때문에, 해당 링크들이 동작하지 않을 경우 uri스킴으로 폴백도 가능해서 편리하다!</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Jest] date-fns 라이브러리 테스팅 이슈 - TypeError: (0 , _parse.default) is not a function]]></title>
            <link>https://velog.io/@mr_brightside/Jest-date-fns-%EB%9D%BC%EC%9D%B4%EB%B8%8C%EB%9F%AC%EB%A6%AC-%ED%85%8C%EC%8A%A4%ED%8C%85-%EC%9D%B4%EC%8A%88-TypeError-0-parse.default-is-not-a-function</link>
            <guid>https://velog.io/@mr_brightside/Jest-date-fns-%EB%9D%BC%EC%9D%B4%EB%B8%8C%EB%9F%AC%EB%A6%AC-%ED%85%8C%EC%8A%A4%ED%8C%85-%EC%9D%B4%EC%8A%88-TypeError-0-parse.default-is-not-a-function</guid>
            <pubDate>Fri, 28 Mar 2025 06:36:57 GMT</pubDate>
            <description><![CDATA[<h3 id="-문제상황">| 문제상황</h3>
<p>입력 폼을 테스트 하다가 아래와 같은 이슈로 테스트가 막혔다.</p>
<p><img src="https://velog.velcdn.com/images/mr_brightside/post/537e52c5-3fd3-4969-b184-ba291b352084/image.png" alt=""></p>
<p>경험상 is not a function으로 끝나는 TypeError는 보통 타입에 관한 문제가 아니라, 환경 안에서 함수를 못 찾는 이슈였다.<br>parse는 date-fns라는 외부 라이브러리에서 가져와서 사용하는 함수였다.</p>
<p>-&gt; 모킹이 안되거나 모듈 시스템 관련 이슈가 있겠구나! 라는 결론에 달했다.</p>
<h3 id="-trial-1---실패">| Trial 1 - 실패</h3>
<p>먼저 클로드에서 알려 준 대로, 모킹을 손보기 위해 jest.setup.js에 아래 코드를 추가해보았다.</p>
<pre><code class="language-javascript">//jest.setup.js

jest.mock(&#39;date-fns&#39;, () =&gt; {
  const actual = jest.requireActual(&#39;date-fns&#39;);
  return {
    ...actual,
    parse: jest.fn(() =&gt; new Date()),
    isValid: jest.fn(() =&gt; true),
    __esModule: true,
  };
});</code></pre>
<p>1. requireActual 메소드로 실제 라이브러리를 가져오고.</p>
<p>2. 에러메세지에 &#39;_parse.default&#39;가 포함된 것으로 보아 default export임을 인식하게 해주기 위해 __esModule: true 를 추가했다.</p>
<p>하지만 에러는 그대로였다. 모킹이 되고 있지 않았다.</p>
<h3 id="-trial-2---성공">| Trial 2 - 성공!</h3>
<p>그런데 실제로 parse를 사용하는 함수가 정의된 파일에서는 아래와 같이 named import를 사용하고 있었다.</p>
<pre><code class="language-javascript">import { isValid, parse } from &#39;date-fns&#39;;</code></pre>
<br/>

<p>왜 jest는 parse 함수가 default export라고 생각했을까? &lt;- 여기부터 문제가 있었다.</p>
<p>그래서 모킹 관련 코드를 모두 삭제 후 구글링을 시작했고,</p>
<p>아래 이슈에서 해결책을 찾아 1초 만에 해결되었다.</p>
<p><a href="https://github.com/date-fns/date-fns/issues/3674">https://github.com/date-fns/date-fns/issues/3674</a></p>
<p>parse를 사용하는 함수가 정의된 곳에서, 아래와 같이 namespace import 문으로 변경하고 해결되었다.</p>
<pre><code class="language-javascript">import * as dateFns from &#39;date-fns&#39;;</code></pre>
<p><br/><br/>
왜 에러메세지에선 default라고 했느냐?</p>
<p>컴파일 과정 중 jest가 parse함수를 찾을 때 아래와 같이 변환된 것 같았다.</p>
<pre><code class="language-javascript">const { default: parse } = require(&#39;date-fns&#39;);</code></pre>
<br/>
요약하자면 아래와 같다.

<ol>
<li>Jest가 ES 모듈의 named exports를 default export로 잘못 해석하는 문제가 있었고</li>
<li>이로 인해 const { default: parse } = require(&#39;date-fns&#39;)와 같은 잘못된 변환이 일어났으며</li>
<li>이를 해결하기 위해 namespace 객체를 통째로 가져오는 import문을 사용해서 해결.<br/>

</li>
</ol>
<h3 id="-느낀-점">| 느낀 점</h3>
<ul>
<li>진작 해당 라이브러리 이슈를 뒤져볼 걸 (...) 아직은 디버깅에서 AI를 완전히 신뢰하기엔 힘들다.</li>
<li>그래도 ES 모듈 시스템과, named import와 namespace import를 더 잘 이해하게된 계기가 되었다.</li>
<li>jest의 컴파일링 과정을 알았다면 실패를 줄일 수 있지 않았을까? jest의 동작원리도 추후 찾아보아야겠다.</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[Cannot create styled-component for component 에러 해결을 위한 VScode 정규표현식]]></title>
            <link>https://velog.io/@mr_brightside/Cannot-create-styled-component-for-component-%EC%97%90%EB%9F%AC-%ED%95%B4%EA%B2%B0%EC%9D%84-%EC%9C%84%ED%95%9C-VScode-%EC%A0%95%EA%B7%9C%ED%91%9C%ED%98%84%EC%8B%9D</link>
            <guid>https://velog.io/@mr_brightside/Cannot-create-styled-component-for-component-%EC%97%90%EB%9F%AC-%ED%95%B4%EA%B2%B0%EC%9D%84-%EC%9C%84%ED%95%9C-VScode-%EC%A0%95%EA%B7%9C%ED%91%9C%ED%98%84%EC%8B%9D</guid>
            <pubDate>Fri, 28 Mar 2025 06:34:17 GMT</pubDate>
            <description><![CDATA[<p>리액트 18.2.0, styled-components 6.1.8로 업그레이드 하면서</p>
<p>Error: Cannot create styled-component for component: undefined.</p>
<p>라는 오류를 만났다.</p>
<pre><code class="language-javascript">//from 
export const Wrapper = styled(PrevComponent)
//to
export const Wrapper = styled( props =&gt; &lt;PrevComponent {...props} /&gt;)</code></pre>
<p>이런 형식으로 바꿔주면 된다고 하는데 일일히 바꾸기엔 해당 문법을 써둔 코드가 너무 많았다 ... 😢</p>
<p>비효율을 너무 싫어하는 나는 방법을 찾았다.</p>
<p>VScode의 찾아바꾸기 기능을 이용해 <code>styled(_문자열_)</code> 을 모두<br><code>styled(props =&gt; &lt;_문자열_&gt; {...props} /&gt;)</code> 형식으로 바꿔주는 것!</p>
<p>정규표현식을 사용하면 특정 문자열을 뽑아낼 수 있다.</p>
<p>VScode의 검색을 켜고 <code>.*</code>을 눌러 정규표현식을 활성화 후, 바꾸기 모드도 활성화 해준다.</p>
<p><strong>styled\((.*?))</strong><br>해당 문자를 찾아<br><strong>styled(props =&gt; &lt;$1 {...props} /&gt;)</strong><br>로 바꿔주면 된다!
<img src="https://velog.velcdn.com/images/mr_brightside/post/47a7b14f-6137-421d-a6b2-10560e6316a7/image.png" alt=""></p>
<p><strong>\</strong>( 이스케이프 문자가 붙은 괄호는 <code>(</code> 이 괄호 스트링을 찾는 것이고,</p>
<p>(.*?)을 묶는 양옆의 괄호는 $1로 괄호안의 문자열을 치환하기 위한 괄호인 것을 주의하기 바람</p>
<p>공익 차원에서 정규식을 공유해본다.</p>
<p>조금이나마 시간 아끼고 효율적인 개발 하는 하루 되시길!</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Linux] oh-my-zsh에 alias 등록하기]]></title>
            <link>https://velog.io/@mr_brightside/Linux-oh-my-zsh%EC%97%90-alias-%EB%93%B1%EB%A1%9D%ED%95%98%EA%B8%B0</link>
            <guid>https://velog.io/@mr_brightside/Linux-oh-my-zsh%EC%97%90-alias-%EB%93%B1%EB%A1%9D%ED%95%98%EA%B8%B0</guid>
            <pubDate>Fri, 28 Mar 2025 06:30:32 GMT</pubDate>
            <description><![CDATA[<p>학교에서 UNIX시스템 과목을 듣는데, alias라는 명령어를 배웠다.<br>회사컴퓨터 개발환경은 mac이다.</p>
<p>zsh를 설치했고, 조금 더 편리하게 사용하기 위해 oh-my-zsh를 사용하고 있다.</p>
<p>어차피 리눅스 기반의 쉘스크립트는 다 유닉스랑 비슷하지 않겠어?라는 생각에 서치해봤더니<br>역시 oh-my-zsh에서도 alias를 등록할 수 있었다.</p>
<p>회사 업무를 하다보면 자주 중복해서 사용하는 명령어가 있기 마련이고, 일일히 타이핑하기 귀찮을 때도 있을 것이다.</p>
<p>alias를 사용하면 더 효율적인 작업이 가능할 듯 했다.</p>
<p>자주 쓰게되는 명령어나, 입력하기 귀찮은 명령어들은 alias로 등록해두면 편하게 사용할 수 있다.</p>
<p>방법은 간단하다.</p>
<p>1. zsh의 rc파일 열기</p>
<pre><code class="language-bash">open ~/.zshrc</code></pre>
<p>2. open ~/.zshrc 파일 편집 후 저장하기 </p>
<p><img src="https://velog.velcdn.com/images/mr_brightside/post/a58d4bd5-294a-41a0-90b1-8299398c3318/image.png" alt=""></p>
<p>.zshrc 파일 내에 내가 지정할 별칭과 명령어를 입력한 후 저장하면 된다.<br>형식은 다음과 같다.</p>
<pre><code class="language-bash"> alias 별칭=&quot;명령어&quot;</code></pre>
<p>3. 수정된  zshrc 저장 후, 수정사항을 등록하기</p>
<pre><code class="language-bash">source ~/.zshrc</code></pre>
<p>이제 내가 지정한 alias를 oh-my-zsh 터미널에서 사용할 수 있다!  </p>
<hr>
<p>++ 아래는 내가 사용하는 alias중 일부이다.
</br></p>
<pre><code class="language-bash">alias addextpod=&quot;git add . &amp;&amp; git reset ios/Podfile.lock&quot;</code></pre>
<p>빌드 시 내 개발환경에서만 문제가 있는 라이브러리가 있어서, 늘 작업할때 Potfile.lock을 바꿔서 작업 후 빌드를 돌려본다.
git add .를 하면 자꾸 lock파일이 변경사항으로 잡혀 같이 올라갔다. gitigonre에 lock파일을 추가하거나 다른 팀원분들의 lock파일까지 바꿔버릴 수 없어서 Podfile.lock을 제외하고 staging하는 명령어를 따로 지정해두었다.
</br></p>
<pre><code class="language-bash">alias stashall=&quot;git stash -u&quot;</code></pre>
<p>그냥 git stash를 하면 untracked file(신규 파일)은 포함되지 않는 것이 불편했다. git stash -u 를 stashall이라는 별칭으로 저장해두었다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Hello, World.]]></title>
            <link>https://velog.io/@mr_brightside/Hello-World</link>
            <guid>https://velog.io/@mr_brightside/Hello-World</guid>
            <pubDate>Fri, 28 Mar 2025 06:23:46 GMT</pubDate>
            <description><![CDATA[<p>처음 블로그를 쓰기 시작한 건 개발자 준비중일 때 였는데,
어느덧 3년차를 향해 달리는 개발자가 되었다.</p>
<p>격세지감이라는 말은 이럴 때 쓰는 건가 싶다.</p>
<p>많은 일이 있었지만 아직도 개발은 좋다.</p>
<p>원래 2022년 부터 작성하던 티스토리 블로그는 편리하고 커스텀하기 좋았지만,
서비스가 불안정해 보여서 벨로그로 마이그레이션 하려고 한다.</p>
<p>2025년 개발 블로그로 다시 시작!</p>
]]></description>
        </item>
    </channel>
</rss>