<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>hwangsoojin 님의 블로그</title>
    <link>https://hwangsoojin.tistory.com/</link>
    <description>hwangsoojin 님의 블로그 입니다.</description>
    <language>ko</language>
    <pubDate>Sat, 13 Jun 2026 12:59:26 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>hwangsoojin</managingEditor>
    <image>
      <title>hwangsoojin 님의 블로그</title>
      <url>https://tistory1.daumcdn.net/tistory/8398986/attach/153387eca0e3403a9d10c5509887e809</url>
      <link>https://hwangsoojin.tistory.com</link>
    </image>
    <item>
      <title>SQLD 59회 합격 결과 &amp;ndash; 첫 도전의 마무리</title>
      <link>https://hwangsoojin.tistory.com/47</link>
      <description>&lt;p data-end=&quot;135&quot; data-start=&quot;105&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;175&quot; data-start=&quot;142&quot; data-ke-size=&quot;size16&quot;&gt;SQLD 59회 시험 결과가 발표되었고, &lt;b&gt;합격&lt;/b&gt;하였다.&lt;/p&gt;
&lt;p data-end=&quot;301&quot; data-start=&quot;177&quot; data-ke-size=&quot;size16&quot;&gt;이번 시험은 개인 일정이 매우 바쁜 시기와 겹쳐 준비 기간이 충분하지 않았고,&lt;br /&gt;또한 첫 응시였기 때문에 결과에 대한 불확실성이 존재했다.&lt;br /&gt;만약 불합격할 경우 다시 시험 일정을 고려해야 한다는 점도 부담으로 작용했다.&lt;/p&gt;
&lt;p data-end=&quot;432&quot; data-start=&quot;303&quot; data-ke-size=&quot;size16&quot;&gt;그럼에도 불구하고 최종적으로 합격 결과를 확인하면서,&lt;br /&gt;짧은 준비 기간 안에서도 핵심 내용을 중심으로 학습한 방향이 유효했음을 확인할 수 있었다.&lt;br /&gt;이번 SQLD 합격은 이후 자격증 학습을 이어가기 위한 하나의 기준점이 되었다.&lt;/p&gt;
&lt;p data-end=&quot;466&quot; data-start=&quot;439&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;SQLD_자격증_티스토리.jpg&quot; data-origin-width=&quot;793&quot; data-origin-height=&quot;1123&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bEONHK/dJMcabCR2lc/83erpyyrS3EEUKrKyNbKYK/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bEONHK/dJMcabCR2lc/83erpyyrS3EEUKrKyNbKYK/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bEONHK/dJMcabCR2lc/83erpyyrS3EEUKrKyNbKYK/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbEONHK%2FdJMcabCR2lc%2F83erpyyrS3EEUKrKyNbKYK%2Fimg.jpg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;793&quot; height=&quot;1123&quot; data-filename=&quot;SQLD_자격증_티스토리.jpg&quot; data-origin-width=&quot;793&quot; data-origin-height=&quot;1123&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-end=&quot;496&quot; data-start=&quot;473&quot; data-ke-size=&quot;size16&quot;&gt;앞으로의 자격증 취득 계획은 다음과 같다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;530&quot; data-start=&quot;498&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;513&quot; data-start=&quot;498&quot;&gt;&lt;b&gt;리눅스마스터 1급&lt;/b&gt;&lt;/li&gt;
&lt;li data-end=&quot;530&quot; data-start=&quot;514&quot;&gt;&lt;b&gt;AWS 관련 자격증&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-end=&quot;598&quot; data-start=&quot;532&quot; data-ke-size=&quot;size16&quot;&gt;SQLD를 통해 다진 데이터베이스 기초를 바탕으로,&lt;br /&gt;운영체제와 클라우드 영역까지 학습 범위를 확장해 나갈 예정이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>자격증</category>
      <author>hwangsoojin</author>
      <guid isPermaLink="true">https://hwangsoojin.tistory.com/47</guid>
      <comments>https://hwangsoojin.tistory.com/47#entry47comment</comments>
      <pubDate>Tue, 6 Jan 2026 01:57:59 +0900</pubDate>
    </item>
    <item>
      <title>리눅스 명령어는 언제 프로그램이고, 언제 프로세스가 될까?</title>
      <link>https://hwangsoojin.tistory.com/46</link>
      <description>&lt;p data-end=&quot;98&quot; data-start=&quot;72&quot; data-ke-size=&quot;size16&quot;&gt;리눅스를 처음 배우면 이런 말들을 자주 듣는다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;169&quot; data-start=&quot;100&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;115&quot; data-start=&quot;100&quot;&gt;&quot;ls는 프로그램이다.&quot;&lt;/li&gt;
&lt;li data-end=&quot;140&quot; data-start=&quot;116&quot;&gt;&quot;명령어를 실행하면 프로세스가 생긴다.&quot;&lt;/li&gt;
&lt;li data-end=&quot;169&quot; data-start=&quot;141&quot;&gt;&quot;cd는 프로세스가 아니라 쉘 내부 기능이다.&quot;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-end=&quot;346&quot; data-start=&quot;171&quot; data-ke-size=&quot;size16&quot;&gt;처음엔 다 비슷하게 들리는데, 사실 이 차이를 정확히 이해하면&lt;/p&gt;
&lt;p data-end=&quot;346&quot; data-start=&quot;171&quot; data-ke-size=&quot;size16&quot;&gt;리눅스/운영체제 공부가 훨씬 쉬워진다.&lt;/p&gt;
&lt;p data-end=&quot;346&quot; data-start=&quot;171&quot; data-ke-size=&quot;size16&quot;&gt;이 글에서는&amp;nbsp;&lt;b&gt;&lt;span style=&quot;background-color: #dddddd;&quot;&gt; cd &lt;/span&gt;&lt;/b&gt;,&amp;nbsp;&lt;b&gt;&lt;span style=&quot;background-color: #dddddd;&quot;&gt; ls &lt;/span&gt;&lt;/b&gt;, &lt;b&gt;&lt;span style=&quot;background-color: #dddddd;&quot;&gt;&amp;nbsp;cat &lt;/span&gt;&lt;/b&gt;,&amp;nbsp;&lt;span style=&quot;background-color: #dddddd;&quot;&gt;&lt;b&gt; echo &lt;/b&gt;&lt;/span&gt;,&amp;nbsp; &lt;b&gt;&lt;span style=&quot;background-color: #dddddd;&quot;&gt;&amp;nbsp;rm&amp;nbsp;&lt;/span&gt;&lt;/b&gt; 같은 대표 명령어를 예로 들어&lt;/p&gt;
&lt;p data-end=&quot;346&quot; data-start=&quot;171&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;명령어(command) / 프로그램(program) / 프로세스(process)&lt;/b&gt; 를&lt;/p&gt;
&lt;p data-end=&quot;346&quot; data-start=&quot;171&quot; data-ke-size=&quot;size16&quot;&gt;깔끔하게 정리해본다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 data-end=&quot;404&quot; data-start=&quot;353&quot; data-ke-size=&quot;size26&quot;&gt;1. 명령어(command), 프로그램(program), 프로세스(process) 차이&lt;/h2&gt;
&lt;h3 data-end=&quot;425&quot; data-start=&quot;406&quot; data-ke-size=&quot;size23&quot;&gt;1) 명령어(command)&lt;/h3&gt;
&lt;p data-end=&quot;454&quot; data-start=&quot;426&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;사용자가 쉘(터미널)에 입력하는 문자열&lt;/b&gt;이다.&lt;/p&gt;
&lt;p data-end=&quot;480&quot; data-start=&quot;456&quot; data-ke-size=&quot;size16&quot;&gt;예를 들어 내가 터미널에 아래를 입력했다면,&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre id=&quot;code_1767252034989&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;ls -l&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;556&quot; data-start=&quot;501&quot; data-ke-size=&quot;size16&quot;&gt;여기서&amp;nbsp;&lt;b&gt;&lt;span style=&quot;background-color: #dddddd;&quot;&gt; ls -l&amp;nbsp;&lt;/span&gt;&lt;/b&gt; 은 사용자가 입력한 &lt;b&gt;명령어&lt;/b&gt;다.&lt;/p&gt;
&lt;p data-end=&quot;556&quot; data-start=&quot;501&quot; data-ke-size=&quot;size16&quot;&gt;즉, 텍스트(문자열)로 된 &quot;지시&quot;다.&lt;/p&gt;
&lt;p data-end=&quot;556&quot; data-start=&quot;501&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-end=&quot;583&quot; data-start=&quot;563&quot; data-ke-size=&quot;size23&quot;&gt;2) 프로그램(program)&lt;/h3&gt;
&lt;p data-end=&quot;633&quot; data-start=&quot;584&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;디스크에 저장된 실행 파일&lt;/b&gt;이다. 예를 들어&amp;nbsp;&lt;b&gt;&lt;span style=&quot;background-color: #dddddd;&quot;&gt; ls &lt;/span&gt;&lt;/b&gt;는 보통 아래 경로에 존재한다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre id=&quot;code_1767252122606&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;/usr/bin/ls&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;782&quot; data-start=&quot;660&quot; data-ke-size=&quot;size16&quot;&gt;즉,&amp;nbsp; &lt;span style=&quot;background-color: #dddddd;&quot;&gt;&lt;b&gt;&amp;nbsp;ls &lt;/b&gt;&lt;/span&gt;라는 이름의 명령어는&lt;/p&gt;
&lt;p data-end=&quot;782&quot; data-start=&quot;660&quot; data-ke-size=&quot;size16&quot;&gt;실제로는&amp;nbsp;&lt;b&gt;&lt;span style=&quot;background-color: #dddddd;&quot;&gt; /usr/bin/ls &lt;/span&gt;&lt;/b&gt;라는 실행 파일(프로그램)을 가리킨다.&lt;/p&gt;
&lt;p data-end=&quot;782&quot; data-start=&quot;660&quot; data-ke-size=&quot;size16&quot;&gt;이 프로그램은 &quot;코드와 데이터&quot;가 파일 형태로 저장된 &lt;b&gt;정적인 상태&lt;/b&gt;다.&lt;/p&gt;
&lt;p data-end=&quot;782&quot; data-start=&quot;660&quot; data-ke-size=&quot;size16&quot;&gt;실행하지 않으면 그저 파일일 뿐이다.&lt;/p&gt;
&lt;p data-end=&quot;782&quot; data-start=&quot;660&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-end=&quot;809&quot; data-start=&quot;789&quot; data-ke-size=&quot;size23&quot;&gt;3) 프로세스(process)&lt;/h3&gt;
&lt;p data-end=&quot;839&quot; data-start=&quot;810&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;프로그램이 메모리에 올라가 실행 중인 상태&lt;/b&gt;다.&lt;/p&gt;
&lt;p data-end=&quot;957&quot; data-start=&quot;841&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span style=&quot;background-color: #dddddd;&quot;&gt;&amp;nbsp;ls -l &lt;/span&gt;&lt;/b&gt;을 실행하면,&lt;/p&gt;
&lt;p data-end=&quot;957&quot; data-start=&quot;841&quot; data-ke-size=&quot;size16&quot;&gt;운영체제는&amp;nbsp;&lt;span style=&quot;background-color: #dddddd;&quot;&gt;&lt;b&gt; /usr/bin/ls&amp;nbsp;&lt;/b&gt;&lt;/span&gt; 프로그램을 메모리에 적재하고 실행한다.&lt;/p&gt;
&lt;p data-end=&quot;957&quot; data-start=&quot;841&quot; data-ke-size=&quot;size16&quot;&gt;이때 생기는 실행 단위를 &lt;b&gt;프로세스&lt;/b&gt;라고 한다.&lt;/p&gt;
&lt;p data-end=&quot;957&quot; data-start=&quot;841&quot; data-ke-size=&quot;size16&quot;&gt;프로세스는 보통 PID(Process ID)를 갖는다.&lt;/p&gt;
&lt;p data-end=&quot;975&quot; data-start=&quot;959&quot; data-ke-size=&quot;size16&quot;&gt;정리하면 이 흐름이 핵심이다.&lt;/p&gt;
&lt;blockquote data-end=&quot;1043&quot; data-start=&quot;977&quot; data-ke-style=&quot;style2&quot;&gt;
&lt;p data-end=&quot;1043&quot; data-start=&quot;979&quot; data-ke-size=&quot;size16&quot;&gt;명령어(문자열)를 입력하면 &lt;br /&gt;&amp;rarr; 쉘이 프로그램(실행 파일)을 찾아 실행하고&lt;br /&gt;&amp;rarr; 실행 중인 프로그램은 프로세스가 된다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 data-end=&quot;1088&quot; data-start=&quot;1050&quot; data-ke-size=&quot;size26&quot;&gt;2. &quot;명령어가 실행될 때 프로세스가 된다&quot;는 말의 정확한 의미&lt;/h2&gt;
&lt;p data-end=&quot;1126&quot; data-start=&quot;1090&quot; data-ke-size=&quot;size16&quot;&gt;조금 더 엄밀히 말하면, &quot;명령어&quot;가 프로세스가 되는 게 아니라:&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;1181&quot; data-start=&quot;1128&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;1149&quot; data-start=&quot;1128&quot;&gt;명령어가 가리키는 &lt;b&gt;프로그램&lt;/b&gt;이&lt;/li&gt;
&lt;li data-end=&quot;1165&quot; data-start=&quot;1150&quot;&gt;운영체제에 의해 실행되어&lt;/li&gt;
&lt;li data-end=&quot;1181&quot; data-start=&quot;1166&quot;&gt;&lt;b&gt;프로세스&lt;/b&gt;가 된다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-end=&quot;1205&quot; data-start=&quot;1183&quot; data-ke-size=&quot;size16&quot;&gt;즉, 프로세스는 &quot;실행된 프로그램&quot;이다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 data-end=&quot;1251&quot; data-start=&quot;1212&quot; data-ke-size=&quot;size26&quot;&gt;3.&amp;nbsp;&lt;span style=&quot;background-color: #dddddd;&quot;&gt;&lt;b&gt; ls &lt;/b&gt;&lt;/span&gt;,&amp;nbsp;&lt;b&gt;&lt;span style=&quot;background-color: #dddddd;&quot;&gt; cat &lt;/span&gt;&lt;/b&gt;,&amp;nbsp;&lt;span style=&quot;background-color: #dddddd;&quot;&gt;&lt;b&gt; rm&lt;/b&gt; &lt;/span&gt;은 보통 &quot;외부 프로그램&quot;이다&lt;/h2&gt;
&lt;p data-end=&quot;1315&quot; data-start=&quot;1253&quot; data-ke-size=&quot;size16&quot;&gt;대표적으로&amp;nbsp;&lt;span style=&quot;background-color: #dddddd;&quot;&gt;&lt;b&gt; ls &lt;/b&gt;&lt;/span&gt;,&amp;nbsp;&lt;span style=&quot;background-color: #dddddd;&quot;&gt;&lt;b&gt; cat &lt;/b&gt;&lt;/span&gt;,&amp;nbsp;&lt;b&gt;&lt;span style=&quot;background-color: #dddddd;&quot;&gt; rm &lt;/span&gt;&lt;/b&gt;은 대부분의 리눅스에서&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;1315&quot; data-start=&quot;1253&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span style=&quot;background-color: #dddddd;&quot;&gt;&amp;nbsp;/usr/bin&amp;nbsp;&lt;/span&gt;&lt;/b&gt; 아래에 있는 실행 파일이다.&lt;/p&gt;
&lt;h3 data-end=&quot;1343&quot; data-start=&quot;1317&quot; data-ke-size=&quot;size23&quot;&gt;확인 방법:&amp;nbsp;&lt;span style=&quot;background-color: #dddddd;&quot;&gt;&lt;b&gt; type &lt;/b&gt;&lt;/span&gt;,&amp;nbsp;&lt;b&gt;&lt;span style=&quot;background-color: #dddddd;&quot;&gt; which&amp;nbsp;&lt;/span&gt;&lt;/b&gt;&amp;nbsp;&lt;/h3&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre id=&quot;code_1767252376252&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;type ls
which ls&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;1473&quot; data-start=&quot;1375&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;1430&quot; data-start=&quot;1375&quot;&gt;&lt;span style=&quot;background-color: #dddddd;&quot;&gt;&lt;b&gt;&amp;nbsp;type ls &lt;/b&gt;&lt;/span&gt;는&amp;nbsp;&lt;b&gt;&lt;span style=&quot;background-color: #dddddd;&quot;&gt; ls &lt;/span&gt;&lt;/b&gt;가 &lt;b&gt;외부 프로그램인지, 쉘 내장(builtin)인지&lt;/b&gt; 알려준다.&lt;/li&gt;
&lt;li data-end=&quot;1473&quot; data-start=&quot;1431&quot;&gt;&lt;span style=&quot;background-color: #dddddd;&quot;&gt;&lt;b&gt;&amp;nbsp;which ls &lt;/b&gt;&lt;/span&gt;는&amp;nbsp;&lt;span style=&quot;background-color: #dddddd;&quot;&gt;&lt;b&gt; ls&amp;nbsp;&lt;/b&gt;&lt;/span&gt; 실행 파일이 &lt;b&gt;어디 있는지&lt;/b&gt; 보여준다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-end=&quot;1490&quot; data-start=&quot;1475&quot; data-ke-size=&quot;size16&quot;&gt;보통은 이런 결과가 나온다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;1558&quot; data-start=&quot;1492&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;1536&quot; data-start=&quot;1492&quot;&gt;&lt;span style=&quot;background-color: #dddddd;&quot;&gt;&lt;b&gt;&amp;nbsp;ls is aliased to ...&amp;nbsp;&lt;/b&gt;&lt;/span&gt; (alias가 걸려있을 수도 있고)&lt;/li&gt;
&lt;li data-end=&quot;1558&quot; data-start=&quot;1537&quot;&gt;&lt;span style=&quot;background-color: #dddddd;&quot;&gt;&lt;b&gt;&amp;nbsp;ls is /usr/bin/ls&amp;nbsp;&lt;/b&gt;&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-end=&quot;1600&quot; data-start=&quot;1560&quot; data-ke-size=&quot;size16&quot;&gt;즉&amp;nbsp;&lt;span style=&quot;background-color: #dddddd;&quot;&gt;&lt;b&gt; ls &lt;/b&gt;&lt;/span&gt;는 실제 실행 파일이 존재하고, 실행하면 프로세스로 실행된다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 data-end=&quot;1633&quot; data-start=&quot;1607&quot; data-ke-size=&quot;size26&quot;&gt;4. 그런데&amp;nbsp;&lt;span style=&quot;background-color: #dddddd;&quot;&gt;&lt;b&gt; cd &lt;/b&gt;&lt;/span&gt;는 왜 다르다고 할까?&lt;/h2&gt;
&lt;p data-end=&quot;1671&quot; data-start=&quot;1635&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #dddddd;&quot;&gt;&lt;b&gt;&amp;nbsp;cd &lt;/b&gt;&lt;/span&gt;는 정말 중요하다. 많은 초보자가 여기서 한 번 헷갈린다.&lt;/p&gt;
&lt;h3 data-end=&quot;1702&quot; data-start=&quot;1673&quot; data-ke-size=&quot;size23&quot;&gt;1)&amp;nbsp;&lt;span style=&quot;background-color: #dddddd;&quot;&gt;&lt;b&gt; cd &lt;/b&gt;&lt;/span&gt;는 보통 &quot;쉘 builtin&quot;이다&lt;/h3&gt;
&lt;p data-end=&quot;1766&quot; data-start=&quot;1703&quot; data-ke-size=&quot;size16&quot;&gt;cd는 디렉터리를 바꾸는 기능인데,&lt;/p&gt;
&lt;p data-end=&quot;1766&quot; data-start=&quot;1703&quot; data-ke-size=&quot;size16&quot;&gt;이 기능은 &lt;b&gt;현재 쉘 프로세스의 작업 디렉터리&lt;/b&gt;를 바꿔야 의미가 있다.&lt;/p&gt;
&lt;p data-end=&quot;1766&quot; data-start=&quot;1703&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;1816&quot; data-start=&quot;1768&quot; data-ke-size=&quot;size16&quot;&gt;만약&amp;nbsp;&lt;span style=&quot;background-color: #dddddd;&quot;&gt;&lt;b&gt; cd&lt;/b&gt; &lt;/span&gt;가&amp;nbsp;&lt;span style=&quot;background-color: #dddddd;&quot;&gt;&lt;b&gt; /usr/bin/cd&amp;nbsp;&lt;/b&gt;&lt;/span&gt; 같은 외부 프로그램이라면 어떤 문제가 생길까?&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;1899&quot; data-start=&quot;1818&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;1849&quot; data-start=&quot;1818&quot;&gt;쉘이&amp;nbsp;&lt;span style=&quot;background-color: #dddddd;&quot;&gt;&lt;b&gt; cd&lt;/b&gt; &lt;/span&gt;를 실행하기 위해 새 프로세스를 만든다.&lt;/li&gt;
&lt;li data-end=&quot;1871&quot; data-start=&quot;1850&quot;&gt;새 프로세스에서 디렉터리를 바꾼다.&lt;/li&gt;
&lt;li data-end=&quot;1885&quot; data-start=&quot;1872&quot;&gt;프로세스가 종료된다.&lt;/li&gt;
&lt;li data-end=&quot;1899&quot; data-start=&quot;1886&quot;&gt;원래 쉘은 그대로다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-end=&quot;1998&quot; data-start=&quot;1901&quot; data-ke-size=&quot;size16&quot;&gt;즉, &quot;디렉터리를 바꿨는데도 터미널의 위치가 안 바뀌는&quot; 이상한 상황이 된다.&lt;/p&gt;
&lt;p data-end=&quot;1998&quot; data-start=&quot;1901&quot; data-ke-size=&quot;size16&quot;&gt;그래서&amp;nbsp;&lt;span style=&quot;background-color: #dddddd;&quot;&gt;&lt;b&gt; cd&lt;/b&gt; &lt;/span&gt;는 &lt;b&gt;현재 쉘 안에서 직접 실행되는 내장 명령(builtin)&lt;/b&gt; 이어야 한다.&lt;/p&gt;
&lt;p data-end=&quot;1998&quot; data-start=&quot;1901&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-end=&quot;2012&quot; data-start=&quot;2000&quot; data-ke-size=&quot;size23&quot;&gt;2) 확인해보기&lt;/h3&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre id=&quot;code_1767252660667&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;type cd&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;2050&quot; data-start=&quot;2035&quot; data-ke-size=&quot;size16&quot;&gt;대부분 이런 결과가 나온다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre id=&quot;code_1767252671926&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;cd is a shell builtin&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;2092&quot; data-start=&quot;2087&quot; data-ke-size=&quot;size16&quot;&gt;정리하면:&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;2184&quot; data-start=&quot;2094&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;2141&quot; data-start=&quot;2094&quot;&gt;&lt;span style=&quot;background-color: #dddddd;&quot;&gt;&lt;b&gt;&amp;nbsp;cd &lt;/b&gt;&lt;/span&gt;는 명령어이긴 하지만, 보통 외부 실행 파일이 아니라 &lt;b&gt;쉘 내부 기능&lt;/b&gt;&lt;/li&gt;
&lt;li data-end=&quot;2184&quot; data-start=&quot;2142&quot;&gt;실행 시 새 프로세스를 만들지 않고 &lt;b&gt;현재 쉘 프로세스가 직접 처리&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 data-end=&quot;2223&quot; data-start=&quot;2191&quot; data-ke-size=&quot;size26&quot;&gt;5.&amp;nbsp;&lt;span style=&quot;background-color: #dddddd;&quot;&gt;&lt;b&gt; echo &lt;/b&gt;&lt;/span&gt;는 프로그램일까? builtin일까?&lt;/h2&gt;
&lt;p data-end=&quot;2259&quot; data-start=&quot;2225&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #dddddd;&quot;&gt;&lt;b&gt;&amp;nbsp;echo&lt;/b&gt; &lt;/span&gt;는 재미있는 케이스다. 환경에 따라 다를 수 있다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;2332&quot; data-start=&quot;2261&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;2288&quot; data-start=&quot;2261&quot;&gt;많은 쉘에서&amp;nbsp;&lt;span style=&quot;background-color: #dddddd;&quot;&gt;&lt;b&gt; echo&lt;/b&gt; &lt;/span&gt;는 builtin이다.&lt;/li&gt;
&lt;li data-end=&quot;2332&quot; data-start=&quot;2289&quot;&gt;동시에&amp;nbsp;&lt;span style=&quot;background-color: #dddddd;&quot;&gt;&lt;b&gt; /usr/bin/echo&amp;nbsp;&lt;/b&gt;&lt;/span&gt; 같은 외부 프로그램도 존재할 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-end=&quot;2340&quot; data-start=&quot;2334&quot; data-ke-size=&quot;size23&quot;&gt;확인&lt;/h3&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre id=&quot;code_1767253440450&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;type echo
which echo&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;2527&quot; data-start=&quot;2376&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;2430&quot; data-start=&quot;2376&quot;&gt;&lt;span style=&quot;background-color: #dddddd;&quot;&gt;&lt;b&gt;&amp;nbsp;type echo&lt;/b&gt; &lt;/span&gt;에서 builtin이라고 나오면, 보통 별도 프로세스 없이 쉘이 처리한다.&lt;/li&gt;
&lt;li data-end=&quot;2480&quot; data-start=&quot;2431&quot;&gt;&lt;span style=&quot;background-color: #dddddd;&quot;&gt;&lt;b&gt;&amp;nbsp;\echo&lt;/b&gt; &lt;/span&gt;처럼 백슬래시를 붙이면 alias/builtin을 우회하는 경우도 있다.&lt;/li&gt;
&lt;li data-end=&quot;2527&quot; data-start=&quot;2481&quot;&gt;&lt;span style=&quot;background-color: #dddddd;&quot;&gt;&lt;b&gt;&amp;nbsp;/usr/bin/echo&lt;/b&gt; &lt;/span&gt;를 직접 호출하면 외부 프로그램 프로세스가 실행된다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-end=&quot;2592&quot; data-start=&quot;2529&quot; data-ke-size=&quot;size16&quot;&gt;즉&amp;nbsp;&lt;span style=&quot;background-color: #dddddd;&quot;&gt;&lt;b&gt; echo&lt;/b&gt; &lt;/span&gt;는 &quot;항상 프로그램/항상 프로세스&quot;라고 단정하기 어렵고,&lt;/p&gt;
&lt;p data-end=&quot;2592&quot; data-start=&quot;2529&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;어떤 echo를 쓰느냐&lt;/b&gt;가 중요하다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 data-end=&quot;2629&quot; data-start=&quot;2599&quot; data-ke-size=&quot;size26&quot;&gt;6. 프로세스가 생기는 순간: 쉘은 무엇을 할까?&lt;/h2&gt;
&lt;p data-end=&quot;2694&quot; data-start=&quot;2631&quot; data-ke-size=&quot;size16&quot;&gt;외부 프로그램(&lt;span style=&quot;background-color: #dddddd;&quot;&gt;&lt;b&gt; ls&lt;/b&gt; &lt;/span&gt;,&amp;nbsp;&lt;span style=&quot;background-color: #dddddd;&quot;&gt;&lt;b&gt; cat&lt;/b&gt; &lt;/span&gt;,&amp;nbsp;&lt;b&gt;&lt;span style=&quot;background-color: #dddddd;&quot;&gt; rm&amp;nbsp;&lt;/span&gt;&lt;/b&gt; 등)을 실행할 때,&lt;/p&gt;
&lt;p data-end=&quot;2694&quot; data-start=&quot;2631&quot; data-ke-size=&quot;size16&quot;&gt;쉘은 보통 이런 흐름으로 동작한다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-end=&quot;2825&quot; data-start=&quot;2696&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li data-end=&quot;2710&quot; data-start=&quot;2696&quot;&gt;사용자가 명령어 입력&lt;/li&gt;
&lt;li data-end=&quot;2728&quot; data-start=&quot;2711&quot;&gt;쉘이 파싱(parsing)&lt;/li&gt;
&lt;li data-end=&quot;2758&quot; data-start=&quot;2729&quot;&gt;리다이렉션 설정(필요하면 파일 디스크립터 변경)&lt;/li&gt;
&lt;li data-end=&quot;2779&quot; data-start=&quot;2759&quot;&gt;새 프로세스 생성(&lt;b&gt;&lt;span style=&quot;background-color: #dddddd;&quot;&gt; fork&lt;/span&gt; &lt;/b&gt;)&lt;/li&gt;
&lt;li data-end=&quot;2806&quot; data-start=&quot;2780&quot;&gt;새 프로세스가 프로그램 실행(&lt;span style=&quot;background-color: #dddddd;&quot;&gt;&lt;b&gt; exec&lt;/b&gt; &lt;/span&gt;)&lt;/li&gt;
&lt;li data-end=&quot;2825&quot; data-start=&quot;2807&quot;&gt;프로그램 종료 후 쉘로 복귀&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-end=&quot;2837&quot; data-start=&quot;2827&quot; data-ke-size=&quot;size16&quot;&gt;여기서 중요한 건:&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;2959&quot; data-start=&quot;2839&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;2897&quot; data-start=&quot;2839&quot;&gt;리다이렉션(&lt;span style=&quot;background-color: #dddddd;&quot;&gt;&lt;b&gt; &amp;gt;&lt;/b&gt; &lt;/span&gt;,&amp;nbsp;&lt;span style=&quot;background-color: #dddddd;&quot;&gt;&lt;b&gt; &amp;lt;&lt;/b&gt; &lt;/span&gt;,&amp;nbsp;&lt;span style=&quot;background-color: #dddddd;&quot;&gt;&lt;b&gt; 2&amp;gt;&lt;/b&gt; &lt;/span&gt;,&amp;nbsp;&lt;span style=&quot;background-color: #dddddd;&quot;&gt;&lt;b&gt; 2&amp;gt;&amp;amp;1&amp;nbsp;&lt;/b&gt;&lt;/span&gt; 등)은 보통 &lt;b&gt;프로그램 실행 전에 쉘이 설정&lt;/b&gt;&lt;/li&gt;
&lt;li data-end=&quot;2959&quot; data-start=&quot;2898&quot;&gt;그래서 프로그램 입장에서는 &quot;그냥 stdout/stderr에 출력했을 뿐&quot;인데, 실제로는 파일로 들어간다.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 data-end=&quot;3004&quot; data-start=&quot;2966&quot; data-ke-size=&quot;size26&quot;&gt;7. 한 장 표로 정리: cd, ls, cat, echo, rm&lt;/h2&gt;
&lt;div&gt;
&lt;div&gt;명령어보통 정체실행 파일 존재?실행 시 프로세스 생성?
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-end=&quot;3299&quot; data-start=&quot;3006&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style13&quot;&gt;
&lt;tbody data-end=&quot;3299&quot; data-start=&quot;3068&quot;&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;명령어&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;&lt;b&gt;보통 정체&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;&lt;b&gt;실행 파일 존재?&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;&lt;b&gt;실행 시 프로세스 생성?&lt;/b&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr data-end=&quot;3114&quot; data-start=&quot;3068&quot;&gt;
&lt;td data-col-size=&quot;sm&quot; data-end=&quot;3075&quot; data-start=&quot;3068&quot;&gt;&lt;b&gt;cd&lt;/b&gt;&lt;/td&gt;
&lt;td data-end=&quot;3087&quot; data-start=&quot;3075&quot; data-col-size=&quot;sm&quot;&gt;쉘 builtin&lt;/td&gt;
&lt;td data-end=&quot;3095&quot; data-start=&quot;3087&quot; data-col-size=&quot;sm&quot;&gt;보통 없음&lt;/td&gt;
&lt;td data-end=&quot;3114&quot; data-start=&quot;3095&quot; data-col-size=&quot;sm&quot;&gt;보통 X (현재 쉘이 처리)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr data-end=&quot;3153&quot; data-start=&quot;3115&quot;&gt;
&lt;td data-col-size=&quot;sm&quot; data-end=&quot;3122&quot; data-start=&quot;3115&quot;&gt;&lt;b&gt;ls&lt;/b&gt;&lt;/td&gt;
&lt;td data-end=&quot;3132&quot; data-start=&quot;3122&quot; data-col-size=&quot;sm&quot;&gt;외부 프로그램&lt;/td&gt;
&lt;td data-end=&quot;3148&quot; data-start=&quot;3132&quot; data-col-size=&quot;sm&quot;&gt;/usr/bin/ls&lt;/td&gt;
&lt;td data-end=&quot;3153&quot; data-start=&quot;3148&quot; data-col-size=&quot;sm&quot;&gt;O&lt;/td&gt;
&lt;/tr&gt;
&lt;tr data-end=&quot;3194&quot; data-start=&quot;3154&quot;&gt;
&lt;td data-col-size=&quot;sm&quot; data-end=&quot;3162&quot; data-start=&quot;3154&quot;&gt;&lt;b&gt;cat&lt;/b&gt;&lt;/td&gt;
&lt;td data-end=&quot;3172&quot; data-start=&quot;3162&quot; data-col-size=&quot;sm&quot;&gt;외부 프로그램&lt;/td&gt;
&lt;td data-end=&quot;3189&quot; data-start=&quot;3172&quot; data-col-size=&quot;sm&quot;&gt;/usr/bin/cat&lt;/td&gt;
&lt;td data-end=&quot;3194&quot; data-start=&quot;3189&quot; data-col-size=&quot;sm&quot;&gt;O&lt;/td&gt;
&lt;/tr&gt;
&lt;tr data-end=&quot;3233&quot; data-start=&quot;3195&quot;&gt;
&lt;td data-col-size=&quot;sm&quot; data-end=&quot;3202&quot; data-start=&quot;3195&quot;&gt;&lt;b&gt;rm&lt;/b&gt;&lt;/td&gt;
&lt;td data-end=&quot;3212&quot; data-start=&quot;3202&quot; data-col-size=&quot;sm&quot;&gt;외부 프로그램&lt;/td&gt;
&lt;td data-end=&quot;3228&quot; data-start=&quot;3212&quot; data-col-size=&quot;sm&quot;&gt;/usr/bin/rm&lt;/td&gt;
&lt;td data-end=&quot;3233&quot; data-start=&quot;3228&quot; data-col-size=&quot;sm&quot;&gt;O&lt;/td&gt;
&lt;/tr&gt;
&lt;tr data-end=&quot;3299&quot; data-start=&quot;3234&quot;&gt;
&lt;td data-col-size=&quot;sm&quot; data-end=&quot;3243&quot; data-start=&quot;3234&quot;&gt;&lt;b&gt;echo&lt;/b&gt;&lt;/td&gt;
&lt;td data-col-size=&quot;sm&quot; data-end=&quot;3259&quot; data-start=&quot;3243&quot;&gt;builtin 또는 외부&lt;/td&gt;
&lt;td data-end=&quot;3285&quot; data-start=&quot;3259&quot; data-col-size=&quot;sm&quot;&gt;/usr/bin/echo 있을 수 있음&lt;/td&gt;
&lt;td data-end=&quot;3299&quot; data-start=&quot;3285&quot; data-col-size=&quot;sm&quot;&gt;상황에 따라 O/X&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 data-end=&quot;3340&quot; data-start=&quot;3306&quot; data-ke-size=&quot;size26&quot;&gt;8. 마무리: &quot;명령어&quot;를 보면 무엇을 떠올리면 좋을까?&lt;/h2&gt;
&lt;p data-end=&quot;3382&quot; data-start=&quot;3342&quot; data-ke-size=&quot;size16&quot;&gt;리눅스에서 어떤 명령어를 봤을 때 이렇게 생각하면 헷갈림이 확 줄어든다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-end=&quot;3572&quot; data-start=&quot;3384&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li data-end=&quot;3408&quot; data-start=&quot;3384&quot;&gt;내가 입력한 건 &quot;명령어 문자열&quot;이다.&lt;/li&gt;
&lt;li data-end=&quot;3488&quot; data-start=&quot;3409&quot;&gt;이 명령어가 가리키는 대상은:
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;3488&quot; data-start=&quot;3432&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;3465&quot; data-start=&quot;3432&quot;&gt;외부 프로그램일 수도 있고 (&lt;span style=&quot;background-color: #dddddd;&quot;&gt;&lt;b&gt; /usr/bin/...&lt;/b&gt; &lt;/span&gt;)&lt;/li&gt;
&lt;li data-end=&quot;3488&quot; data-start=&quot;3469&quot;&gt;쉘 builtin일 수도 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li data-end=&quot;3530&quot; data-start=&quot;3489&quot;&gt;외부 프로그램이면:
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;3530&quot; data-start=&quot;3506&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;3530&quot; data-start=&quot;3506&quot;&gt;실행 순간 운영체제가 프로세스를 만든다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li data-end=&quot;3572&quot; data-start=&quot;3531&quot;&gt;builtin이면:
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;3572&quot; data-start=&quot;3548&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;3572&quot; data-start=&quot;3548&quot;&gt;현재 쉘 프로세스가 내부적으로 처리한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-end=&quot;3653&quot; data-start=&quot;3574&quot; data-ke-size=&quot;size16&quot;&gt;이 관점으로 보면&amp;nbsp;&lt;span style=&quot;background-color: #dddddd;&quot;&gt;&lt;b&gt; cd&lt;/b&gt; &lt;/span&gt;가 왜 특별한지,&lt;/p&gt;
&lt;p data-end=&quot;3653&quot; data-start=&quot;3574&quot; data-ke-size=&quot;size16&quot;&gt;리다이렉션이 왜 쉘의 기능인지,&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;3653&quot; data-start=&quot;3574&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #dddddd;&quot;&gt;&lt;b&gt;&amp;nbsp;echo&lt;/b&gt; &lt;/span&gt;가 왜 환경마다 다르게 느껴지는지까지 한 번에 연결된다.&lt;/p&gt;</description>
      <category>리눅스</category>
      <author>hwangsoojin</author>
      <guid isPermaLink="true">https://hwangsoojin.tistory.com/46</guid>
      <comments>https://hwangsoojin.tistory.com/46#entry46comment</comments>
      <pubDate>Thu, 1 Jan 2026 16:13:30 +0900</pubDate>
    </item>
    <item>
      <title>[Ncloud 1기] 6주차 회고 및 전체를 돌아보며</title>
      <link>https://hwangsoojin.tistory.com/44</link>
      <description>&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot; data-start=&quot;160&quot; data-end=&quot;198&quot;&gt;&lt;b&gt;작성일: 2025.11.29&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot; data-start=&quot;160&quot; data-end=&quot;198&quot;&gt;&lt;b&gt;학습 주제: NCA 자격시험 대비 모의고사&lt;br /&gt;작성자: 황수진(컴퓨터과학부)&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot; data-start=&quot;160&quot; data-end=&quot;198&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot; data-start=&quot;200&quot; data-end=&quot;343&quot;&gt;6주차는 네이버클라우드 아카데미 과정의 마지막 주차였다.&lt;br /&gt;6주차 회고이면서 전체 과정에 대한 회고이기 때문에 이전에 따랐던 형식을 따르지 않고 작성해보려고 한다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-style=&quot;style6&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot; data-start=&quot;350&quot; data-end=&quot;384&quot;&gt;1. 6주차 - NCA 자격 시험 대비 모의고사 응시 &amp;amp; 문제 풀이&lt;/h2&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot; data-start=&quot;386&quot; data-end=&quot;553&quot;&gt;6주차 수업에서는 NCA 자격시험을 대비한 모의고사를 실제 시험처럼 풀어보았다.&lt;br /&gt;시험을 코앞에 두고 있어서 나름대로 열심히 준비하고 싶었지만,&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot; data-start=&quot;386&quot; data-end=&quot;553&quot;&gt;상황은 생각보다 녹록지 않았다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot; data-start=&quot;386&quot; data-end=&quot;553&quot;&gt;학교에서는 전공 팀프로젝트 발표가 다가오고 있었고,&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot; data-start=&quot;386&quot; data-end=&quot;553&quot;&gt;여러 과제들이 동시에 몰리면서 공부 시간을 확보하는 게 쉽지 않았다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot; data-start=&quot;386&quot; data-end=&quot;553&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot; data-start=&quot;555&quot; data-end=&quot;724&quot;&gt;그래도&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;&quot;떨어지면 안된다&quot;&lt;/b&gt;는 마음 하나로&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot; data-start=&quot;555&quot; data-end=&quot;724&quot;&gt;&lt;b&gt;모을 수 있는 자투리 시간을 모조리 모아 공부했다.&lt;/b&gt;&lt;br /&gt;학교 수업 쉬는 시간에 네이버클라우드 아카데미에서 배포해주신 강의자료를&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot; data-start=&quot;555&quot; data-end=&quot;724&quot;&gt;읽으면서 암기해야 할 사항들을 워드 파일로 정리해나갔다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;986&quot; data-origin-height=&quot;678&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/B3VXS/dJMcacBzpFO/b72kATdyfeIazVVkCtDts0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/B3VXS/dJMcacBzpFO/b72kATdyfeIazVVkCtDts0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/B3VXS/dJMcacBzpFO/b72kATdyfeIazVVkCtDts0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FB3VXS%2FdJMcacBzpFO%2Fb72kATdyfeIazVVkCtDts0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;986&quot; height=&quot;678&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;986&quot; data-origin-height=&quot;678&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;989&quot; data-origin-height=&quot;612&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cRBpRp/dJMcafZl7bJ/vSE6qEJ9iq6HOCFZkj8ezk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cRBpRp/dJMcafZl7bJ/vSE6qEJ9iq6HOCFZkj8ezk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cRBpRp/dJMcafZl7bJ/vSE6qEJ9iq6HOCFZkj8ezk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcRBpRp%2FdJMcafZl7bJ%2FvSE6qEJ9iq6HOCFZkj8ezk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;989&quot; height=&quot;612&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;989&quot; data-origin-height=&quot;612&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot; data-start=&quot;555&quot; data-end=&quot;724&quot;&gt;&lt;br /&gt;통학길 지하철에서는 그 정리한 자료를 반복해서 읽었다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot; data-start=&quot;555&quot; data-end=&quot;724&quot;&gt;3시간 정도의 왕복 통학 길이니 그 시간들을 모으면 꽤 많은 시간이 모인다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot; data-start=&quot;555&quot; data-end=&quot;724&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot; data-start=&quot;726&quot; data-end=&quot;830&quot;&gt;모의고사 점수를 확인했을 때는 안도의 한숨을 쉬었다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot; data-start=&quot;726&quot; data-end=&quot;830&quot;&gt;&lt;b&gt;부족한 시간이었지만 준비를 잘 했구나&lt;/b&gt;라는 생각이 들었다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot; data-start=&quot;726&quot; data-end=&quot;830&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot; data-start=&quot;726&quot; data-end=&quot;830&quot;&gt;모의고사가 끝난 뒤 강사님의 문제풀이까지 더해지니&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot; data-start=&quot;726&quot; data-end=&quot;830&quot;&gt;&lt;b&gt;실제 시험날에는 NCA를 분명히 취득할 수 있겠다&lt;/b&gt;는 생각이 들었다.&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-style=&quot;style6&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot; data-start=&quot;837&quot; data-end=&quot;876&quot;&gt;2. 네이버 클라우드 아카데미 - 진로를 바꾸게 된 프로그램&lt;/h2&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot; data-start=&quot;878&quot; data-end=&quot;1065&quot;&gt;아카데미에 처음 지원할 당시에 나는 백엔드 개발 공부를 하고 있었고&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot; data-start=&quot;878&quot; data-end=&quot;1065&quot;&gt;진로도 백엔드 개발자쪽으로 생각하고 있었다.&lt;br /&gt;하지만 늘 &quot;클라우드 기반 지식이 부족하다&quot;는 생각이 머릿속에 자리 잡고 있었고,&lt;br /&gt;혼자 자료를 찾아 공부하는 것은 항상 중간에 흐름이 끊기곤 했다.&lt;br /&gt;그래서 학과 카톡방에서 네이버클라우드 아카데미 모집 공지를 봤을 때,&lt;br /&gt;이 기회는 놓치면 안 된다는 생각이 들었다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot; data-start=&quot;878&quot; data-end=&quot;1065&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot; data-start=&quot;1067&quot; data-end=&quot;1205&quot;&gt;6주 동안 매주 5시간씩 들은 수업은 생각보다 훨씬 더 몰입감 있는 경험이었다.&lt;br /&gt;이영태 강사님의 설명은 실무 이야기가 자연스럽게 녹아 있어서 이해가 잘 되었고,&lt;br /&gt;수업이 끝난 후 복습과 개인 실습을 하다 보면 어느새 시간이 훌쩍 지나 있었다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot; data-start=&quot;1067&quot; data-end=&quot;1205&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot; data-start=&quot;1067&quot; data-end=&quot;1205&quot;&gt;복습과&amp;nbsp;개인&amp;nbsp;실습을&amp;nbsp;진행하면서&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot; data-start=&quot;1067&quot; data-end=&quot;1205&quot;&gt;&lt;b&gt;'네트워크 공부가 이렇게 재밌었나?'&lt;/b&gt;싶을 정도로 푹 빠져서 했던 것 같다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot; data-start=&quot;1067&quot; data-end=&quot;1205&quot;&gt;학교에서 들었던 네트워크 과목들은 이렇게까지 재미있는 공부는 아니었던 것 같은데....&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot; data-start=&quot;1067&quot; data-end=&quot;1205&quot;&gt;학교다니면서 들었던 어떠한 전공과목보다도 분명 유익했던 것 같다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot; data-start=&quot;1067&quot; data-end=&quot;1205&quot;&gt;클라우드 공부를 하면서&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot; data-start=&quot;1067&quot; data-end=&quot;1205&quot;&gt;&lt;b&gt;'내가 지금까지 백엔드 개발 공부를 이렇게 재밌게 해본 적이 있었나?'&lt;/b&gt;라는 생각이 들면서&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot; data-start=&quot;1067&quot; data-end=&quot;1205&quot;&gt;매주차를 거듭할수록 백엔드 개발자가 아니라&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot; data-start=&quot;1067&quot; data-end=&quot;1205&quot;&gt;&lt;b&gt;클라우드 엔지니어가 되고 싶다&lt;/b&gt;는 생각이 굳어지고 있었다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot; data-start=&quot;1207&quot; data-end=&quot;1347&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot; data-start=&quot;1207&quot; data-end=&quot;1347&quot;&gt;학교에서 들었던 네트워크 수업들과는 분명히 느낌이 달랐다.&lt;br /&gt;이번 교육은&lt;b&gt; &quot;운영환경에서 실제로 어떤 일이 일어나는지&quot;&lt;/b&gt;를 눈으로 직접 보면서&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot; data-start=&quot;1207&quot; data-end=&quot;1347&quot;&gt;배우는 과정이었기 때문에 훨씬 생생했고 재미있었다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot; data-start=&quot;1387&quot; data-end=&quot;1552&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot; data-start=&quot;1387&quot; data-end=&quot;1552&quot;&gt;진로에 대한 생각이 바뀐&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot; data-start=&quot;1387&quot; data-end=&quot;1552&quot;&gt;가장 결정적인 순간은 5주차의&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;3-Tier 아키텍처 구현 미니 프로젝트&lt;/b&gt;였다.&lt;br /&gt;나는 팀에서 백엔드와 인프라 구성을 모두 맡았고,&lt;br /&gt;개발을&amp;nbsp;할&amp;nbsp;때는&amp;nbsp;생기없던&amp;nbsp;내가&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot; data-start=&quot;1387&quot; data-end=&quot;1552&quot;&gt;클라우드 인프라를 구현하는 과정에서는 눈에 생기가 도는 나를 발견했다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot; data-start=&quot;1554&quot; data-end=&quot;1701&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot; data-start=&quot;1554&quot; data-end=&quot;1701&quot;&gt;내가 이 일을 즐기고 있다는 걸 깨달은 순간,&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot; data-start=&quot;1554&quot; data-end=&quot;1701&quot;&gt;&quot;클라우드 엔지니어가 되어야겠다.&quot;는 마음을 먹었다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-style=&quot;style6&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot; data-start=&quot;1805&quot; data-end=&quot;1826&quot;&gt;3. 프로그램 전반에서 좋았던 점&lt;/h2&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot; data-start=&quot;1828&quot; data-end=&quot;1940&quot;&gt;&lt;b&gt;① Slack으로 운영진과의 원활한 소통&lt;/b&gt;&lt;br /&gt;슬랙이라는 프로그램으로 강사님과 운영진분들과 소통하였다.&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot; data-start=&quot;1828&quot; data-end=&quot;1940&quot;&gt;&lt;b&gt;공부하다가 궁금한 점은 강사님께 질문&lt;/b&gt;하였고 친절히 답변주셨다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot; data-start=&quot;1828&quot; data-end=&quot;1940&quot;&gt;운영진분들께서 &lt;b&gt;주목할만한 클라우드 관련 기사&lt;/b&gt;도&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot; data-start=&quot;1828&quot; data-end=&quot;1940&quot;&gt;실시간으로 공유해주시고 &lt;b&gt;채용 공고&lt;/b&gt;도 공유해주셨다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot; data-start=&quot;1828&quot; data-end=&quot;1940&quot;&gt;&lt;b&gt;프로그램 내에서 지속적으로 관리받고 있다는 기분&lt;/b&gt;이 들어서&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot; data-start=&quot;1828&quot; data-end=&quot;1940&quot;&gt;강사님과 운영진분들께 감사했다.&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot; data-start=&quot;1828&quot; data-end=&quot;1940&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot; data-start=&quot;1942&quot; data-end=&quot;2092&quot;&gt;&lt;b&gt;② 비용 걱정 없는 실습 환경&lt;/b&gt;&lt;br /&gt;클라우드에서 실습하려면 &lt;b&gt;과금이 늘 부담&lt;/b&gt;된다.&lt;br /&gt;하지만 교육 계정 덕분에 마음껏 서버를 만들고 삭제하고 Auto Scaling도 설정해보고&lt;br /&gt;SSL VPN까지 직접 연결해보는 등 &lt;b&gt;실제 운영 환경에 가까운 경험&lt;/b&gt;을 할 수 있었다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot; data-start=&quot;1942&quot; data-end=&quot;2092&quot;&gt;분명히&amp;nbsp;&lt;b&gt;책으로는&amp;nbsp;배울&amp;nbsp;수&amp;nbsp;없는&amp;nbsp;살아있는&amp;nbsp;경험&lt;/b&gt;이었다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot; data-start=&quot;1942&quot; data-end=&quot;2092&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot; data-start=&quot;2094&quot; data-end=&quot;2194&quot;&gt;&lt;b&gt;③ 저혈당 방지&lt;/b&gt;&lt;br /&gt;매주&amp;nbsp;5시간이라는&amp;nbsp;꽤&amp;nbsp;긴&amp;nbsp;시간의&amp;nbsp;수업이기&amp;nbsp;때문에&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot; data-start=&quot;2094&quot; data-end=&quot;2194&quot;&gt;체력적으로 분명히 지치고 피곤한 순간들이 있었는데&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot; data-start=&quot;2094&quot; data-end=&quot;2194&quot;&gt;당 떨어지는 일은 절대 없었다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot; data-start=&quot;2094&quot; data-end=&quot;2194&quot;&gt;&lt;b&gt;항상 풍족한 과자&lt;/b&gt;를 구비해주셨고 다 떨어지면&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot; data-start=&quot;2094&quot; data-end=&quot;2194&quot;&gt;그만큼의 양을 또 새로 구비해주셨다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot; data-start=&quot;2094&quot; data-end=&quot;2194&quot;&gt;저혈당을 막았다. 정말 감사합니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot; data-start=&quot;2094&quot; data-end=&quot;2194&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-end=&quot;2277&quot; data-start=&quot;2196&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;④ 교육 이후 예정된 네이버 1784 투어&lt;/b&gt;&lt;br /&gt;지금도 기대되는 부분 중 하나다.&lt;br /&gt;실제 현장에 가보는 것은 처음이라 기대된다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-end=&quot;2296&quot; data-start=&quot;2284&quot; data-ke-size=&quot;size26&quot;&gt;4. 아쉬웠던 점&lt;/h2&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-end=&quot;2376&quot; data-start=&quot;2298&quot; data-ke-size=&quot;size16&quot;&gt;&quot;좋았던&amp;nbsp;점&quot;&amp;nbsp;항목이&amp;nbsp;있으면&amp;nbsp;밸런스를&amp;nbsp;맞추기&amp;nbsp;위해&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-end=&quot;2376&quot; data-start=&quot;2298&quot; data-ke-size=&quot;size16&quot;&gt;아쉬웠던&amp;nbsp;점도&amp;nbsp;항목&amp;nbsp;넣어야&amp;nbsp;할&amp;nbsp;것&amp;nbsp;같아서&amp;nbsp;넣긴&amp;nbsp;했는데&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-end=&quot;2376&quot; data-start=&quot;2298&quot; data-ke-size=&quot;size16&quot;&gt;사실&amp;nbsp;아쉬웠던&amp;nbsp;점에&amp;nbsp;대해서는&amp;nbsp;크게&amp;nbsp;생각나는&amp;nbsp;게&amp;nbsp;없다.&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-end=&quot;2376&quot; data-start=&quot;2298&quot; data-ke-size=&quot;size16&quot;&gt;전체적으로는 만족도가 매우 높은 교육이었다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-end=&quot;2394&quot; data-start=&quot;2383&quot; data-ke-size=&quot;size26&quot;&gt;5. 마무리하며&lt;/h2&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-end=&quot;2509&quot; data-start=&quot;2396&quot; data-ke-size=&quot;size16&quot;&gt;6주 동안 아낌없이 가르쳐주신&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-end=&quot;2509&quot; data-start=&quot;2396&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;이영태 강사님&lt;/b&gt;,&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;김지용 강사님&lt;/b&gt;께 진심으로 감사드립니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-end=&quot;2509&quot; data-start=&quot;2396&quot; data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;또한 질문에 항상 친절히 답변해주시고,&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-end=&quot;2509&quot; data-start=&quot;2396&quot; data-ke-size=&quot;size16&quot;&gt;실습 환경까지 세심하게 지원해주신 운영진 분들께도 감사드립니다.&lt;/p&gt;</description>
      <category>네이버클라우드아카데미 Literacy 1기</category>
      <author>hwangsoojin</author>
      <guid isPermaLink="true">https://hwangsoojin.tistory.com/44</guid>
      <comments>https://hwangsoojin.tistory.com/44#entry44comment</comments>
      <pubDate>Sat, 29 Nov 2025 20:51:26 +0900</pubDate>
    </item>
    <item>
      <title>[NCloud 1기] NCP Auto Scaling이 실제로 발동한 날</title>
      <link>https://hwangsoojin.tistory.com/42</link>
      <description>&lt;h2 data-end=&quot;243&quot; data-start=&quot;196&quot; data-ke-size=&quot;size26&quot;&gt;교육 계정의 자동 정지 정책 때문에 예상치 않게 서버가 두 배로 늘어난 사례&lt;/h2&gt;
&lt;p data-end=&quot;365&quot; data-start=&quot;245&quot; data-ke-size=&quot;size16&quot;&gt;네이버클라우드 아카데미 교육 계정을 사용하다가&lt;br /&gt;어느 날 콘솔에서 예상치 못한 장면을 마주했다.&lt;br /&gt;원래 WEB 2대, WAS 2대 총 4대만 있던 서버가&lt;br /&gt;갑자기 &lt;b&gt;8대로 늘어나 있는 것&lt;/b&gt;을 발견한 것이다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h1 data-end=&quot;398&quot; data-start=&quot;372&quot;&gt;1. 아침에 콘솔을 열어보니, 서버가 8대?&lt;/h1&gt;
&lt;p data-end=&quot;485&quot; data-start=&quot;400&quot; data-ke-size=&quot;size16&quot;&gt;어느날 NCP 콘솔의 &lt;b&gt;Server 탭&lt;/b&gt;을 눌러보니&lt;br /&gt;서버 목록이 갑자기 길게 늘어나 있었고, 순간 멈칫했다.&lt;br /&gt;&quot;원래 4대였는데&amp;hellip; 왜 8대지?&quot;&lt;/p&gt;
&lt;p data-end=&quot;485&quot; data-start=&quot;400&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;506&quot; data-start=&quot;487&quot; data-ke-size=&quot;size16&quot;&gt;유심히 보니 다음과 같은 상태였다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;552&quot; data-start=&quot;508&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;530&quot; data-start=&quot;508&quot;&gt;기존 서버 4대 &amp;rarr; &lt;b&gt;정지 상태&lt;/b&gt;&lt;/li&gt;
&lt;li data-end=&quot;552&quot; data-start=&quot;531&quot;&gt;정체불명의 4대 &amp;rarr; &lt;b&gt;운영 중&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-end=&quot;598&quot; data-start=&quot;554&quot; data-ke-size=&quot;size16&quot;&gt;그리고 운영 중인 서버들의 이름을 보는 순간&lt;br /&gt;이유를 바로 떠올릴 수 있었다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;기존_서버_죽_새로_생.png&quot; data-origin-width=&quot;1575&quot; data-origin-height=&quot;779&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/crYcfo/dJMcacBxOAm/dy7t5eK24x3KU8xxkKytL1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/crYcfo/dJMcacBxOAm/dy7t5eK24x3KU8xxkKytL1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/crYcfo/dJMcacBxOAm/dy7t5eK24x3KU8xxkKytL1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcrYcfo%2FdJMcacBxOAm%2Fdy7t5eK24x3KU8xxkKytL1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1575&quot; height=&quot;779&quot; data-filename=&quot;기존_서버_죽_새로_생.png&quot; data-origin-width=&quot;1575&quot; data-origin-height=&quot;779&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-end=&quot;647&quot; data-start=&quot;600&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;766&quot; data-start=&quot;649&quot; data-ke-size=&quot;size16&quot;&gt;운영 중인 서버 이름이&lt;br /&gt;&lt;b&gt;&lt;span style=&quot;background-color: #dddddd;&quot;&gt;&amp;nbsp;webauto-xxxx&amp;nbsp;&lt;/span&gt;&lt;/b&gt; ,&amp;nbsp;&lt;span style=&quot;background-color: #dddddd;&quot;&gt;&lt;b&gt; wasauto-xxxx&amp;nbsp;&lt;/b&gt;&lt;/span&gt; 형태였기 때문이다.&lt;br /&gt;이 접두사(prefix)는 Auto Scaling Group을 만들 때&lt;br /&gt;내가 직접 설정했던 값이었다.&lt;/p&gt;
&lt;h3 data-end=&quot;782&quot; data-start=&quot;768&quot; data-ke-size=&quot;size23&quot;&gt;✔ prefix란?&lt;/h3&gt;
&lt;p data-end=&quot;947&quot; data-start=&quot;783&quot; data-ke-size=&quot;size16&quot;&gt;서버를 자동 생성할 때&lt;br /&gt;*자동 생성 인스턴스 이름 앞에 붙는 고정 문자열(접두사)이다.&lt;br /&gt;NCP에서 Auto Scaling으로 생성되는 서버는&lt;br /&gt;&lt;span style=&quot;background-color: #dddddd;&quot;&gt;&lt;b&gt;&amp;nbsp;prefix-랜덤문자&amp;nbsp;&lt;/b&gt;&lt;/span&gt; 형식으로 이름이 붙는다.&lt;br /&gt;그래서 한눈에 &quot;아, 이건 Auto Scaling이 만든 서버구나&quot;를 알 수 있다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h1 data-end=&quot;981&quot; data-start=&quot;954&quot;&gt;2. 왜 Auto Scaling이 발동했을까?&lt;/h1&gt;
&lt;p data-end=&quot;1061&quot; data-start=&quot;983&quot; data-ke-size=&quot;size16&quot;&gt;내 계정은 &lt;b&gt;교육용 계정&lt;/b&gt;이라 매일 특정 시간대가 되면&lt;br /&gt;계정이 생성한 서버가 자동으로 정지된다.&lt;br /&gt;자원 낭비를 막기 위한 정책이다.&lt;/p&gt;
&lt;p data-end=&quot;1061&quot; data-start=&quot;983&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;1124&quot; data-start=&quot;1063&quot; data-ke-size=&quot;size16&quot;&gt;그 결과,&lt;br /&gt;내가 수동으로 생성해 두었던 WEB 2대, WAS 2대가&lt;br /&gt;밤새 &lt;b&gt;전부 자동 정지&lt;/b&gt;되었다.&lt;/p&gt;
&lt;p data-end=&quot;1124&quot; data-start=&quot;1063&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;1134&quot; data-start=&quot;1126&quot; data-ke-size=&quot;size16&quot;&gt;그런데 문제는 &amp;hellip;&lt;/p&gt;
&lt;blockquote data-end=&quot;1186&quot; data-start=&quot;1136&quot; data-ke-style=&quot;style2&quot;&gt;
&lt;p data-end=&quot;1186&quot; data-start=&quot;1138&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Auto Scaling Group은 &amp;ldquo;최소 용량 2대 유지&amp;rdquo;로 설정해 두었다는 점이다.&lt;/b&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-end=&quot;1219&quot; data-start=&quot;1188&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;1219&quot; data-start=&quot;1188&quot; data-ke-size=&quot;size16&quot;&gt;즉, Auto Scaling 입장에서는 이렇게 판단한다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;1275&quot; data-start=&quot;1221&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;1233&quot; data-start=&quot;1221&quot;&gt;&amp;ldquo;헬스 체크 실패&amp;rdquo;&lt;/li&gt;
&lt;li data-end=&quot;1249&quot; data-start=&quot;1234&quot;&gt;&amp;ldquo;현재 서버 수: 0대&amp;rdquo;&lt;/li&gt;
&lt;li data-end=&quot;1275&quot; data-start=&quot;1250&quot;&gt;&amp;ldquo;최소 용량: 2대 &amp;rarr; 빨리 채워야 한다&amp;rdquo;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-end=&quot;1332&quot; data-start=&quot;1277&quot; data-ke-size=&quot;size16&quot;&gt;그래서 WEB 2대, WAS 2대가&lt;br /&gt;Auto Scaling에 의해 &lt;b&gt;새로 생성된 것&lt;/b&gt;이었다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h1 data-end=&quot;1375&quot; data-start=&quot;1339&quot;&gt;3. 실제 Auto Scaling Group의 최소 용량 설정&lt;/h1&gt;
&lt;p data-end=&quot;1435&quot; data-start=&quot;1377&quot; data-ke-size=&quot;size16&quot;&gt;Auto Scaling Group 목록을 확인해 보니&lt;br /&gt;두 그룹 모두 최소 용량이 2로 설정돼 있었다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;오토스케일링그룹의_최소용량.png&quot; data-origin-width=&quot;1579&quot; data-origin-height=&quot;530&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/0gWTg/dJMcaaDIBpA/iCEVl1DQpTjB2NE0mKZtp0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/0gWTg/dJMcaaDIBpA/iCEVl1DQpTjB2NE0mKZtp0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/0gWTg/dJMcaaDIBpA/iCEVl1DQpTjB2NE0mKZtp0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F0gWTg%2FdJMcaaDIBpA%2FiCEVl1DQpTjB2NE0mKZtp0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1579&quot; height=&quot;530&quot; data-filename=&quot;오토스케일링그룹의_최소용량.png&quot; data-origin-width=&quot;1579&quot; data-origin-height=&quot;530&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-end=&quot;1486&quot; data-start=&quot;1437&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;1562&quot; data-start=&quot;1488&quot; data-ke-size=&quot;size16&quot;&gt;이 설정 때문에&lt;br /&gt;기존 서버가 자동 정지되자마자 Auto Scaling이&lt;br /&gt;&quot;2대를 채워야 한다&quot;며 신규 서버를 만들었던 것이다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h1 data-end=&quot;1606&quot; data-start=&quot;1569&quot;&gt;4. Auto Scaling Group을 만들 때 설정했던 내용&lt;/h1&gt;
&lt;p data-end=&quot;1653&quot; data-start=&quot;1608&quot; data-ke-size=&quot;size16&quot;&gt;Auto Scaling Group을 생성할 때,&lt;br /&gt;나는 다음과 같이 설정했었다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;1731&quot; data-start=&quot;1655&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;1683&quot; data-start=&quot;1655&quot;&gt;&lt;b&gt;서버 이름 Prefix = webauto&lt;/b&gt;&lt;/li&gt;
&lt;li data-end=&quot;1699&quot; data-start=&quot;1684&quot;&gt;&lt;b&gt;최소 용량 = 2&lt;/b&gt;&lt;/li&gt;
&lt;li data-end=&quot;1715&quot; data-start=&quot;1700&quot;&gt;&lt;b&gt;기대 용량 = 2&lt;/b&gt;&lt;/li&gt;
&lt;li data-end=&quot;1731&quot; data-start=&quot;1716&quot;&gt;&lt;b&gt;최대 용량 = 3&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-end=&quot;1836&quot; data-start=&quot;1733&quot; data-ke-size=&quot;size16&quot;&gt;이때 설정한 &lt;b&gt;Prefix(webauto)&lt;/b&gt; 가&lt;br /&gt;새로 생성된 서버 이름에 그대로 붙어 있었기 때문에&lt;br /&gt;&quot;아, Auto Scaling이 발동했구나&quot; 하고 바로 파악할 수 있었다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;그룹을_설정할_때_최소용량을_2대로_설정하였다_prefix_webauto도_그대로_나옴.png&quot; data-origin-width=&quot;1029&quot; data-origin-height=&quot;641&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/c6Ag6L/dJMcaajp6Ob/cFHPo9Bx10Bl8tjTA0SkC1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/c6Ag6L/dJMcaajp6Ob/cFHPo9Bx10Bl8tjTA0SkC1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/c6Ag6L/dJMcaajp6Ob/cFHPo9Bx10Bl8tjTA0SkC1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fc6Ag6L%2FdJMcaajp6Ob%2FcFHPo9Bx10Bl8tjTA0SkC1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1029&quot; height=&quot;641&quot; data-filename=&quot;그룹을_설정할_때_최소용량을_2대로_설정하였다_prefix_webauto도_그대로_나옴.png&quot; data-origin-width=&quot;1029&quot; data-origin-height=&quot;641&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h1 data-end=&quot;1967&quot; data-start=&quot;1928&quot;&gt;5. Auto Scaling에 일정(Schedule)도 걸어두었었다&lt;/h1&gt;
&lt;p data-end=&quot;2017&quot; data-start=&quot;1969&quot; data-ke-size=&quot;size16&quot;&gt;서버를 구축할 당시에 다음과 같은 요구사항을 충족하기 위해&lt;br /&gt;Auto Scaling 일정도 설정해 두었었다.&lt;/p&gt;
&lt;blockquote data-end=&quot;2086&quot; data-start=&quot;2019&quot; data-ke-style=&quot;style2&quot;&gt;
&lt;p data-end=&quot;2086&quot; data-start=&quot;2021&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&quot;매달 1일부터 3일까지 자격증 신청 기간입니다.&lt;/b&gt;&lt;br /&gt;&lt;b&gt;해당 기간에는 WEB, WAS 서버를 3대까지 늘려주세요.&quot;&lt;/b&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-end=&quot;2108&quot; data-start=&quot;2088&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;2108&quot; data-start=&quot;2088&quot; data-ke-size=&quot;size16&quot;&gt;그래서 일정 정책을 이렇게 구성했다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;2174&quot; data-start=&quot;2110&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;2140&quot; data-start=&quot;2110&quot;&gt;매달 1일 00:00 &amp;rarr; 기대 용량 3대로 증가&lt;/li&gt;
&lt;li data-end=&quot;2174&quot; data-start=&quot;2141&quot;&gt;매달 4일 00:00 &amp;rarr; 다시 기대 용량 2대로 감소&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-end=&quot;2241&quot; data-start=&quot;2176&quot; data-ke-size=&quot;size16&quot;&gt;즉, 원래는 &lt;b&gt;특정 기간에만 서버를 늘렸다가 다시 원래대로 돌아오는&lt;/b&gt;&lt;br /&gt;스케일링 목적으로 설정해 둔 정책이었다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;서버증설과_원래대로_돌아오기만_생각했다.png&quot; data-origin-width=&quot;1589&quot; data-origin-height=&quot;848&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/0YjkD/dJMcabJpe65/sjPErBSb1FA8ckTKtWSaJk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/0YjkD/dJMcabJpe65/sjPErBSb1FA8ckTKtWSaJk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/0YjkD/dJMcabJpe65/sjPErBSb1FA8ckTKtWSaJk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F0YjkD%2FdJMcabJpe65%2FsjPErBSb1FA8ckTKtWSaJk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1589&quot; height=&quot;848&quot; data-filename=&quot;서버증설과_원래대로_돌아오기만_생각했다.png&quot; data-origin-width=&quot;1589&quot; data-origin-height=&quot;848&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h1 data-end=&quot;2337&quot; data-start=&quot;2306&quot;&gt;6. 그런데 실제로는 &quot;서버 다운 복구&quot;에도 작동했다&lt;/h1&gt;
&lt;p data-end=&quot;2401&quot; data-start=&quot;2339&quot; data-ke-size=&quot;size16&quot;&gt;나는 Auto Scaling이&lt;br /&gt;주로 트래픽 증가 또는 주기적 스케일링을 기준으로 동작할 것이라고만 생각했다.&lt;/p&gt;
&lt;p data-end=&quot;2489&quot; data-start=&quot;2403&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;2489&quot; data-start=&quot;2403&quot; data-ke-size=&quot;size16&quot;&gt;하지만 이번 사례처럼&lt;br /&gt;&lt;b&gt;서버가 모두 정지돼 헬스 체크에 실패한 상황에서도&lt;br /&gt;최소 서버 수를 맞추기 위해 자동으로 복구&lt;/b&gt;된다는 것을 직접 확인했다.&lt;/p&gt;
&lt;p data-end=&quot;2500&quot; data-start=&quot;2491&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;2500&quot; data-start=&quot;2491&quot; data-ke-size=&quot;size16&quot;&gt;굉장히 신기했다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h1 data-end=&quot;2512&quot; data-start=&quot;2507&quot;&gt;마무리&lt;/h1&gt;
&lt;p data-end=&quot;2570&quot; data-start=&quot;2514&quot; data-ke-size=&quot;size16&quot;&gt;이 경험을 통해 Auto Scaling이 단순히&lt;br /&gt;&quot;서버를 필요할 때 늘렸다 줄이는 기능&quot;을 넘어서&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;2614&quot; data-start=&quot;2572&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;2584&quot; data-start=&quot;2572&quot;&gt;헬스 체크 실패&lt;/li&gt;
&lt;li data-end=&quot;2594&quot; data-start=&quot;2585&quot;&gt;서버 다운&lt;/li&gt;
&lt;li data-end=&quot;2604&quot; data-start=&quot;2595&quot;&gt;강제 정지&lt;/li&gt;
&lt;li data-end=&quot;2614&quot; data-start=&quot;2605&quot;&gt;장애 상황&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-end=&quot;2674&quot; data-start=&quot;2616&quot; data-ke-size=&quot;size16&quot;&gt;이런 환경에서도&amp;nbsp;&lt;span style=&quot;background-color: #dddddd;&quot;&gt;&lt;b&gt; 최소한의 서버 수를 유지하도록 자동 복구&lt;/b&gt;&lt;/span&gt;해 준다는 점을 명확히 이해할 수 있었다.&lt;/p&gt;
&lt;p data-end=&quot;2760&quot; data-start=&quot;2676&quot; data-ke-size=&quot;size16&quot;&gt;교육 계정의 자동 정지 기능 때문에 발생한 일이었지만&lt;br /&gt;결과적으로 Auto Scaling의 핵심 기능을&lt;br /&gt;가장 명확하게 확인할 수 있었던 사례였다.&lt;/p&gt;
&lt;p data-end=&quot;2760&quot; data-start=&quot;2676&quot; data-ke-size=&quot;size16&quot;&gt;자동 정지 감사합니다 ...&lt;/p&gt;</description>
      <category>네이버클라우드아카데미 Literacy 1기</category>
      <author>hwangsoojin</author>
      <guid isPermaLink="true">https://hwangsoojin.tistory.com/42</guid>
      <comments>https://hwangsoojin.tistory.com/42#entry42comment</comments>
      <pubDate>Tue, 25 Nov 2025 17:57:52 +0900</pubDate>
    </item>
    <item>
      <title>[NCloud 1기] 5주차 회고</title>
      <link>https://hwangsoojin.tistory.com/41</link>
      <description>&lt;h1 data-end=&quot;130&quot; data-start=&quot;118&quot;&gt;&lt;b&gt;5주차 회고&lt;/b&gt;&lt;/h1&gt;
&lt;p data-end=&quot;153&quot; data-start=&quot;132&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;작성일:&lt;/b&gt; 2025.11.25&lt;/p&gt;
&lt;p data-end=&quot;314&quot; data-start=&quot;155&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;학습 주제: &lt;/b&gt;3-Tier 아키텍처 설계 및 구현 미니 프로젝트&lt;/p&gt;
&lt;p data-end=&quot;410&quot; data-start=&quot;316&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;실습 환경: &lt;/b&gt;NCP Console + PuTTY + WinSCP&lt;/p&gt;
&lt;p data-end=&quot;431&quot; data-start=&quot;412&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;작성자:&lt;/b&gt; 황수진_컴퓨터과학부&lt;/p&gt;
&lt;p data-end=&quot;431&quot; data-start=&quot;412&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;431&quot; data-start=&quot;412&quot; data-ke-size=&quot;size16&quot;&gt;이번 주차에서는 3-Tier 아키텍처를 직접 구성해보는 팀 프로젝트를 진행했다.&lt;br /&gt;3명으로 팀을 구성했고, 역할은 아키텍처 설계 / 백엔드 / 프론트엔드로 나눴다.&lt;br /&gt;나는 &lt;b&gt;백엔드를 맡아서 API 설계, 구현, 배포&lt;/b&gt;까지 담당했다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 data-end=&quot;302&quot; data-start=&quot;284&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;1. KEEP (유지할 점)&lt;/b&gt;&lt;/h2&gt;
&lt;h3 data-end=&quot;336&quot; data-start=&quot;304&quot; data-ke-size=&quot;size23&quot;&gt;1-1. 피드백을 바로 반영한 아키텍처 구조도 개선&lt;/h3&gt;
&lt;p data-end=&quot;442&quot; data-start=&quot;338&quot; data-ke-size=&quot;size16&quot;&gt;프로젝트 결과를 발표했을 때,&lt;/p&gt;
&lt;p data-end=&quot;442&quot; data-start=&quot;338&quot; data-ke-size=&quot;size16&quot;&gt;강사님께서 전체적인 완성도는 좋다고 칭찬을 해주셨다.&lt;br /&gt;다만 보완할 점으로,&lt;/p&gt;
&lt;p data-end=&quot;442&quot; data-start=&quot;338&quot; data-ke-size=&quot;size16&quot;&gt;우리 팀의 &lt;b&gt;아키텍처 구조도가 눈으로 보기에 너무 복잡하다&lt;/b&gt;는 지적을 받았다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;아키텍처_변경_전.png&quot; data-origin-width=&quot;3184&quot; data-origin-height=&quot;2752&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/3SA35/dJMcafyhQ9Q/ybmYIxYxcmwp2gr9a0b4bK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/3SA35/dJMcafyhQ9Q/ybmYIxYxcmwp2gr9a0b4bK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/3SA35/dJMcafyhQ9Q/ybmYIxYxcmwp2gr9a0b4bK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F3SA35%2FdJMcafyhQ9Q%2FybmYIxYxcmwp2gr9a0b4bK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;3184&quot; height=&quot;2752&quot; data-filename=&quot;아키텍처_변경_전.png&quot; data-origin-width=&quot;3184&quot; data-origin-height=&quot;2752&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;528&quot; data-start=&quot;444&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;466&quot; data-start=&quot;444&quot;&gt;선이 지나치게 많이 교차하고 있고&lt;/li&gt;
&lt;li data-end=&quot;493&quot; data-start=&quot;467&quot;&gt;각 계층이 한 번에 눈에 들어오지 않으며&lt;/li&gt;
&lt;li data-end=&quot;528&quot; data-start=&quot;494&quot;&gt;처음 보는 사람이 보면 흐름을 파악하기 어려운 그림이었다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-end=&quot;717&quot; data-start=&quot;559&quot; data-ke-size=&quot;size16&quot;&gt;다른 팀들의 아키텍처 다이어그램이 생각보다 훨씬 깔끔해서,&lt;/p&gt;
&lt;p data-end=&quot;717&quot; data-start=&quot;559&quot; data-ke-size=&quot;size16&quot;&gt;발표를 들으면서 많이 반성했다.&lt;br /&gt;&lt;b&gt;아키텍처 구조 자체만 맞으면 된다&lt;/b&gt;고 생각했는데,&lt;/p&gt;
&lt;p data-end=&quot;717&quot; data-start=&quot;559&quot; data-ke-size=&quot;size16&quot;&gt;실제로는 &lt;b&gt;이 구조도를 보는 사람(고객사, 다른 팀원, 운영자)이 한눈에 이해할 수 있게&lt;/b&gt;&lt;/p&gt;
&lt;p data-end=&quot;717&quot; data-start=&quot;559&quot; data-ke-size=&quot;size16&quot;&gt;그려져 있는지가 더 중요할 수 있다는 점을 깨달았다.&lt;/p&gt;
&lt;p data-end=&quot;717&quot; data-start=&quot;559&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;771&quot; data-start=&quot;719&quot; data-ke-size=&quot;size16&quot;&gt;그래서 발표가 끝났다고 그냥 넘기지 않고,&lt;/p&gt;
&lt;p data-end=&quot;771&quot; data-start=&quot;719&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;수업이 끝난 뒤에 구조도를 스스로 다시 그려보았다.&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;아키텍처_변경_후.png&quot; data-origin-width=&quot;929&quot; data-origin-height=&quot;567&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/y5xXs/dJMcajm6fc2/GSJK7vyLVlVfwyTYKwSW51/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/y5xXs/dJMcajm6fc2/GSJK7vyLVlVfwyTYKwSW51/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/y5xXs/dJMcajm6fc2/GSJK7vyLVlVfwyTYKwSW51/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fy5xXs%2FdJMcajm6fc2%2FGSJK7vyLVlVfwyTYKwSW51%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;929&quot; height=&quot;567&quot; data-filename=&quot;아키텍처_변경_후.png&quot; data-origin-width=&quot;929&quot; data-origin-height=&quot;567&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;910&quot; data-start=&quot;773&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;792&quot; data-start=&quot;773&quot;&gt;불필요하게 교차하는 선 정리&lt;/li&gt;
&lt;li data-end=&quot;855&quot; data-start=&quot;793&quot;&gt;WEB / WAS / DB / Edge / Security / Monitoring 등 역할별 영역 재배치&lt;/li&gt;
&lt;li data-end=&quot;910&quot; data-start=&quot;856&quot;&gt;관리자 경로(SSL VPN, IDS, Cloud Insight)의 흐름도 따로 눈에 띄게 정리&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-end=&quot;1131&quot; data-start=&quot;941&quot; data-ke-size=&quot;size16&quot;&gt;이렇게 다시 그려보는 과정에서, 나 스스로도 아키텍처를 더 잘 이해하게 되었다.&lt;br /&gt;이미 우리 팀 아키텍처 구조를 잘 이해하고 있다고 생각했지만,&lt;/p&gt;
&lt;p data-end=&quot;1131&quot; data-start=&quot;941&quot; data-ke-size=&quot;size16&quot;&gt;보기 좋은 그림으로 재구성하다 보니&lt;b&gt; 세부 흐름이 더 선명하게 정리되는 경험&lt;/b&gt;을 했다.&lt;br /&gt;앞으로도 아키텍처를 설계할 때는&lt;/p&gt;
&lt;p data-end=&quot;1131&quot; data-start=&quot;941&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&quot;맞게 만드는 것&quot;&lt;/b&gt;과 동시에&lt;b&gt; &lt;/b&gt;&lt;/p&gt;
&lt;p data-end=&quot;1131&quot; data-start=&quot;941&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&quot;보기 좋게 설명할 수 있는 것&quot;&lt;/b&gt;까지 항상 함께 고민해야겠다고 느꼈다.&lt;/p&gt;
&lt;p data-end=&quot;1131&quot; data-start=&quot;941&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-end=&quot;1168&quot; data-start=&quot;1138&quot; data-ke-size=&quot;size23&quot;&gt;1-2. 배운 내용을 꾸준히 기록으로 남긴 습관&lt;/h3&gt;
&lt;p data-end=&quot;1318&quot; data-start=&quot;1170&quot; data-ke-size=&quot;size16&quot;&gt;5주차는 단순히 기능만 구현하는 것으로 끝내지 않았다.&lt;br /&gt;로드밸런서, Auto Scaling, SSL VPN, Object Storage, Image Optimizer 등&lt;/p&gt;
&lt;p data-end=&quot;1318&quot; data-start=&quot;1170&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;과금 걱정없이 다양한 NCP 서비스를 동시에 다루는 경험&lt;/b&gt;이 너무 소중하다 보니&lt;/p&gt;
&lt;p data-end=&quot;1318&quot; data-start=&quot;1170&quot; data-ke-size=&quot;size16&quot;&gt;이 경험, 배운 내용, 문제점을 모두 &lt;b&gt;기록&lt;/b&gt;하고 싶었다.&lt;/p&gt;
&lt;p data-end=&quot;1318&quot; data-start=&quot;1170&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;1352&quot; data-start=&quot;1320&quot; data-ke-size=&quot;size16&quot;&gt;그래서 프로젝트를 진행하면서 다음과 같은 습관을 유지했다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;1422&quot; data-start=&quot;1354&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;1374&quot; data-start=&quot;1354&quot;&gt;각 단계에서 콘솔 화면을 캡처&lt;/li&gt;
&lt;li data-end=&quot;1400&quot; data-start=&quot;1375&quot;&gt;문제가 생긴 에러 메시지를 그대로 저장&lt;/li&gt;
&lt;li data-end=&quot;1422&quot; data-start=&quot;1401&quot;&gt;해결 과정을 티스토리 글로 정리&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-end=&quot;1564&quot; data-start=&quot;1424&quot; data-ke-size=&quot;size16&quot;&gt;사람은 금방 잊어버리는 존재라,&lt;b&gt; &lt;/b&gt;&lt;/p&gt;
&lt;p data-end=&quot;1564&quot; data-start=&quot;1424&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&quot;지금은 당연히 기억하겠지&quot;&lt;/b&gt;라고 생각한 내용도 며칠 지나면 흐릿해진다.&lt;br /&gt;나중에 비슷한 인프라를 다시 구성하거나,&lt;/p&gt;
&lt;p data-end=&quot;1564&quot; data-start=&quot;1424&quot; data-ke-size=&quot;size16&quot;&gt;3-Tier 구조를 발전시킬 일이 생겼을 때&lt;/p&gt;
&lt;p data-end=&quot;1564&quot; data-start=&quot;1424&quot; data-ke-size=&quot;size16&quot;&gt;이 기록들이 큰 도움이 될 것 같아서 꾸준히 남겨 두었다.&lt;/p&gt;
&lt;p data-end=&quot;1564&quot; data-start=&quot;1424&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;1604&quot; data-start=&quot;1566&quot; data-ke-size=&quot;size16&quot;&gt;아래 글들은 이번 3-Tier 프로젝트를 진행하면서 남긴 기록들 중 그 일부를 공유한다.&lt;/p&gt;
&lt;p data-end=&quot;1604&quot; data-start=&quot;1566&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;1604&quot; data-start=&quot;1566&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span style=&quot;letter-spacing: 0px;&quot;&gt;전체 아키텍처를 어떻게 설계하고, &lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-end=&quot;1604&quot; data-start=&quot;1566&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span style=&quot;letter-spacing: 0px;&quot;&gt;어떤 순서로 구성했는지 정리하였습니다.&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #222222; text-align: start;&quot;&gt;  &lt;a href=&quot;https://hwangsoojin.tistory.com/8&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;&lt;b&gt;[NCloud 1기] Naver Cloud Platform으로 3-Tier 아키텍처 구성하기&lt;/b&gt; &lt;/a&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;SSH 세션에 종속된 프로세스, &lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;nohup 실행 방식 등을 정리하였습니다.&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #222222; text-align: start;&quot;&gt; &lt;a href=&quot;https://hwangsoojin.tistory.com/5&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;&lt;b&gt;&lt;span&gt; [NCloud 1기] PuTTY를 끄고 나니 NCP 서버의 Spring Boot가 죽어버린 문제 &lt;/span&gt;&lt;/b&gt;&lt;/a&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;재부팅 후 프로세스 자동 실행, 서버 이미지에 대한 오해, &lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;systemd 도입까지 정리하였습니다.&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #222222; text-align: start;&quot;&gt; &lt;a href=&quot;https://hwangsoojin.tistory.com/29&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;&lt;b&gt;&lt;span&gt; [NCloud 1기] 서버를 껐다 켰더니 백엔드가 사라졌다 &lt;/span&gt;&lt;/b&gt;&lt;/a&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;1980&quot; data-start=&quot;1943&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;이 습관은 앞으로 &lt;/b&gt;&lt;/p&gt;
&lt;p data-end=&quot;1980&quot; data-start=&quot;1943&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&quot;단순히 공부를 하면서&quot;, &quot;다른 프로젝트를 하면서&quot;도 유지하고 싶은 부분&lt;/b&gt;이다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 data-end=&quot;2009&quot; data-start=&quot;1987&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;2. PROBLEM (어려웠던 점)&lt;/b&gt;&lt;/h2&gt;
&lt;h3 data-end=&quot;2054&quot; data-start=&quot;2011&quot; data-ke-size=&quot;size23&quot;&gt;2-1. 예상보다 훨씬 많은 시간과 에너지를 쏟아야 했던 미니 프로젝트&lt;/h3&gt;
&lt;p data-end=&quot;2203&quot; data-start=&quot;2056&quot; data-ke-size=&quot;size16&quot;&gt;처음 이영태 강사님의 안내를 들었을 때는&lt;/p&gt;
&lt;p data-end=&quot;2203&quot; data-start=&quot;2056&quot; data-ke-size=&quot;size16&quot;&gt;&quot;부담 갖지 않고 경험 위주로 진행해도 된다&quot;라는 말이 있어서,&lt;/p&gt;
&lt;p data-end=&quot;2203&quot; data-start=&quot;2056&quot; data-ke-size=&quot;size16&quot;&gt;비교적 가벼운 미니 프로젝트라고 생각했다.&lt;br /&gt;하지만 실제로는 월요일에 아키텍처 초안이 확정된 이후,&lt;/p&gt;
&lt;p data-end=&quot;2203&quot; data-start=&quot;2056&quot; data-ke-size=&quot;size16&quot;&gt;화요일부터 금요일 오전까지 거의 내내 개발과 배포에 시간을 쏟아야 했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&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;b&gt;로컬에서 되던 것이 클라우드 서버에서는 안 되기도&lt;/b&gt; 하였다.&lt;/p&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;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;2531&quot; data-start=&quot;2348&quot; data-ke-size=&quot;size16&quot;&gt;그런데 실제로 에러를 만나고,&lt;/p&gt;
&lt;p data-end=&quot;2531&quot; data-start=&quot;2348&quot; data-ke-size=&quot;size16&quot;&gt;로그를 뒤지고,&lt;/p&gt;
&lt;p data-end=&quot;2531&quot; data-start=&quot;2348&quot; data-ke-size=&quot;size16&quot;&gt;설정을 하나씩 바꿔가며 트러블슈팅하는 동안&lt;/p&gt;
&lt;p data-end=&quot;2531&quot; data-start=&quot;2348&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;클라우드에 대한 이해도가 이전과 비교도 안 될 정도로 올라갔다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-end=&quot;2531&quot; data-start=&quot;2348&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;2531&quot; data-start=&quot;2348&quot; data-ke-size=&quot;size16&quot;&gt;스트레스도 컸지만, 프로젝트가 끝난 뒤에는&lt;/p&gt;
&lt;p data-end=&quot;2531&quot; data-start=&quot;2348&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&quot;이걸 안 해봤으면 어쩔 뻔했나&quot;&lt;/b&gt; 싶을 정도로 성장한 느낌이었다.&lt;/p&gt;
&lt;p data-end=&quot;2531&quot; data-start=&quot;2348&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-end=&quot;2580&quot; data-start=&quot;2538&quot; data-ke-size=&quot;size23&quot;&gt;2-2. Image Optimizer 연동 과정에서의 쿼리스트링 실수 지옥&lt;/h3&gt;
&lt;p data-end=&quot;2693&quot; data-start=&quot;2582&quot; data-ke-size=&quot;size16&quot;&gt;이번 프로젝트에서는 NCP Image Optimizer를 이용해&lt;/p&gt;
&lt;p data-end=&quot;2693&quot; data-start=&quot;2582&quot; data-ke-size=&quot;size16&quot;&gt;업로드된 이미지를 300x300 사이즈로 리사이즈하는 기능을 구현했다.&lt;/p&gt;
&lt;p data-end=&quot;2693&quot; data-start=&quot;2582&quot; data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;문제는, 이 Optimizer API를 처음 연동할 때였다.&lt;/p&gt;
&lt;p data-end=&quot;2733&quot; data-start=&quot;2695&quot; data-ke-size=&quot;size16&quot;&gt;문서를 자세히 읽었다고 생가하였고 그것을 바탕으로 구현했더니,&lt;/p&gt;
&lt;p data-end=&quot;2733&quot; data-start=&quot;2695&quot; data-ke-size=&quot;size16&quot;&gt;계속 이런 식의 에러가 떨어졌다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;2811&quot; data-start=&quot;2735&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;2759&quot; data-start=&quot;2735&quot;&gt;HTTP 400 Bad Request&lt;/li&gt;
&lt;li data-end=&quot;2778&quot; data-start=&quot;2760&quot;&gt;파라미터 누락 오류 메시지&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-end=&quot;2859&quot; data-start=&quot;2813&quot; data-ke-size=&quot;size16&quot;&gt;원인은 요구하는 &quot;쿼리스트링&quot;을 제대로 만족시키지 못한 코드를 짰기 때문이었다.&lt;/p&gt;
&lt;p data-end=&quot;3402&quot; data-start=&quot;3294&quot; data-ke-size=&quot;size16&quot;&gt;문서를 제대로 읽었다고 생각했는데...&lt;/p&gt;
&lt;p data-end=&quot;3402&quot; data-start=&quot;3294&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;3402&quot; data-start=&quot;3294&quot; data-ke-size=&quot;size16&quot;&gt;내가 &lt;b&gt;미처 읽지 못한&amp;nbsp;부분에서&lt;/b&gt;&lt;/p&gt;
&lt;p data-end=&quot;3402&quot; data-start=&quot;3294&quot; data-ke-size=&quot;size16&quot;&gt;쿼리스트링 형식에 대한 디테일이 살아있었다.&lt;/p&gt;
&lt;p data-end=&quot;3402&quot; data-start=&quot;3294&quot; data-ke-size=&quot;size16&quot;&gt;이 디테일을 찾지 못해서 헤메다가 낭비한 시간이&lt;/p&gt;
&lt;p data-end=&quot;3402&quot; data-start=&quot;3294&quot; data-ke-size=&quot;size16&quot;&gt;4-5시간은 됐던 것 같다...&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;3402&quot; data-start=&quot;3294&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;3402&quot; data-start=&quot;3294&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&quot;API 연동은 문서를 꼼꼼히 읽는 것으로 개발을 시작해야 한다&quot;&lt;/b&gt;는&lt;/p&gt;
&lt;p data-end=&quot;3402&quot; data-start=&quot;3294&quot; data-ke-size=&quot;size16&quot;&gt;아주 기본적인 사실을 몸소 체험했다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 data-end=&quot;4785&quot; data-start=&quot;4756&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;3. TRY (개선할 점 / 앞으로 시도할 것)&lt;/b&gt;&lt;/h2&gt;
&lt;h3 data-end=&quot;4822&quot; data-start=&quot;4787&quot; data-ke-size=&quot;size23&quot;&gt;3-1. 에러는 당연하되, 스트레스 관리도 함께 가져가기&lt;/h3&gt;
&lt;p data-end=&quot;4939&quot; data-start=&quot;4824&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;개발이라는 것은 결국 에러와 로그, 트러블슈팅의 연속이다.&lt;/b&gt;&lt;br /&gt;이번에도 여러 문제를 해결하면서 얻은 게 많았지만,&lt;/p&gt;
&lt;p data-end=&quot;4939&quot; data-start=&quot;4824&quot; data-ke-size=&quot;size16&quot;&gt;&quot;기한 안에 배포를 못 끝내면 어떡하지?&quot;라는 &lt;b&gt;불안감&lt;/b&gt; 때문에&lt;/p&gt;
&lt;p data-end=&quot;4939&quot; data-start=&quot;4824&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;과하게 스트레스를 받기도&lt;/b&gt; 했다.&lt;/p&gt;
&lt;p data-end=&quot;4946&quot; data-start=&quot;4941&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;4946&quot; data-start=&quot;4941&quot; data-ke-size=&quot;size16&quot;&gt;앞으로는 에러는 당연한 것으로 인정하고&lt;/p&gt;
&lt;p data-end=&quot;4946&quot; data-start=&quot;4941&quot; data-ke-size=&quot;size16&quot;&gt;조금 더 행복하고 여유 있게 프로젝트를 진행하는 연습이 필요하다고 느꼈다.&lt;/p&gt;
&lt;p data-end=&quot;4946&quot; data-start=&quot;4941&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-end=&quot;5151&quot; data-start=&quot;5110&quot; data-ke-size=&quot;size23&quot;&gt;3-2. 초반 설계 단계에서 다이어그램과 API 문서를 더 철저하게&lt;/h3&gt;
&lt;p data-end=&quot;5218&quot; data-start=&quot;5153&quot; data-ke-size=&quot;size16&quot;&gt;이번 프로젝트를 돌아보면, 초반 설계와 문서 읽기 단계를 좀 더 탄탄하게 가져갔으면 좋았을 것 같다는 아쉬움이 있다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;5322&quot; data-start=&quot;5220&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;5270&quot; data-start=&quot;5220&quot;&gt;아키텍처 다이어그램은 기능 구현이 어느 정도 끝난 뒤에야 &lt;br /&gt;본격적으로 다듬기 시작했고&lt;/li&gt;
&lt;li data-end=&quot;5322&quot; data-start=&quot;5271&quot;&gt;Image Optimizer 연동도 문서를 충분히 읽기 전에 코드를 먼저 짜기 시작했다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-end=&quot;5340&quot; data-start=&quot;5324&quot; data-ke-size=&quot;size16&quot;&gt;다음에는 이렇게 해보고 싶다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;5520&quot; data-start=&quot;5342&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;5397&quot; data-start=&quot;5342&quot;&gt;아키텍처 초안을 그릴 때부터 &quot;보기 좋은 구조도&quot;를 목표로 잡고, &lt;br /&gt;팀원들과 더 많이 리뷰하기&lt;/li&gt;
&lt;li data-end=&quot;5520&quot; data-start=&quot;5487&quot;&gt;설계/문서 단계와 구현 단계를 조금 더 명확하게 분리하기&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-end=&quot;5575&quot; data-start=&quot;5522&quot; data-ke-size=&quot;size16&quot;&gt;이렇게 하면 불필요하게 헤매는 시간을 줄이고,&lt;/p&gt;
&lt;p data-end=&quot;5575&quot; data-start=&quot;5522&quot; data-ke-size=&quot;size16&quot;&gt;더 중요한 부분에 에너지를 쓸 수 있을 것 같다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 data-end=&quot;5900&quot; data-start=&quot;5877&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;4. 느낀 점 (Reflection)&lt;/b&gt;&lt;/h2&gt;
&lt;p data-end=&quot;6023&quot; data-start=&quot;5902&quot; data-ke-size=&quot;size16&quot;&gt;네이버클라우드 아카데미를 수강하기 전과 후를 비교하면,&lt;/p&gt;
&lt;p data-end=&quot;6023&quot; data-start=&quot;5902&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;이론 교육만으로도 이미 클라우드에 대한 이해도는 많이 올라와 있었다.&lt;/b&gt;&lt;br /&gt;하지만 5주차 미니 프로젝트는 그 위에 실제 경험을 한 층 더 쌓아 올려 준 시간이었다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;6216&quot; data-start=&quot;6025&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;6066&quot; data-start=&quot;6025&quot;&gt;&lt;b&gt;WEB / WAS / DB로 나뉜 3-Tier 구조&lt;/b&gt;를 직접 구성하고&lt;/li&gt;
&lt;li data-end=&quot;6149&quot; data-start=&quot;6067&quot;&gt;&lt;b&gt;로드밸런서, Auto Scaling, SSL VPN, &lt;br /&gt;Cloud DB, Object Storage, Image Optimizer&lt;/b&gt;까지 연결해서&lt;/li&gt;
&lt;li data-end=&quot;6216&quot; data-start=&quot;6150&quot;&gt;실제 외부 사용자에게 노출할 수 있는 형태의 서비스를 만들어 본 경험은,&lt;br /&gt;&lt;b&gt;학교 수업만으로는 얻기 어려운 경험&lt;/b&gt;이었다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-end=&quot;6327&quot; data-start=&quot;6218&quot; data-ke-size=&quot;size16&quot;&gt;과금 걱정 없이 서버를 만들었다가 지워 보고, &lt;br /&gt;설정을 바꿔 보고, 잘못 구성해서 망가뜨려 보기도 했다.&lt;br /&gt;이 과정이 나를 &lt;b&gt;&quot;설명만 들은 사람&quot;에서 &quot;직접 운용해 본 사람&quot;으로 바꿔 준 것 같다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-is-only-node=&quot;&quot; data-is-last-node=&quot;&quot; data-end=&quot;6432&quot; data-start=&quot;6329&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-is-only-node=&quot;&quot; data-is-last-node=&quot;&quot; data-end=&quot;6432&quot; data-start=&quot;6329&quot; data-ke-size=&quot;size16&quot;&gt;5주차가 끝난 지금,&lt;/p&gt;
&lt;p data-is-only-node=&quot;&quot; data-is-last-node=&quot;&quot; data-end=&quot;6432&quot; data-start=&quot;6329&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;클라우드 인프라를 바라보는 시야가 분명히 한 단계 넓어졌다.&lt;/b&gt;&lt;br /&gt;이번 경험을 발판 삼아, &lt;br /&gt;&lt;b&gt;앞으로는 더 큰 규모의 아키텍처와 더 복잡한 시스템에도 도전해 보고 싶다.&lt;/b&gt;&lt;/p&gt;</description>
      <category>네이버클라우드아카데미 Literacy 1기</category>
      <author>hwangsoojin</author>
      <guid isPermaLink="true">https://hwangsoojin.tistory.com/41</guid>
      <comments>https://hwangsoojin.tistory.com/41#entry41comment</comments>
      <pubDate>Tue, 25 Nov 2025 17:47:28 +0900</pubDate>
    </item>
    <item>
      <title>SQLD 59회 응시 후기 &amp;ndash; 대학생, 일정 지옥 속에서도 도전하다</title>
      <link>https://hwangsoojin.tistory.com/40</link>
      <description>&lt;p data-end=&quot;456&quot; data-start=&quot;309&quot; data-ke-size=&quot;size16&quot;&gt;이번 학기, 정말 정신이 없었다.&lt;br /&gt;전공 과목만 해도 벅찬데,&lt;/p&gt;
&lt;p data-end=&quot;456&quot; data-start=&quot;309&quot; data-ke-size=&quot;size16&quot;&gt;소프트웨어공학 팀플,&lt;/p&gt;
&lt;p data-end=&quot;456&quot; data-start=&quot;309&quot; data-ke-size=&quot;size16&quot;&gt;우테코 프리코스 오픈 미션,&lt;/p&gt;
&lt;p data-end=&quot;456&quot; data-start=&quot;309&quot; data-ke-size=&quot;size16&quot;&gt;네이버클라우드 아카데미 실습 프로젝트까지&amp;hellip;&lt;br /&gt;게다가 중간중간 학교 과제, 발표, 포트폴리오 준비까지 이어지니 하루가 어떻게 지나가는지도 모르겠더라.&lt;/p&gt;
&lt;p data-end=&quot;456&quot; data-start=&quot;309&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;560&quot; data-start=&quot;458&quot; data-ke-size=&quot;size16&quot;&gt;그럼에도 데이터베이스 전공과목을 수강하고 있는 지금..!&lt;br /&gt;&amp;ldquo;지금이 아니면 SQLD를 이렇게 수월하게 준비할 시기가 또 올까?&amp;rdquo;&lt;/p&gt;
&lt;p data-end=&quot;560&quot; data-start=&quot;458&quot; data-ke-size=&quot;size16&quot;&gt;라는 생각이 갑자기 들었다.&lt;/p&gt;
&lt;p data-end=&quot;560&quot; data-start=&quot;458&quot; data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;그리고 그냥 해버렸다.&lt;br /&gt;&lt;b&gt;SQLD 59회 시험(2024.11.16)&lt;/b&gt; 응시.&lt;/p&gt;
&lt;p data-end=&quot;560&quot; data-start=&quot;458&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;595&quot; data-start=&quot;562&quot; data-ke-size=&quot;size16&quot;&gt;솔직히 미쳤다 싶었다.&lt;br /&gt;하지만 이상하게 후회는 안 된다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 data-end=&quot;634&quot; data-start=&quot;602&quot; data-ke-size=&quot;size26&quot;&gt;1. 시험 접수 &amp;amp; 준비 - 나의 SQLD 도전 동기&lt;/h2&gt;
&lt;p data-end=&quot;731&quot; data-start=&quot;636&quot; data-ke-size=&quot;size16&quot;&gt;나는 이번 학기에 &lt;b&gt;데이터베이스 전공 과목&lt;/b&gt;을 듣고 있었다.&lt;br /&gt;수업 내용이 SQLD 범위와 많이 겹치길래, &amp;lsquo;이왕 공부하는 거 자격증 하나 따두자&amp;rsquo;는 생각이 들었다.&lt;/p&gt;
&lt;p data-end=&quot;814&quot; data-start=&quot;733&quot; data-ke-size=&quot;size16&quot;&gt;자격증 하나라고 해서 부담 없는 건 아니었다.&lt;br /&gt;하지만 개발자로 진지하게 준비하는 지금, &lt;b&gt;나를 한 단계 더 밀어붙일 타이밍&lt;/b&gt;이라고 느꼈다.&lt;/p&gt;
&lt;p data-end=&quot;838&quot; data-start=&quot;816&quot; data-ke-size=&quot;size16&quot;&gt;시험 당일엔 나도 이런 말을 중얼거렸다.&lt;/p&gt;
&lt;blockquote data-end=&quot;869&quot; data-start=&quot;840&quot; data-ke-style=&quot;style2&quot;&gt;
&lt;p data-end=&quot;869&quot; data-start=&quot;842&quot; data-ke-size=&quot;size16&quot;&gt;&amp;ldquo;그래도&amp;hellip; 여기까지 온 것도 대단한 거 아니냐?&amp;rdquo;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;658&quot; data-origin-height=&quot;809&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ccmLkg/dJMcajgi05p/y4LW4DyD136DIwk8ekDKqk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ccmLkg/dJMcajgi05p/y4LW4DyD136DIwk8ekDKqk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ccmLkg/dJMcajgi05p/y4LW4DyD136DIwk8ekDKqk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FccmLkg%2FdJMcajgi05p%2Fy4LW4DyD136DIwk8ekDKqk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;658&quot; height=&quot;809&quot; data-origin-width=&quot;658&quot; data-origin-height=&quot;809&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 data-end=&quot;966&quot; data-start=&quot;931&quot; data-ke-size=&quot;size26&quot;&gt;2. 공부 방법 - 지하철, 새벽, 강의실 사이에서 틈틈이&lt;/h2&gt;
&lt;p data-end=&quot;1055&quot; data-start=&quot;968&quot; data-ke-size=&quot;size16&quot;&gt;본격적인 공부는 &lt;b&gt;한 달 정도&lt;/b&gt;.&lt;br /&gt;하지만 &amp;lsquo;몰아서 공부한 한 달&amp;rsquo;이 아니라&lt;br /&gt;&lt;b&gt;지옥 일정 속에서 가능한 시간들을 다 긁어서 만든 한 달&lt;/b&gt;이었다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;1211&quot; data-start=&quot;1057&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;1080&quot; data-start=&quot;1057&quot;&gt;지하철 통학 시간 = 기출 한 세트&lt;/li&gt;
&lt;li data-end=&quot;1105&quot; data-start=&quot;1081&quot;&gt;팀플 끝나고 카페 = 조인&amp;middot;집계 정리&lt;/li&gt;
&lt;li data-end=&quot;1148&quot; data-start=&quot;1106&quot;&gt;오픈 미션 JSON 분석 코드 짜다 머리아플 때 = SQL 문법 복습&lt;/li&gt;
&lt;li data-end=&quot;1180&quot; data-start=&quot;1149&quot;&gt;NCP 서버 생성 기다리는 동안 = 정규화 외우기&lt;/li&gt;
&lt;li data-end=&quot;1211&quot; data-start=&quot;1181&quot;&gt;새벽 3시 = &quot;아&amp;hellip; IN이냐 EXISTS냐&amp;hellip;&quot;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-end=&quot;1232&quot; data-start=&quot;1213&quot; data-ke-size=&quot;size16&quot;&gt;정말 &amp;lsquo;틈틈이&amp;rsquo;의 극한 버전이었다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 data-end=&quot;1263&quot; data-start=&quot;1239&quot; data-ke-size=&quot;size26&quot;&gt;3. 사용 교재 - 전공 수업 ppt&amp;nbsp; + 노랭이 책 + 기출&lt;/h2&gt;
&lt;p data-end=&quot;1276&quot; data-start=&quot;1265&quot; data-ke-size=&quot;size16&quot;&gt;딱 두 가지만 했다.&lt;/p&gt;
&lt;p data-end=&quot;1276&quot; data-start=&quot;1265&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;1. 전공 ppt 정독하고 개념 잡기&lt;/b&gt;&lt;br /&gt;SQLD는 개념만 잘 잡아도 절반 이상은 간다.&lt;br /&gt;특히 전공 ppt에는 조인이나 정규화와 같은 빈출 개념이 정리가 잘 되어 있었다!&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;1276&quot; data-start=&quot;1265&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;1276&quot; data-start=&quot;1265&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;2. 노랭이 책 (SQLD 수험서)&lt;/b&gt;&lt;br /&gt;핵심 정리 + 기출 유형 감 익히는 데 최고였다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1029&quot; data-origin-height=&quot;582&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/KW3wL/dJMcahixsJN/Rf90fRXSv6PgE4Os8Qtzv1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/KW3wL/dJMcahixsJN/Rf90fRXSv6PgE4Os8Qtzv1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/KW3wL/dJMcahixsJN/Rf90fRXSv6PgE4Os8Qtzv1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FKW3wL%2FdJMcahixsJN%2FRf90fRXSv6PgE4Os8Qtzv1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1029&quot; height=&quot;582&quot; data-origin-width=&quot;1029&quot; data-origin-height=&quot;582&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 data-end=&quot;1518&quot; data-start=&quot;1475&quot; data-ke-size=&quot;size26&quot;&gt;4. 내가 만든 공부 자료들 - &amp;ldquo;이해를 위해 직접 정리한 SQL 노트&amp;rdquo;&lt;/h2&gt;
&lt;p data-end=&quot;1604&quot; data-start=&quot;1520&quot; data-ke-size=&quot;size16&quot;&gt;사실 책만 봤다면 이렇게 오래 기억에 남진 않았을 것 같다.&lt;br /&gt;그래서 &lt;b&gt;내 방식대로 다시 정리&lt;/b&gt;했다.&lt;br /&gt;필기하면서 손이 기억하게 만드는 스타일.&lt;/p&gt;
&lt;p data-end=&quot;1671&quot; data-start=&quot;1606&quot; data-ke-size=&quot;size16&quot;&gt;아래 사진들은 내가 실제로 정리한 SQL 노트들이다.&lt;br /&gt;이걸 기반으로 SQL 개념을 완전히 &amp;lsquo;내 것&amp;rsquo;으로 만들었다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 data-end=&quot;1716&quot; data-start=&quot;1678&quot; data-ke-size=&quot;size23&quot;&gt;4-1. 조인과 서브쿼리 &amp;ndash; 직접 손으로 그려가며 이해한 방식&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;SQLD1.jpg&quot; data-origin-width=&quot;1303&quot; data-origin-height=&quot;4059&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/mWOj5/dJMcadAocdb/id4AgskBST7QDiv8YNUcrk/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/mWOj5/dJMcadAocdb/id4AgskBST7QDiv8YNUcrk/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/mWOj5/dJMcadAocdb/id4AgskBST7QDiv8YNUcrk/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FmWOj5%2FdJMcadAocdb%2Fid4AgskBST7QDiv8YNUcrk%2Fimg.jpg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1303&quot; height=&quot;4059&quot; data-filename=&quot;SQLD1.jpg&quot; data-origin-width=&quot;1303&quot; data-origin-height=&quot;4059&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-end=&quot;1757&quot; data-start=&quot;1718&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;1766&quot; data-start=&quot;1759&quot; data-ke-size=&quot;size16&quot;&gt;이 필기는&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;1843&quot; data-start=&quot;1767&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;1785&quot; data-start=&quot;1767&quot;&gt;JOIN과 서브쿼리의 차이&lt;/li&gt;
&lt;li data-end=&quot;1813&quot; data-start=&quot;1786&quot;&gt;IN / EXISTS / ALL / ANY&lt;/li&gt;
&lt;li data-end=&quot;1843&quot; data-start=&quot;1814&quot;&gt;조인 조건의 의미&lt;br /&gt;를 정리하면서 만든 것이다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-end=&quot;1849&quot; data-start=&quot;1845&quot; data-ke-size=&quot;size16&quot;&gt;특히&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;FROM&lt;/span&gt;&lt;/span&gt;&lt;span&gt; a &lt;/span&gt;&lt;span&gt;&lt;span&gt;INNER&lt;/span&gt;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&lt;span&gt;JOIN&lt;/span&gt;&lt;/span&gt;&lt;span&gt; b &lt;/span&gt;&lt;span&gt;&lt;span&gt;ON&lt;/span&gt;&lt;/span&gt;&lt;span&gt; 조건 &lt;/span&gt;&lt;/span&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;1936&quot; data-start=&quot;1884&quot; data-ke-size=&quot;size16&quot;&gt;이 공식은 그냥 외우는 것보다&lt;br /&gt;왜 저 구조가 되는지를 그림으로 이해하면 훨씬 오래 남는다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 data-end=&quot;1999&quot; data-start=&quot;1943&quot; data-ke-size=&quot;size23&quot;&gt;4-2. FROM &amp;rarr; WHERE &amp;rarr; GROUP BY &amp;rarr; HAVING &amp;rarr; SELECT 실행 순서&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;SQL1.png&quot; data-origin-width=&quot;1188&quot; data-origin-height=&quot;811&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dcV5Mf/dJMcai9xpbM/ibmY7ujfDQZoPKui3sbXAk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dcV5Mf/dJMcai9xpbM/ibmY7ujfDQZoPKui3sbXAk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dcV5Mf/dJMcai9xpbM/ibmY7ujfDQZoPKui3sbXAk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdcV5Mf%2FdJMcai9xpbM%2FibmY7ujfDQZoPKui3sbXAk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1188&quot; height=&quot;811&quot; data-filename=&quot;SQL1.png&quot; data-origin-width=&quot;1188&quot; data-origin-height=&quot;811&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;SQL2.png&quot; data-origin-width=&quot;1174&quot; data-origin-height=&quot;810&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bQssCA/dJMcabJoVj2/Q0dbEtZ7OA6P3e31Vkdg7K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bQssCA/dJMcabJoVj2/Q0dbEtZ7OA6P3e31Vkdg7K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bQssCA/dJMcabJoVj2/Q0dbEtZ7OA6P3e31Vkdg7K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbQssCA%2FdJMcabJoVj2%2FQ0dbEtZ7OA6P3e31Vkdg7K%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1174&quot; height=&quot;810&quot; data-filename=&quot;SQL2.png&quot; data-origin-width=&quot;1174&quot; data-origin-height=&quot;810&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-end=&quot;2124&quot; data-start=&quot;2084&quot; data-ke-size=&quot;size16&quot;&gt;이건 SQL이 어떤 순서로 실행되는지를&lt;br /&gt;직접 단계별로 풀어쓴 노트다.&lt;/p&gt;
&lt;p data-end=&quot;2159&quot; data-start=&quot;2126&quot; data-ke-size=&quot;size16&quot;&gt;SQL은 작성 순서가 아니라 &amp;ldquo;실행 순서&amp;rdquo;가 훨씬 중요하다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;2237&quot; data-start=&quot;2161&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;2169&quot; data-start=&quot;2161&quot;&gt;FROM&lt;/li&gt;
&lt;li data-end=&quot;2179&quot; data-start=&quot;2170&quot;&gt;WHERE&lt;/li&gt;
&lt;li data-end=&quot;2192&quot; data-start=&quot;2180&quot;&gt;GROUP BY&lt;/li&gt;
&lt;li data-end=&quot;2203&quot; data-start=&quot;2193&quot;&gt;HAVING&lt;/li&gt;
&lt;li data-end=&quot;2214&quot; data-start=&quot;2204&quot;&gt;SELECT&lt;/li&gt;
&lt;li data-end=&quot;2227&quot; data-start=&quot;2215&quot;&gt;ORDER BY&lt;/li&gt;
&lt;li data-end=&quot;2237&quot; data-start=&quot;2228&quot;&gt;LIMIT&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-end=&quot;2266&quot; data-start=&quot;2239&quot; data-ke-size=&quot;size16&quot;&gt;이 흐름만 이해해도 기출의 절반은 설명 가능하다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 data-end=&quot;2297&quot; data-start=&quot;2273&quot; data-ke-size=&quot;size23&quot;&gt;4-3. DDL / DML 핵심 정리&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;SQL3.png&quot; data-origin-width=&quot;1211&quot; data-origin-height=&quot;811&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dXJJLx/dJMcajtQd9Q/JvhnLnJ8jdK3ibl2kFWAmk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dXJJLx/dJMcajtQd9Q/JvhnLnJ8jdK3ibl2kFWAmk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dXJJLx/dJMcajtQd9Q/JvhnLnJ8jdK3ibl2kFWAmk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdXJJLx%2FdJMcajtQd9Q%2FJvhnLnJ8jdK3ibl2kFWAmk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1211&quot; height=&quot;811&quot; data-filename=&quot;SQL3.png&quot; data-origin-width=&quot;1211&quot; data-origin-height=&quot;811&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-end=&quot;2382&quot; data-start=&quot;2339&quot; data-ke-size=&quot;size16&quot;&gt;시험 직전까지 보고 들어간 건 아마 이것.&lt;br /&gt;특히 외래키 제약 조건 옵션&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 data-end=&quot;2477&quot; data-start=&quot;2452&quot; data-ke-size=&quot;size26&quot;&gt;5. 시험 당일 &amp;ndash; 피곤과 긴장의 콜라보&lt;/h2&gt;
&lt;p data-end=&quot;2569&quot; data-start=&quot;2479&quot; data-ke-size=&quot;size16&quot;&gt;시험장에 가는 길, 솔직히 너무 졸렸다.&lt;br /&gt;전날까지 우테코 오픈 미션 리팩터링하고&lt;/p&gt;
&lt;p data-end=&quot;2569&quot; data-start=&quot;2479&quot; data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;NCP 프로젝트 문서 만들고&lt;br /&gt;팀플 발표 준비까지 하느라 거의 못 잤다.&lt;/p&gt;
&lt;p data-end=&quot;2569&quot; data-start=&quot;2479&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;2625&quot; data-start=&quot;2571&quot; data-ke-size=&quot;size16&quot;&gt;그런데도 이상하게 마음은 차분했다.&lt;br /&gt;&amp;ldquo;그래도 내가 할 건 다 했다&amp;rdquo;는 느낌이 있었기 때문이다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;293&quot; data-origin-height=&quot;517&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/BJfAf/dJMcacn0bmk/E9qjedeXBtHfeDnW4NJD71/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/BJfAf/dJMcacn0bmk/E9qjedeXBtHfeDnW4NJD71/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/BJfAf/dJMcacn0bmk/E9qjedeXBtHfeDnW4NJD71/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FBJfAf%2FdJMcacn0bmk%2FE9qjedeXBtHfeDnW4NJD71%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;293&quot; height=&quot;517&quot; data-origin-width=&quot;293&quot; data-origin-height=&quot;517&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-end=&quot;2661&quot; data-start=&quot;2652&quot; data-ke-size=&quot;size16&quot;&gt;시험장 내 자리에 앉아서 최종 복습..ㅋㅋ&lt;/p&gt;
&lt;p data-end=&quot;2661&quot; data-start=&quot;2652&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;2661&quot; data-start=&quot;2652&quot; data-ke-size=&quot;size16&quot;&gt;문제 난이도는&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;2723&quot; data-start=&quot;2662&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;2675&quot; data-start=&quot;2662&quot;&gt;개념 파트: 무난&lt;/li&gt;
&lt;li data-end=&quot;2699&quot; data-start=&quot;2676&quot;&gt;SQL 파트: 좀 꼬아낸 문제 존재&lt;/li&gt;
&lt;li data-end=&quot;2723&quot; data-start=&quot;2700&quot;&gt;전체 난이도: 예상보다 살짝 어려움&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-end=&quot;2742&quot; data-start=&quot;2725&quot; data-ke-size=&quot;size16&quot;&gt;그래도 시간은 넉넉하게 풀었다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 data-end=&quot;2769&quot; data-start=&quot;2749&quot; data-ke-size=&quot;size26&quot;&gt;6. 결과는? (아직 발표 전)&lt;/h2&gt;
&lt;p data-end=&quot;2821&quot; data-start=&quot;2771&quot; data-ke-size=&quot;size16&quot;&gt;SQLD 59회는 아직 발표가 나지 않았다.&lt;br /&gt;그래서 긴장되는 마음으로 기다리는 중이다.&lt;/p&gt;
&lt;p data-end=&quot;2860&quot; data-start=&quot;2823&quot; data-ke-size=&quot;size16&quot;&gt;합격하면 좋겠지만,&lt;br /&gt;이 시험을 준비하면서 느낀 건 딱 하나였다.&lt;/p&gt;
&lt;blockquote data-end=&quot;2893&quot; data-start=&quot;2862&quot; data-ke-style=&quot;style2&quot;&gt;
&lt;p data-end=&quot;2893&quot; data-start=&quot;2864&quot; data-ke-size=&quot;size16&quot;&gt;&amp;ldquo;배운 만큼 성장한다는 걸 오랜만에 깊게 체감했다.&amp;rdquo;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-end=&quot;2967&quot; data-start=&quot;2895&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;2967&quot; data-start=&quot;2895&quot; data-ke-size=&quot;size16&quot;&gt;이 바쁜 일정 속에서도 책 붙잡고 문제 풀며&lt;br /&gt;모르는 개념 이해하려고 씨름했던 시간들 자체가&lt;br /&gt;이미 나에게 큰 자산이 되었다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 data-end=&quot;2993&quot; data-start=&quot;2974&quot; data-ke-size=&quot;size26&quot;&gt;7. 마무리 &amp;ndash; 다음 목표는?&lt;/h2&gt;
&lt;p data-end=&quot;3051&quot; data-start=&quot;2995&quot; data-ke-size=&quot;size16&quot;&gt;SQLD 준비하면서&lt;br /&gt;&amp;ldquo;아, 내가 점점 나아지고 있구나&amp;rdquo;라는 감각이 들었다.&lt;br /&gt;그게 너무 좋았다.&lt;/p&gt;
&lt;p data-end=&quot;3051&quot; data-start=&quot;2995&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;3132&quot; data-start=&quot;3053&quot; data-ke-size=&quot;size16&quot;&gt;다음 목표는 SQLP?&lt;br /&gt;아직은 생각 없다.&lt;/p&gt;
&lt;p data-end=&quot;3132&quot; data-start=&quot;3053&quot; data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;지금은 우테코 프리코스 완주부터&amp;hellip;&lt;/p&gt;
&lt;p data-end=&quot;3132&quot; data-start=&quot;3053&quot; data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;그리고 NCP 아키텍처 프로젝트 발표까지 해야 하니까.&lt;/p&gt;
&lt;p data-end=&quot;3132&quot; data-start=&quot;3053&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;3180&quot; data-start=&quot;3134&quot; data-ke-size=&quot;size16&quot;&gt;그래도,&lt;br /&gt;한 달 동안 SQL과 정말 친해졌다!!&lt;/p&gt;
&lt;p data-end=&quot;3180&quot; data-start=&quot;3134&quot; data-ke-size=&quot;size16&quot;&gt;읽기 두려운 SQL문은 이제 거의 별로 없는...&lt;/p&gt;
&lt;p data-end=&quot;3180&quot; data-start=&quot;3134&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;3221&quot; data-start=&quot;3182&quot; data-ke-size=&quot;size16&quot;&gt;읽어줘서 고맙습니다 :)&lt;/p&gt;
&lt;p data-end=&quot;3221&quot; data-start=&quot;3182&quot; data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;시험 결과 나오면 바로 후기 업데이트할게요~&lt;/p&gt;</description>
      <category>자격증</category>
      <author>hwangsoojin</author>
      <guid isPermaLink="true">https://hwangsoojin.tistory.com/40</guid>
      <comments>https://hwangsoojin.tistory.com/40#entry40comment</comments>
      <pubDate>Mon, 24 Nov 2025 23:26:11 +0900</pubDate>
    </item>
    <item>
      <title>JSON DTO Converter (4) - Exception 분석</title>
      <link>https://hwangsoojin.tistory.com/39</link>
      <description>&lt;h3 data-end=&quot;183&quot; data-start=&quot;126&quot; data-ke-size=&quot;size23&quot;&gt;UserException &amp;middot; InternalException &amp;middot; main() 예외 처리 흐름까지&lt;/h3&gt;
&lt;p data-end=&quot;252&quot; data-start=&quot;185&quot; data-ke-size=&quot;size16&quot;&gt;이 글은 JSON DTO Converter 프로젝트의 예외 처리 계층(exception 패키지)를 정리한 글이다.&lt;/p&gt;
&lt;p data-end=&quot;261&quot; data-start=&quot;254&quot; data-ke-size=&quot;size16&quot;&gt;앞선 글들에서&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;375&quot; data-start=&quot;263&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;293&quot; data-start=&quot;263&quot;&gt;CLI 계층: 잘못된 옵션/경로를 어떻게 막는지&lt;/li&gt;
&lt;li data-end=&quot;332&quot; data-start=&quot;294&quot;&gt;JSON 분석 계층: JSON 구조를 어떻게 스키마로 바꾸는지&lt;/li&gt;
&lt;li data-end=&quot;375&quot; data-start=&quot;333&quot;&gt;Generator 계층: 설계도를 실제&amp;nbsp;&lt;span style=&quot;background-color: #dddddd;&quot;&gt;&lt;b&gt; .java&amp;nbsp;&lt;/b&gt;&lt;/span&gt; 파일로 만드는지&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-end=&quot;384&quot; data-start=&quot;377&quot; data-ke-size=&quot;size16&quot;&gt;를 다뤘다면,&lt;/p&gt;
&lt;p data-end=&quot;449&quot; data-start=&quot;386&quot; data-ke-size=&quot;size16&quot;&gt;이 글은 &quot;그 과정에서 발생하는 오류를 어떻게 분류하고, 사용자에게 어떻게 보여줄 것인가&quot;에 초점을 둔다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h1 style=&quot;color: #000000; text-align: start;&quot; data-start=&quot;858&quot; data-end=&quot;879&quot;&gt;0. 왜 예외 설계가 중요한가?&lt;/h1&gt;
&lt;h2 data-end=&quot;476&quot; data-start=&quot;456&quot; data-ke-size=&quot;size26&quot;&gt;&amp;nbsp;&lt;/h2&gt;
&lt;p data-end=&quot;517&quot; data-start=&quot;478&quot; data-ke-size=&quot;size16&quot;&gt;CLI 도구 특성상, 예외 설계는 곧 &lt;b&gt;사용자 경험(UX)&lt;/b&gt; 이다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;617&quot; data-start=&quot;519&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;566&quot; data-start=&quot;519&quot;&gt;잘못된 인자, 없는 파일, 깨진 JSON 같은 &quot;사용자가 고칠 수 있는 오류&quot;&lt;/li&gt;
&lt;li data-end=&quot;617&quot; data-start=&quot;567&quot;&gt;버그, 구현 누락, 설계 상 발생하면 안 되는 상태 같은 &quot;개발자가 고쳐야 하는 오류&quot;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-end=&quot;638&quot; data-start=&quot;619&quot; 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-end=&quot;699&quot; data-start=&quot;640&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;669&quot; data-start=&quot;640&quot;&gt;사용자는 &quot;내가 뭘 잘못한 건지&quot; 알 수 없고&lt;/li&gt;
&lt;li data-end=&quot;699&quot; data-start=&quot;670&quot;&gt;내부 버그가 발생했을 때도 조용히 묻힐 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-end=&quot;729&quot; data-start=&quot;701&quot; data-ke-size=&quot;size16&quot;&gt;그래서 이 프로젝트에서는 예외를 두 축으로 나눴다.&lt;/p&gt;
&lt;blockquote data-end=&quot;813&quot; data-start=&quot;731&quot; data-ke-style=&quot;style2&quot;&gt;
&lt;p data-end=&quot;813&quot; data-start=&quot;733&quot; data-ke-size=&quot;size16&quot;&gt;1. 사용자가 고칠 수 있는 오류 &amp;rarr;&amp;nbsp;&lt;b&gt;&lt;span style=&quot;background-color: #dddddd;&quot;&gt; UserException&amp;nbsp;&lt;/span&gt;&lt;/b&gt;&lt;br /&gt;2. 코드/설계 버그 &amp;rarr;&amp;nbsp;&lt;span style=&quot;background-color: #dddddd;&quot;&gt;&lt;b&gt; InternalException&amp;nbsp;&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-end=&quot;851&quot; data-start=&quot;815&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;851&quot; data-start=&quot;815&quot; data-ke-size=&quot;size16&quot;&gt;이 둘을 분리해두면,&amp;nbsp;&lt;b&gt;&lt;span style=&quot;background-color: #dddddd;&quot;&gt; main()&amp;nbsp;&lt;/span&gt;&lt;/b&gt; 에서 처리 전략도 명확해진다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h1 data-end=&quot;879&quot; data-start=&quot;858&quot;&gt;1. exception 패키지 구조&lt;/h1&gt;
&lt;p data-end=&quot;928&quot; data-start=&quot;881&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span style=&quot;background-color: #dddddd;&quot;&gt;&amp;nbsp;org.example.exception&amp;nbsp;&lt;/span&gt;&lt;/b&gt; 패키지는 단 두 개의 클래스로 이루어진다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre id=&quot;code_1763975701867&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;org.example.exception
 ├─ UserException.java
 └─ InternalException.java&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;1052&quot; data-start=&quot;1015&quot; data-ke-size=&quot;size16&quot;&gt;심플하지만, 이 두 클래스가 예외 설계 방향을 완전히 갈라 놓는다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h1 data-end=&quot;1098&quot; data-start=&quot;1059&quot;&gt;2. UserException &amp;mdash; &quot;사용자가 수정할 수 있는 오류&quot;&lt;/h1&gt;
&lt;h2 data-end=&quot;1110&quot; data-start=&quot;1100&quot; data-ke-size=&quot;size26&quot;&gt;2-1. 개념&lt;/h2&gt;
&lt;p data-end=&quot;1136&quot; data-start=&quot;1112&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;UserException&lt;/b&gt;은 말 그대로&lt;/p&gt;
&lt;blockquote data-end=&quot;1166&quot; data-start=&quot;1138&quot; data-ke-style=&quot;style2&quot;&gt;
&lt;p data-end=&quot;1166&quot; data-start=&quot;1140&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&lt;b&gt;&quot;사용자가 입력을 바꾸면 해결할 수 있는 오류&quot;&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-end=&quot;1175&quot; data-start=&quot;1168&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;1175&quot; data-start=&quot;1168&quot; data-ke-size=&quot;size16&quot;&gt;를 나타낸다.&lt;/p&gt;
&lt;p data-end=&quot;1183&quot; data-start=&quot;1177&quot; data-ke-size=&quot;size16&quot;&gt;예를 들면:&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;1318&quot; data-start=&quot;1185&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;1218&quot; data-start=&quot;1185&quot;&gt;&lt;b&gt;&lt;span style=&quot;background-color: #dddddd;&quot;&gt;&amp;nbsp;--input&amp;nbsp;&lt;/span&gt;&lt;/b&gt; 에 존재하지 않는 파일 경로를 넣었다&lt;/li&gt;
&lt;li data-end=&quot;1244&quot; data-start=&quot;1219&quot;&gt;JSON 문법이 잘못된 파일을 입력했다&lt;/li&gt;
&lt;li data-end=&quot;1290&quot; data-start=&quot;1245&quot;&gt;&lt;b&gt;&lt;span style=&quot;background-color: #dddddd;&quot;&gt;&amp;nbsp;--inner-classes maybe&amp;nbsp;&lt;/span&gt;&lt;/b&gt; 처럼 허용되지 않는 값을 넣었다&lt;/li&gt;
&lt;li data-end=&quot;1318&quot; data-start=&quot;1291&quot;&gt;&lt;span style=&quot;background-color: #dddddd;&quot;&gt;&lt;b&gt;&amp;nbsp;--out&amp;nbsp;&lt;/b&gt;&lt;/span&gt; 디렉터리가 쓰기 권한이 없다&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-end=&quot;1360&quot; data-start=&quot;1320&quot; data-ke-size=&quot;size16&quot;&gt;이런 것은 &lt;b&gt;사용자가 명령을 고쳐서 다시 실행하면 해결되는 문제&lt;/b&gt;다.&lt;/p&gt;
&lt;p data-end=&quot;1366&quot; data-start=&quot;1362&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;1366&quot; data-start=&quot;1362&quot; data-ke-size=&quot;size16&quot;&gt;따라서:&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;1431&quot; data-start=&quot;1368&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;1399&quot; data-start=&quot;1368&quot;&gt;내부 스택 트레이스를 장황하게 보여줄 필요가 없고&lt;/li&gt;
&lt;li data-end=&quot;1431&quot; data-start=&quot;1400&quot;&gt;&lt;b&gt;짧고 맥락 있는 오류 메시지&lt;/b&gt;만 써도 충분하다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-end=&quot;1440&quot; data-start=&quot;1433&quot; data-ke-size=&quot;size16&quot;&gt;예시 메시지:&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre id=&quot;code_1763975826045&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;[ERROR] 입력 JSON 파일을 찾을 수 없습니다: input/not-exists.json
[ERROR] --inner-classes 옵션은 true/false만 허용합니다: maybe
[ERROR] JSON 파싱에 실패했습니다. 파일 형식을 다시 확인해주세요: broken.json&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 data-end=&quot;1634&quot; data-start=&quot;1621&quot; data-ke-size=&quot;size26&quot;&gt;2-2. 구현 형태&lt;/h2&gt;
&lt;p data-end=&quot;1680&quot; data-start=&quot;1636&quot; data-ke-size=&quot;size16&quot;&gt;UserException은 보통 이렇게 구현한다(실제 구현과 개념적으로 동일):&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre id=&quot;code_1763975861493&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;package org.example.exception;

public class UserException extends RuntimeException {

    public UserException(String message) {
        super(message);
    }

    public UserException(String message, Throwable cause) {
        super(message, cause);
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;1958&quot; data-start=&quot;1955&quot; data-ke-size=&quot;size16&quot;&gt;특징:&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;2096&quot; data-start=&quot;1960&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;2017&quot; data-start=&quot;1960&quot;&gt;&lt;span style=&quot;background-color: #dddddd;&quot;&gt;&lt;b&gt;&amp;nbsp;RuntimeException&amp;nbsp;&lt;/b&gt;&lt;/span&gt; 을 상속 &amp;rarr; 체크 예외처럼 매번 throws 선언 강제하지 않음&lt;/li&gt;
&lt;li data-end=&quot;2059&quot; data-start=&quot;2018&quot;&gt;message는 &lt;b&gt;사용자에게 그대로 보여줄 전용 메시지&lt;/b&gt;로 사용&lt;/li&gt;
&lt;li data-end=&quot;2096&quot; data-start=&quot;2060&quot;&gt;필요하면 cause를 함께 넘겨 내부 디버깅에 활용할 수 있음&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 data-end=&quot;2135&quot; data-start=&quot;2103&quot; data-ke-size=&quot;size26&quot;&gt;2-3. 어디서 UserException을 던지는가?&lt;/h2&gt;
&lt;p data-end=&quot;2199&quot; data-start=&quot;2137&quot; data-ke-size=&quot;size16&quot;&gt;프로젝트 전반에서 &quot;사용자가 잘못 입력하면 발생할 만한 곳&quot;은 전부 UserException의 후보이다.&lt;/p&gt;
&lt;p data-end=&quot;2207&quot; data-start=&quot;2201&quot; data-ke-size=&quot;size16&quot;&gt;대표적으로:&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;2449&quot; data-start=&quot;2209&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;2304&quot; data-start=&quot;2209&quot;&gt;&lt;span style=&quot;background-color: #dddddd;&quot;&gt;&lt;b&gt;&amp;nbsp;ArgumentParser&amp;nbsp;&lt;/b&gt;&lt;/span&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;2304&quot; data-start=&quot;2232&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;2244&quot; data-start=&quot;2232&quot;&gt;필수 옵션 누락&lt;/li&gt;
&lt;li data-end=&quot;2259&quot; data-start=&quot;2247&quot;&gt;옵션 이름 오타&lt;/li&gt;
&lt;li data-end=&quot;2304&quot; data-start=&quot;2262&quot;&gt;&lt;span style=&quot;background-color: #dddddd;&quot;&gt;&lt;b&gt;&amp;nbsp;--inner-classes&amp;nbsp;&lt;/b&gt;&lt;/span&gt; 값이 true/false가 아닌 경우&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li data-end=&quot;2399&quot; data-start=&quot;2305&quot;&gt;&lt;span style=&quot;background-color: #dddddd;&quot;&gt;&lt;b&gt;&amp;nbsp;JsonValidator&amp;nbsp;&lt;/b&gt;&lt;/span&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;2399&quot; data-start=&quot;2327&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;2340&quot; data-start=&quot;2327&quot;&gt;입력 파일이 없음&lt;/li&gt;
&lt;li data-end=&quot;2359&quot; data-start=&quot;2343&quot;&gt;디렉터리를 파일로 건넴&lt;/li&gt;
&lt;li data-end=&quot;2376&quot; data-start=&quot;2362&quot;&gt;JSON 파싱 실패&lt;/li&gt;
&lt;li data-end=&quot;2399&quot; data-start=&quot;2379&quot;&gt;루트가 객체/배열이 아닌 경우&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li data-end=&quot;2449&quot; data-start=&quot;2400&quot;&gt;&lt;span style=&quot;background-color: #dddddd;&quot;&gt;&lt;b&gt;&amp;nbsp;FileWriter&amp;nbsp;&lt;/b&gt;&lt;/span&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;2449&quot; data-start=&quot;2419&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;2436&quot; data-start=&quot;2419&quot;&gt;출력 디렉터리 권한 없음&lt;/li&gt;
&lt;li data-end=&quot;2449&quot; data-start=&quot;2439&quot;&gt;파일 쓰기 실패&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-end=&quot;2491&quot; data-start=&quot;2451&quot; data-ke-size=&quot;size16&quot;&gt;예를 들어 FileWriter에서는 이런 식으로 예외를 래핑할 수 있다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre id=&quot;code_1763975950640&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public void write(Path outDir, String className, String content) {
    Path file = outDir.resolve(className + &quot;.java&quot;);
    try {
        Files.writeString(file, content, StandardCharsets.UTF_8);
    } catch (IOException e) {
        throw new UserException(
            &quot;[ERROR] Java 파일을 저장하는 중 오류가 발생했습니다: &quot; + file, e
        );
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;2927&quot; data-start=&quot;2845&quot; data-ke-size=&quot;size16&quot;&gt;여기서 IOException은 &lt;b&gt;사용자가 경로/권한을 바꾸면 해결될 수도 있는 문제&lt;/b&gt;이므로&lt;br /&gt;UserException으로 감싸는 쪽에 가깝다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h1 data-end=&quot;2985&quot; data-start=&quot;2934&quot;&gt;3. InternalException &amp;mdash; &quot;이건 버그다, 사용자가 아니라 개발자의 책임&quot;&lt;/h1&gt;
&lt;h2 data-end=&quot;2997&quot; data-start=&quot;2987&quot; data-ke-size=&quot;size26&quot;&gt;3-1. 개념&lt;/h2&gt;
&lt;p data-end=&quot;3055&quot; data-start=&quot;2999&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;InternalException&lt;/b&gt;은 설계 상 &quot;여기까지 오면 안 된다&quot; 수준의 문제에 사용한다.&lt;/p&gt;
&lt;p data-end=&quot;3063&quot; data-start=&quot;3057&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;3063&quot; data-start=&quot;3057&quot; data-ke-size=&quot;size16&quot;&gt;예를 들면:&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;3235&quot; data-start=&quot;3065&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;3107&quot; data-start=&quot;3065&quot;&gt;&lt;span style=&quot;background-color: #dddddd;&quot;&gt;&lt;b&gt;&amp;nbsp;SchemaPrimitive&amp;nbsp;&lt;/b&gt;&lt;/span&gt; 에 정의되지 않은 PKind가 들어왔다&lt;/li&gt;
&lt;li data-end=&quot;3144&quot; data-start=&quot;3108&quot;&gt;&lt;b&gt;&lt;span style=&quot;background-color: #dddddd;&quot;&gt;&amp;nbsp;SchemaUnion&amp;nbsp;&lt;/span&gt;&lt;/b&gt; 에서 처리해야 할 케이스를 빼먹었다&lt;/li&gt;
&lt;li data-end=&quot;3191&quot; data-start=&quot;3145&quot;&gt;TypeInferencer가 지원하지 않는 SchemaNode 타입을 만났다&lt;/li&gt;
&lt;li data-end=&quot;3235&quot; data-start=&quot;3192&quot;&gt;ModelGraph에서 null이 되어선 안 되는 값이 null이 됐다&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-end=&quot;3286&quot; data-start=&quot;3237&quot; data-ke-size=&quot;size16&quot;&gt;이건 사용자가 뭘 잘못한 게 아니라,&lt;br /&gt;&lt;b&gt;내가(개발자)가 코드를 잘못 짠 것&lt;/b&gt;이다.&lt;/p&gt;
&lt;p data-end=&quot;3292&quot; data-start=&quot;3288&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;3292&quot; data-start=&quot;3288&quot; data-ke-size=&quot;size16&quot;&gt;따라서:&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;3368&quot; data-start=&quot;3294&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;3324&quot; data-start=&quot;3294&quot;&gt;사용자에게는 &quot;내부 오류입니다&quot;라고 알려야 하고&lt;/li&gt;
&lt;li data-end=&quot;3368&quot; data-start=&quot;3325&quot;&gt;동시에 &lt;b&gt;스택 트레이스도 출력해서 개발자가 디버깅할 수 있어야&lt;/b&gt; 한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 data-end=&quot;3388&quot; data-start=&quot;3375&quot; data-ke-size=&quot;size26&quot;&gt;3-2. 구현 형태&lt;/h2&gt;
&lt;p data-end=&quot;3443&quot; data-start=&quot;3390&quot; data-ke-size=&quot;size16&quot;&gt;InternalException도 UserException과 비슷하게 구현하되, 의미만 다르다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre id=&quot;code_1763976030128&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;package org.example.exception;

public class InternalException extends RuntimeException {

    public InternalException(String message) {
        super(message);
    }

    public InternalException(String message, Throwable cause) {
        super(message, cause);
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;3742&quot; data-start=&quot;3730&quot; data-ke-size=&quot;size16&quot;&gt;핵심은 의미적 차이다:&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;3810&quot; data-start=&quot;3744&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;3772&quot; data-start=&quot;3744&quot;&gt;UserException: 사용자 입력 잘못&lt;/li&gt;
&lt;li data-end=&quot;3810&quot; data-start=&quot;3773&quot;&gt;InternalException: 프로그램 내부 상태/설계 오류&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 data-end=&quot;3853&quot; data-start=&quot;3817&quot; data-ke-size=&quot;size26&quot;&gt;3-3. 어디서 InternalException을 던지는가?&lt;/h2&gt;
&lt;p data-end=&quot;3895&quot; data-start=&quot;3855&quot; data-ke-size=&quot;size16&quot;&gt;예를 들어 TypeInferencer가 SchemaNode를 처리할 때:&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre id=&quot;code_1763976044804&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;TypeRef infer(SchemaNode node) {
    if (node instanceof SchemaPrimitive primitive) {
        // ...
    } else if (node instanceof SchemaObject object) {
        // ...
    } else if (node instanceof SchemaArray array) {
        // ...
    } else if (node instanceof SchemaUnion union) {
        // ...
    } else {
        // 여기 오면 안 된다 = 새로운 서브타입이 추가됐는데 처리 안 한 것
        throw new InternalException(
            &quot;[INTERNAL] 지원하지 않는 SchemaNode 타입입니다: &quot; + node.getClass()
        );
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;4472&quot; data-start=&quot;4402&quot; data-ke-size=&quot;size16&quot;&gt;이런 예외는 &lt;b&gt;사용자에게 책임을 돌릴 수 없다&lt;/b&gt;.&lt;br /&gt;&quot;새로운 타입이 추가됐는데, 내가 처리 코드를 안 쓴 것&quot;일 뿐이다.&lt;/p&gt;
&lt;p data-end=&quot;4502&quot; data-start=&quot;4474&quot; data-ke-size=&quot;size16&quot;&gt;이렇게 InternalException을 활용하면:&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;4574&quot; data-start=&quot;4504&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;4533&quot; data-start=&quot;4504&quot;&gt;개발 중에 놓친 케이스를 빨리 발견할 수 있고&lt;/li&gt;
&lt;li data-end=&quot;4574&quot; data-start=&quot;4534&quot;&gt;예외 메시지를 보고 &quot;어디 부분이 설계 미스인지&quot; 바로 알 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h1 data-end=&quot;4604&quot; data-start=&quot;4581&quot;&gt;4. main()에서의 예외 처리 전략&lt;/h1&gt;
&lt;p data-end=&quot;4650&quot; data-start=&quot;4606&quot; data-ke-size=&quot;size16&quot;&gt;모든 예외 설계의 끝은&amp;nbsp;&lt;span style=&quot;background-color: #dddddd;&quot;&gt; &lt;/span&gt;&lt;b&gt;&lt;span style=&quot;background-color: #dddddd;&quot;&gt;main()&amp;nbsp;&lt;/span&gt; 에서 어떻게 처리하느냐&lt;/b&gt;로 귀결된다.&lt;/p&gt;
&lt;p data-end=&quot;4682&quot; data-start=&quot;4652&quot; data-ke-size=&quot;size16&quot;&gt;이 프로젝트에서는 대략 이런 구조를 가진다(의사코드):&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;div&gt;
&lt;pre id=&quot;code_1763976139677&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public class Main {

    public static void main(String[] args) {
        try {
            // 1. CLI 인자 파싱
            ArgumentParser parser = new ArgumentParser();
            ParsedArguments parsed = parser.parse(args);

            // 2. JSON 파일 로드 및 검증
            JsonValidator.Result result =
                JsonValidator.validateAndLoad(parsed.getInputPath());

            // 3. JSON 구조 분석 &amp;rarr; SchemaNode
            JsonAnalyzer analyzer = new JsonAnalyzer();
            SchemaNode rootSchema = analyzer.analyze(result.root());

            // 4. 타입 추론
            TypeInferencer inferencer = new TypeInferencer();
            // 타입 추론 + ModelGraph 구성
            ModelGraph graph = ModelGraph.from(rootSchema, parsed, inferencer);

            // 5. ClassSpec 리스트를 얻고, 각 클래스 생성
            ClassGenerator classGenerator = new ClassGenerator();
            CodeFormatter formatter = new CodeFormatter();
            FileWriter fileWriter = new FileWriter();

            for (ClassSpec spec : graph.getAllClasses()) {
                String raw = classGenerator.generate(spec);
                String formatted = formatter.format(raw);
                fileWriter.write(parsed.getOutDirPath(), spec.className(), formatted);
            }

        } catch (UserException e) {
            System.err.println(e.getMessage());
            System.exit(1);
        } catch (InternalException e) {
            System.err.println(&quot;[INTERNAL ERROR] 예기치 못한 오류가 발생했습니다.&quot;);
            e.printStackTrace();
            System.exit(2);
        }
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 data-end=&quot;6267&quot; data-start=&quot;6247&quot; data-ke-size=&quot;size23&quot;&gt;여기서 중요한 포인트 3가지:&lt;/h3&gt;
&lt;h2 data-end=&quot;6318&quot; data-start=&quot;6274&quot; data-ke-size=&quot;size26&quot;&gt;4-1. UserException &amp;rarr; 메시지만 출력, exit code 1&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;6411&quot; data-start=&quot;6320&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;6350&quot; data-start=&quot;6320&quot;&gt;사용자 잘못이므로 message만 보여주면 충분&lt;/li&gt;
&lt;li data-end=&quot;6411&quot; data-start=&quot;6351&quot;&gt;exit code 1은 &quot;정상은 아니지만, 사용자가 입력을 고쳐서 다시 시도할 수 있음&quot; 정도의 의미&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-end=&quot;6419&quot; data-start=&quot;6413&quot; data-ke-size=&quot;size16&quot;&gt;예를 들어,&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre id=&quot;code_1763976170873&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;[ERROR] 입력 JSON 파일을 찾을 수 없습니다: input/not-exists.json&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;6525&quot; data-start=&quot;6487&quot; data-ke-size=&quot;size16&quot;&gt;이 출력만 보고도 사용자는 &quot;아, 경로가 잘못됐구나&quot;를 알 수 있다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 data-end=&quot;6588&quot; data-start=&quot;6532&quot; data-ke-size=&quot;size26&quot;&gt;4-2. InternalException &amp;rarr; 안내 문구 + 스택 트레이스, exit code 2&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;6714&quot; data-start=&quot;6590&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;6618&quot; data-start=&quot;6590&quot;&gt;사용자에게는 &quot;내부 오류입니다&quot; 정도만 안내&lt;/li&gt;
&lt;li data-end=&quot;6680&quot; data-start=&quot;6619&quot;&gt;하지만&amp;nbsp;&lt;b&gt;&lt;span style=&quot;background-color: #dddddd;&quot;&gt; e.printStackTrace()&amp;nbsp;&lt;/span&gt;&lt;/b&gt; 를 통해 개발자 입장에서는 &lt;b&gt;디버깅 정보&lt;/b&gt;를 확인 가능&lt;/li&gt;
&lt;li data-end=&quot;6714&quot; data-start=&quot;6681&quot;&gt;exit code 2는 &amp;ldquo;내부 버그&amp;rdquo;라는 의미로 구분&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-end=&quot;6724&quot; data-start=&quot;6716&quot; data-ke-size=&quot;size16&quot;&gt;콘솔 출력 예:&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre id=&quot;code_1763976198635&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;[INTERNAL ERROR] 예기치 못한 오류가 발생했습니다.
org.example.exception.InternalException: [INTERNAL] 지원하지 않는 SchemaNode 타입입니다: class org.example.json.SchemaSomethingNew
    at org.example.json.TypeInferencer.infer(TypeInferencer.java:85)
    ...&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;6979&quot; data-start=&quot;6972&quot; data-ke-size=&quot;size16&quot;&gt;이렇게 하면:&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;7053&quot; data-start=&quot;6981&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;7015&quot; data-start=&quot;6981&quot;&gt;사용자는 &quot;내가 뭘 잘못한 건 아니구나&quot;를 알 수 있고&lt;/li&gt;
&lt;li data-end=&quot;7053&quot; data-start=&quot;7016&quot;&gt;나는 스택 트레이스를 보고 어디를 고쳐야 할지 파악할 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 data-end=&quot;7098&quot; data-start=&quot;7060&quot; data-ke-size=&quot;size26&quot;&gt;4-3. 왜 Exception을 전부 main()까지 올렸는가?&lt;/h2&gt;
&lt;p data-end=&quot;7135&quot; data-start=&quot;7100&quot; data-ke-size=&quot;size16&quot;&gt;중간 계층에서 예외를 잡고 &lt;b&gt;silent fail&lt;/b&gt;을 하면:&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;7187&quot; data-start=&quot;7137&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;7149&quot; data-start=&quot;7137&quot;&gt;어디서 터졌는지&lt;/li&gt;
&lt;li data-end=&quot;7165&quot; data-start=&quot;7150&quot;&gt;무슨 종류의 문제인지&lt;/li&gt;
&lt;li data-end=&quot;7187&quot; data-start=&quot;7166&quot;&gt;사용자에게 뭐라고 알려야 하는지&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-end=&quot;7198&quot; data-start=&quot;7189&quot; data-ke-size=&quot;size16&quot;&gt;를 놓치기 쉽다.&lt;/p&gt;
&lt;p data-end=&quot;7198&quot; data-start=&quot;7189&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;7219&quot; data-start=&quot;7200&quot; data-ke-size=&quot;size16&quot;&gt;그래서 설계 방향을 이렇게 잡았다:&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;7306&quot; data-start=&quot;7221&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;7265&quot; data-start=&quot;7221&quot;&gt;각 계층에서 &quot;뜻이 있는 예외 타입(User/Internal)&quot;을 던진다&lt;/li&gt;
&lt;li data-end=&quot;7306&quot; data-start=&quot;7266&quot;&gt;최종적으로 main()에서 &lt;b&gt;종류에 따라 딱 두 갈래로 처리&lt;/b&gt;한다&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-end=&quot;7314&quot; data-start=&quot;7308&quot; data-ke-size=&quot;size16&quot;&gt;이 덕분에:&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;7413&quot; data-start=&quot;7316&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;7337&quot; data-start=&quot;7316&quot;&gt;예외 흐름이 단순하고 예측 가능&lt;/li&gt;
&lt;li data-end=&quot;7365&quot; data-start=&quot;7338&quot;&gt;exit code에 의미를 부여할 수 있음&lt;/li&gt;
&lt;li data-end=&quot;7413&quot; data-start=&quot;7366&quot;&gt;나중에 스크립트/CI에서 exit code를 기준으로 실패 종류를 구분하기도 쉬움&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h1 data-end=&quot;7451&quot; data-start=&quot;7420&quot;&gt;5. 예외 설계와 9가지 예외 테스트 시나리오의 연결&lt;/h1&gt;
&lt;p data-end=&quot;7492&quot; data-start=&quot;7453&quot; data-ke-size=&quot;size16&quot;&gt;별도의 글(《실행 가이드 &amp;amp; 9가지 예외 테스트 시나리오》)에서 정리한&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-end=&quot;7741&quot; data-start=&quot;7494&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li data-end=&quot;7511&quot; data-start=&quot;7494&quot;&gt;&lt;b&gt;&lt;span style=&quot;background-color: #dddddd;&quot;&gt;&amp;nbsp;--input&amp;nbsp;&lt;/span&gt;&lt;/b&gt; 생략&lt;/li&gt;
&lt;li data-end=&quot;7527&quot; data-start=&quot;7512&quot;&gt;존재하지 않는 파일&lt;/li&gt;
&lt;li data-end=&quot;7548&quot; data-start=&quot;7528&quot;&gt;디렉터리/읽을 수 없는 파일&lt;/li&gt;
&lt;li data-end=&quot;7577&quot; data-start=&quot;7549&quot;&gt;깨진 JSON (&lt;span style=&quot;background-color: #dddddd;&quot;&gt;&lt;b&gt; broken.json &lt;/b&gt;&lt;/span&gt;)&lt;/li&gt;
&lt;li data-end=&quot;7616&quot; data-start=&quot;7578&quot;&gt;루트가 숫자인 JSON (&lt;span style=&quot;background-color: #dddddd;&quot;&gt;&lt;b&gt; root_number.json &lt;/b&gt;&lt;/span&gt;)&lt;/li&gt;
&lt;li data-end=&quot;7654&quot; data-start=&quot;7617&quot;&gt;루트가 배열인 JSON (&lt;b&gt;&lt;span style=&quot;background-color: #dddddd;&quot;&gt; root_array.json &lt;/span&gt;&lt;/b&gt;)&lt;/li&gt;
&lt;li data-end=&quot;7677&quot; data-start=&quot;7655&quot;&gt;&lt;span style=&quot;background-color: #dddddd;&quot;&gt;&lt;b&gt;&amp;nbsp;--root-class&amp;nbsp;&lt;/b&gt;&lt;/span&gt; 빠짐&lt;/li&gt;
&lt;li data-end=&quot;7697&quot; data-start=&quot;7678&quot;&gt;&lt;b&gt;&lt;span style=&quot;background-color: #dddddd;&quot;&gt;&amp;nbsp;--package&amp;nbsp;&lt;/span&gt;&lt;/b&gt; 빠짐&lt;/li&gt;
&lt;li data-end=&quot;7741&quot; data-start=&quot;7698&quot;&gt;&lt;span style=&quot;background-color: #dddddd;&quot;&gt;&lt;b&gt;&amp;nbsp;--inner-classes&amp;nbsp;&lt;/b&gt;&lt;/span&gt; 값이 true/false가 아닌 경우&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-end=&quot;7777&quot; data-start=&quot;7743&quot; data-ke-size=&quot;size16&quot;&gt;이 케이스들은 &lt;b&gt;모두 UserException 계열&lt;/b&gt;이다.&lt;/p&gt;
&lt;p data-end=&quot;7808&quot; data-start=&quot;7779&quot; data-ke-size=&quot;size16&quot;&gt;즉, 이 9가지 케이스를 명시적으로 테스트해봄으로써:&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;7890&quot; data-start=&quot;7810&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;7843&quot; data-start=&quot;7810&quot;&gt;&quot;사용자가 실수할 수 있는 부분&quot;을 대부분 커버했는지&lt;/li&gt;
&lt;li data-end=&quot;7890&quot; data-start=&quot;7844&quot;&gt;모든 경우에 &lt;b&gt;친절한 에러 메시지 + exit code 1&lt;/b&gt;로 떨어지는지&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-end=&quot;7902&quot; data-start=&quot;7892&quot; data-ke-size=&quot;size16&quot;&gt;를 검증한 셈이다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h1 data-end=&quot;7931&quot; data-start=&quot;7909&quot;&gt;6. 이 예외 설계가 주는 장점 정리&lt;/h1&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-end=&quot;8337&quot; data-start=&quot;7933&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li data-end=&quot;8019&quot; data-start=&quot;7933&quot;&gt;&lt;b&gt;책임 분리&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;8019&quot; data-start=&quot;7951&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;7993&quot; data-start=&quot;7951&quot;&gt;로직에서 예외를 던질 뿐, &quot;프린트/종료 방식&quot;은 main에서만 결정&lt;/li&gt;
&lt;li data-end=&quot;8019&quot; data-start=&quot;7997&quot;&gt;각 계층은 자기 도메인에만 집중 가능&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li data-end=&quot;8113&quot; data-start=&quot;8021&quot;&gt;&lt;b&gt;사용자 경험&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;8113&quot; data-start=&quot;8040&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;8057&quot; data-start=&quot;8040&quot;&gt;사용자가 잘못한 경우와,&lt;/li&gt;
&lt;li data-end=&quot;8113&quot; data-start=&quot;8061&quot;&gt;도구 내부의 버그를 명확히 구분&lt;br /&gt;&amp;rarr; &quot;내가 뭘 잘못했지?&quot;라는 불필요한 혼란 감소&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li data-end=&quot;8204&quot; data-start=&quot;8115&quot;&gt;&lt;b&gt;디버깅 용이성&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;8204&quot; data-start=&quot;8135&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;8172&quot; data-start=&quot;8135&quot;&gt;InternalException일 때만 스택 트레이스를 출력&lt;/li&gt;
&lt;li data-end=&quot;8204&quot; data-start=&quot;8176&quot;&gt;로그/콘솔에서 버그 지점을 빠르게 찾을 수 있음&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li data-end=&quot;8337&quot; data-start=&quot;8206&quot;&gt;&lt;b&gt;테스트하기 좋은 구조&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;8337&quot; data-start=&quot;8230&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;8261&quot; data-start=&quot;8230&quot;&gt;특정 입력 &amp;rarr; UserException 발생 예상&lt;/li&gt;
&lt;li data-end=&quot;8337&quot; data-start=&quot;8265&quot;&gt;잘못된 SchemaNode 타입 주입 &amp;rarr; InternalException 발생 예상&lt;br /&gt;같은 테스트 케이스를 만들기 쉬움&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h1 data-end=&quot;8376&quot; data-start=&quot;8344&quot;&gt;7. 마무리 &amp;mdash; 작은 도구에도 &quot;예외 설계&quot;는 중요하다&lt;/h1&gt;
&lt;p data-end=&quot;8456&quot; data-start=&quot;8378&quot; data-ke-size=&quot;size16&quot;&gt;JSON DTO Converter는 규모가 큰 애플리케이션은 아니지만,&lt;br /&gt;이 프로젝트를 통해 다음과 같은 것들을 직접 설계해볼 수 있었다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;8613&quot; data-start=&quot;8458&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;8496&quot; data-start=&quot;8458&quot;&gt;&lt;b&gt;사용자 오류와 내부 버그를 예외 계층에서 분리하는 전략&lt;/b&gt;&lt;/li&gt;
&lt;li data-end=&quot;8530&quot; data-start=&quot;8497&quot;&gt;&lt;b&gt;main()에서 예외를 한 번에 처리하는 정책&lt;/b&gt;&lt;/li&gt;
&lt;li data-end=&quot;8576&quot; data-start=&quot;8531&quot;&gt;&lt;b&gt;exit code에 의미를 부여하여 CLI 도구답게 동작시키는 방식&lt;/b&gt;&lt;/li&gt;
&lt;li data-end=&quot;8613&quot; data-start=&quot;8577&quot;&gt;&lt;b&gt;9가지 예외 시나리오를 직접 정의하고 테스트해보는 경험&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-end=&quot;8637&quot; data-start=&quot;8615&quot; data-ke-size=&quot;size16&quot;&gt;단순히 &quot;예외가 나면 터진다&quot;가 아니라,&lt;/p&gt;
&lt;blockquote data-end=&quot;8705&quot; data-start=&quot;8639&quot; data-ke-style=&quot;style2&quot;&gt;
&lt;p data-end=&quot;8705&quot; data-start=&quot;8641&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&quot;예외가 나더라도, 사용자는 정확히 이유를 알고,&lt;/b&gt;&lt;br /&gt;&lt;b&gt;나는 내부 버그를 빠르게 추적할 수 있도록 설계하는 것&quot;&lt;/b&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-end=&quot;8742&quot; data-start=&quot;8707&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;8742&quot; data-start=&quot;8707&quot; data-ke-size=&quot;size16&quot;&gt;이 이 프로젝트에서 예외를 설계할 때 가장 신경 썼던 부분이다.&lt;/p&gt;</description>
      <category>우아한테크코스 8기 precourse</category>
      <author>hwangsoojin</author>
      <guid isPermaLink="true">https://hwangsoojin.tistory.com/39</guid>
      <comments>https://hwangsoojin.tistory.com/39#entry39comment</comments>
      <pubDate>Mon, 24 Nov 2025 18:25:52 +0900</pubDate>
    </item>
    <item>
      <title>JSON DTO Converter (3) - Generator &amp;amp; 출력 분석</title>
      <link>https://hwangsoojin.tistory.com/38</link>
      <description>&lt;h3 data-end=&quot;235&quot; data-start=&quot;157&quot; data-ke-size=&quot;size23&quot;&gt;Template &amp;middot; ClassGenerator &amp;middot; CodeFormatter &amp;middot; FileWriter로 이어지는 &quot;코드 생성 파이프라인'&lt;/h3&gt;
&lt;p data-end=&quot;300&quot; data-start=&quot;237&quot; data-ke-size=&quot;size16&quot;&gt;이 글에서는 JSON DTO Converter의 마지막 단계인 &lt;b&gt;generator 계층&lt;/b&gt;을 집중적으로 다룬다.&lt;/p&gt;
&lt;p data-end=&quot;300&quot; data-start=&quot;237&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;338&quot; data-start=&quot;302&quot; data-ke-size=&quot;size16&quot;&gt;앞선 글(Overview / CLI / JSON 분석)에서 우리는&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;450&quot; data-start=&quot;340&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;357&quot; data-start=&quot;340&quot;&gt;CLI로부터 설정을 받아&lt;/li&gt;
&lt;li data-end=&quot;387&quot; data-start=&quot;358&quot;&gt;JSON을 검사하고(JsonValidator)&lt;/li&gt;
&lt;li data-end=&quot;450&quot; data-start=&quot;388&quot;&gt;JsonNode &amp;rarr; SchemaNode &amp;rarr; 타입 추론(TypeInferencer) &amp;rarr; ModelGraph까지&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-end=&quot;455&quot; data-start=&quot;452&quot; data-ke-size=&quot;size16&quot;&gt;왔다.&lt;/p&gt;
&lt;p data-end=&quot;472&quot; data-start=&quot;457&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;472&quot; data-start=&quot;457&quot; data-ke-size=&quot;size16&quot;&gt;이제 남은 일은 단 하나다!&lt;/p&gt;
&lt;blockquote data-end=&quot;526&quot; data-start=&quot;474&quot; data-ke-style=&quot;style2&quot;&gt;
&lt;p data-end=&quot;526&quot; data-start=&quot;476&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&quot;설계도(ModelGraph)를 실제 Java 코드(.java 파일)로 바꾸는 것&quot;&lt;/b&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-end=&quot;564&quot; data-start=&quot;528&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;564&quot; data-start=&quot;528&quot; data-ke-size=&quot;size16&quot;&gt;이 역할을 담당하는 것이 바로 &lt;b&gt;generator 패키지&lt;/b&gt;다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h1 style=&quot;color: #000000; text-align: start;&quot; data-start=&quot;1005&quot; data-end=&quot;1032&quot;&gt;0. generator 패키지의 구성&lt;/h1&gt;
&lt;h2 data-end=&quot;594&quot; data-start=&quot;571&quot; data-ke-size=&quot;size26&quot;&gt;&amp;nbsp;&lt;/h2&gt;
&lt;p data-end=&quot;641&quot; data-start=&quot;596&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span style=&quot;background-color: #dddddd;&quot;&gt;&amp;nbsp;org.example.generator&amp;nbsp;&lt;/span&gt;&lt;/b&gt; 에는 다음 네 개의 핵심 클래스가 있다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;809&quot; data-start=&quot;643&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;685&quot; data-start=&quot;643&quot;&gt;&lt;b&gt;&lt;span style=&quot;background-color: #dddddd;&quot;&gt;&amp;nbsp;Template&amp;nbsp;&lt;/span&gt;&lt;/b&gt; : 문자열 템플릿 엔진 (&amp;nbsp;&lt;b&gt;&lt;span style=&quot;background-color: #dddddd;&quot;&gt; ${name}&amp;nbsp;&lt;/span&gt;&lt;/b&gt; 치환 )&lt;/li&gt;
&lt;li data-end=&quot;735&quot; data-start=&quot;686&quot;&gt;&lt;b&gt;&lt;span style=&quot;background-color: #dddddd;&quot;&gt;&amp;nbsp;ClassGenerator&amp;nbsp;&lt;/span&gt;&lt;/b&gt; : ClassSpec &amp;rarr; Java 소스코드 문자열 생성&lt;/li&gt;
&lt;li data-end=&quot;769&quot; data-start=&quot;736&quot;&gt;&lt;span style=&quot;background-color: #dddddd;&quot;&gt;&lt;b&gt;&amp;nbsp;CodeFormatter&amp;nbsp;&lt;/b&gt;&lt;/span&gt; : 줄바꿈/공백/빈 줄 정리&lt;/li&gt;
&lt;li data-end=&quot;809&quot; data-start=&quot;770&quot;&gt;&lt;span style=&quot;background-color: #dddddd;&quot;&gt;&lt;b&gt;&amp;nbsp;FileWriter&amp;nbsp;&lt;/b&gt;&lt;/span&gt; : 최종 문자열을&amp;nbsp;&lt;b&gt;&lt;span style=&quot;background-color: #dddddd;&quot;&gt; .java&amp;nbsp;&lt;/span&gt;&lt;/b&gt; 파일로 저장&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-end=&quot;846&quot; data-start=&quot;811&quot; data-ke-size=&quot;size16&quot;&gt;이 네 클래스는 서로 결합되어 다음과 같은 파이프라인을 만든다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre id=&quot;code_1763974197921&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;[ClassSpec]
   &amp;darr; ClassGenerator
[raw Java source String]
   &amp;darr; CodeFormatter
[formatted Java source String]
   &amp;darr; FileWriter
[MyDto.java 파일]&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h1 data-end=&quot;1032&quot; data-start=&quot;1005&quot;&gt;1. Template &amp;mdash; 최소한의 템플릿 엔진&lt;/h1&gt;
&lt;h2 data-end=&quot;1062&quot; data-start=&quot;1034&quot; data-ke-size=&quot;size26&quot;&gt;1-1. 왜 직접 Template을 만들었나?&lt;/h2&gt;
&lt;p data-end=&quot;1114&quot; data-start=&quot;1064&quot; data-ke-size=&quot;size16&quot;&gt;외부 템플릿 엔진(예: Freemarker, Velocity 등)을 쓰는 것도 방법이지만,&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;1229&quot; data-start=&quot;1116&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;1160&quot; data-start=&quot;1116&quot;&gt;프리코스에서는 &lt;b&gt;외부 라이브러리 의존도를 낮추는 것&lt;/b&gt;이 좋다고 판단했고,&lt;/li&gt;
&lt;li data-end=&quot;1197&quot; data-start=&quot;1161&quot;&gt;&quot;템플릿 엔진이 내부에서 어떻게 동작하는지&quot; 이해하고 싶었고,&lt;/li&gt;
&lt;li data-end=&quot;1229&quot; data-start=&quot;1198&quot;&gt;필요한 기능은&amp;nbsp;&lt;b&gt;&lt;span style=&quot;background-color: #dddddd;&quot;&gt; ${var}&amp;nbsp;&lt;/span&gt;&lt;/b&gt; 치환 정도로 충분했다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-end=&quot;1261&quot; data-start=&quot;1231&quot; data-ke-size=&quot;size16&quot;&gt;그래서 아주 작은 범위의 템플릿 기능만 직접 구현했다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 data-end=&quot;1291&quot; data-start=&quot;1268&quot; data-ke-size=&quot;size26&quot;&gt;1-2. Template의 기본 구조&lt;/h2&gt;
&lt;p data-end=&quot;1342&quot; data-start=&quot;1293&quot; data-ke-size=&quot;size16&quot;&gt;Template은 내부에&amp;nbsp;&lt;span style=&quot;background-color: #dddddd;&quot;&gt;&lt;b&gt; pattern&amp;nbsp;&lt;/b&gt;&lt;/span&gt; 문자열을 하나 들고 있는 &lt;b&gt;불변 객체&lt;/b&gt;다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre id=&quot;code_1763974254471&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public class Template {

    private static final char DOLLAR = '$';
    private static final char OPEN_BRACE = '{';
    private static final char CLOSE_BRACE = '}';

    private final String pattern;

    public Template(String pattern) {
        this.pattern = Objects.requireNonNull(pattern, &quot;pattern must not be null&quot;);
    }

    public String render(Map&amp;lt;String, String&amp;gt; variables) {
        StringBuilder result = new StringBuilder();
        // pattern을 순회하며 ${name}을 찾아 variables.get(&quot;name&quot;)으로 치환
        return result.toString();
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;1928&quot; data-start=&quot;1904&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;1928&quot; data-start=&quot;1904&quot; data-ke-size=&quot;size16&quot;&gt;여기서 플레이스홀더는 다음 형태만 지원한다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre id=&quot;code_1763974271933&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;${className}
${package}
${fields}
${methods}
...&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;2017&quot; data-start=&quot;1992&quot; data-ke-size=&quot;size16&quot;&gt;이 정도면 &lt;b&gt;코드 생성용으로는 충분&lt;/b&gt;하다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 data-end=&quot;2041&quot; data-start=&quot;2024&quot; data-ke-size=&quot;size26&quot;&gt;1-3. 템플릿 사용 예시&lt;/h2&gt;
&lt;p data-end=&quot;2080&quot; data-start=&quot;2043&quot; data-ke-size=&quot;size16&quot;&gt;예를 들어 클래스 전체 구조 템플릿은 이런 식으로 설계할 수 있다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre id=&quot;code_1763974289625&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;String pattern = &quot;&quot;&quot;
package ${package};

${imports}

public class ${className} {

${fields}

${methods}
}
&quot;&quot;&quot;;

Template t = new Template(pattern);
String source = t.render(Map.of(
    &quot;package&quot;, &quot;com.example&quot;,
    &quot;imports&quot;, &quot;import java.util.List;&quot;,
    &quot;className&quot;, &quot;User&quot;,
    &quot;fields&quot;, &quot;    private String name;\n    private int age;&quot;,
    &quot;methods&quot;, &quot;    public String getName() { return name; }\n    // ...&quot;
));&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;2599&quot; data-start=&quot;2515&quot; data-ke-size=&quot;size16&quot;&gt;이렇게 하면 Template은 내부적으로 문자열을 순회하며&amp;nbsp;&lt;b&gt;&lt;span style=&quot;background-color: #dddddd;&quot;&gt; ${...}&amp;nbsp;&lt;/span&gt;&lt;/b&gt; 패턴을 찾고&lt;br /&gt;&lt;b&gt;&lt;span style=&quot;background-color: #dddddd;&quot;&gt;&amp;nbsp;variables&amp;nbsp;&lt;/span&gt;&lt;/b&gt; 에서 값을 꺼내 치환한 결과를 돌려준다.&lt;/p&gt;
&lt;p data-end=&quot;2689&quot; data-start=&quot;2601&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;2689&quot; data-start=&quot;2601&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;핵심 포인트&lt;/b&gt;:&lt;br /&gt;Template은&amp;nbsp;&lt;span style=&quot;background-color: #dddddd;&quot;&gt;&lt;b&gt; &quot;문자열 조립&quot;&amp;nbsp;&lt;/b&gt;&lt;/span&gt; 이라는 책임만 가진다.&lt;br /&gt;이 템플릿에 어떤 데이터를 넣을지는 ClassGenerator의 책임이다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h1 data-end=&quot;2740&quot; data-start=&quot;2696&quot;&gt;2. ClassGenerator &amp;mdash; DTO 한 개의 소스 코드를 만드는 역할&lt;/h1&gt;
&lt;h2 data-end=&quot;2787&quot; data-start=&quot;2742&quot; data-ke-size=&quot;size26&quot;&gt;2-1. ClassSpec / FieldSpec - &quot;코드 생성 계획&quot; 객체&lt;/h2&gt;
&lt;p data-end=&quot;2882&quot; data-start=&quot;2789&quot; data-ke-size=&quot;size16&quot;&gt;generator 계층은 ModelGraph로부터 이미 &lt;b&gt;클래스 설계도&lt;/b&gt;를 전달받는다.&lt;br /&gt;이 설계도는 코드 생성에 최적화된 DTO인&amp;nbsp;&lt;b&gt;&lt;span style=&quot;background-color: #dddddd;&quot;&gt; ClassSpec&amp;nbsp;&lt;/span&gt;&lt;/b&gt; 형태다.&lt;/p&gt;
&lt;p data-end=&quot;2890&quot; data-start=&quot;2884&quot; data-ke-size=&quot;size16&quot;&gt;개념적으로:&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre id=&quot;code_1763974372104&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public final class ClassSpec {

    public static final class FieldSpec {
        private final String type;      // &quot;String&quot;, &quot;int&quot;, &quot;List&amp;lt;Location&amp;gt;&quot; ...
        private final String name;      // &quot;name&quot;, &quot;age&quot; ...
        private final boolean optional; // 필요 시 nullable 표현 등에 활용 가능
    }

    private final String packageName;   // com.example.dto
    private final String className;     // WeatherApiResponse
    private final List&amp;lt;FieldSpec&amp;gt; fields;
    private final List&amp;lt;String&amp;gt; importTypes; // java.util.List 등
    private final boolean innerClass;   // 루트 내부에 중첩 클래스인지 여부
    // ...
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;3519&quot; data-start=&quot;3499&quot; data-ke-size=&quot;size16&quot;&gt;이 시점의 ClassSpec은 이미:&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;3632&quot; data-start=&quot;3521&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;3555&quot; data-start=&quot;3521&quot;&gt;필드 타입 추론(TypeInferencer까지 끝난 상태)&lt;/li&gt;
&lt;li data-end=&quot;3584&quot; data-start=&quot;3556&quot;&gt;이름(NameConverter까지 반영된 상태)&lt;/li&gt;
&lt;li data-end=&quot;3604&quot; data-start=&quot;3585&quot;&gt;optional 여부 판단 완료&lt;/li&gt;
&lt;li data-end=&quot;3632&quot; data-start=&quot;3605&quot;&gt;inner-classes 옵션 반영 여부 결정&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-end=&quot;3716&quot; data-start=&quot;3634&quot; data-ke-size=&quot;size16&quot;&gt;즉, &lt;b&gt;ClassGenerator는 비즈니스 로직을 고민할 필요 없이,&lt;/b&gt;&lt;br /&gt;&quot;기계적으로 '코드 텍스트'를 찍어내기만 하면 되는 상태&quot;가 된다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 data-end=&quot;3749&quot; data-start=&quot;3723&quot; data-ke-size=&quot;size26&quot;&gt;2-2. ClassGenerator의 책임&lt;/h2&gt;
&lt;p data-end=&quot;3778&quot; data-start=&quot;3751&quot; data-ke-size=&quot;size16&quot;&gt;ClassGenerator는 다음 책임을 갖는다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-end=&quot;3950&quot; data-start=&quot;3780&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li data-end=&quot;3811&quot; data-start=&quot;3780&quot;&gt;ClassSpec으로부터&amp;nbsp;&lt;b&gt;&lt;span style=&quot;background-color: #dddddd;&quot;&gt; import&amp;nbsp;&lt;/span&gt;&lt;/b&gt; 목록 생성&lt;/li&gt;
&lt;li data-end=&quot;3847&quot; data-start=&quot;3812&quot;&gt;&lt;b&gt;&lt;span style=&quot;background-color: #dddddd;&quot;&gt;&amp;nbsp;fields&amp;nbsp;&lt;/span&gt;&lt;/b&gt; 리스트를 바탕으로 필드 선언부 문자열 생성&lt;/li&gt;
&lt;li data-end=&quot;3883&quot; data-start=&quot;3848&quot;&gt;생성자, getter 같은 부가 메서드를 필요에 맞게 생성&lt;/li&gt;
&lt;li data-end=&quot;3910&quot; data-start=&quot;3884&quot;&gt;Template에 채워 넣을 문자열을 준비&lt;/li&gt;
&lt;li data-end=&quot;3950&quot; data-start=&quot;3911&quot;&gt;Template.render를 호출해 최종 소스 코드 문자열 생성&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-end=&quot;3963&quot; data-start=&quot;3952&quot; data-ke-size=&quot;size16&quot;&gt;의사코드로 표현하면:&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre id=&quot;code_1763974416228&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;String generate(ClassSpec spec) {
    String packageLine = &quot;package &quot; + spec.packageName() + &quot;;&quot;;
    String imports = buildImports(spec.importTypes());
    String fields = buildFieldLines(spec.fields());
    String methods = buildMethods(spec.fields());

    Template t = new Template(CLASS_TEMPLATE);
    return t.render(Map.of(
        &quot;package&quot;, packageLine,
        &quot;imports&quot;, imports,
        &quot;className&quot;, spec.className(),
        &quot;fields&quot;, fields,
        &quot;methods&quot;, methods
    ));
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;h3 data-end=&quot;4510&quot; data-start=&quot;4471&quot; data-ke-size=&quot;size23&quot;&gt;➕ 왜 Template과 ClassGenerator를 분리했나?&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;4574&quot; data-start=&quot;4512&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;4537&quot; data-start=&quot;4512&quot;&gt;Template은 &quot;문자열 틀&quot;에 집중&lt;/li&gt;
&lt;li data-end=&quot;4574&quot; data-start=&quot;4538&quot;&gt;ClassGenerator는 &quot;틀에 넣을 데이터 계산&quot;에 집중&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-end=&quot;4664&quot; data-start=&quot;4576&quot; data-ke-size=&quot;size16&quot;&gt;만약 템플릿이나 출력 스타일을 바꾸고 싶다면 Template 쪽만 손대면 되고,&lt;br /&gt;DTO의 스펙 자체가 바뀌면 ClassGenerator 쪽만 손대면 된다.&lt;/p&gt;
&lt;p data-end=&quot;4664&quot; data-start=&quot;4576&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;4715&quot; data-start=&quot;4666&quot; data-ke-size=&quot;size16&quot;&gt;즉, 관심사의 분리(Separation of Concerns)를 지키기 위함이다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 data-end=&quot;4764&quot; data-start=&quot;4722&quot; data-ke-size=&quot;size26&quot;&gt;2-3. 예시 &amp;mdash; WeatherApiResponse를 어떻게 생성하나?&lt;/h2&gt;
&lt;p data-end=&quot;4808&quot; data-start=&quot;4766&quot; data-ke-size=&quot;size16&quot;&gt;JSON 분석 계층에서 아래 설계도(ClassSpec)가 넘어왔다고 해보자.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre id=&quot;code_1763974454252&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;ClassSpec: WeatherApiResponse
- package: com.team606.mrdinner.entity
- fields:
    - Location location
    - Current current
- innerClasses: true
- nested ClassSpec:
    - Location (fields: String name, String country)
    - Current (fields: double tempC, int humidity, boolean isDay)&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;5141&quot; data-start=&quot;5108&quot; data-ke-size=&quot;size16&quot;&gt;ClassGenerator의 출력은 대략 다음과 같이 된다:&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre id=&quot;code_1763974469691&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;package com.team606.mrdinner.entity;

public class WeatherApiResponse {

    private Location location;
    private Current current;

    public Location getLocation() {
        return location;
    }

    public Current getCurrent() {
        return current;
    }

    public static class Location {
        private String name;
        private String country;

        public String getName() { return name; }
        public String getCountry() { return country; }
    }

    public static class Current {
        private double tempC;
        private int humidity;
        private boolean isDay;

        public double getTempC() { return tempC; }
        public int getHumidity() { return humidity; }
        public boolean isDay() { return isDay; }
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;5985&quot; data-start=&quot;5919&quot; data-ke-size=&quot;size16&quot;&gt;여기까지가 &quot;raw Java 코드 문자열&quot;이다.&lt;br /&gt;이제 이 문자열을 조금 더 정리해서 보기 좋게 만들 차례다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h1 data-end=&quot;6032&quot; data-start=&quot;5992&quot;&gt;3. CodeFormatter - 줄바꿈(LF), 공백, 빈 줄 정리&lt;/h1&gt;
&lt;p data-end=&quot;6121&quot; data-start=&quot;6034&quot; data-ke-size=&quot;size16&quot;&gt;ClassGenerator까지 지나면 기능적으로는 문제가 없는 Java 코드가 나온다.&lt;br /&gt;하지만 코드 스타일 측면에서는 다음과 같은 문제가 있을 수 있다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;6225&quot; data-start=&quot;6123&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;6165&quot; data-start=&quot;6123&quot;&gt;Windows/Unix 환경이 섞여 &lt;b&gt;CRLF / LF&lt;/b&gt;가 뒤섞임&lt;/li&gt;
&lt;li data-end=&quot;6192&quot; data-start=&quot;6166&quot;&gt;일부 줄 끝에 &lt;b&gt;불필요한 공백&lt;/b&gt; 존재&lt;/li&gt;
&lt;li data-end=&quot;6225&quot; data-start=&quot;6193&quot;&gt;템플릿/조립 과정에서 &lt;b&gt;빈 줄이 두 세 줄씩 생김&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-end=&quot;6273&quot; data-start=&quot;6227&quot; data-ke-size=&quot;size16&quot;&gt;이 문제를 해결하기 위해 별도의 &lt;b&gt;CodeFormatter&lt;/b&gt; 클래스를 만들었다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 data-end=&quot;6310&quot; data-start=&quot;6280&quot; data-ke-size=&quot;size26&quot;&gt;3-1. CodeFormatter가 수행하는 규칙&lt;/h2&gt;
&lt;h3 data-end=&quot;6343&quot; data-start=&quot;6312&quot; data-ke-size=&quot;size23&quot;&gt;1) 개행 문자 통일 &amp;mdash; 전부&amp;nbsp;&lt;b&gt;&lt;span style=&quot;background-color: #dddddd;&quot;&gt; \n&amp;nbsp;&lt;/span&gt;&lt;/b&gt; (LF)로&lt;/h3&gt;
&lt;p data-end=&quot;6355&quot; data-start=&quot;6345&quot; data-ke-size=&quot;size16&quot;&gt;테스트 코드 예시:&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre id=&quot;code_1763974533207&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Test
void 개행_문자를_전부_LF로_통일한다() {
    String src = &quot;line1\r\nline2\rline3\n&quot;;
    String result = formatter.format(src);

    assertThat(result).contains(&quot;line1\nline2\nline3\n&quot;);
    assertThat(result).doesNotContain(&quot;\r&quot;);
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;6636&quot; data-start=&quot;6597&quot; data-ke-size=&quot;size16&quot;&gt;어떤 환경에서 생성하든 결과물은 항상 &lt;b&gt;LF 기반 코드&lt;/b&gt;만 남도록.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 data-end=&quot;6659&quot; data-start=&quot;6643&quot; data-ke-size=&quot;size23&quot;&gt;2) 줄 끝 공백 제거&lt;/h3&gt;
&lt;p data-end=&quot;6668&quot; data-start=&quot;6661&quot; data-ke-size=&quot;size16&quot;&gt;테스트 예시:&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre id=&quot;code_1763974552884&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Test
void 줄_끝_공백을_제거한다() {
    String src = &quot;int x = 1;   \nint y = 2;\t\n&quot;;
    String result = formatter.format(src);

    assertThat(result).isEqualTo(&quot;int x = 1;\nint y = 2;\n&quot;);
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;6929&quot; data-start=&quot;6869&quot; data-ke-size=&quot;size16&quot;&gt;코드 리뷰 시 &quot;줄 끝에 공백&quot;이 잡히는 것만큼 거슬리는 것도 없으므로&lt;br /&gt;아예 생성 단계에서 제거해버린다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 data-end=&quot;6957&quot; data-start=&quot;6936&quot; data-ke-size=&quot;size23&quot;&gt;3) 연속된 빈 줄 하나로 축소&lt;/h3&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre id=&quot;code_1763974581827&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Test
void 연속된_빈줄을_하나로_축소한다() {
    String src = &quot;class A {\n\n\n    int x;\n}\n&quot;;
    // ...
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;7085&quot; data-start=&quot;7068&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #dddddd;&quot;&gt;&lt;b&gt;&amp;nbsp;format&amp;nbsp;&lt;/b&gt;&lt;/span&gt; 호출 후 결과:&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre id=&quot;code_1763974606996&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class A {

    int x;
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;7161&quot; data-start=&quot;7124&quot; data-ke-size=&quot;size16&quot;&gt;처럼 중간에 불필요하게 2줄 이상 비어 있는 구간을 하나로 줄인다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 data-end=&quot;7205&quot; data-start=&quot;7168&quot; data-ke-size=&quot;size26&quot;&gt;3-2. 왜 CodeFormatter를 별도 클래스로 뺐을까?&lt;/h2&gt;
&lt;h3 data-end=&quot;7220&quot; data-start=&quot;7207&quot; data-ke-size=&quot;size23&quot;&gt;SRP 관점&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;7391&quot; data-start=&quot;7222&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;7325&quot; data-start=&quot;7222&quot;&gt;ClassGenerator는 &quot;&lt;b&gt;논리적 코드 내용&quot;&lt;/b&gt;만 고민해야 한다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;7325&quot; data-start=&quot;7270&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;7284&quot; data-start=&quot;7270&quot;&gt;어떤 필드를 가질지&lt;/li&gt;
&lt;li data-end=&quot;7303&quot; data-start=&quot;7287&quot;&gt;어떤 메서드를 생성할지&lt;/li&gt;
&lt;li data-end=&quot;7325&quot; data-start=&quot;7306&quot;&gt;어떤 import가 필요한지 등&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li data-end=&quot;7391&quot; data-start=&quot;7327&quot;&gt;CodeFormatter는 &quot;&lt;b&gt;코드의 외형/스타일&quot;&lt;/b&gt;만 고민해야 한다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;7391&quot; data-start=&quot;7375&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;7391&quot; data-start=&quot;7375&quot;&gt;줄바꿈 / 공백 / 빈 줄&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-end=&quot;7405&quot; data-start=&quot;7393&quot; data-ke-size=&quot;size16&quot;&gt;두 가지를 섞어버리면:&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;7473&quot; data-start=&quot;7407&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;7437&quot; data-start=&quot;7407&quot;&gt;ClassGenerator가 지나치게 비대해지고&lt;/li&gt;
&lt;li data-end=&quot;7473&quot; data-start=&quot;7438&quot;&gt;포맷팅 룰을 바꿀 때마다 코드 생성 로직까지 건드려야 한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-end=&quot;7510&quot; data-start=&quot;7475&quot; data-ke-size=&quot;size16&quot;&gt;따라서 유지보수성과 변경 용이성을 위해 &lt;b&gt;완전히 분리&lt;/b&gt;했다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h1 data-end=&quot;7553&quot; data-start=&quot;7517&quot;&gt;4. FileWriter &amp;mdash; 최종 결과를 디스크에 내리는 단계&lt;/h1&gt;
&lt;p data-end=&quot;7610&quot; data-start=&quot;7555&quot; data-ke-size=&quot;size16&quot;&gt;마지막으로,&amp;nbsp;&lt;b&gt;&lt;span style=&quot;background-color: #dddddd;&quot;&gt; FileWriter&amp;nbsp;&lt;/span&gt;&lt;/b&gt; 가&amp;nbsp;&lt;b&gt;&lt;span style=&quot;background-color: #dddddd;&quot;&gt; String&amp;nbsp;&lt;/span&gt;&lt;/b&gt; 으로 된 소스를&amp;nbsp;&lt;b&gt;&lt;span style=&quot;background-color: #dddddd;&quot;&gt; .java&amp;nbsp;&lt;/span&gt;&lt;/b&gt; 파일로 저장한다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 data-end=&quot;7639&quot; data-start=&quot;7617&quot; data-ke-size=&quot;size26&quot;&gt;4-1. FileWriter의 책임&lt;/h2&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre id=&quot;code_1763974663987&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public class FileWriter {

    public void write(Path outDir, String className, String content) {
        Path file = outDir.resolve(className + &quot;.java&quot;);
        try {
            Files.writeString(file, content, StandardCharsets.UTF_8);
        } catch (IOException e) {
            throw new UserException(
                &quot;[ERROR] Java 파일을 저장하는 중 오류가 발생했습니다: &quot; + file, e);
        }
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;8061&quot; data-start=&quot;8049&quot; data-ke-size=&quot;size16&quot;&gt;책임은 딱 두 가지다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-end=&quot;8134&quot; data-start=&quot;8063&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li data-end=&quot;8093&quot; data-start=&quot;8063&quot;&gt;&lt;b&gt;&lt;span style=&quot;background-color: #dddddd;&quot;&gt;&amp;nbsp;&quot;className.java&quot;&amp;nbsp;&lt;/span&gt;&lt;/b&gt; 파일 경로 조합&lt;/li&gt;
&lt;li data-end=&quot;8134&quot; data-start=&quot;8094&quot;&gt;UTF-8로 파일 쓰기 + 예외를 UserException으로 래핑&lt;/li&gt;
&lt;/ol&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 data-end=&quot;8172&quot; data-start=&quot;8141&quot; data-ke-size=&quot;size26&quot;&gt;4-2. 왜 UserException으로 래핑했나?&lt;/h2&gt;
&lt;p data-end=&quot;8206&quot; data-start=&quot;8174&quot; data-ke-size=&quot;size16&quot;&gt;파일 저장 실패는 대부분 사용자가 조정할 수 있는 문제다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;8251&quot; data-start=&quot;8208&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;8225&quot; data-start=&quot;8208&quot;&gt;잘못된 out 경로 지정&lt;/li&gt;
&lt;li data-end=&quot;8240&quot; data-start=&quot;8226&quot;&gt;권한 없는 디렉터리&lt;/li&gt;
&lt;li data-end=&quot;8251&quot; data-start=&quot;8241&quot;&gt;디스크 가득 참&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-end=&quot;8301&quot; data-start=&quot;8253&quot; data-ke-size=&quot;size16&quot;&gt;이런 문제에 대해 스택 트레이스를 그대로 보여주면,&lt;br /&gt;사용자는 혼란만 느낄 수 있다.&lt;/p&gt;
&lt;p data-end=&quot;8301&quot; data-start=&quot;8253&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;8307&quot; data-start=&quot;8303&quot; data-ke-size=&quot;size16&quot;&gt;그래서:&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;8404&quot; data-start=&quot;8309&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;8335&quot; data-start=&quot;8309&quot;&gt;내부적으로는 IOException을 받되&lt;/li&gt;
&lt;li data-end=&quot;8404&quot; data-start=&quot;8336&quot;&gt;사용자에게는 깔끔한 메시지로 전달&lt;br /&gt;(&amp;nbsp;&lt;span style=&quot;background-color: #dddddd;&quot;&gt;&lt;b&gt; [ERROR] Java 파일을 저장하는 중 오류가 발생했습니다: ...&amp;nbsp;&lt;/b&gt;&lt;/span&gt; )&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-end=&quot;8417&quot; data-start=&quot;8406&quot; data-ke-size=&quot;size16&quot;&gt;하는 구조를 택했다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h1 data-end=&quot;8457&quot; data-start=&quot;8424&quot;&gt;5. 전체 Generator 파이프라인 다시 한 번 정리&lt;/h1&gt;
&lt;p data-end=&quot;8483&quot; data-start=&quot;8459&quot; data-ke-size=&quot;size16&quot;&gt;Generator 계층은 한 마디로 말하면:&lt;/p&gt;
&lt;blockquote data-end=&quot;8576&quot; data-start=&quot;8485&quot; data-ke-style=&quot;style2&quot;&gt;
&lt;p data-end=&quot;8576&quot; data-start=&quot;8487&quot; data-ke-size=&quot;size16&quot;&gt;&quot;ModelGraph가 만든 ClassSpec(설계도)를&lt;br /&gt;Java 소스코드 문자열로 만들고,&lt;br /&gt;스타일을 정리한 뒤, 실제 파일로 저장하는 역할&quot;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-end=&quot;8585&quot; data-start=&quot;8578&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;8585&quot; data-start=&quot;8578&quot; data-ke-size=&quot;size16&quot;&gt;을 수행한다.&lt;/p&gt;
&lt;p data-end=&quot;8585&quot; data-start=&quot;8578&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;8611&quot; data-start=&quot;8587&quot; data-ke-size=&quot;size16&quot;&gt;흐름을 코드로 표현하면 대략 이런 느낌이다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre id=&quot;code_1763974731924&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;for (ClassSpec spec : modelGraph.getAllClasses()) {
    String rawSource = classGenerator.generate(spec);
    String formatted = codeFormatter.format(rawSource);
    fileWriter.write(outDir, spec.className(), formatted);
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-end=&quot;8967&quot; data-start=&quot;8849&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li data-end=&quot;8893&quot; data-start=&quot;8849&quot;&gt;&lt;span style=&quot;background-color: #dddddd;&quot;&gt;&lt;b&gt;&amp;nbsp;ClassGenerator&amp;nbsp;&lt;/b&gt;&lt;/span&gt; &amp;rarr; 논리적으로 올바른 Java 코드 생성&lt;/li&gt;
&lt;li data-end=&quot;8929&quot; data-start=&quot;8894&quot;&gt;&lt;b&gt;&lt;span style=&quot;background-color: #dddddd;&quot;&gt;&amp;nbsp;CodeFormatter&amp;nbsp;&lt;/span&gt;&lt;/b&gt; &amp;rarr; 보기 좋은 형태로 정리&lt;/li&gt;
&lt;li data-end=&quot;8967&quot; data-start=&quot;8930&quot;&gt;&lt;b&gt;&lt;span style=&quot;background-color: #dddddd;&quot;&gt;&amp;nbsp;FileWriter&amp;nbsp;&lt;/span&gt;&lt;/b&gt; &amp;rarr; 실제&amp;nbsp;&lt;b&gt;&lt;span style=&quot;background-color: #dddddd;&quot;&gt; .java&amp;nbsp;&lt;/span&gt;&lt;/b&gt; 파일로 저장&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-end=&quot;8989&quot; data-start=&quot;8969&quot; data-ke-size=&quot;size16&quot;&gt;이 세 부분이 역할을 나누어 맡는다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h1 data-end=&quot;9024&quot; data-start=&quot;8996&quot;&gt;6. Generator 계층 설계가 의미하는 것&lt;/h1&gt;
&lt;p data-end=&quot;9072&quot; data-start=&quot;9026&quot; data-ke-size=&quot;size16&quot;&gt;이 구조는 단순히 DTO를 찍어내기 위한 것이 아니다.&lt;br /&gt;조금 관점을 바꿔 보면:&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;9234&quot; data-start=&quot;9074&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;9103&quot; data-start=&quot;9074&quot;&gt;&lt;b&gt;Template&lt;/b&gt; : 출력 형식에 대한 추상&lt;/li&gt;
&lt;li data-end=&quot;9165&quot; data-start=&quot;9104&quot;&gt;&lt;b&gt;ClassGenerator&lt;/b&gt; : 도메인 모델(ClassSpec)을 코드로 매핑하는 컴파일러 프런트엔드&lt;/li&gt;
&lt;li data-end=&quot;9202&quot; data-start=&quot;9166&quot;&gt;&lt;b&gt;CodeFormatter&lt;/b&gt; : pretty printer&lt;/li&gt;
&lt;li data-end=&quot;9234&quot; data-start=&quot;9203&quot;&gt;&lt;b&gt;FileWriter&lt;/b&gt; : 백엔드(코드 &amp;rarr; 파일)&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-end=&quot;9292&quot; data-start=&quot;9236&quot; data-ke-size=&quot;size16&quot;&gt;즉, 아주 작은 스케일이지만 &quot;코드 생성기(Code generator)&quot;를 설계해 본 셈이다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h1 data-end=&quot;9332&quot; data-start=&quot;9299&quot;&gt;7. 마무리 &amp;mdash; Generator가 만들어낸 최종 산출물&lt;/h1&gt;
&lt;p data-end=&quot;9345&quot; data-start=&quot;9334&quot; data-ke-size=&quot;size16&quot;&gt;최종적으로 사용자는:&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;9431&quot; data-start=&quot;9347&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;9367&quot; data-start=&quot;9347&quot;&gt;CLI 옵션만 적절히 넘겨주면&lt;/li&gt;
&lt;li data-end=&quot;9389&quot; data-start=&quot;9368&quot;&gt;JSON 구조를 이해하지 않고도&lt;/li&gt;
&lt;li data-end=&quot;9431&quot; data-start=&quot;9390&quot;&gt;Java 소스코드(DTO)를 바로 받아서 프로젝트에 포함시킬 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-end=&quot;9484&quot; data-start=&quot;9433&quot; data-ke-size=&quot;size16&quot;&gt;Generator 계층 덕분에&lt;br /&gt;이 도구는 &amp;ldquo;콘솔 로그만 찍고 끝나는 프로그램&amp;rdquo;이 아니라,&lt;/p&gt;
&lt;blockquote data-end=&quot;9543&quot; data-start=&quot;9486&quot; data-ke-style=&quot;style2&quot;&gt;
&lt;p data-end=&quot;9543&quot; data-start=&quot;9488&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;실제 프로젝트에서 바로 가져다 쓸 수 있는&amp;nbsp; &lt;span style=&quot;background-color: #dddddd;&quot;&gt;&amp;nbsp;.java&amp;nbsp;&lt;/span&gt; 파일을 생산하는&lt;/b&gt;&lt;br /&gt;&lt;b&gt;작은 빌드 도구&lt;/b&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-end=&quot;9565&quot; data-start=&quot;9545&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;9565&quot; data-start=&quot;9545&quot; data-ke-size=&quot;size16&quot;&gt;의 역할까지 수행할 수 있게 되었다.&lt;/p&gt;
&lt;p data-end=&quot;9565&quot; data-start=&quot;9545&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot; data-start=&quot;5523&quot; data-end=&quot;5557&quot;&gt;마지막으로, 분석된 스키마를 실제 Java 클래스로 변환하는&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot; data-start=&quot;5523&quot; data-end=&quot;5557&quot;&gt;&lt;b&gt;ClassGenerator&amp;middot;Template&amp;middot;CodeFormatter&lt;/b&gt; 구현 과정을 소개한다&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot; data-start=&quot;5523&quot; data-end=&quot;5557&quot;&gt; &lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;a href=&quot;https://hwangsoojin.tistory.com/39&quot;&gt;JSON DTO Converter (4) - Exception 분석&lt;/a&gt;&lt;/p&gt;</description>
      <category>우아한테크코스 8기 precourse</category>
      <author>hwangsoojin</author>
      <guid isPermaLink="true">https://hwangsoojin.tistory.com/38</guid>
      <comments>https://hwangsoojin.tistory.com/38#entry38comment</comments>
      <pubDate>Mon, 24 Nov 2025 18:00:44 +0900</pubDate>
    </item>
    <item>
      <title>JSON DTO Converter (2) - JSON 분석</title>
      <link>https://hwangsoojin.tistory.com/37</link>
      <description>&lt;h3 data-end=&quot;387&quot; data-start=&quot;314&quot; data-ke-size=&quot;size23&quot;&gt;JsonValidator &amp;rarr; JsonNode &amp;rarr; SchemaNode &amp;rarr; TypeInferencer &amp;rarr; ModelGraph&lt;/h3&gt;
&lt;h3 data-end=&quot;431&quot; data-start=&quot;388&quot; data-ke-size=&quot;size23&quot;&gt;&quot;JSON이 어떻게 DTO 설계도로 변환되는가?&quot;를 깊이 있게 분석한다&lt;/h3&gt;
&lt;p data-end=&quot;496&quot; data-start=&quot;433&quot; data-ke-size=&quot;size16&quot;&gt;이 글은 JSON DTO Converter의 &lt;b&gt;핵심 로직&lt;/b&gt;이 담긴 &quot;JSON 분석 계층&quot;을 집중적으로 다룬다.&lt;/p&gt;
&lt;p data-end=&quot;516&quot; data-start=&quot;498&quot; data-ke-size=&quot;size16&quot;&gt;이 계층은 도구 전체의 심장이다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;598&quot; data-start=&quot;518&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;535&quot; data-start=&quot;518&quot;&gt;JSON 파일을 로드하고&lt;/li&gt;
&lt;li data-end=&quot;548&quot; data-start=&quot;536&quot;&gt;구조를 분석하여&lt;/li&gt;
&lt;li data-end=&quot;566&quot; data-start=&quot;549&quot;&gt;Java 타입을 추론하고&lt;/li&gt;
&lt;li data-end=&quot;598&quot; data-start=&quot;567&quot;&gt;DTO 클래스 구조(ModelGraph)를 만드는&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-end=&quot;616&quot; data-start=&quot;600&quot; data-ke-size=&quot;size16&quot;&gt;모든 과정이 여기에 포함된다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h1 data-end=&quot;650&quot; data-start=&quot;623&quot;&gt;0. JSON 분석 계층이 왜 중요한가?&lt;/h1&gt;
&lt;p data-end=&quot;729&quot; data-start=&quot;652&quot; data-ke-size=&quot;size16&quot;&gt;CLI 계층이 &quot;입력 검증&quot;을 담당했다면,&lt;br /&gt;JSON 분석 계층은 &lt;b&gt;실제로 JSON을 분석하여 DTO 설계도를 만들어내는 엔진&lt;/b&gt;이다.&lt;/p&gt;
&lt;p data-end=&quot;729&quot; data-start=&quot;652&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;756&quot; data-start=&quot;731&quot; data-ke-size=&quot;size16&quot;&gt;즉, JSON 분석 계층은 다음 역할을 한다:&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre id=&quot;code_1763972440395&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;JSON 파일           &amp;rarr; JsonValidator
JsonNode 트리       &amp;rarr; JsonAnalyzer
SchemaNode 스키마   &amp;rarr; TypeInferencer
Java 타입           &amp;rarr; ModelGraph
DTO ClassSpec       &amp;rarr; Generator 계층 전달&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;1006&quot; data-start=&quot;937&quot; data-ke-size=&quot;size16&quot;&gt;여기서 잘못되면 &lt;b&gt;올바른 DTO를 만들 수 없다.&lt;/b&gt;&lt;br /&gt;그래서 이 계층이 가장 중요하며, 가장 정교하게 설계되어야 한다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h1 data-end=&quot;1058&quot; data-start=&quot;1013&quot;&gt;1. JsonValidator - JSON 파일을 안전하게 로드하기 위한 필터&lt;/h1&gt;
&lt;p data-end=&quot;1116&quot; data-start=&quot;1060&quot; data-ke-size=&quot;size16&quot;&gt;JsonValidator는 프로그램 전체에서 &lt;b&gt;가장 먼저 JSON 파일을 직접 다루는 계층&lt;/b&gt;이다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 data-end=&quot;1154&quot; data-start=&quot;1123&quot; data-ke-size=&quot;size26&quot;&gt;✔ JsonValidator가 수행하는 검증 리스트&lt;/h2&gt;
&lt;h3 data-end=&quot;1174&quot; data-start=&quot;1156&quot; data-ke-size=&quot;size23&quot;&gt;1) 파일 존재 여부 확인&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;1212&quot; data-start=&quot;1175&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;1190&quot; data-start=&quot;1175&quot;&gt;경로가 존재해야 한다&lt;/li&gt;
&lt;li data-end=&quot;1212&quot; data-start=&quot;1191&quot;&gt;존재하지만 &lt;b&gt;디렉터리라면 금지&lt;/b&gt;&lt;b&gt;&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 data-end=&quot;1235&quot; data-start=&quot;1214&quot; data-ke-size=&quot;size23&quot;&gt;2) 파일 크기 제한 (5MB)&lt;/h3&gt;
&lt;p data-end=&quot;1245&quot; data-start=&quot;1236&quot; data-ke-size=&quot;size16&quot;&gt;왜 제한을 둘까?&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;1345&quot; data-start=&quot;1247&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;1278&quot; data-start=&quot;1247&quot;&gt;JSON이 지나치게 크면 메모리 사용량이 폭증한다&lt;/li&gt;
&lt;li data-end=&quot;1305&quot; data-start=&quot;1279&quot;&gt;CLI 도구는 즉각적인 응답성이 중요하다&lt;/li&gt;
&lt;li data-end=&quot;1345&quot; data-start=&quot;1306&quot;&gt;파일 크기가 비정상적으로 크다면 &lt;b&gt;사용자의 입력 실수 가능성 &amp;uarr;&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-end=&quot;1369&quot; data-start=&quot;1347&quot; data-ke-size=&quot;size16&quot;&gt;따라서 5MB 제한은 &quot;안전 장치&quot;이다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 data-end=&quot;1413&quot; data-start=&quot;1376&quot; data-ke-size=&quot;size23&quot;&gt;3) BOM(Byte Order Mark) 검사 및 제거&lt;/h3&gt;
&lt;h4 data-end=&quot;1429&quot; data-start=&quot;1415&quot; data-ke-size=&quot;size20&quot;&gt;  BOM이란?&lt;/h4&gt;
&lt;p data-end=&quot;1482&quot; data-start=&quot;1430&quot; data-ke-size=&quot;size16&quot;&gt;일부 Windows 에디터는 UTF-8 파일을 저장할 때&lt;br /&gt;맨 앞에 다음 3바이트를 붙인다:&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre id=&quot;code_1763972511323&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;EF BB BF&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;1511&quot; data-start=&quot;1502&quot; data-ke-size=&quot;size16&quot;&gt;이게 BOM이다.&lt;/p&gt;
&lt;p data-end=&quot;1511&quot; data-start=&quot;1502&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;1548&quot; data-start=&quot;1513&quot; data-ke-size=&quot;size16&quot;&gt;문제는 JSON의 시작은 {여야 하는데&lt;br /&gt;BOM이 있으면&amp;hellip;&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre id=&quot;code_1763972538083&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;iuml;&amp;raquo;&amp;iquest;{&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;1625&quot; data-start=&quot;1564&quot; data-ke-size=&quot;size16&quot;&gt;처럼 보이지 않는 쓰레기 바이트가 앞에 붙는다.&lt;br /&gt;파서에 따라 오류가 날 수 있으므로 반드시 제거해야 한다.&lt;/p&gt;
&lt;h4 data-end=&quot;1653&quot; data-start=&quot;1627&quot; data-ke-size=&quot;size20&quot;&gt;JsonValidator의 처리 방식:&lt;/h4&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-end=&quot;1744&quot; data-start=&quot;1655&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li data-end=&quot;1677&quot; data-start=&quot;1655&quot;&gt;파일을 raw byte로 읽는다&lt;/li&gt;
&lt;li data-end=&quot;1699&quot; data-start=&quot;1678&quot;&gt;첫 3바이트가 BOM인지 검사&lt;/li&gt;
&lt;li data-end=&quot;1711&quot; data-start=&quot;1700&quot;&gt;맞으면 제거&lt;/li&gt;
&lt;li data-end=&quot;1744&quot; data-start=&quot;1712&quot;&gt;제거 여부를&amp;nbsp;&lt;span style=&quot;background-color: #dddddd;&quot;&gt;&lt;b&gt; boolean hadBom&amp;nbsp;&lt;/b&gt;&lt;/span&gt; 에 기록&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-end=&quot;1780&quot; data-start=&quot;1746&quot; data-ke-size=&quot;size16&quot;&gt;이것이 JsonValidator.Result 내부에 포함된다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 data-end=&quot;1806&quot; data-start=&quot;1787&quot; data-ke-size=&quot;size23&quot;&gt;4) JSON 문법 파싱&lt;/h3&gt;
&lt;p data-end=&quot;1844&quot; data-start=&quot;1807&quot; data-ke-size=&quot;size16&quot;&gt;Jackson&amp;nbsp;&lt;span style=&quot;background-color: #dddddd;&quot;&gt;&lt;b&gt; ObjectMapper.readTree()&amp;nbsp;&lt;/b&gt;&lt;/span&gt; 사용.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 data-end=&quot;1863&quot; data-start=&quot;1846&quot; data-ke-size=&quot;size23&quot;&gt;5) 루트 타입 검사&lt;/h3&gt;
&lt;p data-end=&quot;1911&quot; data-start=&quot;1864&quot; data-ke-size=&quot;size16&quot;&gt;루트는 반드시 &lt;b&gt;ObjectNode&lt;/b&gt;여야 한다.&lt;br /&gt;배열 루트는 지원하지 않는다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 data-end=&quot;1945&quot; data-start=&quot;1918&quot; data-ke-size=&quot;size26&quot;&gt;✔ JsonValidator.Result&lt;/h2&gt;
&lt;p data-end=&quot;1977&quot; data-start=&quot;1946&quot; data-ke-size=&quot;size16&quot;&gt;JsonValidator는 다음 데이터를 함께 반환한다:&lt;/p&gt;
&lt;div&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-end=&quot;2096&quot; data-start=&quot;1979&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style1&quot;&gt;
&lt;tbody data-end=&quot;2096&quot; data-start=&quot;2007&quot;&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;필드&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;&lt;b&gt;설명&lt;/b&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr data-end=&quot;2039&quot; data-start=&quot;2007&quot;&gt;
&lt;td data-col-size=&quot;sm&quot; data-end=&quot;2025&quot; data-start=&quot;2007&quot;&gt;&lt;b&gt;&lt;span style=&quot;background-color: #dddddd;&quot;&gt;&amp;nbsp;JsonNode root&amp;nbsp;&lt;/span&gt;&lt;/b&gt;&lt;/td&gt;
&lt;td data-col-size=&quot;sm&quot; data-end=&quot;2039&quot; data-start=&quot;2025&quot;&gt;JSON 루트 트리&lt;/td&gt;
&lt;/tr&gt;
&lt;tr data-end=&quot;2072&quot; data-start=&quot;2040&quot;&gt;
&lt;td data-col-size=&quot;sm&quot; data-end=&quot;2059&quot; data-start=&quot;2040&quot;&gt;&lt;b&gt;&lt;span style=&quot;background-color: #dddddd;&quot;&gt;&amp;nbsp;boolean hadBom&amp;nbsp;&lt;/span&gt;&lt;/b&gt;&lt;/td&gt;
&lt;td data-col-size=&quot;sm&quot; data-end=&quot;2072&quot; data-start=&quot;2059&quot;&gt;BOM 제거 여부&lt;/td&gt;
&lt;/tr&gt;
&lt;tr data-end=&quot;2096&quot; data-start=&quot;2073&quot;&gt;
&lt;td data-col-size=&quot;sm&quot; data-end=&quot;2087&quot; data-start=&quot;2073&quot;&gt;&lt;span style=&quot;background-color: #dddddd;&quot;&gt;&lt;b&gt;&amp;nbsp;sizeBytes&amp;nbsp;&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td data-col-size=&quot;sm&quot; data-end=&quot;2096&quot; data-start=&quot;2087&quot;&gt;파일 크기&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;/div&gt;
&lt;p data-end=&quot;2125&quot; data-start=&quot;2098&quot; data-ke-size=&quot;size16&quot;&gt;이 값들은 이후 JSON 분석 단계에서 활용된다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h1 data-end=&quot;2180&quot; data-start=&quot;2132&quot;&gt;2. JsonNode Tree Model &amp;mdash; JSON을 메모리 트리로 표현하는 방식&lt;/h1&gt;
&lt;p data-end=&quot;2226&quot; data-start=&quot;2182&quot; data-ke-size=&quot;size16&quot;&gt;JsonNode는 Jackson이 제공하는 &lt;b&gt;트리 기반 JSON 모델&lt;/b&gt;이다.&lt;/p&gt;
&lt;p data-end=&quot;2246&quot; data-start=&quot;2228&quot; data-ke-size=&quot;size16&quot;&gt;예를 들어 아래 JSON을 보자:&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre id=&quot;code_1763972685274&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;{
  &quot;user&quot;: {
    &quot;name&quot;: &quot;Alice&quot;,
    &quot;age&quot;: 20
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;2357&quot; data-start=&quot;2316&quot; data-ke-size=&quot;size16&quot;&gt;JsonNode 트리 구조는 텍스트가 아니라 실제로 &lt;b&gt;이렇게 생겼다&lt;/b&gt;:&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre id=&quot;code_1763972702451&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;ObjectNode
 └─ &quot;user&quot; &amp;rarr; ObjectNode
         ├─ &quot;name&quot; &amp;rarr; TextNode(&quot;Alice&quot;)
         └─ &quot;age&quot;  &amp;rarr; IntNode(20)&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 data-end=&quot;2500&quot; data-start=&quot;2480&quot; data-ke-size=&quot;size26&quot;&gt;JsonNode를 사용하는 이유&lt;/h2&gt;
&lt;h3 data-end=&quot;2522&quot; data-start=&quot;2502&quot; data-ke-size=&quot;size23&quot;&gt;1) 구조적 탐색에 최적화&lt;/h3&gt;
&lt;p data-end=&quot;2555&quot; data-start=&quot;2523&quot; data-ke-size=&quot;size16&quot;&gt;필드 키, 값, 노드 타입 등에 대해 자유롭게 탐색 가능.&lt;/p&gt;
&lt;h3 data-end=&quot;2589&quot; data-start=&quot;2557&quot; data-ke-size=&quot;size23&quot;&gt;2) JSON 스키마 분석을 정확하게 수행 가능&lt;/h3&gt;
&lt;p data-end=&quot;2622&quot; data-start=&quot;2590&quot; data-ke-size=&quot;size16&quot;&gt;값이 객체인지, 배열인지, 숫자인지 쉽게 판단할 수 있음.&lt;/p&gt;
&lt;h3 data-end=&quot;2660&quot; data-start=&quot;2624&quot; data-ke-size=&quot;size23&quot;&gt;3) &quot;스트리밍(JSON parser)&quot;보다 더 안정적&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;2734&quot; data-start=&quot;2661&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;2691&quot; data-start=&quot;2661&quot;&gt;스트리밍 파서는 순차 처리라 구조 분석이 어렵다&lt;/li&gt;
&lt;li data-end=&quot;2734&quot; data-start=&quot;2692&quot;&gt;DTO 구조 생성에는 트리 기반 파싱(Tree Model)이 필수&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-end=&quot;2797&quot; data-start=&quot;2736&quot; data-ke-size=&quot;size16&quot;&gt;즉, JsonNode는 &quot;스키마 분석용 내부 AST(Abstract Syntax Tree)&quot;라고 볼 수 있다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h1 data-end=&quot;2842&quot; data-start=&quot;2804&quot;&gt;3. SchemaNode &amp;mdash; 도구 내부 JSON 스키마 표현 모델&lt;/h1&gt;
&lt;p data-end=&quot;2904&quot; data-start=&quot;2844&quot; data-ke-size=&quot;size16&quot;&gt;JsonAnalyzer는 JsonNode를 기반으로 JSON 구조를&lt;br /&gt;언어 독립적인 계층 구조로 추출한다.&lt;/p&gt;
&lt;p data-end=&quot;2904&quot; data-start=&quot;2844&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;2926&quot; data-start=&quot;2906&quot; data-ke-size=&quot;size16&quot;&gt;이것이 &lt;b&gt;SchemaNode&lt;/b&gt;다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h1 data-end=&quot;2953&quot; data-start=&quot;2933&quot;&gt;✔ SchemaNode 구조 요약&lt;/h1&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre id=&quot;code_1763972755955&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;SchemaNode
 ├─ SchemaPrimitive   : 문자열/숫자/boolean/null
 ├─ SchemaObject      : 필드 이름 &amp;rarr; FieldInfo
 ├─ SchemaArray       : 요소 타입의 Set
 └─ SchemaUnion       : 혼합된 여러 타입 (예: [1, &quot;text&quot;])&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;3165&quot; data-start=&quot;3147&quot; data-ke-size=&quot;size16&quot;&gt;각 타입은 서로 조합될 수 있다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 data-end=&quot;3197&quot; data-start=&quot;3172&quot; data-ke-size=&quot;size26&quot;&gt;3-1. SchemaPrimitive&lt;/h2&gt;
&lt;p data-end=&quot;3213&quot; data-start=&quot;3198&quot; data-ke-size=&quot;size16&quot;&gt;JSON 원시값에 해당한다.&lt;/p&gt;
&lt;div&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-end=&quot;3382&quot; data-start=&quot;3215&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style1&quot;&gt;
&lt;tbody data-end=&quot;3382&quot; data-start=&quot;3283&quot;&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;JSON 값&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;&lt;b&gt; SchemaPrimitive 타입 &lt;/b&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr data-end=&quot;3303&quot; data-start=&quot;3283&quot;&gt;
&lt;td data-col-size=&quot;sm&quot; data-end=&quot;3293&quot; data-start=&quot;3283&quot;&gt;&lt;b&gt;&lt;span style=&quot;background-color: #dddddd;&quot;&gt;&amp;nbsp;&quot;str&quot;&amp;nbsp;&lt;/span&gt;&lt;/b&gt;&lt;/td&gt;
&lt;td data-end=&quot;3303&quot; data-start=&quot;3293&quot; data-col-size=&quot;sm&quot;&gt;STRING&lt;/td&gt;
&lt;/tr&gt;
&lt;tr data-end=&quot;3323&quot; data-start=&quot;3304&quot;&gt;
&lt;td data-col-size=&quot;sm&quot; data-end=&quot;3312&quot; data-start=&quot;3304&quot;&gt;&lt;span style=&quot;background-color: #dddddd;&quot;&gt;&lt;b&gt;&amp;nbsp;123&amp;nbsp;&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td data-end=&quot;3323&quot; data-start=&quot;3312&quot; data-col-size=&quot;sm&quot;&gt;INTEGER&lt;/td&gt;
&lt;/tr&gt;
&lt;tr data-end=&quot;3343&quot; data-start=&quot;3324&quot;&gt;
&lt;td data-col-size=&quot;sm&quot; data-end=&quot;3333&quot; data-start=&quot;3324&quot;&gt;&lt;span style=&quot;background-color: #dddddd;&quot;&gt;&lt;b&gt;&amp;nbsp;12.3&amp;nbsp;&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td data-end=&quot;3343&quot; data-start=&quot;3333&quot; data-col-size=&quot;sm&quot;&gt;NUMBER&lt;/td&gt;
&lt;/tr&gt;
&lt;tr data-end=&quot;3364&quot; data-start=&quot;3344&quot;&gt;
&lt;td data-col-size=&quot;sm&quot; data-end=&quot;3353&quot; data-start=&quot;3344&quot;&gt;&lt;b&gt;&lt;span style=&quot;background-color: #dddddd;&quot;&gt;&amp;nbsp;true&amp;nbsp;&lt;/span&gt;&lt;/b&gt;&lt;/td&gt;
&lt;td data-end=&quot;3364&quot; data-start=&quot;3353&quot; data-col-size=&quot;sm&quot;&gt;BOOLEAN&lt;/td&gt;
&lt;/tr&gt;
&lt;tr data-end=&quot;3382&quot; data-start=&quot;3365&quot;&gt;
&lt;td data-col-size=&quot;sm&quot; data-end=&quot;3374&quot; data-start=&quot;3365&quot;&gt;&lt;span style=&quot;background-color: #dddddd;&quot;&gt;&lt;b&gt;&amp;nbsp;null&amp;nbsp;&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td data-end=&quot;3382&quot; data-start=&quot;3374&quot; data-col-size=&quot;sm&quot;&gt;NULL&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;/div&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 data-end=&quot;3411&quot; data-start=&quot;3389&quot; data-ke-size=&quot;size26&quot;&gt;3-2. SchemaObject&lt;/h2&gt;
&lt;p data-end=&quot;3417&quot; data-start=&quot;3413&quot; data-ke-size=&quot;size16&quot;&gt;예:&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre id=&quot;code_1763972860163&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;{
  &quot;name&quot;: &quot;Alice&quot;,
  &quot;age&quot;: 20
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;3479&quot; data-start=&quot;3466&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;3479&quot; data-start=&quot;3466&quot; data-ke-size=&quot;size16&quot;&gt;SchemaObject:&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre id=&quot;code_1763972872483&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;SchemaObject
 ├─ name &amp;rarr; FieldInfo(STRING)
 └─ age  &amp;rarr; FieldInfo(INTEGER)&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;3595&quot; data-start=&quot;3562&quot; data-ke-size=&quot;size16&quot;&gt;여기서 FieldInfo는 &lt;b&gt;옵셔널 여부&lt;/b&gt;까지 계산한다.&lt;/p&gt;
&lt;h3 data-end=&quot;3618&quot; data-start=&quot;3597&quot; data-ke-size=&quot;size23&quot;&gt;▷ presenceCount&lt;/h3&gt;
&lt;p data-end=&quot;3665&quot; data-start=&quot;3619&quot; data-ke-size=&quot;size16&quot;&gt;JSON 여러 개를 분석할 때(배열 등),&lt;br /&gt;각 필드가 얼마나 등장했는지를 센다.&lt;/p&gt;
&lt;h3 data-end=&quot;3686&quot; data-start=&quot;3667&quot; data-ke-size=&quot;size23&quot;&gt;▷ optional 판단&lt;/h3&gt;
&lt;p data-end=&quot;3693&quot; data-start=&quot;3687&quot; data-ke-size=&quot;size16&quot;&gt;예를 들어,&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre id=&quot;code_1763972899823&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;[{ &quot;name&quot;: &quot;A&quot; }, { &quot;name&quot;: &quot;B&quot;, &quot;age&quot;: 20 }]&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;3756&quot; data-start=&quot;3750&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;3756&quot; data-start=&quot;3750&quot; data-ke-size=&quot;size16&quot;&gt;출현 횟수:&lt;/p&gt;
&lt;div&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-end=&quot;3877&quot; data-start=&quot;3758&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style1&quot;&gt;
&lt;tbody data-end=&quot;3877&quot; data-start=&quot;3830&quot;&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;필드&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;&lt;b&gt; count &lt;/b&gt;&lt;/td&gt;
&lt;td&gt;&lt;b&gt; total &lt;/b&gt;&lt;/td&gt;
&lt;td&gt;&lt;b&gt;optional&lt;/b&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr data-end=&quot;3854&quot; data-start=&quot;3830&quot;&gt;
&lt;td data-col-size=&quot;sm&quot; data-end=&quot;3837&quot; data-start=&quot;3830&quot;&gt;name&lt;/td&gt;
&lt;td data-end=&quot;3841&quot; data-start=&quot;3837&quot; data-col-size=&quot;sm&quot;&gt;2&lt;/td&gt;
&lt;td data-end=&quot;3845&quot; data-start=&quot;3841&quot; data-col-size=&quot;sm&quot;&gt;2&lt;/td&gt;
&lt;td data-end=&quot;3854&quot; data-start=&quot;3845&quot; data-col-size=&quot;sm&quot;&gt;false&lt;/td&gt;
&lt;/tr&gt;
&lt;tr data-end=&quot;3877&quot; data-start=&quot;3855&quot;&gt;
&lt;td data-col-size=&quot;sm&quot; data-end=&quot;3861&quot; data-start=&quot;3855&quot;&gt;age&lt;/td&gt;
&lt;td data-end=&quot;3865&quot; data-start=&quot;3861&quot; data-col-size=&quot;sm&quot;&gt;1&lt;/td&gt;
&lt;td data-end=&quot;3869&quot; data-start=&quot;3865&quot; data-col-size=&quot;sm&quot;&gt;2&lt;/td&gt;
&lt;td data-end=&quot;3877&quot; data-start=&quot;3869&quot; data-col-size=&quot;sm&quot;&gt;true&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;/div&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 data-end=&quot;3905&quot; data-start=&quot;3884&quot; data-ke-size=&quot;size26&quot;&gt;3-3. SchemaArray&lt;/h2&gt;
&lt;p data-end=&quot;3934&quot; data-start=&quot;3906&quot; data-ke-size=&quot;size16&quot;&gt;배열의 요소 타입을 모두 모아 Set으로 저장한다.&lt;/p&gt;
&lt;p data-end=&quot;3940&quot; data-start=&quot;3936&quot; data-ke-size=&quot;size16&quot;&gt;예:&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre id=&quot;code_1763972995707&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;[&quot;A&quot;, &quot;B&quot;, &quot;C&quot;]&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;3995&quot; data-start=&quot;3970&quot; data-ke-size=&quot;size16&quot;&gt;&amp;rarr;&amp;nbsp;&lt;span style=&quot;background-color: #dddddd;&quot;&gt;&lt;b&gt; SchemaArray{ STRING }&amp;nbsp;&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-end=&quot;4001&quot; data-start=&quot;3997&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;4001&quot; data-start=&quot;3997&quot; data-ke-size=&quot;size16&quot;&gt;예:&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre id=&quot;code_1763973026286&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;[ true, 42 ]&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;4090&quot; data-start=&quot;4028&quot; data-ke-size=&quot;size16&quot;&gt;&amp;rarr;&amp;nbsp;&lt;span style=&quot;background-color: #dddddd;&quot;&gt;&lt;b&gt; SchemaArray{ BOOLEAN, INTEGER }&amp;nbsp;&lt;/b&gt;&lt;/span&gt;&lt;br /&gt;&amp;rarr; 이 경우 SchemaUnion으로 변환됨&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 data-end=&quot;4130&quot; data-start=&quot;4097&quot; data-ke-size=&quot;size26&quot;&gt;3-4. SchemaUnion &amp;mdash; 값이 여러 타입일 때&lt;/h2&gt;
&lt;p data-end=&quot;4134&quot; data-start=&quot;4132&quot; data-ke-size=&quot;size16&quot;&gt;예:&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre id=&quot;code_1763973055223&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;{ &quot;value&quot;: [1, &quot;A&quot;, true] }&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;4180&quot; data-start=&quot;4177&quot; data-ke-size=&quot;size16&quot;&gt;분석:&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre id=&quot;code_1763973067271&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;value &amp;rarr; SchemaUnion{
            INTEGER,
            STRING,
            BOOLEAN
        }&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;4354&quot; data-start=&quot;4283&quot; data-ke-size=&quot;size16&quot;&gt;이는 TypeInferencer 단계에서&lt;br /&gt;&lt;b&gt;공통 상위 타입(Object)&lt;/b&gt; 또는 &lt;b&gt;Wrapper 타입&lt;/b&gt;으로 축소된다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h1 data-end=&quot;4406&quot; data-start=&quot;4361&quot;&gt;4. TypeInferencer &amp;mdash; SchemaNode &amp;rarr; Java 타입 매핑&lt;/h1&gt;
&lt;p data-end=&quot;4441&quot; data-start=&quot;4408&quot; data-ke-size=&quot;size16&quot;&gt;이제 스키마 구조를 바탕으로 Java 실제 타입을 추론한다.&lt;/p&gt;
&lt;h2 data-end=&quot;4454&quot; data-start=&quot;4443&quot; data-ke-size=&quot;size26&quot;&gt;주요 매핑 규칙&lt;/h2&gt;
&lt;h3 data-end=&quot;4475&quot; data-start=&quot;4456&quot; data-ke-size=&quot;size23&quot;&gt;1) Primitive 매핑&lt;/h3&gt;
&lt;div&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-end=&quot;4659&quot; data-start=&quot;4477&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style1&quot;&gt;
&lt;tbody data-end=&quot;4659&quot; data-start=&quot;4540&quot;&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;SchemaPrimitive&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;&lt;b&gt;Java 타입&lt;/b&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr data-end=&quot;4559&quot; data-start=&quot;4540&quot;&gt;
&lt;td data-col-size=&quot;sm&quot; data-end=&quot;4549&quot; data-start=&quot;4540&quot;&gt;STRING&lt;/td&gt;
&lt;td data-end=&quot;4559&quot; data-start=&quot;4549&quot; data-col-size=&quot;sm&quot;&gt;String&lt;/td&gt;
&lt;/tr&gt;
&lt;tr data-end=&quot;4577&quot; data-start=&quot;4560&quot;&gt;
&lt;td data-col-size=&quot;sm&quot; data-end=&quot;4570&quot; data-start=&quot;4560&quot;&gt;INTEGER&lt;/td&gt;
&lt;td data-end=&quot;4577&quot; data-start=&quot;4570&quot; data-col-size=&quot;sm&quot;&gt;int&lt;/td&gt;
&lt;/tr&gt;
&lt;tr data-end=&quot;4597&quot; data-start=&quot;4578&quot;&gt;
&lt;td data-col-size=&quot;sm&quot; data-end=&quot;4587&quot; data-start=&quot;4578&quot;&gt;NUMBER&lt;/td&gt;
&lt;td data-end=&quot;4597&quot; data-start=&quot;4587&quot; data-col-size=&quot;sm&quot;&gt;double&lt;/td&gt;
&lt;/tr&gt;
&lt;tr data-end=&quot;4619&quot; data-start=&quot;4598&quot;&gt;
&lt;td data-col-size=&quot;sm&quot; data-end=&quot;4608&quot; data-start=&quot;4598&quot;&gt;BOOLEAN&lt;/td&gt;
&lt;td data-end=&quot;4619&quot; data-start=&quot;4608&quot; data-col-size=&quot;sm&quot;&gt;boolean&lt;/td&gt;
&lt;/tr&gt;
&lt;tr data-end=&quot;4659&quot; data-start=&quot;4620&quot;&gt;
&lt;td data-col-size=&quot;sm&quot; data-end=&quot;4627&quot; data-start=&quot;4620&quot;&gt;NULL&lt;/td&gt;
&lt;td data-end=&quot;4659&quot; data-start=&quot;4627&quot; data-col-size=&quot;sm&quot;&gt;Object (또는 nullable wrapper)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;/div&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 data-end=&quot;4697&quot; data-start=&quot;4666&quot; data-ke-size=&quot;size23&quot;&gt;2) Optional 필드 &amp;rarr; Wrapper 승격&lt;/h3&gt;
&lt;p data-end=&quot;4701&quot; data-start=&quot;4699&quot; data-ke-size=&quot;size16&quot;&gt;예:&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre id=&quot;code_1763973134486&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;int &amp;rarr; Integer
boolean &amp;rarr; Boolean&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;4787&quot; data-start=&quot;4743&quot; data-ke-size=&quot;size16&quot;&gt;이는 Java에서는 primitive 타입이 null을 허용하지 않기 때문이다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 data-end=&quot;4822&quot; data-start=&quot;4794&quot; data-ke-size=&quot;size23&quot;&gt;3) SchemaArray &amp;rarr; List&amp;lt;T&amp;gt;&lt;/h3&gt;
&lt;p data-end=&quot;4826&quot; data-start=&quot;4824&quot; data-ke-size=&quot;size16&quot;&gt;예:&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre id=&quot;code_1763973150663&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;{ &quot;tags&quot;: [&quot;dev&quot;, &quot;cs&quot;] }&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;4883&quot; data-start=&quot;4867&quot; data-ke-size=&quot;size16&quot;&gt;&amp;rarr;&amp;nbsp;&lt;span style=&quot;background-color: #dddddd;&quot;&gt;&lt;b&gt; List&amp;lt;String&amp;gt;&amp;nbsp;&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 data-end=&quot;4922&quot; data-start=&quot;4890&quot; data-ke-size=&quot;size23&quot;&gt;4) SchemaObject &amp;rarr; 별도 DTO 클래스&lt;/h3&gt;
&lt;p data-end=&quot;4950&quot; data-start=&quot;4924&quot; data-ke-size=&quot;size16&quot;&gt;이는 ModelGraph에서 클래스로 승격된다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 data-end=&quot;4988&quot; data-start=&quot;4957&quot; data-ke-size=&quot;size23&quot;&gt;5) SchemaUnion &amp;rarr; 공통 타입으로 수렴&lt;/h3&gt;
&lt;p data-end=&quot;4992&quot; data-start=&quot;4990&quot; data-ke-size=&quot;size16&quot;&gt;예:&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre id=&quot;code_1763973184670&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;[1, 2, 3] &amp;rarr; int
[1, 1.5] &amp;rarr; double
[1, &quot;a&quot;] &amp;rarr; Object&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h1 data-end=&quot;5096&quot; data-start=&quot;5060&quot;&gt;5. ModelGraph &amp;mdash; 최종 DTO 클래스 설계도(IR)&lt;/h1&gt;
&lt;p data-end=&quot;5163&quot; data-start=&quot;5098&quot; data-ke-size=&quot;size16&quot;&gt;ModelGraph는 TypeInferencer의 결과를 받아서&lt;br /&gt;&lt;b&gt;실제로 생성될 클래스들의 관계도를 만든다.&lt;/b&gt;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 data-end=&quot;5182&quot; data-start=&quot;5170&quot; data-ke-size=&quot;size26&quot;&gt;클래스 생성 전략&lt;/h2&gt;
&lt;h3 data-end=&quot;5224&quot; data-start=&quot;5184&quot; data-ke-size=&quot;size23&quot;&gt;1) SchemaObject마다 하나의 ClassSpec 생성&lt;/h3&gt;
&lt;p data-end=&quot;5227&quot; data-start=&quot;5225&quot; data-ke-size=&quot;size16&quot;&gt;예:&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre id=&quot;code_1763973208347&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;Root
├─ User
└─ Address&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;h3 data-end=&quot;5289&quot; data-start=&quot;5261&quot; data-ke-size=&quot;size23&quot;&gt;2) inner-classes 옵션 반영&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;5364&quot; data-start=&quot;5290&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;5330&quot; data-start=&quot;5290&quot;&gt;true &amp;rarr; Root 클래스 내부에 static class로 생성&lt;/li&gt;
&lt;li data-end=&quot;5364&quot; data-start=&quot;5331&quot;&gt;false &amp;rarr; 모두 독립된&amp;nbsp;&lt;span style=&quot;background-color: #dddddd;&quot;&gt;&lt;b&gt; .java&amp;nbsp;&lt;/b&gt;&lt;/span&gt; 파일로 생성&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 data-end=&quot;5394&quot; data-start=&quot;5371&quot; data-ke-size=&quot;size26&quot;&gt;ModelGraph가 해결하는 문제들&lt;/h2&gt;
&lt;h3 data-end=&quot;5413&quot; data-start=&quot;5396&quot; data-ke-size=&quot;size23&quot;&gt;✔ 클래스 이름 충돌&lt;/h3&gt;
&lt;p data-end=&quot;5438&quot; data-start=&quot;5414&quot; data-ke-size=&quot;size16&quot;&gt;동일한 이름이 나오면 suffix를 붙인다:&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre id=&quot;code_1763973248199&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;Location, Location2&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;h3 data-end=&quot;5489&quot; data-start=&quot;5469&quot; data-ke-size=&quot;size23&quot;&gt;✔ import 목록 관리&lt;/h3&gt;
&lt;p data-end=&quot;5525&quot; data-start=&quot;5490&quot; data-ke-size=&quot;size16&quot;&gt;필드 타입이 다른 클래스라면 import&lt;br /&gt;동일 패키지는 생략&lt;/p&gt;
&lt;p data-end=&quot;5525&quot; data-start=&quot;5490&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-end=&quot;5544&quot; data-start=&quot;5527&quot; data-ke-size=&quot;size23&quot;&gt;✔ 클래스 계층 유지&lt;/h3&gt;
&lt;p data-end=&quot;5553&quot; data-start=&quot;5545&quot; data-ke-size=&quot;size16&quot;&gt;중첩 구조 유지&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h1 data-end=&quot;5590&quot; data-start=&quot;5560&quot;&gt;6. JSON 분석 전체 흐름을 하나의 예시로 정리&lt;/h1&gt;
&lt;p data-end=&quot;5621&quot; data-start=&quot;5592&quot; data-ke-size=&quot;size16&quot;&gt;아래 JSON을 예로 들어 과정 전체를 시각화해보자:&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre id=&quot;code_1763973271787&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;{
  &quot;user&quot;: {
    &quot;name&quot;: &quot;Alice&quot;,
    &quot;age&quot;: 20
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 data-end=&quot;5715&quot; data-start=&quot;5696&quot; data-ke-size=&quot;size26&quot;&gt;6-1. JsonNode 트리&lt;/h2&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre id=&quot;code_1763973284022&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;ObjectNode
 └─ user : ObjectNode
        ├─ name : &quot;Alice&quot;
        └─ age  : 20&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 data-end=&quot;5832&quot; data-start=&quot;5811&quot; data-ke-size=&quot;size26&quot;&gt;6-2. SchemaNode 트리&lt;/h2&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre id=&quot;code_1763973296601&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;SchemaObject
 └─ user : SchemaObject
        ├─ name : STRING
        └─ age  : INTEGER&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 data-end=&quot;5961&quot; data-start=&quot;5936&quot; data-ke-size=&quot;size26&quot;&gt;6-3. TypeInferencer 결과&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;6014&quot; data-start=&quot;5963&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;5982&quot; data-start=&quot;5963&quot;&gt;user &amp;rarr; User 클래스&lt;/li&gt;
&lt;li data-end=&quot;6000&quot; data-start=&quot;5983&quot;&gt;name &amp;rarr; String&lt;/li&gt;
&lt;li data-end=&quot;6014&quot; data-start=&quot;6001&quot;&gt;age &amp;rarr; int&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 data-end=&quot;6042&quot; data-start=&quot;6021&quot; data-ke-size=&quot;size26&quot;&gt;6-4. ModelGraph 결과&lt;/h2&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre id=&quot;code_1763973315663&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;RootDto
 └─ User user

User
 ├─ String name
 └─ int age&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h1 data-end=&quot;6140&quot; data-start=&quot;6114&quot;&gt;7. 이 설계의 장점 &amp;mdash; 유지보수성과 확장성&lt;/h1&gt;
&lt;h3 data-end=&quot;6179&quot; data-start=&quot;6142&quot; data-ke-size=&quot;size23&quot;&gt;✔ 1) JSON 형식만 바뀌어도 DTO 생성 자동 대응&lt;/h3&gt;
&lt;p data-end=&quot;6190&quot; data-start=&quot;6180&quot; data-ke-size=&quot;size16&quot;&gt;DTO 작성 자동화&lt;/p&gt;
&lt;h3 data-end=&quot;6221&quot; data-start=&quot;6192&quot; data-ke-size=&quot;size23&quot;&gt;✔ 2) 다양한 JSON 구조에 미래 대응&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;6270&quot; data-start=&quot;6222&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;6231&quot; data-start=&quot;6222&quot;&gt;가변 필드&lt;/li&gt;
&lt;li data-end=&quot;6247&quot; data-start=&quot;6232&quot;&gt;optional 필드&lt;/li&gt;
&lt;li data-end=&quot;6260&quot; data-start=&quot;6248&quot;&gt;union 타입&lt;/li&gt;
&lt;li data-end=&quot;6270&quot; data-start=&quot;6261&quot;&gt;중첩 구조&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-end=&quot;6293&quot; data-start=&quot;6272&quot; data-ke-size=&quot;size23&quot;&gt;✔ 3) 구조 변경에 강하다&lt;/h3&gt;
&lt;p data-end=&quot;6337&quot; data-start=&quot;6294&quot; data-ke-size=&quot;size16&quot;&gt;스키마 &amp;rarr; 타입 &amp;rarr; 클래스 구조&lt;br /&gt;각 단계가 독립되어 있어 유지보수가 쉽다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h1 data-end=&quot;6362&quot; data-start=&quot;6344&quot;&gt;8. JSON 분석 파트 요약&lt;/h1&gt;
&lt;div&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-end=&quot;6571&quot; data-start=&quot;6364&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style1&quot;&gt;
&lt;tbody data-end=&quot;6571&quot; data-start=&quot;6393&quot;&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;단계&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;&lt;b&gt;설명&lt;/b&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr data-end=&quot;6435&quot; data-start=&quot;6393&quot;&gt;
&lt;td data-col-size=&quot;sm&quot; data-end=&quot;6409&quot; data-start=&quot;6393&quot;&gt;JsonValidator&lt;/td&gt;
&lt;td data-end=&quot;6435&quot; data-start=&quot;6409&quot; data-col-size=&quot;sm&quot;&gt;파일 검증, BOM 제거, JSON 파싱&lt;/td&gt;
&lt;/tr&gt;
&lt;tr data-end=&quot;6464&quot; data-start=&quot;6436&quot;&gt;
&lt;td data-col-size=&quot;sm&quot; data-end=&quot;6447&quot; data-start=&quot;6436&quot;&gt;JsonNode&lt;/td&gt;
&lt;td data-end=&quot;6464&quot; data-start=&quot;6447&quot; data-col-size=&quot;sm&quot;&gt;실제 JSON 트리 모델&lt;/td&gt;
&lt;/tr&gt;
&lt;tr data-end=&quot;6495&quot; data-start=&quot;6465&quot;&gt;
&lt;td data-col-size=&quot;sm&quot; data-end=&quot;6478&quot; data-start=&quot;6465&quot;&gt;SchemaNode&lt;/td&gt;
&lt;td data-end=&quot;6495&quot; data-start=&quot;6478&quot; data-col-size=&quot;sm&quot;&gt;언어 독립적 스키마 구조&lt;/td&gt;
&lt;/tr&gt;
&lt;tr data-end=&quot;6540&quot; data-start=&quot;6496&quot;&gt;
&lt;td data-col-size=&quot;sm&quot; data-end=&quot;6513&quot; data-start=&quot;6496&quot;&gt;TypeInferencer&lt;/td&gt;
&lt;td data-end=&quot;6540&quot; data-start=&quot;6513&quot; data-col-size=&quot;sm&quot;&gt;SchemaNode &amp;rarr; Java 타입 추론&lt;/td&gt;
&lt;/tr&gt;
&lt;tr data-end=&quot;6571&quot; data-start=&quot;6541&quot;&gt;
&lt;td data-col-size=&quot;sm&quot; data-end=&quot;6554&quot; data-start=&quot;6541&quot;&gt;ModelGraph&lt;/td&gt;
&lt;td data-end=&quot;6571&quot; data-start=&quot;6554&quot; data-col-size=&quot;sm&quot;&gt;DTO 클래스 구조 결정&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;/div&gt;
&lt;p data-end=&quot;6614&quot; data-start=&quot;6573&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;이 다섯 단계가 JSON DTO Converter의 핵심 엔진이다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-end=&quot;6614&quot; data-start=&quot;6573&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-end=&quot;5557&quot; data-start=&quot;5523&quot; data-ke-size=&quot;size16&quot;&gt;다음 편에서는 JSON을 구조적으로 해석하는&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-end=&quot;5557&quot; data-start=&quot;5523&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;JsonAnalyzer와 TypeInferencer&lt;/b&gt;의 내부 흐름을 살펴본다&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-end=&quot;5557&quot; data-start=&quot;5523&quot; data-ke-size=&quot;size16&quot;&gt; &lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;a href=&quot;https://hwangsoojin.tistory.com/38&quot;&gt;JSON DTO Converter (3) - Generator &amp;amp; 출력 분석&lt;/a&gt;&lt;/p&gt;</description>
      <category>우아한테크코스 8기 precourse</category>
      <author>hwangsoojin</author>
      <guid isPermaLink="true">https://hwangsoojin.tistory.com/37</guid>
      <comments>https://hwangsoojin.tistory.com/37#entry37comment</comments>
      <pubDate>Mon, 24 Nov 2025 17:16:24 +0900</pubDate>
    </item>
    <item>
      <title>JSON DTO Converter (1) - CLI 분석</title>
      <link>https://hwangsoojin.tistory.com/36</link>
      <description>&lt;h3 data-end=&quot;366&quot; data-start=&quot;283&quot; data-ke-size=&quot;size23&quot;&gt;&quot;ArgumentParser, CommandLineOption, FileValidator, ParsedArguments&quot;를 깊이 있게 파헤치기&lt;/h3&gt;
&lt;p data-end=&quot;427&quot; data-start=&quot;368&quot; data-ke-size=&quot;size16&quot;&gt;이 글은 JSON DTO Converter 프로젝트의 &lt;b&gt;CLI 계층 전체를 상세하게 분석&lt;/b&gt;하는 글이다.&lt;/p&gt;
&lt;p data-end=&quot;427&quot; data-start=&quot;368&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;524&quot; data-start=&quot;429&quot; data-ke-size=&quot;size16&quot;&gt;프로그램이 실행되기 위해서는&lt;br /&gt;&lt;b&gt;CLI입력 &amp;rarr; 인자 파싱 &amp;rarr; 값 검증 &amp;rarr; 환경 DTO(ParsedArguments) 생성&lt;/b&gt;&lt;br /&gt;까지의 과정이 정확히 수행되어야 한다.&lt;/p&gt;
&lt;p data-end=&quot;524&quot; data-start=&quot;429&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;602&quot; data-start=&quot;526&quot; data-ke-size=&quot;size16&quot;&gt;실제로 이 단계는 &amp;ldquo;도구 전체의 입구(Entry Point)&amp;rdquo;이기 때문에&lt;br /&gt;여기서 제대로 설계하지 않으면 전체 동작이 망가질 수 있다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h1 data-end=&quot;624&quot; data-start=&quot;609&quot;&gt;1. CLI 계층의 역할&lt;/h1&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;CLI 계층은 다음 4가지 책임만 가진다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;✔ 1) 명령줄 옵션을 정의하고&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;✔ 2) 사용자가 입력한 인자를 파싱하고&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt; ✔ 3) 검증(필수 옵션 체크 / 타입 체크 / 경로 체크)하고 &lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt; ✔ 4) 결과를 불변 DTO로 제공한다(ParsedArguments) &lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;792&quot; data-start=&quot;790&quot; data-ke-size=&quot;size16&quot;&gt;즉,&lt;/p&gt;
&lt;blockquote data-end=&quot;857&quot; data-start=&quot;794&quot; data-ke-style=&quot;style2&quot;&gt;
&lt;p data-end=&quot;857&quot; data-start=&quot;796&quot; data-ke-size=&quot;size16&quot;&gt;&quot;사용자가 CLI로 전달한 값을,&lt;br /&gt;프로그램이 이해할 수 있는 형태의 설정(config)으로 변환하는 계층&quot;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-end=&quot;869&quot; data-start=&quot;859&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;869&quot; data-start=&quot;859&quot; data-ke-size=&quot;size16&quot;&gt;이라 할 수 있다.&lt;/p&gt;
&lt;p data-end=&quot;901&quot; data-start=&quot;871&quot; data-ke-size=&quot;size16&quot;&gt;이 역할 단 하나만 수행하도록 구조화한 것이 핵심이다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h1 data-end=&quot;928&quot; data-start=&quot;908&quot;&gt;2. CLI 입력 옵션 전체 정리&lt;/h1&gt;
&lt;p data-end=&quot;957&quot; data-start=&quot;930&quot; data-ke-size=&quot;size16&quot;&gt;프로그램이 받아들일 수 있는 옵션은 단 5개이다:&lt;/p&gt;
&lt;div&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%; height: 126px;&quot; border=&quot;1&quot; data-end=&quot;1191&quot; data-start=&quot;959&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody data-end=&quot;1191&quot; data-start=&quot;999&quot;&gt;
&lt;tr style=&quot;height: 21px;&quot;&gt;
&lt;td style=&quot;height: 21px;&quot;&gt;옵션&lt;/td&gt;
&lt;td style=&quot;height: 21px;&quot;&gt;필수&lt;/td&gt;
&lt;td style=&quot;height: 21px;&quot;&gt;설명&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 21px;&quot; data-end=&quot;1029&quot; data-start=&quot;999&quot;&gt;
&lt;td style=&quot;height: 21px;&quot; data-col-size=&quot;sm&quot; data-end=&quot;1011&quot; data-start=&quot;999&quot;&gt;&lt;span style=&quot;background-color: #dddddd;&quot;&gt;&amp;nbsp;&lt;b&gt;--input&lt;/b&gt;&amp;nbsp;&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;height: 21px;&quot; data-end=&quot;1015&quot; data-start=&quot;1011&quot; data-col-size=&quot;sm&quot;&gt;✔&lt;/td&gt;
&lt;td style=&quot;height: 21px;&quot; data-end=&quot;1029&quot; data-start=&quot;1015&quot; data-col-size=&quot;sm&quot;&gt;JSON 파일 경로&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 21px;&quot; data-end=&quot;1068&quot; data-start=&quot;1030&quot;&gt;
&lt;td style=&quot;height: 21px;&quot; data-col-size=&quot;sm&quot; data-end=&quot;1047&quot; data-start=&quot;1030&quot;&gt;&lt;span style=&quot;background-color: #dddddd;&quot;&gt;&amp;nbsp;&lt;b&gt;--root-class&lt;/b&gt;&amp;nbsp;&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;height: 21px;&quot; data-end=&quot;1051&quot; data-start=&quot;1047&quot; data-col-size=&quot;sm&quot;&gt;✔&lt;/td&gt;
&lt;td style=&quot;height: 21px;&quot; data-end=&quot;1068&quot; data-start=&quot;1051&quot; data-col-size=&quot;sm&quot;&gt;루트 DTO 클래스 이름&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 21px;&quot; data-end=&quot;1105&quot; data-start=&quot;1069&quot;&gt;
&lt;td style=&quot;height: 21px;&quot; data-col-size=&quot;sm&quot; data-end=&quot;1083&quot; data-start=&quot;1069&quot;&gt;&lt;span style=&quot;background-color: #dddddd;&quot;&gt;&amp;nbsp;&lt;b&gt;--package&lt;/b&gt;&amp;nbsp;&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;height: 21px;&quot; data-end=&quot;1087&quot; data-start=&quot;1083&quot; data-col-size=&quot;sm&quot;&gt;✔&lt;/td&gt;
&lt;td style=&quot;height: 21px;&quot; data-end=&quot;1105&quot; data-start=&quot;1087&quot; data-col-size=&quot;sm&quot;&gt;생성 DTO의 패키지 이름&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 21px;&quot; data-end=&quot;1140&quot; data-start=&quot;1106&quot;&gt;
&lt;td style=&quot;height: 21px;&quot; data-col-size=&quot;sm&quot; data-end=&quot;1116&quot; data-start=&quot;1106&quot;&gt;&lt;span style=&quot;background-color: #dddddd;&quot;&gt;&amp;nbsp;&lt;b&gt;--out&lt;/b&gt;&amp;nbsp;&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;height: 21px;&quot; data-end=&quot;1120&quot; data-start=&quot;1116&quot; data-col-size=&quot;sm&quot;&gt;✔&lt;/td&gt;
&lt;td style=&quot;height: 21px;&quot; data-end=&quot;1140&quot; data-start=&quot;1120&quot; data-col-size=&quot;sm&quot;&gt;&lt;b&gt;&lt;span style=&quot;background-color: #dddddd;&quot;&gt;&amp;nbsp;.java&amp;nbsp;&lt;/span&gt;&lt;/b&gt; 파일 저장 디렉터리&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 21px;&quot; data-end=&quot;1191&quot; data-start=&quot;1141&quot;&gt;
&lt;td style=&quot;height: 21px;&quot; data-col-size=&quot;sm&quot; data-end=&quot;1161&quot; data-start=&quot;1141&quot;&gt;&lt;span style=&quot;background-color: #dddddd;&quot;&gt;&amp;nbsp;&lt;b&gt;--inner-classes&lt;/b&gt;&amp;nbsp;&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;height: 21px;&quot; data-end=&quot;1165&quot; data-start=&quot;1161&quot; data-col-size=&quot;sm&quot;&gt;✖&lt;/td&gt;
&lt;td style=&quot;height: 21px;&quot; data-end=&quot;1191&quot; data-start=&quot;1165&quot; data-col-size=&quot;sm&quot;&gt;true/false (기본값 false)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;/div&gt;
&lt;h3 data-end=&quot;1211&quot; data-start=&quot;1193&quot; data-ke-size=&quot;size23&quot;&gt;❗ 중요한 설계 원칙:&lt;/h3&gt;
&lt;p data-end=&quot;1242&quot; data-start=&quot;1212&quot; data-ke-size=&quot;size16&quot;&gt;옵션과 값 사이에는 반드시 &lt;b&gt;공백&lt;/b&gt;이 있어야 한다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre id=&quot;code_1763971109039&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;--input sample.json       &amp;larr; ✔
--input=sample.json       &amp;larr; ✖ 금지&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;1367&quot; data-start=&quot;1316&quot; data-ke-size=&quot;size16&quot;&gt;이 규칙은 ArgumentParser가 정확하고 단순하게 동작하도록 설계된 중요한 제약이다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h1 data-end=&quot;1420&quot; data-start=&quot;1374&quot;&gt;3. CommandLineOption - CLI 옵션을 Enum으로 정의한 이유&lt;/h1&gt;
&lt;p data-end=&quot;1483&quot; data-start=&quot;1422&quot; data-ke-size=&quot;size16&quot;&gt;많은 CLI 도구가 문자열 비교로 옵션을 처리하지만&lt;br /&gt;이 프로젝트에서는 &lt;b&gt;Enum으로 옵션을 정의&lt;/b&gt;했다.&lt;/p&gt;
&lt;p data-end=&quot;1483&quot; data-start=&quot;1422&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;1487&quot; data-start=&quot;1485&quot; data-ke-size=&quot;size16&quot;&gt;예:&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre id=&quot;code_1763971168862&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public enum CommandLineOption {
    INPUT(&quot;--input&quot;, true),
    ROOT_CLASS(&quot;--root-class&quot;, true),
    PACKAGE(&quot;--package&quot;, true),
    OUT(&quot;--out&quot;, true),
    INNER_CLASSES(&quot;--inner-classes&quot;, false);
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;h3 data-end=&quot;1716&quot; data-start=&quot;1703&quot; data-ke-size=&quot;size23&quot;&gt;왜 Enum인가?&lt;/h3&gt;
&lt;h3 data-end=&quot;1733&quot; data-start=&quot;1718&quot; data-ke-size=&quot;size23&quot;&gt;(1) 오타 방지&lt;/h3&gt;
&lt;p data-end=&quot;1838&quot; data-start=&quot;1734&quot; data-ke-size=&quot;size16&quot;&gt;문자열 기반 파싱은&amp;nbsp;&lt;span style=&quot;background-color: #dddddd;&quot;&gt;&lt;b&gt; &quot;--input&quot;&lt;/b&gt;&amp;nbsp;&lt;/span&gt; 과&amp;nbsp;&lt;span style=&quot;background-color: #dddddd;&quot;&gt;&lt;b&gt; &quot;--inptu&quot;&lt;/b&gt;&amp;nbsp;&lt;/span&gt; 을 구분하지 못해&lt;br /&gt;에러가 발생하면 파악이 어렵다.&lt;br /&gt;Enum을 사용하면 IDE의 자동완성 + 컴파일러 체크까지 받는다.&lt;/p&gt;
&lt;h3 data-end=&quot;1871&quot; data-start=&quot;1840&quot; data-ke-size=&quot;size23&quot;&gt;(2) 필수 옵션(required) 여부 명시&lt;/h3&gt;
&lt;p data-end=&quot;1956&quot; data-start=&quot;1872&quot; data-ke-size=&quot;size16&quot;&gt;각 옵션이 &lt;b&gt;필수인지 아닌지&lt;/b&gt;를 Enum 안에 저장한다.&lt;br /&gt;따라서 ArgumentParser는 다음과 같이 깔끔하게 필수 옵션을 체크할 수 있다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre id=&quot;code_1763971308102&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;for (CommandLineOption opt : CommandLineOption.values()) {
    if (opt.required() &amp;amp;&amp;amp; !parsed.containsKey(opt)) {
        throw new UserException(&quot;[ERROR] &quot; + opt.name() + &quot;은 필수입니다.&quot;);
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;h3 data-end=&quot;2182&quot; data-start=&quot;2163&quot; data-ke-size=&quot;size23&quot;&gt;(3) 구조적 확장 가능&lt;/h3&gt;
&lt;p data-end=&quot;2214&quot; data-start=&quot;2183&quot; data-ke-size=&quot;size16&quot;&gt;옵션을 더 늘리고 싶어도 Enum에 한 줄 추가하면 끝!&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h1 data-end=&quot;2258&quot; data-start=&quot;2221&quot;&gt;4. ArgumentParser - CLI 인자 파싱 핵심 로직&lt;/h1&gt;
&lt;p data-end=&quot;2289&quot; data-start=&quot;2260&quot; data-ke-size=&quot;size16&quot;&gt;ArgumentParser는 다음 5단계를 수행한다:&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 data-end=&quot;2327&quot; data-start=&quot;2296&quot; data-ke-size=&quot;size26&quot;&gt;4-1. 옵션 이름(--something) 인식&lt;/h2&gt;
&lt;p data-end=&quot;2339&quot; data-start=&quot;2329&quot; data-ke-size=&quot;size16&quot;&gt;CLI 입력의 예:&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre id=&quot;code_1763971347010&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;--input sample.json --root-class ApiRes --package com.test --out src --inner-classes true&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;2475&quot; data-start=&quot;2440&quot; data-ke-size=&quot;size16&quot;&gt;ArgumentParser는 편의를 위해 다음 패턴을 강제한다:&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre id=&quot;code_1763971367827&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;--옵션명  값&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;2518&quot; data-start=&quot;2495&quot; data-ke-size=&quot;size16&quot;&gt;즉&lt;br /&gt;&lt;span style=&quot;background-color: #dddddd;&quot;&gt;&amp;nbsp;&lt;b&gt;--옵션=값&lt;/b&gt;&amp;nbsp;&lt;/span&gt; 과 같이&amp;nbsp;&lt;b&gt;&lt;span style=&quot;background-color: #dddddd;&quot;&gt; '='&amp;nbsp;&lt;/span&gt;&lt;/b&gt; 이 들어간 형태는 허용하지 않는다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 data-end=&quot;2542&quot; data-start=&quot;2525&quot; data-ke-size=&quot;size26&quot;&gt;4-2. 옵션-값 쌍 수집&lt;/h2&gt;
&lt;p data-end=&quot;2559&quot; data-start=&quot;2544&quot; data-ke-size=&quot;size16&quot;&gt;파서가 수행하는 기본 동작:&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-end=&quot;2692&quot; data-start=&quot;2561&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li data-end=&quot;2578&quot; data-start=&quot;2561&quot;&gt;토큰을 순서대로 읽는다&lt;/li&gt;
&lt;li data-end=&quot;2599&quot; data-start=&quot;2579&quot;&gt;&lt;span style=&quot;background-color: #dddddd;&quot;&gt;&lt;b&gt;&amp;nbsp;&quot;--&quot;&amp;nbsp;&lt;/b&gt;&lt;/span&gt; 로 시작하면 옵션&lt;/li&gt;
&lt;li data-end=&quot;2620&quot; data-start=&quot;2600&quot;&gt;다음 토큰을 &amp;ldquo;값&amp;rdquo;으로 간주&lt;/li&gt;
&lt;li data-end=&quot;2663&quot; data-start=&quot;2621&quot;&gt;Map&amp;lt;CommandLineOption, String&amp;gt; 형태로 저장&lt;/li&gt;
&lt;li data-end=&quot;2692&quot; data-start=&quot;2664&quot;&gt;옵션인지 모르면 UserException 발생&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-end=&quot;2719&quot; data-start=&quot;2694&quot; data-ke-size=&quot;size16&quot;&gt;이 구조는 단순하지만 명확하여 오류를 줄인다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 data-end=&quot;2748&quot; data-start=&quot;2726&quot; data-ke-size=&quot;size26&quot;&gt;4-3. 필수 옵션 누락 오류 검사&lt;/h2&gt;
&lt;p data-end=&quot;2838&quot; data-start=&quot;2750&quot; data-ke-size=&quot;size16&quot;&gt;ArgumentParser는 Enum에 저장된&amp;nbsp;&lt;span style=&quot;background-color: #dddddd;&quot;&gt;&lt;b&gt; required&amp;nbsp;&lt;/b&gt;&lt;/span&gt; 플래그를 보고&lt;br /&gt;필수 옵션이 제공되지 않았다면 UserException을 즉시 발생시킨다.&lt;/p&gt;
&lt;p data-end=&quot;2842&quot; data-start=&quot;2840&quot; data-ke-size=&quot;size16&quot;&gt;예:&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;2900&quot; data-start=&quot;2844&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;2861&quot; data-start=&quot;2844&quot;&gt;root-class 누락&lt;/li&gt;
&lt;li data-end=&quot;2876&quot; data-start=&quot;2862&quot;&gt;package 누락&lt;/li&gt;
&lt;li data-end=&quot;2889&quot; data-start=&quot;2877&quot;&gt;input 누락&lt;/li&gt;
&lt;li data-end=&quot;2900&quot; data-start=&quot;2890&quot;&gt;out 누락&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-end=&quot;2949&quot; data-start=&quot;2902&quot; data-ke-size=&quot;size16&quot;&gt;전부 &amp;ldquo;사용자가 수정할 수 있는 오류&amp;rdquo;이므로 &lt;b&gt;UserException&lt;/b&gt;이 맞다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 data-end=&quot;2997&quot; data-start=&quot;2956&quot; data-ke-size=&quot;size26&quot;&gt;4-4. boolean 옵션(&lt;span style=&quot;background-color: #dddddd;&quot;&gt; --inner-classes &lt;/span&gt;)의 처리&lt;/h2&gt;
&lt;p data-end=&quot;3035&quot; data-start=&quot;2999&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #dddddd;&quot;&gt;&lt;b&gt;&amp;nbsp;--inner-classes&amp;nbsp;&lt;/b&gt;&lt;/span&gt; 는 true/false만 허용한다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre id=&quot;code_1763971508915&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;--inner-classes true  &amp;larr; OK
--inner-classes false &amp;larr; OK
--inner-classes maybe &amp;larr; ERROR&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;3226&quot; data-start=&quot;3130&quot; data-ke-size=&quot;size16&quot;&gt;boolean 해석은 ArgumentParser에서 책임지도록 설계했다.&lt;br /&gt;파일 저장과는 관련 없는 로직이므로, generator 계층에 영향을 주지 않게 하기 위함이다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h1 data-end=&quot;3269&quot; data-start=&quot;3233&quot;&gt;5. FileValidator &amp;mdash; 출력 디렉터리 검증 &amp;amp; 생성&lt;/h1&gt;
&lt;p data-end=&quot;3295&quot; data-start=&quot;3271&quot; data-ke-size=&quot;size16&quot;&gt;FileValidator는 다음만 책임진다:&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;3349&quot; data-start=&quot;3297&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;3315&quot; data-start=&quot;3297&quot;&gt;주어진 경로가 디렉터리인지&lt;/li&gt;
&lt;li data-end=&quot;3331&quot; data-start=&quot;3316&quot;&gt;존재하지 않으면 생성&lt;/li&gt;
&lt;li data-end=&quot;3349&quot; data-start=&quot;3332&quot;&gt;쓰기 권한이 있는지 검증&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-end=&quot;3381&quot; data-start=&quot;3351&quot; data-ke-size=&quot;size23&quot;&gt;❗ 왜 이 로직이 CLI 계층에 있어야 하는가?&lt;/h3&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-end=&quot;3574&quot; data-start=&quot;3383&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li data-end=&quot;3455&quot; data-start=&quot;3383&quot;&gt;출력 디렉터리가 유효하지 않으면&lt;br /&gt;&lt;b&gt;나머지 파이프라인 전체가 작동할 수 없기 때문&lt;/b&gt;&lt;br /&gt;(경로 없이 파일 생성 불가능)&lt;/li&gt;
&lt;li data-end=&quot;3574&quot; data-start=&quot;3457&quot;&gt;JSON 파싱 계층(json package)이나 코드 생성(generator package)은&lt;br /&gt;&quot;파일 시스템&quot;이라는 개념을 몰라야 한다.&lt;br /&gt;SRP를 지키기 위해 IO 검증은 CLI 계층에서 종결한다.&lt;/li&gt;
&lt;/ol&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h1 data-end=&quot;3626&quot; data-start=&quot;3581&quot;&gt;6. ParsedArguments &amp;mdash; 불변(immutable) 환경 설정 객체&lt;/h1&gt;
&lt;p data-end=&quot;3678&quot; data-start=&quot;3628&quot; data-ke-size=&quot;size16&quot;&gt;ArgumentParser가 최종적으로 반환하는 객체는&amp;nbsp;&lt;b&gt;&lt;span style=&quot;background-color: #dddddd;&quot;&gt; ParsedArguments&amp;nbsp;&lt;/span&gt;&lt;/b&gt; 다.&lt;/p&gt;
&lt;p data-end=&quot;3678&quot; data-start=&quot;3628&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;3682&quot; data-start=&quot;3680&quot; data-ke-size=&quot;size16&quot;&gt;예:&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre id=&quot;code_1763971574403&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public final class ParsedArguments {
    private final String inputPath;
    private final String rootClass;
    private final String packageName;
    private final String outDir;
    private final boolean innerClasses;
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;h3 data-end=&quot;3937&quot; data-start=&quot;3919&quot; data-ke-size=&quot;size23&quot;&gt;왜 Immutable인가?&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;4038&quot; data-start=&quot;3939&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;3969&quot; data-start=&quot;3939&quot;&gt;설정값은 프로그램 실행 동안 바뀌어서는 안 된다&lt;/li&gt;
&lt;li data-end=&quot;3985&quot; data-start=&quot;3970&quot;&gt;thread-safe&lt;/li&gt;
&lt;li data-end=&quot;4015&quot; data-start=&quot;3986&quot;&gt;이후 계층에서 실수로 값을 수정할 여지가 없다&lt;/li&gt;
&lt;li data-end=&quot;4038&quot; data-start=&quot;4016&quot;&gt;&quot;환경의 스냅샷&quot; 역할 수행 가능&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-end=&quot;4095&quot; data-start=&quot;4040&quot; data-ke-size=&quot;size16&quot;&gt;즉, CLI 분석이 끝난 시점에서&lt;br /&gt;&quot;사용자가 원하는 설정이 무엇이었는가?&quot;를 고정시키는 객체다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h1 data-end=&quot;4135&quot; data-start=&quot;4102&quot;&gt;7. BOM(Byte Order Mark) &amp;mdash; 개념 설명&lt;/h1&gt;
&lt;p data-end=&quot;4174&quot; data-start=&quot;4137&quot; data-ke-size=&quot;size16&quot;&gt;CLI 글에서 BOM 설명을 요청한 만큼, 개념을 쉽게 정리해보자.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 data-end=&quot;4195&quot; data-start=&quot;4181&quot; data-ke-size=&quot;size26&quot;&gt;7-1. BOM이란?&lt;/h2&gt;
&lt;p data-end=&quot;4257&quot; data-start=&quot;4197&quot; data-ke-size=&quot;size16&quot;&gt;UTF-8 BOM(Byte Order Mark)은&lt;br /&gt;텍스트 파일의 맨 앞에 붙는 3바이트 시퀀스다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre id=&quot;code_1763971645874&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;EF BB BF&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;4341&quot; data-start=&quot;4277&quot; data-ke-size=&quot;size16&quot;&gt;일부 Windows 기반 에디터(Notepad 등)는&lt;br /&gt;UTF-8 파일을 저장할 때 BOM을 붙이는 경우가 있다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 data-end=&quot;4376&quot; data-start=&quot;4348&quot; data-ke-size=&quot;size26&quot;&gt;7-2. BOM이 있으면 어떤 문제가 생기나?&lt;/h2&gt;
&lt;p data-end=&quot;4399&quot; data-start=&quot;4378&quot; data-ke-size=&quot;size16&quot;&gt;JSON은 다음과 같이 시작해야 한다:&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre id=&quot;code_1763971658620&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;{ &quot;key&quot;: &quot;value&quot; }&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;4461&quot; data-start=&quot;4429&quot; data-ke-size=&quot;size16&quot;&gt;하지만 BOM이 있으면 실제 파일의 맨 앞은 다음과 같다:&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre id=&quot;code_1763971671259&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;iuml;&amp;raquo;&amp;iquest;{ &quot;key&quot;: &quot;value&quot; }&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;4538&quot; data-start=&quot;4494&quot; data-ke-size=&quot;size16&quot;&gt;사람 눈에는 보이지 않지만,&lt;br /&gt;파서 입장에서는 쓰레기 바이트가 들어온 것이다.&lt;/p&gt;
&lt;p data-end=&quot;4538&quot; data-start=&quot;4494&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;4590&quot; data-start=&quot;4540&quot; data-ke-size=&quot;size16&quot;&gt;Jackson은 BOM 처리를 자동으로 지원하기도 하지만,&lt;br /&gt;이 프로젝트에서는 명시적으로&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;4617&quot; data-start=&quot;4592&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;4605&quot; data-start=&quot;4592&quot;&gt;BOM 여부 확인&lt;/li&gt;
&lt;li data-end=&quot;4617&quot; data-start=&quot;4606&quot;&gt;필요 시 제거&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-end=&quot;4626&quot; data-start=&quot;4619&quot; data-ke-size=&quot;size16&quot;&gt;를 구현했다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 data-end=&quot;4665&quot; data-start=&quot;4633&quot; data-ke-size=&quot;size26&quot;&gt;7-3. 왜 CLI 분석 글에서 BOM 설명을 하나?&lt;/h2&gt;
&lt;p data-end=&quot;4676&quot; data-start=&quot;4667&quot; data-ke-size=&quot;size16&quot;&gt;이유는 간단하다:&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;4792&quot; data-start=&quot;4678&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;4712&quot; data-start=&quot;4678&quot;&gt;BOM은 사용자 입력의 &lt;b&gt;파일 형식 문제&lt;/b&gt;이기 때문&lt;/li&gt;
&lt;li data-end=&quot;4769&quot; data-start=&quot;4713&quot;&gt;JsonValidator는 CLI 입력(&lt;span style=&quot;background-color: #dddddd;&quot;&gt;&lt;b&gt; --input&lt;/b&gt; &lt;/span&gt;)을 처리하는 과정의 연속선상에서 동작&lt;/li&gt;
&lt;li data-end=&quot;4792&quot; data-start=&quot;4770&quot;&gt;따라서 CLI와 밀접한 상위 개념&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-end=&quot;4846&quot; data-start=&quot;4794&quot; data-ke-size=&quot;size16&quot;&gt;그래서 Overview나 JSON 분석 글보다&lt;br /&gt;CLI 글에서 다루는 것이 가장 자연스럽다고 판단하였다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h1 data-end=&quot;4876&quot; data-start=&quot;4853&quot;&gt;8. CLI 단계에서의 오류 처리 전략&lt;/h1&gt;
&lt;p data-end=&quot;4953&quot; data-start=&quot;4878&quot; data-ke-size=&quot;size16&quot;&gt;ArgumentParser / FileValidator는 사용자가 잘못 입력한 경우&lt;br /&gt;전부&amp;nbsp;&lt;b&gt;&lt;span style=&quot;background-color: #dddddd;&quot;&gt; UserException&amp;nbsp;&lt;/span&gt;&lt;/b&gt; 을 발생시킨다.&lt;/p&gt;
&lt;p data-end=&quot;4957&quot; data-start=&quot;4955&quot; data-ke-size=&quot;size16&quot;&gt;예:&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;5044&quot; data-start=&quot;4959&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;4971&quot; data-start=&quot;4959&quot;&gt;필수 옵션 누락&lt;/li&gt;
&lt;li data-end=&quot;4986&quot; data-start=&quot;4972&quot;&gt;존재하지 않는 파일&lt;/li&gt;
&lt;li data-end=&quot;5007&quot; data-start=&quot;4987&quot;&gt;경로가 파일이 아니라 디렉터리&lt;/li&gt;
&lt;li data-end=&quot;5028&quot; data-start=&quot;5008&quot;&gt;boolean 옵션 잘못된 값&lt;/li&gt;
&lt;li data-end=&quot;5044&quot; data-start=&quot;5029&quot;&gt;출력 디렉터리 권한 없음&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-end=&quot;5116&quot; data-start=&quot;5046&quot; data-ke-size=&quot;size16&quot;&gt;이 예외들은 모두 &quot;사용자가 수정할 수 있는 문제&quot;이므로&lt;br /&gt;&lt;b&gt;friendly error message&lt;/b&gt;가 핵심 철학이다.&lt;/p&gt;
&lt;p data-end=&quot;5124&quot; data-start=&quot;5118&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;5124&quot; data-start=&quot;5118&quot; data-ke-size=&quot;size16&quot;&gt;예시 출력:&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre id=&quot;code_1763971771736&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;[ERROR] --input은 필수입니다.&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre id=&quot;code_1763971787269&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;[ERROR] --inner-classes 옵션은 true 또는 false만 허용합니다: maybe&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h1 data-end=&quot;5273&quot; data-start=&quot;5229&quot;&gt;9. 최고로 중요한 포인트 - CLI 계층은 전체 파이프라인의 안전장치다&lt;/h1&gt;
&lt;p data-end=&quot;5297&quot; data-start=&quot;5275&quot; data-ke-size=&quot;size16&quot;&gt;정리하면 CLI 계층의 목적은 다음이다:&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 data-end=&quot;5336&quot; data-start=&quot;5304&quot; data-ke-size=&quot;size23&quot;&gt;✔ 1) 입력이 &amp;ldquo;제대로 된 JSON 파일인지&amp;rdquo;&lt;/h3&gt;
&lt;p data-end=&quot;5376&quot; data-start=&quot;5337&quot; data-ke-size=&quot;size16&quot;&gt;(파일 존재 / 디렉터리 금지 / 크기 제한 / 확장자 / BOM 등)&lt;/p&gt;
&lt;h3 data-end=&quot;5403&quot; data-start=&quot;5378&quot; data-ke-size=&quot;size23&quot;&gt;✔ 2) 옵션이 &amp;ldquo;정확한 형태인지&amp;rdquo;&lt;/h3&gt;
&lt;p data-end=&quot;5428&quot; data-start=&quot;5404&quot; data-ke-size=&quot;size16&quot;&gt;(&lt;b&gt;&lt;span style=&quot;background-color: #dddddd;&quot;&gt; --option value&amp;nbsp;&lt;/span&gt;&lt;/b&gt; 형식 고정)&lt;/p&gt;
&lt;h3 data-end=&quot;5454&quot; data-start=&quot;5430&quot; data-ke-size=&quot;size23&quot;&gt;✔ 3) 필수 입력이 모두 들어왔는지&lt;/h3&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot; data-start=&quot;5430&quot; data-end=&quot;5454&quot;&gt;✔ 3) 필수 입력이 모두 들어왔는지&lt;/h3&gt;
&lt;h3 data-end=&quot;5487&quot; data-start=&quot;5456&quot; data-ke-size=&quot;size23&quot;&gt;✔ 4) Boolean 옵션을 올바르게 파싱했는지&lt;/h3&gt;
&lt;h3 data-end=&quot;5525&quot; data-start=&quot;5489&quot; data-ke-size=&quot;size23&quot;&gt;✔ 5) 출력 디렉터리가 &amp;ldquo;파일 생성 가능한 상태인지&amp;rdquo;&lt;/h3&gt;
&lt;p data-end=&quot;5541&quot; data-start=&quot;5526&quot; data-ke-size=&quot;size16&quot;&gt;(없으면 생성, 권한 체크)&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-end=&quot;5623&quot; data-start=&quot;5548&quot; data-ke-size=&quot;size16&quot;&gt;이 단계가 제대로 처리되고 나면,&lt;br /&gt;다음 단계(JSON 분석 계층)부터는 오직 &lt;b&gt;&quot;JSON 구조 분석과 타입 추론&quot;&lt;/b&gt;에만 집중하면 된다.&lt;/p&gt;
&lt;p data-end=&quot;5623&quot; data-start=&quot;5548&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;5627&quot; data-start=&quot;5625&quot; data-ke-size=&quot;size16&quot;&gt;즉, &lt;b&gt;&lt;span style=&quot;letter-spacing: 0px;&quot;&gt;CLI 계층은 전체 프로그램의 안정성과 예측 가능성을 보장하는 초석이다.&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-end=&quot;5627&quot; data-start=&quot;5625&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot; data-start=&quot;5523&quot; data-end=&quot;5557&quot;&gt;이어지는 글에서는 입력 JSON을 검증하고 정제하는&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot; data-start=&quot;5523&quot; data-end=&quot;5557&quot;&gt;&lt;b&gt;JsonValidator와 JSON 파싱 로직&lt;/b&gt;을 다룬다&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot; data-start=&quot;5523&quot; data-end=&quot;5557&quot;&gt;  &lt;a href=&quot;https://hwangsoojin.tistory.com/37&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;JSON DTO Converter (2) - JSON 분석&lt;/a&gt;&lt;/p&gt;</description>
      <category>우아한테크코스 8기 precourse</category>
      <author>hwangsoojin</author>
      <guid isPermaLink="true">https://hwangsoojin.tistory.com/36</guid>
      <comments>https://hwangsoojin.tistory.com/36#entry36comment</comments>
      <pubDate>Mon, 24 Nov 2025 16:49:55 +0900</pubDate>
    </item>
  </channel>
</rss>