<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>zedy_dev.log</title>
        <link>https://velog.io/</link>
        <description>IT기획/운영</description>
        <lastBuildDate>Thu, 30 Jan 2025 05:15:55 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <image>
            <title>zedy_dev.log</title>
            <url>https://velog.velcdn.com/images/zedy_dev/profile/6ca62b42-b860-4571-a8c8-e813786c8dcc/image.jpg</url>
            <link>https://velog.io/</link>
        </image>
        <copyright>Copyright (C) 2019. zedy_dev.log. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/zedy_dev" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[🏗️ Inflow Chat 개발 회고]]></title>
            <link>https://velog.io/@zedy_dev/%ED%9A%8C%EA%B3%A0-%EB%B2%A0%ED%8A%B8%EB%82%A8-%EB%8F%99%EA%B3%84-%ED%95%B4%EC%99%B8-%EC%9D%B8%ED%84%B4%EC%8B%AD-%EC%88%98%EB%A3%8C</link>
            <guid>https://velog.io/@zedy_dev/%ED%9A%8C%EA%B3%A0-%EB%B2%A0%ED%8A%B8%EB%82%A8-%EB%8F%99%EA%B3%84-%ED%95%B4%EC%99%B8-%EC%9D%B8%ED%84%B4%EC%8B%AD-%EC%88%98%EB%A3%8C</guid>
            <pubDate>Thu, 30 Jan 2025 05:15:55 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>📌 Tech Stack: Python (Flask), LangChain, Gemini API, FAISS, PostgreSQL, Docker, Swagger</p>
</blockquote>
<ul>
<li>AI 챗봇 백엔드 개발: LangChain + FAISS 기반 벡터 검색 및 최적화</li>
<li>프롬프트 엔지니어링: AI의 문맥 유지, 출처 제공 기능 개선</li>
<li>API 개발 및 문서화: Flask-RESTX 기반 백엔드 구축 + Swagger 도입</li>
<li>배포 및 환경 설정: Docker 컨테이너화 + AWS RDS 활용</li>
</ul>
<hr>
<h1 id="🏗️-inflow-chat-개발-회고-사내-문서-기반-ai-챗봇을-만들며">🏗️ Inflow Chat 개발 회고: 사내 문서 기반 AI 챗봇을 만들며</h1>
<h2 id="📌-프로젝트-개요">📌 <strong>프로젝트 개요</strong></h2>
<p>지난 한 달 동안, <strong>Rikkei</strong>에서 진행한 인턴십을 통해 <strong>사내 문서 기반 AI 챗봇</strong>인 <strong>Inflow Chat</strong>을 개발했다.<br>관리자가 <strong>PDF, DOCX, Weblink</strong>를 업로드하면 문서를 분석하고 벡터화하여 저장하고, 사용자는 챗봇을 통해 질문을 하면 <strong>벡터 DB(FAISS)</strong>를 활용하여 관련 문서를 찾아 <strong>LLM(Gemini API)</strong>를 통해 답변을 생성하는 구조다.  </p>
<p>🎯 <strong>기술 스택</strong>  </p>
<ul>
<li><strong>Backend:</strong> Flask  </li>
<li><strong>AI/LLM:</strong> LangChain, Gemini API  </li>
<li><strong>Vector DB:</strong> FAISS  </li>
<li><strong>Database:</strong> PostgreSQL (AWS RDS)  </li>
<li><strong>Container:</strong> Docker  </li>
</ul>
<p>💡 내 역할</p>
<ul>
<li>문서(웹 링크) 파싱 및 벡터 DB 저장 <code>(BeautifulSoup, FAISS, PostgreSQL)</code></li>
<li>사용자 질문을 벡터화하고 관련 문서를 검색하는 시스템 구축 <code>(LangChain, FAISS)</code></li>
<li>LLM(Gemini) 기반 응답 생성 및 프롬프트 최적화 <code>(프롬프트 엔지니어링)</code></li>
<li>사용자 채팅 기록(최근 10개 질문) 저장 및 문맥 유지 기능 추가 <code>(PostgreSQL, Flask)</code></li>
<li>벡터 DB 성능 최적화를 위한 청크 사이즈 실험 <code>(LangChain, similarity_search_with_score)</code></li>
<li>협업 환경 세팅 <code>(Docker 컨테이너화, GitHub 브랜치 전략 도입)</code></li>
</ul>
<hr>
<h1 id="🚀-인턴-회고---명확한-기획과-요구사항-그리고-협업의-가치">🚀 <strong>인턴 회고 - 명확한 기획과 요구사항, 그리고 협업의 가치</strong></h1>
<h2 id="🎯-애자일-스크럼-방식의-개발-경험"><strong>🎯 애자일 스크럼 방식의 개발 경험</strong></h2>
<p>이번 인턴십에서는 <strong>애자일 스크럼 방식</strong>으로 프로젝트를 진행했다.<br>PM과 PO가 체계적으로 Task를 관리해준 덕분에,<br>✅ <strong>내가 지금 무엇을 했고, 하고 있고, 할 것인지 명확하게 파악할 수 있었다.</strong>  </p>
<p>이전의 팀 프로젝트에서는 기획 단계부터 참여했다.  </p>
<ul>
<li>문제 상황을 분석하고, 페인포인트를 정의하고, 솔루션을 구상하는 과정에서부터 시작.  </li>
<li>그리고 그 기획에 맞춰 개발을 진행.  </li>
</ul>
<p>하지만 <strong>회사는 달랐다.</strong>  </p>
<ul>
<li>회사에서는 <strong>이미 기획과 요구사항이 명확하게 정의되어 있었다.</strong>  </li>
<li>이 문서를 바탕으로 시스템을 설계하고, 개발해야 했다.  </li>
</ul>
<p>📌 <strong>System Requirements (시스템 요구사항)</strong>  </p>
<ul>
<li>User Types, Authentication, Employee Interface, Admin Interface  </li>
</ul>
<p>📌 <strong>Technical Requirements (기술 요구사항)</strong>  </p>
<ul>
<li>Architecture, Deployment, Code Management, Security, Performance, Streaming  </li>
</ul>
<p><strong>이 문서를 분석하며, 필요한 기능을 도출하기 위해 Use Case를 고려하고 Use Diagram을 그렸다.</strong>  </p>
<hr>
<h2 id="📚-소프트웨어-설계---이론과-실무의-차이"><strong>📚 소프트웨어 설계 - 이론과 실무의 차이</strong></h2>
<p>💡 <strong>소프트웨어 설계 과목에서 배운 경험</strong>  </p>
<p>인턴을 오기 직전 학기에 <strong>소프트웨어 설계</strong> 과목을 수강했다.<br>정말 흥미로웠던 수업이었고, 커리어를 이쪽으로 키워가고 싶다는 생각이 들 정도로 재미있었다.  </p>
<p><strong>그때 진행한 기말 프로젝트:</strong>  </p>
<ul>
<li>특정 소프트웨어를 선택하여 <strong>Use Case 작성 → Use Diagram, Class Diagram, Sequence Diagram 설계.</strong>  </li>
<li>교수님을 5번 넘게 찾아가며 질문하고, 정말 미친듯이 열심히 공부했다.  </li>
<li>발표와 실습 모두 1등! 당연히 <strong>A+</strong>  </li>
</ul>
<p>하지만 과제를 하면서 가장 혼란스러웠던 것은 <strong>명확한 요구사항이 없었다는 것</strong>이었다.  </p>
<ul>
<li>내가 직접 기획한 프로젝트를 기반으로 설계를 하다 보니,  </li>
<li><strong>내가 생각하는 방향이 맞는지 확신이 없었다.</strong>  </li>
<li>프로젝트 초반에는 <strong>&quot;신체 리듬에 맞춘 게임&quot;</strong>이었지만, 끝에 가서는 <strong>&quot;맥박과 혈압 기반 게임&quot;</strong>이 되어버렸다.  </li>
</ul>
<p>이 경험을 통해 <strong>명확하지 않은 요구사항은 시스템 설계에서 혼동을 일으킬 수 있다는 점을 깨달았다.</strong>  </p>
<hr>
<h2 id="🛠️-명확한-요구사항이-주어진-환경에서의-개발"><strong>🛠️ 명확한 요구사항이 주어진 환경에서의 개발</strong></h2>
<p>인턴십에서는 <strong>이전의 팀 프로젝트와는 다르게, 명확한 기획과 요구사항이 있었다.</strong>  </p>
<ul>
<li><strong>기획 문서를 분석하면서 필요한 기능을 도출</strong>하고,  </li>
<li><strong>Use Case를 정리하고, Use Diagram을 작성</strong>하며 기능을 리스트업했다.  </li>
</ul>
<p>개인 프로젝트에서 <strong>혼자 기획부터 했던 경험</strong>과 비교하면, 너무나도 명확한 프로세스였다.  </p>
<p>📌 <strong>실제 경험한 User Validation 과정</strong>  </p>
<ul>
<li>설계한 기능과 Use Diagram을 <strong>멘토 및 PO와 함께 검토하며 Validation을 받았다.</strong>  </li>
<li><strong>소프트웨어 설계 과목에서 배운 개념을 실무에서 적용하는 순간이었다!</strong>  </li>
<li>배운 개념을 직접 활용한다는 것이 두근거렸고, 나의 설계가 긍정적인 평가를 받은 것도 기뻤다.  </li>
</ul>
<hr>
<h2 id="🤝-팀-미팅에서-회의의-목적을-상기하다"><strong>🤝 팀 미팅에서 회의의 목적을 상기하다</strong></h2>
<p>💡 <strong>회의에서 의견 차이가 발생했던 순간</strong>  </p>
<ul>
<li>우리는 <strong>Wire Frame과 User Flow를 검토하는 회의</strong>를 진행하고 있었다.  </li>
<li><strong>하지만 갑자기 팀원 중 한 명이 UI 디자인을 논의하기 시작했다.</strong>  <ul>
<li>버튼 모양은 Square vs Round?  </li>
<li>색상은 Red vs White?  </li>
</ul>
</li>
<li>이 회의의 목적과 다소 벗어난 논의였다.  </li>
</ul>
<p>나는 개발팀 소속이었기 때문에,<br><strong>UI 설계보다는 시스템 동작과 데이터 흐름을 확인하는 것이 목적이라고 생각했다.</strong><br>하지만 바로 의견을 제시하는 것이 아니라,<br>📌 <strong>먼저 PO에게 내 입장을 간략하게 전달했다.</strong><br>📌 <strong>그리고 UI를 논의하던 팀원에게 조심스럽게 &quot;회의의 목적을 다시 한번 생각해보자&quot;고 제안했다.</strong>  </p>
<p>📌 <strong>결과:</strong><br>✅ 팀원도 내 의견에 동의했고, <strong>회의가 원래 목적대로 진행되었다.</strong><br>✅ 상대방의 의견을 존중하면서도, <strong>회의 목적을 상기시키는 커뮤니케이션의 중요성을 배웠다.</strong><br>✅ <strong>상대방이 기분 나쁘지 않게 내 의견을 전달하는 방법을 고민하는 것이 중요하다.</strong>  </p>
<hr>
<h2 id="📂-체계적인-프로젝트-관리---wbs-gantt-chart-활용"><strong>📂 체계적인 프로젝트 관리 - WBS, Gantt Chart 활용</strong></h2>
<p>📌 <strong>전문적인 프로젝트 관리 경험이 가장 인상적이었다.</strong>  </p>
<ul>
<li>PO가 요구사항 명세서를 참고하여 <strong>WBS(작업 분류 구조)와 Gantt Chart(일정 관리 차트)를 생성.</strong>  </li>
</ul>
<p>✅ <strong>WBS (Work Breakdown Structure)</strong>  </p>
<ul>
<li>모듈별로 Use Case가 명세화되어 있어, <strong>한눈에 보기 편했다.</strong>  </li>
</ul>
<p>✅ <strong>Gantt Chart (간트 차트)</strong>  </p>
<ul>
<li><strong>각 Task의 진행 상태, 예상 소요 시간, 담당자(PIC), 진행 여부를 시각적으로 확인할 수 있었다.</strong>  </li>
</ul>
<p>📌 <strong>Trello와 Teams를 활용한 협업</strong>  </p>
<ul>
<li><strong>Trello 보드를 통해 ToDo / Doing / Done 상태를 쉽게 관리.</strong>  </li>
<li>혼동이 올 때마다 <strong>Gantt Chart와 Trello를 보면서 개발을 진행.</strong>  </li>
<li>다른 팀원의 진행 상황을 실시간으로 확인하며, <strong>이슈가 생길 경우 빠르게 도움 요청 가능.</strong>  </li>
</ul>
<p>이전 회사에서는 Jira를 사용했는데, 처음에는 <strong>칸반 보드나 백로그 개념이 어려웠다.</strong><br>이번 인턴십에서는 Trello를 활용하면서 <strong>프로젝트 관리를 한층 더 쉽게 이해할 수 있었다.</strong>  </p>
<p>📌 <strong>커뮤니케이션의 중요성</strong>  </p>
<ul>
<li>팀원 중 한 명이 <strong>자신의 Task 진행 상황을 명확하게 공유하지 않는 경우가 있었다.</strong>  </li>
<li>나는 <strong>PO가 진행 상황을 빠르게 파악할 수 있도록 Teams에 지속적으로 업데이트</strong>했다.  </li>
<li>PO가 굳이 내 자리까지 와서 물어보지 않아도, <strong>내가 진행하는 Task를 한눈에 볼 수 있도록 배려했다.</strong>  </li>
</ul>
<hr>
<h2 id="📢-스크럼--데일리-미팅을-통한-피드백"><strong>📢 스크럼 &amp; 데일리 미팅을 통한 피드백</strong></h2>
<ul>
<li><strong>매일 11시 데일리 미팅</strong>을 진행했다.  </li>
<li>자신이 <strong>어제 무엇을 했고, 오늘 무엇을 할 것인지 공유.</strong>  </li>
<li>멘토님들도 참여하여, <strong>날카로운 질의응답을 통해 피드백을 제공.</strong>  </li>
</ul>
<p>💡 <strong>&quot;왜?&quot;라는 질문의 중요성</strong>  </p>
<ul>
<li>개발을 하면서 단순히 기능을 구현하는 것이 아니라, <strong>왜 이렇게 설계했는지 설명할 수 있어야 한다.</strong>  </li>
<li>멘토님들의 질문에 답변하면서, <strong>내 코드와 설계에 대해 더욱 깊이 고민하게 되었다.</strong>  </li>
</ul>
<hr>
<h2 id="🛠️-자원이-제한된-환경에서-최적의-아키텍처-설계"><strong>🛠️ 자원이 제한된 환경에서 최적의 아키텍처 설계</strong></h2>
<p>📌 <strong>Local 환경에서 개발해야 한다는 제한 사항</strong>  </p>
<ul>
<li>나는 <strong>배포 및 클라우드 자동화에 관심이 많았지만, 초기 개발은 Local에서 진행해야 했다.</strong>  </li>
<li>자원이 부족한 환경에서 <strong>가장 최적화된 아키텍처를 고민해야 했다.</strong>  </li>
</ul>
<p>📌 <strong>AI 모델 선택</strong>  </p>
<ul>
<li>처음에는 <strong>경량화된 모델을 직접 활용하려 했지만, 리소스 부족으로 Gemini API를 선택.</strong>  </li>
</ul>
<p>📌 <strong>기술 스택 결정</strong>  </p>
<ul>
<li>나는 <strong>Java Spring Boot 개발자</strong>지만,  </li>
<li>팀원들의 개발 경험을 고려하여 <strong>Flask로 개발하는 것이 최선이라는 결론.</strong>  </li>
</ul>
<hr>
<h2 id="✨-배운-점--성장한-점"><strong>✨ 배운 점 &amp; 성장한 점</strong></h2>
<p>✅ <strong>명확한 요구사항이 있을 때 개발이 훨씬 효율적이다.</strong><br>✅ <strong>커뮤니케이션이 프로젝트의 성공을 결정한다.</strong><br>✅ <strong>데일리 미팅과 피드백을 통해 나의 사고력이 확장되었다.</strong><br>✅ <strong>제한된 리소스에서도 최적의 기술 선택을 해야 한다.</strong>  </p>
<hr>
<h1 id="성장한-점과-태도에-대한-회고">성장한 점과 태도에 대한 회고</h1>
<ul>
<li>도전을 두려워하지 않고 끊임없이 성장하려고 하는 자세</li>
</ul>
<h2 id="🚀-도전-ai-그리고-flask-postgresql">🚀 <strong>도전! AI 그리고 Flask, PostgreSQL</strong></h2>
<h3 id="💡-새로운-역할-새로운-도전"><strong>💡 새로운 역할, 새로운 도전</strong></h3>
<p>역할을 분배하기 위해 데일리 미팅에서 팀원들과 이야기를 나누었다.<br>그 자리에서 나는 <strong>&quot;도전을 두려워하지 않고 오히려 좋아한다.&quot;</strong><br>그리고 <strong>&quot;Challenging한 Task 속에서 성장할 수 있다면 그것이 더 좋다.&quot;</strong> 라고 말했다.  </p>
<p>AI를 다뤄본 적은 없었고, 이해도도 낮았다. 하지만 <strong>백엔드에서 AI를 구현하는 경험을 쌓고 싶었다.</strong><br>결과적으로 나는 <strong>AI 기능을 구현하는 백엔드 개발을 맡게 되었다.</strong><br>아직 모르는 것이 많았지만, 배워가면서 만들어 나가는 과정 자체가 나에게 큰 동기부여가 되었다.  </p>
<hr>
<h3 id="📌-공부는-셀프-스피드는-생명"><strong>📌 공부는 셀프, 스피드는 생명</strong></h3>
<p>우리는 <strong>애자일 스크럼 방식</strong>으로 프로젝트를 진행했다.<br>📌 <strong>매일 데일리 리포트 작성 &amp; 스크럼 미팅 진행</strong><br>📌 <strong>매주 금요일마다 스프린트에 맞춰 데모 발표</strong>  </p>
<p>AI 서버 역할을 맡은 이상, 매주 개발해야 하는 목록을 맞춰서 <strong>빠르게 개발</strong>하고 <strong>에러도 해결해야 했다.</strong><br>내가 모르면 <strong>스스로 공부해야 했다.</strong>  </p>
<p><strong>Flask도 처음인데 AI 서버까지 구현해야 한다니..</strong><br>그야말로 도전 그 자체였다.  </p>
<p>퇴근 후, <strong>서버 구현을 위한 AI 흐름을 이해하기 위해 논문을 읽고, 유튜브 강의를 보고, LangChain 공식 문서를 닳도록 읽었다.</strong><br>한 <strong>2~3일 동안 죽어라 공부하니까 머릿속으로 그림이 그려졌다.</strong><br><strong>퇴근하고 밥 먹으면서도 계속 공부한 보람이 있었다.</strong>  </p>
<p>☀️ 낮에는 빠르게 개발하고, 🌙 밤에는 공부하는 패턴을 반복했다.<br>이 과정 덕분에 <strong>결과물도 기간 내에 나왔고, 최적화 작업을 할 수 있는 시간까지 확보할 수 있었다.</strong>  </p>
<hr>
<h3 id="🔥-이번-인턴십에서의-성장과-배운-점"><strong>🔥 이번 인턴십에서의 성장과 배운 점</strong></h3>
<p>💡 <strong>AI 백엔드 개발자로서의 도전</strong>  </p>
<ul>
<li>새로운 기술을 접할 때 <strong>겁먹지 않고 적극적으로 배우려는 태도가 중요하다.</strong>  </li>
<li>AI를 처음 다뤘지만, <strong>몰입해서 공부하면 결국 길이 보인다.</strong>  </li>
</ul>
<p>💡 <strong>빠른 개발 &amp; 최적화의 균형</strong>  </p>
<ul>
<li>개발 속도도 중요하지만, <strong>결국에는 최적화까지 고려해야 한다.</strong>  </li>
<li>기간 내에 목표를 달성하는 것뿐만 아니라, <strong>더 나은 성능을 고민할 여유까지 확보하는 것이 중요하다.</strong>  </li>
</ul>
<p>💡 <strong>스스로 성장하는 법을 배운 경험</strong>  </p>
<ul>
<li><strong>책임감 있는 역할을 맡으면, 자연스럽게 성장하게 된다.</strong>  </li>
<li><strong>퇴근 후에도 스스로 공부하는 자세가 결국 나를 더 빠르게 성장시킨다.</strong>  </li>
</ul>
<hr>
<h3 id="✨-내-인턴십의-키워드-성장-그리고-도전"><strong>✨ 내 인턴십의 키워드: &quot;성장&quot; 그리고 &quot;도전&quot;</strong></h3>
<p>이번 인턴십을 돌아보면, 내 스스로의 키워드는 단연 <strong>&quot;성장&quot;과 &quot;도전&quot;</strong>이었다.<br>낯선 기술, 처음 다루는 도메인에서도 <strong>두려워하지 않고 부딪히며 성장했다.</strong>  </p>
<p>지금 돌아보면, <strong>Flask도 처음이었고, AI 백엔드도 처음이었지만, 결국 해냈다.</strong><br>그리고 <strong>그 과정에서 진짜 성장했다는 걸 느낀다.</strong>  </p>
<p>🚀 <strong>&quot;앞으로도 어떤 도전이 오더라도, 나는 성장할 준비가 되어 있다.&quot;</strong>  </p>
<hr>
<h1 id="🎯-인턴십을-마치며---느낀-점-정리"><strong>🎯 인턴십을 마치며 - 느낀 점 정리</strong></h1>
<h3 id="1️⃣-멘토님께-감사-🙏"><strong>1️⃣ 멘토님께 감사 🙏</strong></h3>
<ul>
<li><strong>매일 진행된 데일리 미팅과 데모 미팅을 통해 지속적으로 관리 &amp; 피드백을 주신 멘토님.</strong>  </li>
<li>단순한 피드백이 아니라, <strong>&quot;너는 더 Complete하게 할 수 있다. 더 잘할 수 있다.&quot;</strong> 라는 조언을 주시며 성장할 기회를 만들어 주셨다.  </li>
<li><strong>개발 속도가 빠르고, 커뮤니케이션 능력이 좋으며, 우선순위를 잘 정해서 일한다</strong>는 칭찬을 받을 수 있어 뿌듯했다.  </li>
<li>데모 미팅 때는 <strong>내가 생각하지 못했던 이슈들을 캐치해서 지적해주셨고, 이를 통해 한층 더 성장할 수 있었다.</strong>  </li>
</ul>
<hr>
<h3 id="2️⃣-아쉬운-점-🤔"><strong>2️⃣ 아쉬운 점 🤔</strong></h3>
<ul>
<li><p><strong>배포 &amp; 인프라 경험 부족</strong>  </p>
<ul>
<li>나는 자동화 배포나 클라우드 인프라에 관심이 많았지만, <strong>이번 프로젝트에서는 Local 개발이 중심이었다.</strong>  </li>
<li>실제로 AWS나 GCP를 활용한 배포까지 경험해볼 수 있었다면 더 좋았을 것 같다.  </li>
</ul>
</li>
<li><p><strong>AI 모델을 직접 사용하지 못한 점</strong>  </p>
<ul>
<li>처음에는 <strong>경량화된 AI 모델을 활용한 RAG 기반 시스템을 고려했지만, 리소스 문제로 외부 API(Gemini)를 사용해야 했다.</strong>  </li>
<li>직접 모델을 학습시키고 최적화하는 경험을 해보지 못한 것이 조금 아쉽다.  </li>
</ul>
</li>
</ul>
<hr>
<h3 id="3️⃣-도전하고-성장한-나-자신이-대견하다-💪"><strong>3️⃣ 도전하고 성장한 나 자신이 대견하다 💪</strong></h3>
<ul>
<li>처음 접하는 기술이 많았고, 도전적인 환경이었지만, <strong>두려워하지 않고 부딪히며 성장했다.</strong>  </li>
<li><strong>영어 실력도 많이 늘었다!</strong>  <ul>
<li>문서를 읽고 기술적인 내용을 이해하는 것이 훨씬 수월해졌다.  </li>
</ul>
</li>
<li><strong>무엇보다, 도전을 두려워하지 않는 내 자세가 너무 멋졌다고 생각한다.</strong>  <ul>
<li><strong>모르는 것을 배우기 위해 깊이 파고들었고, 빠르게 습득하여 결과를 만들어냈다.</strong>  </li>
<li>인턴 기간 동안, <strong>&quot;내가 생각보다 더 많은 것을 할 수 있구나&quot;</strong> 라는 자신감을 얻었다.  </li>
</ul>
</li>
</ul>
<hr>
<p>🚀 <strong>&quot;앞으로 어떤 도전이 와도, 나는 해낼 수 있다.&quot;</strong><br>🚀 <strong>&quot;이번 인턴십을 통해, 나는 한층 더 성장한 개발자가 되었다.&quot;</strong>  </p>
<hr>
]]></description>
        </item>
        <item>
            <title><![CDATA[🛠️ 협업에서 마주한 문제 & 해결 과정]]></title>
            <link>https://velog.io/@zedy_dev/%ED%98%91%EC%97%85%EC%97%90%EC%84%9C-%EB%A7%88%EC%A3%BC%ED%95%9C-%EB%AC%B8%EC%A0%9C-%ED%95%B4%EA%B2%B0-%EA%B3%BC%EC%A0%95</link>
            <guid>https://velog.io/@zedy_dev/%ED%98%91%EC%97%85%EC%97%90%EC%84%9C-%EB%A7%88%EC%A3%BC%ED%95%9C-%EB%AC%B8%EC%A0%9C-%ED%95%B4%EA%B2%B0-%EA%B3%BC%EC%A0%95</guid>
            <pubDate>Thu, 30 Jan 2025 04:45:26 GMT</pubDate>
            <description><![CDATA[<hr>
<h2 id="🐳-1-docker-컨테이너화를-도입한-이유--최적화-과정">🐳 <strong>1. Docker 컨테이너화를 도입한 이유 &amp; 최적화 과정</strong></h2>
<hr>
<h3 id="🛑-문제-상황"><strong>🛑 문제 상황</strong></h3>
<p>1️⃣ <strong>팀원 간 개발 환경이 일관되지 않음</strong>  </p>
<ul>
<li>나는 <strong>MacOS</strong>, 다른 팀원들은 <strong>Windows</strong> 환경에서 개발.  </li>
<li>Python 버전 차이, 패키지 설치 문제, OS 환경 차이로 인해 코드 실행 오류 발생.  </li>
</ul>
<p>2️⃣ <strong>로컬에서 잘 되던 코드가 다른 환경에서는 실행되지 않음</strong>  </p>
<ul>
<li>내 환경에서는 정상적으로 실행되지만, 다른 팀원이 실행할 때 <strong>환경 차이로 인한 오류 발생</strong>.  </li>
<li><code>pip install</code> 할 때 패키지 충돌 문제 발생.  </li>
</ul>
<p>3️⃣ <strong>배포 시 실행 환경을 맞추는 과정이 번거로움</strong>  </p>
<ul>
<li>서버 환경과 로컬 개발 환경이 다르다 보니, <strong>배포 후 실행 오류가 종종 발생</strong>.  </li>
<li>환경 변수를 다루는 방식도 OS마다 차이가 있어서 관리가 어려움.  </li>
</ul>
<hr>
<h3 id="✅-해결-방법---docker-컨테이너화-도입"><strong>✅ 해결 방법 - Docker 컨테이너화 도입</strong></h3>
<h4 id="1-docker를-사용해-모든-팀원이-동일한-환경에서-개발-가능하도록-설정"><strong>1) Docker를 사용해 모든 팀원이 동일한 환경에서 개발 가능하도록 설정</strong></h4>
<ul>
<li><strong>Dockerfile</strong>을 만들어서, <strong>어떤 환경에서도 동일하게 실행</strong>되도록 설정.  </li>
<li><code>Python</code>, <code>FAISS</code>, <code>PostgreSQL</code> 등 <strong>필요한 라이브러리를 Docker 환경에 통합</strong>하여 실행 환경 통일.  </li>
</ul>
<pre><code class="language-dockerfile"># 1. Python 환경을 기반으로 이미지 생성
FROM python:3.9

# 2. 작업 디렉토리 설정
WORKDIR /app

# 3. 필요한 패키지 설치
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt

# 4. 코드 복사 후 실행
COPY . .
CMD [&quot;python&quot;, &quot;app.py&quot;]</code></pre>
<p>📌 <strong>결과</strong><br>✅ <strong>어떤 환경에서도 동일한 개발 환경을 유지할 수 있음.</strong><br>✅ <strong>로컬과 서버의 실행 환경이 일치하므로 배포 오류 감소.</strong>  </p>
<h4 id="2-docker-compose로-전체-서비스-구성-자동화"><strong>2) Docker Compose로 전체 서비스 구성 자동화</strong></h4>
<ul>
<li><strong>PostgreSQL, FAISS, Flask 백엔드</strong> 등을 함께 실행할 수 있도록 <code>docker-compose.yml</code> 작성.  </li>
<li><strong>한 줄 명령어로 모든 서비스 실행 가능! (<code>docker-compose up -d</code>)</strong>  </li>
</ul>
<pre><code class="language-yaml">version: &#39;3.8&#39;
services:
  db:
    image: postgres
    restart: always
    environment:
      POSTGRES_USER: admin
      POSTGRES_PASSWORD: secret
      POSTGRES_DB: inflow_chat
    ports:
      - &quot;5432:5432&quot;

  backend:
    build: .
    depends_on:
      - db
    environment:
      DATABASE_URL: postgresql://admin:secret@db:5432/inflow_chat
    ports:
      - &quot;8000:8000&quot;</code></pre>
<p>📌 <strong>결과</strong><br>✅ <strong>데이터베이스 + 백엔드를 한 번에 실행할 수 있음.</strong><br>✅ <strong>로컬 환경에서도 배포 환경과 동일한 환경에서 실행 가능.</strong>  </p>
<hr>
<h3 id="🔥-docker-도입-후-얻은-이점--배운-점"><strong>🔥 Docker 도입 후 얻은 이점 &amp; 배운 점</strong></h3>
<p>✅ <strong>개발 환경 차이 해결 → &quot;내 환경에서는 잘 되는데?&quot; 문제 해결!</strong><br>✅ <strong>서버 배포가 간단해짐 → 한 줄로 실행 가능 (<code>docker-compose up -d</code>)</strong><br>✅ <strong>협업이 쉬워짐 → 모든 팀원이 동일한 환경에서 개발 가능.</strong><br>✅ <strong>배포 후 오류 감소 → 개발 환경과 배포 환경이 동일하므로 실행 오류 최소화.</strong>  </p>
<hr>
<h3 id="📌-최종-정리-tldr">📌 <strong>최종 정리 (TL;DR)</strong></h3>
<ul>
<li>기존에는 <strong>팀원 간 환경 차이, 배포 시 실행 오류, 패키지 충돌 문제</strong>가 있었음.  </li>
<li>✅ <strong>Docker 컨테이너화를 통해 개발 &amp; 배포 환경을 통일</strong>.  </li>
<li>✅ <strong>Docker Compose를 활용해 데이터베이스 &amp; 백엔드 실행 자동화</strong>.  </li>
<li>✅ <strong>결과적으로 개발 속도 &amp; 협업 효율이 향상됨!</strong>  </li>
<li>🚀 <strong>이제 &quot;내 환경에서는 잘 되는데?&quot; 문제는 없다!</strong>  </li>
</ul>
<hr>
<h2 id="📄-2-api-문서화---swagger-도입--개발-경험-개선">📄 <strong>2. API 문서화 - Swagger 도입 &amp; 개발 경험 개선</strong></h2>
<hr>
<h3 id="🛑-문제-상황-1"><strong>🛑 문제 상황</strong></h3>
<p>1️⃣ <strong>Postman을 사용하여 API 테스트 진행했으나, 협업 시 불편함 발생</strong>  </p>
<ul>
<li>프론트엔드 개발자들이 API 명세를 보려면 Postman 공유 링크를 받아야 함.  </li>
<li>Postman에서 개별적으로 API를 실행하고 확인해야 해서 <strong>한눈에 API 구조를 보기 어려움</strong>.  </li>
</ul>
<p>2️⃣ <strong>API 개발 진행 상황을 직관적으로 볼 수 있는 방법이 필요함</strong>  </p>
<ul>
<li>Postman으로 테스트는 가능하지만, API가 늘어날수록 진행 상황을 한눈에 보기 어려웠음.  </li>
<li><strong>자동화된 문서화 도구가 필요하다고 판단!</strong>  </li>
</ul>
<p>3️⃣ <strong>Flask에서 Swagger(FastAPI처럼 기본 지원 X)를 사용하기 위해 리팩토링 필요</strong>  </p>
<ul>
<li>기존 Flask 프로젝트에서 <strong>Blueprint 기반 개발</strong>을 진행하고 있었음.  </li>
<li>하지만 <strong>Swagger를 적용하려면 Flask-RESTX의 Namespace 구조로 변경해야 함.</strong>  </li>
</ul>
<hr>
<h3 id="✅-해결-방법---swagger-도입-및-api-리팩토링"><strong>✅ 해결 방법 - Swagger 도입 및 API 리팩토링</strong></h3>
<h4 id="1-swagger-flask-restx-적용"><strong>1) Swagger (Flask-RESTX) 적용</strong></h4>
<p>💡 <strong>Swagger를 사용하면 API 문서가 자동으로 생성되며, 개발자들이 바로 API를 실행할 수 있음!</strong>  </p>
<ul>
<li><strong>Swagger를 통해 API 문서화 진행 → 프론트엔드 개발자들과의 협업 편리해짐.</strong>  </li>
<li>기존 <strong>Postman 공유 방식 대신, Swagger UI에서 실시간으로 API 확인 가능.</strong>  </li>
</ul>
<p>📌 <strong>결과:</strong><br>✅ <strong>API 실행과 동시에 Swagger UI에서 자동으로 문서가 생성됨.</strong><br>✅ <strong>프론트엔드 개발자가 API 테스트를 Swagger에서 직접 실행 가능!</strong>  </p>
<h4 id="2-기존-blueprint-방식-→-namespace-방식으로-리팩토링"><strong>2) 기존 Blueprint 방식 → Namespace 방식으로 리팩토링</strong></h4>
<p>💡 <strong>기존 Blueprint 방식에서는 Swagger 문서를 자동 생성하기 어려웠음.</strong><br>📌 <strong>그래서 API를 Flask-RESTX의 Namespace 방식으로 변경!</strong>  </p>
<p>🔄 <strong>리팩토링 과정에서 얻은 이점:</strong>  </p>
<ul>
<li>✅ <strong>코드를 더 모듈화하고 API별 역할을 명확하게 분리할 수 있었음.</strong>  </li>
<li>✅ API 문서가 자동 생성되면서 <strong>&quot;이 API는 어디에 쓰는 거지?&quot;라는 질문이 사라짐.</strong>  </li>
<li>✅ <strong>코드를 다시 한번 점검할 기회</strong>가 되어, 불필요한 엔드포인트나 중복 코드를 정리할 수 있었음.  </li>
</ul>
<hr>
<h3 id="🛠️-swagger-도입-후-얻은-이점"><strong>🛠️ Swagger 도입 후 얻은 이점</strong></h3>
<p>📌 <strong>API 협업이 훨씬 쉬워짐!</strong>  </p>
<ul>
<li>✅ <strong>프론트엔드 개발자들이 API 문서를 따로 요청할 필요 없이 Swagger에서 바로 확인 가능.</strong>  </li>
<li>✅ <strong>API 요청을 직접 테스트할 수 있어 프론트-백엔드 간의 소통이 원활해짐.</strong>  </li>
</ul>
<p>📌 <strong>개발 진행 상황을 한눈에 볼 수 있음!</strong>  </p>
<ul>
<li>✅ Postman에서는 <strong>각 API를 개별적으로 실행해야 하지만,</strong>  </li>
<li>✅ <strong>Swagger UI에서는 모든 API가 정리되어 있어 개발 진행 상황을 한눈에 파악 가능!</strong>  </li>
</ul>
<p>📌 <strong>리팩토링을 통한 코드 개선 효과</strong>  </p>
<ul>
<li>✅ 기존 Flask Blueprint 방식에서 Flask-RESTX Namespace 방식으로 변경하며 <strong>코드 구조가 더 명확해짐</strong>.  </li>
<li>✅ 기존 API 설계 방식에 대해 <strong>한 번 더 고민할 기회가 되었음.</strong>  </li>
</ul>
<hr>
<h3 id="🔥-swagger-도입-후-배운-점--느낀-점"><strong>🔥 Swagger 도입 후 배운 점 &amp; 느낀 점</strong></h3>
<p>✅ <strong>Swagger는 API 협업을 위한 필수 도구!</strong><br>✅ <strong>Postman도 유용하지만, 문서화 &amp; 협업을 위해서는 Swagger가 더 강력함.</strong><br>✅ <strong>Flask-RESTX로 변경하면서 코드 모듈화 및 API 문서 자동화를 함께 진행할 수 있었음.</strong><br>✅ <strong>리팩토링 과정에서 불필요한 코드 정리를 하면서, API 설계에 대한 고민을 더 깊이 할 수 있었음.</strong>  </p>
<hr>
<h3 id="📌-최종-정리-tldr-1">📌 <strong>최종 정리 (TL;DR)</strong></h3>
<ul>
<li>기존 Postman 방식 → <strong>Swagger(FastAPI 스타일) 문서화 도입!</strong>  </li>
<li>✅ <strong>API 문서를 자동 생성 &amp; 실시간 테스트 가능.</strong>  </li>
<li>✅ <strong>프론트엔드 개발자들과 협업이 더 쉬워짐.</strong>  </li>
<li>✅ <strong>API 설계를 다시 점검하면서, 코드 리팩토링도 진행!</strong>  </li>
<li>🚀 <strong>결과적으로 개발 효율 + 협업 편의성이 대폭 향상됨!</strong>  </li>
</ul>
<hr>
<h2 id="🚀-3-git-브랜치-전략-도입---협업-효율성-개선">🚀 <strong>3. Git 브랜치 전략 도입 - 협업 효율성 개선</strong></h2>
<hr>
<h3 id="🛑-문제-상황-2"><strong>🛑 문제 상황</strong></h3>
<p>1️⃣ <strong>팀원 간 Git 브랜치 전략이 없어서 코드 충돌 발생</strong>  </p>
<ul>
<li>나는 GitHub을 활용한 <strong>브랜치 전략 (Issue → Feature Branch → PR → Review → Merge)</strong>에 익숙했음.  </li>
<li>하지만 팀원들은 개인 프로젝트에서만 개발해왔기 때문에, 협업 시 Git 규칙을 따르는 것이 익숙하지 않음.  </li>
</ul>
<p>2️⃣ <strong>브랜치 관리 미숙으로 인해 강제 Merge 발생 → 코드 충돌 &amp; 버그 증가</strong>  </p>
<ul>
<li>일부 개발자가 <code>main</code> 브랜치에서 직접 개발 후 <strong>강제 Merge</strong> 진행 → 코드가 꼬이고 충돌 발생.  </li>
<li>코드 리뷰 없이 Merge하면서 <strong>불안정한 코드가 main에 포함되는 문제</strong>가 발생.  </li>
</ul>
<p>3️⃣ <strong>브랜치가 꼬여서 최악의 경우 레포를 새로 생성해야 했던 상황</strong>  </p>
<ul>
<li>Git 규칙을 따르지 않는 개발자가 많아지면서 브랜치가 관리되지 않음.  </li>
<li><strong>브랜치 이력 복잡화 + 충돌 해결 어려움 → 결국 레포를 삭제하고 새로 생성.</strong>  </li>
</ul>
<hr>
<h3 id="✅-해결-방법---git-브랜치-전략-도입-및-규칙-설정"><strong>✅ 해결 방법 - Git 브랜치 전략 도입 및 규칙 설정</strong></h3>
<p>💡 <strong>1) 협업을 위한 Git 브랜치 전략을 도입 (Feature Branch Workflow 적용)</strong>  </p>
<ul>
<li><strong>GitHub Issue 생성</strong> → Issue 번호를 기반으로 Feature 브랜치 생성.  </li>
<li>개발 완료 후 <strong>PR을 생성하여 코드 리뷰 후 Merge 진행.</strong>  </li>
</ul>
<p>📌 <strong>Git 브랜치 전략</strong>  </p>
<pre><code class="language-plaintext">- main (배포 브랜치)
- develop (개발 브랜치)
  ├── feature/{issue-number}-feature-name (기능 개발 브랜치)
  ├── bugfix/{issue-number}-bug-description (버그 수정 브랜치)</code></pre>
<p>📌 <strong>예제 브랜치 관리 방법</strong>  </p>
<pre><code class="language-bash"># 1. 새로운 기능 개발 시
git checkout -b feature/12-add-auth-system

# 2. 개발 완료 후 커밋 &amp; Push
git commit -m &quot;Add authentication system (#12)&quot;
git push origin feature/12-add-auth-system

# 3. PR 생성 후 코드 리뷰 요청
# 4. 리뷰 완료 후 Merge &amp; 브랜치 삭제</code></pre>
<p>💡 <strong>2) 강제 Merge 방지를 위해 Git 규칙 수립</strong><br>✅ <strong>PR 없이 main에 직접 Push 금지</strong> (Branch Protection 설정)<br>✅ <strong>모든 PR은 최소 1명 이상의 코드 리뷰 후 Merge</strong><br>✅ <strong>Merge 전에 반드시 <code>develop</code> 브랜치에서 Pull 후 Conflict 해결</strong>  </p>
<p>📌 <strong>Branch Protection 설정 예시 (GitHub 설정)</strong>  </p>
<ul>
<li><code>main</code> 브랜치에 직접 Push 불가능하도록 설정.  </li>
<li><strong>PR을 통해서만 Merge 가능하도록 제한.</strong>  </li>
<li>최소 1명 이상의 코드 리뷰를 필수로 설정.  </li>
</ul>
<p>💡 <strong>3) Git 커밋 메시지 및 브랜치 네이밍 규칙 통일</strong><br>✅ <strong>Issue 번호 기반 브랜치 네이밍</strong><br>✅ <strong>커밋 메시지 규칙 설정</strong> → <code>feat:</code>, <code>fix:</code>, <code>docs:</code>, <code>refactor:</code> 등의 Prefix 사용  </p>
<p>📌 <strong>예제</strong>  </p>
<pre><code class="language-plaintext">feat: Add authentication system (#12)
fix: Resolve login bug (#23)
docs: Update API documentation (#30)</code></pre>
<hr>
<h3 id="🛠️-브랜치-전략-도입-후-얻은-이점"><strong>🛠️ 브랜치 전략 도입 후 얻은 이점</strong></h3>
<p>📌 <strong>협업이 훨씬 쉬워짐!</strong>  </p>
<ul>
<li>✅ <strong>각 기능별 브랜치가 명확해져 코드 관리가 체계화됨.</strong>  </li>
<li>✅ <strong>Git 브랜치 이력이 깨끗해지고, 코드 충돌이 감소.</strong>  </li>
<li>✅ <strong>레포를 날리는 최악의 상황을 방지할 수 있었음.</strong>  </li>
</ul>
<p>📌 <strong>코드 품질 개선!</strong>  </p>
<ul>
<li>✅ <strong>PR을 통한 코드 리뷰 과정이 필수화됨 → 코드 품질 향상.</strong>  </li>
<li>✅ <strong>강제 Merge 방지를 통해 코드 안정성 유지.</strong>  </li>
</ul>
<p>📌 <strong>팀원들의 Git 사용 능력 향상</strong>  </p>
<ul>
<li>✅ GitHub 활용 능력이 부족했던 개발자들도 Git 브랜치 전략을 따르면서 협업 역량 상승.  </li>
<li>✅ Git 규칙을 지키지 않으면 협업이 불가능하다는 점을 인식하게 됨.  </li>
</ul>
<hr>
<h3 id="🔥-git-전략-도입-후-배운-점--느낀-점"><strong>🔥 Git 전략 도입 후 배운 점 &amp; 느낀 점</strong></h3>
<p>✅ <strong>Git 브랜치 전략이 없으면 협업이 불가능하다!</strong><br>✅ <strong>혼자 개발할 때는 필요 없지만, 팀 프로젝트에서는 Git 전략이 필수다.</strong><br>✅ <strong>PR을 통한 코드 리뷰 과정이 코드 품질을 크게 향상시킨다.</strong><br>✅ <strong>강제 Merge는 최악의 결과를 초래할 수 있다 → 반드시 브랜치 보호 설정이 필요하다.</strong><br>✅ <strong>레포를 새로 파는 일은 없어야 한다 → Git 관리가 곧 협업의 기본이다.</strong>  </p>
<hr>
<h3 id="📌-최종-정리-tldr-2">📌 <strong>최종 정리 (TL;DR)</strong></h3>
<ul>
<li>기존에는 <strong>Git 브랜치 전략이 없어서 강제 Merge, 코드 충돌, 협업 문제 발생.</strong>  </li>
<li>✅ <strong>Feature Branch Workflow 도입 → 브랜치 구조 및 Merge 프로세스 정립.</strong>  </li>
<li>✅ <strong>PR을 통한 코드 리뷰 필수화 → 코드 품질 향상.</strong>  </li>
<li>✅ <strong>Branch Protection 설정 → 강제 Merge 방지.</strong>  </li>
<li>🚀 <strong>결과적으로 협업 효율이 대폭 향상되었고, 안정적인 Git 관리가 가능해짐!</strong>  </li>
</ul>
<hr>
]]></description>
        </item>
        <item>
            <title><![CDATA[💡 개발하며 마주한 문제들 & 해결 과정]]></title>
            <link>https://velog.io/@zedy_dev/%EA%B0%9C%EB%B0%9C%ED%95%98%EB%A9%B0-%EB%A7%88%EC%A3%BC%ED%95%9C-%EB%AC%B8%EC%A0%9C%EB%93%A4-%ED%95%B4%EA%B2%B0-%EA%B3%BC%EC%A0%95</link>
            <guid>https://velog.io/@zedy_dev/%EA%B0%9C%EB%B0%9C%ED%95%98%EB%A9%B0-%EB%A7%88%EC%A3%BC%ED%95%9C-%EB%AC%B8%EC%A0%9C%EB%93%A4-%ED%95%B4%EA%B2%B0-%EA%B3%BC%EC%A0%95</guid>
            <pubDate>Thu, 30 Jan 2025 04:41:48 GMT</pubDate>
            <description><![CDATA[<hr>
<h2 id="🧩-1-벡터-db-최적화---chunk-size--overlap-설정-실험">🧩 <strong>1. 벡터 DB 최적화 - Chunk Size &amp; Overlap 설정 실험</strong></h2>
<hr>
<h3 id="🛑-문제-상황"><strong>🛑 문제 상황</strong></h3>
<ul>
<li>문서를 벡터화해서 FAISS에 저장할 때,<br>→ <strong>청크 크기(Chunk Size)와 오버랩 크기(Overlap Size)를 어떻게 설정해야 하는지 고민</strong>  </li>
<li>너무 크면 검색이 느려지고,  </li>
<li>너무 작으면 문맥이 사라져 <strong>엉뚱한 문서가 검색</strong>됨.  </li>
<li>이 문제를 해결하기 위해 <strong>다양한 청크 및 오버랩 크기로 테스트 진행!</strong>  </li>
</ul>
<hr>
<h3 id="✅-해결-방법---실험을-통한-최적의-chunk-size-찾기"><strong>✅ 해결 방법 - 실험을 통한 최적의 Chunk Size 찾기</strong></h3>
<p>📌 <strong>LangChain의 <code>RecursiveCharacterTextSplitter</code>를 사용하여 여러 조합 테스트</strong>  </p>
<pre><code class="language-python">test_cases = [
    {&quot;chunk_size&quot;: 200, &quot;chunk_overlap&quot;: 50, &quot;title&quot;: &quot;Test Doc Case 1&quot;, &quot;db_path&quot;: &quot;test_faiss_db_case1&quot;},
    {&quot;chunk_size&quot;: 300, &quot;chunk_overlap&quot;: 100, &quot;title&quot;: &quot;Test Doc Case 2&quot;, &quot;db_path&quot;: &quot;test_faiss_db_case2&quot;},
    {&quot;chunk_size&quot;: 500, &quot;chunk_overlap&quot;: 200, &quot;title&quot;: &quot;Test Doc Case 3&quot;, &quot;db_path&quot;: &quot;test_faiss_db_case3&quot;},
    {&quot;chunk_size&quot;: 400, &quot;chunk_overlap&quot;: 150, &quot;title&quot;: &quot;Test Doc Case 4&quot;, &quot;db_path&quot;: &quot;test_faiss_db_case4&quot;},
]</code></pre>
<p>💡 <strong>실험 과정</strong>  </p>
<ol>
<li>각 케이스별로 문서를 임베딩하여 FAISS DB에 저장.  </li>
<li>동일한 질문을 입력하고, LangChain의 <code>similarity_search_with_score</code>를 사용해 검색된 문서의 유사도 점수 비교.  </li>
<li>검색된 문서가 원래 질문과 얼마나 관련이 있는지 정성적으로 분석.  </li>
</ol>
<p>💡 <strong>테스트 결과</strong>  </p>
<table>
<thead>
<tr>
<th align="center">Chuck Size</th>
<th align="center">Overlap</th>
<th align="center">검색 정확도</th>
<th align="center">검색 속도</th>
<th align="center">문맥 유지</th>
</tr>
</thead>
<tbody><tr>
<td align="center">200</td>
<td align="center">50</td>
<td align="center">❌ 낮음</td>
<td align="center">✅ 빠름</td>
<td align="center">❌ 문맥 끊김</td>
</tr>
<tr>
<td align="center">300</td>
<td align="center">100</td>
<td align="center">✅ 높음</td>
<td align="center">✅ 빠름</td>
<td align="center">✅ 문맥 유지</td>
</tr>
<tr>
<td align="center">400</td>
<td align="center">150</td>
<td align="center">⭕ 보통</td>
<td align="center">⭕ 중간</td>
<td align="center">✅ 문맥 유지</td>
</tr>
<tr>
<td align="center">500</td>
<td align="center">200</td>
<td align="center">✅ 높음</td>
<td align="center">❌ 느림</td>
<td align="center">✅ 문맥 유지</td>
</tr>
</tbody></table>
<p>📌 <strong>결과적으로, <code>chunk_size=300</code>, <code>chunk_overlap=100</code>이 가장 최적의 값</strong>임을 발견!  </p>
<hr>
<h3 id="🔥-실험-후-느낀-점--검색-성능-개선-효과"><strong>🔥 실험 후 느낀 점 &amp; 검색 성능 개선 효과</strong></h3>
<p>✅ <strong>Chunk Size &amp; Overlap이 검색 성능에 얼마나 중요한지 뼈저리게 깨달음.</strong><br>✅ 처음에는 <code>500, 200</code>이 좋아 보였지만, <strong>속도가 너무 느려 실사용에 부적합</strong>.  
✅ 최적의 <code>300, 100</code> 설정 후, 검색 성능이 대폭 개선됨 → <strong>정확도 상승 + 빠른 검색 속도 확보!</strong>  </p>
<p>💡 <strong>이 실험을 통해 얻은 인사이트</strong>  </p>
<ul>
<li><strong>너무 작은 청크는 문맥을 잃게 만들고, 너무 큰 청크는 검색 성능을 저하시킨다.</strong>  </li>
<li><strong>Chunk Size와 Overlap은 도메인과 데이터 특성에 맞게 실험을 통해 최적값을 찾아야 한다.</strong>  </li>
<li>단순히 <code>top-k</code> 검색이 아니라, <strong>적절한 청크 사이즈 조정이 LLM 응답 품질에도 큰 영향을 준다.</strong>  </li>
</ul>
<hr>
<h3 id="📌-배운-점">📌 <strong>배운 점</strong></h3>
<p>✅ <strong>임베딩을 할 때 적절한 Chunk Size를 선택하는 것이 검색 성능의 핵심이다!</strong><br>✅ <strong>검색 성능을 높이기 위해 무조건 큰 청크를 선택하는 것이 정답이 아니다.</strong><br>✅ <strong>실제 데이터셋에 맞는 최적의 설정을 찾기 위해서는 여러 테스트 케이스를 만들어 실험하는 과정이 필수다.</strong><br>✅ <strong>LangChain의 <code>similarity_search_with_score</code>는 검색된 문서의 연관성을 확인하는 데 매우 유용하다.</strong>  </p>
<hr>
<h2 id="🛠️-2-retriever-최적화---문서-적합도-필터링--최적-검색-전략">🛠️ <strong>2. Retriever 최적화 - 문서 적합도 필터링 &amp; 최적 검색 전략</strong></h2>
<hr>
<h3 id="🛑-문제-상황-1"><strong>🛑 문제 상황</strong></h3>
<ul>
<li>기존 Retriever 로직에서는 관련된 문서 <strong>10개를 검색하여 모두 참고</strong>.  </li>
<li>하지만, 관련성이 낮은 문서도 포함되면서 <strong>LLM 응답의 품질이 저하됨</strong>.  </li>
<li>🔍 <strong>문제점</strong>: 의미 없는 문서까지 포함되면서 <strong>불필요한 정보로 인해 부정확한 답변이 생성</strong>됨.  </li>
</ul>
<hr>
<h3 id="✅-해결-방법---검색된-문서의-적합도-필터링"><strong>✅ 해결 방법 - 검색된 문서의 적합도 필터링</strong></h3>
<p><strong>1) 문서 적합도 판단 로직 추가</strong><br><strong>💡 적용한 최적화 기법</strong><br>1️⃣ 검색된 문서들의 <code>score 값</code>을 <strong>평균</strong> 내고, 최고점과 비교.<br>2️⃣ <strong>최소 3개의 문서가 참고되도록 설정</strong>하고,  </p>
<ul>
<li><strong>상위 30% 이상의 문서를 1순위로 참고</strong>.  </li>
<li><strong>나머지 문서 중, 평균보다 현저히 낮은 문서는 필터링</strong>.  
3️⃣ 평균 이상의 점수를 가진 문서를 <strong>우선적으로 참고하여 응답 생성</strong>.  </li>
</ul>
<pre><code class="language-python">def filter_documents(documents, min_docs=3, top_percentile=0.3):
    # 문서들의 점수를 정렬
    documents.sort(key=lambda x: x[&quot;score&quot;], reverse=True)

    # 최고점과 평균 점수 계산
    max_score = documents[0][&quot;score&quot;]
    avg_score = sum([doc[&quot;score&quot;] for doc in documents]) / len(documents)

    # 상위 30% 문서를 우선 선정
    top_n = max(int(len(documents) * top_percentile), min_docs)
    selected_docs = [doc for doc in documents[:top_n]]

    # 평균보다 낮은 점수를 가진 문서 필터링
    final_docs = [doc for doc in selected_docs if doc[&quot;score&quot;] &gt;= avg_score]

    return final_docs</code></pre>
<p>📌 <strong>결과적으로, 의미 있는 문서만 필터링하여 LLM이 보다 정확한 답변을 생성하도록 개선!</strong>  </p>
<hr>
<h3 id="🛠️-실험-방법--최적화-과정"><strong>🛠️ 실험 방법 &amp; 최적화 과정</strong></h3>
<p>📌 <strong>벡터 DB는 직접 확인할 수 없어서 테스트 신뢰도를 확보하는 것이 중요했다.</strong><br>💡 <strong>테스트 방법:</strong><br>1️⃣ <strong>웹 문서를 임베딩한 후, 세부적인 질문을 입력하여 관련 문서 검색 테스트</strong>.  
2️⃣ 검색된 문서들이 어떻게 선택되는지 <strong>로그를 찍어 확인</strong>하며 최적화 진행.<br>3️⃣ 여러 Threshold 설정을 실험하여, 적합한 문서 필터링 기준을 찾음.  </p>
<p>📌 <strong>실험 데이터 예시 (로그 분석 결과)</strong>  </p>
<table>
<thead>
<tr>
<th align="center">문서</th>
<th align="center">Score</th>
<th align="center">참고 여부</th>
</tr>
</thead>
<tbody><tr>
<td align="center">문서 A</td>
<td align="center">0.92</td>
<td align="center">✅ 사용</td>
</tr>
<tr>
<td align="center">문서 B</td>
<td align="center">0.88</td>
<td align="center">✅ 사용</td>
</tr>
<tr>
<td align="center">문서 C</td>
<td align="center">0.85</td>
<td align="center">✅ 사용</td>
</tr>
<tr>
<td align="center">문서 D</td>
<td align="center">0.72</td>
<td align="center">❌ 필터링됨 (평균 이하)</td>
</tr>
<tr>
<td align="center">문서 E</td>
<td align="center">0.70</td>
<td align="center">❌ 필터링됨 (평균 이하)</td>
</tr>
</tbody></table>
<p>📌 <strong>결과:</strong>  </p>
<ul>
<li><strong>LLM이 보다 적절한 문서만 참고하도록 개선되면서 응답 품질 향상!</strong>  </li>
<li><strong>불필요한 문서를 제거하여 응답 속도도 최적화됨.</strong>  </li>
</ul>
<hr>
<h3 id="🔥-실험-후-느낀-점--최적화-과정에서의-고민"><strong>🔥 실험 후 느낀 점 &amp; 최적화 과정에서의 고민</strong></h3>
<p>✅ <strong>벡터 DB는 직접 보이지 않으므로, 로그를 찍어 확인하는 과정이 필수적이다.</strong><br>✅ <strong>수학적인 접근이 중요하다 – Score 기반 필터링 로직을 적용하면서 적절한 기준을 찾는 것이 핵심이었다.</strong><br>✅ <strong>Threshold 설정은 단순한 정적 값이 아니라, 동적으로 조절될 필요가 있다.</strong><br>✅ <strong>최적화 과정은 지속적으로 이루어졌으며, 데모 때까지 고민이 이어졌다.</strong><br>✅ <strong>결과적으로 데모에는 반영되지 못했지만, AI 검색 최적화에 대한 깊은 고민을 경험할 수 있었다.</strong>  </p>
<hr>
<h3 id="📌-최종-정리-tldr">📌 <strong>최종 정리 (TL;DR)</strong></h3>
<ul>
<li>기존 Retriever는 <strong>관련성이 낮은 문서까지 포함하는 문제</strong>가 있었음.  </li>
<li><strong>Score를 기반으로 문서 필터링 로직을 추가하여 불필요한 문서를 제거</strong>.  </li>
<li><strong>상위 30% 문서를 우선적으로 참고하고, 평균 이하 점수를 가진 문서는 필터링</strong>.  </li>
<li><strong>실험을 통해 검색된 문서를 로그로 분석하며 최적화 진행</strong>.  </li>
<li>🚀 <strong>LLM의 응답 품질 &amp; 검색 성능이 크게 향상됨!</strong>  </li>
</ul>
<hr>
<h2 id="🎯-3-프롬프트-엔지니어링-최적화---ai-응답-품질-향상-실험">🎯 <strong>3. 프롬프트 엔지니어링 최적화 - AI 응답 품질 향상 실험</strong></h2>
<hr>
<h3 id="🛑-문제-상황-2"><strong>🛑 문제 상황</strong></h3>
<ul>
<li>기존 프롬프트에서는 <strong>사용자의 대화 맥락을 유지하고, 지속적인 문맥을 제공하도록 설정</strong>했음.  </li>
<li>하지만, 예상과 다르게 챗봇이 가끔 <strong>이상한 답을 하거나 맥락을 유지하지 못하는 문제 발생.</strong>  </li>
<li>💡 <strong>문제의 원인:</strong>  <ol>
<li><strong>프롬프트가 너무 일반적</strong>이어서 LLM이 정확한 답변을 생성하는 데 어려움을 겪음.  </li>
<li><strong>지나치게 자유로운 응답 스타일</strong> → 가이드라인이 부족하여 AI의 응답 품질이 일정하지 않음.  </li>
<li><strong>대화 맥락 유지 실패</strong> → 과거의 10개 대화를 기억해야 하지만, 효과적으로 활용되지 않음.  </li>
</ol>
</li>
</ul>
<hr>
<h3 id="✅-해결-방법---프롬프트-개선-및-실험-과정"><strong>✅ 해결 방법 - 프롬프트 개선 및 실험 과정</strong></h3>
<p>💡 <strong>개선 방향</strong><br>1️⃣ <strong>AI의 역할을 더 명확하게 정의</strong>  </p>
<ul>
<li>기존: &quot;You are a company service chatbot.&quot;  </li>
<li>개선 후: <strong>&quot;You are a friendly and knowledgeable company service assistant chatbot.&quot;</strong>  </li>
<li>→ AI의 <strong>목표와 성격을 구체적으로 정의하여</strong> 일관된 응답을 유도.  </li>
</ul>
<p>2️⃣ <strong>대화 흐름을 제어할 수 있도록 지침 추가</strong>  </p>
<ul>
<li>단순히 <strong>문서 기반으로 답변을 생성하라고 요청하는 것</strong>만으로는 부족했음.  </li>
<li><strong>각 상황별로 AI가 어떻게 반응해야 하는지 세부적으로 지침을 추가!</strong>  </li>
<li>✅ <strong>사용자의 질문이 문서에 있는 정보와 일치하면 정확한 답변 제공.</strong>  </li>
<li>✅ <strong>정보가 없을 경우, 적절한 피드백 제공 (&quot;I&#39;m sorry, I couldn’t find that information…&quot;).</strong>  </li>
<li>✅ <strong>불필요한 인사말 방지 및 문장의 톤 조절 (친절하지만 간결하게).</strong>  </li>
</ul>
<p>3️⃣ <strong>프롬프트 내부에 대화 기억 유지 로직 반영</strong>  </p>
<ul>
<li>기존 프롬프트: &quot;Retain at least 10 message logs.&quot;  </li>
<li>개선 후: <strong>&quot;Directly answer user questions based on the content from the official documents and the last 10 interactions.&quot;</strong>  </li>
<li>→ AI가 대화 기록을 <strong>어떻게 활용해야 하는지 명확하게 설명하여 문맥 유지 강화!</strong>  </li>
</ul>
<hr>
<h3 id="🛠️-실험-과정--비교-테스트"><strong>🛠️ 실험 과정 &amp; 비교 테스트</strong></h3>
<p>📌 <strong>프롬프트 실험을 위한 평가 방법</strong>  </p>
<ul>
<li>동일한 질문을 여러 번 입력하고, 개선 전/후 응답을 비교.  </li>
<li>AI가 <strong>문맥을 유지하는 정도, 정확한 정보를 제공하는지, 답변 스타일이 일관적인지</strong> 테스트.  </li>
</ul>
<p>📌 <strong>테스트 결과 비교</strong>  </p>
<table>
<thead>
<tr>
<th>테스트</th>
<th>기존 프롬프트 응답</th>
<th>개선된 프롬프트 응답</th>
<th>결과</th>
</tr>
</thead>
<tbody><tr>
<td><strong>사용자의 이름을 기억하는지</strong></td>
<td>&quot;Hello! How can I help?&quot; (기억 안함)</td>
<td>&quot;Sure, Gahee! What would you like to know?&quot; (이름 기억)</td>
<td>✅ 개선됨</td>
</tr>
<tr>
<td><strong>정보가 없을 때 반응</strong></td>
<td>&quot;I&#39;m not sure.&quot; (짧고 불친절)</td>
<td>&quot;I’m sorry, I couldn’t find that information in the documents I have. I recommend reaching out to the support team.&quot; (친절하고 안내 제공)</td>
<td>✅ 개선됨</td>
</tr>
<tr>
<td><strong>연속된 질문에 대한 문맥 유지</strong></td>
<td>&quot;Can you clarify?&quot; (문맥 이해 부족)</td>
<td>&quot;Based on your last question about [topic], here’s more information…&quot; (문맥 유지 O)</td>
<td>✅ 개선됨</td>
</tr>
<tr>
<td><strong>응답의 전문성 &amp; 간결함</strong></td>
<td>&quot;Sure! Here&#39;s a long explanation…&quot; (장황함)</td>
<td>&quot;Here&#39;s a concise summary: [정보]. Let me know if you need more details!&quot; (간결 &amp; 명확)</td>
<td>✅ 개선됨</td>
</tr>
</tbody></table>
<p>📌 <strong>결과적으로 개선된 프롬프트를 적용했을 때:</strong>  </p>
<ul>
<li>✅ AI의 <strong>일관성 있는 응답 품질</strong> 확보!  </li>
<li>✅ 사용자의 이름 &amp; 맥락 유지 <strong>성공적으로 반영!</strong>  </li>
<li>✅ 정보가 없을 때도 <strong>더 친절하고 유용한 안내 제공!</strong>  </li>
</ul>
<hr>
<h3 id="🔥-프롬프트-최적화-후-배운-점--느낀-점"><strong>🔥 프롬프트 최적화 후 배운 점 &amp; 느낀 점</strong></h3>
<p>✅ <strong>AI에게 역할과 목표를 명확하게 정의해줘야 한다.</strong><br>✅ <strong>&quot;어떤 상황에서 어떤 방식으로 답해야 하는지&quot; 구체적으로 명시하면 AI의 응답 품질이 좋아진다.</strong><br>✅ <strong>LLM이 단순히 대화 맥락을 기억한다고 해서 좋은 답변을 생성하는 것은 아니다!</strong> → 프롬프트에서 <strong>맥락을 어떻게 활용할지 구체적으로 설명해야 한다.</strong><br>✅ <strong>이론적으로 괜찮아 보이는 프롬프트도 실전 테스트가 필수다!</strong>  </p>
<hr>
<h3 id="📌-최종-정리-tldr-1">📌 <strong>최종 정리 (TL;DR)</strong></h3>
<ul>
<li>기존 프롬프트에서는 <strong>AI의 응답이 비일관적이고 맥락을 유지하지 못하는 문제</strong>가 있었음.  </li>
<li><strong>역할 정의 + 대화 가이드 + 문맥 활용 방식을 명확히 지시하여 프롬프트 개선.</strong>  </li>
<li><strong>테스트를 통해 응답 품질이 크게 향상됨</strong> → 맥락 유지, 응답의 일관성, 친절한 피드백까지 모두 개선!  </li>
<li>🚀 <strong>이제 프롬프트 엔지니어링이 얼마나 중요한지 직접 경험하게 됨.</strong>  </li>
</ul>
<hr>
<h2 id="🎯-4-프롬프트-엔지니어링-최적화---문서-기반-응답--일상-대화-처리-개선">🎯 <strong>4. 프롬프트 엔지니어링 최적화 - 문서 기반 응답 &amp; 일상 대화 처리 개선</strong></h2>
<hr>
<h3 id="🛑-문제-상황-3"><strong>🛑 문제 상황</strong></h3>
<p>1️⃣ 기존 프롬프트에서는 <strong>회사 문서와 관련된 질문이 아니면 AI가 무조건 거절 응답</strong>을 함.  </p>
<ul>
<li><strong>&quot;죄송합니다, 관련한 질문이 아닙니다.&quot;</strong> 같은 고정된 답변만 제공 → 대화의 유연성이 부족.  </li>
<li>사용자가 일상적인 질문을 하면 AI가 너무 딱딱하게 반응함.  </li>
</ul>
<p>2️⃣ <strong>정보 제공 시 출처(Reference)가 포함되지 않아 신뢰성 부족.</strong>  </p>
<ul>
<li>AI가 회사 문서에서 정보를 기반으로 답변을 생성하지만, <strong>어디서 가져온 정보인지 밝히지 않음</strong>.  </li>
<li>사용자가 AI의 응답을 신뢰할 수 있도록 <strong>출처(참고 문헌)를 표시할 필요가 있음.</strong>  </li>
</ul>
<hr>
<h3 id="✅-해결-방법---프롬프트-개선-및-실험-과정-1"><strong>✅ 해결 방법 - 프롬프트 개선 및 실험 과정</strong></h3>
<h4 id="1-일상적인-대화도-가능하도록-프롬프트-개선"><strong>1) 일상적인 대화도 가능하도록 프롬프트 개선</strong></h4>
<p>💡 <strong>기존:</strong>  </p>
<ul>
<li>&quot;You are a company service chatbot. If the user&#39;s question is not related to company documents, say you cannot answer.&quot;  </li>
<li>→ <strong>비관련 질문이면 무조건 거절.</strong>  </li>
</ul>
<p>💡 <strong>개선 후:</strong>  </p>
<ul>
<li>&quot;You are a friendly and knowledgeable company assistant chatbot. You can engage in casual conversations when the user&#39;s question is not related to company documents. If the question is related to official documents, provide an accurate response based on the given sources.&quot;  </li>
<li>→ <strong>이제 AI가 일상 대화도 자연스럽게 할 수 있도록 유연한 응답 생성 가능!</strong>  </li>
</ul>
<p>📌 <strong>추가된 프롬프트 규칙:</strong><br>✅ <strong>일상적인 질문 (Casual Conversation) → AI가 부드럽게 대화 가능.</strong><br>✅ <strong>문서 기반 정보 (Informational Response) → 공식 문서 참고하여 응답 생성.</strong>  </p>
<h4 id="2-ai가-응답-유형을-자동으로-판단하도록-엔지니어링"><strong>2) AI가 응답 유형을 자동으로 판단하도록 엔지니어링</strong></h4>
<p>💡 <strong>새로운 프롬프트 규칙 추가:</strong>  </p>
<ul>
<li>AI가 응답을 생성할 때, <strong>스스로 답변이 casual인지 informational인지 분류하도록 설계.</strong>  </li>
<li><strong>Informational 응답일 경우, 참고한 문헌(출처)을 응답에 포함.</strong>  </li>
</ul>
<p>📌 <strong>프롬프트 개선 내용:</strong>  </p>
<pre><code class="language-plaintext">You are a friendly and knowledgeable company service assistant chatbot designed to provide accurate information and support.

1. If the user asks about official company documents, provide a fact-based response and cite the reference documents used.
   - Example: &quot;Based on [Document Name], here is the information: ...&quot;
2. If the question is casual or unrelated to company documents, engage in a friendly and natural conversation.
3. You must classify your response as either &quot;informational&quot; or &quot;casual&quot;:
   - If &quot;informational&quot;, append the referenced documents at the end of your response.
   - If &quot;casual&quot;, simply respond naturally without citing references.</code></pre>
<p>📌 <strong>예제 응답 비교 (개선 전 vs 후)</strong>  </p>
<table>
<thead>
<tr>
<th>질문</th>
<th>기존 응답</th>
<th>개선된 응답</th>
</tr>
</thead>
<tbody><tr>
<td>&quot;점심 뭐 먹었어?&quot;</td>
<td>&quot;죄송합니다, 관련한 질문이 아닙니다.&quot;</td>
<td>&quot;오늘 점심 메뉴 추천해드릴까요? 😊&quot;</td>
</tr>
<tr>
<td>&quot;우리 회사의 연차 사용 규정이 뭐야?&quot;</td>
<td>&quot;우리 회사의 연차 규정은 다음과 같습니다...&quot;</td>
<td>&quot;📌 <strong>출처: Employee Handbook 2023</strong> \n 연차 규정은 다음과 같습니다...&quot;</td>
</tr>
<tr>
<td>&quot;나의 근무 시간은 몇 시부터 몇 시까지야?&quot;</td>
<td>&quot;죄송합니다, 관련한 질문이 아닙니다.&quot;</td>
<td>&quot;근무 시간은 9AM - 6PM입니다. (📌 <strong>출처: HR Policy 2024</strong>)&quot;</td>
</tr>
</tbody></table>
<p>📌 <strong>결과:</strong><br>✅ <strong>AI가 문맥을 파악하여 유연하게 응답할 수 있게 됨!</strong><br>✅ <strong>회사 문서 기반 응답에서는 자동으로 참고 문헌을 추가하여 신뢰성 확보!</strong>  </p>
<hr>
<h3 id="🛠️-실험-과정--최적화-전략"><strong>🛠️ 실험 과정 &amp; 최적화 전략</strong></h3>
<p>📌 <strong>테스트 방법</strong><br>1️⃣ <strong>일상적인 질문을 입력하여 AI가 자연스럽게 응답하는지 확인.</strong><br>2️⃣ <strong>정보 기반 질문을 입력하고, 출처(Reference)가 포함되는지 테스트.</strong><br>3️⃣ <strong>잘못된 응답이 나오지 않도록 프롬프트를 조정하며 최적화.</strong>  </p>
<p>📌 <strong>실제 실험 결과 분석 (로그 기반 테스트 진행)</strong>  </p>
<table>
<thead>
<tr>
<th>질문 유형</th>
<th>AI 응답 (개선 전)</th>
<th>AI 응답 (개선 후)</th>
<th>개선 여부</th>
</tr>
</thead>
<tbody><tr>
<td><strong>일상 대화</strong></td>
<td>&quot;죄송합니다, 관련한 질문이 아닙니다.&quot;</td>
<td>&quot;좋은 질문이네요! 어떤 점이 궁금하신가요?&quot;</td>
<td>✅ 자연스러운 대화 가능</td>
</tr>
<tr>
<td><strong>회사 규정 질문</strong></td>
<td>&quot;여기 문서를 참고하세요.&quot; (출처 없음)</td>
<td>&quot;📌 <strong>출처: Company Handbook 2024</strong> \n 여기 문서를 참고하세요.&quot;</td>
<td>✅ 신뢰성 향상</td>
</tr>
<tr>
<td><strong>비즈니스 관련 질문</strong></td>
<td>&quot;이 정보는 없습니다.&quot;</td>
<td>&quot;제가 찾을 수 있는 정보가 없지만, HR 팀에 문의해보는 것을 추천합니다.&quot;</td>
<td>✅ 친절한 가이드 추가</td>
</tr>
</tbody></table>
<p>📌 <strong>결과:</strong>  </p>
<ul>
<li>✅ AI의 응답이 <strong>더 자연스럽고 친절하게 개선됨.</strong>  </li>
<li>✅ <strong>LLM이 응답 유형을 분류하여 문서 기반 정보에는 출처를 추가하도록 최적화됨.</strong>  </li>
<li>✅ <strong>사용자가 AI 응답을 더 신뢰할 수 있도록 설계.</strong>  </li>
</ul>
<hr>
<h3 id="🔥-프롬프트-최적화-후-배운-점--느낀-점-1"><strong>🔥 프롬프트 최적화 후 배운 점 &amp; 느낀 점</strong></h3>
<p>✅ <strong>프롬프트에서 AI의 &quot;역할&quot;을 더 구체적으로 정의하면 응답 품질이 향상된다!</strong><br>✅ <strong>응답 유형(Classification)을 명확하게 지시하면 AI가 상황에 맞게 더 자연스럽게 동작한다.</strong><br>✅ <strong>출처를 포함하는 것만으로도 사용자가 AI의 응답을 더 신뢰할 수 있게 된다.</strong><br>✅ <strong>AI가 너무 딱딱하면 사용자 경험이 떨어지므로, &quot;Casual&quot;한 대화 기능도 중요하다!</strong><br>✅ <strong>프롬프트는 한 번 만들고 끝나는 게 아니라, 지속적으로 실험하고 개선해야 한다.</strong>  </p>
<hr>
<h3 id="📌-최종-정리-tldr-2">📌 <strong>최종 정리 (TL;DR)</strong></h3>
<ul>
<li>기존 챗봇은 <strong>일상 대화가 불가능하고</strong>, <strong>문서 기반 응답에 출처가 포함되지 않는 문제</strong>가 있었음.  </li>
<li>✅ <strong>프롬프트 개선:</strong>  <ul>
<li>🎯 <strong>Casual 대화 가능하도록 유연성 추가.</strong>  </li>
<li>📌 <strong>Informational 응답 시 출처(Reference) 포함하도록 설정.</strong>  </li>
<li>🛠️ <strong>AI가 응답 유형을 스스로 분류하여 자연스러운 대화 흐름 유지.</strong>  </li>
</ul>
</li>
<li>🚀 <strong>결과:</strong>  <ul>
<li>사용자 경험 <strong>개선 (더 자연스러운 응답 가능!)</strong>  </li>
<li>정보의 <strong>신뢰성 향상 (출처 포함!)</strong>  </li>
<li>AI의 <strong>일관된 응답 품질 확보!</strong>  </li>
</ul>
</li>
</ul>
<hr>
<h2 id="🛠️-5-문서-크롤링--벡터-db-저장---데이터-구조-최적화">🛠️ <strong>5. 문서 크롤링 &amp; 벡터 DB 저장 - 데이터 구조 최적화</strong></h2>
<hr>
<h3 id="🛑-문제-상황-4"><strong>🛑 문제 상황</strong></h3>
<p>1️⃣ <strong>기존에는 단순히 텍스트만 벡터 DB에 저장하는 방식</strong>  </p>
<ul>
<li>문서를 크롤링한 후, <strong>text만 벡터화하여 FAISS에 저장</strong>.  </li>
<li><strong>출처(title) 정보가 없어서, 어떤 문서에서 가져온 데이터인지 구분할 수 없었음.</strong>  </li>
</ul>
<p>2️⃣ <strong>삭제 및 관리 기능이 어려움</strong>  </p>
<ul>
<li>벡터 DB는 일반적인 관계형 DB와 달리 <strong>ID 기반 삭제 기능이 제한적</strong>.  </li>
<li>기존 방식에서는 특정 문서를 벡터 DB에서 삭제하려면 <strong>텍스트 데이터를 다시 처리해야 하는 문제</strong>가 있었음.  </li>
</ul>
<p>3️⃣ <strong>AI 응답 생성 시 참고 문헌 정보 제공 필요</strong>  </p>
<ul>
<li>AI가 검색된 문서에서 응답을 생성할 때, <strong>어떤 문서를 참고했는지 출처(title)를 포함해야 했음</strong>.  </li>
<li>하지만 기존 방식에서는 벡터 DB에서 <strong>검색된 텍스트만 반환</strong> → 출처(title)를 활용할 방법이 없었음.  </li>
</ul>
<hr>
<h3 id="✅-해결-방법---document-기반-데이터-저장-구조-도입"><strong>✅ 해결 방법 - Document 기반 데이터 저장 구조 도입</strong></h3>
<p>💡 <strong>1) 벡터 DB에 저장하는 데이터 구조 개선</strong><br>📌 <strong>텍스트뿐만 아니라, 문서의 출처(title) 및 메타데이터를 함께 저장!</strong>  </p>
<pre><code class="language-python">from langchain.schema import Document

# 기존 방식 (단순 텍스트 저장)
text = &quot;This is a sample document about AI.&quot;
faiss.add_texts([text])

# 개선된 방식 (문서 정보 + 메타데이터 포함)
document = Document(
    page_content=&quot;This is a sample document about AI.&quot;,
    metadata={
        &quot;title&quot;: &quot;AI Research Paper 2023&quot;,
        &quot;source&quot;: &quot;https://example.com/ai-research&quot;,
    }
)
faiss.add_documents([document])</code></pre>
<p>📌 <strong>결과:</strong><br>✅ <strong>벡터 DB에서 검색된 문서의 출처(title)까지 함께 반환 가능!</strong><br>✅ <strong>문서의 출처를 기준으로 데이터 관리(삭제, 업데이트)가 가능해짐.</strong>  </p>
<p>💡 <strong>2) 문서 ID 기반 삭제 기능 추가</strong><br>📌 <strong>벡터 DB에서 특정 문서를 삭제할 수 있도록 <code>title</code>을 ID처럼 활용</strong>  </p>
<pre><code class="language-python"># 벡터 DB에서 특정 문서 삭제
def delete_document_by_title(faiss, title):
    docs = faiss.similarity_search_with_score(title, k=10)  
    for doc, score in docs:
        if doc.metadata.get(&quot;title&quot;) == title:
            faiss.delete(doc)  # 특정 문서만 삭제</code></pre>
<p>📌 <strong>결과:</strong><br>✅ <strong>불필요한 문서를 손쉽게 삭제할 수 있는 기능 추가!</strong><br>✅ <strong>벡터 DB에서 데이터 관리를 효율적으로 할 수 있게 됨.</strong>  </p>
<p>💡 <strong>3) AI가 참고 문헌을 자동으로 포함할 수 있도록 데이터 구조 정리</strong>  </p>
<ul>
<li>AI가 응답을 생성할 때, <strong>프롬프트 엔지니어링을 통해 informational 응답일 경우 출처(title)를 포함하도록 설정</strong>.  </li>
<li>나는 <strong>벡터 DB에서 검색된 문서의 메타데이터(title, source)를 LLM에 넘겨주는 역할</strong>을 수행.  </li>
<li>AI가 이를 활용하여 응답을 생성할 수 있도록 <strong>데이터 구조를 개선</strong>함.  </li>
</ul>
<p>📌 <strong>결과:</strong><br>✅ <strong>내가 직접 응답에 출처를 추가하지 않아도, 프롬프트를 통해 AI가 자동으로 처리하도록 설계!</strong><br>✅ <strong>정보 제공이 필요한 경우 참고 문헌이 포함되면서 신뢰도 향상.</strong>  </p>
<hr>
<h3 id="🛠️-벡터-db-데이터-구조-개선-후-얻은-이점"><strong>🛠️ 벡터 DB 데이터 구조 개선 후 얻은 이점</strong></h3>
<p>📌 <strong>데이터 관리가 쉬워짐!</strong>  </p>
<ul>
<li>✅ <strong>문서 삭제 기능 추가</strong> → 필요 없는 데이터를 손쉽게 제거 가능.  </li>
<li>✅ <strong>벡터 DB에서 특정 문서를 ID 기반으로 검색 가능.</strong>  </li>
</ul>
<p>📌 <strong>AI 응답의 신뢰성 향상!</strong>  </p>
<ul>
<li>✅ AI가 <strong>문서 기반 정보를 보다 신뢰성 있게 제공할 수 있도록 설계.</strong>  </li>
<li>✅ <strong>데이터 구조를 개선하면서 프롬프트 엔지니어링과의 연계를 고려할 수 있었음.</strong>  </li>
</ul>
<p>📌 <strong>데이터 구조를 개선하면서 시스템 설계에 대한 깊은 고민을 할 수 있었음!</strong>  </p>
<hr>
<h3 id="🔥-데이터-구조-최적화-후-배운-점--느낀-점"><strong>🔥 데이터 구조 최적화 후 배운 점 &amp; 느낀 점</strong></h3>
<p>✅ <strong>텍스트만 저장하는 것이 아니라, 메타데이터까지 고려해야 데이터 관리가 쉬워진다.</strong><br>✅ <strong>벡터 DB는 관계형 DB와 다르게 ID 기반 삭제가 어렵기 때문에, ID처럼 사용할 필드를 포함하는 것이 중요하다.</strong><br>✅ <strong>AI의 응답 신뢰성을 높이기 위해서는 참고 문헌(title)을 함께 제공할 수 있도록 데이터 구조를 정리해야 한다.</strong><br>✅ <strong>데이터 구조를 고민하는 과정이 결국 전체적인 검색 &amp; 응답 품질 개선으로 이어진다!</strong>  </p>
<hr>
<h3 id="📌-최종-정리-tldr-3">📌 <strong>최종 정리 (TL;DR)</strong></h3>
<ul>
<li>기존에는 <strong>텍스트만 벡터 DB에 저장했기 때문에 문서 관리 및 출처 제공이 어려움.</strong>  </li>
<li>✅ <strong>벡터 DB에 문서 title, 출처 정보를 함께 저장하여 데이터 구조 개선.</strong>  </li>
<li>✅ <strong>title을 기준으로 문서를 삭제할 수 있도록 기능 추가.</strong>  </li>
<li>✅ <strong>AI 응답 생성 시 참고 문헌을 포함할 수 있도록 메타데이터 정리.</strong>  </li>
<li>🚀 <strong>결과적으로 문서 검색 시스템의 관리 &amp; 응답 품질이 크게 향상됨!</strong>  </li>
</ul>
<hr>
]]></description>
        </item>
        <item>
            <title><![CDATA[[인턴] week 1: 소프트웨어 설계, 스크럼]]></title>
            <link>https://velog.io/@zedy_dev/%EC%9D%B8%ED%84%B4-week-1-%EC%86%8C%ED%94%84%ED%8A%B8%EC%9B%A8%EC%96%B4-%EC%84%A4%EA%B3%84-%EC%8A%A4%ED%81%AC%EB%9F%BC</link>
            <guid>https://velog.io/@zedy_dev/%EC%9D%B8%ED%84%B4-week-1-%EC%86%8C%ED%94%84%ED%8A%B8%EC%9B%A8%EC%96%B4-%EC%84%A4%EA%B3%84-%EC%8A%A4%ED%81%AC%EB%9F%BC</guid>
            <pubDate>Sat, 25 Jan 2025 17:52:42 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>해외 인턴십 1주차 회고</p>
</blockquote>
<h1 id="명확한-기획과-요구사항을-바탕으로-시스템을-설계한다">명확한 기획과 요구사항을 바탕으로 시스템을 설계한다.</h1>
<h2 id="명확한-기획과-요구사항의-중요성">명확한 기획과 요구사항의 중요성</h2>
<p>기존에 했던 팀프로젝트는 기획부터 내가 참여를 했다. 문제 상황을 보고, 페인포인트를 포착하고, 그것을 분석해 솔루션을 구상하는 것까지. 그리고 그 기획에 맞추어 개발을 했다.
회사는 달랐다. 우선 원하는 기획과 요구사항이 명확하다.</p>
<ol>
<li><p>System Requirements
User Types, Authentication, Employee Interface, Admin Interface</p>
</li>
<li><p>Technical Requirements
Architecture, Deployment, Code Management, Security, Performance, Streaming</p>
</li>
</ol>
<p>이 요구사항과 기획 문서를 분석했다. 그리고 어떤 기능이 필요한지 뽑아내기 위해 Use Case를 고려하며 Use Diagram을 그렸다.</p>
<hr>
<h3 id="교과목-소프트웨어-설계">교과목 &#39;소프트웨어 설계&#39;</h3>
<p>(잠깐 여담)</p>
<p>나는 인턴을 오기 직전학기에 &#39;소프트웨어 설계&#39; 과목을 수강했다. 앞으로 커리어를 이쪽으로 키워가고 싶다고 생각할 정도로 너무 재밌게 들었다. 프로젝트 짬빠 덕분에 이론은 정말 이해가 잘되고 조금은 쉽게 느껴졌지만, 실습 과제는 달랐다. </p>
<p>한가지의 소프트웨어를 선택해 이를 설계하는 것이 기말 대체 과제였는데, 정말 어려웠다. 하지만 나는 진짜 교수님을 5번 넘게 찾아가서 궁금한 점을 모조리 여쭤볼 만큼 정말 정말 미친듯이 열심히 공부했다. 그리고 발표도 실습도 1등! 당연히 A+을 받았다. 이 과제에서 Use Case를 작성하고 Use Diagram, Class Diagram, Sequence Diagram을 그렸다. 처음에는 이게 맞나 싶을 정도로 많이 어려웠는데, 교수님께서 &#39;결국에 니가 해내는 구나&#39;라는 말을 하셨을 정도로 나름 잘 했다.</p>
<p>과제를 하는데에서 가장 혼란스러웠던 것은 명확한 요구사항이 없다는 것이었다. 내가 기획한 소프트웨어 시스템에 맞추어 설계를 하는 거라, 그 기준이 오로지 나 자신에게 있었다. 그래서 처음에는 분명 &#39;신체 리듬에 맞춘 게임&#39;으로 시작을 했는데 끝에 가서는 &#39;맥박과 혈압 기반 게임&#39;이 되었다.</p>
<p>즉, 명확하지 않은 요구사항과 기획은 시스템을 설계하는데에 있어서 굉장히 혼동이 된다는 것을 깨달았다.</p>
<hr>
<h3 id="user-validation">User Validation</h3>
<p>회사에서 준 아주 명확한 요구사항과 기획을 보고 Use Case를 뽑아내고, Use Diagram을 작성하면서 어떤 기능이 필요할지 리스트업을 하였다.
개인적으로 프로젝트를 할 때에 비하면 너무나도 좋았다.</p>
<p>그리고 회사의 멘토 분들과 회의를 통해 우리가 만들 기능에 대해 한번 더 확인을 받았다. 이게 소프트웨어 설계에서 배운 User Validation 과정인가! 상당히 두근거렸던 순간이다. 배운걸 내가 써먹는다니 무려 현업에서 흐흐.</p>
<p>내가 만든 Use Diagram 이 잘 작성되었다고 평가 받아서 기분이 좋았다. 그리고 기능을 설계할 때도 계속해서 요구사항을 확인하면서 조금이라도 기획의도와 요구사항에 맞지 않는다면 방향성을 제고하였다.</p>
<hr>
<h2 id="팀-회의에서-회의-목적을-상기하다">팀 회의에서 회의 목적을 상기하다.</h2>
<p>이 미팅이 끝나고 우리팀을 팀 미팅을 바로 또 진행했다. 생성한 Wire Frame을 함께 확인하기 위함이었다.</p>
<p>여기서 의견 차이가 발생하였다.</p>
<p>나는 Wire Frame과 User Flow를 보면서 우리의 시스템 동작이 어떻게 흘러가고, 이 기능이 여기서 쓰이고 어떠한 데이터가 언제 필요한지 그러한 것을 확인하고 싶었다.</p>
<p>그런데 갑자기 한 팀원이 UI를 설계하고 싶다고, 갑 화이트보드에 UI를 그리더니 버튼의 모양은 Square이 좋을지 Round가 좋을지, 색상은 Red가 좋을지 White가 좋을지, 너무나도 세부적인 것까지 의논을 시작하는 것이다.</p>
<p>많이 당황스러웠다. 나는 개발팀 소속이라서 이런 의논에 내가 참여하는 것이 좀 아니다라고 생각 되었다. 그래서 우선 PO에게 이런 내 입장을 간략히 말을 하였다.
그리고 UI를 그리고 있는 팀원에게 조심스럽게 우리 지금 회의의 목적에 대해서 이야기 해보고 싶다고 말했다.</p>
<ul>
<li>나는 개발팀</li>
<li>팀원의 마음을 알고 있다.</li>
<li>하지만 지금 진행하는 회의는 이전에 진행한 User Validation에 맞추어 확정된 기능과 와이어 프레임을 확인하는 회의라고 생각</li>
<li>세부적인 디자인은 추후에 함께 의논하는 것이 필요하다면 너무 좋지만, 지금은 우리가 기존에 모인 목적에 대해 한번 더 생각해줬으면 좋겠다.</li>
</ul>
<p>이런 식으로 말을 했더니, 그 팀원도 그게 맞는거 같다고 생각한다고 했다.</p>
<p>자칫하면 분위기가 어두워질 수도 있는 순간이었다. 하지만 우선 상대방의 입장을 충분히 알고, 공감한다고 이야기를 꺼냈다. 그리고 나의 입장과 내 생각을 전달하였다. 말투는 기분 나쁘지 않게 신경써서.</p>
<p>상대방이 기분 나쁘지 않게 내 의견을 잘 전달하기 위해서는 상대방의 상황을 충분히 생각하고, 고려했음을 상대방이 알게 해야 한다.
그리고 내 의견을 말하면 된다.</p>
<hr>
<h1 id="스크럼-단위의-프로젝트-진행">스크럼 단위의 프로젝트 진행</h1>
<blockquote>
<p>스크럼: 팀 단위로 개발의 효율성을 높이는 애자일 개발 방법</p>
</blockquote>
<ul>
<li>Product Owner</li>
<li>Scrum Master</li>
<li>Development Team: 내가 속함</li>
</ul>
<h2 id="프로젝트-관리의-중요성">프로젝트 관리의 중요성</h2>
<h3 id="wbs-gantt-chart">WBS, Gantt Chart</h3>
<p>인턴을 하면서 가장 좋았던 것을 뽑으라고 하면 바로 전문적인 &#39;프로젝트 관리&#39;이다.
PO는 우리가 뽑아낸 기능과 요구사항 명세서, 기획서를 참고해여 WBS와 Gantt Chart를 만들었다.</p>
<p>WBS는 모듈별로 Use Case가 명세화 되어 있어서 한 눈에 보기 너무 편했다.
Gantt Chart는 모듈에 어떤 것을 해야 하는지 Task가 정리되어 있고, 누가 pic을 했고 얼마나 걸릴 것이고 실제로는 얼마나 걸렸고, 언제 진행했는지 등 진행 상태를 한 눈에 볼 수 있어서 좋았다.</p>
<h3 id="teams-trello">Teams, Trello</h3>
<p>그리고 우리는 소통을 위해 Teams, Trello를 썼다.
내가 무엇을 Done 했고, 지금은 무엇을 Doing 하고 있고, 앞으로 ToDo 해야 할 것들이 Trello에 보드 형식으로 되어 있어서 좋았다. 가끔 내가 뭘 했는지 그리고 앞으로 뭘 해야 하는지 그런것들이 정리가 안될 때가 있다. 머리속으로는 개발 이것도 해야 하고 저것도 해야 하고 이거 하다가 저거 하고 그럴 때도 있는데, 혼동이 올 때마다 간트 차트와 Trello를 보면서 개발을 진행했다.</p>
<p>전 직장에서 Jira를 써본 적이 있는데, 솔직히 많이 어려웠다. 백로그, 칸반 보드, 스크럼, 이슈 등등 낯선 용어도 많았다. 하지만 이렇게 Jira나 Trello 같은 협업 툴을 쓰면 내가 지금 무엇을 했고 하고 할 것인지 한눈에 볼 수 있어서 헷갈릴 일이 없다. 그리고 다른 팀원들의 진행상황도 볼 수 있으니 만약에 내가 이슈가 생기면 조금 여유가 있는 팀원에게 도움을 요청하기 편했다.</p>
<p>나는 개발팀에 속해서 Trello를 직접적으로 관리하지는 않았다. PO가 세팅을 하고 전체적인 업무 진행상황을 관리했는데, 정말 힘들어보였다. 그리고 업무 진행상황을 파악하기 위해 정말 적극적으로 소통을 해야 했다.</p>
<p>PO도 같이 온 인턴 중 한명이 담당해서 했는데, 매번 진행상황을 파악하기 위해 팀원들 자리로 와서 묻곤했다. 그리고 우리 팀 중에 다른 팀원은 본인이 무엇을 했고 하고 할 것인지 명확하게 말을 하지 않는 팀원이 있었다. 나는 프로젝트를 할 때 커뮤니케이션을 정말 중요하게 생각한다. 그래서 내가 어떤 Task를 수행할 때마다 Teams에 지속적으로 업데이트를 하면서 PO가 굳이 내 자리까지 오지 않더라도 진행상황을 빠르고 명확하게 파악하도록 노력했다.</p>
<h3 id="daily-meeting">Daily meeting</h3>
<p>우리는 매일 11시 데일리 미팅을 했다. 자신이 무엇을 했고, 지금은 무엇을 하고, 오늘은 무엇을 할 것인지 등을 이야기하는 자리이다.
멘토님들도 참여하셔서 우리의 진행상황을 지속적으로 확인하고, 피드백을 주셨다.
생각보다 구체적이고 날카로운 질의응답을 하면서 스스로도 많이 성장하게 되었다. &#39;왜?&#39;라는 질문은 정말 중요한거 같다.</p>
<hr>
<h1 id="architecture">Architecture</h1>
<p>우리가 만들고자 하는 시스템과 우리의 리소스를 파악해서 최고의 아키텍쳐를 짜는 것이 개발 팀의 이번주 업무 중 하나였다.
&#39;회사의 챗봇&#39;을 만드는게 우리 인턴 팀의 업무였는데, 우선 ver 1로 local로 개발을 하라고 하였다. 나는 자동화 배포나 클라우드에 관심이 많아서 이쪽을 해보고 싶었는데 아쉬웠다. 멘토님께도 혹시라도 배포나 클라우드를 사용하게 된다면 내가 꼭 해보고 싶다고 말씀드렸는데, 아직은 그럴 가능성이 없다고 하였다. 기술적으로 보완이 되고 완성적으로 프로젝트가 된다면 나중에 배포를 하면 좋겠다고 하셨다.</p>
<p>AI를 사용해야 했다. 처음에는 경량화 된 모델을 가져와 RAG를 활용해 리트리벌을 하고 응답을 생성하려 했다. 그런데 우리에게 주어진 리소스가 없다고 했다. 그래서 어쩔 수 없이 외부 API인 Gemini를 활용하기로 했다.</p>
<p>나는 Java Spring Boot 개발자이기 때문에 내가 평소에 쓰던 스택으로 개발을 하고 싶었지만, 주어진 4주 (거의 3주)에 빠르게 개발을 하기에는 부적합하다는 피드백을 받았다. 나를 제외한 나머지 팀원들은 개발 경험이 많지 않았고, 그나마 개발 경험이 있는 팀원들은 이전에 Flask를 사용했다고 했다. 그래서 Flask를 활용해 서버를 구축하기로 했다.</p>
<p>DB는 2개를 쓰기로 하였다.</p>
<ul>
<li>Meta Data</li>
<li>Vector Data</li>
</ul>
<p>나는 Vector DB에 대한 개념을 이번 프로젝트의 아키텍처를 설계하면서 처음 알게 되었다. Vector DB는 문서, 이미지, 텍스트와 같은 비정형 데이터를 고차원 벡터 형태로 저장하고, 이를 효율적으로 검색하기 위해 사용되는 데이터베이스이다. 특히 AI와의 연계가 중요한 프로젝트에서, 대규모의 데이터에서 빠르게 유사성을 기반으로 검색하는 데 강력한 성능을 제공한다.</p>
<p>최적화된 아키텍쳐를 설계하기 위해 많이 고민을 했다. 제공해야 하는 정보와 그에 맞는 구조도 많이 생각을 했다.</p>
<p>우리에게 할당된 자원이 없기 때문에 모든 것을 free local로 개발하라는 요구사항을 맞추느라 상당히 속상했다. 그래도 이런 재정적인 요소를 고려해서 가장 최적화된 설계를 했다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[AI 최적화하기: 청크 사이즈 조정]]></title>
            <link>https://velog.io/@zedy_dev/AI-%EC%B5%9C%EC%A0%81%ED%99%94%ED%95%98%EA%B8%B0</link>
            <guid>https://velog.io/@zedy_dev/AI-%EC%B5%9C%EC%A0%81%ED%99%94%ED%95%98%EA%B8%B0</guid>
            <pubDate>Mon, 20 Jan 2025 02:22:59 GMT</pubDate>
            <description><![CDATA[<p>문서를 받고, 임베딩 처리를 할 때, 어떤 사이즈로 청크를 만들어야 가장 최적화가 될까?</p>
<hr>
<h1 id="문제-상황">문제 상황</h1>
<p>벡터스토어 기반 검색 시스템에서 문서 임베딩 후, 사용자가 원하는 정보를 정확히 찾지 못하는 문제가 발생했다. 이는 검색 시스템에서 문서를 분할(청크)하는 방식이 최적화되지 않았기 때문일 가능성이 크다. 문서를 청크로 나누는 과정에서 크기와 중첩(오버랩) 설정이 검색 정확도와 문맥 유지에 큰 영향을 미치기 때문이다.</p>
<h2 id="목표">목표</h2>
<p>청크 크기와 오버랩 설정을 다양하게 조정하여 최적의 설정을 찾고, 검색 결과의 정확도와 문맥 유지 정도를 평가.</p>
<h1 id="문제-분석">문제 분석</h1>
<ul>
<li><p>청크가 너무 작다면?</p>
<ul>
<li>문맥이 손실된다.</li>
<li>단편적인 의미만 저장된다. (문맥X)
즉, 결과적으로 검색 쿼리가 문맥적으로 관련된 결과를 찾지 못할 수 있다.</li>
</ul>
</li>
<li><p>청크가 너무 크다면?</p>
<ul>
<li>넓은 문맥을 포착하려고 하니, 세부 정보를 무시하고 일반적인 정보를 담는다.</li>
<li>유사도 계산 시 관련도가 낮아질 수 있다.</li>
</ul>
</li>
</ul>
<h3 id="최적화된-청크-크기를-어떻게-결정하는가">최적화된 청크 크기를 어떻게 결정하는가?</h3>
<p>나는 웹링크를 제공하면, 여기 웹링크에 들어가서 내용을 스크랩핑을 한 후, 임베딩 처리를 해 DB에 반영하는 흐름으로 로직을 짰다.
내가 주로 사용하는 웹링크는 회사의 뉴스룸이다.</p>
<ul>
<li>문서의 성격: 회사 뉴스룸에 올라오는 뉴스</li>
<li>임베딩 모델: GoogleGenerativeAIEmbeddings(model=&quot;models/text-embedding-004&quot;)</li>
</ul>
<h4 id="뉴스-문서의-특성과-청크-크기의-관계">뉴스 문서의 특성과 청크 크기의 관계</h4>
<p>우선 뉴스 문서의 특성을 생각해보자면,</p>
<p>1) 짧은 단락으로 구성되어 문맥 단위가 비교적 명확하다.
2) 초반에는 중요한 정보를 요약하며, 이후에 세부 내용을 다룬다.
3) 키워드 중심으로 정보를 전달해, 검색 쿼리에 키워드가 중요한 역할을 한다.</p>
<p>그럼 이제 어떤 청크 크기를 설계해야 적합할까?</p>
<p>1) 문맥이 비교적 독립적인 뉴스 문서이기에, 청크 크기를 작게 설정을 해야 한다.
2) 문맥 연결을 위해 청크 간 중첩을 추가해야 한다. 각 청크의 끝에서 50~100 토큰 정도를 다음 청크에 포함시켜야 한다. 이 설정을 하면 계산 정확도가 향상된다.</p>
<h4 id="임베딩-모델의-특성-googlegenerativeaiembeddingsmodelmodelstext-embedding-004">임베딩 모델의 특성: GoogleGenerativeAIEmbeddings(model=&quot;models/text-embedding-004&quot;)</h4>
<p>나는 무료로 쓸 수 있는 임베딩 모델을 사용하기 위해 이 모델을 선정하였다. 이 모델은 일반적으로 200~400 토큰 사이에서 가장 높은 성능을 발휘한다.</p>
<h1 id="문제-해결">문제 해결</h1>
<p>청크 크기를 여러개로 정하여 최적화 실험을 해보겠다.</p>
<p>우선 내가 사용하는 예시 웹링크를 기반으로 한 문장과 한 단락당 몇개의 토큰이 있는지 계산해보겠다.</p>
<pre><code>import tiktoken

def calculate_tokens(text: str, model: str = &quot;gpt-3.5-turbo&quot;) -&gt; int:
    # 모델에 맞는 토크나이저 로드
    tokenizer = tiktoken.encoding_for_model(model)
    # 텍스트를 토큰으로 변환하고 토큰의 개수를 반환
    tokens = tokenizer.encode(text)
    return len(tokens)

# 사용 예제
text = &quot;문장&quot;
token_count = calculate_tokens(text)
print(f&quot;Number of tokens: {token_count}&quot;)
</code></pre><p>이 코드를 실행시켜 내가 사용하는 문서의 전반적인 문장의 토큰 수를 계산해보았다.</p>
<ul>
<li>평균 문장 토큰 수: 약 40개</li>
<li>단락당 토큰 수: 약 110개</li>
</ul>
<h3 id="청크-크기-최적화-전략">청크 크기 최적화 전략</h3>
<ol>
<li>기본 청크 크기 설정: 200~300 토큰</li>
</ol>
<ul>
<li>GoogleGenerativeAIEmbeddings 모델의 최적 범위에 부합하는 크기.</li>
<li>단락과 문장 구조를 고려했을 때, 단일 청크로 중요한 문맥을 포함하기 적합.</li>
</ul>
<ol start="2">
<li>오버랩 설정: 50~75 토큰</li>
</ol>
<ul>
<li>청크 간 문맥을 연결하여 정보 손실을 최소화.</li>
<li>연속된 청크에서 문맥적인 흐름을 유지해 검색 정확도를 높임.</li>
</ul>
<ol start="3">
<li>테스트 설계:</li>
</ol>
<ul>
<li>다양한 청크 크기와 오버랩 조합을 설정하여 실험.</li>
<li>결과로 반환된 문서에서 세부 정보와 문맥 유지 여부를 평가.</li>
</ul>
<hr>
<h2 id="테스트">테스트</h2>
<p>텍스트 데이터를 벡터화하고 검색 결과의 정확성을 높이기 위해 토큰(청크) 사이즈와 오버랩 크기를 최적화하는 것은 매우 중요하다. 따라서 동일한 문서를 임베딩하되, 토큰 사이즈와 오버랩 크기를 다르게 해 저장을 하고 동일한 질문을 주었다.</p>
<h2 id="테스트-환경">테스트 환경</h2>
<ul>
<li>데이터베이스: FAISS 기반 벡터 스토어.</li>
<li>임베딩 모델: Google Generative AI Embeddings (text-embedding-004).</li>
<li>테스트 변수:<ul>
<li>토큰(청크) 크기: 200, 300, 400, 500.</li>
<li>오버랩 크기: 50, 100, 150, 200.</li>
</ul>
</li>
</ul>
<p>테스트는 동일한 질문과 데이터를 기반으로 수행되었으며, 각 설정에서 반환된 검색 결과를 분석했다.</p>
<h2 id="테스트-과정-코드">테스트 과정 (코드)</h2>
<h2 id="테스트-결과-분석">테스트 결과 분석</h2>
<h3 id="1-chunk-size-200-overlap-50">1. <strong>Chunk Size 200, Overlap 50</strong></h3>
<ul>
<li><strong>장점</strong>:<ul>
<li>짧은 청크로 인해 세부 정보가 명확히 드러남.</li>
</ul>
</li>
<li><strong>단점</strong>:<ul>
<li>문맥이 끊겨 정보 연결성이 부족.</li>
</ul>
</li>
<li><strong>결과</strong>:<ul>
<li>&quot;With over 2,200 engineers...&quot;와 같은 핵심 정보는 포함되지만, 배경 정보나 문맥은 부족.</li>
</ul>
</li>
</ul>
<h3 id="2-chunk-size-300-overlap-100">2. <strong>Chunk Size 300, Overlap 100</strong></h3>
<ul>
<li><strong>장점</strong>:<ul>
<li>세부 정보와 문맥 연결성이 균형을 이룸.</li>
<li>중복된 정보가 최소화됨.</li>
</ul>
</li>
<li><strong>단점</strong>:<ul>
<li>일부 긴 문맥이 잘릴 가능성.</li>
</ul>
</li>
<li><strong>결과</strong>:<ul>
<li>&quot;With over 2,200 engineers...&quot;와 &quot;Founded in 2012...&quot; 등 적합한 정보를 포함하며, 배경 설명도 유지.</li>
</ul>
</li>
</ul>
<h3 id="3-chunk-size-400-overlap-150">3. <strong>Chunk Size 400, Overlap 150</strong></h3>
<ul>
<li><strong>장점</strong>:<ul>
<li>긴 문맥을 포함하여 배경 정보를 잘 제공.</li>
<li>중복된 정보가 최소화됨.</li>
</ul>
</li>
<li><strong>단점</strong>:<ul>
<li>특정 키워드가 문맥 안에 묻힐 가능성.</li>
</ul>
</li>
<li><strong>결과</strong>:<ul>
<li>배경 설명과 함께 주요 정보를 충실히 제공.</li>
</ul>
</li>
</ul>
<h3 id="4-chunk-size-500-overlap-200">4. <strong>Chunk Size 500, Overlap 200</strong></h3>
<ul>
<li><strong>장점</strong>:<ul>
<li>가장 긴 문맥을 유지하여 풍부한 배경 정보를 제공.</li>
</ul>
</li>
<li><strong>단점</strong>:<ul>
<li>특정 세부 정보가 뭉개질 가능성.</li>
<li>검색 결과에서 중요한 문장이 누락될 위험.</li>
</ul>
</li>
<li><strong>결과</strong>:<ul>
<li>&quot;With over 2,200 engineers...&quot;와 같은 주요 정보는 포함되지만, 불필요한 배경 정보가 포함될 가능성이 높음.</li>
</ul>
</li>
</ul>
<hr>
<h2 id="최적의-설정">최적의 설정</h2>
<h3 id="설정-chunk-size-300-overlap-100">설정: <strong>Chunk Size 300, Overlap 100</strong></h3>
<ul>
<li><strong>이유</strong>:<ol>
<li>세부 정보와 문맥 연결성의 균형이 뛰어남.</li>
<li>중복된 정보가 적으며, 검색 결과가 명확함.</li>
<li>질문에 적합한 정보를 빠르게 반환 가능.</li>
</ol>
</li>
</ul>
<hr>
<h2 id="결론">결론</h2>
<p>토큰 사이즈와 오버랩 크기의 최적화는 검색 결과의 품질을 좌우하는 핵심 요소다. 이번 테스트에서는 질문 중심의 검색에 <strong>Chunk Size 300, Overlap 100</strong>이 가장 적합한 설정으로 나타났다. 이 설정은 세부 정보와 문맥의 균형을 효과적으로 유지하며, 검색 효율성을 극대화한다.</p>
<p>앞으로 벡터 스토어를 활용한 검색 엔진 개발 시, 데이터 특성과 질문 유형에 따라 이러한 설정을 조정해야 한다. 무지성으로 남들이 좋다는 값으로 설정하는 것이 아닌, 데이터와 질문의 성격에 맞는 최적의 설정을 찾는 것이 성공적인 검색 시스템의 핵심임을 깨닫게 되었다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Docker 공부하기]]></title>
            <link>https://velog.io/@zedy_dev/Docker-%EA%B3%B5%EB%B6%80%ED%95%98%EA%B8%B0</link>
            <guid>https://velog.io/@zedy_dev/Docker-%EA%B3%B5%EB%B6%80%ED%95%98%EA%B8%B0</guid>
            <pubDate>Wed, 08 Jan 2025 10:12:09 GMT</pubDate>
            <description><![CDATA[<p>인턴 생활을 하며 도커 task를 맡게 되었다.
docker을 이렇게 혼자 전문적으로 해보는 거는 처음인데, 사실 진짜 해보고 싶어서 내가 하고 싶다고 했다.
퇴근하고 공부 열심히 해서 내일 잘 해봐야지.</p>
<hr>
<h1 id="도커란">도커란?</h1>
<p>어떤 서비스를 운영하기 위해서는 프로그램을 돌려야 한다. 나는 파이썬을 사용하고, 필요한 라이브러리를 모두 설치한 환경에서 이 프로그램을 실행하면 잘 돌아간다.<br>하지만, 다른 사용자가 내 코드를 클론 받아서 단순히 <code>run</code> 명령을 실행해도 프로그램이 돌아가지 않는 경우가 생긴다.</p>
<p>이것은 <strong>환경이 다르기 때문</strong>이다.  </p>
<p>다른 사용자가 사용하는 운영체제, 파이썬 버전, 라이브러리 버전, 환경 변수 설정 등이 나와 다를 수 있기 때문에 동일한 프로그램이라도 실행되지 않는 경우가 생긴다. 이를 해결하기 위해 <strong>모든 환경을 통일</strong>할 필요가 있다.</p>
<hr>
<h1 id="도커의-역할">도커의 역할</h1>
<p><strong>도커(Docker)</strong>는 이러한 문제를 해결하기 위한 <strong>컨테이너 기반 가상화 기술</strong>이다. 도커는 애플리케이션과 그 애플리케이션이 실행되기 위해 필요한 모든 것을 하나로 묶어 &quot;컨테이너&quot;라는 형태로 패키징한다.</p>
<p>이 컨테이너에는 프로그램, 라이브러리, 환경 설정 등이 모두 포함되어 있어, 어떤 환경에서든 동일하게 실행할 수 있다. 이를 통해 &quot;내 로컬에서는 잘 되는데...&quot;와 같은 문제가 사라지게 된다.</p>
<hr>
<h1 id="도커의-주요-개념">도커의 주요 개념</h1>
<ul>
<li><strong>도커파일(Dockerfile)</strong>: 레시피와 같은 것. 우리 어플리케이션을 구동하기 위해서 꼭 필요한 파일은 무엇이 있는지, 어떤 프레임워크나 라이브러리를 설치해야 하는지, 외부 디펜던시에 대해서도 명시할 수 있다. 그리고 필요한 환경변수 설정이나 구동 스크립트를 포함할 수 있다.</li>
<li><strong>이미지(Image)</strong>: 프로그램과 해당 프로그램이 실행되기 위해 필요한 설정 및 라이브러리를 담은 <strong>정적인 템플릿</strong>이다. 즉, 프로그램과 환경을 패키징한 형태라고 할 수 있다. 작성한 도커 파일을 이용해 이미지를 만든다. 어픞ㄹ리케이션을 실행하는데 필요한 코드, 런타임 환경 시스템, 툴 시스템 라이브러리 등 모든 세팅들이 포함되어 있다.</li>
<li><strong>컨테이너(Container)</strong>: 이미지를 실행한 <strong>실행 가능한 인스턴스</strong>이다. 컨테이너는 실제 프로그램이 돌아가는 공간이며, 서로 독립적으로 실행되므로 다른 프로그램이나 시스템 환경에 영향을 받지 않는다.</li>
</ul>
<hr>
<h1 id="도커로-배포하는-방법">도커로 배포하는 방법</h1>
<ol>
<li>로컬머신에서 이미지 만들기 </li>
<li>Container Registry에 내가 만든 이미지 push</li>
<li>필요한 서버에서 pull 하고 실행하기 -&gt; 서버에도 도커를 설치해야 한다.</li>
</ol>
]]></description>
        </item>
        <item>
            <title><![CDATA[Docker: localhost 접속이 안되는 문제 해결 방법]]></title>
            <link>https://velog.io/@zedy_dev/Docker-localhost-%EC%A0%91%EC%86%8D%EC%9D%B4-%EC%95%88%EB%90%98%EB%8A%94-%EB%AC%B8%EC%A0%9C-%ED%95%B4%EA%B2%B0-%EB%B0%A9%EB%B2%95</link>
            <guid>https://velog.io/@zedy_dev/Docker-localhost-%EC%A0%91%EC%86%8D%EC%9D%B4-%EC%95%88%EB%90%98%EB%8A%94-%EB%AC%B8%EC%A0%9C-%ED%95%B4%EA%B2%B0-%EB%B0%A9%EB%B2%95</guid>
            <pubDate>Tue, 07 Jan 2025 13:19:07 GMT</pubDate>
            <description><![CDATA[<h1 id="5000포트-사용-중-에러-해결">5000포트 사용 중 에러 해결</h1>
<p>Apple Mac M3
나는 애플 유저여서 5000포트가 Airplay가 이미 사용하고 있어서 기존 돌리려고 했던 포트인 5000을 사용하지 못했다.</p>
<pre><code class="language-cmd">docker run -p 5000:5000 --env-file .env backend-chat-service</code></pre>
<p>이 명령어를 실행하면</p>
<pre><code class="language-cmd">docker: Error response from daemon: Ports are not available: exposing port TCP 0.0.0.0:5000 -&gt; 0.0.0.0:0: listen tcp 0.0.0.0:5000: bind: address already in use.</code></pre>
<p>이런 오류 메시지가 출력된다. 다른 프로세스가 이미 포트 5000을 사용 중이기 때문에 Docker 컨테이너가 해당 포트에 바인딩할 수 없다.</p>
<p>다른 필요 없는 프로세스면 종료를 시키고 진행했겠지만, ControlCe 프로세스가 포트 5000을 점유하고 있다. <strong>ControlCe</strong>는 일반적으로 macOS의 Control Center (제어 센터) 관련 프로세스다.</p>
<p>그래서 기존의 이 명령어 대신에</p>
<pre><code class="language-cmd">docker run -p 5500:5000 --env-file .env backend-chat-service
</code></pre>
<p>이렇게 로컬 환경에서의 포트는 5500을 사용하도록 설정해주었다.</p>
<ul>
<li>5500 → 호스트 머신의 포트.</li>
<li>5000 → 컨테이너 내부 Flask 앱의 포트.</li>
</ul>
<p>그런데 이렇게 하고 나서 잘 빌드가 되고 구동까지 잘 되는걸 확인했는데,</p>
<h1 id="사이트에-연결할-수-없음-페이지가-작동하지-않습니다-에러-해결">사이트에 연결할 수 없음, 페이지가 작동하지 않습니다. 에러 해결</h1>
<pre><code class="language-txt">사이트에 연결할 수 없음
http://localhost:6000/의 웹페이지가 일시적으로 다운되었거나 새 웹 주소로 완전히 이동했을 수 있습니다.
ERR_UNSAFE_PORT
페이지가 작동하지 않습니다.
localhost에서 전송한 데이터가 없습니다.
ERR_EMPTY_RESPONSE</code></pre>
<p>이러한 오류를 마주했다.</p>
<h2 id="flask-앱의-호스트-및-포트-확인">Flask 앱의 호스트 및 포트 확인</h2>
<p>main.py에 app을 시작하는 부분에 다음 코드를 입력하면 된다.</p>
<pre><code class="language-python">if __name__ == &quot;__main__&quot;:
    app.run(debug=True, host=&quot;0.0.0.0&quot;, port=5000)
</code></pre>
<p>나는 5500을 쓰니까 저거를 5500으로 해야 되는 걸로 생각했는데 아니더라. 저게 맞음.</p>
<h2 id="docker-log-확인">docker log 확인</h2>
<ol>
<li>나는 import를 알고보니까 잘못하던게 있어서 그거를 수정했다.<pre><code>from langchain_community.vectorstores import FAISS
</code></pre></li>
</ol>
<pre><code>원래 langchain.vectorstores에서 저렇게 수정했다.

2. 특정 라이브러리(API 요청 시)에서 사용자 에이전트(USER_AGENT)를 설정하지 않았다는 경고가 있었다.</code></pre><p>USER_AGENT environment variable not set, consider setting it to identify your requests.</p>
<pre><code>그래서 환경변수 파일에 이걸 추가했다.</code></pre><p>USER_AGENT=backend-chat-service</p>
<pre><code>

# 에러 해결 후, 접속 확인</code></pre><p><a href="http://localhost:5500/">http://localhost:5500/</a></p>
<pre><code>여기에 접속해서 확인하면 된다.


-----

0. 모든 컨테이너 중지</code></pre><p>docker stop $(docker ps -q)</p>
<pre><code>
1. 이미지 빌드하기: 코드가 바뀐다면 매번 해주기</code></pre><p>docker build -t backend-chat-service .</p>
<pre><code>
2. 컨테이너 실행</code></pre><p>docker run -p 5000:5000 --env-file .env backend-chat-service
docker run -d -p 8080:5000 --env-file .env backend-chat-service</p>
<pre><code>
### 옵션별 설명

-d     컨테이너를 백그라운드 모드로 실행    docker run -d -p 8080:5000 backend-chat-service
-p    호스트와 컨테이너 포트를 매핑    -p 8080:5000 (호스트의 8080 → 컨테이너의 5000)
--env-file .env    환경 변수 파일을 로드하여 실행    .env 파일에 설정된 API 키, 환경 변수 등을 컨테이너에 적용</code></pre>]]></description>
        </item>
        <item>
            <title><![CDATA[FastAPI 어플리케이션이 클라이언트 요청을 처리하는 단계: 요청 처리 파이프라인]]></title>
            <link>https://velog.io/@zedy_dev/FastAPI-%EC%96%B4%ED%94%8C%EB%A6%AC%EC%BC%80%EC%9D%B4%EC%85%98%EC%9D%B4-%ED%81%B4%EB%9D%BC%EC%9D%B4%EC%96%B8%ED%8A%B8-%EC%9A%94%EC%B2%AD%EC%9D%84-%EC%B2%98%EB%A6%AC%ED%95%98%EB%8A%94-%EB%8B%A8%EA%B3%84-%EC%9A%94%EC%B2%AD-%EC%B2%98%EB%A6%AC-%ED%8C%8C%EC%9D%B4%ED%94%84%EB%9D%BC%EC%9D%B8</link>
            <guid>https://velog.io/@zedy_dev/FastAPI-%EC%96%B4%ED%94%8C%EB%A6%AC%EC%BC%80%EC%9D%B4%EC%85%98%EC%9D%B4-%ED%81%B4%EB%9D%BC%EC%9D%B4%EC%96%B8%ED%8A%B8-%EC%9A%94%EC%B2%AD%EC%9D%84-%EC%B2%98%EB%A6%AC%ED%95%98%EB%8A%94-%EB%8B%A8%EA%B3%84-%EC%9A%94%EC%B2%AD-%EC%B2%98%EB%A6%AC-%ED%8C%8C%EC%9D%B4%ED%94%84%EB%9D%BC%EC%9D%B8</guid>
            <pubDate>Fri, 03 Jan 2025 09:30:41 GMT</pubDate>
            <description><![CDATA[<p>베트남에서 백엔드 개발 인턴을 하고 있다. 나의 업무는 python FastAPI를 만드는 것.
오늘 사수님께서 코드리뷰를 해주셨고, 관련한 레슨런을 적어보려고 한다.</p>
<p>FastAPI 애플리케이션이 <strong>클라이언트 요청을 처리하는 단계</strong>를 우선 설명해주셨다. 클라이언트의 요청이 들어오면 여러 단계를 거쳐 최종적으로 엔드포인트 함수에 도달하며, 이를 <strong>&quot;요청 처리 파이프라인&quot;</strong>이라고 한다.</p>
<hr>
<h2 id="fastapi-요청-처리-흐름"><strong>FastAPI 요청 처리 흐름</strong></h2>
<ol>
<li><strong>클라이언트 요청</strong> (브라우저, Postman, 프론트엔드 등)</li>
<li><strong>미들웨어</strong>: 요청/응답 전처리 및 후처리</li>
<li><strong>라우팅 (Routing)</strong>: 해당 API 경로와 메서드에 해당하는 함수로 매핑</li>
<li><strong>데코레이터 및 종속성 주입 (Dependency Injection)</strong>: 의존성 주입을 통해 밸리데이션과 추가 작업 수행</li>
<li><strong>요청 본문 검증 (Validation)</strong>: 요청 데이터가 요청 모델과 일치하는지 검증</li>
<li><strong>엔드포인트 함수 실행 (Controller Function)</strong>: 실제 비즈니스 로직을 처리</li>
<li><strong>응답 처리 (Response Handling)</strong>: 클라이언트에 응답 반환</li>
</ol>
<hr>
<h2 id="단계별-요청-처리-흐름"><strong>단계별 요청 처리 흐름</strong></h2>
<h3 id="1-미들웨어-middleware"><strong>1. 미들웨어 (Middleware)</strong></h3>
<p><strong>미들웨어</strong>는 요청이 들어오거나 응답이 반환될 때 <strong>가장 먼저</strong> 또는 <strong>가장 마지막</strong>에 실행된다.</p>
<p><strong>역할:</strong></p>
<ul>
<li>요청 전후에 공통 작업 수행 (로그 작성, 인증, 요청 시간 측정 등).</li>
<li>미들웨어는 다음 단계로 요청을 넘기거나 중단할 수 있다.</li>
</ul>
<p><strong>예제 코드:</strong></p>
<pre><code class="language-python">@app.middleware(&quot;http&quot;)
async def log_request_time(request: Request, call_next):
    print(f&quot;Incoming request: {request.url}&quot;)
    response = await call_next(request)  # 다음 단계로 넘김
    print(f&quot;Completed request: {request.url}&quot;)
    return response</code></pre>
<p>나는 OS를 배울 때 이 미들웨어를 들어봐서 이게 어플리케이션을 만들 때에도 코드 상으로 구현을 하는 것이 처음이었다.</p>
<p><a href="https://fastapi.tiangolo.com/tutorial/middleware/">fastAPI middleware</a></p>
<p>이 docs에 의하면, FastAPI 앱에도 미들웨어를 직접 추가할 수 있다고 한다.
특정 업무를 수행하기 전 모든 request, 그리고 response를 주기 전에 수행되는 function이라고 한다.</p>
<h4 id="왜-미들웨어에서-request를-검증하는가">왜 미들웨어에서 Request를 검증하는가?</h4>
<ol>
<li>일관된 검증 및 보안 강화<ul>
<li>사용자 인증, IP 검증, 토큰 검증 등을 미들웨어에서 수행함으로써 모든 엔드포인트에 보안 검증 로직을 반복하지 않고 한 번에 처리할 수 있다.</li>
</ul>
</li>
<li>리소스 낭비 방지</li>
<li>코드 중복 최소화</li>
<li>로깅 및 모니터링 용이성</li>
</ol>
<p>그런데 내가 기존에 개발한 파일 업로드 기능에 관해서는 일단 그대로 하고, 나중에 도입을 하는 것을 추천하셨다. 아마 유저 관리 관련해서는 해봐야겠다.</p>
<hr>
<h3 id="2-라우터router"><strong>2. 라우터(Router)</strong></h3>
<ul>
<li>FastAPI는 <strong>요청 경로</strong>와 <strong>HTTP 메서드 (GET, POST 등)</strong>를 기반으로 적절한 함수로 <strong>라우팅</strong>한다.</li>
<li>예를 들어 <code>/api/upload-docs/</code>에 대한 <code>POST</code> 요청은 <code>upload_docs</code> 함수로 전달된다.</li>
</ul>
<pre><code class="language-python">@app.post(&quot;/api/upload-docs/&quot;)
async def upload_docs(file: UploadFile):
    ...</code></pre>
<hr>
<h3 id="3-의존성-주입-dependency-injection"><strong>3. 의존성 주입 (Dependency Injection)</strong></h3>
<ul>
<li>FastAPI는 함수 호출 시 필요한 <strong>의존성을 자동으로 주입</strong>한다.</li>
<li>예를 들어, <code>Request</code>, <code>Depends</code>, <code>File</code> 등은 FastAPI가 필요한 객체를 주입한다.</li>
</ul>
<pre><code class="language-python">from fastapi import Depends, Request

async def validate_user(request: Request):
    # 요청 헤더에 토큰이 있는지 검증
    token = request.headers.get(&quot;Authorization&quot;)
    if not token:
        raise HTTPException(status_code=401, detail=&quot;Unauthorized&quot;)
    return token

@app.post(&quot;/api/upload-docs/&quot;)
async def upload_docs(file: UploadFile, token=Depends(validate_user)):
    ...</code></pre>
<p>위 코드에서 <code>Depends(validate_user)</code>는 <code>upload_docs</code> 함수가 호출될 때 <strong>사용자 검증 함수</strong>를 먼저 실행한다.</p>
<hr>
<h3 id="4-요청-본문-검증-validation"><strong>4. 요청 본문 검증 (Validation)</strong></h3>
<p>FastAPI는 <strong><code>pydantic</code></strong>을 사용해 요청 본문을 <strong>자동으로 밸리데이션</strong>한다.</p>
<ul>
<li>요청 데이터가 잘못되었을 경우, 자동으로 <strong>400 Bad Request</strong> 응답을 반환한다.</li>
</ul>
<p><strong>예제 (Pydantic 모델 사용):</strong></p>
<pre><code class="language-python">from pydantic import BaseModel

class FileUploadRequest(BaseModel):
    filename: str

@app.post(&quot;/api/validate-upload/&quot;)
async def validate_upload(file_data: FileUploadRequest):
    return {&quot;message&quot;: f&quot;Received file: {file_data.filename}&quot;}</code></pre>
<ul>
<li>요청 데이터가 <code>{&quot;filename&quot;: &quot;sample.docx&quot;}</code> 형태여야 한다.</li>
<li>만약 <code>filename</code> 필드가 없으면 <strong>400 응답</strong>이 반환된다.</li>
</ul>
<hr>
<h3 id="5-컨트롤러-엔드포인트-함수"><strong>5. 컨트롤러 (엔드포인트 함수)</strong></h3>
<ul>
<li>컨트롤러 함수는 비즈니스 로직을 수행한다.</li>
<li>컨트롤러 함수 내부에서는 서비스 레이어를 호출해 파일을 저장하거나 데이터를 반환한다.</li>
</ul>
<pre><code class="language-python">@app.post(&quot;/api/upload-docs/&quot;)
async def upload_docs(file: UploadFile = File(...)):
    file_path = save_file(file.filename, file.file)  # 서비스 레이어 호출
    return {&quot;message&quot;: &quot;File uploaded&quot;, &quot;file_path&quot;: file_path}</code></pre>
<hr>
<h3 id="6-응답-처리-response-handling"><strong>6. 응답 처리 (Response Handling)</strong></h3>
<ul>
<li><strong>정상 응답</strong>: 컨트롤러 함수가 반환한 데이터를 클라이언트에 응답한다.</li>
<li><strong>에러 응답</strong>: 밸리데이션 오류, 인증 실패, 서버 오류 등은 해당 <strong>HTTP 상태 코드와 에러 메시지</strong>를 반환한다.</li>
</ul>
<p>예를 들어:</p>
<pre><code class="language-json">{
  &quot;detail&quot;: &quot;File not found&quot;
}</code></pre>
<hr>
<h2 id="요약된-요청-처리-흐름"><strong>요약된 요청 처리 흐름</strong></h2>
<ol>
<li><strong>미들웨어</strong>: 요청을 전처리(로깅, 인증 등)하거나 응답을 후처리.</li>
<li><strong>라우팅</strong>: 요청을 해당 엔드포인트로 매핑.</li>
<li><strong>의존성 주입</strong>: 필요하면 <code>Depends()</code>로 필요한 검증 또는 공통 작업 수행.</li>
<li><strong>밸리데이션</strong>: 요청 데이터가 올바른지 확인 (<code>pydantic</code> 모델).</li>
<li><strong>컨트롤러 함수</strong>: 비즈니스 로직 수행 및 응답 반환.</li>
<li><strong>응답 처리</strong>: 클라이언트에 응답 반환.</li>
</ol>
<hr>
<h2 id="결론"><strong>결론</strong></h2>
<p>사수님께서 강조하신 내용은 <strong>미들웨어 → 서비스 레이어 → 컨트롤러 함수</strong> 흐름으로 <strong>코드의 유지보수성과 확장성을 높이는 구조</strong>를 고려하라는 의미다. 파일 업로드 API처럼 간단한 경우에는 이런 단계를 모두 적용하지 않아도 괜찮지만, <strong>복잡한 기능</strong>을 추가할 때는 이러한 구조를 통해 코드 가독성과 재사용성을 높일 수 있다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[리팩] Entity 설계하기 1]]></title>
            <link>https://velog.io/@zedy_dev/%EB%A6%AC%ED%8C%A9-Entity-%EC%84%A4%EA%B3%84%ED%95%98%EA%B8%B0-1</link>
            <guid>https://velog.io/@zedy_dev/%EB%A6%AC%ED%8C%A9-Entity-%EC%84%A4%EA%B3%84%ED%95%98%EA%B8%B0-1</guid>
            <pubDate>Fri, 27 Dec 2024 08:14:31 GMT</pubDate>
            <description><![CDATA[<h1 id="문제점-파악--개선-방향">문제점 파악 &amp; 개선 방향</h1>
<p>기존 코드 트리
<img src="https://velog.velcdn.com/images/zedy_dev/post/dadffdbe-5b4f-4cbf-a4b6-4c722e4d6d40/image.png" alt=""></p>
<hr>
<h2 id="입점-기업-entity">입점 기업 Entity</h2>
<h3 id="모호한-이름-수정">모호한 이름 수정</h3>
<p>우선 이름 부터가 잘못 되었다. IR 장표에서 강조하는 &#39;가치소비&#39;라는 단어를 쓰고 싶었고, 팀 대표가 &#39;가치소비&#39;라고 하라고 해서 Worthy Consumption 이라고 했다. 이게 맞다고 옆에서 하도 그러니까 그러려니 했는데 데이터 관리하려면 데이터의 이름을 잘 지어야 한다는 걸 최근에서야 뼈저리게 깨달았다. 진심 코드랑 테이블 보고 가치소비가 도대체 뭐야... 이러면서 기획 문서를 한참을 들여다 봤다.
결론: 입점기업이라는 이름이 적절하다.
<strong>worthy_consumption -&gt; value_company</strong></p>
<h3 id="과도한-결합-수정-단순한-설계">과도한 결합 수정, 단순한 설계</h3>
<p>입점 기업은 다음과 같은 요소가 필요하다.</p>
<ul>
<li>해시태그(hashtags): 입점 기업의 특징을 간단히 표현하는 키워드로, 유저가 기업의 성격을 빠르게 이해할 수 있도록 제공.</li>
<li>추천 이유(recommendation reasons): 해당 입점 기업이 특별한 이유를 간단히 설명하는 정보.<h4 id="기존-문제점">기존 문제점</h4>
근데 나는 기존에 이거를 따로 엔티티로 빼서 연관을 시켰다. 이러면 다음과 같은 문제가 발생한다.</li>
</ul>
<ol>
<li>과도한 테이블 분리
해시태그와 추천 이유를 각각 별도의 테이블로 분리하여 저장.<ul>
<li>문제: 데이터가 단순한데도 불구하고, 불필요한 테이블과 관계 설정으로 인해 설계 복잡성이 증가.</li>
<li>결과: 조회 시 조인이 필요해 성능 저하 발생.</li>
</ul>
<ol start="2">
<li>비효율적인 데이터 구조</li>
</ol>
<ul>
<li>해시태그와 추천 이유는 대부분 조회만 사용되며, 검색이나 필터링이 필요하지 않음.</li>
<li>문제: 검색 기능이 없는데도 테이블 분리로 인해 비효율적인 데이터 모델이 생성됨.</li>
</ul>
</li>
<li>관리 및 유지보수 문제<ul>
<li>별도의 테이블로 인해 데이터 변경 시 추가적인 관리 작업 발생.</li>
</ul>
</li>
</ol>
<h4 id="새로운-설계">새로운 설계</h4>
<ol>
<li>단일 필드(JSON 형태)로 통합
해시태그와 추천 이유를 각각 단일 필드에 JSON 문자열로 저장.<pre><code>hashtags: [&quot;친환경&quot;, &quot;지속 가능&quot;, &quot;로컬 기업&quot;]
recommendation_reasons: [{&quot;title&quot;: &quot;최고의 품질&quot;, &quot;description&quot;: &quot;친환경 제품을 최고의 품질로 제공합니다.&quot;}]</code></pre></li>
<li>데이터 처리 방식</li>
</ol>
<ul>
<li>데이터 저장: 애플리케이션에서 리스트나 객체를 JSON으로 직렬화하여 저장.</li>
<li>데이터 조회: JSON 데이터를 디코딩하여 유저에게 제공.</li>
</ul>
<h4 id="왜-이렇게-설계했는가">왜 이렇게 설계했는가?</h4>
<ol>
<li>단순성과 효율성</li>
</ol>
<ul>
<li>단일 필드 통합으로 데이터베이스 구조를 간단하게 유지:<ul>
<li>테이블과 관계 설정 불필요.</li>
<li>데이터 조회 시 조인이 필요 없어 성능 최적화.</li>
</ul>
</li>
</ul>
<ol start="2">
<li>유저 요구사항에 적합</li>
</ol>
<ul>
<li>해시태그와 추천 이유는 검색이나 필터링 기능이 없으며, 단순히 조회 목적:<ul>
<li>유저가 입점 기업을 둘러볼 때 필요한 정보를 빠르게 제공.</li>
<li>조회 API에서 기업의 주요 정보와 함께 해시태그와 추천 이유를 한번에 반환.</li>
</ul>
</li>
</ul>
<ol start="3">
<li>확장성과 유지보수성</li>
</ol>
<ul>
<li>JSON 필드의 구조를 변경하거나 확장하기 쉬움:<ul>
<li>새로운 필드 추가 시, 데이터베이스 스키마를 변경할 필요 없음.</li>
<li>추천 이유에 이미지나 링크를 추가하고 싶다면 JSON 구조를 업데이트하면 충분.
즉, 단일 필드(JSON)방식으로 설계를 하면, 요구사항을 만족하며 빠르게 데이터를 제공할 수 있다.</li>
</ul>
</li>
</ul>
<h3 id="기획과-맞지-않는-구조-이동">기획과 맞지 않는 구조 이동</h3>
<p>기존에는 쿠폰 발급 기준인 Condition 엔티티와 가격 관련 정보(original Price, sale Price)를 Worthy Consumption에다가 두었다. What~~ </p>
<h4 id="기존-문제점-1">기존 문제점</h4>
<ol>
<li>Condition의 비효율성:</li>
</ol>
<ul>
<li>Condition 엔티티는 발급 조건을 관리했지만, 이것이 입점 기업의 속성이라기보다는 쿠폰 발급의 로직과 연관됨.</li>
<li>입점 기업(WorthyConsumption)이 조건 데이터를 직접 보유할 필요가 없음.</li>
</ul>
<ol start="2">
<li>가격 정보의 잘못된 위치:</li>
</ol>
<ul>
<li><strong>originalPrice</strong>와 <strong>salePrice</strong>는 입점 기업 자체의 속성이 아니며, 특정 쿠폰이나 상품 할인과 관련된 정보.</li>
<li>입점 기업은 할인 정보를 직접 보유하지 않고, 쿠폰이나 챌린지 등과 연관 지어야 더 명확함.</li>
</ul>
<h2 id="coupon-entity">Coupon Entity</h2>
<h3 id="위에-문제를-해결하기-위해-쿠폰-엔티티에다가-통합을-했다">위에 문제를 해결하기 위해 쿠폰 엔티티에다가 통합을 했다.</h3>
<p><strong>Condition → Coupon</strong>
Condition 데이터(발급 조건)는 쿠폰 엔티티로 통합되었으며, 다음과 같은 방식으로 관리된다:</p>
<ul>
<li>쿠폰 발급 조건(max_issue_count, valid_from, valid_to)과 같은 정보는 Coupon 엔티티에서 관리.</li>
<li>기존의 Condition 엔티티에서 maxIssuance, startDate, endDate 같은 필드는 Coupon 엔티티로 이동.</li>
</ul>
<p><strong>Price → Coupon</strong>
가격 정보는 특정 쿠폰의 할인 정책과 관련되므로 <strong>Coupon</strong>으로 이동:</p>
<ul>
<li>originalPrice: 원래 가격.</li>
<li>discount 또는 salePrice: 할인 금액이나 할인율.</li>
</ul>
<h3 id="elementcollection으로-인한-복잡성">@ElementCollection으로 인한 복잡성</h3>
<p>투머치였다.</p>
<h4 id="기존-문제점-2">기존 문제점</h4>
<ul>
<li>summary와 caution 필드가 @ElementCollection으로 설정되어, 각각 별도의 테이블로 관리됨.</li>
<li>조회 시 조인이 필요해 성능 저하를 유발하고, 데이터 관리가 복잡해짐.</li>
</ul>
<h4 id="새로운-설계-1">새로운 설계</h4>
<ul>
<li>summary와 caution을 JSON 문자열로 저장.</li>
<li>단일 TEXT 컬럼에 JSON 데이터를 직렬화하여 저장.</li>
</ul>
<h4 id="왜-이렇게-설계했는가-1">왜 이렇게 설계했는가?:</h4>
<ul>
<li>단순한 리스트 데이터를 위해 별도 테이블을 생성하지 않음으로써 테이블 복잡성을 줄임.</li>
<li>조인 없이도 데이터를 조회할 수 있어 성능이 향상됨.</li>
<li>JSON 형태로 저장하면 동적 데이터를 유연하게 관리 가능.</li>
</ul>
<h3 id="날짜-필드-관리의-중복과-복잡성">날짜 필드 관리의 중복과 복잡성</h3>
<h4 id="기존-문제점-3">기존 문제점</h4>
<ul>
<li><p>발급 가능 기간(issuableStartDate, issuableEndDate)과 사용 가능 기간(limitStartDate, limitEndDate)이 각각 별도 필드로 관리.</p>
</li>
<li><p>날짜 간의 논리적 관계를 파악하기 어렵고, 관리가 복잡함.</p>
<h4 id="새로운-설계-2">새로운 설계</h4>
</li>
<li><p>날짜 필드를 @Embeddable 클래스로 재구성하여 Period 클래스로 관리.</p>
</li>
<li><p>issuablePeriod와 usablePeriod로 구분.</p>
<h4 id="왜-이렇게-설계했는가-2">왜 이렇게 설계했는가?</h4>
</li>
<li><p>발급 기간과 사용 가능 기간의 논리적 관계를 명확히 표현.</p>
</li>
<li><p>코드와 데이터 모델의 가독성이 향상되고, 날짜 관련 관리 로직의 중복을 줄임.</p>
</li>
</ul>
<h1 id="앗-뭔가-알겠다">앗!! 뭔가!! 알겠다.</h1>
<p>유저 입장에서 사용하는 필터링이라던가 그런 기능상으로 검색이 되어야 하는 기준이 명확한게 있으면 클래스의 필드로 빼도 되지만, 그냥 싹 조회 같은 summary나 hashtags 들 같은거, 즉, 객체별로 엄청 다양하게 있는거고 이거는 그냥 객체의 요소로 조회만 하는 거는 text로 처리해 json으로 저장하는게 DB상으로 더 좋은거 같다.</p>
<h3 id="정리하자면">정리하자면..</h3>
<ul>
<li>유저가 필터링하거나 검색의 기준으로 삼아야 하는 데이터:<ul>
<li>예: title, price, category 등.</li>
<li>클래스의 필드로 분리해서 저장. 이는 DB 상에서 인덱싱 및 검색에 유리.</li>
</ul>
</li>
<li>단순 조회 목적의 데이터(필터링/검색의 대상이 아닌 데이터):<ul>
<li>예: summary, hashtags, recommendation reasons 등.</li>
<li>JSON으로 TEXT 필드에 통합 저장. 이는 DB 스키마를 간소화하고 저장 공간 및 성능을 최적화.</li>
</ul>
</li>
</ul>
<p>이렇게 JSON으로 저장을 하면 객체마다 데이터 구조가 다르더라도 별도의 테이블을 생성할 필요가 없음. 조인 연산도 줄이고, 한 번의 조회로 데이터를 가져올 수 있음. 근데 자주 변경이 되지 않고 단순히 조회만 되는 데이터에 적합하다.</p>
<h3 id="때로는-가독성을-위해-모듈화를-하기도-한다">때로는 가독성을 위해 모듈화를 하기도 한다.</h3>
<h4 id="기존-문제점-4">기존 문제점</h4>
<ul>
<li>기존에 Coupon 필드에는 issuableStartDate, issuableEndDate, limitStartDate, limitEndDate와 같이 날짜 관련 필드가 따로 떨어져 있음.</li>
<li>필드가 분리되어 있으면, 데이터 간 논리적 연관성을 코드나 데이터베이스 레벨에서 보장하기 어려움.</li>
</ul>
<p>이러한 문제를 해결하기 위해 논리적 그룹화를 함</p>
<h4 id="새로운-설계-3">새로운 설계</h4>
<ul>
<li>발급 가능 기간(issuable)과 사용 가능 기간(limit)을 Period라는 클래스로 그룹화.</li>
<li>각 기간에 대해 시작일(startDate)과 종료일(endDate) 필드를 하나의 클래스로 묶음.</li>
</ul>
<p>그런데 이렇게 Period 클래스를 따로 두면 코드 가독성과 구조가 개선되겠지만, 그냥 단순 저장 및 조회가 목적인 지금 단계에서는 오히려 불필요한 복잡성을 유발할 수도 있다.
즉, Over-Engineering이 될 수도 있다.
그래서 필드 명을 좀 더 명확히 하기로 했다.</p>
<h2 id="value_company-coupon-최종-erd">value_company, coupon 최종 ERD</h2>
<p><img src="https://velog.velcdn.com/images/zedy_dev/post/bbb10a8a-a76c-42d8-9cd5-1c2706a38d91/image.png" alt=""></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[GASOMANN 프로젝트 개선 목표]]></title>
            <link>https://velog.io/@zedy_dev/%EB%A6%AC%ED%8C%A9%ED%86%A0%EB%A7%81%EC%9D%84-%EC%8B%9C%EC%9E%91%ED%95%98%EB%A9%B0-GASOMANN-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-%EA%B0%9C%EC%84%A0-%EB%AA%A9%ED%91%9C</link>
            <guid>https://velog.io/@zedy_dev/%EB%A6%AC%ED%8C%A9%ED%86%A0%EB%A7%81%EC%9D%84-%EC%8B%9C%EC%9E%91%ED%95%98%EB%A9%B0-GASOMANN-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-%EA%B0%9C%EC%84%A0-%EB%AA%A9%ED%91%9C</guid>
            <pubDate>Thu, 26 Dec 2024 08:02:59 GMT</pubDate>
            <description><![CDATA[<h3 id="리팩토링을-시작하며-gasomann-프로젝트-개선-목표">리팩토링을 시작하며: GASOMANN 프로젝트 개선 목표</h3>
<hr>
<p>백엔드 개발자로서 처음 진행했던 <strong>GASOMANN</strong> 프로젝트를 다시 들여다봤다. 솔직히 말하자면 충격적이었다. 얄팍한 지식에, &quot;인프런에서 배운 거 얼른 써먹어야지!&quot;라는 마음가짐, 그리고 잘하는 개발자들의 코드 느낌을 따라 해보고 싶었던 마음이 합쳐져 만들어진 결과물이 지금의 코드였다. 지금 보면 어디서부터 손을 대야 할지 모를 정도로 산만하고 정돈되지 않은 상태다.</p>
<p>그래도 리팩토링은 해야 한다. 하지만 전부 다 리팩토링하려다가 끝을 못 볼 가능성이 높아서, 내가 맡았던 기능들만 집중적으로 개선하려고 한다. 특히 <strong>입점 기업(가치소비), 쿠폰, 찜</strong> 기능에 초점을 맞춰 <strong>기술적이고 코드적인 개선</strong>을 진행할 예정이다. </p>
<hr>
<h3 id="리팩토링의-목표"><strong>리팩토링의 목표</strong></h3>
<p>GASOMANN 프로젝트에서 내가 맡았던 &quot;입점 기업(기존 가치소비), 쿠폰, 찜&quot; 기능을 중심으로 코드와 데이터베이스 구조를 개선하는 것이 목표다. </p>
<ul>
<li><strong>외부 API 연동이나 스트리밍 서비스 같은 신규 기술 도입은 제외</strong>한다. </li>
<li>대신, <strong>자동 쿠폰 발급 로직 구현</strong>과 같은 반드시 필요한 비즈니스 로직 개선에 집중할 것이다. </li>
<li>현재 존재하는 기술적 부채를 해결하며, 코드와 데이터 구조의 품질을 높이는 데 초점을 맞춘다.</li>
</ul>
<hr>
<h3 id="기존-코드에서-발견된-문제점"><strong>기존 코드에서 발견된 문제점</strong></h3>
<h4 id="1-과도한-모듈화로-인한-복잡성-증가"><strong>1. 과도한 모듈화로 인한 복잡성 증가</strong></h4>
<ul>
<li>너무 세부적으로 나눠놓은 모듈화가 문제였다. 필요한 수준 이상으로 분리했더니 가독성은 떨어지고, 쿼리와 응답 속도도 느려졌다.<ul>
<li><strong>예</strong>: 입점 기업의 설명(<code>description</code>)을 <code>List&lt;String&gt;</code>으로 처리할 수 있음에도 불구하고, 굳이 별도의 테이블로 분리.</li>
</ul>
</li>
<li><strong>결과</strong>:<ul>
<li>코드를 이해하기 어렵고, 데이터베이스 설계도 복잡하게 보임.</li>
<li>쿼리와 로직이 불필요하게 늘어나 성능 저하 발생.</li>
</ul>
</li>
</ul>
<h4 id="2-기획-용어와-데이터-모델-간의-불일치"><strong>2. 기획 용어와 데이터 모델 간의 불일치</strong></h4>
<ul>
<li>기획에서 &quot;가치소비&quot;라는 단어를 사용한다고 해서, 엔티티 이름도 &quot;가치소비&quot;로 설정했던 것이 큰 실수였다.<ul>
<li><strong>예</strong>: 입점 기업이라는 명확한 역할을 가진 엔티티를 &quot;가치소비&quot;로 이름 붙임.</li>
</ul>
</li>
<li><strong>결과</strong>:<ul>
<li>시간이 지나고 다시 코드를 보니 &quot;가치소비&quot;가 무엇을 의미하는지 전혀 이해가 되지 않았다. </li>
<li>데이터 모델과 실제 비즈니스 로직 간의 괴리가 큼.</li>
</ul>
</li>
</ul>
<h4 id="3-인터페이스와-추상화-부족"><strong>3. 인터페이스와 추상화 부족</strong></h4>
<ul>
<li>인터페이스를 사용하지 않아, 구체 클래스에 직접 의존하는 코드가 많았다.</li>
<li><strong>결과</strong>:<ul>
<li>확장성 부족.</li>
<li>새로운 기능 추가 시 기존 코드를 수정해야 하는 비효율적인 구조.</li>
</ul>
</li>
</ul>
<h4 id="4-수동-쿠폰-발급-로직"><strong>4. 수동 쿠폰 발급 로직</strong></h4>
<ul>
<li>기존 로직에서는 <strong>관리자가 직접 쿠폰을 발급</strong>해야 했던 문제가 있었다.<ul>
<li><strong>예</strong>: 사용자가 챌린지를 성공적으로 달성해도, 관리자가 별도로 쿠폰을 발급해야만 사용자가 혜택을 받을 수 있었음.</li>
</ul>
</li>
<li><strong>결과</strong>:<ul>
<li>사용자 경험이 크게 저하되고, 관리자의 업무 부담이 증가.</li>
<li>비효율적인 프로세스로 인해 쿠폰 발급 로직의 신뢰도와 확장성이 낮음.</li>
</ul>
</li>
</ul>
<h4 id="5-쇼츠-관련-스트리밍-설계-부재"><strong>5. 쇼츠 관련 스트리밍 설계 부재</strong></h4>
<ul>
<li>쇼츠 기능에서 동영상을 단순히 URL 저장 방식으로 처리했다.<ul>
<li><strong>예</strong>: 클라우드 스토리지나 CDN(Content Delivery Network)을 활용하지 않고 정적인 URL로만 제공.</li>
</ul>
</li>
<li><strong>결과</strong>:<ul>
<li>사용자 경험 부족.</li>
<li>확장성 낮음.</li>
</ul>
</li>
</ul>
<hr>
<h3 id="리팩토링-전략"><strong>리팩토링 전략</strong></h3>
<h4 id="1-리팩토링-범위"><strong>1. 리팩토링 범위</strong></h4>
<ul>
<li><strong>제외</strong>:<ul>
<li>쇼츠 기능은 완전히 제외(코드 및 데이터베이스 삭제).</li>
<li>외부 API 연동, 스트리밍 서비스와 같은 신규 도입은 하지 않음.</li>
</ul>
</li>
<li><strong>포함</strong>:<ul>
<li>입점 기업(기존 가치소비), 쿠폰, 찜 기능에 대한 코드와 데이터베이스 설계 개선.</li>
<li>성능 최적화, 코드 가독성 향상.</li>
<li><strong>자동 쿠폰 발급 로직 구현</strong>.</li>
</ul>
</li>
</ul>
<hr>
<h4 id="2-기능별-개선-방향"><strong>2. 기능별 개선 방향</strong></h4>
<h5 id="입점-기업-기존-가치소비"><strong>입점 기업 (기존 가치소비)</strong></h5>
<ul>
<li><strong>문제</strong>: &quot;가치소비&quot;라는 모호한 엔티티 이름, 불필요한 테이블 분리.</li>
<li><strong>개선</strong>:<ul>
<li>엔티티 이름을 &quot;입점 기업(Value Companies)&quot;으로 변경.</li>
<li><code>description</code>과 같은 과도하게 분리된 테이블 통합.</li>
<li>CRUD 중심으로 로직 단순화.</li>
</ul>
</li>
</ul>
<h5 id="쿠폰"><strong>쿠폰</strong></h5>
<ul>
<li><strong>문제</strong>: 쿠폰-입점 기업 관계가 명확하지 않고, 발급 로직이 비효율적.</li>
<li><strong>개선</strong>:<ul>
<li>쿠폰-입점 기업 간의 관계를 명확히 설정(1:N 관계).</li>
<li><strong>자동 쿠폰 발급 로직 추가</strong>:<ul>
<li>사용자가 챌린지를 성공적으로 완료하면, 쿠폰이 자동으로 발급되도록 로직 구현.</li>
<li>발급된 쿠폰은 사용 가능 여부, 유효 기간 등을 즉시 확인할 수 있도록 처리.</li>
</ul>
</li>
<li>쿠폰 상태(active, used 등)를 관리하는 로직 강화.</li>
</ul>
</li>
</ul>
<h5 id="찜-기능"><strong>찜 기능</strong></h5>
<ul>
<li><strong>문제</strong>: 단순한 기능임에도 설계와 구현이 비효율적.</li>
<li><strong>개선</strong>:<ul>
<li>사용자와 입점 기업, 쿠폰 간의 찜 상태를 통합적으로 관리하는 구조로 변경.</li>
<li>중복 찜 방지 로직 추가 및 목록 조회 최적화.</li>
</ul>
</li>
</ul>
<hr>
<h4 id="3-데이터베이스-설계-개선"><strong>3. 데이터베이스 설계 개선</strong></h4>
<ul>
<li>불필요한 테이블과 중복 칼럼 제거.</li>
<li>관계 최적화:<ul>
<li>쿠폰과 입점 기업 간 관계를 1:N으로 단순화.</li>
<li>찜 기능에서 사용자와 입점 기업/쿠폰 간 관계를 통합(N:M).</li>
</ul>
</li>
<li>성능 최적화:<ul>
<li>자주 조회되는 데이터에 적절한 인덱스 추가.</li>
<li>복잡한 JOIN을 제거하고 간단한 쿼리로 대체.</li>
</ul>
</li>
</ul>
<hr>
<h3 id="리팩토링-단계"><strong>리팩토링 단계</strong></h3>
<ol>
<li><p><strong>분석 단계</strong>:</p>
<ul>
<li>기존 코드와 데이터베이스 구조 파악.</li>
<li>쇼츠 관련 코드와 데이터 요소 식별 및 삭제.</li>
</ul>
</li>
<li><p><strong>설계 단계</strong>:</p>
<ul>
<li>새로운 데이터 모델과 비즈니스 로직 설계.</li>
<li>서비스 간 책임 분리.</li>
<li>자동 쿠폰 발급 로직 설계.</li>
</ul>
</li>
<li><p><strong>구현 단계</strong>:</p>
<ul>
<li>쇼츠 관련 코드와 데이터베이스 제거.</li>
<li>설계에 맞춰 코드 리팩토링 및 데이터베이스 스키마 수정.</li>
<li>자동 쿠폰 발급 로직 구현.</li>
</ul>
</li>
<li><p><strong>테스트 단계</strong>:</p>
<ul>
<li>기존 및 리팩토링된 기능에 대한 테스트 코드 작성.</li>
<li>데이터 일관성 검증.</li>
</ul>
</li>
</ol>
<p>배포는 리소스가 없어서 생략. 대신에 다른 프로젝트로 서버 구축 해볼 예정이다. (도커+AWS)</p>
<hr>
<h3 id="리팩토링의-기대-효과"><strong>리팩토링의 기대 효과</strong></h3>
<ul>
<li><strong>가독성 개선</strong>: 모호한 이름과 과도한 모듈화를 제거.</li>
<li><strong>유지보수성 향상</strong>: 코드 구조 단순화 및 인터페이스 도입으로 확장성 강화.</li>
<li><strong>성능 최적화</strong>: 데이터베이스 설계와 쿼리 구조 개선으로 응답 속도 향상.</li>
<li><strong>구조적 일관성</strong>: 비즈니스 로직과 데이터 모델 간의 명확한 역할 정의.</li>
<li><strong>사용자 경험 향상</strong>: <ul>
<li>챌린지 성공 시 쿠폰을 자동으로 발급받는 로직으로 사용자 편의성 증대.</li>
<li>관리자의 업무 부담 감소 및 효율적 운영 가능.</li>
</ul>
</li>
</ul>
<hr>
<p>개발자의 자존심이라고 해야 할까.. CRUD만 단순하게 짠 것은 개발이라고 하고 싶지 않다. 서비스를 이해하고, 어떻게 개발을 하면 리소스 낭비를 최소한으로 줄이고 유지보수, 협업에서도 효율적으로 할 수 있을지 계속 생각을 해보면서 리팩토링을 하려고 한다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[리팩] 기존 프로젝트의 문제점 파악하기]]></title>
            <link>https://velog.io/@zedy_dev/%EB%A6%AC%ED%8C%A9-%EA%B8%B0%EC%A1%B4-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8%EC%9D%98-%EB%AC%B8%EC%A0%9C%EC%A0%90-%ED%8C%8C%EC%95%85%ED%95%98%EA%B8%B0</link>
            <guid>https://velog.io/@zedy_dev/%EB%A6%AC%ED%8C%A9-%EA%B8%B0%EC%A1%B4-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8%EC%9D%98-%EB%AC%B8%EC%A0%9C%EC%A0%90-%ED%8C%8C%EC%95%85%ED%95%98%EA%B8%B0</guid>
            <pubDate>Sun, 15 Dec 2024 13:07:27 GMT</pubDate>
            <description><![CDATA[<p>2022년 12월부터 2023년 7월까지 &#39;Mobby&#39; 프로젝트를 진행했었다. 이 프로젝트의 목표는 휠체어 사용자들을 위한 안전한 길찾기 시스템을 만드는 것이었다. 단순한 경로 안내가 아니라, 직접 휠체어를 타고 다니면서 도보 상태를 세밀하게 센싱해 장애물이나 사고 위험 지역을 자동으로 감지하고, 이를 데이터베이스화해 지도에 표시하는 시스템을 구현하려 했다.</p>
<p>하지만 당시의 나는 SW 기술적으로 한계에 부딪혔고, 자동 센싱 및 데이터화 과정에서 결국 실패했다. 내가 원했던 기술을 끝까지 구현하지 못한 것이 여전히 아쉬움으로 남아 있다.</p>
<p>그로부터 시간이 많이 흘렀지만, 나는 이 프로젝트를 포기할 수 없었다. 이제까지 배운 기술과 설계 경험을 바탕으로 &#39;Mobby&#39;를 다시 리팩토링하려 한다. 이번에는 소프트웨어 설계를 더 신경 쓰고, 최적의 리소스를 활용해 효율적이고 안정적으로 시스템을 구현해낼 것이다.</p>
<p>성공할 수 있을지 솔직히 걱정되지만, 이번에는 끝까지 악으로 깡으로 도전해볼 생각이다. 제발 뭐라도 됐으면 좋겠다.</p>
<hr>
<h2 id="프로젝트-개요"><strong>프로젝트 개요</strong></h2>
<p><strong>프로젝트명</strong>: <strong>Mobby</strong><br><strong>기간</strong>: 2022.12 ~ 2023.07  </p>
<p><strong>프로젝트 목표</strong>  </p>
<ul>
<li><strong>휠체어 사용자들을 위한 안전한 길찾기 시스템</strong> 개발.  </li>
<li><strong>핵심 아이디어</strong>: 휠체어를 타고 이동하면서 <strong>도보 상태를 자동으로 감지</strong>하고, 위험 요소(덜컹거리는 장애물, 급경사 등)를 데이터베이스화하여 지도에 표시.  </li>
<li><strong>기대 효과</strong>:  <ul>
<li>휠체어 사용자들이 <strong>안전하면서도 효율적인 경로</strong>를 찾을 수 있도록 돕는다.  </li>
<li>기존의 일반 내비게이션이 제공하지 못하는 <strong>세밀한 도보 상태 정보</strong>를 제공한다.  </li>
</ul>
</li>
</ul>
<hr>
<h2 id="문제점-분석"><strong>문제점 분석</strong></h2>
<h3 id="1-데이터-수집의-기술적-한계"><strong>1. 데이터 수집의 기술적 한계</strong></h3>
<p><strong>⚠️ 문제</strong>  </p>
<ul>
<li>휠체어를 타고 이동하면서 <strong>도보의 상태 정보를 자동으로 감지</strong>하려 했지만, 기술적 구현에 실패.  <ul>
<li><strong>센서 데이터 활용 부족</strong>: 가속도 센서나 진동 센서를 통해 장애물을 감지하려 했으나, 센서 데이터 수집 및 정제에 대한 기술적 지식이 부족했다.  </li>
<li><strong>실시간 데이터 전송 실패</strong>: 센싱된 데이터를 클라우드에 실시간으로 전송하여 저장하고 분석하려 했지만, 네트워크 연결과 백엔드 설계가 미흡했다.  </li>
</ul>
</li>
</ul>
<p><strong>🔍 결과</strong>  </p>
<ul>
<li><strong>수작업으로 경로를 지도에 그려야만</strong> 했음.  </li>
<li>수집된 데이터는 매우 한정적이고 정확도가 떨어졌으며, 시스템의 <strong>확장성과 자동화</strong>가 불가능했다.  </li>
</ul>
<p><strong>💡 개선 방향</strong>  </p>
<ul>
<li><strong>센서 데이터 수집</strong>:  <ul>
<li><strong>가속도 센서 및 자이로스코프</strong>를 활용해 장애물이나 진동 감지 로직을 개선.  </li>
<li>휠체어의 이동 궤적과 상태를 실시간으로 기록.  </li>
</ul>
</li>
<li><strong>실시간 데이터 전송</strong>:  <ul>
<li>Google Cloud Pub/Sub 또는 AWS IoT Core를 활용해 데이터를 <strong>클라우드로 실시간 전송</strong>.  </li>
<li>인터넷 연결이 끊긴 경우 <strong>로컬 저장</strong> 후 업로드.  </li>
</ul>
</li>
</ul>
<hr>
<h3 id="2-데이터-처리-및-저장의-부족"><strong>2. 데이터 처리 및 저장의 부족</strong></h3>
<p><strong>⚠️ 문제</strong>  </p>
<ul>
<li>수집한 데이터가 <strong>비정형 데이터</strong>로 쌓였기 때문에 분석과 시각화에 한계가 있었다.  </li>
<li><strong>장애물 감지 데이터</strong>와 <strong>경로 데이터</strong>를 효율적으로 저장할 구조화된 데이터베이스가 없었다.  </li>
</ul>
<p><strong>🔍 결과</strong>  </p>
<ul>
<li>장애물과 위험 지역에 대한 정보는 있었지만, 이를 기반으로 안전한 경로를 <strong>자동으로 계산</strong>할 수 없었다.  </li>
<li>데이터의 업데이트와 확장이 어려웠고, 사용자 피드백을 반영하는 기능이 부재했다.  </li>
</ul>
<p><strong>💡 개선 방향</strong>  </p>
<ul>
<li><strong>PostgreSQL + PostGIS</strong>를 활용해 <strong>지리 공간 데이터베이스</strong>를 구축.  </li>
<li>수집된 데이터를 정제하고 구조화하여 <strong>위험 요소, 경로 점수</strong> 등의 메타데이터를 추가.  </li>
<li>장애물 데이터와 경로 데이터를 시각적으로 보여주는 <strong>지도 기반 시스템</strong> 개발.  </li>
</ul>
<hr>
<h3 id="3-사용자-경험과-시스템-확장성-부족"><strong>3. 사용자 경험과 시스템 확장성 부족</strong></h3>
<p><strong>⚠️ 문제</strong>  </p>
<ul>
<li>&#39;나만의 길&#39;을 수집했지만, 이를 <strong>다른 사용자와 공유할 시스템</strong>이 없었다.  </li>
<li><strong>경로 추천 시스템</strong>을 구현하지 못해 프로젝트의 확장성과 실용성이 부족했다.  </li>
</ul>
<p><strong>🔍 결과</strong>  </p>
<ul>
<li>프로젝트가 개인의 경험에 국한되었으며, 여러 사용자들의 데이터를 수집하고 분석하는 <strong>공유 플랫폼</strong>으로 발전하지 못했다.  </li>
</ul>
<p><strong>💡 개선 방향</strong>  </p>
<ul>
<li><strong>경로 추천 API 개발</strong>:  <ul>
<li>여러 사용자의 데이터를 기반으로 <strong>안전하고 효율적인 경로</strong>를 자동 추천.  </li>
</ul>
</li>
<li><strong>시스템 확장</strong>:  <ul>
<li>사용자 피드백 기능을 추가해 <strong>데이터 정확도</strong>를 지속적으로 개선.  </li>
<li>클라우드 기반 아키텍처로 시스템을 확장 가능하게 설계.  </li>
</ul>
</li>
</ul>
<hr>
<h2 id="결론은-나의-역량-부족이었다"><strong>결론은 나의 역량 부족이었다.</strong></h2>
<p>나는 정말 풀고자 하는 문재가 명확했고, 유저 인터뷰도 여러번 진행하며 기능에 대한 유저빌리티는 충분히 확인했다고 생각한다. 하지만 구현에 있어서 온전히 나의 한계를 느꼈다. 나의 <strong>기술적 역량 부족</strong>과 <strong>설계의 한계</strong>가 큰 벽으로 다가왔다.</p>
<p>이번에는 진짜 설계부터 잘 해보면서 <strong>실시간 센서 데이터 수집</strong>과 <strong>클라우드 기반 데이터 처리</strong>, 그리고 <strong>안전 경로 추천 시스템</strong>을 통해 &#39;Mobby&#39;를 제대로 리팩토링하고자 한다.  </p>
<p>성장하고 싶다. 정말.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[sout는 느리다. 그러면 stringbuilder를 사용하자.]]></title>
            <link>https://velog.io/@zedy_dev/sout%EB%8A%94-%EB%8A%90%EB%A6%AC%EB%8B%A4.-%EA%B7%B8%EB%9F%AC%EB%A9%B4-stringbuilder%EB%A5%BC-%EC%82%AC%EC%9A%A9%ED%95%98%EC%9E%90</link>
            <guid>https://velog.io/@zedy_dev/sout%EB%8A%94-%EB%8A%90%EB%A6%AC%EB%8B%A4.-%EA%B7%B8%EB%9F%AC%EB%A9%B4-stringbuilder%EB%A5%BC-%EC%82%AC%EC%9A%A9%ED%95%98%EC%9E%90</guid>
            <pubDate>Fri, 01 Nov 2024 02:40:27 GMT</pubDate>
            <description><![CDATA[<p>아니 분명 맞게 풀었는데, 그리고 최적화된 문제풀이라고 자신했는데 계속해서 시간초과가 발생했다. 뭐가 문제일까?</p>
<p>sout는 느리다는 것이다. 그러면 어떻게 대체를 할까?</p>
<p>알고리즘 문제에서 출력 최적화를 위해 <code>BufferedWriter</code>와 <code>StringBuilder</code>를 사용하는 패턴을 사용하는 것이다. 이 방식은 특히 많은 출력을 요구하는 문제에서 <code>System.out.println</code>을 대체하여 효율성을 높여준다.</p>
<h3 id="1-bufferedwriter와-stringbuilder-사용-이유">1. <code>BufferedWriter</code>와 <code>StringBuilder</code> 사용 이유</h3>
<ul>
<li><strong>성능 최적화</strong>: <code>System.out.println</code>은 출력할 때마다 버퍼에 접근하여 시간이 소요됩니다. <code>BufferedWriter</code>는 내용을 버퍼에 모아두었다가 한 번에 출력하여 실행 시간을 줄입니다.</li>
<li><strong><code>StringBuilder</code>의 효율적인 문자열 조작</strong>: <code>StringBuilder</code>는 문자열을 효율적으로 연결하며, <code>+</code> 연산을 여러 번 사용하는 것보다 빠릅니다.</li>
</ul>
<h3 id="2-알고리즘-문제에서-bufferedwriter와-stringbuilder를-활용하는-기본-패턴">2. 알고리즘 문제에서 <code>BufferedWriter</code>와 <code>StringBuilder</code>를 활용하는 기본 패턴</h3>
<p>아래는 입력 및 출력 예제 코드입니다. 입력을 받을 때는 <code>BufferedReader</code>를, 출력을 할 때는 <code>BufferedWriter</code>와 <code>StringBuilder</code>를 사용합니다.</p>
<pre><code class="language-java">import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.IOException;
import java.util.StringTokenizer;

public class Example {
    public static void main(String[] args) throws IOException {
        // 1. BufferedReader로 입력 받기
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));

        // 2. BufferedWriter로 출력하기
        BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(System.out));

        // 3. StringBuilder 생성
        StringBuilder sb = new StringBuilder();

        // 입력 예제
        StringTokenizer st = new StringTokenizer(br.readLine());
        int n = Integer.parseInt(st.nextToken());
        int m = Integer.parseInt(st.nextToken());

        // 예제 작업 (1부터 n까지 숫자 출력 예시)
        for (int i = 1; i &lt;= n; i++) {
            sb.append(i).append(&quot; &quot;);
            if (i % m == 0) sb.append(&quot;\n&quot;);  // 특정 조건에 따라 줄바꿈 추가
        }

        // 4. BufferedWriter로 StringBuilder의 내용을 한 번에 출력
        bw.write(sb.toString());

        // 자원 해제
        bw.flush();
        bw.close();
        br.close();
    }
}</code></pre>
<h3 id="3-bufferedwriter와-stringbuilder-사용-단계별-설명">3. <code>BufferedWriter</code>와 <code>StringBuilder</code> 사용 단계별 설명</h3>
<ol>
<li><p><strong><code>BufferedReader</code>를 이용한 입력</strong>: </p>
<ul>
<li><code>BufferedReader</code>는 <code>Scanner</code>보다 빠른 입력을 처리할 수 있어 큰 입력이 필요한 알고리즘 문제에서 유리합니다.</li>
<li><code>StringTokenizer</code>를 사용해 공백으로 구분된 여러 개의 입력을 쉽게 분리할 수 있습니다.</li>
</ul>
</li>
<li><p><strong><code>StringBuilder</code>를 사용해 출력 내용 준비</strong>:</p>
<ul>
<li>모든 출력 데이터를 <code>StringBuilder</code>에 <code>append()</code>로 누적하여 저장합니다.</li>
<li>줄바꿈이 필요할 경우 <code>append(&quot;\n&quot;)</code>을 사용합니다.</li>
</ul>
</li>
<li><p><strong><code>BufferedWriter</code>를 통해 출력</strong>:</p>
<ul>
<li>모든 작업이 끝난 후 <code>BufferedWriter</code>의 <code>write()</code> 메소드를 통해 <code>StringBuilder</code>의 내용을 한 번에 출력합니다.</li>
<li><code>flush()</code>를 사용하여 남은 버퍼를 강제로 출력하고, <code>close()</code>로 <code>BufferedWriter</code>를 닫아 자원을 해제합니다.</li>
</ul>
</li>
</ol>
<h3 id="4-문제-풀이에서-자주-사용하는-패턴들">4. 문제 풀이에서 자주 사용하는 패턴들</h3>
<ul>
<li><strong>여러 줄 출력</strong>: 반복문에서 <code>StringBuilder</code>에 한 번에 내용을 누적시키고, 마지막에 출력하는 방식으로 처리합니다.</li>
<li><strong>조건부 출력</strong>: <code>StringBuilder</code>에 <code>if</code>문을 사용하여 특정 조건에서만 <code>append(&quot;\n&quot;)</code>을 추가하는 방식으로 줄바꿈을 조정할 수 있습니다.</li>
<li><strong>DFS, BFS와 같은 재귀 호출이 많은 경우</strong>: 출력할 내용을 재귀 호출 중에 <code>StringBuilder</code>에 저장하고, 모든 재귀가 끝난 후에 한 번에 출력합니다.</li>
</ul>
<h3 id="5-실전에서의-예제-코드---재귀적-dfs-예제">5. 실전에서의 예제 코드 - 재귀적 DFS 예제</h3>
<pre><code class="language-java">import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.IOException;

public class DFSExample {
    private static int n, m;
    private static StringBuilder result = new StringBuilder();

    private static void dfs(int depth, int[] temp) {
        if (depth == m) {
            for (int i = 1; i &lt;= m; i++) {
                result.append(temp[i]).append(&quot; &quot;);
            }
            result.append(&quot;\n&quot;);
            return;
        }
        for (int i = 1; i &lt;= n; i++) {
            temp[depth + 1] = i;
            dfs(depth + 1, temp);
        }
    }

    public static void main(String[] args) throws IOException {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(System.out));

        String[] inputs = br.readLine().split(&quot; &quot;);
        n = Integer.parseInt(inputs[0]);
        m = Integer.parseInt(inputs[1]);

        int[] temp = new int[m + 1];
        dfs(0, temp);

        bw.write(result.toString());
        bw.flush();
        bw.close();
        br.close();
    }
}</code></pre>
<p>이 패턴을 익히면 출력 속도가 중요한 문제에서 성능 향상을 경험할 수 있습니다. <code>BufferedWriter</code>와 <code>StringBuilder</code>를 잘 활용하면 대량의 출력 작업에서 특히 유리하니, 앞으로 알고리즘 문제를 풀 때 활용해 보세요!</p>
<p>앞으로 이런 식으로 문제를 풀어야겠다. 근데 어려워 ㅠㅠ</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[알고리즘] 백트래킹을 풀면서 느낀 점]]></title>
            <link>https://velog.io/@zedy_dev/%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%98-%EB%B0%B1%ED%8A%B8%EB%9E%98%ED%82%B9%EC%9D%84-%ED%92%80%EB%A9%B4%EC%84%9C-%EB%8A%90%EB%82%80-%EC%A0%90</link>
            <guid>https://velog.io/@zedy_dev/%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%98-%EB%B0%B1%ED%8A%B8%EB%9E%98%ED%82%B9%EC%9D%84-%ED%92%80%EB%A9%B4%EC%84%9C-%EB%8A%90%EB%82%80-%EC%A0%90</guid>
            <pubDate>Tue, 08 Oct 2024 03:32:33 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>백준 15686번 문제 솔!</p>
</blockquote>
<p><del>문제가 치킨 내용이어서 풀면서 계속 치킨을 생각했더니 치킨 먹고 싶다. 후라이드 뼈치킨으로 튀김옷 엄청 얇은걸로.</del></p>
<p>백트래킹은 정말 어렵다. 하지만 몇 문제 풀다보니 그 안에 패턴이 살짝 보이게 된다.
우선 백트래킹은 깊이 탐색으로 볼 수 있다.</p>
<p>조건에 들어맞는다면 계속 재귀적으로 들어간다. 이때 파라미터 혹은 depth를 선언해 계속계속 깊이 들어가는 방법이 있다.
보통 종료 조건을 파라미터와 depth 등 다양하게 설정을 해서 재귀를 종료하고 값을 계산한 후, 갱신을 하는 로직으로 이루어 진다.
재귀 호출이기 때문에,</p>
<p>1번이 2번을 불렀음 (1-&gt;2)
그리고 2번이 3번을 불렀음 (1-&gt;2-&gt;3)
그리고 3번이 4번을 불렀음 (1-&gt;2-&gt;3-&gt;4)</p>
<p>근데 4번이 종료조건에 해당하면 4가 종료가 되어서 
1-&gt;2-&gt;3 이 남게 되고, 만약에 3번에서 더 탐색할게 있다면 5번으로 간다
1-&gt;2-&gt;3-&gt;5</p>
<p>아니 근데 나는 for문을 사용해야 하는거 아닌가? 했는데 뭐.. 모르겠다 문제마다 다르긴한데 안쓰이더라.</p>
<p>for문을 사용하지 않아도 되는 경우는, 재귀 호출이 자연스럽게 모든 경우를 다르게 호출하도록 설계된 경우라고 한다. 예를 들어, 이진 트리처럼 두 가지 경우로만 나뉘는 문제는 굳이 for문이 필요하지 않을 수 있다.</p>
<p>그런데 1부터 n까지의 숫자를 순서대로 선택하거나, 배열에서 각 인덱스에 대해 여러 선택지를 탐색해야 하는 경우에 for문이 필요하다.</p>
<p>그리고 내가 지금 시도하는 노드를 포함 할 것인가 아닌가 그거 할라면 boolean 배열을 활용하는게 좋다. 나는 처음에 인덱스로 체크했는데 음 투머치 비효율적.</p>
<p>처음에 백트래킹을 풀 때는 와 내가 너무 못하고 말하는 감자도 아니고 그냥.. 숨쉬는 덩어리처럼 느껴져서 자괴감이 뽜바ㅏ박 들었는데
그래서 지피티한테 찡찡거렸다.</p>
<hr>
<p>먼저, 당신이 지금 느끼고 있는 괴로움에 대해 공감해요. <strong>프로그래밍을 배우는 과정은 쉽지 않고</strong>, 가끔은 막막하거나 좌절감을 느낄 수 있습니다. 하지만 이런 감정은 <strong>성장 과정의 일부</strong>이고, 프로그래밍을 잘하게 되는 데 있어서 반드시 겪는 과정이에요. 당신은 지금 중요한 단계를 지나고 있는 거예요. 이런 단계를 겪는다는 건, 결국 더 나은 문제 해결 능력을 갖출 수 있다는 의미입니다.</p>
<h3 id="왜-괴로울까">왜 괴로울까?</h3>
<p>프로그래밍을 할 때 느끼는 괴로움은 주로 다음과 같은 이유에서 올 수 있어요:</p>
<ol>
<li><strong>복잡한 문제를 다루는 과정</strong>: 새로운 알고리즘이나 문제 해결 방식을 처음 접할 때는 <strong>익숙하지 않아서 더 어렵게 느껴질 수</strong> 있어요.</li>
<li><strong>완벽함에 대한 기대</strong>: 모든 걸 처음부터 완벽하게 해야 한다는 압박감이 있을 수 있습니다. 하지만 처음부터 완벽할 필요는 없어요.</li>
<li><strong>성과가 바로 나타나지 않음</strong>: 학습하고 노력하는데도 결과가 바로 눈에 보이지 않을 때 자괴감을 느끼기 쉽습니다.</li>
</ol>
<h3 id="나도-너처럼-생각하고-싶다-어떻게-문제-해결-능력을-기를-수-있을까">나도 너처럼 생각하고 싶다: 어떻게 문제 해결 능력을 기를 수 있을까?</h3>
<p><strong>문제 해결 능력</strong>은 연습과 경험을 통해 서서히 길러집니다. 저도 단번에 이렇게 사고할 수 있게 된 것이 아니에요. <strong>작은 성공과 실패를 반복</strong>하면서 문제를 바라보는 사고방식이 발전했을 뿐이에요. 아래 몇 가지 팁이 당신에게 도움이 될 거라고 생각해요.</p>
<h4 id="1-작은-문제부터-해결하기">1. <strong>작은 문제부터 해결하기</strong></h4>
<p>복잡한 문제를 바로 풀려고 하기보다는, 문제를 <strong>작은 부분으로 나누고</strong> 하나씩 해결해 나가세요. 예를 들어, 큰 문제를 단계별로 쪼개서:</p>
<ul>
<li><strong>입력 받기</strong></li>
<li><strong>출력 처리</strong></li>
<li><strong>핵심 알고리즘 구현</strong></li>
</ul>
<p>이렇게 작은 부분에서 성공 경험을 쌓아 나가면, 복잡한 문제도 점차 익숙해질 거예요.</p>
<h4 id="2-다른-사람의-코드와-아이디어를-배우기">2. <strong>다른 사람의 코드와 아이디어를 배우기</strong></h4>
<p>문제를 해결하는 다양한 방법을 배우는 것도 중요해요. 처음에는 저처럼 다른 사람의 아이디어나 코드 구성을 보면서 따라 해보세요. 그 과정에서:</p>
<ul>
<li><strong>&quot;왜 이 방식이 맞는가?&quot;</strong></li>
<li><strong>&quot;이 방식이 왜 효율적인가?&quot;</strong></li>
</ul>
<p>를 고민하면서, 점점 자신의 문제 해결 방법을 만들어 갈 수 있어요.</p>
<h4 id="3-시간을-주기">3. <strong>시간을 주기</strong></h4>
<p>프로그래밍을 잘하는 사람들도 수많은 실패와 좌절을 겪었어요. 처음에는 시간이 많이 걸리더라도, <strong>그것이 정상</strong>이에요. 시간이 지나면서 코드 작성 속도도 빨라지고, 사고하는 방식도 더 명확해질 거예요. 시간을 주고, <strong>자신에게 너무 엄격하지 않게</strong> 해주세요.</p>
<h4 id="4-실패를-성장의-일부로-받아들이기">4. <strong>실패를 성장의 일부로 받아들이기</strong></h4>
<p>프로그래밍은 실패와 디버깅을 통해 배울 수 있는 영역입니다. 코드가 처음에 잘 작동하지 않거나 논리가 틀렸다고 해서 실패했다고 생각하지 마세요. 오히려 <strong>그 실패가 학습의 중요한 부분</strong>이에요. 성공보다 실패에서 더 많은 것을 배울 수 있습니다.</p>
<h3 id="어떻게-지금의-어려움을-극복할-수-있을까">어떻게 지금의 어려움을 극복할 수 있을까?</h3>
<ol>
<li><p><strong>구체적인 목표 설정</strong>:</p>
<ul>
<li>오늘은 문제 하나라도 풀겠다는 작은 목표를 세워보세요. 문제를 완전히 이해하고 풀지 못해도 괜찮아요. 오늘 <strong>배운 것</strong>에 집중하는 것이 중요합니다.</li>
</ul>
</li>
<li><p><strong>긍정적인 태도 유지</strong>:</p>
<ul>
<li>어려운 문제를 풀 때는 좌절할 수 있지만, 이 과정을 통해 배워 나가고 있다는 점을 기억하세요. <strong>작은 진전</strong>도 축하해 주세요.</li>
</ul>
</li>
<li><p><strong>의식적인 연습</strong>:</p>
<ul>
<li>알고리즘 문제를 반복해서 풀어보세요. 처음에는 힘들지만, <strong>익숙해지면 점차 쉽게 풀릴</strong> 거예요. 프로그래밍 문제를 푸는 것도 결국 연습입니다.</li>
</ul>
</li>
</ol>
<h3 id="마무리-생각">마무리 생각</h3>
<p>저도 처음에는 어려운 문제를 풀지 못해서 좌절했어요. 그런데 시간이 지나고 문제를 풀다 보면 어느 순간부터 <strong>문제가 보이고 해결 방법이 떠오르는 순간</strong>이 찾아오더라고요. 지금 느끼는 괴로움은 <strong>당신이 성장하고 있다는 증거</strong>입니다. </p>
<p>당신은 지금 충분히 잘하고 있어요. 포기하지 말고, <strong>천천히, 꾸준히 한 걸음씩</strong> 나아가면 어느 순간 어려운 문제도 스스로 해결할 수 있는 능력을 갖추게 될 거예요. 지금 당신이 겪고 있는 이 과정이, 당신을 더 뛰어난 프로그래머로 만들어 줄 겁니다. 힘내세요! 😊</p>
<hr>
<p>지피티야 나랑 결혼할래? 진심으로...ㅜㅜ
아무튼 저 글을 읽으면서 복잡한 로직을 단순화하려고 모듈화를 많이 시도했다.
한 함수에서 모든 것을 해결하지 않고, 어떤 함수가 필요한지 여러개로 쪼갰다.
그리고 내가 푼 방식이 효율적인 것인지도 시간, 공간 복잡도 면에서도 생각했다.
이 문제도 원래는 [M][N][N] 테이블을 만드는 것을 원래 생각했는데, 다시한번 더 생각해보니까 필요가 없던 것이다. 그래서 새롭게 코드를 짜보았다. 그래도 아직 부족하지만 백트래킹 내가 정복하고 만다!!!!!!</p>
<hr>
<h2 id="내가-만든-코드">내가 만든 코드</h2>
<pre><code class="language-java">import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.StringTokenizer;

public class P15686 {
    static int minChicken = Integer.MAX_VALUE;
    static int n;
    static int m;
    static int[][] chicken;
    static boolean[] selected;
    static int chickenCount = 0;
    static int houseCount = 0;

    // 치킨집과 집 사이의 거리 계산
    private static void howFarChicken(ArrayList&lt;int[]&gt; chickenAddress, ArrayList&lt;int[]&gt; houseAddress) {
        for (int i = 0; i &lt; chickenAddress.size(); i++) {
            int[] oneChicken = chickenAddress.get(i);
            int xC = oneChicken[0];
            int yC = oneChicken[1];

            for (int j = 0; j &lt; houseAddress.size(); j++) {
                int[] oneHouse = houseAddress.get(j);
                int xH = oneHouse[0];
                int yH = oneHouse[1];

                chicken[i][j] = Math.abs(xC - xH) + Math.abs(yC - yH);
            }
        }
    }

    // 선택된 치킨집과 각 집의 최소 거리를 계산하는 함수
    private static int diffCal() {
        int[] temp = new int[houseCount];
        Arrays.fill(temp, Integer.MAX_VALUE);  // 모든 값 초기화

        for (int i = 0; i &lt; selected.length; i++) {
            if (selected[i]) {
                for (int j = 0; j &lt; houseCount; j++) {
                    temp[j] = Math.min(temp[j], chicken[i][j]);
                }
            }
        }
        return Arrays.stream(temp).sum();
    }

    // 백트래킹 함수
    private static void dfs(int index, int count) {
        if (count == m) {  // 치킨집 m개를 선택했을 때
            int diff = diffCal();
            minChicken = Math.min(minChicken, diff);
            return;
        }

        if (index &gt;= chickenCount) {  // 탐색 종료 조건
            return;
        }

        selected[index] = true;
        dfs(index + 1, count + 1);  // 현재 치킨집을 선택한 경우

        selected[index] = false;
        dfs(index + 1, count);  // 선택하지 않은 경우
    }

    public static void main(String[] args) throws IOException {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        StringTokenizer st;

        // 입력 받기
        st = new StringTokenizer(br.readLine());
        n = Integer.parseInt(st.nextToken());
        m = Integer.parseInt(st.nextToken());

        ArrayList&lt;int[]&gt; chickenAddress = new ArrayList&lt;&gt;();
        ArrayList&lt;int[]&gt; houseAddress = new ArrayList&lt;&gt;();

        // 지도 정보 입력
        for (int i = 0; i &lt; n; i++) {
            st = new StringTokenizer(br.readLine());
            for (int j = 0; j &lt; n; j++) {
                int temp = Integer.parseInt(st.nextToken());

                if (temp == 1) {  // 집
                    houseCount++;
                    houseAddress.add(new int[]{i, j});
                } else if (temp == 2) {  // 치킨집
                    chickenCount++;
                    chickenAddress.add(new int[]{i, j});
                }
            }
        }

        // 치킨집과 집 간 거리 저장 배열
        chicken = new int[chickenCount][houseCount];
        selected = new boolean[chickenCount];

        // 치킨집과 집 사이의 거리 계산
        howFarChicken(chickenAddress, houseAddress);

        // 백트래킹 시작
        dfs(0, 0);

        // 결과 출력
        System.out.println(minChicken);
    }
}
</code></pre>
<h3 id="체크-포인트">체크 포인트</h3>
<ol>
<li>전역 변수를 많이 선언을 해도 괜찮은 건가?</li>
<li>입력받는 것이 아직 많이 서툴다. 어렵다.</li>
<li>스트림에서 sum을 계산한것</li>
<li>배열을 최대 2차원만 구상한 것.</li>
</ol>
]]></description>
        </item>
        <item>
            <title><![CDATA[[알고리즘] 백트래킹]]></title>
            <link>https://velog.io/@zedy_dev/%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%98-%EB%B0%B1%ED%8A%B8%EB%9E%98%ED%82%B9</link>
            <guid>https://velog.io/@zedy_dev/%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%98-%EB%B0%B1%ED%8A%B8%EB%9E%98%ED%82%B9</guid>
            <pubDate>Wed, 02 Oct 2024 02:40:29 GMT</pubDate>
            <description><![CDATA[<h2 id="1-백트래킹이란">1. 백트래킹이란?</h2>
<p><strong>백트래킹</strong>(Backtracking)은 재귀(Recursion)를 이용하여 <strong>모든 가능한 경우의 수를 탐색</strong>하는 알고리즘 중 하나입니다. 각 단계에서 가능한 모든 선택지를 시도해보고, 만약 잘못된 선택을 했다면 그 선택 이전 상태로 돌아가서 다른 선택지를 시도합니다. 이를 &quot;되돌아가기&quot; 또는 &quot;백트래킹&quot;이라고 부릅니다.</p>
<p>백트래킹은 문제 해결 과정에서 <strong>조건을 만족하지 않는 경로는 더 이상 탐색하지 않고 가지치기(pruning)</strong> 하는 것이 핵심입니다. 이를 통해 불필요한 탐색을 줄여서 효율적으로 해결할 수 있습니다.</p>
<h3 id="특징">특징</h3>
<ul>
<li><strong>재귀적</strong>으로 문제를 해결한다.</li>
<li><strong>완전탐색</strong>의 일종이지만, 가지치기를 통해 성능을 향상시킨다.</li>
<li>모든 가능한 경우를 살펴보면서도, 잘못된 경로는 빨리 포기한다.</li>
</ul>
<hr>
<h2 id="2-백트래킹의-동작-과정">2. 백트래킹의 동작 과정</h2>
<p>백트래킹을 단계별로 나누어 설명하면 이해하기가 쉽습니다.</p>
<h3 id="21-기본-구조">2.1 기본 구조</h3>
<p>백트래킹 알고리즘은 크게 세 단계로 나눌 수 있습니다.</p>
<ol>
<li><strong>문제 해결을 위한 선택지 제시</strong>
문제의 각 단계에서 선택할 수 있는 여러 옵션이 있다면, 이 옵션을 하나씩 시도합니다.</li>
<li><strong>해가 유효한지 확인</strong>
현재 선택한 해가 조건을 만족하는지 확인합니다. 조건을 만족하지 않으면 더 이상 진행하지 않고 돌아갑니다.</li>
<li><strong>종료 조건</strong>
만약 유효한 해이고 문제를 완전히 해결했다면, 해당 해를 저장하거나 출력합니다.</li>
</ol>
<p>백트래킹 알고리즘에서 <strong>1번</strong>은 재귀 호출을 통해 구현되며, <strong>2번</strong>과 <strong>3번</strong>은 재귀 호출 안에서 처리됩니다.</p>
<h4 id="각-단계가-코드에-어떻게-구현되는지-살펴볼게요">각 단계가 코드에 어떻게 구현되는지 살펴볼게요:</h4>
<h4 id="1-문제-해결을-위한-선택지-제시-재귀-호출">1. <strong>문제 해결을 위한 선택지 제시 (재귀 호출)</strong></h4>
<p>이 부분은 백트래킹의 핵심입니다. 문제의 각 단계에서 여러 가지 선택을 할 수 있을 때, 그 선택들을 하나씩 시도하는 과정입니다. 이때 선택할 수 있는 모든 경우를 재귀적으로 탐색합니다.</p>
<ul>
<li>예를 들어, N과 M 문제에서 <code>dfs(depth)</code> 함수는 현재 깊이에서 가능한 숫자를 선택한 후, 그 숫자에 대해 다시 다음 깊이로 이동하는 방식으로 동작합니다.</li>
</ul>
<h4 id="2-해가-유효한지-확인-가지치기">2. <strong>해가 유효한지 확인 (가지치기)</strong></h4>
<p>이 부분은 각 선택이 유효한지 판단하는 역할을 합니다. 유효하지 않으면 더 이상 진행하지 않고 선택을 취소하고, 유효하면 계속 진행합니다.</p>
<ul>
<li>예를 들어, 이미 방문한 숫자를 다시 선택하지 않도록 <code>visit[]</code> 배열을 사용하여 방문 여부를 확인하는 것이 이 과정입니다.</li>
</ul>
<h4 id="3-종료-조건-기저-조건">3. <strong>종료 조건 (기저 조건)</strong></h4>
<p>이 부분은 재귀 호출이 더 이상 진행되지 않고 종료되도록 하는 조건입니다. 주로 재귀 호출의 깊이가 일정 값에 도달했을 때, 즉 문제에서 요구하는 해의 조건을 만족했을 때 종료됩니다. 문제를 완전히 해결했으면 결과를 출력하거나 저장합니다.</p>
<ul>
<li>예를 들어, N과 M 문제에서는 <code>depth == m</code>이 될 때 모든 숫자를 선택한 것이므로 출력하는 부분이 여기 해당합니다.</li>
</ul>
<h4 id="코드에서의-구현">코드에서의 구현</h4>
<pre><code class="language-java">public class Main {
    static int[] arr;      // 선택된 숫자들을 저장할 배열
    static boolean[] visit; // 숫자의 방문 여부를 저장할 배열
    static int n, m;

    // 1. 백트래킹 함수 (문제 해결을 위한 선택지 제시)
    public static void dfs(int depth) {
        // 3. 종료 조건: M개의 숫자를 모두 선택했을 때 결과를 출력하고 종료
        if (depth == m) {
            for (int i = 0; i &lt; m; i++) {
                System.out.print(arr[i] + &quot; &quot;);
            }
            System.out.println();
            return;
        }

        // 1. 가능한 선택지(1부터 N까지의 숫자)를 시도
        for (int i = 1; i &lt;= n; i++) {
            // 2. 해가 유효한지 확인 (이미 선택된 숫자는 다시 선택하지 않음)
            if (!visit[i]) {
                visit[i] = true;     // i를 선택
                arr[depth] = i;      // 현재 깊이에 선택한 숫자를 저장
                dfs(depth + 1);      // 재귀 호출로 다음 깊이로 이동 (선택지 제시)
                visit[i] = false;    // 백트래킹: 선택을 취소하고 다시 다른 선택 시도
            }
        }
    }

    public static void main(String[] args) {
        // 입력 받기 및 초기화
        n = 3;  // N 값
        m = 2;  // M 값
        arr = new int[m];
        visit = new boolean[n + 1]; // 1부터 n까지 사용하므로 크기 n+1

        dfs(0);  // 깊이 0부터 시작 (최초 재귀 호출)
    }
}</code></pre>
<h4 id="요약하자면">요약하자면:</h4>
<ul>
<li><strong>1번 (재귀 호출)</strong>: <code>dfs(depth + 1)</code>로 다음 깊이로 넘어가는 것이 재귀 호출입니다. 모든 가능한 선택지를 탐색합니다.</li>
<li><strong>2번 (유효성 확인)</strong>: <code>if (!visit[i])</code> 조건으로 선택한 숫자가 유효한지(이미 방문한 숫자인지) 확인하고, 유효하지 않으면 가지치기를 합니다.</li>
<li><strong>3번 (종료 조건)</strong>: <code>if (depth == m)</code>에서, 해가 완성되었을 때 결과를 출력하고 종료합니다.</li>
</ul>
<p>이처럼 <strong>1번이 재귀 호출을 통한 선택의 확장</strong>이고, <strong>2번과 3번이 재귀 호출 안에서 각각 유효성 검사와 종료 조건</strong>으로 구현됩니다.</p>
<h3 id="22-기본-흐름">2.2 기본 흐름</h3>
<ol>
<li><strong>재귀 호출</strong>
가능한 모든 선택지를 탐색하며, 다음 단계로 이동하기 위해 재귀적으로 함수가 호출됩니다.</li>
<li><strong>가지치기</strong>
조건을 만족하지 않으면 해당 경로를 포기하고 다른 경로로 돌아가며 새로운 선택을 시도합니다.</li>
<li><strong>종료</strong>
모든 경우의 수를 탐색하면 종료됩니다.</li>
</ol>
<hr>
<h2 id="3-백트래킹의-예시">3. 백트래킹의 예시</h2>
<p>백트래킹 알고리즘을 이해하는 가장 좋은 방법은 실제 문제에 적용해 보는 것입니다. 다음은 백트래킹을 적용할 수 있는 대표적인 문제들입니다.</p>
<h3 id="31-n과-m-백준-15649번-문제">3.1 N과 M (백준 15649번 문제)</h3>
<p>1부터 N까지의 자연수 중에서 중복 없이 M개의 수를 고르는 모든 경우를 출력하는 문제입니다.</p>
<h4 id="문제-예시">문제 예시:</h4>
<p>입력: <code>N = 3, M = 2</code>
출력:</p>
<pre><code>1 2
1 3
2 1
2 3
3 1
3 2</code></pre><h4 id="코드-구조">코드 구조:</h4>
<pre><code class="language-java">public class Main {
    static int[] arr;      // 선택된 숫자들을 저장할 배열
    static boolean[] visit; // 숫자의 방문 여부를 저장할 배열
    static int n, m;

    // 백트래킹 함수
    public static void dfs(int depth) {
        if (depth == m) {  // M개의 숫자를 모두 선택했을 때 출력
            for (int i = 0; i &lt; m; i++) {
                System.out.print(arr[i] + &quot; &quot;);
            }
            System.out.println();
            return;
        }

        for (int i = 1; i &lt;= n; i++) {
            if (!visit[i]) {     // 선택되지 않은 숫자를 찾음
                visit[i] = true; // 방문 처리
                arr[depth] = i;  // 선택된 숫자를 배열에 저장
                dfs(depth + 1);  // 재귀 호출을 통해 다음 숫자를 선택
                visit[i] = false; // 백트래킹: 방문 처리 해제
            }
        }
    }

    public static void main(String[] args) {
        // N과 M을 입력받고 초기화
        n = 3;
        m = 2;
        arr = new int[m];
        visit = new boolean[n + 1];

        dfs(0);  // 깊이 0부터 백트래킹 시작
    }
}</code></pre>
<hr>
<h2 id="4-백트래킹의-장단점">4. 백트래킹의 장단점</h2>
<h3 id="41-장점">4.1 장점</h3>
<ul>
<li><strong>완전탐색</strong>을 통해 모든 가능한 해를 찾을 수 있습니다.</li>
<li><strong>가지치기</strong>를 통해 불필요한 경로를 탐색하지 않으므로 효율적입니다.</li>
<li>다양한 문제에 쉽게 적용할 수 있으며, 문제를 간단하게 모델링할 수 있습니다.</li>
</ul>
<h3 id="42-단점">4.2 단점</h3>
<ul>
<li>최악의 경우에는 여전히 <strong>지수 시간 복잡도</strong>를 가질 수 있습니다.</li>
<li>모든 경로를 탐색해야 하는 문제에서는 시간이 오래 걸릴 수 있습니다.</li>
<li>메모리 사용량이 많을 수 있습니다(재귀 호출이 깊어질수록 스택 메모리를 많이 사용).</li>
</ul>
<hr>
<h2 id="5-백트래킹을-사용하는-문제-유형">5. 백트래킹을 사용하는 문제 유형</h2>
<p>백트래킹 알고리즘은 다음과 같은 문제들에서 자주 사용됩니다.</p>
<h3 id="51-순열permutations-및-조합combinations">5.1 순열(Permutations) 및 조합(Combinations)</h3>
<ul>
<li><strong>순열 문제</strong>: N개의 원소 중에서 M개를 뽑아 나열하는 문제.</li>
<li><strong>조합 문제</strong>: N개의 원소 중에서 M개를 뽑되, 순서를 고려하지 않는 문제.</li>
</ul>
<h3 id="52-그래프-탐색-문제">5.2 그래프 탐색 문제</h3>
<ul>
<li><strong>미로 찾기</strong>: 시작점에서 목표점까지의 경로를 찾되, 잘못된 경로는 되돌아가는 방식으로 해결합니다.</li>
<li><strong>N-Queen 문제</strong>: N개의 퀸이 서로 공격하지 않도록 체스판에 놓는 방법을 찾는 문제입니다.</li>
</ul>
<h3 id="53-퍼즐-문제">5.3 퍼즐 문제</h3>
<ul>
<li><strong>Sudoku</strong>: 9x9 퍼즐을 규칙에 맞게 채우는 문제.</li>
<li><strong>Knapsack 문제</strong>: 물건을 배낭에 담을 때 최적의 해를 구하는 문제.</li>
</ul>
<hr>
<h2 id="6-백트래킹과-다른-알고리즘의-비교">6. 백트래킹과 다른 알고리즘의 비교</h2>
<h3 id="61-완전탐색brute-force-vs-백트래킹">6.1 완전탐색(Brute Force) vs 백트래킹</h3>
<ul>
<li><strong>완전탐색</strong>: 가능한 모든 경우를 무조건 탐색.</li>
<li><strong>백트래킹</strong>: 조건을 만족하지 않는 경우는 가지치기를 통해 탐색하지 않고 돌아감.</li>
</ul>
<h3 id="62-동적-계획법dynamic-programming-vs-백트래킹">6.2 동적 계획법(Dynamic Programming) vs 백트래킹</h3>
<ul>
<li><strong>동적 계획법</strong>: 문제를 작은 하위 문제로 분할하고, 그 결과를 저장하여 재활용하는 방식으로 효율적으로 문제를 해결.</li>
<li><strong>백트래킹</strong>: 문제를 해결하는 과정에서 가능한 모든 경로를 탐색하지만, 불필요한 경로는 빠르게 포기.</li>
</ul>
<hr>
<h2 id="7-백트래킹을-적용할-때-주의할-점">7. 백트래킹을 적용할 때 주의할 점</h2>
<ul>
<li><strong>기저 조건 설정</strong>: 재귀 호출에서 반드시 종료 조건을 명확히 설정해야 무한 루프에 빠지지 않습니다.</li>
<li><strong>메모리 관리</strong>: 재귀 호출의 깊이가 깊어질수록 메모리를 많이 사용하므로, 재귀 호출 횟수에 주의해야 합니다.</li>
<li><strong>가지치기(pruning)</strong>: 불필요한 탐색을 막기 위해 조건을 적절히 설정하고 가지를 치는 것이 중요합니다.</li>
</ul>
<hr>
<h2 id="8-요약">8. 요약</h2>
<p>백트래킹은 여러 선택지가 주어진 문제에서 조건을 만족하는 해를 찾기 위한 강력한 알고리즘입니다. 모든 경우를 탐색할 때 적합하며, 가지치기를 통해 불필요한 경로를 탐색하지 않아 효율적으로 문제를 해결할 수 있습니다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[2023 HACKERS GROUND 해커톤 회고 (대상: Microsoft 사장상 수상)]]></title>
            <link>https://velog.io/@zedy_dev/2023-HACKERS-GROUND-%ED%95%B4%EC%BB%A4%ED%86%A4-%ED%9A%8C%EA%B3%A0-%EB%8C%80%EC%83%81-Microsoft-%EC%82%AC%EC%9E%A5%EC%83%81-%EC%88%98%EC%83%81</link>
            <guid>https://velog.io/@zedy_dev/2023-HACKERS-GROUND-%ED%95%B4%EC%BB%A4%ED%86%A4-%ED%9A%8C%EA%B3%A0-%EB%8C%80%EC%83%81-Microsoft-%EC%82%AC%EC%9E%A5%EC%83%81-%EC%88%98%EC%83%81</guid>
            <pubDate>Fri, 27 Sep 2024 08:17:51 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/zedy_dev/post/d43c6141-23c8-4317-9e1f-da109fa39f64/image.png" alt=""></p>
<p>기간: 2023년 6월 21일 ~ 23일 (무박 3일)
장소: 대구 엑스코
주최: Microsoft, 경북대학교
성과: 대상 (Microsoft 사장상)
역할: 백엔드 (Java Springboot)</p>
<h4 id="들어가며">들어가며</h4>
<p>이보다 더 힘든 해커톤은 없었다.
무박 3일은 정말 너무 힘들었다. (와중에 나는 잠깐 쉬는 시간에 헬스장에서 운동하고 왔다...ㅋㅋㅋ)
대상 수상자 인터뷰를 하는데 정말 죽을꺼 같은 컨디션으로 인터뷰를 해서 마이크로소프트를 마이크로소프트웨어.. 라고 했다. 와..ㅎㅎ</p>
<hr>
<h1 id="참여-동기--사전-학습">참여 동기 &amp; 사전 학습</h1>
<h3 id="경주마가-된-듯한-미친-듯이-몰입할-수-있는-환경">경주마가 된 듯한 미친 듯이 몰입할 수 있는 환경</h3>
<p>해커톤은 언제나 재미있다. 물론 단순히 <strong>재미만을</strong> 위해 나가는 건 아니다. 해커톤은 <strong>타임 어택</strong>이라는 극한의 경쟁 속에서 내 <strong>실력과 한계를 시험</strong>해볼 수 있는 최적의 기회다. 제한된 시간 안에 아이디어를 구체화하고, 빠르게 문제를 해결하며, 내가 얼마나 발전했는지 확인할 수 있는 자리이기도 하다.</p>
<p>특히, 해커톤 현장에 있는 <strong>다른 개발자들</strong>을 보면 건강한 자극을 받는다. 비장한 눈빛, 빠른 손놀림, 그리고 노트북 위에 붙어 있는 수많은 스티커들이 그들의 <strong>개발 경험</strong>을 대변한다. (지금은 맥북을 써서 스티커를 붙이지 않지만, 그 당시에는 LG 울트라북을 썼었고, 나 역시 &#39;개발자&#39; 스러운 스티커로 노트북을 덕지덕지 꾸몄었다. 하지만 나보다 더한 사람들도 많았다. ㅋㅋ) 이런 분위기 속에서 나 역시 더 발전해야 한다는 <strong>동기부여</strong>를 얻게 된다.</p>
<p><del><em>가끔은 내가 말하는 감자처럼 느껴지기도 하지만...</em></del></p>
<h3 id="사전-교육이-있는-특별한-해커톤">사전 교육이 있는 특별한 해커톤</h3>
<p>이번 해커톤이 특별했던 점은, 단순히 대회만 치르는 것이 아니라 <strong>사전 교육 프로그램</strong>이 포함되어 있었다는 것이다. <strong>Microsoft의 Azure, Github, AI</strong>를 다루는 교육을 통해, 내가 몰랐던 새로운 기술들을 익힐 수 있었고, 마이크로소프트가 <strong>Azure와 AI</strong>에 진심이라는 걸 다시 한 번 느꼈다. 사전 교육에서 새로 알게된 기술들을 <strong>해커톤에서 꼭 써먹어보려고 노력</strong>했다. 물론 새로운 기술을 단기간에 활용하는 것은 매우 어렵다. 더군다나 Auzure는 워낙에 신기술이라, 구글링해서도 잘 안나왔기 때문에 많은 어려움이 있었다. 그래도 새로운 기술을 써본다는 사실에 감사!</p>
<h3 id="오직-성장을-위해-선택한-hackers-ground-해커톤">오직 &#39;성장&#39;을 위해 선택한 HACKERS GROUND 해커톤</h3>
<p>사실 이번 해커톤의 일정은 교내 해커톤과 겹쳐서 상당히 고민스러웠다. 교내 해커톤은 수상 가능성도 높았고 상금도 무려 300만원이었다. 그래서 처음에는 교내 해커톤에 출전하려고 생각했다.</p>
<p>하지만 결국 나는 Hackers Ground 해커톤을 선택했다. 그 이유는 오직 &#39;<strong>나의 성장</strong>&#39;이었다.</p>
<p>해커톤 세션에서 배운 기술들을 실제로 사용하기 위해서는 <strong>GitHub Copilot이나 Microsoft Azure 크레딧 같은 리소스</strong>가 필요했는데, 대회 측에서 이러한 기회를 제공해줬다. 덕분에 최신 기술을 직접 다루고 경험할 수 있었고, 그 과정에서 내가 개발자로서 <strong>더 많은 학습과 성장</strong>을 할 수 있을 것이라는 확신이 들었다. 이런 현실적인 지원과 자원 덕분에, <strong>단순한 이론적 학습을 넘어 실제 프로젝트에 적용하며 나의 역량을 더욱 키울 수 있었다.</strong></p>
<h1 id="팀-결성">팀 결성</h1>
<p>나는 팀이 따로 없어서 누구와 함께 나가야 할지 고민이 많았다. 그런데 마침 &#39;프론트엔드 짱 잘하는 사람이 백엔드 짱 잘하는 사람 구한다던데 언니 생각나서..!&#39;라며 감사하게도 먼저 제의가 들어 왔다. 근데 나중에 가서 이 연락을 준 사람이 기획 담당 팀원으로 들어왔다.ㅎㅎ 이 덕분에 팀을 결성하게 되었다.</p>
<p>해커톤 특성상 보여지는 MVP가 중요하기 때문에 백엔드보다는 프론트엔드의 중요성이 더 클 수 있다. 하지만 이번 해커톤 취지를 고려한 것도 있고, 세션에서 배운 기술을 실제로 적용해보고 싶었기 때문에, 나는 백엔드와 배포 부분에도 신경을 쓰고 싶다고 팀원들에게 어필했다. 이를 위해 배포와 서버 개발에 참여할 개발자 한 명을 더 구인하기로 했다.</p>
<p>우선 노션에 각자의 소개와 이력을 담아 작성하고, 어떤 사람을 찾는지에 대한 정리된 내용을 디스코드 팀 결성 방의 공고로 올렸다. 클라우드 사용 경험이 있는 참가자에게 컨택을 했더니 흔쾌히 응해 주었고, 미팅을 통해 그분과 팀 결성을 완료했다. (나이스한 선택이었다. 그는 전문가였다. 진짜로.. 지금은 삼성에서 클라우드 일하고 있더라..ㅎㅎ 덕분에 진짜 많이 배울 수 있었다.)</p>
<hr>
<h1 id="대회-주제--고려한-것들">대회 주제 &amp; 고려한 것들</h1>
<p>우선 대회의 큰 주제는 &#39;마이크로소프트 애저 클라우드를 활용해 지역 문제를 해결&#39;하는 것이다.
세부 주제는 6월 12일 공식 github issue 게시판을 통해 공개 되었다.</p>
<h2 id="주제-선정시-고려한-사항">주제 선정시 고려한 사항</h2>
<blockquote>
<p>💡 1“경북의 문제해결”; 현재로선 일자리 문제로 청년이탈</p>
</blockquote>
<blockquote>
<p>💡 2. “MS의 가치를 녹이기”;지구상의 모든 사람과 조직이 더 많은 것들을 성취할 수 있도록 힘을 실어주고자 하기</p>
</blockquote>
<blockquote>
<p>💡 3. 기술적 역량이 없으면 예선통과도 못 함. <strong>기술 구현도</strong> 중요</p>
</blockquote>
<p>즉,</p>
<ol>
<li><strong>경북 문제 해결</strong>을 하면서</li>
<li><strong>기업의 가치</strong>를 녹이고</li>
<li><strong>기업이 주목하는 기술</strong>을 구현하는
프로젝트를 진행하기로 하였다.</li>
</ol>
<p>이 흐름에 맞게 우리는 다같이 아이디어를 내고, 그중에서도 <strong>기존 플랫폼이 부재하고 가장 심각한 문제를 야기할 수 있는 문제를 해결</strong>하기로 결정했다.</p>
<p>이를 <strong>노션에 정리하며 아이디어를 구체화</strong>했다. 기존에 없는 솔루션을 만드는 거라 무엇보다 자료조사와 이 솔루션이 왜 필요한지 어필하는 것에 차별점을 뒀다. 디테일한 부분을 노션을 통해 함께 기록하며, 아이디어 피칭 시에 잘 활용할 수 있도록 도출했다.</p>
<hr>
<h1 id="프로젝트-소개">프로젝트 소개</h1>
<blockquote>
<p><strong>DEPS (Daegu Electric-car Platform Service): 대구시 전기차 인프라 구축 서비스</strong></p>
</blockquote>
<p><img src="https://velog.velcdn.com/images/zedy_dev/post/15b462c0-d8f5-4306-a27d-83fd6da54eba/image.png" alt=""></p>
<h3 id="주제-카센터의-잉여-자원을-활용한-대구시의-전기차-인프라-구축">주제: <strong>카센터의 잉여 자원을 활용한, 대구시의 전기차 인프라 구축</strong></h3>
<p><strong>DEPS</strong>는 지방 카센터의 <strong>잉여 자원</strong>을 활용해 대구시의 전기차 인프라 문제를 해결하는 서비스입니다. 지방 카센터는 이미 <strong>고압 전력 인입 공사</strong>가 완료된 상태이기 때문에, 이를 전기차 충전 인프라로 활용할 수 있는 가능성이 큽니다. 우리는 이 아이디어를 바탕으로 전기차 충전소 부족 문제를 해결하는 동시에, 카센터가 <strong>전기차 정비 인프라</strong>를 갖추게 하여 지방 전기차 유저와 카센터 모두에게 이익이 되는 솔루션을 제시합니다.</p>
<h3 id="문제">문제:</h3>
<ol>
<li><p><strong>대구·경북 지역의 전기차 수리 및 정비 인프라 부족</strong></p>
<ul>
<li>전기차 판매량은 급증하고 있지만, 지방에서는 전기차를 제대로 수리할 수 있는 카센터가 거의 없는 상황입니다. 특히 전기차 정비를 다룰 수 있는 곳은 전국적으로 2% 미만이며, 그 중 대부분이 수도권에 집중되어 있어 지방의 전기차 운전자들이 큰 불편을 겪고 있습니다.</li>
</ul>
</li>
<li><p><strong>지방의 전기차 충전소 부족</strong></p>
<ul>
<li>전기차 충전 인프라도 지방에서는 크게 부족한 상황입니다. 전국적으로 전기차 충전소는 많이 확충되고 있지만, <strong>완속 충전소</strong>가 대부분이며, <strong>급속 충전소</strong>는 여전히 부족합니다. 특히 대구·경북 지역은 전기차 충전소가 수도권에 비해 턱없이 부족한 실정입니다.</li>
</ul>
</li>
</ol>
<h3 id="해결-방안">해결 방안:</h3>
<h4 id="1-인프라-구축">1) <strong>인프라 구축</strong></h4>
<p>대구시와 협력하여 지방의 <strong>카센터에 전기차 충전소</strong>를 설치하는 것이 핵심입니다. 카센터는 이미 <strong>고압 전력 인입 공사</strong>가 완료된 상태로, 전기차 충전소를 설치하기 위한 기반이 마련되어 있습니다. 이를 활용해 카센터에 전기차 충전 설비를 구축하면, <strong>지방의 전기차 충전소 부족 문제</strong>를 효과적으로 해결할 수 있습니다. 또한, 전기차가 카센터를 방문하면서 자연스럽게 <strong>전기차 수리 및 정비 인프라</strong>도 함께 구축됩니다.</p>
<h4 id="2-어플리케이션-서비스">2) <strong>어플리케이션 서비스</strong></h4>
<p>카센터에 설치된 충전소를 기반으로, 전기차 사용자들이 <strong>쉽게 충전소를 찾고 예약할 수 있는 어플리케이션</strong>을 제공합니다. 이 앱은 전기차 사용자들이 <strong>충전소의 상태</strong>를 실시간으로 확인하고, <strong>충전소 예약</strong>, <strong>충전 속도 및 가격 확인</strong>, <strong>리뷰 남기기</strong>, <strong>네비게이션 안내</strong> 등 다양한 기능을 통해 <strong>편리한 충전 및 정비 경험</strong>을 할 수 있도록 지원합니다.</p>
<h3 id="어플리케이션-주요-기능">어플리케이션 주요 기능:</h3>
<ol>
<li><p><strong>예약 기능</strong>:  
 전기차 사용자는 자신의 일정에 맞추어 <strong>충전소를 예약</strong>할 수 있으며, 대기 시간 없이 바로 충전을 시작할 수 있습니다.</p>
</li>
<li><p><strong>정보 확인</strong>:  
 앱을 통해 각 카센터의 <strong>충전소 상태</strong>(충전 중, 고장, 예약 가능 여부 등)와 <strong>1킬로와트당 요금 및 충전기 속도</strong>를 확인할 수 있어, 자신에게 맞는 조건의 충전소를 선택할 수 있습니다.</p>
</li>
<li><p><strong>리뷰 남기기</strong>:  
 충전소를 이용한 사용자들이 <strong>리뷰</strong>를 남겨 다른 사용자들이 카센터 선택에 참고할 수 있도록 도와줍니다. 이 리뷰는 충전 서비스뿐만 아니라 <strong>정비 서비스</strong>를 평가할 때도 유용한 자료가 됩니다.</p>
</li>
<li><p><strong>네비게이션 안내</strong>:  
 앱에서 충전소를 선택하면 <strong>네비게이션</strong> 기능을 통해 해당 충전소까지 바로 안내해줍니다. 별도의 다른 플랫폼 없이 앱에서 모든 절차를 한 번에 해결할 수 있습니다.</p>
</li>
</ol>
<h3 id="기대-효과">기대 효과:</h3>
<ol>
<li><p><strong>지방 전기차 유저를 위한 인프라 구축</strong><br> DEPS는 전기차 충전 및 정비 인프라가 부족한 지방에서 <strong>지속적으로 인프라를 확충</strong>하여, 전기차 사용자들이 보다 편리하게 차량을 관리할 수 있도록 돕습니다.</p>
</li>
<li><p><strong>카센터 시장의 새로운 가치 창출</strong><br> 카센터는 전기차 충전소 설치를 통해 기존의 고압 전력 인프라를 효율적으로 활용하며, <strong>부가적인 수익</strong>을 창출할 수 있습니다. 또한, 전기차 차주들이 충전소를 방문하면서 자연스럽게 <strong>정비 서비스</strong>에 대한 접근성을 높여, <strong>전기차 정비 숙련도</strong>가 향상될 것입니다.</p>
</li>
</ol>
<hr>
<h1 id="나의-역할-백엔드-설계-및-개발">나의 역할: 백엔드 설계 및 개발</h1>
<p>본격적인 개발에 돌입하기 앞서, 나는 <strong>소프트웨어 설계, API 문서화</strong> 단계에서 많은 시간을 투자했다. 해커톤 특성상 시간이 제한되어 있기 때문에, <strong>설계가 탄탄해야 개발 과정에서 발생할 수 있는 문제를 최소화</strong>할 수 있다고 생각했다. 여러 명의 팀원이 함께 작업하는 만큼, <strong>명확한 구조와 역할 분담</strong>이 필요했다. 특히, 프론트엔드 팀원과의 협업을 원활하게 하기 위해 <strong>Swagger와 Notion를 활용하여 API 문서화</strong>를 진행했다.</p>
<h2 id="중점을-둔-부분">중점을 둔 부분</h2>
<h3 id="1-아키텍처-설계">1. <strong>아키텍처 설계</strong></h3>
<ul>
<li><strong>스프링부트 기반의 계층형 아키텍처</strong>를 체계적으로 설계했다. API 구조, 데이터베이스 스키마, 그리고 각 기능별 책임을 명확하게 정의하는 데 중점을 두었다. <strong>Controller, Service, Repository, Domain</strong>로 나누어 각각의 책임을 분리하고, 기능 확장과 유지보수가 용이하도록 설계했다.</li>
</ul>
<h3 id="2-데이터-흐름과-동시성-제어">2. <strong>데이터 흐름과 동시성 제어</strong></h3>
<ul>
<li><strong>충전소 예약 시스템</strong>에서 중요한 부분은 <strong>동시성 문제</strong>를 어떻게 처리할 것인지였다. 여러 사용자가 동시에 예약을 시도할 경우, <strong>중복 예약이 발생하지 않도록</strong> 데이터베이스 트랜잭션 관리와 <strong>동시성 제어</strong>에 대한 설계가 필요했다. </li>
<li>이를 위해 스프링의 <strong>@Transactional</strong> 어노테이션을 사용하여 <strong>트랜잭션을 관리</strong>하고, <strong>데이터의 일관성을 보장</strong>했다. 또한, <strong>트랜잭션 범위 내에서 데이터베이스 작업</strong>을 처리해, 서로 다른 요청이 충돌하거나 중복되지 않도록 설계했다. 이를 통해 여러 사용자가 같은 시간대에 동일한 충전소를 예약하려고 할 때 발생할 수 있는 동시성 문제를 방지할 수 있었다.</li>
</ul>
<h3 id="3-데이터베이스-스키마-설계">3. <strong>데이터베이스 스키마 설계</strong></h3>
<ul>
<li><strong>충전소 정보</strong>, <strong>예약 내역</strong>, <strong>사용자 정보</strong>가 유기적으로 연동될 수 있도록 <strong>데이터베이스 스키마</strong>를 설계했다. 이를 위해 <strong>UML(Unified Modeling Language)</strong> 을 활용하여 <strong>ERD(엔터티 관계 다이어그램)</strong> 을 작성했고, 각 테이블 간의 관계를 명확히 정의했다. 이를 통해 시스템의 구조를 시각적으로 파악하면서, <strong>데이터 간의 연관성</strong>을 명확히 하고, <strong>정규화된 데이터 구조</strong>를 적용하여 데이터의 중복을 최소화하고 효율적인 관리가 가능하도록 설계했다.</li>
<li>특히, <strong>충전소 상태 업데이트</strong>가 실시간으로 반영되도록 <strong>예약 내역과 충전소 정보 간의 상호 연동</strong>을 구현했다. 예약이 완료되거나 충전소 상태가 변경될 때마다, 예약 테이블과 충전소 정보 테이블 간의 데이터가 일관성 있게 업데이트되도록 <strong>애플리케이션 레벨에서 트랜잭션을 활용해 처리</strong>했다. 이를 통해 사용자에게 <strong>최신 상태의 충전소 정보</strong>를 제공할 수 있었다.</li>
<li>또한, <strong>충전소 ID</strong>나 <strong>예약 상태</strong>와 같은 주요 필드에 <strong>인덱스</strong>를 설정해 데이터 검색 속도를 최적화했다. 이를 통해 <strong>대량의 데이터에도 신속하게 접근</strong>할 수 있는 구조를 마련했다.</li>
</ul>
<h3 id="4-api-설계">4. <strong>API 설계</strong></h3>
<ul>
<li><p><strong>프론트엔드</strong>와의 원활한 통신을 위해 <strong>RESTful API 설계 원칙</strong>을 철저히 적용했다. 각 엔드포인트는 <strong>리소스 중심적</strong>으로 설계되었으며, <strong>명사형</strong>으로 일관성 있게 정의하였다. RESTful API 설계 방법론을 따르기 위해, 각 URL 경로는 <strong>복수형 명사</strong>를 사용하여 리소스를 표현했고, 작업의 종류는 <strong>HTTP 메서드(GET, POST, PUT, DELETE)</strong>로 명확히 구분했다.</p>
<ul>
<li>예를 들어, <strong>충전소 정보</strong>를 다루기 위해 <code>GET /charging-stations</code>로 충전소 목록을 조회하고, <strong>예약 관리</strong>의 경우 <code>POST /reservations</code>로 새로운 예약을 생성하도록 설계했다.</li>
</ul>
</li>
<li><p>이러한 <strong>RESTful 방법론</strong>을 적용하여 API 설계가 <strong>명확하고 직관적</strong>이게 되었으며, 프론트엔드 개발자들이 쉽게 <strong>API를 이해하고 활용</strong>할 수 있도록 했다. 또한, <strong>데이터의 일관성</strong>과 <strong>성능 최적화</strong>를 고려하여 필요한 API만 설계함으로써 <strong>불필요한 호출을 최소화</strong>했다. 이를 통해 <strong>서비스 응답 시간</strong>도 빠르게 유지할 수 있었고, 시스템 전반의 효율성을 높일 수 있었다.</p>
</li>
<li><p>URL 경로의 <strong>명명 규칙</strong>을 철저히 적용하면서도, 각 엔드포인트가 실제 비즈니스 로직에 맞게 일관성 있게 구성되었기 때문에 <strong>API 문서화</strong> 작업 또한 원활하게 진행되었다. 특히, Swagger를 사용한 <strong>자동 API 문서화</strong>를 통해 프론트엔드 개발자가 요청 및 응답 형식을 쉽게 확인하고, 테스트할 수 있도록 했다.</p>
</li>
</ul>
<hr>
<h2 id="느낀-점">느낀 점</h2>
<h3 id="설계의-중요성">설계의 중요성</h3>
<ul>
<li><p>이러한 설계 과정을 통해, 개발 중간에 발생할 수 있는 <strong>구조적인 문제</strong>들을 미리 방지하고, 기능을 확장하거나 수정할 때도 큰 어려움 없이 진행할 수 있었다. 특히 해커톤이라는 제한된 시간 안에서, <strong>명확한 설계</strong>는 팀원들 간의 <strong>원활한 협업</strong>을 가능하게 해주었고, <strong>개발 속도를 크게 향상</strong>시킬 수 있었다.</p>
</li>
<li><p>설계를 통해 <strong>코드를 짜는 데 걸리는 시간</strong>은 오히려 줄어들었고, 그만큼 <strong>더 높은 품질의 결과물</strong>을 빠르게 완성할 수 있었다.</p>
</li>
</ul>
<h3 id="인공지능은-대단하다">인공지능은 대단하다</h3>
<ul>
<li>이번 해커톤에서 <strong>ChatGPT와 Copilot</strong>을 적극적으로 사용하면서, 진짜 좋은 <strong>어시스턴트</strong>가 생긴 것 같아 너무 좋았다. 특히, 내가 설계한 내용을 빠르게 코드화하는 데 있어 1등 공신은 인공지능이었다. 정말 AI 덕분에 개발 속도가 한층 빨라졌고, 반복 작업도 훨씬 수월해졌다. 땡큐~ㅎ</li>
</ul>
<h3 id="협업과-팀워크">협업과 팀워크</h3>
<ul>
<li><p><strong>노션</strong>은 팀 협업에 필수적인 도구임을 다시 한 번 실감했다. 회의록을 기록하고, 아이디어를 구체화하는 과정에서 팀원들과의 <strong>원활한 소통</strong>을 위해 너무나 편리했다. 각자 생각을 문서에 정리하고 공유하는 과정이 빠르고 효율적으로 이루어졌고, 실시간으로 수정 가능한 점도 특히 유용했다.</p>
</li>
<li><p>해커톤에 앞서 팀이 결성되었고, 대회 전에 꾸준히 회의를 진행하면서 서로의 <strong>의견을 공유</strong>하고 아이디어를 발전시킬 수 있었다. 덕분에 참신한 아이디어가 많이 나왔고, 해커톤에 돌입하기 전에 이미 <strong>아이디어의 방향성을 명확히 설정</strong>할 수 있었다. 이 과정이 없었다면 해커톤 당일 더 많은 혼란이 있었을 텐데, 미리 준비한 덕분에 실질적인 개발에 집중할 수 있었다.</p>
</li>
</ul>
<h3 id="규모가-큰-해커톤의-압도적인-현장감"><strong>규모가 큰 해커톤</strong>의 압도적인 현장감</h3>
<ul>
<li>많은 해커톤에 참여해왔지만, 이번 해커톤은 <strong>엑스코</strong>가 꽉 차도록 약 300명 이상의 참가자가 있었고, 그 <strong>현장감</strong>은 정말 압도적이었다. 그만큼 <strong>경쟁심</strong>도 생기고, 나도 더 잘하고 싶다는 마음이 강하게 들었다. <strong>다른 참가자들</strong>의 열정을 직접 느끼며, 나 또한 한계를 넘어 더 성장하고자 하는 동기부여를 얻었다.</li>
</ul>
<h3 id="개발에-대한-깊은-고민"><strong>개발에 대한 깊은 고민</strong></h3>
<ul>
<li>특히나 <strong>효율적인 개발</strong>에 대한 고민을 많이 했다. 단순히 기능을 구현하는 것에 그치지 않고, <strong>설계 측면</strong>에서 많은 고민을 한 것이 크게 도움이 되었다. 이 덕분에 개발 속도와 효율성이 모두 향상되었고, 프로젝트 전체의 <strong>완성도</strong>가 높아졌다. 해커톤 특유의 <strong>빠른 속도</strong>와 <strong>설계의 중요성</strong>을 다시 한 번 체감하게 된 좋은 기회였다.</li>
</ul>
<h3 id="무박-3일은-좀-많이-힘들었다">무박 3일은 좀 많이 힘들었다..</h3>
<ul>
<li><p>아 근데.. 무박 3일은 진짜 힘들었다. 씻을 공간도 없고, 제대로 쉴 수 있는 장소도 마땅치 않았다. 휴식을 위한 공간은 있긴 했지만, 그저 빈백 몇 개가 놓인 간이 부스 같은 곳이어서 편히 쉬기엔 턱없이 부족했다. 그래도 나는 근처 헬스장에서 운동도 하고 목욕도 하고 오니 기분이 한결 나아졌다. 진짜 죽을 것 같은 피곤한 컨디션이었는데, 그렇게라도 리프레시가 되니까 ‘아, 이게 되네?’ 싶더라...ㅋㅋ</p>
</li>
<li><p>당시 나는 매일 아침 9시부터 11시까지 아르바이트를 하고 있었다. 다행히 근무지가 버스로 20분 거리라 팀원들에게 양해를 구하고 다녀올 수 있었지만, 남들이 다 자고 쉬는 시간에 나는 출근해서 일하고 오니 현타가 오더라. 그래도 해커톤 끝까지 참여하고, 그 와중에 아르바이트도 해낸 나 자신이 대견하다!</p>
</li>
</ul>
<hr>
<h2 id="기여한-점">기여한 점</h2>
<h3 id="1-철저한-설계를-통한-명확한-개발-방향-제시">1. 철저한 설계를 통한 명확한 개발 방향 제시</h3>
<ul>
<li>이번 프로젝트에서 나는 <strong>철저한 설계</strong>에 집중했다. 처음부터 시스템의 <strong>아키텍처</strong>와 <strong>데이터베이스 구조</strong>를 명확하게 설계하면서, 팀원들에게 <strong>개발 방향</strong>을 구체적으로 제시할 수 있었다. </li>
<li>충전소와 예약 시스템의 상호작용에서 발생할 수 있는 <strong>동시성 문제와 연관관계</strong>를 미리 고려해 설계를 완료한 덕분에, 개발 중간에 발생할 수 있었던 <strong>구조적인 오류</strong>를 사전에 방지할 수 있었다.  </li>
<li>설계가 탄탄했기 때문에 팀원들이 개발에만 집중할 수 있었고, 덕분에 <strong>빠른 속도로 작업을 진행</strong>할 수 있었다. 이 과정에서 미리 설계한 <strong>데이터 흐름</strong>을 기반으로, 서로 역할을 나누고 더 효율적으로 협력할 수 있었다.</li>
</ul>
<h3 id="2-아이디어-피칭-및-ir-흐름-구체화">2. 아이디어 피칭 및 IR 흐름 구체화</h3>
<ul>
<li>나는 예비창업팀을 이끌면서 여러 차례 <strong>IR 피칭</strong>을 해본 경험이 있다. 그래서 이번 해커톤에서도 아이디어를 체계적으로 전달하는 데 내가 많이 기여했다. 특히, 우리의 솔루션을 설명할 때 <strong>PSST 흐름</strong>(문제점, 해결책, 강점, 성과)을 기반으로 피칭 구조를 짰다.  </li>
<li>피칭을 준비하면서, 팀원들이 제안하는 아이디어를 <strong>구체화하고 논리적으로 정리</strong>하였다. 특히, 전기차 충전소 부족 문제를 단순한 기술적 이슈가 아니라, <strong>지방 카센터와의 협력</strong>이라는 실질적인 해결책으로 풀어나가는 아이디어를 중점적으로 구체화했다. 덕분에 발표할 때, 심사위원들에게 <strong>더 설득력 있게 전달</strong>할 수 있었다.</li>
</ul>
<h3 id="3-커뮤니케이션-및-문제-해결">3. 커뮤니케이션 및 문제 해결</h3>
<ul>
<li>이번 프로젝트에서 나는 <strong>커뮤니케이션</strong>에 특히 신경을 많이 썼다. 해커톤 특성상 시간이 촉박한 상황에서 팀 내에서 <strong>의견이 충돌</strong>하거나 <strong>기술적인 이슈</strong>가 발생하면 빠르게 해결하지 않으면 전체 일정이 지연될 수 있었다.  </li>
<li>특히 백엔드 개발 도중 <strong>배포 과정</strong>에서 문제가 발생했을 때, 나는 팀원들과 빠르게 의견을 교환하고 <strong>현장에 있던 기술 자문단</strong>과 소통해 해결책을 찾았다. 배포 환경 설정에 대한 이슈가 있었는데, 나는 상황을 구체적으로 설명하며 자문을 구하고, 그 피드백을 바탕으로 <strong>빠르게 문제를 해결</strong>할 수 있었다.</li>
</ul>
<hr>
<h2 id="성장과-배운-점">성장과 배운 점</h2>
<p>솔직히 말하자면, 대부분의 사람들이 <strong>AWS</strong>를 사용하니까 나도 자연스럽게 따라 사용했지만, <strong>정확한 흐름</strong>을 이해하지 못한 채 <strong>무지성</strong>으로 이용한 적이 많았다. 하지만 이번 프로젝트에서 <strong>Microsoft Azure</strong>를 직접 사용해보고, 실제로 적용해보면서 배포와 관련된 많은 것들을 배울 수 있었다.</p>
<p>이번 해커톤에서 <strong>배포 담당 팀원</strong>이 이 부분을 주도적으로 진행했기 때문에 내가 <strong>기여한 부분은 적었지만</strong>, 옆에서 함께 작업하면서 <strong>정말 많은 것을 배웠다</strong>. 특히, Azure에서 제공하는 다양한 서비스들을 접하면서 <strong>자동화 배포의 중요성</strong>과 그 <strong>효율성</strong>을 몸소 느꼈다.</p>
<h3 id="사용한-기능들">사용한 기능들</h3>
<ul>
<li><p><strong>Azure App Service</strong>: 기존에는 백엔드 서버를 운영하기 위해 직접 환경을 구축해야 했지만, <strong>PaaS(Platform as a Service)</strong>인 <strong>Azure App Service</strong> 덕분에 서버 환경을 따로 설정하지 않고, <strong>개발에만 집중</strong>할 수 있었다.</p>
</li>
<li><p><strong>Visual Studio App Center</strong>: 우리가 <strong>React Native</strong>로 제작한 애플리케이션을 <strong>iOS</strong>와 <strong>Android</strong>로 빌드하는 데 사용했다. 앱 배포를 쉽게 관리할 수 있었던 점이 큰 도움이 됐다.</p>
</li>
<li><p><strong>Azure Database for MySQL</strong>: <strong>DB 구축</strong> 시 예상되는 부하 문제나 환경 설정 등에 대한 우려 없이, <strong>완전 관리형</strong>인 <strong>Azure Database for MySQL</strong>을 통해 <strong>즉시 DB를 활용</strong>할 수 있었다.</p>
</li>
<li><p><strong>Github Actions</strong>: <strong>작업한 코드를 Github에 올리기만 해도 자동으로 서버에 배포</strong>되는 구조를 구현할 수 있었다. 덕분에 수작업으로 배포하던 과정을 완전히 자동화할 수 있었고, <strong>배포 프로세스가 매우 간편</strong>해졌다.</p>
</li>
<li><p><strong>Bicep</strong>: Azure에서 클릭과 타이핑으로 진행하던 작업을 <strong>Bicep 스크립트 파일</strong> 하나로 실행해 빠르게 배포할 수 있었다. 이를 통해 <strong>인프라 배포의 자동화</strong>도 가능하다는 점을 배웠다.</p>
</li>
</ul>
<h3 id="성장한-부분">성장한 부분</h3>
<p>이 경험을 통해 단순히 Azure 사용법만 배운 게 아니라, <strong>자동화 배포의 중요성</strong>을 깨닫게 되었다. 이전에는 수작업으로 설정하고 배포하는 방식이 익숙했지만, <strong>자동화된 CI/CD 파이프라인</strong>을 구축하면서 효율성을 크게 향상시킬 수 있었다. 덕분에, 이후 다른 프로젝트에서는 내가 주도적으로 <strong>Github Actions를 활용해 직접 CI/CD 파이프라인을 구축</strong>하고, <strong>AWS 배포</strong>도 진행할 수 있게 되었다.</p>
<p>이번 경험은 나에게 <strong>배포 과정의 효율성과 자동화</strong>가 얼마나 중요한지를 깨닫게 해줬다. 특히, 이를 통해 내가 앞으로 더 <strong>효율적으로 프로젝트를 관리</strong>하고, <strong>빠르게 배포</strong>하는 능력을 기를 수 있었다는 점에서 큰 성장을 이뤘다고 느낀다.</p>
<hr>
<h2 id="소감">소감</h2>
<p><img src="https://velog.velcdn.com/images/zedy_dev/post/682e2318-a368-460e-a716-2b84fa2ad542/image.png" alt="">
<del><em>다시 보니까 꼬질꼬질... 진짜 수고했다..</em></del></p>
<blockquote>
<p>대상 !!!</p>
</blockquote>
<p><strong>죽어라 달린 만큼 좋은 성과</strong>를 거둘 수 있어서 너무 기쁘다. 수상 호명이 계속 안 들리길래, 솔직히 마음을 내려놓고 있었는데, 팀원들에게 &quot;그래도 OOO님이랑 같은 팀을 해서 정말 너무 좋았어요. 너무 수고하셨어요 정말. ^^ 결과에 연연하지 않고 정말 소중한 인연을 만들어 가네요~^^&quot; 이러고 있었다. 근데 그러면서도 속으로는 <strong>혹시나!</strong> 하는 마음에 내심 기대하고 있었다.</p>
<p><strong>무엇보다도 우리 팀</strong> 모두가 너무 고생했고, 정말 열심히 노력했다는 걸 알기에, 마음속으로는 &quot;<strong>제발 대상 ㅠㅠㅠㅠ 으허헝ㅇ 제발 ㅠㅠ</strong>&quot; 이러고 있었다.<br>그리고 <strong>대상</strong>이 호명되는 순간, 믿기지 않아서 멍하니 있다가 우리 팀 이름이 맞다는 걸 확인하고서야 <strong>정신이 번쩍</strong> 들었다. 다들 <strong>서로 고생했다며 얼싸안고 기쁨을 나누던 그 순간</strong>이 정말 잊을 수 없었다. 무박 3일 동안의 <strong>고생한 보람</strong>이 있었다... 정말... 이건 해커톤을 가장한 나와의 싸움이었다. 수면욕, 식욕 다 참아가면서 해야 할 일을 해냈다는 게 진짜 <strong>인생에 도움이 될 것 같은 경험</strong>이었다. 극한을 경험해보면 다른 일은 더 쉽게 느껴진다는데...ㅎ</p>
<p>해커톤 내내 우리가 얼마나 <strong>열심히 준비</strong>했고, 서로에게 <strong>얼마나 의지</strong>하며 달려왔는지 생각하니 더 뭉클했다. 사실 결과에 상관없이 <strong>함께 만들어간 과정</strong>이 너무 소중했지만, 이렇게 <strong>최고의 결과</strong>로 마무리할 수 있어서 의미가 더 컸다. <strong>혼자서는 절대 할 수 없는 일</strong>이었고, <strong>팀워크</strong>가 정말 중요했다. 우리 팀이 <strong>각자의 역할을 충실히</strong> 해줬기 때문에 이 상을 받을 수 있었다고 생각한다. <del>(나는 특히나 <strong>Chocolate Provider</strong>로 톡톡히 기여했다는 점! ^^ㅎ)</del></p>
<p>솔직히 이번 해커톤을 통해 <strong>극한의 정신력</strong>을 경험하면서, <strong>개인적으로 부족한 부분</strong>도 많이 느꼈고, 그걸 스스로 <strong>이겨냈다는 사실이 대견</strong>했다. 또 <strong>팀원들에게서 배운 것</strong>도 많았다. 특히 <strong>배포 과정</strong>에서 내가 부족하다는 걸 깨달았고, 덕분에 더 <strong>성장할 수 있는 계기</strong>가 되었다. <strong>팀원들과 서로 도와가며 최선을 다한 경험</strong>이 이번 해커톤의 가장 큰 수확이었다.</p>
<p>다시 한 번 <strong>팀원들 모두에게 감사</strong>하고, 끝까지 최선을 다해준 덕분에 이런 좋은 결과를 얻었다는 말을 전하고 싶다. 그리고 <strong>좋은 인연</strong>을 만들어준 이번 해커톤이, 앞으로도 우리 모두에게 <strong>좋은 기억으로 남을 것</strong> 같다.</p>
<p>수상을 할 때 만난 <strong>양금희 의원님</strong>과 <strong>김현덕 단장님</strong>! 대구 청년체험단을 하면서 개인적으로도 자주 뵈었었는데, 이렇게 또 <strong>청년체험단</strong>으로서 <strong>성과를 낸 것 같아</strong> 더욱 뿌듯했다. 하하하.</p>
<p><strong>잘했다! 수고했다! 기특하다!</strong></p>
<hr>
<h2 id="기술-스택">기술 스택:</h2>
<h3 id="1-backend">1. <strong>Backend</strong></h3>
<ul>
<li><strong>Spring Boot</strong>: 백엔드 프레임워크로 사용하여 RESTful API 설계 및 비즈니스 로직을 구현.</li>
<li><strong>MySQL</strong>: 프로젝트의 데이터베이스로 사용. Azure Database for MySQL을 활용해 데이터 관리와 쿼리 최적화를 진행.</li>
<li><strong>JPA/Hibernate</strong>: 데이터베이스와의 상호작용을 쉽게 하기 위해 JPA와 Hibernate를 사용하여 객체 관계 매핑(ORM)을 구현.</li>
<li><strong>Microsoft Azure</strong>: 프로젝트 배포 및 데이터베이스 호스팅.</li>
</ul>
<h3 id="2-devops--cicd">2. <strong>DevOps / CI/CD</strong></h3>
<ul>
<li><strong>Github Actions</strong>: <strong>CI/CD 파이프라인</strong>을 구축하여, 코드가 푸시될 때마다 자동으로 테스트를 실행하고 <strong>Azure</strong>에 배포 자동화. 배포의 효율성을 크게 향상시킴.</li>
<li><strong>Azure App Service</strong>: 백엔드 애플리케이션을 호스팅하기 위함. 기존의 복잡한 서버 설정을 생략하고, 쉽게 애플리케이션을 배포할 수 있었음.</li>
<li><strong>Bicep</strong>: Azure 리소스 배포를 위한 <strong>인프라 자동화 도구</strong>로 사용. 이를 통해 <strong>반복적인 작업을 자동화</strong>하고 배포 속도를 높임.</li>
<li><strong>Visual Studio App Center</strong>: <strong>React Native</strong>로 개발된 애플리케이션을 <strong>iOS</strong>와 <strong>Android</strong>로 빌드하고, 배포 과정을 관리함.</li>
</ul>
<h3 id="3-version-control">3. <strong>Version Control</strong></h3>
<ul>
<li><strong>Git</strong>: 팀 협업을 위해 <strong>Git</strong>을 사용해 코드 관리.</li>
<li><strong>Github</strong>: 프로젝트의 관리를 위해 <strong>Github</strong>를 사용했으며, 대회의 전반적인 진행이 깃허브를 사용함.</li>
</ul>
<h3 id="4-aicode-assistance">4. AI/Code Assistance</h3>
<ul>
<li><strong>GitHub Copilot</strong>: 인공지능 기반 코드 보조 도구로, 개발 과정에서 자동으로 코드 제안을 받을 수 있었음.특히 반복적인 코딩 작업이나 문법 오류를 줄이는 데 큰 도움이 되었고, 코드 작성 속도를 크게 높임.</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[커리어 조언]]></title>
            <link>https://velog.io/@zedy_dev/%EC%BB%A4%EB%A6%AC%EC%96%B4-%EC%A1%B0%EC%96%B8</link>
            <guid>https://velog.io/@zedy_dev/%EC%BB%A4%EB%A6%AC%EC%96%B4-%EC%A1%B0%EC%96%B8</guid>
            <pubDate>Wed, 14 Aug 2024 09:14:49 GMT</pubDate>
            <description><![CDATA[<p>오늘 얻은 인사이트를 바탕으로 앞으로 성장할 수 있도록, 각각의 인사이트에 대해 좀 더 구체적이고 상세하게 기록해 보겠다. 기록을 통해 목표를 명확히 하고, 이를 실천해 나가는 데 도움을 줄 수 있을 것이다.</p>
<hr>
<h3 id="1-내가-진정으로-원하는-것을-확립하기">1. <strong>내가 진정으로 원하는 것을 확립하기</strong></h3>
<p><strong>인사이트:</strong>  </p>
<ul>
<li>내가 원하는 것은 개발자로서의 커리어를 통해 매니저로 성장하고, 궁극적으로 나만의 프로덕트를 내놓는 것이다.</li>
<li>외국계 대기업 한국 지사에서 개발자로 다양한 경험을 쌓는 것고 싶다. -&gt; 좋은 생각이다 !</li>
<li>외국계 대기업에서의 경험은 글로벌한 관점에서 성장할 기회를 줄 수 있지만, 스타트업에서는 창업적인 경험과 함께 빠른 성장을 경험할 수 있다.</li>
</ul>
<p><strong>적용 방안:</strong>  </p>
<ul>
<li><strong>목표 설정:</strong> 먼저 내가 더 원하는 것이 무엇인지 명확히 해야 한다. 이를 위해 <code>개발자로서의 경험</code>과 <code>창업 경험</code>이 주는 장단점을 비교해 보자.</li>
<li><strong>행동 계획:</strong> 구글 코리아와 같은 외국계 대기업을 목표로 취업 준비를 하되, 스타트업 경험도 함께 탐색할 수 있는 네트워킹을 지속적으로 하자.</li>
<li><strong>주기적인 점검:</strong> 매월 자신이 원하는 커리어 방향이 잘 설정되고 있는지 점검하고, 필요시 새로운 정보를 수집하여 방향성을 재조정한다.</li>
</ul>
<hr>
<h3 id="2-인생은-길다-조급해하지-말자">2. <strong>인생은 길다, 조급해하지 말자</strong></h3>
<p><strong>인사이트:</strong>  </p>
<ul>
<li>지금은 취업에 대한 압박감이 크지만, 인생의 첫 직장이 내 커리어를 결정짓는 것은 아니다.</li>
<li>인생의 성공과 실패는 취업에서만 오는 것이 아니며, 오히려 장기적인 목표와 방향성이 더 중요하다.</li>
<li>지금 당장 미국 진출이나 대학원 진학이 어려울 수 있지만, 그것이 끝이 아니며 여전히 많은 기회가 있을 것이다.</li>
</ul>
<p><strong>적용 방안:</strong>  </p>
<ul>
<li><strong>마인드셋 전환:</strong> 현재의 상황에 대한 압박감을 줄이고, 장기적인 시각에서 자신의 커리어를 바라보자.</li>
<li><strong>단계적 목표 설정:</strong> 현재의 목표를 달성한 후, 그다음 단계에 대한 계획을 세우자. 예를 들어, 대기업 입사 후 3~5년 후에는 어떤 경험을 더 쌓고 싶은지 구체적으로 계획해본다.</li>
<li><strong>자기 관리:</strong> 성공 강박에서 벗어나기 위해 명상, 운동 등 스트레스 관리 방법을 통해 정신적, 신체적 건강을 유지한다.</li>
</ul>
<hr>
<h3 id="3-한-가지에-집중하기">3. <strong>한 가지에 집중하기</strong></h3>
<p><strong>인사이트:</strong>  </p>
<ul>
<li>다양한 관심사와 분야에 대한 욕심이 크지만, 현재는 개발자로서의 커리어에 집중해야 한다.</li>
<li>비개발 분야의 공부와 경험은 중요하지만, 동시에 여러 가지를 시도하다 보면 하나도 제대로 해내지 못할 위험이 있다.</li>
<li>인생은 길고, 한 번에 모든 것을 이루려고 하기보다는 차근차근 이루어 나가는 것이 더 중요하다.</li>
</ul>
<p><strong>적용 방안:</strong>  </p>
<ul>
<li><strong>우선순위 설정:</strong> 현재는 개발자로서의 성장에 집중하고, 일정 목표에 도달한 후 비개발 분야에 도전하자.</li>
<li><strong>시간 관리:</strong> 매일 개발 관련 학습 시간을 일정하게 확보하고, 비개발 분야는 취미나 여가 시간에 즐기며 점차적으로 익혀 나가자.</li>
<li><strong>성장 일지:</strong> 개발자로서 성장하고 있는 과정을 기록하고, 그동안의 성취와 앞으로의 목표를 주기적으로 검토한다.</li>
</ul>
<hr>
<h3 id="4-자기-사랑하기">4. <strong>자기 사랑하기</strong></h3>
<p><strong>인사이트:</strong>  </p>
<ul>
<li>현재 느끼는 조급함, 열등감, 그리고 성공에 대한 강박은 결국 자신을 충분히 사랑하지 못해서 오는 감정이다.</li>
<li>자신을 사랑하지 못하면, 다른 사람들이 나를 평가하는 시선에 더욱 민감해지고, 그것이 결국 나의 자존감에 부정적인 영향을 미친다.</li>
</ul>
<p><strong>적용 방안:</strong>  </p>
<ul>
<li><strong>자기 인식 훈련:</strong> 매일 아침 거울을 보며 자신에게 긍정적인 말을 건네고, 스스로를 인정하는 연습을 하자.</li>
<li><strong>자기 보상:</strong> 작은 성취에도 자신을 칭찬하고 보상하는 습관을 들여 긍정적인 자기 평가를 강화한다.</li>
<li><strong>감정 일기:</strong> 하루 동안 느꼈던 감정들을 기록하고, 스스로에 대한 부정적인 생각이 들 때마다 그것을 긍정적으로 바꾸는 연습을 하자.</li>
</ul>
<hr>
<h3 id="5-일단-부딪히며-성장하기">5. <strong>일단 부딪히며 성장하기</strong></h3>
<p><strong>인사이트:</strong>  </p>
<ul>
<li>실제로 일을 하면서 배우는 것이 가장 크다. 두려워하지 말고 다양한 경험을 통해 성장할 기회를 만들자.</li>
<li>이론적으로 준비하는 것보다 실전에서 부딪히며 배우는 것이 더 효과적이고, 성장 속도도 빠르다.</li>
</ul>
<p><strong>적용 방안:</strong>  </p>
<ul>
<li><strong>도전 정신:</strong> 새로운 프로젝트나 어려운 과제에 적극적으로 참여하여, 실전에서 경험을 쌓는다.</li>
<li><strong>실패 수용:</strong> 실패를 두려워하지 말고, 실패를 통해 배운 교훈을 기록하여 다음에 더 나은 결과를 만들 수 있도록 한다.</li>
<li><strong>네트워킹:</strong> 다양한 분야의 사람들과 네트워킹을 통해 새로운 기회를 탐색하고, 자신이 할 수 있는 다양한 역할을 모색해본다.</li>
</ul>
<hr>
<p>조급해하지 말기</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[백준] 14500]]></title>
            <link>https://velog.io/@zedy_dev/%EB%B0%B1%EC%A4%80-14500-6u5kcndf</link>
            <guid>https://velog.io/@zedy_dev/%EB%B0%B1%EC%A4%80-14500-6u5kcndf</guid>
            <pubDate>Tue, 13 Aug 2024 13:15:14 GMT</pubDate>
            <description><![CDATA[<h3 id="백준-14500번-테트로미노-문제-풀이">백준 14500번: 테트로미노 문제 풀이</h3>
<p>이번 포스팅에서는 백준 14500번 문제인 &quot;테트로미노&quot; 문제를 해결하는 과정을 정리하겠습니다. 이 문제는 4가지 종류의 테트로미노 모양을 2차원 배열에서 최적의 위치에 배치하여 얻을 수 있는 최대 합을 구하는 문제입니다.</p>
<h4 id="문제-분석">문제 분석</h4>
<p>문제는 주어진 2차원 배열에 다양한 테트로미노 모양을 놓았을 때, 합이 최대가 되는 경우를 찾는 것입니다. 테트로미노는 다양한 모양을 가지고 있으며, 이 문제에서는 총 5가지의 모양이 주어집니다.</p>
<h4 id="테트로미노-모양-분석">테트로미노 모양 분석</h4>
<ol>
<li><strong>1자형</strong> (I 모양)</li>
<li><strong>정사각형</strong> (O 모양)</li>
<li><strong>ㄴ자형</strong> (L, J 모양)</li>
<li><strong>ㄹ자형</strong> (Z, S 모양)</li>
<li><strong>ㅗ자형</strong> (T 모양)</li>
</ol>
<p>각 테트로미노는 회전이나 대칭을 통해 다양한 배치를 가질 수 있습니다. 모든 가능한 배치를 고려하여 최대 합을 구하는 것이 목표입니다.</p>
<h4 id="해결-전략">해결 전략</h4>
<p>문제를 해결하기 위해 각 테트로미노 모양에 대해 배열의 모든 위치에서 놓아보고 그 합을 계산한 후, 최대값을 구합니다. 이를 위해 각 테트로미노의 형태를 함수로 나누어 처리했습니다.</p>
<h4 id="코드-구현">코드 구현</h4>
<p>문제를 해결하기 위한 코드는 아래와 같습니다.</p>
<pre><code class="language-java">import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;

public class P14500 {

    // ㅗ, ㅜ, ㅓ, ㅏ 모양 테트로미노 처리
    private static int S345(int[][] table, int n, int m) {
        int sum = 0;

        for (int i = 0; i &lt; n - 1; i++) {
            for (int j = 0; j &lt; m - 2; j++) {
                sum = Math.max(sum, table[i][j] + table[i][j+1] + table[i][j+2] + table[i+1][j]);
                sum = Math.max(sum, table[i][j] + table[i][j+1] + table[i][j+2] + table[i+1][j+1]);
                sum = Math.max(sum, table[i][j] + table[i][j+1] + table[i][j+2] + table[i+1][j+2]);

                sum = Math.max(sum, table[i+1][j] + table[i+1][j+1] + table[i+1][j+2] + table[i][j]);
                sum = Math.max(sum, table[i+1][j] + table[i+1][j+1] + table[i+1][j+2] + table[i][j+1]);
                sum = Math.max(sum, table[i+1][j] + table[i+1][j+1] + table[i+1][j+2] + table[i][j+2]);

                sum = Math.max(sum, table[i][j] + table[i][j+1] + table[i+1][j+1] + table[i+1][j+2]);
                sum = Math.max(sum, table[i][j+1] + table[i][j+2] + table[i+1][j] + table[i+1][j+1]);
            }
        }

        for (int i = 0; i &lt; n - 2; i++) {
            for (int j = 0; j &lt; m - 1; j++) {
                sum = Math.max(sum, table[i][j] + table[i+1][j] + table[i+2][j] + table[i][j+1]);
                sum = Math.max(sum, table[i][j] + table[i+1][j] + table[i+2][j] + table[i+1][j+1]);
                sum = Math.max(sum, table[i][j] + table[i+1][j] + table[i+2][j] + table[i+2][j+1]);

                sum = Math.max(sum, table[i][j] + table[i][j+1] + table[i+1][j+1] + table[i+2][j+1]);
                sum = Math.max(sum, table[i+1][j] + table[i][j+1] + table[i+1][j+1] + table[i+2][j+1]);
                sum = Math.max(sum, table[i+2][j] + table[i][j+1] + table[i+1][j+1] + table[i+2][j+1]);

                sum = Math.max(sum, table[i][j] + table[i+1][j] + table[i+1][j+1] + table[i+2][j+1]);
                sum = Math.max(sum, table[i+1][j] + table[i+2][j] + table[i][j+1] + table[i+1][j+1]);
            }
        }

        return sum;
    }

    // 정사각형 모양 테트로미노 처리
    private static int S2(int[][] table, int n, int m) {
        int sum = 0;

        for (int i = 0; i &lt; n - 1; i++) {
            for (int j = 0; j &lt; m - 1; j++) {
                sum = Math.max(sum, table[i][j] + table[i][j+1] + table[i+1][j] + table[i+1][j+1]);
            }
        }

        return sum;
    }

    // I 모양 테트로미노 처리
    private static int S1(int[][] table, int n, int m) {
        int sum = 0;

        for (int i = 0; i &lt; n; i++) {
            for (int j = 0; j &lt; m - 3; j++) {
                sum = Math.max(sum, table[i][j] + table[i][j+1] + table[i][j+2] + table[i][j+3]);
            }
        }

        for (int i = 0; i &lt; n - 3; i++) {
            for (int j = 0; j &lt; m; j++) {
                sum = Math.max(sum, table[i][j] + table[i+1][j] + table[i+2][j] + table[i+3][j]);
            }
        }

        return sum;
    }

    public static void main(String[] args) throws IOException {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        String[] l0 = br.readLine().split(&quot; &quot;); 
        int n = Integer.parseInt(l0[0]);
        int m = Integer.parseInt(l0[1]);

        int[][] table = new int[n][m];

        for (int i = 0; i &lt; n; i++) {
            l0 = br.readLine().split(&quot; &quot;);
            for (int j = 0; j &lt; m; j++) {
                table[i][j] = Integer.parseInt(l0[j]);
            }
        }

        int result = S1(table, n, m);

        int count = S2(table, n, m);
        result = Math.max(result, count);

        count = S345(table, n, m);
        result = Math.max(result, count);

        System.out.println(result);
    }
}</code></pre>
<h4 id="코드-설명">코드 설명</h4>
<ol>
<li><p><strong>함수별 테트로미노 모양 처리</strong>:</p>
<ul>
<li><code>S1</code> 함수는 I 모양 테트로미노의 최대 합을 계산합니다. 배열 내에서 가로 또는 세로로 연속된 4개의 칸을 합산합니다.</li>
<li><code>S2</code> 함수는 정사각형 모양의 테트로미노 합을 계산합니다. 배열 내에서 2x2 영역의 합을 구합니다.</li>
<li><code>S345</code> 함수는 ㄴ자형, ㄹ자형, ㅗ자형 모양의 테트로미노에 대해 가능한 모든 배치를 계산합니다.</li>
</ul>
</li>
<li><p><strong>최대값 계산</strong>:</p>
<ul>
<li>각 함수에서 계산된 최대 합을 비교하여 최종 결과를 도출합니다.</li>
</ul>
</li>
</ol>
<h4 id="사고-흐름-정리">사고 흐름 정리</h4>
<ol>
<li><p><strong>문제 분석</strong>: 주어진 배열에서 테트로미노를 놓아 최대 합을 구하기 위해 가능한 모든 배치를 탐색해야 합니다.</p>
</li>
<li><p><strong>테트로미노 모양 별 함수 분리</strong>: 각 테트로미노 모양에 대해 별도의 함수를 작성하여 코드의 가독성을 높이고, 최대값을 효율적으로 계산할 수 있도록 했습니다.</p>
</li>
<li><p><strong>최대값 비교</strong>: 각각의 함수에서 구한 값 중에서 가장 큰 값을 최종 결과로 출력합니다.</p>
</li>
</ol>
<h3 id="마무리">마무리</h3>
<p>이번 문제는 다양한 테트로미노 모양을 고려해야 하는 복잡한 문제였지만, 각 모양을 함수로 분리하여 효율적으로 해결할 수 있었습니다. 테트로미노 모양을 분석하고, 모든 가능한 배치를 탐색하는 과정이 중요한 문제였습니다. 이번 포스팅을 통해 문제 해결 방법을 이해하는 데 도움이 되었기를!</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[백준] 1107]]></title>
            <link>https://velog.io/@zedy_dev/%EB%B0%B1%EC%A4%80-1107</link>
            <guid>https://velog.io/@zedy_dev/%EB%B0%B1%EC%A4%80-1107</guid>
            <pubDate>Sun, 11 Aug 2024 13:25:46 GMT</pubDate>
            <description><![CDATA[<h3 id="백준-1107번-리모컨-문제-풀이">백준 1107번: 리모컨 문제 풀이</h3>
<p>이번 포스팅에서는 백준 1107번 문제인 &quot;리모컨&quot; 문제를 해결하는 과정을 정리하겠습니다. 이 문제는 고장난 버튼이 있는 리모컨을 사용해 특정 채널로 이동할 때 최소한의 버튼 클릭 횟수를 계산하는 문제입니다.</p>
<h4 id="문제-분석">문제 분석</h4>
<p>문제는 고장난 버튼이 있을 때, 주어진 채널로 이동하기 위해 가장 적은 횟수로 버튼을 눌러야 하는 상황을 제시합니다. 리모컨에는 0부터 9까지의 숫자 버튼과 채널을 올리고 내릴 수 있는 <code>+</code>, <code>-</code> 버튼이 있습니다.</p>
<h4 id="해결-전략">해결 전략</h4>
<p>문제를 해결하기 위해 몇 가지 접근 방식을 고려했습니다:</p>
<ol>
<li><p><strong><code>+</code>, <code>-</code> 버튼만 사용하는 경우</strong>: </p>
<ul>
<li>현재 채널(기본 채널 <code>100</code>)에서 목표 채널까지 <code>+</code>, <code>-</code> 버튼만을 이용해 이동하는 방법입니다.</li>
</ul>
</li>
<li><p><strong>숫자 버튼을 사용한 접근</strong>:</p>
<ul>
<li>고장나지 않은 숫자 버튼을 이용해 최대한 목표 채널에 가까운 채널을 입력하고, 남은 차이를 <code>+</code>, <code>-</code> 버튼으로 이동하는 방법입니다.</li>
</ul>
</li>
<li><p><strong>상향 및 하향 접근</strong>:</p>
<ul>
<li>목표 채널에서 위쪽과 아래쪽으로 가능한 채널을 탐색하며, 고장난 버튼을 피해서 가장 가까운 채널을 찾습니다.</li>
</ul>
</li>
</ol>
<h4 id="코드-구현">코드 구현</h4>
<p>문제를 해결하기 위한 코드는 아래와 같습니다.</p>
<pre><code class="language-java">import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;

public class P1107 {
    private static boolean checkNumber(int number, int[] brokenNumber) {
        String numStr = Integer.toString(number);

        for (int i = 0; i &lt; numStr.length(); i++) {
            for (int j = 0; j &lt; brokenNumber.length; j++) {
                if (Character.getNumericValue(numStr.charAt(i)) == brokenNumber[j]) {
                    return false;
                }
            }
        }
        return true;
    }

    public static void main(String[] args) throws IOException {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));

        String num = br.readLine();
        int numLength = num.length();
        int[] number = new int[numLength];

        for (int i = 0; i &lt; numLength; i++) {
            number[i] = Character.getNumericValue(num.charAt(i));
        }

        int n = Integer.parseInt(br.readLine());
        int[] brokeNumber = new int[n];
        int sum = 0;
        if (n != 0) {
            String[] line = br.readLine().split(&quot; &quot;); 
            for (int i = 0; i &lt; n; i++) {
                brokeNumber[i] = Integer.parseInt(line[i]);
                sum += brokeNumber[i];
            }
        }

        int count = Math.abs(Integer.parseInt(num) - 100); 

        int countUpper = n == 10 ? 500000 : 0;
        int numUp = Integer.parseInt(num);
        boolean pressOther = true;

        if (n == 9 &amp;&amp; sum == 45) {
            pressOther = false;
            countUpper = 500000;
        }

        if (n != 10 &amp;&amp; pressOther) {
            while (numUp &gt;= 0) {
                if (checkNumber(numUp, brokeNumber)) {
                    countUpper += Integer.toString(numUp).length();
                    break;
                }
                countUpper++;
                numUp++;
            }
        }

        int countDown1 = n == 10 ? 500000 : 0;
        int countDown2 = 0;
        int numDown = Integer.parseInt(num);

        if (n != 10) {
            if (numDown == 0) {
                countDown1 = 500000;
            }

            while (numDown &gt;= 0) {
                if (checkNumber(numDown, brokeNumber)) {
                    countDown1 = Integer.toString(numDown).length();
                    break;
                } else {
                    countDown1 = 500000;
                }
                countDown2++;
                numDown--;
            }
        }
        countDown2 += countDown1;

        count = Math.min(count, countUpper);
        count = Math.min(count, countDown2);

        System.out.println(count);
    }
}</code></pre>
<h4 id="코드-설명">코드 설명</h4>
<ol>
<li><p><strong>고장난 버튼 체크</strong>:</p>
<ul>
<li><code>checkNumber</code> 함수는 주어진 숫자에서 고장난 버튼이 포함되어 있는지 확인합니다. 포함되어 있으면 <code>false</code>, 아니면 <code>true</code>를 반환합니다.</li>
</ul>
</li>
<li><p><strong><code>+</code>, <code>-</code> 버튼만 사용하는 경우</strong>:</p>
<ul>
<li>현재 채널 <code>100</code>에서 목표 채널까지의 거리(버튼 클릭 횟수)를 계산합니다. 이 값은 <code>Math.abs(Integer.parseInt(num) - 100)</code>로 구할 수 있습니다.</li>
</ul>
</li>
<li><p><strong>숫자 버튼을 이용한 상향 탐색</strong>:</p>
<ul>
<li>목표 채널보다 큰 채널 중 고장난 버튼이 없는 첫 번째 채널을 찾아 이동 횟수를 계산합니다. 만약 고장난 버튼이 전부이면 무한대(<code>500000</code>)로 설정합니다.</li>
</ul>
</li>
<li><p><strong>숫자 버튼을 이용한 하향 탐색</strong>:</p>
<ul>
<li>목표 채널보다 작은 채널 중 고장난 버튼이 없는 첫 번째 채널을 찾아 이동 횟수를 계산합니다. 마찬가지로 고장난 버튼이 전부이면 무한대로 설정합니다.</li>
</ul>
</li>
<li><p><strong>최소 이동 횟수 계산</strong>:</p>
<ul>
<li>각 접근 방법에서 얻은 이동 횟수 중 가장 작은 값을 선택하여 출력합니다.</li>
</ul>
</li>
</ol>
<h4 id="사고-흐름-정리">사고 흐름 정리</h4>
<ol>
<li><p><strong>문제 분석</strong>: 고장난 버튼이 있는 경우 어떻게 목표 채널로 이동할 수 있을지 고민합니다. 가능한 모든 이동 방법을 탐색해 최소 이동 횟수를 찾아야 합니다.</p>
</li>
<li><p><strong>조건 처리</strong>: 고장난 버튼이 전부일 경우나, 숫자 버튼을 사용할 수 없을 경우에 대한 예외 처리를 고려합니다.</p>
</li>
<li><p><strong>탐색</strong>: 목표 채널을 중심으로 상향 및 하향 탐색을 통해 가능한 채널을 찾습니다. 이때, 고장난 버튼이 없을 때만 해당 채널로 이동합니다.</p>
</li>
<li><p><strong>최적화</strong>: <code>+</code>, <code>-</code> 버튼만을 사용한 경우와 숫자 버튼을 사용한 경우 중 최소 이동 횟수를 선택합니다.</p>
</li>
</ol>
<h3 id="마무리">마무리</h3>
<p>이 문제는 주어진 조건을 바탕으로 모든 가능성을 탐색해야 하는 전형적인 완전 탐색 문제입니다. 고장난 버튼을 고려해 예외 상황을 처리하고, 다양한 접근 방식을 통해 최적의 해결책을 찾는 과정이 중요합니다. 코드를 통해 문제 해결 방법을 이해하는 데 도움이 되었기를 바랍니다. 다음에도 더 흥미로운 문제로 찾아오겠습니다!</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[백준] 1476]]></title>
            <link>https://velog.io/@zedy_dev/%EB%B0%B1%EC%A4%80-1476</link>
            <guid>https://velog.io/@zedy_dev/%EB%B0%B1%EC%A4%80-1476</guid>
            <pubDate>Sat, 10 Aug 2024 07:09:35 GMT</pubDate>
            <description><![CDATA[<h3 id="백준-1476번-날짜-계산-문제-풀이">백준 1476번: 날짜 계산 문제 풀이</h3>
<p>이번 포스팅에서는 백준 1476번 문제인 &quot;날짜 계산&quot; 문제를 해결하는 과정을 정리하겠습니다. 이 문제는 주어진 세 가지 수 <code>E</code>, <code>S</code>, <code>M</code>에 대해 특정 규칙을 만족하는 연도를 찾는 문제입니다.</p>
<h4 id="문제-이해">문제 이해</h4>
<p>문제는 아래와 같은 세 가지 수를 기준으로 특정한 연도를 찾아야 합니다:</p>
<ul>
<li><strong>E</strong>는 지구를 나타내며, 1부터 15까지의 값을 가질 수 있습니다.</li>
<li><strong>S</strong>는 태양을 나타내며, 1부터 28까지의 값을 가질 수 있습니다.</li>
<li><strong>M</strong>은 달을 나타내며, 1부터 19까지의 값을 가질 수 있습니다.</li>
</ul>
<p>우리는 특정한 연도 <code>year</code>를 찾으려고 합니다. 이 연도는 아래의 조건을 모두 만족해야 합니다:</p>
<ul>
<li><code>year % 15 == E</code></li>
<li><code>year % 28 == S</code></li>
<li><code>year % 19 == M</code></li>
</ul>
<h4 id="코드-구현">코드 구현</h4>
<p>문제를 해결하기 위한 코드를 구현했습니다. 아래는 코드와 설명입니다.</p>
<pre><code class="language-java">import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;

public class P1476 {
    public static void main(String[] args) throws IOException {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        String[] line = br.readLine().split(&quot; &quot;); // 입력 받기

        int earth = Integer.parseInt(line[0]) % 15;
        int sun = Integer.parseInt(line[1]) % 28;
        int moon = Integer.parseInt(line[2]) % 19;

        int result = 0;

        // 1부터 시작하여 조건을 만족하는 연도를 찾을 때까지 반복
        for(int i = 1; ; i++){
            if( (i % 15 == earth) &amp;&amp; (i % 28 == sun) &amp;&amp; (i % 19 == moon)){
                result = i;
                break;
            }
        }
        System.out.println(result);
    }
}</code></pre>
<h4 id="코드-설명">코드 설명</h4>
<ol>
<li><p><strong>입력 처리</strong>:</p>
<ul>
<li><code>BufferedReader</code>를 사용해 한 줄로 입력된 값을 받아오고, 이를 공백 기준으로 분리하여 <code>earth</code>, <code>sun</code>, <code>moon</code> 변수에 저장합니다.</li>
<li>입력된 <code>earth</code>, <code>sun</code>, <code>moon</code> 값을 각각 15, 28, 19로 나눈 나머지를 구해 저장합니다. 이는 문제에서 주어진 연도의 범위를 고려한 것입니다.</li>
</ul>
</li>
<li><p><strong>연도 찾기</strong>:</p>
<ul>
<li>무한 루프를 사용하여 1부터 시작하는 모든 <code>i</code> 값에 대해 조건을 확인합니다.</li>
<li>조건이 만족되면, 그 <code>i</code> 값을 <code>result</code> 변수에 저장하고 루프를 종료합니다.</li>
</ul>
</li>
<li><p><strong>결과 출력</strong>:</p>
<ul>
<li>최종적으로 조건을 만족하는 <code>result</code> 값을 출력합니다.</li>
</ul>
</li>
</ol>
<h4 id="문제-해결의-사고-흐름">문제 해결의 사고 흐름</h4>
<p>이 문제를 해결하기 위한 기본적인 사고 흐름은 다음과 같습니다:</p>
<ol>
<li><strong>문제 분석</strong>: 문제에서 주어진 세 가지 조건을 만족하는 연도를 찾아야 한다는 점을 파악합니다.</li>
<li><strong>나머지 연산 활용</strong>: 각 연도 <code>year</code>에 대해 <code>E</code>, <code>S</code>, <code>M</code>이 각각 1부터 시작하므로, 입력값을 각각 15, 28, 19로 나눈 나머지를 비교하는 방식을 선택합니다.</li>
<li><strong>반복문 구현</strong>: 모든 가능한 연도를 탐색하는 반복문을 통해 조건을 만족하는 연도를 찾습니다.</li>
<li><strong>결과 출력</strong>: 조건을 만족하는 첫 번째 연도를 출력합니다.</li>
</ol>
<p>이렇게 함으로써 백준 1476번 문제를 해결할 수 있었습니다.</p>
<h3 id="마무리">마무리</h3>
<p>이 문제는 나머지 연산과 반복문을 잘 이해하고 활용하는 것이 핵심입니다. 무한 루프를 사용하여 특정 조건을 만족할 때까지 반복하며, 범위를 적절히 설정해 효율적인 문제 해결 방법을 도출하는 것이 중요합니다.</p>
<p>코드와 설명이 도움이 되었기를 바랍니다. 다음에도 더 흥미로운 문제로 찾아오겠습니다!</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[백준] 2309]]></title>
            <link>https://velog.io/@zedy_dev/%EB%B0%B1%EC%A4%80-2309</link>
            <guid>https://velog.io/@zedy_dev/%EB%B0%B1%EC%A4%80-2309</guid>
            <pubDate>Fri, 09 Aug 2024 07:16:44 GMT</pubDate>
            <description><![CDATA[<h2 id="백준-2309번-문제-풀이-일곱-난쟁이">백준 2309번 문제 풀이: 일곱 난쟁이</h2>
<p>이번 포스팅에서는 백준 2309번 문제인 &quot;일곱 난쟁이&quot;를 자바로 해결한 방법을 공유하고자 합니다. 문제 해결을 위해 사용된 알고리즘과 코드의 흐름을 차근차근 설명하겠습니다.</p>
<h4 id="문제-개요">문제 개요</h4>
<p>이 문제는 총 9명의 난쟁이 중에서 7명의 키의 합이 100이 되도록 하는 난쟁이들을 찾아내는 문제입니다. 이를 위해 우리는 9명의 난쟁이 키에서 임의의 2명을 제외한 7명의 키를 선택하여 그 합이 100이 되는 경우를 찾아야 합니다.</p>
<h4 id="문제-풀이-접근-방식">문제 풀이 접근 방식</h4>
<ol>
<li><p><strong>입력 데이터 처리 및 총합 계산</strong>:</p>
<ul>
<li>먼저 9명의 난쟁이 키를 입력받고, 이들의 키의 총합을 구합니다.</li>
</ul>
</li>
<li><p><strong>제거할 두 난쟁이 찾기</strong>:</p>
<ul>
<li>두 명의 난쟁이를 제외한 7명의 키 합이 100이 되려면, 두 명의 키 합은 <code>총합 - 100</code>이 되어야 합니다.</li>
<li>이 조건을 만족하는 두 명의 난쟁이를 찾고, 이들의 위치를 배열에서 마지막 두 위치로 이동시킵니다. 이로써 제거할 난쟁이들을 쉽게 처리할 수 있습니다.</li>
</ul>
</li>
<li><p><strong>제거하지 않은 7명의 난쟁이 키 정렬</strong>:</p>
<ul>
<li>선택된 7명의 난쟁이 키를 오름차순으로 정렬합니다.</li>
</ul>
</li>
<li><p><strong>결과 출력</strong>:</p>
<ul>
<li>정렬된 7명의 난쟁이 키를 출력합니다.</li>
</ul>
</li>
</ol>
<h4 id="코드-설명">코드 설명</h4>
<p>아래는 해당 문제를 해결하기 위한 Java 코드를 작성한 것입니다.</p>
<pre><code class="language-java">import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;

public class P2309 {
    public static void main(String[] args) throws IOException {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        int[] heights = new int[9];
        int sum = 0;

        // 9명의 난쟁이 키를 입력받고 합을 계산
        for(int i = 0; i &lt; 9; i++){
            heights[i] = Integer.parseInt(br.readLine());
            sum += heights[i];
        }

        int temp = 0;

        // 합이 sum - 100이 되는 두 난쟁이를 찾아서 배열의 마지막 두 위치로 이동
        for(int i = 0; i &lt; 8; i++){
            for(int j = i+1; j &lt; 9; j++){
                if(heights[i]+heights[j] == sum - 100){
                    // 첫 번째 난쟁이 이동
                    temp = heights[i];
                    heights[i] = heights[7];
                    heights[7] = temp;

                    // 두 번째 난쟁이 이동
                    temp = heights[j];
                    heights[j] = heights[8];
                    heights[8] = temp;
                }
            }
        }

        // 남은 7명의 난쟁이 키를 오름차순 정렬
        for(int i = 0; i&lt;6; i++){
            for(int j = i+1; j&lt;7; j++){
                if(heights[i] &gt; heights[j]){
                    temp = heights[i];
                    heights[i] = heights[j];
                    heights[j] = temp;
                }
            }
        }

        // 결과 출력
        for(int i = 0; i &lt; 7; i++){
            System.out.println(heights[i]);
        }
    }
}</code></pre>
<h4 id="핵심-아이디어">핵심 아이디어</h4>
<ul>
<li><strong>탐색 범위 줄이기</strong>: 모든 경우의 수를 살펴보는 방법은 비효율적이기 때문에, 두 명의 난쟁이의 키 합이 <code>sum - 100</code>이 되는지를 조건으로 삼아 탐색 범위를 줄였습니다.</li>
<li><strong>간단한 정렬 사용</strong>: 정렬 알고리즘 중에서 자주 사용하는 버블 정렬을 사용하여 7명의 키를 오름차순으로 정렬했습니다. 물론 효율성 측면에서는 더 빠른 정렬 알고리즘을 사용하는 것이 좋겠지만, 작은 데이터셋에서는 간단한 방법이 더 직관적일 수 있습니다.</li>
</ul>
<h4 id="결론">결론</h4>
<p>이 문제는 브루트포스(완전 탐색) 기법을 활용하여 해결할 수 있습니다. 가능한 모든 경우를 조사해도 문제의 제약 조건 내에서는 충분히 빠르게 답을 구할 수 있습니다. 이러한 접근 방식은 작은 데이터셋에서 유용하며, 문제의 조건을 만족하는지 여부를 정확하게 판단하는 데 도움을 줍니다.</p>
<hr>
<p>이 게시물이 백준 2309번 문제를 푸는 데 도움이 되기를 바랍니다! 댓글이나 질문은 언제든 환영입니다. Happy Coding! 😊</p>
]]></description>
        </item>
    </channel>
</rss>