<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" xml:lang="en"><generator uri="https://jekyllrb.com/" version="4.3.4">Jekyll</generator><link href="https://suinwoo.github.io/feed.xml" rel="self" type="application/atom+xml" /><link href="https://suinwoo.github.io/" rel="alternate" type="text/html" hreflang="en" /><updated>2026-03-30T20:21:45+09:00</updated><id>https://suinwoo.github.io/feed.xml</id><title type="html">SuIn’s Dev Blog</title><subtitle>개발공부를 기록하는 블로그입니다.</subtitle><entry><title type="html">[CS] Modern Java: Lambda, Stream &amp;amp; Optional</title><link href="https://suinwoo.github.io/posts/cs-modern-java-lambda-stream-optional/" rel="alternate" type="text/html" title="[CS] Modern Java: Lambda, Stream &amp;amp; Optional" /><published>2026-03-30T00:00:00+09:00</published><updated>2026-03-30T20:21:29+09:00</updated><id>https://suinwoo.github.io/posts/cs-modern-java-lambda-stream-optional</id><content type="html" xml:base="https://suinwoo.github.io/posts/cs-modern-java-lambda-stream-optional/"><![CDATA[<h1 id="cs-modern-java-lambda-stream--optional">[CS] Modern Java: Lambda, Stream &amp; Optional</h1>

<p>과거의 자바가 “어떻게(How) 루프를 돌릴까”에 집중했다면, 현대 자바는 <strong>“무엇(What)을 하고 싶은가”</strong>에 집중합니다.</p>

<hr />

<h2 id="정리">정리</h2>

<h2 id="1-패러다임의-전환-명령형how에서-선언형what으로">1. 패러다임의 전환: 명령형(How)에서 선언형(What)으로</h2>

<p>과거의 자바가 “어떻게(How) 루프를 돌릴까”에 집중했다면, 현대 자바는 <strong>“무엇(What)을 하고 싶은가”</strong>에 집중합니다.</p>

<ul>
  <li>
    <p>명령형: for문을 돌면서 if로 거르고, 새 리스트에 add한다. (코드가 길고 의도가 한눈에 안 들어옴)</p>
  </li>
  <li>
    <p>선언형(Stream): “필터링하고, 변환해서, 수집해라.” (비즈니스 로직이 한눈에 보임)</p>
  </li>
</ul>

<h2 id="2-람다-식-lambda-expression-함수를-변수처럼">2. 람다 식 (Lambda Expression): “함수를 변수처럼”</h2>

<p>람다는 익명 함수를 만드는 방식이지만, 본질은 <strong>“행위(Behavior)를 파라미터로 전달하는 것”</strong>입니다.</p>

<h3 id="21-함수형-인터페이스-functional-interface">2.1 함수형 인터페이스 (Functional Interface)</h3>

<ul>
  <li>
    <p>람다를 쓰려면 단 하나의 추상 메서드만 가진 인터페이스가 필요합니다.</p>
  </li>
  <li>
    <p>@FunctionalInterface 어노테이션을 붙여 컴파일 타임에 체크하는 것이 관례입니다.</p>
  </li>
  <li>
    <p>자주 쓰는 인터페이스: Predicate<T> (조건 체크), Consumer<T> (소비), Function&lt;T, R&gt; (변환).</T></T></p>
  </li>
</ul>

<h2 id="3-스트림-api-stream-api-데이터의-흐름">3. 스트림 API (Stream API): “데이터의 흐름”</h2>

<p>스트림은 데이터 소스(컬렉션, 배열 등)를 추상화하여 연속적으로 처리하는 도구입니다. 원본 데이터를 변경하지 않는다는 점이 가장 중요합니다.</p>

<h3 id="31-스트림의-3단계-파이프라인">3.1 스트림의 3단계 파이프라인</h3>

<ol>
  <li>
    <p>생성 (Source): list.stream()</p>
  </li>
  <li>
    <p>중간 연산 (Intermediate): filter, map, sorted, distinct (결과가 다시 스트림이므로 연결 가능)</p>
  </li>
  <li>
    <p>최종 연산 (Terminal): collect, forEach, count, anyMatch (스트림을 닫고 결과를 반환)</p>
  </li>
</ol>

<h3 id="32--실무-체감-예시-물류-시스템-시나리오">3.2 💡 실무 체감 예시 (물류 시스템 시나리오)</h3>

<blockquote>
  <p>상황: 배차 대기 중인 주문 리스트에서 “서울” 지역 주문만 골라 주문 ID 리스트로 변환해야 함.</p>
</blockquote>

<ul>
  <li>Old 방식 (for-each):</li>
</ul>

<p>Java</p>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
</pre></td><td class="rouge-code"><pre><span class="nc">List</span><span class="o">&lt;</span><span class="nc">Long</span><span class="o">&gt;</span> <span class="n">seoulOrderIds</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">ArrayList</span><span class="o">&lt;&gt;();</span>
<span class="k">for</span> <span class="o">(</span><span class="nc">Order</span> <span class="n">order</span> <span class="o">:</span> <span class="n">orders</span><span class="o">)</span> <span class="o">{</span>
    <span class="k">if</span> <span class="o">(</span><span class="s">"SEOUL"</span><span class="o">.</span><span class="na">equals</span><span class="o">(</span><span class="n">order</span><span class="o">.</span><span class="na">getRegion</span><span class="o">())</span> <span class="o">&amp;&amp;</span> <span class="s">"PENDING"</span><span class="o">.</span><span class="na">equals</span><span class="o">(</span><span class="n">order</span><span class="o">.</span><span class="na">getStatus</span><span class="o">()))</span> <span class="o">{</span>
        <span class="n">seoulOrderIds</span><span class="o">.</span><span class="na">add</span><span class="o">(</span><span class="n">order</span><span class="o">.</span><span class="na">getId</span><span class="o">());</span>
    <span class="o">}</span>
<span class="o">}</span>          <span class="c1">// 결과 수집</span>
</pre></td></tr></tbody></table></code></pre></div></div>

<ul>
  <li>Modern 방식 (Stream):</li>
</ul>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
</pre></td><td class="rouge-code"><pre><span class="nc">List</span><span class="o">&lt;</span><span class="nc">Long</span><span class="o">&gt;</span> <span class="n">seoulOrderIds</span> <span class="o">=</span> <span class="n">orders</span><span class="o">.</span><span class="na">stream</span><span class="o">()</span>
    <span class="o">.</span><span class="na">filter</span><span class="o">(</span><span class="n">o</span> <span class="o">-&gt;</span> <span class="s">"SEOUL"</span><span class="o">.</span><span class="na">equals</span><span class="o">(</span><span class="n">o</span><span class="o">.</span><span class="na">getRegion</span><span class="o">()))</span> <span class="c1">// 조건 필터링</span>
    <span class="o">.</span><span class="na">filter</span><span class="o">(</span><span class="n">o</span> <span class="o">-&gt;</span> <span class="s">"PENDING"</span><span class="o">.</span><span class="na">equals</span><span class="o">(</span><span class="n">o</span><span class="o">.</span><span class="na">getStatus</span><span class="o">()))</span> <span class="c1">// 연속 필터링 가능</span>
    <span class="o">.</span><span class="na">map</span><span class="o">(</span><span class="nl">Order:</span><span class="o">:</span><span class="n">getId</span><span class="o">)</span>                           <span class="c1">// ID만 추출 (Method Reference)</span>
    <span class="o">.</span><span class="na">collect</span><span class="o">(</span><span class="nc">Collectors</span><span class="o">.</span><span class="na">toList</span><span class="o">());</span>               <span class="c1">// 결과 수집</span>
</pre></td></tr></tbody></table></code></pre></div></div>

<h2 id="4-optional-null과의-전쟁-종식">4. Optional: “Null과의 전쟁 종식”</h2>

<p>NullPointerException(NPE)은 개발자의 영원한 숙제입니다. Optional은 값이 “있을 수도 있고 없을 수도 있음”을 명시적으로 표현하는 컨테이너입니다.</p>

<h3 id="41-잘못된-사용-vs-올바른-사용">4.1 잘못된 사용 vs 올바른 사용</h3>

<ul>
  <li>
    <p>Bad: if (opt.isPresent()) { return opt.get(); } (기존 null 체크와 다를 게 없음)</p>
  </li>
  <li>
    <p>Good: 함수형 메서드 활용</p>
  </li>
  <li>
    <p>opt.orElse(“Default”): 없으면 기본값 반환.</p>
  </li>
  <li>
    <p>opt.orElseThrow(): 없으면 예외 던지기.</p>
  </li>
  <li>
    <p>opt.ifPresent(v -&gt; …): 있을 때만 로직 실행.</p>
  </li>
</ul>

<h2 id="5-공부하며-느낀-deep-insight-human-like-notes">5. 공부하며 느낀 Deep Insight (Human-like Notes)</h2>

<h3 id="-지연-연산-lazy-evaluation의-마법">🚀 지연 연산 (Lazy Evaluation)의 마법</h3>

<p>스트림의 중간 연산은 최종 연산이 호출되기 전까지 절대 실행되지 않습니다. * “100만 개 데이터 중 filter 후 findFirst”를 하면, 100만 개를 다 거르는 게 아니라 조건을 만족하는 첫 번째 데이터를 찾는 순간 멈춥니다. 성능 최적화가 자동으로 일어나는 지점입니다.</p>

<h3 id="️-주의할-점-pitfalls">⚠️ 주의할 점 (Pitfalls)</h3>

<ol>
  <li>
    <p>가독성 vs 복잡성: 스트림이 무조건 좋은 건 아닙니다. 로직이 너무 복잡해지면 오히려 for문보다 읽기 힘들어집니다. “한 줄이 너무 길어지면 끊어가자.”</p>
  </li>
  <li>
    <p>병렬 스트림(Parallel Stream): parallelStream()은 멀티코어를 쓰지만, 데이터가 적거나 순서가 중요하면 오히려 더 느리고 위험합니다. 신중히 써야 합니다.</p>
  </li>
  <li>
    <p>Optional은 반환 타입으로만: 필드 값이나 파라미터로 Optional을 쓰는 건 설계 의도에 어긋나며 성능 저하를 유발합니다.</p>
  </li>
</ol>

<h2 id="6-self-quiz-정리용-질문">6. Self-Quiz (정리용 질문)</h2>

<p>스트림은 원본 리스트의 요소를 정렬하면 원본도 정렬될까? (정답: 아니오)</p>

<p>map과 flatMap의 차이는 무엇인가? (중첩 구조를 펴주는 과정의 이해)</p>

<p>왜 Optional.get()을 바로 호출하는 것을 지양해야 할까?</p>]]></content><author><name></name></author><category term="CS" /><category term="CS공부" /><summary type="html"><![CDATA[[CS] Modern Java: Lambda, Stream &amp; Optional]]></summary></entry><entry><title type="html">[CS] Java Deep Dive: JVM &amp;amp; Memory Management</title><link href="https://suinwoo.github.io/posts/cs-java-deep-dive-jvm-memory-management/" rel="alternate" type="text/html" title="[CS] Java Deep Dive: JVM &amp;amp; Memory Management" /><published>2026-03-26T00:00:00+09:00</published><updated>2026-03-26T20:56:17+09:00</updated><id>https://suinwoo.github.io/posts/cs-java-deep-dive-jvm-memory-management</id><content type="html" xml:base="https://suinwoo.github.io/posts/cs-java-deep-dive-jvm-memory-management/"><![CDATA[<h1 id="cs-java-deep-dive-jvm--memory-management">[CS] Java Deep Dive: JVM &amp; Memory Management</h1>

<ul>
  <li>OS 독립성: 바이트코드(.class)는 JVM 위에서 실행되므로, 윈도우에서 짠 코드가 리눅스 서버에서도 동일하게 작동함.</li>
</ul>

<hr />

<h2 id="정리">정리</h2>

<h2 id="1-jvm의-핵심-역할-가상화와-자동화">1. JVM의 핵심 역할: 가상화와 자동화</h2>

<ul>
  <li>
    <p>OS 독립성: 바이트코드(.class)는 JVM 위에서 실행되므로, 윈도우에서 짠 코드가 리눅스 서버에서도 동일하게 작동함.</p>
  </li>
  <li>
    <p>자동 메모리 관리: C/C++처럼 개발자가 free()를 호출할 필요 없이, JVM이 알아서 가비지(Garbage)를 치움.</p>
  </li>
</ul>

<h2 id="2-jvm-내부-구조-상세-internal-architecture">2. JVM 내부 구조 상세 (Internal Architecture)</h2>

<h3 id="21-class-loader-클래스를-언제-어떻게-가져오는가">2.1 Class Loader: “클래스를 언제, 어떻게 가져오는가?”</h3>

<ul>
  <li>
    <p>Lazy Loading (지연 로딩): 모든 클래스를 한꺼번에 올리지 않고, 실제 코드에서 참조될 때 로드함.</p>
  </li>
  <li>
    <p>단계: Loading(읽기) → Linking(검증/준비) → Initialization(static 값 초기화).</p>
  </li>
</ul>

<h3 id="22-execution-engine-바이트코드를-기계어로-바꾸는-두-가지-방식">2.2 Execution Engine: “바이트코드를 기계어로 바꾸는 두 가지 방식”</h3>

<ul>
  <li>
    <p>Interpreter: 바이트코드를 한 줄씩 읽어서 실행. 빠르지만 반복 작업엔 비효율적.</p>
  </li>
  <li>
    <p>JIT (Just-In-Time) Compiler: 자주 실행되는 ‘Hot Method’를 찾아내서 통째로 기계어로 컴파일한 뒤 캐싱함.</p>
  </li>
  <li>
    <p>예시: 반복문이 1만 번 돌아가면 인터프리터 대신 JIT가 개입해 실행 속도를 폭발적으로 높임.</p>
  </li>
</ul>

<h2 id="3-runtime-data-area-데이터가-어디에-저장되는가">3. Runtime Data Area: “데이터가 어디에 저장되는가?”</h2>

<h3 id="31-stack-vs-heap-가장-중요한-구분">3.1 Stack vs Heap (가장 중요한 구분)</h3>

<p>실제 코드를 예시로 보면 이해가 빠릅니다.</p>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
</pre></td><td class="rouge-code"><pre><span class="kd">public</span> <span class="kt">void</span> <span class="nf">study</span><span class="o">()</span> <span class="o">{</span>
    <span class="kt">int</span> <span class="n">age</span> <span class="o">=</span> <span class="mi">25</span><span class="o">;</span>              <span class="c1">// Stack에 저장 (기본 타입 변수)</span>
    <span class="nc">String</span> <span class="n">name</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">String</span><span class="o">(</span><span class="s">"Gemini"</span><span class="o">);</span> <span class="c1">// 'name' 레퍼런스는 Stack, "Gemini" 객체는 Heap에 저장</span>
<span class="o">}</span>
</pre></td></tr></tbody></table></code></pre></div></div>

<h3 id="32-method-area-static-area">3.2 Method Area (Static Area)</h3>

<ul>
  <li>
    <p>클래스 이름, 부모 클래스 이름, 메서드 데이터, static 변수가 저장됨.</p>
  </li>
  <li>
    <p>프로그램 시작부터 종료까지 메모리에 남아있으므로 과도한 static 사용은 메모리 낭비를 초래함.</p>
  </li>
</ul>

<h2 id="4-가비지-컬렉션-gc-상세-메커니즘">4. 가비지 컬렉션 (GC) 상세 메커니즘</h2>

<h3 id="41-reachability-누구를-지울-것인가">4.1 Reachability: “누구를 지울 것인가?”</h3>

<ul>
  <li>
    <p>Root Set: Stack의 변수, Static 변수 등에서 참조가 시작됨.</p>
  </li>
  <li>
    <p>Reachable: Root Set으로부터 연결 고리가 있는 객체. (생존)</p>
  </li>
  <li>
    <p>Unreachable: 연결 고리가 끊긴 객체. (GC 대상)</p>
  </li>
</ul>

<h3 id="42-heap의-세대별-관리-generational-strategy">4.2 Heap의 세대별 관리 (Generational Strategy)</h3>

<p>객체의 90%는 금방 쓰레기가 된다는 통계적 사실에 기반합니다.</p>

<ol>
  <li>Young Generation (Eden, S0, S1):</li>
</ol>

<ul>
  <li>
    <p>새로 만든 객체가 들어감. 꽉 차면 Minor GC 발생.</p>
  </li>
  <li>
    <p>살아남은 객체는 Survivor 영역으로 이동하며 ‘Age’가 올라감.</p>
  </li>
</ul>

<ol>
  <li>Old Generation:</li>
</ol>

<ul>
  <li>
    <p>일정 Age(보통 15회) 이상 살아남은 ‘장수 객체’가 이동함.</p>
  </li>
  <li>
    <p>여기가 꽉 차면 Major GC (Full GC) 발생. (이때 Stop-the-world 발생)</p>
  </li>
</ul>

<h2 id="5-실무-적용-및-트러블슈팅-use-case">5. 실무 적용 및 트러블슈팅 (Use Case)</h2>

<h3 id="51-stop-the-world-stw">5.1 Stop-the-world (STW)</h3>

<ul>
  <li>
    <p>현상: GC를 실행하기 위해 JVM이 애플리케이션의 모든 스레드를 일시 정지시키는 것.</p>
  </li>
  <li>
    <p>해결: 최신 GC(G1, ZGC)는 이 멈춤 시간을 밀리초(ms) 단위로 줄이는 방향으로 발전 중.</p>
  </li>
</ul>

<h3 id="52-메모리-누수memory-leak-사례">5.2 메모리 누수(Memory Leak) 사례</h3>

<ul>
  <li>
    <p>정적 컬렉션 사용: static List에 객체를 계속 넣고 비우지 않으면, GC가 “아직 참조 중이네?”라고 판단해 절대 치우지 않음. 결국 OutOfMemoryError(OOM) 발생.</p>
  </li>
  <li>
    <p>리소스 미폐쇄: DB 연결(Connection)이나 파일 스트림을 열고 close() 하지 않으면 메모리가 점유된 상태로 남음.</p>
  </li>
</ul>]]></content><author><name></name></author><category term="CS" /><category term="CS공부" /><summary type="html"><![CDATA[[CS] Java Deep Dive: JVM &amp; Memory Management]]></summary></entry><entry><title type="html">[CS] Object-Oriented Programming &amp;amp; SOLID Principles</title><link href="https://suinwoo.github.io/posts/cs-object-oriented-programming-solid-principles/" rel="alternate" type="text/html" title="[CS] Object-Oriented Programming &amp;amp; SOLID Principles" /><published>2026-03-26T00:00:00+09:00</published><updated>2026-03-26T20:57:08+09:00</updated><id>https://suinwoo.github.io/posts/cs-object-oriented-programming-solid-principles</id><content type="html" xml:base="https://suinwoo.github.io/posts/cs-object-oriented-programming-solid-principles/"><![CDATA[<h1 id="cs-object-oriented-programming--solid-principles">[CS] Object-Oriented Programming &amp; SOLID Principles</h1>

<p>객체 지향은 단순히 변수와 함수를 묶는 것이 아니라, 객체 간의 메시지 송수신을 통해 시스템을 구축하는 것입니다.</p>

<hr />

<h2 id="정리">정리</h2>

<h2 id="1-객체-지향의-본질-책임과-협력">1. 객체 지향의 본질: “책임과 협력”</h2>

<p>객체 지향은 단순히 변수와 함수를 묶는 것이 아니라, 객체 간의 메시지 송수신을 통해 시스템을 구축하는 것입니다.</p>

<ul>
  <li>
    <p>자율적 객체: 객체는 스스로의 상태를 관리하고, 외부의 요청에 어떻게 응답할지 스스로 결정해야 함.</p>
  </li>
  <li>
    <p>추상화(Abstraction)의 두 얼굴: 1. 구체적인 사물들의 공통점을 뽑아내는 것 (예: 사과, 바나나 → 과일).</p>
    <ol>
      <li>불필요한 세부 사항을 제거하고 핵심만 남기는 것.</li>
    </ol>
  </li>
</ul>

<h2 id="2-solid-원칙의-실무적-해석-deep-interpretation">2. SOLID 원칙의 실무적 해석 (Deep Interpretation)</h2>

<h3 id="-srp-단일-책임-원칙-single-responsibility">① SRP: 단일 책임 원칙 (Single Responsibility)</h3>

<ul>
  <li>
    <p>판단 기준: “어떤 코드를 변경해야 할 때, 그 영향이 어디까지 미치는가?”</p>
  </li>
  <li>
    <p>실무 Tip: 클래스 이름이 Manager, Util, Service처럼 너무 포괄적이면 SRP를 위반할 확률이 높습니다. UserValidator, UserPasswordHasher처럼 구체적으로 쪼개세요.</p>
  </li>
</ul>

<h3 id="-ocp-개방-폐쇄-원칙-open-closed">② OCP: 개방-폐쇄 원칙 (Open-Closed)</h3>

<ul>
  <li>
    <p>핵심 기술: 인터페이스(Interface)와 상속.</p>
  </li>
  <li>
    <p>적용 사례: JDBC 드라이버. 자바 애플리케이션은 동일한 DB 접근 코드를 사용하지만, 드라이버만 교체하면 MySQL, Oracle, PostgreSQL 등 어떤 DB와도 연결됩니다. 애플리케이션 코드는 수정(Closed)하지 않고, DB 지원은 확장(Open)된 사례입니다.</p>
  </li>
</ul>

<h3 id="-lsp-리스코프-치환-원칙-liskov-substitution">③ LSP: 리스코프 치환 원칙 (Liskov Substitution)</h3>

<ul>
  <li>
    <p>주의사항: 단순히 문법적 컴파일 에러가 안 나는 것이 아니라, <strong>부모 클래스의 규약(계약)</strong>을 지켜야 합니다.</p>
  </li>
  <li>
    <p>Bad Example: Bird 클래스에 fly() 메서드가 있는데, 이를 상속받은 Penguin 클래스에서 “저는 못 날아요”라며 예외(Exception)를 던지면 LSP 위반입니다. (날 수 있는 새만 fly()를 가져야 함)</p>
  </li>
</ul>

<h3 id="-isp-인터페이스-분리-원칙-interface-segregation">④ ISP: 인터페이스 분리 원칙 (Interface Segregation)</h3>

<ul>
  <li>
    <p>핵심: “덩치 큰 인터페이스 하나보다, 구체적인 인터페이스 여러 개가 낫다.”</p>
  </li>
  <li>
    <p>실무 Tip: 스프링 프레임워크의 인터페이스들을 보면 Serializable, Cloneable처럼 아주 작게 쪼개져 있는 것을 볼 수 있습니다. 필요한 기능만 골라 조합(Multi Implements)하기 위함입니다.</p>
  </li>
</ul>

<h3 id="-dip-의존-역전-원칙-dependency-inversion">⑤ DIP: 의존 역전 원칙 (Dependency Inversion)</h3>

<ul>
  <li>
    <p>핵심: “변하기 쉬운 것(구현체)에 의존하지 말고, 변하지 않는 것(인터페이스)에 의존하라.”</p>
  </li>
  <li>
    <p>비유: 콘센트(인터페이스)에 의존해야지, 특정 브랜드의 가전제품 선에 직접 연결하면 안 되는 것과 같습니다.</p>
  </li>
</ul>

<h2 id="3-심화-주제-상속보다는-합성을-사용하라-composition-over-inheritance">3. 심화 주제: “상속보다는 합성을 사용하라” (Composition over Inheritance)</h2>

<p>많은 초보 개발자가 재사용을 위해 ‘상속(Extends)’을 쓰지만, 실무에서는 ‘합성(Composition)’을 권장합니다.</p>

<ul>
  <li>
    <p>상속(Inheritance)의 문제점: 부모와 자식 관계가 너무 강하게 결합됨(Strong Coupling). 부모 클래스의 작은 변경이 자식 모두에게 치명적인 버그를 일으킬 수 있음.</p>
  </li>
  <li>
    <p>합성(Composition)의 장점: 클래스 내부에 다른 객체를 필드로 가지고 있는 방식. 실행 시점에 의존성을 교체할 수 있어 훨씬 유연함.</p>
  </li>
</ul>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
</pre></td><td class="rouge-code"><pre><span class="c1">// [합성 예시]</span>
<span class="kd">public</span> <span class="kd">class</span> <span class="nc">Robot</span> <span class="o">{</span>
    <span class="kd">private</span> <span class="nc">MoveStrategy</span> <span class="n">moveStrategy</span><span class="o">;</span> <span class="c1">// 인터페이스를 필드로 가짐 (합성)</span>

    <span class="kd">public</span> <span class="kt">void</span> <span class="nf">setMoveStrategy</span><span class="o">(</span><span class="nc">MoveStrategy</span> <span class="n">strategy</span><span class="o">)</span> <span class="o">{</span>
        <span class="k">this</span><span class="o">.</span><span class="na">moveStrategy</span> <span class="o">=</span> <span class="n">strategy</span><span class="o">;</span> <span class="c1">// 달리기 모드, 비행 모드 등을 자유롭게 교체</span>
    <span class="o">}</span>
<span class="o">}</span>
</pre></td></tr></tbody></table></code></pre></div></div>

<h2 id="4-실전-디자인-패턴과의-연결-bridge-to-design-patterns">4. 실전 디자인 패턴과의 연결 (Bridge to Design Patterns)</h2>

<p>SOLID 원칙을 잘 지키면 자연스럽게 디자인 패턴으로 이어집니다.</p>

<ul>
  <li>
    <p>OCP/DIP를 지키면? → 전략 패턴(Strategy Pattern), 템플릿 메서드 패턴.</p>
  </li>
  <li>
    <p>SRP를 지키면? → 프록시 패턴(Proxy Pattern), 데코레이터 패턴.</p>
  </li>
</ul>

<h2 id="5-학습-포인트-왜why를-생각하기">5. 학습 포인트: “왜(Why)를 생각하기”</h2>

<ol>
  <li>
    <p>인터페이스 없이 개발하면 어떤 일이 벌어지는가? (코드 수정의 지옥)</p>
  </li>
  <li>
    <p>왜 스프링은 모든 서비스 객체를 주입(DI)받아 사용하는가? (DIP를 지키기 위해)</p>
  </li>
  <li>
    <p>내가 진행했던 프로젝트에서 SRP를 가장 잘 지킨 클래스는 무엇인가?</p>
  </li>
</ol>]]></content><author><name></name></author><category term="CS" /><category term="CS공부" /><summary type="html"><![CDATA[[CS] Object-Oriented Programming &amp; SOLID Principles]]></summary></entry><entry><title type="html">AI 활용 결제·OMS Nest 프로젝트 회고 #2</title><link href="https://suinwoo.github.io/posts/ai-%ED%99%9C%EC%9A%A9-%EA%B2%B0%EC%A0%9C-oms-nest-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-%ED%9A%8C%EA%B3%A0-2/" rel="alternate" type="text/html" title="AI 활용 결제·OMS Nest 프로젝트 회고 #2" /><published>2026-03-26T00:00:00+09:00</published><updated>2026-03-26T00:00:00+09:00</updated><id>https://suinwoo.github.io/posts/ai-%ED%99%9C%EC%9A%A9-%EA%B2%B0%EC%A0%9C-oms-nest-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-%ED%9A%8C%EA%B3%A0-2</id><content type="html" xml:base="https://suinwoo.github.io/posts/ai-%ED%99%9C%EC%9A%A9-%EA%B2%B0%EC%A0%9C-oms-nest-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-%ED%9A%8C%EA%B3%A0-2/"><![CDATA[<h1 id="ai-활용-결제oms-nest-프로젝트-회고-2">AI 활용 결제·OMS Nest 프로젝트 회고 #2</h1>

<h2 id="1-결제-모듈-개요">1. 결제 모듈 개요</h2>

<h3 id="역할">역할</h3>

<ul>
  <li>주문 단위 결제 생성/준비/승인과 결제 상태 조회를 담당</li>
</ul>

<h3 id="주요-책임">주요 책임</h3>

<ul>
  <li>
    <p>멱등 키 기반 결제 생성 (POST /payments)</p>
  </li>
  <li>
    <p>토스 결제 준비/승인 (POST /payments/prepare, POST /payments/confirm)</p>
  </li>
  <li>
    <p>주문 기준 결제 상태 조회 및 관리자 목록 조회</p>
  </li>
  <li>
    <p>결제 생성 이벤트 발행(payment.created)으로 주문 모듈과 연동</p>
  </li>
</ul>

<h3 id="주요-연관-도메인">주요 연관 도메인</h3>

<ul>
  <li>주문(OMS), 유저, 정산(향후)</li>
</ul>

<h2 id="2-아키텍처--설계">2. 아키텍처 · 설계</h2>

<h3 id="모듈-구조">모듈 구조</h3>

<ul>
  <li>PaymentModule, PaymentController, PaymentService, PaymentRepository</li>
</ul>

<h3 id="핵심-엔티티테이블">핵심 엔티티/테이블</h3>

<ul>
  <li>
    <p>payments</p>
  </li>
  <li>
    <p>주요 컬럼: id, order_id, amount, idempotency_key, payment_key, status, provider_status, approved_at</p>
  </li>
  <li>
    <p>인덱스: uq_payments_idempotency_key(유니크), ix_payments_order_id</p>
  </li>
</ul>

<h3 id="상태-플로우">상태 플로우</h3>

<ul>
  <li>
    <table>
      <tbody>
        <tr>
          <td>내부 상태: PENDING</td>
          <td>SUCCEEDED</td>
          <td>FAILED</td>
        </tr>
      </tbody>
    </table>
  </li>
  <li>
    <p>POST /payments는 학습용 단순 플로우로 즉시 SUCCEEDED 저장</p>
  </li>
  <li>POST /payments/confirm은 토스 응답 DONE이면 SUCCEEDED, 그 외 PENDING</li>
</ul>

<h3 id="중요-설계-결정">중요 설계 결정</h3>

<ul>
  <li>
    <p>멱등성은 idempotency_key 유니크 + 트랜잭션 + unique_violation(23505) 핸들링으로 보장</p>
  </li>
  <li>
    <p>토스 승인 시 TOSS_SECRET_KEY 및 TossPayments-Api-Version 헤더를 사용</p>
  </li>
  <li>
    <p>외부 PG 오류는 PAYMENT_PROVIDER_FAILED로 표준화</p>
  </li>
</ul>

<h2 id="3-api-설계">3. API 설계</h2>

<h3 id="주요-api-목록">주요 API 목록</h3>

<ul>
  <li>
    <p>GET /payments?order_id= : 결제 목록 조회(관리자)</p>
  </li>
  <li>
    <p>POST /payments : 결제 생성(멱등 키 필수)</p>
  </li>
  <li>
    <p>POST /payments/prepare : 토스 결제 준비</p>
  </li>
  <li>
    <p>POST /payments/confirm : 토스 결제 승인</p>
  </li>
  <li>
    <p>GET /payments/:orderId : 주문별 결제 상태 조회</p>
  </li>
  <li>
    <p>POST /payments/webhook/toss : 토스 웹훅 수신(초안)</p>
  </li>
</ul>

<h3 id="요청응답에서-중요하게-본-포인트">요청/응답에서 중요하게 본 포인트</h3>

<ul>
  <li>
    <p>멱등 충돌: PAYMENT_IDEMPOTENCY_CONFLICT</p>
  </li>
  <li>
    <p>금액 검증: 주문 금액과 다르면 PAYMENT_AMOUNT_MISMATCH</p>
  </li>
  <li>
    <p>중복 승인 방지: PAYMENT_ALREADY_CONFIRMED</p>
  </li>
  <li>
    <p>PG 실패: PAYMENT_PROVIDER_FAILED</p>
  </li>
</ul>

<h2 id="4-테스트-전략-결제-모듈">4. 테스트 전략 (결제 모듈)</h2>

<h3 id="단위-테스트">단위 테스트</h3>

<ul>
  <li>
    <p>서비스 레이어 비즈니스 로직</p>
  </li>
  <li>
    <p>상태 전이 로직</p>
  </li>
</ul>

<h3 id="통합-테스트">통합 테스트</h3>

<ul>
  <li>
    <p>test/e2e/payment.idempotency.int.spec.ts: 같은 멱등 키로 두 번 요청 시 동일 결제 반환 검증</p>
  </li>
  <li>
    <p>test/unit/modules/payment/payment.service.spec.ts: 서비스 단위 로직 검증</p>
  </li>
</ul>

<h3 id="부하안정성-테스트">부하·안정성 테스트</h3>

<ul>
  <li>k6/pay-system.smoke.js에서 POST /payments 포함 E2E 스모크 수행</li>
</ul>

<h2 id="5-리스크--todo">5. 리스크 · TODO</h2>

<h3 id="리스크">리스크</h3>

<ul>
  <li>
    <p>현재 confirmWithToss는 서킷브레이커/재시도 정책이 없어 PG 장애 시 취약</p>
  </li>
  <li>
    <p>웹훅은 서명 검증/이벤트 분기가 미구현 상태라 운영 적용 전 보강 필요</p>
  </li>
</ul>

<h3 id="앞으로-개선하고-싶은-점">앞으로 개선하고 싶은 점</h3>

<ul>
  <li>
    <p>부분 취소/부분 환불 API</p>
  </li>
  <li>
    <p>결제 상태 변경 이력 테이블</p>
  </li>
  <li>
    <p>웹훅 기반 비동기 상태 동기화 및 정산 도메인 연결</p>
  </li>
</ul>

<h2 id="6-개인-회고--느낀-점">6. 개인 회고 · 느낀 점</h2>

<h3 id="이번-챕터에서-느낀-점">이번 챕터에서 느낀 점</h3>

<ul>
  <li>결제 모듈 하나만 보더라도 주문/유저/정산과 강하게 연결되어 있어, “어디까지를 결제 책임으로 둘지”를 명확히 하지 않으면 금방 복잡해진다는 걸 느꼈다.</li>
</ul>

<h3 id="배운-것">배운 것</h3>

<ul>
  <li>상태 전이와 실패 시나리오를 먼저 문서로 써두고 코드로 옮기면, 테스트 코드와 k6 시나리오 설계가 자연스럽게 따라온다는 점.</li>
</ul>

<h3 id="다음-챕터주문-모듈에서-더-신경-쓸-것">다음 챕터(주문 모듈)에서 더 신경 쓸 것</h3>

<ul>
  <li>주문과 결제의 경계를 더 명확히 잡고, “결제 실패/취소가 주문에 어떻게 반영되는지”를 문서·테스트·로그 기준으로 정리해 두기.</li>
</ul>]]></content><author><name></name></author><category term="Project" /><category term="토이프로젝트" /><summary type="html"><![CDATA[AI 활용 결제·OMS Nest 프로젝트 회고 #2]]></summary></entry><entry><title type="html">AI 활용 결제·OMS Nest 프로젝트 회고 #3</title><link href="https://suinwoo.github.io/posts/ai-%ED%99%9C%EC%9A%A9-%EA%B2%B0%EC%A0%9C-oms-nest-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-%ED%9A%8C%EA%B3%A0-3/" rel="alternate" type="text/html" title="AI 활용 결제·OMS Nest 프로젝트 회고 #3" /><published>2026-03-26T00:00:00+09:00</published><updated>2026-03-26T00:00:00+09:00</updated><id>https://suinwoo.github.io/posts/ai-%ED%99%9C%EC%9A%A9-%EA%B2%B0%EC%A0%9C-oms-nest-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-%ED%9A%8C%EA%B3%A0-3</id><content type="html" xml:base="https://suinwoo.github.io/posts/ai-%ED%99%9C%EC%9A%A9-%EA%B2%B0%EC%A0%9C-oms-nest-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-%ED%9A%8C%EA%B3%A0-3/"><![CDATA[<h1 id="ai-활용-결제oms-nest-프로젝트-회고-3">AI 활용 결제·OMS Nest 프로젝트 회고 #3</h1>

<h2 id="1-주문oms-모듈-개요">1. 주문(OMS) 모듈 개요</h2>

<h3 id="역할">역할</h3>

<ul>
  <li>주문 생성/조회/수정과 주문 상세 라인 관리를 담당</li>
</ul>

<h3 id="주요-책임">주요 책임</h3>

<ul>
  <li>
    <p>고객사 기준 주문 분할 생성 (order_group_id, orders[])</p>
  </li>
  <li>
    <p>권한/스코프 기반 주문 목록 조회 (my, company, all)</p>
  </li>
  <li>
    <p>주문 상세 라인 추가 및 합계 재계산</p>
  </li>
  <li>
    <table>
      <tbody>
        <tr>
          <td>결제 상태를 기반으로 주문 목록 status(PAID</td>
          <td>PENDING) 계산</td>
        </tr>
      </tbody>
    </table>
  </li>
</ul>

<h3 id="주요-연관-도메인">주요 연관 도메인</h3>

<ul>
  <li>결제, 유저, 마스터(고객사/상품)</li>
</ul>

<h2 id="2-아키텍처--설계">2. 아키텍처 · 설계</h2>

<h3 id="모듈-구조">모듈 구조</h3>

<ul>
  <li>OmsModule, OmsController, OmsService, OrderRepository, OrderDetailRepository</li>
</ul>

<h3 id="핵심-엔티티테이블">핵심 엔티티/테이블</h3>

<ul>
  <li>orders, order_detail</li>
</ul>

<h3 id="주문-상태-플로우">주문 상태 플로우</h3>

<ul>
  <li>
    <table>
      <tbody>
        <tr>
          <td>orders.delivery_status: WAITING</td>
          <td>SHIPPING</td>
          <td>DELIVERED</td>
        </tr>
      </tbody>
    </table>
  </li>
  <li>
    <table>
      <tbody>
        <tr>
          <td>목록 응답 status: payments 테이블을 조회해 PAID</td>
          <td>PENDING으로 계산</td>
        </tr>
      </tbody>
    </table>
  </li>
</ul>

<h3 id="중요-설계-결정">중요 설계 결정</h3>

<ul>
  <li>
    <p>주문 생성 시 items[]를 client_company_id로 그룹핑해 회사별 주문 분할 생성</p>
  </li>
  <li>
    <p>결제 생성 이벤트(payment.created)를 수신해 없는 주문이면 최소 주문 레코드를 자동 생성</p>
  </li>
  <li>
    <p>개인정보(PII) 암호화: 주문자명, 주소, 연락처, 이메일은 AES-256-GCM으로 DB 저장 시 암호화. PII_ENCRYPTION_KEY 환경변수(32바이트 base64) 필수.</p>
  </li>
</ul>

<h2 id="3-api-설계">3. API 설계</h2>

<h3 id="주요-api-목록">주요 API 목록</h3>

<ul>
  <li>
    <p>GET /orders : 주문 목록 조회(인증 필요, scope/page/limit/order_id/keyword)</p>
  </li>
  <li>
    <p>POST /orders : 주문 생성(고객사별 분할)</p>
  </li>
  <li>
    <p>GET /orders/:orderId : 주문 + 상세 조회</p>
  </li>
  <li>
    <p>POST /orders/:orderId/details : 주문 상세 라인 추가(인증 필요)</p>
  </li>
  <li>
    <p>PATCH /orders/:orderId : 주문 수정(주소/주문자/배송상태)</p>
  </li>
</ul>

<h3 id="주문결제-연동-포인트">주문–결제 연동 포인트</h3>

<ul>
  <li>
    <p>결제 모듈 이벤트(payment.created) 수신 훅 구현</p>
  </li>
  <li>
    <p>주문 목록의 결제 상태는 payments.status=SUCCEEDED 여부로 계산</p>
  </li>
</ul>

<h2 id="4-테스트-전략-주문-모듈">4. 테스트 전략 (주문 모듈)</h2>

<h3 id="단위-테스트">단위 테스트</h3>

<ul>
  <li>서비스 단위 로직(합계 계산, 스코프 검증, 키워드 검색)</li>
</ul>

<h3 id="통합-테스트">통합 테스트</h3>

<ul>
  <li>
    <p>k6/pay-system.smoke.js: 결제 생성 -&gt; 주문 조회 -&gt; 상세 추가 흐름 스모크</p>
  </li>
  <li>
    <p>E2E는 현재 Auth/Master/Payment 중심으로 구성되어 있어 OMS 전용 E2E 보강 필요</p>
  </li>
</ul>

<h2 id="5-리스크--todo">5. 리스크 · TODO</h2>

<h3 id="리스크">리스크</h3>

<ul>
  <li>
    <p>groupItemsByCompany가 아이템별 상품 조회를 순차 수행해 대량 요청에서 지연 가능</p>
  </li>
  <li>
    <p>결제 상태는 조회 시 계산 방식이라, 고트래픽에서 조인/서브쿼리 부담 증가 가능</p>
  </li>
</ul>

<h3 id="앞으로-개선하고-싶은-점">앞으로 개선하고 싶은 점</h3>

<ul>
  <li>
    <p>OMS 전용 E2E 테스트 추가</p>
  </li>
  <li>
    <p>분할 주문/결제 묶음 단위 API(결제 일괄 처리 포함) 확장</p>
  </li>
  <li>
    <p>키워드 검색 DB 오프로딩(현재 메모리 필터링 구간 개선)</p>
  </li>
</ul>

<h2 id="6-개인-회고--느낀-점">6. 개인 회고 · 느낀 점</h2>

<h3 id="이번-챕터에서-느낀-점">이번 챕터에서 느낀 점</h3>

<ul>
  <li>주문과 결제를 어디까지 분리할지에 따라, 이후 정산·배송·재고 등 다른 도메인의 설계 자유도가 크게 달라진다는 걸 다시 느꼈다.</li>
</ul>

<h3 id="배운-것">배운 것</h3>

<ul>
  <li>주문 상태 플로우를 글과 다이어그램으로 먼저 정리하고 나니, 테스트 케이스와 API 설계가 훨씬 자연스럽게 따라온다는 점.</li>
</ul>

<h3 id="다음-챕터유저-모듈에서-더-신경-쓸-것">다음 챕터(유저 모듈)에서 더 신경 쓸 것</h3>

<ul>
  <li>유저 정보와 주문/결제 히스토리 사이의 경계, 그리고 개인정보 보호(로그·백업)를 더 구체적으로 정의해 두기.</li>
</ul>]]></content><author><name></name></author><category term="Project" /><category term="토이프로젝트" /><summary type="html"><![CDATA[AI 활용 결제·OMS Nest 프로젝트 회고 #3]]></summary></entry><entry><title type="html">AI 활용 결제·OMS Nest 프로젝트 회고 #4</title><link href="https://suinwoo.github.io/posts/ai-%ED%99%9C%EC%9A%A9-%EA%B2%B0%EC%A0%9C-oms-nest-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-%ED%9A%8C%EA%B3%A0-4/" rel="alternate" type="text/html" title="AI 활용 결제·OMS Nest 프로젝트 회고 #4" /><published>2026-03-26T00:00:00+09:00</published><updated>2026-03-26T00:00:00+09:00</updated><id>https://suinwoo.github.io/posts/ai-%ED%99%9C%EC%9A%A9-%EA%B2%B0%EC%A0%9C-oms-nest-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-%ED%9A%8C%EA%B3%A0-4</id><content type="html" xml:base="https://suinwoo.github.io/posts/ai-%ED%99%9C%EC%9A%A9-%EA%B2%B0%EC%A0%9C-oms-nest-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-%ED%9A%8C%EA%B3%A0-4/"><![CDATA[<h1 id="ai-활용-결제oms-nest-프로젝트-회고-4">AI 활용 결제·OMS Nest 프로젝트 회고 #4</h1>

<h2 id="1-유저-모듈-개요">1. 유저 모듈 개요</h2>

<h3 id="역할">역할</h3>

<ul>
  <li>회원 계정, 인증 토큰, 내 프로필/배송지 관리</li>
</ul>

<h3 id="주요-책임">주요 책임</h3>

<ul>
  <li>
    <p>회원가입/로그인/토큰 갱신/로그아웃</p>
  </li>
  <li>
    <p>역할(Role) 조회 및 사용자 권한 컨텍스트 제공</p>
  </li>
  <li>
    <p>내 프로필/배송지 CRUD</p>
  </li>
</ul>

<h3 id="주요-연관-도메인">주요 연관 도메인</h3>

<ul>
  <li>주문, 결제, 인증/보안</li>
</ul>

<h2 id="2-아키텍처--설계">2. 아키텍처 · 설계</h2>

<h3 id="모듈-구조">모듈 구조</h3>

<ul>
  <li>
    <p>AuthModule + UsersModule 분리</p>
  </li>
  <li>
    <p>AuthController/AuthService, UsersService, UserRepository 계층</p>
  </li>
</ul>

<h3 id="핵심-엔티티테이블">핵심 엔티티/테이블</h3>

<ul>
  <li>users, roles, user_profiles, user_addresses</li>
</ul>

<h3 id="보안개인정보-관점-고려-사항">보안·개인정보 관점 고려 사항</h3>

<ul>
  <li>
    <p>비밀번호/리프레시 토큰은 bcrypt 해시만 저장(평문 저장 금지)</p>
  </li>
  <li>
    <p>refresh 토큰은 로그인/refresh 시 회전(rotate) 정책</p>
  </li>
  <li>
    <p>JWT access/refresh secret 및 만료시간은 환경변수 기반</p>
  </li>
</ul>

<h2 id="3-api-설계">3. API 설계</h2>

<h3 id="주요-api-목록">주요 API 목록</h3>

<ul>
  <li>
    <p>POST /auth/register : 회원가입</p>
  </li>
  <li>
    <p>POST /auth/login : 로그인</p>
  </li>
  <li>
    <p>POST /auth/logout : 로그아웃(인증 필요)</p>
  </li>
  <li>
    <p>POST /auth/refresh : 토큰 갱신(인증 필요)</p>
  </li>
  <li>
    <p>GET /auth/me : 내 정보 + 메뉴 조회(인증 필요)</p>
  </li>
  <li>
    <p>GET /auth/roles : 역할 목록 조회(인증 필요)</p>
  </li>
  <li>
    <p>GET/POST /auth/me/profile : 내 프로필 조회/저장</p>
  </li>
  <li>
    <p>GET/POST /auth/me/addresses : 내 배송지 조회/생성</p>
  </li>
  <li>
    <p>POST /auth/me/addresses/:id : 내 배송지 수정</p>
  </li>
  <li>
    <p>POST /auth/me/addresses/:id/default : 기본 배송지 설정</p>
  </li>
</ul>

<h3 id="주문결제와의-연동-포인트">주문/결제와의 연동 포인트</h3>

<ul>
  <li>
    <p>유저 ID 기반으로 주문/결제 히스토리 조회</p>
  </li>
  <li>
    <p>주문 생성/조회 권한 제어 시 JWT payload(role, clientCompanyId) 활용</p>
  </li>
  <li>
    <p>결제/주문 API 호출 시 Authorization: Bearer 기반 사용자 컨텍스트 전달</p>
  </li>
</ul>

<h2 id="4-테스트-전략-유저-모듈">4. 테스트 전략 (유저 모듈)</h2>

<h3 id="단위-테스트">단위 테스트</h3>

<ul>
  <li>
    <p>비밀번호/토큰 관련 로직</p>
  </li>
  <li>
    <p>권한(Authorization) 체크 로직</p>
  </li>
</ul>

<h3 id="통합-테스트">통합 테스트</h3>

<ul>
  <li>
    <p>test/e2e/auth.e2e-spec.ts: register + login + me 플로우</p>
  </li>
  <li>
    <p>test/unit/modules/users/users.service.spec.ts: 유저 서비스 단위 검증</p>
  </li>
  <li>
    <p>test/unit/modules/auth/auth.service.spec.ts: 인증 서비스 단위 검증</p>
  </li>
</ul>

<h2 id="5-리스크--todo">5. 리스크 · TODO</h2>

<h3 id="리스크">리스크</h3>

<ul>
  <li>
    <p>현재 리프레시 토큰은 사용자당 단일 해시 저장 방식이라 다중 디바이스 세션 요구사항과 충돌 가능</p>
  </li>
  <li>
    <p>계정 잠금/비정상 로그인 탐지/2차 인증 정책은 미구현</p>
  </li>
</ul>

<h3 id="앞으로-개선하고-싶은-점">앞으로 개선하고 싶은 점</h3>

<ul>
  <li>
    <p>소셜 로그인/외부 인증 연동</p>
  </li>
  <li>
    <p>권한(관리자/일반 유저 등) 세분화</p>
  </li>
  <li>
    <p>디바이스 단위 세션 관리(다중 refresh 토큰)</p>
  </li>
  <li>
    <p>개인정보 마스킹/보존/삭제 정책 문서화와 운영 정책 연동</p>
  </li>
</ul>

<h2 id="6-개인-회고--느낀-점">6. 개인 회고 · 느낀 점</h2>

<h3 id="이번-챕터에서-느낀-점">이번 챕터에서 느낀 점</h3>

<ul>
  <li>유저 도메인이 주문·결제와 강하게 엮여 있어서, “유저 ID”가 거의 모든 로그·추적의 출발점이 된다는 걸 다시 체감했다.</li>
</ul>

<h3 id="배운-것">배운 것</h3>

<ul>
  <li>인증/인가를 “일단 로그인만 되게” 넣어두면, 나중에 권한·운영·감사 요구사항이 생겼을 때 거의 처음부터 다시 설계해야 한다는 교훈.</li>
</ul>

<h3 id="다음-챕터k6성능-테스트에서-더-신경-쓸-것">다음 챕터(k6·성능 테스트)에서 더 신경 쓸 것</h3>

<ul>
  <li>유저 수/트래픽 스케일을 가정하고 k6 시나리오를 설계해, 인증/인가·세션·토큰 검증이 병목이 되지 않도록 미리 확인하기.</li>
</ul>]]></content><author><name></name></author><category term="Project" /><category term="토이프로젝트" /><summary type="html"><![CDATA[AI 활용 결제·OMS Nest 프로젝트 회고 #4]]></summary></entry><entry><title type="html">AI 활용 결제·OMS Nest 프로젝트 회고 #1</title><link href="https://suinwoo.github.io/posts/ai-%ED%99%9C%EC%9A%A9-%EA%B2%B0%EC%A0%9C-oms-nest-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-%ED%9A%8C%EA%B3%A0-1/" rel="alternate" type="text/html" title="AI 활용 결제·OMS Nest 프로젝트 회고 #1" /><published>2026-03-11T00:00:00+09:00</published><updated>2026-03-11T20:42:52+09:00</updated><id>https://suinwoo.github.io/posts/ai-%ED%99%9C%EC%9A%A9-%EA%B2%B0%EC%A0%9C-oms-nest-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-%ED%9A%8C%EA%B3%A0-1</id><content type="html" xml:base="https://suinwoo.github.io/posts/ai-%ED%99%9C%EC%9A%A9-%EA%B2%B0%EC%A0%9C-oms-nest-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-%ED%9A%8C%EA%B3%A0-1/"><![CDATA[<h1 id="ai-활용-결제oms-nest-프로젝트-회고-1">AI 활용 결제·OMS Nest 프로젝트 회고 #1</h1>

<h2 id="1-프로젝트-개요--결심-배경">1. 프로젝트 개요 · 결심 배경</h2>

<ul>
  <li>
    <p>프로젝트 이름: AI 활용 결제·OMS Nest 프로젝트</p>
  </li>
  <li>
    <p>기간: 2026-03 ~ 미정</p>
  </li>
  <li>
    <p>목적: 개인 학습과 동시에, 실무에 바로 가져갈 수 있는 결제/OMS 백엔드 아키텍처를 만들고, AI를 “팀원”처럼 활용하는 개발 프로세스를 실험하는 것</p>
  </li>
</ul>

<h2 id="2-개인-학습--성장-목표">2. 개인 학습 · 성장 목표</h2>

<h3 id="왜-nestjs인가">왜 NestJS인가</h3>

<ul>
  <li>
    <p>현재 회사에서는 Express를 사용하고 있지만, 구조화된 모듈/DI/데코레이터 기반의 NestJS를 직접 써보며 프레임워크 관점을 넓히고 싶었다.</p>
  </li>
  <li>
    <p>회사 도메인(결제·OMS)을 NestJS 위에서 다시 모델링해 보면서, 도메인 이해도를 한 단계 더 끌어올리고자 했다.</p>
  </li>
</ul>

<h3 id="무엇을-경험하고-싶은가">무엇을 경험하고 싶은가</h3>

<ul>
  <li>
    <p>TDD와 테스트 가능한 구조를 실제 도메인(결제/주문/유저)에 적용해 보는 경험</p>
  </li>
  <li>
    <p>k6 등을 활용해 다양한 트래픽 패턴과 장애/에러 상황을 직접 설계·실행·해석해 보는 경험</p>
  </li>
  <li>
    <p>AI를 어떻게하면 다른사람과 다르게, 더 효율적으로 사용할 수 있는가에 대한 경험</p>
  </li>
</ul>

<h2 id="3-왜-aicursor를-팀처럼-쓰기로-했는가">3. 왜 AI·Cursor를 팀처럼 쓰기로 했는가</h2>

<h3 id="문제의식">문제의식</h3>

<ul>
  <li>
    <p>혼자 하는 사이드 프로젝트라도 결제/주문/유저까지 들어가면 도메인 복잡도가 커져, 설계·문서·테스트를 모두 혼자 챙기기 어렵다.</p>
  </li>
  <li>
    <p>반복적인 설정/보일러플레이트와 리뷰가 많아, “사고”에 쓸 수 있는 시간이 줄어든다.</p>
  </li>
</ul>

<h3 id="목표">목표</h3>

<ul>
  <li>
    <p>설계·리뷰·문서화의 상당 부분을 AI에게 맡기고, 사람은 도메인 정의와 의사결정에 집중한다.</p>
  </li>
  <li>
    <p>최근 실무에서도 AI 활용 능력을 요구하는 흐름 속에서, “혼자서 여러 명이 하는 것처럼” 프로젝트 전체를 읽고 관리하는 연습을 하고자 했다.</p>
  </li>
  <li>
    <p>실제 팀 회의처럼 역할(시니어, DB, QA, 보안, 운영, 비즈니스, 노션 등)을 분리하고, 각 역할의 관점에서 끊임없이 질문·리뷰를 받는 개발 프로세스를 만든다.</p>
  </li>
</ul>

<h2 id="4-초기-목표-설정">4. 초기 목표 설정</h2>

<h3 id="기술-스택">기술 스택</h3>

<ul>
  <li>
    <p>Backend: NestJS, TypeScript</p>
  </li>
  <li>
    <p>DB: PostgreSQL</p>
  </li>
  <li>
    <p>인프라: Local -&gt; Docker(이 후 예정)</p>
  </li>
  <li>
    <p>성능/테스트: k6, jest</p>
  </li>
</ul>

<h3 id="도메인-범위">도메인 범위</h3>

<ul>
  <li>
    <p>결제</p>
  </li>
  <li>
    <p>주문(OMS)</p>
  </li>
  <li>
    <p>유저</p>
  </li>
</ul>

<h3 id="비기능-목표">비기능 목표</h3>

<ul>
  <li>
    <p>가독성 좋은 모듈 구조</p>
  </li>
  <li>
    <p>테스트 가능성</p>
  </li>
  <li>
    <p>확장 가능한 결제/주문 플로우</p>
  </li>
</ul>

<h2 id="5-개발-프로세스-설계-ai-팀-구성-아이디어">5. 개발 프로세스 설계 (AI 팀 구성 아이디어)</h2>

<h3 id="cursor를-팀처럼-꾸리기로-한-이유">Cursor를 팀처럼 꾸리기로 한 이유</h3>

<ul>
  <li>시니어/페어/QA/DB/보안/운영/비즈니스/노션 등 역할을 명시적으로 나눠서, “한 사람이 모든 역할을 동시에 한다”는 부담에서 벗어나기 위해서.</li>
</ul>

<h3 id="파트-구분">파트 구분</h3>

<ul>
  <li>
    <p>개발·아키텍처 파트: 시니어담당자, 페어담당자(반대의견제시)</p>
  </li>
  <li>
    <p>데이터·성능 파트: DB담당자, 성능담당자</p>
  </li>
  <li>
    <p>품질·안정성 파트: QA담당자, 에러담당자, 로그담당자</p>
  </li>
  <li>
    <p>보안·운영 파트: 보안담당자, 운영담당자</p>
  </li>
  <li>
    <p>비즈니스·유지보수 파트: 비즈니스담당자, 유지보수담당자</p>
  </li>
  <li>
    <p>문서화·명세 파트: 비즈니스문서담당자, 노션리뷰담당자, API명세담당자</p>
  </li>
</ul>

<h3 id="역할-정의">역할 정의</h3>

<ul>
  <li>
    <p>시니어담당자: 설계, 모듈 구조, 변경 용이성 리뷰</p>
  </li>
  <li>
    <p>페어담당자: 반대 의견 제시</p>
  </li>
  <li>
    <p>DB담당자: 스키마, 인덱스, 쿼리 성능</p>
  </li>
  <li>
    <p>QA담당자: 테스트 전략, 엣지 케이스</p>
  </li>
  <li>
    <p>보안담당자: 인증/인가, 민감정보 보호, 로그 마스킹</p>
  </li>
  <li>
    <p>성능담당자: 응답 시간, 병목, k6 기반 성능 관리</p>
  </li>
  <li>
    <p>에러담당자·로그담당자: 에러 코드/메시지 정책, 추적 가능한 로그 설계</p>
  </li>
  <li>
    <p>비즈니스·유지보수담당자: MVP 범위 설정, 운영자 관점에서의 가독성과 관리자 기능</p>
  </li>
  <li>
    <p>노션리뷰담당자·비즈니스문서담당자: 요구사항·정책 정리, docs/notion/*와 Notion 싱크</p>
  </li>
</ul>

<h3 id="워크플로우-초안">워크플로우 초안</h3>

<ul>
  <li>
    <p>요구사항 정리 → 아키텍처/ERD 구상 → 모듈별 설계 → 구현 → 테스트 → 회고</p>
  </li>
  <li>
    <p>각 단계마다 “역할 회의”를 한 번씩 거치고, 중요한 결정은 회의록(docs/meetings/*.md)과 노션에 함께 남긴다.</p>
  </li>
</ul>

<h2 id="6-이후-어떻게-디벨롭할-계획인가">6. 이후 어떻게 디벨롭할 계획인가</h2>

<h2 id="단계-1--도메인-안정화">단계 1 – 도메인 안정화</h2>

<ul>
  <li>
    <p>결제/주문/유저 각각에 대해 유스케이스를 정의하고, API·DB 설계를 점진적으로 다듬기</p>
  </li>
  <li>
    <p>공통 코드/공통 테이블 전략 정리 (docs/erd.md, docs/schema.sql와 연동)</p>
  </li>
</ul>

<h3 id="단계-2--품질테스트-강화">단계 2 – 품질·테스트 강화</h3>

<ul>
  <li>
    <p>유닛/통합 테스트</p>
  </li>
  <li>
    <p>k6를 활용한 부하 테스트 시나리오 작성 및 정기 실행으로, 주요 플로우(주문→결제)의 성능을 숫자로 관리</p>
  </li>
  <li>
    <p>장애/에러 케이스 정의 및 핸들링 전략을 문서와 테스트 코드로 동시에 관리</p>
  </li>
</ul>

<h3 id="단계-3--운영문서화">단계 3 – 운영·문서화</h3>

<ul>
  <li>
    <p>운영 관점에서 필요한 관리자 기능/로그/모니터링 정리</p>
  </li>
  <li>
    <p>로그에 orderId, userId, requestId 등 공통 식별자를 남기는 규칙을 적용</p>
  </li>
  <li>
    <p>Notion + docs/를 싱크 맞춰 문서화하고, 회의 내용은 docs/meetings/*.md와 노션 회의록에 아카이빙</p>
  </li>
</ul>

<h3 id="단계-4--실무-적용-가능-수준으로-리파인">단계 4 – 실무 적용 가능 수준으로 리파인</h3>

<ul>
  <li>
    <p>예: PG 연동/웹훅 처리/정산 시나리오 등, 현실적인 요구사항을 하나씩 끌어와 반영</p>
  </li>
  <li>
    <p>돈/정산/환불 도메인은 비즈니스·DB·QA 역할이 함께 협의해, 보수적으로 단계별 확장</p>
  </li>
</ul>

<h2 id="7-개인-회고--느낀-점">7. 개인 회고 · 느낀 점</h2>

<h3 id="이번-챕터에서-느낀-점">이번 챕터에서 느낀 점</h3>

<ul>
  <li>
    <p>AI를 “팀처럼” 쓰려면, 도구 성능보다도 역할 정의와 워크플로우 합의가 더 중요하다는 걸 느꼈다.</p>
  </li>
  <li>
    <p>실제로 역할 회의를 하듯 질문을 던지면, 혼자 개발할 때 보이지 않던 리스크(정합성, 로그, 운영, 비즈니스)가 잘 드러난다.</p>
  </li>
  <li>
    <p>아직 처음 사용하다 보니 자연스럽게 사용하는 것이 어렵다.</p>
  </li>
</ul>

<h3 id="배운-것">배운 것</h3>

<ul>
  <li>혼자 하는 프로젝트에도 시니어/DB/QA/보안/운영/비즈니스 관점을 분리해두면, 나중에 실무로 옮길 때 바로 팀 언어로 설명할 수 있다는 점.</li>
</ul>]]></content><author><name></name></author><category term="Project" /><category term="토이프로젝트" /><summary type="html"><![CDATA[AI 활용 결제·OMS Nest 프로젝트 회고 #1]]></summary></entry><entry><title type="html">어두운 굴다리 (BOJ 17266)</title><link href="https://suinwoo.github.io/posts/%EC%96%B4%EB%91%90%EC%9A%B4-%EA%B5%B4%EB%8B%A4%EB%A6%AC-boj-17266/" rel="alternate" type="text/html" title="어두운 굴다리 (BOJ 17266)" /><published>2026-03-04T00:00:00+09:00</published><updated>2026-03-04T00:00:00+09:00</updated><id>https://suinwoo.github.io/posts/%EC%96%B4%EB%91%90%EC%9A%B4-%EA%B5%B4%EB%8B%A4%EB%A6%AC-boj-17266</id><content type="html" xml:base="https://suinwoo.github.io/posts/%EC%96%B4%EB%91%90%EC%9A%B4-%EA%B5%B4%EB%8B%A4%EB%A6%AC-boj-17266/"><![CDATA[<h1 id="어두운-굴다리-boj-17266">어두운 굴다리 (BOJ 17266)</h1>

<h2 id="-문제-설명">📖 문제 설명</h2>

<p>길이 N의 굴다리에 M개의 가로등이 설치되어 있다.</p>

<p>각 가로등은 높이 H만큼 좌우를 비출 수 있다.</p>

<p>굴다리 전체를 밝히기 위한 최소 가로등 높이 H를 구하는 문제이다.</p>

<h2 id="-조건-정리">✅ 조건 정리</h2>

<ul>
  <li>
    <p>N : 굴다리 길이</p>
  </li>
  <li>
    <p>M : 가로등 개수</p>
  </li>
  <li>
    <p>가로등 위치는 오름차순으로 주어짐</p>
  </li>
  <li>
    <p>모든 구간이 어둡지 않도록 해야 함</p>
  </li>
  <li>
    <p>구해야 하는 것 → 최소 높이 H</p>
  </li>
</ul>

<h2 id="-풀이-방법">💡 풀이 방법</h2>

<p>굴다리를 밝히기 위해 고려해야 할 구간은 3가지이다.</p>

<h2 id="1️⃣-왼쪽-끝-구간">1️⃣ 왼쪽 끝 구간</h2>

<p>굴다리 시작점(0)부터 첫 번째 가로등까지의 거리</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
</pre></td><td class="rouge-code"><pre>x[0]
</pre></td></tr></tbody></table></code></pre></div></div>

<p>이 값만큼은 비춰야 한다.</p>

<h2 id="2️⃣-가로등-사이-구간-핵심">2️⃣ 가로등 사이 구간 (핵심)</h2>

<p>두 가로등 사이의 거리:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
</pre></td><td class="rouge-code"><pre>x[i] - x[i-1]
</pre></td></tr></tbody></table></code></pre></div></div>

<p>이 구간은 양쪽 가로등이 절반씩 비추므로</p>

<p>필요한 높이는</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
</pre></td><td class="rouge-code"><pre>ceil((거리) / 2)
</pre></td></tr></tbody></table></code></pre></div></div>

<p>정수 올림 처리를 위해:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
</pre></td><td class="rouge-code"><pre>(x[i]-x[i-1]+1)/2
</pre></td></tr></tbody></table></code></pre></div></div>

<p>을 사용한다.</p>

<h2 id="3️⃣-오른쪽-끝-구간">3️⃣ 오른쪽 끝 구간</h2>

<p>마지막 가로등부터 굴다리 끝까지의 거리</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
</pre></td><td class="rouge-code"><pre>N - x[M-1]
</pre></td></tr></tbody></table></code></pre></div></div>

<h2 id="-최종-아이디어">🎯 최종 아이디어</h2>

<p>위 세 구간 중</p>

<p>가장 큰 값이 최소 가로등 높이 H가 된다.</p>

<h2 id="-코드">💻 코드</h2>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
</pre></td><td class="rouge-code"><pre><span class="kn">package</span> <span class="nn">boj</span><span class="o">;</span>

<span class="kn">import</span> <span class="nn">java.util.*</span><span class="o">;</span>
<span class="kn">import</span> <span class="nn">java.io.*</span><span class="o">;</span>

<span class="kd">public</span> <span class="kd">class</span> <span class="nc">Boj17266</span> <span class="o">{</span>
    <span class="kd">public</span> <span class="kd">static</span> <span class="kt">void</span> <span class="nf">main</span><span class="o">(</span><span class="nc">String</span><span class="o">[]</span> <span class="n">args</span><span class="o">)</span> <span class="kd">throws</span> <span class="nc">IOException</span> <span class="o">{</span>
        <span class="nc">BufferedReader</span> <span class="n">br</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">BufferedReader</span><span class="o">(</span><span class="k">new</span> <span class="nc">InputStreamReader</span><span class="o">(</span><span class="nc">System</span><span class="o">.</span><span class="na">in</span><span class="o">));</span>
        <span class="kt">int</span> <span class="no">N</span> <span class="o">=</span> <span class="nc">Integer</span><span class="o">.</span><span class="na">parseInt</span><span class="o">(</span><span class="n">br</span><span class="o">.</span><span class="na">readLine</span><span class="o">());</span>
        <span class="kt">int</span> <span class="no">M</span> <span class="o">=</span> <span class="nc">Integer</span><span class="o">.</span><span class="na">parseInt</span><span class="o">(</span><span class="n">br</span><span class="o">.</span><span class="na">readLine</span><span class="o">());</span>
        <span class="kt">int</span><span class="o">[]</span> <span class="n">x</span> <span class="o">=</span> <span class="k">new</span> <span class="kt">int</span><span class="o">[</span><span class="no">M</span><span class="o">];</span>
        <span class="nc">StringTokenizer</span> <span class="n">st</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">StringTokenizer</span><span class="o">(</span><span class="n">br</span><span class="o">.</span><span class="na">readLine</span><span class="o">());</span>

        <span class="k">for</span> <span class="o">(</span><span class="kt">int</span> <span class="n">i</span> <span class="o">=</span> <span class="mi">0</span><span class="o">;</span> <span class="n">i</span> <span class="o">&lt;</span> <span class="no">M</span><span class="o">;</span> <span class="n">i</span><span class="o">++)</span> <span class="o">{</span>
            <span class="n">x</span><span class="o">[</span><span class="n">i</span><span class="o">]</span> <span class="o">=</span> <span class="nc">Integer</span><span class="o">.</span><span class="na">parseInt</span><span class="o">(</span><span class="n">st</span><span class="o">.</span><span class="na">nextToken</span><span class="o">());</span>
        <span class="o">}</span>

        <span class="c1">// 각각의 가로등 위치사이 간격의 최대값을 구하기</span>
        <span class="kt">int</span> <span class="n">maxDist</span> <span class="o">=</span> <span class="n">x</span><span class="o">[</span><span class="mi">0</span><span class="o">];</span>

        <span class="k">for</span> <span class="o">(</span><span class="kt">int</span> <span class="n">i</span> <span class="o">=</span> <span class="mi">1</span><span class="o">;</span> <span class="n">i</span> <span class="o">&lt;</span> <span class="no">M</span><span class="o">;</span> <span class="n">i</span><span class="o">++)</span> <span class="o">{</span>
            <span class="kt">int</span> <span class="n">dist</span> <span class="o">=</span> <span class="o">(</span><span class="n">x</span><span class="o">[</span><span class="n">i</span><span class="o">]</span> <span class="o">-</span> <span class="n">x</span><span class="o">[</span><span class="n">i</span><span class="o">-</span><span class="mi">1</span><span class="o">]</span> <span class="o">+</span> <span class="mi">1</span><span class="o">)</span> <span class="o">/</span> <span class="mi">2</span><span class="o">;</span>
            <span class="k">if</span> <span class="o">(</span><span class="n">maxDist</span> <span class="o">&lt;</span> <span class="o">(</span><span class="n">dist</span><span class="o">))</span> <span class="o">{</span>
                <span class="n">maxDist</span> <span class="o">=</span> <span class="n">dist</span><span class="o">;</span>
            <span class="o">}</span>
        <span class="o">}</span>

        <span class="c1">// 최대값 구한 후 마지막을 비출 수 있는지 구하기</span>
        <span class="n">maxDist</span> <span class="o">=</span> <span class="nc">Math</span><span class="o">.</span><span class="na">max</span><span class="o">(</span><span class="n">maxDist</span><span class="o">,</span> <span class="no">N</span> <span class="o">-</span> <span class="n">x</span><span class="o">[</span><span class="no">M</span><span class="o">-</span><span class="mi">1</span><span class="o">]);</span>

        <span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="n">maxDist</span><span class="o">);</span>
    <span class="o">}</span>
<span class="o">}</span>

</pre></td></tr></tbody></table></code></pre></div></div>

<h2 id="-시간-복잡도">⏱ 시간 복잡도</h2>

<ul>
  <li>
    <p>시간복잡도: O(M)</p>
  </li>
  <li>
    <p>공간복잡도: O(M)</p>
  </li>
</ul>

<h2 id="-핵심-포인트-정리">🔎 핵심 포인트 정리</h2>

<ul>
  <li>
    <p>가로등 사이 간격은 그대로 사용하면 안 된다.</p>
  </li>
  <li>
    <p>반드시 2로 나누고 올림 처리해야 한다.</p>
  </li>
  <li>
    <p>왼쪽 끝, 오른쪽 끝도 따로 비교해야 한다.</p>
  </li>
  <li>
    <p>최댓값을 구하는 문제로 변환하면 쉽게 해결된다.</p>
  </li>
</ul>]]></content><author><name></name></author><category term="Algorithm" /><category term="알고리즘" /><summary type="html"><![CDATA[어두운 굴다리 (BOJ 17266)]]></summary></entry><entry><title type="html">등수 구하기 (BOJ 1205)</title><link href="https://suinwoo.github.io/posts/%EB%93%B1%EC%88%98-%EA%B5%AC%ED%95%98%EA%B8%B0-boj-1205/" rel="alternate" type="text/html" title="등수 구하기 (BOJ 1205)" /><published>2026-03-03T00:00:00+09:00</published><updated>2026-03-03T00:00:00+09:00</updated><id>https://suinwoo.github.io/posts/%EB%93%B1%EC%88%98-%EA%B5%AC%ED%95%98%EA%B8%B0-boj-1205</id><content type="html" xml:base="https://suinwoo.github.io/posts/%EB%93%B1%EC%88%98-%EA%B5%AC%ED%95%98%EA%B8%B0-boj-1205/"><![CDATA[<h1 id="등수-구하기-boj-1205">등수 구하기 (BOJ 1205)</h1>

<h2 id="-문제-설명">📖 문제 설명</h2>

<p>현재 랭킹 리스트에 점수들이 내림차순으로 주어져 있다.</p>

<p>새로운 점수를 넣었을 때 몇 등을 할 수 있는지 구하는 문제이다.</p>

<p>단, 랭킹 리스트의 최대 크기 P를 초과하면 점수를 올릴 수 없다.</p>

<h2 id="-조건-정리">✅ 조건 정리</h2>

<ul>
  <li>
    <p>N : 현재 랭킹에 있는 점수 개수</p>
  </li>
  <li>
    <p>내 점수</p>
  </li>
  <li>
    <p>P : 랭킹 리스트 최대 크기</p>
  </li>
  <li>
    <p>점수는 내림차순으로 주어짐</p>
  </li>
  <li>
    <p>같은 점수는 같은 등수</p>
  </li>
  <li>
    <p>리스트가 가득 찼고, 마지막 점수보다 작거나 같으면 등록 불가</p>
  </li>
</ul>

<p>출력:</p>

<ul>
  <li>
    <p>가능한 등수 출력</p>
  </li>
  <li>
    <p>등록 불가 시 1</p>
  </li>
</ul>

<h2 id="-풀이-방법">💡 풀이 방법</h2>

<h2 id="1️⃣-접근-아이디어">1️⃣ 접근 아이디어</h2>

<p>핵심은:</p>

<blockquote>
  <p>🔥 “나보다 높은 점수의 개수 + 1 = 내 등수”</p>
</blockquote>

<p>리스트를 앞에서부터 순회하면서:</p>

<ul>
  <li>
    <p>나보다 높은 점수 → 등수 증가</p>
  </li>
  <li>
    <p>같은 점수 → 순위 유지, 자리 차지</p>
  </li>
  <li>
    <p>더 낮은 점수 → 그 위치에 들어감</p>
  </li>
</ul>

<h2 id="2️⃣-중요한-예외-처리">2️⃣ 중요한 예외 처리</h2>

<h2 id="-n--0-인-경우">✔ N == 0 인 경우</h2>

<ul>
  <li>랭킹이 비어있으면 1등</li>
</ul>

<h2 id="-랭킹이-가득-찬-경우">✔ 랭킹이 가득 찬 경우</h2>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
</pre></td><td class="rouge-code"><pre>현재 들어갈 수 있는 인원 수 &gt;= P
</pre></td></tr></tbody></table></code></pre></div></div>

<p>이면 -1 출력</p>

<p>즉,</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
</pre></td><td class="rouge-code"><pre>내 점수 이상인 사람 수 &gt;= P
</pre></td></tr></tbody></table></code></pre></div></div>

<p>이면 등록 불가</p>

<h2 id="3️⃣-예시">3️⃣ 예시</h2>

<h2 id="예시-1">예시 1</h2>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
</pre></td><td class="rouge-code"><pre>N=3, 내 점수=90, P=3
100 90 80
</pre></td></tr></tbody></table></code></pre></div></div>

<ul>
  <li>
    <p>100 &gt; 90 → 등수 증가</p>
  </li>
  <li>
    <p>90 == 90 → 자리 차지</p>
  </li>
  <li>
    <p>80 &lt; 90 → 여기서 멈춤</p>
  </li>
</ul>

<p>→ 2등 가능</p>

<h2 id="예시-2-등록-불가">예시 2 (등록 불가)</h2>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
</pre></td><td class="rouge-code"><pre>N=3, 내 점수=70, P=3
100 90 80
</pre></td></tr></tbody></table></code></pre></div></div>

<ul>
  <li>
    <p>나보다 높은 점수 3명</p>
  </li>
  <li>
    <p>이미 P명 가득</p>
  </li>
</ul>

<p>→ -1</p>

<h2 id="-코드">💻 코드</h2>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
</pre></td><td class="rouge-code"><pre><span class="kn">package</span> <span class="nn">boj</span><span class="o">;</span>

<span class="kn">import</span> <span class="nn">java.io.*</span><span class="o">;</span>
<span class="kn">import</span> <span class="nn">java.util.*</span><span class="o">;</span>

<span class="kd">public</span> <span class="kd">class</span> <span class="nc">Boj1205</span> <span class="o">{</span>
    <span class="kd">public</span> <span class="kd">static</span> <span class="kt">void</span> <span class="nf">main</span><span class="o">(</span><span class="nc">String</span><span class="o">[]</span> <span class="n">args</span><span class="o">)</span> <span class="kd">throws</span> <span class="nc">IOException</span> <span class="o">{</span>
        <span class="nc">BufferedReader</span> <span class="n">br</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">BufferedReader</span><span class="o">(</span><span class="k">new</span> <span class="nc">InputStreamReader</span><span class="o">(</span><span class="nc">System</span><span class="o">.</span><span class="na">in</span><span class="o">));</span>
        <span class="nc">StringTokenizer</span> <span class="n">st</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">StringTokenizer</span><span class="o">(</span><span class="n">br</span><span class="o">.</span><span class="na">readLine</span><span class="o">());</span>

        <span class="kt">int</span> <span class="no">N</span> <span class="o">=</span> <span class="nc">Integer</span><span class="o">.</span><span class="na">parseInt</span><span class="o">(</span><span class="n">st</span><span class="o">.</span><span class="na">nextToken</span><span class="o">());</span> <span class="c1">// N</span>
        <span class="kt">int</span> <span class="n">rank</span> <span class="o">=</span> <span class="nc">Integer</span><span class="o">.</span><span class="na">parseInt</span><span class="o">(</span><span class="n">st</span><span class="o">.</span><span class="na">nextToken</span><span class="o">());</span> <span class="c1">// 확인할 랭킹</span>
        <span class="kt">int</span> <span class="no">P</span> <span class="o">=</span> <span class="nc">Integer</span><span class="o">.</span><span class="na">parseInt</span><span class="o">(</span><span class="n">st</span><span class="o">.</span><span class="na">nextToken</span><span class="o">());</span> <span class="c1">// P</span>

        <span class="k">if</span> <span class="o">(</span><span class="no">N</span> <span class="o">==</span> <span class="mi">0</span><span class="o">)</span> <span class="o">{</span> <span class="c1">// N이 0 일때는 리스트를 받을 필요 없음</span>
            <span class="k">if</span> <span class="o">(</span><span class="no">P</span> <span class="o">==</span> <span class="mi">0</span><span class="o">)</span> <span class="c1">// 0인경우 아예 랭킹에 올릴 수 없음</span>
                <span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="s">"-1"</span><span class="o">);</span>
            <span class="k">else</span>
                <span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="s">"1"</span><span class="o">);</span>
        <span class="o">}</span> <span class="k">else</span> <span class="o">{</span> <span class="c1">// 리스트 받아야함</span>
            <span class="n">st</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">StringTokenizer</span><span class="o">(</span><span class="n">br</span><span class="o">.</span><span class="na">readLine</span><span class="o">());</span>
            <span class="kt">int</span> <span class="n">checkCount</span> <span class="o">=</span> <span class="mi">0</span><span class="o">;</span> <span class="c1">// 지금까지 확인한 랭킹 개수</span>
            <span class="kt">int</span> <span class="n">myRank</span> <span class="o">=</span> <span class="mi">1</span><span class="o">;</span> <span class="c1">// 나의 순위</span>

            <span class="kt">int</span><span class="o">[]</span> <span class="n">rankList</span> <span class="o">=</span> <span class="k">new</span> <span class="kt">int</span><span class="o">[</span><span class="no">N</span><span class="o">];</span>

            <span class="k">for</span> <span class="o">(</span><span class="kt">int</span> <span class="n">i</span> <span class="o">=</span> <span class="mi">0</span><span class="o">;</span> <span class="n">i</span> <span class="o">&lt;</span> <span class="no">N</span><span class="o">;</span> <span class="n">i</span><span class="o">++)</span> <span class="o">{</span>
                <span class="n">rankList</span><span class="o">[</span><span class="n">i</span><span class="o">]</span> <span class="o">=</span> <span class="nc">Integer</span><span class="o">.</span><span class="na">parseInt</span><span class="o">(</span><span class="n">st</span><span class="o">.</span><span class="na">nextToken</span><span class="o">());</span>
            <span class="o">}</span>

            <span class="k">for</span> <span class="o">(</span><span class="kt">int</span> <span class="n">i</span> <span class="o">=</span> <span class="mi">0</span><span class="o">;</span> <span class="n">i</span> <span class="o">&lt;</span> <span class="no">N</span><span class="o">;</span> <span class="n">i</span><span class="o">++)</span> <span class="o">{</span>
                <span class="k">if</span> <span class="o">(</span><span class="n">rankList</span><span class="o">[</span><span class="n">i</span><span class="o">]</span> <span class="o">&gt;</span> <span class="n">rank</span><span class="o">)</span> <span class="o">{</span> <span class="c1">// 나보다 크면 내가 뒷순번</span>
                    <span class="n">checkCount</span><span class="o">++;</span>
                    <span class="n">myRank</span><span class="o">++;</span>
                <span class="o">}</span> <span class="k">else</span> <span class="k">if</span> <span class="o">(</span><span class="n">rankList</span><span class="o">[</span><span class="n">i</span><span class="o">]</span> <span class="o">==</span> <span class="n">rank</span><span class="o">)</span> <span class="o">{</span><span class="c1">// 같으면</span>
                    <span class="n">checkCount</span><span class="o">++;</span>
                <span class="o">}</span> <span class="k">else</span> <span class="o">{</span> <span class="c1">// 내 자리</span>
                    <span class="k">break</span><span class="o">;</span>
                <span class="o">}</span>
            <span class="o">}</span>

            <span class="k">if</span> <span class="o">(</span><span class="n">checkCount</span> <span class="o">&gt;=</span> <span class="no">P</span><span class="o">)</span> <span class="o">{</span>
                <span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="s">"-1"</span><span class="o">);</span>
            <span class="o">}</span> <span class="k">else</span> <span class="o">{</span>
                <span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="n">myRank</span><span class="o">);</span>
            <span class="o">}</span>
        <span class="o">}</span>

    <span class="o">}</span>
<span class="o">}</span>

</pre></td></tr></tbody></table></code></pre></div></div>

<h2 id="-시간-복잡도">⏱ 시간 복잡도</h2>

<ul>
  <li>
    <p>시간복잡도: O(N)</p>
  </li>
  <li>
    <p>공간복잡도: O(N)</p>
  </li>
</ul>

<h2 id="-배운-점--느낀-점">🔎 배운 점 / 느낀 점</h2>

<ul>
  <li>
    <p>순위 문제는 “나보다 높은 점수 개수”로 접근하면 단순해진다.</p>
  </li>
  <li>
    <p>같은 점수 처리와 리스트 최대 크기 조건이 핵심이다.</p>
  </li>
  <li>
    <p>예외 케이스(N=0, 리스트 가득 참)를 먼저 정리하는 것이 중요하다.</p>
  </li>
</ul>]]></content><author><name></name></author><category term="Algorithm" /><category term="알고리즘" /><summary type="html"><![CDATA[등수 구하기 (BOJ 1205)]]></summary></entry><entry><title type="html">임스와 함께하는 미니게임 (BOJ 25757)</title><link href="https://suinwoo.github.io/posts/%EC%9E%84%EC%8A%A4%EC%99%80-%ED%95%A8%EA%BB%98%ED%95%98%EB%8A%94-%EB%AF%B8%EB%8B%88%EA%B2%8C%EC%9E%84-boj-25757/" rel="alternate" type="text/html" title="임스와 함께하는 미니게임 (BOJ 25757)" /><published>2026-03-03T00:00:00+09:00</published><updated>2026-03-03T00:00:00+09:00</updated><id>https://suinwoo.github.io/posts/%EC%9E%84%EC%8A%A4%EC%99%80-%ED%95%A8%EA%BB%98%ED%95%98%EB%8A%94-%EB%AF%B8%EB%8B%88%EA%B2%8C%EC%9E%84-boj-25757</id><content type="html" xml:base="https://suinwoo.github.io/posts/%EC%9E%84%EC%8A%A4%EC%99%80-%ED%95%A8%EA%BB%98%ED%95%98%EB%8A%94-%EB%AF%B8%EB%8B%88%EA%B2%8C%EC%9E%84-boj-25757/"><![CDATA[<h1 id="임스와-함께하는-미니게임-boj-25757">임스와 함께하는 미니게임 (BOJ 25757)</h1>

<h2 id="-문제-설명">📖 문제 설명</h2>

<p>임스와 함께 미니게임을 하려 한다.</p>

<p>게임 종류에 따라 한 판에 필요한 인원이 다르다.</p>

<ul>
  <li>
    <p>Y : 1명</p>
  </li>
  <li>
    <p>F : 2명</p>
  </li>
  <li>
    <p>O : 3명</p>
  </li>
</ul>

<p>신청한 사람들의 이름이 주어질 때,</p>

<p>임스가 총 몇 번 게임을 할 수 있는지 구하는 문제이다.</p>

<p>※ 같은 사람이 여러 번 신청할 수 있다.</p>

<h2 id="-조건-정리">✅ 조건 정리</h2>

<ul>
  <li>
    <p>신청 인원 수 N (1 ≤ N ≤ 100,000)</p>
  </li>
  <li>
    <p>게임 종류: Y, F, O 중 하나</p>
  </li>
  <li>
    <p>같은 사람이 여러 번 신청할 수 있음</p>
  </li>
  <li>
    <p>중복 신청은 한 번만 인정</p>
  </li>
  <li>
    <p>출력: 임스가 진행할 수 있는 게임 횟수</p>
  </li>
</ul>

<h2 id="-풀이-방법">💡 풀이 방법</h2>

<h2 id="1️⃣-접근-아이디어">1️⃣ 접근 아이디어</h2>

<p>핵심은:</p>

<blockquote>
  <p>🔥 “중복 제거”</p>
</blockquote>

<p>같은 사람이 여러 번 신청할 수 있기 때문에</p>

<p>실제로 게임에 참여 가능한 인원은 중복 제거된 인원 수이다.</p>

<p>이를 위해 HashSet을 사용한다.</p>

<h2 id="2️⃣-게임-종류별-처리">2️⃣ 게임 종류별 처리</h2>

<p>게임 옵션에 따라 한 판에 필요한 인원이 다르다:</p>

<p>따라서:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
</pre></td><td class="rouge-code"><pre>게임 횟수 = (중복 제거된 인원 수) / (게임에 필요한 인원 수)
</pre></td></tr></tbody></table></code></pre></div></div>

<h2 id="-예시">✔ 예시</h2>

<p>입력:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
</pre></td><td class="rouge-code"><pre>7 F
a
b
c
a
d
b
e
</pre></td></tr></tbody></table></code></pre></div></div>

<p>중복 제거 후:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
</pre></td><td class="rouge-code"><pre>a, b, c, d, e → 총 5명
</pre></td></tr></tbody></table></code></pre></div></div>

<p>F는 2명 필요 →</p>

<p>5 / 2 = 2번 가능</p>

<h2 id="-코드">💻 코드</h2>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
</pre></td><td class="rouge-code"><pre><span class="kn">package</span> <span class="nn">boj</span><span class="o">;</span>

<span class="kn">import</span> <span class="nn">java.io.*</span><span class="o">;</span>
<span class="kn">import</span> <span class="nn">java.util.*</span><span class="o">;</span>

<span class="kd">public</span> <span class="kd">class</span> <span class="nc">Boj10431</span> <span class="o">{</span>
    <span class="kd">public</span> <span class="kd">static</span> <span class="kt">void</span> <span class="nf">main</span><span class="o">(</span><span class="nc">String</span><span class="o">[]</span> <span class="n">args</span><span class="o">)</span> <span class="kd">throws</span> <span class="nc">IOException</span> <span class="o">{</span>
        <span class="nc">BufferedReader</span> <span class="n">br</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">BufferedReader</span><span class="o">(</span><span class="k">new</span> <span class="nc">InputStreamReader</span><span class="o">(</span><span class="nc">System</span><span class="o">.</span><span class="na">in</span><span class="o">));</span>
        <span class="kt">int</span> <span class="n">testCase</span> <span class="o">=</span> <span class="nc">Integer</span><span class="o">.</span><span class="na">parseInt</span><span class="o">(</span><span class="n">br</span><span class="o">.</span><span class="na">readLine</span><span class="o">());</span>
        <span class="nc">StringBuilder</span> <span class="n">sb</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">StringBuilder</span><span class="o">();</span>

        <span class="k">for</span> <span class="o">(</span><span class="kt">int</span> <span class="n">t</span> <span class="o">=</span> <span class="mi">1</span><span class="o">;</span> <span class="n">t</span> <span class="o">&lt;=</span> <span class="n">testCase</span><span class="o">;</span> <span class="n">t</span><span class="o">++)</span> <span class="o">{</span>
            <span class="nc">StringTokenizer</span> <span class="n">st</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">StringTokenizer</span><span class="o">(</span><span class="n">br</span><span class="o">.</span><span class="na">readLine</span><span class="o">());</span>

            <span class="kt">int</span> <span class="n">caseNum</span> <span class="o">=</span> <span class="nc">Integer</span><span class="o">.</span><span class="na">parseInt</span><span class="o">(</span><span class="n">st</span><span class="o">.</span><span class="na">nextToken</span><span class="o">());</span> <span class="c1">// 테스트 번호</span>
            <span class="kt">int</span><span class="o">[]</span> <span class="n">arr</span> <span class="o">=</span> <span class="k">new</span> <span class="kt">int</span><span class="o">[</span><span class="mi">20</span><span class="o">];</span>
            <span class="kt">int</span> <span class="n">moveCount</span> <span class="o">=</span> <span class="mi">0</span><span class="o">;</span>

            <span class="k">for</span> <span class="o">(</span><span class="kt">int</span> <span class="n">i</span> <span class="o">=</span> <span class="mi">0</span><span class="o">;</span> <span class="n">i</span> <span class="o">&lt;</span> <span class="mi">20</span><span class="o">;</span> <span class="n">i</span><span class="o">++)</span> <span class="o">{</span>
                <span class="n">arr</span><span class="o">[</span><span class="n">i</span><span class="o">]</span> <span class="o">=</span> <span class="nc">Integer</span><span class="o">.</span><span class="na">parseInt</span><span class="o">(</span><span class="n">st</span><span class="o">.</span><span class="na">nextToken</span><span class="o">());</span>

                <span class="c1">// 현재 학생(arr[i])보다 앞에 있으면서 키 큰 학생 수 세기</span>
                <span class="k">for</span> <span class="o">(</span><span class="kt">int</span> <span class="n">j</span> <span class="o">=</span> <span class="mi">0</span><span class="o">;</span> <span class="n">j</span> <span class="o">&lt;</span> <span class="n">i</span><span class="o">;</span> <span class="n">j</span><span class="o">++)</span> <span class="o">{</span>
                    <span class="k">if</span> <span class="o">(</span><span class="n">arr</span><span class="o">[</span><span class="n">j</span><span class="o">]</span> <span class="o">&gt;</span> <span class="n">arr</span><span class="o">[</span><span class="n">i</span><span class="o">])</span> <span class="o">{</span>
                        <span class="n">moveCount</span><span class="o">++;</span>
                    <span class="o">}</span>
                <span class="o">}</span>
            <span class="o">}</span>

            <span class="n">sb</span><span class="o">.</span><span class="na">append</span><span class="o">(</span><span class="n">caseNum</span><span class="o">).</span><span class="na">append</span><span class="o">(</span><span class="s">" "</span><span class="o">).</span><span class="na">append</span><span class="o">(</span><span class="n">moveCount</span><span class="o">).</span><span class="na">append</span><span class="o">(</span><span class="s">"\n"</span><span class="o">);</span>
        <span class="o">}</span>

        <span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">print</span><span class="o">(</span><span class="n">sb</span><span class="o">);</span>
    <span class="o">}</span>
<span class="o">}</span>

</pre></td></tr></tbody></table></code></pre></div></div>

<h2 id="-시간-복잡도">⏱ 시간 복잡도</h2>

<ul>
  <li>
    <p>시간복잡도: O(N)</p>
  </li>
  <li>
    <p>공간복잡도: O(N)</p>
  </li>
</ul>

<h2 id="-배운-점--느낀-점">🔎 배운 점 / 느낀 점</h2>

<ul>
  <li>
    <p>중복 제거 문제에서는 Set이 가장 간단한 해결 방법이다.</p>
  </li>
  <li>
    <p>조건 분기는 나누기 연산 하나로 해결 가능하다.</p>
  </li>
  <li>
    <p>자료구조 선택이 문제 난이도를 크게 낮춘다.</p>
  </li>
</ul>]]></content><author><name></name></author><category term="Algorithm" /><category term="알고리즘" /><summary type="html"><![CDATA[임스와 함께하는 미니게임 (BOJ 25757)]]></summary></entry></feed>