<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>DONG_MIN.log</title>
        <link>https://velog.io/</link>
        <description>Developer보단 Engineer로 될래</description>
        <lastBuildDate>Sat, 10 Jan 2026 11:34:50 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <image>
            <title>DONG_MIN.log</title>
            <url>https://velog.velcdn.com/images/dongmin_0204/profile/1be6947e-1ec9-4b79-9487-55cc42eff956/image.jpg</url>
            <link>https://velog.io/</link>
        </image>
        <copyright>Copyright (C) 2019. DONG_MIN.log. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/dongmin_0204" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[React를 React답게, Thinking in React(1):  UI는 function]]></title>
            <link>https://velog.io/@dongmin_0204/React%EB%A5%BC-React%EB%8B%B5%EA%B2%8C-Thinking-in-React1-UI%EB%8A%94-function</link>
            <guid>https://velog.io/@dongmin_0204/React%EB%A5%BC-React%EB%8B%B5%EA%B2%8C-Thinking-in-React1-UI%EB%8A%94-function</guid>
            <pubDate>Sat, 10 Jan 2026 11:34:50 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/dongmin_0204/post/160dc5c8-b14a-4dca-b885-db4725222c1c/image.png" alt=""></p>
<h4 id="디자인도-이쁘고-코드도-이쁘게-짤-순-없을까">디자인도 이쁘고 코드도 이쁘게 짤 순 없을까??</h4>
<p>이 단순한 생각에서 이 글 시리즈를 작성하게 된 계기가 되었습니다!
<em>(대 바이브코딩 시대에, 문서보고 코드짜는 멋쟁이가 되고 싶어...)</em></p>
<p><img src="https://velog.velcdn.com/images/dongmin_0204/post/34b95a7e-8e45-439b-9150-c2e4ae9fc931/image.png" alt=""></p>
<h3 id="리액트를-직접-만든-사람들은-어떻게-생각하고-만들었을까">리액트를 직접 만든 사람들은 어떻게 생각하고 만들었을까?</h3>
<p>React 공식 문서 <a href="https://ko.react.dev/learn/thinking-in-react"><em>Thinking in React</em></a>를 단순히 ‘컴포넌트 나누는 방법’으로 보는 것이 아니라,</p>
<p>제가 열심히 해체 분석해보려 합니다! 
<del>shout out to sAewoo</del></p>
<p><strong>React의 본질적 철학(순수 함수 UI, 책임 분리, 단방향 데이터 흐름, 예측 가능성)</strong> 
관점에서 알아보려고 합니다.</p>
<p>이 관점을 기반으로 다음을 얻을 수 있습니다!:</p>
<ul>
<li><p>React가 왜 이런 방식으로 UI를 구성하도록 가이드를 제시하는지 <strong>철학적 이유</strong></p>
</li>
<li><p>컴포넌트 설계가 ‘감’에서 ‘원리 기반 판단’으로 전환하기.(추구했던 바!!)</p>
</li>
</ul>
<hr>
<h1 id="목차">목차</h1>
<ol>
<li><strong>Thinking in React 5단계의 나만의 해석</strong></li>
<li><strong>상태 배치(State Architecture)를 판단하는 기준</strong></li>
<li><strong>단방향 데이터 흐름과 예측 가능성 확보</strong></li>
</ol>
<hr>
<h1 id="1-ui--fstate-왜-이-식이-필요할까">1. UI = f(state): 왜 이 식이 필요할까?</h1>
<p>React를 진지하게 이해하려면, 먼저 이 한 줄을 이해해야 한다.</p>
<pre><code class="language-txt">UI = f(state)</code></pre>
<p>React 제작자 들이 하고 싶은 말은 단순하다.</p>
<blockquote>
<p>“UI는 직접 조작하는 대상이 아니라,
상태(state)를 넣으면 계산되어 나오는 <strong>결과물</strong>이어야 한다.”</p>
</blockquote>
<p>이 식이 왜 필요한지, 그리고 이게 무너지면 어떤 지옥이 펼쳐지는지부터 짚고 가자.</p>
<hr>
<h2 id="11-기존-ui-패턴이-왜-망가지는가-상태-지옥">1.1 기존 UI 패턴이 왜 망가지는가: “상태 지옥”</h2>
<p>전통적인 웹 UI는 대부분 이렇게 동작했었다.</p>
<ul>
<li>DOM에서 요소를 찾고</li>
<li>이벤트가 발생할 때마다</li>
<li>class를 붙였다 떼었다,</li>
<li>텍스트를 바꾸고,</li>
<li>여러 상태나 데이터를 “수동으로” 맞춘다.</li>
</ul>
<p>문제는 <strong>상태가 한 군데에 모여 있지 않다</strong>는 점이다.</p>
<ul>
<li>서버 응답 결과 일부는 전역 변수에,</li>
<li>일부는 DOM attribute에,</li>
<li>일부는 컴포넌트 인스턴스 필드에,</li>
<li>심지어 유저가 뭔가 조작하면 또 다른 곳에 복사본이 생긴다.</li>
</ul>
<p>이렇게 되면 필연적으로 이런 상황이 온다.</p>
<ul>
<li>어떤 버튼은 <code>활성화</code>인데, 다른 영역은 여전히 <code>비활성화</code>로 보인다.</li>
<li>모달은 닫혔는데, 오버레이는 남아 있다.</li>
<li>토글 버튼 텍스트는 <code>ON</code>인데 실제 로직 값은 <code>false</code>다.</li>
</ul>
<p>즉, <strong>“앱의 진짜 상태”와 “눈에 보이는 UI”가 서로 어긋나는 순간</strong>부터
디버깅은 감과 운에 의존하는 게임이 된다.</p>
<p>이게 React가 해결하려는 문제의 본질이다.</p>
<hr>
<h2 id="12-react의-관점-ui를-단순-화면이-아니라-함수로-취급하자">1.2 React의 관점: “UI를 단순 화면이 아니라 함수로 취급하자”</h2>
<p>React는 여기서 접근을 통째로 바꾼다.</p>
<ul>
<li>더 이상 “UI를 어떻게 바꿀지(how)”를 작성하지 말고,</li>
<li>“지금 상태라면 UI가 어떻게 생겨야 하는지(what)”를 정의하라고 한다.</li>
</ul>
<p>즉,</p>
<pre><code class="language-txt">UI = f(state)</code></pre>
<ul>
<li>입력: 현재 앱 상태(state)</li>
<li>출력: 그 상태에서의 UI 트리(React element tree)</li>
</ul>
<p>우리는 더 이상 <code>DOM 조작 절차</code>를 나열하지 않는다.
대신 <code>state를 인자로 받아 UI를 반환하는 함수</code>를 설계한다.</p>
<p>이게 “선언형 UI(Declarative UI)”.</p>
<blockquote>
<p>“상태가 이렇게 생겼으면, 화면은 이렇게 보여야 한다”를
<strong>함수 정의로</strong> 고정해 두는 것.</p>
</blockquote>
<p><img src="https://velog.velcdn.com/images/dongmin_0204/post/3929aae5-4829-495d-b401-f21c87698cb0/image.png" alt=""></p>
<hr>
<h2 id="13-순수-함수-ui의-세-가지-조건">1.3 순수 함수 UI의 세 가지 조건</h2>
<p>React가 진짜로 원하는 건 <strong>“순수 함수처럼 보이는 UI”</strong>다.
순수 함수 UI는 최소한 아래 세 가지를 만족해야 한다.</p>
<ol>
<li><p><strong>같은 state == 항상 같은 UI</strong></p>
<ul>
<li>랜덤, 시간, 전역 mutable 변수 등에 의해 결과가 바뀌지 않는다.</li>
</ul>
</li>
<li><p><strong>UI는 오직 props와 state로만 결정된다</strong></p>
<ul>
<li>컴포넌트 내부에서 몰래 전역 싱글톤을 직접 바꾸고 참조하는 패턴은 독이다.</li>
</ul>
</li>
<li><p><strong>렌더링 과정에는 side effect가 없다</strong></p>
<ul>
<li>DOM 직접 조작, 네트워크 요청, 로깅 등은 <code>useEffect</code> 같은 별도 단계에서 처리해야 한다.</li>
</ul>
</li>
</ol>
<p>이게 지켜져야 다음이 가능해진다.</p>
<ul>
<li>상태만 보면 UI가 어떻게 생겼는지 <strong>예측</strong> 가능</li>
<li>특정 state를 주입해서 화면을 <strong>테스트</strong> 가능 (그래서 TDD를?)</li>
<li>UI 변경 이력을 <strong>추론</strong> 가능(“왜 이렇게 보이는지” 설명 가능)</li>
</ul>
<p>즉, <strong>테스트 가능하고, 예측 가능하고, 리팩터링 가능한 UI</strong>의 최소 조건이 바로 이거다.</p>
<hr>
<h2 id="14-이-식이-설계에-강제하는-구조적-결과">1.4 이 식이 설계에 강제하는 구조적 결과</h2>
<p>UI = f(state)를 진지하게 받아들이면,
설계는 어쩔 수 없이 다음과 같이 바뀐다.</p>
<ol>
<li><p><strong>상태는 함수 밖에서 관리하고, arg로 주입한다.</strong></p>
<ul>
<li>컴포넌트는 “현재 상태를 기반으로 UI를 그리는 기계”가 된다.</li>
</ul>
</li>
<li><p><strong>렌더링 로직은 side effect에서 분리된다.</strong></p>
<ul>
<li>그리는 것(render)과 “반응하는 것(effect)”을 분리해야 순수성이 유지된다.</li>
</ul>
</li>
<li><p><strong>자식 컴포넌트는 함수 합성처럼 다뤄진다.</strong></p>
<ul>
<li>부모가 state → UI로 계산한 결과 일부를 props로 넘긴다.</li>
<li>자식은 받은 props를 다시 UI로 매핑하는 작은 함수다.</li>
</ul>
</li>
</ol>
<p>즉, 전체 React 앱은 거대한:</p>
<pre><code class="language-txt">AppUI = f(globalState)</code></pre>
<p>를 작은 함수 여러 개의 합성으로 분해한 것에 불과하다.</p>
<p>이 관점이 잡히면, 컴포넌트 나누기, props 구조, state 위치 선정은
전부 이 식을 깨지 않기 위한 <strong>아키텍처 작업</strong>으로 이해할 수 있다.</p>
<hr>
<h2 id="15-ui--fstate가-깨질-때-벌어지는-사고들">1.5 UI = f(state)가 깨질 때 벌어지는 사고들</h2>
<p>반대로, 이 원칙을 무시하면 어떤 일이 생길까?
대표적인 실패 패턴 몇 가지를 보자.</p>
<ol>
<li><p><strong>렌더링 중 전역으로 읽고/수정하는 컴포넌트</strong></p>
<ul>
<li>테스트 환경에서 상태 주입이 안 된다.</li>
<li>어디서 값이 바뀌는지 추적이 불가능해진다.</li>
</ul>
</li>
<li><p><strong>derived state를 또 다른 state로 중복 저장하는 패턴</strong></p>
<ul>
<li>예: <code>items</code>와 <code>filteredItems</code>를 둘 다 state로 들고 있기</li>
<li>둘의 동기화를 계속 맞춰야 한다는 건, 결국 언젠가 틀어진다.(디버깅시 매우 까다로워진다.)</li>
<li>사실 <code>filteredItems = f(items, filter)</code>로 매번 계산하는 게 맞다.</li>
</ul>
</li>
<li><p><strong>렌더링 안에서 DOM을 직접 건드리는 코드</strong></p>
<ul>
<li>React의 렌더링 사이클과 충돌한다.</li>
<li>“분명히 상태는 false인데, DOM은 여전히 열려 있음” 같은 모순 발생.</li>
</ul>
</li>
</ol>
<p>이 모든 문제는 한 줄로 정리된다.</p>
<blockquote>
<p>“UI가 state의 함수가 아닐 때, UI와 state는 언젠가 반드시 어긋난다.”</p>
</blockquote>
<hr>
<h2 id="16-thinking-in-react">1.6 Thinking in React</h2>
<p>이제 <strong>“왜 UI = f(state)가 필요한가”</strong>는 어느 정도 명확해졌다.
그럼 공식 문서의 5단계는 어디에 위치할까?</p>
<ul>
<li>UI를 컴포넌트로 나누기</li>
<li>정적 버전 만들기</li>
<li>최소한의 상태 찾기</li>
<li>상태를 어디 둘지 정하기</li>
<li>단방향 데이터 흐름 구성하기</li>
</ul>
<p>이건 전부 메타 수준에서 보면 한 가지 목적.</p>
<blockquote>
<p><strong>“앱 전체를 UI = f(state) 구조에 맞게 재조직하는 절차”</strong></p>
</blockquote>
<ul>
<li>정적 버전 만들기는, <strong>순수 함수 UI</strong> 먼저 확보</li>
<li>최소 상태 찾기는, f의 입력을 <strong>필요 최소한으로 압축</strong></li>
<li>상태 위치 정하기는, f가 어디서 정의되는지, <strong>책임 단위로 배치</strong></li>
<li>단방향 데이터 흐름는, <strong>입력이 어디서 왔는지 추적가능</strong>하게 만들기</li>
</ul>
<p>그래서 이 글 전체의 구조도 이렇게 잡아보자.</p>
<ol>
<li><strong>1장: UI = f(state) — 왜 이 식이 필요한가 (지금 읽고 있는 부분)</strong></li>
<li><strong>2장: 상태관리와 책임 단위 — f(state)에서 state를 어디서 관리할 것인가</strong></li>
<li><strong>3장: 단방향 데이터 흐름 — state -&gt; UI -&gt; 이벤트 -&gt; state로 이어지는 루프를 어떻게 닫을 것인가</strong></li>
</ol>
<p>이제 다음 장에서 다룰 질문은 명확하다.</p>
<blockquote>
<p>“좋다, UI = f(state)인 건 알겠다.
<strong>그럼 그 state는 어디에 둬야 예측 가능한 시스템이 되나?</strong>”</p>
</blockquote>
<p>그게 바로 2장의 주제,
<strong>“상태관리 및 책임 단위(State Architecture)”</strong>이다.</p>
<p>출처 : 
<a href="https://overreacted.io/the-two-reacts/">https://overreacted.io/the-two-reacts/</a>
<a href="https://www.youtube.com/watch?v=x7cQ3mrcKaY">https://www.youtube.com/watch?v=x7cQ3mrcKaY</a>
<a href="https://overreacted.io/a-complete-guide-to-useeffect/">https://overreacted.io/a-complete-guide-to-useeffect/</a>
<a href="https://medium.com/@dan_abramov/smart-and-dumb-components-7ca2f9a7c7d0">https://medium.com/@dan_abramov/smart-and-dumb-components-7ca2f9a7c7d0</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[항해 플러스] 1주차 - 1 : TDD를 통해 포인트의 동시성 락 개선기]]></title>
            <link>https://velog.io/@dongmin_0204/%ED%95%AD%ED%95%B4-%ED%94%8C%EB%9F%AC%EC%8A%A4-1%EC%A3%BC%EC%B0%A8</link>
            <guid>https://velog.io/@dongmin_0204/%ED%95%AD%ED%95%B4-%ED%94%8C%EB%9F%AC%EC%8A%A4-1%EC%A3%BC%EC%B0%A8</guid>
            <pubDate>Sun, 26 Oct 2025 14:58:29 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/dongmin_0204/post/5564b83d-059e-4361-aa8f-f0a0d5ca8f19/image.png" alt=""></p>
<h1 id="문제">문제</h1>
<p>이번 주엔 
<strong>1. TDD를 활용 하기 (RED-GREEN-REFATOR)</strong>
<strong>2. 포인트에서의 동시성 락을 어떻게 해결할까?
(non-blocking, single-thread 기반의 nest.js)</strong></p>
<h2 id="tdd와의-첫-만남-시험-2일-전">TDD와의 첫 만남... (시험 2일 전)</h2>
<p>사실 첫만남은 매우 비호감입니다; 중간고사와 겹치는 불상사!어쩔 수 없습니다.
해내는게 저이고 말고요.</p>
<p>일단 TDD의 기본 베이스는 이렇습니다.</p>
<blockquote>
<p><strong>Red</strong> :  실패하는 테스트 코드를 먼저 작성
<strong>Green</strong> :  테스트 코드를 성공시키기 위한 실제 코드를 작성
<strong>Refactor</strong> : 테스트 결과에 맞추어 (커버리지나 중복 코드 및 읽기 쉬운 코드 등) 리팩토링을 수행하는 것</p>
</blockquote>
<p><img src="https://velog.velcdn.com/images/dongmin_0204/post/d3e785bd-3e13-4dda-b3c9-ed224ac6edb5/image.png" alt=""></p>
<p>개인적으로 느낀 장점은 이렇습니다. </p>
<h3 id="한-번에-조금씩-커밋하고-메시지는-claude로-자동화"><strong>한 번에 조금씩 커밋하고, 메시지는 Claude로 자동화</strong></h3>
<p>이렇게 개발하는 방법은 처음 겪어 봤기에 테스트 코드를 작성을 먼저 해봤습니다.</p>
<pre><code class="language-javascript">// 동시에 충전과 사용 요청
            const promises = [
                service.chargePoint(userId, chargeAmount), // +3000
                service.usePoint(userId, useAmount),       // -2000  
                service.chargePoint(userId, chargeAmount)  // +3000
            ];

            const results = await Promise.all(promises);

            // 최종 잔고는 15000 + 3000 - 2000 + 3000 = 19000이어야 함
            const expectedFinalBalance = 
                initialBalance + chargeAmount - useAmount + chargeAmount;

            // 모든 연산이 순차적으로 적용되었는지 확인
            // 현재는 동시성 제어가 없어서 예상과 다를 것
            const balances = results.map(result =&gt; result.point);
            const maxBalance = Math.max(...balances);

            expect(maxBalance).toBe(expectedFinalBalance);
        }, 10000);</code></pre>
<p>이렇게 spec.ts를 기준으로 가설을 세우고, 동시에 충전과 사용 요청을 만들어 봤습니다. </p>
<hr>
<p><img src="https://velog.velcdn.com/images/dongmin_0204/post/ddf6140e-5c24-4107-bd85-15933e548784/image.png" alt=""></p>
<blockquote>
<p>초기값과 최종값에만 의존하는 상태함수 개념 (간단한 아이디어)</p>
</blockquote>
<p>그래도 아직 테스트 코드 자체의 문법이 익숙하지 않아 학습할 필요가 있어
숙달해야할 듯 합니다.</p>
<p>사실 제대로 했을까?</p>
<h4 id="현재-테스트는-최종-상태를-보지-않고-중간-응답의-최댓값으로-판정하므로-신뢰할-수-없움">현재 테스트는 최종 상태를 보지 않고 중간 응답의 최댓값으로 판정하므로 신뢰할 수 없움</h4>
<p><a href="https://docs.nestjs.com/fundamentals/testing#unit-testing">nest.js의 튜토리얼을 참고해보기!</a></p>
<h3 id="그렇게-커버리지-100퍼-달성은-refactoring-과정에서-달성을-할-수-있었다-좋은거-맞아">그렇게 커버리지 100퍼 달성은 refactoring 과정에서 달성을 할 수 있었다? 좋은거 맞아?</h3>
<p><img src="https://velog.velcdn.com/images/dongmin_0204/post/c07aff8e-169a-40e9-9124-bde5e2c33dec/image.png" alt=""></p>
<p><em>마치 문제를 정의하고 틀리게 해결하면 무슨 소용일까요.</em></p>
<p><a href="https://toss.tech/article/test-strategy-server">가치있는 테스트를 위한 전략과 구현
</a></p>
<p>위의 테스트 코드의 전략과 구현의 글을 보고 좋은 테스트 코드는 무엇인지 잘 숙지를 해야한다는 점입니다.</p>
<blockquote>
<p><strong>Fast</strong>: 테스트는 빠르게 동작하여 자주 돌릴 수 있어야 함
<strong>Independent</strong>: 각각의 테스트는 독립적이며 서로 의존해서는 안 됨
<strong>Repeatable</strong>: 어느 환경에서도 반복 가능해야 함
<strong>Self-Validating</strong>: 테스트는 성공 또는 실패로 bool 값으로 결과를 내어 자체적으로 검증되어야 함
<strong>Timely</strong>: 테스트는 적시에 즉, 테스트하려는 실제 코드를 구현하기 직전에 구현해야 함</p>
</blockquote>
<h3 id="✨-핵심-테스트-코드를-목적에-맞게-적절히-구현하는-것이-중요">✨ 핵심 테스트 코드를 목적에 맞게 적절히 구현하는 것이 중요</h3>
<hr>
<h2 id="동시성-락-싸우지마-애들아">동시성 락, 싸우지마 애들아!</h2>
<p><img src="https://velog.velcdn.com/images/dongmin_0204/post/5cd3b2b2-32be-40a2-916e-fb1a0bca53db/image.png" alt=""></p>
<p>위 그림을 본다면 덮어쓰기가 2번 동작하네??? 어라,,, 그러면 115달러가 아니고 105달러가 최종 답이네...? John과 Alice는 계좌 금액에 대해 동시 접근을 해...</p>
<blockquote>
<h3 id="경쟁-상태race-condition란">경쟁 상태(race condition)란?</h3>
<p>여러 개의 프로세스가 공유 자원에 <strong>동시 접근</strong>할 때 실행 순서에 따라 결과값이 달라질 수 있는 현상</p>
</blockquote>
<p>그렇다면 어떻게 해결하는 것이 좋을까?</p>
<h3 id="누가-먼저-할래-그럼-줄-서"><strong>누가 먼저 할래?</strong> 그럼 줄 서</h3>
<ul>
<li>선택한 접근 방식: Promise 기반 Lock 시스템</li>
</ul>
<h4 id="왜-이-방식을-선택했는가">왜 이 방식을 선택했는가?</h4>
<ul>
<li><ol>
<li>Node.js 싱글 스레드 특성 활용하기</li>
</ol>
</li>
</ul>
<pre><code class="language-javascript">// 전통적인 Mutex 대신 Promise 기반 접근
private readonly locks = new Map&lt;number, Promise&lt;any&gt;&gt;();</code></pre>
<pre><code>Node.js는 싱글 스레드 이벤트 루프 기반
메모리 내 Map을 통한 간단하고 효율적인 구현
별도 라이브러리 의존성 없음</code></pre><ul>
<li><ol start="2">
<li>사용자별 독립적 Lock 관리</li>
</ol>
</li>
</ul>
<pre><code class="language-javascript">async withLock&lt;T&gt;(userId: number, operation: () =&gt; Promise&lt;T&gt;): Promise&lt;T&gt; {
    const existingLock = this.locks.get(userId);
    // 사용자별 개별 Lock 처리
}</code></pre>
<p>다음과 같은 플로우 차트를 기반으로 기획을 했었다.
<img src="https://velog.velcdn.com/images/dongmin_0204/post/2b17cf56-a8d5-4a20-b989-25fbc3ef3c91/image.png" alt=""></p>
<p>사실 이건 거짓 락에 불과한 건데, 제출하고 나서 스스로 분석도 하고 피드백을 참고해보니까...</p>
<ul>
<li><h3 id="1-허들thundering-herd문제--이벤트가-발생하면-모두-동시에-깨어나-하나의-자원을-차지하려고-경쟁하는-현상">1. 허들(Thundering herd)문제 : 이벤트가 발생하면 모두 동시에 깨어나 하나의 자원을 차지하려고 경쟁하는 현상</h3>
</li>
</ul>
<pre><code>if (existingLock) await Promise.race([existingLock, timeout]);
const newLock = operation();
this.locks.set(userId, newLock);</code></pre><p>여러 호출이 같은 기존 락이 끝나길 기다린 뒤, 동시에 operation()을 시작할 수 있습니다. 마치 <strong>오픈런</strong>...</p>
<ul>
<li><h3 id="2-해제-제대로-안된다">2. 해제 제대로 안된다...</h3>
<pre><code class="language-javascript">  operation().finally(() =&gt; this.locks.delete(userId));</code></pre>
</li>
</ul>
<p>나보다 나중에 들어온 작업이 this.locks.set(userId, next)로 <strong>새   작업(tail)</strong>을 세팅해도, 먼저 끝난 작업의 finally가 <strong>무조건   delete</strong>를 호출하면 현재 진행 중인 끝 부분도 같이 지워진다.</p>
<ul>
<li><h3 id="3-이전-작업-실패-전파">3. 이전 작업 실패 전파</h3>
</li>
</ul>
<p>existingLock이 reject 되면 다음 호출도 await에서 <strong>같이 터짐</strong>
-&gt; 연쇄 실패. (보통 직렬화 큐는 “이전 실패와 무관하게 다음
 작업은 진행”이 자연스러움)</p>
<ul>
<li><h3 id="4-공정성대기열-없음">4. 공정성/대기열 없음</h3>
</li>
</ul>
<p>FIFO 보장 없음. <strong>“먼저 도착한 요청이 먼저 실행”</strong>이 깨지기 쉽습니다.</p>
<hr>
<p><img src="https://velog.velcdn.com/images/dongmin_0204/post/446afe1e-1e56-4b86-bca8-8e432f3a3b81/image.png" alt="">
<em>가차없는 피드백을 받았습니다,,, 사실 map말고 queue를 썼다면 좋을텐데~</em></p>
<hr>
<h2 id="다음-목표">다음 목표</h2>
<h3 id="시간에-쫓기지-않고-좀-더-cs기반으로-판단해서-구현을-하는-것을-목적으로">시간에 쫓기지 않고 좀 더 CS기반으로 판단해서 구현을 하는 것을 목적으로!</h3>
<p><em>플로우 차트를 그리면서 고민 또 고민을 했어야했고, 왜 맵을 썼는지 이유를 근거해서 구현을 하면 좋을 것 같아.</em></p>
<h3 id="내가-맞게-했는지-의심하고-또-의심해라">내가 맞게 했는지 의심하고 또 의심해라</h3>
<p><em>구현을 하면서 테스트코드 자체가 맞다고 생각하면서 구현을 하니 아무것도 성공할 수 없었습니다. 잘못된 테스트는 잘못된 리팩토링을 불러옵니다</em></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[항해 플러스] 시작하는 마음]]></title>
            <link>https://velog.io/@dongmin_0204/%ED%95%AD%ED%95%B4-%ED%94%8C%EB%9F%AC%EC%8A%A4-%EC%8B%9C%EC%9E%91%ED%95%98%EB%8A%94-%EB%A7%88%EC%9D%8C</link>
            <guid>https://velog.io/@dongmin_0204/%ED%95%AD%ED%95%B4-%ED%94%8C%EB%9F%AC%EC%8A%A4-%EC%8B%9C%EC%9E%91%ED%95%98%EB%8A%94-%EB%A7%88%EC%9D%8C</guid>
            <pubDate>Mon, 20 Oct 2025 07:46:39 GMT</pubDate>
            <description><![CDATA[<h2 id="항해-플러스-참여-계기">항해 플러스 참여 계기</h2>
<p>사실 프론트엔드 개발만 진행하다 보고, 어쩌다 내일 일경험에서 백엔드도 다뤄야 했어서 조금씩 경험했던 게, 계기가 되었다. 좋은 사람들과 함께 성장할 수 있다는 점도 매우 크게 작용했다. 학교 밖에서의 사람들과 협업할 수 있다니 너무 기쁘다.</p>
<h2 id="10주간의-목표">10주간의 목표</h2>
<h4 id="1-tdd를-앞으로의-프로젝트에서-다룰-수-있을-정도">1. TDD를 앞으로의 프로젝트에서 다룰 수 있을 정도</h4>
<h4 id="2-대용량-트래픽을-활용한-나만의-프로젝트-개발">2. 대용량 트래픽을 활용한 나만의 프로젝트 개발</h4>
<h4 id="3-여러-선배-개발자들의-지식-및-철학-흡수하기">3. 여러 선배 개발자들의 지식 및 철학 흡수하기</h4>
<p><img src="https://velog.velcdn.com/images/dongmin_0204/post/f0e58de6-ef7c-4182-8477-d8f7ec6e78be/image.png" alt=""></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[2018 KAKAO BLIND RECRUITMENT
[1차] 뉴스 클러스터링]]></title>
            <link>https://velog.io/@dongmin_0204/2018-KAKAO-BLIND-RECRUITMENT1%EC%B0%A8-%EB%89%B4%EC%8A%A4-%ED%81%B4%EB%9F%AC%EC%8A%A4%ED%84%B0%EB%A7%81</link>
            <guid>https://velog.io/@dongmin_0204/2018-KAKAO-BLIND-RECRUITMENT1%EC%B0%A8-%EB%89%B4%EC%8A%A4-%ED%81%B4%EB%9F%AC%EC%8A%A4%ED%84%B0%EB%A7%81</guid>
            <pubDate>Tue, 07 Oct 2025 09:47:42 GMT</pubDate>
            <description><![CDATA[<h1 id="프로그래머스-뉴스-클러스터링-풀이">프로그래머스 뉴스 클러스터링 풀이</h1>
<p><a href="https://school.programmers.co.kr/learn/courses/30/lessons/17677">프로그래머스 뉴스 클러스터링</a></p>
<h2 id="문제-정의">문제 정의</h2>
<p>두 문자열에서 연속된 2글자씩 끊어 만든 다중집합을 구성하고, 두 다중집합의 자카드 유사도 J(A, B) = 교집합(A,B) / 합집합(A,B) 를 계산해 
65536을 곱해 소수점 아래를 버린 값을 구해야 한다.</p>
<ul>
<li>알파벳만 유효(숫자/특수문자 포함 쌍은 제외)</li>
<li>대소문자 구분 없음(모두 소문자로 처리)</li>
<li>다중집합(원소 중복 허용) 기준의 교집합/합집합 계산</li>
<li>두 집합 모두 공집합이면 유사도 1로 간주, 65536 반환</li>
</ul>
<h2 id="접근-방법">접근 방법</h2>
<ul>
<li><p>문자열을 순회하며 2글자 쌍을 만들고, 둘 다 알파벳인 경우만 선택</p>
</li>
<li><p>소문자로 변환하여 정규화 : tolower() 사용</p>
</li>
<li><p>다중집합 구현은 2차원 카운팅 배열 사용: 알파벳 26×26 (a~z)</p>
<ul>
<li><code>count1[a][b]</code>, <code>count2[a][b]</code></li>
</ul>
</li>
<li><p>교집합 크기: 각 키에 대해 <code>min(count1, count2)</code>의 합</p>
</li>
<li><p>합집합 크기: 각 키에 대해 <code>max(count1, count2)</code>의 합</p>
<ul>
<li>예시의 힌트를 활용했다!
<img src="https://velog.velcdn.com/images/dongmin_0204/post/39a8528d-62b0-4121-8a8c-a45dc073dbcd/image.png" alt=""></li>
</ul>
</li>
<li><p><em>개꿀*</em></p>
</li>
<li><p>합집합이 0이면 65536, 아니면 <code>floor((inter/union) * 65536)</code> 반환</p>
</li>
</ul>
<h2 id="구현-코드-c">구현 코드 (C++)</h2>
<pre><code class="language-cpp">#include &lt;bits/stdc++.h&gt;

// 어 문자열 문제? 
// 유사도를 구하는데 2개씩 비교 (교집합) / (합집합)
// 집합 A와 집합 B가 모두 공집합일 경우 -&gt; 1

// 확장하니 원소의 중복을 허용하는 다중집합
// (교집합-중복x) / (합집합-중복o)
// 알파벳 + 빈도 수 -&gt; 2차원 &lt;키(a,b), 나오는 빈도&gt; 배열 활용
using namespace std;


int solution(string str1, string str2) {
    int answer = 0;

    int ch_map1[26][26];
    int ch_map2[26][26];

    fill(&amp;ch_map1[0][0], &amp;ch_map1[0][0]+26 * 26, 0);
    fill(&amp;ch_map2[0][0], &amp;ch_map2[0][0]+26 * 26, 0);
    //알바벳 first, second 에 저장되는 cnt -&gt; 0으로 초기화

    for(int i = 0; i &lt; str1.size()-1; i++){
        char a = tolower(str1[i]);
        char b = tolower(str1[i+1]);
        if (isalpha(a) &amp;&amp; isalpha(b)) {
            ch_map1[a - &#39;a&#39;][b - &#39;a&#39;]++;
        }
    }
    for(int i = 0; i &lt; str2.size()-1; i++){
        char a = tolower(str2[i]);
        char b = tolower(str2[i+1]);
        if (isalpha(a) &amp;&amp; isalpha(b)) {
            ch_map2[a - &#39;a&#39;][b - &#39;a&#39;]++;
        }
    }



    int interCnt = 0; //교집합
    int unionCnt = 0; //합집합
    for (int i = 0; i &lt; 26; i++) {
        for (int j = 0; j &lt; 26; j++) {
            interCnt += min(ch_map1[i][j], ch_map2[i][j]); 
            //설명에서 다중집합 확장한 방법
            unionCnt += max(ch_map1[i][j], ch_map2[i][j]);
        }
    }

    if (unionCnt == 0) return 65536; //공집합이면 유사도 1

    //자카드 유사도 계산
    answer = floor( (interCnt*1.0 / unionCnt) * 65536);//소수점 버림

    return answer;
}</code></pre>
<h2 id="핵심-포인트">핵심 포인트</h2>
<ol>
<li><strong>정규화</strong>: 대소문자 무시 -&gt; 모두 소문자 변환</li>
<li><strong>유효 문자 필터링</strong>: 2글자 모두 알파벳일 때만 선택하기</li>
<li><strong>다중집합 계산</strong>: 교집합은 min, 합집합은 max의 합으로 구현 : 설명 내에 답이 있었다.</li>
<li><strong>엣지 케이스</strong>: 합집합이 0이면 65536 반환</li>
<li><strong>상수 크기 맵</strong>: 26×26 카운팅 배열로 빠르게 처리 (메모리/속도 유리)</li>
</ol>
<h2 id="시간-복잡도">시간 복잡도</h2>
<ul>
<li>문자열 전처리: O(|str1| + |str2|)</li>
<li>교집합/합집합 계산: O(26×26) = O(1)</li>
<li>전체: O(N)</li>
</ul>
<h2 id="학습-포인트">학습 포인트</h2>
<ul>
<li>다중집합 교집합/합집합을 빈도 맵으로 구현하는 패턴 이해</li>
<li>문자열 전처리와 유효성 검사(isalpha, tolower) 순서 중요</li>
<li>자카드 유사도 정의 및 엣지 케이스 처리(공집합)</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[
2020 카카오 인턴십
경주로 건설
]]></title>
            <link>https://velog.io/@dongmin_0204/2020-%EC%B9%B4%EC%B9%B4%EC%98%A4-%EC%9D%B8%ED%84%B4%EC%8B%AD%EA%B2%BD%EC%A3%BC%EB%A1%9C-%EA%B1%B4%EC%84%A4</link>
            <guid>https://velog.io/@dongmin_0204/2020-%EC%B9%B4%EC%B9%B4%EC%98%A4-%EC%9D%B8%ED%84%B4%EC%8B%AD%EA%B2%BD%EC%A3%BC%EB%A1%9C-%EA%B1%B4%EC%84%A4</guid>
            <pubDate>Sat, 04 Oct 2025 20:33:16 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/dongmin_0204/post/74ce5190-c79c-4fce-8d9a-ba6f25dde7d9/image.png" alt="프로그래머스 경주로 건설">
<a href="https://school.programmers.co.kr/learn/courses/30/lessons/67259">프로그래머스 경주로 건설</a></p>
<h2 id="문제-정의">문제 정의</h2>
<ul>
<li><p><strong>입력</strong>: N×N 보드 (0=빈칸, 1=벽)</p>
</li>
<li><p><strong>시작점</strong>: (0,0)</p>
</li>
<li><p><strong>목표점</strong>: (N-1, N-1)</p>
</li>
<li><p><strong>이동</strong>: 상하좌우 한 칸씩 이동</p>
</li>
<li><p><strong>비용</strong>: </p>
<ul>
<li>직선 이동: 100원</li>
<li>코너 발생 시: 100 + 500 = 600원
<img src="https://velog.velcdn.com/images/dongmin_0204/post/4312de4e-32fc-41e3-8592-99736dcb56d6/image.png" alt=""></li>
</ul>
</li>
<li><p><strong>목표</strong>: 목표칸까지 누적 비용의 최솟값</p>
</li>
</ul>
<h2 id="핵심-인사이트">핵심 인사이트</h2>
<h3 id="문제의-핵심">문제의 핵심</h3>
<p>같은 좌표라도 <strong>&quot;들어온 방향&quot;</strong>에 따라 이후 코너 비용이 달라지므로, <code>dist[x][y]</code>만으로는 최적성 보장이 불가능.</p>
<h3 id="해결-방안">해결 방안</h3>
<p>좌표 상황을 <code>(x, y, dir)</code>로 확장하여 <code>dist[x][y][dir]</code>에 <strong>&quot;dir로 진입했을 때의 최소비용&quot;</strong>을 저장.</p>
<h3 id="알고리즘-선택">알고리즘 선택</h3>
<p>비용이 균일하지 않으므로 BFS는 부적합, <strong>다익스트라 알고리즘</strong>을 사용해야 한다.</p>
<h2 id="알고리즘-개요">알고리즘 개요</h2>
<h3 id="자료구조">자료구조</h3>
<pre><code class="language-cpp">// 3차원 거리 배열: dist[x][y][dir]
vector&lt;vector&lt;vector&lt;int&gt;&gt;&gt; dist(
    size, vector&lt;vector&lt;int&gt;&gt;(size, vector&lt;int&gt;(4, INF))
);

// 우선순위 큐 (최소 힙)
priority_queue&lt;block, vector&lt;block&gt;, Cmp&gt; pq;</code></pre>
<h3 id="초기화">초기화</h3>
<ul>
<li>시작점 (0,0)은 방향이 없음</li>
<li>(0,0) 인접 유효 칸으로 첫 이동을 직선 비용(100) push</li>
</ul>
<h3 id="전개-과정">전개 과정</h3>
<ol>
<li>pq에서 최소 cost를 꺼냄 (꺼낸 cost != dist이면 스킵)</li>
<li>4방향으로 확장<ul>
<li>다음 방향이 현재 dir과 같으면 +100 (직선)</li>
<li>다르면 +600 (코너)</li>
</ul>
</li>
<li>더 작게 갱신되면 dist 갱신 후 pq에 푸시</li>
</ol>
<h3 id="종료-조건">종료 조건</h3>
<p>정답 = <code>min(dist[N-1][N-1][0..3])</code></p>
<h2 id="구현-포인트">구현 포인트</h2>
<h3 id="방향-벡터-설정">방향 벡터 설정</h3>
<pre><code class="language-cpp">int dir[4][2] = {{0,-1}, {-1,0}, {0,1}, {1,0}}; 
// 상좌하우 방향, 세로 이동 0,2 / 가로이동 1,3</code></pre>
<h3 id="우선순위-큐-설정">우선순위 큐 설정</h3>
<pre><code class="language-cpp">struct Cmp {
    bool operator()(const block&amp; a, const block&amp; b) const {
        return a.cost &gt; b.cost; // 비용 작은 게 top
    }
};</code></pre>
<h3 id="중요한-구현-세부사항">중요한 구현 세부사항</h3>
<ul>
<li>첫 이동만 직선 비용 처리 (시작에 방향 X)</li>
</ul>
<h2 id="자주-나오는-버그와-수정">자주 나오는 버그와 수정</h2>
<table>
<thead>
<tr>
<th>버그</th>
<th>수정 방법</th>
</tr>
</thead>
<tbody><tr>
<td><code>dist</code>를 0 크기로 생성한 뒤 인덱싱</td>
<td>반드시 N×N×4로 초기화</td>
</tr>
<tr>
<td><code>priority_queue</code>에서 <code>front()</code> 사용</td>
<td><code>top()</code>이 정석</td>
</tr>
<tr>
<td>방향 배열을 2×4로 선언</td>
<td>인덱싱 오류 방지</td>
</tr>
<tr>
<td>코너 비용 해석 혼동</td>
<td>&quot;이번 이동이 코너인가?&quot; 기준으로 +600 처리</td>
</tr>
</tbody></table>
<h2 id="복잡도-분석">복잡도 분석</h2>
<ul>
<li><strong>블럭 상태의 수</strong>: O(4N^2) = O(N^2)</li>
<li><strong>시간복잡도</strong>: O(N^2 log N) (각 상태에서 최대 4개의 간선, 힙 시간복잡도 포함)</li>
<li><strong>공간복잡도</strong>: O(N^2)</li>
</ul>
<h2 id="코드-구조">코드 구조</h2>
<pre><code class="language-cpp">typedef struct Block {
    int cost; // 지금까지 누적 cost
    int x;    // 현재 x(행)
    int y;    // 현재 y(열)
    int dir;  // 이 칸으로 들어온 방향(0~3)
} block;

#define L_COST 100  // 직선 비용
#define C_COST 600  // 코너 비용 (100 + 500)
#define INF 999999  // 무한대</code></pre>
<h2 id="핵심-로직">핵심 로직</h2>
<pre><code class="language-cpp">// 코너 판별 및 비용 계산
int add = (i == cur.dir) ? L_COST : C_COST;
int nc = cur.cost + add;

// 최적화: 이미 더 좋은 비용으로 갱신되었다면 스킵
if (cur.cost != dist[cur.x][cur.y][cur.dir]) continue;</code></pre>
<p>이 알고리즘은 <strong>블럭 상태의 추가</strong>와 <strong>다익스트라</strong>를 결합하여 경로의 방향성을 고려한 최적 경로를 찾는 문제.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[백준]10828번 스택]]></title>
            <link>https://velog.io/@dongmin_0204/%EB%B0%B1%EC%A4%8010828%EB%B2%88-%EC%8A%A4%ED%83%9D</link>
            <guid>https://velog.io/@dongmin_0204/%EB%B0%B1%EC%A4%8010828%EB%B2%88-%EC%8A%A4%ED%83%9D</guid>
            <pubDate>Sun, 14 Jul 2024 07:58:59 GMT</pubDate>
            <description><![CDATA[<p><a href="https://www.acmicpc.net/problem/10828">문제 주소</a></p>
<p>이번 기회에 백준 문제풀었던 걸 복기하기 위해 작성을 시작했습니다!!
같이 열심히 백준 풀어봅시다!</p>
<p>이 문제에 대해 간단히 알아봅시다:)</p>
<p>다음과 같은 기능을 구현하는 스택입니다.</p>
<p><em>push X</em>: 정수 X를 스택에 넣는 연산
<em>pop</em>: 스택에서 가장 위에 있는 정수를 빼고, 그 수를 출력한다. 
만약 스택에 들어있는 정수가 없는 경우에는 -1을 출력
<em>size</em>: 스택에 들어있는 정수의 개수를 출력
<em>empty</em>: 스택이 비어있으면 1, 아니면 0을 출력한다.
<em>top</em>: 스택의 가장 위에 있는 정수를 출력한다. 만약 스택에 들어있는 정수가 없는 경우에는 -1을 출력한다.</p>
<h1 id="1-스택-구조란">1. 스택 구조란?</h1>
<p>스택 구조는 흔히 말하는 FILO(First In, Last Out), 즉 선입후출 구조입니다.</p>
<p><img src="https://velog.velcdn.com/images/dongmin_0204/post/8efeb47e-4f8f-4149-bc3a-0dad060303c2/image.png" alt=""></p>
<p>위와 같은 스택 구조는 기본적으로 일직선 적인 구조라 선형구조입니다.
그래서 스택을 배열과 연결 리스트 두 가지 방식으로 짤 수 있습니다.</p>
<pre><code>typedef struct StackNode {
    element data;
    struct StackNode* link;
}stack;</code></pre><p>위의 코드는 간단한 스택의 리스트형 구조입니다.
노드끼리 연결해주는 포인터와 데이터입니다. <em><strong>연결리스트의 기본 요소니 기억해주세요</strong></em></p>
<p>마지막 부분은 TOP으로 잡겠습니다. <em><strong>나가는 출구 및 입구!</strong></em></p>
<h1 id="2-스택-각-기능-구현">2. 스택 각 기능 구현</h1>
<p>스택에서 필요한 것은 딱 두가지 선형 구조와 어디가 마지막인지 알려주는 탑 입니다.</p>
<p><em>push</em> : 
새로운 노드를 생성하고 데이터를 저장합니다.
새 노드의 link를 현재 TOP으로 설정!
<em>pop:</em> 
    1. 스택이 비어있는 경우 -1을 반환합니다.
    2. 스택의 모든 노드를 순회.
<em>size</em>: 스택에 들어있는 정수의 개수를 출력
<em>empty</em>: 스택이 비어있으면 1, 아니면 0을 출력
<em>top</em>: 스택이 비어있으면 -1을, top인덱스가 가르키는 노드의 데이터를 반환</p>
<h1 id="3-답안-코드">3. 답안 코드</h1>
<pre><code>#include &lt;stdio.h&gt;
#include &lt;stdlib.h&gt;
#include &lt;string.h&gt;
#include &lt;ctype.h&gt;

typedef int element;

typedef struct StackNode {
    element data;
    struct StackNode* link;
}stack;

void push(element x);
element pop(void); // no top = -1
int size(void);
int empty(void); //empty == 1, not empty == 0
element top(void); // no top = -1

stack* TOP;

int main()
{
    int n;
    char cmd[6];
    int cnt = 0;

    TOP = NULL;

    scanf(&quot;%d&quot;, &amp;n);

    for (int i = 0; i &lt; n; i++) {
        scanf(&quot;%s&quot;, cmd);
        getchar();

        if (!strcmp(cmd, &quot;push&quot;)) {

            element input;
            scanf(&quot;%d&quot;, &amp;input);

            push(input);
            continue;
        }

        else if (!strcmp(cmd, &quot;pop&quot;)) {
            printf(&quot;%d\n&quot;, pop());
            continue;
        }

        else if (!strcmp(cmd, &quot;size&quot;)) {
            printf(&quot;%d\n&quot;, size());
            continue;
        }

        else if (!strcmp(cmd, &quot;empty&quot;)) {
            printf(&quot;%d\n&quot;, empty());
            continue;
        }

        else if (!strcmp(cmd, &quot;top&quot;)) {
            printf(&quot;%d\n&quot;, top());
            continue;
        }
    }

    return 0;
}

void push(element x) {
    stack* tmp = (stack*)malloc(sizeof(stack));

    tmp-&gt;link = TOP;
    tmp-&gt;data = x;
    TOP = tmp;

    return;
}
element pop(void) {
    if (empty()) return -1;
    else {
        stack* old = TOP;

        TOP = old-&gt;link;
        element tmp = old-&gt;data;
        free(old);
        return tmp;
    }
}
int size(void) {
    stack* tmp = TOP;
    int cnt = 0;

    while (tmp) {
        cnt++;
        tmp = tmp-&gt;link;
    }
    return cnt;
}
int empty(void) {
    if (TOP == NULL) {
        return 1;
    }
    else {
        return  0;
    }
}
element top(void) {
    if(empty()) return -1;
    else {
        return (TOP-&gt;data);
    }
}</code></pre>]]></description>
        </item>
    </channel>
</rss>