<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>SCRIPTS BY</title>
    <link>https://nx006.tistory.com/</link>
    <description>개발을 공부하고 있습니다.</description>
    <language>ko</language>
    <pubDate>Wed, 15 Apr 2026 08:17:12 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>nx006</managingEditor>
    <image>
      <title>SCRIPTS BY</title>
      <url>https://tistory1.daumcdn.net/tistory/5921574/attach/75b978cd2c364ea9b86272101103c324</url>
      <link>https://nx006.tistory.com</link>
    </image>
    <item>
      <title>나의 동아리 면접 준비 전략과 면접을 보며 느낀 점들</title>
      <link>https://nx006.tistory.com/82</link>
      <description>&lt;p 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;/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;/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;/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;참고로 여기에 등장하는 모든 예시들은 내가 다니는 학교 기준이고, 22-23년까지의 기준이었다. 연합 동아리의 경우 학교마다 성격이 천차만별일 수 있다(특히 GDSC같은 경우 학교마다 그 성격이 완전히 다르다). 또한 연도별로 운영진마다 운영 방침이 달라지면서, 동아리 성격 자체가 바뀔 수도 있다. 예시는 그저 참고이다.&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;/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;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 면접관의 입장에서, 좋은 면접자를 가려내기 위한 전략들&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;두 시선이 이 글에 혼재되어 있다. 그래서 &amp;lsquo;면접자&amp;rsquo;, &amp;lsquo;면접관&amp;rsquo;의 시선을 글 머리에 구분해두었다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;면접자의 입장에서 느낀 점들&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;개발 지식에 관한 질문들&lt;/h3&gt;
&lt;p 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;/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;a href=&quot;https://www.likelion.net/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;멋사&lt;/a&gt;같은 경우가 대표적이다. 유명세에 비해서 개발 지식에 관한 질문이 그렇게까지는 어렵지 않은 것으로 알려져 있다(그렇기에 인성 면접이 중요하다). UMC같은 경우도 비슷하다&lt;i&gt;(학교마다 다를 수 있다)&lt;/i&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기본적으로는 그 동아리에서 사용되는 지식들을 물어본다. 예를 들어 아두이노 동아리라면 C에 대한 질문을 물어볼 지언정, 뜬금없이 HTTP에 대한 질문을 물어보진 않을 것이다. 블록체인 동아리라면 블록체인에 관련된 질문을 물어볼 것이다. 뜬금없이 Java의 멀티쓰레딩에 대해 물어보진 않을 것이다.&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;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;입문자 레벨의 경우&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;입문자 레벨의 경우, 보통 학교에서 1, 2학년 때 가르칠만한 개념을 그대로 내는 경우가 많아서, 수업만 잘 들었다면 대답할 수 있는 것들을 보통 질문한다. 적어도 다음 표에 나오는 개념 정도는 미리 숙지하는 게 좋다.&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style12&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;개념&lt;/td&gt;
&lt;td&gt;중요도&lt;/td&gt;
&lt;td&gt;비고&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;객체지향의 개념&lt;/td&gt;
&lt;td&gt;★★★&lt;/td&gt;
&lt;td&gt;객체지향의 3원칙 등&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;클래스의 개념&lt;/td&gt;
&lt;td&gt;★★★&lt;/td&gt;
&lt;td&gt;객체와 클래스의 차이에 대해서 물어볼 수도 있음&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;MVC 패턴&lt;/td&gt;
&lt;td&gt;★&lt;/td&gt;
&lt;td&gt;보통 토이플젝이라도 한 번 해봤던 사람들에게 물어봄&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;간단한 언어 문법&lt;/td&gt;
&lt;td&gt;★&lt;/td&gt;
&lt;td&gt;&amp;nbsp;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;HTTP 개념&lt;/td&gt;
&lt;td&gt;★★&lt;/td&gt;
&lt;td&gt;백엔드 모집 시 물어볼 수 있음&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;깃허브 사용 가능 여부&lt;/td&gt;
&lt;td&gt;★★☆&lt;/td&gt;
&lt;td&gt;&amp;nbsp;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;중요도는 개발자들 기준에서의 중요도가 아니라, 입문자를 상대로 면접을 볼 때 자주 나올만한 순위로 매겼다. MVC 패턴같은 경우 실제론 매우 매우 중요한 개념이지만, 보통 입문자를 상대로 물어보는 경우가 그렇게 많지는 않으므로 중요도를 낮게 평가했다. 동아리에서 가르쳐주기 때문이다. 반면 객체지향의 개념의 경우 입문자라고 하더라도 대부분 물어볼 것이다. 아무리 교육 시스템이 좋아도, 객체지향의 개념을 모르는 사람들을 데리고 갈 수 있을 정도의 동아리는 많지 않기 때문이다.&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;/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;Github 사용에 대해서는 지식을 물어보는 질문은 아니긴 하지만, 입문자의 경우 Github 아이디조차 없는 경우가 있어서 서류에 github 아이디를 제출하게 하거나, 혹은 Github를 어느 정도까지 사용 가능한 지 물어보는 경우가 있다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;경험자 레벨의 경우&lt;/h4&gt;
&lt;p 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;/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;예를 들면, 어떤 면접자가 django로 한 프로젝트를 만들었다고 해보자. 이 프로젝트의 깃허브 등을 참고해서 개별 질문들을 이어갈 수 있다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;서비스의 구조에 대해서 설명해주세요&lt;/li&gt;
&lt;li&gt;프로젝트에서 어떤 기술 스택을 사용하셨나요?&lt;/li&gt;
&lt;li&gt;여기서 왜 그런 기술 스택을 사용하셨나요?&lt;/li&gt;
&lt;/ul&gt;
&lt;p 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;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style8&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #1a5490;&quot;&gt;&lt;b&gt;Q&lt;/b&gt;. 이 서비스의 전반적인 아키텍처에 대해서 설명해주세요. 각 부분에 어떤 기술이나 프레임워크 등을 썼는지 설명해주세요.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;i&gt;&amp;rarr; 내가 궁금했던 건 서비스 레벨에서의 전반적인 구조이다. 이런 티키타카를 원했다.&lt;/i&gt;&lt;/span&gt;&lt;span style=&quot;color: #1a5490;&quot;&gt;&lt;i&gt;&lt;/i&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #1b711d;&quot;&gt;&lt;b&gt;A&lt;/b&gt;. 프론트엔드는 React, 백엔드는 Spring을 사용했습니다. 저는 백엔드를 맡았고, 백엔드의 경우 CQRS 구조를 비슷하게 채용했습니다. 통신 요청/응답은 전부 RabbitMQ를 통해서 전달되구요, 쿼리, 응답에 각각 싱글 스프링 서버를 둬서 리퀘스트를 처리합니다. 한편 디비의 경우 쿼리, 응답 디비 모두 Mongo DB를 사용했습니다. 즉 스프링 서버 두 개, 디비가 두 개, 그리고 RabbitMQ 메시징 큐 하나를 연결해서 사용합니다. 전부 GCP에 올라가있구요.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #1a5490;&quot;&gt;&lt;b&gt;Q&lt;/b&gt;. 엇, CQRS 패턴을 적용하신 특별한 이유가 있을까요? 또한 이 부분을 연결할 때 메시징 큐(Rabbit MQ)를 사용하신 이유도 궁금합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #1b711d;&quot;&gt;&lt;b&gt;A&lt;/b&gt;. (이유 설명. 굳이 이유가 없더라도, 배웠으니 실제 프로젝트에 적용해보고 싶어서라는 답변도 괜찮을 것이다).&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #1a5490;&quot;&gt;&lt;b&gt;Q&lt;/b&gt;. 외부 API를 사용하기도 하는데, 이런 API 요청은 어느 서버에서 언제 날리나요? &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #1b711d;&quot;&gt;&lt;b&gt;A&lt;/b&gt;. 아, 외부 API의 경우 유저의 리퀘스트와는 별개로, ㅇㅇ 서버에서 30초마다 한 번 씩 날려서 디비를 갱신합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #1a5490;&quot;&gt;&lt;b&gt;Q&lt;/b&gt;. 문제점은 없었나요?&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #1b711d;&quot;&gt;&lt;b&gt;A&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;style8&quot; /&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;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style8&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #1b711d;&quot;&gt;&lt;b&gt;면접자&lt;/b&gt;: 아, 네&amp;hellip; 음, 일단 스프링에서 기본 제공하는 모델, 레포지토리를 사용했구요. 모델에 (모델 이름) 모델과 DTO를 만들고, 각 서비스에 필요한 이런 저런 레포지토리를 이렇게 만들었습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #1a5490;&quot;&gt;&lt;b&gt;면접관&lt;/b&gt;: 앗, 제가 물어본 건 코드 레벨에서의 설명이 아니라 서비스 레벨에서의 구조였습니다. 혹시 전체 프로젝트가 어떻게 설계되었는지 알 수 있을까요? 예를 들어 이 서버는 어떤 역할을 하고, 기술 스택은 어떻게 되는 지 등의 전체적인 브리핑이요.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #1b711d;&quot;&gt;&lt;b&gt;면접자&lt;/b&gt;: 서버는 장고를 사용했는데... 질문을 잘 이해하지 못 하겠습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #1a5490;&quot;&gt;&lt;b&gt;면접관&lt;/b&gt;: &amp;hellip; 넘어가겠습니다.&lt;/span&gt;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style8&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;참고로 여러 면접 관련 유튜브 영상에, 비슷한 사례가 많이 나온다. 내가 베낀 게 아닐까 의심할 수도 있다. 하지만 정확히 1년 전 개발 동아리 모집 시 면접관이었던 내가 겪은 그대로의 사례다!&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;/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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;코드 레벨 아키텍처&lt;/b&gt;를 물어보는 경우, MVC, MVVC, 혹은 CQRS 패턴 등 코드 레벨에서의 구조 패턴을 설명하라는 질문일 가능성이 높다. 이런 경우 코드 레벨에서의 답변을 하면 된다. 클린 아키텍처 설계를 적용했다, 혹은 도메인 모델은 이러이러하게 만들었다 등의 설명 말이다.&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;보통 대부분의 학생 레벨에서의 토이 프로젝트의 경우, 배운 내용을 일부러 적용하지 않는 이상 1 프론트엔드, 단일 서버, 그리고 단일 데이터베이스를 쓰는 경우가 대부분일 것이다. 이 경우에도 간단하게 이런 기본적인 구조를 사용한다고 답변하면 될 것이다. 외부 API를 쓰는 경우 같은 서버에서 요청해서 날리고, 유저의 응답이 올 때마다 DB를 갱신한다. DB는 RDBMS를 쓴다. 서버는 django를 썼다 등등의 답변이다. 거기서도 면접관이 흥미가 있다면 질문거리를 쏟아낼 것이다.&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;를 물어보는 질문의 경우는, 유저의 관점에서 이 서비스를 어떻게 이용하는 지 로직을 물어보는 것이다. &amp;lsquo;비즈니스 로직&amp;rsquo;이라는 용어를 몰라서 답변을 못하는 경우도 있다. &amp;lsquo;유저가 앱을 이용할 때 이 버튼을 누르고 이 화면에 들어가서, 이렇게 요청을 날릴 수 있다. 응답이 들어온 후에는 이런 식으로 확인이 가능하다&amp;hellip;&amp;rsquo; 이런 유저 입장에서의 사용 로직이 비즈니스 로직이다.&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;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;단순히 지식을 나열하기보다, 그 안에 경험과 논리를 포함해보는 건 어떨까?&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;단순히 지식을 나열하기보다, 그 안에 논리를 포함할 경우 더 좋은 답변이 되는 경우가 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예컨대 &lt;i&gt;&amp;lsquo;C언어와 C++의 차이점은 무엇이 있죠?&amp;rsquo;&lt;/i&gt;라는 질문이 있다고 해보자.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;C++에는 클래스가 있어요! 그리고 new와 delete로 동적 할당을 해요!&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이런 식으로 답변을 할 수도 있지만, 나라면 아래와 같이 답변할 것 같다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;제가 C와 C++을 사용해본 결과, 자원 관리의 측면에서 가장 큰 차이점을 느꼈습니다. C++은 객체 지향 언어입니다. 객체 지향의 혜택 중에는 일반화된 디자인 패턴(설계)을 적용할 수 있다는 점이 있습니다. C++에서 자원 관리를 할 때 가장 두드러지는 설계 중 하나는 바로 RAII 설계 방식이 있습니다. 자원을 직접 관리하는 것이 아닌 객체를 통해서 관리하는 기법으로, 객체의 파괴 시 자원의 반환을 보장할 수 있습니다.&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;&gt;&lt;span style=&quot;color: #666666;&quot;&gt;자원을 직접 관리해야 했던 C와 달리 C++에서는 객체를 통해 자원을 관리함으로써, 보다 용이하게 자원을 관리할 수 있습니다. 이는 NULL 포인터 에러, 기타 메모리 에러를 방지함은 물론 쓰레드, 파일 스트림 등 거의 모든 수동으로 관리해야 했던 자원을 자동으로 관리하는 규칙을 만들 수 있습니다.&lt;br /&gt;&lt;br /&gt;&lt;/span&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: #666666;&quot;&gt;이로써 개발 시 팀간의 의사소통이 분명해집니다. 굳이 RAII가 아니더라도, C++에서는 이러한 자원을 관리하는 정형화된 방법론이 많이 개발되었고, 이러한 방법론을 적용한다면 개발 시 오버로드를 상당히 줄일 수 있습니다. 이러한 규칙이 없다면 참조하는 리소스가 파괴된다거나, 코루틴이 돌 때 돌아올 쓰레드가 닫히는 등의 사고가 발생할 수 있습니다.&lt;br /&gt;&lt;br /&gt;&lt;/span&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: #666666;&quot;&gt;C에서는 각 팀마다 이런 규칙을 상호 합의해야 하고, 또 규칙이 있더라도 휴먼 에러에 의해 실수가 발생할 수 있죠. 특히 이런 경우 어디서 문제가 발생했는 지, 디버깅하기가 굉장히 곤란한 경우가 많습니다.&lt;br /&gt;&lt;br /&gt;&lt;/span&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: #666666;&quot;&gt;이를 제외하고도 에러 처리 기법 등, 휴먼 에러를 방지하고 문제 발생 시 이를 더 빠르게 수정할 수 있도록 하는 여러 안전 장치들을 언어 설계 단계에서부터 적용하였습니다.&lt;/span&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;TMI 대방출이여서 중간에 끊킬 수도 있지만, 위의 답변의 경우 내가 직접 사용해본 결과 얻어진 경험, 혹은 &lt;i&gt;&lt;b&gt;&amp;lsquo;객채지향이 도입됨 &amp;rarr; 디자인 패턴을 적용할 수 있게 됨(RAII가 예시) &amp;rarr; 여러 안전 장치들을 설계할 수 있게 됨(자원 관리가 예시)&amp;rsquo;&lt;/b&gt;&lt;/i&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;/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;사실 지식을 물어보는 질문의 경우 그 동아리에서 진짜로 필요한 지식일 가능성이 매우 높기에, 원하는 니즈 역시 있을 것이다. 예를 들어 자바 스프링을 쓰는 백엔드 동아리에서 뜬금없이 리액트에 대한 질문을 할 가능성은 대단히 낮다. 이 동아리 면접에서 Java에 대한 어떤 질문을 받는다면, 백엔드 개발에 필요한 지식일 가능성이 매우 높으므로, 백엔드 개발자의 관점에서 답변을 이어가는 게 좋을 것이다.&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;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;경험에 대한 질문들&lt;/h3&gt;
&lt;p 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;/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;한 가지 추천해보고 싶은 건, 무언가를 할 때, 블로그처럼 기록을 해두는 게 상당히 좋은 것 같다. 회고록이든 개발 일지 등 형태는 상관 없다. 혹은 README의 형태로 적어두거나, 개인 노션에 저장해두는 것도 괜찮다. 어떻게 설계했고 무엇이 문제였는 지, 그리고 어떻게 해결했는 지, 아쉬움은 무엇이고 어떻게 개선할 것인지, 배운 점은 무엇인지 등등 다양한 내용이 들어갈 수 있다. 간단히 하나의 글로 정리할 수도 있고, 메모처럼 파편적으로 남길 수도 있다. 물론 파편적으로 남기기보다는, 어딘가에 정리를 해두면 상당히 좋다.&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;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;일단 자기가 한 것의 내용 증명이 되기도 하고(내가 기록한 게 결국 내 역할이자 했던 경험이니)&lt;/li&gt;
&lt;li&gt;또한 시간이 지나서 기억이 잘 안 날 때도, 정리한 글들을 보면서 면접을 준비하거나 할 수 있다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p 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;&amp;lsquo;내가 이런 걸 했었나&amp;hellip;? 뭐 했지?&amp;rsquo;를 당하기 싫으면, 평소에 꼼꼼히 기록해두고 정리해두자.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;인성에 대한 질문들&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;어느 직종에 가든 인성 면접은 나올 수 밖에 없는 것 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;개발 직종에 한해서는 이런 질문들은 간간히 물어보는 것 같다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;(문제 상황을 주고) 팀으로 활동할 때 이러이러한 문제는 어떻게 해결할 것이냐&lt;/li&gt;
&lt;li&gt;(문제 상황을 주고) 개발자와 디자이너, 그리고 기획자와 갈등이 생겼을 시 어떻게 해결해나갈 것인가&lt;/li&gt;
&lt;li&gt;이런 것을 개발할 때, 협업자와 나와 의견이 다르다면 어떻게 해결할 것인가?
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;예를 들어 상태 관리를 할 때, 둘이 서로 다른 방법으로 상태를 관리하고자 할 때&lt;/li&gt;
&lt;li&gt;혹은 어떤 페이지를 만들 때, 객체의 상하 관계를 어떻게 구성할 지에 대해 의견이 나뉠 수 있다&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이런 식으로 주로 팀 내에서 협업할 자세가 되어 있는 지, 혹은 팀 외부적으로도 디자이너, 기획자 등과 협업할 자세가 되어 있는 지를 물어볼 수 있다. 여기에 &lt;i&gt;&amp;lsquo;난 협업할 생각 없어요&amp;rsquo;&lt;/i&gt;라고 답할 사람은 없으므로, 사실 대부분 면접자들은 비슷한 답변을 내놓는다. 다 잘 대답하는 것 같기도 하다. 본인의 성향에 맞춰 잘 답변하자.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;결국 평소에 잘 준비해야 하더라&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;지식의 대한 질문은 평소에 미리미리 CS 등의 공부를 많이 해두어야 하고&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;경험에 대한 질문은 평소에 미리미리 경험을 쌓고, 이를 잘 정리해야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;인성에 대한 질문은 어차피 모든 사람들이 다 잘 대답할 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그렇기 때문에 결국 중요한 건 &amp;lsquo;평소에 잘 해두는 것&amp;rsquo; 같다. 사실 이미 서류와 포트폴리오에서 합격 당락이 결정되는 경우가 많은 것 같다. 면접은 단지 이 사람이 팀에 잘 맞고, 적응을 잘 할 지, 이상한 사람은 아닐 지, 우리 사람이 될 수 있을지를 검토하는 마지막 과정일 뿐이란 게 내 생각이다.&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;/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;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;면접관에 입장에서 느낀 점들&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;누구를 뽑아야 하는 가&lt;/h3&gt;
&lt;p 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;/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;어디서 봤는 지 정확히 기억은 나지 않는데, 이게 성공하는 스타트업의 특징이라고도 한다. 보통 스타트업의 경우 사람을 정말 신중하게 뽑는다고 한다. 아마존이었나? 잘 기억은 안 나는데 한 회사의 경우 초기에 4명으로 운영되는데, 새로운 사람이 필요해서 뽑는데 6개월이 걸렸다고 한다.&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;옛날에는 사람이 많으면, 그 중에 좋은 사람들이 있을 가능성이 높아지므로 더 좋지 않을까? 라는 생각이 있었다. 그런데 특히 초창기 스타트업의 경우, 정말 일에 열정이 있고 리스크를 감수할 수 있는 사람들만 모여야 단체가 굴러갈 수 있기에, 오히려 잘 맞지 않는 사람들이 많아질 경우 그대로 공중분해되는 경우가 대부분이라고 한다. 오히려 지금 잘 나가는 IT 기업들 중 많은 기업들이, 창업 시절에는 4명, 6명 등 극소수의 인원으로 수 개월에서 몇 년은 버텼다고 한다. 맞는 말인 것 같다. 분위기가 정말 중요한 게, 한 번 김이 빠지는 순간 내 스스로 열정을 순식간에 잃고, 정도 떠나간다. 한 번 정 떠난 집에 다시 발 들이기 쉽지 않다.&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;/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;/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;/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;그래서 회사처럼 점수제로 평가하는 게, 동아리 면접에서는 그다지 좋은 방법은 아닌 것 같다. 어설프게 A는 몇 점, B는 몇 점 점수를 매겨서, A는 인성 면접에서 별로 좋아보이지는 않지만 지식이나 경험 면에서 높은 점수를 받아 B보다 총합 점수가 높으므로 A를 뽑겠어! 이런 접근 방식은 좋지 않은 것 같다.&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;새로운 신입 직원을 뽑는데 6개월이 걸렸다는 스타트업의 사례를 생각하자. 정말 마음이 가는 상대방을 뽑아야 한다. 여기에 점수제를 적용하는 건 어찌보면 공정성을 지키는 것일 수도 있지만, 면접이 단체가 필요로 하고 원하는 사람을 뽑는거지, 공정을 지키려고 하는 건 아니지 않은가. 정말 필요하고 원하는 사람을 뽑자.&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; 형태의 면접이&amp;nbsp;유행하고 있는 듯 하다. 오히려 이런 형태가 더, 진솔한 사람을 뽑는데 장점이 있을 수 있다. 나도 한 번 해보고 싶은데, 아쉽게도 아직까지 커피챗 형태의 면접을 경험해본 적이 없다. 나중에 받게 된다면 꼭 후기를 남기고 싶다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;면접은 쌍방의 평가 과정이다&lt;/h3&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-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;/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;/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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;왜 이런 말을 쓰냐면, 이런 서류를 아예 안 보고 면접에 임하는 동아리가 실제로 꽤 많다. 이런 경우 면접자 입장에서 단체에 분위기가 머릿속에 바로 그려지면서, &amp;lsquo;내가 왜 이런 동아리에 들어가야 하지?&amp;rsquo;라는 생각이 들 수 밖에 없다. 동아리 운영진을 했던 사람으로써, 실제로 서류조차 읽지 않고 사람을 뽑는 동아리의 사례를 종종 봤다. 그러지 말자&amp;hellip;.&lt;/p&gt;</description>
      <category>개발 동아리 면접</category>
      <category>면접 질문</category>
      <author>nx006</author>
      <guid isPermaLink="true">https://nx006.tistory.com/82</guid>
      <comments>https://nx006.tistory.com/82#entry82comment</comments>
      <pubDate>Sat, 20 Jul 2024 04:34:33 +0900</pubDate>
    </item>
    <item>
      <title>개발 블로그를 운영하면서 느낀 점들 (7만 뷰 달성 )</title>
      <link>https://nx006.tistory.com/81</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;어느덧 누적 조회수가 7만 뷰를 달성했다. 그런 김에 그동안 블로그를 운영하면서 느낀 후기를 써보려고 한다.&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;원래 개발 블로그 운영 후기는 5만 뷰가 달성되었을 때 쓰려고 했다. 혹은 블로그 1주년 때 쓰려고 했다.&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;/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;하지만 이대로 안 쓰다간 2년 후에나 쓸 것 같아서, 나름 활발하게 블로그를 운영할 때의 기억을 되살려 후기를 남겨보고자 한다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;블로그를 돌아보며&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1년 반 동안의 성과&lt;/h3&gt;
&lt;figure id=&quot;og_1720803803033&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;백준 [17081번 RPG Extreme]&quot; data-og-description=&quot;문제 https://www.acmicpc.net/problem/17081 17081번: RPG Extreme 요즘 택희는 RPG 게임을 하고 있다. 던전을 헤쳐나가며 몬스터를 물리치고, 아이템을 모으고, 레벨 업을 하여 보스 몬스터를 물리치는 전형적인&quot; data-og-host=&quot;nx006.tistory.com&quot; data-og-source-url=&quot;https://nx006.tistory.com/2&quot; data-og-url=&quot;https://nx006.tistory.com/2&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/fttEK/hyWzyrLuV0/e3kBh626wq8IvFRkdK26kk/img.png?width=800&amp;amp;height=533&amp;amp;face=0_0_800_533,https://scrap.kakaocdn.net/dn/QylF6/hyWzzEdevG/mkg29RglekDE8lm5fLu2UK/img.png?width=800&amp;amp;height=533&amp;amp;face=0_0_800_533,https://scrap.kakaocdn.net/dn/bcOfnt/hyWzxzEvRL/U6dFj3qh6pQBzIR7PWn981/img.png?width=2000&amp;amp;height=1500&amp;amp;face=0_0_2000_1500&quot;&gt;&lt;a href=&quot;https://nx006.tistory.com/2&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://nx006.tistory.com/2&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/fttEK/hyWzyrLuV0/e3kBh626wq8IvFRkdK26kk/img.png?width=800&amp;amp;height=533&amp;amp;face=0_0_800_533,https://scrap.kakaocdn.net/dn/QylF6/hyWzzEdevG/mkg29RglekDE8lm5fLu2UK/img.png?width=800&amp;amp;height=533&amp;amp;face=0_0_800_533,https://scrap.kakaocdn.net/dn/bcOfnt/hyWzxzEvRL/U6dFj3qh6pQBzIR7PWn981/img.png?width=2000&amp;amp;height=1500&amp;amp;face=0_0_2000_1500');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;백준 [17081번 RPG Extreme]&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;문제 https://www.acmicpc.net/problem/17081 17081번: RPG Extreme 요즘 택희는 RPG 게임을 하고 있다. 던전을 헤쳐나가며 몬스터를 물리치고, 아이템을 모으고, 레벨 업을 하여 보스 몬스터를 물리치는 전형적인&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;nx006.tistory.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;블로그에 첫 번째 글을 올렸을 때가 2023년 1월 23일이다. 이 글을 쓰는 오늘은 2024년 7월 6일&lt;i&gt;(*포스팅을 마치는 시점은 13일이 되었다)&lt;/i&gt;. 약 1년 반의 시간이 흘렀다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1942&quot; data-origin-height=&quot;1144&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/yUlS1/btsIy1uMTXb/OedlkxBXy9SQR34Hc7G6e0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/yUlS1/btsIy1uMTXb/OedlkxBXy9SQR34Hc7G6e0/img.png&quot; data-alt=&quot;월간 조회수&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/yUlS1/btsIy1uMTXb/OedlkxBXy9SQR34Hc7G6e0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FyUlS1%2FbtsIy1uMTXb%2FOedlkxBXy9SQR34Hc7G6e0%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; alt=&quot;월간 조회수&quot; loading=&quot;lazy&quot; width=&quot;1942&quot; height=&quot;1144&quot; data-origin-width=&quot;1942&quot; data-origin-height=&quot;1144&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;월간 조회수&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;어느덧 누적 조회수도 7만 회를 넘어섰다.&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;6월 달에는 조금 감소하였지만 월 조회수도 꾸준히 증가하여, 4월과 5월에는 1만 회가 넘는 조회수를 기록하기도 했다.&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;하루 조회수도 대강 300회 전후로 찍힌다.&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;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1848&quot; data-origin-height=&quot;1270&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/FbFCZ/btsIyHC74lI/9wh6GaZ4W4MBEmfhG8paH0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/FbFCZ/btsIyHC74lI/9wh6GaZ4W4MBEmfhG8paH0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/FbFCZ/btsIyHC74lI/9wh6GaZ4W4MBEmfhG8paH0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FFbFCZ%2FbtsIyHC74lI%2F9wh6GaZ4W4MBEmfhG8paH0%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;1848&quot; height=&quot;1270&quot; data-origin-width=&quot;1848&quot; data-origin-height=&quot;1270&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;지난 6월의 인기글들인데, 의외로 CMake 저 글이 인기 순위에 있더라. 지금 보면 상당히 부끄러운 글이다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;블로그, 왜 시작함?&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;시작은 동아리에서부터였다. 이전 글에 종종 등장한 B612 동아리가 그 주인공.&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;/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;/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;2022년 12월. 이 당시 개발 블로그가 유행했다. 그러면서 교내에서도 개발 블로그를 운영하는 사람들이 주목을 받기 시작했고, 또한 새롭게 개발 블로그를 운영해보고자 하는 사람들도 생겨났다.&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;이 수요를 기반으로 B612에 &amp;lsquo;모각글&amp;rsquo;이라는 소모임이 있었다. 개발 블로그를 새롭게 운영해보고자 하는 사람들이 이 소모임에 모였다. 나 역시 이 소모임을 통해 블로그를 처음 시작했다.&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;/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;그것도 원래 블로그에 작성한 것이 아니라, B612의 다른 소모임이었던 알고리즘 스터디에서, 내가 준비한 문제 풀이를 약간의 보완을 거쳐서 그대로 올린 것이었다.&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;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;블로그에 진심이 된 계기&lt;/h3&gt;
&lt;p 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;/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;/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;그래도 많은 글들을 썼다. 교내에 논설문 대회같이 글을 쓰는 대회는 빠짐없이 나갔기도 했고, 지금보면 너무 부끄럽지만 틈틈히 스릴러 소설을 쓰기도 했다(조금 끄적이다 만 이 소설은 아직도 본가의 컴퓨터 HDD 어딘가에 저장되어 있다).&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;/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;/p&gt;
&lt;p 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;처음에는 숙제에 불과했지만, 점차 블로그를 &amp;lsquo;잘&amp;rsquo; 만들어보고 싶다는 욕심이 생겼다. 그래서 점차 글을 완성해가는데 집착했던 것 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 지금은 모르겠지만 이 당시만 해도 개발자에게 블로그란 하나의 중요한 포트폴리오이자 자산이었다. 나의 지난 시간의 노력과 경험들을 보관하는 창고이자 남들에게 보여줄 수 있는 증명이기에, 그 자체로 소중한 자산이 된다. 그런 자산을 단지 나 혼자만 알아볼 수 있는 메모들로 채워버리고 싶진 않았다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 글의 완성도를 높이는 데 최대한 집중했다. 포토샵을 이용해서 썸네일을 만들고, 글을 뒷받침하는 여러 도식도 직접 만들었다. 요즘 검색 엔진 트렌드에 맞지 않을 수도 있지만, 그래도 하나의 글에 최대한 많은 정보를 담는 방향으로 블로그의 컨셉을 잡았다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;지금은?&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;마지막 글을 쓴 지 3개월 만에 쓰는 글이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사실 원래 계획했던 글들이 많이 있기는 했다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;948&quot; data-origin-height=&quot;591&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/C7Yoi/btsIyJnoR1k/k0lz8CIlPE9iEFXJEeBfCk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/C7Yoi/btsIyJnoR1k/k0lz8CIlPE9iEFXJEeBfCk/img.png&quot; data-alt=&quot;이게 다 완성되지 못한 글들이다&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/C7Yoi/btsIyJnoR1k/k0lz8CIlPE9iEFXJEeBfCk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FC7Yoi%2FbtsIyJnoR1k%2Fk0lz8CIlPE9iEFXJEeBfCk%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; alt=&quot;이게 다 완성되지 못한 글들이다&quot; loading=&quot;lazy&quot; width=&quot;948&quot; height=&quot;591&quot; data-origin-width=&quot;948&quot; data-origin-height=&quot;591&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;이게 다 완성되지 못한 글들이다&lt;/figcaption&gt;
&lt;/figure&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;/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;/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;/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;/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;내가 한창 C++의 매력에 빠져있었을 때 나는 C++에 관한 여러 포스팅을 올렸다. 하지만 Flutter 등을 배우고 C++에 대한 관심이 서서히 떨어질 때부터, C++에 대한 포스팅은 점차 줄어들었다. 그러면서 작업 중이던 C++에 관한 포스팅 역시 그대로 중지되었다.&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;/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;하지만 각종 정보와 내 생각을 담다 보니 글의 주제가 &amp;lsquo;언어의 역사&amp;rsquo;인지, 혹은 &amp;lsquo;플랫폼 경쟁에 대한 고찰과 의의&amp;rsquo;인지, &amp;lsquo;각 기업의 대표 개발 생태계 소개&amp;rsquo;인지 모호해지고, 내용도 난잡해져서 결국 초안 쓰는 와중에 그대로 미뤄버렸다.&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;올해에 들어서 휴학을 하면서는, 정말 글을 쓸 생각이 들지 않았다. 애초에 개발 공부를 하지 않으니 글을 쓸 수도 없다. 대신 2월에 있었던 솔루션 챌린지에 대한 개발 과정을 기록하려고 했는데, 대회가 끝나자마자 &amp;lsquo;미룬이 병&amp;rsquo;이 도져서 결국 쓸 타이밍을 그대로 놓친 것 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;figure data-ke-type=&quot;video&quot; data-ke-style=&quot;alignCenter&quot; data-video-host=&quot;youtube&quot; data-video-url=&quot;https://www.youtube.com/watch?v=yx1z7MbbKyM&quot; data-video-thumbnail=&quot;https://scrap.kakaocdn.net/dn/m0oeg/hyWzwU3d45/IBnWUxCq0zQzYkmHzapFPk/img.jpg?width=1280&amp;amp;height=720&amp;amp;face=388_204_638_478&quot; data-video-width=&quot;860&quot; data-video-height=&quot;484&quot; data-video-origin-width=&quot;860&quot; data-video-origin-height=&quot;484&quot; data-ke-mobilestyle=&quot;widthContent&quot; data-video-title=&quot;[4K] 미룬이 (prod.과나) Official M/V&quot; data-original-url=&quot;&quot;&gt;&lt;iframe src=&quot;https://www.youtube.com/embed/yx1z7MbbKyM&quot; width=&quot;860&quot; height=&quot;484&quot; frameborder=&quot;&quot; allowfullscreen=&quot;true&quot;&gt;&lt;/iframe&gt;
&lt;figcaption style=&quot;display: none;&quot;&gt;&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사실 나는 이제 26년 초 전역까지 군인이 되기에&amp;hellip; 앞으로 2년 간은 글을 쓸 일이 없을 것 같다.&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;전역하고 나서도 아마 개발 블로그는 운영하지 않을까? 근데 세상이 너무 빠르게 바뀌어서, 어떻게 될 지는 모르겠다. 사실 AI가 등장하고 나서부터, 개발 블로그의 가치 역시 크게 반감된 것 같기도 하다.&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;26년에는 세상이 어떻게 바뀌어있을 지 모르겠지만, 그때 다시 내가 블로그를 찾게 된다면, 이 글은 2년 뒤에 나에게 보내는 인삿말이 될 것이다.&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;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;figure id=&quot;og_1720803870259&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;개발자 일상&quot; data-og-description=&quot; &quot; data-og-host=&quot;nx007.tistory.com&quot; data-og-source-url=&quot;https://nx007.tistory.com/&quot; data-og-url=&quot;https://nx007.tistory.com&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/cUUZ70/hyWzxl566O/v6Gi7B5VvwKCx6KOGWY40k/img.png?width=400&amp;amp;height=400&amp;amp;face=0_0_400_400,https://scrap.kakaocdn.net/dn/PwpuI/hyWzpn7dAk/fybKpffbiFydqkxKhYsIz1/img.png?width=400&amp;amp;height=400&amp;amp;face=0_0_400_400&quot;&gt;&lt;a href=&quot;https://nx007.tistory.com/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://nx007.tistory.com/&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/cUUZ70/hyWzxl566O/v6Gi7B5VvwKCx6KOGWY40k/img.png?width=400&amp;amp;height=400&amp;amp;face=0_0_400_400,https://scrap.kakaocdn.net/dn/PwpuI/hyWzpn7dAk/fybKpffbiFydqkxKhYsIz1/img.png?width=400&amp;amp;height=400&amp;amp;face=0_0_400_400');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;개발자 일상&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;nx007.tistory.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;현재 이 블로그(nx006)는 개발 블로그여서, 다른 주제를 쓸 수가 없다(쓰게 된다면 검색 알고리즘도 무너지고, 포트폴리오의 의미가 없다). 그래서 그냥 군대에 있는 2년 동안 아무 주제나 기록하려고 블로그를 하나 더 만들었다. 저 블로그에는 일기장처럼 아마 말 그대로 아무 주제나 생각나는 대로 끄적일 것 같다.&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;이 개발 블로그가 26년의 나에게 보내는 인삿말이라면, 나만 보라고 만든 저 블로그는 그동안의 나에 대한 더 직접적인 기록이 되지 않을까.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;애드센스 수입&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;월 1만 뷰가 나오니깐 수입도 짭짤할까?&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1046&quot; data-origin-height=&quot;219&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cwYzYq/btsIyFlejAI/Z9zQ2dYclmlSO1u8ekDDfK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cwYzYq/btsIyFlejAI/Z9zQ2dYclmlSO1u8ekDDfK/img.png&quot; data-alt=&quot;애드센스 누적 수입... 포기하는 게 편하다&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cwYzYq/btsIyFlejAI/Z9zQ2dYclmlSO1u8ekDDfK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcwYzYq%2FbtsIyFlejAI%2FZ9zQ2dYclmlSO1u8ekDDfK%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; alt=&quot;애드센스 누적 수입&quot; loading=&quot;lazy&quot; width=&quot;1046&quot; height=&quot;219&quot; data-origin-width=&quot;1046&quot; data-origin-height=&quot;219&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;애드센스 누적 수입... 포기하는 게 편하다&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;음&amp;hellip; 적은 돈은 아니겠지만&amp;hellip; 걍 없다고 생각하는 게 좋다.&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;아마 광고 단 지 1년 정도 되었을텐데, 1년 동안 25달러 번 거면 없다고 생각하는 게 더 편하다.&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;/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;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2024-07-13 오전 2.08.50.png&quot; data-origin-width=&quot;1370&quot; data-origin-height=&quot;589&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cskbig/btsIy0WX4sY/loQAW4IBBWuh3U6prbzWEK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cskbig/btsIy0WX4sY/loQAW4IBBWuh3U6prbzWEK/img.png&quot; data-alt=&quot;애드센스 실험실&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cskbig/btsIy0WX4sY/loQAW4IBBWuh3U6prbzWEK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fcskbig%2FbtsIy0WX4sY%2FloQAW4IBBWuh3U6prbzWEK%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; alt=&quot;애드센스 실험실&quot; loading=&quot;lazy&quot; width=&quot;1370&quot; height=&quot;589&quot; data-filename=&quot;스크린샷 2024-07-13 오전 2.08.50.png&quot; data-origin-width=&quot;1370&quot; data-origin-height=&quot;589&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;애드센스 실험실&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그나저나 애드센스에서 실험실 기능이라고, 자동으로 몇몇 광고 기능들을 실험해서 적용해준다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그런데 몇몇 광고는 월에 1달러 더 들어오는 수준이라 사실상 의미는 없는데, 블로그를 보는 입장에서 오히려 더 불편하게 만들어서 조회수를 감소시킬 수도 있다.&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;Vignette ads&lt;/b&gt;, &lt;b&gt;Anchor ads&lt;/b&gt;가 특히 그런데, Vignette 광고는 모바일에서 내 블로그 내 다른 페이지로 이동할 때 중간 페이지에 삽입되는 광고인데 모바일 사용자 입장에서 상당히 기분이 석나가게 해서 오히려 나가게 되는 요인이 되는 것 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Anchor ads는 블로그 하단에 앵커처럼 뜨는 광고다. 근데 이게 PC든 모바일 화면이든 은근히 사이즈가 커서, 읽는데 상당히 방해된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 둘 다 끄는 게 차라리 나은 것 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Banner 광고는... 잘 모르겠다. 걍 냅두고 있는데, 쟤도 글을 읽는데 방해가 되면 걍 지울 생각이다.&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;참고로 본문의 상단, 혹은 하단(더보기 목차 위에 뜨는 광고)는 내가 삽입한 광고가 아니라, &lt;b&gt;Tistory에서 자체적으로 삽입한, 카카오로 수입이 가는 광고다.&lt;/b&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;/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;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;개발 블로그를 써보며 느낀 점들 - 장점편&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;블로그에 대한 회고와 별개로, 블로그를 운영하면서 느낀 점들을 서술하겠다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;글쓰기의 중요성&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사실 &amp;lsquo;개발&amp;rsquo; 블로그의 장점을 이야기하기 전에, 그냥 &amp;lsquo;글을 쓰는 것&amp;rsquo;에 대한 내 생각을 이야기해보고 싶다.&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;AI가 등장하면서 작문 능력의 가치가 폄하되는 것처럼 보인다. 틀렸다고 보긴 어렵다. 이제는 어떤 주제든지 AI에게 던져주면, LLM 모델이 순식간에 잘 정돈된 글을 완성해준다.&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;근데 이건 작문의 결과물인 &amp;lsquo;글&amp;rsquo;에 집중한 거고, 여기서 말하는 건 글을 쓰는 것, &amp;lsquo;글쓰기&amp;rsquo;에 대한 이야기다.&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;/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;어릴 때부터 느꼈던 건데, &amp;lsquo;작문&amp;rsquo; 능력이 좋은 사람들은 대체로 머리가 좋다고 느꼈다(나같은 예외도 존재한다). 작문이란 머릿속에 혼재된 정보와 생각을 정돈하여 글로 직접 표현하는 행위로써, 추상적인 정보를 구체적이고 명시화된 정보로 바꾸는 사고 과정을 거친다. 이 과정에서 애매하게 알고 있던 정보가 명확성과 구체적인 논리성을 띄게 된다.&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;/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;/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;이는 철저히 능동적인 과정이다. 그래서 글쓰기의 결과로 완성된 &amp;lsquo;글&amp;rsquo;과는 별개로, 작문 능력이 중요하다. 작문 능력이 없다면, 수동적인 정보를 뇌 속에 저장할 수 밖에 없고, 그 정보는 나의 것이 아닌 그저 남의 의견, 혹은 파편적인 지식일 뿐이다.&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;요즘같이 AI 딸칵 한 번에 글이 완성되는 시대와는 별개로, 내가 작문 능력이 매우 중요하다고 생각하는 이유이다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;기록의 중요성&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;글쓰기의 결과물인 &amp;lsquo;글&amp;rsquo; 자체도 중요하다.&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;/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;그런데 그런 걸 다 떠나서, 그냥 &amp;lsquo;기록&amp;rsquo; 자체가 중요한 것 같다. 모든 글이 기록 그 자체는 아니겠지만, 그래도 기록의 형태는 띈다.&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;회고글이라면 말할 것도 없고, 그냥 정보글 자체도 그 사람이 그 당시에 무엇에 관심이 있었는 지에 대한 기록이 된다. 어떤 것에 대한 개인의 생각 혹은 &amp;lsquo;나는 이렇게 했다~&amp;rsquo;하는 후기같은 것들이 전부 기록이 된다.&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;/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;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;공부는 확실히 되더라&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;진짜다. 적어도 내가 개발 블로그에 올린 글들은, 엥간하면 1년이 지난 지금까지도 잊어먹지 않고 있다.&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;a href=&quot;https://nx006.tistory.com/32&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;구글은 exception을 어떻게 사용하는 가&lt;/a&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;면접 때 도움이 좀 되지 않을까? 어차피 면접 때 CS 질문을 물어보긴 할테니깐 말이다.&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;/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;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 개발 일지를 작성해야 하는 이유이기도 한 것 같다. 내가 쓴 코드는 언젠가 잊어버린다. 하지만 블로그에 쓴 개발 일지는 영원히 남는다. 적어도 비슷한 코드를 쓰게 되는 날이 올 때, 도움이 되지 않을까?&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;하나의 든든한 자산이라는 안정감&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;요새는 스펙의 시대인 것 같다. 사실 AI가 등장하면서부터 더 심해진 것 같다. 옛날에 나는 코딩 실력이 무엇보다 중요하다고 믿었었다. 컴공 학점 높으면 뭐해. 연구직이나 대학원이라면 모를까, 개발자로 간다는 가정 하에 학점이 아무리 높아봤자 코딩 실력 없으면 말짱 꽝이다. 학점 높고 데이터베이스 수업을 들었는데도, 막상 프로젝트를 하는데 DB 설계 하나 제대로 못 하고, 정규화 하나 제대로 못 하는 사람들을 적지 않게 봤다. 중요한 건 코딩 실력이라 믿었었다(오해하지 말자. 학점이 낮아도 괜찮다는 게 아니라, 학점 vs 코딩 둘 중 하나만 택할 거라면 후자를 택하겠다는 의미다. 당연히 둘 다 챙기려고 공부할 거다).&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;하지만 AI가 등장하면서 생각이 많이 바뀌기는 했다. 이제 AI가 사람보다 코딩을 더 잘한다. 테스트 스위트 코드만 충분하다면, 심지어 HTTP 엔드포인트 방식으로 설계된 코드를 CQRS 방식으로 통째로 바꾸기도 한다. 물론 아직까지는 제한적이고 불완전하지만, 미래의 관점에서 바라본다면, 위험을 대비해야 하는 건 사실이다.&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;/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;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;개발 블로그를 써보며 느낀 점들 - 단점편&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;부담&lt;/h3&gt;
&lt;p 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;/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;그렇다고 글의 질을 떨어뜨리자니, 개인적으로 그건 블로그를 쓰는 의미가 많이 퇴색된다고 생각한다. 특히 대충 쓴 글을 올리는 순간, 그 글들이 앞 페이지를 차지하므로, 정작 내가 보여주고픈 글들이 뒤로 묻히게 된다. 그래서 대충 쓸 수는 없고&amp;hellip;&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;/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;/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;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;AI&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;최근 티스토리가 API 서비스를 중단했다. 여기엔 이런 저런 이유가 있었겠지만, 나는 감히 AI가 하나의 주된 요인으로 작용하지 않았을까 추측해본다.&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;이제는 AI가 순식간에, 블로그 글들 역시 만들어낸다. 주제만 던져주면 딸칵 한 번에 글 하나가 완성되니깐, 요새는 블로그 개설해서 AI가 글 쓰고, 그걸로 돈 버는 사람들도 늘어났다고 한다. 인간이 만들어내는 것과 비교도 안 되는 속도로 블로그에 글을 올리니깐, 광고 수입도 비교도 안 된다고 한다.&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;근데 AI가 이제 문서 더 잘 쓴다. 기술 문서? AI가 코드 API 읽어서, 스스로 만들어주는 단계에 이르렀다.&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;블로그 글과 같은 글들 역시, AI가 순식간에 만들어낸다. 몇 년의 시간이 지난 후에, 테크니컬 라이터라는 직종이 남아있을까 싶다.&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;그럼에도 불구하고 개인 블로그는 없는 것보다는 있는 게 더 좋긴 하겠지만, 이제는 단순히 &amp;lsquo;정보를 정리&amp;rsquo;하는 식의 블로그는 포트폴리오로써의 가치는 떨어질 것 같다.&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;예를 들어 어떤 기술을 직접 프로젝트에 적용해본 경험을 쓰거나, 혹은 후기 등을 쓰거나 하는 식으로, 내 경험을 녹여내지 않는다면, 단순한 정보의 나열은 AI 글에게 너무 쉽게 밀릴 것 같다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;앞으로&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;군대에 있는 동안이야 블로그를 건들지는 못 하겠지만&amp;hellip; 그래도 군대 내에서도 기회가 된다면 틈틈히 개발 공부는 할 것 같다. 상병이나 병장 때는 시간이 좀 남아돈다는데, 그때 감을 유지하기 위해서 조금씩 개발 관련 책들을 읽거나, 싸지방에서 알골 문제들을 풀어보거나 할 생각이다.&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;블로그를 운영하는 데 있어서 아쉬웠던 점은, 너무 정보 위주의 글들만 정리했다는 것이다. 물론 그래도 내가 공부한 것들, 내가 알아낸 것들을 정리하는 글들은 계속 쓸 생각이다(전역 후에&amp;hellip;).&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;/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;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;결국에, 내가 직접 많은 개발을 해보고, 몸으로 부딪혀 본 뒤에 그 경험을 녹여내는 글들이 좋은 글들이고, 내가 써야 할 글들인 것 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;개발 서적에 관한 독후감을 쓸 생각도 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;요새는 영상 매체가 워낙 발달해서 책을 많이 읽지는 않지만, 어릴 때는 책을 나름 많이 읽었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;개발 관련 책들도 많이 읽었는데, 그런 책들에 대한 후기를 써보는 것도 괜찮을 것 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;암튼&amp;hellip; 시간이 지나고 보니 나름 열심히 블로그 관리했구나라는 생각이 든다.&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;옛날에 쓴 글들은, 솔직히 잘 못 읽겠다. 지금 관점에서는 그때는 몰랐던 것들이 보이면서, &amp;lsquo;이거는 이렇게 할 수 있었구나&amp;rsquo;라는 생각이 계속 든다. 그래도 그런 것조차 하나의 기록이자 추억이라 생각해서, 흑역사 보존하는 기분으로 그냥 냅두고 있다&amp;hellip;.&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;/p&gt;</description>
      <category>회고록</category>
      <category>회고록</category>
      <author>nx006</author>
      <guid isPermaLink="true">https://nx006.tistory.com/81</guid>
      <comments>https://nx006.tistory.com/81#entry81comment</comments>
      <pubDate>Sat, 13 Jul 2024 02:19:07 +0900</pubDate>
    </item>
    <item>
      <title>수치해석을 모르면 생기는 사고들</title>
      <link>https://nx006.tistory.com/80</link>
      <description>&lt;!-- Math Jax --&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;
&lt;script type=&quot;text/x-mathjax-config&quot;&gt;
    MathJax.Hub.Config({
      TeX: {
        extensions: [&quot;mathtools.js&quot;],  // cancel 확장 추가
        equationNumbers: { autoNumber: &quot;AMS&quot; }
      },
      tex2jax: {
        inlineMath: [['$', '$'], ['\\(', '\\)']],
        displayMath: [['$$', '$$'], ['\\[', '\\]']],
        processEscapes: true
      },
      &quot;HTML-CSS&quot;: {
        linebreaks: { automatic: true }
      },
      SVG: {
        linebreaks: { automatic: true }
      }
    });
      &lt;/script&gt;
&lt;script type=&quot;text/javascript&quot; src=&quot;https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.7/MathJax.js?config=TeX-AMS-MML_HTMLorMML&quot;&gt;
  &lt;/script&gt;
&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;수치해석을 모르면 푸틴이 미사일을 잘못 쏴요&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우리 학교에는 학생들 사이에서 인기가 많은 교수님이 한 분 계신다. 강의 시간에 뜬금없거나 재밌는 이야기를 많이 해서, 호불호가 갈릴 지언정 취향에 맞는 사람들에게는 인기가 항상 높다(귀엽다는 평가가 많다). 교수님은 수치해석 강의 시간에 농담 삼아서 위 이야기를 자주 말하신다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;갑자기 뜬금없이 미사일 이야기가 나와서, 많은 학생들은 의아해하기도 한다. 갑자기 수치해석에 관한 수업에서 왜 미사일에 관한 이야기가 나오는가?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이를 알기 위해선 한 가지 역사적 사건을 꼭 알아야 한다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;패트리어트 미사일 격추 실패 사건&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;684&quot; data-origin-height=&quot;904&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/64nVG/btsGzZ8fMPG/HFD8vFQTJzadMzcfzefGxK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/64nVG/btsGzZ8fMPG/HFD8vFQTJzadMzcfzefGxK/img.png&quot; data-alt=&quot;미국 회계감사원(GAO)에서 발표한 다란 패트리어트 격추 실패 사건 조사 보고서&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/64nVG/btsGzZ8fMPG/HFD8vFQTJzadMzcfzefGxK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F64nVG%2FbtsGzZ8fMPG%2FHFD8vFQTJzadMzcfzefGxK%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; alt=&quot;미국 회계감사원(GAO)에서 발표한 다란 패트리어트 격추 실패 사건 조사 보고서&quot; loading=&quot;lazy&quot; width=&quot;434&quot; height=&quot;574&quot; data-origin-width=&quot;684&quot; data-origin-height=&quot;904&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;미국 회계감사원(GAO)에서 발표한 다란 패트리어트 격추 실패 사건 조사 보고서&lt;/figcaption&gt;
&lt;/figure&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;때는 1991년 2월 25일, 이라크 전쟁 시기.&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;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다란(Dhahran) 시에 주둔 중인 미군은 저녁 휴식을 준비하던 중이었다.&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;/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;굉음과 화염, 그리고 그로 인한 연쇄 폭발. 기지는 아수라장이 되었고, 이 폭격으로 인해 28명의 미군이 사망하였고, 97명이 부상을 당했다.&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;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;한국에서도 핵심 자산으로 사용되는 이 패트리어트 미사일은, 마하 2의 속도의 미사일 혹은 기타 공중 위협으로부터 기지를 수호할 수 있도록 설계된 지대공 단거리 미사일 방어 체계 시스템이다. 유럽 전역, 한국과 일본, 중동 등에서 널리 쓰이고 있는 무기 체계이다.&lt;/blockquote&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1065&quot; data-origin-height=&quot;1000&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/rbdUh/btsGCv47DmP/IoEvOnTRBnOxZSNeRDsAWK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/rbdUh/btsGCv47DmP/IoEvOnTRBnOxZSNeRDsAWK/img.png&quot; data-alt=&quot;패트리어트 방공 미사일&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/rbdUh/btsGCv47DmP/IoEvOnTRBnOxZSNeRDsAWK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FrbdUh%2FbtsGCv47DmP%2FIoEvOnTRBnOxZSNeRDsAWK%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; alt=&quot;패트리어트 방공 미사일&quot; loading=&quot;lazy&quot; width=&quot;468&quot; height=&quot;439&quot; data-origin-width=&quot;1065&quot; data-origin-height=&quot;1000&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;패트리어트 방공 미사일&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그 명성에도 불구하고, 패트리어트 방공 요격 시스템은 기지로 날아오는 스커드 미사일을 탐지조차 하지 못 했고, 그로 인해 125명의 사상자가 발생하는 비극을 겪어야만 했다. 어떻게 된 일일까?&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;패트리어트 레이더 시스템&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1496&quot; data-origin-height=&quot;1000&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/byCCKC/btsGCblzH4S/5gENI984BdjtSkaQSMdcA1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/byCCKC/btsGCblzH4S/5gENI984BdjtSkaQSMdcA1/img.png&quot; data-alt=&quot;정상적인 경우 패트리어트 레이더 시스템의 작동 과정&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/byCCKC/btsGCblzH4S/5gENI984BdjtSkaQSMdcA1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbyCCKC%2FbtsGCblzH4S%2F5gENI984BdjtSkaQSMdcA1%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; alt=&quot;정상적인 경우 패트리어트 레이더 시스템의 작동 과정&quot; loading=&quot;lazy&quot; width=&quot;1496&quot; height=&quot;1000&quot; data-origin-width=&quot;1496&quot; data-origin-height=&quot;1000&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;정상적인 경우 패트리어트 레이더 시스템의 작동 과정&lt;/figcaption&gt;
&lt;/figure&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;/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;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;Search Action(검색)&lt;/b&gt; - 비행체(목표물, 여기서는 스커드 미사일)의 속도, 위치, 방위각 등을 파악하여 적대적 물체(미사일)인 지 판단한다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Validation Action(평가)&lt;/b&gt; - 목표물의 속도, 위치 등을 검증한다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Track Action(추적)&lt;/b&gt; - 목표물이 날아올 위치를 예측하여 &lt;u&gt;&lt;b&gt;레인지 게이트 범위(RGA)&lt;/b&gt;&lt;/u&gt;를 설정, 이 범위에 들어온 비행체를 추적한다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이후에 실제 비행체를 격추하는 요격 행동으로 이어진다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이때 &lt;u&gt;&lt;b&gt;RGA&lt;/b&gt;는 실제 미사일이 날아왔을 때의 예상 위치 범위&lt;/u&gt;로, 이 범위에서 목표물을 추적하여 요격 행동에 나서게 된다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;레이더 시스템의 오류&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imagegridblock&quot;&gt;
  &lt;div class=&quot;image-container&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bflXdZ/btsGBcFH0lQ/0UetAx0HKhTEBwtLb5heKK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bflXdZ/btsGBcFH0lQ/0UetAx0HKhTEBwtLb5heKK/img.png&quot; width=&quot;100%&quot; data-origin-width=&quot;1549&quot; data-origin-height=&quot;1000&quot; data-is-animation=&quot;false&quot; style=&quot;width: 48.7484%; margin-right: 10px;&quot; data-widthpercent=&quot;49.32&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bflXdZ/btsGBcFH0lQ/0UetAx0HKhTEBwtLb5heKK/img.png&quot; alt=&quot;패트리어트 레이더 시스템의 오류 1&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbflXdZ%2FbtsGBcFH0lQ%2F0UetAx0HKhTEBwtLb5heKK%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;1549&quot; height=&quot;1000&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/beiI8x/btsGCaAclNV/Lcatt6QIm4wXjPCc6qzSKk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/beiI8x/btsGCaAclNV/Lcatt6QIm4wXjPCc6qzSKk/img.png&quot; width=&quot;100%&quot; data-origin-width=&quot;1590&quot; data-origin-height=&quot;999&quot; data-is-animation=&quot;false&quot; style=&quot;width: 50.0888%;&quot; data-widthpercent=&quot;50.68&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/beiI8x/btsGCaAclNV/Lcatt6QIm4wXjPCc6qzSKk/img.png&quot; alt=&quot;패트리어트 레이더 시스템의 오류&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbeiI8x%2FbtsGCaAclNV%2FLcatt6QIm4wXjPCc6qzSKk%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;1590&quot; height=&quot;999&quot;/&gt;&lt;/span&gt;&lt;/div&gt;
  &lt;figcaption&gt;패트리어트 레이더 시스템의 오류(우측 그림에서 RGA가 이동하여 표적과 전혀 다른 곳을 추적하고 있음을 알 수 있다)&lt;/figcaption&gt;
&lt;/figure&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;문제가 발생한 곳은 3번 추적 단계였다. 당시 패트리어트 레이더의 레인지 게이트 범위는 엉뚱한 곳을 가르키고 있었다. 당시 패트리어트는 스커드 미사일로부터 687m 떨어진 곳의 빈 하늘을 RGA로 설정하여 추적하고 있었다. 스커드 미사일은 프리패스로 방공망을 뚫고 나아갔다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imagegridblock&quot;&gt;
  &lt;div class=&quot;image-container&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/d9idIu/btsGALaEhGk/TEuwSnkk0ae52RDD7rlaY0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/d9idIu/btsGALaEhGk/TEuwSnkk0ae52RDD7rlaY0/img.png&quot; width=&quot;100%&quot; data-origin-width=&quot;3314&quot; data-origin-height=&quot;1000&quot; data-is-animation=&quot;false&quot; style=&quot;width: 57.3938%; margin-right: 10px;&quot; data-widthpercent=&quot;58.07&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/d9idIu/btsGALaEhGk/TEuwSnkk0ae52RDD7rlaY0/img.png&quot; alt=&quot;사건 보고서 발췌&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fd9idIu%2FbtsGALaEhGk%2FTEuwSnkk0ae52RDD7rlaY0%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;3314&quot; height=&quot;1000&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Mh2xY/btsGBQhTjGi/5kpaltISGXsn31l8FMcKp0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Mh2xY/btsGBQhTjGi/5kpaltISGXsn31l8FMcKp0/img.png&quot; width=&quot;100%&quot; data-origin-width=&quot;2393&quot; data-origin-height=&quot;1000&quot; data-is-animation=&quot;false&quot; style=&quot;width: 41.4434%;&quot; data-widthpercent=&quot;41.93&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Mh2xY/btsGBQhTjGi/5kpaltISGXsn31l8FMcKp0/img.png&quot; alt=&quot;사건 보고서 발췌&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FMh2xY%2FbtsGBQhTjGi%2F5kpaltISGXsn31l8FMcKp0%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;2393&quot; height=&quot;1000&quot;/&gt;&lt;/span&gt;&lt;/div&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 당시 패트리어트 시스템에는 심각한 소프트웨어 오류가 있었다. &lt;u&gt;패트리어트 시스템을 8시간 연속 가동하면 RGA가 정상 위치에서 20%씩 이동하는&lt;/u&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;이러한 RGA의 위치 변화는 곧 &lt;b&gt;RGA의 정가운데에 목표물(스커드 미사일)이 위치하지 않음&lt;/b&gt;을 의미한다. 목표물이 RGA의 가운데에 있어야 요격 가능성이 높아지는 데, 목표물이 이 범위의 가운데에 있지 않다는 것은 다시 말해 그만큼 &lt;b&gt;요격 가능성이 떨어진다는 것&lt;/b&gt;을 의미했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약 &lt;b&gt;RGA가 50% 이상 이동한다면, 패트리어트 시스템은 스커드 미사일을 더 이상 추적하지 못 한다&lt;/b&gt;. 패트리어트 시스템이 8시간 연속 가동할 때마다 20% 이동하므로, 20시간을 가동하면 50%가 이동한다. 즉 &lt;b&gt;20시간 연속으로 가동하게 된다면 패트리어트 시스템은 불능이 된다&lt;/b&gt;는 다소 황당한 이야기이다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;640&quot; data-origin-height=&quot;403&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cTLIwN/btsGBORRICW/vcikkT8zEGDCik3DFvRk2K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cTLIwN/btsGBORRICW/vcikkT8zEGDCik3DFvRk2K/img.png&quot; data-alt=&quot;놀랍게도 패트리어트 미사일도 하루 8시간 노동을 주장한다&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cTLIwN/btsGBORRICW/vcikkT8zEGDCik3DFvRk2K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcTLIwN%2FbtsGBORRICW%2FvcikkT8zEGDCik3DFvRk2K%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; alt=&quot;놀랍게도 패트리어트 미사일도 하루 8시간 노동을 주장한다&quot; loading=&quot;lazy&quot; width=&quot;640&quot; height=&quot;403&quot; data-origin-width=&quot;640&quot; data-origin-height=&quot;403&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;놀랍게도 패트리어트 미사일도 하루 8시간 노동을 주장한다&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그렇다면 사건이 발생한 2월 25일, 패트리어트는 얼마나 오래 가동되고 있었을까?&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;u&gt;100시간 연속&lt;/u&gt;으로 가동되고 있었다. 그 결과 RGA는 빈 허공을 가르고 있었고, 비극이 발생했다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;문제의 원인&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;당시 패트리어트 시스템은 내부적으로 1/10초마다 1/10을 곱하는 연산을 수행했다.&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;이 계산은 24비트 고정 소수점 레지스터(24-bits Fixed Point Register)를 통해 수행된다.&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;1/10을 이진수로 바꾸면 $\frac{1}{24}+\frac{1}{25}+\frac{1}{28}+\frac{1}{29}+\frac{1}{212}+\frac{1}{213} + \cdots$ 으로 &lt;code&gt;0.0001 1001 1001 1001 1001 1001 1001 100...&lt;/code&gt; 이 된다. 24비트에 맞춰야 하므로 25비트 이하 소수점을 버리면 &lt;code&gt;0.0001 1001 1001 1001 1001 100&lt;/code&gt;이 된다.&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;  0.0001 1001 1001 1001 1001 1001 1001 100...
- 0.0001 1001 1001 1001 1001 100
---------------------------------------------
  0.0000 0000 0000 0000 0000 0001 1001 100...&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;곧 0.1초마다 &lt;code&gt;0.0000 0000 0000 0000 0000 0001 1001 100...&lt;/code&gt;만큼의 오차가 발생한다. 이를 10진수로 표현하면 &lt;code&gt;0.0000 0009 5&lt;/code&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;따라서 1시간 동안 이 오차가 누적된다고 하면, &lt;code&gt;0.000000095 &amp;times; 60(minutes) &amp;times; 60(secs) &amp;times; 10(operations per seconds) = 0.0034&lt;/code&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;0.0034초라는 시간은 인간에게 있어서는 찰나의 시간이다. 하지만 상대가 마하 5, 초속 1.7km의 속력을 가지는 스커드 미사일이라면 어떨까? 스커드 미사일에 있어서 이 시간은 &lt;b&gt;7m를 이동할 수 있는 시간&lt;/b&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;만약 20시간 연속 시스템을 가동하게 된다면, 누적 시간 오차는 0.0687초로 &lt;b&gt;RGA는 목표물로부터 137m 떨어진 공간을 가리키게 된다.&lt;/b&gt; 이때는 이미 목표물이 RGA 범위 자체를 벗어나게 되므로, 20시간이 지나게 된다면 목표물 추적이 아예 불가능해지는 것이다.&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;다란의 패트리어트는 100시간 연속 가동 중이었고, 0.34초의 오차가 발생하게 되었다. 이때의 &lt;b&gt;RGA는 목표물로부터 무려 687m 벗어나 있었다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;table style=&quot;height: 138px;&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style5&quot;&gt;
&lt;thead&gt;
&lt;tr style=&quot;height: 19px;&quot;&gt;
&lt;th style=&quot;height: 19px;&quot;&gt;Hours&lt;/th&gt;
&lt;th style=&quot;height: 19px;&quot;&gt;Seconds&lt;/th&gt;
&lt;th style=&quot;height: 19px;&quot;&gt;Calculated Time (Secs)&lt;/th&gt;
&lt;th style=&quot;height: 19px;&quot;&gt;Inaccuracy (Secs)&lt;/th&gt;
&lt;th style=&quot;height: 19px;&quot;&gt;Approximate Shift In Range Gate (Meters)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;height: 17px;&quot;&gt;0&lt;/td&gt;
&lt;td style=&quot;height: 17px;&quot;&gt;0&lt;/td&gt;
&lt;td style=&quot;height: 17px;&quot;&gt;0&lt;/td&gt;
&lt;td style=&quot;height: 17px;&quot;&gt;0&lt;/td&gt;
&lt;td style=&quot;height: 17px;&quot;&gt;0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;height: 17px;&quot;&gt;1&lt;/td&gt;
&lt;td style=&quot;height: 17px;&quot;&gt;3600&lt;/td&gt;
&lt;td style=&quot;height: 17px;&quot;&gt;3599.9966&lt;/td&gt;
&lt;td style=&quot;height: 17px;&quot;&gt;.0034&lt;/td&gt;
&lt;td style=&quot;height: 17px;&quot;&gt;7&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;height: 17px;&quot;&gt;8&lt;/td&gt;
&lt;td style=&quot;height: 17px;&quot;&gt;28800&lt;/td&gt;
&lt;td style=&quot;height: 17px;&quot;&gt;28799.9725&lt;/td&gt;
&lt;td style=&quot;height: 17px;&quot;&gt;.0275&lt;/td&gt;
&lt;td style=&quot;height: 17px;&quot;&gt;55&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;height: 17px;&quot;&gt;20&lt;/td&gt;
&lt;td style=&quot;height: 17px;&quot;&gt;72000&lt;/td&gt;
&lt;td style=&quot;height: 17px;&quot;&gt;71999.9313&lt;/td&gt;
&lt;td style=&quot;height: 17px;&quot;&gt;.0687&lt;/td&gt;
&lt;td style=&quot;height: 17px;&quot;&gt;137&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;height: 17px;&quot;&gt;48&lt;/td&gt;
&lt;td style=&quot;height: 17px;&quot;&gt;172800&lt;/td&gt;
&lt;td style=&quot;height: 17px;&quot;&gt;172799.8352&lt;/td&gt;
&lt;td style=&quot;height: 17px;&quot;&gt;.1648&lt;/td&gt;
&lt;td style=&quot;height: 17px;&quot;&gt;330&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;height: 17px;&quot;&gt;72&lt;/td&gt;
&lt;td style=&quot;height: 17px;&quot;&gt;259200&lt;/td&gt;
&lt;td style=&quot;height: 17px;&quot;&gt;259199.7528&lt;/td&gt;
&lt;td style=&quot;height: 17px;&quot;&gt;.2472&lt;/td&gt;
&lt;td style=&quot;height: 17px;&quot;&gt;494&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;height: 17px;&quot;&gt;100&lt;/td&gt;
&lt;td style=&quot;height: 17px;&quot;&gt;360000&lt;/td&gt;
&lt;td style=&quot;height: 17px;&quot;&gt;359999.6667&lt;/td&gt;
&lt;td style=&quot;height: 17px;&quot;&gt;.3433&lt;/td&gt;
&lt;td style=&quot;height: 17px;&quot;&gt;687&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;아쉬웠던 대응, 막을 수 있었던 사고&lt;/h3&gt;
&lt;p 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;사고는 1991년 2월 25일에 발생하였다. 그보다 2주 전인 &lt;i&gt;&lt;b&gt;2월 11일&lt;/b&gt;&lt;/i&gt;, 패트리어트 프로젝트 팀은 이스라엘 군이 넘겨진 시스템 로그를 분석했을 때 이미 RGA가 이동하는 문제를 발견했다.&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;i&gt;&lt;b&gt;2월 16일&lt;/b&gt;&lt;/i&gt;, 버그를 확인한 지 5일 만에 개발 팀은 해당 오류를 수정한 소프트웨어를 배포하기 시작했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;문제는 당시의 소프트웨어 수정과 배포가 지금처럼 물 흐르듯 빠르게 진행되지 못 했다는 점이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;지금이야 웹 개발자들의 입장에선 소프트웨어 배포는 CI/CD 파이프라인을 통해 &amp;lsquo;딸깍&amp;rsquo; 한 번에 끝난다. 데브옵스 개발자들이 만들어놓은 파이프라인과, 클라우드를 통해서 전 세계에 한 번에 소프트웨어를 배포시킬 수 있지만&amp;hellip;&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;하지만 당시는 1991년. 미션은 네트워크에 연결되어 있지도 않은 전 세계에 흩어져 있는 군사 장비에 최대한 빠르게 수정된 소프트웨어 패키지를 설치해야 한다. 당시에 소프트웨어 패키지를 설치하려면 배나 비행기로 SW가 담긴 장비를 실어날라야 했다. 당연히 시간이 오래 걸릴 수 밖에 없었고, 패트리어트 프로젝트 팀은 시간과의 싸움에 직면했다.&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;i&gt;&lt;b&gt;2월 21일&lt;/b&gt;&lt;/i&gt;, 모든 패트리어트를 운용하는 부대에 장시간에 걸쳐 패트리어트를 운용하게 되면 RGA가 이동하게 되어 목표물 추적에 실패할 수 있음을 경고하였다. 동시에 이 버그를 해결한 소프트웨어 패키지를 배포 중이라는 메시지를 보냈다. 문제는 여기서 얼마나 &amp;lsquo;&lt;b&gt;오래&lt;/b&gt;&amp;rsquo; 패트리어트를 운용하게 되면 버그가 발생하는 지 언급하지 않았다는 점이다.&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;당시 다란에 있던 미군 역시 이 메시지를 받았지만, 얼마나 오래 패트리어트를 켜놔도 되는 지 그들은 알지 못 했다. 다란 기지 방어를 맡은 패트리어트 포대는 두 포대(각각 알파 포대와 브라보 포대)였는데, 이 중 브라보 포대는 레이더 문제를 해결하기 위해 가동을 중지했고 알파 포대만이 가동 중이었다. 문제는 이 알파 포대의 패트리어트 시스템이 100시간 연속해서 가동 중이었고, 그로 인해 비극이 발생했다.&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;i&gt;&lt;b&gt;2월 26일&lt;/b&gt;&lt;/i&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;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;시간 순서에 따른 사건 요약&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;2월 11일&lt;/b&gt; - 패트리어트 프로젝트 팀이 RGA가 이동하는 소프트웨어 오류 발견, 즉시 버그 수정 착수&lt;/li&gt;
&lt;li&gt;&lt;b&gt;2월 16일&lt;/b&gt; - 버그 수정 성공, 새로운 소프트웨어 패키지 배포 시작&lt;/li&gt;
&lt;li&gt;&lt;b&gt;2월 21일&lt;/b&gt; - 전세계 패트리어트 부대에 버그의 존재를 알리고 장기적인 패트리어트 시스템 가동에 대해서 경고, 그러나 &lt;u&gt;구체적인 한계 시간을 명시하지는 않음&lt;/u&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;2월 25일&lt;/b&gt; - 다란에서 사건 발생, 패트리어트의 스커드 미사일 격추 실패로 28명의 미군 사망&lt;/li&gt;
&lt;li&gt;&lt;b&gt;2월 26일&lt;/b&gt; - 다란에 버그가 수정된 소프트웨어 패키지 도착&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;아리안5 로켓 폭발 사고&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;패트리어트 미사일 격추 실패 사건 외에, 한 가지 더 소수점에 관한 유명한 사고를 살펴보자. 바로 아리안5 로켓 폭발 사고이다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;968&quot; data-origin-height=&quot;1998&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/mSat9/btsGBtf8NqJ/FikKpmYikOZbNmqlVmwI0K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/mSat9/btsGBtf8NqJ/FikKpmYikOZbNmqlVmwI0K/img.png&quot; data-alt=&quot;아리안5 로켓, 사진 출처: 위키피디아&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/mSat9/btsGBtf8NqJ/FikKpmYikOZbNmqlVmwI0K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FmSat9%2FbtsGBtf8NqJ%2FFikKpmYikOZbNmqlVmwI0K%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; alt=&quot;아리안5 로켓&quot; loading=&quot;lazy&quot; width=&quot;395&quot; height=&quot;815&quot; data-origin-width=&quot;968&quot; data-origin-height=&quot;1998&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;아리안5 로켓, 사진 출처: 위키피디아&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;유럽우주국 ESA(European Space Agency)에서 만든 &lt;b&gt;아리안5 로켓&lt;/b&gt;은 아리안스페이스(Ariane Space)를 대표하는 로켓이다. 1996년부터 2023년까지 운용되어 총 117번의 발사를 마쳤으며, 그 중 112번의 발사에 성공했다. 특히 2003년부터는 100번 연속 발사에 성공하여 놀라운 안전성을 입증했으며, 2021년 12월 25일 JWST의 발사에도 아리안5 로켓이 사용되었다.&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;하지만 이러한 모습의 아리안5가 있기까지, ESA는 초창기에 여러 시행착오를 겪어야만 했다. 그중 아리안5의 첫 번째 발사이자 실패인 &lt;b&gt;아리안5 - 501편 폭발 사고&lt;/b&gt;를 소개하겠다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;아리안5 로켓의 폭발과 오버플로우 오류&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;289&quot; data-origin-height=&quot;117&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/c2TNy7/btsGCtTOFpv/hMekf49pYXKlD56m4kImO1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/c2TNy7/btsGCtTOFpv/hMekf49pYXKlD56m4kImO1/img.png&quot; data-alt=&quot;아리안5 폭발 사고 장면&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/c2TNy7/btsGCtTOFpv/hMekf49pYXKlD56m4kImO1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fc2TNy7%2FbtsGCtTOFpv%2FhMekf49pYXKlD56m4kImO1%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; alt=&quot;아리안5 폭발 사고 장면&quot; loading=&quot;lazy&quot; width=&quot;289&quot; height=&quot;117&quot; data-origin-width=&quot;289&quot; data-origin-height=&quot;117&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;아리안5 폭발 사고 장면&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1996년 6월 4일, 현지 시각 9시 33분 59초, 프랑스령 기아나, 아리안5 - 501편 로켓이 발사되었다. 발사 후 36초 후 로켓은 자세 제어 능력을 상실하여, 몸체가 기운 후 폭발하였다.&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;ESA는 이후 조사에서 이 사고의 원인이, 어이없게도 &lt;u&gt;&lt;b&gt;64bits 실수형에서 16bits 정수형으로의 형 변환 과정에서 일어난 오버플로우&lt;/b&gt;&lt;/u&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;즉 8바이트 &lt;code&gt;double&lt;/code&gt; 형에서 2바이트 &lt;code&gt;short&lt;/code&gt; 형으로의 변환 과정에서 일어난 오버플로우 때문에 문제가 발생하였다(C언어로 쉽게 따지자면).&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;현대에서는, 만약 C/C++ 언어로 &lt;code&gt;double&lt;/code&gt;에서 &lt;code&gt;short&lt;/code&gt;로 type cast를 시도하려 한다면, 컴파일러 경고가 뜨면서 오버플로우 오류가 날 수 있음을 프로그래머가 알 수 있게 해줄 것이다. 하지만 이때 발생한 사고는 그리 단순한 문제는 아니었다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;소스 코드 분석&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;당시 문제가 발생했던 소스 코드를 살펴보자. 이 소스 코드는 아리안5에 탑재된 &lt;b&gt;&lt;a href=&quot;https://skybrary.aero/articles/inertial-reference-system-irs&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;SRI&lt;/a&gt;(Inertial Reference System,&lt;/b&gt; 또는 IRS라 한다&lt;b&gt;)&lt;/b&gt;라는 모듈에 사용되는 코드로, SRI는 로켓의 현재 고도와 속도를 측정한다. 소스 코드는 &lt;a href=&quot;https://ko.wikipedia.org/wiki/%EC%97%90%EC%9D%B4%EB%8B%A4_(%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%B0%8D_%EC%96%B8%EC%96%B4)&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Ada 언어&lt;/a&gt;로 작성되어 있다.&lt;/p&gt;
&lt;pre class=&quot;ada&quot;&gt;&lt;code&gt;L_M_BV_32 := TBD.T_ENTIER_32S ((1.0/C_M_LSB_BV) BV) * G_M_INFO_DERIVE(T_ALG.E_BV));

if L_M_BV_32 &amp;gt; 32767 then
        P_M_DERIVE(T_ALG.E_BV) := 16#7FFF#;
elsif L_M_BV_32 &amp;lt; -32768 then
        P_M_DERIVE(T_ALG.E_BV) := 16#8000#;
else
        P_M_DERIVE(T_ALG.E_BV) := UC_16S_EN_16NS(TDB.T_ENTIER_16S(L_M_BV_32));
end if;

P_M_DERIVE(T_ALG.E_BV) := UC_16S_EN_16NS (TDB.T_ENTIER_16S((1.0/C_M_LSB_BH) * G_M_INFO_DERIVE(T_ALG.E_BV)));&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이를 우리에게 익숙한 C언어로 바꾸어보자.&lt;/p&gt;
&lt;pre class=&quot;c arduino&quot; data-ke-language=&quot;c&quot;&gt;&lt;code&gt;int32_t L_M_BV_32 = (int32_t)((1.0 / C_M_LSB_BV) * G_M_INFO_DERIVE(T_ALG.E_BV));

if (L_M_BV_32 &amp;gt; 32767) {
    P_M_DERIVE(T_ALG.E_BV) = 0x7FFF; 
} else if (L_M_BV_32 &amp;lt; -32768) {
    P_M_DERIVE(T_ALG.E_BV) = 0x8000; 
} else {
    P_M_DERIVE(T_ALG.E_BV) = UC_16S_EN_16NS((uint16_t)L_M_BV_32); 
}

P_M_DERIVE(T_ALG.E_BV) = UC_16S_EN_16NS((uint16_t)((1.0 / C_M_LSB_BH) * G_M_INFO_DERIVE(T_ALG.E_BV)));&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;오버플로우 문제가 발생한 곳은 &lt;b&gt;마지막 줄&lt;/b&gt;이다. 바로 위에서는 &lt;code&gt;if-else&lt;/code&gt; 구문으로 오버플로우에 대한 에러 처리를 하고 있다. 그러나 마지막 줄에서는 &lt;b&gt;예외 처리를 하지 않고, 64bits 실수형에서 16bits 정수형으로 바로 변환하면서 문제가 발생했다.&lt;/b&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;조사위원회는 이러한 연산자 변환 오류의 위험을 앉고 있는 변수를 7개 발견하였고, 이중 4개는 &lt;code&gt;if - else&lt;/code&gt;에 의한 예외 처리로 보호되어 있지만 3개는 보호되지 않았음을 확인했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;프로그래머가 if-else문을 작성하는 것을 까먹었을까? 아니다.&lt;/blockquote&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;SRI 모듈의 목표 CPU 최대 부하치가 80%에 맞춰져 있었기 때문이다.&lt;/b&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;만약에 모든 구문을 if-else문으로 감싸게 된다면, 최대 CPU 부하치가 80%를 넘길 수 있었다. 그렇기에 기능 명세를 맞추기 위해서 일부 if-else 문을 제외할 수 밖에 없었고, 이로 인해 사고가 발생하였다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;아리안5의 구조&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 사고가 발생하게 된 더 근본적인 원인이 하나 더 있다. 이를 알기 위해서는 아리안5 로켓의 구조와, 자세한 사고 경위에 대해서 알아야 한다.&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;아리안5는 기본적으로 아리안4 로켓을 계승하기 위해 만들어진 로켓이다. 완전히 새롭게 설계하기는 했지만, 일부 부품은 아리안4 로켓의 그것을 재활용했다. 그 중 하나가 바로 &lt;b&gt;SRI&lt;/b&gt;이다. 아리안4 로켓 역시 116번의 발사 중 113번을 성공했을 정도로 매우 신뢰성 높은 로켓이었기에, 아리안4의 SRI 모듈을 재활용하는 것은 경제적으로 현명한 선택이었을 것이다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1481&quot; data-origin-height=&quot;1000&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bnKaM9/btsGB0R7drA/kva0xWw6BGNRHaycafcIRk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bnKaM9/btsGB0R7drA/kva0xWw6BGNRHaycafcIRk/img.png&quot; data-alt=&quot;SRI는 OBC에게 가속도, 각속도, 고도 등을 전송한다.&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bnKaM9/btsGB0R7drA/kva0xWw6BGNRHaycafcIRk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbnKaM9%2FbtsGB0R7drA%2Fkva0xWw6BGNRHaycafcIRk%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; alt=&quot;SRI의 역할&quot; loading=&quot;lazy&quot; width=&quot;1481&quot; height=&quot;1000&quot; data-origin-width=&quot;1481&quot; data-origin-height=&quot;1000&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;SRI는 OBC에게 가속도, 각속도, 고도 등을 전송한다.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아리안5는 두 대의 SRI을 탑재하고 있다. 하나는 메인으로 사용되고, 다른 하나는 메인으로 쓰이는 SRI가 정상 작동하지 않을 경우를 대비해서 예비용으로 사용된다. SRI는 매우 중요한 부품이기에 예비용 SRI는 메인 SRI가 정지할 경우 즉각 가동할 수 있도록, &lt;b&gt;hot-standby&lt;/b&gt; 모드로 가동 중이었고, &lt;b&gt;이를 위해 두 대의 SRI는 동일한 데이터를 입력받고 있었다.&lt;/b&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;SRI의 제어는 &lt;b&gt;OBC(On Board Computer)&lt;/b&gt;가 제어한다. OBC는 SRI가 넘겨주는 데이터를 바탕으로 엔진 노즐의 각도와 추력을 조절해 비행 궤도를 유지한다. 이러한 OBC 역시 이중화되어 있었다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;사고의 발생 과정&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사고의 자세한 발생 과정을 추적하겠다. $H_0$는 발사 시각을 의미한다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;$H_0 - 3$ 초: SRI가 Alignment 모드에서 Flight 모드로 전환.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;이때부터 이미 SRI의 소프트웨어는 오작동하기 시작했다.&lt;/li&gt;
&lt;li&gt;Flight 모드로 전환된 후에도, 작동하지 말아야 할 Alignment 모드의 일부 기능들이 계속해서 작동했기 때문이다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;$H_0 + 36.672$초: SRI-2의 동작 정지.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;16bits 정수형을 사용하는 SRI-2가 64bits 실수형 데이터를 입력받으면서, 오버플로우 에러로 인해 SRI-2가 동작을 정지하였다.&lt;/li&gt;
&lt;li&gt;이 문제는 Flight 모드의 소프트웨어에서 발생한 것이 아니라, 실행되지 않았어야 하는 Alignment 모드의 소프트웨어에서 발생되었다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;$H_0 + 36.744$초: OBC가 SRI-1으로의 전환 시도 - &lt;i&gt;실패&lt;/i&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;OBC가 SRI-2로부터 데이터를 받지 못하자, 즉시 SRI-1으로의 전환을 시도했다.&lt;/li&gt;
&lt;li&gt;문제는&amp;hellip; SRI 둘 다 똑같은 SW를 쓰는데, hot-standby를 위해 &lt;b&gt;둘 다 똑같은 데이터를 입력받는다&lt;/b&gt;. 당연히 SRI-1 역시 동일한 64bits 실수형 데이터를 입력받고 오버플로우 문제로 작동을 정지한 상태였다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;$H_0+37.032$초: OBC는 SRI로부터 정상적인 데이터를 받지 못 하자 엔진 노즐을 정상적으로 컨트롤하지 못 하고, 기체가 기울기 시작했다.&lt;/li&gt;
&lt;li&gt;$H_0 + 39.3$초: OBC-1의 기능 중지. 시스템은 곧바로 OBC-2로 전환
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;이때 이미 로켓이 기울어, 부하를 견디지 못한 로켓에 손상이 가기 시작했다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;$H_0 +40.45$초: 로켓 부스터 1기 소실. 로켓이 파괴되기 시작&lt;/li&gt;
&lt;li&gt;$H_0 + 40.55$초: 로켓이 화염에 휩싸임.&lt;/li&gt;
&lt;li&gt;$H_0+66$초: 관제소는 임무의 실패를 판단, 자폭 명령 송신. 로켓은 자폭 명령 수신 후 자폭.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;902&quot; data-origin-height=&quot;1342&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/vMYsM/btsGA0rPZYU/Z8Urjz1orHZziN3NRGVvUK/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/vMYsM/btsGA0rPZYU/Z8Urjz1orHZziN3NRGVvUK/img.jpg&quot; data-alt=&quot;한 짤 요약 가능하다.&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/vMYsM/btsGA0rPZYU/Z8Urjz1orHZziN3NRGVvUK/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FvMYsM%2FbtsGA0rPZYU%2FZ8Urjz1orHZziN3NRGVvUK%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;465&quot; height=&quot;692&quot; data-origin-width=&quot;902&quot; data-origin-height=&quot;1342&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;한 짤 요약 가능하다.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imagegridblock&quot;&gt;
  &lt;div class=&quot;image-container&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/T8JXa/btsGAzIbFnq/2e20UlMW27qNFwViZNAgAK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/T8JXa/btsGAzIbFnq/2e20UlMW27qNFwViZNAgAK/img.png&quot; width=&quot;100%&quot; data-origin-width=&quot;3102&quot; data-origin-height=&quot;2178&quot; data-is-animation=&quot;false&quot; style=&quot;width: 49.4186%; margin-right: 10px;&quot; data-widthpercent=&quot;50&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/T8JXa/btsGAzIbFnq/2e20UlMW27qNFwViZNAgAK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FT8JXa%2FbtsGAzIbFnq%2F2e20UlMW27qNFwViZNAgAK%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;3102&quot; height=&quot;2178&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/sZWxK/btsGAzVIKBW/a5bL8afyolXCwSGmKiYkZ0/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/sZWxK/btsGAzVIKBW/a5bL8afyolXCwSGmKiYkZ0/img.jpg&quot; width=&quot;100%&quot; data-origin-width=&quot;3102&quot; data-origin-height=&quot;2178&quot; data-is-animation=&quot;false&quot; style=&quot;width: 49.4186%;&quot; data-widthpercent=&quot;50&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/sZWxK/btsGAzVIKBW/a5bL8afyolXCwSGmKiYkZ0/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FsZWxK%2FbtsGAzVIKBW%2Fa5bL8afyolXCwSGmKiYkZ0%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;3102&quot; height=&quot;2178&quot;/&gt;&lt;/span&gt;&lt;/div&gt;
  &lt;figcaption&gt;좌: 사고 발생 과정 요약 / 우: 주요 사건 발생 과정(36~37초 대 SRI 모듈의 정지)&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;두 번째 원인 - 명세 실패와 테스트 실패&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;결론적으로 아리안4 로켓의 SRI 모듈을 아리안5 로켓에 어거지로 우겨넣은 게 문제의 시작이었다.&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;ESA는 이를 &lt;u&gt;&lt;b&gt;SRI 시스템 명세서의 인터페이스 부문 명세의 실패&lt;/b&gt;&lt;/u&gt;라 지적하였다. SRI 시스템 명세서에는 아리안4 외의 사용에 대한 어떤 제약 사항도 명시해놓고 있지 않았다.&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;u&gt;&lt;b&gt;테스트 부족&lt;/b&gt;&lt;/u&gt;에 대해서도 문제가 많았다. SRI에 대해 카운트다운, 비행, 궤도 진입에 대한 테스트를 실시하지 않았던 것이 밝혀졌다. 이때 시뮬레이션을 통해서 테스트를 했었더라면, SRI에서 발생하고 있는 문제점들을 미리 파악할 수 있었을 것이란 지적이다.&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;그러나 당시 ESA는 SRI이라는 모듈 자체에 대해, 이미 아리안4에서 안정성이 검증되었으므로 기기 자체가 안전할 것이라 믿었다. 아리안4에서 잘 작동했으므로 아리안5에서도 잘 작동하리라 막연히 생각할 뿐이었다.&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;실제로 SRI에 대해 테스트한 부분은 아리안5의 OBC와 잘 연결되는 지, 호환성 부분만 검증되었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;결론적으로 이 문제의 근본적인 시발점은, 호환되지도 않는 SRI를 아리안5 로켓에 아무런 테스트 없이 재사용했다는 것이다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;사고 이후 ESA의 대응&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imagegridblock&quot;&gt;
  &lt;div class=&quot;image-container&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/SshYd/btsGBYUjXAt/pIAzpJxZGkKHYzTsek99TK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/SshYd/btsGBYUjXAt/pIAzpJxZGkKHYzTsek99TK/img.png&quot; width=&quot;100%&quot; data-origin-width=&quot;663&quot; data-origin-height=&quot;835&quot; data-is-animation=&quot;false&quot; style=&quot;width: 42.0896%; margin-right: 10px;&quot; data-widthpercent=&quot;42.58&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/SshYd/btsGBYUjXAt/pIAzpJxZGkKHYzTsek99TK/img.png&quot; alt=&quot;사고 조사 보고서에서 발췌한 대응 방안&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FSshYd%2FbtsGBYUjXAt%2FpIAzpJxZGkKHYzTsek99TK%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;663&quot; height=&quot;835&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/6a3Z9/btsGB2vzNMF/czM0DqUCK6Ta24MFAhjw0K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/6a3Z9/btsGB2vzNMF/czM0DqUCK6Ta24MFAhjw0K/img.png&quot; width=&quot;100%&quot; data-origin-width=&quot;683&quot; data-origin-height=&quot;638&quot; data-is-animation=&quot;false&quot; style=&quot;width: 56.7476%;&quot; data-widthpercent=&quot;57.42&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/6a3Z9/btsGB2vzNMF/czM0DqUCK6Ta24MFAhjw0K/img.png&quot; alt=&quot;사고 조사 보고서에서 발췌한 대응 방안 2&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F6a3Z9%2FbtsGB2vzNMF%2FczM0DqUCK6Ta24MFAhjw0K%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;683&quot; height=&quot;638&quot;/&gt;&lt;/span&gt;&lt;/div&gt;
  &lt;figcaption&gt;사고 조사 보고서에서 발췌한 대응 방안&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;한편 사고 이후 ESA의 대응은 주목할 만하다. 문제의 원인을 파악하자 마자, 곧바로 조사 보고서에서 SW 시스템 테스팅에 관한 가이드라인을 새로 짤 것을 지시했다. 근본적인 문제의 원인인 소프트웨어의 명세 부분을 새롭게 문서화하고, 임베디드 소프트웨어를 포함한 모든 비행 소프트웨어를 다시 검토하였다. 특히 Single Point Failure을 가진 부품들, 곧 critical components(반드시 성공이 보장되어야 하는 부품들)에 대한 검증이 이루어졌다. 외부에도 코드와 명세 문서를 공개하여, 외부로부터도 리뷰를 받을 수 있게 하였다.&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;/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;실제로 이러한 노력 끝에 아리안5는 2023년 은퇴하기까지 세상에서 가장 신뢰받는 로켓으로 평가받았다. 일반인들에게 있어 가장 유명한 로켓 세 개를 뽑으라 하면 아마 러시아의 소유즈, SpaceX의 팔콘, 그리고 아리안5 로켓이 아닐까 싶다.&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;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;  참고자료&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;GENERAL ACCOUNTING OFFICE WASHINGTON DC INFORMATION MANAGEMENT AND TECHNOLOGY DIV. Patriot Missile Defense: Software Problem Led to System Failure at Dhahran, Saudi Arabia. 1992.&lt;/li&gt;
&lt;li&gt;김종하. 역사 속의 소프트웨어 오류 : 부실한 소프트웨어가 초래한 위험천만한 사건 사고들. 에이콘출판사, 2014.&lt;/li&gt;
&lt;li&gt;LIONS, Jacques-Louis, et al. Flight 501 failure. &lt;i&gt;Report by the Inquiry Board&lt;/i&gt;, 1996.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.iro.umontreal.ca/~mignotte/IFT2425/Disasters.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Some&amp;nbsp;disasters&amp;nbsp;attributable&amp;nbsp;to&amp;nbsp;bad&amp;nbsp;numerical&amp;nbsp;computing&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>Computer Science</category>
      <author>nx006</author>
      <guid isPermaLink="true">https://nx006.tistory.com/80</guid>
      <comments>https://nx006.tistory.com/80#entry80comment</comments>
      <pubDate>Sat, 13 Apr 2024 04:09:26 +0900</pubDate>
    </item>
    <item>
      <title>[SAFY 개발일지] API 명세를 통해 레포지토리 추상화하기</title>
      <link>https://nx006.tistory.com/79</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;이전 글: &lt;a href=&quot;https://nx006.tistory.com/78&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Solution Challenge 2024 참여 후기&lt;/a&gt;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;필요한 정보 수집하기&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;개발을 시작하기 전 가장 먼저 해야 할 일은 바로 API를 명세하는 일이다.&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;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;홈화면&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imagegridblock&quot;&gt;
  &lt;div class=&quot;image-container&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/wufX8/btsFodsf7Lc/JJ1oxcL5bXeO3Bu3SE4Zl1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/wufX8/btsFodsf7Lc/JJ1oxcL5bXeO3Bu3SE4Zl1/img.png&quot; width=&quot;100%&quot; data-origin-width=&quot;856&quot; data-origin-height=&quot;1852&quot; data-is-animation=&quot;false&quot; style=&quot;width: 32.5581%; margin-right: 10px;&quot; data-widthpercent=&quot;33.33&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/wufX8/btsFodsf7Lc/JJ1oxcL5bXeO3Bu3SE4Zl1/img.png&quot; alt=&quot;홈화면&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FwufX8%2FbtsFodsf7Lc%2FJJ1oxcL5bXeO3Bu3SE4Zl1%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;856&quot; height=&quot;1852&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b9iSeN/btsFm1FHdon/C6Rfgl8eHn1SJFuQaEhGB1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b9iSeN/btsFm1FHdon/C6Rfgl8eHn1SJFuQaEhGB1/img.png&quot; width=&quot;100%&quot; data-origin-width=&quot;856&quot; data-origin-height=&quot;1852&quot; data-is-animation=&quot;false&quot; style=&quot;width: 32.5581%; margin-right: 10px;&quot; data-widthpercent=&quot;33.33&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b9iSeN/btsFm1FHdon/C6Rfgl8eHn1SJFuQaEhGB1/img.png&quot; alt=&quot;홈화면&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb9iSeN%2FbtsFm1FHdon%2FC6Rfgl8eHn1SJFuQaEhGB1%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;856&quot; height=&quot;1852&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bsp56W/btsFlQxUvIG/b8nF9EWq6CYJpBkSfL3mzk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bsp56W/btsFlQxUvIG/b8nF9EWq6CYJpBkSfL3mzk/img.png&quot; width=&quot;100%&quot; data-origin-width=&quot;856&quot; data-origin-height=&quot;1852&quot; data-is-animation=&quot;false&quot; style=&quot;width: 32.5581%;&quot; data-widthpercent=&quot;33.34&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bsp56W/btsFlQxUvIG/b8nF9EWq6CYJpBkSfL3mzk/img.png&quot; alt=&quot;홈화면&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbsp56W%2FbtsFlQxUvIG%2Fb8nF9EWq6CYJpBkSfL3mzk%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;856&quot; height=&quot;1852&quot;/&gt;&lt;/span&gt;&lt;/div&gt;
  &lt;figcaption&gt;홈화면 피그마&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;교육의 각 카테고리를 Education, 그리고 행동에 관한 카테고리를 Action이라 하겠다(Action으로 할 지 Pose로 할 지를 고민했었다).&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;홈 화면을 구현하기 위해서는, Education 리스트 안에 썸네일 사진, 제목, 설명, 그리고 디테일 정보가 필요하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;디테일 정보의 경우 Education Card 우상단의 버튼을 누르면, 디테일한 정보가 펼쳐지도록 디자인하였다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;교육 디테일 화면&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;각 카드를 클릭하면, 퀴즈에 대한 정보가 나온다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;856&quot; data-origin-height=&quot;1852&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/U0Mx9/btsFuOEDrsd/sU7LjOn5EpvoQK5HK2nfWk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/U0Mx9/btsFuOEDrsd/sU7LjOn5EpvoQK5HK2nfWk/img.png&quot; data-alt=&quot;Quiz 화면 피그마&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/U0Mx9/btsFuOEDrsd/sU7LjOn5EpvoQK5HK2nfWk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FU0Mx9%2FbtsFuOEDrsd%2FsU7LjOn5EpvoQK5HK2nfWk%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;432&quot; height=&quot;935&quot; data-origin-width=&quot;856&quot; data-origin-height=&quot;1852&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;Quiz 화면 피그마&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;퀴즈 화면은 퀴즈의 상태에 따른 정보로, 사용자가 퀴즈를 풀었는지에 따라&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;풀지 않았으면 &lt;span style=&quot;color: #666666;&quot;&gt;&lt;b&gt;회색&lt;/b&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;풀었는데
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;맞았으면 &lt;span style=&quot;color: #409d00;&quot;&gt;&lt;b&gt;녹색&lt;/b&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;맞았으면 &lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;적색&lt;/b&gt;&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;으로 표시한다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;퀴즈 풀기 화면&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;저 퀴즈 아이콘을 클릭하면&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imagegridblock&quot;&gt;
  &lt;div class=&quot;image-container&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bSJZMG/btsFuKIZME2/6iyUEMvPRlXJlufTEwTG40/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bSJZMG/btsFuKIZME2/6iyUEMvPRlXJlufTEwTG40/img.png&quot; width=&quot;100%&quot; data-origin-width=&quot;1944&quot; data-origin-height=&quot;1852&quot; data-is-animation=&quot;false&quot; style=&quot;width: 68.6213%; margin-right: 10px;&quot; data-widthpercent=&quot;69.43&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bSJZMG/btsFuKIZME2/6iyUEMvPRlXJlufTEwTG40/img.png&quot; alt=&quot;퀴즈 내부 화면 피그마&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbSJZMG%2FbtsFuKIZME2%2F6iyUEMvPRlXJlufTEwTG40%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;1944&quot; height=&quot;1852&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/F83t6/btsFoH7D5QK/dAhPbL5pWbDFZP2XAbpmXk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/F83t6/btsFoH7D5QK/dAhPbL5pWbDFZP2XAbpmXk/img.png&quot; width=&quot;100%&quot; data-origin-width=&quot;856&quot; data-origin-height=&quot;1852&quot; data-is-animation=&quot;false&quot; style=&quot;width: 30.2159%;&quot; data-widthpercent=&quot;30.57&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/F83t6/btsFoH7D5QK/dAhPbL5pWbDFZP2XAbpmXk/img.png&quot; alt=&quot;퀴즈 내부 화면 피그마&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FF83t6%2FbtsFoH7D5QK%2FdAhPbL5pWbDFZP2XAbpmXk%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;856&quot; height=&quot;1852&quot;/&gt;&lt;/span&gt;&lt;/div&gt;
  &lt;figcaption&gt;퀴즈 내부 화면 피그마&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위와 같이 퀴즈를 풀 수 있는 화면이 뜬다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이때 퀴즈의 경우 크게 두 가지 타입으로 나뉘어지는데, &lt;b&gt;객관식&lt;/b&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-ke-size=&quot;size16&quot;&gt;객관식의 경우 여러 선택지 중 하나를 선택하는 타입의 퀴즈이다. 선택지가 O, X인 OX 문제도 구조상 객관식 퀴즈에 속한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;순서 맞추기 퀴즈의 경우 여러 선택지를 올바른 순서로 배열하는 타입의 퀴즈이다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;행동 디테일 화면&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;856&quot; data-origin-height=&quot;1852&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ud4Gx/btsFuOYV7pn/kTHnWl3LHrR0GnAIrFmSTK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ud4Gx/btsFuOYV7pn/kTHnWl3LHrR0GnAIrFmSTK/img.png&quot; data-alt=&quot;Action Detail View Figma&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ud4Gx/btsFuOYV7pn/kTHnWl3LHrR0GnAIrFmSTK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fud4Gx%2FbtsFuOYV7pn%2FkTHnWl3LHrR0GnAIrFmSTK%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; alt=&quot;Action Detail View Figma&quot; loading=&quot;lazy&quot; width=&quot;391&quot; height=&quot;846&quot; data-origin-width=&quot;856&quot; data-origin-height=&quot;1852&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;Action Detail View Figma&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;한편 사용자의 행동에 관한 Action Card를 클릭하면 위와 같은 화면이 나온다. 제목, 동영상, 그리고 설명이 나온다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;영상 제출 화면&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;856&quot; data-origin-height=&quot;1852&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/rfyuX/btsFmeL82zH/NkuWuP2IdwgvBk2zHMsn9k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/rfyuX/btsFmeL82zH/NkuWuP2IdwgvBk2zHMsn9k/img.png&quot; data-alt=&quot;Action Submit View Figma&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/rfyuX/btsFmeL82zH/NkuWuP2IdwgvBk2zHMsn9k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FrfyuX%2FbtsFmeL82zH%2FNkuWuP2IdwgvBk2zHMsn9k%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; alt=&quot;Action Submit View&quot; loading=&quot;lazy&quot; width=&quot;392&quot; height=&quot;848&quot; data-origin-width=&quot;856&quot; data-origin-height=&quot;1852&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;Action Submit View Figma&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;현재 제공되는 액션 교육은 CPR이 유일하다. 사용자가 CPR하는 모습을 찍고, 이를 올릴 수 있는 화면은 위와 같다. 위 화면은 서버로부터 &lt;code&gt;GET&lt;/code&gt; 요청을 보낼 일은 없고, 사용자의 영상을 서버로 올리는 요청을 해야 한다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;API 명세하기&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;개발해야 하는 주요 화면은 전부 살펴보았으니 이제 API를 명세할 차례이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;먼저 여기서 내가 가져갈 전략이 있다. 바로 &lt;u&gt;상위 모델과 디테일 모델을 구분하는 것&lt;/u&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;위 화면에서 공통적으로, 모델의 리스트 화면(Education, Action List 화면)과, 그리고 해당 모델의 위젯(카드나 아이콘 버튼)을 눌렀을 때 나오는 디테일 화면(Education Detail 화면, Quiz Detail 화면)이 있다.&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;code&gt;Model-DetailModel&lt;/code&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;상위 모델을 부모 클래스로, 하위 Detail 모델을 자식 클래스로 정의하면, &lt;u&gt;캐싱을 쉽게 가져갈 수 있다.&lt;/u&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;캐싱에 대한 내용은 다음 글로 넘기겠다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Education&lt;/h3&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;EducationDetailModel&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;먼저 &lt;code&gt;EducationModel&lt;/code&gt;에서 필요한 정보를 나열해보자.&lt;/p&gt;
&lt;pre class=&quot;json&quot;&gt;&lt;code&gt;{
    &quot;id&quot;: 0,
    &quot;title&quot;: &quot;String&quot;,
    &quot;description&quot;: &quot;String?&quot;,
    &quot;thumbUrl&quot;: &quot;String?&quot;,
    &quot;detail&quot;: &quot;String?&quot;,
    &quot;images&quot;: [&quot;List&amp;lt;String&amp;gt;?&quot;]
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 정보들이 있다면 화면을 구현할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 Description과 Detail의 차이라면, Description은 EducationCard의 하단에 나오는 짧막한 설명글이고, Detail은 자세한 설명 보기를 눌렀을 때 나오는 html 형식의 긴 설명글이다.&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;images 역시 설명 보기를 눌렀을 때 나오는 사진들을 의미한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imagegridblock&quot;&gt;
  &lt;div class=&quot;image-container&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/KNtut/btsFlV6YnHC/RXBf89CEQd8ATOs7oM2sL0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/KNtut/btsFlV6YnHC/RXBf89CEQd8ATOs7oM2sL0/img.png&quot; width=&quot;100%&quot; data-origin-width=&quot;1700&quot; data-origin-height=&quot;2475&quot; data-is-animation=&quot;false&quot; style=&quot;width: 42.3873%; margin-right: 10px;&quot; data-widthpercent=&quot;42.89&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/KNtut/btsFlV6YnHC/RXBf89CEQd8ATOs7oM2sL0/img.png&quot; alt=&quot;Education 모델의 필드&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FKNtut%2FbtsFlV6YnHC%2FRXBf89CEQd8ATOs7oM2sL0%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;1700&quot; height=&quot;2475&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cMG1cA/btsFsceWCBO/rlmaPekfQBHTuqzbOko6ek/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cMG1cA/btsFsceWCBO/rlmaPekfQBHTuqzbOko6ek/img.jpg&quot; width=&quot;100%&quot; data-origin-width=&quot;2264&quot; data-origin-height=&quot;2475&quot; data-is-animation=&quot;false&quot; style=&quot;width: 56.4499%;&quot; data-widthpercent=&quot;57.11&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cMG1cA/btsFsceWCBO/rlmaPekfQBHTuqzbOko6ek/img.jpg&quot; alt=&quot;Education 모델의 필드&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcMG1cA%2FbtsFsceWCBO%2FrlmaPekfQBHTuqzbOko6ek%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;2264&quot; height=&quot;2475&quot;/&gt;&lt;/span&gt;&lt;/div&gt;
  &lt;figcaption&gt;Education 모델의 필드&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 정보들이 리스트 형태로 온다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 정보들은 &lt;code&gt;GET /education&lt;/code&gt;으로 요청할 것이다.&lt;/p&gt;
&lt;pre class=&quot;json&quot;&gt;&lt;code&gt;{
    &quot;meta&quot;: {
        &quot;count&quot;: 1
    },
    &quot;data&quot;: [
        {
            &quot;id&quot;: 0,
            &quot;title&quot;: &quot;String&quot;,
            &quot;description&quot;: &quot;String?&quot;,
            &quot;thumbUrl&quot;: &quot;String?&quot;,
            &quot;detail&quot;: &quot;String?&quot;,
            &quot;images&quot;: [&quot;List&amp;lt;String&amp;gt;?&quot;]
        }
    ]
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 오게끔 디자인하였다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이때 &lt;b&gt;meta&lt;/b&gt; 정보가 굳이 필요한가 의아할 수 있다. count 정보는 사실 HTTP Header에도 넣을 수 있는 정보라서, 불필요해보인다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이는 개인의 선호 영역인데, 나의 경우는 meta 정보는 관습적으로 넣는다. 특히 List 형태의 경우 &lt;a href=&quot;https://nx006.tistory.com/58&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Pagination&lt;/a&gt;이 들어갈 수도 있는데, Pagination의 경우 meta를 따로 두는 것이 API를 깔끔하게 명세하기 유리하기 때문이다.&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;물론 Pagination에 필요한 정보 역시 Body가 아닌 Header에 추가하는 전략 역시 유효하기에, 이는 각 프로젝트에 따라 백엔드와 상의해보고 결정할 문제이다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;EducationDetailModel&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 정보는 &lt;code&gt;GET /education/{education-id}&lt;/code&gt; 로 요청 시 나오는 정보이다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;856&quot; data-origin-height=&quot;1852&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/mmFoZ/btsFs06nzfz/yq0oQuaXwEYefB7vBXMsk0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/mmFoZ/btsFs06nzfz/yq0oQuaXwEYefB7vBXMsk0/img.png&quot; data-alt=&quot;Quiz 화면 피그마&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/mmFoZ/btsFs06nzfz/yq0oQuaXwEYefB7vBXMsk0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FmmFoZ%2FbtsFs06nzfz%2Fyq0oQuaXwEYefB7vBXMsk0%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; alt=&quot;Quiz 화면 피그마&quot; loading=&quot;lazy&quot; width=&quot;407&quot; height=&quot;881&quot; data-origin-width=&quot;856&quot; data-origin-height=&quot;1852&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;Quiz 화면 피그마&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;EducationCard를 눌렀을 때 나오는 Education Detail View에서 추가되는 정보는 Quiz 정보들밖에 없다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;EducationDetailModel&lt;/b&gt;:&lt;/p&gt;
&lt;pre class=&quot;json&quot;&gt;&lt;code&gt;{
    &quot;id&quot;: 0,
    &quot;title&quot;: &quot;String&quot;,
    &quot;description&quot;: &quot;String?&quot;,
    &quot;thumbUrl&quot;: &quot;String?&quot;,
    &quot;detail&quot;: &quot;String?&quot;,
    &quot;images&quot;: [&quot;List&amp;lt;String&amp;gt;?&quot;]
    &quot;quizzes&quot;: [
        {
            &quot;id&quot;: 0,
            &quot;isSolved&quot;: 0
        }
    ]
}&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Q. detail과 images는 EducationDetailModel에 있어야 하는 정보들 아닌가?&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;맞는 말이다! 원래는 위 정보들은 &lt;code&gt;EducationDetailModel&lt;/code&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;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1700&quot; data-origin-height=&quot;2475&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bQKtnE/btsFuOSazVa/0FBnyntKiJrOcTbOSD4AxK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bQKtnE/btsFuOSazVa/0FBnyntKiJrOcTbOSD4AxK/img.png&quot; data-alt=&quot;Education Card List View&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bQKtnE/btsFuOSazVa/0FBnyntKiJrOcTbOSD4AxK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbQKtnE%2FbtsFuOSazVa%2F0FBnyntKiJrOcTbOSD4AxK%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; alt=&quot;Education 모델의 필드&quot; loading=&quot;lazy&quot; width=&quot;412&quot; height=&quot;600&quot; data-origin-width=&quot;1700&quot; data-origin-height=&quot;2475&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;Education Card List View&lt;/figcaption&gt;
&lt;/figure&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;code&gt;Education&lt;/code&gt;의 자세한 정보를 보려면 카드를 눌러서 DetailView에서 확인했어야 했다.&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;code&gt;detail&lt;/code&gt; 설명과 &lt;code&gt;images&lt;/code&gt; 역시 한 번에 오도록 변경했다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그렇다면 detail 버튼을 눌렀을 때 detail 정보를 가져오도록 하면 안 되나?&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;어&amp;hellip; 그것도 맞는 말이다. 사실 글을 쓰면서 이 방법이 생각이 났다.&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;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style15&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;detail&lt;span style=&quot;color: #333333; text-align: left;&quot;&gt;,&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;images의 위치&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;장점&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;단점&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;`&lt;span style=&quot;color: #6164c6;&quot;&gt;&lt;i&gt;GET /education&lt;/i&gt;&lt;/span&gt;`에서 한 번에 오는 경우&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;
&lt;ul style=&quot;list-style-type: disc; color: #333333; text-align: left;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;detail 보기 버튼을 눌렀을 때 로딩 없이 바로 설명글을 볼 수 있음&lt;/li&gt;
&lt;li&gt;detail 보기 버튼을 눌렀을 때 별도의 로직을 고민하지 않아도 됨&lt;/li&gt;
&lt;/ul&gt;
&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;
&lt;ul style=&quot;list-style-type: disc; color: #333333; text-align: left;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;detail, images 정보가 추가되므로, 리스트뷰 입장에서는 필요도 없는 정보를 기다리느라 로딩이 더 오래 걸림(홈 화면에서 EducationCard를 그릴 때는 detail, images 정보가 필요 없으므로&amp;hellip;)&lt;/li&gt;
&lt;/ul&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;`&lt;span style=&quot;color: #6164c6;&quot;&gt;&lt;i&gt;GET /education/{education-id}&lt;/i&gt;&lt;/span&gt;`에서 오는 경우&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;
&lt;ul style=&quot;list-style-type: disc; color: #333333; text-align: left;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;detail,&lt;span&gt;&amp;nbsp;&lt;/span&gt;images가 없으므로 좀 더 빠르게 Education Card View를 보여줄 수 있음&lt;/li&gt;
&lt;li&gt;detail, images 정보를 명확하게 분리할 수 있음&lt;/li&gt;
&lt;/ul&gt;
&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;
&lt;ul style=&quot;list-style-type: disc; color: #333333; text-align: left;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;detail 버튼을 눌렀을 때 detail 정보가 뜨는 게 느릴 수 있음&lt;/li&gt;
&lt;li&gt;detail 버튼을 눌러도 그 안에 quiz 정보가 뜨는 건 아님. 그렇다면 엄격하게 설계 시 quiz 정보를 또다시 분리하는 것도 고민해야 함. 그렇게 하면 모델이 3개로 늘어나야 하거나, 혹은 nullable 처리를 복잡하게 가져가야 함.&lt;/li&gt;
&lt;/ul&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;장단점이 명확하긴 한데, 후자의 방식 중 단점 두 번째, quiz 정보의 경우, 사실 nullable 처리가 그리 복잡하진 않을 것 같거니와 별도의 상태를 따로 만드는 것도 그리 코드를 헤치지 않는 선에서 해결될 것 같다.&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;quizzes 정보의 경우, 사실 별도의 API에서 요청하는 방법도 있다. 예를 들어 &lt;code&gt;GET /quiz?education=0&lt;/code&gt;이런 식으로 query parameter를 이용해서 요청하는 방법을 생각해볼 수 있다. 후자의 방식으로 요청하게 된다면, 위 방식이 더욱 더 자연스러운 방식이 될 것이다.&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;솔직히 지금 시점에서는 후자의 방식이 더 끌리긴 한다. 지금 다시 API 디자인하라고 하면 후자의 방식으로 디자인할 것 같다. 하지만 이미 전자의 방식으로 디자인하였기에 어쩔 수 없다...&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Quiz&lt;/h3&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;QuizStatusModel&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;EducationModel&lt;/code&gt;에 대응하는 퀴즈의 모델이 &lt;code&gt;QuizStatusModel&lt;/code&gt;이다. 이때 &lt;code&gt;QuizModel&lt;/code&gt;이라고 명명하지 않은 이유는, 이 모델이 실제 Quiz 정보를 담는다고 하기에는 너무 빈약한 정보만을 담고 있기 때문이다.&lt;/p&gt;
&lt;pre class=&quot;json&quot;&gt;&lt;code&gt;{
    &quot;id&quot;: 0,
    &quot;isSolved&quot;: 0
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;isSolved&lt;/code&gt;는 유저의 풀이 상태를 나타내는 데, 0인 경우 풀지 않았음을, 1인 경우 풀었는데 틀렸음을, 2인 경우 풀었는데 맞았음을 의미한다.&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;code&gt;GET /education/0&lt;/code&gt;과 같이 &lt;code&gt;EducationDetailModel&lt;/code&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;&amp;hellip;이렇게 하는 방법이 있고, 사실 개발 중간에 바뀔 뻔한 내용이기는 한데 &lt;code&gt;GET /quiz?education=0&lt;/code&gt; 이런 식으로 query parameter를 이용해서 요청하는 방법도 있기는 하다. 이는 RESTful에서 컬렉션과 리소스의 분류에 대한 논의인데, 결론부터 말하자면 둘 다 맞는 방법이다.&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;code&gt;QuizStatusModel&lt;/code&gt;이라는 리소스는 어쨌건 quiz collection에 속해 있는 리소스이고&lt;/b&gt;, 그렇기 때문에 path가 &lt;code&gt;/quiz&lt;/code&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;u&gt;이 모델이 Quiz 컬렉션에 들어가는 자원인 지는 더 고민해봐야 한다.&lt;/u&gt; &lt;code&gt;QuizStatusModel&lt;/code&gt;은 Quiz라는 자원의 정보를 나타낸다고 보기보다는, &lt;b&gt;유저의 풀이 상태에 따라 달라지는 Quiz-User 사이의 모델에 더 가깝다&lt;/b&gt;고 볼 수 있다. DB의 시점까지 내려와서 본다면, 위 &lt;code&gt;QuizStatusModel&lt;/code&gt;은 엄밀히 말해 &lt;b&gt;Quiz 테이블을 조회하지 않는다.&lt;/b&gt; 유저가 이 문제를 풀었는지, 그 Solved Status를 모아두는 테이블을 조회한다. 그렇기 때문에 이 자원은 Quiz 컬렉션에 들어가는 자원으로 보기는 어렵다는 게 나의 입장이다&lt;i&gt;(&lt;code&gt;QuizModel&lt;/code&gt;이 아닌 &lt;code&gt;QuizStatusModel&lt;/code&gt;로 명명한 또다른 이유이다)&lt;/i&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;결론적으로 Detail 모델에 QuizStatusModel을 포함하는 방식으로 디자인이 정해졌다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;뭐 둘 다 실제 현장에서 많이 쓰이는 방식이자 둘 다 RESTful한 방식으로 알고 있다. 정답이 있진 않다. 선택만이 존재할 뿐이다.&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;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;QuizDetailModel&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;GET /quiz/{quiz-id}&lt;/code&gt;로 요청 시 나오는 모델이다.&lt;/p&gt;
&lt;pre class=&quot;json&quot;&gt;&lt;code&gt;{
  &quot;id&quot;: 1,
  &quot;type&quot;: &quot;MULTIPLE_CHOICE&quot;, // &quot;ORDERING&quot; or &quot;MULTIPLE_CHOICE&quot;
  &quot;data&quot;: {
    &quot;description&quot;: &quot;지진 대피소 표시로 올바른 것을 고르세요&quot;,
    &quot;answer&quot;: 1,
    &quot;options&quot;: [
      {
        &quot;number&quot;: &quot;0&quot;,
        &quot;description&quot;: &quot;지진 대피소 표시 1&quot;
      },
      {
        &quot;number&quot;: &quot;1&quot;,
        &quot;description&quot;: &quot;지진 대피소 표시 2&quot;,
        &quot;imageUrl&quot;: &quot;images/2.jpg&quot;
      },
      {
        &quot;number&quot;: &quot;2&quot;,
        &quot;description&quot;: &quot;지진 대피소 표시 3&quot;,
        &quot;imageUrl&quot;: &quot;images/3.jpg&quot;
      },
      {
        &quot;number&quot;: &quot;3&quot;,
        &quot;description&quot;: &quot;지진 대피소 표시 4&quot;,
        &quot;imageUrl&quot;: &quot;images/4.jpg&quot;
      }
    ]
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 아주 중요한 특징이 하나 있다. &lt;b&gt;퀴즈의 타입 별로 data가 달라진다.&lt;/b&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;code&gt;MULTIPLE_CHOICE&lt;/code&gt;(객관식) 타입&lt;/b&gt;이다. 이 경우 data에 &lt;code&gt;answer&lt;/code&gt;가 int형으로 존재한다. 이때 &lt;code&gt;answer&lt;/code&gt;는 정답인 &lt;code&gt;option&lt;/code&gt;의 인덱스 위치를 나타낸다. 인덱스 번호임과 동시에 &lt;code&gt;number&lt;/code&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;여기서 option number는 사실상 PK로 받아들여도 괜찮다. 실제 DB 상에는 Quiz와 Option을 연결해주는 부분키가 따로 존재한다(&lt;code&gt;(quizId, optionId)&lt;/code&gt; 형태의 복합키). 다만 PK의 경우 만약 옵션을 한 번 지웠다가 새로 생성하면, 기존의 PK를 사용하지 못할 수도 있기에, 별도의 number라는 일반 &lt;code&gt;int&lt;/code&gt;형 컬럼을 따로 두었다. 이 부분은 백엔드에서 개발 시에 낸 아이디어인데, 굉장히 합리적이다. &lt;code&gt;number&lt;/code&gt;는 PK가 아니기에, &lt;code&gt;id&lt;/code&gt;라는 이름을 사용하지 않고 &lt;code&gt;number&lt;/code&gt;라는 이름을 사용했다(Flutter 코드 상에서는 id라고 받아들여도 무방하다).&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;만약에 type이 &lt;b&gt;&lt;code&gt;ORDERING&lt;/code&gt;(순서 맞추기)&lt;/b&gt;일 경우, &lt;u&gt;answer가 사라진다&lt;/u&gt;. 대신 options의 순서 그대로 정답이 된다.&lt;/p&gt;
&lt;pre class=&quot;json&quot;&gt;&lt;code&gt;{
  &quot;description&quot;: &quot;순서를 올바르게 배치하시오&quot;,
  &quot;options&quot;: [
    {
      &quot;id&quot;: 1,
      &quot;description&quot;: &quot;지진 대피소 표시 1&quot;
    },
    {
      &quot;id&quot;: 2,
      &quot;description&quot;: &quot;지진 대피소 표시 2&quot;,
      &quot;imageUrl&quot;: &quot;images/2.jpg&quot;
    },
    {
      &quot;id&quot;: 3,
      &quot;description&quot;: &quot;지진 대피소 표시 3&quot;,
      &quot;imageUrl&quot;: &quot;images/3.jpg&quot;
    },
    {
      &quot;id&quot;: 4,
      &quot;description&quot;: &quot;지진 대피소 표시 4&quot;,
      &quot;imageUrl&quot;: &quot;images/4.jpg&quot;
    }
  ]
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Flutter 상에서는 이 options를 랜덤으로 셔플해서 배치하도록 하였다.&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;물론 타입 별로 data를 나누지 않을 방법은 충분히 있다. &lt;code&gt;ORDERING&lt;/code&gt;에서 answer에 아무 의미 없는 0을 넣어 남겨둘 수도 있다. 혹은 &lt;code&gt;MULTIPLE_CHOICE&lt;/code&gt;에서 answer를 없애고, 무조건 0번 인덱스의 Option이 답이 되도록 설계할 수도 있다. 이때도 Flutter 상에서 Option을 셔플해서 배치해주면 된다.&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;다만 이럴 경우 타입이 다양해질 경우에는 어떻게 대응할 것인가? 예를 들어, 주관식이라는 타입이 새로 등장한다면? 이 경우 답은 int형이 아닌 String 형이 될 수도 있다. 물론 이때도 가능한 정답을 options에 넣어서, option의 description에 일치하는 String만 정답으로 처리할 수도 있다. 이때는 Flutter 상에서 option을 보이지 않게 하면 된다.&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;하지만 더 다양한 타입이 등장한다면? 결국에는 타입 별로 데이터를 변경해야만 하는 시점이 올 수 있다. 그리고 데이터를 언제나 일관적으로 유지한다면, 각기 다른 타입 별로 option을 배치하느라 백엔드에서 고민이 더 많아질 수도 있다. 그래서 그냥 처음부터 타입 별로 데이터가 다르게 들어올 수 있게끔 디자인하였다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Action&lt;/h3&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;ActionModel&lt;/h4&gt;
&lt;pre class=&quot;json&quot;&gt;&lt;code&gt;{
    &quot;id&quot;: 0,
    &quot;title&quot;: &quot;CPR&quot;,
    &quot;description&quot;: &quot;Let's learn How to do CPR correctly&quot;,
    &quot;thumbUrl&quot;: &quot;https://firebasestorage.googleapis.com/v0/b/solution-2024-safety-edu.appspot.com/o/assets%2Fimages%2Fthumbnail%2Fcpr.webp?alt=media&amp;amp;token=1454397f-b24e-4afe-bf09-3f3d2a3e499b&quot;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Action의 경우 서버 자체가 달라진다. 하지만 요청 양식은 거의 비슷하다. Flutter 상의 컴포넌트도 동일하게 공유한다. 한 가지 차이점이라면 여기서는 detail 정보와 images 정보가 없다.&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;code&gt;GET /action&lt;/code&gt; 요청 시:&lt;/p&gt;
&lt;pre class=&quot;json&quot;&gt;&lt;code&gt;{
    &quot;meta&quot;: {
        &quot;count&quot;: 1
    },
    &quot;data&quot;: [
        {
            &quot;id&quot;: 0,
            &quot;title&quot;: &quot;CPR&quot;,
            &quot;description&quot;: &quot;Let's learn How to do CPR correctly&quot;,
            &quot;thumbUrl&quot;: &quot;https://firebasestorage.googleapis.com/v0/b/solution-2024-safety-edu.appspot.com/o/assets%2Fimages%2Fthumbnail%2Fcpr.webp?alt=media&amp;amp;token=1454397f-b24e-4afe-bf09-3f3d2a3e499b&quot;
        }
    ]
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위처럼 meta 데이터와 함께 리스트 형태로 오는 것까지 동일하다.&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;code&gt;detail&lt;/code&gt; 정보와 &lt;code&gt;images&lt;/code&gt; 정보를 제외하면 &lt;code&gt;EducationModel&lt;/code&gt;과 필드가 거의 일치한다. 그래서 상속 구조나 인터페이스 구조로 만들 수도 있기는 한데, 굳이 그렇게까지 추상화를 해야할까 싶어서 그러지는 않았다(대신 후술하겠지만 &lt;code&gt;IModelWithId&lt;/code&gt; 인터페이스를 만들어서 PK를 가진 모델들을 implements시켰다).&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;ActionDetailModel&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;GET /action/{action-id}&lt;/code&gt; 로 요청 시 detail 정보가 들어온다.&lt;/p&gt;
&lt;pre class=&quot;xml&quot;&gt;&lt;code&gt;{
    &quot;id&quot;: 0,
    &quot;title&quot;: &quot;CPR&quot;,
    &quot;description&quot;: &quot;Let's learn How to do CPR correctly&quot;,
    &quot;thumbUrl&quot;: &quot;https://firebasestorage.googleapis.com/v0/b/solution-2024-safety-edu.appspot.com/o/assets%2Fimages%2Fthumbnail%2Fcpr.webp?alt=media&amp;amp;token=1454397f-b24e-4afe-bf09-3f3d2a3e499b&quot;,
    &quot;detail&quot;: &quot;&amp;lt;h2&amp;gt;Try CPR Yourself&amp;lt;/h2&amp;gt;\n&amp;lt;p&amp;gt;Watch the video and follow these steps!&amp;lt;/p&amp;gt;\n&amp;lt;h3&amp;gt;Preparing for CPR&amp;lt;/h3&amp;gt;\n&amp;lt;ol&amp;gt;\n&amp;lt;li&amp;gt;Assess the scene.&amp;lt;/li&amp;gt;\n&amp;lt;li&amp;gt;Check the patient's responsiveness.&amp;lt;/li&amp;gt;\n&amp;lt;li&amp;gt;Call 911.&amp;lt;/li&amp;gt;\n&amp;lt;li&amp;gt;Retrieve an AED (Automated External Defibrillator).&amp;lt;/li&amp;gt;\n&amp;lt;li&amp;gt;Check the patient's breathing.&amp;lt;/li&amp;gt;\n&amp;lt;/ol&amp;gt;\n&amp;lt;h3&amp;gt;CPR Procedure&amp;lt;/h3&amp;gt;\n&amp;lt;ol&amp;gt;\n&amp;lt;li&amp;gt;Lay the patient flat on a firm surface.&amp;lt;/li&amp;gt;\n&amp;lt;li&amp;gt;Identify the correct hand placement for chest compressions.&amp;lt;/li&amp;gt;\n&amp;lt;li&amp;gt;Place the heel of one hand on the lower half of the breastbone.&amp;lt;/li&amp;gt;\n&amp;lt;li&amp;gt;Interlock fingers after positioning, keeping the fingers off the palm.&amp;lt;/li&amp;gt;\n&amp;lt;ul&amp;gt;\n&amp;lt;li&amp;gt;&amp;lt;em&amp;gt;Fractures may occur if chest compressions are performed with fingers rather than palms.&amp;lt;/em&amp;gt;&amp;lt;/li&amp;gt;\n&amp;lt;/ul&amp;gt;\n&amp;lt;li&amp;gt;Ensure shoulders, elbows, and wrists are perpendicular.&amp;lt;/li&amp;gt;\n&amp;lt;li&amp;gt;Press down vertically on the patient's chest about 5cm deep.&amp;lt;/li&amp;gt;\n&amp;lt;li&amp;gt;Perform 30 compressions at a rate of 100 to 120 per minute.&amp;lt;/li&amp;gt;\n&amp;lt;/ol&amp;gt;&quot;,
    &quot;videoUrl&quot;: &quot;https://www.youtube.com/watch?v=q7J2T6MFA9g&amp;amp;t=84s&quot;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;ActionDetailModel&lt;/code&gt;의 경우 &lt;code&gt;images&lt;/code&gt; 대신 &lt;code&gt;videoUrl&lt;/code&gt;이 들어간다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;모델을 만들어보자&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;API 명세를 만들었다면 이제 실제 코드 상에서 &lt;b&gt;Repository Layer&lt;/b&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;원래 Repository Layer는 &lt;b&gt;Model&lt;/b&gt;, &lt;b&gt;Repository&lt;/b&gt;, 그리고 DTO로 구성된다.&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;다만 여기서는 DTO를 사용하지 않고, Model을 직접 사용했다. 백엔드 개발하는 것도 아니고, 테이블의 형태가 달라지는 DB를 다루는 것도 아니고, 보통 프론트엔드에서는 들어오는 모델 그대로를 사용하기에 DTO를 굳이 사용해야 하나, 프론트엔드인데 너무 투머치 아닌가 하는 생각이었다. 그리고 일부러 아키텍처 구조를 생각하면서 API를 기껏 명세했다.&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;사실 따지고보면, QuizDetailModel에 &lt;code&gt;isSolved&lt;/code&gt; 와 같은 정보가 필요 없는데도 일부러 상속 구조를 위해 남겨두었다(DTO를 안 쓰기 위해서).&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;하지만 DTO를 안 쓰니깐, API가 변경될 뻔한 상황(위에서 퀴즈에 쿼리 파라미터를 사용해서, 따로 요청하는 상황)에서 막막하기는 하더라. 변경이 되었을 때도 다행히 DTO를 만들지 않고 기존의 Model에 주입시킬 수 있긴 했다. 그래서 장단점이 있긴 한 것 같다.&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;Json Serializable과 freezed를 이용했다. 기본적으로 freezed를 이용하되, freezed는 상속을 지원하지 않으므로 상속 구조가 필요한 곳에서는 Json Serializable을 이용했다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;IModelWithId 인터페이스로 엔티티 클래스 묶기&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;id&lt;/code&gt;를 가지는 모델들을 인터페이스로 묶자. 이럴 경우 추후에 Repository와 StateNotifier를 추상화하기에 아주 도움이 된다. 이 인터페이스의 역할은 장고에서 모델 클래스를 구현할 때 Model 모듈을 상속받는 것과 비슷하다.&lt;/p&gt;
&lt;pre class=&quot;dart&quot;&gt;&lt;code&gt;typedef Id = int;

extension IdExtension on String {
  Id toId() =&amp;gt; int.parse(this);
}

/// id를 가지는 모델의 추상 클래스
abstract interface class IModelWithId&amp;lt;T extends Id&amp;gt; {
  final T id;

  const IModelWithId({
    required this.id,
  });

    @override
  String toString() {
    return 'id: $id';
  }

  @override
  bool operator ==(Object other) {
    if (identical(this, other)) return true;

    return other is IModelWithId &amp;amp;&amp;amp; other.id == id;
  }

  @override
  int get hashCode =&amp;gt; id.hashCode;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래의 &lt;code&gt;toString&lt;/code&gt;, &lt;code&gt;operator==&lt;/code&gt;, &lt;code&gt;hashCode&lt;/code&gt;는 구현해도 되고 안 해도 된다. 다만 &lt;code&gt;toString&lt;/code&gt;을 구현해두면 디버깅 시에 아주 편리하다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;엔티티와 값 객체&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;동등성 체크 로직&lt;/b&gt; 역시 인터페이스에 구현해두는 편이 좋다. 왜냐하면 이 인터페이스는 &lt;b&gt;엔티티&lt;/b&gt;를 나타내기 때문이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;PK를 가지는 모델을 &lt;b&gt;엔티티&lt;/b&gt;라고 한다. 엔티티의 경우 두 객체의 PK가 같으면, 같은 객체로 취급한다.&lt;/p&gt;
&lt;pre class=&quot;dts&quot;&gt;&lt;code&gt;final person1 = Person(
    name: &quot;Peter&quot;, // pk
    age: 10,
);

final person2 = Person(
    name: &quot;Peter&quot;, // pk
    age: 24,
);

print(person1 == person2); // true&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Person 엔티티에서 name을 PK라 한다면, &amp;ldquo;Peter&amp;rdquo;라는 사람은 같은 사람(객체)를 의미한다(동명 이인은 없다고 가정). 세월이 지나 나이가 변한다 하더라도 두 객체는 같은 사람을 나타낸다. 그렇기 때문에 여기서 동등성 체크 로직은 나이를 신경쓰지 않아야 한다. PK가 같으면 같은 객체이다.&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;/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;한편 Dart 외의 언어에서는 interface에 메소드를 추가하는 것을 허용하지 않는 경우가 많다. interface에 메소드를 이용할 수 있는 건 Dart의 특권 중 하나이다.&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;code&gt;QuizOption&lt;/code&gt;, &lt;code&gt;QuizDataModel&lt;/code&gt; 등이 값 객체라 할 수 있다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;개별 모델 구현하기&lt;/h3&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;QuizStatusModel&lt;/h4&gt;
&lt;pre class=&quot;kotlin&quot;&gt;&lt;code&gt;/// 정답의 상태
///
/// [AnswerStatus.none] : 아직 풀지 않은 경우
/// [AnswerStatus.correct] : 풀고 정답인 경우
/// [AnswerStatus.wrong] : 풀었는데 틀린 경우
enum AnswerStatus {
  @JsonValue(0)
  none,

  @JsonValue(1)
  correct,

  @JsonValue(2)
  wrong,
}

extension AnswerStatusExtension on int {
  AnswerStatus toAnswerStatus() {
    switch (this) {
      case 0:
        return AnswerStatus.none;
      case 1:
        return AnswerStatus.correct;
      case 2:
        return AnswerStatus.wrong;
      default:
        throw ArgumentError.value(this, 'value', 'Invalid AnswerStatus value');
    }
  }
}

@JsonSerializable()
class QuizStatusModel implements IModelWithId {
//   const factory QuizStatusModel({
//     required AnswerStatus answerStatus,
//     required Id id,
//   }) = _QuizStatusModel;

//   factory QuizStatusModel.fromJson(Map&amp;lt;String, dynamic&amp;gt; json) =&amp;gt;
//       _$QuizStatusModelFromJson(json);
//
  @override
  final Id id;

  @JsonKey(name: 'isSolved')
  final AnswerStatus answerStatus;

  QuizStatusModel({
    required this.answerStatus,
    required this.id,
  });

  factory QuizStatusModel.fromJson(Map&amp;lt;String, dynamic&amp;gt; json) =&amp;gt;
      _$QuizStatusModelFromJson(json);

  QuizStatusModel copyWith({
    Id? id,
    AnswerStatus? answerStatus,
  }) {
    return QuizStatusModel(
      id: id ?? this.id,
      answerStatus: answerStatus ?? this.answerStatus,
    );
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;solvedStatus는 JSON에서 int형으로 들어온다. 이를 Dart 코드 상에서는 enum으로 받고, &lt;code&gt;JSONKEY&lt;/code&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;한편 Dart의 기능 중 하나인 &lt;code&gt;extension&lt;/code&gt;을 만들어두면, &lt;code&gt;0.toAnswerStatus()&lt;/code&gt;와 같이 int에다가 직접 method를 추가 가능하다. 매우 강력한 기능이니 애용하도록 하자.&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;code&gt;isSolved&lt;/code&gt;의 경우 원래 API 명세 단계에서 &lt;code&gt;answerStatus&lt;/code&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;code&gt;isSolved&lt;/code&gt;의 경우 true, false로 나타냈었는데, 나중에 유저가 틀렸는지 맞았는지에 따라 퀴즈 아이콘 색상을 빨간색, 녹색으로 표시하는 기능을 추가하면서 boolean이 아닌 enum으로 변경되었다. 그래서 &lt;code&gt;isSolved&lt;/code&gt;에서 &lt;code&gt;answerStatus&lt;/code&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;그런데 실제 API를 받아보니깐 &lt;code&gt;answerStatus&lt;/code&gt;로 바뀌지 않고 isSolved가 그대로 사용되고 있어서, 그냥 flutter code 상에서는 더 직관적인 answerStatus를 사용하고, 대신 &lt;code&gt;JSONKEY&lt;/code&gt;를 붙여서 대응하였다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;EducationModel&lt;/h4&gt;
&lt;pre class=&quot;dart&quot;&gt;&lt;code&gt;@JsonSerializable()
class EducationModel implements IModelWithId {
  @override
  final Id id;

  // 카테고리 제목
  final String title;

  // 카테고리 설명
  final String? description;

  // 카테고리 이미지
  final String? thumbUrl;

  // 카테고리 상세 내용
  final String? detail;

  /// detail 페이지에 표시할 이미지들(nullable)
  final List&amp;lt;String&amp;gt;? images;

  const EducationModel({
    required this.id,
    required this.title,
    required this.description,
    required this.thumbUrl,
    required this.detail,
    required this.images,
  });

  factory EducationModel.fromJson(Map&amp;lt;String, dynamic&amp;gt; json) =&amp;gt;
      _$EducationModelFromJson(json);

  factory EducationModel.copyWith({
    required EducationModel model,
    Id? id,
    String? title,
    String? description,
    String? thumbUrl,
    String? detail,
    List&amp;lt;String&amp;gt;? images,
  }) {
    return EducationModel(
      id: id ?? model.id,
      title: title ?? model.title,
      description: description ?? model.description,
      thumbUrl: thumbUrl ?? model.thumbUrl,
      detail: detail ?? model.detail,
      images: images ?? model.images,
    );
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;JsonSerializable은 freezed와 달리 &lt;code&gt;copyWith&lt;/code&gt;를 자동생성해주지 않는다. 그래서 필요하다면 따로 구현해야 한다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;EducationDetailModel&lt;/h4&gt;
&lt;pre class=&quot;scala&quot;&gt;&lt;code&gt;@JsonSerializable()
class EducationDetailModel extends EducationModel {
  final List&amp;lt;QuizStatusModel&amp;gt; quizzes;

  const EducationDetailModel({
    required super.id,
    required super.title,
    required super.description,
    required super.thumbUrl,
    required super.detail,
    required super.images,
    required this.quizzes,
  });

  factory EducationDetailModel.fromJson(Map&amp;lt;String, dynamic&amp;gt; json) =&amp;gt;
      _$EducationDetailModelFromJson(json);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;JsonSerializable을 이용하였기에 위처럼 상속 구조를 사용할 수 있다. 그리고 Dart 최신 버전의 경우 &lt;code&gt;super&lt;/code&gt; 키워드를 통해서 부모 클래스의 초기화를 굉장히 직관적으로 작성할 수 있다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;퀴즈마다 데이터가 달라진다면 어떻게 파싱해야 할까?&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다른 모델들은 어렵지 않게 구현할 수 있지만, QuizDetailModel은 조금 까다롭다. &lt;u&gt;type에 따라 데이터가 달라지기 때문이다.&lt;/u&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;먼저 type을 enum으로 정의하자.&lt;/p&gt;
&lt;pre class=&quot;dart&quot;&gt;&lt;code&gt;enum QuizType {
  @JsonValue('MULTIPLE_CHOICE')
  multipleChoice,

  @JsonValue('ORDERING')
  order,
}

extension QuizTypeExtension on String {
  QuizType toQuizType() {
    switch (this) {
      case 'MULTIPLE_CHOICE':
        return QuizType.multipleChoice;
      case 'ORDERING':
        return QuizType.order;
      default:
        throw ArgumentError.value(this, 'value', 'Invalid QuizType value');
    }
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 Detail 모델을 만들기 전에 먼저 QuizDataModel을 만들자.&lt;/p&gt;
&lt;pre class=&quot;dart&quot;&gt;&lt;code&gt;@freezed
sealed class QuizDataModel with _$QuizDataModel {
  const factory QuizDataModel.ordering({
    required String description,
    required List&amp;lt;QuizOption&amp;gt; options,
  }) = QuizDataOrdering;

  const factory QuizDataModel.multipleChoice({
    required String description,
    required List&amp;lt;QuizOption&amp;gt; options,
    required int answer,
  }) = QuizDataMultipleChoice;

  factory QuizDataModel.fromJson(Map&amp;lt;String, dynamic&amp;gt; json) =&amp;gt;
      _$QuizDataModelFromJson(json);
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;freezed의 union 기능을 이용한다면, &lt;code&gt;QuizDataModel&lt;/code&gt;을 implements하는 &lt;code&gt;QuizDataOrdering&lt;/code&gt;, &lt;code&gt;QuizDataMultipleChoice&lt;/code&gt;를 쉽게 만들 수 있다. fromJson 메소드도 두 타입 각각 알아서 잘 생성이 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 QuizDetailModel을 만들자.&lt;/p&gt;
&lt;pre class=&quot;scala&quot;&gt;&lt;code&gt;class QuizDetailModel extends QuizStatusModel {
  final QuizType type;
  final QuizDataModel data;

  QuizDetailModel({
    required this.type,
    required this.data,
    required super.id,
    required super.answerStatus,
  });

    factory QuizDetailModel.fromJson(Map&amp;lt;String, dynamic&amp;gt; json) {
        // fromJson 구현
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 fromJson을 직접 구현해야 한다. type에 맞추어서 각각의 QuizDataModel에 대해 Json 파싱을 진행하는 코드를 만들어보자.&lt;/p&gt;
&lt;pre class=&quot;dart&quot;&gt;&lt;code&gt;factory QuizDetailModel.fromJson(Map&amp;lt;String, dynamic&amp;gt; json) {
  final id = json['id'] as Id;
  final type = (json['type'] as String).toQuizType();
  final data = json['data'] as Map&amp;lt;String, dynamic&amp;gt;;
  final answerStatus = (json['answerStatus'] as int).toAnswerStatus();

  if (type == QuizType.multipleChoice) {
    return QuizDetailModel(
      id: id,
      answerStatus: answerStatus,
      type: QuizType.multipleChoice,
      data: QuizDataMultipleChoice.fromJson(data),
    );
  } else {
    return QuizDetailModel(
      id: id,
      answerStatus: answerStatus,
      type: QuizType.order,
      data: QuizDataOrdering.fromJson(data),
    );
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;id, type, data, answerStatus는 공통으로 있는 필드이므로 그대로 직렬화한다.&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;if 문이 중요한 데, 타입에 맞추어서 &lt;code&gt;data: QuizDataMultipleChoice.fromJson(data)&lt;/code&gt;, &lt;code&gt;data: QuizDataOrdering.fromJson(data)&lt;/code&gt; 를 다르게 파싱해주면 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;한편 QuizOption 모델은 아래와 같다.&lt;/p&gt;
&lt;pre class=&quot;scala&quot;&gt;&lt;code&gt;@freezed
class QuizOption with _$QuizOption {
  const factory QuizOption({
    required int number,
    String? description,
    String? imageUrl,
  }) = _QuizOption;

  factory QuizOption.fromJson(Map&amp;lt;String, dynamic&amp;gt; json) =&amp;gt;
      _$QuizOptionFromJson(json);
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;QuizDetailModel의 구현은 다소 복잡했기에 전체 코드를 남긴다.&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;/p&gt;
&lt;div data-ke-type=&quot;moreLess&quot; data-text-more=&quot;더보기&quot; data-text-less=&quot;닫기&quot;&gt;&lt;a class=&quot;btn-toggle-moreless&quot;&gt;더보기&lt;/a&gt;
&lt;div class=&quot;moreless-content&quot;&gt;
&lt;pre id=&quot;code_1709324986242&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;enum QuizType {
  @JsonValue('MULTIPLE_CHOICE')
  multipleChoice,

  @JsonValue('ORDERING')
  order,
}

extension QuizTypeExtension on String {
  QuizType toQuizType() {
    switch (this) {
      case 'MULTIPLE_CHOICE':
        return QuizType.multipleChoice;
      case 'ORDERING':
        return QuizType.order;
      default:
        throw ArgumentError.value(this, 'value', 'Invalid QuizType value');
    }
  }
}

/// ### 퀴즈 모델
///
/// - [id] 퀴즈의 ID
/// - [type] 퀴즈의 타입
///   - [QuizType.multipleChoice] 객관식 퀴즈
///   - [QuizType.order] 순서 맞추기 퀴즈
/// - [item] 퀴즈의 내용
///
/// 예시:
/// ```json
/// {
///   &quot;id&quot;: 1,
///   &quot;type&quot;: &quot;MULTIPLE_CHOICE&quot;, // &quot;ORDERING&quot; or &quot;MULTIPLE_CHOICE&quot;
///   &quot;item&quot;: {
///     &quot;description&quot;: &quot;지진 대피소 표시로 올바른 것을 고르세요&quot;,
///     &quot;answer&quot;: 1,
///     &quot;options&quot;: [
///       {
///         &quot;id&quot;: &quot;0&quot;,
///         &quot;description&quot;: &quot;지진 대피소 표시 1&quot;
///       },
///       {
///         &quot;id&quot;: &quot;1&quot;,
///         &quot;description&quot;: &quot;지진 대피소 표시 2&quot;,
///         &quot;imageUrl&quot;: &quot;images/2.jpg&quot;
///       },
///       {
///         &quot;id&quot;: &quot;2&quot;,
///         &quot;description&quot;: &quot;지진 대피소 표시 3&quot;,
///         &quot;imageUrl&quot;: &quot;images/3.jpg&quot;
///       },
///       {
///         &quot;id&quot;: &quot;3&quot;,
///         &quot;description&quot;: &quot;지진 대피소 표시 4&quot;,
///         &quot;imageUrl&quot;: &quot;images/4.jpg&quot;
///       }
///     ]
///   }
/// }
/// ```
class QuizDetailModel extends QuizStatusModel {
  final QuizType type;
  final QuizDataModel data;

  QuizDetailModel({
    required this.type,
    required this.data,
    required super.id,
    required super.answerStatus,
  });

  factory QuizDetailModel.fromJson(Map&amp;lt;String, dynamic&amp;gt; json) {
    final id = json['id'] as Id;
    final type = (json['type'] as String).toQuizType();
    final data = json['data'] as Map&amp;lt;String, dynamic&amp;gt;;
    final answerStatus = (json['answerStatus'] as int).toAnswerStatus();

    if (type == QuizType.multipleChoice) {
      return QuizDetailModel(
        id: id,
        answerStatus: answerStatus,
        type: QuizType.multipleChoice,
        data: QuizDataMultipleChoice.fromJson(data),
      );
    } else {
      return QuizDetailModel(
        id: id,
        answerStatus: answerStatus,
        type: QuizType.order,
        data: QuizDataOrdering.fromJson(data),
      );
    }
  }
}

/// 퀴즈 아이템 모델
///
/// [QuizItemModel.ordering]과 [QuizItemModel.multipleChoice] 두 가지가 존재함
@freezed
sealed class QuizDataModel with _$QuizDataModel {
  /// 순서 맞추기 퀴즈 타입\
  /// 이때 options.id가 그대로 순서가 된다
  ///
  /// `answer` 필드가 없음
  ///
  /// 예시:
  /// ```json
  /// {
  ///   &quot;description&quot;: &quot;지진 대피소 표시로 올바른 것을 고르세요&quot;,
  ///   &quot;options&quot;: [
  ///     {
  ///       &quot;id&quot;: 1,
  ///       &quot;description&quot;: &quot;지진 대피소 표시 1&quot;
  ///     },
  ///     {
  ///       &quot;id&quot;: 2,
  ///       &quot;description&quot;: &quot;지진 대피소 표시 2&quot;,
  ///       &quot;imageUrl&quot;: &quot;images/2.jpg&quot;
  ///     },
  ///     {
  ///       &quot;id&quot;: 3,
  ///       &quot;description&quot;: &quot;지진 대피소 표시 3&quot;,
  ///       &quot;imageUrl&quot;: &quot;images/3.jpg&quot;
  ///     },
  ///     {
  ///       &quot;id&quot;: 4,
  ///       &quot;description&quot;: &quot;지진 대피소 표시 4&quot;,
  ///       &quot;imageUrl&quot;: &quot;images/4.jpg&quot;
  ///     }
  ///   ]
  /// }
  /// ```
  const factory QuizDataModel.ordering({
    required String description,
    required List&amp;lt;QuizOption&amp;gt; options,
  }) = QuizDataOrdering;

  /// 객관식 퀴즈 타입
  ///
  /// 예시:
  /// ```json
  /// {
  ///   &quot;description&quot;: &quot;지진 대피소 표시로 올바른 것을 고르세요&quot;,
  ///   &quot;answer&quot;: 1,
  ///   &quot;options&quot;: [
  ///     {
  ///       &quot;id&quot;: &quot;0&quot;,
  ///       &quot;description&quot;: &quot;지진 대피소 표시 1&quot;
  ///     },
  ///     {
  ///       &quot;id&quot;: &quot;1&quot;,
  ///       &quot;description&quot;: &quot;지진 대피소 표시 2&quot;,
  ///       &quot;imageUrl&quot;: &quot;images/2.jpg&quot;
  ///     },
  ///     {
  ///       &quot;id&quot;: &quot;2&quot;,
  ///       &quot;description&quot;: &quot;지진 대피소 표시 3&quot;,
  ///       &quot;imageUrl&quot;: &quot;images/3.jpg&quot;
  ///     },
  ///     {
  ///       &quot;id&quot;: &quot;3&quot;,
  ///       &quot;description&quot;: &quot;지진 대피소 표시 4&quot;,
  ///       &quot;imageUrl&quot;: &quot;images/4.jpg&quot;
  ///     }
  ///   ]
  /// }
  /// ```
  const factory QuizDataModel.multipleChoice({
    required String description,
    required List&amp;lt;QuizOption&amp;gt; options,
    required int answer,
  }) = QuizDataMultipleChoice;

  factory QuizDataModel.fromJson(Map&amp;lt;String, dynamic&amp;gt; json) =&amp;gt;
      _$QuizDataModelFromJson(json);
}

/// 퀴즈의 선택지
///
/// - [id] 선택지 id는 `int`여야 함
///   - 만약 [ORDERING] 타입의 퀴즈라면, id의 순서가 정답이 됨
/// - [description] 퀴즈 설명
/// - [imageUrl] 퀴즈 이미지
///
/// **둘 중 하나는 있어야 함**
@freezed
class QuizOption with _$QuizOption {
  const factory QuizOption({
    required int number,
    String? description,
    String? imageUrl,
  }) = _QuizOption;

  factory QuizOption.fromJson(Map&amp;lt;String, dynamic&amp;gt; json) =&amp;gt;
      _$QuizOptionFromJson(json);
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;레포지토리를 만들어보자&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Repository Layer에서 가장 중요한 Repository를 만들 차례다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;레포지토리 인터페이스로 묶기&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서도 마찬가지로 인터페이스부터 만들자. 엔티티 모델 클래스를 &lt;code&gt;IModelWithId&lt;/code&gt;로 묶은 보람이 여기서부터 드러난다.&lt;/p&gt;
&lt;pre class=&quot;scala&quot;&gt;&lt;code&gt;abstract interface class IModelListRepository&amp;lt;T extends IModelWithId&amp;gt; {
  Future&amp;lt;ModelList&amp;lt;T&amp;gt;&amp;gt; fetch();
}

abstract interface class IDetailRepository&amp;lt;T extends IModelWithId&amp;gt;
    implements IModelListRepository&amp;lt;T&amp;gt; {
  Future&amp;lt;T&amp;gt; getDetail({required Id id});
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Dart에서도 C++의 &lt;a href=&quot;https://en.cppreference.com/w/cpp/language/constraints&quot;&gt;template concept&lt;/a&gt;같은 기능을 제공한다. &lt;code&gt;&amp;lt;T extends IModelWithId&amp;gt;&lt;/code&gt; 로 제네릭을 작성하면, 타입 T는 &lt;code&gt;IModelWithId&lt;/code&gt;를 implements, extends하는 타입만 올 수 있게 된다.&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;한편 현재까지의 개발 과정에서 모든 레포지토리는 &lt;code&gt;fetch&lt;/code&gt;, &lt;code&gt;getDetail&lt;/code&gt;을 사용한다. 그럼에도 두 레포지토리를 나눠놓은 이유는 따로 있는데, 일부 레포지토리의 경우 &lt;code&gt;fetch&lt;/code&gt;는 사용하는 데 &lt;code&gt;getDetail&lt;/code&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;그게 rankRepository이다. 원래는 유저의 퀴즈 성적, CPR과 같은 Pose Estimation 성적을 기반으로 랭킹을 매기려고 했었다. 그런데 개발 과정에서 어느샌가 랭킹이 사라졌고, 자연스레 레포지토리를 구분해놓은 의미 역시 퇴색되었다&amp;hellip;.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Retrofit을 이용해서 Repository 구현하기&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;모델만 잘 정의하다면 Retrofit을 이용하여 RESTful API 레포지토리를 아주 쉽게 구현할 수 있다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;Education, Quiz Repository&lt;/h4&gt;
&lt;pre class=&quot;kotlin&quot;&gt;&lt;code&gt;final educationRepositoryProvider = Provider&amp;lt;EducationRepository&amp;gt;(
  (ref) {
    final dio = ref.watch(dioProvider);

    return EducationRepository(dio);
  },
);

@RestApi(baseUrl: '/education')
abstract class EducationRepository
    implements IDetailRepository&amp;lt;EducationModel&amp;gt; {
  factory EducationRepository(Dio dio) = _EducationRepository;

  @override
  @GET('')
  @Headers({'accessToken': 'true'})
  Future&amp;lt;ModelList&amp;lt;EducationModel&amp;gt;&amp;gt; fetch();

  @override
  @GET('/{id}')
  @Headers({'accessToken': 'true'})
  Future&amp;lt;EducationDetailModel&amp;gt; getDetail({
    @Path() required Id id,
  });
}&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;kotlin&quot;&gt;&lt;code&gt;final quizRepositoryProvider = Provider&amp;lt;QuizRepository&amp;gt;(
  (ref) {
    final dio = ref.watch(dioProvider);

    return QuizRepository(dio);
  },
);

@RestApi(baseUrl: '/quiz')
abstract class QuizRepository implements IDetailRepository&amp;lt;QuizStatusModel&amp;gt; {
  factory QuizRepository(Dio dio) = _QuizRepository;

    /// depreciated
  @override
  @GET('/')
  @Headers({'accessToken': 'true'})
    @Deprecated('Quiz fetch does not work') // 실제로는 작동하지 않는 함수임
  Future&amp;lt;ModelList&amp;lt;QuizStatusModel&amp;gt;&amp;gt; fetch();

  @override
  @GET('/{id}')
  @Headers({'accessToken': 'true'})
  Future&amp;lt;QuizDetailModel&amp;gt; getDetail({
    @Path() required Id id,
  });

  @POST('/{id}')
  @Headers({
    'accessToken': 'true',
    'Content-Type': 'application/json',
  })
  Future&amp;lt;void&amp;gt; submit({
    @Path() required Id id,
    @Body() required UserAnswerModel userAnswer,
  });
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;헤더에 붙은 &lt;code&gt;accessToken&lt;/code&gt;에 대한 내용은 이전에 쓴 &lt;a href=&quot;https://nx006.tistory.com/72&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;JWT 토큰에 관한 내용&lt;/a&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;@RestApi(baseUrl: '/quiz')&lt;/code&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 붙는 baseUrl은 dio에 붙는 baseUrl 뒤에 덧붙여진다.&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;그래서 dio에서 서버의 baseUrl을 지정했으면, 위 퀴즈 레포지토리는 알아서 &lt;code&gt;/{server-url}/quiz&lt;/code&gt; 이런 식으로 path가 설정된다.&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;한편 QuizRepository의 fetch 함수의 경우, &lt;b&gt;사실은 사용되지 않는 함수&lt;/b&gt;이다. 왜냐면 &lt;code&gt;List&amp;lt;QuizStatusModel&amp;gt;&lt;/code&gt;의 경우 이미 &lt;code&gt;EducationDetailModel&lt;/code&gt;을 받아올 때 내부 속성으로 갖고 있기 때문이다. 또한 &lt;code&gt;GET /quiz&lt;/code&gt;라는 url 자체가 존재하지 않는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 함수를 구현한 이유는 순전히 상속 과정에서 &lt;code&gt;fetch&lt;/code&gt; 함수를 구현할 문법상의 필요가 있었기 때문이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그렇기 때문에 사용하지 말라는 의미에서 &lt;code&gt;@Depreciated&lt;/code&gt; Annotation을 붙여주었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;한편 퀴즈를 풀었으면, 퀴즈를 풀었다고 서버에 알려야 하기 때문에 submit 함수도 추가된다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;ActionRepository&lt;/h4&gt;
&lt;pre class=&quot;kotlin&quot;&gt;&lt;code&gt;final actionRepositoryProvider = Provider&amp;lt;ActionRepository&amp;gt;(
  (ref) {
    final dio = ref.watch(dioProvider);
    final env = ref.watch(envProvider);

    return ActionRepository(dio, baseUrl: '${env.actionApiUrl}/action');
  },
);

@RestApi()
abstract class ActionRepository implements IDetailRepository&amp;lt;ActionModel&amp;gt; {
  factory ActionRepository(Dio dio, {String baseUrl}) = _ActionRepository;

  @override
  @GET('/')
  @Headers({'accessToken': 'true'})
  Future&amp;lt;ModelList&amp;lt;ActionModel&amp;gt;&amp;gt; fetch();

  @override
  @GET('/{id}')
  @Headers({'accessToken': 'true'})
  Future&amp;lt;ActionDetailModel&amp;gt; getDetail({
    @Path() required Id id,
  });
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;한편 Action의 경우 baseUrl 자체가 완전히 달라지기에 위와 같이 해야 한다.&lt;/p&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;ActionRepository(dio, baseUrl: '${env.actionApiUrl}/action');
factory ActionRepository(Dio dio, {String baseUrl}) = _ActionRepository;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 지정해주어야 dio의 base url을 신경쓰지 않고, 완전히 새로운 url로 덮어쓴다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사실 원래 프론트 상에서 서버의 변화를 신경쓰면 잘못된 거긴 하다. 원래의 정석적인 개발은, 서버 단에서 API 게이트웨이를 만들고, 모바일에서는 언제나 같은 서버 url로 요청을 보내야 한다. 서버에서 redirection 등을 통해서 firebase functions 등의 url로 요청을 보내는 게 정석적인 방법이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다만 서버 측 개발자들이 이런 방식을 시도해본 경험 자체가 전무했기에, 빠른 개발을 위해서 프론트 단에서 서로 다른 서버 주소로 요청을 보내도록 개발했다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;mixin 사용을 시도해봤다&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;한편 fetch, getDetail 함수를 mixin으로 사용하는 방법도 고민했다. 이 부분을 사실 꽤 많이 고민했는데, getDetail만 있고 fetch가 없는 경우도 고려해봐야 하기 때문이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;왜냐면 사실 QuizRepository의 경우 fetch 함수가 없다. &lt;code&gt;GET /quiz&lt;/code&gt;라는 url이 존재하지 않기 때문이다. 그런데 QuizRepository에서 getDetail을 사용하고 싶은 경우, 위 2차 계층 상속 구조에서는 fetch 함수 역시 구현해야 한다. Retrofit을 사용하기에 fetch 함수를 일단 구현해놓고, QuizStateNotifier의 fetch 함수에서 repository의 fetch 함수를 사용하지 않도록, 아무 의미없는 빈 로직을 오버라이드하였다. 이게 얼마나 비효율적인 구현인가&amp;hellip;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 mixin을 사용해서, EducationRepository의 경우 fetch, getDetail을 모두 with로 붙이고, QuizRepository의 경우 getDetail만 with로 붙이는 방법을 고민했다.&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;abstract interface class IModelListRepository&amp;lt;T extends IModelWithId&amp;gt; {
  Future&amp;lt;ModelList&amp;lt;T&amp;gt;&amp;gt; fetch();
}

mixin GetDetailRepositoryMixin&amp;lt;T extends IModelWithId&amp;gt; {
  Future&amp;lt;T&amp;gt; getDetail({required Id id});
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다만 이 방식은 provider를 구현하는 단계에서 막혔다.&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;class ModelListProvider&amp;lt;Model extends IModelWithId,
        Repository extends IModelListRepository&amp;lt;Model&amp;gt;&amp;gt;
    extends StateNotifier&amp;lt;ModelListState&amp;gt; {
  final Repository repository;

  ModelListProvider({
    required this.repository,
  }) : super(ModelListLoading());

  Future&amp;lt;void&amp;gt; fetch() async {
    // 구현 생략
  }
}

mixin DetailProviderMixin&amp;lt;Model extends IModelWithId,
        Repository extends IModelListRepository&amp;lt;Model&amp;gt; with GetDetailRepositoryMixin&amp;gt; // 여기서 문제 발생  
    on ModelListProvider&amp;lt;Model, Repository&amp;gt; {
  Future&amp;lt;void&amp;gt; getDetail({
    required Id id,
  }) async {
    // 만약 데이터가 없다면 fetch()를 호출
    if (state is! ModelList) {
      await fetch();
    }

    // 구현 생략

    final response = await repository.getDetail(id: id); // 문제 발생

    // 구현 생략
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이론상 여기까지만 잘 되면 좋았겠으나&amp;hellip;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1057&quot; data-origin-height=&quot;542&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/U3HI7/btsFoDRSe56/08amnhphyJV6Ky1i6NjJXK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/U3HI7/btsFoDRSe56/08amnhphyJV6Ky1i6NjJXK/img.png&quot; data-alt=&quot;결국에 실패...&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/U3HI7/btsFoDRSe56/08amnhphyJV6Ky1i6NjJXK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FU3HI7%2FbtsFoDRSe56%2F08amnhphyJV6Ky1i6NjJXK%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; alt=&quot;Error&quot; loading=&quot;lazy&quot; width=&quot;1057&quot; height=&quot;542&quot; data-origin-width=&quot;1057&quot; data-origin-height=&quot;542&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;결국에 실패...&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;pre class=&quot;actionscript&quot;&gt;&lt;code&gt;Repository extends IModelListRepository&amp;lt;Model&amp;gt; with GetDetailRepositoryMixin // with 지원 안 함&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;제네릭 내부에서 with를 지원하지 않아서 턱하고 막혔다&amp;hellip; 결국 많은 고민을 한 이 mixin을 이용하는 방법은 실패로 돌아갔다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style8&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Repository까지 구현했다. 잘 추상화된 Repository가 구현되었으므로, 이제는 이를 이용해서 Service Layer를 개발할 차례이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음 개발일지에서 캐싱 로직을 어떻게 일반화하였는 지 작성하겠다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 으레 그렇듯이 개발할 때는 몰랐지만 지금 돌아보면 개선할 점들이 조금씩 보이기 시작한다. API 명세부터 벌써 이게 맞나 의심이 들기 시작하지만 어쩌겠어&amp;hellip; 이렇게 배워가는 거겠지.&lt;/p&gt;</description>
      <category>개발일지</category>
      <category>개발일지</category>
      <author>nx006</author>
      <guid isPermaLink="true">https://nx006.tistory.com/79</guid>
      <comments>https://nx006.tistory.com/79#entry79comment</comments>
      <pubDate>Sat, 2 Mar 2024 05:37:08 +0900</pubDate>
    </item>
    <item>
      <title>Solution Challenge 2024 참여 후기</title>
      <link>https://nx006.tistory.com/78</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;Solution Challenge 2024&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2000&quot; data-origin-height=&quot;1077&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/4wxcK/btsFisJIEoQ/tSLpCvgKgKkstCqetTLaZk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/4wxcK/btsFisJIEoQ/tSLpCvgKgKkstCqetTLaZk/img.png&quot; data-alt=&quot;Solution Challenge 2024&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/4wxcK/btsFisJIEoQ/tSLpCvgKgKkstCqetTLaZk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F4wxcK%2FbtsFisJIEoQ%2FtSLpCvgKgKkstCqetTLaZk%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; alt=&quot;Solution Challenge 2024&quot; loading=&quot;lazy&quot; width=&quot;2000&quot; height=&quot;1077&quot; data-origin-width=&quot;2000&quot; data-origin-height=&quot;1077&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;Solution Challenge 2024&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;GDSC Solution Challenge 2024에 참여하게 되었다.&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;사실 작년 2023년에도 참여했었는데, 이때는 나를 포함한 팀원 모두가 첫 프로젝트라 많은 시행착오가 있었다. 하필 HIBL의 프로젝트 시기와도 동일하게 겹치는 시기라, 어려운 프로젝트 두 개를 동시에 진행하는 시기였다. 그래서 갈등도 있었고, 고생도 많았다.&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;/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;/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;/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;솔&amp;hellip; 직히 이런 권유를 받았으니 참여를 안 할 수가 없다. 원래는 거절하려고 했으나, 솔챌이 나를 부르는 소리가 마음 속 깊이 들려오는 데&amp;hellip; 이걸 참아? 이틀 뒤에 재권유가 왔을 때 참여했다.&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;팀원 자체는 백엔드 2명, AI 한 명, 모바일 1명으로 안정적인 구성이었다. 한 가지 걱정이 되는 점이라면 디자이너의 부재인데, 솔루션 챌린지 자체가 기획과 디자인의 영역이 매우 큰 만큼 이 부분은 약점으로 느껴졌다. 하지만 다행히 팀에 디자인 능력자 분들이 두 분이나 계셔서, 약점인 줄 알았던 디자인 영역은 오히려 우리 팀의 강점으로 작용했다.&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;AI를 맡으신 분이 경험이나 실력이 많으셔서 만능이셨는데, 덕분에 AI 쪽 모델 개발이 매우 수월하게 진행됐다. 이쪽이 쉽게 해결이 되니깐 기획 과정에서 많은 것들을 시도할 수 있었고, 개발이 매우 편했던 것 같다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;기획&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;솔루션 챌린지는 기획이 제일 중요하다. 기획부터 시작하여 기획으로 끝난다. 구현은 사실 부차적인 문제이다.&lt;/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;심지어 작년 2023년, TOP 100위에 들었던 우리 학교의 한 팀은 구현을 끝마치지도 못한 채로 제출하였다. 그 팀은 아이디어가 매우 좋았는데(음식과 기부를 엮은 기획이었다), 구현을 끝내지 못한 게 아쉽다고 생각했었다. 구현을 못 하면 우승이 어려울 것이라 생각했다. 그런데 발표날 보니깐 구현이 안 되었어도, TOP 100에 들 수 있더라. 구현 여부와 관계 없이 아이디어로 승부보는 게 솔루션 챌린지이다.&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;그렇기 때문에 이번에는 개발을 급하게 시작하는 데 치중하지 않고, 기획에 많은 공을 들였다. 선술한 예의 팀은 심지어 중간 발표 이후 2월 달에 급하게 기획의 대전환을 거친 뒤에 매우 짧은 기간 동안 구현한 뒤 우승하였다. 앞에 사례에서 기획에 많은 시간을 할애해야 한다는 교훈을 얻었다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;헬스케어&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기획 단계에서 다양한 아이디어들이 논의되었다. 가장 먼저 제시된 것은 의료 케어 서비스였다. 개발도상국에서 의료 서비스를 받지 못하는 사람들을 대상으로 모바일로 간편하게 이용할 수 있는 의료 케어 서비스 아이디어가 제시되었다. 아이디어를 제시하신 AI를 맡으신 분이 HCI 분야, 그리고 모바일 기기로 심장 펄스를 측정하는 기술 등을 연구하셨다고 했다. 이 연구 결과가 아이디어를 선정하는 데 영향을 주지 않았나 싶다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;648&quot; data-origin-height=&quot;1312&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b3BXHh/btsFhnB8roW/Irwap9R8qoHilKfWqgHihK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b3BXHh/btsFhnB8roW/Irwap9R8qoHilKfWqgHihK/img.png&quot; data-alt=&quot;초기에 시도된 헬스케어 앱의 피그마 디자인 일부&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b3BXHh/btsFhnB8roW/Irwap9R8qoHilKfWqgHihK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb3BXHh%2FbtsFhnB8roW%2FIrwap9R8qoHilKfWqgHihK%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; alt=&quot;초기에 시도된 헬스케어 앱의 피그마 디자인 일부&quot; loading=&quot;lazy&quot; width=&quot;532&quot; height=&quot;1077&quot; data-origin-width=&quot;648&quot; data-origin-height=&quot;1312&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;초기에 시도된 헬스케어 앱의 피그마 디자인 일부&lt;/figcaption&gt;
&lt;/figure&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;매우 좋은 아이디어였는데, 치명적인 단점이라면 비슷한 아이디어가 이미 솔루션 챌린지 지난 회차에서 많이 제출되기는 했었다&amp;hellip; 헬스 케어 이쪽은 워낙에 관심도가 높은 분야이다 보니깐 아이디어 자체도 많이 시도되었고, 이중 지난 솔루션 챌린지 TOP 100에 선정된 제출물들도 꽤 되었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;작년에 제출된 솔루션이랑 아이디어가 겹치면 새롭게 상을 받기에는 한계가 있다. 게다가 여러 기술적인 한계(모바일 기기로 생체 데이터를 측정할 시 정확도가 아직은 높지 않다는 문제 등)가 있어, 이 아이디어를 보완할 새로운 시도가 필요했다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;알레르기&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;누가 말을 했는지는 기억이 안 나는데, 아마 백엔드를 맡으신 분으로 기억한다. 알레르기에 대한 아이디어를 제시했다. 정말 괜찮은 아이디어였는데, 바로 음식에 들어있는 알레르기를 쉽게 파악할 수 있도록 정보를 제공하는 아이디어였다. 알레르기 자체가 꽤 중요한 문제인만큼, 솔루션 챌린지에서 이목을 끌기에 괜찮은 주제같았다. 하지만 이 역시 한계가 두 가지 있었는데&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;알레르기를 유발할 가능성이 있는 식품의 첨가 여부를 어떻게 판단할 것인가? 기술적 한계가 있었다.&lt;/li&gt;
&lt;li&gt;제일 중요하게 &lt;a href=&quot;https://www.allergyalyozo.com/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;알레르기에 관련된 서비스&lt;/a&gt;가 이미 출시된 적이 있었다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;좋은 아이디어였으나 이마저 파기되었다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;안전 교육 앱&lt;/h3&gt;
&lt;p 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;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;duolingo.webp&quot; data-origin-width=&quot;1050&quot; data-origin-height=&quot;523&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dueGNW/btsFhTHBPAq/kViIHcHIcF4ffA5pTd9lEK/img.webp&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dueGNW/btsFhTHBPAq/kViIHcHIcF4ffA5pTd9lEK/img.webp&quot; data-alt=&quot;언어 학습 앱 Duolingo&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dueGNW/btsFhTHBPAq/kViIHcHIcF4ffA5pTd9lEK/img.webp&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdueGNW%2FbtsFhTHBPAq%2FkViIHcHIcF4ffA5pTd9lEK%2Fimg.webp&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; alt=&quot;언어 학습 앱 Duolingo&quot; loading=&quot;lazy&quot; width=&quot;1050&quot; height=&quot;523&quot; data-filename=&quot;duolingo.webp&quot; data-origin-width=&quot;1050&quot; data-origin-height=&quot;523&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;언어 학습 앱 Duolingo&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;안전 교육을 쉽게 전달하는 앱&lt;/b&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-ke-size=&quot;size16&quot;&gt;듀오링고는 언어 교육 앱인데, 귀여운 자체 캐릭터(부엉이가 나온다)가 나와서 굉장히 친숙하게 언어 학습을 도와준다. 퀴즈 형식인데, 예전에 일본어를 배울 때 재미있게 사용한 기억이 난다(재미있게 사용만 했지 일본어를 배우지는 못 했다&amp;hellip;).&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;여기서 아이디어를 차용해서, 귀여운 동물 캐릭터와 함께 안전을 쉽게 알려주는 퀴즈 앱을 제시했다. 예를 들어서 자체적인 캐릭터(사자나 호랑이같은 동물 캐릭터)를 만들고, 그 캐릭터가 CPR을 하는 모습을 보여준다든지&amp;hellip; 그런 아이디어였다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;lackofedu.png&quot; data-origin-width=&quot;3166&quot; data-origin-height=&quot;1348&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/RfrYY/btsFm6yFAKU/ri0heRi9j908Cwc6nBuO3k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/RfrYY/btsFm6yFAKU/ri0heRi9j908Cwc6nBuO3k/img.png&quot; data-alt=&quot;안전 교육에 대한 설문 조사&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/RfrYY/btsFm6yFAKU/ri0heRi9j908Cwc6nBuO3k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FRfrYY%2FbtsFm6yFAKU%2Fri0heRi9j908Cwc6nBuO3k%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; alt=&quot;안전 교육에 대한 설문 조사&quot; loading=&quot;lazy&quot; width=&quot;3166&quot; height=&quot;1348&quot; data-filename=&quot;lackofedu.png&quot; data-origin-width=&quot;3166&quot; data-origin-height=&quot;1348&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;안전 교육에 대한 설문 조사&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;안전 교육은 정확한 정보를 전달하는 것도 중요하지만, &lt;b&gt;사용자에게 정보가 전달이 되는 것 자체&lt;/b&gt;도 중요하다. 대부분의 안전 교육 현장에서 내용에만 초점을 맞추고 있지, &lt;u&gt;이를 &amp;lsquo;&lt;b&gt;어떻게&lt;/b&gt;&amp;rsquo; 전달할 지에 대해서는 그다지 관심을 갖지 않는다.&lt;/u&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;/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;지난 2023년 10월, 한국은 서울 이태원에서 150명이 사망하는 비극적인 사건을 겪었다. 전 국민이 놀라움과 충격을 공유했는데, 그중에는 &amp;lsquo;저게 어떻게 저렇게 많은 사람들이 사망하는 참사로 번질 수가 있느냐&amp;rsquo;는 놀라움이었다. 그 중에서도 기억에 남는 한 가지 글이 있다. 과거에 위기탈출 넘버원이라는 프로그램을 재조명하는 글이었다. 위기탈출 넘버원에서 사람들이 많을 때, 가슴 앞으로 팔을 X자를 그리면, 흉부의 공간을 확보하면서 압박을 최소화할 수 있음을 소개한 적이 있다. 만약에 사람들이 이 대처를 알았더라면, 발생한 피해를 조금이라도 더 최소화시킬 수 있었을 지도 모른다.&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;나 역시 이때 CPR을 제대로 배워봐야겠다고 생각하기도 했었고, 또 안전 교육이 중요하구나(위기탈출 넘버원이 오버스러운 게 많아서 그렇지 은근 유용했구나)라는 생각을 갖게 되었다.&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;사실 대부분의 사고가 그렇다. 적지 않은 사고들은 예방 가능했고, 이미 발생한 사고에 대해서는&amp;nbsp;&lt;u&gt;피해를 최소화할 수 있었다.&lt;/u&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;u&gt;어떻게 최소화할 것인지이다.&lt;/u&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;당연하지만 피해가 발생하고 이를 찾고 있으면 늦는다! 미리 알고 있어야 한다! 그렇기에 평소에 기본적인 안전 교육이 중요한 것이고, CPR 정도는 배워둬야 하는 이유이다.&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;u&gt;학생들이 쉽게 집중력을 잃는 문제점을 갖고 있다.&lt;/u&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;/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;아이디어는 매우 호평을 받았고, 결론적으로 이 아이디어를 디벨롭하기로 결정했다. 이때가 1월 중반이었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;applsci-12-04847-g003.webp&quot; data-origin-width=&quot;1623&quot; data-origin-height=&quot;550&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/quqZS/btsFhfKQIit/91vKVGq5ax4MHkSQ5ixfx0/img.webp&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/quqZS/btsFhfKQIit/91vKVGq5ax4MHkSQ5ixfx0/img.webp&quot; data-alt=&quot;CPR Pose Estimation, 출처: https://www.mdpi.com/2076-3417/12/10/4847&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/quqZS/btsFhfKQIit/91vKVGq5ax4MHkSQ5ixfx0/img.webp&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FquqZS%2FbtsFhfKQIit%2F91vKVGq5ax4MHkSQ5ixfx0%2Fimg.webp&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; alt=&quot;CPR Pose Estimation&quot; loading=&quot;lazy&quot; width=&quot;1623&quot; height=&quot;550&quot; data-filename=&quot;applsci-12-04847-g003.webp&quot; data-origin-width=&quot;1623&quot; data-origin-height=&quot;550&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;CPR Pose Estimation, 출처: https://www.mdpi.com/2076-3417/12/10/4847&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또한 회의 과정에서 아이디어는 매우 발전되었다. 그냥 한 번 툭 던져본 건데, &lt;u&gt;&lt;b&gt;CPR을 동영상으로 찍으면 이를 피드백할 수 있는 시스템&lt;/b&gt;&lt;/u&gt;에 관한 아이디어였다. 원래 이 아이디어는 비현실적이라 생각했다. 왜냐면 이를 인공지능으로 자동화하기에는 어려울 것이라 생각했고, 사람이 수동으로 피드백할 경우 피드백을 제공할 동기가 필요했고, 또한 그 피드백을 제공하는 사람들 또한 전문가의 영역이여야 했다. 이는 단순히 헬스 커뮤니티에서 스쿼트 자세를 평가해달라는 것과 차원이 다른 문제다. CPR과 같은 의료 행위의 포즈 평가는 전문 의료인의 평가가 필요했고, 전문 의료인이 앱에서 다른 사람들의 자세를 피드백할 동기를 디자인하는 건 사실상 불가능했다.&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;Pose estimation&lt;/b&gt;에 관한 연구인데, 여러 pose estimation 모델을 통해 학습된 정답 CPR Pose와 사용자가 영상으로 남긴 Pose를 비교하여, 유사도를 검사한다면, 이 기능이 가능할 지도 모른다고 가능성을 열어주셨다.&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;영상을 평가하는 것은 단순히 이미지의 유사도를 검사하는 작업과 차원이 다르다고 한다. 일단 타임 스케일 자체가 달라서, 예를 들어서 원본 영상은 10초, 유저가 제출한 영상이 20초라면 어떻게 할 것인가? 그리고 포즈의 속도가 다르다든지 하는 문제도 있다. 그래서 어려울 줄 알았는데 인공지능을 맡으신 분께서 이러한 타임 스케일 역시 보정해줄 수 있는 평가 모델을 또 찾아오셨더라.&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;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;앱 디자인&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1522&quot; data-origin-height=&quot;1102&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cVVBgW/btsFm66uWDs/oaNjkXXK2G1gdMZDYSqjH0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cVVBgW/btsFm66uWDs/oaNjkXXK2G1gdMZDYSqjH0/img.png&quot; data-alt=&quot;Safy 앱 디자인 피그마&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cVVBgW/btsFm66uWDs/oaNjkXXK2G1gdMZDYSqjH0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcVVBgW%2FbtsFm66uWDs%2FoaNjkXXK2G1gdMZDYSqjH0%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; alt=&quot;앱 디자인&quot; loading=&quot;lazy&quot; width=&quot;1522&quot; height=&quot;1102&quot; data-origin-width=&quot;1522&quot; data-origin-height=&quot;1102&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;Safy 앱 디자인 피그마&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;나는 디자인은 1도 모르기에 앱 디자인은 팀의 다른 두 분이 맡아주셨다. 두 분이 말아주신 디자인은 아주 훌륭했다.&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;사실 Flutter를 사용한다면 Material Design 3에 기댈 수도 있긴 하다. 하지만 Material 3는 결과가 나쁘지는 않게 나오겠지만 우리가 원하는 결과의 디자인이 나올 보장은 없다. 나도 실제로는 써본 적이 없기도 하고, 이걸 실제로 쓰는 프로젝트가 많을 지는 모르겠다. 그래서 그냥 두 분의 피그마를 기대하기로 했다. 결과는 대성공이다.&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;앱의 이름은 결론적으로 마감 직전에서야 정식으로 결정되었다. 이전에는 Little Ways to Live, SafetyEdu 등 중구난방으로 불렸는데, 마감 며칠 전에 &lt;i&gt;Safety Awareness For Youth&lt;/i&gt;, 줄여서 &lt;b&gt;SAFY&lt;/b&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;/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;예를 들어 기존에는 퀴즈를 5개 정도로 묶어 스테이지를 나누기로 했었는데, 여러 논의 사항을 거쳐 스테이지를 나누는 것도 폐기한다든지 등 변경 사항이 많았다.&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;그리고 실제 개발 과정에서, 기획 때 상의한 기능들이 구현되지 않은 경우도 있었다. 예를 들어 퀴즈의 난이도는 어느순간 개발 과정에서 제외되었고, 랭킹 시스템 역시 사라졌다. 이는 개발 기간이 너무 짧아서 생긴 문제이기는 하다. 너무 느긋하게 있었다&amp;hellip;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;중간 평가&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;GDSC Hongik에서 자체적으로 진행한 중간 평가가 있다. 이때 발표 내용을 토대로 멘토의 피드백을 받을 수 있다&lt;i&gt;(라떼는 이런 거 없었는데 올해 GDSC는 정말 많이 발전했다)&lt;/i&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;나는 이 날 하필 공군을 위한 정보처리기능사 시험이 있어서 중간 평가에 같이 참여하지는 못 했다. 정보처리기능사 필기야 컴공 학생 입장에서는 아가 다루는 수준이므로, 다행히 5분만에 끝내고 우리 팀 발표를 지켜볼 수 있었다.&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;a href=&quot;https://www.notion.so/Design-Sprint-36ba9eac1cdb4b5897ee3a5fd24f84c9?pvs=21&quot;&gt;https://joonlee.notion.site/Design-Sprint-36ba9eac1cdb4b5897ee3a5fd24f84c9?pvs=4&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1708976257411&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;Design Sprint 기획안 최종 | Notion&quot; data-og-description=&quot;개요&quot; data-og-host=&quot;joonlee.notion.site&quot; data-og-source-url=&quot;https://www.notion.so/Design-Sprint-36ba9eac1cdb4b5897ee3a5fd24f84c9?pvs=21&quot; data-og-url=&quot;https://joonlee.notion.site/36ba9eac1cdb4b5897ee3a5fd24f84c9&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/C0aRM/hyVql8qSSs/PB4BqZEfW9ElupXiO4wE61/img.png?width=2000&amp;amp;height=587&amp;amp;face=0_0_2000_587,https://scrap.kakaocdn.net/dn/diBatq/hyVqqIFVxs/hePNvZiM2EDGwzCxCGf7N1/img.png?width=2000&amp;amp;height=587&amp;amp;face=0_0_2000_587&quot;&gt;&lt;a href=&quot;https://www.notion.so/Design-Sprint-36ba9eac1cdb4b5897ee3a5fd24f84c9?pvs=21&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://www.notion.so/Design-Sprint-36ba9eac1cdb4b5897ee3a5fd24f84c9?pvs=21&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/C0aRM/hyVql8qSSs/PB4BqZEfW9ElupXiO4wE61/img.png?width=2000&amp;amp;height=587&amp;amp;face=0_0_2000_587,https://scrap.kakaocdn.net/dn/diBatq/hyVqqIFVxs/hePNvZiM2EDGwzCxCGf7N1/img.png?width=2000&amp;amp;height=587&amp;amp;face=0_0_2000_587');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;Design Sprint 기획안 최종 | Notion&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;개요&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;joonlee.notion.site&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p 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;/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;a href=&quot;https://www.notion.so/6-0c001ed4854a48d39ecd426e5f179ab4?pvs=21&quot;&gt;https://childlike-kumquat-540.notion.site/6-0c001ed4854a48d39ecd426e5f179ab4&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1708976258172&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;솔루션 챌린지 6팀 피드백 | Notion&quot; data-og-description=&quot; &amp;nbsp;프로젝트 컨셉 및 기획 의도&quot; data-og-host=&quot;childlike-kumquat-540.notion.site&quot; data-og-source-url=&quot;https://www.notion.so/6-0c001ed4854a48d39ecd426e5f179ab4?pvs=21&quot; data-og-url=&quot;https://childlike-kumquat-540.notion.site/0c001ed4854a48d39ecd426e5f179ab4&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/rHGdf/hyVqnrD9iL/35VnKDfBLrwtGYvfwApnB0/img.png?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630,https://scrap.kakaocdn.net/dn/fl9hT/hyVqjQjJgX/X7tmTco8D3nuGOkqvOCA00/img.png?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630&quot;&gt;&lt;a href=&quot;https://www.notion.so/6-0c001ed4854a48d39ecd426e5f179ab4?pvs=21&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://www.notion.so/6-0c001ed4854a48d39ecd426e5f179ab4?pvs=21&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/rHGdf/hyVqnrD9iL/35VnKDfBLrwtGYvfwApnB0/img.png?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630,https://scrap.kakaocdn.net/dn/fl9hT/hyVqjQjJgX/X7tmTco8D3nuGOkqvOCA00/img.png?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;솔루션 챌린지 6팀 피드백 | Notion&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt; &amp;nbsp;프로젝트 컨셉 및 기획 의도&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;childlike-kumquat-540.notion.site&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아니 근데 멘토 피드백 받기 위해서는 레포지토리를 public으로 바꿔놓아야 하는데, 내 레포를 그냥 private로 해놓고 있었다&amp;hellip; 생각해보면 당연한 건데 신경을 못 쓰고 있었다. 나도 피드백 해줘요&amp;hellip;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;개발 과정&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;몇 가지 정리하고픈 개발 과정이 있다. 나중에 개별 글로 정리할 예정이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사실 모바일 쪽의 개발 자체는 순조롭게 진행되었다. 나도 이제 어느덧 복잡한 애니메이션이 있지 않은 한, 평면적인 앱은 수월하게 개발할 수 있는 레벨이 되었다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;아키텍처&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아키텍처는 &lt;b&gt;MVVM 구조&lt;/b&gt;를 채용했다. 정확히 말하면 MVVM을 노리고 만든 건 아닌데, 만들다 보니깐 MVVM에 가까워졌다.&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;정확히는 여기서 ViewModel은 클린 아키텍처의 Service Layer에 가까운 역할이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;각 레이어는 다음과 같다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;Domain Layer&lt;/b&gt;: 도메인 모델과 백엔드와의 통신, 그리고 앱 데이터를 관리한다
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;model&lt;/b&gt;: 도메인 모델로, 앱 내에서 사용하는 모델과, 백엔드에서 오는 데이터 모델을 포함한다
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;참고로 DTO와 앱에서 사용할 모델을 나눌 수도 있다. 그러나 나는 굳이 프론트엔드에서 DTO를 따로 가져가는 건 투머치라고 생각했는데, 중간에 API 명세가 한 번 바뀔 뻔 하면서 DTO를 분리할 걸 후회하기도 했다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Repository&lt;/b&gt;: 백엔드 서버와 통신하는 레포지토리는 전부 Retrofit을 이용했고, Firebase와 통신하는 부분은 firebase instance를 주입받아서 구현하였다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Firebase와 통신하는 부분의 경우, Firebase instance를 상위 서비스 레이어에서 직접 사용할 수도 있는데, 그냥 일관성을 위해 Firebase와 연결되는 통신 역시 Repository를 따로 만들었다. 이럴 경우 지금은 필요 없지만, Service Layer에서 Firebase와의 의존을 줄일 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Service Layer&lt;/b&gt;: View Layer와 Domain Layer를 중계한다. 다만 여기서는 Service라는 이름을 직접적으로 사용하지는 않았다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;Provider&lt;/b&gt;: Repository를 주입받아서, 데이터의 상태를 관리한다. Riverpod을 통해 캐싱 및 여러 최적화를 구현했다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;한편 Service라는 이름을 사용하지 않은 이유는 명확하다. 이 프로젝트에서는 Riverpod State Management를 적극적으로 채용했다. Riverpod에는 Provider라는 직관적인 이름으로 StateNotifier 클래스 등을 제공한다. 그래서 Service라는 이름 대신 Provider라는 이름을 채용했다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;View Layer&lt;/b&gt;: 말 그대로 유저에게 보여지는 View Layer이다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;Component&lt;/b&gt;: 자주 사용되는 위젯들을 이 폴더 안에 모아두었다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;View&lt;/b&gt;: Screen, 즉 Page를 이 폴더에 모아두었다. 이때 모든 페이지는 &lt;code&gt;DefaultLayout&lt;/code&gt;을 정의하여, 기본 레이아웃 아래에서 화면을 그리도록 하였다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;한편 폴더를 나눌 때 &lt;b&gt;feature-first&lt;/b&gt; 구조를 채택했다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;API&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;보통은 백엔드에서 API 명세를 정의하는데, 우리의 경우 빠르게 개발이 필요했고, 이때는 프론트엔드인 내가 백엔드에게 필요한 데이터를 요구하는 게 가장 빠르다. 내가 요구한 명세를 백엔드에서 받고 이를 구체적으로 명세화하는 과정인데, 별다른 이견은 없어서 바로 어셉트되었다.&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;내가 직접 요구하면서 한 가지 좋은 점은, API 설계 과정에서부터 전략적으로 추상화가 가능하도록 설계가 가능하다는 점이다. 이는 상속 구조를 적절히 활용만 한다면 매우 강력한 힘을 가질 수 있다.&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;그래서 리소스 설계를 할 때부터 플러터 코드의 설계를 염두해두고 API 리소스 설계를 진행했다.&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;DDD, 즉 도메인 주도 설계라는 용어가 있다. 농담 삼아서 이와 반대로 나는 API 설계 시 상위 아키텍처를 미리 염두해두고 도메인을 구성한 느낌이다.&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;뭐 당연히 나중에 가서 이것이 가진 치명적인 문제점이 발생했고&amp;hellip; 그에 따라 조금 고생하긴 했다.&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;어떤 문제점이 발생했냐면, API가 변경되었을 시 대응이 안 된다. 예를 들어 들어오는 데이터의 구조가 달라질 때 문제가 발생한다.&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;원랜 서비스 레이어와 도메인 레어어를 분리하였기에, 데이터를 받는 DTO를 Repository에서 사용하고, 이를 서비스 단에서 잘 조합해서 기존에 사용하던 모델을 그대로 사용할 수 있다. 이것이 원래 MVVM의 구조에서 ViewModel이 해야 하는 일이다.&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;그런데 내가 너무 설계에만 집착한 나머지&amp;hellip; 오히려 ViewModel이 이런 구조 변화에 대응하지 못하는 일이 벌어졌다.&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;물론 후에 상속 구조를 풀어서, 바뀐 API에 대해 ViewModel이 대응할 수 있도록, 그래서 바뀐 repository와 dto가 view 단에 영향을 미치지 않도록 수정하였다. 어려운 작업은 아니고 중간에 어댑터같은 거를 달아서 model과 ViewModel을 연결하였다.&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;하지만 Repository에 관해서, 쿼리 파리미터를 쓰도록 API 명세가 변경되었을 때 Repository도 추상화시켜놓았는데 이를 풀어야 했다. 그런데 이를 푸는 순간 이 Repository를 사용하는 상위 추상 StateNotifer(Service)도 상속을 풀어야 했고, 그래서 이 Provider를 사용하는 Component 역시 사용이 안 되는 불상사가 발생할 뻔 했다물론 이 문제는 선술했듯이 중간에 변환을 해주도록 계층 하나를 더 추가해주면 해결이 되는 문제이기는 하다. 그래서 어떻게든 View의 Layer까지 변화의 영향이 침범하는 문제는 막을 수 있었다.&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;결론적으로 API 명세는 롤백되었다. 하지만 바뀌려고 시도되었던 API 명세도 충분히 고민해볼 만한 명세이기는 하다. 왜냐면 바뀐 명세가 개발 초기에 논의된 방식이기는 했다. 물론 현실적으로 API 명세는 개발 초기에 정해진 약속이고, 개발이 마무리되는 시점에서 이를 변경할 시 구조에 영향을 미치는 것은 막을 수는 없다. 그래서 한 번 API가 정해진 이상 바뀌기 어려운 거고, 그렇기 때문에 처음에 컬렉션과 리소스의 구분에 대한 논의를 확실히 정한 뒤에 개발을 진행해야 한다.&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;하지만&amp;hellip; 현실은 또한 언제나 이상적이지는 않다. 앞으로 개발을 하다보면 API의 구조 자체가 변화되는 일은 수없이 많을 것이다. 단순히 패스가 변화되는 것 뿐만 아니라 리소스 자체가 변화할 수도 있다. 모든 것이 전부 다 바뀔 수도 있다. 이에 대응할 줄 알아야 한다.&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;/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;/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;이번 경험을 통해 매우 큰 교훈을 얻었다. 또한 ViewModel에서의 적절한 처리를 통해 View까지 영향이 가는 것을 방어하였으니, 미비하지만 이렇게 대응하면 되겠구나 느끼기도 하였다. 큰 경험이다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;채택된 기술 스택&lt;/h3&gt;
&lt;p 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;여기에는 AI를 담당한 분의 배려가 들어가있었다. 이 분은 원래 모바일 개발로 팀에 들어오셨다. 문제는 이 분은 네이티브 개발, 즉 Swift와 Kotlin 쪽 개발만 진행하셨다.&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;하지만 나의 경우는 Flutter 외에 할 수 있는 것이 없다. Spring 백엔드가 두 명이나 있는데 django로 백엔드 개발을 할 수도 없는 노릇이니깐.&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;새롭게 네이티브 개발을 배울 수도 없는 노릇이여서, 기술 스택은 Flutter로 정하고 모바일 개발의 기술 스택을 내가 원하는 것으로 정할 수 있었다.&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;그에 따라 AI를 맡은 분의 학습 부담은 늘어났다. 그래서 모바일 개발에 직접적으로 참여하기 보다, AI 모델 개발 및 Firebase functions와 GCP ML 구성을 주도적으로 맡으셨고, 플러터 개발의 경우 Pose Estimation 관련 기능만 맡으셨다.&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;일단 여기가 Solution Challenge인 이상 Flutter로 하는 것이야 기정 사실이고, 나 역시 애초에 Flutter 개발로 권유를 받고 들어왔으니 여기에 변동은 없다. 그 외 세부적인 주요 기술 스택을 정해야 하는데&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;Riverpod&lt;/b&gt;: 상태 관리로는 Riverpod을 채택하였다. 이유는 가장 많이 사용했고, 가장 제대로 배운 상태 관리 툴이라서 그렇다. GetX와 비교하면, GetX는 배울 때 제대로 배우지 못한 것 같아서 조금 어려움을 겪었다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Go Router&lt;/b&gt;: URL로 페이지 라우팅을 관리할 수 있는 패키지이다. Flutter의 위젯 트리 특유의 단점을 상쇄시킬 수 있다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Freezed, JsonSerializable, Retrofit&lt;/b&gt;: Code Generation의 대표적인 3인방이다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p 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;그런데 사실 위 구성은 거의 표준이 되어 가고 있는 듯 하다. 최근에 내가 관심을 갖는 Flutter 앱 중에서, Lichess 모바일 앱의 v2 버전 개발이 있다. 나중에 기회가 되면 어떻게 개발하는 지 분석하고 블로그에 공유하고자 하는데, 여기서도 riverpod, go router, retrofit 등의 기술을 똑같이 사용하고 있다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Firebase와 GCP&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Firebase는 알면 알수록 좋다. 사용하기 편하고, 가격도 싸다. Firebase는 후에 제대로 각잡고 공부해보는 것도 좋을 듯 하다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;Authentication&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;나는 이번에 Firebase Auth만을 사용했다.&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;사실 프론트엔드 입장에서는 서버에서 직접 토큰 방식 로그인을 구현하든, Firebase Auth를 사용하든 별 차이는 없다. 그러나 백엔드에서 JWT를 구현하는 게 시간이 걸릴 수 있기에 이를 절약하고자 Firebase Auth를 사용하자고 했는데, 오히려 이것이 백엔드 측에서 유저 테이블이 없어지면서 어떻게 처리해야 할 지 고민을 한 것 같다.&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;/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;먼저 나는 원래 User 테이블과 Auth 테이블을 분리한다. Auth 테이블은 순수 Authentication에 관한 내용만 저장하고, User 테이블은 Auth 기능 외에 유저 정보에 대한 데이터를 저정한다.&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;대형 서비스로 갈 수록 User 테이블과 Auth 테이블은 분리된다고 한다. 기능이 많아질 수록 User 테이블에 변화가 일어나게 되면, Auth 서비스에도 영향을 미치기 때문이다. 그리고 Auth 등 User 등 만약에 어느 한 쪽 DB를 샤딩하거나 레플리케이션하게 된다면 분리되어 있는 게 무조건 유리하다. 특히 Auth가 그런데, 예를 들어 Authetication 서비스를 Redis 데이터베이스에 저장하고, 이를 샤딩하거나 지역별로 레플리케이션을 만든다고 가정하자. 그렇다면 다른 User 컬럼 데이터까지 전부 Redis에 넣을텐가? 아닐 것이다. 확장성에 유리함이 많은 만큼 Auth와 User 테이블은 분리해서 나쁠 것이 없다. 그리고 엄밀히 따지면 정규화의 입장에서도, Auth와 User의 기능은 구분되어야 한다고 생각한다.&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;User 테이블과 Auth 테이블이 분리되었다면, 우리는 이제 Auth 테이블이 MSA의 구조처럼 분리되는 상황을 해봐야 한다. 이 경우 기존의 서버에서는 Auth 서버로 API 호출을 통해 Authentication의 인증이 유효한 지를 체크할 것이다.&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;이를 구현하는 아키텍처는 여러 가지가 있다. 예를 들어, API Gateway Server가 있고, 그 뒤에 Authentication Server, File Server가 동시에 존재한다고 가정하자. 파일 서버는 인가된 유저의 파일만 업로드해야 한다. API Gateway가 있으니, 먼저 API Gateway에서 Auth Server로 JWT 토큰의 인증/인가를 검사한 뒤에, 적절하다면 File Server로 Request를 통과시키는 방법이 있다(내가 구현했던 방식이다).&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;아니면 굳이 API Gateway를 만들지 않고, 그냥 File Server에서 직접 Auth Server로 API 호출을 통해 JWT를 검사하는 로직이 있다(이 프로젝트에서 백엔드가 이런 방식을 채택한 것으로 보인다).&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;Firebase Auth를 이용한다면, 바로 이 Auth Server를 분리한 것과 똑같은 효과를 얻게 된다.&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;다만 여기서 주의해야 하는 것은 User 테이블과 Auth 테이블의 동기화 문제이다. 회원 가입 시 이를 File Server에서 어떻게 알 것인가? 이 부분은 일단 프론트 단에서 유저가 회원 가입을 할 시, 이 사실을 Firebase Auth 뿐만 아니라 다른 서버로 알리는 방법이 있다.&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;/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;하지만 이 프로젝트에서는 디비 구조상 유저의 퀴즈 풀이 여부를 저장해야 하기에, User : Quiz 테이블 사이 User-Quiz 테이블이 존재해야 한다. 아마 여기서 User-Quiz 테이블이 UserId를 FK로 가져야 하는데, 이 FK로 가질 키가 없으니깐 문제가 발생한 것으로 보인다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이를 해결하는 검증된 방법이 있는지는 모르겠다. 그런 상황에서 이를 해결하느라 고생한 것 같다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;Firebase Functions&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Firebase Functions를 이용해서, Pose estimation 모델을 돌리는 작업은 AI 팀원 분이 담당하였다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;매우 신기했다. GCP ML과 연계하여 서버리스로 빠르게 개발을 완료하시는 걸 보고 멋지다고 생각하기도 했다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;이거 어디서 많이 봤는데&amp;hellip;&lt;/h4&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;730&quot; data-origin-height=&quot;880&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bFoJbz/btsFkGOhftI/Lw6WEitXcn9mN20FP4IKK0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bFoJbz/btsFkGOhftI/Lw6WEitXcn9mN20FP4IKK0/img.png&quot; data-alt=&quot;PTSD On...&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bFoJbz/btsFkGOhftI/Lw6WEitXcn9mN20FP4IKK0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbFoJbz%2FbtsFkGOhftI%2FLw6WEitXcn9mN20FP4IKK0%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; alt=&quot;GCP 가격&quot; loading=&quot;lazy&quot; width=&quot;730&quot; height=&quot;880&quot; data-origin-width=&quot;730&quot; data-origin-height=&quot;880&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;PTSD On...&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이거 &lt;a href=&quot;https://nx006.tistory.com/57&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;어디서 많이 본 상황&lt;/a&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;이번에도 크게 다르지 않다. DB를 일반 서버에 올리는 게 아니라, 아주 Flex한 옵션인 완전 관리형 데이터베이스 서비스를 사용하였고, 또 VM 인스턴스 역시 무료 버전이 아닌 유료 VM을 사용하여 배포되었다.&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;아쉬운 점은 이 프로젝트에서 사용된 Firebase는 Blaze 요금제, GCP는 3개월 동안 무료로 사용할 수 있어 비싼 서비스를 이용한다고 하더라도 돈을 안 낼 수 있었다. 한 번의 방어선이 있었던 셈이다. 그런데 왜 돈을 내게 되었냐고? 서비스가 프로젝트 GCP 계정이 아닌, 배포자 개인의 계정으로 따로 배포되었기 때문이다. 이거 정말&amp;hellip; 6개월 전의 공포가 생각난다.&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;u&gt;&lt;i&gt;이런 일은 어떻게 막을 수 있었을까?&lt;/i&gt;&lt;/u&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이런 일은 보통 개인의 문제라기 보다는 팀 전체의 문제에서 비롯된다. 배포 시 소통이 부족했고, 역할을 제대로 분배하는 데 실패했다. 배포는 특히 비용에 관련된 부분인만큼 팀 전체가 집중해서 진행해야 했다. 작년의 사례를 미리 경험한 나인만큼 특히 더 방지할 수 있는 기회가 있었던 것 같다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;최종 제출&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;올해도 역시나 기한은 연장되었다&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;원래는 2월 22일까지 제출이었다. 그러나 역시나 얘네들, 25일까지로 기한 연장하였다.&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;22일 전날에 밤 새가면서 개발을 하였는데, 아침 6시에 연장되었다는 말 듣고 바로 취침하러 갔다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사실 천운이기도 한게, 연장 안 되었다면 개발 절대 다 못 끝냈다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;웃긴 점은 기능의 90%는 제출 열흘 전에 완성되었다. 그 중에서도 60%는 마감 5일 전에 전부 완성되었다.&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;특히 22일 마감 직전 매우 유튜브 플레이어, 혹은 카메라 녹화라는 네이티브 기능을 쓰는 페이지가 두 개 있었는데, 이 두 페이지는 불과 8시간만에 구현되었다. 1월 달에는 단순 메인 페이지 하나를 2주에 걸쳐서 느긋~하게 만들었는데, 역시 마감 직전이 되어서야 사람은 급해지나 보다. 원래 개발이 이런 건가&amp;hellip;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;영상 찍기&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하필 25일, 제출해야 하는데 역시나 문제가 터지더라. 코드가 그리 바뀐 게 없는데 앱이 녹화 중에 꺼진다든지 하는 문제가 보고되었다. 사실 굉장히 어이가 없기도 하고 억울했던 게 아니 코드가 바뀌지 않았는데 왜 갑자기 안 되는 거야&amp;hellip; 라고 플러터에게 떼써봐도 상황은 바뀌지 않는다. 어떻게든 문제가 발생할 수 있는 부분을 뗌빵하고, 영상을 찍어야 한다.&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;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만&amp;hellip;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;648&quot; data-origin-height=&quot;1360&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b55tNv/btsFioAA8pd/Ylw8rv52kP2TaNQX4uWqN1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b55tNv/btsFioAA8pd/Ylw8rv52kP2TaNQX4uWqN1/img.png&quot; data-alt=&quot;당신의 USB 디버깅 나의 비활성화로 대체되었다&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b55tNv/btsFioAA8pd/Ylw8rv52kP2TaNQX4uWqN1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb55tNv%2FbtsFioAA8pd%2FYlw8rv52kP2TaNQX4uWqN1%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; alt=&quot;당신의 USB 디버깅 나의 비활성화로 대체되었다&quot; loading=&quot;lazy&quot; width=&quot;418&quot; height=&quot;877&quot; data-origin-width=&quot;648&quot; data-origin-height=&quot;1360&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;당신의 USB 디버깅 나의 비활성화로 대체되었다&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아니 평소에는 USB 디버깅 잘만 켜져 있는데 왜 갑자기 안 되는 거야&amp;hellip;?&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;정말 뜬금없이 내 휴대폰에서 USB 디버깅 옵션이 비활성화되었다. 이때 내가 느낀 심정은 충격과 공포 그 자체다. 어떻게 다시 활성화하는 지도 모르겠다.&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;정말 다행히 학교에서 빌린 아이패드가 있어서 아이패드에 대신 연결하기로 했다. 문제는 내가 아이폰을 쓰는 것도 아니여서, iOS 실기기에 연결해보는 것은 이번이 처음이었다. 인터넷에 검색해가면서 어찌저찌 연결에 성공은 했으나&amp;hellip;&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;정말 기도 메타였다. 나 역시 앱이 뜬금없는 타이밍에 꺼지는 문제가 계속되었다. 다시 켜보려 해봐도 방금 전까지 빌드가 성공했는데, 다시 빌드 실패가 된다든지&amp;hellip; XCode에서 안 되니깐 vscode에서 되었고, 그러다가 다시 꺼졌다. 그래서 다시 빌드했는데 이번에는 vscode에서 빌드가 실패하고 xcode에서 된다든지&amp;hellip; 이런 상황이 몇 번이고 반복되었다. 그래서 마지막으로 되었을 때 정말 기도 메타로 꺼지지 말아주세요 하고 앱 시연을 녹화했다.&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;내가 녹화한 부분은 CPR을 진행하는 부분이다. 내가 CPR 하는 장면을 카메라로 녹화하고, 이를 서버로 올리면 그에 대한 결과를 반환받는다.&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;여기서도 멀쩡히 작동하던 firebase functions에서 파일을 못 찾는다든지 문제가 발생했다.&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;원래 firebase storage로 유저의 영상이 업로드되고 나면, 이후에 firebase functions에서 이를 수집해서 pose estimation model을 돌린 뒤 결과를 다시 반환하는 구조인데, functions에서 storage의 파일을 찾지 못한다. 분명 업로드가 완료된 이후에 firebase functions를 호출하는데, 파일이 없다니 그저 황당할 뿐이다. &lt;code&gt;await&lt;/code&gt;를 안 한 것도 아니고&amp;hellip;&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;파일이 100% 업로드가 된다 하더라도, 어쩌면 즉시 firebase functions를 호출 시 스토리지에서 파일을 찾지 못할 가능성도 있다. 그래서 임시로 해결한 방법은 그냥 2초를 기다리는 것이다.&lt;/p&gt;
&lt;pre class=&quot;cs&quot;&gt;&lt;code&gt;  Future&amp;lt;void&amp;gt; uploadAndSubmit({
    required Id actionId,
    required File file,
    required String videoUrl,
  }) async {
    await upload(
      actionId: actionId,
      file: file,
    );

    // wait for 3 seconds preventing server cannot find the uploaded file
    await Future.delayed(const Duration(seconds: 2));

    await submit(
      actionId: actionId,
      videoUrl: videoUrl,
    );
  }&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;원래는 &lt;code&gt;upload&lt;/code&gt;, &lt;code&gt;submit&lt;/code&gt; 함수를 따로따로 호출했는데, 이를 통합하는 하나의 함수를 만든 뒤에 여기서 2초간 기다린 후 submit을 진행했다. 솔직히 근본적인 솔루션은 절대 아니고, 이런 방식은 결코 좋은 방법은 아니지만&amp;hellip; 어쩔 수 없다.&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;a href=&quot;https://www.youtube.com/watch?v=uTz138f3SOM&quot;&gt;https://www.youtube.com/watch?v=uTz138f3SOM&lt;/a&gt;&lt;/p&gt;
&lt;figure data-ke-type=&quot;video&quot; data-ke-style=&quot;alignCenter&quot; data-video-host=&quot;youtube&quot; data-video-url=&quot;https://www.youtube.com/watch?v=uTz138f3SOM&quot; data-video-thumbnail=&quot;https://scrap.kakaocdn.net/dn/bmBedy/hyVqpiGE56/Wt9oWk72ACRZW5lZP50Uj0/img.jpg?width=1280&amp;amp;height=720&amp;amp;face=0_0_1280_720&quot; data-video-width=&quot;860&quot; data-video-height=&quot;484&quot; data-video-origin-width=&quot;860&quot; data-video-origin-height=&quot;484&quot; data-ke-mobilestyle=&quot;widthContent&quot; data-original-url=&quot;&quot; data-video-title=&quot;&quot;&gt;&lt;iframe src=&quot;https://www.youtube.com/embed/uTz138f3SOM&quot; width=&quot;860&quot; height=&quot;484&quot; frameborder=&quot;&quot; allowfullscreen=&quot;true&quot;&gt;&lt;/iframe&gt;
&lt;figcaption style=&quot;display: none;&quot;&gt;&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;최종 제출 영상이다. AI를 맡으신 분이 제작하셨고, 내가 녹화한 건 1:25s 부근부터 나온다. 아주 열심히 CPR 하는 모습이 인상적이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이때가 한국 시간으로 새벽 11시였다. 밤에 집에서 패딩으로 간이 토르소처럼 만들고 열심히 CPR 연기하는 게 참&amp;hellip; 웃겼다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;서류&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;과거에는 영어로 서류를 작성하는 게 참 부담이었는데, 이제는 GPT와 여러 언어 모델 기반 번역기(deepL 등)의 등장으로 전보다 훨씬 쉬워졌다. 영어로 내가 직접 작성해도 되지만, 이제는 한국어로 작성한 다음에 GPT 혹은 deepL을 돌리는 게 더 빨라졌다. 정말&amp;hellip; 세상의 혁명이다.&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;나는 서류 작성은 부분적으로만 도와주었다. 거의 대부분은 AI 분이 영상을 제작하시면서 서류까지 마무리하셨고, 다소 빈약한 부분이랑 수정이 필요한 한 두 질문만 답을 추가하였다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;제출!&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1454&quot; data-origin-height=&quot;1176&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/JwfOZ/btsFjUFZR4B/SIdYVD0jNqMvOQqGfGV51K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/JwfOZ/btsFjUFZR4B/SIdYVD0jNqMvOQqGfGV51K/img.png&quot; data-alt=&quot;최종 제출 구글 서브미션 폼&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/JwfOZ/btsFjUFZR4B/SIdYVD0jNqMvOQqGfGV51K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FJwfOZ%2FbtsFjUFZR4B%2FSIdYVD0jNqMvOQqGfGV51K%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; alt=&quot;최종 제출 구글 서브미션 폼&quot; loading=&quot;lazy&quot; width=&quot;1454&quot; height=&quot;1176&quot; data-origin-width=&quot;1454&quot; data-origin-height=&quot;1176&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;최종 제출 구글 서브미션 폼&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2704&quot; data-origin-height=&quot;1476&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/zHHFV/btsFmgBxKJ9/ZpsDRat4RA40kRsxcVH0Bk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/zHHFV/btsFmgBxKJ9/ZpsDRat4RA40kRsxcVH0Bk/img.png&quot; data-alt=&quot;팀 레포지토리&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/zHHFV/btsFmgBxKJ9/ZpsDRat4RA40kRsxcVH0Bk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FzHHFV%2FbtsFmgBxKJ9%2FZpsDRat4RA40kRsxcVH0Bk%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; alt=&quot;팀 레포지토리&quot; loading=&quot;lazy&quot; width=&quot;2704&quot; height=&quot;1476&quot; data-origin-width=&quot;2704&quot; data-origin-height=&quot;1476&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;팀 레포지토리&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 해서 최종적으로 제출하게 되었다!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;길다면 길고 짧다면 짧은 지난 두 달 간의 여정이 끝나게 되었다!&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;후기&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;클라우드 중요하더라&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 난 전역하자 마자 GCP, AWS까지 전부 배울 예정이다. 아예 제대로 각잡고 교육 이수할 생각이다.&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;지난 NCP 스터디는 솔직히 말해서 수박 겉핡기에 지나지 않았다. 이번 프로젝트를 계기로, 클라우드를 제대로 활용할 수 있는 사람과 활용하지 못하는 사람의 개발자로서의 격차가 얼마나 큰 지를 느꼈다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;테스트 중요하더라&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;테스트 코드 작성이 중요하다는 건 누구나 안다. 그런데 테스트 코드를 정작 짜는 사람은 그렇게 많지는 않았던 것 같다. 그중 하나가 나다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;특히 플러터인만큼 테스트 코드를 작성하는데 있어서 이질적인 것도 있다. 프론트엔드의 테스트 코드는 어떻게 짜야 하지? 라는 고민이 든다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그걸 공부하기 위해서 아주 오래전에 비싼 강의도 구매했었는데, 놀랍게도 반 년 째 안 보고 있다. 봐야겠다. 오히려 프론트엔드에서의 테스트가 아주 중요한 것 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;한편 화면을 개발할 때, 토스같은 경우 자체적인 모듈 시스템으로 프론트엔드 개발 시 화면을 테스트하고 개발한다는 글을 본 적이 있다. 매우 감명깊었고 원하는 기술이었다. 왜냐면 특히 동적인 화면, 혹은 진입 시까지 오래 걸리는 화면을 개발할 때, 그렇게 분리된 컴포넌트 없이는 개발이 참 난항을 겪기 때문이다. 파일 업로드 시 % 변화 화면은 어딘가에 픽스해두어야 하고, 또 진입 시 오래 걸리는 화면은, 개발 과정에서 실수로 에러가 나서 hot reload를 하는 순간 다시 그 패스로 찾아가야 한다. 이 얼마나 원시적인가&amp;hellip;&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;이런 테스트 코드, 모듈러 시스템에 관한 내용 말고도 심지어 API 연결 역시 테스트화할 수 있다. POSTMAN의 mock 서버를 이용하면 되는데, 이번 프로젝트에서 이 기능을 굉장히 유용하게 적용하였다. 진작에 사용할 걸 그랬다. 백엔드에서 개발이 늦어지니깐, 바로 목 서버를 구축해서 API 연결을 테스트하고 개발을 진행했는데, 아주 만족스러웠다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;상태 관리를 제대로 아는 것, 너무나 중요하더라&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Riverpod을 적극적으로 사용했다. 하지만 과연 제대로 사용하였는가는 잘 모르겠다. 정확히는, 설계를 제대로 하였는가에 대한 질문에 답을 못 하겠다.&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;캐싱을 하는 것은 좋다. 그런데 오히려 캐싱을 생각하느라 성능적인 문제를 야기한 코드가 눈에 너무 많이 보인다. 예를 들어 Action에 대해 User Submission의 상태를 제공하는 Provider가 있다. 이때 파일 업로드 상태는, 그냥 별개의 Provider로 제공하는 게 더 좋았을 것이라는 생각이 든다. 각 id 별로 파일 업로드 상태를 따로 구분할 필요가 없다. 현재의 구현에서는 파일 업로드, 결과 대기, 결과 점수 모델이 전부 하나의 상태 동류들로 관리된다.&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;이게 왜 문제냐면 결국 id에 맞는 상태를 찾아 매핑해야 하는데, 파일 업로드같은 경우 워낙 빠르게 상태가 변하다 보니깐 StateNotifier의 상태 변화 속도가 파일 업로드 변화 속도를 가끔씩 놓친다. 그럴 때 로직 상 state가 아주 잠깐(한 틱 정도) ErrorState로 변하게 되는데, 유저 입장에서는 0.1초 정도 순간적으로 에러 화면을 보게 된다. 그리 기분 좋은 경험은 아니다.&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;그래서 위 두 개의 교훈에서 얻은 결론은? 이전에 사두었던 Code with Andrea의 강의를 정독하자&amp;hellip; 그리고 테스트 관련된 책도 좀 읽자.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;미루지 말자&lt;/h3&gt;
&lt;p 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;이중 90%의 기능은 마감 2주일 안에 개발되었다.&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;그중에서도 60%의 기능은 불과 3일만에 개발되었다.&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;기한이 다행히 연장되어서 망정이지, 22일까지였으면 완성도 못 했다.&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;물론 개발이 딜레이될 수 밖에 없던 몇 가지 사정들이 있었고(어떤 기능이 개발되어야 다음 기능을 개발하는데, 그 기능 개발이 딜레이된다든가 하는 문제들), 내 개인적으로도 시험 등 여러 일정들이 겹치긴 했었다. 뭐 근데 그건 다 핑계고 솔직히 유튜브 행복하게 볼 시간에, 2시간 정도만이라도 야무지게 개발에 투자했으면 지금쯤 앱 출시도 했겠다&amp;hellip;&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;직장인들 9-6 worklife를 유지한다는 게 정말 대단할 따름이다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;입대 전 마지막 추억이 될 것 같다&lt;/h3&gt;
&lt;p 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;솔직히 말해서 작년은 슬럼프 기간이었다. 2학기 막바지에 가서는 컴공이 과연 나의 길이 맞는 지조차 의심이 들었다. 내가 이 직업을 소망한 것이 1-2년 된 것도 아니고, 15살 때부터 간절히 소망해왔고 배워왔던 길이다. 그렇기에 내가 과연 이 길을 걷는 것이 맞는 것인가에 대한 의문이 들었을 때 꽤 힘들었다. 진심 반 농담 반, 그러나 꽤 깊게 전과도 생각했다. 컴공이 나의 길이 아닌 것 같았다.&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;/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;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;결국 모두가 역할을 맡아주었기에 완성할 수 있었다&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;각자 맡은 바의 역할을 포기하지 않고 끝까지 다 해주어서 끝까지 제출하는 데 성공하지 않았나 싶다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;기획부터 디자인을 발전시키고, AI 모델을 개발하시고 팀을 안정적으로 매니징한 &lt;a href=&quot;https://github.com/jlstdio&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;jlstdio&lt;/a&gt; 님 + 영상 만들고 발표하고, 서류 작성하느라 수고 많았습니다&lt;/li&gt;
&lt;li&gt;백엔드 개발을 맡고 발생한 문제를 빠르게 해결한 &lt;a href=&quot;https://github.com/alsrudursla&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;alsrudursla&lt;/a&gt; 님&lt;/li&gt;
&lt;li&gt;디자인을 맡고, 기획적인 부분에서 놓치고 있는 부분을 짚어주신 &lt;a href=&quot;https://github.com/DamWon-KIM&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;DamWon-KIM&lt;/a&gt; 님 + 배포 과정 중 발생한 문제 해결하느라 고생 많았습니다&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;모두 고생 많았습니다.&lt;/p&gt;</description>
      <category>회고록</category>
      <author>nx006</author>
      <guid isPermaLink="true">https://nx006.tistory.com/78</guid>
      <comments>https://nx006.tistory.com/78#entry78comment</comments>
      <pubDate>Tue, 27 Feb 2024 04:53:07 +0900</pubDate>
    </item>
    <item>
      <title>[Flutter Error] Uncategorized (Xcode): Command CodeSign failed with a nonzero exit code 임시 해결 방법</title>
      <link>https://nx006.tistory.com/77</link>
      <description>&lt;h2&gt;문제 상황&lt;/h2&gt;
&lt;p&gt;Flutter에서 iOS 빌드 시 다음과 같은 에러가 발생하였다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;Launching lib/main.dart on iPhone 15 Pro in debug mode...
Running Xcode build...                                                  
Xcode build done.                                            6.2s
Failed to build iOS app
Uncategorized (Xcode): Command CodeSign failed with a nonzero exit code

Could not build the application for the simulator.
Error launching application on iPhone 15 Pro.&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;Uncategorized (Xcode): Command CodeSign failed with a nonzero exit code&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;까다로운 에러다. XCode에 관련된 에러는 주로 XCode를 업데이트했을 때, iOS 빌드 버전이 바뀌었을 때 등 상황에서 일어날 수 있다. 필자의 경우는 확실하지는 않지만 iCloud로 document를 백업하고, 연결을 끊으면서부터 문제가 발생하기 시작했다(연결을 끊을 경우 로컬의 document 폴더가 삭제되기 때문에 발생한 것으로 생각한다).&lt;/p&gt;
&lt;h2&gt;임시 해결 방법&lt;/h2&gt;
&lt;p&gt;우선 다음 사항들을 먼저 확인해보자.&lt;/p&gt;
&lt;h3&gt;일단 flutter doctor부터&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;$ flutter doctor
Doctor summary (to see all details, run flutter doctor -v):
[✓] Flutter (Channel stable, 3.16.7, on macOS 14.2.1 23C71
    darwin-arm64, locale ko-KR)
[✓] Android toolchain - develop for Android devices (Android SDK
    version 34.0.0)
[✓] Xcode - develop for iOS and macOS (Xcode 15.2)
[✓] Chrome - develop for the web
[✓] Android Studio (version 2023.1)
[✓] IntelliJ IDEA Ultimate Edition (version 2023.3.4)
[✓] VS Code (version 1.86.1)
[✓] Connected device (3 available)
[✓] Network resources

• No issues found!&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;우선 &lt;code&gt;flutter doctor&lt;/code&gt; 부터 확인해보자. 여기서 문제가 발견되면 운이 좋은 것이다.&lt;/p&gt;
&lt;p&gt;하지만 &lt;code&gt;flutter doctor&lt;/code&gt;에서는 문제가 확인되지 않았다. 다음 과정으로 넘어간다.&lt;/p&gt;
&lt;h3&gt;codesign 확인&lt;/h3&gt;
&lt;p&gt;code sign에 관한 문제이므로 code sign의 위치도 확인해보자.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;$ which codesign
/usr/bin/codesign&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;만약에 &lt;code&gt;which codesign&lt;/code&gt; 을 통해서 얻은 path값이 없거나, 혹은 &lt;code&gt;/usr/bin/codesign&lt;/code&gt;이 아니라면 문제가 있는 것이다. 가령 &lt;code&gt;venv/anaconda/usr/bin/codesign&lt;/code&gt; 등 경로명이 다르면 문제가 생길 수 있다.&lt;/p&gt;
&lt;p&gt;이 경우 &lt;code&gt;export PATH=$PATH:/usr/bin/codesign&lt;/code&gt;을 통해서 해결할 수 있다.&lt;/p&gt;
&lt;p&gt;하지만 나의 경우는 &lt;code&gt;which codesign&lt;/code&gt;도 정상으로 나왔으니 다음으로 넘어간다.&lt;/p&gt;
&lt;h3&gt;XCode 로그인&lt;/h3&gt;
&lt;p&gt;XCode를 켠 후 &lt;code&gt;cmd + ,&lt;/code&gt;을 눌러서 Configuration으로 들어가자.&lt;/p&gt;
&lt;p&gt;Configuration → Accounts 탭에서 로그인이 제대로 되어 있는 지 확인해보자.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; width=&quot;100%&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/UDde4/btsE3m23j1F/FZIk8WVZuOgdsQWd8WIhy1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/UDde4/btsE3m23j1F/FZIk8WVZuOgdsQWd8WIhy1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/UDde4/btsE3m23j1F/FZIk8WVZuOgdsQWd8WIhy1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FUDde4%2FbtsE3m23j1F%2FFZIk8WVZuOgdsQWd8WIhy1%2Fimg.png&quot; width=&quot;100%&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;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;로그인이 안 되어 있다면 로그인을 해보고 다시 시도한다.&lt;/p&gt;
&lt;p&gt;이마저 문제가 해결되지 않으므로 다음으로 넘어간다.&lt;/p&gt;
&lt;h3&gt;XCode에서 직접 실행 후 attach하기&lt;/h3&gt;
&lt;p&gt;XCode에서 &lt;strong&gt;Open Existing Project&lt;/strong&gt;를 눌러 프로젝트를 연다.&lt;/p&gt;
&lt;p&gt;우리가 열어야 할 것은 &lt;code&gt;{project-name}/ios/Runner.xcodeproj&lt;/code&gt; 파일이다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; width=&quot;100%&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/k0pKU/btsEZA13yxa/Mmfk10wq9gcZ2OTwKyyQwk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/k0pKU/btsEZA13yxa/Mmfk10wq9gcZ2OTwKyyQwk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/k0pKU/btsEZA13yxa/Mmfk10wq9gcZ2OTwKyyQwk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fk0pKU%2FbtsEZA13yxa%2FMmfk10wq9gcZ2OTwKyyQwk%2Fimg.png&quot; width=&quot;100%&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;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; width=&quot;100%&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cdqaKD/btsE3pepuwh/krkfgycYXXf1VIbUT1kPNK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cdqaKD/btsE3pepuwh/krkfgycYXXf1VIbUT1kPNK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cdqaKD/btsE3pepuwh/krkfgycYXXf1VIbUT1kPNK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcdqaKD%2FbtsE3pepuwh%2FkrkfgycYXXf1VIbUT1kPNK%2Fimg.png&quot; width=&quot;100%&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;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;이런 화면이 뜬다. iOS 빌드 버전, &lt;code&gt;Release&lt;/code&gt;, &lt;code&gt;Debug&lt;/code&gt;, &lt;code&gt;Profile&lt;/code&gt; 등의 Configuration 등을 설정할 수 있다.&lt;/p&gt;
&lt;p&gt;여기서 설정을 변경하면 이는 실제 &lt;code&gt;ios/Runner.xcworkspace/contents.xcworkspacedata&lt;/code&gt; 파일이나 &lt;code&gt;ios/Runner.xcodeproj/project.pbxproj&lt;/code&gt; 파일 등 &lt;code&gt;ios&lt;/code&gt; 폴더의 설정 파일에 반영이 된다.&lt;/p&gt;
&lt;p&gt;여기서 iOS 버전을 가장 최신 버전(현재 기준 17.2 버전), 혹은 그보다는 살짝 아래 버전으로 낮춘 후, 좌측 상단 ▷ 버튼을 눌러서 xcode에서 직접 실행시킨다(iOS 시뮬레이터 켜주는 것을 잊지 말자).&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; width=&quot;100%&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Ck8yU/btsEZoOlm2s/0QBgXNQsNkjrzKYR3ngwPk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Ck8yU/btsEZoOlm2s/0QBgXNQsNkjrzKYR3ngwPk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Ck8yU/btsEZoOlm2s/0QBgXNQsNkjrzKYR3ngwPk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FCk8yU%2FbtsEZoOlm2s%2F0QBgXNQsNkjrzKYR3ngwPk%2Fimg.png&quot; width=&quot;100%&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;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;이 과정에서도 빌드가 실패하는 잡음이 있었는데, 이는 &lt;a href=&quot;https://mo.wealth-info.co.kr/26&quot;&gt;이 글&lt;/a&gt;을 보고 해결하였다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; width=&quot;100%&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/GieZK/btsE5DKelRS/d6QcMR8O9zWYj6qxwZSJNK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/GieZK/btsE5DKelRS/d6QcMR8O9zWYj6qxwZSJNK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/GieZK/btsE5DKelRS/d6QcMR8O9zWYj6qxwZSJNK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FGieZK%2FbtsE5DKelRS%2Fd6QcMR8O9zWYj6qxwZSJNK%2Fimg.png&quot; width=&quot;100%&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;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;xcode를 통한 빌드는 성공하였다. 앱이 잘 실행되는 것을 확인.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; width=&quot;100%&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cngnhL/btsEYVljxmx/Hj9kXC6uCKKHRCTXzZgMnk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cngnhL/btsEYVljxmx/Hj9kXC6uCKKHRCTXzZgMnk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cngnhL/btsEYVljxmx/Hj9kXC6uCKKHRCTXzZgMnk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcngnhL%2FbtsEYVljxmx%2FHj9kXC6uCKKHRCTXzZgMnk%2Fimg.png&quot; width=&quot;100%&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;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;맨 밑을 늘려보면, 로그를 확인할 수 있다.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;flutter: The Dart VM service is listening on http://127.0.0.1:53713/9ySr_eAsCOs=/&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;이 부분이 중요하다. 여기서 포트 53713을 얻었다. 이 포트는 매번 바뀐다.&lt;/p&gt;
&lt;p&gt;그리고서 vscode로 돌아와서 터미널에서 다음 명령어를 시도한다:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;$ flutter attach --app-id &amp;quot;safetyedu.example.your_project&amp;quot; --device-vm-service-port 54585&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;하지만 나의 경우에는 위의 방법도 안 되었는데, &lt;code&gt;--device-vm-service-port&lt;/code&gt;라는 명령어가 없다는 오류였다(Flutter 버전 업그레이드가 되면서 사라진 것 같다).&lt;/p&gt;
&lt;p&gt;그래서 그냥 &lt;code&gt;flutter attach --profile&lt;/code&gt;을 시도했다(혹은 &lt;code&gt;flutter attach&lt;/code&gt;만을 시도해보라).&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;$ flutter attach --profile
Waiting for a connection from Flutter on iPhone 15 Pro...

Flutter run key commands.
h List all available interactive commands.
d Detach (terminate &amp;quot;flutter run&amp;quot; but leave application running).
c Clear the screen
q Quit (terminate the application on the device).
A Dart VM Service on iPhone 15 Pro is available at: http://127.0.0.1:54168/kA601h-CNFw=/
The Flutter DevTools debugger and profiler on iPhone 15 Pro is available at: http://127.0.0.1:9101?uri=http://127.0.0.1:54168/kA601h-CNFw=/&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;성공이다.&lt;/p&gt;
&lt;p&gt;interactive 터미널이 켜지는데, 여기서 h를 누르면 모든 가능한 옵션키들을 확인할 수 있다.&lt;/p&gt;
&lt;p&gt;하지만 내가 원하는 것은 이게 아니다.&lt;/p&gt;
&lt;p&gt;vscode에서 &lt;code&gt;cmd+shift+p&lt;/code&gt;로 명령 팔레트 창을 연 뒤, launch.json을 검색한다.&lt;/p&gt;
&lt;p&gt;그러면 launch configuration을 할 수 있는 &lt;code&gt;.vscode/launch.json&lt;/code&gt;이 뜬다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-json&quot;&gt;{
    // IntelliSense를 사용하여 가능한 특성에 대해 알아보세요.
    // 기존 특성에 대한 설명을 보려면 가리킵니다.
    // 자세한 내용을 보려면 https://go.microsoft.com/fwlink/?linkid=830387을(를) 방문하세요.
    &amp;quot;version&amp;quot;: &amp;quot;0.2.0&amp;quot;,
    &amp;quot;configurations&amp;quot;: [
        {
            &amp;quot;name&amp;quot;: &amp;quot;safetyedu&amp;quot;,
            &amp;quot;request&amp;quot;: &amp;quot;launch&amp;quot;,
            &amp;quot;type&amp;quot;: &amp;quot;dart&amp;quot;
        },
        {
            &amp;quot;name&amp;quot;: &amp;quot;safetyedu (profile mode)&amp;quot;,
            &amp;quot;request&amp;quot;: &amp;quot;launch&amp;quot;,
            &amp;quot;type&amp;quot;: &amp;quot;dart&amp;quot;,
            &amp;quot;flutterMode&amp;quot;: &amp;quot;profile&amp;quot;
        },
        {
            &amp;quot;name&amp;quot;: &amp;quot;safetyedu (release mode)&amp;quot;,
            &amp;quot;request&amp;quot;: &amp;quot;launch&amp;quot;,
            &amp;quot;type&amp;quot;: &amp;quot;dart&amp;quot;,
            &amp;quot;flutterMode&amp;quot;: &amp;quot;release&amp;quot;
        },
        {
            &amp;quot;name&amp;quot;: &amp;quot;safetyedu (attach)&amp;quot;,
            &amp;quot;request&amp;quot;: &amp;quot;attach&amp;quot;,
            &amp;quot;type&amp;quot;: &amp;quot;dart&amp;quot;
        } // 이 부분 추가
    ]
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;이름은 프로젝트 세팅마다 다를 것이다.&lt;/p&gt;
&lt;p&gt;여기서 초기에는 맨 앞 세 세팅밖에 없을 것이다.&lt;/p&gt;
&lt;p&gt;여기서 맨 마지막 부분을 추가한다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-json&quot;&gt;{
    &amp;quot;name&amp;quot;: &amp;quot;safetyedu (attach)&amp;quot;,
    &amp;quot;request&amp;quot;: &amp;quot;attach&amp;quot;,
    &amp;quot;type&amp;quot;: &amp;quot;dart&amp;quot;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;그리고 다시 명령 팔레트 창으로 돌아와서, attach를 검색한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; width=&quot;100%&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ZlNHG/btsEZpzI301/8Y7Eu1SUKFvyMeTIzQXi9k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ZlNHG/btsEZpzI301/8Y7Eu1SUKFvyMeTIzQXi9k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ZlNHG/btsEZpzI301/8Y7Eu1SUKFvyMeTIzQXi9k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FZlNHG%2FbtsEZpzI301%2F8Y7Eu1SUKFvyMeTIzQXi9k%2Fimg.png&quot; width=&quot;100%&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;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;&lt;code&gt;Debug: Attach to Flutter on Device&lt;/code&gt;가 보인다. 이를 누르면…&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; width=&quot;100%&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bCsUXj/btsEWw003su/IFlCcpU6kzUr3f4Shp6q6k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bCsUXj/btsEWw003su/IFlCcpU6kzUr3f4Shp6q6k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bCsUXj/btsEWw003su/IFlCcpU6kzUr3f4Shp6q6k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbCsUXj%2FbtsEWw003su%2FIFlCcpU6kzUr3f4Shp6q6k%2Fimg.png&quot; width=&quot;100%&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;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;이렇게 되면 성공이다.&lt;/p&gt;
&lt;p&gt;xcode에서 실행한 앱을 Attach하였다.&lt;/p&gt;
&lt;p&gt;앱도 잘 작동하고, 터미널에서 로그도 정상적으로 출력된다.&lt;/p&gt;
&lt;p&gt;임시 방편이기는 하나, 현재로써는 이 방법이 가장 유효한 진전이었다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; width=&quot;100%&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/tOHFa/btsEZCFAz1H/NhEYZ1MjhfKhUB4Ruww980/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/tOHFa/btsEZCFAz1H/NhEYZ1MjhfKhUB4Ruww980/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/tOHFa/btsEZCFAz1H/NhEYZ1MjhfKhUB4Ruww980/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FtOHFa%2FbtsEZCFAz1H%2FNhEYZ1MjhfKhUB4Ruww980%2Fimg.png&quot; width=&quot;100%&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;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;Detach는 우측에서 두 번째 빨간 플러그 아이콘을 누르면 되고, 나머지 아이콘은 전부 기존과 동일하게 사용할 수있다.&lt;/p&gt;
&lt;p&gt;혹시 만약 아래와 같은 에러가 뜰 수 있다.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Waiting for a connection from Flutter on iPhone 15 Pro...
There are multiple Dart VM Services available.
Rerun this command with one of the following passed in as the app-id and device-vmservice-port:

  flutter attach --app-id &amp;quot;com.example.safetyedu&amp;quot; --device-vmservice-port 53713
  flutter attach --app-id &amp;quot;com.example.safetyedu (2)&amp;quot; --device-vmservice-port 54168

Exited (1).&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;글에서도 이미 터미널에서 &lt;code&gt;flutter attach&lt;/code&gt;를 실행하였기에 같은 에러가 발생했다.&lt;/p&gt;
&lt;p&gt;당황하지 말고, xcode에서 실행 중인 VM들을 전부 중지시킨다.&lt;/p&gt;
&lt;p&gt;그리고 다시 하나만 실행시킨 후, 명령 팔레트에서 attach를 시도하면 된다.&lt;/p&gt;
&lt;p&gt;Attach를 통해서 임시적으로 해결하기는 하였으나, 여전히 vscode에서 직접 실행하는 수단을 잃어버렸다.&lt;/p&gt;
&lt;p&gt;추후 해결이 된다면 어떻게 해결하였는지, 이 글에 추가해서 공유하겠다.&lt;/p&gt;</description>
      <category>Flutter, Dart/Flutter</category>
      <category>flutter attach</category>
      <category>flutter 에러</category>
      <author>nx006</author>
      <guid isPermaLink="true">https://nx006.tistory.com/77</guid>
      <comments>https://nx006.tistory.com/77#entry77comment</comments>
      <pubDate>Mon, 19 Feb 2024 04:35:22 +0900</pubDate>
    </item>
    <item>
      <title>바쁜 비버 함수</title>
      <link>https://nx006.tistory.com/76</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;
&lt;script&gt;
    window.MathJax = {
        tex: {
            inlineMath: [['$', '$'], ['\\(', '\\)']],
            displayMath: [['$$', '$$'], ['\\[', '\\]']],
            processEscapes: true,
            packages: {'[+]': ['base', 'mathtools', 'cases' ]}
        },
        loader: {
            load: ['[tex]/mathtools', '[tex]/cases']
        }
    };
&lt;/script&gt;
&lt;script type=&quot;text/javascript&quot; src=&quot;https://cdn.jsdelivr.net/npm/mathjax@3/es5/tex-mml-chtml.js&quot;&gt;
&lt;/script&gt;
&lt;/p&gt;
&lt;h1&gt;바쁜 비버&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;지난 학기에 논리회로 과목을 수강하던 중, 기말고사 시험 문제로 &amp;lsquo;바쁜 비버&amp;rsquo;에 대한 문제가 출제되었다.&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;/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;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;바쁜 비버 게임을 소개한다.&lt;/li&gt;
&lt;li&gt;바쁜 비버 함수와 최대 시프트 함수를 소개한다. 바쁜 비버 함수의 계산 불가능성을 증명한다.&lt;/li&gt;
&lt;li&gt;최대 시프트 함수를 중심으로 성질과 하한을 찾아가는 과정을 다룬다. 이 과정에서 아커만 함수, ZF 공리계, 콜라츠 추측과의 연관 관계를 다룰 것이다.&lt;/li&gt;
&lt;li&gt;바쁜 비버로부터 파생된 함수들을 다룬다.&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;바쁜 비버 게임&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;바쁜 비버 게임은 1962년 Tibor Rad&amp;oacute;의 논문 &lt;i&gt;On Non-Computable Functions&lt;/i&gt;에서 처음 소개되었다. Tibor Rad&amp;oacute;는 컴퓨터 과학에서 많은 업적을 남겼는데, 대부분 튜링 머신과 계산 불가능성에 집중되었다. 이 논문 역시 바쁜 비버 함수의 계산 불가능성(Non-computable)에 대해 다룬다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;264&quot; data-origin-height=&quot;321&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bPJWE9/btsEEWd6kQ7/a4FHDoIanWCT0NZuv4hkw1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bPJWE9/btsEEWd6kQ7/a4FHDoIanWCT0NZuv4hkw1/img.png&quot; data-alt=&quot;Tibor Rado&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bPJWE9/btsEEWd6kQ7/a4FHDoIanWCT0NZuv4hkw1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbPJWE9%2FbtsEEWd6kQ7%2Fa4FHDoIanWCT0NZuv4hkw1%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; alt=&quot;Tibor Rado&quot; loading=&quot;lazy&quot; width=&quot;264&quot; height=&quot;321&quot; data-origin-width=&quot;264&quot; data-origin-height=&quot;321&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;Tibor Rado&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;528&quot; data-origin-height=&quot;825&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cvTGYx/btsEFhvsU9J/uEWdrKikGOGPKSbt5rGnO1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cvTGYx/btsEFhvsU9J/uEWdrKikGOGPKSbt5rGnO1/img.png&quot; data-alt=&quot;바쁜 비버 기계가 소개된 On Non-Computable Functions의 첫 페이지&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cvTGYx/btsEFhvsU9J/uEWdrKikGOGPKSbt5rGnO1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcvTGYx%2FbtsEFhvsU9J%2FuEWdrKikGOGPKSbt5rGnO1%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; alt=&quot;바쁜 비버 기계가 소개된 On Non-Computable Functions의 첫 페이지&quot; loading=&quot;lazy&quot; width=&quot;528&quot; height=&quot;825&quot; data-origin-width=&quot;528&quot; data-origin-height=&quot;825&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;바쁜 비버 기계가 소개된 On Non-Computable Functions의 첫 페이지&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이후 바쁜 비버와 튜링 머신에 관한 연구는 T. Rad&amp;oacute;의 후속 연구인 &lt;i&gt;Computer Studies of Turing Machine Problems(1965)&lt;/i&gt;에서 이어진다. 이 논문은 바쁜 비버 게임의 Shift 연산에 대한 논의를 포함하고 있다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;이진 튜링 머신&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;바쁜 비버 게임을 소개하기 전에, 바쁜 비버 게임이 진행될 &lt;b&gt;이진 튜링 머신(Binary Turing Machine)&lt;/b&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;이진 튜링 머신은 S C. Kleene의 &lt;i&gt;Introduction to Metamathematics(1952)&lt;/i&gt;에서 소개된 튜링 머신을 일부 변형하여 작동한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1143&quot; data-origin-height=&quot;859&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ENaom/btsEGSBE04h/LxKl31sbXG6GTbxfurjV3K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ENaom/btsEGSBE04h/LxKl31sbXG6GTbxfurjV3K/img.png&quot; data-alt=&quot;Introduction to Metamathematics&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ENaom/btsEGSBE04h/LxKl31sbXG6GTbxfurjV3K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FENaom%2FbtsEGSBE04h%2FLxKl31sbXG6GTbxfurjV3K%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; alt=&quot;Introduction to Metamathematics&quot; loading=&quot;lazy&quot; width=&quot;1143&quot; height=&quot;859&quot; data-origin-width=&quot;1143&quot; data-origin-height=&quot;859&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;Introduction to Metamathematics&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그렇기 때문에 책에서 소개된 튜링 머신에 대해서 다루어야 하는데, 여느 책에서 소개되는 튜링 머신과 크게 다를 건 없다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;셀로 나누어진, 선형의 무한히 긴 테이프&lt;/li&gt;
&lt;li&gt;유한 개의 상태 중 하나의 상태에 있는 제어 장치&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;크게 두 장치로 구성되어 있으며, 테이프에서 기호를 읽고 제어 장치의 상태를 변형시킨 뒤, 다시 테이프의 각 셀에 기호를 쓸 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;튜링 머신 $T=(Q, S, f, q_0)$는 유한 상태 집합 $Q={ q_1, \ldots, q_j}(j\ge 1)$(이때 $q_1$는 시작 상태)와 기호 $S$로 구성된다. $S={s_0, s_1, \ldots, s_k}(k\ge 1)$에서 $s_0$는 공백(blank)를 의미한다.&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;각 셀은 비어있거나(blank, $s_0$), 유한 개의 기호(symbol, $S$)로 채워질 수 있다. 튜링 머신의 한 동작(move)은 원자적(atomic)적이다.&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;제어 장치가 현재의 테이프 기호 $s_a$를 읽는다고 해보자.&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;제어 장치가 상태 $q_c$에 있고, 부분 함수 $f$가 $(q_c, s_a)$에 대해 $f(q_c, s_a)=(q_d, s_b, d)$로 정의될 때 제어 장치는 다음 동작을 한다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;상태 $q_c$으로 이동한다.&lt;/li&gt;
&lt;li&gt;현재 셀에 있는 $s_a$를 지우고 기호 $s_b$을 쓴다.&lt;/li&gt;
&lt;li&gt;$d=R$이면 오른쪽으로 한 칸 이동하고, $d=R$이면 왼쪽으로 한 칸 이동한다. $d=C$이면 가만히 있는다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 동작을 기호로 표현하면, 다음 세 가지 형태로 표현할 수 있다:&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;$s_bLq_d$&lt;/li&gt;
&lt;li&gt;$s_bCq_d$&lt;/li&gt;
&lt;li&gt;$s_bRq_d$&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이진 튜링 머신은 셀에 적힌 기호 $S={0, 1}$인 튜링 머신이다.&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;다만 바쁜 비버 게임에 사용되는 이진 튜링 머신에는 2가지 차이점을 두었다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;먼저 논의를 간편하게 하기 위해, &lt;u&gt;Center Shift&lt;/u&gt;($d=C$)&lt;u&gt;를 허용하지 않는다.&lt;/u&gt; 따라서 제어 장치의 모든 이동은 &amp;ldquo;덮어쓰기(overprint)&amp;rdquo; 이후에 실행된다.&lt;/li&gt;
&lt;li&gt;T. Rad&amp;oacute;는 상태(state)라는 표현 대신 &lt;b&gt;카드(card)&lt;/b&gt;라는 표현을 썼다. 따라서 바쁜 비버 게임을 다루는 본 글에서도 카드라는 표현을 쓸 것이다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;${0, 1} &amp;times; {L, R}$의 이진 튜링 머신을 만들었으니 바쁜 비버 게임을 소개하겠다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;바쁜 비버 게임&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;무한히 긴 양방향 테이프를 생각해보자. 테이프의 모든 셀에는 0이 쓰여져 있다. 바쁜 비버 게임은 &lt;b&gt;n개 카드를 갖는 튜링 머신으로 최대한 많은 수의 1을 쓰는 게임&lt;/b&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;카드가 n개일 때 &lt;b&gt;BB-n 게임&lt;/b&gt;이라 하며, BB-n classification에서 가장 많은 1을 쓰는 튜링 머신이 &lt;b&gt;n번째 바쁜 비버&lt;/b&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;u&gt;&lt;b&gt;튜링 머신은 반드시 정지해야 한다.&lt;/b&gt;&lt;/u&gt; 그렇지 않으면 무한히 반복되기 때문이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;게임의 규칙을 살펴보자. 여기서는 카드가 3개인 BB-3를 예시로 설명하겠다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;449&quot; data-origin-height=&quot;178&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b9k3c7/btsEGS9vNsF/jehm7TSPdyKrz8MZRwQylK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b9k3c7/btsEGS9vNsF/jehm7TSPdyKrz8MZRwQylK/img.png&quot; data-alt=&quot;3-Card Turing Machine&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b9k3c7/btsEGS9vNsF/jehm7TSPdyKrz8MZRwQylK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb9k3c7%2FbtsEGS9vNsF%2Fjehm7TSPdyKrz8MZRwQylK%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; alt=&quot;3-card Turing Machine&quot; loading=&quot;lazy&quot; width=&quot;449&quot; height=&quot;178&quot; data-origin-width=&quot;449&quot; data-origin-height=&quot;178&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;3-Card Turing Machine&lt;/figcaption&gt;
&lt;/figure&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;3개의 카드(상태)가 존재하는 튜링 머신을 생각해보자.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;먼저 플레이어는 n개의 카드를 갖는 이진 튜링 머신을 정의한다. 초기 상태는 Card 1에서 시작하며, 반드시 정지해야 한다.&lt;/li&gt;
&lt;li&gt;맨 왼쪽 열(1열)에는 ${0, 1}$ 기호가 있다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;만약 현재 셀에서 읽은 값이 0이면 윗 행의 동작을, 1이면 아랫 행의 동작을 수행한다.&lt;/li&gt;
&lt;li&gt;예를 들어 현재 제어 장치의 상태가 $C_1$이고, 현재 셀의 값이 1이면, &lt;code&gt;113&lt;/code&gt;의 동작을 수행한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;그 다음 열(2열)은 &amp;ldquo;덮어쓸 값(overprint by)&amp;rdquo; 열이다. 마찬가지로 ${0, 1}$이 올 수 있다.&lt;/li&gt;
&lt;li&gt;그 다음 열(3열)은 &amp;ldquo;이동(shift)&amp;rdquo;할 열이다. 0이면 왼쪽, 1이면 오른쪽으로 이동한다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;본 글에서는 편의상 $L$, $R$로 표기한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;마지막 열(4열)은 &amp;ldquo;다음 카드(call card)&amp;rdquo; 열이다. 여기서는 제어 장치의 다음 카드의 인덱스 번호가 온다. 0은 정지를 의미한다. 카드가 세 개이므로 ${0, 1, 2, 3}$이 올 수 있다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://youtu.be/2PjU6DJyBpw?si=CY4BszBBvk9G0j0w&quot;&gt;A Turing Machine - Busy Beaver 4-state&lt;/a&gt;&lt;/p&gt;
&lt;figure data-ke-type=&quot;video&quot; data-ke-style=&quot;alignCenter&quot; data-video-host=&quot;youtube&quot; data-video-url=&quot;https://www.youtube.com/watch?v=2PjU6DJyBpw&quot; data-video-thumbnail=&quot;https://scrap.kakaocdn.net/dn/cc5jrX/hyVf2vmLRf/EQOHLj3DnLG3KyNcyVMK61/img.jpg?width=480&amp;amp;height=360&amp;amp;face=0_0_480_360&quot; data-video-width=&quot;480&quot; data-video-height=&quot;360&quot; data-video-origin-width=&quot;480&quot; data-video-origin-height=&quot;360&quot; data-ke-mobilestyle=&quot;widthContent&quot; data-original-url=&quot;&quot; data-video-title=&quot;&quot;&gt;&lt;iframe src=&quot;https://www.youtube.com/embed/2PjU6DJyBpw&quot; width=&quot;480&quot; height=&quot;360&quot; frameborder=&quot;&quot; allowfullscreen=&quot;true&quot;&gt;&lt;/iframe&gt;
&lt;figcaption style=&quot;display: none;&quot;&gt;&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;영상을 보면 이해가 빠를 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사진에 나와있는 3-카드 튜링 머신의 동작은 아래와 같다(밑줄친 굵은 글씨는 현재 셀의 위치를 나타낸다).&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%; height: 240px;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style13&quot;&gt;
&lt;tbody&gt;
&lt;tr style=&quot;height: 19px;&quot;&gt;
&lt;td style=&quot;width: 25%; height: 19px;&quot;&gt;단계&lt;/td&gt;
&lt;td style=&quot;width: 25%; height: 19px;&quot;&gt;카드&lt;/td&gt;
&lt;td style=&quot;width: 25%; height: 19px;&quot;&gt;테이프&lt;/td&gt;
&lt;td style=&quot;width: 25%; height: 19px;&quot;&gt;행동&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;width: 25%; height: 17px;&quot;&gt;1&lt;/td&gt;
&lt;td style=&quot;width: 25%; height: 17px;&quot;&gt;C1&lt;/td&gt;
&lt;td style=&quot;width: 25%; height: 17px;&quot;&gt;000&lt;b&gt;&lt;u&gt;0&lt;/u&gt;&lt;/b&gt;000&lt;/td&gt;
&lt;td style=&quot;width: 25%; height: 17px;&quot;&gt;1L2&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;width: 25%; height: 17px;&quot;&gt;2&lt;/td&gt;
&lt;td style=&quot;width: 25%; height: 17px;&quot;&gt;C2&lt;/td&gt;
&lt;td style=&quot;width: 25%; height: 17px;&quot;&gt;00&lt;u&gt;&lt;b&gt;0&lt;/b&gt;&lt;/u&gt;1000&lt;/td&gt;
&lt;td style=&quot;width: 25%; height: 17px;&quot;&gt;1R1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;width: 25%; height: 17px;&quot;&gt;3&lt;/td&gt;
&lt;td style=&quot;width: 25%; height: 17px;&quot;&gt;C1&lt;/td&gt;
&lt;td style=&quot;width: 25%; height: 17px;&quot;&gt;001&lt;u&gt;&lt;b&gt;1&lt;/b&gt;&lt;/u&gt;000&lt;/td&gt;
&lt;td style=&quot;width: 25%; height: 17px;&quot;&gt;1R3&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;width: 25%; height: 17px;&quot;&gt;4&lt;/td&gt;
&lt;td style=&quot;width: 25%; height: 17px;&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;C3&lt;/span&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 25%; height: 17px;&quot;&gt;0011&lt;u&gt;&lt;b&gt;0&lt;/b&gt;&lt;/u&gt;00&lt;/td&gt;
&lt;td style=&quot;width: 25%; height: 17px;&quot;&gt;1R2&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;width: 25%; height: 17px;&quot;&gt;5&lt;/td&gt;
&lt;td style=&quot;width: 25%; height: 17px;&quot;&gt;C2&lt;/td&gt;
&lt;td style=&quot;width: 25%; height: 17px;&quot;&gt;00111&lt;u&gt;&lt;b&gt;0&lt;/b&gt;&lt;/u&gt;0&lt;/td&gt;
&lt;td style=&quot;width: 25%; height: 17px;&quot;&gt;1R1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;width: 25%; height: 17px;&quot;&gt;6&lt;/td&gt;
&lt;td style=&quot;width: 25%; height: 17px;&quot;&gt;C1&lt;/td&gt;
&lt;td style=&quot;width: 25%; height: 17px;&quot;&gt;001111&lt;u&gt;&lt;b&gt;0&lt;/b&gt;&lt;/u&gt;&lt;/td&gt;
&lt;td style=&quot;width: 25%; height: 17px;&quot;&gt;1L2&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;width: 25%; height: 17px;&quot;&gt;7&lt;/td&gt;
&lt;td style=&quot;width: 25%; height: 17px;&quot;&gt;C2&lt;/td&gt;
&lt;td style=&quot;width: 25%; height: 17px;&quot;&gt;00111&lt;u&gt;&lt;b&gt;1&lt;/b&gt;&lt;/u&gt;1&lt;/td&gt;
&lt;td style=&quot;width: 25%; height: 17px;&quot;&gt;1L2&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;width: 25%; height: 17px;&quot;&gt;8&lt;/td&gt;
&lt;td style=&quot;width: 25%; height: 17px;&quot;&gt;C2&lt;/td&gt;
&lt;td style=&quot;width: 25%; height: 17px;&quot;&gt;0011&lt;u&gt;&lt;b&gt;1&lt;/b&gt;&lt;/u&gt;11&lt;/td&gt;
&lt;td style=&quot;width: 25%; height: 17px;&quot;&gt;1L2&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;width: 25%; height: 17px;&quot;&gt;9&lt;/td&gt;
&lt;td style=&quot;width: 25%; height: 17px;&quot;&gt;C2&lt;/td&gt;
&lt;td style=&quot;width: 25%; height: 17px;&quot;&gt;001&lt;u&gt;&lt;b&gt;1&lt;/b&gt;&lt;/u&gt;111&lt;/td&gt;
&lt;td style=&quot;width: 25%; height: 17px;&quot;&gt;1L2&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;width: 25%; height: 17px;&quot;&gt;10&lt;/td&gt;
&lt;td style=&quot;width: 25%; height: 17px;&quot;&gt;C2&lt;/td&gt;
&lt;td style=&quot;width: 25%; height: 17px;&quot;&gt;00&lt;u&gt;&lt;b&gt;1&lt;/b&gt;&lt;/u&gt;1111&lt;/td&gt;
&lt;td style=&quot;width: 25%; height: 17px;&quot;&gt;1L2&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;width: 25%; height: 17px;&quot;&gt;11&lt;/td&gt;
&lt;td style=&quot;width: 25%; height: 17px;&quot;&gt;C2&lt;/td&gt;
&lt;td style=&quot;width: 25%; height: 17px;&quot;&gt;0&lt;u&gt;&lt;b&gt;0&lt;/b&gt;&lt;/u&gt;11111&lt;/td&gt;
&lt;td style=&quot;width: 25%; height: 17px;&quot;&gt;1R1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;width: 25%; height: 17px;&quot;&gt;12&lt;/td&gt;
&lt;td style=&quot;width: 25%; height: 17px;&quot;&gt;C1&lt;/td&gt;
&lt;td style=&quot;width: 25%; height: 17px;&quot;&gt;01&lt;u&gt;&lt;b&gt;1&lt;/b&gt;&lt;/u&gt;1111&lt;/td&gt;
&lt;td style=&quot;width: 25%; height: 17px;&quot;&gt;1R3&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;width: 25%; height: 17px;&quot;&gt;13&lt;/td&gt;
&lt;td style=&quot;width: 25%; height: 17px;&quot;&gt;C3&lt;/td&gt;
&lt;td style=&quot;width: 25%; height: 17px;&quot;&gt;011&lt;u&gt;&lt;b&gt;1&lt;/b&gt;&lt;/u&gt;111&lt;/td&gt;
&lt;td style=&quot;width: 25%; height: 17px;&quot;&gt;1L0&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;13번의 이동을 통해 6개의 1을 작성하는데, 이는 &lt;b&gt;3번째 가장 바쁜 비버&lt;/b&gt;이다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;바쁜 비버 함수&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;바쁜 비버 함수(라도 시그마 함수)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;n개 카드 튜링 머신으로 바쁜 비버 게임을 진행했을 때 쓸 수 있는 1의 개수 최댓값을 $B(n)$으로 표기한다. 이를 &lt;b&gt;바쁜 비버 함수&lt;/b&gt; 혹은 &lt;b&gt;라도 시그마 함수(Rad&amp;oacute;'s sigma function)&lt;/b&gt; $\Sigma (n)$이라 한다. Rad&amp;oacute;는 그의 논문에서 $\Sigma (n)$ 표현을 사용했으며, 현대의 논문들은 바쁜 비버 함수를 $BB(n)$으로 표기하기도 한다.&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;바쁜 비버 함수의 가장 큰 특징으로는, 매우 빠르게 증가하는 함수란 사실이다. 얼마나 빠르게 증가하냐면, $n=5$정도만 되어도 그 값을 알지 못한다.&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;현재까지 $B(2)=4, B(3)=6, B(4)=13$으로 알려져 있으나, 5 이상의 n에 대해서는 $B(5)\ge 4098$, $B(6)\ge 3.5&amp;times;10^{18267}$ 임이 알려져 있을 뿐이다.&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;$B(n)$은 &lt;u&gt;매우 빠르게 증가하는 함수&lt;/u&gt;다. 그리고 이로부터 그 어떤 튜링 머신도 충분히 n에 대해 $B(n)$을 계산할 수 없음을, 즉 $B(n)$이 계산 불가능함(Non-computable)을 증명할 수 있다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일부 컴퓨터 과학을 다루는 한국어 번역본 서적에선 바쁜 비버 함수를 &amp;lsquo;&lt;b&gt;일벌레 함수&lt;/b&gt;&amp;rsquo;라고 번역하기도 한다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;바쁜 비버 함수의 계산 불가능성 증명&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;모든 n에 대해 BB-n classification의 가장 높은 점수 $B(n)$을 결정할 수 있을까?&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;이 질문은 바꾸어 말하면 튜링 머신으로 특정한 $n$에 대해 함수 $B(n)$을 계산하는 문제다. 이것이 왜 불가능한 지를 증명해보자.&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;튜링 머신의 부분 함수 $f(x)$, $g(x)$가 있을 때 특정한 $x_0$보다 큰 $x$에 대해 $f(x) &amp;gt; g(x)$임을 다음과 같이 표현한다:&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;$$&lt;br /&gt;f(x) &amp;gt; -g(x)&lt;br /&gt;$$&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;모든 계산 가능한 $f(x)$에 대해 $B(x) &amp;gt; -f(n)$임을 보여 $B(x)$가 계산 불가능함을 보일 것이다.&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;$f(x)$를 도입하기 위해 그 보조 함수(Auxiliary function) $F(x)$를 도입한다:&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;$$&lt;br /&gt;F(x) = \sum_{i=0}^{x}[f(i)+i^{2}]&lt;br /&gt;$$&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;f(x)가 계산 가능한 함수이므로 F(x)도 계산 가능한 함수이고, 자명하게 다음 성질들이 성립한다&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;i&gt;성질 1&lt;/i&gt;: $F(x) \ge f(x)$ ($\Sigma$ 안에 $f(i)$를 포함하고 있으므로)&lt;/li&gt;
&lt;li&gt;&lt;i&gt;성질 2&lt;/i&gt;: $F(x) \ge x^2$ ($\Sigma$ 안에 $i^2$ 항을 포함하고 있으므로)&lt;/li&gt;
&lt;li&gt;&lt;i&gt;성질 3&lt;/i&gt;: $F(x+1) &amp;gt; F(x)$ (증가함수이므로)&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;$F(x)$가 계산 가능하므로, $F(x)$를 계산하는(부분 함수로 갖는) n-카드 이진 튜링 머신 $M_F$를 설정한다.&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;그리고 임의의 양의 정수 $x$에 대해 $x+1$ 카드 이진 튜링 머신 $M^{(x)}$를 설정한다. 이 튜링 머신은 테이프에 $x+1$개의 연속된 1을 출력한 후 정지한다.&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;예를 들어 $x=2$에 대해 $M^{(2)}$는 다음 3-Cards를 갖는다:&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;618&quot; data-origin-height=&quot;244&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bA07Bj/btsEEXYn2yh/kjziMHz0q4eFP1EkDM9uzK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bA07Bj/btsEEXYn2yh/kjziMHz0q4eFP1EkDM9uzK/img.png&quot; data-alt=&quot;3-Card&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bA07Bj/btsEEXYn2yh/kjziMHz0q4eFP1EkDM9uzK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbA07Bj%2FbtsEEXYn2yh%2FkjziMHz0q4eFP1EkDM9uzK%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; alt=&quot;3-Card&quot; loading=&quot;lazy&quot; width=&quot;618&quot; height=&quot;244&quot; data-origin-width=&quot;618&quot; data-origin-height=&quot;244&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;3-Card&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 새로운 이진 튜링 머신 $M_F^{(x)}$을 도입하자. 이 튜링 머신은 $M^{(x)}$를 시작하여 $M_F$를 실행시키고, 다시 $M_F$를 실행하는 일련의 동작을 수행한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;$$&lt;br /&gt;M_{F}^{(x)} : M^{(x)} \rightarrow M_F \rightarrow M_F&lt;br /&gt;$$&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이때 $M^{(x)}$의 카드 수는 $x+1$, $M_F$의 카드 수는 C이다. $M_F^{(x)}$가 $M^{(x)}$와 2개의 $M_F$로 이루어져 있기에 $M_F^{(x)}$의 카드 수는 $1+x + 2c$이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;$M_F^{(x)}$의 All-Zero 테이프에서 출발한다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;먼저 오른쪽 방향으로 $x+1$개의 연속된 1을 출력한다.&lt;/li&gt;
&lt;li&gt;또다시 0이 나오면 그 오른쪽에 다시 연속된 $F[F(x)]+1$개의 1을 출력한다.&lt;/li&gt;
&lt;li&gt;마지막으로 가장 오른쪽에 출력한 1 아래에서 멈춘다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;$M_F^{(x)}$를 통해 구성된 이진 튜링 머신은 $B(1+x+2C)$에서의 동작에 해당된다. 곧 $T_F^{(x)}$는 $B(1+x+2c)$의 상한에 대한 항목이며($BB-(1+x+2C)$ Classification), 그 점수는 $3+x+F(x) + F[F(x)]$이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 이 Classification의 최대 점수 B(1+x+2C)는 다음을 만족한다(&lt;i&gt;부등식 1&lt;/i&gt;):&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;$$&lt;br /&gt;B(1 + x + 2C)\ge 3 + x + F(x) + F[F(x)]&lt;br /&gt;$$&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 자명하게 $x^2 &amp;gt; - (1+x+2C)$이고, &lt;i&gt;성질 2&lt;/i&gt;에서 $F(x) \ge x ^ 2$이므로&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;$$&lt;br /&gt;F(x) &amp;gt; - (1+x+2C)&lt;br /&gt;$$&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또한 &lt;i&gt;성질 3에서 F(x)가 단조 증가하므로 바로 위 식에서(부등식 2&lt;/i&gt;):&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;$$&lt;br /&gt;F[F(x)] &amp;gt; - F(1 + z + 2C)&lt;br /&gt;$$&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;를 끌어낼 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;_부등식 1, 2_에서:&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;$$&lt;br /&gt;B(1 +x + 2C) &amp;gt; - F(1 + x + 2C)&lt;br /&gt;$$&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;$F(x) \ge f(x)$이므로&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;$$&lt;br /&gt;B(1 +x + 2C) &amp;gt; - f(1 + x + 2C)&lt;br /&gt;$$&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;$n=1+x+2C$를 대입하면&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;$$&lt;br /&gt;B(n) &amp;gt; -f(n)&lt;br /&gt;$$&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;을 얻는다. 곧 $B(n)$은 그 어떤 계산 가능한 함수 $f(n)$보다 크므로 $B(n)$은 계산 불가능하다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;최대 시프트 함수&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;최대 시프트 함수(미친 개구리 함수)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;최대 시프트 함수 $S(n)$은, &lt;b&gt;BB-n Classification에서의 최대 이동 횟수&lt;/b&gt;에 대응한다. 미친 개구리 함수 $FF(x)$라 하기도 한다.&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;$k$개의 1을 쓰기 위해서는 적어도 $k$번 이상의 이동(Shift)을 거쳐야 하므로 자명하게 $S(n) \ge B(n)$이 성립한다.&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;앞에서의 증명과 비슷한 과정으로 모든 계산 가능한 함수 f(n)에 대해 $S(n) &amp;gt; -f(n)$임을 보일 수 있고, 따라서 최대 시프트 함수 역시 계산 불가능하다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Aaronson 교수 등 바쁜 비버 함수를 연구한 현대의 많은 이들은 최대 시프트 함수 $S(n)$을 바쁜 비버 함수 $BB(n)$으로 바꾸어 부르기도 한다. 그러나 본 글에서는 Rad&amp;oacute;가 처음 제시한 표현을 따르겠다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Rad&amp;oacute;는 1962년의 논문에서 최대 시프트 함수의 정의에 대해서만 제시하고, 그에 대한 자세한 연구는 1965년 &lt;i&gt;Computer Studies of Turing Machine Problems&lt;/i&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;최대 시프트 함수에 대한 논의는, BB-n Classification에서 가장 많은 1을 쓰는 튜링 머신은 하나가 아닌, 여러 개가 존재할 수 있다는 점에서 출발한다. 예를 들어 BB-3 Classification에서 6개의 1을 적는 튜링 머신은, 위에서 소개한 튜링 머신 이외에 여러 개가 있다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;430&quot; data-origin-height=&quot;94&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/5HYZV/btsEF7lCAT9/06MchgnUeCUAhDEHa0nSGK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/5HYZV/btsEF7lCAT9/06MchgnUeCUAhDEHa0nSGK/img.png&quot; data-alt=&quot;3-card Turing Machine (2)&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/5HYZV/btsEF7lCAT9/06MchgnUeCUAhDEHa0nSGK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F5HYZV%2FbtsEF7lCAT9%2F06MchgnUeCUAhDEHa0nSGK%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;430&quot; height=&quot;94&quot; data-origin-width=&quot;430&quot; data-origin-height=&quot;94&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;3-card Turing Machine (2)&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;153&quot; data-origin-height=&quot;341&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cu53Lp/btsEHp0oMOi/cGZ0WjQ2KKiBQqJKlTh1F0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cu53Lp/btsEHp0oMOi/cGZ0WjQ2KKiBQqJKlTh1F0/img.png&quot; data-alt=&quot;TM (2)의 작동&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cu53Lp/btsEHp0oMOi/cGZ0WjQ2KKiBQqJKlTh1F0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fcu53Lp%2FbtsEHp0oMOi%2FcGZ0WjQ2KKiBQqJKlTh1F0%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; alt=&quot;TM (2)의 작동&quot; loading=&quot;lazy&quot; width=&quot;153&quot; height=&quot;341&quot; data-origin-width=&quot;153&quot; data-origin-height=&quot;341&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;TM (2)의 작동&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 튜링 머신(2) 오른쪽 그림과 같은 과정을 거치며 6개의 1을 쓴다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래 그림은 튜링 머신(1), (2) 이외에 6개의 1을 쓰는 또다른 튜링 머신을 나열한 것이다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;659&quot; data-origin-height=&quot;640&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dNTZKM/btsEINzWd6p/fPbKZmBUyxqdKbSQKoIW30/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dNTZKM/btsEINzWd6p/fPbKZmBUyxqdKbSQKoIW30/img.png&quot; data-alt=&quot;여러 3-Card TM의 작동&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dNTZKM/btsEINzWd6p/fPbKZmBUyxqdKbSQKoIW30/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdNTZKM%2FbtsEINzWd6p%2FfPbKZmBUyxqdKbSQKoIW30%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; alt=&quot;여러 3-Card TM의 작동&quot; loading=&quot;lazy&quot; width=&quot;659&quot; height=&quot;640&quot; data-origin-width=&quot;659&quot; data-origin-height=&quot;640&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;여러 3-Card TM의 작동&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;주목해야 할 점은 1을 쓰기 위해서, 몇 번의 이동(Shift) 연산을 거쳐야 하는 지다. 같은 6개의 1을 쓰더라도, 이동 연산의 횟수가 모두 다르다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;649&quot; data-origin-height=&quot;461&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bzV5Zf/btsEExMEnSh/1pdyutOSjMlJGOkw3KRfAK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bzV5Zf/btsEExMEnSh/1pdyutOSjMlJGOkw3KRfAK/img.png&quot; data-alt=&quot;더 많은 이동 횟수를 갖는 3-Card TM&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bzV5Zf/btsEExMEnSh/1pdyutOSjMlJGOkw3KRfAK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbzV5Zf%2FbtsEExMEnSh%2F1pdyutOSjMlJGOkw3KRfAK%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; alt=&quot;더 많은 이동 횟수를 갖는 3-Card TM&quot; loading=&quot;lazy&quot; width=&quot;649&quot; height=&quot;461&quot; data-origin-width=&quot;649&quot; data-origin-height=&quot;461&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;더 많은 이동 횟수를 갖는 3-Card TM&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약 6개의 1을 포기한다면, 더 많은 이동 횟수를 갖는 튜링 머신을 확인할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 그림은 5개의 1을 쓰며, 20 이상의 이동 횟수를 갖는 튜링 머신의 예시다.&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;u&gt;최대 시프트 함수와 바쁜 비버 함수가 갖는 튜링 머신은 서로 다를 수 있다.&lt;/u&gt; 예컨대 BB-3 Classification에서 $B(3) = 6$, $S(3) = 21$이지만, 21번의 이동 연산을 통해 6개의 1을 찍는 튜링머신은 존재하지 않는다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;최대 시프트 함수의 엄밀한 정의&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;최대 시프트 함수 $S(n)$을 엄밀히 정의해보자.&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;튜링 머신 $M$에 대해, M이 초기 all-0 테이프에서 실행될 때 $s(M)$을 M이 정지하기 전까지 Shift Step의 수로 정의하자. 이때 마지막 정지 STEP도 포함된다.&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;만약 M이 정지하지 않는다면 $s(M)\coloneqq\infty$로 표기한다.&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;한편 n 카드 이진 튜링 머신 $T(n)$을 생각해보자. 이때 $|T(n)| = (4n+1)^{2n}$이 성립한다 &lt;i&gt;(Rad&amp;oacute;는 이 증명을 생략했다).&lt;/i&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;이를 계산하면 각각의 n-카드 머신을 $n\log_{2}n + O(n)$ 개의 비트를 이용해 특정할 수 있다.&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;그렇다면 $S(n)$을 아래와 같이 정의할 수 있다:&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;$$&lt;br /&gt;S(n)\coloneqq \max_{M\in T(n) : s(M) &amp;lt; \infty} s(M)&lt;br /&gt;$$&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이는 양의 정수 n에 대해 성립한다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;최대 시프트 함수와 바쁜 비버 함수와의 관계&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자명하게 $S(n) \ge B(n)$이 성립하지만, 현대에서는 더 구체적인 관계성을 확인했거나 추정하고 있다.&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;2002년 Ben Amram과 Petersen은 바쁜 비버 함수를 이용해 최대 시프트 함수의 상한을 제시했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;$$&lt;br /&gt;\exists c \text { s.t. } \forall n : S(n) &amp;lt; B(n+8\lceil \frac{n}{\log_2 n}\rceil + c)&lt;br /&gt;$$&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이는 2024년 현재까지 확인된 최대 시프트 함수의 가장 강한 상한이기도 하다.&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;더 나아가서 Aaronson은 2020년, 추정이기는 하나 더 구체적인 상한을 제시했다:&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;$$&lt;br /&gt;\forall n \ge4: S(n) &amp;lt;B(n+1)&lt;br /&gt;$$&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이는 $n\ge 4$일 때 성립되는 데, 현재까지 확인된 바로는 $n = 3$일 때 $S(3) = 21$, $B(4) = 13$에서 거짓인 것 외에는 모두 성립된다고 한다.&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;동시에 그는 더 과감한 추정을 제시했다. $S(n)\approx B(n)^2$이 성립한다는 추정이다. 즉 다시 말해:&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;$$&lt;br /&gt;\lim_{n\to\infty} \frac{\log S(n)}{\log B(n)} = 2&lt;br /&gt;$$&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;가 성립한다는 추측이다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;하한과 특징&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;본 차례에선 최대 시프트 함수를 주인공으로 삼고 있지만, 바쁜 비버 함수(라도 시그마 함수)에도 동일하게 적용된다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;아커만 함수와의 비교&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;최대 시프트 함수 $S(n)$의 더 일반적인 하한은 알려져 있다. $n$이 작을 때만 유의미한 약한 하한이기는 하나, 1964년 Green은 &lt;b&gt;아커만 함수(Ackermann function)&lt;/b&gt; $A$에 대해, $n\ge 2$에서 $S(2n) \ge A(n-2)$ 임을 보였다.&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;아커만 함수는 1928년 빌헬름 아커만(Wilhelm Ackermann)이 발표한 함수이다. 바쁜 비버 함수 이전에 폭발적으로 증가하는 함수의 대표격이었다. 재귀의 깊이가 매우 깊어 m, n이 4 이상 정도만 되어도 값이 매우 커지는 함수다.&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;br /&gt;A(m, n) = \begin{cases}&lt;br /&gt;n+1 &amp;amp;\quad\text{if } m=0, \\&lt;br /&gt;A(m-1, 1) &amp;amp;\quad\text{if } m&amp;gt;0\text{ and } n=0\\&lt;br /&gt;A(m-1, A(m, n-1))&amp;amp;\quad\text{if } m&amp;gt;0 \text{ and } n&amp;gt;0&lt;br /&gt;\end{cases}&lt;br /&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;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;공리계&lt;/h3&gt;
&lt;p 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;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;ZF - SRP 공리계&lt;/h4&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Aaronson 정리&lt;/b&gt;: ZF + SRP (SRP는 Stationary Ramsey Property의 약자) 집합 이론이 모순되는 경우에만 멈추는 &quot;명시적인&quot; 7910-카드 튜링 머신이 존재한다. 즉, 집합 이론이 모순이 없다면, ZF 공리계는 S(7910)의 값을 증명하지 못한다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;놀랍게도 이게 증명이 된다! 분명 $S(7910)$은 하나의 존재하는 숫자이다. 그리고 가능한 7910-카드 튜링 머신은 유한 개이고, 그 중 일부는 영원히 돌아가고, 일부는 정지한다. 그 정지하는 기계들 중 가장 길게 돌아가는 특정한 기계가 존재할 것이다.&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;Aaronson과 Adam 교수는 널리 사용되는 ZF 공리계 안에서, S(7910)이라는 정확한 값을 증명하는 것이 본질적으로 불가능함을 보였다(7910이 아니라 8000, 8001 등 더 큰 숫자도 마찬가지다).&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;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;먼저 7910 카드 튜링 머신을 손수 만든다. 이들은 이 튜링 머신을 만들기 위해 &amp;lsquo;&lt;b&gt;Laconic&lt;/b&gt;&amp;rsquo;이라고 하는 특수한 프로그래밍 언어를 직접 만들었다. 이 튜링 머신은 ZF 공리계에서 나올 수 있는 결과들을 하나하나 나열하고, 모순을 발견하는 경우(0=1을 보이는 경우)에만 멈추는 기계이다.&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;집합론에 일관성이 있다면, 즉 ZF 공리계가 모순이 없이 참이라면, 프로그램은 영원히 실행될 것이다. 만약 프로그램이 영원히 실행된다는 것을 증명한다면, 이는 곧 ZF 집합론의 일관성을 증명하는 것과 같다.&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;괴델의 증명에 따르면 A라는 집합론이 있을때, A만을 갖고서 A의 일관성을 증명하지 못한다. 다시 말해 위의 프로그램이 영원히 돌아가는 지, 혹은 멈추는 지를 ZF 공리계를 갖고 증명하지 못한다.&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;이미 한 집합론 내에서 알아낼 수 있는 바쁜 비버 함수의 값이 유한 개라는 것은 알려져 있었다. 이들은 고도의 최적화와 공학적 기술을 통해(특수한 인코딩 기법을 사용했다), 그 값이 8000보다 아래라는 것을 알아냈다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;ZF 공리계&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그 이후에 Stefan O&amp;rsquo;Rear는 이 값을 1919로 내렸고, 이후에는 748-카드 튜링 머신까지 증명했다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;O&amp;rsquo;Rear 정리&lt;/b&gt;: ZF 집합 이론이 모순되는 경우에만 멈추는 명시적인 748-카드 튜링 머신이 존재한다. 즉, 집합 이론이 모순이 없다면, ZF 공리계는 S(748)의 값을 증명하지 못한다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;O&amp;rsquo;Rear는 Laconic 언어와, Aaronson이 사용한 인코딩 기법을 재활용했다. 여기서 더 나아가 Friedman의 ZF + SRP 이론에서 벗어나, SRP에 대한 의존성을 제거하고 ZF 공리계에 직접 접근하여 연구를 진행했다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;골드바흐의 추측&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Code Golf Addict라는 github 유저는 다음을 증명했다(&lt;a href=&quot;https://gist.github.com/anonymous/a64213f391339236c2fe31f8749a0df6&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;링크&lt;/a&gt;):&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Code Golf Addict의 증명&lt;/b&gt;: 골드바흐의 추측이 거짓이라면 멈추는 명시적인 27-카드 튜링 머신이 존재한다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그 이전에 Jared Showalter는 같은 내용의 47-카드 튜링 머신을 증명했는데, 여기에 더 자세한 설명이 담겨있다: &lt;a href=&quot;https://gist.github.com/jms137/cbb66fb58dde067b0bece12873fadc76&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://gist.github.com/jms137/cbb66fb58dde067b0bece12873fadc76&lt;/a&gt;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;리만 가설&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Matiyasevich, O&amp;rsquo;Rear, Aaronson은 다음을 증명했다(&lt;a href=&quot;https://github.com/sorear/metamath-turing-machines/blob/master/riemann-matiyasevich-aaronson.nql&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;링크&lt;/a&gt;):&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Matiyasevich, O&amp;rsquo;Rear, Aaronson의 증명&lt;/b&gt;: 리만 가설이 거짓이라면 멈추는 명시적인 744-카드 튜링 머신이 존재한다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;그 외 추정&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위의 내용은 이미 증명된 사실이다.&amp;nbsp;&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;Aaronson 교수는 여기서 더 나아가서 많은 추정들을 제시한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;현재까지 $S(5)$의 값은 증명되지 않았지만, 1990년 Marxen과 Buntrock이 발견한 &lt;u&gt;47176870번의 스텝이 $S(5)$의 값&lt;/u&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;반대로 $S(6)$, $S(7)$ 등 6 이상부터는 그 값을 영원히 알지 못할 것이라 추측된다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;&lt;b&gt;Aaronson의 최대 시프트 함수 추정&lt;/b&gt;: $S(5)=47176870$이다. $n\ge 6$부터는 $S(n)$의 값을 알 지 못 한다.&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;한편 ZF 집합론에서 증명 불가능한 $S(n)$에 대해 최소의 n값은 얼마일까? 위에서 Aaronson은 이미 $S(748)$을 증명하지 못함을 보였다. 그는 &lt;u&gt;ZF 공리계가 $S(20)$을 증명하지 못할 것&lt;/u&gt;이라 추정한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;콜라츠 추측&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;콜라츠 추측(Collatz Conjecture)이란 게 있다. 함수 $f:\mathbb{N}\to \mathbb{N}$에 대해&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;$$&lt;br /&gt;f(x) = \begin{cases}&lt;br /&gt;\cfrac{x}{2} &amp;amp;\quad\text{if } x \text{ is even} \\&lt;br /&gt;3x+1 &amp;amp;\quad\text{if } x \text{ is odd}&lt;br /&gt;\end{cases}&lt;br /&gt;$$&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;가 있을 때, 모든 양의 정수 $x$에 대해 $(f(x), f(f(x)), \ldots)$로 유한 번 재귀 반복하면 1로 간다는 추측이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어 5에서 시작하면&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;$$&lt;br /&gt;5\to 16 \to 8\to 4\to2\to1&lt;br /&gt;$$&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 된다. 1에 도달한 이후에는 $4\to2\to1$을 반복한다.&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;이 추측은 현재까지 증명되지 않았지만, $x\le 2^{68}$까지 성립한다.&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;/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;위에서 Marxen과 Buntrock이 발견한 5번째 바쁜 비버 튜링 머신도 그 예시이다.&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;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;$$&lt;br /&gt;g(x) \coloneqq&lt;br /&gt;\begin{cases}&lt;br /&gt;\cfrac{5x+18}{3} &amp;amp; \quad\text{if } x \equiv 0\pmod{3} \\&lt;br /&gt;\cfrac{5x+22}{3} &amp;amp; \quad\text{if } x \equiv 1\pmod{3} \\&lt;br /&gt;\perp &amp;amp; \quad\text{if } x \equiv 2\pmod{3}&lt;br /&gt;\end{cases}&lt;br /&gt;$$&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;질문: 만약 $x=0$에서부터 시작해서 $x\coloneqq g(x)$를 재귀적으로 반복한다면 결국 $\perp$ 상태에 도달할까?&lt;/p&gt;
&lt;pre id=&quot;code_1707588155731&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;0 &amp;rarr; 6 &amp;rarr; 16 &amp;rarr; 34 &amp;rarr; 64 &amp;rarr; 114 &amp;rarr; 196 &amp;rarr; 334 &amp;rarr; 564
&amp;rarr; 946 &amp;rarr; 1584 &amp;rarr; 2646 &amp;rarr; 4416 &amp;rarr; 7366 &amp;rarr; 12284  &amp;rarr; ⟂&lt;/code&gt;&lt;/pre&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;5-카드 튜링 머신은 Marxen-Buntrock 머신 외에, 6-카드 튜링 머신인 Kropitz의 튜링 머신도 콜라츠 수열과 유사한 동작을 한다고 알려져있다.&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;이처럼 $n$이 낮은 수일 때, 바쁜 비버 튜링 머신은 콜라츠 수열과 비슷한 양상으로 행동한다. 이를 토대로, John Conway는 콜라츠 수열의 일반화된 버전이 튜링 결정불가능(Turing-Undecidable)함을 증명했다.&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;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;파생 함수&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;고차 바쁜 비버 함수&lt;/h3&gt;
&lt;p 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;/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;$B(n)$, $S(n)$은 튜링 머신으로 계산이 불가능한 함수다. 그러나 계산 가능성 이론에서는 어떤 $n$에 대해 $B(n)$, $S(n)$을 바로 출력하는 마법의 상자를 생각해볼 수 있다. 이 상자를 &amp;lsquo;&lt;b&gt;오라클(Oracle)&lt;/b&gt;&amp;rsquo;이라 부른다.&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;오라클은 현실에는 존재하지 않는, 마법적인 능력의 슈퍼 튜링 기계이다. 그렇다면 슈퍼 바쁜 비버 함수 $SBB(n)$을 &lt;b&gt;n 카드 슈퍼 튜링 기계가 중지하기 전 시행하는 최대 시행 수&lt;/b&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;이 슈퍼 바쁜 비버 함수 $SBB(n)$은 그 어떤 슈퍼 튜링 머신으로 계산할 수 있는 함수보다도 빠르게 증가한다. 예컨대 $B(n)$, $B(B(n))$보다 빠르게 증가한다.&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;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;  Scott Aaronson의 글 Who Can Name the Bigger Number?에서 쓰인 표현을 그대로 쓴 것인데, 원문은 각각 &lt;i&gt;Super Turing machine, super duper machine, super duper pooper machine&lt;/i&gt;으로 번역되었다.&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;더 강력한 튜링 머신을 정의하기 위한, 이러한 계층구조는 1943년 Kleene에 의해 만들어졌다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;물론 Kleene가 &lt;i&gt;'Super duper pooper'&lt;/i&gt;나 &lt;i&gt;'울트라 짱짱맨'&lt;/i&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;대신 $B_1(n)$, $B_2(n)$, $B_3(n)$과 같은 표현을 쓸 수 있다. $B_1(n)$을 보통의 바쁜 비버 함수라 한다면, 2차 바쁜 비버 함수 $B_2(n)$은 $B_1(n)$을 이용해 정의된 함수이고, 3차 바쁜 비버 함수 $B_3(n)$은 $B_2(n)$으로 정의된 함수이다. 곧 n차 바쁜 비버 함수를 정의할 수 있다.&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;수 체계를 확장한다면, &amp;omega;차 바쁜 비버 함수 $B_\omega(n)$을 생각해볼 수 있다. 이때 &amp;omega;는 첫 번째 무한 서수(the first infinite ordinal, 첫 번째 서수)이다. 서수란 무한 집합까지 포함한 이름지을 수 있는 모든 숫자의 집합에서, 그 모든 것보다 더 큰 숫자로 가는 수 체계의 일종이다. $B_\omega(n)$은 모든 자연수 $k$에 대해 $B_k(n)$을 오라클로 쓸 수 있는 튜링 머신을 통해 정의된 바쁜 비버 함수다.&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;멈추지 않고 더 나아갈 수 있다. $B_\omega (n)$을 오라클로 쓰는 튜링 머신으로 $B_{w+1}(n)$을 정의할 수 있고, $B_{w+2}(n)$, $B_{w+3}(n)$ 등을 정의할 수 있다. 이를 무한히 반복하면 모든 자연수 k에 대해 $B_{w+k}(n)$을 오라클로 쓸 수 있는 튜링 머신을 통해 정의된 2&amp;omega;차 바쁜 비버 함수 $B_{2\omega}(n)$을 정의할 수 있다.&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;$B_{3\omega}(n)$, $B_{4\omega}(n)$ 등을 반복하면 모든 자연수 k에 대해 $B_{k\omega}(n)$을 오라클로 쓸 수 있는 튜링 머신을 통해 정의된 $\omega ^ 2$차 바쁜 비버 함수 $B_{\omega ^2}(n)$을 정의할 수 있고, $B_{\omega ^3}(n), B_{\omega ^ 4}(n), \cdots, B_{\omega^k}(n)$를 통해 $\omega^{\omega}$차 바쁜 비버 함수, $\omega^{\omega^{\omega}}$차 바쁜 비버 함수 등을 정의할 수 있다. &amp;omega;가 &amp;omega;번 반복되는 $\omega^{\omega^{\omega^{\dots}}}=\varepsilon_0$에 대해 $B_{\varepsilon_{0}}(n)$에 도착할 수도 있다. 각 함수는 오라클로 쓰이는 그 이전 함수보다 더 빠르게 증가한다.&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;언뜻 보면 &amp;lsquo;손오공에서 전투력 숫자 싸움&amp;rsquo;이나, &amp;lsquo;타노스보다 쎈 캉&amp;rsquo;처럼 아무 의미 없는 숫자 싸움으로 보일 수 있다. 하지만 서수를 통해 초월성의 개념을 체계화할 수 있고, 이러한 체계의 확장은 수학적 본질에도 맞닿아 있다고 믿는다.&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;/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;/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;가령 ZF 집합론에서 증명 가능한 가장 큰 서수 $\omega_{\text{ZF}}$에 대해 $B_{\omega_{\text{ZF}}}(n)$이 ZF에서 상상 가능한 가장 큰 비버 함수라 볼 수 있겠다.&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;한편 $\omega(n)$을 n-카드 튜링 머신에 의해 계산 가능한 모든 서수의 최상위값으로 정의한다면, $F(n)\coloneqq B_{\omega(n)}(n)$로 정의할 수 있다. 이는 모든 계산 가능한 서수에 대해 새로운 함수를 정의할 수 있음을 의미한다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;삑삑거리는 바쁜 비버 함수(Beeping Busy beaver function)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2차 바쁜 비버 함수 $B_2(n)$의 초기 값을 구하기 위해 정의된 함수다. Aaronson과 Friedman이 연구했다.&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;바쁜 비버 함수와 동작은 유사한데, 대신 미리 정해진 특정한 상태를 벗어날 때 &amp;lsquo;beep&amp;rsquo; 소리를 낸다. 이를 &amp;lsquo;beep state&amp;rsquo;라 한다.&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;beeping TM M에 대해 M이 초기 all-zero tape에서 시작할 때, $b(M)$을 M이 beep 소리를 내기까지 스텝 횟수라 할 때(만약 M이 무한번 소리내면 $b(M)\coloneqq\infty$) n 번째 삑삑거리는 바쁜 비버 $BBB(n)$은 다음과 같이 정의된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;$$&lt;br /&gt;BBB(n)\coloneqq \max_{M\in T(n) : b(M) &amp;lt; \infty}b(M)&lt;br /&gt;$$&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;$BBB(n)\ge S(n)$ 관계이고, n이 커질 수록 BBB는 S(n)과 비교해서 기하급수적으로 빠르게 증가한다.&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;$BBB(1)=1, BBB(2)=6$임이 알려져 있으며&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style13&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 25%;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;td style=&quot;width: 25%;&quot;&gt;A&lt;/td&gt;
&lt;td style=&quot;width: 25%;&quot;&gt;B&lt;/td&gt;
&lt;td style=&quot;width: 25%;&quot;&gt;C&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 25%;&quot;&gt;0&lt;/td&gt;
&lt;td style=&quot;width: 25%;&quot;&gt;1LB&lt;/td&gt;
&lt;td style=&quot;width: 25%;&quot;&gt;1RA&lt;/td&gt;
&lt;td style=&quot;width: 25%;&quot;&gt;1RC&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 25%;&quot;&gt;1&lt;/td&gt;
&lt;td style=&quot;width: 25%;&quot;&gt;0RB&lt;/td&gt;
&lt;td style=&quot;width: 25%;&quot;&gt;0LC&lt;/td&gt;
&lt;td style=&quot;width: 25%;&quot;&gt;1RA&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&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;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;$$&lt;br /&gt;BBB(3) \ge 55 &amp;gt; S(3) =21&lt;br /&gt;$$&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;임이 알려져 있다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;세미 바쁜 비버 함수(Semi-Busy beaver function)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;바쁜 비버 함수보다는 느리지만, 여전히 모든 계산 가능한 함수보다도 빠르게 증가하는, 중간에 위치한 함수다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;모든 계산 가능한 함수 $f$에 대해 $\exists n_f \text{ s. t. } \forall n\ge n_f : g(n) &amp;gt; f(n)$이지만&lt;/li&gt;
&lt;li&gt;$g$의 오라클에 대해 여전히 계산 불가능한, 정지하는(혹은 바쁜 비버와 동치인)&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;함수 $g:\mathbb{N}\to\mathbb{N}$이 존재함을 보이자.&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;$\mathbb{N}\to\mathbb{N}$ 함수 $f_1, f_2, \ldots$를 모든 계산 가능한 함수를 열거한 것이라 하자. 무한히 증가하는 비감소 함수 $\omega :\mathbb{N}\to\mathbb{N}$에 대해&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;$$&lt;br /&gt;g(n) \coloneqq \max_{i\le \omega(n)} f_i (n)&lt;br /&gt;$$&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;을 정의하자. g는 이미 모든 계산 가능한 함수 $f_i$보다 커질 것이므로 위에서 (1)을 만족한다. 이제 (2)를 만족시킬 &amp;omega;를 골라보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;튜링 머신 $R_1, R_2, \ldots$를 HALT에서 g로 가는 후보들의 축소(Reduction)를 열거한 것이라 하자. 그렇다면 아래 과정을 반복하여 &amp;omega;를 만들 수 있다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;$\omega(1) \coloneqq 1$이라 한다.&lt;/li&gt;
&lt;li&gt;각 $n=1, 2, 3,\ldots$에 대해
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;머신 $R^{g}_{w(n)}(x)$이 n 이하의 값에 대해서만 g를 쿼리하게끔 하는 $x \in {0, 1}^*$가 존재하고,&lt;/li&gt;
&lt;li&gt;$x\in \text{HALT}$인지 정확하게 결정하는 것에 실패한다면&lt;/li&gt;
&lt;li&gt;$\omega(n+1)\coloneqq \omega(n)+1$로 한다.&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;그렇지 않다면, $\omega(n+1)\coloneqq \omega(n)$로 한다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;omega;는 무한대로 증가한다고 가정하면, 증명하고자 하는 (2)를 만족한다. 모든 HALT에서 g로의 가능한 Reduction을 제거하기 때문이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그렇다면 &amp;omega;가 무한대로 증가함을 보이자. 이를 보이기 위해선 우선 어떤 고정된 값 $\omega^{*}$에 대해 $\omega(n)$이 증가하지 않는다면, $g$가 계산 가능하다는 것을 생각해보자. $R^{g}_{w^*}$는 모든 입력에 대해 HALT할 것이다. 그러므로 HALT는 계산 가능하다. 실제론 HALT는 계산 불가능하다. 이는 모순이므로 곧 &amp;omega;는 무한대로 증가한다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;차분한 오리너구리 함수(Placid playtpus function)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;$B(n) \ge n$이라는 것은 자명하다. n 카드 튜링 머신이 n개의 1을 출력한 후 정지하는 상황을 생각해보라.&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;Boolos와 Jeffrey는 주어진 n 카드 튜링 머신을 갖고, 원래 기계가 출력하는 1의 문자열 길이의 두 배를 생성하는 튜링 머신을 연구했다. $B(n+7) \ge 2n$, 즉 n 카드 튜링 머신이 m개의 1을 출력할 때, n+7k 카드의 튜링 머신이 $m &amp;times; 2^k$개의 1을 쓴다는 것을 밝혔다.&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;더 나아가 Dewdney는 1993년 $B(n) \ge 2^n$임을 주장했다. 즉 m개의 1을 쓰기 위해 필요한 카드의 개수는 최대 $\log_2 m$개를 넘지 않는다는 의미다.&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;u&gt;&amp;ldquo;n개의 1을 쓰기 위해 필요한 최소한의 카드 개수는 무엇인가?&amp;rdquo;&lt;/u&gt;라는 질문으로 이어진다. 이에 대한 함수를 &lt;b&gt;차분한 오리너구리 함수(Placid playtpus function)&lt;/b&gt; $PP(n)$이라 한다.&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;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;$$&lt;br /&gt;PP^{-1}(n) = BB(n)&lt;br /&gt;$$&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;곧 m개의 1을 출력하는 n 카드 튜링 머신이 있을 때 $BB(n) \ge m$과 $PP(m) \le n$을 동시에 만족한다.&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;아커만 함수의 역함수와 마찬가지로, $BB(n)$이 매우 빠르게 증가하는 만큼 $PP(n)$ 역시 &lt;u&gt;매우 느리게 증가한다.&lt;/u&gt; &amp;lsquo;우주적으로&amp;rsquo; 큰 수를 집어넣어도 매우 작은 결과가 튀어나오며, 계산 불가능한 함수의 역함수이기에 $PP(n)$ 역시 계산 불가능하다.&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;그럼에도 불구하고 $PP(n)$ 역시 증가하는 함수이며, $\displaystyle\lim_{n\to\infty} PP(n) = \infty$이다&lt;i&gt;(단지 매우 느리게 증가할 뿐이다)&lt;/i&gt;.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;피곤한 웜뱃 함수(Weary wombat function)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;최대 시프트 함수에도 똑같은 논리로, &lt;u&gt;n번의 이동 연산을 위해 필요한 최소한의 카드 개수&lt;/u&gt;를 &lt;b&gt;피곤한 웜뱃 함수&lt;/b&gt;(Weary wombat function) $WW(n)$으로 정의한다. $WW(n)$은 $S(n)$의 역함수이다.&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;br /&gt;FF^{-1}(n) = S(n)&lt;br /&gt;$$&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;한편 차분한 오리너구리 함수, 피곤한 웜뱃 함수의 초기 값들은 대략적으로 구해져있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%; height: 170px;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style9&quot;&gt;
&lt;tbody&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;width: 33.3333%; height: 17px;&quot;&gt;n&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%; height: 17px;&quot;&gt;$PP(n)$&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%; height: 17px;&quot;&gt;$WW(n)$&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;width: 33.3333%; height: 17px;&quot;&gt;4&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%; height: 17px;&quot;&gt;2&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%; height: 17px;&quot;&gt;4&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;width: 33.3333%; height: 17px;&quot;&gt;5&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%; height: 17px;&quot;&gt;3&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%; height: 17px;&quot;&gt;7&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;width: 33.3333%; height: 17px;&quot;&gt;6&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%; height: 17px;&quot;&gt;3&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%; height: 17px;&quot;&gt;11&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;width: 33.3333%; height: 17px;&quot;&gt;7&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%; height: 17px;&quot;&gt;4&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%; height: 17px;&quot;&gt;12&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;width: 33.3333%; height: 17px;&quot;&gt;8&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%; height: 17px;&quot;&gt;4&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%; height: 17px;&quot;&gt;14&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;width: 33.3333%; height: 17px;&quot;&gt;9&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%; height: 17px;&quot;&gt;4&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%; height: 17px;&quot;&gt;19&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;width: 33.3333%; height: 17px;&quot;&gt;10&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%; height: 17px;&quot;&gt;4&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%; height: 17px;&quot;&gt;30&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;width: 33.3333%; height: 17px;&quot;&gt;11&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%; height: 17px;&quot;&gt;4&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%; height: 17px;&quot;&gt;40&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;width: 33.3333%; height: 17px;&quot;&gt;12&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%; height: 17px;&quot;&gt;4&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%; height: 17px;&quot;&gt;53&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;13&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;4&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;96&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;14&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;5&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;&amp;le; 41&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;15&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;5&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;&amp;le; 45&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;마치며&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번 글을 통해 바쁜 비버 함수에 대한 여러 내용을 간단히 살펴보았다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;바쁜 비버 함수에 대한 연구는 현재도 계속되는 중인데, 이중 많은 부분은 수 이론(Number Theory)와 연관되어 있어서, 글을 쓰는 데 있어 어려운 점도 있었고, 특히 서수에 관한 부분은 이해를 포기했다...&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;한편 여전히 현대의 컴퓨터과학자들이 답하지 못하는 질문들도 많다. 가령 $S(6)$, $B(7)$과 같은 값은 짝수인가, 홀수인가라는 질문 등이다.&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;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 글은 바쁜 비버에 대한 자세하거나 엄밀한 내용을 다루고 있지 않다. 이에 대해 더 자세히 알고 싶다면, 아래 References를 확인해보라.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;  References&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Aaronson, S. (2020). &lt;i&gt;The busy beaver frontier&lt;/i&gt;. ACM SIGACT News, 51(3), 32-54.&lt;/li&gt;
&lt;li&gt;Aaronson, S.&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;i&gt;&lt;a href=&quot;https://www.scottaaronson.com/writings/bignumbers.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Who Can Name the Bigger Number?&lt;/a&gt;&lt;/i&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;Shtetl-Optimized.&lt;/li&gt;
&lt;li&gt;Fu, L. E., &amp;amp; Pan, S. (2022). &lt;i&gt;The Busy Beaver Problem&lt;/i&gt;.&lt;/li&gt;
&lt;li&gt;Harland,&amp;nbsp;J.&amp;nbsp;(2006,&amp;nbsp;January).&amp;nbsp;&lt;i&gt;The&amp;nbsp;Busy&amp;nbsp;Beaver,&amp;nbsp;the&amp;nbsp;Placid&amp;nbsp;Platypus&amp;nbsp;and&amp;nbsp;other&amp;nbsp;Crazy&amp;nbsp;Creatures&lt;/i&gt;.&amp;nbsp;In&amp;nbsp;CATS&amp;nbsp;(Vol.&amp;nbsp;6,&amp;nbsp;pp.&amp;nbsp;79-86).&lt;/li&gt;
&lt;li&gt;Hhan. (2020, June 1).&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;i&gt;&lt;a href=&quot;https://pseudorandomstring.wordpress.com/2020/06/01/%ED%81%B0-%EC%88%AB%EC%9E%90%EB%93%A4-by-scott-aaronson/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;큰 숫자들 by Scott Aaronson&lt;/a&gt;&lt;/i&gt;. PSEUDORANDOM THINGS.&lt;/li&gt;
&lt;li&gt;Kleene, S. C. (1952). &lt;i&gt;Introduction to metamathematics&lt;/i&gt;. Princeton, N. J.: D. Van Nostrand Co.&lt;/li&gt;
&lt;li&gt;Lin, S., &amp;amp; Rado, T. (1965). &lt;i&gt;Computer studies of Turing machine problems&lt;/i&gt;. Journal of the ACM (JACM), 12(2), 196-212.&lt;/li&gt;
&lt;li&gt;Rado, T. (1962). &lt;i&gt;On non‐computable functions&lt;/i&gt;. Bell System Technical Journal, &lt;i&gt;41&lt;/i&gt;(3), 877-884.&lt;/li&gt;
&lt;li&gt;Rosen, K. H. (2020). &lt;i&gt;Rosen의 이산수학&lt;/i&gt; (정진우, 역.; 8th ed.). McGraw-Hill Education Korea, LTD.&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>Computer Science</category>
      <category>계산 가능성 이론</category>
      <category>바쁜 비버 함수</category>
      <category>최대 시프트 함수</category>
      <author>nx006</author>
      <guid isPermaLink="true">https://nx006.tistory.com/76</guid>
      <comments>https://nx006.tistory.com/76#entry76comment</comments>
      <pubDate>Sun, 11 Feb 2024 02:39:24 +0900</pubDate>
    </item>
    <item>
      <title>부동소수점의 성질과 계산, 그리고 FPU</title>
      <link>https://nx006.tistory.com/75</link>
      <description>&lt;!-- Math Jax --&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;script type=&quot;text/x-mathjax-config&quot;&gt;
    MathJax.Hub.Config({
      TeX: {
        extensions: [&quot;cancel.js&quot;],  // cancel 확장 추가
        equationNumbers: { autoNumber: &quot;AMS&quot; }
      },
      tex2jax: {
        inlineMath: [['$', '$'], ['\\(', '\\)']],
        displayMath: [['$$', '$$'], ['\\[', '\\]']],
        processEscapes: true
      },
      &quot;HTML-CSS&quot;: {
        linebreaks: { automatic: true }
      },
      SVG: {
        linebreaks: { automatic: true }
      }
    });
      &lt;/script&gt;
&lt;script type=&quot;text/javascript&quot; src=&quot;https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.7/MathJax.js?config=TeX-AMS-MML_HTMLorMML&quot;&gt;
    &lt;/script&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h1&gt;부동 소수점의 성질과 연산, 그리고 FPU&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 글에서는 컴퓨터에서의 부동소수점을 저장하고 연산하는 법과 그 성질에 대해서 다루어볼 것이다. 또한 부동소수점을 처리하는 하드웨어인 FPU에 대해서 가볍게 소개하며, 어떻게 실수형 연산을 처리하는지를 알아볼 것이다.&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;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;부동소수점의 저장을 다룬다. 이 과정에서 수의 정규화와 비정규화에 대해 다룬다.&lt;/li&gt;
&lt;li&gt;부동소수점의 성질을 다룬다. 이 과정에서 머신 엡실론과 실수의 분포를 다룬다.&lt;/li&gt;
&lt;li&gt;부동소수점의 연산을 다룬다.&lt;/li&gt;
&lt;li&gt;FPU에 대해서 다룬다. 이 과정에서 Sparc 어셈블리어를 이용해서 어떻게 FPU를 활용하여 연산을 처리하는 지 알아본다.&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;  컴퓨터의 수의 표현과 부동 소수점&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;부동소수점의 표현&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;컴퓨터에서 숫자 체계는 크게 두 가지로 나뉜다. 정수(Integer)와 실수(Float).&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;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;고정 소수점 방식은 제한된 범위에서, 정수처럼 정확한 숫자의 표현이 가능하나, 표현 범위가 좁기에 특수한 경우에만 사용된다.&lt;/li&gt;
&lt;li&gt;부동 소수점 방식은 정확한 숫자의 표현이 불가능하나 넓은 범위를 효과적으로 커버할 수 있기에, 일반적으로 사용되는 방식이다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;컴퓨터가 어떻게 부동소수점을 저장하는 지 먼저 알아보자. 부동소수점의 표현과 연산에 대한 표준은 1985년 ANSI IEEE 754 표준으로 지정되었다. 단일정밀도(Single precision, Float), 2배정밀도(Double Precision), 4배정밀도(Quadruple Precision)에 대한 표준이 존재하는 데, 일반적으로 단일, 2배 정밀도까지는 하드웨어 적으로 처리되고, 4배 정밀도부터는 소프트웨어 연산을 통해 처리된다.&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;실수의 표현을 알기 위해서, 실수의 정규화(Normalization)에 대해서 알아야 한다.&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;컴퓨터 과학에서 정규화된 수(Normalized Number)란, $3.14 * 10 ^ 7$, $1.0 * 10 ^{-18}$과 같은 형태의 수를 의미한다. 이때 가수부에는 선행하는 0이 없어야 한다(즉 $0.1 * 10 ^{-1}$의 경우 $1.0 * 10 ^ {-2}$으로 표기해야 한다).&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;이때 중요한 것은 유효숫자의 표현이다. 이는 저장공간과 연산 능력에 한계가 있는 컴퓨터과학이나, 그 외 물리학 등 현실에 접목되는 학문에서 매우 중요한 문제이다. 현실에서는 무한한 수의 표현을 할 수 없기에, 적당한 유효숫자를 제한한다. 예를 들어 실수부의 유효 숫자가 세 자리라면, $1.xxx * 10^{n}$ 꼴로 표현되어야 한다($1.000 *10^2$ 등).&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;이때 xxx 부분을 &lt;b&gt;가수부(Fractional Part, Mantissa),&lt;/b&gt; n 부분을 &lt;b&gt;진수부(Exponent)&lt;/b&gt;라고 한다. 10의 경우는 base이다.&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;다만 컴퓨터는 이진(Binary) 체계이므로, Base로 10 대신 2를 사용한다.&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;또한 10진수와 달리 2진수에서는 정규화 시 가수부의 맨 처음은 항상 1이 된다. 따라서 1을 굳이 저장하지 않아도 되므로, 저장공간을 1비트 절약할 수 있다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;750&quot; data-origin-height=&quot;304&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/clioBc/btsCTxF0LCc/x6iQlcJCpuxSkshQbibQF1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/clioBc/btsCTxF0LCc/x6iQlcJCpuxSkshQbibQF1/img.png&quot; data-alt=&quot;32비트 실수(Float)의 구조, 사진출처: https://www.geeksforgeeks.org/ieee-standard-754-floating-point-numbers/&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/clioBc/btsCTxF0LCc/x6iQlcJCpuxSkshQbibQF1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FclioBc%2FbtsCTxF0LCc%2Fx6iQlcJCpuxSkshQbibQF1%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; alt=&quot;32비트 실수(Float)의 구조&quot; loading=&quot;lazy&quot; width=&quot;750&quot; height=&quot;304&quot; data-origin-width=&quot;750&quot; data-origin-height=&quot;304&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;32비트 실수(Float)의 구조, 사진출처: https://www.geeksforgeeks.org/ieee-standard-754-floating-point-numbers/&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;32비트 Float 부동 소수점을 기준으로 컴퓨터가 어떻게 실수를 저장하는 지 알아보자.&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;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;부호 비트(Sign bit): 맨 처음 비트는 부호(+, -)를 표현하는 부호 비트(s)로, 0이면 양(+), 1이면 음(-)을 의미한다.&lt;/li&gt;
&lt;li&gt;지수부(Exponent): 8비트로 지수(e)를 저장한다. 이때 e는 unsigned bit로 저장되며, 부호를 표시하기 위해 별도의 Sign 비트를 사용하는 것이 아니라, Bias를 더해 계산한다(후술).&lt;/li&gt;
&lt;li&gt;가수부(Mantissa): 23비트로 가수(f)를 저장한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 저장된 값의 실제 수치 N은 다음과 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;$$&lt;br /&gt;N = (-1)^s * 2^{e-b} * (1.f)_2&lt;br /&gt;$$&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이때 b는 bias로 단일정밀도에서는 127이 된다. 곧&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;$$&lt;br /&gt;N = (-1)^s * 2^{e-127} * (1.f)_2&lt;br /&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;bias를 두는 이유는 &lt;a href=&quot;https://unagi-zoso.tistory.com/8&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;이 글&lt;/a&gt;에 잘 설명되어 있으니 이 분 글을 첨부한다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;실습: IEEE 754 단정밀도 변환&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;설명만 들으면 복잡하니, 직접 계산해보자.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;IEEE754 to 10진수&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;먼저 &lt;code&gt;0xC094000&lt;/code&gt; 을 10진수로 변환해 보자.&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;16진수 &lt;code&gt;0xC0940000&lt;/code&gt;을 2진수로 변환하면 &lt;code&gt;1100 0000 1001 0100 0000 0000 0000 0000&lt;/code&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;이때 맨 앞 sign 비트가 1이므로, 이 수는 음수(-) 임을 알 수 있다. s = 1&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그 뒤 8비트는 지수부이다. $e = (10000001)_2 = 128 + 1 = 129$.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이때 Bias를 빼야 하므로 지수는 $e - b = 129 - 127 = 2$.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;마지막으로 23비트의 가수부는 &lt;code&gt;001 0100 0000 0000 0000 0000&lt;/code&gt; 이다. 소수점 뒤 0을 생략하면 $(1.00101)_2$으로 표현 가능하다.&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;곧 $N=(-1) * (1.00101)_{2} * 2^{2} = -(100.101)_{2} = -(4.625)_{10}$&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;10진수 to IEEE754&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;반대로 이번에는 10진수 -6.75를 IEEE 754 단정밀도 부동소수점 형식으로 변환해 보자.&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;$-6.75 = -1 *(1.1011)_2 * 2^{2}$ 이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;s = 1&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;e = 2 + 127(bias) = 129 = &lt;code&gt;1000 0001&lt;/code&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;f = &lt;code&gt;101 1000 0000 0000 0000 0000&lt;/code&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;곧 이진수로 표현하면 &lt;code&gt;1100 0000 1101 1000 0000 0000 0000 0000&lt;/code&gt; 이고, 이를 16비트로 표현하면 &lt;code&gt;0xC0D80000&lt;/code&gt; 이다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;배정밀도의 표현&lt;/h3&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;2배 정밀도&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;64bit를 사용하는 2배정밀도 표현은 유효숫자가 더 크므로 단정밀도보다 더 정교하게 숫자 표현이 가능하다.&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;code&gt;float&lt;/code&gt;보다는 &lt;code&gt;double&lt;/code&gt;로 지정하는 것이 일반적이다. 부동 소수점 오차 없이 훨씬 정확하게 계산이 가능하기 때문이다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1024&quot; data-origin-height=&quot;266&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dCdmGq/btsCK7oMCly/vV99PcgjFWqHKAKVY1kVMk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dCdmGq/btsCK7oMCly/vV99PcgjFWqHKAKVY1kVMk/img.png&quot; data-alt=&quot;64비트 실수(Double)의 구조&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dCdmGq/btsCK7oMCly/vV99PcgjFWqHKAKVY1kVMk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdCdmGq%2FbtsCK7oMCly%2FvV99PcgjFWqHKAKVY1kVMk%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; alt=&quot;64비트 실수(Double)의 구조&quot; loading=&quot;lazy&quot; width=&quot;1024&quot; height=&quot;266&quot; data-origin-width=&quot;1024&quot; data-origin-height=&quot;266&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;64비트 실수(Double)의 구조&lt;/figcaption&gt;
&lt;/figure&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;2배정밀도에서는 진수부는 11비트, 가수부는 52비트를 사용하며, bias = 1023이다.&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;2배정밀도에서 실제 N값의 결과는 다음과 같다.&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;br /&gt;N = (-1)^s * 2^{e-1023} * (1.f)_2&lt;br /&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;한편 배정밀도까지는 하드웨어 레벨에서 계산이 가능하다. 하나의 레지스터는 4바이트 크기(단정밀도 32bit)를 저장할 수 있으므로, 배정밀도 계산 시 2개의 레지스터를 사용하여 계산한다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;4배 정밀도&lt;/h4&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1200&quot; data-origin-height=&quot;126&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/0UZXx/btsCQqtZhoL/9lzhgudKOGvuiBMTvGwGE0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/0UZXx/btsCQqtZhoL/9lzhgudKOGvuiBMTvGwGE0/img.png&quot; data-alt=&quot;4배정밀도 구조&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/0UZXx/btsCQqtZhoL/9lzhgudKOGvuiBMTvGwGE0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F0UZXx%2FbtsCQqtZhoL%2F9lzhgudKOGvuiBMTvGwGE0%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; alt=&quot;4배정밀도 구조&quot; loading=&quot;lazy&quot; width=&quot;1200&quot; height=&quot;126&quot; data-origin-width=&quot;1200&quot; data-origin-height=&quot;126&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;4배정밀도 구조&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;4배정밀도의 경우 128비트를 사용하며, 여기서부터는 대부분의 시스템에서 하드웨어 레벨보다는 소프트웨어 레벨에서 연산을 수행한다.&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;진수부는 15비트, 가수부는 112비트, bias는 16383을 사용한다.&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;br /&gt;N = (-1)^s * 2^{e-16383} * (1.f)_2&lt;br /&gt;$$&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;특수한 수의 표현과 수의 범위&lt;/h3&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;단정밀도&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;0(zero), 무한대, NAN(Not A Number)와 같은 특수한 경우를 표현하기 위해 몇 가지 규칙을 지정해 두었다.&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;이때 NAN은 실수 표현이 불가능한 경우로 $\sqrt{-1}$ 등을 의미한다.&lt;/p&gt;
&lt;table style=&quot;height: 106px;&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style7&quot;&gt;
&lt;thead&gt;
&lt;tr style=&quot;height: 20px;&quot;&gt;
&lt;th style=&quot;height: 20px;&quot;&gt;e&lt;/th&gt;
&lt;th style=&quot;height: 20px;&quot;&gt;f&lt;/th&gt;
&lt;th style=&quot;height: 20px;&quot;&gt;의미&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;height: 17px;&quot;&gt;255&lt;/td&gt;
&lt;td style=&quot;height: 17px;&quot;&gt;&amp;ne; 0&lt;/td&gt;
&lt;td style=&quot;height: 17px;&quot;&gt;NAN&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;height: 17px;&quot;&gt;255&lt;/td&gt;
&lt;td style=&quot;height: 17px;&quot;&gt;0&lt;/td&gt;
&lt;td style=&quot;height: 17px;&quot;&gt;+-inf&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 16px;&quot;&gt;
&lt;td style=&quot;height: 16px;&quot;&gt;0&lt;/td&gt;
&lt;td style=&quot;height: 16px;&quot;&gt;&amp;ne;0&lt;/td&gt;
&lt;td style=&quot;height: 16px;&quot;&gt;subnormal&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;height: 17px;&quot;&gt;0&lt;/td&gt;
&lt;td style=&quot;height: 17px;&quot;&gt;=0&lt;/td&gt;
&lt;td style=&quot;height: 17px;&quot;&gt;0 (zero)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 19px;&quot;&gt;
&lt;td style=&quot;height: 19px;&quot;&gt;0 &amp;lt; e &amp;lt; 255&lt;/td&gt;
&lt;td style=&quot;height: 19px;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;td style=&quot;height: 19px;&quot;&gt;비영의 값&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;e = 255, f = 0은 무한대($\infty$)로, s에 따라 $\pm\infty$로 나뉜다.&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;e = 0, f = 0은 말 그대로 정확한 0(zero)으로 표현된다.&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;그 외 0 &amp;lt; e &amp;lt; 255인 경우는 비영(non-zero)의 정규화된 수를 표현하게 된다.&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;단 $e=0, f\neq0$ 일 때는 Subnormal로 수를 표현한다.&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;/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;br /&gt;\text{0x 0080 0000} \le N \le \text{0x 7f7f ffff}&lt;br /&gt;$$&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;$$&lt;br /&gt;10 * 2 ^{-126} \le N \le 2.0 * 2^{127}&lt;br /&gt;$$&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;$$&lt;br /&gt;1.17549435 * 10 ^{-38} \le N \le 3.40282347 * 10 ^{38}&lt;br /&gt;$$&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;2배정밀도&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;두 배 정밀도에서는 e가 255가 아닌, 2047일 때 특수한 의미를 갖는다.&lt;/p&gt;
&lt;table data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style7&quot;&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;e&lt;/th&gt;
&lt;th&gt;f&lt;/th&gt;
&lt;th&gt;의미&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;2047&lt;/td&gt;
&lt;td&gt;&amp;ne; 0&lt;/td&gt;
&lt;td&gt;NAN&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2047&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;+-inf&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;&amp;ne;0&lt;/td&gt;
&lt;td&gt;subnormal&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;=0&lt;/td&gt;
&lt;td&gt;0 (zero)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;0 &amp;lt; e &amp;lt; 2047&lt;/td&gt;
&lt;td&gt;&amp;nbsp;&lt;/td&gt;
&lt;td&gt;비영의 값&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2배정밀도에서 수의 범위는&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;$$&lt;br /&gt;2.225073858507201 * 10 ^{-308} \le N \le 1.7976931348623157 * 10 ^ {308}&lt;br /&gt;$$&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;비정규화(Subnormal)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;글에서 중점으로 다루지는 않지만, 정규화된 수(Normalized Number) 외에 비정규화(Subnormal) 수 역시 IEEE 754에서 중요한 표준이다.&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;0.0에 매우 가까운 범위에서의 수를 표현하기 위해 사용된다.&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;위에서 수의 범위를 보면, 단정밀도를 기준으로 $0 \sim \pm 1.17549435 * 10 ^{-38}$ 사이 수치가 표현되지 않는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;0.0에 근접하는 더 작은 수들을 표현하기 위해 subnormal을 사용한다.&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;Subnormal을 사용 시 가장 작은 양수의 수치 표현은 &lt;code&gt;0x00000001&lt;/code&gt;이 되며, $1.40129846 * 10 ^{-45}$ 까지의 수 표현이 가능하다.&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;Subnormal 덕분에 &lt;b&gt;정규화된 수보다 더 촘촘하게 0에 가까운 수를 표현할 수 있다.&lt;/b&gt; Subnormal에서 N의 값은 아래와 같다.&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;br /&gt;N = (-1)^s * 2^{-126} * (0.f)_{2}&lt;br /&gt;$$&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러나 이 Subnormal의 존재 때문에 후술할 컴퓨터에서 &lt;b&gt;실수의 고르지 않게 분산된(Non-evenly Distributed) 문제를&lt;/b&gt; 야기하기도 한다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;  머신 앱실론과 실수의 분포&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;컴퓨터에서 실수를 다루는 것은 주의를 요한다. 실수의 연산 과정에서 생각지도 못한 결과, 치명적인 오류를 발생할 수 있다. 그렇기 때문에 실수의 연산을 알아보기 전에, 머신 앱실론(machine epsilon)과 실수의 분포를 알아야 한다.&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;불연속적(discontinuous)&lt;/b&gt;이고, &lt;b&gt;고르게 분포되어 있지 않다(non-evenly disritubted)&lt;/b&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;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;머신 엡실론과 실수의 불연속성&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;머신 엡실론(Machine Epsilon, $\varepsilon$)은 부동소수점 수의 표현 한계로 인해 나타나는 오차의 상한을 의미한다. 이 오차로 인해 수는 불연속적으로 분포되어 있으며, 이 차이를 머신 엡실론이라 한다.&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;머신 엡실론 $\varepsilon$은 다음과 같이 계산한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;$$&lt;br /&gt;\varepsilon = b^{-(p-1)}&lt;br /&gt;$$&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이때 b는 base(여기서는 2), p는 정밀도이다.&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;단정밀도 float은 p = 24로 $\varepsilon = 2^{-23}$, 배정밀도 double은 p = 53으로 $\varepsilon = 2^{-54}$ 이다.&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;컴퓨터에서 $\varepsilon$보다 작은 간격은 표현되지 않는다. $1$과 $1+\varepsilon$ 사이의 &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-ke-size=&quot;size16&quot;&gt;곧 컴퓨터는 어떤 두 실수의 차이가 $\varepsilon$보다 작으면, 두 수는 같다고 판단한다($|a - b| &amp;lt; \varepsilon \implies a = b$)&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;실수의 분포&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위에서 소개한 Subnormal의 존재와, 실수의 계산 표현 그 자체의 한계로, 실수는 불연속적으로 분포한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1654&quot; data-origin-height=&quot;884&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cYFgw8/btsCMrgcHrQ/c6xHw3weEHlWKAqNY1G4kK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cYFgw8/btsCMrgcHrQ/c6xHw3weEHlWKAqNY1G4kK/img.png&quot; data-alt=&quot;실수의 분포, 사진 출처: https://courses.engr.illinois.edu/cs357/fa2019/references/ref-1-fp/&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cYFgw8/btsCMrgcHrQ/c6xHw3weEHlWKAqNY1G4kK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcYFgw8%2FbtsCMrgcHrQ%2Fc6xHw3weEHlWKAqNY1G4kK%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; alt=&quot;실수의 분포&quot; loading=&quot;lazy&quot; width=&quot;1654&quot; height=&quot;884&quot; data-origin-width=&quot;1654&quot; data-origin-height=&quot;884&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;실수의 분포, 사진 출처: https://courses.engr.illinois.edu/cs357/fa2019/references/ref-1-fp/&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;0.0 부근에서 subnormal의 존재로 수는 더 촘촘히 분포한다.&lt;/span&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;0과 Subnormal이 표현할 수 있는 최소하한보다 작은 수 사이는 어떤 실수도 존재하지 않는 공간으로, 이 영역에 접근하려 하면 underflow가 발생한다.&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;한편 정규화된 경우 역시 부동소수점 표현 자체의 특성상, 수가 커질수록 그 오차 간격 역시 커진다. 때문에 일반적으로 실수는 0에 가까울수록 더 촘촘하게 분포한다.&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;표현 가능한 최대 상한을 벗어날 경우 overflow가 발생한다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;C언어로 머신 엡실론과 실수의 최대, 최소 구하기&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;못 믿겠다면 직접 구해보자. 누구나 자신의 컴퓨터에서 머신 엡실론과, 실수의 최대, 최솟값을 구할 수 있다.&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;#include &amp;lt;stdio.h&amp;gt;

void min_num() {
  float x = 1.0;
  for (;;) {
    printf(&quot;f %.30e\n&quot;, x / 2.0);
    x = x / 2.0;
    if (x == 0.0)
      break;
  }
}

void emach() {
  float emach = 1.0, emach2, tmp;

  for (;;) {
    emach2 = emach;
    emach /= (float)2.;
    tmp = (float)((float)1. + (float)emach);
    if (tmp == (float)1.0)
      break;
  }
  printf(&quot;single Emach = %.30e\n&quot;, emach2);
}

void max_num() {
  float x = 1.0;
  int i = 0;
  for (int i = 0; i &amp;lt; 130; i++) {
    printf(&quot;%.30e\n&quot;, x);
    fflush(stdout);
    x = x * 2.0;
  }
}

int main() {
  min_num();
  emach();
  max_num();

  return 0;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 코드는 각각 실수의 최소, 머신 엡실론, 최대를 구하는 코드이다.&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;f 2.802596928649634141847459166580e-45
f 1.401298464324817070923729583290e-45
f 7.006492321624085354618647916450e-46
single Emach = 1.192092895507812500000000000000e-07
(생략)
4.253529586511730793292182592897e+37
8.507059173023461586584365185794e+37
1.701411834604692317316873037159e+38
inf
inf
inf&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;단, 이 결과는 시스템 혹은 컴파일러에 의해서 크게 달라질 수 있다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;➕ 실수의 연산&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실수의 연산은 크게 덧셈과 뺄셈, 곱셈과 나눗셈으로 나뉜다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;덧셈과 뺄셈&lt;/h3&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;유효숫자를 정렬한다.&lt;/li&gt;
&lt;li&gt;유효숫자를 덧셈/뺄셈한다.&lt;/li&gt;
&lt;li&gt;결과를 정규화한다.&lt;/li&gt;
&lt;li&gt;(필요시) 반올림 후 다시 정규화한다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 단계를 토대로 아래 10진수 실수의 덧셈을 계산해 보자.&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;br /&gt;9.999 &amp;times; 10 ^{1} + 2.622 &amp;times; 10 ^{-1}&lt;br /&gt;$$&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;유효숫자 정렬: 지수가 작은 쪽 Operand를 Right Shift 한다.&lt;br /&gt;$$&lt;br /&gt;2.622 * 10^{-1} \rightarrow 0.026 * 10 ^{1}&lt;br /&gt;$$&lt;/li&gt;
&lt;li&gt;유효숫자 덧셈/뺄셈&lt;br /&gt;$$&lt;br /&gt;9.999 + 0.026 = 10.025&lt;br /&gt;$$&lt;/li&gt;
&lt;li&gt;결과 정규화 &amp;amp; 오버플로우/언더플로우 검사&lt;br /&gt;$$&lt;br /&gt;10.025 &amp;times; 10^{1} \rightarrow 1.0025 &amp;times; 10 ^{2}&lt;br /&gt;$$&lt;/li&gt;
&lt;li&gt;(필요시) 반올림 후 재정규화&lt;br /&gt;$$&lt;br /&gt;1.0025 &amp;times; 10 ^{2} \rightarrow 1.003 &amp;times; 10 ^2&lt;br /&gt;$$&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;p data-ke-size=&quot;size16&quot;&gt;이제 2진수 실수의 덧셈을 진행해 보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;$$&lt;br /&gt;-1.110_2 &amp;times; 2^{-2} + {1.000}_{2} &amp;times; 2^{-1}&lt;br /&gt;$$&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;유효숫자 정렬: 지수가 작은 쪽 Operand를 Right Shift 한다.&lt;br /&gt;$$&lt;br /&gt;&amp;minus;1.110_2 &amp;times; 2^{&amp;minus;2} \rightarrow &amp;minus;0.111_2 &amp;times; 2^{&amp;minus;1}&lt;br /&gt;$$&lt;/li&gt;
&lt;li&gt;유효숫자 덧셈/뺄셈&lt;br /&gt;$$&lt;br /&gt;&amp;minus;0.111_2 + 1.000_2 &amp;rarr; 0.001_2&lt;br /&gt;$$&lt;/li&gt;
&lt;li&gt;결과 정규화 &amp;amp; 오버플로우/언더플로우 검사&lt;br /&gt;$$&lt;br /&gt;0.001_2 &amp;times; 2^{&amp;minus;1} \rightarrow 1.000 &amp;times; 2^{&amp;minus;4}&lt;br /&gt;$$&lt;/li&gt;
&lt;li&gt;(필요시) 반올림 후 재정규화&lt;br /&gt;$$&lt;br /&gt;1.000 &amp;times; 2^{-4}&lt;br /&gt;$$&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;곱셈과 나눗셈&lt;/h3&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;지수를 덧셈/뺄셈한다.&lt;/li&gt;
&lt;li&gt;유효숫자를 곱셈/나눗셈한다.&lt;/li&gt;
&lt;li&gt;결과를 정규화한다.&lt;/li&gt;
&lt;li&gt;반올림한다.&lt;/li&gt;
&lt;li&gt;Sign 연산으로 부호를 결정한다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 단계를 토대로 아래 10진수를 곱셈해 보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;$$&lt;br /&gt;&amp;minus;1.110 &amp;times; 10^{10} &amp;times; 9.200 &amp;times; 10^{&amp;minus;5}&lt;br /&gt;$$&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;지수 덧셈/뺄셈&lt;br /&gt;$$&lt;br /&gt;10 + (-5)= 5&lt;br /&gt;$$&lt;/li&gt;
&lt;li&gt;유효숫자 곱셈/나눗셈&lt;br /&gt;$$&lt;br /&gt;1.110 &amp;times; 9.200 = 10.212 \rightarrow 10.212 &amp;times; 10^5&lt;br /&gt;$$&lt;/li&gt;
&lt;li&gt;결과 정규화 &amp;amp; 오버플로우/언더플로우 검사&lt;br /&gt;$$&lt;br /&gt;10.212 &amp;times; 10^5 \rightarrow 1.0212 &amp;times; 10^6&lt;br /&gt;$$&lt;/li&gt;
&lt;li&gt;반올림 후 재정규화&lt;br /&gt;$$&lt;br /&gt;1.0212 &amp;times; 10^6 \rightarrow 1.021 &amp;times; 10^6&lt;br /&gt;$$&lt;/li&gt;
&lt;li&gt;부호 결정&lt;br /&gt;$$&lt;br /&gt;+1.021 &amp;times; 10^6&lt;br /&gt;$$&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;p data-ke-size=&quot;size16&quot;&gt;이제 2진수의 곱셈을 계산해 보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;$$&lt;br /&gt;&amp;minus;1.110_2 &amp;times; 2^{&amp;minus;2} &amp;times; 1.000_2 &amp;times; 2^{&amp;minus;1}&lt;br /&gt;$$&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;지수 덧셈/뺄셈&lt;br /&gt;$$&lt;br /&gt;-2+(-1)=-3&lt;br /&gt;$$&lt;/li&gt;
&lt;li&gt;유효숫자 곱셈/나눗셈&lt;br /&gt;$$&lt;br /&gt;1.110_2 &amp;times; 1.000_2 = 1.110_2 \rightarrow 1.110_2 &amp;times; 10^{&amp;minus;3}&lt;br /&gt;$$&lt;/li&gt;
&lt;li&gt;결과 정규화 &amp;amp; 오버플로우/언더플로우 검사&lt;br /&gt;$$&lt;br /&gt;1.110_2 &amp;times; 10^{&amp;minus;3}&lt;br /&gt;$$&lt;/li&gt;
&lt;li&gt;반올림 후 재정규화&lt;br /&gt;$$&lt;br /&gt;1.110_2 &amp;times; 10^{&amp;minus;3}&lt;br /&gt;$$&lt;/li&gt;
&lt;li&gt;부호 결정&lt;br /&gt;$$&lt;br /&gt;-1.110_2 &amp;times; 10^{&amp;minus;3}&lt;br /&gt;$$&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;  FPU&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;FPU의 개념&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;330&quot; data-origin-height=&quot;245&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bkbjLe/btsCQsehFNZ/Kn3ZRimykEe3w7tMyCkVJK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bkbjLe/btsCQsehFNZ/Kn3ZRimykEe3w7tMyCkVJK/img.png&quot; data-alt=&quot;FPU와 CPU, 사진 출처: 위키피디아 FPU&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bkbjLe/btsCQsehFNZ/Kn3ZRimykEe3w7tMyCkVJK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbkbjLe%2FbtsCQsehFNZ%2FKn3ZRimykEe3w7tMyCkVJK%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; alt=&quot;FPU와 CPU&quot; loading=&quot;lazy&quot; width=&quot;330&quot; height=&quot;245&quot; data-origin-width=&quot;330&quot; data-origin-height=&quot;245&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;FPU와 CPU, 사진 출처: 위키피디아 FPU&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1777&quot; data-origin-height=&quot;1000&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/1utBe/btsCQp2Zyhi/30A8pxbRKRZH3Nzk1OzsKK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/1utBe/btsCQp2Zyhi/30A8pxbRKRZH3Nzk1OzsKK/img.png&quot; data-alt=&quot;FPU와 CPU의 연결&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/1utBe/btsCQp2Zyhi/30A8pxbRKRZH3Nzk1OzsKK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F1utBe%2FbtsCQp2Zyhi%2F30A8pxbRKRZH3Nzk1OzsKK%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; alt=&quot;FPU와 CPU&quot; loading=&quot;lazy&quot; width=&quot;1777&quot; height=&quot;1000&quot; data-origin-width=&quot;1777&quot; data-origin-height=&quot;1000&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;FPU와 CPU의 연결&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실수의 연산은 정수의 연산보다 훨씬 복잡하다. 지수부, 가수부를 별도로 계산해야 하기 때문이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이를 소프트웨어적으로 처리할 수도 있지만, FPU(Floating Point Unit)이라는 하드웨어를 통해서 하드웨어로 연산을 대신할 수도 있다.&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;FPU가 있는 시스템의 경우 부동소수점 연산을 매우 빠르게 수행할 수 있어, 대규모 병렬 연산 및 슈퍼 컴퓨팅에 강점이 있다.&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;만약 FPU가 없는 시스템의 경우 정수 연산을 위한 ALU(Arithmetic and Logic Unit)에서 실수 연산을 처리한다. 다만 이 경우 FPU를 사용하는 것보다 느리다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;현재의 FPU&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;원래 초창기 x86 컴퓨터 시스템에서는 CPU와 분리된 별도의 FPU를 갖고 있다. 이때는 ALU의 성능이 워낙 낮았기 때문에 부동소수점 역시 ALU에서 처리하기에는 부담이 있었기 때문이다.&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;하지만 CPU 외부에 존재하는 FPU의 경우, CPU의 레지스터에서 FPU의 레지스터로 데이터가 이동하는 I/O 딜레이가 존재한다는 단점이 있다.&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;더군다나 Sparc 시스템 기준으로 &lt;u&gt;CPU의 레지스터에서 FPU의 레지스터로 직접 전달하는 방법 또한 없다.&lt;/u&gt; 무조건 Memory를 거쳐야 한다. CPU의 레지스터에서 FPU의 레지스터까지 CPU &amp;rarr; MEM &amp;rarr; FPU 순서를 거쳐야 하고, 이 과정에서 매번 RAM에 적재/저장(Load/Store)이라는 비싼 연산을 거쳐야 한다. 이 모든 것이 I/O 딜레이를 유발한다.&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;초기에는 ALU의 성능이 낮아서 I/O 성능이 크게 병목 현상을 일으키지는 않았지만, 점차 ALU의 성능이 발전하면서 결국 I/O 딜레이가 치명적인 병목 현상으로 지적되었다.&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;Intel 486을 기점으로 FPU는 CPU로 통합되게 된다.&lt;/b&gt; 486 때만 해도 Single Core 시절이었다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;3237&quot; data-origin-height=&quot;1000&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bTzjmF/btsCU08M69n/n8CSTb24uvQJGn1Syw9Of1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bTzjmF/btsCU08M69n/n8CSTb24uvQJGn1Syw9Of1/img.png&quot; data-alt=&quot;인텔의 듀얼 코어 CPU와 쿼드 코어 CPU의 구조. FPU와 ALU가 같이 섞여 있다.&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bTzjmF/btsCU08M69n/n8CSTb24uvQJGn1Syw9Of1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbTzjmF%2FbtsCU08M69n%2Fn8CSTb24uvQJGn1Syw9Of1%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; alt=&quot;인텔의 듀얼 코어 CPU와 쿼드 코어 CPU의 구조&quot; loading=&quot;lazy&quot; width=&quot;3237&quot; height=&quot;1000&quot; data-origin-width=&quot;3237&quot; data-origin-height=&quot;1000&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;인텔의 듀얼 코어 CPU와 쿼드 코어 CPU의 구조. FPU와 ALU가 같이 섞여 있다.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;FPU의 형태를 정리하면 아래와 같다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;소프트웨어 처리(FPU Emulator): 부동소수점 라이브러리가 ISA 레벨에서 제공됨&lt;/li&gt;
&lt;li&gt;별도의 FPU 하드웨어&lt;/li&gt;
&lt;li&gt;CPU에 통합된 형태&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 이제는 CPU에 통합되었거나 소프트웨어적으로 처리하여, FPU 하드웨어는 역사의 뒤안길로&amp;hellip; 사라지게 되었다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1690&quot; data-origin-height=&quot;920&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/MJHwF/btsCSAJCVGz/OVe65au5kFkeJDgH4tZAW0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/MJHwF/btsCSAJCVGz/OVe65au5kFkeJDgH4tZAW0/img.png&quot; data-alt=&quot;사진 출처: https://www.production-expert.com/production-expert-1/why-are-the-apple-m1-m1-pro-and-m1-max-chips-so-fast&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/MJHwF/btsCSAJCVGz/OVe65au5kFkeJDgH4tZAW0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FMJHwF%2FbtsCSAJCVGz%2FOVe65au5kFkeJDgH4tZAW0%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; alt=&quot;애플 m1&quot; loading=&quot;lazy&quot; width=&quot;1690&quot; height=&quot;920&quot; data-origin-width=&quot;1690&quot; data-origin-height=&quot;920&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;사진 출처: https://www.production-expert.com/production-expert-1/why-are-the-apple-m1-m1-pro-and-m1-max-chips-so-fast&lt;/figcaption&gt;
&lt;/figure&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;사실 FPU만의 운명은 아닌 게, 요새 트렌드 자체가 한 칩에 모든 HW를 통합하는 SoC 구조가 유행이다.&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;최근에 나온 Apple Silicon을 생각해 보자. 채널 사이의 딜레이를 줄이기 위해, DRAM, CPU, GPU 등을 SoC 구조로 한 칩에다가 전부 때려 박는다. AMD Ryzen도 마찬가지고, 많은 시스템에서 점차 채널 사이 딜레이를 줄이려고 노력한다.&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;물론 인공지능에 사용되는 Cuda 연산 등에서는 매우 높은 GPU/TPU 연산 성능이 필요하기에, 아직까지는 별도의 VGA를 두는 게 일반적이기는 하지만&amp;hellip; ARM 아키텍처나 그 외 모바일 프로세서 등에 사용되는 퀄컴 프로세서도 SoC를 많이 채택한다.&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;언젠가는 GPU도 완전히 CPU에 통합되게 되는 날이 올까? 지금은 AI 때문에 워낙 GPU 사용률이 높아서, 근미래에는 오지 않을 것 같다. 하지만 먼 미래에는 또 모를지도?&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;FPU를 이용한 연산의 구현(Sparc)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 글은 Sparc ISA를 기준으로 작성되었다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;FPU 레지스터 및 명령어 알아보기&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Sparc에서 FPU의 레지스터는 &lt;code&gt;%f0&lt;/code&gt;부터 &lt;code&gt;%f31&lt;/code&gt;까지 32개가 있고, 1개의 레지스터가 32비트를 저장할 수 있다.&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;단정밀도 부동소수점의 경우 하나의 레지스터를 사용하면 되고, 배정밀도 부동소수점(double)의 경우는 두 개를 묶어서 사용한다. 곧 &lt;code&gt;%f4&lt;/code&gt;에 double을 저장할 경우 실제로는 &lt;code&gt;%f4%f5&lt;/code&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;FPU에서 제공하는 대표적인 실수 연산은 덧셈, 뺄셈, 곱셈, 나눗셈, 제곱근 등이 있다. 이를 명령어로 표현하면 &lt;code&gt;fadds&lt;/code&gt;, &lt;code&gt;fsubs&lt;/code&gt;, &lt;code&gt;fmuls&lt;/code&gt;, &lt;code&gt;fdivs&lt;/code&gt;, &lt;code&gt;fsqrts&lt;/code&gt; 이다. 이때 맨 뒤에 붙는 &amp;lsquo;s&amp;rsquo;는 단정밀도를 의미하는 single의 s이다. 만약 double을 사용해야 한다면, 맨 뒤에 s 대신 d를 붙이면 된다. &lt;code&gt;faddd&lt;/code&gt;, &lt;code&gt;fsubd&lt;/code&gt;, &lt;code&gt;fmuld&lt;/code&gt;, &lt;code&gt;fdivd&lt;/code&gt;, &lt;code&gt;fsqrtd&lt;/code&gt; 이런 식으로 말이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;&lt;i&gt;✅ TIP: 구형 Sparc 시스템에서는 정수의 곱셈, 나눗셈 등의 연산이 명령어로 제공되지 않고 함수로 제공되기도 했었다. 그에 반해 FPU에서는 곱셈, 나눗셈, 제곱근 등의 연산이 모두 명령어로 제공된다.&lt;/i&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;double이 온다면 f 레지스터는 반드시 0 또는 짝수가 와야 한다. 즉 &lt;code&gt;faddd %f0, %f2, %f4&lt;/code&gt;는 올바른 명령어지만 &lt;code&gt;faddd %f0, %f1, %f4&lt;/code&gt;는 올바른 명령어가 아니다. &lt;code&gt;%f0%f1&lt;/code&gt; 이 실제로는 하나의 double을 저장하고 있기 때문이다.&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;한편 move 연산, 부호변환, 절댓값은 각각 &lt;code&gt;fmovs&lt;/code&gt;, &lt;code&gt;fnegs&lt;/code&gt;, &lt;code&gt;fabss&lt;/code&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;데이터 타입의 변환 역시 필요하다. float에서 int로의 변환(single to int)는 &lt;code&gt;fstoi&lt;/code&gt;, int에서 float으로의 변환(int to single)은 &lt;code&gt;fitos&lt;/code&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;표로 정리하면 아래와 같다. double은 맨 뒤에 s를 d로 바꾼다.&lt;/p&gt;
&lt;table style=&quot;height: 225px;&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style6&quot;&gt;
&lt;thead&gt;
&lt;tr style=&quot;height: 19px;&quot;&gt;
&lt;th style=&quot;height: 19px;&quot;&gt;&amp;nbsp;&lt;/th&gt;
&lt;th style=&quot;height: 19px;&quot;&gt;&amp;nbsp;&lt;/th&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 20px;&quot;&gt;
&lt;th style=&quot;height: 20px;&quot;&gt;명령어&lt;/th&gt;
&lt;th style=&quot;height: 20px;&quot;&gt;동작&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr style=&quot;height: 19px;&quot;&gt;
&lt;td style=&quot;height: 19px;&quot;&gt;fadds fr1, fr2, frd&lt;/td&gt;
&lt;td style=&quot;height: 19px;&quot;&gt;덧셈&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 19px;&quot;&gt;
&lt;td style=&quot;height: 19px;&quot;&gt;fsubs fr1, fr2, frd&lt;/td&gt;
&lt;td style=&quot;height: 19px;&quot;&gt;뺄셈&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 19px;&quot;&gt;
&lt;td style=&quot;height: 19px;&quot;&gt;fmuls fr1, fr2, frd&lt;/td&gt;
&lt;td style=&quot;height: 19px;&quot;&gt;곱셈&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 19px;&quot;&gt;
&lt;td style=&quot;height: 19px;&quot;&gt;fdivs fr1, fr2, frd&lt;/td&gt;
&lt;td style=&quot;height: 19px;&quot;&gt;나눗셈&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 19px;&quot;&gt;
&lt;td style=&quot;height: 19px;&quot;&gt;fsqrts fr1, fr2, frd&lt;/td&gt;
&lt;td style=&quot;height: 19px;&quot;&gt;제곱근&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 19px;&quot;&gt;
&lt;td style=&quot;height: 19px;&quot;&gt;fmovs fr1, frd&lt;/td&gt;
&lt;td style=&quot;height: 19px;&quot;&gt;레지스터 복사&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 19px;&quot;&gt;
&lt;td style=&quot;height: 19px;&quot;&gt;fnegs fr1, frd&lt;/td&gt;
&lt;td style=&quot;height: 19px;&quot;&gt;부호변환&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 19px;&quot;&gt;
&lt;td style=&quot;height: 19px;&quot;&gt;fabss fr1, frd&lt;/td&gt;
&lt;td style=&quot;height: 19px;&quot;&gt;절대값&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;height: 17px;&quot;&gt;fitos fr1, frd&lt;/td&gt;
&lt;td style=&quot;height: 17px;&quot;&gt;int to single(float)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;height: 17px;&quot;&gt;fstoi fr1, frd&lt;/td&gt;
&lt;td style=&quot;height: 17px;&quot;&gt;single(float) to int&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;FPU의 비교 명령어와 분기 명령어&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 분기 시에 사용되는 FPU 비교 명령어를 알아보자. FPU에서 생성되는 조건 코드(Condition Code, 이하 CC)를 FCC라 한다. FCC는 E, L, G, U가 존재한다.&lt;/p&gt;
&lt;table data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style4&quot;&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;fcc&lt;/th&gt;
&lt;th&gt;기호&lt;/th&gt;
&lt;th&gt;의미&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;E&lt;/td&gt;
&lt;td&gt;fr1 = fr2&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;L&lt;/td&gt;
&lt;td&gt;fr1 &amp;lt; fr2&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;G&lt;/td&gt;
&lt;td&gt;fr1 &amp;gt; fr2&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;U&lt;/td&gt;
&lt;td&gt;fr1 ? fr2&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Equal, Less, Greater를 의미하는 앞에 세 개는 이해되지만, U가 생소할 수 있다. U는 Unordered의 의미로써, Operand인 fr1, fr2 두 개 중 적어도 하나는 유효하지 않은 실수(NaN)임을 의미한다. 즉 오류의 의미를 담는다.&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;code&gt;fcmps fr1, fr2&lt;/code&gt;와 같은 명령어는, 위 E, L, G, U 네 가지 CC를 생성한다. 예를 들어&lt;/p&gt;
&lt;pre class=&quot;applescript&quot;&gt;&lt;code&gt;fcmps   %f1, %f2
fbe     equal ! equal은 브랜치 이름
nop&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;처럼 사용할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;FPU의 분기 명령어와 사용하는 CC는 아래 표로 정리하였다.&lt;/p&gt;
&lt;table data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style5&quot;&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;명령어&lt;/th&gt;
&lt;th&gt;의미&lt;/th&gt;
&lt;th&gt;조건코드&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;fba&lt;/td&gt;
&lt;td&gt;always&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;fbn&lt;/td&gt;
&lt;td&gt;never&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;fbo&lt;/td&gt;
&lt;td&gt;on ordered&lt;/td&gt;
&lt;td&gt;E or L or G&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;fbu&lt;/td&gt;
&lt;td&gt;on unordered&lt;/td&gt;
&lt;td&gt;U&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;fbul&lt;/td&gt;
&lt;td&gt;on unordered or less&lt;/td&gt;
&lt;td&gt;L or U&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;fbl&lt;/td&gt;
&lt;td&gt;on less&lt;/td&gt;
&lt;td&gt;L&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;fbule&lt;/td&gt;
&lt;td&gt;on unordered or less or equal&lt;/td&gt;
&lt;td&gt;E or L or U&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;fble&lt;/td&gt;
&lt;td&gt;on less or equal&lt;/td&gt;
&lt;td&gt;E or L&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;fbue&lt;/td&gt;
&lt;td&gt;on unordered or equal&lt;/td&gt;
&lt;td&gt;E or U&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;fbe&lt;/td&gt;
&lt;td&gt;on equal&lt;/td&gt;
&lt;td&gt;E&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;fbne&lt;/td&gt;
&lt;td&gt;on not equal&lt;/td&gt;
&lt;td&gt;L or G or U&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;fblg&lt;/td&gt;
&lt;td&gt;on less or greater&lt;/td&gt;
&lt;td&gt;L or G&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;fbuge&lt;/td&gt;
&lt;td&gt;on unordered or greater or equal&lt;/td&gt;
&lt;td&gt;E or G or U&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;fbge&lt;/td&gt;
&lt;td&gt;on greater or equal&lt;/td&gt;
&lt;td&gt;E or G&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;fbug&lt;/td&gt;
&lt;td&gt;on unordered or greater&lt;/td&gt;
&lt;td&gt;G or U&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;fbg&lt;/td&gt;
&lt;td&gt;on greater&lt;/td&gt;
&lt;td&gt;G&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;대부분 less, greater, equal, unordered의 앞 글자를 그대로 조합해서 명령어를 구성하기에 매우 직관적이다.&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;code&gt;fblg&lt;/code&gt;와 &lt;code&gt;fbne&lt;/code&gt;가 무엇이 다른 지 헷갈릴 수 있다. 둘 다 not equal을 체크하는 건 동일한데, &lt;code&gt;fblg&lt;/code&gt;는 L, G만 체크하고 &lt;code&gt;fbne&lt;/code&gt;는 L, G에 더해 U(NaN)도 체크한다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;CPU 레지스터에서 FPU 레지스터로의 이동&lt;/h4&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1553&quot; data-origin-height=&quot;1000&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bSVxH8/btsCRiP0aup/qqp3WqABFK3rVvN7nDDaO0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bSVxH8/btsCRiP0aup/qqp3WqABFK3rVvN7nDDaO0/img.png&quot; data-alt=&quot;CPU에서 MEM, FPU로 레지스터를 복사하는 과정&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bSVxH8/btsCRiP0aup/qqp3WqABFK3rVvN7nDDaO0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbSVxH8%2FbtsCRiP0aup%2Fqqp3WqABFK3rVvN7nDDaO0%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; alt=&quot;CPU에서 MEM&amp;amp;#44; FPU로 레지스터를 복사하는 과정&quot; loading=&quot;lazy&quot; width=&quot;1553&quot; height=&quot;1000&quot; data-origin-width=&quot;1553&quot; data-origin-height=&quot;1000&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;CPU에서 MEM, FPU로 레지스터를 복사하는 과정&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;선술했듯이, Sparc 시스템에서는 CPU의 레지스터에서 FPU의 레지스터로 직접 전달하는 방법은 없다. &lt;code&gt;%l0&lt;/code&gt;에 있는 값을 &lt;code&gt;%f0&lt;/code&gt;로 옮기기 위해 &lt;code&gt;mov %l0, %f0&lt;/code&gt;와 같은 순진한 방법을 생각했다면, &lt;s&gt;아직 어셈블리어의 쓴 맛을 깨닫지 못한 상태다.&lt;/s&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;CPU 레지스터의 값을 FPU 레지스터로 복사하기 위해서는 반드시 Memory 영역을 거쳐야 한다.&lt;/p&gt;
&lt;pre class=&quot;llvm&quot;&gt;&lt;code&gt;.global main
main:   
    save %sp, -96, %sp

    mov 3, %o0      ! %o0: 0x00000003
    st %o0, [%fp-4] ! %o0에 있는 값을 메모리에 저장
    ld [%fp-4], %f1 ! %f1: 0x00000003
    fitos %f1, %f2  ! %f2: 0x40400000&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 코드를 살펴보자. &lt;code&gt;mov 3, %o0&lt;/code&gt;로 o0 레지스터에 3을 저장한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;%o0&lt;/code&gt; 레지스터에 있는 값을 메모리에 저장한다(&lt;code&gt;st %o0, [%fp - 4]&lt;/code&gt;).&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;메모리에 저장된 값을 &lt;code&gt;%f1&lt;/code&gt;으로 적재한다(&lt;code&gt;ld [%fp - 4], %f1&lt;/code&gt;).&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이때 저장된 3이라는 값은 정수형이므로, 이를 &lt;code&gt;fitos&lt;/code&gt;를 이용해서 실수형으로 변경한다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;FPU에서 double 연산&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 FPU에서 double을 연산해보자. 2배정밀도 double은 레지스터를 두 개를 사용한다. 만약 &lt;code&gt;%f0&lt;/code&gt;에 값을 저장했으면, 실제로는 &lt;code&gt;%f0%f1&lt;/code&gt; 두 개의 레지스터를 사용한다.&lt;/p&gt;
&lt;pre class=&quot;cpp&quot;&gt;&lt;code&gt;#include &amp;lt;stdio.h&amp;gt;

void main() {
    register double a = 1.5;
    register double b = 2.0;
    register double c = 0.0;

    c = a + b;

    printf(&quot;a+b=%f\n&quot;, c);
    return;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 C언어 코드를 Sparc로 구현하면, 아래와 같이 구현할 수 있다.&lt;/p&gt;
&lt;pre class=&quot;bash&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;            .data
a:      .double 0r1.5
b:      .double 0r2.0
c:      .double 0r0.0
str:    .asciz &amp;ldquo;a+b=%f\n&amp;rdquo;
    .text
    .global main
main: 
    save    %sp, -96, %sp
    set     a, %l0
    set     b, %l1
    set     c, %l2

    ldd     [%l0], %f0 ! stores a&amp;rsquo;s value, 1.5 to %f0%f1
    ldd     [%l1], %f2 ! stores b&amp;rsquo;s value 2.0 to %f2%f3

    faddd   %f0, %f2, %f4 ! %f4%f5=%f0%f1+%f2%f3

    std     %f4, [%l2]
    ldd     [%l2], %o2 ! load c&amp;rsquo;s value to %o2%o3, %o2 an even numbered register
    set     str, %o0
    mov     %o2, %o1 ! printf takes a real number argument as a double precision number
    call    printf
    mov     %o3, %o2 ! %o2%o3 -&amp;gt; %o1%o2

    ret
    restore&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;std&lt;/code&gt;, &lt;code&gt;ldd&lt;/code&gt;, &lt;code&gt;faddd&lt;/code&gt; 처럼 명령어의 뒤에 &amp;lsquo;d&amp;rsquo;가 붙음을 주의하라. 이러면 알아서 두 개의 레지스터를 사용하여 double의 계산을 처리한다.&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;code&gt;ldd [%l0], %f0&lt;/code&gt;의 경우 실제로는 &lt;code&gt;%f0%f1&lt;/code&gt; 두 레지스터에 a = 1.5 값이 저장된다.&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;그렇기 때문에 다음 b값을 저장하기 위해서는 &lt;code&gt;ldd [%l1], %f2&lt;/code&gt; 로 저장해야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이는 FPU의 레지스터에서 CPU의 레지스터로 이동 시에도 마찬가지이다.&lt;/p&gt;
&lt;pre class=&quot;gcode&quot;&gt;&lt;code&gt;std     %f4, [%l2]
ldd     [%l2], %o2 ! 실제로는 %o2, %o3 레지스터에 저장됨&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;한편 printf 함수를 호출할 때에는, &lt;code&gt;%o2%o3&lt;/code&gt;의 레지스터를 &lt;code&gt;%o1%o2&lt;/code&gt;의 레지스터로 옮기는 작업을 하였다. &lt;code&gt;%o0&lt;/code&gt;는 &lt;code&gt;&quot;a+b=%f&quot;&lt;/code&gt;라는 첫 번째 문자열 인자값이다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;함수에서 실수 주고 받기&lt;/h4&gt;
&lt;p 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;만약 일반적인 정수형 인자를 주고 받는다면, 레지스터 윈도우를 사용하는 Sparc 시스템의 경우 &lt;code&gt;%o&lt;/code&gt; 레지스터에서 &lt;code&gt;%i&lt;/code&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;code&gt;%o&lt;/code&gt;에서 &lt;code&gt;%i&lt;/code&gt;로 인자를 넘긴다. FPU 레지스터로 옮기기 위해 메모리를 경유하는 과정이 추가될 뿐이다.&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;한편 반환값을 받을 때는 어떤 FPU 레지스터를 사용해도 상관은 없으나, 일반적으로 &lt;code&gt;%f0&lt;/code&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;정수형 레지스터와 달리 FPU 레지스터는 별도의 레지스터 윈도우가 없다. 그래서 FPU의 레지스터를 사용하기 전에, 기존에 있는 값을 어딘가에 저장해두었다가 사용 후 다시 복구시키는 습관은 유용하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;어딘선가 기존에 있는 값을 이용하고 있을 수도 있기 때문이다. 단, OS의 강제사항은 아니고, 권장되는 규칙이다.&lt;/p&gt;
&lt;pre class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot;&gt;&lt;code&gt;void main() {
  static float a = 3.0, b = 2.5, c = 0.0;
  c = single_add(a, b);
}

float single_add(float a, float b) { 
    return a + b; 
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 C언어 코드를 구현해보자.&lt;/p&gt;
&lt;pre class=&quot;llvm&quot;&gt;&lt;code&gt;    .data
a:  .single 0r3.0 ! 0x40400000
b:  .single 0r2.5 ! 0x40200000
c:  .single 0r0.0
    .text
    .global main
main: 
    save %sp, -96, %sp

    set a, %l0
    set b, %l1
    ld  [%l0], %o0 ! argument 1 (0x40400000)
    ld  [%l1], %o1 ! argument 2 (0x40200000)

    st  %f0, [%fp-4] ! 기존에 있는 %f0를 메모리에 저장

    call single_add
    nop

    set c, %l2
    st  %f0, [%l2] ! %f0에 저장된 return 값을 c에 저장

    ld  [%fp-4], %f0 ! 아까 전에 메모리에 저장한 %f0 값을 복구

    mov 1, %g1
    ta  0       ! 프로그램 종료

single_add:
    save    %sp, -96, %sp
    st      %i0,[%fp-4]     ! i 레지스터의 값을 메모리 저장 후 %f 레지스터로 적재
    ld      [%fp-4], %f1    ! %i0 &amp;rarr; [%fp-4] &amp;rarr; %f1
    st      %i1,[%fp-4]
    ld      [%fp-4], %f2    ! %i1 &amp;rarr; [%fp-4] &amp;rarr; %f2

    fadds   %f1, %f2, %f0   ! 덧셈 결과를 %f0에 저장

    ret
    restore&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;두 가지 포인트를 볼 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;먼저 &lt;code&gt;st %f0, [%fp - 4]&lt;/code&gt;로 기존에 저장된 &lt;code&gt;%f0&lt;/code&gt; 레지스터 값을 메모리에 저장한다. 이후 모든 계산이 끝난 후에 &lt;code&gt;ld [%fp - 4], %f0&lt;/code&gt;로 &lt;code&gt;%f0&lt;/code&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;single_add 서브루틴을 살펴보자. 여기서도 마찬가지로 &lt;code&gt;%i0&lt;/code&gt;, &lt;code&gt;%i1&lt;/code&gt; 등에 들어온 인자값을 메모리를 거쳐서 &lt;code&gt;%f1&lt;/code&gt;, &lt;code&gt;%f2&lt;/code&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;메모리에 저장, FPU에 적재 과정만 추가되었을 뿐 정수형 인자를 주고 받는 것과 크게 다르지 않다.&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;code&gt;fadds %f1, %f2, %f0&lt;/code&gt;로 &lt;code&gt;%f0&lt;/code&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;만약 double이라면, &lt;code&gt;%o0&lt;/code&gt;에 넘긴 인자는 실제로는 &lt;code&gt;%i0, %i1&lt;/code&gt; 두 개의 레지스터를 사용한다는 것을 기억하자.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;  참고 문헌&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 글은 가볍게 소개만 하는 수준으로 오류가 있을 수 있기에, 참고만 하기를 바란다. 더 정확하고 자세한 내용을 알아보려면 참고 문헌을 활용하길 권장한다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;E. Ward Cheney and David R. Kincaid. 2007. &lt;i&gt;Numerical Mathematics and Computing (6th. ed.).&lt;/i&gt; Brooks/Cole Publishing Co., USA.&lt;/li&gt;
&lt;li&gt;박도순, 표창우. 2022. &lt;i&gt;SPARC 프로세서 어셈블리 언어&lt;/i&gt;. 대한민국: 시큐어 코딩 테크놀로지.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://courses.engr.illinois.edu/cs357/fa2019/references/ref-1-fp/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Floating Point Representation&lt;/a&gt;, &lt;i&gt;courses.engr.illinois.edu&lt;/i&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://en.wikipedia.org/wiki/Floating-point_unit&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Floating-point unit&lt;/a&gt;, &lt;i&gt;WIKIPEDIA&lt;/i&gt;&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>Computer Science</category>
      <category>fpu</category>
      <category>sparc</category>
      <category>부동소수점 계산</category>
      <author>nx006</author>
      <guid isPermaLink="true">https://nx006.tistory.com/75</guid>
      <comments>https://nx006.tistory.com/75#entry75comment</comments>
      <pubDate>Sat, 30 Dec 2023 16:42:01 +0900</pubDate>
    </item>
    <item>
      <title>Sealed Class로 상태 패턴 사용하기</title>
      <link>https://nx006.tistory.com/74</link>
      <description>&lt;h1&gt;Sealed Class로 상태 패턴 사용하기&lt;/h1&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Sealed Class란?&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Dart 3.0 버전부터 &lt;code&gt;sealed&lt;/code&gt;란 키워드가 class modifier로 새롭게 추가되었다. &lt;code&gt;sealed&lt;/code&gt; 클래스는 enum의 확장판으로, class를 enum처럼 사용할 수 있게 해준다.&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;code&gt;sealed&lt;/code&gt; 클래스의 용법을 찾아보면 다음과 같다.&lt;/p&gt;
&lt;pre class=&quot;scala&quot;&gt;&lt;code&gt;sealed class Vehicle {}

class Car extends Vehicle {}

class Truck implements Vehicle {}

class Bicycle extends Vehicle {}

// ERROR: Cannot be instantiated
Vehicle myVehicle = Vehicle();

// Subclasses can be instantiated
Vehicle myCar = Car();

String getVehicleSound(Vehicle vehicle) {
  // ERROR: The switch is missing the Bicycle subtype or a default case.
  return switch (vehicle) {
    Car() =&amp;gt; 'vroom',
    Truck() =&amp;gt; 'VROOOOMM',
  };
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 예제는 &lt;code&gt;sealed&lt;/code&gt; 클래스에 대해서 많은 내용을 담고 있다. &lt;code&gt;sealed&lt;/code&gt; 클래스는, 기본적으로 &lt;code&gt;abstract&lt;/code&gt; 클래스이다. 그래서 다음 줄은 컴파일링에 실패한다.&lt;/p&gt;
&lt;pre class=&quot;sqf&quot;&gt;&lt;code&gt;// ERROR: Cannot be instantiated
Vehicle myVehicle = Vehicle();&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Vehicle은 추상 클래스이기에, 무조건 Vehicle을 상속받는 클래스, 이를테면 Car, Truck, Bicycle과 같은 클래스로 객체를 생성해야 한다. 이때 상속을 받는 자식 클래스에서는, &lt;code&gt;implements&lt;/code&gt;와 &lt;code&gt;extends&lt;/code&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;/p&gt;
&lt;pre class=&quot;armasm&quot;&gt;&lt;code&gt;// Subclasses can be instantiated
Vehicle myCar = Car();&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Sealed Class의 장점은, switch 문을 간결하게 가져갈 수 있는 점도 장점이다. 여기서 오류를 찾아줄 수 있는 추가 기능도 제공한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음 예시를 보자.&lt;/p&gt;
&lt;pre class=&quot;dart&quot;&gt;&lt;code&gt;String getVehicleSound(Vehicle vehicle) {
  // ERROR: The switch is missing the Bicycle subtype or a default case.
  return switch (vehicle) {
    Car() =&amp;gt; 'vroom',
    Truck() =&amp;gt; 'VROOOOMM',
  };
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 예시는 컴파일에 실패한다. Bicycle에 대해서는 리턴값이 정의되어 있지 않기 때문이다.&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;그러나 다음과 같이 사라진 Bicylce에 대해서 case를 정의해주면, 에러가 사라진다.&lt;/p&gt;
&lt;pre class=&quot;dart&quot;&gt;&lt;code&gt;String getVehicleSound(Vehicle vehicle) {
  return switch (vehicle) {
    Car() =&amp;gt; 'vroom',
    Truck() =&amp;gt; 'VROOOOMM',
    Bicycle() =&amp;gt; 'ring ring', // 정의되지 않은 case를 추가
  };
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약에 위 예시를 &lt;code&gt;sealed&lt;/code&gt; 클래스 없이 사용했다면, if-else 구문을 사용해서 다음과 같이 작성해야 했을 것이다.&lt;/p&gt;
&lt;pre class=&quot;kotlin&quot;&gt;&lt;code&gt;String getVehicleSound(Vehicle vehicle) {
  if (vehicle is Car) {
    return 'vroom';
  } else if (vehicle is Truck) {
    return 'VROOOOMM';
  } else if (vehicle is Bicycle) {
    return 'ring ring';
  } else {
    return 'unknown'; // sealed class가 없다면, 무조건 default else를 정의해야 함
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;sealed class가 있다면 모든 subclass들을 알 수 있다. 그러나 일반 abstract class의 경우 어디에서 새롭게 subclass가 만들어지고 있는 지 알 수 없어서, 무조건 default value를 지정해주어야 한다.&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;code&gt;sealed&lt;/code&gt; 클래스는 다른 dart 파일에서는 상속받지 못한다.&lt;/b&gt; &lt;u&gt;무조건 하나의 dart 파일 내에서만 상속받아야 한다.&lt;/u&gt;&lt;/p&gt;
&lt;pre class=&quot;scala&quot;&gt;&lt;code&gt;// example/base_vehicle.dart

sealed class Vehicle {}

class Car extends Vehicle {} // ok&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;scala&quot;&gt;&lt;code&gt;// truck.dart
import 'example/base_vehicle.dart';

// Error: The class 'Vehicle' can't be extended, implemented, or 
// mixed in outside of its library because it's a sealed class.
class Truck extends Vehicle {}&lt;/code&gt;&lt;/pre&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Sealed class로 상태 관리하기&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;sealed class는 비교적 최근(올해 3월)에 등장한 탓에, 아직까지 Dart 세계의 많은 곳에서 정립되어 사용되고 있지는 않다. 그래도 sealed class를 유용하게 사용할 수 있는 부분 하나는 어느 정도 인정을 받고 사용되고 있는데, 바로 &lt;a href=&quot;https://ko.wikipedia.org/wiki/%EC%83%81%ED%83%9C_%ED%8C%A8%ED%84%B4&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;상태(State)&lt;/a&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;상태 패턴은 이미 Flutter 카테고리 내 많은 글에서 사용했는데, 여기에 sealed class를 지정해서 사용할 수 있다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;예시: UserState 모델 구현&lt;/h3&gt;
&lt;pre class=&quot;scala&quot;&gt;&lt;code&gt;// 주의: import 문들은 모두 생략함

part 'user_model.freezed.dart';
part 'user_model.g.dart';

sealed class UserState {}

class UserError extends UserState {
  final String message;

  UserError({
    required this.message,
  });
}

class UserLoading extends UserState {}

@freezed
class UserModel extends UserState with _$UserModel {
  const factory UserModel({
    required String id,
    required String username,
        required String imageUrl,
  }) = _UserModel;

  factory UserModel.fromJson(Map&amp;lt;String, dynamic&amp;gt; json) =&amp;gt;
      _$UserModelFromJson(json);
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;단순히 &lt;code&gt;abstract&lt;/code&gt; 키워드에서 &lt;code&gt;sealed&lt;/code&gt; 클래스로 바꾼 것인데, 다음 리팩토링이 가능하다.&lt;/p&gt;
&lt;pre class=&quot;crmsh&quot;&gt;&lt;code&gt;final user = ref.read(userMeProvider);

if (user == null) {
  // return ...
}

if (user is UserModel) {
  // return ...
}

if (user is UserError) {
  // return ...
}

return null; // default =&amp;gt; null 반환&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 코드를 다음과 같이 작성할 수 있다:&lt;/p&gt;
&lt;pre class=&quot;dart&quot;&gt;&lt;code&gt;final userState = ref.read(userMeProvider);

return switch (user) {
  UserError() =&amp;gt; logginIn ? null : '/login',
  UserModel() =&amp;gt; switch (logginIn || goState.fullPath == '/splash') {
      true =&amp;gt; '/',
      false =&amp;gt; null,
    },
  null =&amp;gt; logginIn ? null : '/login', // user == null
  _ =&amp;gt; null, // default =&amp;gt; null 반환
};&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;if - else 문에서 생략한 부분을 추가했기 떄문에, 생소하게 느껴질 수 있으나, 결국 위에서 봤던 switch 문과 정확히 동일하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;무엇이 더 보기 편안한가? 취향의 영역이지만 새로 나온 방식 역시 충분히 적용할만 하다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;그 외 use case&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Flutter에서 보통 한 모델에 대해 다음 세 가지 상태는 정의하게 된다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;code&gt;LoadingState&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;SuccessState&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;ErrorState&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 외에도 예를 들어 Data의 상태를 나타내는 데 사용할 수도 있다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;code&gt;TextData&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;ImageData&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;VideoData&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Freezed와 함께 사용하기&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번에 글을 쓰려고 준비하다가 알게 된 건데, freezed와도 함께 사용할 수 있다.&lt;/p&gt;
&lt;pre class=&quot;scala&quot;&gt;&lt;code&gt;part 'home_state.freezed.dart';

@freezed
sealed class HomeState with _$HomeState {
  const factory HomeState.loading() = LoadingState;

  const factory HomeState.loaded(String data) = LoadedState;

  const factory HomeState.error(String message) = ErrorState;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이런 식으로 factory constructor를 이용해서, 각각의 loading, loaded (success), error 상태를 정의하자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;loaded에는 data, error에는 error message(여기서는 둘 다 &lt;code&gt;String&lt;/code&gt; 타입)가 들어있다.&lt;/p&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;final homeStateProvider = StateNotifierProvider&amp;lt;HomeStateNotifier, HomeState&amp;gt;(
  (ref) =&amp;gt; HomeStateNotifier(),
);

class HomeStateNotifier extends StateNotifier&amp;lt;HomeState&amp;gt; {
  HomeStateNotifier() : super(const HomeState.loading()) {
    loadData();
  }

  void loadData() async {
    state = const HomeState.loading();
    try {
      await Future.delayed(const Duration(seconds: 2));
      // 여기에 data fetch logic 작성

      state = const HomeState.loaded('Hello World');
    } catch (e) {
      state = HomeState.error(e.toString());
    }
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Provider는 다음과 같이 작성할 수 있다. &lt;code&gt;loadData&lt;/code&gt; 부분을 보면, &lt;code&gt;HomeState.loading()&lt;/code&gt;과 같이 사용 방법을 확인할 수 있다.&lt;/p&gt;
&lt;pre class=&quot;pf&quot;&gt;&lt;code&gt;String homeStateToString(HomeState state) {
    return switch (state) {
    LoadingState() =&amp;gt; &quot;we are loading&quot;,
    LoadedState() =&amp;gt; &quot;we are loaded&quot;,
    ErrorState() =&amp;gt; &quot;we are error&quot;
  };
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 사용 가능하다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;  References&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;a href=&quot;https://dart.dev/language/class-modifiers&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Class modifiers&lt;/a&gt;, &lt;i&gt;dart.dev&lt;/i&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://medium.com/@aliammariraq/sealed-classes-in-dart-unlocking-powerful-features-d8dba185925f&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Sealed Classes in Dart: Unlocking Powerful Features&lt;/a&gt;, &lt;i&gt;Ali Ammar&lt;/i&gt;&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>Flutter, Dart/Dart</category>
      <author>nx006</author>
      <guid isPermaLink="true">https://nx006.tistory.com/74</guid>
      <comments>https://nx006.tistory.com/74#entry74comment</comments>
      <pubDate>Tue, 31 Oct 2023 16:10:33 +0900</pubDate>
    </item>
    <item>
      <title>[Flask] ImportError: cannot import name '_app_ctx_stack'</title>
      <link>https://nx006.tistory.com/73</link>
      <description>&lt;h1&gt;Flask Dependency 문제 해결&lt;/h1&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;문제&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Kubernetes로 MSA 어플리케이션을 테스트하는 도중, 시작부터 난관에 부딪혔다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;941&quot; data-origin-height=&quot;497&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bq2X7d/btswkmjU6Xt/yRTbFG7MMYfncNVP0mknQk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bq2X7d/btswkmjU6Xt/yRTbFG7MMYfncNVP0mknQk/img.png&quot; data-alt=&quot;K9S&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bq2X7d/btswkmjU6Xt/yRTbFG7MMYfncNVP0mknQk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbq2X7d%2FbtswkmjU6Xt%2FyRTbFG7MMYfncNVP0mknQk%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; alt=&quot;K9S&quot; loading=&quot;lazy&quot; width=&quot;941&quot; height=&quot;497&quot; data-origin-width=&quot;941&quot; data-origin-height=&quot;497&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;K9S&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Pods가 미친듯이 꺼졌다가 켜진다. 처음에는 쿠버네티스 세팅 문제인 줄 알았다. minikube부터 시작해서 k8s yaml 파일들에 문제가 없는 지 계속해서 체크했는데, 별 문제가 없어 보였다. 로그를 분석해보니 Flask 문제였다ㅠㅠ.&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;로그부터 분석하는 건 기본 중에 기본인데 멍충했다...&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;에러 내용은 다음과 같았다:&lt;/p&gt;
&lt;pre class=&quot;sql&quot;&gt;&lt;code&gt;Traceback (most recent call last):
  File &quot;/app/server.py&quot;, line 3, in &amp;lt;module&amp;gt;
    from flask_mysqldb import MySQL
  File &quot;/usr/local/lib/python3.11/site-packages/flask_mysqldb/__init__.py&quot;, line 3, in &amp;lt;module&amp;gt;
    from flask import _app_ctx_stack, current_app
ImportError: cannot import name '_app_ctx_stack' from 'flask' (/usr/local/lib/python3.11/site-packages/flask/__init__.py)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;대놓고서 Flask 에러이다. 근데 &lt;a href=&quot;https://flask-docs-kr.readthedocs.io/ko/latest/appcontext.html&quot;&gt;&lt;code&gt;_app_ctx_stack&lt;/code&gt;&lt;/a&gt;을 flask 모듈에서 찾을 수 없다고 한다. 무슨 문제일까?&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;  &lt;b&gt;_app_ctx_stack은 무엇일까?&lt;/b&gt; django는 Request를 인자로 전달받는 반면, flask는 전역적인 Request를 사용한다. 그로 인해 발생되는 동시성 제어를 관리하기 위해 context가 존재한다. _app_ctx_stack 어플리케이션 컨텍스트 스택으로, db configuration과 같은 정보를 담는다고 한다 [&lt;a href=&quot;https://velog.io/@jihwankim94/Python-Flask-%EC%97%90%EC%84%9C%EC%9D%98-context-%EC%9D%B4%ED%95%B4%ED%95%98%EA%B8%B0&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;1&lt;/a&gt;].&lt;/blockquote&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;해결&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;구글링을 해보니깐, Flask 2.2.0부터 무엇인가가 변경되면서, 외부 패키지 사용에 여러 충돌이 일어나고 있는 듯 하다 [&lt;a title=&quot;2&quot; href=&quot;https://stackoverflow.com/questions/73340344/cannot-use-module-aioflaskpython-importerror-cannot-import-name-app-ctx-st&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;2&lt;/a&gt;]. 그 중 하나가 바로 Python Version에 관한 문제였는데, 특히 나의 경우에 Python3.11 버전을 사용하면서, Flask와 버전 호환성이 맞지 않는 것으로 보인다.&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;requirements.txt:&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;blinker==1.6.2
click==8.1.7
Flask==3.0.0
Flask-MySQLdb==1.0.1
itsdangerous==2.1.2
Jinja2==3.1.2
MarkupSafe==2.1.3
mysqlclient==2.2.0
PyJWT==2.8.0
PyMySQL==1.1.0
Werkzeug==3.0.0&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;dockerfile:&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;stata&quot;&gt;&lt;code&gt;FROM python:3.11-slim-bullseye

RUN apt-get update \
    &amp;amp;&amp;amp; apt-get install -y --no-install-recommends --no-install-suggests \
    build-essential default-libmysqlclient-dev pkg-config \
    &amp;amp;&amp;amp; pip install --no-cache-dir --upgrade pip

WORKDIR /app
COPY ./requirements.txt /app/requirements.txt

RUN pip install --no-cache-dir -r /app/requirements.txt

COPY . /app

EXPOSE 5000

CMD [&quot;python3&quot;, &quot;server.py&quot;]&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위에를 보면, Flask는 3.0.0, Python은 3.11 버전을 사용하고 있다. 버전을 낮추자.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;패키지 버전 낮추기&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Image 생성 시에는 Dockerfile을 3.9로 바꾸어주자.&lt;/p&gt;
&lt;pre class=&quot;dockerfile&quot;&gt;&lt;code&gt;FROM python:3.9-slim-bullseye

# 나머지는 모두 동일&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Flask 버전도 2.0.1로 낮추어준다.&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;Flask==2.0.2
// ...&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 image를 만들어서 올리면 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약에 로컬에서 Python 버전을 낮추려면, virtual environment만 바꾸어주면 된다.&lt;/p&gt;
&lt;pre class=&quot;sql&quot;&gt;&lt;code&gt;$ brew install python@3.9
$ python3.9 -m venv venv
$ source venv/bin/activate
$ pip install --upgrade pip
$ pip install -r requirements.txt&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;결과&lt;/h3&gt;
&lt;pre class=&quot;coq&quot;&gt;&lt;code&gt;$ kubectl apply -f ./&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Kubernetes Pod을 다시 실행하면...&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1103&quot; data-origin-height=&quot;481&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/QaVYE/btsv9I9mk41/w2KQKjakoNVi0PQtlnKlyK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/QaVYE/btsv9I9mk41/w2KQKjakoNVi0PQtlnKlyK/img.png&quot; data-alt=&quot;K9S&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/QaVYE/btsv9I9mk41/w2KQKjakoNVi0PQtlnKlyK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FQaVYE%2Fbtsv9I9mk41%2Fw2KQKjakoNVi0PQtlnKlyK%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; alt=&quot;K9S&quot; loading=&quot;lazy&quot; width=&quot;1103&quot; height=&quot;481&quot; data-origin-width=&quot;1103&quot; data-origin-height=&quot;481&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;K9S&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;문제 없이 작동한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Python 3.11 버전이 현재 나왔긴 한데, 엥간하면 아직까지는 &lt;b&gt;3.9, 3.10 버전 등 좀 안정적인 버전을 사용하자&lt;/b&gt;. Flask나 django를 사용하는 데 있어서 어떤 이상한 에러가 나타날 지 모르기에...&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;참고 자료&lt;/h2&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;a href=&quot;https://velog.io/@jihwankim94/Python-Flask-%EC%97%90%EC%84%9C%EC%9D%98-context-%EC%9D%B4%ED%95%B4%ED%95%98%EA%B8%B0&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;[Python] Flask 에서의 context 이해하기&lt;/a&gt;,&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;i&gt;jihwankim94, velog&lt;/i&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://stackoverflow.com/questions/73340344/cannot-use-module-aioflaskpython-importerror-cannot-import-name-app-ctx-st&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Cannot use module aioflask(Python) ...&lt;/a&gt;,&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;i&gt;stackoverflow&lt;/i&gt;&lt;/li&gt;
&lt;/ol&gt;</description>
      <category>Web</category>
      <category>flask</category>
      <category>에러 해결</category>
      <author>nx006</author>
      <guid isPermaLink="true">https://nx006.tistory.com/73</guid>
      <comments>https://nx006.tistory.com/73#entry73comment</comments>
      <pubDate>Mon, 2 Oct 2023 21:24:14 +0900</pubDate>
    </item>
  </channel>
</rss>