<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>취미생활</title>
    <link>https://barisein.tistory.com/</link>
    <description>Windows, Kubernetes, Elasticsearch, 개발 환경에서 겪은 문제와 해결 과정을 정리하는 기술 블로그입니다.</description>
    <language>ko</language>
    <pubDate>Thu, 6 Aug 2026 01:38:06 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>바리새인</managingEditor>
    <item>
      <title>AI 시대의 소프트웨어 개발 방법론: 요구사항 정의 편</title>
      <link>https://barisein.tistory.com/1334</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이전 글에서는 사람 주도 AI 개발의 전체 흐름을 정리했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;핵심은 간단했다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AI가 빠르게 달릴 수 있도록, 사람이 방향과 기준을 세운다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번 글에서는 그 첫 번째 실무 단계인 &lt;b&gt;요구사항 정의&lt;/b&gt;를 다룬다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AI와 함께 개발할 때 요구사항은 단순한 기능 목록이 아니다.&lt;br /&gt;AI가 무엇을 만들어야 하는지, 어디까지 만들어야 하는지, 무엇을 만들면 안 되는지를 판단하는 기준이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;요구사항이 흔들리면 구현도 흔들린다.&lt;br /&gt;반대로 요구사항이 잘 정리되어 있으면 AI는 훨씬 안정적으로 작업한다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;왜 요구사항 정의가 더 중요해졌는가&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AI 개발 도구를 쓰면 구현 속도는 빠르다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예전에는 요구사항이 조금 애매해도 구현 과정에서 시간이 걸렸기 때문에 중간에 생각을 정리할 여지가 있었다.&lt;br /&gt;하지만 AI는 다르다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;요구사항이 애매한 상태에서도 빠르게 코드를 만든다.&lt;br /&gt;문제는 그 코드가 &amp;ldquo;내가 원한 것&amp;rdquo;인지, &amp;ldquo;AI가 추측한 것&amp;rdquo;인지 구분하기 어렵다는 점이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자주 생기는 문제는 다음과 같다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;처음 말한 의도와 다른 기능이 구현된다.&lt;/li&gt;
&lt;li&gt;대화 중 나온 결정이 다음 작업에서 사라진다.&lt;/li&gt;
&lt;li&gt;비슷한 기능이 중복으로 만들어진다.&lt;/li&gt;
&lt;li&gt;MVP에 들어가지 않아야 할 기능이 섞인다.&lt;/li&gt;
&lt;li&gt;수정할수록 전체 기준이 흐려진다.&lt;/li&gt;
&lt;li&gt;검증할 때 무엇이 맞는지 판단하기 어렵다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;결국 요구사항 정의는 AI에게 일을 시키기 위한 문장이 아니라,&lt;br /&gt;사람과 AI가 같은 기준으로 작업하기 위한 계약에 가깝다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;요구사항은 기능 목록이 아니다&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;요구사항을 적는다고 하면 보통 이런 식으로 시작한다.&lt;/p&gt;
&lt;pre class=&quot;asciidoc&quot;&gt;&lt;code&gt;- 로그인 기능
- 게시글 작성 기능
- 검색 기능
- 관리자 화면&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이런 목록도 출발점으로는 괜찮다.&lt;br /&gt;하지만 AI와 협업하기에는 부족하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;왜냐하면 각각의 항목이 너무 많은 해석을 허용하기 때문이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어 &amp;ldquo;검색 기능&amp;rdquo;이라고만 쓰면 AI는 여러 가지를 추측해야 한다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;제목만 검색하는가?&lt;/li&gt;
&lt;li&gt;본문도 검색하는가?&lt;/li&gt;
&lt;li&gt;태그도 검색하는가?&lt;/li&gt;
&lt;li&gt;부분 일치를 허용하는가?&lt;/li&gt;
&lt;li&gt;대소문자를 구분하는가?&lt;/li&gt;
&lt;li&gt;삭제된 항목도 검색 대상인가?&lt;/li&gt;
&lt;li&gt;검색 결과는 어디에 표시하는가?&lt;/li&gt;
&lt;li&gt;검색 조건을 초기화할 수 있는가?&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사람은 머릿속으로 대충 알고 있다고 생각하지만, AI는 그 기준을 모른다.&lt;br /&gt;그래서 요구사항은 기능 이름보다 조금 더 구체적이어야 한다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;좋은 요구사항의 기본 형태&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;내가 사용하는 기본 형태는 다음과 같다.&lt;/p&gt;
&lt;pre class=&quot;nginx&quot;&gt;&lt;code&gt;ID
제목
설명
단계
우선순위
유형
상태&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들면 이렇게 쓴다.&lt;/p&gt;
&lt;pre class=&quot;makefile&quot;&gt;&lt;code&gt;ID: REQ-001
제목: 요구사항 등록
설명: 사용자는 화면에서 새 요구사항을 등록할 수 있어야 한다.
단계: MVP
우선순위: high
유형: feature
상태: approved&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 중요한 것은 문장을 복잡하게 쓰는 것이 아니다.&lt;br /&gt;각 요구사항이 독립적으로 추적 가능해야 한다는 점이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;특히 ID가 중요하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;요구사항에 ID가 없으면 나중에 이런 대화가 된다.&lt;/p&gt;
&lt;pre class=&quot;erlang&quot;&gt;&lt;code&gt;검색 기능 수정해줘.
아니, 그 검색 말고 왼쪽 목록에 있는 검색.
아니, 삭제된 항목 포함할 때 검색되는 그 부분.&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;반면 ID가 있으면 이렇게 말할 수 있다.&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;REQ-031의 검색 대상에 태그를 포함해줘.
REQ-022B 검증에서 그래프 제외 조건도 확인해줘.
REQ-035C는 원본 파일 삭제가 아니라 등록 삭제만 해야 해.&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AI에게도 훨씬 명확하고, 사람도 변경 이력을 따라가기 쉽다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;요구사항 ID를 붙이는 이유&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;요구사항 ID는 단순한 번호가 아니다.&lt;br /&gt;개발 전체를 연결하는 기준점이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;요구사항 ID가 있으면 다음을 연결할 수 있다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;요구사항&lt;/li&gt;
&lt;li&gt;분석 내용&lt;/li&gt;
&lt;li&gt;설계 요소&lt;/li&gt;
&lt;li&gt;API&lt;/li&gt;
&lt;li&gt;UI 화면&lt;/li&gt;
&lt;li&gt;테스트&lt;/li&gt;
&lt;li&gt;검증 결과&lt;/li&gt;
&lt;li&gt;변경 로그&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어 다음처럼 추적할 수 있다.&lt;/p&gt;
&lt;pre class=&quot;livescript&quot;&gt;&lt;code&gt;REQ-035 프로젝트 등록과 등록 해제
-&amp;gt; API: POST /api/projects, DELETE /api/projects/{id}
-&amp;gt; UI: 프로젝트 관리 패널
-&amp;gt; 테스트: 프로젝트 등록/삭제 테스트
-&amp;gt; 검증: api-smoke-tested, pending-browser-test&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 되면 &amp;ldquo;기능을 만들었다&amp;rdquo;에서 끝나지 않는다.&lt;br /&gt;어떤 요구사항이 어디에 반영되었고, 무엇이 아직 검증되지 않았는지 알 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AI와 협업할수록 이 추적성이 중요해진다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AI는 코드를 빠르게 바꾸지만, 바뀐 코드가 어떤 요구사항을 만족하는지 스스로 안정적으로 관리하지 못한다.&lt;br /&gt;그 연결은 사람이 기준을 만들고, AI가 문서와 테스트로 보조하게 해야 한다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;요구사항은 단계로 나눈다&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;모든 요구사항을 한 번에 만들려고 하면 프로젝트가 쉽게 커진다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 요구사항에는 단계를 붙인다.&lt;/p&gt;
&lt;pre class=&quot;fortran&quot;&gt;&lt;code&gt;MVP
Future
Optional
Out of Scope&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;나는 보통 이렇게 구분한다.&lt;/p&gt;
&lt;div&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;단계&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; 의미&lt;span style=&quot;letter-spacing: 0px;&quot;&gt;&amp;nbsp;&amp;nbsp;&lt;/span&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;MVP&lt;/td&gt;
&lt;td&gt;첫 버전에 반드시 들어갈 것&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Future&lt;/td&gt;
&lt;td&gt;필요하지만 첫 버전에서는 제외할 것&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Optional&lt;/td&gt;
&lt;td&gt;있으면 좋지만 필수는 아닌 것&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Out of Scope&lt;/td&gt;
&lt;td&gt;이번 프로젝트에서는 하지 않을 것&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 구분이 없으면 AI는 사용자가 말한 모든 것을 지금 구현하려고 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어 요구사항 관리 도구를 만든다고 할 때, 처음부터 다음 기능을 모두 넣고 싶어질 수 있다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;요구사항 CRUD&lt;/li&gt;
&lt;li&gt;관계 관리&lt;/li&gt;
&lt;li&gt;그래프 시각화&lt;/li&gt;
&lt;li&gt;Mermaid 다이어그램&lt;/li&gt;
&lt;li&gt;사용자 계정&lt;/li&gt;
&lt;li&gt;권한 관리&lt;/li&gt;
&lt;li&gt;DB 저장&lt;/li&gt;
&lt;li&gt;변경 이력&lt;/li&gt;
&lt;li&gt;알림&lt;/li&gt;
&lt;li&gt;외부 프로젝트 연동&lt;/li&gt;
&lt;li&gt;배포 자동화&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 첫 버전에서 중요한 것은 &amp;ldquo;작동하는 핵심 흐름&amp;rdquo;이다.&lt;br /&gt;그래서 MVP를 정해야 한다.&lt;/p&gt;
&lt;pre class=&quot;asciidoc&quot;&gt;&lt;code&gt;MVP:
- 요구사항 등록, 수정, 삭제
- 요구사항 관계 등록, 수정, 삭제
- 요구사항 그래프 조회
- 기본 검증

Future:
- 사용자 계정
- 권한 관리
- RDBMS 전환
- 저장형 보기&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 나누면 AI에게도 명확하게 말할 수 있다.&lt;/p&gt;
&lt;pre class=&quot;erlang&quot;&gt;&lt;code&gt;이번 구현에서는 MVP 항목만 반영하고, Future 항목은 문서에만 남겨줘.&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 한 문장이 프로젝트 범위를 지켜준다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;상위 요구사항과 세부 요구사항을 나눈다&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;요구사항은 너무 크면 구현하기 어렵고, 너무 작으면 관리하기 어렵다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 상위 요구사항과 세부 요구사항을 나누는 방식이 유용하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어 다음과 같이 나눌 수 있다.&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;REQ-035 프로젝트 등록과 등록 해제
REQ-035A 프로젝트 등록 입력
REQ-035B 프로젝트 등록 검증
REQ-035C 외부 프로젝트 등록 삭제
REQ-035D 기본 프로젝트 표시 설정
REQ-035E 프로젝트 루트 선택과 등록값 추천&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;상위 요구사항은 사용자의 목적을 표현한다.&lt;br /&gt;세부 요구사항은 구현과 검증이 가능한 단위로 나눈다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 구조가 있으면 AI에게 일을 나누어 맡기기 좋다.&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;REQ-035A부터 REQ-035C까지만 구현해줘.
REQ-035E는 보류하고 Future로 남겨줘.
REQ-035 전체가 설계와 추적표에 연결되어 있는지 검증해줘.&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;요구사항을 이렇게 나누면 기능 추가도 깔끔해진다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;처음에는 프로젝트 ID와 경로를 직접 입력하는 방식만 있었다가, 나중에 &amp;ldquo;디렉터리 선택과 자동 추천도 있으면 좋겠다&amp;rdquo;는 생각이 들 수 있다.&lt;br /&gt;그때 기존 요구사항을 애매하게 고치는 대신 REQ-035E처럼 세부 요구사항을 추가하면 된다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;요구사항에는 하지 않을 것도 적어야 한다&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;요구사항 정의에서 자주 빠지는 것이 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;바로 &amp;ldquo;하지 않을 것&amp;rdquo;이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사람은 암묵적으로 알고 있다고 생각하지만, AI는 그렇지 않다.&lt;br /&gt;말하지 않으면 AI는 필요하다고 판단한 기능을 추가할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어 프로젝트 등록/삭제 기능을 만든다고 할 때, &amp;ldquo;삭제&amp;rdquo;라는 말은 위험하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;삭제가 의미하는 것이 무엇인지 분명히 해야 한다.&lt;/p&gt;
&lt;pre class=&quot;erlang&quot;&gt;&lt;code&gt;사용자는 등록된 외부 프로젝트를 목록에서 삭제할 수 있어야 한다.
삭제는 원본 프로젝트 파일을 삭제하지 않아야 한다.&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 써야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그렇지 않으면 AI가 실제 디렉터리를 삭제하는 기능으로 해석할 여지가 생긴다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;요구사항에서는 특히 다음을 명확히 해야 한다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;삭제가 실제 삭제인지 등록 해제인지&lt;/li&gt;
&lt;li&gt;읽기 전용인지 쓰기 가능한지&lt;/li&gt;
&lt;li&gt;기본값은 무엇인지&lt;/li&gt;
&lt;li&gt;오류 상황에서는 어떻게 해야 하는지&lt;/li&gt;
&lt;li&gt;이번 버전에서 제외할 기능은 무엇인지&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AI와 협업할 때는 애매함이 곧 버그가 된다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;요구사항 관계도 관리한다&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;요구사항은 서로 독립적이지 않다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;어떤 요구사항은 다른 요구사항을 포함하고, 어떤 요구사항은 더 작은 요구사항으로 분해된다.&lt;br /&gt;또 어떤 요구사항은 서로 충돌하거나, 하나가 바뀌면 다른 것도 영향을 받는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 관계를 기록하는 것이 좋다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들면 다음과 같다.&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;REQ-034 includes REQ-035
REQ-035 breaks_down_to REQ-035A
REQ-035 breaks_down_to REQ-035B
REQ-035 breaks_down_to REQ-035C&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 하면 &amp;ldquo;여러 프로젝트 조회&amp;rdquo;라는 요구사항 안에 &amp;ldquo;프로젝트 등록과 등록 해제&amp;rdquo;가 포함된다는 것을 알 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;관계가 있으면 변경 영향도 확인하기 쉽다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어 REQ-035를 수정하면 그 아래의 REQ-035A부터 REQ-035E까지 같이 봐야 한다.&lt;br /&gt;반대로 REQ-035E만 수정한다면 디렉터리 선택과 추천 기능만 집중해서 보면 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AI에게도 이런 식으로 요청할 수 있다.&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;REQ-035E와 직접 연결된 요구사항만 보고 영향 범위를 검토해줘.&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이것이 가능하려면 요구사항 관계가 문서나 데이터로 남아 있어야 한다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;요구사항 문서와 로그를 분리한다&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;요구사항을 정리하다 보면 대화 내용, 결정 이유, 변경 이력이 함께 섞이기 쉽다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 현재 기준과 과거 대화는 분리하는 것이 좋다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;나는 보통 이렇게 나눈다.&lt;/p&gt;
&lt;pre class=&quot;dos&quot;&gt;&lt;code&gt;docs/requirements.md
logs/conversation-log.md&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;requirements.md에는 현재 기준만 둔다.&lt;br /&gt;즉, 지금 이 프로젝트가 만족해야 하는 요구사항을 정리한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;반면 conversation-log.md에는 왜 그렇게 결정했는지 남긴다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들면 다음과 같다.&lt;/p&gt;
&lt;pre class=&quot;erlang&quot;&gt;&lt;code&gt;requirements.md:
REQ-035C 사용자는 등록된 외부 프로젝트를 목록에서 삭제할 수 있어야 하며, 삭제는 원본 프로젝트 파일을 삭제하지 않아야 한다.

conversation-log.md:
사용자가 다른 PC에서 외부 프로젝트를 확인하는 흐름을 요청했다.
프로젝트 삭제는 원본 파일 삭제가 아니라 로컬 설정에서 등록만 제거하는 의미로 결정했다.&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 나누면 나중에 AI에게 다시 작업을 맡길 때도 기준이 흔들리지 않는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AI에게는 현재 기준을 먼저 읽게 하고, 필요할 때 로그를 참고하게 하면 된다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;요구사항 정의 단계에서 AI에게 시킬 일&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;요구사항 정의는 사람이 다 써야 하는 작업이 아니다.&lt;br /&gt;AI를 잘 활용하면 빠르게 초안을 만들고 누락을 찾을 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다만 최종 판단은 사람이 해야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AI에게 시키기 좋은 일은 다음과 같다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;대화 내용을 요구사항 목록으로 정리하기&lt;/li&gt;
&lt;li&gt;요구사항에 ID 붙이기&lt;/li&gt;
&lt;li&gt;상위 요구사항과 세부 요구사항 나누기&lt;/li&gt;
&lt;li&gt;MVP와 Future 구분 제안하기&lt;/li&gt;
&lt;li&gt;중복 요구사항 찾기&lt;/li&gt;
&lt;li&gt;애매한 표현 찾기&lt;/li&gt;
&lt;li&gt;검증 가능한 문장으로 바꾸기&lt;/li&gt;
&lt;li&gt;요구사항 간 관계 정리하기&lt;/li&gt;
&lt;li&gt;추적표 초안 만들기&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어 이렇게 요청할 수 있다.&lt;/p&gt;
&lt;pre class=&quot;erlang&quot;&gt;&lt;code&gt;지금까지 대화 내용을 기준으로 요구사항을 정리해줘.
각 요구사항에는 ID, 제목, 설명, 단계, 우선순위를 붙여줘.
MVP와 Future를 구분하고, 애매한 요구사항은 질문 목록으로 따로 빼줘.&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또는 이렇게 요청할 수도 있다.&lt;/p&gt;
&lt;pre class=&quot;erlang&quot;&gt;&lt;code&gt;REQ-035를 세부 요구사항으로 분해해줘.
각 세부 요구사항은 구현과 검증이 가능한 단위로 나눠줘.
삭제 기능은 원본 파일 삭제가 아니라 등록 해제라는 점을 명확히 반영해줘.&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AI는 이런 정리 작업에 강하다.&lt;br /&gt;하지만 AI가 제안한 요구사항을 그대로 확정하면 안 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사람이 봐야 할 것은 이것이다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;정말 필요한가?&lt;/li&gt;
&lt;li&gt;이번 버전에 들어가야 하는가?&lt;/li&gt;
&lt;li&gt;사용자의 목적과 맞는가?&lt;/li&gt;
&lt;li&gt;너무 크거나 작지 않은가?&lt;/li&gt;
&lt;li&gt;검증 가능한가?&lt;/li&gt;
&lt;li&gt;빠진 예외 상황은 없는가?&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;요구사항 정의 체크리스트&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;요구사항을 작성한 뒤에는 다음을 확인한다.&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;1. 각 요구사항에 ID가 있는가?
2. 요구사항이 사용자의 행동이나 시스템 책임으로 표현되어 있는가?
3. MVP와 Future가 구분되어 있는가?
4. 삭제, 읽기 전용, 오류 처리처럼 위험한 동작이 명확한가?
5. 상위 요구사항과 세부 요구사항이 연결되어 있는가?
6. 중복되거나 서로 충돌하는 요구사항이 없는가?
7. 구현 가능한 단위로 나뉘어 있는가?
8. 검증 기준을 만들 수 있는가?
9. 현재 기준 문서와 대화 로그가 분리되어 있는가?
10. AI가 다음 세션에서 읽어도 같은 의미로 이해할 수 있는가?&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 체크리스트를 통과하지 못하면 바로 구현으로 가면 안 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AI는 빠르게 만들 수 있기 때문에, 잘못된 요구사항도 빠르게 구현해버린다.&lt;br /&gt;그래서 구현 전에 요구사항을 한 번 멈춰서 보는 과정이 필요하다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;요구사항 정의의 목표&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;요구사항 정의의 목표는 완벽한 문서를 만드는 것이 아니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;목표는 이것이다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AI와 사람이 같은 기준으로 개발을 이어갈 수 있게 만드는 것.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;요구사항은 길 필요가 없다.&lt;br /&gt;작은 프로젝트라면 짧아도 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 다음은 반드시 있어야 한다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;무엇을 만들 것인가&lt;/li&gt;
&lt;li&gt;왜 필요한가&lt;/li&gt;
&lt;li&gt;어디까지가 이번 버전인가&lt;/li&gt;
&lt;li&gt;무엇은 하지 않을 것인가&lt;/li&gt;
&lt;li&gt;어떻게 검증할 것인가&lt;/li&gt;
&lt;li&gt;변경되면 어디에 기록할 것인가&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 기준이 있으면 AI는 단순히 코드를 생성하는 도구가 아니라, 요구사항을 따라 움직이는 작업 에이전트가 된다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;다음 글&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음 글에서는 요구사항을 바탕으로 &lt;b&gt;분석 단계&lt;/b&gt;를 다룰 예정이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;요구사항을 단순 목록으로 두는 것이 아니라,&lt;br /&gt;어떤 문제 구조인지, 어떤 개념이 반복되는지, 어떤 관계와 충돌이 있는지를 정리하는 방법을 살펴보겠다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;요구사항이 &amp;ldquo;무엇을 만들 것인가&amp;rdquo;를 정리하는 단계라면,&lt;br /&gt;분석은 &amp;ldquo;이 문제를 어떻게 이해할 것인가&amp;rdquo;를 정리하는 단계다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;참고한 원문: &lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;a href=&quot;https://barisein.tistory.com/1326&quot;&gt;https://barisein.tistory.com/1326&lt;/a&gt;&lt;/p&gt;</description>
      <category>AI</category>
      <category>ai개발</category>
      <category>AI협업</category>
      <category>MVP</category>
      <category>개발문서화</category>
      <category>검증방법론</category>
      <category>사람주도AI개발</category>
      <category>소프트웨어개발방법론</category>
      <category>소프트웨어설계</category>
      <category>요구사항관리</category>
      <category>요구사항정의</category>
      <author>바리새인</author>
      <guid isPermaLink="true">https://barisein.tistory.com/1334</guid>
      <comments>https://barisein.tistory.com/1334#entry1334comment</comments>
      <pubDate>Tue, 4 Aug 2026 13:14:58 +0900</pubDate>
    </item>
    <item>
      <title>바이브 코딩 교육 2편: 사용자의 한 마디가 어떻게 구체적인 스펙이 되는가</title>
      <link>https://barisein.tistory.com/1333</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;목차&lt;/h2&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;들어가며&lt;/li&gt;
&lt;li&gt;실제 사용자가 입력한 프롬프트&lt;/li&gt;
&lt;li&gt;이 프롬프트에 빠져 있는 것들&lt;/li&gt;
&lt;li&gt;AI가 빈 곳을 채우는 방식&lt;/li&gt;
&lt;li&gt;Before / After 비교&lt;/li&gt;
&lt;li&gt;왜 AI가 대신 채워도 괜찮은가&lt;/li&gt;
&lt;li&gt;정리&lt;/li&gt;
&lt;/ol&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;들어가며&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;지난 글([바이브 코딩 교육: 좋은 프롬프트로 Python 샘플 프로젝트 설계하기])에서는 이미 완성된 형태의 프롬프트, 즉 환경변수 이름, 오류 코드, 테스트 항목까지 다 정해진 프롬프트를 예시로 다뤘습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그런데 실제로 바이브 코딩을 처음 하는 사람은 그렇게 완벽한 프롬프트를 처음부터 쓰지 않습니다. 보통은 &quot;이런 흐름으로 만들어줬으면 좋겠다&quot;는 자연스러운 요청에서 시작합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번 글에서는 &lt;b&gt;일반 사용자가 실제로 입력할 법한 프롬프트&lt;/b&gt;를 시작점으로 놓고, 이 프롬프트가 AI와의 상호작용을 통해 어떻게 지난 글의 구체적인 스펙으로 발전하는지를 살펴봅니다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;실제 사용자가 입력한 프롬프트&lt;/h2&gt;
&lt;pre class=&quot;erlang&quot;&gt;&lt;code&gt;공공데이터 API를 연동하는 Python 샘플 프로젝트를 만들어줘.
내가 쓰려는 API는 공공데이터포털의 기상청 단기예보 조회서비스야.
API 인증키는 .env에 넣어서 관리하고, 코드에는 노출되지 않게 해줘.

샘플에서 보여주고 싶은 흐름은 이거야.
1. 기상청 단기예보 API를 호출한다.
2. 조회 결과를 콘솔에 보기 좋게 출력한다.
3. 결과를 로컬 JSON 파일로 저장한다.
4. 필요하면 Gmail로 조회 결과를 공유한다.
5. Gmail 메일은 원본 JSON만 첨부하지 말고, 받는 사람이
   바로 이해할 수 있게 HTML 요약 본문으로 보내준다.
6. 원본 JSON 첨부는 옵션으로 켜고 끌 수 있게 해준다.
7. 주기적으로 다시 조회하는 예시도 있으면 좋겠다.
8. 기본적인 오류 처리와 테스트 코드도 같이 만들어줘.

나는 UI는 필요 없고, 소스 코드를 통해 공공데이터 API를
어떻게 호출하고 사용하는지 보여주고 싶어.
기술 스택은 Python으로 해줘.

필요한 라이브러리, 실행 방법, .env 예시, 테스트 방법까지
README에 정리해줘.
공공데이터 API 호출에 필요한 조회 파라미터 기본값은
네가 학습용으로 적절히 정해줘.
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 프롬프트는 문법이나 완성도가 부족한 것이 아니라, &lt;b&gt;오히려 실제 사람이 쓰는 방식에 가깝습니다.&lt;/b&gt; 목적과 원하는 흐름은 분명하지만, 구현에 필요한 세부값은 정해두지 않았습니다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;이 프롬프트에 빠져 있는 것들&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;지난 글의 완성된 프롬프트와 비교하면, 이번 프롬프트에는 다음과 같은 정보가 빠져 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;빠진 정보 완성된 프롬프트에서는&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;API의 정확한 Endpoint 주소&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://apis.data.go.kr/1360000/VilageFcstInfoService_2.0/getVilageFcst&quot;&gt;https://apis.data.go.kr/1360000/VilageFcstInfoService_2.0/getVilageFcst&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;.env 변수 이름 목록&lt;/td&gt;
&lt;td&gt;PUBLIC_DATA_SERVICE_KEY, WEATHER_NX 등 구체적 이름&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;조회 격자(nx, ny) 값&lt;/td&gt;
&lt;td&gt;nx=60, ny=127 (서울시청 인근)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;base_time 자동 선택 로직&lt;/td&gt;
&lt;td&gt;후보 8개 시각, 10분 규칙, 전날 값 사용 조건&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;오류 종류 구분&lt;/td&gt;
&lt;td&gt;API_AUTH_ERROR 등 7가지 코드&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;테스트 항목 목록&lt;/td&gt;
&lt;td&gt;7가지 구체적 테스트 시나리오&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;CLI 옵션 이름&lt;/td&gt;
&lt;td&gt;--send-email, --monitor&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;저장 파일명 규칙&lt;/td&gt;
&lt;td&gt;data/forecast_YYYYMMDD_HHMMSS.json&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;주기 실행 간격 설정 방법&lt;/td&gt;
&lt;td&gt;MONITOR_INTERVAL_SECONDS 환경변수&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 목록을 보면, 사용자가 &lt;b&gt;&quot;무엇을 하고 싶은지&quot;는 다 말했지만 &quot;어떻게 구현할지&quot;는 말하지 않았다&lt;/b&gt;는 것을 알 수 있습니다. 이 간극을 메우는 것이 바이브 코딩에서 AI의 실제 역할입니다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;AI가 빈 곳을 채우는 방식&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AI는 이 빈 곳을 아무렇게나 채우지 않습니다. 사용자가 준 맥락과 API 자체의 특성을 근거로 값을 결정합니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1. Endpoint 주소 확정&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사용자는 &quot;기상청 단기예보 조회서비스&quot;라고만 했습니다. AI는 이 서비스명을 근거로 공공데이터포털에 등록된 실제 Endpoint(VilageFcstInfoService_2.0/getVilageFcst)를 찾아 프롬프트/코드에 반영합니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2. 환경변수 이름 설계&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&quot;.env에 인증키를 넣어줘&quot;라는 요청만으로는 변수 이름이 정해지지 않습니다. AI는 코드 안에서 값의 역할이 드러나도록 이름을 짓습니다.&lt;/p&gt;
&lt;pre class=&quot;properties&quot;&gt;&lt;code&gt;PUBLIC_DATA_SERVICE_KEY   &amp;rarr; 공공데이터 인증키라는 것이 이름에서 드러남
WEATHER_NX / WEATHER_NY   &amp;rarr; 날씨 조회용 격자값임을 구분
GMAIL_APP_PASSWORD        &amp;rarr; 일반 비밀번호가 아니라 앱 비밀번호임을 이름으로 표시
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3. 조회 파라미터 기본값 결정&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사용자가 직접 &quot;학습용으로 적절히 정해줘&quot;라고 위임한 부분입니다. AI는 다음 기준으로 값을 정합니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;nx=60, ny=127: 특정 지역이 언급되지 않았으므로, 예시로 가장 널리 쓰이는 서울시청 인근 격자를 기본값으로 선택&lt;/li&gt;
&lt;li&gt;numOfRows=1000: 단기예보 API는 한 번의 조회에 여러 시간대&amp;middot;항목이 섞여 나오므로, 결과가 잘리지 않도록 충분히 큰 값을 선택&lt;/li&gt;
&lt;li&gt;dataType=JSON: Python에서 다루기 쉬운 형식을 우선 선택(XML도 가능하지만 파싱이 더 복잡함)&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;4. 도메인 로직 설계 (base_time 자동 선택)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사용자는 &quot;최신 데이터를 가져와줘&quot; 같은 말조차 하지 않았습니다. 다만 &quot;조회 결과를 보여준다&quot;는 목적에서, AI는 기상청 단기예보 API가 &lt;b&gt;하루 8회, 특정 시각에만 데이터를 발표&lt;/b&gt;한다는 특성을 반영해 다음 로직을 스스로 설계합니다.&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;발표시각 후보: 0200, 0500, 0800, 1100, 1400, 1700, 2000, 2300
규칙: 각 발표시각 + 10분이 지나야 데이터를 조회할 수 있음
예외: 당일 첫 발표(0200) 전에 실행되면 전날 마지막 발표(2300) 사용
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 부분은 사용자가 전혀 언급하지 않았지만, API를 실제로 정상 동작시키기 위해서는 반드시 필요한 로직입니다. &lt;b&gt;바이브 코딩에서 AI는 사용자가 말하지 않은 &quot;API 자체의 제약&quot;까지 파악해서 로직을 보완하는 역할&lt;/b&gt;을 합니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;5. 오류 처리와 테스트를 구체적 항목으로 분해&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사용자는 &quot;기본적인 오류 처리와 테스트 코드&quot;라고만 했습니다. AI는 이 요청을 구현 가능한 단위로 쪼갭니다.&lt;/p&gt;
&lt;pre class=&quot;jboss-cli&quot;&gt;&lt;code&gt;&quot;오류 처리를 해줘&quot; &amp;rarr; API_AUTH_ERROR, API_TIMEOUT, API_EMPTY_RESULT ... (7종)
&quot;테스트 코드를 만들어줘&quot; &amp;rarr; base_time 선택 테스트, JSON 파싱 테스트,
                          이메일 첨부 옵션별 테스트 ... (7종)
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 나누는 이유는 명확합니다. &quot;오류 처리 해줘&quot;라는 한 문장으로는 코드에 무엇을 작성해야 하는지 알 수 없지만, 오류를 종류별로 나누면 각각 대응하는 if/except 분기와 테스트 함수가 자연스럽게 생깁니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;6. 사용자 경험을 고려한 세부 결정&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사용자는 &quot;HTML 요약 본문으로 보내달라&quot;고만 했지만, AI는 여기에 &quot;받는 사람이 바로 이해할 수 있게&quot;라는 목적을 근거로 본문에 들어갈 구체적인 항목(발표일자, 조회 격자, 항목 수, 저장 시각, 예보 시간별 표)까지 정합니다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Before / After 비교&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;구분 사용자의 원래 요청 AI가 구체화한 결과&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;API 정보&lt;/td&gt;
&lt;td&gt;&quot;기상청 단기예보 조회서비스&quot;&lt;/td&gt;
&lt;td&gt;정확한 Endpoint URL 지정&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;인증키 관리&lt;/td&gt;
&lt;td&gt;&quot;.env에 넣어줘&quot;&lt;/td&gt;
&lt;td&gt;변수명, .env.example, .gitignore 규칙까지 지정&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;조회 파라미터&lt;/td&gt;
&lt;td&gt;&quot;네가 적절히 정해줘&quot;&lt;/td&gt;
&lt;td&gt;nx/ny/numOfRows/dataType 값과 그 이유&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;최신 데이터 조회&lt;/td&gt;
&lt;td&gt;(언급 없음)&lt;/td&gt;
&lt;td&gt;base_time 자동 선택 로직 8단계&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;오류 처리&lt;/td&gt;
&lt;td&gt;&quot;기본적인 오류 처리&quot;&lt;/td&gt;
&lt;td&gt;7개 오류 코드로 세분화&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;테스트&lt;/td&gt;
&lt;td&gt;&quot;테스트 코드도 같이&quot;&lt;/td&gt;
&lt;td&gt;7개 구체적 테스트 시나리오&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;주기 실행&lt;/td&gt;
&lt;td&gt;&quot;예시가 있으면 좋겠다&quot;&lt;/td&gt;
&lt;td&gt;--monitor 옵션과 간격 설정 변수&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;이메일 내용&lt;/td&gt;
&lt;td&gt;&quot;HTML 요약 본문&quot;&lt;/td&gt;
&lt;td&gt;포함할 5가지 항목 구체화&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 놓고 보면, &lt;b&gt;사용자가 한 줄로 요청한 항목이 AI를 거치면서 여러 줄의 구체적인 스펙으로 확장&lt;/b&gt;되는 것을 볼 수 있습니다. 이것이 지난 글에서 다룬 &quot;완성된 프롬프트&quot;가 실제로 만들어지는 과정입니다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;왜 AI가 대신 채워도 괜찮은가&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 한 가지 질문이 생길 수 있습니다. &quot;AI가 알아서 정한 값을 그대로 믿어도 될까?&quot;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;답은 **&quot;검증 가능한 형태로 채우는 한 괜찮다&quot;**입니다. 이 프롬프트에서 AI가 채운 값들은 다음과 같은 공통점이 있습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;값이 코드나 .env.example에 그대로 드러나 있어 나중에 쉽게 바꿀 수 있다 (nx=60, numOfRows=1000 등)&lt;/li&gt;
&lt;li&gt;로직이 README나 주석으로 설명되어 있어 왜 그렇게 동작하는지 추적할 수 있다 (base_time 규칙)&lt;/li&gt;
&lt;li&gt;사용자가 원래 위임한 범위(&amp;ldquo;학습용으로 적절히 정해줘&amp;rdquo;) 안에 있다&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;반대로, 만약 사용자가 실제 운영 서비스를 만드는 것이었다면 격자값이나 조회 간격 같은 값은 AI에게 위임하지 않고 처음부터 직접 지정하는 것이 안전합니다. &lt;b&gt;학습&amp;middot;샘플 목적처럼 위임 범위가 넓을 때만 AI가 기본값을 정하도록 두는 것&lt;/b&gt;이 바이브 코딩에서의 합리적인 경계입니다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;정리&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번 글에서 살펴본 흐름을 한 문장으로 정리하면 다음과 같습니다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사용자는 &quot;무엇을 원하는지&quot;를 말하고, AI는 그것을 &quot;어떻게 구현할지&quot;로 번역한다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;바이브 코딩을 잘하기 위해 처음부터 완벽한 스펙 문서를 쓸 필요는 없습니다. 오히려 다음 두 가지만 지키면, 이번 글에서 본 것처럼 AI가 나머지를 자연스럽게 구체화해 줍니다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;목적과 원하는 흐름을 순서대로 명확히 말한다&lt;/b&gt; (이번 프롬프트의 1~8번 항목처럼)&lt;/li&gt;
&lt;li&gt;&lt;b&gt;AI에게 위임할 부분과 위임하지 않을 부분을 구분해서 알려준다&lt;/b&gt; (&quot;파라미터 기본값은 네가 정해줘&quot;처럼)&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 시작한 요청은 AI와의 대화를 거치며 지난 글에서 본 것처럼 변수명, 오류 코드, 테스트 항목까지 갖춘 구체적인 스펙으로 자연스럽게 발전하게 됩니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;fileblock&quot; data-ke-align=&quot;alignCenter&quot;&gt;&lt;a href=&quot;https://blog.kakaocdn.net/dn/rZSJo/dJMcacKT0Lf/iUoR5my9TdFaoSLNZOXTD1/vibe_coding_education_2.md.pdf?attach=1&amp;amp;knm=tfile.pdf&quot; class=&quot;&quot;&gt;
    &lt;div class=&quot;image&quot;&gt;&lt;/div&gt;
    &lt;div class=&quot;desc&quot;&gt;&lt;div class=&quot;filename&quot;&gt;&lt;span class=&quot;name&quot;&gt;vibe_coding_education_2.md.pdf&lt;/span&gt;&lt;/div&gt;
&lt;div class=&quot;size&quot;&gt;0.36MB&lt;/div&gt;
&lt;/div&gt;
  &lt;/a&gt;&lt;/figure&gt;
&lt;/p&gt;</description>
      <category>AI</category>
      <category>ai개발</category>
      <category>AI코딩교육</category>
      <category>Python샘플프로젝트</category>
      <category>vibecoding</category>
      <category>개발자블로그</category>
      <category>공공데이터API</category>
      <category>기상청api</category>
      <category>바이브코딩</category>
      <category>프롬프트엔지니어링</category>
      <category>프롬프트작성법</category>
      <author>바리새인</author>
      <guid isPermaLink="true">https://barisein.tistory.com/1333</guid>
      <comments>https://barisein.tistory.com/1333#entry1333comment</comments>
      <pubDate>Tue, 4 Aug 2026 12:16:17 +0900</pubDate>
    </item>
    <item>
      <title>바이브 코딩 교육: 좋은 프롬프트로 Python 샘플 프로젝트 설계하기</title>
      <link>https://barisein.tistory.com/1332</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;목차&lt;/h2&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;바이브 코딩이란&lt;/li&gt;
&lt;li&gt;왜 이 프롬프트가 좋은 예시인가&lt;/li&gt;
&lt;li&gt;프롬프트 구조 분석&lt;/li&gt;
&lt;li&gt;항목별 핵심 포인트&lt;/li&gt;
&lt;li&gt;이 프롬프트를 실제로 사용하면 생기는 일&lt;/li&gt;
&lt;li&gt;나만의 프롬프트로 응용하기&lt;/li&gt;
&lt;li&gt;정리&lt;/li&gt;
&lt;/ol&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;바이브 코딩이란&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;**바이브 코딩(Vibe Coding)**은 개발자가 코드를 한 줄씩 직접 작성하는 대신, AI에게 목적과 요구사항을 자연어로 전달하고 AI가 실제 동작하는 코드를 생성하도록 하는 개발 방식입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;바이브 코딩에서 결과물의 품질은 대부분 &lt;b&gt;프롬프트의 완성도&lt;/b&gt;에서 결정됩니다. AI는 프롬프트에 없는 내용을 마음대로 추측하기 때문에, 프롬프트가 모호하면 결과물도 모호해집니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번 글에서는 &quot;공공데이터 API 연동 샘플&quot; 프로젝트를 요청한 실제 프롬프트를 예시로, 좋은 바이브 코딩 프롬프트가 어떤 요소로 구성되는지 살펴봅니다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;왜 이 프롬프트가 좋은 예시인가&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래 프롬프트는 실제로 AI에게 전달된 요청입니다.&lt;/p&gt;
&lt;pre class=&quot;markdown&quot;&gt;&lt;code&gt;너는 소프트웨어 개발 보조자야.
다음 목표로 Python 샘플 프로젝트를 만들어줘.
프로젝트 이름:
공공데이터 API 연동 샘플
목적:
공공데이터 API를 처음 사용하는 사람이 샘플 소스를 통해 API 호출, 결과 저장, 이메일 공유 흐름을 이해할 수 있게 한다.
대상 API:
기상청_단기예보 조회서비스
Endpoint:
https://apis.data.go.kr/1360000/VilageFcstInfoService_2.0/getVilageFcst
기술 스택:
- Python 3.12 이상
- requests
- python-dotenv
- APScheduler
- pytest
- smtplib
- email.message
구현 요구사항:
1. `.env.example`을 만들고 실제 인증키는 `.env`에서만 읽게 해줘.
2. `.env`에는 다음 항목을 사용해줘.
   - PUBLIC_DATA_SERVICE_KEY
   - WEATHER_NX=60
   - WEATHER_NY=127
   - WEATHER_NUM_OF_ROWS=1000
   - WEATHER_PAGE_NO=1
   - WEATHER_DATA_TYPE=JSON
   - WEATHER_TIMEOUT_SECONDS=10
   - MONITOR_INTERVAL_SECONDS=3600
   - OUTPUT_DIR=data
   - GMAIL_SMTP_HOST=smtp.gmail.com
   - GMAIL_SMTP_PORT=587
   - GMAIL_USERNAME
   - GMAIL_APP_PASSWORD
   - EMAIL_FROM
   - EMAIL_TO
   - EMAIL_SUBJECT=기상청 단기예보 조회 결과
   - EMAIL_ATTACH_JSON=false
3. 단기예보 API 조회 기본값:
   - pageNo=1
   - numOfRows=1000
   - dataType=JSON
   - nx=60
   - ny=127
   - base_date는 실행일 기준 YYYYMMDD
   - base_time은 최신 사용 가능 발표시각을 자동 선택
   - base_time 후보는 0200, 0500, 0800, 1100, 1400, 1700, 2000, 2300
   - 각 발표시각 10분 이후부터 사용 가능하다고 보고, 당일 첫 발표 전이면 전날 2300을 사용
4. API 응답은 JSON으로 파싱하고, 결과를 `data/forecast_YYYYMMDD_HHMMSS.json` 파일로 저장해줘.
5. CLI를 만들어줘.
   - 단발 실행: `python -m open_data_sample.cli`
   - 이메일 전송: `python -m open_data_sample.cli --send-email`
   - 주기 실행: `python -m open_data_sample.cli --monitor`
6. 이메일 전송:
   - Gmail SMTP를 사용해줘.
   - 기본은 HTML 요약 본문만 보내줘.
   - 메일 본문에는 발표일자/시각, 조회 격자, 항목 수, 저장 시각, 가까운 예보 시간별 주요 항목 표를 포함해줘.
   - `EMAIL_ATTACH_JSON=true`일 때만 원본 JSON 파일을 첨부해줘.
   - 일반 Gmail 비밀번호가 아니라 Gmail 앱 비밀번호를 사용한다는 설명을 README에 넣어줘.
7. 오류 처리를 구분해줘.
   - API_AUTH_ERROR
   - API_REQUEST_ERROR
   - API_EMPTY_RESULT
   - API_TIMEOUT
   - API_SCHEMA_CHANGED
   - FILE_WRITE_ERROR
   - EMAIL_SEND_ERROR
8. 테스트를 작성해줘.
   - base_time 자동 선택 테스트
   - API 응답 파싱 테스트
   - 빈 결과/오류 응답 테스트
   - JSON 파일 저장 테스트
   - HTML 이메일 본문 생성 테스트
   - EMAIL_ATTACH_JSON=false일 때 첨부 없음 테스트
   - EMAIL_ATTACH_JSON=true일 때 JSON 첨부 테스트
9. README에 설치, 실행, 이메일 전송, 테스트 방법을 정리해줘.
주의사항:
- 실제 API 키, Gmail 계정, 앱 비밀번호, 수신자 이메일은 코드나 문서에 쓰지 마.
- `.env`는 `.gitignore`에 넣어줘.
- `.env.example`에는 placeholder만 넣어줘.
- UI는 만들지 마. 소스 코드와 CLI 실행 흐름으로 보여주는 샘플이면 된다.
완료 후에는 파일 구조, 주요 구현 내용, 실행 방법, 테스트 방법을 요약해줘.
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 프롬프트가 좋은 이유는 다음과 같습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;역할(Role)을 먼저 지정&lt;/b&gt;했다 &amp;rarr; &quot;너는 소프트웨어 개발 보조자야&quot;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;목적(Why)을 명확히&lt;/b&gt; 밝혔다 &amp;rarr; 누구를 위한 것이고 무엇을 이해시키려는지&lt;/li&gt;
&lt;li&gt;&lt;b&gt;대상 기술(What)을 구체적으로&lt;/b&gt; 지정했다 &amp;rarr; API, 라이브러리, 버전&lt;/li&gt;
&lt;li&gt;&lt;b&gt;구현 요구사항을 번호로 나열&lt;/b&gt;했다 &amp;rarr; AI가 빠뜨리지 않고 순서대로 처리 가능&lt;/li&gt;
&lt;li&gt;&lt;b&gt;결과물의 형태(파일 경로, 실행 명령어)를 예시로 제시&lt;/b&gt;했다 &amp;rarr; 결과가 예측 가능해짐&lt;/li&gt;
&lt;li&gt;&lt;b&gt;하지 말아야 할 것(주의사항)을 명시&lt;/b&gt;했다 &amp;rarr; 보안 사고나 불필요한 작업을 방지&lt;/li&gt;
&lt;li&gt;&lt;b&gt;완료 후 산출물(요약 보고)을 요구&lt;/b&gt;했다 &amp;rarr; 결과를 검증할 수 있는 형태로 받음&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, 이 프롬프트는 &quot;무엇을 만들어줘&quot;에서 끝나지 않고 &lt;b&gt;목적 &amp;rarr; 대상 &amp;rarr; 기술 &amp;rarr; 요구사항 &amp;rarr; 제약조건 &amp;rarr; 검증 방법&lt;/b&gt;까지 하나의 완결된 스펙 문서 형태를 갖추고 있습니다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;프롬프트 구조 분석&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 프롬프트를 분해하면 다음과 같은 7개 블록으로 나눌 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;블록 내용 역할&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1. 역할 지정&lt;/td&gt;
&lt;td&gt;&quot;너는 소프트웨어 개발 보조자야&quot;&lt;/td&gt;
&lt;td&gt;AI가 어떤 관점으로 답할지 설정&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2. 목표/목적&lt;/td&gt;
&lt;td&gt;프로젝트 이름, 목적 설명&lt;/td&gt;
&lt;td&gt;무엇을, 왜 만드는지 정의&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3. 대상 시스템&lt;/td&gt;
&lt;td&gt;대상 API, Endpoint&lt;/td&gt;
&lt;td&gt;연동할 외부 시스템을 명확히 지정&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4. 기술 스택&lt;/td&gt;
&lt;td&gt;사용할 언어와 라이브러리 목록&lt;/td&gt;
&lt;td&gt;구현 방식의 범위를 제한&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;5. 구현 요구사항&lt;/td&gt;
&lt;td&gt;번호로 나열된 상세 스펙&lt;/td&gt;
&lt;td&gt;실제로 코드에 들어갈 기능 목록&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;6. 주의사항&lt;/td&gt;
&lt;td&gt;하지 말아야 할 것&lt;/td&gt;
&lt;td&gt;보안, 범위 제한&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;7. 완료 후 요청&lt;/td&gt;
&lt;td&gt;요약 보고 요청&lt;/td&gt;
&lt;td&gt;결과를 검증 가능하게 함&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;바이브 코딩을 처음 접하는 사람이 흔히 하는 실수는 2번(목적)과 5번(구현 요구사항)만 쓰고 나머지를 생략하는 것입니다. 이 경우 AI는 스스로 기술 스택을 고르거나, 보안이 필요한 값을 예시 코드에 그대로 적어버리는 등 원하지 않는 결과를 만들 수 있습니다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;항목별 핵심 포인트&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1) 인증키&amp;middot;비밀값 분리 요구&lt;/h3&gt;
&lt;pre class=&quot;autohotkey&quot;&gt;&lt;code&gt;`.env.example`을 만들고 실제 인증키는 `.env`에서만 읽게 해줘.
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;바이브 코딩에서 가장 자주 발생하는 사고 중 하나는 AI가 예시 코드에 실제 값처럼 보이는 값을 그대로 하드코딩하는 것입니다. 이를 막기 위해 &quot;실제 값은 .env에서만 읽는다&quot;는 규칙을 프롬프트에 명시적으로 넣었습니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2) 기본값을 표나 목록으로 고정&lt;/h3&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;- WEATHER_NX=60
- WEATHER_NY=127
- WEATHER_NUM_OF_ROWS=1000
...
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;값을 문장으로 설명하는 대신 키=값 형태로 나열하면, AI가 변수명과 기본값을 그대로 코드에 반영하기 쉬워집니다. 값이 애매하면 AI가 임의의 값을 채우게 되므로, 확정된 값은 프롬프트에서 직접 지정하는 것이 안전합니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3) 비즈니스 로직을 조건문 형태로 설명&lt;/h3&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;각 발표시각 10분 이후부터 사용 가능하다고 보고,
당일 첫 발표 전이면 전날 2300을 사용
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&quot;자동으로 최신 발표시각을 선택해줘&quot;라고만 하면 AI가 판단 기준을 스스로 정하게 됩니다. 여기서는 판단 기준(10분 이후, 첫 발표 전이면 전날 사용)을 구체적인 조건문 형태로 미리 정의해서 결과가 예측 가능하도록 만들었습니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;4) CLI 명령어를 실행 예시로 제시&lt;/h3&gt;
&lt;pre class=&quot;autohotkey&quot;&gt;&lt;code&gt;- 단발 실행: `python -m open_data_sample.cli`
- 이메일 전송: `python -m open_data_sample.cli --send-email`
- 주기 실행: `python -m open_data_sample.cli --monitor`
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기능을 설명(&quot;이메일도 보낼 수 있게 해줘&quot;)하는 대신 실제 실행 명령어 형태로 제시하면, AI가 옵션 이름(--send-email, --monitor)까지 그대로 구현하게 되어 결과물의 일관성이 높아집니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;5) 오류를 종류별로 미리 정의&lt;/h3&gt;
&lt;pre class=&quot;asciidoc&quot;&gt;&lt;code&gt;- API_AUTH_ERROR
- API_REQUEST_ERROR
- API_EMPTY_RESULT
...
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&quot;오류 처리를 잘 해줘&quot;라는 요청은 모호합니다. 오류 종류를 코드명으로 미리 나열하면 AI가 각 오류에 대응하는 예외 처리 분기를 빠짐없이 만들 수 있습니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;6) 테스트 항목을 기능 단위로 명시&lt;/h3&gt;
&lt;pre class=&quot;markdown&quot;&gt;&lt;code&gt;- base_time 자동 선택 테스트
- API 응답 파싱 테스트
- 빈 결과/오류 응답 테스트
...
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;테스트를 &quot;알아서 작성해줘&quot;라고 하면 커버리지가 들쭉날쭉해집니다. 검증하고 싶은 시나리오를 목록으로 주면, AI가 각 항목에 대응하는 테스트 함수를 하나씩 만들게 되어 결과를 검증하기 쉬워집니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;7) 명확한 금지 사항&lt;/h3&gt;
&lt;pre class=&quot;erlang&quot;&gt;&lt;code&gt;실제 API 키, Gmail 계정, 앱 비밀번호, 수신자 이메일은
코드나 문서에 쓰지 마.
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;바이브 코딩에서는 &quot;하지 마&quot;라는 지시가 &quot;해줘&quot;라는 지시만큼 중요합니다. 특히 보안과 관련된 항목은 명시적으로 금지하지 않으면 AI가 예시를 만들기 위해 실제처럼 보이는 값을 채워 넣는 경우가 있습니다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;이 프롬프트를 실제로 사용하면 생기는 일&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 프롬프트를 AI에게 전달하면, 아래와 같은 결과물이 생성됩니다.&lt;/p&gt;
&lt;pre class=&quot;jboss-cli&quot;&gt;&lt;code&gt;src/open_data_sample/
  cli.py             # 실행 진입점
  config.py          # .env 설정 로딩
  forecast_time.py   # base_date/base_time 자동 계산
  weather_client.py  # API 호출과 응답 파싱
  presenter.py       # 콘솔 요약과 HTML 메일 본문 생성
  repository.py      # JSON 파일 저장
  email_share.py     # Gmail SMTP 전송
  service.py         # 전체 실행 흐름 조립
  errors.py          # 오류 코드와 안내 메시지
tests/
.env.example
.gitignore
README.md
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프롬프트에서 요구한 7가지 구현 요구사항이 각각 하나의 모듈(.py 파일)로 대응되는 것을 확인할 수 있습니다. 이는 우연이 아니라, 프롬프트를 기능 단위로 나눠서 작성했기 때문에 생기는 결과입니다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;  &lt;b&gt;학습 포인트&lt;/b&gt;: 프롬프트의 항목 구성이 곧 결과 코드의 모듈 구성으로 이어지는 경우가 많습니다. 프롬프트를 작성할 때 이미 &quot;이 요구사항은 하나의 파일/함수로 분리될 수 있는가?&quot;를 생각하면서 항목을 나누면, AI가 더 깔끔한 구조로 코드를 생성합니다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;나만의 프롬프트로 응용하기&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 프롬프트 구조를 그대로 템플릿으로 재사용할 수 있습니다.&lt;/p&gt;
&lt;pre class=&quot;prolog&quot;&gt;&lt;code&gt;너는 [역할]이야.
다음 목표로 [프로젝트 종류]를 만들어줘.

프로젝트 이름: [이름]
목적: [누가, 무엇을 위해 사용하는지]

대상 시스템/API: [연동 대상]
Endpoint 또는 접속 정보: [주소]

기술 스택:
- [언어/버전]
- [라이브러리 목록]

구현 요구사항:
1. [기능 1]
2. [기능 2]
...

오류 처리:
- [오류 종류 나열]

테스트:
- [검증할 시나리오 나열]

주의사항:
- [보안/제약 조건]

완료 후에는 [원하는 요약 보고 형식]을 알려줘.
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 템플릿에서 가장 중요한 것은 &lt;b&gt;&quot;구현 요구사항&quot;을 문장이 아니라 번호가 있는 목록으로 쓰는 것&lt;/b&gt;입니다. 문장으로 길게 설명하면 AI가 일부 조건을 놓치기 쉽지만, 번호로 나누면 각 항목을 하나씩 처리하도록 유도할 수 있습니다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;정리&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;바이브 코딩에서 좋은 결과를 얻으려면 다음 3가지를 기억하면 됩니다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;목적과 대상을 먼저 명확히 한다&lt;/b&gt; &amp;mdash; 누구를 위한 것이고, 무엇과 연동하는지&lt;/li&gt;
&lt;li&gt;&lt;b&gt;요구사항을 번호로 쪼갠다&lt;/b&gt; &amp;mdash; 문장이 아니라 목록으로, 값은 키=값 형태로&lt;/li&gt;
&lt;li&gt;&lt;b&gt;하지 말아야 할 것을 명시한다&lt;/b&gt; &amp;mdash; 특히 보안, 실제 값 노출과 관련된 항목&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번 예시로 살펴본 &quot;공공데이터 API 연동 샘플&quot; 프롬프트는 이 3가지 원칙을 모두 지키고 있어, 실제로 AI에게 전달했을 때 목적에 맞는 프로젝트 구조와 코드가 그대로 생성되는 것을 확인할 수 있었습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;fileblock&quot; data-ke-align=&quot;alignCenter&quot;&gt;&lt;a href=&quot;https://blog.kakaocdn.net/dn/FgRK2/dJMcagzFuz7/YvKkDmf39bp2SDgtVNZ3h0/vibe_coding_education.md.pdf?attach=1&amp;amp;knm=tfile.pdf&quot; class=&quot;&quot;&gt;
    &lt;div class=&quot;image&quot;&gt;&lt;/div&gt;
    &lt;div class=&quot;desc&quot;&gt;&lt;div class=&quot;filename&quot;&gt;&lt;span class=&quot;name&quot;&gt;vibe_coding_education.md.pdf&lt;/span&gt;&lt;/div&gt;
&lt;div class=&quot;size&quot;&gt;0.34MB&lt;/div&gt;
&lt;/div&gt;
  &lt;/a&gt;&lt;/figure&gt;
&lt;/p&gt;</description>
      <category>AI</category>
      <category>ai개발</category>
      <category>AI코딩교육</category>
      <category>Python샘플프로젝트</category>
      <category>vibecoding</category>
      <category>개발자블로그</category>
      <category>공공데이터API</category>
      <category>기상청api</category>
      <category>바이브코딩</category>
      <category>프롬프트엔지니어링</category>
      <category>프롬프트작성법</category>
      <author>바리새인</author>
      <guid isPermaLink="true">https://barisein.tistory.com/1332</guid>
      <comments>https://barisein.tistory.com/1332#entry1332comment</comments>
      <pubDate>Tue, 4 Aug 2026 12:14:48 +0900</pubDate>
    </item>
    <item>
      <title>공공데이터 API 연동 샘플 만들기: 기상청 단기예보 API를 Python으로 호출하고 Gmail로 공유하기</title>
      <link>https://barisein.tistory.com/1331</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;목차&lt;/h2&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;글 요약&lt;/li&gt;
&lt;li data-sourcepos=&quot;6:1-6:16;79-94&quot;&gt;왜 이 샘플을 만들었나&lt;/li&gt;
&lt;li data-sourcepos=&quot;7:1-7:13;95-107&quot;&gt;사용한 공공데이터&lt;/li&gt;
&lt;li data-sourcepos=&quot;8:1-8:9;108-116&quot;&gt;기술 스택&lt;/li&gt;
&lt;li data-sourcepos=&quot;9:1-9:11;117-127&quot;&gt;프로젝트 구조&lt;/li&gt;
&lt;li data-sourcepos=&quot;10:1-10:12;128-139&quot;&gt;환경 변수 설정&lt;/li&gt;
&lt;li data-sourcepos=&quot;11:1-11:9;140-148&quot;&gt;실행 방법&lt;/li&gt;
&lt;li data-sourcepos=&quot;12:1-12:13;149-161&quot;&gt;이메일 공유 방식&lt;/li&gt;
&lt;li data-sourcepos=&quot;13:1-13:12;162-173&quot;&gt;오류 처리 기준&lt;/li&gt;
&lt;li data-sourcepos=&quot;14:1-14:8;174-181&quot;&gt;테스트&lt;/li&gt;
&lt;li data-sourcepos=&quot;15:1-15:17;182-198&quot;&gt;구현하면서 신경 쓴 점&lt;/li&gt;
&lt;li data-sourcepos=&quot;16:1-16:8;199-206&quot;&gt;마무리&lt;/li&gt;
&lt;/ol&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;글 요약&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;공공데이터 API를 처음 연동할 때는 &quot;API를 어떻게 호출하지?&quot;에서 끝나지 않는다. 실제로는 인증키 관리, 조회 파라미터 구성, 오류 처리, 결과 저장, 주기적 실행, 공유 방식까지 함께 고민해야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 글에서는 기상청_단기예보 조회서비스를 예시로 Python 샘플 프로젝트를 구성한 과정을 정리한다. 목표는 운영용 완성 서비스가 아니라, 공공데이터 API를 어떻게 호출하고 활용하는지 소스 코드로 이해할 수 있는 학습용 샘플이다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;왜 이 샘플을 만들었나&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;공공데이터 API를 사용하려는 사람은 보통 다음 지점에서 막힌다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;서비스키를 어디에 넣어야 하는지&lt;/li&gt;
&lt;li&gt;어떤 endpoint와 파라미터를 호출해야 하는지&lt;/li&gt;
&lt;li&gt;응답이 비었는지, 오류인지 어떻게 구분해야 하는지&lt;/li&gt;
&lt;li&gt;결과를 파일로 남기려면 어떤 구조가 좋은지&lt;/li&gt;
&lt;li&gt;주기적으로 조회하려면 어떻게 실행해야 하는지&lt;/li&gt;
&lt;li&gt;조회 결과를 다른 사람에게 공유하려면 어떻게 해야 하는지&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 이번 샘플은 단순 호출 예제보다 조금 더 실제 흐름에 가깝게 구성했다.&lt;/p&gt;
&lt;pre class=&quot;clean&quot;&gt;&lt;code&gt;API 설정 입력
-&amp;gt; 공공데이터 API 호출
-&amp;gt; API 응답 수신
-&amp;gt; 결과 변환
-&amp;gt; 콘솔 출력
-&amp;gt; 로컬 JSON 파일 저장
-&amp;gt; HTML 이메일 공유
&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;사용한 공공데이터&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;대상 API는 기상청_단기예보 조회서비스다.&lt;/p&gt;
&lt;pre class=&quot;makefile&quot;&gt;&lt;code&gt;서비스: 기상청_단기예보 조회서비스
방식: REST
응답 포맷: JSON 우선 사용
Endpoint:
https://apis.data.go.kr/1360000/VilageFcstInfoService_2.0/getVilageFcst
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;샘플 기본 조회값은 다음처럼 잡았다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;파라미터 기본값 설명&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;pageNo&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;첫 페이지&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;numOfRows&lt;/td&gt;
&lt;td&gt;1000&lt;/td&gt;
&lt;td&gt;단기예보 항목을 충분히 받기 위한 샘플 행 수&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;dataType&lt;/td&gt;
&lt;td&gt;JSON&lt;/td&gt;
&lt;td&gt;JSON 응답 사용&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;base_date&lt;/td&gt;
&lt;td&gt;실행일 기준&lt;/td&gt;
&lt;td&gt;YYYYMMDD 형식&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;base_time&lt;/td&gt;
&lt;td&gt;자동 선택&lt;/td&gt;
&lt;td&gt;최신 사용 가능 발표시각&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;nx&lt;/td&gt;
&lt;td&gt;60&lt;/td&gt;
&lt;td&gt;서울 시청 인근 격자 X&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;ny&lt;/td&gt;
&lt;td&gt;127&lt;/td&gt;
&lt;td&gt;서울 시청 인근 격자 Y&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;base_time은 고정값으로 두지 않고 실행 시각 기준으로 자동 계산했다. 후보는 다음과 같다.&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;0200, 0500, 0800, 1100, 1400, 1700, 2000, 2300
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;각 발표시각 10분 이후부터 사용할 수 있다고 보고, 아직 당일 첫 발표 전이면 전날 2300을 사용하도록 했다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;기술 스택&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번 샘플은 UI 없는 로컬 실행 예제다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;영역 선택&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Runtime&lt;/td&gt;
&lt;td&gt;Python 3.12&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;API Client&lt;/td&gt;
&lt;td&gt;requests&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;환경 변수&lt;/td&gt;
&lt;td&gt;python-dotenv&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;스케줄링&lt;/td&gt;
&lt;td&gt;APScheduler&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;저장&lt;/td&gt;
&lt;td&gt;로컬 JSON 파일&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;이메일&lt;/td&gt;
&lt;td&gt;Gmail SMTP, smtplib, email.message&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;테스트&lt;/td&gt;
&lt;td&gt;pytest&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;화면을 만드는 대신 코드 실행 결과, 저장 파일, 이메일 본문으로 흐름을 보여주는 데 집중했다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;프로젝트 구조&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;핵심 소스 구조는 다음과 같다.&lt;/p&gt;
&lt;pre class=&quot;avrasm&quot;&gt;&lt;code&gt;src/open_data_sample/
  cli.py             # 실행 진입점
  config.py          # .env 설정 로딩
  forecast_time.py   # base_date/base_time 자동 계산
  weather_client.py  # 기상청 단기예보 API 호출과 응답 파싱
  presenter.py       # 콘솔 요약과 HTML 메일 본문 생성
  repository.py      # JSON 파일 저장
  email_share.py     # Gmail SMTP 전송
  service.py         # 전체 실행 흐름 조립
  errors.py          # 오류 코드와 사용자 안내 메시지
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;구조를 나눈 이유는 API 호출, 결과 변환, 저장, 이메일 공유 책임을 분리하기 위해서다. 학습용 샘플이어도 한 파일에 모두 넣으면 흐름을 이해하기 어려워진다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;환경 변수 설정&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실제 API 키와 Gmail 앱 비밀번호는 코드에 넣지 않고 .env로 분리했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;.env.example 예시는 다음과 같다.&lt;/p&gt;
&lt;pre class=&quot;ini&quot;&gt;&lt;code&gt;PUBLIC_DATA_SERVICE_KEY=your-public-data-service-key

WEATHER_NX=60
WEATHER_NY=127
WEATHER_NUM_OF_ROWS=1000
WEATHER_PAGE_NO=1
WEATHER_DATA_TYPE=JSON
WEATHER_TIMEOUT_SECONDS=10
MONITOR_INTERVAL_SECONDS=3600

OUTPUT_DIR=data

GMAIL_SMTP_HOST=smtp.gmail.com
GMAIL_SMTP_PORT=587
GMAIL_USERNAME=your-gmail-address@gmail.com
GMAIL_APP_PASSWORD=your-gmail-app-password
EMAIL_FROM=your-gmail-address@gmail.com
EMAIL_TO=recipient@example.com
EMAIL_SUBJECT=기상청 단기예보 조회 결과
EMAIL_ATTACH_JSON=false
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;주의할 점은 Gmail 비밀번호다. 일반 계정 비밀번호가 아니라 Gmail 앱 비밀번호를 사용해야 한다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;실행 방법&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;의존성을 설치한다.&lt;/p&gt;
&lt;pre class=&quot;cmake&quot;&gt;&lt;code&gt;python -m pip install -e &quot;.[dev]&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;단발 조회를 실행한다.&lt;/p&gt;
&lt;pre class=&quot;autohotkey&quot;&gt;&lt;code&gt;python -m open_data_sample.cli
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;성공하면 콘솔에 요약이 출력되고 data/ 아래에 JSON 파일이 저장된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예시 출력:&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;기상청 단기예보 조회 성공
- 발표일자/시각: 20260804 0800
- 격자: nx=60, ny=127
- 항목 수: 871 / totalCount=871
- 카테고리: PCP, POP, PTY, REH, SKY, SNO, TMN, TMP, TMX, UUU, VEC, VVV, WAV, WSD
- 저장 파일: data\forecast_20260804_100048.json
- 이메일 전송: 건너뜀
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이메일까지 보내려면 다음처럼 실행한다.&lt;/p&gt;
&lt;pre class=&quot;ada&quot;&gt;&lt;code&gt;python -m open_data_sample.cli --send-email
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;주기적 모니터링 흐름을 확인하려면 다음 옵션을 사용한다.&lt;/p&gt;
&lt;pre class=&quot;x86asm&quot;&gt;&lt;code&gt;python -m open_data_sample.cli --monitor
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;반복 간격은 .env의 MONITOR_INTERVAL_SECONDS로 조정한다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;이메일 공유 방식&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;처음에는 저장된 JSON 파일을 그대로 첨부하는 방식도 생각했다. 하지만 수신자 입장에서 원본 JSON 파일은 바로 읽기 어렵다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 기본 동작은 HTML 요약 메일로 바꿨다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;메일 본문에는 다음 정보가 들어간다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;발표일자와 발표시각&lt;/li&gt;
&lt;li&gt;조회 격자&lt;/li&gt;
&lt;li&gt;조회 항목 수&lt;/li&gt;
&lt;li&gt;저장 시각&lt;/li&gt;
&lt;li&gt;가까운 예보 시간별 주요 항목 표&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;원본 JSON 첨부는 기본적으로 끈다.&lt;/p&gt;
&lt;pre class=&quot;ini&quot;&gt;&lt;code&gt;EMAIL_ATTACH_JSON=false
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;원본 파일도 같이 보내고 싶을 때만 다음처럼 바꾼다.&lt;/p&gt;
&lt;pre class=&quot;ini&quot;&gt;&lt;code&gt;EMAIL_ATTACH_JSON=true
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 하면 HTML 요약 본문은 그대로 보내고, 원본 JSON 파일도 첨부한다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;오류 처리 기준&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;샘플에서는 오류를 한 덩어리로 처리하지 않고 코드로 구분했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;코드 의미&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;API_AUTH_ERROR&lt;/td&gt;
&lt;td&gt;API 인증키 또는 권한 문제&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;API_REQUEST_ERROR&lt;/td&gt;
&lt;td&gt;API 호출 실패&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;API_EMPTY_RESULT&lt;/td&gt;
&lt;td&gt;호출은 성공했지만 결과 없음&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;API_TIMEOUT&lt;/td&gt;
&lt;td&gt;응답 지연 또는 타임아웃&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;API_SCHEMA_CHANGED&lt;/td&gt;
&lt;td&gt;응답 형식 변경&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;FILE_WRITE_ERROR&lt;/td&gt;
&lt;td&gt;파일 저장 실패&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;EMAIL_SEND_ERROR&lt;/td&gt;
&lt;td&gt;이메일 전송 실패&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;CLI에서는 오류 코드, 사용자가 할 수 있는 조치, 재시도 가능 여부를 함께 보여준다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;테스트&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;테스트는 pytest로 작성했다.&lt;/p&gt;
&lt;pre class=&quot;ebnf&quot;&gt;&lt;code&gt;python -m pytest
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;검증한 내용은 다음과 같다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;base_time 자동 선택&lt;/li&gt;
&lt;li&gt;공공데이터 API 응답 파싱&lt;/li&gt;
&lt;li&gt;빈 결과와 API 오류 구분&lt;/li&gt;
&lt;li&gt;로컬 JSON 저장&lt;/li&gt;
&lt;li&gt;HTML 이메일 본문 생성&lt;/li&gt;
&lt;li&gt;JSON 첨부 기본 비활성화&lt;/li&gt;
&lt;li&gt;EMAIL_ATTACH_JSON=true일 때만 첨부&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;현재 테스트 결과는 다음과 같다.&lt;/p&gt;
&lt;pre class=&quot;basic&quot;&gt;&lt;code&gt;13 passed
&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;구현하면서 신경 쓴 점&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1. 비밀값은 Git에 올리지 않는다&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;.env는 .gitignore에 넣고, .env.example만 공유한다. API 키, Gmail 앱 비밀번호, 수신자 같은 실제 값은 공개 문서와 로그에도 남기지 않는다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2. 서비스키 인코딩 문제를 고려한다&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;공공데이터포털 서비스키는 인코딩된 키를 복사하는 경우가 있다. 그래서 설정 로딩 시 unquote를 적용해 인코딩된 키도 자연스럽게 처리하도록 했다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3. 수신자가 읽을 수 있는 결과를 보낸다&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;원본 JSON은 개발자에게는 유용하지만 일반 수신자에게는 불편하다. 그래서 기본 공유 방식은 HTML 본문으로 정리하고, JSON 첨부는 선택 옵션으로 뺐다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;4. 운영용 서비스와 샘플의 경계를 분리한다&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 프로젝트는 학습용 샘플이다. 운영 환경용 권한 관리, 대규모 저장소, 고급 알림, 배포 자동화는 의도적으로 제외했다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;마무리&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;공공데이터 API 연동은 단순히 requests.get() 한 줄로 끝나지 않는다. 실제로 공유 가능한 샘플을 만들려면 설정 관리, 파라미터 계산, 오류 구분, 저장, 이메일 공유, 테스트까지 함께 정리해야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번 샘플은 기상청 단기예보 API를 기준으로 만들었지만, 구조 자체는 다른 공공데이터 API에도 응용할 수 있다. endpoint와 파라미터, 응답 파싱 부분만 바꾸면 비슷한 흐름으로 확장할 수 있다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;fileblock&quot; data-ke-align=&quot;alignCenter&quot;&gt;&lt;a href=&quot;https://blog.kakaocdn.net/dn/MEl2T/dJMcajiTBtx/zACsvlJcd5KsaBj93Saiuk/weather_api_sample.md.pdf?attach=1&amp;amp;knm=tfile.pdf&quot; class=&quot;&quot;&gt;
    &lt;div class=&quot;image&quot;&gt;&lt;/div&gt;
    &lt;div class=&quot;desc&quot;&gt;&lt;div class=&quot;filename&quot;&gt;&lt;span class=&quot;name&quot;&gt;weather_api_sample.md.pdf&lt;/span&gt;&lt;/div&gt;
&lt;div class=&quot;size&quot;&gt;0.36MB&lt;/div&gt;
&lt;/div&gt;
  &lt;/a&gt;&lt;/figure&gt;
&lt;/p&gt;</description>
      <category>파이썬</category>
      <category>API연동샘플</category>
      <category>APScheduler</category>
      <category>GmailSMTP</category>
      <category>Python</category>
      <category>requests라이브러리</category>
      <category>개발자블로그</category>
      <category>공공데이터API</category>
      <category>기상청api</category>
      <category>단기예보</category>
      <category>파이썬프로젝트</category>
      <author>바리새인</author>
      <guid isPermaLink="true">https://barisein.tistory.com/1331</guid>
      <comments>https://barisein.tistory.com/1331#entry1331comment</comments>
      <pubDate>Tue, 4 Aug 2026 11:09:01 +0900</pubDate>
    </item>
    <item>
      <title>공공데이터포털 오픈 API 신청 및 활용 가이드</title>
      <link>https://barisein.tistory.com/1330</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;이 가이드에서 배우는 것&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;**오픈 API(Open API)**란 공공기관이나 기업이 자신들의 데이터를 외부 개발자도 사용할 수 있도록 공개해 놓은 인터페이스입니다. 공공데이터포털(data.go.kr)은 정부와 공공기관이 보유한 다양한 데이터를 API 형태로 제공하는 사이트입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 가이드를 따라 하면 다음을 직접 경험할 수 있습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;공공데이터포털에서 원하는 데이터를 검색하는 방법&lt;/li&gt;
&lt;li&gt;오픈 API 사용을 신청하고 인증키를 발급받는 방법&lt;/li&gt;
&lt;li&gt;발급받은 인증키로 실제 API를 호출해 데이터를 받아보는 방법&lt;/li&gt;
&lt;li&gt;API 호출이 실패했을 때 원인을 확인하는 방법&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;  &lt;b&gt;학습 포인트&lt;/b&gt;: 대부분의 오픈 API는 &quot;신청 &amp;rarr; 인증키 발급 &amp;rarr; 인증키를 포함해 요청 &amp;rarr; 응답(JSON/XML) 받기&quot;라는 동일한 흐름을 가집니다. 이 흐름을 한 번 익히면 다른 공공 API나 민간 API를 사용할 때도 그대로 적용할 수 있습니다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;목차&lt;/h2&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;사이트 접속&lt;/li&gt;
&lt;li&gt;오픈 API 검색 및 신청&lt;/li&gt;
&lt;li&gt;신청 내역 확인&lt;/li&gt;
&lt;li&gt;API 활용&lt;/li&gt;
&lt;li&gt;학습 정리&lt;/li&gt;
&lt;/ol&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;1. 사이트 접속&lt;/h2&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;a href=&quot;https://www.data.go.kr&quot;&gt;https://www.data.go.kr&lt;/a&gt; 접속&lt;/li&gt;
&lt;li&gt;회원가입&lt;/li&gt;
&lt;li&gt;로그인&lt;/li&gt;
&lt;/ol&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;  &lt;b&gt;왜 회원가입이 필요할까요?&lt;/b&gt; 공공데이터포털은 API 사용자별로 별도의 인증키를 발급합니다. 이는 누가 API를 얼마나 호출하는지 추적하고, 트래픽이 과도할 경우 관리하기 위한 목적입니다. 대부분의 API 서비스가 이런 방식(회원가입 &amp;rarr; 인증키 발급 &amp;rarr; 인증키로 요청)을 사용합니다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;2. 오픈 API 검색 및 신청&lt;/h2&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;공공데이터 &amp;gt; 데이터목록&lt;/b&gt; 이동&lt;/li&gt;
&lt;li&gt;검색창에 중소기업 검색&lt;/li&gt;
&lt;li&gt;&lt;b&gt;오픈 API&lt;/b&gt; 클릭
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;참고: 검색 결과에는 파일 형태로 제공되는 데이터(엑셀, CSV 등)와 API로 제공되는 데이터가 함께 나옵니다. 실시간으로 데이터를 받아 프로그램에 연동하려면 &lt;b&gt;오픈 API&lt;/b&gt;를 선택해야 합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;목록 중 원하는 글 선택 후 &lt;b&gt;활용신청&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;예: 중소기업은행_월평균&amp;amp;연평균 환율 정보&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;활용목적 입력
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;웹 사이트 개발 체크&lt;/li&gt;
&lt;li&gt;설명 기술: 공공데이터 API 연동을 위한 샘플 작성&lt;/li&gt;
&lt;li&gt;이용허락범위 동의 체크&lt;/li&gt;
&lt;li&gt;&lt;b&gt;활용 신청하기&lt;/b&gt; 클릭&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;  &lt;b&gt;활용목적을 입력하는 이유&lt;/b&gt;: 공공데이터는 예산으로 운영되기 때문에, 어떤 목적으로 데이터가 사용되는지 파악하기 위해 활용목적을 요구합니다. 교육이나 학습 목적으로 사용하는 경우 사실대로 간단히 작성하면 됩니다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;3. 신청 내역 확인&lt;/h2&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;마이페이지&lt;/b&gt; 클릭
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;오픈 API 활용 현황 &amp;gt; &lt;b&gt;활용 중&lt;/b&gt; 선택&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;메뉴로 이동
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;데이터 활용 &amp;gt; Open API &amp;gt; 활용신청 현황&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;  API 중에는 신청 즉시 승인되는 것과, 관리기관의 별도 심사가 필요한 것이 있습니다. 상태가 &quot;활용 중&quot;으로 표시되어야 실제로 인증키가 정상 작동합니다. &quot;승인대기&quot; 상태라면 사용이 아직 불가능합니다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;4. API 활용&lt;/h2&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;신청한 목록 중 원하는 API 선택&lt;/li&gt;
&lt;li&gt;서비스 정보 확인
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;End Point&lt;/b&gt;: API를 호출할 주소 (예: &lt;a href=&quot;https://apis.data.go.kr/...&quot;&gt;https://apis.data.go.kr/...&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;&lt;b&gt;일반 인증키&lt;/b&gt;: 요청할 때마다 함께 전달해야 하는 인증 값&lt;/li&gt;
&lt;/ul&gt;
  &lt;b&gt;End Point와 인증키란?&lt;/b&gt; End Point는 데이터를 요청하는 주소(URL)이고, 인증키(서비스키)는 &quot;이 요청을 보낸 사람이 누구인지&quot; 증명하는 값입니다. 웹 브라우저 주소창에 End Point + 인증키 + 필요한 파라미터를 조합해 입력하면, 그 결과로 데이터(JSON 또는 XML)를 돌려받는 구조입니다.&lt;/li&gt;
&lt;li&gt;테스트
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;활용신청 상세기능정보에서 &lt;b&gt;열기&lt;/b&gt; 클릭&lt;/li&gt;
&lt;li&gt;serviceKey에 일반 인증키를 복사해서 입력&lt;/li&gt;
&lt;li&gt;미리보기에서는 입력하지 않아도 작동함 (사이트에서 자동으로 넣어주는 경우가 많음)&lt;/li&gt;
&lt;li&gt;&lt;b&gt;미리보기&lt;/b&gt; 클릭&lt;/li&gt;
&lt;li&gt;결과 확인&lt;/li&gt;
&lt;/ul&gt;
  &lt;b&gt;미리보기 기능의 의미&lt;/b&gt;: 실제 코드를 작성하기 전에, API가 정상적으로 데이터를 반환하는지 웹 페이지에서 먼저 확인할 수 있는 기능입니다. 이 단계에서 정상적으로 결과가 나온다면, 이후 Python이나 JavaScript 같은 프로그래밍 언어로 동일한 요청을 보내도 같은 결과를 받을 수 있습니다.&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;오류 확인 및 대응 방법&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;결과에 아래와 같이 나오면 공공데이터 API 쪽 문제로 판단할 수 있습니다.&lt;/p&gt;
&lt;pre class=&quot;json&quot;&gt;&lt;code&gt;{
  &quot;response&quot;: {
    &quot;header&quot;: {
      &quot;resultCode&quot;: &quot;04&quot;,
      &quot;resultMsg&quot;: &quot;NO_MANDATORY_REQUEST_PARAMETERS_ERROR&quot;
    },
    &quot;body&quot;: {
      &quot;items&quot;: {},
      &quot;numOfRows&quot;: 0,
      &quot;pageNo&quot;: 0,
      &quot;totalCount&quot;: 0
    }
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;**resultCode: &quot;04&quot;**는 필수 파라미터가 빠졌다는 의미입니다. 인증키를 정확히 입력했는데도 이런 결과가 나온다면, 코드나 입력값의 문제가 아니라 공공데이터 API 자체(문서화 오류, 서버 문제 등)에 문제가 있을 가능성이 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이런 경우 다음 중 하나를 추천합니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;공공데이터포털 쪽에 문의하기&lt;/li&gt;
&lt;li&gt;다른 API로 연동하기&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;  &lt;b&gt;학습 포인트&lt;/b&gt;: 처음 API를 배울 때는 &quot;내가 뭘 잘못 입력했나?&quot;라고 먼저 생각하게 되지만, 공공데이터 API는 문서와 실제 동작이 다르거나 오류가 있는 경우가 종종 있습니다. 파라미터를 정확히 입력했는데도 계속 같은 오류가 난다면, 다른 API로 바꿔서 연습하는 것도 좋은 학습 전략입니다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;학습 정리&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 실습을 통해 익힌 오픈 API 활용의 기본 흐름은 다음과 같습니다.&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;1. 사이트 가입/로그인
2. 원하는 데이터(API) 검색
3. 활용 신청 &amp;rarr; 인증키 발급
4. End Point + 인증키 + 파라미터로 요청
5. JSON/XML 형태의 응답 데이터 확인
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 흐름은 공공데이터포털뿐 아니라 대부분의 오픈 API(날씨, 지도, 환율, 뉴스 등)에서 공통적으로 사용됩니다. 이후에는 이 인증키를 Python의 requests 라이브러리 등으로 직접 코드에서 호출하는 실습으로 이어갈 수 있습니다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;체크리스트&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;[ ] data.go.kr 회원가입 및 로그인 완료&lt;/li&gt;
&lt;li&gt;[ ] 오픈 API의 의미와 인증키가 필요한 이유를 이해했다&lt;/li&gt;
&lt;li&gt;[ ] 원하는 오픈 API 검색 및 활용신청 완료&lt;/li&gt;
&lt;li&gt;[ ] 마이페이지에서 신청 내역(활용 중 여부) 확인&lt;/li&gt;
&lt;li&gt;[ ] End Point 및 일반 인증키의 역할을 이해했다&lt;/li&gt;
&lt;li&gt;[ ] serviceKey 입력 후 미리보기 테스트 완료&lt;/li&gt;
&lt;li&gt;[ ] 오류 발생 시 원인이 내 입력값인지, API 자체 문제인지 구분할 수 있다&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;fileblock&quot; data-ke-align=&quot;alignCenter&quot;&gt;&lt;a href=&quot;https://blog.kakaocdn.net/dn/dW4O3g/dJMcaiEh2b5/kVebP6EP9vPJkJpydPTOBk/%EA%B3%B5%EA%B3%B5%EB%8D%B0%EC%9D%B4%ED%84%B0%ED%8F%AC%ED%84%B8_%EC%98%A4%ED%94%88API_%ED%99%9C%EC%9A%A9%EA%B0%80%EC%9D%B4%EB%93%9C.md.pdf?attach=1&amp;amp;knm=tfile.pdf&quot; class=&quot;&quot;&gt;
    &lt;div class=&quot;image&quot;&gt;&lt;/div&gt;
    &lt;div class=&quot;desc&quot;&gt;&lt;div class=&quot;filename&quot;&gt;&lt;span class=&quot;name&quot;&gt;공공데이터포털_오픈API_활용가이드.md.pdf&lt;/span&gt;&lt;/div&gt;
&lt;div class=&quot;size&quot;&gt;0.32MB&lt;/div&gt;
&lt;/div&gt;
  &lt;/a&gt;&lt;/figure&gt;
&lt;figure class=&quot;fileblock&quot; data-ke-align=&quot;alignCenter&quot;&gt;&lt;a href=&quot;https://blog.kakaocdn.net/dn/bHeQ1O/dJMcaa0wEru/uvfHoPRnMTGwHqDUdefEN0/%EA%B3%B5%EA%B3%B5%EB%8D%B0%EC%9D%B4%ED%84%B0%ED%8F%AC%ED%84%B8_%EC%98%A4%ED%94%88API_%ED%99%9C%EC%9A%A9%EA%B0%80%EC%9D%B4%EB%93%9C_%EA%B8%B0%EB%B3%B8%EB%B2%84%EC%A0%84.md.pdf?attach=1&amp;amp;knm=tfile.pdf&quot; class=&quot;&quot;&gt;
    &lt;div class=&quot;image&quot;&gt;&lt;/div&gt;
    &lt;div class=&quot;desc&quot;&gt;&lt;div class=&quot;filename&quot;&gt;&lt;span class=&quot;name&quot;&gt;공공데이터포털_오픈API_활용가이드_기본버전.md.pdf&lt;/span&gt;&lt;/div&gt;
&lt;div class=&quot;size&quot;&gt;0.18MB&lt;/div&gt;
&lt;/div&gt;
  &lt;/a&gt;&lt;/figure&gt;
&lt;/p&gt;</description>
      <category>파이썬</category>
      <category>API신청</category>
      <category>API연동</category>
      <category>data.go.kr</category>
      <category>serviceKey</category>
      <category>개발자교육</category>
      <category>공공데이터API</category>
      <category>공공데이터포털</category>
      <category>오픈API</category>
      <category>웹개발기초 ```</category>
      <category>인증키발급</category>
      <author>바리새인</author>
      <guid isPermaLink="true">https://barisein.tistory.com/1330</guid>
      <comments>https://barisein.tistory.com/1330#entry1330comment</comments>
      <pubDate>Tue, 4 Aug 2026 10:54:11 +0900</pubDate>
    </item>
    <item>
      <title>Python에서 Gmail SMTP 사용하기</title>
      <link>https://barisein.tistory.com/1329</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;Python 프로그램에서 Gmail을 통해 메일을 발송하려면 Gmail 계정의 &lt;b&gt;앱 비밀번호&lt;/b&gt;를 생성하고, Gmail SMTP 서버에 연결해야 합니다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;⚠️ 일반 Google 계정 비밀번호는 사용하지 않습니다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;목차&lt;/h2&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;Gmail 계정 준비&lt;/li&gt;
&lt;li&gt;2단계 인증 활성화&lt;/li&gt;
&lt;li&gt;앱 비밀번호 메뉴 찾기&lt;/li&gt;
&lt;li&gt;앱 비밀번호 생성&lt;/li&gt;
&lt;li&gt;앱 비밀번호 보안 안내&lt;/li&gt;
&lt;li&gt;Gmail SMTP 서버 정보&lt;/li&gt;
&lt;li&gt;Python에서 메일 발송하기&lt;/li&gt;
&lt;li&gt;인증정보 소스 코드 분리&lt;/li&gt;
&lt;li&gt;네트워크 및 방화벽 확인&lt;/li&gt;
&lt;li&gt;메일 발송 테스트 순서&lt;/li&gt;
&lt;li&gt;자주 발생하는 오류&lt;/li&gt;
&lt;li&gt;앱 비밀번호 관리&lt;/li&gt;
&lt;li&gt;최종 체크리스트&lt;/li&gt;
&lt;/ol&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;1. Gmail 계정 준비&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;메일 발송에 사용할 Gmail 계정을 준비합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;예:&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;css&quot;&gt;&lt;code&gt;example@gmail.com
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;SMTP 로그인 시 이 Gmail 주소가 사용자 이름으로 사용됩니다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;2. Google 계정의 2단계 인증 활성화&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;앱 비밀번호를 생성하려면 먼저 Google 계정의 &lt;b&gt;2단계 인증&lt;/b&gt;이 활성화되어 있어야 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Google 계정 관리 화면에서 다음 순서로 이동합니다.&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;Google 계정
&amp;rarr; 보안 및 로그인
&amp;rarr; 2단계 인증
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2단계 인증이 꺼져 있다면 안내에 따라 활성화합니다. 2단계 인증 방법으로는 다음과 같은 방식이 사용될 수 있습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Google 메시지&lt;/li&gt;
&lt;li&gt;휴대전화 인증&lt;/li&gt;
&lt;li&gt;OTP 인증 앱&lt;/li&gt;
&lt;li&gt;패스키&lt;/li&gt;
&lt;li&gt;보안 키&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2단계 인증이 이미 활성화되어 있다면 다음 단계로 이동합니다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;3. 앱 비밀번호 메뉴 찾기&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Google 계정 화면에서 앱 비밀번호 메뉴가 바로 보이지 않을 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 경우 Google 계정 상단의 검색창에 다음과 같이 입력합니다.&lt;/p&gt;
&lt;pre class=&quot;&quot;&gt;&lt;code&gt;앱 비밀번호
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;검색 결과에서 &lt;b&gt;앱 비밀번호&lt;/b&gt;를 선택합니다. 또는 Google 계정의 앱 비밀번호 페이지로 직접 이동할 수도 있습니다.&lt;/p&gt;
&lt;pre class=&quot;dts&quot;&gt;&lt;code&gt;https://myaccount.google.com/apppasswords
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;앱 비밀번호 메뉴가 표시되지 않는 경우에는 다음 사항을 확인합니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Google 계정의 2단계 인증이 활성화되어 있는지&lt;/li&gt;
&lt;li&gt;회사 또는 학교의 Google Workspace 계정인지&lt;/li&gt;
&lt;li&gt;조직 관리자가 앱 비밀번호 사용을 차단했는지&lt;/li&gt;
&lt;li&gt;Google 고급 보호 프로그램을 사용하고 있는지&lt;/li&gt;
&lt;li&gt;보안 정책상 앱 비밀번호 생성이 제한된 계정인지&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;개인 Gmail 계정이고 2단계 인증이 활성화되어 있다면 일반적으로 앱 비밀번호를 생성할 수 있습니다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;4. 앱 비밀번호 생성&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;앱 비밀번호 화면에서 사용할 프로그램을 구분할 수 있는 이름을 입력합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;예:&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;ebnf&quot;&gt;&lt;code&gt;Python SMTP
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또는:&lt;/p&gt;
&lt;pre class=&quot;ebnf&quot;&gt;&lt;code&gt;Mail Sender
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 이름은 앱 비밀번호의 용도를 구분하기 위한 이름일 뿐이며, SMTP 설정값에는 사용하지 않습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이름을 입력한 뒤 &lt;b&gt;만들기&lt;/b&gt;를 누르면 16자리 앱 비밀번호가 생성됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;예:&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;ebnf&quot;&gt;&lt;code&gt;abcd efgh ijkl mnop
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;앱 비밀번호는 생성될 때 한 번만 표시됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;SMTP 프로그램에는 공백을 제외하거나 포함한 형태로 입력할 수 있지만, 일반적으로 다음처럼 공백 없이 저장합니다.&lt;/p&gt;
&lt;pre class=&quot;ebnf&quot;&gt;&lt;code&gt;abcdefghijklmnop
&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일반 Google 계정 비밀번호가 아니라 반드시 생성된 &lt;b&gt;앱 비밀번호&lt;/b&gt;를 사용해야 합니다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;앱 비밀번호의 보안 안내&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;앱 비밀번호 화면에는 다음과 같은 경고 문구가 표시될 수 있습니다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;앱 비밀번호는 최신 보안 표준을 사용하는 앱 및 서비스보다 보안 수준이 낮습니다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 문구는 앱 비밀번호 자체가 위험하거나 계정에 문제가 있다는 뜻은 아닙니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;앱 비밀번호는 웹브라우저에서 사용하는 다음과 같은 최신 로그인 방식을 사용할 수 없는 프로그램을 위한 기능입니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;패스키&lt;/li&gt;
&lt;li&gt;생체 인증&lt;/li&gt;
&lt;li&gt;Google 로그인 화면&lt;/li&gt;
&lt;li&gt;2단계 인증 확인&lt;/li&gt;
&lt;li&gt;OAuth 2.0&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;SMTP 프로그램은 보통 사용자 이름과 비밀번호 방식으로 로그인하기 때문에, Google이 SMTP용으로 별도의 앱 비밀번호를 제공하는 것입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;앱 비밀번호의 장점&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;실제 Google 계정 비밀번호를 프로그램에 입력하지 않아도 됩니다.&lt;/li&gt;
&lt;li&gt;특정 프로그램에만 별도의 비밀번호를 사용할 수 있습니다.&lt;/li&gt;
&lt;li&gt;비밀번호가 노출되면 해당 앱 비밀번호만 삭제할 수 있습니다.&lt;/li&gt;
&lt;li&gt;Google 계정의 기본 비밀번호를 변경할 필요가 없습니다.&lt;/li&gt;
&lt;li&gt;앱별로 서로 다른 비밀번호를 만들 수 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어 Python SMTP용 앱 비밀번호가 노출되었다면 Google 계정의 앱 비밀번호 화면에서 해당 비밀번호를 삭제하면 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다만 앱 비밀번호를 가진 사람은 해당 계정의 SMTP 기능을 이용할 수 있으므로 일반 비밀번호처럼 안전하게 관리해야 합니다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;운영 서비스나 보안 요구 수준이 높은 시스템에서는 앱 비밀번호보다 &lt;b&gt;OAuth 2.0 인증 방식&lt;/b&gt;이 더 적합할 수 있습니다. 교육용 프로그램이나 개인 자동화에서는 앱 비밀번호 방식이 구현하기 쉽습니다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;5. Gmail SMTP 서버 정보&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;STARTTLS 방식&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;항목 설정값&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;SMTP 서버&lt;/td&gt;
&lt;td&gt;smtp.gmail.com&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;포트&lt;/td&gt;
&lt;td&gt;587&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;인증&lt;/td&gt;
&lt;td&gt;사용&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;STARTTLS&lt;/td&gt;
&lt;td&gt;사용&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;사용자 이름&lt;/td&gt;
&lt;td&gt;Gmail 주소&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;비밀번호&lt;/td&gt;
&lt;td&gt;앱 비밀번호&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일반적으로 포트 587과 STARTTLS 방식을 권장합니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;SSL 방식&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;항목 설정값&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;SMTP 서버&lt;/td&gt;
&lt;td&gt;smtp.gmail.com&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;포트&lt;/td&gt;
&lt;td&gt;465&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;SSL&lt;/td&gt;
&lt;td&gt;사용&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;사용자 이름&lt;/td&gt;
&lt;td&gt;Gmail 주소&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;비밀번호&lt;/td&gt;
&lt;td&gt;앱 비밀번호&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;587과 465를 혼합해서 설정하면 SSL 오류가 발생할 수 있으므로 한 가지 방식만 선택해야 합니다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;6. Python에서 메일 발송하기&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Python의 기본 라이브러리인 smtplib와 email을 사용하면 별도의 패키지 설치 없이 메일을 발송할 수 있습니다.&lt;/p&gt;
&lt;pre class=&quot;sas&quot;&gt;&lt;code&gt;import smtplib
from email.message import EmailMessage

SMTP_SERVER = &quot;smtp.gmail.com&quot;
SMTP_PORT = 587

GMAIL_USERNAME = &quot;example@gmail.com&quot;
GMAIL_APP_PASSWORD = &quot;abcdefghijklmnop&quot;

message = EmailMessage()
message[&quot;Subject&quot;] = &quot;Gmail SMTP 발송 테스트&quot;
message[&quot;From&quot;] = GMAIL_USERNAME
message[&quot;To&quot;] = &quot;receiver@example.com&quot;

message.set_content(&quot;Python에서 발송한 테스트 메일입니다.&quot;)

try:
    with smtplib.SMTP(SMTP_SERVER, SMTP_PORT) as smtp:
        smtp.starttls()
        smtp.login(GMAIL_USERNAME, GMAIL_APP_PASSWORD)
        smtp.send_message(message)

    print(&quot;메일 발송이 완료되었습니다.&quot;)

except smtplib.SMTPAuthenticationError:
    print(&quot;SMTP 인증에 실패했습니다. Gmail 주소와 앱 비밀번호를 확인하세요.&quot;)

except smtplib.SMTPException as error:
    print(f&quot;메일 발송 중 SMTP 오류가 발생했습니다: {error}&quot;)

except Exception as error:
    print(f&quot;메일 발송 중 오류가 발생했습니다: {error}&quot;)
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;SMTP 로그인 정보는 다음과 같이 사용됩니다.&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;사용자 이름 = Gmail 전체 주소
비밀번호 = 16자리 앱 비밀번호
&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일반 Google 계정 비밀번호는 입력하지 않습니다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;7. 인증정보를 소스 코드에서 분리하기&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;교육 초기에는 소스 코드에 값을 직접 입력할 수 있지만, 실제 프로젝트에서는 앱 비밀번호를 소스에 작성하지 않는 것이 좋습니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;환경변수 설정 예시&lt;/h3&gt;
&lt;pre class=&quot;ini&quot;&gt;&lt;code&gt;GMAIL_USERNAME=example@gmail.com
GMAIL_APP_PASSWORD=abcdefghijklmnop
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Python에서는 다음과 같이 읽습니다.&lt;/p&gt;
&lt;pre class=&quot;lua&quot;&gt;&lt;code&gt;import os

GMAIL_USERNAME = os.getenv(&quot;GMAIL_USERNAME&quot;)
GMAIL_APP_PASSWORD = os.getenv(&quot;GMAIL_APP_PASSWORD&quot;)

if not GMAIL_USERNAME or not GMAIL_APP_PASSWORD:
    raise ValueError(&quot;Gmail SMTP 인증정보가 설정되지 않았습니다.&quot;)
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;.env 파일을 사용할 경우에는 해당 파일을 GitHub에 올리지 않아야 합니다. .gitignore 파일에 다음 내용을 추가합니다.&lt;/p&gt;
&lt;pre class=&quot;asciidoc&quot;&gt;&lt;code&gt;.env
&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;8. 네트워크 및 방화벽 확인&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;메일을 발송하는 PC나 서버에서 다음 주소와 포트에 접속할 수 있어야 합니다.&lt;/p&gt;
&lt;pre class=&quot;css&quot;&gt;&lt;code&gt;smtp.gmail.com:587
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또는 SSL 방식을 사용한다면:&lt;/p&gt;
&lt;pre class=&quot;css&quot;&gt;&lt;code&gt;smtp.gmail.com:465
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;회사 네트워크나 서버 환경에서는 보안 정책으로 외부 SMTP 포트가 차단될 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;인증정보가 정확한데도 연결 시간 초과가 발생한다면 다음 사항을 확인합니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;인터넷 연결 상태&lt;/li&gt;
&lt;li&gt;회사 방화벽 정책&lt;/li&gt;
&lt;li&gt;서버의 아웃바운드 포트 제한&lt;/li&gt;
&lt;li&gt;보안 프로그램의 SMTP 차단 여부&lt;/li&gt;
&lt;li&gt;DNS에서 smtp.gmail.com을 조회할 수 있는지&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;9. 메일 발송 테스트 순서&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;처음부터 여러 수신자에게 발송하지 말고 다음 순서로 테스트하는 것이 좋습니다.&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;1. 발신 Gmail &amp;rarr; 동일한 Gmail 계정
2. 발신 Gmail &amp;rarr; 다른 Gmail 계정
3. 발신 Gmail &amp;rarr; Outlook 또는 다른 외부 메일
4. 발신 Gmail &amp;rarr; 회사 메일
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;메일이 도착하지 않으면 받은편지함뿐 아니라 스팸함도 확인합니다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;10. 자주 발생하는 오류&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;인증 실패&lt;/h3&gt;
&lt;pre class=&quot;smali&quot;&gt;&lt;code&gt;SMTPAuthenticationError
Username and Password not accepted
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;확인할 내용:&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;일반 Google 비밀번호를 입력하지 않았는지&lt;/li&gt;
&lt;li&gt;16자리 앱 비밀번호를 사용했는지&lt;/li&gt;
&lt;li&gt;Gmail 주소 전체를 사용자 이름으로 입력했는지&lt;/li&gt;
&lt;li&gt;앱 비밀번호를 복사하면서 일부 문자가 빠지지 않았는지&lt;/li&gt;
&lt;li&gt;앱 비밀번호를 삭제한 적이 없는지&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;연결 시간 초과&lt;/h3&gt;
&lt;pre class=&quot;pgsql&quot;&gt;&lt;code&gt;Connection timed out
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;확인할 내용:&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;인터넷 연결&lt;/li&gt;
&lt;li&gt;방화벽&lt;/li&gt;
&lt;li&gt;smtp.gmail.com 접속 가능 여부&lt;/li&gt;
&lt;li&gt;포트 587 또는 465 허용 여부&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;SSL 또는 TLS 오류&lt;/h3&gt;
&lt;pre class=&quot;applescript&quot;&gt;&lt;code&gt;SSL error
TLS error
Wrong version number
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음 설정 조합을 확인합니다.&lt;/p&gt;
&lt;pre class=&quot;gcode&quot;&gt;&lt;code&gt;포트 587 &amp;rarr; smtplib.SMTP() + starttls()
포트 465 &amp;rarr; smtplib.SMTP_SSL()
&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;포트 465에서 starttls()를 사용하거나, 포트 587에서 처음부터 SMTP_SSL()을 사용하면 오류가 발생할 수 있습니다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;11. 앱 비밀번호 관리&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;앱 비밀번호는 Google 계정의 앱 비밀번호 화면에서 관리할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음과 같은 경우에는 기존 앱 비밀번호를 삭제하고 새로 만드는 것이 좋습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;비밀번호가 외부에 노출된 경우&lt;/li&gt;
&lt;li&gt;GitHub에 실수로 등록한 경우&lt;/li&gt;
&lt;li&gt;사용하지 않는 프로그램의 비밀번호인 경우&lt;/li&gt;
&lt;li&gt;어느 프로그램에서 사용하는 비밀번호인지 알 수 없는 경우&lt;/li&gt;
&lt;li&gt;프로그램이나 서버를 폐기한 경우&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;앱 비밀번호를 삭제하면 해당 비밀번호를 사용하던 SMTP 프로그램은 더 이상 로그인할 수 없습니다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;최종 체크리스트&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;[ ] Gmail 계정을 준비했다.&lt;/li&gt;
&lt;li&gt;[ ] Google 계정에서 2단계 인증을 활성화했다.&lt;/li&gt;
&lt;li&gt;[ ] 메뉴가 보이지 않을 경우 검색창에서 앱 비밀번호를 검색했다.&lt;/li&gt;
&lt;li&gt;[ ] 앱 비밀번호를 생성했다.&lt;/li&gt;
&lt;li&gt;[ ] SMTP 사용자 이름에 Gmail 전체 주소를 입력했다.&lt;/li&gt;
&lt;li&gt;[ ] 일반 계정 비밀번호 대신 앱 비밀번호를 입력했다.&lt;/li&gt;
&lt;li&gt;[ ] SMTP 서버를 smtp.gmail.com으로 설정했다.&lt;/li&gt;
&lt;li&gt;[ ] 포트 587과 STARTTLS를 사용했다.&lt;/li&gt;
&lt;li&gt;[ ] 인증정보를 환경변수로 관리했다.&lt;/li&gt;
&lt;li&gt;[ ] .env 파일을 Git 저장소에서 제외했다.&lt;/li&gt;
&lt;li&gt;[ ] 방화벽에서 SMTP 접속이 가능한지 확인했다.&lt;/li&gt;
&lt;li&gt;[ ] 테스트 메일 발송을 완료했다.&lt;/li&gt;
&lt;li&gt;[ ] 노출된 앱 비밀번호는 즉시 삭제하고 다시 생성했다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;fileblock&quot; data-ke-align=&quot;alignCenter&quot;&gt;&lt;a href=&quot;https://blog.kakaocdn.net/dn/umizs/dJMcaasKmjI/EnTGjSwpTVQhsksrsepGtK/Python_Gmail_SMTP_%EA%B0%80%EC%9D%B4%EB%93%9C.md.pdf?attach=1&amp;amp;knm=tfile.pdf&quot; class=&quot;&quot;&gt;
    &lt;div class=&quot;image&quot;&gt;&lt;/div&gt;
    &lt;div class=&quot;desc&quot;&gt;&lt;div class=&quot;filename&quot;&gt;&lt;span class=&quot;name&quot;&gt;Python_Gmail_SMTP_가이드.md.pdf&lt;/span&gt;&lt;/div&gt;
&lt;div class=&quot;size&quot;&gt;0.40MB&lt;/div&gt;
&lt;/div&gt;
  &lt;/a&gt;&lt;/figure&gt;
&lt;/p&gt;</description>
      <category>파이썬</category>
      <category>2단계인증</category>
      <category>gmail smtp</category>
      <category>smtplib</category>
      <category>starttls</category>
      <category>구글계정보안</category>
      <category>앱비밀번호</category>
      <category>파이썬</category>
      <category>파이썬이메일발송</category>
      <category>파이썬자동화</category>
      <category>파이썬튜토리얼</category>
      <author>바리새인</author>
      <guid isPermaLink="true">https://barisein.tistory.com/1329</guid>
      <comments>https://barisein.tistory.com/1329#entry1329comment</comments>
      <pubDate>Tue, 4 Aug 2026 09:50:48 +0900</pubDate>
    </item>
    <item>
      <title>Claude.ai /honest 명령어 관련 정리</title>
      <link>https://barisein.tistory.com/1328</link>
      <description>&lt;h1&gt;/honest 명령어 관련 정리&lt;/h1&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;질문 1: /honest라는 명령이 있어?&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;claude.ai 채팅 인터페이스에는 그런 내장 명령어가 없습니다. Claude에는 미리 정의된 슬래시 커맨드 목록이 따로 존재하지 않습니다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;질문 2: 사용자가 커스텀 명령어를 만들 수 있어?&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;claude.ai 자체에는 사용자 정의 슬래시 커맨드 기능이 없습니다. 다만 비슷한 효과는 다음으로 낼 수 있습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;Projects의 커스텀 지침(instructions)&lt;/b&gt;: 프로젝트 안에서 항상 특정 방식으로 답변하게 설정&lt;/li&gt;
&lt;li&gt;&lt;b&gt;스타일(Style) 기능&lt;/b&gt;: 원하는 말투나 포맷을 저장해두고 대화마다 적용&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;반면 개발자용 도구인 &lt;b&gt;Claude Code&lt;/b&gt;에는 실제로 사용자가 만드는 커스텀 슬래시 커맨드 기능이 있습니다. (.claude/commands/ 또는 .claude/skills/ 폴더 사용)&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;질문 3: Claude Code에서 /honest라는 명령이 있나?&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;/honest는 Claude Code의 &lt;b&gt;공식 내장 명령어가 아닙니다&lt;/b&gt;.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Claude Code의 대표적인 내장 명령어:&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;/clear - 대화 컨텍스트 초기화&lt;/li&gt;
&lt;li&gt;/compact - 이전 대화 압축&lt;/li&gt;
&lt;li&gt;/help - 명령어 목록 보기&lt;/li&gt;
&lt;li&gt;/model - 모델 변경&lt;/li&gt;
&lt;li&gt;/review, /resume, /agents, /mcp 등&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다만 .claude/commands/ 또는 .claude/skills/ 폴더에 마크다운 파일을 만들면 사용자가 직접 /honest라는 커스텀 명령어를 만들 수 있습니다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;/honest 커스텀 명령어 등록 샘플&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;파일 경로: .claude/commands/honest.md&lt;/p&gt;
&lt;pre class=&quot;yaml&quot;&gt;&lt;code&gt;---
description: 현재 코드나 작업 상태에 대해 솔직하고 비판적인 평가를 요청
allowed-tools: Read, Glob, Grep
---

지금까지 작업한 코드/변경사항을 최대한 솔직하게 평가해줘. 다음 기준으로 답변해:

1. **잘된 점**: 실제로 잘 작동하거나 잘 설계된 부분만 간단히
2. **문제점**: 숨기지 말고 지적할 것
   - 버그 가능성이 있는 부분
   - 성능/보안상 우려되는 부분
   - 확장성이나 유지보수 측면에서 아쉬운 부분
3. **미완성/누락**: 아직 처리되지 않았거나 테스트되지 않은 부분
4. **개선 제안**: 구체적으로 무엇을, 왜 바꿔야 하는지

칭찬 위주로 포장하지 말고, 실제로 문제가 있으면 명확히 &quot;이건 문제가 있다&quot;고 말해줘. 애매하면 &quot;확실하지 않다&quot;고 솔직히 말해줘.

$ARGUMENTS
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;사용 방법&lt;/h3&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;프로젝트 루트에 .claude/commands/ 폴더가 없으면 생성&lt;/li&gt;
&lt;li&gt;위 파일을 .claude/commands/honest.md 경로에 저장&lt;/li&gt;
&lt;li&gt;Claude Code에서 /honest라고 입력하면 이 지침이 실행됨&lt;/li&gt;
&lt;li&gt;필요하면 /honest 이 함수만 봐줘 처럼 뒤에 인자를 붙일 수 있음 ($ARGUMENTS에 들어감)&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;참고&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;상단의 ---로 감싼 부분(YAML frontmatter)은 선택사항이며, description은 명령어 설명, allowed-tools는 이 명령어 실행 시 자동으로 허용할 도구를 지정한 것&lt;/li&gt;
&lt;li&gt;팀 전체가 같이 쓰려면 이 파일을 Git에 커밋해서 공유하면 됨&lt;/li&gt;
&lt;li&gt;개인 전역 명령어로 쓰려면 ~/.claude/commands/honest.md처럼 홈 디렉토리에 저장하면 모든 프로젝트에서 사용 가능&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;결론&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;/honest는 어떤 Claude 제품에도 기본 내장된 명령어가 아니며, Claude Code에서만 사용자가 직접 만들 수 있는 종류의 기능입니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;fileblock&quot; data-ke-align=&quot;alignCenter&quot;&gt;&lt;a href=&quot;https://blog.kakaocdn.net/dn/9xmCk/dJMcaiqHAK2/5UoGaUpgyfzSFcnK7SOBK1/claude-code-honest-command-summary.md.pdf?attach=1&amp;amp;knm=tfile.pdf&quot; class=&quot;&quot;&gt;
    &lt;div class=&quot;image&quot;&gt;&lt;/div&gt;
    &lt;div class=&quot;desc&quot;&gt;&lt;div class=&quot;filename&quot;&gt;&lt;span class=&quot;name&quot;&gt;claude-code-honest-command-summary.md.pdf&lt;/span&gt;&lt;/div&gt;
&lt;div class=&quot;size&quot;&gt;0.22MB&lt;/div&gt;
&lt;/div&gt;
  &lt;/a&gt;&lt;/figure&gt;
&lt;/p&gt;</description>
      <category>AI</category>
      <category>#honest #ClaudeCode #슬래시명령어 #CustomCommands #claude.ai #커스텀명령어 #.claude/commands #SKILL.md #Anthropic #개발자도구</category>
      <author>바리새인</author>
      <guid isPermaLink="true">https://barisein.tistory.com/1328</guid>
      <comments>https://barisein.tistory.com/1328#entry1328comment</comments>
      <pubDate>Mon, 3 Aug 2026 21:16:15 +0900</pubDate>
    </item>
    <item>
      <title>AI 개발에서 뒤늦게 깨달은 것들: 코딩보다 먼저 정해야 하는 것들</title>
      <link>https://barisein.tistory.com/1327</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;AI와 함께 소프트웨어를 만들다 보면 처음에는 속도에 놀라게 된다.&lt;br /&gt;아이디어를 말하면 화면이 나오고, 기능을 설명하면 코드가 만들어지고, 문서를 요청하면 순식간에 초안이 생긴다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 프로젝트를 조금만 이어가다 보면 다른 문제가 드러난다.&lt;br /&gt;빠르게 만들어진 결과가 정말 내가 원한 것인지, 나중에 수정할 수 있는 구조인지, 요구사항과 설계가 서로 맞는지 확인해야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번 프로젝트를 진행하면서 가장 크게 느낀 점은 이것이었다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AI 개발에서도 소프트웨어 개발의 기본은 사라지지 않는다.&lt;br /&gt;오히려 AI가 빠르게 만들어주기 때문에 기준을 더 빨리 잡아야 한다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;AI는 속도를 높여주지만 방향을 정해주지는 않는다&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AI는 모호한 요청에도 그럴듯한 결과를 만들어낸다.&lt;br /&gt;이건 장점이기도 하지만 위험이기도 하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;요구사항이 흐리면 AI는 빈칸을 스스로 채운다.&lt;br /&gt;그러면 결과물은 빠르게 나오지만, 내가 원한 소프트웨어와 조금씩 멀어질 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 AI 개발에서 먼저 해야 할 일은 코딩이 아니었다.&lt;br /&gt;무엇을 만들 것인지, 누구를 위한 것인지, 어디까지 만들 것인지, 무엇은 하지 않을 것인지를 먼저 정해야 했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;처음 요구사항이 완벽할 필요는 없다.&lt;br /&gt;하지만 최소한의 기준은 있어야 한다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;목적&lt;/li&gt;
&lt;li&gt;사용자&lt;/li&gt;
&lt;li&gt;핵심 기능&lt;/li&gt;
&lt;li&gt;제외할 기능&lt;/li&gt;
&lt;li&gt;완료 기준&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 기준이 있어야 AI가 만든 결과를 보고 &amp;ldquo;맞다&amp;rdquo; 또는 &amp;ldquo;아니다&amp;rdquo;라고 판단할 수 있다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;요구사항은 목록이 아니라 구조로 관리해야 한다&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;처음에는 요구사항을 목록으로 정리하면 충분해 보인다.&lt;br /&gt;하지만 요구사항이 늘어나면 단순 목록은 금방 한계에 부딪힌다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;어떤 요구사항이 다른 요구사항을 포함하는지, 어떤 요구사항이 중복되는지, 어떤 요구사항이 서로 충돌하는지 알기 어려워진다.&lt;br /&gt;하나를 바꾸면 어디까지 영향을 주는지도 추적하기 힘들다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 요구사항은 단순한 문장이 아니라 관계를 가진 구조로 관리해야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;각 요구사항에는 ID가 있어야 하고, 요구사항 사이에는 관계가 있어야 한다.&lt;br /&gt;포함, 세분화, 중복, 충돌, 의존, 관련, 대체 같은 관계를 명시해야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 해야 요구사항이 쌓여도 전체 구조를 잃지 않는다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;사람에게는 추상화된 보기가 필요하다&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;요구사항을 꼼꼼히 기록하는 것만으로는 부족했다.&lt;br /&gt;사람은 모든 요구사항을 한 번에 읽고 이해하기 어렵다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 상세 문서와 함께 추상화된 보기가 필요했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들면 이런 것들이다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;전체 관계를 보는 그래프&lt;/li&gt;
&lt;li&gt;특정 요구사항 주변만 보는 포커스 보기&lt;/li&gt;
&lt;li&gt;상위 요구사항에서 하위 요구사항으로 내려가는 브레이크다운 보기&lt;/li&gt;
&lt;li&gt;여러 세부 요구사항을 큰 개념으로 묶어 보는 추상화 보기&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;결국 요구사항 관리는 단순 문서 관리가 아니라, 사람이 복잡한 구조를 이해할 수 있게 돕는 도구가 되어야 했다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;분석은 생략하면 안 되는 단계였다&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;요구사항을 정리한 뒤 바로 설계로 넘어가고 싶어진다.&lt;br /&gt;하지만 실제로 해보니 요구사항과 설계 사이에는 분석 단계가 필요했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;분석은 &amp;ldquo;무엇을 만들 것인가&amp;rdquo;를 &amp;ldquo;어떤 문제 구조인가&amp;rdquo;로 바꾸는 과정이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 단계에서 도메인 개념, 상태, 관계, 업무 규칙이 드러난다.&lt;br /&gt;예를 들어 요구사항 삭제를 어떻게 처리할지, 관계가 있는 요구사항은 삭제할 수 있는지, 삭제된 항목을 조회할 수 있는지 같은 문제는 단순 UI 문제가 아니었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이런 규칙을 분석에서 정리하지 않으면 설계와 구현 단계에서 계속 다시 결정해야 한다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;MVP는 작게 만드는 것이 아니라 검증 가능한 범위를 정하는 것이다&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MVP를 처음에는 &amp;ldquo;작게 만든 첫 버전&amp;rdquo; 정도로 생각하기 쉽다.&lt;br /&gt;하지만 이번 프로젝트에서 MVP는 &amp;ldquo;검증 가능한 첫 범위&amp;rdquo;에 더 가까웠다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;무엇을 넣을지보다 중요한 것은 무엇을 뺄지였다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어 저장형 보기는 나중에 필요할 수 있지만 MVP에서는 제외했다.&lt;br /&gt;대신 요구사항 CRUD, 관계 CRUD, 기본 그래프 조회, Mermaid 출력처럼 첫 버전에서 검증해야 할 핵심만 남겼다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MVP에는 포함 범위뿐 아니라 제외 범위, 저장 포맷, API 응답, 완료 기준, 검증 체크리스트까지 있어야 했다.&lt;br /&gt;그래야 첫 버전이 끝났는지 판단할 수 있다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;용어집은 나중에 만들면 비용이 커진다&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;가장 체감이 컸던 부분 중 하나는 용어였다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;처음에는 비슷한 말을 섞어 써도 큰 문제가 없어 보였다.&lt;br /&gt;하지만 문서가 늘어나자 같은 개념을 여러 표현으로 부르고 있었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어 View, 뷰, 롤업, 추상화 보기 같은 표현이 섞이면 나중에 대표 용어를 정리할 때 요구사항, 분석, 설계, MVP 문서를 모두 다시 확인해야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 경험을 통해 용어집은 문서의 부록이 아니라는 걸 알게 됐다.&lt;br /&gt;용어집은 사람과 AI가 같은 개념을 같은 말로 다루기 위한 작업 도구다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;새로운 핵심 개념이 나오면 먼저 대표 용어를 정하고, 사람이 이해할 수 있도록 유사어를 함께 기록해야 한다.&lt;br /&gt;AI는 대표 용어를 사용하고, 사람은 유사어를 통해 검색하고 이해할 수 있어야 한다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;검증은 나누어 보고 마지막에 연결해서 봐야 한다&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;검증도 한 번에 전체를 보면 놓치는 것이 생긴다.&lt;br /&gt;반대로 부분만 보면 전체 흐름이 끊긴 것을 놓칠 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 검증은 두 단계가 필요했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;먼저 나누어 검증한다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;산출물 구조 검증&lt;/li&gt;
&lt;li&gt;내용 정합성 검증&lt;/li&gt;
&lt;li&gt;방법론/인수인계 검증&lt;/li&gt;
&lt;li&gt;통합 흐름 검증&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 수정 후에는 전체 흐름을 다시 본다.&lt;br /&gt;요구사항, 용어집, 분석, 설계, MVP, 로그, 방법론이 서로 이어지는지 확인해야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또 하나 중요한 점은 검증과 수정을 섞지 않는 것이다.&lt;br /&gt;검증 단계에서는 먼저 문제를 찾고, 수정은 발견사항을 모은 뒤 묶어서 하는 것이 더 효율적이었다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;로그는 산출물이 아니라 시간순 증거다&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프로젝트를 진행하면서 로그의 역할도 다시 생각하게 됐다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;산출물은 현재 기준을 정리한 문서다.&lt;br /&gt;요구사항 문서, 분석 문서, 설계 문서, MVP 명세, 용어집 같은 것들이 여기에 해당한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;반면 로그는 현재 기준을 다시 정리하는 문서가 아니다.&lt;br /&gt;로그는 그 기준이 어떤 대화와 판단을 거쳐 만들어졌는지 남기는 시간순 기록이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;나중에 검증하려면 로그가 필요하다.&lt;br /&gt;왜 그런 결정을 했는지, 어떤 질문이 있었는지, AI가 어떤 판단을 했고 사람이 어떻게 조율했는지 확인할 수 있어야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정리는 산출물에서 하고, 흐름은 로그에 남긴다.&lt;br /&gt;이 구분이 중요했다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;좋은 방식은 규칙과 템플릿으로 남겨야 한다&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;작업 중에 좋은 방식이 생겨도 문서화하지 않으면 다음 세션에서 반복되지 않는다.&lt;br /&gt;특히 AI와 작업할 때는 세션이 바뀌면 이전 맥락이 사라질 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 반복되는 방식은 규칙과 템플릿으로 승격해야 한다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;새 세션이 먼저 읽을 문서&lt;/li&gt;
&lt;li&gt;작업 에이전트의 기본 규칙&lt;/li&gt;
&lt;li&gt;검증 에이전트의 기준&lt;/li&gt;
&lt;li&gt;요구사항 템플릿&lt;/li&gt;
&lt;li&gt;분석 템플릿&lt;/li&gt;
&lt;li&gt;로그 템플릿&lt;/li&gt;
&lt;li&gt;새 프로젝트 체크리스트&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이런 것들이 있어야 다음 작업도 같은 기준에서 시작할 수 있다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;정리하며&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번 프로젝트를 통해 얻은 가장 큰 교훈은 단순하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AI로 소프트웨어를 만들수록 대충 시작해도 되는 것이 아니다.&lt;br /&gt;오히려 기준을 더 빨리 잡아야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;요구사항을 먼저 잡고, 용어를 통일하고, 분석을 분리하고, MVP 범위를 고정하고, 검증 방식을 정하고, 로그를 남겨야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;나중에 정리해도 된다고 생각한 것들은 결국 더 큰 비용으로 돌아왔다.&lt;br /&gt;용어집을 늦게 만든 것도 그랬고, 로그의 목적을 뒤늦게 분명히 한 것도 그랬다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AI 개발에서 중요한 것은 AI가 코드를 얼마나 빨리 만드는지가 아니었다.&lt;br /&gt;사람이 얼마나 명확하게 기준을 세우고, AI와의 대화를 그 기준 안에서 관리하느냐였다.&lt;/p&gt;</description>
      <category>AI</category>
      <category>티스토리 태그는 이 정도가 적절합니다. - AI개발 - 소프트웨어개발 - 개발방법론 - 요구사항정의 - 시스템분석 - MVP - 용어집 - 검증 - 개발회고 - 바이브코딩</category>
      <author>바리새인</author>
      <guid isPermaLink="true">https://barisein.tistory.com/1327</guid>
      <comments>https://barisein.tistory.com/1327#entry1327comment</comments>
      <pubDate>Sat, 1 Aug 2026 14:14:50 +0900</pubDate>
    </item>
    <item>
      <title>AI 시대의 소프트웨어 개발 방법론: 사람 주도 AI 개발</title>
      <link>https://barisein.tistory.com/1326</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;AI로 코드를 만드는 일은 이제 특별한 일이 아니다.&lt;br /&gt;간단한 웹앱, 자동화 스크립트, 내부 도구, 문서 정리 도구 정도는 자연어 설명만으로도 빠르게 만들 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 막상 작업을 해보면 한 가지 문제가 반복된다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AI는 빠르게 만들지만, 내가 무엇을 만들고 있는지 놓치면 결과물도 쉽게 흔들린다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;처음에는 &amp;ldquo;이런 기능 만들어줘&amp;rdquo;로 시작한다.&lt;br /&gt;AI는 코드를 작성한다.&lt;br /&gt;조금 고쳐달라고 하면 또 고친다.&lt;br /&gt;그런데 어느 순간부터 요구사항이 섞이고, 이전 결정이 사라지고, 기능의 기준이 애매해진다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;결국 문제는 AI의 속도가 아니라 &lt;b&gt;개발을 이끄는 기준이 없다는 것&lt;/b&gt;이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 글은 내가 정리하고 있는 &lt;b&gt;사람 주도 AI 소프트웨어 개발 방법론&lt;/b&gt;의 첫 번째 글이다.&lt;br /&gt;이번 글에서는 전체 컨셉과 목적을 설명하고, 이후 글에서 요구사항, 분석, 설계, MVP, 검증 단계를 하나씩 다룰 예정이다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;왜 방법론이 필요한가&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AI 개발 도구를 쓰면 구현 속도는 확실히 빨라진다.&lt;br /&gt;하지만 속도가 빨라진 만큼 다음 문제가 더 자주 생긴다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;요구사항이 명확하지 않은 상태로 구현이 시작된다.&lt;/li&gt;
&lt;li&gt;대화 중 결정한 내용이 문서에 남지 않는다.&lt;/li&gt;
&lt;li&gt;기능을 추가할수록 처음 의도와 달라진다.&lt;/li&gt;
&lt;li&gt;AI가 이전 맥락을 잘못 기억하거나 누락한다.&lt;/li&gt;
&lt;li&gt;검증 없이 &amp;ldquo;그럴듯한 결과&amp;rdquo;를 완료로 착각한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AI가 코드를 잘 만든다고 해서 소프트웨어 개발의 기본 단계가 사라지는 것은 아니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;오히려 AI를 제대로 쓰려면 사람이 더 명확하게 해야 한다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;무엇을 만들 것인가&lt;/li&gt;
&lt;li&gt;왜 만드는가&lt;/li&gt;
&lt;li&gt;어디까지 만들 것인가&lt;/li&gt;
&lt;li&gt;어떤 기준이면 완료인가&lt;/li&gt;
&lt;li&gt;무엇은 이번 버전에서 하지 않을 것인가&lt;/li&gt;
&lt;li&gt;변경과 결정은 어디에 기록할 것인가&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 기준이 있어야 AI는 단순한 코드 생성기가 아니라 개발을 돕는 작업 에이전트가 된다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;사람 주도 AI 개발이란&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사람 주도 AI 개발은 말 그대로 &lt;b&gt;사람이 방향과 판단 기준을 잡고, AI가 정리와 구현과 검증을 돕는 개발 방식&lt;/b&gt;이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;핵심은 AI에게 모든 판단을 맡기지 않는 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AI는 빠르게 정리하고, 코드를 만들고, 문서를 비교하고, 누락을 찾는 데 강하다.&lt;br /&gt;하지만 &amp;ldquo;이 제품이 왜 필요한가&amp;rdquo;, &amp;ldquo;이번 버전에서 무엇을 제외할 것인가&amp;rdquo;, &amp;ldquo;이 결정이 비즈니스나 사용자에게 맞는가&amp;rdquo; 같은 판단은 사람이 해야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 이 방법론에서는 역할을 이렇게 나눈다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;역할&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;책임
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;사람&lt;/td&gt;
&lt;td&gt;목적, 우선순위, 최종 판단&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;작업 에이전트&lt;/td&gt;
&lt;td&gt;문서 작성, 설계 보강, 구현, 로그 기록&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;검증 에이전트&lt;/td&gt;
&lt;td&gt;독립 검증, 불일치 탐지, 누락 확인&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AI는 개발을 대신하는 존재라기보다, 사람이 정한 기준을 빠르게 실행하고 점검하는 협업자에 가깝다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;전체 흐름&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 방법론의 기본 흐름은 다음과 같다.&lt;/p&gt;
&lt;pre class=&quot;clean&quot;&gt;&lt;code&gt;의도 정의
-&amp;gt; 요구사항 정의
-&amp;gt; 분석
-&amp;gt; 관계와 모순 정리
-&amp;gt; 설계
-&amp;gt; MVP 범위 고정
-&amp;gt; 구현
-&amp;gt; 검증
-&amp;gt; 로그 기록
-&amp;gt; 피드백
-&amp;gt; 규칙/템플릿 개선&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;각 단계는 다음 질문에 답하기 위한 것이다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;단계&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; 핵심 질문
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;의도 정의&lt;/td&gt;
&lt;td&gt;왜 이걸 만드는가?&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;요구사항 정의&lt;/td&gt;
&lt;td&gt;무엇을 만들어야 하는가?&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;분석&lt;/td&gt;
&lt;td&gt;이것은 어떤 문제 구조인가?&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;관계 정리&lt;/td&gt;
&lt;td&gt;요구사항끼리 어떻게 연결되는가?&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;설계&lt;/td&gt;
&lt;td&gt;어떤 구조로 만들 것인가?&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;MVP&lt;/td&gt;
&lt;td&gt;첫 버전은 어디까지인가?&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;구현&lt;/td&gt;
&lt;td&gt;실제로 무엇을 바꿀 것인가?&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;검증&lt;/td&gt;
&lt;td&gt;기준대로 되었는가?&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;로그&lt;/td&gt;
&lt;td&gt;왜 그렇게 결정했는가?&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;피드백&lt;/td&gt;
&lt;td&gt;다음에는 무엇을 개선할 것인가?&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;중요한 점은 이 흐름이 거창한 대형 프로젝트에만 필요한 것이 아니라는 점이다.&lt;br /&gt;개인 홈페이지, 작은 업무 도구, 자동화 스크립트에도 적용할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;작은 프로젝트일수록 문서는 짧아도 된다.&lt;br /&gt;다만 기준은 있어야 한다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;이 방법론에서 중요한 산출물&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 방식에서는 대화만 믿지 않고, 기준을 문서로 남긴다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기본 산출물은 다음과 같다.&lt;/p&gt;
&lt;pre class=&quot;dos&quot;&gt;&lt;code&gt;README.md
AGENTS.md
docs/session-start.md
docs/requirements.md
docs/analysis.md
docs/system-design.md
docs/mvp-spec.md
docs/verification-agent.md
logs/conversation-log.md&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;각 문서의 역할은 분명히 나눈다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;문서&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; 역할
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;requirements.md&lt;/td&gt;
&lt;td&gt;무엇을 만들지 정의&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;analysis.md&lt;/td&gt;
&lt;td&gt;요구사항을 문제 구조로 분석&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;system-design.md&lt;/td&gt;
&lt;td&gt;어떤 구조로 만들지 정의&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;mvp-spec.md&lt;/td&gt;
&lt;td&gt;첫 버전의 정확한 범위 정의&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;verification-agent.md&lt;/td&gt;
&lt;td&gt;검증 기준 정의&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;conversation-log.md&lt;/td&gt;
&lt;td&gt;결정과 변경 이력 기록&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;AGENTS.md&lt;/td&gt;
&lt;td&gt;작업 에이전트가 따라야 할 규칙&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 나누는 이유는 간단하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;요구사항, 분석, 설계, 로그가 한 문서에 섞이면 나중에 기준을 복원하기 어렵다.&lt;br /&gt;AI에게 다시 작업을 맡길 때도 어떤 문서를 우선해야 하는지 모호해진다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;문서의 목적이 분리되어 있어야 다음 세션에서도 같은 기준으로 이어갈 수 있다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;검증은 왜 따로 두는가&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AI와 함께 작업할 때 특히 중요한 단계가 검증이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;작업을 한 에이전트는 자신이 만든 결과를 그럴듯하게 설명할 수 있다.&lt;br /&gt;하지만 설명이 그럴듯하다고 해서 결과가 맞는 것은 아니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 검증은 가능하면 독립적으로 수행한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;검증 기준은 네 가지로 나눈다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;산출물 구조 검증&lt;/li&gt;
&lt;li&gt;내용 정합성 검증&lt;/li&gt;
&lt;li&gt;방법론/인수인계 검증&lt;/li&gt;
&lt;li&gt;통합 흐름 검증&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;나눠서 보면 세부 문제를 잘 찾을 수 있고, 마지막에 통합 흐름으로 보면 전체 연결이 끊기지 않았는지 확인할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또한 검증 후 수정은 다음 흐름을 따른다.&lt;/p&gt;
&lt;pre class=&quot;clean&quot;&gt;&lt;code&gt;분할 검증
-&amp;gt; 발견사항 목록화와 우선순위 결정
-&amp;gt; 묶음 수정
-&amp;gt; 통합 검증
-&amp;gt; 연결 문제 수정
-&amp;gt; 최종 스모크 검증&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, 문제를 발견하자마자 하나씩 고치기보다 먼저 모아서 우선순위를 정한다.&lt;br /&gt;그 다음 같은 원인을 가진 문제를 묶어서 수정하고, 마지막에 전체 흐름을 다시 확인한다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;이 방법론의 목적&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 방법론의 목적은 AI를 쓰면서도 개발 기준을 잃지 않는 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AI가 코드를 빠르게 만드는 시대에는 &amp;ldquo;얼마나 빨리 만들었는가&amp;rdquo;보다 &amp;ldquo;무엇을 기준으로 만들었는가&amp;rdquo;가 더 중요해진다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사람 주도 AI 개발은 다음을 목표로 한다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;요구사항을 명확히 기록한다.&lt;/li&gt;
&lt;li&gt;분석을 통해 문제 구조를 드러낸다.&lt;/li&gt;
&lt;li&gt;MVP 범위를 분명히 정한다.&lt;/li&gt;
&lt;li&gt;구현 전에 판단 기준을 만든다.&lt;/li&gt;
&lt;li&gt;검증을 통해 누락과 불일치를 찾는다.&lt;/li&gt;
&lt;li&gt;결정과 변경 이유를 로그로 남긴다.&lt;/li&gt;
&lt;li&gt;반복되는 방식은 규칙과 템플릿으로 만든다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;결국 핵심은 하나다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AI가 빠르게 달릴 수 있도록, 사람이 방향과 기준을 세운다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;다음 글&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음 글에서는 첫 번째 단계인 &lt;b&gt;요구사항 정의&lt;/b&gt;를 다룰 예정이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;단순히 &amp;ldquo;기능 목록을 적는 것&amp;rdquo;이 아니라, AI와 협업하기 위해 요구사항을 어떻게 나누고, ID를 붙이고, MVP와 후속 범위를 어떻게 구분할지 정리해보겠다.&lt;/p&gt;</description>
      <category>AI</category>
      <category>추천 태그: `AI개발` `소프트웨어개발방법론` `사람주도AI` `바이브코딩` `요구사항정의` `소프트웨어설계` `MVP` `검증자동화` `개발문서화` `AI협업`</category>
      <author>바리새인</author>
      <guid isPermaLink="true">https://barisein.tistory.com/1326</guid>
      <comments>https://barisein.tistory.com/1326#entry1326comment</comments>
      <pubDate>Sat, 1 Aug 2026 13:13:29 +0900</pubDate>
    </item>
    <item>
      <title>기존 로컬 프로젝트를 GitHub Repository에 연결하기</title>
      <link>https://barisein.tistory.com/1325</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;로컬에서 이미 개발 중인 디렉터리가 있다면 &lt;b&gt;다시 Clone하지 않고&lt;/b&gt;, 그 디렉터리를 Git Repository로 만든 뒤 GitHub Repository를 origin으로 연결하면 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예:&lt;/p&gt;
&lt;pre class=&quot;groovy&quot;&gt;&lt;code&gt;로컬: C:\work\company-homepage
GitHub: https://github.com/계정/company-homepage.git
&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;1. VS Code에서 기존 폴더 열기&lt;/h2&gt;
&lt;pre class=&quot;coq&quot;&gt;&lt;code&gt;File &amp;rarr; Open Folder...
&amp;rarr; C:\work\company-homepage 선택
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;VS Code 터미널을 엽니다.&lt;/p&gt;
&lt;pre class=&quot;ada&quot;&gt;&lt;code&gt;Terminal &amp;rarr; New Terminal
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;현재 위치 확인:&lt;/p&gt;
&lt;pre class=&quot;bash&quot;&gt;&lt;code&gt;cd
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;반드시 개발 중인 프로젝트 디렉터리인지 확인합니다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;2. Git Repository 초기화&lt;/h2&gt;
&lt;pre class=&quot;ebnf&quot;&gt;&lt;code&gt;git init
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;확인:&lt;/p&gt;
&lt;pre class=&quot;ebnf&quot;&gt;&lt;code&gt;git status
&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;3. .gitignore 먼저 생성&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Vue 프로젝트 예:&lt;/p&gt;
&lt;pre class=&quot;jboss-cli&quot;&gt;&lt;code&gt;node_modules/
dist/
.env
.env.*
*.log
.vscode/
&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;.env나 API 인증키가 Commit되지 않도록 git add보다 먼저 만드는 것이 중요합니다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;4. 기존 파일을 첫 번째 Commit으로 저장&lt;/h2&gt;
&lt;pre class=&quot;dockerfile&quot;&gt;&lt;code&gt;git add .
git status
git commit -m &quot;기존 프로젝트 최초 등록&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Commit할 사용자 정보가 설정되지 않았다는 오류가 나오면:&lt;/p&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;git config --global user.name &quot;GitHub계정명&quot;
git config --global user.email &quot;GitHub이메일&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다시 Commit합니다.&lt;/p&gt;
&lt;pre class=&quot;nginx&quot;&gt;&lt;code&gt;git commit -m &quot;기존 프로젝트 최초 등록&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;5. 기본 Branch를 main으로 설정&lt;/h2&gt;
&lt;pre class=&quot;armasm&quot;&gt;&lt;code&gt;git branch -M main
&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;6. GitHub Repository 연결&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;GitHub Repository의 &amp;lt; &amp;gt; Code &amp;rarr; HTTPS에서 URL을 복사합니다.&lt;/p&gt;
&lt;pre class=&quot;vim&quot;&gt;&lt;code&gt;git remote add origin https://github.com/계정명/company-homepage.git
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;연결 확인:&lt;/p&gt;
&lt;pre class=&quot;ebnf&quot;&gt;&lt;code&gt;git remote -v
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;결과 예:&lt;/p&gt;
&lt;pre class=&quot;awk&quot;&gt;&lt;code&gt;origin  https://github.com/계정명/company-homepage.git (fetch)
origin  https://github.com/계정명/company-homepage.git (push)
&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;경우 1: GitHub Repository가 비어 있는 경우&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;GitHub Repository를 만들 때 README, .gitignore, License를 추가하지 않았다면 바로 Push합니다.&lt;/p&gt;
&lt;pre class=&quot;maxima&quot;&gt;&lt;code&gt;git push -u origin main
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;-u는 로컬 main과 GitHub main을 연결하는 옵션입니다. 이후에는 간단히 실행할 수 있습니다.&lt;/p&gt;
&lt;pre class=&quot;properties&quot;&gt;&lt;code&gt;git push
git pull
&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;경우 2: GitHub에 README가 있는 경우&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;GitHub에서 README를 만들었다면 로컬과 GitHub에 서로 다른 최초 Commit이 존재합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;바로 Push하면 다음과 비슷한 오류가 발생할 수 있습니다.&lt;/p&gt;
&lt;pre class=&quot;applescript&quot;&gt;&lt;code&gt;rejected
fetch first
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;먼저 GitHub 내용을 가져와 합칩니다.&lt;/p&gt;
&lt;pre class=&quot;ada&quot;&gt;&lt;code&gt;git pull origin main --allow-unrelated-histories
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;충돌이 없으면 Merge Commit 메시지 화면이 나타날 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Vim 화면이라면:&lt;/p&gt;
&lt;pre class=&quot;css&quot;&gt;&lt;code&gt;Esc
:wq
Enter
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그다음 Push합니다.&lt;/p&gt;
&lt;pre class=&quot;maxima&quot;&gt;&lt;code&gt;git push -u origin main
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;README 충돌이 발생한 경우&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음 명령으로 확인합니다.&lt;/p&gt;
&lt;pre class=&quot;ebnf&quot;&gt;&lt;code&gt;git status
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;충돌 파일에는 다음과 같은 표시가 나타납니다.&lt;/p&gt;
&lt;pre class=&quot;asciidoc&quot;&gt;&lt;code&gt;&amp;lt;&amp;lt;&amp;lt;&amp;lt;&amp;lt;&amp;lt;&amp;lt; HEAD
로컬 내용
=======
GitHub 내용
&amp;gt;&amp;gt;&amp;gt;&amp;gt;&amp;gt;&amp;gt;&amp;gt; origin/main
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;필요한 내용만 남기고 위 표시를 모두 삭제한 다음:&lt;/p&gt;
&lt;pre class=&quot;armasm&quot;&gt;&lt;code&gt;git add .
git commit -m &quot;로컬 프로젝트와 GitHub 내용 병합&quot;
git push -u origin main
&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;이미 origin이 등록됐다는 오류&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음 오류가 나올 수 있습니다.&lt;/p&gt;
&lt;pre class=&quot;basic&quot;&gt;&lt;code&gt;remote origin already exists
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;현재 연결을 확인합니다.&lt;/p&gt;
&lt;pre class=&quot;ebnf&quot;&gt;&lt;code&gt;git remote -v
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;URL이 잘못됐다면 변경합니다.&lt;/p&gt;
&lt;pre class=&quot;dsconfig&quot;&gt;&lt;code&gt;git remote set-url origin https://github.com/계정명/company-homepage.git
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그다음:&lt;/p&gt;
&lt;pre class=&quot;maxima&quot;&gt;&lt;code&gt;git push -u origin main
&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;전체 명령어 요약&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;GitHub가 빈 Repository라면&lt;/h3&gt;
&lt;pre class=&quot;properties&quot;&gt;&lt;code&gt;cd C:\work\company-homepage

git init
git add .
git commit -m &quot;기존 프로젝트 최초 등록&quot;
git branch -M main
git remote add origin https://github.com/계정명/company-homepage.git
git push -u origin main
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;GitHub에 README가 있다면&lt;/h3&gt;
&lt;pre class=&quot;properties&quot;&gt;&lt;code&gt;cd C:\work\company-homepage

git init
git add .
git commit -m &quot;기존 프로젝트 최초 등록&quot;
git branch -M main
git remote add origin https://github.com/계정명/company-homepage.git
git pull origin main --allow-unrelated-histories
git push -u origin main
&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;핵심&lt;/b&gt;: 동일한 이름의 디렉터리인지가 아니라, 로컬 폴더의 Git에 GitHub 주소를 origin으로 연결했는지가 중요합니다. GitHub Repository 이름과 로컬 디렉터리 이름은 달라도 연결할 수 있습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;fileblock&quot; data-ke-align=&quot;alignCenter&quot;&gt;&lt;a href=&quot;https://blog.kakaocdn.net/dn/bax7iW/dJMcagsSRxp/4em5UaCAv5YZZZzlrBmj91/%EA%B8%B0%EC%A1%B4-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-github-%EC%97%B0%EA%B2%B0.md.pdf?attach=1&amp;amp;knm=tfile.pdf&quot; class=&quot;&quot;&gt;
    &lt;div class=&quot;image&quot;&gt;&lt;/div&gt;
    &lt;div class=&quot;desc&quot;&gt;&lt;div class=&quot;filename&quot;&gt;&lt;span class=&quot;name&quot;&gt;기존-프로젝트-github-연결.md.pdf&lt;/span&gt;&lt;/div&gt;
&lt;div class=&quot;size&quot;&gt;0.28MB&lt;/div&gt;
&lt;/div&gt;
  &lt;/a&gt;&lt;/figure&gt;
&lt;/p&gt;</description>
      <category>Git</category>
      <category>`git init`</category>
      <category>`git push`</category>
      <category>`GitHub`</category>
      <category>`gitignore`</category>
      <category>`origin설정`</category>
      <category>`VSCode`</category>
      <category>`개발환경설정`</category>
      <category>`기존프로젝트연결`</category>
      <category>`깃허브연동`</category>
      <category>태그 10개 추천드립니다. `Git`</category>
      <author>바리새인</author>
      <guid isPermaLink="true">https://barisein.tistory.com/1325</guid>
      <comments>https://barisein.tistory.com/1325#entry1325comment</comments>
      <pubDate>Fri, 31 Jul 2026 23:18:17 +0900</pubDate>
    </item>
  </channel>
</rss>