<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>dev-woohyeok.log</title>
        <link>https://velog.io/</link>
        <description>꾸준히 한걸음씩</description>
        <lastBuildDate>Mon, 30 Jun 2025 01:52:17 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <image>
            <title>dev-woohyeok.log</title>
            <url>https://velog.velcdn.com/images/dev-woohyeok/profile/48a3339a-8ef0-4964-a6ed-872844762881/image.jpg</url>
            <link>https://velog.io/</link>
        </image>
        <copyright>Copyright (C) 2019. dev-woohyeok.log. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/dev-woohyeok" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[텍스타일 패턴 이미지 생성 AI 개발 회고]]></title>
            <link>https://velog.io/@dev-woohyeok/%ED%85%8D%EC%8A%A4%ED%83%80%EC%9D%BC-%ED%8C%A8%ED%84%B4-%EC%9D%B4%EB%AF%B8%EC%A7%80-%EC%83%9D%EC%84%B1-AI-%EA%B0%9C%EB%B0%9C-%ED%9A%8C%EA%B3%A0</link>
            <guid>https://velog.io/@dev-woohyeok/%ED%85%8D%EC%8A%A4%ED%83%80%EC%9D%BC-%ED%8C%A8%ED%84%B4-%EC%9D%B4%EB%AF%B8%EC%A7%80-%EC%83%9D%EC%84%B1-AI-%EA%B0%9C%EB%B0%9C-%ED%9A%8C%EA%B3%A0</guid>
            <pubDate>Mon, 30 Jun 2025 01:52:17 GMT</pubDate>
            <description><![CDATA[<p>생성형 AI를 활용하여 고화질 텍스타일 디자인 이미지를 만드는 프로젝트를 진행하면서 겪었던 문제점들과 이를 해결하기 위한 노력, 그리고 최종적으로 얻은 결과에 대해 정리해보겠습니다.</p>
<p>특히, 텍스타일 디자인 분야의 특성상 심리스(seamless) 패턴 생성과 원본 디자인의 유지 및 변형이라는 상반된 요구사항을 만족시키기 위한 과정에 집중했습니다.</p>
<blockquote>
<p>** 텍스타일 디자인이란?**
옷, 커튼, 소파 커버, 침구류, 벽지 등 다양한 천(직물) 제품에 사용될 무늬, 질감, 색상 등을 디자인하는 예술이자 기술을 말합니다. 쉽게 말해 &#39;원단에 입히는 옷&#39; 또는 &#39;패턴&#39;을 만드는 작업으로 <em><strong>특정 패턴이 끊김 없이 반복되어 연속적으로 이어지는 심리스(seamless) 특성</strong></em> 을 가지는 것이 매우 중요합니다.</p>
</blockquote>
<h1 id="1프로젝트-목표-및-초기-문제점">1.프로젝트 목표 및 초기 문제점</h1>
<h3 id="1-1초기-문제점">1-1.초기 문제점</h3>
<p>프로젝트는 크게 두 가지 문제점에 직면해있었습니다. 
<img src="https://velog.velcdn.com/images/dev-woohyeok/post/b96ff114-f0e4-4f68-8362-462903f5985f/image.jpeg" width="300" height="300">
첫째, 생성된 결과물을 확대할 경우 <strong>심한 노이즈</strong>가 발생하여, 실제 텍스타일 제품에 적용하기 어려웠습니다.
텍스타일 디자인은 실제 원단에 고해상도로 인쇄되어야 하므로, 이러한 노이즈는 치명적인 문제였습니다.
<img src="https://velog.velcdn.com/images/dev-woohyeok/post/95e8367c-3040-4546-9f31-d2249b09cf6a/image.jpeg" alt="">
둘째, 특정 스타일의 재현율이 현저히 떨어졌습니다. 꽃이나 추상적인 이미지 생성에는 문제가 없었지만, 캐릭터, 체크무늬, 타이다이와 같이 정형화된 패턴을 제대로 적용되지 않거나 왜곡되어 생성되는 문제가 있엇습니다.</p>
<h3 id="1-2초기-해결-방안과-한계">1-2.초기 해결 방안과 한계</h3>
<p>초기 프로젝트는 SD1.5 모델과 IP-Adapter만을 사용하여 이미지를 생성하고 있었습니다. 따라서 최신 모델, ControlNet, LoRA 등을 활용하면 충분히 문제를 해결할 수 있을 것이라고 예상했습니다.</p>
<p>하지만 문제는 의외의 곳에서 발생했습니다. 최신 모델인 SD3.5나 Flux 모델은 기존에 사용하던 Automatic1111 라이브러리에서 지원하지 않았습니다. 심지어 해당 라이브러리는 2024년 7월 이후로 업데이트가 없어 새로운 API 서버를 구축해야 하는 상황에 놓였습니다.</p>
<h3 id="1-3comfyui">1-3.ComfyUI</h3>
<p><img src="https://velog.velcdn.com/images/dev-woohyeok/post/9b18eb80-d94e-46eb-9e95-8db4c52bf3d7/image.png" alt=""></p>
<p>새로운 라이브러리를 찾던 중, ComfyUI를 활용하여 엔드포인트 서버를 구축하기로 결정했습니다. 
ComfyUI를 선택한 주된 이유는 다음과 같습니다.</p>
<ol>
<li><p>최신 모델 지원: SD3.5, Flux 등 최신 모델을 지원하여 다양한 시도가 가능했습니다.</p>
</li>
<li><p>커스텀 노드를 활용한 자유로운 워크플로우: 특히 ControlNet과 IP-Adapter를 적극적으로 활용하여 이미지 디테일을 만들어야 하는 저희 서비스 특성상, 높은 자유도의 워크플로우는 매우 중요한 선택 기준이었습니다.</p>
</li>
<li><p>기본적인 API 서버 기능 제공: API 서버로서 활용 가능하여 기존 시스템과의 연동이 용이했습니다.</p>
</li>
</ol>
<h3 id="1-4모델-선정">1-4.모델 선정</h3>
<p>ComfyUI를 기반으로 서버를 구축한 후, 각 모델이 텍스타일 디자인 생성에 얼마나 적합한지 테스트를 진행했습니다. 
모델 선정 기준은 상업적 이용 가능 여부, 생성 결과물의 품질, ControlNet 및 IP-Adapter 등 추가 모델 지원 여부, 현재 컴퓨터 사양으로 사용 가능 여부, 심리스 패턴 생성 가능 여부였습니다.</p>
<p>최종적으로 Flux, SD3.5, SDXL 세 가지 모델을 비교했습니다.</p>
<ol>
<li><p>상업적 라이선스: Flux는 결과물에 대한 상업적 이용이 무료였으며, SD 모델들은 연매출 10억 원 이하인 경우 상업적 이용이 가능하여 문제가 없었습니다.</p>
</li>
<li><p>생성 결과 품질: 세 모델 모두 기존 모델보다 좋은 결과를 보여주었습니다. 특히 Flux와 SD3.5의 경우 결과물의 퀄리티 자체가 다른 모델들에 비해 높았지만, 실사에 가깝게 생성되는 특성 때문에 레퍼런스 이미지와 유사한 모티브를 유지하며 변형해야 하는 저희 서비스에는 오히려 마이너스 요소로 작용했습니다.</p>
</li>
<li><p>IP-Adapter 및 ControlNet 지원: 세 모델 모두 IP-Adapter와 ControlNet을 지원했지만, SD3.5 모델은 지원하는 ControlNet의 종류가 미흡했습니다.</p>
</li>
<li><p>심리스 디자인 생성: Flux는 LoRA를 지원했지만, 심리스가 깨지거나 기존 레퍼런스 이미지와 너무 다른 이미지를 생성하는 문제가 있었습니다. SD3.5는 심리스 디자인 생성을 지원하지 않았습니다. SDXL만이 심리스 디자인을 완벽하게 지원했습니다.</p>
</li>
</ol>
<p>이러한 비교를 통해 최종적으로 SDXL 모델을 선택하게 되었습니다.</p>
<h1 id="2-상반된-요구사항에-대한-해결">2. 상반된 요구사항에 대한 해결</h1>
<p>SDXL 모델을 선정한 후, img2img, img2txt, ControlNet, IP-Adapter, LoRA 등을 중첩시켜 최적의 하이퍼파라미터를 찾기 위해 노력했습니다. 
저희 서비스의 핵심은 <strong>원본의 느낌과 색상은 유지하면서도 형태와 배열이 변형</strong>되어야 한다는 것이었습니다.</p>
<p>하지만 여기서 또 다른 난관에 부딪혔습니다.</p>
<p><strong>캐릭터나 체크무늬처럼 세밀한 표현이 필요한 패턴은 기존의 패턴을 유지</strong>해야 하는 반면, <strong>꽃과 같은 추상적인 이미지는 원본의 느낌 정도만 가져오면서 배열이나 형태가 바뀌어야 했습니다.</strong></p>
<p>이는 원본 이미지의 느낌과 형태를 유지하는 것 (IP-Adapter, LoRA)과 세밀한 표현을 통해 원본과 유사하게 유지하는 것 (ControlNet)이라는 상반된 요구사항이 충돌하는 지점이었습니다. </p>
<p>세밀하게 표현하면 원본과 너무 유사해지고, 원본과 다르게 변형하면 정형화된 캐릭터가 제대로 표현되지 않는 이도 저도 안 되는 상황이 반복되었습니다.
<img src="https://velog.velcdn.com/images/dev-woohyeok/post/07545714-b093-4de2-b550-941ebedd448e/image.png" ></p>
<h3 id="2-1-스타일별-워크플로우-최적화">2-1. 스타일별 워크플로우 최적화</h3>
<p>숱한 시도 끝에, <strong>모든 스타일을 만족시키는 단일 설정은 불가능</strong> 하다는 결론에 도달했습니다. 특정 스타일이 잘나오면 다른 스타일이 제대로 나오지 않는 현상이 반복되었고, 특정 수치를 기준으로 스타일이 급격하게 변화하는 경향을 보였습니다.</p>
<p>따라서 <strong>모든 스타일별로 워크플로우 구성을 다르게하여 이미지를 생성하기로 결정</strong>했습니다. </p>
<p>이후 스타일별 최적화를 진행하면서, 각 스타일 특성에 맞는 Controlnet, IP-Apdater, LoRA의 조합 및 하이퍼파라미터를 세밀하게 조정하였습니다. 그 결과, 초기 프로젝트에서 가장 큰 문제였던 <strong>특정 스타일의 재현율 향상과 노이즈 문제</strong>를 동시에 해결 할 수 있었습니다.
<img src="https://velog.velcdn.com/images/dev-woohyeok/post/ab667d7f-8ee6-46cc-8b0a-fe1ba1fccef4/image.jpeg" ></p>
<h1 id="3-결론">3. 결론</h1>
<p>이번 프로젝트를 통해 얻은 가장 큰 깨달음은 <strong>복잡한 문제를 해결하려 할수록 오히려 단순하게 접근</strong>해야한다는 점 이었습니다. 처음에는 상반된 요구사항들을 모두 만족시키기 위해 하나의 복잡한 해결책을 찾으려 했지만, 
결국 복잡하게 얽힌 문제일수록 오히려 <strong>작고 단순하게 쪼개어 바라보는 시각</strong>이 필요했습니다.</p>
<p>각각의 문제점을 개별적으로 정의하고, 그에 맞는 최적의 해결책을 하나씩 찾아가는 과정을 통해 비로소 명확한 답을 발견 할 수 있었습니다. 모든 스타일을 만족시키는 하나의 워크플로우 대신, 각 스타일별로 최적화된 워크플로우를 구성한 것이 바로 이 단순화 전략의 결과라고 생각합니다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[테스트란 무엇인가요?]]></title>
            <link>https://velog.io/@dev-woohyeok/%ED%85%8C%EC%8A%A4%ED%8A%B8%EB%9E%80-%EB%AC%B4%EC%97%87%EC%9D%B8%EA%B0%80%EC%9A%94</link>
            <guid>https://velog.io/@dev-woohyeok/%ED%85%8C%EC%8A%A4%ED%8A%B8%EB%9E%80-%EB%AC%B4%EC%97%87%EC%9D%B8%EA%B0%80%EC%9A%94</guid>
            <pubDate>Tue, 17 Jun 2025 04:26:57 GMT</pubDate>
            <description><![CDATA[<p>이번 글에서는 프로그래밍 테스트의 중요성과 실제 적용 방안에 대해서 다루도록 하겠습니다.</p>
<h1 id="1테스트란-무엇인가">1.테스트란 무엇인가?</h1>
<p><img src="https://velog.velcdn.com/images/dev-woohyeok/post/b719c3db-5d25-440e-9eb7-d55734918b11/image.png" alt=""></p>
<p>테스트는 <strong>작성된 코드가 의도대로 정확하게 작동하는지 확인</strong>하는 과정입니다.
단순히 말해서, 개발 과정에서 코드에 숨겨진 버그를 발견하고, 사용자에게 안정적인 서비스를 제공하는데 목적이 있습니다.</p>
<p>테스트에는 다양한 종류가 있는데, 보통 범위에 따라 단위 테스트, 통합 테스트드, E2E 테스트로 구분 할 수 있습니다.</p>
<h2 id="1-2-테스트-왜-중요할까">1-2. 테스트, 왜 중요할까?</h2>
<p><img src="https://velog.velcdn.com/images/dev-woohyeok/post/773051c1-7f6b-41f0-98d8-793b87a6be1c/image.png" alt=""></p>
<p>리팩토링으로 유명한 마틴 파울러는 테스트의 중요성을 특히 강조했습니다. 그에 따르면 테스트를 먼저 작성하는 것은 여러모로 큰 이점을 가져다줍니다.</p>
<p>첫째, 테스트를 먼저 작성하면 코드의 <strong>API가 잘 설계되었는지 자연스럽게 검증</strong>할 수 있습니다. 
테스트를 만들면서 원하는 기능을 추가하기 위해 무엇이 필요한지 깊이 고민하게 되므로, 이는 결국 좋은 설계를 이끌어내는 데 큰 도움이 됩니다.</p>
<p>둘째, 코드를 조금씩 변경하고 매번 테스트를 진행하면, 변경 폭이 작아 문제가 발생했을 때 훨씬 찾기 쉽습니다. 이처럼 대부분의 결함을 코드 제출 전에 미리 고칠 수 있기 때문에, <strong>더 안정적인 배포가 가능</strong>해지는 것이죠.</p>
<p>셋째, 테스트 코드는 그 자체로 <strong>훌륭한 문서 역할</strong>을 합니다. 해당 기능을 구현하기 위한 테스트를 읽는 것만으로도 코드의 동작을 명확하게 이해할 수 있습니다.</p>
<p>마지막으로, 테스트는 <strong>코드 검증 시간을 줄여줍니다</strong>. 이미 잘 준비된 테스트 코드가 있다면, 코드 리뷰 단계에서 수동으로 코드를 검증하는 데 드는 시간을 크게 절약할 수 있습니다.</p>
<h2 id="1-3-올바른-테스트-작성을-위한-규칙">1-3. 올바른 테스트 작성을 위한 규칙</h2>
<p>효율적이고 유지보수하기 좋은 코드를 작성하기 위해선 몇 가지 중요한 규칙이 있습니다.</p>
<p><strong>인터페이스를 기준으로 테스트를 작성</strong>하기, 모든 테스트는 내부 구현이 아닌 외부에 노출되는 public 메서드를 기준으로 작성해야합니다.
캡슐화된 내부 구현에 대한 테스트는 불필요하게 많은 테스트 코드를 만들고, 구현이 조금만 변경되어도, 유지보수가 어렵기 때문입니다. 컴퍼넌트에 대한 테스트 코드는 사용자가 앱을 사용하는 방식으로 UI 구성 요소를 테스트 하는 것이 좋으며, 이는 테스트 신뢰성을 높입니다.</p>
<p>또한 커버리지보다는 <strong>의미 있는 테스트인지 고민</strong>하는 것이 좋습니다. 변경 가능성이 거의 없고 내부 로직이 너무 간단한 유틸리티 함수는 과감하게 테스트를 생략하는 것이 좋습니다. 이러한 함수들은 다른 모듈이나 컴퍼넌트의 로직이 포함 되었을때, 한번에 검증하는 것이 효율적이기 때문입니다.</p>
<p>그리고 잘 작성된 테스트 코드는 그 자체로 훌룡한 문서가 되지만, 명확한 테스크 설명이 있다면, 테스트 파일만 보고도 앱이 어떻게 동작하는지 쉽게 파악할 수 있기 때문에 가급적 <strong>가독성 있는 테스트 코드 작성</strong>하는 것이 좋습니다.</p>
<p>마지막으로 <strong>하나의 테스트에는 가급적 하나의 동작만이 검증</strong>하는 것이 좋습니다. 다양한 컴퍼넌트들이 조합된 시나리오를 검증해야한다면, 하나의 테스트에서 한번에 검증하기 보다는 여러 개의 개별 테스트로 나누어 검증하는 것이 가독성과 유지보수 측면에서 좋습니다.</p>
<h1 id="2-테스트-작성-패턴aaa--gwt">2. 테스트 작성 패턴(AAA / GWT)</h1>
<p>테스트를 작성하는 일반적인 방법으로는 AAA(Arrange-Act-Assert) 패턴 또는 GWT(Given-When-Then) 패턴을 따릅니다.
먼저, 테스트를 위한 환경(<code>Arrange/Given</code>)을 만들고, 그 다음 테스트할 동작을 재현한 후(<code>Act/When</code>), 마지막으로 올바른 동작을 실행되었는지 또는 변경사항을 검증합니다.(<code>Assert/Then</code>)</p>
<pre><code class="language-js">it(&#39;className prop으로 설정한 css class가 적용된다.&#39;, async () =&gt; {
  // Arrange - 테스트를 위한 환경 만들기
  // -&gt; className을 지닌 컴포넌트 렌더링
  await render(&lt;TextField className=&quot;my-class&quot; /&gt;);

  // Act - 테스트할 동작 발생 (렌더링 검증이므로 생략)

  // Assert - 올바른 동작이 실행되었는지 검증
  // -&gt; 렌더링 후 DOM에 해당 class가 존재하는지 검증
  expect(screen.getByPlaceholderText(&#39;텍스트를 입력해 주세요.&#39;)).toHaveClass(&#39;my-class&#39;);
});</code></pre>
<p>예를 들어, 위에 코드는 TextFieldID 컴퍼넌트의 className prop에 설정된 CSS 클래스가 텍스트 필드에 제대로 적용되었는지 검증하는 코드입니다.</p>
<p>해당 코드에서는 컴퍼넌트를 랜더링하여, 환경을 설정하고(<code>Arrange</code>), 랜더링 검증이므로 특정 동작 재현(<code>Act</code>)은 생략하며, 마지마긍로 랜더링된 DOM에 해당하는 클래스가 존재하는지 검증(<code>Assert</code>)합니다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[React] 컴파운드 패턴을 사용해 Custom SelectBox 컴퍼넌트 구현하기]]></title>
            <link>https://velog.io/@dev-woohyeok/React-%EC%BB%B4%ED%8C%8C%EC%9A%B4%EB%93%9C-%ED%8C%A8%ED%84%B4%EC%9D%84-%EC%82%AC%EC%9A%A9%ED%95%B4Custom-SelectBox-%EC%BB%B4%ED%8D%BC%EB%84%8C%ED%8A%B8-%EA%B5%AC%ED%98%84%ED%95%98%EA%B8%B0</link>
            <guid>https://velog.io/@dev-woohyeok/React-%EC%BB%B4%ED%8C%8C%EC%9A%B4%EB%93%9C-%ED%8C%A8%ED%84%B4%EC%9D%84-%EC%82%AC%EC%9A%A9%ED%95%B4Custom-SelectBox-%EC%BB%B4%ED%8D%BC%EB%84%8C%ED%8A%B8-%EA%B5%AC%ED%98%84%ED%95%98%EA%B8%B0</guid>
            <pubDate>Mon, 27 Jan 2025 07:38:10 GMT</pubDate>
            <description><![CDATA[<h1 id="select-태그의-최악의-사용성"><code>&lt;select&gt;</code> 태그의 최악의 사용성</h1>
<hr>
<p><img src="https://velog.velcdn.com/images/dev-woohyeok/post/aee2d261-1810-4983-9023-067eeba06c4b/image.png" alt=""></p>
<p>프로젝트에서 국가 선택 기능을 단순히 <code>&lt;select&gt;</code> 태그로 구현했을 때, 수십 혹은 수백 개의 국가가 나열되는 경우 사용자가 일일이 스크롤을 해야하는 불편함이 발생했습니다.</p>
<p>따라서 이 문제를 해결하기 위해서 검색 기능을 구현하면서 자유로운 디자인을 위해 Custom Select 컴퍼넌트를 직접 구현하기로 결정했습니다.</p>
<h1 id="왜-컴파운드-패턴-을-적용했는가">왜? “컴파운드 패턴” 을 적용했는가?</h1>
<hr>
<h3 id="컴파운드-패턴이란">컴파운드 패턴이란?</h3>
<p><a href="https://betterprogramming.pub/compound-component-design-pattern-in-react-34b50e32dea0">Compound Component Design Pattern in React</a></p>
<p>Compound Compoenet Design Pattern 에 대해서 소개하는 수 많은 글 중에서 이 글은 다음과 같이 표현하고 있습니다.
<img src="https://velog.velcdn.com/images/dev-woohyeok/post/ff631b95-ca40-4755-9b12-a593cfc66bf3/image.png" alt=""></p>
<blockquote>
<p>컴파운드 컴퍼넌트 패턴은 부모와 자식 컴퍼넌트간의 로직과 UI 를 각각 명확하게 분리함으로써, 유연한 표현을 제공하는 리액트 패턴이다. </p>
</blockquote>
<p>다시 말해, 컴파운트 패턴은 부모와 자식 컴퍼넌트 로직과 UI 를 명확하게 분담하도록 함으로써, 이를 조립하여 하나의 유연한 형태의 컴퍼넌트를 만드는 것이라고 할 수 있습니다.</p>
<p><a href="https://ko.react.dev/learn/thinking-in-react">React로 사고하기 – React</a></p>
<p>이 내용만 접했을때, 컴파운드, React 에서 이야기하는 컴퍼넌트 패턴과 무엇이 다른가? 라는 의문이 들었습니다.</p>
<p>실제로 컴파운드 패턴은 React 가 지향하는 컴퍼넌트 기반 설계의 철학과 크게 다르지 않습니다.
위 공식 문서에서도, 상태를 기준으로 UI 계층을 구분하고, props를 통해 데이터를 전달하는 방식(컴퍼넌트 쪼개기 + props 전파, 데이터 기반 사고)을 권장하고 있기 때문입니다.</p>
<h3 id="컴파운드-패턴은-무엇이-다른건가">컴파운드 패턴은 무엇이 다른건가?</h3>
<h4 id="react의-기본-철학"><strong>React의 기본 철학</strong></h4>
<ul>
<li>부모-자식 구조로 컴포넌트를 쪼개고, props를 통해 데이터와 이벤트 핸들러를 전달한다</li>
<li>상태는 일반적으로 상위 컴포넌트에서 관리한다(Props Drilling)</li>
</ul>
<h4 id="컴파운드-패턴"><strong>컴파운드 패턴</strong></h4>
<ul>
<li>필요에 따라 <strong>Context</strong>를 활용하여 부모와 자식이 <strong>전역적으로</strong> 상태를 공유할 수 있도록 한다(물론 ‘무조건’ Context를 써야 하는 것은 아님)</li>
<li><SelectBox>, &lt;SelectBox.Input&gt;, &lt;SelectBox.List&gt;와 같이 <strong>명시적인 하위 컴포넌트</strong>를 제공하여, 개발자가 HTML 태그를 작성하듯 필요한 자식 컴포넌트를 조합할 수 있도록 한다</li>
</ul>
<p>이로 인해 코드 가독성도 높아지고, 각 자식 컴포넌트가 어떤 역할을 담당하는지 명확해진다</p>
<p>즉, 두 방식이 <strong>추구하는 바는 동일</strong>합니다. 다만, 컴파운드 상태와 로직의 분리를 더 의도적으로 명확히 했다는 것이라고 생각합니다.</p>
<pre><code>&lt;SelectBox
                items={OLYMPIC_COUNTRIES_LIST}
                value={stateForm[STATE_FORM.COUNTRY]}
                onSelect={(value) =&gt;
                    setStateForm((prev) =&gt; ({
                        ...prev,
                        country: value,
                    }))
                }
            &gt;
                &lt;SelectBox.Input
                    label=&quot;국가&quot;
                    placeholder=&quot;국가를 선택해주세요&quot;
                /&gt;
                &lt;SelectBox.List /&gt;
            &lt;/SelectBox&gt;</code></pre><h2 id="도입-이유">도입 이유</h2>
<p>실제로 현재 프로젝트에서는 당장 필요한 패턴은 아닙니다. 
하지만 앞으로 유연한 컴퍼넌트를 만들어야할 때, 어떻게 구조를 잡고, 관리 해야할지 방향을 제시 해 줄 수 있는 내용이라 생각되어서, 프로젝트에 경험삼아 적용해보기로 결정 했습니다.</p>
<h1 id="개발-과정">개발 과정</h1>
<p><img src="https://velog.velcdn.com/images/dev-woohyeok/post/162b2c49-a9f8-4cde-990a-e5addb412184/image.png" alt=""></p>
<p>  위 그림은 개발에 들어가기전, 설계한 그림입니다.</p>
<ol>
<li>SelectBox(부모 컴퍼넌트)<ul>
<li>isOpen, setIsOpen, filteredItems 등의 상태와 함수를 Context API 를 사용해 전역적으로 상태를 관리</li>
</ul>
</li>
<li>SelectBox.Input( 검색 영역)<ul>
<li>사용자가 검색어를 입력하거나, Item 을 선택하면, onSelect()로 상태를 처리</li>
<li>focus, blur 이벤트로 List 열림/닫힘 제어</li>
</ul>
</li>
<li>SelectBox.List(드롭 다운 목록 영역)<ul>
<li>isOpen 상태 값에 따라서 List 열림/닫힘 제어</li>
<li>필터링된 items 를 받아서 UI 를 화면에 표시해줌</li>
</ul>
</li>
<li>ListItem(아이탬 영역)<ul>
<li>선택 된 item 의 값과 List 의 열림/닫힘 여부를 부모 컴퍼넌트에 전달</li>
</ul>
</li>
</ol>
<h1 id="도입-후-느낀점">도입 후 느낀점</h1>
<p>컴파운드 패턴을 적용하면서, 단순히 데이터 중심으로만 생각하기 보다는, 부모-자식 컴퍼넌트의 관계를 기준으로 로직과 상태를 분리를 조금 더 명확히 해보는 경험이었습니다.</p>
<p><img src="https://velog.velcdn.com/images/dev-woohyeok/post/56532515-be55-49aa-ba79-d06f075c0f90/image.gif" alt=""></p>
<h1 id="전체-코드">전체 코드</h1>
<pre><code class="language-jsx">import { useState, useContext, createContext } from &#39;react&#39;;
import styles from &#39;./../styles/SelectBox.module.css&#39;;
import { STLYES_SELECTBOX } from &#39;../constant/type&#39;;
import Input from &#39;./Input&#39;;
const SelectContext = createContext();

function SelectBox({ items = [], value, onSelect, children }) {
    const [isOpen, setIsOpen] = useState(false); // 드롭다운 열림/닫힘 상태

    const filteredItems = items.filter((item) =&gt;
        item.toLowerCase().includes(value.toLowerCase()),
    );

    const contextValue = {
        isOpen,
        setIsOpen,
        filteredItems,
        value,
        onSelect,
    };

    return (
        &lt;SelectContext.Provider value={contextValue}&gt;
            &lt;div className={styles[STLYES_SELECTBOX.CONTAINER]}&gt;{children}&lt;/div&gt;
        &lt;/SelectContext.Provider&gt;
    );
}

function useSelectContext() {
    const context = useContext(SelectContext);
    if (!context) {
        throw new Error(
            &#39;Select 내부에서만 사용 가능한 컴포넌트입니다. &lt;Select&gt;로 감싸주세요.&#39;,
        );
    }
    return context;
}

function SelectInput({ label, placeholder }) {
    const { setIsOpen, onSelect, value } = useSelectContext();

    const handleFocus = (e) =&gt; {
        setIsOpen(true);
        e.target.select();
    };

    const handleBlur = () =&gt; {
        setIsOpen(false);
    };

    const handleChange = (e) =&gt; {
        onSelect(e.target.value);
    };

    const handleKeyDown = (e) =&gt; {
        if (e.key === &#39;Enter&#39;) {
            e.stopPropagation();
            e.preventDefault();
        }
    };

    return (
        &lt;Input
            type=&quot;text&quot;
            label={label}
            value={value}
            onKeyDown={handleKeyDown}
            onChange={handleChange}
            onFocus={handleFocus}
            onBlur={handleBlur}
            placeholder={placeholder}
        /&gt;
    );
}

function SelectList() {
    const { isOpen, filteredItems } = useSelectContext();
    if (!isOpen) return null;

    return (
        &lt;div className={styles[STLYES_SELECTBOX.LIST]}&gt;
            {filteredItems.length === 0 ? (
                &lt;div className={styles[STLYES_SELECTBOX.NO_ITEM]}&gt;
                    검색 결과가 없습니다.
                &lt;/div&gt;
            ) : (
                filteredItems.map((value) =&gt; (
                    &lt;ListItem key={value} value={value} /&gt;
                ))
            )}
        &lt;/div&gt;
    );
}

function ListItem({ value: itemValue }) {
    const { onSelect, setIsOpen, value: selectedValue } = useSelectContext();
    const handleClick = () =&gt; {
        onSelect(itemValue);
        setIsOpen(false);
    };
    const handleMouseDown = (e) =&gt; {
        e.preventDefault();
    };

    return (
        &lt;div
            className={styles[STLYES_SELECTBOX.ITEM]}
            role=&quot;button&quot;
            onMouseDown={handleMouseDown}
            onClick={handleClick}
            tabIndex={0}
        &gt;
            {itemValue}
        &lt;/div&gt;
    );
}

SelectBox.Input = SelectInput;
SelectBox.List = SelectList;

export default SelectBox;
</code></pre>
<h1 id="참고자료">참고자료</h1>
<hr>
<p><a href="https://patterns-dev-kr.github.io/design-patterns/compound-pattern/">Compound 패턴</a></p>
<p><a href="https://kentcdodds.com/blog/compound-components-with-react-hooks">React Hooks: Compound Components</a></p>
<p><a href="https://itchallenger.tistory.com/710">리액트 디자인 패턴 : 컴파운드 컴포넌트 패턴 [Compound Component Pattern] 2</a></p>
<p><a href="https://betterprogramming.pub/compound-component-design-pattern-in-react-34b50e32dea0">Compound Component Design Pattern in React</a></p>
<p><a href="https://ko.react.dev/learn/thinking-in-react">React로 사고하기 – React</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[회고] 나만의 색깔을 찾아가는 여정]]></title>
            <link>https://velog.io/@dev-woohyeok/%ED%9A%8C%EA%B3%A0-%EB%82%98%EB%A7%8C%EC%9D%98-%EC%83%89%EA%B9%94%EC%9D%84-%EC%B0%BE%EC%95%84%EA%B0%80%EB%8A%94-%EC%97%AC%EC%A0%95</link>
            <guid>https://velog.io/@dev-woohyeok/%ED%9A%8C%EA%B3%A0-%EB%82%98%EB%A7%8C%EC%9D%98-%EC%83%89%EA%B9%94%EC%9D%84-%EC%B0%BE%EC%95%84%EA%B0%80%EB%8A%94-%EC%97%AC%EC%A0%95</guid>
            <pubDate>Fri, 17 Jan 2025 11:46:22 GMT</pubDate>
            <description><![CDATA[<h1 id="무색무취한-출발">무색무취한 출발</h1>
<p>저는 흔히 말하는 ‘무색무취한’ 삶을 살아왔다고 할 수 있습니다.
한 번도 무언가에 열정적으로 미쳐본 적이 없었고, 특별히 자랑할 만한 스펙이나 경험도 내세울 수 없었습니다.
그저 근심 걱정 없이, 주어진 길을 따라 흘러온 것이 전부였죠.</p>
<p>그러다 보니 초·중·고를 별다른 고민 없이 졸업하고, <strong>성적에 맞춰 전공</strong>을 선택했습니다.
취업을 고민해야 할 무렵에도 ‘뭘 해야 할지 모르겠다’는 이유로 무작정 <strong>대학원</strong>에 진학했지만,
막상 가보니 <strong>기계 세팅</strong>이나 <strong>실험 보조</strong>에 가까운 일을 반복하는 환경에서 “이게 정말 내 미래인가?”라는 의문이 들었습니다.</p>
<h1 id="불현듯-찾아온-개발자의-길"><strong>불현듯 찾아온 개발자의 길</strong></h1>
<p>그때, “<strong>부트캠프 수료생 평균 연봉 5000 이상</strong>”이라는 광고가 눈에 들어왔습니다.
“학벌이나 전공을 따지지 않고 비전공자도 개발을 할 수 있다”는 말이 묘하게 매력적이었고,
결국 대학원을 포기하고 <strong>홀린 듯</strong> 부트캠프에 등록했습니다.
그렇게 부트캠프 홍보 내용처럼 휩쓸려 가다 보니, 어느새 <strong>스타트업에 풀스택 개발자로 합류</strong>하게 되었죠.</p>
<h1 id="방황-그리고-인연"><strong>방황, 그리고 인연</strong></h1>
<p>하지만 막상 스타트업에서 일해보니, 제게 <strong>근본적인 문제가 있음을 깨달았습니다.</strong>
iOS, 안드로이드, 인공지능 등 여러 플랫폼을 얕게나마 건드렸지만,그저 <strong>빠르게 기능을 구현</strong>하는 데에만 집중했을 뿐, 깊이 있는 전문성을 쌓지 못했던 겁니다.</p>
<p>결국 “이제라도 <strong>한 분야</strong>에 집중해 제대로 성장해보자”라는 생각으로,
무작정 <strong>프론트엔드 개발자 채용 공고</strong>를 찾아 지원했습니다.
“문제를 해결하는 능력”만큼은 괜찮다고 생각했지만,실제 면접에서는 <strong>제 부족함이 여실히 드러났습니다.</strong>
답답함이 커지던 중 여러 멘토링 프로그램에 참여했고,운 좋게도 <strong>저처럼 비전공자로 풀스택 개발자가 된 멘토</strong>분을 만났습니다.</p>
<p>그분은 제 상황에 깊이 공감해주며,</p>
<blockquote>
<p><strong>“진짜 하고 싶은 건 뭔지, 우선순위를 명확히 해봐라, 돈인지, 성장인지, 아니면 또 다른 무엇인지. 결국 완벽한 선택은 없으니, 후회하지 않을 만큼 노력하는 게 중요하다.”</strong></p>
</blockquote>
<p>라는 조언을 해주셨습니다.</p>
<p>이 말이 결정적인 전환점이 되었습니다.</p>
<p>아직 일어나지도 않은 미래를 두려워하며 아무것도 선택하지 못하는 제 모습을 돌아보게 된 저는, “어차피 후회할 거라면 지금 제대로 부딪쳐보자” 라는 마음으로 과감히 회사를 퇴사하게 되었습니다.</p>
<h1 id="다시-찾은-부트캠프-그리고-성장의-즐거움"><strong>다시 찾은 부트캠프, 그리고 성장의 즐거움</strong></h1>
<p>그러나 혼자 공부하기 시작하자, 생각만큼 쉽지 않았습니다.
흥미 있는 것만 골라 보거나, 시간 관리를 잘 못해 금세 흐트러지곤 했습니다.
그러던 중, ‘9 to 9’ 온라인으로 진행되는 부트캠프에 대해 알게 되었고,
“무료로 공부 패턴을 잡아주면서 포트폴리오 까지 만들수 있겠다” 라는 생각으로 가볍게 지원했습니다.</p>
<p>어렴풋이 알기만 했던 개념이나 동작 원리를 하나하나 파헤치면서, 짧은 기간임에도 불구하고 제 실력이 부쩍 늘었다는 걸 체감하면서, 오랜만에 성취감을 느낄 수 있었습니다.</p>
<p>특히 부트캠프에는 이미 제가 했던 고민을 먼저 겪은 멘토들이 많았고, “아는 만큼 보인다” 라는 말처럼 제 문제를 구체적으로 짚어주며 명확한 해결책을 제시해주었습니다. 또 동료들과 서로 배움과 가르침을 주고받는 과정에서, 설명하기 위해 기본을 다시 다지고, 미쳐 생각하지 못했던 부분까지 함께 고민하면서 한번 더 성장 할 수 있었습니다.</p>
<h1 id="무색무취를-벗어나며"><strong>무색무취를 벗어나며</strong></h1>
<p>물론 아직 배워야 할 것들은 끝이 없고, 제 커리어 방향 역시 여전히 고민 중입니다. 
하지만 확실히 달라진 건 “모르는 걸 깨닫고,  스스로 파고들어 공부할 수 있다”는 자신감입니다. 
그동안 ‘무색무취하다’고 생각했던 제 삶이었지만, 이제는 저만의 색깔을 조금씩 찾아가고 있는것 같습니다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[알고리즘] - 귤 고르기]]></title>
            <link>https://velog.io/@dev-woohyeok/%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%98-%EA%B7%A4-%EA%B3%A0%EB%A5%B4%EA%B8%B0-zp4osftv</link>
            <guid>https://velog.io/@dev-woohyeok/%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%98-%EA%B7%A4-%EA%B3%A0%EB%A5%B4%EA%B8%B0-zp4osftv</guid>
            <pubDate>Fri, 17 Jan 2025 06:50:00 GMT</pubDate>
            <description><![CDATA[<h3 id="요약">요약</h3>
<pre><code class="language-jsx">풀이 시간 : 7분 18초

1. 귤의 갯수를 카운팅하고, 갯수가 많은 순으로 정렬 후, 최대로 담을 수 있는 귤의 종류를 카운팅</code></pre>
<h3 id="문제-링크">문제 링크</h3>
<p><a href="https://school.programmers.co.kr/learn/courses/30/lessons/138476">https://school.programmers.co.kr/learn/courses/30/lessons/138476</a></p>
<h3 id="문제-풀이">문제 풀이</h3>
<p>```jsx
function solution(t, tangerine) {
    const tangerineCounts = new Map();</p>
<pre><code>// 귤 갯수 세기
tangerine.forEach((size) =&gt; {
    tangerineCounts.set(size, (tangerineCounts.get(size) || 0) + 1);
});

// 귤 정렬 하기
const sortedTangerines = tangerineCounts.values().sort(
    (a, b) =&gt; b - a,
);

// 상자에 담기
let count = 0;
let total = 0;
for(const tangerines of sortedTangerines){
    total += tangerines;
    count ++;
    if(total &gt;= t) break;
}

return count;</code></pre><p>}</p>
<h1 id="">```</h1>
]]></description>
        </item>
        <item>
            <title><![CDATA[[알고리즘] - 멀리뛰기]]></title>
            <link>https://velog.io/@dev-woohyeok/%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%98-%EB%A9%80%EB%A6%AC%EB%9B%B0%EA%B8%B0</link>
            <guid>https://velog.io/@dev-woohyeok/%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%98-%EB%A9%80%EB%A6%AC%EB%9B%B0%EA%B8%B0</guid>
            <pubDate>Thu, 16 Jan 2025 11:34:33 GMT</pubDate>
            <description><![CDATA[<h3 id="요약">요약</h3>
<pre><code class="language-jsx">문제풀이 : 11분 01초 

1. 피보나치 수열 문제</code></pre>
<h3 id="문제-링크">문제 링크</h3>
<p><a href="https://school.programmers.co.kr/learn/courses/30/lessons/12914">https://school.programmers.co.kr/learn/courses/30/lessons/12914</a></p>
<h3 id="풀이">풀이</h3>
<pre><code class="language-jsx">function solution(n) {
    let prev = 0;
    let cur = 1;
    for(let i = 1; i &lt;= n; i++){
        const next = (prev + cur) % 1234567
        prev = cur;
        cur = next;
    }
    return cur;
}</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[[알고리즘] N개의 최소공배수]]></title>
            <link>https://velog.io/@dev-woohyeok/%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%98-N%EA%B0%9C%EC%9D%98-%EC%B5%9C%EC%86%8C%EA%B3%B5%EB%B0%B0%EC%88%98</link>
            <guid>https://velog.io/@dev-woohyeok/%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%98-N%EA%B0%9C%EC%9D%98-%EC%B5%9C%EC%86%8C%EA%B3%B5%EB%B0%B0%EC%88%98</guid>
            <pubDate>Tue, 14 Jan 2025 12:04:18 GMT</pubDate>
            <description><![CDATA[<h3 id="요약">요약</h3>
<pre><code class="language-jsx">문제 풀이 시간 : 32분 11초 

1. 유클리드 호제법 생각이 안나서 검색
2. 순회하면서 특정 계산을 진행해야하는 경우는 reduce() 메서드 사용</code></pre>
<h3 id="문제-링크">문제 링크</h3>
<p><a href="https://school.programmers.co.kr/learn/courses/30/lessons/12953">https://school.programmers.co.kr/learn/courses/30/lessons/12953</a></p>
<h3 id="문제-풀이">문제 풀이</h3>
<pre><code class="language-jsx">function solution(arr) {
    // 최대공약수를 구하면서, 최소공배수를 배열로 순환하면서 구하면됨

    const gcd = (a, b) =&gt; (b === 0 ? a : gcd(b, a % b));
    const lcm = (a, b) =&gt; (a * b) / gcd(a, b);

    return arr.reduce((acc, cur) =&gt; lcm(acc, cur), 1);
}</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[[알고리즘] - 예상 대진표]]></title>
            <link>https://velog.io/@dev-woohyeok/%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%98-%EC%98%88%EC%83%81-%EB%8C%80%EC%A7%84%ED%91%9C</link>
            <guid>https://velog.io/@dev-woohyeok/%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%98-%EC%98%88%EC%83%81-%EB%8C%80%EC%A7%84%ED%91%9C</guid>
            <pubDate>Mon, 13 Jan 2025 01:16:40 GMT</pubDate>
            <description><![CDATA[<h3 id="요약">요약</h3>
<pre><code class="language-jsx">풀이 시간 : 12분 23초 (반례 찾아봄) 

1. Math 메서드 활용 
</code></pre>
<h3 id="문제-링크">문제 링크</h3>
<p><a href="https://school.programmers.co.kr/learn/courses/30/lessons/12985">https://school.programmers.co.kr/learn/courses/30/lessons/12985</a></p>
<h3 id="1차-풀이">1차 풀이</h3>
<hr>
<pre><code class="language-jsx">// 토너먼트 1~N, 1-2, 3-4, 이런식으로 게임 진행
// 1-N/2 번 승리한 사람이 앞번호
// n: 참가자수, a: 내번호, b: 상대 번호
function solution(n,a,b){
    // 홀짝 여부
    // 둘의 차이가 같은지
    // n 을 기준으로 반복이고
    // 내번호 중 이긴거 /2 의 반올림이겟네 
    let round = 1;
    const getNextRank = (number) =&gt; Math.ceil(number / 2);

    // 두 번호가 서로 대결할떄까지 반복
    while(Math.abs(a - b) !== 1) {
        a = getNextRank(a);
        b = getNextRank(b);
        round++;
    }

    return round;
}</code></pre>
<p>테스트 케이스 7, 9, 27, 33 에러 발생</p>
<p>이유 : 2,3 위인 경우 제외 반례가 생김</p>
<h3 id="2차-풀이">2차 풀이</h3>
<hr>
<pre><code class="language-jsx">// 토너먼트 1~N, 1-2, 3-4, 이런식으로 게임 진행
// 1-N/2 번 승리한 사람이 앞번호
// n: 참가자수, a: 내번호, b: 상대 번호
function solution(n,a,b){
    // 홀짝 여부
    // 둘의 차이가 같은지
    // n 을 기준으로 반복이고
    // 내번호 중 이긴거 /2 의 반올림이겟네 
    let round = 1;
    const getNextRank = (number) =&gt; Math.ceil(number / 2);

    // 두 번호가 서로 대결할떄까지 반복
    while(Math.abs(a - b) !== 1 || Math.min(a, b) % 2 !== 1) {
        a = getNextRank(a);
        b = getNextRank(b);
        round++;
    }

    return round;
}</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[[알고리즘] 카펫]]></title>
            <link>https://velog.io/@dev-woohyeok/%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%98-%EC%B9%B4%ED%8E%AB-d1y5nw9f</link>
            <guid>https://velog.io/@dev-woohyeok/%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%98-%EC%B9%B4%ED%8E%AB-d1y5nw9f</guid>
            <pubDate>Sun, 12 Jan 2025 09:32:03 GMT</pubDate>
            <description><![CDATA[<h3 id="요약">요약</h3>
<hr>
<pre><code class="language-jsx">풀이 시간 : 7분 41

1. 구해야하는건 결국 가운데 노란색 타일의 모양 </code></pre>
<h3 id="문제-링크">문제 링크</h3>
<hr>
<p><a href="https://school.programmers.co.kr/learn/courses/30/lessons/42842">프로그래머스-카펫</a></p>
<h3 id="풀이">풀이</h3>
<hr>
<pre><code class="language-jsx">function solution(brown, yellow) {
    // 카펫, 중앙애만 칠해져있고, 외각은 갈색으로 칠해짐
    // 노란색과 갈색의 갯수만을 알고있음

    // 가로길이는 세로와 같거나 세로길이보다 더 길다.
    // yello의 약수중에 가장 큰 값이 가로의 길이 아닌가?
    // 아 그러면 yellow를 한줄로 놓고 이거의 세로길이 + 2 , 가로길이 + 2 를 곱했을때, 판넬의 갯수가 토탈이면 ok

    const total = brown + yellow;
    for (let i = 1; i &lt;= yellow; i ++){
        const width = (yellow / i) + 2;
        const height = i + 2;
        const area = width * height;

        if(area === total){
            return [width, height];
        }
    }
}</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[[알고리즘] 피보나치의 수]]></title>
            <link>https://velog.io/@dev-woohyeok/%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%98-%ED%94%BC%EB%B3%B4%EB%82%98%EC%B9%98%EC%9D%98-%EC%88%98</link>
            <guid>https://velog.io/@dev-woohyeok/%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%98-%ED%94%BC%EB%B3%B4%EB%82%98%EC%B9%98%EC%9D%98-%EC%88%98</guid>
            <pubDate>Fri, 10 Jan 2025 00:27:20 GMT</pubDate>
            <description><![CDATA[<h3 id="요약">요약</h3>
<pre><code class="language-jsx">문제 풀이 시간 : 문제 풀이 힌트 확인 

1. Javascript의 정수타입 제한 -&gt; 큰수로 인해 오버플로우 발생가능성 있
2. 모듈러 연산 성질 -&gt; (a + b) \mod m = [(a \mod m) + (b \mod m)] \mod m

즉, 메모리 최적화를 위해 계산중 숫자를 작게 유지해야함</code></pre>
<h3 id="문제-링크">문제 링크</h3>
<p><a href="https://school.programmers.co.kr/learn/courses/30/lessons/12945">[프로그래머스] - 피보나치의 수</a></p>
<h3 id="1차-풀이">1차 풀이</h3>
<pre><code class="language-jsx">function solution(n) {
    let prev = 0;
    let curr = 1;
    for(let i = 2; i &lt;= n; i++){
        const next = prev + curr
        prev = curr;
        curr = next;
    }
    return curr % 1234567;
}</code></pre>
<h3 id="2차-풀이">2차 풀이</h3>
<pre><code class="language-jsx">function solution(n) {
    let prev = 0;
    let curr = 1;
    for(let i = 2; i &lt;= n; i++){
        const next = (prev + curr) % 1234567;
        prev = curr;
        curr = next;
    }
    return curr;
}</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[[알고리즘] - 이진 변환 반복하기]]></title>
            <link>https://velog.io/@dev-woohyeok/%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%98-%EC%9D%B4%EC%A7%84-%EB%B3%80%ED%99%98-%EB%B0%98%EB%B3%B5%ED%95%98%EA%B8%B0-3mfwjoks</link>
            <guid>https://velog.io/@dev-woohyeok/%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%98-%EC%9D%B4%EC%A7%84-%EB%B3%80%ED%99%98-%EB%B0%98%EB%B3%B5%ED%95%98%EA%B8%B0-3mfwjoks</guid>
            <pubDate>Wed, 08 Jan 2025 02:00:38 GMT</pubDate>
            <description><![CDATA[<h3 id="요약">요약</h3>
<pre><code class="language-jsx">풀이 시간 : 19분 21초 

1. 문제 순서대로 처리하면됨
2. while 문으로 처리하면되는거아닌가?, 재귀에 대해서 공부 필요</code></pre>
<h3 id="문제-링크">문제 링크</h3>
<p><a href="https://school.programmers.co.kr/learn/courses/30/lessons/70129">[프로그래머스] 이진 변환 반복하기</a></p>
<h3 id="풀이">풀이</h3>
<pre><code class="language-jsx">function solution(s) {
    let currentS = s;
    let zeroCount = 0;
    let steps = 0;

    while (currentS.length !== 1) {
        const filteredArray = [...currentS].filter(str =&gt; str !== &quot;0&quot;);

        zeroCount += currentS.length - filteredArray.length;// 누적된 0의 개수 계산
        currentS = filteredArray.length.toString(2); // 필터링된 결과를 이진 변환
        steps++;
    }

    return [steps, zeroCount];
}</code></pre>
<h3 id="재귀-적용해서-리팩토링">재귀 적용해서 리팩토링</h3>
<pre><code class="language-jsx">function solution(s) {
    const process = (currentS, steps = 0 ,zeroCount = 0) =&gt; {
        if(currentS.length === 1) return [steps, zeroCount]; // 종료 조건

        const filteredArray = [...currentS].filter(s =&gt; s !== &quot;0&quot;);
        zeroCount += currentS.length - filteredArray.length;
        const nextS = filteredArray.length.toString(2);

        return process(nextS, steps + 1, zeroCount);
    }

    return process(s);
}</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[[알고리즘] JadenCase 문자열 만들기]]></title>
            <link>https://velog.io/@dev-woohyeok/%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%98-JadenCase-%EB%AC%B8%EC%9E%90%EC%97%B4-%EB%A7%8C%EB%93%A4%EA%B8%B0</link>
            <guid>https://velog.io/@dev-woohyeok/%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%98-JadenCase-%EB%AC%B8%EC%9E%90%EC%97%B4-%EB%A7%8C%EB%93%A4%EA%B8%B0</guid>
            <pubDate>Tue, 07 Jan 2025 01:00:04 GMT</pubDate>
            <description><![CDATA[<h3 id="요약">요약</h3>
<pre><code class="language-jsx">문제풀이 : 3분 11초

요약
1. toUpperCase, toLowerCase 의 활용  </code></pre>
<h3 id="문제-링크">문제 링크</h3>
<p><a href="https://school.programmers.co.kr/learn/courses/30/lessons/12951">[프로그래머스] - JadenCase 문자열 만들기</a></p>
<h3 id="풀이">풀이</h3>
<pre><code class="language-jsx">function solution(s) {
    const result = s.split(&quot; &quot;)
    .map(str =&gt; 
         [...str].map((char, idx) =&gt; idx === 0 ? char.toUpperCase() : char.toLowerCase())
                 .join(&quot;&quot;))
    .join(&quot; &quot;)

    return result;    
}</code></pre>
<h3 id="풀이-개선">풀이 개선</h3>
<pre><code class="language-jsx">function solution(s) {
    const result = s.split(&quot; &quot;)
    .map(str =&gt; str[0].toUpperCase() + str.slice(1).toLowerCase())
    .join(&quot; &quot;)

    return result;    
}</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[[알고리즘] 최대값과 최솟값]]></title>
            <link>https://velog.io/@dev-woohyeok/%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%98-%EC%B5%9C%EB%8C%80%EA%B0%92%EA%B3%BC-%EC%B5%9C%EC%86%9F%EA%B0%92</link>
            <guid>https://velog.io/@dev-woohyeok/%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%98-%EC%B5%9C%EB%8C%80%EA%B0%92%EA%B3%BC-%EC%B5%9C%EC%86%9F%EA%B0%92</guid>
            <pubDate>Tue, 07 Jan 2025 00:49:03 GMT</pubDate>
            <description><![CDATA[<h3 id="요약">요약</h3>
<pre><code class="language-jsx">문제풀이 : 5분 32초

요약
1. split, sort, filter, join method의 활
2. 최대값, 최소값 =&gt; Math.min, Math.max</code></pre>
<h3 id="문제-링크">문제 링크</h3>
<p><a href="https://school.programmers.co.kr/learn/courses/30/lessons/12939">[프로그래머스] - 최솟값과 최대값</a></p>
<h3 id="풀이">풀이</h3>
<pre><code class="language-jsx">function solution(s) {
    return s.split(&quot; &quot;)
       .sort((a, b) =&gt; a - b)
       .filter((_, idx, arr) =&gt; idx === 0 || idx === arr.length -1 )
       .join(&quot; &quot;);
}

function solution(s) {
    const arr = s.split(&quot; &quot;);
    const min = Math.min(...arr);
    const max = Math.max(...arr);

    return min +&quot; &quot;+ max;
}</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[[알고리즘]-신고 결과 받기]]></title>
            <link>https://velog.io/@dev-woohyeok/%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%98-%EC%8B%A0%EA%B3%A0-%EA%B2%B0%EA%B3%BC-%EB%B0%9B%EA%B8%B0</link>
            <guid>https://velog.io/@dev-woohyeok/%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%98-%EC%8B%A0%EA%B3%A0-%EA%B2%B0%EA%B3%BC-%EB%B0%9B%EA%B8%B0</guid>
            <pubDate>Mon, 06 Jan 2025 03:14:09 GMT</pubDate>
            <description><![CDATA[<h3 id="요약">요약</h3>
<pre><code class="language-jsx">풀이 시간 : 23분 14초

풀이
1. 중복 제거 
2. 데이터 매핑

다름 사람 풀이를 참고한 리펙토링
1. 중복의 대상을 명확히 하기, -&gt; report에서 target 만 중복처리해서 추가 로직생김
2. || 연산자 활용, -&gt; map.set(id , (map.get(id) || 0) + 1);</code></pre>
<h3 id="문제-링크">문제 링크</h3>
<p><a href="https://school.programmers.co.kr/learn/courses/30/lessons/92334">[프로그래머스]-신고 결과 받기</a></p>
<h3 id="풀이">풀이</h3>
<pre><code class="language-jsx">function solution(id_list, report, k) {
    // 동일한 유저에 대한 신고는 1번만 Map -&gt; 중복처리
    // k번 이상 신고시 정지 -&gt; 대상자 기준
    // id_list 순서대로 처리

    const reportMailCounts = new Map(id_list.map((id) =&gt; [id, 0])); // 유저별 처리 결과 메일
    const reportMap = new Map(id_list.map((id) =&gt; [id, new Set()])); // 작성자-메일 매핑, 중복처리를 위해 Set() 사용
    const stopMap = new Map();// ID-누적횟수 매핑

    for (const rep of report) { // 작성자-메일 매핑
        const [id, target] = rep.split(&#39; &#39;);
        reportMap.get(id).add(target);
    }

    for (const targets of reportMap.values()) { // ID-누적횟수 매핑
        for (const id of [...targets]) {
            if (stopMap.has(id)) {
                stopMap.set(id, stopMap.get(id) + 1);
            } else {
                stopMap.set(id, 1);
            }
        }
    }

    for (const [id, targets] of reportMap) { // 최종 결과 매핑
        for (const target of [...targets]) {
            if (stopMap.get(target) &gt;= k) {
                reportMailCounts.set(id, reportMailCounts.get(id) + 1);
            }
        }
    }

    return [...reportMailCounts.values()];
}</code></pre>
<h3 id="다른-사람-풀이">다른 사람 풀이</h3>
<pre><code class="language-jsx">function solution(id_list, report, k) {
    let reports = [...new Set(report)].map(a=&gt;{return a.split(&#39; &#39;)});
    let counts = new Map();
    for (const bad of reports){
        counts.set(bad[1],counts.get(bad[1])+1||1)
    }
    let good = new Map();
    for(const report of reports){
        if(counts.get(report[1])&gt;=k){
            good.set(report[0],good.get(report[0])+1||1)
        }
    }
    let answer = id_list.map(a=&gt;good.get(a)||0)
    return answer;
}</code></pre>
<h3 id="코드-리펙토링">코드 리펙토링</h3>
<pre><code class="language-jsx">function solution(id_list, report, k) {
    // 중복 신고 제거
    const uniqueReports = [...new Set(report)].map((rep) =&gt; rep.split(&#39; &#39;));

    // 신고당한 횟수 계산
    const reportCounts = new Map();
    for (const [_, target] of uniqueReports){
        reportCounts.set(target, (reportCounts.get(target) || 0) + 1);
    }

    // 신고자별 처리 결과 메일 카운트
    const resultMailCounts = new Map(id_list.map((id) =&gt; [id, 0]));
    for (const [reporter, target] of uniqueReports) {
        if (reportCounts.get(target) &gt;= k) {
            resultMailCounts.set(reporter, resultMailCounts.get(reporter) + 1);
        }
    };

    // id_list 순서에 맞는 결과 반환
    return id_list.map((id) =&gt; resultMailCounts.get(id));
}</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[[알고리즘]-공원 산책]]></title>
            <link>https://velog.io/@dev-woohyeok/%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%98-%EA%B3%B5%EC%9B%90-%EC%82%B0%EC%B1%85</link>
            <guid>https://velog.io/@dev-woohyeok/%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%98-%EA%B3%B5%EC%9B%90-%EC%82%B0%EC%B1%85</guid>
            <pubDate>Sun, 05 Jan 2025 14:49:51 GMT</pubDate>
            <description><![CDATA[<h3 id="요약">요약</h3>
<pre><code class="language-jsx">풀이 시간 : 49분 32초

풀이 정리
1. 좌표 방향
2. 이동 가능 여부 확인

다른 사람 풀이
1. 2차원 배열은 [][]로 표현가능</code></pre>
<h3 id="문제-링크">문제 링크</h3>
<p><a href="https://school.programmers.co.kr/learn/courses/30/lessons/172928">[알고리즘]-공원산책</a></p>
<h3 id="풀이">풀이</h3>
<pre><code class="language-jsx">function solution(park, routes) {
    const parkHeight = park.length, parkWidth = park[0].length

    let [currentHeight, currentWidth] = park.flatMap((park, hei) =&gt; 
                [...park].map((state, wid) =&gt; state === &quot;S&quot; ? [hei, wid] : null)).find(Boolean);

    const directions = {
       &quot;N&quot; : [-1, 0],
       &quot;S&quot; : [1, 0],
       &quot;W&quot; : [0, -1],
       &quot;E&quot; : [0, 1],
    };

    for(const route of routes){
        const [direction, moveStr] = route.split(&quot; &quot;);
        const [dy, dx] = directions[direction];
        const move = Number(moveStr);
        let isUpdate = true;

        for(let step = 1; step &lt;= move; step++){
            const nextHeight = step * dy + currentHeight;
            const nextWidth = step * dx + currentWidth;

            const isBlocked = park[nextHeight][nextWidth] === &quot;X&quot;;
            const outOfPark = nextHeight &lt; 0 || nextHeight &gt;= parkHeight || 
                              nextWidth &lt; 0 || nextWidth &gt;= parkWidth;


            if(outOfPark || isBlocked){
                isUpdate = false;
                break;
            }
        }

        if(isUpdate){
            [currentHeight, currentWidth] = [currentHeight + move * dy, currentWidth + move * dx];
        }
    }

    return [currentHeight, currentWidth];
}</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[[알고리즘]-달리기 경주]]></title>
            <link>https://velog.io/@dev-woohyeok/%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%98-%EB%8B%AC%EB%A6%AC%EA%B8%B0-%EA%B2%BD%EC%A3%BC</link>
            <guid>https://velog.io/@dev-woohyeok/%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%98-%EB%8B%AC%EB%A6%AC%EA%B8%B0-%EA%B2%BD%EC%A3%BC</guid>
            <pubDate>Fri, 03 Jan 2025 01:16:50 GMT</pubDate>
            <description><![CDATA[<h3 id="요약">요약</h3>
<pre><code class="language-jsx">풀이 시간 : X (검색해서 찾아봄)

1차 풀이
1. 타임 아웃 발생, 배열 탐색, 시간복잡도 O(n)

2차 풀이
1. Map, Object =&gt; 해시테이블 기반 구조, 시간복잡도 O(1)

다른사람풀이
1. 배열의 구조 분해 할등을 통한 요소 교환

정리
1. 시간복잡도
2. 직관적인 변수명 사용
3. 배열의 구조 분해 할당을 통한 요소 교환</code></pre>
<h3 id="문제-링크">문제 링크</h3>
<p><a href="https://school.programmers.co.kr/learn/courses/30/lessons/178871">[프로그래머스]-달리기 경주</a></p>
<h3 id="1차-풀이">1차 풀이</h3>
<pre><code class="language-jsx">
function solution(players, callings) {    
    for(const [idx, call] of callings.entries()){
        const caller = players.indexOf(call);
        const passer = caller - 1;
        if(passer &gt;= 0){
            const temp = players[passer];
            players[passer] = players[caller];
            players[caller] = temp;
        }
    }
    return players;
}</code></pre>
<h3 id="2차-풀이">2차 풀이</h3>
<pre><code class="language-jsx">function solution(players, callings) {
    // 검색을 위한 이름-인덱스 매핑 맵
    const mapRank = new Map(players.map((player, idx)=&gt; [player, idx]));

    for(const [idx, call] of callings.entries()){
        const callerIdx = mapRank.get(call); // 호출된 선수 인덱스
        const passerIdx = callerIdx - 1; // 앞 순위 선수 인덱스
          const shouldChangeRank = passerIdx &gt;= 0; 

        if(shouldChangeRank){ // 순위 변경이 가능한 경우
            // 순위 반영
            const passerName = players[passerIdx];
            players[passerIdx] = players[callerIdx];
            players[callerIdx] = passerName;

            // 맵 인덱스 업데이트
            mapRank.set(call, passerIdx);
            mapRank.set(passerName, callerIdx);
        }
    }

    return players;
}</code></pre>
<h3 id="다른-사람-풀이-정리">다른 사람 풀이 정리</h3>
<pre><code class="language-jsx">function solution(players, callings) {
    // 검색을 위한 이름-인덱스 매핑 맵 초기화
    const mapRank = new Map(players.map((player, idx)=&gt; [player, idx]));

    for(const [idx, call] of callings.entries()){
        const callerIdx = mapRank.get(call); // 호출된 선수 인덱스
        const passerIdx = callerIdx - 1; // 앞 순위 선수 인덱스
        if(passerIdx &gt;= 0){
            // 순위 반영, 구조 분해 할당을 활용한 요소 교환
            [players[passerIdx], players[callerIdx]] = [players[callerIdx], players[passerIdx]];

            // 맵 인덱스 업데이트
            mapRank.set(call, passerIdx);
            mapRank.set(passerName, callerIdx);
        }
    }

    return players;
}</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[[CleanCode]좋은 변수명 작성을 위한 나만의 가이드]]></title>
            <link>https://velog.io/@dev-woohyeok/cleanCode%EC%A2%8B%EC%9D%80-%EB%B3%80%EC%88%98%EB%AA%85-%EC%9E%91%EC%84%B1%EC%9D%84-%EC%9C%84%ED%95%9C-%EB%82%98%EB%A7%8C%EC%9D%98-%EA%B0%80%EC%9D%B4%EB%93%9C</link>
            <guid>https://velog.io/@dev-woohyeok/cleanCode%EC%A2%8B%EC%9D%80-%EB%B3%80%EC%88%98%EB%AA%85-%EC%9E%91%EC%84%B1%EC%9D%84-%EC%9C%84%ED%95%9C-%EB%82%98%EB%A7%8C%EC%9D%98-%EA%B0%80%EC%9D%B4%EB%93%9C</guid>
            <pubDate>Thu, 02 Jan 2025 12:23:10 GMT</pubDate>
            <description><![CDATA[<p>알고리즘 문제를 풀다 보면 가독성 있고 간결하게 작성된 다른 사람들의 풀이를 자주 접하게 됩니다.
그럴때 마다 <code>“어떻게 저렇게 간결하게 쓸 수 있을까?“</code> 라는 생각과 함께 조용히 감탄했습니다.</p>
<p>이후 메서드를 활용하는 방법들은 문제를 반복적으로 풀면서 점차 익히게 되었지만, 변수명을 간결하고 명확하게 짓는 일은 여전히 어려운 과제로 남아 있었습니다. </p>
<p>그래서 이 글에서 좋은 변수명 짓는 방법에 대해 정리해보겠습니다</p>
<h1 id="1-좋은-변수명이-왜-필요한가">1. 좋은 변수명이 왜 필요한가?</h1>
<hr>
<p>다양한 이유가 있지만, 가장 중요한 이유는 “기능을 명확하고 간결하게 표현” 하기 위해서 라고 생각합니다.</p>
<p>개발을 처음 배울 때는 단순히 기능을 구현하는 것이 전부라고 여겼던 적이 있습니다. 하지만 현업에서는 다양한 이유로 끊임 없는 기능의 수정이 필요했습니다. 그때마다, 오래전에 작성한 코드를 다시 보거나, 다른 사람이 만든 코드를 수정해야 하는 상황도 빈번히 발생했고, 코드를 읽고 이해하는 데 많은 시간이 소모 되는 것을 경험했습니다.</p>
<p>즉, 코드를 읽고 이해하는 데 걸리는 시간을 최소화하려면 기능을 명확하고 간결하게 표현하는 것이 필수적입니다.이는 가독성 좋은 코드를 작성하기 위한 기본 원칙이자 출발점이라 할 수 있습니다.</p>
<h1 id="2-좋은-변수-작성-가이드">2. 좋은 변수 작성 가이드</h1>
<hr>
<p>좋은 변수명을 짓는 방법에 대해 많은 글에서 공통적으로 강조하는 점은, <strong>변수명에 코드의 의도와 의미를 명확히 담아야 한다</strong>는 것입니다. </p>
<p>하지만 코드 스타일은 사람마다 주관적으로 생각하는 부분도 많기 때문에,  다양한 사례를 정리하여 참고할 수 있도록 하는 데 의의를 두고자 합니다.</p>
<h2 id="2-0-공통-사항">2-0. 공통 사항</h2>
<pre><code class="language-jsx">Javascript
1. CamelCase 사용 
2. 클래스명은 대문자 시작
3. 상수는 대문자 표기
4. 로직이 끝날시 줄바꿈
5. element 앞에는 $ 붙이기, 여러개 호출시는 $$</code></pre>
<h2 id="2-1--문맥-중복을-피하기">2-1.  문맥 중복을 피하기</h2>
<p>변수나 메서드 이름에서 이미 해당 컨텍스트가 의도를 전달하고 있다면, 불필요한 반복은 제거</p>
<pre><code class="language-jsx">class MenuItem {
    // 중복된 문맥 표현
    handleMenuItemClick = (event) =&gt; { ... }

    // 간결한 표현
    handleClick = (event) = { ... }
}</code></pre>
<h2 id="2-2-예상-결과-반영하기">2-2. 예상 결과 반영하기</h2>
<p>변수명은 결과를 직관적으로 전달해야함, 혼동을 줄 수 있는 변수명은 자제</p>
<pre><code class="language-jsx">// 부정 표현임으로 한번더 논리적으로 생각이 필요
const isEnabled = itemCount &gt; 3
return &lt;Button disabled={!isEnabled} /&gt;

// 직관적으로 이해됨
const isDisabled = itemCount &lt;= 3
return &lt;Button disabled={isDisabled} /&gt;</code></pre>
<h2 id="2-3-의미-없는-단어-피하기">2-3. 의미 없는 단어 피하기</h2>
<p>변수명이 해당 해당 값의 역할을 명확히 들어내는 표현으로 작성</p>
<pre><code class="language-jsx">
// 보편적으로 쓰이는 무의미한 변수명
function add(a, b) {
    return a + b;
}

// 명확한 변수명
function add(num1, num2) {
    return num1 + num2;
}</code></pre>
<h2 id="2-4-과도한-축약어-사용-피하기">2-4. 과도한 축약어 사용 피하기</h2>
<p>변수명은 오해할 여지를 최소화해야한다. 잘못된 ㅇ</p>
<pre><code class="language-jsx">// 과도한 축약어 사용으로 의미 파악이 어려움
const onItmClk = () =&gt; {} 

// 명확한 이름
const onItemClick = () =&gt; {}

// 축약이 사용되는 경우도 있지만 협업시에는 자제하거나, 공통 규칙으로 만들자
const idx;
const count_num;</code></pre>
<h2 id="2-5-오해가-생기는-의미는-피하기">2-5. 오해가 생기는 의미는 피하기</h2>
<p>변수명은 직관적인 이름으로 만들기, 오해할 여지가 있는 변수명은 의도를 제대로 전달하기 어려움</p>
<pre><code class="language-jsx">// 오해를 부를 수 있는 변수 이름
const ON_GOING = true; // 뜻: 진행중, 실제 의미 : 유효함 (validate)
const IS_VALID = true; // 명확한 이름 사용</code></pre>
<h2 id="2-6-블리언-변수에-이름-붙이기">2-6. 블리언 변수에 이름 붙이기</h2>
<p>블리언 변수의 경우,  is, has, can, should, use 와 같은 접두사를 붙여 불리언임을 표시</p>
<pre><code class="language-jsx">const valid = true; // 의미는 유효성을 나타내지만, 불리언임이 명확하지 않음
const isValid = true; // 유효성을 나타내며 불리언임을 명확히 알 수 있음

const clicked = false; // 클릭 여부를 나타내지만 불리언임이 명확하지 않음
const isClicked = false; // 클릭 여부를 명확히 표현</code></pre>
<h1 id="3-좋은-변수명을-짓기-위한-방법들">3. 좋은 변수명을 짓기 위한 방법들</h1>
<hr>
<h2 id="3-1-smart">3-1. SMART</h2>
<ol>
<li>easy to Search : 검색하기 쉽고</li>
<li>easy to Mix : 조합하기 쉽고</li>
<li>easy to Agree : 동의하기 쉽고</li>
<li>easy to Remember : 기억하기 쉽고</li>
<li>easy to Type : 입력하기 쉽고</li>
</ol>
<h3 id="1-검색하기-쉽게-이름짓기">1. 검색하기 쉽게 이름짓기</h3>
<p>공통된 상위 범주의 개념을 앞의 이름으로 정하기</p>
<pre><code class="language-jsx">// 개별 검색
const SERVER_TIMEOUT;
const NO_RESULT;

// ERROR 로 빠르게 검색
const ERROR_SERVER_TIMEOUT;
const ERROR_NO_RESULT;</code></pre>
<h3 id="2-조합하기-쉽게-이름-짓기">2. 조합하기 쉽게 이름 짓기</h3>
<p>같은 주제나 카테고리에 속한 이름들이 쉽게 조합될 수 있도록 규칙적이고 체계적으로 이름을 사용하기</p>
<pre><code class="language-jsx">// 비체계적인 이름: 조합이나 검색 어려움
const BTN_PRIMARY = &#39;btn-primary&#39;;
const HIGHLIGHT_COLOR = &#39;#ff0&#39;;
const ICON_DELETE = &#39;icon-delete&#39;;

// 조합하기 쉬운 체계적인 이름
const BUTTON_PRIMARY = &#39;btn-primary&#39;;
const BUTTON_SECONDARY = &#39;btn-secondary&#39;;
const COLOR_HIGHLIGHT = &#39;#ff0&#39;;
const COLOR_BACKGROUND = &#39;#f0f0f0&#39;;
const ICON_TRASH = &#39;icon-trash&#39;;
const ICON_SAVE = &#39;icon-save&#39;;</code></pre>
<h3 id="3-동의하기-쉽게-이름-짓기">3. 동의하기 쉽게 이름 짓기</h3>
<p>변수명은 팀원들이 직관적으로 이해할 수 있는 단어를 사용해야한다. 상황에 따라 다르지만 search 라는 일반적인 단어 대신 inquiry 라는 단어를 사용할 필요는 없음</p>
<pre><code class="language-jsx">// 이해하기 어려운 변수 이름
const inquiryData = () =&gt; {
  // 실제로는 검색 기능을 수행
  ...
};

// 자연스럽고 직관적인 변수 이름
const searchData = () =&gt; {
  // 검색 기능임을 명확히 표현
  ...
};</code></pre>
<h3 id="4-기억하기-쉽게-이름-짓기">4. 기억하기 쉽게 이름 짓기</h3>
<p>변수명은 기억하기 쉬운 이름으로 지어야 쉽게 기억할 수 있음</p>
<pre><code class="language-jsx">Amazon S3 : Simple Storage Service 이지만 S3 라는 간결한 이름으로 쉽게 기억 가능
MVC 패턴 : model view controller 의 약자로, 역활과 의미를 쉽게 이해 할 수 있음</code></pre>
<h3 id="5-입력하기-쉽게-이름-짓기">5. 입력하기 쉽게 이름 짓기</h3>
<p>공통적으로 많이 사용되는 단축어를 사용해 변수명 짓기</p>
<pre><code class="language-jsx">success =&gt; ok
address =&gt; addr
index =&gt; idx
array =&gt; arr 
number =&gt; num
count =&gt; cnt
rm =&gt; remove 
delete =&gt; del
textToImg =&gt; txt2Img
등등 다양한거같음 상황에 맞게 쓰자 
이름 긴것도, IDE 를 활용하기 때문에 효율적으로 작성 할 수 있음 </code></pre>
<h1 id="4-결론">4. 결론</h1>
<p>좋은 변수명을 만드는 방법을 정리해 보았지만, 오히려 더 모호해졌다는 느낌이 듭니다. 다양한 방법이 존재하지만, 서로의 주장과 의도가 주관적이고 다르게 느껴지기 때문입니다.</p>
<p>물론 공통적으로 동의할 수 있는 개념들이 존재하지만, 그것조차도 명확하게 정의되지 않고 다소 추상적으로 느껴집니다. 결국, 좋은 변수명은 경험을 통해 하나씩 찾아가는 과정이 필요하다는 생각이 듭니다.</p>
<p>그럼에도 불구하고, 기본적인 네이밍 원칙과 큰 틀을 통해 방향을 잡을 수 있는 시간이었습니다.</p>
<h1 id="참고자료">참고자료</h1>
<p><a href="https://github.com/kettanaito/naming-cheatsheet">github[naming-cheatsheet]</a>
<a href="https://www.syncfusion.com/blogs/post/top-javascript-naming-convention">Naming Conventions Every Developer Should Know in Javascript</a>
<a href="https://www.digdeeproots.com/articles/naming-process/">Naming as a Process</a> (영문이라 아직 다 못읽어봄)
<a href="https://blog.naver.com/sooftware/221846090355">[좋은 코딩 습관] 깔끔하게 코드 짜는 10가지 규칙</a>
<a href="https://brunch.co.kr/@wapj2000/29">변수명을 잘 짓기 위한 몸부림</a></p>
<h1 id="추천-도서">추천 도서</h1>
<p><a href="http://www.yes24.com/Product/Goods/79378905">개발자의 글쓰기</a>(아직 안읽음)
<a href="http://www.yes24.com/Product/Goods/6692314">읽기 좋은 코드가 좋은 코드다</a> (아직 안읽음)</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[알고리즘]-개인정보 수집 유효기간]]></title>
            <link>https://velog.io/@dev-woohyeok/%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%98-%EA%B0%9C%EC%9D%B8%EC%A0%95%EB%B3%B4-%EC%88%98%EC%A7%91-%EC%9C%A0%ED%9A%A8%EA%B8%B0%EA%B0%84</link>
            <guid>https://velog.io/@dev-woohyeok/%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%98-%EA%B0%9C%EC%9D%B8%EC%A0%95%EB%B3%B4-%EC%88%98%EC%A7%91-%EC%9C%A0%ED%9A%A8%EA%B8%B0%EA%B0%84</guid>
            <pubDate>Thu, 02 Jan 2025 02:10:52 GMT</pubDate>
            <description><![CDATA[<h3 id="요약">요약</h3>
<pre><code>풀이시간 : 42분 12초

문제 풀이
1. 1달을 28일로 계산
2. 만료일이 되는 순간 파기
3. split() 메서드의 활용

주의점
1. 타입 변환

고민
1. 변수명 짓기 참 어렵다
2. 시간이 걸려도 천천히 문제 읽기 (실수 줄이는게 관건)
3. 결국 알고리즘 문제는 1.데이터 전처리, 2.계산, 3.결과반환순임 각 파트 명확히하기</code></pre><h3 id="문제">문제</h3>
<p><a href="https://school.programmers.co.kr/learn/courses/30/lessons/150370">[프로그래머스]개인정보 수집 유효기간</a></p>
<h3 id="풀이">풀이</h3>
<pre><code class="language-js">// today : 오늘일짜, term : 약관-유효기간, privacies : 수집일자 - 약관종류
// 1달 28일 기준, 유효기간이 지난 약관 폐기
function solution(today, terms, privacies) {
    const result = []; // 파기대상

    const [yearT, monthT, dayT] = today.split(&quot;.&quot;).map(Number); 
    const totalDaysT = yearT * 12 * 28 + monthT * 28 + dayT;  // 오늘 날짜를 일단위로 변환 -&gt; 1달은 28일

    const termMap = new Map(terms.map(term =&gt; {// 약관 정보
        const [type, duration] = term.split(&quot; &quot;)
        return [type, Number(duration) * 28];
    }));

    for(const [i, privacy] of privacies.entries()){ // 개인정보를 순회하며 파기 여부 파악
        const order = i + 1;
        const [date, termType] = privacy.split(&quot; &quot;);
        const [year, month, day] = date.split(&quot;.&quot;).map(Number);

        const totalDaysP = year * 12 * 28 + month * 28 + day; // 계약일
        const expireDay = totalDaysP + termMap.get(termType); // 만료일 

        // 만료 판단 여부
          expireDay &lt;= totalDaysT &amp;&amp; result.push(order);
    }
    return result;
}</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[[알고리즘] - 바탕화면 정리]]></title>
            <link>https://velog.io/@dev-woohyeok/%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%98-%EB%B0%94%ED%83%95%ED%99%94%EB%A9%B4-%EC%A0%95%EB%A6%AC</link>
            <guid>https://velog.io/@dev-woohyeok/%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%98-%EB%B0%94%ED%83%95%ED%99%94%EB%A9%B4-%EC%A0%95%EB%A6%AC</guid>
            <pubDate>Wed, 01 Jan 2025 14:58:50 GMT</pubDate>
            <description><![CDATA[<h3 id="요약">요약</h3>
<pre><code>풀이시간 : 10분 02초

1. 배열을 통해 좌표를 표현 vs 개별 변수에 좌표 표현
2. 최소값 , 최대값 비교는 나중에 한번에 처리 가능</code></pre><h3 id="문제">문제</h3>
<p><a href="https://school.programmers.co.kr/learn/courses/30/lessons/161990">바탕화면 정리</a></p>
<h3 id="풀이">풀이</h3>
<pre><code class="language-js">function solution(wallpaper) {
    const start = [Infinity, Infinity]; // 최소값
    const end = [-Infinity, -Infinity]; // 최대값
    const file = &#39;#&#39;;

    for (const [i, line] of wallpaper.entries()) {
        const startX = line.indexOf(file);
        const endX = line.lastIndexOf(file);

        if (startX !== -1) { // 파일이 존재하는 경우
            start[0] = Math.min(start[0], i); // y축 최소값
            start[1] = Math.min(start[1], startX); // x축 최소값
            end[0] = Math.max(end[0], i); // y축 최대값
            end[1] = Math.max(end[1], endX); // x축 최대값
        }
    }

    // 파일이 1칸을 차지함으로 x, y 축 +1 씩 
    return [start[0], start[1], end[0] + 1, end[1] + 1];
}</code></pre>
<p>해당 문제는 #로 표시된 파일의 위치를 탐색하여 x 축과 y축의 최대값과 최소값을 계산하는것이 핵심인 문제입니다. 최대값과 최소값을 계산하기 위해서 indexOf 와 lastIndexOf 메서드를 활용해 x 좌표를 계산하였고, file 존재 여부를 기준으로 y축의 최소값과 최대값을 구했습니다.</p>
<h3 id="풀이개선">풀이개선</h3>
<pre><code class="language-js">function solution(wallpaper) {
    const minX = [];
    const minY= [];
    const maxX = [];
    const maxY= [];
    const file = &#39;#&#39;;

    for (const [i, line] of wallpaper.entries()) {
        const startX = line.indexOf(file);
        const endX = line.lastIndexOf(file);

        if (startX !== -1) { // 파일이 존재하는 라인일 경우
            minX.push(startX);
            minY.push(i);
            maxX.push(endX + 1);
            maxY.push(i + 1);
        }
    }

    // 파일이 1칸을 차지함으로 x, y +1 씩 
    return [Math.min(...minY), Math.min(...minX), Math.max(...maxY), Math.max(...maxX)];
}</code></pre>
<p>기존에는 배열로 x, y 좌표를 관리하던 방식을 minX, maxY 등 직관적인 변수로 변경하여 가독성을 높였으며, 모든 라인에서 최소값과 최대값을 반복적으로 계산하던 과정을 제거하고, 마지막에 한 번에 계산하도록 수정하엿습니다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[알고리즘]-성격 유형 검사하기]]></title>
            <link>https://velog.io/@dev-woohyeok/%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%98-%EC%84%B1%EA%B2%A9-%EC%9C%A0%ED%98%95-%EA%B2%80%EC%82%AC%ED%95%98%EA%B8%B0</link>
            <guid>https://velog.io/@dev-woohyeok/%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%98-%EC%84%B1%EA%B2%A9-%EC%9C%A0%ED%98%95-%EA%B2%80%EC%82%AC%ED%95%98%EA%B8%B0</guid>
            <pubDate>Wed, 01 Jan 2025 14:03:49 GMT</pubDate>
            <description><![CDATA[<h3 id="요약">요약</h3>
<pre><code>풀이 시간 : 19분 27초

1. 직관적인 변수명 사용하기
2. Math.abs() 절대값을 통해 값 계산하는 유형 파악
  -&gt; 이외에도 count ++ -- 로 균형을 이루는 계산도 있음
3. flatMap() 메서드의 활용
4. 구조분해할당의 활용
5. 배열을 활용한  Object, Map, Set 객체 선언</code></pre><h3 id="문제-링크">문제 링크</h3>
<p><a href="https://school.programmers.co.kr/learn/courses/30/lessons/118666">성격 유형 검사하기</a></p>
<h3 id="풀이">풀이</h3>
<pre><code class="language-js">function solution(survey, choices) {
    const arr = [
        &quot;R&quot;,&quot;T&quot;,
        &quot;C&quot;,&quot;F&quot;,
        &quot;J&quot;,&quot;M&quot;,
        &quot;A&quot;,&quot;N&quot;,
    ]; // 성격 지표
    const scores = new Map(arr.map(key =&gt; [key, 0])); // 성격 지표 점수
    const points = [&quot;&quot;, 3, 2, 1, 0, 1, 2, 3]; // 점수 환산

    for(const [i, pair] of survey.entries()){
        const choice = choices[i]; // 선택지
        const point = points[choice]; // 반영 점수
        const [key1, key2] = pair; // key값 반영
        if(choice &lt; 4){ // 점수 반영하기
            scores.set(key1, scores.get(key1) + point);
        }else if(choice &gt; 4 ){
            scores.set(key2, scores.get(key2) + point);
        }
    }

    let result = &quot;&quot;;
    result += scores.get(&quot;R&quot;) &gt;= scores.get(&quot;T&quot;) ? &quot;R&quot; : &quot;T&quot;;
    result += scores.get(&quot;C&quot;) &gt;= scores.get(&quot;F&quot;) ? &quot;C&quot; : &quot;F&quot;;
    result += scores.get(&quot;J&quot;) &gt;= scores.get(&quot;M&quot;) ? &quot;J&quot; : &quot;M&quot;;
    result += scores.get(&quot;A&quot;) &gt;= scores.get(&quot;N&quot;) ? &quot;A&quot; : &quot;N&quot;;
    return result;
}</code></pre>
<p>성격 유형별 점수를 계산하고, 최종적으로 더 높은 점수를 가진 항목을 선택하여, 성격유형을 선택하는 문제입니다. 성격 지표 점수를 반영할 Map 객체를 만들고, survey 를 순회하면서, 선택한 점수를 반영하고, 최종적으로 선택된 결과를 반환하는 문제입니다.</p>
<h3 id="풀이-개선">풀이 개선</h3>
<pre><code class="language-js">function solution(survey, choices) {
    const MBTIs = [&quot;RT&quot;,&quot;CF&quot;,&quot;JM&quot;,&quot;AN&quot;]; // MBTI 유형들
    const scores = new Map(MBTIs.flatMap(([key1, key2]) =&gt; [[key1, 0], [key2, 0]])); // 점수 반영

    for(const [i, MBTI] of survey.entries()){ // 검사 반영
        const choice = choices[i]; // 검사결과
        const [key1, key2] = MBTI; 
        if(choice &lt; 4) {
            scores.set(key1, scores.get(key1) + Math.abs(choice - 4));
        }else if(choice &gt; 4){
            scores.set(key2, scores.get(key2) + Math.abs(choice - 4));
        }
    }

    return MBTIs
    .map(([key1, key2]) =&gt; scores.get(key1) &gt;= scores.get(key2) ? key1 : key2)
    .join(&quot;&quot;);
}</code></pre>
<p>변수명을 더 직관적인 이름인 MBTI로 변경하여 코드의 가독성을 높였으며, 중복 코드를 map 메서드를 활용해 제거했습니다. 또한, 점수 계산은 Math.abs()를 사용하여 간단하고 직관적으로 개선했습니다.</p>
]]></description>
        </item>
    </channel>
</rss>