<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>Deep Dive</title>
        <link>https://velog.io/</link>
        <description>AI/ML Engineer</description>
        <lastBuildDate>Tue, 09 Sep 2025 03:47:23 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <image>
            <title>Deep Dive</title>
            <url>https://velog.velcdn.com/images/cha-suyeon/profile/6f5766b6-8d96-4088-84ed-d2ddcf298f35/image.png</url>
            <link>https://velog.io/</link>
        </image>
        <copyright>Copyright (C) 2019. Deep Dive. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/cha-suyeon" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[Git: cannot checkout branch - error: pathspec '...' did not match any file(s) known to git]]></title>
            <link>https://velog.io/@cha-suyeon/Git-cannot-checkout-branch-error-pathspec-...-did-not-match-any-files-known-to-git</link>
            <guid>https://velog.io/@cha-suyeon/Git-cannot-checkout-branch-error-pathspec-...-did-not-match-any-files-known-to-git</guid>
            <pubDate>Tue, 09 Sep 2025 03:47:23 GMT</pubDate>
            <description><![CDATA[<p>이 에러는 Git에서 브랜치를 체크아웃할 때 자주 발생하는 문제입니다.</p>
<p>출처: <a href="https://stackoverflow.com/questions/5989592/git-cannot-checkout-branch-error-pathspec-did-not-match-any-files-kn">https://stackoverflow.com/questions/5989592/git-cannot-checkout-branch-error-pathspec-did-not-match-any-files-kn</a></p>
<h2 id="현상과-원인-분석">현상과 원인 분석</h2>
<h3 id="에러-메시지"><strong>에러 메시지</strong></h3>
<pre><code>error: pathspec &#39;branch_name&#39; did not match any file(s) known to git</code></pre><h3 id="발생-원인"><strong>발생 원인</strong></h3>
<p><strong>1. 로컬-원격 저장소 동기화 문제 (가장 일반적)</strong></p>
<ul>
<li>로컬 저장소의 git 정보와 원격 저장소의 git 정보가 동기화되지 않음[1][2]</li>
<li>다른 개발자가 새로운 브랜치를 원격에 생성했지만, 본인의 로컬에는 아직 업데이트되지 않은 상태</li>
</ul>
<p><strong>2. 브랜치 이름 오타 또는 존재하지 않는 브랜치</strong></p>
<ul>
<li>브랜치 이름을 잘못 입력한 경우[3]</li>
<li>실제로 원격 저장소에 존재하지 않는 브랜치인 경우[2]</li>
</ul>
<p><strong>3. 브랜치가 로컬에만 생성되어 원격에 푸시되지 않은 경우</strong></p>
<ul>
<li>A 개발자가 로컬에서만 브랜치를 생성하고 원격에 푸시하지 않은 상태에서 B 개발자가 해당 브랜치로 체크아웃을 시도하는 경우[2]</li>
</ul>
<h2 id="해결-방법">해결 방법</h2>
<h3 id="방법-1-원격-브랜치-정보-업데이트-추천"><strong>방법 1: 원격 브랜치 정보 업데이트 (추천)</strong></h3>
<p>가장 일반적이고 효과적인 해결 방법입니다:</p>
<pre><code class="language-bash"># 1. 모든 원격 브랜치 정보를 업데이트
git remote update

# 2. 원격 저장소의 최신 정보를 가져오기
git fetch

# 3. 브랜치 체크아웃
git checkout branch_name</code></pre>
<p><strong>각 명령어 설명:</strong></p>
<ul>
<li><code>git remote update</code>: 모든 원격 브랜치를 업데이트하여 최신 상태로 갱신 (로컬 저장소에서 변동사항을 병합하지 않음)[1]</li>
<li><code>git fetch</code>: 원격 저장소의 최신 정보를 로컬로 가져옴[4]</li>
</ul>
<h3 id="방법-2-원격-저장소-재연결"><strong>방법 2: 원격 저장소 재연결</strong></h3>
<p>원격 저장소 연결에 문제가 있는 경우:</p>
<pre><code class="language-bash"># 1. 현재 원격 저장소 확인
git remote -v

# 2. 원격 저장소 제거
git remote remove origin

# 3. 원격 저장소 다시 추가 (올바른 URL 사용)
git remote add origin git@github.com:username/repository.git

# 4. 원격 브랜치 정보 가져오기
git pull --ff-only

# 5. 업스트림 설정
git branch --set-upstream-to=origin/current_branch</code></pre>
<h3 id="방법-3-브랜치-존재-여부-확인"><strong>방법 3: 브랜치 존재 여부 확인</strong></h3>
<p>먼저 브랜치가 실제로 존재하는지 확인:</p>
<pre><code class="language-bash"># 모든 브랜치 확인 (로컬 + 원격)
git branch -a

# 원격 브랜치만 확인
git branch -r</code></pre>
<h3 id="방법-4-특수-문자가-포함된-브랜치명"><strong>방법 4: 특수 문자가 포함된 브랜치명</strong></h3>
<p>Windows 환경에서 브랜치명에 특수문자(예: &#39;)가 포함된 경우:</p>
<pre><code class="language-bash"># 잘못된 방법
git checkout bugfix/some-&#39;branch&#39;-name

# 올바른 방법 (이스케이프 문자 사용)
git checkout bugfix/some-\&#39;branch\&#39;-name</code></pre>
<h2 id="예방-방법">예방 방법</h2>
<ol>
<li><strong>정기적인 동기화</strong>: <code>git fetch</code> 또는 <code>git remote update</code>를 정기적으로 실행</li>
<li><strong>브랜치 확인</strong>: 체크아웃 전에 <code>git branch -a</code>로 브랜치 존재 여부 확인</li>
<li><strong>올바른 워크플로우</strong>: 새 브랜치 생성 후 반드시 원격에 푸시</li>
</ol>
<h2 id="상황별-대응">상황별 대응</h2>
<ul>
<li><strong>협업 환경</strong>: 방법 1을 먼저 시도</li>
<li><strong>원격 연결 문제</strong>: 방법 2 적용</li>
<li><strong>브랜치명 불확실</strong>: 방법 3으로 확인 후 진행</li>
</ul>
<p>대부분의 경우 방법 1(<code>git remote update</code> → <code>git fetch</code> → <code>git checkout</code>)로 해결됩니다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Git 커밋 메시지 작성에 대한 일반적인 규칙과 컨벤션]]></title>
            <link>https://velog.io/@cha-suyeon/Git-%EC%BB%A4%EB%B0%8B-%EB%A9%94%EC%8B%9C%EC%A7%80-%EC%9E%91%EC%84%B1%EC%97%90-%EB%8C%80%ED%95%9C-%EC%9D%BC%EB%B0%98%EC%A0%81%EC%9D%B8-%EA%B7%9C%EC%B9%99%EA%B3%BC-%EC%BB%A8%EB%B2%A4%EC%85%98</link>
            <guid>https://velog.io/@cha-suyeon/Git-%EC%BB%A4%EB%B0%8B-%EB%A9%94%EC%8B%9C%EC%A7%80-%EC%9E%91%EC%84%B1%EC%97%90-%EB%8C%80%ED%95%9C-%EC%9D%BC%EB%B0%98%EC%A0%81%EC%9D%B8-%EA%B7%9C%EC%B9%99%EA%B3%BC-%EC%BB%A8%EB%B2%A4%EC%85%98</guid>
            <pubDate>Mon, 08 Sep 2025 07:23:46 GMT</pubDate>
            <description><![CDATA[<p>Git 커밋 메시지 작성에 대한 일반적인 규칙과 컨벤션에 대한 정리입니다.</p>
<p>잘 작성된 커밋 메시지는 프로젝트의 히스토리를 깔끔하게 관리하고, 다른 팀원들이 변경 사항을 쉽게 이해하도록 돕는 중요한 역할을 합니다.</p>
<h3 id="좋은-커밋-메시지의-7가지-규칙"><strong>좋은 커밋 메시지의 7가지 규칙</strong></h3>
<ol>
<li><p><strong>제목과 본문을 빈 줄로 구분하기</strong></p>
<ul>
<li>첫 줄은 <strong>제목(subject)</strong>, 그 다음 빈 줄을 두고 <strong>본문(body)</strong>을 작성하는 것이 일반적입니다.</li>
</ul>
</li>
<li><p><strong>제목은 50자 이내로 작성하기</strong></p>
<ul>
<li>제목은 간결하게 핵심 내용을 담아야 합니다. 50자를 넘어가면 가독성이 떨어지므로, 너무 길게 작성하지 않도록 주의해야 합니다.</li>
</ul>
</li>
<li><p><strong>제목 첫 글자는 대문자로 시작하기</strong></p>
<ul>
<li>예: <code>Fix: Add missing feature</code> (O)</li>
<li>예: <code>fix: add missing feature</code> (X)</li>
</ul>
</li>
<li><p><strong>제목 끝에 마침표를 찍지 않기</strong></p>
<ul>
<li>제목은 짧은 문구이므로, 마침표를 사용하지 않습니다.</li>
</ul>
</li>
<li><p><strong>명령형으로 작성하기</strong></p>
<ul>
<li>커밋 메시지는 &quot;내가 무엇을 변경했다&quot;가 아니라, &quot;이 커밋이 무엇을 하는가&quot;에 초점을 맞춰야 합니다.</li>
<li>예: <code>Add new API endpoint</code> (O)</li>
<li>예: <code>Added new API endpoint</code> (X)</li>
</ul>
</li>
<li><p><strong>본문은 72자 이내로 줄바꿈하기</strong></p>
<ul>
<li>본문은 제목에서 다루지 못한 상세 내용을 작성합니다. 줄이 너무 길어지지 않도록 한 줄에 72자 이내로 작성하는 것이 좋습니다.</li>
</ul>
</li>
<li><p><strong>본문은 &quot;무엇을&quot;, &quot;왜&quot; 변경했는지 설명하기</strong></p>
<ul>
<li>본문에는 커밋을 하게 된 <strong>동기(motivation)</strong>와 변경에 대한 상세 내용을 포함합니다. <strong>어떻게</strong> 변경했는지 보다는 <strong>왜</strong> 변경했는지에 대해 설명하는 것이 중요합니다.</li>
</ul>
</li>
</ol>
<hr>
<h3 id="일반적으로-사용되는-커밋-타입-컨벤션"><strong>일반적으로 사용되는 커밋 타입 컨벤션</strong></h3>
<p>많은 프로젝트에서 <strong>커밋 타입</strong>을 제목에 포함하는 컨벤션을 사용합니다. 이는 커밋의 목적을 한눈에 파악하게 도와줍니다. 가장 널리 사용되는 <a href="https://www.conventionalcommits.org/en/v1.0.0/">Conventional Commits</a> 스펙을 기준으로 자주 쓰이는 타입을 정리해 드립니다.</p>
<p><code>**&lt;type&gt;(&lt;scope&gt;): &lt;description&gt;**</code></p>
<ul>
<li><strong><code>&lt;type&gt;</code></strong>: 커밋의 종류를 나타냅니다.</li>
<li><strong><code>&lt;scope&gt;</code></strong>(선택 사항): 변경 사항이 미치는 범위를 나타냅니다.</li>
<li><strong><code>&lt;description&gt;</code></strong>: 변경 사항에 대한 간결한 설명입니다.</li>
</ul>
<p><strong>자주 사용되는 <code>&lt;type&gt;</code> 예시:</strong></p>
<ul>
<li><strong><code>feat</code></strong>: 새로운 기능 추가. (e.g., <code>feat: Add user authentication</code>)</li>
<li><strong><code>fix</code></strong>: 버그 수정. (e.g., <code>fix: Resolve login button crash</code>)</li>
<li><strong><code>docs</code></strong>: 문서 수정. (e.g., <code>docs: Update API documentation</code>)</li>
<li><strong><code>style</code></strong>: 코드 스타일 변경 (세미콜론, 들여쓰기 등. 코드 동작에는 영향을 주지 않음). (e.g., <code>style: Format code with Prettier</code>)</li>
<li><strong><code>refactor</code></strong>: 코드 리팩토링 (기능 변경 없이 코드 구조 개선). (e.g., <code>refactor: Improve component logic</code>)</li>
<li><strong><code>test</code></strong>: 테스트 코드 추가 또는 수정. (e.g., <code>test: Add unit tests for API</code>)</li>
<li><strong><code>chore</code></strong>: 빌드 시스템, 라이브러리, 의존성 관리 등 개발 환경 관련 변경. (e.g., <code>chore: Update webpack configuration</code>)</li>
</ul>
<p><strong>예시:</strong></p>
<pre><code class="language-git">feat: Add a new user profile page

This commit introduces a new user profile page.
It includes a user&#39;s name, profile picture, and a list of their recent activity.
The page is accessible at /users/:username.</code></pre>
<hr>
<h3 id="왜-커밋-메시지-컨벤션을-따라야-할까요"><strong>왜 커밋 메시지 컨벤션을 따라야 할까요?</strong></h3>
<ul>
<li><strong>히스토리 파악 용이</strong>: 프로젝트의 변경 이력을 한눈에 쉽게 파악할 수 있습니다.</li>
<li><strong>코드 리뷰 효율성 증가</strong>: 팀원들이 커밋의 목적을 빠르게 이해하여 리뷰 시간을 단축할 수 있습니다.</li>
<li><strong>자동화</strong>: <strong><code>feat</code></strong>와 <strong><code>fix</code></strong> 타입 등을 기준으로 릴리스 노트를 자동으로 생성하거나, 버전을 관리하는 등의 자동화 시스템을 구축할 수 있습니다.</li>
</ul>
<p>팀에서 통일된 컨벤션을 정하고 지키는 것이 매우 중요합니다. 프로젝트를 시작하기 전에 팀원들과 커밋 메시지 규칙에 대해 논의하고 합의하는 것을 추천합니다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[fatal: Need to specify how to reconcile divergent branches.]]></title>
            <link>https://velog.io/@cha-suyeon/fatal-Need-to-specify-how-to-reconcile-divergent-branches</link>
            <guid>https://velog.io/@cha-suyeon/fatal-Need-to-specify-how-to-reconcile-divergent-branches</guid>
            <pubDate>Sun, 07 Sep 2025 23:47:54 GMT</pubDate>
            <description><![CDATA[<h3 id="1-git-안내-메시지">1. git 안내 메시지</h3>
<p><code>fatal: Need to specify how to reconcile divergent branches.</code></p>
<p>이 메시지의 핵심은 <strong>&quot;Divergent branches&quot; (분기된 브랜치)</strong> 입니다.</p>
<p>쉽게 말해, <strong>로컬 저장소(내 컴퓨터)의 커밋(작업 내용)과 원격 저장소(GitHub 등)의 커밋이 서로 다른 방향으로 진행되어 역사가 갈라졌다</strong>는 의미입니다.</p>
<p>이런 상황은 보통 다음과 같을 때 발생합니다.</p>
<ol>
<li>내가 로컬에서 <code>git commit</code>으로 작업을 완료했습니다.</li>
<li>하지만 <code>git push</code>로 원격에 올리기 전에, 다른 팀원(또는 내가 다른 컴퓨터에서)이 원격 저장소에 새로운 내용을 <code>push</code> 했습니다.</li>
<li>이 상태에서 내가 <code>git pull</code>을 실행하면, Git은 원격의 새로운 내용과 내 로컬의 새로운 내용을 어떻게 합쳐야 할지 모릅니다. 역사가 두 갈래로 나뉘었기 때문이죠.</li>
</ol>
<p>따라서 Git은 사용자에게 &quot;두 갈래로 나뉜 역사를 합치는 방식을 앞으로 어떻게 할지 정책을 정해주세요&quot;라고 요청하는 것입니다. </p>
<p>Git이 제시한 3가지 선택지는 다음과 같습니다.</p>
<ul>
<li><p><code>git config pull.rebase false</code>: <strong>Merge 방식</strong> (병합)</p>
<ul>
<li>두 갈래의 브랜치를 그대로 유지하면서, 두 브랜치를 하나로 합치는 새로운 <strong>&#39;병합 커밋(Merge Commit)&#39;</strong>을 만듭니다.</li>
<li><strong>장점:</strong> 누가 언제 무엇을 병합했는지 역사가 명확하게 남습니다.</li>
<li><strong>단점:</strong> 커밋 히스토리가 복잡해 보일 수 있습니다.</li>
</ul>
</li>
<li><p><code>git config pull.rebase true</code>: <strong>Rebase 방식</strong> (재배치)</p>
<ul>
<li>내 로컬 커밋을 잠시 떼어놓고, 원격의 새로운 내용을 먼저 가져옵니다. 그 다음, 가져온 최신 내용 위에 내가 작업한 로컬 커밋을 다시 차곡차곡 쌓습니다.</li>
<li><strong>장점:</strong> 커밋 히토리가 한 줄로 깔끔하게 정리됩니다.</li>
<li><strong>단점:</strong> 원래의 커밋 순서 등 히스토리 정보가 변경될 수 있습니다.</li>
</ul>
</li>
<li><p><code>git config pull.ff only</code>: <strong>Fast-forward only 방식</strong> (빨리감기만 허용)</p>
<ul>
<li>가장 엄격한 방식입니다. 브랜치가 갈라진(divergent) 상황에서는 <code>pull</code>을 아예 실패시킵니다. <code>pull</code>은 내 로컬 작업이 없을 때, 즉 원격 히스토리를 그대로 가져오기만 하면 되는 경우에만 성공합니다.</li>
</ul>
</li>
</ul>
<h3 id="2-어떻게-해결된-건지-해결-과정-분석">2. &quot;어떻게 해결된 건지?&quot; (해결 과정 분석)</h3>
<p><code>git config pull.rebase false</code></p>
<p>이 명령어는 위에서 설명한 3가지 정책 중 <strong>첫 번째인 Merge 방식을 선택하여 Git에게 알려준 것</strong>입니다.</p>
<p>명령어를 풀어보면 다음과 같습니다.</p>
<ul>
<li><code>git config</code>: Git의 설정을 변경하는 명령어입니다.</li>
<li><code>pull.rebase false</code>: <code>git pull</code>을 할 때 <code>rebase</code>를 사용하지 않겠다(<code>false</code>)는 의미입니다. <code>rebase</code>를 사용하지 않으면 자동으로 <strong><code>merge</code></strong> 방식을 사용하게 됩니다.</li>
</ul>
<p>따라서 이 명령어를 실행함으로써 <strong>&quot;앞으로 이 <code>Repository</code> 저장소에서는 <code>git pull</code>을 할 때 역사가 갈라졌으면, 자동으로 병합(Merge)해서 합쳐줘&quot;</strong> 라고 Git에게 기본 규칙을 설정해준 것입니다.</p>
<p><code>git config pull.rebase true</code></p>
<p>이 명령어를 실행하면 rebase 되어서 로컬 커밋이 정리됩니다.</p>
<p>위 명령어를 사용한 후에 <strong>이제 다시 <code>git pull</code> 명령어를 실행하면,</strong> Git은 이 설정에 따라 원격 저장소의 내용과 로컬 저장소의 내용을 합치는 병합 커밋을 자동으로 생성하며 <code>pull</code>을 성공적으로 완료할 것입니다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[AWS Lambda 애플리케이션 로그 레벨 설정 방법]]></title>
            <link>https://velog.io/@cha-suyeon/AWS-Lambda-%EC%95%A0%ED%94%8C%EB%A6%AC%EC%BC%80%EC%9D%B4%EC%85%98-%EB%A1%9C%EA%B7%B8-%EB%A0%88%EB%B2%A8-%EC%84%A4%EC%A0%95-%EB%B0%A9%EB%B2%95</link>
            <guid>https://velog.io/@cha-suyeon/AWS-Lambda-%EC%95%A0%ED%94%8C%EB%A6%AC%EC%BC%80%EC%9D%B4%EC%85%98-%EB%A1%9C%EA%B7%B8-%EB%A0%88%EB%B2%A8-%EC%84%A4%EC%A0%95-%EB%B0%A9%EB%B2%95</guid>
            <pubDate>Fri, 05 Sep 2025 00:44:11 GMT</pubDate>
            <description><![CDATA[<h3 id="aws-lambda-애플리케이션-로그-누락-문제-해결-가이드"><strong>AWS Lambda 애플리케이션 로그 누락 문제 해결 가이드</strong></h3>
<p>이 가이드는 Python Lambda 함수에서 <code>logger</code>를 사용한 로그가 CloudWatch에 보이지 않는 문제를 진단하고 해결하는 전체 과정을 정리한 것입니다.</p>
<h4 id="1-문제-상황-the-problem"><strong>1. 문제 상황 (The Problem)</strong></h4>
<ul>
<li><strong>현상:</strong> 코드에 <code>logger.info()</code>, <code>logger.debug()</code> 등 로그 출력 구문을 작성했음에도 불구하고, AWS CloudWatch Logs에서는 해당 로그들이 전혀 보이지 않았습니다.</li>
<li><strong>관찰된 로그:</strong> Lambda 실행 시작/종료/리포트를 알리는 시스템 로그(<code>START</code>, <code>END</code>, <code>REPORT</code>)만 기록되었습니다.</li>
<li><strong>목표:</strong> 디버깅과 성공적인 API 응답 확인을 위해 코드에서 의도한 모든 애플리케이션 로그를 CloudWatch에서 확인하는 것.</li>
</ul>
<h4 id="2-원인-분석-the-analysis"><strong>2. 원인 분석 (The Analysis)</strong></h4>
<p>조사와 테스트를 통해 확인된 핵심 원인은 다음과 같습니다.</p>
<ul>
<li><p><strong>주요 원인: Lambda 함수의 로그 구성 설정</strong>
Lambda는 코드 레벨의 로깅 설정과 별개로, 함수 자체의 실행 환경(Runtime)에 로그를 처리하는 방식을 설정할 수 있습니다. 이 설정이 코드의 설정을 덮어쓰거나 무시하게 만들어 로그가 누락되었습니다.</p>
</li>
<li><p><strong>발견 1: <code>로그 형식</code>과 <code>로그 수준</code>의 연관 관계 (가장 중요)</strong></p>
<ul>
<li>Lambda의 로그 설정에는 <strong><code>로그 형식(Log format)</code></strong>과 <strong><code>애플리케이션 로그 수준(Application log level)</code></strong>이라는 두 가지 주요 옵션이 있습니다.</li>
<li><strong><code>로그 형식</code></strong>이 기본값인 <strong><code>Text</code></strong>로 설정되어 있으면, 로그를 단순 문자열로만 취급합니다. 이 상태에서는 로그의 중요도(<code>INFO</code>, <code>DEBUG</code> 등)를 구분할 수 없으므로, <strong><code>애플리케이션 로그 수준</code> 설정 자체가 비활성화</strong>됩니다.</li>
<li><strong><code>로그 형식</code></strong>을 <strong><code>JSON</code></strong>으로 변경해야만, Lambda가 로그를 <code>{&quot;level&quot;: &quot;INFO&quot;, &quot;message&quot;: &quot;...&quot;}</code>와 같은 구조화된 데이터로 인식합니다. 이 때 비로소 <strong><code>애플리케이션 로그 수준</code> 설정이 활성화</strong>되어, 특정 레벨 이상의 로그만 필터링하는 기능이 동작합니다.</li>
</ul>
</li>
<li><p><strong>발견 2: 코드의 안정성 문제</strong></p>
<ul>
<li>AWS Lambda 환경은 이미 기본 로거(Root Logger)를 구성한 상태에서 코드를 실행합니다. 이 때문에 코드 시작 부분의 <code>logging.basicConfig()</code> 함수가 예상대로 동작하지 않거나 무시될 수 있습니다.</li>
</ul>
</li>
</ul>
<h4 id="3-해결-과정-the-step-by-step-solution"><strong>3. 해결 과정 (The Step-by-Step Solution)</strong></h4>
<p><img src="https://velog.velcdn.com/images/cha-suyeon/post/85323e63-71e4-4483-9b1e-98f5f439dd17/image.png" alt=""></p>
<p>아래의 단계적인 절차를 통해 문제를 해결했습니다.</p>
<ul>
<li><p><strong>Step 1: Lambda 구성 변경 (AWS Console에서 설정)</strong></p>
<ol>
<li>해당 Lambda 함수의 <strong>[구성(Configuration)]</strong> 탭으로 이동했습니다.</li>
<li>왼쪽 메뉴에서 <strong>[모니터링 및 운영 도구(Monitoring and operations tools)]</strong>를 클릭하고 <strong>[편집(Edit)]</strong> 버튼을 눌렀습니다.</li>
<li><strong><code>로그 형식(Log format)</code></strong>을 <code>Text</code>에서 <strong><code>JSON</code></strong>으로 변경했습니다. (이것이 로그 수준 설정을 활성화하는 핵심 단계입니다.)</li>
<li>활성화된 <strong><code>애플리케이션 로그 수준(Application log level)</code></strong>을 <strong><code>INFO</code></strong>로 설정했습니다. (개발 시에는 <code>DEBUG</code>로 설정해도 좋습니다.)</li>
<li>설정을 <strong>[저장(Save)]</strong>하고 함수를 다시 테스트하여 로그가 정상적으로 출력되는 것을 확인했습니다.</li>
</ol>
</li>
<li><p><strong>Step 2: 안정적인 로깅을 위한 코드 수정 (권장 사항)</strong></p>
<ul>
<li>Lambda 환경에서 더 확실하게 동작하도록 로거 설정 코드를 수정했습니다.</li>
<li><strong>기존 방식:</strong><pre><code class="language-python"># import logging
# logger = logging.getLogger(__name__)
# logging.basicConfig(level=logging.INFO)</code></pre>
</li>
<li><strong>변경 후 안정적인 방식:</strong><pre><code class="language-python">import logging
# Lambda의 기본 로거를 가져와서 레벨을 설정
logger = logging.getLogger()
logger.setLevel(logging.INFO)</code></pre>
</li>
</ul>
</li>
</ul>
<p><img src="https://velog.velcdn.com/images/cha-suyeon/post/de0711c0-b1c7-4fcb-9a02-b2e3485a7392/image.png" alt=""></p>
<ul>
<li><p><strong>Step 3: 성공 응답 로깅 코드 추가</strong></p>
<ul>
<li>정상적으로 처리된 API의 최종 응답 내용도 로그로 남기기 위해, <code>return</code> 직전에 아래 코드를 추가했습니다.</li>
</ul>
<!-- end list -->

<pre><code class="language-python"># ... 이전 로직 ...
response_data = {&quot;data&quot;: data}

# 최종 응답 데이터를 JSON 문자열로 변환하여 로그에 남김
logger.info(f&quot;[RESPONSE BODY] {json.dumps(response_data, indent=2, ensure_ascii=False)}&quot;)

return ResponseManager.success(response_data)</code></pre>
</li>
</ul>
<h3 id="최종-요약-key-takeaways"><strong>최종 요약 (Key Takeaways)</strong></h3>
<ol>
<li>Lambda에서 애플리케이션 로그가 보이지 않는다면, 코드보다는 <strong>Lambda 함수 자체의 구성(Configuration)을 가장 먼저 확인</strong>해야 합니다.</li>
<li>로그 레벨(<code>INFO</code>, <code>DEBUG</code> 등)을 기준으로 로그를 필터링하려면, <strong>반드시 로그 형식을 <code>JSON</code>으로 설정</strong>해야 합니다.</li>
<li>로그 형식을 <code>JSON</code>으로 바꾼 뒤, <strong>애플리케이션 로그 수준을 원하는 레벨(<code>INFO</code> 등)로 설정</strong>해야 해당 레벨 이상의 모든 로그가 보입니다.</li>
<li>코드에서는 <code>logging.basicConfig()</code>보다 <strong><code>logging.getLogger().setLevel()</code></strong> 방식을 사용하는 것이 Lambda 환경에서 훨씬 안정적입니다.</li>
</ol>
]]></description>
        </item>
        <item>
            <title><![CDATA[`psycopg2` 라이브러리의 핵심 개념]]></title>
            <link>https://velog.io/@cha-suyeon/psycopg2-%EB%9D%BC%EC%9D%B4%EB%B8%8C%EB%9F%AC%EB%A6%AC%EC%9D%98-%ED%95%B5%EC%8B%AC-%EA%B0%9C%EB%85%90</link>
            <guid>https://velog.io/@cha-suyeon/psycopg2-%EB%9D%BC%EC%9D%B4%EB%B8%8C%EB%9F%AC%EB%A6%AC%EC%9D%98-%ED%95%B5%EC%8B%AC-%EA%B0%9C%EB%85%90</guid>
            <pubDate>Fri, 05 Sep 2025 00:11:56 GMT</pubDate>
            <description><![CDATA[<h1 id="psycopg2-라이브러리의-핵심-개념">psycopg2 라이브러리의 핵심 개념</h1>
<p>Python에서 PostgreSQL 데이터베이스를 다룰 때 널리 쓰이는 라이브러리가 바로 <strong><code>psycopg2</code></strong>입니다.</p>
<p>데이터베이스와의 상호작용 과정을 이해하기 쉽게 <strong>레스토랑에서 주문하고 식사하는 과정</strong>에 비유해 보겠습니다.</p>
<hr>
<h2 id="비유로-먼저-이해하기">비유로 먼저 이해하기</h2>
<ul>
<li><code>connect()</code> / <code>conn</code> (연결): 레스토랑에 들어가서 <strong>내 테이블에 앉는 것</strong></li>
<li><code>cursor()</code> / <code>cur</code> (커서): <strong>웨이터를 부르는 것</strong>. 모든 주문은 웨이터를 통해 전달된다.</li>
<li><code>execute()</code> (실행): 웨이터에게 <strong>“파스타 하나 주세요”</strong>라고 주문하는 것</li>
<li><code>fetchall()</code> (결과 가져오기): 웨이터가 <strong>음식을 가져오는 것</strong></li>
<li><code>commit()</code> (확정): 음식을 다 먹고 <strong>계산하는 것</strong></li>
<li><code>rollback()</code> (취소): 잘못 주문해서 <strong>“주문 취소할게요”</strong>라고 하는 것</li>
<li><code>close()</code> (종료): 식사를 마치고 <strong>레스토랑에서 나오는 것</strong></li>
</ul>
<hr>
<h2 id="핵심-개념과-코드-예제">핵심 개념과 코드 예제</h2>
<h3 id="1-psycopg2connect-와-conn-연결-객체">1. <code>psycopg2.connect()</code> 와 <code>conn</code> (연결 객체)</h3>
<ul>
<li>역할: 데이터베이스와 연결을 생성한다. DB 주소, 사용자 계정, 비밀번호 등을 입력하여 접속하며, 연결 정보를 담은 <code>conn</code> 객체가 반환된다.</li>
<li>비유: 레스토랑에 들어가서 <strong>내 테이블에 앉는 것</strong>.</li>
</ul>
<pre><code class="language-python">import psycopg2

# 데이터베이스 연결
conn = psycopg2.connect(
    host=&quot;localhost&quot;,
    database=&quot;testdb&quot;,
    user=&quot;postgres&quot;,
    password=&quot;password&quot;
)</code></pre>
<hr>
<h3 id="2-conncursor-와-cur-커서-객체">2. <code>conn.cursor()</code> 와 <code>cur</code> (커서 객체)</h3>
<ul>
<li>역할: SQL 명령을 실행하고 결과를 받아오는 중간 역할. 모든 DB 상호작용은 커서를 통해 이루어진다.</li>
<li>비유: <strong>웨이터를 부르는 것</strong>.</li>
</ul>
<pre><code class="language-python">cur = conn.cursor()</code></pre>
<hr>
<h3 id="3-curexecutequery-params-쿼리-실행">3. <code>cur.execute(query, params)</code> (쿼리 실행)</h3>
<ul>
<li>역할: SQL 쿼리를 실행한다. 파라미터 바인딩을 지원하여 SQL Injection 공격을 방어할 수 있다.</li>
<li>비유: 웨이터에게 <strong>“이 메뉴 주세요”</strong>라고 주문하는 것.</li>
</ul>
<pre><code class="language-python"># 테이블 생성
cur.execute(&quot;&quot;&quot;
    CREATE TABLE IF NOT EXISTS users (
        id SERIAL PRIMARY KEY,
        name TEXT,
        age INT
    )
&quot;&quot;&quot;)

# 데이터 삽입 (파라미터 바인딩)
cur.execute(&quot;INSERT INTO users (name, age) VALUES (%s, %s)&quot;, (&quot;Alice&quot;, 25))</code></pre>
<hr>
<h3 id="4-fetchall--fetchone--fetchmany">4. <code>fetchall()</code> / <code>fetchone()</code> / <code>fetchmany()</code></h3>
<ul>
<li>역할: SELECT 쿼리 결과를 가져온다.</li>
<li>비유: 웨이터가 음식을 <strong>한 접시, 일부, 전부</strong> 가져오는 것.</li>
</ul>
<pre><code class="language-python">cur.execute(&quot;SELECT * FROM users&quot;)
rows = cur.fetchall()
for row in rows:
    print(row)</code></pre>
<hr>
<h3 id="5-conncommit-트랜잭션-확정">5. <code>conn.commit()</code> (트랜잭션 확정)</h3>
<ul>
<li>역할: 변경 사항을 최종적으로 DB에 반영한다. <code>commit()</code>을 호출하지 않으면 DB에는 반영되지 않는다.</li>
<li>비유: 식사를 마치고 <strong>계산하는 것</strong>.</li>
</ul>
<pre><code class="language-python">conn.commit()</code></pre>
<hr>
<h3 id="6-connrollback-트랜잭션-취소">6. <code>conn.rollback()</code> (트랜잭션 취소)</h3>
<ul>
<li>역할: <code>commit()</code> 전에 실행된 변경 사항을 모두 취소한다. 주로 에러 발생 시 사용된다.</li>
<li>비유: <strong>주문을 취소하는 것</strong>.</li>
</ul>
<pre><code class="language-python">try:
    cur.execute(&quot;INSERT INTO users (name, age) VALUES (%s, %s)&quot;, (&quot;Bob&quot;, &quot;not_a_number&quot;))
    conn.commit()
except Exception as e:
    print(&quot;에러 발생:&quot;, e)
    conn.rollback()</code></pre>
<hr>
<h3 id="7-curclose-와-connclose-자원-해제">7. <code>cur.close()</code> 와 <code>conn.close()</code> (자원 해제)</h3>
<ul>
<li>역할: 커서와 연결을 닫아 자원을 반환한다. 닫지 않으면 연결이 계속 유지되어 서버에 부담이 될 수 있다.</li>
<li>비유: <strong>웨이터를 돌려보내고, 레스토랑에서 나오는 것</strong>.</li>
</ul>
<pre><code class="language-python">cur.close()
conn.close()</code></pre>
<hr>
<h2 id="전체-예제-코드">전체 예제 코드</h2>
<pre><code class="language-python">import psycopg2

try:
    # 연결
    conn = psycopg2.connect(
        host=&quot;localhost&quot;,
        database=&quot;testdb&quot;,
        user=&quot;postgres&quot;,
        password=&quot;password&quot;
    )
    cur = conn.cursor()

    # 테이블 생성
    cur.execute(&quot;&quot;&quot;
        CREATE TABLE IF NOT EXISTS users (
            id SERIAL PRIMARY KEY,
            name TEXT,
            age INT
        )
    &quot;&quot;&quot;)

    # 데이터 삽입
    cur.execute(&quot;INSERT INTO users (name, age) VALUES (%s, %s)&quot;, (&quot;Alice&quot;, 25))

    # 조회
    cur.execute(&quot;SELECT * FROM users&quot;)
    rows = cur.fetchall()
    for row in rows:
        print(row)

    # 확정
    conn.commit()

except Exception as e:
    print(&quot;에러 발생:&quot;, e)
    conn.rollback()

finally:
    # 자원 해제
    if cur:
        cur.close()
    if conn:
        conn.close()</code></pre>
<hr>
<h2 id="정리">정리</h2>
<ul>
<li><code>connect()</code> → 데이터베이스 연결 (테이블에 앉기)</li>
<li><code>cursor()</code> → 커서 생성 (웨이터 부르기)</li>
<li><code>execute()</code> → SQL 실행 (주문하기)</li>
<li><code>fetch*()</code> → 결과 가져오기 (음식 받기)</li>
<li><code>commit()</code> → 변경 사항 확정 (계산하기)</li>
<li><code>rollback()</code> → 변경 사항 취소 (주문 취소)</li>
<li><code>close()</code> → 자원 해제 (퇴장하기)</li>
</ul>
<p><code>psycopg2</code>는 PostgreSQL과 상호작용하기 위한 표준적인 도구이며, 올바르게 <code>commit</code>, <code>rollback</code>, <code>close</code>를 사용하는 것이 안정적인 데이터베이스 프로그래밍의 핵심입니다.</p>
<hr>
]]></description>
        </item>
        <item>
            <title><![CDATA[파이썬 로깅 레벨(Logging Level)]]></title>
            <link>https://velog.io/@cha-suyeon/%ED%8C%8C%EC%9D%B4%EC%8D%AC-%EB%A1%9C%EA%B9%85-%EB%A0%88%EB%B2%A8Logging-Level</link>
            <guid>https://velog.io/@cha-suyeon/%ED%8C%8C%EC%9D%B4%EC%8D%AC-%EB%A1%9C%EA%B9%85-%EB%A0%88%EB%B2%A8Logging-Level</guid>
            <pubDate>Fri, 05 Sep 2025 00:04:09 GMT</pubDate>
            <description><![CDATA[<h3 id="로깅-레벨이란">로깅 레벨이란?</h3>
<p><strong>로그의 중요도를 구분하는 등급</strong>입니다. 개발자가 프로그램의 실행 상태를 기록할 때, 모든 기록이 다 같은 중요도를 갖지 않습니다. 로깅 레벨을 사용하면 &quot;이 로그는 단순 정보야&quot;, &quot;이 로그는 심각한 에러야&quot; 와 같이 각 로그의 성격을 명확히 할 수 있습니다.</p>
<p>이를 통해 <strong>필요한 정보만 골라서 보거나</strong>, 운영 중인 서비스에서 <strong>심각한 문제만 빠르게 필터링</strong>하는 것이 가능해집니다.</p>
<hr>
<h3 id="표준-로깅-레벨-낮은-중요도-→-높은-중요도-순">표준 로깅 레벨 (낮은 중요도 → 높은 중요도 순)</h3>
<p>아래 5가지 레벨이 가장 표준적으로 사용됩니다. 설정된 레벨 이상의 로그만 출력됩니다.</p>
<p>(예: <code>INFO</code> 레벨로 설정하면 <code>DEBUG</code> 로그는 보이지 않고, <code>INFO</code>, <code>WARNING</code>, <code>ERROR</code>, <code>CRITICAL</code> 로그가 보입니다.)</p>
<table>
<thead>
<tr>
<th align="left">레벨 (Level)</th>
<th align="left">설명</th>
<th align="left">사용 예시</th>
</tr>
</thead>
<tbody><tr>
<td align="left"><strong>1. DEBUG</strong></td>
<td align="left"><strong>상세한 디버깅 정보.</strong> 개발 시에만 필요한 변수 값, 특정 로직의 시작과 끝 등 가장 자세한 내용을 기록합니다.</td>
<td align="left"><code>변수 a의 값: 10</code>, <code>for문 시작</code></td>
</tr>
<tr>
<td align="left"><strong>2. INFO</strong></td>
<td align="left"><strong>일반적인 정보.</strong> 프로그램이 예상대로 잘 동작하고 있음을 보여주는 정보성 메시지입니다.</td>
<td align="left"><code>서버 시작</code>, <code>사용자 로그인 성공</code>, <code>DB 연결 완료</code></td>
</tr>
<tr>
<td align="left"><strong>3. WARNING</strong></td>
<td align="left"><strong>경고.</strong> 현재는 문제가 없지만, 미래에 잠재적인 문제가 될 수 있는 상황을 알립니다. 프로그램은 중단되지 않습니다.</td>
<td align="left"><code>메모리 사용량 80% 초과</code>, <code>오래된 API 버전 사용 중</code></td>
</tr>
<tr>
<td align="left"><strong>4. ERROR</strong></td>
<td align="left"><strong>오류.</strong> 심각한 문제로 인해 프로그램의 일부 기능이 제대로 동작하지 못하는 상황입니다.</td>
<td align="left"><code>파일을 찾을 수 없음</code>, <code>DB 쿼리 실패</code>, <code>네트워크 연결 끊김</code></td>
</tr>
<tr>
<td align="left"><strong>5. CRITICAL</strong></td>
<td align="left"><strong>치명적인 오류.</strong> 프로그램 전체가 중단되거나 더 이상 실행을 계속할 수 없는 매우 심각한 상태입니다.</td>
<td align="left"><code>시스템 전체 다운</code>, <code>주요 데이터베이스 접근 불가</code></td>
</tr>
</tbody></table>
<hr>
<h3 id="쉽게-비유하자면">쉽게 비유하자면?</h3>
<p>로깅 레벨은 <strong>라디오 볼륨 조절기</strong>와 같습니다.</p>
<ul>
<li><strong>DEBUG 레벨:</strong> 볼륨을 최대로 높여서 아주 작은 소리(모든 정보)까지 다 듣는 것. (개발할 때 유용)</li>
<li><strong>INFO 레벨:</strong> 적당한 볼륨으로 주요 뉴스나 안내 방송(핵심 정보)만 듣는 것. (평상시 운영 상태)</li>
<li><strong>ERROR 레벨:</strong> 볼륨을 최소로 줄여서 비상 사이렌 소리(심각한 문제)만 듣는 것. (장애 발생 시)</li>
</ul>
<p>이렇게 상황에 맞게 로그 레벨을 조절하면, 수많은 로그 속에서 원하는 정보를 효율적으로 찾아낼 수 있습니다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[파이썬 nonlocal` 완벽 가이드]]></title>
            <link>https://velog.io/@cha-suyeon/%ED%8C%8C%EC%9D%B4%EC%8D%AC-nonlocal-%EC%99%84%EB%B2%BD-%EA%B0%80%EC%9D%B4%EB%93%9C</link>
            <guid>https://velog.io/@cha-suyeon/%ED%8C%8C%EC%9D%B4%EC%8D%AC-nonlocal-%EC%99%84%EB%B2%BD-%EA%B0%80%EC%9D%B4%EB%93%9C</guid>
            <pubDate>Thu, 04 Sep 2025 03:20:37 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/cha-suyeon/post/495dfb80-3597-4365-8e46-823b24cf2686/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/cha-suyeon/post/5330d8be-b763-480f-8570-a8477c600813/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/cha-suyeon/post/805bd044-52d5-4217-8d35-d109fae420db/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/cha-suyeon/post/f74a4831-81fe-4173-9543-6992f73e9df4/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/cha-suyeon/post/8f326685-7423-4c21-b698-333c71713538/image.png" alt=""></p>
<hr>
<p>프로그래밍의 본질은 &#39;데이터를 효과적으로 관리하고 조작하는 기술&#39;이라고 할 수 있습니다. 이 데이터는 &#39;변수&#39;라는 이름표를 달고 메모리 어딘가에 저장됩니다. 그런데 이 변수들은 프로그램의 아무 곳에서나 마음대로 접근할 수 있는 것이 아닙니다. 모든 변수에는 자신만의 &#39;활동할 수 있는 영역&#39; 또는 &#39;집 주소&#39;가 정해져 있는데, 이를 <strong>스코프(Scope)</strong>라고 부릅니다.</p>
<p>파이썬을 배우다 보면 함수 안에서 변수를 만들고 사용하는 데 익숙해집니다. 하지만 함수 안에 또 다른 함수를 만드는 &#39;중첩 함수&#39; 구조를 만나게 되면, 변수의 주소록은 한층 더 복잡해집니다. </p>
<p>바로 이 복잡한 주소 체계 속에서 &quot;안쪽 집에서는 바깥쪽 집의 가구(변수)를 보기만 할 수 있고, 위치를 바꿀 수는 없다&quot;는 독특한 규칙과 마주하게 됩니다. 이 규칙을 어기려고 할 때 우리는 <code>UnboundLocalError</code>라는 당혹스러운 에러를 만나게 됩니다.</p>
<p><strong><code>nonlocal</code></strong> 키워드는 바로 이 문제를 해결하기 위해 등장한 &#39;<strong>마스터 키</strong>&#39;입니다. 이 키워드는 변수의 기본 주소 규칙을 더 세밀하게 제어하여, 중첩된 함수 구조에서 발생하는 까다로운 문제들을 우아하게 풀어냅니다. <code>nonlocal</code>은 단순히 에러를 피하는 기술을 넘어, &#39;상태를 기억하는 함수(클로저)&#39;와 같은 강력하고 세련된 프로그래밍 패턴을 구현하는 핵심적인 도구입니다.</p>
<p>LEGB 규칙이라는 기본 원리부터 시작하여, <code>nonlocal</code>이 왜 필요한지, <code>global</code>과는 무엇이 다른지, 그리고 실제 코드에서 어떻게 그 힘을 발휘하는지까지 차근차근 탐험할 것입니다. 이 여정이 끝날 때쯤, 여러분은 변수의 &#39;주소&#39;를 완벽하게 이해하고 <code>nonlocal</code>이라는 마스터 키를 자신 있게 사용하여 더 깔끔하고 강력한 파이썬 코드를 작성하는 개발자로 거듭나게 될 것입니다.</p>
<h2 id="1-스코프scope의-이해">1. 스코프(Scope)의 이해</h2>
<p><code>nonlocal</code>의 존재 이유를 파악하려면, 먼저 파이썬이 변수 이름을 어떻게 찾고 관리하는지에 대한 근본적인 규칙을 이해해야 합니다. 이 규칙의 핵심이 바로 &#39;스코프(Scope)&#39;입니다.</p>
<h3 id="스코프란-무엇인가">스코프란 무엇인가?</h3>
<p>스코프는 변수가 코드 내에서 유효하게 존재하고 접근할 수 있는 범위를 정의하는 규칙의 집합입니다. &quot;모든 변수는 자신만의 &#39;활동 구역&#39;을 갖는다&quot;고 생각하면 쉽습니다. 어떤 변수는 특정 함수 안에서만 쓸 수 있는 반면, 어떤 변수는 프로그램 전체에서 접근할 수 있습니다. 스코프는 이처럼 변수의 생명주기와 가시성(visibility)을 제어합니다.</p>
<p>초기 프로그래밍 언어 중 일부는 오직 전역(global) 스코프만 가지고 있었습니다. 이는 프로그램의 규모가 커질수록 서로 다른 코드 조각에서 사용된 같은 이름의 변수가 충돌하여 예상치 못한 버그를 일으키는 원인이 되었습니다. 파이썬은 이러한 혼란을 막기 위해 변수를 각자의 스코프에 격리시키는 정교한 시스템을 채택했습니다.</p>
<h3 id="파이썬의-변수-탐색-순서-legb-규칙">파이썬의 변수 탐색 순서: LEGB 규칙</h3>
<p>파이썬 인터프리터가 코드에서 특정 변수 이름을 만났을 때, 그 변수가 어떤 값을 가리키는지 찾기 위해 정해진 순서에 따라 여러 스코프를 탐색합니다. 이 순서를 <strong>LEGB 규칙</strong>이라고 부릅니다. LEGB는 다음과 같은 네 가지 스코프의 앞 글자를 딴 것입니다.</p>
<ol>
<li><p><strong>L (Local): 지역 스코프</strong></p>
<ul>
<li>가장 안쪽의 가장 좁은 범위로, 현재 실행 중인 함수나 클래스 메서드 내부를 의미합니다. 함수가 호출될 때마다 새로운 지역 스코프가 생성되고, 함수 실행이 끝나면 사라집니다. 함수에 전달되는 매개변수(parameter) 역시 지역 스코프에 속합니다. 파이썬은 변수를 찾을 때 가장 먼저 이 &#39;내 방&#39;부터 살펴봅니다.</li>
</ul>
</li>
<li><p><strong>E (Enclosing): 둘러싸는 스코프</strong></p>
<ul>
<li><p>중첩 함수 구조(함수 안에 다른 함수가 정의된 경우)에서만 존재하는 특별한 스코프입니다. 내부 함수 입장에서 자신을 감싸고 있는 외부 함수의 스코프를 가리킵니다. 파이썬이 지역 스코프에서 변수를 찾지 못하면, 그 다음으로 &#39;우리 집 거실&#39;에 해당하는 이 Enclosing 스코프를 확인합니다.</p>
<p>  <code>nonlocal</code> 키워드가 활약하는 주 무대가 바로 이곳입니다.</p>
</li>
</ul>
</li>
<li><p><strong>G (Global): 전역 스코프</strong></p>
<ul>
<li>모듈(일반적으로 <code>.py</code> 파일 하나)의 최상위 레벨에 정의된 변수들이 속하는 공간입니다. 함수나 클래스 바깥에 선언된 모든 변수는 전역 변수가 되며, 코드 어디서든 접근할 수 있습니다. &#39;우리 동네&#39;처럼 넓은 활동 범위를 가집니다.</li>
</ul>
</li>
<li><p><strong>B (Built-in): 내장 스코프</strong></p>
<ul>
<li><p>파이썬이 기본적으로 제공하는 모든 이름들이 포함된 가장 바깥쪽의 스코프입니다.</p>
<p>  <code>print()</code>, <code>len()</code>, <code>range()</code>와 같은 내장 함수나 <code>SyntaxError</code>, <code>NameError</code> 같은 예외 이름들이 여기에 속합니다. 이들은 &#39;온 세상&#39; 어디에나 존재하는 것처럼, 별도의 선언 없이 언제나 사용할 수 있습니다.</p>
</li>
</ul>
</li>
</ol>
<h3 id="legb-규칙의-동작-원리">LEGB 규칙의 동작 원리</h3>
<p>파이썬은 변수 이름을 해석할 때 항상 <code>L → E → G → B</code> 순서, 즉 가장 좁은 스코프에서 시작하여 점차 넓은 스코프로 확장하며 검색을 진행합니다. 검색 과정에서 해당 이름을 가진 변수를 가장 먼저 발견하는 즉시 검색을 멈추고 그 변수를 사용합니다. 만약 내장 스코프까지 모두 확인했는데도 변수를 찾지 못하면, <code>NameError</code> 예외가 발생합니다.</p>
<p>이 LEGB 규칙은 단순한 검색 순서를 넘어, &#39;이름 충돌을 해결하고 변수의 영향 범위를 제한하는&#39; 파이썬의 핵심적인 이름 해석(Name Resolution) 메커니즘입니다. </p>
<p>스코프는 변수들을 각자의 공간에 안전하게 격리시키고, LEGB 규칙은 이 격리된 공간들 사이에서 변수를 찾아 나갈지에 대한 명확한 지도를 제공합니다. </p>
<p>따라서 LEGB를 이해하는 것은 <code>nonlocal</code>을 배우기 위한 준비운동일 뿐만 아니라, 파이썬 코드의 동작 방식을 근본적으로 이해하는 첫걸음입니다.</p>
<h2 id="2-함수-속의-함수-중첩-스코프enclosing-scope">2. 함수 속의 함수, 중첩 스코프(Enclosing Scope)</h2>
<p><code>nonlocal</code> 키워드가 왜 필요한지를 이해하려면, 이 키워드가 사용되는 특별한 환경인 &#39;중첩 함수&#39; 구조를 먼저 알아야 합니다. 이 구조에서 바로 LEGB 규칙의 &#39;E&#39;, 즉 Enclosing 스코프가 탄생하기 때문입니다.</p>
<h3 id="중첩-함수nested-function란">중첩 함수(Nested Function)란?</h3>
<p>이름 그대로, 하나의 함수 내부에 또 다른 함수를 정의하는 프로그래밍 구조를 말합니다. 바깥쪽 함수를 외부 함수(outer function), 안쪽 함수를 내부 함수(inner function)라고 부릅니다.</p>
<pre><code class="language-python">def outer():
    print(&quot;여기는 외부 함수입니다.&quot;)

    def inner():
        print(&quot;여기는 내부 함수입니다.&quot;)

    inner() # 외부 함수 안에서 내부 함수를 호출

outer()
</code></pre>
<p>위 코드에서 <code>inner</code> 함수는 <code>outer</code> 함수 안에서만 정의되고 호출될 수 있습니다. <code>outer</code> 함수 바깥에서 <code>inner()</code>를 직접 호출하려고 하면 <code>NameError</code>가 발생합니다. 이는 <code>inner</code> 함수 자체가 <code>outer</code> 함수의 지역 스코프(Local Scope)에 속하기 때문입니다.</p>
<p>이러한 중첩 구조는 특정 기능을 외부로부터 숨겨서 오직 특정 함수 내에서만 사용되는 &#39;도우미 함수(Helper Function)&#39;를 만들 때 유용합니다. 전역 스코프를 불필요한 함수들로 어지럽히지 않고, 코드의 논리적 구조를 더 명확하게 만들어주는 장점이 있습니다.</p>
<h3 id="enclosing-스코프의-탄생">Enclosing 스코프의 탄생</h3>
<p>중첩 함수 구조가 만들어지는 순간, 스코프에도 새로운 층이 생겨납니다. 외부 함수(<code>outer</code>)가 호출되면 그 안의 변수들을 위한 지역 스코프가 생성됩니다. 그런데 이 스코프는 그 안에 있는 내부 함수(<code>inner</code>)의 입장에서는 자신을 둘러싸고 있는 <strong>Enclosing 스코프</strong>가 됩니다.</p>
<h3 id="기본-규칙-읽기는-자유롭다">기본 규칙: 읽기는 자유롭다</h3>
<p>내부 함수는 자신을 감싸는 외부 함수의 변수를 자유롭게 &#39;읽을(read)&#39; 수 있습니다. 이는 LEGB 규칙에 따른 자연스러운 동작입니다. 다음 예제를 살펴보겠습니다.</p>
<pre><code class="language-python">def outer():
    message = &quot;Hello from outer&quot;  # Enclosing 스코프의 변수

    def inner():
        # inner의 지역(Local) 스코프에는 message가 없음
        # 따라서 한 단계 위인 Enclosing 스코프에서 message를 찾아 사용
        print(message)

    inner()

outer()</code></pre>
<p><strong>실행 결과:</strong></p>
<p><code>Hello from outer</code></p>
<p>이 코드가 문제없이 실행되는 이유는 LEGB 규칙 때문입니다. <code>inner</code> 함수가 <code>print(message)</code>를 실행할 때, 파이썬은 먼저 <code>inner</code> 함수의 지역(Local) 스코프에서 <code>message</code> 변수를 찾습니다. 하지만 없으므로, 다음 단계인 Enclosing 스코프로 이동합니다. <code>outer</code> 함수의 스코프에 <code>message</code> 변수가 존재하므로, 그 값을 가져와 성공적으로 출력합니다.</p>
<p>이처럼 내부 함수가 외부 함수의 변수를 참조(읽기)하는 것은 매우 직관적이고 간단합니다. 하지만 문제가 발생하는 지점은 바로 이 변수의 값을 &#39;수정(modify)&#39;하려고 할 때입니다.</p>
<h2 id="3-unboundlocalerror의-함정">3. <code>UnboundLocalError</code>의 함정</h2>
<p>외부 함수의 변수를 읽는 것은 자유롭지만, 그 값을 바꾸려고 시도하는 순간 파이썬은 예상치 못한 에러를 발생시킵니다. 이 현상은 많은 초보 개발자들을 혼란에 빠뜨리는 주범이며, <code>nonlocal</code>이 필요한 이유를 가장 극적으로 보여주는 사례입니다.</p>
<h3 id="문제의-코드">문제의 코드</h3>
<p>2부의 예제를 약간 수정하여, 내부 함수에서 외부 함수의 변수 값을 변경해 보겠습니다. 방문 횟수를 세는 간단한 카운터 함수를 가정해 봅시다.</p>
<pre><code class="language-python">def outer():
    count = 0  # 방문 횟수를 기록할 변수

    def inner():
        # 외부 함수의 count 값을 1 증가시키려고 시도
        count += 1  # 여기서 문제가 발생한다!
        print(f&quot;방문 횟수: {count}&quot;)

    inner()

outer()
</code></pre>
<p>이 코드를 실행하면 <code>방문 횟수: 1</code>이 출력될 것이라고 기대하기 쉽습니다. 하지만 실제 결과는 다음과 같은 에러 메시지입니다.</p>
<p><strong>에러 발생:</strong></p>
<p><code>UnboundLocalError: local variable &#39;count&#39; referenced before assignment</code></p>
<p>&quot;지역 변수 &#39;count&#39;가 할당되기 전에 참조되었습니다&quot;라는 이 에러 메시지는 언뜻 보기에 이상합니다. <code>count</code>는 분명히 <code>outer</code> 함수에 <code>0</code>으로 할당되어 있는데 왜 이런 에러가 발생하는 것일까요?</p>
<h3 id="왜-에러가-날까---파이썬의-규칙">왜 에러가 날까? - 파이썬의 규칙</h3>
<p>이 에러의 근본적인 원인은 파이썬이 변수의 스코프를 결정하는 방식에 있습니다.</p>
<ol>
<li><strong>할당문은 지역 변수를 만든다:</strong> 파이썬은 어떤 함수 내부를 해석할 때, 그 함수 안에서 <strong>할당문(<code>=</code>, <code>+=</code>, <code>=</code>, <code>=</code>, <code>/=</code>)을 사용하여 값을 할당받는 변수는 무조건 그 함수의 &#39;지역 변수(Local Variable)&#39;로 간주</strong>합니다. 이는 코드가 실제로 실행되기 전, 함수 정의를 읽어들이는 시점에 결정됩니다.</li>
<li><strong><code>count += 1</code>의 함정:</strong> <code>inner</code> 함수 안에 <code>count += 1</code>이라는 코드가 있습니다. 이 코드는 <code>count = count + 1</code>과 동일한 의미를 가지는 명백한 할당문입니다. 파이썬 인터프리터는 <code>inner</code> 함수를 훑어보다가 이 할당문을 발견하고, &quot;아하, <code>count</code>는 <code>inner</code> 함수의 지역 변수구나!&quot;라고 결론 내립니다.</li>
<li><strong>스코프 검색의 중단:</strong> 일단 <code>count</code>가 <code>inner</code>의 지역 변수로 &#39;낙인&#39;찍히고 나면, 파이썬은 <code>inner</code> 함수 안에서 <code>count</code>라는 이름을 만날 때 더 이상 바깥쪽 스코프(Enclosing 또는 Global)를 쳐다보지 않습니다. 오직 <code>inner</code> 함수 내부에서만 <code>count</code>를 찾으려고 합니다.</li>
<li><strong>에러의 순간:</strong> 이제 <code>count = count + 1</code>의 오른쪽 부분, 즉 <code>count</code>의 현재 값을 읽어오려는 시점에 문제가 발생합니다. 파이썬은 <code>inner</code> 함수의 지역 스코프에서 <code>count</code>의 값을 찾지만, 아직 아무런 값도 할당된 적이 없습니다. 따라서 &quot;값이 할당되기도 전에 참조하려고 했다&quot;는 <code>UnboundLocalError</code>가 발생하는 것입니다.</li>
</ol>
<p>결론적으로, 이 에러는 &#39;외부 변수 수정 시도의 실패&#39;가 아니라, &#39;지역 변수로 잘못 간주된 변수를 초기값 없이 사용하려는 시도&#39;에서 비롯된 것입니다. <code>count += 1</code>이라는 코드 한 줄 때문에 <code>outer</code> 함수에 있는 <code>count</code>는 <code>inner</code> 함수에게 완전히 보이지 않는 존재가 되어버린 셈입니다. 이 미묘하지만 결정적인 차이를 이해하는 것이 <code>nonlocal</code>의 필요성을 깨닫는 열쇠입니다.</p>
<h2 id="4-nonlocal-키워드">4. <code>nonlocal</code> 키워드</h2>
<p>앞서 살펴본 <code>UnboundLocalError</code>는 파이썬의 스코프 규칙 때문에 발생하는, 피할 수 없는 함정처럼 보입니다. 하지만 파이썬은 이 문제를 해결할 명쾌한 방법을 제공합니다. 바로 <code>nonlocal</code> 키워드입니다.</p>
<h3 id="해결사-nonlocal의-등장">해결사 <code>nonlocal</code>의 등장</h3>
<p><code>nonlocal</code>은 파이썬 인터프리터에게 보내는 명시적인 선언입니다. &quot;지금부터 내가 사용하려는 이 변수는 내 방(Local 스코프)에 새로 만드는 것이 아니다. 우리 집 거실(Enclosing 스코프)에 있는 바로 그 변수를 가져다 쓰겠다&quot;고 알려주는 역할을 합니다. 이 선언을 통해 파이썬이 변수를 지역 변수로 오인하는 것을 막고, 의도대로 외부 함수의 변수에 접근하여 수정할 수 있게 됩니다.</p>
<h3 id="before--after-코드-비교">Before &amp; After 코드 비교</h3>
<p>3부에서 에러를 일으켰던 코드를 <code>nonlocal</code>을 사용하여 수정한 버전과 나란히 비교해 보면 그 차이가 극명하게 드러납니다.</p>
<pre><code class="language-python"># Before (Error 발생)
def outer_before():
    count = 0
    def inner():
        # 파이썬은 이 할당문 때문에 count를 inner의 지역 변수로 착각
        count += 1
        print(f&quot;방문 횟수: {count}&quot;)
    inner()

# After (성공!)
def outer_after():
    count = 0
    def inner():
        nonlocal count  # &quot;count는 지역 변수가 아니야!&quot; 라고 선언
        count += 1
        print(f&quot;방문 횟수: {count}&quot;)
    inner()

# outer_before()  # UnboundLocalError 발생
outer_after()     # 정상 실행
</code></pre>
<p><strong><code>outer_after()</code> 실행 결과:</strong></p>
<p><code>방문 횟수: 1</code></p>
<h3 id="nonlocal의-역할"><code>nonlocal</code>의 역할</h3>
<p><code>outer_after</code> 함수에서 <code>inner</code> 함수의 시작 부분에 추가된 <code>nonlocal count</code> 한 줄이 모든 것을 바꿉니다. 이 선언은 파이썬 인터프리터에게 다음과 같이 지시합니다 :</p>
<blockquote>
<p>&quot;이 inner 함수 안에서 count라는 이름에 값을 할당하더라도, 새로운 지역 변수를 만들지 마라. 대신, 이 함수를 둘러싸고 있는 가장 가까운 외부 함수(Enclosing Scope)에서 count라는 이름의 변수를 찾아 그것을 사용해라.&quot;</p>
</blockquote>
<p>이 지시 덕분에, 파이썬은 <code>count += 1</code>을 보더라도 <code>count</code>를 지역 변수로 간주하지 않습니다. 대신 LEGB 규칙의 &#39;E&#39; 단계로 바로 넘어가 <code>outer_after</code> 함수에 있는 <code>count</code> 변수를 찾아내고, 그 변수의 값을 성공적으로 1 증가시킵니다.</p>
<p>이처럼 <code>nonlocal</code>은 파이썬의 기본 스코프 결정 규칙(함수 내 할당문 = 지역 변수)을 개발자가 의도적으로 재정의할 수 있게 해주는 &#39;명시적 선언&#39;입니다. 이는 &quot;암묵적인 것보다 명시적인 것이 낫다(Explicit is better than implicit)&quot;는 파이썬의 핵심 철학(Zen of Python)과도 맞닿아 있습니다. <code>nonlocal</code> 키워드를 사용함으로써, 개발자는 &quot;나는 지금 의도적으로 바깥 스코프의 변수를 수정하고 있다&quot;는 명확한 &#39;깃발&#39;을 코드에 꽂는 셈입니다. 이는 코드를 읽는 다른 사람(혹은 미래의 나 자신)에게 변수의 흐름과 의도를 명확하게 전달하는 중요한 역할을 합니다.</p>
<h2 id="5-nonlocal-vs-global">5. <code>nonlocal</code> vs. <code>global</code></h2>
<p><code>nonlocal</code>을 배우는 과정에서 많은 이들이 <code>global</code> 키워드와 혼동을 겪습니다. 두 키워드 모두 함수 내부에서 바깥 스코프의 변수를 수정할 때 사용된다는 공통점이 있지만, 그들이 가리키는 &#39;바깥&#39;의 정의는 완전히 다릅니다. 이 둘의 차이점을 명확히 이해하는 것은 스코프를 제대로 다루기 위해 필수적입니다.</p>
<h3 id="global-전역-스코프"><code>global</code>: 전역 스코프</h3>
<p><code>global</code> 키워드는 이름 그대로 오직 <strong>전역(Global) 스코프</strong>, 즉 모듈 최상위 레벨에 있는 변수만을 가리킵니다. 함수가 아무리 깊게 중첩되어 있더라도</p>
<p><code>global</code> 선언은 모든 중간 스코프(Enclosing)를 무시하고 곧장 맨 꼭대기 층으로 올라가 변수를 찾습니다.</p>
<h3 id="nonlocal-가장-가까운-스코프"><code>nonlocal</code>: 가장 가까운 스코프</h3>
<p>반면, <code>nonlocal</code> 키워드는 바로 한 단계 위 스코프부터 시작하여 자신을 감싸고 있는 <strong>가장 가까운 Enclosing 스코프</strong>의 변수만을 찾습니다. 만약 가장 가까운 Enclosing 스코프에 해당 변수가 없다면 그보다 더 바깥쪽의 Enclosing 스코프를 순차적으로 탐색합니다. 중요한 점은,</p>
<p><code>nonlocal</code>은 아무리 탐색해도 전역 스코프의 변수는 절대 건드리지 않는다는 것입니다.</p>
<h3 id="결정적-차이를-보여주는-예제">결정적 차이를 보여주는 예제</h3>
<p>세 개의 다른 스코프(Global, Enclosing, Local)에 모두 같은 이름의 변수 <code>x</code>가 있을 때, <code>nonlocal</code>과 <code>global</code>이 각각 어떤 변수를 수정하는지 살펴보면 그 차이가 명확해집니다.</p>
<pre><code class="language-python">x = &quot;나는 전역(Global) 변수&quot;

def outer():
    x = &quot;나는 둘러싸는(Enclosing) 변수&quot;

    def inner_nonlocal():
        nonlocal x  # Enclosing 스코프의 x를 가리킴
        x = &quot;nonlocal에 의해 수정됨&quot;
        print(f&quot;  [inner_nonlocal] x = {x}&quot;)

    def inner_global():
        global x  # Global 스코프의 x를 가리킴
        x = &quot;global에 의해 수정됨&quot;
        print(f&quot;  [inner_global] x = {x}&quot;)

    print(f&quot;[outer] 시작 시 x = {x}&quot;)
    inner_nonlocal()
    print(f&quot;[outer] nonlocal 호출 후 x = {x}&quot;) # Enclosing 변수가 바뀜
    inner_global()
    print(f&quot;[outer] global 호출 후 x = {x}&quot;)   # Enclosing 변수는 영향 없음

print(f&quot;[전역] 시작 시 x = {x}&quot;)
outer()
print(f&quot;[전역] 모든 호출 후 x = {x}&quot;) # Global 변수가 바뀜
</code></pre>
<p><strong>실행 결과:</strong></p>
<p><code>[전역] 시작 시 x = 나는 전역(Global) 변수
[outer] 시작 시 x = 나는 둘러싸는(Enclosing) 변수
  [inner_nonlocal] x = nonlocal에 의해 수정됨
[outer] nonlocal 호출 후 x = nonlocal에 의해 수정됨
  [inner_global] x = global에 의해 수정됨
[outer] global 호출 후 x = nonlocal에 의해 수정됨
[전역] 모든 호출 후 x = global에 의해 수정됨</code></p>
<p>결과에서 볼 수 있듯이, <code>inner_nonlocal</code>은 <code>outer</code> 함수의 <code>x</code> 값을 변경했고, <code>inner_global</code>은 모듈 최상단의 전역 <code>x</code> 값을 변경했습니다. <code>outer</code> 함수 내의 <code>x</code>는 <code>inner_global</code> 호출에 전혀 영향을 받지 않았습니다.</p>
<h3 id="또-다른-중요한-차이점">또 다른 중요한 차이점</h3>
<p><code>nonlocal</code>로 지정된 변수는 반드시 Enclosing 스코프에 <strong>미리 존재해야</strong> 합니다. 만약 존재하지 않는 변수를 <code>nonlocal</code>로 선언하면 <code>SyntaxError</code>가 발생합니다. 반면, <code>global</code>은 전역 스코프에 해당 변수가 없더라도 에러를 발생시키지 않으며, 그 함수 내에서 값을 할당하는 순간 <strong>새로운 전역 변수를 생성</strong>합니다.</p>
<h3 id="nonlocal-vs-global-심층-비교"><code>nonlocal</code> vs. <code>global</code> 심층 비교</h3>
<p>두 키워드의 핵심적인 차이점을 다음 표로 정리할 수 있습니다.</p>
<table>
<thead>
<tr>
<th>특징</th>
<th><code>nonlocal</code></th>
<th><code>global</code></th>
</tr>
</thead>
<tbody><tr>
<td><strong>목표 스코프</strong></td>
<td>가장 가까운 Enclosing(둘러싸는) 스코프</td>
<td>최상위 Global(전역) 스코프</td>
</tr>
<tr>
<td><strong>검색 방식</strong></td>
<td>안쪽에서 바깥쪽으로 가장 가까운 것 하나만 찾음</td>
<td>스코프 계층을 무시하고 바로 전역으로 이동</td>
</tr>
<tr>
<td><strong>변수 사전 존재</strong></td>
<td><strong>반드시</strong> Enclosing 스코프에 존재해야 함</td>
<td>존재하지 않으면 새로 생성 가능</td>
</tr>
<tr>
<td><strong>사용 가능 위치</strong></td>
<td>중첩 함수 내부에서만 의미 있음</td>
<td>모든 함수 내부에서 사용 가능</td>
</tr>
<tr>
<td><strong>주요 사용처</strong></td>
<td>클로저(Closure), 팩토리 함수 등 상태 관리</td>
<td>모듈 전체에서 공유되는 설정 값, 상태 변수</td>
</tr>
</tbody></table>
<p>이처럼 <code>nonlocal</code>과 <code>global</code>은 비슷해 보이지만, 변수를 찾는 범위와 규칙에서 근본적인 차이를 가집니다. <code>nonlocal</code>은 중첩된 구조 내에서의 &#39;지역적&#39; 상태 변경을, <code>global</code>은 프로그램 전체의 &#39;전역적&#39; 상태 변경을 담당한다고 이해하면 명확합니다.</p>
<h2 id="6-nonlocal의-실전-활용-사례">6. <code>nonlocal</code>의 실전 활용 사례</h2>
<p>지금까지 <code>nonlocal</code>을 <code>UnboundLocalError</code>를 해결하기 위한 문법적 도구로 살펴보았다면, 이제부터는 <code>nonlocal</code>이 어떻게 더 높은 차원의 프로그래밍 패턴을 구현하는 강력한 도구가 되는지 알아보겠습니다. <code>nonlocal</code>의 진정한 힘은 &#39;상태를 기억하고 성장하는 함수&#39;를 만들 때 드러납니다.</p>
<h3 id="예제-1-상태를-기억하는-함수-클로저closure">예제 1: 상태를 기억하는 함수, 클로저(Closure)</h3>
<p><strong>클로저란 무엇인가?</strong>
클로저는 &quot;자신이 정의될 때의 환경(스코프)을 기억하는 함수&quot;입니다. 조금 더 풀어서 설명하면, 어떤 함수가 자신의 본문 밖에서 정의된, 그러나 전역 변수는 아닌 변수를 참조할 때, 그 함수와 변수가 함께 묶여 하나의 단위처럼 동작하는 것을 말합니다. 외부 함수는 이미 실행이 끝나 사라졌지만, 그 외부 함수가 가지고 있던 변수(자유 변수, free variable)들은 내부 함수에 의해 계속 &#39;붙잡혀서&#39; 살아있는 상태가 됩니다. 마치 &#39;레시피(함수 코드)와 특별한 재료(환경 변수)를 함께 담아둔 밀키트&#39;와 같습니다.</p>
<p><strong>대표 예제: 카운터 함수</strong><code>nonlocal</code>을 사용한 카운터 함수는 클로저의 가장 대표적인 예제입니다.</p>
<pre><code class="language-python">def counter():
    count = 0  # Enclosing 스코프의 변수. 이 값이 기억될 것임.
    def increment():
        nonlocal count  # &#39;count&#39;는 지역 변수가 아님을 선언
        count += 1
        return count
    return increment  # 내부 함수 자체를 반환

# 카운터 인스턴스 생성
counter1 = counter()
counter2 = counter()

print(f&quot;counter1 첫 번째 호출: {counter1()}&quot;) # 출력: 1
print(f&quot;counter1 두 번째 호출: {counter1()}&quot;) # 출력: 2
print(f&quot;counter1 세 번째 호출: {counter1()}&quot;) # 출력: 3

print(f&quot;counter2 첫 번째 호출: {counter2()}&quot;) # 출력: 1
print(f&quot;counter2 두 번째 호출: {counter2()}&quot;) # 출력: 2
</code></pre>
<p>이 코드는 다음과 같은 단계로 동작합니다.</p>
<ol>
<li><code>counter1 = counter()</code>가 호출되면, <code>counter</code> 함수가 실행됩니다. <code>count</code> 변수가 <code>0</code>으로 초기화되고, <code>increment</code> 함수가 정의됩니다.</li>
<li><code>counter</code> 함수는 <code>increment</code> 함수 객체를 반환하고 자신의 생을 마감합니다.</li>
<li>이때 <strong>클로저</strong>가 생성됩니다. <code>increment</code> 함수는 자신이 만들어진 환경, 즉 <code>count = 0</code>이라는 변수와의 연결을 &#39;기억&#39;한 채로 <code>counter1</code> 변수에 저장됩니다. <code>count</code> 변수는 사라지지 않고 <code>counter1</code>에 의해 붙잡혀 살아남습니다.</li>
<li><code>counter1()</code>이 호출될 때마다, <code>increment</code> 함수가 실행됩니다. <code>nonlocal count</code> 선언 덕분에, 이 함수는 새로 <code>count</code>를 만드는 대신 &#39;살아남은&#39; 바로 그 <code>count</code> 변수에 접근하여 값을 1씩 증가시킵니다.</li>
<li><code>counter2 = counter()</code>를 통해 새로운 카운터를 만들면, <code>counter2</code>는 <code>counter1</code>과는 완전히 독립적인 자신만의 <code>count</code> 변수를 가진 클로저가 됩니다. 따라서 두 카운터는 서로의 상태에 영향을 주지 않습니다.</li>
</ol>
<p><code>nonlocal</code>이 없었다면, 클로저는 자신의 환경을 &#39;기억&#39;은 할 수 있어도 &#39;변경&#39;할 수는 없었을 것입니다. <code>nonlocal</code>은 클로저가 단순한 기억을 넘어 &#39;성장(상태 변경)&#39;할 수 있게 만드는 핵심 동력입니다.</p>
<h3 id="예제-2-유연한-함수-생성기factory-function">예제 2: 유연한 함수 생성기(Factory Function)</h3>
<p>함수 팩토리는 특정 설정값을 받아 그에 맞는 &#39;맞춤형 함수&#39;를 동적으로 생성하여 반환하는 함수입니다. 클로저는 이러한 함수 팩토리를 구현하는 자연스러운 방법입니다.</p>
<p><strong>예제: 메시지 생성기</strong>
다양한 종류의 로그 메시지를 출력하는 함수를 만드는 팩토리 함수 예제입니다.</p>
<pre><code class="language-python">def message_factory(prefix):
    # &#39;prefix&#39;는 반환될 &#39;logger&#39; 함수의 Enclosing 스코프에 저장됨
    def logger(content):
        # nonlocal이 필요 없는 이유: prefix를 &#39;읽기만&#39; 하기 때문
        print(f&quot;[{prefix}] {content}&quot;)
    return logger

log_error = message_factory(&quot;ERROR&quot;)
log_info = message_factory(&quot;INFO&quot;)

log_error(&quot;파일을 찾을 수 없습니다.&quot;)
log_info(&quot;데이터 처리 중입니다.&quot;)
</code></pre>
<p><strong>실행 결과:</strong></p>
<p> <code>파일을 찾을 수 없습니다.
[INFO] 데이터 처리 중입니다.</code></p>
<p><code>log_error</code>와 <code>log_info</code>는 각각 <code>&quot;ERROR&quot;</code>와 <code>&quot;INFO&quot;</code>라는 <code>prefix</code> 값을 기억하는 서로 다른 클로저입니다.</p>
<p><strong>예제: 비밀번호 검증기 (상태 저장)</strong><code>nonlocal</code>을 사용하면 상태를 저장하고 변경하는, 한층 더 발전된 팩토리 함수를 만들 수 있습니다. 예를 들어, 비밀번호 시도 횟수를 제한하는 검증기를 만들어 보겠습니다.</p>
<pre><code class="language-python">def create_validator(correct_password, max_attempts=3):
    attempts_left = max_attempts

    def validate(password):
        nonlocal attempts_left
        if attempts_left &lt;= 0:
            print(&quot;계정이 잠겼습니다. 더 이상 시도할 수 없습니다.&quot;)
            return False

        if password == correct_password:
            print(&quot;인증 성공!&quot;)
            return True
        else:
            attempts_left -= 1
            print(f&quot;비밀번호가 틀렸습니다. 남은 시도 횟수: {attempts_left}&quot;)
            return False

    return validate

validator_A = create_validator(&quot;pa$$w0rd&quot;)

validator_A(&quot;wrong_pass&quot;)
validator_A(&quot;another_wrong&quot;)
validator_A(&quot;pa$$w0rd&quot;)
validator_A(&quot;too_late&quot;)
</code></pre>
<p><strong>실행 결과:</strong></p>
<p><code>비밀번호가 틀렸습니다. 남은 시도 횟수: 2
비밀번호가 틀렸습니다. 남은 시도 횟수: 1
인증 성공!
비밀번호가 틀렸습니다. 남은 시도 횟수: 0</code></p>
<p>이 예제에서 <code>validator_A</code>는 <code>attempts_left</code>라는 상태를 내부적으로 유지하며, 호출될 때마다 <code>nonlocal</code>을 통해 이 상태를 변경합니다. 이처럼 <code>nonlocal</code>과 클로저를 활용하면, 객체 지향 프로그래밍의 클래스(Class)가 제공하는 &#39;상태 캡슐화&#39;를 함수형 프로그래밍 스타일로도 우아하게 구현할 수 있습니다.</p>
<h2 id="7-흔히-저지르는-실수와-모범-사례">7. 흔히 저지르는 실수와 모범 사례</h2>
<p><code>nonlocal</code>은 강력한 도구이지만, 잘못 사용하면 에러를 발생시키거나 코드의 의도를 흐릴 수 있습니다. <code>nonlocal</code> 사용 시 발생할 수 있는 일반적인 실수와 함정을 미리 알아두고, 언제 사용하는 것이 좋은지에 대한 가이드라인을 따르는 것이 중요합니다.</p>
<h3 id="실수-1-전역-변수에-nonlocal-사용하기">실수 1: 전역 변수에 <code>nonlocal</code> 사용하기</h3>
<p><code>nonlocal</code>은 자신을 둘러싼 Enclosing 스코프의 변수만을 찾습니다. 전역 스코프는 Enclosing 스코프가 아니므로, 전역 변수를 <code>nonlocal</code>로 참조하려고 하면 에러가 발생합니다.</p>
<pre><code class="language-python"># 잘못된 사용 예
x = 100

def my_func():
    # SyntaxError: no binding for nonlocal &#39;x&#39; found
    # &#39;x&#39;를 둘러싼 Enclosing 스코프가 없기 때문
    nonlocal x
    x = 200
</code></pre>
<p>전역 변수를 수정하고 싶을 때는 <code>global</code> 키워드를 사용해야 합니다.</p>
<h3 id="실수-2-중첩-함수가-아닌-곳에-nonlocal-사용하기">실수 2: 중첩 함수가 아닌 곳에 <code>nonlocal</code> 사용하기</h3>
<p><code>nonlocal</code>은 Enclosing 스코프가 존재하는, 즉 중첩된 함수 구조 안에서만 의미가 있습니다. 일반 함수나 모듈의 최상위 레벨에서 <code>nonlocal</code>을 사용하면 <code>SyntaxError</code>가 발생합니다.</p>
<pre><code class="language-python"># 잘못된 사용 예
def my_func():
    # SyntaxError: nonlocal declaration not allowed at module level
    # (Jupyter Notebook 등에서는 다른 에러 메시지가 나올 수 있음)
    # 이 함수는 중첩 함수가 아니므로 Enclosing 스코프가 없음
    nonlocal y
    y = 10
</code></pre>
<h3 id="함정-변경-가능한mutable-객체는-nonlocal이-필요-없다">함정: 변경 가능한(Mutable) 객체는 <code>nonlocal</code>이 필요 없다?</h3>
<p>리스트(list), 딕셔너리(dict), 세트(set)와 같은 변경 가능한(mutable) 객체의 경우, <code>nonlocal</code> 선언 없이도 내부 함수에서 그 객체의 &#39;내용물&#39;을 수정할 수 있습니다. 이는 혼동을 일으키기 쉬운 지점입니다.</p>
<pre><code class="language-python">def outer():
    my_list =

    def inner_append():
        # my_list 변수 자체를 바꾸는 것이 아니라,
        # 그 변수가 가리키는 리스트 객체의 내용을 수정하는 것
        my_list.append(4)

    def inner_reassign():
        # 여기서 UnboundLocalError 발생!
        # my_list라는 변수 자체에 새로운 리스트를 할당하려 하기 때문
        my_list =

    def inner_reassign_fixed():
        nonlocal my_list
        # nonlocal 선언이 있으면 변수 자체를 재할당할 수 있음
        my_list =

    inner_append()
    print(f&quot;append 후: {my_list}&quot;) # 출력:

    inner_reassign_fixed()
    print(f&quot;재할당 후: {my_list}&quot;) # 출력:
</code></pre>
<p>핵심은 <strong>객체의 내용을 변경하는 것(mutation)</strong>과 <strong>변수에 다른 객체를 재할당하는 것(rebinding)</strong>의 차이를 이해하는 것입니다. <code>.append()</code>, <code>.pop()</code>, <code>my_dict[&#39;key&#39;] = value</code>와 같은 연산은 변수가 가리키는 객체는 그대로 둔 채 내용만 바꾸므로 <code>nonlocal</code>이 필요 없습니다. 하지만 <code>my_list = [...]</code>와 같이 변수 자체가 다른 객체를 가리키도록 하려면 반드시 <code>nonlocal</code> 선언이 필요합니다.</p>
<h3 id="모범-사례">모범 사례</h3>
<ol>
<li><strong>꼭 필요할 때만 사용하라:</strong> <code>nonlocal</code>은 코드의 흐름을 조금 더 복잡하게 만들 수 있습니다. 단순히 값을 전달하는 것이 목적이라면, 함수 인자(argument)로 넘겨주고 반환값(return)을 받는 것이 더 명확하고 직관적인 방법일 수 있습니다.</li>
<li><strong>클래스를 고려하라:</strong> <code>nonlocal</code>을 사용하여 여러 변수의 상태를 복잡하게 관리해야 한다면, 이는 클래스(Class)를 사용해야 할 신호일 수 있습니다. 클래스는 상태(속성, attribute)와 그 상태를 조작하는 행위(메서드, method)를 하나의 단위로 묶어주므로, 복잡한 상태 관리에 더 구조적이고 명확한 해결책을 제공합니다. <code>nonlocal</code>과 클로저는 가벼운 상태 캡슐화에 적합하고, 클래스는 더 무겁고 체계적인 상태 관리에 적합합니다.</li>
</ol>
<h2 id="8-결론-코드의-환경을-지배하는-개발자로-거듭나기">8. 결론: 코드의 환경을 지배하는 개발자로 거듭나기</h2>
<p>지금까지 우리는 파이썬의 <code>nonlocal</code> 키워드를 중심으로 변수가 살아가는 세상, 즉 스코프의 복잡하고 정교한 규칙들을 탐험했습니다. 이 여정을 통해 우리는 다음과 같은 핵심적인 사실들을 확인했습니다.</p>
<ul>
<li><strong>스코프(LEGB)는 변수의 &#39;활동 무대&#39;입니다.</strong> 파이썬은 L → E → G → B라는 명확한 순서에 따라 변수를 찾아내며, 이 규칙은 코드의 안정성과 예측 가능성을 보장하는 기반이 됩니다.</li>
<li><strong><code>nonlocal</code>은 중첩된 무대 사이를 연결하는 특별한 &#39;통로&#39;입니다.</strong> 기본적으로 내부 함수는 외부 함수의 변수를 수정할 수 없지만, <code>nonlocal</code>은 이 장벽을 넘어 가장 가까운 외부 스코프의 변수에 대한 수정 권한을 명시적으로 부여합니다.</li>
<li><strong><code>nonlocal</code>은 단순한 에러 해결사가 아닙니다.</strong> <code>UnboundLocalError</code>를 해결하는 열쇠일 뿐만 아니라, 클로저와 팩토리 함수 같은 강력한 프로그래밍 패턴을 통해 &#39;상태를 기억하고 성장하는 함수&#39;를 만드는 창의적인 도구입니다. 이를 통해 함수만으로도 객체와 유사한 캡슐화를 구현할 수 있습니다.</li>
<li><strong><code>nonlocal</code>과 <code>global</code>은 목표가 다릅니다.</strong> <code>nonlocal</code>이 중첩된 함수 구조 내의 &#39;가까운&#39; 상태를 다루는 데 집중한다면, <code>global</code>은 프로그램 전체에 걸친 &#39;먼&#39; 상태를 다룹니다. 둘의 차이를 명확히 아는 것은 코드의 의도를 정확하게 표현하는 데 필수적입니다.</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[개발자의 나침반: 최신 API 기초 완벽 가이드]]></title>
            <link>https://velog.io/@cha-suyeon/%EA%B0%9C%EB%B0%9C%EC%9E%90%EC%9D%98-%EB%82%98%EC%B9%A8%EB%B0%98-%EC%B5%9C%EC%8B%A0-API-%EA%B8%B0%EC%B4%88-%EC%99%84%EB%B2%BD-%EA%B0%80%EC%9D%B4%EB%93%9C</link>
            <guid>https://velog.io/@cha-suyeon/%EA%B0%9C%EB%B0%9C%EC%9E%90%EC%9D%98-%EB%82%98%EC%B9%A8%EB%B0%98-%EC%B5%9C%EC%8B%A0-API-%EA%B8%B0%EC%B4%88-%EC%99%84%EB%B2%BD-%EA%B0%80%EC%9D%B4%EB%93%9C</guid>
            <pubDate>Wed, 27 Aug 2025 05:03:39 GMT</pubDate>
            <description><![CDATA[<h1 id="개발자의-나침반-최신-api-기초-완벽-가이드">개발자의 나침반: 최신 API 기초 완벽 가이드</h1>
<p><img src="https://velog.velcdn.com/images/cha-suyeon/post/cf215dbd-727a-4f49-980a-2fbc8bb70878/image.png" alt=""></p>
<h2 id="서론">서론</h2>
<p>오늘날 우리가 사용하는 거의 모든 최신 애플리케이션은 API(Application Programming Interface)에 의해 구동됩니다. 스마트폰의 날씨 앱부터 복잡한 기업용 대시보드에 이르기까지, API는 우리 디지털 세계를 함께 엮는 보이지 않는 실과 같습니다. API는 서로 다른 소프트웨어 시스템이 통신할 수 있도록 하는 규칙과 프로토콜의 집합입니다. 이는 클라이언트가 서버에 정보나 작업을 어떻게 요청할 수 있는지를 정의하는 계약서와 같습니다.</p>
<p>수많은 API 구축 방식 중에서, REST(REpresentational State Transfer)는 오늘날 웹 API를 구축하는 데 있어 지배적인 아키텍처 스타일로 자리 잡았습니다. 그 이유는 단순성, 확장성, 그리고 웹 자체의 아키텍처와 잘 부합하기 때문입니다.</p>
<p>이 가이드는 API 개발의 여정을 체계적으로 안내할 것입니다. 먼저 &#39;왜&#39;에 해당하는 REST의 핵심 원칙부터 시작하여, &#39;무엇을&#39; 의미하는 HTTP 메서드와 상태 코드를 살펴볼 것입니다. 그다음 &#39;어떻게&#39;에 해당하는 실제 코드 예제를 통해 이론을 실습으로 옮기고, 마지막으로 &#39;어떻게 잘할 것인가&#39;에 대한 고급 설계 및 보안 모범 사례를 다루며 마무리할 것입니다. 이 글을 통해 API의 기초를 다지고, 더 나아가 견고하고 효율적인 API를 설계하고 개발하는 데 필요한 깊이 있는 지식을 얻게 될 것입니다.</p>
<hr>
<h2 id="1장-통신의-청사진---restful-api-이해하기">1장: 통신의 청사진 - RESTful API 이해하기</h2>
<p>API 개발의 세계로 뛰어들기 전에, 가장 중요한 개념 중 하나를 명확히 해야 합니다. 바로 REST는 프로토콜이 아닌 아키텍처 스타일이라는 점입니다. 이는 매우 중요한 구별입니다. HTTP는 클라이언트와 서버가 데이터를 주고받는 데 사용하는 통신 프로토콜, 즉 &#39;언어와 전달 메커니즘&#39;입니다. 반면, REST는 우리가 HTTP를 효과적으로 사용하도록 안내하는 아키텍처 스타일, 즉 &#39;문법과 대화의 규칙&#39;입니다. 이러한 분리 덕분에 REST는 웹의 기존 인프라를 최대한 활용하여 단순하고 확장 가능한 시스템을 구축할 수 있습니다.</p>
<h3 id="rest의-여섯-가지-핵심-원칙-the-why">REST의 여섯 가지 핵심 원칙 (The &quot;Why&quot;)</h3>
<p>REST 아키텍처는 견고하고 확장 가능한 시스템을 구축하기 위한 여섯 가지 핵심 원칙 또는 제약 조건에 기반합니다. 이 원칙들은 개별적인 규칙이 아니라 서로 긴밀하게 연결된 시스템으로, 하나를 위반하면 다른 원칙들의 이점까지 약화될 수 있습니다..</p>
<h3 id="클라이언트-서버-분리-client-server-separation">클라이언트-서버 분리 (Client-Server Separation)</h3>
<p>이 원칙은 클라이언트(사용자 인터페이스, 프론트엔드)와 서버(데이터 저장 및 비즈니스 로직, 백엔드)가 완전히 독립적인 개체로 존재해야 함을 의미합니다. 둘 사이의 유일한 소통 창구는 네트워크를 통해 주고받는 요청과 응답뿐입니다.</p>
<p>이러한 관심사의 분리가 중요한 이유는 각 구성 요소가 독립적으로 발전하고, 배포되고, 확장될 수 있기 때문입니다. 예를 들어, 서버의 로직이 특정 사용자 인터페이스에 얽매여 있지 않기 때문에 하나의 잘 설계된 API를 통해 웹 애플리케이션, 모바일 앱, 데스크톱 앱이 모두 동일한 데이터와 기능에 접근할 수 있습니다. 클라이언트-서버 분리는 &#39;균일 인터페이스(Uniform Interface)&#39;라는 안정적이고 예측 가능한 계약이 존재하기에 실질적으로 작동할 수 있습니다. 이 계약이 없다면 클라이언트와 서버는 서로의 구현에 대해 너무 많은 것을 알아야 하므로 긴밀하게 결합되어 분리의 목적을 상실하게 됩니다.</p>
<h3 id="무상태성-statelessness">무상태성 (Statelessness)</h3>
<p>무상태성은 REST의 가장 중요한 특징 중 하나입니다. 이는 클라이언트가 서버로 보내는 모든 요청이 해당 요청을 이해하고 처리하는 데 필요한 모든 정보를 포함해야 한다는 것을 의미합니다. 서버는 요청과 요청 사이에 클라이언트의 세션 상태(컨텍스트)를 저장하지 않습니다.</p>
<p>이 원칙은 신뢰성과 확장성을 극적으로 향상시킵니다. 서버는 이전 요청에 대한 기록을 유지할 필요가 없으므로, 어떤 서버 인스턴스라도 들어오는 모든 요청을 처리할 수 있습니다. 이는 서버 설계를 단순화하고 로드 밸런싱을 매우 용이하게 만듭니다. 무상태성은 또한 캐시 가능성(Cacheability) 원칙이 효과적으로 작동하는 기반이 됩니다. 각 요청이 독립적이고 완전하기 때문에, 중간 캐시는 이전 세션 상태를 확인할 필요 없이 동일한 요청에 대해 캐시된 응답을 안전하게 제공할 수 있습니다.</p>
<h3 id="균일-인터페이스-uniform-interface">균일 인터페이스 (Uniform Interface)</h3>
<p>균일 인터페이스는 REST 아키텍처의 핵심이며, API와 상호작용하는 일관되고 예측 가능한 방식을 보장합니다. 이 원칙은 네 가지 하위 제약 조건으로 구성됩니다.</p>
<ol>
<li><strong>리소스 식별 (Identification of Resources via URIs):</strong> REST에서 모든 것은 &#39;리소스&#39;로 간주됩니다. 사용자가 될 수도 있고, 제품, 주문 등 모든 개념이 리소스가 될 수 있습니다. 각 리소스는 <code>/users/123</code>이나 <code>/products/456</code>과 같은 고유한 URI(Uniform Resource Identifier)를 통해 식별됩니다. URI는 API의 &#39;명사&#39; 역할을 합니다.</li>
<li><strong>표현을 통한 리소스 조작 (Manipulation of Resources Through Representations):</strong> 클라이언트는 리소스 자체를 직접 조작하는 것이 아니라, 리소스의 &#39;표현&#39;을 통해 상호작용합니다. 이 표현은 일반적으로 JSON 또는 XML 형식입니다. 예를 들어, 서버는 특정 사용자를 나타내는 JSON 객체를 클라이언트에 보내고, 클라이언트는 이 JSON 객체를 수정하여 서버에 다시 보내 사용자를 업데이트합니다.</li>
<li><strong>자기 서술적 메시지 (Self-descriptive Messages):</strong> 각 메시지(요청과 응답)는 수신자가 해당 메시지를 이해하는 데 충분한 정보를 담고 있어야 합니다. 예를 들어, HTTP 메서드(<code>GET</code>, <code>POST</code>)는 서버에게 무엇을 해야 할지 알려주고, HTTP 상태 코드(<code>200</code>, <code>404</code>)는 클라이언트에게 요청의 결과를 알려줍니다. 또한 <code>Content-Type</code> 헤더는 메시지 본문이 JSON인지 XML인지를 명시합니다.</li>
<li><strong>HATEOAS (Hypermedia as the Engine of Application State):</strong> 이는 REST의 가장 성숙한 단계로, 응답에 관련된 다른 작업이나 리소스로 연결되는 링크(하이퍼미디어)를 포함해야 한다는 원칙입니다. 예를 들어, 특정 주문에 대한 응답에는 해당 주문을 취소하거나 배송 상태를 조회할 수 있는 URL 링크가 포함될 수 있습니다. 이를 통해 클라이언트는 마치 사용자가 웹사이트를 탐색하듯이 동적으로 API를 탐색할 수 있게 되어 클라이언트와 서버 간의 결합도를 낮춥니다.</li>
</ol>
<h3 id="캐시-가능성-cacheability">캐시 가능성 (Cacheability)</h3>
<p>RESTful API의 응답은 스스로 캐시가 가능한지 여부를 명시해야 합니다. <code>Cache-Control</code>과 같은 HTTP 헤더를 통해 응답이 캐시될 수 있는지, 그리고 얼마나 오랫동안 유효한지를 클라이언트에게 알려줍니다.</p>
<p>캐시 가능성은 성능 향상에 지대한 영향을 미칩니다. 자주 접근되지만 내용이 자주 바뀌지 않는 데이터의 경우, 클라이언트나 중간 프록시 서버가 응답을 캐시에 저장해두고 동일한 요청이 다시 들어왔을 때 서버까지 가지 않고 캐시된 데이터를 반환할 수 있습니다. 이는 서버의 부하를 줄이고 사용자에게 더 빠른 응답 시간을 제공합니다.</p>
<h3 id="계층화된-시스템-layered-system">계층화된 시스템 (Layered System)</h3>
<p>REST 아키텍처는 여러 계층으로 구성될 수 있습니다. 클라이언트는 API 서버와 직접 통신하는지, 아니면 로드 밸런서, API 게이트웨이, 보안 프록시와 같은 중간 계층을 통해 통신하는지 알 수 없으며 알 필요도 없습니다. 클라이언트는 오직 최종 목적지의 URI와만 상호작용합니다.</p>
<p>이러한 구조는 시스템 아키텍처에 엄청난 유연성을 부여합니다. 클라이언트나 서버 코드에 영향을 주지 않으면서 보안, 로깅, 캐싱을 위한 새로운 계층을 시스템에 추가할 수 있습니다.</p>
<h3 id="주문형-코드-code-on-demand-선택-사항">주문형 코드 (Code on Demand) (선택 사항)</h3>
<p>이는 REST의 유일한 선택적 제약 조건입니다. 서버가 클라이언트의 기능을 일시적으로 확장하기 위해 JavaScript와 같은 실행 가능한 코드를 전송할 수 있음을 의미합니다. 이 원칙은 널리 사용되지는 않지만, 특정 시나리오에서 클라이언트의 복잡성을 줄이고 유연성을 높일 수 있습니다.</p>
<hr>
<h2 id="2장-api의-언어---http-메서드-심층-분석">2장: API의 언어 - HTTP 메서드 심층 분석</h2>
<p>RESTful API의 상호작용은 HTTP 메서드(동사)를 사용하여 리소스(명사)에 대한 CRUD(Create, Read, Update, Delete) 작업을 수행하는 것을 기본으로 합니다. 각 메서드는 고유한 의미와 특성을 가지며, 이를 올바르게 사용하는 것이 잘 설계된 API의 첫걸음입니다.</p>
<h3 id="주요-http-메서드">주요 HTTP 메서드</h3>
<h3 id="get">GET</h3>
<ul>
<li><strong>목적:</strong> 리소스 조회 (Read)</li>
<li><strong>설명:</strong> <code>GET</code> 메서드는 특정 리소스의 표현을 검색하는 데 사용됩니다. 예를 들어, <code>/users/123</code>으로 <code>GET</code> 요청을 보내면 ID가 123인 사용자의 정보를 가져옵니다.</li>
<li><strong>특성:</strong><ul>
<li><strong>안전(Safe):</strong> <code>GET</code> 요청은 서버의 상태를 변경하지 않아야 합니다. 즉, 리소스를 조회하는 것 외에 다른 부수 효과(side effect)를 일으키지 않습니다.</li>
<li><strong>멱등성(Idempotent):</strong> 동일한 <code>GET</code> 요청을 한 번 보내든 여러 번 보내든 결과는 항상 동일합니다.</li>
</ul>
</li>
<li><strong>데이터 전달:</strong> 데이터는 URL의 쿼리 문자열(query string)을 통해 전달됩니다 (예: <code>/users?status=active</code>). 이 때문에 URL이 서버 로그에 남거나 브라우저 히스토리에 저장될 수 있어 민감한 정보를 전달하는 데는 적합하지 않습니다.</li>
</ul>
<h3 id="post">POST</h3>
<ul>
<li><strong>목적:</strong> 새 리소스 생성 (Create)</li>
<li><strong>설명:</strong> <code>POST</code> 메서드는 서버에 데이터를 제출하여 새로운 리소스를 생성하는 데 사용됩니다. 예를 들어, <code>/users</code>로 사용자 정보를 담아 <code>POST</code> 요청을 보내면 새로운 사용자가 생성됩니다.</li>
<li><strong>특성:</strong><ul>
<li><strong>안전하지 않음(Not Safe):</strong> 서버에 새로운 리소스를 생성하므로 서버의 상태를 변경합니다.</li>
<li><strong>멱등성 없음(Not Idempotent):</strong> 동일한 <code>POST</code> 요청을 두 번 보내면 두 개의 다른 리소스가 생성될 수 있습니다.</li>
</ul>
</li>
<li><strong>데이터 전달:</strong> 데이터는 요청 본문(request body)에 담겨 전송됩니다. 따라서 크기가 크거나 복잡한 데이터, 또는 민감한 정보를 보내는 데 적합합니다.</li>
</ul>
<h3 id="put">PUT</h3>
<ul>
<li><strong>목적:</strong> 리소스 전체 교체 또는 생성 (Update/Replace)</li>
<li><strong>설명:</strong> <code>PUT</code> 메서드는 지정된 URI의 리소스를 요청 본문에 담긴 내용으로 완전히 교체합니다. 만약 해당 URI에 리소스가 존재하지 않으면 새로 생성할 수도 있습니다.</li>
<li><strong>특성:</strong><ul>
<li><strong>안전하지 않음(Not Safe):</strong> 리소스를 수정하거나 생성하므로 서버 상태를 변경합니다.</li>
<li><strong>멱등성(Idempotent):</strong> 동일한 <code>PUT</code> 요청을 여러 번 보내도 결과는 항상 동일합니다. 즉, 리소스는 항상 요청 본문의 내용과 같은 최종 상태를 갖게 됩니다.</li>
</ul>
</li>
<li><strong>데이터 전달:</strong> 요청 본문에 리소스의 <em>전체</em> 표현을 담아 보내야 합니다. 만약 특정 필드를 누락하면 해당 필드는 서버에서 삭제되거나 기본값으로 설정될 수 있습니다.</li>
</ul>
<h3 id="delete">DELETE</h3>
<ul>
<li><strong>목적:</strong> 리소스 삭제 (Delete)</li>
<li><strong>설명:</strong> <code>DELETE</code> 메서드는 지정된 URI의 리소스를 삭제합니다.</li>
<li><strong>특성:</strong><ul>
<li><strong>안전하지 않음(Not Safe):</strong> 리소스를 삭제하므로 서버 상태를 변경합니다.</li>
<li><strong>멱등성(Idempotent):</strong> 동일한 <code>DELETE</code> 요청을 여러 번 보내도 결과는 동일합니다. 첫 번째 요청에서 리소스가 삭제되고, 이후의 요청은 &#39;리소스 없음&#39;(예: 404 Not Found)을 반환하겠지만, 시스템의 최종 상태는 동일하게 유지됩니다.</li>
</ul>
</li>
</ul>
<h3 id="patch">PATCH</h3>
<ul>
<li><strong>목적:</strong> 리소스 부분 수정 (Partial Update)</li>
<li><strong>설명:</strong> <code>PATCH</code> 메서드는 리소스의 일부만 수정할 때 사용됩니다. <code>PUT</code>이 리소스 전체를 교체하는 반면, <code>PATCH</code>는 변경하려는 필드만 요청 본문에 담아 보냅니다.</li>
<li><strong>특성:</strong><ul>
<li><strong>안전하지 않음(Not Safe):</strong> 리소스를 수정하므로 서버 상태를 변경합니다.</li>
<li><strong>멱등성 없음(Not Idempotent, 일반적으로):</strong> <code>PATCH</code> 연산의 결과는 리소스의 현재 상태에 따라 달라질 수 있으므로 일반적으로 멱등하지 않습니다. 예를 들어, &#39;카운터를 1 증가시켜라&#39;는 <code>PATCH</code> 요청은 보낼 때마다 결과가 달라집니다.</li>
</ul>
</li>
<li><strong>데이터 전달:</strong> 변경할 필드만 요청 본문에 담아 보내므로, 작은 변경에 대해 <code>PUT</code>보다 네트워크 효율성이 높습니다.</li>
</ul>
<h3 id="기타-메서드">기타 메서드</h3>
<ul>
<li><strong>HEAD:</strong> <code>GET</code>과 동일하지만, 응답 본문(body) 없이 헤더(header)만 반환합니다. 리소스의 존재 여부나 메타데이터(예: <code>Content-Length</code>, <code>Last-Modified</code>)를 확인하는 데 유용합니다.</li>
<li><strong>OPTIONS:</strong> 대상 리소스에 대해 수행할 수 있는 통신 옵션(예: 허용되는 HTTP 메서드)을 설명하는 데 사용됩니다. 주로 CORS(Cross-Origin Resource Sharing)에서 preflight 요청에 사용됩니다.</li>
</ul>
<h3 id="심층-분석-put과-patch의-끝나지-않는-논쟁">심층 분석: PUT과 PATCH의 끝나지 않는 논쟁</h3>
<p><code>PUT</code>과 <code>PATCH</code>의 차이점은 API 설계의 미묘한 트레이드오프를 보여주는 좋은 예시입니다.</p>
<p><code>PUT</code>은 리소스의 상태를 &#39;대체&#39;하는 것이고, <code>PATCH</code>는 현재 상태를 &#39;수정&#39;하는 것입니다.</p>
<p>이를 문서 편집에 비유할 수 있습니다. <code>PUT</code> 요청은 문서의 완전히 새로운 버전을 업로드하여 기존 버전을 덮어쓰는 것과 같습니다. 반면, <code>PATCH</code> 요청은 &quot;5번 줄을 변경하고 3번 단락을 삭제하라&quot;와 같은 편집 지시 목록을 보내는 것과 유사합니다.</p>
<h3 id="실제-사용-사례-사용자-프로필-업데이트">실제 사용 사례: 사용자 프로필 업데이트</h3>
<ul>
<li><p><strong>기존 리소스:</strong></p>
<pre><code class="language-json">  {
    &quot;username&quot;: &quot;alex&quot;,
    &quot;email&quot;: &quot;alex@example.com&quot;,
    &quot;status&quot;: &quot;active&quot;
  }</code></pre>
</li>
<li><p><strong>목표:</strong> 이메일 주소만 변경하기</p>
</li>
<li><p><strong><code>PUT</code> 요청:</strong> 리소스의 <em>전체</em> 표현을 보내야 합니다. 만약 <code>status</code> 필드를 누락하면 서버 구현에 따라 사용자의 계정이 비활성화될 수도 있습니다. JSON</p>
<pre><code class="language-json">  PUT /users/123
  Content-Type: application/json

  {
    &quot;username&quot;: &quot;alex&quot;,
    &quot;email&quot;: &quot;alex.new@example.com&quot;,
    &quot;status&quot;: &quot;active&quot;
  }</code></pre>
</li>
<li><p><strong><code>PATCH</code> 요청:</strong> 변경할 필드만 보내므로 훨씬 효율적이고 의도치 않은 수정을 방지할 수 있습니다.</p>
<pre><code class="language-json">  PATCH /users/123
  Content-Type: application/json

  {
    &quot;email&quot;: &quot;alex.new@example.com&quot;
  }</code></pre>
</li>
</ul>
<table>
<thead>
<tr>
<th>기능</th>
<th>PUT</th>
<th>PATCH</th>
</tr>
</thead>
<tbody><tr>
<td><strong>업데이트 범위</strong></td>
<td>리소스 <strong>전체</strong>를 교체합니다.</td>
<td>리소스의 <strong>일부</strong>를 업데이트합니다.</td>
</tr>
<tr>
<td><strong>멱등성</strong></td>
<td><strong>보장됨.</strong> 동일한 요청은 항상 동일한 최종 상태를 만듭니다.</td>
<td><strong>보장되지 않음 (일반적).</strong> 결과가 리소스의 현재 상태에 따라 달라질 수 있습니다.</td>
</tr>
<tr>
<td><strong>요청 페이로드</strong></td>
<td>리소스의 <strong>완전한</strong> 표현을 보내야 합니다.</td>
<td>변경이 필요한 <strong>필드만</strong> 보냅니다.</td>
</tr>
<tr>
<td><strong>네트워크 효율성</strong></td>
<td>큰 리소스의 작은 변경 시 비효율적일 수 있습니다.</td>
<td>데이터 전송을 최소화하여 매우 효율적입니다.</td>
</tr>
<tr>
<td><strong>주요 사용 사례</strong></td>
<td>전체 레코드 동기화, 설정 파일 교체.</td>
<td>단일 필드 업데이트 (예: 사용자 이메일, 제품 가격).</td>
</tr>
</tbody></table>
<h3 id="신뢰성-있는-api의-초석-멱등성idempotency의-중요성">신뢰성 있는 API의 초석: 멱등성(Idempotency)의 중요성</h3>
<p><code>GET</code>, <code>PUT</code>, <code>DELETE</code> 메서드에 대해서는 멱등성이 반복적으로 언급되지만 <code>POST</code>나 <code>PATCH</code>에 대해서는 그렇지 않습니다. 이는 단순히 학술적인 구분이 아니라, 장애에 강한 클라이언트를 구축하는 데 있어 근본적인 차이를 만듭니다.</p>
<p>네트워크는 본질적으로 불안정합니다. 클라이언트는 요청을 보냈지만, 서버의 응답을 받기 전에 연결이 끊어질 수 있습니다. 이 경우 클라이언트는 요청이 성공적으로 처리되었는지 알 수 없습니다. &quot;요청을 재시도해야 할까?&quot;라는 딜레마에 빠지게 됩니다.</p>
<p>이때 연산의 멱등성이 중요해집니다. 만약 <code>PUT</code>이나 <code>DELETE</code>처럼 연산이 멱등하다면, 클라이언트는 의도치 않은 부수 효과(예: 중복 리소스 생성)에 대한 걱정 없이 동일한 요청을 안전하게 다시 보낼 수 있습니다. 시스템의 최종 상태는 요청이 첫 번째에 성공했든, 재시도 끝에 성공했든 동일할 것이기 때문입니다.</p>
<p>반면, <code>POST</code>와 같이 멱등하지 않은 연산을 재시도하면, 두 개의 중복된 리소스가 생성될 수 있습니다. 이는 신용카드 결제를 두 번 처리하거나, 동일한 게시물을 두 번 등록하는 것과 같은 심각한 문제를 야기할 수 있습니다. 따라서 멱등성을 이해하는 것은 클라이언트의 오류 처리 및 재시도 로직을 어떻게 구현해야 하는지를 직접적으로 결정하는 중요한 요소입니다. 멱등한 메서드를 올바르게 사용하는 API는 본질적으로 더 견고하며 개발자가 더 안정적으로 사용하기 쉽습니다.</p>
<hr>
<h2 id="3장-서버의-응답-해석하기---필수-http-상태-코드">3장: 서버의 응답 해석하기 - 필수 HTTP 상태 코드</h2>
<p>클라이언트가 서버에 요청을 보내면, 서버는 요청의 처리 결과를 나타내는 HTTP 상태 코드를 포함한 응답을 반환합니다. 이 상태 코드는 API 통신의 핵심적인 부분으로, 클라이언트가 다음 행동을 결정하는 데 중요한 단서를 제공합니다. 상태 코드는 다섯 가지 클래스로 분류되어 응답의 성격을 대략적으로 알려줍니다.</p>
<ul>
<li><strong>1xx (Informational):</strong> 요청을 받았으며, 프로세스를 계속 진행합니다.</li>
<li><strong>2xx (Successful):</strong> 요청을 성공적으로 수신, 이해, 수락했습니다.</li>
<li><strong>3xx (Redirection):</strong> 요청을 완료하기 위해 추가 조치가 필요합니다.</li>
<li><strong>4xx (Client Error):</strong> 클라이언트의 요청에 오류가 있습니다 (예: 잘못된 문법).</li>
<li><strong>5xx (Server Error):</strong> 서버가 유효한 요청을 처리하는 데 실패했습니다.</li>
</ul>
<h3 id="api-개발자를-위한-핵심-http-상태-코드">API 개발자를 위한 핵심 HTTP 상태 코드</h3>
<p>모든 상태 코드를 알 필요는 없지만, API 개발자라면 반드시 알아야 할 핵심적인 코드들이 있습니다. 다음 표는 클라이언트 측 로직에서 자주 처리해야 하는 주요 상태 코드와 그 사용 시나리오를 요약한 것입니다.</p>
<table>
<thead>
<tr>
<th>코드</th>
<th>의미</th>
<th>일반적인 API 시나리오</th>
</tr>
</thead>
<tbody><tr>
<td><strong>200 OK</strong></td>
<td>성공</td>
<td><code>GET</code> 요청에 대한 표준적인 성공 응답. 응답 본문에 요청한 데이터가 포함됩니다.</td>
</tr>
<tr>
<td><strong>201 Created</strong></td>
<td>리소스 생성됨</td>
<td><code>POST</code> 요청이 성공적으로 완료되어 새로운 리소스가 생성되었을 때 반환됩니다. 응답 헤더에 새로 생성된 리소스의 URI를 가리키는 <code>Location</code> 헤더가 포함되는 경우가 많습니다.</td>
</tr>
<tr>
<td><strong>204 No Content</strong></td>
<td>성공, 내용 없음</td>
<td><code>DELETE</code> 요청이 성공적으로 처리되었거나, 데이터를 반환할 필요가 없는 <code>PUT</code>/<code>PATCH</code> 요청에 대한 성공 응답입니다. 응답 본문은 비어 있습니다.</td>
</tr>
<tr>
<td><strong>400 Bad Request</strong></td>
<td>잘못된 요청</td>
<td>요청의 문법이 잘못되었거나 유효하지 않은 데이터를 포함할 때 반환됩니다 (예: JSON 형식 오류, 필수 필드 누락).</td>
</tr>
<tr>
<td><strong>401 Unauthorized</strong></td>
<td>인증 필요</td>
<td>사용자가 로그인하지 않았거나 유효하지 않은 API 키/토큰을 제공하여 인증에 실패했을 때 반환됩니다.</td>
</tr>
<tr>
<td><strong>403 Forbidden</strong></td>
<td>접근 금지</td>
<td>사용자는 성공적으로 인증되었지만(로그인 상태), 요청한 리소스에 접근할 권한이 없을 때 반환됩니다.</td>
</tr>
<tr>
<td><strong>404 Not Found</strong></td>
<td>리소스 없음</td>
<td>요청한 URI에 해당하는 리소스가 존재하지 않을 때 반환됩니다 (예: <code>/users/9999</code>).</td>
</tr>
<tr>
<td><strong>500 Internal Server Error</strong></td>
<td>서버 내부 오류</td>
<td>서버 측에서 예기치 않은 오류가 발생했을 때 반환되는 일반적인 오류 코드입니다 (예: 데이터베이스 연결 실패, 처리되지 않은 예외 발생).</td>
</tr>
</tbody></table>
<h3 id="심층-분석-문지기의-수수께끼---401-unauthorized-vs-403-forbidden">심층 분석: 문지기의 수수께끼 - 401 Unauthorized vs. 403 Forbidden</h3>
<p><code>401</code>과 <code>403</code>의 차이는 API 보안과 사용자 경험 설계에 있어 매우 중요하지만, 종종 혼동되는 개념입니다.</p>
<ul>
<li><p><strong>인증(Authentication) 오류: 401 Unauthorized</strong></p>
<ul>
<li><p><strong>질문:</strong> &quot;당신은 누구십니까?&quot;</p>
</li>
<li><p><strong>상황:</strong> 서버가 요청을 보낸 클라이언트의 신원을 확인하지 못했거나, 제공된 신원 정보(API 키, 토큰 등)가 유효하지 않은 경우입니다. 문제는 &#39;유효한 자격 증명의 부재&#39;입니다.</p>
</li>
<li><p><strong>해결:</strong> 클라이언트는 로그인을 하거나 유효한 토큰을 발급받아 다시 요청함으로써 이 문제를 해결할 수 있습니다. 좋은 API는</p>
<p>  <code>401</code> 응답 시 클라이언트가 어떻게 인증해야 하는지에 대한 정보를 담은 <code>WWW-Authenticate</code> 헤더를 함께 보내야 합니다.</p>
</li>
</ul>
</li>
<li><p><strong>인가(Authorization) 오류: 403 Forbidden</strong></p>
<ul>
<li><strong>질문:</strong> &quot;당신은 들어올 수 없습니다.&quot;</li>
<li><strong>상황:</strong> 서버는 클라이언트의 신원을 성공적으로 확인했습니다(즉, 인증은 통과했습니다). 하지만 해당 클라이언트가 가진 권한으로는 요청한 작업이나 리소스에 접근할 수 없는 경우입니다. 문제는 &#39;권한 부족&#39;입니다.</li>
<li><strong>해결:</strong> 단순히 다시 인증하는 것으로는 이 문제가 해결되지 않습니다. 사용자는 관리자에게 권한을 요청하는 등의 별도 조치가 필요합니다.</li>
</ul>
</li>
</ul>
<h3 id="실제-시나리오-hr-포털-api">실제 시나리오: HR 포털 API</h3>
<ul>
<li><strong>사례 1 (401 Unauthorized):</strong> 한 직원이 로그인하지 않은 상태에서 자신의 급여 정보를 보기 위해 <code>https://hr.portal/api/salary/123</code>에 접근을 시도합니다. API 서버는 이 요청이 누구로부터 온 것인지 알 수 없습니다.<ul>
<li><strong>응답: <code>401 Unauthorized</code></strong></li>
</ul>
</li>
<li><strong>사례 2 (403 Forbidden):</strong> 직원 &#39;Alice&#39;(ID: 123)가 성공적으로 로그인했습니다. 그녀는 자신의 급여 정보(<code>https://hr.portal/api/salary/123</code>)에 접근합니다.<ul>
<li><strong>응답: <code>200 OK</code></strong></li>
<li>이제 Alice는 호기심에 동료 &#39;Bob&#39;(ID: 456)의 급여 정보(<code>https://hr.portal/api/salary/456</code>)에 접근을 시도합니다. API 서버는 요청자가 &#39;Alice&#39;임을 알고 있지만, 일반 직원 역할(role)을 가진 Alice에게는 다른 직원의 급여 정보를 볼 권한이 없습니다.</li>
<li><strong>응답: <code>403 Forbidden</code></strong></li>
</ul>
</li>
</ul>
<h3 id="api-계약의-일부로서의-상태-코드">API 계약의 일부로서의 상태 코드</h3>
<p>상태 코드의 선택은 단순한 오류 신호를 넘어, 때로는 의도적인 보안 결정이 될 수 있습니다. 특히 <code>403 Forbidden</code>과 <code>404 Not Found</code> 사이의 선택이 그렇습니다.</p>
<p>권한이 없는 사용자가 <code>/admin/dashboard</code>와 같이 민감한 리소스에 접근을 시도했다고 가정해 봅시다. 이때 서버가 <code>403 Forbidden</code>을 반환하면, 이는 두 가지 정보를 암묵적으로 확인해 주는 셈입니다. 첫째, 사용자는 인증되었다. 둘째, <code>/admin/dashboard</code>라는 리소스가 <em>실제로 존재한다</em>.</p>
<p>이러한 정보의 존재 확인 자체만으로도 공격자에게는 유용한 단서가 될 수 있습니다. 공격자는 이 반응을 이용해 시스템에 존재하는 유효한 사용자 ID나 민감한 엔드포인트를 추측하고 열거하는 공격을 시도할 수 있습니다.</p>
<p>이러한 정보 유출을 방지하기 위해, 서버는 <code>403</code> 대신 <code>404 Not Found</code>를 반환하도록 설계될 수 있습니다. 이렇게 하면 권한 없는 사용자의 입장에서는 해당 리소스가 단순히 존재하지 않는 것처럼 보입니다. 즉, &quot;권한 없는 클라이언트로부터 리소스의 존재를 숨기는 것&quot;입니다. 이는 API 보안이 얼마나 미묘한지를 보여줍니다. 상태 코드는 단순한 성공/실패 신호가 아니라, 효과적으로 소통하면서도 보안 위험을 최소화하기 위해 신중하게 사용되어야 하는 API 계약의 중요한 일부입니다.</p>
<hr>
<h2 id="4장-이론을-현실로---api-상호작용-예제">4장: 이론을 현실로 - API 상호작용 예제</h2>
<p>이론적인 개념을 이해했다면, 이제 실제 코드를 통해 어떻게 API와 상호작용하는지 살펴볼 차례입니다. 이 장에서는 프론트엔드 개발자들이 주로 사용하는 JavaScript의 <code>fetch()</code> API와, 백엔드 및 데브옵스 엔지니어들이 애용하는 <code>cURL</code> 커맨드 라인 도구를 사용하여 API 요청을 보내는 구체적인 예제를 제공합니다.</p>
<h3 id="프론트엔드-개발자를-위한-예제-javascript-fetch-api">프론트엔드 개발자를 위한 예제: JavaScript <code>fetch()</code> API</h3>
<p><code>fetch()</code>는 최신 브라우저에 내장된 강력하고 유연한 네트워크 요청 API입니다. Promise 기반으로 작동하여 비동기 통신을 깔끔하게 처리할 수 있습니다.</p>
<p>기본 문법은 <code>fetch(url, options)</code> 형태이며, <code>async/await</code> 또는 <code>.then()</code> 체이닝을 사용하여 응답을 처리합니다.</p>
<p><strong>중요: <code>fetch()</code>의 오류 처리</strong><code>fetch()</code>는 네트워크 오류(예: DNS 조회 실패, 인터넷 연결 없음)가 발생한 경우에만 Promise를 reject합니다. 서버가 <code>404 Not Found</code>나 <code>500 Internal Server Error</code> 같은 HTTP 오류 상태 코드를 반환하더라도, <code>fetch()</code>는 이를 성공적인 요청으로 간주하고 Promise를 resolve합니다. 따라서 API 레벨의 오류를 처리하려면 반드시 <code>response.ok</code> 속성(상태 코드가 200-299 범위에 있으면 <code>true</code>)을 확인해야 합니다.</p>
<h3 id="get-리소스-조회">GET: 리소스 조회</h3>
<p><code>GET</code> 요청은 데이터를 조회할 때 사용되며, 필요한 파라미터는 URL 쿼리 문자열로 전달합니다. <code>URLSearchParams</code> 객체를 사용하면 이를 쉽게 구성할 수 있습니다.</p>
<pre><code class="language-jsx">async function getUser(userId) {
  const url = `https://api.example.com/users/${userId}`;

  try {
    const response = await fetch(url);

    if (!response.ok) {
      // 4xx, 5xx 같은 오류 응답을 처리
      throw new Error(`HTTP error! status: ${response.status}`);
    }

    const data = await response.json();
    console.log(data);
  } catch (error) {
    console.error(&#39;Fetch error:&#39;, error);
  }
}

getUser(1);</code></pre>
<h3 id="post-새-리소스-생성">POST: 새 리소스 생성</h3>
<p><code>POST</code> 요청 시에는 <code>options</code> 객체에 <code>method</code>, <code>headers</code>, <code>body</code>를 명시해야 합니다. 특히 <code>Content-Type</code> 헤더를 <code>application/json</code>으로 설정하고, <code>body</code>에는 <code>JSON.stringify()</code>를 사용하여 JavaScript 객체를 JSON 문자열로 변환하여 전달해야 합니다.</p>
<pre><code class="language-jsx">async function createUser(userData) {
  const url = &#39;https://api.example.com/users&#39;;

  try {
    const response = await fetch(url, {
      method: &#39;POST&#39;,
      headers: {
        &#39;Content-Type&#39;: &#39;application/json&#39;,
        &#39;Authorization&#39;: &#39;Bearer YOUR_API_TOKEN&#39; // 인증 토큰이 필요한 경우
      },
      body: JSON.stringify(userData)
    });

    if (!response.ok) {
      throw new Error(`HTTP error! status: ${response.status}`);
    }

    const createdUser = await response.json();
    console.log(&#39;User created:&#39;, createdUser);
  } catch (error) {
    console.error(&#39;Fetch error:&#39;, error);
  }
}

const newUser = { name: &#39;Jane Doe&#39;, email: &#39;jane.doe@example.com&#39; };
createUser(newUser);</code></pre>
<h3 id="put--patch-리소스-수정">PUT &amp; PATCH: 리소스 수정</h3>
<p><code>PUT</code>(전체 교체)과 <code>PATCH</code>(부분 수정) 요청은 <code>POST</code>와 유사하게 <code>method</code>, <code>headers</code>, <code>body</code>를 설정합니다. 요청 본문의 내용이 전체 리소스인지, 변경된 부분만인지에 따라 달라집니다.</p>
<pre><code class="language-jsx">// PUT 예제: 사용자 전체 정보 교체
async function updateUserWithPut(userId, fullUserData) {
  const url = `https://api.example.com/users/${userId}`;

  const response = await fetch(url, {
    method: &#39;PUT&#39;,
    headers: { &#39;Content-Type&#39;: &#39;application/json&#39; },
    body: JSON.stringify(fullUserData)
  });

  //... 오류 처리 및 응답 처리 로직...
}

// PATCH 예제: 사용자 이메일만 변경
async function updateUserWithPatch(userId, partialUserData) {
  const url = `https://api.example.com/users/${userId}`;

  const response = await fetch(url, {
    method: &#39;PATCH&#39;,
    headers: { &#39;Content-Type&#39;: &#39;application/json&#39; },
    body: JSON.stringify(partialUserData)
  });

  //... 오류 처리 및 응답 처리 로직...
}</code></pre>
<h3 id="delete-리소스-삭제">DELETE: 리소스 삭제</h3>
<p><code>DELETE</code> 요청은 보통 요청 본문이 필요 없으며, <code>method</code>만 지정하면 됩니다.</p>
<pre><code class="language-jsx">async function deleteUser(userId) {
  const url = `https://api.example.com/users/${userId}`;

  try {
    const response = await fetch(url, {
      method: &#39;DELETE&#39;,
      headers: {
        &#39;Authorization&#39;: &#39;Bearer YOUR_API_TOKEN&#39;
      }
    });

    if (response.status === 204) {
      console.log(&#39;User deleted successfully.&#39;);
    } else if (!response.ok) {
      throw new Error(`HTTP error! status: ${response.status}`);
    }
  } catch (error) {
    console.error(&#39;Fetch error:&#39;, error);
  }
}

deleteUser(1);</code></pre>
<h3 id="백엔드데브옵스-엔지니어를-위한-예제-curl">백엔드/데브옵스 엔지니어를 위한 예제: <code>cURL</code></h3>
<p><code>cURL</code>은 API를 테스트하고 상호작용하는 데 사용되는 강력한 커맨드 라인 도구입니다.</p>
<ul>
<li><p><strong>GET:</strong>Bash</p>
<p>  <code>curl -X GET &quot;https://api.example.com/users/1&quot;</code></p>
</li>
<li><p><strong>POST:</strong> <code>X</code> 플래그로 메서드를, <code>H</code>로 헤더를, <code>d</code>로 데이터를 지정합니다. Bash</p>
<p>  `curl -X POST \</p>
<pre><code>-H &quot;Content-Type: application/json&quot; \
-d &#39;{&quot;name&quot;: &quot;John Doe&quot;, &quot;email&quot;: &quot;john.doe@example.com&quot;}&#39; \
&quot;https://api.example.com/users&quot;`</code></pre></li>
<li><p><strong>PUT:</strong>Bash</p>
<p>  `curl -X PUT \</p>
<pre><code>-H &quot;Content-Type: application/json&quot; \
-d &#39;{&quot;name&quot;: &quot;Johnathan Doe&quot;, &quot;email&quot;: &quot;john.doe.new@example.com&quot;}&#39; \
&quot;https://api.example.com/users/1&quot;`</code></pre></li>
<li><p><strong>PATCH:</strong>Bash</p>
<p>  `curl -X PATCH \</p>
<pre><code>-H &quot;Content-Type: application/json&quot; \
-d &#39;{&quot;email&quot;: &quot;john.doe.updated@example.com&quot;}&#39; \
&quot;https://api.example.com/users/1&quot;`</code></pre></li>
<li><p><strong>DELETE:</strong>  Bash</p>
<p>  <code>curl -X DELETE &quot;https://api.example.com/users/1&quot;</code></p>
</li>
</ul>
<hr>
<h2 id="5장-성공을-위한-아키텍처---고급-api-설계-및-보안">5장: 성공을 위한 아키텍처 - 고급 API 설계 및 보안</h2>
<p>기능적으로 동작하는 API를 만드는 것을 넘어, 장기적으로 유지보수 가능하고, 안전하며, 사용하기 좋은 API를 설계하는 것은 또 다른 차원의 과제입니다. 이 장에서는 데이터 형식 선택, 버전 관리 전략, 그리고 필수적인 보안 모범 사례와 같은 고급 주제를 다룹니다.</p>
<h3 id="데이터-교환-형식-json이-지배하는-이유">데이터 교환 형식: JSON이 지배하는 이유</h3>
<p>API는 다양한 데이터 형식(XML, Protocol Buffers 등)을 사용할 수 있지만, 현대 웹 API의 세계에서는 JSON(JavaScript Object Notation)이 사실상의 표준으로 자리 잡았습니다.</p>
<ul>
<li><strong>성능:</strong> JSON은 XML에 비해 훨씬 덜 장황합니다. 시작 태그와 종료 태그가 없어 페이로드 크기가 작고, 이는 네트워크 대역폭을 절약하며 파싱 속도를 높여줍니다. 특히 모바일 환경이나 트래픽이 많은 애플리케이션에서 이러한 성능 차이는 사용자 경험에 직접적인 영향을 미칩니다.</li>
<li><strong>개발자 경험:</strong> JSON의 가장 큰 장점은 JavaScript 및 대부분의 최신 프로그래밍 언어에서 네이티브 객체로 쉽게 변환된다는 점입니다. 개발자는 복잡한 파싱 라이브러리 없이도 데이터를 직관적으로 다룰 수 있습니다. 반면, XML을 파싱하는 것은 종종 더 많은 코드와 노력을 필요로 합니다.</li>
</ul>
<h3 id="진화하는-api-관리-버전-관리-전략">진화하는 API 관리: 버전 관리 전략</h3>
<p>API는 비즈니스 요구사항이 변화함에 따라 함께 진화합니다. 이때 기존 클라이언트 애플리케이션의 작동을 중단시키지 않으면서 호환성이 깨지는 변경(breaking changes)을 도입하려면 API 버전 관리가 필수적입니다.</p>
<p>올바른 버전 관리 전략을 선택하는 것은 장기적인 유지보수성, 캐싱 효율성, 그리고 개발자 경험에 큰 영향을 미칩니다. 다음은 주요 버전 관리 전략들의 장단점을 비교한 표입니다.</p>
<table>
<thead>
<tr>
<th>전략</th>
<th>예시</th>
<th>장점</th>
<th>단점</th>
</tr>
</thead>
<tbody><tr>
<td><strong>URI 경로 (URI Path)</strong></td>
<td><code>api.example.com/v1/products</code></td>
<td>버전이 명시적이고 직관적임. 문서화 및 캐싱이 용이함. 가장 널리 사용됨.</td>
<td>URI가 지저분해짐. 버전 변경 시 코드베이스가 크게 변경될 수 있음.</td>
</tr>
<tr>
<td><strong>쿼리 파라미터 (Query Parameter)</strong></td>
<td><code>api.example.com/products?version=1</code></td>
<td>구현이 간단하고, 최신 버전을 기본값으로 설정하기 쉬움.</td>
<td>라우팅 및 캐싱 구현이 복잡해질 수 있음. URI 경로 방식보다 덜 명시적임.</td>
</tr>
<tr>
<td><strong>커스텀 헤더 (Custom Header)</strong></td>
<td><code>Accepts-version: 1.0</code></td>
<td>URI를 깔끔하게 유지할 수 있음. 더 유연한 버전 제어가 가능함.</td>
<td>브라우저에서 직접 테스트하기 어려움. 클라이언트 측에서 추가 설정이 필요함.</td>
</tr>
</tbody></table>
<h3 id="api-요새화-보안-모범-사례-입문">API 요새화: 보안 모범 사례 입문</h3>
<p>API 보안은 선택이 아닌 필수입니다. 안전하지 않은 API는 데이터 유출, 서비스 거부 공격 등 심각한 위협에 노출될 수 있습니다. OWASP API Security Top 10과 같은 자료는 API 보안의 지침서 역할을 합니다.</p>
<h3 id="인증authentication과-인가authorization">인증(Authentication)과 인가(Authorization)</h3>
<ul>
<li><strong>인증 (Authentication):</strong> &quot;당신은 누구인가?&quot;를 확인하는 과정입니다. 사용자의 신원을 증명하는 단계로, 로그인 과정이 대표적입니다.</li>
<li><strong>인가 (Authorization):</strong> &quot;당신은 무엇을 할 수 있는가?&quot;를 결정하는 과정입니다. 인증된 사용자가 특정 리소스나 기능에 접근할 수 있는 권한이 있는지 확인하는 단계입니다.</li>
</ul>
<p>다음은 일반적인 API 인증 방식들입니다.</p>
<table>
<thead>
<tr>
<th>방식</th>
<th>작동 원리</th>
<th>최적 사용 사례</th>
<th>주요 트레이드오프</th>
</tr>
</thead>
<tbody><tr>
<td><strong>API 키 (API Key)</strong></td>
<td>헤더에 정적인 비밀 토큰을 전송.</td>
<td>간단한 서버 간 통신, 내부용 앱.</td>
<td>단순함 vs. 낮은 보안 수준.</td>
</tr>
<tr>
<td><strong>OAuth 2.0</strong></td>
<td>위임된 인가를 위한 프로토콜.</td>
<td>제3자 애플리케이션의 사용자 데이터 접근.</td>
<td>강력한 보안 vs. 높은 복잡도.</td>
</tr>
<tr>
<td><strong>JWT (JSON Web Token)</strong></td>
<td>클레임을 포함하는 자체 서명된 토큰.</td>
<td>상태 비저장 마이크로서비스, SPA.</td>
<td>성능 및 확장성 vs. 내장된 폐기 메커니즘 부재.</td>
</tr>
</tbody></table>
<h3 id="전송-계층-보안-https">전송 계층 보안 (HTTPS)</h3>
<p>모든 API 통신은 반드시 HTTPS(TLS)를 통해 암호화되어야 합니다. 이는 중간자 공격(Man-in-the-Middle attack)을 방지하고 전송 중인 데이터를 보호하는 가장 기본적인 조치입니다.</p>
<h3 id="요청-제한-rate-limiting">요청 제한 (Rate Limiting)</h3>
<p>요청 제한은 특정 클라이언트가 정해진 시간 동안 보낼 수 있는 요청의 수를 제한하는 기술입니다. 이는 서비스 거부(DoS) 공격을 완화하고, 특정 사용자가 시스템 리소스를 독점하는 것을 방지하여 서비스의 안정성을 유지하는 데 매우 중요합니다.</p>
<h3 id="입력값-검증-input-validation">입력값 검증 (Input Validation)</h3>
<p>&quot;절대 클라이언트를 신뢰하지 말라&quot;는 보안의 기본 원칙입니다. 클라이언트로부터 들어오는 모든 데이터(URL 파라미터, 요청 본문, 헤더 등)는 SQL 인젝션, XSS(Cross-Site Scripting) 등의 공격을 방지하기 위해 서버 측에서 철저하게 검증하고 정제(sanitize)해야 합니다.</p>
<hr>
<h2 id="결론">결론</h2>
<p>지금까지 우리는 API 개발의 광범위한 지형을 탐험했습니다. REST 아키텍처 스타일의 근본적인 &#39;왜&#39;부터 시작하여, HTTP 메서드와 상태 코드라는 API의 &#39;언어&#39;를 배우고, JavaScript <code>fetch</code>와 <code>cURL</code>을 통해 이론을 실제 &#39;코드&#39;로 옮기는 방법을 살펴보았습니다. 마지막으로 버전 관리, 데이터 형식 선택, 그리고 보안과 같은 &#39;성공적인 설계&#39;의 원칙들을 다루었습니다.</p>
<p>이 여정을 통해 명확해진 것은, 잘 설계된 API는 단순히 기능을 제공하는 것을 넘어선다는 사실입니다. 훌륭한 API는 신뢰할 수 있고, 안전하며, 무엇보다 개발자에게 훌륭한 사용 경험을 제공합니다. 이는 마치 잘 쓰인 설명서와 같아서, 개발자가 API의 기능을 쉽고 예측 가능하게 사용하여 더 강력하고 혁신적인 애플리케이션을 만들 수 있도록 돕습니다.</p>
<p>이제 이 지식을 실제 프로젝트에 적용해 볼 차례입니다. 작은 개인 프로젝트를 위해 간단한 API를 직접 만들어보거나, Postman이나 cURL 같은 도구를 사용하여 GitHub API와 같은 공개 API를 탐색해 보세요. 우리가 매일 사용하는 웹사이트와 애플리케이션의 네트워크 요청을 개발자 도구로 살펴보는 것만으로도 이 가이드에서 다룬 개념들이 실제로 어떻게 작동하는지 생생하게 경험할 수 있을 것입니다. 결국, 가장 효과적인 학습은 직접 만들어보고 경험하는 것에서 비롯됩니다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[🔀 Git 브랜치 전략 & 명령어 매뉴얼 총정리]]></title>
            <link>https://velog.io/@cha-suyeon/Git-%EB%B8%8C%EB%9E%9C%EC%B9%98-%EC%A0%84%EB%9E%B5-%EB%AA%85%EB%A0%B9%EC%96%B4-%EB%A7%A4%EB%89%B4%EC%96%BC-%EC%B4%9D%EC%A0%95%EB%A6%AC</link>
            <guid>https://velog.io/@cha-suyeon/Git-%EB%B8%8C%EB%9E%9C%EC%B9%98-%EC%A0%84%EB%9E%B5-%EB%AA%85%EB%A0%B9%EC%96%B4-%EB%A7%A4%EB%89%B4%EC%96%BC-%EC%B4%9D%EC%A0%95%EB%A6%AC</guid>
            <pubDate>Tue, 26 Aug 2025 01:06:48 GMT</pubDate>
            <description><![CDATA[<h1 id="🔀-git-브랜치-전략--명령어-매뉴얼">🔀 Git 브랜치 전략 &amp; 명령어 매뉴얼</h1>
<p>실무에서 바로 쓰는 흐름대로, 개념→명령어→주의점→복구까지 한 번에 정리했습니다.</p>
<hr>
<h2 id="목차">목차</h2>
<ol>
<li>시작 준비 – 사용자 정보, 편집기, 줄바꿈</li>
<li>저장소와 원격 – clone, remote, fetch vs pull, tracking</li>
<li>브랜치 만들기/이동 – 안전한 파생 규칙</li>
<li>하루 업무 흐름(권장 루틴) – 번호 순서 전체 명령어</li>
<li>Merge vs Rebase – 개념/그림/언제/주의점/명령어</li>
<li>내 브랜치를 최신화하는 3가지 방법(merge/rebase/새로파생)</li>
<li>충돌 처리 가이드 – 병합/리베이스/중단·재시도</li>
<li>Stash(임시저장) – 실무에서 쓰는 모든 패턴</li>
<li>커밋 관리 – Conventional Commits, amend, squash/fixup</li>
<li>되돌리기 &amp; 복구 – revert/reset/restore/reflog/force-with-lease</li>
<li>.gitignore 제대로 – 이미 추적된 파일 제외하기</li>
<li>PR(코드 리뷰) 흐름 – push, 리뷰, 머지 전략</li>
<li>자주 겪는 오류/상황별 처방전 – 당신의 콘솔 메시지 해결집</li>
<li>치트시트(작업→명령어 매핑 표)</li>
</ol>
<hr>
<h2 id="1-시작-준비--사용자-정보-편집기-줄바꿈">1) 시작 준비 – 사용자 정보, 편집기, 줄바꿈</h2>
<ul>
<li>사용자/메일(커밋 서명자)</li>
</ul>
<pre><code class="language-bash">git config --global user.name &quot;홍길동&quot;
git config --global user.email &quot;me@example.com&quot;</code></pre>
<ul>
<li>기본 편집기(옵션)</li>
</ul>
<pre><code class="language-bash">git config --global core.editor &quot;code --wait&quot;      # VS Code</code></pre>
<ul>
<li>줄바꿈(팀 규칙에 맞게)</li>
</ul>
<pre><code class="language-bash">git config --global core.autocrlf input            # macOS/Linux 권장
git config --global core.autocrlf true             # Windows 혼자 개발 시</code></pre>
<ul>
<li>상태/최근 기록/원격 확인</li>
</ul>
<pre><code class="language-bash">git status
git log --oneline --graph --decorate -20
git remote -v</code></pre>
<hr>
<h2 id="2-저장소와-원격--clone-remote-fetch-vs-pull-tracking">2) 저장소와 원격 – clone, remote, fetch vs pull, tracking</h2>
<ul>
<li>클론</li>
</ul>
<pre><code class="language-bash">git clone &lt;url&gt;
cd &lt;repo&gt;</code></pre>
<ul>
<li>원격/브랜치 추적(Tracking)</li>
</ul>
<pre><code class="language-bash">git branch -vv                    # 현재 브랜치가 어떤 원격 브랜치를 추적하는지
git push -u origin feature/x      # 최초 푸시 시 -u로 추적 설정</code></pre>
<ul>
<li><p>fetch vs pull</p>
<ul>
<li>fetch: “원격 정보만 갱신(병합/리베이스 X)”</li>
<li>pull:   “fetch + 현재 브랜치에 반영(merge 또는 rebase)”</li>
</ul>
</li>
</ul>
<pre><code class="language-bash">git fetch origin
git pull --ff-only                 # 실수로 merge commit 생기는 것 방지
# 또는
git pull --rebase --autostash      # 개인 브랜치에서 깔끔히 최신화할 때</code></pre>
<hr>
<h2 id="3-브랜치-만들기이동--안전한-파생-규칙">3) 브랜치 만들기/이동 – 안전한 파생 규칙</h2>
<ul>
<li>최신 development에서 바로 파생(권장)</li>
</ul>
<pre><code class="language-bash">git fetch origin
git switch development
git pull --ff-only origin development
git switch -c feature/login       # development 최신 커밋에서 새 브랜치</code></pre>
<ul>
<li>기존 브랜치로 이동(구문 2종)</li>
</ul>
<pre><code class="language-bash">git switch feature/login
# (구식) git checkout feature/login</code></pre>
<ul>
<li><p>브랜치 네이밍(권장)</p>
<ul>
<li>feature/기능-설명</li>
<li>bugfix/이슈-설명</li>
<li>hotfix/긴급-설명</li>
</ul>
</li>
</ul>
<hr>
<h2 id="4-하루-업무-흐름권장-루틴">4) 하루 업무 흐름(권장 루틴)</h2>
<ol>
<li>원격 최신 확인</li>
</ol>
<pre><code class="language-bash">git fetch origin
git switch development
git pull --ff-only origin development</code></pre>
<ol start="2">
<li>기능 브랜치 생성/이동</li>
</ol>
<pre><code class="language-bash">git switch -c feature/awesome
# (이미 만든 경우) git switch feature/awesome</code></pre>
<ol start="3">
<li>작업 → 상태 확인 → 스테이징 → 커밋</li>
</ol>
<pre><code class="language-bash">git status
git add -A
git commit -m &quot;feat(awesome): 초기 컴포넌트 추가&quot;</code></pre>
<ol start="4">
<li>중간중간 최신화 반영(아래 6장 참고: merge 또는 rebase)</li>
<li>원격에 푸시(+추적 설정)</li>
</ol>
<pre><code class="language-bash">git push -u origin feature/awesome</code></pre>
<ol start="6">
<li>PR 생성 → 리뷰 반영 → 스쿼시/머지 전략 선택(12장 참고)</li>
</ol>
<hr>
<h2 id="5-merge-vs-rebase--핵심-비교">5) Merge vs Rebase – 핵심 비교</h2>
<p>개념 그림</p>
<p>development: A -- B -- C -- D</p>
<p>feature/app:             E -- F</p>
<h3 id="merge머지">Merge(머지)</h3>
<pre><code class="language-bash">git switch feature/app
git merge development</code></pre>
<p>결과(merge commit M 생성)
A -- B -- C -- D ----------- M
\    /
E--F</p>
<ul>
<li>장점: 안전, 공동 작업에 적합, 원본 히스토리 보존</li>
<li>단점: 머지 커밋이 누적되면 그래프가 복잡</li>
</ul>
<p>옵션:</p>
<pre><code class="language-bash">git merge --no-ff --no-edit development   # merge commit을 항상 남김</code></pre>
<h3 id="rebase리베이스">Rebase(리베이스)</h3>
<pre><code class="language-bash">git switch feature/app
git rebase development</code></pre>
<p>결과(직선형)
A -- B -- C -- D -- E&#39; -- F&#39;</p>
<ul>
<li>장점: 히스토리 깔끔(읽기 쉬움)</li>
<li>단점: 공유 브랜치에서 금지(히스토리 재작성); 충돌을 커밋마다 해결</li>
</ul>
<p>리베이스 중 유용한 명령:</p>
<pre><code class="language-bash">git rebase --continue
git rebase --skip
git rebase --abort</code></pre>
<h3 id="언제-무엇을">언제 무엇을?</h3>
<ul>
<li>팀 공용 브랜치(main/development): Merge 권장</li>
<li>개인 작업 브랜치(PR 전 정리): Rebase 자주 사용</li>
<li>원격에 이미 공유된 브랜치를 rebase → 위험. 꼭 필요하면 협의 후 <code>--force-with-lease</code></li>
</ul>
<h3 id="한눈-비교">한눈 비교</h3>
<table>
<thead>
<tr>
<th>항목</th>
<th>merge</th>
<th>rebase</th>
</tr>
</thead>
<tbody><tr>
<td>히스토리</td>
<td>분기+머지 커밋 남김</td>
<td>직선화(깔끔)</td>
</tr>
<tr>
<td>안전성</td>
<td>높음(공유에 적합)</td>
<td>낮음(공유 금지)</td>
</tr>
<tr>
<td>충돌 처리</td>
<td>한 번에</td>
<td>커밋마다</td>
</tr>
<tr>
<td>사용 맥락</td>
<td>팀/공유</td>
<td>개인/PR 전</td>
</tr>
</tbody></table>
<hr>
<h2 id="6-내-브랜치를-최신화하는-3가지-방법">6) 내 브랜치를 최신화하는 3가지 방법</h2>
<p>전제: 항상 먼저 development를 최신으로</p>
<pre><code class="language-bash">git fetch origin
git switch development
git pull --ff-only origin development</code></pre>
<p>방법 A) 내 브랜치에서 development를 머지(안전, 권장)</p>
<pre><code class="language-bash">git switch feature/awesome
git merge development
# 충돌 시 해결 → git add -A → git commit</code></pre>
<p>방법 B) 내 브랜치를 development 위로 리베이스(히스토리 깔끔)</p>
<pre><code class="language-bash">git switch feature/awesome
git rebase development
# 충돌 시 해결 → git add -A → git rebase --continue</code></pre>
<p>방법 C) 아예 최신 development에서 새로 파생(히스토리 가장 깔끔)</p>
<pre><code class="language-bash">git switch development
git pull --ff-only origin development
git switch -c feature/awesome-v2
# 필요 커밋만 cherry-pick
git cherry-pick &lt;E&gt; &lt;F&gt; ...</code></pre>
<p>이미 만들어둔 브랜치에 development 최신을 합칠 때”는</p>
<pre><code class="language-bash">git checkout {new\_branch} &amp;&amp; git merge development</code></pre>
<p>명령어 사용 가능. 단, <strong>항상 그 전에</strong> development를 원격과 동기화해야 함.</p>
<hr>
<h2 id="7-충돌-처리-가이드">7) 충돌 처리 가이드</h2>
<p>공통</p>
<ul>
<li>충돌 파일에 &lt;&lt;&lt;&lt;&lt;&lt;&lt;, =======, &gt;&gt;&gt;&gt;&gt;&gt;&gt; 마커 등장</li>
<li>원하는 코드로 정리 후 저장</li>
<li>스테이징하여 다음 단계 진행</li>
</ul>
<p>머지 중</p>
<pre><code class="language-bash">git merge development
# 충돌 해결
git add -A
git commit
# 중단하려면: git merge --abort</code></pre>
<p>리베이스 중</p>
<pre><code class="language-bash">git rebase development
# 충돌 해결
git add -A
git rebase --continue
# 전체 중단: git rebase --abort</code></pre>
<p>팁</p>
<ul>
<li>파일별로 순차 해결: <code>git add &lt;file&gt;</code> 반복</li>
<li>충돌 보기: <code>git status</code>, <code>git diff</code>, 에디터 내 Conflict View 활용</li>
<li>테스트/빌드 통과 확인 후 커밋</li>
</ul>
<hr>
<h2 id="8-stash임시저장--실무-패턴">8) Stash(임시저장) – 실무 패턴</h2>
<p>기본</p>
<pre><code class="language-bash">git stash                 # 추적 파일 변경만
git stash -u              # untracked(새 파일)도 포함
git stash -a              # .gitignore까지 포함(주의)
git stash list
git stash show -p stash@{0}
git stash apply stash@{0} # 적용(스택에 남김)
git stash pop             # 적용+스택에서 제거</code></pre>
<p>부분 적용(대화형)</p>
<pre><code class="language-bash">git stash -p</code></pre>
<p>인덱스 유지/메시지</p>
<pre><code class="language-bash">git stash --keep-index
git stash push -m &quot;WIP: 로그인 폼&quot;</code></pre>
<hr>
<h2 id="9-커밋-관리--좋은-메시지와-정리">9) 커밋 관리 – 좋은 메시지와 정리</h2>
<p>Conventional Commits(추천)</p>
<ul>
<li>타입: feat, fix, docs, style, refactor, test, chore, build, ci, perf</li>
<li>형식: <code>type(scope): 메시지</code></li>
<li>예:</li>
</ul>
<pre><code class="language-bash">feat(auth): 로그인 폼 및 유효성 검증 추가
fix(api): 400 응답 시 에러코드 파싱 버그 수정
docs(readme): 설치 방법 갱신</code></pre>
<p>커밋 수정</p>
<pre><code class="language-bash">git commit --amend                 # 직전 커밋 메시지/내용 수정</code></pre>
<p>히스토리 정리(인터랙티브 리베이스)</p>
<pre><code class="language-bash">git rebase -i HEAD~5
# pick → squash/fixup 로 묶기
# autosquash
git commit --fixup &lt;commit&gt;
git rebase -i --autosquash HEAD~5</code></pre>
<hr>
<h2 id="10-되돌리기--복구">10) 되돌리기 &amp; 복구</h2>
<p>안전하게 되돌리기(공유 브랜치 OK)</p>
<pre><code class="language-bash">git revert &lt;commit&gt;                # 반대 변경 커밋 생성</code></pre>
<p>히스토리 자체를 이동(공유 브랜치 X)</p>
<pre><code class="language-bash">git reset --soft &lt;commit&gt;          # 인덱스/워크트리 유지
git reset --mixed &lt;commit&gt;         # 기본, 인덱스 해제
git reset --hard &lt;commit&gt;          # 전부 되돌림(주의)</code></pre>
<p>파일 단위 복원(새 Git)</p>
<pre><code class="language-bash">git restore &lt;path&gt;                 # 워크트리만 복원
git restore --staged &lt;path&gt;        # 스테이징 해제</code></pre>
<p>마지막 안전망 – reflog</p>
<pre><code class="language-bash">git reflog                         # 브랜치 이동/리셋/리베이스 이력
git reset --hard &lt;reflog-id&gt;       # 과거 지점으로 복원</code></pre>
<p>원격 덮어쓰기(꼭 필요할 때만)</p>
<pre><code class="language-bash">git push --force-with-lease        # 내 변경만 덮도록 안전장치</code></pre>
<hr>
<h2 id="11-gitignore-제대로--이미-추적된-파일-제외">11) .gitignore 제대로 – 이미 추적된 파일 제외</h2>
<ol>
<li>.gitignore에 패턴 추가</li>
<li>인덱스에서만 제거(파일은 남김)</li>
</ol>
<pre><code class="language-bash">git rm --cached -r path/or/file
git commit -m &quot;chore: ignore 빌드 산출물&quot;
git push</code></pre>
<p>전역 ignore(개인 환경 파일)</p>
<pre><code class="language-bash">git config --global core.excludesfile ~/.gitignore_global</code></pre>
<hr>
<h2 id="12-pr코드-리뷰-흐름">12) PR(코드 리뷰) 흐름</h2>
<ol>
<li>기능 브랜치 푸시</li>
</ol>
<pre><code class="language-bash">git push -u origin feature/awesome</code></pre>
<ol start="2">
<li><p>PR 생성(베이스: development 또는 main)</p>
</li>
<li><p>리뷰 반영 → 추가 커밋 푸시</p>
</li>
<li><p>병합 전략</p>
<ul>
<li>Squash Merge: PR 커밋들을 1개로. 히스토리 깨끗</li>
<li>Merge Commit: 머지 커밋 남김. 기록 충실</li>
<li>Rebase &amp; Merge: 직선화하되, 공유 정책에 맞춰 신중히</li>
</ul>
</li>
<li><p>머지 후 브랜치 정리</p>
</li>
</ol>
<pre><code class="language-bash">git branch -d feature/awesome
git push origin --delete feature/awesome</code></pre>
<hr>
<h2 id="13-자주-겪는-오류상황별-처방전">13) 자주 겪는 오류/상황별 처방전</h2>
<p>A) “Your local changes would be overwritten by checkout”</p>
<ul>
<li>원인: 미커밋 변경이 있어서 브랜치 전환 불가</li>
<li>해결:</li>
</ul>
<pre><code class="language-bash">git add -A &amp;&amp; git commit -m &quot;wip: 저장&quot;
# 또는
git stash
git switch 다른브랜치
# 또는 특정 파일만 임시 백업/삭제</code></pre>
<p>B) “The following untracked working tree files would be overwritten by checkout”</p>
<ul>
<li>원인: 전환하려는 브랜치에 동일 경로 파일 존재(내 쪽은 untracked)</li>
<li>해결(선택)</li>
</ul>
<pre><code class="language-bash">git add -A &amp;&amp; git commit   # 커밋 후 전환
git stash -u               # untracked 포함 임시저장
git clean -fd              # 진짜 삭제(주의)</code></pre>
<p>C) .gitignore 추가했는데 여전히 올라감</p>
<pre><code class="language-bash">git rm --cached -r path/
git commit -m &quot;chore: stop tracking generated files&quot;
git push</code></pre>
<p>D) development가 “Already up to date”인데 최신이 아님</p>
<pre><code class="language-bash">git fetch origin
git switch development
git reset --hard origin/development   # 원격 기준으로 딱 맞춤(주의)</code></pre>
<p>E) 리베이스 꼬였을 때</p>
<pre><code class="language-bash">git rebase --abort
# 또는
git reflog  # 이전 상태 찾아 복구
git reset --hard &lt;reflog-id&gt;</code></pre>
<p>F) 잘못 푸시한 커밋 되돌리기(공유 브랜치)</p>
<pre><code class="language-bash">git revert &lt;commit&gt;
git push</code></pre>
<p>G) 과거 실수로 민감정보 푸시(비밀번호/키)</p>
<ul>
<li>즉시 키 폐기/교체 → 새 키 발급</li>
<li>기록 정리(BFG Repo-Cleaner 등) + 정책 공지</li>
</ul>
<hr>
<h2 id="14-치트시트작업→명령어">14) 치트시트(작업→명령어)</h2>
<p>브랜치 최신화(개인 브랜치에서 안전)</p>
<ul>
<li><p>merge:
git switch feature/x
git merge development</p>
</li>
<li><p>rebase:
git switch feature/x
git rebase development</p>
</li>
</ul>
<p>새 브랜치 시작(항상 최신 development에서)</p>
<ul>
<li>git fetch origin</li>
<li>git switch development</li>
<li>git pull --ff-only origin development</li>
<li>git switch -c feature/x</li>
</ul>
<p>PR 전 커밋 정리</p>
<ul>
<li>git rebase -i HEAD~N</li>
<li>pick → squash/fixup</li>
<li>git push --force-with-lease</li>
</ul>
<p>되돌리기(공유 안전)</p>
<ul>
<li>git revert <commit></li>
</ul>
<p>이미 추적 중인 파일 ignore</p>
<ul>
<li>.gitignore 추가</li>
<li>git rm --cached -r path/</li>
<li>git commit &amp;&amp; git push</li>
</ul>
<p>브랜치 전환 오류 해결</p>
<ul>
<li>git add -A &amp;&amp; git commit</li>
<li>git stash (-u)</li>
<li>git clean -fd  (주의)</li>
</ul>
<p>마지막 복구망</p>
<ul>
<li>git reflog</li>
<li>git reset --hard <id></li>
</ul>
<hr>
<p>저는 이런 방식으로 작업을 진행하고 있습니다.</p>
<ol>
<li>최신 베이스에서 브랜치 파생</li>
<li>작은 단위로 커밋(명확 메시지)</li>
<li>주기적 최신화(merge 또는 rebase 원칙 유지)</li>
<li>PR로 검토·병합</li>
</ol>
]]></description>
        </item>
        <item>
            <title><![CDATA[Computer Vision 기본 개념 CLAHE]]></title>
            <link>https://velog.io/@cha-suyeon/Computer-Vision-%EA%B8%B0%EB%B3%B8-%EA%B0%9C%EB%85%90-CLAHE</link>
            <guid>https://velog.io/@cha-suyeon/Computer-Vision-%EA%B8%B0%EB%B3%B8-%EA%B0%9C%EB%85%90-CLAHE</guid>
            <pubDate>Wed, 13 Aug 2025 07:17:02 GMT</pubDate>
            <description><![CDATA[<h1 id="🏗️-clahe를-활용한-다리-균열크랙-탐지--computer-vision-기본-개념">🏗️ CLAHE를 활용한 다리 균열(크랙) 탐지 – Computer Vision 기본 개념</h1>
<h2 id="1-들어가며">1. 들어가며</h2>
<p>다리나 건축 구조물의 안전 점검에서는 <strong>미세한 균열(crack)</strong>을 빠르고 정확하게 찾는 것이 매우 중요합니다.</p>
<p>이 작업을 자동화하려면, 사람이 육안으로 보는 것보다 <strong>이미지 전처리</strong>를 통해 균열을 더 뚜렷하게 드러내야 합니다.</p>
<p>이번 글에서는 <strong>CLAHE(Contrast Limited Adaptive Histogram Equalization)</strong>라는 전처리 기법과 엣지 검출을 이용해 다리 하부의 균열을 감지하는 과정을 소개합니다.</p>
<hr>
<h2 id="2-computer-vision에서의-전처리preprocessing">2. Computer Vision에서의 전처리(Preprocessing)</h2>
<p>컴퓨터 비전(Computer Vision)에서 전처리는 <strong>원본 이미지를 분석·인식하기 전에 품질을 향상시키는 과정</strong>입니다.</p>
<p>전처리의 목적:</p>
<ul>
<li>불필요한 노이즈 제거</li>
<li>대비(contrast) 향상</li>
<li>조명 불균형 보정</li>
<li>관심 영역(ROI) 강조</li>
</ul>
<p>균열 탐지에서는 <strong>대비 향상과 경계선(edge) 강화</strong>가 핵심입니다.</p>
<hr>
<h2 id="3-콘트라스트contrast의-개념">3. 콘트라스트(Contrast)의 개념</h2>
<p><strong>콘트라스트</strong>는 이미지의 밝은 부분과 어두운 부분의 차이입니다.</p>
<ul>
<li><strong>고콘트라스트</strong>: 경계가 뚜렷하고, 세부가 잘 보임</li>
<li><strong>저콘트라스트</strong>: 모든 영역이 비슷한 밝기, 흐릿한 이미지</li>
</ul>
<p>구조물 촬영 시 조명 조건이 나쁘면 저콘트라스트 이미지가 나오기 쉬운데, 이 경우 균열이 배경과 섞여 잘 보이지 않게 됩니다.</p>
<hr>
<h2 id="4-clahe란">4. CLAHE란?</h2>
<p>CLAHE는 <strong>국소 대비 향상</strong>을 위한 알고리즘입니다.</p>
<ol>
<li><p><strong>Histogram Equalization (HE)</strong>
전체 픽셀 밝기 분포를 넓혀 대비를 강화. 단점은 조명 불균형에 취약.</p>
</li>
<li><p><strong>Adaptive Histogram Equalization (AHE)</strong>
이미지를 작은 블록(타일)로 나누고 각 블록별로 대비를 강화.
국소 영역의 세부가 잘 보이지만, 잡음이 심해질 수 있음.</p>
</li>
<li><p><strong>CLAHE (Contrast Limited AHE)</strong>
AHE에 <strong>대비 제한</strong>을 추가하여 과도한 대비 증가를 방지.
결과적으로 <strong>세부는 살리고, 노이즈는 줄이는</strong> 효과.</p>
</li>
</ol>
<hr>
<h2 id="5-실습-예시--다리-균열-검출">5. 실습 예시 – 다리 균열 검출</h2>
<p>아래는 실제 다리 하부 사진에 CLAHE와 엣지 검출을 적용한 예시입니다.</p>
<p><img src="https://velog.velcdn.com/images/cha-suyeon/post/a669b652-93c8-4bee-8838-4b9258ea3806/image.png" alt=""></p>
<p><strong>해석:</strong></p>
<ul>
<li><strong>원본</strong>: 중앙에 균열이 있지만 전체적으로 흐릿.</li>
<li><strong>CLAHE 적용</strong>: 콘크리트 표면 질감과 균열 대비가 강화됨.</li>
<li><strong>엣지 검출</strong>: 균열 경계가 선명하게 드러나며, 자동 검출에 활용 가능.</li>
</ul>
<hr>
<h2 id="6-활용-가능-분야">6. 활용 가능 분야</h2>
<ul>
<li><strong>교량 및 건축물 안전 점검</strong></li>
<li><strong>터널 내벽 균열 탐지</strong></li>
<li><strong>도로 포트홀, 아스팔트 손상 검사</strong></li>
<li><strong>문화재 보존 상태 모니터링</strong></li>
</ul>
<hr>
<h2 id="7-마무리">7. 마무리</h2>
<p>CLAHE와 엣지 검출은 <strong>단순하지만 강력한 Computer Vision 전처리 조합</strong>입니다.
딥러닝 기반 세그멘테이션 모델을 쓰기 전 단계에서, 혹은 데이터셋 품질 향상 목적으로 활용하면
구조물 안전 점검의 자동화 정확도를 크게 높일 수 있습니다.</p>
<hr>
]]></description>
        </item>
        <item>
            <title><![CDATA[HTTP 상태 코드와 에러 코드: 차이점과 효과적인 설계 방법]]></title>
            <link>https://velog.io/@cha-suyeon/HTTP-%EC%83%81%ED%83%9C-%EC%BD%94%EB%93%9C%EC%99%80-%EC%97%90%EB%9F%AC-%EC%BD%94%EB%93%9C-%EC%B0%A8%EC%9D%B4%EC%A0%90%EA%B3%BC-%ED%9A%A8%EA%B3%BC%EC%A0%81%EC%9D%B8-%EC%84%A4%EA%B3%84-%EB%B0%A9%EB%B2%95</link>
            <guid>https://velog.io/@cha-suyeon/HTTP-%EC%83%81%ED%83%9C-%EC%BD%94%EB%93%9C%EC%99%80-%EC%97%90%EB%9F%AC-%EC%BD%94%EB%93%9C-%EC%B0%A8%EC%9D%B4%EC%A0%90%EA%B3%BC-%ED%9A%A8%EA%B3%BC%EC%A0%81%EC%9D%B8-%EC%84%A4%EA%B3%84-%EB%B0%A9%EB%B2%95</guid>
            <pubDate>Mon, 11 Aug 2025 07:20:09 GMT</pubDate>
            <description><![CDATA[<h1 id="http-상태-코드와-에러-코드-차이점과-효과적인-설계-방법">HTTP 상태 코드와 에러 코드: 차이점과 효과적인 설계 방법</h1>
<p>API를 설계하다 보면 <strong>HTTP 상태 코드</strong>와 <strong>에러 코드</strong>를 혼용하거나, 명확하게 구분하지 않고 사용하는 경우가 종종 있습니다.</p>
<p>하지만 이 둘은 역할과 목적이 다르며, 잘 설계하면 디버깅과 유지보수 효율이 크게 올라갑니다.</p>
<p>이번 글에서는 <strong>두 개념의 차이점</strong>과 <strong>효과적인 설계 방법</strong>을 정리해 보겠습니다.</p>
<hr>
<h2 id="1-상태-코드-http-status-code">1. 상태 코드 (HTTP Status Code)</h2>
<p><strong>정의</strong>
HTTP 프로토콜 표준(RFC 9110 등)에 따라 서버가 요청을 처리한 결과를 숫자로 표현한 값입니다.</p>
<p><strong>범위와 의미</strong></p>
<ul>
<li><p><strong>1xx (Informational)</strong>: 요청 수신 및 처리 진행 중</p>
</li>
<li><p><strong>2xx (Success)</strong>: 요청이 정상적으로 처리됨</p>
<ul>
<li>예: <code>200 OK</code>, <code>201 Created</code></li>
</ul>
</li>
<li><p><strong>3xx (Redirection)</strong>: 요청을 다른 위치로 보내야 함</p>
<ul>
<li>예: <code>301 Moved Permanently</code>, <code>302 Found</code></li>
</ul>
</li>
<li><p><strong>4xx (Client Error)</strong>: 클라이언트 요청이 잘못됨</p>
<ul>
<li>예: <code>400 Bad Request</code>, <code>404 Not Found</code>, <code>409 Conflict</code></li>
</ul>
</li>
<li><p><strong>5xx (Server Error)</strong>: 서버 내부 오류 발생</p>
<ul>
<li>예: <code>500 Internal Server Error</code>, <code>503 Service Unavailable</code></li>
</ul>
</li>
</ul>
<p><strong>특징</strong></p>
<ul>
<li>국제 표준 → 모든 HTTP 기반 시스템에서 동일하게 해석 가능</li>
<li>에러뿐 아니라 <strong>성공 상태도 표현</strong> 가능</li>
<li>클라이언트가 응답의 대분류를 판단하는 기준이 됨</li>
</ul>
<hr>
<h2 id="2-에러-코드-error-code">2. 에러 코드 (Error Code)</h2>
<p><strong>정의</strong>
애플리케이션이나 서비스 내부에서 발생한 <strong>특정 오류 상황을 식별</strong>하기 위해 개발자가 정의한 고유 코드입니다.</p>
<p><strong>특징</strong></p>
<ul>
<li>표준 없음 → 프로젝트/조직별로 자유롭게 설계 가능</li>
<li>HTTP 상태 코드의 범주 내에서 <strong>세부적인 원인</strong>을 구분</li>
<li>디버깅과 사용자 대응에 유용</li>
</ul>
<p><strong>예시</strong></p>
<table>
<thead>
<tr>
<th>HTTP 상태 코드</th>
<th>에러 코드</th>
<th>의미</th>
</tr>
</thead>
<tbody><tr>
<td>400 Bad Request</td>
<td>201</td>
<td>필수 필드 누락 (<code>name</code> 값 없음)</td>
</tr>
<tr>
<td>400 Bad Request</td>
<td>202</td>
<td>데이터 형식 불일치 (<code>date</code> 필드가 문자열이 아님)</td>
</tr>
<tr>
<td>401 Unauthorized</td>
<td>101</td>
<td>인증 토큰 만료</td>
</tr>
<tr>
<td>401 Unauthorized</td>
<td>102</td>
<td>잘못된 API 키</td>
</tr>
<tr>
<td>403 Forbidden</td>
<td>301</td>
<td>해당 리소스에 접근 권한 없음</td>
</tr>
<tr>
<td>404 Not Found</td>
<td>401</td>
<td>요청한 리소스가 존재하지 않음</td>
</tr>
<tr>
<td>409 Conflict</td>
<td>342</td>
<td>이미 동일한 사용자명이 존재</td>
</tr>
<tr>
<td>500 Internal Server Error</td>
<td>501</td>
<td>DB 연결 실패</td>
</tr>
<tr>
<td>500 Internal Server Error</td>
<td>502</td>
<td>외부 API 호출 실패</td>
</tr>
</tbody></table>
<hr>
<h2 id="3-상태-코드-vs-에러-코드-비교">3. 상태 코드 vs 에러 코드 비교</h2>
<table>
<thead>
<tr>
<th>구분</th>
<th>상태 코드 (HTTP)</th>
<th>에러 코드 (App/System)</th>
</tr>
</thead>
<tbody><tr>
<td>정의</td>
<td>HTTP 요청 처리 결과를 표준 숫자로 표현</td>
<td>내부 로직/비즈니스 규칙 기반의 고유 오류 식별자</td>
</tr>
<tr>
<td>표준 여부</td>
<td>있음 (RFC 표준)</td>
<td>없음 (서비스별 정의)</td>
</tr>
<tr>
<td>범위</td>
<td>100~599</td>
<td>자유롭게 설정</td>
</tr>
<tr>
<td>목적</td>
<td>결과의 큰 범주 전달</td>
<td>원인에 대한 세부 식별</td>
</tr>
<tr>
<td>예시</td>
<td><code>200 OK</code>, <code>404 Not Found</code></td>
<td><code>344 시작점 오류</code>, <code>341 중복 데이터</code></td>
</tr>
</tbody></table>
<hr>
<h2 id="4-예시-응답-구조">4. 예시 응답 구조</h2>
<pre><code class="language-json">{
  &quot;status&quot;: 409,            // HTTP 상태 코드: 리소스 충돌
  &quot;error_code&quot;: 341,        // 에러 코드: 중복된 데이터 존재
  &quot;message&quot;: &quot;이미 저장된 데이터가 있습니다.&quot;
}</code></pre>
<ul>
<li><code>status</code>(409)는 &quot;무언가 충돌이 있다&quot;는 대분류를 나타내고,</li>
<li><code>error_code</code>(341)는 구체적으로 <strong>&quot;중복 데이터&quot;</strong> 문제임을 알려줍니다.</li>
</ul>
<hr>
<h2 id="5-효과적으로-설계하는-방법">5. 효과적으로 설계하는 방법</h2>
<p>HTTP 상태 코드와 에러 코드를 구분해 잘 설계하면 <strong>클라이언트-서버 간 의사소통이 명확해지고</strong>, 유지보수가 쉬워집니다.</p>
<h3 id="1-http-상태-코드는-표준에-맞춰-사용">(1) HTTP 상태 코드는 표준에 맞춰 사용</h3>
<ul>
<li>2xx: 성공, 4xx: 클라이언트 잘못, 5xx: 서버 잘못</li>
<li>잘못된 사용 예: 모든 실패를 <code>200 OK</code>로 반환하고, 본문에서만 실패 표시
→ <strong>표준 위반</strong>이며, API 사용자가 예외 처리를 어렵게 함</li>
</ul>
<h3 id="2-에러-코드는-서비스별로-일관성-있게-정의">(2) 에러 코드는 서비스별로 일관성 있게 정의</h3>
<ul>
<li><p>숫자 범위 또는 문자열 패턴을 미리 정해 관리</p>
</li>
<li><p>예:</p>
<ul>
<li><code>1xx</code>: 인증/권한 관련 에러</li>
<li><code>2xx</code>: 요청 데이터 검증 실패</li>
<li><code>3xx</code>: 리소스 상태 관련 에러</li>
</ul>
</li>
<li><p>에러 코드와 메시지를 <strong>매핑 테이블</strong>로 관리 → 코드 변경 시 메시지 자동 반영</p>
</li>
</ul>
<h3 id="3-응답-포맷을-통일">(3) 응답 포맷을 통일</h3>
<ul>
<li>성공과 실패 모두 동일한 JSON 구조 사용</li>
<li>예:</li>
</ul>
<pre><code class="language-json">{
  &quot;status&quot;: &lt;HTTP_STATUS_CODE&gt;,
  &quot;error_code&quot;: &lt;INTERNAL_ERROR_CODE&gt;,
  &quot;message&quot;: &quot;&lt;DESCRIPTION&gt;&quot;,
  &quot;data&quot;: { ... }
}</code></pre>
<h3 id="4-메시지는-개발자와-사용자-모두-이해할-수-있도록">(4) 메시지는 개발자와 사용자 모두 이해할 수 있도록</h3>
<ul>
<li><code>error_code</code>는 기계 친화적 (정수/식별자)</li>
<li><code>message</code>는 사람이 읽기 쉬운 문장</li>
<li>다국어 지원이 필요한 경우 메시지를 별도 리소스 파일로 관리</li>
</ul>
<h3 id="5-로깅과-연계">(5) 로깅과 연계</h3>
<ul>
<li>서버 로그에는 <code>status</code>, <code>error_code</code>, 요청 ID, 사용자 정보, 발생 시각을 함께 저장</li>
<li>장애 분석 및 모니터링 도구(Grafana, Kibana 등)에서 필터링 가능하도록 설계</li>
</ul>
<hr>
<h2 id="6-결론">6. 결론</h2>
<ul>
<li><strong>상태 코드</strong>는 &quot;결과의 큰 범주&quot;를,</li>
<li><strong>에러 코드</strong>는 &quot;문제의 구체적인 원인&quot;을 알려줍니다.</li>
<li>두 코드를 잘 결합하면, <strong>API 사용자는 빠르고 정확하게 오류를 이해</strong>하고,
<strong>개발자는 유지보수와 문제 해결이 쉬워집니다.</strong></li>
</ul>
<hr>
]]></description>
        </item>
        <item>
            <title><![CDATA[Python psycopg2.errors 사용법: PostgreSQL 오류 코드보다 편한 예외 처리]]></title>
            <link>https://velog.io/@cha-suyeon/Python-psycopg2.errors-%EC%82%AC%EC%9A%A9%EB%B2%95-PostgreSQL-%EC%98%A4%EB%A5%98-%EC%BD%94%EB%93%9C%EB%B3%B4%EB%8B%A4-%ED%8E%B8%ED%95%9C-%EC%98%88%EC%99%B8-%EC%B2%98%EB%A6%AC</link>
            <guid>https://velog.io/@cha-suyeon/Python-psycopg2.errors-%EC%82%AC%EC%9A%A9%EB%B2%95-PostgreSQL-%EC%98%A4%EB%A5%98-%EC%BD%94%EB%93%9C%EB%B3%B4%EB%8B%A4-%ED%8E%B8%ED%95%9C-%EC%98%88%EC%99%B8-%EC%B2%98%EB%A6%AC</guid>
            <pubDate>Fri, 08 Aug 2025 03:57:08 GMT</pubDate>
            <description><![CDATA[<p>Python과 PostgreSQL을 연결하고, 오류까지 똑똑하게 처리하는 방법을 배워봅니다.</p>
<h2 id="1-postgresql-데이터-저장소">1. <strong>PostgreSQL: 데이터 저장소</strong></h2>
<ul>
<li><p><strong>정의:</strong>
PostgreSQL(포스트그레스큐엘, 줄여서 Postgres)은 **오픈소스 관계형 데이터베이스 관리 시스템(RDBMS)**입니다.</p>
</li>
<li><p><strong>하는 일:</strong>
데이터를 <strong>표(table)</strong> 형태로 저장하고, <strong>SQL</strong>이라는 언어를 사용해 데이터를 검색·추가·수정·삭제할 수 있게 해줍니다.</p>
</li>
<li><p><strong>예시:</strong></p>
<pre><code>┌────────────┬──────────┐
│ name       │ age      │
├────────────┼──────────┤
│ Alice      │ 25       │
│ Bob        │ 32       │
└────────────┴──────────┘</code></pre><ul>
<li>&quot;25살인 사람만 찾아줘&quot; → SQL로 요청 → PostgreSQL이 결과를 반환</li>
</ul>
</li>
<li><p><strong>특징:</strong></p>
<ul>
<li>무료이면서도 기업급 성능</li>
<li>복잡한 쿼리, 대량 데이터 처리, 확장성 우수</li>
<li>금융, 통계, GIS(지도 데이터) 등 다양한 분야에서 사용</li>
</ul>
</li>
</ul>
<hr>
<h2 id="2-psycopg2-postgresql을-다루는-python-도구-상자">2. <strong>psycopg2: PostgreSQL을 다루는 Python 도구 상자</strong></h2>
<ul>
<li><p><strong>정의:</strong>
psycopg2는 Python 코드에서 PostgreSQL 데이터베이스에 접속하고, SQL을 실행하고, 결과를 가져올 수 있게 해주는 <strong>라이브러리</strong>입니다.</p>
</li>
<li><p><strong>역할:</strong></p>
<ol>
<li><strong>연결(Connection):</strong> 데이터베이스에 접속</li>
<li><strong>명령 실행(Execute SQL):</strong> SELECT, INSERT, UPDATE, DELETE 실행</li>
<li><strong>결과 가져오기(Fetch):</strong> 실행 결과를 Python에서 활용</li>
<li><strong>에러 처리(Error Handling):</strong> 실행 중 발생한 문제 확인 및 대응</li>
</ol>
</li>
<li><p><strong>간단한 예시:</strong></p>
<pre><code class="language-python">import psycopg2

# 1. 데이터베이스 연결
conn = psycopg2.connect(
    host=&quot;localhost&quot;,
    dbname=&quot;mydb&quot;,
    user=&quot;myuser&quot;,
    password=&quot;mypassword&quot;
)

cur = conn.cursor()

# 2. SQL 실행
cur.execute(&quot;SELECT * FROM users&quot;)

# 3. 결과 가져오기
rows = cur.fetchall()
print(rows)

conn.close()</code></pre>
</li>
</ul>
<hr>
<h2 id="3-psycopg2errors-postgresql-오류를-다루는-작은-상자">3. <strong>psycopg2.errors: PostgreSQL 오류를 다루는 작은 상자</strong></h2>
<ul>
<li><p><strong>정의:</strong>
<code>psycopg2.errors</code>는 psycopg2 안에 들어 있는 <strong>오류 처리 전용 모듈</strong>입니다.</p>
</li>
<li><p><strong>하는 일:</strong>
PostgreSQL이 보낸 오류 코드(SQLSTATE)를 <strong>이름이 있는 예외 클래스</strong>로 변환해 줍니다.</p>
</li>
<li><p><strong>장점:</strong></p>
<ul>
<li>가독성 ↑: 코드에서 오류 이름만 보고 무슨 문제인지 바로 알 수 있음</li>
<li>유지보수 ↑: 오타나 잘못된 코드 비교를 줄임</li>
<li>호환성 ↑: 기존 DB-API 방식(<code>IntegrityError</code> 등)과도 함께 사용 가능</li>
</ul>
</li>
</ul>
<hr>
<h2 id="4-psycopg2errors의-주요-특징">4. <strong>psycopg2.errors의 주요 특징</strong></h2>
<h3 id="1-sqlstate와-pgcode">(1) SQLSTATE와 <code>pgcode</code></h3>
<ul>
<li><p>PostgreSQL 오류는 <strong>SQLSTATE</strong>라는 5자리 문자열 코드로 구분됨</p>
</li>
<li><p>psycopg2 예외 객체의 <code>.pgcode</code> 속성에 저장</p>
<pre><code class="language-python">except psycopg2.OperationalError as e:
    print(e.pgcode)  # 예: &#39;55P03&#39;</code></pre>
</li>
</ul>
<h3 id="2-자동-생성된-예외-클래스">(2) 자동 생성된 예외 클래스</h3>
<ul>
<li><p>PostgreSQL v9.1 ~ v15까지의 모든 오류를 클래스 형태로 제공</p>
</li>
<li><p>예:</p>
<ul>
<li>코드: <code>22012</code> (division_by_zero) → 클래스: <code>DivisionByZero</code></li>
<li>코드: <code>55P03</code> (lock_not_available) → 클래스: <code>LockNotAvailable</code></li>
</ul>
</li>
</ul>
<h3 id="3-db-api-호환성">(3) DB-API 호환성</h3>
<ul>
<li><p>새 예외 클래스는 기존 DB-API 예외를 상속받아 호환됨</p>
<pre><code class="language-python">except psycopg2.errors.UniqueViolation:  # 더 구체적
    ...
except psycopg2.IntegrityError:  # 상위 클래스
    ...</code></pre>
</li>
</ul>
<h3 id="4-예전-방식-vs-새로운-방식">(4) 예전 방식 vs 새로운 방식</h3>
<p><strong>예전 방식 (오류 코드 비교)</strong></p>
<pre><code class="language-python">try:
    cur.execute(&quot;LOCK TABLE mytable IN ACCESS EXCLUSIVE MODE NOWAIT&quot;)
except psycopg2.OperationalError as e:
    if e.pgcode == psycopg2.errorcodes.LOCK_NOT_AVAILABLE:
        locked = True</code></pre>
<p><strong>새로운 방식 (클래스 직접 사용)</strong></p>
<pre><code class="language-python">try:
    cur.execute(&quot;LOCK TABLE mytable IN ACCESS EXCLUSIVE MODE NOWAIT&quot;)
except psycopg2.errors.LockNotAvailable:
    locked = True</code></pre>
<h3 id="5-lookup-기능">(5) <code>lookup</code> 기능</h3>
<ul>
<li><p>오류 코드를 예외 클래스로 변환해 주는 함수</p>
<pre><code class="language-python">psycopg2.errors.lookup(&quot;55P03&quot;)
# → &lt;class &#39;psycopg2.errors.LockNotAvailable&#39;&gt;</code></pre>
</li>
<li><p>응용 예:</p>
<pre><code class="language-python">try:
    cur.execute(&quot;LOCK TABLE mytable IN ACCESS EXCLUSIVE MODE NOWAIT&quot;)
except psycopg2.errors.lookup(&quot;55P03&quot;):
    locked = True</code></pre>
</li>
</ul>
<hr>
<h2 id="5-전체-요약-그림">5. <strong>전체 요약 그림</strong></h2>
<pre><code>[ PostgreSQL ]
   │  (SQL 실행, 결과/오류 반환)
   ▼
[ psycopg2 ]
   ├─ 데이터베이스 연결
   ├─ SQL 실행
   ├─ 결과 조회
   └─ 오류 처리
        │
        ▼
[ psycopg2.errors ]
   ├─ PostgreSQL 오류 코드(SQLSTATE) → 예외 클래스 변환
   ├─ DB-API 호환
   ├─ 가독성 높은 예외 처리
   └─ lookup()으로 코드 기반 예외 검색</code></pre><hr>
<p>💡 <strong>정리:</strong></p>
<ul>
<li><strong>PostgreSQL</strong> → 데이터를 저장·조회하는 ‘창고’</li>
<li><strong>psycopg2</strong> → Python에서 PostgreSQL과 대화하는 ‘통역사 + 작업 도구 세트’</li>
<li><strong>psycopg2.errors</strong> → 발생한 오류를 읽기 쉽게 알려주는 ‘오류 사전 + 알람 시스템’</li>
</ul>
<hr>
]]></description>
        </item>
        <item>
            <title><![CDATA[in보다 더 나은 선택: 파이썬 딕셔너리 실전 패턴 모음]]></title>
            <link>https://velog.io/@cha-suyeon/in%EB%B3%B4%EB%8B%A4-%EB%8D%94-%EB%82%98%EC%9D%80-%EC%84%A0%ED%83%9D-%ED%8C%8C%EC%9D%B4%EC%8D%AC-%EB%94%95%EC%85%94%EB%84%88%EB%A6%AC-%EC%8B%A4%EC%A0%84-%ED%8C%A8%ED%84%B4-%EB%AA%A8%EC%9D%8C</link>
            <guid>https://velog.io/@cha-suyeon/in%EB%B3%B4%EB%8B%A4-%EB%8D%94-%EB%82%98%EC%9D%80-%EC%84%A0%ED%83%9D-%ED%8C%8C%EC%9D%B4%EC%8D%AC-%EB%94%95%EC%85%94%EB%84%88%EB%A6%AC-%EC%8B%A4%EC%A0%84-%ED%8C%A8%ED%84%B4-%EB%AA%A8%EC%9D%8C</guid>
            <pubDate>Thu, 07 Aug 2025 07:09:00 GMT</pubDate>
            <description><![CDATA[<h2 id="in을-사용하고-딕셔너리-키가-없을-때-keyerror를-처리하기보다는-get을-사용하라">in을 사용하고 딕셔너리 키가 없을 때 KeyError를 처리하기보다는 get을 사용하라</h2>
<p>딕셔너리에서 키가 존재하지 않을 수 있는 상황을 처리하는 방법은 여러 가지가 있습니다. 각 방법의 장단점을 이해하고 상황에 맞는 최적의 방법을 선택하는 것이 중요합니다.</p>
<h3 id="1-딕셔너리-키-존재-여부-확인의-다양한-방법">1. 딕셔너리 키 존재 여부 확인의 다양한 방법</h3>
<h4 id="기본-상황-설정">기본 상황 설정</h4>
<pre><code class="language-python">counters = {
    &#39;품퍼니켈&#39;: 2,
    &#39;사워도우&#39;: 1,
}
key = &#39;밀&#39;  # 존재하지 않는 키</code></pre>
<h3 id="2-방법-1-in-연산자-사용">2. 방법 1: in 연산자 사용</h3>
<pre><code class="language-python">if key in counters:
    count = counters[key]
else:
    count = 0

counters[key] = count + 1</code></pre>
<p><strong>특징:</strong></p>
<ul>
<li>가장 직관적이고 이해하기 쉬운 방법</li>
<li>두 번의 딕셔너리 접근이 필요 (<code>in</code> 검사 + 실제 접근)</li>
<li>코드가 길어질 수 있음</li>
</ul>
<h3 id="3-방법-2-tryexcept-사용">3. 방법 2: try/except 사용</h3>
<pre><code class="language-python">try:
    count = counters[key]
except KeyError:
    count = 0

counters[key] = count + 1</code></pre>
<p><strong>특징:</strong></p>
<ul>
<li>EAFP(Easier to Ask for Forgiveness than Permission) 방식</li>
<li>키가 존재할 가능성이 높을 때 효율적</li>
<li>예외 처리 오버헤드가 있을 수 있음</li>
</ul>
<h3 id="4-방법-3-get-메서드-사용-⭐-권장">4. 방법 3: get() 메서드 사용 ⭐ (권장)</h3>
<pre><code class="language-python">count = counters.get(key, 0)
counters[key] = count + 1</code></pre>
<p><strong>장점:</strong></p>
<ul>
<li><strong>가장 간결하고 pythonic한 방법</strong></li>
<li>한 줄로 해결 가능</li>
<li>딕셔너리 접근이 한 번만 필요</li>
<li>기본값을 명시적으로 지정 가능</li>
</ul>
<h3 id="5-카운터-패턴의-진화">5. 카운터 패턴의 진화</h3>
<h4 id="5-1-not-in-패턴">5-1. not in 패턴</h4>
<pre><code class="language-python">if key not in counters:
    counters[key] = 0

counters[key] += 1</code></pre>
<h4 id="5-2-inelse-패턴">5-2. in/else 패턴</h4>
<pre><code class="language-python">if key in counters:
    counters[key] += 1
else:
    counters[key] = 1</code></pre>
<h4 id="5-3-tryexcept-패턴">5-3. try/except 패턴</h4>
<pre><code class="language-python">try:
    counters[key] += 1
except KeyError:
    counters[key] = 1</code></pre>
<p><strong>분석:</strong></p>
<ul>
<li>모든 방법이 동일한 결과를 만들어내지만</li>
<li>코드의 명확성과 효율성 면에서 차이가 있습니다</li>
</ul>
<h3 id="6-복잡한-기본값-처리-리스트-예제">6. 복잡한 기본값 처리: 리스트 예제</h3>
<h4 id="기본-상황">기본 상황</h4>
<pre><code class="language-python">votes = {
    &#39;바게트&#39;: [&#39;철수&#39;, &#39;순이&#39;],
    &#39;치아바타&#39;: [&#39;하니&#39;, &#39;유리&#39;],
}
key = &#39;브리오슈&#39;  # 새로운 빵 종류
who = &#39;단이&#39;</code></pre>
<h4 id="6-1-ifelse-패턴">6-1. if/else 패턴</h4>
<pre><code class="language-python">if key in votes:
    names = votes[key]
else:
    votes[key] = names = []

names.append(who)</code></pre>
<h4 id="6-2-tryexcept-패턴">6-2. try/except 패턴</h4>
<pre><code class="language-python">try:
    names = votes[key]
except KeyError:
    votes[key] = names = []

names.append(who)</code></pre>
<h4 id="6-3-get-with-none-체크">6-3. get() with None 체크</h4>
<pre><code class="language-python">names = votes.get(key)
if names is None:
    votes[key] = names = []

names.append(who)</code></pre>
<h4 id="6-4-월러스-연산자-활용-python-38">6-4. 월러스 연산자 활용 (Python 3.8+)</h4>
<pre><code class="language-python">if (names := votes.get(key)) is None:
    votes[key] = names = []

names.append(who)</code></pre>
<h3 id="7-setdefault-메서드-궁극의-해결책-⭐⭐">7. setdefault() 메서드: 궁극의 해결책 ⭐⭐</h3>
<pre><code class="language-python">names = votes.setdefault(key, [])
names.append(who)</code></pre>
<p><strong>setdefault()의 동작:</strong></p>
<ul>
<li>키가 존재하면: 기존 값을 반환</li>
<li>키가 존재하지 않으면: 기본값을 설정하고 그 값을 반환</li>
<li><strong>한 줄로 모든 처리가 완료됨</strong></li>
</ul>
<h3 id="8-setdefault-사용-시-주의사항">8. setdefault() 사용 시 주의사항</h3>
<pre><code class="language-python">data = {}
key = &#39;foo&#39;
value = []
data.setdefault(key, value)
print(&#39;이전:&#39;, data)    # {&#39;foo&#39;: []}
value.append(&#39;hello&#39;)
print(&#39;이후: &#39;, data)    # {&#39;foo&#39;: [&#39;hello&#39;]}</code></pre>
<p><strong>중요한 포인트:</strong></p>
<ul>
<li><code>setdefault()</code>는 기본값 객체를 <strong>직접 딕셔너리에 저장</strong>합니다</li>
<li>반환된 객체와 딕셔너리에 저장된 객체는 <strong>같은 객체</strong>입니다</li>
<li>뮤터블 객체(리스트, 딕셔너리 등)를 기본값으로 사용할 때 이를 반드시 고려해야 합니다</li>
</ul>
<h3 id="9-setdefault가-적합하지-않은-경우">9. setdefault()가 적합하지 않은 경우</h3>
<pre><code class="language-python">count = counters.setdefault(key, 0)
counters[key] = count + 1</code></pre>
<p><strong>문제점:</strong></p>
<ul>
<li>카운터처럼 값을 즉시 수정하는 경우</li>
<li><code>setdefault()</code>로 가져온 후 다시 할당하는 것은 비효율적</li>
<li>이런 경우는 <code>get()</code> 방법이 더 적합합니다</li>
</ul>
<h3 id="10-상황별-최적-선택-가이드">10. 상황별 최적 선택 가이드</h3>
<h4 id="simple-값-처리-int-str-등">Simple 값 처리 (int, str 등)</h4>
<pre><code class="language-python"># ✅ 권장: get() 사용
count = counters.get(key, 0) + 1
counters[key] = count</code></pre>
<h4 id="복잡한-객체-처리-list-dict-등">복잡한 객체 처리 (list, dict 등)</h4>
<pre><code class="language-python"># ✅ 권장: setdefault() 사용
names = votes.setdefault(key, [])
names.append(who)</code></pre>
<h4 id="기본값이-복잡한-계산을-요구하는-경우">기본값이 복잡한 계산을 요구하는 경우</h4>
<pre><code class="language-python"># ✅ 권장: if/get() 조합
if key not in expensive_cache:
    expensive_cache[key] = expensive_computation()
result = expensive_cache[key]</code></pre>
<h3 id="11-실제-활용-예시">11. 실제 활용 예시</h3>
<pre><code class="language-python"># 단어 빈도 계산
def count_words(text):
    word_counts = {}
    for word in text.split():
        word_counts[word] = word_counts.get(word, 0) + 1
    return word_counts

# 그룹별 분류
def group_by_category(items):
    groups = {}
    for item in items:
        category = item.get_category()
        groups.setdefault(category, []).append(item)
    return groups

# 중첩 딕셔너리 처리
def nested_dict_access(data, key1, key2):
    return data.get(key1, {}).get(key2, &#39;default_value&#39;)</code></pre>
<h3 id="최종-정리">최종 정리</h3>
<blockquote>
<p><strong>딕셔너리 키 처리의 핵심 원칙:</strong></p>
<ol>
<li><strong>단순한 값</strong>: <code>dict.get(key, default)</code> 사용</li>
<li><strong>복잡한 객체</strong>: <code>dict.setdefault(key, default)</code> 사용  </li>
<li><strong>KeyError 예외 처리보다는 명시적 기본값 처리를 선호</strong></li>
<li><strong>코드의 의도를 명확하게 드러내는 방법 선택</strong></li>
</ol>
<p><code>get()</code>과 <code>setdefault()</code>를 적절히 활용하면:</p>
<ul>
<li>코드가 더 간결해집니다</li>
<li>KeyError 예외를 방지할 수 있습니다  </li>
<li>의도가 명확하게 드러납니다</li>
<li>성능도 향상됩니다</li>
</ul>
<p>특히 데이터 집계, 그룹화, 캐싱 등의 작업에서 이러한 패턴들이 매우 유용합니다.</p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[리스트 조작의 핵심 기술, 슬라이싱 제대로 배우기]]></title>
            <link>https://velog.io/@cha-suyeon/%EB%A6%AC%EC%8A%A4%ED%8A%B8-%EC%A1%B0%EC%9E%91%EC%9D%98-%ED%95%B5%EC%8B%AC-%EA%B8%B0%EC%88%A0-%EC%8A%AC%EB%9D%BC%EC%9D%B4%EC%8B%B1-%EC%A0%9C%EB%8C%80%EB%A1%9C-%EB%B0%B0%EC%9A%B0%EA%B8%B0</link>
            <guid>https://velog.io/@cha-suyeon/%EB%A6%AC%EC%8A%A4%ED%8A%B8-%EC%A1%B0%EC%9E%91%EC%9D%98-%ED%95%B5%EC%8B%AC-%EA%B8%B0%EC%88%A0-%EC%8A%AC%EB%9D%BC%EC%9D%B4%EC%8B%B1-%EC%A0%9C%EB%8C%80%EB%A1%9C-%EB%B0%B0%EC%9A%B0%EA%B8%B0</guid>
            <pubDate>Thu, 07 Aug 2025 06:57:55 GMT</pubDate>
            <description><![CDATA[<h2 id="시퀀스를-슬라이싱하는-방법을-익히기">시퀀스를 슬라이싱하는 방법을 익히기</h2>
<p>파이썬의 슬라이싱은 시퀀스(리스트, 튜플, 문자열 등)의 일부를 효율적으로 가져오거나 수정할 수 있는 강력한 기능입니다. 하지만 제대로 이해하지 못하면 혼란스럽고 버그를 만들어낼 수 있습니다.</p>
<h3 id="1-기본-슬라이싱-문법">1. 기본 슬라이싱 문법</h3>
<p>슬라이싱의 기본 형태는 <code>somelist[start:end]</code>입니다.</p>
<pre><code class="language-python">a = [&#39;a&#39;, &#39;b&#39;, &#39;c&#39;, &#39;d&#39;, &#39;e&#39;, &#39;f&#39;, &#39;g&#39;, &#39;h&#39;]
print(&#39;가운데 2개: &#39;, a[3:5])        # [&#39;d&#39;, &#39;e&#39;]
print(&#39;마지막을 제외한 나머지:&#39;, a[1:7])  # [&#39;b&#39;, &#39;c&#39;, &#39;d&#39;, &#39;e&#39;, &#39;f&#39;, &#39;g&#39;]</code></pre>
<p><strong>핵심 원리:</strong></p>
<ul>
<li><code>start</code> 인덱스는 <strong>포함</strong>됩니다</li>
<li><code>end</code> 인덱스는 <strong>제외</strong>됩니다 (end 바로 앞까지만)</li>
<li><code>a[3:5]</code>는 인덱스 3, 4번째 요소를 가져옵니다 (5번째는 제외)</li>
</ul>
<h3 id="2-생략-가능한-인덱스들">2. 생략 가능한 인덱스들</h3>
<pre><code class="language-python"># 시작 인덱스 생략 (처음부터)
assert a[:5] == a[0:5]    # [&#39;a&#39;, &#39;b&#39;, &#39;c&#39;, &#39;d&#39;, &#39;e&#39;]

# 끝 인덱스 생략 (끝까지)
assert a[5:] == a[5:len(a)]  # [&#39;f&#39;, &#39;g&#39;, &#39;h&#39;]

# 둘 다 생략 (전체 복사)
a[:]  # [&#39;a&#39;, &#39;b&#39;, &#39;c&#39;, &#39;d&#39;, &#39;e&#39;, &#39;f&#39;, &#39;g&#39;, &#39;h&#39;]</code></pre>
<p><strong>가독성 향상을 위한 규칙:</strong></p>
<ul>
<li>처음부터 슬라이싱할 때는 <code>a[:5]</code>처럼 0을 생략하세요</li>
<li>끝까지 슬라이싱할 때는 <code>a[4:]</code>처럼 len()을 생략하세요</li>
</ul>
<h3 id="3-음수-인덱스-활용">3. 음수 인덱스 활용</h3>
<pre><code class="language-python">a[:-1]   # [&#39;a&#39;, &#39;b&#39;, &#39;c&#39;, &#39;d&#39;, &#39;e&#39;, &#39;f&#39;, &#39;g&#39;] (마지막 요소 제외)
a[-3:]   # [&#39;f&#39;, &#39;g&#39;, &#39;h&#39;] (뒤에서 3개)
a[2:-1]  # [&#39;c&#39;, &#39;d&#39;, &#39;e&#39;, &#39;f&#39;, &#39;g&#39;] (인덱스 2부터 마지막 전까지)
a[-3:-1] # [&#39;f&#39;, &#39;g&#39;] (뒤에서 3번째부터 마지막 전까지)</code></pre>
<p><strong>음수 인덱스의 장점:</strong></p>
<ul>
<li>리스트 길이를 몰라도 뒤에서부터 접근 가능</li>
<li><code>a[-1]</code>은 마지막 요소, <code>a[-2]</code>는 마지막에서 두 번째 요소</li>
</ul>
<h3 id="4-경계를-벗어나는-인덱스-처리">4. 경계를 벗어나는 인덱스 처리</h3>
<pre><code class="language-python">first_twenty_items = a[:20]   # 리스트가 8개 요소만 있어도 에러 없음
last_twenty_items = a[-20:]   # 마찬가지로 에러 없음</code></pre>
<p><strong>슬라이싱의 안전성:</strong></p>
<ul>
<li>일반 인덱싱(<code>a</code>)은 IndexError를 발생시키지만</li>
<li>슬라이싱(<code>a[:20]</code>)은 가능한 범위 내에서만 동작하므로 안전합니다</li>
<li>이는 사용자 입력이나 동적 데이터를 다룰 때 매우 유용합니다</li>
</ul>
<h3 id="5-슬라이싱과-원본-리스트의-관계">5. 슬라이싱과 원본 리스트의 관계</h3>
<pre><code class="language-python">b = a[3:]
print(&#39;이전:&#39;, b)    # [&#39;d&#39;, &#39;e&#39;, &#39;f&#39;, &#39;g&#39;, &#39;h&#39;]
b[1] = 99
print(&#39;이후:&#39;, b)    # [&#39;d&#39;, 99, &#39;f&#39;, &#39;g&#39;, &#39;h&#39;]
print(&#39;변화 없음:&#39;, a) # [&#39;a&#39;, &#39;b&#39;, &#39;c&#39;, &#39;d&#39;, &#39;e&#39;, &#39;f&#39;, &#39;g&#39;, &#39;h&#39;]</code></pre>
<p><strong>중요한 개념: 새로운 리스트 생성</strong></p>
<ul>
<li>슬라이싱은 <strong>새로운 리스트 객체</strong>를 만듭니다</li>
<li>원본 리스트 <code>a</code>는 영향받지 않습니다</li>
<li>이는 데이터를 안전하게 복사하고 수정할 때 유용합니다</li>
</ul>
<h3 id="6-슬라이스-할당-slice-assignment">6. 슬라이스 할당 (Slice Assignment)</h3>
<h4 id="6-1-같은-길이로-교체">6-1. 같은 길이로 교체</h4>
<pre><code class="language-python">print(&#39;이전:&#39;, a)           # [&#39;a&#39;, &#39;b&#39;, &#39;c&#39;, &#39;d&#39;, &#39;e&#39;, &#39;f&#39;, &#39;g&#39;, &#39;h&#39;]
a[2:7] = [99, 22, 14]
print(&#39;이후:&#39;, a)           # [&#39;a&#39;, &#39;b&#39;, 99, 22, 14, &#39;h&#39;]</code></pre>
<h4 id="6-2-다른-길이로-교체-리스트-크기-변경">6-2. 다른 길이로 교체 (리스트 크기 변경)</h4>
<pre><code class="language-python">print(&#39;이전:&#39;, a)           # [&#39;a&#39;, &#39;b&#39;, 99, 22, 14, &#39;h&#39;]
a[2:3] = [47, 11]          # 1개 요소를 2개로 교체
print(&#39;이후:&#39;, a)           # [&#39;a&#39;, &#39;b&#39;, 47, 11, 22, 14, &#39;h&#39;]</code></pre>
<p><strong>슬라이스 할당의 특징:</strong></p>
<ul>
<li>할당하는 요소의 개수가 달라도 됩니다</li>
<li>리스트의 크기가 동적으로 조정됩니다</li>
<li>이는 리스트 중간에 여러 요소를 삽입하거나 제거할 때 매우 효율적입니다</li>
</ul>
<h3 id="7-얕은-복사-vs-참조">7. 얕은 복사 vs 참조</h3>
<h4 id="7-1-얕은-복사-만들기">7-1. 얕은 복사 만들기</h4>
<pre><code class="language-python">b = a[:]                    # 전체 슬라이싱으로 복사
assert b == a and b is not a  # 내용은 같지만 다른 객체</code></pre>
<h4 id="7-2-기존-리스트-내용-전체-교체">7-2. 기존 리스트 내용 전체 교체</h4>
<pre><code class="language-python">b = a                       # 같은 객체를 참조
print(&#39;이전 a:&#39;, a)
print(&#39;이전 b:&#39;, b)
a[:] = [101, 102, 103]      # 기존 리스트 객체의 내용만 교체
assert a is b               # 여전히 같은 리스트 객체
print(&#39;이후 a:&#39;, a)         # [101, 102, 103]
print(&#39;이후 b:&#39;, b)         # [101, 102, 103] (같은 객체이므로 동일한 변화)</code></pre>
<p><strong>중요한 차이점:</strong></p>
<ul>
<li><code>b = a[:]</code>: <strong>새로운 리스트</strong> 생성 (얕은 복사)</li>
<li><code>b = a</code>: <strong>같은 리스트</strong>를 참조</li>
<li><code>a[:] = [...]</code>: 기존 리스트 객체의 <strong>내용만 교체</strong></li>
</ul>
<h3 id="8-실제-활용-예시와-베스트-프랙티스">8. 실제 활용 예시와 베스트 프랙티스</h3>
<pre><code class="language-python"># 1. 안전한 리스트 복사
original = [1, 2, 3, 4, 5]
backup = original[:]  # 원본을 안전하게 보관

# 2. 리스트의 특정 부분만 처리
def process_middle_items(items):
    middle = items[1:-1]  # 첫 번째와 마지막 제외
    return [x * 2 for x in middle]

# 3. 대용량 데이터의 청크 처리
def process_in_chunks(data, chunk_size=100):
    for i in range(0, len(data), chunk_size):
        chunk = data[i:i+chunk_size]  # 경계를 벗어나도 안전
        yield process_chunk(chunk)</code></pre>
<h3 id="최종-정리">최종 정리</h3>
<blockquote>
<p><strong>슬라이싱은 파이썬에서 시퀀스를 다루는 가장 pythonic한 방법입니다.</strong></p>
<ol>
<li><strong>간결하고 표현력이 좋습니다</strong>: <code>a[2:-1]</code>은 의도가 명확합니다</li>
<li><strong>경계 안전성</strong>: 인덱스 범위를 벗어나도 예외가 발생하지 않습니다</li>
<li><strong>유연한 할당</strong>: 다른 길이의 시퀀스로도 교체 가능합니다</li>
<li><strong>메모리 효율성</strong>: 새로운 객체 생성 vs 기존 객체 수정을 명확히 구분할 수 있습니다</li>
</ol>
</blockquote>
<blockquote>
<p>슬라이싱을 제대로 익히면 리스트 조작 코드가 훨씬 깔끔하고 읽기 쉬워집니다.</p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[파이썬에서 복잡한 식 대신 도우미 함수를 쓰는 이유]]></title>
            <link>https://velog.io/@cha-suyeon/%ED%8C%8C%EC%9D%B4%EC%8D%AC%EC%97%90%EC%84%9C-%EB%B3%B5%EC%9E%A1%ED%95%9C-%EC%8B%9D-%EB%8C%80%EC%8B%A0-%EB%8F%84%EC%9A%B0%EB%AF%B8-%ED%95%A8%EC%88%98%EB%A5%BC-%EC%93%B0%EB%8A%94-%EC%9D%B4%EC%9C%A0</link>
            <guid>https://velog.io/@cha-suyeon/%ED%8C%8C%EC%9D%B4%EC%8D%AC%EC%97%90%EC%84%9C-%EB%B3%B5%EC%9E%A1%ED%95%9C-%EC%8B%9D-%EB%8C%80%EC%8B%A0-%EB%8F%84%EC%9A%B0%EB%AF%B8-%ED%95%A8%EC%88%98%EB%A5%BC-%EC%93%B0%EB%8A%94-%EC%9D%B4%EC%9C%A0</guid>
            <pubDate>Thu, 07 Aug 2025 06:38:11 GMT</pubDate>
            <description><![CDATA[<p>이 항목의 핵심 메시지는 <strong>&quot;코드는 간결함보다 명료함이 우선&quot;</strong>이라는 것입니다. 한 줄로 복잡한 로직을 표현하려는 시도는 종종 가독성을 해치고, 잠재적인 버그를 만들어내기 쉽습니다. 이럴 때 잘 명명된 도우미 함수(Helper Function)를 사용하는 것이 훨씬 효과적인 방법입니다.</p>
<h3 id="코드-분석을-통한-단계별-학습">코드 분석을 통한 단계별 학습</h3>
<h4 id="1-문제-상황-url-쿼리-파라미터-처리">1. 문제 상황: URL 쿼리 파라미터 처리</h4>
<p>먼저, 코드가 어떤 문제를 해결하려 하는지 이해해야 합니다.</p>
<p><code>urllib.parse.parse_qs</code> 함수는 URL의 쿼리 문자열(예: <code>?name=value&amp;...</code>)을 파싱하여 딕셔너리로 만들어 줍니다.</p>
<pre><code class="language-python">from urllib.parse import parse_qs

my_values = parse_qs(&#39;빨강=5&amp;파랑=0&amp;초록=&#39;,
                     keep_blank_values=True)
print(repr(my_values))
# 출력 결과: {&#39;빨강&#39;: [&#39;5&#39;], &#39;파랑&#39;: [&#39;0&#39;], &#39;초록&#39;: [&#39;&#39;]}</code></pre>
<p>여기서 중요한 점은 <code>parse_qs</code>의 결과입니다.</p>
<ul>
<li>모든 값은 <strong>리스트</strong>에 담겨 있습니다. (예: <code>&#39;5&#39;</code>가 아닌 <code>[&#39;5&#39;]</code>)</li>
<li>값이 없는 키(<code>초록=</code>)는 빈 문자열을 담은 리스트(<code>[&#39;&#39;]</code>)가 됩니다.</li>
<li>쿼리에 없는 키(예: <code>투명도</code>)에 접근하려고 하면 에러가 발생하므로 <code>.get()</code> 메서드를 사용해야 합니다.</li>
</ul>
<p>우리가 하려는 작업은 이 딕셔너리에서 각 색상 값을 <strong>정수(integer)형</strong>으로 안전하게 가져오는 것입니다.</p>
<h4 id="2-나쁜-접근-방식-복잡한-한-줄-표현식">2. 나쁜 접근 방식: 복잡한 한 줄 표현식</h4>
<p>이 문제를 해결하기 위해 다음과 같은 복잡한 한 줄 코드를 사용할 수 있습니다.</p>
<pre><code class="language-python"># &#39;or&#39; 트릭을 이용한 복잡한 식
red = my_values.get(&#39;빨강&#39;, [&#39;&#39;])[0] or 0
green = my_values.get(&#39;파랑&#39;, [&#39;&#39;])[0] or 0 # 의도와 다른 키(&#39;파랑&#39;)를 사용하는 실수
opacity = my_values.get(&#39;투명도&#39;, [&#39;&#39;])[0] or 0

print(f&#39;빨강: {red!r}&#39;)     # 출력: &#39;5&#39; (문자열)
print(f&#39;초록: {green!r}&#39;)   # 출력: &#39;0&#39; (문자열)
print(f&#39;투명도: {opacity!r}&#39;) # 출력: 0   (정수)</code></pre>
<p><strong>이 코드의 문제점:</strong></p>
<ol>
<li><strong>가독성이 매우 낮습니다:</strong> <code>.get()</code>의 기본값, 리스트 인덱싱(&#39;&#39;), 그리고 <code>or</code> 연산자의 단락 평가(short-circuit) 원리까지 한 번에 이해해야 합니다. 코드를 처음 보는 사람은 의도를 파악하기 어렵습니다.</li>
<li><strong>잠재적 버그:</strong><ul>
<li><code>red</code>의 값은 문자열 <code>&#39;5&#39;</code>이지만, <code>opacity</code>의 값은 정수 <code>0</code>입니다. 이렇게 <strong>반환 타입이 일관되지 않으면</strong> 나중에 타입 관련 버그(TypeError)를 유발할 수 있습니다.</li>
<li>코드가 복잡하다 보니 복사-붙여넣기 과정에서 <code>green</code> 변수에 <code>파랑</code> 키를 사용하는 실수가 발생했습니다. 코드가 명료했다면 이런 실수를 더 쉽게 발견했을 것입니다.</li>
</ul>
</li>
</ol>
<h4 id="3-조금-나은-접근-방식-조건부-표현식">3. 조금 나은 접근 방식: 조건부 표현식</h4>
<p>위 문제를 해결하기 위해 코드를 조금 더 풀어쓰면 가독성이 약간 향상됩니다.</p>
<pre><code class="language-python"># if/else 조건부 표현식 사용
red_str = my_values.get(&#39;빨강&#39;, [&#39;&#39;])
red = int(red_str[0]) if red_str[0] else 0</code></pre>
<p>이 코드는 <code>or</code> 트릭보다는 의도가 명확합니다. &quot;문자열이 존재하면 정수로 바꾸고, 그렇지 않으면 0으로 설정하라&quot;는 의미를 더 쉽게 읽을 수 있습니다.</p>
<p>하지만 여전히 문제입니다. 만약 파라미터를 10개 처리해야 한다면, 이 두 줄짜리 로직을 10번 반복해야 합니다. 이는 <strong>코드 중복</strong>을 야기하며, 나중에 로직을 수정해야 할 때 10곳을 모두 고쳐야 하는 유지보수의 어려움을 만듭니다.</p>
<h4 id="4-가장-좋은-해결책-도우미-함수-작성">4. 가장 좋은 해결책: 도우미 함수 작성</h4>
<p>이 모든 문제를 해결하는 가장 좋은 방법은 로직을 <strong>도우미 함수</strong>로 분리하는 것입니다.</p>
<pre><code class="language-python">def get_first_int(values, key, default=0):
    found = values.get(key, [&#39;&#39;])
    if found[0]:
        # 값이 존재하면 정수로 변환하여 반환
        return int(found[0])
    # 키가 없거나 값이 비어있으면 기본값 반환
    return default

# 도우미 함수 사용
green = get_first_int(my_values, &#39;초록&#39;)
red = get_first_int(my_values, &#39;빨강&#39;)
opacity = get_first_int(my_values, &#39;투명도&#39;)

print(f&#39;초록: {green}&#39;)     # 출력: 0
print(f&#39;빨강: {red}&#39;)       # 출력: 5
print(f&#39;투명도: {opacity}&#39;) # 출력: 0</code></pre>
<p><strong>도우미 함수의 장점:</strong></p>
<ol>
<li><strong>가독성 (Readability):</strong> <code>get_first_int(my_values, &#39;초록&#39;)</code>이라는 코드만 봐도 &quot;my_values에서 &#39;초록&#39; 키에 해당하는 첫 번째 값을 정수로 가져온다&quot;는 의도가 명확하게 드러납니다.</li>
<li><strong>재사용성 (Reusability):</strong> 동일한 로직이 필요한 모든 곳에서 이 함수를 호출하기만 하면 됩니다. 코드 중복이 사라집니다 (DRY: Don&#39;t Repeat Yourself 원칙).</li>
<li><strong>유지보수성 (Maintainability):</strong> 만약 파라미터 값이 숫자가 아닐 경우를 대비해 예외 처리(<code>try-except ValueError</code>)를 추가하고 싶다면, <strong>함수 내부만 한 번 수정</strong>하면 이 함수를 사용하는 모든 곳에 적용됩니다.</li>
<li><strong>견고함 (Robustness):</strong> 함수 내에서 모든 엣지 케이스(키가 없는 경우, 값이 비어있는 경우)를 처리하므로 사용자는 안심하고 함수를 호출할 수 있습니다.</li>
</ol>
<h3 id="최종-정리">최종 정리</h3>
<blockquote>
<p>복잡한 로직을 한 줄의 표현식으로 압축하려는 유혹을 피해야 합니다.</p>
<p><strong>대신, 로직의 의도를 명확히 설명하는 이름의 도우미 함수를 작성하세요.</strong></p>
<p>이렇게 하면 코드가 더 명료해지고, 재사용하기 쉬워지며, 미래에 수정하기도 훨씬 수월해집니다. 이는 파이썬다운 코드를 작성하는 중요한 원칙 중 하나입니다.</p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[Postman 테스트 스크립트 개념 및 활용 정리]]></title>
            <link>https://velog.io/@cha-suyeon/Postman-%ED%85%8C%EC%8A%A4%ED%8A%B8-%EC%8A%A4%ED%81%AC%EB%A6%BD%ED%8A%B8-%EA%B0%9C%EB%85%90-%EB%B0%8F-%ED%99%9C%EC%9A%A9-%EC%A0%95%EB%A6%AC</link>
            <guid>https://velog.io/@cha-suyeon/Postman-%ED%85%8C%EC%8A%A4%ED%8A%B8-%EC%8A%A4%ED%81%AC%EB%A6%BD%ED%8A%B8-%EA%B0%9C%EB%85%90-%EB%B0%8F-%ED%99%9C%EC%9A%A9-%EC%A0%95%EB%A6%AC</guid>
            <pubDate>Tue, 05 Aug 2025 07:47:27 GMT</pubDate>
            <description><![CDATA[<h1 id="✅-postman-테스트-스크립트-개념-및-활용-정리">✅ Postman 테스트 스크립트 개념 및 활용 정리</h1>
<h2 id="1-postman에서-테스트-방법-비교">1. Postman에서 테스트 방법 비교</h2>
<table>
<thead>
<tr>
<th>방식</th>
<th>수동 시나리오 테스트</th>
<th>스크립트 기반 테스트</th>
</tr>
</thead>
<tbody><tr>
<td><strong>검증 방식</strong></td>
<td>사용자가 직접 응답(상태 코드, 메시지) 확인</td>
<td>코드(Assertion)로 자동 검증</td>
</tr>
<tr>
<td><strong>자동화 수준</strong></td>
<td>❌ 없음</td>
<td>✅ 응답 Pass/Fail 자동 판정</td>
</tr>
<tr>
<td><strong>재사용성</strong></td>
<td>❌ 없음</td>
<td>✅ 여러 요청에 공통 적용 가능</td>
</tr>
<tr>
<td><strong>테스트 신뢰성</strong></td>
<td>사람에 따라 달라짐</td>
<td>항상 일정하고 정확</td>
</tr>
</tbody></table>
<blockquote>
<p><strong>즉, 스크립트를 작성하면</strong> → 수동 확인 없이 자동으로 Pass/Fail이 판단되어 테스트 품질이 향상됨.</p>
</blockquote>
<p><strong>수동</strong>으로 할 경우, 시간이 오래 걸리며, 테스트 결과 기록이 남지 않는다는 단점이 있다. </p>
<p>예시로 403 응답이 맞는지, 메시지에 &quot;어노테이션&quot;이 들어있는지 직접 확인해야 한다.</p>
<p>반대로 <strong>스크립트</strong> 사용 시에, 사람이 일일이 확인할 필요 없다. 명세 위반 시 바로 탐지되며, CI/CD 파이프라인(Newman)으로도 재사용 가능하다.</p>
<p>예시로 403 응답인데 메시지가 예상과 다르면 자동으로 Fail 표시된다.</p>
<hr>
<h2 id="2-postman-스크립트의-구조">2. Postman 스크립트의 구조</h2>
<p>Postman 스크립트는 <strong>JavaScript</strong>로 작성되며,<br>Postman의 <code>pm</code> 객체와 내장 <strong>Chai Assertion 라이브러리</strong>를 사용하여 응답을 검증한다.</p>
<pre><code class="language-javascript">pm.test(&quot;Check status code&quot;, function () {
    pm.expect(pm.response.code).to.be.oneOf([200, 204, 400, 403, 404]);
});</code></pre>
<hr>
<h2 id="3-pre-request-vs-tests-post-response-차이">3. Pre-request vs Tests (Post-response) 차이</h2>
<table>
<thead>
<tr>
<th>구분</th>
<th>Pre-request Script</th>
<th>Tests (Post-response)</th>
</tr>
</thead>
<tbody><tr>
<td><strong>실행 시점</strong></td>
<td>요청 전 실행</td>
<td>응답 후 실행</td>
</tr>
<tr>
<td><strong>주요 용도</strong></td>
<td>토큰 생성, 변수 설정 등 요청 준비</td>
<td>응답 상태 코드, 메시지, JSON 필드 자동 검증</td>
</tr>
<tr>
<td><strong>예시</strong></td>
<td>JWT 토큰 자동 발급</td>
<td>상태 코드 200인지 검사</td>
</tr>
</tbody></table>
<hr>
<h2 id="4-강화된-postman-test-script-예시">4. 강화된 Postman Test Script 예시</h2>
<pre><code class="language-javascript">if (pm.response) {
    const statusCode = pm.response.code;
    const json = statusCode !== 204 ? pm.response.json() : null;

    // ✅ [1] 상태 코드 검증
    pm.test(&quot;Status code is valid&quot;, function () {
        pm.expect(statusCode).to.be.oneOf([200, 204, 400, 401, 403, 404, 409, 500]);
    });

    // ✅ [2] 응답 시간 검증
    pm.test(&quot;Response time &lt; 500ms&quot;, function () {
        pm.expect(pm.response.responseTime).to.be.below(500);
    });

    // ✅ [3] 204 응답 → 바디가 비어있는지 체크
    if (statusCode === 204) {
        pm.test(&quot;No body for 204&quot;, function () {
            pm.expect(pm.response.text()).to.eql(&quot;&quot;);
        });
    } else {
        pm.test(&quot;Response is JSON&quot;, function () {
            pm.expect(json).to.be.an(&quot;object&quot;);
        });

        pm.test(&quot;Message field exists&quot;, function () {
            pm.expect(json.message).to.be.a(&quot;string&quot;);
        });

        // ✅ [4] 상태 코드별 기대 메시지 확인
        if (statusCode === 400) pm.expect(json.message).to.match(/잘못된 요청|유효하지 않음/);
        if (statusCode === 403) pm.expect(json.message).to.include(&quot;어노테이션&quot;);
        if (statusCode === 404) pm.expect(json.message).to.match(/찾을 수 없음|존재하지 않음/);
        if (statusCode === 409) pm.expect(json.message).to.match(/중복|이미 존재/);
        if (statusCode === 500) pm.expect(json.message).to.match(/서버 오류|내부 오류/);

        // ✅ [5] 추가 JSON 필드 검증
        pm.test(&quot;JSON should have timestamp&quot;, function () {
            pm.expect(json).to.have.property(&quot;timestamp&quot;);
        });
    }
}</code></pre>
<hr>
<h2 id="5-스크립트-작성-방법">5. 스크립트 작성 방법</h2>
<ol>
<li>Postman에서 요청 열기</li>
<li>Scripts 탭 선택</li>
<li>위 코드를 붙여넣기</li>
<li>요청 실행 → Tests 탭에서 Pass/Fail 확인</li>
</ol>
<p>안에 세부적으로 두 가지 탭이 있는데 테스트는 <code>post-response</code>에 입력하여 실행하면 된다.</p>
<p><code>pre-request</code>와  <code>test (post-response)</code>의 차이로는 </p>
<table>
<thead>
<tr>
<th>구분</th>
<th><strong>Pre-request Script</strong></th>
<th><strong>Tests (Post-response) Script</strong></th>
</tr>
</thead>
<tbody><tr>
<td><strong>실행 시점</strong></td>
<td>요청 <strong>보내기 전</strong> 실행</td>
<td>응답 <strong>받은 후</strong> 실행</td>
</tr>
<tr>
<td><strong>주요 용도</strong></td>
<td>- 요청에 동적 값 삽입</td>
<td></td>
</tr>
</tbody></table>
<p>이며</p>
<ul>
<li>환경 변수 설정</li>
<li>인증 토큰 갱신 | - 응답 값 검증</li>
<li>상태 코드, 바디, 헤더 검사</li>
<li>결과에 따른 후속 변수 설정</li>
<li>| 예시 |JWT 토큰 자동 발급 후 요청 헤더에 추가 | ✅ 응답 코드가 200인지 검사</li>
</ul>
<p>✅ 정리하면</p>
<ul>
<li>지금처럼 scripts(Tests)를 작성하면, API 응답이 명세를 따르는지 자동으로 확인 가능</li>
<li>Pre-request는 요청 전 필요한 준비 작업, Post-response(Test)는 응답 검증에 사용</li>
</ul>
<hr>
<h2 id="6-스크립트-적용-시-효과-vs-미적용-차이">6. 스크립트 적용 시 효과 vs 미적용 차이</h2>
<table>
<thead>
<tr>
<th>구분</th>
<th>스크립트 미적용</th>
<th>스크립트 적용</th>
</tr>
</thead>
<tbody><tr>
<td><strong>검증 정확도</strong></td>
<td>사람이 눈으로 확인 → 실수 가능</td>
<td>자동 검사 → 작은 오류도 즉시 탐지</td>
</tr>
<tr>
<td><strong>속도</strong></td>
<td>느림</td>
<td>빠름</td>
</tr>
<tr>
<td><strong>결과 기록</strong></td>
<td>없음</td>
<td>Pass/Fail 기록</td>
</tr>
<tr>
<td><strong>테스트 품질</strong></td>
<td>일정하지 않음</td>
<td>항상 동일하고 신뢰성 높음</td>
</tr>
</tbody></table>
<hr>
<h2 id="7-실제-에러-발생-시-동작-예시">7. 실제 에러 발생 시 동작 (예시)</h2>
<ul>
<li>예: <code>403 Forbidden</code> 응답이 왔을 때,  <ul>
<li><strong>API 메시지</strong>: <code>&quot;어노테이션에 해당 카테고리 이름이 등록되어있어 삭제가 불가합니다.&quot;</code>  </li>
<li><strong>스크립트 기대값</strong>: <code>&quot;어노테이션 연결&quot;</code> 포함  </li>
<li>→ <strong>Assertion Fail</strong> 발생  </li>
<li><strong>결과</strong>: 테스트 결과에 Fail 표시 → 문제 즉시 확인 가능</li>
</ul>
</li>
</ul>
<hr>
<h2 id="7-결론">7. 결론</h2>
<ul>
<li><p>Postman 테스트 스크립트는 <strong>응답 검증의 자동화</strong>를 통해  </p>
<ul>
<li>테스트 속도 ✅  </li>
<li>정확도 ✅  </li>
<li>품질 ✅<br>를 높인다.</li>
</ul>
</li>
<li><p>지금처럼 Assertion Fail이 표시되는 것도 <strong>스크립트가 정상 동작 중</strong>이라는 신호이며,<br>필요 시 메시지 패턴을 조정하여 유연하게 관리할 수 있다.</p>
</li>
</ul>
<hr>
<blockquote>
<p>✅ <strong>향후 확장</strong>: Collection Runner + CSV 데이터 파일을 사용하면 <strong>요청 변경까지 자동화</strong> 가능!</p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[🐍 파이썬에서 assert vs raise 완벽 가이드]]></title>
            <link>https://velog.io/@cha-suyeon/%ED%8C%8C%EC%9D%B4%EC%8D%AC%EC%97%90%EC%84%9C-assert-vs-raise-%EC%99%84%EB%B2%BD-%EA%B0%80%EC%9D%B4%EB%93%9C</link>
            <guid>https://velog.io/@cha-suyeon/%ED%8C%8C%EC%9D%B4%EC%8D%AC%EC%97%90%EC%84%9C-assert-vs-raise-%EC%99%84%EB%B2%BD-%EA%B0%80%EC%9D%B4%EB%93%9C</guid>
            <pubDate>Tue, 05 Aug 2025 07:27:08 GMT</pubDate>
            <description><![CDATA[<h1 id="🐍-파이썬에서-assert-vs-raise-완벽-가이드">🐍 <strong>파이썬에서 assert vs raise 완벽 가이드</strong></h1>
<p>파이썬에서 오류를 발생시키는 방법에는 여러 가지가 있지만,
가장 자주 사용되는 것이 <strong><code>assert</code></strong> 와 <strong><code>raise</code></strong> 입니다.</p>
<p>둘은 비슷해 보이지만 <strong>용도와 동작 방식이 다릅니다.</strong>
이 글에서는 <strong><code>assert</code>와 <code>raise</code>의 차이</strong>, <strong>상황별 사용법</strong>, 그리고 <strong>실전 예제</strong>를 통해 개념을 정리합니다.</p>
<hr>
<h2 id="✅-1-assert--디버깅용-조건-검사">✅ <strong>1. assert — 디버깅용 조건 검사</strong></h2>
<h3 id="📌-개념">📌 <strong>개념</strong></h3>
<ul>
<li><strong><code>assert 조건, &quot;에러 메시지&quot;</code></strong></li>
<li><strong>조건이 거짓(False)</strong> 이면 <code>AssertionError</code>를 발생시킵니다.</li>
<li>프로그램이 잘못된 상태로 진행되는 것을 막기 위해 사용됩니다.</li>
</ul>
<hr>
<h3 id="🧩-예제">🧩 <strong>예제</strong></h3>
<pre><code class="language-python">x = 10
assert x &gt; 0, &quot;x는 양수여야 합니다&quot;   # ✅ 통과
assert x &lt; 0, &quot;x는 음수여야 합니다&quot;   # ❌ AssertionError: x는 음수여야 합니다</code></pre>
<hr>
<h3 id="✅-특징">✅ <strong>특징</strong></h3>
<ul>
<li><strong>디버깅 단계에서 상태 검증</strong>을 위해 사용.</li>
<li><strong><code>python -O</code> (옵티마이즈 모드)</strong> 로 실행하면 <strong>assert가 무시</strong>됩니다.</li>
<li>따라서 <strong>프로덕션 코드(실제 서비스)에서는 남용하면 안 됨</strong>.</li>
</ul>
<hr>
<h2 id="✅-2-raise--명시적-예외-발생">✅ <strong>2. raise — 명시적 예외 발생</strong></h2>
<h3 id="📌-개념-1">📌 <strong>개념</strong></h3>
<ul>
<li><strong><code>raise 예외(&quot;에러 메시지&quot;)</code></strong></li>
<li>개발자가 직접 <strong>특정 예외를 발생</strong>시키는 방법입니다.</li>
<li>모든 예외 타입(<code>ValueError</code>, <code>TypeError</code>, 사용자 정의 예외 등)과 함께 사용 가능.</li>
</ul>
<hr>
<h3 id="🧩-예제-1">🧩 <strong>예제</strong></h3>
<pre><code class="language-python">x = -5
if x &lt; 0:
    raise ValueError(&quot;x는 음수일 수 없습니다&quot;)  # ❌ ValueError: x는 음수일 수 없습니다</code></pre>
<hr>
<h3 id="✅-특징-1">✅ <strong>특징</strong></h3>
<ul>
<li><strong>항상 실행</strong> → 최적화 옵션과 관계없이 작동.</li>
<li>예외를 명확히 제어할 수 있어 <strong>프로덕션 코드에서 권장</strong>.</li>
<li><code>try</code>-<code>except</code>와 함께 사용 가능.</li>
</ul>
<hr>
<h2 id="✅-3-assert-vs-raise--차이-한눈에-보기">✅ <strong>3. assert vs raise — 차이 한눈에 보기</strong></h2>
<table>
<thead>
<tr>
<th>구분</th>
<th><code>assert</code></th>
<th><code>raise</code></th>
</tr>
</thead>
<tbody><tr>
<td><strong>목적</strong></td>
<td>디버깅용 조건 검사</td>
<td>명시적으로 예외 발생</td>
</tr>
<tr>
<td><strong>예외 타입</strong></td>
<td>항상 <code>AssertionError</code></td>
<td>다양한 예외(<code>ValueError</code>, <code>TypeError</code>, 사용자 정의 예외)</td>
</tr>
<tr>
<td><strong>실행 환경</strong></td>
<td>최적화 모드(<code>-O</code>)에서 비활성화됨</td>
<td>항상 실행됨</td>
</tr>
<tr>
<td><strong>사용 시기</strong></td>
<td>디버깅, 테스트</td>
<td>프로덕션 코드, 예외 처리 로직</td>
</tr>
</tbody></table>
<hr>
<h2 id="✅-4-assert와-raise-함께-사용하기">✅ <strong>4. assert와 raise 함께 사용하기</strong></h2>
<p>두 개념은 <strong>상호 보완적으로 사용</strong>할 수 있습니다.</p>
<ul>
<li><strong>개발 중</strong>: <code>assert</code>로 빠른 검증</li>
<li><strong>운영 중</strong>: <code>raise</code>로 명시적 예외 처리</li>
</ul>
<hr>
<h3 id="🧩-예제-2">🧩 <strong>예제</strong></h3>
<pre><code class="language-python">def process_value(x):
    assert isinstance(x, int), &quot;x는 int 타입이어야 합니다&quot;  # 디버깅용
    if x &lt; 0:
        raise ValueError(&quot;x는 음수일 수 없습니다&quot;)         # 프로덕션용
    return x * 2

print(process_value(5))    # ✅ 10
print(process_value(-3))   # ❌ ValueError</code></pre>
<hr>
<h2 id="✅-5-assert와-isinstance-함께-사용">✅ <strong>5. assert와 isinstance 함께 사용</strong></h2>
<p>앞서 배운 <strong><code>isinstance</code></strong>를 활용하면 타입 검증이 더 명확합니다.</p>
<pre><code class="language-python">def greet(name):
    assert isinstance(name, str), &quot;name은 문자열이어야 합니다&quot;  # 디버깅에서만 체크
    return f&quot;Hello, {name}!&quot;</code></pre>
<hr>
<h2 id="✅-6-eval과-assert의-예제-연계">✅ <strong>6. eval과 assert의 예제 연계</strong></h2>
<p><code>assert</code>를 이용해 <strong><code>eval(repr(obj)) == obj</code></strong> 관계를 확인합니다.</p>
<pre><code class="language-python">a = &#39;\x07&#39;
b = eval(repr(a))
assert a == b   # ✅ True → repr 결과로 복원 가능</code></pre>
<ul>
<li><code>assert</code>는 이 관계가 항상 참인지 확인하는 <strong>디버깅 안전장치</strong> 역할을 합니다.</li>
</ul>
<hr>
<h2 id="✅-7-실전에서-raise의-활용">✅ <strong>7. 실전에서 raise의 활용</strong></h2>
<ul>
<li>사용자 입력 검증</li>
<li>API 예외 처리</li>
<li>커스텀 예외 클래스와 함께 사용</li>
</ul>
<pre><code class="language-python">class CustomError(Exception):
    pass

def check_value(x):
    if x != 42:
        raise CustomError(&quot;정답은 42여야 합니다!&quot;)

check_value(10)  # ❌ CustomError 발생</code></pre>
<hr>
<h2 id="🎯-정리">🎯 <strong>정리</strong></h2>
<ul>
<li><p>✅ <strong><code>assert</code></strong>
→ <strong>디버깅용</strong>으로 조건을 확인, 실패 시 <code>AssertionError</code> 발생
→ 프로덕션에서는 비활성화될 수 있으니 <strong>주의</strong></p>
</li>
<li><p>✅ <strong><code>raise</code></strong>
→ <strong>명시적으로 예외 발생</strong>, 다양한 예외 타입 사용 가능
→ 운영 환경에서도 항상 작동 → <strong>예외 처리의 핵심</strong></p>
</li>
</ul>
<hr>
]]></description>
        </item>
        <item>
            <title><![CDATA[🐍 파이썬에서 __repr__과 eval(repr(obj)) 관계 완벽 이해하기]]></title>
            <link>https://velog.io/@cha-suyeon/%ED%8C%8C%EC%9D%B4%EC%8D%AC%EC%97%90%EC%84%9C-repr%EA%B3%BC-evalreprobj-%EA%B4%80%EA%B3%84-%EC%99%84%EB%B2%BD-%EC%9D%B4%ED%95%B4%ED%95%98%EA%B8%B0</link>
            <guid>https://velog.io/@cha-suyeon/%ED%8C%8C%EC%9D%B4%EC%8D%AC%EC%97%90%EC%84%9C-repr%EA%B3%BC-evalreprobj-%EA%B4%80%EA%B3%84-%EC%99%84%EB%B2%BD-%EC%9D%B4%ED%95%B4%ED%95%98%EA%B8%B0</guid>
            <pubDate>Tue, 05 Aug 2025 07:24:09 GMT</pubDate>
            <description><![CDATA[<h1 id="🐍-파이썬에서-__repr__과-evalreprobj-관계-완벽-이해하기">🐍 <strong>파이썬에서 <code>__repr__</code>과 <code>eval(repr(obj))</code> 관계 완벽 이해하기</strong></h1>
<p>파이썬을 공부하다 보면 <strong><code>__repr__</code></strong> 과 <strong><code>eval(repr(obj))</code></strong> 라는 표현을 자주 보게 됩니다.
특히 <strong>Effective Python 아이템 75</strong>에서는 이 두 개념의 관계를 강조합니다.
이 글에서는 <strong><code>__repr__</code>의 역할</strong>, <strong><code>eval(repr(obj))</code>의 의미</strong>, 그리고 <strong>두 개념의 관계</strong>를 상세히 설명합니다.</p>
<hr>
<h2 id="✅-1-__repr__란-무엇인가">✅ <strong>1. <code>__repr__</code>란 무엇인가?</strong></h2>
<h3 id="📌-개념">📌 <strong>개념</strong></h3>
<ul>
<li><code>__repr__</code>은 <strong>객체의 “공식적인” 문자열 표현</strong>을 반환하는 특수 메서드입니다.</li>
<li>목적: <strong>개발자(디버깅)</strong> 를 위한 객체의 정보 제공.</li>
<li><code>repr(obj)</code> 또는 <code>print(repr(obj))</code>를 호출하면 <strong><code>obj.__repr__()</code></strong> 이 실행됩니다.</li>
</ul>
<hr>
<h3 id="🧩-예제">🧩 <strong>예제</strong></h3>
<pre><code class="language-python">class User:
    def __init__(self, name, age):
        self.name = name
        self.age = age

user = User(&quot;Suyeon&quot;, 27)
print(repr(user))  # &lt;__main__.User object at 0x7f...&gt;</code></pre>
<ul>
<li><code>__repr__</code>을 정의하지 않으면 <strong>기본 출력은 메모리 주소만</strong> 보여줍니다.</li>
</ul>
<hr>
<h3 id="✅-__repr__을-재정의하면">✅ <strong><code>__repr__</code>을 재정의하면?</strong></h3>
<pre><code class="language-python">class User:
    def __init__(self, name, age):
        self.name = name
        self.age = age

    def __repr__(self):
        return f&quot;User({self.name!r}, {self.age!r})&quot;

user = User(&quot;Suyeon&quot;, 27)
print(repr(user))  # User(&#39;Suyeon&#39;, 27)</code></pre>
<ul>
<li>이제 <strong>디버깅 시 더 유용한 정보</strong>를 얻을 수 있습니다.</li>
</ul>
<hr>
<h2 id="✅-2-evalreprobj란-무엇인가">✅ <strong>2. <code>eval(repr(obj))</code>란 무엇인가?</strong></h2>
<h3 id="📌-개념-1">📌 <strong>개념</strong></h3>
<ul>
<li><strong><code>eval()</code></strong> 은 문자열을 <strong>파이썬 코드로 실행</strong>하고 그 결과를 반환합니다.</li>
<li><strong><code>repr(obj)</code></strong> 의 결과가 <strong>유효한 파이썬 표현식</strong>이면,
<code>eval(repr(obj))</code>을 통해 <strong>원본 객체를 재생성</strong>할 수 있습니다.</li>
</ul>
<hr>
<h3 id="🧩-예제-1">🧩 <strong>예제</strong></h3>
<pre><code class="language-python">a = &quot;hello&quot;
b = eval(repr(a))
print(b)         # hello
assert a == b    # ✅ True</code></pre>
<ul>
<li><code>repr(a)</code> → <code>&#39;hello&#39;</code></li>
<li><code>eval(&quot;&#39;hello&#39;&quot;)</code> → 문자열 <code>&quot;hello&quot;</code> 생성</li>
</ul>
<hr>
<h2 id="✅-3-__repr__과-evalreprobj의-관계">✅ <strong>3. <code>__repr__</code>과 <code>eval(repr(obj))</code>의 관계</strong></h2>
<h3 id="📌-관계-설명">📌 <strong>관계 설명</strong></h3>
<ul>
<li><strong>이상적인 <code>__repr__</code></strong> 은 <strong><code>eval(repr(obj)) == obj</code></strong> 를 만족해야 합니다.</li>
<li>즉, <code>repr(obj)</code>의 결과가 <strong>객체를 다시 만들 수 있는 파이썬 코드</strong>라면 이상적입니다.</li>
</ul>
<hr>
<h3 id="🧩-예제-이상적인-repr">🧩 <strong>예제: 이상적인 <strong>repr</strong></strong></h3>
<pre><code class="language-python">class Point:
    def __init__(self, x, y):
        self.x = x
        self.y = y

    def __repr__(self):
        return f&quot;Point({self.x!r}, {self.y!r})&quot;

p1 = Point(2, 3)
p2 = eval(repr(p1))
print(p2)            # Point(2, 3)
print(p1.x, p1.y)    # 2 3
print(p1.__dict__)   # {&#39;x&#39;: 2, &#39;y&#39;: 3}</code></pre>
<ul>
<li><code>eval(repr(p1))</code> → 새로운 Point 객체 생성</li>
<li><code>repr</code>이 디버깅과 객체 재생성에 모두 유용해짐.</li>
</ul>
<hr>
<h2 id="✅-4-repr-vs-str-차이">✅ <strong>4. **repr</strong> vs <strong>str</strong> 차이**</h2>
<table>
<thead>
<tr>
<th>항목</th>
<th><code>__repr__</code></th>
<th><code>__str__</code></th>
</tr>
</thead>
<tbody><tr>
<td><strong>목적</strong></td>
<td>개발자 디버깅용</td>
<td>사용자 친화적 출력</td>
</tr>
<tr>
<td><strong>호출 방식</strong></td>
<td><code>repr(obj)</code></td>
<td><code>str(obj)</code></td>
</tr>
<tr>
<td><strong>권장 구현</strong></td>
<td><code>eval(repr(obj)) == obj</code> 만족하도록</td>
<td>읽기 좋은 문자열</td>
</tr>
</tbody></table>
<hr>
<h2 id="✅-5-실전에서의-활용">✅ <strong>5. 실전에서의 활용</strong></h2>
<ul>
<li><strong>디버깅용</strong>: <code>__repr__</code> 을 잘 구현하면 print만으로 내부 상태 확인 가능</li>
<li><strong>객체 복원용</strong>: <code>repr</code>이 <code>eval</code>과 호환되면 테스트 및 직렬화에 유용</li>
<li><strong>예외 상황</strong>: 모든 객체가 <code>eval(repr(obj))</code>을 만족할 필요는 없음 (예: 파일 핸들, 소켓 등 복원 불가 객체)</li>
</ul>
<hr>
<h2 id="✅-6-eval의-위험성-주의">✅ <strong>6. eval의 위험성 주의</strong></h2>
<ul>
<li><code>eval</code>은 <strong>임의 코드 실행</strong> 가능 → 보안상 매우 위험.</li>
<li>신뢰할 수 없는 입력에는 절대 사용하지 말 것.</li>
<li>안전하게 평가할 때는 <strong><code>ast.literal_eval</code></strong>을 사용.</li>
</ul>
<hr>
<h2 id="🎯-정리">🎯 <strong>정리</strong></h2>
<ul>
<li>✅ <strong><code>__repr__</code></strong>: 객체의 <strong>공식적 표현</strong>(디버깅/복원 목적)</li>
<li>✅ <strong><code>eval(repr(obj))</code></strong>: <code>repr(obj)</code>가 파이썬 코드라면 <strong>원본 객체 재생성</strong> 가능</li>
<li>✅ <strong>이상적인 <code>__repr__</code></strong>: <code>eval(repr(obj)) == obj</code> 만족</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[🐍 파이썬에서 isinstance vs issubclass 완벽 가이드]]></title>
            <link>https://velog.io/@cha-suyeon/%ED%8C%8C%EC%9D%B4%EC%8D%AC%EC%97%90%EC%84%9C-isinstance-vs-issubclass-%EC%99%84%EB%B2%BD-%EA%B0%80%EC%9D%B4%EB%93%9C</link>
            <guid>https://velog.io/@cha-suyeon/%ED%8C%8C%EC%9D%B4%EC%8D%AC%EC%97%90%EC%84%9C-isinstance-vs-issubclass-%EC%99%84%EB%B2%BD-%EA%B0%80%EC%9D%B4%EB%93%9C</guid>
            <pubDate>Tue, 05 Aug 2025 07:21:09 GMT</pubDate>
            <description><![CDATA[<h1 id="🐍-파이썬에서-isinstance-vs-issubclass-완벽-가이드">🐍 <strong>파이썬에서 isinstance vs issubclass 완벽 가이드</strong></h1>
<p>파이썬에서는 <strong>객체나 클래스의 타입을 확인</strong>할 때 <code>isinstance</code>와 <code>issubclass</code>를 자주 사용합니다.</p>
<p>두 함수는 비슷해 보이지만 <strong>확인하는 대상과 용도가 다릅니다.</strong></p>
<p>이 글에서는 <strong>두 함수의 차이, 사용법, 비교</strong>를 초보자도 이해할 수 있도록 쉽게 설명하겠습니다.</p>
<hr>
<h2 id="✅-1-isinstanceobj-classinfo--객체가-특정-클래스의-인스턴스인지-확인">✅ <strong>1. isinstance(obj, classinfo) — 객체가 특정 클래스의 인스턴스인지 확인</strong></h2>
<h3 id="📌-개념">📌 <strong>개념</strong></h3>
<ul>
<li><strong><code>isinstance(객체, 클래스)</code></strong></li>
<li>객체가 특정 클래스(또는 그 하위 클래스)의 <strong>인스턴스인지</strong> 검사.</li>
</ul>
<h3 id="🧩-기본-예제">🧩 <strong>기본 예제</strong></h3>
<pre><code class="language-python">class Animal: pass
class Dog(Animal): pass

dog = Dog()

print(isinstance(dog, Dog))      # ✅ True
print(isinstance(dog, Animal))   # ✅ True (Dog은 Animal의 하위 클래스)
print(isinstance(dog, object))   # ✅ True (모든 클래스는 object 상속)
print(isinstance(dog, str))      # ❌ False</code></pre>
<h3 id="✅-특징">✅ <strong>특징</strong></h3>
<ul>
<li><strong>객체와 클래스 관계</strong>를 확인.</li>
<li><strong>상속 관계도 인정</strong> → 하위 클래스 인스턴스도 상위 클래스로 인정.</li>
</ul>
<p>또한, 두 번째 인자로 타입 튜플을 넣으면 여러 타입 중 하나라도 일치하면 True를 리턴한다.</p>
<pre><code class="language-python">x = 42
print(isinstance(x, int))         # True
print(isinstance(x, (str, int)))  # True (int가 포함되어 있음)
print(isinstance(x, str))         # False</code></pre>
<p>사용하는 이유로는 타입 검증 시 <code>type(obj) == int</code>보다 유연하며, 상속 관계까지 확인할 수 있기 때문이다.</p>
<hr>
<h2 id="✅-2-issubclassclass-classinfo--클래스가-특정-클래스의-하위-클래스인지-확인">✅ <strong>2. issubclass(class, classinfo) — 클래스가 특정 클래스의 하위 클래스인지 확인</strong></h2>
<h3 id="📌-개념-1">📌 <strong>개념</strong></h3>
<ul>
<li><strong><code>issubclass(클래스, 클래스)</code></strong></li>
<li>클래스가 다른 클래스의 <strong>하위 클래스인지</strong> 검사.</li>
</ul>
<h3 id="🧩-기본-예제-1">🧩 <strong>기본 예제</strong></h3>
<pre><code class="language-python">class Animal: pass
class Dog(Animal): pass

print(issubclass(Dog, Animal))   # ✅ True
print(issubclass(Animal, Dog))   # ❌ False
print(issubclass(Dog, object))   # ✅ True (모든 클래스는 object 하위)</code></pre>
<h3 id="✅-특징-1">✅ <strong>특징</strong></h3>
<ul>
<li><strong>클래스 간 상속 관계</strong>를 확인.</li>
<li><strong>첫 번째 인자는 반드시 클래스</strong>여야 한다.</li>
</ul>
<hr>
<h2 id="✅-3-두-함수의-차이">✅ <strong>3. 두 함수의 차이</strong></h2>
<table>
<thead>
<tr>
<th>구분</th>
<th><code>isinstance</code></th>
<th><code>issubclass</code></th>
</tr>
</thead>
<tbody><tr>
<td><strong>확인 대상</strong></td>
<td><strong>객체</strong> vs 클래스</td>
<td><strong>클래스</strong> vs 클래스</td>
</tr>
<tr>
<td><strong>반환 값</strong></td>
<td><code>True</code> / <code>False</code></td>
<td><code>True</code> / <code>False</code></td>
</tr>
<tr>
<td><strong>상속 관계</strong></td>
<td>하위 클래스 인스턴스도 <code>True</code></td>
<td>하위 클래스이면 <code>True</code></td>
</tr>
<tr>
<td><strong>예시</strong></td>
<td><code>isinstance(dog, Animal)</code> → <code>True</code></td>
<td><code>issubclass(Dog, Animal)</code> → <code>True</code></td>
</tr>
</tbody></table>
<hr>
<h2 id="✅-4-튜플로-여러-클래스-확인-가능">✅ <strong>4. 튜플로 여러 클래스 확인 가능</strong></h2>
<p>두 함수 모두 <strong>두 번째 인자</strong>에 <strong>클래스 튜플</strong>을 전달할 수 있습니다.</p>
<h3 id="🧩-예제">🧩 <strong>예제</strong></h3>
<pre><code class="language-python">class Animal: pass
class Dog(Animal): pass
class Cat(Animal): pass

dog = Dog()

# isinstance
print(isinstance(dog, (Dog, Cat)))    # ✅ True (Dog 포함)

# issubclass
print(issubclass(Dog, (Animal, str))) # ✅ True (Animal 포함)</code></pre>
<hr>
<h2 id="✅-5-실전-사용-시-주의점">✅ <strong>5. 실전 사용 시 주의점</strong></h2>
<ol>
<li><p><code>isinstance</code>는 <strong>객체 확인용</strong></p>
</li>
<li><p><code>issubclass</code>는 <strong>클래스 확인용</strong></p>
</li>
<li><p><code>type(obj) == Class</code> 대신 <code>isinstance(obj, Class)</code> 사용</p>
<ul>
<li>이유: <code>type()</code>은 상속 관계를 고려하지 않음.</li>
</ul>
</li>
</ol>
<pre><code class="language-python">dog = Dog()
print(type(dog) == Animal)      # ❌ False
print(isinstance(dog, Animal))  # ✅ True</code></pre>
<hr>
<h2 id="✅-6-실전-예제-안전한-타입-검사">✅ <strong>6. 실전 예제: 안전한 타입 검사</strong></h2>
<pre><code class="language-python">def process(animal):
    if isinstance(animal, Animal):
        print(&quot;동물 처리 중...&quot;)
    else:
        raise TypeError(&quot;Animal 또는 하위 클래스만 허용됩니다.&quot;)

process(Dog())    # ✅ 동물 처리 중...
process(&quot;cat&quot;)    # ❌ TypeError</code></pre>
<hr>
<h2 id="🎯-정리">🎯 <strong>정리</strong></h2>
<ul>
<li><p>✅ <strong><code>isinstance(obj, Class)</code></strong>
→ <strong>객체가 클래스(또는 하위 클래스) 인스턴스인지 확인</strong></p>
</li>
<li><p>✅ <strong><code>issubclass(Class1, Class2)</code></strong>
→ <strong>Class1이 Class2의 하위 클래스인지 확인</strong></p>
</li>
<li><p>✅ <strong>사용 가이드</strong></p>
<ul>
<li><strong>객체 → isinstance</strong></li>
<li><strong>클래스 → issubclass</strong></li>
</ul>
</li>
</ul>
<hr>
<h2 id="💡-마무리">💡 <strong>마무리</strong></h2>
<p><code>isinstance</code>와 <code>issubclass</code>는 <strong>비슷하지만 용도가 다른 타입 검사 도구</strong>입니다.</p>
<ul>
<li><strong>객체의 타입 확인</strong> → <code>isinstance</code></li>
<li><strong>클래스 간 상속 관계 확인</strong> → <code>issubclass</code></li>
</ul>
<p>이제 타입 검사 시 <strong>언제 어떤 함수를 써야 할지 명확해졌을 것</strong>입니다.</p>
<hr>
]]></description>
        </item>
        <item>
            <title><![CDATA[logging보다 직관적인 디버깅? repr로 해결!]]></title>
            <link>https://velog.io/@cha-suyeon/logging%EB%B3%B4%EB%8B%A4-%EC%A7%81%EA%B4%80%EC%A0%81%EC%9D%B8-%EB%94%94%EB%B2%84%EA%B9%85-repr%EB%A1%9C-%ED%95%B4%EA%B2%B0-4h6xolg0</link>
            <guid>https://velog.io/@cha-suyeon/logging%EB%B3%B4%EB%8B%A4-%EC%A7%81%EA%B4%80%EC%A0%81%EC%9D%B8-%EB%94%94%EB%B2%84%EA%B9%85-repr%EB%A1%9C-%ED%95%B4%EA%B2%B0-4h6xolg0</guid>
            <pubDate>Tue, 05 Aug 2025 07:14:09 GMT</pubDate>
            <description><![CDATA[<p>아래는 <strong>「파이썬 코딩의 기술 (Effective Python)」 2판의 아이템 75</strong> <strong>&quot;디버깅 출력에는 repr 문자열을 사용하라&quot;</strong> 내용을 초보자도 쉽게 이해할 수 있도록 <strong>개념 → 코드 분석 → logging과의 차이 → 정리</strong> 순으로 꼼꼼하게 설명한 정리입니다.</p>
<hr>
<h2 id="✅-1-아이템-75-핵심-아이디어">✅ <strong>1. 아이템 75 핵심 아이디어</strong></h2>
<ul>
<li><p><strong>디버깅 시 print() 대신 repr()을 활용하면 유용하다.</strong></p>
</li>
<li><p>이유:</p>
<ul>
<li><code>repr()</code>은 <strong>객체를 개발자 친화적으로</strong> 표현</li>
<li><code>str()</code>보다 <strong>더 많은 정보</strong>를 제공</li>
<li><code>eval(repr(obj))</code>로 원본 객체를 복원할 수 있도록 설계되는 경우가 많다</li>
</ul>
</li>
<li><p>또한, <strong>클래스에 <code>__repr__</code>을 구현</strong>하면, 객체의 디버깅 출력이 <strong>훨씬 가독성 있게</strong> 바뀐다.</p>
</li>
</ul>
<hr>
<h2 id="✅-2-str-vs-repr-차이">✅ <strong>2. str vs repr 차이</strong></h2>
<table>
<thead>
<tr>
<th>함수</th>
<th>목적</th>
<th>출력 특징</th>
</tr>
</thead>
<tbody><tr>
<td><strong><code>str(obj)</code></strong></td>
<td>사용자 친화적, 읽기 쉽게</td>
<td>사람이 보기 좋은 문자열</td>
</tr>
<tr>
<td><strong><code>repr(obj)</code></strong></td>
<td>개발자 친화적, 디버깅용</td>
<td><strong>객체를 다시 만들 수 있는 표현</strong>을 목표</td>
</tr>
<tr>
<td><strong><code>print(obj)</code></strong></td>
<td>내부적으로 <code>str(obj)</code> 호출</td>
<td>기본은 사용자 친화적 출력</td>
</tr>
</tbody></table>
<hr>
<h2 id="🧩-3-코드-예제-분석-제공-코드-기반">🧩 <strong>3. 코드 예제 분석 (제공 코드 기반)</strong></h2>
<h3 id="🔹-기본-print-사용">🔹 <strong>기본 print() 사용</strong></h3>
<pre><code class="language-python">print(&#39;foo 뭐시기&#39;)</code></pre>
<ul>
<li>단순 출력, 사람이 보기 좋게 문자열 그대로 표시.</li>
</ul>
<hr>
<h3 id="🔹-str와-다양한-출력-방식-비교">🔹 <strong>str()와 다양한 출력 방식 비교</strong></h3>
<pre><code class="language-python">my_value = &#39;foo 뭐시기&#39;
print(str(my_value))          # foo 뭐시기
print(&#39;%s&#39; % my_value)        # foo 뭐시기
print(f&#39;{my_value}&#39;)          # foo 뭐시기
print(format(my_value))       # foo 뭐시기
print(my_value.__str__())     # foo 뭐시기</code></pre>
<ul>
<li>모두 <strong>사람이 읽기 좋게</strong> 문자열을 그대로 출력.</li>
<li><strong>따라서 디버깅 시에는 부족</strong>하다 (특수 문자, 공백, 이스케이프 정보가 숨겨짐).</li>
</ul>
<hr>
<h3 id="🔹-repr-사용">🔹 <strong>repr() 사용</strong></h3>
<pre><code class="language-python">a = &#39;\x07&#39;
print(repr(a))   # &#39;\x07&#39;</code></pre>
<ul>
<li><strong>repr()은 이스케이프 문자까지 그대로 보여줌</strong> → 디버깅에 유리.</li>
</ul>
<hr>
<h3 id="🔹-repr-→-eval-복원-가능">🔹 <strong>repr() → eval() 복원 가능</strong></h3>
<pre><code class="language-python">b = eval(repr(a))
assert a == b</code></pre>
<ul>
<li><code>repr()</code> 결과를 <code>eval()</code> 하면 <strong>원본 객체 복원</strong> 가능(가능하도록 설계된 경우).</li>
</ul>
<hr>
<h3 id="🔹-숫자와-문자열-비교-시-repr">🔹 <strong>숫자와 문자열 비교 시 repr</strong></h3>
<pre><code class="language-python">int_value = 5
str_value = &#39;5&#39;
print(f&#39;{int_value!r} != {str_value!r}&#39;)  # 5 != &#39;5&#39;</code></pre>
<ul>
<li><code>!r</code> 사용 → f-string에서도 <strong>repr 출력</strong> 강제.</li>
</ul>
<hr>
<h3 id="🔹-클래스에서-repr이-없는-경우">🔹 <strong>클래스에서 repr이 없는 경우</strong></h3>
<pre><code class="language-python">class OpaqueClass:
    def __init__(self, x, y):
        self.x = x
        self.y = y

obj = OpaqueClass(1, &#39;foo&#39;)
print(obj)  
# &lt;__main__.OpaqueClass object at 0x7f3e2c9d5d60&gt;</code></pre>
<ul>
<li><strong>기본 출력은 메모리 주소만 보여줌</strong> → 디버깅 시 유용하지 않음.</li>
</ul>
<hr>
<h3 id="🔹-클래스에서-repr-구현">🔹 <strong>클래스에서 **repr</strong> 구현**</h3>
<pre><code class="language-python">class BetterClass:
    def __init__(self, x, y):
        self.x = x
        self.y = y

    def __repr__(self):
        return f&#39;BetterClass({self.x!r}, {self.y!r})&#39;

obj = BetterClass(2, &#39;뭐시기&#39;)
print(obj)  
# BetterClass(2, &#39;뭐시기&#39;)</code></pre>
<ul>
<li><strong><code>__repr__</code>을 정의하면 디버깅 출력이 더 명확</strong>해짐.</li>
<li>속성 값도 명시적으로 확인 가능.</li>
</ul>
<hr>
<h3 id="🔹-객체-내부-확인-dict">🔹 <strong>객체 내부 확인 (dict)</strong></h3>
<pre><code class="language-python">obj = OpaqueClass(4, &#39;baz&#39;)
print(obj.__dict__)  
# {&#39;x&#39;: 4, &#39;y&#39;: &#39;baz&#39;}</code></pre>
<ul>
<li><code>__dict__</code>로 내부 상태 확인 가능하지만, 매번 수동으로 보기 불편.</li>
<li><strong>따라서 __repr__을 잘 구현하면 훨씬 편리</strong>.</li>
</ul>
<hr>
<h2 id="✅-4-logging보다-repr이-나은-이유">✅ <strong>4. logging보다 repr이 나은 이유?</strong></h2>
<p>많은 초보자가 <strong>&quot;디버깅에는 logging을 쓰면 되지 않나?&quot;</strong> 라고 생각합니다.
하지만 <strong>아이템 75에서 강조하는 부분은 ‘디버깅 출력의 형식’</strong>입니다.</p>
<h3 id="🔹-logging과-repr-비교">🔹 <strong>logging과 repr 비교</strong></h3>
<table>
<thead>
<tr>
<th>구분</th>
<th>logging</th>
<th>repr</th>
</tr>
</thead>
<tbody><tr>
<td><strong>목적</strong></td>
<td>로그 기록 (파일, 콘솔 등)</td>
<td>객체를 개발자 친화적으로 출력</td>
</tr>
<tr>
<td><strong>출력 포맷</strong></td>
<td>사용자가 지정한 메시지 형식</td>
<td>객체의 상태를 <strong>자동</strong>으로 자세히 표시</td>
</tr>
<tr>
<td><strong>사용 난이도</strong></td>
<td>logging 설정 필요 (레벨, 핸들러 등)</td>
<td><code>print(repr(obj))</code> 즉시 사용 가능</td>
</tr>
<tr>
<td><strong>디버깅 정보</strong></td>
<td>기본적으로 str() 사용 → 상세 정보 부족</td>
<td>이스케이프, 타입, 내부 표현까지 표시</td>
</tr>
</tbody></table>
<h3 id="✅-왜-repr이-더-낫나">✅ <strong>왜 repr이 더 낫나?</strong></h3>
<ul>
<li><strong>간단한 디버깅</strong>에서는 <strong>logging보다 repr 출력이 즉시 도움이 됨</strong></li>
<li><strong>repr은 객체 내부 상태를 더 많이 보여줌</strong></li>
<li><strong>클래스에서 <code>__repr__</code> 구현 시</strong> 디버깅 출력이 logging보다 직관적</li>
</ul>
<blockquote>
<p>📝 <strong>결론</strong>: logging은 <strong>장기 로그 관리용</strong>, repr은 <strong>디버깅 순간에 강력</strong>.</p>
</blockquote>
<hr>
<h2 id="✅-5-정리">✅ <strong>5. 정리</strong></h2>
<ol>
<li><strong>디버깅할 때는 <code>print(obj)</code> 대신 <code>print(repr(obj))</code> 사용</strong></li>
<li><strong>f-string에서는 <code>{obj!r}</code> 사용</strong> → repr 출력 강제</li>
<li><strong>클래스에 <code>__repr__</code>을 구현</strong>하면 print만으로도 강력한 디버깅 가능</li>
<li><strong>logging은 장기 추적용</strong>, <strong>repr은 즉시 확인용</strong></li>
<li><strong>특수 문자, 공백, 타입 구분이 필요한 경우 repr 필수</strong></li>
</ol>
<hr>
<h2 id="🎯-아이템-75-한-줄-요약">🎯 <strong>아이템 75 한 줄 요약</strong></h2>
<blockquote>
<p><strong>&quot;디버깅할 때는 str이 아닌 repr을 사용하라.
객체의 내부 상태를 더 잘 파악할 수 있고, 클래스에 <code>__repr__</code>을 구현하면 print만으로도 훌륭한 디버깅이 된다.&quot;</strong></p>
</blockquote>
]]></description>
        </item>
    </channel>
</rss>